diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3773b563 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Files that a Linux shell runs directly must keep LF line endings in a +# Windows checkout (core.autocrlf): a CRLF shebang line does not start. +*.sh text eol=lf +port/android/gradlew text eol=lf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8506d796..4e086a31 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,8 +8,8 @@ name: Build # - one job per platform builds both configurations, so its set-up (tools, # downloads, caches) is paid once; # - compiles go through ccache, whose cache carries over from run to run, so -# a commit only recompiles what it changed; downloads (SDL, musl, Gradle) -# are cached too; +# a commit only recompiles what it changed; downloads (SDL, musl, Gradle, +# the Linux build's system root) are cached too; # - a newer push to the same branch cancels a run still going; # - pushes that only change documentation do not build; # - Windows, whose minutes cost twice Linux's, builds only Windows; @@ -24,6 +24,11 @@ on: paths-ignore: - "**.md" workflow_dispatch: + inputs: + profile: + description: "Also build the profiling flavour (configure.py --profile) on Linux" + type: boolean + default: false concurrency: group: build-${{ github.ref }} @@ -41,7 +46,11 @@ env: jobs: linux: # Arch Linux has what the build needs as packages: clang 22 (for the - # profile-guided optimisation in pgo/) and 32-bit SDL 3. + # profile-guided optimisation in pgo/), and CMake, pkg-config and + # wayland-scanner for the SDL 3 that the portable build brings. The build + # links against Debian 11's 32-bit glibc (tools/linux_sysroot.py), not + # Arch's, so that it starts on SteamOS and older distributions; Arch's + # 32-bit glibc is for the tests. runs-on: ubuntu-24.04 container: archlinux:latest timeout-minutes: 40 @@ -50,7 +59,7 @@ jobs: run: | printf '\n[multilib]\nInclude = /etc/pacman.d/mirrorlist\n' >> /etc/pacman.conf pacman -Syu --noconfirm --needed git zstd python python-pytest ninja ccache \ - clang lld gcc lib32-glibc lib32-gcc-libs lib32-sdl3 + clang lld gcc lib32-glibc lib32-gcc-libs cmake pkgconf wayland - uses: actions/checkout@v4 @@ -60,15 +69,28 @@ jobs: key: ccache-linux-${{ github.sha }} restore-keys: ccache-linux- + # the system root and SDL's source, which the build downloads + - uses: actions/cache@v4 + with: + path: build/linux/third_party + key: linux-third-party-${{ hashFiles('tools/linux_sysroot.py') }} + - name: Debug run: python tools/ci_build.py linux debug + # (the profiling build, configure.py --profile, which no other step + # compiles: only when a run started by hand asks for it, as it costs a + # build of its own) + - name: Profile + if: github.event_name == 'workflow_dispatch' && inputs.profile + run: python tools/ci_build.py linux profile + - name: Release run: python tools/ci_build.py linux release - name: Test run: | - python -m pytest -q tools/test_linux_port.py tools/harness + python -m pytest -q tools/test_linux_port.py tools/test_host_watch_hash.py tools/test_touch_menu.py tools/harness tools/test_profile.py python tools/test_light_storage.py - name: Compile cache @@ -147,6 +169,10 @@ jobs: HALO_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} run: python3 tools/ci_build.py android release + # the touch controls' layout and gyroscope maths (the runner's JDK) + - name: Test the touch layout + run: python3 tools/test_touch_layout.py + - name: Compile cache run: ccache --show-stats diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..d7078466 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,209 @@ +# Notes for agents + +This repository ports the Halo: Combat Evolved decompilation (Xbox build +01.01.14.2342, `cachebeta.exe`) to Linux, Windows and Android. The game's C +sources are the decompilation; the port adds a platform layer that +implements the Xbox APIs the game calls, and features the Xbox game did not +have (a new netcode, internet play, the PC version's menus, Custom Edition +maps, touch controls). The project is published as OpenCE. + +Start with [README.md](README.md). The other documents are listed at the +end. + +## Layout + +| Path | Contents | +| --- | --- | +| `source/` | The game: 466 C files of the decompilation, compiled as they are with few changes (each marked, below) | +| `port/linux/src/` | The platform layer the three ports share: Direct3D 8 on OpenGL (`d3d8_gl.c`), DirectSound and XInput on SDL3, files, threads, memory, sockets, settings (`port_config.c`), internet play (`p2p*.c`) | +| `port/linux/game/` | The port's game-side code, compiled as the game's sources are: the distributed netcode (`network_*.c`), co-op, the menus (`menu_tags.c`, `menu_functions.c`), Custom Edition maps (`custom_edition_*.c`, `cache_file_formats.c`), the tag validator (`tag_validate.c`, `tag_schema_*.c`), touch, FOV and more | +| `port/linux/include/` | The port's headers: the limits (`halo_port_limits.h`, with `HALO_PORT_NETWORK_VERSION`), capacities (`halo_port_capacity.h`), the prefix header, `halo_linux_source_fixups.h` (port functions the game's sources call) | +| `port/include/xdk/` | Stand-ins for the Xbox SDK's headers (declarations only) | +| `port/windows/` | The Windows build's own files (`win32_*.c`, headers) | +| `port/android/` | The Android app: the guest runtime, the host library (`host/`), the Java app (`app/`), host imports (`host_imports.list`) | +| `port/assets/` | What the builds embed or ship: high-res HUD (`hud/`), fonts, titles, menus (`menus/`), icons, network brokers | +| `port/third_party/` | Vendored libraries, each with a README naming its upstream, version and checksum | +| `port/tools/` | Stand-alone tools (`cache_file_report.c`) | +| `tools/` | Build scripts (`linux_build.py`, `windows_build.py`, `android_build.py`, `ci_build.py`), generators (`ce_menus.py`, `port_settings.py`, `hud_assets.py`, `title_assets.py`, `embed_assets.py`, `xdk_headers.py`) and tests | +| `docs/` | Longer design notes (`custom_edition_caches.md`) | +| `pgo/` | Profile-guided optimisation profiles | +| `assets/` | Game data for local runs (gitignored: `maps/`, `custom_maps/`) | +| `.github/workflows/` | CI: builds every platform on each push, publishes releases from `main` | + +## Building + +```sh +python configure.py # debug build; add --release, --portable, --profile, --pgo=off, --lto=off +ninja linux # build/linux/halo +ninja android # the Android guest image and native libraries +ninja android_apk # the APK (Gradle) +ninja windows # on Windows only +python tools/ci_build.py linux release # what CI builds +``` + +- `configure.py` writes `build.ninja`; `configure_args` at its top records + the options it was run with. Restore them after trying other options. +- The Linux and Windows builds are 32-bit x86 with clang, in MSVC's ABI + (`-fms-extensions -fshort-wchar -malign-double -fcommon`), C89 (`gnu89`): + declarations come before statements. +- Every native build compiles without fused multiply-add and with the + vendored musl maths (`halo_math.h`), so that every machine of a network + game computes the same results. Do not call the system's maths functions. +- Android: the game is an ILP32 AArch64 guest (`arm64_32`) in a 64-bit app. + Guest code can call only the host imports declared in + `port/android/host_imports.list` and `port/android/guest/runtime/guest_host.h`, + and only the SDL functions `guest_sdl.c` bridges. Run `ninja android` + after changing anything it compiles: `port/linux/src`, `port/linux/game`, + `port/android`, `source/`, the build scripts. + +### Defines + +| Define | Set by | Meaning | +| --- | --- | --- | +| `HALO_ANDROID` | Android | The app: its display, input, files and lifecycle | +| `HALO_GLES` | Android | The OpenGL ES renderer | +| `HALO_ARM64_GUEST` | Android | The guest's ABI (ILP32 AArch64) | +| `HALO_WINDOWS` | Windows (`halo_windows_prefix.h`) | The Windows build | +| `HALO_RELEASE` | `--release` | No assertions checked | +| `HALO_PROFILE` | `--profile` | The profiling build; a normal build must be unchanged by it | + +`HALO_LINUX` and `HALO_CUSTOM_EDITION` are **not** defined by any build: +code guarded by them compiles to nothing. Custom Edition support is a +setting (`game.custom_edition`), checked at run time with +`custom_edition_cache_tags_loaded()`. + +## Testing + +```sh +python -m pytest -q tools/harness tools/test_touch_menu.py # the engine's functions in a fake world +python -m pytest -q tools/test_linux_port.py # the build, the maps' checks (needs assets/maps) +python -m pytest -q tools/test_cache_file_formats.py tools/test_bmp_files.py tools/test_profile.py +python tools/test_touch_layout.py # the Android touch layout (JDK 17+) +python tools/test_light_storage.py +python tools/test_death_timing.py +``` + +`tools/harness/README.md` explains how to add an asset-free test: the code +under test is taken from the sources, not copied. + +Running the game without a person at it: + +- `debug.network_test` (`HALO_NETWORK_TEST=host:` or `join`) hosts or + joins a game without the menus and logs each player's state every second; + several copies play on one machine on different loopback addresses + (`HALO_NET_ADDRESS`, `HALO_NET_BROADCAST`). `NETCODE.md` describes the + test settings. +- `debug.exit_after`, `debug.hidden_window`, `debug.null_renderer`, + `debug.menu_open` (start on one menu screen), `debug.telnet_console`. +- Use a virtual display (Xvfb, with `SDL_VIDEO_DRIVER=x11`) for windowed + runs, never the desktop of the person you work for, and stop only the + processes you started. + +## Rules of the project + +### The game's sources + +- Change `source/` as little as possible, and mark each change from the + original with a comment that starts `port:`. Write like the surrounding + code: its naming, its comment density, and the project's plain, + descriptive comment style. +- A port function the game's sources call is declared in + `halo_linux_source_fixups.h` or included with a note naming its file + (`#include "view_fov.h" /* port: port/linux/game/view_fov.c */`). +- Notes such as `BUG (original, preserved for exact matching)` mark the + original's bugs. Fixing one is allowed; say so in a `port:` comment. + +### The Xbox SDK + +`port/include/xdk` was written without the SDK's headers, from the January +build's debug information and public documentation. Never read the SDK's +headers or anything derived from them, never copy from them, and do not +read `xbox/include`. To add a name, follow `port/include/xdk/README.md`. + +### Networking + +- Raise `HALO_PORT_NETWORK_VERSION` (`port/linux/include/halo_port_limits.h`) + with any change to what the machines send each other, or to anything they + must agree on, and say why in the commit. Machines of different versions + do not play together. +- Only the host decides; clients predict their own players. Read + `port/linux/NETCODE.md` before changing the netcode. +- Anything another machine sends is untrusted: check sizes, indices and + rates, as the existing handlers do. `debug.network_corrupt` tests this. + +### Menus + +- The menus must match Halo Custom Edition's. Their XML (`port/assets/menus/ce/`) + is generated by `tools/ce_menus.py` from the PC version's tags; the + settings screens come from `tools/port_settings.py`. Do not edit the XML + by hand: change the generator and run it (the command is in + `port/assets/menus/README.md`, "Writing them again"), then check that + `git status` shows only the screens meant to change. +- Functions the menus call but the port has not wired are listed in + `port/assets/menus/UNWIRED.md`. + +### Art and credit + +- Replacement HUD, title and menu art is made of hand-drawn redraws. +- Prefer an existing redraw (the ui-svg-handmade set), then a new redraw. A + Custom Edition picture that cannot reasonably be redrawn (a 3D render, a + screenshot, a logo) may be used only if it is listed in + `port/assets/menus/NON_HANDDRAWN.md`. +- `AUTHORS.md` credits art only. +- Fonts: Overpass and OpenCE (Newtown, respaced) only; see + `port/assets/fonts/README.md`. Never ship the maps' commercial typefaces. + +### Custom Edition maps + +Custom Edition maps live in the data root's `custom_maps` folder with +`bitmaps.map`, `sounds.map` and `loc.map`; their level names are +`custom_maps\`. OpenSauce features are not supported and must not be +added: a map that needs them is refused, and one that only carries +OpenSauce's header runs as stock Custom Edition runs it. Map files are +untrusted input: read them through bounds-checked readers. See +`docs/custom_edition_caches.md`. + +### Settings + +A setting is an entry in `port/linux/src/port_config.c`, with a default, an +environment variable and a comment that `config.toml` gets. Document it in +the settings table of `port/linux/README.md` (and the Android README for an +Android-only one). A departure from the stock game's look or behaviour +should be a setting, off or stock by default, unless the project decides +otherwise. + +### Third-party code and licences + +The repository is CC0. Vendored code goes in `port/third_party/`, +copied unchanged, with a README naming the upstream, the version or commit, +the checksum and what uses it. GPL code (OpenSauce, Reclaimer) may be read +as documentation of file formats, never copied. + +### Commits and pull requests + +- Commit subjects are plain sentences saying what changed; the body says + why and what was checked. +- A pull request is merged with a merge commit (`Merge pull request #N from + /`), and follow-up changes go in separate commits whose + subject ends with `(#N)`. +- A commit taken from a fork keeps its author and is made with + `git cherry-pick -x`. + +## Documents + +| Document | About | +| --- | --- | +| [README.md](README.md) | Downloads, game data, building, build options, profiling builds | +| [port/linux/README.md](port/linux/README.md) | The Linux build, controls, menus, every setting, updates, frame rate, field of view, system link, internet play, voice chat, the server browser, security, map checks, how the port operates, the game source changes | +| [port/linux/NETCODE.md](port/linux/NETCODE.md) | The distributed netcode, network versions, joining a game in progress, transport, testing | +| [port/windows/README.md](port/windows/README.md) | The Windows build, its headers, inline functions, crash reports | +| [port/android/README.md](port/android/README.md) | The Android build, the guest and host, touch controls, Android settings, finding problems | +| [port/include/xdk/README.md](port/include/xdk/README.md) | How the SDK declarations were written and are checked | +| [docs/custom_edition_caches.md](docs/custom_edition_caches.md) | Loading and converting Custom Edition maps, and the evidence for each layout | +| [port/assets/menus/README.md](port/assets/menus/README.md) | The menu files' format and how to write them again | +| [port/assets/menus/NON_HANDDRAWN.md](port/assets/menus/NON_HANDDRAWN.md) | Menu pictures that are not redraws | +| [port/assets/menus/UNWIRED.md](port/assets/menus/UNWIRED.md) | Menu functions that do nothing yet | +| [port/assets/fonts/README.md](port/assets/fonts/README.md) | The fonts and their licences | +| [tools/harness/README.md](tools/harness/README.md) | Asset-free regression tests | +| `port/third_party/*/README.md` | Each vendored library | +| [AUTHORS.md](AUTHORS.md) | Art credits | diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 00000000..913dd699 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,14 @@ +# Authors + +## Art + +- **The game's icon** (the Android app's launcher icon, `halo.exe`'s icon + and the desktop windows' icon): by ORION (`port/assets/icon`). + +## Third-party art + +- **Voice chat's speaker icons** (who talks, and who is muted, in the lobby, + on the scoreboard and in the game): `volume-2` and `volume-x` from + [Lucide](https://lucide.dev) 1.53.0, copyright (c) 2026 Lucide Icons and + Contributors, under the ISC licence + (`port/assets/icons/lucide/LICENSE`). diff --git a/configure.py b/configure.py index 3bff193b..0a39d0f4 100644 --- a/configure.py +++ b/configure.py @@ -12,7 +12,7 @@ from types import SimpleNamespace from tools import ninja_syntax from tools.android_build import android_configure_inputs, generate_android_build -from tools.linux_build import generate_linux_build, linux_configure_inputs +from tools.linux_build import check_profile_options, generate_linux_build, linux_configure_inputs from tools.windows_build import generate_windows_build, windows_configure_inputs # arguments @@ -33,6 +33,12 @@ parser.add_argument( action="store_true", help="release builds (Linux, Windows, Android): assertions are not checked", ) +parser.add_argument( + "--profile", + action="store_true", + help="profiling builds (Linux, Windows, Android): CPU scopes recorded on a " + "console command or launch setting (README, \"Profiling builds\"); not with --pgo=train", +) parser.add_argument( "--lto", choices=["full", "thin", "off"], @@ -44,7 +50,11 @@ parser.add_argument( "--portable", action="store_true", help="x86 builds (Linux, Windows): code for any x86-64 processor (SSE2) rather than for this " - "machine's (-march=native, the default); use it for builds that run on other computers", + "machine's (-march=native, the default); use it for builds that run on other computers. On Linux it also " + "builds against Debian 11's glibc 2.31 rather than this machine's, and brings its own SDL 3 (libSDL3.so.0, " + "beside the executable), so that it starts on SteamOS and older distributions: the first build downloads " + "the Debian packages and SDL's source (build/linux/third_party, tools/linux_sysroot.py), and needs CMake, " + "pkgconf and wayland-scanner, not the 32-bit SDL 3", ) parser.add_argument( "--pgo", @@ -74,6 +84,10 @@ parser.add_argument( help="clang with the arm64_32 target for the Android guest (default: clang)", ) args = parser.parse_args() +try: + check_profile_options(args.profile, args.pgo) +except ValueError as error: + parser.error(str(error)) # the settings the builds read sln = SimpleNamespace( @@ -81,6 +95,7 @@ sln = SimpleNamespace( linux_cc=args.linux_cc, compiler_launcher=args.compiler_launcher, port_release=args.release, + port_profile=args.profile, port_lto=args.lto, port_portable=args.portable, port_pgo=args.pgo, diff --git a/docs/OPENCE_README.md b/docs/OPENCE_README.md index 961ab856..ab05f51e 100644 --- a/docs/OPENCE_README.md +++ b/docs/OPENCE_README.md @@ -115,10 +115,11 @@ Give these options to `configure.py`: | --- | --- | | (none) | A debug build. A failed assertion stops the game. | | `--release` | A release build. The game does not examine assertions, as in the retail game. | -| `--portable` | The Linux and Windows builds operate on all x86-64 processors. Use this option for builds that you give to other persons. | +| `--portable` | The Linux and Windows builds operate on all x86-64 processors. The Linux build also operates on older distributions and on SteamOS: refer to "Portable build" in [port/linux/README.md](../port/linux/README.md#portable-build). Use this option for builds that you give to other persons. | | `--lto=thin`, `--lto=off` | Less link-time optimization. The link is faster. | | `--pgo=off` | No profile-guided optimization. | | `--pgo=train` | Records a new optimization profile. Refer to "Optimization profiles". | +| `--profile` | A profiling build. It records CPU times. Refer to "Profiling builds". | Without `--portable`, the Linux and Windows builds use all the instructions of the processor that builds them (`-march=native`). Such a build does not @@ -143,3 +144,40 @@ To record a new profile: The build then plays the main menu and the first minute of each campaign level. This procedure continues for approximately 15 minutes. The game data must be in `assets/`. + +### Profiling builds + +A profiling build records where the game spends its time. + +1. Enter `python configure.py --profile`, then build as usual. +2. Start a recording. Enter `profile_record` in the console (or the telnet + console), or set `debug.profile_record = true` in `config.toml`. To + record each game of a session, also set + `debug.profile_record_when = "game"`. +3. Stop the recording. Enter `profile_stop`, load another map, or quit. + +The game writes numbered `.part.json` files under `profiles/` in its data root +and logs their paths. Parts are retained after the recording stops. Logs go to +standard error; a Windows release build without standard error writes them to +`halo.log` next to `halo.exe`. + +`tools/net_report.py` is a helper for developers. Enter +`python tools/net_report.py ` to read a recording +as tables. The command also writes `.summary.txt`, a short text +file. + +| Command | Result | +| --- | --- | +| `profile_record [seconds]` | Starts a recording. With a number of seconds, the recording stops after that time. | +| `profile_stop` | Stops the recording. | + +`debug.profile_memory` sets the memory of a recording in MB (default 256, +from 4 to 1024). A recording has no length limit: +the game writes a part each time the memory is full. Refer to "Settings" in +[port/linux/README.md](../port/linux/README.md). + +On Android, the files are in `/sdcard/Android/data/com.halo.decomp/files/profiles/`. +Enter `adb pull` to copy the recording parts to a computer. + +A profiling build plays with normal builds. It does not use +`--pgo=train`. diff --git a/port/android/README.md b/port/android/README.md index c932af38..533adabe 100644 --- a/port/android/README.md +++ b/port/android/README.md @@ -8,8 +8,9 @@ its Java classes keep the decompilation's package, `com.halo.decomp`). The game shows its graphics with OpenGL ES 3. It plays sound through SDL3 (AAudio). It accepts input from game controllers, for example a PlayStation -5 DualSense on Bluetooth. The app needs Android 9 (API 28) or later. It -operates on 64-bit-only devices, for example the Pixel 9 Pro XL. +5 DualSense on Bluetooth, and from the touchscreen (refer to "Controls"). +The app needs Android 9 (API 28) or later. It operates on 64-bit-only +devices, for example the Pixel 9 Pro XL. The Android build uses the platform layer of the Linux build (`port/linux/src`). Refer to [port/linux/README.md](../linux/README.md). @@ -33,6 +34,36 @@ ninja) and these items: 1.2.5 and SDL 3.4.16 to `build/android/third_party`. Gradle downloads the Android Gradle Plugin. +On Windows, build in WSL2 (Ubuntu 24.04). The Android build does not run +on Windows itself. Enter +`wsl -d Ubuntu -- bash tools/wsl_setup_android.sh` one time: it installs +the tools and the Android SDK and NDK in WSL (apt asks for your +password). Then enter +`wsl -d Ubuntu -- bash tools/wsl_build_android.sh` for each build. The +script copies the repository into WSL (`~/halo-build/src`) with LF line +endings (a Windows checkout has CRLF line endings, which the build's text +tools do not accept), builds there, and puts the APK in +`dist/android/app-debug.apk`. Only the changed files are copied, so the +next build is incremental. Install the APK with the adb of Windows. + +A build that you make yourself has a different signature from the builds +of GitHub Actions. Android installs it only after you remove the app, and +removing the app deletes its data folder (`maps/`, `save/` and +`config.toml`). Thus a change between the builds of GitHub Actions and your +own builds loses the saved games, unless you copy them first (below). +Your own builds all have the same signature (the debug key of WSL, +`~/.android`), so they install over each other and keep the data +(`adb install -r`). + +To keep the saved games before you remove the app, enter +`adb pull /sdcard/Android/data/io.github.cesarus85.ringfall/files/save/u` (the profiles). +After the new install, before the first start of the app, enter +`adb push u /sdcard/Android/data/io.github.cesarus85.ringfall/files/save/u`. The app reads +and overwrites what adb pushes. adb cannot add files to `save/u` or `save/z` once the app made them, or +change files that the app wrote, so push before the first start. adb cannot read +some files that the app writes (the `blam.lst` files of `save/z`), so `adb pull` +of the whole `save` folder stops there. + ## Build and install the app 1. Go to the root folder of the repository. @@ -71,7 +102,9 @@ To install the data from a computer: | Settings | `config.toml` | To make a copy of the saved games, enter -`adb pull /sdcard/Android/data/io.github.cesarus85.ringfall/files/save`. +`adb pull /sdcard/Android/data/io.github.cesarus85.ringfall/files/save/u` (the profiles). +An `adb pull` of the whole `save` folder stops at the `blam.lst` files of +`save/z`, which adb cannot read. ## Controls @@ -97,8 +130,82 @@ with the positions on the Xbox controller: | Create | back | | The controller gets the rumble. The back gesture of Android is the B -button. A Bluetooth or USB keyboard operates as on Linux. The screen does -not accept touch input. +button. A Bluetooth or USB keyboard operates as on Linux. + +The touchscreen operates the menus: tap an item to select it (on a +setting with values, tap its left or right half), tap a button of the key +at the bottom of a screen (for example "B = Back") to push it, and drag to +scroll a list (down or right steps back, up or left steps forward). A drag +stops at the first and the last item, and it does not change a setting's +value. Touches that start in the edge-gesture zones of Android do not tap +or scroll at the sides, and do not scroll at the top and bottom, because the +first swipe from an edge in full screen only shows the system bars. + +A tap during a cinematic that can be skipped skips it, as A does. On the +on-screen keyboard, tap a key to press it, "B =BACK" to cancel and +"A =ENTER" to accept the name. + +### Touch controls + +In a game, the app shows touch controls over the picture. They are a +controller for player 1. With the default buttons of the profile: + +| Control | Function in the game | +| --- | --- | +| stick (lower left) | move | +| swipe on the screen away from the buttons | look | +| Fire | right trigger | +| Grenade | left trigger | +| A / Jump, B / Melee, X / Reload, Y / Weapon | A, B, X, Y | +| Crouch, Zoom | left and right stick clicks | +| Light, Gren. type | white, black | +| Pause, Back | start, back | +| Up, Down, Left, Right | D-pad | + +The names on the buttons follow the profile's "Button layout" (Settings > +Gamepads): with "Swap triggers", the right trigger's button says "Grenade". +The stick moves the player with every "Stick layout", southpaw too. + +A finger that holds a button can also swipe to look, so you can fire and +aim with one thumb. The controls are a finger wide (48 dp) or larger, but +on a small screen of high density they stay apart rather than reach that +size. A short tap reaches the game even when it is shorter than one frame. +Swipes that start in the edge-gesture zones of Android do not turn the +view. The swipe aims as the controller's stick does, not as a mouse: the +aim slows over a target and follows a moving one, as with a controller, +and it follows the profile's "invert look" (the gyroscope turns the view +as the phone turns, never inverted). The setting `input.touch_aim_assist` +turns the aim assist off. "Look sensitivity" sets how far a swipe turns; +the mouse settings do not apply. + +The touch controls show only in a game. In the menus and during +cinematics they hide, and the touchscreen operates the menus as described +above. They also hide when a controller is connected, for example the +built-in controller of a handheld. A device without a touchscreen (a TV) +never shows them. The setting `input.touch_controls` changes this (refer +to "Settings"). + +The buttons at the top of the screen: + +- "Hide" removes the controls (and stops the gyroscope aiming and the + vibration) until you push "Touch"; the app remembers it. +- "Options" opens these items: + - "General": the phone's vibration (on by default; it follows the + vibration setting of the game's profile), aiming with the gyroscope + (off by default), a floating move stick (off by default: the stick goes + where your thumb lands in the lower left of the screen), the opacity of + the controls, "Hide or add buttons" (hide a button, add a copy of a + button) and "Edit buttons size". + - "Edit buttons layout": drag the controls to new positions, then push + "Save". "Export" and "Import" write and read a layout file with the + file picker of the system. + - "Look sensitivity". + +"Hide" and "Options" act when the finger lifts on them: a swipe that +starts on them turns the view instead. + +The app keeps the layout in its own preferences, not in `config.toml`. +Removing the app's data or the app removes the layout. ## Settings @@ -118,8 +225,11 @@ These settings are only for Android: | Setting | Function | | --- | --- | +| `input.touch_aim_assist` | `true` (the default): the touch controls' swipe aiming gets the aim assist of a controller (the aim slows over a target and follows a moving one). `false`: none, as with a mouse; the bullets' own autoaim stays. | +| `input.touch_controls` | The touch controls in a game. `"auto"` (the default): shown on a touchscreen while no controller is connected. `"on"`: also shown with a controller. `"off"`: never shown. A device without a touchscreen never shows them. The menus take taps in any case. | | `display.screen_width` | The number of columns of the 480-line picture. `0` (the default): the shape of the display (1068 on a 20:9 phone). `640`: the 4:3 shape of the Xbox. | | `debug.sample_seconds` | Refer to "Find problems". | +| `debug.memory_watch` | `true` (the default): the app notices the game's writes to textures and vertices by page protection. `false`: it compares page contents once a frame instead, which is slower. Refer to "Limits". | ## Internet play @@ -170,7 +280,7 @@ key when `port/android/signing.properties` (not in the repository) names it: Keep the keystore and its password safe: a lost key means every player must remove the app before the next version. If you installed a build that has a different signature, remove that build before you install a new build. Removing the app deletes its data -folder: first make a copy of `maps/` and `save/`. +folder: first make a copy of `maps/` and `save/u`. ## Widescreen @@ -184,6 +294,8 @@ the Xbox: - Black bars and fades cover all of the screen, and so do the menus' dims and backgrounds (the pause menu's dim, dialogs, the menus' gradient). +All the ports draw this way. + ## VR On a VR headset with an OpenXR runtime (Meta Quest 3 and 3S; the Steam @@ -253,12 +365,6 @@ Not done yet: aiming with the controller and the weapon in the hand, the HUD's parts on the controllers, zoom through the scope, a first-person view in vehicles, and a separate depth for the HUD's reticle. -## How it works - -The changes are in `#ifdef HALO_ANDROID` in `rasterizer_xbox.c`, `render.c`, -`ui_widget.c`, `cinematics.c`, `main.c` and -`rasterizer_xbox_screen_effect.c`. - ## How the port operates ### ILP32 code @@ -362,9 +468,17 @@ floating-point contraction, as on x86. ### Game source changes +The port's code tells three things apart, each its own macro, which the +Android build defines all of (`tools/android_build.py`): + +- `HALO_ARM64_GUEST`: the guest's ABI (ILP32 AArch64 code in a 64-bit + process); +- `HALO_GLES`: the OpenGL ES renderer; +- `HALO_ANDROID`: the app (its display, input, files and lifecycle). + The x86 inline assembly is replaced by C (refer to [port/linux/README.md](../linux/README.md#game-source-changes)). -These changes are in `#ifdef HALO_ANDROID`: +These changes are in `#ifdef HALO_ARM64_GUEST`: - Seven `#pragma bss_seg(".bss")` lines are removed. The Darwin target does not accept them. @@ -391,6 +505,8 @@ assembly of the port is necessary: at this interval. This finds hangs on devices without root access. - Set `gl_debug = true` in `[debug]` of `config.toml`. The log then shows the OpenGL ES errors. +- The log says which write tracking the app uses: "page protection" + (devices) or "page hashes" (the emulator, or `debug.memory_watch = false`). ## Limits @@ -398,6 +514,16 @@ assembly of the port is necessary: - The device must let the app reserve the fixed guest addresses, from `0x80000000` to approximately `0x89000000`. If the addresses are not available, the app shows a message. -- The game does not accept touch input. Use a controller or a keyboard. +- Touch operates the menus and skips cinematics. In the game, the touch + controls appear when no controller is connected; their size follows the + height of the screen (larger on a tablet than on a phone), never smaller + than a finger. - Kernels with 16 KB pages (a developer option of Android 15) do not operate. The Xbox memory uses 4 KB pages. +- The x86 Android emulator runs the app through its ARM translation. The + translation cannot deliver the page faults that the renderer uses to + notice changed textures and vertices. On the emulator the app compares + page contents once a frame instead ("write tracking: page hashes" in the + log). This is slower, and a write shows one frame later, so moving + geometry can glitch briefly. `debug.memory_watch = false` does the same + on a device. diff --git a/port/android/VR_ROADMAP.md b/port/android/VR_ROADMAP.md index 7ce5345f..a36ba564 100644 --- a/port/android/VR_ROADMAP.md +++ b/port/android/VR_ROADMAP.md @@ -23,7 +23,7 @@ named where code is taken over. | Vehicles | The Warthog's wheel turned with the hands, right trigger throttle, left brake; first-person seats; turrets with handles | | Comfort | Snap or smooth turning, a comfort vignette in vehicles, VR options in the main and pause menus (Y), a page of every controller button | | Menus | The PC version's menus in VR (`vr.menus`), clicked with a laser; text typed on the on-screen keyboard | -| Multiplayer | Up to date with OpenCE (network version 24, 2026-10-08): cross-play, its server browser, co-op campaign, password lobbies; join codes and the VR online list | +| Multiplayer | Up to date with OpenCE (network version 25, 2026-10-10): cross-play, its server browser, co-op campaign, password lobbies; join codes and the VR online list | | Bots | Bots in multiplayer: on foot (paths, items, objectives), in vehicles and at turrets; they make room for a joining person | | Controls | Left-handed mode (`vr.handedness`): the weapon in the left hand, the arms and grips mirrored, sticks and buttons mirrored on both controllers (`vr.mirror_controls`), the assault rifle's counter read the right way round (2026-10-06) | | Graphics | Presets low / medium / high / max (`vr.graphics`), refresh rate and resolution in the VR options, all kept over a restart (2026-10-06) | diff --git a/port/android/app/src/main/AndroidManifest.xml b/port/android/app/src/main/AndroidManifest.xml index 5bea5f71..5fbdc8af 100644 --- a/port/android/app/src/main/AndroidManifest.xml +++ b/port/android/app/src/main/AndroidManifest.xml @@ -19,9 +19,15 @@ + + + + android:hardwareAccelerated="true" + android:enableOnBackInvokedCallback="false"> diff --git a/port/android/app/src/main/java/com/halo/decomp/GyroscopeAim.java b/port/android/app/src/main/java/com/halo/decomp/GyroscopeAim.java new file mode 100644 index 00000000..9c2bdb72 --- /dev/null +++ b/port/android/app/src/main/java/com/halo/decomp/GyroscopeAim.java @@ -0,0 +1,71 @@ +package com.halo.decomp; + +/** + * Gyroscope aiming (TouchControls): integrates the sensor's angular velocity, + * turned into the display's coordinates for its rotation, into radians of + * view turn between samples. + */ +final class GyroscopeAim { + /** below this (radians a second) a reading is the sensor's noise at rest, which must not drift the view */ + private static final float NOISE = 0.01f; + /** a gap longer than this (seconds) starts over rather than turn at once */ + private static final float LONGEST_GAP = 0.1f; + + private long previousTime; + private int previousRotation = -1; + private float previousX; + private float previousY; + + void reset() { + previousTime = 0; + previousRotation = -1; + } + + /** + * takes one sample (its time in nanoseconds, the angular velocity about + * the device's x and y axes, the display's rotation 0..3) and puts the turn + * since the last into delta {yaw, pitch}; false while there is none + */ + boolean sample(long time, float x, float y, int rotation, float[] delta) { + delta[0] = 0; + delta[1] = 0; + if (!Float.isFinite(x) || !Float.isFinite(y) || rotation < 0 || rotation > 3) { + reset(); + return false; + } + float screenX = x; + float screenY = y; + switch (rotation) { + case 1: + screenX = y; + screenY = -x; + break; + case 2: + screenX = -x; + screenY = -y; + break; + case 3: + screenX = -y; + screenY = x; + break; + default: + break; + } + if (Math.abs(screenX) < NOISE) + screenX = 0; + if (Math.abs(screenY) < NOISE) + screenY = 0; + float seconds = (time - previousTime) * 1e-9f; + boolean valid = previousTime != 0 && rotation == previousRotation && seconds > 0 && seconds <= LONGEST_GAP; + if (valid) { + // the trapezoid rule over the two samples + delta[0] = -(screenY + previousY) * 0.5f * seconds; + delta[1] = -(screenX + previousX) * 0.5f * seconds; + } + previousTime = time; + previousRotation = rotation; + previousX = screenX; + previousY = screenY; + return valid && (delta[0] != 0 || delta[1] != 0); + } +} diff --git a/port/android/app/src/main/java/com/halo/decomp/HaloActivity.java b/port/android/app/src/main/java/com/halo/decomp/HaloActivity.java index 3e4497df..84cc3023 100644 --- a/port/android/app/src/main/java/com/halo/decomp/HaloActivity.java +++ b/port/android/app/src/main/java/com/halo/decomp/HaloActivity.java @@ -1,10 +1,25 @@ package com.halo.decomp; +import android.app.AlertDialog; import android.content.Context; +import android.content.Intent; +import android.graphics.Insets; +import android.net.Uri; import android.net.wifi.WifiManager; +import android.os.Build; import android.os.Bundle; import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; +import android.widget.Toast; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import org.libsdl.app.SDLActivity; @@ -15,6 +30,16 @@ import org.libsdl.app.SDLActivity; public class HaloActivity extends SDLActivity { /** lets system link's broadcasts in over Wi-Fi while the game runs */ private WifiManager.MulticastLock multicastLock; + /** + * the latest system gesture insets {left, top, right, bottom}, in pixels; + * written on the UI thread, read by the game's thread: replaced as a + * whole, never changed in place + */ + private volatile int[] gestureInsets = new int[] { 0, 0, 0, 0 }; + /** the on-screen touch controls, over SDL's surface */ + private TouchControls touchControls; + private static final int EXPORT_LAYOUT = 401, IMPORT_LAYOUT = 402; + private String pendingLayoutExport; @Override protected String[] getLibraries() { @@ -24,21 +49,208 @@ public class HaloActivity extends SDLActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (savedInstanceState != null) + pendingLayoutExport = savedInstanceState.getString("pending-layout-export"); + if (mLayout != null) { + touchControls = new TouchControls(this); + mLayout.addView(touchControls, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); preferHighestRefreshRate(); + trackGestureInsets(); acquireMulticastLock(); // a new version looked for while the game starts Updater.start(this); } + /** the system's file picker, for the touch layout; no storage permission */ + public void chooseLayoutFile(boolean export, String configuration) { + String message = export + ? "Choose the folder and the name of the touch layout file." + : "Choose a touch layout file. It replaces your buttons, the look sensitivity and the General settings."; + new AlertDialog.Builder(this) + .setTitle(export ? "Export the layout" : "Import a layout") + .setMessage(message) + .setNegativeButton("Cancel", null) + .setPositiveButton(export ? "Choose the place" : "Choose the file", (dialog, which) -> { + Intent intent = new Intent(export ? Intent.ACTION_CREATE_DOCUMENT : Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(export ? "text/plain" : "*/*"); + if (export) { + pendingLayoutExport = configuration; + intent.putExtra(Intent.EXTRA_TITLE, "halo-touch-layout.halolayout"); + } + try { + startActivityForResult(intent, export ? EXPORT_LAYOUT : IMPORT_LAYOUT); + } catch (android.content.ActivityNotFoundException e) { + pendingLayoutExport = null; + Toast.makeText(this, "This device has no file picker.", Toast.LENGTH_LONG).show(); + } + }) + .show(); + } + + @Override + protected void onSaveInstanceState(Bundle state) { + super.onSaveInstanceState(state); + state.putString("pending-layout-export", pendingLayoutExport); + } + + @Override + protected void onActivityResult(int request, int result, Intent data) { + if (request != EXPORT_LAYOUT && request != IMPORT_LAYOUT) { + super.onActivityResult(request, result, data); + return; + } + String exported = pendingLayoutExport; + pendingLayoutExport = null; + if (result != RESULT_OK || data == null || data.getData() == null) + return; + Uri document = data.getData(); + // (the file is read and written away from the UI thread) + new Thread(() -> { + try { + if (request == EXPORT_LAYOUT) + writeLayoutFile(document, exported); + else + readLayoutFile(document); + } catch (Exception e) { + runOnUiThread(() -> layoutFileError(e)); + } + }, "halo-touch-layout-file").start(); + } + + private void writeLayoutFile(Uri document, String configuration) throws IOException { + if (configuration == null) + throw new IOException("the layout to export is gone"); + try (OutputStream output = getContentResolver().openOutputStream(document, "wt")) { + if (output == null) + throw new IOException("the file cannot be opened"); + output.write(configuration.getBytes(StandardCharsets.UTF_8)); + } + runOnUiThread(() -> Toast.makeText(this, "The layout is exported.", Toast.LENGTH_SHORT).show()); + } + + private void readLayoutFile(Uri document) throws IOException { + ByteArrayOutputStream contents = new ByteArrayOutputStream(); + try (InputStream input = getContentResolver().openInputStream(document)) { + if (input == null) + throw new IOException("the file cannot be opened"); + byte[] buffer = new byte[4096]; + int size; + while ((size = input.read(buffer)) != -1) { + if (contents.size() + size > 65536) + throw new IOException("the file is too large"); + contents.write(buffer, 0, size); + } + } + String configuration = new String(contents.toByteArray(), StandardCharsets.UTF_8); + // checked here, then applied to the overlay at once + TouchLayout.importConfiguration(configuration); + runOnUiThread(() -> { + if (isFinishing() || isDestroyed() || touchControls == null) + return; + try { + touchControls.importLayout(configuration); + Toast.makeText(this, "The layout is imported and saved.", Toast.LENGTH_SHORT).show(); + } catch (IllegalArgumentException e) { + layoutFileError(e); + } + }); + } + + private void layoutFileError(Exception error) { + if (isFinishing() || isDestroyed()) + return; + new AlertDialog.Builder(this) + .setTitle("Touch layout file") + .setMessage("The operation did not complete: " + error.getMessage()) + .setPositiveButton("OK", null) + .show(); + } + + @Override + protected void onResume() { + super.onResume(); + if (touchControls != null && getWindow().getDecorView().hasWindowFocus()) + touchControls.startDeviceInput(); + } + + @Override + protected void onPause() { + if (touchControls != null) + touchControls.stopDeviceInput(); + super.onPause(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + if (touchControls != null) { + if (hasFocus) + touchControls.startDeviceInput(); + else + touchControls.stopDeviceInput(); + } + super.onWindowFocusChanged(hasFocus); + } + @Override protected void onDestroy() { + if (touchControls != null) + touchControls.stopDeviceInput(); if (multicastLock != null && multicastLock.isHeld()) multicastLock.release(); multicastLock = null; super.onDestroy(); } + /** + * keeps gestureInsets current; Android sends the insets again when the + * activity rotates (after the game's native code has started on a phone + * launched from portrait), so a single read at startup would keep the + * portrait values; the listener hands the insets on so SDL's own + * handling still sees them + */ + private void trackGestureInsets() { + getWindow().getDecorView().setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { + @Override + public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { + gestureInsets = readGestureInsets(insets); + return view.onApplyWindowInsets(insets); + } + }); + } + + @SuppressWarnings("deprecation") // getSystemGestureInsets is the only call on Android 10 + private static int[] readGestureInsets(WindowInsets insets) { + Insets gesture; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + gesture = insets.getInsets(WindowInsets.Type.systemGestures()); + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + gesture = insets.getSystemGestureInsets(); + else + return new int[] { 0, 0, 0, 0 }; + return new int[] { gesture.left, gesture.top, gesture.right, gesture.bottom }; + } + + /** + * the edges of the screen where Android keeps its gestures; in sticky + * full screen the first swipe from an edge only shows the system bars, + * and Android hands that swipe to the game as an ordinary finger, so + * the game (port/linux/src/touch_input.c) must ignore touches that + * begin there; returns a copy of {left, top, right, bottom} in pixels: + * all 0 before Android 10 (which has no insets) and until the first + * insets arrive. The game's native code calls this through JNI by name + * and signature (host_main.c GetMethodID(..., + * "getSystemGestureInsetsPixels", "()[I")), at every finger down, so + * it must not be renamed, retyped or removed as unused + */ + public int[] getSystemGestureInsetsPixels() { + return gestureInsets.clone(); + } + /** * Many phones drop the Wi-Fi's broadcast and multicast datagrams to * save power unless an app holds this: without it they would not see diff --git a/port/android/app/src/main/java/com/halo/decomp/TouchControls.java b/port/android/app/src/main/java/com/halo/decomp/TouchControls.java new file mode 100644 index 00000000..329e1208 --- /dev/null +++ b/port/android/app/src/main/java/com/halo/decomp/TouchControls.java @@ -0,0 +1,1255 @@ +package com.halo.decomp; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.input.InputManager; +import android.media.AudioAttributes; +import android.os.Build; +import android.os.SystemClock; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.os.VibratorManager; +import android.util.SparseArray; +import android.util.SparseIntArray; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.SeekBar; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.Arrays; +import java.util.Locale; + +/** + * The on-screen touch controls: a multitouch overlay over SDL's surface, + * with a stick, the controller's buttons, swipe (and optionally gyroscope) + * aiming and an editor for the layout (TouchLayout). Its state goes to the + * game through port/android/host/host_touch.c, as port 0's controller. + * + * It shows only in a game, never at the main menu, over a menu (which + * takes taps itself, port/linux/src/touch_input.c) or during a cinematic, + * only on a touchscreen, and with input.touch_controls = "auto" only while + * no controller is connected. While it is not shown it lets every finger + * through to the game. + */ +public final class TouchControls extends View implements SensorEventListener, InputManager.InputDeviceListener { + /** what a finger holds besides a control: the view swipe */ + private static final int LOOK = -5; + private static final int NOTHING = Integer.MIN_VALUE; + /** the toolbar's items (at the top of the screen) */ + private static final int TOOLBAR_TOGGLE = -3; + private static final int TOOLBAR_OPTIONS = -4; + private static final int TOOLBAR_EXPORT = -6; + private static final int TOOLBAR_IMPORT = -7; + private static final float TOOLBAR_RADIUS = 28; + private static final float OPTIONS_RADIUS = 34; + + /** the logical grid the controls are laid out on, scaled to the display's height */ + private static final float GRID_WIDTH = 960; + private static final float GRID_HEIGHT = 540; + /** a control is never smaller than a finger: 24 dp of radius, 48 across */ + private static final float MINIMUM_RADIUS_DP = 24; + /** a finger moves this far (logical units) before a press becomes a swipe */ + private static final float SLOP = 10; + /** the move stick's dead zone and its touch area beyond its circle */ + private static final float STICK_DEAD_ZONE = 0.12f; + private static final float STICK_REACH = 1.28f; + private static final float STICK_KNOB_RADIUS = 24; + /** a floating stick goes where a thumb lands in this part of the display (from the lower left) */ + private static final float FLOATING_ZONE_WIDTH = 0.45f; + private static final float FLOATING_ZONE_TOP = 0.35f; + /** radians per logical unit of swipe (the mouse's rate: xinput_sdl.c) */ + private static final float LOOK_RADIANS_PER_UNIT = 0.0022f; + /** a label is at most this many radii wide */ + private static final float LABEL_WIDTH = 1.7f; + private static final long POLL_MS = 16; + /** the game is in play this long before the controls show (no flash at a level's first frame) */ + private static final long SHOW_DELAY_MS = 200; + private static final long VIBRATION_MS = 110; + private static final long VIBRATION_RENEW_MS = 70; + + /** the game's HALO_TOUCH_SCENE bits (port/linux/src/touch_input.c) */ + private static final int SCENE_KNOWN = 1; + private static final int SCENE_MENUS = 2; + private static final int SCENE_ON = 4; + private static final int SCENE_OFF = 8; + + private static final String PREFERENCES = "touch-layout-v1"; + private static final String KEY_CONFIGURATION = "configuration"; + private static final String KEY_HIDDEN = "hidden"; + + /** + * a kind of control: the SDL button bit or trigger axis it sends (-1 for + * none), and the game's controller button it is (port/linux/game/ + * touch_game.c numbers them: input.h's _gamepad_*), which names it + */ + private static final class Kind { + final int bit; + final int trigger; + final int gamepadButton; + final String buttonName; + final boolean faceButton; + + Kind(int bit, int trigger, int gamepadButton, String buttonName, boolean faceButton) { + this.bit = bit; + this.trigger = trigger; + this.gamepadButton = gamepadButton; + this.buttonName = buttonName; + this.faceButton = faceButton; + } + } + + /** by TouchLayout's kinds; bits follow SDL_GamepadButton, triggers are SDL axes 4 and 5 */ + private static final Kind[] KINDS = { + new Kind(0, -1, 0, "A", true), + new Kind(1, -1, 1, "B", true), + new Kind(2, -1, 2, "X", true), + new Kind(3, -1, 3, "Y", true), + new Kind(-1, 5, 7, "RT", false), + new Kind(-1, 4, 6, "LT", false), + new Kind(7, -1, 14, "LS", false), + new Kind(8, -1, 15, "RS", false), + new Kind(9, -1, 5, "White", false), + new Kind(10, -1, 4, "Black", false), + new Kind(6, -1, 12, "Start", false), + new Kind(4, -1, 13, "Back", false), + new Kind(11, -1, 8, "Up", false), + new Kind(12, -1, 9, "Down", false), + new Kind(13, -1, 10, "Left", false), + new Kind(14, -1, 11, "Right", false), + new Kind(-1, -1, -1, "Move", false), + new Kind(-1, 5, 7, "RT", false), + }; + + /** the game's controls (input_abstraction.c's _game_control_*), as the buttons name them */ + private static final String[] GAME_CONTROLS = { + "Jump", "Gren. type", "Reload", "Weapon", "Melee", "Light", + "Grenade", "Fire", "Pause", "Back", "Crouch", "Zoom", + }; + /** the default profile's mapping, until the game sends the player's own */ + private static final int[] DEFAULT_BINDINGS = { + 0, 4, 2, 3, 1, 5, 6, 7, -1, -1, -1, -1, 8, 9, 10, 11, + }; + + private static native void nativeState(int lx, int ly, int rx, int ry, int lt, int rt, int buttons); + private static native void nativeLook(float dx, float dy); + private static native void nativeGyro(float dx, float dy); + private static native void nativeLookReset(); + private static native int nativeRumble(); + private static native int nativeScene(); + private static native int nativeBindings(int[] controls); + + private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final SharedPreferences preferences; + private final SensorManager sensors; + private final Sensor gyroscope; + private final Vibrator vibrator; + private final InputManager inputManager; + private final boolean touchscreenFeature; + private final GyroscopeAim gyroAim = new GyroscopeAim(); + private final float[] gyroDelta = new float[2]; + private final AudioAttributes rumbleAttributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_GAME) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build(); + + private TouchLayout layout = new TouchLayout(); + private float sensitivity = 1f; + /** the controls removed by the toolbar's Hide, until its Touch */ + private boolean hidden; + private boolean editing; + /** the option dialogs open (one opens before the last one's dismissal is told) */ + private int openDialogs; + + /** what each finger holds: a control, LOOK, or (in toolbarFingers) a toolbar item */ + private final SparseIntArray owners = new SparseIntArray(); + /** where each finger on a button went down, in pixels: dragged, it swipes */ + private final SparseArray buttonTouches = new SparseArray<>(); + /** each finger on a toolbar item: {x, y, item}; the item acts when it lifts there */ + private final SparseArray toolbarFingers = new SparseArray<>(); + private final int[] axes = new int[6]; + private int lookPointer = -1; + private float lookX; + private float lookY; + /** a floating stick's centre while a thumb holds it (logical units) */ + private boolean stickFloating; + private float stickX; + private float stickY; + private int dragPointer = -1; + private int dragControl = -1; + private float dragOffsetX; + private float dragOffsetY; + + /** pixels per logical unit, and the display in logical units */ + private float scale = 1; + private float logicalWidth = GRID_WIDTH; + private float logicalHeight = GRID_HEIGHT; + private int insetRight; + private int insetTop; + + private int scene; + private boolean controllerConnected; + private boolean touchscreen; + private boolean deviceInputActive; + /** the controls are on screen: shown() for SHOW_DELAY_MS */ + private boolean showing; + private long shownSince = -1; + private boolean gyroRegistered; + private int lastAmplitude; + private long lastVibration; + private final int[] bindings = DEFAULT_BINDINGS.clone(); + private final int[] bindingsRead = new int[bindings.length]; + private int bindingsSerial; + + /** reads what the game says every frame: when to show, the buttons' names, and the rumble */ + private final Runnable poll = new Runnable() { + @Override + public void run() { + if (!deviceInputActive) + return; + scene = nativeScene(); + updateShowing(); + readBindings(); + updateRumble(); + postDelayed(this, POLL_MS); + } + }; + + public TouchControls(Context context) { + super(context); + sensors = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + gyroscope = sensors == null ? null : sensors.getDefaultSensor(Sensor.TYPE_GYROSCOPE); + if (Build.VERSION.SDK_INT >= 31) { + VibratorManager manager = (VibratorManager) context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE); + vibrator = manager == null ? null : manager.getDefaultVibrator(); + } else { + vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + } + inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + touchscreenFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); + preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE); + loadLayout(context); + hidden = preferences.getBoolean(KEY_HIDDEN, false); + refreshInputDevices(); + setFocusable(false); + setContentDescription("Halo touch controller"); + setOnApplyWindowInsetsListener((view, insets) -> { + // (only the toolbar moves: the fingers stay held as the system bars come and go) + int right = insetRight; + int top = insetTop; + insetRight = insets.getSystemWindowInsetRight(); + insetTop = insets.getSystemWindowInsetTop(); + if (insets.getDisplayCutout() != null) { + insetRight = Math.max(insetRight, insets.getDisplayCutout().getSafeInsetRight()); + insetTop = Math.max(insetTop, insets.getDisplayCutout().getSafeInsetTop()); + } + if (insetRight != right || insetTop != top) + invalidate(); + return insets; + }); + } + + private void loadLayout(Context context) { + String configuration = preferences.getString(KEY_CONFIGURATION, null); + if (configuration == null) + return; + try { + TouchLayout.Configuration saved = TouchLayout.importConfiguration(configuration); + layout = saved.layout; + sensitivity = saved.sensitivity; + } catch (IllegalArgumentException e) { + Toast.makeText(context, "The saved touch layout could not be read. Using the default one.", + Toast.LENGTH_LONG).show(); + } + } + + // ---------- the activity's lifecycle (HaloActivity) + + public void startDeviceInput() { + if (deviceInputActive) + return; + deviceInputActive = true; + if (inputManager != null) + inputManager.registerInputDeviceListener(this, null); + refreshInputDevices(); + gyroAim.reset(); + updateSensors(); + post(poll); + } + + public void stopDeviceInput() { + deviceInputActive = false; + removeCallbacks(poll); + if (inputManager != null) + inputManager.unregisterInputDeviceListener(this); + updateSensors(); + cancelRumble(); + reset(); + } + + // ---------- the display + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + layoutControls(); + requestApplyInsets(); + } + + /** scales the grid to the display (its height, keeping the circles round) */ + private void layoutControls() { + if (getWidth() <= 0 || getHeight() <= 0) + return; + reset(); + float width = getWidth(); + float height = getHeight(); + scale = Math.max(0.01f, Math.min(width / GRID_WIDTH, height / GRID_HEIGHT)); + logicalWidth = width / scale; + logicalHeight = height / scale; + layout.bounds(logicalWidth, logicalHeight); + layout.setMinimumRadius(minimumRadius()); + invalidate(); + } + + /** a finger's width, in logical units (at most TouchLayout.MAX_MINIMUM_RADIUS) */ + private float minimumRadius() { + float radius = MINIMUM_RADIUS_DP * getResources().getDisplayMetrics().density / scale; + return Math.min(radius, TouchLayout.MAX_MINIMUM_RADIUS); + } + + private float logical(float pixels) { + return pixels / scale; + } + + private float toolbarY() { + return Math.max(38, insetTop / scale + 38); + } + + private float optionsX() { + return logicalWidth - Math.max(42, insetRight / scale + 42); + } + + private float toolbarRadius(float radius) { + return Math.max(radius, minimumRadius()); + } + + // ---------- when the controls show + + /** + * whether the controls belong on screen: in a game (no menu or + * cinematic), never without a touchscreen, and with + * input.touch_controls = "auto" only while no controller is connected + */ + private boolean shown() { + if ((scene & SCENE_KNOWN) == 0 || (scene & (SCENE_MENUS | SCENE_OFF)) != 0 || !touchscreen) + return false; + return (scene & SCENE_ON) != 0 || !controllerConnected; + } + + /** shows the controls once shown() has held for SHOW_DELAY_MS; hides them at once */ + private void updateShowing() { + boolean show = false; + if (!shown()) { + shownSince = -1; + } else { + long now = SystemClock.uptimeMillis(); + if (shownSince < 0) + shownSince = now; + show = now - shownSince >= SHOW_DELAY_MS; + } + if (show != showing) { + showing = show; + reset(); + updateSensors(); + } + } + + /** a game controller: a stick or a hat, not a phone's few gamepad keys */ + private static boolean isController(InputDevice device) { + if (device == null || device.isVirtual()) + return false; + if ((device.getSources() & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) + return false; + return device.getMotionRange(MotionEvent.AXIS_X, InputDevice.SOURCE_JOYSTICK) != null + || device.getMotionRange(MotionEvent.AXIS_HAT_X, InputDevice.SOURCE_JOYSTICK) != null; + } + + private void refreshInputDevices() { + boolean controller = false; + boolean touch = touchscreenFeature; + for (int id : InputDevice.getDeviceIds()) { + InputDevice device = InputDevice.getDevice(id); + if (device == null) + continue; + if (isController(device)) + controller = true; + boolean touchSource = (device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN; + if (!device.isVirtual() && touchSource) + touch = true; + } + controllerConnected = controller; + touchscreen = touch; + updateShowing(); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + refreshInputDevices(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + refreshInputDevices(); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + refreshInputDevices(); + } + + private void setHidden(boolean hidden) { + reset(); + this.hidden = hidden; + preferences.edit().putBoolean(KEY_HIDDEN, hidden).apply(); + updateSensors(); + } + + // ---------- the controller state the game reads + + /** lets go of every finger and sends a released controller */ + public void reset() { + dragPointer = -1; + dragControl = -1; + owners.clear(); + buttonTouches.clear(); + toolbarFingers.clear(); + lookPointer = -1; + stickFloating = false; + gyroAim.reset(); + nativeLookReset(); + Arrays.fill(axes, 0); + publish(); + invalidate(); + } + + private void publish() { + if (editing || openDialogs > 0) { + nativeState(0, 0, 0, 0, 0, 0, 0); + return; + } + int bits = 0; + axes[4] = 0; + axes[5] = 0; + for (int i = 0; i < owners.size(); i++) { + int control = owners.valueAt(i); + if (control < 0 || control >= layout.size()) + continue; + Kind kind = KINDS[layout.type(control)]; + if (kind.bit >= 0) + bits |= 1 << kind.bit; + if (kind.trigger >= 0) + axes[kind.trigger] = 32767; + } + nativeState(axes[0], axes[1], axes[2], axes[3], axes[4], axes[5], bits); + } + + private boolean held(int control) { + return owners.indexOfValue(control) >= 0; + } + + private float stickCenterX() { + return stickFloating ? stickX : layout.x(TouchLayout.LEFT); + } + + private float stickCenterY() { + return stickFloating ? stickY : layout.y(TouchLayout.LEFT); + } + + /** the floating stick, centred where a thumb landed (at rest there, even near an edge) */ + private void floatStick(float x, float y) { + stickFloating = true; + stickX = x; + stickY = y; + } + + private boolean inFloatingZone(float x, float y) { + return x < logicalWidth * FLOATING_ZONE_WIDTH && y > logicalHeight * FLOATING_ZONE_TOP; + } + + private void moveStick(int control, float x, float y) { + float dx = (x - stickCenterX()) / layout.radius(control); + float dy = (y - stickCenterY()) / layout.radius(control); + float length = (float) Math.sqrt(dx * dx + dy * dy); + if (length < STICK_DEAD_ZONE) { + dx = 0; + dy = 0; + } else if (length > 1) { + dx /= length; + dy /= length; + } + axes[0] = Math.round(dx * 32767); + axes[1] = Math.round(dy * 32767); + } + + // ---------- fingers + + private static boolean inside(float x, float y, float cx, float cy, float radius) { + return (x - cx) * (x - cx) + (y - cy) * (y - cy) <= radius * radius; + } + + /** what is under a point (logical units): a toolbar item, a control, or NOTHING */ + private int hit(float x, float y) { + if (inside(x, y, optionsX(), toolbarY(), toolbarRadius(OPTIONS_RADIUS))) + return TOOLBAR_OPTIONS; + if (editing && inside(x, y, optionsX() - 156, toolbarY(), toolbarRadius(OPTIONS_RADIUS))) + return TOOLBAR_EXPORT; + if (editing && inside(x, y, optionsX() - 78, toolbarY(), toolbarRadius(OPTIONS_RADIUS))) + return TOOLBAR_IMPORT; + if (!editing && inside(x, y, logicalWidth / 2, toolbarY(), toolbarRadius(TOOLBAR_RADIUS))) + return TOOLBAR_TOGGLE; + if (hidden && !editing) + return NOTHING; + for (int i = 0; i < layout.size(); i++) { + if (layout.shown(i) && layout.type(i) != TouchLayout.LEFT + && inside(x, y, layout.x(i), layout.y(i), layout.radius(i))) + return i; + } + int stick = TouchLayout.LEFT; + float reach = editing ? 1 : STICK_REACH; + if (layout.shown(stick) && !held(stick) + && inside(x, y, layout.x(stick), layout.y(stick), layout.radius(stick) * reach)) + return stick; + return NOTHING; + } + + /** + * whether a finger went down where Android keeps its edge gestures; in + * sticky full screen the first swipe in from an edge only shows the + * system bars, and Android also hands it over as an ordinary finger + */ + private boolean inGestureZone(float x, float y, boolean sidesOnly) { + int[] insets = ((HaloActivity) getContext()).getSystemGestureInsetsPixels(); + if (x < insets[0] || x >= getWidth() - insets[2]) + return true; + return !sidesOnly && (y < insets[1] || y >= getHeight() - insets[3]); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int action = event.getActionMasked(); + if (!showing && !editing && openDialogs == 0) { + // the menus and cinematics take the fingers (SDL's surface below) + if (action == MotionEvent.ACTION_DOWN) + return false; + if (owners.size() > 0 || lookPointer >= 0) + reset(); + return true; + } + if (editing) + editTouch(event); + else + gameTouch(event); + publish(); + invalidate(); + return true; + } + + @Override + public boolean performClick() { + super.performClick(); + return true; + } + + private void gameTouch(MotionEvent event) { + int index = event.getActionIndex(); + int id = event.getPointerId(index); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + fingerDown(id, event.getX(index), event.getY(index)); + break; + case MotionEvent.ACTION_MOVE: + for (int i = 0; i < event.getPointerCount(); i++) + fingerMoved(event.getPointerId(i), event.getX(i), event.getY(i)); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + fingerUp(id, event.getX(index), event.getY(index)); + break; + case MotionEvent.ACTION_CANCEL: + reset(); + break; + default: + break; + } + } + + /** a finger down in a game, at a point in pixels */ + private void fingerDown(int id, float px, float py) { + if (inGestureZone(px, py, true)) + return; + float x = logical(px); + float y = logical(py); + int target = hit(x, y); + int stick = TouchLayout.LEFT; + if (target == NOTHING && layout.floatingStick && !hidden && layout.shown(stick) && !held(stick) + && inFloatingZone(x, y) && !inGestureZone(px, py, false)) { + floatStick(x, y); + target = stick; + } + if (target == TOOLBAR_OPTIONS || target == TOOLBAR_TOGGLE) { + toolbarFingers.put(id, new float[] {px, py, target}); + } else if (target != NOTHING) { + owners.put(id, target); + if (layout.type(target) == TouchLayout.LEFT) + moveStick(target, x, y); + else + buttonTouches.put(id, new float[] {px, py}); + } else if (!hidden && lookPointer < 0 && !inGestureZone(px, py, false)) { + startLook(id, px, py); + } + } + + private void startLook(int id, float px, float py) { + lookPointer = id; + lookX = px; + lookY = py; + if (owners.get(id, NOTHING) == NOTHING) + owners.put(id, LOOK); + } + + private boolean movedPastSlop(float[] origin, float px, float py) { + return Math.hypot(px - origin[0], py - origin[1]) > SLOP * scale; + } + + private void fingerMoved(int id, float px, float py) { + int control = owners.get(id, NOTHING); + if (control >= 0 && layout.type(control) == TouchLayout.LEFT) + moveStick(control, logical(px), logical(py)); + float[] toolbar = toolbarFingers.get(id); + if (toolbar != null && movedPastSlop(toolbar, px, py)) { + // a swipe that began on the toolbar aims; the item does not act + toolbarFingers.remove(id); + if (!hidden && lookPointer < 0) + startLook(id, toolbar[0], toolbar[1]); + } + float[] origin = buttonTouches.get(id); + if (origin != null && lookPointer >= 0 && lookPointer != id) { + // while another finger aims, a held button's finger may only take over from where it is + origin[0] = px; + origin[1] = py; + } + if (lookPointer < 0 && origin != null && movedPastSlop(origin, px, py)) { + // a held button (either Fire too) also aims + lookPointer = id; + lookX = origin[0]; + lookY = origin[1]; + } + if (id == lookPointer) { + nativeLook(logical(px - lookX) * sensitivity, logical(py - lookY) * sensitivity); + lookX = px; + lookY = py; + } + } + + private void fingerUp(int id, float px, float py) { + float[] toolbar = toolbarFingers.get(id); + if (toolbar != null) { + toolbarFingers.remove(id); + int item = (int) toolbar[2]; + if (hit(logical(px), logical(py)) == item) + toolbarAction(item); + return; + } + int control = owners.get(id, NOTHING); + if (control >= 0 && layout.type(control) == TouchLayout.LEFT) { + axes[0] = 0; + axes[1] = 0; + stickFloating = false; + } + if (id == lookPointer) + lookPointer = -1; + owners.delete(id); + buttonTouches.remove(id); + } + + private void toolbarAction(int item) { + if (item == TOOLBAR_OPTIONS) + showOptions(); + else if (item == TOOLBAR_TOGGLE) + setHidden(!hidden); + performClick(); + } + + /** a finger in the layout editor: drags controls; Save, Export and Import act at once */ + private void editTouch(MotionEvent event) { + int action = event.getActionMasked(); + int index = event.getActionIndex(); + int id = event.getPointerId(index); + if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) { + if (dragPointer >= 0) + return; + float x = logical(event.getX(index)); + float y = logical(event.getY(index)); + int target = hit(x, y); + if (target == TOOLBAR_OPTIONS) { + if (saveLayout()) { + reset(); + editing = false; + performClick(); + } + } else if (target == TOOLBAR_EXPORT || target == TOOLBAR_IMPORT) { + reset(); + ((HaloActivity) getContext()).chooseLayoutFile(target == TOOLBAR_EXPORT, exportLayout()); + } else if (target >= 0) { + dragPointer = id; + dragControl = target; + dragOffsetX = x - layout.x(target); + dragOffsetY = y - layout.y(target); + } + } else if (action == MotionEvent.ACTION_MOVE && dragPointer >= 0) { + int pointer = event.findPointerIndex(dragPointer); + if (pointer >= 0) { + layout.move(dragControl, logical(event.getX(pointer)) - dragOffsetX, + logical(event.getY(pointer)) - dragOffsetY); + } + } else if (action == MotionEvent.ACTION_CANCEL + || ((action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) && id == dragPointer)) { + dragPointer = -1; + dragControl = -1; + } + } + + // ---------- the buttons' names, from the player's profile + + private void readBindings() { + int serial = nativeBindings(bindingsRead); + if (serial == 0 || serial == bindingsSerial) + return; + bindingsSerial = serial; + if (!Arrays.equals(bindingsRead, bindings)) { + System.arraycopy(bindingsRead, 0, bindings, 0, bindings.length); + invalidate(); + } + } + + /** a control's name: its button and what the profile has it do ("A / Jump", "Fire", "RT") */ + private String label(int type) { + Kind kind = KINDS[type]; + if (kind.gamepadButton < 0) + return kind.buttonName; + int control = bindings[kind.gamepadButton]; + if (control < 0 || control >= GAME_CONTROLS.length) + return kind.buttonName; + return kind.faceButton ? kind.buttonName + " / " + GAME_CONTROLS[control] : GAME_CONTROLS[control]; + } + + private String controlName(int type) { + return type == TouchLayout.LEFT ? "Move stick" : label(type); + } + + // ---------- the layout, saved and in files + + private boolean saveLayout() { + boolean saved = preferences.edit().putString(KEY_CONFIGURATION, exportLayout()).commit(); + if (!saved) + Toast.makeText(getContext(), "The touch layout could not be saved. Try again.", Toast.LENGTH_LONG).show(); + return saved; + } + + public String exportLayout() { + return layout.exportConfiguration(sensitivity); + } + + /** replaces the layout with a layout file's (saved first: it throws if the file is not valid) */ + public void importLayout(String text) { + TouchLayout.Configuration imported = TouchLayout.importConfiguration(text); + imported.layout.bounds(logicalWidth, logicalHeight); + imported.layout.setMinimumRadius(minimumRadius()); + String normalized = imported.layout.exportConfiguration(imported.sensitivity); + if (!preferences.edit().putString(KEY_CONFIGURATION, normalized).commit()) + throw new IllegalArgumentException("Could not save the imported layout"); + reset(); + layout = imported.layout; + sensitivity = imported.sensitivity; + setHidden(false); + cancelRumble(); + invalidate(); + } + + // ---------- the options + + /** opens an option dialog; the controls are released while any is open, and saved when the last closes */ + private void openDialog(AlertDialog dialog) { + dialog.setOnDismissListener(d -> { + openDialogs--; + if (openDialogs > 0) + return; + openDialogs = 0; + saveLayout(); + reset(); + updateSensors(); + }); + openDialogs++; + reset(); + dialog.show(); + } + + private void showOptions() { + String[] items = {"General", "Edit buttons layout", "Look sensitivity"}; + AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setTitle("Options") + .setItems(items, (d, which) -> { + if (which == 0) { + post(this::showGeneral); + } else if (which == 1) { + editing = true; + setHidden(false); + } else { + post(this::showSensitivity); + } + }) + .setNegativeButton("Close", null) + .create(); + openDialog(dialog); + } + + private TextView text(String value) { + TextView view = new TextView(getContext()); + view.setText(value); + return view; + } + + private Button button(String label, View.OnClickListener listener) { + Button button = new Button(getContext()); + button.setText(label); + button.setOnClickListener(listener); + return button; + } + + private void showGeneral() { + cancelRumble(); + LinearLayout panel = new LinearLayout(getContext()); + panel.setOrientation(LinearLayout.VERTICAL); + panel.setPadding(24, 16, 24, 16); + + boolean hasRumble = vibrator != null && vibrator.hasVibrator(); + Switch rumble = new Switch(getContext()); + rumble.setText(hasRumble ? "Vibration" : "Vibration (not on this phone)"); + rumble.setChecked(layout.rumbleEnabled); + rumble.setEnabled(hasRumble); + rumble.setOnCheckedChangeListener((view, enabled) -> { + layout.rumbleEnabled = enabled; + cancelRumble(); + saveLayout(); + }); + panel.addView(rumble); + + Switch gyro = new Switch(getContext()); + gyro.setText(gyroscope != null ? "Gyroscope aiming (experimental)" : "Gyroscope aiming (not on this phone)"); + gyro.setChecked(layout.gyroscopeEnabled); + gyro.setEnabled(gyroscope != null); + gyro.setOnCheckedChangeListener((view, enabled) -> { + layout.gyroscopeEnabled = enabled; + gyroAim.reset(); + saveLayout(); + }); + panel.addView(gyro); + + Switch floating = new Switch(getContext()); + floating.setText("Floating move stick: it goes where your thumb lands in the lower left"); + floating.setChecked(layout.floatingStick); + floating.setOnCheckedChangeListener((view, enabled) -> { + layout.floatingStick = enabled; + saveLayout(); + }); + panel.addView(floating); + + TextView opacityLabel = text(""); + SeekBar opacity = new SeekBar(getContext()); + int steps = Math.round((TouchLayout.MAX_OPACITY - TouchLayout.MIN_OPACITY) * 20); + opacity.setMax(steps); + opacity.setProgress(Math.round((layout.opacity - TouchLayout.MIN_OPACITY) * 20)); + Runnable showOpacity = () -> opacityLabel.setText(String.format(Locale.US, "Opacity: %d%%", + Math.round(layout.opacity * 100))); + showOpacity.run(); + opacity.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar bar, int progress, boolean user) { + layout.setOpacity(Math.min(TouchLayout.MAX_OPACITY, TouchLayout.MIN_OPACITY + progress / 20f)); + showOpacity.run(); + invalidate(); + } + + @Override + public void onStartTrackingTouch(SeekBar bar) { + } + + @Override + public void onStopTrackingTouch(SeekBar bar) { + saveLayout(); + } + }); + panel.addView(opacityLabel); + panel.addView(opacity); + + AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setTitle("General") + .setView(panel) + .setPositiveButton("Done", null) + .create(); + panel.addView(button("Hide or add buttons", view -> { + dialog.dismiss(); + post(this::showButtonManager); + })); + panel.addView(button("Edit buttons size", view -> { + dialog.dismiss(); + post(this::showButtonSizes); + })); + openDialog(dialog); + } + + private String rowName(int control, boolean withHidden) { + String name = controlName(layout.type(control)); + if (control >= TouchLayout.BASE_COUNT) + name += " (copy)"; + if (withHidden && !layout.shown(control)) + name += " (hidden)"; + return name; + } + + private LinearLayout row(int control, boolean withHidden) { + LinearLayout row = new LinearLayout(getContext()); + row.setPadding(16, 4, 16, 4); + TextView name = text(rowName(control, withHidden)); + name.setTextColor(Color.WHITE); + row.addView(name, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1)); + return row; + } + + private void showButtonSizes() { + LinearLayout list = new LinearLayout(getContext()); + list.setOrientation(LinearLayout.VERTICAL); + for (int i = 0; i < layout.size(); i++) { + final int control = i; + LinearLayout row = row(control, true); + TextView value = text(""); + value.setTextColor(Color.WHITE); + Button smaller = new Button(getContext()); + Button larger = new Button(getContext()); + Runnable refresh = () -> { + int percent = Math.round(layout.sizeScale(control) * 100); + value.setText(percent + "%"); + smaller.setEnabled(percent > Math.round(TouchLayout.MIN_SIZE * 100)); + larger.setEnabled(percent < Math.round(TouchLayout.MAX_SIZE * 100)); + }; + smaller.setText("-"); + smaller.setOnClickListener(view -> resize(control, -10, refresh)); + larger.setText("+"); + larger.setOnClickListener(view -> resize(control, 10, refresh)); + row.addView(smaller); + row.addView(value); + row.addView(larger); + list.addView(row); + refresh.run(); + } + ScrollView scroll = new ScrollView(getContext()); + scroll.addView(list); + openDialog(new AlertDialog.Builder(getContext()) + .setTitle("Edit buttons size") + .setView(scroll) + .setPositiveButton("Done", null) + .create()); + } + + private void resize(int control, int percent, Runnable refresh) { + int size = Math.round(layout.sizeScale(control) * 100) + percent; + size = Math.max(Math.round(TouchLayout.MIN_SIZE * 100), Math.min(Math.round(TouchLayout.MAX_SIZE * 100), size)); + layout.setSize(control, size / 100f); + saveLayout(); + refresh.run(); + invalidate(); + } + + private void showButtonManager() { + LinearLayout list = new LinearLayout(getContext()); + list.setOrientation(LinearLayout.VERTICAL); + Runnable[] refresh = new Runnable[1]; + refresh[0] = () -> { + list.removeAllViews(); + for (int i = 0; i < layout.size(); i++) { + final int control = i; + LinearLayout row = row(control, false); + row.addView(button(layout.shown(control) ? "Hide" : "Show", view -> { + reset(); + layout.setShown(control, !layout.shown(control)); + saveLayout(); + refresh[0].run(); + invalidate(); + })); + if (layout.type(control) != TouchLayout.LEFT) { + row.addView(button("Duplicate", view -> { + reset(); + if (layout.duplicate(control) < 0) + toast("At most " + TouchLayout.MAX_CONTROLS + " controls. Reset removes the copies."); + else + saveLayout(); + refresh[0].run(); + invalidate(); + })); + } + list.addView(row); + } + }; + refresh[0].run(); + ScrollView scroll = new ScrollView(getContext()); + scroll.addView(list); + AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setTitle("Hide or add buttons") + .setView(scroll) + .setPositiveButton("Done", null) + .setNegativeButton("Reset", null) + .setNeutralButton("Add button", null) + .create(); + openDialog(dialog); + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(view -> confirmReset(refresh[0])); + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(view -> chooseButtonToAdd(refresh[0])); + } + + private void confirmReset(Runnable refresh) { + new AlertDialog.Builder(getContext()) + .setTitle("Reset the buttons?") + .setMessage("This puts every button back in its place and size, shows them all and removes the copies.") + .setNegativeButton("Cancel", null) + .setPositiveButton("Reset", (d, which) -> { + reset(); + layout.resetDefaults(); + setHidden(false); + saveLayout(); + refresh.run(); + invalidate(); + }) + .show(); + } + + private void chooseButtonToAdd(Runnable refresh) { + String[] names = new String[TouchLayout.BASE_COUNT]; + for (int i = 0; i < names.length; i++) + names[i] = controlName(i); + new AlertDialog.Builder(getContext()) + .setTitle("Add button") + .setItems(names, (d, type) -> { + reset(); + if (layout.add(type) < 0) + toast(type == TouchLayout.LEFT ? "The move stick already shows." : "At most " + TouchLayout.MAX_CONTROLS + " controls."); + else + saveLayout(); + refresh.run(); + invalidate(); + }) + .setNegativeButton("Cancel", null) + .show(); + } + + private void showSensitivity() { + LinearLayout panel = new LinearLayout(getContext()); + panel.setOrientation(LinearLayout.VERTICAL); + panel.setPadding(32, 16, 32, 16); + TextView value = text(""); + SeekBar slider = new SeekBar(getContext()); + int steps = Math.round((TouchLayout.MAX_SENSITIVITY - TouchLayout.MIN_SENSITIVITY) / 0.025f); + slider.setMax(steps); + slider.setProgress(Math.round((sensitivity - TouchLayout.MIN_SENSITIVITY) / 0.025f)); + Runnable showValue = () -> value.setText(String.format(Locale.US, "Look sensitivity: %.2fx", sensitivity)); + showValue.run(); + slider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar bar, int progress, boolean user) { + sensitivity = Math.min(TouchLayout.MAX_SENSITIVITY, TouchLayout.MIN_SENSITIVITY + progress * 0.025f); + showValue.run(); + } + + @Override + public void onStartTrackingTouch(SeekBar bar) { + } + + @Override + public void onStopTrackingTouch(SeekBar bar) { + saveLayout(); + } + }); + panel.addView(value); + panel.addView(slider); + openDialog(new AlertDialog.Builder(getContext()) + .setTitle("Look sensitivity") + .setView(panel) + .setPositiveButton("Done", null) + .create()); + } + + private void toast(String message) { + Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show(); + } + + // ---------- the gyroscope and the vibration + + private void updateSensors() { + boolean needed = deviceInputActive && showing && !hidden && layout.gyroscopeEnabled && gyroscope != null; + if (needed && !gyroRegistered) { + gyroAim.reset(); + gyroRegistered = sensors.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_GAME); + } else if (!needed && gyroRegistered) { + sensors.unregisterListener(this); + gyroRegistered = false; + gyroAim.reset(); + } + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (!deviceInputActive || !showing || hidden || !layout.gyroscopeEnabled || editing || openDialogs > 0) { + gyroAim.reset(); + return; + } + int rotation = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay().getRotation(); + if (gyroAim.sample(event.timestamp, event.values[0], event.values[1], rotation, gyroDelta)) { + // as a swipe of this many logical units (which the profile's invert leaves alone) + nativeGyro(-gyroDelta[0] / LOOK_RADIANS_PER_UNIT * sensitivity, + -gyroDelta[1] / LOOK_RADIANS_PER_UNIT * sensitivity); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + + /** the phone vibrates as port 0's controller would, while the controls show */ + private void updateRumble() { + if (vibrator == null || !vibrator.hasVibrator()) + return; + boolean active = showing && !hidden && layout.rumbleEnabled && !editing && openDialogs == 0; + int amplitude = active ? nativeRumble() : 0; + if (amplitude == 0) { + cancelRumble(); + return; + } + long now = SystemClock.uptimeMillis(); + if (amplitude == lastAmplitude && now - lastVibration < VIBRATION_RENEW_MS) + return; + try { + int strength = vibrator.hasAmplitudeControl() ? amplitude : VibrationEffect.DEFAULT_AMPLITUDE; + vibrator.vibrate(VibrationEffect.createOneShot(VIBRATION_MS, strength), rumbleAttributes); + lastAmplitude = amplitude; + lastVibration = now; + } catch (RuntimeException e) { + cancelRumble(); + } + } + + private void cancelRumble() { + if (lastAmplitude != 0 && vibrator != null) { + try { + vibrator.cancel(); + } catch (RuntimeException ignored) { + // the vibrator may already be gone with the activity + } + } + lastAmplitude = 0; + } + + // ---------- drawing + + private static int alpha(int color, float opacity) { + int alpha = Math.round(Color.alpha(color) * opacity); + return (color & 0x00ffffff) | (Math.max(0, Math.min(255, alpha)) << 24); + } + + private void circle(Canvas canvas, float x, float y, float radius, String label, boolean active, + float textSize, float opacity) { + paint.setStyle(Paint.Style.FILL); + paint.setColor(alpha(active ? 0x9983d9ff : 0x55304050, opacity)); + canvas.drawCircle(x, y, radius, paint); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(2); + paint.setColor(alpha(active ? 0xffaee7ff : 0xffffffff, opacity)); + canvas.drawCircle(x, y, radius, paint); + paint.setStyle(Paint.Style.FILL); + paint.setColor(alpha(Color.WHITE, Math.max(opacity, 0.5f))); + paint.setTextAlign(Paint.Align.CENTER); + paint.setTextSize(textSize); + // (a long name, "Y / Weapon", is made smaller to fit inside the circle) + float width = paint.measureText(label); + if (width > radius * LABEL_WIDTH) { + textSize *= radius * LABEL_WIDTH / width; + paint.setTextSize(textSize); + } + canvas.drawText(label, x, y + textSize * 0.36f, paint); + } + + /** a toolbar item: never fainter than half, so that it can always be found */ + private void toolbarItem(Canvas canvas, float x, float radius, String label) { + circle(canvas, x, toolbarY(), toolbarRadius(radius), label, false, 11, Math.max(layout.opacity, 0.5f)); + } + + private void drawToolbar(Canvas canvas) { + if (editing) { + toolbarItem(canvas, optionsX(), OPTIONS_RADIUS, "Save"); + toolbarItem(canvas, optionsX() - 156, OPTIONS_RADIUS, "Export"); + toolbarItem(canvas, optionsX() - 78, OPTIONS_RADIUS, "Import"); + paint.setColor(Color.WHITE); + paint.setTextSize(14); + canvas.drawText("Drag the controls, then push Save", logicalWidth / 2, toolbarY() + 58, paint); + } else { + toolbarItem(canvas, logicalWidth / 2, TOOLBAR_RADIUS, hidden ? "Touch" : "Hide"); + toolbarItem(canvas, optionsX(), OPTIONS_RADIUS, "Options"); + } + } + + private void drawStick(Canvas canvas) { + int stick = TouchLayout.LEFT; + float radius = layout.radius(stick); + float size = layout.sizeScale(stick); + // (a floating stick near an edge is drawn whole, nearer the middle than its centre) + float x = Math.max(radius, Math.min(logicalWidth - radius, stickCenterX())); + float y = Math.max(radius, Math.min(logicalHeight - radius, stickCenterY())); + circle(canvas, x, y, radius, "Move", false, 11 * size, layout.opacity); + circle(canvas, x + axes[0] / 32767f * radius, y + axes[1] / 32767f * radius, + Math.max(STICK_KNOB_RADIUS * size, radius * 0.375f), "", held(stick), 11, layout.opacity); + } + + @Override + protected void onDraw(Canvas canvas) { + if (!showing && !editing) + return; + canvas.save(); + canvas.scale(scale, scale); + if (!hidden || editing) { + if (layout.shown(TouchLayout.LEFT)) + drawStick(canvas); + for (int i = 0; i < layout.size(); i++) { + if (!layout.shown(i) || layout.type(i) == TouchLayout.LEFT) + continue; + float textSize = 11 * Math.max(layout.sizeScale(i), layout.radius(i) / 36f); + circle(canvas, layout.x(i), layout.y(i), layout.radius(i), label(layout.type(i)), + held(i) || dragControl == i, Math.min(textSize, 16), layout.opacity); + } + } + drawToolbar(canvas); + canvas.restore(); + } +} diff --git a/port/android/app/src/main/java/com/halo/decomp/TouchLayout.java b/port/android/app/src/main/java/com/halo/decomp/TouchLayout.java new file mode 100644 index 00000000..d0f37f9a --- /dev/null +++ b/port/android/app/src/main/java/com/halo/decomp/TouchLayout.java @@ -0,0 +1,335 @@ +package com.halo.decomp; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Properties; + +/** + * The touch controls' layout (TouchControls): each control's kind, place, + * size and whether it shows, with the settings that go with them, in the + * overlay's logical units. Places are kept for the display the layout is + * on and saved on a 960 x 540 grid, so a layout moves between displays of + * other shapes. A layout file is Java properties text (version 3; 1 and 2 + * are still read). + */ +final class TouchLayout { + /** the kinds of control: a button of TouchControls's list, by index */ + static final int LEFT = 16; + static final int FIRE_LEFT = 17; + static final int BASE_COUNT = 18; + static final int MAX_CONTROLS = 64; + static final float MIN_SIZE = 0.5f; + static final float MAX_SIZE = 2f; + static final float MIN_OPACITY = 0.2f; + static final float MAX_OPACITY = 1f; + static final float MIN_SENSITIVITY = 0.25f; + static final float MAX_SENSITIVITY = 4f; + /** + * the largest minimum radius: a finger's width is capped here on a small, + * dense display, where the D-pad's buttons (69 apart) would overlap + */ + static final float MAX_MINIMUM_RADIUS = 34; + + /** the saved grid */ + private static final float GRID_WIDTH = 960; + private static final float GRID_HEIGHT = 540; + private static final int VERSION = 3; + private static final int MAX_FILE_LENGTH = 65536; + + /** each kind's default place on the grid, and its radius */ + private static final float[][] DEFAULTS = { + {856, 447}, {913, 377}, {794, 377}, {850, 312}, {915, 239}, {802, 239}, + {236, 449}, {691, 449}, {360, 490}, {438, 490}, {570, 36}, {390, 36}, + {100, 237}, {100, 335}, {51, 286}, {149, 286}, {115, 440}, {255, 239} + }; + private static final float[] RADII = { + 36, 32, 34, 32, 39, 35, 32, 32, 27, 29, 28, 28, 25, 25, 25, 25, 64, 39 + }; + + private static final class Control { + final int type; + float x; + float y; + boolean shown = true; + float size = 1f; + + Control(int type, float x, float y) { + this.type = type; + this.x = x; + this.y = y; + } + } + + /** a layout and the sensitivity saved with it */ + static final class Configuration { + final TouchLayout layout; + final float sensitivity; + + Configuration(TouchLayout layout, float sensitivity) { + this.layout = layout; + this.sensitivity = sensitivity; + } + } + + private final ArrayList controls = new ArrayList<>(); + private float width = GRID_WIDTH; + private float height = GRID_HEIGHT; + /** the smallest radius a control is drawn and touched at, in logical units */ + private float minimumRadius; + + boolean rumbleEnabled = true; + boolean gyroscopeEnabled = false; + /** how solid the controls are drawn, MIN_OPACITY..MAX_OPACITY */ + float opacity = 1f; + /** the move stick goes where the thumb lands in the lower left, not at its place */ + boolean floatingStick = false; + + TouchLayout() { + resetDefaults(); + } + + /** places the layout on a display of this size, in logical units */ + void bounds(float width, float height) { + if (!Float.isFinite(width) || !Float.isFinite(height) || width <= 0 || height <= 0) + throw new IllegalArgumentException("Invalid display dimensions"); + for (Control control : controls) { + control.x *= width / this.width; + control.y *= height / this.height; + } + this.width = width; + this.height = height; + keepAllInside(); + } + + /** + * the smallest radius, in logical units, at which a control is drawn and + * touched (a finger's width, whatever the display's density), at most + * MAX_MINIMUM_RADIUS + */ + void setMinimumRadius(float radius) { + minimumRadius = Float.isFinite(radius) && radius > 0 ? Math.min(radius, MAX_MINIMUM_RADIUS) : 0; + keepAllInside(); + } + + int size() { + return controls.size(); + } + + int type(int control) { + return controls.get(control).type; + } + + float x(int control) { + return controls.get(control).x; + } + + float y(int control) { + return controls.get(control).y; + } + + float savedX(int control) { + return x(control) * GRID_WIDTH / width; + } + + float savedY(int control) { + return y(control) * GRID_HEIGHT / height; + } + + float radius(int control) { + return Math.max(RADII[type(control)] * sizeScale(control), minimumRadius); + } + + float sizeScale(int control) { + return controls.get(control).size; + } + + void setSize(int control, float size) { + if (!Float.isFinite(size) || size < MIN_SIZE || size > MAX_SIZE) + throw new IllegalArgumentException("Button size must be between 50% and 200%"); + controls.get(control).size = size; + move(control, x(control), y(control)); + } + + boolean shown(int control) { + return controls.get(control).shown; + } + + void setShown(int control, boolean shown) { + controls.get(control).shown = shown; + } + + void setOpacity(float opacity) { + if (!validOpacity(opacity)) + throw new IllegalArgumentException("Opacity must be between 20% and 100%"); + this.opacity = opacity; + } + + /** the default controls, at their default places, sizes and visibility */ + void resetDefaults() { + controls.clear(); + for (int i = 0; i < DEFAULTS.length; i++) + controls.add(new Control(i, DEFAULTS[i][0] * width / GRID_WIDTH, DEFAULTS[i][1] * height / GRID_HEIGHT)); + keepAllInside(); + } + + /** adds a copy of a control beside it; -1 if there may be no more (or it is the stick) */ + int duplicate(int source) { + if (size() >= MAX_CONTROLS || type(source) == LEFT) + return -1; + float offset = radius(source) * 2 + 12; + Control added = new Control(type(source), x(source) + offset, y(source)); + added.size = sizeScale(source); + controls.add(added); + int index = size() - 1; + // where there is no room to the right, the copy goes to the left + if (added.x > width - radius(index)) + added.x = x(source) - offset; + move(index, added.x, added.y); + return index; + } + + /** shows a hidden control of this kind, or else adds a copy; -1 if neither can be */ + int add(int type) { + if (type < 0 || type >= BASE_COUNT) + return -1; + for (int i = 0; i < size(); i++) { + if (type(i) == type && !shown(i)) { + setShown(i, true); + return i; + } + } + return duplicate(type); + } + + /** moves a control, keeping all of it on the display */ + void move(int control, float x, float y) { + if (!Float.isFinite(x) || !Float.isFinite(y)) + return; + float radius = radius(control); + controls.get(control).x = Math.max(radius, Math.min(width - radius, x)); + controls.get(control).y = Math.max(radius, Math.min(height - radius, y)); + } + + /** puts a control at a saved place on the grid, if it is a valid one */ + void restore(int control, float x, float y) { + if (Float.isFinite(x) && Float.isFinite(y) && x >= 0 && x <= GRID_WIDTH && y >= 0 && y <= GRID_HEIGHT) { + controls.get(control).x = x; + controls.get(control).y = y; + } + } + + private void keepAllInside() { + for (int i = 0; i < size(); i++) + move(i, x(i), y(i)); + } + + /** the layout as a layout file's text, with the look sensitivity */ + String exportConfiguration(float sensitivity) { + if (!validSensitivity(sensitivity)) + throw new IllegalArgumentException("Invalid sensitivity"); + Properties values = new Properties(); + values.setProperty("format", "halo-touch-layout"); + values.setProperty("version", Integer.toString(VERSION)); + values.setProperty("rumble", Boolean.toString(rumbleEnabled)); + values.setProperty("gyroscope", Boolean.toString(gyroscopeEnabled)); + values.setProperty("opacity", Float.toString(opacity)); + values.setProperty("floating-stick", Boolean.toString(floatingStick)); + values.setProperty("count", Integer.toString(size())); + values.setProperty("sensitivity", Float.toString(sensitivity)); + for (int i = 0; i < size(); i++) { + String key = "control." + i + "."; + values.setProperty(key + "type", Integer.toString(type(i))); + values.setProperty(key + "x", Float.toString(savedX(i))); + values.setProperty(key + "y", Float.toString(savedY(i))); + values.setProperty(key + "visible", Boolean.toString(shown(i))); + values.setProperty(key + "size", Float.toString(sizeScale(i))); + } + StringWriter text = new StringWriter(); + try { + values.store(text, "Halo Android touch layout"); + } catch (IOException e) { + throw new IllegalStateException(e); + } + return text.toString(); + } + + /** reads a layout file's text; throws IllegalArgumentException for anything not valid */ + static Configuration importConfiguration(String text) { + if (text == null || text.length() > MAX_FILE_LENGTH) + throw new IllegalArgumentException("Invalid layout file"); + Properties values = new Properties(); + try { + values.load(new StringReader(text)); + int version = readVersion(values); + int count = Integer.parseInt(values.getProperty("count")); + float sensitivity = Float.parseFloat(values.getProperty("sensitivity")); + if (count < BASE_COUNT || count > MAX_CONTROLS || !validSensitivity(sensitivity)) + throw new IllegalArgumentException("Invalid layout settings"); + TouchLayout layout = new TouchLayout(); + layout.controls.clear(); + if (version >= 2) { + layout.rumbleEnabled = readBoolean(values, "rumble"); + layout.gyroscopeEnabled = readBoolean(values, "gyroscope"); + } + if (version >= 3) { + float opacity = Float.parseFloat(values.getProperty("opacity")); + if (!validOpacity(opacity)) + throw new IllegalArgumentException("Invalid opacity"); + layout.opacity = opacity; + layout.floatingStick = readBoolean(values, "floating-stick"); + } + for (int i = 0; i < count; i++) + layout.controls.add(readControl(values, i, version)); + return new Configuration(layout, sensitivity); + } catch (IOException | NullPointerException e) { + throw new IllegalArgumentException("Incomplete layout file", e); + } + } + + private static int readVersion(Properties values) { + if (!"halo-touch-layout".equals(values.getProperty("format"))) + throw new IllegalArgumentException("Unsupported layout file"); + String version = values.getProperty("version"); + for (int known = 1; known <= VERSION; known++) { + if (Integer.toString(known).equals(version)) + return known; + } + throw new IllegalArgumentException("Unsupported layout file"); + } + + private static Control readControl(Properties values, int index, int version) { + String key = "control." + index + "."; + int type = Integer.parseInt(values.getProperty(key + "type")); + float x = Float.parseFloat(values.getProperty(key + "x")); + float y = Float.parseFloat(values.getProperty(key + "y")); + String shown = values.getProperty(key + "visible"); + float size = version >= 2 ? Float.parseFloat(values.getProperty(key + "size")) : 1f; + boolean validType = type >= 0 && type < BASE_COUNT && (index >= BASE_COUNT ? type != LEFT : type == index); + boolean validPlace = Float.isFinite(x) && Float.isFinite(y) && x >= 0 && x <= GRID_WIDTH + && y >= 0 && y <= GRID_HEIGHT; + boolean validSize = Float.isFinite(size) && size >= MIN_SIZE && size <= MAX_SIZE; + if (!validType || !validPlace || !validSize || !("true".equals(shown) || "false".equals(shown))) + throw new IllegalArgumentException("Invalid control in layout file"); + Control control = new Control(type, x, y); + control.shown = Boolean.parseBoolean(shown); + control.size = size; + return control; + } + + private static boolean readBoolean(Properties values, String key) { + String value = values.getProperty(key); + if (!("true".equals(value) || "false".equals(value))) + throw new IllegalArgumentException("Invalid " + key + " option"); + return Boolean.parseBoolean(value); + } + + static boolean validSensitivity(float value) { + return Float.isFinite(value) && value >= MIN_SENSITIVITY && value <= MAX_SENSITIVITY; + } + + static boolean validOpacity(float value) { + return Float.isFinite(value) && value >= MIN_OPACITY && value <= MAX_OPACITY; + } +} diff --git a/port/android/app/src/main/java/com/halo/decomp/Updater.java b/port/android/app/src/main/java/com/halo/decomp/Updater.java index 9f86065d..19a245fc 100644 --- a/port/android/app/src/main/java/com/halo/decomp/Updater.java +++ b/port/android/app/src/main/java/com/halo/decomp/Updater.java @@ -53,6 +53,8 @@ final class Updater { private static final String REPOSITORY = "Cesarus85/ringfall-mjolnir-vr"; private static final String USER_AGENT = "halo-ce-universal-updater"; private static final int TIMEOUT_MILLISECONDS = 20000; + /** the most a download (a release's zip, about 35 MB) or the app in it may be */ + private static final long MAXIMUM_UPDATE_SIZE = 256L * 1024 * 1024; private Updater() { } @@ -305,8 +307,10 @@ final class Updater { int count; while ((count = in.read(buffer)) > 0) { - out.write(buffer, 0, count); received += count; + if (received > MAXIMUM_UPDATE_SIZE) + throw new IOException("the download is larger than expected"); + out.write(buffer, 0, count); if (received - reported >= 256 * 1024 || received == total) { progress.report(received, total); reported = received; @@ -330,10 +334,15 @@ final class Updater { continue; try (OutputStream out = new FileOutputStream(apk)) { byte[] buffer = new byte[65536]; + long written = 0; int count; - while ((count = in.read(buffer)) > 0) + while ((count = in.read(buffer)) > 0) { + written += count; + if (written > MAXIMUM_UPDATE_SIZE) + throw new IOException("the app in the download is larger than expected"); out.write(buffer, 0, count); + } } return; } diff --git a/port/android/app/src/main/java/com/halo/decomp/XisoExtractor.java b/port/android/app/src/main/java/com/halo/decomp/XisoExtractor.java index 31ffe28a..95ce9372 100644 --- a/port/android/app/src/main/java/com/halo/decomp/XisoExtractor.java +++ b/port/android/app/src/main/java/com/halo/decomp/XisoExtractor.java @@ -135,6 +135,20 @@ final class XisoExtractor { return true; } + /** whether a name is one to write in the maps folder: no path, no "." or + * "..", and only printable ASCII that every file system takes as a plain + * name (a disc's maps folder holds .map files and loading.tga) */ + static boolean isPlainName(String name) { + if (name.isEmpty() || name.equals(".") || name.equals("..")) + return false; + for (int index = 0; index < name.length(); index++) { + char c = name.charAt(index); + if (c < 0x20 || c > 0x7E || "/\\:*?\"<>|".indexOf(c) >= 0) + return false; + } + return true; + } + /** the partition's volume descriptor: the root directory's sector and size */ private long[] findVolume() throws IOException { for (long offset : PARTITION_OFFSETS) { @@ -190,8 +204,9 @@ final class XisoExtractor { for (int index = 0; index < nameLength; index++) bytes[index] = table.get(at + ENTRY_HEADER_SIZE + index); String name = new String(bytes, StandardCharsets.ISO_8859_1); - /* (as extract-xiso refuses them: no name may leave the folder) */ - if (!name.equals(".") && !name.equals("..") && name.indexOf('/') < 0 && name.indexOf('\\') < 0) + /* (as extract-xiso refuses them: no name may leave the folder, and + none but a plain file name is written) */ + if (isPlainName(name)) out.add(new Entry(name, table.getInt(at + 4) & 0xFFFFFFFFL, table.getInt(at + 8) & 0xFFFFFFFFL)); } if (right != 0) diff --git a/port/android/guest/runtime/guest_host.h b/port/android/guest/runtime/guest_host.h index 74f38f4b..c9cb960b 100644 --- a/port/android/guest/runtime/guest_host.h +++ b/port/android/guest/runtime/guest_host.h @@ -46,6 +46,8 @@ unsigned int host_memory_watch_generation(unsigned int address, unsigned int siz unsigned int host_memory_watch_serial(void); void host_memory_watch_prepare_write(unsigned int address, unsigned int size); void host_memory_watch_forget(unsigned int address, unsigned int size); +/* memory_watch_begin_frame (port/linux/src/platform.h) */ +void host_memory_watch_begin_frame(void); /* ---------- SDL (guest/runtime/guest_sdl.c) @@ -80,11 +82,16 @@ int host_sdl_gamepad_axis(unsigned int gamepad, int axis); int host_sdl_gamepad_button(unsigned int gamepad, int button); int host_sdl_gamepad_type(unsigned int gamepad); int host_sdl_rumble_gamepad(unsigned int gamepad, unsigned int low, unsigned int high, unsigned int milliseconds); +void host_sdl_close_gamepad(unsigned int gamepad); /* callback: void (*)(void *userdata, unsigned int stream, int additional, int total), called on the audio thread */ unsigned int host_sdl_open_audio_stream(unsigned int device, const void *spec, unsigned int callback, unsigned int userdata); int host_sdl_put_audio_stream_data(unsigned int stream, const void *data, int length); int host_sdl_resume_audio_stream_device(unsigned int stream); +/* (voice chat's microphone, a stream without a callback) */ +int host_sdl_get_audio_stream_data(unsigned int stream, void *data, int length); +int host_sdl_get_audio_stream_available(unsigned int stream); +void host_sdl_destroy_audio_stream(unsigned int stream); /* ---------- OpenGL ES */ @@ -147,4 +154,24 @@ void host_xr_set_refresh_rate(float wanted); /* the storage directories the port uses, copied into buffer */ void host_android_path(int which, char *buffer, unsigned int size); +/* the edges where Android keeps its gestures, as left, top, right, bottom +in pixels of the current orientation, into insets[4]; all 0 when unknown */ +void host_gesture_insets(int *insets); + +/* ---------- the on-screen touch controls (host_touch.c) */ + +/* the overlay's controller: the SDL axes (left x, y, right x, y, left +trigger, right trigger) and the SDL button bits, into state[7] */ +void host_touch_read(int *state); +/* the overlay's view swipe, then its gyroscope turn, since the last read, +into delta[4] */ +void host_touch_look_read(float *delta); +/* port 0's motors, for the phone's vibration */ +void host_touch_rumble(unsigned int low, unsigned int high); +/* tells the overlay when to show: touch_input.c's _touch_scene_* */ +void host_touch_scene(int scene); +/* the game control on each of the 16 controller buttons (touch_game.c), +for the overlay's labels */ +void host_touch_bindings(const int *controls); + #endif diff --git a/port/android/guest/runtime/guest_memory_watch.c b/port/android/guest/runtime/guest_memory_watch.c index 4f339852..d5ccfd2c 100644 --- a/port/android/guest/runtime/guest_memory_watch.c +++ b/port/android/guest/runtime/guest_memory_watch.c @@ -39,3 +39,8 @@ void memory_watch_forget(void *address, unsigned long size) { host_memory_watch_forget((unsigned int)address, size); } + +void memory_watch_begin_frame(void) +{ + host_memory_watch_begin_frame(); +} diff --git a/port/android/guest/runtime/guest_sdl.c b/port/android/guest/runtime/guest_sdl.c index 7bd89689..bd7b309b 100644 --- a/port/android/guest/runtime/guest_sdl.c +++ b/port/android/guest/runtime/guest_sdl.c @@ -216,6 +216,11 @@ bool SDL_RumbleGamepad(SDL_Gamepad *gamepad, Uint16 low, Uint16 high, Uint32 mil return host_sdl_rumble_gamepad((unsigned int)gamepad, low, high, milliseconds) != 0; } +void SDL_CloseGamepad(SDL_Gamepad *gamepad) +{ + host_sdl_close_gamepad((unsigned int)gamepad); +} + /* ---------- audio */ SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID device, const SDL_AudioSpec *spec, @@ -234,3 +239,18 @@ bool SDL_ResumeAudioStreamDevice(SDL_AudioStream *stream) { return host_sdl_resume_audio_stream_device((unsigned int)stream) != 0; } + +int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *data, int length) +{ + return host_sdl_get_audio_stream_data((unsigned int)stream, data, length); +} + +int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) +{ + return host_sdl_get_audio_stream_available((unsigned int)stream); +} + +void SDL_DestroyAudioStream(SDL_AudioStream *stream) +{ + host_sdl_destroy_audio_stream((unsigned int)stream); +} diff --git a/port/android/guest/runtime/guest_start.c b/port/android/guest/runtime/guest_start.c index e0beba77..8242ec50 100644 --- a/port/android/guest/runtime/guest_start.c +++ b/port/android/guest/runtime/guest_start.c @@ -25,6 +25,7 @@ extern void (*__guest_init_end[])(void); void __guest_start(const struct halo_guest_boot *boot); void __guest_thread_start(unsigned int thread); unsigned int __guest_thread_attach(void); +void __guest_thread_detach(void); void __guest_thread_initialize_main(void); extern char **__environ; @@ -41,6 +42,7 @@ const struct halo_guest_header __guest_header = (uint32_t)__guest_start, (uint32_t)__guest_thread_start, (uint32_t)__guest_thread_attach, + (uint32_t)__guest_thread_detach, (uint32_t)__guest_init_start, (uint32_t)__guest_init_end, }; diff --git a/port/android/guest/runtime/guest_thread.c b/port/android/guest/runtime/guest_thread.c index 56223a1a..13fe28a0 100644 --- a/port/android/guest/runtime/guest_thread.c +++ b/port/android/guest/runtime/guest_thread.c @@ -125,6 +125,18 @@ unsigned int __guest_thread_attach(void) return (unsigned int)thread; } +/* a host thread that ran guest code (__guest_thread_attach) is ending: +its struct pthread goes */ +void __guest_thread_detach(void) +{ + struct guest_thread *thread = (struct guest_thread *)host_get_tp(); + + if (!thread || thread == &main_thread) + return; + host_set_tp(0); + thread_free(thread); +} + int pthread_create(pthread_t *restrict result, const pthread_attr_t *restrict attributes, void *(*start)(void *), void *restrict argument) { diff --git a/port/android/host/host.h b/port/android/host/host.h index 8512c995..11df4365 100644 --- a/port/android/host/host.h +++ b/port/android/host/host.h @@ -37,6 +37,10 @@ mappings) from pools of address space it reserves below 4 GB on demand. */ /* reserves the fixed ranges; returns 0 on success */ int host_memory_initialize(uint32_t image_base, uint32_t image_size); +/* Makes the write tracking compare page contents instead of catching page +faults (host_watch_hash.h), for when the app cannot receive its own +SIGSEGV (ARM translation). Call before the guest starts. */ +void host_memory_watch_use_hashes(void); /* page-granular allocations below 4 GB; NULL on failure */ void *host_low_map(size_t size, int protection); void host_low_unmap(void *address, size_t size); diff --git a/port/android/host/host_main.c b/port/android/host/host_main.c index 2e3479df..71cf14c1 100644 --- a/port/android/host/host_main.c +++ b/port/android/host/host_main.c @@ -25,12 +25,14 @@ that runs here. #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -137,6 +139,52 @@ struct environment int count; }; +/* asks the activity for Android's system gesture insets via JNI +(HaloActivity.getSystemGestureInsetsPixels); the guest calls it at every +finger down because the insets change when the phone rotates; the method is +found on the activity's own class because the game's native thread has the +system's class loader, which does not know the app's classes; a change is +logged so the log shows the value the game works with; all 0 on any failure +(the touch controls then use the whole screen) */ +void host_gesture_insets(int *insets) +{ + static int logged[4] = { -1, -1, -1, -1 }; + JNIEnv *env = (JNIEnv *)SDL_GetAndroidJNIEnv(); + jobject activity = (jobject)SDL_GetAndroidActivity(); + jint values[4] = { 0, 0, 0, 0 }; + int index; + + if (env && activity) + { + jclass activity_class = (*env)->GetObjectClass(env, activity); + jmethodID method = activity_class ? (*env)->GetMethodID(env, activity_class, "getSystemGestureInsetsPixels", "()[I") : NULL; + jintArray array = method ? (jintArray)(*env)->CallObjectMethod(env, activity, method) : NULL; + + if (array && !(*env)->ExceptionCheck(env) && (*env)->GetArrayLength(env, array) == 4) + (*env)->GetIntArrayRegion(env, array, 0, 4, values); + /* a Java exception left pending would break the next JNI call of the + thread, and a failed read must leave zeros */ + if ((*env)->ExceptionCheck(env)) + { + (*env)->ExceptionClear(env); + for (index = 0; index < 4; index++) + values[index] = 0; + } + if (array) + (*env)->DeleteLocalRef(env, array); + if (activity_class) + (*env)->DeleteLocalRef(env, activity_class); + (*env)->DeleteLocalRef(env, activity); + } + for (index = 0; index < 4; index++) + insets[index] = (int)values[index]; + if (memcmp(logged, insets, sizeof(logged))) + { + memcpy(logged, insets, sizeof(logged)); + host_logf(HOST_LOG_INFO, "system gesture insets %d,%d,%d,%d", insets[0], insets[1], insets[2], insets[3]); + } +} + static void environment_set(struct environment *environment, const char *name, const char *value) { size_t length = strlen(name); @@ -205,6 +253,37 @@ static double config_number(const char *path, const char *key, double fallback) return value; } +/* a boolean setting of config.toml (a dotted name), or otherwise when the +file, the setting or a boolean is missing */ +static int config_boolean_or(const char *path, const char *name, int otherwise) +{ + toml_result_t result = toml_parse_file_ex(path); + int value = otherwise; + + if (!result.ok) + return otherwise; + { + toml_datum_t datum = toml_seek(result.toptab, name); + + if (datum.type == TOML_BOOLEAN) + value = datum.u.boolean ? 1 : 0; + } + toml_free(result); + return value; +} + +/* Whether the app runs through an ARM translator. The x86 emulator runs the +app's ARM code through one, which ro.dalvik.vm.native.bridge names; it +cannot deliver the page faults of the write tracking to the app +(host_memory.c). */ +static int native_bridge_active(void) +{ + char value[PROP_VALUE_MAX] = ""; + + __system_property_get("ro.dalvik.vm.native.bridge", value); + return value[0] && strcmp(value, "0") != 0; +} + /* POSIX TZ for the current local offset (the guest's musl has no zone database) */ static void time_zone(char *buffer, size_t size) @@ -257,6 +336,11 @@ static uint32_t make_boot(const struct environment *environment) #define MAIN_STACK_SIZE (16 * 1024 * 1024) +/* the thread that runs the game: passes the display and its density, the +time zone and the data and save folders to the guest through its +environment, chooses the write tracking (page protection, or page hashes when +translated or when debug.memory_watch is false), and runs the guest's main; +never returns normally */ static void *game_main(void *unused) { struct environment environment = { { 0 }, 0 }; @@ -370,6 +454,7 @@ static void *game_main(void *unused) (landscape) unless display.screen_width says otherwise (d3d8_gl.c) */ const SDL_DisplayMode *mode; char width[16]; + char text_density[16]; SDL_InitSubSystem(SDL_INIT_VIDEO); mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay()); @@ -382,6 +467,18 @@ static void *game_main(void *unused) environment_set(&environment, "HALO_DISPLAY_WIDTH", width); host_logf(HOST_LOG_INFO, "display %dx%d: rendering %sx480", mode->w, mode->h, width); } + { + /* dp for the touch controls (port/linux/src/touch_input.c): SDL + gives Android's densityDpi / 160 */ + float density = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); + + if (density > 0.0f) + { + snprintf(text_density, sizeof(text_density), "%g", density); + environment_set(&environment, "HALO_DISPLAY_DENSITY", text_density); + host_logf(HOST_LOG_INFO, "display density %s", text_density); + } + } } } @@ -391,6 +488,20 @@ static void *game_main(void *unused) if (config_sample_seconds(path, seconds, sizeof(seconds))) host_debug_start_sampler(seconds); } + if (native_bridge_active()) + { + host_memory_watch_use_hashes(); + host_logf(HOST_LOG_INFO, "write tracking: page hashes (ARM translation)"); + } + else if (!config_boolean_or(path, "debug.memory_watch", 1)) + { + host_memory_watch_use_hashes(); + host_logf(HOST_LOG_INFO, "write tracking: page hashes (debug.memory_watch = false)"); + } + else + { + host_logf(HOST_LOG_INFO, "write tracking: page protection"); + } boot = make_boot(&environment); host_logf(HOST_LOG_INFO, "data %s, saves %s", data_root, save_root); host_run_guest_main(boot); diff --git a/port/android/host/host_memory.c b/port/android/host/host_memory.c index 8e5a3e45..3c74519d 100644 --- a/port/android/host/host_memory.c +++ b/port/android/host/host_memory.c @@ -17,10 +17,13 @@ This file also implements guest memory write tracking (the interface of port/linux/src/memory_watch.c): the renderer write-protects the pages behind the textures it caches, and the SIGSEGV handler here records the first write to each. Other faults are reported (with guest-relative addresses) and -passed on to the previous handler. +passed on to the previous handler. Under ARM translation (the x86 emulator) +page faults cannot be caught, and the tracking compares page contents +instead (host_watch_hash.c). */ #include "host.h" +#include "host_watch_hash.h" #include #include @@ -478,6 +481,21 @@ static uint8_t page_protected[WATCH_PAGE_COUNT]; static uint32_t page_generation[WATCH_PAGE_COUNT]; static volatile uint32_t current_generation = 1; static int watch_active; +static int watch_hashing; +static uint8_t page_hash_watched[WATCH_PAGE_COUNT]; +static uint64_t page_hash[WATCH_PAGE_COUNT]; +static uint32_t page_hashed_frame[WATCH_PAGE_COUNT]; +static struct watch_hash page_hashes = +{ + .base = (const uint8_t *)(uintptr_t)HALO_GUEST_WINDOW_BASE, + .page_count = WATCH_PAGE_COUNT, + .watched = page_hash_watched, + .hash = page_hash, + .hashed_frame = page_hashed_frame, + .generation = page_generation, + .current_generation = ¤t_generation, + .frame = 1, +}; static int in_window(uint64_t address) { @@ -601,6 +619,14 @@ void host_memory_watch_initialize(void) watch_active = 1; } +void host_memory_watch_use_hashes(void) +{ + watch_hashing = 1; +} + +/* memory_watch_protect: write-protects the pages of a range, or keeps their +hash under host_memory_watch_use_hashes; a size outside the guest window +is ignored */ void host_memory_watch_protect(uint32_t address, uint32_t size) { uint64_t first, last, page; @@ -611,6 +637,11 @@ void host_memory_watch_protect(uint32_t address, uint32_t size) last = watch_page((uint64_t)address + size - 1); if (last >= WATCH_PAGE_COUNT) last = WATCH_PAGE_COUNT - 1; + if (watch_hashing) + { + watch_hash_protect(&page_hashes, (uint32_t)first, (uint32_t)last); + return; + } for (page = first; page <= last; page++) { if (!page_protected[page]) @@ -626,6 +657,8 @@ uint32_t host_memory_watch_serial(void) return current_generation; } +/* memory_watch_generation: the newest generation of the pages of a range. +Under hashing this hashes the pages again, once a frame. */ uint32_t host_memory_watch_generation(uint32_t address, uint32_t size) { uint64_t first, last, page; @@ -637,6 +670,8 @@ uint32_t host_memory_watch_generation(uint32_t address, uint32_t size) last = watch_page((uint64_t)address + size - 1); if (last >= WATCH_PAGE_COUNT) last = WATCH_PAGE_COUNT - 1; + if (watch_hashing) + return watch_hash_generation(&page_hashes, (uint32_t)first, (uint32_t)last); for (page = first; page <= last; page++) { if (page_generation[page] > newest) @@ -645,11 +680,14 @@ uint32_t host_memory_watch_generation(uint32_t address, uint32_t size) return newest; } +/* memory_watch_prepare_write: unprotects the pages the host itself is about +to write into. Nothing under hashing: the write shows in the next hash and +there is no protection to fault on. */ void host_memory_watch_prepare_write(uint32_t address, uint32_t size) { uint64_t start = address, first, last, page; - if (!watch_active || !size) + if (!watch_active || watch_hashing || !size) return; if (start + size <= HALO_GUEST_WINDOW_BASE || start >= (uint64_t)HALO_GUEST_WINDOW_BASE + HALO_GUEST_WINDOW_SIZE) return; @@ -666,6 +704,8 @@ void host_memory_watch_prepare_write(uint32_t address, uint32_t size) } } +/* memory_watch_forget: the pages of a range were remapped or reprotected, so +treat them as written and unwatched */ void host_memory_watch_forget(uint32_t address, uint32_t size) { uint64_t first, last, page; @@ -676,9 +716,22 @@ void host_memory_watch_forget(uint32_t address, uint32_t size) last = watch_page((uint64_t)address + size - 1); if (last >= WATCH_PAGE_COUNT) last = WATCH_PAGE_COUNT - 1; + if (watch_hashing) + { + watch_hash_forget(&page_hashes, (uint32_t)first, (uint32_t)last); + return; + } for (page = first; page <= last; page++) { page_protected[page] = 0; page_generation[page] = __sync_add_and_fetch(¤t_generation, 1); } } + +/* memory_watch_begin_frame: pages may be hashed again; nothing under page +protection */ +void host_memory_watch_begin_frame(void) +{ + if (watch_hashing) + watch_hash_begin_frame(&page_hashes); +} diff --git a/port/android/host/host_sdl.c b/port/android/host/host_sdl.c index da0a3730..e3c5bb12 100644 --- a/port/android/host/host_sdl.c +++ b/port/android/host/host_sdl.c @@ -32,6 +32,8 @@ struct handle { int type; void *object; + /* an audio stream with a callback: its binding (below) */ + void *binding; }; static struct handle handles[HANDLE_COUNT]; @@ -59,6 +61,7 @@ static uint32_t handle_new(int type, void *object) { handles[index].type = type; handles[index].object = object; + handles[index].binding = NULL; pthread_mutex_unlock(&handle_lock); return index; } @@ -81,6 +84,30 @@ static void *handle_get(uint32_t handle, int type) return object; } +/* the object of a handle, which the handle no longer refers to; NULL when +there is none. *binding gets what handle_new's caller stored (audio) */ +static void *handle_release(uint32_t handle, int type, void **binding) +{ + void *object = NULL; + + if (binding) + *binding = NULL; + if (handle == 0 || handle >= HANDLE_COUNT) + return NULL; + pthread_mutex_lock(&handle_lock); + if (handles[handle].type == type) + { + object = handles[handle].object; + if (binding) + *binding = handles[handle].binding; + handles[handle].type = _handle_free; + handles[handle].object = NULL; + handles[handle].binding = NULL; + } + pthread_mutex_unlock(&handle_lock); + return object; +} + /* ---------- general */ int host_sdl_init(uint32_t flags) @@ -221,6 +248,15 @@ uint32_t host_sdl_gamepad_from_id(uint32_t id) return handle_new(_handle_gamepad, SDL_GetGamepadFromID((SDL_JoystickID)id)); } +/* (the event thread, when the controller goes: sdl_platform.c) */ +void host_sdl_close_gamepad(uint32_t gamepad) +{ + SDL_Gamepad *object = handle_release(gamepad, _handle_gamepad, NULL); + + if (object) + SDL_CloseGamepad(object); +} + int host_sdl_gamepad_axis(uint32_t gamepad, int axis) { SDL_Gamepad *object = handle_get(gamepad, _handle_gamepad); @@ -267,6 +303,8 @@ struct audio_binding pthread_cond_t requested; pthread_cond_t done; int pending; + /* the stream is destroyed: the thread frees the binding and ends */ + int quit; int additional; int total; unsigned char *buffer; @@ -286,8 +324,10 @@ static void *audio_thread(void *context) { int additional, total; - while (!binding->pending) + while (!binding->pending && !binding->quit) pthread_cond_wait(&binding->requested, &binding->lock); + if (binding->quit) + break; additional = binding->additional; total = binding->total; pthread_mutex_unlock(&binding->lock); @@ -298,6 +338,12 @@ static void *audio_thread(void *context) binding->pending = 0; pthread_cond_signal(&binding->done); } + pthread_mutex_unlock(&binding->lock); + pthread_cond_destroy(&binding->done); + pthread_cond_destroy(&binding->requested); + pthread_mutex_destroy(&binding->lock); + SDL_free(binding->buffer); + SDL_free(binding); return NULL; } @@ -343,9 +389,19 @@ static int audio_keep(struct audio_binding *binding, const void *data, int lengt uint32_t host_sdl_open_audio_stream(uint32_t device, const void *spec, uint32_t callback, uint32_t userdata) { - struct audio_binding *binding = SDL_calloc(1, sizeof(*binding)); + struct audio_binding *binding; SDL_AudioStream *stream; + /* (one without a callback, the microphone's, which the guest reads: + nothing to bind) */ + if (!callback) + { + stream = SDL_OpenAudioDeviceStream((SDL_AudioDeviceID)device, spec, NULL, NULL); + return stream ? handle_new(_handle_audio, stream) : 0; + } + binding = SDL_calloc(1, sizeof(*binding)); + if (!binding) + return 0; binding->callback = callback; binding->userdata = userdata; pthread_mutex_init(&binding->lock, NULL); @@ -360,7 +416,19 @@ uint32_t host_sdl_open_audio_stream(uint32_t device, const void *spec, uint32_t } /* the device starts paused, so no callback can run before this */ binding->handle = handle_new(_handle_audio, stream); - if (callback && host_native_thread_create(audio_thread, binding, 256 * 1024) != 0) + if (!binding->handle) + { + SDL_DestroyAudioStream(stream); + pthread_cond_destroy(&binding->done); + pthread_cond_destroy(&binding->requested); + pthread_mutex_destroy(&binding->lock); + SDL_free(binding); + return 0; + } + pthread_mutex_lock(&handle_lock); + handles[binding->handle].binding = binding; + pthread_mutex_unlock(&handle_lock); + if (host_native_thread_create(audio_thread, binding, 256 * 1024) != 0) host_fatal("cannot start the audio thread"); return binding->handle; } @@ -383,6 +451,43 @@ int host_sdl_resume_audio_stream_device(uint32_t stream) return object ? SDL_ResumeAudioStreamDevice(object) : 0; } +/* (voice chat's microphone: port/linux/src/voice_audio.c) */ +int host_sdl_get_audio_stream_data(uint32_t stream, void *data, int length) +{ + SDL_AudioStream *object = handle_get(stream, _handle_audio); + + return object ? SDL_GetAudioStreamData(object, data, length) : -1; +} + +int host_sdl_get_audio_stream_available(uint32_t stream) +{ + SDL_AudioStream *object = handle_get(stream, _handle_audio); + + return object ? SDL_GetAudioStreamAvailable(object) : -1; +} + +/* (a stream with a callback has a thread the guest's callbacks run on: +once no callback can run any more, it is told to end, and frees the +binding and its stack as it goes) */ +void host_sdl_destroy_audio_stream(uint32_t stream) +{ + void *context; + SDL_AudioStream *object = handle_release(stream, _handle_audio, &context); + struct audio_binding *binding = context; + + if (!object) + return; + /* (returns once a callback that is running has finished) */ + SDL_DestroyAudioStream(object); + if (binding) + { + pthread_mutex_lock(&binding->lock); + binding->quit = 1; + pthread_cond_signal(&binding->requested); + pthread_mutex_unlock(&binding->lock); + } +} + /* ---------- the clipboard (internet play's invite links) */ int host_sdl_set_clipboard_text(const char *text) diff --git a/port/android/host/host_thread.c b/port/android/host/host_thread.c index bc939b8b..1d391456 100644 --- a/port/android/host/host_thread.c +++ b/port/android/host/host_thread.c @@ -131,6 +131,10 @@ static void *thread_main(void *context) free(context); host_debug_thread_started(); start.function(start.argument); + /* (a thread the guest made has let its struct pthread go by now; one + of the host's that ran guest code, the audio thread, has it still) */ + if (guest_tp) + ((guest_function)(uintptr_t)host_image.header->thread_detach)(0, 0, 0, 0); host_debug_thread_exited(); guest_tp = 0; diff --git a/port/android/host/host_touch.c b/port/android/host/host_touch.c new file mode 100644 index 00000000..50eab656 --- /dev/null +++ b/port/android/host/host_touch.c @@ -0,0 +1,229 @@ +/* +HOST_TOUCH.C + +The on-screen touch controls between the overlay and the game. The overlay +(app/.../TouchControls.java) is an Android view over SDL's surface: it +hands its stick, buttons, view swipes and gyroscope turns over JNI on the UI +thread, and the game reads them through host imports when it reads port 0's +controller (port/linux/src/xinput_sdl.c). The game hands back what the +overlay needs to know: whether it is in a game with no menu or cinematic up +and the input.touch_controls setting (host_touch_scene), the profile's +button mapping, which names the buttons (host_touch_bindings), and how hard +port 0 rumbles. +*/ + +#include +#include +#include +#include +#include + +/* a press shorter than this still reaches the game: it reads the +controller once a frame and acts at 30 ticks a second, and a quick tap can +go down and up between two reads */ +#define TOUCH_TAP_NS 60000000LL +/* view motion and rumble older than this are dropped: left over from the +menus, a cinematic or the app in the background */ +#define TOUCH_STALE_NS 150000000LL + +/* SDL's 15 gamepad buttons, then the left and right triggers */ +#define TOUCH_INPUTS 17 + +static pthread_mutex_t touch_lock = PTHREAD_MUTEX_INITIALIZER; +/* the SDL axes (left x, y, right x, y, left trigger, right trigger), then +the SDL button bits */ +static int32_t touch_state[7]; +static int64_t touch_pressed_ns[TOUCH_INPUTS]; +static float look_delta[2]; +static int64_t look_ns; +/* the gyroscope's turn, apart: the profile's invert is the swipe's only */ +static float gyro_delta[2]; +static int64_t gyro_ns; +static int rumble_amplitude; +static int64_t rumble_ns; +static volatile int touch_scene; +/* the game control on each controller button (-1: none), and a count of +its changes, which the overlay polls */ +#define TOUCH_BUTTONS 16 +static int32_t touch_bindings[TOUCH_BUTTONS]; +static int touch_bindings_serial; + +static int64_t now_ns(void) +{ + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + return (int64_t)now.tv_sec * 1000000000LL + now.tv_nsec; +} + +/* the inputs of a state as bits: buttons, then the triggers */ +static uint32_t touch_inputs(const int32_t *state) +{ + return ((uint32_t)state[6] & 0x7fff) | (state[4] ? 1u << 15 : 0) | (state[5] ? 1u << 16 : 0); +} + +JNIEXPORT void JNICALL Java_com_halo_decomp_TouchControls_nativeState( + JNIEnv *env, jclass cls, jint lx, jint ly, jint rx, jint ry, + jint lt, jint rt, jint buttons) +{ + int32_t next[] = { lx, ly, rx, ry, lt, rt, buttons }; + uint32_t pressed; + int64_t now = now_ns(); + int input; + + (void)env; + (void)cls; + pthread_mutex_lock(&touch_lock); + pressed = touch_inputs(next) & ~touch_inputs(touch_state); + for (input = 0; input < TOUCH_INPUTS; input++) + { + if (pressed & (1u << input)) + touch_pressed_ns[input] = now; + } + memcpy(touch_state, next, sizeof(next)); + pthread_mutex_unlock(&touch_lock); +} + +/* the guest's: the overlay's controller state, with each press held for +at least TOUCH_TAP_NS */ +void host_touch_read(int32_t *state) +{ + int64_t now = now_ns(); + int input; + + pthread_mutex_lock(&touch_lock); + memcpy(state, touch_state, sizeof(touch_state)); + for (input = 0; input < TOUCH_INPUTS; input++) + { + if (touch_pressed_ns[input] && now - touch_pressed_ns[input] < TOUCH_TAP_NS) + { + if (input < 15) + state[6] |= 1 << input; + else if (!state[4 + input - 15]) + state[4 + input - 15] = 32767; + } + } + pthread_mutex_unlock(&touch_lock); +} + +JNIEXPORT void JNICALL Java_com_halo_decomp_TouchControls_nativeLook( + JNIEnv *env, jclass cls, jfloat dx, jfloat dy) +{ + (void)env; + (void)cls; + pthread_mutex_lock(&touch_lock); + look_delta[0] += dx; + look_delta[1] += dy; + look_ns = now_ns(); + pthread_mutex_unlock(&touch_lock); +} + +JNIEXPORT void JNICALL Java_com_halo_decomp_TouchControls_nativeGyro( + JNIEnv *env, jclass cls, jfloat dx, jfloat dy) +{ + (void)env; + (void)cls; + pthread_mutex_lock(&touch_lock); + gyro_delta[0] += dx; + gyro_delta[1] += dy; + gyro_ns = now_ns(); + pthread_mutex_unlock(&touch_lock); +} + +JNIEXPORT void JNICALL Java_com_halo_decomp_TouchControls_nativeLookReset(JNIEnv *env, jclass cls) +{ + (void)env; + (void)cls; + pthread_mutex_lock(&touch_lock); + look_delta[0] = look_delta[1] = 0.0f; + gyro_delta[0] = gyro_delta[1] = 0.0f; + pthread_mutex_unlock(&touch_lock); +} + +/* the guest's: the view swipe, then the gyroscope's turn, since the last +read, into delta[4], in the overlay's logical pixels with its sensitivity +applied */ +void host_touch_look_read(float *delta) +{ + int64_t now = now_ns(); + + pthread_mutex_lock(&touch_lock); + memcpy(delta, look_delta, sizeof(look_delta)); + memcpy(delta + 2, gyro_delta, sizeof(gyro_delta)); + if (now - look_ns > TOUCH_STALE_NS) + delta[0] = delta[1] = 0.0f; + if (now - gyro_ns > TOUCH_STALE_NS) + delta[2] = delta[3] = 0.0f; + look_delta[0] = look_delta[1] = 0.0f; + gyro_delta[0] = gyro_delta[1] = 0.0f; + pthread_mutex_unlock(&touch_lock); +} + +/* the guest's: port 0's motors (XInputSetState), each 0..65535; the game +sends them every frame, and zero while paused or with the profile's +vibration off */ +void host_touch_rumble(unsigned int low, unsigned int high) +{ + unsigned int strength = low > high ? low : high; + + pthread_mutex_lock(&touch_lock); + rumble_amplitude = strength ? 64 + (int)(strength * 191u / 65535u) : 0; + rumble_ns = now_ns(); + pthread_mutex_unlock(&touch_lock); +} + +/* the phone's vibration strength, 0 or 64..255 */ +JNIEXPORT jint JNICALL Java_com_halo_decomp_TouchControls_nativeRumble(JNIEnv *env, jclass cls) +{ + int amplitude; + + (void)env; + (void)cls; + pthread_mutex_lock(&touch_lock); + amplitude = now_ns() - rumble_ns > TOUCH_STALE_NS ? 0 : rumble_amplitude; + pthread_mutex_unlock(&touch_lock); + return amplitude; +} + +/* the guest's, at every read of port 0: port/linux/src/touch_input.c's +_touch_scene_* bits */ +void host_touch_scene(int scene) +{ + touch_scene = scene; +} + +/* the guest's, when the profile's mapping changes: the game control on +each of the 16 controller buttons (port/linux/game/touch_game.c) */ +void host_touch_bindings(const int32_t *controls) +{ + pthread_mutex_lock(&touch_lock); + memcpy(touch_bindings, controls, sizeof(touch_bindings)); + touch_bindings_serial++; + pthread_mutex_unlock(&touch_lock); +} + +/* the mapping into controls[16]; returns its count of changes (0 before the +game has sent it) */ +JNIEXPORT jint JNICALL Java_com_halo_decomp_TouchControls_nativeBindings(JNIEnv *env, jclass cls, + jintArray controls) +{ + int32_t copy[TOUCH_BUTTONS]; + int serial; + + (void)cls; + pthread_mutex_lock(&touch_lock); + memcpy(copy, touch_bindings, sizeof(copy)); + serial = touch_bindings_serial; + pthread_mutex_unlock(&touch_lock); + if (controls && (*env)->GetArrayLength(env, controls) >= TOUCH_BUTTONS) + (*env)->SetIntArrayRegion(env, controls, 0, TOUCH_BUTTONS, (const jint *)copy); + return serial; +} + +/* 0 until the game has read its controller once */ +JNIEXPORT jint JNICALL Java_com_halo_decomp_TouchControls_nativeScene(JNIEnv *env, jclass cls) +{ + (void)env; + (void)cls; + return touch_scene; +} diff --git a/port/android/host/host_watch_hash.c b/port/android/host/host_watch_hash.c new file mode 100644 index 00000000..7bf2a6fb --- /dev/null +++ b/port/android/host/host_watch_hash.c @@ -0,0 +1,114 @@ +/* +HOST_WATCH_HASH.C + +Write tracking by page contents (host_watch_hash.h). +*/ + +#include "host_watch_hash.h" + +/* +FNV-style mixing in four independent lanes, so that the multiplies of +one lane do not wait for another's. A change of one word changes its +lane's state for good (xor and an odd multiply are both invertible), so +a single-word write always shows. Changes of several words could cancel +each other, which is unlikely for game data. (host_watch_hash.h) +*/ +uint64_t watch_hash_page(const uint8_t *page) +{ + const uint64_t *words = (const uint64_t *)page; + uint64_t lane0 = 0xcbf29ce484222325ULL, lane1 = 0x9e3779b97f4a7c15ULL; + uint64_t lane2 = 0xc2b2ae3d27d4eb4fULL, lane3 = 0x165667b19e3779f9ULL; + uint32_t index; + + for (index = 0; index < WATCH_HASH_PAGE_SIZE / 8; index += 4) + { + lane0 = (lane0 ^ words[index + 0]) * 0x100000001b3ULL; + lane1 = (lane1 ^ words[index + 1]) * 0x100000001b3ULL; + lane2 = (lane2 ^ words[index + 2]) * 0x100000001b3ULL; + lane3 = (lane3 ^ words[index + 3]) * 0x100000001b3ULL; + } + return lane0 ^ ((lane1 << 16) | (lane1 >> 48)) ^ ((lane2 << 32) | (lane2 >> 32)) ^ + ((lane3 << 48) | (lane3 >> 16)); +} + +/* clamps last to the watched memory; 0 when nothing is left of the range */ +static int clamp(const struct watch_hash *watch, uint32_t first, uint32_t *last) +{ + if (first >= watch->page_count || first > *last) + return 0; + if (*last >= watch->page_count) + *last = watch->page_count - 1; + return 1; +} + +static const uint8_t *page_memory(const struct watch_hash *watch, uint32_t page) +{ + return watch->base + (uint64_t)page * WATCH_HASH_PAGE_SIZE; +} + +void watch_hash_protect(struct watch_hash *watch, uint32_t first, uint32_t last) +{ + uint32_t page; + + if (!clamp(watch, first, &last)) + return; + for (page = first; page <= last; page++) + { + if (watch->watched[page]) + continue; + watch->watched[page] = 1; + watch->hash[page] = watch_hash_page(page_memory(watch, page)); + watch->hashed_frame[page] = watch->frame; + } +} + +uint32_t watch_hash_generation(struct watch_hash *watch, uint32_t first, uint32_t last) +{ + uint32_t page, newest = 0; + + if (!clamp(watch, first, &last)) + return 0; + for (page = first; page <= last; page++) + { + if (watch->watched[page] && watch->hashed_frame[page] != watch->frame) + { + uint64_t hash = watch_hash_page(page_memory(watch, page)); + + watch->hashed_frame[page] = watch->frame; + if (hash != watch->hash[page]) + { + watch->hash[page] = hash; + watch->generation[page] = __sync_add_and_fetch(watch->current_generation, 1); + } + } + if (watch->generation[page] > newest) + newest = watch->generation[page]; + } + return newest; +} + +void watch_hash_forget(struct watch_hash *watch, uint32_t first, uint32_t last) +{ + uint32_t page; + + if (!clamp(watch, first, &last)) + return; + for (page = first; page <= last; page++) + { + watch->watched[page] = 0; + watch->generation[page] = __sync_add_and_fetch(watch->current_generation, 1); + } +} + +/* +The serial changes each frame, so a cache that skips generation queries +while the serial stays the same (xbox_textures.c's recent_textures) asks +again once a frame. (host_watch_hash.h) +*/ +void watch_hash_begin_frame(struct watch_hash *watch) +{ + watch->frame++; + if (!watch->frame) + watch->frame = 1; + __sync_add_and_fetch(watch->current_generation, 1); +} diff --git a/port/android/host/host_watch_hash.h b/port/android/host/host_watch_hash.h new file mode 100644 index 00000000..8f0838f3 --- /dev/null +++ b/port/android/host/host_watch_hash.h @@ -0,0 +1,57 @@ +/* +HOST_WATCH_HASH.H + +Write tracking by page contents, for processes that cannot catch their own +page protection faults: the ARM translation of the x86 Android emulator +cannot deliver SIGSEGV to the app, so the protection-based tracking of +host_memory.c would stop the game at its first write to a cached page. + +A watched page's hash is kept. Asking for its generation hashes it again, +at most once a frame (watch_hash_begin_frame), and a different hash counts +as a write. A write is therefore seen in the next frame, not at once. +*/ + +#ifndef HOST_WATCH_HASH_H +#define HOST_WATCH_HASH_H + +#include + +#define WATCH_HASH_PAGE_SIZE 0x1000u + +struct watch_hash +{ + /* the memory of page 0 */ + const uint8_t *base; + uint32_t page_count; + /* page_count entries each */ + uint8_t *watched; + uint64_t *hash; + uint32_t *hashed_frame; + /* shared with the protection-based tracking (host_memory.c) */ + uint32_t *generation; + volatile uint32_t *current_generation; + uint32_t frame; +}; + +/* a hash of one page (WATCH_HASH_PAGE_SIZE bytes, 8-byte aligned) that +changes whenever a word of it changes */ +uint64_t watch_hash_page(const uint8_t *page); + +/* Starts watching pages first to last (inclusive), remembering their hashes. +Pages already watched keep their hash; pages beyond page_count are +ignored. */ +void watch_hash_protect(struct watch_hash *watch, uint32_t first, uint32_t last); + +/* The newest generation among pages first to last (inclusive), after hashing +again the watched ones not yet hashed this frame; a page whose hash +changed gets a new generation. 0 if the range is outside the watched +memory. */ +uint32_t watch_hash_generation(struct watch_hash *watch, uint32_t first, uint32_t last); + +/* The pages were remapped: treat them as written and unwatched. */ +void watch_hash_forget(struct watch_hash *watch, uint32_t first, uint32_t last); + +/* A new frame: pages may be hashed again, and the serial changes. */ +void watch_hash_begin_frame(struct watch_hash *watch); + +#endif diff --git a/port/android/host_imports.list b/port/android/host_imports.list index 5c39a447..b64ccb87 100644 --- a/port/android/host_imports.list +++ b/port/android/host_imports.list @@ -13,6 +13,7 @@ host_memory_watch_generation host_memory_watch_serial host_memory_watch_prepare_write host_memory_watch_forget +host_memory_watch_begin_frame host_sdl_init host_sdl_set_hint host_sdl_get_error @@ -38,11 +39,15 @@ host_sdl_gamepad_axis host_sdl_gamepad_button host_sdl_gamepad_type host_sdl_rumble_gamepad +host_sdl_close_gamepad host_sdl_scancode_name host_sdl_scancode_from_name host_sdl_open_audio_stream host_sdl_put_audio_stream_data host_sdl_resume_audio_stream_device +host_sdl_get_audio_stream_data +host_sdl_get_audio_stream_available +host_sdl_destroy_audio_stream host_gl_get_string host_gl_has_extension host_gl_read_buffer @@ -64,5 +69,11 @@ host_xr_copy_layer_array host_xr_refresh_rates host_xr_set_refresh_rate host_android_path +host_gesture_insets +host_touch_read +host_touch_look_read +host_touch_rumble +host_touch_scene +host_touch_bindings host_errno diff --git a/port/android/include/halo_android_abi.h b/port/android/include/halo_android_abi.h index baa28789..3b9c2963 100644 --- a/port/android/include/halo_android_abi.h +++ b/port/android/include/halo_android_abi.h @@ -35,7 +35,7 @@ keeps its heaps low in the address space and fills it upwards */ #define HALO_GUEST_WINDOW_SIZE 0x08000000u #define HALO_GUEST_MAGIC 0x4f4c4148u /* 'HALO' */ -#define HALO_GUEST_ABI_VERSION 1 +#define HALO_GUEST_ABI_VERSION 2 /* at HALO_GUEST_IMAGE_BASE */ struct halo_guest_header @@ -49,6 +49,7 @@ struct halo_guest_header uint32_t start; /* void __guest_start(struct halo_guest_boot *) */ uint32_t thread_start; /* void __guest_thread_start(uint32_t thread) */ uint32_t thread_attach; /* uint32_t __guest_thread_attach(void) */ + uint32_t thread_detach; /* void __guest_thread_detach(void) */ uint32_t init_array_start; /* void (*)(void) entries, 4 bytes each */ uint32_t init_array_end; }; diff --git a/port/android/tests/host_watch_hash_test.c b/port/android/tests/host_watch_hash_test.c new file mode 100644 index 00000000..9b8515eb --- /dev/null +++ b/port/android/tests/host_watch_hash_test.c @@ -0,0 +1,216 @@ +/* +HOST_WATCH_HASH_TEST.C + +Tests of the page-hash write tracking (port/android/host/host_watch_hash.c). +Built and run by tools/test_host_watch_hash.py; exits nonzero on a failure. +*/ + +#include "../host/host_watch_hash.h" + +#include +#include +#include + +#define PAGES 4 + +static int failures; + +#define CHECK(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + failures++; \ + } \ + } while (0) + +struct fixture +{ + uint8_t *memory; + uint8_t watched[PAGES]; + uint64_t hash[PAGES]; + uint32_t hashed_frame[PAGES]; + uint32_t generation[PAGES]; + volatile uint32_t current; + struct watch_hash watch; +}; + +static void setup(struct fixture *f) +{ + memset(f, 0, sizeof(*f)); + f->memory = aligned_alloc(WATCH_HASH_PAGE_SIZE, PAGES * WATCH_HASH_PAGE_SIZE); + memset(f->memory, 0x5a, PAGES * WATCH_HASH_PAGE_SIZE); + f->current = 1; + f->watch.base = f->memory; + f->watch.page_count = PAGES; + f->watch.watched = f->watched; + f->watch.hash = f->hash; + f->watch.hashed_frame = f->hashed_frame; + f->watch.generation = f->generation; + f->watch.current_generation = &f->current; + f->watch.frame = 1; +} + +static void teardown(struct fixture *f) +{ + free(f->memory); +} + +static uint8_t *byte_of(struct fixture *f, uint32_t page, uint32_t offset) +{ + return f->memory + page * WATCH_HASH_PAGE_SIZE + offset; +} + +static void test_unchanged_page_keeps_generation(void) +{ + struct fixture f; + uint32_t before; + + setup(&f); + watch_hash_protect(&f.watch, 0, 0); + before = watch_hash_generation(&f.watch, 0, 0); + watch_hash_begin_frame(&f.watch); + CHECK(watch_hash_generation(&f.watch, 0, 0) == before); + teardown(&f); +} + +static void test_write_is_seen_next_frame(void) +{ + struct fixture f; + uint32_t before, after; + + setup(&f); + watch_hash_protect(&f.watch, 1, 1); + before = watch_hash_generation(&f.watch, 1, 1); + *byte_of(&f, 1, 100) ^= 1; + /* the page was hashed in this frame already */ + CHECK(watch_hash_generation(&f.watch, 1, 1) == before); + watch_hash_begin_frame(&f.watch); + after = watch_hash_generation(&f.watch, 1, 1); + CHECK(after > before); + CHECK(f.current >= after); + teardown(&f); +} + +static void test_any_byte_is_seen(void) +{ + static const uint32_t offsets[] = { 0, 2047, WATCH_HASH_PAGE_SIZE - 1 }; + unsigned index; + + for (index = 0; index < sizeof(offsets) / sizeof(offsets[0]); index++) + { + struct fixture f; + uint32_t before; + + setup(&f); + watch_hash_protect(&f.watch, 2, 2); + watch_hash_begin_frame(&f.watch); + before = watch_hash_generation(&f.watch, 2, 2); + *byte_of(&f, 2, offsets[index]) += 1; + watch_hash_begin_frame(&f.watch); + CHECK(watch_hash_generation(&f.watch, 2, 2) > before); + teardown(&f); + } +} + +static void test_hash_at_most_once_per_frame(void) +{ + struct fixture f; + uint32_t before; + + setup(&f); + watch_hash_protect(&f.watch, 0, 0); + watch_hash_begin_frame(&f.watch); + before = watch_hash_generation(&f.watch, 0, 0); + CHECK(f.hashed_frame[0] == f.watch.frame); + *byte_of(&f, 0, 8) ^= 0xff; + CHECK(watch_hash_generation(&f.watch, 0, 0) == before); + watch_hash_begin_frame(&f.watch); + CHECK(watch_hash_generation(&f.watch, 0, 0) > before); + teardown(&f); +} + +static void test_unwatched_page_is_not_hashed(void) +{ + struct fixture f; + + setup(&f); + *byte_of(&f, 3, 0) ^= 1; + watch_hash_begin_frame(&f.watch); + CHECK(watch_hash_generation(&f.watch, 3, 3) == 0); + CHECK(f.hashed_frame[3] == 0); + teardown(&f); +} + +static void test_forget_unwatches_and_bumps(void) +{ + struct fixture f; + uint32_t before; + + setup(&f); + watch_hash_protect(&f.watch, 2, 2); + before = watch_hash_generation(&f.watch, 2, 2); + watch_hash_forget(&f.watch, 2, 2); + CHECK(f.watched[2] == 0); + CHECK(watch_hash_generation(&f.watch, 2, 2) > before); + teardown(&f); +} + +static void test_range_returns_newest(void) +{ + struct fixture f; + uint32_t newest; + + setup(&f); + watch_hash_protect(&f.watch, 0, 3); + watch_hash_begin_frame(&f.watch); + *byte_of(&f, 3, 5) ^= 1; + watch_hash_begin_frame(&f.watch); + newest = watch_hash_generation(&f.watch, 0, 3); + CHECK(newest == f.generation[3]); + CHECK(watch_hash_generation(&f.watch, 0, 2) < newest); + teardown(&f); +} + +static void test_begin_frame_changes_serial(void) +{ + struct fixture f; + uint32_t serial; + + setup(&f); + serial = f.current; + watch_hash_begin_frame(&f.watch); + CHECK(f.current != serial); + teardown(&f); +} + +static void test_ranges_are_clamped(void) +{ + struct fixture f; + + setup(&f); + watch_hash_protect(&f.watch, 2, 99); + CHECK(f.watched[2] == 1 && f.watched[3] == 1); + CHECK(watch_hash_generation(&f.watch, 5, 9) == 0); + watch_hash_forget(&f.watch, 7, 8); + teardown(&f); +} + +int main(void) +{ + test_unchanged_page_keeps_generation(); + test_write_is_seen_next_frame(); + test_any_byte_is_seen(); + test_hash_at_most_once_per_frame(); + test_unwatched_page_is_not_hashed(); + test_forget_unwatches_and_bumps(); + test_range_returns_newest(); + test_begin_frame_changes_serial(); + test_ranges_are_clamped(); + if (failures) + printf("%d failures\n", failures); + else + printf("all tests passed\n"); + return failures != 0; +} diff --git a/port/assets/icon/opence-icon-256.png b/port/assets/icon/opence-icon-256.png new file mode 100644 index 00000000..71f19675 Binary files /dev/null and b/port/assets/icon/opence-icon-256.png differ diff --git a/port/assets/icon/opence-icon-render.png b/port/assets/icon/opence-icon-render.png new file mode 100644 index 00000000..e4c1c20f Binary files /dev/null and b/port/assets/icon/opence-icon-render.png differ diff --git a/port/assets/icon/opence-icon.png b/port/assets/icon/opence-icon.png new file mode 100644 index 00000000..99e27c63 Binary files /dev/null and b/port/assets/icon/opence-icon.png differ diff --git a/port/assets/icons/lucide/LICENSE b/port/assets/icons/lucide/LICENSE new file mode 100644 index 00000000..718bb3f0 --- /dev/null +++ b/port/assets/icons/lucide/LICENSE @@ -0,0 +1,43 @@ +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/port/assets/icons/lucide/README.md b/port/assets/icons/lucide/README.md new file mode 100644 index 00000000..534da090 --- /dev/null +++ b/port/assets/icons/lucide/README.md @@ -0,0 +1,11 @@ +# Lucide icons + +Two icons of [Lucide](https://lucide.dev) 1.53.0 (ISC licence: `LICENSE`), +as `icons/` has them at the `1.53.0` tag of +https://github.com/lucide-icons/lucide: + +- `volume-2.svg`: voice chat's speaker, of a player who talks; +- `volume-x.svg`: ... of a player muted. + +`tools/ce_menus.py` draws them, white, as the menus' bitmap `voice/speaker` +(port/linux/game/network_voice.c tints it). Credited in `AUTHORS.md`. diff --git a/port/assets/icons/lucide/volume-2.svg b/port/assets/icons/lucide/volume-2.svg new file mode 100644 index 00000000..519e6cbd --- /dev/null +++ b/port/assets/icons/lucide/volume-2.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/port/assets/icons/lucide/volume-x.svg b/port/assets/icons/lucide/volume-x.svg new file mode 100644 index 00000000..3e260094 --- /dev/null +++ b/port/assets/icons/lucide/volume-x.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/port/assets/menus/NON_HANDDRAWN.md b/port/assets/menus/NON_HANDDRAWN.md index 3e973564..2177f58a 100644 --- a/port/assets/menus/NON_HANDDRAWN.md +++ b/port/assets/menus/NON_HANDDRAWN.md @@ -62,7 +62,7 @@ until it is redrawn. | `ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png` | 512x256 | `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png` | | `ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png` | 512x256 | `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png` | -13 of 157 frames are placeholders. +13 of 161 frames are placeholders. The PC version's `ui\gamespy` and `ui\ticker` fonts are drawn with `ui\small_ui`, which the Xbox's map has. diff --git a/port/assets/menus/README.md b/port/assets/menus/README.md index 32190ad1..91332b25 100644 --- a/port/assets/menus/README.md +++ b/port/assets/menus/README.md @@ -30,6 +30,38 @@ file, the line and the problem, and the game uses the Xbox's menus. The setting `display.menus = "xbox"` also uses them. `debug.menu_open` starts on one screen (by its name, `main_menu/settings_select/...`), for looking at it. +## Writing them again + +`tools/ce_menus.py` writes every file in `ce/` and `menus.json` again, +the settings screens too (it runs `tools/port_settings.py`'s): + +```sh +python3 tools/ce_menus.py \ + --tags /tags \ + --definitions /src/tag/hek/definition \ + --redraws \ + --placeholder \ + --pictures +``` + +- `--tags`: the PC version's tags (its `ui\shell` widgets, strings and + bitmaps). +- `--definitions`: Invader's tag definitions, which say how the tags are + laid out. +- `--redraws`: the hand-made SVG redraws, copied to `svg/`. +- `--placeholder`: the picture drawn where a picture has no redraw and the + Xbox's map has not got it. +- `--pictures` (optional): the PC version's pictures, of which only where + each sits in its frame is read. + +It needs `rsvg-convert`, Pillow, NumPy and SciPy. It writes every file, so +`git status` should show only the screens meant to change; then +`ninja linux` (or `ninja android`) embeds them in the game. + +A settings screen (Controls Setup, Gamepads, Mouse, Audio, Video or Network +Setup) is changed in `tools/port_settings.py`, not in its XML, and written +with the command above. + ## Elements A file holds one `` element (`root` names the main menu's widget, in diff --git a/port/assets/menus/UNWIRED.md b/port/assets/menus/UNWIRED.md index 3b20f26a..27d20537 100644 --- a/port/assets/menus/UNWIRED.md +++ b/port/assets/menus/UNWIRED.md @@ -10,7 +10,7 @@ do (save a setting, join a game) does not happen; the screens open and close as | Function | Uses | | | --- | --- | --- | -| `common button bar update` | 25 | PC function | +| `common button bar update` | 27 | PC function | | `direct ip connect init` | 1 | PC function | | `direct ip connect update` | 1 | PC function | | `direct ip edit field` | 4 | PC function | @@ -21,7 +21,7 @@ do (save a setting, join a game) does not happen; the screens open and close as | `gamespy update filter settings` | 2 | PC function | | `gt edit list update` | 1 | PC function | | `gt select list update` | 1 | PC function | -| `mouse spinner 1wide click` | 72 | PC function | +| `mouse spinner 1wide click` | 82 | PC function | | `mp map list update` | 1 | PC function | | `mp prof init teamplay options` | 1 | PC function | | `mp prof init vehicle options` | 1 | PC function | diff --git a/port/assets/menus/ce/bitmaps.xml b/port/assets/menus/ce/bitmaps.xml index 2c12b6c6..1fd31ec1 100644 --- a/port/assets/menus/ce/bitmaps.xml +++ b/port/assets/menus/ce/bitmaps.xml @@ -332,6 +332,12 @@ + + + + + + @@ -344,6 +350,10 @@ + + + + diff --git a/port/assets/menus/ce/main_menu.multiplayer_type_select.server_settings.xml b/port/assets/menus/ce/main_menu.multiplayer_type_select.server_settings.xml index 31f42d3d..732cb636 100644 --- a/port/assets/menus/ce/main_menu.multiplayer_type_select.server_settings.xml +++ b/port/assets/menus/ce/main_menu.multiplayer_type_select.server_settings.xml @@ -19,11 +19,8 @@ - - - - - + + @@ -89,64 +86,25 @@ - + - - - + + + + + - + - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -233,11 +191,8 @@ - - - - - + + @@ -253,6 +208,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -261,9 +330,152 @@ + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/port/assets/menus/ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.port.xml b/port/assets/menus/ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.port.xml new file mode 100644 index 00000000..f80aee33 --- /dev/null +++ b/port/assets/menus/ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.port.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/port/assets/menus/ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.xml b/port/assets/menus/ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.xml index 898b5c7e..12bebaa8 100644 --- a/port/assets/menus/ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.xml +++ b/port/assets/menus/ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.xml @@ -48,6 +48,11 @@ + + + + + diff --git a/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.audio_settings.xml b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.audio_settings.xml index 55a92dd3..3ad3251d 100644 --- a/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.audio_settings.xml +++ b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.audio_settings.xml @@ -13,11 +13,22 @@ - - - - - + + + + + + + + + + + + + + + + @@ -25,11 +36,29 @@ + + + + + + + + + + + + + + + + + + - + @@ -38,7 +67,7 @@ - + @@ -47,7 +76,7 @@ - + @@ -56,7 +85,7 @@ - + @@ -65,11 +94,29 @@ - + + + + + + + + + + + + + + + + + + + @@ -81,18 +128,26 @@ + + + + + + + + diff --git a/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.mouse_settings.xml b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.mouse_settings.xml index a26889d4..a3590681 100644 --- a/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.mouse_settings.xml +++ b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.mouse_settings.xml @@ -30,7 +30,7 @@ - + @@ -39,7 +39,7 @@ - + diff --git a/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.fov_viewmodels.xml b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.fov_viewmodels.xml new file mode 100644 index 00000000..faf580b8 --- /dev/null +++ b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.fov_viewmodels.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.graphics.xml b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.graphics.xml new file mode 100644 index 00000000..f6396ebd --- /dev/null +++ b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.graphics.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.xml b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.xml index fb8f6484..0173ec76 100644 --- a/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.xml +++ b/port/assets/menus/ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.xml @@ -14,19 +14,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -106,59 +106,21 @@ - - - + + + + + - + - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -179,12 +141,8 @@ - - - - - - + + @@ -196,11 +154,7 @@ - - - - - - + + diff --git a/port/assets/menus/ce/port/main_menu/multiplayer_type_select/server_settings/coop_options/header_coop_options.png b/port/assets/menus/ce/port/main_menu/multiplayer_type_select/server_settings/coop_options/header_coop_options.png new file mode 100644 index 00000000..e24a4f60 Binary files /dev/null and b/port/assets/menus/ce/port/main_menu/multiplayer_type_select/server_settings/coop_options/header_coop_options.png differ diff --git a/port/assets/menus/ce/port/main_menu/multiplayer_type_select/server_settings/voice_options/header_voice_options.png b/port/assets/menus/ce/port/main_menu/multiplayer_type_select/server_settings/voice_options/header_voice_options.png new file mode 100644 index 00000000..d1dab3f9 Binary files /dev/null and b/port/assets/menus/ce/port/main_menu/multiplayer_type_select/server_settings/voice_options/header_voice_options.png differ diff --git a/port/assets/menus/ce/port/voice/speaker__0.png b/port/assets/menus/ce/port/voice/speaker__0.png new file mode 100644 index 00000000..68340095 Binary files /dev/null and b/port/assets/menus/ce/port/voice/speaker__0.png differ diff --git a/port/assets/menus/ce/port/voice/speaker__1.png b/port/assets/menus/ce/port/voice/speaker__1.png new file mode 100644 index 00000000..1447dc5a Binary files /dev/null and b/port/assets/menus/ce/port/voice/speaker__1.png differ diff --git a/port/assets/menus/ce/strings.xml b/port/assets/menus/ce/strings.xml index f267aa32..99d8a5e9 100644 --- a/port/assets/menus/ce/strings.xml +++ b/port/assets/menus/ce/strings.xml @@ -1023,11 +1023,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1111,7 +1138,6 @@ - diff --git a/port/assets/menus/menus.json b/port/assets/menus/menus.json index 6986c10f..b7f114c4 100644 --- a/port/assets/menus/menus.json +++ b/port/assets/menus/menus.json @@ -35,6 +35,7 @@ "ce/main_menu.settings_select.multiplayer_setup.playlist_edit.slayer_edit.xml", "ce/main_menu.settings_select.multiplayer_setup.playlist_edit.xml", "ce/main_menu.settings_select.multiplayer_setup.playlist_select.xml", + "ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.port.xml", "ce/main_menu.settings_select.multiplayer_setup.teamplay_options_edit.xml", "ce/main_menu.settings_select.multiplayer_setup.vehicle_options_edit.xml", "ce/main_menu.settings_select.multiplayer_setup.xml", @@ -45,6 +46,8 @@ "ce/main_menu.settings_select.player_setup.player_profile_edit.mouse_settings.xml", "ce/main_menu.settings_select.player_setup.player_profile_edit.name_edit.xml", "ce/main_menu.settings_select.player_setup.player_profile_edit.network_setup.xml", + "ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.fov_viewmodels.xml", + "ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.graphics.xml", "ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.xml", "ce/main_menu.settings_select.player_setup.player_profile_edit.xml", "ce/main_menu.solo_level_select.xml", @@ -58,12 +61,16 @@ "ce/port/main_menu/multiplayer_type_select/join_game/header_server_browser.png", "ce/port/main_menu/multiplayer_type_select/lobby/header_add_player.png", "ce/port/main_menu/multiplayer_type_select/lobby/header_lobby.png", + "ce/port/main_menu/multiplayer_type_select/server_settings/coop_options/header_coop_options.png", + "ce/port/main_menu/multiplayer_type_select/server_settings/voice_options/header_voice_options.png", "ce/port/pause/pausebox_center__0.png", "ce/port/pause/pausebox_center__1.png", "ce/port/pause/pausebox_left__0.png", "ce/port/pause/pausebox_left__1.png", "ce/port/pause/pausebox_right__0.png", "ce/port/pause/pausebox_right__1.png", + "ce/port/voice/speaker__0.png", + "ce/port/voice/speaker__1.png", "ce/shell/bitmaps/alert_bkd__0.png", "ce/shell/bitmaps/arrow_sm_down__0.png", "ce/shell/bitmaps/arrow_sm_down__1.png", diff --git a/port/assets/menus/port_svg/main_menu/multiplayer_type_select/server_settings/coop_options/header_coop_options.svg b/port/assets/menus/port_svg/main_menu/multiplayer_type_select/server_settings/coop_options/header_coop_options.svg new file mode 100644 index 00000000..3e25b31b --- /dev/null +++ b/port/assets/menus/port_svg/main_menu/multiplayer_type_select/server_settings/coop_options/header_coop_options.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/port/assets/menus/port_svg/main_menu/multiplayer_type_select/server_settings/voice_options/header_voice_options.svg b/port/assets/menus/port_svg/main_menu/multiplayer_type_select/server_settings/voice_options/header_voice_options.svg new file mode 100644 index 00000000..e85d5312 --- /dev/null +++ b/port/assets/menus/port_svg/main_menu/multiplayer_type_select/server_settings/voice_options/header_voice_options.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/port/assets/menus/port_svg/voice/speaker__0.svg b/port/assets/menus/port_svg/voice/speaker__0.svg new file mode 100644 index 00000000..030a3541 --- /dev/null +++ b/port/assets/menus/port_svg/voice/speaker__0.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/port/assets/menus/port_svg/voice/speaker__1.svg b/port/assets/menus/port_svg/voice/speaker__1.svg new file mode 100644 index 00000000..80dfa19c --- /dev/null +++ b/port/assets/menus/port_svg/voice/speaker__1.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/port/linux/NETCODE.md b/port/linux/NETCODE.md index 001ca220..890a1e4f 100644 --- a/port/linux/NETCODE.md +++ b/port/linux/NETCODE.md @@ -90,7 +90,9 @@ with ideas from VALORANT's netcode articles, keeping the 30 Hz tick: - **Corrections.** The host sends each client the authoritative state of the players' units and the game's moving objects; a client moves its copies toward it, a small error half of the way each tick, a larger one - at once, drawn gliding from where they were. A client's own unit and + at once, drawn gliding from where they were (a player's moved at once + is on the teleporter it lands on, as a teleporter leaves one it sends, + so the client's does not send it back). A client's own unit and vehicle are only corrected past a tolerance, so prediction does not rubber-band: the host tells the client which of the client's ticks it has its player at (its prediction come back), and the client compares @@ -231,6 +233,45 @@ machine tells them: anyone with administrator or root access can change them, and players behind one address share it. The console's `kick ` drops a player as `ban` does (every machine told), but adds no line and keeps no address out: the player may join again at once. + +The players' votes to kick a player (`network_votekick.c`): a client sends +`_distributed_message_votekick` (the player's absolute index) reliably, and +the host takes it only from the client's stream. The host alone counts: +one vote per real address (an internet play peer's endpoint, not its +stand-in), and votes again deduplicated by hardware id, which only ever +removes votes (the players who may vote are counted by address alone, so a +copied hardware id cannot lower the votes needed). A vote needs more than +half of the players who may vote, the target counted, and at least two; +starting one needs `network.votekick_minutes` played on this host in its +own ticks, voting two minutes; one vote runs at a time, and a failed vote +cools its starter down and protects its target. A vote that passes kicks as +the `kick` command does, and the host refuses the address and hardware id +for `network.votekick_ban_minutes`. The host sends each client +`_distributed_message_votekick_status` every second while a vote runs (and +as it changes), for the scoreboard, whose right click frees the mouse to +pick a player; the host's own scoreboard adds Kick and Ban. Builds without +votes drop both kinds as unknown, so the network version is unchanged. + +Voice chat (`network_voice.c`, the sound in `port/linux/src/voice_audio.c`) +travels as distributed messages too, but in the lobby as well as in a game: +`network_voice_handles_message` takes them before the netcode's checks that +a game is running (`network_distributed_handle_message`, and the host's +stream and datagram paths in `network_server_message_handler.c`). A client +sends its 20 ms Opus frames to the host unreliably +(`_distributed_message_voice_up`), each with the key the host gave it over +its stream (`_distributed_message_voice_config`, with the host's settings, +again every ten seconds and when they change), so a datagram sent as from +another machine is dropped; an empty one every two seconds says it hears +voices. The host checks the key, the frame's size against its quality, a +rate of 50 frames a second (a burst of 10), and at most 8 machines talking, +then sends the frame to each machine that may hear it +(`_distributed_message_voice_down`, with that machine's key) by its +`network.voice_mode`, `network.voice_lobby` and `network.voice_proximity`, +or plays it itself. The host never decodes a frame it only relays, and +never sends a machine its own. A listener sets the gain and pan of a voice +heard for being near by its own view of the speaker's unit. Mutes are each +machine's own. Builds without voice chat never say they hear voices, so the +host sends them none, and drop the kinds as unknown. A speed hack of less than a tenth is let be: the host's bounds on how far and how fast a client's player moves and fires hold it to the host's time anyway. @@ -325,7 +366,9 @@ a pregame keep-alive every five seconds from the host indices from the upper half of the object array, clear of the host's. - Ten times a second, what every unit carries (the host's weapons, slot for slot, their ammunition, the weapon in hand, the grenades); a - client moves the same weapon objects in and out of its units. A + client moves the same weapon objects in and out of its units, which + carry no grenades until the host says (not the unit tag's: a player + spawned with the button held threw one the host's copy had not). A change of weapons or grenades goes to every client at once; one of ammunition only to the unit's player's machine at once, and to the others as often as they are sent that player. A client takes its own @@ -379,8 +422,9 @@ a pregame keep-alive every five seconds from the host player; damage one of their weapons (a vehicle's a driver's or gunner's; now or in the last ten seconds), a grenade the host's own game saw them throw in the last ten seconds that has not gone off (each - throw's explosion is taken once, its other hits that tick with it: - holding grenades deals nothing) or the vehicle they drove (in the + throw's explosion is taken once, its other hits and its other damage + (a frag grenade's shock wave) that tick with it: holding grenades + deals nothing) or the vehicle they drove (in the last ten seconds: its collisions) can deal (its projectiles' impacts and detonations, followed through the tags), no harder than it can be (all of it, but an airborne melee blow's half again); of the shape the diff --git a/port/linux/README.md b/port/linux/README.md index adc6f996..6abf475f 100644 --- a/port/linux/README.md +++ b/port/linux/README.md @@ -21,13 +21,65 @@ To build: - The 32-bit glibc development files: `lib32-glibc` on Arch Linux, `gcc-multilib` and `libc6-dev-i386` on Debian and Ubuntu. - The 32-bit SDL3: `lib32-sdl3` on Arch Linux, `libsdl3-dev:i386` on Debian - and Ubuntu. + and Ubuntu. The portable build does not need it: refer to "Portable + build". To start the game: +- glibc 2.29 or later (32-bit), for the builds from GitHub Actions, which + are built against glibc 2.31. They start on SteamOS 3 (Steam Deck). They + should also start on Debian 11, Ubuntu 20.04, Fedora 32 and later + distributions, but this is not tested. A build that you make without + `--portable` needs the glibc of the computer that built it, or a later + one. - The 32-bit OpenGL libraries (`lib32-mesa`). - The 32-bit PipeWire or PulseAudio client libraries (`lib32-pipewire` or `lib32-libpulse`). +- The 32-bit X11 libraries (`lib32-libx11`, `lib32-libxext`). In a Wayland + session, the game uses them through XWayland. SDL uses Wayland itself + only if the 32-bit Wayland libraries are version 1.20 or later + (`lib32-wayland`, `lib32-libxkbcommon`; Debian 11, Ubuntu 20.04 and + Fedora 32 have older ones) and the compositor has the fifo-v1 protocol. + On GNOME, the window then has borders only with the 32-bit libdecor + (`lib32-libdecor`). + +SteamOS has all of these. Its system is read-only, and the builds from +GitHub Actions need no package: they bring their own SDL3 +(`libSDL3.so.0`, next to the executable). + +### Portable build + +The builds from GitHub Actions are portable builds (`--portable`; refer to +"Build options" in the main [README](../../README.md#build-options)). The +portable build starts on more systems than the build machine's: + +- It is compiled and linked against the 32-bit glibc 2.31 of Debian 11, the + glibc of the Steam Runtime 3 ("sniper"), not against the glibc of the + build machine. An executable needs the glibc version that it was built + against, or a later one. +- It brings SDL 3 (`libSDL3.so.0`), built from source against the same + glibc. The executable looks for it in its own folder first (a `DT_RPATH` + of `$ORIGIN`, which comes before `LD_LIBRARY_PATH`), so that it uses this + SDL even when Steam sets `LD_LIBRARY_PATH`. Few distributions have a + 32-bit SDL 3. This SDL loads X11, Wayland, libdecor, PipeWire, PulseAudio + and ALSA when it starts, so it starts with whichever of them the system + has. It is linked only to glibc. Its license is `SDL3-LICENSE.txt`. + +At the first build, `tools/linux_sysroot.py` downloads the Debian packages +(about 25 MB) and the source of SDL. Each package comes from +`archive.debian.org` or `deb.debian.org`, or else from +`snapshot.debian.org`; the source of SDL comes from GitHub or from +`libsdl.org`. It checks each download against its SHA-256 sum. The system root is in +`build/linux/third_party/sysroot`. `tools/linux_sysroot.cmake` builds SDL +against it. + +To make the portable build you need, in addition to the tools above, +CMake, `pkg-config` (`pkgconf`) and `wayland-scanner` (`wayland` on Arch +Linux, `libwayland-bin` on Debian and Ubuntu). You do not need the 32-bit +SDL3. You need the 32-bit glibc only for the tests. + +To see the glibc version that a build needs, enter +`readelf -V build/linux/halo | grep -o 'GLIBC_[0-9.]*' | sort -uV | tail -1`. ## Build the game @@ -39,6 +91,11 @@ To start the game: Enter `build/linux/halo`. +To play on a Steam Deck, unpack `halo-linux-release.zip` into a folder (in +Desktop Mode), and add `halo` to Steam as a non-Steam game ("Add a Game" in +the Games menu of Steam). The game then starts in Game Mode as well. Refer +to "Steam Deck". + The game data is the folder that contains `maps/`, from an Xbox disc image of any version of the game. The game looks for this folder in this sequence: @@ -60,6 +117,38 @@ The game writes the copy to `maps.partial`. When the copy is complete, the game changes the name to `maps`. If the copy stops before it is complete, the game asks for the disc image again at the next start. +### Steam Deck + +Do the set-up in Desktop Mode: unpack the release, put `maps/` next to +`halo` (or start the game one time to copy it from a disc image), and add +`halo` to Steam. Then start the game from the library in Game Mode. + +- Screen: the game fills the screen without borders (`display.mode` empty, + thus borderless). The 3D view and the HUD have the shape of the screen + (16:10) and are drawn at its resolution; the menus are at the center. +- Controls: Steam Input gives the game a virtual controller, which the game + operates as the controller of the Xbox. With the template that Steam selects + ("Gamepad With Joystick Trackpad"), the buttons, sticks and triggers have + the functions of the same controls on an Xbox controller, the right + trackpad operates as the right stick, and the back buttons (L4, L5, R4, + R5) do nothing until you assign them in the controller settings of the + game in Steam. With a template that makes a trackpad a mouse, the mouse + aims in the game and moves the pointer in the menus. +- Text: names of profiles and gametypes use the keyboard of the game, which + the controller operates. The text fields of the menus (the name and the + password in Server Setup, and the password of a game in the server + browser) open the keyboard of Steam. Type the text, then select Enter on + that keyboard. +- Frame rate: the game shows one frame for each refresh of the display. It + follows the refresh rate and the frame limit of Quick Access > + Performance (40 to 60 Hz on the LCD model, up to 90 Hz on the OLED model). + The world is calculated at 30 Hz at all rates. Keep `display.vsync = + true`. Refer to "Frame rate". +- Sleep: the clocks of the game do not count the time that the Deck sleeps, + so the game continues from where it stopped. A network game does not + wait: the host drops a machine that it has not heard from for 15 seconds, + and while a Deck that hosts sleeps, the other players have no host. + ## Files and folders | Xbox drive | Folder | @@ -121,9 +210,15 @@ gamepads' only. | zoom | Z, middle mouse button | | show the scores (hold) | tab | | pause menu | escape | +| screenshot | F10 | +| talk in voice chat (hold) | V | Always: \` opens the developer console, F12 releases or captures the mouse, -F11 changes between fullscreen and window. +F11 changes between fullscreen and window. Screenshot (default F10, +rebindable under Controls Setup > Actions, below Pause Menu; not on Android) saves a PNG of the completed +frame to `screenshots/` beside `maps/`, named `YYYY-MM-DD_HH.MM.SS.png` in +local time, and prints the filename in the console. Captures in the same +second get a numeric suffix so previous screenshots are preserved. One movement of the mouse wheel changes the weapon one time. A second movement after a short pause changes it again. @@ -134,6 +229,8 @@ In the menus, the mouse moves a pointer: - A left click selects the item. On a setting with values, a click on the left or right half changes the value. On a button in the key of a screen (for example "B = Back"), a click pushes that button. +- On the on-screen keyboard (a profile's name), a left click presses the + key below the pointer, or pushes the "B =BACK" or "A =ENTER" legend. - A right click goes back. - The mouse wheel moves through the items. @@ -194,6 +291,12 @@ stay. The Xbox's pause box is drawn taller to hold them (a redraw, what is below its list moves down. The few pictures of the settings that come from the main menu's map are not drawn there. +In a single-player campaign, the pause menu has SETTINGS too, before REVERT +TO SAVED, in the same box: a list with room for it centres its rows, and +one without keeps its size, its rows closer. It opens the same settings, +while the game stays paused, with only Controls, Gamepads, Mouse, Audio and +Video Setup; Gamepads' OK saves the profile at once. + The menus are XML files in `port/assets/menus` (`tools/ce_menus.py` writes them from the PC version's tags), which the game contains. To change them, put files in a `menus` folder next to `config.toml`: a file with the same @@ -233,6 +336,9 @@ the setting for one start of the game. It has priority over the file. | `debug.gpu_flush_draws` | `-1` | `HALO_GPU_FLUSH_DRAWS` | Flush the GPU's pipeline every this many draws. `-1`: every 3 on Intel graphics with Mesa's driver, which can otherwise hang in the game's long runs of small draws and reset the desktop's graphics too. `0`: never. | | `display.interpolation` | `true` | `HALO_INTERPOLATION` | `true`: one frame for each refresh of the display. `false`: 30 frames each second, as on the Xbox. Refer to "Frame rate". | | `display.direct_camera` | `true` | `HALO_DIRECT_CAMERA` | `true`: in first person, on foot, the view points where the player aims in each frame, not where the last tick left it. Refer to "Frame rate". | +| `display.fov` | `0.0` | `HALO_FOV` | The first-person view's field of view on foot, in degrees across at 16:9, from 20 to 150. `0`: the stock view. Refer to "Field of view". | +| `display.viewmodel_fov` | `0.0` | `HALO_VIEWMODEL_FOV` | The first-person weapon's and hands' field of view, in degrees across at 16:9, from 20 to 150. `0`: the weapon's stock view, also when `display.fov` widens the world. Refer to "Field of view". | +| `display.viewmodel_visible` | `true` | `HALO_VIEWMODEL_VISIBLE` | `true`: the first-person weapon, hands and what is attached to them are drawn. `false`: they are not; firing, animation, sound and lights go on, and other players' models are drawn. | | `display.high_res_hud` | `true` | `HALO_HIGH_RES_HUD` | `true`: the HUD (meters, counters, panels and their outlines, the motion sensor, reticles, waypoints, scopes) is drawn from the high-res assets in `port/assets/hud`, 8x the size of the maps' bitmaps. The bitmaps with English text keep the maps' own. `false`: the maps' own bitmaps. | | `display.high_res_text` | `true` | `HALO_HIGH_RES_TEXT` | `true`: the menus' and HUD's text is drawn with the fonts in `port/assets/fonts` (Overpass, in place of the maps' Interstate) at the resolution the game draws at, laid out as before, and the menus' titles are drawn from the high-res pictures in `port/assets/titles`. `false`: the maps' bitmap fonts and titles. | | `display.shadow_resolution` | `128` | `HALO_SHADOW_RESOLUTION` | The size of the maps that the shadows of the objects are drawn in, in pixels each way: `128`, `256`, `512` or `1024` (other values go down to one of these). The game draws the shadow of each object into a map of 128x128 pixels, blurs it and projects it onto the ground. On a large screen, the edges of these shadows show steps that move when the object moves. A larger map makes the edges smooth; the blur is made wider to match, so the shadows are as soft as on the Xbox. Each doubling adds two passes of the blur. `128`: as on the Xbox. | @@ -248,14 +354,18 @@ the setting for one start of the game. It has priority over the file. | `audio.music_volume` | `1.0` | `HALO_MUSIC_VOLUME` | The music's volume, of the master volume. | | `audio.effects_volume` | `1.0` | `HALO_EFFECTS_VOLUME` | The volume of the other sounds (effects and speech), of the master volume. | | `audio.reverb` | `true` | `HALO_REVERB` | `true`: the sounds of the world reverberate as the place the player is in does: the sound environments of the maps (a corridor, a cave, a large hall, outdoors) set the reverberation, as the I3DL2 reverb of the Xbox did. A sound behind a wall or a door is muffled in it too. `false`: no reverberation (sounds behind a wall are still muffled). | +| `audio.voice_chat` | `"push_to_talk"` | `HALO_VOICE_CHAT` | How you talk in voice chat: `"push_to_talk"` (while `controls.push_to_talk` is held; the microphone opens when you first press it), `"open_mic"` (when the microphone hears speech), or `"off"`. You hear the other players in every case. Refer to "Voice chat". | +| `audio.voice_volume` | `1.0` | `HALO_VOICE_VOLUME` | The volume of the voices of the other players, `0` to `2`. | +| `audio.output_device`, `audio.input_device` | `"default"` | `HALO_AUDIO_OUTPUT_DEVICE`, `HALO_AUDIO_INPUT_DEVICE` | The speakers and the microphone, by the name that Settings > Audio shows, or `"default"` for the device of the system. If the device is not found, the game uses the device of the system. Not on Android. | | `audio.loose_sounds` | `false` | `HALO_LOOSE_SOUNDS` | For those who make sounds. `true`: each sound of a map that has a sound tag file of its name in `tags/` in the data root (for example `tags/sound/sfx/weapons/assault rifle/fire.sound`) plays from that file. The files are Halo PC tag files, as the Halo Editing Kit and Invader write them. At the console, `loose_sounds_reload` reads the files again, and `loose_sounds false` plays the sounds of the map again. When a file changes, all sounds stop. | | `input.mouse_sensitivity` | `1.0` | `HALO_MOUSE_SENSITIVITY` | The multiplier for the mouse aim. | | `input.mouse_vertical_sensitivity` | `0.0` | `HALO_MOUSE_VERTICAL_SENSITIVITY` | The multiplier for the vertical mouse aim. `0`: the same as `input.mouse_sensitivity`. | | `input.invert_mouse` | `false` | `HALO_MOUSE_INVERT=1` sets `true` | `true`: the vertical mouse aim is inverted. | | `input.mouse_aim_assist` | `false` | `HALO_MOUSE_AIM_ASSIST` | `true`: the magnetism of the controller also operates for the mouse. `false`: when the mouse moved after the right stick, the view is not slowed or dragged by a target. The autoaim of the bullets operates in both cases. | -| `controls.` | (the table in "Controls") | `HALO_KEY_` | The keys and mouse buttons of an action, up to two, separated by a comma: `move_forward`, `move_backward`, `strafe_left`, `strafe_right`, `jump`, `crouch`, `fire`, `throw_grenade`, `melee`, `reload`, `zoom`, `switch_weapon`, `switch_grenade`, `action`, `flashlight`, `scoreboard`, `pause`. Keys by their names (`"W"`, `"Space"`, `"Left Ctrl"`, `"F1"`), and `"Mouse Left"`, `"Mouse Right"`, `"Mouse Middle"`, `"Mouse 4"`, `"Mouse 5"`, `"Wheel"` (either way), `"Wheel Up"`, `"Wheel Down"`. | +| `controls.` | (the table in "Controls") | `HALO_KEY_` | The keys and mouse buttons of an action, up to two, separated by a comma: `move_forward`, `move_backward`, `strafe_left`, `strafe_right`, `jump`, `crouch`, `fire`, `throw_grenade`, `melee`, `reload`, `zoom`, `switch_weapon`, `switch_grenade`, `action`, `flashlight`, `scoreboard`, `pause`, `screenshot`, `push_to_talk`. Keys by their names (`"W"`, `"Space"`, `"Left Ctrl"`, `"F1"`), and `"Mouse Left"`, `"Mouse Right"`, `"Mouse Middle"`, `"Mouse 4"`, `"Mouse 5"`, `"Wheel"` (either way), `"Wheel Up"`, `"Wheel Down"`. | | `game.console_log` | `"important"` | `HALO_CONSOLE_LOG` | What the console shows on the screen. `"important"`: bans, players that the host drops for cheating, the reasons that the game refuses a command, and the asserts that stop the game. `"all"`: all the lines. `"none"`: only the asserts that stop the game. The output of a command always shows. `debug.txt` gets all the lines. | | `game.language` | `""` | `HALO_LANGUAGE` | The language of the menus: `ja`, `de`, `fr`, `es` or `it`. Empty: English. | +| `game.enhanced_animations` | `true` | `HALO_ENHANCED_ANIMATIONS` | `true`: the player bipeds' grenade throws keep their legs moving, blended by speed and direction (crouched throws stay crouched, throws in the air use the jump's legs), Warthog and Scorpion riders stay seated to throw and let go of the grips to throw and reload, and a player turns with the aim while throwing, as while meleeing. `false`: the original animations, which freeze the legs during a throw and stand a rider up. Only in config.toml, not in the menus. | | `paths.data` | `""` | `HALO_DATA_ROOT` | The data root. Refer to "Start the game". | | `paths.saves` | `""` | `HALO_SAVE_ROOT` | The save root. Refer to "Files and folders". | | `network.address` | `""` | `HALO_NET_ADDRESS` | The IPv4 address of this machine for system link. Refer to "Play on one computer". | @@ -266,11 +376,18 @@ the setting for one start of the game. It has priority over the file. | `network.allow_upnp` | `true` | `HALO_NET_ALLOW_UPNP` | `true`: internet play can ask the router to forward its port (UPnP). `false`: the game does not ask. Refer to "Internet play". | | `network.public_lobby` | `true` | `HALO_NET_PUBLIC_LOBBY` | `true`: the server browser. Public games are listed, and Join Game > Server Browser shows them. `false`: no games are listed or shown. Refer to "Server browser". | | `network.host_public` | `true` | `HALO_NET_HOST_PUBLIC` | `true`: a new game of Create Game > Internet starts as PUBLIC. `false`: it starts as PRIVATE. LISTING in Server Setup changes it for each game. Refer to "Server browser". | -| `network.coop_friendly_fire` | `"on"` | `HALO_NET_COOP_FRIENDLY_FIRE` | Whether the players of an online co-op game hurt each other: `"off"`, `"on"`, `"shields_only"` or `"explosives_only"`. FRIENDLY FIRE in co-op's Server Setup writes its choice here. Their AI allies they always can, as in the campaign. | -| `network.coop_player_collisions` | `true` | `HALO_NET_COOP_PLAYER_COLLISIONS` | Whether the players of an online co-op game bump into each other. `false`: they walk through each other, so that one cannot block a doorway or stand on another; they still bump into the AI's characters. PLAYER COLLISIONS in co-op's Server Setup writes its choice here. | -| `network.coop_enemies_mode` | `"per_player"` | `HALO_NET_COOP_ENEMIES_MODE` | Online co-op's extra enemies: `"none"`; `"per_player"`, each squad of enemies that a level places grows by `network.coop_enemies` for each player past the first; or `"multiplier"`, each squad is `network.coop_enemies_multiplier` times as large, for any number of players. The extra enemies stand around the squad's places, and those that a dropship has no seats for drop out of it after its passengers. EXTRA ENEMIES in co-op's Server Setup writes its choice here. | -| `network.coop_enemies` | `50` | `HALO_NET_COOP_ENEMIES` | The extra enemies per player, a percentage from `25` to `200`: for each player past the first, each squad of enemies gets this much of itself more (`100`: as many again, so four players meet four times the squad), up to 8 times the squad however many players there are. PER PLAYER in co-op's Server Setup writes its choice here. | -| `network.coop_enemies_multiplier` | `2` | `HALO_NET_COOP_ENEMIES_MULTIPLIER` | The static multiplier of the enemies, `2` to `32`: each squad of enemies is this many times as large. MULTIPLIER in co-op's Server Setup writes its choice here. | +| `network.coop_friendly_fire` | `"on"` | `HALO_NET_COOP_FRIENDLY_FIRE` | Whether the players of an online co-op game hurt each other: `"off"`, `"on"`, `"shields_only"` or `"explosives_only"`. FRIENDLY FIRE in co-op's Server Setup > Co-op Options writes its choice here. Their AI allies they always can, as in the campaign. | +| `network.coop_player_collisions` | `true` | `HALO_NET_COOP_PLAYER_COLLISIONS` | Whether the players of an online co-op game bump into each other. `false`: they walk through each other, so that one cannot block a doorway or stand on another; they still bump into the AI's characters. PLAYER COLLISIONS in co-op's Server Setup > Co-op Options writes its choice here. | +| `network.coop_enemies_mode` | `"per_player"` | `HALO_NET_COOP_ENEMIES_MODE` | Online co-op's extra enemies: `"none"`; `"per_player"`, each squad of enemies that a level places grows by `network.coop_enemies` for each player past the first; or `"multiplier"`, each squad is `network.coop_enemies_multiplier` times as large, for any number of players. The extra enemies stand around the squad's places, and those that a dropship has no seats for drop out of it after its passengers. EXTRA ENEMIES in co-op's Server Setup > Co-op Options writes its choice here. | +| `network.coop_enemies` | `50` | `HALO_NET_COOP_ENEMIES` | The extra enemies per player, a percentage from `25` to `200`: for each player past the first, each squad of enemies gets this much of itself more (`100`: as many again, so four players meet four times the squad), up to 8 times the squad however many players there are. PER PLAYER in co-op's Server Setup > Co-op Options writes its choice here. | +| `network.coop_enemies_multiplier` | `2` | `HALO_NET_COOP_ENEMIES_MULTIPLIER` | The static multiplier of the enemies, `2` to `32`: each squad of enemies is this many times as large. MULTIPLIER in co-op's Server Setup > Co-op Options writes its choice here. | +| `network.voice_lobby` | `true` | `HALO_NET_VOICE_LOBBY` | When you host: `true`, all players hear all players in the lobby, before and after a game. | +| `network.voice_mode` | `"team_global_enemy_proximity"` | `HALO_NET_VOICE_MODE` | When you host: who hears whom during a game. `"off"`; `"team_proximity"` (teammates who are near); `"team_enemy_proximity"` (all players who are near); `"team_global"` (all teammates); `"team_global_enemy_proximity"` (all teammates, and enemies who are near). Refer to "Voice chat". | +| `network.voice_kbps` | `24` | `HALO_NET_VOICE_KBPS` | When you host: the voice quality in the lobby and in a game, in kilobits per second, `8` to `64`. | +| `network.voice_proximity` | `15.0` | `HALO_NET_VOICE_PROXIMITY` | When you host: the distance in world units (1 unit is approximately 3 metres) at which players are near, for voice chat. `5` to `100`. | +| `network.votekick` | `true` | `HALO_NET_VOTEKICK` | When you host: `true`, the players can vote to kick a player. Refer to "Security". `false`: no votes. VOTE KICK in Server Setup > Teamplay Options (in co-op, Voice and Voting) writes its choice here. | +| `network.votekick_minutes` | `5` | `HALO_NET_VOTEKICK_MINUTES` | When you host: the minutes that a player must play on the server before the player can start a vote to kick (`0` to `60`). To vote, a player must play for 2 minutes, or for this time if it is less. | +| `network.votekick_ban_minutes` | `30` | `HALO_NET_VOTEKICK_BAN_MINUTES` | When you host: the minutes that a player who is kicked by a vote cannot join again (`1` to `1440`). | | `network.coop_public` | `false` | `HALO_NET_COOP_PUBLIC` | `true`: an online co-op game (Create Game > Internet, a SINGLEPLAYER map) starts as PUBLIC. `false`: it starts as PRIVATE. LISTING in co-op's Server Setup writes its choice here. Refer to "Server browser". | | `network.brokers_file` | `"brokers.txt"` | `HALO_NET_BROKERS_FILE` | The file of the public MQTT brokers that let the machines of an invite find each other, and that carry the listings of the server browser: next to `config.toml`, unless a full path. One `host:port` on each line, up to 4; `#` starts a comment. | | `network.stun_servers` | Google and Cloudflare | `HALO_NET_STUN` | The public STUN servers (`host:port`, with commas between them) that give the internet address of a machine. | @@ -285,8 +402,12 @@ the setting for one start of the game. It has priority over the file. | `debug.menu_open` | `""` | `HALO_MENU_OPEN` | Start on this screen of the menus (`main_menu/settings_select/...`, as `port/assets/menus` names it), a player profile being edited, to look at it. | | `debug.gpu_skip_vertex_shaders`, `debug.gpu_debug_expression`, `debug.gpu_debug_flat`, `debug.gpu_debug_texture0` | off | `HALO_GPU_SKIP_VS`, `HALO_GPU_DEBUG_EXPR`, `HALO_GPU_DEBUG_FLAT`, `HALO_GPU_DEBUG_T0` | Tools to find problems in the graphics: skip the draws of a vertex shader, or replace the output of all pixel shaders with a GLSL expression (for example `t0.rgb`). | | `debug.network_test`, `debug.network_test_start`, `debug.network_test_kill`, `debug.network_test_score`, `debug.network_test_shoot`, `debug.network_test_vehicle`, `debug.network_test_pickup`, `debug.network_test_pickup_weapon`, `debug.test_input` | off | `HALO_NETWORK_TEST`, `HALO_NETWORK_TEST_START`, `HALO_NETWORK_TEST_KILL`, `HALO_NETWORK_TEST_SCORE`, `HALO_NETWORK_TEST_SHOOT`, `HALO_NETWORK_TEST_VEHICLE`, `HALO_NETWORK_TEST_PICKUP`, `HALO_NETWORK_TEST_PICKUP_WEAPON`, `HALO_TEST_INPUT` | Automatic tests of system link (`game/network_test.c`). Refer to `NETCODE.md`. | +| `debug.touch_targets` | `false` | `HALO_TOUCH_TARGETS` | Outlines the tap targets of the menus (item green, value blue, list slot yellow, legend button red, the band beside the slots of a list orange, keys of the on-screen keyboard white), marks where the last finger went down and the last tap landed for 3 seconds, and logs each tap with the target that it hit (for a value, also where it splits into previous and next): to judge the accuracy of touch. | | `debug.network_latency`, `debug.network_loss`, `debug.network_corrupt`, `debug.network_corrupt_stream`, `debug.network_corrupt_after` | `0` | `HALO_NETWORK_LATENCY`, `HALO_NETWORK_LOSS`, `HALO_NETWORK_CORRUPT`, `HALO_NETWORK_CORRUPT_STREAM`, `HALO_NETWORK_CORRUPT_AFTER` | The game holds all the data that it receives for this number of milliseconds, ignores this percentage of the datagrams, and damages this percentage of the datagrams it receives, and this percentage of its reads of streams, at random (bytes changed, cut short, stretched or replaced), from this many seconds after the start. Use the first two to test the netcode as on the internet, and the others to test that nothing another machine sends can crash the game (a damaged stream is closed, so a little goes a long way; a host's messages to its own client are damaged too, so start damaging once the game has started). | +| `debug.voice_test` | `false` | `HALO_VOICE_TEST` | Automatic tests of voice chat: a tone replaces the microphone, and each voice that the game hears is written to the log once each second. | | `debug.telnet_console`, `debug.telnet_console_port` | `false`, `2323` | `HALO_TELNET_CONSOLE`, `HALO_TELNET_CONSOLE_PORT` | The game listens on 127.0.0.1, on this port, for a script console (connect with telnet). The console has no password, so only this computer can reach it. | +| `debug.profile_record`, `debug.profile_record_when`, `debug.profile_memory` | `false`, `"start"`, `256` | `HALO_PROFILE_RECORD`, `HALO_PROFILE_RECORD_WHEN`, `HALO_PROFILE_MEMORY` | Profiling builds only (`configure.py --profile`). Record without a command; `"start"` records from the first frame until the first map change, `"game"` records each game outside the main menu. Memory is 4–1024 MB. Numbered `profile__.part.json` files remain in `profiles/` after recording. Refer to "Profiling builds" in the main README. | + With Mesa drivers, the game sends its GL calls through the GL thread of Mesa. To stop this, set the environment variable `mesa_glthread=false`. @@ -332,6 +453,10 @@ Each frame shows the world between the last two ticks the last tick. - Rotations use quaternions. Positions and scales are linear. - A teleport, a respawn or a cut of the camera does not mix. It jumps. +- After a long frame (several ticks in one frame), the camera mixes the + last tick only, as the objects do. +- In cinematics, a camera that moves with an object (the lifepod in a30, + a Pelican) moves with the object as it is drawn. Thus the frames are one tick (33 ms) after the calculation. The calculation does not change. @@ -357,6 +482,29 @@ the world (such as `rasterizer_wireframe`). Refer to `NETCODE.md`. The frame rate shows at the bottom right of the screen. It is the mean over half a second. +## Field of view + +Video Setup's FOV AND VIEWMODELS screen sets the field of view of the +first-person view (`display.fov`) and of the weapon and hands +(`display.viewmodel_fov`), from 80 to 150 degrees in steps of 5, and can +hide the weapon (`display.viewmodel_visible`). DEFAULT, the setting's `0`, +keeps the stock view. `config.toml` takes any angle from 20 to 150. + +- An angle is across the screen at 16:9. Another shape keeps the same + angle up and down, as the stock view does. +- The field of view applies on foot in first person only. Vehicles, death, + cinematics and scripted cameras keep their own view. +- A scope's zoom levels keep their stock view: the extra width fades out as + the zoom comes in. A view narrower than the stock one narrows the zoom + too. +- The reticles scale with the view, so that they stay on the aim. The + scopes' pictures keep their place. +- By default the weapon keeps its stock view when the world is wider: + arms and a gun right against the camera stretch at a wide angle. + +Only this machine's view changes. Nothing the machines send each other +changes, so players with different settings play together. + ## System link The Xbox game lets 16 players on 4 machines play a system link game. This @@ -456,6 +604,47 @@ When the machines connect, the game of the host shows in Multiplayer, System Link. Join the game as on a local network. System link on a local network does not need an invite. +### Voice chat + +Network games have voice chat, on the local network and on the internet. +Hold V (`controls.push_to_talk`, Settings > Controls Setup) to talk, or set +VOICE CHAT in Settings > Audio to OPEN MIC. VOICE VOLUME sets the volume of +the other players. On Android, set VOICE CHAT to OPEN MIC to talk: the game +then asks for the microphone. + +OUTPUT DEVICE and INPUT DEVICE in Settings > Audio select the speakers and +the microphone (SYSTEM DEFAULT, the default, follows the system). The list +has the devices that the computer had when the game started. + +The host sets voice chat for all players in Server Setup > Teamplay Options +(`network.voice_*` in `config.toml`): + +- In the lobby, before and after a game, all players hear all players + (LOBBY VOICE CHAT). +- During a game (VOICE CHAT): OFF; TEAM NEAR; ANYONE NEAR; TEAM; or TEAM, + ENEMIES NEAR (the default). Near is VOICE NEAR DISTANCE (45 metres is the + default), and both players must be alive. A player who is near sounds + quieter when further, and from their direction. In a game without teams + all players are enemies; in co-op, all are teammates. +- The quality (VOICE QUALITY), 8 to 64 kilobits per second (24 is the + default), in the lobby and in the game. + +In co-op, Server Setup has no Teamplay Options: the same settings are in +Server Setup > Voice and Voting. + +A speaker shows next to the name of a player who talks: in the lobby, on +the scoreboard, in a list at the left of the screen during a game, and +beside the name above their head (when names are shown there: +`display.player_names`). To mute a player, open the scoreboard, +right-click, click the player, and click Mute voice. You no longer hear +them, and nobody else is told. + +The host sends each voice on to the players who can hear it. The host +accepts a voice only from the machine of the player (with a key that it +gives each machine on its connection), only at the quality it set, at most +50 frames each second from a machine, and from at most 8 players at a time. +The voices are compressed with Opus (`port/third_party/opus`). + ### Server browser Server Setup in Create Game > Internet has a LISTING row: @@ -585,6 +774,41 @@ Only machines with the invite can find the game: Refer to `NETCODE.md`. `kick ` drops the player the same way, but keeps nothing: no line in `bans.txt`, and the player can join again at once. In co-op, `bringto` brings every player to the host. +- Players can vote to kick a player. The host turns this on or off with + VOTE KICK in Server Setup > Teamplay Options (`network.votekick`; in + co-op, Server Setup > Voice and Voting). Hold the scoreboard key, right-click to + show the pointer, and click the name of the player. Then click **Start + a vote to kick**. Other players vote in the same way, and see the vote on + the scoreboard. `votekick ` in the developer console does + the same. The host also gets **Kick** and **Ban** in this menu: these do + the same as the `kick` and `ban` commands (click **Ban** two times). The + host counts the votes, and these rules prevent abuse: + - The vote passes when more than half of the players vote for it, and at + least two players. The player of the vote is counted, but cannot vote. + Thus, in a game of two equal teams, one team cannot kick a player of + the other team without help. + - The host counts one vote for each internet address (for internet + play, the real address of the player, not the address of the tunnel). + Two machines at one address, or with one hardware id, have one vote. + - To start a vote, a player must have played for + `network.votekick_minutes` (5) on this server. To vote, a player must + have played for 2 minutes (or less, if that setting is less). The host + counts the time. When a player joins again, the time starts again. + Players who cannot vote yet are not counted. + - The host only accepts a vote that comes on the connection of the + player, not a datagram, which another machine can send with the + address of the player. + - One vote runs at a time, for 45 seconds, with 30 seconds before the + next vote. If a vote fails, the player who started it cannot start a + vote for 5 minutes, and nobody can start a vote against the same player + for 10 minutes. + - Nobody can vote to kick a player of the host. + - A player kicked by a vote cannot join again for + `network.votekick_ban_minutes` (30), by address and hardware id. + - A player who leaves during a vote against them is kept out the same + way, as if the vote passed. While the vote runs, the host refuses the + player if they try to join again. Thus nobody can avoid a vote by + leaving. So that every player can be named, the host trims the spaces around a name and removes characters that draw as nothing. A letter with a mark is typed as the plain letter (`ban jose` for "José"). @@ -754,7 +978,7 @@ These files supply the MSVC functions that clang does not have: | `port/include/xdk` | The Xbox SDK declarations. The compiler reads this folder after all the other folders. | | `tools/linux_msvc_semantics.py` | Makes a header that declares each struct tag at file scope, as MSVC does. It also makes the header inline functions weak, as the COMDAT functions of MSVC. `game/msvc_comdat.c` gives one external copy of each. | | `include/halo_linux_winsock_names.h` | Gives new names to the Winsock functions of the SDK. Thus they do not link to the glibc functions with the same names. | -| `include/halo_linux_source_fixups.h` | Repairs one declaration conflict (`rasterizer_debug_drawing_begin`). | +| `include/halo_linux_source_fixups.h` | Declares the port's functions that the game's sources call. | `tools/linux_link_check.py` stops the link if a weak reference has no definition. Without this check, the linker gives the reference the address @@ -779,7 +1003,9 @@ definition. Without this check, the linker gives the reference the address performance captures (`gpu-trace.service`), and its Mesa then writes a marker for each traced driver function: on the Steam Frame, some 480,000 writes a second, which took the game from the headset's 72 Hz to about - 50 frames a second. The Steam Deck runs the same service and Mesa. + 50 frames a second. The Steam Deck runs the same service, but its Mesa + (25.3, 32-bit and 64-bit) has no markers to write: there the refusal + changes nothing (measured: the same frame times and power either way). `HALO_GPU_TRACE_MARKERS=1` lets the driver write them, to capture with gpuvis. diff --git a/port/linux/game/menu_functions.c b/port/linux/game/menu_functions.c index 3f57138b..a2b50b80 100644 --- a/port/linux/game/menu_functions.c +++ b/port/linux/game/menu_functions.c @@ -78,6 +78,8 @@ their handlers open opens. #include "halo_menus.h" #include "custom_edition_cache.h" #include "custom_edition_maps.h" +#include "network_voice.h" +#include "text/draw_string.h" /* (internet play's server browser: the platform layer's) */ #include "../src/p2p.h" @@ -101,6 +103,8 @@ void platform_binding_capture_begin(void); int platform_binding_capture_poll(int *input); void halo_input_name(int input, char *name, unsigned int size); short pc_menu_string_index(long definition_index); +/* cseries_windows.c's */ +unsigned long system_milliseconds(void); /* the game's (port) */ boolean ui_widget_port_dispatch_event(struct widget_instance *widget, short event_type, short controller_index, @@ -815,6 +819,7 @@ static void profile_name_show(struct widget_instance *description) static struct player_profile profile; static long read_index = NONE; static unsigned long read_time; + static boolean read_good; long index = player_ui_get_active_player_profile_index(0); if (!description) @@ -826,13 +831,17 @@ static void profile_name_show(struct widget_instance *description) } else if ((index = player_ui_get_player1_last_used_profile_index()) == NONE) return; - else if (index != read_index || system_milliseconds() - read_time > 1000) + else { - read_index = NONE; - if (!player_profile_get(index, &profile)) + /* (one that cannot be read too: tried again a second later) */ + if (index != read_index || system_milliseconds() - read_time > 1000) + { + read_index = index; + read_good = player_profile_get(index, &profile); + read_time = system_milliseconds(); + } + if (!read_good) return; - read_index = index; - read_time = system_milliseconds(); } text_set(named(description, "current_profile_name", 0), profile.player_name); } @@ -1497,6 +1506,17 @@ static boolean color_list_initialize(struct widget_instance *list) return TRUE; } +/* (ui_widget.c) whether the pointer moved the menus' focus last */ +boolean ui_widget_port_pointer_focused(void); + +/* Whether a list whose focus is on its first or last row moves on: when the +d-pad put it there. The pointer resting there would make it move every +frame; the wheel scrolls it instead. */ +static boolean list_scrolls_at_end(void) +{ + return !ui_widget_port_pointer_focused(); +} + /* a list of more items than its rows: the row chosen kept off its ends while there are more past them, the list moving instead; the item chosen, or NONE (its buttons) */ @@ -1504,12 +1524,12 @@ static short list_scroll(struct widget_instance *list, short *first, short count { short row = focused_row(list); - if (row == rows - 1 && *first + rows < count) + if (row == rows - 1 && *first + rows < count && list_scrolls_at_end()) { (*first)++; focus_row(list, --row); } - else if (row == 0 && *first > 0) + else if (row == 0 && *first > 0 && list_scrolls_at_end()) { (*first)--; focus_row(list, ++row); @@ -1875,6 +1895,8 @@ static struct { "controls.flashlight", L"FLASHLIGHT", 2 }, { "controls.scoreboard", L"SHOW SCORES", 2 }, { "controls.pause", L"PAUSE MENU", 2 }, + { "controls.screenshot", L"SCREENSHOT", 2 }, + { "controls.push_to_talk", L"PUSH TO TALK", 2 }, }; static struct @@ -2198,7 +2220,7 @@ void p2p_set_hosting_allowed(int allowed); int p2p_peer_address(unsigned char const *identifier, unsigned long *address); int platform_clipboard_get(char *text, int size); void platform_clipboard_set(char const *text); -void platform_text_field(int typing); +void platform_text_field(int typing, int password); #ifdef HALO_ANDROID /* (VR: the Xbox's on-screen keyboard, virtual_keyboard.c) */ int halo_vr_active(void); @@ -2229,9 +2251,10 @@ static struct starts with COOPERATIVE_DEFAULT_PLAYERS players at most) */ short cooperative_maximum_players_index; boolean cooperative_maximum_players_set; - /* co-op's FRIENDLY FIRE shown (network.coop_friendly_fire's) */ + /* the co-op game's FRIENDLY FIRE, and its PLAYER COLLISIONS OFF, as it + starts (CO-OP OPTIONS': network.coop_friendly_fire's and + network.coop_player_collisions': server_start) */ short cooperative_friendly_fire; - /* co-op's PLAYER COLLISIONS shown, OFF (network.coop_player_collisions's) */ boolean cooperative_no_player_collisions; /* the browser's games */ struct advertised_game *games[MAXIMUM_ADVERTISED_GAMES]; @@ -2297,8 +2320,8 @@ static boolean text_field_editing(struct widget_instance *row) return text_field.row && (!row || text_field.row == row); } -static void text_field_begin(struct widget_instance *row, char const *text, short maximum, - void (*done)(char const *text)) +static void text_field_open(struct widget_instance *row, char const *text, short maximum, + void (*done)(char const *text), boolean masked) { struct key_stroke key; @@ -2307,30 +2330,35 @@ static void text_field_begin(struct widget_instance *row, char const *text, shor snprintf(text_field.before, sizeof(text_field.before), "%s", text); text_field.maximum = (short)MIN(maximum, TEXT_FIELD_LENGTH - 1); text_field.done = done; - text_field.masked = FALSE; + text_field.masked = masked; text_field_shown_time = system_milliseconds(); while (input_get_key(&key)) ; - platform_text_field(TRUE); + platform_text_field(TRUE, masked); #ifdef HALO_ANDROID if (halo_vr_active()) text_field_keyboard_launch(); #endif } +static void text_field_begin(struct widget_instance *row, char const *text, short maximum, + void (*done)(char const *text)) +{ + text_field_open(row, text, maximum, done, FALSE); +} + /* a password's field: as text_field_begin, its text shown as stars */ static void text_field_begin_masked(struct widget_instance *row, char const *text, short maximum, void (*done)(char const *text)) { - text_field_begin(row, text, maximum, done); - text_field.masked = TRUE; + text_field_open(row, text, maximum, done, TRUE); } static void text_field_end(boolean keep) { void (*done)(char const *text) = text_field.done; - platform_text_field(FALSE); + platform_text_field(FALSE, FALSE); text_field.row = NULL; text_field.done = NULL; if (!keep) @@ -2717,13 +2745,13 @@ static void gametype_list_update(struct widget_instance *list) if (row == list->focused_child && row_index >= 1 && row_index <= GAMETYPE_ROWS) focused = (short)(row_index - 1); } - if (focused == count - 1 && multiplayer.gametype_first + count < multiplayer.bank_count) + if (focused == count - 1 && multiplayer.gametype_first + count < multiplayer.bank_count && list_scrolls_at_end()) { multiplayer.gametype_first++; focus_row(list, (short)focused); focused--; } - else if (focused == 0 && multiplayer.gametype_first > 0) + else if (focused == 0 && multiplayer.gametype_first > 0 && list_scrolls_at_end()) { multiplayer.gametype_first--; focus_row(list, 2); @@ -2819,18 +2847,15 @@ static char const *const server_settings_gametype_rows[] = /* the most players a co-op game hosted starts with (maximum_players') */ #define COOPERATIVE_DEFAULT_PLAYERS 16 -/* Server Setup's help for co-op's FRIENDLY FIRE and EXTRA ENEMIES, by -their choices, and EXTRA ENEMIES' PER PLAYER and MULTIPLIER (its -help_strings, tools/port_settings.py) */ -#define COOPERATIVE_FRIENDLY_FIRE_HELP 12 -#define COOPERATIVE_EXTRA_ENEMIES_HELP 16 -#define COOPERATIVE_ENEMIES_PER_PLAYER_HELP 19 -#define COOPERATIVE_ENEMIES_MULTIPLIER_HELP 20 -#define COOPERATIVE_PLAYER_COLLISIONS_HELP 21 -#define SERVER_PASSWORD_HELP 23 +/* Server Setup's help for co-op's options' rows (CO-OP OPTIONS, VOICE AND +VOTING), and PASSWORD's (its help_strings, tools/port_settings.py) */ +#define COOPERATIVE_OPTIONS_HELP 12 +#define COOPERATIVE_VOICE_OPTIONS_HELP 13 +#define SERVER_PASSWORD_HELP 14 -/* co-op's EXTRA ENEMIES' choices (port_settings.COOP_ENEMIES_MODES, in this -order): its amount's row is the choice's */ +/* co-op's EXTRA ENEMIES' choices (network.coop_enemies_mode's values, as +gametype_options' coop_extra_enemies_spinner has them, in this order): its +amount's row is the choice's */ enum { _cooperative_enemies_none, @@ -2839,8 +2864,9 @@ enum NUMBER_OF_COOPERATIVE_ENEMIES_MODES }; -/* co-op's FRIENDLY FIRE's choices (network.coop_friendly_fire's values, -port_settings.COOP_FRIENDLY_FIRE_VALUES, in this order) */ +/* co-op's FRIENDLY FIRE's choices (network.coop_friendly_fire's values, as +gametype_options' coop_friendly_fire_spinner and server_start have them, in +this order) */ static short const cooperative_friendly_fire_modes[] = { _friendly_fire_off, _friendly_fire_on, _friendly_fire_shields_only, _friendly_fire_explosives_only @@ -2954,54 +2980,17 @@ static void server_settings_update(struct widget_instance *list) for (row = 0; row < NUMBEROF(server_settings_gametype_rows); row++) visible_set(named(list, server_settings_gametype_rows[row], 0), !cooperative); - /* co-op's FRIENDLY FIRE, in their place: its help is its choice's */ - visible_set(named(list, "op_friendly_fire", 0), cooperative); - if (cooperative && (spinner = named(list, "friendly_fire_spinner", 0)) != NULL) - { - short choice = (short)PIN(spinner->parameters.list.selected_index, 0, - NUMBEROF(cooperative_friendly_fire_modes) - 1); - struct widget_instance *help = list->parameters.list.extended_description; - - multiplayer.cooperative_friendly_fire = cooperative_friendly_fire_modes[choice]; - if (help && list->focused_child == named(list, "op_friendly_fire", 0)) - help->parameters.text_box.string_list_index = (short)(COOPERATIVE_FRIENDLY_FIRE_HELP + choice); - } - /* ... and its EXTRA ENEMIES, with the row of the amount of the one - chosen below it (network.coop_enemies_mode and its amounts, which the - host reads as the game begins: coop_enemies.c) */ + /* co-op's options in their place, rows opening their screens + (tools/port_settings.py's COOP_SETUP_SCREENS), each its help */ { struct widget_instance *help = list->parameters.list.extended_description; - short mode = _cooperative_enemies_none; - if ((spinner = named(list, "extra_enemies_spinner", 0)) != NULL) - { - mode = (short)PIN(spinner->parameters.list.selected_index, 0, - NUMBER_OF_COOPERATIVE_ENEMIES_MODES - 1); - } - visible_set(named(list, "op_extra_enemies", 0), cooperative); - visible_set(named(list, "op_enemies_per_player", 0), cooperative && mode == _cooperative_enemies_per_player); - visible_set(named(list, "op_enemies_multiplier", 0), cooperative && mode == _cooperative_enemies_multiplier); - if (cooperative && help) - { - if (list->focused_child == named(list, "op_extra_enemies", 0)) - help->parameters.text_box.string_list_index = (short)(COOPERATIVE_EXTRA_ENEMIES_HELP + mode); - else if (list->focused_child == named(list, "op_enemies_per_player", 0)) - help->parameters.text_box.string_list_index = COOPERATIVE_ENEMIES_PER_PLAYER_HELP; - else if (list->focused_child == named(list, "op_enemies_multiplier", 0)) - help->parameters.text_box.string_list_index = COOPERATIVE_ENEMIES_MULTIPLIER_HELP; - } - } - /* ... and its PLAYER COLLISIONS, below them: ON or OFF, its help its - choice's */ - visible_set(named(list, "op_player_collisions", 0), cooperative); - if (cooperative && (spinner = named(list, "player_collisions_spinner", 0)) != NULL) - { - short choice = (short)PIN(spinner->parameters.list.selected_index, 0, 1); - struct widget_instance *help = list->parameters.list.extended_description; - - multiplayer.cooperative_no_player_collisions = choice == 1; - if (help && list->focused_child == named(list, "op_player_collisions", 0)) - help->parameters.text_box.string_list_index = (short)(COOPERATIVE_PLAYER_COLLISIONS_HELP + choice); + visible_set(named(list, "op_coop_options", 0), cooperative); + visible_set(named(list, "op_voice_options", 0), cooperative); + if (help && list->focused_child == named(list, "op_coop_options", 0)) + help->parameters.text_box.string_list_index = COOPERATIVE_OPTIONS_HELP; + else if (help && list->focused_child == named(list, "op_voice_options", 0)) + help->parameters.text_box.string_list_index = COOPERATIVE_VOICE_OPTIONS_HELP; } } /* LISTING (an internet game's): PUBLIC, listed in everyone's server @@ -3096,7 +3085,21 @@ static boolean server_start(void) and its FRIENDLY FIRE) */ if (hosting_cooperative()) { + /* (as CO-OP OPTIONS left them: network.coop_friendly_fire, in + cooperative_friendly_fire_modes' order, and + network.coop_player_collisions) */ + static char const *const friendly_fire_names[] = { "off", "on", "shields_only", "explosives_only" }; + char const *friendly_fire = config_string("network.coop_friendly_fire"); + short index; + gametype_setup_end(); + multiplayer.cooperative_friendly_fire = _friendly_fire_on; + for (index = 0; friendly_fire && index < (short)NUMBEROF(friendly_fire_names); index++) + { + if (!strcmp(friendly_fire, friendly_fire_names[index])) + multiplayer.cooperative_friendly_fire = cooperative_friendly_fire_modes[index]; + } + multiplayer.cooperative_no_player_collisions = !config_boolean("network.coop_player_collisions"); network_game_server_port_set_cooperative_friendly_fire(multiplayer.cooperative_friendly_fire); network_game_server_port_set_cooperative_player_collisions(!multiplayer.cooperative_no_player_collisions); } @@ -3302,8 +3305,8 @@ scenario's path or name */ static void map_display_name(char const *map_name, wchar_t *text) { char const *const *names; - short last, index; - short count = xbox_multiplayer_map_count(ui_widget_port_multiplayer_maps(&names, &last)); + short index; + short count = xbox_multiplayer_map_count(ui_widget_port_multiplayer_maps(&names, NULL)); /* (a Custom Edition map's, if this machine has it: custom_edition_maps.c) */ if (custom_edition_level_name(map_name)) @@ -3493,12 +3496,12 @@ static void lobby_browser_update(struct widget_instance *list) lobby_browser.count = lobby_browser_valid_games(lobby_browser.games, (short)p2p_lobby_games(lobby_browser.games, LOBBY_BROWSER_GAMES)); - if (focused == BROWSER_ROWS - 1 && lobby_browser.first + BROWSER_ROWS < lobby_browser.count) + if (focused == BROWSER_ROWS - 1 && lobby_browser.first + BROWSER_ROWS < lobby_browser.count && list_scrolls_at_end()) { lobby_browser.first++; lobby_browser_focus_row(list, --focused); } - else if (focused == 0 && lobby_browser.first > 0) + else if (focused == 0 && lobby_browser.first > 0 && list_scrolls_at_end()) { lobby_browser.first--; lobby_browser_focus_row(list, ++focused); @@ -4009,6 +4012,27 @@ static boolean profile_save_changes(struct widget_instance *widget, boolean *wid return TRUE; } +/* "port profile settings save" (Gamepads' OK in a single-player campaign: +menu_tags.c's pause_settings_patch): the profile saved at once, not on +Settings' OK, so that the campaign's next save of the player's profile +keeps it; saving makes it the player's own (player_ui_save_profile), and it +is edited again from what was saved, for Settings to go on with */ +static boolean profile_settings_save(struct widget_instance *widget) +{ + long index = player_ui_get_edit_profile_index(); + + settings_each(screen_of(widget), setting_changed_save); + if (!player_ui_get_edit_player_profile() || !player_ui_edit_profile_is_dirty()) + return TRUE; + if (!player_ui_save_profile()) + { + platform_log("menus: could not save the profile's changes"); + return campaign_fail(); + } + player_ui_begin_editing_profile(index); + return TRUE; +} + /* "port pause end game" (the in-game pause menu's END GAME, the host's: menu_tags.c's pause_patch): the game ends as its time limit would, its players staying for the next (the carnage report, then the host's PICK GAME) */ @@ -4251,7 +4275,7 @@ static void lobby_row_text(short row, wchar_t *text) static void lobby_map_show(struct widget_instance *description, char const *map_name) { char const *const *names; - short last, count = xbox_multiplayer_map_count(ui_widget_port_multiplayer_maps(&names, &last)), map = 19, index; + short count = xbox_multiplayer_map_count(ui_widget_port_multiplayer_maps(&names, NULL)), map = 19, index; short level = campaign_level_of(map_name); struct widget_instance *widget; @@ -4282,6 +4306,59 @@ static void lobby_map_show(struct widget_instance *description, char const *map_ widget->parameters.text_box.string_list_index = level; } +/* the lobby's rows' texts as last updated, and their players' machines, +for their speaker icons (menu_functions_text_box_drawn); and when */ +static struct +{ + struct widget_instance *texts[LOBBY_ROWS]; + long machines[LOBBY_ROWS]; + short count; + unsigned long updated_at; +} lobby_icons; + +/* ui_widget.c's: a text box drawn, at the bounds its text was drawn in +(its font and justification still set): a lobby row's player talking in +voice chat (or muted) has a speaker just left of their name +(port/linux/game/network_voice.c) */ +void menu_functions_text_box_drawn(struct widget_instance *widget, rectangle2d const *bounds) +{ + short row; + + /* (as the lobby last updated them: the rows' widgets go with it) */ + if (!lobby_icons.count || system_milliseconds() - lobby_icons.updated_at > 250) + return; + for (row = 0; row < lobby_icons.count; row++) + { + long machine_index = lobby_icons.machines[row]; + rectangle2d icon; + short size; + + if (lobby_icons.texts[row] != widget) + continue; + if (!network_voice_machine_speaking(machine_index) && !network_voice_machine_muted(machine_index)) + return; + size = (short)MIN(14, bounds->y1 - bounds->y0); + icon.x1 = (short)(bounds->x1 - 4); + icon.y0 = (short)((bounds->y0 + bounds->y1 - size) / 2); + /* (the name's ink: the icon just left of it, centred on its + capitals) */ + if (widget->parameters.text_box.text && widget->parameters.text_box.text[0]) + { + rectangle2d text; + rectangle2d cursor; + + draw_unicode_string_compute_bounds(bounds, widget->parameters.text_box.text, &text, &cursor); + if (text.x0 - size - 6 >= bounds->x0) + icon.x1 = (short)(text.x0 - 6); + icon.y0 = (short)(draw_unicode_string_capital_middle(bounds, widget->parameters.text_box.text) - size / 2); + } + icon.x0 = (short)(icon.x1 - size); + icon.y1 = (short)(icon.y0 + size); + network_voice_draw_icon(&icon, network_voice_machine_muted(machine_index), 1.0f); + return; + } +} + /* "port lobby update" */ static void lobby_update(struct widget_instance *list) { @@ -4302,6 +4379,21 @@ static void lobby_update(struct widget_instance *list) if (multiplayer.lobby_first > MAX(0, lobby_player_count - LOBBY_ROWS)) multiplayer.lobby_first = (short)MAX(0, lobby_player_count - LOBBY_ROWS); rows_update(list, (short)MIN(lobby_player_count, LOBBY_ROWS), lobby_row_text); + /* (the rows' players, for their speaker icons) */ + { + struct widget_instance *row = list->child; + + lobby_icons.count = 0; + for (index = 0; row && index < MIN(lobby_player_count, LOBBY_ROWS); row = row->next, index++) + { + if (strncmp(row->name, "list_item_", 10)) + break; + lobby_icons.texts[index] = named(row, "list_item_text", 0); + lobby_icons.machines[index] = lobby_players[multiplayer.lobby_first + index]->machine_index; + lobby_icons.count = (short)(index + 1); + } + lobby_icons.updated_at = system_milliseconds(); + } lobby_join_help(list); visible_set(named(list, "lobby_button_team", 0), game && game->variant.universal_variant.teams); /* (the buttons' focus, off Switch Team when it is hidden) */ @@ -4536,7 +4628,8 @@ enum _option_health, /* the variant's health, tenths */ _option_short, /* a short of the options */ _option_option_byte, /* a byte of the options */ - _option_radar /* the options' radar players, and the variant's flag */ + _option_radar, /* the options' radar players, and the variant's flag */ + _option_setting /* the host's own (config.toml): Server Setup's copy only */ }; struct gametype_option @@ -4547,6 +4640,9 @@ struct gametype_option unsigned long argument; short count; long values[16]; + /* (_option_setting: the setting, and its values' text) */ + char const *setting; + char const *setting_values[16]; }; #define VARIANT_FIELD(field) (short)offsetof(struct game_variant, field) @@ -4638,11 +4734,52 @@ static struct gametype_option const gametype_options[] = { _friendly_fire_off, _friendly_fire_on, _friendly_fire_shields_only, _friendly_fire_explosives_only } }, { "friendly_fire_penalty_spinner", _option_short, OPTIONS_FIELD(friendly_fire_penalty), 0, 4, { 0, 5, 10, 15 } }, { "autobalance_spinner", _option_option_byte, OPTIONS_FIELD(auto_team_balance), 0, 2, { 0, 1 } }, + /* (the host's voice chat, below them: port/linux/game/network_voice.c; + tools/port_settings.py's TEAMPLAY_ROWS) */ + { "voice_mode_spinner", _option_setting, 0, 0, 5, { 0 }, "network.voice_mode", + { "off", "team_proximity", "team_enemy_proximity", "team_global", "team_global_enemy_proximity" } }, + { "voice_lobby_spinner", _option_setting, 0, 0, 2, { 0 }, "network.voice_lobby", { "true", "false" } }, + { "voice_kbps_spinner", _option_setting, 0, 0, 7, { 0 }, "network.voice_kbps", + { "8", "12", "16", "24", "32", "48", "64" } }, + { "voice_proximity_spinner", _option_setting, 0, 0, 6, { 0 }, "network.voice_proximity", + { "5", "10", "15", "20", "30", "50" } }, + /* (and the players' votes to kick: port/linux/game/network_votekick.c) */ + { "votekick_spinner", _option_setting, 0, 0, 2, { 0 }, "network.votekick", { "true", "false" } }, + /* co-op's options (Server Setup's CO-OP OPTIONS: tools/port_settings.py's + COOP_SETUP_SCREENS; coop_enemies.c) */ + { "coop_friendly_fire_spinner", _option_setting, 0, 0, 4, { 0 }, "network.coop_friendly_fire", + { "off", "on", "shields_only", "explosives_only" } }, + { "coop_extra_enemies_spinner", _option_setting, 0, 0, 3, { 0 }, "network.coop_enemies_mode", + { "none", "per_player", "multiplier" } }, + { "coop_enemies_per_player_spinner", _option_setting, 0, 0, 5, { 0 }, "network.coop_enemies", + { "25", "50", "100", "150", "200" } }, + { "coop_enemies_multiplier_spinner", _option_setting, 0, 0, 5, { 0 }, "network.coop_enemies_multiplier", + { "2", "4", "8", "16", "32" } }, + { "coop_player_collisions_spinner", _option_setting, 0, 0, 2, { 0 }, "network.coop_player_collisions", + { "true", "false" } }, /* vehicle options (the side's set and counts: vehicles_update) */ { "vehicles_respawn_spinner", _option_short, OPTIONS_FIELD(vehicle_respawn_time), 0, 7, { 0, 30, 60, 90, 120, 180, 300 } }, }; +/* an option of the host's own setting: the value shown for it (the one +set, else the nearest) */ +static short gametype_option_setting_index(struct gametype_option const *option) +{ + struct pc_menu_setting setting; + char text[64]; + short index; + + csmemset(&setting, 0, sizeof(setting)); + setting.setting = option->setting; + setting.value_count = option->count; + for (index = 0; index < option->count; index++) + setting.values[index] = option->setting_values[index]; + if (!setting_text(option->setting, text, sizeof(text), FALSE)) + return 0; + return setting_value_index(&setting, text); +} + /* (an instance's name is its definition's cut to 31 characters: "item_options_infinite_grenades_") */ #define WIDGET_NAME_LENGTH 31 @@ -4748,6 +4885,25 @@ static void gametype_options_each(struct widget_instance *list, boolean save) if (!option) continue; + /* (the host's own settings: Server Setup's alone, their rows + hidden in Edit Gametypes', which edits a gametype for any game; + written on OK, as the gametype's are kept) */ + if (option->kind == _option_setting) + { + short index = (short)PIN(spinner->parameters.list.selected_index, 0, option->count - 1); + + visible_set(row, gametype_edit.setup); + if (!gametype_edit.setup) + continue; + if (!save) + spinner->parameters.list.selected_index = gametype_option_setting_index(option); + else if (index != gametype_option_setting_index(option) && + !setting_write(option->setting, option->setting_values[index])) + { + platform_log("menus: could not set %s", option->setting); + } + continue; + } if (save) { short index = (short)PIN(spinner->parameters.list.selected_index, 0, option->count - 1); @@ -4761,6 +4917,36 @@ static void gametype_options_each(struct widget_instance *list, boolean save) } } +/* each of a screen's rows of the host's own settings (_option_setting: +co-op's options' screens, tools/port_settings.py's _setup_option_screen), +shown, or (save) set */ +static void setting_options_each(struct widget_instance *list, boolean save) +{ + struct widget_instance *row; + + for (row = list->child; row; row = row->next) + { + struct widget_instance *spinner; + + for (spinner = row->child; spinner; spinner = spinner->next) + { + struct gametype_option const *option = spinner->type == 2 ? gametype_option_named(spinner->name) : NULL; + short index; + + if (!option || option->kind != _option_setting) + continue; + index = (short)PIN(spinner->parameters.list.selected_index, 0, option->count - 1); + if (!save) + spinner->parameters.list.selected_index = gametype_option_setting_index(option); + else if (index != gametype_option_setting_index(option) && + !setting_write(option->setting, option->setting_values[index])) + { + platform_log("menus: could not set %s", option->setting); + } + } + } +} + /* the options' list of a button of its bar (OK's): the one above it whose rows are its options */ static struct widget_instance *gametype_options_list(struct widget_instance *widget) @@ -4999,7 +5185,7 @@ static void gametype_edit_list_update(struct widget_instance *list) gametype_edit_read(); focused = list_scroll(list, &gametype_edit.first, gametype_edit.count, GAMETYPE_EDIT_ROWS); if (focused != NONE) - gametype_edit.chosen = (short)MIN(focused, gametype_edit.count - 1); + gametype_edit.chosen = (short)MAX(0, MIN(focused, gametype_edit.count - 1)); rows_update(list, (short)MIN(gametype_edit.count, GAMETYPE_EDIT_ROWS), gametype_edit_row_text); visible_set(named(description, "gametype_right_item", 0), gametype_edit.count > 0); if (gametype_edit.chosen < gametype_edit.count) @@ -5093,6 +5279,15 @@ static void gametype_option_help(struct widget_instance *list) visible_set(named(list, "op_primary_weapon", 0), custom); visible_set(named(list, "op_secondary_weapon", 0), custom); } + /* (CO-OP OPTIONS: the row of the amount of the extra enemies chosen, + PER PLAYER's or MULTIPLIER's, in the one place) */ + if (named(list, "coop_extra_enemies_spinner", 0)) + { + short mode = named(list, "coop_extra_enemies_spinner", 0)->parameters.list.selected_index; + + visible_set(named(list, "op_coop_enemies_per_player", 0), mode == _cooperative_enemies_per_player); + visible_set(named(list, "op_coop_enemies_multiplier", 0), mode == _cooperative_enemies_multiplier); + } /* (the server browser's filters, hidden: their helps are fewer than their values) */ if (!description || !list->focused_child || !strncmp(list->name, "filters", 7)) @@ -5261,6 +5456,18 @@ boolean pc_menu_event_function_invoke( { return gametype_setup_edit(); } + /* (co-op's options' screens: their settings shown, and OK's) */ + else if (!strcmp(name, "port setup options init")) + { + setting_options_each(widget, FALSE); + } + else if (!strcmp(name, "port setup options save")) + { + struct widget_instance *list = gametype_options_list(widget); + + if (list) + setting_options_each(list, TRUE); + } else if (!strcmp(name, "mp profile save changes")) { return ui_widget_port_gametype_save(widget, widget_deleted); @@ -5311,6 +5518,10 @@ boolean pc_menu_event_function_invoke( { return profile_save_changes(widget, widget_deleted); } + else if (!strcmp(name, "port profile settings save")) + { + return profile_settings_save(widget); + } else if (!strcmp(name, "direct ip connect go")) { return direct_link_from_clipboard(); diff --git a/port/linux/game/menu_tags.c b/port/linux/game/menu_tags.c index 4a8d79f4..93492123 100644 --- a/port/linux/game/menu_tags.c +++ b/port/linux/game/menu_tags.c @@ -28,10 +28,14 @@ A multiplayer map gets them too, for its in-game pause menu's SETTINGS (pause_patch): there the map's tags they name that it has not (ui.map's pictures, sounds and screens) are none, a missing picture drawn blank, and no screen pauses the game (a network game's clock runs on). + +So does a single-player campaign's map (pause_campaign_patch), whose screens +pause it as its pause menu does. */ #include "cseries.h" #include "bitmaps/bitmap_group.h" +#include "game/game.h" #include "tag_files/tag_groups.h" #include "text/text_group.h" #include "rasterizer/xbox/rasterizer_xbox_hardware_bitmaps.h" @@ -47,9 +51,12 @@ void platform_log(char const *format, ...); char const *config_string(char const *name); int platform_display_resolutions(long *widths, long *heights, int maximum); int platform_window_sizes(long *widths, long *heights, int maximum); +/* (sdl_platform.c's PLATFORM_AUDIO_DEVICE_NAME_SIZE) */ +int platform_audio_devices(int recording, char (*names)[128], int maximum); /* the game's network server (this machine hosts: network_game_globals.c) */ void *global_network_game_server_get(void); +boolean network_game_is_active(void); /* cache_files.c's (port) */ void *cache_files_tag_instances(long *count); @@ -73,6 +80,8 @@ boolean pc_menu_frame_placement(struct bitmap_data const *bitmap, short *x, shor /* the multiplayer maps' widget collection, whose pause screens pause_patch adds to */ #define MULTIPLAYER_COLLECTION "ui\\shell\\multiplayer" +/* the campaign's full screen pause screen, which pause_campaign_patch adds to */ +#define CAMPAIGN_PAUSE_SCREEN "ui\\shell\\solo_game\\pause_game\\pause_game" /* the tags pause_patch adds: two buttons and their texts */ #define PAUSE_PATCH_TAGS 4 /* what a frame of ui.map's is drawn as in a game map, which has not got it */ @@ -353,6 +362,7 @@ static char const *const port_function_names[] = "mp level select", "mp profiles list initialize", "mp profiles list dispose", "mp profile set for game", "port lobby preview join", "port setup edit", + "port setup options init", "port setup options save", "port map list back", "port pause end game", "port coop begin", "port coop player 2 list initialize", "port coop player 2", @@ -373,6 +383,8 @@ static char const *const port_function_names[] = "player profile save changes", /* (the server browser's password screen) */ "port password init", "port password edit", "port password join", "port password back", + /* (Gamepads' OK in a single-player campaign: pause_settings_patch) */ + "port profile settings save", }; /* the PC version's game data functions that the Xbox's have not, from @@ -428,9 +440,11 @@ static struct long *text_tags; long *spinner_tags; boolean failed; - /* a multiplayer map's build (the pause menu's SETTINGS): what the map - has not got is none, not a failure */ + /* a game map's build (the pause menu's SETTINGS): what the map has not + got is none, not a failure */ boolean game_map; + /* a single-player campaign's map's, whose screens pause the game */ + boolean campaign; } build; /* ---------- private code */ @@ -735,13 +749,50 @@ static long split(char const *text, char const **pieces) /* a spinner's strings, or its values, split: its own, but Video Setup's sizes are the display's (sdl_platform.c), shown "1920 x 1080" and set "1920x1080": its resolutions after Resolution's own (NATIVE), and the -window sizes that fit it in place of Window Size's own */ +window sizes that fit it in place of Window Size's own; and Audio Setup's +devices SDL finds, after their SYSTEM DEFAULT, each set by its name and +shown by it (in capitals, as the menus' words are, and cut short) */ static long spinner_split(struct halo_menu_widget const *widget, boolean values, char const **pieces) { long count = split(values ? widget->values : widget->strings, pieces); long widths[MAXIMUM_STRINGS], heights[MAXIMUM_STRINGS]; long added, index; + if (widget->setting && (!strcmp(widget->setting, "audio.output_device") || + !strcmp(widget->setting, "audio.input_device"))) + { + static char names[MAXIMUM_STRINGS][128]; + + added = platform_audio_devices(!strcmp(widget->setting, "audio.input_device"), names, + (int)(MAXIMUM_STRINGS - count)); + for (index = 0; index < added; index++) + { + char shown[32]; + char const *name = names[index]; + char *piece; + long length; + + if (!values) + { + long at; + + for (at = 0; name[at] && at < (long)sizeof(shown) - 1; at++) + shown[at] = name[at] >= 'a' && name[at] <= 'z' ? (char)(name[at] - 'a' + 'A') : name[at]; + shown[at] = 0; + /* (cut short: what the spinner shows) */ + if (strlen(name) > 18) + strcpy(shown + 15, "..."); + name = shown; + } + length = (long)strlen(name); + piece = allocate(length + 1); + if (!piece) + break; + strcpy(piece, name); + pieces[count++] = piece; + } + return count; + } if (widget->setting && !strcmp(widget->setting, "display.resolution")) added = platform_display_resolutions(widths, heights, (int)(MAXIMUM_STRINGS - count)); else if (widget->setting && !strcmp(widget->setting, "display.window_size")) @@ -862,7 +913,18 @@ static void *bitmap_build(struct halo_menu_bitmap const *source, long tag_index) } bitmap->width = (short)width; bitmap->height = (short)height; - menu_tags.bitmaps = realloc(menu_tags.bitmaps, (menu_tags.bitmap_count + 1) * sizeof(*menu_tags.bitmaps)); + { + struct bitmap_data **bitmaps = realloc(menu_tags.bitmaps, + (menu_tags.bitmap_count + 1) * sizeof(*menu_tags.bitmaps)); + + if (!bitmaps) + { + rasterizer_bitmap_delete(bitmap); + problem(source->file, source->line, "out of memory for the bitmaps of", source->name); + return group; + } + menu_tags.bitmaps = bitmaps; + } menu_tags.bitmaps[menu_tags.bitmap_count++] = bitmap; /* (none without a renderer: debug.null_renderer) */ if (bitmap->hardware_format) @@ -992,8 +1054,15 @@ static void handler_build(struct ui_widget_event_handler_reference *handler, str static void setting_add(struct halo_menu_widget const *source, long definition_index) { struct pc_menu_setting *setting; + struct pc_menu_setting *settings = realloc(menu_tags.settings, + (menu_tags.setting_count + 1) * sizeof(*menu_tags.settings)); - menu_tags.settings = realloc(menu_tags.settings, (menu_tags.setting_count + 1) * sizeof(*menu_tags.settings)); + if (!settings) + { + problem(source->file, source->line, "out of memory for the settings of", source->name); + return; + } + menu_tags.settings = settings; setting = &menu_tags.settings[menu_tags.setting_count++]; memset(setting, 0, sizeof(*setting)); setting->definition_index = definition_index; @@ -1048,7 +1117,7 @@ static void *widget_build(long widget_index) definition->flags = flags_parse(source->flags, widget_flag_names, NUMBEROF(widget_flag_names), source->file, source->line); /* (in a network game the clock runs on) */ - if (build.game_map) + if (build.game_map && !build.campaign) definition->flags &= ~FLAG(1); /* pause_game */ definition->milliseconds_to_auto_close = source->auto_close; definition->auto_close_fade_time = source->auto_close_fade; @@ -1492,9 +1561,10 @@ static void *pause_button(struct cache_file_tag_instance *instances, struct ui_w } /* the list's buttons: SETTINGS (and the host's END GAME) put before LEAVE -GAME (quit); returns how many were added */ +GAME (quit); returns how many were added, and how much taller the list +grew for them (growth) */ static long pause_list_patch(struct cache_file_tag_instance *instances, struct ui_widget_definition *list, long quit, - boolean host) + boolean host, short *growth) { struct ui_widget_child_reference *children = list->child_widgets.address; long count = list->child_widgets.count, added = host ? 2 : 1, child; @@ -1536,8 +1606,25 @@ static long pause_list_patch(struct cache_file_tag_instance *instances, struct u } list->child_widgets.address = grown; list->child_widgets.count = count + added; - /* (the list draws within its bounds) */ - list->bounds.y1 = (short)(list->bounds.y1 + added * spacing); + /* (a list with room for them, Halo PC's of five rows that Custom Edition + maps keep two of: the buttons centred in it; else it grows, as the + Xbox's of two rows does, and draws within its bounds) */ + { + short room = (short)(list->bounds.y1 - list->bounds.y0); + short column = (short)((count + added - 1) * spacing + model->bounds.y1 - model->bounds.y0); + + *growth = 0; + if (column <= room) + { + for (child = 0; child < count + added; child++) + grown[child].vertical_offset = (short)((room - column) / 2 + child * spacing); + } + else + { + *growth = (short)(added * PAUSE_BUTTON_SPACING); + list->bounds.y1 = (short)(list->bounds.y1 + added * spacing); + } + } return added; } @@ -1585,6 +1672,7 @@ static void pause_patch(struct cache_file_tag_instance *instances) boolean host = global_network_game_server_get() != NULL; struct tag_block const *screens; long patched_list = NONE, added = 0, buttons = 0, screen; + short grow = 0; boolean box_redrawn = FALSE; if (collection == NONE || quit_function == NONE) @@ -1596,7 +1684,7 @@ static void pause_patch(struct cache_file_tag_instance *instances) struct ui_widget_definition *definition; struct ui_widget_child_reference *children; long child, list_child = NONE, box_child = NONE; - short grow, list_top; + short list_top; if (screen_tag == NONE) continue; @@ -1621,7 +1709,7 @@ static void pause_patch(struct cache_file_tag_instance *instances) quit = pause_quit_button(list, quit_function); if (quit == NONE || patched_list != NONE) continue; - added = pause_list_patch(instances, list, quit, host); + added = pause_list_patch(instances, list, quit, host, &grow); if (!added) return; buttons = list->child_widgets.count; @@ -1639,8 +1727,8 @@ static void pause_patch(struct cache_file_tag_instance *instances) } } /* the stock box: taller, centred where it was, the list with it, and - what is below it moved down; else only what is below the list */ - grow = (short)(added * PAUSE_BUTTON_SPACING); + what is below it moved down; else only what is below the list (as far + as the list grew) */ list_top = children[list_child].vertical_offset; for (child = 0; child < definition->child_widgets.count; child++) { @@ -1659,6 +1747,191 @@ static void pause_patch(struct cache_file_tag_instance *instances) platform_log("menus: the pause menu has SETTINGS%s", host ? " and END GAME" : ""); } +/* ---------- the single-player campaign's pause menu + +Its list gets the same SETTINGS (pause_list_patch) before REVERT TO SAVED, a +copy of it; a list with room centres its rows (Halo PC's), and one that has +none keeps its size, its rows closer, as its box beside the mission +objectives' does. Settings there has only the items that work in a game +(pause_settings_patch), and Gamepads' OK saves the profile itself. */ + +#define PAUSE_SETTINGS_LIST "main_menu/settings_select/player_setup/player_profile_edit/profile_edit_select_list" +#define PAUSE_SETTINGS_GAMEPADS_OK "main_menu/settings_select/player_setup/player_profile_edit/gamepad_setup/button_ok" + +/* Change Name's keyboard and Change Color's pictures are ui.map's, Network +Setup's settings are not for a game under way, and About is nothing to it */ +static char const *const pause_settings_hidden_items[] = +{ + "main_menu/settings_select/player_setup/player_profile_edit/name_profile_item", + "main_menu/settings_select/player_setup/player_profile_edit/network_settings_profile_item", + "main_menu/settings_select/player_setup/player_profile_edit/color_profile_item", + "main_menu/settings_select/player_setup/player_profile_edit/about_item", +}; + +/* the list's rows spaced evenly over the span its rows had (a list that grew +for SETTINGS, back to its own size) */ +static void pause_list_fit(struct ui_widget_definition *list, short span) +{ + struct ui_widget_child_reference *children = list->child_widgets.address; + long count = list->child_widgets.count, child; + + for (child = 1; child < count; child++) + children[child].vertical_offset = (short)(children[0].vertical_offset + child * span / (count - 1)); +} + +static boolean pause_settings_item_hidden(long tag_index) +{ + short index; + + for (index = 0; index < (short)NUMBEROF(pause_settings_hidden_items); index++) + { + long widget = widget_named(pause_settings_hidden_items[index]); + + if (widget != NONE && build.widget_tags[widget] == tag_index) + return TRUE; + } + return FALSE; +} + +/* the hidden items stay in the list, empty and taking no events (which it +passes over), as an item's description is the one at its place in the list; +the others move up into the first places */ +static void pause_settings_patch(void) +{ + long list_widget = widget_named(PAUSE_SETTINGS_LIST); + long ok_widget = widget_named(PAUSE_SETTINGS_GAMEPADS_OK); + long settings_save = function_index("port settings save", "pause", 0); + struct ui_widget_definition *list, *ok; + struct ui_widget_child_reference *children; + struct ui_widget_event_handler_reference *handlers; + short places[16]; + long child, handler, place_count = 0, place = 0; + + if (list_widget == NONE || ok_widget == NONE) + return; + list = tag_get(UI_WIDGET_DEFINITION_TAG, build.widget_tags[list_widget]); + children = list->child_widgets.address; + for (child = 0; child < list->child_widgets.count && place_count < (long)NUMBEROF(places); child++) + { + struct ui_widget_definition *item = tag_get(UI_WIDGET_DEFINITION_TAG, children[child].widget_tag.index); + + if (item->type == _widget_type_text_box) + places[place_count++] = (short)(item->bounds.y0 + children[child].vertical_offset); + } + for (child = 0; child < list->child_widgets.count && place < place_count; child++) + { + struct ui_widget_definition *item = tag_get(UI_WIDGET_DEFINITION_TAG, children[child].widget_tag.index); + + if (item->type != _widget_type_text_box) + continue; + if (pause_settings_item_hidden(children[child].widget_tag.index)) + { + item->event_handlers.count = 0; + reference_clear(&item->background_bitmap, BITMAP_GROUP_TAG); + reference_clear(&item->text_label_string_list, UNICODE_STRING_LIST_TAG); + /* (nowhere for the mouse to focus it) */ + item->bounds.y1 = item->bounds.y0; + item->bounds.x1 = item->bounds.x0; + } + else + { + children[child].vertical_offset = (short)(places[place++] - item->bounds.y0); + } + } + ok = tag_get(UI_WIDGET_DEFINITION_TAG, build.widget_tags[ok_widget]); + handlers = ok->event_handlers.address; + for (handler = 0; handler < ok->event_handlers.count; handler++) + { + if (handlers[handler].function == settings_save) + handlers[handler].function = (short)function_index("port profile settings save", "pause", 0); + } +} + +static void pause_campaign_patch(struct cache_file_tag_instance *instances) +{ + long screen = tag_loaded(UI_WIDGET_DEFINITION_TAG, CAMPAIGN_PAUSE_SCREEN); + struct ui_widget_definition *definition; + struct ui_widget_child_reference *children; + long child; + + if (screen == NONE) + return; + definition = tag_get(UI_WIDGET_DEFINITION_TAG, screen); + children = definition->child_widgets.address; + for (child = 0; child < definition->child_widgets.count; child++) + { + struct ui_widget_definition *list; + struct ui_widget_child_reference const *rows; + short span, grow, bottom; + long revert; + + if (children[child].widget_tag.index == NONE) + continue; + list = tag_get(UI_WIDGET_DEFINITION_TAG, children[child].widget_tag.index); + if (list->type != _widget_type_column_list || list->child_widgets.count < 2) + continue; + rows = list->child_widgets.address; + /* (REVERT TO SAVED, by its tag: it opens its confirmation rather + than running a function, and a map's list need not have it second) */ + for (revert = 0; revert < list->child_widgets.count; revert++) + { + if (tag_name_ends(rows[revert].widget_tag.index, "\\restart_at_save_point_button")) + break; + } + if (revert == list->child_widgets.count) + return; + span = (short)(rows[list->child_widgets.count - 1].vertical_offset - rows[0].vertical_offset); + bottom = list->bounds.y1; + if (pause_list_patch(instances, list, revert, FALSE, &grow)) + { + if (grow) + { + pause_list_fit(list, span); + list->bounds.y1 = bottom; + } +#ifdef HALO_ANDROID + /* port: in VR the rows closer within the span they had even when + the list has room: the pause menu's button hints (B Cancel, A + Select) sit just below its last row, which a list grown longer + and centred would put on their line */ + else if (halo_vr_active()) + { + pause_list_fit(list, span); + } +#endif + pause_settings_patch(); + platform_log("menus: the pause menu has SETTINGS"); + } + return; + } +} + +/* the campaign played here by one player: not network co-op, nor split +screen */ +static boolean single_player_campaign_map(void) +{ + return tag_loaded('Soul', MULTIPLAYER_COLLECTION) == NONE && + tag_loaded(UI_WIDGET_DEFINITION_TAG, CAMPAIGN_PAUSE_SCREEN) != NONE && + game_connection() == _game_connection_local && !network_game_is_active() && !game_is_cooperative(); +} + +/* whether display.menus asks for the PC version's menus: "pc" in any case +(a value neither "pc" nor "xbox" is said once in the log, and is the Xbox's) */ +static boolean menus_pc_chosen(void) +{ + static boolean said; + char const *value = config_string("display.menus"); + + if (!csstrcasecmp(value, "pc")) + return TRUE; + if (csstrcasecmp(value, "xbox") && !said) + { + platform_log("menus: display.menus \"%s\" is not \"xbox\" or \"pc\": the Xbox's", value); + said = TRUE; + } + return FALSE; +} + void menu_tags_loaded( char const *map_name) { @@ -1667,10 +1940,12 @@ void menu_tags_loaded( long widget_count, own_lists = 0, total, index; boolean game_map = strcmp(map_name, "ui") != 0; + boolean campaign = game_map && single_player_campaign_map(); - /* (ui.map, and a multiplayer map: its pause menu's SETTINGS) */ - if ((game_map && tag_loaded('Soul', MULTIPLAYER_COLLECTION) == NONE) || - strcmp(config_string("display.menus"), "pc")) + /* (ui.map, and a multiplayer map: its pause menu's SETTINGS; a + single-player campaign's too) */ + if ((game_map && tag_loaded('Soul', MULTIPLAYER_COLLECTION) == NONE && !campaign) || + !menus_pc_chosen()) { return; } @@ -1683,6 +1958,7 @@ void menu_tags_loaded( memset(&build, 0, sizeof(build)); build.menus = menus; build.game_map = game_map; + build.campaign = campaign; widget_count = menus->widget_count; build.widget_tags = malloc((widget_count + 1) * sizeof(long)); build.text_tags = malloc((widget_count + 1) * sizeof(long)); @@ -1765,6 +2041,8 @@ void menu_tags_loaded( if (game_map) { pause_patch(instances); + if (campaign) + pause_campaign_patch(instances); if (build.failed) goto failed; /* (those it made) */ diff --git a/port/linux/game/network_damage.c b/port/linux/game/network_damage.c index 519722c7..3ed444a5 100644 --- a/port/linux/game/network_damage.c +++ b/port/linux/game/network_damage.c @@ -1163,20 +1163,28 @@ static short distributed_grenade_throw( return oldest; } -/* whether an explosion of the damage is among the player's this tick -(distributed_report_paid): at the epicenter, or (NULL) anywhere */ +/* whether an explosion of the type of grenade is among the player's this +tick (distributed_report_paid), of any damage the grenade deals (one +explosion deals more than one, each reported: a frag grenade's explosion +and its shock wave): at the epicenter, or (NULL) anywhere */ static boolean distributed_explosion_this_tick( short player_index, - long damage_index, + short grenade_type, real_point3d const *epicenter) { + struct game_globals_grenade *grenade = TAG_BLOCK_GET_ELEMENT(&scenario_get_game_globals()->grenades, + grenade_type, struct game_globals_grenade); short index; - if (damage_explosions[player_index].time != game_time_get()) + if (damage_explosions[player_index].time != game_time_get() || grenade->projectile.index == NONE) return FALSE; for (index = 0; index < damage_explosions[player_index].count; index++) { - if (damage_explosions[player_index].explosions[index].definition_index == damage_index && + byte kinds; + struct damage_reach reach; + + if (distributed_source_deals(grenade->projectile.index, + damage_explosions[player_index].explosions[index].definition_index, TRUE, &kinds, &reach) > 0.0f && (!epicenter || (damage_explosions[player_index].explosions[index].epicenter.x == epicenter->x && damage_explosions[player_index].explosions[index].epicenter.y == epicenter->y && damage_explosions[player_index].explosions[index].epicenter.z == epicenter->z))) @@ -1189,10 +1197,11 @@ static boolean distributed_explosion_this_tick( /* how many hits a second the player could deal of the damage: their weapons, lately, a grenade the host saw them throw that has not gone off -(or one going off this tick, hitting more than one object), the vehicle -they drove lately; 0 when none of them deals it; how they deal it (the -_damage_source flags); how far from them what they fire deals it; and the -type of grenade when nothing but a grenade of theirs deals it, else NONE */ +(or one going off this tick, hitting more than one object, dealing more +than one damage), the vehicle they drove lately; 0 when none of them deals +it; how they deal it (the _damage_source flags); how far from them what +they fire deals it; and the type of grenade when nothing but a grenade of +theirs deals it, else NONE */ static real distributed_player_deals( short player_index, long damage_index, @@ -1255,7 +1264,7 @@ static real distributed_player_deals( struct game_globals_grenade); boolean thrown = distributed_grenade_throw(player_index, index) != NONE; - if ((thrown || distributed_explosion_this_tick(player_index, damage_index, NULL)) && + if ((thrown || distributed_explosion_this_tick(player_index, index, NULL)) && grenade->projectile.index != NONE) { byte this_kinds; @@ -1835,10 +1844,10 @@ static boolean distributed_report_valid( return FALSE; /* what only a grenade of theirs deals, from one the host saw them throw that has not gone off: its explosion once (the explosion's other objects - that tick come free, distributed_report_paid), what it hits at a point - while it is about */ + that tick come free, distributed_report_paid, and its other damage takes + no throw of its own), what it hits at a point while it is about */ if (grenade_type != NONE && - !distributed_explosion_this_tick(player_index, report->damage.definition_index, &report->damage.epicenter)) + !distributed_explosion_this_tick(player_index, grenade_type, &report->damage.epicenter)) { throw_index = distributed_grenade_throw(player_index, grenade_type); if (throw_index == NONE) diff --git a/port/linux/game/network_distributed.c b/port/linux/game/network_distributed.c index 339c080b..9d0f363f 100644 --- a/port/linux/game/network_distributed.c +++ b/port/linux/game/network_distributed.c @@ -72,6 +72,8 @@ machine (their datum identifiers need not be). #include "units/bipeds.h" #include "network_coop.h" #include "network_distributed.h" +#include "network_voice.h" +#include "profile_sections.h" #include #include @@ -457,6 +459,28 @@ struct distributed_packer static long distributed_last_sent_time = NONE; +/* the tick's steps, timed in the profiling build */ +PROFILE_SECTION(distributed_tick_apply_predictions_section, "network_distributed_tick.apply_predictions") +PROFILE_SECTION(distributed_tick_apply_vehicle_predictions_section, "network_distributed_tick.apply_vehicle_predictions") +PROFILE_SECTION(distributed_tick_objects_host_tick_section, "network_distributed_tick.objects_host_tick") +PROFILE_SECTION(distributed_tick_plan_players_section, "network_distributed_tick.plan_players") +PROFILE_SECTION(distributed_tick_damage_host_tick_section, "network_distributed_tick.damage_host_tick") +PROFILE_SECTION(distributed_tick_send_statistics_section, "network_distributed_tick.send_statistics") +PROFILE_SECTION(distributed_tick_send_pings_section, "network_distributed_tick.send_pings") +PROFILE_SECTION(distributed_tick_send_structure_bsp_section, "network_distributed_tick.send_structure_bsp") +PROFILE_SECTION(distributed_tick_send_players_section, "network_distributed_tick.send_players") +PROFILE_SECTION(distributed_tick_actors_host_tick_section, "network_distributed_tick.actors_host_tick") +PROFILE_SECTION(distributed_tick_coop_host_tick_section, "network_distributed_tick.coop_host_tick") +PROFILE_SECTION(distributed_tick_send_pickups_section, "network_distributed_tick.send_pickups") +PROFILE_SECTION(distributed_tick_send_game_state_section, "network_distributed_tick.send_game_state") +PROFILE_SECTION(distributed_tick_note_own_positions_section, "network_distributed_tick.note_own_positions") +PROFILE_SECTION(distributed_tick_send_inputs_section, "network_distributed_tick.send_inputs") +PROFILE_SECTION(distributed_tick_send_predictions_section, "network_distributed_tick.send_predictions") +PROFILE_SECTION(distributed_tick_objects_client_tick_section, "network_distributed_tick.objects_client_tick") +PROFILE_SECTION(distributed_tick_damage_client_tick_section, "network_distributed_tick.damage_client_tick") +PROFILE_SECTION(distributed_tick_coop_client_tick_section, "network_distributed_tick.coop_client_tick") +PROFILE_SECTION(distributed_tick_batches_flush_section, "network_distributed_tick.batches_flush") + /* how each player last died, by absolute index: the host's own, which it sends its clients, and a client's copy of the host's */ static struct distributed_death @@ -2245,6 +2269,8 @@ static void distributed_handle_unit_state( if (TEST_FLAG(state->flags, _distributed_unit_placed_bit) && object_get(unit_index)->object.parent_object_index == NONE) { + real_point3d before = object_get(unit_index)->object.position; + if (local) distributed_correct_own_unit(player, unit_index, state); else @@ -2252,6 +2278,16 @@ static void distributed_handle_unit_state( distributed_apply_state(unit_index, state, &state->position, REMOTE_CORRECTION_TOLERANCE, REMOTE_BLEND_DISTANCE, 0.0f, 0.0f); } + /* moved further than a step at once (the host's teleporter sent + it): on the teleporter it lands on, as a teleporter leaves the + player it sends, so that this machine's does not send it back each + time the host's word puts it there until it steps off + (game_engine_update_teleporter) */ + if (distance_squared3d(&before, &object_get(unit_index)->object.position) > 1.0f) + { + player->teleporter_index = find_netgame_flag(&object_get(unit_index)->object.position, 1.0f, 0.0f, + _netgame_flag_teleporter_source, NONE); + } } } @@ -3301,6 +3337,7 @@ void network_distributed_new_game( bots_new_game(); network_actors_new_game(); network_coop_new_game(); + network_votekick_new_game(); } /* after each tick (game_time.c) */ @@ -3333,43 +3370,45 @@ void network_distributed_tick( decided first: the damage goes where the players it hurt do), and a kill's statistics before the kill, so that a client announcing it counts it (a double kill, a killing spree) */ - distributed_apply_predictions(); - network_objects_apply_vehicle_predictions(); - network_objects_host_tick(); - distributed_host_plan_players(); - network_damage_host_tick(); + profile_scope(distributed_tick_apply_predictions_section, distributed_apply_predictions();) + profile_scope(distributed_tick_apply_vehicle_predictions_section, network_objects_apply_vehicle_predictions();) + profile_scope(distributed_tick_objects_host_tick_section, network_objects_host_tick();) + profile_scope(distributed_tick_plan_players_section, distributed_host_plan_players();) + profile_scope(distributed_tick_damage_host_tick_section, network_damage_host_tick();) if (distributed_statistics_due || game_time_get() % STATISTICS_INTERVAL_TICKS == 0) - distributed_send_statistics(game_time_get() % STATISTICS_REFRESH_TICKS == 0); + profile_scope(distributed_tick_send_statistics_section, + distributed_send_statistics(game_time_get() % STATISTICS_REFRESH_TICKS == 0);) distributed_statistics_due = FALSE; if (game_time_get() % PING_INTERVAL_TICKS == 0) - distributed_send_pings(); + profile_scope(distributed_tick_send_pings_section, distributed_send_pings();) /* (before the players, so clients load a new BSP before they hear where the host moved everyone into it) */ if (network_coop_active() && (global_structure_bsp_index_get() != distributed_sent_structure_bsp_index || game_time_get() % STRUCTURE_BSP_INTERVAL_TICKS == 0)) { - distributed_send_structure_bsp(); + profile_scope(distributed_tick_send_structure_bsp_section, distributed_send_structure_bsp();) } - distributed_host_send_players(); + profile_scope(distributed_tick_send_players_section, distributed_host_send_players();) network_vr_tick(); bots_tick(); - network_actors_host_tick(); - network_coop_host_tick(); - distributed_send_pickups(); + profile_scope(distributed_tick_actors_host_tick_section, network_actors_host_tick();) + profile_scope(distributed_tick_coop_host_tick_section, network_coop_host_tick();) + profile_scope(distributed_tick_send_pickups_section, distributed_send_pickups();) if (game_time_get() % GAME_STATE_INTERVAL_TICKS == 0) - distributed_send_game_state(NONE); + profile_scope(distributed_tick_send_game_state_section, distributed_send_game_state(NONE);) + network_votekick_host_tick(); } else if (connection == _game_connection_network_client) { - distributed_note_own_positions(); - distributed_client_send_inputs(); + profile_scope(distributed_tick_note_own_positions_section, distributed_note_own_positions();) + profile_scope(distributed_tick_send_inputs_section, distributed_client_send_inputs();) network_vr_tick(); - distributed_client_send_predictions(); - network_objects_client_tick(); - network_damage_client_tick(); - network_coop_client_tick(); + profile_scope(distributed_tick_send_predictions_section, distributed_client_send_predictions();) + profile_scope(distributed_tick_objects_client_tick_section, network_objects_client_tick();) + profile_scope(distributed_tick_damage_client_tick_section, network_damage_client_tick();) + profile_scope(distributed_tick_coop_client_tick_section, network_coop_client_tick();) } - distributed_batches_flush(); + profile_scope(distributed_tick_batches_flush_section, distributed_batches_flush();) distributed_machines.in_tick = FALSE; distributed_machines.valid = FALSE; } @@ -3409,6 +3448,7 @@ static boolean distributed_message_stale( case _distributed_message_damage_animations: case _distributed_message_coop_screen_effect: case _distributed_message_coop_device_states: + case _distributed_message_votekick_status: break; default: return FALSE; @@ -3425,7 +3465,7 @@ static boolean distributed_message_stale( /* (the host) a text shown in red on every machine's console: its own, and every client's (_distributed_message_notice) */ -static void distributed_send_notice( +void distributed_send_notice( char const *text) { struct @@ -3445,9 +3485,34 @@ static void distributed_send_notice( _distributed_to_clients_reliably); } +/* (the host) ... on one client's console only, or (NONE) the host's own */ +void distributed_send_notice_to_machine( + long machine_index, + char const *text) +{ + struct + { + struct distributed_message_header header; + char text[MAXIMUM_NOTICE_LENGTH]; + } message; + long length = csstrlen(text); + + if (length > MAXIMUM_NOTICE_LENGTH - 1) + length = MAXIMUM_NOTICE_LENGTH - 1; + csmemset(&message, 0, sizeof(message)); + csmemcpy(message.text, text, length); + if (machine_index == NONE) + { + console_warning("%s", message.text); + return; + } + distributed_send_to_machine_reliably(machine_index, &message, _distributed_message_notice, 0, + (word)(sizeof(message.header) + length + 1)); +} + /* (the host) the names of a client machine's players, in ASCII, for a notice ("?" for what is not ASCII) */ -static void distributed_machine_player_names( +void distributed_machine_player_names( long machine_index, char *names, long size) @@ -3506,6 +3571,31 @@ void distributed_client_send_identity( _distributed_to_host_reliably); } +/* (the host) a client machine's address (host byte order) as it really is: +an internet play peer's endpoint for its stand-in in 100.64.0.0/10 (p2p.c, +which numbers the stand-in from the peer's identifier: a peer makes a new +one at will), 0 if that is not known */ +unsigned long distributed_real_address( + unsigned long address) +{ + if ((address & 0xFFC00000) == 0x64400000) + { + /* (p2p.c's in network byte order: the first number the lowest byte) */ + unsigned long network = (address >> 24) | ((address >> 8) & 0xFF00) | ((address << 8) & 0xFF0000) | + (address << 24); + unsigned long real = p2p_peer_endpoint_address(network); + + return (real >> 24) | ((real >> 8) & 0xFF00) | ((real << 8) & 0xFF0000) | (real << 24); + } + return address; +} + +unsigned long distributed_machine_real_address( + long machine_index) +{ + return distributed_real_address(network_game_server_machine_address(machine_index)); +} + /* (the host) a client machine's address as text: its real one, for an internet play peer's stand-in (p2p.c) */ static void distributed_address_text( @@ -3513,21 +3603,9 @@ static void distributed_address_text( char *text, long size) { - /* 100.64.0.0/10: an internet play peer's, by its real address (network - byte order: its first number the lowest byte) */ - if ((address & 0xFFC00000) == 0x64400000) - { - unsigned long network = (address >> 24) | ((address >> 8) & 0xFF00) | ((address << 8) & 0xFF0000) | - (address << 24); - unsigned long real = p2p_peer_endpoint_address(network); - - if (real) - { - snprintf(text, size, "%lu.%lu.%lu.%lu", real & 255, (real >> 8) & 255, (real >> 16) & 255, - (real >> 24) & 255); - return; - } - } + /* 100.64.0.0/10: an internet play peer's, by its real address */ + if ((address & 0xFFC00000) == 0x64400000 && distributed_real_address(address)) + address = distributed_real_address(address); if (address) { snprintf(text, size, "%lu.%lu.%lu.%lu", (address >> 24) & 255, (address >> 16) & 255, (address >> 8) & 255, @@ -3869,6 +3947,12 @@ void network_distributed_handle_message( short index; word entry_size; + /* (voice chat's, in the lobby too: network_voice.c) */ + if (network_voice_handles_message(message, size)) + { + network_voice_handle_message(machine_index, message, size); + return; + } /* (none between games: loading, or in the menus) */ if (size < sizeof(header) || !game_in_progress()) return; @@ -3925,6 +4009,8 @@ void network_distributed_handle_message( case _distributed_message_coop_screen_effect: entry_size = network_coop_screen_effect_entry_size(); break; case _distributed_message_coop_device_states: entry_size = network_coop_device_state_entry_size(); break; case _distributed_message_pickups: entry_size = sizeof(struct distributed_pickup); break; + case _distributed_message_votekick: entry_size = network_votekick_request_entry_size(); break; + case _distributed_message_votekick_status: entry_size = network_votekick_status_entry_size(); break; case _distributed_message_player_inputs: entry_size = sizeof(struct distributed_player_input); break; case _distributed_message_relayed_actions: entry_size = DISTRIBUTED_RELAYED_ACTION_MINIMUM_SIZE; break; case _distributed_message_game_state: @@ -3949,6 +4035,8 @@ void network_distributed_handle_message( case _distributed_message_coop_presentation: case _distributed_message_coop_object_names: case _distributed_message_coop_skip_vote: + case _distributed_message_votekick: + case _distributed_message_votekick_status: if (header.count < 1) return; break; @@ -3979,6 +4067,7 @@ void network_distributed_handle_message( case _distributed_message_vehicle_prediction: case _distributed_message_player_inputs: case _distributed_message_coop_skip_vote: + case _distributed_message_votekick: if (machine_index == NONE || game_connection() != _game_connection_network_server) return; break; @@ -4033,6 +4122,12 @@ void network_distributed_handle_message( case _distributed_message_coop_skip_vote: network_coop_handle_skip_vote(machine_index, entries); break; + case _distributed_message_votekick: + network_votekick_handle_request(machine_index, entries, distributed_handling_stream_message); + break; + case _distributed_message_votekick_status: + network_votekick_handle_status(entries); + break; case _distributed_message_coop_device_groups: network_coop_handle_device_groups(entries, header.count); break; diff --git a/port/linux/game/network_distributed.h b/port/linux/game/network_distributed.h index f27bf21b..f435d605 100644 --- a/port/linux/game/network_distributed.h +++ b/port/linux/game/network_distributed.h @@ -106,6 +106,18 @@ enum /* co-op: where the host's devices are (doors, elevators), when they move (network_coop.c) */ _distributed_message_coop_device_states = 77, + /* a player's vote to kick a player (network_votekick.c): to the host, + reliably, which takes it only from the voter's own stream */ + _distributed_message_votekick = 78, + /* the vote running as the host counts it, to each client, every second + and when it changes (unreliable: network_votekick.c) */ + _distributed_message_votekick_status = 79, + /* voice chat (network_voice.c), in the lobby too: a client's frame to + the host, unreliably, with its key; a frame the host relays to a + client; the host's settings and the client's key, reliably */ + _distributed_message_voice_up = 80, + _distributed_message_voice_down = 81, + _distributed_message_voice_config = 82, /* The VR port's own messages, numbered far past the game's (240 on), so that messages upstream adds never take their numbers: a build @@ -257,6 +269,31 @@ real distributed_angle_unpack(short value, boolean signed_angle); word distributed_vitality_pack(real value); real distributed_vitality_unpack(word value); +/* (the host) a text in red on every machine's console, or on one client's +(machine_index; NONE: the host's own) */ +void distributed_send_notice(char const *text); +void distributed_send_notice_to_machine(long machine_index, char const *text); +/* (the host) the names of a client machine's players, in ASCII */ +void distributed_machine_player_names(long machine_index, char *names, long size); +/* (the host) a client machine's real IPv4 address (host byte order): an +internet play peer's endpoint, not its stand-in; 0 if not known */ +unsigned long distributed_machine_real_address(long machine_index); +/* ... and the real address of a machine's address, as it joined from */ +unsigned long distributed_real_address(unsigned long address); + +/* ---------- prototypes/NETWORK_VOTEKICK.C */ + +void network_votekick_new_game(void); +/* after each tick, on the host */ +void network_votekick_host_tick(void); +/* (the host) a client's vote; from_stream: it came over the machine's own +stream (anything else is refused) */ +void network_votekick_handle_request(long machine_index, void const *entries, boolean from_stream); +word network_votekick_request_entry_size(void); +/* (a client) the host's count */ +void network_votekick_handle_status(void const *entries); +word network_votekick_status_entry_size(void); + /* ---------- prototypes/NETWORK_ACTORS.C */ void network_actors_new_game(void); diff --git a/port/linux/game/network_objects.c b/port/linux/game/network_objects.c index 8ee2020e..64273967 100644 --- a/port/linux/game/network_objects.c +++ b/port/linux/game/network_objects.c @@ -64,6 +64,7 @@ same datum index (identifier and all), so that any message can name one: #include "cutscene/cinematics.h" #include "network_coop.h" #include "network_distributed.h" +#include "profile_sections.h" #include @@ -243,6 +244,10 @@ enum /* (struct distributed_object_state, the host's) the host has the vehicle where the client it goes to had it at the tick in time */ _distributed_object_predicted_bit, + /* (struct distributed_object_state) a unit's integrated light is on, and + the host sent it at all: older builds leave both 0, which is not "off" */ + _distributed_object_light_sent_bit, + _distributed_object_light_on_bit, }; struct distributed_object_change @@ -672,6 +677,18 @@ static boolean distributed_vehicle_unsteered( unit_get(vehicle->unit.driver_object_index)->unit.player_index == NONE; } +/* whether this machine's player drives the unit: it is their biped, or the +vehicle they drive */ +static boolean distributed_unit_driven_here( + long unit_index) +{ + struct unit_datum *unit = unit_get(unit_index); + + if (unit->unit.driver_object_index != NONE) + unit = unit_get(unit->unit.driver_object_index); + return unit->unit.player_index != NONE && distributed_player_is_local(unit->unit.player_index); +} + /* Whether a client puts its copy exactly where the host has it, rather than closing on it: an unsteered vehicle, and anything in a co-op cutscene (where it keeps up with the host's camera). */ @@ -807,6 +824,12 @@ static void distributed_state_from_object( SET_FLAG(state->flags, _distributed_object_at_rest_bit, TEST_FLAG(object->object.flags, _object_at_rest_bit)); SET_FLAG(state->flags, _distributed_object_dead_bit, TEST_FLAG(_object_mask_unit, object->object.type) && TEST_FLAG(object->object.damage_flags, _object_dead_bit)); + if (TEST_FLAG(_object_mask_unit, object->object.type)) + { + SET_FLAG(state->flags, _distributed_object_light_sent_bit, TRUE); + SET_FLAG(state->flags, _distributed_object_light_on_bit, + TEST_FLAG(unit_get(object_index)->unit.flags, _unit_integrated_light_on_bit)); + } state->position = object->object.position; distributed_vector_pack(&object->object.forward, DISTRIBUTED_UNIT_SCALE, &state->forward); distributed_vector_pack(&object->object.up, DISTRIBUTED_UNIT_SCALE, &state->up); @@ -1976,15 +1999,22 @@ void network_objects_apply_vehicle_predictions( } } +/* the host tick's steps, timed in the profiling build */ +PROFILE_SECTION(objects_host_tick_update_objects_section, "network_objects_host_tick.update_objects") +PROFILE_SECTION(objects_host_tick_find_viewers_section, "network_objects_host_tick.find_viewers") +PROFILE_SECTION(objects_host_tick_send_states_section, "network_objects_host_tick.send_states") +PROFILE_SECTION(objects_host_tick_send_inventories_section, "network_objects_host_tick.send_inventories") +PROFILE_SECTION(objects_host_tick_send_damage_animations_section, "network_objects_host_tick.send_damage_animations") + void network_objects_host_tick( void) { - distributed_host_update_objects(); - distributed_host_find_viewers(); - distributed_host_send_states(); + profile_scope(objects_host_tick_update_objects_section, distributed_host_update_objects();) + profile_scope(objects_host_tick_find_viewers_section, distributed_host_find_viewers();) + profile_scope(objects_host_tick_send_states_section, distributed_host_send_states();) if (game_time_get() % INVENTORY_INTERVAL_TICKS == 0) - distributed_host_send_inventories(); - distributed_host_send_damage_animations(); + profile_scope(objects_host_tick_send_inventories_section, distributed_host_send_inventories();) + profile_scope(objects_host_tick_send_damage_animations_section, distributed_host_send_damage_animations();) } /* ---------- a client */ @@ -2316,6 +2346,12 @@ static void distributed_client_create( return; } objects_client_has[absolute_index] = object_index; + /* (a unit's grenades are the host's, which its inventories bring: none + until they come, not the tag's that unit_new gave it, which the host's + game engine replaces with the game's at a player's spawn: a player + spawned with the button held threw one here the host's copy had not) */ + if (TEST_FLAG(_object_mask_unit, object_get(object_index)->object.type)) + csmemset(unit_get(object_index)->unit.grenade_counts, 0, sizeof(unit_get(object_index)->unit.grenade_counts)); distributed_client_apply_change(object_index, change); } @@ -2490,6 +2526,17 @@ void network_objects_handle_states( if (!distributed_object_index_valid(state->object_index) || !network_objects_client_has(state->object_index)) continue; object = object_get(state->object_index); + /* (the unit's light as the host has it: only presses toggle it here, + so one missed, or made before this machine joined, left it wrong for + good. Not for what this machine's player drives: their press lands + here first) */ + if (TEST_FLAG(state->flags, _distributed_object_light_sent_bit) && + TEST_FLAG(_object_mask_unit, object->object.type) && + !distributed_unit_driven_here(state->object_index)) + { + SET_FLAG(unit_get(state->object_index)->unit.flags, _unit_integrated_light_on_bit, + TEST_FLAG(state->flags, _distributed_object_light_on_bit)); + } if (TEST_FLAG(state->flags, _distributed_object_dead_bit)) { distributed_client_dead_biped(state->object_index); diff --git a/port/linux/game/network_voice.c b/port/linux/game/network_voice.c new file mode 100644 index 00000000..c606e6cb --- /dev/null +++ b/port/linux/game/network_voice.c @@ -0,0 +1,1016 @@ +/* +NETWORK_VOICE.C + +Voice chat (port/linux/NETCODE.md): each machine's microphone, encoded with +Opus (port/linux/src/voice_audio.c), goes to the host, which sends it on to +the machines that may hear it. The host decides everything, from its own +config.toml: + +- In the lobby, before and after a game (and in a game's postgame), every + player hears every other (network.voice_lobby). +- In a game, network.voice_mode: off; teammates near; anyone near; all + teammates; or all teammates and enemies near ("near": within + network.voice_proximity world units of each other, both alive). A game + without teams has only enemies, and co-op only teammates. A voice heard + for being near is quieter further away, and comes from the speaker's side + (each listener's own reckoning of where everyone is). +- The quality: network.voice_kbps (8 to 64), in the lobby and in a game, + which the host tells each machine to encode at, and holds it to. + +What the host takes is held to that too: a machine's frames only with the +key the host gave it over its stream (a datagram's source address is all +that names its machine, and anyone can send one as from another's), no +larger than the quality allows, no more than VOICE_FRAMES_PER_SECOND (with +a little burst), only while voice is on, and no more than +VOICE_MAXIMUM_TALKERS machines at once. A machine is sent voices once it +has said it hears them (a frame, or an empty one every two seconds), so a +build without voice chat is never sent any. The host relays only: it never +decodes, and a machine's own voice never comes back to it. + +Each machine decides only for itself: whether it talks (audio.voice_chat: +push to talk, open mic, or not), how loud the others are +(audio.voice_volume), and whom it mutes (the scoreboard's menu, +game_engine.c), which it does not tell anyone. +*/ + +#include "cseries.h" +#include "cseries/errors.h" +#include "memory/data.h" +#include "math/integer_math.h" +#include "bitmaps/bitmap_group.h" +#include "bitmaps/bitmap_group_lookup.h" +#include "interface/ui_widget.h" +#include "tag_files/tag_groups.h" +#include "camera/observer.h" +#include "game/game.h" +#include "game/game_engine.h" +#include "game/players.h" +#include "main/console.h" +#include "networking/network_game_globals.h" +#include "networking/network_game_manager.h" +#include "networking/network_client_manager.h" +#include "objects/objects.h" +#include "network_distributed.h" +#include "network_voice.h" +#include "../src/voice_audio.h" + +#include + +/* port_config.c's, network_game_globals.c's, network_server_message_handler.c's, +xinput_sdl.c's, posix_net.c's, cseries_windows.c's */ +int config_boolean(const char *name); +long config_integer(const char *name); +double config_real(const char *name); +const char *config_string(const char *name); +unsigned long config_changes(void); +boolean network_distributed_client_send(void *message, word size); +boolean network_distributed_server_send_to_machine(long machine_index, void *message, word size); +boolean network_distributed_server_send_to_machine_reliably(long machine_index, void *message, word size); +short network_distributed_server_machines(long *machine_indices, short maximum); +int halo_push_to_talk_held(void); +void posix_random_bytes(void *buffer, unsigned int size); +unsigned long system_milliseconds(void); + +/* ---------- constants */ + +enum +{ + VOICE_MACHINES = HALO_PORT_MAXIMUM_NETWORK_MACHINES, + /* a frame every 20 ms, and a burst of a few (a client's frames sent + in a clump after a stall) */ + VOICE_FRAMES_PER_SECOND = 50, + VOICE_FRAME_BURST = 10, + /* machines heard at once, at most */ + VOICE_MAXIMUM_TALKERS = 8, + /* a machine talking: one whose frame came this lately (ms) */ + VOICE_TALKING_MS = 500, + /* a machine that hears voices: one heard from this lately (ms) */ + VOICE_LISTENING_MS = 6000, + /* how often a client says it hears voices, and the host repeats its + settings (ms) */ + VOICE_HELLO_MS = 2000, + VOICE_CONFIG_MS = 10000, + /* a client forgets the host's settings unheard this long (ms) */ + VOICE_CONFIG_TIMEOUT_MS = 25000, + /* a speaker's icon shows this long after their last frame (ms) */ + VOICE_SPEAKING_MS = 300, + /* open mic: talking while the microphone's level is over this (about + -40 dB), and for this long after (ms) */ + VOICE_OPEN_MIC_HOLD_MS = 400, + VOICE_MINIMUM_KBPS = 8, + VOICE_MAXIMUM_KBPS = 64, +}; + +/* the speaker's bitmap (network_voice_draw_icon): the menus' voice/speaker, +as their bitmaps are named (menu_tags.c) */ +#define VOICE_ICON_BITMAP "pc\\voice\\speaker" +#define VOICE_OPEN_MIC_LEVEL 0.01f +/* proximity: full volume this near, quieter further (world units) */ +#define VOICE_NEAR_DISTANCE 3.0f +#define VOICE_FARTHEST_GAIN 0.2f + +/* how the host's settings travel */ +enum +{ + _voice_config_lobby_bit, +}; + +/* network_client_manager.c's client state in game (its enum is its own) */ +enum +{ + _voice_client_state_ingame = 3, +}; + +/* ---------- structures */ + +/* a client's frame to the host (its Opus packet follows; none: it hears +voices, and says so) */ +struct distributed_voice_up +{ + unsigned long key; + word sequence; + byte length; + byte pad; +}; + +/* a frame relayed to a client (the packet follows) */ +struct distributed_voice_down +{ + unsigned long key; + word sequence; + /* the machine that spoke, and its first player (absolute; NO_PLAYER + in the lobby) */ + byte machine_index; + byte player_index; + /* how it is heard (_voice_route_*) */ + byte route; + byte length; + byte pad[2]; +}; + +/* the host's settings, to each client over its stream, with its key */ +struct distributed_voice_config +{ + unsigned long key; + byte flags; + byte mode; + byte kbps; + byte pad0; + word proximity_tenths; + byte pad[2]; +}; + +typedef char distributed_voice_up_size_assert[sizeof(struct distributed_voice_up) == 8 ? 1 : -1]; +typedef char distributed_voice_down_size_assert[sizeof(struct distributed_voice_down) == 12 ? 1 : -1]; +typedef char distributed_voice_config_size_assert[sizeof(struct distributed_voice_config) == 12 ? 1 : -1]; + +/* the host's settings as they are used */ +struct voice_settings +{ + boolean lobby; + short mode; + short kbps; + real proximity; +}; + +/* ---------- globals */ + +/* the host: each machine slot's key, whether it was told it, and when it +was told the settings last; when it was heard from; its frames' allowance +and when it was topped up; when its last frame was relayed */ +static struct +{ + unsigned long key; + boolean told; + unsigned long told_at; + unsigned long heard_at; + boolean heard; + real allowance; + unsigned long allowance_at; + unsigned long talked_at; + boolean talked; +} voice_machines[VOICE_MACHINES]; +static struct voice_settings voice_host_settings; +static unsigned long voice_host_settings_read_at = (unsigned long)-1; +/* (the settings told, to tell again when they change) */ +static struct voice_settings voice_host_settings_told; + +/* this machine: the host's settings and its key (heard: a client), when +they came; the sequence of its frames; whether it talked lately, and the +microphone was asked for */ +static struct voice_settings voice_settings; +static unsigned long voice_key; +static boolean voice_settings_heard; +static unsigned long voice_settings_heard_at; +static word voice_sequence; +static unsigned long voice_hello_at; +static unsigned long voice_talked_at; +static boolean voice_talked; +static unsigned long voice_open_mic_until; +static boolean voice_microphone_wanted; +static boolean voice_session; + +/* every machine slot's last frame heard (for its icon), and whether this +machine mutes it, with the names it had then (a new machine at the slot is +not muted) */ +static unsigned long voice_heard_at[VOICE_MACHINES]; +static boolean voice_heard[VOICE_MACHINES]; +static boolean voice_muted[VOICE_MACHINES]; +static wchar_t voice_muted_names[VOICE_MACHINES][12]; + +/* ---------- the route (pure: tools/harness/tests/voice_route.c) */ + +/* how a listener hears a speaker: in the lobby (and the postgame), anyone +if the lobby has voice; in a game, by the mode, teammates anywhere or near, +enemies near ("near": within the range, both alive) */ +static short voice_route( + short mode, + boolean lobby, + boolean lobby_voice, + boolean teammates, + boolean both_alive, + real distance, + real range) +{ + boolean near = both_alive && distance <= range; + + if (lobby) + return lobby_voice ? _voice_route_global : _voice_route_none; + switch (mode) + { + case _voice_mode_team_proximity: + return teammates && near ? _voice_route_proximity : _voice_route_none; + case _voice_mode_team_enemy_proximity: + return near ? _voice_route_proximity : _voice_route_none; + case _voice_mode_team_global: + return teammates ? _voice_route_global : _voice_route_none; + case _voice_mode_team_global_enemy_proximity: + if (teammates) + return _voice_route_global; + return near ? _voice_route_proximity : _voice_route_none; + default: + return _voice_route_none; + } +} + +/* the largest packet a frame may be at the quality (kilobits a second): +twice its share, for Opus's variable rate, and a little */ +static long voice_packet_limit( + short kbps) +{ + long limit = (long)kbps * 1000 / 8 / VOICE_FRAMES_PER_SECOND * 2 + 16; + + return MIN(limit, VOICE_MAXIMUM_PACKET); +} + +/* ---------- settings */ + +static short voice_mode_from_text( + char const *text) +{ + static char const *const names[NUMBER_OF_VOICE_MODES] = + { + "off", "team_proximity", "team_enemy_proximity", "team_global", "team_global_enemy_proximity", + }; + short mode; + + for (mode = 0; text && mode < NUMBER_OF_VOICE_MODES; mode++) + { + if (!csstrcmp(text, names[mode])) + return mode; + } + return _voice_mode_team_global_enemy_proximity; +} + +/* the host's, from its config.toml (read again as it changes) */ +static void voice_host_settings_read( + void) +{ + if (voice_host_settings_read_at == config_changes()) + return; + voice_host_settings_read_at = config_changes(); + voice_host_settings.lobby = config_boolean("network.voice_lobby") != 0; + voice_host_settings.mode = voice_mode_from_text(config_string("network.voice_mode")); + voice_host_settings.kbps = (short)PIN(config_integer("network.voice_kbps"), VOICE_MINIMUM_KBPS, + VOICE_MAXIMUM_KBPS); + voice_host_settings.proximity = (real)PIN(config_real("network.voice_proximity"), 5.0, 100.0); +} + +static boolean voice_host( + void) +{ + return global_network_game_server_get() != NULL; +} + +/* in the lobby, as voice has it: this machine's network game not in game +(the menus' own game, the main menu's, runs meanwhile), or in a multiplayer +game's postgame (co-op, with no game engine, has none) */ +static boolean voice_in_lobby( + void) +{ + struct network_game_client *client = global_network_game_client_get(); + short state_data; + + if (!client || network_game_client_get_state(client, &state_data) != _voice_client_state_ingame || + !game_in_progress()) + { + return TRUE; + } + return game_engine && game_engine_showing_postgame(); +} + +/* ---------- players */ + +/* the network game this machine is in (the host's, its client's), or NULL */ +static struct network_game *voice_network_game( + void) +{ + struct network_game_client *client = global_network_game_client_get(); + + return client ? network_game_client_get_game(client) : NULL; +} + +/* a machine's first player in the game (its datum), or NULL */ +static struct player_datum *voice_machine_player( + long machine_index, + long *player_index) +{ + struct data_iterator iterator; + struct player_datum *player; + + if (!game_in_progress() || !player_data) + return NULL; + data_iterator_new(&iterator, player_data); + while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL) + { + if (!player->quit_out_of_game && player->network_player_data.machine_index == machine_index) + { + if (player_index) + *player_index = iterator.datum_index; + return player; + } + } + return NULL; +} + +/* a machine's first player's name as the network game has it (empty if +none) */ +static void voice_machine_name( + long machine_index, + wchar_t *name) +{ + struct network_game *game = voice_network_game(); + short index; + + csmemset(name, 0, 12 * sizeof(*name)); + for (index = 0; game && index < HALO_PORT_MAXIMUM_NETWORK_PLAYERS; index++) + { + struct network_player *player = &game->players[index]; + + if (network_player_is_valid(player) && player->machine_index == machine_index) + { + csmemcpy(name, player->name, sizeof(player->name)); + return; + } + } +} + +/* a player's living unit's position */ +static boolean voice_player_position( + struct player_datum const *player, + real_point3d *position) +{ + long unit_index = player ? distributed_living_unit(player) : NONE; + + if (unit_index == NONE) + return FALSE; + object_get_origin(unit_index, position); + return TRUE; +} + +/* (the host) each machine's first player in the game (NULL: none), found +once for a frame's relaying */ +static void voice_machine_players( + struct player_datum **players) +{ + struct data_iterator iterator; + struct player_datum *player; + + csmemset(players, 0, VOICE_MACHINES * sizeof(*players)); + if (voice_in_lobby() || !player_data) + return; + data_iterator_new(&iterator, player_data); + while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL) + { + long machine_index = player->network_player_data.machine_index; + + if (!player->quit_out_of_game && machine_index >= 0 && machine_index < VOICE_MACHINES && + !players[machine_index]) + { + players[machine_index] = player; + } + } +} + +/* (the host) how a listener's machine hears a speaker's, now, by their +first players */ +static short voice_route_between( + struct player_datum *speaker, + struct player_datum *listener) +{ + real_point3d speaker_position; + real_point3d listener_position; + boolean both_alive; + boolean teammates; + real distance = 0.0f; + + if (voice_in_lobby()) + return voice_route(voice_host_settings.mode, TRUE, voice_host_settings.lobby, FALSE, FALSE, 0.0f, 0.0f); + if (!speaker || !listener) + return _voice_route_none; + /* (co-op: one team; a game without teams: none) */ + teammates = !game_engine || (game_engine_has_teams() && speaker->team_index == listener->team_index); + both_alive = voice_player_position(speaker, &speaker_position) && + voice_player_position(listener, &listener_position); + if (both_alive) + distance = distance3d(&speaker_position, &listener_position); + return voice_route(voice_host_settings.mode, FALSE, voice_host_settings.lobby, teammates, both_alive, distance, + voice_host_settings.proximity); +} + +/* a message's headers: the transport's (its size: the connection checks it) +and the distributed netcode's (its time the game's, none in the lobby) */ +static void voice_fill_header( + struct distributed_message_header *header, + byte type, + word size) +{ + header->header = 0; + build_message_header(&header->header, size, 2, 0); + header->type = type; + header->count = 1; + header->game_time = game_in_progress() ? game_time_get() : 0; +} + +/* ---------- the host */ + +static boolean voice_machine_valid( + long machine_index) +{ + return machine_index >= 0 && machine_index < VOICE_MACHINES; +} + +/* (network_server_manager.c) a machine joined at the slot: a new key, told +to it before anything is taken from it */ +void network_voice_machine_joined( + long machine_index) +{ + if (!voice_machine_valid(machine_index)) + return; + csmemset(&voice_machines[machine_index], 0, sizeof(voice_machines[machine_index])); + do + { + posix_random_bytes(&voice_machines[machine_index].key, sizeof(voice_machines[machine_index].key)); + } while (!voice_machines[machine_index].key); +} + +/* the settings and its key, to one client, over its stream */ +static void voice_host_tell( + long machine_index) +{ + struct + { + struct distributed_message_header header; + struct distributed_voice_config config; + } message; + + csmemset(&message, 0, sizeof(message)); + voice_fill_header(&message.header, _distributed_message_voice_config, (word)sizeof(message)); + message.config.key = voice_machines[machine_index].key; + message.config.flags = voice_host_settings.lobby ? FLAG(_voice_config_lobby_bit) : 0; + message.config.mode = (byte)voice_host_settings.mode; + message.config.kbps = (byte)voice_host_settings.kbps; + message.config.proximity_tenths = (word)(voice_host_settings.proximity * 10.0f); + if (network_distributed_server_send_to_machine_reliably(machine_index, &message, (word)sizeof(message))) + { + voice_machines[machine_index].told = TRUE; + voice_machines[machine_index].told_at = system_milliseconds(); + } +} + +/* whether a machine is one this machine plays a voice of: not muted */ +static boolean voice_audible( + long machine_index) +{ + return voice_machine_valid(machine_index) && !voice_muted[machine_index]; +} + +/* the gain and pan of a voice heard for being near (this machine's own +reckoning: its first player's view, and the speaker's unit) */ +static void voice_proximity_gain( + short player_index, + real *gain, + real *pan) +{ + struct player_datum *player = player_index != NONE ? distributed_player(player_index) : NULL; + struct observer_result const *camera; + real_point3d position; + real_vector3d direction; + real_vector3d right; + real distance; + real range = voice_settings.proximity > VOICE_NEAR_DISTANCE ? voice_settings.proximity : VOICE_NEAR_DISTANCE + 1.0f; + + *gain = 1.0f; + *pan = 0.0f; + if (!voice_player_position(player, &position) || local_player_get_next(NONE) == NONE) + return; + camera = observer_get_camera((short)local_player_get_next(NONE)); + if (!camera) + return; + vector_from_points3d(&camera->position, &position, &direction); + distance = magnitude3d(&direction); + if (distance > VOICE_NEAR_DISTANCE) + { + real t = PIN((distance - VOICE_NEAR_DISTANCE) / (range - VOICE_NEAR_DISTANCE), 0.0f, 1.0f); + + *gain = 1.0f - (1.0f - VOICE_FARTHEST_GAIN) * t; + } + if (distance > 0.01f) + { + cross_product3d(&camera->forward, &camera->up, &right); + normalize3d(&right); + scale_vector3d(&direction, 1.0f / distance, &direction); + *pan = 0.8f * dot_product3d(&direction, &right); + } +} + +/* a frame heard here (the host's own listening, or one relayed to a +client); the speaker's first player (absolute index, NONE for none) */ +static void voice_play( + long machine_index, + short player_index, + short route, + word sequence, + byte const *packet, + long length) +{ + real gain = 1.0f; + real pan = 0.0f; + + if (!voice_machine_valid(machine_index) || length <= 0) + return; + voice_heard_at[machine_index] = system_milliseconds(); + voice_heard[machine_index] = TRUE; + if (!voice_audible(machine_index)) + return; + if (route == _voice_route_proximity) + voice_proximity_gain(player_index, &gain, &pan); + voice_audio_play((int)machine_index, sequence, packet, (int)length, gain, pan); +} + +/* how many machines talk now, but the one */ +static short voice_host_talkers( + long except_machine, + unsigned long now) +{ + short count = 0; + long index; + + for (index = 0; index < VOICE_MACHINES; index++) + { + if (index != except_machine && voice_machines[index].talked && + now - voice_machines[index].talked_at < VOICE_TALKING_MS) + { + count++; + } + } + return count; +} + +/* (the host) a frame of a machine (its own, or a client's), checked, and +sent to those that hear it */ +static void voice_host_relay( + long speaker_machine, + word sequence, + byte const *packet, + long length) +{ + static struct player_datum *players[VOICE_MACHINES]; + long machine_indices[VOICE_MACHINES]; + short count = network_distributed_server_machines(machine_indices, VOICE_MACHINES); + short local_machine = network_game_client_get_local_machine_index(); + long speaker_player = NONE; + unsigned long now = system_milliseconds(); + short index; + + if (!voice_machine_valid(speaker_machine) || length <= 0 || + length > voice_packet_limit(voice_host_settings.kbps)) + { + return; + } + /* (no more than so many at once: those talking keep talking) */ + if (!(voice_machines[speaker_machine].talked && now - voice_machines[speaker_machine].talked_at < VOICE_TALKING_MS) && + voice_host_talkers(speaker_machine, now) >= VOICE_MAXIMUM_TALKERS) + { + return; + } + voice_machines[speaker_machine].talked = TRUE; + voice_machines[speaker_machine].talked_at = now; + voice_machine_players(players); + /* (the lobby has no players' positions: the menus' game has players of + its own) */ + if (!voice_in_lobby()) + voice_machine_player(speaker_machine, &speaker_player); + for (index = 0; index < count; index++) + { + long listener = machine_indices[index]; + short route; + + if (!voice_machine_valid(listener) || listener == speaker_machine) + continue; + route = voice_route_between(players[speaker_machine], players[listener]); + if (route == _voice_route_none) + continue; + if (listener == local_machine) + { + voice_play(speaker_machine, speaker_player != NONE ? (short)DATUM_INDEX_TO_ABSOLUTE_INDEX(speaker_player) : + NONE, route, sequence, packet, length); + continue; + } + /* (only to a machine that has said it hears voices) */ + if (!voice_machines[listener].heard || now - voice_machines[listener].heard_at >= VOICE_LISTENING_MS || + !voice_machines[listener].told) + { + continue; + } + { + struct + { + struct distributed_message_header header; + struct distributed_voice_down down; + byte packet[VOICE_MAXIMUM_PACKET]; + } message; + + csmemset(&message, 0, sizeof(message.header) + sizeof(message.down)); + voice_fill_header(&message.header, _distributed_message_voice_down, + (word)(sizeof(message.header) + sizeof(message.down) + length)); + message.down.key = voice_machines[listener].key; + message.down.sequence = sequence; + message.down.machine_index = (byte)speaker_machine; + message.down.player_index = speaker_player != NONE ? + (byte)DATUM_INDEX_TO_ABSOLUTE_INDEX(speaker_player) : NO_PLAYER; + message.down.route = (byte)route; + message.down.length = (byte)length; + csmemcpy(message.packet, packet, length); + network_distributed_server_send_to_machine(listener, &message, + (word)(sizeof(message.header) + sizeof(message.down) + length)); + } + } +} + +/* (the host) a client's frame: its key, its allowance, the settings' */ +static void voice_host_handle_up( + long machine_index, + struct distributed_voice_up const *up, + byte const *packet, + long length) +{ + unsigned long now = system_milliseconds(); + + if (!voice_machine_valid(machine_index) || !voice_machines[machine_index].key || + up->key != voice_machines[machine_index].key || up->length != length) + { + return; + } + voice_machines[machine_index].heard = TRUE; + voice_machines[machine_index].heard_at = now; + /* (an empty frame: it hears voices) */ + if (!length) + return; + /* (its allowance: so many frames a second, a few at once) */ + if (!voice_machines[machine_index].allowance_at) + voice_machines[machine_index].allowance = VOICE_FRAME_BURST; + else + { + voice_machines[machine_index].allowance += (real)(now - voice_machines[machine_index].allowance_at) * + VOICE_FRAMES_PER_SECOND / 1000.0f; + } + voice_machines[machine_index].allowance = MIN(voice_machines[machine_index].allowance, (real)VOICE_FRAME_BURST); + voice_machines[machine_index].allowance_at = now; + if (voice_machines[machine_index].allowance < 1.0f) + return; + voice_machines[machine_index].allowance -= 1.0f; + voice_host_relay(machine_index, up->sequence, packet, length); +} + +/* ---------- messages (network_distributed.c) */ + +boolean network_voice_handles_message( + word const *message, + word size) +{ + struct distributed_message_header header; + + if (size < sizeof(header)) + return FALSE; + csmemcpy(&header, message, sizeof(header)); + return header.type == _distributed_message_voice_up || header.type == _distributed_message_voice_down || + header.type == _distributed_message_voice_config; +} + +void network_voice_handle_message( + long machine_index, + word const *message, + word size) +{ + struct distributed_message_header header; + byte const *entries = (byte const *)message + sizeof(header); + long length = (long)size - (long)sizeof(header); + + if (!network_voice_handles_message(message, size)) + return; + csmemcpy(&header, message, sizeof(header)); + switch (header.type) + { + case _distributed_message_voice_up: + { + struct distributed_voice_up up; + + if (machine_index == NONE || !voice_host() || length < (long)sizeof(up)) + return; + csmemcpy(&up, entries, sizeof(up)); + voice_host_handle_up(machine_index, &up, entries + sizeof(up), length - (long)sizeof(up)); + break; + } + case _distributed_message_voice_down: + { + struct distributed_voice_down down; + + if (machine_index != NONE || voice_host() || !voice_settings_heard || length < (long)sizeof(down)) + return; + csmemcpy(&down, entries, sizeof(down)); + if (down.key != voice_key || down.length != length - (long)sizeof(down) || + down.machine_index == network_game_client_get_local_machine_index()) + { + return; + } + voice_play(down.machine_index, down.player_index != NO_PLAYER && !voice_in_lobby() ? down.player_index : NONE, + down.route, down.sequence, entries + sizeof(down), down.length); + break; + } + case _distributed_message_voice_config: + { + struct distributed_voice_config config; + + if (machine_index != NONE || voice_host() || length < (long)sizeof(config)) + return; + csmemcpy(&config, entries, sizeof(config)); + voice_key = config.key; + voice_settings.lobby = TEST_FLAG(config.flags, _voice_config_lobby_bit); + voice_settings.mode = config.mode < NUMBER_OF_VOICE_MODES ? config.mode : _voice_mode_off; + voice_settings.kbps = (short)PIN(config.kbps, VOICE_MINIMUM_KBPS, VOICE_MAXIMUM_KBPS); + voice_settings.proximity = config.proximity_tenths / 10.0f; + voice_settings_heard = TRUE; + voice_settings_heard_at = system_milliseconds(); + break; + } + } +} + +/* ---------- every frame (main.c) */ + +/* whether this machine may talk now, as the host's settings have it */ +static boolean voice_may_talk( + void) +{ + if (voice_in_lobby()) + return voice_settings.lobby; + return voice_settings.mode != _voice_mode_off; +} + +/* the host: every client told its key and the settings (again as they +change, and now and then) */ +static void voice_host_update( + void) +{ + long machine_indices[VOICE_MACHINES]; + short count = network_distributed_server_machines(machine_indices, VOICE_MACHINES); + short local_machine = network_game_client_get_local_machine_index(); + boolean changed; + unsigned long now = system_milliseconds(); + short index; + + voice_host_settings_read(); + changed = csmemcmp(&voice_host_settings, &voice_host_settings_told, sizeof(voice_host_settings)) != 0; + voice_host_settings_told = voice_host_settings; + /* (the host's own, as a client hears them) */ + voice_settings = voice_host_settings; + voice_settings_heard = TRUE; + voice_settings_heard_at = now; + for (index = 0; index < count; index++) + { + long machine_index = machine_indices[index]; + + if (!voice_machine_valid(machine_index) || machine_index == local_machine) + continue; + if (!voice_machines[machine_index].key) + network_voice_machine_joined(machine_index); + if (!voice_machines[machine_index].told || changed || + now - voice_machines[machine_index].told_at >= VOICE_CONFIG_MS) + { + voice_host_tell(machine_index); + } + } +} + +/* this machine's frame: the host's own relayed, a client's sent up (none: +it hears voices) */ +static void voice_send( + byte const *packet, + long length) +{ + if (voice_host()) + { + if (length > 0) + voice_host_relay(network_game_client_get_local_machine_index(), voice_sequence, packet, length); + } + else + { + struct + { + struct distributed_message_header header; + struct distributed_voice_up up; + byte packet[VOICE_MAXIMUM_PACKET]; + } message; + + csmemset(&message, 0, sizeof(message.header) + sizeof(message.up)); + voice_fill_header(&message.header, _distributed_message_voice_up, + (word)(sizeof(message.header) + sizeof(message.up) + length)); + message.up.key = voice_key; + message.up.sequence = voice_sequence; + message.up.length = (byte)length; + if (length > 0) + csmemcpy(message.packet, packet, length); + network_distributed_client_send(&message, (word)(sizeof(message.header) + sizeof(message.up) + length)); + } + if (length > 0) + voice_sequence++; +} + +/* the session over (or none): the microphone closed, voices forgotten */ +static void voice_end_session( + void) +{ + if (!voice_session) + return; + voice_session = FALSE; + voice_microphone_wanted = FALSE; + voice_audio_microphone(FALSE); + voice_audio_forget_all(); + voice_settings_heard = FALSE; + voice_talked = FALSE; + csmemset(voice_heard, 0, sizeof(voice_heard)); +} + +void network_voice_update( + void) +{ + char const *chat = config_string("audio.voice_chat"); + boolean open_mic = chat && !csstrcmp(chat, "open_mic"); + boolean push_to_talk = chat && !csstrcmp(chat, "push_to_talk"); + boolean held; + unsigned long now = system_milliseconds(); + float frame[VOICE_FRAME_SAMPLES]; + short local_machine = network_game_client_get_local_machine_index(); + long index; + + voice_audio_set_volume((float)PIN(config_real("audio.voice_volume"), 0.0, 2.0)); + /* (a network game: the host's, or one joined) */ + if (!voice_network_game() || local_machine == NONE) + { + voice_end_session(); + return; + } + voice_session = TRUE; + if (voice_host()) + voice_host_update(); + else if (voice_settings_heard && now - voice_settings_heard_at >= VOICE_CONFIG_TIMEOUT_MS) + { + /* (the host repeats them: one that stopped has no voice chat) */ + voice_settings_heard = FALSE; + } + /* (mutes of a machine whose players changed are forgotten) */ + for (index = 0; index < VOICE_MACHINES; index++) + { + wchar_t name[12]; + + if (!voice_muted[index]) + continue; + voice_machine_name(index, name); + if (csmemcmp(name, voice_muted_names[index], sizeof(name))) + voice_muted[index] = FALSE; + } + if (!voice_settings_heard) + { + voice_audio_microphone(FALSE); + voice_microphone_wanted = FALSE; + return; + } + /* (a client says it hears voices) */ + if (!voice_host() && now - voice_hello_at >= VOICE_HELLO_MS) + { + voice_hello_at = now; + voice_send(NULL, 0); + } + /* the microphone: open mic, open while voice is on; push to talk, from + the first press (none asked for until then) */ + held = push_to_talk && halo_push_to_talk_held() && !console_is_active(); + if (!voice_may_talk() || (!open_mic && !push_to_talk)) + voice_microphone_wanted = FALSE; + else if (open_mic || held) + voice_microphone_wanted = TRUE; + voice_audio_microphone(voice_microphone_wanted); + while (voice_microphone_wanted && voice_audio_read_frame(frame)) + { + boolean talk = held; + byte packet[VOICE_MAXIMUM_PACKET]; + long length; + + if (open_mic) + { + if (voice_audio_level(frame) >= VOICE_OPEN_MIC_LEVEL) + voice_open_mic_until = now + VOICE_OPEN_MIC_HOLD_MS; + talk = (long)(voice_open_mic_until - now) > 0; + } + if (!talk || !voice_may_talk()) + continue; + length = voice_audio_encode(frame, voice_settings.kbps * 1000, packet, + (int)MIN(voice_packet_limit(voice_settings.kbps), VOICE_MAXIMUM_PACKET)); + if (length <= 0) + continue; + voice_send(packet, length); + voice_talked = TRUE; + voice_talked_at = now; + } +} + +/* ---------- the menus and the scoreboard (network_voice.h) */ + +boolean network_voice_machine_speaking( + long machine_index) +{ + unsigned long now = system_milliseconds(); + + if (!voice_session || !voice_machine_valid(machine_index)) + return FALSE; + if (machine_index == network_game_client_get_local_machine_index()) + return voice_talked && now - voice_talked_at < VOICE_SPEAKING_MS; + return voice_heard[machine_index] && now - voice_heard_at[machine_index] < VOICE_SPEAKING_MS && + !voice_muted[machine_index]; +} + +boolean network_voice_machine_muted( + long machine_index) +{ + return voice_machine_valid(machine_index) && voice_muted[machine_index]; +} + +void network_voice_mute_machine( + long machine_index, + boolean mute) +{ + if (!voice_machine_valid(machine_index) || machine_index == network_game_client_get_local_machine_index()) + return; + voice_muted[machine_index] = mute; + voice_machine_name(machine_index, voice_muted_names[machine_index]); + if (mute) + voice_audio_forget((int)machine_index); +} + +boolean network_voice_available( + void) +{ + return voice_session && voice_settings_heard; +} + +/* a speaker drawn in the rectangle (a square of its height, at its left; +muted: struck through, in red), fading with alpha: Lucide's icons +(port/assets/icons/lucide), drawn white as the menus' bitmap voice/speaker, +a frame each (tools/ce_menus.py), and tinted; none without the menus' +bitmaps */ +void network_voice_draw_icon( + rectangle2d const *bounds, + boolean muted, + real alpha) +{ + long bitmap_index = tag_loaded(BITMAP_GROUP_TAG, VOICE_ICON_BITMAP); + struct bitmap_data *bitmap = bitmap_index != NONE ? bitmap_group_try_and_get_bitmap(bitmap_index, muted ? 1 : 0) : + NULL; + pixel32 a = (pixel32)(PIN(alpha, 0.0f, 1.0f) * 255.0f + 0.5f) << 24; + rectangle2d square = *bounds; + + if (!bitmap || bounds->y1 - bounds->y0 < 4) + return; + square.x1 = (short)(square.x0 + (square.y1 - square.y0)); + draw_bitmap_in_rect(bitmap, &square, NULL, NULL, a | (muted ? 0x00E05A5A : 0x0050E050), NULL, TRUE); +} diff --git a/port/linux/game/network_voice.h b/port/linux/game/network_voice.h new file mode 100644 index 00000000..b0a36a45 --- /dev/null +++ b/port/linux/game/network_voice.h @@ -0,0 +1,59 @@ +/* +NETWORK_VOICE.H + +Voice chat (network_voice.c): what the main loop, the netcode, the host's +server, the lobby (menu_functions.c) and the scoreboard (game_engine.c) ask +of it. +*/ + +#ifndef __NETWORK_VOICE_H +#define __NETWORK_VOICE_H +#pragma once + +/* ---------- constants */ + +/* network.voice_mode: who hears whom in a game */ +enum +{ + _voice_mode_off, + _voice_mode_team_proximity, + _voice_mode_team_enemy_proximity, + _voice_mode_team_global, + _voice_mode_team_global_enemy_proximity, + NUMBER_OF_VOICE_MODES +}; + +/* how a listener hears a speaker */ +enum +{ + _voice_route_none, + /* as spoken */ + _voice_route_global, + /* quieter further, from the speaker's side */ + _voice_route_proximity, +}; + +/* ---------- prototypes/NETWORK_VOICE.C */ + +/* every frame (main.c): the microphone, the host's settings told */ +void network_voice_update(void); +/* whether a message of the distributed kind is voice chat's, which it takes +in the lobby too; and its handling (machine_index: the sender's on the +host, NONE on a client) */ +boolean network_voice_handles_message(word const *message, word size); +void network_voice_handle_message(long machine_index, word const *message, word size); +/* (the host's server) a machine joined at the slot: its new key */ +void network_voice_machine_joined(long machine_index); +/* whether this machine has voice chat now (the host's settings heard) */ +boolean network_voice_available(void); +/* whether a machine (its index in the network game) is talking now (this +machine's own, as it sends); muted ones never */ +boolean network_voice_machine_speaking(long machine_index); +/* this machine's mutes of others (which it tells no one) */ +boolean network_voice_machine_muted(long machine_index); +void network_voice_mute_machine(long machine_index, boolean mute); +/* a speaker icon, at the rectangle's left and as tall as it (muted: struck +through), fading with alpha */ +void network_voice_draw_icon(rectangle2d const *bounds, boolean muted, real alpha); + +#endif // __NETWORK_VOICE_H diff --git a/port/linux/game/network_votekick.c b/port/linux/game/network_votekick.c new file mode 100644 index 00000000..2b416a40 --- /dev/null +++ b/port/linux/game/network_votekick.c @@ -0,0 +1,1079 @@ +/* +NETWORK_VOTEKICK.C + +The players' vote to kick a player (port/linux/NETCODE.md). A player starts +one by picking another's name on the scoreboard (game_engine.c) or with the +console's "votekick "; the others vote the same way. The host alone +counts, and kicks the machine of the player once enough have voted. A +vote's power is against the game's other players, so the host decides +every part of it and trusts nothing a client says but which player it +votes against: + +- A vote counts only from its machine's own stream: a datagram's source + address is all that names its machine, and anyone can send one as from + another's. +- One vote a person. The votes and the players who may vote are counted by + their machines' real addresses (an internet play peer's endpoint, not its + stand-in, which the peer can make anew), and the votes again by hardware + id: two machines of one address or one hardware id are one vote. The + hardware id a machine tells the host joining (anyone's it likes), so it + only ever takes votes away: the players who may vote are counted by their + addresses alone, or a machine could copy another's id and so lower the + votes needed. +- Time played: on this host, counted in its own ticks of game, not told + by the client: votekick_minutes (config.toml) to start a vote, and a + part of that to vote, so that a player who has only just joined (or a + cheater's second machine) neither starts nor counts. Joining again starts + over. +- More than half of the players who may vote, the player voted against + counted among them (who never votes for it), and at least two: one team + alone kicks nobody from the other in an even game, and two people never + kick each other. +- One vote at a time, for VOTE_SECONDS, then a pause before the next. A + player whose vote failed starts none for STARTER_COOLDOWN_SECONDS, and a + player a vote failed against is not voted against again for + TARGET_PROTECTION_SECONDS, both by address and hardware id. +- The host's own players are never voted against; the host kicks and bans + as it likes (network_server_manager.c), and its machine votes as any + other. +- Only the host speaks: what a client did wrong is told to it alone, at + most once a second; the votes to everyone, only as they change. +- A machine kicked by a vote is kept out of the host's games, by address + and hardware id, for votekick_ban_minutes. One that leaves during a vote + against it is too, as if the vote passed, and while the vote runs it is + refused if it tries to join again: no one dodges a vote by leaving. + +A client hears the host's count every second (and as it changes), for the +scoreboard. A build without votes drops both messages as kinds it does not +know: its players cannot vote, and count as players who did not. +*/ + +#include "cseries.h" +#include "cseries/errors.h" +#include "memory/data.h" +#include "game/game.h" +#include "game/players.h" +#include "networking/network_game_globals.h" +#include "network_distributed.h" +#include "network_votekick.h" + +#include + +/* port_config.c's, network_server_manager.c's, cseries_windows.c's */ +int config_boolean(const char *name); +long config_integer(const char *name); +void network_game_server_kick_machine(long machine_index, boolean kept_out); +boolean network_game_server_kick_machine_of_player(long machine_index, boolean ban); +char const *network_game_server_machine_hardware_id(long machine_index); +void p2p_hardware_id_sanitize(char *destination, int size, const char *source); +unsigned long system_milliseconds(void); +void console_warning(const char *format, ...); + +/* ---------- constants */ + +enum +{ + /* p2p.h's P2P_HARDWARE_ID_SIZE */ + VOTEKICK_HARDWARE_ID_SIZE = 33, + VOTE_SECONDS = 45, + /* after a vote, before the next */ + VOTE_GAP_SECONDS = 30, + STARTER_COOLDOWN_SECONDS = 5 * 60, + TARGET_PROTECTION_SECONDS = 10 * 60, + /* the time played to vote, at most (and no more than to start one) */ + VOTER_MINIMUM_SECONDS = 2 * 60, + /* a machine's requests the host takes, at most one this often */ + REQUEST_INTERVAL_MILLISECONDS = 1000, + MINIMUM_VOTES = 2, + /* the addresses (and hardware ids) remembered: kept out, cooling down, + protected; the oldest forgotten first */ + MAXIMUM_REMEMBERED = 32, + /* a client forgets the host's count it has not heard again this long */ + STATUS_TIMEOUT_MILLISECONDS = 3000, + MAXIMUM_VOTEKICK_MACHINES = HALO_PORT_MAXIMUM_NETWORK_MACHINES, +}; + +/* the status's flags */ +enum +{ + /* this machine (the one told) has voted */ + _votekick_status_voted_bit, + /* ... may vote */ + _votekick_status_may_vote_bit, +}; + +/* ---------- structures */ + +struct distributed_votekick_request +{ + /* the player voted against (absolute index) */ + byte player_index; + byte pad[3]; +}; + +struct distributed_votekick_status +{ + /* the player voted against (absolute index), NO_PLAYER for no vote */ + byte player_index; + byte votes; + byte needed; + byte seconds_left; + byte flags; + byte pad[3]; +}; + +typedef char distributed_votekick_request_size_assert[sizeof(struct distributed_votekick_request) == 4 ? 1 : -1]; +typedef char distributed_votekick_status_size_assert[sizeof(struct distributed_votekick_status) == 8 ? 1 : -1]; + +/* a machine as the count sees it (votekick_count) */ +struct votekick_voter +{ + /* its real address (host byte order; 0: not known) */ + unsigned long address; + char hardware_id[VOTEKICK_HARDWARE_ID_SIZE]; + /* the host's own machine */ + boolean host; + /* the machine of the player voted against */ + boolean target; + /* has played long enough to vote (the host's, always) */ + boolean eligible; + boolean voted; +}; + +/* an address and hardware id remembered until a time (system_milliseconds) */ +struct votekick_identity +{ + unsigned long address; + char hardware_id[VOTEKICK_HARDWARE_ID_SIZE]; + unsigned long until; + boolean valid; +}; + +/* ---------- globals */ + +/* the host: each machine slot's time in game, in the host's ticks, since it +joined (network_votekick_machine_joined), and how many machines joined at +it (a vote names the one it was) */ +static long votekick_played_ticks[MAXIMUM_VOTEKICK_MACHINES]; +static long votekick_generations[MAXIMUM_VOTEKICK_MACHINES]; +/* ... when it last asked something (system_milliseconds), and whether it +has */ +static unsigned long votekick_request_times[MAXIMUM_VOTEKICK_MACHINES]; +static boolean votekick_requested[MAXIMUM_VOTEKICK_MACHINES]; + +/* the host: the vote */ +static struct +{ + boolean active; + /* the machine voted against, and its players' first (for the clients) */ + long target_machine; + long target_generation; + short target_player; + unsigned long target_address; + char target_hardware_id[VOTEKICK_HARDWARE_ID_SIZE]; + char target_names[64]; + /* who started it, cooling down if it fails */ + unsigned long starter_address; + char starter_hardware_id[VOTEKICK_HARDWARE_ID_SIZE]; + unsigned long started_at; + /* the machines that voted (as they were at their slots), the host's */ + boolean voted[MAXIMUM_VOTEKICK_MACHINES]; + long voted_generations[MAXIMUM_VOTEKICK_MACHINES]; + boolean host_voted; + /* as last counted, and told */ + short votes; + short needed; +} votekick; + +/* the host: no vote before this (a vote's end and the gap after) */ +static unsigned long votekick_next_allowed; +static boolean votekick_gap; + +static struct votekick_identity votekick_kept_out_identities[MAXIMUM_REMEMBERED]; +static struct votekick_identity votekick_cooling_starters[MAXIMUM_REMEMBERED]; +static struct votekick_identity votekick_protected_targets[MAXIMUM_REMEMBERED]; + +/* a client: the host's count, as last heard (the time: 0, none) */ +static struct distributed_votekick_status votekick_status; +static unsigned long votekick_status_heard; + +/* ---------- the count (pure: tools/harness/tests/votekick_count.c) */ + +/* whether two machines are one person's by address: the host's own is +only itself; an address not known, no other's */ +static boolean votekick_same_address( + struct votekick_voter const *a, + struct votekick_voter const *b) +{ + if (a->host || b->host) + return a->host && b->host; + return a->address != 0 && a->address == b->address; +} + +/* ... or by hardware id too (none told: no other's) */ +static boolean votekick_same_person( + struct votekick_voter const *a, + struct votekick_voter const *b) +{ + if (votekick_same_address(a, b)) + return TRUE; + return !a->host && !b->host && a->hardware_id[0] && !csstrcmp(a->hardware_id, b->hardware_id); +} + +/* whether a machine is one of those who may vote: played long enough, and +its address known (else it could not be told from another) */ +static boolean votekick_may_vote( + struct votekick_voter const *voter) +{ + return voter->eligible && !voter->target && (voter->host || voter->address != 0); +} + +/* the players who may vote (by address: the target's among them, once) and +the votes for (by address and hardware id, none of the target's person) */ +static void votekick_count( + struct votekick_voter const *voters, + short count, + short *votes, + short *electorate) +{ + short index; + short other; + + *votes = 0; + *electorate = 0; + for (index = 0; index < count; index++) + { + boolean counted = votekick_may_vote(&voters[index]) || voters[index].target; + + /* (an address counted once: the first of its machines) */ + for (other = 0; counted && other < index; other++) + { + if ((votekick_may_vote(&voters[other]) || voters[other].target) && + votekick_same_address(&voters[index], &voters[other])) + { + counted = FALSE; + } + } + if (counted) + (*electorate)++; + } + for (index = 0; index < count; index++) + { + boolean counted = voters[index].voted && votekick_may_vote(&voters[index]); + + for (other = 0; counted && other < count; other++) + { + /* (not the target's person, whatever it says; a person once) */ + if (voters[other].target && votekick_same_person(&voters[index], &voters[other])) + counted = FALSE; + if (other < index && voters[other].voted && votekick_may_vote(&voters[other]) && + votekick_same_person(&voters[index], &voters[other])) + { + counted = FALSE; + } + } + if (counted) + (*votes)++; + } +} + +/* the votes that kick: more than half of those who may vote, and at least +MINIMUM_VOTES */ +static short votekick_votes_needed( + short electorate) +{ + short needed = (short)(electorate / 2 + 1); + + return needed < MINIMUM_VOTES ? MINIMUM_VOTES : needed; +} + +/* whether a time (system_milliseconds) has come; times wrap, so by their +difference */ +static boolean votekick_time_reached( + unsigned long now, + unsigned long time) +{ + return (long)(now - time) >= 0; +} + +/* ---------- remembered identities */ + +static void votekick_remember( + struct votekick_identity *identities, + unsigned long address, + char const *hardware_id, + unsigned long milliseconds) +{ + unsigned long now = system_milliseconds(); + short index; + short oldest = 0; + + /* (a free entry, one gone by, else the one that ends soonest) */ + for (index = 0; index < MAXIMUM_REMEMBERED; index++) + { + if (!identities[index].valid || votekick_time_reached(now, identities[index].until)) + { + oldest = index; + break; + } + if ((long)(identities[index].until - identities[oldest].until) < 0) + oldest = index; + } + identities[oldest].valid = TRUE; + identities[oldest].address = address; + p2p_hardware_id_sanitize(identities[oldest].hardware_id, VOTEKICK_HARDWARE_ID_SIZE, hardware_id); + identities[oldest].until = now + milliseconds; +} + +/* whether an address or hardware id is remembered (not gone by); the +milliseconds left */ +static boolean votekick_remembered( + struct votekick_identity const *identities, + unsigned long address, + char const *hardware_id, + unsigned long *milliseconds_left) +{ + unsigned long now = system_milliseconds(); + short index; + + for (index = 0; index < MAXIMUM_REMEMBERED; index++) + { + struct votekick_identity const *identity = &identities[index]; + + if (!identity->valid || votekick_time_reached(now, identity->until)) + continue; + if ((address && identity->address == address) || + (hardware_id && hardware_id[0] && identity->hardware_id[0] && + !csstrcmp(hardware_id, identity->hardware_id))) + { + if (milliseconds_left) + *milliseconds_left = identity->until - now; + return TRUE; + } + } + return FALSE; +} + +/* ---------- the host */ + +static boolean votekick_host( + void) +{ + return game_connection() == _game_connection_network_server; +} + +/* the time played to start a vote, and to vote (in ticks) */ +static long votekick_starter_ticks( + void) +{ + long minutes = config_integer("network.votekick_minutes"); + + return PIN(minutes, 0, 60) * 60 * TICKS_PER_SECOND; +} + +static long votekick_voter_ticks( + void) +{ + return MIN(votekick_starter_ticks(), VOTER_MINIMUM_SECONDS * TICKS_PER_SECOND); +} + +static boolean votekick_machine_valid( + long machine_index) +{ + return machine_index >= 0 && machine_index < MAXIMUM_VOTEKICK_MACHINES; +} + +/* (network_server_manager.c) a machine joined the host at the slot: its +time played starts over, and no vote of the machine before is its */ +void network_votekick_machine_joined( + long machine_index) +{ + if (!votekick_machine_valid(machine_index)) + return; + votekick_played_ticks[machine_index] = 0; + votekick_generations[machine_index]++; + votekick_requested[machine_index] = FALSE; + votekick.voted[machine_index] = FALSE; +} + +/* (network_server_manager.c) whether a machine of the address (as it +joins from: host byte order) and hardware id is kept out, kicked by a vote */ +boolean network_votekick_kept_out( + unsigned long address, + char const *hardware_id) +{ + unsigned long real_address = distributed_real_address(address); + + /* (nor the player a vote runs against, joining again during it: it + would drop the machine voted against for a new one, out of the vote) */ + if (votekick.active && votekick_host() && + ((real_address && real_address == votekick.target_address) || + (hardware_id && hardware_id[0] && votekick.target_hardware_id[0] && + !csstrcmp(hardware_id, votekick.target_hardware_id)))) + { + return TRUE; + } + return votekick_remembered(votekick_kept_out_identities, real_address, hardware_id, NULL); +} + +/* the client machines in the game with a player who has not quit, and the +host's own if it has one (NONE); their count */ +static short votekick_machines( + long *machine_indices, + short maximum) +{ + struct data_iterator iterator; + struct player_datum *player; + short count = 0; + + data_iterator_new(&iterator, player_data); + while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL) + { + long machine_index = distributed_player_machine((short)DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index)); + short index; + + if (player->quit_out_of_game || (machine_index != NONE && !votekick_machine_valid(machine_index))) + continue; + for (index = 0; index < count && machine_indices[index] != machine_index; index++) + ; + if (index == count && count < maximum) + machine_indices[count++] = machine_index; + } + return count; +} + +/* a machine (NONE: the host's own) as the count sees it */ +static void votekick_voter_get( + long machine_index, + struct votekick_voter *voter) +{ + csmemset(voter, 0, sizeof(*voter)); + if (machine_index == NONE) + { + voter->host = TRUE; + voter->eligible = TRUE; + voter->voted = votekick.active && votekick.host_voted; + return; + } + voter->address = distributed_machine_real_address(machine_index); + p2p_hardware_id_sanitize(voter->hardware_id, VOTEKICK_HARDWARE_ID_SIZE, + network_game_server_machine_hardware_id(machine_index)); + voter->eligible = votekick_played_ticks[machine_index] >= votekick_voter_ticks(); + voter->target = votekick.active && machine_index == votekick.target_machine; + voter->voted = votekick.active && votekick.voted[machine_index] && + votekick.voted_generations[machine_index] == votekick_generations[machine_index]; +} + +/* the target's machine still the one voted against, in the game */ +static boolean votekick_target_present( + void) +{ + long machine_indices[MAXIMUM_VOTEKICK_MACHINES + 1]; + short count = votekick_machines(machine_indices, (short)NUMBEROF(machine_indices)); + short index; + + if (votekick_generations[votekick.target_machine] != votekick.target_generation) + return FALSE; + for (index = 0; index < count; index++) + { + if (machine_indices[index] == votekick.target_machine) + return TRUE; + } + return FALSE; +} + +/* the count now, of the machines in the game (the target's among them: +votekick_target_present) */ +static void votekick_recount( + void) +{ + struct votekick_voter voters[MAXIMUM_VOTEKICK_MACHINES + 1]; + long machine_indices[MAXIMUM_VOTEKICK_MACHINES + 1]; + short count = votekick_machines(machine_indices, (short)NUMBEROF(machine_indices)); + short electorate; + short index; + + for (index = 0; index < count; index++) + votekick_voter_get(machine_indices[index], &voters[index]); + votekick_count(voters, count, &votekick.votes, &electorate); + votekick.needed = votekick_votes_needed(electorate); +} + +/* the name of a machine's players for a notice (NONE: the host's own) */ +static void votekick_names( + long machine_index, + char *names, + long size) +{ + distributed_machine_player_names(machine_index, names, size); +} + +/* the count to every client: what it may do, and has */ +static void votekick_send_status( + void) +{ + long machine_indices[MAXIMUM_VOTEKICK_MACHINES]; + short count = distributed_client_machines(machine_indices, MAXIMUM_VOTEKICK_MACHINES); + unsigned long now = system_milliseconds(); + short index; + + for (index = 0; index < count; index++) + { + struct + { + struct distributed_message_header header; + struct distributed_votekick_status status; + } message; + struct votekick_voter voter; + + csmemset(&message, 0, sizeof(message)); + message.status.player_index = NO_PLAYER; + if (votekick.active) + { + unsigned long elapsed = now - votekick.started_at; + long left = VOTE_SECONDS - (long)(elapsed / 1000); + + votekick_voter_get(machine_indices[index], &voter); + message.status.player_index = distributed_player_to_byte(votekick.target_player); + message.status.votes = (byte)PIN(votekick.votes, 0, 255); + message.status.needed = (byte)PIN(votekick.needed, 0, 255); + message.status.seconds_left = (byte)PIN(left, 0, 255); + if (voter.voted) + message.status.flags |= FLAG(_votekick_status_voted_bit); + if (votekick_may_vote(&voter)) + message.status.flags |= FLAG(_votekick_status_may_vote_bit); + } + distributed_send_to_machine(machine_indices[index], &message, _distributed_message_votekick_status, 1, + (word)sizeof(message)); + } +} + +/* the vote over (passed or not: its notice said), and none for a while */ +static void votekick_end( + void) +{ + votekick.active = FALSE; + votekick_next_allowed = system_milliseconds() + VOTE_GAP_SECONDS * 1000; + votekick_gap = TRUE; + votekick_send_status(); +} + +/* the vote passed: the player kicked and kept out; or (left) the player +left during it, which passes it too (no one dodges a vote by leaving), and +is kept out as well */ +static void votekick_pass( + boolean left) +{ + char notice[160]; + long ban_minutes = PIN(config_integer("network.votekick_ban_minutes"), 1, 24 * 60); + + if (left) + { + snprintf(notice, sizeof(notice), "%s left during the vote to kick them, kept out for %ld minute%s", + votekick.target_names, ban_minutes, ban_minutes == 1 ? "" : "s"); + } + else + { + snprintf(notice, sizeof(notice), "%s kicked by vote (%d of %d), kept out for %ld minute%s", + votekick.target_names, votekick.votes, votekick.needed, ban_minutes, ban_minutes == 1 ? "" : "s"); + } + distributed_send_notice(notice); + votekick_remember(votekick_kept_out_identities, votekick.target_address, votekick.target_hardware_id, + (unsigned long)ban_minutes * 60 * 1000); + /* (one that left is gone: its slot may be another's now) */ + if (!left) + network_game_server_kick_machine(votekick.target_machine, FALSE); + votekick_end(); +} + +static void votekick_fail( + void) +{ + char notice[160]; + + snprintf(notice, sizeof(notice), "The vote to kick %s failed (%d of %d)", votekick.target_names, votekick.votes, + votekick.needed); + distributed_send_notice(notice); + votekick_remember(votekick_cooling_starters, votekick.starter_address, votekick.starter_hardware_id, + STARTER_COOLDOWN_SECONDS * 1000UL); + votekick_remember(votekick_protected_targets, votekick.target_address, votekick.target_hardware_id, + TARGET_PROTECTION_SECONDS * 1000UL); + votekick_end(); +} + +/* a refusal, to the asking machine alone */ +static void votekick_refuse( + long machine_index, + char const *text) +{ + distributed_send_notice_to_machine(machine_index, text); +} + +/* a machine's vote against a player (NONE: the host's own machine): a new +vote, or one for the vote running */ +static void votekick_host_request( + long machine_index, + short player_index) +{ + struct player_datum *player = distributed_player(player_index); + long target_machine = player ? distributed_player_machine(player_index) : NONE; + struct votekick_voter voter; + struct votekick_voter target; + char names[64]; + char notice[160]; + unsigned long now = system_milliseconds(); + unsigned long left; + + if (!config_boolean("network.votekick")) + { + votekick_refuse(machine_index, "votekick: the host has turned votes off"); + return; + } + if (machine_index == NONE && local_player_get_next(NONE) == NONE) + return; + if (!player || player->quit_out_of_game) + { + votekick_refuse(machine_index, "votekick: no such player"); + return; + } + if (target_machine == NONE) + { + votekick_refuse(machine_index, "votekick: the host's players cannot be voted out"); + return; + } + if (!votekick_machine_valid(target_machine) || target_machine == machine_index) + { + votekick_refuse(machine_index, "votekick: not against your own players"); + return; + } + votekick_voter_get(machine_index, &voter); + votekick_voter_get(target_machine, &target); + target.target = TRUE; + if (votekick_same_person(&voter, &target)) + { + votekick_refuse(machine_index, "votekick: not against your own machine"); + return; + } + if (!voter.host && !voter.address) + { + votekick_refuse(machine_index, "votekick: the host does not know your address"); + return; + } + /* a vote for the vote running */ + if (votekick.active) + { + if (target_machine != votekick.target_machine) + { + snprintf(notice, sizeof(notice), "votekick: wait for the vote to kick %s to end", votekick.target_names); + votekick_refuse(machine_index, notice); + return; + } + if (!votekick_may_vote(&voter)) + { + votekick_refuse(machine_index, "votekick: you have not played long enough on this server to vote"); + return; + } + if (voter.voted) + return; + if (machine_index == NONE) + votekick.host_voted = TRUE; + else + { + votekick.voted[machine_index] = TRUE; + votekick.voted_generations[machine_index] = votekick_generations[machine_index]; + } + { + short votes = votekick.votes; + + votekick_recount(); + /* (told only as the count changes: a second machine of one + person's adds nothing) */ + if (votekick.votes != votes && votekick.votes < votekick.needed) + { + votekick_names(machine_index, names, sizeof(names)); + snprintf(notice, sizeof(notice), "%s voted to kick %s (%d of %d)", names, votekick.target_names, + votekick.votes, votekick.needed); + distributed_send_notice(notice); + } + } + if (votekick.votes >= votekick.needed) + votekick_pass(FALSE); + else + votekick_send_status(); + return; + } + /* a new vote */ + if (votekick_gap && !votekick_time_reached(now, votekick_next_allowed)) + { + snprintf(notice, sizeof(notice), "votekick: the next vote can start in %lu seconds", + (votekick_next_allowed - now + 999) / 1000); + votekick_refuse(machine_index, notice); + return; + } + if (!voter.host && votekick_played_ticks[machine_index] < votekick_starter_ticks()) + { + long minutes = votekick_starter_ticks() / (60 * TICKS_PER_SECOND); + + snprintf(notice, sizeof(notice), + "votekick: play %ld minute%s on this server to start a vote (%ld played)", minutes, + minutes == 1 ? "" : "s", votekick_played_ticks[machine_index] / (60 * TICKS_PER_SECOND)); + votekick_refuse(machine_index, notice); + return; + } + if (!voter.host && votekick_remembered(votekick_cooling_starters, voter.address, voter.hardware_id, &left)) + { + snprintf(notice, sizeof(notice), "votekick: your last vote failed: wait %lu seconds", (left + 999) / 1000); + votekick_refuse(machine_index, notice); + return; + } + if (votekick_remembered(votekick_protected_targets, target.address, target.hardware_id, &left)) + { + snprintf(notice, sizeof(notice), "votekick: a vote against that player failed lately: wait %lu seconds", + (left + 999) / 1000); + votekick_refuse(machine_index, notice); + return; + } + csmemset(&votekick, 0, sizeof(votekick)); + votekick.active = TRUE; + votekick.target_machine = target_machine; + votekick.target_generation = votekick_generations[target_machine]; + votekick.target_player = player_index; + votekick.target_address = target.address; + csstrcpy(votekick.target_hardware_id, target.hardware_id); + votekick_names(target_machine, votekick.target_names, sizeof(votekick.target_names)); + votekick.starter_address = voter.address; + csstrcpy(votekick.starter_hardware_id, voter.hardware_id); + votekick.started_at = now; + if (machine_index == NONE) + votekick.host_voted = TRUE; + else + { + votekick.voted[machine_index] = TRUE; + votekick.voted_generations[machine_index] = votekick_generations[machine_index]; + } + votekick_recount(); + votekick_names(machine_index, names, sizeof(names)); + snprintf(notice, sizeof(notice), "%s started a vote to kick %s (%d of %d): open the scores and pick the name to vote", + names, votekick.target_names, votekick.votes, votekick.needed); + distributed_send_notice(notice); + error(_error_log, "votekick: started by machine %ld against machine %ld", machine_index, target_machine); + if (votekick.votes >= votekick.needed) + votekick_pass(FALSE); + else + votekick_send_status(); +} + +void network_votekick_handle_request( + long machine_index, + void const *entries, + boolean from_stream) +{ + struct distributed_votekick_request request; + unsigned long now = system_milliseconds(); + + if (!votekick_host() || !votekick_machine_valid(machine_index) || !from_stream) + return; + /* (at most one a second: more are dropped, untold) */ + if (votekick_requested[machine_index] && + !votekick_time_reached(now, votekick_request_times[machine_index] + REQUEST_INTERVAL_MILLISECONDS)) + { + return; + } + votekick_requested[machine_index] = TRUE; + votekick_request_times[machine_index] = now; + csmemcpy(&request, entries, sizeof(request)); + if (request.player_index >= MAXIMUM_TRACKED_PLAYERS) + return; + votekick_host_request(machine_index, request.player_index); +} + +word network_votekick_request_entry_size( + void) +{ + return sizeof(struct distributed_votekick_request); +} + +void network_votekick_new_game( + void) +{ + /* (a vote ends with its game, failing nobody; the time played stays) */ + if (votekick.active && votekick_host()) + distributed_send_notice("The vote ended with the game"); + votekick.active = FALSE; + csmemset(&votekick_status, 0, sizeof(votekick_status)); + votekick_status_heard = 0; +} + +void network_votekick_host_tick( + void) +{ + long machine_indices[MAXIMUM_VOTEKICK_MACHINES]; + short count = distributed_client_machines(machine_indices, MAXIMUM_VOTEKICK_MACHINES); + short index; + + for (index = 0; index < count; index++) + { + if (votekick_machine_valid(machine_indices[index])) + votekick_played_ticks[machine_indices[index]]++; + } + if (!votekick.active) + return; + /* (the player left: the vote passes) */ + if (!votekick_target_present()) + { + votekick_pass(TRUE); + return; + } + /* (those gone counted no more, and those who have since played long + enough to vote among those who may) */ + if (game_time_get() % TICKS_PER_SECOND == 0) + { + votekick_recount(); + if (votekick.votes >= votekick.needed) + { + votekick_pass(FALSE); + return; + } + if (votekick_time_reached(system_milliseconds(), votekick.started_at + VOTE_SECONDS * 1000)) + { + votekick_fail(); + return; + } + votekick_send_status(); + } +} + +/* ---------- a client */ + +void network_votekick_handle_status( + void const *entries) +{ + if (game_connection() != _game_connection_network_client) + return; + csmemcpy(&votekick_status, entries, sizeof(votekick_status)); + votekick_status_heard = system_milliseconds(); + if (votekick_status.player_index != NO_PLAYER && !distributed_player(votekick_status.player_index)) + votekick_status.player_index = NO_PLAYER; + /* (0: none heard) */ + if (!votekick_status_heard) + votekick_status_heard = 1; +} + +word network_votekick_status_entry_size( + void) +{ + return sizeof(struct distributed_votekick_status); +} + +/* ---------- every machine (network_votekick.h) */ + +boolean network_votekick_get_status( + struct network_votekick_status *status) +{ + csmemset(status, 0, sizeof(*status)); + status->player_index = NONE; + if (votekick_host()) + { + struct votekick_voter voter; + unsigned long elapsed; + + if (!votekick.active) + return FALSE; + votekick_voter_get(NONE, &voter); + elapsed = system_milliseconds() - votekick.started_at; + status->player_index = votekick.target_player; + status->votes = votekick.votes; + status->needed = votekick.needed; + status->seconds_left = (short)MAX(0, VOTE_SECONDS - (long)(elapsed / 1000)); + status->voted = votekick.host_voted; + status->may_vote = TRUE; + return TRUE; + } + if (game_connection() != _game_connection_network_client || !votekick_status_heard || + votekick_time_reached(system_milliseconds(), votekick_status_heard + STATUS_TIMEOUT_MILLISECONDS) || + votekick_status.player_index == NO_PLAYER) + { + return FALSE; + } + status->player_index = distributed_player_from_byte(votekick_status.player_index) == NONE ? NONE : + votekick_status.player_index; + status->votes = votekick_status.votes; + status->needed = votekick_status.needed; + status->seconds_left = votekick_status.seconds_left; + status->voted = TEST_FLAG(votekick_status.flags, _votekick_status_voted_bit); + status->may_vote = TEST_FLAG(votekick_status.flags, _votekick_status_may_vote_bit); + return status->player_index != NONE; +} + +/* a player's name in ASCII (player_name_character_ascii), at most size */ +static void votekick_player_name( + struct player_datum const *player, + char *text, + long size) +{ + long index; + + for (index = 0; index < (long)NUMBEROF(player->name) && player->name[index] && index < size - 1; index++) + text[index] = player_name_character_ascii(player->name[index]); + text[index] = 0; +} + +/* whether a name begins with the text, in either case; and is it */ +static boolean votekick_name_begins_with( + char const *name, + char const *text, + boolean whole) +{ + for (; *text; name++, text++) + { + char a = *name >= 'A' && *name <= 'Z' ? *name - 'A' + 'a' : *name; + char b = *text >= 'A' && *text <= 'Z' ? *text - 'A' + 'a' : *text; + + if (!*name || a != b) + return FALSE; + } + return !whole || !*name; +} + +short network_votekick_matching_player_names( + char const *text, + char (*names)[VOTEKICK_NAME_TEXT_SIZE], + short maximum_count) +{ + struct data_iterator iterator; + struct player_datum *player; + short count = 0; + + if (!network_votekick_available()) + return 0; + data_iterator_new(&iterator, player_data); + while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL && count < maximum_count) + { + if (player->quit_out_of_game || player->local_player_index != NONE) + continue; + votekick_player_name(player, names[count], VOTEKICK_NAME_TEXT_SIZE); + if (votekick_name_begins_with(names[count], text, FALSE)) + count++; + } + return count; +} + +boolean network_votekick_player_named( + char const *text) +{ + struct data_iterator iterator; + struct player_datum *player; + long found = NONE; + short matches = 0; + short exact = 0; + long exact_found = NONE; + + if (!network_votekick_available()) + { + console_warning("votekick: only in a network game"); + return FALSE; + } + if (!text[0]) + { + console_warning("votekick: give a player's name (Tab completes it)"); + return FALSE; + } + data_iterator_new(&iterator, player_data); + while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL) + { + char name[VOTEKICK_NAME_TEXT_SIZE]; + + if (player->quit_out_of_game) + continue; + votekick_player_name(player, name, sizeof(name)); + if (votekick_name_begins_with(name, text, TRUE)) + { + exact++; + exact_found = iterator.datum_index; + } + if (votekick_name_begins_with(name, text, FALSE)) + { + matches++; + found = iterator.datum_index; + } + } + if (exact == 1) + { + found = exact_found; + matches = 1; + } + if (matches > 1) + { + console_warning("votekick: %d players' names begin with \"%s\": give more of it", matches, text); + return FALSE; + } + if (matches == 0) + { + console_warning("votekick: no player's name begins with \"%s\"", text); + return FALSE; + } + return network_votekick_request((short)DATUM_INDEX_TO_ABSOLUTE_INDEX(found)); +} + +/* (the host) its Kick and Ban on the scoreboard: the player's machine */ +boolean network_votekick_host_kick( + short player_index, + boolean ban) +{ + long machine_index; + + if (!votekick_host() || !distributed_player(player_index)) + return FALSE; + machine_index = distributed_player_machine(player_index); + if (machine_index == NONE) + { + console_warning("%s: not a player of the host's own machine", ban ? "ban" : "kick"); + return FALSE; + } + return network_game_server_kick_machine_of_player(machine_index, ban); +} + +boolean network_votekick_host( + void) +{ + return votekick_host(); +} + +boolean network_votekick_available( + void) +{ + return game_connection() == _game_connection_network_client || + game_connection() == _game_connection_network_server; +} + +boolean network_votekick_request( + short player_index) +{ + struct + { + struct distributed_message_header header; + struct distributed_votekick_request request; + } message; + + if (!distributed_player(player_index)) + return FALSE; + if (distributed_player_is_local(player_index)) + { + console_warning("votekick: not against your own players"); + return FALSE; + } + if (votekick_host()) + { + votekick_host_request(NONE, player_index); + return TRUE; + } + if (game_connection() != _game_connection_network_client) + { + console_warning("votekick: only in a network game"); + return FALSE; + } + csmemset(&message, 0, sizeof(message)); + message.request.player_index = distributed_player_to_byte(player_index); + distributed_send(&message, _distributed_message_votekick, 1, (word)sizeof(message), _distributed_to_host_reliably); + return TRUE; +} diff --git a/port/linux/game/network_votekick.h b/port/linux/game/network_votekick.h new file mode 100644 index 00000000..72e0a679 --- /dev/null +++ b/port/linux/game/network_votekick.h @@ -0,0 +1,63 @@ +/* +NETWORK_VOTEKICK.H + +The players' vote to kick a player (network_votekick.c): what the +scoreboard (game_engine.c), the console (hs.c) and the host's server +(network_server_manager.c) ask of it. +*/ + +#ifndef __NETWORK_VOTEKICK_H +#define __NETWORK_VOTEKICK_H +#pragma once + +/* ---------- constants */ + +enum +{ + /* a player's name in ASCII, with its end */ + VOTEKICK_NAME_TEXT_SIZE = 16, +}; + +/* ---------- structures */ + +/* the vote running, as the host counts it */ +struct network_votekick_status +{ + /* the player voted against (absolute index) */ + short player_index; + short votes; + short needed; + short seconds_left; + /* this machine has voted; may vote */ + boolean voted; + boolean may_vote; +}; + +/* ---------- prototypes/NETWORK_VOTEKICK.C */ + +/* whether this machine is in a network game, where votes are */ +boolean network_votekick_available(void); +/* the vote running; FALSE for none */ +boolean network_votekick_get_status(struct network_votekick_status *status); +/* this machine votes against the player (absolute index): starts a vote, +or votes in the one running; the host answers on the console */ +boolean network_votekick_request(short player_index); +/* whether this machine hosts the game (its scoreboard offers Kick and Ban) */ +boolean network_votekick_host(void); +/* (the host) kicks, or bans, the machine of the player (absolute index): +its kick and ban commands' (network_server_manager.c) */ +boolean network_votekick_host_kick(short player_index, boolean ban); +/* the console's "votekick ": the player of that name (or the one +whose name begins with it) voted against */ +boolean network_votekick_player_named(char const *text); +/* the names of the game's players but this machine's that begin with the +text (the command's completion: console.c); how many */ +short network_votekick_matching_player_names(char const *text, char (*names)[VOTEKICK_NAME_TEXT_SIZE], + short maximum_count); +/* (the host's server) a machine joined at the slot */ +void network_votekick_machine_joined(long machine_index); +/* (the host's server) whether a machine of the address (host byte order) +and hardware id is kept out, kicked by a vote */ +boolean network_votekick_kept_out(unsigned long address, char const *hardware_id); + +#endif // __NETWORK_VOTEKICK_H diff --git a/port/linux/game/profile_console.c b/port/linux/game/profile_console.c new file mode 100644 index 00000000..7e0057c0 --- /dev/null +++ b/port/linux/game/profile_console.c @@ -0,0 +1,304 @@ +/* +PROFILE_CONSOLE.C + +Connect profiling commands and launch settings to game recordings. +*/ + +#ifdef HALO_PROFILE + +#include "cseries.h" +#include "game/game.h" +#include "game/game_engine.h" +#include "main/console.h" +#include "networking/network_game_globals.h" +#include "network_distributed.h" +#include "profile_console.h" +#include "profile_console_gametype.h" +#include "profile_trace.h" + +#include +#include +#include + +/* the platform layer's (platform.h, port_config.h), and main.c's */ +void platform_log(const char *format, ...); +const char *platform_data_root(void); +int posix_make_directory(const char *path); +void *posix_directory_open(const char *path); +void posix_directory_close(void *directory); +void p2p_profile_lock(void); +void p2p_profile_unlock(void); +int config_boolean(const char *name); +long config_integer(const char *name); +const char *config_string(const char *name); +boolean main_menu_is_loaded(void); + + +/* ---------- globals */ + +static boolean profile_console_installed; + +static void profile_console_map_component(const char *map, char *name, int size) +{ + const char *component = map; + const char *cursor; + int used = 0; + + for (cursor = map; *cursor; cursor++) + if (*cursor == '/' || *cursor == '\\') + component = cursor + 1; + for (cursor = component; *cursor && used + 1 < size; cursor++) + { + unsigned char character = (unsigned char)*cursor; + if (character >= 'A' && character <= 'Z') + character = (unsigned char)(character + ('a' - 'A')); + name[used++] = (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || + character == '_' || character == '-' ? (char)character : '-'; + } + name[used] = 0; +} + +static const char *profile_console_gametype(void) +{ + long engine; + + if (!game_in_progress() || main_menu_is_loaded() || game_connection() == _game_connection_film_playback) + return "none"; + if (!game_engine_running()) + return profile_console_engine_gametype(FALSE, 0); + engine = game_engine_get_variant()->game_engine_index; + return profile_console_engine_gametype(TRUE, engine); +} + +/* the map the header names: the one loading takes its place only after the +frame boundary that stops a recording on it (profile_console_frame) */ +static char profile_console_map[64]; +static char profile_console_next_map[64]; + +/* ---------- private code */ + +static void profile_console_log( + const char *text) +{ + platform_log("profile: %s", text); +} + +static void profile_console_notify( + const char *text) +{ + console_printf(FALSE, "%s", text); +} + +static int profile_console_ready( + void) +{ + return game_in_progress() && !main_menu_is_loaded(); +} + +static void profile_console_session( + struct profile_trace_session *session) +{ + short player_index; + const char *scenario = profile_console_scenario_map(profile_console_next_map, profile_console_map); + void *folder; + + csmemset(session, 0, sizeof(*session)); + snprintf(session->folder, sizeof(session->folder), "%s/profiles", platform_data_root()); + posix_make_directory(session->folder); + /* (a folder that can't be made must not lose the recording: the parts go + to the data root itself then) */ + folder = posix_directory_open(session->folder); + if (folder) + posix_directory_close(folder); + else + strncpy(session->folder, platform_data_root(), sizeof(session->folder) - 1); +#ifdef HALO_RELEASE + strcpy(session->build, "release profile"); +#else + strcpy(session->build, "debug profile"); +#endif + session->own_machine = -1; + switch (game_connection()) + { + case _game_connection_network_server: + strcpy(session->role, "host"); + break; + case _game_connection_network_client: + strcpy(session->role, "client"); + session->own_machine = network_game_client_get_local_machine_index(); + break; + default: + strcpy(session->role, "local"); + break; + } + strncpy(session->map, scenario, sizeof(session->map) - 1); + profile_console_map_component(scenario, session->map_name, sizeof(session->map_name)); + strncpy(session->gametype, profile_console_gametype(), sizeof(session->gametype) - 1); + for (player_index = 0; player_index < MAXIMUM_TRACKED_PLAYERS; player_index++) + { + if (distributed_player(player_index)) + session->players++; + } + session->players_most = session->players; +} + +/* (once a second on the game thread: unlike the start-time description it +makes and opens no folder, which would land in the measured frame) */ +static void profile_console_sample_session( + struct profile_trace_session *session) +{ + short player_index; + const char *scenario = profile_console_scenario_map(profile_console_next_map, profile_console_map); + + if (!session->map_name[0] && scenario[0]) + { + strncpy(session->map, scenario, sizeof(session->map) - 1); + profile_console_map_component(scenario, session->map_name, sizeof(session->map_name)); + strncpy(session->gametype, profile_console_gametype(), sizeof(session->gametype) - 1); + } + for (player_index = 0; player_index < MAXIMUM_TRACKED_PLAYERS; player_index++) + if (distributed_player(player_index)) + session->players++; +} + +/* the recording's seams. Not left to the first frame: init.txt's +profile_record is handled before it, and the recording's name (folder and +role) is chosen from the session when it is requested */ +static void profile_console_install( + void) +{ + if (profile_console_installed) + return; + profile_console_installed = TRUE; + profile_trace_set_log(profile_console_log); + profile_trace_set_notify(profile_console_notify); + profile_trace_set_ready(profile_console_ready); + profile_trace_set_session(profile_console_session); + profile_trace_set_session_sampler(profile_console_sample_session); + profile_trace_set_track_lock(p2p_profile_lock, p2p_profile_unlock); + /* (the window's close, debug.exit_after and XLaunchNewImage end the + process with exit(), which main_exit never sees) */ + atexit(profile_trace_shutdown); +} + +/* what follows the command word an expression starts with (past spaces and +an opening parenthesis, as the console and init.txt give it), else NULL */ +static char const *profile_console_word( + char const *expression, + char const *word) +{ + long length = (long)strlen(word); + long index; + + while (*expression == ' ' || *expression == '\t' || *expression == '(') + expression++; + for (index = 0; index < length; index++) + { + char character = expression[index] >= 'A' && expression[index] <= 'Z' ? expression[index] - 'A' + 'a' : expression[index]; + + if (character != word[index]) + return NULL; + } + expression += length; + if (*expression && *expression != ' ' && *expression != '\t' && *expression != ')') + return NULL; + while (*expression == ' ' || *expression == '\t') + expression++; + return expression; +} + +/* ---------- public code */ + +void profile_console_launch( + void) +{ + char const *when = config_string("debug.profile_record_when"); + + strcpy(profile_console_map, profile_console_next_map); + profile_console_install(); + profile_trace_thread_register(_profile_track_game); + if (strcmp(when, "start") != 0 && strcmp(when, "game") != 0) + platform_log("profile: debug.profile_record_when is \"%s\": \"start\" taken", when); + if (config_boolean("debug.profile_record")) + { + profile_trace_request_start(0.0, strcmp(when, "game") == 0 ? _profile_trace_when_game : _profile_trace_when_now, + config_integer("debug.profile_memory")); + } +} + +void profile_console_frame( + void) +{ + + strcpy(profile_console_map, profile_console_next_map); +} + +void profile_console_map_loaded( + char const *map_name) +{ + strncpy(profile_console_next_map, map_name ? map_name : "", sizeof(profile_console_next_map) - 1); + profile_trace_map_loaded(); +} + +/* Parse these before the script compiler: compiled map scripts index the +script function table, so profiling commands must not add entries to it. */ +boolean profile_console_command( + char const *expression) +{ + struct profile_trace_status status; + char const *rest; + + profile_console_install(); + if ((rest = profile_console_word(expression, "profile_record")) != NULL) + { + char *end; + double seconds = strtod(rest, &end); + + if (end == rest) + seconds = 0.0; + if ((*rest && end == rest) || seconds < 0.0 || + (*end && *end != ' ' && *end != '\t' && *end != ')')) + { + console_printf(FALSE, "profile: profile_record takes a number of seconds, 0 or more"); + return TRUE; + } + switch (profile_trace_request_start(seconds, _profile_trace_when_now, config_integer("debug.profile_memory"))) + { + case _profile_trace_answer_armed: + profile_trace_status(&status); + if (seconds > 0.0) + console_printf(FALSE, "profile: recording %s, %g s", status.name, seconds); + else + console_printf(FALSE, "profile: recording %s, until profile_stop", status.name); + break; + case _profile_trace_answer_still_writing: + profile_trace_status(&status); + console_printf(FALSE, "profile: still writing %s", status.name); + break; + default: + console_printf(FALSE, "profile: already recording"); + break; + } + return TRUE; + } + if (profile_console_word(expression, "profile_stop")) + { + profile_trace_status(&status); + switch (profile_trace_request_stop()) + { + case _profile_trace_answer_stopping: + console_printf(FALSE, "profile: writing %s, %ld parts", status.name, status.part); + break; + case _profile_trace_answer_disarmed: + console_printf(FALSE, "profile: not waiting for a game any more"); + break; + default: + console_printf(FALSE, "profile: not recording"); + break; + } + return TRUE; + } + return FALSE; +} + +#endif diff --git a/port/linux/game/profile_console.h b/port/linux/game/profile_console.h new file mode 100644 index 00000000..8502bc7f --- /dev/null +++ b/port/linux/game/profile_console.h @@ -0,0 +1,25 @@ +/* +PROFILE_CONSOLE.H + +Expose the game recording console hooks. +*/ + +#ifndef __PROFILE_CONSOLE_H +#define __PROFILE_CONSOLE_H +#pragma once + +#ifdef HALO_PROFILE + +/* profile_record [seconds], profile_stop: TRUE +when the expression was one of them (hs.c, before the script compiler) */ +boolean profile_console_command(char const *expression); +/* the first frame: the recording's seams, and the launch settings */ +void profile_console_launch(void); +/* each frame, right after the frame boundary */ +void profile_console_frame(void); +/* a map starts loading (game_load) */ +void profile_console_map_loaded(char const *map_name); + +#endif + +#endif diff --git a/port/linux/game/render_interpolation.c b/port/linux/game/render_interpolation.c index 8462295d..05aae5df 100644 --- a/port/linux/game/render_interpolation.c +++ b/port/linux/game/render_interpolation.c @@ -35,6 +35,7 @@ with their unit, and with what it rides. #include "cseries.h" #include "math/real_math.h" #include "objects/objects.h" +#include "camera/camera_scripting.h" #include "camera/director.h" #include "camera/observer.h" #include "cutscene/cinematics.h" @@ -726,6 +727,48 @@ struct observer_result const *render_interpolation_camera( render_interpolation_blended_camera(local_player_index, observer)); } +/* between two of the observer's results: the position, the axes (up kept +square to forward) and the field of view */ +static void observer_blend( + struct observer_result const *a, + struct observer_result const *b, + real t, + struct observer_result *result) +{ + real along, length; + + *result = *b; + point_lerp(&a->position, &b->position, t, &result->position); + vector_nlerp(&a->forward, &b->forward, t, &result->forward); + vector_nlerp(&a->up, &b->up, t, &result->up); + along = result->up.i * result->forward.i + result->up.j * result->forward.j + result->up.k * result->forward.k; + result->up.i -= result->forward.i * along; + result->up.j -= result->forward.j * along; + result->up.k -= result->forward.k * along; + length = vector_length(&result->up); + if (length > 1e-6f) + { + result->up.i /= length; + result->up.j /= length; + result->up.k /= length; + } + else + { + result->up = b->up; + } + result->field_of_view = lerp(a->field_of_view, b->field_of_view, t); +} + +/* a cut between two of the observer's results (so written that a position +or direction not a number cuts) */ +static boolean observer_cut( + struct observer_result const *a, + struct observer_result const *b) +{ + return !(distance_squared(&a->position, &b->position) <= CAMERA_CUT_DISTANCE * CAMERA_CUT_DISTANCE) || + !(a->forward.i * b->forward.i + a->forward.j * b->forward.j + a->forward.k * b->forward.k >= CAMERA_CUT_COSINE); +} + static struct observer_result const *render_interpolation_blended_camera( short local_player_index, struct observer_result const *observer) @@ -738,6 +781,42 @@ static struct observer_result const *render_interpolation_blended_camera( return observer; } camera = &interpolated_cameras[local_player_index]; + /* A scripted camera that moves with an object (a cinematic's camera + point relative to a lifepod or dropship, or a unit's eyes in one) is + placed by the object as it stands after the last tick. Blended from its + own snapshots (taken at the first frame after each tick, a different + part of a tick each tick), it did not follow the object as drawn, which + is blended from the object's snapshots, or steps from tick to tick when + its pose snaps: it ran up to 2 m ahead of or behind the object, out + through the lifepod's hull in a30. It is drawn where the observer has + it, moved with the object from where the object is to where it is + drawn. (An animated camera has no such object; a scripted first-person + camera looks along its unit's aim, which turns once a tick, and is + blended from its snapshots as the player's camera is.) */ + if (director_get_perspective(local_player_index) == _director_perspective_scripted) + { + long relative_object_index = scripted_camera_object_relative_to(); + real_matrix4x3 const *drawn; + + if (relative_object_index != NONE && + !scripted_camera_object_is_first_person_camera(relative_object_index) && + object_try_and_get_and_verify_type(relative_object_index, _object_mask_all) && + (drawn = render_interpolation_object_node_matrices(relative_object_index)) != NULL) + { + /* (as of the last tick: object_get_node_matrices gives the drawn + pose while a frame is drawn) */ + real_matrix4x3 const *nodes = (real_matrix4x3 const *)object_header_block_get( + relative_object_index, &object_get(relative_object_index)->object.node_matrices); + + camera->blended = *observer; + camera->blended.position.x += drawn[0].position.x - nodes[0].position.x; + camera->blended.position.y += drawn[0].position.y - nodes[0].position.y; + camera->blended.position.z += drawn[0].position.z - nodes[0].position.z; + /* (the snapshots start again after) */ + camera->valid = FALSE; + return &camera->blended; + } + } /* the observer as it stood after each tick (the first frame drawn after the tick) */ if (!camera->valid || camera->tick != interpolation_tick) @@ -751,22 +830,29 @@ static struct observer_result const *render_interpolation_blended_camera( camera->has_previous = camera->valid; camera->previous = camera->latest; camera->latest = *observer; + /* Several ticks since the last snapshot (a long frame): the objects + are drawn between the last two ticks, so the camera's previous is + where it was a tick ago, as nearly as a steady move from the last + snapshot tells. (Blended across all the ticks since, the camera + moved further each frame than the world it is in, out through a + Pelican's hull for a frame.) A cut stays a cut. */ + if (camera->has_previous && interpolation_tick - camera->tick > 1 && + !observer_cut(&camera->previous, &camera->latest)) + { + real ticks = (real)(interpolation_tick - camera->tick); + struct observer_result previous = camera->previous; + + observer_blend(&previous, &camera->latest, (ticks - 1.0f) / ticks, &camera->previous); + } camera->tick = interpolation_tick; camera->valid = TRUE; } - /* (so written that a position or direction not a number cuts) */ - if (!camera->has_previous || - !(distance_squared(&camera->previous.position, &camera->latest.position) <= - CAMERA_CUT_DISTANCE * CAMERA_CUT_DISTANCE) || - !(camera->previous.forward.i * camera->latest.forward.i + - camera->previous.forward.j * camera->latest.forward.j + - camera->previous.forward.k * camera->latest.forward.k >= CAMERA_CUT_COSINE)) + if (!camera->has_previous || observer_cut(&camera->previous, &camera->latest)) { return observer; } - camera->blended = camera->latest; - point_lerp(&camera->previous.position, &camera->latest.position, t, &camera->blended.position); + observer_blend(&camera->previous, &camera->latest, t, &camera->blended); if (correction_significant(&camera->correction) || correction_significant(&camera->correction_pending)) { real_vector3d drawn; @@ -776,31 +862,6 @@ static struct observer_result const *render_interpolation_blended_camera( camera->blended.position.y += drawn.j; camera->blended.position.z += drawn.k; } - vector_nlerp(&camera->previous.forward, &camera->latest.forward, t, &camera->blended.forward); - vector_nlerp(&camera->previous.up, &camera->latest.up, t, &camera->blended.up); - { - /* keep up perpendicular to forward */ - real_vector3d *forward = &camera->blended.forward; - real_vector3d *up = &camera->blended.up; - real along = up->i * forward->i + up->j * forward->j + up->k * forward->k; - real length; - - up->i -= forward->i * along; - up->j -= forward->j * along; - up->k -= forward->k * along; - length = vector_length(up); - if (length > 1e-6f) - { - up->i /= length; - up->j /= length; - up->k /= length; - } - else - { - *up = camera->latest.up; - } - } - camera->blended.field_of_view = lerp(camera->previous.field_of_view, camera->latest.field_of_view, t); return &camera->blended; } diff --git a/port/linux/game/stb_vorbis.c b/port/linux/game/stb_vorbis.c index 89c3f497..202f0b78 100644 --- a/port/linux/game/stb_vorbis.c +++ b/port/linux/game/stb_vorbis.c @@ -1243,12 +1243,18 @@ static int vorbis_validate(uint8 *data) // (formula implied by specification) static int lookup1_values(int entries, int dim) { - int r = (int) floor(exp((float) log((float) entries) / dim)); - if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; - ++r; // floor() to avoid _ftol() when non-CRT + int r; + // port: a code book of no dimensions or entries has no values (the + // division below is by zero) + if (dim <= 0 || entries <= 0) return -1; + r = (int) floor(exp((float) log((float) entries) / dim)); + // port: the powers compared as they are, not cast to int first (a large + // dim's are past an int, where the cast is undefined) + if (floor(pow((float) r+1, dim)) <= entries) + ++r; if (pow((float) r+1, dim) <= entries) return -1; - if ((int) floor(pow((float) r, dim)) > entries) + if (floor(pow((float) r, dim)) > entries) return -1; return r; } diff --git a/port/linux/game/tag_validate.c b/port/linux/game/tag_validate.c index 786f8f8b..dccfd832 100644 --- a/port/linux/game/tag_validate.c +++ b/port/linux/game/tag_validate.c @@ -61,6 +61,11 @@ enum /* how deep blocks go in a schema (the deepest are 6 or 7) */ MAXIMUM_VALIDATION_DEPTH = 32, + /* the longest a tag's name is, with its terminator (Halo's tools' paths: + the game's buffers for them, game_state.c's, objects.c's, are this + size) */ + MAXIMUM_TAG_NAME_LENGTH = 256, + /* how many corrections are logged one by one, for each map or bsp */ MAXIMUM_LOGGED_CORRECTIONS = 64, MAXIMUM_MESSAGE_LENGTH = 512, @@ -431,12 +436,18 @@ static boolean string_valid( /* (the name a nameless tag or reference is given) */ if (string == tag_validate_empty_name) return TRUE; + /* (and no longer than a tag's path is, MAXIMUM_TAG_NAME_LENGTH: the + game copies and formats names into buffers of that size) */ if (region_contains(validation, string, 1)) - return memchr(string, 0, validation->region_size - offset) != NULL; + { + return memchr(string, 0, MIN(validation->region_size - offset, (unsigned long)MAXIMUM_TAG_NAME_LENGTH)) != + NULL; + } if (tags && (unsigned long)string >= (unsigned long)tags && tags_offset < (unsigned long)tag_validate_globals.tag_data_size) { - return memchr(string, 0, tag_validate_globals.tag_data_size - tags_offset) != NULL; + return memchr(string, 0, MIN(tag_validate_globals.tag_data_size - tags_offset, + (unsigned long)MAXIMUM_TAG_NAME_LENGTH)) != NULL; } return FALSE; diff --git a/port/linux/game/tiff_file.c b/port/linux/game/tiff_file.c new file mode 100644 index 00000000..1609dab0 --- /dev/null +++ b/port/linux/game/tiff_file.c @@ -0,0 +1,24 @@ +/* +TIFF_FILE.C + +port: the builds leave out source/bitmaps/tiff_file.c and the libtiff it +writes with (port/linux/port.json). Its one function, tiff_export, is +called by main.c's screenshot_record: for the Xbox's movie recorder +(main_movie_start, which nothing starts) and for the screenshot_count and +screenshot_size globals (screenshot_render). Those read the back buffer's +memory, which the port draws into with OpenGL, never the CPU, so they never +had the picture; the port's screenshots are its own (the Screenshot key, +controls.screenshot, and debug.screenshot_every: d3d8_gl.c). +*/ + +#include "cseries/cseries.h" +#include "bitmaps/tiff_file.h" + +char const *tiff_export( + struct file_reference *file, + struct bitmap_data *bitmap) +{ + (void)file; + (void)bitmap; + return "this build writes no TIFF screenshots: use the Screenshot key (controls.screenshot)"; +} diff --git a/port/linux/game/touch_game.c b/port/linux/game/touch_game.c new file mode 100644 index 00000000..2d7b7e0d --- /dev/null +++ b/port/linux/game/touch_game.c @@ -0,0 +1,59 @@ +/* +TOUCH_GAME.C + +What the touch controls (port/linux/src/touch_input.c) need to know of the +game. The platform layer cannot see the game's types, so it asks here +(this file is compiled as the game's own sources are). +*/ + +#include "cseries.h" +#include "game/game.h" +#include "cutscene/cinematics.h" +#include "input/input.h" +#include "input/input_abstraction.h" +#include "interface/ui_widget.h" + +/* asks whether a cinematic is playing that A would skip +(player_control.c, player_control_action_test_check_reset_input_blob); tests +cinematic_globals first because the controller is read before the game exists +during input_initialize and shell_initialize, and game_initialize makes it +after the game time globals that game_in_progress reads (game.c) */ +int touch_game_cinematic_skippable(void) +{ + return cinematic_globals && game_in_progress() && cinematic_in_progress() && + cinematic_can_be_skipped(); +} + +/* asks whether a cinematic is playing, skippable or not: the player has no +controls, so the on-screen touch controls hide (touch_input.c) */ +int touch_game_cinematic_playing(void) +{ + return cinematic_globals && game_in_progress() && cinematic_in_progress(); +} + +/* asks whether a game is being played: not while the game starts, nor +between maps, nor at the main menu (whose ui.map is a game too), where the +on-screen touch controls do not show (touch_input.c); cinematic_globals +first, as touch_game_cinematic_skippable has it */ +int touch_game_playing(void) +{ + return cinematic_globals && game_in_progress() && !main_menu_is_active(); +} + +/* (touch_input.c's TOUCH_BUTTONS and the overlay count 16 buttons) */ +typedef char verify_touch_buttons[NUMBER_OF_GAMEPAD_BUTTONS == 16 ? 1 : -1]; + +/* the game control on each of player 1's 16 controller buttons, as the +gamepad's buttons are numbered (input.h), -1 for none: the touch controls +name their buttons by them, so a profile's own mapping shows */ +void touch_game_button_controls(int *controls) +{ + short buttons[NUMBER_OF_GAMEPAD_BUTTONS]; + short index; + + input_abstraction_port_button_controls(0, buttons); + for (index = 0; index < NUMBER_OF_GAMEPAD_BUTTONS; index++) + { + controls[index] = buttons[index]; + } +} diff --git a/port/linux/game/view_fov.c b/port/linux/game/view_fov.c new file mode 100644 index 00000000..3fb7c9de --- /dev/null +++ b/port/linux/game/view_fov.c @@ -0,0 +1,286 @@ +/* +VIEW_FOV.C + +The first-person view's field of view (display.fov, display.viewmodel_fov) +and whether the weapon is drawn (display.viewmodel_visible), the local +render view only: the observer, its transitions and what the network sends +keep the game's own camera. Each angle is horizontal at 16:9 (the vertical +angle is kept, so other shapes of window widen or narrow as the stock view +does), and 0 keeps the stock view. Called from the game's camera +(main.c's set_window_camera_values), its HUD (hud_draw.c) and the passes +that draw the first-person weapon (rasterizer and render sources). +*/ + +#include "cseries.h" +#include "view_fov.h" +#include "camera/director.h" +#include "cutscene/cinematics.h" +#include "game/players.h" +#include "items/weapons.h" +#include "objects/objects.h" +#include "rasterizer/rasterizer.h" +#include "rasterizer/xbox/rasterizer_xbox_internal.h" +#include "render/render.h" +#include "render/render_cameras.h" +#include "render/render_cameras_internal.h" +#include "units/unit_definitions.h" +#include "units/units.h" +#ifdef HALO_ANDROID +#include "halo_vr.h" +#endif + +#include + +/* (the window's rasterizer target, as the rasterizer's sources number it) */ +enum +{ + _rasterizer_target_render_primary = 0 +}; + +double config_real(const char *name); +int config_boolean(const char *name); +unsigned long config_changes(void); + +/* port: in VR the headset gives each eye's view (vr.c) and the weapon is +held in the hand: display.fov, display.viewmodel_fov and +display.viewmodel_visible do nothing */ +static boolean view_fov_vr(void) +{ +#ifdef HALO_ANDROID + return halo_vr_active() != 0; +#else + return FALSE; +#endif +} + +/* ---------- the world's field of view */ + +static real reticle_scales[MAXIMUM_LOCAL_PLAYERS]; +static real authored_vertical[MAXIMUM_LOCAL_PLAYERS]; + +/* the view's vertical angle for display.fov: the game's own in a vehicle, +dead, in a cinematic, a scripted camera or a view the director holds, and +at a scope's full zoom; between, the zoom's own transition */ +static real render_fov_adjust(short local_player_index, real native_vertical_field_of_view) +{ + static boolean initialized; + static unsigned long read_at; + static real requested_tangent; + unsigned long changes = config_changes(); + long unit_index, weapon_index; + struct unit_datum *unit; + real unzoomed_angle, unzoomed_tangent, native_tangent, adjusted_tangent; + + if (!initialized || read_at != changes) + { + double degrees = config_real("display.fov"); + + initialized = TRUE; + read_at = changes; + requested_tangent = degrees >= 20.0 && degrees <= 150.0 ? + (real)(tan(degrees * _pi / 360.0) * (9.0 / 16.0)) : 0.0f; + } + if (!requested_tangent || view_fov_vr() || local_player_index < 0 || local_player_index >= MAXIMUM_LOCAL_PLAYERS || + cinematic_in_progress() || (director_camera_scripted && *director_camera_scripted) || + director_get_perspective(local_player_index) != _director_perspective_first_person || + director_inhibited_facing(local_player_index)) + { + return native_vertical_field_of_view; + } + unit_index = player_control_get_unit_index(local_player_index); + if (unit_index == NONE) + return native_vertical_field_of_view; + unit = unit_try_and_get(unit_index); + if (!unit || unit->object.parent_object_index != NONE || TEST_FLAG(unit->object.damage_flags, _object_dead_bit)) + return native_vertical_field_of_view; + + unzoomed_angle = unit_definition_get(unit->definition_index)->unit.camera_field_of_view; + if (!(unzoomed_angle >= 0.001f && unzoomed_angle <= _pi / 2.0f) || + !(native_vertical_field_of_view > 0.0f && native_vertical_field_of_view < _pi)) + { + return native_vertical_field_of_view; + } + unzoomed_tangent = 0.75f * render_camera_get_adjusted_field_of_view_tangent(unzoomed_angle); + native_tangent = tanf(native_vertical_field_of_view * 0.5f); + if (requested_tangent < unzoomed_tangent) + { + /* (a view narrower than the stock one: the zoom narrows it further) */ + adjusted_tangent = native_tangent * (requested_tangent / unzoomed_tangent); + } + else + { + real blend = 1.0f; + + weapon_index = unit_inventory_get_weapon(unit_index, unit->unit.current_weapon_index); + if (weapon_index != NONE) + { + real scoped_angle = weapon_get_field_of_view(weapon_index, unzoomed_angle, 0); + + if (scoped_angle > 0.0f && scoped_angle < unzoomed_angle) + { + real scoped_tangent = 0.75f * render_camera_get_adjusted_field_of_view_tangent(scoped_angle); + /* (a scope's full zoom keeps its stock angle exactly: the + observer's arctangent and tangent can land just above it) */ + if (native_tangent <= scoped_tangent * 1.000001f) + return native_vertical_field_of_view; + + /* (the extra width fades out along the scope's own + transition, so every zoom level reached is the stock view) */ + blend = PIN((native_tangent - scoped_tangent) / + (unzoomed_tangent - scoped_tangent), 0.0f, 1.0f); + } + } + if (blend == 0.0f) + return native_vertical_field_of_view; + adjusted_tangent = native_tangent + (requested_tangent - unzoomed_tangent) * blend; + } + return 2.0f * atanf(adjusted_tangent); +} + +real render_fov_vertical(short local_player_index, real native_vertical_field_of_view) +{ + real adjusted = render_fov_adjust(local_player_index, native_vertical_field_of_view); + + if (local_player_index >= 0 && local_player_index < MAXIMUM_LOCAL_PLAYERS) + { + real scale = 1.0f; + + if (adjusted != native_vertical_field_of_view && + native_vertical_field_of_view > 0.0f && native_vertical_field_of_view < _pi && + adjusted > 0.0f && adjusted < _pi) + { + scale = tanf(native_vertical_field_of_view * 0.5f) / tanf(adjusted * 0.5f); + if (!(scale > 0.0f && scale < 1000.0f)) + scale = 1.0f; + } + /* (the view's projection, the zoom's transition too, for the HUD: + the weapon's own projection, viewmodel_projection_begin, is not it) */ + reticle_scales[local_player_index] = scale; + /* (a widened view's own angle, which the weapon keeps by default) */ + authored_vertical[local_player_index] = + adjusted != native_vertical_field_of_view ? native_vertical_field_of_view : 0.0f; + } + return adjusted; +} + +real render_fov_authored_vertical(short local_player_index) +{ + if (local_player_index < 0 || local_player_index >= MAXIMUM_LOCAL_PLAYERS) + return 0.0f; + return authored_vertical[local_player_index]; +} + +real render_fov_reticle_scale(short local_player_index) +{ + if (local_player_index < 0 || local_player_index >= MAXIMUM_LOCAL_PLAYERS || + reticle_scales[local_player_index] == 0.0f) + { + return 1.0f; + } + return reticle_scales[local_player_index]; +} + +/* ---------- the first-person weapon's */ + +/* how deep the weapon's passes nest, and whether the outermost changed the +view (the cameras and frustums it saved) */ +static unsigned long projection_depth; +static boolean projection_applied; +static struct render_camera saved_render_camera, saved_rasterizer_camera; +static struct render_frustum saved_render_frustum, saved_rasterizer_frustum; + +static real viewmodel_vertical(void) +{ + static boolean initialized; + static unsigned long read_at; + static real angle; + unsigned long changes = config_changes(); + + if (!initialized || read_at != changes) + { + double degrees = config_real("display.viewmodel_fov"); + + angle = degrees >= 20.0 && degrees <= 150.0 ? + (real)(2.0 * atan(tan(degrees * _pi / 360.0) * (9.0 / 16.0))) : 0.0f; + initialized = TRUE; + read_at = changes; + } + return angle; +} + +void viewmodel_projection_begin(void) +{ + real angle; + real_rectangle2d render_bounds, rasterizer_bounds; + + if (projection_depth++ != 0) + return; + projection_applied = FALSE; + if (view_fov_vr()) + return; + /* (display.viewmodel_fov's angle; by default the view's own, from before + display.fov widened it: arms and a gun right against the camera stretch + at a wide angle) */ + angle = viewmodel_vertical(); + if (!angle) + angle = render_fov_authored_vertical(render.local_player_index); + if (!angle || render.local_player_index < 0 || render.local_player_index >= MAXIMUM_LOCAL_PLAYERS || + global_window_parameters.rasterizer_target != _rasterizer_target_render_primary || + cinematic_in_progress() || (director_camera_scripted && *director_camera_scripted) || + director_get_perspective(render.local_player_index) != _director_perspective_first_person || + director_inhibited_facing(render.local_player_index)) + { + return; + } + + saved_render_camera = render.camera; + saved_render_frustum = render.frustum; + saved_rasterizer_camera = global_window_parameters.camera; + saved_rasterizer_frustum = global_window_parameters.frustum; + /* (the frustums' own bounds, the view's crop of a split screen: the + visibility and the projection change together, the weapon's pose and + markers do not; get_projection_bounds' slopes are not bounds) */ + render_bounds = saved_render_frustum.frustum_bounds; + rasterizer_bounds = saved_rasterizer_frustum.frustum_bounds; + render.camera.vertical_field_of_view = angle; + global_window_parameters.camera.vertical_field_of_view = angle; + render_camera_build_frustum(&render.camera, &render_bounds, &render.frustum, TRUE); + render_camera_build_frustum(&global_window_parameters.camera, &rasterizer_bounds, + &global_window_parameters.frustum, TRUE); + projection_applied = TRUE; + rasterizer_set_frustum_z(0.0f, 0.0f); +} + +void viewmodel_projection_end(void) +{ + if (!projection_depth || --projection_depth || !projection_applied) + return; + render.camera = saved_render_camera; + render.frustum = saved_render_frustum; + global_window_parameters.camera = saved_rasterizer_camera; + global_window_parameters.frustum = saved_rasterizer_frustum; + projection_applied = FALSE; + rasterizer_set_frustum_z(0.0f, 0.0f); +} + +/* ---------- whether the first-person weapon is drawn */ + +boolean viewmodel_is_visible(void) +{ + static boolean initialized, visible; + static unsigned long read_at; + unsigned long changes = config_changes(); + + if (!initialized || read_at != changes) + { + visible = config_boolean("display.viewmodel_visible") != 0; + initialized = TRUE; + read_at = changes; + } + return visible || view_fov_vr(); +} + +boolean viewmodel_draws_geometry(boolean first_person) +{ + return !first_person || viewmodel_is_visible(); +} diff --git a/port/linux/game/view_fov.h b/port/linux/game/view_fov.h new file mode 100644 index 00000000..6f410e0a --- /dev/null +++ b/port/linux/game/view_fov.h @@ -0,0 +1,31 @@ +/* +VIEW_FOV.H + +The first-person view's field of view and weapon (view_fov.c). +*/ + +#ifndef VIEW_FOV_H +#define VIEW_FOV_H + +/* the vertical angle the local player's world view is drawn at (display.fov, +after the observer and before the projection and the visibility frustum +are built), from the game's own */ +real render_fov_vertical(short local_player_index, real native_vertical_field_of_view); +/* how much the reticle shrinks or grows to stay on its aim at that view (the +last local view's own projection against the game's): 1 for the stock view */ +real render_fov_reticle_scale(short local_player_index); +/* the view's own vertical angle when display.fov widened it, else 0 */ +real render_fov_authored_vertical(short local_player_index); + +/* the first-person weapon's projection (display.viewmodel_fov) around its +passes: balanced and nestable, the current local render view only */ +void viewmodel_projection_begin(void); +void viewmodel_projection_end(void); + +/* whether the first-person weapon, hands and attached visuals are drawn +(display.viewmodel_visible); its firing, sounds and lights go on either way */ +boolean viewmodel_is_visible(void); +/* whether a pass draws: not one of the first-person weapon's that is hidden */ +boolean viewmodel_draws_geometry(boolean first_person); + +#endif diff --git a/port/linux/include/halo_keyboard.h b/port/linux/include/halo_keyboard.h index e8a66d12..c642d0e5 100644 --- a/port/linux/include/halo_keyboard.h +++ b/port/linux/include/halo_keyboard.h @@ -34,6 +34,10 @@ enum halo_keyboard_action read from it) */ HALO_KEYBOARD_SCOREBOARD, HALO_KEYBOARD_PAUSE, + HALO_KEYBOARD_SCREENSHOT, + /* (voice chat's, which the game itself does not read: + halo_push_to_talk_held) */ + HALO_KEYBOARD_PUSH_TO_TALK, NUMBER_OF_HALO_KEYBOARD_ACTIONS }; @@ -41,5 +45,8 @@ enum halo_keyboard_action (a bit for each halo_keyboard_action): theirs is the first's; none while a menu or the console is up */ unsigned long halo_keyboard_actions(short controller_index); +/* whether voice chat's push to talk is held (controls.push_to_talk), in the +game and in the menus alike */ +int halo_push_to_talk_held(void); #endif diff --git a/port/linux/include/halo_linux_prefix.h b/port/linux/include/halo_linux_prefix.h index dc366045..0667a642 100644 --- a/port/linux/include/halo_linux_prefix.h +++ b/port/linux/include/halo_linux_prefix.h @@ -10,7 +10,7 @@ byte-for-byte identical to what the matching MSVC build compiles. #ifndef __HALO_LINUX_PREFIX_H #define __HALO_LINUX_PREFIX_H -#if !defined(__i386__) && !defined(HALO_ANDROID) +#if !defined(__i386__) && !defined(HALO_ARM64_GUEST) #error the Linux port targets 32-bit x86: game data structures assume 32-bit pointers #endif diff --git a/port/linux/include/halo_linux_source_fixups.h b/port/linux/include/halo_linux_source_fixups.h index d4586323..3b7ea4b5 100644 --- a/port/linux/include/halo_linux_source_fixups.h +++ b/port/linux/include/halo_linux_source_fixups.h @@ -1,22 +1,12 @@ /* HALO_LINUX_SOURCE_FIXUPS.H -Game-only workarounds for source that MSVC accepts but clang rejects, where -editing the source itself would change the byte-matched MSVC output (see -port/linux/README.md for how each was checked). +The port's functions that the game's sources call, declared for them. */ #ifndef __HALO_LINUX_SOURCE_FIXUPS_H #define __HALO_LINUX_SOURCE_FIXUPS_H -/* rasterizer.h declares rasterizer_debug_drawing_begin(boolean opaque) while -rasterizer_xbox_debug.h declares a second `long zbias` parameter, and -rasterizer_debug.c includes both and passes two arguments. MSVC tolerates -the mismatch; the definition ignores zbias. Adding the parameter to -rasterizer.h perturbs MSVC's register allocation elsewhere, so instead every -declaration and call collapses to the one-parameter form here. */ -#define rasterizer_debug_drawing_begin(opaque, ...) (rasterizer_debug_drawing_begin)(opaque) - /* frames between the 30 Hz ticks (port/linux/game/render_interpolation.c); the platform layer reads the display.interpolation setting */ struct observer_result; @@ -57,6 +47,8 @@ unsigned char custom_edition_vehicle_placement_allowed(struct scenario_object_da /* the width of the screen the game draws, 480 lines tall: the device's or the display's shape, or 640 (port/linux/src/d3d8_gl.c) */ long halo_screen_width(void); +/* the screen's pixels to the Xbox's one (port/linux/src/d3d8_gl.c) */ +float halo_screen_scale(void); /* takes up a new width between frames (F11); returns the width */ long halo_screen_commit(void); /* the shadow maps' pixels for each of their 128 texels each way, a power of diff --git a/port/linux/include/halo_port_capacity.h b/port/linux/include/halo_port_capacity.h index 0212aafc..868e2aa2 100644 --- a/port/linux/include/halo_port_capacity.h +++ b/port/linux/include/halo_port_capacity.h @@ -46,7 +46,7 @@ measurement). The desktop builds' cache is 256 MB, half their 512 MB memory window (port/linux/src/platform.h), whose pages are backed as they are used. Android's window is 128 MB, and its cache the Xbox's. */ -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST #define HALO_PORT_TEXTURE_CACHE_PAGE_COUNT 0x580 /* (0x580) */ #else #define HALO_PORT_TEXTURE_CACHE_PAGE_COUNT 0x4000 /* (0x580) */ @@ -62,7 +62,7 @@ over a map's (loose_sounds.c), are longer than the Xbox maps' and fill it (a campaign map's dialogue and music did, "SOUND CACHE BLOWN" in debug.txt). The desktop builds' cache is 16 MB; Android's window keeps the Xbox's. */ -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST #define HALO_PORT_SOUND_CACHE_SIZE 0x400000 /* (0x400000) */ #else #define HALO_PORT_SOUND_CACHE_SIZE 0x1000000 /* (0x400000) */ diff --git a/port/linux/include/halo_port_limits.h b/port/linux/include/halo_port_limits.h index 1a2f7eb4..3d2ef473 100644 --- a/port/linux/include/halo_port_limits.h +++ b/port/linux/include/halo_port_limits.h @@ -66,7 +66,7 @@ is newer (network_client_manager.c). A host advertises it, with its netcode, in its game's advertisement's reserved bytes (network_server_message_handler.c), which hosts built before there was a version send as zeros: version 0. Raise it with any change to what the machines send each other. */ -#define HALO_PORT_NETWORK_VERSION 24 +#define HALO_PORT_NETWORK_VERSION 25 /* ... the advertisement's reserved bytes: the version (a little-endian word), then flags */ #define HALO_PORT_ADVERTISED_VERSION_OFFSET 0 diff --git a/port/linux/include/halo_ui_pointer.h b/port/linux/include/halo_ui_pointer.h index bc7035a1..434ad2b3 100644 --- a/port/linux/include/halo_ui_pointer.h +++ b/port/linux/include/halo_ui_pointer.h @@ -1,9 +1,11 @@ /* HALO_UI_POINTER.H -The mouse in the menus of the desktop builds. While a menu is up, the mouse -is released and its pointer shows (port/linux/src/sdl_platform.c); each frame -the menus (source/interface/ui_widget.c) ask where it is, in their own 640x480 +The mouse in the menus of the desktop builds, and the touchscreen in those +of the Android build. While a menu is up, the mouse is released and its +pointer shows (port/linux/src/sdl_platform.c), or taps and drags go to the +menus (port/linux/src/touch_input.c); each frame the menus +(source/interface/ui_widget.c) ask where the pointer is, in their own 640x480 coordinates (port/linux/src/d3d8_gl.c undoes the letterbox, the scale and the widescreen centering), and what it did since. */ @@ -21,11 +23,26 @@ struct halo_ui_pointer unsigned char right_clicks; /* whole wheel notches, away from the user positive */ signed char wheel_steps; + /* nonzero when the pointer is the touchscreen, not a mouse: a drag and a + wheel notch are the same steps, but the menus treat them differently */ + unsigned char touch; + /* fingers down since the last read and where the latest went down; only + the touchscreen reports them (debug.touch_targets shows them) */ + unsigned char downs; + short down_x, down_y; }; -/* frees the mouse for the menus while menus_active, and captures it again -for aiming when not; while menus are active returns nonzero and what the -pointer did since the last call. Always 0 on Android. */ +/* gives the menus the pointer; frees the mouse for the menus while +menus_active and captures it for aiming when not (touchscreen: taps and +drags go to the menus while menus_active); returns nonzero while menus are +active and the pointer could be read */ int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer); +/* the open scoreboard's pointer (game_engine.c), offered (a network game's) +or not: 1 while a right click has freed it, with where it is and what it +did since the last call, in the screen's coordinates (the game's drawing, +not the menus' centered 640); 0 while it is not; -1 where there is none +(Android) */ +int halo_scoreboard_pointer_update(int offered, struct halo_ui_pointer *pointer); + #endif diff --git a/port/linux/include/profile_console_gametype.h b/port/linux/include/profile_console_gametype.h new file mode 100644 index 00000000..259cb397 --- /dev/null +++ b/port/linux/include/profile_console_gametype.h @@ -0,0 +1,30 @@ +/* +PROFILE_CONSOLE_GAMETYPE.H + +Name the current scenario and running game engine for recordings. +*/ + +#ifndef __PROFILE_CONSOLE_GAMETYPE_H +#define __PROFILE_CONSOLE_GAMETYPE_H + +static inline const char *profile_console_scenario_map(const char *pending, const char *current) +{ + return pending && pending[0] ? pending : current; +} + +/* (engine indices none, ctf, slayer, oddball, king, race; the check program +cannot include game_engine.h. The variant outlives the engine into campaign +and co-op, so it is read only while an engine runs) */ +static inline const char *profile_console_engine_gametype(int running, long engine) +{ + static const char *const names[] = { "none", "ctf", "slayer", "oddball", "king", "race" }; + if (!running) + return "campaign"; + if (engine == 0) + return "none"; + if (engine >= 1 && engine <= 5) + return names[engine]; + return "none"; +} + +#endif diff --git a/port/linux/include/profile_sections.h b/port/linux/include/profile_sections.h new file mode 100644 index 00000000..5184b872 --- /dev/null +++ b/port/linux/include/profile_sections.h @@ -0,0 +1,33 @@ +/* +PROFILE_SECTIONS.H + +The profiling build's CPU sections around the game's steps (configure.py +--profile, port/linux/src/profile_trace.c): in a normal build each wraps +its statement and leaves nothing else. +*/ + +#ifndef __PROFILE_SECTIONS_H +#define __PROFILE_SECTIONS_H + +#ifdef HALO_PROFILE + +#include "cseries/profile.h" + +/* these macros leave no tokens in a normal build: call sites have no trailing +semicolon. a call site may be an unbraced if-branch: in a normal build +profile_scope is the statement alone. */ +#define PROFILE_SECTION(variable, name) static struct profile_section variable = { name, NONE, TRUE }; +#define profile_scope_enter(variable) profile_enter(variable) +#define profile_scope_exit(variable) profile_exit(variable) +#define profile_scope(variable, statement) { profile_enter(variable) statement profile_exit(variable) } + +#else + +#define PROFILE_SECTION(variable, name) +#define profile_scope_enter(variable) +#define profile_scope_exit(variable) +#define profile_scope(variable, statement) statement + +#endif + +#endif diff --git a/port/linux/include/profile_trace.h b/port/linux/include/profile_trace.h new file mode 100644 index 00000000..324a95d2 --- /dev/null +++ b/port/linux/include/profile_trace.h @@ -0,0 +1,144 @@ +/* +PROFILE_TRACE.H + +Expose bounded CPU trace recording and session controls. +*/ + +#ifndef __PROFILE_TRACE_H +#define __PROFILE_TRACE_H + +#ifdef HALO_PROFILE + +/* included by C89 game units: keep declarations compatible with them. */ + +/* ---------- constants */ + +enum +{ + _profile_track_game = 0, + _profile_track_p2p, + NUMBER_OF_PROFILE_TRACKS, + + MAXIMUM_PROFILE_TRACE_NAMES = 512, + MAXIMUM_PROFILE_TRACE_DEPTH = 64, + MAXIMUM_PROFILE_TRACE_AGGREGATES = 32, + /* debug.profile_memory: both arenas together, megabytes */ + DEFAULT_PROFILE_TRACE_MEMORY = 256, + MINIMUM_PROFILE_TRACE_MEMORY = 4, + MAXIMUM_PROFILE_TRACE_MEMORY = 1024, +}; + +enum profile_trace_state +{ + _profile_trace_idle = 0, + _profile_trace_armed, + _profile_trace_recording, + _profile_trace_finishing, +}; + +/* when an armed recording starts: at the next frame, or once a game that is +not the main menu is in progress (debug.profile_record_when = "game") */ +enum profile_trace_when +{ + _profile_trace_when_now = 0, + _profile_trace_when_game, +}; + +enum profile_trace_stop_reason +{ + _profile_trace_stop_none = 0, + _profile_trace_stop_command, + _profile_trace_stop_seconds, + _profile_trace_stop_map_load, + _profile_trace_stop_exit, +}; + +enum profile_trace_answer +{ + _profile_trace_answer_armed = 0, + _profile_trace_answer_already_recording, + _profile_trace_answer_still_writing, + _profile_trace_answer_stopping, + _profile_trace_answer_disarmed, + _profile_trace_answer_not_recording, +}; + +/* ---------- structures */ + +/* (the recording's identity at start; players_most grows while it runs) */ +struct profile_trace_session +{ + char folder[260]; + char platform[16]; + char build[48]; + /* host, client or local */ + char role[8]; + /* a client's own machine index, -1 otherwise */ + long own_machine; + char map[64]; + char map_name[64]; + char gametype[16]; + long players; + long players_most; +}; + +struct profile_trace_status +{ + int state; + char name[64]; + long part; +}; + +/* ---------- prototypes/PROFILE_TRACE.C */ + +/* the seams: set once at start-up by the game (profile_console.c), and by +tools/profile_check.c with fakes */ +void profile_trace_set_clock(unsigned long long (*now_ns)(void)); +void profile_trace_set_track_lock(void (*lock)(void), void (*unlock)(void)); +void profile_trace_set_allocator(void *(*allocate)(unsigned long size), void (*release)(void *block)); +void profile_trace_set_log(void (*log)(const char *text)); +void profile_trace_set_notify(void (*notify)(const char *text)); +/* starts the writer of a new recording (profile_json_writer_start): 0 when +it cannot */ +void profile_trace_set_writer(int (*start)(void)); +/* whether a game that is not the main menu is in progress */ +void profile_trace_set_ready(int (*ready)(void)); +void profile_trace_set_session(void (*describe)(struct profile_trace_session *session)); +void profile_trace_set_session_sampler(void (*sample)(struct profile_trace_session *session)); + +/* which track the calling thread records on */ +void profile_trace_thread_register(int track); + +/* a name's id (the same name, the same id, for the whole run); an +aggregate name's scopes are summed per frame rather than recorded */ +int profile_trace_name(const char *name); +void profile_trace_name_aggregate(const char *name); + +void profile_trace_begin(int name); +void profile_trace_end(int name); + +/* the game thread, with no scope open: where a recording starts, is cut +and stops */ +void profile_trace_frame_boundary(void); +/* the game thread, at each game tick's start */ +void profile_trace_tick(void); + +/* the console's requests (the game thread), applied at the next frame +boundary; seconds 0 records until stopped */ +int profile_trace_request_start(double seconds, int when, long memory_megabytes); +int profile_trace_request_stop(void); +/* a map starts loading: a recording stops, but for the first load after +launch (the main menu, or init.txt's map) */ +void profile_trace_map_loaded(void); +/* the process ends: stops a recording, cuts its last part and waits for +the writer (main_exit, and atexit for the other ways out) */ +void profile_trace_shutdown(void); + +/* whether the tracks record now: changed under the track lock, so a p2p +pass sees one value */ +int profile_trace_recording(void); +void profile_trace_status(struct profile_trace_status *status); + +#endif + +#endif diff --git a/port/linux/port.json b/port/linux/port.json index 9ffc8ae8..b9dd6a6c 100644 --- a/port/linux/port.json +++ b/port/linux/port.json @@ -1,11 +1,11 @@ { "comment": "Native build inputs, shared by the Windows and Android builds. See port/linux/README.md.", "game": { - "comment": "The game: every C file under root but those excluded (zlib's and libtiff's own programs), built with these defines and include directories.", + "comment": "The game: every C file under root but those excluded (a folder ends in /: libtiff and the TIFF writer that uses it, port/linux/game/tiff_file.c has why; zlib's own programs), built with these defines and include directories.", "root": "source", "exclude": [ - "source/bitmaps/libtiff/mkg3states.c", - "source/bitmaps/libtiff/mkspans.c", + "source/bitmaps/libtiff/", + "source/bitmaps/tiff_file.c", "source/memory/zlib/contrib/asm386/gvmat32c.c", "source/memory/zlib/contrib/minizip/miniunz.c", "source/memory/zlib/contrib/minizip/minizip.c", diff --git a/port/linux/src/d3d8_gl.c b/port/linux/src/d3d8_gl.c index f550fa14..1d76acd8 100644 --- a/port/linux/src/d3d8_gl.c +++ b/port/linux/src/d3d8_gl.c @@ -26,6 +26,9 @@ Conventions carried over from the Xbox: #include "halo_ui_pointer.h" #include "halo_vr.h" #include "port_config.h" +#include "main/console.h" + +#include #include #include @@ -36,7 +39,7 @@ Conventions carried over from the Xbox: void d3d8_surface_initialize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height); void d3d8_surface_resize(D3DSurface *surface, D3DFORMAT format, unsigned long width, unsigned long height); -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* OpenGL ES 3 (port/android/README.md): the desktop formats, enumerants and entry points used below that ES lacks */ #define GL_BGRA GL_RGBA @@ -114,7 +117,7 @@ static const struct { { "off", _anti_aliasing_off, 0 }, { "fxaa", _anti_aliasing_fxaa, 0 }, -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* (SMAA's three passes and supersampling's four times the pixels are more than a phone's GPU has to spare: FXAA in SMAA's place, and none) */ { "smaa", _anti_aliasing_fxaa, 0 }, @@ -257,6 +260,14 @@ float halo_screen_pixel_scale(void) return screen_scale[1]; } +/* how many pixels the screen's targets draw to the Xbox's one, the larger +of the two ways (the screen effects' convolutions: rasterizer_xbox_screen_effect.c) */ +float halo_screen_scale(void) +{ + halo_screen_width(); + return screen_scale[0] > screen_scale[1] ? screen_scale[0] : screen_scale[1]; +} + /* display.shadow_resolution: the size the shadow maps are drawn at. Each object's shadow is drawn from above into a 128x128 map, blurred into @@ -352,12 +363,16 @@ struct vertex_shader_object /* a shader lit for each pixel failed to compile or link: lit as the vertex shader lights it from then on */ BOOL lighting_failed; -#ifndef HALO_ANDROID +#ifndef HALO_GLES /* the vertex array its draws last used, and the streams they had (setup_streams: the layout follows from the two) */ struct vertex_array_entry *vertex_array; unsigned long vertex_array_streams; #endif + /* which of shader[] and lit_shader[] have been compiled (bits 0 and 1, + and 2 and 3): one that failed stays 0 and is not compiled again at each + draw */ + unsigned char shaders_tried; }; /* ---------- programs */ @@ -437,6 +452,17 @@ static struct program_entry *program_buckets[PROGRAM_BUCKETS]; /* ---------- render targets */ +/* what a render target's scale follows (render_target_get): an entry whose +scale no longer fits is freed (render_targets_evict) */ +enum render_target_kind +{ + _render_target_plain, + /* the screen's targets and the secondary target: screen_scale's */ + _render_target_screen, + /* a shadow map: halo_shadow_map_scale's */ + _render_target_shadow_map, +}; + struct render_target_entry { struct render_target_entry *next; @@ -447,6 +473,7 @@ struct render_target_entry /* the back buffer or its depth buffer, which the 3D view is drawn into: multisampled with multisampling (render_target_multisample) */ BOOL screen_buffer; + enum render_target_kind kind; }; /* every draw looks up its targets and whether its textures are render @@ -473,9 +500,11 @@ struct framebuffer_entry static struct render_target_entry *render_targets; static struct framebuffer_entry *framebuffers; +static void render_targets_evict(void); + /* ---------- the device */ -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* Mobile drivers (Mali) keep every orphaned copy of a buffer until the GPU is done with it, so a large buffer orphaned each frame costs its size per frame in flight and more. Instead each frame streams into the next of a few @@ -490,7 +519,7 @@ vertices. */ #define INDEX_BUFFER_SIZE (8 * 1024 * 1024) #endif #define VISIBILITY_TEST_SLOTS 4096 -#ifdef HALO_ANDROID +#ifdef HALO_GLES #define VISIBILITY_QUERY GL_ANY_SAMPLES_PASSED #define VISIBILITY_ALL_SAMPLES 1000000 /* the most tests a frame reads back (the game submits a few hundred at most) */ @@ -539,7 +568,7 @@ struct gl_device GLuint vertex_array; GLuint stream_buffer; -#ifdef HALO_ANDROID +#ifdef HALO_GLES GLuint stream_buffers[STREAM_BUFFER_RING]; GLuint index_buffers[STREAM_BUFFER_RING]; BOOL stream_persistent[STREAM_BUFFER_RING]; @@ -557,6 +586,11 @@ struct gl_device (render_target_get), which its count is divided by */ float query_area[VISIBILITY_TEST_SLOTS]; GLuint active_query; + /* the query a test runs on until it ends (swapped into its slot, or with + the results mapped and the batch full, dropped): a slot of its own, so + that every slot, 0 too, is a test's (the game's lens flares number + theirs from 0) */ + GLuint scratch_query; BOOL visibility_test_active; /* (queries read on the CPU) each slot's latest count known, and whether its query has yet to be read: the game spins on a result it is told is @@ -565,7 +599,7 @@ struct gl_device lights makes hundreds of tests. */ GLuint visibility_known[VISIBILITY_TEST_SLOTS]; BOOL visibility_unread[VISIBILITY_TEST_SLOTS]; -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* with atomic counters: one counter per test, used as a ring */ GLuint visibility_counters; unsigned long counter_next; @@ -584,11 +618,21 @@ struct gl_device GLuint visibility_latest[VISIBILITY_TEST_SLOTS]; #else /* each test's latest result, which the GPU writes (as a query buffer) - when the test's draws are done: the game waits for results at the start - of the next frame, and a query would stop the CPU there until the GPU - had caught up */ + a frame after the test (visibility_copy_batch): the game waits for + results at the start of the next frame, and a query would stop the CPU + there until the GPU had caught up */ GLuint visibility_results_buffer; volatile GLuint *visibility_results; + /* the tests of this frame and of the one before, whose counts the GPU + is yet to copy into their slots; each batch keeps its own query objects, + which are not reused until their counts are copied */ + struct + { + GLuint queries[VISIBILITY_TEST_SLOTS]; + unsigned short slots[VISIBILITY_TEST_SLOTS]; + unsigned long count; + } visibility_batches[2]; + unsigned long visibility_batch; /* a pipeline flush every flush_every draws (draw_flush), 0 never */ unsigned long flush_every; unsigned long flush_draws; @@ -654,7 +698,7 @@ xgpu_gl_state_invalidate, after which every value is set again. Unknown values are all ones, which no real value matches (floats become NaN, which compares unequal to everything). */ -#ifdef HALO_ANDROID +#ifdef HALO_GLES struct attribute_pointer { GLuint buffer; @@ -743,7 +787,7 @@ static struct GLuint array_buffer; GLuint element_array_buffer; unsigned char attribute_enabled[XGPU_VERTEX_ATTRIBUTE_COUNT]; -#ifdef HALO_ANDROID +#ifdef HALO_GLES struct attribute_pointer attribute_pointers[XGPU_VERTEX_ATTRIBUTE_COUNT]; #else GLuint vertex_array; @@ -795,7 +839,7 @@ static int texture_slot(GLenum target) return target == GL_TEXTURE_CUBE_MAP ? 1 : target == GL_TEXTURE_3D ? 2 : 0; } -#ifdef HALO_ANDROID +#ifdef HALO_GLES static void state_texture(int unit, GLenum target, GLuint texture) { int slot = texture_slot(target); @@ -839,7 +883,7 @@ static void state_element_array_buffer(GLuint buffer) } } -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* enables the attribute, reading size elements of type from buffer: each vertex is stride bytes on from the one before it, starting at buffer_offset, with the attribute relative_offset bytes into it */ @@ -966,7 +1010,7 @@ static void state_attribute_value(GLuint index, const float *value) { unsigned char kind = value ? 0 : 1; -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (gl_state.attribute_enabled[index] != 0) { gl_state.attribute_enabled[index] = 0; @@ -1127,7 +1171,7 @@ GLuint xgpu_link_program(GLuint vertex_shader, GLuint fragment_shader, const cha return program; } -#ifndef HALO_ANDROID +#ifndef HALO_GLES static void GLAPIENTRY gl_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user) { @@ -1163,6 +1207,10 @@ static BOOL surface_is_shadow_map(const D3DSurface *surface) description.height == SHADOW_MAP_SIZE; } +/* the secondary render target, the size of the Xbox's (rasterizer_xbox.c) */ +#define SECONDARY_TARGET_WIDTH 320 +#define SECONDARY_TARGET_HEIGHT 240 + /* the targets render_target_get found last, by what it found them from: each draw asks again for the same two (bind_targets) */ #define RECENT_RENDER_TARGET_COUNT 4 @@ -1201,8 +1249,9 @@ static struct render_target_entry *render_target_get(const D3DSurface *surface) if (!surface || !surface->Data) return NULL; float scale[2] = { 1.0f, 1.0f }; + enum render_target_kind kind = _render_target_plain; - /* (the entries are never freed) */ + /* (an entry lives until its scale no longer fits: render_targets_evict) */ screen = halo_screen_width(); for (slot = 0; slot < RECENT_RENDER_TARGET_COUNT; slot++) { @@ -1215,17 +1264,23 @@ static struct render_target_entry *render_target_get(const D3DSurface *surface) } } surface_dimensions(surface, &width, &height, &depth); - /* the screen's targets are drawn at the screen's scale, and the shadow - maps at display.shadow_resolution's (halo_shadow_map_scale) */ - if (width == (unsigned long)halo_screen_width() && height == SCREEN_HEIGHT) + /* the screen's targets are drawn at the screen's scale, and so is the + secondary target (the reflections' and active camouflage's: its size is + rasterizer_xbox.c's), which keeps the game's 320x240 units, that its + shaders' constants are in, and gets the screen's pixels a unit; the + shadow maps at display.shadow_resolution's (halo_shadow_map_scale) */ + if ((width == (unsigned long)halo_screen_width() && height == SCREEN_HEIGHT) || + (!depth && width == SECONDARY_TARGET_WIDTH && height == SECONDARY_TARGET_HEIGHT)) { scale[0] = screen_scale[0]; scale[1] = screen_scale[1]; + kind = _render_target_screen; } else if (!depth && surface_is_shadow_map(surface)) { scale[0] = (float)halo_shadow_map_scale(); scale[1] = scale[0]; + kind = _render_target_shadow_map; } for (entry = *render_target_bucket(surface->Data); entry; entry = entry->next_in_bucket) { @@ -1243,6 +1298,7 @@ static struct render_target_entry *render_target_get(const D3DSurface *surface) entry->target.depth = depth; entry->target.scale[0] = scale[0]; entry->target.scale[1] = scale[1]; + entry->kind = kind; entry->target.gl_width = (unsigned long)(width * scale[0] + 0.5f); entry->target.gl_height = (unsigned long)(height * scale[1] + 0.5f); glGenTextures(1, &entry->target.texture); @@ -1557,7 +1613,7 @@ static void render_target_multisample(struct xgpu_render_target *target, int sam return; draw = target->depth ? framebuffer_find(0, target->multisample, TRUE) : framebuffer_find(target->multisample, 0, TRUE); -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* (ES blits into no multisampled framebuffer: it is cleared instead. A change of the setting is taken up between frames, and the frame clears the screen's targets before it draws) */ @@ -1685,7 +1741,7 @@ static void gl_initialize(void) glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor); -#ifdef HALO_ANDROID +#ifdef HALO_GLES { BOOL es32 = major > 3 || (major == 3 && minor >= 2); @@ -1726,7 +1782,7 @@ static void gl_initialize(void) #endif glGenVertexArrays(1, &device.vertex_array); glBindVertexArray(device.vertex_array); -#ifdef HALO_ANDROID +#ifdef HALO_GLES { int ring; @@ -1748,7 +1804,7 @@ static void gl_initialize(void) device.index_buffer = device.index_buffers[0]; } #endif -#ifndef HALO_ANDROID +#ifndef HALO_GLES glGenBuffers(1, &device.stream_buffer); glBindBuffer(GL_ARRAY_BUFFER, device.stream_buffer); glBufferData(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE, NULL, GL_STREAM_DRAW); @@ -1758,15 +1814,24 @@ static void gl_initialize(void) #endif glGenSamplers(D3DTSS_MAXSTAGES, device.samplers); glGenQueries(VISIBILITY_TEST_SLOTS, device.queries); -#ifndef HALO_ANDROID + glGenQueries(1, &device.scratch_query); +#ifndef HALO_GLES glGenBuffers(1, &device.visibility_results_buffer); glBindBuffer(GL_QUERY_BUFFER, device.visibility_results_buffer); glBufferStorage(GL_QUERY_BUFFER, VISIBILITY_TEST_SLOTS * sizeof(GLuint), NULL, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); device.visibility_results = glMapBufferRange(GL_QUERY_BUFFER, 0, VISIBILITY_TEST_SLOTS * sizeof(GLuint), GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - if (!device.visibility_results) + if (device.visibility_results) + { + glGenQueries(VISIBILITY_TEST_SLOTS, device.visibility_batches[0].queries); + glGenQueries(VISIBILITY_TEST_SLOTS, device.visibility_batches[1].queries); + } + else platform_log("cannot map the visibility test results; tests wait for the GPU"); + /* (unbound: a query read into a bound query buffer takes its pointer + for an offset into it) */ + glBindBuffer(GL_QUERY_BUFFER, 0); { long every = config_integer("debug.gpu_flush_draws"); const char *renderer = (const char *)glGetString(GL_RENDERER); @@ -1780,7 +1845,7 @@ static void gl_initialize(void) } } #endif -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (xgpu_capabilities.atomic_counters) { glGenBuffers(1, &device.visibility_counters); @@ -2011,8 +2076,10 @@ HRESULT WINAPI Direct3D_CreateDevice(UINT adapter, D3DDEVTYPE device_type, void /* a point in the window, as SDL reports it, in the menus' coordinates: the inverse of the letterboxed display blit at presentation, the screen's -width and the menus' centering (halo_screen_ui_offset) */ -static void ui_point_from_window(float window_x, float window_y, short *x, short *y) +width and the menus' centering (halo_screen_ui_offset); or, not centered, +the screen's (the game's drawing: the scoreboard's); x and y are -1 if there +is no back buffer or window yet */ +static void ui_point_from_window_on(float window_x, float window_y, int centered, short *x, short *y) { struct render_target_entry *back_buffer = render_target_get(&device.back_buffer); int window_width, window_height, pixel_width, pixel_height, width, height, left, top; @@ -2036,56 +2103,63 @@ static void ui_point_from_window(float window_x, float window_y, short *x, short top = (pixel_height - height) / 2; screen_x = (window_x * pixel_width / window_width - left) * (float)back_buffer->target.width / (float)width; screen_y = (window_y * pixel_height / window_height - top) * (float)back_buffer->target.height / (float)height; - *x = (short)floorf(screen_x - (float)(halo_screen_width() - 640) / 2.0f); + *x = (short)floorf(screen_x - (centered ? (float)(halo_screen_width() - 640) / 2.0f : 0.0f)); *y = (short)floorf(screen_y); } -#ifdef HALO_ANDROID -BOOL platform_flat_mouse(float *x, float *y); - -/* the controller pointing at the menus' screen in VR (halo_vr_menu_pointer); -flat (the emulator), the mouse */ -int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer) +static void ui_point_from_window(float window_x, float window_y, short *x, short *y) { - int x, y, click_x, click_y, moved, clicks; + ui_point_from_window_on(window_x, window_y, TRUE, x, y); +} - platform_menus_set_active(menus_active != 0); - if (menus_active && !halo_vr_active() && device.gl_ready) - { - static BOOL was_down; - static short last_x, last_y; - float window_x, window_y; - BOOL down = platform_flat_mouse(&window_x, &window_y); - - memset(pointer, 0, sizeof(*pointer)); - ui_point_from_window(window_x, window_y, &pointer->x, &pointer->y); - pointer->click_x = pointer->x; - pointer->click_y = pointer->y; - pointer->moved = pointer->x != last_x || pointer->y != last_y; - pointer->left_clicks = (unsigned char)(down && !was_down); - last_x = pointer->x; - last_y = pointer->y; - was_down = down; - return 1; - } - if (!menus_active || !halo_vr_menu_pointer(&x, &y, &click_x, &click_y, &moved, &clicks)) - return 0; - memset(pointer, 0, sizeof(*pointer)); - pointer->x = (short)(x - (halo_screen_width() - 640) / 2); - pointer->y = (short)y; - pointer->click_x = (short)(click_x - (halo_screen_width() - 640) / 2); - pointer->click_y = (short)click_y; - pointer->moved = (unsigned char)(moved != 0); - pointer->left_clicks = (unsigned char)(clicks < 255 ? clicks : 255); - return 1; +#ifdef HALO_ANDROID +int halo_scoreboard_pointer_update(int offered, struct halo_ui_pointer *pointer) +{ + (void)offered; + (void)pointer; + return -1; } #else +int halo_scoreboard_pointer_update(int offered, struct halo_ui_pointer *pointer) +{ + struct platform_ui_pointer state; + + memset(pointer, 0, sizeof(*pointer)); + if (!platform_scoreboard_pointer(offered != 0, &state) || !device.gl_ready) + return 0; + ui_point_from_window_on(state.x, state.y, FALSE, &pointer->x, &pointer->y); + ui_point_from_window_on(state.click_x, state.click_y, FALSE, &pointer->click_x, &pointer->click_y); + pointer->moved = state.moved != FALSE; + pointer->left_clicks = (unsigned char)(state.left_clicks < 255 ? state.left_clicks : 255); + return 1; +} +#endif int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer) { struct platform_ui_pointer state; platform_menus_set_active(menus_active != 0); +#ifdef HALO_ANDROID + /* port: in VR the controller points at the menus' screen + (halo_vr_menu_pointer), and the touchscreen is not the pointer */ + if (halo_vr_active()) + { + int x, y, click_x, click_y, moved, clicks; + + platform_ui_pointer_set_active(FALSE); + if (!menus_active || !halo_vr_menu_pointer(&x, &y, &click_x, &click_y, &moved, &clicks)) + return 0; + memset(pointer, 0, sizeof(*pointer)); + pointer->x = (short)(x - (halo_screen_width() - 640) / 2); + pointer->y = (short)y; + pointer->click_x = (short)(click_x - (halo_screen_width() - 640) / 2); + pointer->click_y = (short)click_y; + pointer->moved = (unsigned char)(moved != 0); + pointer->left_clicks = (unsigned char)(clicks < 255 ? clicks : 255); + return 1; + } +#endif platform_ui_pointer_set_active(menus_active != 0); if (!menus_active || !device.gl_ready || !platform_ui_pointer_read(&state)) return 0; @@ -2096,9 +2170,11 @@ int halo_ui_pointer_update(int menus_active, struct halo_ui_pointer *pointer) pointer->left_clicks = (unsigned char)(state.left_clicks < 255 ? state.left_clicks : 255); pointer->right_clicks = (unsigned char)(state.right_clicks < 255 ? state.right_clicks : 255); pointer->wheel_steps = (signed char)(state.wheel_steps < -8 ? -8 : state.wheel_steps > 8 ? 8 : state.wheel_steps); + pointer->touch = state.touch != FALSE; + pointer->downs = (unsigned char)(state.downs < 255 ? state.downs : 255); + ui_point_from_window(state.down_x, state.down_y, &pointer->down_x, &pointer->down_y); return 1; } -#endif /* takes up the display's shape and resolution, or the window's, if they have changed; between frames, since the game's layout and the targets must @@ -2112,7 +2188,7 @@ long halo_screen_commit(void) supersampling changes the scale below) */ anti_aliasing_read(); /* display.shadow_resolution, if it has changed: the maps' entries at - the old scale stay (render_target_get), but are no longer found */ + the old scale are freed (render_targets_evict), new ones made at the new */ if (shadow_scale && shadow_scale_read_at != config_changes()) { long shadow = shadow_scale_choose(); @@ -2122,7 +2198,7 @@ long halo_screen_commit(void) { platform_log("shadow maps: %ldx%ld", SHADOW_MAP_SIZE * shadow, SHADOW_MAP_SIZE * shadow); shadow_scale = shadow; - memset(recent_render_targets, 0, sizeof(recent_render_targets)); + render_targets_evict(); } } if (!screen_width) @@ -2143,6 +2219,8 @@ long halo_screen_commit(void) d3d8_surface_resize(&device.depth_buffer, D3DFMT_LIN_D24S8, (unsigned long)width, SCREEN_HEIGHT); } #endif + /* (the entries of the old size and scale, which nothing finds now) */ + render_targets_evict(); } return screen_width; } @@ -2279,7 +2357,7 @@ void WINAPI D3DDevice_BeginVisibilityTest(void) return; /* the query object is chosen when the test ends; use a scratch one */ device.visibility_test_active = TRUE; -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (xgpu_capabilities.atomic_counters) { const GLuint zero = 0; @@ -2296,8 +2374,20 @@ void WINAPI D3DDevice_BeginVisibilityTest(void) } return; } +#else + if (device.visibility_results) + { + unsigned long count = device.visibility_batches[device.visibility_batch].count; + + /* the batch's next query, or (with the batch full) a scratch one + whose count is dropped */ + device.active_query = count < VISIBILITY_TEST_SLOTS ? + device.visibility_batches[device.visibility_batch].queries[count] : device.scratch_query; + glBeginQuery(VISIBILITY_QUERY, device.active_query); + return; + } #endif - glBeginQuery(VISIBILITY_QUERY, device.queries[0]); + glBeginQuery(VISIBILITY_QUERY, device.scratch_query); } HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) @@ -2308,9 +2398,7 @@ HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) return S_OK; device.visibility_test_active = FALSE; index %= VISIBILITY_TEST_SLOTS; - if (!index) - index = 1; -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (xgpu_capabilities.atomic_counters) { unsigned long *count = &device.frame_test_count[device.buffer_ring]; @@ -2321,7 +2409,11 @@ HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) { device.frame_tests[device.buffer_ring][*count].slot = (unsigned short)index; device.frame_tests[device.buffer_ring][*count].counter = (unsigned short)device.counter_active; +#ifdef HALO_ANDROID device.frame_tests[device.buffer_ring][*count].views = (unsigned short)(multiview.active ? 2 : 1); +#else + device.frame_tests[device.buffer_ring][*count].views = 1; +#endif (*count)++; } return S_OK; @@ -2334,26 +2426,31 @@ HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index) (lens flares, rasterizer_lights.c), a split-screen window's or the screen's alike */ device.query_area[index] = target_scale[0] * target_scale[1] * (float)target_samples; +#ifndef HALO_GLES + if (device.visibility_results) + { + /* the GPU copies the count into the slot at the next frame's end + (visibility_copy_batch) */ + unsigned long *count = &device.visibility_batches[device.visibility_batch].count; + + if (*count < VISIBILITY_TEST_SLOTS) + { + device.visibility_batches[device.visibility_batch].slots[(*count)++] = (unsigned short)index; + device.query_pending[index] = TRUE; + } + return S_OK; + } +#endif /* swap the scratch query into the requested slot */ - scratch = device.queries[0]; - device.queries[0] = device.queries[index]; + scratch = device.scratch_query; + device.scratch_query = device.queries[index]; device.queries[index] = scratch; device.query_pending[index] = TRUE; device.visibility_unread[index] = TRUE; -#ifndef HALO_ANDROID - if (device.visibility_results) - { - /* the GPU writes the count into the slot once it is known (given - by name: Mesa's GL thread waits for everything before a - glGetQueryObjectuiv, even one into a bound buffer) */ - glGetQueryBufferObjectuiv(device.queries[index], device.visibility_results_buffer, GL_QUERY_RESULT, - (GLintptr)(index * sizeof(GLuint))); - } -#endif return S_OK; } -#ifndef HALO_ANDROID +#ifndef HALO_GLES /* a count of pixels in the game's pixels */ static GLuint visibility_unscaled(GLuint samples, DWORD index) { @@ -2363,7 +2460,7 @@ static GLuint visibility_unscaled(GLuint samples, DWORD index) } #endif -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* at Present: snapshots this frame's counters, and takes the counts of the frame the ring comes back to, which host_gl_wait_frame has seen finish */ static void visibility_frame_end(void) @@ -2402,15 +2499,13 @@ HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULON if (time_stamp) *time_stamp = 0; index %= VISIBILITY_TEST_SLOTS; - if (!index) - index = 1; if (!device.gl_ready || !device.query_pending[index]) { if (result) *result = 0; return S_OK; } -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (xgpu_capabilities.atomic_counters) { /* the count of the slot's test a few frames ago, which the GPU has @@ -2421,7 +2516,7 @@ HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULON return S_OK; } #endif -#ifndef HALO_ANDROID +#ifndef HALO_GLES if (device.visibility_results) { /* the latest count the GPU has written: from this test, or while @@ -2439,7 +2534,7 @@ HRESULT WINAPI D3DDevice_GetVisibilityTestResult(DWORD index, UINT *result, ULON if (available) { glGetQueryObjectuiv(device.queries[index], GL_QUERY_RESULT, &samples); -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES only says whether any sample passed. The game divides the count by the test's area (lens flare brightness, rasterizer_lights.c): report more than any test covers, well @@ -2803,12 +2898,14 @@ static GLuint vertex_shader_get(struct vertex_shader_object *program, BOOL immed { int variant = (immediate ? 1 : 0) + (multiview_view ? 2 : 0); GLuint *shader = lit ? &program->lit_shader[variant] : &program->shader[variant]; + unsigned char tried = (unsigned char)(1 << (variant + (lit ? 2 : 0))); - if (!*shader) + if (!*shader && !(program->shaders_tried & tried)) { char *source = nv2a_vertex_shader_to_glsl(program->instructions, program->instruction_count, immediate ? 0 : device.vertex_shader->packed_mask, lit ? &program->lighting : NULL, multiview_view); + program->shaders_tried |= tried; *shader = xgpu_compile_shader(GL_VERTEX_SHADER, source, "vertex"); if (!program->projection_known) { @@ -2991,7 +3088,7 @@ static GLenum address_mode(DWORD mode) { case D3DTADDRESS_MIRROR: return GL_MIRRORED_REPEAT; case D3DTADDRESS_CLAMP: return GL_CLAMP_TO_EDGE; -#ifdef HALO_ANDROID +#ifdef HALO_GLES case D3DTADDRESS_BORDER: return xgpu_capabilities.border_clamp ? GL_CLAMP_TO_BORDER : GL_CLAMP_TO_EDGE; #else case D3DTADDRESS_BORDER: return GL_CLAMP_TO_BORDER; @@ -3037,7 +3134,7 @@ static void sampler_parameters(GLuint sampler, const DWORD *inputs) glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, (GLint)address_mode(inputs[3])); glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, (GLint)address_mode(inputs[4])); glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, (GLint)address_mode(inputs[5])); -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES has no sampler LOD bias; the pixel shader applies it (texture_lod_bias) */ (void)lod_bias; @@ -3147,7 +3244,108 @@ struct mip_composite static struct mip_composite *mip_composites; -#ifdef HALO_ANDROID +/* an entry render_target_get no longer finds: the screen's targets at +another scale or screen width than now, the shadow maps at another +display.shadow_resolution */ +static BOOL render_target_stale(const struct render_target_entry *entry) +{ + switch (entry->kind) + { + case _render_target_screen: + return entry->target.scale[0] != screen_scale[0] || entry->target.scale[1] != screen_scale[1] || + (entry->target.height == SCREEN_HEIGHT && entry->target.width != (unsigned long)halo_screen_width()); + case _render_target_shadow_map: + return entry->target.scale[0] != (float)halo_shadow_map_scale(); + default: + return FALSE; + } +} + +/* frees the framebuffers made of a texture, or with renderbuffers, of a +multisampled renderbuffer, which is going */ +static void framebuffers_forget(GLuint attachment, BOOL renderbuffers) +{ + struct framebuffer_entry *entry, **link = &framebuffers; + + while ((entry = *link) != NULL) + { + if (entry->renderbuffers == renderbuffers && (entry->color == attachment || entry->depth == attachment)) + { + *link = entry->next; + glDeleteFramebuffers(1, &entry->framebuffer); + free(entry); + } + else + { + link = &entry->next; + } + } +} + +/* frees an entry taken out of render_targets: its GL objects, the +framebuffers made of them, and what remembered them */ +static void render_target_delete(struct render_target_entry *entry) +{ + struct render_target_entry **link; + struct mip_composite *composite; + + for (link = render_target_bucket(entry->target.data); *link != entry; link = &(*link)->next_in_bucket) + ; + *link = entry->next_in_bucket; + framebuffers_forget(entry->target.texture, FALSE); + if (entry->target.multisample) + { + framebuffers_forget(entry->target.multisample, TRUE); + glDeleteRenderbuffers(1, &entry->target.multisample); + } + /* (a composite that copied from the texture copies again, rather than + take a new texture with the same name for it) */ + for (composite = mip_composites; composite; composite = composite->next) + { + unsigned long level; + + for (level = 0; level < composite->rendered_levels; level++) + { + if (composite->level_sources[level] == entry->target.texture) + { + composite->rendered_levels = 0; + break; + } + } + } + glDeleteTextures(1, &entry->target.texture); + free(entry); +} + +/* (between frames, when the screen's or the shadow maps' scale has +changed: halo_screen_commit) frees the entries render_target_get no longer +finds, which a frame at the old scale left */ +static void render_targets_evict(void) +{ + struct render_target_entry *entry, **link = &render_targets; + BOOL any = FALSE; + + while ((entry = *link) != NULL) + { + if (render_target_stale(entry)) + { + *link = entry->next; + render_target_delete(entry); + any = TRUE; + } + else + { + link = &entry->next; + } + } + if (any) + { + memset(recent_render_targets, 0, sizeof(recent_render_targets)); + xgpu_gl_state_invalidate(); + } +} + +#ifdef HALO_GLES static GLuint framebuffer_get(GLuint color, GLuint depth); /* glCopyImageSubData for ES 3.0/3.1 contexts without the extension */ @@ -3235,7 +3433,7 @@ static GLuint mip_composite_get(const struct xgpu_texture_description *descripti composite->level_sources[level] = target->texture; composite->level_written[level] = target->written; render_target_resolve(target); -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (!xgpu_capabilities.copy_image) { copy_level_by_blit(target->texture, composite->texture, (GLint)level, width, height); @@ -3345,7 +3543,7 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale gl_target == GL_TEXTURE_3D ? _xgpu_sampler_3d : _xgpu_sampler_2d; } } -#ifdef HALO_ANDROID +#ifdef HALO_GLES for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++) state_texture(stage, gl_targets[stage], gl_textures[stage]); #else @@ -3540,7 +3738,7 @@ static void apply_raster_state(BOOL has_depth) state_enable(&gl_state.cull_face, GL_CULL_FACE, rs[D3DRS_CULLMODE] != D3DCULL_NONE); if (rs[D3DRS_CULLMODE] != D3DCULL_NONE) { -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* the vertex shader flips y in clip space, which (unlike desktop GL's upper-left clip origin) also flips the winding */ GLenum front_face = rs[D3DRS_FRONTFACE] == D3DFRONT_CCW ? GL_CW : GL_CCW; @@ -3562,7 +3760,7 @@ static void apply_raster_state(BOOL has_depth) glCullFace(cull_mode); } } -#ifndef HALO_ANDROID +#ifndef HALO_GLES /* ES draws filled polygons only (wireframe is a debug mode) */ { GLenum polygon_mode = rs[D3DRS_FILLMODE] == D3DFILL_WIREFRAME ? GL_LINE : @@ -3578,7 +3776,7 @@ static void apply_raster_state(BOOL has_depth) /* D3DRS_ZBIAS is expressed in these states (D3DDevice_SetRenderState_ZBias) */ state_enable(&gl_state.offset_fill, GL_POLYGON_OFFSET_FILL, rs[D3DRS_SOLIDOFFSETENABLE] != 0); -#ifndef HALO_ANDROID +#ifndef HALO_GLES state_enable(&gl_state.offset_line, GL_POLYGON_OFFSET_LINE, rs[D3DRS_SOLIDOFFSETENABLE] != 0); #endif if (rs[D3DRS_SOLIDOFFSETENABLE]) @@ -3595,7 +3793,7 @@ static void apply_raster_state(BOOL has_depth) } } -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES has no debug callback in 3.0; debug.gl_debug polls glGetError around each draw instead, reporting each distinct error a few times */ static void gl_check_errors(const char *where) @@ -3653,7 +3851,7 @@ apply to the others. A memory barrier is one (and only that: nothing here writes images). */ static void draw_flush(void) { -#ifndef HALO_ANDROID +#ifndef HALO_GLES if (device.flush_every && ++device.flush_draws >= device.flush_every) { device.flush_draws = 0; @@ -3741,14 +3939,14 @@ static struct program_entry *prepare_draw(BOOL immediate) D3D__RenderState[D3DRS_DESTBLEND] == D3DBLEND_SRCALPHA; key.point_threshold = key.point_threshold && key.coverage_alpha; key.alpha_test_function = D3D__RenderState[D3DRS_ALPHATESTENABLE] ? D3D__RenderState[D3DRS_ALPHAFUNC] : 0; -#ifndef HALO_ANDROID +#ifndef HALO_GLES /* (gl_SampleMask: ES has it only from 3.2) */ if (target_samples > 1 && key.alpha_test_function && !D3D__RenderState[D3DRS_ALPHABLENDENABLE]) key.alpha_test_samples = (unsigned char)target_samples; #endif key.fog_enable = D3D__RenderState[D3DRS_FOGENABLE] != 0; key.fog_table_mode = (unsigned char)D3D__RenderState[D3DRS_FOGTABLEMODE]; -#ifdef HALO_ANDROID +#ifdef HALO_GLES key.count_samples = device.visibility_test_active && xgpu_capabilities.atomic_counters; #endif @@ -3817,6 +4015,8 @@ static struct program_entry *prepare_draw(BOOL immediate) depth[3] = 0.0f; uniform_vec4(entry->mv_depth, entry->mv_depth_value, depth, 1); } +#endif +#ifdef HALO_GLES if (key.count_samples) glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, device.visibility_counters, (GLintptr)(device.counter_active * sizeof(GLuint)), sizeof(GLuint)); @@ -4158,6 +4358,10 @@ static struct /* the page's memory_watch generation when it was uploaded */ unsigned long generation[MIRROR_PAGE_COUNT]; unsigned long rewritten_frame[MIRROR_PAGE_COUNT]; +#ifdef HALO_GLES + /* device.frame at the segment's latest glBufferSubData */ + unsigned long subdata_frame[MIRROR_SEGMENT_COUNT]; +#endif } mirror; /* uploads the pages of [first, last) that are absent or stale; FALSE if one @@ -4240,17 +4444,25 @@ static BOOL mirror_refresh(unsigned long first, unsigned long last) { glGenBuffers(1, &mirror.buffers[segment]); glBindBuffer(GL_COPY_WRITE_BUFFER, mirror.buffers[segment]); -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (!host_gl_buffer_storage(GL_COPY_WRITE_BUFFER, MIRROR_SEGMENT_SIZE)) #endif glBufferData(GL_COPY_WRITE_BUFFER, MIRROR_SEGMENT_SIZE, NULL, GL_DYNAMIC_DRAW); +#ifdef HALO_GLES + /* (no glBufferSubData yet: as if long enough ago) */ + mirror.subdata_frame[segment] = device.frame - STREAM_BUFFER_RING; +#endif } glBindBuffer(GL_COPY_WRITE_BUFFER, mirror.buffers[segment]); -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* Mali copies the whole buffer for a glBufferSubData that queued draws might read (see STREAM_BUFFER_RING); unused pages can be - written without waiting for them */ - if (unused) + written without waiting for them. Not while such a copy may be + pending: it gives the buffer new storage at once but fills it on + the GPU's timeline, over what was written into it meanwhile (a level + loaded after another drew some of the earlier level's vertices). + The GPU is done with a frame's work STREAM_BUFFER_RING frames on */ + if (unused && device.frame - mirror.subdata_frame[segment] >= STREAM_BUFFER_RING) { unsigned int offset = (unsigned int)(address - PLATFORM_CONTIGUOUS_BASE - segment * MIRROR_SEGMENT_SIZE); @@ -4258,6 +4470,7 @@ static BOOL mirror_refresh(unsigned long first, unsigned long last) host_gl_buffer_write(GL_COPY_WRITE_BUFFER, offset, (unsigned int)size, (const void *)address); continue; } + mirror.subdata_frame[segment] = device.frame; #else (void)unused; #endif @@ -4378,7 +4591,7 @@ static void stream_reserve(unsigned long size) { if (device.stream_offset + size > STREAM_BUFFER_SIZE) { -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* persistent storage cannot be orphaned: wait for the GPU to be done with it (a frame should never stream this much) */ if (device.stream_persistent[device.buffer_ring]) @@ -4398,22 +4611,24 @@ static void stream_reserve(unsigned long size) static unsigned long stream_upload(const void *data, unsigned long size) { unsigned long offset; + /* (as index_upload: the vertices' own bytes, in room rounded up to 16) */ + unsigned long length = size; size = (size + 15) & ~15UL; stream_reserve(size); offset = device.stream_offset; state_array_buffer(device.stream_buffer); -#ifdef HALO_ANDROID - if (!host_gl_persistent_write(device.stream_buffer, (unsigned int)offset, (unsigned int)size, data)) - host_gl_buffer_write(GL_ARRAY_BUFFER, (unsigned int)offset, (unsigned int)size, data); +#ifdef HALO_GLES + if (!host_gl_persistent_write(device.stream_buffer, (unsigned int)offset, (unsigned int)length, data)) + host_gl_buffer_write(GL_ARRAY_BUFFER, (unsigned int)offset, (unsigned int)length, data); #else - buffer_upload(GL_ARRAY_BUFFER, offset, size, data); + buffer_upload(GL_ARRAY_BUFFER, offset, length, data); #endif device.stream_offset += size; return offset; } -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* stream_upload, with the D3DCOLOR elements of the stream turned from BGRA into the RGBA byte order ES reads */ static unsigned long stream_upload_swizzled(const struct vertex_shader_object *declaration, unsigned long stream, @@ -4458,12 +4673,16 @@ static unsigned long stream_upload_swizzled(const struct vertex_shader_object *d static unsigned long index_upload(const void *data, unsigned long size) { unsigned long offset; + /* (the indices' own bytes are written; the room they take is rounded up + to 16, for the next ones' alignment: rounding what was read too read + past the caller's indices) */ + unsigned long length = size; size = (size + 15) & ~15UL; state_element_array_buffer(device.index_buffer); if (device.index_offset + size > INDEX_BUFFER_SIZE) { -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (device.index_persistent[device.buffer_ring]) glFinish(); else @@ -4472,11 +4691,11 @@ static unsigned long index_upload(const void *data, unsigned long size) device.index_offset = 0; } offset = device.index_offset; -#ifdef HALO_ANDROID - if (!host_gl_persistent_write(device.index_buffer, (unsigned int)offset, (unsigned int)size, data)) - host_gl_buffer_write(GL_ELEMENT_ARRAY_BUFFER, (unsigned int)offset, (unsigned int)size, data); +#ifdef HALO_GLES + if (!host_gl_persistent_write(device.index_buffer, (unsigned int)offset, (unsigned int)length, data)) + host_gl_buffer_write(GL_ELEMENT_ARRAY_BUFFER, (unsigned int)offset, (unsigned int)length, data); #else - buffer_upload(GL_ELEMENT_ARRAY_BUFFER, offset, size, data); + buffer_upload(GL_ELEMENT_ARRAY_BUFFER, offset, length, data); #endif device.index_offset += size; return offset; @@ -4491,7 +4710,7 @@ static void attribute_format(const struct vertex_element *element, GLint *size, case D3DVSDT_FLOAT2: *size = 2; *type = GL_FLOAT; break; case D3DVSDT_FLOAT3: case D3DVSDT_FLOAT2H: *size = 3; *type = GL_FLOAT; break; case D3DVSDT_FLOAT4: *size = 4; *type = GL_FLOAT; break; -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES has no BGRA attributes: stream_upload_swizzled swaps the bytes */ case D3DVSDT_D3DCOLOR: *size = 4; *type = GL_UNSIGNED_BYTE; *normalized = GL_TRUE; break; #else @@ -4516,7 +4735,7 @@ static void attribute_format(const struct vertex_element *element, GLint *size, /* upload vertices [first, first + count) of every stream the declaration uses and point the attributes at them; attribute data then starts at vertex 0 of the uploaded range */ -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES has no BGRA attributes, so a stream with colours is swizzled as it is uploaded (stream_upload_swizzled) and cannot come from the mirror */ static BOOL stream_has_colors(const struct vertex_shader_object *declaration, unsigned long stream) @@ -4540,7 +4759,7 @@ static void setup_streams(unsigned long first, unsigned long count) BOOL placed[16] = { FALSE }; BOOL enabled[XGPU_VERTEX_ATTRIBUTE_COUNT] = { FALSE }; unsigned long index, total = 0; -#ifndef HALO_ANDROID +#ifndef HALO_GLES struct vertex_layout layout; unsigned long streams_used = 0; @@ -4562,7 +4781,7 @@ static void setup_streams(unsigned long first, unsigned long count) placed[stream] = TRUE; stream_buffers[stream] = 0; base = (unsigned long)PLATFORM_PHYSICAL_TO_VIRTUAL(device.streams[stream].data) + first * stride; -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (!stream_has_colors(declaration, stream)) #endif if (mirror_range(base, bytes, &stream_buffers[stream], &stream_offsets[stream], NULL)) @@ -4587,7 +4806,7 @@ static void setup_streams(unsigned long first, unsigned long count) const unsigned char *base = PLATFORM_PHYSICAL_TO_VIRTUAL(device.streams[stream].data); unsigned long bytes = stride ? stride * count : 64; -#ifdef HALO_ANDROID +#ifdef HALO_GLES stream_offsets[stream] = stream_upload_swizzled(declaration, stream, base + first * stride, bytes, stride); #else stream_offsets[stream] = stream_upload(base + first * stride, bytes); @@ -4595,7 +4814,7 @@ static void setup_streams(unsigned long first, unsigned long count) stream_buffers[stream] = device.stream_buffer; stats.streamed_bytes += bytes; } -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (element->type == D3DVSDT_NORMPACKED3) { state_attribute_stream(element->reg, (GLuint)stream, stream_buffers[stream], 1, GL_UNSIGNED_INT, GL_FALSE, @@ -4621,7 +4840,7 @@ static void setup_streams(unsigned long first, unsigned long count) #endif enabled[element->reg] = TRUE; } -#ifndef HALO_ANDROID +#ifndef HALO_GLES /* (the layout follows from the declaration and the streams it has, so the same two have the same vertex array: no need to look it up) */ if (!declaration->vertex_array || declaration->vertex_array_streams != streams_used) @@ -4733,7 +4952,7 @@ void WINAPI D3DDevice_DrawIndexedVertices(D3DPRIMITIVETYPE primitive_type, UINT return; /* quads are drawn as triangles, from indices made for the draw */ mirrored = primitive_type != D3DPT_QUADLIST && -#ifdef HALO_ANDROID +#ifdef HALO_GLES xgpu_capabilities.base_vertex && #endif mirror_range((unsigned long)index_data, vertex_count * sizeof(WORD), &index_buffer, &index_offset, &generation); @@ -4756,7 +4975,7 @@ void WINAPI D3DDevice_DrawIndexedVertices(D3DPRIMITIVETYPE primitive_type, UINT indices = quad_indices(index_data, vertex_count, &count); source = indices; } -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (!xgpu_capabilities.base_vertex) { /* the indices are copied anyway: rebase them */ @@ -4792,9 +5011,14 @@ static void immediate_emit(void) if (device.immediate_count == device.immediate_capacity) { - device.immediate_capacity = device.immediate_capacity ? device.immediate_capacity * 2 : 256; - device.immediate_vertices = realloc(device.immediate_vertices, - device.immediate_capacity * floats * sizeof(float)); + unsigned long capacity = device.immediate_capacity ? device.immediate_capacity * 2 : 256; + float *vertices = realloc(device.immediate_vertices, capacity * floats * sizeof(float)); + + /* (out of memory: the vertex is dropped) */ + if (!vertices) + return; + device.immediate_vertices = vertices; + device.immediate_capacity = capacity; } memcpy(device.immediate_vertices + device.immediate_count * floats, device.attributes, floats * sizeof(float)); device.immediate_count++; @@ -4811,7 +5035,7 @@ void WINAPI D3DDevice_End(void) return; trace_draw("immediate", type, count, device.immediate_vertices); offset = stream_upload(device.immediate_vertices, count * stride); -#ifdef HALO_ANDROID +#ifdef HALO_GLES for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++) { state_attribute_stream(index, 0, device.stream_buffer, 4, GL_FLOAT, GL_FALSE, FALSE, (GLsizei)stride, @@ -5009,6 +5233,104 @@ void halo_screen_anti_alias(short x0, short y0, short x1, short y1) /* ---------- presentation */ +#ifndef HALO_GLES +/* the screenshot key's PNG (controls.screenshot; the Android guest has none +of the SDL calls it takes) */ +static void write_key_screenshot(struct render_target_entry *target) +{ + char directory[512], path[640], timestamp[32], filename[64]; + SDL_Time now; + SDL_DateTime date; + SDL_Surface *surface = NULL; + unsigned int collision; + unsigned long width = target->target.gl_width, height = target->target.gl_height; + GLint framebuffer, draw_framebuffer, pack_buffer, alignment, row_length, skip_rows, skip_pixels; + GLenum error; + unsigned long row, column; + + if (snprintf(directory, sizeof(directory), "%s/screenshots", platform_data_root()) >= (int)sizeof(directory)) + { + SDL_SetError("Screenshot directory path is too long"); + goto failed; + } + if (!SDL_CreateDirectory(directory) || !SDL_GetCurrentTime(&now) || !SDL_TimeToDateTime(now, &date, true)) + goto failed; + snprintf(timestamp, sizeof(timestamp), "%04d-%02d-%02d_%02d.%02d.%02d", + date.year, date.month, date.day, date.hour, date.minute, date.second); + for (collision = 0; collision < 1000; collision++) + { + if (collision) + snprintf(filename, sizeof(filename), "%s_%u.png", timestamp, collision + 1); + else + snprintf(filename, sizeof(filename), "%s.png", timestamp); + snprintf(path, sizeof(path), "%s/%s", directory, filename); + if (!SDL_GetPathInfo(path, NULL)) + break; + } + if (collision == 1000) + { + SDL_SetError("Too many screenshots with the same timestamp"); + goto failed; + } + if (!width || !height || width > INT_MAX / 4 || height > INT_MAX) + { + SDL_SetError("Invalid screenshot dimensions"); + goto failed; + } + surface = SDL_CreateSurface((int)width, (int)height, SDL_PIXELFORMAT_BGRA32); + if (!surface) + goto failed; + + /* Read the render target before the display blit's vertical flip. Its + row zero is the top of the image. Preserve the caller's readback state. */ + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &framebuffer); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw_framebuffer); + glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &pack_buffer); + glGetIntegerv(GL_PACK_ALIGNMENT, &alignment); + glGetIntegerv(GL_PACK_ROW_LENGTH, &row_length); + glGetIntegerv(GL_PACK_SKIP_ROWS, &skip_rows); + glGetIntegerv(GL_PACK_SKIP_PIXELS, &skip_pixels); + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_get(target->target.texture, 0)); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glPixelStorei(GL_PACK_ROW_LENGTH, surface->pitch / 4); + glPixelStorei(GL_PACK_SKIP_ROWS, 0); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + glReadPixels(0, 0, (GLsizei)width, (GLsizei)height, GL_BGRA, GL_UNSIGNED_BYTE, surface->pixels); + error = glGetError(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer); + glBindBuffer(GL_PIXEL_PACK_BUFFER, pack_buffer); + glPixelStorei(GL_PACK_ALIGNMENT, alignment); + glPixelStorei(GL_PACK_ROW_LENGTH, row_length); + glPixelStorei(GL_PACK_SKIP_ROWS, skip_rows); + glPixelStorei(GL_PACK_SKIP_PIXELS, skip_pixels); + if (error != GL_NO_ERROR) + { + SDL_SetError("OpenGL screenshot readback failed (%04x)", (unsigned)error); + goto failed; + } + /* Destination alpha is scratch data in the game, not transparency. */ + for (row = 0; row < height; row++) + { + unsigned char *pixels = (unsigned char *)surface->pixels + (size_t)row * surface->pitch; + + for (column = 0; column < width; column++) + pixels[column * 4 + 3] = 0xff; + } + if (!SDL_SavePNG(surface, path)) + goto failed; + SDL_DestroySurface(surface); + console_printf(FALSE, "Saved screenshot as %s", filename); + return; + +failed: + console_printf(FALSE, "Screenshot failed: %.180s", SDL_GetError()); + if (surface) + SDL_DestroySurface(surface); +} + +#endif static void write_screenshot(struct render_target_entry *target) { const char *directory = *config_string("debug.screenshot_directory") ? @@ -5030,7 +5352,7 @@ static void write_screenshot(struct render_target_entry *target) image viewers would show it as transparency */ for (row = 0; row < width * height; row++) { -#ifdef HALO_ANDROID +#ifdef HALO_GLES unsigned char red = pixels[row * 4]; pixels[row * 4] = pixels[row * 4 + 2]; @@ -5126,6 +5448,31 @@ void d3d8_clear_back_buffer_region(float left, float top, float right, float bot xgpu_gl_state_invalidate(); } +#ifndef HALO_GLES +/* the GPU copies the counts of the previous frame's tests into their slots +(given by name: Mesa's GL thread waits for everything before a +glGetQueryObjectuiv, even one into a bound buffer). A copy right after each +test stopped the GPU until that test's draws were through, hundreds of times +a frame in The Library; a frame later the draws are long done, so the copies +do not wait. */ +static void visibility_copy_batch(void) +{ + unsigned long i; + + if (!device.visibility_results) + return; + /* this frame's tests wait for the next present */ + device.visibility_batch ^= 1; + for (i = 0; i < device.visibility_batches[device.visibility_batch].count; i++) + { + glGetQueryBufferObjectuiv(device.visibility_batches[device.visibility_batch].queries[i], + device.visibility_results_buffer, GL_QUERY_RESULT, + (GLintptr)(device.visibility_batches[device.visibility_batch].slots[i] * sizeof(GLuint))); + } + device.visibility_batches[device.visibility_batch].count = 0; +} + +#endif void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destination_rectangle, void *unused, void *unused2) { @@ -5147,8 +5494,15 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat platform_log("present back buffer %08lx texture %u", (unsigned long)device.back_buffer.Data, back_buffer->target.texture); render_target_resolve(&back_buffer->target); +#ifndef HALO_GLES + visibility_copy_batch(); +#endif if (screenshot_every > 0 && device.frame % (unsigned long)screenshot_every == 0) write_screenshot(back_buffer); +#ifndef HALO_GLES + if (platform_screenshot_take_request()) + write_key_screenshot(back_buffer); +#endif #ifdef HALO_ANDROID if (halo_vr_active()) @@ -5182,8 +5536,9 @@ void WINAPI D3DDevice_Present(CONST RECT *source_rectangle, CONST RECT *destinat platform_video_swap(); } xgpu_gl_state_invalidate(); + memory_watch_begin_frame(); xgpu_texture_cache_begin_frame(); -#ifdef HALO_ANDROID +#ifdef HALO_GLES visibility_frame_end(); host_gl_fence_frame((unsigned int)device.buffer_ring); device.buffer_ring = (device.buffer_ring + 1) % STREAM_BUFFER_RING; diff --git a/port/linux/src/dsound_sdl.c b/port/linux/src/dsound_sdl.c index 230bb6a6..928bb54e 100644 --- a/port/linux/src/dsound_sdl.c +++ b/port/linux/src/dsound_sdl.c @@ -37,9 +37,11 @@ turns the reverb off; audio.enabled = false skips opening a device #include "platform.h" #include "sdl_platform.h" #include "port_config.h" +#include "voice_audio.h" #include #include +#include #include #include #include @@ -60,6 +62,11 @@ turns the reverb off; audio.enabled = false skips opening a device #define RESAMPLER_KAISER_BETA 6.5 #define RESAMPLER_MAXIMUM_STRETCH 2 #define RESAMPLER_HISTORY 128 +#if defined(__GNUC__) || defined(__clang__) +#define RESAMPLER_ALIGNED __attribute__((aligned(16))) +#else +#define RESAMPLER_ALIGNED +#endif /* ---------- voices */ @@ -119,9 +126,12 @@ struct sdl_stream /* the next frame to take from the first packet not finished */ unsigned long cursor; /* the resampler's (resampler_reset): the last RESAMPLER_HISTORY frames - taken, how many were taken, the frame the output is at and how far past - it, and the frames of silence taken since the packets ran out */ - float history[RESAMPLER_HISTORY][2]; + taken, each channel's apart and each frame twice, RESAMPLER_HISTORY apart, + so that the frames a low pass reaches are side by side however the ring + wraps (resampler_dot); how many were taken, the frame the output is at and + how far past it, and the frames of silence taken since the packets ran + out */ + float history[2][2 * RESAMPLER_HISTORY]; unsigned long history_count; unsigned long center; double phase; @@ -467,8 +477,11 @@ static float resampler_table[RESAMPLER_ZERO_CROSSINGS * RESAMPLER_TABLE_STEPS + to RESAMPLER_ZERO_CROSSINGS, at each of RESAMPLER_TABLE_STEPS phases between two source frames (and one row more, for the last's blend): a voice at the output rate or slower blends two rows by its phase, which is what the table gave tap -by tap, a third of the work */ -static float resampler_phases[RESAMPLER_TABLE_STEPS + 1][2 * RESAMPLER_ZERO_CROSSINGS]; +by tap, a third of the work. resampler_deltas holds each row's step to the +next, so the blend is a multiply and an add a tap */ +#define RESAMPLER_TAPS (2 * RESAMPLER_ZERO_CROSSINGS) +static float resampler_phases[RESAMPLER_TABLE_STEPS + 1][RESAMPLER_TAPS] RESAMPLER_ALIGNED; +static float resampler_deltas[RESAMPLER_TABLE_STEPS][RESAMPLER_TAPS] RESAMPLER_ALIGNED; static double bessel_i0(double x) { @@ -527,6 +540,77 @@ static void resampler_phases_initialize(void) resampler_phases[phase][tap] = resampler_weight(distance * RESAMPLER_TABLE_STEPS); } } + for (phase = 0; phase < RESAMPLER_TABLE_STEPS; phase++) + { + for (tap = 0; tap < RESAMPLER_TAPS; tap++) + resampler_deltas[phase][tap] = resampler_phases[phase + 1][tap] - resampler_phases[phase][tap]; + } +} + +/* The low pass over a voice's frames, the sum of each frame times its weight: +four taps at a time where the compiler has vectors (SSE, NEON), summed in four +lanes, then the lanes together. Only the order of the additions differs from +tap by tap, a difference of the order of float's rounding (-115 dB or less); +RESAMPLER_SCALAR adds them tap by tap (tools/harness/tests/test_mixer.py). */ +#if (defined(__GNUC__) || defined(__clang__)) && !defined(RESAMPLER_SCALAR) +typedef float resampler_vector __attribute__((vector_size(16))); +typedef float resampler_unaligned_vector __attribute__((vector_size(16), aligned(4))); +/* (each row of the phase tables whole vectors, aligned) */ +typedef char resampler_taps_in_fours[RESAMPLER_TAPS % 4 ? -1 : 1]; +#define RESAMPLER_LANES 4 +#else +#define RESAMPLER_LANES 1 +#endif + +/* frames[0, RESAMPLER_TAPS) weighted by the phase table's row blended blend of +the way to the next */ +static float resampler_dot(const float *frames, const float *weights, const float *deltas, float blend) +{ +#if RESAMPLER_LANES == 4 + resampler_vector sum = { 0.0f, 0.0f, 0.0f, 0.0f }, blends = { blend, blend, blend, blend }; + int tap; + + for (tap = 0; tap < RESAMPLER_TAPS; tap += 4) + { + resampler_vector weight = *(const resampler_vector *)(weights + tap) + + *(const resampler_vector *)(deltas + tap) * blends; + + sum += *(const resampler_unaligned_vector *)(frames + tap) * weight; + } + return (sum[0] + sum[1]) + (sum[2] + sum[3]); +#else + float sum = 0.0f; + int tap; + + for (tap = 0; tap < RESAMPLER_TAPS; tap++) + sum += frames[tap] * (weights[tap] + deltas[tap] * blend); + return sum; +#endif +} + +/* resampler_dot of two channels' frames, with the same weights */ +static void resampler_dot2(const float *left_frames, const float *right_frames, const float *weights, + const float *deltas, float blend, float *left, float *right) +{ +#if RESAMPLER_LANES == 4 + resampler_vector left_sum = { 0.0f, 0.0f, 0.0f, 0.0f }, right_sum = left_sum; + resampler_vector blends = { blend, blend, blend, blend }; + int tap; + + for (tap = 0; tap < RESAMPLER_TAPS; tap += 4) + { + resampler_vector weight = *(const resampler_vector *)(weights + tap) + + *(const resampler_vector *)(deltas + tap) * blends; + + left_sum += *(const resampler_unaligned_vector *)(left_frames + tap) * weight; + right_sum += *(const resampler_unaligned_vector *)(right_frames + tap) * weight; + } + *left = (left_sum[0] + left_sum[1]) + (left_sum[2] + left_sum[3]); + *right = (right_sum[0] + right_sum[1]) + (right_sum[2] + right_sum[3]); +#else + *left = resampler_dot(left_frames, weights, deltas, blend); + *right = resampler_dot(right_frames, weights, deltas, blend); +#endif } /* a voice starting (over): silence before its first frame, which the output @@ -579,6 +663,26 @@ static BOOL take_frame(struct sdl_stream *stream, float *frame) } } +/* whether the voice has a frame to take; the packets with none left are +marked finished on the way, as take_frame marks them */ +static BOOL voice_has_frames(struct sdl_stream *stream) +{ + unsigned long position; + + for (position = 0; position < stream->packet_count; position++) + { + struct voice_packet *packet = &stream->packets[(stream->packet_head + position) % MAXIMUM_STREAM_PACKETS]; + + if (packet->finished) + continue; + if (stream->cursor < packet->frames) + return TRUE; + stream->cursor = 0; + packet->finished = TRUE; + } + return FALSE; +} + /* ---------- mixing */ /* mixes one voice into output (frames of stereo float), and into the reverb's @@ -589,6 +693,7 @@ static void mix_voice(struct sdl_stream *stream, float *output, float *send, uns float target_left, target_right, target_room, left, right, room, ramp_left, ramp_right, ramp_room, scale; float target_direct_lowpass, target_room_lowpass, direct_lowpass, room_lowpass; float ramp_direct_lowpass, ramp_room_lowpass; + float direct_state[2], room_state; long width; unsigned long frame; /* (a stereo voice panned towards its sound: voice_gains) */ @@ -601,6 +706,16 @@ static void mix_voice(struct sdl_stream *stream, float *output, float *send, uns rate, and the frames it reaches on each side */ scale = step > 1.0 ? (float)(1.0 / (step < RESAMPLER_MAXIMUM_STRETCH ? step : RESAMPLER_MAXIMUM_STRETCH)) : 1.0f; width = (long)ceilf(RESAMPLER_ZERO_CROSSINGS / scale); + /* a voice that ran dry and stopped (below) starts over once it has + frames again: stream_process starts over only a stream with no packets, + and the next can come before the finished ones are completed */ + if (stream->silence > (unsigned long)(2 * width)) + { + if (!voice_has_frames(stream)) + return; + resampler_reset(stream); + stream->gains_valid = FALSE; + } voice_gains(stream, &target_left, &target_right, &target_room, &target_direct_lowpass, &target_room_lowpass); if (!stream->gains_valid) { @@ -618,6 +733,11 @@ static void mix_voice(struct sdl_stream *stream, float *output, float *send, uns room = stream->current_room; direct_lowpass = stream->current_direct_lowpass; room_lowpass = stream->current_room_lowpass; + /* the low passes' states, here while the mix writes the output (which + the compiler cannot tell from them) */ + direct_state[0] = stream->direct_lowpass[0]; + direct_state[1] = stream->direct_lowpass[1]; + room_state = stream->room_lowpass; ramp_left = (target_left - left) / (float)frames; ramp_right = (target_right - right) / (float)frames; ramp_room = (target_room - room) / (float)frames; @@ -633,17 +753,20 @@ static void mix_voice(struct sdl_stream *stream, float *output, float *send, uns silence, until what was taken has played out */ while ((long)(stream->history_count - stream->center) <= width) { - float *slot = stream->history[stream->history_count % RESAMPLER_HISTORY]; + unsigned long slot = stream->history_count % RESAMPLER_HISTORY; + float taken[2]; - if (take_frame(stream, slot)) + if (take_frame(stream, taken)) { stream->silence = 0; } else { - slot[0] = slot[1] = 0.0f; + taken[0] = taken[1] = 0.0f; stream->silence++; } + stream->history[0][slot] = stream->history[0][slot + RESAMPLER_HISTORY] = taken[0]; + stream->history[1][slot] = stream->history[1][slot + RESAMPLER_HISTORY] = taken[1]; stream->history_count++; } if (stream->silence > (unsigned long)(2 * width)) @@ -659,46 +782,53 @@ static void mix_voice(struct sdl_stream *stream, float *output, float *send, uns double position = stream->phase * RESAMPLER_TABLE_STEPS; unsigned long row = (unsigned long)position; float blend = (float)(position - (double)row); - const float *weights = resampler_phases[row], *next_weights = resampler_phases[row + 1]; - unsigned long first = stream->center + 1 - RESAMPLER_ZERO_CROSSINGS; + unsigned long first = (stream->center + 1 - RESAMPLER_ZERO_CROSSINGS) % RESAMPLER_HISTORY; - for (tap = 0; tap < 2 * RESAMPLER_ZERO_CROSSINGS; tap++) + /* (a mono voice's two channels are the same: take_frame) */ + if (stream->channels == 1) { - const float *source = stream->history[(first + (unsigned long)tap) % RESAMPLER_HISTORY]; - float weight = weights[tap] + (next_weights[tap] - weights[tap]) * blend; - - sample_left += source[0] * weight; - sample_right += source[1] * weight; + sample_left = sample_right = resampler_dot(stream->history[0] + first, resampler_phases[row], + resampler_deltas[row], blend); + } + else + { + resampler_dot2(stream->history[0] + first, stream->history[1] + first, resampler_phases[row], + resampler_deltas[row], blend, &sample_left, &sample_right); } } else { + unsigned long first = (stream->center + 1 - (unsigned long)width) % RESAMPLER_HISTORY; + const float *left_frames = stream->history[0] + first, *right_frames = stream->history[1] + first; + for (tap = 1 - width; tap <= width; tap++) { - const float *source = stream->history[(stream->center + (unsigned long)tap) % RESAMPLER_HISTORY]; float weight = scale * resampler_weight(fabsf((float)tap - (float)stream->phase) * scale * RESAMPLER_TABLE_STEPS); - sample_left += source[0] * weight; - sample_right += source[1] * weight; + sample_left += left_frames[tap + width - 1] * weight; + if (stream->channels == 2) + sample_right += right_frames[tap + width - 1] * weight; } + if (stream->channels == 1) + sample_right = sample_left; } /* the room send, with its own low pass */ if (room || ramp_room) { float mono = stream->channels == 1 ? sample_left : 0.5f * (sample_left + sample_right); - stream->room_lowpass = flush_denormal(mono + room_lowpass * (stream->room_lowpass - mono)); - send[frame] += stream->room_lowpass * room; + room_state = flush_denormal(mono + room_lowpass * (room_state - mono)); + send[frame] += room_state * room; } /* the direct path, muffled, or as it is; a mono voice's mix bins or pan split it across the speakers */ if (direct_lowpass || ramp_direct_lowpass) { - sample_left = flush_denormal(sample_left + direct_lowpass * (stream->direct_lowpass[0] - sample_left)); - sample_right = flush_denormal(sample_right + direct_lowpass * (stream->direct_lowpass[1] - sample_right)); + sample_left = flush_denormal(sample_left + direct_lowpass * (direct_state[0] - sample_left)); + sample_right = flush_denormal(sample_right + direct_lowpass * (direct_state[1] - sample_right)); } - stream->direct_lowpass[0] = sample_left; - stream->direct_lowpass[1] = sample_right; + direct_state[0] = sample_left; + direct_state[1] = sample_right; if (stream->channels == 1) { output[frame * 2] += sample_left * left; @@ -733,6 +863,9 @@ static void mix_voice(struct sdl_stream *stream, float *output, float *send, uns stream->center++; } } + stream->direct_lowpass[0] = direct_state[0]; + stream->direct_lowpass[1] = direct_state[1]; + stream->room_lowpass = room_state; stream->current_left = target_left; stream->current_right = target_right; stream->current_room = target_room; @@ -1053,6 +1186,9 @@ static struct /* the frames the output is delayed by, and the gain each needs */ float delay[LIMITER_LOOKAHEAD][OUTPUT_CHANNELS]; float needed[LIMITER_LOOKAHEAD]; + /* how many of them are under 1: none while nothing is too loud, when the + smallest is 1 without looking */ + unsigned long limiting; /* the gain held down to what the frames ahead need, coming back up */ float held; /* its last LIMITER_LOOKAHEAD values, and their sum */ @@ -1092,12 +1228,18 @@ static void limit(float *output, unsigned long frames) peak = fabsf(sample[channel]); limiter.delay[position][channel] = sample[channel]; } + limiter.limiting -= limiter.needed[position] < 1.0f; limiter.needed[position] = peak > LIMITER_CEILING ? LIMITER_CEILING / peak : 1.0f; - lowest = limiter.needed[0]; - for (index = 1; index < LIMITER_LOOKAHEAD; index++) + limiter.limiting += limiter.needed[position] < 1.0f; + lowest = 1.0f; + if (limiter.limiting) { - if (limiter.needed[index] < lowest) - lowest = limiter.needed[index]; + lowest = limiter.needed[0]; + for (index = 1; index < LIMITER_LOOKAHEAD; index++) + { + if (limiter.needed[index] < lowest) + lowest = limiter.needed[index]; + } } if (lowest < limiter.held) limiter.held = lowest; @@ -1143,6 +1285,8 @@ static void mix(float *output, unsigned long frames) if ((!enabled && reverb.level <= 0.0f) || reverb.quiet > REVERB_QUIET_FRAMES) reverb_clear(); } + /* the players' voices (voice_audio.c), dry, under the limiter */ + voice_audio_mix(output, frames); limit(output, frames); } @@ -1178,6 +1322,18 @@ static void audio_log_level(const float *samples, unsigned long count) } } +/* the device it plays on (audio.output_device), and when it was looked at */ +static char audio_device_name[PLATFORM_AUDIO_DEVICE_NAME_SIZE]; +static unsigned long audio_device_read_at = (unsigned long)-1; + +/* audio.output_device ("default": the system's; none on Android) */ +static const char *audio_device_setting(void) +{ + const char *name = config_string("audio.output_device"); + + return name && name[0] ? name : "default"; +} + static void SDLCALL audio_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount) { float buffer[MIX_CHUNK_FRAMES * OUTPUT_CHANNELS]; @@ -1241,7 +1397,7 @@ static void audio_start(void) spec.format = SDL_AUDIO_F32; spec.channels = OUTPUT_CHANNELS; spec.freq = OUTPUT_RATE; -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST /* frames per callback: on Android each callback is handed to a thread that can run the guest (host_sdl.c): 512 left it too little time and the menus' music broke up, which 1024 does not (about 21 ms at 48 kHz, @@ -1250,7 +1406,12 @@ static void audio_start(void) #else SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, "512"); #endif - audio_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, audio_callback, NULL); + snprintf(audio_device_name, sizeof(audio_device_name), "%s", audio_device_setting()); + audio_device_read_at = config_changes(); + audio_stream = SDL_OpenAudioDeviceStream(platform_audio_device(FALSE, audio_device_name), &spec, + audio_callback, NULL); + if (!audio_stream && strcmp(audio_device_name, "default")) + audio_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, audio_callback, NULL); if (audio_stream) { SDL_ResumeAudioStreamDevice(audio_stream); @@ -1268,6 +1429,44 @@ static void audio_start(void) } } +/* (the event thread, each frame: sdl_platform.c) audio.output_device +changed (Settings > Audio): the sound goes on on the new device, else the +system's default */ +void dsound_sdl_output_device_check(void) +{ + SDL_AudioSpec spec; + SDL_AudioStream *stream; + + if (!audio_stream || audio_device_read_at == config_changes()) + return; + audio_device_read_at = config_changes(); + if (!strcmp(audio_device_name, audio_device_setting())) + return; + snprintf(audio_device_name, sizeof(audio_device_name), "%s", audio_device_setting()); + spec.format = SDL_AUDIO_F32; + spec.channels = OUTPUT_CHANNELS; + spec.freq = OUTPUT_RATE; + SDL_DestroyAudioStream(audio_stream); + stream = SDL_OpenAudioDeviceStream(platform_audio_device(FALSE, audio_device_name), &spec, audio_callback, NULL); + if (!stream) + stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, audio_callback, NULL); + audio_stream = stream; + if (audio_stream) + { + SDL_ResumeAudioStreamDevice(audio_stream); + platform_log("audio: playing on %s", audio_device_name); + } + else + { + pthread_t thread; + + /* (none at all: the voices drained in real time, as at the start) */ + platform_log("audio: cannot open an audio device (%s); sound is silent", SDL_GetError()); + pthread_create(&thread, NULL, silent_clock_thread, NULL); + pthread_detach(thread); + } +} + /* ---------- completion */ static void packet_release(struct voice_packet *entry) @@ -1283,6 +1482,10 @@ static void stream_complete_head(struct sdl_stream *stream, DWORD status, DWORD struct voice_packet *entry = &stream->packets[stream->packet_head]; XMEDIAPACKET packet = entry->packet; + /* (one not played to its end, flushed: the cursor was its place, and the + mixer may take the next while the lock is let go below) */ + if (!entry->finished) + stream->cursor = 0; packet_release(entry); entry->finished = FALSE; stream->packet_head = (stream->packet_head + 1) % MAXIMUM_STREAM_PACKETS; @@ -1440,7 +1643,9 @@ static HRESULT STDMETHODCALLTYPE stream_flush(IDirectSoundStream *object) { struct voice_packet *head = &stream->packets[stream->packet_head]; - stream_complete_head(stream, head->finished ? XMEDIAPACKET_STATUS_SUCCESS : XMEDIAPACKET_STATUS_FLUSHED, + /* Cancellation is synchronous. SUCCESS would let the game's + completion callback refill the stream we are stopping. */ + stream_complete_head(stream, XMEDIAPACKET_STATUS_FLUSHED, head->finished ? head->packet.dwMaxSize : 0); } stream->cursor = 0; diff --git a/port/linux/src/gl.h b/port/linux/src/gl.h index 9ca613d8..fe177f30 100644 --- a/port/linux/src/gl.h +++ b/port/linux/src/gl.h @@ -16,7 +16,7 @@ Windows it is __stdcall too, and SDL would include windows.h without it) */ #ifndef _WIN32 #undef APIENTRY #endif -#ifdef HALO_ANDROID +#ifdef HALO_GLES #include #include #define GLAPIENTRY GL_APIENTRY @@ -25,7 +25,7 @@ Windows it is __stdcall too, and SDL would include windows.h without it) */ #endif #pragma pop_macro("APIENTRY") -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* OpenGL ES 3.2 (port/android/README.md); tools/android_gl_stubs.py reads this list to generate the guest's entry points */ /* ANDROID_GL_FUNCTIONS_BEGIN */ @@ -87,6 +87,7 @@ this list to generate the guest's entry points */ X(glBlitFramebuffer) \ X(glDrawBuffers) \ X(glGenRenderbuffers) \ + X(glDeleteRenderbuffers) \ X(glBindRenderbuffer) \ X(glRenderbufferStorageMultisample) \ X(glFramebufferRenderbuffer) \ @@ -202,6 +203,7 @@ this list to generate the guest's entry points */ X(glBlitFramebuffer) \ X(glDrawBuffers) \ X(glGenRenderbuffers) \ + X(glDeleteRenderbuffers) \ X(glBindRenderbuffer) \ X(glRenderbufferStorageMultisample) \ X(glFramebufferRenderbuffer) \ @@ -265,7 +267,7 @@ GL_FUNCTIONS(GL_DECLARE_FUNCTION) /* call sites use the ordinary names; gl_functions.c, which defines the pointers, sees the declarations without these aliases */ #ifndef GL_FUNCTIONS_DEFINE -#ifdef HALO_ANDROID +#ifdef HALO_GLES #define glGetString halo_glGetString #define glGetIntegerv halo_glGetIntegerv #define glCopyImageSubData halo_glCopyImageSubData @@ -323,6 +325,7 @@ pointers, sees the declarations without these aliases */ #define glBlitFramebuffer halo_glBlitFramebuffer #define glDrawBuffers halo_glDrawBuffers #define glGenRenderbuffers halo_glGenRenderbuffers +#define glDeleteRenderbuffers halo_glDeleteRenderbuffers #define glBindRenderbuffer halo_glBindRenderbuffer #define glRenderbufferStorageMultisample halo_glRenderbufferStorageMultisample #define glFramebufferRenderbuffer halo_glFramebufferRenderbuffer @@ -436,6 +439,7 @@ pointers, sees the declarations without these aliases */ #define glBlitFramebuffer halo_glBlitFramebuffer #define glDrawBuffers halo_glDrawBuffers #define glGenRenderbuffers halo_glGenRenderbuffers +#define glDeleteRenderbuffers halo_glDeleteRenderbuffers #define glBindRenderbuffer halo_glBindRenderbuffer #define glRenderbufferStorageMultisample halo_glRenderbufferStorageMultisample #define glFramebufferRenderbuffer halo_glFramebufferRenderbuffer diff --git a/port/linux/src/hud_hires.c b/port/linux/src/hud_hires.c index 5352c483..439ebdcc 100644 --- a/port/linux/src/hud_hires.c +++ b/port/linux/src/hud_hires.c @@ -185,6 +185,8 @@ static unsigned char *png_decode(const unsigned char *data, unsigned long size, ended when the output is exactly full: all of it is enough) */ if ((result != Z_OK && result != Z_BUF_ERROR) || inflated_size != filtered_size) goto failed; + /* (each row by its filter, the first pixel's 4 bytes, which have none to + their left, apart; the first row has none above: zeroes) */ for (row = 0; row < height; row++) { const unsigned char *line = filtered + row * (stride + 1) + 1; @@ -194,22 +196,38 @@ static unsigned char *png_decode(const unsigned char *data, unsigned long size, if (filter > 4) goto failed; - for (column = 0; column < stride; column++) + if (!above && filter == 2) + filter = 0; /* (up: zero) */ + else if (!above && filter == 4) + filter = 1; /* (Paeth of left, zero and zero: left) */ + switch (filter) { - unsigned char left = column >= 4 ? out[column - 4] : 0; - unsigned char up = above ? above[column] : 0; - unsigned char up_left = above && column >= 4 ? above[column - 4] : 0; - unsigned char predicted; - - switch (filter) - { - case 1: predicted = left; break; - case 2: predicted = up; break; - case 3: predicted = (unsigned char)(((unsigned)left + up) / 2); break; - case 4: predicted = paeth(left, up, up_left); break; - default: predicted = 0; break; - } - out[column] = (unsigned char)(line[column] + predicted); + case 0: + memcpy(out, line, stride); + break; + case 1: + memcpy(out, line, 4); + for (column = 4; column < stride; column++) + out[column] = (unsigned char)(line[column] + out[column - 4]); + break; + case 2: + for (column = 0; column < stride; column++) + out[column] = (unsigned char)(line[column] + above[column]); + break; + case 3: + for (column = 0; column < 4; column++) + out[column] = (unsigned char)(line[column] + (above ? above[column] : 0) / 2); + for (column = 4; column < stride; column++) + out[column] = (unsigned char)(line[column] + + ((unsigned)out[column - 4] + (above ? above[column] : 0)) / 2); + break; + default: + /* (Paeth of zero, up and zero: up) */ + for (column = 0; column < 4; column++) + out[column] = (unsigned char)(line[column] + above[column]); + for (column = 4; column < stride; column++) + out[column] = (unsigned char)(line[column] + paeth(out[column - 4], above[column], above[column - 4])); + break; } } free(compressed); diff --git a/port/linux/src/memory_watch.c b/port/linux/src/memory_watch.c index 843022cd..89105cd2 100644 --- a/port/linux/src/memory_watch.c +++ b/port/linux/src/memory_watch.c @@ -223,3 +223,8 @@ void memory_watch_forget(void *address, unsigned long size) page_generation[page] = __sync_add_and_fetch(¤t_generation, 1); } } + +/* page protection sees each write at once: nothing to do per frame */ +void memory_watch_begin_frame(void) +{ +} diff --git a/port/linux/src/menu_files.c b/port/linux/src/menu_files.c index b890f57f..580121c9 100644 --- a/port/linux/src/menu_files.c +++ b/port/linux/src/menu_files.c @@ -99,7 +99,7 @@ static long file_add(const char *path, const unsigned char *data, unsigned long static unsigned char *file_read(const char *path, unsigned long *size) { -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST FILE *file = fopen(path, "rb"); unsigned char *data = NULL; long length; diff --git a/port/linux/src/msvc_crt.c b/port/linux/src/msvc_crt.c index afeaddac..e23fc9af 100644 --- a/port/linux/src/msvc_crt.c +++ b/port/linux/src/msvc_crt.c @@ -1,8 +1,8 @@ /* MSVC_CRT.C -Microsoft C runtime functions the game (and its copies of libtiff and zlib) -call that glibc does not provide under the same names, plus the Xbox-path +Microsoft C runtime functions the game (and its copy of zlib) call that +glibc does not provide under the same names, plus the Xbox-path aware fopen/open family that the game's headers redirect to (see port/linux/include/stdio.h). */ @@ -295,7 +295,7 @@ static unsigned short msvc_to_control_word(unsigned int value, unsigned short wo return word; } -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST /* AArch64: the rounding mode lives in FPCR.RMode, the sticky exception flags in FPSR. Precision control and exception unmasking have no equivalent; the rest of the MSVC control word is only remembered. */ @@ -678,7 +678,8 @@ int _fileno(FILE *stream) return fileno(stream); } -/* libtiff maps its POSIX calls onto the MSVC underscore names itself */ +/* the MSVC underscore names of the POSIX file calls (the game's libtiff +used them; the builds leave it out: port.json) */ int _open(const char *path, int flags, ...) { diff --git a/port/linux/src/msvc_wide.c b/port/linux/src/msvc_wide.c index 8d5fa31d..358a34fa 100644 --- a/port/linux/src/msvc_wide.c +++ b/port/linux/src/msvc_wide.c @@ -520,6 +520,10 @@ long msvc_wtol(const wchar_t *string) /* ---------- formatted output */ +/* the widest field and the longest precision a conversion is given (a +format's own, or an argument's) */ +#define WIDE_FORMAT_MAXIMUM_WIDTH 4096L + struct wide_output { wchar_t *buffer; @@ -599,54 +603,32 @@ static int wide_format(struct wide_output *output, const wchar_t *format, va_lis continue; } - specification[specification_length++] = '%'; - /* flags */ - while (*format == '-' || *format == '+' || *format == ' ' || *format == '#' || *format == '0') + /* (the specification handed to snprintf below is built from what + was parsed, bounded: a format may be a map's text, ui strings and + HUD messages among them, so its flags and digits may be any number, + and its width and precision are capped at WIDE_FORMAT_MAXIMUM_WIDTH) */ { - if (*format == '-') - left = TRUE; - specification[specification_length++] = (char)*format++; - } - /* width */ - if (*format == '*') - { - width = va_arg(arguments, int); - if (width < 0) + char flags[8]; + size_t flag_count = 0; + + while (*format == '-' || *format == '+' || *format == ' ' || *format == '#' || *format == '0') { - left = TRUE; - width = -width; - specification[specification_length++] = '-'; + if (*format == '-') + left = TRUE; + if (flag_count < sizeof(flags) - 1) + flags[flag_count++] = (char)*format; + format++; } - specification_length += (size_t)snprintf(specification + specification_length, - sizeof(specification) - specification_length, "%ld", width); - format++; - } - else - { - while (*format >= '0' && *format <= '9') - { - width = width * 10 + (*format - '0'); - specification[specification_length++] = (char)*format++; - } - } - /* precision */ - if (*format == '.') - { - precision = 0; - specification[specification_length++] = '.'; - format++; + /* width */ if (*format == '*') { - precision = va_arg(arguments, int); - if (precision < 0) + width = va_arg(arguments, int); + if (width < 0) { - precision = -1; - specification_length--; - } - else - { - specification_length += (size_t)snprintf(specification + specification_length, - sizeof(specification) - specification_length, "%ld", precision); + left = TRUE; + width = width < -WIDE_FORMAT_MAXIMUM_WIDTH ? WIDE_FORMAT_MAXIMUM_WIDTH : -width; + if (flag_count < sizeof(flags) - 1) + flags[flag_count++] = '-'; } format++; } @@ -654,10 +636,44 @@ static int wide_format(struct wide_output *output, const wchar_t *format, va_lis { while (*format >= '0' && *format <= '9') { - precision = precision * 10 + (*format - '0'); - specification[specification_length++] = (char)*format++; + if (width <= WIDE_FORMAT_MAXIMUM_WIDTH) + width = width * 10 + (*format - '0'); + format++; } } + if (width > WIDE_FORMAT_MAXIMUM_WIDTH) + width = WIDE_FORMAT_MAXIMUM_WIDTH; + flags[flag_count] = '\0'; + /* precision */ + if (*format == '.') + { + precision = 0; + format++; + if (*format == '*') + { + precision = va_arg(arguments, int); + if (precision < 0) + precision = -1; + format++; + } + else + { + while (*format >= '0' && *format <= '9') + { + if (precision <= WIDE_FORMAT_MAXIMUM_WIDTH) + precision = precision * 10 + (*format - '0'); + format++; + } + } + if (precision > WIDE_FORMAT_MAXIMUM_WIDTH) + precision = WIDE_FORMAT_MAXIMUM_WIDTH; + } + if (precision >= 0) + specification_length = (size_t)snprintf(specification, sizeof(specification), "%%%s%ld.%ld", flags, + width, precision); + else + specification_length = (size_t)snprintf(specification, sizeof(specification), "%%%s%ld", flags, + width); } /* size */ if (*format == 'h') @@ -716,13 +732,10 @@ static int wide_format(struct wide_output *output, const wchar_t *format, va_lis break; } case 'n': - { - int *count = va_arg(arguments, int *); - - if (count) - *count = (int)output->length; + /* (never written: a format may be a map's text. Its argument + is passed over, as UCRT does with %n disabled) */ + (void)va_arg(arguments, int *); break; - } case 'd': case 'i': case 'u': case 'x': case 'X': case 'o': case 'e': case 'E': case 'f': case 'g': case 'G': case 'p': { diff --git a/port/linux/src/nv2a_psh.c b/port/linux/src/nv2a_psh.c index 211146d1..8ee338be 100644 --- a/port/linux/src/nv2a_psh.c +++ b/port/linux/src/nv2a_psh.c @@ -314,7 +314,7 @@ static void dot_input(struct xgpu_text *text, const DWORD *state, int stage) } } -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES samplers have no LOD bias: pass D3DTSS_MIPMAPLODBIAS to the lookup */ #define SAMPLE_BIAS ", texture_lod_bias[%d]" #define SHADER_VERSION \ @@ -334,21 +334,21 @@ static void sample(struct xgpu_text *text, const struct nv2a_pixel_shader_key *k { case _xgpu_sampler_3d: xgpu_text_append(text, "texture(tex%d, (%s).xyz" SAMPLE_BIAS ")", stage, coordinates -#ifdef HALO_ANDROID +#ifdef HALO_GLES , stage #endif ); break; case _xgpu_sampler_cube: xgpu_text_append(text, "texture(tex%d, (%s).xyz" SAMPLE_BIAS ")", stage, coordinates -#ifdef HALO_ANDROID +#ifdef HALO_GLES , stage #endif ); break; default: xgpu_text_append(text, "texture(tex%d, (%s).xy * texture_scale[%d].xy" SAMPLE_BIAS ")", stage, coordinates, stage -#ifdef HALO_ANDROID +#ifdef HALO_GLES , stage #endif ); @@ -594,7 +594,7 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key) if (combiner_count > 8) combiner_count = 8; -#ifdef HALO_ANDROID +#ifdef HALO_GLES xgpu_text_append(&text, "#version %s\n", xgpu_capabilities.shading_language); if (key->count_samples) { @@ -741,7 +741,7 @@ char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key) xgpu_text_append(&text, "\tresult = vec4(t0.rgb, 1.0);\n"); if (config_boolean("debug.gpu_debug_flat")) xgpu_text_append(&text, "\tresult = xD0.a > 0.0 ? vec4(xD0.rgb, 1.0) : vec4(1.0, 0.0, 1.0, 1.0);\n"); -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (key->count_samples) xgpu_text_append(&text, "\tatomicCounterIncrement(visible_samples);\n"); #endif diff --git a/port/linux/src/nv2a_vsh.c b/port/linux/src/nv2a_vsh.c index 0910f39f..a80ae02f 100644 --- a/port/linux/src/nv2a_vsh.c +++ b/port/linux/src/nv2a_vsh.c @@ -329,7 +329,7 @@ BOOL nv2a_vertex_shader_lighting(const DWORD *instructions, unsigned long instru /* ---------- translation */ static const char shader_prologue[] = -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* the #version line comes first, from the context's capabilities */ "precision highp float;\n" "precision highp int;\n" @@ -389,7 +389,7 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru struct xgpu_text text = { 0 }; unsigned long index; -#ifdef HALO_ANDROID +#ifdef HALO_GLES xgpu_text_append(&text, "#version %s\n", xgpu_capabilities.shading_language); if (multiview) { @@ -582,7 +582,7 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru "\tif (!(abs(position.w) > 0.0))\n" "\t\tposition = vec4(0.0, 0.0, 0.0, -1.0);\n" "\tgl_Position = position;\n" -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* what glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE) does on desktop GL: rows from the top, depth 0..1 */ "\tgl_Position.y = -gl_Position.y;\n" diff --git a/port/linux/src/p2p.c b/port/linux/src/p2p.c index 9652a3da..11a57ab3 100644 --- a/port/linux/src/p2p.c +++ b/port/linux/src/p2p.c @@ -58,6 +58,9 @@ only look up and create stand-ins. #include "port_config.h" #include "p2p_internal.h" #include "ikcp.h" +#ifdef HALO_PROFILE +#include "profile_trace.h" +#endif #include #include @@ -276,6 +279,32 @@ struct stun_server pthread_mutex_t p2p_lock = PTHREAD_MUTEX_INITIALIZER; +#ifdef HALO_PROFILE +/* the p2p thread's own track of the profiling build's recording +(profile_trace.c): profile.c's sections are the game thread's alone */ +static struct +{ + int pass; + int tunnel_receive; + int kcp_update; + int streams; +} p2p_profile_names; + +/* the recording's track lock: the game thread flips the recording and its +arenas under it, and this thread records under it, so each record lands in +one arena; a pass that drops the lock (DNS, key exchange) can straddle a +cut, and profile_trace_end drops that scope */ +void p2p_profile_lock(void) +{ + pthread_mutex_lock(&p2p_lock); +} + +void p2p_profile_unlock(void) +{ + pthread_mutex_unlock(&p2p_lock); +} +#endif + static struct { int running; @@ -2971,6 +3000,13 @@ static void *p2p_thread(void *unused) static int read_owners[MAXIMUM_SOCKETS], write_owners[MAXIMUM_SOCKETS]; (void)unused; +#ifdef HALO_PROFILE + profile_trace_thread_register(_profile_track_p2p); + p2p_profile_names.pass = profile_trace_name("p2p.pass"); + p2p_profile_names.tunnel_receive = profile_trace_name("p2p.tunnel_receive"); + p2p_profile_names.kcp_update = profile_trace_name("p2p.kcp_update"); + p2p_profile_names.streams = profile_trace_name("p2p.streams"); +#endif pthread_mutex_lock(&p2p_lock); #ifndef HALO_ANDROID /* (here: it may wait for a program) */ @@ -3050,6 +3086,9 @@ static void *p2p_thread(void *unused) read_count = write_count = 0; } pthread_mutex_lock(&p2p_lock); +#ifdef HALO_PROFILE + profile_trace_begin(p2p_profile_names.pass); +#endif /* what is ready (the lists now hold only ready sockets, in the order asked, so each one's owner is found going along both); a socket @@ -3068,7 +3107,13 @@ static void *p2p_thread(void *unused) switch (owner & 255) { case _owner_tunnel: +#ifdef HALO_PROFILE + profile_trace_begin(p2p_profile_names.tunnel_receive); +#endif tunnel_readable(); +#ifdef HALO_PROFILE + profile_trace_end(p2p_profile_names.tunnel_receive); +#endif break; case _owner_handoff: if (socket == p2p.handoff_socket) @@ -3088,6 +3133,9 @@ static void *p2p_thread(void *unused) break; } } +#ifdef HALO_PROFILE + profile_trace_begin(p2p_profile_names.streams); +#endif for (index = 0, asked = 0; index < write_count; index++) { int socket = write[index]; @@ -3112,13 +3160,22 @@ static void *p2p_thread(void *unused) if (stream->used && stream->state == _stream_connecting && elapsed(stream->created_time, 5000)) stream_local_closed(stream); } +#ifdef HALO_PROFILE + profile_trace_end(p2p_profile_names.streams); +#endif p2p_signal_update(read, read_count, write, write_count); +#ifdef HALO_PROFILE + profile_trace_begin(p2p_profile_names.kcp_update); +#endif for (index = 0; index < MAXIMUM_STREAMS; index++) { if (p2p.streams[index].used) stream_update(&p2p.streams[index]); } +#ifdef HALO_PROFILE + profile_trace_end(p2p_profile_names.kcp_update); +#endif update_peers(); expire_proxies(); stun_update(); @@ -3146,6 +3203,9 @@ static void *p2p_thread(void *unused) p2p_discord_update(); #ifdef HALO_ANDROID poll_invite_file(); +#endif +#ifdef HALO_PROFILE + profile_trace_end(p2p_profile_names.pass); #endif } return NULL; diff --git a/port/linux/src/platform.h b/port/linux/src/platform.h index 504cbea2..31a3b335 100644 --- a/port/linux/src/platform.h +++ b/port/linux/src/platform.h @@ -114,7 +114,7 @@ that window at start-up and hands out page-granular blocks from it, so the physical/virtual arithmetic the game and Direct3D rely on keeps working. */ #define PLATFORM_CONTIGUOUS_BASE 0x80000000UL -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST /* 128 MB, a development kit's: Android's guest image is linked just above the window (port/android/include/halo_android_abi.h) */ #define PLATFORM_CONTIGUOUS_SIZE 0x08000000UL @@ -162,6 +162,10 @@ unsigned long memory_watch_serial(void); void memory_watch_prepare_write(void *address, unsigned long size); /* the range was remapped or reprotected: treat it as written and unwatched */ void memory_watch_forget(void *address, unsigned long size); +/* a new frame starts (Present): tracking that compares page contents +(Android under ARM translation, host_watch_hash.h) hashes a page at most +once a frame, so it sees a write in the next frame, not at once */ +void memory_watch_begin_frame(void); /* ---------- time */ diff --git a/port/linux/src/port_config.c b/port/linux/src/port_config.c index 16c158b2..896d899e 100644 --- a/port/linux/src/port_config.c +++ b/port/linux/src/port_config.c @@ -38,9 +38,9 @@ enum config_environment /* the variable's text is the value ("0", "false", "no" and "off" are false for a boolean) */ _environment_value, - /* the variable being set at all makes it true */ + /* the variable being set (not empty, "0", "false", "no" or "off") makes it true */ _environment_set_is_true, - /* the variable being set at all makes it false */ + /* the variable being set (not empty, "0", "false", "no" or "off") makes it false */ _environment_set_is_false, }; @@ -60,6 +60,7 @@ struct config_setting enum config_type type; /* as it is written in the file */ const char *default_value; + /* NULL: none, for a setting the Android app reads from the file itself */ const char *environment; enum config_environment environment_style; unsigned platforms; @@ -112,6 +113,17 @@ static const struct config_setting config_settings[] = "In first person, point the view where the player aims now instead of\n" "where the last tick left it: the view turns the frame the mouse moves,\n" "not up to two ticks (66 ms) later." }, + { "display.fov", _config_real, "0.0", "HALO_FOV", _environment_value, _platform_all, + "The first-person view's field of view on foot, in degrees across at\n" + "16:9 (20 to 150); 0 keeps the stock view. Vehicles, cinematics and\n" + "scripted cameras keep their own." }, + { "display.viewmodel_fov", _config_real, "0.0", "HALO_VIEWMODEL_FOV", _environment_value, _platform_all, + "The first-person weapon's and hands' field of view, in degrees across\n" + "at 16:9 (20 to 150); 0 keeps the weapon's stock view, also when\n" + "display.fov widens the world." }, + { "display.viewmodel_visible", _config_boolean, "true", "HALO_VIEWMODEL_VISIBLE", _environment_value, _platform_all, + "Draw the first-person weapon, hands and what is attached to them.\n" + "Off, they are not drawn; firing, animation, sound and lights go on." }, { "display.high_res_hud", _config_boolean, "true", "HALO_HIGH_RES_HUD", _environment_value, _platform_all, "Draw the HUD (meters, counters, panels, motion sensor, reticles,\n" "waypoints, scopes) from the high-res assets (8x the maps' bitmaps);\n" @@ -385,6 +397,21 @@ static const struct config_setting config_settings[] = "Reverberate the world's sounds as the place the player is in does (the\n" "maps' sound environments, as the Xbox's I3DL2 reverb did); false keeps\n" "them dry." }, + { "audio.voice_chat", _config_string, "\"push_to_talk\"", "HALO_VOICE_CHAT", _environment_value, _platform_all, + "Talking in network games' voice chat: \"push_to_talk\" (while\n" + "controls.push_to_talk is held; the microphone opens the first time),\n" + "\"open_mic\" (whenever the microphone hears speech), or \"off\". Others'\n" + "voices play whatever this is (audio.voice_volume 0 silences them)." }, + { "audio.voice_volume", _config_real, "1.0", "HALO_VOICE_VOLUME", _environment_value, _platform_all, + "The volume of the other players' voices (0 to 2)." }, + { "audio.output_device", _config_string, "\"default\"", "HALO_AUDIO_OUTPUT_DEVICE", _environment_value, + _platform_desktop, + "Where the game's sound and the voices play: a device's name as Settings >\n" + "Audio lists it, or \"default\" for the system's (also if it is not found)." }, + { "audio.input_device", _config_string, "\"default\"", "HALO_AUDIO_INPUT_DEVICE", _environment_value, + _platform_desktop, + "The microphone voice chat listens to: a device's name as Settings >\n" + "Audio lists it, or \"default\" for the system's (also if it is not found)." }, { "audio.loose_sounds", _config_boolean, "false", "HALO_LOOSE_SOUNDS", _environment_value, _platform_all, "For those making sounds: play each of a map's sounds that has a Halo PC\n" "sound tag file of its name under the data root's tags folder\n" @@ -392,6 +419,15 @@ static const struct config_setting config_settings[] = "loose_sounds_reload reads the files again and loose_sounds false gives\n" "the map's sounds back." }, + { "input.touch_controls", _config_string, "\"auto\"", "HALO_TOUCH_CONTROLS", _environment_value, _platform_android, + "The on-screen touch controls in a game: \"auto\" shows them on a\n" + "touchscreen while no controller is connected, \"on\" also with a\n" + "controller, \"off\" never. A device without a touchscreen never shows\n" + "them. The menus take taps in any case." }, + { "input.touch_aim_assist", _config_boolean, "true", "HALO_TOUCH_AIM_ASSIST", _environment_value, _platform_android, + "The touch controls' swipe aiming gets a controller's aim assist: the\n" + "aim slows over a target and follows a moving one. false: none, as a\n" + "mouse (the bullets' own autoaim stays)." }, { "input.mouse_sensitivity", _config_real, "1.0", "HALO_MOUSE_SENSITIVITY", _environment_value, _platform_desktop, "How far the view turns for the mouse's movement." }, { "input.invert_mouse", _config_boolean, "false", "HALO_MOUSE_INVERT", _environment_set_is_true, _platform_desktop, @@ -449,6 +485,10 @@ static const struct config_setting config_settings[] = "Showing the scores (the controller's Back)." }, { "controls.pause", _config_string, "\"Escape\"", "HALO_KEY_PAUSE", _environment_value, _platform_all, "The pause menu (the controller's Start)." }, + { "controls.screenshot", _config_string, "\"F10\"", "HALO_KEY_SCREENSHOT", _environment_value, _platform_all, + "Save a PNG screenshot beside maps/ (press once per capture)." }, + { "controls.push_to_talk", _config_string, "\"V\"", "HALO_KEY_PUSH_TO_TALK", _environment_value, _platform_all, + "Voice chat: talk while it is held (audio.voice_chat \"push_to_talk\")." }, { "game.console_log", _config_string, "\"important\"", "HALO_CONSOLE_LOG", _environment_value, _platform_all, "What the game's console shows on screen of what it logs: \"important\"\n" @@ -460,6 +500,12 @@ static const struct config_setting config_settings[] = { "game.language", _config_string, "\"\"", "HALO_LANGUAGE", _environment_value, _platform_all, "The language the game asks the Xbox for: \"ja\", \"de\", \"fr\", \"es\" or \"it\";\n" "empty for English. The game data decides what is translated." }, + { "game.enhanced_animations", _config_boolean, "true", "HALO_ENHANCED_ANIMATIONS", _environment_value, _platform_all, + "The player bipeds' grenade throws keep their legs moving (crouched,\n" + "in the air and in a vehicle's seat too), riders' hands leave the grips\n" + "to throw and reload, and a player turns with the aim while throwing;\n" + "false: the original animations, which freeze the legs and stand a\n" + "rider up." }, { "game.custom_edition", _config_boolean, "true", "HALO_CUSTOM_EDITION", _environment_value, _platform_all, "Load and run Halo Custom Edition maps (not those that need OpenSauce):\n" "put them and Custom Edition's bitmaps.map, sounds.map and loc.map in\n" @@ -516,6 +562,35 @@ static const struct config_setting config_settings[] = "in everyone's server browser: anyone can see and join it) or, false,\n" "PRIVATE (only players with its invite link can join). Server Setup's\n" "LISTING changes it for each game." }, + { "network.voice_lobby", _config_boolean, "true", "HALO_NET_VOICE_LOBBY", _environment_value, _platform_all, + "Hosting: voice chat in the lobby, before and after a game, where every\n" + "player hears every other." }, + { "network.voice_mode", _config_string, "\"team_global_enemy_proximity\"", "HALO_NET_VOICE_MODE", + _environment_value, _platform_all, + "Hosting: voice chat in a game: \"off\"; \"team_proximity\" (teammates\n" + "near); \"team_enemy_proximity\" (anyone near); \"team_global\" (all\n" + "teammates); or \"team_global_enemy_proximity\" (all teammates, and\n" + "enemies near). A game without teams has only enemies; co-op only\n" + "teammates." }, + { "network.voice_kbps", _config_integer, "24", "HALO_NET_VOICE_KBPS", _environment_value, _platform_all, + "Hosting: the voices' quality, in the lobby and in a game, in kilobits a\n" + "second (8 to 64)." }, + { "network.voice_proximity", _config_real, "15.0", "HALO_NET_VOICE_PROXIMITY", _environment_value, + _platform_all, + "Hosting: how near (world units: 1 is about 3 metres) a player must be\n" + "to be heard by proximity voice chat (5 to 100)." }, + { "network.votekick", _config_boolean, "true", "HALO_NET_VOTEKICK", _environment_value, _platform_all, + "Hosting: let the players vote to kick a player (the scoreboard's\n" + "right-click, or the console's votekick). More than half of the players\n" + "must vote, counted once per address." }, + { "network.votekick_minutes", _config_integer, "5", "HALO_NET_VOTEKICK_MINUTES", _environment_value, + _platform_all, + "Hosting: the minutes a player must have played on this server to start\n" + "a vote to kick (0 to 60); to vote, 2 minutes or this, the less." }, + { "network.votekick_ban_minutes", _config_integer, "30", "HALO_NET_VOTEKICK_BAN_MINUTES", _environment_value, + _platform_all, + "Hosting: the minutes a player kicked by a vote cannot join again (1 to\n" + "1440)." }, { "network.coop_public", _config_boolean, "false", "HALO_NET_COOP_PUBLIC", _environment_value, _platform_all, "Whether an online co-op game (Create Game > Internet, a SINGLEPLAYER\n" "map) starts as PUBLIC or, false, PRIVATE: Server Setup's LISTING in\n" @@ -523,32 +598,32 @@ static const struct config_setting config_settings[] = { "network.coop_friendly_fire", _config_string, "\"on\"", "HALO_NET_COOP_FRIENDLY_FIRE", _environment_value, _platform_all, "Whether the players of an online co-op game hurt each other: \"off\",\n" - "\"on\", \"shields_only\" or \"explosives_only\" (Server Setup's FRIENDLY\n" - "FIRE in co-op, which writes its choice here). Their AI allies they\n" - "always can, as in the campaign." }, + "\"on\", \"shields_only\" or \"explosives_only\" (FRIENDLY FIRE in\n" + "co-op's Server Setup > Co-op Options writes its choice here). Their AI\n" + "allies they always can, as in the campaign." }, { "network.coop_player_collisions", _config_boolean, "true", "HALO_NET_COOP_PLAYER_COLLISIONS", _environment_value, _platform_all, "Whether the players of an online co-op game bump into each other;\n" "false, they walk through each other (the AI's characters they still\n" - "bump into). Server Setup's PLAYER COLLISIONS in co-op writes its\n" - "choice here." }, + "bump into). PLAYER COLLISIONS in co-op's Server Setup > Co-op Options\n" + "writes its choice here." }, { "network.coop_enemies_mode", _config_string, "\"per_player\"", "HALO_NET_COOP_ENEMIES_MODE", _environment_value, _platform_all, "Online co-op's extra enemies: \"none\", \"per_player\" (each squad of\n" "enemies grows by coop_enemies for each player past the first) or\n" "\"multiplier\" (each is coop_enemies_multiplier times as large, for any\n" - "number of players). Server Setup's EXTRA ENEMIES in co-op writes its\n" - "choice here." }, + "number of players). EXTRA ENEMIES in co-op's Server Setup > Co-op\n" + "Options writes its choice here." }, { "network.coop_enemies", _config_integer, "50", "HALO_NET_COOP_ENEMIES", _environment_value, _platform_all, "Online co-op's extra enemies per player, a percentage: for each player\n" "past the first, each squad of enemies a level places gets this much of\n" - "itself more (100: as many again; 25 to 200). Server Setup's PER PLAYER\n" - "in co-op writes its choice here." }, + "itself more (100: as many again; 25 to 200). PER PLAYER in co-op's\n" + "Server Setup > Co-op Options writes its choice here." }, { "network.coop_enemies_multiplier", _config_integer, "2", "HALO_NET_COOP_ENEMIES_MULTIPLIER", _environment_value, _platform_all, "Online co-op's static multiplier of its enemies: each squad of enemies\n" - "a level places is this many times as large (2 to 32). Server Setup's\n" - "MULTIPLIER in co-op writes its choice here." }, + "a level places is this many times as large (2 to 32). MULTIPLIER in\n" + "co-op's Server Setup > Co-op Options writes its choice here." }, { "network.brokers_file", _config_string, "\"brokers.txt\"", "HALO_NET_BROKERS_FILE", _environment_value, _platform_all, "The file of the public MQTT brokers through which the machines of an\n" @@ -611,6 +686,12 @@ static const struct config_setting config_settings[] = _platform_all, "The port of the script console (telnet_console); the Xbox's was 23, which\n" "only the administrator can listen on." }, + { "debug.touch_targets", _config_boolean, "false", "HALO_TOUCH_TARGETS", _environment_set_is_true, _platform_all, + "Outline the menus' tap targets (item green, value blue, list slot yellow,\n" + "legend button red, the band beside a list's slots orange; the virtual\n" + "keyboard's keys white), mark where the last finger went down and the\n" + "last tap landed for 3 seconds, and log each tap with the target it hit\n" + "(and a value's split); to judge touch accuracy." }, { "debug.network_latency", _config_real, "0.0", "HALO_NETWORK_LATENCY", _environment_value, _platform_all, "Milliseconds everything received is held back (a round trip between two\n" "machines of twice it), to test the netcode as over the internet; 0 none." }, @@ -657,6 +738,9 @@ static const struct config_setting config_settings[] = "Quit this many seconds after the window opens; 0 never." }, { "debug.hidden_window", _config_boolean, "false", "HALO_HIDDEN_WINDOW", _environment_set_is_true, _platform_desktop, "Keep the window hidden (and never fullscreen)." }, + { "debug.voice_test", _config_boolean, "false", "HALO_VOICE_TEST", _environment_set_is_true, _platform_all, + "Voice chat's automated tests: a tone instead of the microphone, and\n" + "each voice heard logged once a second." }, { "debug.null_renderer", _config_boolean, "false", "HALO_NULL_RENDERER", _environment_set_is_true, _platform_all, "Run without a window, drawing nothing." }, { "debug.gl_debug", _config_boolean, "false", "HALO_GL_DEBUG", _environment_set_is_true, _platform_all, @@ -701,6 +785,26 @@ static const struct config_setting config_settings[] = { "debug.sample_seconds", _config_real, "0.0", "HALO_SAMPLE", _environment_value, _platform_android, "Log where every game thread is this often, in seconds (read by the\n" "app, port/android/host/host_debug.c); 0 never." }, + { "debug.memory_watch", _config_boolean, "true", NULL, _environment_value, _platform_android, + "Notice the game's writes to cached textures and vertices by page\n" + "protection; false compares page contents once a frame instead, which is\n" + "slower. Under ARM translation (the x86 emulator) the app always compares\n" + "contents. Read by the app from the file (port/android/host/host_main.c)." }, +#ifdef HALO_PROFILE + { "debug.profile_record", _config_boolean, "false", "HALO_PROFILE_RECORD", _environment_value, _platform_all, + "Record a profile with no command (configure.py --profile builds): from\n" + "when profile_record_when says until profile_stop, a map change or the\n" + "end, into numbered part files in the data folder's profiles folder\n" + "(tools/net_report.py reads it)." }, + { "debug.profile_record_when", _config_string, "\"start\"", "HALO_PROFILE_RECORD_WHEN", _environment_value, + _platform_all, + "\"start\": from the first frame until the first map change; \"game\":\n" + "each game that is not the main menu, a recording each, until its map\n" + "goes." }, + { "debug.profile_memory", _config_integer, "256", "HALO_PROFILE_MEMORY", _environment_value, _platform_all, + "Megabytes a recording keeps in memory, 4 to 1024: two halves, each\n" + "written out as a part when it fills." }, +#endif }; #define NUMBER_OF_CONFIG_SETTINGS (sizeof(config_settings) / sizeof(config_settings[0])) @@ -745,7 +849,7 @@ static void config_path(char *path, size_t size) /* the whole file, NUL terminated, or NULL; free() it */ static char *config_read_file(const char *path, size_t *size) { -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST FILE *file = fopen(path, "rb"); char *text = NULL; long length; @@ -788,7 +892,7 @@ static char *config_read_file(const char *path, size_t *size) static int config_write_file(const char *path, const char *text) { -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST /* written beside it and renamed over it: a file the app may not write (one pushed by adb belongs to the shell) is replaced all the same, as the folder is the app's, and a write cut short leaves the old file whole */ @@ -1022,6 +1126,13 @@ static int config_text_is_false(const char *text) return !strcmp(lower, "0") || !strcmp(lower, "false") || !strcmp(lower, "no") || !strcmp(lower, "off"); } +/* whether a variable that only has to be set (_environment_set_is_true or +_environment_set_is_false) is: empty, "0", "false", "no" or "off" is not */ +static int config_environment_set(const char *text) +{ + return text[0] && !config_text_is_false(text); +} + static void config_set_from_text(struct config_value *value, enum config_type type, const char *text) { switch (type) @@ -1201,9 +1312,9 @@ static void config_load(void) for (index = 0; index < NUMBER_OF_CONFIG_SETTINGS; index++) { const struct config_setting *setting = &config_settings[index]; - const char *environment = getenv(setting->environment); + const char *environment = setting->environment ? getenv(setting->environment) : NULL; - if (!environment) + if (!environment || (setting->environment_style != _environment_value && !config_environment_set(environment))) continue; switch (setting->environment_style) { diff --git a/port/linux/src/posix_trace_marker.c b/port/linux/src/posix_trace_marker.c index 4a0a853e..764f41ee 100644 --- a/port/linux/src/posix_trace_marker.c +++ b/port/linux/src/posix_trace_marker.c @@ -8,7 +8,8 @@ buffer for each traced driver function while tracing is on, and SteamOS keeps tracing on for its GPU performance captures (gpu-trace.service). On the Steam Frame that is some 480,000 writes a second: the driver's thread spends its time in them, and the game ran at about 50 frames a second instead of -the headset's 72. The Steam Deck runs the same service (not measured there). A +the headset's 72. The Steam Deck runs the same service, but its Mesa (25.3) +has no markers to write, so there this changes nothing (measured). A trace_marker that cannot be opened turns the markers off (gpuvis then skips them). diff --git a/port/linux/src/posix_update.c b/port/linux/src/posix_update.c index f7fabc96..ca411c8e 100644 --- a/port/linux/src/posix_update.c +++ b/port/linux/src/posix_update.c @@ -340,11 +340,19 @@ static int read_body(struct connection *connection, struct download *download, i for (;;) { char line[128]; + char *after; unsigned long long remaining; if (!connection_read_line(connection, line, sizeof(line))) return 0; - remaining = strtoull(line, NULL, 16); + /* (a size in hexadecimal, perhaps with extensions after a ";": a + line without one is no chunk, and not the last) */ + remaining = strtoull(line, &after, 16); + if (after == line || (*after && *after != ';' && *after != ' ' && *after != '\t' && + *after != '\r' && *after != '\n')) + { + return 0; + } if (!remaining) return 1; while (remaining) diff --git a/port/linux/src/posix_upnp.c b/port/linux/src/posix_upnp.c index 30545da7..28e75e07 100644 --- a/port/linux/src/posix_upnp.c +++ b/port/linux/src/posix_upnp.c @@ -151,8 +151,9 @@ static int upnp_find_router(char *error, int error_size) upnp_remove_left_forwardings(); return 1; case UPNP_PRIVATEIP_IGD: + /* (not the address itself: logs and screens show none) */ snprintf(error, (size_t)error_size, - "the router's internet address (%s) is a private one: another NAT is beyond it", wan_address); + "the router's internet address is a private one: another NAT is beyond it"); break; case UPNP_DISCONNECTED_IGD: snprintf(error, (size_t)error_size, "the UPnP router is not connected to the internet"); diff --git a/port/linux/src/profile_json.c b/port/linux/src/profile_json.c new file mode 100644 index 00000000..5942eb38 --- /dev/null +++ b/port/linux/src/profile_json.c @@ -0,0 +1,522 @@ +/* +PROFILE_JSON.C + +Write recording parts on the writer thread. +*/ + +#ifdef HALO_PROFILE + +#include "profile_part.h" + +#include +#include +#include + +/* ---------- constants */ + +enum +{ + PROFILE_JSON_WORST_FRAMES = 10, + PROFILE_JSON_LONGEST_CHILDREN = 3, +}; + +/* ---------- structures */ + +/* where the next item of a list goes: a comma and a line before all but +the first */ +struct profile_json_list +{ + FILE *file; + int items; +}; + +struct profile_json_name_totals +{ + unsigned long long count; + unsigned long long total; + unsigned long long maximum; +}; + +/* ---------- globals */ + +static const char *profile_json_stop_reasons[] = { "", "command", "seconds", "map_load", "exit" }; + +/* ---------- private code */ + +static void profile_json_item( + struct profile_json_list *list) +{ + if (list->items++) + fputs(",\n", list->file); +} + +static void profile_json_escaped( + FILE *file, + const char *text) +{ + for (; text && *text; text++) + { + unsigned char character = (unsigned char)*text; + + if (character == '"' || character == '\\') + fprintf(file, "\\%c", character); + else if (character < 32 || character > 126) + fprintf(file, "\\u%04x", character); + else + fputc(character, file); + } +} + +static void profile_json_string( + FILE *file, + const char *text) +{ + fputc('"', file); + profile_json_escaped(file, text); + fputc('"', file); +} + +/* microseconds with three decimals, as the trace event format has them */ +static void profile_json_time( + FILE *file, + unsigned long long nanoseconds) +{ + fprintf(file, "%llu.%03llu", nanoseconds / 1000ULL, nanoseconds % 1000ULL); +} + +static int profile_json_compare_records( + const void *a, + const void *b) +{ + const struct profile_trace_record *first = a; + const struct profile_trace_record *second = b; + + if (first->start != second->start) + return first->start < second->start ? -1 : 1; + return (int)first->depth - (int)second->depth; +} + +static int profile_json_name_of( + const struct profile_part *part, + const char *text) +{ + int name; + + for (name = 0; name < part->name_count; name++) + { + if (strcmp(profile_trace_name_text(name), text) == 0) + return name; + } + return -1; +} + +static void profile_json_header( + const struct profile_part *part, + FILE *file) +{ + const struct profile_trace_session *session = &part->session; + + fputs("\"header\": {\"format\": 1, \"build\": ", file); + profile_json_string(file, session->build); + fputs(", \"platform\": ", file); + profile_json_string(file, session->platform); + fputs(", \"role\": ", file); + profile_json_string(file, session->role); + fprintf(file, ", \"own_machine\": %ld, \"map\": ", session->own_machine); + profile_json_string(file, session->map); + fputs(", \"map_name\": ", file); + profile_json_string(file, session->map_name); + fputs(", \"gametype\": ", file); + profile_json_string(file, session->gametype); + fprintf(file, ", \"players\": %ld, \"players_most\": %ld, \"start_utc\": ", session->players, + session->players_most); + profile_json_string(file, part->start_utc); + fputs(", \"recording\": ", file); + profile_json_string(file, part->name); + fprintf(file, ", \"part\": %ld, \"last_part\": %s" + ", \"first_frame\": %ld, \"frames\": %ld, \"first_tick\": %ld, \"ticks\": %ld", + part->number, part->last ? "true" : "false", + part->first_frame, part->frames, part->first_tick, part->ticks); + fprintf(file, ", \"start_s\": %.6f, \"duration_s\": %.6f, \"stop_reason\": ", + (double)part->start_ns / 1e9, (double)(part->end_ns - part->start_ns) / 1e9); + profile_json_string(file, profile_json_stop_reasons[part->stop_reason]); + fprintf(file, ", \"clock_read_ns\": %.1f, \"memory_used\": %lu, \"memory_limit\": %lu, \"writer_wait_ms\": %.3f" + ", \"foreign_scopes\": %lu, \"deep_scopes\": %lu, \"unbalanced_scopes\": %lu, \"dropped_scopes\": %lu}", + part->clock_read_ns, part->memory_used, part->memory_limit, part->writer_wait_ms, + part->foreign_scopes, part->deep_scopes, part->unbalanced_scopes, part->dropped_scopes); +} + +/* a worst frame's scopes: the open ones (one a depth, so no more than the +depth limit) and the three with the most time of their own so far */ +struct profile_json_ranking +{ + unsigned long scope[MAXIMUM_PROFILE_TRACE_DEPTH]; + unsigned long long children[MAXIMUM_PROFILE_TRACE_DEPTH]; + int open; + unsigned long longest[PROFILE_JSON_LONGEST_CHILDREN]; + unsigned int own[PROFILE_JSON_LONGEST_CHILDREN]; + int count; +}; + +/* the innermost open scope ends: its time less its children's is ranked, and +all of it is its parent's child time */ +static void profile_json_close( + struct profile_json_ranking *ranking, + const struct profile_trace_record *game) +{ + int open = --ranking->open; + unsigned long scope = ranking->scope[open]; + unsigned long long duration = game[scope].duration; + unsigned int own = (unsigned int)(duration > ranking->children[open] ? duration - ranking->children[open] : 0); + int slot; + + for (slot = ranking->count; slot > 0 && ranking->own[slot - 1] < own; slot--) + { + if (slot < PROFILE_JSON_LONGEST_CHILDREN) + { + ranking->longest[slot] = ranking->longest[slot - 1]; + ranking->own[slot] = ranking->own[slot - 1]; + } + } + if (slot < PROFILE_JSON_LONGEST_CHILDREN) + { + ranking->longest[slot] = scope; + ranking->own[slot] = own; + if (ranking->count < PROFILE_JSON_LONGEST_CHILDREN) + ranking->count++; + } + if (open > 0) + ranking->children[open - 1] += duration; +} + +static void profile_json_cpu_summary( + const struct profile_part *part, + FILE *file) +{ + struct profile_json_name_totals totals[MAXIMUM_PROFILE_TRACE_NAMES]; + const struct profile_trace_record *game = part->records[_profile_track_game]; + unsigned long game_count = part->record_counts[_profile_track_game]; + unsigned long worst[PROFILE_JSON_WORST_FRAMES]; + long worst_frame_number[PROFILE_JSON_WORST_FRAMES]; + int worst_count = 0; + struct profile_json_ranking ranking; + int frame_name = profile_json_name_of(part, "frame"); + int tick_name = profile_json_name_of(part, "game_tick"); + struct profile_json_list list; + unsigned long index; + long frames = 0; + int track, name; + + memset(totals, 0, sizeof(totals)); + for (track = 0; track < NUMBER_OF_PROFILE_TRACKS; track++) + { + for (index = 0; index < part->record_counts[track]; index++) + { + const struct profile_trace_record *record = &part->records[track][index]; + struct profile_json_name_totals *name_totals; + + if (record->name >= MAXIMUM_PROFILE_TRACE_NAMES) + continue; + name_totals = &totals[record->name]; + if (record->depth < MAXIMUM_PROFILE_TRACE_DEPTH || record->depth == PROFILE_TRACE_DEPTH_AGGREGATE_TIME) + { + name_totals->total += record->duration; + if (record->duration > name_totals->maximum) + name_totals->maximum = record->duration; + } + if (record->depth < MAXIMUM_PROFILE_TRACE_DEPTH) + name_totals->count++; + else if (record->depth == PROFILE_TRACE_DEPTH_AGGREGATE_COUNT) + name_totals->count += record->duration; + } + } + /* the worst frames: the game track is sorted, so a frame's children + follow it */ + for (index = 0; index < game_count; index++) + { + int slot; + + if (game[index].depth >= MAXIMUM_PROFILE_TRACE_DEPTH || game[index].name != frame_name) + continue; + frames++; + for (slot = worst_count; slot > 0 && game[worst[slot - 1]].duration < game[index].duration; slot--) + { + if (slot < PROFILE_JSON_WORST_FRAMES) + { + worst[slot] = worst[slot - 1]; + worst_frame_number[slot] = worst_frame_number[slot - 1]; + } + } + if (slot < PROFILE_JSON_WORST_FRAMES) + { + worst[slot] = index; + worst_frame_number[slot] = part->first_frame + frames - 1; + if (worst_count < PROFILE_JSON_WORST_FRAMES) + worst_count++; + } + } + + fprintf(file, "{\"frames\": %ld, \"ticks\": %ld, \"columns\": [\"name\", \"count\", \"total_ms\", " + "\"mean_ms\", \"max_ms\", \"per_frame\", \"per_tick_ms\"], \"rows\": [\n", frames, part->ticks); + list.file = file; + list.items = 0; + for (name = 0; name < part->name_count && name < MAXIMUM_PROFILE_TRACE_NAMES; name++) + { + const char *text = profile_trace_name_text(name); + struct profile_json_name_totals const *name_totals = &totals[name]; + int per_tick = strncmp(text, "game_tick", 9) == 0 || strncmp(text, "network_distributed_tick", 24) == 0; + + if (!name_totals->count) + continue; + profile_json_item(&list); + fputc('[', file); + profile_json_string(file, text); + fprintf(file, ", %llu, %.3f, %.4f, %.3f, %.3f, ", name_totals->count, name_totals->total / 1e6, + name_totals->total / 1e6 / (double)name_totals->count, name_totals->maximum / 1e6, + frames ? (double)name_totals->count / (double)frames : 0.0); + if (per_tick && part->ticks) + fprintf(file, "%.4f]", name_totals->total / 1e6 / (double)part->ticks); + else + fputs("null]", file); + } + fputs(list.items ? "\n]" : "]", file); + + fputs(", \"worst_frames\": {\"columns\": [\"frame\", \"at_s\", \"frame_ms\", \"ticks\", \"longest\"], \"rows\": [\n", file); + list.items = 0; + for (index = 0; index < (unsigned long)worst_count; index++) + { + const struct profile_trace_record *frame = &game[worst[index]]; + unsigned long long end = frame->start + frame->duration; + int ticks = 0; + unsigned long child; + int slot; + + /* (a tick sits under whatever the main loop wraps it in, so every + depth of the frame counts; the scopes are ranked by the time they have + of their own, or the parents, which hold all of it, would always come + first. The open scopes are a stack, so nothing is allocated for it) */ + memset(&ranking, 0, sizeof(ranking)); + for (child = worst[index] + 1; child < game_count && game[child].start < end; child++) + { + if (game[child].depth >= MAXIMUM_PROFILE_TRACE_DEPTH || game[child].depth <= frame->depth) + continue; + if (game[child].name == tick_name) + ticks++; + while (ranking.open && game[ranking.scope[ranking.open - 1]].depth >= game[child].depth) + profile_json_close(&ranking, game); + ranking.scope[ranking.open] = child; + ranking.children[ranking.open++] = 0; + } + while (ranking.open) + profile_json_close(&ranking, game); + profile_json_item(&list); + fprintf(file, "[%ld, %.3f, %.3f, %d, [", worst_frame_number[index], frame->start / 1e9, frame->duration / 1e6, + ticks); + for (slot = 0; slot < ranking.count; slot++) + { + fputs(slot ? ", [" : "[", file); + profile_json_string(file, profile_trace_name_text(game[ranking.longest[slot]].name)); + fprintf(file, ", %.3f]", ranking.own[slot] / 1e6); + } + fputs("]]", file); + } + fputs(list.items ? "\n]}}" : "]}}", file); +} + +static void profile_json_event( + FILE *file, + struct profile_json_list *list, + const struct profile_trace_record *record) +{ + const char *name = profile_trace_name_text(record->name); + + profile_json_item(list); + switch (record->depth) + { + case PROFILE_TRACE_DEPTH_AGGREGATE_TIME: + case PROFILE_TRACE_DEPTH_AGGREGATE_COUNT: + fputs("{\"ph\":\"C\",\"name\":\"", file); + profile_json_escaped(file, name); + fputs(record->depth == PROFILE_TRACE_DEPTH_AGGREGATE_TIME ? "_ms\",\"pid\":1,\"tid\":1,\"ts\":" : + "_count\",\"pid\":1,\"tid\":1,\"ts\":", file); + profile_json_time(file, record->start); + if (record->depth == PROFILE_TRACE_DEPTH_AGGREGATE_TIME) + fprintf(file, ",\"args\":{\"value\":%.4f}}", record->duration / 1e6); + else + fprintf(file, ",\"args\":{\"value\":%u}}", record->duration); + break; + default: + fputs("{\"ph\":\"X\",\"name\":", file); + profile_json_string(file, name); + fprintf(file, ",\"pid\":1,\"tid\":%d,\"ts\":", record->track + 1); + profile_json_time(file, record->start); + fputs(",\"dur\":", file); + profile_json_time(file, record->duration); + fputc('}', file); + break; + } +} + +/* ---------- public code */ + +int profile_json_write( + struct profile_part *part, + FILE *file) +{ + struct profile_json_list list; + unsigned long next[NUMBER_OF_PROFILE_TRACKS] = { 0, 0 }; + int track; + + for (track = 0; track < NUMBER_OF_PROFILE_TRACKS; track++) + { + if (part->record_counts[track]) + { + qsort(part->records[track], part->record_counts[track], sizeof(struct profile_trace_record), + profile_json_compare_records); + } + } + + fputs("{\"halo\": {\n", file); + profile_json_header(part, file); + fputs("},\n\"cpu_summary\": ", file); + profile_json_cpu_summary(part, file); + fputs(",\n\"traceEvents\": [\n", file); + + list.file = file; + list.items = 0; + profile_json_item(&list); + fprintf(file, "{\"ph\":\"M\",\"name\":\"process_name\",\"pid\":1,\"args\":{\"name\":\"halo %s %s\"}}", + part->session.role, part->session.platform); + profile_json_item(&list); + fputs("{\"ph\":\"M\",\"name\":\"thread_name\",\"pid\":1,\"tid\":1,\"args\":{\"name\":\"game\"}}", file); + profile_json_item(&list); + fputs("{\"ph\":\"M\",\"name\":\"thread_name\",\"pid\":1,\"tid\":2,\"args\":{\"name\":\"p2p\"}}", file); + if (part->number == 1) + { + profile_json_item(&list); + fputs("{\"ph\":\"i\",\"name\":\"recording_start\",\"s\":\"g\",\"pid\":1,\"tid\":1,\"ts\":0.000}", file); + } + /* the two tracks merged, in order of start and depth: each scope after + its parents */ + for (;;) + { + const struct profile_trace_record *game = next[0] < part->record_counts[0] ? &part->records[0][next[0]] : NULL; + const struct profile_trace_record *p2p = next[1] < part->record_counts[1] ? &part->records[1][next[1]] : NULL; + + if (!game && !p2p) + break; + if (game && (!p2p || profile_json_compare_records(game, p2p) <= 0)) + { + profile_json_event(file, &list, game); + next[0]++; + } + else + { + profile_json_event(file, &list, p2p); + next[1]++; + } + } + if (part->last) + { + profile_json_item(&list); + fputs("{\"ph\":\"i\",\"name\":\"recording_stop\",\"s\":\"g\",\"pid\":1,\"tid\":1,\"ts\":", file); + profile_json_time(file, part->end_ns); + fprintf(file, ",\"args\":{\"reason\":\"%s\"}}", profile_json_stop_reasons[part->stop_reason]); + } + fputs("\n],\n\"displayTimeUnit\": \"ms\"}\n", file); + return !ferror(file); +} + +void profile_json_choose_name( + const char *folder, + const char *stamp, + const char *role, + char *name, + int size) +{ + char path[512]; + int suffix; + + snprintf(name, (size_t)size, "profile_%s_%s", stamp, role); + for (suffix = 2; suffix < 1000; suffix++) + { + FILE *file; + int occupied = 0; + + snprintf(path, sizeof(path), "%s/%s.part1.json", folder, name); + file = fopen(path, "rb"); + if (file) + { + fclose(file); + occupied = 1; + } + if (!occupied) + break; + snprintf(name, (size_t)size, "profile_%s_%s_%d", stamp, role, suffix); + } +} + +int profile_json_write_part( + struct profile_part *part) +{ + char temporary[520]; + char path[512]; + FILE *file; + int written; + + snprintf(path, sizeof(path), "%s/%s.part%ld.json", part->folder, part->name, part->number); + snprintf(temporary, sizeof(temporary), "%s.tmp", path); + file = fopen(temporary, "wb"); + if (!file) + { + profile_trace_log("cannot write %s", temporary); + return 0; + } + written = profile_json_write(part, file); + if (fclose(file) != 0) + written = 0; + /* (Windows' rename does not replace a file: the recording's name was + chosen not to be there) */ + if (written && rename(temporary, path) != 0) + written = 0; + if (!written) + { + remove(temporary); + profile_trace_log("cannot write %s", path); + return 0; + } + profile_trace_log("wrote %s", path); + return 1; +} + +static void *profile_json_writer( + void *unused) +{ + (void)unused; + for (;;) + { + struct profile_part *part = profile_trace_writer_next(); + int last = part->last; + profile_json_write_part(part); + profile_trace_writer_done(part); + if (last) + break; + } + return NULL; +} + +int profile_json_writer_start( + void) +{ + pthread_attr_t attributes; + pthread_t thread; + int started; + + pthread_attr_init(&attributes); + pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); + started = pthread_create(&thread, &attributes, profile_json_writer, NULL) == 0; + pthread_attr_destroy(&attributes); + return started; +} + +#endif diff --git a/port/linux/src/profile_part.h b/port/linux/src/profile_part.h new file mode 100644 index 00000000..d85c412b --- /dev/null +++ b/port/linux/src/profile_part.h @@ -0,0 +1,97 @@ +/* +PROFILE_PART.H + +Define frozen recording parts shared by the recorder and writer. +*/ + +#ifndef __PROFILE_PART_H +#define __PROFILE_PART_H + +#ifdef HALO_PROFILE + +#include "profile_trace.h" + +#include + +/* ---------- constants */ + +/* records that are not scopes, by their depth (a scope's is under +MAXIMUM_PROFILE_TRACE_DEPTH): an aggregate name's time and count over a +frame (start: the frame's) */ +enum +{ + PROFILE_TRACE_DEPTH_AGGREGATE_TIME = 255, + PROFILE_TRACE_DEPTH_AGGREGATE_COUNT = 254, +}; + +/* ---------- structures */ + +/* a scope that ended: 16 bytes */ +struct profile_trace_record +{ + /* nanoseconds since the recording started */ + unsigned long long start; + /* nanoseconds, saturating (or the counter's value) */ + unsigned int duration; + unsigned short name; + unsigned char depth; + unsigned char track; +}; + +struct profile_part +{ + char name[64]; + char folder[260]; + long number; + int last; + int stop_reason; + int arena; + struct profile_trace_record *records[NUMBER_OF_PROFILE_TRACKS]; + unsigned long record_counts[NUMBER_OF_PROFILE_TRACKS]; + unsigned long long start_ns; + unsigned long long end_ns; + long first_frame; + long frames; + long first_tick; + long ticks; + long name_count; + struct profile_trace_session session; + char start_utc[32]; + double clock_read_ns; + unsigned long memory_used; + unsigned long memory_limit; + double writer_wait_ms; + unsigned long foreign_scopes; + unsigned long deep_scopes; + unsigned long unbalanced_scopes; + unsigned long dropped_scopes; +}; + +typedef char profile_trace_record_size_assert[sizeof(struct profile_trace_record) == 16 ? 1 : -1]; + +/* ---------- prototypes/PROFILE_TRACE.C */ + +const char *profile_trace_name_text(int name); +int profile_trace_name_is_aggregate(int name); +/* the writer: the next part (waits for one), and a part written (its +arena back; after the last, both arenas freed and the recording over) */ +struct profile_part *profile_trace_writer_next(void); +/* a line in the log (profile_trace_set_log) */ +void profile_trace_log(const char *format, ...); +void profile_trace_writer_done(struct profile_part *part); + +/* ---------- prototypes/PROFILE_JSON.C */ + +/* a part as a Chrome trace with the "halo" and "cpu_summary" keys; 0 when +the file could not be written */ +int profile_json_write(struct profile_part *part, FILE *file); +/* Part names keep profile__. */ +void profile_json_choose_name(const char *folder, const char *stamp, const char *role, char *name, int size); +/* the writer thread of a recording: 0 when it cannot start */ +int profile_json_writer_start(void); +/* a part's file: /.part.json, written as .tmp and renamed */ +int profile_json_write_part(struct profile_part *part); + +#endif + +#endif diff --git a/port/linux/src/profile_trace.c b/port/linux/src/profile_trace.c new file mode 100644 index 00000000..7af3c511 --- /dev/null +++ b/port/linux/src/profile_trace.c @@ -0,0 +1,1006 @@ +/* +PROFILE_TRACE.C + +Record bounded CPU traces and hand frozen parts to the writer. +*/ + +#ifdef HALO_PROFILE + +#include "profile_part.h" + +#include +#include +#include +#include +#include +#include + +/* state changes happen only at the game frame boundary. The recording flag +and track regions change under the p2p lock. two arenas let the writer own +one frozen part while the game and p2p threads fill the other. */ + +/* ---------- constants */ + +enum +{ + /* a region is cut when no more than this share of it is left (a + sixteenth: what a frame adds must still fit) */ + PROFILE_TRACE_HEADROOM_SHIFT = 4, + PROFILE_TRACE_CLOCK_SAMPLES = 1000, +}; + +/* ---------- structures */ + +struct profile_trace_open +{ + unsigned long long start; + unsigned short name; + /* begun while recording: only those are written */ + unsigned char recorded; +}; + +struct profile_trace_track +{ + int index; + struct profile_trace_record *records; + unsigned long count; + unsigned long capacity; + struct profile_trace_open stack[MAXIMUM_PROFILE_TRACE_DEPTH]; + int depth; + int open_aggregates; + /* scopes begun past the stack's depth and not yet ended */ + int overflow; + /* the track's own counts (no other thread writes them) */ + unsigned long deep; + unsigned long unbalanced; + unsigned long dropped; +}; + +struct profile_trace_aggregate +{ + int name; + unsigned long long time; + unsigned long count; +}; + +/* ---------- prototypes */ + +static unsigned long long profile_trace_default_clock(void); +static void profile_trace_no_lock(void); +static void *profile_trace_default_allocate(unsigned long size); +static void profile_trace_default_log(const char *text); +static void profile_trace_no_notify(const char *text); +static int profile_trace_always_ready(void); +static void profile_trace_default_session(struct profile_trace_session *session); +static void profile_trace_default_session_sampler(struct profile_trace_session *session); +static void profile_trace_sample_session(void); + +/* ---------- globals */ + +static struct +{ + unsigned long long (*now)(void); + void (*lock)(void); + void (*unlock)(void); + void *(*allocate)(unsigned long size); + void (*release)(void *block); + void (*emit)(const char *text); + void (*notify)(const char *text); + int (*start_writer)(void); + int (*ready)(void); + void (*describe)(struct profile_trace_session *session); + void (*sample)(struct profile_trace_session *session); +} profile_trace_seams = +{ + profile_trace_default_clock, + profile_trace_no_lock, + profile_trace_no_lock, + profile_trace_default_allocate, + free, + profile_trace_default_log, + profile_trace_no_notify, + profile_json_writer_start, + profile_trace_always_ready, + profile_trace_default_session, + profile_trace_default_session_sampler, +}; + +/* the names, for the whole run (profile.c keeps their ids) */ +static pthread_mutex_t profile_trace_names_lock = PTHREAD_MUTEX_INITIALIZER; +static const char *profile_trace_names[MAXIMUM_PROFILE_TRACE_NAMES]; +static unsigned char profile_trace_aggregate_names[MAXIMUM_PROFILE_TRACE_NAMES]; +static int profile_trace_name_count; +static struct profile_trace_aggregate profile_trace_aggregates[MAXIMUM_PROFILE_TRACE_AGGREGATES]; +static int profile_trace_aggregate_count; +static int profile_trace_wait_name = -1; + +static struct profile_trace_track profile_trace_tracks[NUMBER_OF_PROFILE_TRACKS]; +static __thread struct profile_trace_track *profile_trace_thread_track; +/* scopes from threads with no track (any thread: atomically) */ +static unsigned long profile_trace_foreign_scopes; + +static struct +{ + int state; + /* stored by the writer alone, once it has freed the arenas */ + int finished; + int rearm; + int when; + double seconds; + long memory_megabytes; + int stop_reason; + int map_loaded; + int name_chosen; + /* the tracks append; changed under the track lock */ + int recording; + unsigned long long origin; + unsigned long long frame_start; + void *arenas[2]; + unsigned long arena_size; + unsigned long game_bytes; + unsigned long p2p_bytes; + struct profile_part parts[2]; + int current; + long part_number; + long frame; + long tick; + unsigned long long next_session_sample; + char name[64]; + struct profile_trace_session session; + char start_utc[32]; + double clock_read_ns; + double writer_wait_ms; + /* the hand-off with the writer: which arenas it holds, and the parts + queued for it */ + pthread_mutex_t writer_lock; + pthread_cond_t writer_changed; + int writer_holds[2]; + struct profile_part *queue[2]; + int queue_count; +} profile_trace_globals = +{ + .state = _profile_trace_idle, + .memory_megabytes = DEFAULT_PROFILE_TRACE_MEMORY, + .writer_lock = PTHREAD_MUTEX_INITIALIZER, + .writer_changed = PTHREAD_COND_INITIALIZER, +}; + +/* ---------- private code */ + +static unsigned long long profile_trace_default_clock(void) +{ + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + return (unsigned long long)now.tv_sec * 1000000000ULL + (unsigned long long)now.tv_nsec; +} + +static void profile_trace_no_lock(void) +{ +} + +static void *profile_trace_default_allocate(unsigned long size) +{ + return malloc(size); +} + +static void profile_trace_default_log(const char *text) +{ + fprintf(stderr, "profile: %s\n", text); +} + +static void profile_trace_no_notify(const char *text) +{ + (void)text; +} + +static int profile_trace_always_ready(void) +{ + return 1; +} + +static void profile_trace_default_session(struct profile_trace_session *session) +{ + memset(session, 0, sizeof(*session)); + strcpy(session->folder, "."); + strcpy(session->role, "local"); + session->own_machine = -1; + strcpy(session->gametype, "none"); +} + +static void profile_trace_default_session_sampler(struct profile_trace_session *session) +{ + (void)session; +} + +static void profile_trace_sample_session(void) +{ + struct profile_trace_session current; + unsigned long long now = profile_trace_seams.now(); + + if (now < profile_trace_globals.next_session_sample) + return; + profile_trace_globals.next_session_sample = now + 1000000000ULL; + memset(¤t, 0, sizeof(current)); + profile_trace_seams.sample(¤t); + if (!profile_trace_globals.session.map_name[0] && current.map_name[0]) + { + strcpy(profile_trace_globals.session.map, current.map); + strcpy(profile_trace_globals.session.map_name, current.map_name); + strcpy(profile_trace_globals.session.gametype, current.gametype); + } + if (current.players > profile_trace_globals.session.players_most) + profile_trace_globals.session.players_most = current.players; +} + +static const char *profile_trace_platform(void) +{ +#if defined(HALO_ANDROID) + return "android"; +#elif defined(_WIN32) + return "windows"; +#else + return "linux"; +#endif +} + +static int profile_trace_is_recording(void) +{ + return __atomic_load_n(&profile_trace_globals.recording, __ATOMIC_RELAXED); +} + +static void profile_trace_append( + struct profile_trace_track *track, + unsigned long long start, + unsigned long long duration, + int name, + int depth) +{ + struct profile_trace_record *record; + + if (!track->records || track->count >= track->capacity) + { + track->dropped++; + return; + } + record = &track->records[track->count]; + /* (the game thread reads the p2p track's count at the frame boundary, + without its lock) */ + __atomic_store_n(&track->count, track->count + 1, __ATOMIC_RELAXED); + record->start = start; + record->duration = duration > 0xFFFFFFFFULL ? 0xFFFFFFFFU : (unsigned int)duration; + record->name = (unsigned short)name; + record->depth = (unsigned char)depth; + record->track = (unsigned char)track->index; +} + +static int profile_trace_track_full( + struct profile_trace_track const *track) +{ + unsigned long count = __atomic_load_n(&track->count, __ATOMIC_RELAXED); + + return count + (track->capacity >> PROFILE_TRACE_HEADROOM_SHIFT) >= track->capacity; +} + +/* the arena's part n: the tracks write to its regions (under the track +lock: the p2p thread reads them) */ +static void profile_trace_open_part( + int arena, + unsigned long long now) +{ + struct profile_part *part = &profile_trace_globals.parts[arena]; + char *base = profile_trace_globals.arenas[arena]; + struct profile_trace_track *game = &profile_trace_tracks[_profile_track_game]; + struct profile_trace_track *p2p = &profile_trace_tracks[_profile_track_p2p]; + + memset(part, 0, sizeof(*part)); + part->arena = arena; + part->number = profile_trace_globals.part_number; + part->start_ns = now - profile_trace_globals.origin; + part->first_frame = profile_trace_globals.frame; + part->first_tick = profile_trace_globals.tick; + game->records = (struct profile_trace_record *)base; + game->capacity = profile_trace_globals.game_bytes / sizeof(struct profile_trace_record); + game->count = 0; + p2p->records = (struct profile_trace_record *)(base + profile_trace_globals.game_bytes); + p2p->capacity = profile_trace_globals.p2p_bytes / sizeof(struct profile_trace_record); + p2p->count = 0; + part->records[_profile_track_game] = game->records; + part->records[_profile_track_p2p] = p2p->records; + profile_trace_globals.current = arena; +} + +static void profile_trace_emit_aggregates( + void) +{ + struct profile_trace_track *game = &profile_trace_tracks[_profile_track_game]; + unsigned long long start = profile_trace_globals.frame_start - profile_trace_globals.origin; + int index; + + for (index = 0; index < profile_trace_aggregate_count; index++) + { + struct profile_trace_aggregate *aggregate = &profile_trace_aggregates[index]; + + if (!aggregate->count) + continue; + profile_trace_append(game, start, aggregate->time, aggregate->name, PROFILE_TRACE_DEPTH_AGGREGATE_TIME); + profile_trace_append(game, start, aggregate->count, aggregate->name, PROFILE_TRACE_DEPTH_AGGREGATE_COUNT); + aggregate->time = 0; + aggregate->count = 0; + } +} + +/* the current part frozen and queued for the writer: the last of the +recording, or the tracks go on in the other arena (once the writer has +given it back: the game thread waits for it here, and the wait is a +profile_wait scope in the new part) */ +static void profile_trace_cut( + int last, + int reason) +{ + struct profile_part *part = &profile_trace_globals.parts[profile_trace_globals.current]; + int next = 1 - profile_trace_globals.current; + unsigned long long now; + unsigned long long wait_start = 0; + unsigned long long waited = 0; + int track; + + profile_trace_emit_aggregates(); + /* (a map load that stops the recording already names the next map: a + recording that never learnt its map stays without one rather than take + that one) */ + if (!(last && reason == _profile_trace_stop_map_load)) + { + profile_trace_globals.next_session_sample = 0; + profile_trace_sample_session(); + } + if (!last) + { + pthread_mutex_lock(&profile_trace_globals.writer_lock); + if (profile_trace_globals.writer_holds[next]) + { + wait_start = profile_trace_seams.now(); + while (profile_trace_globals.writer_holds[next]) + pthread_cond_wait(&profile_trace_globals.writer_changed, &profile_trace_globals.writer_lock); + waited = profile_trace_seams.now() - wait_start; + } + pthread_mutex_unlock(&profile_trace_globals.writer_lock); + profile_trace_globals.writer_wait_ms += (double)waited / 1000000.0; + } + + profile_trace_seams.lock(); + /* (read under the lock, after the wait for the writer: the p2p thread appends to this + arena until here, and every record it appended ended before this time) */ + now = profile_trace_seams.now(); + for (track = 0; track < NUMBER_OF_PROFILE_TRACKS; track++) + { + struct profile_trace_track *source = &profile_trace_tracks[track]; + + part->record_counts[track] = source->count; + part->deep_scopes += source->deep; + part->unbalanced_scopes += source->unbalanced; + part->dropped_scopes += source->dropped; + source->deep = source->unbalanced = source->dropped = 0; + } + if (last) + { + __atomic_store_n(&profile_trace_globals.recording, 0, __ATOMIC_RELAXED); + for (track = 0; track < NUMBER_OF_PROFILE_TRACKS; track++) + { + profile_trace_tracks[track].records = NULL; + profile_trace_tracks[track].capacity = 0; + profile_trace_tracks[track].count = 0; + } + } + else + { + profile_trace_globals.part_number++; + profile_trace_open_part(next, now); + } + profile_trace_seams.unlock(); + + part->last = last; + part->stop_reason = last ? reason : _profile_trace_stop_none; + part->end_ns = now - profile_trace_globals.origin; + part->frames = profile_trace_globals.frame - part->first_frame; + part->ticks = profile_trace_globals.tick - part->first_tick; + strcpy(part->name, profile_trace_globals.name); + strcpy(part->folder, profile_trace_globals.session.folder); + part->session = profile_trace_globals.session; + strcpy(part->session.platform, profile_trace_platform()); + strcpy(part->start_utc, profile_trace_globals.start_utc); + part->clock_read_ns = profile_trace_globals.clock_read_ns; + part->memory_used = (unsigned long)(part->record_counts[0] + part->record_counts[1]) * + sizeof(struct profile_trace_record); + part->memory_limit = profile_trace_globals.arena_size; + part->writer_wait_ms = profile_trace_globals.writer_wait_ms; + part->foreign_scopes = __atomic_exchange_n(&profile_trace_foreign_scopes, 0, __ATOMIC_RELAXED); + pthread_mutex_lock(&profile_trace_names_lock); + part->name_count = profile_trace_name_count; + pthread_mutex_unlock(&profile_trace_names_lock); + + if (!last && waited) + { + profile_trace_append(&profile_trace_tracks[_profile_track_game], wait_start - profile_trace_globals.origin, + now - wait_start, profile_trace_wait_name, 0); + } + + pthread_mutex_lock(&profile_trace_globals.writer_lock); + profile_trace_globals.writer_holds[part->arena] = 1; + profile_trace_globals.queue[profile_trace_globals.queue_count++] = part; + pthread_cond_broadcast(&profile_trace_globals.writer_changed); + pthread_mutex_unlock(&profile_trace_globals.writer_lock); + + if (last) + { + profile_trace_globals.state = _profile_trace_finishing; + profile_trace_log("writing %s, %ld parts", profile_trace_globals.name, profile_trace_globals.part_number); + } +} + +/* the recording's name, from the time and the machine's role: when it is +asked for (profile_record answers with it), or when a game starts */ +static void profile_trace_choose_name( + void) +{ + time_t wall = time(NULL); + struct tm *utc = gmtime(&wall); + char stamp[32]; + + profile_trace_seams.describe(&profile_trace_globals.session); + if (utc) + { + strftime(stamp, sizeof(stamp), "%Y%m%d-%H%M%S", utc); + strftime(profile_trace_globals.start_utc, sizeof(profile_trace_globals.start_utc), "%Y-%m-%d %H:%M:%S", utc); + } + else + { + strcpy(stamp, "00000000-000000"); + strcpy(profile_trace_globals.start_utc, ""); + } + profile_json_choose_name(profile_trace_globals.session.folder, stamp, profile_trace_globals.session.role, + profile_trace_globals.name, sizeof(profile_trace_globals.name)); + profile_trace_globals.session.players_most = profile_trace_globals.session.players; + profile_trace_globals.name_chosen = 1; +} + +static void profile_trace_start_recording( + void) +{ + unsigned long long memory = (unsigned long long)profile_trace_globals.memory_megabytes * 1024 * 1024; + unsigned long size = (unsigned long)(memory / 2) & ~63UL; + unsigned long scope_bytes = size; + unsigned long long now; + int track, sample; + + if (!profile_trace_globals.name_chosen) + profile_trace_choose_name(); + profile_trace_globals.name_chosen = 0; + profile_trace_globals.arenas[0] = profile_trace_seams.allocate(size); + profile_trace_globals.arenas[1] = profile_trace_globals.arenas[0] ? profile_trace_seams.allocate(size) : NULL; + if (!profile_trace_globals.arenas[1]) + { + char text[96]; + + if (profile_trace_globals.arenas[0]) + profile_trace_seams.release(profile_trace_globals.arenas[0]); + profile_trace_globals.arenas[0] = NULL; + snprintf(text, sizeof(text), "profile: no memory for %ld MB", profile_trace_globals.memory_megabytes); + profile_trace_log("%s", text + 9); + profile_trace_seams.notify(text); + profile_trace_globals.state = _profile_trace_idle; + profile_trace_globals.rearm = 0; + return; + } + profile_trace_globals.arena_size = size; + /* the scopes: seven eighths the game track's, an eighth + the p2p track's (whole records each) */ + profile_trace_globals.game_bytes = scope_bytes / 8 * 7 / sizeof(struct profile_trace_record) * + sizeof(struct profile_trace_record); + profile_trace_globals.p2p_bytes = (scope_bytes - profile_trace_globals.game_bytes) / + sizeof(struct profile_trace_record) * sizeof(struct profile_trace_record); + + __atomic_store_n(&profile_trace_foreign_scopes, 0, __ATOMIC_RELAXED); + for (track = 0; track < profile_trace_aggregate_count; track++) + { + profile_trace_aggregates[track].time = 0; + profile_trace_aggregates[track].count = 0; + } + profile_trace_globals.frame = 0; + profile_trace_globals.tick = 0; + profile_trace_globals.part_number = 1; + profile_trace_globals.writer_wait_ms = 0.0; + profile_trace_globals.stop_reason = _profile_trace_stop_none; + profile_trace_globals.writer_holds[0] = profile_trace_globals.writer_holds[1] = 0; + profile_trace_globals.queue_count = 0; + __atomic_store_n(&profile_trace_globals.finished, 0, __ATOMIC_RELAXED); + + now = profile_trace_seams.now(); + for (sample = 0; sample < PROFILE_TRACE_CLOCK_SAMPLES; sample++) + profile_trace_seams.now(); + profile_trace_globals.clock_read_ns = (double)(profile_trace_seams.now() - now) / PROFILE_TRACE_CLOCK_SAMPLES; + + now = profile_trace_seams.now(); + profile_trace_globals.origin = now; + profile_trace_globals.frame_start = now; + profile_trace_seams.lock(); + /* (under the lock: a p2p pass of the last recording may end, and count, meanwhile) */ + for (track = 0; track < NUMBER_OF_PROFILE_TRACKS; track++) + { + profile_trace_tracks[track].deep = 0; + profile_trace_tracks[track].unbalanced = 0; + profile_trace_tracks[track].dropped = 0; + } + profile_trace_open_part(0, now); + profile_trace_seams.unlock(); + if (!profile_trace_seams.start_writer()) + { + profile_trace_seams.lock(); + for (track = 0; track < NUMBER_OF_PROFILE_TRACKS; track++) + profile_trace_tracks[track].records = NULL; + profile_trace_seams.unlock(); + profile_trace_seams.release(profile_trace_globals.arenas[0]); + profile_trace_seams.release(profile_trace_globals.arenas[1]); + profile_trace_globals.arenas[0] = profile_trace_globals.arenas[1] = NULL; + profile_trace_log("the writer thread cannot start: not recording"); + profile_trace_seams.notify("profile: the writer thread cannot start"); + profile_trace_globals.state = _profile_trace_idle; + profile_trace_globals.rearm = 0; + return; + } + profile_trace_seams.lock(); + __atomic_store_n(&profile_trace_globals.recording, 1, __ATOMIC_RELAXED); + profile_trace_seams.unlock(); + profile_trace_globals.state = _profile_trace_recording; + profile_trace_log("recording %s", profile_trace_globals.name); +} + +/* ---------- public code */ + +void profile_trace_set_clock( + unsigned long long (*now_ns)(void)) +{ + profile_trace_seams.now = now_ns ? now_ns : profile_trace_default_clock; +} + +void profile_trace_set_track_lock( + void (*lock)(void), + void (*unlock)(void)) +{ + profile_trace_seams.lock = lock && unlock ? lock : profile_trace_no_lock; + profile_trace_seams.unlock = lock && unlock ? unlock : profile_trace_no_lock; +} + +void profile_trace_set_allocator( + void *(*allocate)(unsigned long size), + void (*release)(void *block)) +{ + profile_trace_seams.allocate = allocate && release ? allocate : profile_trace_default_allocate; + profile_trace_seams.release = allocate && release ? release : free; +} + +void profile_trace_set_log( + void (*log)(const char *text)) +{ + profile_trace_seams.emit = log ? log : profile_trace_default_log; +} + +void profile_trace_set_notify( + void (*notify)(const char *text)) +{ + profile_trace_seams.notify = notify ? notify : profile_trace_no_notify; +} + +void profile_trace_set_writer( + int (*start)(void)) +{ + profile_trace_seams.start_writer = start ? start : profile_json_writer_start; +} + +void profile_trace_set_ready( + int (*ready)(void)) +{ + profile_trace_seams.ready = ready ? ready : profile_trace_always_ready; +} + +void profile_trace_set_session( + void (*describe)(struct profile_trace_session *session)) +{ + profile_trace_seams.describe = describe ? describe : profile_trace_default_session; +} + +void profile_trace_set_session_sampler( + void (*sample)(struct profile_trace_session *session)) +{ + profile_trace_seams.sample = sample ? sample : profile_trace_default_session_sampler; +} + +void profile_trace_log( + const char *format, + ...) +{ + char text[512]; + va_list arguments; + + va_start(arguments, format); + vsnprintf(text, sizeof(text), format, arguments); + va_end(arguments); + profile_trace_seams.emit(text); +} + +void profile_trace_thread_register( + int track) +{ + if (track < 0 || track >= NUMBER_OF_PROFILE_TRACKS) + return; + profile_trace_tracks[track].index = track; + profile_trace_thread_track = &profile_trace_tracks[track]; +} + +int profile_trace_name( + const char *name) +{ + int index; + + if (!name) + return -1; + pthread_mutex_lock(&profile_trace_names_lock); + for (index = 0; index < profile_trace_name_count; index++) + { + if (strcmp(profile_trace_names[index], name) == 0) + break; + } + if (index == profile_trace_name_count) + { + if (index < MAXIMUM_PROFILE_TRACE_NAMES) + profile_trace_names[profile_trace_name_count++] = name; + else + index = -1; + } + pthread_mutex_unlock(&profile_trace_names_lock); + return index; +} + +void profile_trace_name_aggregate( + const char *name) +{ + int id = profile_trace_name(name); + + if (id < 0 || profile_trace_aggregate_names[id] || + profile_trace_aggregate_count >= MAXIMUM_PROFILE_TRACE_AGGREGATES) + { + return; + } + profile_trace_aggregates[profile_trace_aggregate_count].name = id; + profile_trace_aggregates[profile_trace_aggregate_count].time = 0; + profile_trace_aggregates[profile_trace_aggregate_count].count = 0; + profile_trace_aggregate_count++; + profile_trace_aggregate_names[id] = 1; +} + +const char *profile_trace_name_text( + int name) +{ + const char *text = "unknown"; + + pthread_mutex_lock(&profile_trace_names_lock); + if (name >= 0 && name < profile_trace_name_count) + text = profile_trace_names[name]; + pthread_mutex_unlock(&profile_trace_names_lock); + return text; +} + +int profile_trace_name_is_aggregate( + int name) +{ + return name >= 0 && name < MAXIMUM_PROFILE_TRACE_NAMES && profile_trace_aggregate_names[name]; +} + +void profile_trace_begin( + int name) +{ + struct profile_trace_track *track = profile_trace_thread_track; + struct profile_trace_open *open; + + if (!track) + { + if (profile_trace_is_recording()) + __atomic_fetch_add(&profile_trace_foreign_scopes, 1, __ATOMIC_RELAXED); + return; + } + if (name < 0) + return; + if (track->depth >= MAXIMUM_PROFILE_TRACE_DEPTH) + { + track->overflow++; + if (profile_trace_is_recording()) + track->deep++; + return; + } + open = &track->stack[track->depth++]; + open->name = (unsigned short)name; + open->recorded = (unsigned char)profile_trace_is_recording(); + open->start = open->recorded ? profile_trace_seams.now() : 0; + if (profile_trace_aggregate_names[name]) + track->open_aggregates++; +} + +void profile_trace_end( + int name) +{ + struct profile_trace_track *track = profile_trace_thread_track; + struct profile_trace_open *open; + unsigned long long duration; + + if (!track || name < 0) + return; + if (track->overflow) + { + track->overflow--; + return; + } + if (!track->depth || track->stack[track->depth - 1].name != name) + { + if (profile_trace_is_recording()) + track->unbalanced++; + return; + } + open = &track->stack[--track->depth]; + if (profile_trace_aggregate_names[name] && track->open_aggregates > 0) + track->open_aggregates--; + if (!open->recorded) + return; + /* (a scope that outlived its recording: the p2p thread lets go of its + lock for slow work, and a stop can come meanwhile) */ + if (!profile_trace_is_recording() || !track->records || open->start < profile_trace_globals.origin) + { + track->dropped++; + return; + } + duration = profile_trace_seams.now() - open->start; + if (profile_trace_aggregate_names[name]) + { + int index; + + if (track->index != _profile_track_game) + return; + for (index = 0; index < profile_trace_aggregate_count; index++) + { + if (profile_trace_aggregates[index].name == name) + { + profile_trace_aggregates[index].time += duration; + profile_trace_aggregates[index].count++; + break; + } + } + return; + } + { + /* (an aggregated parent leaves no record, so depth counts only the + recorded ancestors) */ + int depth = track->depth - track->open_aggregates; + profile_trace_append(track, open->start - profile_trace_globals.origin, duration, name, depth); + } +} + +void profile_trace_frame_boundary( + void) +{ + struct profile_trace_track *game = &profile_trace_tracks[_profile_track_game]; + unsigned long long now; + + /* (a scope the frame left open: its end is lost, and so is it) */ + if (game->depth || game->overflow) + { + if (profile_trace_is_recording()) + game->unbalanced += (unsigned long)(game->depth + game->overflow); + game->depth = 0; + game->open_aggregates = 0; + game->overflow = 0; + } + if (profile_trace_wait_name < 0) + profile_trace_wait_name = profile_trace_name("profile_wait"); + switch (profile_trace_globals.state) + { + case _profile_trace_armed: + if (profile_trace_globals.when == _profile_trace_when_now || profile_trace_seams.ready()) + profile_trace_start_recording(); + break; + case _profile_trace_recording: + now = profile_trace_seams.now(); + if (now >= profile_trace_globals.next_session_sample) + profile_trace_sample_session(); + profile_trace_emit_aggregates(); + profile_trace_globals.frame++; + profile_trace_globals.frame_start = now; + if (!profile_trace_globals.stop_reason && profile_trace_globals.seconds > 0.0 && + (double)(now - profile_trace_globals.origin) >= profile_trace_globals.seconds * 1000000000.0) + { + profile_trace_globals.stop_reason = _profile_trace_stop_seconds; + } + if (profile_trace_globals.stop_reason) + { + profile_trace_cut(1, profile_trace_globals.stop_reason); + } + else if (profile_trace_track_full(&profile_trace_tracks[_profile_track_game]) || + profile_trace_track_full(&profile_trace_tracks[_profile_track_p2p])) + { + profile_trace_cut(0, _profile_trace_stop_none); + } + break; + case _profile_trace_finishing: + if (__atomic_load_n(&profile_trace_globals.finished, __ATOMIC_ACQUIRE)) + { + __atomic_store_n(&profile_trace_globals.finished, 0, __ATOMIC_RELAXED); + profile_trace_globals.state = profile_trace_globals.rearm ? _profile_trace_armed : _profile_trace_idle; + } + break; + default: + break; + } +} + +void profile_trace_tick( + void) +{ + if (profile_trace_globals.state != _profile_trace_recording) + return; + profile_trace_globals.tick++; +} + +int profile_trace_request_start( + double seconds, + int when, + long memory_megabytes) +{ + switch (profile_trace_globals.state) + { + case _profile_trace_armed: + case _profile_trace_recording: + return _profile_trace_answer_already_recording; + case _profile_trace_finishing: + return _profile_trace_answer_still_writing; + default: + break; + } + if (memory_megabytes < MINIMUM_PROFILE_TRACE_MEMORY) + memory_megabytes = MINIMUM_PROFILE_TRACE_MEMORY; + if (memory_megabytes > MAXIMUM_PROFILE_TRACE_MEMORY) + memory_megabytes = MAXIMUM_PROFILE_TRACE_MEMORY; + profile_trace_globals.seconds = seconds > 0.0 ? seconds : 0.0; + profile_trace_globals.when = when; + profile_trace_globals.memory_megabytes = memory_megabytes; + profile_trace_globals.rearm = when == _profile_trace_when_game; + profile_trace_globals.stop_reason = _profile_trace_stop_none; + profile_trace_globals.state = _profile_trace_armed; + profile_trace_globals.name_chosen = 0; + if (when == _profile_trace_when_now) + profile_trace_choose_name(); + return _profile_trace_answer_armed; +} + +int profile_trace_request_stop( + void) +{ + switch (profile_trace_globals.state) + { + case _profile_trace_armed: + profile_trace_globals.state = _profile_trace_idle; + profile_trace_globals.rearm = 0; + return profile_trace_globals.when == _profile_trace_when_game ? _profile_trace_answer_disarmed : + _profile_trace_answer_not_recording; + case _profile_trace_recording: + profile_trace_globals.rearm = 0; + if (!profile_trace_globals.stop_reason) + profile_trace_globals.stop_reason = _profile_trace_stop_command; + return _profile_trace_answer_stopping; + case _profile_trace_finishing: + if (profile_trace_globals.rearm) + { + profile_trace_globals.rearm = 0; + return _profile_trace_answer_disarmed; + } + return _profile_trace_answer_not_recording; + default: + return _profile_trace_answer_not_recording; + } +} + +void profile_trace_map_loaded( + void) +{ + if (!profile_trace_globals.map_loaded) + { + profile_trace_globals.map_loaded = 1; + return; + } + if (profile_trace_globals.state == _profile_trace_recording && !profile_trace_globals.stop_reason) + profile_trace_globals.stop_reason = _profile_trace_stop_map_load; +} + +void profile_trace_shutdown( + void) +{ + struct profile_trace_track *game = &profile_trace_tracks[_profile_track_game]; + + /* (exit called on another thread: the game thread may be mid-frame, + writing to the arena; the part being recorded is lost, as when the + process is killed) */ + if (profile_trace_thread_track != game) + return; + profile_trace_globals.rearm = 0; + if (profile_trace_globals.state == _profile_trace_armed) + profile_trace_globals.state = _profile_trace_idle; + if (profile_trace_globals.state == _profile_trace_recording) + { + game->depth = 0; + game->open_aggregates = 0; + game->overflow = 0; + profile_trace_cut(1, _profile_trace_stop_exit); + } + if (profile_trace_globals.state == _profile_trace_finishing) + { + pthread_mutex_lock(&profile_trace_globals.writer_lock); + while (!__atomic_load_n(&profile_trace_globals.finished, __ATOMIC_ACQUIRE)) + pthread_cond_wait(&profile_trace_globals.writer_changed, &profile_trace_globals.writer_lock); + pthread_mutex_unlock(&profile_trace_globals.writer_lock); + __atomic_store_n(&profile_trace_globals.finished, 0, __ATOMIC_RELAXED); + profile_trace_globals.state = _profile_trace_idle; + } +} + +int profile_trace_recording( + void) +{ + return profile_trace_is_recording(); +} + +void profile_trace_status( + struct profile_trace_status *status) +{ + memset(status, 0, sizeof(*status)); + status->state = profile_trace_globals.state; + strcpy(status->name, profile_trace_globals.name); + if (profile_trace_globals.state != _profile_trace_recording) + return; + status->part = profile_trace_globals.part_number; +} + +struct profile_part *profile_trace_writer_next( + void) +{ + struct profile_part *part; + + pthread_mutex_lock(&profile_trace_globals.writer_lock); + while (!profile_trace_globals.queue_count) + pthread_cond_wait(&profile_trace_globals.writer_changed, &profile_trace_globals.writer_lock); + part = profile_trace_globals.queue[0]; + profile_trace_globals.queue[0] = profile_trace_globals.queue[1]; + profile_trace_globals.queue_count--; + pthread_mutex_unlock(&profile_trace_globals.writer_lock); + return part; +} + +void profile_trace_writer_done( + struct profile_part *part) +{ + int last = part->last; + + pthread_mutex_lock(&profile_trace_globals.writer_lock); + profile_trace_globals.writer_holds[part->arena] = 0; + pthread_cond_broadcast(&profile_trace_globals.writer_changed); + pthread_mutex_unlock(&profile_trace_globals.writer_lock); + if (!last) + return; + /* (the last part: no track writes to either arena any more, and the + writer has written the other part before this one) */ + profile_trace_seams.release(profile_trace_globals.arenas[0]); + profile_trace_seams.release(profile_trace_globals.arenas[1]); + profile_trace_globals.arenas[0] = profile_trace_globals.arenas[1] = NULL; + pthread_mutex_lock(&profile_trace_globals.writer_lock); + __atomic_store_n(&profile_trace_globals.finished, 1, __ATOMIC_RELEASE); + pthread_cond_broadcast(&profile_trace_globals.writer_changed); + pthread_mutex_unlock(&profile_trace_globals.writer_lock); +} + +#endif diff --git a/port/linux/src/sdl_platform.c b/port/linux/src/sdl_platform.c index 0481aa48..645c6a6a 100644 --- a/port/linux/src/sdl_platform.c +++ b/port/linux/src/sdl_platform.c @@ -16,6 +16,7 @@ and the debug keyboard that the game's console reads. #include "port_config.h" #include "p2p.h" #include "xiso.h" +#include "touch_input.h" #include #include @@ -34,6 +35,8 @@ static struct platform_input_state input_state; /* keys pressed since the last read, so a press and release between two reads still counts as a press (input injected on Android, or a slow frame) */ static unsigned char keys_pressed[SDL_SCANCODE_COUNT]; +/* The bound Screenshot action requests capture at the next presentation. */ +static BOOL screenshot_requested; /* likewise the mouse buttons pressed since the last read, so that a click quicker than a frame still counts */ static unsigned char mouse_buttons_pressed[PLATFORM_MOUSE_BUTTON_COUNT]; @@ -62,10 +65,6 @@ gone, ends: else the keyboard stays held from the game) */ static Uint64 binding_polled_ms; #define BINDING_ABANDONED_MS 500 static unsigned mouse_buttons_down; -#ifdef HALO_ANDROID -/* the mouse's place, flat (platform_flat_mouse) */ -static float flat_mouse_x, flat_mouse_y; -#endif /* (an input taken that nothing asks for is let go after this) */ #define BINDING_UNCLAIMED_MS 2000 /* the multiplayer scoreboard is open (platform_scoreboard_scroll): the wheel @@ -78,6 +77,16 @@ static Uint64 scoreboard_open_until_ms; static float scoreboard_wheel; static long scoreboard_notches; static long scoreboard_pages; +#ifndef HALO_ANDROID +/* the scoreboard's pointer (platform_scoreboard_pointer): while the game +offers it (a network game's scoreboard is open), a right click frees the +mouse, whose pointer then picks a player; its motion and clicks go to it, +not to the aim and the triggers. Another right click, or the scoreboard +closing, takes the mouse back for the aim. */ +static BOOL scoreboard_pointer_offered; +static struct platform_ui_pointer scoreboard_pointer; +#endif +static BOOL scoreboard_pointer_active; /* debug keyboard queue */ #define KEYSTROKE_QUEUE_SIZE 64 @@ -85,9 +94,15 @@ static struct platform_keystroke keystroke_queue[KEYSTROKE_QUEUE_SIZE]; static unsigned long keystroke_head, keystroke_count; #ifndef HALO_ANDROID +/* dsound_sdl.c's: the output device followed */ +void dsound_sdl_output_device_check(void); /* updater.c's: the desktop self-updater */ void updater_start(void); void updater_poll(SDL_Window *window); +static void screen_keyboard_update(void); +/* the windows' icon, a PNG (tools/embed_assets.py, from port/assets/icon) */ +extern const unsigned int platform_window_icon[]; +extern const unsigned long platform_window_icon_size; #endif BOOL platform_sdl_initialize(void) @@ -670,6 +685,73 @@ int platform_window_sizes(long *widths, long *heights, int maximum) } #endif + +/* ---------- audio devices (Settings > Audio: audio.output_device, +audio.input_device) */ + +#ifndef HALO_ANDROID +int platform_audio_devices(int recording, char (*names)[PLATFORM_AUDIO_DEVICE_NAME_SIZE], int maximum) +{ + SDL_AudioDeviceID *devices; + int device_count = 0, count = 0, index; + + if (maximum < 1 || !platform_sdl_initialize()) + return 0; + devices = recording ? SDL_GetAudioRecordingDevices(&device_count) : SDL_GetAudioPlaybackDevices(&device_count); + for (index = 0; devices && index < device_count && count < maximum; index++) + { + const char *name = SDL_GetAudioDeviceName(devices[index]); + + /* (a name a setting can hold, and a menu show: no "|", which + separates a spinner's values) */ + if (!name || !name[0] || strchr(name, '|') || strlen(name) >= PLATFORM_AUDIO_DEVICE_NAME_SIZE) + continue; + snprintf(names[count++], PLATFORM_AUDIO_DEVICE_NAME_SIZE, "%s", name); + } + SDL_free(devices); + return count; +} + +SDL_AudioDeviceID platform_audio_device(int recording, const char *name) +{ + SDL_AudioDeviceID *devices; + SDL_AudioDeviceID found = recording ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + int device_count = 0, index; + + if (!name || !name[0] || !strcmp(name, "default")) + return found; + devices = recording ? SDL_GetAudioRecordingDevices(&device_count) : SDL_GetAudioPlaybackDevices(&device_count); + for (index = 0; devices && index < device_count; index++) + { + const char *device_name = SDL_GetAudioDeviceName(devices[index]); + + if (device_name && !strcmp(device_name, name)) + { + found = devices[index]; + break; + } + } + SDL_free(devices); + if (found == (recording ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)) + platform_log("audio: no %s device named \"%s\": the system's default", recording ? "input" : "output", name); + return found; +} +#else +int platform_audio_devices(int recording, char (*names)[PLATFORM_AUDIO_DEVICE_NAME_SIZE], int maximum) +{ + (void)recording; + (void)names; + (void)maximum; + return 0; +} + +SDL_AudioDeviceID platform_audio_device(int recording, const char *name) +{ + (void)name; + return recording ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; +} +#endif + #ifndef HALO_ANDROID /* the window's size (platform_window_size_setting), as the window was made or last resized: platform_display_apply */ @@ -685,7 +767,7 @@ BOOL platform_video_initialize(unsigned long width, unsigned long height) if (!platform_sdl_initialize()) return FALSE; -#ifdef HALO_ANDROID +#ifdef HALO_GLES SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); @@ -735,11 +817,21 @@ BOOL platform_video_initialize(unsigned long width, unsigned long height) return FALSE; } #ifndef HALO_ANDROID + /* the game's icon, which the desktop shows for the window (on Windows + also halo.exe's own, port/windows/halo.rc) */ + if (platform_window_icon_size) + { + SDL_Surface *icon = SDL_LoadPNG_IO(SDL_IOFromConstMem(platform_window_icon, platform_window_icon_size), true); + + if (!icon || !SDL_SetWindowIcon(platform_window, icon)) + platform_log("cannot set the window's icon: %s", SDL_GetError()); + SDL_DestroySurface(icon); + } platform_fullscreen_requested = platform_fullscreen_setting(); platform_fullscreen_kind_apply(); #endif platform_gl_context = SDL_GL_CreateContext(platform_window); -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES 3.2 where the driver has it, otherwise the renderer makes do with 3.0 plus extensions */ if (!platform_gl_context) @@ -1189,10 +1281,10 @@ static void platform_show_pending_message(void) /* ---------- events */ /* quits as closing the window does, when the events are next read (the -menus' Quit: port/linux/game/menu_functions.c); on Android, where the -system closes its apps, the VR options' Quit (ui_widget.c) ends the process -at once: on the Steam Frame, Steam cannot end the game, which runs in -Lepton's container (port/android/STEAM_FRAME.md) */ +menus' Quit: port/linux/game/menu_functions.c); on Android at once, as +the system closes its apps: the VR options' Quit (ui_widget.c) too, as on +the Steam Frame Steam cannot end the game, which runs in Lepton's container +(port/android/STEAM_FRAME.md) */ void platform_request_quit(void) { #ifndef HALO_ANDROID @@ -1202,6 +1294,7 @@ void platform_request_quit(void) event.type = SDL_EVENT_QUIT; SDL_PushEvent(&event); #else + /* (the guest has no SDL_PushEvent: exit ends the process, host_exit) */ { /* (and says so beside the game's data: the Steam Frame's launcher does not then take it for a start that failed and start it again) */ @@ -1218,6 +1311,52 @@ void platform_request_quit(void) #endif } +#ifndef HALO_ANDROID +/* (under input_lock, on the event thread) the scoreboard's pointer on: the +mouse freed, at the window's middle, and nothing held for the triggers */ +static void scoreboard_pointer_start(void) +{ + int width, height; + + scoreboard_pointer_active = TRUE; + memset(&scoreboard_pointer, 0, sizeof(scoreboard_pointer)); + memset(input_state.mouse_buttons, 0, sizeof(input_state.mouse_buttons)); + memset(mouse_buttons_pressed, 0, sizeof(mouse_buttons_pressed)); + input_state.mouse_dx = input_state.mouse_dy = 0.0f; + platform_mouse_capture(FALSE); + SDL_GetWindowSize(platform_window, &width, &height); + SDL_WarpMouseInWindow(platform_window, width * 0.5f, height * 0.5f); + scoreboard_pointer.x = width * 0.5f; + scoreboard_pointer.y = height * 0.5f; +} + +/* ... off: the mouse the aim's again (unless freed: F12, or the menus) */ +static void scoreboard_pointer_stop(void) +{ + if (!scoreboard_pointer_active) + return; + scoreboard_pointer_active = FALSE; + memset(&scoreboard_pointer, 0, sizeof(scoreboard_pointer)); + platform_mouse_capture(!input_state.mouse_released && !input_state.ui_pointer); +} + +BOOL platform_scoreboard_pointer(BOOL offered, struct platform_ui_pointer *pointer) +{ + BOOL active; + + pthread_mutex_lock(&input_lock); + scoreboard_pointer_offered = offered; + active = scoreboard_pointer_active && offered; + *pointer = scoreboard_pointer; + scoreboard_pointer.moved = FALSE; + scoreboard_pointer.left_clicks = 0; + scoreboard_pointer.right_clicks = 0; + scoreboard_pointer.wheel_steps = 0; + pthread_mutex_unlock(&input_lock); + return active; +} +#endif + void platform_scoreboard_scroll(int open, long *notches, long *pages) { Uint64 now = SDL_GetTicks(); @@ -1230,6 +1369,10 @@ void platform_scoreboard_scroll(int open, long *notches, long *pages) scoreboard_pages = 0; } scoreboard_open_until_ms = open ? now + SCOREBOARD_OPEN_MS : 0; +#ifndef HALO_ANDROID + if (!open) + scoreboard_pointer_offered = FALSE; +#endif if (notches) *notches = scoreboard_notches; if (pages) @@ -1239,6 +1382,24 @@ void platform_scoreboard_scroll(int open, long *notches, long *pages) pthread_mutex_unlock(&input_lock); } +void platform_screenshot_request(void) +{ + pthread_mutex_lock(&input_lock); + screenshot_requested = TRUE; + pthread_mutex_unlock(&input_lock); +} + +BOOL platform_screenshot_take_request(void) +{ + BOOL requested; + + pthread_mutex_lock(&input_lock); + requested = screenshot_requested; + screenshot_requested = FALSE; + pthread_mutex_unlock(&input_lock); + return requested; +} + void platform_pump_events(void) { /* debug.exit_after (seconds) ends the game that long after the window @@ -1267,8 +1428,19 @@ void platform_pump_events(void) platform_show_pending_message(); #ifndef HALO_ANDROID updater_poll(platform_window); + /* (Settings > Audio's output device, as it changes: dsound_sdl.c) */ + dsound_sdl_output_device_check(); + screen_keyboard_update(); #endif pthread_mutex_lock(&input_lock); +#ifndef HALO_ANDROID + /* (the scoreboard closed, or no longer offering it: the pointer goes) */ + if (scoreboard_pointer_active && (SDL_GetTicks() >= scoreboard_open_until_ms || !scoreboard_pointer_offered || + input_state.ui_pointer)) + { + scoreboard_pointer_stop(); + } +#endif while (SDL_PollEvent(&event)) { switch (event.type) @@ -1305,7 +1477,8 @@ void platform_pump_events(void) if (event.key.down && !event.key.repeat && event.key.scancode == SDL_SCANCODE_F12) { input_state.mouse_released = !input_state.mouse_released; - platform_mouse_capture(!input_state.mouse_released && !input_state.ui_pointer); + platform_mouse_capture(!input_state.mouse_released && !input_state.ui_pointer && + !scoreboard_pointer_active); } #ifndef HALO_ANDROID /* F11 switches between fullscreen and the window (SDL keeps the @@ -1317,10 +1490,14 @@ void platform_pump_events(void) #endif break; case SDL_EVENT_MOUSE_MOTION: -#ifdef HALO_ANDROID - flat_mouse_x = event.motion.x; - flat_mouse_y = event.motion.y; -#else +#ifndef HALO_ANDROID + if (scoreboard_pointer_active) + { + scoreboard_pointer.x = event.motion.x; + scoreboard_pointer.y = event.motion.y; + scoreboard_pointer.moved = TRUE; + break; + } /* in the menus the mouse moves the pointer, not the view */ if (input_state.ui_pointer) { @@ -1352,6 +1529,26 @@ void platform_pump_events(void) break; } #ifndef HALO_ANDROID + /* the open scoreboard's pointer: a right click frees it (and + fires nothing), and another takes it back; its clicks pick */ + if (!input_state.ui_pointer && SDL_GetTicks() < scoreboard_open_until_ms && scoreboard_pointer_offered && + (scoreboard_pointer_active || (event.button.down && event.button.button == SDL_BUTTON_RIGHT))) + { + if (event.button.down && event.button.button == SDL_BUTTON_RIGHT) + { + if (scoreboard_pointer_active) + scoreboard_pointer_stop(); + else + scoreboard_pointer_start(); + } + else if (event.button.down && event.button.button == SDL_BUTTON_LEFT) + { + scoreboard_pointer.left_clicks++; + scoreboard_pointer.click_x = event.button.x; + scoreboard_pointer.click_y = event.button.y; + } + break; + } /* clicks in the menus go to the pointer; a button held down when the menu closes stays up until pressed again, so the click that resumes the game does not also fire */ @@ -1427,17 +1624,58 @@ void platform_pump_events(void) memset(input_state.mouse_buttons, 0, sizeof(input_state.mouse_buttons)); memset(mouse_buttons_pressed, 0, sizeof(mouse_buttons_pressed)); input_state.focused = FALSE; + /* (the scoreboard's pointer goes; the mouse is taken back for + the aim as the window has the focus again) */ + scoreboard_pointer_active = FALSE; +#ifdef HALO_ANDROID + touch_input_cancel(); +#endif break; case SDL_EVENT_WINDOW_FOCUS_GAINED: input_state.focused = TRUE; look_at_clipboard = TRUE; #ifndef HALO_ANDROID - if (!input_state.mouse_released && !input_state.ui_pointer) + if (!input_state.mouse_released && !input_state.ui_pointer && !scoreboard_pointer_active) platform_mouse_capture(TRUE); #endif break; +#ifdef HALO_ANDROID + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_MOTION: + case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: + touch_input_event(event.type, &event.tfinger); + break; +#endif case SDL_EVENT_GAMEPAD_ADDED: +#ifdef HALO_ARM64_GUEST + /* (the guest reaches SDL only through host_imports.list, which + has no SDL_GetGamepadName) */ SDL_OpenGamepad(event.gdevice.which); +#else + { + SDL_Gamepad *gamepad = SDL_OpenGamepad(event.gdevice.which); + + /* (which pads the game drives: under Steam Input, Steam's + virtual ones, named for the controllers behind them) */ + if (gamepad) + { + const char *name = SDL_GetGamepadName(gamepad); + + platform_log("gamepad: %s", name ? name : "(unnamed)"); + } + } +#endif + break; + case SDL_EVENT_GAMEPAD_REMOVED: + /* (SDL keeps a gamepad open until it is closed, even once the + controller has gone) */ + { + SDL_Gamepad *gamepad = SDL_GetGamepadFromID(event.gdevice.which); + + if (gamepad) + SDL_CloseGamepad(gamepad); + } break; default: break; @@ -1480,23 +1718,6 @@ int platform_binding_capture_poll(int *input) return result; } -#ifdef HALO_ANDROID -/* flat, not in VR (the emulator, for testing the menus): the mouse's place -in the window and whether its left button is down, polled */ -BOOL platform_flat_mouse(float *x, float *y) -{ - BOOL down; - - pthread_mutex_lock(&input_lock); - *x = flat_mouse_x; - *y = flat_mouse_y; - /* (an injected mouse's, adb shell input mouse: button 0) */ - down = (mouse_buttons_down & ((1u << SDL_BUTTON_LEFT) | 1u)) != 0; - pthread_mutex_unlock(&input_lock); - return down; -} -#endif - #ifndef HALO_ANDROID /* ---------- the menus' pointer */ @@ -1547,17 +1768,123 @@ BOOL platform_ui_pointer_read(struct platform_ui_pointer *pointer) return active; } -#endif - void platform_video_window_size(int *width, int *height) { -#ifdef HALO_ANDROID - SDL_GetWindowSizeInPixels(platform_window, width, height); -#else SDL_GetWindowSize(platform_window, width, height); -#endif } +/* ---------- the system's on-screen keyboard */ + +/* A menu's text field is typed into (platform_text_field, xinput_sdl.c). +Where Steam's on-screen keyboard is there to bring up (in Big Picture and in +the Steam Deck's Game Mode, which ask for it with +SDL_ENABLE_STEAM_SCREEN_KEYBOARD), SDL's text input runs while the field is +typed into: the keyboard comes up with the field and goes with it, and what +it types arrives as keys. Elsewhere text input stays off, as before, so that +no input method takes the keys the field reads: a Wayland touch screen's +keyboard (text-input-v3) would type text events, which the field does not +read. */ +static SDL_AtomicInt screen_keyboard_wanted; +/* (each field begun, which brings the keyboard up again: Steam does not say +when its keyboard goes, by its own Enter or closed by hand, so SDL holds it +to be up still; after a field ended and another begun in the same frame, as +the password screen's is after a wrong password, it would not come back) */ +static SDL_AtomicInt screen_keyboard_requests; + +void platform_screen_keyboard(BOOL show, BOOL password) +{ + SDL_SetAtomicInt(&screen_keyboard_wanted, !show ? 0 : password ? 2 : 1); + if (show) + SDL_AddAtomicInt(&screen_keyboard_requests, 1); +} + +/* (on the window's thread, as SDL asks: platform_pump_events) */ +static void screen_keyboard_update(void) +{ + /* (a keyboard shown again is closed first, as SDL opens none that it + holds to be up, and opened a moment later: Steam takes each as a URL, + steam://close/keyboard then steam://open/keyboard, which must not + arrive the other way round) */ + enum { REOPEN_DELAY_MS = 500 }; + static int requests_handled; + static Uint64 open_time; + int requests = SDL_GetAtomicInt(&screen_keyboard_requests); + int wanted = SDL_GetAtomicInt(&screen_keyboard_wanted); + + if (!wanted) + { + open_time = 0; + if (SDL_TextInputActive(platform_window)) + SDL_StopTextInput(platform_window); + return; + } + if (requests != requests_handled) + { + requests_handled = requests; + if (!SDL_HasScreenKeyboardSupport() || + !SDL_GetHintBoolean(SDL_HINT_ENABLE_STEAM_SCREEN_KEYBOARD, false)) + { + return; + } + open_time = SDL_GetTicks(); + if (SDL_TextInputActive(platform_window)) + { + SDL_StopTextInput(platform_window); + open_time += REOPEN_DELAY_MS; + } + } + if (open_time && SDL_GetTicks() >= open_time) + { + /* one line: the keyboard's Enter ends the field (and Steam's + keyboard goes with it); a password's, for the keyboards that hide + what is typed into one */ + SDL_PropertiesID properties = SDL_CreateProperties(); + + open_time = 0; + platform_log("text field: showing the on-screen keyboard"); + SDL_SetBooleanProperty(properties, SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, false); + SDL_SetNumberProperty(properties, SDL_PROP_TEXTINPUT_TYPE_NUMBER, + wanted == 2 ? SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN : SDL_TEXTINPUT_TYPE_TEXT); + SDL_StartTextInputWithProperties(platform_window, properties); + SDL_DestroyProperties(properties); + } +} + +#else +/* ---------- the menus' pointer (the touchscreen) + +While a menu is up, taps and drags go to the menus (touch_input.c, +halo_ui_pointer_update in d3d8_gl.c). */ +void platform_ui_pointer_set_active(BOOL active) +{ + pthread_mutex_lock(&input_lock); + if ((active != FALSE) != (input_state.ui_pointer != FALSE)) + { + input_state.ui_pointer = active; + touch_input_menu_set_active(active != FALSE); + } + pthread_mutex_unlock(&input_lock); +} + +BOOL platform_ui_pointer_read(struct platform_ui_pointer *pointer) +{ + BOOL active; + + pthread_mutex_lock(&input_lock); + active = input_state.ui_pointer; + touch_input_menu_read(pointer); + pthread_mutex_unlock(&input_lock); + return active; +} + +/* the window is its pixels on Android (no display scaling): touch_input.c +scales the fingers' 0..1 by the drawable's size */ +void platform_video_window_size(int *width, int *height) +{ + platform_video_drawable_size(width, height); +} + +#endif void platform_input_read(struct platform_input_state *state, BOOL consume_motion) { pthread_mutex_lock(&input_lock); diff --git a/port/linux/src/sdl_platform.h b/port/linux/src/sdl_platform.h index 127b0f64..3e94b9d3 100644 --- a/port/linux/src/sdl_platform.h +++ b/port/linux/src/sdl_platform.h @@ -9,6 +9,7 @@ controller emulation (see sdl_platform.c). #define __HALO_LINUX_SDL_PLATFORM_H #include +#include #include #define PLATFORM_MOUSE_BUTTON_COUNT 8 @@ -71,6 +72,14 @@ int platform_display_resolutions(long *widths, long *heights, int maximum); that fit the desktop, and display.window_size's; how many. None on Android. */ int platform_window_sizes(long *widths, long *heights, int maximum); +/* Settings > Audio's devices (port/linux/game/menu_tags.c): the names of +the output (or, recording, input) devices SDL finds; how many. None on +Android, which plays and records with the system's. */ +#define PLATFORM_AUDIO_DEVICE_NAME_SIZE 128 +int platform_audio_devices(int recording, char (*names)[PLATFORM_AUDIO_DEVICE_NAME_SIZE], int maximum); +/* ... the device of a name (audio.output_device, audio.input_device): +the system's default for "default", or one not found */ +SDL_AudioDeviceID platform_audio_device(int recording, const char *name); void platform_video_drawable_size(int *width, int *height); /* the window's mode and size and V-Sync, from config.toml as Settings has just written it (the main thread's) */ @@ -81,25 +90,54 @@ display.interpolation is false (port/linux/game/render_interpolation.c) */ int halo_interpolation_enabled(void); void platform_mouse_capture(BOOL capture); -/* main thread only; a no-op elsewhere */ +/* handles the window's and input devices' events; main thread only, a no-op +elsewhere; on Android this is where finger events reach touch_input.c, which +is why that module's state is only touched from the main thread */ void platform_pump_events(void); +/* request a capture from the bound Screenshot action */ +void platform_screenshot_request(void); +/* consume the request; call on the render thread with a completed frame */ +BOOL platform_screenshot_take_request(void); /* a snapshot of the input state; consume_motion resets the mouse deltas */ void platform_input_read(struct platform_input_state *state, BOOL consume_motion); -#ifndef HALO_ANDROID /* the pointer in the menus (d3d8_gl.c, halo_ui_pointer_update) */ struct platform_ui_pointer { - /* in window coordinates, as SDL reports them */ + /* in window coordinates, as SDL reports them (on Android the window's + pixels: touch_input.c) */ float x, y; float click_x, click_y; BOOL moved; int left_clicks, right_clicks; int wheel_steps; + /* fingers down since the last read, and where the latest went down + (the touchscreen only; the debug view of the menus' targets shows it) */ + int downs; + float down_x, down_y; + /* the pointer is the touchscreen (touch_input.c), not a mouse */ + BOOL touch; }; +/* says whether a menu is up; the mouse is released for the menus (desktop), +or the touchscreen goes to them (Android); a change of mode drops the gesture +in progress */ void platform_ui_pointer_set_active(BOOL active); + +/* pointer receives what the pointer did since the last call; returns +nonzero while a menu is up (platform_ui_pointer_set_active) */ BOOL platform_ui_pointer_read(struct platform_ui_pointer *pointer); -#endif + +/* returns the window's size in the units that pointer positions come in, +which differ from the drawable's pixels on displays that scale */ void platform_video_window_size(int *width, int *height); +#ifndef HALO_ANDROID +/* the open scoreboard's pointer, offered (a network game's) or not: TRUE +while a right click has freed it, with what it did since the last call */ +BOOL platform_scoreboard_pointer(BOOL offered, struct platform_ui_pointer *pointer); +/* a menu's text field typed into or not (a password's or not): the system's +on-screen keyboard up while it is, where there is one that text input shows +(Steam's); each field begun brings it up again */ +void platform_screen_keyboard(BOOL show, BOOL password); +#endif BOOL platform_next_keystroke(struct platform_keystroke *keystroke); /* the multiplayer scoreboard (game_engine.c) open or not: while it is, the mouse wheel and Page Up/Down scroll it instead of switching weapons; how diff --git a/port/linux/src/touch_input.c b/port/linux/src/touch_input.c new file mode 100644 index 00000000..16611015 --- /dev/null +++ b/port/linux/src/touch_input.c @@ -0,0 +1,351 @@ +/* +TOUCH_INPUT.C + +The touchscreen of the Android build. SDL's finger events become the +menus' pointer (touch_menu.c) while a menu is up, as the desktop mouse +does (sdl_platform.c, platform_ui_pointer_read). Sizes are in dp, from the +display density the app passes (HALO_DISPLAY_DENSITY, host_main.c). The edges +of the screen where Android keeps its gestures are asked of the host at every +finger down (host_gesture_insets). +*/ + +#include "touch_input.h" +#include "touch_menu.h" +#include "port_config.h" + +#include +#include +#include + +#ifdef HALO_ANDROID +/* port/android/guest/runtime/guest_host.h */ +void host_gesture_insets(int *insets); +void host_touch_read(int *state); +void host_touch_look_read(float *delta); +void host_touch_rumble(unsigned int low, unsigned int high); +void host_touch_scene(int scene); +void host_touch_bindings(const int *controls); +#endif + +/* the game's (port/linux/game/touch_game.c) */ +int touch_game_cinematic_skippable(void); +int touch_game_cinematic_playing(void); +int touch_game_playing(void); +void touch_game_button_controls(int *controls); + +/* the gamepad's buttons (input.h), which the touch controls are named by */ +#define TOUCH_BUTTONS 16 + +/* a tap moves at most this far; a drag of this length is one wheel step */ +#define TOUCH_TAP_SLOP_DP 12.0f +#define TOUCH_SCROLL_STEP_DP 40.0f +/* the menus post one press a frame and drop presses beyond their queue +(ui_widget.c): one step a read keeps up with them without losing any */ +#define TOUCH_STEPS_PER_READ 1 + +static struct touch_menu menu; +static int menu_ready; +static int menu_active; +/* polls left to hold A: a tap is shorter than the game's poll, and the +game must see the press */ +static int skip_polls; +#define TOUCH_PRESS_POLLS 2 + +/* returns pixels per dp; 0 only while neither the density +(HALO_DISPLAY_DENSITY) nor the window's size is known */ +static float pixels_per_dp(void) +{ + const char *density = getenv("HALO_DISPLAY_DENSITY"); + int width = 0, height = 0; + + if (density && atof(density) > 0.0) + return (float)atof(density); + platform_video_drawable_size(&width, &height); + /* a phone held in landscape is about 360 dp high, whatever its density */ + return height > 0 ? (float)height / 360.0f : 0.0f; +} + +/* sets up gesture sizes once they can be known; tries again until they are */ +static void menu_setup(void) +{ + if (!menu_ready) + { + struct touch_menu_settings settings; + float dp = pixels_per_dp(); + int width = 0, height = 0; + + platform_video_drawable_size(&width, &height); + + settings.slop = TOUCH_TAP_SLOP_DP * (dp > 0.0f ? dp : 1.0f); + settings.step = TOUCH_SCROLL_STEP_DP * (dp > 0.0f ? dp : 1.0f); + settings.steps_per_read = TOUCH_STEPS_PER_READ; + /* no zones until a finger goes down: touch_input_event reads them */ + settings.edge_left = settings.edge_top = settings.edge_right = settings.edge_bottom = 0.0f; + settings.width = (float)width; + settings.height = (float)height; + touch_menu_init(&menu, &settings); + menu_ready = dp > 0.0f && width > 0 && height > 0; + } +} + +/* updates gesture zones at every finger down: the phone rotates after startup +and zones matter only at a down, so changing them between two fingers is safe */ +static void refresh_zones(int width, int height) +{ +#ifdef HALO_ANDROID + int insets[4]; + + host_gesture_insets(insets); + menu.settings.edge_left = (float)insets[0]; + menu.settings.edge_top = (float)insets[1]; + menu.settings.edge_right = (float)insets[2]; + menu.settings.edge_bottom = (float)insets[3]; +#endif + menu.settings.width = (float)width; + menu.settings.height = (float)height; +} + +void touch_input_event(unsigned int type, const SDL_TouchFingerEvent *finger) +{ + int width = 0, height = 0; + float x, y; + + menu_setup(); + /* SDL gives 0..1 of the window; the window is its pixels on Android */ + platform_video_drawable_size(&width, &height); + x = finger->x * (float)width; + y = finger->y * (float)height; + switch (type) + { + case SDL_EVENT_FINGER_DOWN: + refresh_zones(width, height); + touch_menu_down(&menu, finger->fingerID, x, y); + break; + case SDL_EVENT_FINGER_MOTION: + touch_menu_move(&menu, finger->fingerID, x, y); + break; + case SDL_EVENT_FINGER_UP: + touch_menu_up(&menu, finger->fingerID, x, y); + break; + case SDL_EVENT_FINGER_CANCELED: + touch_menu_cancel(&menu); + break; + } +} + +void touch_input_cancel(void) +{ + menu_setup(); + touch_menu_cancel(&menu); +} + +void touch_input_menu_set_active(int active) +{ + /* what a finger began before the menu opened or closed must not + click in it */ + menu_setup(); + touch_menu_reset(&menu); + menu_active = active; +} + +void touch_input_menu_read(struct platform_ui_pointer *pointer) +{ + struct touch_menu_output output; + + menu_setup(); + touch_menu_read(&menu, &output); + pointer->x = output.x; + pointer->y = output.y; + pointer->click_x = output.click_x; + pointer->click_y = output.click_y; + pointer->moved = output.moved ? TRUE : FALSE; + pointer->left_clicks = output.clicks; + pointer->right_clicks = 0; + pointer->wheel_steps = output.wheel_steps; + pointer->downs = output.downs; + pointer->down_x = output.down_x; + pointer->down_y = output.down_y; + pointer->touch = TRUE; +} + +void touch_input_gamepad(XINPUT_GAMEPAD *pad) +{ + /* outside the menus the touchscreen only skips a cinematic that can be + skipped: a tap there presses A */ + if (!menu_active) + { + struct touch_menu_output output; + + menu_setup(); + touch_menu_read(&menu, &output); + if (output.clicks && touch_game_cinematic_skippable()) + { + skip_polls = TOUCH_PRESS_POLLS; + platform_log("touch: A pressed to skip a cinematic"); + } + } + if (skip_polls > 0) + { + pad->bAnalogButtons[XINPUT_GAMEPAD_A] = 0xff; + skip_polls--; + } +} + +#ifdef HALO_ANDROID +/* ---------- the on-screen touch controls + +The overlay (port/android/app/.../TouchControls.java) draws and reads its +own fingers on Android's UI thread; port/android/host/host_touch.c hands +its state over. These bits tell it when to show: */ +enum +{ + /* the game has read its controller: the other bits are known */ + _touch_scene_known = 1 << 0, + /* a menu or a cinematic is up: the overlay hides and lets the fingers + through to the menus' pointer above */ + _touch_scene_menus = 1 << 1, + /* input.touch_controls: "on" and "off" (none: "auto", shown when the + device has a touchscreen and no controller) */ + _touch_scene_on = 1 << 2, + _touch_scene_off = 1 << 3, +}; + +/* the touch controls' stick at port 0's last read, -1..1, y down */ +static float move_x, move_y; + +/* input.touch_controls as _touch_scene_on, _touch_scene_off or 0 */ +static int touch_controls_setting(void) +{ + static int setting; + static unsigned long read_at = (unsigned long)-1; + + if (read_at != config_changes()) + { + const char *value = config_string("input.touch_controls"); + + read_at = config_changes(); + setting = 0; + if (value && !strcmp(value, "on")) + setting = _touch_scene_on; + else if (value && !strcmp(value, "off")) + setting = _touch_scene_off; + } + return setting; +} + +void touch_input_controls(XINPUT_GAMEPAD *pad, int menus) +{ + /* SDL's gamepad buttons, in order (SDL_GamepadButton); the first four + are the analog A, B, X and Y */ + static const WORD digital[] = + { + 0, 0, 0, 0, XINPUT_GAMEPAD_BACK, 0, XINPUT_GAMEPAD_START, + XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, + 0, 0, XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_DOWN, + XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_RIGHT + }; + static const int analog[] = + { + XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y + }; + static int bindings_sent[TOUCH_BUTTONS]; + static int bindings_known; + int bindings[TOUCH_BUTTONS]; + int state[7]; + int index; + + /* (the controls show only in a game: not while it starts, at the main + menu, in a menu or during a cinematic) */ + host_touch_scene(_touch_scene_known | touch_controls_setting() | + (menus || !touch_game_playing() || touch_game_cinematic_playing() ? _touch_scene_menus : 0)); + /* (the profile's mapping, for the buttons' names; only when it changes) */ + touch_game_button_controls(bindings); + if (!bindings_known || memcmp(bindings, bindings_sent, sizeof(bindings))) + { + memcpy(bindings_sent, bindings, sizeof(bindings)); + bindings_known = TRUE; + host_touch_bindings(bindings); + } + host_touch_read(state); + /* the stick moves the player through the game's input state, whatever + the profile's sticks do (touch_input_move), not as the left stick */ + move_x = state[0] / 32767.0f; + move_y = state[1] / 32767.0f; + for (index = 0; index < 4; index++) + { + if (state[6] & (1 << index)) + pad->bAnalogButtons[analog[index]] = 0xff; + } + for (index = 0; index < (int)(sizeof(digital) / sizeof(digital[0])); index++) + { + if (state[6] & (1 << index)) + pad->wButtons |= digital[index]; + } + /* the shoulders are white and black, as a DualSense's L1 and R1 */ + if (state[6] & (1 << 9)) + pad->bAnalogButtons[XINPUT_GAMEPAD_WHITE] = 0xff; + if (state[6] & (1 << 10)) + pad->bAnalogButtons[XINPUT_GAMEPAD_BLACK] = 0xff; + if (state[4]) + pad->bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] = 0xff; + if (state[5]) + pad->bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] = 0xff; +} + +int touch_input_move(float *forward, float *strafe) +{ + float x = move_x; + float y = move_y; + float largest = fabsf(x) > fabsf(y) ? fabsf(x) : fabsf(y); + + if (largest == 0.0f) + { + *forward = 0.0f; + *strafe = 0.0f; + return FALSE; + } + /* the overlay's circle onto the square a controller's stick gives + (input_abstraction_update): a full diagonal is full on both axes */ + { + float scale = sqrtf(x * x + y * y) / largest; + + x *= scale; + y *= scale; + x = x < -1.0f ? -1.0f : x > 1.0f ? 1.0f : x; + y = y < -1.0f ? -1.0f : y > 1.0f ? 1.0f : y; + } + /* (SDL's y runs down; strafe is positive to the left) */ + *forward = -y; + *strafe = -x; + return TRUE; +} + +void touch_input_look(float scale, float *yaw, float *pitch, float *gyro_yaw, float *gyro_pitch) +{ + float delta[4]; + + host_touch_look_read(delta); + *yaw -= delta[0] * scale; + *pitch -= delta[1] * scale; + *gyro_yaw -= delta[2] * scale; + *gyro_pitch -= delta[3] * scale; +} + +int touch_input_aim_assist(void) +{ + static int assisted; + static unsigned long read_at = (unsigned long)-1; + + if (read_at != config_changes()) + { + read_at = config_changes(); + assisted = config_boolean("input.touch_aim_assist") != 0; + } + return assisted; +} + +void touch_input_rumble(unsigned int left, unsigned int right) +{ + host_touch_rumble(left, right); +} +#endif diff --git a/port/linux/src/touch_input.h b/port/linux/src/touch_input.h new file mode 100644 index 00000000..15f2f632 --- /dev/null +++ b/port/linux/src/touch_input.h @@ -0,0 +1,60 @@ +/* +TOUCH_INPUT.H + +The touchscreen (touch_input.c). Everything here runs on the game's main +thread. The finger events and touch_input_menu_read run under +sdl_platform.c's input lock; touch_input_gamepad, called from +XInputGetState, does not, and reads the same touch_menu state without it. +That is safe only because platform_pump_events and XInputGetState both run +on the game's main thread: a call from any other thread needs the lock. +*/ + +#ifndef TOUCH_INPUT_H +#define TOUCH_INPUT_H + +#include "platform.h" +#include "sdl_platform.h" + +#include + +/* feeds one finger event to the menus' pointer; x and y are 0..1 of the window */ +void touch_input_event(unsigned int type, const SDL_TouchFingerEvent *finger); + +/* the window lost the focus: every touch ends without effect */ +void touch_input_cancel(void); + +/* sets whether the menus' pointer is used (a menu is up); any touch in +progress is dropped so it does not act in the other mode */ +void touch_input_menu_set_active(int active); + +/* reads what the fingers did in the menus since the last read; pointer is +marked as touch */ +void touch_input_menu_read(struct platform_ui_pointer *pointer); + +/* outside the menus, presses A for a few polls after a tap when a +cinematic can be skipped (the on-screen controls: touch_input_controls) */ +void touch_input_gamepad(XINPUT_GAMEPAD *pad); + +#ifdef HALO_ANDROID +/* the on-screen touch controls (port/android/app/.../TouchControls.java), +an Android view over the game: tells them whether a menu is up and +input.touch_controls, and adds their stick and buttons to port 0's state */ +void touch_input_controls(XINPUT_GAMEPAD *pad, int menus); + +/* the touch controls' stick as the player's movement, -1..1 (forward, and +strafe to the left); nonzero while it is pushed */ +int touch_input_move(float *forward, float *strafe); + +/* adds the touch controls' view swipe and gyroscope turn since the last +call to yaw and pitch and to gyro_yaw and gyro_pitch, in radians at `scale` +radians per pixel */ +void touch_input_look(float scale, float *yaw, float *pitch, float *gyro_yaw, float *gyro_pitch); + +/* whether the swipe gets the stick's aim assist (input.touch_aim_assist) */ +int touch_input_aim_assist(void); + +/* port 0's motors, for the phone's vibration while the touch controls show */ +void touch_input_rumble(unsigned int left, unsigned int right); +#endif + +#endif diff --git a/port/linux/src/touch_menu.c b/port/linux/src/touch_menu.c new file mode 100644 index 00000000..fadac2ea --- /dev/null +++ b/port/linux/src/touch_menu.c @@ -0,0 +1,156 @@ +/* +TOUCH_MENU.C + +The menus' pointer from a touchscreen (touch_menu.h). +*/ + +#include "touch_menu.h" + +#include +#include + +void touch_menu_init(struct touch_menu *menu, const struct touch_menu_settings *settings) +{ + memset(menu, 0, sizeof(*menu)); + menu->settings = *settings; +} + +void touch_menu_reset(struct touch_menu *menu) +{ + struct touch_menu_settings settings = menu->settings; + + touch_menu_init(menu, &settings); +} + +/* checks whether x is in the left or right gesture zone; a zone with no inset +does not exist, and the right zone needs a window size */ +static int in_side_zone(const struct touch_menu *menu, float x) +{ + const struct touch_menu_settings *s = &menu->settings; + + return x < s->edge_left || (s->edge_right > 0.0f && s->width > 0.0f && x >= s->width - s->edge_right); +} + +/* checks whether y is in the top or bottom gesture zone; like the side zones, +the bottom one needs an inset and the window's height */ +static int in_top_or_bottom_zone(const struct touch_menu *menu, float y) +{ + const struct touch_menu_settings *s = &menu->settings; + + return y < s->edge_top || (s->edge_bottom > 0.0f && s->height > 0.0f && y >= s->height - s->edge_bottom); +} + +void touch_menu_down(struct touch_menu *menu, unsigned long long finger, float x, float y) +{ + if (menu->finger_down) + return; + /* no state for an ignored finger: its moves and its lift find no finger + down, or another finger's id, and do nothing. It must not set + finger_down, or it would shut out the next finger. */ + if (in_side_zone(menu, x)) + return; + menu->finger_down = 1; + menu->finger = finger; + menu->down_x = menu->last_x = x; + menu->down_y = menu->last_y = y; + menu->output.downs++; + menu->output.down_x = x; + menu->output.down_y = y; + menu->scrolling = 0; + menu->no_scroll = in_top_or_bottom_zone(menu, y); + menu->void_tap = 0; + menu->remainder = 0.0f; + /* a finger on the screen stops a list still scrolling, so that a tap + hits what the finger sees */ + menu->pending_steps = 0; +} + +void touch_menu_move(struct touch_menu *menu, unsigned long long finger, float x, float y) +{ + float along; + + if (!menu->finger_down || finger != menu->finger) + return; + if (!menu->scrolling) + { + float dx = x - menu->down_x, dy = y - menu->down_y; + + if (dx * dx + dy * dy <= menu->settings.slop * menu->settings.slop) + return; + /* a finger from the top or bottom zone may be Android's own edge swipe, + which reaches the game too. It may tap (the legends sit in the bottom + zone), but beyond the slop it is neither a scroll nor a tap, so that + its lift does not click where it began. */ + if (menu->no_scroll) + { + menu->void_tap = 1; + return; + } + /* the steps count from the slop's edge on the axis: the slop is not + a step, and a fast first sample is not lost */ + menu->scrolling = 1; + menu->axis = fabsf(dx) > fabsf(dy); + if (menu->axis) + menu->last_x = menu->down_x + (dx > 0.0f ? menu->settings.slop : -menu->settings.slop); + else + menu->last_y = menu->down_y + (dy > 0.0f ? menu->settings.slop : -menu->settings.slop); + } + along = menu->axis ? x - menu->last_x : y - menu->last_y; + menu->last_x = x; + menu->last_y = y; + menu->remainder += along; + while (menu->remainder >= menu->settings.step) + { + menu->pending_steps++; + menu->remainder -= menu->settings.step; + } + while (menu->remainder <= -menu->settings.step) + { + menu->pending_steps--; + menu->remainder += menu->settings.step; + } +} + +void touch_menu_up(struct touch_menu *menu, unsigned long long finger, float x, float y) +{ + if (!menu->finger_down || finger != menu->finger) + return; + touch_menu_move(menu, finger, x, y); + if (!menu->scrolling && !menu->void_tap) + { + menu->output.moved = 1; + menu->output.x = menu->output.click_x = menu->down_x; + menu->output.y = menu->output.click_y = menu->down_y; + menu->output.clicks++; + } + menu->finger_down = 0; + menu->scrolling = 0; + menu->void_tap = 0; +} + +void touch_menu_cancel(struct touch_menu *menu) +{ + menu->finger_down = 0; + menu->scrolling = 0; + menu->void_tap = 0; + menu->remainder = 0.0f; + menu->pending_steps = 0; + menu->output.downs = 0; +} + +void touch_menu_read(struct touch_menu *menu, struct touch_menu_output *output) +{ + int limit = menu->settings.steps_per_read; + int steps = menu->pending_steps; + + if (steps > limit) + steps = limit; + if (steps < -limit) + steps = -limit; + *output = menu->output; + output->wheel_steps = steps; + menu->pending_steps -= steps; + menu->output.moved = 0; + menu->output.clicks = 0; + menu->output.downs = 0; +} diff --git a/port/linux/src/touch_menu.h b/port/linux/src/touch_menu.h new file mode 100644 index 00000000..6a8a3fc6 --- /dev/null +++ b/port/linux/src/touch_menu.h @@ -0,0 +1,112 @@ +/* +TOUCH_MENU.H + +The menus' pointer from a touchscreen: taps and drags of one finger +become what the desktop mouse gives the menus (port/linux/src/sdl_platform.c, +platform_ui_pointer_read). A finger that goes down and up without moving +further than the slop is a tap: the pointer goes where it went down, and +clicks there. A finger that moves further scrolls instead: each step of +its drag along the axis that dominated when it started is one notch of the +wheel, counted from the edge of the slop. No tap and no hover come from a +scroll, so the focus does not follow the finger. Other fingers are +ignored while one is down, and a new finger stops the steps still waiting. + +No SDL or game types, so that the build machine can test it +(port/linux/tests/touch_menu_test.c). +*/ + +#ifndef TOUCH_MENU_H +#define TOUCH_MENU_H + +struct touch_menu_settings +{ + /* pixels a tap may move */ + float slop; + /* pixels of drag for one wheel step */ + float step; + /* at most this many steps per read: the rest waits, so that a fast + swipe moves a list gradually, as fast as the menus take the steps */ + int steps_per_read; + /* Android's system gesture zones, in pixels of the window (all 0: none). + With the system bars hidden the first edge swipe only shows them, and + Android hands that swipe to the game as an ordinary finger, so a finger + that begins in a zone must not act as a menu touch: + - in the left or right zone it is ignored for its whole life, a corner + included; it does not keep another finger from being used; + - in the top or bottom zone it still taps (the A/B legends sit in the + bottom zone) but never scrolls: once it moves beyond the slop it is + neither a tap nor a scroll. */ + float edge_left, edge_top, edge_right, edge_bottom; + /* the window's size, for the right and bottom zones: 0 means those two + zones do not exist, so that a size not yet known cannot fill the screen. + A zone with a 0 inset does not exist either. The zones may change + between two fingers (the phone rotates): they are read at a down. */ + float width, height; +}; + +struct touch_menu_output +{ + /* where the latest tap was, in pixels */ + float x, y; + float click_x, click_y; + /* a tap happened: the pointer is at x, y */ + int moved; + /* taps since the last read */ + int clicks; + /* positive: back (the finger moved down or right) */ + int wheel_steps; + /* fingers that went down since the last read, and where the latest + did: a drag reports no tap, and the debug view (debug.touch_targets) + wants to show where it began */ + int downs; + float down_x, down_y; +}; + +struct touch_menu +{ + struct touch_menu_settings settings; + int finger_down; + unsigned long long finger; + float down_x, down_y; + float last_x, last_y; + int scrolling; + /* the finger began in the top or bottom zone: it may tap, not scroll */ + int no_scroll; + /* a no_scroll finger moved beyond the slop: no tap and no scroll */ + int void_tap; + /* 0: vertical, 1: horizontal */ + int axis; + /* drag not yet a whole step */ + float remainder; + int pending_steps; + struct touch_menu_output output; +}; + +/* prepares a menu pointer; the state is cleared and settings are copied */ +void touch_menu_init(struct touch_menu *menu, const struct touch_menu_settings *settings); + +/* forgets the finger, pending steps and taps but keeps settings: what a +finger began before a menu opened or closed must not act in the other one */ +void touch_menu_reset(struct touch_menu *menu); + +/* a finger went down; a second finger is ignored, and pending steps are +dropped so a tap hits what the finger sees on a list still scrolling */ +void touch_menu_down(struct touch_menu *menu, unsigned long long finger, float x, float y); + +/* the finger moved; once further than the slop it scrolls, with steps +counted from the slop's edge so the slop is not a step and a fast first +sample is not lost */ +void touch_menu_move(struct touch_menu *menu, unsigned long long finger, float x, float y); + +/* the finger went up; a tap if it never scrolled, at the place where it +went down (the last part of a drag counts) */ +void touch_menu_up(struct touch_menu *menu, unsigned long long finger, float x, float y); + +/* the system took the touch away: the gesture neither taps nor scrolls */ +void touch_menu_cancel(struct touch_menu *menu); + +/* hands out what happened since the last read; at most steps_per_read +wheel steps come out; the rest waits for the next read */ +void touch_menu_read(struct touch_menu *menu, struct touch_menu_output *output); + +#endif diff --git a/port/linux/src/updater.c b/port/linux/src/updater.c index c170fcf5..f901a24a 100644 --- a/port/linux/src/updater.c +++ b/port/linux/src/updater.c @@ -47,7 +47,7 @@ update.h's: posix_update.c on Linux, win32_update.c on Windows. #endif /* the VR port's own releases (the original's must not replace it) */ -#define UPDATE_REPOSITORY "Cesarus85/halo-ce-vr" +#define UPDATE_REPOSITORY "Cesarus85/ringfall-mjolnir-vr" #ifdef _WIN32 #define UPDATE_PLATFORM "windows" #define PATH_SEPARATOR "\\" @@ -415,6 +415,7 @@ static int updater_download_zip(const char *zip_path, char *error, size_t error_ thread = SDL_CreateThread(updater_download_thread, "update download", &download); if (!thread) { + SDL_DestroyMutex(download.lock); snprintf(error, error_size, "could not start the download"); return 0; } @@ -526,7 +527,8 @@ static void updater_clean_up(void) { static const char *const names[] = { - "halo.old", "halo.exe.old", "SDL3.dll.old", "extract-xiso-LICENSE.txt.old", "mbedtls-LICENSE.txt.old", + "halo.old", "halo.exe.old", "SDL3.dll.old", "libSDL3.so.0.old", "SDL3-LICENSE.txt.old", + "extract-xiso-LICENSE.txt.old", "mbedtls-LICENSE.txt.old", }; char path[1200]; size_t index; diff --git a/port/linux/src/voice_audio.c b/port/linux/src/voice_audio.c new file mode 100644 index 00000000..297242fa --- /dev/null +++ b/port/linux/src/voice_audio.c @@ -0,0 +1,549 @@ +/* +VOICE_AUDIO.C + +Voice chat's sound (port/linux/game/network_voice.c decides who hears whom): +the microphone, read in frames of 20 ms at 48 kHz, mono, and encoded with +Opus (port/third_party/opus); and the other players' voices, decoded into a +buffer each and mixed into the game's output (dsound_sdl.c's mix, at its +48 kHz, stereo) at their gain and pan. + +The microphone is opened on its own thread (SDL asks Android for the +permission to record there, and waits for the answer), and only while voice +chat may send: network_voice.c closes it otherwise. A speaker plays once +VOICE_PREBUFFER_SAMPLES are buffered (the network's jitter), and waits again +when its buffer runs out; a frame lost in between is made up from the next +one's redundancy (Opus's in-band FEC), or by concealment. +*/ + +#include "platform.h" +#include "sdl_platform.h" +#include "port_config.h" +#include "voice_audio.h" +#include "../../third_party/opus/include/opus.h" + +#include +#include +#include +#include +#include +#include + +#define VOICE_RATE 48000 +/* a speaker's buffer, and how much it holds before it plays */ +#define VOICE_RING_SAMPLES (VOICE_RATE / 2) +#define VOICE_PREBUFFER_SAMPLES (3 * VOICE_FRAME_SAMPLES) +/* how many speakers are decoded at once, and how long one unheard keeps +its decoder (milliseconds) */ +#define VOICE_SLOTS 16 +#define VOICE_SLOT_IDLE_MS 5000 +/* frames made up for when packets are lost, at most; more lost, it starts +over */ +#define VOICE_MAXIMUM_CONCEALED 3 +/* the microphone's frames kept before the oldest are dropped (a game that +stalled must not talk late) */ +#define VOICE_MAXIMUM_BACKLOG (5 * VOICE_FRAME_SAMPLES) +/* how often a microphone that would not open is tried again (ms) */ +#define VOICE_RETRY_MS 10000 + +struct voice_slot +{ + BOOL used; + int speaker; + OpusDecoder *decoder; + /* the sequence next expected; FALSE: none yet */ + BOOL sequenced; + unsigned short next_sequence; + Uint64 heard_ms; + /* the decoded samples, from read for count */ + float ring[VOICE_RING_SAMPLES]; + int read, count; + BOOL playing; + /* the gain and pan wanted, and those mixed last */ + float gain, pan; + float left, right; + /* (debug.voice_test: when it was logged) */ + Uint64 logged_ms; +}; + +static pthread_mutex_t voice_lock = PTHREAD_MUTEX_INITIALIZER; +static struct voice_slot voice_slots[VOICE_SLOTS]; +static float voice_volume = 1.0f; + +/* the microphone (under voice_lock: the opening thread sets it) */ +static SDL_AudioStream *microphone; +/* the device it was opened on (audio.input_device), and the one to open */ +static char microphone_device[PLATFORM_AUDIO_DEVICE_NAME_SIZE]; +static char microphone_opening_device[PLATFORM_AUDIO_DEVICE_NAME_SIZE]; +static BOOL microphone_wanted; +static BOOL microphone_opening; +static Uint64 microphone_failed_ms; +static BOOL microphone_failed; +static float capture[VOICE_FRAME_SAMPLES]; +static int capture_count; +static OpusEncoder *encoder; +static int encoder_bitrate; +/* debug.voice_test (the automated tests): a tone instead of the microphone, +a frame each 20 ms since the first; and each speaker's level logged once a +second */ +static int voice_test = -1; +static Uint64 voice_test_started_ms; +static unsigned long voice_test_frames; +static double voice_test_phase; + +/* ---------- the microphone */ + +static void *microphone_open_thread(void *parameter) +{ + SDL_AudioSpec spec; + SDL_AudioStream *stream; + + (void)parameter; + spec.format = SDL_AUDIO_F32; + spec.channels = 1; + spec.freq = VOICE_RATE; + stream = SDL_OpenAudioDeviceStream(platform_audio_device(TRUE, microphone_opening_device), &spec, NULL, NULL); + if (!stream && strcmp(microphone_opening_device, "default")) + stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &spec, NULL, NULL); + if (stream) + SDL_ResumeAudioStreamDevice(stream); + else + platform_log("voice chat: no microphone (%s)", SDL_GetError()); + pthread_mutex_lock(&voice_lock); + microphone_opening = FALSE; + if (stream && !microphone_wanted) + { + /* (closed again meanwhile) */ + pthread_mutex_unlock(&voice_lock); + SDL_DestroyAudioStream(stream); + return NULL; + } + microphone = stream; + snprintf(microphone_device, sizeof(microphone_device), "%s", microphone_opening_device); + microphone_failed = stream == NULL; + microphone_failed_ms = SDL_GetTicks(); + pthread_mutex_unlock(&voice_lock); + if (stream) + platform_log("voice chat: microphone open"); + return NULL; +} + +static BOOL voice_testing(void) +{ + if (voice_test < 0) + voice_test = config_boolean("debug.voice_test") ? 1 : 0; + return voice_test != 0; +} + +int voice_audio_microphone(int open) +{ + SDL_AudioStream *closing = NULL; + BOOL start = FALSE; + BOOL ready; + + if (voice_testing()) + { + if (open && !voice_test_started_ms) + { + voice_test_started_ms = SDL_GetTicks(); + voice_test_frames = 0; + } + if (!open) + voice_test_started_ms = 0; + return open; + } + if (!platform_sdl_initialize()) + return FALSE; + { + /* (audio.input_device: Settings > Audio's, none on Android) */ + const char *device = config_string("audio.input_device"); + + device = device && device[0] ? device : "default"; + pthread_mutex_lock(&voice_lock); + /* (another device chosen: the one open closed, and the new opened) */ + if (microphone && strcmp(microphone_device, device)) + { + closing = microphone; + microphone = NULL; + microphone_failed = FALSE; + } + if (!microphone_opening) + snprintf(microphone_opening_device, sizeof(microphone_opening_device), "%s", device); + pthread_mutex_unlock(&voice_lock); + if (closing) + { + SDL_DestroyAudioStream(closing); + closing = NULL; + capture_count = 0; + } + } + pthread_mutex_lock(&voice_lock); + microphone_wanted = open; + if (open && !microphone && !microphone_opening && + (!microphone_failed || SDL_GetTicks() - microphone_failed_ms >= VOICE_RETRY_MS)) + { + microphone_opening = TRUE; + start = TRUE; + } + if (!open && microphone) + { + closing = microphone; + microphone = NULL; + microphone_failed = FALSE; + } + ready = microphone != NULL; + pthread_mutex_unlock(&voice_lock); + if (closing) + { + SDL_DestroyAudioStream(closing); + capture_count = 0; + platform_log("voice chat: microphone closed"); + } + if (start) + { + pthread_t thread; + + if (pthread_create(&thread, NULL, microphone_open_thread, NULL) == 0) + pthread_detach(thread); + else + { + pthread_mutex_lock(&voice_lock); + microphone_opening = FALSE; + pthread_mutex_unlock(&voice_lock); + } + } + return ready; +} + +int voice_audio_read_frame(float *frame) +{ + SDL_AudioStream *stream; + + if (voice_testing()) + { + int index; + + if (!voice_test_started_ms || + (SDL_GetTicks() - voice_test_started_ms) / 20 <= voice_test_frames) + { + return FALSE; + } + voice_test_frames++; + for (index = 0; index < VOICE_FRAME_SAMPLES; index++) + { + frame[index] = 0.3f * (float)sin(voice_test_phase); + voice_test_phase += 2.0 * 3.14159265358979 * 440.0 / VOICE_RATE; + } + voice_test_phase = fmod(voice_test_phase, 2.0 * 3.14159265358979); + return TRUE; + } + + pthread_mutex_lock(&voice_lock); + stream = microphone; + pthread_mutex_unlock(&voice_lock); + if (!stream) + return FALSE; + /* (a backlog dropped but for the latest frames) */ + { + int available = SDL_GetAudioStreamAvailable(stream) / (int)sizeof(float); + + while (available > VOICE_MAXIMUM_BACKLOG) + { + float discard[VOICE_FRAME_SAMPLES]; + int take = available - VOICE_MAXIMUM_BACKLOG; + + if (take > VOICE_FRAME_SAMPLES) + take = VOICE_FRAME_SAMPLES; + if (SDL_GetAudioStreamData(stream, discard, take * (int)sizeof(float)) <= 0) + break; + available -= take; + } + } + while (capture_count < VOICE_FRAME_SAMPLES) + { + int got = SDL_GetAudioStreamData(stream, capture + capture_count, + (VOICE_FRAME_SAMPLES - capture_count) * (int)sizeof(float)); + + if (got <= 0) + return FALSE; + capture_count += got / (int)sizeof(float); + } + memcpy(frame, capture, sizeof(capture)); + capture_count = 0; + return TRUE; +} + +float voice_audio_level(const float *frame) +{ + double sum = 0.0; + int index; + + for (index = 0; index < VOICE_FRAME_SAMPLES; index++) + sum += (double)frame[index] * frame[index]; + return (float)sqrt(sum / VOICE_FRAME_SAMPLES); +} + +int voice_audio_encode(const float *frame, int bitrate, unsigned char *packet, int maximum) +{ + int length; + + if (!encoder) + { + int error = 0; + + encoder = opus_encoder_create(VOICE_RATE, 1, OPUS_APPLICATION_VOIP, &error); + if (!encoder) + { + platform_log("voice chat: no Opus encoder (%d)", error); + return 0; + } + /* (a lost frame made up from the next's redundancy; modest + complexity: Android runs the game emulated) */ + opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(1)); + opus_encoder_ctl(encoder, OPUS_SET_PACKET_LOSS_PERC(10)); + opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(5)); + opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); + encoder_bitrate = 0; + } + if (bitrate != encoder_bitrate) + { + opus_encoder_ctl(encoder, OPUS_SET_BITRATE(bitrate)); + encoder_bitrate = bitrate; + } + length = opus_encode_float(encoder, frame, VOICE_FRAME_SAMPLES, packet, maximum); + return length > 0 ? length : 0; +} + +/* ---------- the voices */ + +/* (under voice_lock) the slot of a speaker, or a free one (or the longest +unheard) taken for it */ +static struct voice_slot *voice_slot_for(int speaker, BOOL create) +{ + Uint64 now = SDL_GetTicks(); + struct voice_slot *oldest = NULL; + int index; + + for (index = 0; index < VOICE_SLOTS; index++) + { + if (voice_slots[index].used && voice_slots[index].speaker == speaker) + return &voice_slots[index]; + } + if (!create) + return NULL; + for (index = 0; index < VOICE_SLOTS; index++) + { + struct voice_slot *slot = &voice_slots[index]; + + if (!slot->used) + { + oldest = slot; + break; + } + if (now - slot->heard_ms >= VOICE_SLOT_IDLE_MS && (!oldest || slot->heard_ms < oldest->heard_ms)) + oldest = slot; + } + if (!oldest) + return NULL; + if (!oldest->decoder) + { + int error = 0; + + oldest->decoder = opus_decoder_create(VOICE_RATE, 1, &error); + if (!oldest->decoder) + return NULL; + } + else + { + opus_decoder_ctl(oldest->decoder, OPUS_RESET_STATE); + } + oldest->used = TRUE; + oldest->speaker = speaker; + oldest->sequenced = FALSE; + oldest->read = oldest->count = 0; + oldest->playing = FALSE; + oldest->left = oldest->right = 0.0f; + return oldest; +} + +/* (under voice_lock) samples into a slot's buffer, the oldest dropped when +it is full */ +static void voice_slot_put(struct voice_slot *slot, const float *samples, int count) +{ + int index; + + for (index = 0; index < count; index++) + { + if (slot->count == VOICE_RING_SAMPLES) + { + slot->read = (slot->read + 1) % VOICE_RING_SAMPLES; + slot->count--; + } + slot->ring[(slot->read + slot->count) % VOICE_RING_SAMPLES] = samples[index]; + slot->count++; + } +} + +void voice_audio_play(int speaker, unsigned short sequence, const unsigned char *packet, int length, float gain, + float pan) +{ + float samples[(VOICE_MAXIMUM_CONCEALED + 1) * VOICE_FRAME_SAMPLES]; + struct voice_slot *slot; + OpusDecoder *decoder; + int lost = 0; + int total = 0; + int decoded; + + if (length <= 0 || length > VOICE_MAXIMUM_PACKET) + return; + pthread_mutex_lock(&voice_lock); + slot = voice_slot_for(speaker, TRUE); + if (!slot) + { + pthread_mutex_unlock(&voice_lock); + return; + } + slot->heard_ms = SDL_GetTicks(); + slot->gain = gain < 0.0f ? 0.0f : gain > 2.0f ? 2.0f : gain; + slot->pan = pan < -1.0f ? -1.0f : pan > 1.0f ? 1.0f : pan; + if (slot->sequenced) + { + unsigned short ahead = (unsigned short)(sequence - slot->next_sequence); + + /* (late, or repeated: dropped) */ + if (ahead >= 0x8000) + { + pthread_mutex_unlock(&voice_lock); + return; + } + /* (a few lost are made up for; more, it starts over) */ + if (ahead <= VOICE_MAXIMUM_CONCEALED) + lost = ahead; + } + slot->sequenced = TRUE; + slot->next_sequence = (unsigned short)(sequence + 1); + decoder = slot->decoder; + pthread_mutex_unlock(&voice_lock); + /* (decoded outside the lock, which the mixer waits on: only this + thread uses a slot's decoder, and only it gives slots to speakers) */ + while (lost > 0) + { + /* (the last lost from this packet's redundancy, those before it + concealed) */ + BOOL redundancy = lost == 1; + + decoded = opus_decode_float(decoder, redundancy ? packet : NULL, redundancy ? length : 0, + samples + total, VOICE_FRAME_SAMPLES, redundancy ? 1 : 0); + if (decoded > 0) + total += decoded; + lost--; + } + decoded = opus_decode_float(decoder, packet, length, samples + total, VOICE_FRAME_SAMPLES, 0); + if (decoded > 0) + total += decoded; + /* (the tests: what each speaker sounds like, once a second) */ + if (voice_testing() && total >= VOICE_FRAME_SAMPLES) + { + Uint64 now = SDL_GetTicks(); + + if (now - slot->logged_ms >= 1000) + { + slot->logged_ms = now; + platform_log("voice test: speaker %d level %.3f gain %.2f pan %.2f bytes %d", speaker, + voice_audio_level(samples + total - VOICE_FRAME_SAMPLES), gain, pan, length); + } + } + pthread_mutex_lock(&voice_lock); + if (slot->used && slot->speaker == speaker) + voice_slot_put(slot, samples, total); + pthread_mutex_unlock(&voice_lock); +} + +void voice_audio_forget(int speaker) +{ + struct voice_slot *slot; + + pthread_mutex_lock(&voice_lock); + slot = voice_slot_for(speaker, FALSE); + if (slot) + { + slot->used = FALSE; + slot->count = 0; + slot->playing = FALSE; + } + pthread_mutex_unlock(&voice_lock); +} + +void voice_audio_forget_all(void) +{ + int index; + + pthread_mutex_lock(&voice_lock); + for (index = 0; index < VOICE_SLOTS; index++) + { + voice_slots[index].used = FALSE; + voice_slots[index].count = 0; + voice_slots[index].playing = FALSE; + } + pthread_mutex_unlock(&voice_lock); +} + +int voice_audio_speaking(int speaker) +{ + struct voice_slot *slot; + BOOL speaking = FALSE; + + pthread_mutex_lock(&voice_lock); + slot = voice_slot_for(speaker, FALSE); + if (slot) + speaking = slot->playing || slot->count >= VOICE_FRAME_SAMPLES; + pthread_mutex_unlock(&voice_lock); + return speaking; +} + +void voice_audio_set_volume(float volume) +{ + voice_volume = volume < 0.0f ? 0.0f : volume > 2.0f ? 2.0f : volume; +} + +void voice_audio_mix(float *output, unsigned long frames) +{ + int index; + + pthread_mutex_lock(&voice_lock); + for (index = 0; index < VOICE_SLOTS; index++) + { + struct voice_slot *slot = &voice_slots[index]; + float angle, left, right, step_left, step_right; + unsigned long frame, count; + + if (!slot->used) + continue; + if (!slot->playing && slot->count >= VOICE_PREBUFFER_SAMPLES) + slot->playing = TRUE; + if (!slot->playing) + continue; + /* (equal power: both sides at the gain when centred) */ + angle = (slot->pan + 1.0f) * 0.25f * 3.14159265f; + left = slot->gain * voice_volume * cosf(angle) * 1.41421356f; + right = slot->gain * voice_volume * sinf(angle) * 1.41421356f; + count = (unsigned long)slot->count < frames ? (unsigned long)slot->count : frames; + /* (the gains glide to their new values over the chunk) */ + step_left = count ? (left - slot->left) / (float)count : 0.0f; + step_right = count ? (right - slot->right) / (float)count : 0.0f; + for (frame = 0; frame < count; frame++) + { + float sample = slot->ring[slot->read]; + + slot->left += step_left; + slot->right += step_right; + output[frame * 2] += sample * slot->left; + output[frame * 2 + 1] += sample * slot->right; + slot->read = (slot->read + 1) % VOICE_RING_SAMPLES; + } + slot->count -= (int)count; + /* (run out: it waits to buffer again) */ + if (!slot->count) + slot->playing = FALSE; + } + pthread_mutex_unlock(&voice_lock); +} diff --git a/port/linux/src/voice_audio.h b/port/linux/src/voice_audio.h new file mode 100644 index 00000000..428e08ba --- /dev/null +++ b/port/linux/src/voice_audio.h @@ -0,0 +1,44 @@ +/* +VOICE_AUDIO.H + +Voice chat's sound (voice_audio.c): the microphone, Opus, and the other +players' voices mixed into the game's output. Called by +port/linux/game/network_voice.c (the game's side, so its flags are int) and +dsound_sdl.c (the mix). +*/ + +#ifndef VOICE_AUDIO_H +#define VOICE_AUDIO_H + +/* a frame: 20 ms at 48 kHz, mono */ +#define VOICE_FRAME_SAMPLES 960 +/* the longest packet sent or taken: the voice messages give its length in a +byte (network_voice.c). 64 kbps is 160 bytes a frame; the encoder fits a +louder one to it rather than going over */ +#define VOICE_MAXIMUM_PACKET 255 + +/* opens the microphone (on another thread: not at once) or closes it; +whether it is open */ +int voice_audio_microphone(int open); +/* the next 20 ms the microphone heard, if it has them all; 1 if so */ +int voice_audio_read_frame(float *frame); +/* a frame's level (its root mean square, 1 at full scale) */ +float voice_audio_level(const float *frame); +/* a frame encoded at the bitrate (bits a second); its packet's length, 0 +for none */ +int voice_audio_encode(const float *frame, int bitrate, unsigned char *packet, int maximum); +/* a speaker's packet (speaker: any number naming them), to play at the +gain (1: as spoken) and pan (-1 left to 1 right) */ +void voice_audio_play(int speaker, unsigned short sequence, const unsigned char *packet, int length, float gain, + float pan); +/* a speaker no longer heard: their buffer and decoder freed; or all */ +void voice_audio_forget(int speaker); +void voice_audio_forget_all(void); +/* whether a speaker's voice is playing now */ +int voice_audio_speaking(int speaker); +void voice_audio_set_volume(float volume); +/* (dsound_sdl.c, on the audio thread) the voices added to the output +(stereo, 48 kHz) */ +void voice_audio_mix(float *output, unsigned long frames); + +#endif diff --git a/port/linux/src/xbox_textures.c b/port/linux/src/xbox_textures.c index 558fe055..9e3840dc 100644 --- a/port/linux/src/xbox_textures.c +++ b/port/linux/src/xbox_textures.c @@ -22,7 +22,7 @@ memory_watch.c detects that by write-protecting the pages. #include "../game/cache_file_formats.h" #include -#ifdef HALO_ANDROID +#ifdef HALO_GLES #define GL_BGRA GL_RGBA #endif #include @@ -423,7 +423,7 @@ static BOOL decode_level(const struct xgpu_texture_description *description, uns return TRUE; } -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ---------- DXT decoding, for ES drivers without S3TC (Mali) */ static unsigned long color565(unsigned long value) @@ -563,7 +563,7 @@ static GLenum compressed_format(unsigned char kind) /* debug.texture_dump_directory writes level 0 of every upload as a TGA, read back from GL */ static void texture_dump(GLenum target, const struct xgpu_texture_description *description) { -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES cannot read textures back */ (void)target; (void)description; @@ -706,7 +706,7 @@ static void upload(GLuint texture, GLenum target, const struct xgpu_texture_desc unsigned long *converted; unsigned long face, level; -#ifdef HALO_ANDROID +#ifdef HALO_GLES decode_compressed = description->compressed && !xgpu_capabilities.s3tc; #endif converted = description->compressed && !decode_compressed ? NULL : malloc(largest * sizeof(unsigned long)); @@ -723,7 +723,7 @@ static void upload(GLuint texture, GLenum target, const struct xgpu_texture_desc { GLint channels[4] = { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA }; -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* converted texels are BGRA in memory (32-bit ARGB words); ES takes RGBA */ if (converted) @@ -770,7 +770,7 @@ static void upload(GLuint texture, GLenum target, const struct xgpu_texture_desc } else { -#ifdef HALO_ANDROID +#ifdef HALO_GLES if (decode_compressed) dxt_decode_level(information.kind, source, (unsigned long)width, (unsigned long)height, (unsigned long)depth, converted); diff --git a/port/linux/src/xgpu.h b/port/linux/src/xgpu.h index 9943d939..d6cbe3d5 100644 --- a/port/linux/src/xgpu.h +++ b/port/linux/src/xgpu.h @@ -13,7 +13,7 @@ device itself (d3d8_gl.c). #include "platform.h" #include "gl.h" -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* OpenGL ES features that are optional (d3d8_gl.c gl_initialize) */ struct xgpu_capabilities { @@ -156,7 +156,7 @@ struct nv2a_pixel_shader_key char *nv2a_pixel_shader_to_glsl(const struct nv2a_pixel_shader_key *key); -#ifdef HALO_ANDROID +#ifdef HALO_GLES /* ES samplers have no LOD bias of their own */ #define XGPU_PIXEL_UNIFORMS_ES "uniform vec4 texture_lod_bias;\n" #else diff --git a/port/linux/src/xgpu_post.c b/port/linux/src/xgpu_post.c index 03461ad2..a7bd4669 100644 --- a/port/linux/src/xgpu_post.c +++ b/port/linux/src/xgpu_post.c @@ -24,7 +24,7 @@ anti_aliasing_values). #include -#ifndef HALO_ANDROID +#ifndef HALO_GLES #include "zlib_prefixed.h" /* port/third_party/smaa, embedded by tools/embed_assets.py: the shader's @@ -74,7 +74,7 @@ static struct static void shader_header(struct xgpu_text *text) { -#ifdef HALO_ANDROID +#ifdef HALO_GLES xgpu_text_append(text, "#version %s\n" "precision highp float;\n" @@ -188,7 +188,7 @@ static const char fxaa_source[] = "\tresult = vec4(color_at(position + max(varies ? pixel_offset : 0.0, subpixel) * across), 1.0);\n" "}\n"; -#ifndef HALO_ANDROID +#ifndef HALO_GLES /* the main functions of SMAA's passes, after SMAA.hlsl; its vertex functions' offsets are found for each pixel */ static const char *const smaa_mains[] = @@ -267,7 +267,7 @@ static GLuint program_build(int which) { xgpu_text_append(&text, "%s", fxaa_source); } -#ifndef HALO_ANDROID +#ifndef HALO_GLES else if (xgpu_smaa_shader_size) { xgpu_text_append(&text, @@ -358,7 +358,7 @@ static void textures_fit(BOOL smaa, unsigned long width, unsigned long height) } } -#ifndef HALO_ANDROID +#ifndef HALO_GLES /* a lookup texture of SMAA's from its zlib stream; 0 if it does not inflate */ static GLuint lookup_texture(const unsigned int *stream, unsigned long stream_size, GLenum format, @@ -437,7 +437,7 @@ BOOL xgpu_post_prepare(BOOL smaa) return FALSE; } } -#ifndef HALO_ANDROID +#ifndef HALO_GLES if (smaa && !post.area) { post.area = lookup_texture(xgpu_smaa_area_texture, xgpu_smaa_area_texture_size, GL_RG8, GL_RG, @@ -478,7 +478,7 @@ BOOL xgpu_post_anti_alias(BOOL smaa, GLuint framebuffer, unsigned long width, un glDisable(GL_BLEND); glDisable(GL_CULL_FACE); glDisable(GL_POLYGON_OFFSET_FILL); -#ifndef HALO_ANDROID +#ifndef HALO_GLES glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif glBindVertexArray(post.vertex_array); diff --git a/port/linux/src/xgpu_text.c b/port/linux/src/xgpu_text.c index 23beb89c..83a84b3f 100644 --- a/port/linux/src/xgpu_text.c +++ b/port/linux/src/xgpu_text.c @@ -26,7 +26,15 @@ void xgpu_text_append(struct xgpu_text *text, const char *format, ...) text->length += (unsigned long)needed; return; } - text->capacity = (text->capacity + (unsigned long)needed + 1) * 2; - text->buffer = realloc(text->buffer, text->capacity); + { + unsigned long capacity = (text->capacity + (unsigned long)needed + 1) * 2; + char *buffer = realloc(text->buffer, capacity); + + /* (out of memory: the text stays as it is, short) */ + if (!buffer) + return; + text->buffer = buffer; + text->capacity = capacity; + } } } diff --git a/port/linux/src/xinput_sdl.c b/port/linux/src/xinput_sdl.c index e3c308ec..9151011a 100644 --- a/port/linux/src/xinput_sdl.c +++ b/port/linux/src/xinput_sdl.c @@ -22,8 +22,9 @@ In the menus the keys drive the controller, to move about them: on-screen keyboard takes what is typed, and the mouse is free and drives a pointer (port/linux/include/halo_ui_pointer.h, source/interface/ui_widget.c). -F11 switches between fullscreen and the window, and F12 releases or -recaptures the mouse, always. +Screenshot is a normal bound action (default F10), also available in the +menus. F11 switches between fullscreen and the window, and F12 releases +or recaptures the mouse, always. Mouse aim does not go through the right stick: the game's look code asks halo_linux_mouse_look for the motion since its last call and adds it to the @@ -40,6 +41,7 @@ drive the controller. #include "sdl_platform.h" #include "port_config.h" #include "halo_vr.h" +#include "touch_input.h" #include "halo_keyboard.h" #include @@ -90,10 +92,14 @@ static Uint64 wheel_press_until_ms = 0; static BOOL wheel_scrolling = FALSE; /* the way the scroll under way turns: 1 up (away), -1 down */ static int wheel_direction = 0; -/* when port 0's aim last moved, by the mouse and by the right stick -(halo_linux_mouse_aiming) */ +/* when port 0's aim last moved, by the mouse, by the right stick, and by +the touch controls (their swipe, gyroscope or stick: halo_linux_mouse_aiming, +halo_linux_touch_aiming) */ static Uint64 mouse_aimed_ms = 0; static Uint64 stick_aimed_ms = 0; +#ifdef HALO_ANDROID +static Uint64 touch_aimed_ms = 0; +#endif /* the right stick's deflection that counts as aiming with it, clear of a worn stick's drift */ @@ -153,6 +159,83 @@ int halo_linux_mouse_look(short gamepad_index, float *yaw, float *pitch) return TRUE; } +#ifdef HALO_ANDROID +/* the touch controls moved the player or the aim (halo_linux_touch_aiming) */ +static void touch_used(void) +{ + pthread_mutex_lock(&mouse_lock); + touch_aimed_ms = SDL_GetTicks(); + pthread_mutex_unlock(&mouse_lock); +} +#endif + +/* the touch controls' stick as the player's movement (input_abstraction.c's +keyboard_controls_update), forward and strafe -1..1, whatever the +profile's sticks do; nonzero while it is pushed. Nothing but on Android. */ +int halo_linux_touch_move(short controller_index, float *forward, float *strafe) +{ + *forward = 0.0f; + *strafe = 0.0f; + if (controller_index != 0) + return FALSE; +#ifdef HALO_ANDROID + /* (port: not in VR, where the touch controls are off) */ + if (halo_vr_active() || !touch_input_move(forward, strafe)) + return FALSE; + touch_used(); + return TRUE; +#else + return FALSE; +#endif +} + +/* the touch controls' swipe and gyroscope since the last call, in radians at +the mouse's rate per pixel (the view applies its own sensitivity), each +apart: player_control.c inverts the swipe as the profile inverts the stick, +not the gyroscope, which turns as the phone does, and makes them the +stick's rate before the magnetism. Nothing but on Android. */ +int halo_linux_touch_look(short gamepad_index, float *yaw, float *pitch, float *gyro_yaw, float *gyro_pitch) +{ + *yaw = 0.0f; + *pitch = 0.0f; + *gyro_yaw = 0.0f; + *gyro_pitch = 0.0f; + if (gamepad_index != 0) + return FALSE; +#ifdef HALO_ANDROID + /* (port: not in VR, where the touch controls are off) */ + if (halo_vr_active()) + return FALSE; + touch_input_look(0.0022f, yaw, pitch, gyro_yaw, gyro_pitch); + if (*yaw == 0.0f && *pitch == 0.0f && *gyro_yaw == 0.0f && *gyro_pitch == 0.0f) + return FALSE; + touch_used(); + return TRUE; +#else + return FALSE; +#endif +} + +/* whether port 0's player aims with the touch controls without their aim +assist (input.touch_aim_assist false): no magnetism, as for the mouse +(player_control.c), until a stick moves the aim again */ +int halo_linux_touch_aiming(short gamepad_index) +{ +#ifdef HALO_ANDROID + int aiming; + + if (gamepad_index != 0 || touch_input_aim_assist()) + return FALSE; + pthread_mutex_lock(&mouse_lock); + aiming = touch_aimed_ms != 0 && touch_aimed_ms >= stick_aimed_ms; + pthread_mutex_unlock(&mouse_lock); + return aiming; +#else + (void)gamepad_index; + return FALSE; +#endif +} + /* whether the player on the gamepad aims with the mouse (it moved after the right stick last did) and input.mouse_aim_assist is off: then the view's magnetism leaves them be (player_control.c); the bullets' autoaim stays */ @@ -232,10 +315,16 @@ void platform_text_typing(int typing) text_typing_update(); } -void platform_text_field(int typing) +void platform_text_field(int typing, int password) { text_typing_field = typing != 0; text_typing_update(); +#ifndef HALO_ANDROID + /* (with no keyboard: Steam's on-screen one, sdl_platform.c) */ + platform_screen_keyboard(text_typing_field, typing && password); +#else + (void)password; +#endif } static void typing_gamepad(const struct platform_input_state *input, XINPUT_GAMEPAD *pad) @@ -300,18 +389,21 @@ static void keyboard_gamepad(const struct platform_input_state *input, XINPUT_GA pad->bAnalogButtons[XINPUT_GAMEPAD_Y] |= analog(k[SDL_SCANCODE_TAB]); } -/* the keys held when the game and the menus switch count as up until let go -of: the escape that opens the pause menu does not also back out of it, nor -the one that closes it pause the game again */ +/* the keys held when the game and the menus switch, or typing or the +console starts or ends, count as up until let go of: the escape that opens +the pause menu does not also back out of it, nor the one that closes it (or +the console) pause the game again, nor the Enter that ends typing press the +next screen's A */ static void keys_held_over_switch(struct platform_input_state *input) { static unsigned char held[SDL_SCANCODE_COUNT]; - static int menus = -1; + static int context = -1; + int next_context = (input->menus != FALSE) | (text_typing ? 2 : 0) | (console_is_active() ? 4 : 0); int scancode; - if (menus != (input->menus != FALSE)) + if (context != next_context) { - menus = input->menus != FALSE; + context = next_context; memcpy(held, input->keys, sizeof(held)); } for (scancode = 0; scancode < SDL_SCANCODE_COUNT; scancode++) @@ -332,7 +424,8 @@ static const char *const binding_settings[NUMBER_OF_HALO_KEYBOARD_ACTIONS] = "controls.move_forward", "controls.move_backward", "controls.strafe_left", "controls.strafe_right", "controls.jump", "controls.crouch", "controls.fire", "controls.throw_grenade", "controls.melee", "controls.reload", "controls.zoom", "controls.switch_weapon", "controls.switch_grenade", "controls.action", - "controls.flashlight", "controls.scoreboard", "controls.pause", + "controls.flashlight", "controls.scoreboard", "controls.pause", "controls.screenshot", + "controls.push_to_talk", }; static const struct @@ -461,9 +554,8 @@ static BOOL input_held(const struct platform_input_state *input, int code) return wheel && wheel_direction == (code == INPUT_WHEEL_UP ? 1 : -1); } -/* in the game: the actions held, and the controller's Start and Back for -the pause menu and the scoreboard */ -static void keyboard_controls(const struct platform_input_state *input, XINPUT_GAMEPAD *pad) +/* Shared binding lookup for gameplay and Screenshot, including in menus. */ +static unsigned long keyboard_bound_actions(const struct platform_input_state *input) { unsigned long held = 0; int action, slot; @@ -477,6 +569,23 @@ static void keyboard_controls(const struct platform_input_state *input, XINPUT_G held |= 1UL << action; } } + return held; +} + +/* One capture per press, regardless of how long the binding is held. */ +static void keyboard_screenshot(unsigned long held) +{ + static BOOL was_down; + BOOL down = (held & (1UL << HALO_KEYBOARD_SCREENSHOT)) != 0; + + if (down && !was_down) + platform_screenshot_request(); + was_down = down; +} + +/* in the game: the actions held, and Start/Back for pause/scores */ +static void keyboard_controls(unsigned long held, XINPUT_GAMEPAD *pad) +{ if (held & (1UL << HALO_KEYBOARD_PAUSE)) pad->wButtons |= XINPUT_GAMEPAD_START; if (held & (1UL << HALO_KEYBOARD_SCOREBOARD)) @@ -489,6 +598,22 @@ unsigned long halo_keyboard_actions(short controller_index) return controller_index == 0 ? keyboard_actions_held : 0; } +int halo_push_to_talk_held(void) +{ + struct platform_input_state input; + int slot; + + /* (the window losing the focus lets every key go: sdl_platform.c) */ + bindings_read(); + platform_input_read(&input, FALSE); + for (slot = 0; slot < MAXIMUM_BINDINGS; slot++) + { + if (input_held(&input, bindings[HALO_KEYBOARD_PUSH_TO_TALK][slot])) + return 1; + } + return 0; +} + /* A scroll of the wheel switches weapons once: it holds Y for WHEEL_PRESS_MS once the wheel has turned a notch, and the scroll lasts until the wheel has been still for WHEEL_SCROLL_GAP_MS. One notch often arrives as several events @@ -983,6 +1108,10 @@ static void vr_gamepad_state(XINPUT_GAMEPAD *pad) } #endif +/* reads a controller's state; for port 0 the keyboard, mouse, debug input +and touchscreen are merged into the first gamepad's; runs on the game's main +thread (touch_input_gamepad relies on it); returns ERROR_SUCCESS or +ERROR_DEVICE_NOT_CONNECTED for an unknown port */ DWORD WINAPI XInputGetState(HANDLE device, PXINPUT_STATE state) { int port = controller_port(device); @@ -997,18 +1126,23 @@ DWORD WINAPI XInputGetState(HANDLE device, PXINPUT_STATE state) if (port == 0) { struct platform_input_state input; + unsigned long held; + BOOL console_active; platform_input_read(&input, TRUE); mouse_poll(&input); wheel_update(); keyboard_actions_held = 0; keys_held_over_switch(&input); - if (!console_is_active()) + console_active = console_is_active(); + held = console_active ? 0 : keyboard_bound_actions(&input); + keyboard_screenshot(held); + if (!console_active) { if (input.menus) keyboard_gamepad(&input, &state->Gamepad); else - keyboard_controls(&input, &state->Gamepad); + keyboard_controls(held, &state->Gamepad); } if (port_gamepad(gamepads, count, 0)) sdl_gamepad_state(gamepads[0], &state->Gamepad); @@ -1016,6 +1150,17 @@ DWORD WINAPI XInputGetState(HANDLE device, PXINPUT_STATE state) vr_gamepad_state(&state->Gamepad); #endif test_input_gamepad(&state->Gamepad); +#ifdef HALO_ANDROID + /* port: in VR the touchscreen's controls neither show (the overlay + stays hidden until touch_input_controls reports the scene) nor act */ + if (!halo_vr_active()) +#endif + { + touch_input_gamepad(&state->Gamepad); +#ifdef HALO_ANDROID + touch_input_controls(&state->Gamepad, input.menus); +#endif + } if (abs(state->Gamepad.sThumbRX) > STICK_AIMING_DEFLECTION || abs(state->Gamepad.sThumbRY) > STICK_AIMING_DEFLECTION) { @@ -1060,6 +1205,9 @@ DWORD WINAPI XInputSetState(HANDLE device, PXINPUT_FEEDBACK feedback) host_xr_haptic(HALO_XR_HAND_COUNT - 1 - weapon, feedback->Rumble.wLeftMotorSpeed / 65535.0f, 0.1f); host_xr_haptic(weapon, feedback->Rumble.wRightMotorSpeed / 65535.0f, 0.1f); } + /* the phone vibrates for the touch controls' player */ + else if (port == 0) + touch_input_rumble(feedback->Rumble.wLeftMotorSpeed, feedback->Rumble.wRightMotorSpeed); #endif count = sdl_gamepads(gamepads); if (port_gamepad(gamepads, count, port)) diff --git a/port/linux/src/xiso.c b/port/linux/src/xiso.c index 066b4300..c9b08adb 100644 --- a/port/linux/src/xiso.c +++ b/port/linux/src/xiso.c @@ -76,6 +76,9 @@ includes software developed by in . #ifndef O_CLOEXEC #define O_CLOEXEC 0 #endif +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif enum { @@ -110,6 +113,8 @@ struct xiso_image int error_size; }; +static int name_is_plain(const char *name); + static unsigned long read_u32(const unsigned char *bytes) { return (unsigned long)bytes[0] | (unsigned long)bytes[1] << 8 | (unsigned long)bytes[2] << 16 | @@ -224,9 +229,9 @@ static void walk_directory(struct directory_walk *walk, unsigned long offset, in file->name[name_length] = 0; file->sector = read_u32(entry + 4); file->size = read_u32(entry + 8); - /* (as extract-xiso refuses them: no name may leave the folder) */ - if (strcmp(file->name, ".") && strcmp(file->name, "..") && !strchr(file->name, '/') && - !strchr(file->name, '\\')) + /* (as extract-xiso refuses them: no name may leave the folder, and + none but a plain file name is written) */ + if (name_is_plain(file->name)) { walk->entry_count++; } @@ -235,6 +240,25 @@ static void walk_directory(struct directory_walk *walk, unsigned long offset, in walk_directory(walk, right, depth + 1); } +/* whether a name is one to write in the maps folder: no path, no "." or +"..", and only printable ASCII that every desktop file system takes as a +plain name (a disc's maps folder holds .map files and loading.tga) */ +static int name_is_plain(const char *name) +{ + const char *at; + + if (!*name || !strcmp(name, ".") || !strcmp(name, "..")) + return 0; + for (at = name; *at; at++) + { + unsigned char c = (unsigned char)*at; + + if (c < 0x20 || c > 0x7e || strchr("/\\:*?\"<>|", c)) + return 0; + } + return 1; +} + static int names_match(const char *a, const char *b) { for (; *a && *b; a++, b++) @@ -254,7 +278,8 @@ static int copy_file(struct xiso_image *image, const struct xiso_file *file, con { unsigned long long offset = image->partition + (unsigned long long)file->sector * SECTOR_SIZE; unsigned long remaining = file->size; - int output = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + /* (not through a link left in the folder) */ + int output = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, 0644); if (output < 0) return fail(image, "Could not write %s.", path); diff --git a/port/linux/tests/touch_menu_test.c b/port/linux/tests/touch_menu_test.c new file mode 100644 index 00000000..9c952d53 --- /dev/null +++ b/port/linux/tests/touch_menu_test.c @@ -0,0 +1,1190 @@ +/* +TOUCH_MENU_TEST.C + +Tests of the menus' touch gestures (port/linux/src/touch_menu.c). Built and +run by tools/test_touch_menu.py; exits nonzero on a failure. +*/ + +#include "../src/touch_menu.h" + +#include + +static int failures; + +#define CHECK(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + failures++; \ + } \ + } while (0) + +/* 3 pixels a dp, as on the S23: slop 12 dp, step 40 dp; 2 steps a read, to +test the limit (the game uses 1) */ +static void setup(struct touch_menu *menu) +{ + struct touch_menu_settings settings = { 36.0f, 120.0f, 2, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + + touch_menu_init(menu, &settings); +} + +/* the S23's system gesture insets, as dumpsys window reports them, on its +2340x1080 window: 90 px at the sides, 40 above (the status bar), 96 below */ +static void setup_zones(struct touch_menu *menu) +{ + struct touch_menu_settings settings = { 36.0f, 120.0f, 2, 90.0f, 40.0f, 90.0f, 96.0f, 2340.0f, 1080.0f }; + + touch_menu_init(menu, &settings); +} + +/* a finger from (x0, y0) to (x1, y1) in 12 samples, lifted at the end */ +static void swipe(struct touch_menu *menu, unsigned long long finger, float x0, float y0, float x1, float y1) +{ + int i; + + touch_menu_down(menu, finger, x0, y0); + for (i = 1; i <= 12; i++) + touch_menu_move(menu, finger, x0 + (x1 - x0) * i / 12.0f, y0 + (y1 - y0) * i / 12.0f); + touch_menu_up(menu, finger, x1, y1); +} + +/* first, so that a unit that lost its settings (a step of 0 never ends its +loops) fails here and not by hanging a later test */ +static void test_init_stores_the_settings(void) +{ + struct touch_menu menu; + + setup(&menu); + CHECK(menu.settings.slop == 36.0f); + CHECK(menu.settings.step == 120.0f); + CHECK(menu.settings.steps_per_read == 2); + CHECK(menu.settings.edge_left == 0.0f && menu.settings.edge_top == 0.0f); + CHECK(menu.settings.edge_right == 0.0f && menu.settings.edge_bottom == 0.0f); + CHECK(menu.settings.width == 0.0f && menu.settings.height == 0.0f); + setup_zones(&menu); + CHECK(menu.settings.edge_left == 90.0f); + CHECK(menu.settings.edge_top == 40.0f); + CHECK(menu.settings.edge_right == 90.0f); + CHECK(menu.settings.edge_bottom == 96.0f); + CHECK(menu.settings.width == 2340.0f); + CHECK(menu.settings.height == 1080.0f); +} + +static void test_tap_clicks_where_the_finger_went_down(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 7, 500.0f, 300.0f); + touch_menu_move(&menu, 7, 510.0f, 305.0f); + touch_menu_up(&menu, 7, 512.0f, 306.0f); + touch_menu_read(&menu, &out); + CHECK(out.moved == 1); + CHECK(out.clicks == 1); + CHECK(out.x == 500.0f && out.y == 300.0f); + CHECK(out.click_x == 500.0f && out.click_y == 300.0f); + CHECK(out.wheel_steps == 0); +} + +static void test_read_clears_the_tap(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 10.0f, 10.0f); + touch_menu_up(&menu, 1, 10.0f, 10.0f); + touch_menu_read(&menu, &out); + touch_menu_read(&menu, &out); + CHECK(out.moved == 0); + CHECK(out.clicks == 0); +} + +static void test_nothing_until_the_finger_lifts(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 10.0f, 10.0f); + touch_menu_read(&menu, &out); + CHECK(out.moved == 0); + CHECK(out.clicks == 0); +} + +static void test_a_tap_may_move_the_whole_slop(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 100.0f, 100.0f); + touch_menu_up(&menu, 1, 136.0f, 100.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); +} + +static void test_two_taps_between_reads(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 10.0f, 10.0f); + touch_menu_up(&menu, 1, 10.0f, 10.0f); + touch_menu_down(&menu, 2, 70.0f, 80.0f); + touch_menu_up(&menu, 2, 70.0f, 80.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 2); + CHECK(out.click_x == 70.0f && out.click_y == 80.0f); +} + +static void test_drag_down_scrolls_back_without_clicking(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 100.0f); + /* past the slop (counted from 136), then 250 further: 2 steps */ + touch_menu_move(&menu, 1, 500.0f, 140.0f); + touch_menu_move(&menu, 1, 500.0f, 390.0f); + touch_menu_up(&menu, 1, 500.0f, 390.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 2); + CHECK(out.clicks == 0); + CHECK(out.moved == 0); +} + +static void test_drag_up_scrolls_forward(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 800.0f); + touch_menu_move(&menu, 1, 500.0f, 760.0f); + touch_menu_move(&menu, 1, 500.0f, 630.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -1); +} + +static void test_horizontal_drag_uses_the_x_axis(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 1500.0f, 500.0f); + /* mostly left: forward */ + touch_menu_move(&menu, 1, 1460.0f, 510.0f); + touch_menu_move(&menu, 1, 1330.0f, 540.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -1); +} + +static void test_equal_dx_dy_scrolls_vertically(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 500.0f); + touch_menu_move(&menu, 1, 530.0f, 530.0f); + touch_menu_move(&menu, 1, 530.0f, 680.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 1); +} + +static void test_the_axis_stays_after_the_start(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 500.0f); + touch_menu_move(&menu, 1, 500.0f, 540.0f); + touch_menu_move(&menu, 1, 900.0f, 540.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); +} + +static void test_a_fast_drag_counts_from_the_slop(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + int total = 0, index; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + /* one sample far past the slop, as a flick or a slow frame gives */ + touch_menu_move(&menu, 1, 500.0f, 600.0f); + touch_menu_up(&menu, 1, 500.0f, 600.0f); + for (index = 0; index < 5; index++) + { + touch_menu_read(&menu, &out); + total += out.wheel_steps; + } + /* (600 - 36) / 120 = 4.7 */ + CHECK(total == 4); + CHECK(out.clicks == 0); +} + +static void test_steps_come_out_gradually(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + touch_menu_move(&menu, 1, 500.0f, 40.0f); + /* five steps at once */ + touch_menu_move(&menu, 1, 500.0f, 640.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 2); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 2); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 1); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); +} + +static void test_forward_steps_come_out_gradually(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 1000.0f); + touch_menu_move(&menu, 1, 500.0f, 960.0f); + touch_menu_move(&menu, 1, 500.0f, 360.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -1); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); +} + +static void test_part_of_a_step_is_kept(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + touch_menu_move(&menu, 1, 500.0f, 40.0f); + touch_menu_move(&menu, 1, 500.0f, 100.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); + touch_menu_move(&menu, 1, 500.0f, 160.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 1); +} + +static void test_lift_far_away_is_no_tap(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 100.0f, 100.0f); + touch_menu_up(&menu, 1, 100.0f, 600.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.moved == 0); + /* the lift's own move scrolls: (600 - 100 - 36) / 120 = 3.9 steps, 2 a read */ + CHECK(out.wheel_steps == 2); +} + +static void test_a_scroll_keeps_the_last_tap_point_without_moving(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 10.0f, 20.0f); + touch_menu_up(&menu, 1, 10.0f, 20.0f); + touch_menu_read(&menu, &out); + touch_menu_down(&menu, 2, 500.0f, 0.0f); + touch_menu_move(&menu, 2, 500.0f, 40.0f); + touch_menu_move(&menu, 2, 500.0f, 200.0f); + touch_menu_up(&menu, 2, 500.0f, 200.0f); + touch_menu_read(&menu, &out); + CHECK(out.moved == 0); + CHECK(out.x == 10.0f && out.y == 20.0f); +} + +static void test_a_new_finger_stops_pending_steps(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + touch_menu_move(&menu, 1, 500.0f, 40.0f); + touch_menu_move(&menu, 1, 500.0f, 640.0f); + touch_menu_up(&menu, 1, 500.0f, 640.0f); + /* the tap hits the list as the finger saw it */ + touch_menu_down(&menu, 2, 300.0f, 300.0f); + touch_menu_up(&menu, 2, 300.0f, 300.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.wheel_steps == 0); +} + +static void test_second_finger_is_ignored(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 100.0f, 100.0f); + touch_menu_down(&menu, 2, 900.0f, 900.0f); + touch_menu_move(&menu, 2, 900.0f, 100.0f); + touch_menu_up(&menu, 2, 900.0f, 100.0f); + touch_menu_up(&menu, 1, 100.0f, 100.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.x == 100.0f && out.y == 100.0f); + CHECK(out.wheel_steps == 0); +} + +static void test_lift_of_another_finger_is_ignored(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 100.0f, 100.0f); + touch_menu_up(&menu, 9, 100.0f, 100.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0 && out.moved == 0); + touch_menu_up(&menu, 1, 100.0f, 100.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); +} + +static void test_move_and_lift_without_down_do_nothing(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_move(&menu, 1, 100.0f, 900.0f); + touch_menu_up(&menu, 1, 100.0f, 900.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0 && out.moved == 0 && out.wheel_steps == 0); +} + +static void test_cancel_drops_the_gesture(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + touch_menu_move(&menu, 1, 500.0f, 40.0f); + touch_menu_move(&menu, 1, 500.0f, 400.0f); + touch_menu_cancel(&menu); + touch_menu_up(&menu, 1, 500.0f, 400.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); + CHECK(out.clicks == 0); + /* and the next finger works */ + touch_menu_down(&menu, 3, 20.0f, 20.0f); + touch_menu_up(&menu, 3, 20.0f, 20.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); +} + +static void test_reset_clears_everything(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 10.0f, 10.0f); + touch_menu_up(&menu, 1, 10.0f, 10.0f); + touch_menu_reset(&menu); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.moved == 0); +} + +static void test_reset_during_a_touch_ignores_its_lift(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 100.0f, 100.0f); + touch_menu_reset(&menu); + touch_menu_move(&menu, 1, 100.0f, 500.0f); + touch_menu_up(&menu, 1, 100.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0 && out.wheel_steps == 0); +} + +/* a step is exactly the step setting: 120 pixels, not 119.9 or 120.1 */ +static void test_a_step_is_exactly_the_step_setting(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + touch_menu_move(&menu, 1, 500.0f, 40.0f); + touch_menu_move(&menu, 1, 500.0f, 156.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 1); + touch_menu_cancel(&menu); + touch_menu_down(&menu, 2, 500.0f, 1000.0f); + touch_menu_move(&menu, 2, 500.0f, 960.0f); + touch_menu_move(&menu, 2, 500.0f, 844.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -1); +} + +static void test_horizontal_drag_right_scrolls_back(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 100.0f, 500.0f); + /* no vertical movement at all: the distance is all in x */ + touch_menu_move(&menu, 1, 140.0f, 500.0f); + touch_menu_move(&menu, 1, 180.0f, 500.0f); + touch_menu_move(&menu, 1, 245.0f, 500.0f); + touch_menu_read(&menu, &out); + /* 245 - 136 = 109 of the 120 */ + CHECK(out.wheel_steps == 0); + touch_menu_move(&menu, 1, 257.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 1); + CHECK(out.clicks == 0); +} + +static void test_a_new_finger_starts_a_fresh_step(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + touch_menu_move(&menu, 1, 500.0f, 40.0f); + touch_menu_move(&menu, 1, 500.0f, 100.0f); + touch_menu_up(&menu, 1, 500.0f, 100.0f); + /* the same 64 pixels again: 128 together would be a step */ + touch_menu_down(&menu, 2, 500.0f, 0.0f); + touch_menu_move(&menu, 2, 500.0f, 40.0f); + touch_menu_move(&menu, 2, 500.0f, 100.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); +} + +static void test_a_lifted_finger_cannot_move_again(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 100.0f, 100.0f); + touch_menu_up(&menu, 1, 100.0f, 100.0f); + touch_menu_read(&menu, &out); + /* the same id, no finger down */ + touch_menu_move(&menu, 1, 100.0f, 900.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); +} + +static void test_reset_keeps_the_settings(void) +{ + struct touch_menu menu; + + setup(&menu); + touch_menu_reset(&menu); + CHECK(menu.settings.slop == 36.0f); + CHECK(menu.settings.step == 120.0f); + CHECK(menu.settings.steps_per_read == 2); +} + +static void test_cancel_keeps_a_finished_tap(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 30.0f, 40.0f); + touch_menu_up(&menu, 1, 30.0f, 40.0f); + touch_menu_cancel(&menu); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.moved == 1); + CHECK(out.click_x == 30.0f && out.click_y == 40.0f); +} + +/* the game's value: one step a read */ +static void test_one_step_a_read(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + struct touch_menu_settings settings = { 36.0f, 120.0f, 1, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + int index; + + touch_menu_init(&menu, &settings); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + touch_menu_move(&menu, 1, 500.0f, 40.0f); + touch_menu_move(&menu, 1, 500.0f, 640.0f); + for (index = 0; index < 5; index++) + { + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 1); + } + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); + touch_menu_cancel(&menu); + touch_menu_down(&menu, 2, 500.0f, 1000.0f); + touch_menu_move(&menu, 2, 500.0f, 960.0f); + touch_menu_move(&menu, 2, 500.0f, 360.0f); + for (index = 0; index < 5; index++) + { + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -1); + } + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); +} + +/* the debug view (debug.touch_targets) shows where a drag began, which no +tap or hover reports */ +static void test_a_drag_reports_where_the_finger_went_down(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 3, 400.0f, 200.0f); + touch_menu_move(&menu, 3, 400.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.downs == 1); + CHECK(out.down_x == 400.0f && out.down_y == 200.0f); + CHECK(out.clicks == 0); + touch_menu_read(&menu, &out); + CHECK(out.downs == 0); +} + +static void test_a_tap_reports_its_down(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 50.0f, 60.0f); + touch_menu_up(&menu, 1, 50.0f, 60.0f); + touch_menu_read(&menu, &out); + CHECK(out.downs == 1); + CHECK(out.down_x == 50.0f && out.down_y == 60.0f); +} + +static void test_two_downs_between_reads_count_twice(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 50.0f, 60.0f); + touch_menu_up(&menu, 1, 50.0f, 60.0f); + touch_menu_down(&menu, 2, 70.0f, 80.0f); + touch_menu_read(&menu, &out); + CHECK(out.downs == 2); + CHECK(out.down_x == 70.0f && out.down_y == 80.0f); +} + +/* an ignored second finger is not a down */ +static void test_a_second_finger_is_no_down(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 50.0f, 60.0f); + touch_menu_down(&menu, 2, 70.0f, 80.0f); + touch_menu_read(&menu, &out); + CHECK(out.downs == 1); + CHECK(out.down_x == 50.0f && out.down_y == 60.0f); +} + +static void test_reset_and_cancel_drop_the_unread_downs(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 50.0f, 60.0f); + touch_menu_reset(&menu); + touch_menu_read(&menu, &out); + CHECK(out.downs == 0); + touch_menu_down(&menu, 1, 50.0f, 60.0f); + touch_menu_cancel(&menu); + touch_menu_read(&menu, &out); + CHECK(out.downs == 0); +} + +static void test_a_tap_in_the_left_zone_is_ignored(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 10.0f, 500.0f); + touch_menu_up(&menu, 1, 10.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.moved == 0); + CHECK(out.downs == 0); + CHECK(out.wheel_steps == 0); +} + +static void test_the_left_zone_ends_at_the_inset(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 89.0f, 500.0f); + touch_menu_up(&menu, 1, 89.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.downs == 0); + touch_menu_down(&menu, 1, 90.0f, 500.0f); + touch_menu_up(&menu, 1, 90.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.downs == 1); + CHECK(out.click_x == 90.0f); +} + +static void test_the_right_zone_begins_at_width_minus_the_inset(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 2250.0f, 500.0f); + touch_menu_up(&menu, 1, 2250.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.downs == 0); + touch_menu_down(&menu, 1, 2249.0f, 500.0f); + touch_menu_up(&menu, 1, 2249.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.downs == 1); + CHECK(out.click_x == 2249.0f); +} + +/* the reported bug: the first edge swipe in immersive mode scrolled the list */ +static void test_an_edge_swipe_does_not_scroll_or_click(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + int i, steps = 0; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 10.0f, 500.0f); + for (i = 1; i <= 14; i++) + { + touch_menu_move(&menu, 1, 10.0f + 50.0f * i, 500.0f); + touch_menu_read(&menu, &out); + steps += out.wheel_steps; + CHECK(out.clicks == 0); + } + touch_menu_up(&menu, 1, 710.0f, 500.0f); + touch_menu_read(&menu, &out); + steps += out.wheel_steps; + CHECK(steps == 0); + CHECK(out.clicks == 0); + CHECK(out.moved == 0); + CHECK(out.downs == 0); +} + +static void test_an_ignored_finger_does_not_stick(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + swipe(&menu, 1, 10.0f, 500.0f, 700.0f, 500.0f); + touch_menu_read(&menu, &out); + touch_menu_down(&menu, 1, 1170.0f, 540.0f); + touch_menu_up(&menu, 1, 1170.0f, 540.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.downs == 1); +} + +static void test_an_edge_finger_does_not_block_a_second_finger(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 10.0f, 500.0f); + touch_menu_down(&menu, 2, 1170.0f, 540.0f); + touch_menu_up(&menu, 2, 1170.0f, 540.0f); + touch_menu_up(&menu, 1, 10.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.downs == 1); + CHECK(out.click_x == 1170.0f); +} + +/* an edge finger must not stop the steps of a scroll by another finger */ +static void test_an_edge_finger_leaves_another_fingers_scroll_alone(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 2, 1170.0f, 800.0f); + touch_menu_move(&menu, 2, 1170.0f, 500.0f); + touch_menu_down(&menu, 1, 10.0f, 500.0f); + touch_menu_move(&menu, 1, 400.0f, 500.0f); + touch_menu_up(&menu, 1, 400.0f, 500.0f); + touch_menu_up(&menu, 2, 1170.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + CHECK(out.clicks == 0); +} + +/* a finger in a zone is no finger on the screen: the list goes on scrolling */ +static void test_an_edge_finger_keeps_the_pending_steps(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + swipe(&menu, 1, 1170.0f, 800.0f, 1170.0f, 100.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + touch_menu_down(&menu, 2, 10.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); +} + +static void test_a_tap_in_the_bottom_zone_clicks(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 1170.0f, 1050.0f); + touch_menu_up(&menu, 1, 1170.0f, 1050.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.downs == 1); + CHECK(out.click_x == 1170.0f && out.click_y == 1050.0f); +} + +static void test_a_swipe_up_from_the_bottom_zone_does_nothing(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + int i, steps = 0; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 1170.0f, 1060.0f); + for (i = 1; i <= 12; i++) + { + touch_menu_move(&menu, 1, 1170.0f, 1060.0f - 50.0f * i); + touch_menu_read(&menu, &out); + steps += out.wheel_steps; + } + touch_menu_up(&menu, 1, 1170.0f, 460.0f); + touch_menu_read(&menu, &out); + steps += out.wheel_steps; + CHECK(steps == 0); + CHECK(out.clicks == 0); + CHECK(out.moved == 0); +} + +static void test_a_swipe_down_from_the_top_zone_does_nothing(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + int i, steps = 0; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 1170.0f, 10.0f); + for (i = 1; i <= 12; i++) + { + touch_menu_move(&menu, 1, 1170.0f, 10.0f + 50.0f * i); + touch_menu_read(&menu, &out); + steps += out.wheel_steps; + } + touch_menu_up(&menu, 1, 1170.0f, 610.0f); + touch_menu_read(&menu, &out); + steps += out.wheel_steps; + CHECK(steps == 0); + CHECK(out.clicks == 0); + CHECK(out.moved == 0); +} + +/* a sample past the slop voids the tap for good: coming back to the start +must not bring it back */ +static void test_a_no_scroll_finger_stays_void_after_returning(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 1170.0f, 1050.0f); + touch_menu_move(&menu, 1, 1170.0f, 900.0f); + touch_menu_up(&menu, 1, 1170.0f, 1050.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.moved == 0); +} + +/* a no-scroll finger may still wobble inside the slop and tap */ +static void test_a_no_scroll_finger_taps_within_the_slop(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 1170.0f, 1050.0f); + touch_menu_move(&menu, 1, 1190.0f, 1040.0f); + touch_menu_up(&menu, 1, 1190.0f, 1040.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.click_x == 1170.0f && out.click_y == 1050.0f); +} + +static void test_just_outside_the_zones_scrolls_as_usual(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 1170.0f, 900.0f); + touch_menu_move(&menu, 1, 1170.0f, 840.0f); + touch_menu_move(&menu, 1, 1170.0f, 300.0f); + touch_menu_up(&menu, 1, 1170.0f, 300.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + CHECK(out.clicks == 0); +} + +static void test_the_bottom_and_top_zones_end_at_their_insets(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + /* 1080 - 96 = 984 is the first y of the bottom zone */ + setup_zones(&menu); + swipe(&menu, 1, 1170.0f, 984.0f, 1170.0f, 300.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); + swipe(&menu, 1, 1170.0f, 983.0f, 1170.0f, 300.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + /* 39 is the last y of the top zone */ + setup_zones(&menu); + swipe(&menu, 1, 1170.0f, 39.0f, 1170.0f, 700.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); + swipe(&menu, 1, 1170.0f, 40.0f, 1170.0f, 700.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 2); +} + +/* a no-scroll finger must not leave its state to the next finger */ +static void test_a_scroll_after_a_no_scroll_finger_still_scrolls(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + swipe(&menu, 1, 1170.0f, 1060.0f, 1170.0f, 700.0f); + touch_menu_read(&menu, &out); + swipe(&menu, 1, 1170.0f, 800.0f, 1170.0f, 300.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + CHECK(out.clicks == 0); +} + +/* the side zones win over the bottom: a corner touch is ignored */ +static void test_a_corner_touch_is_ignored(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 10.0f, 1060.0f); + touch_menu_up(&menu, 1, 10.0f, 1060.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.downs == 0); + touch_menu_down(&menu, 1, 2300.0f, 10.0f); + touch_menu_up(&menu, 1, 2300.0f, 10.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.downs == 0); +} + +/* no insets (button navigation, desktop, unreadable): the edges count */ +static void test_without_zones_the_edges_work(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup(&menu); + touch_menu_down(&menu, 1, 0.0f, 500.0f); + touch_menu_up(&menu, 1, 0.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + touch_menu_down(&menu, 1, 500.0f, 0.0f); + touch_menu_up(&menu, 1, 500.0f, 0.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + swipe(&menu, 1, 0.0f, 500.0f, 500.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 2); + CHECK(out.clicks == 0); +} + +/* insets without a window size: the right and bottom zones do not exist */ +static void test_without_a_size_only_the_left_and_top_zones_exist(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + struct touch_menu_settings settings = { 36.0f, 120.0f, 2, 90.0f, 40.0f, 90.0f, 96.0f, 0.0f, 0.0f }; + + touch_menu_init(&menu, &settings); + touch_menu_down(&menu, 1, 10.0f, 500.0f); + touch_menu_up(&menu, 1, 10.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + swipe(&menu, 1, 5000.0f, 2000.0f, 5000.0f, 1000.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); +} + +/* the S23's insets are symmetric, so only unequal ones tell the two sides +(and the top and the bottom) apart */ +static void test_each_side_has_its_own_inset(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + struct touch_menu_settings settings = { 36.0f, 120.0f, 2, 30.0f, 20.0f, 200.0f, 150.0f, 2340.0f, 1080.0f }; + + touch_menu_init(&menu, &settings); + touch_menu_down(&menu, 1, 29.0f, 500.0f); + touch_menu_up(&menu, 1, 29.0f, 500.0f); + touch_menu_down(&menu, 1, 2140.0f, 500.0f); + touch_menu_up(&menu, 1, 2140.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + touch_menu_down(&menu, 1, 30.0f, 500.0f); + touch_menu_up(&menu, 1, 30.0f, 500.0f); + touch_menu_down(&menu, 1, 2139.0f, 500.0f); + touch_menu_up(&menu, 1, 2139.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 2); + /* above 1080 - 150 = 930 and from 20 down a swipe scrolls; the top + zone is 20 high and the bottom one 150 */ + swipe(&menu, 1, 1170.0f, 929.0f, 1170.0f, 300.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + swipe(&menu, 1, 1170.0f, 930.0f, 1170.0f, 300.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); + swipe(&menu, 1, 1170.0f, 19.0f, 1170.0f, 700.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 0); + swipe(&menu, 1, 1170.0f, 20.0f, 1170.0f, 700.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == 2); +} + +/* a window size without insets: its far edge is not a zone */ +static void test_without_insets_the_far_edges_are_no_zones(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + struct touch_menu_settings settings = { 36.0f, 120.0f, 2, 0.0f, 0.0f, 0.0f, 0.0f, 2340.0f, 1080.0f }; + + touch_menu_init(&menu, &settings); + touch_menu_down(&menu, 1, 2340.0f, 500.0f); + touch_menu_up(&menu, 1, 2340.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.downs == 1); + swipe(&menu, 1, 1170.0f, 1080.0f, 1170.0f, 300.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); +} + +/* the phone rotates after the app started: the zones the host reads at a +down replace the old ones, for the next finger on */ +static void test_zones_changed_between_fingers_apply_at_the_next_down(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + struct touch_menu_settings settings = { 36.0f, 120.0f, 2, 0.0f, 0.0f, 0.0f, 0.0f, 1080.0f, 2340.0f }; + + touch_menu_init(&menu, &settings); + touch_menu_down(&menu, 1, 10.0f, 500.0f); + touch_menu_up(&menu, 1, 10.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + menu.settings.edge_left = 90.0f; + menu.settings.edge_right = 90.0f; + menu.settings.width = 2340.0f; + menu.settings.height = 1080.0f; + touch_menu_down(&menu, 1, 10.0f, 500.0f); + touch_menu_up(&menu, 1, 10.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + CHECK(out.downs == 0); + touch_menu_down(&menu, 1, 2300.0f, 500.0f); + touch_menu_up(&menu, 1, 2300.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 0); + menu.settings.edge_left = 0.0f; + menu.settings.edge_right = 0.0f; + touch_menu_down(&menu, 1, 10.0f, 500.0f); + touch_menu_up(&menu, 1, 10.0f, 500.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); +} + +/* a zone is judged at the down only: a finger already down keeps its kind +when the zones change under it */ +static void test_zones_changed_under_a_finger_do_not_touch_it(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 1170.0f, 540.0f); + menu.settings.edge_top = 600.0f; + menu.settings.edge_left = 2000.0f; + touch_menu_move(&menu, 1, 1170.0f, 100.0f); + touch_menu_up(&menu, 1, 1170.0f, 100.0f); + touch_menu_read(&menu, &out); + CHECK(out.wheel_steps == -2); + CHECK(out.clicks == 0); +} + +static void test_reset_and_cancel_drop_a_zone_finger(void) +{ + struct touch_menu menu; + struct touch_menu_output out; + + setup_zones(&menu); + touch_menu_down(&menu, 1, 10.0f, 500.0f); + touch_menu_reset(&menu); + touch_menu_down(&menu, 2, 1170.0f, 540.0f); + touch_menu_up(&menu, 2, 1170.0f, 540.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + /* a bottom finger that moved on is void; after a cancel the next + finger taps */ + touch_menu_down(&menu, 1, 1170.0f, 1060.0f); + touch_menu_move(&menu, 1, 1170.0f, 800.0f); + touch_menu_cancel(&menu); + touch_menu_down(&menu, 2, 1170.0f, 540.0f); + touch_menu_up(&menu, 2, 1170.0f, 540.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); + CHECK(out.downs == 1); + /* and after a reset */ + touch_menu_down(&menu, 1, 1170.0f, 1060.0f); + touch_menu_move(&menu, 1, 1170.0f, 800.0f); + touch_menu_reset(&menu); + touch_menu_down(&menu, 2, 1170.0f, 540.0f); + touch_menu_up(&menu, 2, 1170.0f, 540.0f); + touch_menu_read(&menu, &out); + CHECK(out.clicks == 1); +} + +int main(void) +{ + test_init_stores_the_settings(); + if (failures) + return 1; + test_tap_clicks_where_the_finger_went_down(); + test_read_clears_the_tap(); + test_nothing_until_the_finger_lifts(); + test_a_tap_may_move_the_whole_slop(); + test_two_taps_between_reads(); + test_drag_down_scrolls_back_without_clicking(); + test_drag_up_scrolls_forward(); + test_horizontal_drag_uses_the_x_axis(); + test_equal_dx_dy_scrolls_vertically(); + test_the_axis_stays_after_the_start(); + test_a_fast_drag_counts_from_the_slop(); + test_steps_come_out_gradually(); + test_forward_steps_come_out_gradually(); + test_part_of_a_step_is_kept(); + test_lift_far_away_is_no_tap(); + test_a_scroll_keeps_the_last_tap_point_without_moving(); + test_a_new_finger_stops_pending_steps(); + test_second_finger_is_ignored(); + test_lift_of_another_finger_is_ignored(); + test_move_and_lift_without_down_do_nothing(); + test_cancel_drops_the_gesture(); + test_reset_clears_everything(); + test_reset_during_a_touch_ignores_its_lift(); + test_a_step_is_exactly_the_step_setting(); + test_horizontal_drag_right_scrolls_back(); + test_a_new_finger_starts_a_fresh_step(); + test_a_lifted_finger_cannot_move_again(); + test_reset_keeps_the_settings(); + test_cancel_keeps_a_finished_tap(); + test_one_step_a_read(); + test_a_drag_reports_where_the_finger_went_down(); + test_a_tap_reports_its_down(); + test_two_downs_between_reads_count_twice(); + test_a_second_finger_is_no_down(); + test_reset_and_cancel_drop_the_unread_downs(); + test_a_tap_in_the_left_zone_is_ignored(); + test_the_left_zone_ends_at_the_inset(); + test_the_right_zone_begins_at_width_minus_the_inset(); + test_an_edge_swipe_does_not_scroll_or_click(); + test_an_ignored_finger_does_not_stick(); + test_an_edge_finger_does_not_block_a_second_finger(); + test_an_edge_finger_leaves_another_fingers_scroll_alone(); + test_an_edge_finger_keeps_the_pending_steps(); + test_a_tap_in_the_bottom_zone_clicks(); + test_a_swipe_up_from_the_bottom_zone_does_nothing(); + test_a_swipe_down_from_the_top_zone_does_nothing(); + test_a_no_scroll_finger_stays_void_after_returning(); + test_a_no_scroll_finger_taps_within_the_slop(); + test_just_outside_the_zones_scrolls_as_usual(); + test_the_bottom_and_top_zones_end_at_their_insets(); + test_a_scroll_after_a_no_scroll_finger_still_scrolls(); + test_a_corner_touch_is_ignored(); + test_without_zones_the_edges_work(); + test_without_a_size_only_the_left_and_top_zones_exist(); + test_each_side_has_its_own_inset(); + test_without_insets_the_far_edges_are_no_zones(); + test_zones_changed_between_fingers_apply_at_the_next_down(); + test_zones_changed_under_a_finger_do_not_touch_it(); + test_reset_and_cancel_drop_a_zone_finger(); + if (failures) + printf("%d failures\n", failures); + else + printf("all tests passed\n"); + return failures != 0; +} diff --git a/port/third_party/opus/AUTHORS b/port/third_party/opus/AUTHORS new file mode 100644 index 00000000..b3d22a20 --- /dev/null +++ b/port/third_party/opus/AUTHORS @@ -0,0 +1,6 @@ +Jean-Marc Valin (jmvalin@jmvalin.ca) +Koen Vos (koenvos74@gmail.com) +Timothy Terriberry (tterribe@xiph.org) +Karsten Vandborg Sorensen (karsten.vandborg.sorensen@skype.net) +Soren Skak Jensen (ssjensen@gn.com) +Gregory Maxwell (greg@xiph.org) diff --git a/port/third_party/opus/COPYING b/port/third_party/opus/COPYING new file mode 100644 index 00000000..75711467 --- /dev/null +++ b/port/third_party/opus/COPYING @@ -0,0 +1,44 @@ +Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic, + Jean-Marc Valin, Timothy B. Terriberry, + CSIRO, Gregory Maxwell, Mark Borgerding, + Erik de Castro Lopo, Mozilla, Amazon + +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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER +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. + +Opus is subject to the royalty-free patent licenses which are +specified at: + +Xiph.Org Foundation: +https://datatracker.ietf.org/ipr/1524/ + +Microsoft Corporation: +https://datatracker.ietf.org/ipr/1914/ + +Broadcom Corporation: +https://datatracker.ietf.org/ipr/1526/ diff --git a/port/third_party/opus/PORT.md b/port/third_party/opus/PORT.md new file mode 100644 index 00000000..81847a9f --- /dev/null +++ b/port/third_party/opus/PORT.md @@ -0,0 +1,11 @@ +# Opus + +[Opus](https://opus-codec.org) 1.5.2 (`opus-1.5.2.tar.gz`, SHA-256 +`65c1d2f78b9f2fb20082c38cbe47c951ad5839345876e46941612ee87f9a7ce1`), for +voice chat (`port/linux/game/network_voice.c`). BSD licence: `COPYING`. + +Only the library is kept: `include/`, `src/`, `celt/` and `silk/` with +`silk/float/` (the floating-point build), without the multistream and +projection API, the demos, the tests and the SIMD and DNN code. `sources.txt` +lists the files the builds compile (`tools/linux_build.py`), as plain C with +`OPUS_BUILD`, `VAR_ARRAYS`, `HAVE_LRINTF` and `HAVE_LRINT`. diff --git a/port/third_party/opus/README b/port/third_party/opus/README new file mode 100644 index 00000000..bcf2376d --- /dev/null +++ b/port/third_party/opus/README @@ -0,0 +1,189 @@ +== Opus audio codec == + +Opus is a codec for interactive speech and audio transmission over the Internet. + + Opus can handle a wide range of interactive audio applications, including +Voice over IP, videoconferencing, in-game chat, and even remote live music +performances. It can scale from low bit-rate narrowband speech to very high +quality stereo music. + + Opus, when coupled with an appropriate container format, is also suitable +for non-realtime stored-file applications such as music distribution, game +soundtracks, portable music players, jukeboxes, and other applications that +have historically used high latency formats such as MP3, AAC, or Vorbis. + + Opus is specified by IETF RFC 6716: + https://tools.ietf.org/html/rfc6716 + + The Opus format and this implementation of it are subject to the royalty- +free patent and copyright licenses specified in the file COPYING. + +This package implements a shared library for encoding and decoding raw Opus +bitstreams. Raw Opus bitstreams should be used over RTP according to + https://tools.ietf.org/html/rfc7587 + +The package also includes a number of test tools used for testing the +correct operation of the library. The bitstreams read/written by these +tools should not be used for Opus file distribution: They include +additional debugging data and cannot support seeking. + +Opus stored in files should use the Ogg encapsulation for Opus which is +described at: + https://tools.ietf.org/html/rfc7845 + +An opus-tools package is available which provides encoding and decoding of +Ogg encapsulated Opus files and includes a number of useful features. + +Opus-tools can be found at: + https://gitlab.xiph.org/xiph/opus-tools.git +or on the main Opus website: + https://opus-codec.org/ + +== Deep Learning and Opus == + +Lossy networks continue to be a challenge for real-time communications. +While the original implementation of Opus provides an excellent packet loss +concealment mechanism, the team has continued to advance the methodology used +to improve audio quality in challenge network environments. + +In Opus 1.5, we added a deep learning based redundancy encoder that enhances +audio in lossy networks by embedding one second of recovery data in the padding +data of each packet. The underlying algorithm behind encoding and decoding the +recovery data is called the deep redundancy (DRED) algorithm. By leveraging +the padding data within the packet, Opus 1.5 is fully backward compatible with +prior revisions of Opus. Please see the README under the "dnn" subdirectory to +understand DRED. + +DRED was developed by a team that Amazon Web Services initially sponsored, +who open-sourced the implementation as well as began the +standardization process at the IETF: + https://datatracker.ietf.org/doc/draft-ietf-mlcodec-opus-extension/ +The license behind Opus or the intellectual property position of Opus does +not change with Opus 1.5. + +== Compiling libopus == + +To build from a distribution tarball, you only need to do the following: + + % ./configure + % make + +To build from the git repository, the following steps are necessary: + +0) Set up a development environment: + +On an Ubuntu or Debian family Linux distribution: + + % sudo apt-get install git autoconf automake libtool gcc make + +On a Fedora/Redhat based Linux: + + % sudo dnf install git autoconf automake libtool gcc make + +Or for older Redhat/Centos Linux releases: + + % sudo yum install git autoconf automake libtool gcc make + +On Apple macOS, install Xcode and brew.sh, then in the Terminal enter: + + % brew install autoconf automake libtool + +1) Clone the repository: + + % git clone https://gitlab.xiph.org/xiph/opus.git + % cd opus + +2) Compiling the source + + % ./autogen.sh + % ./configure + % make + +On x86, it's a good idea to use a -march= option that allows the use of AVX2. + +3) Install the codec libraries (optional) + + % sudo make install + +Once you have compiled the codec, there will be a opus_demo executable +in the top directory. + +Usage: opus_demo [-e] + [options] + opus_demo -d [options] + + +mode: voip | audio | restricted-lowdelay +options: + -e : only runs the encoder (output the bit-stream) + -d : only runs the decoder (reads the bit-stream as input) + -cbr : enable constant bitrate; default: variable bitrate + -cvbr : enable constrained variable bitrate; default: + unconstrained + -bandwidth + : audio bandwidth (from narrowband to fullband); + default: sampling rate + -framesize <2.5|5|10|20|40|60> + : frame size in ms; default: 20 + -max_payload + : maximum payload size in bytes, default: 1024 + -complexity + : complexity, 0 (lowest) ... 10 (highest); default: 10 + -inbandfec : enable SILK inband FEC + -forcemono : force mono encoding, even for stereo input + -dtx : enable SILK DTX + -loss : simulate packet loss, in percent (0-100); default: 0 + +input and output are little-endian signed 16-bit PCM files or opus +bitstreams with simple opus_demo proprietary framing. + +== Testing == + +This package includes a collection of automated unit and system tests +which SHOULD be run after compiling the package especially the first +time it is run on a new platform. + +To run the integrated tests: + + % make check + +There is also collection of standard test vectors which are not +included in this package for size reasons but can be obtained from: +https://opus-codec.org/docs/opus_testvectors-rfc8251.tar.gz + +To run compare the code to these test vectors: + + % curl -OL https://opus-codec.org/docs/opus_testvectors-rfc8251.tar.gz + % tar -zxf opus_testvectors-rfc8251.tar.gz + % ./tests/run_vectors.sh ./ opus_newvectors 48000 + +== Compiling libopus for Windows and alternative build systems == + +See cmake/README.md or meson/README.md. + +== Portability notes == + +This implementation uses floating-point by default but can be compiled to +use only fixed-point arithmetic by setting --enable-fixed-point (if using +autoconf) or by defining the FIXED_POINT macro (if building manually). +The fixed point implementation has somewhat lower audio quality and is +slower on platforms with fast FPUs, it is normally only used in embedded +environments. + +The implementation can be compiled with either a C89 or a C99 compiler. +While it does not rely on any _undefined behavior_ as defined by C89 or +C99, it relies on common _implementation-defined behavior_ for two's +complement architectures: + +o Right shifts of negative values are consistent with two's + complement arithmetic, so that a>>b is equivalent to + floor(a/(2^b)), + +o For conversion to a signed integer of N bits, the value is reduced + modulo 2^N to be within range of the type, + +o The result of integer division of a negative value is truncated + towards zero, and + +o The compiler provides a 64-bit integer type (a C99 requirement + which is supported by most C89 compilers). diff --git a/port/third_party/opus/celt/_kiss_fft_guts.h b/port/third_party/opus/celt/_kiss_fft_guts.h new file mode 100644 index 00000000..17392b3e --- /dev/null +++ b/port/third_party/opus/celt/_kiss_fft_guts.h @@ -0,0 +1,182 @@ +/*Copyright (c) 2003-2004, Mark Borgerding + + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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.*/ + +#ifndef KISS_FFT_GUTS_H +#define KISS_FFT_GUTS_H + +#define MIN(a,b) ((a)<(b) ? (a):(b)) +#define MAX(a,b) ((a)>(b) ? (a):(b)) + +/* kiss_fft.h + defines kiss_fft_scalar as either short or a float type + and defines + typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ +#include "kiss_fft.h" + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ +#ifdef FIXED_POINT +#include "arch.h" + + +#define SAMP_MAX 2147483647 +#define TWID_MAX 32767 +#define TRIG_UPSCALE 1 + +#define SAMP_MIN -SAMP_MAX + + +# define S_MUL(a,b) MULT16_32_Q15(b, a) + +# define C_MUL(m,a,b) \ + do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \ + (m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0) + +# define C_MULC(m,a,b) \ + do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \ + (m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0) + +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r = S_MUL( (c).r , s ) ;\ + (c).i = S_MUL( (c).i , s ) ; }while(0) + +# define DIVSCALAR(x,k) \ + (x) = S_MUL( x, (TWID_MAX-((k)>>1))/(k)+1 ) + +# define C_FIXDIV(c,div) \ + do { DIVSCALAR( (c).r , div); \ + DIVSCALAR( (c).i , div); }while (0) + +#define C_ADD( res, a,b)\ + do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \ + }while(0) +#define C_SUB( res, a,b)\ + do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \ + }while(0) +#define C_ADDTO( res , a)\ + do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \ + }while(0) + +#if defined(OPUS_ARM_INLINE_ASM) +#include "arm/kiss_fft_armv4.h" +#endif + +#if defined(OPUS_ARM_INLINE_EDSP) +#include "arm/kiss_fft_armv5e.h" +#endif +#if defined(MIPSr1_ASM) +#include "mips/kiss_fft_mipsr1.h" +#endif + +#else /* not FIXED_POINT*/ + +# define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) +#define C_MULC(m,a,b) \ + do{ (m).r = (a).r*(b).r + (a).i*(b).i;\ + (m).i = (a).i*(b).r - (a).r*(b).i; }while(0) + +#define C_MUL4(m,a,b) C_MUL(m,a,b) + +# define C_FIXDIV(c,div) /* NOOP */ +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) +#endif + +#ifndef CHECK_OVERFLOW_OP +# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ +#endif + +#ifndef C_ADD +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) +#endif /* C_ADD defined */ + +#ifdef FIXED_POINT +/*# define KISS_FFT_COS(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase)))) +# define KISS_FFT_SIN(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase))))*/ +# define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase)) +# define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase)) +# define HALF_OF(x) ((x)>>1) +#elif defined(USE_SIMD) +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5f)) +#else +# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) +# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) +# define HALF_OF(x) ((x)*.5f) +#endif + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = KISS_FFT_COS(phase);\ + (x)->i = KISS_FFT_SIN(phase);\ + }while(0) + +#define kf_cexp2(x,phase) \ + do{ \ + (x)->r = TRIG_UPSCALE*celt_cos_norm((phase));\ + (x)->i = TRIG_UPSCALE*celt_cos_norm((phase)-32768);\ +}while(0) + +#endif /* KISS_FFT_GUTS_H */ diff --git a/port/third_party/opus/celt/arch.h b/port/third_party/opus/celt/arch.h new file mode 100644 index 00000000..3845c3a0 --- /dev/null +++ b/port/third_party/opus/celt/arch.h @@ -0,0 +1,291 @@ +/* Copyright (c) 2003-2008 Jean-Marc Valin + Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/** + @file arch.h + @brief Various architecture definitions for CELT +*/ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef ARCH_H +#define ARCH_H + +#include "opus_types.h" +#include "opus_defines.h" + +# if !defined(__GNUC_PREREQ) +# if defined(__GNUC__)&&defined(__GNUC_MINOR__) +# define __GNUC_PREREQ(_maj,_min) \ + ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) +# else +# define __GNUC_PREREQ(_maj,_min) 0 +# endif +# endif + +#if OPUS_GNUC_PREREQ(3, 0) +#define opus_likely(x) (__builtin_expect(!!(x), 1)) +#define opus_unlikely(x) (__builtin_expect(!!(x), 0)) +#else +#define opus_likely(x) (!!(x)) +#define opus_unlikely(x) (!!(x)) +#endif + +#define CELT_SIG_SCALE 32768.f + +#define CELT_FATAL(str) celt_fatal(str, __FILE__, __LINE__); + +#if defined(ENABLE_ASSERTIONS) || defined(ENABLE_HARDENING) +#ifdef __GNUC__ +__attribute__((noreturn)) +#endif +void celt_fatal(const char *str, const char *file, int line); + +#if defined(CELT_C) && !defined(OVERRIDE_celt_fatal) +#include +#include +#ifdef __GNUC__ +__attribute__((noreturn)) +#endif +void celt_fatal(const char *str, const char *file, int line) +{ + fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str); +#if defined(_MSC_VER) + _set_abort_behavior( 0, _WRITE_ABORT_MSG); +#endif + abort(); +} +#endif + +#define celt_assert(cond) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond);}} +#define celt_assert2(cond, message) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond "\n" message);}} +#define MUST_SUCCEED(call) celt_assert((call) == OPUS_OK) +#else +#define celt_assert(cond) +#define celt_assert2(cond, message) +#define MUST_SUCCEED(call) do {if((call) != OPUS_OK) {RESTORE_STACK; return OPUS_INTERNAL_ERROR;} } while (0) +#endif + +#if defined(ENABLE_ASSERTIONS) +#define celt_sig_assert(cond) {if (!(cond)) {CELT_FATAL("signal assertion failed: " #cond);}} +#else +#define celt_sig_assert(cond) +#endif + +#define IMUL32(a,b) ((a)*(b)) + +#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */ +#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */ +#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ +#define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */ +#define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */ +#define UADD32(a,b) ((a)+(b)) +#define USUB32(a,b) ((a)-(b)) + +/* Set this if opus_int64 is a native type of the CPU. */ +/* Assume that all LP64 architectures have fast 64-bit types; also x86_64 + (which can be ILP32 for x32) and Win64 (which is LLP64). */ +#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64) +#define OPUS_FAST_INT64 1 +#else +#define OPUS_FAST_INT64 0 +#endif + +#define PRINT_MIPS(file) + +#ifdef FIXED_POINT + +typedef opus_int16 opus_val16; +typedef opus_int32 opus_val32; +typedef opus_int64 opus_val64; + +typedef opus_val32 celt_sig; +typedef opus_val16 celt_norm; +typedef opus_val32 celt_ener; + +#define celt_isnan(x) 0 + +#define Q15ONE 32767 + +#define SIG_SHIFT 12 +/* Safe saturation value for 32-bit signals. Should be less than + 2^31*(1-0.85) to avoid blowing up on DC at deemphasis.*/ +#define SIG_SAT (300000000) + +#define NORM_SCALING 16384 + +#define DB_SHIFT 10 + +#define EPSILON 1 +#define VERY_SMALL 0 +#define VERY_LARGE16 ((opus_val16)32767) +#define Q15_ONE ((opus_val16)32767) + +#define SCALEIN(a) (a) +#define SCALEOUT(a) (a) + +#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) +#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) + +static OPUS_INLINE opus_int16 SAT16(opus_int32 x) { + return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x; +} + +#ifdef FIXED_DEBUG +#include "fixed_debug.h" +#else + +#include "fixed_generic.h" + +#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR +#include "arm/fixed_arm64.h" +#elif defined (OPUS_ARM_INLINE_EDSP) +#include "arm/fixed_armv5e.h" +#elif defined (OPUS_ARM_INLINE_ASM) +#include "arm/fixed_armv4.h" +#elif defined (BFIN_ASM) +#include "fixed_bfin.h" +#elif defined (TI_C5X_ASM) +#include "fixed_c5x.h" +#elif defined (TI_C6X_ASM) +#include "fixed_c6x.h" +#endif + +#endif + +#else /* FIXED_POINT */ + +typedef float opus_val16; +typedef float opus_val32; +typedef float opus_val64; + +typedef float celt_sig; +typedef float celt_norm; +typedef float celt_ener; + +#ifdef FLOAT_APPROX +/* This code should reliably detect NaN/inf even when -ffast-math is used. + Assumes IEEE 754 format. */ +static OPUS_INLINE int celt_isnan(float x) +{ + union {float f; opus_uint32 i;} in; + in.f = x; + return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0; +} +#else +#ifdef __FAST_MATH__ +#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input +#endif +#define celt_isnan(x) ((x)!=(x)) +#endif + +#define Q15ONE 1.0f + +#define NORM_SCALING 1.f + +#define EPSILON 1e-15f +#define VERY_SMALL 1e-30f +#define VERY_LARGE16 1e15f +#define Q15_ONE ((opus_val16)1.f) + +/* This appears to be the same speed as C99's fabsf() but it's more portable. */ +#define ABS16(x) ((float)fabs(x)) +#define ABS32(x) ((float)fabs(x)) + +#define QCONST16(x,bits) (x) +#define QCONST32(x,bits) (x) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define NEG32_ovflw(x) (-(x)) +#define EXTRACT16(x) (x) +#define EXTEND32(x) (x) +#define SHR16(a,shift) (a) +#define SHL16(a,shift) (a) +#define SHR32(a,shift) (a) +#define SHL32(a,shift) (a) +#define PSHR32(a,shift) (a) +#define VSHR32(a,shift) (a) + +#define PSHR(a,shift) (a) +#define SHR(a,shift) (a) +#define SHL(a,shift) (a) +#define SATURATE(x,a) (x) +#define SATURATE16(x) (x) + +#define ROUND16(a,shift) (a) +#define SROUND16(a,shift) (a) +#define HALF16(x) (.5f*(x)) +#define HALF32(x) (.5f*(x)) + +#define ADD16(a,b) ((a)+(b)) +#define SUB16(a,b) ((a)-(b)) +#define ADD32(a,b) ((a)+(b)) +#define SUB32(a,b) ((a)-(b)) +#define ADD32_ovflw(a,b) ((a)+(b)) +#define SUB32_ovflw(a,b) ((a)-(b)) +#define MULT16_16_16(a,b) ((a)*(b)) +#define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b)) +#define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b)) + +#define MULT16_32_Q15(a,b) ((a)*(b)) +#define MULT16_32_Q16(a,b) ((a)*(b)) + +#define MULT32_32_Q31(a,b) ((a)*(b)) + +#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) +#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b)) + +#define MULT16_16_Q11_32(a,b) ((a)*(b)) +#define MULT16_16_Q11(a,b) ((a)*(b)) +#define MULT16_16_Q13(a,b) ((a)*(b)) +#define MULT16_16_Q14(a,b) ((a)*(b)) +#define MULT16_16_Q15(a,b) ((a)*(b)) +#define MULT16_16_P15(a,b) ((a)*(b)) +#define MULT16_16_P13(a,b) ((a)*(b)) +#define MULT16_16_P14(a,b) ((a)*(b)) +#define MULT16_32_P16(a,b) ((a)*(b)) + +#define DIV32_16(a,b) (((opus_val32)(a))/(opus_val16)(b)) +#define DIV32(a,b) (((opus_val32)(a))/(opus_val32)(b)) + +#define SCALEIN(a) ((a)*CELT_SIG_SCALE) +#define SCALEOUT(a) ((a)*(1/CELT_SIG_SCALE)) + +#define SIG2WORD16(x) (x) + +#endif /* !FIXED_POINT */ + +#ifndef GLOBAL_STACK_SIZE +#ifdef FIXED_POINT +#define GLOBAL_STACK_SIZE 120000 +#else +#define GLOBAL_STACK_SIZE 120000 +#endif +#endif + +#endif /* ARCH_H */ diff --git a/port/third_party/opus/celt/bands.c b/port/third_party/opus/celt/bands.c new file mode 100644 index 00000000..6785e08e --- /dev/null +++ b/port/third_party/opus/celt/bands.c @@ -0,0 +1,1673 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008-2009 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "bands.h" +#include "modes.h" +#include "vq.h" +#include "cwrs.h" +#include "stack_alloc.h" +#include "os_support.h" +#include "mathops.h" +#include "rate.h" +#include "quant_bands.h" +#include "pitch.h" + +int hysteresis_decision(opus_val16 val, const opus_val16 *thresholds, const opus_val16 *hysteresis, int N, int prev) +{ + int i; + for (i=0;iprev && val < thresholds[prev]+hysteresis[prev]) + i=prev; + if (i thresholds[prev-1]-hysteresis[prev-1]) + i=prev; + return i; +} + +opus_uint32 celt_lcg_rand(opus_uint32 seed) +{ + return 1664525 * seed + 1013904223; +} + +/* This is a cos() approximation designed to be bit-exact on any platform. Bit exactness + with this approximation is important because it has an impact on the bit allocation */ +opus_int16 bitexact_cos(opus_int16 x) +{ + opus_int32 tmp; + opus_int16 x2; + tmp = (4096+((opus_int32)(x)*(x)))>>13; + celt_sig_assert(tmp<=32767); + x2 = tmp; + x2 = (32767-x2) + FRAC_MUL16(x2, (-7651 + FRAC_MUL16(x2, (8277 + FRAC_MUL16(-626, x2))))); + celt_sig_assert(x2<=32766); + return 1+x2; +} + +int bitexact_log2tan(int isin,int icos) +{ + int lc; + int ls; + lc=EC_ILOG(icos); + ls=EC_ILOG(isin); + icos<<=15-lc; + isin<<=15-ls; + return (ls-lc)*(1<<11) + +FRAC_MUL16(isin, FRAC_MUL16(isin, -2597) + 7932) + -FRAC_MUL16(icos, FRAC_MUL16(icos, -2597) + 7932); +} + +#ifdef FIXED_POINT +/* Compute the amplitude (sqrt energy) in each of the bands */ +void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int LM, int arch) +{ + int i, c, N; + const opus_int16 *eBands = m->eBands; + (void)arch; + N = m->shortMdctSize< 0) + { + int shift = celt_ilog2(maxval) - 14 + (((m->logN[i]>>BITRES)+LM+1)>>1); + j=eBands[i]<0) + { + do { + sum = MAC16_16(sum, EXTRACT16(SHR32(X[j+c*N],shift)), + EXTRACT16(SHR32(X[j+c*N],shift))); + } while (++jnbEBands] = EPSILON+VSHR32(EXTEND32(celt_sqrt(sum)),-shift); + } else { + bandE[i+c*m->nbEBands] = EPSILON; + } + /*printf ("%f ", bandE[i+c*m->nbEBands]);*/ + } + } while (++ceBands; + N = M*m->shortMdctSize; + c=0; do { + i=0; do { + opus_val16 g; + int j,shift; + opus_val16 E; + shift = celt_zlog2(bandE[i+c*m->nbEBands])-13; + E = VSHR32(bandE[i+c*m->nbEBands], shift); + g = EXTRACT16(celt_rcp(SHL32(E,3))); + j=M*eBands[i]; do { + X[j+c*N] = MULT16_16_Q15(VSHR32(freq[j+c*N],shift-1),g); + } while (++jeBands; + N = m->shortMdctSize<nbEBands] = celt_sqrt(sum); + /*printf ("%f ", bandE[i+c*m->nbEBands]);*/ + } + } while (++ceBands; + N = M*m->shortMdctSize; + c=0; do { + for (i=0;inbEBands]); + for (j=M*eBands[i];jeBands; + N = M*m->shortMdctSize; + bound = M*eBands[end]; + if (downsample!=1) + bound = IMIN(bound, N/downsample); + if (silence) + { + bound = 0; + start = end = 0; + } + f = freq; + x = X+M*eBands[start]; + for (i=0;i>DB_SHIFT); + if (shift>31) + { + shift=0; + g=0; + } else { + /* Handle the fractional part. */ + g = celt_exp2_frac(lg&((1< 16384 we'd be likely to overflow, so we're + capping the gain here, which is equivalent to a cap of 18 on lg. + This shouldn't trigger unless the bitstream is already corrupted. */ + if (shift <= -2) + { + g = 16384; + shift = -2; + } + do { + *f++ = SHL32(MULT16_16(*x++, g), -shift); + } while (++jeBands[i+1]-m->eBands[i]; + /* depth in 1/8 bits */ + celt_sig_assert(pulses[i]>=0); + depth = celt_udiv(1+pulses[i], (m->eBands[i+1]-m->eBands[i]))>>LM; + +#ifdef FIXED_POINT + thresh32 = SHR32(celt_exp2(-SHL16(depth, 10-BITRES)),1); + thresh = MULT16_32_Q15(QCONST16(0.5f, 15), MIN32(32767,thresh32)); + { + opus_val32 t; + t = N0<>1; + t = SHL32(t, (7-shift)<<1); + sqrt_1 = celt_rsqrt_norm(t); + } +#else + thresh = .5f*celt_exp2(-.125f*depth); + sqrt_1 = celt_rsqrt(N0<nbEBands+i]; + prev2 = prev2logE[c*m->nbEBands+i]; + if (C==1) + { + prev1 = MAX16(prev1,prev1logE[m->nbEBands+i]); + prev2 = MAX16(prev2,prev2logE[m->nbEBands+i]); + } + Ediff = EXTEND32(logE[c*m->nbEBands+i])-EXTEND32(MIN16(prev1,prev2)); + Ediff = MAX32(0, Ediff); + +#ifdef FIXED_POINT + if (Ediff < 16384) + { + opus_val32 r32 = SHR32(celt_exp2(-EXTRACT16(Ediff)),1); + r = 2*MIN16(16383,r32); + } else { + r = 0; + } + if (LM==3) + r = MULT16_16_Q14(23170, MIN32(23169, r)); + r = SHR16(MIN16(thresh, r),1); + r = SHR32(MULT16_16_Q15(sqrt_1, r),shift); +#else + /* r needs to be multiplied by 2 or 2*sqrt(2) depending on LM because + short blocks don't have the same energy as long */ + r = 2.f*celt_exp2(-Ediff); + if (LM==3) + r *= 1.41421356f; + r = MIN16(thresh, r); + r = r*sqrt_1; +#endif + X = X_+c*size+(m->eBands[i]<nbEBands]))-13; +#endif + left = VSHR32(bandE[i],shift); + right = VSHR32(bandE[i+m->nbEBands],shift); + norm = EPSILON + celt_sqrt(EPSILON+MULT16_16(left,left)+MULT16_16(right,right)); + a1 = DIV32_16(SHL32(EXTEND32(left),14),norm); + a2 = DIV32_16(SHL32(EXTEND32(right),14),norm); + for (j=0;j>1; + kr = celt_ilog2(Er)>>1; +#endif + t = VSHR32(El, (kl-7)<<1); + lgain = celt_rsqrt_norm(t); + t = VSHR32(Er, (kr-7)<<1); + rgain = celt_rsqrt_norm(t); + +#ifdef FIXED_POINT + if (kl < 7) + kl = 7; + if (kr < 7) + kr = 7; +#endif + + for (j=0;jeBands; + int decision; + int hf_sum=0; + + celt_assert(end>0); + + N0 = M*m->shortMdctSize; + + if (M*(eBands[end]-eBands[end-1]) <= 8) + return SPREAD_NONE; + c=0; do { + for (i=0;im->nbEBands-4) + hf_sum += celt_udiv(32*(tcount[1]+tcount[0]), N); + tmp = (2*tcount[2] >= N) + (2*tcount[1] >= N) + (2*tcount[0] >= N); + sum += tmp*spread_weight[i]; + nbBands+=spread_weight[i]; + } + } while (++cnbEBands+end)); + *hf_average = (*hf_average+hf_sum)>>1; + hf_sum = *hf_average; + if (*tapset_decision==2) + hf_sum += 4; + else if (*tapset_decision==0) + hf_sum -= 4; + if (hf_sum > 22) + *tapset_decision=2; + else if (hf_sum > 18) + *tapset_decision=1; + else + *tapset_decision=0; + } + /*printf("%d %d %d\n", hf_sum, *hf_average, *tapset_decision);*/ + celt_assert(nbBands>0); /* end has to be non-zero */ + celt_assert(sum>=0); + sum = celt_udiv((opus_int32)sum<<8, nbBands); + /* Recursive averaging */ + sum = (sum+*average)>>1; + *average = sum; + /* Hysteresis */ + sum = (3*sum + (((3-last_decision)<<7) + 64) + 2)>>2; + if (sum < 80) + { + decision = SPREAD_AGGRESSIVE; + } else if (sum < 256) + { + decision = SPREAD_NORMAL; + } else if (sum < 384) + { + decision = SPREAD_LIGHT; + } else { + decision = SPREAD_NONE; + } +#ifdef FUZZING + decision = rand()&0x3; + *tapset_decision=rand()%3; +#endif + return decision; +} + +/* Indexing table for converting from natural Hadamard to ordery Hadamard + This is essentially a bit-reversed Gray, on top of which we've added + an inversion of the order because we want the DC at the end rather than + the beginning. The lines are for N=2, 4, 8, 16 */ +static const int ordery_table[] = { + 1, 0, + 3, 0, 2, 1, + 7, 0, 4, 3, 6, 1, 5, 2, + 15, 0, 8, 7, 12, 3, 11, 4, 14, 1, 9, 6, 13, 2, 10, 5, +}; + +static void deinterleave_hadamard(celt_norm *X, int N0, int stride, int hadamard) +{ + int i,j; + VARDECL(celt_norm, tmp); + int N; + SAVE_STACK; + N = N0*stride; + ALLOC(tmp, N, celt_norm); + celt_assert(stride>0); + if (hadamard) + { + const int *ordery = ordery_table+stride-2; + for (i=0;i>= 1; + for (i=0;i>1)) { + qn = 1; + } else { + qn = exp2_table8[qb&0x7]>>(14-(qb>>BITRES)); + qn = (qn+1)>>1<<1; + } + celt_assert(qn <= 256); + return qn; +} + +struct band_ctx { + int encode; + int resynth; + const CELTMode *m; + int i; + int intensity; + int spread; + int tf_change; + ec_ctx *ec; + opus_int32 remaining_bits; + const celt_ener *bandE; + opus_uint32 seed; + int arch; + int theta_round; + int disable_inv; + int avoid_split_noise; +}; + +struct split_ctx { + int inv; + int imid; + int iside; + int delta; + int itheta; + int qalloc; +}; + +static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, + celt_norm *X, celt_norm *Y, int N, int *b, int B, int B0, + int LM, + int stereo, int *fill) +{ + int qn; + int itheta=0; + int delta; + int imid, iside; + int qalloc; + int pulse_cap; + int offset; + opus_int32 tell; + int inv=0; + int encode; + const CELTMode *m; + int i; + int intensity; + ec_ctx *ec; + const celt_ener *bandE; + + encode = ctx->encode; + m = ctx->m; + i = ctx->i; + intensity = ctx->intensity; + ec = ctx->ec; + bandE = ctx->bandE; + + /* Decide on the resolution to give to the split parameter theta */ + pulse_cap = m->logN[i]+LM*(1<>1) - (stereo&&N==2 ? QTHETA_OFFSET_TWOPHASE : QTHETA_OFFSET); + qn = compute_qn(N, *b, offset, pulse_cap, stereo); + if (stereo && i>=intensity) + qn = 1; + if (encode) + { + /* theta is the atan() of the ratio between the (normalized) + side and mid. With just that parameter, we can re-scale both + mid and side because we know that 1) they have unit norm and + 2) they are orthogonal. */ + itheta = stereo_itheta(X, Y, stereo, N, ctx->arch); + } + tell = ec_tell_frac(ec); + if (qn!=1) + { + if (encode) + { + if (!stereo || ctx->theta_round == 0) + { + itheta = (itheta*(opus_int32)qn+8192)>>14; + if (!stereo && ctx->avoid_split_noise && itheta > 0 && itheta < qn) + { + /* Check if the selected value of theta will cause the bit allocation + to inject noise on one side. If so, make sure the energy of that side + is zero. */ + int unquantized = celt_udiv((opus_int32)itheta*16384, qn); + imid = bitexact_cos((opus_int16)unquantized); + iside = bitexact_cos((opus_int16)(16384-unquantized)); + delta = FRAC_MUL16((N-1)<<7,bitexact_log2tan(iside,imid)); + if (delta > *b) + itheta = qn; + else if (delta < -*b) + itheta = 0; + } + } else { + int down; + /* Bias quantization towards itheta=0 and itheta=16384. */ + int bias = itheta > 8192 ? 32767/qn : -32767/qn; + down = IMIN(qn-1, IMAX(0, (itheta*(opus_int32)qn + bias)>>14)); + if (ctx->theta_round < 0) + itheta = down; + else + itheta = down+1; + } + } + /* Entropy coding of the angle. We use a uniform pdf for the + time split, a step for stereo, and a triangular one for the rest. */ + if (stereo && N>2) + { + int p0 = 3; + int x = itheta; + int x0 = qn/2; + int ft = p0*(x0+1) + x0; + /* Use a probability of p0 up to itheta=8192 and then use 1 after */ + if (encode) + { + ec_encode(ec,x<=x0?p0*x:(x-1-x0)+(x0+1)*p0,x<=x0?p0*(x+1):(x-x0)+(x0+1)*p0,ft); + } else { + int fs; + fs=ec_decode(ec,ft); + if (fs<(x0+1)*p0) + x=fs/p0; + else + x=x0+1+(fs-(x0+1)*p0); + ec_dec_update(ec,x<=x0?p0*x:(x-1-x0)+(x0+1)*p0,x<=x0?p0*(x+1):(x-x0)+(x0+1)*p0,ft); + itheta = x; + } + } else if (B0>1 || stereo) { + /* Uniform pdf */ + if (encode) + ec_enc_uint(ec, itheta, qn+1); + else + itheta = ec_dec_uint(ec, qn+1); + } else { + int fs=1, ft; + ft = ((qn>>1)+1)*((qn>>1)+1); + if (encode) + { + int fl; + + fs = itheta <= (qn>>1) ? itheta + 1 : qn + 1 - itheta; + fl = itheta <= (qn>>1) ? itheta*(itheta + 1)>>1 : + ft - ((qn + 1 - itheta)*(qn + 2 - itheta)>>1); + + ec_encode(ec, fl, fl+fs, ft); + } else { + /* Triangular pdf */ + int fl=0; + int fm; + fm = ec_decode(ec, ft); + + if (fm < ((qn>>1)*((qn>>1) + 1)>>1)) + { + itheta = (isqrt32(8*(opus_uint32)fm + 1) - 1)>>1; + fs = itheta + 1; + fl = itheta*(itheta + 1)>>1; + } + else + { + itheta = (2*(qn + 1) + - isqrt32(8*(opus_uint32)(ft - fm - 1) + 1))>>1; + fs = qn + 1 - itheta; + fl = ft - ((qn + 1 - itheta)*(qn + 2 - itheta)>>1); + } + + ec_dec_update(ec, fl, fl+fs, ft); + } + } + celt_assert(itheta>=0); + itheta = celt_udiv((opus_int32)itheta*16384, qn); + if (encode && stereo) + { + if (itheta==0) + intensity_stereo(m, X, Y, bandE, i, N); + else + stereo_split(X, Y, N); + } + /* NOTE: Renormalising X and Y *may* help fixed-point a bit at very high rate. + Let's do that at higher complexity */ + } else if (stereo) { + if (encode) + { + inv = itheta > 8192 && !ctx->disable_inv; + if (inv) + { + int j; + for (j=0;j2<remaining_bits > 2<disable_inv) + inv = 0; + itheta = 0; + } + qalloc = ec_tell_frac(ec) - tell; + *b -= qalloc; + + if (itheta == 0) + { + imid = 32767; + iside = 0; + *fill &= (1<inv = inv; + sctx->imid = imid; + sctx->iside = iside; + sctx->delta = delta; + sctx->itheta = itheta; + sctx->qalloc = qalloc; +} +static unsigned quant_band_n1(struct band_ctx *ctx, celt_norm *X, celt_norm *Y, + celt_norm *lowband_out) +{ + int c; + int stereo; + celt_norm *x = X; + int encode; + ec_ctx *ec; + + encode = ctx->encode; + ec = ctx->ec; + + stereo = Y != NULL; + c=0; do { + int sign=0; + if (ctx->remaining_bits>=1<remaining_bits -= 1<resynth) + x[0] = sign ? -NORM_SCALING : NORM_SCALING; + x = Y; + } while (++c<1+stereo); + if (lowband_out) + lowband_out[0] = SHR16(X[0],4); + return 1; +} + +/* This function is responsible for encoding and decoding a mono partition. + It can split the band in two and transmit the energy difference with + the two half-bands. It can be called recursively so bands can end up being + split in 8 parts. */ +static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, + int N, int b, int B, celt_norm *lowband, + int LM, + opus_val16 gain, int fill) +{ + const unsigned char *cache; + int q; + int curr_bits; + int imid=0, iside=0; + int B0=B; + opus_val16 mid=0, side=0; + unsigned cm=0; + celt_norm *Y=NULL; + int encode; + const CELTMode *m; + int i; + int spread; + ec_ctx *ec; + + encode = ctx->encode; + m = ctx->m; + i = ctx->i; + spread = ctx->spread; + ec = ctx->ec; + + /* If we need 1.5 more bit than we can produce, split the band in two. */ + cache = m->cache.bits + m->cache.index[(LM+1)*m->nbEBands+i]; + if (LM != -1 && b > cache[cache[0]]+12 && N>2) + { + int mbits, sbits, delta; + int itheta; + int qalloc; + struct split_ctx sctx; + celt_norm *next_lowband2=NULL; + opus_int32 rebalance; + + N >>= 1; + Y = X+N; + LM -= 1; + if (B==1) + fill = (fill&1)|(fill<<1); + B = (B+1)>>1; + + compute_theta(ctx, &sctx, X, Y, N, &b, B, B0, LM, 0, &fill); + imid = sctx.imid; + iside = sctx.iside; + delta = sctx.delta; + itheta = sctx.itheta; + qalloc = sctx.qalloc; +#ifdef FIXED_POINT + mid = imid; + side = iside; +#else + mid = (1.f/32768)*imid; + side = (1.f/32768)*iside; +#endif + + /* Give more bits to low-energy MDCTs than they would otherwise deserve */ + if (B0>1 && (itheta&0x3fff)) + { + if (itheta > 8192) + /* Rough approximation for pre-echo masking */ + delta -= delta>>(4-LM); + else + /* Corresponds to a forward-masking slope of 1.5 dB per 10 ms */ + delta = IMIN(0, delta + (N<>(5-LM))); + } + mbits = IMAX(0, IMIN(b, (b-delta)/2)); + sbits = b-mbits; + ctx->remaining_bits -= qalloc; + + if (lowband) + next_lowband2 = lowband+N; /* >32-bit split case */ + + rebalance = ctx->remaining_bits; + if (mbits >= sbits) + { + cm = quant_partition(ctx, X, N, mbits, B, lowband, LM, + MULT16_16_P15(gain,mid), fill); + rebalance = mbits - (rebalance-ctx->remaining_bits); + if (rebalance > 3<>B)<<(B0>>1); + } else { + cm = quant_partition(ctx, Y, N, sbits, B, next_lowband2, LM, + MULT16_16_P15(gain,side), fill>>B)<<(B0>>1); + rebalance = sbits - (rebalance-ctx->remaining_bits); + if (rebalance > 3<remaining_bits -= curr_bits; + + /* Ensures we can never bust the budget */ + while (ctx->remaining_bits < 0 && q > 0) + { + ctx->remaining_bits += curr_bits; + q--; + curr_bits = pulses2bits(m, i, LM, q); + ctx->remaining_bits -= curr_bits; + } + + if (q!=0) + { + int K = get_pulses(q); + + /* Finally do the actual quantization */ + if (encode) + { + cm = alg_quant(X, N, K, spread, B, ec, gain, ctx->resynth, ctx->arch); + } else { + cm = alg_unquant(X, N, K, spread, B, ec, gain); + } + } else { + /* If there's no pulse, fill the band anyway */ + int j; + if (ctx->resynth) + { + unsigned cm_mask; + /* B can be as large as 16, so this shift might overflow an int on a + 16-bit platform; use a long to get defined behavior.*/ + cm_mask = (unsigned)(1UL<seed = celt_lcg_rand(ctx->seed); + X[j] = (celt_norm)((opus_int32)ctx->seed>>20); + } + cm = cm_mask; + } else { + /* Folded spectrum */ + for (j=0;jseed = celt_lcg_rand(ctx->seed); + /* About 48 dB below the "normal" folding level */ + tmp = QCONST16(1.0f/256, 10); + tmp = (ctx->seed)&0x8000 ? tmp : -tmp; + X[j] = lowband[j]+tmp; + } + cm = fill; + } + renormalise_vector(X, N, gain, ctx->arch); + } + } + } + } + + return cm; +} + + +/* This function is responsible for encoding and decoding a band for the mono case. */ +static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, + int N, int b, int B, celt_norm *lowband, + int LM, celt_norm *lowband_out, + opus_val16 gain, celt_norm *lowband_scratch, int fill) +{ + int N0=N; + int N_B=N; + int N_B0; + int B0=B; + int time_divide=0; + int recombine=0; + int longBlocks; + unsigned cm=0; + int k; + int encode; + int tf_change; + + encode = ctx->encode; + tf_change = ctx->tf_change; + + longBlocks = B0==1; + + N_B = celt_udiv(N_B, B); + + /* Special case for one sample */ + if (N==1) + { + return quant_band_n1(ctx, X, NULL, lowband_out); + } + + if (tf_change>0) + recombine = tf_change; + /* Band recombining to increase frequency resolution */ + + if (lowband_scratch && lowband && (recombine || ((N_B&1) == 0 && tf_change<0) || B0>1)) + { + OPUS_COPY(lowband_scratch, lowband, N); + lowband = lowband_scratch; + } + + for (k=0;k>k, 1<>k, 1<>4]<<2; + } + B>>=recombine; + N_B<<=recombine; + + /* Increasing the time resolution */ + while ((N_B&1) == 0 && tf_change<0) + { + if (encode) + haar1(X, N_B, B); + if (lowband) + haar1(lowband, N_B, B); + fill |= fill<>= 1; + time_divide++; + tf_change++; + } + B0=B; + N_B0 = N_B; + + /* Reorganize the samples in time order instead of frequency order */ + if (B0>1) + { + if (encode) + deinterleave_hadamard(X, N_B>>recombine, B0<>recombine, B0<resynth) + { + /* Undo the sample reorganization going from time order to frequency order */ + if (B0>1) + interleave_hadamard(X, N_B>>recombine, B0<>= 1; + N_B <<= 1; + cm |= cm>>B; + haar1(X, N_B, B); + } + + for (k=0;k>k, 1<encode; + ec = ctx->ec; + + /* Special case for one sample */ + if (N==1) + { + return quant_band_n1(ctx, X, Y, lowband_out); + } + + orig_fill = fill; + + compute_theta(ctx, &sctx, X, Y, N, &b, B, B, LM, 1, &fill); + inv = sctx.inv; + imid = sctx.imid; + iside = sctx.iside; + delta = sctx.delta; + itheta = sctx.itheta; + qalloc = sctx.qalloc; +#ifdef FIXED_POINT + mid = imid; + side = iside; +#else + mid = (1.f/32768)*imid; + side = (1.f/32768)*iside; +#endif + + /* This is a special case for N=2 that only works for stereo and takes + advantage of the fact that mid and side are orthogonal to encode + the side with just one bit. */ + if (N==2) + { + int c; + int sign=0; + celt_norm *x2, *y2; + mbits = b; + sbits = 0; + /* Only need one bit for the side. */ + if (itheta != 0 && itheta != 16384) + sbits = 1< 8192; + ctx->remaining_bits -= qalloc+sbits; + + x2 = c ? Y : X; + y2 = c ? X : Y; + if (sbits) + { + if (encode) + { + /* Here we only need to encode a sign for the side. */ + sign = x2[0]*y2[1] - x2[1]*y2[0] < 0; + ec_enc_bits(ec, sign, 1); + } else { + sign = ec_dec_bits(ec, 1); + } + } + sign = 1-2*sign; + /* We use orig_fill here because we want to fold the side, but if + itheta==16384, we'll have cleared the low bits of fill. */ + cm = quant_band(ctx, x2, N, mbits, B, lowband, LM, lowband_out, Q15ONE, + lowband_scratch, orig_fill); + /* We don't split N=2 bands, so cm is either 1 or 0 (for a fold-collapse), + and there's no need to worry about mixing with the other channel. */ + y2[0] = -sign*x2[1]; + y2[1] = sign*x2[0]; + if (ctx->resynth) + { + celt_norm tmp; + X[0] = MULT16_16_Q15(mid, X[0]); + X[1] = MULT16_16_Q15(mid, X[1]); + Y[0] = MULT16_16_Q15(side, Y[0]); + Y[1] = MULT16_16_Q15(side, Y[1]); + tmp = X[0]; + X[0] = SUB16(tmp,Y[0]); + Y[0] = ADD16(tmp,Y[0]); + tmp = X[1]; + X[1] = SUB16(tmp,Y[1]); + Y[1] = ADD16(tmp,Y[1]); + } + } else { + /* "Normal" split code */ + opus_int32 rebalance; + + mbits = IMAX(0, IMIN(b, (b-delta)/2)); + sbits = b-mbits; + ctx->remaining_bits -= qalloc; + + rebalance = ctx->remaining_bits; + if (mbits >= sbits) + { + /* In stereo mode, we do not apply a scaling to the mid because we need the normalized + mid for folding later. */ + cm = quant_band(ctx, X, N, mbits, B, lowband, LM, lowband_out, Q15ONE, + lowband_scratch, fill); + rebalance = mbits - (rebalance-ctx->remaining_bits); + if (rebalance > 3<>B); + } else { + /* For a stereo split, the high bits of fill are always zero, so no + folding will be done to the side. */ + cm = quant_band(ctx, Y, N, sbits, B, NULL, LM, NULL, side, NULL, fill>>B); + rebalance = sbits - (rebalance-ctx->remaining_bits); + if (rebalance > 3<resynth) + { + if (N!=2) + stereo_merge(X, Y, mid, N, ctx->arch); + if (inv) + { + int j; + for (j=0;jeBands; + n1 = M*(eBands[start+1]-eBands[start]); + n2 = M*(eBands[start+2]-eBands[start+1]); + /* Duplicate enough of the first band folding data to be able to fold the second band. + Copies no data for CELT-only mode. */ + OPUS_COPY(&norm[n1], &norm[2*n1 - n2], n2-n1); + if (dual_stereo) + OPUS_COPY(&norm2[n1], &norm2[2*n1 - n2], n2-n1); +} +#endif + +void quant_all_bands(int encode, const CELTMode *m, int start, int end, + celt_norm *X_, celt_norm *Y_, unsigned char *collapse_masks, + const celt_ener *bandE, int *pulses, int shortBlocks, int spread, + int dual_stereo, int intensity, int *tf_res, opus_int32 total_bits, + opus_int32 balance, ec_ctx *ec, int LM, int codedBands, + opus_uint32 *seed, int complexity, int arch, int disable_inv) +{ + int i; + opus_int32 remaining_bits; + const opus_int16 * OPUS_RESTRICT eBands = m->eBands; + celt_norm * OPUS_RESTRICT norm, * OPUS_RESTRICT norm2; + VARDECL(celt_norm, _norm); + VARDECL(celt_norm, _lowband_scratch); + VARDECL(celt_norm, X_save); + VARDECL(celt_norm, Y_save); + VARDECL(celt_norm, X_save2); + VARDECL(celt_norm, Y_save2); + VARDECL(celt_norm, norm_save2); + int resynth_alloc; + celt_norm *lowband_scratch; + int B; + int M; + int lowband_offset; + int update_lowband = 1; + int C = Y_ != NULL ? 2 : 1; + int norm_offset; + int theta_rdo = encode && Y_!=NULL && !dual_stereo && complexity>=8; +#ifdef RESYNTH + int resynth = 1; +#else + int resynth = !encode || theta_rdo; +#endif + struct band_ctx ctx; + SAVE_STACK; + + M = 1<nbEBands-1]-norm_offset), celt_norm); + norm = _norm; + norm2 = norm + M*eBands[m->nbEBands-1]-norm_offset; + + /* For decoding, we can use the last band as scratch space because we don't need that + scratch space for the last band and we don't care about the data there until we're + decoding the last band. */ + if (encode && resynth) + resynth_alloc = M*(eBands[m->nbEBands]-eBands[m->nbEBands-1]); + else + resynth_alloc = ALLOC_NONE; + ALLOC(_lowband_scratch, resynth_alloc, celt_norm); + if (encode && resynth) + lowband_scratch = _lowband_scratch; + else + lowband_scratch = X_+M*eBands[m->effEBands-1]; + ALLOC(X_save, resynth_alloc, celt_norm); + ALLOC(Y_save, resynth_alloc, celt_norm); + ALLOC(X_save2, resynth_alloc, celt_norm); + ALLOC(Y_save2, resynth_alloc, celt_norm); + ALLOC(norm_save2, resynth_alloc, celt_norm); + + lowband_offset = 0; + ctx.bandE = bandE; + ctx.ec = ec; + ctx.encode = encode; + ctx.intensity = intensity; + ctx.m = m; + ctx.seed = *seed; + ctx.spread = spread; + ctx.arch = arch; + ctx.disable_inv = disable_inv; + ctx.resynth = resynth; + ctx.theta_round = 0; + /* Avoid injecting noise in the first band on transients. */ + ctx.avoid_split_noise = B > 1; + for (i=start;i 0); + tell = ec_tell_frac(ec); + + /* Compute how many bits we want to allocate to this band */ + if (i != start) + balance -= tell; + remaining_bits = total_bits-tell-1; + ctx.remaining_bits = remaining_bits; + if (i <= codedBands-1) + { + curr_balance = celt_sudiv(balance, IMIN(3, codedBands-i)); + b = IMAX(0, IMIN(16383, IMIN(remaining_bits+1,pulses[i]+curr_balance))); + } else { + b = 0; + } + +#ifndef DISABLE_UPDATE_DRAFT + if (resynth && (M*eBands[i]-N >= M*eBands[start] || i==start+1) && (update_lowband || lowband_offset==0)) + lowband_offset = i; + if (i == start+1) + special_hybrid_folding(m, norm, norm2, start, M, dual_stereo); +#else + if (resynth && M*eBands[i]-N >= M*eBands[start] && (update_lowband || lowband_offset==0)) + lowband_offset = i; +#endif + + tf_change = tf_res[i]; + ctx.tf_change = tf_change; + if (i>=m->effEBands) + { + X=norm; + if (Y_!=NULL) + Y = norm; + lowband_scratch = NULL; + } + if (last && !theta_rdo) + lowband_scratch = NULL; + + /* Get a conservative estimate of the collapse_mask's for the bands we're + going to be folding from. */ + if (lowband_offset != 0 && (spread!=SPREAD_AGGRESSIVE || B>1 || tf_change<0)) + { + int fold_start; + int fold_end; + int fold_i; + /* This ensures we never repeat spectral content within one band */ + effective_lowband = IMAX(0, M*eBands[lowband_offset]-norm_offset-N); + fold_start = lowband_offset; + while(M*eBands[--fold_start] > effective_lowband+norm_offset); + fold_end = lowband_offset-1; +#ifndef DISABLE_UPDATE_DRAFT + while(++fold_end < i && M*eBands[fold_end] < effective_lowband+norm_offset+N); +#else + while(M*eBands[++fold_end] < effective_lowband+norm_offset+N); +#endif + x_cm = y_cm = 0; + fold_i = fold_start; do { + x_cm |= collapse_masks[fold_i*C+0]; + y_cm |= collapse_masks[fold_i*C+C-1]; + } while (++fold_inbEBands], w); + /* Make a copy. */ + cm = x_cm|y_cm; + ec_save = *ec; + ctx_save = ctx; + OPUS_COPY(X_save, X, N); + OPUS_COPY(Y_save, Y, N); + /* Encode and round down. */ + ctx.theta_round = -1; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, cm); + dist0 = MULT16_32_Q15(w[0], celt_inner_prod(X_save, X, N, arch)) + MULT16_32_Q15(w[1], celt_inner_prod(Y_save, Y, N, arch)); + + /* Save first result. */ + cm2 = x_cm; + ec_save2 = *ec; + ctx_save2 = ctx; + OPUS_COPY(X_save2, X, N); + OPUS_COPY(Y_save2, Y, N); + if (!last) + OPUS_COPY(norm_save2, norm+M*eBands[i]-norm_offset, N); + nstart_bytes = ec_save.offs; + nend_bytes = ec_save.storage; + bytes_buf = ec_save.buf+nstart_bytes; + save_bytes = nend_bytes-nstart_bytes; + OPUS_COPY(bytes_save, bytes_buf, save_bytes); + + /* Restore */ + *ec = ec_save; + ctx = ctx_save; + OPUS_COPY(X, X_save, N); + OPUS_COPY(Y, Y_save, N); +#ifndef DISABLE_UPDATE_DRAFT + if (i == start+1) + special_hybrid_folding(m, norm, norm2, start, M, dual_stereo); +#endif + /* Encode and round up. */ + ctx.theta_round = 1; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, cm); + dist1 = MULT16_32_Q15(w[0], celt_inner_prod(X_save, X, N, arch)) + MULT16_32_Q15(w[1], celt_inner_prod(Y_save, Y, N, arch)); + if (dist0 >= dist1) { + x_cm = cm2; + *ec = ec_save2; + ctx = ctx_save2; + OPUS_COPY(X, X_save2, N); + OPUS_COPY(Y, Y_save2, N); + if (!last) + OPUS_COPY(norm+M*eBands[i]-norm_offset, norm_save2, N); + OPUS_COPY(bytes_buf, bytes_save, save_bytes); + } + } else { + ctx.theta_round = 0; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, x_cm|y_cm); + } + } else { + x_cm = quant_band(&ctx, X, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, Q15ONE, lowband_scratch, x_cm|y_cm); + } + y_cm = x_cm; + } + collapse_masks[i*C+0] = (unsigned char)x_cm; + collapse_masks[i*C+C-1] = (unsigned char)y_cm; + balance += pulses[i] + tell; + + /* Update the folding position only as long as we have 1 bit/sample depth. */ + update_lowband = b>(N< +#include "celt.h" +#include "pitch.h" +#include "bands.h" +#include "modes.h" +#include "entcode.h" +#include "quant_bands.h" +#include "rate.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "float_cast.h" +#include +#include "celt_lpc.h" +#include "vq.h" + +#ifndef PACKAGE_VERSION +#define PACKAGE_VERSION "unknown" +#endif + +#if defined(MIPSr1_ASM) +#include "mips/celt_mipsr1.h" +#endif + + +int resampling_factor(opus_int32 rate) +{ + int ret; + switch (rate) + { + case 48000: + ret = 1; + break; + case 24000: + ret = 2; + break; + case 16000: + ret = 3; + break; + case 12000: + ret = 4; + break; + case 8000: + ret = 6; + break; + default: +#ifndef CUSTOM_MODES + celt_assert(0); +#endif + ret = 0; + break; + } + return ret; +} + +#if !defined(OVERRIDE_COMB_FILTER_CONST) || defined(NON_STATIC_COMB_FILTER_CONST_C) +/* This version should be faster on ARM */ +#ifdef OPUS_ARM_ASM +#ifndef NON_STATIC_COMB_FILTER_CONST_C +static +#endif +void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N, + opus_val16 g10, opus_val16 g11, opus_val16 g12) +{ + opus_val32 x0, x1, x2, x3, x4; + int i; + x4 = SHL32(x[-T-2], 1); + x3 = SHL32(x[-T-1], 1); + x2 = SHL32(x[-T], 1); + x1 = SHL32(x[-T+1], 1); + for (i=0;inbEBands;i++) + { + int N; + N=(m->eBands[i+1]-m->eBands[i])<cache.caps[m->nbEBands*(2*LM+C-1)+i]+64)*C*N>>2; + } +} + + + +const char *opus_strerror(int error) +{ + static const char * const error_strings[8] = { + "success", + "invalid argument", + "buffer too small", + "internal error", + "corrupted stream", + "request not implemented", + "invalid state", + "memory allocation failed" + }; + if (error > 0 || error < -7) + return "unknown error"; + else + return error_strings[-error]; +} + +const char *opus_get_version_string(void) +{ + return "libopus " PACKAGE_VERSION + /* Applications may rely on the presence of this substring in the version + string to determine if they have a fixed-point or floating-point build + at runtime. */ +#ifdef FIXED_POINT + "-fixed" +#endif +#ifdef FUZZING + "-fuzzing" +#endif + ; +} diff --git a/port/third_party/opus/celt/celt.h b/port/third_party/opus/celt/celt.h new file mode 100644 index 00000000..2f501951 --- /dev/null +++ b/port/third_party/opus/celt/celt.h @@ -0,0 +1,252 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/** + @file celt.h + @brief Contains all the functions for encoding and decoding audio + */ + +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef CELT_H +#define CELT_H + +#include "opus_types.h" +#include "opus_defines.h" +#include "opus_custom.h" +#include "entenc.h" +#include "entdec.h" +#include "arch.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define CELTEncoder OpusCustomEncoder +#define CELTDecoder OpusCustomDecoder +#define CELTMode OpusCustomMode + +#define LEAK_BANDS 19 + +typedef struct { + int valid; + float tonality; + float tonality_slope; + float noisiness; + float activity; + float music_prob; + float music_prob_min; + float music_prob_max; + int bandwidth; + float activity_probability; + float max_pitch_ratio; + /* Store as Q6 char to save space. */ + unsigned char leak_boost[LEAK_BANDS]; +} AnalysisInfo; + +typedef struct { + int signalType; + int offset; +} SILKInfo; + +#define __celt_check_mode_ptr_ptr(ptr) ((ptr) + ((ptr) - (const CELTMode**)(ptr))) + +#define __celt_check_analysis_ptr(ptr) ((ptr) + ((ptr) - (const AnalysisInfo*)(ptr))) + +#define __celt_check_silkinfo_ptr(ptr) ((ptr) + ((ptr) - (const SILKInfo*)(ptr))) + +/* Encoder/decoder Requests */ + + +#define CELT_SET_PREDICTION_REQUEST 10002 +/** Controls the use of interframe prediction. + 0=Independent frames + 1=Short term interframe prediction allowed + 2=Long term prediction allowed + */ +#define CELT_SET_PREDICTION(x) CELT_SET_PREDICTION_REQUEST, __opus_check_int(x) + +#define CELT_SET_INPUT_CLIPPING_REQUEST 10004 +#define CELT_SET_INPUT_CLIPPING(x) CELT_SET_INPUT_CLIPPING_REQUEST, __opus_check_int(x) + +#define CELT_GET_AND_CLEAR_ERROR_REQUEST 10007 +#define CELT_GET_AND_CLEAR_ERROR(x) CELT_GET_AND_CLEAR_ERROR_REQUEST, __opus_check_int_ptr(x) + +#define CELT_SET_CHANNELS_REQUEST 10008 +#define CELT_SET_CHANNELS(x) CELT_SET_CHANNELS_REQUEST, __opus_check_int(x) + + +/* Internal */ +#define CELT_SET_START_BAND_REQUEST 10010 +#define CELT_SET_START_BAND(x) CELT_SET_START_BAND_REQUEST, __opus_check_int(x) + +#define CELT_SET_END_BAND_REQUEST 10012 +#define CELT_SET_END_BAND(x) CELT_SET_END_BAND_REQUEST, __opus_check_int(x) + +#define CELT_GET_MODE_REQUEST 10015 +/** Get the CELTMode used by an encoder or decoder */ +#define CELT_GET_MODE(x) CELT_GET_MODE_REQUEST, __celt_check_mode_ptr_ptr(x) + +#define CELT_SET_SIGNALLING_REQUEST 10016 +#define CELT_SET_SIGNALLING(x) CELT_SET_SIGNALLING_REQUEST, __opus_check_int(x) + +#define CELT_SET_TONALITY_REQUEST 10018 +#define CELT_SET_TONALITY(x) CELT_SET_TONALITY_REQUEST, __opus_check_int(x) +#define CELT_SET_TONALITY_SLOPE_REQUEST 10020 +#define CELT_SET_TONALITY_SLOPE(x) CELT_SET_TONALITY_SLOPE_REQUEST, __opus_check_int(x) + +#define CELT_SET_ANALYSIS_REQUEST 10022 +#define CELT_SET_ANALYSIS(x) CELT_SET_ANALYSIS_REQUEST, __celt_check_analysis_ptr(x) + +#define OPUS_SET_LFE_REQUEST 10024 +#define OPUS_SET_LFE(x) OPUS_SET_LFE_REQUEST, __opus_check_int(x) + +#define OPUS_SET_ENERGY_MASK_REQUEST 10026 +#define OPUS_SET_ENERGY_MASK(x) OPUS_SET_ENERGY_MASK_REQUEST, __opus_check_val16_ptr(x) + +#define CELT_SET_SILK_INFO_REQUEST 10028 +#define CELT_SET_SILK_INFO(x) CELT_SET_SILK_INFO_REQUEST, __celt_check_silkinfo_ptr(x) + +/* Encoder stuff */ + +int celt_encoder_get_size(int channels); + +int celt_encode_with_ec(OpusCustomEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes, ec_enc *enc); + +int celt_encoder_init(CELTEncoder *st, opus_int32 sampling_rate, int channels, + int arch); + + + +/* Decoder stuff */ + +int celt_decoder_get_size(int channels); + + +int celt_decoder_init(CELTDecoder *st, opus_int32 sampling_rate, int channels); + +int celt_decode_with_ec_dred(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, + int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum +#ifdef ENABLE_DEEP_PLC + ,LPCNetPLCState *lpcnet +#endif + ); + +int celt_decode_with_ec(OpusCustomDecoder * OPUS_RESTRICT st, const unsigned char *data, + int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum); + +#define celt_encoder_ctl opus_custom_encoder_ctl +#define celt_decoder_ctl opus_custom_decoder_ctl + + +#ifdef CUSTOM_MODES +#define OPUS_CUSTOM_NOSTATIC +#else +#define OPUS_CUSTOM_NOSTATIC static OPUS_INLINE +#endif + +static const unsigned char trim_icdf[11] = {126, 124, 119, 109, 87, 41, 19, 9, 4, 2, 0}; +/* Probs: NONE: 21.875%, LIGHT: 6.25%, NORMAL: 65.625%, AGGRESSIVE: 6.25% */ +static const unsigned char spread_icdf[4] = {25, 23, 2, 0}; + +static const unsigned char tapset_icdf[3]={2,1,0}; + +#ifdef CUSTOM_MODES +static const unsigned char toOpusTable[20] = { + 0xE0, 0xE8, 0xF0, 0xF8, + 0xC0, 0xC8, 0xD0, 0xD8, + 0xA0, 0xA8, 0xB0, 0xB8, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x88, 0x90, 0x98, +}; + +static const unsigned char fromOpusTable[16] = { + 0x80, 0x88, 0x90, 0x98, + 0x40, 0x48, 0x50, 0x58, + 0x20, 0x28, 0x30, 0x38, + 0x00, 0x08, 0x10, 0x18 +}; + +static OPUS_INLINE int toOpus(unsigned char c) +{ + int ret=0; + if (c<0xA0) + ret = toOpusTable[c>>3]; + if (ret == 0) + return -1; + else + return ret|(c&0x7); +} + +static OPUS_INLINE int fromOpus(unsigned char c) +{ + if (c<0x80) + return -1; + else + return fromOpusTable[(c>>3)-16] | (c&0x7); +} +#endif /* CUSTOM_MODES */ + +#define COMBFILTER_MAXPERIOD 1024 +#define COMBFILTER_MINPERIOD 15 + +extern const signed char tf_select_table[4][8]; + +#if defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS) +void validate_celt_decoder(CELTDecoder *st); +#define VALIDATE_CELT_DECODER(st) validate_celt_decoder(st) +#else +#define VALIDATE_CELT_DECODER(st) +#endif + +int resampling_factor(opus_int32 rate); + +void celt_preemphasis(const opus_val16 * OPUS_RESTRICT pcmp, celt_sig * OPUS_RESTRICT inp, + int N, int CC, int upsample, const opus_val16 *coef, celt_sig *mem, int clip); + +void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N, + opus_val16 g0, opus_val16 g1, int tapset0, int tapset1, + const opus_val16 *window, int overlap, int arch); + +void init_caps(const CELTMode *m,int *cap,int LM,int C); + +#ifdef RESYNTH +void deemphasis(celt_sig *in[], opus_val16 *pcm, int N, int C, int downsample, const opus_val16 *coef, celt_sig *mem, int accum); +void celt_synthesis(const CELTMode *mode, celt_norm *X, celt_sig * out_syn[], + opus_val16 *oldBandE, int start, int effEnd, int C, int CC, int isTransient, + int LM, int downsample, int silence, int arch); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* CELT_H */ diff --git a/port/third_party/opus/celt/celt_decoder.c b/port/third_party/opus/celt/celt_decoder.c new file mode 100644 index 00000000..743c2031 --- /dev/null +++ b/port/third_party/opus/celt/celt_decoder.c @@ -0,0 +1,1591 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2010 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define CELT_DECODER_C + +#include "cpu_support.h" +#include "os_support.h" +#include "mdct.h" +#include +#include "celt.h" +#include "pitch.h" +#include "bands.h" +#include "modes.h" +#include "entcode.h" +#include "quant_bands.h" +#include "rate.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "float_cast.h" +#include +#include "celt_lpc.h" +#include "vq.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet.h" +#include "lpcnet_private.h" +#endif + +/* The maximum pitch lag to allow in the pitch-based PLC. It's possible to save + CPU time in the PLC pitch search by making this smaller than MAX_PERIOD. The + current value corresponds to a pitch of 66.67 Hz. */ +#define PLC_PITCH_LAG_MAX (720) +/* The minimum pitch lag to allow in the pitch-based PLC. This corresponds to a + pitch of 480 Hz. */ +#define PLC_PITCH_LAG_MIN (100) + +/**********************************************************************/ +/* */ +/* DECODER */ +/* */ +/**********************************************************************/ +#define DECODE_BUFFER_SIZE 2048 + +#define PLC_UPDATE_FRAMES 4 +#define PLC_UPDATE_SAMPLES (PLC_UPDATE_FRAMES*FRAME_SIZE) + +/** Decoder state + @brief Decoder state + */ +struct OpusCustomDecoder { + const OpusCustomMode *mode; + int overlap; + int channels; + int stream_channels; + + int downsample; + int start, end; + int signalling; + int disable_inv; + int complexity; + int arch; + + /* Everything beyond this point gets cleared on a reset */ +#define DECODER_RESET_START rng + + opus_uint32 rng; + int error; + int last_pitch_index; + int loss_duration; + int skip_plc; + int postfilter_period; + int postfilter_period_old; + opus_val16 postfilter_gain; + opus_val16 postfilter_gain_old; + int postfilter_tapset; + int postfilter_tapset_old; + int prefilter_and_fold; + + celt_sig preemph_memD[2]; + +#ifdef ENABLE_DEEP_PLC + opus_int16 plc_pcm[PLC_UPDATE_SAMPLES]; + int plc_fill; + float plc_preemphasis_mem; +#endif + + celt_sig _decode_mem[1]; /* Size = channels*(DECODE_BUFFER_SIZE+mode->overlap) */ + /* opus_val16 lpc[], Size = channels*CELT_LPC_ORDER */ + /* opus_val16 oldEBands[], Size = 2*mode->nbEBands */ + /* opus_val16 oldLogE[], Size = 2*mode->nbEBands */ + /* opus_val16 oldLogE2[], Size = 2*mode->nbEBands */ + /* opus_val16 backgroundLogE[], Size = 2*mode->nbEBands */ +}; + +#if defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS) +/* Make basic checks on the CELT state to ensure we don't end + up writing all over memory. */ +void validate_celt_decoder(CELTDecoder *st) +{ +#ifndef CUSTOM_MODES + celt_assert(st->mode == opus_custom_mode_create(48000, 960, NULL)); + celt_assert(st->overlap == 120); + celt_assert(st->end <= 21); +#else +/* From Section 4.3 in the spec: "The normal CELT layer uses 21 of those bands, + though Opus Custom (see Section 6.2) may use a different number of bands" + + Check if it's within the maximum number of Bark frequency bands instead */ + celt_assert(st->end <= 25); +#endif + celt_assert(st->channels == 1 || st->channels == 2); + celt_assert(st->stream_channels == 1 || st->stream_channels == 2); + celt_assert(st->downsample > 0); + celt_assert(st->start == 0 || st->start == 17); + celt_assert(st->start < st->end); +#ifdef OPUS_ARCHMASK + celt_assert(st->arch >= 0); + celt_assert(st->arch <= OPUS_ARCHMASK); +#endif + celt_assert(st->last_pitch_index <= PLC_PITCH_LAG_MAX); + celt_assert(st->last_pitch_index >= PLC_PITCH_LAG_MIN || st->last_pitch_index == 0); + celt_assert(st->postfilter_period < MAX_PERIOD); + celt_assert(st->postfilter_period >= COMBFILTER_MINPERIOD || st->postfilter_period == 0); + celt_assert(st->postfilter_period_old < MAX_PERIOD); + celt_assert(st->postfilter_period_old >= COMBFILTER_MINPERIOD || st->postfilter_period_old == 0); + celt_assert(st->postfilter_tapset <= 2); + celt_assert(st->postfilter_tapset >= 0); + celt_assert(st->postfilter_tapset_old <= 2); + celt_assert(st->postfilter_tapset_old >= 0); +} +#endif + +int celt_decoder_get_size(int channels) +{ + const CELTMode *mode = opus_custom_mode_create(48000, 960, NULL); + return opus_custom_decoder_get_size(mode, channels); +} + +OPUS_CUSTOM_NOSTATIC int opus_custom_decoder_get_size(const CELTMode *mode, int channels) +{ + int size = sizeof(struct CELTDecoder) + + (channels*(DECODE_BUFFER_SIZE+mode->overlap)-1)*sizeof(celt_sig) + + channels*CELT_LPC_ORDER*sizeof(opus_val16) + + 4*2*mode->nbEBands*sizeof(opus_val16); + return size; +} + +#ifdef CUSTOM_MODES +CELTDecoder *opus_custom_decoder_create(const CELTMode *mode, int channels, int *error) +{ + int ret; + CELTDecoder *st = (CELTDecoder *)opus_alloc(opus_custom_decoder_get_size(mode, channels)); + ret = opus_custom_decoder_init(st, mode, channels); + if (ret != OPUS_OK) + { + opus_custom_decoder_destroy(st); + st = NULL; + } + if (error) + *error = ret; + return st; +} +#endif /* CUSTOM_MODES */ + +int celt_decoder_init(CELTDecoder *st, opus_int32 sampling_rate, int channels) +{ + int ret; + ret = opus_custom_decoder_init(st, opus_custom_mode_create(48000, 960, NULL), channels); + if (ret != OPUS_OK) + return ret; + st->downsample = resampling_factor(sampling_rate); + if (st->downsample==0) + return OPUS_BAD_ARG; + else + return OPUS_OK; +} + +OPUS_CUSTOM_NOSTATIC int opus_custom_decoder_init(CELTDecoder *st, const CELTMode *mode, int channels) +{ + if (channels < 0 || channels > 2) + return OPUS_BAD_ARG; + + if (st==NULL) + return OPUS_ALLOC_FAIL; + + OPUS_CLEAR((char*)st, opus_custom_decoder_get_size(mode, channels)); + + st->mode = mode; + st->overlap = mode->overlap; + st->stream_channels = st->channels = channels; + + st->downsample = 1; + st->start = 0; + st->end = st->mode->effEBands; + st->signalling = 1; +#ifndef DISABLE_UPDATE_DRAFT + st->disable_inv = channels == 1; +#else + st->disable_inv = 0; +#endif + st->arch = opus_select_arch(); + + opus_custom_decoder_ctl(st, OPUS_RESET_STATE); + + return OPUS_OK; +} + +#ifdef CUSTOM_MODES +void opus_custom_decoder_destroy(CELTDecoder *st) +{ + opus_free(st); +} +#endif /* CUSTOM_MODES */ + +#ifndef CUSTOM_MODES +/* Special case for stereo with no downsampling and no accumulation. This is + quite common and we can make it faster by processing both channels in the + same loop, reducing overhead due to the dependency loop in the IIR filter. */ +static void deemphasis_stereo_simple(celt_sig *in[], opus_val16 *pcm, int N, const opus_val16 coef0, + celt_sig *mem) +{ + celt_sig * OPUS_RESTRICT x0; + celt_sig * OPUS_RESTRICT x1; + celt_sig m0, m1; + int j; + x0=in[0]; + x1=in[1]; + m0 = mem[0]; + m1 = mem[1]; + for (j=0;j1) + { + /* Shortcut for the standard (non-custom modes) case */ + for (j=0;joverlap; + nbEBands = mode->nbEBands; + N = mode->shortMdctSize<shortMdctSize; + shift = mode->maxLM; + } else { + B = 1; + NB = mode->shortMdctSize<maxLM-LM; + } + + if (CC==2&&C==1) + { + /* Copying a mono streams to two channels */ + celt_sig *freq2; + denormalise_bands(mode, X, freq, oldBandE, start, effEnd, M, + downsample, silence); + /* Store a temporary copy in the output buffer because the IMDCT destroys its input. */ + freq2 = out_syn[1]+overlap/2; + OPUS_COPY(freq2, freq, N); + for (b=0;bmdct, &freq2[b], out_syn[0]+NB*b, mode->window, overlap, shift, B, arch); + for (b=0;bmdct, &freq[b], out_syn[1]+NB*b, mode->window, overlap, shift, B, arch); + } else if (CC==1&&C==2) + { + /* Downmixing a stereo stream to mono */ + celt_sig *freq2; + freq2 = out_syn[0]+overlap/2; + denormalise_bands(mode, X, freq, oldBandE, start, effEnd, M, + downsample, silence); + /* Use the output buffer as temp array before downmixing. */ + denormalise_bands(mode, X+N, freq2, oldBandE+nbEBands, start, effEnd, M, + downsample, silence); + for (i=0;imdct, &freq[b], out_syn[0]+NB*b, mode->window, overlap, shift, B, arch); + } else { + /* Normal case (mono or stereo) */ + c=0; do { + denormalise_bands(mode, X+c*N, freq, oldBandE+c*nbEBands, start, effEnd, M, + downsample, silence); + for (b=0;bmdct, &freq[b], out_syn[c]+NB*b, mode->window, overlap, shift, B, arch); + } while (++cstorage*8; + tell = ec_tell(dec); + logp = isTransient ? 2 : 4; + tf_select_rsv = LM>0 && tell+logp+1<=budget; + budget -= tf_select_rsv; + tf_changed = curr = 0; + for (i=start;i>1, opus_val16 ); + pitch_downsample(decode_mem, lp_pitch_buf, + DECODE_BUFFER_SIZE, C, arch); + pitch_search(lp_pitch_buf+(PLC_PITCH_LAG_MAX>>1), lp_pitch_buf, + DECODE_BUFFER_SIZE-PLC_PITCH_LAG_MAX, + PLC_PITCH_LAG_MAX-PLC_PITCH_LAG_MIN, &pitch_index, arch); + pitch_index = PLC_PITCH_LAG_MAX-pitch_index; + RESTORE_STACK; + return pitch_index; +} + +static void prefilter_and_fold(CELTDecoder * OPUS_RESTRICT st, int N) +{ + int c; + int CC; + int i; + int overlap; + celt_sig *decode_mem[2]; + const OpusCustomMode *mode; + VARDECL(opus_val32, etmp); + mode = st->mode; + overlap = st->overlap; + CC = st->channels; + ALLOC(etmp, overlap, opus_val32); + c=0; do { + decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap); + } while (++cpostfilter_period_old, st->postfilter_period, overlap, + -st->postfilter_gain_old, -st->postfilter_gain, + st->postfilter_tapset_old, st->postfilter_tapset, NULL, 0, st->arch); + + /* Simulate TDAC on the concealed audio so that it blends with the + MDCT of the next frame. */ + for (i=0;iwindow[i], etmp[overlap-1-i]) + + MULT16_32_Q15(mode->window[overlap-i-1], etmp[i]); + } + } while (++cfec_read_pos; + tmp_fec_skip = lpcnet->fec_skip; + for (i=0;ifec_read_pos = tmp_read_post; + lpcnet->fec_skip = tmp_fec_skip; +} +#endif + +static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM +#ifdef ENABLE_DEEP_PLC + ,LPCNetPLCState *lpcnet +#endif + ) +{ + int c; + int i; + const int C = st->channels; + celt_sig *decode_mem[2]; + celt_sig *out_syn[2]; + opus_val16 *lpc; + opus_val16 *oldBandE, *oldLogE, *oldLogE2, *backgroundLogE; + const OpusCustomMode *mode; + int nbEBands; + int overlap; + int start; + int loss_duration; + int noise_based; + const opus_int16 *eBands; + SAVE_STACK; + + mode = st->mode; + nbEBands = mode->nbEBands; + overlap = mode->overlap; + eBands = mode->eBands; + + c=0; do { + decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap); + out_syn[c] = decode_mem[c]+DECODE_BUFFER_SIZE-N; + } while (++c_decode_mem+(DECODE_BUFFER_SIZE+overlap)*C); + oldBandE = lpc+C*CELT_LPC_ORDER; + oldLogE = oldBandE + 2*nbEBands; + oldLogE2 = oldLogE + 2*nbEBands; + backgroundLogE = oldLogE2 + 2*nbEBands; + + loss_duration = st->loss_duration; + start = st->start; +#ifdef ENABLE_DEEP_PLC + noise_based = start != 0 || (lpcnet->fec_fill_pos == 0 && (st->skip_plc || loss_duration >= 80)); +#else + noise_based = loss_duration >= 40 || start != 0 || st->skip_plc; +#endif + if (noise_based) + { + /* Noise-based PLC/CNG */ + VARDECL(celt_norm, X); + opus_uint32 seed; + int end; + int effEnd; + opus_val16 decay; + end = st->end; + effEnd = IMAX(start, IMIN(end, mode->effEBands)); + + ALLOC(X, C*N, celt_norm); /**< Interleaved normalised MDCTs */ + c=0; do { + OPUS_MOVE(decode_mem[c], decode_mem[c]+N, + DECODE_BUFFER_SIZE-N+overlap); + } while (++cprefilter_and_fold) { + prefilter_and_fold(st, N); + } + + /* Energy decay */ + decay = loss_duration==0 ? QCONST16(1.5f, DB_SHIFT) : QCONST16(.5f, DB_SHIFT); + c=0; do + { + for (i=start;irng; + for (c=0;c>20); + } + renormalise_vector(X+boffs, blen, Q15ONE, st->arch); + } + } + st->rng = seed; + + celt_synthesis(mode, X, out_syn, oldBandE, start, effEnd, C, C, 0, LM, st->downsample, 0, st->arch); + st->prefilter_and_fold = 0; + /* Skip regular PLC until we get two consecutive packets. */ + st->skip_plc = 1; + } else { + int exc_length; + /* Pitch-based PLC */ + const opus_val16 *window; + opus_val16 *exc; + opus_val16 fade = Q15ONE; + int pitch_index; + VARDECL(opus_val16, _exc); + VARDECL(opus_val16, fir_tmp); + + if (loss_duration == 0) + { +#ifdef ENABLE_DEEP_PLC + if (lpcnet->loaded) update_plc_state(lpcnet, decode_mem, &st->plc_preemphasis_mem, C); +#endif + st->last_pitch_index = pitch_index = celt_plc_pitch_search(decode_mem, C, st->arch); + } else { + pitch_index = st->last_pitch_index; + fade = QCONST16(.8f,15); + } + + /* We want the excitation for 2 pitch periods in order to look for a + decaying signal, but we can't get more than MAX_PERIOD. */ + exc_length = IMIN(2*pitch_index, MAX_PERIOD); + + ALLOC(_exc, MAX_PERIOD+CELT_LPC_ORDER, opus_val16); + ALLOC(fir_tmp, exc_length, opus_val16); + exc = _exc+CELT_LPC_ORDER; + window = mode->window; + c=0; do { + opus_val16 decay; + opus_val16 attenuation; + opus_val32 S1=0; + celt_sig *buf; + int extrapolation_offset; + int extrapolation_len; + int j; + + buf = decode_mem[c]; + for (i=0;iarch); + /* Add a noise floor of -40 dB. */ +#ifdef FIXED_POINT + ac[0] += SHR32(ac[0],13); +#else + ac[0] *= 1.0001f; +#endif + /* Use lag windowing to stabilize the Levinson-Durbin recursion. */ + for (i=1;i<=CELT_LPC_ORDER;i++) + { + /*ac[i] *= exp(-.5*(2*M_PI*.002*i)*(2*M_PI*.002*i));*/ +#ifdef FIXED_POINT + ac[i] -= MULT16_32_Q15(2*i*i, ac[i]); +#else + ac[i] -= ac[i]*(0.008f*0.008f)*i*i; +#endif + } + _celt_lpc(lpc+c*CELT_LPC_ORDER, ac, CELT_LPC_ORDER); +#ifdef FIXED_POINT + /* For fixed-point, apply bandwidth expansion until we can guarantee that + no overflow can happen in the IIR filter. This means: + 32768*sum(abs(filter)) < 2^31 */ + while (1) { + opus_val16 tmp=Q15ONE; + opus_val32 sum=QCONST16(1., SIG_SHIFT); + for (i=0;iarch); + OPUS_COPY(exc+MAX_PERIOD-exc_length, fir_tmp, exc_length); + } + + /* Check if the waveform is decaying, and if so how fast. + We do this to avoid adding energy when concealing in a segment + with decaying energy. */ + { + opus_val32 E1=1, E2=1; + int decay_length; +#ifdef FIXED_POINT + int shift = IMAX(0,2*celt_zlog2(celt_maxabs16(&exc[MAX_PERIOD-exc_length], exc_length))-20); +#endif + decay_length = exc_length>>1; + for (i=0;i= pitch_index) { + j -= pitch_index; + attenuation = MULT16_16_Q15(attenuation, decay); + } + buf[DECODE_BUFFER_SIZE-N+i] = + SHL32(EXTEND32(MULT16_16_Q15(attenuation, + exc[extrapolation_offset+j])), SIG_SHIFT); + /* Compute the energy of the previously decoded signal whose + excitation we're copying. */ + tmp = SROUND16( + buf[DECODE_BUFFER_SIZE-MAX_PERIOD-N+extrapolation_offset+j], + SIG_SHIFT); + S1 += SHR32(MULT16_16(tmp, tmp), 10); + } + { + opus_val16 lpc_mem[CELT_LPC_ORDER]; + /* Copy the last decoded samples (prior to the overlap region) to + synthesis filter memory so we can have a continuous signal. */ + for (i=0;iarch); +#ifdef FIXED_POINT + for (i=0; i < extrapolation_len; i++) + buf[DECODE_BUFFER_SIZE-N+i] = SATURATE(buf[DECODE_BUFFER_SIZE-N+i], SIG_SAT); +#endif + } + + /* Check if the synthesis energy is higher than expected, which can + happen with the signal changes during our window. If so, + attenuate. */ + { + opus_val32 S2=0; + for (i=0;i SHR32(S2,2))) +#else + /* The float test is written this way to catch NaNs in the output + of the IIR filter at the same time. */ + if (!(S1 > 0.2f*S2)) +#endif + { + for (i=0;iloaded && (st->complexity >= 5 || lpcnet->fec_fill_pos > 0)) { + float overlap_mem; + int samples_needed16k; + celt_sig *buf; + VARDECL(float, buf_copy); + buf = decode_mem[0]; + ALLOC(buf_copy, C*overlap, float); + c=0; do { + OPUS_COPY(buf_copy+c*overlap, &decode_mem[c][DECODE_BUFFER_SIZE-N], overlap); + } while (++cplc_fill = 0; + } + while (st->plc_fill < samples_needed16k) { + lpcnet_plc_conceal(lpcnet, &st->plc_pcm[st->plc_fill]); + st->plc_fill += FRAME_SIZE; + } + /* Resample to 48 kHz. */ + for (i=0;i<(N+overlap)/3;i++) { + int j; + float sum; + for (sum=0, j=0;j<17;j++) sum += 3*st->plc_pcm[i+j]*sinc_filter[3*j]; + buf[DECODE_BUFFER_SIZE-N+3*i] = sum; + for (sum=0, j=0;j<16;j++) sum += 3*st->plc_pcm[i+j+1]*sinc_filter[3*j+2]; + buf[DECODE_BUFFER_SIZE-N+3*i+1] = sum; + for (sum=0, j=0;j<16;j++) sum += 3*st->plc_pcm[i+j+1]*sinc_filter[3*j+1]; + buf[DECODE_BUFFER_SIZE-N+3*i+2] = sum; + } + OPUS_MOVE(st->plc_pcm, &st->plc_pcm[N/3], st->plc_fill-N/3); + st->plc_fill -= N/3; + for (i=0;iplc_preemphasis_mem; + st->plc_preemphasis_mem = tmp; + } + overlap_mem = st->plc_preemphasis_mem; + for (i=0;iprefilter_and_fold = 1; + } + + /* Saturate to soemthing large to avoid wrap-around. */ + st->loss_duration = IMIN(10000, loss_duration+(1<channels; + int LM, M; + int start; + int end; + int effEnd; + int codedBands; + int alloc_trim; + int postfilter_pitch; + opus_val16 postfilter_gain; + int intensity=0; + int dual_stereo=0; + opus_int32 total_bits; + opus_int32 balance; + opus_int32 tell; + int dynalloc_logp; + int postfilter_tapset; + int anti_collapse_rsv; + int anti_collapse_on=0; + int silence; + int C = st->stream_channels; + const OpusCustomMode *mode; + int nbEBands; + int overlap; + const opus_int16 *eBands; + opus_val16 max_background_increase; + ALLOC_STACK; + + VALIDATE_CELT_DECODER(st); + mode = st->mode; + nbEBands = mode->nbEBands; + overlap = mode->overlap; + eBands = mode->eBands; + start = st->start; + end = st->end; + frame_size *= st->downsample; + + lpc = (opus_val16*)(st->_decode_mem+(DECODE_BUFFER_SIZE+overlap)*CC); + oldBandE = lpc+CC*CELT_LPC_ORDER; + oldLogE = oldBandE + 2*nbEBands; + oldLogE2 = oldLogE + 2*nbEBands; + backgroundLogE = oldLogE2 + 2*nbEBands; + +#ifdef CUSTOM_MODES + if (st->signalling && data!=NULL) + { + int data0=data[0]; + /* Convert "standard mode" to Opus header */ + if (mode->Fs==48000 && mode->shortMdctSize==120) + { + data0 = fromOpus(data0); + if (data0<0) + return OPUS_INVALID_PACKET; + } + st->end = end = IMAX(1, mode->effEBands-2*(data0>>5)); + LM = (data0>>3)&0x3; + C = 1 + ((data0>>2)&0x1); + data++; + len--; + if (LM>mode->maxLM) + return OPUS_INVALID_PACKET; + if (frame_size < mode->shortMdctSize<shortMdctSize<maxLM;LM++) + if (mode->shortMdctSize<mode->maxLM) + return OPUS_BAD_ARG; + } + M=1<1275 || pcm==NULL) + return OPUS_BAD_ARG; + + N = M*mode->shortMdctSize; + c=0; do { + decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap); + out_syn[c] = decode_mem[c]+DECODE_BUFFER_SIZE-N; + } while (++c mode->effEBands) + effEnd = mode->effEBands; + + if (data == NULL || len<=1) + { + celt_decode_lost(st, N, LM +#ifdef ENABLE_DEEP_PLC + , lpcnet +#endif + ); + deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, accum); + RESTORE_STACK; + return frame_size/st->downsample; + } +#ifdef ENABLE_DEEP_PLC + else { + /* FIXME: This is a bit of a hack just to make sure opus_decode_native() knows we're no longer in PLC. */ + if (lpcnet) lpcnet->blend = 0; + } +#endif + + /* Check if there are at least two packets received consecutively before + * turning on the pitch-based PLC */ + if (st->loss_duration == 0) st->skip_plc = 0; + + if (dec == NULL) + { + ec_dec_init(&_dec,(unsigned char*)data,len); + dec = &_dec; + } + + if (C==1) + { + for (i=0;i= total_bits) + silence = 1; + else if (tell==1) + silence = ec_dec_bit_logp(dec, 15); + else + silence = 0; + if (silence) + { + /* Pretend we've read all the remaining bits */ + tell = len*8; + dec->nbits_total+=tell-ec_tell(dec); + } + + postfilter_gain = 0; + postfilter_pitch = 0; + postfilter_tapset = 0; + if (start==0 && tell+16 <= total_bits) + { + if(ec_dec_bit_logp(dec, 1)) + { + int qg, octave; + octave = ec_dec_uint(dec, 6); + postfilter_pitch = (16< 0 && tell+3 <= total_bits) + { + isTransient = ec_dec_bit_logp(dec, 3); + tell = ec_tell(dec); + } + else + isTransient = 0; + + if (isTransient) + shortBlocks = M; + else + shortBlocks = 0; + + /* Decode the global flags (first symbols in the stream) */ + intra_ener = tell+3<=total_bits ? ec_dec_bit_logp(dec, 3) : 0; + /* If recovering from packet loss, make sure we make the energy prediction safe to reduce the + risk of getting loud artifacts. */ + if (!intra_ener && st->loss_duration != 0) { + c=0; do + { + opus_val16 safety = 0; + int missing = IMIN(10, st->loss_duration>>LM); + if (LM==0) safety = QCONST16(1.5f,DB_SHIFT); + else if (LM==1) safety = QCONST16(.5f,DB_SHIFT); + for (i=start;i0) + dynalloc_logp = IMAX(2, dynalloc_logp-1); + } + + ALLOC(fine_quant, nbEBands, int); + alloc_trim = tell+(6<=2&&bits>=((LM+2)<rng, 0, + st->arch, st->disable_inv); + + if (anti_collapse_rsv > 0) + { + anti_collapse_on = ec_dec_bits(dec, 1); + } + + unquant_energy_finalise(mode, start, end, oldBandE, + fine_quant, fine_priority, len*8-ec_tell(dec), dec, C); + + if (anti_collapse_on) + anti_collapse(mode, X, collapse_masks, LM, C, N, + start, end, oldBandE, oldLogE, oldLogE2, pulses, st->rng, st->arch); + + if (silence) + { + for (i=0;iprefilter_and_fold) { + prefilter_and_fold(st, N); + } + celt_synthesis(mode, X, out_syn, oldBandE, start, effEnd, + C, CC, isTransient, LM, st->downsample, silence, st->arch); + + c=0; do { + st->postfilter_period=IMAX(st->postfilter_period, COMBFILTER_MINPERIOD); + st->postfilter_period_old=IMAX(st->postfilter_period_old, COMBFILTER_MINPERIOD); + comb_filter(out_syn[c], out_syn[c], st->postfilter_period_old, st->postfilter_period, mode->shortMdctSize, + st->postfilter_gain_old, st->postfilter_gain, st->postfilter_tapset_old, st->postfilter_tapset, + mode->window, overlap, st->arch); + if (LM!=0) + comb_filter(out_syn[c]+mode->shortMdctSize, out_syn[c]+mode->shortMdctSize, st->postfilter_period, postfilter_pitch, N-mode->shortMdctSize, + st->postfilter_gain, postfilter_gain, st->postfilter_tapset, postfilter_tapset, + mode->window, overlap, st->arch); + + } while (++cpostfilter_period_old = st->postfilter_period; + st->postfilter_gain_old = st->postfilter_gain; + st->postfilter_tapset_old = st->postfilter_tapset; + st->postfilter_period = postfilter_pitch; + st->postfilter_gain = postfilter_gain; + st->postfilter_tapset = postfilter_tapset; + if (LM!=0) + { + st->postfilter_period_old = st->postfilter_period; + st->postfilter_gain_old = st->postfilter_gain; + st->postfilter_tapset_old = st->postfilter_tapset; + } + + if (C==1) + OPUS_COPY(&oldBandE[nbEBands], oldBandE, nbEBands); + + if (!isTransient) + { + OPUS_COPY(oldLogE2, oldLogE, 2*nbEBands); + OPUS_COPY(oldLogE, oldBandE, 2*nbEBands); + } else { + for (i=0;i<2*nbEBands;i++) + oldLogE[i] = MIN16(oldLogE[i], oldBandE[i]); + } + /* In normal circumstances, we only allow the noise floor to increase by + up to 2.4 dB/second, but when we're in DTX we give the weight of + all missing packets to the update packet. */ + max_background_increase = IMIN(160, st->loss_duration+M)*QCONST16(0.001f,DB_SHIFT); + for (i=0;i<2*nbEBands;i++) + backgroundLogE[i] = MIN16(backgroundLogE[i] + max_background_increase, oldBandE[i]); + /* In case start or end were to change */ + c=0; do + { + for (i=0;irng = dec->rng; + + deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, accum); + st->loss_duration = 0; + st->prefilter_and_fold = 0; + RESTORE_STACK; + if (ec_tell(dec) > 8*len) + return OPUS_INTERNAL_ERROR; + if(ec_get_error(dec)) + st->error = 1; + return frame_size/st->downsample; +} + +int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, + int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum) +{ + return celt_decode_with_ec_dred(st, data, len, pcm, frame_size, dec, accum +#ifdef ENABLE_DEEP_PLC + , NULL +#endif + ); +} + +#ifdef CUSTOM_MODES + +#ifdef FIXED_POINT +int opus_custom_decode(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, int len, opus_int16 * OPUS_RESTRICT pcm, int frame_size) +{ + return celt_decode_with_ec(st, data, len, pcm, frame_size, NULL, 0); +} + +#ifndef DISABLE_FLOAT_API +int opus_custom_decode_float(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, int len, float * OPUS_RESTRICT pcm, int frame_size) +{ + int j, ret, C, N; + VARDECL(opus_int16, out); + ALLOC_STACK; + + if (pcm==NULL) + return OPUS_BAD_ARG; + + C = st->channels; + N = frame_size; + + ALLOC(out, C*N, opus_int16); + ret=celt_decode_with_ec(st, data, len, out, frame_size, NULL, 0); + if (ret>0) + for (j=0;jchannels; + N = frame_size; + ALLOC(out, C*N, celt_sig); + + ret=celt_decode_with_ec(st, data, len, out, frame_size, NULL, 0); + + if (ret>0) + for (j=0;j10) + { + goto bad_arg; + } + st->complexity = value; + } + break; + case OPUS_GET_COMPLEXITY_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->complexity; + } + break; + case CELT_SET_START_BAND_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<0 || value>=st->mode->nbEBands) + goto bad_arg; + st->start = value; + } + break; + case CELT_SET_END_BAND_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<1 || value>st->mode->nbEBands) + goto bad_arg; + st->end = value; + } + break; + case CELT_SET_CHANNELS_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<1 || value>2) + goto bad_arg; + st->stream_channels = value; + } + break; + case CELT_GET_AND_CLEAR_ERROR_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (value==NULL) + goto bad_arg; + *value=st->error; + st->error = 0; + } + break; + case OPUS_GET_LOOKAHEAD_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (value==NULL) + goto bad_arg; + *value = st->overlap/st->downsample; + } + break; + case OPUS_RESET_STATE: + { + int i; + opus_val16 *lpc, *oldBandE, *oldLogE, *oldLogE2; + lpc = (opus_val16*)(st->_decode_mem+(DECODE_BUFFER_SIZE+st->overlap)*st->channels); + oldBandE = lpc+st->channels*CELT_LPC_ORDER; + oldLogE = oldBandE + 2*st->mode->nbEBands; + oldLogE2 = oldLogE + 2*st->mode->nbEBands; + OPUS_CLEAR((char*)&st->DECODER_RESET_START, + opus_custom_decoder_get_size(st->mode, st->channels)- + ((char*)&st->DECODER_RESET_START - (char*)st)); + for (i=0;i<2*st->mode->nbEBands;i++) + oldLogE[i]=oldLogE2[i]=-QCONST16(28.f,DB_SHIFT); + st->skip_plc = 1; + } + break; + case OPUS_GET_PITCH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (value==NULL) + goto bad_arg; + *value = st->postfilter_period; + } + break; + case CELT_GET_MODE_REQUEST: + { + const CELTMode ** value = va_arg(ap, const CELTMode**); + if (value==0) + goto bad_arg; + *value=st->mode; + } + break; + case CELT_SET_SIGNALLING_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->signalling = value; + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + opus_uint32 * value = va_arg(ap, opus_uint32 *); + if (value==0) + goto bad_arg; + *value=st->rng; + } + break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->disable_inv = value; + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->disable_inv; + } + break; + default: + goto bad_request; + } + va_end(ap); + return OPUS_OK; +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +bad_request: + va_end(ap); + return OPUS_UNIMPLEMENTED; +} diff --git a/port/third_party/opus/celt/celt_encoder.c b/port/third_party/opus/celt/celt_encoder.c new file mode 100644 index 00000000..7f32a801 --- /dev/null +++ b/port/third_party/opus/celt/celt_encoder.c @@ -0,0 +1,2639 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2010 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define CELT_ENCODER_C + +#include "cpu_support.h" +#include "os_support.h" +#include "mdct.h" +#include +#include "celt.h" +#include "pitch.h" +#include "bands.h" +#include "modes.h" +#include "entcode.h" +#include "quant_bands.h" +#include "rate.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "float_cast.h" +#include +#include "celt_lpc.h" +#include "vq.h" + + +/** Encoder state + @brief Encoder state + */ +struct OpusCustomEncoder { + const OpusCustomMode *mode; /**< Mode used by the encoder */ + int channels; + int stream_channels; + + int force_intra; + int clip; + int disable_pf; + int complexity; + int upsample; + int start, end; + + opus_int32 bitrate; + int vbr; + int signalling; + int constrained_vbr; /* If zero, VBR can do whatever it likes with the rate */ + int loss_rate; + int lsb_depth; + int lfe; + int disable_inv; + int arch; + + /* Everything beyond this point gets cleared on a reset */ +#define ENCODER_RESET_START rng + + opus_uint32 rng; + int spread_decision; + opus_val32 delayedIntra; + int tonal_average; + int lastCodedBands; + int hf_average; + int tapset_decision; + + int prefilter_period; + opus_val16 prefilter_gain; + int prefilter_tapset; +#ifdef RESYNTH + int prefilter_period_old; + opus_val16 prefilter_gain_old; + int prefilter_tapset_old; +#endif + int consec_transient; + AnalysisInfo analysis; + SILKInfo silk_info; + + opus_val32 preemph_memE[2]; + opus_val32 preemph_memD[2]; + + /* VBR-related parameters */ + opus_int32 vbr_reservoir; + opus_int32 vbr_drift; + opus_int32 vbr_offset; + opus_int32 vbr_count; + opus_val32 overlap_max; + opus_val16 stereo_saving; + int intensity; + opus_val16 *energy_mask; + opus_val16 spec_avg; + +#ifdef RESYNTH + /* +MAX_PERIOD/2 to make space for overlap */ + celt_sig syn_mem[2][2*MAX_PERIOD+MAX_PERIOD/2]; +#endif + + celt_sig in_mem[1]; /* Size = channels*mode->overlap */ + /* celt_sig prefilter_mem[], Size = channels*COMBFILTER_MAXPERIOD */ + /* opus_val16 oldBandE[], Size = channels*mode->nbEBands */ + /* opus_val16 oldLogE[], Size = channels*mode->nbEBands */ + /* opus_val16 oldLogE2[], Size = channels*mode->nbEBands */ + /* opus_val16 energyError[], Size = channels*mode->nbEBands */ +}; + +int celt_encoder_get_size(int channels) +{ + CELTMode *mode = opus_custom_mode_create(48000, 960, NULL); + return opus_custom_encoder_get_size(mode, channels); +} + +OPUS_CUSTOM_NOSTATIC int opus_custom_encoder_get_size(const CELTMode *mode, int channels) +{ + int size = sizeof(struct CELTEncoder) + + (channels*mode->overlap-1)*sizeof(celt_sig) /* celt_sig in_mem[channels*mode->overlap]; */ + + channels*COMBFILTER_MAXPERIOD*sizeof(celt_sig) /* celt_sig prefilter_mem[channels*COMBFILTER_MAXPERIOD]; */ + + 4*channels*mode->nbEBands*sizeof(opus_val16); /* opus_val16 oldBandE[channels*mode->nbEBands]; */ + /* opus_val16 oldLogE[channels*mode->nbEBands]; */ + /* opus_val16 oldLogE2[channels*mode->nbEBands]; */ + /* opus_val16 energyError[channels*mode->nbEBands]; */ + return size; +} + +#ifdef CUSTOM_MODES +CELTEncoder *opus_custom_encoder_create(const CELTMode *mode, int channels, int *error) +{ + int ret; + CELTEncoder *st = (CELTEncoder *)opus_alloc(opus_custom_encoder_get_size(mode, channels)); + /* init will handle the NULL case */ + ret = opus_custom_encoder_init(st, mode, channels); + if (ret != OPUS_OK) + { + opus_custom_encoder_destroy(st); + st = NULL; + } + if (error) + *error = ret; + return st; +} +#endif /* CUSTOM_MODES */ + +static int opus_custom_encoder_init_arch(CELTEncoder *st, const CELTMode *mode, + int channels, int arch) +{ + if (channels < 0 || channels > 2) + return OPUS_BAD_ARG; + + if (st==NULL || mode==NULL) + return OPUS_ALLOC_FAIL; + + OPUS_CLEAR((char*)st, opus_custom_encoder_get_size(mode, channels)); + + st->mode = mode; + st->stream_channels = st->channels = channels; + + st->upsample = 1; + st->start = 0; + st->end = st->mode->effEBands; + st->signalling = 1; + st->arch = arch; + + st->constrained_vbr = 1; + st->clip = 1; + + st->bitrate = OPUS_BITRATE_MAX; + st->vbr = 0; + st->force_intra = 0; + st->complexity = 5; + st->lsb_depth=24; + + opus_custom_encoder_ctl(st, OPUS_RESET_STATE); + + return OPUS_OK; +} + +#ifdef CUSTOM_MODES +int opus_custom_encoder_init(CELTEncoder *st, const CELTMode *mode, int channels) +{ + return opus_custom_encoder_init_arch(st, mode, channels, opus_select_arch()); +} +#endif + +int celt_encoder_init(CELTEncoder *st, opus_int32 sampling_rate, int channels, + int arch) +{ + int ret; + ret = opus_custom_encoder_init_arch(st, + opus_custom_mode_create(48000, 960, NULL), channels, arch); + if (ret != OPUS_OK) + return ret; + st->upsample = resampling_factor(sampling_rate); + return OPUS_OK; +} + +#ifdef CUSTOM_MODES +void opus_custom_encoder_destroy(CELTEncoder *st) +{ + opus_free(st); +} +#endif /* CUSTOM_MODES */ + + +static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int C, + opus_val16 *tf_estimate, int *tf_chan, int allow_weak_transients, + int *weak_transient) +{ + int i; + VARDECL(opus_val16, tmp); + opus_val32 mem0,mem1; + int is_transient = 0; + opus_int32 mask_metric = 0; + int c; + opus_val16 tf_max; + int len2; + /* Forward masking: 6.7 dB/ms. */ +#ifdef FIXED_POINT + int forward_shift = 4; +#else + opus_val16 forward_decay = QCONST16(.0625f,15); +#endif + /* Table of 6*64/x, trained on real data to minimize the average error */ + static const unsigned char inv_table[128] = { + 255,255,156,110, 86, 70, 59, 51, 45, 40, 37, 33, 31, 28, 26, 25, + 23, 22, 21, 20, 19, 18, 17, 16, 16, 15, 15, 14, 13, 13, 12, 12, + 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 9, 9, 8, 8, + 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, + }; + SAVE_STACK; + ALLOC(tmp, len, opus_val16); + + *weak_transient = 0; + /* For lower bitrates, let's be more conservative and have a forward masking + decay of 3.3 dB/ms. This avoids having to code transients at very low + bitrate (mostly for hybrid), which can result in unstable energy and/or + partial collapse. */ + if (allow_weak_transients) + { +#ifdef FIXED_POINT + forward_shift = 5; +#else + forward_decay = QCONST16(.03125f,15); +#endif + } + len2=len/2; + for (c=0;c=0;i--) + { + /* Backward masking: 13.9 dB/ms. */ +#ifdef FIXED_POINT + /* FIXME: Use PSHR16() instead */ + tmp[i] = mem0 + PSHR32(tmp[i]-mem0,3); + mem0 = tmp[i]; + maxE = MAX16(maxE, mem0); +#else + mem0 = tmp[i] + 0.875f*mem0; + tmp[i] = 0.125f*mem0; + maxE = MAX16(maxE, 0.125f*mem0); +#endif + } + /*for (i=0;i>1))); +#else + mean = celt_sqrt(mean * maxE*.5*len2); +#endif + /* Inverse of the mean energy in Q15+6 */ + norm = SHL32(EXTEND32(len2),6+14)/ADD32(EPSILON,SHR32(mean,1)); + /* Compute harmonic mean discarding the unreliable boundaries + The data is smooth, so we only take 1/4th of the samples */ + unmask=0; + /* We should never see NaNs here. If we find any, then something really bad happened and we better abort + before it does any damage later on. If these asserts are disabled (no hardening), then the table + lookup a few lines below (id = ...) is likely to crash dur to an out-of-bounds read. DO NOT FIX + that crash on NaN since it could result in a worse issue later on. */ + celt_assert(!celt_isnan(tmp[0])); + celt_assert(!celt_isnan(norm)); + for (i=12;imask_metric) + { + *tf_chan = c; + mask_metric = unmask; + } + } + is_transient = mask_metric>200; + /* For low bitrates, define "weak transients" that need to be + handled differently to avoid partial collapse. */ + if (allow_weak_transients && is_transient && mask_metric<600) { + is_transient = 0; + *weak_transient = 1; + } + /* Arbitrary metric for VBR boost */ + tf_max = MAX16(0,celt_sqrt(27*mask_metric)-42); + /* *tf_estimate = 1 + MIN16(1, sqrt(MAX16(0, tf_max-30))/20); */ + *tf_estimate = celt_sqrt(MAX32(0, SHL32(MULT16_16(QCONST16(0.0069,14),MIN16(163,tf_max)),14)-QCONST32(0.139,28))); + /*printf("%d %f\n", tf_max, mask_metric);*/ + RESTORE_STACK; +#ifdef FUZZING + is_transient = rand()&0x1; +#endif + /*printf("%d %f %d\n", is_transient, (float)*tf_estimate, tf_max);*/ + return is_transient; +} + +/* Looks for sudden increases of energy to decide whether we need to patch + the transient decision */ +static int patch_transient_decision(opus_val16 *newE, opus_val16 *oldE, int nbEBands, + int start, int end, int C) +{ + int i, c; + opus_val32 mean_diff=0; + opus_val16 spread_old[26]; + /* Apply an aggressive (-6 dB/Bark) spreading function to the old frame to + avoid false detection caused by irrelevant bands */ + if (C==1) + { + spread_old[start] = oldE[start]; + for (i=start+1;i=start;i--) + spread_old[i] = MAX16(spread_old[i], spread_old[i+1]-QCONST16(1.0f, DB_SHIFT)); + /* Compute mean increase */ + c=0; do { + for (i=IMAX(2,start);i QCONST16(1.f, DB_SHIFT); +} + +/** Apply window and compute the MDCT for all sub-frames and + all channels in a frame */ +static void compute_mdcts(const CELTMode *mode, int shortBlocks, celt_sig * OPUS_RESTRICT in, + celt_sig * OPUS_RESTRICT out, int C, int CC, int LM, int upsample, + int arch) +{ + const int overlap = mode->overlap; + int N; + int B; + int shift; + int i, b, c; + if (shortBlocks) + { + B = shortBlocks; + N = mode->shortMdctSize; + shift = mode->maxLM; + } else { + B = 1; + N = mode->shortMdctSize<maxLM-LM; + } + c=0; do { + for (b=0;bmdct, in+c*(B*N+overlap)+b*N, + &out[b+c*N*B], mode->window, overlap, shift, B, + arch); + } + } while (++ceBands[len]-m->eBands[len-1])<eBands[len]-m->eBands[len-1])<eBands[i+1]-m->eBands[i])<eBands[i+1]-m->eBands[i])==1; + OPUS_COPY(tmp, &X[tf_chan*N0 + (m->eBands[i]<eBands[i]<>LM, 1<>k, 1<=0;i--) + { + if (tf_res[i+1] == 1) + tf_res[i] = path1[i+1]; + else + tf_res[i] = path0[i+1]; + } + /*printf("%d %f\n", *tf_sum, tf_estimate);*/ + RESTORE_STACK; +#ifdef FUZZING + tf_select = rand()&0x1; + tf_res[0] = rand()&0x1; + for (i=1;istorage*8; + tell = ec_tell(enc); + logp = isTransient ? 2 : 4; + /* Reserve space to code the tf_select decision. */ + tf_select_rsv = LM>0 && tell+logp+1 <= budget; + budget -= tf_select_rsv; + curr = tf_changed = 0; + for (i=start;i> 10; + trim = QCONST16(4.f, 8) + QCONST16(1.f/16.f, 8)*frac; + } + if (C==2) + { + opus_val16 sum = 0; /* Q10 */ + opus_val16 minXC; /* Q10 */ + /* Compute inter-channel correlation for low frequencies */ + for (i=0;i<8;i++) + { + opus_val32 partial; + partial = celt_inner_prod(&X[m->eBands[i]<eBands[i]<eBands[i+1]-m->eBands[i])<eBands[i]<eBands[i]<eBands[i+1]-m->eBands[i])<nbEBands]*(opus_int32)(2+2*i-end); + } + } while (++cvalid) + { + trim -= MAX16(-QCONST16(2.f, 8), MIN16(QCONST16(2.f, 8), + (opus_val16)(QCONST16(2.f, 8)*(analysis->tonality_slope+.05f)))); + } +#else + (void)analysis; +#endif + +#ifdef FIXED_POINT + trim_index = PSHR32(trim, 8); +#else + trim_index = (int)floor(.5f+trim); +#endif + trim_index = IMAX(0, IMIN(10, trim_index)); + /*printf("%d\n", trim_index);*/ +#ifdef FUZZING + trim_index = rand()%11; +#endif + return trim_index; +} + +static int stereo_analysis(const CELTMode *m, const celt_norm *X, + int LM, int N0) +{ + int i; + int thetas; + opus_val32 sumLR = EPSILON, sumMS = EPSILON; + + /* Use the L1 norm to model the entropy of the L/R signal vs the M/S signal */ + for (i=0;i<13;i++) + { + int j; + for (j=m->eBands[i]<eBands[i+1]<eBands[13]<<(LM+1))+thetas, sumMS) + > MULT16_32_Q15(m->eBands[13]<<(LM+1), sumLR); +} + +#define MSWAP(a,b) do {opus_val16 tmp = a;a=b;b=tmp;} while(0) +static opus_val16 median_of_5(const opus_val16 *x) +{ + opus_val16 t0, t1, t2, t3, t4; + t2 = x[2]; + if (x[0] > x[1]) + { + t0 = x[1]; + t1 = x[0]; + } else { + t0 = x[0]; + t1 = x[1]; + } + if (x[3] > x[4]) + { + t3 = x[4]; + t4 = x[3]; + } else { + t3 = x[3]; + t4 = x[4]; + } + if (t0 > t3) + { + MSWAP(t0, t3); + MSWAP(t1, t4); + } + if (t2 > t1) + { + if (t1 < t3) + return MIN16(t2, t3); + else + return MIN16(t4, t1); + } else { + if (t2 < t3) + return MIN16(t1, t3); + else + return MIN16(t2, t4); + } +} + +static opus_val16 median_of_3(const opus_val16 *x) +{ + opus_val16 t0, t1, t2; + if (x[0] > x[1]) + { + t0 = x[1]; + t1 = x[0]; + } else { + t0 = x[0]; + t1 = x[1]; + } + t2 = x[2]; + if (t1 < t2) + return t1; + else if (t0 < t2) + return t2; + else + return t0; +} + +static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16 *bandLogE2, const opus_val16 *oldBandE, + int nbEBands, int start, int end, int C, int *offsets, int lsb_depth, const opus_int16 *logN, + int isTransient, int vbr, int constrained_vbr, const opus_int16 *eBands, int LM, + int effectiveBytes, opus_int32 *tot_boost_, int lfe, opus_val16 *surround_dynalloc, + AnalysisInfo *analysis, int *importance, int *spread_weight) +{ + int i, c; + opus_int32 tot_boost=0; + opus_val16 maxDepth; + VARDECL(opus_val16, follower); + VARDECL(opus_val16, noise_floor); + VARDECL(opus_val16, bandLogE3); + SAVE_STACK; + ALLOC(follower, C*nbEBands, opus_val16); + ALLOC(noise_floor, C*nbEBands, opus_val16); + ALLOC(bandLogE3, nbEBands, opus_val16); + OPUS_CLEAR(offsets, nbEBands); + /* Dynamic allocation code */ + maxDepth=-QCONST16(31.9f, DB_SHIFT); + for (i=0;i=0;i--) + mask[i] = MAX16(mask[i], mask[i+1] - QCONST16(3.f, DB_SHIFT)); + for (i=0;i> shift; + } + /*for (i=0;i= (30 + 5*LM) && !lfe) + { + int last=0; + c=0;do + { + opus_val16 offset; + opus_val16 tmp; + opus_val16 *f; + OPUS_COPY(bandLogE3, &bandLogE2[c*nbEBands], end); + if (LM==0) { + /* For 2.5 ms frames, the first 8 bands have just one bin, so the + energy is highly unreliable (high variance). For that reason, + we take the max with the previous energy so that at least 2 bins + are getting used. */ + for (i=0;i bandLogE3[i-1]+QCONST16(.5f,DB_SHIFT)) + last=i; + f[i] = MIN16(f[i-1]+QCONST16(1.5f,DB_SHIFT), bandLogE3[i]); + } + for (i=last-1;i>=0;i--) + f[i] = MIN16(f[i], MIN16(f[i+1]+QCONST16(2.f,DB_SHIFT), bandLogE3[i])); + + /* Combine with a median filter to avoid dynalloc triggering unnecessarily. + The "offset" value controls how conservative we are -- a higher offset + reduces the impact of the median filter and makes dynalloc use more bits. */ + offset = QCONST16(1.f, DB_SHIFT); + for (i=2;i=12) + follower[i] = HALF16(follower[i]); + } +#ifdef DISABLE_FLOAT_API + (void)analysis; +#else + if (analysis->valid) + { + for (i=start;ileak_boost[i]; + } +#endif + for (i=start;i 48) { + boost = (int)SHR32(EXTEND32(follower[i])*8,DB_SHIFT); + boost_bits = (boost*width<>BITRES>>3 > 2*effectiveBytes/3) + { + opus_int32 cap = ((2*effectiveBytes/3)<mode; + overlap = mode->overlap; + ALLOC(_pre, CC*(N+COMBFILTER_MAXPERIOD), celt_sig); + + pre[0] = _pre; + pre[1] = _pre + (N+COMBFILTER_MAXPERIOD); + + + c=0; do { + OPUS_COPY(pre[c], prefilter_mem+c*COMBFILTER_MAXPERIOD, COMBFILTER_MAXPERIOD); + OPUS_COPY(pre[c]+COMBFILTER_MAXPERIOD, in+c*(N+overlap)+overlap, N); + } while (++c>1, opus_val16); + + pitch_downsample(pre, pitch_buf, COMBFILTER_MAXPERIOD+N, CC, st->arch); + /* Don't search for the fir last 1.5 octave of the range because + there's too many false-positives due to short-term correlation */ + pitch_search(pitch_buf+(COMBFILTER_MAXPERIOD>>1), pitch_buf, N, + COMBFILTER_MAXPERIOD-3*COMBFILTER_MINPERIOD, &pitch_index, + st->arch); + pitch_index = COMBFILTER_MAXPERIOD-pitch_index; + + gain1 = remove_doubling(pitch_buf, COMBFILTER_MAXPERIOD, COMBFILTER_MINPERIOD, + N, &pitch_index, st->prefilter_period, st->prefilter_gain, st->arch); + if (pitch_index > COMBFILTER_MAXPERIOD-2) + pitch_index = COMBFILTER_MAXPERIOD-2; + gain1 = MULT16_16_Q15(QCONST16(.7f,15),gain1); + /*printf("%d %d %f %f\n", pitch_change, pitch_index, gain1, st->analysis.tonality);*/ + if (st->loss_rate>2) + gain1 = HALF32(gain1); + if (st->loss_rate>4) + gain1 = HALF32(gain1); + if (st->loss_rate>8) + gain1 = 0; + } else { + gain1 = 0; + pitch_index = COMBFILTER_MINPERIOD; + } +#ifndef DISABLE_FLOAT_API + if (analysis->valid) + gain1 = (opus_val16)(gain1 * analysis->max_pitch_ratio); +#else + (void)analysis; +#endif + /* Gain threshold for enabling the prefilter/postfilter */ + pf_threshold = QCONST16(.2f,15); + + /* Adjusting the threshold based on rate and continuity */ + if (abs(pitch_index-st->prefilter_period)*10>pitch_index) + pf_threshold += QCONST16(.2f,15); + if (nbAvailableBytes<25) + pf_threshold += QCONST16(.1f,15); + if (nbAvailableBytes<35) + pf_threshold += QCONST16(.1f,15); + if (st->prefilter_gain > QCONST16(.4f,15)) + pf_threshold -= QCONST16(.1f,15); + if (st->prefilter_gain > QCONST16(.55f,15)) + pf_threshold -= QCONST16(.1f,15); + + /* Hard threshold at 0.2 */ + pf_threshold = MAX16(pf_threshold, QCONST16(.2f,15)); + if (gain1prefilter_gain)prefilter_gain; + +#ifdef FIXED_POINT + qg = ((gain1+1536)>>10)/3-1; +#else + qg = (int)floor(.5f+gain1*32/3)-1; +#endif + qg = IMAX(0, IMIN(7, qg)); + gain1 = QCONST16(0.09375f,15)*(qg+1); + pf_on = 1; + } + /*printf("%d %f\n", pitch_index, gain1);*/ + + c=0; do { + int offset = mode->shortMdctSize-overlap; + st->prefilter_period=IMAX(st->prefilter_period, COMBFILTER_MINPERIOD); + OPUS_COPY(in+c*(N+overlap), st->in_mem+c*(overlap), overlap); + if (offset) + comb_filter(in+c*(N+overlap)+overlap, pre[c]+COMBFILTER_MAXPERIOD, + st->prefilter_period, st->prefilter_period, offset, -st->prefilter_gain, -st->prefilter_gain, + st->prefilter_tapset, st->prefilter_tapset, NULL, 0, st->arch); + + comb_filter(in+c*(N+overlap)+overlap+offset, pre[c]+COMBFILTER_MAXPERIOD+offset, + st->prefilter_period, pitch_index, N-offset, -st->prefilter_gain, -gain1, + st->prefilter_tapset, prefilter_tapset, mode->window, overlap, st->arch); + OPUS_COPY(st->in_mem+c*(overlap), in+c*(N+overlap)+N, overlap); + + if (N>COMBFILTER_MAXPERIOD) + { + OPUS_COPY(prefilter_mem+c*COMBFILTER_MAXPERIOD, pre[c]+N, COMBFILTER_MAXPERIOD); + } else { + OPUS_MOVE(prefilter_mem+c*COMBFILTER_MAXPERIOD, prefilter_mem+c*COMBFILTER_MAXPERIOD+N, COMBFILTER_MAXPERIOD-N); + OPUS_COPY(prefilter_mem+c*COMBFILTER_MAXPERIOD+COMBFILTER_MAXPERIOD-N, pre[c]+COMBFILTER_MAXPERIOD, N); + } + } while (++cnbEBands; + eBands = mode->eBands; + + coded_bands = lastCodedBands ? lastCodedBands : nbEBands; + coded_bins = eBands[coded_bands]<analysis.activity, st->analysis.tonality, tf_estimate, st->stereo_saving, tot_boost, coded_bands);*/ +#ifndef DISABLE_FLOAT_API + if (analysis->valid && analysis->activity<.4) + target -= (opus_int32)((coded_bins<activity)); +#endif + /* Stereo savings */ + if (C==2) + { + int coded_stereo_bands; + int coded_stereo_dof; + opus_val16 max_frac; + coded_stereo_bands = IMIN(intensity, coded_bands); + coded_stereo_dof = (eBands[coded_stereo_bands]<valid && !lfe) + { + opus_int32 tonal_target; + float tonal; + + /* Tonality boost (compensating for the average). */ + tonal = MAX16(0.f,analysis->tonality-.15f)-0.12f; + tonal_target = target + (opus_int32)((coded_bins<tonality, tonal);*/ + target = tonal_target; + } +#else + (void)analysis; + (void)pitch_change; +#endif + + if (has_surround_mask&&!lfe) + { + opus_int32 surround_target = target + (opus_int32)SHR32(MULT16_16(surround_masking,coded_bins<end, st->intensity, surround_target, target, st->bitrate);*/ + target = IMAX(target/4, surround_target); + } + + { + opus_int32 floor_depth; + int bins; + bins = eBands[nbEBands-2]<>2); + target = IMIN(target, floor_depth); + /*printf("%f %d\n", maxDepth, floor_depth);*/ + } + + /* Make VBR less aggressive for constrained VBR because we can't keep a higher bitrate + for long. Needs tuning. */ + if ((!has_surround_mask||lfe) && constrained_vbr) + { + target = base_target + (opus_int32)MULT16_32_Q15(QCONST16(0.67f, 15), target-base_target); + } + + if (!has_surround_mask && tf_estimate < QCONST16(.2f, 14)) + { + opus_val16 amount; + opus_val16 tvbr_factor; + amount = MULT16_16_Q15(QCONST16(.0000031f, 30), IMAX(0, IMIN(32000, 96000-bitrate))); + tvbr_factor = SHR32(MULT16_16(temporal_vbr, amount), DB_SHIFT); + target += (opus_int32)MULT16_32_Q15(tvbr_factor, target); + } + + /* Don't allow more than doubling the rate */ + target = IMIN(2*base_target, target); + + return target; +} + +int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes, ec_enc *enc) +{ + int i, c, N; + opus_int32 bits; + ec_enc _enc; + VARDECL(celt_sig, in); + VARDECL(celt_sig, freq); + VARDECL(celt_norm, X); + VARDECL(celt_ener, bandE); + VARDECL(opus_val16, bandLogE); + VARDECL(opus_val16, bandLogE2); + VARDECL(int, fine_quant); + VARDECL(opus_val16, error); + VARDECL(int, pulses); + VARDECL(int, cap); + VARDECL(int, offsets); + VARDECL(int, importance); + VARDECL(int, spread_weight); + VARDECL(int, fine_priority); + VARDECL(int, tf_res); + VARDECL(unsigned char, collapse_masks); + celt_sig *prefilter_mem; + opus_val16 *oldBandE, *oldLogE, *oldLogE2, *energyError; + int shortBlocks=0; + int isTransient=0; + const int CC = st->channels; + const int C = st->stream_channels; + int LM, M; + int tf_select; + int nbFilledBytes, nbAvailableBytes; + int start; + int end; + int effEnd; + int codedBands; + int alloc_trim; + int pitch_index=COMBFILTER_MINPERIOD; + opus_val16 gain1 = 0; + int dual_stereo=0; + int effectiveBytes; + int dynalloc_logp; + opus_int32 vbr_rate; + opus_int32 total_bits; + opus_int32 total_boost; + opus_int32 balance; + opus_int32 tell; + opus_int32 tell0_frac; + int prefilter_tapset=0; + int pf_on; + int anti_collapse_rsv; + int anti_collapse_on=0; + int silence=0; + int tf_chan = 0; + opus_val16 tf_estimate; + int pitch_change=0; + opus_int32 tot_boost; + opus_val32 sample_max; + opus_val16 maxDepth; + const OpusCustomMode *mode; + int nbEBands; + int overlap; + const opus_int16 *eBands; + int secondMdct; + int signalBandwidth; + int transient_got_disabled=0; + opus_val16 surround_masking=0; + opus_val16 temporal_vbr=0; + opus_val16 surround_trim = 0; + opus_int32 equiv_rate; + int hybrid; + int weak_transient = 0; + int enable_tf_analysis; + VARDECL(opus_val16, surround_dynalloc); + ALLOC_STACK; + + mode = st->mode; + nbEBands = mode->nbEBands; + overlap = mode->overlap; + eBands = mode->eBands; + start = st->start; + end = st->end; + hybrid = start != 0; + tf_estimate = 0; + if (nbCompressedBytes<2 || pcm==NULL) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + frame_size *= st->upsample; + for (LM=0;LM<=mode->maxLM;LM++) + if (mode->shortMdctSize<mode->maxLM) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + M=1<shortMdctSize; + + prefilter_mem = st->in_mem+CC*(overlap); + oldBandE = (opus_val16*)(st->in_mem+CC*(overlap+COMBFILTER_MAXPERIOD)); + oldLogE = oldBandE + CC*nbEBands; + oldLogE2 = oldLogE + CC*nbEBands; + energyError = oldLogE2 + CC*nbEBands; + + if (enc==NULL) + { + tell0_frac=tell=1; + nbFilledBytes=0; + } else { + tell0_frac=ec_tell_frac(enc); + tell=ec_tell(enc); + nbFilledBytes=(tell+4)>>3; + } + +#ifdef CUSTOM_MODES + if (st->signalling && enc==NULL) + { + int tmp = (mode->effEBands-end)>>1; + end = st->end = IMAX(1, mode->effEBands-tmp); + compressed[0] = tmp<<5; + compressed[0] |= LM<<3; + compressed[0] |= (C==2)<<2; + /* Convert "standard mode" to Opus header */ + if (mode->Fs==48000 && mode->shortMdctSize==120) + { + int c0 = toOpus(compressed[0]); + if (c0<0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + compressed[0] = c0; + } + compressed++; + nbCompressedBytes--; + } +#else + celt_assert(st->signalling==0); +#endif + + /* Can't produce more than 1275 output bytes */ + nbCompressedBytes = IMIN(nbCompressedBytes,1275); + nbAvailableBytes = nbCompressedBytes - nbFilledBytes; + + if (st->vbr && st->bitrate!=OPUS_BITRATE_MAX) + { + opus_int32 den=mode->Fs>>BITRES; + vbr_rate=(st->bitrate*frame_size+(den>>1))/den; +#ifdef CUSTOM_MODES + if (st->signalling) + vbr_rate -= 8<>(3+BITRES); + } else { + opus_int32 tmp; + vbr_rate = 0; + tmp = st->bitrate*frame_size; + if (tell>1) + tmp += tell*mode->Fs; + if (st->bitrate!=OPUS_BITRATE_MAX) + { + nbCompressedBytes = IMAX(2, IMIN(nbCompressedBytes, + (tmp+4*mode->Fs)/(8*mode->Fs)-!!st->signalling)); + ec_enc_shrink(enc, nbCompressedBytes); + } + effectiveBytes = nbCompressedBytes - nbFilledBytes; + } + equiv_rate = ((opus_int32)nbCompressedBytes*8*50 << (3-LM)) - (40*C+20)*((400>>LM) - 50); + if (st->bitrate != OPUS_BITRATE_MAX) + equiv_rate = IMIN(equiv_rate, st->bitrate - (40*C+20)*((400>>LM) - 50)); + + if (enc==NULL) + { + ec_enc_init(&_enc, compressed, nbCompressedBytes); + enc = &_enc; + } + + if (vbr_rate>0) + { + /* Computes the max bit-rate allowed in VBR mode to avoid violating the + target rate and buffering. + We must do this up front so that bust-prevention logic triggers + correctly if we don't have enough bits. */ + if (st->constrained_vbr) + { + opus_int32 vbr_bound; + opus_int32 max_allowed; + /* We could use any multiple of vbr_rate as bound (depending on the + delay). + This is clamped to ensure we use at least two bytes if the encoder + was entirely empty, but to allow 0 in hybrid mode. */ + vbr_bound = vbr_rate; + max_allowed = IMIN(IMAX(tell==1?2:0, + (vbr_rate+vbr_bound-st->vbr_reservoir)>>(BITRES+3)), + nbAvailableBytes); + if(max_allowed < nbAvailableBytes) + { + nbCompressedBytes = nbFilledBytes+max_allowed; + nbAvailableBytes = max_allowed; + ec_enc_shrink(enc, nbCompressedBytes); + } + } + } + total_bits = nbCompressedBytes*8; + + effEnd = end; + if (effEnd > mode->effEBands) + effEnd = mode->effEBands; + + ALLOC(in, CC*(N+overlap), celt_sig); + + sample_max=MAX32(st->overlap_max, celt_maxabs16(pcm, C*(N-overlap)/st->upsample)); + st->overlap_max=celt_maxabs16(pcm+C*(N-overlap)/st->upsample, C*overlap/st->upsample); + sample_max=MAX32(sample_max, st->overlap_max); +#ifdef FIXED_POINT + silence = (sample_max==0); +#else + silence = (sample_max <= (opus_val16)1/(1<lsb_depth)); +#endif +#ifdef FUZZING + if ((rand()&0x3F)==0) + silence = 1; +#endif + if (tell==1) + ec_enc_bit_logp(enc, silence, 15); + else + silence=0; + if (silence) + { + /*In VBR mode there is no need to send more than the minimum. */ + if (vbr_rate>0) + { + effectiveBytes=nbCompressedBytes=IMIN(nbCompressedBytes, nbFilledBytes+2); + total_bits=nbCompressedBytes*8; + nbAvailableBytes=2; + ec_enc_shrink(enc, nbCompressedBytes); + } + /* Pretend we've filled all the remaining bits with zeros + (that's what the initialiser did anyway) */ + tell = nbCompressedBytes*8; + enc->nbits_total+=tell-ec_tell(enc); + } + c=0; do { + int need_clip=0; +#ifndef FIXED_POINT + need_clip = st->clip && sample_max>65536.f; +#endif + celt_preemphasis(pcm+c, in+c*(N+overlap)+overlap, N, CC, st->upsample, + mode->preemph, st->preemph_memE+c, need_clip); + } while (++clfe&&nbAvailableBytes>3) || nbAvailableBytes>12*C) && !hybrid && !silence && !st->disable_pf + && st->complexity >= 5; + + prefilter_tapset = st->tapset_decision; + pf_on = run_prefilter(st, in, prefilter_mem, CC, N, prefilter_tapset, &pitch_index, &gain1, &qg, enabled, nbAvailableBytes, &st->analysis); + if ((gain1 > QCONST16(.4f,15) || st->prefilter_gain > QCONST16(.4f,15)) && (!st->analysis.valid || st->analysis.tonality > .3) + && (pitch_index > 1.26*st->prefilter_period || pitch_index < .79*st->prefilter_period)) + pitch_change = 1; + if (pf_on==0) + { + if(!hybrid && tell+16<=total_bits) + ec_enc_bit_logp(enc, 0, 1); + } else { + /*This block is not gated by a total bits check only because + of the nbAvailableBytes check above.*/ + int octave; + ec_enc_bit_logp(enc, 1, 1); + pitch_index += 1; + octave = EC_ILOG(pitch_index)-5; + ec_enc_uint(enc, octave, 6); + ec_enc_bits(enc, pitch_index-(16<complexity >= 1 && !st->lfe) + { + /* Reduces the likelihood of energy instability on fricatives at low bitrate + in hybrid mode. It seems like we still want to have real transients on vowels + though (small SILK quantization offset value). */ + int allow_weak_transients = hybrid && effectiveBytes<15 && st->silk_info.signalType != 2; + isTransient = transient_analysis(in, N+overlap, CC, + &tf_estimate, &tf_chan, allow_weak_transients, &weak_transient); + } + if (LM>0 && ec_tell(enc)+3<=total_bits) + { + if (isTransient) + shortBlocks = M; + } else { + isTransient = 0; + transient_got_disabled=1; + } + + ALLOC(freq, CC*N, celt_sig); /**< Interleaved signal MDCTs */ + ALLOC(bandE,nbEBands*CC, celt_ener); + ALLOC(bandLogE,nbEBands*CC, opus_val16); + + secondMdct = shortBlocks && st->complexity>=8; + ALLOC(bandLogE2, C*nbEBands, opus_val16); + if (secondMdct) + { + compute_mdcts(mode, 0, in, freq, C, CC, LM, st->upsample, st->arch); + compute_band_energies(mode, freq, bandE, effEnd, C, LM, st->arch); + amp2Log2(mode, effEnd, end, bandE, bandLogE2, C); + for (c=0;cupsample, st->arch); + /* This should catch any NaN in the CELT input. Since we're not supposed to see any (they're filtered + at the Opus layer), just abort. */ + celt_assert(!celt_isnan(freq[0]) && (C==1 || !celt_isnan(freq[N]))); + if (CC==2&&C==1) + tf_chan = 0; + compute_band_energies(mode, freq, bandE, effEnd, C, LM, st->arch); + + if (st->lfe) + { + for (i=2;ienergy_mask&&!st->lfe) + { + int mask_end; + int midband; + int count_dynalloc; + opus_val32 mask_avg=0; + opus_val32 diff=0; + int count=0; + mask_end = IMAX(2,st->lastCodedBands); + for (c=0;cenergy_mask[nbEBands*c+i], + QCONST16(.25f, DB_SHIFT)), -QCONST16(2.0f, DB_SHIFT)); + if (mask > 0) + mask = HALF16(mask); + mask_avg += MULT16_16(mask, eBands[i+1]-eBands[i]); + count += eBands[i+1]-eBands[i]; + diff += MULT16_16(mask, 1+2*i-mask_end); + } + } + celt_assert(count>0); + mask_avg = DIV32_16(mask_avg,count); + mask_avg += QCONST16(.2f, DB_SHIFT); + diff = diff*6/(C*(mask_end-1)*(mask_end+1)*mask_end); + /* Again, being conservative */ + diff = HALF32(diff); + diff = MAX32(MIN32(diff, QCONST32(.031f, DB_SHIFT)), -QCONST32(.031f, DB_SHIFT)); + /* Find the band that's in the middle of the coded spectrum */ + for (midband=0;eBands[midband+1] < eBands[mask_end]/2;midband++); + count_dynalloc=0; + for(i=0;ienergy_mask[i], st->energy_mask[nbEBands+i]); + else + unmask = st->energy_mask[i]; + unmask = MIN16(unmask, QCONST16(.0f, DB_SHIFT)); + unmask -= lin; + if (unmask > QCONST16(.25f, DB_SHIFT)) + { + surround_dynalloc[i] = unmask - QCONST16(.25f, DB_SHIFT); + count_dynalloc++; + } + } + if (count_dynalloc>=3) + { + /* If we need dynalloc in many bands, it's probably because our + initial masking rate was too low. */ + mask_avg += QCONST16(.25f, DB_SHIFT); + if (mask_avg>0) + { + /* Something went really wrong in the original calculations, + disabling masking. */ + mask_avg = 0; + diff = 0; + OPUS_CLEAR(surround_dynalloc, mask_end); + } else { + for(i=0;ilfe) + { + opus_val16 follow=-QCONST16(10.0f,DB_SHIFT); + opus_val32 frame_avg=0; + opus_val16 offset = shortBlocks?HALF16(SHL16(LM, DB_SHIFT)):0; + for(i=start;ispec_avg); + temporal_vbr = MIN16(QCONST16(3.f, DB_SHIFT), MAX16(-QCONST16(1.5f, DB_SHIFT), temporal_vbr)); + st->spec_avg += MULT16_16_Q15(QCONST16(.02f, 15), temporal_vbr); + } + /*for (i=0;i<21;i++) + printf("%f ", bandLogE[i]); + printf("\n");*/ + + if (!secondMdct) + { + OPUS_COPY(bandLogE2, bandLogE, C*nbEBands); + } + + /* Last chance to catch any transient we might have missed in the + time-domain analysis */ + if (LM>0 && ec_tell(enc)+3<=total_bits && !isTransient && st->complexity>=5 && !st->lfe && !hybrid) + { + if (patch_transient_decision(bandLogE, oldBandE, nbEBands, start, end, C)) + { + isTransient = 1; + shortBlocks = M; + compute_mdcts(mode, shortBlocks, in, freq, C, CC, LM, st->upsample, st->arch); + compute_band_energies(mode, freq, bandE, effEnd, C, LM, st->arch); + amp2Log2(mode, effEnd, end, bandE, bandLogE, C); + /* Compensate for the scaling of short vs long mdcts */ + for (c=0;c0 && ec_tell(enc)+3<=total_bits) + ec_enc_bit_logp(enc, isTransient, 3); + + ALLOC(X, C*N, celt_norm); /**< Interleaved normalised MDCTs */ + + /* Band normalisation */ + normalise_bands(mode, freq, X, bandE, effEnd, C, M); + + enable_tf_analysis = effectiveBytes>=15*C && !hybrid && st->complexity>=2 && !st->lfe; + + ALLOC(offsets, nbEBands, int); + ALLOC(importance, nbEBands, int); + ALLOC(spread_weight, nbEBands, int); + + maxDepth = dynalloc_analysis(bandLogE, bandLogE2, oldBandE, nbEBands, start, end, C, offsets, + st->lsb_depth, mode->logN, isTransient, st->vbr, st->constrained_vbr, + eBands, LM, effectiveBytes, &tot_boost, st->lfe, surround_dynalloc, &st->analysis, importance, spread_weight); + + ALLOC(tf_res, nbEBands, int); + /* Disable variable tf resolution for hybrid and at very low bitrate */ + if (enable_tf_analysis) + { + int lambda; + lambda = IMAX(80, 20480/effectiveBytes + 2); + tf_select = tf_analysis(mode, effEnd, isTransient, tf_res, lambda, X, N, LM, tf_estimate, tf_chan, importance); + for (i=effEnd;isilk_info.signalType != 2) + { + /* For low bitrate hybrid, we force temporal resolution to 5 ms rather than 2.5 ms. */ + for (i=0;iforce_intra, + &st->delayedIntra, st->complexity >= 4, st->loss_rate, st->lfe); + + tf_encode(start, end, isTransient, tf_res, LM, tf_select, enc); + + if (ec_tell(enc)+4<=total_bits) + { + if (st->lfe) + { + st->tapset_decision = 0; + st->spread_decision = SPREAD_NORMAL; + } else if (hybrid) + { + if (st->complexity == 0) + st->spread_decision = SPREAD_NONE; + else if (isTransient) + st->spread_decision = SPREAD_NORMAL; + else + st->spread_decision = SPREAD_AGGRESSIVE; + } else if (shortBlocks || st->complexity < 3 || nbAvailableBytes < 10*C) + { + if (st->complexity == 0) + st->spread_decision = SPREAD_NONE; + else + st->spread_decision = SPREAD_NORMAL; + } else { + /* Disable new spreading+tapset estimator until we can show it works + better than the old one. So far it seems like spreading_decision() + works best. */ +#if 0 + if (st->analysis.valid) + { + static const opus_val16 spread_thresholds[3] = {-QCONST16(.6f, 15), -QCONST16(.2f, 15), -QCONST16(.07f, 15)}; + static const opus_val16 spread_histeresis[3] = {QCONST16(.15f, 15), QCONST16(.07f, 15), QCONST16(.02f, 15)}; + static const opus_val16 tapset_thresholds[2] = {QCONST16(.0f, 15), QCONST16(.15f, 15)}; + static const opus_val16 tapset_histeresis[2] = {QCONST16(.1f, 15), QCONST16(.05f, 15)}; + st->spread_decision = hysteresis_decision(-st->analysis.tonality, spread_thresholds, spread_histeresis, 3, st->spread_decision); + st->tapset_decision = hysteresis_decision(st->analysis.tonality_slope, tapset_thresholds, tapset_histeresis, 2, st->tapset_decision); + } else +#endif + { + st->spread_decision = spreading_decision(mode, X, + &st->tonal_average, st->spread_decision, &st->hf_average, + &st->tapset_decision, pf_on&&!shortBlocks, effEnd, C, M, spread_weight); + } + /*printf("%d %d\n", st->tapset_decision, st->spread_decision);*/ + /*printf("%f %d %f %d\n\n", st->analysis.tonality, st->spread_decision, st->analysis.tonality_slope, st->tapset_decision);*/ + } + ec_enc_icdf(enc, st->spread_decision, spread_icdf, 5); + } + + /* For LFE, everything interesting is in the first band */ + if (st->lfe) + offsets[0] = IMIN(8, effectiveBytes/3); + ALLOC(cap, nbEBands, int); + init_caps(mode,cap,LM,C); + + dynalloc_logp = 6; + total_bits<<=BITRES; + total_boost = 0; + tell = ec_tell_frac(enc); + for (i=start;iintensity = hysteresis_decision((opus_val16)(equiv_rate/1000), + intensity_thresholds, intensity_histeresis, 21, st->intensity); + st->intensity = IMIN(end,IMAX(start, st->intensity)); + } + + alloc_trim = 5; + if (tell+(6< 0 || st->lfe) + { + st->stereo_saving = 0; + alloc_trim = 5; + } else { + alloc_trim = alloc_trim_analysis(mode, X, bandLogE, + end, LM, C, N, &st->analysis, &st->stereo_saving, tf_estimate, + st->intensity, surround_trim, equiv_rate, st->arch); + } + ec_enc_icdf(enc, alloc_trim, trim_icdf, 7); + tell = ec_tell_frac(enc); + } + + /* Variable bitrate */ + if (vbr_rate>0) + { + opus_val16 alpha; + opus_int32 delta; + /* The target rate in 8th bits per frame */ + opus_int32 target, base_target; + opus_int32 min_allowed; + int lm_diff = mode->maxLM - LM; + + /* Don't attempt to use more than 510 kb/s, even for frames smaller than 20 ms. + The CELT allocator will just not be able to use more than that anyway. */ + nbCompressedBytes = IMIN(nbCompressedBytes,1275>>(3-LM)); + if (!hybrid) + { + base_target = vbr_rate - ((40*C+20)<constrained_vbr) + base_target += (st->vbr_offset>>lm_diff); + + if (!hybrid) + { + target = compute_vbr(mode, &st->analysis, base_target, LM, equiv_rate, + st->lastCodedBands, C, st->intensity, st->constrained_vbr, + st->stereo_saving, tot_boost, tf_estimate, pitch_change, maxDepth, + st->lfe, st->energy_mask!=NULL, surround_masking, + temporal_vbr); + } else { + target = base_target; + /* Tonal frames (offset<100) need more bits than noisy (offset>100) ones. */ + if (st->silk_info.offset < 100) target += 12 << BITRES >> (3-LM); + if (st->silk_info.offset > 100) target -= 18 << BITRES >> (3-LM); + /* Boosting bitrate on transients and vowels with significant temporal + spikes. */ + target += (opus_int32)MULT16_16_Q14(tf_estimate-QCONST16(.25f,14), (50< QCONST16(.7f,14)) + target = IMAX(target, 50<>(BITRES+3)) + 2; + /* Take into account the 37 bits we need to have left in the packet to + signal a redundant frame in hybrid mode. Creating a shorter packet would + create an entropy coder desync. */ + if (hybrid) + min_allowed = IMAX(min_allowed, (tell0_frac+(37<>(BITRES+3)); + + nbAvailableBytes = (target+(1<<(BITRES+2)))>>(BITRES+3); + nbAvailableBytes = IMAX(min_allowed,nbAvailableBytes); + nbAvailableBytes = IMIN(nbCompressedBytes,nbAvailableBytes); + + /* By how much did we "miss" the target on that frame */ + delta = target - vbr_rate; + + target=nbAvailableBytes<<(BITRES+3); + + /*If the frame is silent we don't adjust our drift, otherwise + the encoder will shoot to very high rates after hitting a + span of silence, but we do allow the bitres to refill. + This means that we'll undershoot our target in CVBR/VBR modes + on files with lots of silence. */ + if(silence) + { + nbAvailableBytes = 2; + target = 2*8<vbr_count < 970) + { + st->vbr_count++; + alpha = celt_rcp(SHL32(EXTEND32(st->vbr_count+20),16)); + } else + alpha = QCONST16(.001f,15); + /* How many bits have we used in excess of what we're allowed */ + if (st->constrained_vbr) + st->vbr_reservoir += target - vbr_rate; + /*printf ("%d\n", st->vbr_reservoir);*/ + + /* Compute the offset we need to apply in order to reach the target */ + if (st->constrained_vbr) + { + st->vbr_drift += (opus_int32)MULT16_32_Q15(alpha,(delta*(1<vbr_offset-st->vbr_drift); + st->vbr_offset = -st->vbr_drift; + } + /*printf ("%d\n", st->vbr_drift);*/ + + if (st->constrained_vbr && st->vbr_reservoir < 0) + { + /* We're under the min value -- increase rate */ + int adjust = (-st->vbr_reservoir)/(8<vbr_reservoir = 0; + /*printf ("+%d\n", adjust);*/ + } + nbCompressedBytes = IMIN(nbCompressedBytes,nbAvailableBytes); + /*printf("%d\n", nbCompressedBytes*50*8);*/ + /* This moves the raw bits to take into account the new compressed size */ + ec_enc_shrink(enc, nbCompressedBytes); + } + + /* Bit allocation */ + ALLOC(fine_quant, nbEBands, int); + ALLOC(pulses, nbEBands, int); + ALLOC(fine_priority, nbEBands, int); + + /* bits = packet size - where we are - safety*/ + bits = (((opus_int32)nbCompressedBytes*8)<=2&&bits>=((LM+2)<analysis.valid) + { + int min_bandwidth; + if (equiv_rate < (opus_int32)32000*C) + min_bandwidth = 13; + else if (equiv_rate < (opus_int32)48000*C) + min_bandwidth = 16; + else if (equiv_rate < (opus_int32)60000*C) + min_bandwidth = 18; + else if (equiv_rate < (opus_int32)80000*C) + min_bandwidth = 19; + else + min_bandwidth = 20; + signalBandwidth = IMAX(st->analysis.bandwidth, min_bandwidth); + } +#endif + if (st->lfe) + signalBandwidth = 1; + codedBands = clt_compute_allocation(mode, start, end, offsets, cap, + alloc_trim, &st->intensity, &dual_stereo, bits, &balance, pulses, + fine_quant, fine_priority, C, LM, enc, 1, st->lastCodedBands, signalBandwidth); + if (st->lastCodedBands) + st->lastCodedBands = IMIN(st->lastCodedBands+1,IMAX(st->lastCodedBands-1,codedBands)); + else + st->lastCodedBands = codedBands; + + quant_fine_energy(mode, start, end, oldBandE, error, fine_quant, enc, C); + + /* Residual quantisation */ + ALLOC(collapse_masks, C*nbEBands, unsigned char); + quant_all_bands(1, mode, start, end, X, C==2 ? X+N : NULL, collapse_masks, + bandE, pulses, shortBlocks, st->spread_decision, + dual_stereo, st->intensity, tf_res, nbCompressedBytes*(8<rng, st->complexity, st->arch, st->disable_inv); + + if (anti_collapse_rsv > 0) + { + anti_collapse_on = st->consec_transient<2; +#ifdef FUZZING + anti_collapse_on = rand()&0x1; +#endif + ec_enc_bits(enc, anti_collapse_on, 1); + } + quant_energy_finalise(mode, start, end, oldBandE, error, fine_quant, fine_priority, nbCompressedBytes*8-ec_tell(enc), enc, C); + OPUS_CLEAR(energyError, nbEBands*CC); + c=0; + do { + for (i=start;irng, st->arch); + } + + c=0; do { + OPUS_MOVE(st->syn_mem[c], st->syn_mem[c]+N, 2*MAX_PERIOD-N+overlap/2); + } while (++csyn_mem[c]+2*MAX_PERIOD-N; + } while (++cupsample, silence, st->arch); + + c=0; do { + st->prefilter_period=IMAX(st->prefilter_period, COMBFILTER_MINPERIOD); + st->prefilter_period_old=IMAX(st->prefilter_period_old, COMBFILTER_MINPERIOD); + comb_filter(out_mem[c], out_mem[c], st->prefilter_period_old, st->prefilter_period, mode->shortMdctSize, + st->prefilter_gain_old, st->prefilter_gain, st->prefilter_tapset_old, st->prefilter_tapset, + mode->window, overlap, st->arch); + if (LM!=0) + comb_filter(out_mem[c]+mode->shortMdctSize, out_mem[c]+mode->shortMdctSize, st->prefilter_period, pitch_index, N-mode->shortMdctSize, + st->prefilter_gain, gain1, st->prefilter_tapset, prefilter_tapset, + mode->window, overlap, st->arch); + } while (++cupsample, mode->preemph, st->preemph_memD, 0); + st->prefilter_period_old = st->prefilter_period; + st->prefilter_gain_old = st->prefilter_gain; + st->prefilter_tapset_old = st->prefilter_tapset; + } +#endif + + st->prefilter_period = pitch_index; + st->prefilter_gain = gain1; + st->prefilter_tapset = prefilter_tapset; +#ifdef RESYNTH + if (LM!=0) + { + st->prefilter_period_old = st->prefilter_period; + st->prefilter_gain_old = st->prefilter_gain; + st->prefilter_tapset_old = st->prefilter_tapset; + } +#endif + + if (CC==2&&C==1) { + OPUS_COPY(&oldBandE[nbEBands], oldBandE, nbEBands); + } + + if (!isTransient) + { + OPUS_COPY(oldLogE2, oldLogE, CC*nbEBands); + OPUS_COPY(oldLogE, oldBandE, CC*nbEBands); + } else { + for (i=0;iconsec_transient++; + else + st->consec_transient=0; + st->rng = enc->rng; + + /* If there's any room left (can only happen for very high rates), + it's already filled with zeros */ + ec_enc_done(enc); + +#ifdef CUSTOM_MODES + if (st->signalling) + nbCompressedBytes++; +#endif + + RESTORE_STACK; + if (ec_get_error(enc)) + return OPUS_INTERNAL_ERROR; + else + return nbCompressedBytes; +} + + +#ifdef CUSTOM_MODES + +#ifdef FIXED_POINT +int opus_custom_encode(CELTEncoder * OPUS_RESTRICT st, const opus_int16 * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes) +{ + return celt_encode_with_ec(st, pcm, frame_size, compressed, nbCompressedBytes, NULL); +} + +#ifndef DISABLE_FLOAT_API +int opus_custom_encode_float(CELTEncoder * OPUS_RESTRICT st, const float * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes) +{ + int j, ret, C, N; + VARDECL(opus_int16, in); + ALLOC_STACK; + + if (pcm==NULL) + return OPUS_BAD_ARG; + + C = st->channels; + N = frame_size; + ALLOC(in, C*N, opus_int16); + + for (j=0;jchannels; + N=frame_size; + ALLOC(in, C*N, celt_sig); + for (j=0;j10) + goto bad_arg; + st->complexity = value; + } + break; + case CELT_SET_START_BAND_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<0 || value>=st->mode->nbEBands) + goto bad_arg; + st->start = value; + } + break; + case CELT_SET_END_BAND_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<1 || value>st->mode->nbEBands) + goto bad_arg; + st->end = value; + } + break; + case CELT_SET_PREDICTION_REQUEST: + { + int value = va_arg(ap, opus_int32); + if (value<0 || value>2) + goto bad_arg; + st->disable_pf = value<=1; + st->force_intra = value==0; + } + break; + case OPUS_SET_PACKET_LOSS_PERC_REQUEST: + { + int value = va_arg(ap, opus_int32); + if (value<0 || value>100) + goto bad_arg; + st->loss_rate = value; + } + break; + case OPUS_SET_VBR_CONSTRAINT_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->constrained_vbr = value; + } + break; + case OPUS_SET_VBR_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->vbr = value; + } + break; + case OPUS_SET_BITRATE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<=500 && value!=OPUS_BITRATE_MAX) + goto bad_arg; + value = IMIN(value, 260000*st->channels); + st->bitrate = value; + } + break; + case CELT_SET_CHANNELS_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<1 || value>2) + goto bad_arg; + st->stream_channels = value; + } + break; + case OPUS_SET_LSB_DEPTH_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<8 || value>24) + goto bad_arg; + st->lsb_depth=value; + } + break; + case OPUS_GET_LSB_DEPTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + *value=st->lsb_depth; + } + break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->disable_inv = value; + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->disable_inv; + } + break; + case OPUS_RESET_STATE: + { + int i; + opus_val16 *oldBandE, *oldLogE, *oldLogE2; + oldBandE = (opus_val16*)(st->in_mem+st->channels*(st->mode->overlap+COMBFILTER_MAXPERIOD)); + oldLogE = oldBandE + st->channels*st->mode->nbEBands; + oldLogE2 = oldLogE + st->channels*st->mode->nbEBands; + OPUS_CLEAR((char*)&st->ENCODER_RESET_START, + opus_custom_encoder_get_size(st->mode, st->channels)- + ((char*)&st->ENCODER_RESET_START - (char*)st)); + for (i=0;ichannels*st->mode->nbEBands;i++) + oldLogE[i]=oldLogE2[i]=-QCONST16(28.f,DB_SHIFT); + st->vbr_offset = 0; + st->delayedIntra = 1; + st->spread_decision = SPREAD_NORMAL; + st->tonal_average = 256; + st->hf_average = 0; + st->tapset_decision = 0; + } + break; +#ifdef CUSTOM_MODES + case CELT_SET_INPUT_CLIPPING_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->clip = value; + } + break; +#endif + case CELT_SET_SIGNALLING_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->signalling = value; + } + break; + case CELT_SET_ANALYSIS_REQUEST: + { + AnalysisInfo *info = va_arg(ap, AnalysisInfo *); + if (info) + OPUS_COPY(&st->analysis, info, 1); + } + break; + case CELT_SET_SILK_INFO_REQUEST: + { + SILKInfo *info = va_arg(ap, SILKInfo *); + if (info) + OPUS_COPY(&st->silk_info, info, 1); + } + break; + case CELT_GET_MODE_REQUEST: + { + const CELTMode ** value = va_arg(ap, const CELTMode**); + if (value==0) + goto bad_arg; + *value=st->mode; + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + opus_uint32 * value = va_arg(ap, opus_uint32 *); + if (value==0) + goto bad_arg; + *value=st->rng; + } + break; + case OPUS_SET_LFE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->lfe = value; + } + break; + case OPUS_SET_ENERGY_MASK_REQUEST: + { + opus_val16 *value = va_arg(ap, opus_val16*); + st->energy_mask = value; + } + break; + default: + goto bad_request; + } + va_end(ap); + return OPUS_OK; +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +bad_request: + va_end(ap); + return OPUS_UNIMPLEMENTED; +} diff --git a/port/third_party/opus/celt/celt_lpc.c b/port/third_party/opus/celt/celt_lpc.c new file mode 100644 index 00000000..fabca65c --- /dev/null +++ b/port/third_party/opus/celt/celt_lpc.c @@ -0,0 +1,363 @@ +/* Copyright (c) 2009-2010 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "pitch.h" + +void _celt_lpc( + opus_val16 *_lpc, /* out: [0...p-1] LPC coefficients */ +const opus_val32 *ac, /* in: [0...p] autocorrelation values */ +int p +) +{ + int i, j; + opus_val32 r; + opus_val32 error = ac[0]; +#ifdef FIXED_POINT + opus_val32 lpc[CELT_LPC_ORDER]; +#else + float *lpc = _lpc; +#endif + + OPUS_CLEAR(lpc, p); +#ifdef FIXED_POINT + if (ac[0] != 0) +#else + if (ac[0] > 1e-10f) +#endif + { + for (i = 0; i < p; i++) { + /* Sum up this iteration's reflection coefficient */ + opus_val32 rr = 0; + for (j = 0; j < i; j++) + rr += MULT32_32_Q31(lpc[j],ac[i - j]); + rr += SHR32(ac[i + 1],6); + r = -frac_div32(SHL32(rr,6), error); + /* Update LPC coefficients and total error */ + lpc[i] = SHR32(r,6); + for (j = 0; j < (i+1)>>1; j++) + { + opus_val32 tmp1, tmp2; + tmp1 = lpc[j]; + tmp2 = lpc[i-1-j]; + lpc[j] = tmp1 + MULT32_32_Q31(r,tmp2); + lpc[i-1-j] = tmp2 + MULT32_32_Q31(r,tmp1); + } + + error = error - MULT32_32_Q31(MULT32_32_Q31(r,r),error); + /* Bail out once we get 30 dB gain */ +#ifdef FIXED_POINT + if (error<=SHR32(ac[0],10)) + break; +#else + if (error<=.001f*ac[0]) + break; +#endif + } + } +#ifdef FIXED_POINT + { + /* Convert the int32 lpcs to int16 and ensure there are no wrap-arounds. + This reuses the logic in silk_LPC_fit() and silk_bwexpander_32(). Any bug + fixes should also be applied there. */ + int iter, idx = 0; + opus_val32 maxabs, absval, chirp_Q16, chirp_minus_one_Q16; + + for (iter = 0; iter < 10; iter++) { + maxabs = 0; + for (i = 0; i < p; i++) { + absval = ABS32(lpc[i]); + if (absval > maxabs) { + maxabs = absval; + idx = i; + } + } + maxabs = PSHR32(maxabs, 13); /* Q25->Q12 */ + + if (maxabs > 32767) { + maxabs = MIN32(maxabs, 163838); + chirp_Q16 = QCONST32(0.999, 16) - DIV32(SHL32(maxabs - 32767, 14), + SHR32(MULT32_32_32(maxabs, idx + 1), 2)); + chirp_minus_one_Q16 = chirp_Q16 - 65536; + + /* Apply bandwidth expansion. */ + for (i = 0; i < p - 1; i++) { + lpc[i] = MULT32_32_Q16(chirp_Q16, lpc[i]); + chirp_Q16 += PSHR32(MULT32_32_32(chirp_Q16, chirp_minus_one_Q16), 16); + } + lpc[p - 1] = MULT32_32_Q16(chirp_Q16, lpc[p - 1]); + } else { + break; + } + } + + if (iter == 10) { + /* If the coeffs still do not fit into the 16 bit range after 10 iterations, + fall back to the A(z)=1 filter. */ + OPUS_CLEAR(lpc, p); + _lpc[0] = 4096; /* Q12 */ + } else { + for (i = 0; i < p; i++) { + _lpc[i] = EXTRACT16(PSHR32(lpc[i], 13)); /* Q25->Q12 */ + } + } + } +#endif +} + + +void celt_fir_c( + const opus_val16 *x, + const opus_val16 *num, + opus_val16 *y, + int N, + int ord, + int arch) +{ + int i,j; + VARDECL(opus_val16, rnum); + SAVE_STACK; + celt_assert(x != y); + ALLOC(rnum, ord, opus_val16); + for(i=0;i=1;j--) + { + mem[j]=mem[j-1]; + } + mem[0] = SROUND16(sum, SIG_SHIFT); + _y[i] = sum; + } +#else + int i,j; + VARDECL(opus_val16, rden); + VARDECL(opus_val16, y); + SAVE_STACK; + + celt_assert((ord&3)==0); + ALLOC(rden, ord, opus_val16); + ALLOC(y, N+ord, opus_val16); + for(i=0;i0); + celt_assert(overlap>=0); + if (overlap == 0) + { + xptr = x; + } else { + for (i=0;i0) + { + for(i=0;i= 536870912) + { + int shift2=1; + if (ac[0] >= 1073741824) + shift2++; + for (i=0;i<=lag;i++) + ac[i] = SHR32(ac[i], shift2); + shift += shift2; + } +#endif + + RESTORE_STACK; + return shift; +} diff --git a/port/third_party/opus/celt/celt_lpc.h b/port/third_party/opus/celt/celt_lpc.h new file mode 100644 index 00000000..97dee82f --- /dev/null +++ b/port/third_party/opus/celt/celt_lpc.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2009-2010 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef PLC_H +#define PLC_H + +#include "arch.h" +#include "cpu_support.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +#include "x86/celt_lpc_sse.h" +#endif + +#define CELT_LPC_ORDER 24 + +void _celt_lpc(opus_val16 *_lpc, const opus_val32 *ac, int p); + +void celt_fir_c( + const opus_val16 *x, + const opus_val16 *num, + opus_val16 *y, + int N, + int ord, + int arch); + +#if !defined(OVERRIDE_CELT_FIR) +#define celt_fir(x, num, y, N, ord, arch) \ + (celt_fir_c(x, num, y, N, ord, arch)) +#endif + +void celt_iir(const opus_val32 *x, + const opus_val16 *den, + opus_val32 *y, + int N, + int ord, + opus_val16 *mem, + int arch); + +int _celt_autocorr(const opus_val16 *x, opus_val32 *ac, + const opus_val16 *window, int overlap, int lag, int n, int arch); + +#endif /* PLC_H */ diff --git a/port/third_party/opus/celt/cpu_support.h b/port/third_party/opus/celt/cpu_support.h new file mode 100644 index 00000000..9f13d8ae --- /dev/null +++ b/port/third_party/opus/celt/cpu_support.h @@ -0,0 +1,72 @@ +/* Copyright (c) 2010 Xiph.Org Foundation + * Copyright (c) 2013 Parrot */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef CPU_SUPPORT_H +#define CPU_SUPPORT_H + +#include "opus_types.h" +#include "opus_defines.h" + +#if defined(OPUS_HAVE_RTCD) && \ + (defined(OPUS_ARM_ASM) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) +#include "arm/armcpu.h" + +/* We currently support 5 ARM variants: + * arch[0] -> ARMv4 + * arch[1] -> ARMv5E + * arch[2] -> ARMv6 + * arch[3] -> NEON + * arch[4] -> NEON+DOTPROD + */ +#define OPUS_ARCHMASK 7 + +#elif defined(OPUS_HAVE_RTCD) && \ + ((defined(OPUS_X86_MAY_HAVE_SSE) && !defined(OPUS_X86_PRESUME_SSE)) || \ + (defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(OPUS_X86_PRESUME_SSE2)) || \ + (defined(OPUS_X86_MAY_HAVE_SSE4_1) && !defined(OPUS_X86_PRESUME_SSE4_1)) || \ + (defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_PRESUME_AVX2))) + +#include "x86/x86cpu.h" +/* We currently support 5 x86 variants: + * arch[0] -> non-sse + * arch[1] -> sse + * arch[2] -> sse2 + * arch[3] -> sse4.1 + * arch[4] -> avx + */ +#define OPUS_ARCHMASK 7 +int opus_select_arch(void); + +#else +#define OPUS_ARCHMASK 0 + +static OPUS_INLINE int opus_select_arch(void) +{ + return 0; +} +#endif +#endif diff --git a/port/third_party/opus/celt/cwrs.c b/port/third_party/opus/celt/cwrs.c new file mode 100644 index 00000000..a552e4f0 --- /dev/null +++ b/port/third_party/opus/celt/cwrs.c @@ -0,0 +1,715 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2007-2009 Timothy B. Terriberry + Written by Timothy B. Terriberry and Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "os_support.h" +#include "cwrs.h" +#include "mathops.h" +#include "arch.h" + +#ifdef CUSTOM_MODES + +/*Guaranteed to return a conservatively large estimate of the binary logarithm + with frac bits of fractional precision. + Tested for all possible 32-bit inputs with frac=4, where the maximum + overestimation is 0.06254243 bits.*/ +int log2_frac(opus_uint32 val, int frac) +{ + int l; + l=EC_ILOG(val); + if(val&(val-1)){ + /*This is (val>>l-16), but guaranteed to round up, even if adding a bias + before the shift would cause overflow (e.g., for 0xFFFFxxxx). + Doesn't work for val=0, but that case fails the test above.*/ + if(l>16)val=((val-1)>>(l-16))+1; + else val<<=16-l; + l=(l-1)<>16); + l+=b<>b; + val=(val*val+0x7FFF)>>15; + } + while(frac-->0); + /*If val is not exactly 0x8000, then we have to round up the remainder.*/ + return l+(val>0x8000); + } + /*Exact powers of two require no rounding.*/ + else return (l-1)<0 ? sum(k=1...K,2**k*choose(N,k)*choose(K-1,k-1)) : 1, + where choose() is the binomial function. + A table of values for N<10 and K<10 looks like: + V[10][10] = { + {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + {1, 4, 8, 12, 16, 20, 24, 28, 32, 36}, + {1, 6, 18, 38, 66, 102, 146, 198, 258, 326}, + {1, 8, 32, 88, 192, 360, 608, 952, 1408, 1992}, + {1, 10, 50, 170, 450, 1002, 1970, 3530, 5890, 9290}, + {1, 12, 72, 292, 912, 2364, 5336, 10836, 20256, 35436}, + {1, 14, 98, 462, 1666, 4942, 12642, 28814, 59906, 115598}, + {1, 16, 128, 688, 2816, 9424, 27008, 68464, 157184, 332688}, + {1, 18, 162, 978, 4482, 16722, 53154, 148626, 374274, 864146} + }; + + U(N,K) = the number of such combinations wherein N-1 objects are taken at + most K-1 at a time. + This is given by + U(N,K) = sum(k=0...K-1,V(N-1,k)) + = K>0 ? (V(N-1,K-1) + V(N,K-1))/2 : 0. + The latter expression also makes clear that U(N,K) is half the number of such + combinations wherein the first object is taken at least once. + Although it may not be clear from either of these definitions, U(N,K) is the + natural function to work with when enumerating the pulse vector codebooks, + not V(N,K). + U(N,K) is not well-defined for N=0, but with the extension + U(0,K) = K>0 ? 0 : 1, + the function becomes symmetric: U(N,K) = U(K,N), with a similar table: + U[10][10] = { + {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 1, 3, 5, 7, 9, 11, 13, 15, 17}, + {0, 1, 5, 13, 25, 41, 61, 85, 113, 145}, + {0, 1, 7, 25, 63, 129, 231, 377, 575, 833}, + {0, 1, 9, 41, 129, 321, 681, 1289, 2241, 3649}, + {0, 1, 11, 61, 231, 681, 1683, 3653, 7183, 13073}, + {0, 1, 13, 85, 377, 1289, 3653, 8989, 19825, 40081}, + {0, 1, 15, 113, 575, 2241, 7183, 19825, 48639, 108545}, + {0, 1, 17, 145, 833, 3649, 13073, 40081, 108545, 265729} + }; + + With this extension, V(N,K) may be written in terms of U(N,K): + V(N,K) = U(N,K) + U(N,K+1) + for all N>=0, K>=0. + Thus U(N,K+1) represents the number of combinations where the first element + is positive or zero, and U(N,K) represents the number of combinations where + it is negative. + With a large enough table of U(N,K) values, we could write O(N) encoding + and O(min(N*log(K),N+K)) decoding routines, but such a table would be + prohibitively large for small embedded devices (K may be as large as 32767 + for small N, and N may be as large as 200). + + Both functions obey the same recurrence relation: + V(N,K) = V(N-1,K) + V(N,K-1) + V(N-1,K-1), + U(N,K) = U(N-1,K) + U(N,K-1) + U(N-1,K-1), + for all N>0, K>0, with different initial conditions at N=0 or K=0. + This allows us to construct a row of one of the tables above given the + previous row or the next row. + Thus we can derive O(NK) encoding and decoding routines with O(K) memory + using only addition and subtraction. + + When encoding, we build up from the U(2,K) row and work our way forwards. + When decoding, we need to start at the U(N,K) row and work our way backwards, + which requires a means of computing U(N,K). + U(N,K) may be computed from two previous values with the same N: + U(N,K) = ((2*N-1)*U(N,K-1) - U(N,K-2))/(K-1) + U(N,K-2) + for all N>1, and since U(N,K) is symmetric, a similar relation holds for two + previous values with the same K: + U(N,K>1) = ((2*K-1)*U(N-1,K) - U(N-2,K))/(N-1) + U(N-2,K) + for all K>1. + This allows us to construct an arbitrary row of the U(N,K) table by starting + with the first two values, which are constants. + This saves roughly 2/3 the work in our O(NK) decoding routine, but costs O(K) + multiplications. + Similar relations can be derived for V(N,K), but are not used here. + + For N>0 and K>0, U(N,K) and V(N,K) take on the form of an (N-1)-degree + polynomial for fixed N. + The first few are + U(1,K) = 1, + U(2,K) = 2*K-1, + U(3,K) = (2*K-2)*K+1, + U(4,K) = (((4*K-6)*K+8)*K-3)/3, + U(5,K) = ((((2*K-4)*K+10)*K-8)*K+3)/3, + and + V(1,K) = 2, + V(2,K) = 4*K, + V(3,K) = 4*K*K+2, + V(4,K) = 8*(K*K+2)*K/3, + V(5,K) = ((4*K*K+20)*K*K+6)/3, + for all K>0. + This allows us to derive O(N) encoding and O(N*log(K)) decoding routines for + small N (and indeed decoding is also O(N) for N<3). + + @ARTICLE{Fis86, + author="Thomas R. Fischer", + title="A Pyramid Vector Quantizer", + journal="IEEE Transactions on Information Theory", + volume="IT-32", + number=4, + pages="568--583", + month=Jul, + year=1986 + }*/ + +#if !defined(SMALL_FOOTPRINT) + +/*U(N,K) = U(K,N) := N>0?K>0?U(N-1,K)+U(N,K-1)+U(N-1,K-1):0:K>0?1:0*/ +# define CELT_PVQ_U(_n,_k) (CELT_PVQ_U_ROW[IMIN(_n,_k)][IMAX(_n,_k)]) +/*V(N,K) := U(N,K)+U(N,K+1) = the number of PVQ codewords for a band of size N + with K pulses allocated to it.*/ +# define CELT_PVQ_V(_n,_k) (CELT_PVQ_U(_n,_k)+CELT_PVQ_U(_n,(_k)+1)) + +/*For each V(N,K) supported, we will access element U(min(N,K+1),max(N,K+1)). + Thus, the number of entries in row I is the larger of the maximum number of + pulses we will ever allocate for a given N=I (K=128, or however many fit in + 32 bits, whichever is smaller), plus one, and the maximum N for which + K=I-1 pulses fit in 32 bits. + The largest band size in an Opus Custom mode is 208. + Otherwise, we can limit things to the set of N which can be achieved by + splitting a band from a standard Opus mode: 176, 144, 96, 88, 72, 64, 48, + 44, 36, 32, 24, 22, 18, 16, 8, 4, 2).*/ +#if defined(CUSTOM_MODES) +static const opus_uint32 CELT_PVQ_U_DATA[1488]={ +#else +static const opus_uint32 CELT_PVQ_U_DATA[1272]={ +#endif + /*N=0, K=0...176:*/ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#if defined(CUSTOM_MODES) + /*...208:*/ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, +#endif + /*N=1, K=1...176:*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +#if defined(CUSTOM_MODES) + /*...208:*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, +#endif + /*N=2, K=2...176:*/ + 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, + 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, + 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, + 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, + 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, + 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, + 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, + 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263, + 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287, 289, 291, 293, + 295, 297, 299, 301, 303, 305, 307, 309, 311, 313, 315, 317, 319, 321, 323, + 325, 327, 329, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351, +#if defined(CUSTOM_MODES) + /*...208:*/ + 353, 355, 357, 359, 361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, + 383, 385, 387, 389, 391, 393, 395, 397, 399, 401, 403, 405, 407, 409, 411, + 413, 415, +#endif + /*N=3, K=3...176:*/ + 13, 25, 41, 61, 85, 113, 145, 181, 221, 265, 313, 365, 421, 481, 545, 613, + 685, 761, 841, 925, 1013, 1105, 1201, 1301, 1405, 1513, 1625, 1741, 1861, + 1985, 2113, 2245, 2381, 2521, 2665, 2813, 2965, 3121, 3281, 3445, 3613, 3785, + 3961, 4141, 4325, 4513, 4705, 4901, 5101, 5305, 5513, 5725, 5941, 6161, 6385, + 6613, 6845, 7081, 7321, 7565, 7813, 8065, 8321, 8581, 8845, 9113, 9385, 9661, + 9941, 10225, 10513, 10805, 11101, 11401, 11705, 12013, 12325, 12641, 12961, + 13285, 13613, 13945, 14281, 14621, 14965, 15313, 15665, 16021, 16381, 16745, + 17113, 17485, 17861, 18241, 18625, 19013, 19405, 19801, 20201, 20605, 21013, + 21425, 21841, 22261, 22685, 23113, 23545, 23981, 24421, 24865, 25313, 25765, + 26221, 26681, 27145, 27613, 28085, 28561, 29041, 29525, 30013, 30505, 31001, + 31501, 32005, 32513, 33025, 33541, 34061, 34585, 35113, 35645, 36181, 36721, + 37265, 37813, 38365, 38921, 39481, 40045, 40613, 41185, 41761, 42341, 42925, + 43513, 44105, 44701, 45301, 45905, 46513, 47125, 47741, 48361, 48985, 49613, + 50245, 50881, 51521, 52165, 52813, 53465, 54121, 54781, 55445, 56113, 56785, + 57461, 58141, 58825, 59513, 60205, 60901, 61601, +#if defined(CUSTOM_MODES) + /*...208:*/ + 62305, 63013, 63725, 64441, 65161, 65885, 66613, 67345, 68081, 68821, 69565, + 70313, 71065, 71821, 72581, 73345, 74113, 74885, 75661, 76441, 77225, 78013, + 78805, 79601, 80401, 81205, 82013, 82825, 83641, 84461, 85285, 86113, +#endif + /*N=4, K=4...176:*/ + 63, 129, 231, 377, 575, 833, 1159, 1561, 2047, 2625, 3303, 4089, 4991, 6017, + 7175, 8473, 9919, 11521, 13287, 15225, 17343, 19649, 22151, 24857, 27775, + 30913, 34279, 37881, 41727, 45825, 50183, 54809, 59711, 64897, 70375, 76153, + 82239, 88641, 95367, 102425, 109823, 117569, 125671, 134137, 142975, 152193, + 161799, 171801, 182207, 193025, 204263, 215929, 228031, 240577, 253575, + 267033, 280959, 295361, 310247, 325625, 341503, 357889, 374791, 392217, + 410175, 428673, 447719, 467321, 487487, 508225, 529543, 551449, 573951, + 597057, 620775, 645113, 670079, 695681, 721927, 748825, 776383, 804609, + 833511, 863097, 893375, 924353, 956039, 988441, 1021567, 1055425, 1090023, + 1125369, 1161471, 1198337, 1235975, 1274393, 1313599, 1353601, 1394407, + 1436025, 1478463, 1521729, 1565831, 1610777, 1656575, 1703233, 1750759, + 1799161, 1848447, 1898625, 1949703, 2001689, 2054591, 2108417, 2163175, + 2218873, 2275519, 2333121, 2391687, 2451225, 2511743, 2573249, 2635751, + 2699257, 2763775, 2829313, 2895879, 2963481, 3032127, 3101825, 3172583, + 3244409, 3317311, 3391297, 3466375, 3542553, 3619839, 3698241, 3777767, + 3858425, 3940223, 4023169, 4107271, 4192537, 4278975, 4366593, 4455399, + 4545401, 4636607, 4729025, 4822663, 4917529, 5013631, 5110977, 5209575, + 5309433, 5410559, 5512961, 5616647, 5721625, 5827903, 5935489, 6044391, + 6154617, 6266175, 6379073, 6493319, 6608921, 6725887, 6844225, 6963943, + 7085049, 7207551, +#if defined(CUSTOM_MODES) + /*...208:*/ + 7331457, 7456775, 7583513, 7711679, 7841281, 7972327, 8104825, 8238783, + 8374209, 8511111, 8649497, 8789375, 8930753, 9073639, 9218041, 9363967, + 9511425, 9660423, 9810969, 9963071, 10116737, 10271975, 10428793, 10587199, + 10747201, 10908807, 11072025, 11236863, 11403329, 11571431, 11741177, + 11912575, +#endif + /*N=5, K=5...176:*/ + 321, 681, 1289, 2241, 3649, 5641, 8361, 11969, 16641, 22569, 29961, 39041, + 50049, 63241, 78889, 97281, 118721, 143529, 172041, 204609, 241601, 283401, + 330409, 383041, 441729, 506921, 579081, 658689, 746241, 842249, 947241, + 1061761, 1186369, 1321641, 1468169, 1626561, 1797441, 1981449, 2179241, + 2391489, 2618881, 2862121, 3121929, 3399041, 3694209, 4008201, 4341801, + 4695809, 5071041, 5468329, 5888521, 6332481, 6801089, 7295241, 7815849, + 8363841, 8940161, 9545769, 10181641, 10848769, 11548161, 12280841, 13047849, + 13850241, 14689089, 15565481, 16480521, 17435329, 18431041, 19468809, + 20549801, 21675201, 22846209, 24064041, 25329929, 26645121, 28010881, + 29428489, 30899241, 32424449, 34005441, 35643561, 37340169, 39096641, + 40914369, 42794761, 44739241, 46749249, 48826241, 50971689, 53187081, + 55473921, 57833729, 60268041, 62778409, 65366401, 68033601, 70781609, + 73612041, 76526529, 79526721, 82614281, 85790889, 89058241, 92418049, + 95872041, 99421961, 103069569, 106816641, 110664969, 114616361, 118672641, + 122835649, 127107241, 131489289, 135983681, 140592321, 145317129, 150160041, + 155123009, 160208001, 165417001, 170752009, 176215041, 181808129, 187533321, + 193392681, 199388289, 205522241, 211796649, 218213641, 224775361, 231483969, + 238341641, 245350569, 252512961, 259831041, 267307049, 274943241, 282741889, + 290705281, 298835721, 307135529, 315607041, 324252609, 333074601, 342075401, + 351257409, 360623041, 370174729, 379914921, 389846081, 399970689, 410291241, + 420810249, 431530241, 442453761, 453583369, 464921641, 476471169, 488234561, + 500214441, 512413449, 524834241, 537479489, 550351881, 563454121, 576788929, + 590359041, 604167209, 618216201, 632508801, +#if defined(CUSTOM_MODES) + /*...208:*/ + 647047809, 661836041, 676876329, 692171521, 707724481, 723538089, 739615241, + 755958849, 772571841, 789457161, 806617769, 824056641, 841776769, 859781161, + 878072841, 896654849, 915530241, 934702089, 954173481, 973947521, 994027329, + 1014416041, 1035116809, 1056132801, 1077467201, 1099123209, 1121104041, + 1143412929, 1166053121, 1189027881, 1212340489, 1235994241, +#endif + /*N=6, K=6...96:*/ + 1683, 3653, 7183, 13073, 22363, 36365, 56695, 85305, 124515, 177045, 246047, + 335137, 448427, 590557, 766727, 982729, 1244979, 1560549, 1937199, 2383409, + 2908411, 3522221, 4235671, 5060441, 6009091, 7095093, 8332863, 9737793, + 11326283, 13115773, 15124775, 17372905, 19880915, 22670725, 25765455, + 29189457, 32968347, 37129037, 41699767, 46710137, 52191139, 58175189, + 64696159, 71789409, 79491819, 87841821, 96879431, 106646281, 117185651, + 128542501, 140763503, 153897073, 167993403, 183104493, 199284183, 216588185, + 235074115, 254801525, 275831935, 298228865, 322057867, 347386557, 374284647, + 402823977, 433078547, 465124549, 499040399, 534906769, 572806619, 612825229, + 655050231, 699571641, 746481891, 795875861, 847850911, 902506913, 959946283, + 1020274013, 1083597703, 1150027593, 1219676595, 1292660325, 1369097135, + 1449108145, 1532817275, 1620351277, 1711839767, 1807415257, 1907213187, + 2011371957, 2120032959, +#if defined(CUSTOM_MODES) + /*...109:*/ + 2233340609U, 2351442379U, 2474488829U, 2602633639U, 2736033641U, 2874848851U, + 3019242501U, 3169381071U, 3325434321U, 3487575323U, 3655980493U, 3830829623U, + 4012305913U, +#endif + /*N=7, K=7...54*/ + 8989, 19825, 40081, 75517, 134245, 227305, 369305, 579125, 880685, 1303777, + 1884961, 2668525, 3707509, 5064793, 6814249, 9041957, 11847485, 15345233, + 19665841, 24957661, 31388293, 39146185, 48442297, 59511829, 72616013, + 88043969, 106114625, 127178701, 151620757, 179861305, 212358985, 249612805, + 292164445, 340600625, 395555537, 457713341, 527810725, 606639529, 695049433, + 793950709, 904317037, 1027188385, 1163673953, 1314955181, 1482288821, + 1667010073, 1870535785, 2094367717, +#if defined(CUSTOM_MODES) + /*...60:*/ + 2340095869U, 2609401873U, 2904062449U, 3225952925U, 3577050821U, 3959439497U, +#endif + /*N=8, K=8...37*/ + 48639, 108545, 224143, 433905, 795455, 1392065, 2340495, 3800305, 5984767, + 9173505, 13726991, 20103025, 28875327, 40754369, 56610575, 77500017, + 104692735, 139703809, 184327311, 240673265, 311207743, 398796225, 506750351, + 638878193, 799538175, 993696769, 1226990095, 1505789553, 1837271615, + 2229491905U, +#if defined(CUSTOM_MODES) + /*...40:*/ + 2691463695U, 3233240945U, 3866006015U, +#endif + /*N=9, K=9...28:*/ + 265729, 598417, 1256465, 2485825, 4673345, 8405905, 14546705, 24331777, + 39490049, 62390545, 96220561, 145198913, 214828609, 312193553, 446304145, + 628496897, 872893441, 1196924561, 1621925137, 2173806145U, +#if defined(CUSTOM_MODES) + /*...29:*/ + 2883810113U, +#endif + /*N=10, K=10...24:*/ + 1462563, 3317445, 7059735, 14218905, 27298155, 50250765, 89129247, 152951073, + 254831667, 413442773, 654862247, 1014889769, 1541911931, 2300409629U, + 3375210671U, + /*N=11, K=11...19:*/ + 8097453, 18474633, 39753273, 81270333, 158819253, 298199265, 540279585, + 948062325, 1616336765, +#if defined(CUSTOM_MODES) + /*...20:*/ + 2684641785U, +#endif + /*N=12, K=12...18:*/ + 45046719, 103274625, 224298231, 464387817, 921406335, 1759885185, + 3248227095U, + /*N=13, K=13...16:*/ + 251595969, 579168825, 1267854873, 2653649025U, + /*N=14, K=14:*/ + 1409933619 +}; + +#if defined(CUSTOM_MODES) +static const opus_uint32 *const CELT_PVQ_U_ROW[15]={ + CELT_PVQ_U_DATA+ 0,CELT_PVQ_U_DATA+ 208,CELT_PVQ_U_DATA+ 415, + CELT_PVQ_U_DATA+ 621,CELT_PVQ_U_DATA+ 826,CELT_PVQ_U_DATA+1030, + CELT_PVQ_U_DATA+1233,CELT_PVQ_U_DATA+1336,CELT_PVQ_U_DATA+1389, + CELT_PVQ_U_DATA+1421,CELT_PVQ_U_DATA+1441,CELT_PVQ_U_DATA+1455, + CELT_PVQ_U_DATA+1464,CELT_PVQ_U_DATA+1470,CELT_PVQ_U_DATA+1473 +}; +#else +static const opus_uint32 *const CELT_PVQ_U_ROW[15]={ + CELT_PVQ_U_DATA+ 0,CELT_PVQ_U_DATA+ 176,CELT_PVQ_U_DATA+ 351, + CELT_PVQ_U_DATA+ 525,CELT_PVQ_U_DATA+ 698,CELT_PVQ_U_DATA+ 870, + CELT_PVQ_U_DATA+1041,CELT_PVQ_U_DATA+1131,CELT_PVQ_U_DATA+1178, + CELT_PVQ_U_DATA+1207,CELT_PVQ_U_DATA+1226,CELT_PVQ_U_DATA+1240, + CELT_PVQ_U_DATA+1248,CELT_PVQ_U_DATA+1254,CELT_PVQ_U_DATA+1257 +}; +#endif + +#if defined(CUSTOM_MODES) +void get_required_bits(opus_int16 *_bits,int _n,int _maxk,int _frac){ + int k; + /*_maxk==0 => there's nothing to do.*/ + celt_assert(_maxk>0); + _bits[0]=0; + for(k=1;k<=_maxk;k++)_bits[k]=log2_frac(CELT_PVQ_V(_n,k),_frac); +} +#endif + +static opus_uint32 icwrs(int _n,const int *_y){ + opus_uint32 i; + int j; + int k; + celt_assert(_n>=2); + j=_n-1; + i=_y[j]<0; + k=abs(_y[j]); + do{ + j--; + i+=CELT_PVQ_U(_n-j,k); + k+=abs(_y[j]); + if(_y[j]<0)i+=CELT_PVQ_U(_n-j,k+1); + } + while(j>0); + return i; +} + +void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ + celt_assert(_k>0); + ec_enc_uint(_enc,icwrs(_n,_y),CELT_PVQ_V(_n,_k)); +} + +static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ + opus_uint32 p; + int s; + int k0; + opus_int16 val; + opus_val32 yy=0; + celt_assert(_k>0); + celt_assert(_n>1); + while(_n>2){ + opus_uint32 q; + /*Lots of pulses case:*/ + if(_k>=_n){ + const opus_uint32 *row; + row=CELT_PVQ_U_ROW[_n]; + /*Are the pulses in this dimension negative?*/ + p=row[_k+1]; + s=-(_i>=p); + _i-=p&s; + /*Count how many pulses were placed in this dimension.*/ + k0=_k; + q=row[_n]; + if(q>_i){ + celt_sig_assert(p>q); + _k=_n; + do p=CELT_PVQ_U_ROW[--_k][_n]; + while(p>_i); + } + else for(p=row[_k];p>_i;p=row[_k])_k--; + _i-=p; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); + } + /*Lots of dimensions case:*/ + else{ + /*Are there any pulses in this dimension at all?*/ + p=CELT_PVQ_U_ROW[_k][_n]; + q=CELT_PVQ_U_ROW[_k+1][_n]; + if(p<=_i&&_i=q); + _i-=q&s; + /*Count how many pulses were placed in this dimension.*/ + k0=_k; + do p=CELT_PVQ_U_ROW[--_k][_n]; + while(p>_i); + _i-=p; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); + } + } + _n--; + } + /*_n==2*/ + p=2*_k+1; + s=-(_i>=p); + _i-=p&s; + k0=_k; + _k=(_i+1)>>1; + if(_k)_i-=2*_k-1; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); + /*_n==1*/ + s=-(int)_i; + val=(_k+s)^s; + *_y=val; + yy=MAC16_16(yy,val,val); + return yy; +} + +opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ + return cwrsi(_n,_k,ec_dec_uint(_dec,CELT_PVQ_V(_n,_k)),_y); +} + +#else /* SMALL_FOOTPRINT */ + +/*Computes the next row/column of any recurrence that obeys the relation + u[i][j]=u[i-1][j]+u[i][j-1]+u[i-1][j-1]. + _ui0 is the base case for the new row/column.*/ +static OPUS_INLINE void unext(opus_uint32 *_ui,unsigned _len,opus_uint32 _ui0){ + opus_uint32 ui1; + unsigned j; + /*This do-while will overrun the array if we don't have storage for at least + 2 values.*/ + j=1; do { + ui1=UADD32(UADD32(_ui[j],_ui[j-1]),_ui0); + _ui[j-1]=_ui0; + _ui0=ui1; + } while (++j<_len); + _ui[j-1]=_ui0; +} + +/*Computes the previous row/column of any recurrence that obeys the relation + u[i-1][j]=u[i][j]-u[i][j-1]-u[i-1][j-1]. + _ui0 is the base case for the new row/column.*/ +static OPUS_INLINE void uprev(opus_uint32 *_ui,unsigned _n,opus_uint32 _ui0){ + opus_uint32 ui1; + unsigned j; + /*This do-while will overrun the array if we don't have storage for at least + 2 values.*/ + j=1; do { + ui1=USUB32(USUB32(_ui[j],_ui[j-1]),_ui0); + _ui[j-1]=_ui0; + _ui0=ui1; + } while (++j<_n); + _ui[j-1]=_ui0; +} + +/*Compute V(_n,_k), as well as U(_n,0..._k+1). + _u: On exit, _u[i] contains U(_n,i) for i in [0..._k+1].*/ +static opus_uint32 ncwrs_urow(unsigned _n,unsigned _k,opus_uint32 *_u){ + opus_uint32 um2; + unsigned len; + unsigned k; + len=_k+2; + /*We require storage at least 3 values (e.g., _k>0).*/ + celt_assert(len>=3); + _u[0]=0; + _u[1]=um2=1; + /*If _n==0, _u[0] should be 1 and the rest should be 0.*/ + /*If _n==1, _u[i] should be 1 for i>1.*/ + celt_assert(_n>=2); + /*If _k==0, the following do-while loop will overflow the buffer.*/ + celt_assert(_k>0); + k=2; + do _u[k]=(k<<1)-1; + while(++k0); + j=0; + do{ + opus_uint32 p; + int s; + int yj; + p=_u[_k+1]; + s=-(_i>=p); + _i-=p&s; + yj=_k; + p=_u[_k]; + while(p>_i)p=_u[--_k]; + _i-=p; + yj-=_k; + val=(yj+s)^s; + _y[j]=val; + yy=MAC16_16(yy,val,val); + uprev(_u,_k+2,0); + } + while(++j<_n); + return yy; +} + +/*Returns the index of the given combination of K elements chosen from a set + of size 1 with associated sign bits. + _y: The vector of pulses, whose sum of absolute values is K. + _k: Returns K.*/ +static OPUS_INLINE opus_uint32 icwrs1(const int *_y,int *_k){ + *_k=abs(_y[0]); + return _y[0]<0; +} + +/*Returns the index of the given combination of K elements chosen from a set + of size _n with associated sign bits. + _y: The vector of pulses, whose sum of absolute values must be _k. + _nc: Returns V(_n,_k).*/ +static OPUS_INLINE opus_uint32 icwrs(int _n,int _k,opus_uint32 *_nc,const int *_y, + opus_uint32 *_u){ + opus_uint32 i; + int j; + int k; + /*We can't unroll the first two iterations of the loop unless _n>=2.*/ + celt_assert(_n>=2); + _u[0]=0; + for(k=1;k<=_k+1;k++)_u[k]=(k<<1)-1; + i=icwrs1(_y+_n-1,&k); + j=_n-2; + i+=_u[k]; + k+=abs(_y[j]); + if(_y[j]<0)i+=_u[k+1]; + while(j-->0){ + unext(_u,_k+2,0); + i+=_u[k]; + k+=abs(_y[j]); + if(_y[j]<0)i+=_u[k+1]; + } + *_nc=_u[k]+_u[k+1]; + return i; +} + +#ifdef CUSTOM_MODES +void get_required_bits(opus_int16 *_bits,int _n,int _maxk,int _frac){ + int k; + /*_maxk==0 => there's nothing to do.*/ + celt_assert(_maxk>0); + _bits[0]=0; + if (_n==1) + { + for (k=1;k<=_maxk;k++) + _bits[k] = 1<<_frac; + } + else { + VARDECL(opus_uint32,u); + SAVE_STACK; + ALLOC(u,_maxk+2U,opus_uint32); + ncwrs_urow(_n,_maxk,u); + for(k=1;k<=_maxk;k++) + _bits[k]=log2_frac(u[k]+u[k+1],_frac); + RESTORE_STACK; + } +} +#endif /* CUSTOM_MODES */ + +void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ + opus_uint32 i; + VARDECL(opus_uint32,u); + opus_uint32 nc; + SAVE_STACK; + celt_assert(_k>0); + ALLOC(u,_k+2U,opus_uint32); + i=icwrs(_n,_k,&nc,_y,u); + ec_enc_uint(_enc,i,nc); + RESTORE_STACK; +} + +opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ + VARDECL(opus_uint32,u); + int ret; + SAVE_STACK; + celt_assert(_k>0); + ALLOC(u,_k+2U,opus_uint32); + ret = cwrsi(_n,_k,ec_dec_uint(_dec,ncwrs_urow(_n,_k,u)),_y,u); + RESTORE_STACK; + return ret; +} + +#endif /* SMALL_FOOTPRINT */ diff --git a/port/third_party/opus/celt/cwrs.h b/port/third_party/opus/celt/cwrs.h new file mode 100644 index 00000000..7cd47174 --- /dev/null +++ b/port/third_party/opus/celt/cwrs.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2007-2009 Timothy B. Terriberry + Written by Timothy B. Terriberry and Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef CWRS_H +#define CWRS_H + +#include "arch.h" +#include "stack_alloc.h" +#include "entenc.h" +#include "entdec.h" + +#ifdef CUSTOM_MODES +int log2_frac(opus_uint32 val, int frac); +#endif + +void get_required_bits(opus_int16 *bits, int N, int K, int frac); + +void encode_pulses(const int *_y, int N, int K, ec_enc *enc); + +opus_val32 decode_pulses(int *_y, int N, int K, ec_dec *dec); + +#endif /* CWRS_H */ diff --git a/port/third_party/opus/celt/ecintrin.h b/port/third_party/opus/celt/ecintrin.h new file mode 100644 index 00000000..66a4c36e --- /dev/null +++ b/port/third_party/opus/celt/ecintrin.h @@ -0,0 +1,91 @@ +/* Copyright (c) 2003-2008 Timothy B. Terriberry + Copyright (c) 2008 Xiph.Org Foundation */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +/*Some common macros for potential platform-specific optimization.*/ +#include "opus_types.h" +#include +#include +#include "arch.h" +#if !defined(_ecintrin_H) +# define _ecintrin_H (1) + +/*Some specific platforms may have optimized intrinsic or OPUS_INLINE assembly + versions of these functions which can substantially improve performance. + We define macros for them to allow easy incorporation of these non-ANSI + features.*/ + +/*Modern gcc (4.x) can compile the naive versions of min and max with cmov if + given an appropriate architecture, but the branchless bit-twiddling versions + are just as fast, and do not require any special target architecture. + Earlier gcc versions (3.x) compiled both code to the same assembly + instructions, because of the way they represented ((_b)>(_a)) internally.*/ +# define EC_MINI(_a,_b) ((_a)+(((_b)-(_a))&-((_b)<(_a)))) + +/*Count leading zeros. + This macro should only be used for implementing ec_ilog(), if it is defined. + All other code should use EC_ILOG() instead.*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#if defined(_MSC_VER) && (_MSC_VER >= 1910) +# include /* Improve compiler throughput. */ +#else +# include +#endif +/*In _DEBUG mode this is not an intrinsic by default.*/ +# pragma intrinsic(_BitScanReverse) + +static __inline int ec_bsr(unsigned long _x){ + unsigned long ret; + _BitScanReverse(&ret,_x); + return (int)ret; +} +# define EC_CLZ0 (1) +# define EC_CLZ(_x) (-ec_bsr(_x)) +#elif defined(ENABLE_TI_DSPLIB) +# include "dsplib.h" +# define EC_CLZ0 (31) +# define EC_CLZ(_x) (_lnorm(_x)) +#elif __GNUC_PREREQ(3,4) +# if INT_MAX>=2147483647 +# define EC_CLZ0 ((int)sizeof(unsigned)*CHAR_BIT) +# define EC_CLZ(_x) (__builtin_clz(_x)) +# elif LONG_MAX>=2147483647L +# define EC_CLZ0 ((int)sizeof(unsigned long)*CHAR_BIT) +# define EC_CLZ(_x) (__builtin_clzl(_x)) +# endif +#endif + +#if defined(EC_CLZ) +/*Note that __builtin_clz is not defined when _x==0, according to the gcc + documentation (and that of the BSR instruction that implements it on x86). + The majority of the time we can never pass it zero. + When we need to, it can be special cased.*/ +# define EC_ILOG(_x) (EC_CLZ0-EC_CLZ(_x)) +#else +int ec_ilog(opus_uint32 _v); +# define EC_ILOG(_x) (ec_ilog(_x)) +#endif +#endif diff --git a/port/third_party/opus/celt/entcode.c b/port/third_party/opus/celt/entcode.c new file mode 100644 index 00000000..70f32016 --- /dev/null +++ b/port/third_party/opus/celt/entcode.c @@ -0,0 +1,153 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry +*/ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "entcode.h" +#include "arch.h" + +#if !defined(EC_CLZ) +/*This is a fallback for systems where we don't know how to access + a BSR or CLZ instruction (see ecintrin.h). + If you are optimizing Opus on a new platform and it has a native CLZ or + BZR (e.g. cell, MIPS, x86, etc) then making it available to Opus will be + an easy performance win.*/ +int ec_ilog(opus_uint32 _v){ + /*On a Pentium M, this branchless version tested as the fastest on + 1,000,000,000 random 32-bit integers, edging out a similar version with + branches, and a 256-entry LUT version.*/ + int ret; + int m; + ret=!!_v; + m=!!(_v&0xFFFF0000)<<4; + _v>>=m; + ret|=m; + m=!!(_v&0xFF00)<<3; + _v>>=m; + ret|=m; + m=!!(_v&0xF0)<<2; + _v>>=m; + ret|=m; + m=!!(_v&0xC)<<1; + _v>>=m; + ret|=m; + ret+=!!(_v&0x2); + return ret; +} +#endif + +#if 1 +/* This is a faster version of ec_tell_frac() that takes advantage + of the low (1/8 bit) resolution to use just a linear function + followed by a lookup to determine the exact transition thresholds. */ +opus_uint32 ec_tell_frac(ec_ctx *_this){ + static const unsigned correction[8] = + {35733, 38967, 42495, 46340, + 50535, 55109, 60097, 65535}; + opus_uint32 nbits; + opus_uint32 r; + int l; + unsigned b; + nbits=_this->nbits_total<rng); + r=_this->rng>>(l-16); + b = (r>>12)-8; + b += r>correction[b]; + l = (l<<3)+b; + return nbits-l; +} +#else +opus_uint32 ec_tell_frac(ec_ctx *_this){ + opus_uint32 nbits; + opus_uint32 r; + int l; + int i; + /*To handle the non-integral number of bits still left in the encoder/decoder + state, we compute the worst-case number of bits of val that must be + encoded to ensure that the value is inside the range for any possible + subsequent bits. + The computation here is independent of val itself (the decoder does not + even track that value), even though the real number of bits used after + ec_enc_done() may be 1 smaller if rng is a power of two and the + corresponding trailing bits of val are all zeros. + If we did try to track that special case, then coding a value with a + probability of 1/(1<nbits_total<rng); + r=_this->rng>>(l-16); + for(i=BITRES;i-->0;){ + int b; + r=r*r>>15; + b=(int)(r>>16); + l=l<<1|b; + r>>=b; + } + return nbits-l; +} +#endif + +#ifdef USE_SMALL_DIV_TABLE +/* Result of 2^32/(2*i+1), except for i=0. */ +const opus_uint32 SMALL_DIV_TABLE[129] = { + 0xFFFFFFFF, 0x55555555, 0x33333333, 0x24924924, + 0x1C71C71C, 0x1745D174, 0x13B13B13, 0x11111111, + 0x0F0F0F0F, 0x0D79435E, 0x0C30C30C, 0x0B21642C, + 0x0A3D70A3, 0x097B425E, 0x08D3DCB0, 0x08421084, + 0x07C1F07C, 0x07507507, 0x06EB3E45, 0x06906906, + 0x063E7063, 0x05F417D0, 0x05B05B05, 0x0572620A, + 0x05397829, 0x05050505, 0x04D4873E, 0x04A7904A, + 0x047DC11F, 0x0456C797, 0x04325C53, 0x04104104, + 0x03F03F03, 0x03D22635, 0x03B5CC0E, 0x039B0AD1, + 0x0381C0E0, 0x0369D036, 0x03531DEC, 0x033D91D2, + 0x0329161F, 0x03159721, 0x03030303, 0x02F14990, + 0x02E05C0B, 0x02D02D02, 0x02C0B02C, 0x02B1DA46, + 0x02A3A0FD, 0x0295FAD4, 0x0288DF0C, 0x027C4597, + 0x02702702, 0x02647C69, 0x02593F69, 0x024E6A17, + 0x0243F6F0, 0x0239E0D5, 0x02302302, 0x0226B902, + 0x021D9EAD, 0x0214D021, 0x020C49BA, 0x02040810, + 0x01FC07F0, 0x01F44659, 0x01ECC07B, 0x01E573AC, + 0x01DE5D6E, 0x01D77B65, 0x01D0CB58, 0x01CA4B30, + 0x01C3F8F0, 0x01BDD2B8, 0x01B7D6C3, 0x01B20364, + 0x01AC5701, 0x01A6D01A, 0x01A16D3F, 0x019C2D14, + 0x01970E4F, 0x01920FB4, 0x018D3018, 0x01886E5F, + 0x0183C977, 0x017F405F, 0x017AD220, 0x01767DCE, + 0x01724287, 0x016E1F76, 0x016A13CD, 0x01661EC6, + 0x01623FA7, 0x015E75BB, 0x015AC056, 0x01571ED3, + 0x01539094, 0x01501501, 0x014CAB88, 0x0149539E, + 0x01460CBC, 0x0142D662, 0x013FB013, 0x013C995A, + 0x013991C2, 0x013698DF, 0x0133AE45, 0x0130D190, + 0x012E025C, 0x012B404A, 0x01288B01, 0x0125E227, + 0x01234567, 0x0120B470, 0x011E2EF3, 0x011BB4A4, + 0x01194538, 0x0116E068, 0x011485F0, 0x0112358E, + 0x010FEF01, 0x010DB20A, 0x010B7E6E, 0x010953F3, + 0x01073260, 0x0105197F, 0x0103091B, 0x01010101 +}; +#endif diff --git a/port/third_party/opus/celt/entcode.h b/port/third_party/opus/celt/entcode.h new file mode 100644 index 00000000..3763e3f2 --- /dev/null +++ b/port/third_party/opus/celt/entcode.h @@ -0,0 +1,152 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#include "opus_types.h" +#include "opus_defines.h" + +#if !defined(_entcode_H) +# define _entcode_H (1) +# include +# include +# include "ecintrin.h" + +extern const opus_uint32 SMALL_DIV_TABLE[129]; + +#ifdef OPUS_ARM_ASM +#define USE_SMALL_DIV_TABLE +#endif + +/*OPT: ec_window must be at least 32 bits, but if you have fast arithmetic on a + larger type, you can speed up the decoder by using it here.*/ +typedef opus_uint32 ec_window; +typedef struct ec_ctx ec_ctx; +typedef struct ec_ctx ec_enc; +typedef struct ec_ctx ec_dec; + +# define EC_WINDOW_SIZE ((int)sizeof(ec_window)*CHAR_BIT) + +/*The number of bits to use for the range-coded part of unsigned integers.*/ +# define EC_UINT_BITS (8) + +/*The resolution of fractional-precision bit usage measurements, i.e., + 3 => 1/8th bits.*/ +# define BITRES 3 + +/*The entropy encoder/decoder context. + We use the same structure for both, so that common functions like ec_tell() + can be used on either one.*/ +struct ec_ctx{ + /*Buffered input/output.*/ + unsigned char *buf; + /*The size of the buffer.*/ + opus_uint32 storage; + /*The offset at which the last byte containing raw bits was read/written.*/ + opus_uint32 end_offs; + /*Bits that will be read from/written at the end.*/ + ec_window end_window; + /*Number of valid bits in end_window.*/ + int nend_bits; + /*The total number of whole bits read/written. + This does not include partial bits currently in the range coder.*/ + int nbits_total; + /*The offset at which the next range coder byte will be read/written.*/ + opus_uint32 offs; + /*The number of values in the current range.*/ + opus_uint32 rng; + /*In the decoder: the difference between the top of the current range and + the input value, minus one. + In the encoder: the low end of the current range.*/ + opus_uint32 val; + /*In the decoder: the saved normalization factor from ec_decode(). + In the encoder: the number of oustanding carry propagating symbols.*/ + opus_uint32 ext; + /*A buffered input/output symbol, awaiting carry propagation.*/ + int rem; + /*Nonzero if an error occurred.*/ + int error; +}; + +static OPUS_INLINE opus_uint32 ec_range_bytes(ec_ctx *_this){ + return _this->offs; +} + +static OPUS_INLINE unsigned char *ec_get_buffer(ec_ctx *_this){ + return _this->buf; +} + +static OPUS_INLINE int ec_get_error(ec_ctx *_this){ + return _this->error; +} + +/*Returns the number of bits "used" by the encoded or decoded symbols so far. + This same number can be computed in either the encoder or the decoder, and is + suitable for making coding decisions. + Return: The number of bits. + This will always be slightly larger than the exact value (e.g., all + rounding error is in the positive direction).*/ +static OPUS_INLINE int ec_tell(ec_ctx *_this){ + return _this->nbits_total-EC_ILOG(_this->rng); +} + +/*Returns the number of bits "used" by the encoded or decoded symbols so far. + This same number can be computed in either the encoder or the decoder, and is + suitable for making coding decisions. + Return: The number of bits scaled by 2**BITRES. + This will always be slightly larger than the exact value (e.g., all + rounding error is in the positive direction).*/ +opus_uint32 ec_tell_frac(ec_ctx *_this); + +/* Tested exhaustively for all n and for 1<=d<=256 */ +static OPUS_INLINE opus_uint32 celt_udiv(opus_uint32 n, opus_uint32 d) { + celt_sig_assert(d>0); +#ifdef USE_SMALL_DIV_TABLE + if (d>256) + return n/d; + else { + opus_uint32 t, q; + t = EC_ILOG(d&-d); + q = (opus_uint64)SMALL_DIV_TABLE[d>>t]*(n>>(t-1))>>32; + return q+(n-q*d >= d); + } +#else + return n/d; +#endif +} + +static OPUS_INLINE opus_int32 celt_sudiv(opus_int32 n, opus_int32 d) { + celt_sig_assert(d>0); +#ifdef USE_SMALL_DIV_TABLE + if (n<0) + return -(opus_int32)celt_udiv(-n, d); + else + return celt_udiv(n, d); +#else + return n/d; +#endif +} + +#endif diff --git a/port/third_party/opus/celt/entdec.c b/port/third_party/opus/celt/entdec.c new file mode 100644 index 00000000..027aa24b --- /dev/null +++ b/port/third_party/opus/celt/entdec.c @@ -0,0 +1,266 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "os_support.h" +#include "arch.h" +#include "entdec.h" +#include "mfrngcod.h" + +/*A range decoder. + This is an entropy decoder based upon \cite{Mar79}, which is itself a + rediscovery of the FIFO arithmetic code introduced by \cite{Pas76}. + It is very similar to arithmetic encoding, except that encoding is done with + digits in any base, instead of with bits, and so it is faster when using + larger bases (i.e.: a byte). + The author claims an average waste of $\frac{1}{2}\log_b(2b)$ bits, where $b$ + is the base, longer than the theoretical optimum, but to my knowledge there + is no published justification for this claim. + This only seems true when using near-infinite precision arithmetic so that + the process is carried out with no rounding errors. + + An excellent description of implementation details is available at + http://www.arturocampos.com/ac_range.html + A recent work \cite{MNW98} which proposes several changes to arithmetic + encoding for efficiency actually re-discovers many of the principles + behind range encoding, and presents a good theoretical analysis of them. + + End of stream is handled by writing out the smallest number of bits that + ensures that the stream will be correctly decoded regardless of the value of + any subsequent bits. + ec_tell() can be used to determine how many bits were needed to decode + all the symbols thus far; other data can be packed in the remaining bits of + the input buffer. + @PHDTHESIS{Pas76, + author="Richard Clark Pasco", + title="Source coding algorithms for fast data compression", + school="Dept. of Electrical Engineering, Stanford University", + address="Stanford, CA", + month=May, + year=1976 + } + @INPROCEEDINGS{Mar79, + author="Martin, G.N.N.", + title="Range encoding: an algorithm for removing redundancy from a digitised + message", + booktitle="Video & Data Recording Conference", + year=1979, + address="Southampton", + month=Jul + } + @ARTICLE{MNW98, + author="Alistair Moffat and Radford Neal and Ian H. Witten", + title="Arithmetic Coding Revisited", + journal="{ACM} Transactions on Information Systems", + year=1998, + volume=16, + number=3, + pages="256--294", + month=Jul, + URL="http://www.stanford.edu/class/ee398a/handouts/papers/Moffat98ArithmCoding.pdf" + }*/ + +static int ec_read_byte(ec_dec *_this){ + return _this->offs<_this->storage?_this->buf[_this->offs++]:0; +} + +static int ec_read_byte_from_end(ec_dec *_this){ + return _this->end_offs<_this->storage? + _this->buf[_this->storage-++(_this->end_offs)]:0; +} + +/*Normalizes the contents of val and rng so that rng lies entirely in the + high-order symbol.*/ +static void ec_dec_normalize(ec_dec *_this){ + /*If the range is too small, rescale it and input some bits.*/ + while(_this->rng<=EC_CODE_BOT){ + int sym; + _this->nbits_total+=EC_SYM_BITS; + _this->rng<<=EC_SYM_BITS; + /*Use up the remaining bits from our last symbol.*/ + sym=_this->rem; + /*Read the next value from the input.*/ + _this->rem=ec_read_byte(_this); + /*Take the rest of the bits we need from this new symbol.*/ + sym=(sym<rem)>>(EC_SYM_BITS-EC_CODE_EXTRA); + /*And subtract them from val, capped to be less than EC_CODE_TOP.*/ + _this->val=((_this->val<buf=_buf; + _this->storage=_storage; + _this->end_offs=0; + _this->end_window=0; + _this->nend_bits=0; + /*This is the offset from which ec_tell() will subtract partial bits. + The final value after the ec_dec_normalize() call will be the same as in + the encoder, but we have to compensate for the bits that are added there.*/ + _this->nbits_total=EC_CODE_BITS+1 + -((EC_CODE_BITS-EC_CODE_EXTRA)/EC_SYM_BITS)*EC_SYM_BITS; + _this->offs=0; + _this->rng=1U<rem=ec_read_byte(_this); + _this->val=_this->rng-1-(_this->rem>>(EC_SYM_BITS-EC_CODE_EXTRA)); + _this->error=0; + /*Normalize the interval.*/ + ec_dec_normalize(_this); +} + +unsigned ec_decode(ec_dec *_this,unsigned _ft){ + unsigned s; + _this->ext=celt_udiv(_this->rng,_ft); + s=(unsigned)(_this->val/_this->ext); + return _ft-EC_MINI(s+1,_ft); +} + +unsigned ec_decode_bin(ec_dec *_this,unsigned _bits){ + unsigned s; + _this->ext=_this->rng>>_bits; + s=(unsigned)(_this->val/_this->ext); + return (1U<<_bits)-EC_MINI(s+1U,1U<<_bits); +} + +void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft){ + opus_uint32 s; + s=IMUL32(_this->ext,_ft-_fh); + _this->val-=s; + _this->rng=_fl>0?IMUL32(_this->ext,_fh-_fl):_this->rng-s; + ec_dec_normalize(_this); +} + +/*The probability of having a "one" is 1/(1<<_logp).*/ +int ec_dec_bit_logp(ec_dec *_this,unsigned _logp){ + opus_uint32 r; + opus_uint32 d; + opus_uint32 s; + int ret; + r=_this->rng; + d=_this->val; + s=r>>_logp; + ret=dval=d-s; + _this->rng=ret?s:r-s; + ec_dec_normalize(_this); + return ret; +} + +int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb){ + opus_uint32 r; + opus_uint32 d; + opus_uint32 s; + opus_uint32 t; + int ret; + s=_this->rng; + d=_this->val; + r=s>>_ftb; + ret=-1; + do{ + t=s; + s=IMUL32(r,_icdf[++ret]); + } + while(dval=d-s; + _this->rng=t-s; + ec_dec_normalize(_this); + return ret; +} + +int ec_dec_icdf16(ec_dec *_this,const opus_uint16 *_icdf,unsigned _ftb){ + opus_uint32 r; + opus_uint32 d; + opus_uint32 s; + opus_uint32 t; + int ret; + s=_this->rng; + d=_this->val; + r=s>>_ftb; + ret=-1; + do{ + t=s; + s=IMUL32(r,_icdf[++ret]); + } + while(dval=d-s; + _this->rng=t-s; + ec_dec_normalize(_this); + return ret; +} + +opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft){ + unsigned ft; + unsigned s; + int ftb; + /*In order to optimize EC_ILOG(), it is undefined for the value 0.*/ + celt_assert(_ft>1); + _ft--; + ftb=EC_ILOG(_ft); + if(ftb>EC_UINT_BITS){ + opus_uint32 t; + ftb-=EC_UINT_BITS; + ft=(unsigned)(_ft>>ftb)+1; + s=ec_decode(_this,ft); + ec_dec_update(_this,s,s+1,ft); + t=(opus_uint32)s<error=1; + return _ft; + } + else{ + _ft++; + s=ec_decode(_this,(unsigned)_ft); + ec_dec_update(_this,s,s+1,(unsigned)_ft); + return s; + } +} + +opus_uint32 ec_dec_bits(ec_dec *_this,unsigned _bits){ + ec_window window; + int available; + opus_uint32 ret; + window=_this->end_window; + available=_this->nend_bits; + if((unsigned)available<_bits){ + do{ + window|=(ec_window)ec_read_byte_from_end(_this)<>=_bits; + available-=_bits; + _this->end_window=window; + _this->nend_bits=available; + _this->nbits_total+=_bits; + return ret; +} diff --git a/port/third_party/opus/celt/entdec.h b/port/third_party/opus/celt/entdec.h new file mode 100644 index 00000000..c81f26fd --- /dev/null +++ b/port/third_party/opus/celt/entdec.h @@ -0,0 +1,110 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#if !defined(_entdec_H) +# define _entdec_H (1) +# include +# include "entcode.h" + +/*Initializes the decoder. + _buf: The input buffer to use. + Return: 0 on success, or a negative value on error.*/ +void ec_dec_init(ec_dec *_this,unsigned char *_buf,opus_uint32 _storage); + +/*Calculates the cumulative frequency for the next symbol. + This can then be fed into the probability model to determine what that + symbol is, and the additional frequency information required to advance to + the next symbol. + This function cannot be called more than once without a corresponding call to + ec_dec_update(), or decoding will not proceed correctly. + _ft: The total frequency of the symbols in the alphabet the next symbol was + encoded with. + Return: A cumulative frequency representing the encoded symbol. + If the cumulative frequency of all the symbols before the one that + was encoded was fl, and the cumulative frequency of all the symbols + up to and including the one encoded is fh, then the returned value + will fall in the range [fl,fh).*/ +unsigned ec_decode(ec_dec *_this,unsigned _ft); + +/*Equivalent to ec_decode() with _ft==1<<_bits.*/ +unsigned ec_decode_bin(ec_dec *_this,unsigned _bits); + +/*Advance the decoder past the next symbol using the frequency information the + symbol was encoded with. + Exactly one call to ec_decode() must have been made so that all necessary + intermediate calculations are performed. + _fl: The cumulative frequency of all symbols that come before the symbol + decoded. + _fh: The cumulative frequency of all symbols up to and including the symbol + decoded. + Together with _fl, this defines the range [_fl,_fh) in which the value + returned above must fall. + _ft: The total frequency of the symbols in the alphabet the symbol decoded + was encoded in. + This must be the same as passed to the preceding call to ec_decode().*/ +void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft); + +/* Decode a bit that has a 1/(1<<_logp) probability of being a one */ +int ec_dec_bit_logp(ec_dec *_this,unsigned _logp); + +/*Decodes a symbol given an "inverse" CDF table. + No call to ec_dec_update() is necessary after this call. + _icdf: The "inverse" CDF, such that symbol s falls in the range + [s>0?ft-_icdf[s-1]:0,ft-_icdf[s]), where ft=1<<_ftb. + The values must be monotonically non-increasing, and the last value + must be 0. + _ftb: The number of bits of precision in the cumulative distribution. + Return: The decoded symbol s.*/ +int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb); + +/*Decodes a symbol given an "inverse" CDF table. + No call to ec_dec_update() is necessary after this call. + _icdf: The "inverse" CDF, such that symbol s falls in the range + [s>0?ft-_icdf[s-1]:0,ft-_icdf[s]), where ft=1<<_ftb. + The values must be monotonically non-increasing, and the last value + must be 0. + _ftb: The number of bits of precision in the cumulative distribution. + Return: The decoded symbol s.*/ +int ec_dec_icdf16(ec_dec *_this,const opus_uint16 *_icdf,unsigned _ftb); + +/*Extracts a raw unsigned integer with a non-power-of-2 range from the stream. + The bits must have been encoded with ec_enc_uint(). + No call to ec_dec_update() is necessary after this call. + _ft: The number of integers that can be decoded (one more than the max). + This must be at least 2, and no more than 2**32-1. + Return: The decoded bits.*/ +opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft); + +/*Extracts a sequence of raw bits from the stream. + The bits must have been encoded with ec_enc_bits(). + No call to ec_dec_update() is necessary after this call. + _ftb: The number of bits to extract. + This must be between 0 and 25, inclusive. + Return: The decoded bits.*/ +opus_uint32 ec_dec_bits(ec_dec *_this,unsigned _ftb); + +#endif diff --git a/port/third_party/opus/celt/entenc.c b/port/third_party/opus/celt/entenc.c new file mode 100644 index 00000000..69c6f835 --- /dev/null +++ b/port/third_party/opus/celt/entenc.c @@ -0,0 +1,305 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#if defined(HAVE_CONFIG_H) +# include "config.h" +#endif +#include "os_support.h" +#include "arch.h" +#include "entenc.h" +#include "mfrngcod.h" + +/*A range encoder. + See entdec.c and the references for implementation details \cite{Mar79,MNW98}. + + @INPROCEEDINGS{Mar79, + author="Martin, G.N.N.", + title="Range encoding: an algorithm for removing redundancy from a digitised + message", + booktitle="Video \& Data Recording Conference", + year=1979, + address="Southampton", + month=Jul + } + @ARTICLE{MNW98, + author="Alistair Moffat and Radford Neal and Ian H. Witten", + title="Arithmetic Coding Revisited", + journal="{ACM} Transactions on Information Systems", + year=1998, + volume=16, + number=3, + pages="256--294", + month=Jul, + URL="http://www.stanford.edu/class/ee398/handouts/papers/Moffat98ArithmCoding.pdf" + }*/ + +static int ec_write_byte(ec_enc *_this,unsigned _value){ + if(_this->offs+_this->end_offs>=_this->storage)return -1; + _this->buf[_this->offs++]=(unsigned char)_value; + return 0; +} + +static int ec_write_byte_at_end(ec_enc *_this,unsigned _value){ + if(_this->offs+_this->end_offs>=_this->storage)return -1; + _this->buf[_this->storage-++(_this->end_offs)]=(unsigned char)_value; + return 0; +} + +/*Outputs a symbol, with a carry bit. + If there is a potential to propagate a carry over several symbols, they are + buffered until it can be determined whether or not an actual carry will + occur. + If the counter for the buffered symbols overflows, then the stream becomes + undecodable. + This gives a theoretical limit of a few billion symbols in a single packet on + 32-bit systems. + The alternative is to truncate the range in order to force a carry, but + requires similar carry tracking in the decoder, needlessly slowing it down.*/ +static void ec_enc_carry_out(ec_enc *_this,int _c){ + if(_c!=EC_SYM_MAX){ + /*No further carry propagation possible, flush buffer.*/ + int carry; + carry=_c>>EC_SYM_BITS; + /*Don't output a byte on the first write. + This compare should be taken care of by branch-prediction thereafter.*/ + if(_this->rem>=0)_this->error|=ec_write_byte(_this,_this->rem+carry); + if(_this->ext>0){ + unsigned sym; + sym=(EC_SYM_MAX+carry)&EC_SYM_MAX; + do _this->error|=ec_write_byte(_this,sym); + while(--(_this->ext)>0); + } + _this->rem=_c&EC_SYM_MAX; + } + else _this->ext++; +} + +static OPUS_INLINE void ec_enc_normalize(ec_enc *_this){ + /*If the range is too small, output some bits and rescale it.*/ + while(_this->rng<=EC_CODE_BOT){ + ec_enc_carry_out(_this,(int)(_this->val>>EC_CODE_SHIFT)); + /*Move the next-to-high-order symbol into the high-order position.*/ + _this->val=(_this->val<rng<<=EC_SYM_BITS; + _this->nbits_total+=EC_SYM_BITS; + } +} + +void ec_enc_init(ec_enc *_this,unsigned char *_buf,opus_uint32 _size){ + _this->buf=_buf; + _this->end_offs=0; + _this->end_window=0; + _this->nend_bits=0; + /*This is the offset from which ec_tell() will subtract partial bits.*/ + _this->nbits_total=EC_CODE_BITS+1; + _this->offs=0; + _this->rng=EC_CODE_TOP; + _this->rem=-1; + _this->val=0; + _this->ext=0; + _this->storage=_size; + _this->error=0; +} + +void ec_encode(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _ft){ + opus_uint32 r; + r=celt_udiv(_this->rng,_ft); + if(_fl>0){ + _this->val+=_this->rng-IMUL32(r,(_ft-_fl)); + _this->rng=IMUL32(r,(_fh-_fl)); + } + else _this->rng-=IMUL32(r,(_ft-_fh)); + ec_enc_normalize(_this); +} + +void ec_encode_bin(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _bits){ + opus_uint32 r; + r=_this->rng>>_bits; + if(_fl>0){ + _this->val+=_this->rng-IMUL32(r,((1U<<_bits)-_fl)); + _this->rng=IMUL32(r,(_fh-_fl)); + } + else _this->rng-=IMUL32(r,((1U<<_bits)-_fh)); + ec_enc_normalize(_this); +} + +/*The probability of having a "one" is 1/(1<<_logp).*/ +void ec_enc_bit_logp(ec_enc *_this,int _val,unsigned _logp){ + opus_uint32 r; + opus_uint32 s; + opus_uint32 l; + r=_this->rng; + l=_this->val; + s=r>>_logp; + r-=s; + if(_val)_this->val=l+r; + _this->rng=_val?s:r; + ec_enc_normalize(_this); +} + +void ec_enc_icdf(ec_enc *_this,int _s,const unsigned char *_icdf,unsigned _ftb){ + opus_uint32 r; + r=_this->rng>>_ftb; + if(_s>0){ + _this->val+=_this->rng-IMUL32(r,_icdf[_s-1]); + _this->rng=IMUL32(r,_icdf[_s-1]-_icdf[_s]); + } + else _this->rng-=IMUL32(r,_icdf[_s]); + ec_enc_normalize(_this); +} + +void ec_enc_icdf16(ec_enc *_this,int _s,const opus_uint16 *_icdf,unsigned _ftb){ + opus_uint32 r; + r=_this->rng>>_ftb; + if(_s>0){ + _this->val+=_this->rng-IMUL32(r,_icdf[_s-1]); + _this->rng=IMUL32(r,_icdf[_s-1]-_icdf[_s]); + } + else _this->rng-=IMUL32(r,_icdf[_s]); + ec_enc_normalize(_this); +} + +void ec_enc_uint(ec_enc *_this,opus_uint32 _fl,opus_uint32 _ft){ + unsigned ft; + unsigned fl; + int ftb; + /*In order to optimize EC_ILOG(), it is undefined for the value 0.*/ + celt_assert(_ft>1); + _ft--; + ftb=EC_ILOG(_ft); + if(ftb>EC_UINT_BITS){ + ftb-=EC_UINT_BITS; + ft=(_ft>>ftb)+1; + fl=(unsigned)(_fl>>ftb); + ec_encode(_this,fl,fl+1,ft); + ec_enc_bits(_this,_fl&(((opus_uint32)1<end_window; + used=_this->nend_bits; + celt_assert(_bits>0); + if(used+_bits>EC_WINDOW_SIZE){ + do{ + _this->error|=ec_write_byte_at_end(_this,(unsigned)window&EC_SYM_MAX); + window>>=EC_SYM_BITS; + used-=EC_SYM_BITS; + } + while(used>=EC_SYM_BITS); + } + window|=(ec_window)_fl<end_window=window; + _this->nend_bits=used; + _this->nbits_total+=_bits; +} + +void ec_enc_patch_initial_bits(ec_enc *_this,unsigned _val,unsigned _nbits){ + int shift; + unsigned mask; + celt_assert(_nbits<=EC_SYM_BITS); + shift=EC_SYM_BITS-_nbits; + mask=((1<<_nbits)-1)<offs>0){ + /*The first byte has been finalized.*/ + _this->buf[0]=(unsigned char)((_this->buf[0]&~mask)|_val<rem>=0){ + /*The first byte is still awaiting carry propagation.*/ + _this->rem=(_this->rem&~mask)|_val<rng<=(EC_CODE_TOP>>_nbits)){ + /*The renormalization loop has never been run.*/ + _this->val=(_this->val&~((opus_uint32)mask<error=-1; +} + +void ec_enc_shrink(ec_enc *_this,opus_uint32 _size){ + celt_assert(_this->offs+_this->end_offs<=_size); + OPUS_MOVE(_this->buf+_size-_this->end_offs, + _this->buf+_this->storage-_this->end_offs,_this->end_offs); + _this->storage=_size; +} + +void ec_enc_done(ec_enc *_this){ + ec_window window; + int used; + opus_uint32 msk; + opus_uint32 end; + int l; + /*We output the minimum number of bits that ensures that the symbols encoded + thus far will be decoded correctly regardless of the bits that follow.*/ + l=EC_CODE_BITS-EC_ILOG(_this->rng); + msk=(EC_CODE_TOP-1)>>l; + end=(_this->val+msk)&~msk; + if((end|msk)>=_this->val+_this->rng){ + l++; + msk>>=1; + end=(_this->val+msk)&~msk; + } + while(l>0){ + ec_enc_carry_out(_this,(int)(end>>EC_CODE_SHIFT)); + end=(end<rem>=0||_this->ext>0)ec_enc_carry_out(_this,0); + /*If we have buffered extra bits, flush them as well.*/ + window=_this->end_window; + used=_this->nend_bits; + while(used>=EC_SYM_BITS){ + _this->error|=ec_write_byte_at_end(_this,(unsigned)window&EC_SYM_MAX); + window>>=EC_SYM_BITS; + used-=EC_SYM_BITS; + } + /*Clear any excess space and add any remaining extra bits to the last byte.*/ + if(!_this->error){ + OPUS_CLEAR(_this->buf+_this->offs, + _this->storage-_this->offs-_this->end_offs); + if(used>0){ + /*If there's no range coder data at all, give up.*/ + if(_this->end_offs>=_this->storage)_this->error=-1; + else{ + l=-l; + /*If we've busted, don't add too many extra bits to the last byte; it + would corrupt the range coder data, and that's more important.*/ + if(_this->offs+_this->end_offs>=_this->storage&&lerror=-1; + } + _this->buf[_this->storage-_this->end_offs-1]|=(unsigned char)window; + } + } + } +} diff --git a/port/third_party/opus/celt/entenc.h b/port/third_party/opus/celt/entenc.h new file mode 100644 index 00000000..010874bb --- /dev/null +++ b/port/third_party/opus/celt/entenc.h @@ -0,0 +1,119 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#if !defined(_entenc_H) +# define _entenc_H (1) +# include +# include "entcode.h" + +/*Initializes the encoder. + _buf: The buffer to store output bytes in. + _size: The size of the buffer, in chars.*/ +void ec_enc_init(ec_enc *_this,unsigned char *_buf,opus_uint32 _size); +/*Encodes a symbol given its frequency information. + The frequency information must be discernable by the decoder, assuming it + has read only the previous symbols from the stream. + It is allowable to change the frequency information, or even the entire + source alphabet, so long as the decoder can tell from the context of the + previously encoded information that it is supposed to do so as well. + _fl: The cumulative frequency of all symbols that come before the one to be + encoded. + _fh: The cumulative frequency of all symbols up to and including the one to + be encoded. + Together with _fl, this defines the range [_fl,_fh) in which the + decoded value will fall. + _ft: The sum of the frequencies of all the symbols*/ +void ec_encode(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _ft); + +/*Equivalent to ec_encode() with _ft==1<<_bits.*/ +void ec_encode_bin(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _bits); + +/* Encode a bit that has a 1/(1<<_logp) probability of being a one */ +void ec_enc_bit_logp(ec_enc *_this,int _val,unsigned _logp); + +/*Encodes a symbol given an "inverse" CDF table. + _s: The index of the symbol to encode. + _icdf: The "inverse" CDF, such that symbol _s falls in the range + [_s>0?ft-_icdf[_s-1]:0,ft-_icdf[_s]), where ft=1<<_ftb. + The values must be monotonically non-increasing, and the last value + must be 0. + _ftb: The number of bits of precision in the cumulative distribution.*/ +void ec_enc_icdf(ec_enc *_this,int _s,const unsigned char *_icdf,unsigned _ftb); + +/*Encodes a symbol given an "inverse" CDF table. + _s: The index of the symbol to encode. + _icdf: The "inverse" CDF, such that symbol _s falls in the range + [_s>0?ft-_icdf[_s-1]:0,ft-_icdf[_s]), where ft=1<<_ftb. + The values must be monotonically non-increasing, and the last value + must be 0. + _ftb: The number of bits of precision in the cumulative distribution.*/ +void ec_enc_icdf16(ec_enc *_this,int _s,const opus_uint16 *_icdf,unsigned _ftb); + +/*Encodes a raw unsigned integer in the stream. + _fl: The integer to encode. + _ft: The number of integers that can be encoded (one more than the max). + This must be at least 2, and no more than 2**32-1.*/ +void ec_enc_uint(ec_enc *_this,opus_uint32 _fl,opus_uint32 _ft); + +/*Encodes a sequence of raw bits in the stream. + _fl: The bits to encode. + _ftb: The number of bits to encode. + This must be between 1 and 25, inclusive.*/ +void ec_enc_bits(ec_enc *_this,opus_uint32 _fl,unsigned _ftb); + +/*Overwrites a few bits at the very start of an existing stream, after they + have already been encoded. + This makes it possible to have a few flags up front, where it is easy for + decoders to access them without parsing the whole stream, even if their + values are not determined until late in the encoding process, without having + to buffer all the intermediate symbols in the encoder. + In order for this to work, at least _nbits bits must have already been + encoded using probabilities that are an exact power of two. + The encoder can verify the number of encoded bits is sufficient, but cannot + check this latter condition. + _val: The bits to encode (in the least _nbits significant bits). + They will be decoded in order from most-significant to least. + _nbits: The number of bits to overwrite. + This must be no more than 8.*/ +void ec_enc_patch_initial_bits(ec_enc *_this,unsigned _val,unsigned _nbits); + +/*Compacts the data to fit in the target size. + This moves up the raw bits at the end of the current buffer so they are at + the end of the new buffer size. + The caller must ensure that the amount of data that's already been written + will fit in the new size. + _size: The number of bytes in the new buffer. + This must be large enough to contain the bits already written, and + must be no larger than the existing size.*/ +void ec_enc_shrink(ec_enc *_this,opus_uint32 _size); + +/*Indicates that there are no more symbols to encode. + All reamining output bytes are flushed to the output buffer. + ec_enc_init() must be called before the encoder can be used again.*/ +void ec_enc_done(ec_enc *_this); + +#endif diff --git a/port/third_party/opus/celt/fixed_debug.h b/port/third_party/opus/celt/fixed_debug.h new file mode 100644 index 00000000..ef2e5d02 --- /dev/null +++ b/port/third_party/opus/celt/fixed_debug.h @@ -0,0 +1,836 @@ +/* Copyright (C) 2003-2008 Jean-Marc Valin + Copyright (C) 2007-2012 Xiph.Org Foundation */ +/** + @file fixed_debug.h + @brief Fixed-point operations with debugging +*/ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef FIXED_DEBUG_H +#define FIXED_DEBUG_H + +#include +#include "opus_defines.h" + +#ifdef CELT_C +OPUS_EXPORT opus_int64 celt_mips=0; +#else +extern opus_int64 celt_mips; +#endif + +#define MULT16_16SU(a,b) ((opus_val32)(opus_val16)(a)*(opus_val32)(opus_uint16)(b)) +#define MULT32_32_Q31(a,b) ADD32(ADD32(SHL32(MULT16_16(SHR32((a),16),SHR((b),16)),1), SHR32(MULT16_16SU(SHR32((a),16),((b)&0x0000ffff)),15)), SHR32(MULT16_16SU(SHR32((b),16),((a)&0x0000ffff)),15)) + +/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#define MULT16_32_Q16(a,b) ADD32(MULT16_16((a),SHR32((b),16)), SHR32(MULT16_16SU((a),((b)&0x0000ffff)),16)) + +#define MULT16_32_P16(a,b) MULT16_32_PX(a,b,16) + +#define QCONST16(x,bits) ((opus_val16)(.5+(x)*(((opus_val32)1)<<(bits)))) +#define QCONST32(x,bits) ((opus_val32)(.5+(x)*(((opus_val32)1)<<(bits)))) + +#define VERIFY_SHORT(x) ((x)<=32767&&(x)>=-32768) +#define VERIFY_INT(x) ((x)<=2147483647LL&&(x)>=-2147483648LL) +#define VERIFY_UINT(x) ((x)<=(2147483647LLU<<1)) + +#define SHR(a,b) SHR32(a,b) +#define PSHR(a,b) PSHR32(a,b) + +/** Add two 32-bit values, ignore any overflows */ +#define ADD32_ovflw(a,b) (celt_mips+=2,(opus_val32)((opus_uint32)(a)+(opus_uint32)(b))) +/** Subtract two 32-bit values, ignore any overflows */ +#define SUB32_ovflw(a,b) (celt_mips+=2,(opus_val32)((opus_uint32)(a)-(opus_uint32)(b))) +/* Avoid MSVC warning C4146: unary minus operator applied to unsigned type */ +/** Negate 32-bit value, ignore any overflows */ +#define NEG32_ovflw(a) (celt_mips+=2,(opus_val32)(0-(opus_uint32)(a))) + +static OPUS_INLINE short NEG16(int x) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "NEG16: input is not short: %d\n", (int)x); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = -x; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "NEG16: output is not short: %d\n", (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} +static OPUS_INLINE int NEG32(opus_int64 x) +{ + opus_int64 res; + if (!VERIFY_INT(x)) + { + fprintf (stderr, "NEG16: input is not int: %d\n", (int)x); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = -x; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "NEG16: output is not int: %d\n", (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#define EXTRACT16(x) EXTRACT16_(x, __FILE__, __LINE__) +static OPUS_INLINE short EXTRACT16_(int x, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "EXTRACT16: input is not short: %d in %s: line %d\n", x, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = x; + celt_mips++; + return res; +} + +#define EXTEND32(x) EXTEND32_(x, __FILE__, __LINE__) +static OPUS_INLINE int EXTEND32_(int x, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "EXTEND32: input is not short: %d in %s: line %d\n", x, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = x; + celt_mips++; + return res; +} + +#define SHR16(a, shift) SHR16_(a, shift, __FILE__, __LINE__) +static OPUS_INLINE short SHR16_(int a, int shift, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHR16: inputs are not short: %d >> %d in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a>>shift; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "SHR16: output is not short: %d in %s: line %d\n", res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} +#define SHL16(a, shift) SHL16_(a, shift, __FILE__, __LINE__) +static OPUS_INLINE short SHL16_(int a, int shift, char *file, int line) +{ + opus_int32 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHL16: inputs are not short: %d %d in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = (opus_int32)((opus_uint32)a<>shift; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "SHR32: output is not int: %d\n", (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} +#define SHL32(a, shift) SHL32_(a, shift, __FILE__, __LINE__) +static OPUS_INLINE int SHL32_(opus_int64 a, int shift, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_INT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHL32: inputs are not int: %lld %d in %s: line %d\n", (long long)a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = (opus_int64)((opus_uint64)a<>1))),shift)) +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) + +#define ROUND16(x,a) (celt_mips--,EXTRACT16(PSHR32((x),(a)))) +#define SROUND16(x,a) (celt_mips--,EXTRACT16(SATURATE(PSHR32(x,a), 32767))); + +#define HALF16(x) (SHR16(x,1)) +#define HALF32(x) (SHR32(x,1)) + +#define ADD16(a, b) ADD16_(a, b, __FILE__, __LINE__) +static OPUS_INLINE short ADD16_(int a, int b, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "ADD16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a+b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "ADD16: output is not short: %d+%d=%d in %s: line %d\n", a,b,res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} + +#define SUB16(a, b) SUB16_(a, b, __FILE__, __LINE__) +static OPUS_INLINE short SUB16_(int a, int b, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "SUB16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a-b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "SUB16: output is not short: %d in %s: line %d\n", res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} + +#define ADD32(a, b) ADD32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE int ADD32_(opus_int64 a, opus_int64 b, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "ADD32: inputs are not int: %d %d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a+b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "ADD32: output is not int: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#define SUB32(a, b) SUB32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE int SUB32_(opus_int64 a, opus_int64 b, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "SUB32: inputs are not int: %d %d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a-b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "SUB32: output is not int: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#undef UADD32 +#define UADD32(a, b) UADD32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE unsigned int UADD32_(opus_uint64 a, opus_uint64 b, char *file, int line) +{ + opus_uint64 res; + if (!VERIFY_UINT(a) || !VERIFY_UINT(b)) + { + fprintf (stderr, "UADD32: inputs are not uint32: %llu %llu in %s: line %d\n", (unsigned long long)a, (unsigned long long)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a+b; + if (!VERIFY_UINT(res)) + { + fprintf (stderr, "UADD32: output is not uint32: %llu in %s: line %d\n", (unsigned long long)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#undef USUB32 +#define USUB32(a, b) USUB32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE unsigned int USUB32_(opus_uint64 a, opus_uint64 b, char *file, int line) +{ + opus_uint64 res; + if (!VERIFY_UINT(a) || !VERIFY_UINT(b)) + { + fprintf (stderr, "USUB32: inputs are not uint32: %llu %llu in %s: line %d\n", (unsigned long long)a, (unsigned long long)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (a> 16; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT32_32_Q16: output is not int: %lld*%lld=%lld\n", (long long)a, (long long)b, (long long)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=5; + return res; +} + +#define MULT16_16(a, b) MULT16_16_(a, b, __FILE__, __LINE__) +static OPUS_INLINE int MULT16_16_(int a, int b, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16: output is not int: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} + +#define MAC16_16(c,a,b) (celt_mips-=2,ADD32((c),MULT16_16((a),(b)))) + +#define MULT16_32_QX(a, b, Q) MULT16_32_QX_(a, b, Q, __FILE__, __LINE__) +static OPUS_INLINE int MULT16_32_QX_(int a, opus_int64 b, int Q, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "MULT16_32_Q%d: inputs are not short+int: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (ABS32(b)>=((opus_int64)(1)<<(16+Q))) + { + fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = (((opus_int64)a)*(opus_int64)b) >> Q; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_32_Q%d: output is not int: %d*%d=%d in %s: line %d\n", Q, (int)a, (int)b,(int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (Q==15) + celt_mips+=3; + else + celt_mips+=4; + return res; +} + +#define MULT16_32_PX(a, b, Q) MULT16_32_PX_(a, b, Q, __FILE__, __LINE__) +static OPUS_INLINE int MULT16_32_PX_(int a, opus_int64 b, int Q, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "MULT16_32_P%d: inputs are not short+int: %d %d in %s: line %d\n\n", Q, (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (ABS32(b)>=((opus_int64)(1)<<(16+Q))) + { + fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d in %s: line %d\n\n", Q, (int)a, (int)b,file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((((opus_int64)a)*(opus_int64)b) + (((opus_val32)(1)<>1))>> Q; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_32_P%d: output is not int: %d*%d=%d in %s: line %d\n\n", Q, (int)a, (int)b,(int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (Q==15) + celt_mips+=4; + else + celt_mips+=5; + return res; +} + +#define MULT16_32_Q15(a,b) MULT16_32_QX(a,b,15) +#define MAC16_32_Q15(c,a,b) (celt_mips-=2,ADD32((c),MULT16_32_Q15((a),(b)))) +#define MAC16_32_Q16(c,a,b) (celt_mips-=2,ADD32((c),MULT16_32_Q16((a),(b)))) + +static OPUS_INLINE int SATURATE(int a, int b) +{ + if (a>b) + a=b; + if (a<-b) + a = -b; + celt_mips+=3; + return a; +} + +static OPUS_INLINE opus_int16 SATURATE16(opus_int32 a) +{ + celt_mips+=3; + if (a>32767) + return 32767; + else if (a<-32768) + return -32768; + else return a; +} + +static OPUS_INLINE int MULT16_16_Q11_32(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q11: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res >>= 11; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16_Q11: output is not short: %d*%d=%d\n", (int)a, (int)b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=3; + return res; +} +static OPUS_INLINE short MULT16_16_Q13(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q13: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res >>= 13; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_Q13: output is not short: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=3; + return res; +} +static OPUS_INLINE short MULT16_16_Q14(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q14: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res >>= 14; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_Q14: output is not short: %d\n", (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=3; + return res; +} + +#define MULT16_16_Q15(a, b) MULT16_16_Q15_(a, b, __FILE__, __LINE__) +static OPUS_INLINE short MULT16_16_Q15_(int a, int b, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q15: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res >>= 15; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_Q15: output is not short: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=1; + return res; +} + +static OPUS_INLINE short MULT16_16_P13(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P13: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res += 4096; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16_P13: overflow: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res >>= 13; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_P13: output is not short: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=4; + return res; +} +static OPUS_INLINE short MULT16_16_P14(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P14: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res += 8192; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16_P14: overflow: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res >>= 14; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_P14: output is not short: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=4; + return res; +} +static OPUS_INLINE short MULT16_16_P15(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P15: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res += 16384; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16_P15: overflow: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res >>= 15; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_P15: output is not short: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#define DIV32_16(a, b) DIV32_16_(a, b, __FILE__, __LINE__) + +static OPUS_INLINE int DIV32_16_(opus_int64 a, opus_int64 b, char *file, int line) +{ + opus_int64 res; + if (b==0) + { + fprintf(stderr, "DIV32_16: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + return 0; + } + if (!VERIFY_INT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "DIV32_16: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a/b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "DIV32_16: output is not short: %d / %d = %d in %s: line %d\n", (int)a,(int)b,(int)res, file, line); + if (res>32767) + res = 32767; + if (res<-32768) + res = -32768; +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=35; + return res; +} + +#define DIV32(a, b) DIV32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE int DIV32_(opus_int64 a, opus_int64 b, char *file, int line) +{ + opus_int64 res; + if (b==0) + { + fprintf(stderr, "DIV32: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + return 0; + } + + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "DIV32: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a/b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "DIV32: output is not int: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=70; + return res; +} + +static OPUS_INLINE opus_val16 SIG2WORD16_generic(celt_sig x) +{ + x = PSHR32(x, SIG_SHIFT); + x = MAX32(x, -32768); + x = MIN32(x, 32767); + return EXTRACT16(x); +} +#define SIG2WORD16(x) (SIG2WORD16_generic(x)) + + +#undef PRINT_MIPS +#define PRINT_MIPS(file) do {fprintf (file, "total complexity = %llu MIPS\n", (unsigned long long)celt_mips);} while (0); + +#endif diff --git a/port/third_party/opus/celt/fixed_generic.h b/port/third_party/opus/celt/fixed_generic.h new file mode 100644 index 00000000..8f29d46b --- /dev/null +++ b/port/third_party/opus/celt/fixed_generic.h @@ -0,0 +1,188 @@ +/* Copyright (C) 2007-2009 Xiph.Org Foundation + Copyright (C) 2003-2008 Jean-Marc Valin + Copyright (C) 2007-2008 CSIRO */ +/** + @file fixed_generic.h + @brief Generic fixed-point operations +*/ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef FIXED_GENERIC_H +#define FIXED_GENERIC_H + +/** Multiply a 16-bit signed value by a 16-bit unsigned value. The result is a 32-bit signed value */ +#define MULT16_16SU(a,b) ((opus_val32)(opus_val16)(a)*(opus_val32)(opus_uint16)(b)) + +/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_Q16(a,b) ((opus_val32)SHR((opus_int64)((opus_val16)(a))*(b),16)) +#else +#define MULT16_32_Q16(a,b) ADD32(MULT16_16((a),SHR((b),16)), SHR(MULT16_16SU((a),((b)&0x0000ffff)),16)) +#endif + +/** 16x32 multiplication, followed by a 16-bit shift right (round-to-nearest). Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_P16(a,b) ((opus_val32)PSHR((opus_int64)((opus_val16)(a))*(b),16)) +#else +#define MULT16_32_P16(a,b) ADD32(MULT16_16((a),SHR((b),16)), PSHR(MULT16_16SU((a),((b)&0x0000ffff)),16)) +#endif + +/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_Q15(a,b) ((opus_val32)SHR((opus_int64)((opus_val16)(a))*(b),15)) +#else +#define MULT16_32_Q15(a,b) ADD32(SHL(MULT16_16((a),SHR((b),16)),1), SHR(MULT16_16SU((a),((b)&0x0000ffff)),15)) +#endif + +/** 32x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT32_32_Q16(a,b) ((opus_val32)SHR((opus_int64)(a)*(opus_int64)(b),16)) +#else +#define MULT32_32_Q16(a,b) (ADD32(ADD32(ADD32((opus_val32)(SHR32(((opus_uint32)((a)&0x0000ffff)*(opus_uint32)((b)&0x0000ffff)),16)), MULT16_16SU(SHR32(a,16),((b)&0x0000ffff))), MULT16_16SU(SHR32(b,16),((a)&0x0000ffff))), SHL32(MULT16_16(SHR32(a,16),SHR32(b,16)),16))) +#endif + +/** 32x32 multiplication, followed by a 31-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT32_32_Q31(a,b) ((opus_val32)SHR((opus_int64)(a)*(opus_int64)(b),31)) +#else +#define MULT32_32_Q31(a,b) ADD32(ADD32(SHL(MULT16_16(SHR((a),16),SHR((b),16)),1), SHR(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),15)), SHR(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),15)) +#endif + +/** Compile-time conversion of float constant to 16-bit value */ +#define QCONST16(x,bits) ((opus_val16)(.5+(x)*(((opus_val32)1)<<(bits)))) + +/** Compile-time conversion of float constant to 32-bit value */ +#define QCONST32(x,bits) ((opus_val32)(.5+(x)*(((opus_val32)1)<<(bits)))) + +/** Negate a 16-bit value */ +#define NEG16(x) (-(x)) +/** Negate a 32-bit value */ +#define NEG32(x) (-(x)) + +/** Change a 32-bit value into a 16-bit value. The value is assumed to fit in 16-bit, otherwise the result is undefined */ +#define EXTRACT16(x) ((opus_val16)(x)) +/** Change a 16-bit value into a 32-bit value */ +#define EXTEND32(x) ((opus_val32)(x)) + +/** Arithmetic shift-right of a 16-bit value */ +#define SHR16(a,shift) ((a) >> (shift)) +/** Arithmetic shift-left of a 16-bit value */ +#define SHL16(a,shift) ((opus_int16)((opus_uint16)(a)<<(shift))) +/** Arithmetic shift-right of a 32-bit value */ +#define SHR32(a,shift) ((a) >> (shift)) +/** Arithmetic shift-left of a 32-bit value */ +#define SHL32(a,shift) ((opus_int32)((opus_uint32)(a)<<(shift))) + +/** 32-bit arithmetic shift right with rounding-to-nearest instead of rounding down */ +#define PSHR32(a,shift) (SHR32((a)+((EXTEND32(1)<<((shift))>>1)),shift)) +/** 32-bit arithmetic shift right where the argument can be negative */ +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) + +/** "RAW" macros, should not be used outside of this header file */ +#define SHR(a,shift) ((a) >> (shift)) +#define SHL(a,shift) SHL32(a,shift) +#define PSHR(a,shift) (SHR((a)+((EXTEND32(1)<<((shift))>>1)),shift)) +#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + +#define SATURATE16(x) (EXTRACT16((x)>32767 ? 32767 : (x)<-32768 ? -32768 : (x))) + +/** Shift by a and round-to-nearest 32-bit value. Result is a 16-bit value */ +#define ROUND16(x,a) (EXTRACT16(PSHR32((x),(a)))) +/** Shift by a and round-to-nearest 32-bit value. Result is a saturated 16-bit value */ +#define SROUND16(x,a) EXTRACT16(SATURATE(PSHR32(x,a), 32767)); + +/** Divide by two */ +#define HALF16(x) (SHR16(x,1)) +#define HALF32(x) (SHR32(x,1)) + +/** Add two 16-bit values */ +#define ADD16(a,b) ((opus_val16)((opus_val16)(a)+(opus_val16)(b))) +/** Subtract two 16-bit values */ +#define SUB16(a,b) ((opus_val16)(a)-(opus_val16)(b)) +/** Add two 32-bit values */ +#define ADD32(a,b) ((opus_val32)(a)+(opus_val32)(b)) +/** Subtract two 32-bit values */ +#define SUB32(a,b) ((opus_val32)(a)-(opus_val32)(b)) + +/** Add two 32-bit values, ignore any overflows */ +#define ADD32_ovflw(a,b) ((opus_val32)((opus_uint32)(a)+(opus_uint32)(b))) +/** Subtract two 32-bit values, ignore any overflows */ +#define SUB32_ovflw(a,b) ((opus_val32)((opus_uint32)(a)-(opus_uint32)(b))) +/* Avoid MSVC warning C4146: unary minus operator applied to unsigned type */ +/** Negate 32-bit value, ignore any overflows */ +#define NEG32_ovflw(a) ((opus_val32)(0-(opus_uint32)(a))) + +/** 16x16 multiplication where the result fits in 16 bits */ +#define MULT16_16_16(a,b) ((((opus_val16)(a))*((opus_val16)(b)))) + +/** 32x32 multiplication where the result fits in 32 bits */ +#define MULT32_32_32(a,b) ((((opus_val32)(a))*((opus_val32)(b)))) + +/* (opus_val32)(opus_val16) gives TI compiler a hint that it's 16x16->32 multiply */ +/** 16x16 multiplication where the result fits in 32 bits */ +#define MULT16_16(a,b) (((opus_val32)(opus_val16)(a))*((opus_val32)(opus_val16)(b))) + +/** 16x16 multiply-add where the result fits in 32 bits */ +#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) +/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add. + b must fit in 31 bits. + Result fits in 32 bits. */ +#define MAC16_32_Q15(c,a,b) ADD32((c),ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) + +/** 16x32 multiplication, followed by a 16-bit shift right and 32-bit add. + Results fits in 32 bits */ +#define MAC16_32_Q16(c,a,b) ADD32((c),ADD32(MULT16_16((a),SHR((b),16)), SHR(MULT16_16SU((a),((b)&0x0000ffff)),16))) + +#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) +#define MULT16_16_Q11(a,b) (SHR(MULT16_16((a),(b)),11)) +#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13)) +#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14)) +#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15)) + +#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13)) +#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14)) +#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15)) + +/** Divide a 32-bit value by a 16-bit value. Result fits in 16 bits */ +#define DIV32_16(a,b) ((opus_val16)(((opus_val32)(a))/((opus_val16)(b)))) + +/** Divide a 32-bit value by a 32-bit value. Result fits in 32 bits */ +#define DIV32(a,b) (((opus_val32)(a))/((opus_val32)(b))) + +#if defined(MIPSr1_ASM) +#include "mips/fixed_generic_mipsr1.h" +#endif + +static OPUS_INLINE opus_val16 SIG2WORD16_generic(celt_sig x) +{ + x = PSHR32(x, SIG_SHIFT); + x = MAX32(x, -32768); + x = MIN32(x, 32767); + return EXTRACT16(x); +} +#define SIG2WORD16(x) (SIG2WORD16_generic(x)) + +#endif diff --git a/port/third_party/opus/celt/float_cast.h b/port/third_party/opus/celt/float_cast.h new file mode 100644 index 00000000..8915a5fd --- /dev/null +++ b/port/third_party/opus/celt/float_cast.h @@ -0,0 +1,152 @@ +/* Copyright (C) 2001 Erik de Castro Lopo */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +/* Version 1.1 */ + +#ifndef FLOAT_CAST_H +#define FLOAT_CAST_H + + +#include "arch.h" + +/*============================================================================ +** On Intel Pentium processors (especially PIII and probably P4), converting +** from float to int is very slow. To meet the C specs, the code produced by +** most C compilers targeting Pentium needs to change the FPU rounding mode +** before the float to int conversion is performed. +** +** Changing the FPU rounding mode causes the FPU pipeline to be flushed. It +** is this flushing of the pipeline which is so slow. +** +** Fortunately the ISO C99 specifications define the functions lrint, lrintf, +** llrint and llrintf which fix this problem as a side effect. +** +** On Unix-like systems, the configure process should have detected the +** presence of these functions. If they weren't found we have to replace them +** here with a standard C cast. +*/ + +/* +** The C99 prototypes for lrint and lrintf are as follows: +** +** long int lrintf (float x) ; +** long int lrint (double x) ; +*/ + +/* The presence of the required functions are detected during the configure +** process and the values HAVE_LRINT and HAVE_LRINTF are set accordingly in +** the config.h file. +*/ + +/* With GCC, when SSE is available, the fastest conversion is cvtss2si. */ +#if defined(__GNUC__) && defined(__SSE__) + +#include +static OPUS_INLINE opus_int32 float2int(float x) {return _mm_cvt_ss2si(_mm_set_ss(x));} + +#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && (defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1)) + + #include + static OPUS_INLINE opus_int32 float2int(float value) + { + /* _mm_load_ss will generate same code as _mm_set_ss + ** in _MSC_VER >= 1914 /02 so keep __mm_load__ss + ** for backward compatibility. + */ + return _mm_cvtss_si32(_mm_load_ss(&value)); + } + +#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && defined (_M_IX86) + + #include + + /* Win32 doesn't seem to have these functions. + ** Therefore implement OPUS_INLINE versions of these functions here. + */ + + static OPUS_INLINE opus_int32 + float2int (float flt) + { int intgr; + + _asm + { fld flt + fistp intgr + } ; + + return intgr ; + } + +#elif defined(HAVE_LRINTF) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* These defines enable functionality introduced with the 1999 ISO C +** standard. They must be defined before the inclusion of math.h to +** engage them. If optimisation is enabled, these functions will be +** inlined. With optimisation switched off, you have to link in the +** maths library using -lm. +*/ + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 + +#define __USE_ISOC9X 1 +#define __USE_ISOC99 1 + +#include +#define float2int(x) lrintf(x) + +#elif defined(HAVE_LRINT) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 + +#define __USE_ISOC9X 1 +#define __USE_ISOC99 1 + +#include +#define float2int(x) lrint(x) + +#else + +#if (defined(__GNUC__) && defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) + /* supported by gcc in C99 mode, but not by all other compilers */ + #warning "Don't have the functions lrint() and lrintf ()." + #warning "Replacing these functions with a standard C cast." +#endif /* __STDC_VERSION__ >= 199901L */ + #include + #define float2int(flt) ((int)(floor(.5+flt))) +#endif + +#ifndef DISABLE_FLOAT_API +static OPUS_INLINE opus_int16 FLOAT2INT16(float x) +{ + x = x*CELT_SIG_SCALE; + x = MAX32(x, -32768); + x = MIN32(x, 32767); + return (opus_int16)float2int(x); +} +#endif /* DISABLE_FLOAT_API */ + +#endif /* FLOAT_CAST_H */ diff --git a/port/third_party/opus/celt/kiss_fft.c b/port/third_party/opus/celt/kiss_fft.c new file mode 100644 index 00000000..83775165 --- /dev/null +++ b/port/third_party/opus/celt/kiss_fft.c @@ -0,0 +1,604 @@ +/*Copyright (c) 2003-2004, Mark Borgerding + Lots of modifications by Jean-Marc Valin + Copyright (c) 2005-2007, Xiph.Org Foundation + Copyright (c) 2008, Xiph.Org Foundation, CSIRO + + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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.*/ + +/* This code is originally from Mark Borgerding's KISS-FFT but has been + heavily modified to better suit Opus */ + +#ifndef SKIP_CONFIG_H +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif +#endif + +#include "_kiss_fft_guts.h" +#include "arch.h" +#include "os_support.h" +#include "mathops.h" +#include "stack_alloc.h" + +/* The guts header contains all the multiplication and addition macros that are defined for + complex numbers. It also delares the kf_ internal functions. +*/ + +static void kf_bfly2( + kiss_fft_cpx * Fout, + int m, + int N + ) +{ + kiss_fft_cpx * Fout2; + int i; + (void)m; +#ifdef CUSTOM_MODES + if (m==1) + { + celt_assert(m==1); + for (i=0;itwiddles; + /* m is guaranteed to be a multiple of 4. */ + for (j=0;jtwiddles[fstride*m]; +#endif + for (i=0;itwiddles; + /* For non-custom modes, m is guaranteed to be a multiple of 4. */ + k=m; + do { + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = SUB32_ovflw(Fout->r, HALF_OF(scratch[3].r)); + Fout[m].i = SUB32_ovflw(Fout->i, HALF_OF(scratch[3].i)); + + C_MULBYSCALAR( scratch[0] , epi3.i ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = ADD32_ovflw(Fout[m].r, scratch[0].i); + Fout[m2].i = SUB32_ovflw(Fout[m].i, scratch[0].r); + + Fout[m].r = SUB32_ovflw(Fout[m].r, scratch[0].i); + Fout[m].i = ADD32_ovflw(Fout[m].i, scratch[0].r); + + ++Fout; + } while(--k); + } +} + + +#ifndef OVERRIDE_kf_bfly5 +static void kf_bfly5( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_state *st, + int m, + int N, + int mm + ) +{ + kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + int i, u; + kiss_fft_cpx scratch[13]; + const kiss_twiddle_cpx *tw; + kiss_twiddle_cpx ya,yb; + kiss_fft_cpx * Fout_beg = Fout; + +#ifdef FIXED_POINT + ya.r = 10126; + ya.i = -31164; + yb.r = -26510; + yb.i = -19261; +#else + ya = st->twiddles[fstride*m]; + yb = st->twiddles[fstride*2*m]; +#endif + tw=st->twiddles; + + for (i=0;ir = ADD32_ovflw(Fout0->r, ADD32_ovflw(scratch[7].r, scratch[8].r)); + Fout0->i = ADD32_ovflw(Fout0->i, ADD32_ovflw(scratch[7].i, scratch[8].i)); + + scratch[5].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,ya.r), S_MUL(scratch[8].r,yb.r))); + scratch[5].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,ya.r), S_MUL(scratch[8].i,yb.r))); + + scratch[6].r = ADD32_ovflw(S_MUL(scratch[10].i,ya.i), S_MUL(scratch[9].i,yb.i)); + scratch[6].i = NEG32_ovflw(ADD32_ovflw(S_MUL(scratch[10].r,ya.i), S_MUL(scratch[9].r,yb.i))); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,yb.r), S_MUL(scratch[8].r,ya.r))); + scratch[11].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,yb.r), S_MUL(scratch[8].i,ya.r))); + scratch[12].r = SUB32_ovflw(S_MUL(scratch[9].i,ya.i), S_MUL(scratch[10].i,yb.i)); + scratch[12].i = SUB32_ovflw(S_MUL(scratch[10].r,yb.i), S_MUL(scratch[9].r,ya.i)); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } + } +} +#endif /* OVERRIDE_kf_bfly5 */ + + +#endif + + +#ifdef CUSTOM_MODES + +static +void compute_bitrev_table( + int Fout, + opus_int16 *f, + const size_t fstride, + int in_stride, + opus_int16 * factors, + const kiss_fft_state *st + ) +{ + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + + /*printf ("fft %d %d %d %d %d %d\n", p*m, m, p, s2, fstride*in_stride, N);*/ + if (m==1) + { + int j; + for (j=0;j32000 || (opus_int32)p*(opus_int32)p > n) + p = n; /* no more factors, skip to end */ + } + n /= p; +#ifdef RADIX_TWO_ONLY + if (p!=2 && p != 4) +#else + if (p>5) +#endif + { + return 0; + } + facbuf[2*stages] = p; + if (p==2 && stages > 1) + { + facbuf[2*stages] = 4; + facbuf[2] = 2; + } + stages++; + } while (n > 1); + n = nbak; + /* Reverse the order to get the radix 4 at the end, so we can use the + fast degenerate case. It turns out that reversing the order also + improves the noise behaviour. */ + for (i=0;i= memneeded) + st = (kiss_fft_state*)mem; + *lenmem = memneeded; + } + if (st) { + opus_int16 *bitrev; + kiss_twiddle_cpx *twiddles; + + st->nfft=nfft; +#ifdef FIXED_POINT + st->scale_shift = celt_ilog2(st->nfft); + if (st->nfft == 1<scale_shift) + st->scale = Q15ONE; + else + st->scale = (1073741824+st->nfft/2)/st->nfft>>(15-st->scale_shift); +#else + st->scale = 1.f/nfft; +#endif + if (base != NULL) + { + st->twiddles = base->twiddles; + st->shift = 0; + while (st->shift < 32 && nfft<shift != base->nfft) + st->shift++; + if (st->shift>=32) + goto fail; + } else { + st->twiddles = twiddles = (kiss_twiddle_cpx*)KISS_FFT_MALLOC(sizeof(kiss_twiddle_cpx)*nfft); + compute_twiddles(twiddles, nfft); + st->shift = -1; + } + if (!kf_factor(nfft,st->factors)) + { + goto fail; + } + + /* bitrev */ + st->bitrev = bitrev = (opus_int16*)KISS_FFT_MALLOC(sizeof(opus_int16)*nfft); + if (st->bitrev==NULL) + goto fail; + compute_bitrev_table(0, bitrev, 1,1, st->factors,st); + + /* Initialize architecture specific fft parameters */ + if (opus_fft_alloc_arch(st, arch)) + goto fail; + } + return st; +fail: + opus_fft_free(st, arch); + return NULL; +} + +kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch) +{ + return opus_fft_alloc_twiddles(nfft, mem, lenmem, NULL, arch); +} + +void opus_fft_free_arch_c(kiss_fft_state *st) { + (void)st; +} + +void opus_fft_free(const kiss_fft_state *cfg, int arch) +{ + if (cfg) + { + opus_fft_free_arch((kiss_fft_state *)cfg, arch); + opus_free((opus_int16*)cfg->bitrev); + if (cfg->shift < 0) + opus_free((kiss_twiddle_cpx*)cfg->twiddles); + opus_free((kiss_fft_state*)cfg); + } +} + +#endif /* CUSTOM_MODES */ + +void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout) +{ + int m2, m; + int p; + int L; + int fstride[MAXFACTORS]; + int i; + int shift; + + /* st->shift can be -1 */ + shift = st->shift>0 ? st->shift : 0; + + fstride[0] = 1; + L=0; + do { + p = st->factors[2*L]; + m = st->factors[2*L+1]; + fstride[L+1] = fstride[L]*p; + L++; + } while(m!=1); + m = st->factors[2*L-1]; + for (i=L-1;i>=0;i--) + { + if (i!=0) + m2 = st->factors[2*i-1]; + else + m2 = 1; + switch (st->factors[2*i]) + { + case 2: + kf_bfly2(fout, m, fstride[i]); + break; + case 4: + kf_bfly4(fout,fstride[i]<scale_shift-1; +#endif + scale = st->scale; + + celt_assert2 (fin != fout, "In-place FFT not supported"); + /* Bit-reverse the input */ + for (i=0;infft;i++) + { + kiss_fft_cpx x = fin[i]; + fout[st->bitrev[i]].r = SHR32(MULT16_32_Q16(scale, x.r), scale_shift); + fout[st->bitrev[i]].i = SHR32(MULT16_32_Q16(scale, x.i), scale_shift); + } + opus_fft_impl(st, fout); +} + + +void opus_ifft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + int i; + celt_assert2 (fin != fout, "In-place FFT not supported"); + /* Bit-reverse the input */ + for (i=0;infft;i++) + fout[st->bitrev[i]] = fin[i]; + for (i=0;infft;i++) + fout[i].i = -fout[i].i; + opus_fft_impl(st, fout); + for (i=0;infft;i++) + fout[i].i = -fout[i].i; +} diff --git a/port/third_party/opus/celt/kiss_fft.h b/port/third_party/opus/celt/kiss_fft.h new file mode 100644 index 00000000..267f72f9 --- /dev/null +++ b/port/third_party/opus/celt/kiss_fft.h @@ -0,0 +1,210 @@ +/*Copyright (c) 2003-2004, Mark Borgerding + Lots of modifications by Jean-Marc Valin + Copyright (c) 2005-2007, Xiph.Org Foundation + Copyright (c) 2008, Xiph.Org Foundation, CSIRO + + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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.*/ + +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include +#include +#include "arch.h" +#include "cpu_support.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef USE_SIMD +# include +# define kiss_fft_scalar __m128 +#define KISS_FFT_MALLOC(nbytes) memalign(16,nbytes) +#else +#define KISS_FFT_MALLOC opus_alloc +#endif + +#ifdef FIXED_POINT +#include "arch.h" + +# define kiss_fft_scalar opus_int32 +# define kiss_twiddle_scalar opus_int16 + +/* Some 32-bit CPUs would load/store a kiss_twiddle_cpx with a single memory + * access, and could benefit from additional alignment. + */ +# define KISS_TWIDDLE_CPX_ALIGNMENT (sizeof(opus_int32)) + +#else +# ifndef kiss_fft_scalar +/* default is float */ +# define kiss_fft_scalar float +# define kiss_twiddle_scalar float +# define KF_SUFFIX _celt_single +# endif +#endif + +#if defined(__GNUC__) && defined(KISS_TWIDDLE_CPX_ALIGNMENT) +#define KISS_TWIDDLE_CPX_ALIGNED __attribute__((aligned(KISS_TWIDDLE_CPX_ALIGNMENT))) +#else +#define KISS_TWIDDLE_CPX_ALIGNED +#endif + +typedef struct { + kiss_fft_scalar r; + kiss_fft_scalar i; +}kiss_fft_cpx; + +typedef struct { + kiss_twiddle_scalar r; + kiss_twiddle_scalar i; +} KISS_TWIDDLE_CPX_ALIGNED kiss_twiddle_cpx; + +#define MAXFACTORS 8 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +typedef struct arch_fft_state{ + int is_supported; + void *priv; +} arch_fft_state; + +typedef struct kiss_fft_state{ + int nfft; + opus_val16 scale; +#ifdef FIXED_POINT + int scale_shift; +#endif + int shift; + opus_int16 factors[2*MAXFACTORS]; + const opus_int16 *bitrev; + const kiss_twiddle_cpx *twiddles; + arch_fft_state *arch_fft; +} kiss_fft_state; + +#if defined(HAVE_ARM_NE10) +#include "arm/fft_arm.h" +#endif + +/*typedef struct kiss_fft_state* kiss_fft_cfg;*/ + +/** + * opus_fft_alloc + * + * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. + * + * typical usage: kiss_fft_cfg mycfg=opus_fft_alloc(1024,0,NULL,NULL); + * + * The return value from fft_alloc is a cfg buffer used internally + * by the fft routine or NULL. + * + * If lenmem is NULL, then opus_fft_alloc will allocate a cfg buffer using malloc. + * The returned value should be free()d when done to avoid memory leaks. + * + * The state can be placed in a user supplied buffer 'mem': + * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, + * then the function places the cfg in mem and the size used in *lenmem + * and returns mem. + * + * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), + * then the function returns NULL and places the minimum cfg + * buffer size in *lenmem. + * */ + +kiss_fft_state *opus_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, const kiss_fft_state *base, int arch); + +kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch); + +/** + * opus_fft(cfg,in_out_buf) + * + * Perform an FFT on a complex input buffer. + * for a forward FFT, + * fin should be f[0] , f[1] , ... ,f[nfft-1] + * fout will be F[0] , F[1] , ... ,F[nfft-1] + * Note that each element is complex and can be accessed like + f[k].r and f[k].i + * */ +void opus_fft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); +void opus_ifft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout); +void opus_ifft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout); + +void opus_fft_free(const kiss_fft_state *cfg, int arch); + + +void opus_fft_free_arch_c(kiss_fft_state *st); +int opus_fft_alloc_arch_c(kiss_fft_state *st); + +#if !defined(OVERRIDE_OPUS_FFT) +/* Is run-time CPU detection enabled on this platform? */ +#if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) + +extern int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])( + kiss_fft_state *st); + +#define opus_fft_alloc_arch(_st, arch) \ + ((*OPUS_FFT_ALLOC_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st)) + +extern void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])( + kiss_fft_state *st); +#define opus_fft_free_arch(_st, arch) \ + ((*OPUS_FFT_FREE_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st)) + +extern void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, kiss_fft_cpx *fout); +#define opus_fft(_cfg, _fin, _fout, arch) \ + ((*OPUS_FFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout)) + +extern void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, kiss_fft_cpx *fout); +#define opus_ifft(_cfg, _fin, _fout, arch) \ + ((*OPUS_IFFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout)) + +#else /* else for if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */ + +#define opus_fft_alloc_arch(_st, arch) \ + ((void)(arch), opus_fft_alloc_arch_c(_st)) + +#define opus_fft_free_arch(_st, arch) \ + ((void)(arch), opus_fft_free_arch_c(_st)) + +#define opus_fft(_cfg, _fin, _fout, arch) \ + ((void)(arch), opus_fft_c(_cfg, _fin, _fout)) + +#define opus_ifft(_cfg, _fin, _fout, arch) \ + ((void)(arch), opus_ifft_c(_cfg, _fin, _fout)) + +#endif /* end if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */ +#endif /* end if !defined(OVERRIDE_OPUS_FFT) */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/celt/laplace.c b/port/third_party/opus/celt/laplace.c new file mode 100644 index 00000000..21809666 --- /dev/null +++ b/port/third_party/opus/celt/laplace.c @@ -0,0 +1,235 @@ +/* Copyright (c) 2007 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "laplace.h" +#include "mathops.h" + +/* The minimum probability of an energy delta (out of 32768). */ +#define LAPLACE_LOG_MINP (0) +#define LAPLACE_MINP (1<>15; +} + +void ec_laplace_encode(ec_enc *enc, int *value, unsigned fs, int decay) +{ + unsigned fl; + int val = *value; + fl = 0; + if (val) + { + int s; + int i; + s = -(val<0); + val = (val+s)^s; + fl = fs; + fs = ec_laplace_get_freq1(fs, decay); + /* Search the decaying part of the PDF.*/ + for (i=1; fs > 0 && i < val; i++) + { + fs *= 2; + fl += fs+2*LAPLACE_MINP; + fs = (fs*(opus_int32)decay)>>15; + } + /* Everything beyond that has probability LAPLACE_MINP. */ + if (!fs) + { + int di; + int ndi_max; + ndi_max = (32768-fl+LAPLACE_MINP-1)>>LAPLACE_LOG_MINP; + ndi_max = (ndi_max-s)>>1; + di = IMIN(val - i, ndi_max - 1); + fl += (2*di+1+s)*LAPLACE_MINP; + fs = IMIN(LAPLACE_MINP, 32768-fl); + *value = (i+di+s)^s; + } + else + { + fs += LAPLACE_MINP; + fl += fs&~s; + } + celt_assert(fl+fs<=32768); + celt_assert(fs>0); + } + ec_encode_bin(enc, fl, fl+fs, 15); +} + +int ec_laplace_decode(ec_dec *dec, unsigned fs, int decay) +{ + int val=0; + unsigned fl; + unsigned fm; + fm = ec_decode_bin(dec, 15); + fl = 0; + if (fm >= fs) + { + val++; + fl = fs; + fs = ec_laplace_get_freq1(fs, decay)+LAPLACE_MINP; + /* Search the decaying part of the PDF.*/ + while(fs > LAPLACE_MINP && fm >= fl+2*fs) + { + fs *= 2; + fl += fs; + fs = ((fs-2*LAPLACE_MINP)*(opus_int32)decay)>>15; + fs += LAPLACE_MINP; + val++; + } + /* Everything beyond that has probability LAPLACE_MINP. */ + if (fs <= LAPLACE_MINP) + { + int di; + di = (fm-fl)>>(LAPLACE_LOG_MINP+1); + val += di; + fl += 2*di*LAPLACE_MINP; + } + if (fm < fl+fs) + val = -val; + else + fl += fs; + } + celt_assert(fl<32768); + celt_assert(fs>0); + celt_assert(fl<=fm); + celt_assert(fm 0 ? 1 : 2); + ec_enc_icdf16(enc, s, sign_icdf, 15); + value = abs(value); + if (value) + { + int i; + opus_uint16 icdf[8]; + icdf[0] = IMAX(7, decay); + for (i=1;i<7;i++) + { + icdf[i] = IMAX(7-i, (icdf[i-1] * (opus_int32)decay) >> 15); + } + icdf[7] = 0; + value--; + do { + ec_enc_icdf16(enc, IMIN(value, 7), icdf, 15); + value -= 7; + } while (value >= 0); + } +} + +int ec_laplace_decode_p0(ec_dec *dec, opus_uint16 p0, opus_uint16 decay) +{ + int s; + int value; + opus_uint16 sign_icdf[3]; + sign_icdf[0] = 32768-p0; + sign_icdf[1] = sign_icdf[0]/2; + sign_icdf[2] = 0; + s = ec_dec_icdf16(dec, sign_icdf, 15); + if (s==2) s = -1; + if (s != 0) + { + int i; + int v; + opus_uint16 icdf[8]; + icdf[0] = IMAX(7, decay); + for (i=1;i<7;i++) + { + icdf[i] = IMAX(7-i, (icdf[i-1] * (opus_int32)decay) >> 15); + } + icdf[7] = 0; + value = 1; + do { + v = ec_dec_icdf16(dec, icdf, 15); + value += v; + } while (v == 7); + return s*value; + } else return 0; +} + +#if 0 + +#include +#define NB_VALS 10 +#define DATA_SIZE 10000 +int main() { + ec_enc enc; + ec_dec dec; + unsigned char *ptr; + int i; + int decay, p0; + int val[NB_VALS] = {6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + /*for (i=0;i>1; + b=1U<>=1; + bshift--; + } + while(bshift>=0); + return g; +} + +#ifdef FIXED_POINT + +opus_val32 frac_div32(opus_val32 a, opus_val32 b) +{ + opus_val16 rcp; + opus_val32 result, rem; + int shift = celt_ilog2(b)-29; + a = VSHR32(a,shift); + b = VSHR32(b,shift); + /* 16-bit reciprocal */ + rcp = ROUND16(celt_rcp(ROUND16(b,16)),3); + result = MULT16_32_Q15(rcp, a); + rem = PSHR32(a,2)-MULT32_32_Q31(result, b); + result = ADD32(result, SHL32(MULT16_32_Q15(rcp, rem),2)); + if (result >= 536870912) /* 2^29 */ + return 2147483647; /* 2^31 - 1 */ + else if (result <= -536870912) /* -2^29 */ + return -2147483647; /* -2^31 */ + else + return SHL32(result, 2); +} + +/** Reciprocal sqrt approximation in the range [0.25,1) (Q16 in, Q14 out) */ +opus_val16 celt_rsqrt_norm(opus_val32 x) +{ + opus_val16 n; + opus_val16 r; + opus_val16 r2; + opus_val16 y; + /* Range of n is [-16384,32767] ([-0.5,1) in Q15). */ + n = x-32768; + /* Get a rough initial guess for the root. + The optimal minimax quadratic approximation (using relative error) is + r = 1.437799046117536+n*(-0.823394375837328+n*0.4096419668459485). + Coefficients here, and the final result r, are Q14.*/ + r = ADD16(23557, MULT16_16_Q15(n, ADD16(-13490, MULT16_16_Q15(n, 6713)))); + /* We want y = x*r*r-1 in Q15, but x is 32-bit Q16 and r is Q14. + We can compute the result from n and r using Q15 multiplies with some + adjustment, carefully done to avoid overflow. + Range of y is [-1564,1594]. */ + r2 = MULT16_16_Q15(r, r); + y = SHL16(SUB16(ADD16(MULT16_16_Q15(r2, n), r2), 16384), 1); + /* Apply a 2nd-order Householder iteration: r += r*y*(y*0.375-0.5). + This yields the Q14 reciprocal square root of the Q16 x, with a maximum + relative error of 1.04956E-4, a (relative) RMSE of 2.80979E-5, and a + peak absolute error of 2.26591/16384. */ + return ADD16(r, MULT16_16_Q15(r, MULT16_16_Q15(y, + SUB16(MULT16_16_Q15(y, 12288), 16384)))); +} + +/** Sqrt approximation (QX input, QX/2 output) */ +opus_val32 celt_sqrt(opus_val32 x) +{ + int k; + opus_val16 n; + opus_val32 rt; + static const opus_val16 C[5] = {23175, 11561, -3011, 1699, -664}; + if (x==0) + return 0; + else if (x>=1073741824) + return 32767; + k = (celt_ilog2(x)>>1)-7; + x = VSHR32(x, 2*k); + n = x-32768; + rt = ADD16(C[0], MULT16_16_Q15(n, ADD16(C[1], MULT16_16_Q15(n, ADD16(C[2], + MULT16_16_Q15(n, ADD16(C[3], MULT16_16_Q15(n, (C[4]))))))))); + rt = VSHR32(rt,7-k); + return rt; +} + +#define L1 32767 +#define L2 -7651 +#define L3 8277 +#define L4 -626 + +static OPUS_INLINE opus_val16 _celt_cos_pi_2(opus_val16 x) +{ + opus_val16 x2; + + x2 = MULT16_16_P15(x,x); + return ADD16(1,MIN16(32766,ADD32(SUB16(L1,x2), MULT16_16_P15(x2, ADD32(L2, MULT16_16_P15(x2, ADD32(L3, MULT16_16_P15(L4, x2 + )))))))); +} + +#undef L1 +#undef L2 +#undef L3 +#undef L4 + +opus_val16 celt_cos_norm(opus_val32 x) +{ + x = x&0x0001ffff; + if (x>SHL32(EXTEND32(1), 16)) + x = SUB32(SHL32(EXTEND32(1), 17),x); + if (x&0x00007fff) + { + if (x0); + i = celt_ilog2(x); + /* n is Q15 with range [0,1). */ + n = VSHR32(x,i-15)-32768; + /* Start with a linear approximation: + r = 1.8823529411764706-0.9411764705882353*n. + The coefficients and the result are Q14 in the range [15420,30840].*/ + r = ADD16(30840, MULT16_16_Q15(-15420, n)); + /* Perform two Newton iterations: + r -= r*((r*n)-1.Q15) + = r*((r*n)+(r-1.Q15)). */ + r = SUB16(r, MULT16_16_Q15(r, + ADD16(MULT16_16_Q15(r, n), ADD16(r, -32768)))); + /* We subtract an extra 1 in the second iteration to avoid overflow; it also + neatly compensates for truncation error in the rest of the process. */ + r = SUB16(r, ADD16(1, MULT16_16_Q15(r, + ADD16(MULT16_16_Q15(r, n), ADD16(r, -32768))))); + /* r is now the Q15 solution to 2/(n+1), with a maximum relative error + of 7.05346E-5, a (relative) RMSE of 2.14418E-5, and a peak absolute + error of 1.24665/32768. */ + return VSHR32(EXTEND32(r),i-16); +} + +#endif diff --git a/port/third_party/opus/celt/mathops.h b/port/third_party/opus/celt/mathops.h new file mode 100644 index 00000000..e2eece29 --- /dev/null +++ b/port/third_party/opus/celt/mathops.h @@ -0,0 +1,296 @@ +/* Copyright (c) 2002-2008 Jean-Marc Valin + Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/** + @file mathops.h + @brief Various math functions +*/ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef MATHOPS_H +#define MATHOPS_H + +#include "arch.h" +#include "entcode.h" +#include "os_support.h" + +#define PI 3.141592653f + +/* Multiplies two 16-bit fractional values. Bit-exactness of this macro is important */ +#define FRAC_MUL16(a,b) ((16384+((opus_int32)(opus_int16)(a)*(opus_int16)(b)))>>15) + +unsigned isqrt32(opus_uint32 _val); + +/* CELT doesn't need it for fixed-point, by analysis.c does. */ +#if !defined(FIXED_POINT) || defined(ANALYSIS_C) +#define cA 0.43157974f +#define cB 0.67848403f +#define cC 0.08595542f +#define cE ((float)PI/2) +static OPUS_INLINE float fast_atan2f(float y, float x) { + float x2, y2; + x2 = x*x; + y2 = y*y; + /* For very small values, we don't care about the answer, so + we can just return 0. */ + if (x2 + y2 < 1e-18f) + { + return 0; + } + if(x2>23)-127; + in.i -= (opus_uint32)integer<<23; + frac = in.f - 1.5f; + frac = -0.41445418f + frac*(0.95909232f + + frac*(-0.33951290f + frac*0.16541097f)); + return 1+integer+frac; +} + +/** Base-2 exponential approximation (2^x). */ +static OPUS_INLINE float celt_exp2(float x) +{ + int integer; + float frac; + union { + float f; + opus_uint32 i; + } res; + integer = (int)floor(x); + if (integer < -50) + return 0; + frac = x-integer; + /* K0 = 1, K1 = log(2), K2 = 3-4*log(2), K3 = 3*log(2) - 2 */ + res.f = 0.99992522f + frac * (0.69583354f + + frac * (0.22606716f + 0.078024523f*frac)); + res.i = (res.i + ((opus_uint32)integer<<23)) & 0x7fffffff; + return res.f; +} + +#else +#define celt_log2(x) ((float)(1.442695040888963387*log(x))) +#define celt_exp2(x) ((float)exp(0.6931471805599453094*(x))) +#endif + +#endif + +#ifdef FIXED_POINT + +#include "os_support.h" + +#ifndef OVERRIDE_CELT_ILOG2 +/** Integer log in base2. Undefined for zero and negative numbers */ +static OPUS_INLINE opus_int16 celt_ilog2(opus_int32 x) +{ + celt_sig_assert(x>0); + return EC_ILOG(x)-1; +} +#endif + + +/** Integer log in base2. Defined for zero, but not for negative numbers */ +static OPUS_INLINE opus_int16 celt_zlog2(opus_val32 x) +{ + return x <= 0 ? 0 : celt_ilog2(x); +} + +opus_val16 celt_rsqrt_norm(opus_val32 x); + +opus_val32 celt_sqrt(opus_val32 x); + +opus_val16 celt_cos_norm(opus_val32 x); + +/** Base-2 logarithm approximation (log2(x)). (Q14 input, Q10 output) */ +static OPUS_INLINE opus_val16 celt_log2(opus_val32 x) +{ + int i; + opus_val16 n, frac; + /* -0.41509302963303146, 0.9609890551383969, -0.31836011537636605, + 0.15530808010959576, -0.08556153059057618 */ + static const opus_val16 C[5] = {-6801+(1<<(13-DB_SHIFT)), 15746, -5217, 2545, -1401}; + if (x==0) + return -32767; + i = celt_ilog2(x); + n = VSHR32(x,i-15)-32768-16384; + frac = ADD16(C[0], MULT16_16_Q15(n, ADD16(C[1], MULT16_16_Q15(n, ADD16(C[2], MULT16_16_Q15(n, ADD16(C[3], MULT16_16_Q15(n, C[4])))))))); + return SHL16(i-13,DB_SHIFT)+SHR16(frac,14-DB_SHIFT); +} + +/* + K0 = 1 + K1 = log(2) + K2 = 3-4*log(2) + K3 = 3*log(2) - 2 +*/ +#define D0 16383 +#define D1 22804 +#define D2 14819 +#define D3 10204 + +static OPUS_INLINE opus_val32 celt_exp2_frac(opus_val16 x) +{ + opus_val16 frac; + frac = SHL16(x, 4); + return ADD16(D0, MULT16_16_Q15(frac, ADD16(D1, MULT16_16_Q15(frac, ADD16(D2 , MULT16_16_Q15(D3,frac)))))); +} + +#undef D0 +#undef D1 +#undef D2 +#undef D3 + +/** Base-2 exponential approximation (2^x). (Q10 input, Q16 output) */ +static OPUS_INLINE opus_val32 celt_exp2(opus_val16 x) +{ + int integer; + opus_val16 frac; + integer = SHR16(x,10); + if (integer>14) + return 0x7f000000; + else if (integer < -15) + return 0; + frac = celt_exp2_frac(x-SHL16(integer,10)); + return VSHR32(EXTEND32(frac), -integer-2); +} + +opus_val32 celt_rcp(opus_val32 x); + +#define celt_div(a,b) MULT32_32_Q31((opus_val32)(a),celt_rcp(b)) + +opus_val32 frac_div32(opus_val32 a, opus_val32 b); + +#define M1 32767 +#define M2 -21 +#define M3 -11943 +#define M4 4936 + +/* Atan approximation using a 4th order polynomial. Input is in Q15 format + and normalized by pi/4. Output is in Q15 format */ +static OPUS_INLINE opus_val16 celt_atan01(opus_val16 x) +{ + return MULT16_16_P15(x, ADD32(M1, MULT16_16_P15(x, ADD32(M2, MULT16_16_P15(x, ADD32(M3, MULT16_16_P15(M4, x))))))); +} + +#undef M1 +#undef M2 +#undef M3 +#undef M4 + +/* atan2() approximation valid for positive input values */ +static OPUS_INLINE opus_val16 celt_atan2p(opus_val16 y, opus_val16 x) +{ + if (y < x) + { + opus_val32 arg; + arg = celt_div(SHL32(EXTEND32(y),15),x); + if (arg >= 32767) + arg = 32767; + return SHR16(celt_atan01(EXTRACT16(arg)),1); + } else { + opus_val32 arg; + arg = celt_div(SHL32(EXTEND32(x),15),y); + if (arg >= 32767) + arg = 32767; + return 25736-SHR16(celt_atan01(EXTRACT16(arg)),1); + } +} + +#endif /* FIXED_POINT */ +#endif /* MATHOPS_H */ diff --git a/port/third_party/opus/celt/mdct.c b/port/third_party/opus/celt/mdct.c new file mode 100644 index 00000000..5c6dab5b --- /dev/null +++ b/port/third_party/opus/celt/mdct.c @@ -0,0 +1,343 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2008 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +/* This is a simple MDCT implementation that uses a N/4 complex FFT + to do most of the work. It should be relatively straightforward to + plug in pretty much and FFT here. + + This replaces the Vorbis FFT (and uses the exact same API), which + was a bit too messy and that was ending up duplicating code + (might as well use the same FFT everywhere). + + The algorithm is similar to (and inspired from) Fabrice Bellard's + MDCT implementation in FFMPEG, but has differences in signs, ordering + and scaling in many places. +*/ + +#ifndef SKIP_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#endif + +#include "mdct.h" +#include "kiss_fft.h" +#include "_kiss_fft_guts.h" +#include +#include "os_support.h" +#include "mathops.h" +#include "stack_alloc.h" + +#if defined(MIPSr1_ASM) +#include "mips/mdct_mipsr1.h" +#endif + + +#ifdef CUSTOM_MODES + +int clt_mdct_init(mdct_lookup *l,int N, int maxshift, int arch) +{ + int i; + kiss_twiddle_scalar *trig; + int shift; + int N2=N>>1; + l->n = N; + l->maxshift = maxshift; + for (i=0;i<=maxshift;i++) + { + if (i==0) + l->kfft[i] = opus_fft_alloc(N>>2>>i, 0, 0, arch); + else + l->kfft[i] = opus_fft_alloc_twiddles(N>>2>>i, 0, 0, l->kfft[0], arch); +#ifndef ENABLE_TI_DSPLIB55 + if (l->kfft[i]==NULL) + return 0; +#endif + } + l->trig = trig = (kiss_twiddle_scalar*)opus_alloc((N-(N2>>maxshift))*sizeof(kiss_twiddle_scalar)); + if (l->trig==NULL) + return 0; + for (shift=0;shift<=maxshift;shift++) + { + /* We have enough points that sine isn't necessary */ +#if defined(FIXED_POINT) +#if 1 + for (i=0;i>= 1; + N >>= 1; + } + return 1; +} + +void clt_mdct_clear(mdct_lookup *l, int arch) +{ + int i; + for (i=0;i<=l->maxshift;i++) + opus_fft_free(l->kfft[i], arch); + opus_free((kiss_twiddle_scalar*)l->trig); +} + +#endif /* CUSTOM_MODES */ + +/* Forward MDCT trashes the input array */ +#ifndef OVERRIDE_clt_mdct_forward +void clt_mdct_forward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + VARDECL(kiss_fft_scalar, f); + VARDECL(kiss_fft_cpx, f2); + const kiss_fft_state *st = l->kfft[shift]; + const kiss_twiddle_scalar *trig; + opus_val16 scale; +#ifdef FIXED_POINT + /* Allows us to scale with MULT16_32_Q16(), which is faster than + MULT16_32_Q15() on ARM. */ + int scale_shift = st->scale_shift-1; +#endif + SAVE_STACK; + (void)arch; + scale = st->scale; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + ALLOC(f, N2, kiss_fft_scalar); + ALLOC(f2, N4, kiss_fft_cpx); + + /* Consider the input to be composed of four blocks: [a, b, c, d] */ + /* Window, shuffle, fold */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in+(overlap>>1); + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+N2-1+(overlap>>1); + kiss_fft_scalar * OPUS_RESTRICT yp = f; + const opus_val16 * OPUS_RESTRICT wp1 = window+(overlap>>1); + const opus_val16 * OPUS_RESTRICT wp2 = window+(overlap>>1)-1; + for(i=0;i<((overlap+3)>>2);i++) + { + /* Real part arranged as -d-cR, Imag part arranged as -b+aR*/ + *yp++ = MULT16_32_Q15(*wp2, xp1[N2]) + MULT16_32_Q15(*wp1,*xp2); + *yp++ = MULT16_32_Q15(*wp1, *xp1) - MULT16_32_Q15(*wp2, xp2[-N2]); + xp1+=2; + xp2-=2; + wp1+=2; + wp2-=2; + } + wp1 = window; + wp2 = window+overlap-1; + for(;i>2);i++) + { + /* Real part arranged as a-bR, Imag part arranged as -c-dR */ + *yp++ = *xp2; + *yp++ = *xp1; + xp1+=2; + xp2-=2; + } + for(;ibitrev[i]] = yc; + } + } + + /* N/4 complex FFT, does not downscale anymore */ + opus_fft_impl(st, f2); + + /* Post-rotate */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_cpx * OPUS_RESTRICT fp = f2; + kiss_fft_scalar * OPUS_RESTRICT yp1 = out; + kiss_fft_scalar * OPUS_RESTRICT yp2 = out+stride*(N2-1); + const kiss_twiddle_scalar *t = &trig[0]; + /* Temp pointers to make it really clear to the compiler what we're doing */ + for(i=0;ii,t[N4+i]) - S_MUL(fp->r,t[i]); + yi = S_MUL(fp->r,t[N4+i]) + S_MUL(fp->i,t[i]); + *yp1 = yr; + *yp2 = yi; + fp++; + yp1 += 2*stride; + yp2 -= 2*stride; + } + } + RESTORE_STACK; +} +#endif /* OVERRIDE_clt_mdct_forward */ + +#ifndef OVERRIDE_clt_mdct_backward +void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 * OPUS_RESTRICT window, int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + const kiss_twiddle_scalar *trig; + (void) arch; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + /* Pre-rotate */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in; + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+stride*(N2-1); + kiss_fft_scalar * OPUS_RESTRICT yp = out+(overlap>>1); + const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0]; + const opus_int16 * OPUS_RESTRICT bitrev = l->kfft[shift]->bitrev; + for(i=0;ikfft[shift], (kiss_fft_cpx*)(out+(overlap>>1))); + + /* Post-rotate and de-shuffle from both ends of the buffer at once to make + it in-place. */ + { + kiss_fft_scalar * yp0 = out+(overlap>>1); + kiss_fft_scalar * yp1 = out+(overlap>>1)+N2-2; + const kiss_twiddle_scalar *t = &trig[0]; + /* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the + middle pair will be computed twice. */ + for(i=0;i<(N4+1)>>1;i++) + { + kiss_fft_scalar re, im, yr, yi; + kiss_twiddle_scalar t0, t1; + /* We swap real and imag because we're using an FFT instead of an IFFT. */ + re = yp0[1]; + im = yp0[0]; + t0 = t[i]; + t1 = t[N4+i]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = ADD32_ovflw(S_MUL(re,t0), S_MUL(im,t1)); + yi = SUB32_ovflw(S_MUL(re,t1), S_MUL(im,t0)); + /* We swap real and imag because we're using an FFT instead of an IFFT. */ + re = yp1[1]; + im = yp1[0]; + yp0[0] = yr; + yp1[1] = yi; + + t0 = t[(N4-i-1)]; + t1 = t[(N2-i-1)]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = ADD32_ovflw(S_MUL(re,t0), S_MUL(im,t1)); + yi = SUB32_ovflw(S_MUL(re,t1), S_MUL(im,t0)); + yp1[0] = yr; + yp0[1] = yi; + yp0 += 2; + yp1 -= 2; + } + } + + /* Mirror on both sides for TDAC */ + { + kiss_fft_scalar * OPUS_RESTRICT xp1 = out+overlap-1; + kiss_fft_scalar * OPUS_RESTRICT yp1 = out; + const opus_val16 * OPUS_RESTRICT wp1 = window; + const opus_val16 * OPUS_RESTRICT wp2 = window+overlap-1; + + for(i = 0; i < overlap/2; i++) + { + kiss_fft_scalar x1, x2; + x1 = *xp1; + x2 = *yp1; + *yp1++ = SUB32_ovflw(MULT16_32_Q15(*wp2, x2), MULT16_32_Q15(*wp1, x1)); + *xp1-- = ADD32_ovflw(MULT16_32_Q15(*wp1, x2), MULT16_32_Q15(*wp2, x1)); + wp1++; + wp2--; + } + } +} +#endif /* OVERRIDE_clt_mdct_backward */ diff --git a/port/third_party/opus/celt/mdct.h b/port/third_party/opus/celt/mdct.h new file mode 100644 index 00000000..160ae4e0 --- /dev/null +++ b/port/third_party/opus/celt/mdct.h @@ -0,0 +1,112 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2008 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +/* This is a simple MDCT implementation that uses a N/4 complex FFT + to do most of the work. It should be relatively straightforward to + plug in pretty much and FFT here. + + This replaces the Vorbis FFT (and uses the exact same API), which + was a bit too messy and that was ending up duplicating code + (might as well use the same FFT everywhere). + + The algorithm is similar to (and inspired from) Fabrice Bellard's + MDCT implementation in FFMPEG, but has differences in signs, ordering + and scaling in many places. +*/ + +#ifndef MDCT_H +#define MDCT_H + +#include "opus_defines.h" +#include "kiss_fft.h" +#include "arch.h" + +typedef struct { + int n; + int maxshift; + const kiss_fft_state *kfft[4]; + const kiss_twiddle_scalar * OPUS_RESTRICT trig; +} mdct_lookup; + +#if defined(HAVE_ARM_NE10) +#include "arm/mdct_arm.h" +#endif + + +int clt_mdct_init(mdct_lookup *l,int N, int maxshift, int arch); +void clt_mdct_clear(mdct_lookup *l, int arch); + +/** Compute a forward MDCT and scale by 4/N, trashes the input array */ +void clt_mdct_forward_c(const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, + int shift, int stride, int arch); + +/** Compute a backward MDCT (no scaling) and performs weighted overlap-add + (scales implicitly by 1/2) */ +void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 * OPUS_RESTRICT window, + int overlap, int shift, int stride, int arch); + +#if !defined(OVERRIDE_OPUS_MDCT) +/* Is run-time CPU detection enabled on this platform? */ +#if defined(OPUS_HAVE_RTCD) && defined(HAVE_ARM_NE10) + +extern void (*const CLT_MDCT_FORWARD_IMPL[OPUS_ARCHMASK+1])( + const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, const opus_val16 *window, + int overlap, int shift, int stride, int arch); + +#define clt_mdct_forward(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) \ + ((*CLT_MDCT_FORWARD_IMPL[(arch)&OPUS_ARCHMASK])(_l, _in, _out, \ + _window, _overlap, _shift, \ + _stride, _arch)) + +extern void (*const CLT_MDCT_BACKWARD_IMPL[OPUS_ARCHMASK+1])( + const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, const opus_val16 *window, + int overlap, int shift, int stride, int arch); + +#define clt_mdct_backward(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) \ + (*CLT_MDCT_BACKWARD_IMPL[(arch)&OPUS_ARCHMASK])(_l, _in, _out, \ + _window, _overlap, _shift, \ + _stride, _arch) + +#else /* if defined(OPUS_HAVE_RTCD) && defined(HAVE_ARM_NE10) */ + +#define clt_mdct_forward(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) \ + clt_mdct_forward_c(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) + +#define clt_mdct_backward(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) \ + clt_mdct_backward_c(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) + +#endif /* end if defined(OPUS_HAVE_RTCD) && defined(HAVE_ARM_NE10) && !defined(FIXED_POINT) */ +#endif /* end if !defined(OVERRIDE_OPUS_MDCT) */ + +#endif diff --git a/port/third_party/opus/celt/mfrngcod.h b/port/third_party/opus/celt/mfrngcod.h new file mode 100644 index 00000000..809152a5 --- /dev/null +++ b/port/third_party/opus/celt/mfrngcod.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2001-2008 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#if !defined(_mfrngcode_H) +# define _mfrngcode_H (1) +# include "entcode.h" + +/*Constants used by the entropy encoder/decoder.*/ + +/*The number of bits to output at a time.*/ +# define EC_SYM_BITS (8) +/*The total number of bits in each of the state registers.*/ +# define EC_CODE_BITS (32) +/*The maximum symbol value.*/ +# define EC_SYM_MAX ((1U<>EC_SYM_BITS) +/*The number of bits available for the last, partial symbol in the code field.*/ +# define EC_CODE_EXTRA ((EC_CODE_BITS-2)%EC_SYM_BITS+1) +#endif diff --git a/port/third_party/opus/celt/modes.c b/port/third_party/opus/celt/modes.c new file mode 100644 index 00000000..23f7cde6 --- /dev/null +++ b/port/third_party/opus/celt/modes.c @@ -0,0 +1,445 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "celt.h" +#include "modes.h" +#include "rate.h" +#include "os_support.h" +#include "stack_alloc.h" +#include "quant_bands.h" +#include "cpu_support.h" + +static const opus_int16 eband5ms[] = { +/*0 200 400 600 800 1k 1.2 1.4 1.6 2k 2.4 2.8 3.2 4k 4.8 5.6 6.8 8k 9.6 12k 15.6 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 34, 40, 48, 60, 78, 100 +}; + +/* Alternate tuning (partially derived from Vorbis) */ +#define BITALLOC_SIZE 11 +/* Bit allocation table in units of 1/32 bit/sample (0.1875 dB SNR) */ +static const unsigned char band_allocation[] = { +/*0 200 400 600 800 1k 1.2 1.4 1.6 2k 2.4 2.8 3.2 4k 4.8 5.6 6.8 8k 9.6 12k 15.6 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 90, 80, 75, 69, 63, 56, 49, 40, 34, 29, 20, 18, 10, 0, 0, 0, 0, 0, 0, 0, 0, +110,100, 90, 84, 78, 71, 65, 58, 51, 45, 39, 32, 26, 20, 12, 0, 0, 0, 0, 0, 0, +118,110,103, 93, 86, 80, 75, 70, 65, 59, 53, 47, 40, 31, 23, 15, 4, 0, 0, 0, 0, +126,119,112,104, 95, 89, 83, 78, 72, 66, 60, 54, 47, 39, 32, 25, 17, 12, 1, 0, 0, +134,127,120,114,103, 97, 91, 85, 78, 72, 66, 60, 54, 47, 41, 35, 29, 23, 16, 10, 1, +144,137,130,124,113,107,101, 95, 88, 82, 76, 70, 64, 57, 51, 45, 39, 33, 26, 15, 1, +152,145,138,132,123,117,111,105, 98, 92, 86, 80, 74, 67, 61, 55, 49, 43, 36, 20, 1, +162,155,148,142,133,127,121,115,108,102, 96, 90, 84, 77, 71, 65, 59, 53, 46, 30, 1, +172,165,158,152,143,137,131,125,118,112,106,100, 94, 87, 81, 75, 69, 63, 56, 45, 20, +200,200,200,200,200,200,200,200,198,193,188,183,178,173,168,163,158,153,148,129,104, +}; + +#ifndef CUSTOM_MODES_ONLY + #ifdef FIXED_POINT + #include "static_modes_fixed.h" + #else + #include "static_modes_float.h" + #endif +#endif /* CUSTOM_MODES_ONLY */ + +#ifndef M_PI +#define M_PI 3.141592653 +#endif + +#ifdef CUSTOM_MODES + +/* Defining 25 critical bands for the full 0-20 kHz audio bandwidth + Taken from http://ccrma.stanford.edu/~jos/bbt/Bark_Frequency_Scale.html */ +#define BARK_BANDS 25 +static const opus_int16 bark_freq[BARK_BANDS+1] = { + 0, 100, 200, 300, 400, + 510, 630, 770, 920, 1080, + 1270, 1480, 1720, 2000, 2320, + 2700, 3150, 3700, 4400, 5300, + 6400, 7700, 9500, 12000, 15500, + 20000}; + +static opus_int16 *compute_ebands(opus_int32 Fs, int frame_size, int res, int *nbEBands) +{ + opus_int16 *eBands; + int i, j, lin, low, high, nBark, offset=0; + + /* All modes that have 2.5 ms short blocks use the same definition */ + if (Fs == 400*(opus_int32)frame_size) + { + *nbEBands = sizeof(eband5ms)/sizeof(eband5ms[0])-1; + eBands = opus_alloc(sizeof(opus_int16)*(*nbEBands+1)); + for (i=0;i<*nbEBands+1;i++) + eBands[i] = eband5ms[i]; + return eBands; + } + /* Find the number of critical bands supported by our sampling rate */ + for (nBark=1;nBark= Fs) + break; + + /* Find where the linear part ends (i.e. where the spacing is more than min_width */ + for (lin=0;lin= res) + break; + + low = (bark_freq[lin]+res/2)/res; + high = nBark-lin; + *nbEBands = low+high; + eBands = opus_alloc(sizeof(opus_int16)*(*nbEBands+2)); + + if (eBands==NULL) + return NULL; + + /* Linear spacing (min_width) */ + for (i=0;i0) + offset = eBands[low-1]*res - bark_freq[lin-1]; + /* Spacing follows critical bands */ + for (i=0;i frame_size) + eBands[*nbEBands] = frame_size; + for (i=1;i<*nbEBands-1;i++) + { + if (eBands[i+1]-eBands[i] < eBands[i]-eBands[i-1]) + { + eBands[i] -= (2*eBands[i]-eBands[i-1]-eBands[i+1])/2; + } + } + /* Remove any empty bands. */ + for (i=j=0;i<*nbEBands;i++) + if(eBands[i+1]>eBands[j]) + eBands[++j]=eBands[i+1]; + *nbEBands=j; + + for (i=1;i<*nbEBands;i++) + { + /* Every band must be smaller than the last band. */ + celt_assert(eBands[i]-eBands[i-1]<=eBands[*nbEBands]-eBands[*nbEBands-1]); + /* Each band must be no larger than twice the size of the previous one. */ + celt_assert(eBands[i+1]-eBands[i]<=2*(eBands[i]-eBands[i-1])); + } + + return eBands; +} + +static void compute_allocation_table(CELTMode *mode) +{ + int i, j; + unsigned char *allocVectors; + int maxBands = sizeof(eband5ms)/sizeof(eband5ms[0])-1; + + mode->nbAllocVectors = BITALLOC_SIZE; + allocVectors = opus_alloc(sizeof(unsigned char)*(BITALLOC_SIZE*mode->nbEBands)); + if (allocVectors==NULL) + { + mode->allocVectors = NULL; + return; + } + + /* Check for standard mode */ + if (mode->Fs == 400*(opus_int32)mode->shortMdctSize) + { + for (i=0;inbEBands;i++) + allocVectors[i] = band_allocation[i]; + mode->allocVectors = allocVectors; + return; + } + /* If not the standard mode, interpolate */ + /* Compute per-codec-band allocation from per-critical-band matrix */ + for (i=0;inbEBands;j++) + { + int k; + for (k=0;k mode->eBands[j]*(opus_int32)mode->Fs/mode->shortMdctSize) + break; + } + if (k>maxBands-1) + allocVectors[i*mode->nbEBands+j] = band_allocation[i*maxBands + maxBands-1]; + else { + opus_int32 a0, a1; + a1 = mode->eBands[j]*(opus_int32)mode->Fs/mode->shortMdctSize - 400*(opus_int32)eband5ms[k-1]; + a0 = 400*(opus_int32)eband5ms[k] - mode->eBands[j]*(opus_int32)mode->Fs/mode->shortMdctSize; + allocVectors[i*mode->nbEBands+j] = (a0*band_allocation[i*maxBands+k-1] + + a1*band_allocation[i*maxBands+k])/(a0+a1); + } + } + } + + /*printf ("\n"); + for (i=0;inbEBands;j++) + printf ("%d ", allocVectors[i*mode->nbEBands+j]); + printf ("\n"); + } + exit(0);*/ + + mode->allocVectors = allocVectors; +} + +#endif /* CUSTOM_MODES */ + +CELTMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error) +{ + int i; +#ifdef CUSTOM_MODES + CELTMode *mode=NULL; + int res; + opus_val16 *window; + opus_int16 *logN; + int LM; + int arch = opus_select_arch(); + ALLOC_STACK; +#if !defined(VAR_ARRAYS) && !defined(USE_ALLOCA) + if (global_stack==NULL) + goto failure; +#endif +#endif + +#ifndef CUSTOM_MODES_ONLY + for (i=0;iFs && + (frame_size<shortMdctSize*static_mode_list[i]->nbShortMdcts) + { + if (error) + *error = OPUS_OK; + return (CELTMode*)static_mode_list[i]; + } + } + } +#endif /* CUSTOM_MODES_ONLY */ + +#ifndef CUSTOM_MODES + if (error) + *error = OPUS_BAD_ARG; + return NULL; +#else + + /* The good thing here is that permutation of the arguments will automatically be invalid */ + + if (Fs < 8000 || Fs > 96000) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + if (frame_size < 40 || frame_size > 1024 || frame_size%2!=0) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + /* Frames of less than 1ms are not supported. */ + if ((opus_int32)frame_size*1000 < Fs) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + + if ((opus_int32)frame_size*75 >= Fs && (frame_size%16)==0) + { + LM = 3; + } else if ((opus_int32)frame_size*150 >= Fs && (frame_size%8)==0) + { + LM = 2; + } else if ((opus_int32)frame_size*300 >= Fs && (frame_size%4)==0) + { + LM = 1; + } else + { + LM = 0; + } + + /* Shorts longer than 3.3ms are not supported. */ + if ((opus_int32)(frame_size>>LM)*300 > Fs) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + + mode = opus_alloc(sizeof(CELTMode)); + if (mode==NULL) + goto failure; + mode->Fs = Fs; + + /* Pre/de-emphasis depends on sampling rate. The "standard" pre-emphasis + is defined as A(z) = 1 - 0.85*z^-1 at 48 kHz. Other rates should + approximate that. */ + if(Fs < 12000) /* 8 kHz */ + { + mode->preemph[0] = QCONST16(0.3500061035f, 15); + mode->preemph[1] = -QCONST16(0.1799926758f, 15); + mode->preemph[2] = QCONST16(0.2719968125f, SIG_SHIFT); /* exact 1/preemph[3] */ + mode->preemph[3] = QCONST16(3.6765136719f, 13); + } else if(Fs < 24000) /* 16 kHz */ + { + mode->preemph[0] = QCONST16(0.6000061035f, 15); + mode->preemph[1] = -QCONST16(0.1799926758f, 15); + mode->preemph[2] = QCONST16(0.4424998650f, SIG_SHIFT); /* exact 1/preemph[3] */ + mode->preemph[3] = QCONST16(2.2598876953f, 13); + } else if(Fs < 40000) /* 32 kHz */ + { + mode->preemph[0] = QCONST16(0.7799987793f, 15); + mode->preemph[1] = -QCONST16(0.1000061035f, 15); + mode->preemph[2] = QCONST16(0.7499771125f, SIG_SHIFT); /* exact 1/preemph[3] */ + mode->preemph[3] = QCONST16(1.3333740234f, 13); + } else /* 48 kHz */ + { + mode->preemph[0] = QCONST16(0.8500061035f, 15); + mode->preemph[1] = QCONST16(0.0f, 15); + mode->preemph[2] = QCONST16(1.f, SIG_SHIFT); + mode->preemph[3] = QCONST16(1.f, 13); + } + + mode->maxLM = LM; + mode->nbShortMdcts = 1<shortMdctSize = frame_size/mode->nbShortMdcts; + res = (mode->Fs+mode->shortMdctSize)/(2*mode->shortMdctSize); + + mode->eBands = compute_ebands(Fs, mode->shortMdctSize, res, &mode->nbEBands); + if (mode->eBands==NULL) + goto failure; +#if !defined(SMALL_FOOTPRINT) + /* Make sure we don't allocate a band larger than our PVQ table. + 208 should be enough, but let's be paranoid. */ + if ((mode->eBands[mode->nbEBands] - mode->eBands[mode->nbEBands-1])< + 208) { + goto failure; + } +#endif + + mode->effEBands = mode->nbEBands; + while (mode->eBands[mode->effEBands] > mode->shortMdctSize) + mode->effEBands--; + + /* Overlap must be divisible by 4 */ + mode->overlap = ((mode->shortMdctSize>>2)<<2); + + compute_allocation_table(mode); + if (mode->allocVectors==NULL) + goto failure; + + window = (opus_val16*)opus_alloc(mode->overlap*sizeof(opus_val16)); + if (window==NULL) + goto failure; + +#ifndef FIXED_POINT + for (i=0;ioverlap;i++) + window[i] = Q15ONE*sin(.5*M_PI* sin(.5*M_PI*(i+.5)/mode->overlap) * sin(.5*M_PI*(i+.5)/mode->overlap)); +#else + for (i=0;ioverlap;i++) + window[i] = MIN32(32767,floor(.5+32768.*sin(.5*M_PI* sin(.5*M_PI*(i+.5)/mode->overlap) * sin(.5*M_PI*(i+.5)/mode->overlap)))); +#endif + mode->window = window; + + logN = (opus_int16*)opus_alloc(mode->nbEBands*sizeof(opus_int16)); + if (logN==NULL) + goto failure; + + for (i=0;inbEBands;i++) + logN[i] = log2_frac(mode->eBands[i+1]-mode->eBands[i], BITRES); + mode->logN = logN; + + compute_pulse_cache(mode, mode->maxLM); + + if (clt_mdct_init(&mode->mdct, 2*mode->shortMdctSize*mode->nbShortMdcts, + mode->maxLM, arch) == 0) + goto failure; + + if (error) + *error = OPUS_OK; + + return mode; +failure: + if (error) + *error = OPUS_ALLOC_FAIL; + if (mode!=NULL) + opus_custom_mode_destroy(mode); + return NULL; +#endif /* !CUSTOM_MODES */ +} + +#ifdef CUSTOM_MODES +void opus_custom_mode_destroy(CELTMode *mode) +{ + int arch = opus_select_arch(); + + if (mode == NULL) + return; +#ifndef CUSTOM_MODES_ONLY + { + int i; + for (i=0;ieBands); + opus_free((unsigned char*)mode->allocVectors); + + opus_free((opus_val16*)mode->window); + opus_free((opus_int16*)mode->logN); + + opus_free((opus_int16*)mode->cache.index); + opus_free((unsigned char*)mode->cache.bits); + opus_free((unsigned char*)mode->cache.caps); + clt_mdct_clear(&mode->mdct, arch); + + opus_free((CELTMode *)mode); +} +#endif diff --git a/port/third_party/opus/celt/modes.h b/port/third_party/opus/celt/modes.h new file mode 100644 index 00000000..be813ccc --- /dev/null +++ b/port/third_party/opus/celt/modes.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef MODES_H +#define MODES_H + +#include "opus_types.h" +#include "celt.h" +#include "arch.h" +#include "mdct.h" +#include "entenc.h" +#include "entdec.h" + +#define MAX_PERIOD 1024 + +typedef struct { + int size; + const opus_int16 *index; + const unsigned char *bits; + const unsigned char *caps; +} PulseCache; + +/** Mode definition (opaque) + @brief Mode definition + */ +struct OpusCustomMode { + opus_int32 Fs; + int overlap; + + int nbEBands; + int effEBands; + opus_val16 preemph[4]; + const opus_int16 *eBands; /**< Definition for each "pseudo-critical band" */ + + int maxLM; + int nbShortMdcts; + int shortMdctSize; + + int nbAllocVectors; /**< Number of lines in the matrix below */ + const unsigned char *allocVectors; /**< Number of bits in each band for several rates */ + const opus_int16 *logN; + + const opus_val16 *window; + mdct_lookup mdct; + PulseCache cache; +}; + + +#endif diff --git a/port/third_party/opus/celt/os_support.h b/port/third_party/opus/celt/os_support.h new file mode 100644 index 00000000..7d2d3781 --- /dev/null +++ b/port/third_party/opus/celt/os_support.h @@ -0,0 +1,99 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: os_support.h + This is the (tiny) OS abstraction layer. Aside from math.h, this is the + only place where system headers are allowed. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. +*/ + +#ifndef OS_SUPPORT_H +#define OS_SUPPORT_H + +#ifdef CUSTOM_SUPPORT +# include "custom_support.h" +#endif + +#include "opus_types.h" +#include "opus_defines.h" + +#include +#include + +/** Opus wrapper for malloc(). To do your own dynamic allocation replace this function, opus_realloc, and opus_free */ +#ifndef OVERRIDE_OPUS_ALLOC +static OPUS_INLINE void *opus_alloc (size_t size) +{ + return malloc(size); +} +#endif + +#ifndef OVERRIDE_OPUS_REALLOC +static OPUS_INLINE void *opus_realloc (void *ptr, size_t size) +{ + return realloc(ptr, size); +} +#endif + +/** Used only for non-threadsafe pseudostack. + If desired, this can always return the same area of memory rather than allocating a new one every time. */ +#ifndef OVERRIDE_OPUS_ALLOC_SCRATCH +static OPUS_INLINE void *opus_alloc_scratch (size_t size) +{ + /* Scratch space doesn't need to be cleared */ + return opus_alloc(size); +} +#endif + +/** Opus wrapper for free(). To do your own dynamic allocation replace this function, opus_realloc, and opus_free */ +#ifndef OVERRIDE_OPUS_FREE +static OPUS_INLINE void opus_free (void *ptr) +{ + free(ptr); +} +#endif + +/** Copy n elements from src to dst. The 0* term provides compile-time type checking */ +#ifndef OVERRIDE_OPUS_COPY +#define OPUS_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) +#endif + +/** Copy n elements from src to dst, allowing overlapping regions. The 0* term + provides compile-time type checking */ +#ifndef OVERRIDE_OPUS_MOVE +#define OPUS_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) +#endif + +/** Set n elements of dst to zero */ +#ifndef OVERRIDE_OPUS_CLEAR +#define OPUS_CLEAR(dst, n) (memset((dst), 0, (n)*sizeof(*(dst)))) +#endif + +/*#ifdef __GNUC__ +#pragma GCC poison printf sprintf +#pragma GCC poison malloc free realloc calloc +#endif*/ + +#endif /* OS_SUPPORT_H */ + diff --git a/port/third_party/opus/celt/pitch.c b/port/third_party/opus/celt/pitch.c new file mode 100644 index 00000000..e33c60a3 --- /dev/null +++ b/port/third_party/opus/celt/pitch.c @@ -0,0 +1,555 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/** + @file pitch.c + @brief Pitch analysis + */ + +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pitch.h" +#include "os_support.h" +#include "modes.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "celt_lpc.h" + +static void find_best_pitch(opus_val32 *xcorr, opus_val16 *y, int len, + int max_pitch, int *best_pitch +#ifdef FIXED_POINT + , int yshift, opus_val32 maxcorr +#endif + ) +{ + int i, j; + opus_val32 Syy=1; + opus_val16 best_num[2]; + opus_val32 best_den[2]; +#ifdef FIXED_POINT + int xshift; + + xshift = celt_ilog2(maxcorr)-14; +#endif + + best_num[0] = -1; + best_num[1] = -1; + best_den[0] = 0; + best_den[1] = 0; + best_pitch[0] = 0; + best_pitch[1] = 1; + for (j=0;j0) + { + opus_val16 num; + opus_val32 xcorr16; + xcorr16 = EXTRACT16(VSHR32(xcorr[i], xshift)); +#ifndef FIXED_POINT + /* Considering the range of xcorr16, this should avoid both underflows + and overflows (inf) when squaring xcorr16 */ + xcorr16 *= 1e-12f; +#endif + num = MULT16_16_Q15(xcorr16,xcorr16); + if (MULT16_32_Q15(num,best_den[1]) > MULT16_32_Q15(best_num[1],Syy)) + { + if (MULT16_32_Q15(num,best_den[0]) > MULT16_32_Q15(best_num[0],Syy)) + { + best_num[1] = best_num[0]; + best_den[1] = best_den[0]; + best_pitch[1] = best_pitch[0]; + best_num[0] = num; + best_den[0] = Syy; + best_pitch[0] = i; + } else { + best_num[1] = num; + best_den[1] = Syy; + best_pitch[1] = i; + } + } + } + Syy += SHR32(MULT16_16(y[i+len],y[i+len]),yshift) - SHR32(MULT16_16(y[i],y[i]),yshift); + Syy = MAX32(1, Syy); + } +} + +static void celt_fir5(opus_val16 *x, + const opus_val16 *num, + int N) +{ + int i; + opus_val16 num0, num1, num2, num3, num4; + opus_val32 mem0, mem1, mem2, mem3, mem4; + num0=num[0]; + num1=num[1]; + num2=num[2]; + num3=num[3]; + num4=num[4]; + mem0=0; + mem1=0; + mem2=0; + mem3=0; + mem4=0; + for (i=0;i>1;i++) + x_lp[i] = SHR32(x[0][(2*i-1)], shift+2) + SHR32(x[0][(2*i+1)], shift+2) + SHR32(x[0][2*i], shift+1); + x_lp[0] = SHR32(x[0][1], shift+2) + SHR32(x[0][0], shift+1); + if (C==2) + { + for (i=1;i>1;i++) + x_lp[i] += SHR32(x[1][(2*i-1)], shift+2) + SHR32(x[1][(2*i+1)], shift+2) + SHR32(x[1][2*i], shift+1); + x_lp[0] += SHR32(x[1][1], shift+2) + SHR32(x[1][0], shift+1); + } +#else + for (i=1;i>1;i++) + x_lp[i] = .25f*x[0][(2*i-1)] + .25f*x[0][(2*i+1)] + .5f*x[0][2*i]; + x_lp[0] = .25f*x[0][1] + .5f*x[0][0]; + if (C==2) + { + for (i=1;i>1;i++) + x_lp[i] += .25f*x[1][(2*i-1)] + .25f*x[1][(2*i+1)] + .5f*x[1][2*i]; + x_lp[0] += .25f*x[1][1] + .5f*x[1][0]; + } +#endif + _celt_autocorr(x_lp, ac, NULL, 0, + 4, len>>1, arch); + + /* Noise floor -40 dB */ +#ifdef FIXED_POINT + ac[0] += SHR32(ac[0],13); +#else + ac[0] *= 1.0001f; +#endif + /* Lag windowing */ + for (i=1;i<=4;i++) + { + /*ac[i] *= exp(-.5*(2*M_PI*.002*i)*(2*M_PI*.002*i));*/ +#ifdef FIXED_POINT + ac[i] -= MULT16_32_Q15(2*i*i, ac[i]); +#else + ac[i] -= ac[i]*(.008f*i)*(.008f*i); +#endif + } + + _celt_lpc(lpc, ac, 4); + for (i=0;i<4;i++) + { + tmp = MULT16_16_Q15(QCONST16(.9f,15), tmp); + lpc[i] = MULT16_16_Q15(lpc[i], tmp); + } + /* Add a zero */ + lpc2[0] = lpc[0] + QCONST16(.8f,SIG_SHIFT); + lpc2[1] = lpc[1] + MULT16_16_Q15(c1,lpc[0]); + lpc2[2] = lpc[2] + MULT16_16_Q15(c1,lpc[1]); + lpc2[3] = lpc[3] + MULT16_16_Q15(c1,lpc[2]); + lpc2[4] = MULT16_16_Q15(c1,lpc[3]); + celt_fir5(x_lp, lpc2, len>>1); +} + +/* Pure C implementation. */ +#ifdef FIXED_POINT +opus_val32 +#else +void +#endif +celt_pitch_xcorr_c(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch, int arch) +{ + +#if 0 /* This is a simple version of the pitch correlation that should work + well on DSPs like Blackfin and TI C5x/C6x */ + int i, j; +#ifdef FIXED_POINT + opus_val32 maxcorr=1; +#endif +#if !defined(OVERRIDE_PITCH_XCORR) + (void)arch; +#endif + for (i=0;i0); + celt_sig_assert(((size_t)_x&3)==0); + for (i=0;i0); + celt_assert(max_pitch>0); + lag = len+max_pitch; + + ALLOC(x_lp4, len>>2, opus_val16); + ALLOC(y_lp4, lag>>2, opus_val16); + ALLOC(xcorr, max_pitch>>1, opus_val32); + + /* Downsample by 2 again */ + for (j=0;j>2;j++) + x_lp4[j] = x_lp[2*j]; + for (j=0;j>2;j++) + y_lp4[j] = y[2*j]; + +#ifdef FIXED_POINT + xmax = celt_maxabs16(x_lp4, len>>2); + ymax = celt_maxabs16(y_lp4, lag>>2); + shift = celt_ilog2(MAX32(1, MAX32(xmax, ymax)))-11; + if (shift>0) + { + for (j=0;j>2;j++) + x_lp4[j] = SHR16(x_lp4[j], shift); + for (j=0;j>2;j++) + y_lp4[j] = SHR16(y_lp4[j], shift); + /* Use double the shift for a MAC */ + shift *= 2; + } else { + shift = 0; + } +#endif + + /* Coarse search with 4x decimation */ + +#ifdef FIXED_POINT + maxcorr = +#endif + celt_pitch_xcorr(x_lp4, y_lp4, xcorr, len>>2, max_pitch>>2, arch); + + find_best_pitch(xcorr, y_lp4, len>>2, max_pitch>>2, best_pitch +#ifdef FIXED_POINT + , 0, maxcorr +#endif + ); + + /* Finer search with 2x decimation */ +#ifdef FIXED_POINT + maxcorr=1; +#endif + for (i=0;i>1;i++) + { + opus_val32 sum; + xcorr[i] = 0; + if (abs(i-2*best_pitch[0])>2 && abs(i-2*best_pitch[1])>2) + continue; +#ifdef FIXED_POINT + sum = 0; + for (j=0;j>1;j++) + sum += SHR32(MULT16_16(x_lp[j],y[i+j]), shift); +#else + sum = celt_inner_prod(x_lp, y+i, len>>1, arch); +#endif + xcorr[i] = MAX32(-1, sum); +#ifdef FIXED_POINT + maxcorr = MAX32(maxcorr, sum); +#endif + } + find_best_pitch(xcorr, y, len>>1, max_pitch>>1, best_pitch +#ifdef FIXED_POINT + , shift+1, maxcorr +#endif + ); + + /* Refine by pseudo-interpolation */ + if (best_pitch[0]>0 && best_pitch[0]<(max_pitch>>1)-1) + { + opus_val32 a, b, c; + a = xcorr[best_pitch[0]-1]; + b = xcorr[best_pitch[0]]; + c = xcorr[best_pitch[0]+1]; + if ((c-a) > MULT16_32_Q15(QCONST16(.7f,15),b-a)) + offset = 1; + else if ((a-c) > MULT16_32_Q15(QCONST16(.7f,15),b-c)) + offset = -1; + else + offset = 0; + } else { + offset = 0; + } + *pitch = 2*best_pitch[0]-offset; + + RESTORE_STACK; +} + +#ifdef FIXED_POINT +static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy) +{ + opus_val32 x2y2; + int sx, sy, shift; + opus_val32 g; + opus_val16 den; + if (xy == 0 || xx == 0 || yy == 0) + return 0; + sx = celt_ilog2(xx)-14; + sy = celt_ilog2(yy)-14; + shift = sx + sy; + x2y2 = SHR32(MULT16_16(VSHR32(xx, sx), VSHR32(yy, sy)), 14); + if (shift & 1) { + if (x2y2 < 32768) + { + x2y2 <<= 1; + shift--; + } else { + x2y2 >>= 1; + shift++; + } + } + den = celt_rsqrt_norm(x2y2); + g = MULT16_32_Q15(den, xy); + g = VSHR32(g, (shift>>1)-1); + return EXTRACT16(MIN32(g, Q15ONE)); +} +#else +static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy) +{ + return xy/celt_sqrt(1+xx*yy); +} +#endif + +static const int second_check[16] = {0, 0, 3, 2, 3, 2, 5, 2, 3, 2, 3, 2, 5, 2, 3, 2}; +opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, + int N, int *T0_, int prev_period, opus_val16 prev_gain, int arch) +{ + int k, i, T, T0; + opus_val16 g, g0; + opus_val16 pg; + opus_val32 xy,xx,yy,xy2; + opus_val32 xcorr[3]; + opus_val32 best_xy, best_yy; + int offset; + int minperiod0; + VARDECL(opus_val32, yy_lookup); + SAVE_STACK; + + minperiod0 = minperiod; + maxperiod /= 2; + minperiod /= 2; + *T0_ /= 2; + prev_period /= 2; + N /= 2; + x += maxperiod; + if (*T0_>=maxperiod) + *T0_=maxperiod-1; + + T = T0 = *T0_; + ALLOC(yy_lookup, maxperiod+1, opus_val32); + dual_inner_prod(x, x, x-T0, N, &xx, &xy, arch); + yy_lookup[0] = xx; + yy=xx; + for (i=1;i<=maxperiod;i++) + { + yy = yy+MULT16_16(x[-i],x[-i])-MULT16_16(x[N-i],x[N-i]); + yy_lookup[i] = MAX32(0, yy); + } + yy = yy_lookup[T0]; + best_xy = xy; + best_yy = yy; + g = g0 = compute_pitch_gain(xy, xx, yy); + /* Look for any pitch at T/k */ + for (k=2;k<=15;k++) + { + int T1, T1b; + opus_val16 g1; + opus_val16 cont=0; + opus_val16 thresh; + T1 = celt_udiv(2*T0+k, 2*k); + if (T1 < minperiod) + break; + /* Look for another strong correlation at T1b */ + if (k==2) + { + if (T1+T0>maxperiod) + T1b = T0; + else + T1b = T0+T1; + } else + { + T1b = celt_udiv(2*second_check[k]*T0+k, 2*k); + } + dual_inner_prod(x, &x[-T1], &x[-T1b], N, &xy, &xy2, arch); + xy = HALF32(xy + xy2); + yy = HALF32(yy_lookup[T1] + yy_lookup[T1b]); + g1 = compute_pitch_gain(xy, xx, yy); + if (abs(T1-prev_period)<=1) + cont = prev_gain; + else if (abs(T1-prev_period)<=2 && 5*k*k < T0) + cont = HALF16(prev_gain); + else + cont = 0; + thresh = MAX16(QCONST16(.3f,15), MULT16_16_Q15(QCONST16(.7f,15),g0)-cont); + /* Bias against very high pitch (very short period) to avoid false-positives + due to short-term correlation */ + if (T1<3*minperiod) + thresh = MAX16(QCONST16(.4f,15), MULT16_16_Q15(QCONST16(.85f,15),g0)-cont); + else if (T1<2*minperiod) + thresh = MAX16(QCONST16(.5f,15), MULT16_16_Q15(QCONST16(.9f,15),g0)-cont); + if (g1 > thresh) + { + best_xy = xy; + best_yy = yy; + T = T1; + g = g1; + } + } + best_xy = MAX32(0, best_xy); + if (best_yy <= best_xy) + pg = Q15ONE; + else + pg = SHR32(frac_div32(best_xy,best_yy+1),16); + + for (k=0;k<3;k++) + xcorr[k] = celt_inner_prod(x, x-(T+k-1), N, arch); + if ((xcorr[2]-xcorr[0]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[0])) + offset = 1; + else if ((xcorr[0]-xcorr[2]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[2])) + offset = -1; + else + offset = 0; + if (pg > g) + pg = g; + *T0_ = 2*T+offset; + + if (*T0_=3); + y_3=0; /* gcc doesn't realize that y_3 can't be used uninitialized */ + y_0=*y++; + y_1=*y++; + y_2=*y++; + for (j=0;j +#include "os_support.h" +#include "arch.h" +#include "mathops.h" +#include "stack_alloc.h" +#include "rate.h" + +#ifdef FIXED_POINT +/* Mean energy in each band quantized in Q4 */ +const signed char eMeans[25] = { + 103,100, 92, 85, 81, + 77, 72, 70, 78, 75, + 73, 71, 78, 74, 69, + 72, 70, 74, 76, 71, + 60, 60, 60, 60, 60 +}; +#else +/* Mean energy in each band quantized in Q4 and converted back to float */ +const opus_val16 eMeans[25] = { + 6.437500f, 6.250000f, 5.750000f, 5.312500f, 5.062500f, + 4.812500f, 4.500000f, 4.375000f, 4.875000f, 4.687500f, + 4.562500f, 4.437500f, 4.875000f, 4.625000f, 4.312500f, + 4.500000f, 4.375000f, 4.625000f, 4.750000f, 4.437500f, + 3.750000f, 3.750000f, 3.750000f, 3.750000f, 3.750000f +}; +#endif +/* prediction coefficients: 0.9, 0.8, 0.65, 0.5 */ +#ifdef FIXED_POINT +static const opus_val16 pred_coef[4] = {29440, 26112, 21248, 16384}; +static const opus_val16 beta_coef[4] = {30147, 22282, 12124, 6554}; +static const opus_val16 beta_intra = 4915; +#else +static const opus_val16 pred_coef[4] = {29440/32768., 26112/32768., 21248/32768., 16384/32768.}; +static const opus_val16 beta_coef[4] = {30147/32768., 22282/32768., 12124/32768., 6554/32768.}; +static const opus_val16 beta_intra = 4915/32768.; +#endif + +/*Parameters of the Laplace-like probability models used for the coarse energy. + There is one pair of parameters for each frame size, prediction type + (inter/intra), and band number. + The first number of each pair is the probability of 0, and the second is the + decay rate, both in Q8 precision.*/ +static const unsigned char e_prob_model[4][2][42] = { + /*120 sample frames.*/ + { + /*Inter*/ + { + 72, 127, 65, 129, 66, 128, 65, 128, 64, 128, 62, 128, 64, 128, + 64, 128, 92, 78, 92, 79, 92, 78, 90, 79, 116, 41, 115, 40, + 114, 40, 132, 26, 132, 26, 145, 17, 161, 12, 176, 10, 177, 11 + }, + /*Intra*/ + { + 24, 179, 48, 138, 54, 135, 54, 132, 53, 134, 56, 133, 55, 132, + 55, 132, 61, 114, 70, 96, 74, 88, 75, 88, 87, 74, 89, 66, + 91, 67, 100, 59, 108, 50, 120, 40, 122, 37, 97, 43, 78, 50 + } + }, + /*240 sample frames.*/ + { + /*Inter*/ + { + 83, 78, 84, 81, 88, 75, 86, 74, 87, 71, 90, 73, 93, 74, + 93, 74, 109, 40, 114, 36, 117, 34, 117, 34, 143, 17, 145, 18, + 146, 19, 162, 12, 165, 10, 178, 7, 189, 6, 190, 8, 177, 9 + }, + /*Intra*/ + { + 23, 178, 54, 115, 63, 102, 66, 98, 69, 99, 74, 89, 71, 91, + 73, 91, 78, 89, 86, 80, 92, 66, 93, 64, 102, 59, 103, 60, + 104, 60, 117, 52, 123, 44, 138, 35, 133, 31, 97, 38, 77, 45 + } + }, + /*480 sample frames.*/ + { + /*Inter*/ + { + 61, 90, 93, 60, 105, 42, 107, 41, 110, 45, 116, 38, 113, 38, + 112, 38, 124, 26, 132, 27, 136, 19, 140, 20, 155, 14, 159, 16, + 158, 18, 170, 13, 177, 10, 187, 8, 192, 6, 175, 9, 159, 10 + }, + /*Intra*/ + { + 21, 178, 59, 110, 71, 86, 75, 85, 84, 83, 91, 66, 88, 73, + 87, 72, 92, 75, 98, 72, 105, 58, 107, 54, 115, 52, 114, 55, + 112, 56, 129, 51, 132, 40, 150, 33, 140, 29, 98, 35, 77, 42 + } + }, + /*960 sample frames.*/ + { + /*Inter*/ + { + 42, 121, 96, 66, 108, 43, 111, 40, 117, 44, 123, 32, 120, 36, + 119, 33, 127, 33, 134, 34, 139, 21, 147, 23, 152, 20, 158, 25, + 154, 26, 166, 21, 173, 16, 184, 13, 184, 10, 150, 13, 139, 15 + }, + /*Intra*/ + { + 22, 178, 63, 114, 74, 82, 84, 83, 92, 82, 103, 62, 96, 72, + 96, 67, 101, 73, 107, 72, 113, 55, 118, 52, 125, 52, 118, 52, + 117, 55, 135, 49, 137, 39, 157, 32, 145, 29, 97, 33, 77, 40 + } + } +}; + +static const unsigned char small_energy_icdf[3]={2,1,0}; + +static opus_val32 loss_distortion(const opus_val16 *eBands, opus_val16 *oldEBands, int start, int end, int len, int C) +{ + int c, i; + opus_val32 dist = 0; + c=0; do { + for (i=start;inbEBands]; + oldE = MAX16(-QCONST16(9.f,DB_SHIFT), oldEBands[i+c*m->nbEBands]); +#ifdef FIXED_POINT + f = SHL32(EXTEND32(x),7) - PSHR32(MULT16_16(coef,oldE), 8) - prev[c]; + /* Rounding to nearest integer here is really important! */ + qi = (f+QCONST32(.5f,DB_SHIFT+7))>>(DB_SHIFT+7); + decay_bound = EXTRACT16(MAX32(-QCONST16(28.f,DB_SHIFT), + SUB32((opus_val32)oldEBands[i+c*m->nbEBands],max_decay))); +#else + f = x-coef*oldE-prev[c]; + /* Rounding to nearest integer here is really important! */ + qi = (int)floor(.5f+f); + decay_bound = MAX16(-QCONST16(28.f,DB_SHIFT), oldEBands[i+c*m->nbEBands]) - max_decay; +#endif + /* Prevent the energy from going down too quickly (e.g. for bands + that have just one bin) */ + if (qi < 0 && x < decay_bound) + { + qi += (int)SHR16(SUB16(decay_bound,x), DB_SHIFT); + if (qi > 0) + qi = 0; + } + qi0 = qi; + /* If we don't have enough bits to encode all the energy, just assume + something safe. */ + tell = ec_tell(enc); + bits_left = budget-tell-3*C*(end-i); + if (i!=start && bits_left < 30) + { + if (bits_left < 24) + qi = IMIN(1, qi); + if (bits_left < 16) + qi = IMAX(-1, qi); + } + if (lfe && i>=2) + qi = IMIN(qi, 0); + if (budget-tell >= 15) + { + int pi; + pi = 2*IMIN(i,20); + ec_laplace_encode(enc, &qi, + prob_model[pi]<<7, prob_model[pi+1]<<6); + } + else if(budget-tell >= 2) + { + qi = IMAX(-1, IMIN(qi, 1)); + ec_enc_icdf(enc, 2*qi^-(qi<0), small_energy_icdf, 2); + } + else if(budget-tell >= 1) + { + qi = IMIN(0, qi); + ec_enc_bit_logp(enc, -qi, 1); + } + else + qi = -1; + error[i+c*m->nbEBands] = PSHR32(f,7) - SHL16(qi,DB_SHIFT); + badness += abs(qi0-qi); + q = (opus_val32)SHL32(EXTEND32(qi),DB_SHIFT); + + tmp = PSHR32(MULT16_16(coef,oldE),8) + prev[c] + SHL32(q,7); +#ifdef FIXED_POINT + tmp = MAX32(-QCONST32(28.f, DB_SHIFT+7), tmp); +#endif + oldEBands[i+c*m->nbEBands] = PSHR32(tmp, 7); + prev[c] = prev[c] + SHL32(q,7) - MULT16_16(beta,PSHR32(q,8)); + } while (++c < C); + } + return lfe ? 0 : badness; +} + +void quant_coarse_energy(const CELTMode *m, int start, int end, int effEnd, + const opus_val16 *eBands, opus_val16 *oldEBands, opus_uint32 budget, + opus_val16 *error, ec_enc *enc, int C, int LM, int nbAvailableBytes, + int force_intra, opus_val32 *delayedIntra, int two_pass, int loss_rate, int lfe) +{ + int intra; + opus_val16 max_decay; + VARDECL(opus_val16, oldEBands_intra); + VARDECL(opus_val16, error_intra); + ec_enc enc_start_state; + opus_uint32 tell; + int badness1=0; + opus_int32 intra_bias; + opus_val32 new_distortion; + SAVE_STACK; + + intra = force_intra || (!two_pass && *delayedIntra>2*C*(end-start) && nbAvailableBytes > (end-start)*C); + intra_bias = (opus_int32)((budget**delayedIntra*loss_rate)/(C*512)); + new_distortion = loss_distortion(eBands, oldEBands, start, effEnd, m->nbEBands, C); + + tell = ec_tell(enc); + if (tell+3 > budget) + two_pass = intra = 0; + + max_decay = QCONST16(16.f,DB_SHIFT); + if (end-start>10) + { +#ifdef FIXED_POINT + max_decay = MIN32(max_decay, SHL32(EXTEND32(nbAvailableBytes),DB_SHIFT-3)); +#else + max_decay = MIN32(max_decay, .125f*nbAvailableBytes); +#endif + } + if (lfe) + max_decay = QCONST16(3.f,DB_SHIFT); + enc_start_state = *enc; + + ALLOC(oldEBands_intra, C*m->nbEBands, opus_val16); + ALLOC(error_intra, C*m->nbEBands, opus_val16); + OPUS_COPY(oldEBands_intra, oldEBands, C*m->nbEBands); + + if (two_pass || intra) + { + badness1 = quant_coarse_energy_impl(m, start, end, eBands, oldEBands_intra, budget, + tell, e_prob_model[LM][1], error_intra, enc, C, LM, 1, max_decay, lfe); + } + + if (!intra) + { + unsigned char *intra_buf; + ec_enc enc_intra_state; + opus_int32 tell_intra; + opus_uint32 nstart_bytes; + opus_uint32 nintra_bytes; + opus_uint32 save_bytes; + int badness2; + VARDECL(unsigned char, intra_bits); + + tell_intra = ec_tell_frac(enc); + + enc_intra_state = *enc; + + nstart_bytes = ec_range_bytes(&enc_start_state); + nintra_bytes = ec_range_bytes(&enc_intra_state); + intra_buf = ec_get_buffer(&enc_intra_state) + nstart_bytes; + save_bytes = nintra_bytes-nstart_bytes; + if (save_bytes == 0) + save_bytes = ALLOC_NONE; + ALLOC(intra_bits, save_bytes, unsigned char); + /* Copy bits from intra bit-stream */ + OPUS_COPY(intra_bits, intra_buf, nintra_bytes - nstart_bytes); + + *enc = enc_start_state; + + badness2 = quant_coarse_energy_impl(m, start, end, eBands, oldEBands, budget, + tell, e_prob_model[LM][intra], error, enc, C, LM, 0, max_decay, lfe); + + if (two_pass && (badness1 < badness2 || (badness1 == badness2 && ((opus_int32)ec_tell_frac(enc))+intra_bias > tell_intra))) + { + *enc = enc_intra_state; + /* Copy intra bits to bit-stream */ + OPUS_COPY(intra_buf, intra_bits, nintra_bytes - nstart_bytes); + OPUS_COPY(oldEBands, oldEBands_intra, C*m->nbEBands); + OPUS_COPY(error, error_intra, C*m->nbEBands); + intra = 1; + } + } else { + OPUS_COPY(oldEBands, oldEBands_intra, C*m->nbEBands); + OPUS_COPY(error, error_intra, C*m->nbEBands); + } + + if (intra) + *delayedIntra = new_distortion; + else + *delayedIntra = ADD32(MULT16_32_Q15(MULT16_16_Q15(pred_coef[LM], pred_coef[LM]),*delayedIntra), + new_distortion); + + RESTORE_STACK; +} + +void quant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, opus_val16 *error, int *fine_quant, ec_enc *enc, int C) +{ + int i, c; + + /* Encode finer resolution */ + for (i=start;inbEBands]+QCONST16(.5f,DB_SHIFT))>>(DB_SHIFT-fine_quant[i]); +#else + q2 = (int)floor((error[i+c*m->nbEBands]+.5f)*frac); +#endif + if (q2 > frac-1) + q2 = frac-1; + if (q2<0) + q2 = 0; + ec_enc_bits(enc, q2, fine_quant[i]); +#ifdef FIXED_POINT + offset = SUB16(SHR32(SHL32(EXTEND32(q2),DB_SHIFT)+QCONST16(.5f,DB_SHIFT),fine_quant[i]),QCONST16(.5f,DB_SHIFT)); +#else + offset = (q2+.5f)*(1<<(14-fine_quant[i]))*(1.f/16384) - .5f; +#endif + oldEBands[i+c*m->nbEBands] += offset; + error[i+c*m->nbEBands] -= offset; + /*printf ("%f ", error[i] - offset);*/ + } while (++c < C); + } +} + +void quant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *oldEBands, opus_val16 *error, int *fine_quant, int *fine_priority, int bits_left, ec_enc *enc, int C) +{ + int i, prio, c; + + /* Use up the remaining bits */ + for (prio=0;prio<2;prio++) + { + for (i=start;i=C ;i++) + { + if (fine_quant[i] >= MAX_FINE_BITS || fine_priority[i]!=prio) + continue; + c=0; + do { + int q2; + opus_val16 offset; + q2 = error[i+c*m->nbEBands]<0 ? 0 : 1; + ec_enc_bits(enc, q2, 1); +#ifdef FIXED_POINT + offset = SHR16(SHL16(q2,DB_SHIFT)-QCONST16(.5f,DB_SHIFT),fine_quant[i]+1); +#else + offset = (q2-.5f)*(1<<(14-fine_quant[i]-1))*(1.f/16384); +#endif + oldEBands[i+c*m->nbEBands] += offset; + error[i+c*m->nbEBands] -= offset; + bits_left--; + } while (++c < C); + } + } +} + +void unquant_coarse_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int intra, ec_dec *dec, int C, int LM) +{ + const unsigned char *prob_model = e_prob_model[LM][intra]; + int i, c; + opus_val32 prev[2] = {0, 0}; + opus_val16 coef; + opus_val16 beta; + opus_int32 budget; + opus_int32 tell; + + if (intra) + { + coef = 0; + beta = beta_intra; + } else { + beta = beta_coef[LM]; + coef = pred_coef[LM]; + } + + budget = dec->storage*8; + + /* Decode at a fixed coarse resolution */ + for (i=start;i=15) + { + int pi; + pi = 2*IMIN(i,20); + qi = ec_laplace_decode(dec, + prob_model[pi]<<7, prob_model[pi+1]<<6); + } + else if(budget-tell>=2) + { + qi = ec_dec_icdf(dec, small_energy_icdf, 2); + qi = (qi>>1)^-(qi&1); + } + else if(budget-tell>=1) + { + qi = -ec_dec_bit_logp(dec, 1); + } + else + qi = -1; + q = (opus_val32)SHL32(EXTEND32(qi),DB_SHIFT); + + oldEBands[i+c*m->nbEBands] = MAX16(-QCONST16(9.f,DB_SHIFT), oldEBands[i+c*m->nbEBands]); + tmp = PSHR32(MULT16_16(coef,oldEBands[i+c*m->nbEBands]),8) + prev[c] + SHL32(q,7); +#ifdef FIXED_POINT + tmp = MAX32(-QCONST32(28.f, DB_SHIFT+7), tmp); +#endif + oldEBands[i+c*m->nbEBands] = PSHR32(tmp, 7); + prev[c] = prev[c] + SHL32(q,7) - MULT16_16(beta,PSHR32(q,8)); + } while (++c < C); + } +} + +void unquant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, ec_dec *dec, int C) +{ + int i, c; + /* Decode finer resolution */ + for (i=start;inbEBands] += offset; + } while (++c < C); + } +} + +void unquant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, int *fine_priority, int bits_left, ec_dec *dec, int C) +{ + int i, prio, c; + + /* Use up the remaining bits */ + for (prio=0;prio<2;prio++) + { + for (i=start;i=C ;i++) + { + if (fine_quant[i] >= MAX_FINE_BITS || fine_priority[i]!=prio) + continue; + c=0; + do { + int q2; + opus_val16 offset; + q2 = ec_dec_bits(dec, 1); +#ifdef FIXED_POINT + offset = SHR16(SHL16(q2,DB_SHIFT)-QCONST16(.5f,DB_SHIFT),fine_quant[i]+1); +#else + offset = (q2-.5f)*(1<<(14-fine_quant[i]-1))*(1.f/16384); +#endif + oldEBands[i+c*m->nbEBands] += offset; + bits_left--; + } while (++c < C); + } + } +} + +void amp2Log2(const CELTMode *m, int effEnd, int end, + celt_ener *bandE, opus_val16 *bandLogE, int C) +{ + int c, i; + c=0; + do { + for (i=0;inbEBands] = + celt_log2(bandE[i+c*m->nbEBands]) + - SHL16((opus_val16)eMeans[i],6); +#ifdef FIXED_POINT + /* Compensate for bandE[] being Q12 but celt_log2() taking a Q14 input. */ + bandLogE[i+c*m->nbEBands] += QCONST16(2.f, DB_SHIFT); +#endif + } + for (i=effEnd;inbEBands+i] = -QCONST16(14.f,DB_SHIFT); + } while (++c < C); +} diff --git a/port/third_party/opus/celt/quant_bands.h b/port/third_party/opus/celt/quant_bands.h new file mode 100644 index 00000000..0490bca4 --- /dev/null +++ b/port/third_party/opus/celt/quant_bands.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef QUANT_BANDS +#define QUANT_BANDS + +#include "arch.h" +#include "modes.h" +#include "entenc.h" +#include "entdec.h" +#include "mathops.h" + +#ifdef FIXED_POINT +extern const signed char eMeans[25]; +#else +extern const opus_val16 eMeans[25]; +#endif + +void amp2Log2(const CELTMode *m, int effEnd, int end, + celt_ener *bandE, opus_val16 *bandLogE, int C); + +void log2Amp(const CELTMode *m, int start, int end, + celt_ener *eBands, const opus_val16 *oldEBands, int C); + +void quant_coarse_energy(const CELTMode *m, int start, int end, int effEnd, + const opus_val16 *eBands, opus_val16 *oldEBands, opus_uint32 budget, + opus_val16 *error, ec_enc *enc, int C, int LM, + int nbAvailableBytes, int force_intra, opus_val32 *delayedIntra, + int two_pass, int loss_rate, int lfe); + +void quant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, opus_val16 *error, int *fine_quant, ec_enc *enc, int C); + +void quant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *oldEBands, opus_val16 *error, int *fine_quant, int *fine_priority, int bits_left, ec_enc *enc, int C); + +void unquant_coarse_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int intra, ec_dec *dec, int C, int LM); + +void unquant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, ec_dec *dec, int C); + +void unquant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, int *fine_priority, int bits_left, ec_dec *dec, int C); + +#endif /* QUANT_BANDS */ diff --git a/port/third_party/opus/celt/rate.c b/port/third_party/opus/celt/rate.c new file mode 100644 index 00000000..7f7ad3fa --- /dev/null +++ b/port/third_party/opus/celt/rate.c @@ -0,0 +1,646 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "modes.h" +#include "cwrs.h" +#include "arch.h" +#include "os_support.h" + +#include "entcode.h" +#include "rate.h" + +static const unsigned char LOG2_FRAC_TABLE[24]={ + 0, + 8,13, + 16,19,21,23, + 24,26,27,28,29,30,31,32, + 32,33,34,34,35,36,36,37,37 +}; + +#ifdef CUSTOM_MODES + +/*Determines if V(N,K) fits in a 32-bit unsigned integer. + N and K are themselves limited to 15 bits.*/ +static int fits_in32(int _n, int _k) +{ + static const opus_int16 maxN[15] = { + 32767, 32767, 32767, 1476, 283, 109, 60, 40, + 29, 24, 20, 18, 16, 14, 13}; + static const opus_int16 maxK[15] = { + 32767, 32767, 32767, 32767, 1172, 238, 95, 53, + 36, 27, 22, 18, 16, 15, 13}; + if (_n>=14) + { + if (_k>=14) + return 0; + else + return _n <= maxN[_k]; + } else { + return _k <= maxK[_n]; + } +} + +void compute_pulse_cache(CELTMode *m, int LM) +{ + int C; + int i; + int j; + int curr=0; + int nbEntries=0; + int entryN[100], entryK[100], entryI[100]; + const opus_int16 *eBands = m->eBands; + PulseCache *cache = &m->cache; + opus_int16 *cindex; + unsigned char *bits; + unsigned char *cap; + + cindex = (opus_int16 *)opus_alloc(sizeof(cache->index[0])*m->nbEBands*(LM+2)); + cache->index = cindex; + + /* Scan for all unique band sizes */ + for (i=0;i<=LM+1;i++) + { + for (j=0;jnbEBands;j++) + { + int k; + int N = (eBands[j+1]-eBands[j])<>1; + cindex[i*m->nbEBands+j] = -1; + /* Find other bands that have the same size */ + for (k=0;k<=i;k++) + { + int n; + for (n=0;nnbEBands && (k!=i || n>1) + { + cindex[i*m->nbEBands+j] = cindex[k*m->nbEBands+n]; + break; + } + } + } + if (cache->index[i*m->nbEBands+j] == -1 && N!=0) + { + int K; + entryN[nbEntries] = N; + K = 0; + while (fits_in32(N,get_pulses(K+1)) && KnbEBands+j] = curr; + entryI[nbEntries] = curr; + + curr += K+1; + nbEntries++; + } + } + } + bits = (unsigned char *)opus_alloc(sizeof(unsigned char)*curr); + cache->bits = bits; + cache->size = curr; + /* Compute the cache for all unique sizes */ + for (i=0;icaps = cap = (unsigned char *)opus_alloc(sizeof(cache->caps[0])*(LM+1)*2*m->nbEBands); + for (i=0;i<=LM;i++) + { + for (C=1;C<=2;C++) + { + for (j=0;jnbEBands;j++) + { + int N0; + int max_bits; + N0 = m->eBands[j+1]-m->eBands[j]; + /* N=1 bands only have a sign bit and fine bits. */ + if (N0<1 are even, including custom modes.*/ + if (N0 > 2) + { + N0>>=1; + LM0--; + } + /* N0=1 bands can't be split down to N<2. */ + else if (N0 <= 1) + { + LM0=IMIN(i,1); + N0<<=LM0; + } + /* Compute the cost for the lowest-level PVQ of a fully split + band. */ + pcache = bits + cindex[(LM0+1)*m->nbEBands+j]; + max_bits = pcache[pcache[0]]+1; + /* Add in the cost of coding regular splits. */ + N = N0; + for(k=0;klogN[j]+((LM0+k)<>1)-QTHETA_OFFSET; + /* The number of qtheta bits we'll allocate if the remainder + is to be max_bits. + The average measured cost for theta is 0.89701 times qb, + approximated here as 459/512. */ + num=459*(opus_int32)((2*N-1)*offset+max_bits); + den=((opus_int32)(2*N-1)<<9)-459; + qb = IMIN((num+(den>>1))/den, 57); + celt_assert(qb >= 0); + max_bits += qb; + N <<= 1; + } + /* Add in the cost of a stereo split, if necessary. */ + if (C==2) + { + max_bits <<= 1; + offset = ((m->logN[j]+(i<>1)-(N==2?QTHETA_OFFSET_TWOPHASE:QTHETA_OFFSET); + ndof = 2*N-1-(N==2); + /* The average measured cost for theta with the step PDF is + 0.95164 times qb, approximated here as 487/512. */ + num = (N==2?512:487)*(opus_int32)(max_bits+ndof*offset); + den = ((opus_int32)ndof<<9)-(N==2?512:487); + qb = IMIN((num+(den>>1))/den, (N==2?64:61)); + celt_assert(qb >= 0); + max_bits += qb; + } + /* Add the fine bits we'll use. */ + /* Compensate for the extra DoF in stereo */ + ndof = C*N + ((C==2 && N>2) ? 1 : 0); + /* Offset the number of fine bits by log2(N)/2 + FINE_OFFSET + compared to their "fair share" of total/N */ + offset = ((m->logN[j] + (i<>1)-FINE_OFFSET; + /* N=2 is the only point that doesn't match the curve */ + if (N==2) + offset += 1<>2; + /* The number of fine bits we'll allocate if the remainder is + to be max_bits. */ + num = max_bits+ndof*offset; + den = (ndof-1)<>1))/den, MAX_FINE_BITS); + celt_assert(qb >= 0); + max_bits += C*qb<eBands[j+1]-m->eBands[j])<= 0); + celt_assert(max_bits < 256); + *cap++ = (unsigned char)max_bits; + } + } + } +} + +#endif /* CUSTOM_MODES */ + +#define ALLOC_STEPS 6 + +static OPUS_INLINE int interp_bits2pulses(const CELTMode *m, int start, int end, int skip_start, + const int *bits1, const int *bits2, const int *thresh, const int *cap, opus_int32 total, opus_int32 *_balance, + int skip_rsv, int *intensity, int intensity_rsv, int *dual_stereo, int dual_stereo_rsv, int *bits, + int *ebits, int *fine_priority, int C, int LM, ec_ctx *ec, int encode, int prev, int signalBandwidth) +{ + opus_int32 psum; + int lo, hi; + int i, j; + int logM; + int stereo; + int codedBands=-1; + int alloc_floor; + opus_int32 left, percoeff; + int done; + opus_int32 balance; + SAVE_STACK; + + alloc_floor = C<1; + + logM = LM<>1; + psum = 0; + done = 0; + for (j=end;j-->start;) + { + int tmp = bits1[j] + (mid*(opus_int32)bits2[j]>>ALLOC_STEPS); + if (tmp >= thresh[j] || done) + { + done = 1; + /* Don't allocate more than we can actually use */ + psum += IMIN(tmp, cap[j]); + } else { + if (tmp >= alloc_floor) + psum += alloc_floor; + } + } + if (psum > total) + hi = mid; + else + lo = mid; + } + psum = 0; + /*printf ("interp bisection gave %d\n", lo);*/ + done = 0; + for (j=end;j-->start;) + { + int tmp = bits1[j] + ((opus_int32)lo*bits2[j]>>ALLOC_STEPS); + if (tmp < thresh[j] && !done) + { + if (tmp >= alloc_floor) + tmp = alloc_floor; + else + tmp = 0; + } else + done = 1; + /* Don't allocate more than we can actually use */ + tmp = IMIN(tmp, cap[j]); + bits[j] = tmp; + psum += tmp; + } + + /* Decide which bands to skip, working backwards from the end. */ + for (codedBands=end;;codedBands--) + { + int band_width; + int band_bits; + int rem; + j = codedBands-1; + /* Never skip the first band, nor a band that has been boosted by + dynalloc. + In the first case, we'd be coding a bit to signal we're going to waste + all the other bits. + In the second case, we'd be coding a bit to redistribute all the bits + we just signaled should be cocentrated in this band. */ + if (j<=skip_start) + { + /* Give the bit we reserved to end skipping back. */ + total += skip_rsv; + break; + } + /*Figure out how many left-over bits we would be adding to this band. + This can include bits we've stolen back from higher, skipped bands.*/ + left = total-psum; + percoeff = celt_udiv(left, m->eBands[codedBands]-m->eBands[start]); + left -= (m->eBands[codedBands]-m->eBands[start])*percoeff; + rem = IMAX(left-(m->eBands[j]-m->eBands[start]),0); + band_width = m->eBands[codedBands]-m->eBands[j]; + band_bits = (int)(bits[j] + percoeff*band_width + rem); + /*Only code a skip decision if we're above the threshold for this band. + Otherwise it is force-skipped. + This ensures that we have enough bits to code the skip flag.*/ + if (band_bits >= IMAX(thresh[j], alloc_floor+(1< 17) + depth_threshold = j (depth_threshold*band_width<>4 && j<=signalBandwidth)) +#endif + { + ec_enc_bit_logp(ec, 1, 1); + break; + } + ec_enc_bit_logp(ec, 0, 1); + } else if (ec_dec_bit_logp(ec, 1)) { + break; + } + /*We used a bit to skip this band.*/ + psum += 1< 0) + intensity_rsv = LOG2_FRAC_TABLE[j-start]; + psum += intensity_rsv; + if (band_bits >= alloc_floor) + { + /*If we have enough for a fine energy bit per channel, use it.*/ + psum += alloc_floor; + bits[j] = alloc_floor; + } else { + /*Otherwise this band gets nothing at all.*/ + bits[j] = 0; + } + } + + celt_assert(codedBands > start); + /* Code the intensity and dual stereo parameters. */ + if (intensity_rsv > 0) + { + if (encode) + { + *intensity = IMIN(*intensity, codedBands); + ec_enc_uint(ec, *intensity-start, codedBands+1-start); + } + else + *intensity = start+ec_dec_uint(ec, codedBands+1-start); + } + else + *intensity = 0; + if (*intensity <= start) + { + total += dual_stereo_rsv; + dual_stereo_rsv = 0; + } + if (dual_stereo_rsv > 0) + { + if (encode) + ec_enc_bit_logp(ec, *dual_stereo, 1); + else + *dual_stereo = ec_dec_bit_logp(ec, 1); + } + else + *dual_stereo = 0; + + /* Allocate the remaining bits */ + left = total-psum; + percoeff = celt_udiv(left, m->eBands[codedBands]-m->eBands[start]); + left -= (m->eBands[codedBands]-m->eBands[start])*percoeff; + for (j=start;jeBands[j+1]-m->eBands[j])); + for (j=start;jeBands[j+1]-m->eBands[j]); + bits[j] += tmp; + left -= tmp; + } + /*for (j=0;j= 0); + N0 = m->eBands[j+1]-m->eBands[j]; + N=N0<1) + { + excess = MAX32(bit-cap[j],0); + bits[j] = bit-excess; + + /* Compensate for the extra DoF in stereo */ + den=(C*N+ ((C==2 && N>2 && !*dual_stereo && j<*intensity) ? 1 : 0)); + + NClogN = den*(m->logN[j] + logM); + + /* Offset for the number of fine bits by log2(N)/2 + FINE_OFFSET + compared to their "fair share" of total/N */ + offset = (NClogN>>1)-den*FINE_OFFSET; + + /* N=2 is the only point that doesn't match the curve */ + if (N==2) + offset += den<>2; + + /* Changing the offset for allocating the second and third + fine energy bit */ + if (bits[j] + offset < den*2<>2; + else if (bits[j] + offset < den*3<>3; + + /* Divide with rounding */ + ebits[j] = IMAX(0, (bits[j] + offset + (den<<(BITRES-1)))); + ebits[j] = celt_udiv(ebits[j], den)>>BITRES; + + /* Make sure not to bust */ + if (C*ebits[j] > (bits[j]>>BITRES)) + ebits[j] = bits[j] >> stereo >> BITRES; + + /* More than that is useless because that's about as far as PVQ can go */ + ebits[j] = IMIN(ebits[j], MAX_FINE_BITS); + + /* If we rounded down or capped this band, make it a candidate for the + final fine energy pass */ + fine_priority[j] = ebits[j]*(den<= bits[j]+offset; + + /* Remove the allocated fine bits; the rest are assigned to PVQ */ + bits[j] -= C*ebits[j]< 0) + { + int extra_fine; + int extra_bits; + extra_fine = IMIN(excess>>(stereo+BITRES),MAX_FINE_BITS-ebits[j]); + ebits[j] += extra_fine; + extra_bits = extra_fine*C<= excess-balance; + excess -= extra_bits; + } + balance = excess; + + celt_assert(bits[j] >= 0); + celt_assert(ebits[j] >= 0); + } + /* Save any remaining bits over the cap for the rebalancing in + quant_all_bands(). */ + *_balance = balance; + + /* The skipped bands use all their bits for fine energy. */ + for (;j> stereo >> BITRES; + celt_assert(C*ebits[j]<nbEBands; + skip_start = start; + /* Reserve a bit to signal the end of manually skipped bands. */ + skip_rsv = total >= 1<total) + intensity_rsv = 0; + else + { + total -= intensity_rsv; + dual_stereo_rsv = total>=1<eBands[j+1]-m->eBands[j])<>4); + /* Tilt of the allocation curve */ + trim_offset[j] = C*(m->eBands[j+1]-m->eBands[j])*(alloc_trim-5-LM)*(end-j-1) + *(1<<(LM+BITRES))>>6; + /* Giving less resolution to single-coefficient bands because they get + more benefit from having one coarse value per coefficient*/ + if ((m->eBands[j+1]-m->eBands[j])<nbAllocVectors - 1; + do + { + int done = 0; + int psum = 0; + int mid = (lo+hi) >> 1; + for (j=end;j-->start;) + { + int bitsj; + int N = m->eBands[j+1]-m->eBands[j]; + bitsj = C*N*m->allocVectors[mid*len+j]<>2; + if (bitsj > 0) + bitsj = IMAX(0, bitsj + trim_offset[j]); + bitsj += offsets[j]; + if (bitsj >= thresh[j] || done) + { + done = 1; + /* Don't allocate more than we can actually use */ + psum += IMIN(bitsj, cap[j]); + } else { + if (bitsj >= C< total) + hi = mid - 1; + else + lo = mid + 1; + /*printf ("lo = %d, hi = %d\n", lo, hi);*/ + } + while (lo <= hi); + hi = lo--; + /*printf ("interp between %d and %d\n", lo, hi);*/ + for (j=start;jeBands[j+1]-m->eBands[j]; + bits1j = C*N*m->allocVectors[lo*len+j]<>2; + bits2j = hi>=m->nbAllocVectors ? + cap[j] : C*N*m->allocVectors[hi*len+j]<>2; + if (bits1j > 0) + bits1j = IMAX(0, bits1j + trim_offset[j]); + if (bits2j > 0) + bits2j = IMAX(0, bits2j + trim_offset[j]); + if (lo > 0) + bits1j += offsets[j]; + bits2j += offsets[j]; + if (offsets[j]>0) + skip_start = j; + bits2j = IMAX(0,bits2j-bits1j); + bits1[j] = bits1j; + bits2[j] = bits2j; + } + codedBands = interp_bits2pulses(m, start, end, skip_start, bits1, bits2, thresh, cap, + total, balance, skip_rsv, intensity, intensity_rsv, dual_stereo, dual_stereo_rsv, + pulses, ebits, fine_priority, C, LM, ec, encode, prev, signalBandwidth); + RESTORE_STACK; + return codedBands; +} + diff --git a/port/third_party/opus/celt/rate.h b/port/third_party/opus/celt/rate.h new file mode 100644 index 00000000..fad5e412 --- /dev/null +++ b/port/third_party/opus/celt/rate.h @@ -0,0 +1,101 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef RATE_H +#define RATE_H + +#define MAX_PSEUDO 40 +#define LOG_MAX_PSEUDO 6 + +#define CELT_MAX_PULSES 128 + +#define MAX_FINE_BITS 8 + +#define FINE_OFFSET 21 +#define QTHETA_OFFSET 4 +#define QTHETA_OFFSET_TWOPHASE 16 + +#include "cwrs.h" +#include "modes.h" + +void compute_pulse_cache(CELTMode *m, int LM); + +static OPUS_INLINE int get_pulses(int i) +{ + return i<8 ? i : (8 + (i&7)) << ((i>>3)-1); +} + +static OPUS_INLINE int bits2pulses(const CELTMode *m, int band, int LM, int bits) +{ + int i; + int lo, hi; + const unsigned char *cache; + + LM++; + cache = m->cache.bits + m->cache.index[LM*m->nbEBands+band]; + + lo = 0; + hi = cache[0]; + bits--; + for (i=0;i>1; + /* OPT: Make sure this is implemented with a conditional move */ + if ((int)cache[mid] >= bits) + hi = mid; + else + lo = mid; + } + if (bits- (lo == 0 ? -1 : (int)cache[lo]) <= (int)cache[hi]-bits) + return lo; + else + return hi; +} + +static OPUS_INLINE int pulses2bits(const CELTMode *m, int band, int LM, int pulses) +{ + const unsigned char *cache; + + LM++; + cache = m->cache.bits + m->cache.index[LM*m->nbEBands+band]; + return pulses == 0 ? 0 : cache[pulses]+1; +} + +/** Compute the pulse allocation, i.e. how many pulses will go in each + * band. + @param m mode + @param offsets Requested increase or decrease in the number of bits for + each band + @param total Number of bands + @param pulses Number of pulses per band (returned) + @return Total number of bits allocated +*/ +int clt_compute_allocation(const CELTMode *m, int start, int end, const int *offsets, const int *cap, int alloc_trim, int *intensity, int *dual_stereo, + opus_int32 total, opus_int32 *balance, int *pulses, int *ebits, int *fine_priority, int C, int LM, ec_ctx *ec, int encode, int prev, int signalBandwidth); + +#endif diff --git a/port/third_party/opus/celt/stack_alloc.h b/port/third_party/opus/celt/stack_alloc.h new file mode 100644 index 00000000..e2739bdf --- /dev/null +++ b/port/third_party/opus/celt/stack_alloc.h @@ -0,0 +1,184 @@ +/* Copyright (C) 2002-2003 Jean-Marc Valin + Copyright (C) 2007-2009 Xiph.Org Foundation */ +/** + @file stack_alloc.h + @brief Temporary memory allocation on stack +*/ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifndef STACK_ALLOC_H +#define STACK_ALLOC_H + +#include "opus_types.h" +#include "opus_defines.h" + +#if (!defined (VAR_ARRAYS) && !defined (USE_ALLOCA) && !defined (NONTHREADSAFE_PSEUDOSTACK)) +#error "Opus requires one of VAR_ARRAYS, USE_ALLOCA, or NONTHREADSAFE_PSEUDOSTACK be defined to select the temporary allocation mode." +#endif + +#ifdef USE_ALLOCA +# ifdef _WIN32 +# include +# else +# ifdef HAVE_ALLOCA_H +# include +# else +# include +# endif +# endif +#endif + +/** + * @def ALIGN(stack, size) + * + * Aligns the stack to a 'size' boundary + * + * @param stack Stack + * @param size New size boundary + */ + +/** + * @def PUSH(stack, size, type) + * + * Allocates 'size' elements of type 'type' on the stack + * + * @param stack Stack + * @param size Number of elements + * @param type Type of element + */ + +/** + * @def VARDECL(var) + * + * Declare variable on stack + * + * @param var Variable to declare + */ + +/** + * @def ALLOC(var, size, type) + * + * Allocate 'size' elements of 'type' on stack + * + * @param var Name of variable to allocate + * @param size Number of elements + * @param type Type of element + */ + +#if defined(VAR_ARRAYS) + +#define VARDECL(type, var) +#define ALLOC(var, size, type) type var[size] +#define SAVE_STACK +#define RESTORE_STACK +#define ALLOC_STACK +/* C99 does not allow VLAs of size zero */ +#define ALLOC_NONE 1 + +#elif defined(USE_ALLOCA) + +#define VARDECL(type, var) type *var + +# ifdef _WIN32 +# define ALLOC(var, size, type) var = ((type*)_alloca(sizeof(type)*(size))) +# else +# define ALLOC(var, size, type) var = ((type*)alloca(sizeof(type)*(size))) +# endif + +#define SAVE_STACK +#define RESTORE_STACK +#define ALLOC_STACK +#define ALLOC_NONE 0 + +#else + +#ifdef CELT_C +char *scratch_ptr=0; +char *global_stack=0; +#else +extern char *global_stack; +extern char *scratch_ptr; +#endif /* CELT_C */ + +#ifdef ENABLE_VALGRIND + +#include + +#ifdef CELT_C +char *global_stack_top=0; +#else +extern char *global_stack_top; +#endif /* CELT_C */ + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) +#define PUSH(stack, size, type) (VALGRIND_MAKE_MEM_NOACCESS(stack, global_stack_top-stack),ALIGN((stack),sizeof(type)/sizeof(char)),VALGRIND_MAKE_MEM_UNDEFINED(stack, ((size)*sizeof(type)/sizeof(char))),(stack)+=(2*(size)*sizeof(type)/sizeof(char)),(type*)((stack)-(2*(size)*sizeof(type)/sizeof(char)))) +#define RESTORE_STACK ((global_stack = _saved_stack),VALGRIND_MAKE_MEM_NOACCESS(global_stack, global_stack_top-global_stack)) +#define ALLOC_STACK char *_saved_stack; ((global_stack = (global_stack==0) ? ((global_stack_top=opus_alloc_scratch(GLOBAL_STACK_SIZE*2)+(GLOBAL_STACK_SIZE*2))-(GLOBAL_STACK_SIZE*2)) : global_stack),VALGRIND_MAKE_MEM_NOACCESS(global_stack, global_stack_top-global_stack)); _saved_stack = global_stack; + +#else + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) +#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)/(sizeof(char))),(stack)+=(size)*(sizeof(type)/(sizeof(char))),(type*)((stack)-(size)*(sizeof(type)/(sizeof(char))))) +#if 0 /* Set this to 1 to instrument pseudostack usage */ +#define RESTORE_STACK (printf("%ld %s:%d\n", global_stack-scratch_ptr, __FILE__, __LINE__),global_stack = _saved_stack) +#else +#define RESTORE_STACK (global_stack = _saved_stack) +#endif +#define ALLOC_STACK char *_saved_stack; (global_stack = (global_stack==0) ? (scratch_ptr=opus_alloc_scratch(GLOBAL_STACK_SIZE)) : global_stack); _saved_stack = global_stack; + +#endif /* ENABLE_VALGRIND */ + +#include "os_support.h" +#define VARDECL(type, var) type *var +#define ALLOC(var, size, type) var = PUSH(global_stack, size, type) +#define SAVE_STACK char *_saved_stack = global_stack; +#define ALLOC_NONE 0 + +#endif /* VAR_ARRAYS */ + + +#ifdef ENABLE_VALGRIND + +#include +#define OPUS_CHECK_ARRAY(ptr, len) VALGRIND_CHECK_MEM_IS_DEFINED(ptr, len*sizeof(*ptr)) +#define OPUS_CHECK_VALUE(value) VALGRIND_CHECK_VALUE_IS_DEFINED(value) +#define OPUS_CHECK_ARRAY_COND(ptr, len) VALGRIND_CHECK_MEM_IS_DEFINED(ptr, len*sizeof(*ptr)) +#define OPUS_CHECK_VALUE_COND(value) VALGRIND_CHECK_VALUE_IS_DEFINED(value) +#define OPUS_PRINT_INT(value) do {fprintf(stderr, #value " = %d at %s:%d\n", value, __FILE__, __LINE__);}while(0) +#define OPUS_FPRINTF fprintf + +#else + +static OPUS_INLINE int _opus_false(void) {return 0;} +#define OPUS_CHECK_ARRAY(ptr, len) _opus_false() +#define OPUS_CHECK_VALUE(value) _opus_false() +#define OPUS_PRINT_INT(value) do{}while(0) +#define OPUS_FPRINTF (void) + +#endif + + +#endif /* STACK_ALLOC_H */ diff --git a/port/third_party/opus/celt/static_modes_fixed.h b/port/third_party/opus/celt/static_modes_fixed.h new file mode 100644 index 00000000..8717d626 --- /dev/null +++ b/port/third_party/opus/celt/static_modes_fixed.h @@ -0,0 +1,892 @@ +/* The contents of this file was automatically generated by dump_modes.c + with arguments: 48000 960 + It contains static definitions for some pre-defined modes. */ +#include "modes.h" +#include "rate.h" + +#ifdef HAVE_ARM_NE10 +#define OVERRIDE_FFT 1 +#include "static_modes_fixed_arm_ne10.h" +#endif + +#ifndef DEF_WINDOW120 +#define DEF_WINDOW120 +static const opus_val16 window120[120] = { +2, 20, 55, 108, 178, +266, 372, 494, 635, 792, +966, 1157, 1365, 1590, 1831, +2089, 2362, 2651, 2956, 3276, +3611, 3961, 4325, 4703, 5094, +5499, 5916, 6346, 6788, 7241, +7705, 8179, 8663, 9156, 9657, +10167, 10684, 11207, 11736, 12271, +12810, 13353, 13899, 14447, 14997, +15547, 16098, 16648, 17197, 17744, +18287, 18827, 19363, 19893, 20418, +20936, 21447, 21950, 22445, 22931, +23407, 23874, 24330, 24774, 25208, +25629, 26039, 26435, 26819, 27190, +27548, 27893, 28224, 28541, 28845, +29135, 29411, 29674, 29924, 30160, +30384, 30594, 30792, 30977, 31151, +31313, 31463, 31602, 31731, 31849, +31958, 32057, 32148, 32229, 32303, +32370, 32429, 32481, 32528, 32568, +32604, 32634, 32661, 32683, 32701, +32717, 32729, 32740, 32748, 32754, +32758, 32762, 32764, 32766, 32767, +32767, 32767, 32767, 32767, 32767, +}; +#endif + +#ifndef DEF_LOGN400 +#define DEF_LOGN400 +static const opus_int16 logN400[21] = { +0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 16, 16, 16, 21, 21, 24, 29, 34, 36, }; +#endif + +#ifndef DEF_PULSE_CACHE50 +#define DEF_PULSE_CACHE50 +static const opus_int16 cache_index50[105] = { +-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 41, 41, 41, +82, 82, 123, 164, 200, 222, 0, 0, 0, 0, 0, 0, 0, 0, 41, +41, 41, 41, 123, 123, 123, 164, 164, 240, 266, 283, 295, 41, 41, 41, +41, 41, 41, 41, 41, 123, 123, 123, 123, 240, 240, 240, 266, 266, 305, +318, 328, 336, 123, 123, 123, 123, 123, 123, 123, 123, 240, 240, 240, 240, +305, 305, 305, 318, 318, 343, 351, 358, 364, 240, 240, 240, 240, 240, 240, +240, 240, 305, 305, 305, 305, 343, 343, 343, 351, 351, 370, 376, 382, 387, +}; +static const unsigned char cache_bits50[392] = { +40, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 40, 15, 23, 28, +31, 34, 36, 38, 39, 41, 42, 43, 44, 45, 46, 47, 47, 49, 50, +51, 52, 53, 54, 55, 55, 57, 58, 59, 60, 61, 62, 63, 63, 65, +66, 67, 68, 69, 70, 71, 71, 40, 20, 33, 41, 48, 53, 57, 61, +64, 66, 69, 71, 73, 75, 76, 78, 80, 82, 85, 87, 89, 91, 92, +94, 96, 98, 101, 103, 105, 107, 108, 110, 112, 114, 117, 119, 121, 123, +124, 126, 128, 40, 23, 39, 51, 60, 67, 73, 79, 83, 87, 91, 94, +97, 100, 102, 105, 107, 111, 115, 118, 121, 124, 126, 129, 131, 135, 139, +142, 145, 148, 150, 153, 155, 159, 163, 166, 169, 172, 174, 177, 179, 35, +28, 49, 65, 78, 89, 99, 107, 114, 120, 126, 132, 136, 141, 145, 149, +153, 159, 165, 171, 176, 180, 185, 189, 192, 199, 205, 211, 216, 220, 225, +229, 232, 239, 245, 251, 21, 33, 58, 79, 97, 112, 125, 137, 148, 157, +166, 174, 182, 189, 195, 201, 207, 217, 227, 235, 243, 251, 17, 35, 63, +86, 106, 123, 139, 152, 165, 177, 187, 197, 206, 214, 222, 230, 237, 250, +25, 31, 55, 75, 91, 105, 117, 128, 138, 146, 154, 161, 168, 174, 180, +185, 190, 200, 208, 215, 222, 229, 235, 240, 245, 255, 16, 36, 65, 89, +110, 128, 144, 159, 173, 185, 196, 207, 217, 226, 234, 242, 250, 11, 41, +74, 103, 128, 151, 172, 191, 209, 225, 241, 255, 9, 43, 79, 110, 138, +163, 186, 207, 227, 246, 12, 39, 71, 99, 123, 144, 164, 182, 198, 214, +228, 241, 253, 9, 44, 81, 113, 142, 168, 192, 214, 235, 255, 7, 49, +90, 127, 160, 191, 220, 247, 6, 51, 95, 134, 170, 203, 234, 7, 47, +87, 123, 155, 184, 212, 237, 6, 52, 97, 137, 174, 208, 240, 5, 57, +106, 151, 192, 231, 5, 59, 111, 158, 202, 243, 5, 55, 103, 147, 187, +224, 5, 60, 113, 161, 206, 248, 4, 65, 122, 175, 224, 4, 67, 127, +182, 234, }; +static const unsigned char cache_caps50[168] = { +224, 224, 224, 224, 224, 224, 224, 224, 160, 160, 160, 160, 185, 185, 185, +178, 178, 168, 134, 61, 37, 224, 224, 224, 224, 224, 224, 224, 224, 240, +240, 240, 240, 207, 207, 207, 198, 198, 183, 144, 66, 40, 160, 160, 160, +160, 160, 160, 160, 160, 185, 185, 185, 185, 193, 193, 193, 183, 183, 172, +138, 64, 38, 240, 240, 240, 240, 240, 240, 240, 240, 207, 207, 207, 207, +204, 204, 204, 193, 193, 180, 143, 66, 40, 185, 185, 185, 185, 185, 185, +185, 185, 193, 193, 193, 193, 193, 193, 193, 183, 183, 172, 138, 65, 39, +207, 207, 207, 207, 207, 207, 207, 207, 204, 204, 204, 204, 201, 201, 201, +188, 188, 176, 141, 66, 40, 193, 193, 193, 193, 193, 193, 193, 193, 193, +193, 193, 193, 194, 194, 194, 184, 184, 173, 139, 65, 39, 204, 204, 204, +204, 204, 204, 204, 204, 201, 201, 201, 201, 198, 198, 198, 187, 187, 175, +140, 66, 40, }; +#endif + +#ifndef FFT_TWIDDLES48000_960 +#define FFT_TWIDDLES48000_960 +static const kiss_twiddle_cpx fft_twiddles48000_960[480] = { +{32767, 0}, {32766, -429}, +{32757, -858}, {32743, -1287}, +{32724, -1715}, {32698, -2143}, +{32667, -2570}, {32631, -2998}, +{32588, -3425}, {32541, -3851}, +{32488, -4277}, {32429, -4701}, +{32364, -5125}, {32295, -5548}, +{32219, -5971}, {32138, -6393}, +{32051, -6813}, {31960, -7231}, +{31863, -7650}, {31760, -8067}, +{31652, -8481}, {31539, -8895}, +{31419, -9306}, {31294, -9716}, +{31165, -10126}, {31030, -10532}, +{30889, -10937}, {30743, -11340}, +{30592, -11741}, {30436, -12141}, +{30274, -12540}, {30107, -12935}, +{29936, -13328}, {29758, -13718}, +{29577, -14107}, {29390, -14493}, +{29197, -14875}, {29000, -15257}, +{28797, -15635}, {28590, -16010}, +{28379, -16384}, {28162, -16753}, +{27940, -17119}, {27714, -17484}, +{27482, -17845}, {27246, -18205}, +{27006, -18560}, {26760, -18911}, +{26510, -19260}, {26257, -19606}, +{25997, -19947}, {25734, -20286}, +{25466, -20621}, {25194, -20952}, +{24918, -21281}, {24637, -21605}, +{24353, -21926}, {24063, -22242}, +{23770, -22555}, {23473, -22865}, +{23171, -23171}, {22866, -23472}, +{22557, -23769}, {22244, -24063}, +{21927, -24352}, {21606, -24636}, +{21282, -24917}, {20954, -25194}, +{20622, -25465}, {20288, -25733}, +{19949, -25997}, {19607, -26255}, +{19261, -26509}, {18914, -26760}, +{18561, -27004}, {18205, -27246}, +{17846, -27481}, {17485, -27713}, +{17122, -27940}, {16755, -28162}, +{16385, -28378}, {16012, -28590}, +{15636, -28797}, {15258, -28999}, +{14878, -29197}, {14494, -29389}, +{14108, -29576}, {13720, -29757}, +{13329, -29934}, {12937, -30107}, +{12540, -30274}, {12142, -30435}, +{11744, -30592}, {11342, -30743}, +{10939, -30889}, {10534, -31030}, +{10127, -31164}, {9718, -31294}, +{9307, -31418}, {8895, -31537}, +{8482, -31652}, {8067, -31759}, +{7650, -31862}, {7233, -31960}, +{6815, -32051}, {6393, -32138}, +{5973, -32219}, {5549, -32294}, +{5127, -32364}, {4703, -32429}, +{4278, -32487}, {3852, -32541}, +{3426, -32588}, {2999, -32630}, +{2572, -32667}, {2144, -32698}, +{1716, -32724}, {1287, -32742}, +{860, -32757}, {430, -32766}, +{0, -32767}, {-429, -32766}, +{-858, -32757}, {-1287, -32743}, +{-1715, -32724}, {-2143, -32698}, +{-2570, -32667}, {-2998, -32631}, +{-3425, -32588}, {-3851, -32541}, +{-4277, -32488}, {-4701, -32429}, +{-5125, -32364}, {-5548, -32295}, +{-5971, -32219}, {-6393, -32138}, +{-6813, -32051}, {-7231, -31960}, +{-7650, -31863}, {-8067, -31760}, +{-8481, -31652}, {-8895, -31539}, +{-9306, -31419}, {-9716, -31294}, +{-10126, -31165}, {-10532, -31030}, +{-10937, -30889}, {-11340, -30743}, +{-11741, -30592}, {-12141, -30436}, +{-12540, -30274}, {-12935, -30107}, +{-13328, -29936}, {-13718, -29758}, +{-14107, -29577}, {-14493, -29390}, +{-14875, -29197}, {-15257, -29000}, +{-15635, -28797}, {-16010, -28590}, +{-16384, -28379}, {-16753, -28162}, +{-17119, -27940}, {-17484, -27714}, +{-17845, -27482}, {-18205, -27246}, +{-18560, -27006}, {-18911, -26760}, +{-19260, -26510}, {-19606, -26257}, +{-19947, -25997}, {-20286, -25734}, +{-20621, -25466}, {-20952, -25194}, +{-21281, -24918}, {-21605, -24637}, +{-21926, -24353}, {-22242, -24063}, +{-22555, -23770}, {-22865, -23473}, +{-23171, -23171}, {-23472, -22866}, +{-23769, -22557}, {-24063, -22244}, +{-24352, -21927}, {-24636, -21606}, +{-24917, -21282}, {-25194, -20954}, +{-25465, -20622}, {-25733, -20288}, +{-25997, -19949}, {-26255, -19607}, +{-26509, -19261}, {-26760, -18914}, +{-27004, -18561}, {-27246, -18205}, +{-27481, -17846}, {-27713, -17485}, +{-27940, -17122}, {-28162, -16755}, +{-28378, -16385}, {-28590, -16012}, +{-28797, -15636}, {-28999, -15258}, +{-29197, -14878}, {-29389, -14494}, +{-29576, -14108}, {-29757, -13720}, +{-29934, -13329}, {-30107, -12937}, +{-30274, -12540}, {-30435, -12142}, +{-30592, -11744}, {-30743, -11342}, +{-30889, -10939}, {-31030, -10534}, +{-31164, -10127}, {-31294, -9718}, +{-31418, -9307}, {-31537, -8895}, +{-31652, -8482}, {-31759, -8067}, +{-31862, -7650}, {-31960, -7233}, +{-32051, -6815}, {-32138, -6393}, +{-32219, -5973}, {-32294, -5549}, +{-32364, -5127}, {-32429, -4703}, +{-32487, -4278}, {-32541, -3852}, +{-32588, -3426}, {-32630, -2999}, +{-32667, -2572}, {-32698, -2144}, +{-32724, -1716}, {-32742, -1287}, +{-32757, -860}, {-32766, -430}, +{-32767, 0}, {-32766, 429}, +{-32757, 858}, {-32743, 1287}, +{-32724, 1715}, {-32698, 2143}, +{-32667, 2570}, {-32631, 2998}, +{-32588, 3425}, {-32541, 3851}, +{-32488, 4277}, {-32429, 4701}, +{-32364, 5125}, {-32295, 5548}, +{-32219, 5971}, {-32138, 6393}, +{-32051, 6813}, {-31960, 7231}, +{-31863, 7650}, {-31760, 8067}, +{-31652, 8481}, {-31539, 8895}, +{-31419, 9306}, {-31294, 9716}, +{-31165, 10126}, {-31030, 10532}, +{-30889, 10937}, {-30743, 11340}, +{-30592, 11741}, {-30436, 12141}, +{-30274, 12540}, {-30107, 12935}, +{-29936, 13328}, {-29758, 13718}, +{-29577, 14107}, {-29390, 14493}, +{-29197, 14875}, {-29000, 15257}, +{-28797, 15635}, {-28590, 16010}, +{-28379, 16384}, {-28162, 16753}, +{-27940, 17119}, {-27714, 17484}, +{-27482, 17845}, {-27246, 18205}, +{-27006, 18560}, {-26760, 18911}, +{-26510, 19260}, {-26257, 19606}, +{-25997, 19947}, {-25734, 20286}, +{-25466, 20621}, {-25194, 20952}, +{-24918, 21281}, {-24637, 21605}, +{-24353, 21926}, {-24063, 22242}, +{-23770, 22555}, {-23473, 22865}, +{-23171, 23171}, {-22866, 23472}, +{-22557, 23769}, {-22244, 24063}, +{-21927, 24352}, {-21606, 24636}, +{-21282, 24917}, {-20954, 25194}, +{-20622, 25465}, {-20288, 25733}, +{-19949, 25997}, {-19607, 26255}, +{-19261, 26509}, {-18914, 26760}, +{-18561, 27004}, {-18205, 27246}, +{-17846, 27481}, {-17485, 27713}, +{-17122, 27940}, {-16755, 28162}, +{-16385, 28378}, {-16012, 28590}, +{-15636, 28797}, {-15258, 28999}, +{-14878, 29197}, {-14494, 29389}, +{-14108, 29576}, {-13720, 29757}, +{-13329, 29934}, {-12937, 30107}, +{-12540, 30274}, {-12142, 30435}, +{-11744, 30592}, {-11342, 30743}, +{-10939, 30889}, {-10534, 31030}, +{-10127, 31164}, {-9718, 31294}, +{-9307, 31418}, {-8895, 31537}, +{-8482, 31652}, {-8067, 31759}, +{-7650, 31862}, {-7233, 31960}, +{-6815, 32051}, {-6393, 32138}, +{-5973, 32219}, {-5549, 32294}, +{-5127, 32364}, {-4703, 32429}, +{-4278, 32487}, {-3852, 32541}, +{-3426, 32588}, {-2999, 32630}, +{-2572, 32667}, {-2144, 32698}, +{-1716, 32724}, {-1287, 32742}, +{-860, 32757}, {-430, 32766}, +{0, 32767}, {429, 32766}, +{858, 32757}, {1287, 32743}, +{1715, 32724}, {2143, 32698}, +{2570, 32667}, {2998, 32631}, +{3425, 32588}, {3851, 32541}, +{4277, 32488}, {4701, 32429}, +{5125, 32364}, {5548, 32295}, +{5971, 32219}, {6393, 32138}, +{6813, 32051}, {7231, 31960}, +{7650, 31863}, {8067, 31760}, +{8481, 31652}, {8895, 31539}, +{9306, 31419}, {9716, 31294}, +{10126, 31165}, {10532, 31030}, +{10937, 30889}, {11340, 30743}, +{11741, 30592}, {12141, 30436}, +{12540, 30274}, {12935, 30107}, +{13328, 29936}, {13718, 29758}, +{14107, 29577}, {14493, 29390}, +{14875, 29197}, {15257, 29000}, +{15635, 28797}, {16010, 28590}, +{16384, 28379}, {16753, 28162}, +{17119, 27940}, {17484, 27714}, +{17845, 27482}, {18205, 27246}, +{18560, 27006}, {18911, 26760}, +{19260, 26510}, {19606, 26257}, +{19947, 25997}, {20286, 25734}, +{20621, 25466}, {20952, 25194}, +{21281, 24918}, {21605, 24637}, +{21926, 24353}, {22242, 24063}, +{22555, 23770}, {22865, 23473}, +{23171, 23171}, {23472, 22866}, +{23769, 22557}, {24063, 22244}, +{24352, 21927}, {24636, 21606}, +{24917, 21282}, {25194, 20954}, +{25465, 20622}, {25733, 20288}, +{25997, 19949}, {26255, 19607}, +{26509, 19261}, {26760, 18914}, +{27004, 18561}, {27246, 18205}, +{27481, 17846}, {27713, 17485}, +{27940, 17122}, {28162, 16755}, +{28378, 16385}, {28590, 16012}, +{28797, 15636}, {28999, 15258}, +{29197, 14878}, {29389, 14494}, +{29576, 14108}, {29757, 13720}, +{29934, 13329}, {30107, 12937}, +{30274, 12540}, {30435, 12142}, +{30592, 11744}, {30743, 11342}, +{30889, 10939}, {31030, 10534}, +{31164, 10127}, {31294, 9718}, +{31418, 9307}, {31537, 8895}, +{31652, 8482}, {31759, 8067}, +{31862, 7650}, {31960, 7233}, +{32051, 6815}, {32138, 6393}, +{32219, 5973}, {32294, 5549}, +{32364, 5127}, {32429, 4703}, +{32487, 4278}, {32541, 3852}, +{32588, 3426}, {32630, 2999}, +{32667, 2572}, {32698, 2144}, +{32724, 1716}, {32742, 1287}, +{32757, 860}, {32766, 430}, +}; +#ifndef FFT_BITREV480 +#define FFT_BITREV480 +static const opus_int16 fft_bitrev480[480] = { +0, 96, 192, 288, 384, 32, 128, 224, 320, 416, 64, 160, 256, 352, 448, +8, 104, 200, 296, 392, 40, 136, 232, 328, 424, 72, 168, 264, 360, 456, +16, 112, 208, 304, 400, 48, 144, 240, 336, 432, 80, 176, 272, 368, 464, +24, 120, 216, 312, 408, 56, 152, 248, 344, 440, 88, 184, 280, 376, 472, +4, 100, 196, 292, 388, 36, 132, 228, 324, 420, 68, 164, 260, 356, 452, +12, 108, 204, 300, 396, 44, 140, 236, 332, 428, 76, 172, 268, 364, 460, +20, 116, 212, 308, 404, 52, 148, 244, 340, 436, 84, 180, 276, 372, 468, +28, 124, 220, 316, 412, 60, 156, 252, 348, 444, 92, 188, 284, 380, 476, +1, 97, 193, 289, 385, 33, 129, 225, 321, 417, 65, 161, 257, 353, 449, +9, 105, 201, 297, 393, 41, 137, 233, 329, 425, 73, 169, 265, 361, 457, +17, 113, 209, 305, 401, 49, 145, 241, 337, 433, 81, 177, 273, 369, 465, +25, 121, 217, 313, 409, 57, 153, 249, 345, 441, 89, 185, 281, 377, 473, +5, 101, 197, 293, 389, 37, 133, 229, 325, 421, 69, 165, 261, 357, 453, +13, 109, 205, 301, 397, 45, 141, 237, 333, 429, 77, 173, 269, 365, 461, +21, 117, 213, 309, 405, 53, 149, 245, 341, 437, 85, 181, 277, 373, 469, +29, 125, 221, 317, 413, 61, 157, 253, 349, 445, 93, 189, 285, 381, 477, +2, 98, 194, 290, 386, 34, 130, 226, 322, 418, 66, 162, 258, 354, 450, +10, 106, 202, 298, 394, 42, 138, 234, 330, 426, 74, 170, 266, 362, 458, +18, 114, 210, 306, 402, 50, 146, 242, 338, 434, 82, 178, 274, 370, 466, +26, 122, 218, 314, 410, 58, 154, 250, 346, 442, 90, 186, 282, 378, 474, +6, 102, 198, 294, 390, 38, 134, 230, 326, 422, 70, 166, 262, 358, 454, +14, 110, 206, 302, 398, 46, 142, 238, 334, 430, 78, 174, 270, 366, 462, +22, 118, 214, 310, 406, 54, 150, 246, 342, 438, 86, 182, 278, 374, 470, +30, 126, 222, 318, 414, 62, 158, 254, 350, 446, 94, 190, 286, 382, 478, +3, 99, 195, 291, 387, 35, 131, 227, 323, 419, 67, 163, 259, 355, 451, +11, 107, 203, 299, 395, 43, 139, 235, 331, 427, 75, 171, 267, 363, 459, +19, 115, 211, 307, 403, 51, 147, 243, 339, 435, 83, 179, 275, 371, 467, +27, 123, 219, 315, 411, 59, 155, 251, 347, 443, 91, 187, 283, 379, 475, +7, 103, 199, 295, 391, 39, 135, 231, 327, 423, 71, 167, 263, 359, 455, +15, 111, 207, 303, 399, 47, 143, 239, 335, 431, 79, 175, 271, 367, 463, +23, 119, 215, 311, 407, 55, 151, 247, 343, 439, 87, 183, 279, 375, 471, +31, 127, 223, 319, 415, 63, 159, 255, 351, 447, 95, 191, 287, 383, 479, +}; +#endif + +#ifndef FFT_BITREV240 +#define FFT_BITREV240 +static const opus_int16 fft_bitrev240[240] = { +0, 48, 96, 144, 192, 16, 64, 112, 160, 208, 32, 80, 128, 176, 224, +4, 52, 100, 148, 196, 20, 68, 116, 164, 212, 36, 84, 132, 180, 228, +8, 56, 104, 152, 200, 24, 72, 120, 168, 216, 40, 88, 136, 184, 232, +12, 60, 108, 156, 204, 28, 76, 124, 172, 220, 44, 92, 140, 188, 236, +1, 49, 97, 145, 193, 17, 65, 113, 161, 209, 33, 81, 129, 177, 225, +5, 53, 101, 149, 197, 21, 69, 117, 165, 213, 37, 85, 133, 181, 229, +9, 57, 105, 153, 201, 25, 73, 121, 169, 217, 41, 89, 137, 185, 233, +13, 61, 109, 157, 205, 29, 77, 125, 173, 221, 45, 93, 141, 189, 237, +2, 50, 98, 146, 194, 18, 66, 114, 162, 210, 34, 82, 130, 178, 226, +6, 54, 102, 150, 198, 22, 70, 118, 166, 214, 38, 86, 134, 182, 230, +10, 58, 106, 154, 202, 26, 74, 122, 170, 218, 42, 90, 138, 186, 234, +14, 62, 110, 158, 206, 30, 78, 126, 174, 222, 46, 94, 142, 190, 238, +3, 51, 99, 147, 195, 19, 67, 115, 163, 211, 35, 83, 131, 179, 227, +7, 55, 103, 151, 199, 23, 71, 119, 167, 215, 39, 87, 135, 183, 231, +11, 59, 107, 155, 203, 27, 75, 123, 171, 219, 43, 91, 139, 187, 235, +15, 63, 111, 159, 207, 31, 79, 127, 175, 223, 47, 95, 143, 191, 239, +}; +#endif + +#ifndef FFT_BITREV120 +#define FFT_BITREV120 +static const opus_int16 fft_bitrev120[120] = { +0, 24, 48, 72, 96, 8, 32, 56, 80, 104, 16, 40, 64, 88, 112, +4, 28, 52, 76, 100, 12, 36, 60, 84, 108, 20, 44, 68, 92, 116, +1, 25, 49, 73, 97, 9, 33, 57, 81, 105, 17, 41, 65, 89, 113, +5, 29, 53, 77, 101, 13, 37, 61, 85, 109, 21, 45, 69, 93, 117, +2, 26, 50, 74, 98, 10, 34, 58, 82, 106, 18, 42, 66, 90, 114, +6, 30, 54, 78, 102, 14, 38, 62, 86, 110, 22, 46, 70, 94, 118, +3, 27, 51, 75, 99, 11, 35, 59, 83, 107, 19, 43, 67, 91, 115, +7, 31, 55, 79, 103, 15, 39, 63, 87, 111, 23, 47, 71, 95, 119, +}; +#endif + +#ifndef FFT_BITREV60 +#define FFT_BITREV60 +static const opus_int16 fft_bitrev60[60] = { +0, 12, 24, 36, 48, 4, 16, 28, 40, 52, 8, 20, 32, 44, 56, +1, 13, 25, 37, 49, 5, 17, 29, 41, 53, 9, 21, 33, 45, 57, +2, 14, 26, 38, 50, 6, 18, 30, 42, 54, 10, 22, 34, 46, 58, +3, 15, 27, 39, 51, 7, 19, 31, 43, 55, 11, 23, 35, 47, 59, +}; +#endif + +#ifndef FFT_STATE48000_960_0 +#define FFT_STATE48000_960_0 +static const kiss_fft_state fft_state48000_960_0 = { +480, /* nfft */ +17476, /* scale */ +8, /* scale_shift */ +-1, /* shift */ +{5, 96, 3, 32, 4, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev480, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_480, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_1 +#define FFT_STATE48000_960_1 +static const kiss_fft_state fft_state48000_960_1 = { +240, /* nfft */ +17476, /* scale */ +7, /* scale_shift */ +1, /* shift */ +{5, 48, 3, 16, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev240, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_240, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_2 +#define FFT_STATE48000_960_2 +static const kiss_fft_state fft_state48000_960_2 = { +120, /* nfft */ +17476, /* scale */ +6, /* scale_shift */ +2, /* shift */ +{5, 24, 3, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev120, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_120, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_3 +#define FFT_STATE48000_960_3 +static const kiss_fft_state fft_state48000_960_3 = { +60, /* nfft */ +17476, /* scale */ +5, /* scale_shift */ +3, /* shift */ +{5, 12, 3, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev60, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_60, +#else +NULL, +#endif +}; +#endif + +#endif + +#ifndef MDCT_TWIDDLES960 +#define MDCT_TWIDDLES960 +static const opus_val16 mdct_twiddles960[1800] = { +32767, 32767, 32767, 32766, 32765, +32763, 32761, 32759, 32756, 32753, +32750, 32746, 32742, 32738, 32733, +32728, 32722, 32717, 32710, 32704, +32697, 32690, 32682, 32674, 32666, +32657, 32648, 32639, 32629, 32619, +32609, 32598, 32587, 32576, 32564, +32552, 32539, 32526, 32513, 32500, +32486, 32472, 32457, 32442, 32427, +32411, 32395, 32379, 32362, 32345, +32328, 32310, 32292, 32274, 32255, +32236, 32217, 32197, 32177, 32157, +32136, 32115, 32093, 32071, 32049, +32027, 32004, 31981, 31957, 31933, +31909, 31884, 31859, 31834, 31809, +31783, 31756, 31730, 31703, 31676, +31648, 31620, 31592, 31563, 31534, +31505, 31475, 31445, 31415, 31384, +31353, 31322, 31290, 31258, 31226, +31193, 31160, 31127, 31093, 31059, +31025, 30990, 30955, 30920, 30884, +30848, 30812, 30775, 30738, 30701, +30663, 30625, 30587, 30548, 30509, +30470, 30430, 30390, 30350, 30309, +30269, 30227, 30186, 30144, 30102, +30059, 30016, 29973, 29930, 29886, +29842, 29797, 29752, 29707, 29662, +29616, 29570, 29524, 29477, 29430, +29383, 29335, 29287, 29239, 29190, +29142, 29092, 29043, 28993, 28943, +28892, 28842, 28791, 28739, 28688, +28636, 28583, 28531, 28478, 28425, +28371, 28317, 28263, 28209, 28154, +28099, 28044, 27988, 27932, 27876, +27820, 27763, 27706, 27648, 27591, +27533, 27474, 27416, 27357, 27298, +27238, 27178, 27118, 27058, 26997, +26936, 26875, 26814, 26752, 26690, +26628, 26565, 26502, 26439, 26375, +26312, 26247, 26183, 26119, 26054, +25988, 25923, 25857, 25791, 25725, +25658, 25592, 25524, 25457, 25389, +25322, 25253, 25185, 25116, 25047, +24978, 24908, 24838, 24768, 24698, +24627, 24557, 24485, 24414, 24342, +24270, 24198, 24126, 24053, 23980, +23907, 23834, 23760, 23686, 23612, +23537, 23462, 23387, 23312, 23237, +23161, 23085, 23009, 22932, 22856, +22779, 22701, 22624, 22546, 22468, +22390, 22312, 22233, 22154, 22075, +21996, 21916, 21836, 21756, 21676, +21595, 21515, 21434, 21352, 21271, +21189, 21107, 21025, 20943, 20860, +20777, 20694, 20611, 20528, 20444, +20360, 20276, 20192, 20107, 20022, +19937, 19852, 19767, 19681, 19595, +19509, 19423, 19336, 19250, 19163, +19076, 18988, 18901, 18813, 18725, +18637, 18549, 18460, 18372, 18283, +18194, 18104, 18015, 17925, 17835, +17745, 17655, 17565, 17474, 17383, +17292, 17201, 17110, 17018, 16927, +16835, 16743, 16650, 16558, 16465, +16372, 16279, 16186, 16093, 15999, +15906, 15812, 15718, 15624, 15529, +15435, 15340, 15245, 15150, 15055, +14960, 14864, 14769, 14673, 14577, +14481, 14385, 14288, 14192, 14095, +13998, 13901, 13804, 13706, 13609, +13511, 13414, 13316, 13218, 13119, +13021, 12923, 12824, 12725, 12626, +12527, 12428, 12329, 12230, 12130, +12030, 11930, 11831, 11730, 11630, +11530, 11430, 11329, 11228, 11128, +11027, 10926, 10824, 10723, 10622, +10520, 10419, 10317, 10215, 10113, +10011, 9909, 9807, 9704, 9602, +9499, 9397, 9294, 9191, 9088, +8985, 8882, 8778, 8675, 8572, +8468, 8364, 8261, 8157, 8053, +7949, 7845, 7741, 7637, 7532, +7428, 7323, 7219, 7114, 7009, +6905, 6800, 6695, 6590, 6485, +6380, 6274, 6169, 6064, 5958, +5853, 5747, 5642, 5536, 5430, +5325, 5219, 5113, 5007, 4901, +4795, 4689, 4583, 4476, 4370, +4264, 4157, 4051, 3945, 3838, +3732, 3625, 3518, 3412, 3305, +3198, 3092, 2985, 2878, 2771, +2664, 2558, 2451, 2344, 2237, +2130, 2023, 1916, 1809, 1702, +1594, 1487, 1380, 1273, 1166, +1059, 952, 844, 737, 630, +523, 416, 308, 201, 94, +-13, -121, -228, -335, -442, +-550, -657, -764, -871, -978, +-1086, -1193, -1300, -1407, -1514, +-1621, -1728, -1835, -1942, -2049, +-2157, -2263, -2370, -2477, -2584, +-2691, -2798, -2905, -3012, -3118, +-3225, -3332, -3439, -3545, -3652, +-3758, -3865, -3971, -4078, -4184, +-4290, -4397, -4503, -4609, -4715, +-4821, -4927, -5033, -5139, -5245, +-5351, -5457, -5562, -5668, -5774, +-5879, -5985, -6090, -6195, -6301, +-6406, -6511, -6616, -6721, -6826, +-6931, -7036, -7140, -7245, -7349, +-7454, -7558, -7663, -7767, -7871, +-7975, -8079, -8183, -8287, -8390, +-8494, -8597, -8701, -8804, -8907, +-9011, -9114, -9217, -9319, -9422, +-9525, -9627, -9730, -9832, -9934, +-10037, -10139, -10241, -10342, -10444, +-10546, -10647, -10748, -10850, -10951, +-11052, -11153, -11253, -11354, -11455, +-11555, -11655, -11756, -11856, -11955, +-12055, -12155, -12254, -12354, -12453, +-12552, -12651, -12750, -12849, -12947, +-13046, -13144, -13242, -13340, -13438, +-13536, -13633, -13731, -13828, -13925, +-14022, -14119, -14216, -14312, -14409, +-14505, -14601, -14697, -14793, -14888, +-14984, -15079, -15174, -15269, -15364, +-15459, -15553, -15647, -15741, -15835, +-15929, -16023, -16116, -16210, -16303, +-16396, -16488, -16581, -16673, -16766, +-16858, -16949, -17041, -17133, -17224, +-17315, -17406, -17497, -17587, -17678, +-17768, -17858, -17948, -18037, -18127, +-18216, -18305, -18394, -18483, -18571, +-18659, -18747, -18835, -18923, -19010, +-19098, -19185, -19271, -19358, -19444, +-19531, -19617, -19702, -19788, -19873, +-19959, -20043, -20128, -20213, -20297, +-20381, -20465, -20549, -20632, -20715, +-20798, -20881, -20963, -21046, -21128, +-21210, -21291, -21373, -21454, -21535, +-21616, -21696, -21776, -21856, -21936, +-22016, -22095, -22174, -22253, -22331, +-22410, -22488, -22566, -22643, -22721, +-22798, -22875, -22951, -23028, -23104, +-23180, -23256, -23331, -23406, -23481, +-23556, -23630, -23704, -23778, -23852, +-23925, -23998, -24071, -24144, -24216, +-24288, -24360, -24432, -24503, -24574, +-24645, -24716, -24786, -24856, -24926, +-24995, -25064, -25133, -25202, -25270, +-25339, -25406, -25474, -25541, -25608, +-25675, -25742, -25808, -25874, -25939, +-26005, -26070, -26135, -26199, -26264, +-26327, -26391, -26455, -26518, -26581, +-26643, -26705, -26767, -26829, -26891, +-26952, -27013, -27073, -27133, -27193, +-27253, -27312, -27372, -27430, -27489, +-27547, -27605, -27663, -27720, -27777, +-27834, -27890, -27946, -28002, -28058, +-28113, -28168, -28223, -28277, -28331, +-28385, -28438, -28491, -28544, -28596, +-28649, -28701, -28752, -28803, -28854, +-28905, -28955, -29006, -29055, -29105, +-29154, -29203, -29251, -29299, -29347, +-29395, -29442, -29489, -29535, -29582, +-29628, -29673, -29719, -29764, -29808, +-29853, -29897, -29941, -29984, -30027, +-30070, -30112, -30154, -30196, -30238, +-30279, -30320, -30360, -30400, -30440, +-30480, -30519, -30558, -30596, -30635, +-30672, -30710, -30747, -30784, -30821, +-30857, -30893, -30929, -30964, -30999, +-31033, -31068, -31102, -31135, -31168, +-31201, -31234, -31266, -31298, -31330, +-31361, -31392, -31422, -31453, -31483, +-31512, -31541, -31570, -31599, -31627, +-31655, -31682, -31710, -31737, -31763, +-31789, -31815, -31841, -31866, -31891, +-31915, -31939, -31963, -31986, -32010, +-32032, -32055, -32077, -32099, -32120, +-32141, -32162, -32182, -32202, -32222, +-32241, -32260, -32279, -32297, -32315, +-32333, -32350, -32367, -32383, -32399, +-32415, -32431, -32446, -32461, -32475, +-32489, -32503, -32517, -32530, -32542, +-32555, -32567, -32579, -32590, -32601, +-32612, -32622, -32632, -32641, -32651, +-32659, -32668, -32676, -32684, -32692, +-32699, -32706, -32712, -32718, -32724, +-32729, -32734, -32739, -32743, -32747, +-32751, -32754, -32757, -32760, -32762, +-32764, -32765, -32767, -32767, -32767, +32767, 32767, 32765, 32761, 32756, +32750, 32742, 32732, 32722, 32710, +32696, 32681, 32665, 32647, 32628, +32608, 32586, 32562, 32538, 32512, +32484, 32455, 32425, 32393, 32360, +32326, 32290, 32253, 32214, 32174, +32133, 32090, 32046, 32001, 31954, +31906, 31856, 31805, 31753, 31700, +31645, 31588, 31530, 31471, 31411, +31349, 31286, 31222, 31156, 31089, +31020, 30951, 30880, 30807, 30733, +30658, 30582, 30504, 30425, 30345, +30263, 30181, 30096, 30011, 29924, +29836, 29747, 29656, 29564, 29471, +29377, 29281, 29184, 29086, 28987, +28886, 28784, 28681, 28577, 28471, +28365, 28257, 28147, 28037, 27925, +27812, 27698, 27583, 27467, 27349, +27231, 27111, 26990, 26868, 26744, +26620, 26494, 26367, 26239, 26110, +25980, 25849, 25717, 25583, 25449, +25313, 25176, 25038, 24900, 24760, +24619, 24477, 24333, 24189, 24044, +23898, 23751, 23602, 23453, 23303, +23152, 22999, 22846, 22692, 22537, +22380, 22223, 22065, 21906, 21746, +21585, 21423, 21261, 21097, 20933, +20767, 20601, 20434, 20265, 20096, +19927, 19756, 19584, 19412, 19239, +19065, 18890, 18714, 18538, 18361, +18183, 18004, 17824, 17644, 17463, +17281, 17098, 16915, 16731, 16546, +16361, 16175, 15988, 15800, 15612, +15423, 15234, 15043, 14852, 14661, +14469, 14276, 14083, 13889, 13694, +13499, 13303, 13107, 12910, 12713, +12515, 12317, 12118, 11918, 11718, +11517, 11316, 11115, 10913, 10710, +10508, 10304, 10100, 9896, 9691, +9486, 9281, 9075, 8869, 8662, +8455, 8248, 8040, 7832, 7623, +7415, 7206, 6996, 6787, 6577, +6366, 6156, 5945, 5734, 5523, +5311, 5100, 4888, 4675, 4463, +4251, 4038, 3825, 3612, 3399, +3185, 2972, 2758, 2544, 2330, +2116, 1902, 1688, 1474, 1260, +1045, 831, 617, 402, 188, +-27, -241, -456, -670, -885, +-1099, -1313, -1528, -1742, -1956, +-2170, -2384, -2598, -2811, -3025, +-3239, -3452, -3665, -3878, -4091, +-4304, -4516, -4728, -4941, -5153, +-5364, -5576, -5787, -5998, -6209, +-6419, -6629, -6839, -7049, -7258, +-7467, -7676, -7884, -8092, -8300, +-8507, -8714, -8920, -9127, -9332, +-9538, -9743, -9947, -10151, -10355, +-10558, -10761, -10963, -11165, -11367, +-11568, -11768, -11968, -12167, -12366, +-12565, -12762, -12960, -13156, -13352, +-13548, -13743, -13937, -14131, -14324, +-14517, -14709, -14900, -15091, -15281, +-15470, -15659, -15847, -16035, -16221, +-16407, -16593, -16777, -16961, -17144, +-17326, -17508, -17689, -17869, -18049, +-18227, -18405, -18582, -18758, -18934, +-19108, -19282, -19455, -19627, -19799, +-19969, -20139, -20308, -20475, -20642, +-20809, -20974, -21138, -21301, -21464, +-21626, -21786, -21946, -22105, -22263, +-22420, -22575, -22730, -22884, -23037, +-23189, -23340, -23490, -23640, -23788, +-23935, -24080, -24225, -24369, -24512, +-24654, -24795, -24934, -25073, -25211, +-25347, -25482, -25617, -25750, -25882, +-26013, -26143, -26272, -26399, -26526, +-26651, -26775, -26898, -27020, -27141, +-27260, -27379, -27496, -27612, -27727, +-27841, -27953, -28065, -28175, -28284, +-28391, -28498, -28603, -28707, -28810, +-28911, -29012, -29111, -29209, -29305, +-29401, -29495, -29587, -29679, -29769, +-29858, -29946, -30032, -30118, -30201, +-30284, -30365, -30445, -30524, -30601, +-30677, -30752, -30825, -30897, -30968, +-31038, -31106, -31172, -31238, -31302, +-31365, -31426, -31486, -31545, -31602, +-31658, -31713, -31766, -31818, -31869, +-31918, -31966, -32012, -32058, -32101, +-32144, -32185, -32224, -32262, -32299, +-32335, -32369, -32401, -32433, -32463, +-32491, -32518, -32544, -32568, -32591, +-32613, -32633, -32652, -32669, -32685, +-32700, -32713, -32724, -32735, -32744, +-32751, -32757, -32762, -32766, -32767, +32767, 32764, 32755, 32741, 32720, +32694, 32663, 32626, 32583, 32535, +32481, 32421, 32356, 32286, 32209, +32128, 32041, 31948, 31850, 31747, +31638, 31523, 31403, 31278, 31148, +31012, 30871, 30724, 30572, 30415, +30253, 30086, 29913, 29736, 29553, +29365, 29172, 28974, 28771, 28564, +28351, 28134, 27911, 27684, 27452, +27216, 26975, 26729, 26478, 26223, +25964, 25700, 25432, 25159, 24882, +24601, 24315, 24026, 23732, 23434, +23133, 22827, 22517, 22204, 21886, +21565, 21240, 20912, 20580, 20244, +19905, 19563, 19217, 18868, 18516, +18160, 17802, 17440, 17075, 16708, +16338, 15964, 15588, 15210, 14829, +14445, 14059, 13670, 13279, 12886, +12490, 12093, 11693, 11291, 10888, +10482, 10075, 9666, 9255, 8843, +8429, 8014, 7597, 7180, 6760, +6340, 5919, 5496, 5073, 4649, +4224, 3798, 3372, 2945, 2517, +2090, 1661, 1233, 804, 375, +-54, -483, -911, -1340, -1768, +-2197, -2624, -3052, -3479, -3905, +-4330, -4755, -5179, -5602, -6024, +-6445, -6865, -7284, -7702, -8118, +-8533, -8946, -9358, -9768, -10177, +-10584, -10989, -11392, -11793, -12192, +-12589, -12984, -13377, -13767, -14155, +-14541, -14924, -15305, -15683, -16058, +-16430, -16800, -17167, -17531, -17892, +-18249, -18604, -18956, -19304, -19649, +-19990, -20329, -20663, -20994, -21322, +-21646, -21966, -22282, -22595, -22904, +-23208, -23509, -23806, -24099, -24387, +-24672, -24952, -25228, -25499, -25766, +-26029, -26288, -26541, -26791, -27035, +-27275, -27511, -27741, -27967, -28188, +-28405, -28616, -28823, -29024, -29221, +-29412, -29599, -29780, -29957, -30128, +-30294, -30455, -30611, -30761, -30906, +-31046, -31181, -31310, -31434, -31552, +-31665, -31773, -31875, -31972, -32063, +-32149, -32229, -32304, -32373, -32437, +-32495, -32547, -32594, -32635, -32671, +-32701, -32726, -32745, -32758, -32766, +32767, 32754, 32717, 32658, 32577, +32473, 32348, 32200, 32029, 31837, +31624, 31388, 31131, 30853, 30553, +30232, 29891, 29530, 29148, 28746, +28324, 27883, 27423, 26944, 26447, +25931, 25398, 24847, 24279, 23695, +23095, 22478, 21846, 21199, 20538, +19863, 19174, 18472, 17757, 17030, +16291, 15541, 14781, 14010, 13230, +12441, 11643, 10837, 10024, 9204, +8377, 7545, 6708, 5866, 5020, +4171, 3319, 2464, 1608, 751, +-107, -965, -1822, -2678, -3532, +-4383, -5232, -6077, -6918, -7754, +-8585, -9409, -10228, -11039, -11843, +-12639, -13426, -14204, -14972, -15730, +-16477, -17213, -17937, -18648, -19347, +-20033, -20705, -21363, -22006, -22634, +-23246, -23843, -24423, -24986, -25533, +-26062, -26573, -27066, -27540, -27995, +-28431, -28848, -29245, -29622, -29979, +-30315, -30630, -30924, -31197, -31449, +-31679, -31887, -32074, -32239, -32381, +-32501, -32600, -32675, -32729, -32759, +}; +#endif + +static const CELTMode mode48000_960_120 = { +48000, /* Fs */ +120, /* overlap */ +21, /* nbEBands */ +21, /* effEBands */ +{27853, 0, 4096, 8192, }, /* preemph */ +eband5ms, /* eBands */ +3, /* maxLM */ +8, /* nbShortMdcts */ +120, /* shortMdctSize */ +11, /* nbAllocVectors */ +band_allocation, /* allocVectors */ +logN400, /* logN */ +window120, /* window */ +{1920, 3, {&fft_state48000_960_0, &fft_state48000_960_1, &fft_state48000_960_2, &fft_state48000_960_3, }, mdct_twiddles960}, /* mdct */ +{392, cache_index50, cache_bits50, cache_caps50}, /* cache */ +}; + +/* List of all the available modes */ +#define TOTAL_MODES 1 +static const CELTMode * const static_mode_list[TOTAL_MODES] = { +&mode48000_960_120, +}; diff --git a/port/third_party/opus/celt/static_modes_fixed_arm_ne10.h b/port/third_party/opus/celt/static_modes_fixed_arm_ne10.h new file mode 100644 index 00000000..76230921 --- /dev/null +++ b/port/third_party/opus/celt/static_modes_fixed_arm_ne10.h @@ -0,0 +1,388 @@ +/* The contents of this file was automatically generated by + * dump_mode_arm_ne10.c with arguments: 48000 960 + * It contains static definitions for some pre-defined modes. */ +#include + +#ifndef NE10_FFT_PARAMS48000_960 +#define NE10_FFT_PARAMS48000_960 +static const ne10_int32_t ne10_factors_480[64] = { +4, 40, 4, 30, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_240[64] = { +3, 20, 4, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_120[64] = { +3, 10, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_60[64] = { +2, 5, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_fft_cpx_int32_t ne10_twiddles_480[480] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{-94,-2147483647}, {-224473265,-2135719496}, {-446487060,-2100555955}, +{-663609049,-2042378281}, {-873460398,-1961823883}, {-1073741932,-1859775330}, +{-1262259116,-1737350839}, {-1436947137,-1595891268}, {-1595891628,-1436946738}, +{-1737350854,-1262259096}, {-1859775343,-1073741910}, {-1961823997,-873460141}, +{-2042378447,-663608538}, {-2100556013,-446486785}, {-2135719499,-224473240}, +{2147483647,0}, {2121044558,-335940465}, {2042378310,-663608960}, +{1913421927,-974937199}, {1737350743,-1262259248}, {1518500216,-1518500282}, +{1262259172,-1737350799}, {974937230,-1913421912}, {663608871,-2042378339}, +{335940246,-2121044593}, {-94,-2147483647}, {-335940431,-2121044564}, +{-663609049,-2042378281}, {-974937397,-1913421827}, {-1262259116,-1737350839}, +{-1518500258,-1518500240}, {-1737350854,-1262259096}, {-1913422071,-974936918}, +{-2042378447,-663608538}, {-2121044568,-335940406}, {-2147483647,188}, +{-2121044509,335940777}, {-2042378331,663608895}, {-1913421900,974937252}, +{-1737350633,1262259400}, {-1518499993,1518500506}, {-1262258813,1737351059}, +{-974936606,1913422229}, {-663609179,2042378239}, {-335940566,2121044542}, +{2147483647,0}, {2147299667,-28109693}, {2146747758,-56214570}, +{2145828015,-84309815}, {2144540595,-112390613}, {2142885719,-140452154}, +{2140863671,-168489630}, {2138474797,-196498235}, {2135719506,-224473172}, +{2132598271,-252409646}, {2129111626,-280302871}, {2125260168,-308148068}, +{2121044558,-335940465}, {2116465518,-363675300}, {2111523833,-391347822}, +{2106220349,-418953288}, {2100555974,-446486968}, {2094531681,-473944146}, +{2088148500,-501320115}, {2081407525,-528610186}, {2074309912,-555809682}, +{2066856885,-582913912}, {2059049696,-609918325}, {2050889698,-636818231}, +{2042378310,-663608960}, {2033516972,-690285983}, {2024307180,-716844791}, +{2014750533,-743280770}, {2004848691,-769589332}, {1994603329,-795766029}, +{1984016179,-821806435}, {1973089077,-847706028}, {1961823921,-873460313}, +{1950222618,-899064934}, {1938287127,-924515564}, {1926019520,-949807783}, +{1913421927,-974937199}, {1900496481,-999899565}, {1887245364,-1024690661}, +{1873670877,-1049306180}, {1859775377,-1073741851}, {1845561215,-1097993541}, +{1831030826,-1122057097}, {1816186632,-1145928502}, {1801031311,-1169603450}, +{1785567394,-1193077993}, {1769797456,-1216348214}, {1753724345,-1239409914}, +{1737350743,-1262259248}, {1720679456,-1284892300}, {1703713340,-1307305194}, +{1686455222,-1329494189}, {1668908218,-1351455280}, {1651075255,-1373184807}, +{1632959307,-1394679144}, {1614563642,-1415934412}, {1595891331,-1436947067}, +{1576945572,-1457713510}, {1557729613,-1478230181}, {1538246655,-1498493658}, +{1518500216,-1518500282}, {1498493590,-1538246721}, {1478230113,-1557729677}, +{1457713441,-1576945636}, {1436946998,-1595891394}, {1415934341,-1614563704}, +{1394679073,-1632959368}, {1373184735,-1651075315}, {1351455207,-1668908277}, +{1329494115,-1686455280}, {1307305120,-1703713397}, {1284892225,-1720679512}, +{1262259172,-1737350799}, {1239409837,-1753724400}, {1216348136,-1769797510}, +{1193077915,-1785567446}, {1169603371,-1801031362}, {1145928423,-1816186682}, +{1122057017,-1831030875}, {1097993571,-1845561197}, {1073741769,-1859775424}, +{1049305987,-1873670985}, {1024690635,-1887245378}, {999899482,-1900496524}, +{974937230,-1913421912}, {949807699,-1926019561}, {924515422,-1938287195}, +{899064965,-1950222603}, {873460227,-1961823959}, {847705824,-1973089164}, +{821806407,-1984016190}, {795765941,-1994603364}, {769589125,-2004848771}, +{743280682,-2014750566}, {716844642,-2024307233}, {690286016,-2033516961}, +{663608871,-2042378339}, {636818019,-2050889764}, {609918296,-2059049705}, +{582913822,-2066856911}, {555809715,-2074309903}, {528610126,-2081407540}, +{501319962,-2088148536}, {473944148,-2094531680}, {446486876,-2100555994}, +{418953102,-2106220386}, {391347792,-2111523838}, {363675176,-2116465540}, +{335940246,-2121044593}, {308148006,-2125260177}, {280302715,-2129111646}, +{252409648,-2132598271}, {224473078,-2135719516}, {196498046,-2138474814}, +{168489600,-2140863674}, {140452029,-2142885728}, {112390647,-2144540593}, +{84309753,-2145828017}, {56214412,-2146747762}, {28109695,-2147299667}, +{2147483647,0}, {2146747758,-56214570}, {2144540595,-112390613}, +{2140863671,-168489630}, {2135719506,-224473172}, {2129111626,-280302871}, +{2121044558,-335940465}, {2111523833,-391347822}, {2100555974,-446486968}, +{2088148500,-501320115}, {2074309912,-555809682}, {2059049696,-609918325}, +{2042378310,-663608960}, {2024307180,-716844791}, {2004848691,-769589332}, +{1984016179,-821806435}, {1961823921,-873460313}, {1938287127,-924515564}, +{1913421927,-974937199}, {1887245364,-1024690661}, {1859775377,-1073741851}, +{1831030826,-1122057097}, {1801031311,-1169603450}, {1769797456,-1216348214}, +{1737350743,-1262259248}, {1703713340,-1307305194}, {1668908218,-1351455280}, +{1632959307,-1394679144}, {1595891331,-1436947067}, {1557729613,-1478230181}, +{1518500216,-1518500282}, {1478230113,-1557729677}, {1436946998,-1595891394}, +{1394679073,-1632959368}, {1351455207,-1668908277}, {1307305120,-1703713397}, +{1262259172,-1737350799}, {1216348136,-1769797510}, {1169603371,-1801031362}, +{1122057017,-1831030875}, {1073741769,-1859775424}, {1024690635,-1887245378}, +{974937230,-1913421912}, {924515422,-1938287195}, {873460227,-1961823959}, +{821806407,-1984016190}, {769589125,-2004848771}, {716844642,-2024307233}, +{663608871,-2042378339}, {609918296,-2059049705}, {555809715,-2074309903}, +{501319962,-2088148536}, {446486876,-2100555994}, {391347792,-2111523838}, +{335940246,-2121044593}, {280302715,-2129111646}, {224473078,-2135719516}, +{168489600,-2140863674}, {112390647,-2144540593}, {56214412,-2146747762}, +{-94,-2147483647}, {-56214600,-2146747757}, {-112390835,-2144540584}, +{-168489787,-2140863659}, {-224473265,-2135719496}, {-280302901,-2129111622}, +{-335940431,-2121044564}, {-391347977,-2111523804}, {-446487060,-2100555955}, +{-501320144,-2088148493}, {-555809896,-2074309855}, {-609918476,-2059049651}, +{-663609049,-2042378281}, {-716844819,-2024307170}, {-769589300,-2004848703}, +{-821806581,-1984016118}, {-873460398,-1961823883}, {-924515591,-1938287114}, +{-974937397,-1913421827}, {-1024690575,-1887245411}, {-1073741932,-1859775330}, +{-1122057395,-1831030643}, {-1169603421,-1801031330}, {-1216348291,-1769797403}, +{-1262259116,-1737350839}, {-1307305268,-1703713283}, {-1351455453,-1668908078}, +{-1394679021,-1632959413}, {-1436947137,-1595891268}, {-1478230435,-1557729372}, +{-1518500258,-1518500240}, {-1557729742,-1478230045}, {-1595891628,-1436946738}, +{-1632959429,-1394679001}, {-1668908417,-1351455035}, {-1703713298,-1307305248}, +{-1737350854,-1262259096}, {-1769797708,-1216347848}, {-1801031344,-1169603400}, +{-1831030924,-1122056937}, {-1859775343,-1073741910}, {-1887245423,-1024690552}, +{-1913422071,-974936918}, {-1938287125,-924515568}, {-1961823997,-873460141}, +{-1984016324,-821806084}, {-2004848713,-769589276}, {-2024307264,-716844553}, +{-2042378447,-663608538}, {-2059049731,-609918206}, {-2074309994,-555809377}, +{-2088148499,-501320119}, {-2100556013,-446486785}, {-2111523902,-391347448}, +{-2121044568,-335940406}, {-2129111659,-280302621}, {-2135719499,-224473240}, +{-2140863681,-168489506}, {-2144540612,-112390298}, {-2146747758,-56214574}, +{2147483647,0}, {2145828015,-84309815}, {2140863671,-168489630}, +{2132598271,-252409646}, {2121044558,-335940465}, {2106220349,-418953288}, +{2088148500,-501320115}, {2066856885,-582913912}, {2042378310,-663608960}, +{2014750533,-743280770}, {1984016179,-821806435}, {1950222618,-899064934}, +{1913421927,-974937199}, {1873670877,-1049306180}, {1831030826,-1122057097}, +{1785567394,-1193077993}, {1737350743,-1262259248}, {1686455222,-1329494189}, +{1632959307,-1394679144}, {1576945572,-1457713510}, {1518500216,-1518500282}, +{1457713441,-1576945636}, {1394679073,-1632959368}, {1329494115,-1686455280}, +{1262259172,-1737350799}, {1193077915,-1785567446}, {1122057017,-1831030875}, +{1049305987,-1873670985}, {974937230,-1913421912}, {899064965,-1950222603}, +{821806407,-1984016190}, {743280682,-2014750566}, {663608871,-2042378339}, +{582913822,-2066856911}, {501319962,-2088148536}, {418953102,-2106220386}, +{335940246,-2121044593}, {252409648,-2132598271}, {168489600,-2140863674}, +{84309753,-2145828017}, {-94,-2147483647}, {-84309940,-2145828010}, +{-168489787,-2140863659}, {-252409834,-2132598249}, {-335940431,-2121044564}, +{-418953286,-2106220349}, {-501320144,-2088148493}, {-582914003,-2066856860}, +{-663609049,-2042378281}, {-743280858,-2014750501}, {-821806581,-1984016118}, +{-899065136,-1950222525}, {-974937397,-1913421827}, {-1049306374,-1873670768}, +{-1122057395,-1831030643}, {-1193078284,-1785567199}, {-1262259116,-1737350839}, +{-1329494061,-1686455323}, {-1394679021,-1632959413}, {-1457713485,-1576945595}, +{-1518500258,-1518500240}, {-1576945613,-1457713466}, {-1632959429,-1394679001}, +{-1686455338,-1329494041}, {-1737350854,-1262259096}, {-1785567498,-1193077837}, +{-1831030924,-1122056937}, {-1873671031,-1049305905}, {-1913422071,-974936918}, +{-1950222750,-899064648}, {-1984016324,-821806084}, {-2014750687,-743280354}, +{-2042378447,-663608538}, {-2066856867,-582913978}, {-2088148499,-501320119}, +{-2106220354,-418953261}, {-2121044568,-335940406}, {-2132598282,-252409555}, +{-2140863681,-168489506}, {-2145828021,-84309659}, {-2147483647,188}, +{-2145828006,84310034}, {-2140863651,168489881}, {-2132598237,252409928}, +{-2121044509,335940777}, {-2106220281,418953629}, {-2088148411,501320484}, +{-2066856765,582914339}, {-2042378331,663608895}, {-2014750557,743280706}, +{-1984016181,821806431}, {-1950222593,899064989}, {-1913421900,974937252}, +{-1873670848,1049306232}, {-1831030728,1122057257}, {-1785567289,1193078149}, +{-1737350633,1262259400}, {-1686455106,1329494336}, {-1632959185,1394679287}, +{-1576945358,1457713742}, {-1518499993,1518500506}, {-1457713209,1576945850}, +{-1394678735,1632959656}, {-1329493766,1686455555}, {-1262258813,1737351059}, +{-1193077546,1785567692}, {-1122056638,1831031107}, {-1049305599,1873671202}, +{-974936606,1913422229}, {-899064330,1950222896}, {-821805761,1984016458}, +{-743280025,2014750808}, {-663609179,2042378239}, {-582914134,2066856823}, +{-501320277,2088148461}, {-418953420,2106220322}, {-335940566,2121044542}, +{-252409716,2132598263}, {-168489668,2140863668}, {-84309821,2145828015}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_240[240] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2042378310,-663608960}, {1737350743,-1262259248}, +{1262259172,-1737350799}, {663608871,-2042378339}, {-94,-2147483647}, +{-663609049,-2042378281}, {-1262259116,-1737350839}, {-1737350854,-1262259096}, +{-2042378447,-663608538}, {-2147483647,188}, {-2042378331,663608895}, +{-1737350633,1262259400}, {-1262258813,1737351059}, {-663609179,2042378239}, +{2147483647,0}, {2146747758,-56214570}, {2144540595,-112390613}, +{2140863671,-168489630}, {2135719506,-224473172}, {2129111626,-280302871}, +{2121044558,-335940465}, {2111523833,-391347822}, {2100555974,-446486968}, +{2088148500,-501320115}, {2074309912,-555809682}, {2059049696,-609918325}, +{2042378310,-663608960}, {2024307180,-716844791}, {2004848691,-769589332}, +{1984016179,-821806435}, {1961823921,-873460313}, {1938287127,-924515564}, +{1913421927,-974937199}, {1887245364,-1024690661}, {1859775377,-1073741851}, +{1831030826,-1122057097}, {1801031311,-1169603450}, {1769797456,-1216348214}, +{1737350743,-1262259248}, {1703713340,-1307305194}, {1668908218,-1351455280}, +{1632959307,-1394679144}, {1595891331,-1436947067}, {1557729613,-1478230181}, +{1518500216,-1518500282}, {1478230113,-1557729677}, {1436946998,-1595891394}, +{1394679073,-1632959368}, {1351455207,-1668908277}, {1307305120,-1703713397}, +{1262259172,-1737350799}, {1216348136,-1769797510}, {1169603371,-1801031362}, +{1122057017,-1831030875}, {1073741769,-1859775424}, {1024690635,-1887245378}, +{974937230,-1913421912}, {924515422,-1938287195}, {873460227,-1961823959}, +{821806407,-1984016190}, {769589125,-2004848771}, {716844642,-2024307233}, +{663608871,-2042378339}, {609918296,-2059049705}, {555809715,-2074309903}, +{501319962,-2088148536}, {446486876,-2100555994}, {391347792,-2111523838}, +{335940246,-2121044593}, {280302715,-2129111646}, {224473078,-2135719516}, +{168489600,-2140863674}, {112390647,-2144540593}, {56214412,-2146747762}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{-94,-2147483647}, {-112390835,-2144540584}, {-224473265,-2135719496}, +{-335940431,-2121044564}, {-446487060,-2100555955}, {-555809896,-2074309855}, +{-663609049,-2042378281}, {-769589300,-2004848703}, {-873460398,-1961823883}, +{-974937397,-1913421827}, {-1073741932,-1859775330}, {-1169603421,-1801031330}, +{-1262259116,-1737350839}, {-1351455453,-1668908078}, {-1436947137,-1595891268}, +{-1518500258,-1518500240}, {-1595891628,-1436946738}, {-1668908417,-1351455035}, +{-1737350854,-1262259096}, {-1801031344,-1169603400}, {-1859775343,-1073741910}, +{-1913422071,-974936918}, {-1961823997,-873460141}, {-2004848713,-769589276}, +{-2042378447,-663608538}, {-2074309994,-555809377}, {-2100556013,-446486785}, +{-2121044568,-335940406}, {-2135719499,-224473240}, {-2144540612,-112390298}, +{2147483647,0}, {2140863671,-168489630}, {2121044558,-335940465}, +{2088148500,-501320115}, {2042378310,-663608960}, {1984016179,-821806435}, +{1913421927,-974937199}, {1831030826,-1122057097}, {1737350743,-1262259248}, +{1632959307,-1394679144}, {1518500216,-1518500282}, {1394679073,-1632959368}, +{1262259172,-1737350799}, {1122057017,-1831030875}, {974937230,-1913421912}, +{821806407,-1984016190}, {663608871,-2042378339}, {501319962,-2088148536}, +{335940246,-2121044593}, {168489600,-2140863674}, {-94,-2147483647}, +{-168489787,-2140863659}, {-335940431,-2121044564}, {-501320144,-2088148493}, +{-663609049,-2042378281}, {-821806581,-1984016118}, {-974937397,-1913421827}, +{-1122057395,-1831030643}, {-1262259116,-1737350839}, {-1394679021,-1632959413}, +{-1518500258,-1518500240}, {-1632959429,-1394679001}, {-1737350854,-1262259096}, +{-1831030924,-1122056937}, {-1913422071,-974936918}, {-1984016324,-821806084}, +{-2042378447,-663608538}, {-2088148499,-501320119}, {-2121044568,-335940406}, +{-2140863681,-168489506}, {-2147483647,188}, {-2140863651,168489881}, +{-2121044509,335940777}, {-2088148411,501320484}, {-2042378331,663608895}, +{-1984016181,821806431}, {-1913421900,974937252}, {-1831030728,1122057257}, +{-1737350633,1262259400}, {-1632959185,1394679287}, {-1518499993,1518500506}, +{-1394678735,1632959656}, {-1262258813,1737351059}, {-1122056638,1831031107}, +{-974936606,1913422229}, {-821805761,1984016458}, {-663609179,2042378239}, +{-501320277,2088148461}, {-335940566,2121044542}, {-168489668,2140863668}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_120[120] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{-94,-2147483647}, {-224473265,-2135719496}, {-446487060,-2100555955}, +{-663609049,-2042378281}, {-873460398,-1961823883}, {-1073741932,-1859775330}, +{-1262259116,-1737350839}, {-1436947137,-1595891268}, {-1595891628,-1436946738}, +{-1737350854,-1262259096}, {-1859775343,-1073741910}, {-1961823997,-873460141}, +{-2042378447,-663608538}, {-2100556013,-446486785}, {-2135719499,-224473240}, +{2147483647,0}, {2121044558,-335940465}, {2042378310,-663608960}, +{1913421927,-974937199}, {1737350743,-1262259248}, {1518500216,-1518500282}, +{1262259172,-1737350799}, {974937230,-1913421912}, {663608871,-2042378339}, +{335940246,-2121044593}, {-94,-2147483647}, {-335940431,-2121044564}, +{-663609049,-2042378281}, {-974937397,-1913421827}, {-1262259116,-1737350839}, +{-1518500258,-1518500240}, {-1737350854,-1262259096}, {-1913422071,-974936918}, +{-2042378447,-663608538}, {-2121044568,-335940406}, {-2147483647,188}, +{-2121044509,335940777}, {-2042378331,663608895}, {-1913421900,974937252}, +{-1737350633,1262259400}, {-1518499993,1518500506}, {-1262258813,1737351059}, +{-974936606,1913422229}, {-663609179,2042378239}, {-335940566,2121044542}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_60[60] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2042378310,-663608960}, {1737350743,-1262259248}, +{1262259172,-1737350799}, {663608871,-2042378339}, {-94,-2147483647}, +{-663609049,-2042378281}, {-1262259116,-1737350839}, {-1737350854,-1262259096}, +{-2042378447,-663608538}, {-2147483647,188}, {-2042378331,663608895}, +{-1737350633,1262259400}, {-1262258813,1737351059}, {-663609179,2042378239}, +}; +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_480 = { +120, +(ne10_int32_t *)ne10_factors_480, +(ne10_fft_cpx_int32_t *)ne10_twiddles_480, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_480[120], +}; +static const arch_fft_state cfg_arch_480 = { +1, +(void *)&ne10_fft_state_int32_t_480, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_240 = { +60, +(ne10_int32_t *)ne10_factors_240, +(ne10_fft_cpx_int32_t *)ne10_twiddles_240, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_240[60], +}; +static const arch_fft_state cfg_arch_240 = { +1, +(void *)&ne10_fft_state_int32_t_240, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_120 = { +30, +(ne10_int32_t *)ne10_factors_120, +(ne10_fft_cpx_int32_t *)ne10_twiddles_120, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_120[30], +}; +static const arch_fft_state cfg_arch_120 = { +1, +(void *)&ne10_fft_state_int32_t_120, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_60 = { +15, +(ne10_int32_t *)ne10_factors_60, +(ne10_fft_cpx_int32_t *)ne10_twiddles_60, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_60[15], +}; +static const arch_fft_state cfg_arch_60 = { +1, +(void *)&ne10_fft_state_int32_t_60, +}; + +#endif /* end NE10_FFT_PARAMS48000_960 */ diff --git a/port/third_party/opus/celt/static_modes_float.h b/port/third_party/opus/celt/static_modes_float.h new file mode 100644 index 00000000..e102a383 --- /dev/null +++ b/port/third_party/opus/celt/static_modes_float.h @@ -0,0 +1,888 @@ +/* The contents of this file was automatically generated by dump_modes.c + with arguments: 48000 960 + It contains static definitions for some pre-defined modes. */ +#include "modes.h" +#include "rate.h" + +#ifdef HAVE_ARM_NE10 +#define OVERRIDE_FFT 1 +#include "static_modes_float_arm_ne10.h" +#endif + +#ifndef DEF_WINDOW120 +#define DEF_WINDOW120 +static const opus_val16 window120[120] = { +6.7286966e-05f, 0.00060551348f, 0.0016815970f, 0.0032947962f, 0.0054439943f, +0.0081276923f, 0.011344001f, 0.015090633f, 0.019364886f, 0.024163635f, +0.029483315f, 0.035319905f, 0.041668911f, 0.048525347f, 0.055883718f, +0.063737999f, 0.072081616f, 0.080907428f, 0.090207705f, 0.099974111f, +0.11019769f, 0.12086883f, 0.13197729f, 0.14351214f, 0.15546177f, +0.16781389f, 0.18055550f, 0.19367290f, 0.20715171f, 0.22097682f, +0.23513243f, 0.24960208f, 0.26436860f, 0.27941419f, 0.29472040f, +0.31026818f, 0.32603788f, 0.34200931f, 0.35816177f, 0.37447407f, +0.39092462f, 0.40749142f, 0.42415215f, 0.44088423f, 0.45766484f, +0.47447104f, 0.49127978f, 0.50806798f, 0.52481261f, 0.54149077f, +0.55807973f, 0.57455701f, 0.59090049f, 0.60708841f, 0.62309951f, +0.63891306f, 0.65450896f, 0.66986776f, 0.68497077f, 0.69980010f, +0.71433873f, 0.72857055f, 0.74248043f, 0.75605424f, 0.76927895f, +0.78214257f, 0.79463430f, 0.80674445f, 0.81846456f, 0.82978733f, +0.84070669f, 0.85121779f, 0.86131698f, 0.87100183f, 0.88027111f, +0.88912479f, 0.89756398f, 0.90559094f, 0.91320904f, 0.92042270f, +0.92723738f, 0.93365955f, 0.93969656f, 0.94535671f, 0.95064907f, +0.95558353f, 0.96017067f, 0.96442171f, 0.96834849f, 0.97196334f, +0.97527906f, 0.97830883f, 0.98106616f, 0.98356480f, 0.98581869f, +0.98784191f, 0.98964856f, 0.99125274f, 0.99266849f, 0.99390969f, +0.99499004f, 0.99592297f, 0.99672162f, 0.99739874f, 0.99796667f, +0.99843728f, 0.99882195f, 0.99913147f, 0.99937606f, 0.99956527f, +0.99970802f, 0.99981248f, 0.99988613f, 0.99993565f, 0.99996697f, +0.99998518f, 0.99999457f, 0.99999859f, 0.99999982f, 1.0000000f, +}; +#endif + +#ifndef DEF_LOGN400 +#define DEF_LOGN400 +static const opus_int16 logN400[21] = { +0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 16, 16, 16, 21, 21, 24, 29, 34, 36, }; +#endif + +#ifndef DEF_PULSE_CACHE50 +#define DEF_PULSE_CACHE50 +static const opus_int16 cache_index50[105] = { +-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 41, 41, 41, +82, 82, 123, 164, 200, 222, 0, 0, 0, 0, 0, 0, 0, 0, 41, +41, 41, 41, 123, 123, 123, 164, 164, 240, 266, 283, 295, 41, 41, 41, +41, 41, 41, 41, 41, 123, 123, 123, 123, 240, 240, 240, 266, 266, 305, +318, 328, 336, 123, 123, 123, 123, 123, 123, 123, 123, 240, 240, 240, 240, +305, 305, 305, 318, 318, 343, 351, 358, 364, 240, 240, 240, 240, 240, 240, +240, 240, 305, 305, 305, 305, 343, 343, 343, 351, 351, 370, 376, 382, 387, +}; +static const unsigned char cache_bits50[392] = { +40, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 40, 15, 23, 28, +31, 34, 36, 38, 39, 41, 42, 43, 44, 45, 46, 47, 47, 49, 50, +51, 52, 53, 54, 55, 55, 57, 58, 59, 60, 61, 62, 63, 63, 65, +66, 67, 68, 69, 70, 71, 71, 40, 20, 33, 41, 48, 53, 57, 61, +64, 66, 69, 71, 73, 75, 76, 78, 80, 82, 85, 87, 89, 91, 92, +94, 96, 98, 101, 103, 105, 107, 108, 110, 112, 114, 117, 119, 121, 123, +124, 126, 128, 40, 23, 39, 51, 60, 67, 73, 79, 83, 87, 91, 94, +97, 100, 102, 105, 107, 111, 115, 118, 121, 124, 126, 129, 131, 135, 139, +142, 145, 148, 150, 153, 155, 159, 163, 166, 169, 172, 174, 177, 179, 35, +28, 49, 65, 78, 89, 99, 107, 114, 120, 126, 132, 136, 141, 145, 149, +153, 159, 165, 171, 176, 180, 185, 189, 192, 199, 205, 211, 216, 220, 225, +229, 232, 239, 245, 251, 21, 33, 58, 79, 97, 112, 125, 137, 148, 157, +166, 174, 182, 189, 195, 201, 207, 217, 227, 235, 243, 251, 17, 35, 63, +86, 106, 123, 139, 152, 165, 177, 187, 197, 206, 214, 222, 230, 237, 250, +25, 31, 55, 75, 91, 105, 117, 128, 138, 146, 154, 161, 168, 174, 180, +185, 190, 200, 208, 215, 222, 229, 235, 240, 245, 255, 16, 36, 65, 89, +110, 128, 144, 159, 173, 185, 196, 207, 217, 226, 234, 242, 250, 11, 41, +74, 103, 128, 151, 172, 191, 209, 225, 241, 255, 9, 43, 79, 110, 138, +163, 186, 207, 227, 246, 12, 39, 71, 99, 123, 144, 164, 182, 198, 214, +228, 241, 253, 9, 44, 81, 113, 142, 168, 192, 214, 235, 255, 7, 49, +90, 127, 160, 191, 220, 247, 6, 51, 95, 134, 170, 203, 234, 7, 47, +87, 123, 155, 184, 212, 237, 6, 52, 97, 137, 174, 208, 240, 5, 57, +106, 151, 192, 231, 5, 59, 111, 158, 202, 243, 5, 55, 103, 147, 187, +224, 5, 60, 113, 161, 206, 248, 4, 65, 122, 175, 224, 4, 67, 127, +182, 234, }; +static const unsigned char cache_caps50[168] = { +224, 224, 224, 224, 224, 224, 224, 224, 160, 160, 160, 160, 185, 185, 185, +178, 178, 168, 134, 61, 37, 224, 224, 224, 224, 224, 224, 224, 224, 240, +240, 240, 240, 207, 207, 207, 198, 198, 183, 144, 66, 40, 160, 160, 160, +160, 160, 160, 160, 160, 185, 185, 185, 185, 193, 193, 193, 183, 183, 172, +138, 64, 38, 240, 240, 240, 240, 240, 240, 240, 240, 207, 207, 207, 207, +204, 204, 204, 193, 193, 180, 143, 66, 40, 185, 185, 185, 185, 185, 185, +185, 185, 193, 193, 193, 193, 193, 193, 193, 183, 183, 172, 138, 65, 39, +207, 207, 207, 207, 207, 207, 207, 207, 204, 204, 204, 204, 201, 201, 201, +188, 188, 176, 141, 66, 40, 193, 193, 193, 193, 193, 193, 193, 193, 193, +193, 193, 193, 194, 194, 194, 184, 184, 173, 139, 65, 39, 204, 204, 204, +204, 204, 204, 204, 204, 201, 201, 201, 201, 198, 198, 198, 187, 187, 175, +140, 66, 40, }; +#endif + +#ifndef FFT_TWIDDLES48000_960 +#define FFT_TWIDDLES48000_960 +static const kiss_twiddle_cpx fft_twiddles48000_960[480] = { +{1.0000000f, -0.0000000f}, {0.99991433f, -0.013089596f}, +{0.99965732f, -0.026176948f}, {0.99922904f, -0.039259816f}, +{0.99862953f, -0.052335956f}, {0.99785892f, -0.065403129f}, +{0.99691733f, -0.078459096f}, {0.99580493f, -0.091501619f}, +{0.99452190f, -0.10452846f}, {0.99306846f, -0.11753740f}, +{0.99144486f, -0.13052619f}, {0.98965139f, -0.14349262f}, +{0.98768834f, -0.15643447f}, {0.98555606f, -0.16934950f}, +{0.98325491f, -0.18223553f}, {0.98078528f, -0.19509032f}, +{0.97814760f, -0.20791169f}, {0.97534232f, -0.22069744f}, +{0.97236992f, -0.23344536f}, {0.96923091f, -0.24615329f}, +{0.96592583f, -0.25881905f}, {0.96245524f, -0.27144045f}, +{0.95881973f, -0.28401534f}, {0.95501994f, -0.29654157f}, +{0.95105652f, -0.30901699f}, {0.94693013f, -0.32143947f}, +{0.94264149f, -0.33380686f}, {0.93819134f, -0.34611706f}, +{0.93358043f, -0.35836795f}, {0.92880955f, -0.37055744f}, +{0.92387953f, -0.38268343f}, {0.91879121f, -0.39474386f}, +{0.91354546f, -0.40673664f}, {0.90814317f, -0.41865974f}, +{0.90258528f, -0.43051110f}, {0.89687274f, -0.44228869f}, +{0.89100652f, -0.45399050f}, {0.88498764f, -0.46561452f}, +{0.87881711f, -0.47715876f}, {0.87249601f, -0.48862124f}, +{0.86602540f, -0.50000000f}, {0.85940641f, -0.51129309f}, +{0.85264016f, -0.52249856f}, {0.84572782f, -0.53361452f}, +{0.83867057f, -0.54463904f}, {0.83146961f, -0.55557023f}, +{0.82412619f, -0.56640624f}, {0.81664156f, -0.57714519f}, +{0.80901699f, -0.58778525f}, {0.80125381f, -0.59832460f}, +{0.79335334f, -0.60876143f}, {0.78531693f, -0.61909395f}, +{0.77714596f, -0.62932039f}, {0.76884183f, -0.63943900f}, +{0.76040597f, -0.64944805f}, {0.75183981f, -0.65934582f}, +{0.74314483f, -0.66913061f}, {0.73432251f, -0.67880075f}, +{0.72537437f, -0.68835458f}, {0.71630194f, -0.69779046f}, +{0.70710678f, -0.70710678f}, {0.69779046f, -0.71630194f}, +{0.68835458f, -0.72537437f}, {0.67880075f, -0.73432251f}, +{0.66913061f, -0.74314483f}, {0.65934582f, -0.75183981f}, +{0.64944805f, -0.76040597f}, {0.63943900f, -0.76884183f}, +{0.62932039f, -0.77714596f}, {0.61909395f, -0.78531693f}, +{0.60876143f, -0.79335334f}, {0.59832460f, -0.80125381f}, +{0.58778525f, -0.80901699f}, {0.57714519f, -0.81664156f}, +{0.56640624f, -0.82412619f}, {0.55557023f, -0.83146961f}, +{0.54463904f, -0.83867057f}, {0.53361452f, -0.84572782f}, +{0.52249856f, -0.85264016f}, {0.51129309f, -0.85940641f}, +{0.50000000f, -0.86602540f}, {0.48862124f, -0.87249601f}, +{0.47715876f, -0.87881711f}, {0.46561452f, -0.88498764f}, +{0.45399050f, -0.89100652f}, {0.44228869f, -0.89687274f}, +{0.43051110f, -0.90258528f}, {0.41865974f, -0.90814317f}, +{0.40673664f, -0.91354546f}, {0.39474386f, -0.91879121f}, +{0.38268343f, -0.92387953f}, {0.37055744f, -0.92880955f}, +{0.35836795f, -0.93358043f}, {0.34611706f, -0.93819134f}, +{0.33380686f, -0.94264149f}, {0.32143947f, -0.94693013f}, +{0.30901699f, -0.95105652f}, {0.29654157f, -0.95501994f}, +{0.28401534f, -0.95881973f}, {0.27144045f, -0.96245524f}, +{0.25881905f, -0.96592583f}, {0.24615329f, -0.96923091f}, +{0.23344536f, -0.97236992f}, {0.22069744f, -0.97534232f}, +{0.20791169f, -0.97814760f}, {0.19509032f, -0.98078528f}, +{0.18223553f, -0.98325491f}, {0.16934950f, -0.98555606f}, +{0.15643447f, -0.98768834f}, {0.14349262f, -0.98965139f}, +{0.13052619f, -0.99144486f}, {0.11753740f, -0.99306846f}, +{0.10452846f, -0.99452190f}, {0.091501619f, -0.99580493f}, +{0.078459096f, -0.99691733f}, {0.065403129f, -0.99785892f}, +{0.052335956f, -0.99862953f}, {0.039259816f, -0.99922904f}, +{0.026176948f, -0.99965732f}, {0.013089596f, -0.99991433f}, +{6.1230318e-17f, -1.0000000f}, {-0.013089596f, -0.99991433f}, +{-0.026176948f, -0.99965732f}, {-0.039259816f, -0.99922904f}, +{-0.052335956f, -0.99862953f}, {-0.065403129f, -0.99785892f}, +{-0.078459096f, -0.99691733f}, {-0.091501619f, -0.99580493f}, +{-0.10452846f, -0.99452190f}, {-0.11753740f, -0.99306846f}, +{-0.13052619f, -0.99144486f}, {-0.14349262f, -0.98965139f}, +{-0.15643447f, -0.98768834f}, {-0.16934950f, -0.98555606f}, +{-0.18223553f, -0.98325491f}, {-0.19509032f, -0.98078528f}, +{-0.20791169f, -0.97814760f}, {-0.22069744f, -0.97534232f}, +{-0.23344536f, -0.97236992f}, {-0.24615329f, -0.96923091f}, +{-0.25881905f, -0.96592583f}, {-0.27144045f, -0.96245524f}, +{-0.28401534f, -0.95881973f}, {-0.29654157f, -0.95501994f}, +{-0.30901699f, -0.95105652f}, {-0.32143947f, -0.94693013f}, +{-0.33380686f, -0.94264149f}, {-0.34611706f, -0.93819134f}, +{-0.35836795f, -0.93358043f}, {-0.37055744f, -0.92880955f}, +{-0.38268343f, -0.92387953f}, {-0.39474386f, -0.91879121f}, +{-0.40673664f, -0.91354546f}, {-0.41865974f, -0.90814317f}, +{-0.43051110f, -0.90258528f}, {-0.44228869f, -0.89687274f}, +{-0.45399050f, -0.89100652f}, {-0.46561452f, -0.88498764f}, +{-0.47715876f, -0.87881711f}, {-0.48862124f, -0.87249601f}, +{-0.50000000f, -0.86602540f}, {-0.51129309f, -0.85940641f}, +{-0.52249856f, -0.85264016f}, {-0.53361452f, -0.84572782f}, +{-0.54463904f, -0.83867057f}, {-0.55557023f, -0.83146961f}, +{-0.56640624f, -0.82412619f}, {-0.57714519f, -0.81664156f}, +{-0.58778525f, -0.80901699f}, {-0.59832460f, -0.80125381f}, +{-0.60876143f, -0.79335334f}, {-0.61909395f, -0.78531693f}, +{-0.62932039f, -0.77714596f}, {-0.63943900f, -0.76884183f}, +{-0.64944805f, -0.76040597f}, {-0.65934582f, -0.75183981f}, +{-0.66913061f, -0.74314483f}, {-0.67880075f, -0.73432251f}, +{-0.68835458f, -0.72537437f}, {-0.69779046f, -0.71630194f}, +{-0.70710678f, -0.70710678f}, {-0.71630194f, -0.69779046f}, +{-0.72537437f, -0.68835458f}, {-0.73432251f, -0.67880075f}, +{-0.74314483f, -0.66913061f}, {-0.75183981f, -0.65934582f}, +{-0.76040597f, -0.64944805f}, {-0.76884183f, -0.63943900f}, +{-0.77714596f, -0.62932039f}, {-0.78531693f, -0.61909395f}, +{-0.79335334f, -0.60876143f}, {-0.80125381f, -0.59832460f}, +{-0.80901699f, -0.58778525f}, {-0.81664156f, -0.57714519f}, +{-0.82412619f, -0.56640624f}, {-0.83146961f, -0.55557023f}, +{-0.83867057f, -0.54463904f}, {-0.84572782f, -0.53361452f}, +{-0.85264016f, -0.52249856f}, {-0.85940641f, -0.51129309f}, +{-0.86602540f, -0.50000000f}, {-0.87249601f, -0.48862124f}, +{-0.87881711f, -0.47715876f}, {-0.88498764f, -0.46561452f}, +{-0.89100652f, -0.45399050f}, {-0.89687274f, -0.44228869f}, +{-0.90258528f, -0.43051110f}, {-0.90814317f, -0.41865974f}, +{-0.91354546f, -0.40673664f}, {-0.91879121f, -0.39474386f}, +{-0.92387953f, -0.38268343f}, {-0.92880955f, -0.37055744f}, +{-0.93358043f, -0.35836795f}, {-0.93819134f, -0.34611706f}, +{-0.94264149f, -0.33380686f}, {-0.94693013f, -0.32143947f}, +{-0.95105652f, -0.30901699f}, {-0.95501994f, -0.29654157f}, +{-0.95881973f, -0.28401534f}, {-0.96245524f, -0.27144045f}, +{-0.96592583f, -0.25881905f}, {-0.96923091f, -0.24615329f}, +{-0.97236992f, -0.23344536f}, {-0.97534232f, -0.22069744f}, +{-0.97814760f, -0.20791169f}, {-0.98078528f, -0.19509032f}, +{-0.98325491f, -0.18223553f}, {-0.98555606f, -0.16934950f}, +{-0.98768834f, -0.15643447f}, {-0.98965139f, -0.14349262f}, +{-0.99144486f, -0.13052619f}, {-0.99306846f, -0.11753740f}, +{-0.99452190f, -0.10452846f}, {-0.99580493f, -0.091501619f}, +{-0.99691733f, -0.078459096f}, {-0.99785892f, -0.065403129f}, +{-0.99862953f, -0.052335956f}, {-0.99922904f, -0.039259816f}, +{-0.99965732f, -0.026176948f}, {-0.99991433f, -0.013089596f}, +{-1.0000000f, -1.2246064e-16f}, {-0.99991433f, 0.013089596f}, +{-0.99965732f, 0.026176948f}, {-0.99922904f, 0.039259816f}, +{-0.99862953f, 0.052335956f}, {-0.99785892f, 0.065403129f}, +{-0.99691733f, 0.078459096f}, {-0.99580493f, 0.091501619f}, +{-0.99452190f, 0.10452846f}, {-0.99306846f, 0.11753740f}, +{-0.99144486f, 0.13052619f}, {-0.98965139f, 0.14349262f}, +{-0.98768834f, 0.15643447f}, {-0.98555606f, 0.16934950f}, +{-0.98325491f, 0.18223553f}, {-0.98078528f, 0.19509032f}, +{-0.97814760f, 0.20791169f}, {-0.97534232f, 0.22069744f}, +{-0.97236992f, 0.23344536f}, {-0.96923091f, 0.24615329f}, +{-0.96592583f, 0.25881905f}, {-0.96245524f, 0.27144045f}, +{-0.95881973f, 0.28401534f}, {-0.95501994f, 0.29654157f}, +{-0.95105652f, 0.30901699f}, {-0.94693013f, 0.32143947f}, +{-0.94264149f, 0.33380686f}, {-0.93819134f, 0.34611706f}, +{-0.93358043f, 0.35836795f}, {-0.92880955f, 0.37055744f}, +{-0.92387953f, 0.38268343f}, {-0.91879121f, 0.39474386f}, +{-0.91354546f, 0.40673664f}, {-0.90814317f, 0.41865974f}, +{-0.90258528f, 0.43051110f}, {-0.89687274f, 0.44228869f}, +{-0.89100652f, 0.45399050f}, {-0.88498764f, 0.46561452f}, +{-0.87881711f, 0.47715876f}, {-0.87249601f, 0.48862124f}, +{-0.86602540f, 0.50000000f}, {-0.85940641f, 0.51129309f}, +{-0.85264016f, 0.52249856f}, {-0.84572782f, 0.53361452f}, +{-0.83867057f, 0.54463904f}, {-0.83146961f, 0.55557023f}, +{-0.82412619f, 0.56640624f}, {-0.81664156f, 0.57714519f}, +{-0.80901699f, 0.58778525f}, {-0.80125381f, 0.59832460f}, +{-0.79335334f, 0.60876143f}, {-0.78531693f, 0.61909395f}, +{-0.77714596f, 0.62932039f}, {-0.76884183f, 0.63943900f}, +{-0.76040597f, 0.64944805f}, {-0.75183981f, 0.65934582f}, +{-0.74314483f, 0.66913061f}, {-0.73432251f, 0.67880075f}, +{-0.72537437f, 0.68835458f}, {-0.71630194f, 0.69779046f}, +{-0.70710678f, 0.70710678f}, {-0.69779046f, 0.71630194f}, +{-0.68835458f, 0.72537437f}, {-0.67880075f, 0.73432251f}, +{-0.66913061f, 0.74314483f}, {-0.65934582f, 0.75183981f}, +{-0.64944805f, 0.76040597f}, {-0.63943900f, 0.76884183f}, +{-0.62932039f, 0.77714596f}, {-0.61909395f, 0.78531693f}, +{-0.60876143f, 0.79335334f}, {-0.59832460f, 0.80125381f}, +{-0.58778525f, 0.80901699f}, {-0.57714519f, 0.81664156f}, +{-0.56640624f, 0.82412619f}, {-0.55557023f, 0.83146961f}, +{-0.54463904f, 0.83867057f}, {-0.53361452f, 0.84572782f}, +{-0.52249856f, 0.85264016f}, {-0.51129309f, 0.85940641f}, +{-0.50000000f, 0.86602540f}, {-0.48862124f, 0.87249601f}, +{-0.47715876f, 0.87881711f}, {-0.46561452f, 0.88498764f}, +{-0.45399050f, 0.89100652f}, {-0.44228869f, 0.89687274f}, +{-0.43051110f, 0.90258528f}, {-0.41865974f, 0.90814317f}, +{-0.40673664f, 0.91354546f}, {-0.39474386f, 0.91879121f}, +{-0.38268343f, 0.92387953f}, {-0.37055744f, 0.92880955f}, +{-0.35836795f, 0.93358043f}, {-0.34611706f, 0.93819134f}, +{-0.33380686f, 0.94264149f}, {-0.32143947f, 0.94693013f}, +{-0.30901699f, 0.95105652f}, {-0.29654157f, 0.95501994f}, +{-0.28401534f, 0.95881973f}, {-0.27144045f, 0.96245524f}, +{-0.25881905f, 0.96592583f}, {-0.24615329f, 0.96923091f}, +{-0.23344536f, 0.97236992f}, {-0.22069744f, 0.97534232f}, +{-0.20791169f, 0.97814760f}, {-0.19509032f, 0.98078528f}, +{-0.18223553f, 0.98325491f}, {-0.16934950f, 0.98555606f}, +{-0.15643447f, 0.98768834f}, {-0.14349262f, 0.98965139f}, +{-0.13052619f, 0.99144486f}, {-0.11753740f, 0.99306846f}, +{-0.10452846f, 0.99452190f}, {-0.091501619f, 0.99580493f}, +{-0.078459096f, 0.99691733f}, {-0.065403129f, 0.99785892f}, +{-0.052335956f, 0.99862953f}, {-0.039259816f, 0.99922904f}, +{-0.026176948f, 0.99965732f}, {-0.013089596f, 0.99991433f}, +{-1.8369095e-16f, 1.0000000f}, {0.013089596f, 0.99991433f}, +{0.026176948f, 0.99965732f}, {0.039259816f, 0.99922904f}, +{0.052335956f, 0.99862953f}, {0.065403129f, 0.99785892f}, +{0.078459096f, 0.99691733f}, {0.091501619f, 0.99580493f}, +{0.10452846f, 0.99452190f}, {0.11753740f, 0.99306846f}, +{0.13052619f, 0.99144486f}, {0.14349262f, 0.98965139f}, +{0.15643447f, 0.98768834f}, {0.16934950f, 0.98555606f}, +{0.18223553f, 0.98325491f}, {0.19509032f, 0.98078528f}, +{0.20791169f, 0.97814760f}, {0.22069744f, 0.97534232f}, +{0.23344536f, 0.97236992f}, {0.24615329f, 0.96923091f}, +{0.25881905f, 0.96592583f}, {0.27144045f, 0.96245524f}, +{0.28401534f, 0.95881973f}, {0.29654157f, 0.95501994f}, +{0.30901699f, 0.95105652f}, {0.32143947f, 0.94693013f}, +{0.33380686f, 0.94264149f}, {0.34611706f, 0.93819134f}, +{0.35836795f, 0.93358043f}, {0.37055744f, 0.92880955f}, +{0.38268343f, 0.92387953f}, {0.39474386f, 0.91879121f}, +{0.40673664f, 0.91354546f}, {0.41865974f, 0.90814317f}, +{0.43051110f, 0.90258528f}, {0.44228869f, 0.89687274f}, +{0.45399050f, 0.89100652f}, {0.46561452f, 0.88498764f}, +{0.47715876f, 0.87881711f}, {0.48862124f, 0.87249601f}, +{0.50000000f, 0.86602540f}, {0.51129309f, 0.85940641f}, +{0.52249856f, 0.85264016f}, {0.53361452f, 0.84572782f}, +{0.54463904f, 0.83867057f}, {0.55557023f, 0.83146961f}, +{0.56640624f, 0.82412619f}, {0.57714519f, 0.81664156f}, +{0.58778525f, 0.80901699f}, {0.59832460f, 0.80125381f}, +{0.60876143f, 0.79335334f}, {0.61909395f, 0.78531693f}, +{0.62932039f, 0.77714596f}, {0.63943900f, 0.76884183f}, +{0.64944805f, 0.76040597f}, {0.65934582f, 0.75183981f}, +{0.66913061f, 0.74314483f}, {0.67880075f, 0.73432251f}, +{0.68835458f, 0.72537437f}, {0.69779046f, 0.71630194f}, +{0.70710678f, 0.70710678f}, {0.71630194f, 0.69779046f}, +{0.72537437f, 0.68835458f}, {0.73432251f, 0.67880075f}, +{0.74314483f, 0.66913061f}, {0.75183981f, 0.65934582f}, +{0.76040597f, 0.64944805f}, {0.76884183f, 0.63943900f}, +{0.77714596f, 0.62932039f}, {0.78531693f, 0.61909395f}, +{0.79335334f, 0.60876143f}, {0.80125381f, 0.59832460f}, +{0.80901699f, 0.58778525f}, {0.81664156f, 0.57714519f}, +{0.82412619f, 0.56640624f}, {0.83146961f, 0.55557023f}, +{0.83867057f, 0.54463904f}, {0.84572782f, 0.53361452f}, +{0.85264016f, 0.52249856f}, {0.85940641f, 0.51129309f}, +{0.86602540f, 0.50000000f}, {0.87249601f, 0.48862124f}, +{0.87881711f, 0.47715876f}, {0.88498764f, 0.46561452f}, +{0.89100652f, 0.45399050f}, {0.89687274f, 0.44228869f}, +{0.90258528f, 0.43051110f}, {0.90814317f, 0.41865974f}, +{0.91354546f, 0.40673664f}, {0.91879121f, 0.39474386f}, +{0.92387953f, 0.38268343f}, {0.92880955f, 0.37055744f}, +{0.93358043f, 0.35836795f}, {0.93819134f, 0.34611706f}, +{0.94264149f, 0.33380686f}, {0.94693013f, 0.32143947f}, +{0.95105652f, 0.30901699f}, {0.95501994f, 0.29654157f}, +{0.95881973f, 0.28401534f}, {0.96245524f, 0.27144045f}, +{0.96592583f, 0.25881905f}, {0.96923091f, 0.24615329f}, +{0.97236992f, 0.23344536f}, {0.97534232f, 0.22069744f}, +{0.97814760f, 0.20791169f}, {0.98078528f, 0.19509032f}, +{0.98325491f, 0.18223553f}, {0.98555606f, 0.16934950f}, +{0.98768834f, 0.15643447f}, {0.98965139f, 0.14349262f}, +{0.99144486f, 0.13052619f}, {0.99306846f, 0.11753740f}, +{0.99452190f, 0.10452846f}, {0.99580493f, 0.091501619f}, +{0.99691733f, 0.078459096f}, {0.99785892f, 0.065403129f}, +{0.99862953f, 0.052335956f}, {0.99922904f, 0.039259816f}, +{0.99965732f, 0.026176948f}, {0.99991433f, 0.013089596f}, +}; +#ifndef FFT_BITREV480 +#define FFT_BITREV480 +static const opus_int16 fft_bitrev480[480] = { +0, 96, 192, 288, 384, 32, 128, 224, 320, 416, 64, 160, 256, 352, 448, +8, 104, 200, 296, 392, 40, 136, 232, 328, 424, 72, 168, 264, 360, 456, +16, 112, 208, 304, 400, 48, 144, 240, 336, 432, 80, 176, 272, 368, 464, +24, 120, 216, 312, 408, 56, 152, 248, 344, 440, 88, 184, 280, 376, 472, +4, 100, 196, 292, 388, 36, 132, 228, 324, 420, 68, 164, 260, 356, 452, +12, 108, 204, 300, 396, 44, 140, 236, 332, 428, 76, 172, 268, 364, 460, +20, 116, 212, 308, 404, 52, 148, 244, 340, 436, 84, 180, 276, 372, 468, +28, 124, 220, 316, 412, 60, 156, 252, 348, 444, 92, 188, 284, 380, 476, +1, 97, 193, 289, 385, 33, 129, 225, 321, 417, 65, 161, 257, 353, 449, +9, 105, 201, 297, 393, 41, 137, 233, 329, 425, 73, 169, 265, 361, 457, +17, 113, 209, 305, 401, 49, 145, 241, 337, 433, 81, 177, 273, 369, 465, +25, 121, 217, 313, 409, 57, 153, 249, 345, 441, 89, 185, 281, 377, 473, +5, 101, 197, 293, 389, 37, 133, 229, 325, 421, 69, 165, 261, 357, 453, +13, 109, 205, 301, 397, 45, 141, 237, 333, 429, 77, 173, 269, 365, 461, +21, 117, 213, 309, 405, 53, 149, 245, 341, 437, 85, 181, 277, 373, 469, +29, 125, 221, 317, 413, 61, 157, 253, 349, 445, 93, 189, 285, 381, 477, +2, 98, 194, 290, 386, 34, 130, 226, 322, 418, 66, 162, 258, 354, 450, +10, 106, 202, 298, 394, 42, 138, 234, 330, 426, 74, 170, 266, 362, 458, +18, 114, 210, 306, 402, 50, 146, 242, 338, 434, 82, 178, 274, 370, 466, +26, 122, 218, 314, 410, 58, 154, 250, 346, 442, 90, 186, 282, 378, 474, +6, 102, 198, 294, 390, 38, 134, 230, 326, 422, 70, 166, 262, 358, 454, +14, 110, 206, 302, 398, 46, 142, 238, 334, 430, 78, 174, 270, 366, 462, +22, 118, 214, 310, 406, 54, 150, 246, 342, 438, 86, 182, 278, 374, 470, +30, 126, 222, 318, 414, 62, 158, 254, 350, 446, 94, 190, 286, 382, 478, +3, 99, 195, 291, 387, 35, 131, 227, 323, 419, 67, 163, 259, 355, 451, +11, 107, 203, 299, 395, 43, 139, 235, 331, 427, 75, 171, 267, 363, 459, +19, 115, 211, 307, 403, 51, 147, 243, 339, 435, 83, 179, 275, 371, 467, +27, 123, 219, 315, 411, 59, 155, 251, 347, 443, 91, 187, 283, 379, 475, +7, 103, 199, 295, 391, 39, 135, 231, 327, 423, 71, 167, 263, 359, 455, +15, 111, 207, 303, 399, 47, 143, 239, 335, 431, 79, 175, 271, 367, 463, +23, 119, 215, 311, 407, 55, 151, 247, 343, 439, 87, 183, 279, 375, 471, +31, 127, 223, 319, 415, 63, 159, 255, 351, 447, 95, 191, 287, 383, 479, +}; +#endif + +#ifndef FFT_BITREV240 +#define FFT_BITREV240 +static const opus_int16 fft_bitrev240[240] = { +0, 48, 96, 144, 192, 16, 64, 112, 160, 208, 32, 80, 128, 176, 224, +4, 52, 100, 148, 196, 20, 68, 116, 164, 212, 36, 84, 132, 180, 228, +8, 56, 104, 152, 200, 24, 72, 120, 168, 216, 40, 88, 136, 184, 232, +12, 60, 108, 156, 204, 28, 76, 124, 172, 220, 44, 92, 140, 188, 236, +1, 49, 97, 145, 193, 17, 65, 113, 161, 209, 33, 81, 129, 177, 225, +5, 53, 101, 149, 197, 21, 69, 117, 165, 213, 37, 85, 133, 181, 229, +9, 57, 105, 153, 201, 25, 73, 121, 169, 217, 41, 89, 137, 185, 233, +13, 61, 109, 157, 205, 29, 77, 125, 173, 221, 45, 93, 141, 189, 237, +2, 50, 98, 146, 194, 18, 66, 114, 162, 210, 34, 82, 130, 178, 226, +6, 54, 102, 150, 198, 22, 70, 118, 166, 214, 38, 86, 134, 182, 230, +10, 58, 106, 154, 202, 26, 74, 122, 170, 218, 42, 90, 138, 186, 234, +14, 62, 110, 158, 206, 30, 78, 126, 174, 222, 46, 94, 142, 190, 238, +3, 51, 99, 147, 195, 19, 67, 115, 163, 211, 35, 83, 131, 179, 227, +7, 55, 103, 151, 199, 23, 71, 119, 167, 215, 39, 87, 135, 183, 231, +11, 59, 107, 155, 203, 27, 75, 123, 171, 219, 43, 91, 139, 187, 235, +15, 63, 111, 159, 207, 31, 79, 127, 175, 223, 47, 95, 143, 191, 239, +}; +#endif + +#ifndef FFT_BITREV120 +#define FFT_BITREV120 +static const opus_int16 fft_bitrev120[120] = { +0, 24, 48, 72, 96, 8, 32, 56, 80, 104, 16, 40, 64, 88, 112, +4, 28, 52, 76, 100, 12, 36, 60, 84, 108, 20, 44, 68, 92, 116, +1, 25, 49, 73, 97, 9, 33, 57, 81, 105, 17, 41, 65, 89, 113, +5, 29, 53, 77, 101, 13, 37, 61, 85, 109, 21, 45, 69, 93, 117, +2, 26, 50, 74, 98, 10, 34, 58, 82, 106, 18, 42, 66, 90, 114, +6, 30, 54, 78, 102, 14, 38, 62, 86, 110, 22, 46, 70, 94, 118, +3, 27, 51, 75, 99, 11, 35, 59, 83, 107, 19, 43, 67, 91, 115, +7, 31, 55, 79, 103, 15, 39, 63, 87, 111, 23, 47, 71, 95, 119, +}; +#endif + +#ifndef FFT_BITREV60 +#define FFT_BITREV60 +static const opus_int16 fft_bitrev60[60] = { +0, 12, 24, 36, 48, 4, 16, 28, 40, 52, 8, 20, 32, 44, 56, +1, 13, 25, 37, 49, 5, 17, 29, 41, 53, 9, 21, 33, 45, 57, +2, 14, 26, 38, 50, 6, 18, 30, 42, 54, 10, 22, 34, 46, 58, +3, 15, 27, 39, 51, 7, 19, 31, 43, 55, 11, 23, 35, 47, 59, +}; +#endif + +#ifndef FFT_STATE48000_960_0 +#define FFT_STATE48000_960_0 +static const kiss_fft_state fft_state48000_960_0 = { +480, /* nfft */ +0.002083333f, /* scale */ +-1, /* shift */ +{5, 96, 3, 32, 4, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev480, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_480, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_1 +#define FFT_STATE48000_960_1 +static const kiss_fft_state fft_state48000_960_1 = { +240, /* nfft */ +0.004166667f, /* scale */ +1, /* shift */ +{5, 48, 3, 16, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev240, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_240, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_2 +#define FFT_STATE48000_960_2 +static const kiss_fft_state fft_state48000_960_2 = { +120, /* nfft */ +0.008333333f, /* scale */ +2, /* shift */ +{5, 24, 3, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev120, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_120, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_3 +#define FFT_STATE48000_960_3 +static const kiss_fft_state fft_state48000_960_3 = { +60, /* nfft */ +0.016666667f, /* scale */ +3, /* shift */ +{5, 12, 3, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev60, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_60, +#else +NULL, +#endif +}; +#endif + +#endif + +#ifndef MDCT_TWIDDLES960 +#define MDCT_TWIDDLES960 +static const opus_val16 mdct_twiddles960[1800] = { +0.99999994f, 0.99999321f, 0.99997580f, 0.99994773f, 0.99990886f, +0.99985933f, 0.99979913f, 0.99972820f, 0.99964654f, 0.99955416f, +0.99945110f, 0.99933738f, 0.99921292f, 0.99907774f, 0.99893188f, +0.99877530f, 0.99860805f, 0.99843007f, 0.99824142f, 0.99804211f, +0.99783206f, 0.99761140f, 0.99737996f, 0.99713790f, 0.99688518f, +0.99662173f, 0.99634761f, 0.99606287f, 0.99576741f, 0.99546129f, +0.99514455f, 0.99481714f, 0.99447906f, 0.99413031f, 0.99377096f, +0.99340093f, 0.99302030f, 0.99262899f, 0.99222708f, 0.99181455f, +0.99139136f, 0.99095762f, 0.99051321f, 0.99005818f, 0.98959261f, +0.98911643f, 0.98862964f, 0.98813224f, 0.98762429f, 0.98710573f, +0.98657662f, 0.98603696f, 0.98548669f, 0.98492593f, 0.98435456f, +0.98377270f, 0.98318028f, 0.98257732f, 0.98196387f, 0.98133987f, +0.98070538f, 0.98006040f, 0.97940493f, 0.97873890f, 0.97806245f, +0.97737551f, 0.97667813f, 0.97597027f, 0.97525197f, 0.97452319f, +0.97378403f, 0.97303438f, 0.97227436f, 0.97150391f, 0.97072303f, +0.96993178f, 0.96913016f, 0.96831810f, 0.96749574f, 0.96666300f, +0.96581990f, 0.96496642f, 0.96410263f, 0.96322852f, 0.96234411f, +0.96144938f, 0.96054435f, 0.95962906f, 0.95870346f, 0.95776761f, +0.95682150f, 0.95586514f, 0.95489854f, 0.95392174f, 0.95293468f, +0.95193744f, 0.95093000f, 0.94991243f, 0.94888461f, 0.94784665f, +0.94679856f, 0.94574034f, 0.94467193f, 0.94359344f, 0.94250488f, +0.94140619f, 0.94029742f, 0.93917859f, 0.93804967f, 0.93691075f, +0.93576175f, 0.93460274f, 0.93343377f, 0.93225473f, 0.93106574f, +0.92986679f, 0.92865789f, 0.92743903f, 0.92621022f, 0.92497152f, +0.92372292f, 0.92246443f, 0.92119598f, 0.91991776f, 0.91862965f, +0.91733170f, 0.91602397f, 0.91470635f, 0.91337901f, 0.91204184f, +0.91069490f, 0.90933824f, 0.90797186f, 0.90659571f, 0.90520984f, +0.90381432f, 0.90240908f, 0.90099424f, 0.89956969f, 0.89813554f, +0.89669174f, 0.89523834f, 0.89377540f, 0.89230281f, 0.89082074f, +0.88932908f, 0.88782793f, 0.88631725f, 0.88479710f, 0.88326746f, +0.88172835f, 0.88017982f, 0.87862182f, 0.87705445f, 0.87547767f, +0.87389153f, 0.87229604f, 0.87069118f, 0.86907703f, 0.86745358f, +0.86582077f, 0.86417878f, 0.86252749f, 0.86086690f, 0.85919720f, +0.85751826f, 0.85583007f, 0.85413277f, 0.85242635f, 0.85071075f, +0.84898609f, 0.84725231f, 0.84550947f, 0.84375757f, 0.84199661f, +0.84022665f, 0.83844769f, 0.83665979f, 0.83486289f, 0.83305705f, +0.83124226f, 0.82941860f, 0.82758605f, 0.82574469f, 0.82389444f, +0.82203537f, 0.82016748f, 0.81829083f, 0.81640542f, 0.81451124f, +0.81260836f, 0.81069672f, 0.80877650f, 0.80684757f, 0.80490994f, +0.80296379f, 0.80100900f, 0.79904562f, 0.79707366f, 0.79509324f, +0.79310423f, 0.79110676f, 0.78910083f, 0.78708643f, 0.78506362f, +0.78303236f, 0.78099275f, 0.77894479f, 0.77688843f, 0.77482378f, +0.77275085f, 0.77066964f, 0.76858020f, 0.76648247f, 0.76437658f, +0.76226246f, 0.76014024f, 0.75800985f, 0.75587130f, 0.75372469f, +0.75157005f, 0.74940729f, 0.74723655f, 0.74505776f, 0.74287105f, +0.74067634f, 0.73847371f, 0.73626316f, 0.73404479f, 0.73181850f, +0.72958434f, 0.72734243f, 0.72509271f, 0.72283524f, 0.72057003f, +0.71829706f, 0.71601641f, 0.71372813f, 0.71143216f, 0.70912862f, +0.70681745f, 0.70449871f, 0.70217246f, 0.69983864f, 0.69749737f, +0.69514859f, 0.69279242f, 0.69042879f, 0.68805778f, 0.68567938f, +0.68329364f, 0.68090063f, 0.67850029f, 0.67609268f, 0.67367786f, +0.67125577f, 0.66882652f, 0.66639012f, 0.66394657f, 0.66149592f, +0.65903819f, 0.65657341f, 0.65410155f, 0.65162271f, 0.64913690f, +0.64664418f, 0.64414448f, 0.64163786f, 0.63912445f, 0.63660413f, +0.63407701f, 0.63154310f, 0.62900239f, 0.62645501f, 0.62390089f, +0.62134010f, 0.61877263f, 0.61619854f, 0.61361790f, 0.61103064f, +0.60843682f, 0.60583651f, 0.60322970f, 0.60061646f, 0.59799677f, +0.59537065f, 0.59273821f, 0.59009939f, 0.58745426f, 0.58480281f, +0.58214509f, 0.57948118f, 0.57681108f, 0.57413477f, 0.57145232f, +0.56876373f, 0.56606907f, 0.56336832f, 0.56066155f, 0.55794877f, +0.55523002f, 0.55250537f, 0.54977477f, 0.54703826f, 0.54429591f, +0.54154772f, 0.53879374f, 0.53603399f, 0.53326851f, 0.53049731f, +0.52772039f, 0.52493787f, 0.52214974f, 0.51935595f, 0.51655668f, +0.51375180f, 0.51094145f, 0.50812566f, 0.50530440f, 0.50247771f, +0.49964568f, 0.49680826f, 0.49396557f, 0.49111754f, 0.48826426f, +0.48540577f, 0.48254207f, 0.47967321f, 0.47679919f, 0.47392011f, +0.47103590f, 0.46814668f, 0.46525243f, 0.46235323f, 0.45944905f, +0.45653993f, 0.45362595f, 0.45070711f, 0.44778344f, 0.44485497f, +0.44192174f, 0.43898380f, 0.43604112f, 0.43309379f, 0.43014181f, +0.42718524f, 0.42422408f, 0.42125839f, 0.41828820f, 0.41531351f, +0.41233435f, 0.40935081f, 0.40636289f, 0.40337059f, 0.40037400f, +0.39737311f, 0.39436796f, 0.39135858f, 0.38834500f, 0.38532731f, +0.38230544f, 0.37927949f, 0.37624949f, 0.37321547f, 0.37017745f, +0.36713544f, 0.36408952f, 0.36103970f, 0.35798600f, 0.35492846f, +0.35186714f, 0.34880206f, 0.34573323f, 0.34266070f, 0.33958447f, +0.33650464f, 0.33342120f, 0.33033419f, 0.32724363f, 0.32414958f, +0.32105204f, 0.31795108f, 0.31484672f, 0.31173897f, 0.30862790f, +0.30551350f, 0.30239585f, 0.29927495f, 0.29615086f, 0.29302359f, +0.28989318f, 0.28675964f, 0.28362307f, 0.28048345f, 0.27734083f, +0.27419522f, 0.27104670f, 0.26789525f, 0.26474094f, 0.26158381f, +0.25842386f, 0.25526115f, 0.25209570f, 0.24892756f, 0.24575676f, +0.24258332f, 0.23940729f, 0.23622867f, 0.23304754f, 0.22986393f, +0.22667783f, 0.22348931f, 0.22029841f, 0.21710514f, 0.21390954f, +0.21071166f, 0.20751151f, 0.20430915f, 0.20110460f, 0.19789790f, +0.19468907f, 0.19147816f, 0.18826519f, 0.18505022f, 0.18183327f, +0.17861435f, 0.17539354f, 0.17217083f, 0.16894630f, 0.16571994f, +0.16249183f, 0.15926196f, 0.15603039f, 0.15279715f, 0.14956227f, +0.14632578f, 0.14308774f, 0.13984816f, 0.13660708f, 0.13336454f, +0.13012058f, 0.12687522f, 0.12362850f, 0.12038045f, 0.11713112f, +0.11388054f, 0.11062872f, 0.10737573f, 0.10412160f, 0.10086634f, +0.097609997f, 0.094352618f, 0.091094226f, 0.087834857f, 0.084574550f, +0.081313334f, 0.078051247f, 0.074788325f, 0.071524605f, 0.068260118f, +0.064994894f, 0.061728980f, 0.058462404f, 0.055195201f, 0.051927410f, +0.048659060f, 0.045390189f, 0.042120833f, 0.038851023f, 0.035580799f, +0.032310195f, 0.029039243f, 0.025767982f, 0.022496443f, 0.019224664f, +0.015952680f, 0.012680525f, 0.0094082337f, 0.0061358409f, 0.0028633832f, +-0.00040910527f, -0.0036815894f, -0.0069540343f, -0.010226404f, -0.013498665f, +-0.016770782f, -0.020042717f, -0.023314439f, -0.026585912f, -0.029857099f, +-0.033127967f, -0.036398482f, -0.039668605f, -0.042938303f, -0.046207540f, +-0.049476285f, -0.052744497f, -0.056012146f, -0.059279196f, -0.062545612f, +-0.065811358f, -0.069076397f, -0.072340697f, -0.075604223f, -0.078866936f, +-0.082128808f, -0.085389800f, -0.088649876f, -0.091909006f, -0.095167145f, +-0.098424271f, -0.10168034f, -0.10493532f, -0.10818918f, -0.11144188f, +-0.11469338f, -0.11794366f, -0.12119267f, -0.12444039f, -0.12768677f, +-0.13093179f, -0.13417540f, -0.13741758f, -0.14065829f, -0.14389749f, +-0.14713514f, -0.15037122f, -0.15360570f, -0.15683852f, -0.16006967f, +-0.16329910f, -0.16652679f, -0.16975269f, -0.17297678f, -0.17619900f, +-0.17941935f, -0.18263777f, -0.18585424f, -0.18906870f, -0.19228116f, +-0.19549155f, -0.19869985f, -0.20190603f, -0.20511003f, -0.20831184f, +-0.21151142f, -0.21470875f, -0.21790376f, -0.22109644f, -0.22428675f, +-0.22747467f, -0.23066014f, -0.23384315f, -0.23702365f, -0.24020162f, +-0.24337701f, -0.24654980f, -0.24971995f, -0.25288740f, -0.25605217f, +-0.25921419f, -0.26237345f, -0.26552987f, -0.26868346f, -0.27183419f, +-0.27498198f, -0.27812684f, -0.28126872f, -0.28440759f, -0.28754342f, +-0.29067615f, -0.29380578f, -0.29693225f, -0.30005556f, -0.30317566f, +-0.30629250f, -0.30940607f, -0.31251630f, -0.31562322f, -0.31872672f, +-0.32182685f, -0.32492352f, -0.32801670f, -0.33110636f, -0.33419248f, +-0.33727503f, -0.34035397f, -0.34342924f, -0.34650084f, -0.34956875f, +-0.35263291f, -0.35569328f, -0.35874987f, -0.36180258f, -0.36485144f, +-0.36789638f, -0.37093741f, -0.37397444f, -0.37700745f, -0.38003644f, +-0.38306138f, -0.38608220f, -0.38909888f, -0.39211139f, -0.39511973f, +-0.39812380f, -0.40112361f, -0.40411916f, -0.40711036f, -0.41009718f, +-0.41307965f, -0.41605768f, -0.41903123f, -0.42200032f, -0.42496487f, +-0.42792490f, -0.43088034f, -0.43383113f, -0.43677729f, -0.43971881f, +-0.44265559f, -0.44558764f, -0.44851488f, -0.45143735f, -0.45435500f, +-0.45726776f, -0.46017563f, -0.46307856f, -0.46597654f, -0.46886954f, +-0.47175750f, -0.47464043f, -0.47751826f, -0.48039100f, -0.48325855f, +-0.48612097f, -0.48897815f, -0.49183011f, -0.49467680f, -0.49751821f, +-0.50035429f, -0.50318497f, -0.50601029f, -0.50883019f, -0.51164466f, +-0.51445359f, -0.51725709f, -0.52005500f, -0.52284735f, -0.52563411f, +-0.52841520f, -0.53119069f, -0.53396046f, -0.53672451f, -0.53948283f, +-0.54223537f, -0.54498214f, -0.54772300f, -0.55045801f, -0.55318713f, +-0.55591035f, -0.55862761f, -0.56133890f, -0.56404412f, -0.56674337f, +-0.56943649f, -0.57212353f, -0.57480448f, -0.57747924f, -0.58014780f, +-0.58281022f, -0.58546633f, -0.58811617f, -0.59075975f, -0.59339696f, +-0.59602785f, -0.59865236f, -0.60127044f, -0.60388207f, -0.60648727f, +-0.60908598f, -0.61167812f, -0.61426371f, -0.61684275f, -0.61941516f, +-0.62198097f, -0.62454009f, -0.62709254f, -0.62963831f, -0.63217729f, +-0.63470948f, -0.63723493f, -0.63975352f, -0.64226526f, -0.64477009f, +-0.64726806f, -0.64975911f, -0.65224314f, -0.65472025f, -0.65719032f, +-0.65965337f, -0.66210932f, -0.66455823f, -0.66700000f, -0.66943461f, +-0.67186207f, -0.67428231f, -0.67669535f, -0.67910111f, -0.68149966f, +-0.68389088f, -0.68627477f, -0.68865126f, -0.69102043f, -0.69338220f, +-0.69573659f, -0.69808346f, -0.70042288f, -0.70275480f, -0.70507920f, +-0.70739603f, -0.70970529f, -0.71200693f, -0.71430099f, -0.71658736f, +-0.71886611f, -0.72113711f, -0.72340041f, -0.72565591f, -0.72790372f, +-0.73014367f, -0.73237586f, -0.73460019f, -0.73681659f, -0.73902518f, +-0.74122584f, -0.74341851f, -0.74560326f, -0.74778003f, -0.74994880f, +-0.75210953f, -0.75426215f, -0.75640678f, -0.75854325f, -0.76067162f, +-0.76279181f, -0.76490390f, -0.76700771f, -0.76910341f, -0.77119076f, +-0.77326995f, -0.77534080f, -0.77740335f, -0.77945763f, -0.78150350f, +-0.78354102f, -0.78557014f, -0.78759086f, -0.78960317f, -0.79160696f, +-0.79360235f, -0.79558921f, -0.79756755f, -0.79953730f, -0.80149853f, +-0.80345118f, -0.80539525f, -0.80733067f, -0.80925739f, -0.81117553f, +-0.81308490f, -0.81498563f, -0.81687760f, -0.81876087f, -0.82063532f, +-0.82250100f, -0.82435787f, -0.82620591f, -0.82804507f, -0.82987541f, +-0.83169687f, -0.83350939f, -0.83531296f, -0.83710766f, -0.83889335f, +-0.84067005f, -0.84243774f, -0.84419644f, -0.84594607f, -0.84768665f, +-0.84941816f, -0.85114056f, -0.85285389f, -0.85455805f, -0.85625303f, +-0.85793889f, -0.85961550f, -0.86128294f, -0.86294121f, -0.86459017f, +-0.86622989f, -0.86786032f, -0.86948150f, -0.87109333f, -0.87269586f, +-0.87428904f, -0.87587279f, -0.87744725f, -0.87901229f, -0.88056785f, +-0.88211405f, -0.88365078f, -0.88517809f, -0.88669586f, -0.88820416f, +-0.88970292f, -0.89119220f, -0.89267188f, -0.89414203f, -0.89560264f, +-0.89705360f, -0.89849502f, -0.89992678f, -0.90134889f, -0.90276134f, +-0.90416414f, -0.90555727f, -0.90694070f, -0.90831441f, -0.90967834f, +-0.91103262f, -0.91237706f, -0.91371179f, -0.91503674f, -0.91635185f, +-0.91765714f, -0.91895264f, -0.92023826f, -0.92151409f, -0.92277998f, +-0.92403603f, -0.92528218f, -0.92651838f, -0.92774469f, -0.92896110f, +-0.93016750f, -0.93136400f, -0.93255049f, -0.93372697f, -0.93489349f, +-0.93604994f, -0.93719643f, -0.93833286f, -0.93945926f, -0.94057560f, +-0.94168180f, -0.94277799f, -0.94386405f, -0.94494003f, -0.94600588f, +-0.94706154f, -0.94810712f, -0.94914252f, -0.95016778f, -0.95118284f, +-0.95218778f, -0.95318246f, -0.95416695f, -0.95514119f, -0.95610523f, +-0.95705903f, -0.95800257f, -0.95893586f, -0.95985889f, -0.96077162f, +-0.96167403f, -0.96256620f, -0.96344805f, -0.96431959f, -0.96518075f, +-0.96603161f, -0.96687216f, -0.96770233f, -0.96852213f, -0.96933156f, +-0.97013056f, -0.97091925f, -0.97169751f, -0.97246534f, -0.97322279f, +-0.97396982f, -0.97470641f, -0.97543252f, -0.97614825f, -0.97685349f, +-0.97754824f, -0.97823256f, -0.97890645f, -0.97956979f, -0.98022264f, +-0.98086500f, -0.98149687f, -0.98211825f, -0.98272908f, -0.98332942f, +-0.98391914f, -0.98449844f, -0.98506713f, -0.98562527f, -0.98617285f, +-0.98670989f, -0.98723638f, -0.98775226f, -0.98825759f, -0.98875231f, +-0.98923647f, -0.98971003f, -0.99017298f, -0.99062532f, -0.99106705f, +-0.99149817f, -0.99191868f, -0.99232858f, -0.99272782f, -0.99311644f, +-0.99349445f, -0.99386179f, -0.99421853f, -0.99456459f, -0.99489999f, +-0.99522477f, -0.99553883f, -0.99584228f, -0.99613506f, -0.99641716f, +-0.99668860f, -0.99694937f, -0.99719942f, -0.99743885f, -0.99766755f, +-0.99788558f, -0.99809295f, -0.99828959f, -0.99847561f, -0.99865085f, +-0.99881548f, -0.99896932f, -0.99911255f, -0.99924499f, -0.99936682f, +-0.99947786f, -0.99957830f, -0.99966794f, -0.99974692f, -0.99981517f, +-0.99987274f, -0.99991959f, -0.99995571f, -0.99998116f, -0.99999589f, +0.99999964f, 0.99997288f, 0.99990326f, 0.99979085f, 0.99963558f, +0.99943751f, 0.99919659f, 0.99891287f, 0.99858636f, 0.99821711f, +0.99780506f, 0.99735034f, 0.99685282f, 0.99631262f, 0.99572974f, +0.99510419f, 0.99443603f, 0.99372530f, 0.99297196f, 0.99217612f, +0.99133772f, 0.99045694f, 0.98953366f, 0.98856801f, 0.98756003f, +0.98650974f, 0.98541719f, 0.98428243f, 0.98310548f, 0.98188645f, +0.98062533f, 0.97932225f, 0.97797716f, 0.97659022f, 0.97516143f, +0.97369087f, 0.97217858f, 0.97062469f, 0.96902919f, 0.96739221f, +0.96571374f, 0.96399397f, 0.96223283f, 0.96043050f, 0.95858705f, +0.95670253f, 0.95477700f, 0.95281059f, 0.95080340f, 0.94875544f, +0.94666684f, 0.94453770f, 0.94236809f, 0.94015813f, 0.93790787f, +0.93561745f, 0.93328691f, 0.93091643f, 0.92850608f, 0.92605597f, +0.92356616f, 0.92103678f, 0.91846794f, 0.91585976f, 0.91321236f, +0.91052586f, 0.90780038f, 0.90503591f, 0.90223277f, 0.89939094f, +0.89651060f, 0.89359182f, 0.89063478f, 0.88763964f, 0.88460642f, +0.88153529f, 0.87842643f, 0.87527996f, 0.87209594f, 0.86887461f, +0.86561602f, 0.86232042f, 0.85898781f, 0.85561842f, 0.85221243f, +0.84876984f, 0.84529096f, 0.84177583f, 0.83822471f, 0.83463764f, +0.83101481f, 0.82735640f, 0.82366252f, 0.81993335f, 0.81616908f, +0.81236988f, 0.80853581f, 0.80466717f, 0.80076402f, 0.79682660f, +0.79285502f, 0.78884947f, 0.78481019f, 0.78073722f, 0.77663082f, +0.77249116f, 0.76831841f, 0.76411277f, 0.75987434f, 0.75560343f, +0.75130010f, 0.74696463f, 0.74259710f, 0.73819780f, 0.73376691f, +0.72930455f, 0.72481096f, 0.72028631f, 0.71573079f, 0.71114463f, +0.70652801f, 0.70188117f, 0.69720417f, 0.69249737f, 0.68776089f, +0.68299496f, 0.67819971f, 0.67337549f, 0.66852236f, 0.66364062f, +0.65873051f, 0.65379208f, 0.64882571f, 0.64383155f, 0.63880974f, +0.63376063f, 0.62868434f, 0.62358117f, 0.61845124f, 0.61329484f, +0.60811216f, 0.60290343f, 0.59766883f, 0.59240872f, 0.58712316f, +0.58181250f, 0.57647687f, 0.57111657f, 0.56573176f, 0.56032276f, +0.55488980f, 0.54943299f, 0.54395270f, 0.53844911f, 0.53292239f, +0.52737290f, 0.52180082f, 0.51620632f, 0.51058978f, 0.50495136f, +0.49929130f, 0.49360985f, 0.48790723f, 0.48218375f, 0.47643960f, +0.47067502f, 0.46489030f, 0.45908567f, 0.45326138f, 0.44741765f, +0.44155475f, 0.43567297f, 0.42977250f, 0.42385364f, 0.41791660f, +0.41196167f, 0.40598908f, 0.39999911f, 0.39399201f, 0.38796803f, +0.38192743f, 0.37587047f, 0.36979741f, 0.36370850f, 0.35760403f, +0.35148421f, 0.34534934f, 0.33919969f, 0.33303553f, 0.32685706f, +0.32066461f, 0.31445843f, 0.30823877f, 0.30200592f, 0.29576012f, +0.28950164f, 0.28323078f, 0.27694780f, 0.27065292f, 0.26434645f, +0.25802869f, 0.25169984f, 0.24536023f, 0.23901010f, 0.23264973f, +0.22627939f, 0.21989937f, 0.21350993f, 0.20711134f, 0.20070387f, +0.19428782f, 0.18786344f, 0.18143101f, 0.17499080f, 0.16854310f, +0.16208819f, 0.15562633f, 0.14915779f, 0.14268288f, 0.13620184f, +0.12971498f, 0.12322257f, 0.11672486f, 0.11022217f, 0.10371475f, +0.097202882f, 0.090686858f, 0.084166944f, 0.077643424f, 0.071116582f, +0.064586692f, 0.058054037f, 0.051518895f, 0.044981543f, 0.038442269f, +0.031901345f, 0.025359053f, 0.018815678f, 0.012271495f, 0.0057267868f, +-0.00081816671f, -0.0073630852f, -0.013907688f, -0.020451695f, -0.026994826f, +-0.033536803f, -0.040077340f, -0.046616159f, -0.053152986f, -0.059687532f, +-0.066219524f, -0.072748676f, -0.079274714f, -0.085797355f, -0.092316322f, +-0.098831341f, -0.10534211f, -0.11184838f, -0.11834986f, -0.12484626f, +-0.13133731f, -0.13782275f, -0.14430228f, -0.15077563f, -0.15724251f, +-0.16370267f, -0.17015581f, -0.17660165f, -0.18303993f, -0.18947038f, +-0.19589271f, -0.20230664f, -0.20871192f, -0.21510825f, -0.22149536f, +-0.22787298f, -0.23424086f, -0.24059868f, -0.24694622f, -0.25328314f, +-0.25960925f, -0.26592422f, -0.27222782f, -0.27851975f, -0.28479972f, +-0.29106751f, -0.29732284f, -0.30356544f, -0.30979502f, -0.31601134f, +-0.32221413f, -0.32840309f, -0.33457801f, -0.34073856f, -0.34688455f, +-0.35301566f, -0.35913166f, -0.36523229f, -0.37131724f, -0.37738630f, +-0.38343921f, -0.38947567f, -0.39549544f, -0.40149832f, -0.40748394f, +-0.41345215f, -0.41940263f, -0.42533514f, -0.43124944f, -0.43714526f, +-0.44302234f, -0.44888046f, -0.45471936f, -0.46053877f, -0.46633846f, +-0.47211814f, -0.47787762f, -0.48361665f, -0.48933494f, -0.49503228f, +-0.50070840f, -0.50636309f, -0.51199609f, -0.51760709f, -0.52319598f, +-0.52876246f, -0.53430629f, -0.53982723f, -0.54532504f, -0.55079949f, +-0.55625033f, -0.56167740f, -0.56708032f, -0.57245898f, -0.57781315f, +-0.58314258f, -0.58844697f, -0.59372622f, -0.59897995f, -0.60420811f, +-0.60941035f, -0.61458647f, -0.61973625f, -0.62485951f, -0.62995601f, +-0.63502556f, -0.64006782f, -0.64508271f, -0.65007001f, -0.65502942f, +-0.65996075f, -0.66486382f, -0.66973841f, -0.67458433f, -0.67940134f, +-0.68418926f, -0.68894786f, -0.69367695f, -0.69837630f, -0.70304573f, +-0.70768511f, -0.71229410f, -0.71687263f, -0.72142041f, -0.72593731f, +-0.73042315f, -0.73487765f, -0.73930067f, -0.74369204f, -0.74805158f, +-0.75237900f, -0.75667429f, -0.76093709f, -0.76516730f, -0.76936477f, +-0.77352923f, -0.77766061f, -0.78175867f, -0.78582323f, -0.78985411f, +-0.79385114f, -0.79781419f, -0.80174309f, -0.80563760f, -0.80949765f, +-0.81332302f, -0.81711352f, -0.82086903f, -0.82458937f, -0.82827437f, +-0.83192390f, -0.83553779f, -0.83911592f, -0.84265804f, -0.84616417f, +-0.84963393f, -0.85306740f, -0.85646427f, -0.85982448f, -0.86314780f, +-0.86643422f, -0.86968350f, -0.87289548f, -0.87607014f, -0.87920725f, +-0.88230664f, -0.88536829f, -0.88839203f, -0.89137769f, -0.89432514f, +-0.89723432f, -0.90010506f, -0.90293723f, -0.90573072f, -0.90848541f, +-0.91120118f, -0.91387796f, -0.91651553f, -0.91911387f, -0.92167282f, +-0.92419231f, -0.92667222f, -0.92911243f, -0.93151283f, -0.93387336f, +-0.93619382f, -0.93847424f, -0.94071442f, -0.94291431f, -0.94507378f, +-0.94719279f, -0.94927126f, -0.95130903f, -0.95330608f, -0.95526224f, +-0.95717752f, -0.95905179f, -0.96088499f, -0.96267700f, -0.96442777f, +-0.96613729f, -0.96780539f, -0.96943200f, -0.97101706f, -0.97256058f, +-0.97406244f, -0.97552258f, -0.97694093f, -0.97831738f, -0.97965199f, +-0.98094457f, -0.98219514f, -0.98340368f, -0.98457009f, -0.98569429f, +-0.98677629f, -0.98781598f, -0.98881340f, -0.98976845f, -0.99068111f, +-0.99155134f, -0.99237907f, -0.99316430f, -0.99390697f, -0.99460709f, +-0.99526459f, -0.99587947f, -0.99645168f, -0.99698120f, -0.99746799f, +-0.99791211f, -0.99831343f, -0.99867201f, -0.99898779f, -0.99926084f, +-0.99949104f, -0.99967843f, -0.99982297f, -0.99992472f, -0.99998361f, +0.99999869f, 0.99989158f, 0.99961317f, 0.99916345f, 0.99854255f, +0.99775058f, 0.99678761f, 0.99565387f, 0.99434954f, 0.99287480f, +0.99122995f, 0.98941529f, 0.98743105f, 0.98527765f, 0.98295540f, +0.98046476f, 0.97780609f, 0.97497988f, 0.97198665f, 0.96882683f, +0.96550101f, 0.96200979f, 0.95835376f, 0.95453346f, 0.95054960f, +0.94640291f, 0.94209403f, 0.93762374f, 0.93299282f, 0.92820197f, +0.92325211f, 0.91814411f, 0.91287869f, 0.90745693f, 0.90187967f, +0.89614785f, 0.89026248f, 0.88422459f, 0.87803519f, 0.87169534f, +0.86520612f, 0.85856867f, 0.85178405f, 0.84485358f, 0.83777827f, +0.83055943f, 0.82319832f, 0.81569612f, 0.80805415f, 0.80027372f, +0.79235619f, 0.78430289f, 0.77611518f, 0.76779449f, 0.75934225f, +0.75075996f, 0.74204898f, 0.73321080f, 0.72424710f, 0.71515924f, +0.70594883f, 0.69661748f, 0.68716675f, 0.67759830f, 0.66791373f, +0.65811473f, 0.64820296f, 0.63818014f, 0.62804794f, 0.61780810f, +0.60746247f, 0.59701276f, 0.58646071f, 0.57580817f, 0.56505698f, +0.55420899f, 0.54326600f, 0.53222996f, 0.52110273f, 0.50988621f, +0.49858227f, 0.48719296f, 0.47572014f, 0.46416581f, 0.45253196f, +0.44082057f, 0.42903364f, 0.41717321f, 0.40524128f, 0.39323992f, +0.38117120f, 0.36903715f, 0.35683987f, 0.34458145f, 0.33226398f, +0.31988961f, 0.30746040f, 0.29497850f, 0.28244606f, 0.26986524f, +0.25723818f, 0.24456702f, 0.23185398f, 0.21910121f, 0.20631088f, +0.19348522f, 0.18062639f, 0.16773662f, 0.15481812f, 0.14187308f, +0.12890373f, 0.11591230f, 0.10290100f, 0.089872077f, 0.076827750f, +0.063770257f, 0.050701842f, 0.037624735f, 0.024541186f, 0.011453429f, +-0.0016362892f, -0.014725727f, -0.027812643f, -0.040894791f, -0.053969935f, +-0.067035832f, -0.080090240f, -0.093130924f, -0.10615565f, -0.11916219f, +-0.13214831f, -0.14511178f, -0.15805040f, -0.17096193f, -0.18384418f, +-0.19669491f, -0.20951195f, -0.22229309f, -0.23503613f, -0.24773891f, +-0.26039925f, -0.27301496f, -0.28558388f, -0.29810387f, -0.31057280f, +-0.32298848f, -0.33534884f, -0.34765175f, -0.35989508f, -0.37207675f, +-0.38419467f, -0.39624676f, -0.40823093f, -0.42014518f, -0.43198743f, +-0.44375566f, -0.45544785f, -0.46706200f, -0.47859612f, -0.49004826f, +-0.50141639f, -0.51269865f, -0.52389306f, -0.53499764f, -0.54601061f, +-0.55693001f, -0.56775403f, -0.57848072f, -0.58910829f, -0.59963489f, +-0.61005878f, -0.62037814f, -0.63059121f, -0.64069623f, -0.65069145f, +-0.66057515f, -0.67034572f, -0.68000144f, -0.68954057f, -0.69896162f, +-0.70826286f, -0.71744281f, -0.72649974f, -0.73543227f, -0.74423873f, +-0.75291771f, -0.76146764f, -0.76988715f, -0.77817470f, -0.78632891f, +-0.79434842f, -0.80223179f, -0.80997771f, -0.81758487f, -0.82505190f, +-0.83237761f, -0.83956063f, -0.84659988f, -0.85349399f, -0.86024189f, +-0.86684239f, -0.87329435f, -0.87959671f, -0.88574833f, -0.89174819f, +-0.89759529f, -0.90328854f, -0.90882701f, -0.91420978f, -0.91943592f, +-0.92450452f, -0.92941469f, -0.93416560f, -0.93875647f, -0.94318646f, +-0.94745487f, -0.95156091f, -0.95550388f, -0.95928317f, -0.96289814f, +-0.96634805f, -0.96963239f, -0.97275060f, -0.97570217f, -0.97848648f, +-0.98110318f, -0.98355180f, -0.98583186f, -0.98794299f, -0.98988485f, +-0.99165714f, -0.99325943f, -0.99469161f, -0.99595332f, -0.99704438f, +-0.99796462f, -0.99871385f, -0.99929196f, -0.99969882f, -0.99993443f, +0.99999464f, 0.99956632f, 0.99845290f, 0.99665523f, 0.99417448f, +0.99101239f, 0.98717111f, 0.98265326f, 0.97746199f, 0.97160077f, +0.96507365f, 0.95788515f, 0.95004016f, 0.94154406f, 0.93240267f, +0.92262226f, 0.91220951f, 0.90117162f, 0.88951606f, 0.87725091f, +0.86438453f, 0.85092574f, 0.83688372f, 0.82226819f, 0.80708915f, +0.79135692f, 0.77508235f, 0.75827658f, 0.74095112f, 0.72311783f, +0.70478898f, 0.68597710f, 0.66669506f, 0.64695615f, 0.62677377f, +0.60616189f, 0.58513457f, 0.56370622f, 0.54189157f, 0.51970547f, +0.49716324f, 0.47428027f, 0.45107225f, 0.42755505f, 0.40374488f, +0.37965798f, 0.35531086f, 0.33072025f, 0.30590299f, 0.28087607f, +0.25565663f, 0.23026201f, 0.20470956f, 0.17901683f, 0.15320139f, +0.12728097f, 0.10127331f, 0.075196236f, 0.049067631f, 0.022905400f, +-0.0032725304f, -0.029448219f, -0.055603724f, -0.081721120f, -0.10778251f, +-0.13377003f, -0.15966587f, -0.18545228f, -0.21111161f, -0.23662624f, +-0.26197869f, -0.28715160f, -0.31212771f, -0.33688989f, -0.36142120f, +-0.38570482f, -0.40972409f, -0.43346253f, -0.45690393f, -0.48003218f, +-0.50283146f, -0.52528608f, -0.54738069f, -0.56910020f, -0.59042966f, +-0.61135447f, -0.63186026f, -0.65193301f, -0.67155898f, -0.69072473f, +-0.70941705f, -0.72762316f, -0.74533063f, -0.76252723f, -0.77920127f, +-0.79534131f, -0.81093621f, -0.82597536f, -0.84044844f, -0.85434550f, +-0.86765707f, -0.88037395f, -0.89248747f, -0.90398932f, -0.91487163f, +-0.92512697f, -0.93474823f, -0.94372886f, -0.95206273f, -0.95974404f, +-0.96676767f, -0.97312868f, -0.97882277f, -0.98384601f, -0.98819500f, +-0.99186671f, -0.99485862f, -0.99716878f, -0.99879545f, -0.99973762f, +}; +#endif + +static const CELTMode mode48000_960_120 = { +48000, /* Fs */ +120, /* overlap */ +21, /* nbEBands */ +21, /* effEBands */ +{0.85000610f, 0.0000000f, 1.0000000f, 1.0000000f, }, /* preemph */ +eband5ms, /* eBands */ +3, /* maxLM */ +8, /* nbShortMdcts */ +120, /* shortMdctSize */ +11, /* nbAllocVectors */ +band_allocation, /* allocVectors */ +logN400, /* logN */ +window120, /* window */ +{1920, 3, {&fft_state48000_960_0, &fft_state48000_960_1, &fft_state48000_960_2, &fft_state48000_960_3, }, mdct_twiddles960}, /* mdct */ +{392, cache_index50, cache_bits50, cache_caps50}, /* cache */ +}; + +/* List of all the available modes */ +#define TOTAL_MODES 1 +static const CELTMode * const static_mode_list[TOTAL_MODES] = { +&mode48000_960_120, +}; diff --git a/port/third_party/opus/celt/static_modes_float_arm_ne10.h b/port/third_party/opus/celt/static_modes_float_arm_ne10.h new file mode 100644 index 00000000..66e1abb1 --- /dev/null +++ b/port/third_party/opus/celt/static_modes_float_arm_ne10.h @@ -0,0 +1,404 @@ +/* The contents of this file was automatically generated by + * dump_mode_arm_ne10.c with arguments: 48000 960 + * It contains static definitions for some pre-defined modes. */ +#include + +#ifndef NE10_FFT_PARAMS48000_960 +#define NE10_FFT_PARAMS48000_960 +static const ne10_int32_t ne10_factors_480[64] = { +4, 40, 4, 30, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_240[64] = { +3, 20, 4, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_120[64] = { +3, 10, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_60[64] = { +2, 5, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_fft_cpx_float32_t ne10_twiddles_480[480] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{-4.3711388e-08f,-1.0000000f}, {-0.10452851f,-0.99452192f}, {-0.20791174f,-0.97814757f}, +{-0.30901703f,-0.95105648f}, {-0.40673670f,-0.91354543f}, {-0.50000006f,-0.86602533f}, +{-0.58778518f,-0.80901700f}, {-0.66913068f,-0.74314475f}, {-0.74314493f,-0.66913044f}, +{-0.80901700f,-0.58778518f}, {-0.86602539f,-0.50000006f}, {-0.91354549f,-0.40673658f}, +{-0.95105654f,-0.30901679f}, {-0.97814763f,-0.20791161f}, {-0.99452192f,-0.10452849f}, +{1.0000000f,-0.0000000f}, {0.98768836f,-0.15643448f}, {0.95105648f,-0.30901700f}, +{0.89100653f,-0.45399052f}, {0.80901700f,-0.58778524f}, {0.70710677f,-0.70710683f}, +{0.58778524f,-0.80901700f}, {0.45399052f,-0.89100653f}, {0.30901697f,-0.95105654f}, +{0.15643437f,-0.98768836f}, {-4.3711388e-08f,-1.0000000f}, {-0.15643445f,-0.98768836f}, +{-0.30901703f,-0.95105648f}, {-0.45399061f,-0.89100647f}, {-0.58778518f,-0.80901700f}, +{-0.70710677f,-0.70710677f}, {-0.80901700f,-0.58778518f}, {-0.89100659f,-0.45399037f}, +{-0.95105654f,-0.30901679f}, {-0.98768836f,-0.15643445f}, {-1.0000000f,8.7422777e-08f}, +{-0.98768830f,0.15643461f}, {-0.95105654f,0.30901697f}, {-0.89100653f,0.45399055f}, +{-0.80901694f,0.58778536f}, {-0.70710665f,0.70710689f}, {-0.58778507f,0.80901712f}, +{-0.45399022f,0.89100665f}, {-0.30901709f,0.95105648f}, {-0.15643452f,0.98768830f}, +{1.0000000f,-0.0000000f}, {0.99991435f,-0.013089596f}, {0.99965733f,-0.026176950f}, +{0.99922901f,-0.039259817f}, {0.99862951f,-0.052335959f}, {0.99785894f,-0.065403134f}, +{0.99691731f,-0.078459099f}, {0.99580491f,-0.091501623f}, {0.99452192f,-0.10452846f}, +{0.99306846f,-0.11753740f}, {0.99144489f,-0.13052620f}, {0.98965138f,-0.14349262f}, +{0.98768836f,-0.15643448f}, {0.98555607f,-0.16934951f}, {0.98325491f,-0.18223552f}, +{0.98078525f,-0.19509032f}, {0.97814763f,-0.20791170f}, {0.97534233f,-0.22069745f}, +{0.97236991f,-0.23344538f}, {0.96923089f,-0.24615330f}, {0.96592581f,-0.25881904f}, +{0.96245521f,-0.27144045f}, {0.95881975f,-0.28401536f}, {0.95501995f,-0.29654160f}, +{0.95105648f,-0.30901700f}, {0.94693011f,-0.32143945f}, {0.94264150f,-0.33380687f}, +{0.93819129f,-0.34611708f}, {0.93358040f,-0.35836795f}, {0.92880952f,-0.37055743f}, +{0.92387956f,-0.38268346f}, {0.91879117f,-0.39474389f}, {0.91354543f,-0.40673664f}, +{0.90814316f,-0.41865975f}, {0.90258527f,-0.43051112f}, {0.89687270f,-0.44228873f}, +{0.89100653f,-0.45399052f}, {0.88498765f,-0.46561453f}, {0.87881708f,-0.47715878f}, +{0.87249601f,-0.48862126f}, {0.86602545f,-0.50000000f}, {0.85940641f,-0.51129311f}, +{0.85264015f,-0.52249855f}, {0.84572786f,-0.53361452f}, {0.83867055f,-0.54463905f}, +{0.83146960f,-0.55557024f}, {0.82412618f,-0.56640625f}, {0.81664151f,-0.57714522f}, +{0.80901700f,-0.58778524f}, {0.80125380f,-0.59832460f}, {0.79335332f,-0.60876143f}, +{0.78531694f,-0.61909395f}, {0.77714598f,-0.62932038f}, {0.76884180f,-0.63943899f}, +{0.76040596f,-0.64944810f}, {0.75183982f,-0.65934587f}, {0.74314475f,-0.66913062f}, +{0.73432249f,-0.67880076f}, {0.72537434f,-0.68835455f}, {0.71630192f,-0.69779050f}, +{0.70710677f,-0.70710683f}, {0.69779044f,-0.71630198f}, {0.68835455f,-0.72537440f}, +{0.67880070f,-0.73432255f}, {0.66913056f,-0.74314487f}, {0.65934581f,-0.75183982f}, +{0.64944804f,-0.76040596f}, {0.63943899f,-0.76884186f}, {0.62932038f,-0.77714598f}, +{0.61909395f,-0.78531694f}, {0.60876137f,-0.79335338f}, {0.59832460f,-0.80125386f}, +{0.58778524f,-0.80901700f}, {0.57714516f,-0.81664151f}, {0.56640625f,-0.82412618f}, +{0.55557019f,-0.83146960f}, {0.54463899f,-0.83867055f}, {0.53361452f,-0.84572786f}, +{0.52249849f,-0.85264015f}, {0.51129311f,-0.85940641f}, {0.49999997f,-0.86602545f}, +{0.48862118f,-0.87249601f}, {0.47715876f,-0.87881708f}, {0.46561447f,-0.88498765f}, +{0.45399052f,-0.89100653f}, {0.44228867f,-0.89687276f}, {0.43051103f,-0.90258533f}, +{0.41865975f,-0.90814316f}, {0.40673661f,-0.91354549f}, {0.39474380f,-0.91879129f}, +{0.38268343f,-0.92387956f}, {0.37055740f,-0.92880958f}, {0.35836786f,-0.93358046f}, +{0.34611705f,-0.93819135f}, {0.33380681f,-0.94264150f}, {0.32143947f,-0.94693011f}, +{0.30901697f,-0.95105654f}, {0.29654151f,-0.95501995f}, {0.28401533f,-0.95881975f}, +{0.27144039f,-0.96245527f}, {0.25881907f,-0.96592581f}, {0.24615327f,-0.96923089f}, +{0.23344530f,-0.97236991f}, {0.22069745f,-0.97534233f}, {0.20791166f,-0.97814763f}, +{0.19509023f,-0.98078531f}, {0.18223552f,-0.98325491f}, {0.16934945f,-0.98555607f}, +{0.15643437f,-0.98768836f}, {0.14349259f,-0.98965138f}, {0.13052613f,-0.99144489f}, +{0.11753740f,-0.99306846f}, {0.10452842f,-0.99452192f}, {0.091501534f,-0.99580491f}, +{0.078459084f,-0.99691731f}, {0.065403074f,-0.99785894f}, {0.052335974f,-0.99862951f}, +{0.039259788f,-0.99922901f}, {0.026176875f,-0.99965733f}, {0.013089597f,-0.99991435f}, +{1.0000000f,-0.0000000f}, {0.99965733f,-0.026176950f}, {0.99862951f,-0.052335959f}, +{0.99691731f,-0.078459099f}, {0.99452192f,-0.10452846f}, {0.99144489f,-0.13052620f}, +{0.98768836f,-0.15643448f}, {0.98325491f,-0.18223552f}, {0.97814763f,-0.20791170f}, +{0.97236991f,-0.23344538f}, {0.96592581f,-0.25881904f}, {0.95881975f,-0.28401536f}, +{0.95105648f,-0.30901700f}, {0.94264150f,-0.33380687f}, {0.93358040f,-0.35836795f}, +{0.92387956f,-0.38268346f}, {0.91354543f,-0.40673664f}, {0.90258527f,-0.43051112f}, +{0.89100653f,-0.45399052f}, {0.87881708f,-0.47715878f}, {0.86602545f,-0.50000000f}, +{0.85264015f,-0.52249855f}, {0.83867055f,-0.54463905f}, {0.82412618f,-0.56640625f}, +{0.80901700f,-0.58778524f}, {0.79335332f,-0.60876143f}, {0.77714598f,-0.62932038f}, +{0.76040596f,-0.64944810f}, {0.74314475f,-0.66913062f}, {0.72537434f,-0.68835455f}, +{0.70710677f,-0.70710683f}, {0.68835455f,-0.72537440f}, {0.66913056f,-0.74314487f}, +{0.64944804f,-0.76040596f}, {0.62932038f,-0.77714598f}, {0.60876137f,-0.79335338f}, +{0.58778524f,-0.80901700f}, {0.56640625f,-0.82412618f}, {0.54463899f,-0.83867055f}, +{0.52249849f,-0.85264015f}, {0.49999997f,-0.86602545f}, {0.47715876f,-0.87881708f}, +{0.45399052f,-0.89100653f}, {0.43051103f,-0.90258533f}, {0.40673661f,-0.91354549f}, +{0.38268343f,-0.92387956f}, {0.35836786f,-0.93358046f}, {0.33380681f,-0.94264150f}, +{0.30901697f,-0.95105654f}, {0.28401533f,-0.95881975f}, {0.25881907f,-0.96592581f}, +{0.23344530f,-0.97236991f}, {0.20791166f,-0.97814763f}, {0.18223552f,-0.98325491f}, +{0.15643437f,-0.98768836f}, {0.13052613f,-0.99144489f}, {0.10452842f,-0.99452192f}, +{0.078459084f,-0.99691731f}, {0.052335974f,-0.99862951f}, {0.026176875f,-0.99965733f}, +{-4.3711388e-08f,-1.0000000f}, {-0.026176963f,-0.99965733f}, {-0.052336060f,-0.99862951f}, +{-0.078459173f,-0.99691731f}, {-0.10452851f,-0.99452192f}, {-0.13052621f,-0.99144489f}, +{-0.15643445f,-0.98768836f}, {-0.18223560f,-0.98325491f}, {-0.20791174f,-0.97814757f}, +{-0.23344538f,-0.97236991f}, {-0.25881916f,-0.96592581f}, {-0.28401542f,-0.95881969f}, +{-0.30901703f,-0.95105648f}, {-0.33380687f,-0.94264150f}, {-0.35836795f,-0.93358040f}, +{-0.38268352f,-0.92387950f}, {-0.40673670f,-0.91354543f}, {-0.43051112f,-0.90258527f}, +{-0.45399061f,-0.89100647f}, {-0.47715873f,-0.87881708f}, {-0.50000006f,-0.86602533f}, +{-0.52249867f,-0.85264009f}, {-0.54463905f,-0.83867055f}, {-0.56640631f,-0.82412612f}, +{-0.58778518f,-0.80901700f}, {-0.60876143f,-0.79335332f}, {-0.62932050f,-0.77714586f}, +{-0.64944804f,-0.76040596f}, {-0.66913068f,-0.74314475f}, {-0.68835467f,-0.72537428f}, +{-0.70710677f,-0.70710677f}, {-0.72537446f,-0.68835449f}, {-0.74314493f,-0.66913044f}, +{-0.76040596f,-0.64944804f}, {-0.77714604f,-0.62932026f}, {-0.79335332f,-0.60876143f}, +{-0.80901700f,-0.58778518f}, {-0.82412624f,-0.56640613f}, {-0.83867055f,-0.54463899f}, +{-0.85264021f,-0.52249849f}, {-0.86602539f,-0.50000006f}, {-0.87881714f,-0.47715873f}, +{-0.89100659f,-0.45399037f}, {-0.90258527f,-0.43051112f}, {-0.91354549f,-0.40673658f}, +{-0.92387956f,-0.38268328f}, {-0.93358040f,-0.35836792f}, {-0.94264150f,-0.33380675f}, +{-0.95105654f,-0.30901679f}, {-0.95881975f,-0.28401530f}, {-0.96592587f,-0.25881892f}, +{-0.97236991f,-0.23344538f}, {-0.97814763f,-0.20791161f}, {-0.98325491f,-0.18223536f}, +{-0.98768836f,-0.15643445f}, {-0.99144489f,-0.13052608f}, {-0.99452192f,-0.10452849f}, +{-0.99691737f,-0.078459039f}, {-0.99862957f,-0.052335810f}, {-0.99965733f,-0.026176952f}, +{1.0000000f,-0.0000000f}, {0.99922901f,-0.039259817f}, {0.99691731f,-0.078459099f}, +{0.99306846f,-0.11753740f}, {0.98768836f,-0.15643448f}, {0.98078525f,-0.19509032f}, +{0.97236991f,-0.23344538f}, {0.96245521f,-0.27144045f}, {0.95105648f,-0.30901700f}, +{0.93819129f,-0.34611708f}, {0.92387956f,-0.38268346f}, {0.90814316f,-0.41865975f}, +{0.89100653f,-0.45399052f}, {0.87249601f,-0.48862126f}, {0.85264015f,-0.52249855f}, +{0.83146960f,-0.55557024f}, {0.80901700f,-0.58778524f}, {0.78531694f,-0.61909395f}, +{0.76040596f,-0.64944810f}, {0.73432249f,-0.67880076f}, {0.70710677f,-0.70710683f}, +{0.67880070f,-0.73432255f}, {0.64944804f,-0.76040596f}, {0.61909395f,-0.78531694f}, +{0.58778524f,-0.80901700f}, {0.55557019f,-0.83146960f}, {0.52249849f,-0.85264015f}, +{0.48862118f,-0.87249601f}, {0.45399052f,-0.89100653f}, {0.41865975f,-0.90814316f}, +{0.38268343f,-0.92387956f}, {0.34611705f,-0.93819135f}, {0.30901697f,-0.95105654f}, +{0.27144039f,-0.96245527f}, {0.23344530f,-0.97236991f}, {0.19509023f,-0.98078531f}, +{0.15643437f,-0.98768836f}, {0.11753740f,-0.99306846f}, {0.078459084f,-0.99691731f}, +{0.039259788f,-0.99922901f}, {-4.3711388e-08f,-1.0000000f}, {-0.039259877f,-0.99922901f}, +{-0.078459173f,-0.99691731f}, {-0.11753749f,-0.99306846f}, {-0.15643445f,-0.98768836f}, +{-0.19509032f,-0.98078525f}, {-0.23344538f,-0.97236991f}, {-0.27144048f,-0.96245521f}, +{-0.30901703f,-0.95105648f}, {-0.34611711f,-0.93819129f}, {-0.38268352f,-0.92387950f}, +{-0.41865984f,-0.90814310f}, {-0.45399061f,-0.89100647f}, {-0.48862135f,-0.87249595f}, +{-0.52249867f,-0.85264009f}, {-0.55557036f,-0.83146954f}, {-0.58778518f,-0.80901700f}, +{-0.61909389f,-0.78531694f}, {-0.64944804f,-0.76040596f}, {-0.67880076f,-0.73432249f}, +{-0.70710677f,-0.70710677f}, {-0.73432249f,-0.67880070f}, {-0.76040596f,-0.64944804f}, +{-0.78531694f,-0.61909389f}, {-0.80901700f,-0.58778518f}, {-0.83146966f,-0.55557019f}, +{-0.85264021f,-0.52249849f}, {-0.87249607f,-0.48862115f}, {-0.89100659f,-0.45399037f}, +{-0.90814322f,-0.41865960f}, {-0.92387956f,-0.38268328f}, {-0.93819135f,-0.34611690f}, +{-0.95105654f,-0.30901679f}, {-0.96245521f,-0.27144048f}, {-0.97236991f,-0.23344538f}, +{-0.98078531f,-0.19509031f}, {-0.98768836f,-0.15643445f}, {-0.99306846f,-0.11753736f}, +{-0.99691737f,-0.078459039f}, {-0.99922901f,-0.039259743f}, {-1.0000000f,8.7422777e-08f}, +{-0.99922901f,0.039259918f}, {-0.99691731f,0.078459218f}, {-0.99306846f,0.11753753f}, +{-0.98768830f,0.15643461f}, {-0.98078525f,0.19509049f}, {-0.97236985f,0.23344554f}, +{-0.96245515f,0.27144065f}, {-0.95105654f,0.30901697f}, {-0.93819135f,0.34611705f}, +{-0.92387956f,0.38268346f}, {-0.90814316f,0.41865975f}, {-0.89100653f,0.45399055f}, +{-0.87249601f,0.48862129f}, {-0.85264015f,0.52249861f}, {-0.83146960f,0.55557030f}, +{-0.80901694f,0.58778536f}, {-0.78531688f,0.61909401f}, {-0.76040590f,0.64944816f}, +{-0.73432243f,0.67880082f}, {-0.70710665f,0.70710689f}, {-0.67880058f,0.73432261f}, +{-0.64944792f,0.76040608f}, {-0.61909378f,0.78531706f}, {-0.58778507f,0.80901712f}, +{-0.55557001f,0.83146977f}, {-0.52249837f,0.85264033f}, {-0.48862100f,0.87249613f}, +{-0.45399022f,0.89100665f}, {-0.41865945f,0.90814328f}, {-0.38268313f,0.92387968f}, +{-0.34611672f,0.93819147f}, {-0.30901709f,0.95105648f}, {-0.27144054f,0.96245521f}, +{-0.23344545f,0.97236991f}, {-0.19509038f,0.98078525f}, {-0.15643452f,0.98768830f}, +{-0.11753743f,0.99306846f}, {-0.078459114f,0.99691731f}, {-0.039259821f,0.99922901f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_240[240] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.95105648f,-0.30901700f}, {0.80901700f,-0.58778524f}, +{0.58778524f,-0.80901700f}, {0.30901697f,-0.95105654f}, {-4.3711388e-08f,-1.0000000f}, +{-0.30901703f,-0.95105648f}, {-0.58778518f,-0.80901700f}, {-0.80901700f,-0.58778518f}, +{-0.95105654f,-0.30901679f}, {-1.0000000f,8.7422777e-08f}, {-0.95105654f,0.30901697f}, +{-0.80901694f,0.58778536f}, {-0.58778507f,0.80901712f}, {-0.30901709f,0.95105648f}, +{1.0000000f,-0.0000000f}, {0.99965733f,-0.026176950f}, {0.99862951f,-0.052335959f}, +{0.99691731f,-0.078459099f}, {0.99452192f,-0.10452846f}, {0.99144489f,-0.13052620f}, +{0.98768836f,-0.15643448f}, {0.98325491f,-0.18223552f}, {0.97814763f,-0.20791170f}, +{0.97236991f,-0.23344538f}, {0.96592581f,-0.25881904f}, {0.95881975f,-0.28401536f}, +{0.95105648f,-0.30901700f}, {0.94264150f,-0.33380687f}, {0.93358040f,-0.35836795f}, +{0.92387956f,-0.38268346f}, {0.91354543f,-0.40673664f}, {0.90258527f,-0.43051112f}, +{0.89100653f,-0.45399052f}, {0.87881708f,-0.47715878f}, {0.86602545f,-0.50000000f}, +{0.85264015f,-0.52249855f}, {0.83867055f,-0.54463905f}, {0.82412618f,-0.56640625f}, +{0.80901700f,-0.58778524f}, {0.79335332f,-0.60876143f}, {0.77714598f,-0.62932038f}, +{0.76040596f,-0.64944810f}, {0.74314475f,-0.66913062f}, {0.72537434f,-0.68835455f}, +{0.70710677f,-0.70710683f}, {0.68835455f,-0.72537440f}, {0.66913056f,-0.74314487f}, +{0.64944804f,-0.76040596f}, {0.62932038f,-0.77714598f}, {0.60876137f,-0.79335338f}, +{0.58778524f,-0.80901700f}, {0.56640625f,-0.82412618f}, {0.54463899f,-0.83867055f}, +{0.52249849f,-0.85264015f}, {0.49999997f,-0.86602545f}, {0.47715876f,-0.87881708f}, +{0.45399052f,-0.89100653f}, {0.43051103f,-0.90258533f}, {0.40673661f,-0.91354549f}, +{0.38268343f,-0.92387956f}, {0.35836786f,-0.93358046f}, {0.33380681f,-0.94264150f}, +{0.30901697f,-0.95105654f}, {0.28401533f,-0.95881975f}, {0.25881907f,-0.96592581f}, +{0.23344530f,-0.97236991f}, {0.20791166f,-0.97814763f}, {0.18223552f,-0.98325491f}, +{0.15643437f,-0.98768836f}, {0.13052613f,-0.99144489f}, {0.10452842f,-0.99452192f}, +{0.078459084f,-0.99691731f}, {0.052335974f,-0.99862951f}, {0.026176875f,-0.99965733f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{-4.3711388e-08f,-1.0000000f}, {-0.052336060f,-0.99862951f}, {-0.10452851f,-0.99452192f}, +{-0.15643445f,-0.98768836f}, {-0.20791174f,-0.97814757f}, {-0.25881916f,-0.96592581f}, +{-0.30901703f,-0.95105648f}, {-0.35836795f,-0.93358040f}, {-0.40673670f,-0.91354543f}, +{-0.45399061f,-0.89100647f}, {-0.50000006f,-0.86602533f}, {-0.54463905f,-0.83867055f}, +{-0.58778518f,-0.80901700f}, {-0.62932050f,-0.77714586f}, {-0.66913068f,-0.74314475f}, +{-0.70710677f,-0.70710677f}, {-0.74314493f,-0.66913044f}, {-0.77714604f,-0.62932026f}, +{-0.80901700f,-0.58778518f}, {-0.83867055f,-0.54463899f}, {-0.86602539f,-0.50000006f}, +{-0.89100659f,-0.45399037f}, {-0.91354549f,-0.40673658f}, {-0.93358040f,-0.35836792f}, +{-0.95105654f,-0.30901679f}, {-0.96592587f,-0.25881892f}, {-0.97814763f,-0.20791161f}, +{-0.98768836f,-0.15643445f}, {-0.99452192f,-0.10452849f}, {-0.99862957f,-0.052335810f}, +{1.0000000f,-0.0000000f}, {0.99691731f,-0.078459099f}, {0.98768836f,-0.15643448f}, +{0.97236991f,-0.23344538f}, {0.95105648f,-0.30901700f}, {0.92387956f,-0.38268346f}, +{0.89100653f,-0.45399052f}, {0.85264015f,-0.52249855f}, {0.80901700f,-0.58778524f}, +{0.76040596f,-0.64944810f}, {0.70710677f,-0.70710683f}, {0.64944804f,-0.76040596f}, +{0.58778524f,-0.80901700f}, {0.52249849f,-0.85264015f}, {0.45399052f,-0.89100653f}, +{0.38268343f,-0.92387956f}, {0.30901697f,-0.95105654f}, {0.23344530f,-0.97236991f}, +{0.15643437f,-0.98768836f}, {0.078459084f,-0.99691731f}, {-4.3711388e-08f,-1.0000000f}, +{-0.078459173f,-0.99691731f}, {-0.15643445f,-0.98768836f}, {-0.23344538f,-0.97236991f}, +{-0.30901703f,-0.95105648f}, {-0.38268352f,-0.92387950f}, {-0.45399061f,-0.89100647f}, +{-0.52249867f,-0.85264009f}, {-0.58778518f,-0.80901700f}, {-0.64944804f,-0.76040596f}, +{-0.70710677f,-0.70710677f}, {-0.76040596f,-0.64944804f}, {-0.80901700f,-0.58778518f}, +{-0.85264021f,-0.52249849f}, {-0.89100659f,-0.45399037f}, {-0.92387956f,-0.38268328f}, +{-0.95105654f,-0.30901679f}, {-0.97236991f,-0.23344538f}, {-0.98768836f,-0.15643445f}, +{-0.99691737f,-0.078459039f}, {-1.0000000f,8.7422777e-08f}, {-0.99691731f,0.078459218f}, +{-0.98768830f,0.15643461f}, {-0.97236985f,0.23344554f}, {-0.95105654f,0.30901697f}, +{-0.92387956f,0.38268346f}, {-0.89100653f,0.45399055f}, {-0.85264015f,0.52249861f}, +{-0.80901694f,0.58778536f}, {-0.76040590f,0.64944816f}, {-0.70710665f,0.70710689f}, +{-0.64944792f,0.76040608f}, {-0.58778507f,0.80901712f}, {-0.52249837f,0.85264033f}, +{-0.45399022f,0.89100665f}, {-0.38268313f,0.92387968f}, {-0.30901709f,0.95105648f}, +{-0.23344545f,0.97236991f}, {-0.15643452f,0.98768830f}, {-0.078459114f,0.99691731f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_120[120] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{-4.3711388e-08f,-1.0000000f}, {-0.10452851f,-0.99452192f}, {-0.20791174f,-0.97814757f}, +{-0.30901703f,-0.95105648f}, {-0.40673670f,-0.91354543f}, {-0.50000006f,-0.86602533f}, +{-0.58778518f,-0.80901700f}, {-0.66913068f,-0.74314475f}, {-0.74314493f,-0.66913044f}, +{-0.80901700f,-0.58778518f}, {-0.86602539f,-0.50000006f}, {-0.91354549f,-0.40673658f}, +{-0.95105654f,-0.30901679f}, {-0.97814763f,-0.20791161f}, {-0.99452192f,-0.10452849f}, +{1.0000000f,-0.0000000f}, {0.98768836f,-0.15643448f}, {0.95105648f,-0.30901700f}, +{0.89100653f,-0.45399052f}, {0.80901700f,-0.58778524f}, {0.70710677f,-0.70710683f}, +{0.58778524f,-0.80901700f}, {0.45399052f,-0.89100653f}, {0.30901697f,-0.95105654f}, +{0.15643437f,-0.98768836f}, {-4.3711388e-08f,-1.0000000f}, {-0.15643445f,-0.98768836f}, +{-0.30901703f,-0.95105648f}, {-0.45399061f,-0.89100647f}, {-0.58778518f,-0.80901700f}, +{-0.70710677f,-0.70710677f}, {-0.80901700f,-0.58778518f}, {-0.89100659f,-0.45399037f}, +{-0.95105654f,-0.30901679f}, {-0.98768836f,-0.15643445f}, {-1.0000000f,8.7422777e-08f}, +{-0.98768830f,0.15643461f}, {-0.95105654f,0.30901697f}, {-0.89100653f,0.45399055f}, +{-0.80901694f,0.58778536f}, {-0.70710665f,0.70710689f}, {-0.58778507f,0.80901712f}, +{-0.45399022f,0.89100665f}, {-0.30901709f,0.95105648f}, {-0.15643452f,0.98768830f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_60[60] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.95105648f,-0.30901700f}, {0.80901700f,-0.58778524f}, +{0.58778524f,-0.80901700f}, {0.30901697f,-0.95105654f}, {-4.3711388e-08f,-1.0000000f}, +{-0.30901703f,-0.95105648f}, {-0.58778518f,-0.80901700f}, {-0.80901700f,-0.58778518f}, +{-0.95105654f,-0.30901679f}, {-1.0000000f,8.7422777e-08f}, {-0.95105654f,0.30901697f}, +{-0.80901694f,0.58778536f}, {-0.58778507f,0.80901712f}, {-0.30901709f,0.95105648f}, +}; +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_480 = { +120, +(ne10_int32_t *)ne10_factors_480, +(ne10_fft_cpx_float32_t *)ne10_twiddles_480, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_480[120], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_480 = { +1, +(void *)&ne10_fft_state_float32_t_480, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_240 = { +60, +(ne10_int32_t *)ne10_factors_240, +(ne10_fft_cpx_float32_t *)ne10_twiddles_240, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_240[60], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_240 = { +1, +(void *)&ne10_fft_state_float32_t_240, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_120 = { +30, +(ne10_int32_t *)ne10_factors_120, +(ne10_fft_cpx_float32_t *)ne10_twiddles_120, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_120[30], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_120 = { +1, +(void *)&ne10_fft_state_float32_t_120, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_60 = { +15, +(ne10_int32_t *)ne10_factors_60, +(ne10_fft_cpx_float32_t *)ne10_twiddles_60, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_60[15], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_60 = { +1, +(void *)&ne10_fft_state_float32_t_60, +}; + +#endif /* end NE10_FFT_PARAMS48000_960 */ diff --git a/port/third_party/opus/celt/vq.c b/port/third_party/opus/celt/vq.c new file mode 100644 index 00000000..8011e225 --- /dev/null +++ b/port/third_party/opus/celt/vq.c @@ -0,0 +1,442 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mathops.h" +#include "cwrs.h" +#include "vq.h" +#include "arch.h" +#include "os_support.h" +#include "bands.h" +#include "rate.h" +#include "pitch.h" + +#if defined(MIPSr1_ASM) +#include "mips/vq_mipsr1.h" +#endif + +#ifndef OVERRIDE_vq_exp_rotation1 +static void exp_rotation1(celt_norm *X, int len, int stride, opus_val16 c, opus_val16 s) +{ + int i; + opus_val16 ms; + celt_norm *Xptr; + Xptr = X; + ms = NEG16(s); + for (i=0;i=0;i--) + { + celt_norm x1, x2; + x1 = Xptr[0]; + x2 = Xptr[stride]; + Xptr[stride] = EXTRACT16(PSHR32(MAC16_16(MULT16_16(c, x2), s, x1), 15)); + *Xptr-- = EXTRACT16(PSHR32(MAC16_16(MULT16_16(c, x1), ms, x2), 15)); + } +} +#endif /* OVERRIDE_vq_exp_rotation1 */ + +void exp_rotation(celt_norm *X, int len, int dir, int stride, int K, int spread) +{ + static const int SPREAD_FACTOR[3]={15,10,5}; + int i; + opus_val16 c, s; + opus_val16 gain, theta; + int stride2=0; + int factor; + + if (2*K>=len || spread==SPREAD_NONE) + return; + factor = SPREAD_FACTOR[spread-1]; + + gain = celt_div((opus_val32)MULT16_16(Q15_ONE,len),(opus_val32)(len+factor*K)); + theta = HALF16(MULT16_16_Q15(gain,gain)); + + c = celt_cos_norm(EXTEND32(theta)); + s = celt_cos_norm(EXTEND32(SUB16(Q15ONE,theta))); /* sin(theta) */ + + if (len>=8*stride) + { + stride2 = 1; + /* This is just a simple (equivalent) way of computing sqrt(len/stride) with rounding. + It's basically incrementing long as (stride2+0.5)^2 < len/stride. */ + while ((stride2*stride2+stride2)*stride + (stride>>2) < len) + stride2++; + } + /*NOTE: As a minor optimization, we could be passing around log2(B), not B, for both this and for + extract_collapse_mask().*/ + len = celt_udiv(len, stride); + for (i=0;i>1; +#endif + t = VSHR32(Ryy, 2*(k-7)); + g = MULT16_16_P15(celt_rsqrt_norm(t),gain); + + i=0; + do + X[i] = EXTRACT16(PSHR32(MULT16_16(g, iy[i]), k+1)); + while (++i < N); +} + +static unsigned extract_collapse_mask(int *iy, int N, int B) +{ + unsigned collapse_mask; + int N0; + int i; + if (B<=1) + return 1; + /*NOTE: As a minor optimization, we could be passing around log2(B), not B, for both this and for + exp_rotation().*/ + N0 = celt_udiv(N, B); + collapse_mask = 0; + i=0; do { + int j; + unsigned tmp=0; + j=0; do { + tmp |= iy[i*N0+j]; + } while (++j (N>>1)) + { + opus_val16 rcp; + j=0; do { + sum += X[j]; + } while (++j EPSILON && sum < 64)) +#endif + { + X[0] = QCONST16(1.f,14); + j=1; do + X[j]=0; + while (++j=0); + + /* This should never happen, but just in case it does (e.g. on silence) + we fill the first bin with pulses. */ +#ifdef FIXED_POINT_DEBUG + celt_sig_assert(pulsesLeft<=N+3); +#endif + if (pulsesLeft > N+3) + { + opus_val16 tmp = (opus_val16)pulsesLeft; + yy = MAC16_16(yy, tmp, tmp); + yy = MAC16_16(yy, tmp, y[0]); + iy[0] += pulsesLeft; + pulsesLeft=0; + } + + for (i=0;i= best_num/best_den, but that way + we can do it without any division */ + /* OPT: It's not clear whether a cmov is faster than a branch here + since the condition is more often false than true and using + a cmov introduces data dependencies across iterations. The optimal + choice may be architecture-dependent. */ + if (opus_unlikely(MULT16_16(best_den, Rxy) > MULT16_16(Ryy, best_num))) + { + best_den = Ryy; + best_num = Rxy; + best_id = j; + } + } while (++j0, "alg_quant() needs at least one pulse"); + celt_assert2(N>1, "alg_quant() needs at least two dimensions"); + + /* Covers vectorization by up to 4. */ + ALLOC(iy, N+3, int); + + exp_rotation(X, N, 1, B, K, spread); + + yy = op_pvq_search(X, iy, K, N, arch); + + encode_pulses(iy, N, K, enc); + + if (resynth) + { + normalise_residual(iy, X, N, yy, gain); + exp_rotation(X, N, -1, B, K, spread); + } + + collapse_mask = extract_collapse_mask(iy, N, B); + RESTORE_STACK; + return collapse_mask; +} + +/** Decode pulse vector and combine the result with the pitch vector to produce + the final normalised signal in the current band. */ +unsigned alg_unquant(celt_norm *X, int N, int K, int spread, int B, + ec_dec *dec, opus_val16 gain) +{ + opus_val32 Ryy; + unsigned collapse_mask; + VARDECL(int, iy); + SAVE_STACK; + + celt_assert2(K>0, "alg_unquant() needs at least one pulse"); + celt_assert2(N>1, "alg_unquant() needs at least two dimensions"); + ALLOC(iy, N, int); + Ryy = decode_pulses(iy, N, K, dec); + normalise_residual(iy, X, N, Ryy, gain); + exp_rotation(X, N, -1, B, K, spread); + collapse_mask = extract_collapse_mask(iy, N, B); + RESTORE_STACK; + return collapse_mask; +} + +#ifndef OVERRIDE_renormalise_vector +void renormalise_vector(celt_norm *X, int N, opus_val16 gain, int arch) +{ + int i; +#ifdef FIXED_POINT + int k; +#endif + opus_val32 E; + opus_val16 g; + opus_val32 t; + celt_norm *xptr; + E = EPSILON + celt_inner_prod(X, X, N, arch); +#ifdef FIXED_POINT + k = celt_ilog2(E)>>1; +#endif + t = VSHR32(E, 2*(k-7)); + g = MULT16_16_P15(celt_rsqrt_norm(t),gain); + + xptr = X; + for (i=0;i + *
  • audio_frame is the audio data in opus_int16 (or float for opus_encode_float())
  • + *
  • frame_size is the duration of the frame in samples (per channel)
  • + *
  • packet is the byte array to which the compressed data is written
  • + *
  • max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended). + * Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.
  • + * + * + * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. + * The return value can be negative, which indicates that an error has occurred. If the return value + * is 2 bytes or less, then the packet does not need to be transmitted (DTX). + * + * Once the encoder state if no longer needed, it can be destroyed with + * + * @code + * opus_encoder_destroy(enc); + * @endcode + * + * If the encoder was created with opus_encoder_init() rather than opus_encoder_create(), + * then no action is required aside from potentially freeing the memory that was manually + * allocated for it (calling free(enc) for the example above) + * + */ + +/** Opus encoder state. + * This contains the complete state of an Opus encoder. + * It is position independent and can be freely copied. + * @see opus_encoder_create,opus_encoder_init + */ +typedef struct OpusEncoder OpusEncoder; + +/** Gets the size of an OpusEncoder structure. + * @param[in] channels int: Number of channels. + * This must be 1 or 2. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels); + +/** + */ + +/** Allocates and initializes an encoder state. + * There are three coding modes: + * + * @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice + * signals. It enhances the input signal by high-pass filtering and + * emphasizing formants and harmonics. Optionally it includes in-band + * forward error correction to protect against packet loss. Use this + * mode for typical VoIP applications. Because of the enhancement, + * even at high bitrates the output may sound different from the input. + * + * @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most + * non-voice signals like music. Use this mode for music and mixed + * (music/voice) content, broadcast, and applications requiring less + * than 15 ms of coding delay. + * + * @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that + * disables the speech-optimized mode in exchange for slightly reduced delay. + * This mode can only be set on an newly initialized or freshly reset encoder + * because it changes the codec delay. + * + * This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution). + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (one of @ref OPUS_APPLICATION_VOIP, @ref OPUS_APPLICATION_AUDIO, or @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @param [out] error int*: @ref opus_errorcodes + * @note Regardless of the sampling rate and number channels selected, the Opus encoder + * can switch to a lower audio bandwidth or number of channels if the bitrate + * selected is too low. This also means that it is safe to always use 48 kHz stereo input + * and let the encoder optimize the encoding. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create( + opus_int32 Fs, + int channels, + int application, + int *error +); + +/** Initializes a previously allocated encoder state + * The memory pointed to by st must be at least the size returned by opus_encoder_get_size(). + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_encoder_create(),opus_encoder_get_size() + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (one of OPUS_APPLICATION_VOIP, OPUS_APPLICATION_AUDIO, or OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @retval #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_encoder_init( + OpusEncoder *st, + opus_int32 Fs, + int channels, + int application +) OPUS_ARG_NONNULL(1); + +/** Encodes an Opus frame. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] pcm opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size int: Number of samples per channel in the + * input signal. + * This must be an Opus frame size for + * the encoder's sampling rate. + * For example, at 48 kHz the permitted + * values are 120, 240, 480, 960, 1920, + * and 2880. + * Passing in a duration of less than + * 10 ms (480 samples at 48 kHz) will + * prevent the encoder from using the LPC + * or hybrid modes. + * @param [out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode( + OpusEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes an Opus frame from floating point input. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] pcm float*: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0. + * Samples with a range beyond +/-1.0 are supported but will + * be clipped by decoders using the integer API and should + * only be used if it is known that the far end supports + * extended dynamic range. + * length is frame_size*channels*sizeof(float) + * @param [in] frame_size int: Number of samples per channel in the + * input signal. + * This must be an Opus frame size for + * the encoder's sampling rate. + * For example, at 48 kHz the permitted + * values are 120, 240, 480, 960, 1920, + * and 2880. + * Passing in a duration of less than + * 10 ms (480 samples at 48 kHz) will + * prevent the encoder from using the LPC + * or hybrid modes. + * @param [out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float( + OpusEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Frees an OpusEncoder allocated by opus_encoder_create(). + * @param[in] st OpusEncoder*: State to be freed. + */ +OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st); + +/** Perform a CTL function on an Opus encoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param st OpusEncoder*: Encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_encoderctls. + * @see opus_genericctls + * @see opus_encoderctls + */ +OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); +/**@}*/ + +/** @defgroup opus_decoder Opus Decoder + * @{ + * + * @brief This page describes the process and functions used to decode Opus. + * + * The decoding process also starts with creating a decoder + * state. This can be done with: + * @code + * int error; + * OpusDecoder *dec; + * dec = opus_decoder_create(Fs, channels, &error); + * @endcode + * where + * @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000 + * @li channels is the number of channels (1 or 2) + * @li error will hold the error code in case of failure (or #OPUS_OK on success) + * @li the return value is a newly created decoder state to be used for decoding + * + * While opus_decoder_create() allocates memory for the state, it's also possible + * to initialize pre-allocated memory: + * @code + * int size; + * int error; + * OpusDecoder *dec; + * size = opus_decoder_get_size(channels); + * dec = malloc(size); + * error = opus_decoder_init(dec, Fs, channels); + * @endcode + * where opus_decoder_get_size() returns the required size for the decoder state. Note that + * future versions of this code may change the size, so no assumptions should be made about it. + * + * The decoder state is always continuous in memory and only a shallow copy is sufficient + * to copy it (e.g. memcpy()) + * + * To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data: + * @code + * frame_size = opus_decode(dec, packet, len, decoded, max_size, 0); + * @endcode + * where + * + * @li packet is the byte array containing the compressed data + * @li len is the exact number of bytes contained in the packet + * @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float()) + * @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array + * + * opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet. + * If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio + * buffer is too small to hold the decoded audio. + * + * Opus is a stateful codec with overlapping blocks and as a result Opus + * packets are not coded independently of each other. Packets must be + * passed into the decoder serially and in the correct order for a correct + * decode. Lost packets can be replaced with loss concealment by calling + * the decoder with a null pointer and zero length for the missing packet. + * + * A single codec state may only be accessed from a single thread at + * a time and any required locking must be performed by the caller. Separate + * streams must be decoded with separate decoder states and can be decoded + * in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK + * defined. + * + */ + +/** Opus decoder state. + * This contains the complete state of an Opus decoder. + * It is position independent and can be freely copied. + * @see opus_decoder_create,opus_decoder_init + */ +typedef struct OpusDecoder OpusDecoder; + +/** Opus DRED decoder. + * This contains the complete state of an Opus DRED decoder. + * It is position independent and can be freely copied. + * @see opus_dred_decoder_create,opus_dred_decoder_init + */ +typedef struct OpusDREDDecoder OpusDREDDecoder; + + +/** Opus DRED state. + * This contains the complete state of an Opus DRED packet. + * It is position independent and can be freely copied. + * @see opus_dred_create,opus_dred_init + */ +typedef struct OpusDRED OpusDRED; + +/** Gets the size of an OpusDecoder structure. + * @param [in] channels int: Number of channels. + * This must be 1 or 2. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels); + +/** Allocates and initializes a decoder state. + * @param [in] Fs opus_int32: Sample rate to decode at (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + * + * Internally Opus stores data at 48000 Hz, so that should be the default + * value for Fs. However, the decoder can efficiently decode to buffers + * at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use + * data at the full sample rate, or knows the compressed data doesn't + * use the full frequency range, it can request decoding at a reduced + * rate. Likewise, the decoder is capable of filling in either mono or + * interleaved stereo pcm buffers, at the caller's request. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create( + opus_int32 Fs, + int channels, + int *error +); + +/** Initializes a previously allocated decoder state. + * The state must be at least the size returned by opus_decoder_get_size(). + * This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @param [in] st OpusDecoder*: Decoder state. + * @param [in] Fs opus_int32: Sampling rate to decode to (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @retval #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_init( + OpusDecoder *st, + opus_int32 Fs, + int channels +) OPUS_ARG_NONNULL(1); + +/** Decode an Opus packet. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload* + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel of available space in \a pcm. + * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will + * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), + * then frame_size needs to be exactly the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and + * FEC cases, frame_size must be a multiple of 2.5 ms. + * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be + * decoded. If no such data is available, the frame is decoded as if it were lost. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode( + OpusDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode an Opus packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel of available space in \a pcm. + * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will + * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), + * then frame_size needs to be exactly the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and + * FEC cases, frame_size must be a multiple of 2.5 ms. + * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be + * decoded. If no such data is available the frame is decoded as if it were lost. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float( + OpusDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param st OpusDecoder*: Decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_decoderctls. + * @see opus_genericctls + * @see opus_decoderctls + */ +OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/** Frees an OpusDecoder allocated by opus_decoder_create(). + * @param[in] st OpusDecoder*: State to be freed. + */ +OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st); + +/** Gets the size of an OpusDREDDecoder structure. + * @returns The size in bytes. + */ +OPUS_EXPORT int opus_dred_decoder_get_size(void); + +/** Allocates and initializes an OpusDREDDecoder state. + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT OpusDREDDecoder *opus_dred_decoder_create(int *error); + +/** Initializes an OpusDREDDecoder state. + * @param[in] dec OpusDREDDecoder*: State to be initialized. + */ +OPUS_EXPORT int opus_dred_decoder_init(OpusDREDDecoder *dec); + +/** Frees an OpusDREDDecoder allocated by opus_dred_decoder_create(). + * @param[in] dec OpusDREDDecoder*: State to be freed. + */ +OPUS_EXPORT void opus_dred_decoder_destroy(OpusDREDDecoder *dec); + +/** Perform a CTL function on an Opus DRED decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param dred_dec OpusDREDDecoder*: DRED Decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_decoderctls. + * @see opus_genericctls + * @see opus_decoderctls + */ +OPUS_EXPORT int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...); + +/** Gets the size of an OpusDRED structure. + * @returns The size in bytes. + */ +OPUS_EXPORT int opus_dred_get_size(void); + +/** Allocates and initializes a DRED state. + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT OpusDRED *opus_dred_alloc(int *error); + +/** Frees an OpusDRED allocated by opus_dred_create(). + * @param[in] dec OpusDRED*: State to be freed. + */ +OPUS_EXPORT void opus_dred_free(OpusDRED *dec); + +/** Decode an Opus DRED packet. + * @param [in] dred_dec OpusDRED*: DRED Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] data char*: Input payload + * @param [in] len opus_int32: Number of bytes in payload + * @param [in] max_dred_samples opus_int32: Maximum number of DRED samples that may be needed (if available in the packet). + * @param [in] sampling_rate opus_int32: Sampling rate used for max_dred_samples argument. Needs not match the actual sampling rate of the decoder. + * @param [out] dred_end opus_int32*: Number of non-encoded (silence) samples between the DRED timestamp and the last DRED sample. + * @param [in] defer_processing int: Flag (0 or 1). If set to one, the CPU-intensive part of the DRED decoding is deferred until opus_dred_process() is called. + * @returns Offset (positive) of the first decoded DRED samples, zero if no DRED is present, or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_dred_parse(OpusDREDDecoder *dred_dec, OpusDRED *dred, const unsigned char *data, opus_int32 len, opus_int32 max_dred_samples, opus_int32 sampling_rate, int *dred_end, int defer_processing) OPUS_ARG_NONNULL(1); + +/** Finish decoding an Opus DRED packet. The function only needs to be called if opus_dred_parse() was called with defer_processing=1. + * The source and destination will often be the same DRED state. + * @param [in] dred_dec OpusDRED*: DRED Decoder state + * @param [in] src OpusDRED*: Source DRED state to start the processing from. + * @param [out] dst OpusDRED*: Destination DRED state to store the updated state after processing. + * @returns @ref opus_errorcodes + */ +OPUS_EXPORT int opus_dred_process(OpusDREDDecoder *dred_dec, const OpusDRED *src, OpusDRED *dst); + +/** Decode audio from an Opus DRED packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] dred_offset opus_int32: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet). + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel to decode in \a pcm. + * frame_size must be a multiple of 2.5 ms. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_dred_decode(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, opus_int16 *pcm, opus_int32 frame_size); + +/** Decode audio from an Opus DRED packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] dred_offset opus_int32: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet). + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel to decode in \a pcm. + * frame_size must be a multiple of 2.5 ms. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_dred_decode_float(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, float *pcm, opus_int32 frame_size); + + +/** Parse an opus packet into one or more frames. + * Opus_decode will perform this operation internally so most applications do + * not need to use this function. + * This function does not copy the frames, the returned pointers are pointers into + * the input packet. + * @param [in] data char*: Opus packet to be parsed + * @param [in] len opus_int32: size of data + * @param [out] out_toc char*: TOC pointer + * @param [out] frames char*[48] encapsulated frames + * @param [out] size opus_int16[48] sizes of the encapsulated frames + * @param [out] payload_offset int*: returns the position of the payload within the packet (in bytes) + * @returns number of frames + */ +OPUS_EXPORT int opus_packet_parse( + const unsigned char *data, + opus_int32 len, + unsigned char *out_toc, + const unsigned char *frames[48], + opus_int16 size[48], + int *payload_offset +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5); + +/** Gets the bandwidth of an Opus packet. + * @param [in] data char*: Opus packet + * @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass) + * @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass) + * @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass) + * @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass) + * @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass) + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples per frame from an Opus packet. + * @param [in] data char*: Opus packet. + * This must contain at least one byte of + * data. + * @param [in] Fs opus_int32: Sampling rate in Hz. + * This must be a multiple of 400, or + * inaccurate results will be returned. + * @returns Number of samples per frame. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1); + +/** Gets the number of channels from an Opus packet. + * @param [in] data char*: Opus packet + * @returns Number of channels + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1); + +/** Gets the number of frames in an Opus packet. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns Number of frames + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples of an Opus packet. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @param [in] Fs opus_int32: Sampling rate in Hz. + * This must be a multiple of 400, or + * inaccurate results will be returned. + * @returns Number of samples + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1); + +/** Checks whether an Opus packet has LBRR. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns 1 is LBRR is present, 0 otherwise + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_has_lbrr(const unsigned char packet[], opus_int32 len); + +/** Gets the number of samples of an Opus packet. + * @param [in] dec OpusDecoder*: Decoder state + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns Number of samples + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + +/** Applies soft-clipping to bring a float signal within the [-1,1] range. If + * the signal is already in that range, nothing is done. If there are values + * outside of [-1,1], then the signal is clipped as smoothly as possible to + * both fit in the range and avoid creating excessive distortion in the + * process. + * @param [in,out] pcm float*: Input PCM and modified PCM + * @param [in] frame_size int Number of samples per channel to process + * @param [in] channels int: Number of channels + * @param [in,out] softclip_mem float*: State memory for the soft clipping process (one float per channel, initialized to zero) + */ +OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem); + + +/**@}*/ + +/** @defgroup opus_repacketizer Repacketizer + * @{ + * + * The repacketizer can be used to merge multiple Opus packets into a single + * packet or alternatively to split Opus packets that have previously been + * merged. Splitting valid Opus packets is always guaranteed to succeed, + * whereas merging valid packets only succeeds if all frames have the same + * mode, bandwidth, and frame size, and when the total duration of the merged + * packet is no more than 120 ms. The 120 ms limit comes from the + * specification and limits decoder memory requirements at a point where + * framing overhead becomes negligible. + * + * The repacketizer currently only operates on elementary Opus + * streams. It will not manipualte multistream packets successfully, except in + * the degenerate case where they consist of data from a single stream. + * + * The repacketizing process starts with creating a repacketizer state, either + * by calling opus_repacketizer_create() or by allocating the memory yourself, + * e.g., + * @code + * OpusRepacketizer *rp; + * rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size()); + * if (rp != NULL) + * opus_repacketizer_init(rp); + * @endcode + * + * Then the application should submit packets with opus_repacketizer_cat(), + * extract new packets with opus_repacketizer_out() or + * opus_repacketizer_out_range(), and then reset the state for the next set of + * input packets via opus_repacketizer_init(). + * + * For example, to split a sequence of packets into individual frames: + * @code + * unsigned char *data; + * int len; + * while (get_next_packet(&data, &len)) + * { + * unsigned char out[1276]; + * opus_int32 out_len; + * int nb_frames; + * int err; + * int i; + * err = opus_repacketizer_cat(rp, data, len); + * if (err != OPUS_OK) + * { + * release_packet(data); + * return err; + * } + * nb_frames = opus_repacketizer_get_nb_frames(rp); + * for (i = 0; i < nb_frames; i++) + * { + * out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out)); + * if (out_len < 0) + * { + * release_packet(data); + * return (int)out_len; + * } + * output_next_packet(out, out_len); + * } + * opus_repacketizer_init(rp); + * release_packet(data); + * } + * @endcode + * + * Alternatively, to combine a sequence of frames into packets that each + * contain up to TARGET_DURATION_MS milliseconds of data: + * @code + * // The maximum number of packets with duration TARGET_DURATION_MS occurs + * // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5) + * // packets. + * unsigned char *data[(TARGET_DURATION_MS*2/5)+1]; + * opus_int32 len[(TARGET_DURATION_MS*2/5)+1]; + * int nb_packets; + * unsigned char out[1277*(TARGET_DURATION_MS*2/2)]; + * opus_int32 out_len; + * int prev_toc; + * nb_packets = 0; + * while (get_next_packet(data+nb_packets, len+nb_packets)) + * { + * int nb_frames; + * int err; + * nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]); + * if (nb_frames < 1) + * { + * release_packets(data, nb_packets+1); + * return nb_frames; + * } + * nb_frames += opus_repacketizer_get_nb_frames(rp); + * // If adding the next packet would exceed our target, or it has an + * // incompatible TOC sequence, output the packets we already have before + * // submitting it. + * // N.B., The nb_packets > 0 check ensures we've submitted at least one + * // packet since the last call to opus_repacketizer_init(). Otherwise a + * // single packet longer than TARGET_DURATION_MS would cause us to try to + * // output an (invalid) empty packet. It also ensures that prev_toc has + * // been set to a valid value. Additionally, len[nb_packets] > 0 is + * // guaranteed by the call to opus_packet_get_nb_frames() above, so the + * // reference to data[nb_packets][0] should be valid. + * if (nb_packets > 0 && ( + * ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) || + * opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames > + * TARGET_DURATION_MS*48)) + * { + * out_len = opus_repacketizer_out(rp, out, sizeof(out)); + * if (out_len < 0) + * { + * release_packets(data, nb_packets+1); + * return (int)out_len; + * } + * output_next_packet(out, out_len); + * opus_repacketizer_init(rp); + * release_packets(data, nb_packets); + * data[0] = data[nb_packets]; + * len[0] = len[nb_packets]; + * nb_packets = 0; + * } + * err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]); + * if (err != OPUS_OK) + * { + * release_packets(data, nb_packets+1); + * return err; + * } + * prev_toc = data[nb_packets][0]; + * nb_packets++; + * } + * // Output the final, partial packet. + * if (nb_packets > 0) + * { + * out_len = opus_repacketizer_out(rp, out, sizeof(out)); + * release_packets(data, nb_packets); + * if (out_len < 0) + * return (int)out_len; + * output_next_packet(out, out_len); + * } + * @endcode + * + * An alternate way of merging packets is to simply call opus_repacketizer_cat() + * unconditionally until it fails. At that point, the merged packet can be + * obtained with opus_repacketizer_out() and the input packet for which + * opus_repacketizer_cat() needs to be re-added to a newly reinitialized + * repacketizer state. + */ + +typedef struct OpusRepacketizer OpusRepacketizer; + +/** Gets the size of an OpusRepacketizer structure. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void); + +/** (Re)initializes a previously allocated repacketizer state. + * The state must be at least the size returned by opus_repacketizer_get_size(). + * This can be used for applications which use their own allocator instead of + * malloc(). + * It must also be called to reset the queue of packets waiting to be + * repacketized, which is necessary if the maximum packet duration of 120 ms + * is reached or if you wish to submit packets with a different Opus + * configuration (coding mode, audio bandwidth, frame size, or channel count). + * Failure to do so will prevent a new packet from being added with + * opus_repacketizer_cat(). + * @see opus_repacketizer_create + * @see opus_repacketizer_get_size + * @see opus_repacketizer_cat + * @param rp OpusRepacketizer*: The repacketizer state to + * (re)initialize. + * @returns A pointer to the same repacketizer state that was passed in. + */ +OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); + +/** Allocates memory and initializes the new repacketizer with + * opus_repacketizer_init(). + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void); + +/** Frees an OpusRepacketizer allocated by + * opus_repacketizer_create(). + * @param[in] rp OpusRepacketizer*: State to be freed. + */ +OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp); + +/** Add a packet to the current repacketizer state. + * This packet must match the configuration of any packets already submitted + * for repacketization since the last call to opus_repacketizer_init(). + * This means that it must have the same coding mode, audio bandwidth, frame + * size, and channel count. + * This can be checked in advance by examining the top 6 bits of the first + * byte of the packet, and ensuring they match the top 6 bits of the first + * byte of any previously submitted packet. + * The total duration of audio in the repacketizer state also must not exceed + * 120 ms, the maximum duration of a single packet, after adding this packet. + * + * The contents of the current repacketizer state can be extracted into new + * packets using opus_repacketizer_out() or opus_repacketizer_out_range(). + * + * In order to add a packet with a different configuration or to add more + * audio beyond 120 ms, you must clear the repacketizer state by calling + * opus_repacketizer_init(). + * If a packet is too large to add to the current repacketizer state, no part + * of it is added, even if it contains multiple frames, some of which might + * fit. + * If you wish to be able to add parts of such packets, you should first use + * another repacketizer to split the packet into pieces and add them + * individually. + * @see opus_repacketizer_out_range + * @see opus_repacketizer_out + * @see opus_repacketizer_init + * @param rp OpusRepacketizer*: The repacketizer state to which to + * add the packet. + * @param[in] data const unsigned char*: The packet data. + * The application must ensure + * this pointer remains valid + * until the next call to + * opus_repacketizer_init() or + * opus_repacketizer_destroy(). + * @param len opus_int32: The number of bytes in the packet data. + * @returns An error code indicating whether or not the operation succeeded. + * @retval #OPUS_OK The packet's contents have been added to the repacketizer + * state. + * @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence, + * the packet's TOC sequence was not compatible + * with previously submitted packets (because + * the coding mode, audio bandwidth, frame size, + * or channel count did not match), or adding + * this packet would increase the total amount of + * audio stored in the repacketizer state to more + * than 120 ms. + */ +OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + + +/** Construct a new packet from data previously submitted to the repacketizer + * state via opus_repacketizer_cat(). + * @param rp OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param begin int: The index of the first frame in the current + * repacketizer state to include in the output. + * @param end int: One past the index of the last frame in the + * current repacketizer state to include in the + * output. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1276 for a single frame, + * or for multiple frames, + * 1277*(end-begin). + * However, 1*(end-begin) plus + * the size of all packet data submitted to + * the repacketizer since the last call to + * opus_repacketizer_init() or + * opus_repacketizer_create() is also + * sufficient, and possibly much smaller. + * @returns The total size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG [begin,end) was an invalid range of + * frames (begin < 0, begin >= end, or end > + * opus_repacketizer_get_nb_frames()). + * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the + * complete output packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Return the total number of frames contained in packet data submitted to + * the repacketizer state so far via opus_repacketizer_cat() since the last + * call to opus_repacketizer_init() or opus_repacketizer_create(). + * This defines the valid range of packets that can be extracted with + * opus_repacketizer_out_range() or opus_repacketizer_out(). + * @param rp OpusRepacketizer*: The repacketizer state containing the + * frames. + * @returns The total number of frames contained in the packet data submitted + * to the repacketizer state. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); + +/** Construct a new packet from data previously submitted to the repacketizer + * state via opus_repacketizer_cat(). + * This is a convenience routine that returns all the data submitted so far + * in a single packet. + * It is equivalent to calling + * @code + * opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp), + * data, maxlen) + * @endcode + * @param rp OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1277*opus_repacketizer_get_nb_frames(rp). + * However, + * 1*opus_repacketizer_get_nb_frames(rp) + * plus the size of all packet data + * submitted to the repacketizer since the + * last call to opus_repacketizer_init() or + * opus_repacketizer_create() is also + * sufficient, and possibly much smaller. + * @returns The total size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the + * complete output packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1); + +/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence). + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least as large as len. + * @returns an error code + * @retval #OPUS_OK \a on success. + * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len); + +/** Remove all padding from a given Opus packet and rewrite the TOC sequence to + * minimize space usage. + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @returns The new size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG \a len was less than 1. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len); + +/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence). + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least 1. + * @param nb_streams opus_int32: The number of streams (not channels) in the packet. + * This must be at least as large as len. + * @returns an error code + * @retval #OPUS_OK \a on success. + * @retval #OPUS_BAD_ARG \a len was less than 1. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams); + +/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to + * minimize space usage. + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param nb_streams opus_int32: The number of streams (not channels) in the packet. + * This must be at least 1. + * @returns The new size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_H */ diff --git a/port/third_party/opus/include/opus_custom.h b/port/third_party/opus/include/opus_custom.h new file mode 100644 index 00000000..2f22d4b3 --- /dev/null +++ b/port/third_party/opus/include/opus_custom.h @@ -0,0 +1,343 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008-2012 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +/** + @file opus_custom.h + @brief Opus-Custom reference implementation API + */ + +#ifndef OPUS_CUSTOM_H +#define OPUS_CUSTOM_H + +#include "opus_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef CUSTOM_MODES +# define OPUS_CUSTOM_EXPORT OPUS_EXPORT +# define OPUS_CUSTOM_EXPORT_STATIC OPUS_EXPORT +#else +# define OPUS_CUSTOM_EXPORT +# ifdef OPUS_BUILD +# define OPUS_CUSTOM_EXPORT_STATIC static OPUS_INLINE +# else +# define OPUS_CUSTOM_EXPORT_STATIC +# endif +#endif + +/** @defgroup opus_custom Opus Custom + * @{ + * Opus Custom is an optional part of the Opus specification and + * reference implementation which uses a distinct API from the regular + * API and supports frame sizes that are not normally supported.\ Use + * of Opus Custom is discouraged for all but very special applications + * for which a frame size different from 2.5, 5, 10, or 20 ms is needed + * (for either complexity or latency reasons) and where interoperability + * is less important. + * + * In addition to the interoperability limitations the use of Opus custom + * disables a substantial chunk of the codec and generally lowers the + * quality available at a given bitrate. Normally when an application needs + * a different frame size from the codec it should buffer to match the + * sizes but this adds a small amount of delay which may be important + * in some very low latency applications. Some transports (especially + * constant rate RF transports) may also work best with frames of + * particular durations. + * + * Libopus only supports custom modes if they are enabled at compile time. + * + * The Opus Custom API is similar to the regular API but the + * @ref opus_encoder_create and @ref opus_decoder_create calls take + * an additional mode parameter which is a structure produced by + * a call to @ref opus_custom_mode_create. Both the encoder and decoder + * must create a mode using the same sample rate (fs) and frame size + * (frame size) so these parameters must either be signaled out of band + * or fixed in a particular implementation. + * + * Similar to regular Opus the custom modes support on the fly frame size + * switching, but the sizes available depend on the particular frame size in + * use. For some initial frame sizes on a single on the fly size is available. + */ + +/** Contains the state of an encoder. One encoder state is needed + for each stream. It is initialized once at the beginning of the + stream. Do *not* re-initialize the state for every frame. + @brief Encoder state + */ +typedef struct OpusCustomEncoder OpusCustomEncoder; + +/** State of the decoder. One decoder state is needed for each stream. + It is initialized once at the beginning of the stream. Do *not* + re-initialize the state for every frame. + @brief Decoder state + */ +typedef struct OpusCustomDecoder OpusCustomDecoder; + +/** The mode contains all the information necessary to create an + encoder. Both the encoder and decoder need to be initialized + with exactly the same mode, otherwise the output will be + corrupted. The mode MUST NOT BE DESTROYED until the encoders and + decoders that use it are destroyed as well. + @brief Mode configuration + */ +typedef struct OpusCustomMode OpusCustomMode; + +/** Creates a new mode struct. This will be passed to an encoder or + * decoder. The mode MUST NOT BE DESTROYED until the encoders and + * decoders that use it are destroyed as well. + * @param [in] Fs int: Sampling rate (8000 to 96000 Hz) + * @param [in] frame_size int: Number of samples (per channel) to encode in each + * packet (64 - 1024, prime factorization must contain zero or more 2s, 3s, or 5s and no other primes) + * @param [out] error int*: Returned error code (if NULL, no error will be returned) + * @return A newly created mode + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error); + +/** Destroys a mode struct. Only call this after all encoders and + * decoders using this mode are destroyed as well. + * @param [in] mode OpusCustomMode*: Mode to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_mode_destroy(OpusCustomMode *mode); + + +#if !defined(OPUS_BUILD) || defined(CELT_ENCODER_C) + +/* Encoder */ +/** Gets the size of an OpusCustomEncoder structure. + * @param [in] mode OpusCustomMode *: Mode configuration + * @param [in] channels int: Number of channels + * @returns size + */ +OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_encoder_get_size( + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1); + +# ifdef CUSTOM_MODES +/** Initializes a previously allocated encoder state + * The memory pointed to by st must be the size returned by opus_custom_encoder_get_size. + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_custom_encoder_create(),opus_custom_encoder_get_size() + * To reset a previously initialized state use the OPUS_RESET_STATE CTL. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] mode OpusCustomMode *: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * decoder) + * @param [in] channels int: Number of channels + * @return OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT int opus_custom_encoder_init( + OpusCustomEncoder *st, + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); +# endif +#endif + + +/** Creates a new encoder state. Each stream needs its own encoder + * state (can't be shared across simultaneous streams). + * @param [in] mode OpusCustomMode*: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * decoder) + * @param [in] channels int: Number of channels + * @param [out] error int*: Returns an error code + * @return Newly created encoder state. +*/ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomEncoder *opus_custom_encoder_create( + const OpusCustomMode *mode, + int channels, + int *error +) OPUS_ARG_NONNULL(1); + + +/** Destroys an encoder state. + * @param[in] st OpusCustomEncoder*: State to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_encoder_destroy(OpusCustomEncoder *st); + +/** Encodes a frame of audio. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] pcm float*: PCM audio in float format, with a normal range of +/-1.0. + * Samples with a range beyond +/-1.0 are supported but will + * be clipped by decoders using the integer API and should + * only be used if it is known that the far end supports + * extended dynamic range. There must be exactly + * frame_size samples per channel. + * @param [in] frame_size int: Number of samples per frame of input signal + * @param [out] compressed char *: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long. + * @param [in] maxCompressedBytes int: Maximum number of bytes to use for compressing the frame + * (can change from one frame to another) + * @return Number of bytes written to "compressed". + * If negative, an error has occurred (see error codes). It is IMPORTANT that + * the length returned be somehow transmitted to the decoder. Otherwise, no + * decoding is possible. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode_float( + OpusCustomEncoder *st, + const float *pcm, + int frame_size, + unsigned char *compressed, + int maxCompressedBytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes a frame of audio. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] pcm opus_int16*: PCM audio in signed 16-bit format (native endian). + * There must be exactly frame_size samples per channel. + * @param [in] frame_size int: Number of samples per frame of input signal + * @param [out] compressed char *: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long. + * @param [in] maxCompressedBytes int: Maximum number of bytes to use for compressing the frame + * (can change from one frame to another) + * @return Number of bytes written to "compressed". + * If negative, an error has occurred (see error codes). It is IMPORTANT that + * the length returned be somehow transmitted to the decoder. Otherwise, no + * decoding is possible. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode( + OpusCustomEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *compressed, + int maxCompressedBytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus custom encoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @see opus_encoderctls + */ +OPUS_CUSTOM_EXPORT int opus_custom_encoder_ctl(OpusCustomEncoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1); + + +#if !defined(OPUS_BUILD) || defined(CELT_DECODER_C) +/* Decoder */ + +/** Gets the size of an OpusCustomDecoder structure. + * @param [in] mode OpusCustomMode *: Mode configuration + * @param [in] channels int: Number of channels + * @returns size + */ +OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_decoder_get_size( + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1); + +/** Initializes a previously allocated decoder state + * The memory pointed to by st must be the size returned by opus_custom_decoder_get_size. + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_custom_decoder_create(),opus_custom_decoder_get_size() + * To reset a previously initialized state use the OPUS_RESET_STATE CTL. + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] mode OpusCustomMode *: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * encoder) + * @param [in] channels int: Number of channels + * @return OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT_STATIC int opus_custom_decoder_init( + OpusCustomDecoder *st, + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + +#endif + + +/** Creates a new decoder state. Each stream needs its own decoder state (can't + * be shared across simultaneous streams). + * @param [in] mode OpusCustomMode: Contains all the information about the characteristics of the + * stream (must be the same characteristics as used for the encoder) + * @param [in] channels int: Number of channels + * @param [out] error int*: Returns an error code + * @return Newly created decoder state. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomDecoder *opus_custom_decoder_create( + const OpusCustomMode *mode, + int channels, + int *error +) OPUS_ARG_NONNULL(1); + +/** Destroys a decoder state. + * @param[in] st OpusCustomDecoder*: State to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_decoder_destroy(OpusCustomDecoder *st); + +/** Decode an opus custom frame with floating point output + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len int: Number of bytes in payload + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel of available space in *pcm. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode_float( + OpusCustomDecoder *st, + const unsigned char *data, + int len, + float *pcm, + int frame_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode an opus custom frame + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len int: Number of bytes in payload + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel of available space in *pcm. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode( + OpusCustomDecoder *st, + const unsigned char *data, + int len, + opus_int16 *pcm, + int frame_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus custom decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @see opus_genericctls + */ +OPUS_CUSTOM_EXPORT int opus_custom_decoder_ctl(OpusCustomDecoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_CUSTOM_H */ diff --git a/port/third_party/opus/include/opus_defines.h b/port/third_party/opus/include/opus_defines.h new file mode 100644 index 00000000..cd8f4dde --- /dev/null +++ b/port/third_party/opus/include/opus_defines.h @@ -0,0 +1,830 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +/** + * @file opus_defines.h + * @brief Opus reference implementation constants + */ + +#ifndef OPUS_DEFINES_H +#define OPUS_DEFINES_H + +#include "opus_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup opus_errorcodes Error codes + * @{ + */ +/** No error @hideinitializer*/ +#define OPUS_OK 0 +/** One or more invalid/out of range arguments @hideinitializer*/ +#define OPUS_BAD_ARG -1 +/** Not enough bytes allocated in the buffer @hideinitializer*/ +#define OPUS_BUFFER_TOO_SMALL -2 +/** An internal error was detected @hideinitializer*/ +#define OPUS_INTERNAL_ERROR -3 +/** The compressed data passed is corrupted @hideinitializer*/ +#define OPUS_INVALID_PACKET -4 +/** Invalid/unsupported request number @hideinitializer*/ +#define OPUS_UNIMPLEMENTED -5 +/** An encoder or decoder structure is invalid or already freed @hideinitializer*/ +#define OPUS_INVALID_STATE -6 +/** Memory allocation has failed @hideinitializer*/ +#define OPUS_ALLOC_FAIL -7 +/**@}*/ + +/** @cond OPUS_INTERNAL_DOC */ +/**Export control for opus functions */ + +#ifndef OPUS_EXPORT +# if defined(_WIN32) +# if defined(OPUS_BUILD) && defined(DLL_EXPORT) +# define OPUS_EXPORT __declspec(dllexport) +# else +# define OPUS_EXPORT +# endif +# elif defined(__GNUC__) && defined(OPUS_BUILD) +# define OPUS_EXPORT __attribute__ ((visibility ("default"))) +# else +# define OPUS_EXPORT +# endif +#endif + +# if !defined(OPUS_GNUC_PREREQ) +# if defined(__GNUC__)&&defined(__GNUC_MINOR__) +# define OPUS_GNUC_PREREQ(_maj,_min) \ + ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) +# else +# define OPUS_GNUC_PREREQ(_maj,_min) 0 +# endif +# endif + +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if OPUS_GNUC_PREREQ(3,0) +# define OPUS_RESTRICT __restrict__ +# elif (defined(_MSC_VER) && _MSC_VER >= 1400) +# define OPUS_RESTRICT __restrict +# else +# define OPUS_RESTRICT +# endif +#else +# define OPUS_RESTRICT restrict +#endif + +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if OPUS_GNUC_PREREQ(2,7) +# define OPUS_INLINE __inline__ +# elif (defined(_MSC_VER)) +# define OPUS_INLINE __inline +# else +# define OPUS_INLINE +# endif +#else +# define OPUS_INLINE inline +#endif + +/**Warning attributes for opus functions + * NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out + * some paranoid null checks. */ +#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) +# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__)) +#else +# define OPUS_WARN_UNUSED_RESULT +#endif +#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) +# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x))) +#else +# define OPUS_ARG_NONNULL(_x) +#endif + +/** These are the actual Encoder CTL ID numbers. + * They should not be used directly by applications. + * In general, SETs should be even and GETs should be odd.*/ +#define OPUS_SET_APPLICATION_REQUEST 4000 +#define OPUS_GET_APPLICATION_REQUEST 4001 +#define OPUS_SET_BITRATE_REQUEST 4002 +#define OPUS_GET_BITRATE_REQUEST 4003 +#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004 +#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005 +#define OPUS_SET_VBR_REQUEST 4006 +#define OPUS_GET_VBR_REQUEST 4007 +#define OPUS_SET_BANDWIDTH_REQUEST 4008 +#define OPUS_GET_BANDWIDTH_REQUEST 4009 +#define OPUS_SET_COMPLEXITY_REQUEST 4010 +#define OPUS_GET_COMPLEXITY_REQUEST 4011 +#define OPUS_SET_INBAND_FEC_REQUEST 4012 +#define OPUS_GET_INBAND_FEC_REQUEST 4013 +#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014 +#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015 +#define OPUS_SET_DTX_REQUEST 4016 +#define OPUS_GET_DTX_REQUEST 4017 +#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020 +#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021 +#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022 +#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023 +#define OPUS_SET_SIGNAL_REQUEST 4024 +#define OPUS_GET_SIGNAL_REQUEST 4025 +#define OPUS_GET_LOOKAHEAD_REQUEST 4027 +/* #define OPUS_RESET_STATE 4028 */ +#define OPUS_GET_SAMPLE_RATE_REQUEST 4029 +#define OPUS_GET_FINAL_RANGE_REQUEST 4031 +#define OPUS_GET_PITCH_REQUEST 4033 +#define OPUS_SET_GAIN_REQUEST 4034 +#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */ +#define OPUS_SET_LSB_DEPTH_REQUEST 4036 +#define OPUS_GET_LSB_DEPTH_REQUEST 4037 +#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039 +#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040 +#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041 +#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042 +#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043 +/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */ +#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046 +#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047 +#define OPUS_GET_IN_DTX_REQUEST 4049 +#define OPUS_SET_DRED_DURATION_REQUEST 4050 +#define OPUS_GET_DRED_DURATION_REQUEST 4051 +#define OPUS_SET_DNN_BLOB_REQUEST 4052 +/*#define OPUS_GET_DNN_BLOB_REQUEST 4053 */ + +/** Defines for the presence of extended APIs. */ +#define OPUS_HAVE_OPUS_PROJECTION_H + +/* Macros to trigger compilation errors when the wrong types are provided to a CTL */ +#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x)) + +#ifdef DISABLE_PTR_CHECK +/* Disable checks to prevent ubsan from complaining about NULL checks + in test_opus_api. */ +#define __opus_check_int_ptr(ptr) (ptr) +#define __opus_check_uint_ptr(ptr) (ptr) +#define __opus_check_uint8_ptr(ptr) (ptr) +#define __opus_check_val16_ptr(ptr) (ptr) +#define __opus_check_void_ptr(ptr) (ptr) +#else +#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr))) +#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr))) +#define __opus_check_uint8_ptr(ptr) ((ptr) + ((ptr) - (opus_uint8*)(ptr))) +#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr))) +#define __opus_check_void_ptr(x) ((void)((void *)0 == (x)), (x)) +#endif +/** @endcond */ + +/** @defgroup opus_ctlvalues Pre-defined values for CTL interface + * @see opus_genericctls, opus_encoderctls + * @{ + */ +/* Values for the various encoder CTLs */ +#define OPUS_AUTO -1000 /**opus_int32: Allowed values: 0-10, inclusive. + * + * @hideinitializer */ +#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x) +/** Gets the encoder's complexity configuration. + * @see OPUS_SET_COMPLEXITY + * @param[out] x opus_int32 *: Returns a value in the range 0-10, + * inclusive. + * @hideinitializer */ +#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x) + +/** Configures the bitrate in the encoder. + * Rates from 500 to 512000 bits per second are meaningful, as well as the + * special values #OPUS_AUTO and #OPUS_BITRATE_MAX. + * The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much + * rate as it can, which is useful for controlling the rate by adjusting the + * output buffer size. + * @see OPUS_GET_BITRATE + * @param[in] x opus_int32: Bitrate in bits per second. The default + * is determined based on the number of + * channels and the input sampling rate. + * @hideinitializer */ +#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x) +/** Gets the encoder's bitrate configuration. + * @see OPUS_SET_BITRATE + * @param[out] x opus_int32 *: Returns the bitrate in bits per second. + * The default is determined based on the + * number of channels and the input + * sampling rate. + * @hideinitializer */ +#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x) + +/** Enables or disables variable bitrate (VBR) in the encoder. + * The configured bitrate may not be met exactly because frames must + * be an integer number of bytes in length. + * @see OPUS_GET_VBR + * @see OPUS_SET_VBR_CONSTRAINT + * @param[in] x opus_int32: Allowed values: + *
    + *
    0
    Hard CBR. For LPC/hybrid modes at very low bit-rate, this can + * cause noticeable quality degradation.
    + *
    1
    VBR (default). The exact type of VBR is controlled by + * #OPUS_SET_VBR_CONSTRAINT.
    + *
    + * @hideinitializer */ +#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x) +/** Determine if variable bitrate (VBR) is enabled in the encoder. + * @see OPUS_SET_VBR + * @see OPUS_GET_VBR_CONSTRAINT + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    0
    Hard CBR.
    + *
    1
    VBR (default). The exact type of VBR may be retrieved via + * #OPUS_GET_VBR_CONSTRAINT.
    + *
    + * @hideinitializer */ +#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x) + +/** Enables or disables constrained VBR in the encoder. + * This setting is ignored when the encoder is in CBR mode. + * @warning Only the MDCT mode of Opus currently heeds the constraint. + * Speech mode ignores it completely, hybrid mode may fail to obey it + * if the LPC layer uses more bitrate than the constraint would have + * permitted. + * @see OPUS_GET_VBR_CONSTRAINT + * @see OPUS_SET_VBR + * @param[in] x opus_int32: Allowed values: + *
    + *
    0
    Unconstrained VBR.
    + *
    1
    Constrained VBR (default). This creates a maximum of one + * frame of buffering delay assuming a transport with a + * serialization speed of the nominal bitrate.
    + *
    + * @hideinitializer */ +#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x) +/** Determine if constrained VBR is enabled in the encoder. + * @see OPUS_SET_VBR_CONSTRAINT + * @see OPUS_GET_VBR + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    0
    Unconstrained VBR.
    + *
    1
    Constrained VBR (default).
    + *
    + * @hideinitializer */ +#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x) + +/** Configures mono/stereo forcing in the encoder. + * This can force the encoder to produce packets encoded as either mono or + * stereo, regardless of the format of the input audio. This is useful when + * the caller knows that the input signal is currently a mono source embedded + * in a stereo stream. + * @see OPUS_GET_FORCE_CHANNELS + * @param[in] x opus_int32: Allowed values: + *
    + *
    #OPUS_AUTO
    Not forced (default)
    + *
    1
    Forced mono
    + *
    2
    Forced stereo
    + *
    + * @hideinitializer */ +#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x) +/** Gets the encoder's forced channel configuration. + * @see OPUS_SET_FORCE_CHANNELS + * @param[out] x opus_int32 *: + *
    + *
    #OPUS_AUTO
    Not forced (default)
    + *
    1
    Forced mono
    + *
    2
    Forced stereo
    + *
    + * @hideinitializer */ +#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x) + +/** Configures the maximum bandpass that the encoder will select automatically. + * Applications should normally use this instead of #OPUS_SET_BANDWIDTH + * (leaving that set to the default, #OPUS_AUTO). This allows the + * application to set an upper bound based on the type of input it is + * providing, but still gives the encoder the freedom to reduce the bandpass + * when the bitrate becomes too low, for better overall quality. + * @see OPUS_GET_MAX_BANDWIDTH + * @param[in] x opus_int32: Allowed values: + *
    + *
    OPUS_BANDWIDTH_NARROWBAND
    4 kHz passband
    + *
    OPUS_BANDWIDTH_MEDIUMBAND
    6 kHz passband
    + *
    OPUS_BANDWIDTH_WIDEBAND
    8 kHz passband
    + *
    OPUS_BANDWIDTH_SUPERWIDEBAND
    12 kHz passband
    + *
    OPUS_BANDWIDTH_FULLBAND
    20 kHz passband (default)
    + *
    + * @hideinitializer */ +#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x) + +/** Gets the encoder's configured maximum allowed bandpass. + * @see OPUS_SET_MAX_BANDWIDTH + * @param[out] x opus_int32 *: Allowed values: + *
    + *
    #OPUS_BANDWIDTH_NARROWBAND
    4 kHz passband
    + *
    #OPUS_BANDWIDTH_MEDIUMBAND
    6 kHz passband
    + *
    #OPUS_BANDWIDTH_WIDEBAND
    8 kHz passband
    + *
    #OPUS_BANDWIDTH_SUPERWIDEBAND
    12 kHz passband
    + *
    #OPUS_BANDWIDTH_FULLBAND
    20 kHz passband (default)
    + *
    + * @hideinitializer */ +#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) + +/** Sets the encoder's bandpass to a specific value. + * This prevents the encoder from automatically selecting the bandpass based + * on the available bitrate. If an application knows the bandpass of the input + * audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH + * instead, which still gives the encoder the freedom to reduce the bandpass + * when the bitrate becomes too low, for better overall quality. + * @see OPUS_GET_BANDWIDTH + * @param[in] x opus_int32: Allowed values: + *
    + *
    #OPUS_AUTO
    (default)
    + *
    #OPUS_BANDWIDTH_NARROWBAND
    4 kHz passband
    + *
    #OPUS_BANDWIDTH_MEDIUMBAND
    6 kHz passband
    + *
    #OPUS_BANDWIDTH_WIDEBAND
    8 kHz passband
    + *
    #OPUS_BANDWIDTH_SUPERWIDEBAND
    12 kHz passband
    + *
    #OPUS_BANDWIDTH_FULLBAND
    20 kHz passband
    + *
    + * @hideinitializer */ +#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x) + +/** Configures the type of signal being encoded. + * This is a hint which helps the encoder's mode selection. + * @see OPUS_GET_SIGNAL + * @param[in] x opus_int32: Allowed values: + *
    + *
    #OPUS_AUTO
    (default)
    + *
    #OPUS_SIGNAL_VOICE
    Bias thresholds towards choosing LPC or Hybrid modes.
    + *
    #OPUS_SIGNAL_MUSIC
    Bias thresholds towards choosing MDCT modes.
    + *
    + * @hideinitializer */ +#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured signal type. + * @see OPUS_SET_SIGNAL + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    #OPUS_AUTO
    (default)
    + *
    #OPUS_SIGNAL_VOICE
    Bias thresholds towards choosing LPC or Hybrid modes.
    + *
    #OPUS_SIGNAL_MUSIC
    Bias thresholds towards choosing MDCT modes.
    + *
    + * @hideinitializer */ +#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x) + + +/** Configures the encoder's intended application. + * The initial value is a mandatory argument to the encoder_create function. + * @see OPUS_GET_APPLICATION + * @param[in] x opus_int32: Returns one of the following values: + *
    + *
    #OPUS_APPLICATION_VOIP
    + *
    Process signal for improved speech intelligibility.
    + *
    #OPUS_APPLICATION_AUDIO
    + *
    Favor faithfulness to the original input.
    + *
    #OPUS_APPLICATION_RESTRICTED_LOWDELAY
    + *
    Configure the minimum possible coding delay by disabling certain modes + * of operation.
    + *
    + * @hideinitializer */ +#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured application. + * @see OPUS_SET_APPLICATION + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    #OPUS_APPLICATION_VOIP
    + *
    Process signal for improved speech intelligibility.
    + *
    #OPUS_APPLICATION_AUDIO
    + *
    Favor faithfulness to the original input.
    + *
    #OPUS_APPLICATION_RESTRICTED_LOWDELAY
    + *
    Configure the minimum possible coding delay by disabling certain modes + * of operation.
    + *
    + * @hideinitializer */ +#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the total samples of delay added by the entire codec. + * This can be queried by the encoder and then the provided number of samples can be + * skipped on from the start of the decoder's output to provide time aligned input + * and output. From the perspective of a decoding application the real data begins this many + * samples late. + * + * The decoder contribution to this delay is identical for all decoders, but the + * encoder portion of the delay may vary from implementation to implementation, + * version to version, or even depend on the encoder's initial configuration. + * Applications needing delay compensation should call this CTL rather than + * hard-coding a value. + * @param[out] x opus_int32 *: Number of lookahead samples + * @hideinitializer */ +#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of inband forward error correction (FEC). + * @note This is only applicable to the LPC layer + * @see OPUS_GET_INBAND_FEC + * @param[in] x opus_int32: Allowed values: + *
    + *
    0
    Disable inband FEC (default).
    + *
    1
    Inband FEC enabled. If the packet loss rate is sufficiently high, Opus will automatically switch to SILK even at high rates to enable use of that FEC.
    + *
    2
    Inband FEC enabled, but does not necessarily switch to SILK if we have music.
    + *
    + * @hideinitializer */ +#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x) +/** Gets encoder's configured use of inband forward error correction. + * @see OPUS_SET_INBAND_FEC + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    0
    Inband FEC disabled (default).
    + *
    1
    Inband FEC enabled. If the packet loss rate is sufficiently high, Opus will automatically switch to SILK even at high rates to enable use of that FEC.
    + *
    2
    Inband FEC enabled, but does not necessarily switch to SILK if we have music.
    + *
    + * @hideinitializer */ +#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's expected packet loss percentage. + * Higher values trigger progressively more loss resistant behavior in the encoder + * at the expense of quality at a given bitrate in the absence of packet loss, but + * greater quality under loss. + * @see OPUS_GET_PACKET_LOSS_PERC + * @param[in] x opus_int32: Loss percentage in the range 0-100, inclusive (default: 0). + * @hideinitializer */ +#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured packet loss percentage. + * @see OPUS_SET_PACKET_LOSS_PERC + * @param[out] x opus_int32 *: Returns the configured loss percentage + * in the range 0-100, inclusive (default: 0). + * @hideinitializer */ +#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of discontinuous transmission (DTX). + * @note This is only applicable to the LPC layer + * @see OPUS_GET_DTX + * @param[in] x opus_int32: Allowed values: + *
    + *
    0
    Disable DTX (default).
    + *
    1
    Enabled DTX.
    + *
    + * @hideinitializer */ +#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x) +/** Gets encoder's configured use of discontinuous transmission. + * @see OPUS_SET_DTX + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    0
    DTX disabled (default).
    + *
    1
    DTX enabled.
    + *
    + * @hideinitializer */ +#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x) +/** Configures the depth of signal being encoded. + * + * This is a hint which helps the encoder identify silence and near-silence. + * It represents the number of significant bits of linear intensity below + * which the signal contains ignorable quantization or other noise. + * + * For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting + * for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate + * for 16-bit linear pcm input with opus_encode_float(). + * + * When using opus_encode() instead of opus_encode_float(), or when libopus + * is compiled for fixed-point, the encoder uses the minimum of the value + * set here and the value 16. + * + * @see OPUS_GET_LSB_DEPTH + * @param[in] x opus_int32: Input precision in bits, between 8 and 24 + * (default: 24). + * @hideinitializer */ +#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured signal depth. + * @see OPUS_SET_LSB_DEPTH + * @param[out] x opus_int32 *: Input precision in bits, between 8 and + * 24 (default: 24). + * @hideinitializer */ +#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of variable duration frames. + * When variable duration is enabled, the encoder is free to use a shorter frame + * size than the one requested in the opus_encode*() call. + * It is then the user's responsibility + * to verify how much audio was encoded by checking the ToC byte of the encoded + * packet. The part of the audio that was not encoded needs to be resent to the + * encoder for the next call. Do not use this option unless you really + * know what you are doing. + * @see OPUS_GET_EXPERT_FRAME_DURATION + * @param[in] x opus_int32: Allowed values: + *
    + *
    OPUS_FRAMESIZE_ARG
    Select frame size from the argument (default).
    + *
    OPUS_FRAMESIZE_2_5_MS
    Use 2.5 ms frames.
    + *
    OPUS_FRAMESIZE_5_MS
    Use 5 ms frames.
    + *
    OPUS_FRAMESIZE_10_MS
    Use 10 ms frames.
    + *
    OPUS_FRAMESIZE_20_MS
    Use 20 ms frames.
    + *
    OPUS_FRAMESIZE_40_MS
    Use 40 ms frames.
    + *
    OPUS_FRAMESIZE_60_MS
    Use 60 ms frames.
    + *
    OPUS_FRAMESIZE_80_MS
    Use 80 ms frames.
    + *
    OPUS_FRAMESIZE_100_MS
    Use 100 ms frames.
    + *
    OPUS_FRAMESIZE_120_MS
    Use 120 ms frames.
    + *
    + * @hideinitializer */ +#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured use of variable duration frames. + * @see OPUS_SET_EXPERT_FRAME_DURATION + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    OPUS_FRAMESIZE_ARG
    Select frame size from the argument (default).
    + *
    OPUS_FRAMESIZE_2_5_MS
    Use 2.5 ms frames.
    + *
    OPUS_FRAMESIZE_5_MS
    Use 5 ms frames.
    + *
    OPUS_FRAMESIZE_10_MS
    Use 10 ms frames.
    + *
    OPUS_FRAMESIZE_20_MS
    Use 20 ms frames.
    + *
    OPUS_FRAMESIZE_40_MS
    Use 40 ms frames.
    + *
    OPUS_FRAMESIZE_60_MS
    Use 60 ms frames.
    + *
    OPUS_FRAMESIZE_80_MS
    Use 80 ms frames.
    + *
    OPUS_FRAMESIZE_100_MS
    Use 100 ms frames.
    + *
    OPUS_FRAMESIZE_120_MS
    Use 120 ms frames.
    + *
    + * @hideinitializer */ +#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables almost all use of prediction, making frames almost + * completely independent. This reduces quality. + * @see OPUS_GET_PREDICTION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
    + *
    0
    Enable prediction (default).
    + *
    1
    Disable prediction.
    + *
    + * @hideinitializer */ +#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured prediction status. + * @see OPUS_SET_PREDICTION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    0
    Prediction enabled (default).
    + *
    1
    Prediction disabled.
    + *
    + * @hideinitializer */ +#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x) + +/** If non-zero, enables Deep Redundancy (DRED) and use the specified maximum number of 10-ms redundant frames + * @hideinitializer */ +#define OPUS_SET_DRED_DURATION(x) OPUS_SET_DRED_DURATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured Deep Redundancy (DRED) maximum number of frames. + * @hideinitializer */ +#define OPUS_GET_DRED_DURATION(x) OPUS_GET_DRED_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** Provide external DNN weights from binary object (only when explicitly built without the weights) + * @hideinitializer */ +#define OPUS_SET_DNN_BLOB(data, len) OPUS_SET_DNN_BLOB_REQUEST, __opus_check_void_ptr(data), __opus_check_int(len) + + +/**@}*/ + +/** @defgroup opus_genericctls Generic CTLs + * + * These macros are used with the \c opus_decoder_ctl and + * \c opus_encoder_ctl calls to generate a particular + * request. + * + * When called on an \c OpusDecoder they apply to that + * particular decoder instance. When called on an + * \c OpusEncoder they apply to the corresponding setting + * on that encoder instance, if present. + * + * Some usage examples: + * + * @code + * int ret; + * opus_int32 pitch; + * ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch)); + * if (ret == OPUS_OK) return ret; + * + * opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE); + * opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE); + * + * opus_int32 enc_bw, dec_bw; + * opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw)); + * opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw)); + * if (enc_bw != dec_bw) { + * printf("packet bandwidth mismatch!\n"); + * } + * @endcode + * + * @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls + * @{ + */ + +/** Resets the codec state to be equivalent to a freshly initialized state. + * This should be called when switching streams in order to prevent + * the back to back decoding from giving different results from + * one at a time decoding. + * @hideinitializer */ +#define OPUS_RESET_STATE 4028 + +/** Gets the final state of the codec's entropy coder. + * This is used for testing purposes, + * The encoder and decoder state should be identical after coding a payload + * (assuming no data corruption or software bugs) + * + * @param[out] x opus_uint32 *: Entropy coder state + * + * @hideinitializer */ +#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x) + +/** Gets the encoder's configured bandpass or the decoder's last bandpass. + * @see OPUS_SET_BANDWIDTH + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    #OPUS_AUTO
    (default)
    + *
    #OPUS_BANDWIDTH_NARROWBAND
    4 kHz passband
    + *
    #OPUS_BANDWIDTH_MEDIUMBAND
    6 kHz passband
    + *
    #OPUS_BANDWIDTH_WIDEBAND
    8 kHz passband
    + *
    #OPUS_BANDWIDTH_SUPERWIDEBAND
    12 kHz passband
    + *
    #OPUS_BANDWIDTH_FULLBAND
    20 kHz passband
    + *
    + * @hideinitializer */ +#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) + +/** Gets the sampling rate the encoder or decoder was initialized with. + * This simply returns the Fs value passed to opus_encoder_init() + * or opus_decoder_init(). + * @param[out] x opus_int32 *: Sampling rate of encoder or decoder. + * @hideinitializer + */ +#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables the use of phase inversion for intensity stereo, + * improving the quality of mono downmixes, but slightly reducing normal + * stereo quality. Disabling phase inversion in the decoder does not comply + * with RFC 6716, although it does not cause any interoperability issue and + * is expected to become part of the Opus standard once RFC 6716 is updated + * by draft-ietf-codec-opus-update. + * @see OPUS_GET_PHASE_INVERSION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
    + *
    0
    Enable phase inversion (default).
    + *
    1
    Disable phase inversion.
    + *
    + * @hideinitializer */ +#define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured phase inversion status. + * @see OPUS_SET_PHASE_INVERSION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    0
    Stereo phase inversion enabled (default).
    + *
    1
    Stereo phase inversion disabled.
    + *
    + * @hideinitializer */ +#define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x) +/** Gets the DTX state of the encoder. + * Returns whether the last encoded frame was either a comfort noise update + * during DTX or not encoded because of DTX. + * @param[out] x opus_int32 *: Returns one of the following values: + *
    + *
    0
    The encoder is not in DTX.
    + *
    1
    The encoder is in DTX.
    + *
    + * @hideinitializer */ +#define OPUS_GET_IN_DTX(x) OPUS_GET_IN_DTX_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_decoderctls Decoder related CTLs + * @see opus_genericctls, opus_encoderctls, opus_decoder + * @{ + */ + +/** Configures decoder gain adjustment. + * Scales the decoded output by a factor specified in Q8 dB units. + * This has a maximum range of -32768 to 32767 inclusive, and returns + * OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment. + * This setting survives decoder reset. + * + * gain = pow(10, x/(20.0*256)) + * + * @param[in] x opus_int32: Amount to scale PCM signal by in Q8 dB units. + * @hideinitializer */ +#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x) +/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN + * + * @param[out] x opus_int32 *: Amount to scale PCM signal by in Q8 dB units. + * @hideinitializer */ +#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x) + +/** Gets the duration (in samples) of the last packet successfully decoded or concealed. + * @param[out] x opus_int32 *: Number of samples (at current sampling rate). + * @hideinitializer */ +#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the pitch of the last decoded frame, if available. + * This can be used for any post-processing algorithm requiring the use of pitch, + * e.g. time stretching/shortening. If the last frame was not voiced, or if the + * pitch was not coded in the frame, then zero is returned. + * + * This CTL is only implemented for decoder instances. + * + * @param[out] x opus_int32 *: pitch period at 48 kHz (or 0 if not available) + * + * @hideinitializer */ +#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_libinfo Opus library information functions + * @{ + */ + +/** Converts an opus error code into a human readable string. + * + * @param[in] error int: Error number + * @returns Error string + */ +OPUS_EXPORT const char *opus_strerror(int error); + +/** Gets the libopus version string. + * + * Applications may look for the substring "-fixed" in the version string to + * determine whether they have a fixed-point or floating-point build at + * runtime. + * + * @returns Version string + */ +OPUS_EXPORT const char *opus_get_version_string(void); +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_DEFINES_H */ diff --git a/port/third_party/opus/include/opus_multistream.h b/port/third_party/opus/include/opus_multistream.h new file mode 100644 index 00000000..824cc55a --- /dev/null +++ b/port/third_party/opus/include/opus_multistream.h @@ -0,0 +1,660 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +/** + * @file opus_multistream.h + * @brief Opus reference implementation multistream API + */ + +#ifndef OPUS_MULTISTREAM_H +#define OPUS_MULTISTREAM_H + +#include "opus.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond OPUS_INTERNAL_DOC */ + +/** Macros to trigger compilation errors when the wrong types are provided to a + * CTL. */ +/**@{*/ +#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr))) +#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr))) +/**@}*/ + +/** These are the actual encoder and decoder CTL ID numbers. + * They should not be used directly by applications. + * In general, SETs should be even and GETs should be odd.*/ +/**@{*/ +#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120 +#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122 +/**@}*/ + +/** @endcond */ + +/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs + * + * These are convenience macros that are specific to the + * opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl() + * interface. + * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and + * @ref opus_decoderctls may be applied to a multistream encoder or decoder as + * well. + * In addition, you may retrieve the encoder or decoder state for an specific + * stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or + * #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually. + */ +/**@{*/ + +/** Gets the encoder state for an individual stream of a multistream encoder. + * @param[in] x opus_int32: The index of the stream whose encoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the encoder. + * @param[out] y OpusEncoder**: Returns a pointer to the given + * encoder state. + * @retval OPUS_BAD_ARG The index of the requested stream was out of range. + * @hideinitializer + */ +#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y) + +/** Gets the decoder state for an individual stream of a multistream decoder. + * @param[in] x opus_int32: The index of the stream whose decoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the decoder. + * @param[out] y OpusDecoder**: Returns a pointer to the given + * decoder state. + * @retval OPUS_BAD_ARG The index of the requested stream was out of range. + * @hideinitializer + */ +#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y) + +/**@}*/ + +/** @defgroup opus_multistream Opus Multistream API + * @{ + * + * The multistream API allows individual Opus streams to be combined into a + * single packet, enabling support for up to 255 channels. Unlike an + * elementary Opus stream, the encoder and decoder must negotiate the channel + * configuration before the decoder can successfully interpret the data in the + * packets produced by the encoder. Some basic information, such as packet + * duration, can be computed without any special negotiation. + * + * The format for multistream Opus packets is defined in + * RFC 7845 + * and is based on the self-delimited Opus framing described in Appendix B of + * RFC 6716. + * Normal Opus packets are just a degenerate case of multistream Opus packets, + * and can be encoded or decoded with the multistream API by setting + * streams to 1 when initializing the encoder or + * decoder. + * + * Multistream Opus streams can contain up to 255 elementary Opus streams. + * These may be either "uncoupled" or "coupled", indicating that the decoder + * is configured to decode them to either 1 or 2 channels, respectively. + * The streams are ordered so that all coupled streams appear at the + * beginning. + * + * A mapping table defines which decoded channel i + * should be used for each input/output (I/O) channel j. This table is + * typically provided as an unsigned char array. + * Let i = mapping[j] be the index for I/O channel j. + * If i < 2*coupled_streams, then I/O channel j is + * encoded as the left channel of stream (i/2) if i + * is even, or as the right channel of stream (i/2) if + * i is odd. Otherwise, I/O channel j is encoded as + * mono in stream (i - coupled_streams), unless it has the special + * value 255, in which case it is omitted from the encoding entirely (the + * decoder will reproduce it as silence). Each value i must either + * be the special value 255 or be less than streams + coupled_streams. + * + * The output channels specified by the encoder + * should use the + * Vorbis + * channel ordering. A decoder may wish to apply an additional permutation + * to the mapping the encoder used to achieve a different output channel + * order (e.g. for outputting in WAV order). + * + * Each multistream packet contains an Opus packet for each stream, and all of + * the Opus packets in a single multistream packet must have the same + * duration. Therefore the duration of a multistream packet can be extracted + * from the TOC sequence of the first stream, which is located at the + * beginning of the packet, just like an elementary Opus stream: + * + * @code + * int nb_samples; + * int nb_frames; + * nb_frames = opus_packet_get_nb_frames(data, len); + * if (nb_frames < 1) + * return nb_frames; + * nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames; + * @endcode + * + * The general encoding and decoding process proceeds exactly the same as in + * the normal @ref opus_encoder and @ref opus_decoder APIs. + * See their documentation for an overview of how to use the corresponding + * multistream functions. + */ + +/** Opus multistream encoder state. + * This contains the complete state of a multistream Opus encoder. + * It is position independent and can be freely copied. + * @see opus_multistream_encoder_create + * @see opus_multistream_encoder_init + */ +typedef struct OpusMSEncoder OpusMSEncoder; + +/** Opus multistream decoder state. + * This contains the complete state of a multistream Opus decoder. + * It is position independent and can be freely copied. + * @see opus_multistream_decoder_create + * @see opus_multistream_decoder_init + */ +typedef struct OpusMSDecoder OpusMSDecoder; + +/**\name Multistream encoder functions */ +/**@{*/ + +/** Gets the size of an OpusMSEncoder structure. + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size( + int streams, + int coupled_streams +); + +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size( + int channels, + int mapping_family +); + + +/** Allocates and initializes a multistream encoder state. + * Call opus_multistream_encoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: Mapping from + * encoded channels to input channels, as described in + * @ref opus_multistream. As an extra constraint, the + * multistream encoder does not allow encoding coupled + * streams for which one channel is unused since this + * is never a good idea. + * @param application int: The target encoder application. + * This must be one of the following: + *
    + *
    #OPUS_APPLICATION_VOIP
    + *
    Process signal for improved speech intelligibility.
    + *
    #OPUS_APPLICATION_AUDIO
    + *
    Favor faithfulness to the original input.
    + *
    #OPUS_APPLICATION_RESTRICTED_LOWDELAY
    + *
    Configure the minimum possible coding delay by disabling certain modes + * of operation.
    + *
    + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application, + int *error +) OPUS_ARG_NONNULL(5); + +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create( + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application, + int *error +) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); + +/** Initialize a previously allocated multistream encoder state. + * The memory pointed to by \a st must be at least the size returned by + * opus_multistream_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_multistream_encoder_create + * @see opus_multistream_encoder_get_size + * @param st OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: Mapping from + * encoded channels to input channels, as described in + * @ref opus_multistream. As an extra constraint, the + * multistream encoder does not allow encoding coupled + * streams for which one channel is unused since this + * is never a good idea. + * @param application int: The target encoder application. + * This must be one of the following: + *
    + *
    #OPUS_APPLICATION_VOIP
    + *
    Process signal for improved speech intelligibility.
    + *
    #OPUS_APPLICATION_AUDIO
    + *
    Favor faithfulness to the original input.
    + *
    #OPUS_APPLICATION_RESTRICTED_LOWDELAY
    + *
    Configure the minimum possible coding delay by disabling certain modes + * of operation.
    + *
    + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_multistream_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + +OPUS_EXPORT int opus_multistream_surround_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6) OPUS_ARG_NONNULL(7); + +/** Encodes a multistream Opus frame. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode( + OpusMSEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes a multistream Opus frame from floating point input. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const float*: The input signal as interleaved + * samples with a normal range of + * +/-1.0. + * Samples with a range beyond +/-1.0 + * are supported but will be clipped by + * decoders using the integer API and + * should only be used if it is known + * that the far end supports extended + * dynamic range. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float( + OpusMSEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Frees an OpusMSEncoder allocated by + * opus_multistream_encoder_create(). + * @param st OpusMSEncoder*: Multistream encoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st); + +/** Perform a CTL function on a multistream Opus encoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_encoderctls, or @ref opus_multistream_ctls. + * @see opus_genericctls + * @see opus_encoderctls + * @see opus_multistream_ctls + */ +OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/**@}*/ + +/**\name Multistream decoder functions */ +/**@{*/ + +/** Gets the size of an OpusMSDecoder structure. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size( + int streams, + int coupled_streams +); + +/** Allocates and initializes a multistream decoder state. + * Call opus_multistream_decoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int *error +) OPUS_ARG_NONNULL(5); + +/** Intialize a previously allocated decoder state object. + * The memory pointed to by \a st must be at least the size returned by + * opus_multistream_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_multistream_decoder_create + * @see opus_multistream_deocder_get_size + * @param st OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_multistream_decoder_init( + OpusMSDecoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + +/** Decode a multistream Opus packet. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode( + OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode a multistream Opus packet with floating point output. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float( + OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on a multistream Opus decoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_decoderctls, or @ref opus_multistream_ctls. + * @see opus_genericctls + * @see opus_decoderctls + * @see opus_multistream_ctls + */ +OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/** Frees an OpusMSDecoder allocated by + * opus_multistream_decoder_create(). + * @param st OpusMSDecoder: Multistream decoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st); + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_MULTISTREAM_H */ diff --git a/port/third_party/opus/include/opus_projection.h b/port/third_party/opus/include/opus_projection.h new file mode 100644 index 00000000..9dabf4e8 --- /dev/null +++ b/port/third_party/opus/include/opus_projection.h @@ -0,0 +1,568 @@ +/* Copyright (c) 2017 Google Inc. + Written by Andrew Allen */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +/** + * @file opus_projection.h + * @brief Opus projection reference API + */ + +#ifndef OPUS_PROJECTION_H +#define OPUS_PROJECTION_H + +#include "opus_multistream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond OPUS_INTERNAL_DOC */ + +/** These are the actual encoder and decoder CTL ID numbers. + * They should not be used directly by applications.c + * In general, SETs should be even and GETs should be odd.*/ +/**@{*/ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST 6001 +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST 6003 +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST 6005 +/**@}*/ + + +/** @endcond */ + +/** @defgroup opus_projection_ctls Projection specific encoder and decoder CTLs + * + * These are convenience macros that are specific to the + * opus_projection_encoder_ctl() and opus_projection_decoder_ctl() + * interface. + * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, + * @ref opus_decoderctls, and @ref opus_multistream_ctls may be applied to a + * projection encoder or decoder as well. + */ +/**@{*/ + +/** Gets the gain (in dB. S7.8-format) of the demixing matrix from the encoder. + * @param[out] x opus_int32 *: Returns the gain (in dB. S7.8-format) + * of the demixing matrix. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST, __opus_check_int_ptr(x) + + +/** Gets the size in bytes of the demixing matrix from the encoder. + * @param[out] x opus_int32 *: Returns the size in bytes of the + * demixing matrix. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, __opus_check_int_ptr(x) + + +/** Copies the demixing matrix to the supplied pointer location. + * @param[out] x unsigned char *: Returns the demixing matrix to the + * supplied pointer location. + * @param y opus_int32: The size in bytes of the reserved memory at the + * pointer location. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX(x,y) OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, x, __opus_check_int(y) + + +/**@}*/ + +/** Opus projection encoder state. + * This contains the complete state of a projection Opus encoder. + * It is position independent and can be freely copied. + * @see opus_projection_ambisonics_encoder_create + */ +typedef struct OpusProjectionEncoder OpusProjectionEncoder; + + +/** Opus projection decoder state. + * This contains the complete state of a projection Opus decoder. + * It is position independent and can be freely copied. + * @see opus_projection_decoder_create + * @see opus_projection_decoder_init + */ +typedef struct OpusProjectionDecoder OpusProjectionDecoder; + + +/**\name Projection encoder functions */ +/**@{*/ + +/** Gets the size of an OpusProjectionEncoder structure. + * @param channels int: The total number of input channels to encode. + * This must be no more than 255. + * @param mapping_family int: The mapping family to use for selecting + * the appropriate projection. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_ambisonics_encoder_get_size( + int channels, + int mapping_family +); + + +/** Allocates and initializes a projection encoder state. + * Call opus_projection_encoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param mapping_family int: The mapping family to use for selecting + * the appropriate projection. + * @param[out] streams int *: The total number of streams that will + * be encoded from the input. + * @param[out] coupled_streams int *: Number of coupled (2 channel) + * streams that will be encoded from the input. + * @param application int: The target encoder application. + * This must be one of the following: + *
    + *
    #OPUS_APPLICATION_VOIP
    + *
    Process signal for improved speech intelligibility.
    + *
    #OPUS_APPLICATION_AUDIO
    + *
    Favor faithfulness to the original input.
    + *
    #OPUS_APPLICATION_RESTRICTED_LOWDELAY
    + *
    Configure the minimum possible coding delay by disabling certain modes + * of operation.
    + *
    + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionEncoder *opus_projection_ambisonics_encoder_create( + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + int application, + int *error +) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5); + + +/** Initialize a previously allocated projection encoder state. + * The memory pointed to by \a st must be at least the size returned by + * opus_projection_ambisonics_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_projection_ambisonics_encoder_create + * @see opus_projection_ambisonics_encoder_get_size + * @param st OpusProjectionEncoder*: Projection encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param application int: The target encoder application. + * This must be one of the following: + *
    + *
    #OPUS_APPLICATION_VOIP
    + *
    Process signal for improved speech intelligibility.
    + *
    #OPUS_APPLICATION_AUDIO
    + *
    Favor faithfulness to the original input.
    + *
    #OPUS_APPLICATION_RESTRICTED_LOWDELAY
    + *
    Configure the minimum possible coding delay by disabling certain modes + * of operation.
    + *
    + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_projection_ambisonics_encoder_init( + OpusProjectionEncoder *st, + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); + + +/** Encodes a projection Opus frame. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode( + OpusProjectionEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + + +/** Encodes a projection Opus frame from floating point input. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param[in] pcm const float*: The input signal as interleaved + * samples with a normal range of + * +/-1.0. + * Samples with a range beyond +/-1.0 + * are supported but will be clipped by + * decoders using the integer API and + * should only be used if it is known + * that the far end supports extended + * dynamic range. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode_float( + OpusProjectionEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + + +/** Frees an OpusProjectionEncoder allocated by + * opus_projection_ambisonics_encoder_create(). + * @param st OpusProjectionEncoder*: Projection encoder state to be freed. + */ +OPUS_EXPORT void opus_projection_encoder_destroy(OpusProjectionEncoder *st); + + +/** Perform a CTL function on a projection Opus encoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_encoderctls, @ref opus_multistream_ctls, or + * @ref opus_projection_ctls + * @see opus_genericctls + * @see opus_encoderctls + * @see opus_multistream_ctls + * @see opus_projection_ctls + */ +OPUS_EXPORT int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); + + +/**@}*/ + +/**\name Projection decoder functions */ +/**@{*/ + +/** Gets the size of an OpusProjectionDecoder structure. + * @param channels int: The total number of output channels. + * This must be no more than 255. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_decoder_get_size( + int channels, + int streams, + int coupled_streams +); + + +/** Allocates and initializes a projection decoder state. + * Call opus_projection_decoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix + * that mapping from coded channels to output channels, + * as described in @ref opus_projection and + * @ref opus_projection_ctls. + * @param demixing_matrix_size opus_int32: The size in bytes of the + * demixing matrix, as + * described in @ref + * opus_projection_ctls. + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionDecoder *opus_projection_decoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + unsigned char *demixing_matrix, + opus_int32 demixing_matrix_size, + int *error +) OPUS_ARG_NONNULL(5); + + +/** Intialize a previously allocated projection decoder state object. + * The memory pointed to by \a st must be at least the size returned by + * opus_projection_decoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_projection_decoder_create + * @see opus_projection_deocder_get_size + * @param st OpusProjectionDecoder*: Projection encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix + * that mapping from coded channels to output channels, + * as described in @ref opus_projection and + * @ref opus_projection_ctls. + * @param demixing_matrix_size opus_int32: The size in bytes of the + * demixing matrix, as + * described in @ref + * opus_projection_ctls. + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_projection_decoder_init( + OpusProjectionDecoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + unsigned char *demixing_matrix, + opus_int32 demixing_matrix_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + + +/** Decode a projection Opus packet. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode( + OpusProjectionDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + + +/** Decode a projection Opus packet with floating point output. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode_float( + OpusProjectionDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + + +/** Perform a CTL function on a projection Opus decoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_decoderctls, @ref opus_multistream_ctls, or + * @ref opus_projection_ctls. + * @see opus_genericctls + * @see opus_decoderctls + * @see opus_multistream_ctls + * @see opus_projection_ctls + */ +OPUS_EXPORT int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + + +/** Frees an OpusProjectionDecoder allocated by + * opus_projection_decoder_create(). + * @param st OpusProjectionDecoder: Projection decoder state to be freed. + */ +OPUS_EXPORT void opus_projection_decoder_destroy(OpusProjectionDecoder *st); + + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_PROJECTION_H */ diff --git a/port/third_party/opus/include/opus_types.h b/port/third_party/opus/include/opus_types.h new file mode 100644 index 00000000..7cf67558 --- /dev/null +++ b/port/third_party/opus/include/opus_types.h @@ -0,0 +1,166 @@ +/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */ +/* Modified by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ +/* opus_types.h based on ogg_types.h from libogg */ + +/** + @file opus_types.h + @brief Opus reference implementation types +*/ +#ifndef OPUS_TYPES_H +#define OPUS_TYPES_H + +#define opus_int int /* used for counters etc; at least 16 bits */ +#define opus_int64 long long +#define opus_int8 signed char + +#define opus_uint unsigned int /* used for counters etc; at least 16 bits */ +#define opus_uint64 unsigned long long +#define opus_uint8 unsigned char + +/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */ +#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H)) +#include +# undef opus_int64 +# undef opus_int8 +# undef opus_uint64 +# undef opus_uint8 + typedef int8_t opus_int8; + typedef uint8_t opus_uint8; + typedef int16_t opus_int16; + typedef uint16_t opus_uint16; + typedef int32_t opus_int32; + typedef uint32_t opus_uint32; + typedef int64_t opus_int64; + typedef uint64_t opus_uint64; +#elif defined(_WIN32) + +# if defined(__CYGWIN__) +# include <_G_config.h> + typedef _G_int32_t opus_int32; + typedef _G_uint32_t opus_uint32; + typedef _G_int16 opus_int16; + typedef _G_uint16 opus_uint16; +# elif defined(__MINGW32__) + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; +# elif defined(__MWERKS__) + typedef int opus_int32; + typedef unsigned int opus_uint32; + typedef short opus_int16; + typedef unsigned short opus_uint16; +# else + /* MSVC/Borland */ + typedef __int32 opus_int32; + typedef unsigned __int32 opus_uint32; + typedef __int16 opus_int16; + typedef unsigned __int16 opus_uint16; +# endif + +#elif defined(__MACOS__) + +# include + typedef SInt16 opus_int16; + typedef UInt16 opus_uint16; + typedef SInt32 opus_int32; + typedef UInt32 opus_uint32; + +#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ + +# include + typedef int16_t opus_int16; + typedef u_int16_t opus_uint16; + typedef int32_t opus_int32; + typedef u_int32_t opus_uint32; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16 opus_int16; + typedef u_int16 opus_uint16; + typedef int32_t opus_int32; + typedef u_int32_t opus_uint32; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined(R5900) + + /* PS2 EE */ + typedef int opus_int32; + typedef unsigned opus_uint32; + typedef short opus_int16; + typedef unsigned short opus_uint16; + +#elif defined(__SYMBIAN32__) + + /* Symbian GCC */ + typedef signed short opus_int16; + typedef unsigned short opus_uint16; + typedef signed int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef long opus_int32; + typedef unsigned long opus_uint32; + +#elif defined(CONFIG_TI_C6X) + + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#else + + /* Give up, take a reasonable guess */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#endif + +#endif /* OPUS_TYPES_H */ diff --git a/port/third_party/opus/silk/A2NLSF.c b/port/third_party/opus/silk/A2NLSF.c new file mode 100644 index 00000000..b487686f --- /dev/null +++ b/port/third_party/opus/silk/A2NLSF.c @@ -0,0 +1,267 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +/* Conversion between prediction filter coefficients and NLSFs */ +/* Requires the order to be an even number */ +/* A piecewise linear approximation maps LSF <-> cos(LSF) */ +/* Therefore the result is not accurate NLSFs, but the two */ +/* functions are accurate inverses of each other */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "tables.h" + +/* Number of binary divisions, when not in low complexity mode */ +#define BIN_DIV_STEPS_A2NLSF_FIX 3 /* must be no higher than 16 - log2( LSF_COS_TAB_SZ_FIX ) */ +#define MAX_ITERATIONS_A2NLSF_FIX 16 + +/* Helper function for A2NLSF(..) */ +/* Transforms polynomials from cos(n*f) to cos(f)^n */ +static OPUS_INLINE void silk_A2NLSF_trans_poly( + opus_int32 *p, /* I/O Polynomial */ + const opus_int dd /* I Polynomial order (= filter order / 2 ) */ +) +{ + opus_int k, n; + + for( k = 2; k <= dd; k++ ) { + for( n = dd; n > k; n-- ) { + p[ n - 2 ] -= p[ n ]; + } + p[ k - 2 ] -= silk_LSHIFT( p[ k ], 1 ); + } +} +/* Helper function for A2NLSF(..) */ +/* Polynomial evaluation */ +static OPUS_INLINE opus_int32 silk_A2NLSF_eval_poly( /* return the polynomial evaluation, in Q16 */ + opus_int32 *p, /* I Polynomial, Q16 */ + const opus_int32 x, /* I Evaluation point, Q12 */ + const opus_int dd /* I Order */ +) +{ + opus_int n; + opus_int32 x_Q16, y32; + + y32 = p[ dd ]; /* Q16 */ + x_Q16 = silk_LSHIFT( x, 4 ); + + if ( opus_likely( 8 == dd ) ) + { + y32 = silk_SMLAWW( p[ 7 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 6 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 5 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 4 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 3 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 2 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 1 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 0 ], y32, x_Q16 ); + } + else + { + for( n = dd - 1; n >= 0; n-- ) { + y32 = silk_SMLAWW( p[ n ], y32, x_Q16 ); /* Q16 */ + } + } + return y32; +} + +static OPUS_INLINE void silk_A2NLSF_init( + const opus_int32 *a_Q16, + opus_int32 *P, + opus_int32 *Q, + const opus_int dd +) +{ + opus_int k; + + /* Convert filter coefs to even and odd polynomials */ + P[dd] = silk_LSHIFT( 1, 16 ); + Q[dd] = silk_LSHIFT( 1, 16 ); + for( k = 0; k < dd; k++ ) { + P[ k ] = -a_Q16[ dd - k - 1 ] - a_Q16[ dd + k ]; /* Q16 */ + Q[ k ] = -a_Q16[ dd - k - 1 ] + a_Q16[ dd + k ]; /* Q16 */ + } + + /* Divide out zeros as we have that for even filter orders, */ + /* z = 1 is always a root in Q, and */ + /* z = -1 is always a root in P */ + for( k = dd; k > 0; k-- ) { + P[ k - 1 ] -= P[ k ]; + Q[ k - 1 ] += Q[ k ]; + } + + /* Transform polynomials from cos(n*f) to cos(f)^n */ + silk_A2NLSF_trans_poly( P, dd ); + silk_A2NLSF_trans_poly( Q, dd ); +} + +/* Compute Normalized Line Spectral Frequencies (NLSFs) from whitening filter coefficients */ +/* If not all roots are found, the a_Q16 coefficients are bandwidth expanded until convergence. */ +void silk_A2NLSF( + opus_int16 *NLSF, /* O Normalized Line Spectral Frequencies in Q15 (0..2^15-1) [d] */ + opus_int32 *a_Q16, /* I/O Monic whitening filter coefficients in Q16 [d] */ + const opus_int d /* I Filter order (must be even) */ +) +{ + opus_int i, k, m, dd, root_ix, ffrac; + opus_int32 xlo, xhi, xmid; + opus_int32 ylo, yhi, ymid, thr; + opus_int32 nom, den; + opus_int32 P[ SILK_MAX_ORDER_LPC / 2 + 1 ]; + opus_int32 Q[ SILK_MAX_ORDER_LPC / 2 + 1 ]; + opus_int32 *PQ[ 2 ]; + opus_int32 *p; + + /* Store pointers to array */ + PQ[ 0 ] = P; + PQ[ 1 ] = Q; + + dd = silk_RSHIFT( d, 1 ); + + silk_A2NLSF_init( a_Q16, P, Q, dd ); + + /* Find roots, alternating between P and Q */ + p = P; /* Pointer to polynomial */ + + xlo = silk_LSFCosTab_FIX_Q12[ 0 ]; /* Q12*/ + ylo = silk_A2NLSF_eval_poly( p, xlo, dd ); + + if( ylo < 0 ) { + /* Set the first NLSF to zero and move on to the next */ + NLSF[ 0 ] = 0; + p = Q; /* Pointer to polynomial */ + ylo = silk_A2NLSF_eval_poly( p, xlo, dd ); + root_ix = 1; /* Index of current root */ + } else { + root_ix = 0; /* Index of current root */ + } + k = 1; /* Loop counter */ + i = 0; /* Counter for bandwidth expansions applied */ + thr = 0; + while( 1 ) { + /* Evaluate polynomial */ + xhi = silk_LSFCosTab_FIX_Q12[ k ]; /* Q12 */ + yhi = silk_A2NLSF_eval_poly( p, xhi, dd ); + + /* Detect zero crossing */ + if( ( ylo <= 0 && yhi >= thr ) || ( ylo >= 0 && yhi <= -thr ) ) { + if( yhi == 0 ) { + /* If the root lies exactly at the end of the current */ + /* interval, look for the next root in the next interval */ + thr = 1; + } else { + thr = 0; + } + /* Binary division */ + ffrac = -256; + for( m = 0; m < BIN_DIV_STEPS_A2NLSF_FIX; m++ ) { + /* Evaluate polynomial */ + xmid = silk_RSHIFT_ROUND( xlo + xhi, 1 ); + ymid = silk_A2NLSF_eval_poly( p, xmid, dd ); + + /* Detect zero crossing */ + if( ( ylo <= 0 && ymid >= 0 ) || ( ylo >= 0 && ymid <= 0 ) ) { + /* Reduce frequency */ + xhi = xmid; + yhi = ymid; + } else { + /* Increase frequency */ + xlo = xmid; + ylo = ymid; + ffrac = silk_ADD_RSHIFT( ffrac, 128, m ); + } + } + + /* Interpolate */ + if( silk_abs( ylo ) < 65536 ) { + /* Avoid dividing by zero */ + den = ylo - yhi; + nom = silk_LSHIFT( ylo, 8 - BIN_DIV_STEPS_A2NLSF_FIX ) + silk_RSHIFT( den, 1 ); + if( den != 0 ) { + ffrac += silk_DIV32( nom, den ); + } + } else { + /* No risk of dividing by zero because abs(ylo - yhi) >= abs(ylo) >= 65536 */ + ffrac += silk_DIV32( ylo, silk_RSHIFT( ylo - yhi, 8 - BIN_DIV_STEPS_A2NLSF_FIX ) ); + } + NLSF[ root_ix ] = (opus_int16)silk_min_32( silk_LSHIFT( (opus_int32)k, 8 ) + ffrac, silk_int16_MAX ); + + silk_assert( NLSF[ root_ix ] >= 0 ); + + root_ix++; /* Next root */ + if( root_ix >= d ) { + /* Found all roots */ + break; + } + /* Alternate pointer to polynomial */ + p = PQ[ root_ix & 1 ]; + + /* Evaluate polynomial */ + xlo = silk_LSFCosTab_FIX_Q12[ k - 1 ]; /* Q12*/ + ylo = silk_LSHIFT( 1 - ( root_ix & 2 ), 12 ); + } else { + /* Increment loop counter */ + k++; + xlo = xhi; + ylo = yhi; + thr = 0; + + if( k > LSF_COS_TAB_SZ_FIX ) { + i++; + if( i > MAX_ITERATIONS_A2NLSF_FIX ) { + /* Set NLSFs to white spectrum and exit */ + NLSF[ 0 ] = (opus_int16)silk_DIV32_16( 1 << 15, d + 1 ); + for( k = 1; k < d; k++ ) { + NLSF[ k ] = (opus_int16)silk_ADD16( NLSF[ k-1 ], NLSF[ 0 ] ); + } + return; + } + + /* Error: Apply progressively more bandwidth expansion and run again */ + silk_bwexpander_32( a_Q16, d, 65536 - silk_LSHIFT( 1, i ) ); + + silk_A2NLSF_init( a_Q16, P, Q, dd ); + p = P; /* Pointer to polynomial */ + xlo = silk_LSFCosTab_FIX_Q12[ 0 ]; /* Q12*/ + ylo = silk_A2NLSF_eval_poly( p, xlo, dd ); + if( ylo < 0 ) { + /* Set the first NLSF to zero and move on to the next */ + NLSF[ 0 ] = 0; + p = Q; /* Pointer to polynomial */ + ylo = silk_A2NLSF_eval_poly( p, xlo, dd ); + root_ix = 1; /* Index of current root */ + } else { + root_ix = 0; /* Index of current root */ + } + k = 1; /* Reset loop counter */ + } + } + } +} diff --git a/port/third_party/opus/silk/API.h b/port/third_party/opus/silk/API.h new file mode 100644 index 00000000..878965c7 --- /dev/null +++ b/port/third_party/opus/silk/API.h @@ -0,0 +1,156 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_API_H +#define SILK_API_H + +#include "control.h" +#include "typedef.h" +#include "errors.h" +#include "entenc.h" +#include "entdec.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet_private.h" +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define SILK_MAX_FRAMES_PER_PACKET 3 + +/* Struct for TOC (Table of Contents) */ +typedef struct { + opus_int VADFlag; /* Voice activity for packet */ + opus_int VADFlags[ SILK_MAX_FRAMES_PER_PACKET ]; /* Voice activity for each frame in packet */ + opus_int inbandFECFlag; /* Flag indicating if packet contains in-band FEC */ +} silk_TOC_struct; + +/****************************************/ +/* Encoder functions */ +/****************************************/ + +/***********************************************/ +/* Get size in bytes of the Silk encoder state */ +/***********************************************/ +opus_int silk_Get_Encoder_Size( /* O Returns error code */ + opus_int *encSizeBytes /* O Number of bytes in SILK encoder state */ +); + +/*************************/ +/* Init or reset encoder */ +/*************************/ +opus_int silk_InitEncoder( /* O Returns error code */ + void *encState, /* I/O State */ + int arch, /* I Run-time architecture */ + silk_EncControlStruct *encStatus /* O Encoder Status */ +); + +/**************************/ +/* Encode frame with Silk */ +/**************************/ +/* Note: if prefillFlag is set, the input must contain 10 ms of audio, irrespective of what */ +/* encControl->payloadSize_ms is set to */ +opus_int silk_Encode( /* O Returns error code */ + void *encState, /* I/O State */ + silk_EncControlStruct *encControl, /* I Control status */ + const opus_int16 *samplesIn, /* I Speech sample input vector */ + opus_int nSamplesIn, /* I Number of samples in input vector */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int32 *nBytesOut, /* I/O Number of bytes in payload (input: Max bytes) */ + const opus_int prefillFlag, /* I Flag to indicate prefilling buffers no coding */ + int activity /* I Decision of Opus voice activity detector */ +); + +/****************************************/ +/* Decoder functions */ +/****************************************/ + + +/***********************************************/ +/* Load OSCE models from external data pointer */ +/***********************************************/ +opus_int silk_LoadOSCEModels( + void *decState, /* O I/O State */ + const unsigned char *data, /* I pointer to binary blob */ + int len /* I length of binary blob data */ +); + +/***********************************************/ +/* Get size in bytes of the Silk decoder state */ +/***********************************************/ +opus_int silk_Get_Decoder_Size( /* O Returns error code */ + opus_int *decSizeBytes /* O Number of bytes in SILK decoder state */ +); + +/*************************/ +/* Init and Reset decoder */ +/*************************/ +opus_int silk_ResetDecoder( /* O Returns error code */ + void *decState /* I/O State */ +); + +opus_int silk_InitDecoder( /* O Returns error code */ + void *decState /* I/O State */ +); + +/******************/ +/* Decode a frame */ +/******************/ +opus_int silk_Decode( /* O Returns error code */ + void* decState, /* I/O State */ + silk_DecControlStruct* decControl, /* I/O Control Structure */ + opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ + opus_int newPacketFlag, /* I Indicates first decoder call for this packet */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 *samplesOut, /* O Decoded output speech vector */ + opus_int32 *nSamplesOut, /* O Number of samples decoded */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +); + +#if 0 +/**************************************/ +/* Get table of contents for a packet */ +/**************************************/ +opus_int silk_get_TOC( + const opus_uint8 *payload, /* I Payload data */ + const opus_int nBytesIn, /* I Number of input bytes */ + const opus_int nFramesPerPayload, /* I Number of SILK frames per payload */ + silk_TOC_struct *Silk_TOC /* O Type of content */ +); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/silk/CNG.c b/port/third_party/opus/silk/CNG.c new file mode 100644 index 00000000..2a910099 --- /dev/null +++ b/port/third_party/opus/silk/CNG.c @@ -0,0 +1,188 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/* Generates excitation for CNG LPC synthesis */ +static OPUS_INLINE void silk_CNG_exc( + opus_int32 exc_Q14[], /* O CNG excitation signal Q10 */ + opus_int32 exc_buf_Q14[], /* I Random samples buffer Q10 */ + opus_int length, /* I Length */ + opus_int32 *rand_seed /* I/O Seed to random index generator */ +) +{ + opus_int32 seed; + opus_int i, idx, exc_mask; + + exc_mask = CNG_BUF_MASK_MAX; + while( exc_mask > length ) { + exc_mask = silk_RSHIFT( exc_mask, 1 ); + } + + seed = *rand_seed; + for( i = 0; i < length; i++ ) { + seed = silk_RAND( seed ); + idx = (opus_int)( silk_RSHIFT( seed, 24 ) & exc_mask ); + silk_assert( idx >= 0 ); + silk_assert( idx <= CNG_BUF_MASK_MAX ); + exc_Q14[ i ] = exc_buf_Q14[ idx ]; + } + *rand_seed = seed; +} + +void silk_CNG_Reset( + silk_decoder_state *psDec /* I/O Decoder state */ +) +{ + opus_int i, NLSF_step_Q15, NLSF_acc_Q15; + + NLSF_step_Q15 = silk_DIV32_16( silk_int16_MAX, psDec->LPC_order + 1 ); + NLSF_acc_Q15 = 0; + for( i = 0; i < psDec->LPC_order; i++ ) { + NLSF_acc_Q15 += NLSF_step_Q15; + psDec->sCNG.CNG_smth_NLSF_Q15[ i ] = NLSF_acc_Q15; + } + psDec->sCNG.CNG_smth_Gain_Q16 = 0; + psDec->sCNG.rand_seed = 3176576; +} + +/* Updates CNG estimate, and applies the CNG when packet was lost */ +void silk_CNG( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* I/O Signal */ + opus_int length /* I Length of residual */ +) +{ + opus_int i, subfr; + opus_int32 LPC_pred_Q10, max_Gain_Q16, gain_Q16, gain_Q10; + opus_int16 A_Q12[ MAX_LPC_ORDER ]; + silk_CNG_struct *psCNG = &psDec->sCNG; + SAVE_STACK; + + if( psDec->fs_kHz != psCNG->fs_kHz ) { + /* Reset state */ + silk_CNG_Reset( psDec ); + + psCNG->fs_kHz = psDec->fs_kHz; + } + if( psDec->lossCnt == 0 && psDec->prevSignalType == TYPE_NO_VOICE_ACTIVITY ) { + /* Update CNG parameters */ + + /* Smoothing of LSF's */ + for( i = 0; i < psDec->LPC_order; i++ ) { + psCNG->CNG_smth_NLSF_Q15[ i ] += silk_SMULWB( (opus_int32)psDec->prevNLSF_Q15[ i ] - (opus_int32)psCNG->CNG_smth_NLSF_Q15[ i ], CNG_NLSF_SMTH_Q16 ); + } + /* Find the subframe with the highest gain */ + max_Gain_Q16 = 0; + subfr = 0; + for( i = 0; i < psDec->nb_subfr; i++ ) { + if( psDecCtrl->Gains_Q16[ i ] > max_Gain_Q16 ) { + max_Gain_Q16 = psDecCtrl->Gains_Q16[ i ]; + subfr = i; + } + } + /* Update CNG excitation buffer with excitation from this subframe */ + silk_memmove( &psCNG->CNG_exc_buf_Q14[ psDec->subfr_length ], psCNG->CNG_exc_buf_Q14, ( psDec->nb_subfr - 1 ) * psDec->subfr_length * sizeof( opus_int32 ) ); + silk_memcpy( psCNG->CNG_exc_buf_Q14, &psDec->exc_Q14[ subfr * psDec->subfr_length ], psDec->subfr_length * sizeof( opus_int32 ) ); + + /* Smooth gains */ + for( i = 0; i < psDec->nb_subfr; i++ ) { + psCNG->CNG_smth_Gain_Q16 += silk_SMULWB( psDecCtrl->Gains_Q16[ i ] - psCNG->CNG_smth_Gain_Q16, CNG_GAIN_SMTH_Q16 ); + /* If the smoothed gain is 3 dB greater than this subframe's gain, use this subframe's gain to adapt faster. */ + if( silk_SMULWW( psCNG->CNG_smth_Gain_Q16, CNG_GAIN_SMTH_THRESHOLD_Q16 ) > psDecCtrl->Gains_Q16[ i ] ) { + psCNG->CNG_smth_Gain_Q16 = psDecCtrl->Gains_Q16[ i ]; + } + } + } + + /* Add CNG when packet is lost or during DTX */ + if( psDec->lossCnt ) { + VARDECL( opus_int32, CNG_sig_Q14 ); + ALLOC( CNG_sig_Q14, length + MAX_LPC_ORDER, opus_int32 ); + + /* Generate CNG excitation */ + gain_Q16 = silk_SMULWW( psDec->sPLC.randScale_Q14, psDec->sPLC.prevGain_Q16[1] ); + if( gain_Q16 >= (1 << 21) || psCNG->CNG_smth_Gain_Q16 > (1 << 23) ) { + gain_Q16 = silk_SMULTT( gain_Q16, gain_Q16 ); + gain_Q16 = silk_SUB_LSHIFT32(silk_SMULTT( psCNG->CNG_smth_Gain_Q16, psCNG->CNG_smth_Gain_Q16 ), gain_Q16, 5 ); + gain_Q16 = silk_LSHIFT32( silk_SQRT_APPROX( gain_Q16 ), 16 ); + } else { + gain_Q16 = silk_SMULWW( gain_Q16, gain_Q16 ); + gain_Q16 = silk_SUB_LSHIFT32(silk_SMULWW( psCNG->CNG_smth_Gain_Q16, psCNG->CNG_smth_Gain_Q16 ), gain_Q16, 5 ); + gain_Q16 = silk_LSHIFT32( silk_SQRT_APPROX( gain_Q16 ), 8 ); + } + gain_Q10 = silk_RSHIFT( gain_Q16, 6 ); + + silk_CNG_exc( CNG_sig_Q14 + MAX_LPC_ORDER, psCNG->CNG_exc_buf_Q14, length, &psCNG->rand_seed ); + + /* Convert CNG NLSF to filter representation */ + silk_NLSF2A( A_Q12, psCNG->CNG_smth_NLSF_Q15, psDec->LPC_order, psDec->arch ); + + /* Generate CNG signal, by synthesis filtering */ + silk_memcpy( CNG_sig_Q14, psCNG->CNG_synth_state, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + celt_assert( psDec->LPC_order == 10 || psDec->LPC_order == 16 ); + for( i = 0; i < length; i++ ) { + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q10 = silk_RSHIFT( psDec->LPC_order, 1 ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 1 ], A_Q12[ 0 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 2 ], A_Q12[ 1 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 3 ], A_Q12[ 2 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 4 ], A_Q12[ 3 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 5 ], A_Q12[ 4 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 6 ], A_Q12[ 5 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 7 ], A_Q12[ 6 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 8 ], A_Q12[ 7 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 9 ], A_Q12[ 8 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 10 ], A_Q12[ 9 ] ); + if( psDec->LPC_order == 16 ) { + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 11 ], A_Q12[ 10 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 12 ], A_Q12[ 11 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 13 ], A_Q12[ 12 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 14 ], A_Q12[ 13 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 15 ], A_Q12[ 14 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 16 ], A_Q12[ 15 ] ); + } + + /* Update states */ + CNG_sig_Q14[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( CNG_sig_Q14[ MAX_LPC_ORDER + i ], silk_LSHIFT_SAT32( LPC_pred_Q10, 4 ) ); + + /* Scale with Gain and add to input signal */ + frame[ i ] = (opus_int16)silk_ADD_SAT16( frame[ i ], silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( CNG_sig_Q14[ MAX_LPC_ORDER + i ], gain_Q10 ), 8 ) ) ); + + } + silk_memcpy( psCNG->CNG_synth_state, &CNG_sig_Q14[ length ], MAX_LPC_ORDER * sizeof( opus_int32 ) ); + } else { + silk_memset( psCNG->CNG_synth_state, 0, psDec->LPC_order * sizeof( opus_int32 ) ); + } + RESTORE_STACK; +} diff --git a/port/third_party/opus/silk/HP_variable_cutoff.c b/port/third_party/opus/silk/HP_variable_cutoff.c new file mode 100644 index 00000000..bbe10f04 --- /dev/null +++ b/port/third_party/opus/silk/HP_variable_cutoff.c @@ -0,0 +1,77 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef FIXED_POINT +#include "main_FIX.h" +#else +#include "main_FLP.h" +#endif +#include "tuning_parameters.h" + +/* High-pass filter with cutoff frequency adaptation based on pitch lag statistics */ +void silk_HP_variable_cutoff( + silk_encoder_state_Fxx state_Fxx[] /* I/O Encoder states */ +) +{ + opus_int quality_Q15; + opus_int32 pitch_freq_Hz_Q16, pitch_freq_log_Q7, delta_freq_Q7; + silk_encoder_state *psEncC1 = &state_Fxx[ 0 ].sCmn; + + /* Adaptive cutoff frequency: estimate low end of pitch frequency range */ + if( psEncC1->prevSignalType == TYPE_VOICED ) { + /* difference, in log domain */ + pitch_freq_Hz_Q16 = silk_DIV32_16( silk_LSHIFT( silk_MUL( psEncC1->fs_kHz, 1000 ), 16 ), psEncC1->prevLag ); + pitch_freq_log_Q7 = silk_lin2log( pitch_freq_Hz_Q16 ) - ( 16 << 7 ); + + /* adjustment based on quality */ + quality_Q15 = psEncC1->input_quality_bands_Q15[ 0 ]; + pitch_freq_log_Q7 = silk_SMLAWB( pitch_freq_log_Q7, silk_SMULWB( silk_LSHIFT( -quality_Q15, 2 ), quality_Q15 ), + pitch_freq_log_Q7 - ( silk_lin2log( SILK_FIX_CONST( VARIABLE_HP_MIN_CUTOFF_HZ, 16 ) ) - ( 16 << 7 ) ) ); + + /* delta_freq = pitch_freq_log - psEnc->variable_HP_smth1; */ + delta_freq_Q7 = pitch_freq_log_Q7 - silk_RSHIFT( psEncC1->variable_HP_smth1_Q15, 8 ); + if( delta_freq_Q7 < 0 ) { + /* less smoothing for decreasing pitch frequency, to track something close to the minimum */ + delta_freq_Q7 = silk_MUL( delta_freq_Q7, 3 ); + } + + /* limit delta, to reduce impact of outliers in pitch estimation */ + delta_freq_Q7 = silk_LIMIT_32( delta_freq_Q7, -SILK_FIX_CONST( VARIABLE_HP_MAX_DELTA_FREQ, 7 ), SILK_FIX_CONST( VARIABLE_HP_MAX_DELTA_FREQ, 7 ) ); + + /* update smoother */ + psEncC1->variable_HP_smth1_Q15 = silk_SMLAWB( psEncC1->variable_HP_smth1_Q15, + silk_SMULBB( psEncC1->speech_activity_Q8, delta_freq_Q7 ), SILK_FIX_CONST( VARIABLE_HP_SMTH_COEF1, 16 ) ); + + /* limit frequency range */ + psEncC1->variable_HP_smth1_Q15 = silk_LIMIT_32( psEncC1->variable_HP_smth1_Q15, + silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ), + silk_LSHIFT( silk_lin2log( VARIABLE_HP_MAX_CUTOFF_HZ ), 8 ) ); + } +} diff --git a/port/third_party/opus/silk/Inlines.h b/port/third_party/opus/silk/Inlines.h new file mode 100644 index 00000000..ec986cdf --- /dev/null +++ b/port/third_party/opus/silk/Inlines.h @@ -0,0 +1,188 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +/*! \file silk_Inlines.h + * \brief silk_Inlines.h defines OPUS_INLINE signal processing functions. + */ + +#ifndef SILK_FIX_INLINES_H +#define SILK_FIX_INLINES_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* count leading zeros of opus_int64 */ +static OPUS_INLINE opus_int32 silk_CLZ64( opus_int64 in ) +{ + opus_int32 in_upper; + + in_upper = (opus_int32)silk_RSHIFT64(in, 32); + if (in_upper == 0) { + /* Search in the lower 32 bits */ + return 32 + silk_CLZ32( (opus_int32) in ); + } else { + /* Search in the upper 32 bits */ + return silk_CLZ32( in_upper ); + } +} + +/* get number of leading zeros and fractional part (the bits right after the leading one */ +static OPUS_INLINE void silk_CLZ_FRAC( + opus_int32 in, /* I input */ + opus_int32 *lz, /* O number of leading zeros */ + opus_int32 *frac_Q7 /* O the 7 bits right after the leading one */ +) +{ + opus_int32 lzeros = silk_CLZ32(in); + + * lz = lzeros; + * frac_Q7 = silk_ROR32(in, 24 - lzeros) & 0x7f; +} + +/* Approximation of square root */ +/* Accuracy: < +/- 10% for output values > 15 */ +/* < +/- 2.5% for output values > 120 */ +static OPUS_INLINE opus_int32 silk_SQRT_APPROX( opus_int32 x ) +{ + opus_int32 y, lz, frac_Q7; + + if( x <= 0 ) { + return 0; + } + + silk_CLZ_FRAC(x, &lz, &frac_Q7); + + if( lz & 1 ) { + y = 32768; + } else { + y = 46214; /* 46214 = sqrt(2) * 32768 */ + } + + /* get scaling right */ + y >>= silk_RSHIFT(lz, 1); + + /* increment using fractional part of input */ + y = silk_SMLAWB(y, y, silk_SMULBB(213, frac_Q7)); + + return y; +} + +/* Divide two int32 values and return result as int32 in a given Q-domain */ +static OPUS_INLINE opus_int32 silk_DIV32_varQ( /* O returns a good approximation of "(a32 << Qres) / b32" */ + const opus_int32 a32, /* I numerator (Q0) */ + const opus_int32 b32, /* I denominator (Q0) */ + const opus_int Qres /* I Q-domain of result (>= 0) */ +) +{ + opus_int a_headrm, b_headrm, lshift; + opus_int32 b32_inv, a32_nrm, b32_nrm, result; + + silk_assert( b32 != 0 ); + silk_assert( Qres >= 0 ); + + /* Compute number of bits head room and normalize inputs */ + a_headrm = silk_CLZ32( silk_abs(a32) ) - 1; + a32_nrm = silk_LSHIFT(a32, a_headrm); /* Q: a_headrm */ + b_headrm = silk_CLZ32( silk_abs(b32) ) - 1; + b32_nrm = silk_LSHIFT(b32, b_headrm); /* Q: b_headrm */ + + /* Inverse of b32, with 14 bits of precision */ + b32_inv = silk_DIV32_16( silk_int32_MAX >> 2, silk_RSHIFT(b32_nrm, 16) ); /* Q: 29 + 16 - b_headrm */ + + /* First approximation */ + result = silk_SMULWB(a32_nrm, b32_inv); /* Q: 29 + a_headrm - b_headrm */ + + /* Compute residual by subtracting product of denominator and first approximation */ + /* It's OK to overflow because the final value of a32_nrm should always be small */ + a32_nrm = silk_SUB32_ovflw(a32_nrm, silk_LSHIFT_ovflw( silk_SMMUL(b32_nrm, result), 3 )); /* Q: a_headrm */ + + /* Refinement */ + result = silk_SMLAWB(result, a32_nrm, b32_inv); /* Q: 29 + a_headrm - b_headrm */ + + /* Convert to Qres domain */ + lshift = 29 + a_headrm - b_headrm - Qres; + if( lshift < 0 ) { + return silk_LSHIFT_SAT32(result, -lshift); + } else { + if( lshift < 32){ + return silk_RSHIFT(result, lshift); + } else { + /* Avoid undefined result */ + return 0; + } + } +} + +/* Invert int32 value and return result as int32 in a given Q-domain */ +static OPUS_INLINE opus_int32 silk_INVERSE32_varQ( /* O returns a good approximation of "(1 << Qres) / b32" */ + const opus_int32 b32, /* I denominator (Q0) */ + const opus_int Qres /* I Q-domain of result (> 0) */ +) +{ + opus_int b_headrm, lshift; + opus_int32 b32_inv, b32_nrm, err_Q32, result; + + silk_assert( b32 != 0 ); + silk_assert( Qres > 0 ); + + /* Compute number of bits head room and normalize input */ + b_headrm = silk_CLZ32( silk_abs(b32) ) - 1; + b32_nrm = silk_LSHIFT(b32, b_headrm); /* Q: b_headrm */ + + /* Inverse of b32, with 14 bits of precision */ + b32_inv = silk_DIV32_16( silk_int32_MAX >> 2, silk_RSHIFT(b32_nrm, 16) ); /* Q: 29 + 16 - b_headrm */ + + /* First approximation */ + result = silk_LSHIFT(b32_inv, 16); /* Q: 61 - b_headrm */ + + /* Compute residual by subtracting product of denominator and first approximation from one */ + err_Q32 = silk_LSHIFT( ((opus_int32)1<<29) - silk_SMULWB(b32_nrm, b32_inv), 3 ); /* Q32 */ + + /* Refinement */ + result = silk_SMLAWW(result, err_Q32, b32_inv); /* Q: 61 - b_headrm */ + + /* Convert to Qres domain */ + lshift = 61 - b_headrm - Qres; + if( lshift <= 0 ) { + return silk_LSHIFT_SAT32(result, -lshift); + } else { + if( lshift < 32){ + return silk_RSHIFT(result, lshift); + }else{ + /* Avoid undefined result */ + return 0; + } + } +} + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_FIX_INLINES_H */ diff --git a/port/third_party/opus/silk/LPC_analysis_filter.c b/port/third_party/opus/silk/LPC_analysis_filter.c new file mode 100644 index 00000000..d34b5eb7 --- /dev/null +++ b/port/third_party/opus/silk/LPC_analysis_filter.c @@ -0,0 +1,111 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "celt_lpc.h" + +/*******************************************/ +/* LPC analysis filter */ +/* NB! State is kept internally and the */ +/* filter always starts with zero state */ +/* first d output samples are set to zero */ +/*******************************************/ + +/* OPT: Using celt_fir() for this function should be faster, but it may cause + integer overflows in intermediate values (not final results), which the + current implementation silences by casting to unsigned. Enabling + this should be safe in pretty much all cases, even though it is not technically + C89-compliant. */ +#define USE_CELT_FIR 0 + +void silk_LPC_analysis_filter( + opus_int16 *out, /* O Output signal */ + const opus_int16 *in, /* I Input signal */ + const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */ + const opus_int32 len, /* I Signal length */ + const opus_int32 d, /* I Filter order */ + int arch /* I Run-time architecture */ +) +{ + opus_int j; +#if defined(FIXED_POINT) && USE_CELT_FIR + opus_int16 num[SILK_MAX_ORDER_LPC]; +#else + int ix; + opus_int32 out32_Q12, out32; + const opus_int16 *in_ptr; +#endif + + celt_assert( d >= 6 ); + celt_assert( (d & 1) == 0 ); + celt_assert( d <= len ); + +#if defined(FIXED_POINT) && USE_CELT_FIR + celt_assert( d <= SILK_MAX_ORDER_LPC ); + for ( j = 0; j < d; j++ ) { + num[ j ] = -B[ j ]; + } + celt_fir( in + d, num, out + d, len - d, d, arch ); + for ( j = 0; j < d; j++ ) { + out[ j ] = 0; + } +#else + (void)arch; + for( ix = d; ix < len; ix++ ) { + in_ptr = &in[ ix - 1 ]; + + out32_Q12 = silk_SMULBB( in_ptr[ 0 ], B[ 0 ] ); + /* Allowing wrap around so that two wraps can cancel each other. The rare + cases where the result wraps around can only be triggered by invalid streams*/ + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -1 ], B[ 1 ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -2 ], B[ 2 ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -3 ], B[ 3 ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -4 ], B[ 4 ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -5 ], B[ 5 ] ); + for( j = 6; j < d; j += 2 ) { + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -j ], B[ j ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -j - 1 ], B[ j + 1 ] ); + } + + /* Subtract prediction */ + out32_Q12 = silk_SUB32_ovflw( silk_LSHIFT( (opus_int32)in_ptr[ 1 ], 12 ), out32_Q12 ); + + /* Scale to Q0 */ + out32 = silk_RSHIFT_ROUND( out32_Q12, 12 ); + + /* Saturate output */ + out[ ix ] = (opus_int16)silk_SAT16( out32 ); + } + + /* Set first d output samples to zero */ + silk_memset( out, 0, d * sizeof( opus_int16 ) ); +#endif +} diff --git a/port/third_party/opus/silk/LPC_fit.c b/port/third_party/opus/silk/LPC_fit.c new file mode 100644 index 00000000..c0690a1f --- /dev/null +++ b/port/third_party/opus/silk/LPC_fit.c @@ -0,0 +1,82 @@ +/*********************************************************************** +Copyright (c) 2013, Koen Vos. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Convert int32 coefficients to int16 coefs and make sure there's no wrap-around. + This logic is reused in _celt_lpc(). Any bug fixes should also be applied there. */ +void silk_LPC_fit( + opus_int16 *a_QOUT, /* O Output signal */ + opus_int32 *a_QIN, /* I/O Input signal */ + const opus_int QOUT, /* I Input Q domain */ + const opus_int QIN, /* I Input Q domain */ + const opus_int d /* I Filter order */ +) +{ + opus_int i, k, idx = 0; + opus_int32 maxabs, absval, chirp_Q16; + + /* Limit the maximum absolute value of the prediction coefficients, so that they'll fit in int16 */ + for( i = 0; i < 10; i++ ) { + /* Find maximum absolute value and its index */ + maxabs = 0; + for( k = 0; k < d; k++ ) { + absval = silk_abs( a_QIN[k] ); + if( absval > maxabs ) { + maxabs = absval; + idx = k; + } + } + maxabs = silk_RSHIFT_ROUND( maxabs, QIN - QOUT ); + + if( maxabs > silk_int16_MAX ) { + /* Reduce magnitude of prediction coefficients */ + maxabs = silk_min( maxabs, 163838 ); /* ( silk_int32_MAX >> 14 ) + silk_int16_MAX = 163838 */ + chirp_Q16 = SILK_FIX_CONST( 0.999, 16 ) - silk_DIV32( silk_LSHIFT( maxabs - silk_int16_MAX, 14 ), + silk_RSHIFT32( silk_MUL( maxabs, idx + 1), 2 ) ); + silk_bwexpander_32( a_QIN, d, chirp_Q16 ); + } else { + break; + } + } + + if( i == 10 ) { + /* Reached the last iteration, clip the coefficients */ + for( k = 0; k < d; k++ ) { + a_QOUT[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( a_QIN[ k ], QIN - QOUT ) ); + a_QIN[ k ] = silk_LSHIFT( (opus_int32)a_QOUT[ k ], QIN - QOUT ); + } + } else { + for( k = 0; k < d; k++ ) { + a_QOUT[ k ] = (opus_int16)silk_RSHIFT_ROUND( a_QIN[ k ], QIN - QOUT ); + } + } +} diff --git a/port/third_party/opus/silk/LPC_inv_pred_gain.c b/port/third_party/opus/silk/LPC_inv_pred_gain.c new file mode 100644 index 00000000..a3746a6e --- /dev/null +++ b/port/third_party/opus/silk/LPC_inv_pred_gain.c @@ -0,0 +1,141 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "define.h" + +#define QA 24 +#define A_LIMIT SILK_FIX_CONST( 0.99975, QA ) + +#define MUL32_FRAC_Q(a32, b32, Q) ((opus_int32)(silk_RSHIFT_ROUND64(silk_SMULL(a32, b32), Q))) + +/* Compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +static opus_int32 LPC_inverse_pred_gain_QA_c( /* O Returns inverse prediction gain in energy domain, Q30 */ + opus_int32 A_QA[ SILK_MAX_ORDER_LPC ], /* I Prediction coefficients */ + const opus_int order /* I Prediction order */ +) +{ + opus_int k, n, mult2Q; + opus_int32 invGain_Q30, rc_Q31, rc_mult1_Q30, rc_mult2, tmp1, tmp2; + + invGain_Q30 = SILK_FIX_CONST( 1, 30 ); + for( k = order - 1; k > 0; k-- ) { + /* Check for stability */ + if( ( A_QA[ k ] > A_LIMIT ) || ( A_QA[ k ] < -A_LIMIT ) ) { + return 0; + } + + /* Set RC equal to negated AR coef */ + rc_Q31 = -silk_LSHIFT( A_QA[ k ], 31 - QA ); + + /* rc_mult1_Q30 range: [ 1 : 2^30 ] */ + rc_mult1_Q30 = silk_SUB32( SILK_FIX_CONST( 1, 30 ), silk_SMMUL( rc_Q31, rc_Q31 ) ); + silk_assert( rc_mult1_Q30 > ( 1 << 15 ) ); /* reduce A_LIMIT if fails */ + silk_assert( rc_mult1_Q30 <= ( 1 << 30 ) ); + + /* Update inverse gain */ + /* invGain_Q30 range: [ 0 : 2^30 ] */ + invGain_Q30 = silk_LSHIFT( silk_SMMUL( invGain_Q30, rc_mult1_Q30 ), 2 ); + silk_assert( invGain_Q30 >= 0 ); + silk_assert( invGain_Q30 <= ( 1 << 30 ) ); + if( invGain_Q30 < SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN, 30 ) ) { + return 0; + } + + /* rc_mult2 range: [ 2^30 : silk_int32_MAX ] */ + mult2Q = 32 - silk_CLZ32( silk_abs( rc_mult1_Q30 ) ); + rc_mult2 = silk_INVERSE32_varQ( rc_mult1_Q30, mult2Q + 30 ); + + /* Update AR coefficient */ + for( n = 0; n < (k + 1) >> 1; n++ ) { + opus_int64 tmp64; + tmp1 = A_QA[ n ]; + tmp2 = A_QA[ k - n - 1 ]; + tmp64 = silk_RSHIFT_ROUND64( silk_SMULL( silk_SUB_SAT32(tmp1, + MUL32_FRAC_Q( tmp2, rc_Q31, 31 ) ), rc_mult2 ), mult2Q); + if( tmp64 > silk_int32_MAX || tmp64 < silk_int32_MIN ) { + return 0; + } + A_QA[ n ] = ( opus_int32 )tmp64; + tmp64 = silk_RSHIFT_ROUND64( silk_SMULL( silk_SUB_SAT32(tmp2, + MUL32_FRAC_Q( tmp1, rc_Q31, 31 ) ), rc_mult2), mult2Q); + if( tmp64 > silk_int32_MAX || tmp64 < silk_int32_MIN ) { + return 0; + } + A_QA[ k - n - 1 ] = ( opus_int32 )tmp64; + } + } + + /* Check for stability */ + if( ( A_QA[ k ] > A_LIMIT ) || ( A_QA[ k ] < -A_LIMIT ) ) { + return 0; + } + + /* Set RC equal to negated AR coef */ + rc_Q31 = -silk_LSHIFT( A_QA[ 0 ], 31 - QA ); + + /* Range: [ 1 : 2^30 ] */ + rc_mult1_Q30 = silk_SUB32( SILK_FIX_CONST( 1, 30 ), silk_SMMUL( rc_Q31, rc_Q31 ) ); + + /* Update inverse gain */ + /* Range: [ 0 : 2^30 ] */ + invGain_Q30 = silk_LSHIFT( silk_SMMUL( invGain_Q30, rc_mult1_Q30 ), 2 ); + silk_assert( invGain_Q30 >= 0 ); + silk_assert( invGain_Q30 <= ( 1 << 30 ) ); + if( invGain_Q30 < SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN, 30 ) ) { + return 0; + } + + return invGain_Q30; +} + +/* For input in Q12 domain */ +opus_int32 silk_LPC_inverse_pred_gain_c( /* O Returns inverse prediction gain in energy domain, Q30 */ + const opus_int16 *A_Q12, /* I Prediction coefficients, Q12 [order] */ + const opus_int order /* I Prediction order */ +) +{ + opus_int k; + opus_int32 Atmp_QA[ SILK_MAX_ORDER_LPC ]; + opus_int32 DC_resp = 0; + + /* Increase Q domain of the AR coefficients */ + for( k = 0; k < order; k++ ) { + DC_resp += (opus_int32)A_Q12[ k ]; + Atmp_QA[ k ] = silk_LSHIFT32( (opus_int32)A_Q12[ k ], QA - 12 ); + } + /* If the DC is unstable, we don't even need to do the full calculations */ + if( DC_resp >= 4096 ) { + return 0; + } + return LPC_inverse_pred_gain_QA_c( Atmp_QA, order ); +} diff --git a/port/third_party/opus/silk/LP_variable_cutoff.c b/port/third_party/opus/silk/LP_variable_cutoff.c new file mode 100644 index 00000000..79112ad3 --- /dev/null +++ b/port/third_party/opus/silk/LP_variable_cutoff.c @@ -0,0 +1,135 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* + Elliptic/Cauer filters designed with 0.1 dB passband ripple, + 80 dB minimum stopband attenuation, and + [0.95 : 0.15 : 0.35] normalized cut off frequencies. +*/ + +#include "main.h" + +/* Helper function, interpolates the filter taps */ +static OPUS_INLINE void silk_LP_interpolate_filter_taps( + opus_int32 B_Q28[ TRANSITION_NB ], + opus_int32 A_Q28[ TRANSITION_NA ], + const opus_int ind, + const opus_int32 fac_Q16 +) +{ + opus_int nb, na; + + if( ind < TRANSITION_INT_NUM - 1 ) { + if( fac_Q16 > 0 ) { + if( fac_Q16 < 32768 ) { /* fac_Q16 is in range of a 16-bit int */ + /* Piece-wise linear interpolation of B and A */ + for( nb = 0; nb < TRANSITION_NB; nb++ ) { + B_Q28[ nb ] = silk_SMLAWB( + silk_Transition_LP_B_Q28[ ind ][ nb ], + silk_Transition_LP_B_Q28[ ind + 1 ][ nb ] - + silk_Transition_LP_B_Q28[ ind ][ nb ], + fac_Q16 ); + } + for( na = 0; na < TRANSITION_NA; na++ ) { + A_Q28[ na ] = silk_SMLAWB( + silk_Transition_LP_A_Q28[ ind ][ na ], + silk_Transition_LP_A_Q28[ ind + 1 ][ na ] - + silk_Transition_LP_A_Q28[ ind ][ na ], + fac_Q16 ); + } + } else { /* ( fac_Q16 - ( 1 << 16 ) ) is in range of a 16-bit int */ + silk_assert( fac_Q16 - ( 1 << 16 ) == silk_SAT16( fac_Q16 - ( 1 << 16 ) ) ); + /* Piece-wise linear interpolation of B and A */ + for( nb = 0; nb < TRANSITION_NB; nb++ ) { + B_Q28[ nb ] = silk_SMLAWB( + silk_Transition_LP_B_Q28[ ind + 1 ][ nb ], + silk_Transition_LP_B_Q28[ ind + 1 ][ nb ] - + silk_Transition_LP_B_Q28[ ind ][ nb ], + fac_Q16 - ( (opus_int32)1 << 16 ) ); + } + for( na = 0; na < TRANSITION_NA; na++ ) { + A_Q28[ na ] = silk_SMLAWB( + silk_Transition_LP_A_Q28[ ind + 1 ][ na ], + silk_Transition_LP_A_Q28[ ind + 1 ][ na ] - + silk_Transition_LP_A_Q28[ ind ][ na ], + fac_Q16 - ( (opus_int32)1 << 16 ) ); + } + } + } else { + silk_memcpy( B_Q28, silk_Transition_LP_B_Q28[ ind ], TRANSITION_NB * sizeof( opus_int32 ) ); + silk_memcpy( A_Q28, silk_Transition_LP_A_Q28[ ind ], TRANSITION_NA * sizeof( opus_int32 ) ); + } + } else { + silk_memcpy( B_Q28, silk_Transition_LP_B_Q28[ TRANSITION_INT_NUM - 1 ], TRANSITION_NB * sizeof( opus_int32 ) ); + silk_memcpy( A_Q28, silk_Transition_LP_A_Q28[ TRANSITION_INT_NUM - 1 ], TRANSITION_NA * sizeof( opus_int32 ) ); + } +} + +/* Low-pass filter with variable cutoff frequency based on */ +/* piece-wise linear interpolation between elliptic filters */ +/* Start by setting psEncC->mode <> 0; */ +/* Deactivate by setting psEncC->mode = 0; */ +void silk_LP_variable_cutoff( + silk_LP_state *psLP, /* I/O LP filter state */ + opus_int16 *frame, /* I/O Low-pass filtered output signal */ + const opus_int frame_length /* I Frame length */ +) +{ + opus_int32 B_Q28[ TRANSITION_NB ], A_Q28[ TRANSITION_NA ], fac_Q16 = 0; + opus_int ind = 0; + + silk_assert( psLP->transition_frame_no >= 0 && psLP->transition_frame_no <= TRANSITION_FRAMES ); + + /* Run filter if needed */ + if( psLP->mode != 0 ) { + /* Calculate index and interpolation factor for interpolation */ +#if( TRANSITION_INT_STEPS == 64 ) + fac_Q16 = silk_LSHIFT( TRANSITION_FRAMES - psLP->transition_frame_no, 16 - 6 ); +#else + fac_Q16 = silk_DIV32_16( silk_LSHIFT( TRANSITION_FRAMES - psLP->transition_frame_no, 16 ), TRANSITION_FRAMES ); +#endif + ind = silk_RSHIFT( fac_Q16, 16 ); + fac_Q16 -= silk_LSHIFT( ind, 16 ); + + silk_assert( ind >= 0 ); + silk_assert( ind < TRANSITION_INT_NUM ); + + /* Interpolate filter coefficients */ + silk_LP_interpolate_filter_taps( B_Q28, A_Q28, ind, fac_Q16 ); + + /* Update transition frame number for next frame */ + psLP->transition_frame_no = silk_LIMIT( psLP->transition_frame_no + psLP->mode, 0, TRANSITION_FRAMES ); + + /* ARMA low-pass filtering */ + silk_assert( TRANSITION_NB == 3 && TRANSITION_NA == 2 ); + silk_biquad_alt_stride1( frame, B_Q28, A_Q28, psLP->In_LP_State, frame, frame_length); + } +} diff --git a/port/third_party/opus/silk/MacroCount.h b/port/third_party/opus/silk/MacroCount.h new file mode 100644 index 00000000..dab2f57a --- /dev/null +++ b/port/third_party/opus/silk/MacroCount.h @@ -0,0 +1,710 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SIGPROCFIX_API_MACROCOUNT_H +#define SIGPROCFIX_API_MACROCOUNT_H + +#ifdef silk_MACRO_COUNT +#include +#define varDefine opus_int64 ops_count = 0; + +extern opus_int64 ops_count; + +static OPUS_INLINE opus_int64 silk_SaveCount(){ + return(ops_count); +} + +static OPUS_INLINE opus_int64 silk_SaveResetCount(){ + opus_int64 ret; + + ret = ops_count; + ops_count = 0; + return(ret); +} + +static OPUS_INLINE silk_PrintCount(){ + printf("ops_count = %d \n ", (opus_int32)ops_count); +} + +#undef silk_MUL +static OPUS_INLINE opus_int32 silk_MUL(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 4; + ret = a32 * b32; + return ret; +} + +#undef silk_MUL_uint +static OPUS_INLINE opus_uint32 silk_MUL_uint(opus_uint32 a32, opus_uint32 b32){ + opus_uint32 ret; + ops_count += 4; + ret = a32 * b32; + return ret; +} +#undef silk_MLA +static OPUS_INLINE opus_int32 silk_MLA(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 4; + ret = a32 + b32 * c32; + return ret; +} + +#undef silk_MLA_uint +static OPUS_INLINE opus_int32 silk_MLA_uint(opus_uint32 a32, opus_uint32 b32, opus_uint32 c32){ + opus_uint32 ret; + ops_count += 4; + ret = a32 + b32 * c32; + return ret; +} + +#undef silk_SMULWB +static OPUS_INLINE opus_int32 silk_SMULWB(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 5; + ret = (a32 >> 16) * (opus_int32)((opus_int16)b32) + (((a32 & 0x0000FFFF) * (opus_int32)((opus_int16)b32)) >> 16); + return ret; +} +#undef silk_SMLAWB +static OPUS_INLINE opus_int32 silk_SMLAWB(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 5; + ret = ((a32) + ((((b32) >> 16) * (opus_int32)((opus_int16)(c32))) + ((((b32) & 0x0000FFFF) * (opus_int32)((opus_int16)(c32))) >> 16))); + return ret; +} + +#undef silk_SMULWT +static OPUS_INLINE opus_int32 silk_SMULWT(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 4; + ret = (a32 >> 16) * (b32 >> 16) + (((a32 & 0x0000FFFF) * (b32 >> 16)) >> 16); + return ret; +} +#undef silk_SMLAWT +static OPUS_INLINE opus_int32 silk_SMLAWT(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 4; + ret = a32 + ((b32 >> 16) * (c32 >> 16)) + (((b32 & 0x0000FFFF) * ((c32 >> 16)) >> 16)); + return ret; +} + +#undef silk_SMULBB +static OPUS_INLINE opus_int32 silk_SMULBB(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 1; + ret = (opus_int32)((opus_int16)a32) * (opus_int32)((opus_int16)b32); + return ret; +} +#undef silk_SMLABB +static OPUS_INLINE opus_int32 silk_SMLABB(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 1; + ret = a32 + (opus_int32)((opus_int16)b32) * (opus_int32)((opus_int16)c32); + return ret; +} + +#undef silk_SMULBT +static OPUS_INLINE opus_int32 silk_SMULBT(opus_int32 a32, opus_int32 b32 ){ + opus_int32 ret; + ops_count += 4; + ret = ((opus_int32)((opus_int16)a32)) * (b32 >> 16); + return ret; +} + +#undef silk_SMLABT +static OPUS_INLINE opus_int32 silk_SMLABT(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 1; + ret = a32 + ((opus_int32)((opus_int16)b32)) * (c32 >> 16); + return ret; +} + +#undef silk_SMULTT +static OPUS_INLINE opus_int32 silk_SMULTT(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 1; + ret = (a32 >> 16) * (b32 >> 16); + return ret; +} + +#undef silk_SMLATT +static OPUS_INLINE opus_int32 silk_SMLATT(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 1; + ret = a32 + (b32 >> 16) * (c32 >> 16); + return ret; +} + + +/* multiply-accumulate macros that allow overflow in the addition (ie, no asserts in debug mode)*/ +#undef silk_MLA_ovflw +#define silk_MLA_ovflw silk_MLA + +#undef silk_SMLABB_ovflw +#define silk_SMLABB_ovflw silk_SMLABB + +#undef silk_SMLABT_ovflw +#define silk_SMLABT_ovflw silk_SMLABT + +#undef silk_SMLATT_ovflw +#define silk_SMLATT_ovflw silk_SMLATT + +#undef silk_SMLAWB_ovflw +#define silk_SMLAWB_ovflw silk_SMLAWB + +#undef silk_SMLAWT_ovflw +#define silk_SMLAWT_ovflw silk_SMLAWT + +#undef silk_SMULL +static OPUS_INLINE opus_int64 silk_SMULL(opus_int32 a32, opus_int32 b32){ + opus_int64 ret; + ops_count += 8; + ret = ((opus_int64)(a32) * /*(opus_int64)*/(b32)); + return ret; +} + +#undef silk_SMLAL +static OPUS_INLINE opus_int64 silk_SMLAL(opus_int64 a64, opus_int32 b32, opus_int32 c32){ + opus_int64 ret; + ops_count += 8; + ret = a64 + ((opus_int64)(b32) * /*(opus_int64)*/(c32)); + return ret; +} +#undef silk_SMLALBB +static OPUS_INLINE opus_int64 silk_SMLALBB(opus_int64 a64, opus_int16 b16, opus_int16 c16){ + opus_int64 ret; + ops_count += 4; + ret = a64 + ((opus_int64)(b16) * /*(opus_int64)*/(c16)); + return ret; +} + +#undef SigProcFIX_CLZ16 +static OPUS_INLINE opus_int32 SigProcFIX_CLZ16(opus_int16 in16) +{ + opus_int32 out32 = 0; + ops_count += 10; + if( in16 == 0 ) { + return 16; + } + /* test nibbles */ + if( in16 & 0xFF00 ) { + if( in16 & 0xF000 ) { + in16 >>= 12; + } else { + out32 += 4; + in16 >>= 8; + } + } else { + if( in16 & 0xFFF0 ) { + out32 += 8; + in16 >>= 4; + } else { + out32 += 12; + } + } + /* test bits and return */ + if( in16 & 0xC ) { + if( in16 & 0x8 ) + return out32 + 0; + else + return out32 + 1; + } else { + if( in16 & 0xE ) + return out32 + 2; + else + return out32 + 3; + } +} + +#undef SigProcFIX_CLZ32 +static OPUS_INLINE opus_int32 SigProcFIX_CLZ32(opus_int32 in32) +{ + /* test highest 16 bits and convert to opus_int16 */ + ops_count += 2; + if( in32 & 0xFFFF0000 ) { + return SigProcFIX_CLZ16((opus_int16)(in32 >> 16)); + } else { + return SigProcFIX_CLZ16((opus_int16)in32) + 16; + } +} + +#undef silk_DIV32 +static OPUS_INLINE opus_int32 silk_DIV32(opus_int32 a32, opus_int32 b32){ + ops_count += 64; + return a32 / b32; +} + +#undef silk_DIV32_16 +static OPUS_INLINE opus_int32 silk_DIV32_16(opus_int32 a32, opus_int32 b32){ + ops_count += 32; + return a32 / b32; +} + +#undef silk_SAT8 +static OPUS_INLINE opus_int8 silk_SAT8(opus_int64 a){ + opus_int8 tmp; + ops_count += 1; + tmp = (opus_int8)((a) > silk_int8_MAX ? silk_int8_MAX : \ + ((a) < silk_int8_MIN ? silk_int8_MIN : (a))); + return(tmp); +} + +#undef silk_SAT16 +static OPUS_INLINE opus_int16 silk_SAT16(opus_int64 a){ + opus_int16 tmp; + ops_count += 1; + tmp = (opus_int16)((a) > silk_int16_MAX ? silk_int16_MAX : \ + ((a) < silk_int16_MIN ? silk_int16_MIN : (a))); + return(tmp); +} +#undef silk_SAT32 +static OPUS_INLINE opus_int32 silk_SAT32(opus_int64 a){ + opus_int32 tmp; + ops_count += 1; + tmp = (opus_int32)((a) > silk_int32_MAX ? silk_int32_MAX : \ + ((a) < silk_int32_MIN ? silk_int32_MIN : (a))); + return(tmp); +} +#undef silk_POS_SAT32 +static OPUS_INLINE opus_int32 silk_POS_SAT32(opus_int64 a){ + opus_int32 tmp; + ops_count += 1; + tmp = (opus_int32)((a) > silk_int32_MAX ? silk_int32_MAX : (a)); + return(tmp); +} + +#undef silk_ADD_POS_SAT8 +static OPUS_INLINE opus_int8 silk_ADD_POS_SAT8(opus_int64 a, opus_int64 b){ + opus_int8 tmp; + ops_count += 1; + tmp = (opus_int8)((((a)+(b)) & 0x80) ? silk_int8_MAX : ((a)+(b))); + return(tmp); +} +#undef silk_ADD_POS_SAT16 +static OPUS_INLINE opus_int16 silk_ADD_POS_SAT16(opus_int64 a, opus_int64 b){ + opus_int16 tmp; + ops_count += 1; + tmp = (opus_int16)((((a)+(b)) & 0x8000) ? silk_int16_MAX : ((a)+(b))); + return(tmp); +} + +#undef silk_ADD_POS_SAT32 +static OPUS_INLINE opus_int32 silk_ADD_POS_SAT32(opus_int64 a, opus_int64 b){ + opus_int32 tmp; + ops_count += 1; + tmp = (opus_int32)((((a)+(b)) & 0x80000000) ? silk_int32_MAX : ((a)+(b))); + return(tmp); +} + +#undef silk_LSHIFT8 +static OPUS_INLINE opus_int8 silk_LSHIFT8(opus_int8 a, opus_int32 shift){ + opus_int8 ret; + ops_count += 1; + ret = a << shift; + return ret; +} +#undef silk_LSHIFT16 +static OPUS_INLINE opus_int16 silk_LSHIFT16(opus_int16 a, opus_int32 shift){ + opus_int16 ret; + ops_count += 1; + ret = a << shift; + return ret; +} +#undef silk_LSHIFT32 +static OPUS_INLINE opus_int32 silk_LSHIFT32(opus_int32 a, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a << shift; + return ret; +} +#undef silk_LSHIFT64 +static OPUS_INLINE opus_int64 silk_LSHIFT64(opus_int64 a, opus_int shift){ + ops_count += 1; + return a << shift; +} + +#undef silk_LSHIFT_ovflw +static OPUS_INLINE opus_int32 silk_LSHIFT_ovflw(opus_int32 a, opus_int32 shift){ + ops_count += 1; + return a << shift; +} + +#undef silk_LSHIFT_uint +static OPUS_INLINE opus_uint32 silk_LSHIFT_uint(opus_uint32 a, opus_int32 shift){ + opus_uint32 ret; + ops_count += 1; + ret = a << shift; + return ret; +} + +#undef silk_RSHIFT8 +static OPUS_INLINE opus_int8 silk_RSHIFT8(opus_int8 a, opus_int32 shift){ + ops_count += 1; + return a >> shift; +} +#undef silk_RSHIFT16 +static OPUS_INLINE opus_int16 silk_RSHIFT16(opus_int16 a, opus_int32 shift){ + ops_count += 1; + return a >> shift; +} +#undef silk_RSHIFT32 +static OPUS_INLINE opus_int32 silk_RSHIFT32(opus_int32 a, opus_int32 shift){ + ops_count += 1; + return a >> shift; +} +#undef silk_RSHIFT64 +static OPUS_INLINE opus_int64 silk_RSHIFT64(opus_int64 a, opus_int64 shift){ + ops_count += 1; + return a >> shift; +} + +#undef silk_RSHIFT_uint +static OPUS_INLINE opus_uint32 silk_RSHIFT_uint(opus_uint32 a, opus_int32 shift){ + ops_count += 1; + return a >> shift; +} + +#undef silk_ADD_LSHIFT +static OPUS_INLINE opus_int32 silk_ADD_LSHIFT(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a + (b << shift); + return ret; /* shift >= 0*/ +} +#undef silk_ADD_LSHIFT32 +static OPUS_INLINE opus_int32 silk_ADD_LSHIFT32(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a + (b << shift); + return ret; /* shift >= 0*/ +} +#undef silk_ADD_LSHIFT_uint +static OPUS_INLINE opus_uint32 silk_ADD_LSHIFT_uint(opus_uint32 a, opus_uint32 b, opus_int32 shift){ + opus_uint32 ret; + ops_count += 1; + ret = a + (b << shift); + return ret; /* shift >= 0*/ +} +#undef silk_ADD_RSHIFT +static OPUS_INLINE opus_int32 silk_ADD_RSHIFT(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a + (b >> shift); + return ret; /* shift > 0*/ +} +#undef silk_ADD_RSHIFT32 +static OPUS_INLINE opus_int32 silk_ADD_RSHIFT32(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a + (b >> shift); + return ret; /* shift > 0*/ +} +#undef silk_ADD_RSHIFT_uint +static OPUS_INLINE opus_uint32 silk_ADD_RSHIFT_uint(opus_uint32 a, opus_uint32 b, opus_int32 shift){ + opus_uint32 ret; + ops_count += 1; + ret = a + (b >> shift); + return ret; /* shift > 0*/ +} +#undef silk_SUB_LSHIFT32 +static OPUS_INLINE opus_int32 silk_SUB_LSHIFT32(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a - (b << shift); + return ret; /* shift >= 0*/ +} +#undef silk_SUB_RSHIFT32 +static OPUS_INLINE opus_int32 silk_SUB_RSHIFT32(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a - (b >> shift); + return ret; /* shift > 0*/ +} + +#undef silk_RSHIFT_ROUND +static OPUS_INLINE opus_int32 silk_RSHIFT_ROUND(opus_int32 a, opus_int32 shift){ + opus_int32 ret; + ops_count += 3; + ret = shift == 1 ? (a >> 1) + (a & 1) : ((a >> (shift - 1)) + 1) >> 1; + return ret; +} + +#undef silk_RSHIFT_ROUND64 +static OPUS_INLINE opus_int64 silk_RSHIFT_ROUND64(opus_int64 a, opus_int32 shift){ + opus_int64 ret; + ops_count += 6; + ret = shift == 1 ? (a >> 1) + (a & 1) : ((a >> (shift - 1)) + 1) >> 1; + return ret; +} + +#undef silk_abs_int64 +static OPUS_INLINE opus_int64 silk_abs_int64(opus_int64 a){ + ops_count += 1; + return (((a) > 0) ? (a) : -(a)); /* Be careful, silk_abs returns wrong when input equals to silk_intXX_MIN*/ +} + +#undef silk_abs_int32 +static OPUS_INLINE opus_int32 silk_abs_int32(opus_int32 a){ + ops_count += 1; + return silk_abs(a); +} + + +#undef silk_min +static silk_min(a, b){ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} +#undef silk_max +static silk_max(a, b){ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} +#undef silk_sign +static silk_sign(a){ + ops_count += 1; + return ((a) > 0 ? 1 : ( (a) < 0 ? -1 : 0 )); +} + +#undef silk_ADD16 +static OPUS_INLINE opus_int16 silk_ADD16(opus_int16 a, opus_int16 b){ + opus_int16 ret; + ops_count += 1; + ret = a + b; + return ret; +} + +#undef silk_ADD32 +static OPUS_INLINE opus_int32 silk_ADD32(opus_int32 a, opus_int32 b){ + opus_int32 ret; + ops_count += 1; + ret = a + b; + return ret; +} + +#undef silk_ADD64 +static OPUS_INLINE opus_int64 silk_ADD64(opus_int64 a, opus_int64 b){ + opus_int64 ret; + ops_count += 2; + ret = a + b; + return ret; +} + +#undef silk_SUB16 +static OPUS_INLINE opus_int16 silk_SUB16(opus_int16 a, opus_int16 b){ + opus_int16 ret; + ops_count += 1; + ret = a - b; + return ret; +} + +#undef silk_SUB32 +static OPUS_INLINE opus_int32 silk_SUB32(opus_int32 a, opus_int32 b){ + opus_int32 ret; + ops_count += 1; + ret = a - b; + return ret; +} + +#undef silk_SUB64 +static OPUS_INLINE opus_int64 silk_SUB64(opus_int64 a, opus_int64 b){ + opus_int64 ret; + ops_count += 2; + ret = a - b; + return ret; +} + +#undef silk_ADD_SAT16 +static OPUS_INLINE opus_int16 silk_ADD_SAT16( opus_int16 a16, opus_int16 b16 ) { + opus_int16 res; + /* Nb will be counted in AKP_add32 and silk_SAT16*/ + res = (opus_int16)silk_SAT16( silk_ADD32( (opus_int32)(a16), (b16) ) ); + return res; +} + +#undef silk_ADD_SAT32 +static OPUS_INLINE opus_int32 silk_ADD_SAT32(opus_int32 a32, opus_int32 b32){ + opus_int32 res; + ops_count += 1; + res = ((((a32) + (b32)) & 0x80000000) == 0 ? \ + ((((a32) & (b32)) & 0x80000000) != 0 ? silk_int32_MIN : (a32)+(b32)) : \ + ((((a32) | (b32)) & 0x80000000) == 0 ? silk_int32_MAX : (a32)+(b32)) ); + return res; +} + +#undef silk_ADD_SAT64 +static OPUS_INLINE opus_int64 silk_ADD_SAT64( opus_int64 a64, opus_int64 b64 ) { + opus_int64 res; + ops_count += 1; + res = ((((a64) + (b64)) & 0x8000000000000000LL) == 0 ? \ + ((((a64) & (b64)) & 0x8000000000000000LL) != 0 ? silk_int64_MIN : (a64)+(b64)) : \ + ((((a64) | (b64)) & 0x8000000000000000LL) == 0 ? silk_int64_MAX : (a64)+(b64)) ); + return res; +} + +#undef silk_SUB_SAT16 +static OPUS_INLINE opus_int16 silk_SUB_SAT16( opus_int16 a16, opus_int16 b16 ) { + opus_int16 res; + silk_assert(0); + /* Nb will be counted in sub-macros*/ + res = (opus_int16)silk_SAT16( silk_SUB32( (opus_int32)(a16), (b16) ) ); + return res; +} + +#undef silk_SUB_SAT32 +static OPUS_INLINE opus_int32 silk_SUB_SAT32( opus_int32 a32, opus_int32 b32 ) { + opus_int32 res; + ops_count += 1; + res = ((((a32)-(b32)) & 0x80000000) == 0 ? \ + (( (a32) & ((b32)^0x80000000) & 0x80000000) ? silk_int32_MIN : (a32)-(b32)) : \ + ((((a32)^0x80000000) & (b32) & 0x80000000) ? silk_int32_MAX : (a32)-(b32)) ); + return res; +} + +#undef silk_SUB_SAT64 +static OPUS_INLINE opus_int64 silk_SUB_SAT64( opus_int64 a64, opus_int64 b64 ) { + opus_int64 res; + ops_count += 1; + res = ((((a64)-(b64)) & 0x8000000000000000LL) == 0 ? \ + (( (a64) & ((b64)^0x8000000000000000LL) & 0x8000000000000000LL) ? silk_int64_MIN : (a64)-(b64)) : \ + ((((a64)^0x8000000000000000LL) & (b64) & 0x8000000000000000LL) ? silk_int64_MAX : (a64)-(b64)) ); + + return res; +} + +#undef silk_SMULWW +static OPUS_INLINE opus_int32 silk_SMULWW(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + /* Nb will be counted in sub-macros*/ + ret = silk_MLA(silk_SMULWB((a32), (b32)), (a32), silk_RSHIFT_ROUND((b32), 16)); + return ret; +} + +#undef silk_SMLAWW +static OPUS_INLINE opus_int32 silk_SMLAWW(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + /* Nb will be counted in sub-macros*/ + ret = silk_MLA(silk_SMLAWB((a32), (b32), (c32)), (b32), silk_RSHIFT_ROUND((c32), 16)); + return ret; +} + +#undef silk_min_int +static OPUS_INLINE opus_int silk_min_int(opus_int a, opus_int b) +{ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} + +#undef silk_min_16 +static OPUS_INLINE opus_int16 silk_min_16(opus_int16 a, opus_int16 b) +{ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} +#undef silk_min_32 +static OPUS_INLINE opus_int32 silk_min_32(opus_int32 a, opus_int32 b) +{ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} +#undef silk_min_64 +static OPUS_INLINE opus_int64 silk_min_64(opus_int64 a, opus_int64 b) +{ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} + +/* silk_min() versions with typecast in the function call */ +#undef silk_max_int +static OPUS_INLINE opus_int silk_max_int(opus_int a, opus_int b) +{ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} +#undef silk_max_16 +static OPUS_INLINE opus_int16 silk_max_16(opus_int16 a, opus_int16 b) +{ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} +#undef silk_max_32 +static OPUS_INLINE opus_int32 silk_max_32(opus_int32 a, opus_int32 b) +{ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} + +#undef silk_max_64 +static OPUS_INLINE opus_int64 silk_max_64(opus_int64 a, opus_int64 b) +{ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} + + +#undef silk_LIMIT_int +static OPUS_INLINE opus_int silk_LIMIT_int(opus_int a, opus_int limit1, opus_int limit2) +{ + opus_int ret; + ops_count += 6; + + ret = ((limit1) > (limit2) ? ((a) > (limit1) ? (limit1) : ((a) < (limit2) ? (limit2) : (a))) \ + : ((a) > (limit2) ? (limit2) : ((a) < (limit1) ? (limit1) : (a)))); + + return(ret); +} + +#undef silk_LIMIT_16 +static OPUS_INLINE opus_int16 silk_LIMIT_16(opus_int16 a, opus_int16 limit1, opus_int16 limit2) +{ + opus_int16 ret; + ops_count += 6; + + ret = ((limit1) > (limit2) ? ((a) > (limit1) ? (limit1) : ((a) < (limit2) ? (limit2) : (a))) \ + : ((a) > (limit2) ? (limit2) : ((a) < (limit1) ? (limit1) : (a)))); + +return(ret); +} + + +#undef silk_LIMIT_32 +static OPUS_INLINE opus_int32 silk_LIMIT_32(opus_int32 a, opus_int32 limit1, opus_int32 limit2) +{ + opus_int32 ret; + ops_count += 6; + + ret = ((limit1) > (limit2) ? ((a) > (limit1) ? (limit1) : ((a) < (limit2) ? (limit2) : (a))) \ + : ((a) > (limit2) ? (limit2) : ((a) < (limit1) ? (limit1) : (a)))); + return(ret); +} + +#else +#define varDefine +#define silk_SaveCount() + +#endif +#endif + diff --git a/port/third_party/opus/silk/MacroDebug.h b/port/third_party/opus/silk/MacroDebug.h new file mode 100644 index 00000000..3110da9a --- /dev/null +++ b/port/third_party/opus/silk/MacroDebug.h @@ -0,0 +1,945 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Copyright (C) 2012 Xiph.Org Foundation +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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef MACRO_DEBUG_H +#define MACRO_DEBUG_H + +/* Redefine macro functions with extensive assertion in DEBUG mode. + As functions can't be undefined, this file can't work with SigProcFIX_MacroCount.h */ + +#if ( defined (FIXED_DEBUG) || ( 0 && defined (_DEBUG) ) ) && !defined (silk_MACRO_COUNT) + +#undef silk_ADD16 +#define silk_ADD16(a,b) silk_ADD16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_ADD16_(opus_int16 a, opus_int16 b, char *file, int line){ + opus_int16 ret; + + ret = a + b; + if ( ret != silk_ADD_SAT16( a, b ) ) + { + fprintf (stderr, "silk_ADD16(%d, %d) in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_ADD32 +#define silk_ADD32(a,b) silk_ADD32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_ADD32_(opus_int32 a, opus_int32 b, char *file, int line){ + opus_int32 ret; + + ret = (opus_int32)((opus_uint32)a + (opus_uint32)b); + if ( ret != silk_ADD_SAT32( a, b ) ) + { + fprintf (stderr, "silk_ADD32(%d, %d) in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_ADD64 +#define silk_ADD64(a,b) silk_ADD64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_ADD64_(opus_int64 a, opus_int64 b, char *file, int line){ + opus_int64 ret; + + ret = a + b; + if ( ret != silk_ADD_SAT64( a, b ) ) + { + fprintf (stderr, "silk_ADD64(%lld, %lld) in %s: line %d\n", (long long)a, (long long)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SUB16 +#define silk_SUB16(a,b) silk_SUB16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_SUB16_(opus_int16 a, opus_int16 b, char *file, int line){ + opus_int16 ret; + + ret = a - b; + if ( ret != silk_SUB_SAT16( a, b ) ) + { + fprintf (stderr, "silk_SUB16(%d, %d) in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SUB32 +#define silk_SUB32(a,b) silk_SUB32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SUB32_(opus_int32 a, opus_int32 b, char *file, int line){ + opus_int64 ret; + + ret = a - (opus_int64)b; + if ( ret != silk_SUB_SAT32( a, b ) ) + { + fprintf (stderr, "silk_SUB32(%d, %d) in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SUB64 +#define silk_SUB64(a,b) silk_SUB64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_SUB64_(opus_int64 a, opus_int64 b, char *file, int line){ + opus_int64 ret; + + ret = a - b; + if ( ret != silk_SUB_SAT64( a, b ) ) + { + fprintf (stderr, "silk_SUB64(%lld, %lld) in %s: line %d\n", (long long)a, (long long)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_ADD_SAT16 +#define silk_ADD_SAT16(a,b) silk_ADD_SAT16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_ADD_SAT16_( opus_int16 a16, opus_int16 b16, char *file, int line) { + opus_int16 res; + res = (opus_int16)silk_SAT16( silk_ADD32( (opus_int32)(a16), (b16) ) ); + if ( res != silk_SAT16( (opus_int32)a16 + (opus_int32)b16 ) ) + { + fprintf (stderr, "silk_ADD_SAT16(%d, %d) in %s: line %d\n", a16, b16, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_ADD_SAT32 +#define silk_ADD_SAT32(a,b) silk_ADD_SAT32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_ADD_SAT32_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 res; + res = ((((opus_uint32)(a32) + (opus_uint32)(b32)) & 0x80000000) == 0 ? \ + ((((a32) & (b32)) & 0x80000000) != 0 ? silk_int32_MIN : (a32)+(b32)) : \ + ((((a32) | (b32)) & 0x80000000) == 0 ? silk_int32_MAX : (a32)+(b32)) ); + if ( res != silk_SAT32( (opus_int64)a32 + (opus_int64)b32 ) ) + { + fprintf (stderr, "silk_ADD_SAT32(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_ADD_SAT64 +#define silk_ADD_SAT64(a,b) silk_ADD_SAT64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_ADD_SAT64_( opus_int64 a64, opus_int64 b64, char *file, int line) { + opus_int64 res; + int fail = 0; + res = ((((a64) + (b64)) & 0x8000000000000000LL) == 0 ? \ + ((((a64) & (b64)) & 0x8000000000000000LL) != 0 ? silk_int64_MIN : (a64)+(b64)) : \ + ((((a64) | (b64)) & 0x8000000000000000LL) == 0 ? silk_int64_MAX : (a64)+(b64)) ); + if( res != a64 + b64 ) { + /* Check that we saturated to the correct extreme value */ + if ( !(( res == silk_int64_MAX && ( ( a64 >> 1 ) + ( b64 >> 1 ) > ( silk_int64_MAX >> 3 ) ) ) || + ( res == silk_int64_MIN && ( ( a64 >> 1 ) + ( b64 >> 1 ) < ( silk_int64_MIN >> 3 ) ) ) ) ) + { + fail = 1; + } + } else { + /* Saturation not necessary */ + fail = res != a64 + b64; + } + if ( fail ) + { + fprintf (stderr, "silk_ADD_SAT64(%lld, %lld) in %s: line %d\n", (long long)a64, (long long)b64, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_SUB_SAT16 +#define silk_SUB_SAT16(a,b) silk_SUB_SAT16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_SUB_SAT16_( opus_int16 a16, opus_int16 b16, char *file, int line ) { + opus_int16 res; + res = (opus_int16)silk_SAT16( silk_SUB32( (opus_int32)(a16), (b16) ) ); + if ( res != silk_SAT16( (opus_int32)a16 - (opus_int32)b16 ) ) + { + fprintf (stderr, "silk_SUB_SAT16(%d, %d) in %s: line %d\n", a16, b16, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_SUB_SAT32 +#define silk_SUB_SAT32(a,b) silk_SUB_SAT32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SUB_SAT32_( opus_int32 a32, opus_int32 b32, char *file, int line ) { + opus_int32 res; + res = ((((opus_uint32)(a32)-(opus_uint32)(b32)) & 0x80000000) == 0 ? \ + (( (a32) & ((b32)^0x80000000) & 0x80000000) ? silk_int32_MIN : (a32)-(b32)) : \ + ((((a32)^0x80000000) & (b32) & 0x80000000) ? silk_int32_MAX : (a32)-(b32)) ); + if ( res != silk_SAT32( (opus_int64)a32 - (opus_int64)b32 ) ) + { + fprintf (stderr, "silk_SUB_SAT32(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_SUB_SAT64 +#define silk_SUB_SAT64(a,b) silk_SUB_SAT64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_SUB_SAT64_( opus_int64 a64, opus_int64 b64, char *file, int line ) { + opus_int64 res; + int fail = 0; + res = ((((a64)-(b64)) & 0x8000000000000000LL) == 0 ? \ + (( (a64) & ((b64)^0x8000000000000000LL) & 0x8000000000000000LL) ? silk_int64_MIN : (a64)-(b64)) : \ + ((((a64)^0x8000000000000000LL) & (b64) & 0x8000000000000000LL) ? silk_int64_MAX : (a64)-(b64)) ); + if( res != a64 - b64 ) { + /* Check that we saturated to the correct extreme value */ + if( !(( res == silk_int64_MAX && ( ( a64 >> 1 ) + ( b64 >> 1 ) > ( silk_int64_MAX >> 3 ) ) ) || + ( res == silk_int64_MIN && ( ( a64 >> 1 ) + ( b64 >> 1 ) < ( silk_int64_MIN >> 3 ) ) ) )) + { + fail = 1; + } + } else { + /* Saturation not necessary */ + fail = res != a64 - b64; + } + if ( fail ) + { + fprintf (stderr, "silk_SUB_SAT64(%lld, %lld) in %s: line %d\n", (long long)a64, (long long)b64, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_MUL +#define silk_MUL(a,b) silk_MUL_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_MUL_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 ret; + opus_int64 ret64; + ret = (opus_int32)((opus_uint32)a32 * (opus_uint32)b32); + ret64 = (opus_int64)a32 * (opus_int64)b32; + if ( (opus_int64)ret != ret64 ) + { + fprintf (stderr, "silk_MUL(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_MUL_uint +#define silk_MUL_uint(a,b) silk_MUL_uint_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_MUL_uint_(opus_uint32 a32, opus_uint32 b32, char *file, int line){ + opus_uint32 ret; + ret = a32 * b32; + if ( (opus_uint64)ret != (opus_uint64)a32 * (opus_uint64)b32 ) + { + fprintf (stderr, "silk_MUL_uint(%u, %u) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_MLA +#define silk_MLA(a,b,c) silk_MLA_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_MLA_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + b32 * c32; + if ( (opus_int64)ret != (opus_int64)a32 + (opus_int64)b32 * (opus_int64)c32 ) + { + fprintf (stderr, "silk_MLA(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_MLA_uint +#define silk_MLA_uint(a,b,c) silk_MLA_uint_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_MLA_uint_(opus_uint32 a32, opus_uint32 b32, opus_uint32 c32, char *file, int line){ + opus_uint32 ret; + ret = a32 + b32 * c32; + if ( (opus_int64)ret != (opus_int64)a32 + (opus_int64)b32 * (opus_int64)c32 ) + { + fprintf (stderr, "silk_MLA_uint(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMULWB +#define silk_SMULWB(a,b) silk_SMULWB_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMULWB_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 ret; + ret = (a32 >> 16) * (opus_int32)((opus_int16)b32) + (((a32 & 0x0000FFFF) * (opus_int32)((opus_int16)b32)) >> 16); + if ( (opus_int64)ret != ((opus_int64)a32 * (opus_int16)b32) >> 16 ) + { + fprintf (stderr, "silk_SMULWB(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMLAWB +#define silk_SMLAWB(a,b,c) silk_SMLAWB_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLAWB_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = silk_ADD32_ovflw( a32, silk_SMULWB( b32, c32 ) ); + if ( ret != silk_ADD_SAT32( a32, silk_SMULWB( b32, c32 ) ) ) + { + fprintf (stderr, "silk_SMLAWB(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMULWT +#define silk_SMULWT(a,b) silk_SMULWT_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMULWT_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 ret; + ret = (a32 >> 16) * (b32 >> 16) + (((a32 & 0x0000FFFF) * (b32 >> 16)) >> 16); + if ( (opus_int64)ret != ((opus_int64)a32 * (b32 >> 16)) >> 16 ) + { + fprintf (stderr, "silk_SMULWT(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMLAWT +#define silk_SMLAWT(a,b,c) silk_SMLAWT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLAWT_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + ((b32 >> 16) * (c32 >> 16)) + (((b32 & 0x0000FFFF) * ((c32 >> 16)) >> 16)); + if ( (opus_int64)ret != (opus_int64)a32 + (((opus_int64)b32 * (c32 >> 16)) >> 16) ) + { + fprintf (stderr, "silk_SMLAWT(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMULL +#define silk_SMULL(a,b) silk_SMULL_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_SMULL_(opus_int64 a64, opus_int64 b64, char *file, int line){ + opus_int64 ret64; + int fail = 0; + ret64 = a64 * b64; + if( b64 != 0 ) { + fail = a64 != (ret64 / b64); + } else if( a64 != 0 ) { + fail = b64 != (ret64 / a64); + } + if ( fail ) + { + fprintf (stderr, "silk_SMULL(%lld, %lld) in %s: line %d\n", (long long)a64, (long long)b64, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret64; +} + +/* no checking needed for silk_SMULBB */ +#undef silk_SMLABB +#define silk_SMLABB(a,b,c) silk_SMLABB_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLABB_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + (opus_int32)((opus_int16)b32) * (opus_int32)((opus_int16)c32); + if ( (opus_int64)ret != (opus_int64)a32 + (opus_int64)b32 * (opus_int16)c32 ) + { + fprintf (stderr, "silk_SMLABB(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +/* no checking needed for silk_SMULBT */ +#undef silk_SMLABT +#define silk_SMLABT(a,b,c) silk_SMLABT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLABT_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + ((opus_int32)((opus_int16)b32)) * (c32 >> 16); + if ( (opus_int64)ret != (opus_int64)a32 + (opus_int64)b32 * (c32 >> 16) ) + { + fprintf (stderr, "silk_SMLABT(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +/* no checking needed for silk_SMULTT */ +#undef silk_SMLATT +#define silk_SMLATT(a,b,c) silk_SMLATT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLATT_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + (b32 >> 16) * (c32 >> 16); + if ( (opus_int64)ret != (opus_int64)a32 + (b32 >> 16) * (c32 >> 16) ) + { + fprintf (stderr, "silk_SMLATT(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMULWW +#define silk_SMULWW(a,b) silk_SMULWW_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMULWW_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 ret, tmp1, tmp2; + opus_int64 ret64; + int fail = 0; + + ret = silk_SMULWB( a32, b32 ); + tmp1 = silk_RSHIFT_ROUND( b32, 16 ); + tmp2 = silk_MUL( a32, tmp1 ); + + fail |= (opus_int64)tmp2 != (opus_int64) a32 * (opus_int64) tmp1; + + tmp1 = ret; + ret = silk_ADD32( tmp1, tmp2 ); + fail |= silk_ADD32( tmp1, tmp2 ) != silk_ADD_SAT32( tmp1, tmp2 ); + + ret64 = silk_RSHIFT64( silk_SMULL( a32, b32 ), 16 ); + fail |= (opus_int64)ret != ret64; + + if ( fail ) + { + fprintf (stderr, "silk_SMULWW(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + + return ret; +} + +#undef silk_SMLAWW +#define silk_SMLAWW(a,b,c) silk_SMLAWW_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLAWW_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret, tmp; + + tmp = silk_SMULWW( b32, c32 ); + ret = silk_ADD32( a32, tmp ); + if ( ret != silk_ADD_SAT32( a32, tmp ) ) + { + fprintf (stderr, "silk_SMLAWW(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +/* no checking needed for silk_SMULL + no checking needed for silk_SMLAL + no checking needed for silk_SMLALBB + no checking needed for SigProcFIX_CLZ16 + no checking needed for SigProcFIX_CLZ32*/ + +#undef silk_DIV32 +#define silk_DIV32(a,b) silk_DIV32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_DIV32_(opus_int32 a32, opus_int32 b32, char *file, int line){ + if ( b32 == 0 ) + { + fprintf (stderr, "silk_DIV32(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a32 / b32; +} + +#undef silk_DIV32_16 +#define silk_DIV32_16(a,b) silk_DIV32_16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_DIV32_16_(opus_int32 a32, opus_int32 b32, char *file, int line){ + int fail = 0; + fail |= b32 == 0; + fail |= b32 > silk_int16_MAX; + fail |= b32 < silk_int16_MIN; + if ( fail ) + { + fprintf (stderr, "silk_DIV32_16(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a32 / b32; +} + +/* no checking needed for silk_SAT8 + no checking needed for silk_SAT16 + no checking needed for silk_SAT32 + no checking needed for silk_POS_SAT32 + no checking needed for silk_ADD_POS_SAT8 + no checking needed for silk_ADD_POS_SAT16 + no checking needed for silk_ADD_POS_SAT32 */ + +#undef silk_LSHIFT8 +#define silk_LSHIFT8(a,b) silk_LSHIFT8_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int8 silk_LSHIFT8_(opus_int8 a, opus_int32 shift, char *file, int line){ + opus_int8 ret; + int fail = 0; + ret = (opus_int8)((opus_uint8)a << shift); + fail |= shift < 0; + fail |= shift >= 8; + fail |= (opus_int64)ret != (opus_int64)(((opus_uint64)a) << shift); + if ( fail ) + { + fprintf (stderr, "silk_LSHIFT8(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_LSHIFT16 +#define silk_LSHIFT16(a,b) silk_LSHIFT16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_LSHIFT16_(opus_int16 a, opus_int32 shift, char *file, int line){ + opus_int16 ret; + int fail = 0; + ret = (opus_int16)((opus_uint16)a << shift); + fail |= shift < 0; + fail |= shift >= 16; + fail |= (opus_int64)ret != (opus_int64)(((opus_uint64)a) << shift); + if ( fail ) + { + fprintf (stderr, "silk_LSHIFT16(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_LSHIFT32 +#define silk_LSHIFT32(a,b) silk_LSHIFT32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_LSHIFT32_(opus_int32 a, opus_int32 shift, char *file, int line){ + opus_int32 ret; + int fail = 0; + ret = (opus_int32)((opus_uint32)a << shift); + fail |= shift < 0; + fail |= shift >= 32; + fail |= (opus_int64)ret != (opus_int64)(((opus_uint64)a) << shift); + if ( fail ) + { + fprintf (stderr, "silk_LSHIFT32(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_LSHIFT64 +#define silk_LSHIFT64(a,b) silk_LSHIFT64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_LSHIFT64_(opus_int64 a, opus_int shift, char *file, int line){ + opus_int64 ret; + int fail = 0; + ret = (opus_int64)((opus_uint64)a << shift); + fail |= shift < 0; + fail |= shift >= 64; + fail |= (ret>>shift) != ((opus_int64)a); + if ( fail ) + { + fprintf (stderr, "silk_LSHIFT64(%lld, %d) in %s: line %d\n", (long long)a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_LSHIFT_ovflw +#define silk_LSHIFT_ovflw(a,b) silk_LSHIFT_ovflw_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_LSHIFT_ovflw_(opus_int32 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift >= 32) ) /* no check for overflow */ + { + fprintf (stderr, "silk_LSHIFT_ovflw(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a << shift; +} + +#undef silk_LSHIFT_uint +#define silk_LSHIFT_uint(a,b) silk_LSHIFT_uint_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_LSHIFT_uint_(opus_uint32 a, opus_int32 shift, char *file, int line){ + opus_uint32 ret; + ret = a << shift; + if ( (shift < 0) || ((opus_int64)ret != ((opus_int64)a) << shift)) + { + fprintf (stderr, "silk_LSHIFT_uint(%u, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_RSHIFT8 +#define silk_RSHITF8(a,b) silk_RSHIFT8_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int8 silk_RSHIFT8_(opus_int8 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift>=8) ) + { + fprintf (stderr, "silk_RSHITF8(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_RSHIFT16 +#define silk_RSHITF16(a,b) silk_RSHIFT16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_RSHIFT16_(opus_int16 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift>=16) ) + { + fprintf (stderr, "silk_RSHITF16(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_RSHIFT32 +#define silk_RSHIFT32(a,b) silk_RSHIFT32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_RSHIFT32_(opus_int32 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift>=32) ) + { + fprintf (stderr, "silk_RSHITF32(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_RSHIFT64 +#define silk_RSHIFT64(a,b) silk_RSHIFT64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_RSHIFT64_(opus_int64 a, opus_int64 shift, char *file, int line){ + if ( (shift < 0) || (shift>=64) ) + { + fprintf (stderr, "silk_RSHITF64(%lld, %lld) in %s: line %d\n", (long long)a, (long long)shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_RSHIFT_uint +#define silk_RSHIFT_uint(a,b) silk_RSHIFT_uint_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_RSHIFT_uint_(opus_uint32 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift>32) ) + { + fprintf (stderr, "silk_RSHIFT_uint(%u, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_ADD_LSHIFT +#define silk_ADD_LSHIFT(a,b,c) silk_ADD_LSHIFT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE int silk_ADD_LSHIFT_(int a, int b, int shift, char *file, int line){ + opus_int16 ret; + ret = a + (opus_int16)((opus_uint16)b << shift); + if ( (shift < 0) || (shift>15) || ((opus_int64)ret != (opus_int64)a + (opus_int64)(((opus_uint64)b) << shift)) ) + { + fprintf (stderr, "silk_ADD_LSHIFT(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift >= 0 */ +} + +#undef silk_ADD_LSHIFT32 +#define silk_ADD_LSHIFT32(a,b,c) silk_ADD_LSHIFT32_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_ADD_LSHIFT32_(opus_int32 a, opus_int32 b, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = silk_ADD32_ovflw(a, (opus_int32)((opus_uint32)b << shift)); + if ( (shift < 0) || (shift>31) || ((opus_int64)ret != (opus_int64)a + (opus_int64)(((opus_uint64)b) << shift)) ) + { + fprintf (stderr, "silk_ADD_LSHIFT32(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift >= 0 */ +} + +#undef silk_ADD_LSHIFT_uint +#define silk_ADD_LSHIFT_uint(a,b,c) silk_ADD_LSHIFT_uint_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_ADD_LSHIFT_uint_(opus_uint32 a, opus_uint32 b, opus_int32 shift, char *file, int line){ + opus_uint32 ret; + ret = a + (b << shift); + if ( (shift < 0) || (shift>32) || ((opus_int64)ret != (opus_int64)a + (((opus_int64)b) << shift)) ) + { + fprintf (stderr, "silk_ADD_LSHIFT_uint(%u, %u, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift >= 0 */ +} + +#undef silk_ADD_RSHIFT +#define silk_ADD_RSHIFT(a,b,c) silk_ADD_RSHIFT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE int silk_ADD_RSHIFT_(int a, int b, int shift, char *file, int line){ + opus_int16 ret; + ret = a + (b >> shift); + if ( (shift < 0) || (shift>15) || ((opus_int64)ret != (opus_int64)a + (((opus_int64)b) >> shift)) ) + { + fprintf (stderr, "silk_ADD_RSHIFT(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift > 0 */ +} + +#undef silk_ADD_RSHIFT32 +#define silk_ADD_RSHIFT32(a,b,c) silk_ADD_RSHIFT32_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_ADD_RSHIFT32_(opus_int32 a, opus_int32 b, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = silk_ADD32_ovflw(a, (b >> shift)); + if ( (shift < 0) || (shift>31) || ((opus_int64)ret != (opus_int64)a + (((opus_int64)b) >> shift)) ) + { + fprintf (stderr, "silk_ADD_RSHIFT32(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift > 0 */ +} + +#undef silk_ADD_RSHIFT_uint +#define silk_ADD_RSHIFT_uint(a,b,c) silk_ADD_RSHIFT_uint_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_ADD_RSHIFT_uint_(opus_uint32 a, opus_uint32 b, opus_int32 shift, char *file, int line){ + opus_uint32 ret; + ret = a + (b >> shift); + if ( (shift < 0) || (shift>32) || ((opus_int64)ret != (opus_int64)a + (((opus_int64)b) >> shift)) ) + { + fprintf (stderr, "silk_ADD_RSHIFT_uint(%u, %u, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift > 0 */ +} + +#undef silk_SUB_LSHIFT32 +#define silk_SUB_LSHIFT32(a,b,c) silk_SUB_LSHIFT32_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SUB_LSHIFT32_(opus_int32 a, opus_int32 b, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = silk_SUB32_ovflw(a, (opus_int32)((opus_uint32)b << shift)); + if ( (shift < 0) || (shift>31) || ((opus_int64)ret != (opus_int64)a - (opus_int64)(((opus_uint64)b) << shift)) ) + { + fprintf (stderr, "silk_SUB_LSHIFT32(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift >= 0 */ +} + +#undef silk_SUB_RSHIFT32 +#define silk_SUB_RSHIFT32(a,b,c) silk_SUB_RSHIFT32_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SUB_RSHIFT32_(opus_int32 a, opus_int32 b, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = silk_SUB32_ovflw(a, (b >> shift)); + if ( (shift < 0) || (shift>31) || ((opus_int64)ret != (opus_int64)a - (((opus_int64)b) >> shift)) ) + { + fprintf (stderr, "silk_SUB_RSHIFT32(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift > 0 */ +} + +#undef silk_RSHIFT_ROUND +#define silk_RSHIFT_ROUND(a,b) silk_RSHIFT_ROUND_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_RSHIFT_ROUND_(opus_int32 a, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = shift == 1 ? (a >> 1) + (a & 1) : ((a >> (shift - 1)) + 1) >> 1; + /* the macro definition can't handle a shift of zero */ + if ( (shift <= 0) || (shift>31) || ((opus_int64)ret != ((opus_int64)a + ((opus_int64)1 << (shift - 1))) >> shift) ) + { + fprintf (stderr, "silk_RSHIFT_ROUND(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_RSHIFT_ROUND64 +#define silk_RSHIFT_ROUND64(a,b) silk_RSHIFT_ROUND64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_RSHIFT_ROUND64_(opus_int64 a, opus_int32 shift, char *file, int line){ + opus_int64 ret; + /* the macro definition can't handle a shift of zero */ + if ( (shift <= 0) || (shift>=64) ) + { + fprintf (stderr, "silk_RSHIFT_ROUND64(%lld, %d) in %s: line %d\n", (long long)a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + ret = shift == 1 ? (a >> 1) + (a & 1) : ((a >> (shift - 1)) + 1) >> 1; + return ret; +} + +/* silk_abs is used on floats also, so doesn't work... */ +/*#undef silk_abs +static OPUS_INLINE opus_int32 silk_abs(opus_int32 a){ + silk_assert(a != 0x80000000); + return (((a) > 0) ? (a) : -(a)); // Be careful, silk_abs returns wrong when input equals to silk_intXX_MIN +}*/ + +#undef silk_abs_int64 +#define silk_abs_int64(a) silk_abs_int64_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_abs_int64_(opus_int64 a, char *file, int line){ + if ( a == silk_int64_MIN ) + { + fprintf (stderr, "silk_abs_int64(%lld) in %s: line %d\n", (long long)a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return (((a) > 0) ? (a) : -(a)); /* Be careful, silk_abs returns wrong when input equals to silk_intXX_MIN */ +} + +#undef silk_abs_int32 +#define silk_abs_int32(a) silk_abs_int32_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_abs_int32_(opus_int32 a, char *file, int line){ + if ( a == silk_int32_MIN ) + { + fprintf (stderr, "silk_abs_int32(%d) in %s: line %d\n", a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return silk_abs(a); +} + +#undef silk_CHECK_FIT8 +#define silk_CHECK_FIT8(a) silk_CHECK_FIT8_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int8 silk_CHECK_FIT8_( opus_int64 a, char *file, int line ){ + opus_int8 ret; + ret = (opus_int8)a; + if ( (opus_int64)ret != a ) + { + fprintf (stderr, "silk_CHECK_FIT8(%lld) in %s: line %d\n", (long long)a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return( ret ); +} + +#undef silk_CHECK_FIT16 +#define silk_CHECK_FIT16(a) silk_CHECK_FIT16_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_CHECK_FIT16_( opus_int64 a, char *file, int line ){ + opus_int16 ret; + ret = (opus_int16)a; + if ( (opus_int64)ret != a ) + { + fprintf (stderr, "silk_CHECK_FIT16(%lld) in %s: line %d\n", (long long)a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return( ret ); +} + +#undef silk_CHECK_FIT32 +#define silk_CHECK_FIT32(a) silk_CHECK_FIT32_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_CHECK_FIT32_( opus_int64 a, char *file, int line ){ + opus_int32 ret; + ret = (opus_int32)a; + if ( (opus_int64)ret != a ) + { + fprintf (stderr, "silk_CHECK_FIT32(%lld) in %s: line %d\n", (long long)a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return( ret ); +} + +/* no checking for silk_NSHIFT_MUL_32_32 + no checking for silk_NSHIFT_MUL_16_16 + no checking needed for silk_min + no checking needed for silk_max + no checking needed for silk_sign +*/ + +#endif +#endif /* MACRO_DEBUG_H */ diff --git a/port/third_party/opus/silk/NLSF2A.c b/port/third_party/opus/silk/NLSF2A.c new file mode 100644 index 00000000..d5b77306 --- /dev/null +++ b/port/third_party/opus/silk/NLSF2A.c @@ -0,0 +1,141 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* conversion between prediction filter coefficients and LSFs */ +/* order should be even */ +/* a piecewise linear approximation maps LSF <-> cos(LSF) */ +/* therefore the result is not accurate LSFs, but the two */ +/* functions are accurate inverses of each other */ + +#include "SigProc_FIX.h" +#include "tables.h" + +#define QA 16 + +/* helper function for NLSF2A(..) */ +static OPUS_INLINE void silk_NLSF2A_find_poly( + opus_int32 *out, /* O intermediate polynomial, QA [dd+1] */ + const opus_int32 *cLSF, /* I vector of interleaved 2*cos(LSFs), QA [d] */ + opus_int dd /* I polynomial order (= 1/2 * filter order) */ +) +{ + opus_int k, n; + opus_int32 ftmp; + + out[0] = silk_LSHIFT( 1, QA ); + out[1] = -cLSF[0]; + for( k = 1; k < dd; k++ ) { + ftmp = cLSF[2*k]; /* QA*/ + out[k+1] = silk_LSHIFT( out[k-1], 1 ) - (opus_int32)silk_RSHIFT_ROUND64( silk_SMULL( ftmp, out[k] ), QA ); + for( n = k; n > 1; n-- ) { + out[n] += out[n-2] - (opus_int32)silk_RSHIFT_ROUND64( silk_SMULL( ftmp, out[n-1] ), QA ); + } + out[1] -= ftmp; + } +} + +/* compute whitening filter coefficients from normalized line spectral frequencies */ +void silk_NLSF2A( + opus_int16 *a_Q12, /* O monic whitening filter coefficients in Q12, [ d ] */ + const opus_int16 *NLSF, /* I normalized line spectral frequencies in Q15, [ d ] */ + const opus_int d, /* I filter order (should be even) */ + int arch /* I Run-time architecture */ +) +{ + /* This ordering was found to maximize quality. It improves numerical accuracy of + silk_NLSF2A_find_poly() compared to "standard" ordering. */ + static const unsigned char ordering16[16] = { + 0, 15, 8, 7, 4, 11, 12, 3, 2, 13, 10, 5, 6, 9, 14, 1 + }; + static const unsigned char ordering10[10] = { + 0, 9, 6, 3, 4, 5, 8, 1, 2, 7 + }; + const unsigned char *ordering; + opus_int k, i, dd; + opus_int32 cos_LSF_QA[ SILK_MAX_ORDER_LPC ]; + opus_int32 P[ SILK_MAX_ORDER_LPC / 2 + 1 ], Q[ SILK_MAX_ORDER_LPC / 2 + 1 ]; + opus_int32 Ptmp, Qtmp, f_int, f_frac, cos_val, delta; + opus_int32 a32_QA1[ SILK_MAX_ORDER_LPC ]; + + silk_assert( LSF_COS_TAB_SZ_FIX == 128 ); + celt_assert( d==10 || d==16 ); + + /* convert LSFs to 2*cos(LSF), using piecewise linear curve from table */ + ordering = d == 16 ? ordering16 : ordering10; + for( k = 0; k < d; k++ ) { + silk_assert( NLSF[k] >= 0 ); + + /* f_int on a scale 0-127 (rounded down) */ + f_int = silk_RSHIFT( NLSF[k], 15 - 7 ); + + /* f_frac, range: 0..255 */ + f_frac = NLSF[k] - silk_LSHIFT( f_int, 15 - 7 ); + + silk_assert(f_int >= 0); + silk_assert(f_int < LSF_COS_TAB_SZ_FIX ); + + /* Read start and end value from table */ + cos_val = silk_LSFCosTab_FIX_Q12[ f_int ]; /* Q12 */ + delta = silk_LSFCosTab_FIX_Q12[ f_int + 1 ] - cos_val; /* Q12, with a range of 0..200 */ + + /* Linear interpolation */ + cos_LSF_QA[ordering[k]] = silk_RSHIFT_ROUND( silk_LSHIFT( cos_val, 8 ) + silk_MUL( delta, f_frac ), 20 - QA ); /* QA */ + } + + dd = silk_RSHIFT( d, 1 ); + + /* generate even and odd polynomials using convolution */ + silk_NLSF2A_find_poly( P, &cos_LSF_QA[ 0 ], dd ); + silk_NLSF2A_find_poly( Q, &cos_LSF_QA[ 1 ], dd ); + + /* convert even and odd polynomials to opus_int32 Q12 filter coefs */ + for( k = 0; k < dd; k++ ) { + Ptmp = P[ k+1 ] + P[ k ]; + Qtmp = Q[ k+1 ] - Q[ k ]; + + /* the Ptmp and Qtmp values at this stage need to fit in int32 */ + a32_QA1[ k ] = -Qtmp - Ptmp; /* QA+1 */ + a32_QA1[ d-k-1 ] = Qtmp - Ptmp; /* QA+1 */ + } + + /* Convert int32 coefficients to Q12 int16 coefs */ + silk_LPC_fit( a_Q12, a32_QA1, 12, QA + 1, d ); + + for( i = 0; silk_LPC_inverse_pred_gain( a_Q12, d, arch ) == 0 && i < MAX_LPC_STABILIZE_ITERATIONS; i++ ) { + /* Prediction coefficients are (too close to) unstable; apply bandwidth expansion */ + /* on the unscaled coefficients, convert to Q12 and measure again */ + silk_bwexpander_32( a32_QA1, d, 65536 - silk_LSHIFT( 2, i ) ); + for( k = 0; k < d; k++ ) { + a_Q12[ k ] = (opus_int16)silk_RSHIFT_ROUND( a32_QA1[ k ], QA + 1 - 12 ); /* QA+1 -> Q12 */ + } + } +} + diff --git a/port/third_party/opus/silk/NLSF_VQ.c b/port/third_party/opus/silk/NLSF_VQ.c new file mode 100644 index 00000000..b83182a7 --- /dev/null +++ b/port/third_party/opus/silk/NLSF_VQ.c @@ -0,0 +1,76 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Compute quantization errors for an LPC_order element input vector for a VQ codebook */ +void silk_NLSF_VQ( + opus_int32 err_Q24[], /* O Quantization errors [K] */ + const opus_int16 in_Q15[], /* I Input vectors to be quantized [LPC_order] */ + const opus_uint8 pCB_Q8[], /* I Codebook vectors [K*LPC_order] */ + const opus_int16 pWght_Q9[], /* I Codebook weights [K*LPC_order] */ + const opus_int K, /* I Number of codebook vectors */ + const opus_int LPC_order /* I Number of LPCs */ +) +{ + opus_int i, m; + opus_int32 diff_Q15, diffw_Q24, sum_error_Q24, pred_Q24; + const opus_int16 *w_Q9_ptr; + const opus_uint8 *cb_Q8_ptr; + + celt_assert( ( LPC_order & 1 ) == 0 ); + + /* Loop over codebook */ + cb_Q8_ptr = pCB_Q8; + w_Q9_ptr = pWght_Q9; + for( i = 0; i < K; i++ ) { + sum_error_Q24 = 0; + pred_Q24 = 0; + for( m = LPC_order-2; m >= 0; m -= 2 ) { + /* Compute weighted absolute predictive quantization error for index m + 1 */ + diff_Q15 = silk_SUB_LSHIFT32( in_Q15[ m + 1 ], (opus_int32)cb_Q8_ptr[ m + 1 ], 7 ); /* range: [ -32767 : 32767 ]*/ + diffw_Q24 = silk_SMULBB( diff_Q15, w_Q9_ptr[ m + 1 ] ); + sum_error_Q24 = silk_ADD32( sum_error_Q24, silk_abs( silk_SUB_RSHIFT32( diffw_Q24, pred_Q24, 1 ) ) ); + pred_Q24 = diffw_Q24; + + /* Compute weighted absolute predictive quantization error for index m */ + diff_Q15 = silk_SUB_LSHIFT32( in_Q15[ m ], (opus_int32)cb_Q8_ptr[ m ], 7 ); /* range: [ -32767 : 32767 ]*/ + diffw_Q24 = silk_SMULBB( diff_Q15, w_Q9_ptr[ m ] ); + sum_error_Q24 = silk_ADD32( sum_error_Q24, silk_abs( silk_SUB_RSHIFT32( diffw_Q24, pred_Q24, 1 ) ) ); + pred_Q24 = diffw_Q24; + + silk_assert( sum_error_Q24 >= 0 ); + } + err_Q24[ i ] = sum_error_Q24; + cb_Q8_ptr += LPC_order; + w_Q9_ptr += LPC_order; + } +} diff --git a/port/third_party/opus/silk/NLSF_VQ_weights_laroia.c b/port/third_party/opus/silk/NLSF_VQ_weights_laroia.c new file mode 100644 index 00000000..9873bcde --- /dev/null +++ b/port/third_party/opus/silk/NLSF_VQ_weights_laroia.c @@ -0,0 +1,80 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "define.h" +#include "SigProc_FIX.h" + +/* +R. Laroia, N. Phamdo and N. Farvardin, "Robust and Efficient Quantization of Speech LSP +Parameters Using Structured Vector Quantization", Proc. IEEE Int. Conf. Acoust., Speech, +Signal Processing, pp. 641-644, 1991. +*/ + +/* Laroia low complexity NLSF weights */ +void silk_NLSF_VQ_weights_laroia( + opus_int16 *pNLSFW_Q_OUT, /* O Pointer to input vector weights [D] */ + const opus_int16 *pNLSF_Q15, /* I Pointer to input vector [D] */ + const opus_int D /* I Input vector dimension (even) */ +) +{ + opus_int k; + opus_int32 tmp1_int, tmp2_int; + + celt_assert( D > 0 ); + celt_assert( ( D & 1 ) == 0 ); + + /* First value */ + tmp1_int = silk_max_int( pNLSF_Q15[ 0 ], 1 ); + tmp1_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp1_int ); + tmp2_int = silk_max_int( pNLSF_Q15[ 1 ] - pNLSF_Q15[ 0 ], 1 ); + tmp2_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp2_int ); + pNLSFW_Q_OUT[ 0 ] = (opus_int16)silk_min_int( tmp1_int + tmp2_int, silk_int16_MAX ); + silk_assert( pNLSFW_Q_OUT[ 0 ] > 0 ); + + /* Main loop */ + for( k = 1; k < D - 1; k += 2 ) { + tmp1_int = silk_max_int( pNLSF_Q15[ k + 1 ] - pNLSF_Q15[ k ], 1 ); + tmp1_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp1_int ); + pNLSFW_Q_OUT[ k ] = (opus_int16)silk_min_int( tmp1_int + tmp2_int, silk_int16_MAX ); + silk_assert( pNLSFW_Q_OUT[ k ] > 0 ); + + tmp2_int = silk_max_int( pNLSF_Q15[ k + 2 ] - pNLSF_Q15[ k + 1 ], 1 ); + tmp2_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp2_int ); + pNLSFW_Q_OUT[ k + 1 ] = (opus_int16)silk_min_int( tmp1_int + tmp2_int, silk_int16_MAX ); + silk_assert( pNLSFW_Q_OUT[ k + 1 ] > 0 ); + } + + /* Last value */ + tmp1_int = silk_max_int( ( 1 << 15 ) - pNLSF_Q15[ D - 1 ], 1 ); + tmp1_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp1_int ); + pNLSFW_Q_OUT[ D - 1 ] = (opus_int16)silk_min_int( tmp1_int + tmp2_int, silk_int16_MAX ); + silk_assert( pNLSFW_Q_OUT[ D - 1 ] > 0 ); +} diff --git a/port/third_party/opus/silk/NLSF_decode.c b/port/third_party/opus/silk/NLSF_decode.c new file mode 100644 index 00000000..eeb0ba8c --- /dev/null +++ b/port/third_party/opus/silk/NLSF_decode.c @@ -0,0 +1,93 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Predictive dequantizer for NLSF residuals */ +static OPUS_INLINE void silk_NLSF_residual_dequant( /* O Returns RD value in Q30 */ + opus_int16 x_Q10[], /* O Output [ order ] */ + const opus_int8 indices[], /* I Quantization indices [ order ] */ + const opus_uint8 pred_coef_Q8[], /* I Backward predictor coefs [ order ] */ + const opus_int quant_step_size_Q16, /* I Quantization step size */ + const opus_int16 order /* I Number of input values */ +) +{ + opus_int i, out_Q10, pred_Q10; + + out_Q10 = 0; + for( i = order-1; i >= 0; i-- ) { + pred_Q10 = silk_RSHIFT( silk_SMULBB( out_Q10, (opus_int16)pred_coef_Q8[ i ] ), 8 ); + out_Q10 = silk_LSHIFT( indices[ i ], 10 ); + if( out_Q10 > 0 ) { + out_Q10 = silk_SUB16( out_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else if( out_Q10 < 0 ) { + out_Q10 = silk_ADD16( out_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } + out_Q10 = silk_SMLAWB( pred_Q10, (opus_int32)out_Q10, quant_step_size_Q16 ); + x_Q10[ i ] = out_Q10; + } +} + + +/***********************/ +/* NLSF vector decoder */ +/***********************/ +void silk_NLSF_decode( + opus_int16 *pNLSF_Q15, /* O Quantized NLSF vector [ LPC_ORDER ] */ + opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ + const silk_NLSF_CB_struct *psNLSF_CB /* I Codebook object */ +) +{ + opus_int i; + opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; + opus_int16 ec_ix[ MAX_LPC_ORDER ]; + opus_int16 res_Q10[ MAX_LPC_ORDER ]; + opus_int32 NLSF_Q15_tmp; + const opus_uint8 *pCB_element; + const opus_int16 *pCB_Wght_Q9; + + /* Unpack entropy table indices and predictor for current CB1 index */ + silk_NLSF_unpack( ec_ix, pred_Q8, psNLSF_CB, NLSFIndices[ 0 ] ); + + /* Predictive residual dequantizer */ + silk_NLSF_residual_dequant( res_Q10, &NLSFIndices[ 1 ], pred_Q8, psNLSF_CB->quantStepSize_Q16, psNLSF_CB->order ); + + /* Apply inverse square-rooted weights to first stage and add to output */ + pCB_element = &psNLSF_CB->CB1_NLSF_Q8[ NLSFIndices[ 0 ] * psNLSF_CB->order ]; + pCB_Wght_Q9 = &psNLSF_CB->CB1_Wght_Q9[ NLSFIndices[ 0 ] * psNLSF_CB->order ]; + for( i = 0; i < psNLSF_CB->order; i++ ) { + NLSF_Q15_tmp = silk_ADD_LSHIFT32( silk_DIV32_16( silk_LSHIFT( (opus_int32)res_Q10[ i ], 14 ), pCB_Wght_Q9[ i ] ), (opus_int16)pCB_element[ i ], 7 ); + pNLSF_Q15[ i ] = (opus_int16)silk_LIMIT( NLSF_Q15_tmp, 0, 32767 ); + } + + /* NLSF stabilization */ + silk_NLSF_stabilize( pNLSF_Q15, psNLSF_CB->deltaMin_Q15, psNLSF_CB->order ); +} diff --git a/port/third_party/opus/silk/NLSF_del_dec_quant.c b/port/third_party/opus/silk/NLSF_del_dec_quant.c new file mode 100644 index 00000000..44a16acd --- /dev/null +++ b/port/third_party/opus/silk/NLSF_del_dec_quant.c @@ -0,0 +1,215 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Delayed-decision quantizer for NLSF residuals */ +opus_int32 silk_NLSF_del_dec_quant( /* O Returns RD value in Q25 */ + opus_int8 indices[], /* O Quantization indices [ order ] */ + const opus_int16 x_Q10[], /* I Input [ order ] */ + const opus_int16 w_Q5[], /* I Weights [ order ] */ + const opus_uint8 pred_coef_Q8[], /* I Backward predictor coefs [ order ] */ + const opus_int16 ec_ix[], /* I Indices to entropy coding tables [ order ] */ + const opus_uint8 ec_rates_Q5[], /* I Rates [] */ + const opus_int quant_step_size_Q16, /* I Quantization step size */ + const opus_int16 inv_quant_step_size_Q6, /* I Inverse quantization step size */ + const opus_int32 mu_Q20, /* I R/D tradeoff */ + const opus_int16 order /* I Number of input values */ +) +{ + opus_int i, j, nStates, ind_tmp, ind_min_max, ind_max_min, in_Q10, res_Q10; + opus_int pred_Q10, diff_Q10, rate0_Q5, rate1_Q5; + opus_int16 out0_Q10, out1_Q10; + opus_int32 RD_tmp_Q25, min_Q25, min_max_Q25, max_min_Q25; + opus_int ind_sort[ NLSF_QUANT_DEL_DEC_STATES ]; + opus_int8 ind[ NLSF_QUANT_DEL_DEC_STATES ][ MAX_LPC_ORDER ]; + opus_int16 prev_out_Q10[ 2 * NLSF_QUANT_DEL_DEC_STATES ]; + opus_int32 RD_Q25[ 2 * NLSF_QUANT_DEL_DEC_STATES ]; + opus_int32 RD_min_Q25[ NLSF_QUANT_DEL_DEC_STATES ]; + opus_int32 RD_max_Q25[ NLSF_QUANT_DEL_DEC_STATES ]; + const opus_uint8 *rates_Q5; + + opus_int out0_Q10_table[2 * NLSF_QUANT_MAX_AMPLITUDE_EXT]; + opus_int out1_Q10_table[2 * NLSF_QUANT_MAX_AMPLITUDE_EXT]; + + for (i = -NLSF_QUANT_MAX_AMPLITUDE_EXT; i <= NLSF_QUANT_MAX_AMPLITUDE_EXT-1; i++) + { + out0_Q10 = silk_LSHIFT( i, 10 ); + out1_Q10 = silk_ADD16( out0_Q10, 1024 ); + if( i > 0 ) { + out0_Q10 = silk_SUB16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + out1_Q10 = silk_SUB16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else if( i == 0 ) { + out1_Q10 = silk_SUB16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else if( i == -1 ) { + out0_Q10 = silk_ADD16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else { + out0_Q10 = silk_ADD16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + out1_Q10 = silk_ADD16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } + out0_Q10_table[ i + NLSF_QUANT_MAX_AMPLITUDE_EXT ] = silk_RSHIFT( silk_SMULBB( out0_Q10, quant_step_size_Q16 ), 16 ); + out1_Q10_table[ i + NLSF_QUANT_MAX_AMPLITUDE_EXT ] = silk_RSHIFT( silk_SMULBB( out1_Q10, quant_step_size_Q16 ), 16 ); + } + + silk_assert( (NLSF_QUANT_DEL_DEC_STATES & (NLSF_QUANT_DEL_DEC_STATES-1)) == 0 ); /* must be power of two */ + + nStates = 1; + RD_Q25[ 0 ] = 0; + prev_out_Q10[ 0 ] = 0; + for( i = order - 1; i >= 0; i-- ) { + rates_Q5 = &ec_rates_Q5[ ec_ix[ i ] ]; + in_Q10 = x_Q10[ i ]; + for( j = 0; j < nStates; j++ ) { + pred_Q10 = silk_RSHIFT( silk_SMULBB( (opus_int16)pred_coef_Q8[ i ], prev_out_Q10[ j ] ), 8 ); + res_Q10 = silk_SUB16( in_Q10, pred_Q10 ); + ind_tmp = silk_RSHIFT( silk_SMULBB( inv_quant_step_size_Q6, res_Q10 ), 16 ); + ind_tmp = silk_LIMIT( ind_tmp, -NLSF_QUANT_MAX_AMPLITUDE_EXT, NLSF_QUANT_MAX_AMPLITUDE_EXT-1 ); + ind[ j ][ i ] = (opus_int8)ind_tmp; + + /* compute outputs for ind_tmp and ind_tmp + 1 */ + out0_Q10 = out0_Q10_table[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE_EXT ]; + out1_Q10 = out1_Q10_table[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE_EXT ]; + + out0_Q10 = silk_ADD16( out0_Q10, pred_Q10 ); + out1_Q10 = silk_ADD16( out1_Q10, pred_Q10 ); + prev_out_Q10[ j ] = out0_Q10; + prev_out_Q10[ j + nStates ] = out1_Q10; + + /* compute RD for ind_tmp and ind_tmp + 1 */ + if( ind_tmp + 1 >= NLSF_QUANT_MAX_AMPLITUDE ) { + if( ind_tmp + 1 == NLSF_QUANT_MAX_AMPLITUDE ) { + rate0_Q5 = rates_Q5[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE ]; + rate1_Q5 = 280; + } else { + rate0_Q5 = silk_SMLABB( 280 - 43 * NLSF_QUANT_MAX_AMPLITUDE, 43, ind_tmp ); + rate1_Q5 = silk_ADD16( rate0_Q5, 43 ); + } + } else if( ind_tmp <= -NLSF_QUANT_MAX_AMPLITUDE ) { + if( ind_tmp == -NLSF_QUANT_MAX_AMPLITUDE ) { + rate0_Q5 = 280; + rate1_Q5 = rates_Q5[ ind_tmp + 1 + NLSF_QUANT_MAX_AMPLITUDE ]; + } else { + rate0_Q5 = silk_SMLABB( 280 - 43 * NLSF_QUANT_MAX_AMPLITUDE, -43, ind_tmp ); + rate1_Q5 = silk_SUB16( rate0_Q5, 43 ); + } + } else { + rate0_Q5 = rates_Q5[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE ]; + rate1_Q5 = rates_Q5[ ind_tmp + 1 + NLSF_QUANT_MAX_AMPLITUDE ]; + } + RD_tmp_Q25 = RD_Q25[ j ]; + diff_Q10 = silk_SUB16( in_Q10, out0_Q10 ); + RD_Q25[ j ] = silk_SMLABB( silk_MLA( RD_tmp_Q25, silk_SMULBB( diff_Q10, diff_Q10 ), w_Q5[ i ] ), mu_Q20, rate0_Q5 ); + diff_Q10 = silk_SUB16( in_Q10, out1_Q10 ); + RD_Q25[ j + nStates ] = silk_SMLABB( silk_MLA( RD_tmp_Q25, silk_SMULBB( diff_Q10, diff_Q10 ), w_Q5[ i ] ), mu_Q20, rate1_Q5 ); + } + + if( nStates <= NLSF_QUANT_DEL_DEC_STATES/2 ) { + /* double number of states and copy */ + for( j = 0; j < nStates; j++ ) { + ind[ j + nStates ][ i ] = ind[ j ][ i ] + 1; + } + nStates = silk_LSHIFT( nStates, 1 ); + for( j = nStates; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { + ind[ j ][ i ] = ind[ j - nStates ][ i ]; + } + } else { + /* sort lower and upper half of RD_Q25, pairwise */ + for( j = 0; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { + if( RD_Q25[ j ] > RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ] ) { + RD_max_Q25[ j ] = RD_Q25[ j ]; + RD_min_Q25[ j ] = RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ]; + RD_Q25[ j ] = RD_min_Q25[ j ]; + RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ] = RD_max_Q25[ j ]; + /* swap prev_out values */ + out0_Q10 = prev_out_Q10[ j ]; + prev_out_Q10[ j ] = prev_out_Q10[ j + NLSF_QUANT_DEL_DEC_STATES ]; + prev_out_Q10[ j + NLSF_QUANT_DEL_DEC_STATES ] = out0_Q10; + ind_sort[ j ] = j + NLSF_QUANT_DEL_DEC_STATES; + } else { + RD_min_Q25[ j ] = RD_Q25[ j ]; + RD_max_Q25[ j ] = RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ]; + ind_sort[ j ] = j; + } + } + /* compare the highest RD values of the winning half with the lowest one in the losing half, and copy if necessary */ + /* afterwards ind_sort[] will contain the indices of the NLSF_QUANT_DEL_DEC_STATES winning RD values */ + while( 1 ) { + min_max_Q25 = silk_int32_MAX; + max_min_Q25 = 0; + ind_min_max = 0; + ind_max_min = 0; + for( j = 0; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { + if( min_max_Q25 > RD_max_Q25[ j ] ) { + min_max_Q25 = RD_max_Q25[ j ]; + ind_min_max = j; + } + if( max_min_Q25 < RD_min_Q25[ j ] ) { + max_min_Q25 = RD_min_Q25[ j ]; + ind_max_min = j; + } + } + if( min_max_Q25 >= max_min_Q25 ) { + break; + } + /* copy ind_min_max to ind_max_min */ + ind_sort[ ind_max_min ] = ind_sort[ ind_min_max ] ^ NLSF_QUANT_DEL_DEC_STATES; + RD_Q25[ ind_max_min ] = RD_Q25[ ind_min_max + NLSF_QUANT_DEL_DEC_STATES ]; + prev_out_Q10[ ind_max_min ] = prev_out_Q10[ ind_min_max + NLSF_QUANT_DEL_DEC_STATES ]; + RD_min_Q25[ ind_max_min ] = 0; + RD_max_Q25[ ind_min_max ] = silk_int32_MAX; + silk_memcpy( ind[ ind_max_min ], ind[ ind_min_max ], MAX_LPC_ORDER * sizeof( opus_int8 ) ); + } + /* increment index if it comes from the upper half */ + for( j = 0; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { + ind[ j ][ i ] += silk_RSHIFT( ind_sort[ j ], NLSF_QUANT_DEL_DEC_STATES_LOG2 ); + } + } + } + + /* last sample: find winner, copy indices and return RD value */ + ind_tmp = 0; + min_Q25 = silk_int32_MAX; + for( j = 0; j < 2 * NLSF_QUANT_DEL_DEC_STATES; j++ ) { + if( min_Q25 > RD_Q25[ j ] ) { + min_Q25 = RD_Q25[ j ]; + ind_tmp = j; + } + } + for( j = 0; j < order; j++ ) { + indices[ j ] = ind[ ind_tmp & ( NLSF_QUANT_DEL_DEC_STATES - 1 ) ][ j ]; + silk_assert( indices[ j ] >= -NLSF_QUANT_MAX_AMPLITUDE_EXT ); + silk_assert( indices[ j ] <= NLSF_QUANT_MAX_AMPLITUDE_EXT ); + } + indices[ 0 ] += silk_RSHIFT( ind_tmp, NLSF_QUANT_DEL_DEC_STATES_LOG2 ); + silk_assert( indices[ 0 ] <= NLSF_QUANT_MAX_AMPLITUDE_EXT ); + silk_assert( min_Q25 >= 0 ); + return min_Q25; +} diff --git a/port/third_party/opus/silk/NLSF_encode.c b/port/third_party/opus/silk/NLSF_encode.c new file mode 100644 index 00000000..01ac7db7 --- /dev/null +++ b/port/third_party/opus/silk/NLSF_encode.c @@ -0,0 +1,124 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/***********************/ +/* NLSF vector encoder */ +/***********************/ +opus_int32 silk_NLSF_encode( /* O Returns RD value in Q25 */ + opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ + opus_int16 *pNLSF_Q15, /* I/O (Un)quantized NLSF vector [ LPC_ORDER ] */ + const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ + const opus_int16 *pW_Q2, /* I NLSF weight vector [ LPC_ORDER ] */ + const opus_int NLSF_mu_Q20, /* I Rate weight for the RD optimization */ + const opus_int nSurvivors, /* I Max survivors after first stage */ + const opus_int signalType /* I Signal type: 0/1/2 */ +) +{ + opus_int i, s, ind1, bestIndex, prob_Q8, bits_q7; + opus_int32 W_tmp_Q9, ret; + VARDECL( opus_int32, err_Q24 ); + VARDECL( opus_int32, RD_Q25 ); + VARDECL( opus_int, tempIndices1 ); + VARDECL( opus_int8, tempIndices2 ); + opus_int16 res_Q10[ MAX_LPC_ORDER ]; + opus_int16 NLSF_tmp_Q15[ MAX_LPC_ORDER ]; + opus_int16 W_adj_Q5[ MAX_LPC_ORDER ]; + opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; + opus_int16 ec_ix[ MAX_LPC_ORDER ]; + const opus_uint8 *pCB_element, *iCDF_ptr; + const opus_int16 *pCB_Wght_Q9; + SAVE_STACK; + + celt_assert( signalType >= 0 && signalType <= 2 ); + silk_assert( NLSF_mu_Q20 <= 32767 && NLSF_mu_Q20 >= 0 ); + + /* NLSF stabilization */ + silk_NLSF_stabilize( pNLSF_Q15, psNLSF_CB->deltaMin_Q15, psNLSF_CB->order ); + + /* First stage: VQ */ + ALLOC( err_Q24, psNLSF_CB->nVectors, opus_int32 ); + silk_NLSF_VQ( err_Q24, pNLSF_Q15, psNLSF_CB->CB1_NLSF_Q8, psNLSF_CB->CB1_Wght_Q9, psNLSF_CB->nVectors, psNLSF_CB->order ); + + /* Sort the quantization errors */ + ALLOC( tempIndices1, nSurvivors, opus_int ); + silk_insertion_sort_increasing( err_Q24, tempIndices1, psNLSF_CB->nVectors, nSurvivors ); + + ALLOC( RD_Q25, nSurvivors, opus_int32 ); + ALLOC( tempIndices2, nSurvivors * MAX_LPC_ORDER, opus_int8 ); + + /* Loop over survivors */ + for( s = 0; s < nSurvivors; s++ ) { + ind1 = tempIndices1[ s ]; + + /* Residual after first stage */ + pCB_element = &psNLSF_CB->CB1_NLSF_Q8[ ind1 * psNLSF_CB->order ]; + pCB_Wght_Q9 = &psNLSF_CB->CB1_Wght_Q9[ ind1 * psNLSF_CB->order ]; + for( i = 0; i < psNLSF_CB->order; i++ ) { + NLSF_tmp_Q15[ i ] = silk_LSHIFT16( (opus_int16)pCB_element[ i ], 7 ); + W_tmp_Q9 = pCB_Wght_Q9[ i ]; + res_Q10[ i ] = (opus_int16)silk_RSHIFT( silk_SMULBB( pNLSF_Q15[ i ] - NLSF_tmp_Q15[ i ], W_tmp_Q9 ), 14 ); + W_adj_Q5[ i ] = silk_DIV32_varQ( (opus_int32)pW_Q2[ i ], silk_SMULBB( W_tmp_Q9, W_tmp_Q9 ), 21 ); + } + + /* Unpack entropy table indices and predictor for current CB1 index */ + silk_NLSF_unpack( ec_ix, pred_Q8, psNLSF_CB, ind1 ); + + /* Trellis quantizer */ + RD_Q25[ s ] = silk_NLSF_del_dec_quant( &tempIndices2[ s * MAX_LPC_ORDER ], res_Q10, W_adj_Q5, pred_Q8, ec_ix, + psNLSF_CB->ec_Rates_Q5, psNLSF_CB->quantStepSize_Q16, psNLSF_CB->invQuantStepSize_Q6, NLSF_mu_Q20, psNLSF_CB->order ); + + /* Add rate for first stage */ + iCDF_ptr = &psNLSF_CB->CB1_iCDF[ ( signalType >> 1 ) * psNLSF_CB->nVectors ]; + if( ind1 == 0 ) { + prob_Q8 = 256 - iCDF_ptr[ ind1 ]; + } else { + prob_Q8 = iCDF_ptr[ ind1 - 1 ] - iCDF_ptr[ ind1 ]; + } + bits_q7 = ( 8 << 7 ) - silk_lin2log( prob_Q8 ); + RD_Q25[ s ] = silk_SMLABB( RD_Q25[ s ], bits_q7, silk_RSHIFT( NLSF_mu_Q20, 2 ) ); + } + + /* Find the lowest rate-distortion error */ + silk_insertion_sort_increasing( RD_Q25, &bestIndex, nSurvivors, 1 ); + + NLSFIndices[ 0 ] = (opus_int8)tempIndices1[ bestIndex ]; + silk_memcpy( &NLSFIndices[ 1 ], &tempIndices2[ bestIndex * MAX_LPC_ORDER ], psNLSF_CB->order * sizeof( opus_int8 ) ); + + /* Decode */ + silk_NLSF_decode( pNLSF_Q15, NLSFIndices, psNLSF_CB ); + + ret = RD_Q25[ 0 ]; + RESTORE_STACK; + return ret; +} diff --git a/port/third_party/opus/silk/NLSF_stabilize.c b/port/third_party/opus/silk/NLSF_stabilize.c new file mode 100644 index 00000000..8f3426b9 --- /dev/null +++ b/port/third_party/opus/silk/NLSF_stabilize.c @@ -0,0 +1,142 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* NLSF stabilizer: */ +/* */ +/* - Moves NLSFs further apart if they are too close */ +/* - Moves NLSFs away from borders if they are too close */ +/* - High effort to achieve a modification with minimum */ +/* Euclidean distance to input vector */ +/* - Output are sorted NLSF coefficients */ +/* */ + +#include "SigProc_FIX.h" + +/* Constant Definitions */ +#define MAX_LOOPS 20 + +/* NLSF stabilizer, for a single input data vector */ +void silk_NLSF_stabilize( + opus_int16 *NLSF_Q15, /* I/O Unstable/stabilized normalized LSF vector in Q15 [L] */ + const opus_int16 *NDeltaMin_Q15, /* I Min distance vector, NDeltaMin_Q15[L] must be >= 1 [L+1] */ + const opus_int L /* I Number of NLSF parameters in the input vector */ +) +{ + opus_int i, I=0, k, loops; + opus_int16 center_freq_Q15; + opus_int32 diff_Q15, min_diff_Q15, min_center_Q15, max_center_Q15; + + /* This is necessary to ensure an output within range of a opus_int16 */ + silk_assert( NDeltaMin_Q15[L] >= 1 ); + + for( loops = 0; loops < MAX_LOOPS; loops++ ) { + /**************************/ + /* Find smallest distance */ + /**************************/ + /* First element */ + min_diff_Q15 = NLSF_Q15[0] - NDeltaMin_Q15[0]; + I = 0; + /* Middle elements */ + for( i = 1; i <= L-1; i++ ) { + diff_Q15 = NLSF_Q15[i] - ( NLSF_Q15[i-1] + NDeltaMin_Q15[i] ); + if( diff_Q15 < min_diff_Q15 ) { + min_diff_Q15 = diff_Q15; + I = i; + } + } + /* Last element */ + diff_Q15 = ( 1 << 15 ) - ( NLSF_Q15[L-1] + NDeltaMin_Q15[L] ); + if( diff_Q15 < min_diff_Q15 ) { + min_diff_Q15 = diff_Q15; + I = L; + } + + /***************************************************/ + /* Now check if the smallest distance non-negative */ + /***************************************************/ + if( min_diff_Q15 >= 0 ) { + return; + } + + if( I == 0 ) { + /* Move away from lower limit */ + NLSF_Q15[0] = NDeltaMin_Q15[0]; + + } else if( I == L) { + /* Move away from higher limit */ + NLSF_Q15[L-1] = ( 1 << 15 ) - NDeltaMin_Q15[L]; + + } else { + /* Find the lower extreme for the location of the current center frequency */ + min_center_Q15 = 0; + for( k = 0; k < I; k++ ) { + min_center_Q15 += NDeltaMin_Q15[k]; + } + min_center_Q15 += silk_RSHIFT( NDeltaMin_Q15[I], 1 ); + + /* Find the upper extreme for the location of the current center frequency */ + max_center_Q15 = 1 << 15; + for( k = L; k > I; k-- ) { + max_center_Q15 -= NDeltaMin_Q15[k]; + } + max_center_Q15 -= silk_RSHIFT( NDeltaMin_Q15[I], 1 ); + + /* Move apart, sorted by value, keeping the same center frequency */ + center_freq_Q15 = (opus_int16)silk_LIMIT_32( silk_RSHIFT_ROUND( (opus_int32)NLSF_Q15[I-1] + (opus_int32)NLSF_Q15[I], 1 ), + min_center_Q15, max_center_Q15 ); + NLSF_Q15[I-1] = center_freq_Q15 - silk_RSHIFT( NDeltaMin_Q15[I], 1 ); + NLSF_Q15[I] = NLSF_Q15[I-1] + NDeltaMin_Q15[I]; + } + } + + /* Safe and simple fall back method, which is less ideal than the above */ + if( loops == MAX_LOOPS ) + { + /* Insertion sort (fast for already almost sorted arrays): */ + /* Best case: O(n) for an already sorted array */ + /* Worst case: O(n^2) for an inversely sorted array */ + silk_insertion_sort_increasing_all_values_int16( &NLSF_Q15[0], L ); + + /* First NLSF should be no less than NDeltaMin[0] */ + NLSF_Q15[0] = silk_max_int( NLSF_Q15[0], NDeltaMin_Q15[0] ); + + /* Keep delta_min distance between the NLSFs */ + for( i = 1; i < L; i++ ) + NLSF_Q15[i] = silk_max_int( NLSF_Q15[i], silk_ADD_SAT16( NLSF_Q15[i-1], NDeltaMin_Q15[i] ) ); + + /* Last NLSF should be no higher than 1 - NDeltaMin[L] */ + NLSF_Q15[L-1] = silk_min_int( NLSF_Q15[L-1], (1<<15) - NDeltaMin_Q15[L] ); + + /* Keep NDeltaMin distance between the NLSFs */ + for( i = L-2; i >= 0; i-- ) + NLSF_Q15[i] = silk_min_int( NLSF_Q15[i], NLSF_Q15[i+1] - NDeltaMin_Q15[i+1] ); + } +} diff --git a/port/third_party/opus/silk/NLSF_unpack.c b/port/third_party/opus/silk/NLSF_unpack.c new file mode 100644 index 00000000..17bd23f7 --- /dev/null +++ b/port/third_party/opus/silk/NLSF_unpack.c @@ -0,0 +1,55 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Unpack predictor values and indices for entropy coding tables */ +void silk_NLSF_unpack( + opus_int16 ec_ix[], /* O Indices to entropy tables [ LPC_ORDER ] */ + opus_uint8 pred_Q8[], /* O LSF predictor [ LPC_ORDER ] */ + const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ + const opus_int CB1_index /* I Index of vector in first LSF codebook */ +) +{ + opus_int i; + opus_uint8 entry; + const opus_uint8 *ec_sel_ptr; + + ec_sel_ptr = &psNLSF_CB->ec_sel[ CB1_index * psNLSF_CB->order / 2 ]; + for( i = 0; i < psNLSF_CB->order; i += 2 ) { + entry = *ec_sel_ptr++; + ec_ix [ i ] = silk_SMULBB( silk_RSHIFT( entry, 1 ) & 7, 2 * NLSF_QUANT_MAX_AMPLITUDE + 1 ); + pred_Q8[ i ] = psNLSF_CB->pred_Q8[ i + ( entry & 1 ) * ( psNLSF_CB->order - 1 ) ]; + ec_ix [ i + 1 ] = silk_SMULBB( silk_RSHIFT( entry, 5 ) & 7, 2 * NLSF_QUANT_MAX_AMPLITUDE + 1 ); + pred_Q8[ i + 1 ] = psNLSF_CB->pred_Q8[ i + ( silk_RSHIFT( entry, 4 ) & 1 ) * ( psNLSF_CB->order - 1 ) + 1 ]; + } +} + diff --git a/port/third_party/opus/silk/NSQ.c b/port/third_party/opus/silk/NSQ.c new file mode 100644 index 00000000..1caa829b --- /dev/null +++ b/port/third_party/opus/silk/NSQ.c @@ -0,0 +1,437 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" +#include "NSQ.h" + + +static OPUS_INLINE void silk_nsq_scale_states( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + const opus_int16 x16[], /* I input */ + opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ + const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I subframe number */ + const opus_int LTP_scale_Q14, /* I */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type /* I Signal type */ +); + +#if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE void silk_noise_shape_quantizer( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int shapingLPCOrder, /* I Noise shaping AR filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + int arch /* I Architecture */ +); +#endif + +void silk_NSQ_c +( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ + opus_int k, lag, start_idx, LSF_interpolation_flag; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + SAVE_STACK; + + NSQ->rand_seed = psIndices->Seed; + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + /* Set up pointers to start of sub frame */ + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ (( k >> 1 ) | ( 1 - LSF_interpolation_flag )) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + celt_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->rewhite_flag = 1; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + } + } + + silk_nsq_scale_states( psEncC, NSQ, x16, x_sc_Q10, sLTP, sLTP_Q15, k, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType ); + + silk_noise_shape_quantizer( NSQ, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, A_Q12, B_Q14, + AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], Gains_Q16[ k ], Lambda_Q10, + offset_Q10, psEncC->subfr_length, psEncC->shapingLPCOrder, psEncC->predictLPCOrder, psEncC->arch ); + + x16 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Update lagPrev for next frame */ + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech and noise shaping signals */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + RESTORE_STACK; +} + +/******************************/ +/* silk_noise_shape_quantizer */ +/******************************/ + +#if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE +#endif +void silk_noise_shape_quantizer( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int shapingLPCOrder, /* I Noise shaping AR filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + int arch /* I Architecture */ +) +{ + opus_int i; + opus_int32 LTP_pred_Q13, LPC_pred_Q10, n_AR_Q12, n_LTP_Q13; + opus_int32 n_LF_Q12, r_Q10, rr_Q10, q1_Q0, q1_Q10, q2_Q10, rd1_Q20, rd2_Q20; + opus_int32 exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *psLPC_Q14, *shp_lag_ptr, *pred_lag_ptr; +#ifdef silk_short_prediction_create_arch_coef + opus_int32 a_Q12_arch[MAX_LPC_ORDER]; +#endif + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + + /* Set up short term AR state */ + psLPC_Q14 = &NSQ->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 ]; + +#ifdef silk_short_prediction_create_arch_coef + silk_short_prediction_create_arch_coef(a_Q12_arch, a_Q12, predictLPCOrder); +#endif + + for( i = 0; i < length; i++ ) { + /* Generate dither */ + NSQ->rand_seed = silk_RAND( NSQ->rand_seed ); + + /* Short-term prediction */ + LPC_pred_Q10 = silk_noise_shape_quantizer_short_prediction(psLPC_Q14, a_Q12, a_Q12_arch, predictLPCOrder, arch); + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q13 = 2; + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ 0 ], b_Q14[ 0 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -1 ], b_Q14[ 1 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -2 ], b_Q14[ 2 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -3 ], b_Q14[ 3 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + pred_lag_ptr++; + } else { + LTP_pred_Q13 = 0; + } + + /* Noise shape feedback */ + celt_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ + n_AR_Q12 = silk_NSQ_noise_shape_feedback_loop(&NSQ->sDiff_shp_Q14, NSQ->sAR2_Q14, AR_shp_Q13, shapingLPCOrder, arch); + + n_AR_Q12 = silk_SMLAWB( n_AR_Q12, NSQ->sLF_AR_shp_Q14, Tilt_Q14 ); + + n_LF_Q12 = silk_SMULWB( NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - 1 ], LF_shp_Q14 ); + n_LF_Q12 = silk_SMLAWT( n_LF_Q12, NSQ->sLF_AR_shp_Q14, LF_shp_Q14 ); + + celt_assert( lag > 0 || signalType != TYPE_VOICED ); + + /* Combine prediction and noise shaping signals */ + tmp1 = silk_SUB32_ovflw( silk_LSHIFT32( LPC_pred_Q10, 2 ), n_AR_Q12 ); /* Q12 */ + tmp1 = silk_SUB32_ovflw( tmp1, n_LF_Q12 ); /* Q12 */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q13 = silk_SMULWB( silk_ADD_SAT32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q13 = silk_SMLAWT( n_LTP_Q13, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q13 = silk_LSHIFT( n_LTP_Q13, 1 ); + shp_lag_ptr++; + + tmp2 = silk_SUB32( LTP_pred_Q13, n_LTP_Q13 ); /* Q13 */ + tmp1 = silk_ADD32_ovflw( tmp2, silk_LSHIFT32( tmp1, 1 ) ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 3 ); /* Q10 */ + } else { + tmp1 = silk_RSHIFT_ROUND( tmp1, 2 ); /* Q10 */ + } + + r_Q10 = silk_SUB32( x_sc_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Flip sign depending on dither */ + if( NSQ->rand_seed < 0 ) { + r_Q10 = -r_Q10; + } + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if (Lambda_Q10 > 2048) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + int rdo_offset = Lambda_Q10/2 - 512; + if (q1_Q10 > rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 - rdo_offset, 10 ); + } else if (q1_Q10 < -rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 + rdo_offset, 10 ); + } else if (q1_Q10 < 0) { + q1_Q0 = -1; + } else { + q1_Q0 = 0; + } + } + if( q1_Q0 > 0 ) { + q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q20 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q20 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == 0 ) { + q1_Q10 = offset_Q10; + q2_Q10 = silk_ADD32( q1_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q20 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q20 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == -1 ) { + q2_Q10 = offset_Q10; + q1_Q10 = silk_SUB32( q2_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q20 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q20 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else { /* Q1_Q0 < -1 */ + q1_Q10 = silk_ADD32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q20 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q20 = silk_SMULBB( -q2_Q10, Lambda_Q10 ); + } + rr_Q10 = silk_SUB32( r_Q10, q1_Q10 ); + rd1_Q20 = silk_SMLABB( rd1_Q20, rr_Q10, rr_Q10 ); + rr_Q10 = silk_SUB32( r_Q10, q2_Q10 ); + rd2_Q20 = silk_SMLABB( rd2_Q20, rr_Q10, rr_Q10 ); + + if( rd2_Q20 < rd1_Q20 ) { + q1_Q10 = q2_Q10; + } + + pulses[ i ] = (opus_int8)silk_RSHIFT_ROUND( q1_Q10, 10 ); + + /* Excitation */ + exc_Q14 = silk_LSHIFT( q1_Q10, 4 ); + if ( NSQ->rand_seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD_LSHIFT32( exc_Q14, LTP_pred_Q13, 1 ); + xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, silk_LSHIFT32( LPC_pred_Q10, 4 ) ); + + /* Scale XQ back to normal level before saving */ + xq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( xq_Q14, Gain_Q10 ), 8 ) ); + + /* Update states */ + psLPC_Q14++; + *psLPC_Q14 = xq_Q14; + NSQ->sDiff_shp_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_sc_Q10[ i ], 4 ); + sLF_AR_shp_Q14 = silk_SUB32_ovflw( NSQ->sDiff_shp_Q14, silk_LSHIFT32( n_AR_Q12, 2 ) ); + NSQ->sLF_AR_shp_Q14 = sLF_AR_shp_Q14; + + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx ] = silk_SUB32_ovflw(sLF_AR_shp_Q14, silk_LSHIFT32(n_LF_Q12, 2)); + sLTP_Q15[ NSQ->sLTP_buf_idx ] = silk_LSHIFT( LPC_exc_Q14, 1 ); + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Make dither dependent on quantized signal */ + NSQ->rand_seed = silk_ADD32_ovflw( NSQ->rand_seed, pulses[ i ] ); + } + + /* Update LPC synth buffer */ + silk_memcpy( NSQ->sLPC_Q14, &NSQ->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); +} + +static OPUS_INLINE void silk_nsq_scale_states( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + const opus_int16 x16[], /* I input */ + opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ + const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I subframe number */ + const opus_int LTP_scale_Q14, /* I */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type /* I Signal type */ +) +{ + opus_int i, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + silk_assert( inv_gain_Q31 != 0 ); + + /* Scale input */ + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); + for( i = 0; i < psEncC->subfr_length; i++ ) { + x_sc_Q10[ i ] = silk_SMULWW( x16[ i ], inv_gain_Q26 ); + } + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + silk_assert( i < MAX_FRAME_LENGTH ); + sLTP_Q15[ i ] = silk_SMULWB( inv_gain_Q31, sLTP[ i ] ); + } + } + + /* Adjust for changing gain */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + + /* Scale long-term shaping state */ + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + NSQ->sLF_AR_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sLF_AR_shp_Q14 ); + NSQ->sDiff_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sDiff_shp_Q14 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + NSQ->sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLPC_Q14[ i ] ); + } + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + NSQ->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sAR2_Q14[ i ] ); + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + } +} diff --git a/port/third_party/opus/silk/NSQ.h b/port/third_party/opus/silk/NSQ.h new file mode 100644 index 00000000..971832f6 --- /dev/null +++ b/port/third_party/opus/silk/NSQ.h @@ -0,0 +1,101 @@ +/*********************************************************************** +Copyright (c) 2014 Vidyo. +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ +#ifndef SILK_NSQ_H +#define SILK_NSQ_H + +#include "SigProc_FIX.h" + +#undef silk_short_prediction_create_arch_coef + +static OPUS_INLINE opus_int32 silk_noise_shape_quantizer_short_prediction_c(const opus_int32 *buf32, const opus_int16 *coef16, opus_int order) +{ + opus_int32 out; + silk_assert( order == 10 || order == 16 ); + + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + out = silk_RSHIFT( order, 1 ); + out = silk_SMLAWB( out, buf32[ 0 ], coef16[ 0 ] ); + out = silk_SMLAWB( out, buf32[ -1 ], coef16[ 1 ] ); + out = silk_SMLAWB( out, buf32[ -2 ], coef16[ 2 ] ); + out = silk_SMLAWB( out, buf32[ -3 ], coef16[ 3 ] ); + out = silk_SMLAWB( out, buf32[ -4 ], coef16[ 4 ] ); + out = silk_SMLAWB( out, buf32[ -5 ], coef16[ 5 ] ); + out = silk_SMLAWB( out, buf32[ -6 ], coef16[ 6 ] ); + out = silk_SMLAWB( out, buf32[ -7 ], coef16[ 7 ] ); + out = silk_SMLAWB( out, buf32[ -8 ], coef16[ 8 ] ); + out = silk_SMLAWB( out, buf32[ -9 ], coef16[ 9 ] ); + + if( order == 16 ) + { + out = silk_SMLAWB( out, buf32[ -10 ], coef16[ 10 ] ); + out = silk_SMLAWB( out, buf32[ -11 ], coef16[ 11 ] ); + out = silk_SMLAWB( out, buf32[ -12 ], coef16[ 12 ] ); + out = silk_SMLAWB( out, buf32[ -13 ], coef16[ 13 ] ); + out = silk_SMLAWB( out, buf32[ -14 ], coef16[ 14 ] ); + out = silk_SMLAWB( out, buf32[ -15 ], coef16[ 15 ] ); + } + return out; +} + +#define silk_noise_shape_quantizer_short_prediction(in, coef, coefRev, order, arch) ((void)arch,silk_noise_shape_quantizer_short_prediction_c(in, coef, order)) + +static OPUS_INLINE opus_int32 silk_NSQ_noise_shape_feedback_loop_c(const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, opus_int order) +{ + opus_int32 out; + opus_int32 tmp1, tmp2; + opus_int j; + + tmp2 = data0[0]; + tmp1 = data1[0]; + data1[0] = tmp2; + + out = silk_RSHIFT(order, 1); + out = silk_SMLAWB(out, tmp2, coef[0]); + + for (j = 2; j < order; j += 2) { + tmp2 = data1[j - 1]; + data1[j - 1] = tmp1; + out = silk_SMLAWB(out, tmp1, coef[j - 1]); + tmp1 = data1[j + 0]; + data1[j + 0] = tmp2; + out = silk_SMLAWB(out, tmp2, coef[j]); + } + data1[order - 1] = tmp1; + out = silk_SMLAWB(out, tmp1, coef[order - 1]); + /* Q11 -> Q12 */ + out = silk_LSHIFT32( out, 1 ); + return out; +} + +#define silk_NSQ_noise_shape_feedback_loop(data0, data1, coef, order, arch) ((void)arch,silk_NSQ_noise_shape_feedback_loop_c(data0, data1, coef, order)) + +#if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +#include "arm/NSQ_neon.h" +#endif + +#endif /* SILK_NSQ_H */ diff --git a/port/third_party/opus/silk/NSQ_del_dec.c b/port/third_party/opus/silk/NSQ_del_dec.c new file mode 100644 index 00000000..e8dadf15 --- /dev/null +++ b/port/third_party/opus/silk/NSQ_del_dec.c @@ -0,0 +1,733 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" +#include "NSQ.h" + + +typedef struct { + opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ]; + opus_int32 RandState[ DECISION_DELAY ]; + opus_int32 Q_Q10[ DECISION_DELAY ]; + opus_int32 Xq_Q14[ DECISION_DELAY ]; + opus_int32 Pred_Q15[ DECISION_DELAY ]; + opus_int32 Shape_Q14[ DECISION_DELAY ]; + opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 LF_AR_Q14; + opus_int32 Diff_Q14; + opus_int32 Seed; + opus_int32 SeedInit; + opus_int32 RD_Q10; +} NSQ_del_dec_struct; + +typedef struct { + opus_int32 Q_Q10; + opus_int32 RD_Q10; + opus_int32 xq_Q14; + opus_int32 LF_AR_Q14; + opus_int32 Diff_Q14; + opus_int32 sLTP_shp_Q14; + opus_int32 LPC_exc_Q14; +} NSQ_sample_struct; + +typedef NSQ_sample_struct NSQ_sample_pair[ 2 ]; + +#if defined(MIPSr1_ASM) +#include "mips/NSQ_del_dec_mipsr1.h" +#endif +static OPUS_INLINE void silk_nsq_del_dec_scale_states( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + opus_int nStatesDelayedDecision, /* I Number of del dec states */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +); + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay, /* I */ + int arch /* I */ +); + +void silk_NSQ_del_dec_c( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ + opus_int i, k, lag, start_idx, LSF_interpolation_flag, Winner_ind, subfr; + opus_int last_smple_idx, smpl_buf_idx, decisionDelay; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + opus_int32 RDmin_Q10, Gain_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + VARDECL( opus_int32, delayedGain_Q10 ); + VARDECL( NSQ_del_dec_struct, psDelDec ); + NSQ_del_dec_struct *psDD; + SAVE_STACK; + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + /* Initialize delayed decision states */ + ALLOC( psDelDec, psEncC->nStatesDelayedDecision, NSQ_del_dec_struct ); + silk_memset( psDelDec, 0, psEncC->nStatesDelayedDecision * sizeof( NSQ_del_dec_struct ) ); + for( k = 0; k < psEncC->nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psDD->Seed = ( k + psIndices->Seed ) & 3; + psDD->SeedInit = psDD->Seed; + psDD->RD_Q10 = 0; + psDD->LF_AR_Q14 = NSQ->sLF_AR_shp_Q14; + psDD->Diff_Q14 = NSQ->sDiff_shp_Q14; + psDD->Shape_Q14[ 0 ] = NSQ->sLTP_shp_Q14[ psEncC->ltp_mem_length - 1 ]; + silk_memcpy( psDD->sLPC_Q14, NSQ->sLPC_Q14, NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + silk_memcpy( psDD->sAR2_Q14, NSQ->sAR2_Q14, sizeof( NSQ->sAR2_Q14 ) ); + } + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + smpl_buf_idx = 0; /* index of oldest samples */ + + decisionDelay = silk_min_int( DECISION_DELAY, psEncC->subfr_length ); + + /* For voiced frames limit the decision delay to lower than the pitch lag */ + if( psIndices->signalType == TYPE_VOICED ) { + for( k = 0; k < psEncC->nb_subfr; k++ ) { + decisionDelay = silk_min_int( decisionDelay, pitchL[ k ] - LTP_ORDER / 2 - 1 ); + } + } else { + if( lag > 0 ) { + decisionDelay = silk_min_int( decisionDelay, lag - LTP_ORDER / 2 - 1 ); + } + } + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + ALLOC( delayedGain_Q10, DECISION_DELAY, opus_int32 ); + /* Set up pointers to start of sub frame */ + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + subfr = 0; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ ( ( k >> 1 ) | ( 1 - LSF_interpolation_flag ) ) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + if( k == 2 ) { + /* RESET DELAYED DECISIONS */ + /* Find winner */ + RDmin_Q10 = psDelDec[ 0 ].RD_Q10; + Winner_ind = 0; + for( i = 1; i < psEncC->nStatesDelayedDecision; i++ ) { + if( psDelDec[ i ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psDelDec[ i ].RD_Q10; + Winner_ind = i; + } + } + for( i = 0; i < psEncC->nStatesDelayedDecision; i++ ) { + if( i != Winner_ind ) { + psDelDec[ i ].RD_Q10 += ( silk_int32_MAX >> 4 ); + silk_assert( psDelDec[ i ].RD_Q10 >= 0 ); + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + last_smple_idx = smpl_buf_idx + decisionDelay; + for( i = 0; i < decisionDelay; i++ ) { + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gains_Q16[ 1 ] ), 14 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDD->Shape_Q14[ last_smple_idx ]; + } + + subfr = 0; + } + + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + celt_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + NSQ->rewhite_flag = 1; + } + } + + silk_nsq_del_dec_scale_states( psEncC, NSQ, psDelDec, x16, x_sc_Q10, sLTP, sLTP_Q15, k, + psEncC->nStatesDelayedDecision, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType, decisionDelay ); + + silk_noise_shape_quantizer_del_dec( NSQ, psDelDec, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, + delayedGain_Q10, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], + Gains_Q16[ k ], Lambda_Q10, offset_Q10, psEncC->subfr_length, subfr++, psEncC->shapingLPCOrder, + psEncC->predictLPCOrder, psEncC->warping_Q16, psEncC->nStatesDelayedDecision, &smpl_buf_idx, decisionDelay, psEncC->arch ); + + x16 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Find winner */ + RDmin_Q10 = psDelDec[ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < psEncC->nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psDelDec[ k ].RD_Q10; + Winner_ind = k; + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + psIndices->Seed = psDD->SeedInit; + last_smple_idx = smpl_buf_idx + decisionDelay; + Gain_Q10 = silk_RSHIFT32( Gains_Q16[ psEncC->nb_subfr - 1 ], 6 ); + for( i = 0; i < decisionDelay; i++ ) { + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gain_Q10 ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDD->Shape_Q14[ last_smple_idx ]; + } + silk_memcpy( NSQ->sLPC_Q14, &psDD->sLPC_Q14[ psEncC->subfr_length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + silk_memcpy( NSQ->sAR2_Q14, psDD->sAR2_Q14, sizeof( psDD->sAR2_Q14 ) ); + + /* Update states */ + NSQ->sLF_AR_shp_Q14 = psDD->LF_AR_Q14; + NSQ->sDiff_shp_Q14 = psDD->Diff_Q14; + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech signal */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + RESTORE_STACK; +} + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +#ifndef OVERRIDE_silk_noise_shape_quantizer_del_dec +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay, /* I */ + int arch /* I */ +) +{ + opus_int i, j, k, Winner_ind, RDmin_ind, RDmax_ind, last_smple_idx; + opus_int32 Winner_rand_state; + opus_int32 LTP_pred_Q14, LPC_pred_Q14, n_AR_Q14, n_LTP_Q14; + opus_int32 n_LF_Q14, r_Q10, rr_Q10, rd1_Q10, rd2_Q10, RDmin_Q10, RDmax_Q10; + opus_int32 q1_Q0, q1_Q10, q2_Q10, exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *pred_lag_ptr, *shp_lag_ptr, *psLPC_Q14; +#ifdef silk_short_prediction_create_arch_coef + opus_int32 a_Q12_arch[MAX_LPC_ORDER]; +#endif + + VARDECL( NSQ_sample_pair, psSampleState ); + NSQ_del_dec_struct *psDD; + NSQ_sample_struct *psSS; + SAVE_STACK; + + celt_assert( nStatesDelayedDecision > 0 ); + ALLOC( psSampleState, nStatesDelayedDecision, NSQ_sample_pair ); + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + +#ifdef silk_short_prediction_create_arch_coef + silk_short_prediction_create_arch_coef(a_Q12_arch, a_Q12, predictLPCOrder); +#endif + + for( i = 0; i < length; i++ ) { + /* Perform common calculations used in all states */ + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q14 = 2; + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ 0 ], b_Q14[ 0 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -1 ], b_Q14[ 1 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -2 ], b_Q14[ 2 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -3 ], b_Q14[ 3 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + LTP_pred_Q14 = silk_LSHIFT( LTP_pred_Q14, 1 ); /* Q13 -> Q14 */ + pred_lag_ptr++; + } else { + LTP_pred_Q14 = 0; + } + + /* Long-term shaping */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q14 = silk_SMULWB( silk_ADD_SAT32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SMLAWT( n_LTP_Q14, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SUB_LSHIFT32( LTP_pred_Q14, n_LTP_Q14, 2 ); /* Q12 -> Q14 */ + shp_lag_ptr++; + } else { + n_LTP_Q14 = 0; + } + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + /* Delayed decision state */ + psDD = &psDelDec[ k ]; + + /* Sample state */ + psSS = psSampleState[ k ]; + + /* Generate dither */ + psDD->Seed = silk_RAND( psDD->Seed ); + + /* Pointer used in short term prediction and shaping */ + psLPC_Q14 = &psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 + i ]; + /* Short-term prediction */ + LPC_pred_Q14 = silk_noise_shape_quantizer_short_prediction(psLPC_Q14, a_Q12, a_Q12_arch, predictLPCOrder, arch); + LPC_pred_Q14 = silk_LSHIFT( LPC_pred_Q14, 4 ); /* Q10 -> Q14 */ + + /* Noise shape feedback */ + celt_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ + /* Output of lowpass section */ + tmp2 = silk_SMLAWB( psDD->Diff_Q14, psDD->sAR2_Q14[ 0 ], warping_Q16 ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ 1 ], tmp2), warping_Q16 ); + psDD->sAR2_Q14[ 0 ] = tmp2; + n_AR_Q14 = silk_RSHIFT( shapingLPCOrder, 1 ); + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ 0 ] ); + /* Loop over allpass sections */ + for( j = 2; j < shapingLPCOrder; j += 2 ) { + /* Output of allpass section */ + tmp2 = silk_SMLAWB( psDD->sAR2_Q14[ j - 1 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 0 ], tmp1), warping_Q16 ); + psDD->sAR2_Q14[ j - 1 ] = tmp1; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ j - 1 ] ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ j + 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 1 ], tmp2), warping_Q16 ); + psDD->sAR2_Q14[ j + 0 ] = tmp2; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ j ] ); + } + psDD->sAR2_Q14[ shapingLPCOrder - 1 ] = tmp1; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ shapingLPCOrder - 1 ] ); + + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 1 ); /* Q11 -> Q12 */ + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, psDD->LF_AR_Q14, Tilt_Q14 ); /* Q12 */ + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 2 ); /* Q12 -> Q14 */ + + n_LF_Q14 = silk_SMULWB( psDD->Shape_Q14[ *smpl_buf_idx ], LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_SMLAWT( n_LF_Q14, psDD->LF_AR_Q14, LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_LSHIFT( n_LF_Q14, 2 ); /* Q12 -> Q14 */ + + /* Input minus prediction plus noise feedback */ + /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */ + tmp1 = silk_ADD_SAT32( n_AR_Q14, n_LF_Q14 ); /* Q14 */ + tmp2 = silk_ADD32_ovflw( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */ + tmp1 = silk_SUB_SAT32( tmp2, tmp1 ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 4 ); /* Q10 */ + + r_Q10 = silk_SUB32( x_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Flip sign depending on dither */ + if ( psDD->Seed < 0 ) { + r_Q10 = -r_Q10; + } + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if (Lambda_Q10 > 2048) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + int rdo_offset = Lambda_Q10/2 - 512; + if (q1_Q10 > rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 - rdo_offset, 10 ); + } else if (q1_Q10 < -rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 + rdo_offset, 10 ); + } else if (q1_Q10 < 0) { + q1_Q0 = -1; + } else { + q1_Q0 = 0; + } + } + if( q1_Q0 > 0 ) { + q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == 0 ) { + q1_Q10 = offset_Q10; + q2_Q10 = silk_ADD32( q1_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == -1 ) { + q2_Q10 = offset_Q10; + q1_Q10 = silk_SUB32( q2_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else { /* q1_Q0 < -1 */ + q1_Q10 = silk_ADD32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( -q2_Q10, Lambda_Q10 ); + } + rr_Q10 = silk_SUB32( r_Q10, q1_Q10 ); + rd1_Q10 = silk_RSHIFT( silk_SMLABB( rd1_Q10, rr_Q10, rr_Q10 ), 10 ); + rr_Q10 = silk_SUB32( r_Q10, q2_Q10 ); + rd2_Q10 = silk_RSHIFT( silk_SMLABB( rd2_Q10, rr_Q10, rr_Q10 ), 10 ); + + if( rd1_Q10 < rd2_Q10 ) { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 0 ].Q_Q10 = q1_Q10; + psSS[ 1 ].Q_Q10 = q2_Q10; + } else { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 0 ].Q_Q10 = q2_Q10; + psSS[ 1 ].Q_Q10 = q1_Q10; + } + + /* Update states for best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 0 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + psSS[ 0 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) ); + sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 0 ].Diff_Q14, n_AR_Q14 ); + psSS[ 0 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 0 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 0 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 0 ].xq_Q14 = xq_Q14; + + /* Update states for second best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 1 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + psSS[ 1 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) ); + sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 1 ].Diff_Q14, n_AR_Q14 ); + psSS[ 1 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 1 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 1 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 1 ].xq_Q14 = xq_Q14; + } + + *smpl_buf_idx = ( *smpl_buf_idx - 1 ) % DECISION_DELAY; + if( *smpl_buf_idx < 0 ) *smpl_buf_idx += DECISION_DELAY; + last_smple_idx = ( *smpl_buf_idx + decisionDelay ) % DECISION_DELAY; + + /* Find winner */ + RDmin_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + if( psSampleState[ k ][ 0 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + Winner_ind = k; + } + } + + /* Increase RD values of expired states */ + Winner_rand_state = psDelDec[ Winner_ind ].RandState[ last_smple_idx ]; + for( k = 0; k < nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RandState[ last_smple_idx ] != Winner_rand_state ) { + psSampleState[ k ][ 0 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 0 ].RD_Q10, silk_int32_MAX >> 4 ); + psSampleState[ k ][ 1 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 1 ].RD_Q10, silk_int32_MAX >> 4 ); + silk_assert( psSampleState[ k ][ 0 ].RD_Q10 >= 0 ); + } + } + + /* Find worst in first set and best in second set */ + RDmax_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + RDmin_Q10 = psSampleState[ 0 ][ 1 ].RD_Q10; + RDmax_ind = 0; + RDmin_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + /* find worst in first set */ + if( psSampleState[ k ][ 0 ].RD_Q10 > RDmax_Q10 ) { + RDmax_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + RDmax_ind = k; + } + /* find best in second set */ + if( psSampleState[ k ][ 1 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 1 ].RD_Q10; + RDmin_ind = k; + } + } + + /* Replace a state if best from second set outperforms worst in first set */ + if( RDmin_Q10 < RDmax_Q10 ) { + silk_memcpy( ( (opus_int32 *)&psDelDec[ RDmax_ind ] ) + i, + ( (opus_int32 *)&psDelDec[ RDmin_ind ] ) + i, sizeof( NSQ_del_dec_struct ) - i * sizeof( opus_int32) ); + silk_memcpy( &psSampleState[ RDmax_ind ][ 0 ], &psSampleState[ RDmin_ind ][ 1 ], sizeof( NSQ_sample_struct ) ); + } + + /* Write samples from winner to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + if( subfr > 0 || i >= decisionDelay ) { + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + xq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], delayedGain_Q10[ last_smple_idx ] ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay ] = psDD->Shape_Q14[ last_smple_idx ]; + sLTP_Q15[ NSQ->sLTP_buf_idx - decisionDelay ] = psDD->Pred_Q15[ last_smple_idx ]; + } + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Update states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psSS = &psSampleState[ k ][ 0 ]; + psDD->LF_AR_Q14 = psSS->LF_AR_Q14; + psDD->Diff_Q14 = psSS->Diff_Q14; + psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH + i ] = psSS->xq_Q14; + psDD->Xq_Q14[ *smpl_buf_idx ] = psSS->xq_Q14; + psDD->Q_Q10[ *smpl_buf_idx ] = psSS->Q_Q10; + psDD->Pred_Q15[ *smpl_buf_idx ] = silk_LSHIFT32( psSS->LPC_exc_Q14, 1 ); + psDD->Shape_Q14[ *smpl_buf_idx ] = psSS->sLTP_shp_Q14; + psDD->Seed = silk_ADD32_ovflw( psDD->Seed, silk_RSHIFT_ROUND( psSS->Q_Q10, 10 ) ); + psDD->RandState[ *smpl_buf_idx ] = psDD->Seed; + psDD->RD_Q10 = psSS->RD_Q10; + } + delayedGain_Q10[ *smpl_buf_idx ] = Gain_Q10; + } + /* Update LPC states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + silk_memcpy( psDD->sLPC_Q14, &psDD->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + } + RESTORE_STACK; +} +#endif /* OVERRIDE_silk_noise_shape_quantizer_del_dec */ + +static OPUS_INLINE void silk_nsq_del_dec_scale_states( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + opus_int nStatesDelayedDecision, /* I Number of del dec states */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +) +{ + opus_int i, k, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; + NSQ_del_dec_struct *psDD; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + silk_assert( inv_gain_Q31 != 0 ); + + /* Scale input */ + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); + for( i = 0; i < psEncC->subfr_length; i++ ) { + x_sc_Q10[ i ] = silk_SMULWW( x16[ i ], inv_gain_Q26 ); + } + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + silk_assert( i < MAX_FRAME_LENGTH ); + sLTP_Q15[ i ] = silk_SMULWB( inv_gain_Q31, sLTP[ i ] ); + } + } + + /* Adjust for changing gain */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + + /* Scale long-term shaping state */ + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx - decisionDelay; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + + /* Scale scalar states */ + psDD->LF_AR_Q14 = silk_SMULWW( gain_adj_Q16, psDD->LF_AR_Q14 ); + psDD->Diff_Q14 = silk_SMULWW( gain_adj_Q16, psDD->Diff_Q14 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + psDD->sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->sLPC_Q14[ i ] ); + } + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + psDD->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->sAR2_Q14[ i ] ); + } + for( i = 0; i < DECISION_DELAY; i++ ) { + psDD->Pred_Q15[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Pred_Q15[ i ] ); + psDD->Shape_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Shape_Q14[ i ] ); + } + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + } +} diff --git a/port/third_party/opus/silk/PLC.c b/port/third_party/opus/silk/PLC.c new file mode 100644 index 00000000..b35bf750 --- /dev/null +++ b/port/third_party/opus/silk/PLC.c @@ -0,0 +1,493 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" +#include "PLC.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet.h" +#endif + +#define NB_ATT 2 +static const opus_int16 HARM_ATT_Q15[NB_ATT] = { 32440, 31130 }; /* 0.99, 0.95 */ +static const opus_int16 PLC_RAND_ATTENUATE_V_Q15[NB_ATT] = { 31130, 26214 }; /* 0.95, 0.8 */ +static const opus_int16 PLC_RAND_ATTENUATE_UV_Q15[NB_ATT] = { 32440, 29491 }; /* 0.99, 0.9 */ + +static OPUS_INLINE void silk_PLC_update( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl /* I/O Decoder control */ +); + +static OPUS_INLINE void silk_PLC_conceal( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* O LPC residual signal */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +); + + +void silk_PLC_Reset( + silk_decoder_state *psDec /* I/O Decoder state */ +) +{ + psDec->sPLC.pitchL_Q8 = silk_LSHIFT( psDec->frame_length, 8 - 1 ); + psDec->sPLC.prevGain_Q16[ 0 ] = SILK_FIX_CONST( 1, 16 ); + psDec->sPLC.prevGain_Q16[ 1 ] = SILK_FIX_CONST( 1, 16 ); + psDec->sPLC.subfr_length = 20; + psDec->sPLC.nb_subfr = 2; +} + +void silk_PLC( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* I/O signal */ + opus_int lost, /* I Loss flag */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +) +{ + /* PLC control function */ + if( psDec->fs_kHz != psDec->sPLC.fs_kHz ) { + silk_PLC_Reset( psDec ); + psDec->sPLC.fs_kHz = psDec->fs_kHz; + } + + if( lost ) { + /****************************/ + /* Generate Signal */ + /****************************/ + silk_PLC_conceal( psDec, psDecCtrl, frame, +#ifdef ENABLE_DEEP_PLC + lpcnet, +#endif + arch ); + + psDec->lossCnt++; + } else { + /****************************/ + /* Update state */ + /****************************/ + silk_PLC_update( psDec, psDecCtrl ); +#ifdef ENABLE_DEEP_PLC + if ( lpcnet != NULL && psDec->sPLC.fs_kHz == 16 ) { + int k; + for( k = 0; k < psDec->nb_subfr; k += 2 ) { + lpcnet_plc_update( lpcnet, frame + k * psDec->subfr_length ); + } + } +#endif + } +} + +/**************************************************/ +/* Update state of PLC */ +/**************************************************/ +static OPUS_INLINE void silk_PLC_update( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl /* I/O Decoder control */ +) +{ + opus_int32 LTP_Gain_Q14, temp_LTP_Gain_Q14; + opus_int i, j; + silk_PLC_struct *psPLC; + + psPLC = &psDec->sPLC; + + /* Update parameters used in case of packet loss */ + psDec->prevSignalType = psDec->indices.signalType; + LTP_Gain_Q14 = 0; + if( psDec->indices.signalType == TYPE_VOICED ) { + /* Find the parameters for the last subframe which contains a pitch pulse */ + for( j = 0; j * psDec->subfr_length < psDecCtrl->pitchL[ psDec->nb_subfr - 1 ]; j++ ) { + if( j == psDec->nb_subfr ) { + break; + } + temp_LTP_Gain_Q14 = 0; + for( i = 0; i < LTP_ORDER; i++ ) { + temp_LTP_Gain_Q14 += psDecCtrl->LTPCoef_Q14[ ( psDec->nb_subfr - 1 - j ) * LTP_ORDER + i ]; + } + if( temp_LTP_Gain_Q14 > LTP_Gain_Q14 ) { + LTP_Gain_Q14 = temp_LTP_Gain_Q14; + silk_memcpy( psPLC->LTPCoef_Q14, + &psDecCtrl->LTPCoef_Q14[ silk_SMULBB( psDec->nb_subfr - 1 - j, LTP_ORDER ) ], + LTP_ORDER * sizeof( opus_int16 ) ); + + psPLC->pitchL_Q8 = silk_LSHIFT( psDecCtrl->pitchL[ psDec->nb_subfr - 1 - j ], 8 ); + } + } + + silk_memset( psPLC->LTPCoef_Q14, 0, LTP_ORDER * sizeof( opus_int16 ) ); + psPLC->LTPCoef_Q14[ LTP_ORDER / 2 ] = LTP_Gain_Q14; + + /* Limit LT coefs */ + if( LTP_Gain_Q14 < V_PITCH_GAIN_START_MIN_Q14 ) { + opus_int scale_Q10; + opus_int32 tmp; + + tmp = silk_LSHIFT( V_PITCH_GAIN_START_MIN_Q14, 10 ); + scale_Q10 = silk_DIV32( tmp, silk_max( LTP_Gain_Q14, 1 ) ); + for( i = 0; i < LTP_ORDER; i++ ) { + psPLC->LTPCoef_Q14[ i ] = silk_RSHIFT( silk_SMULBB( psPLC->LTPCoef_Q14[ i ], scale_Q10 ), 10 ); + } + } else if( LTP_Gain_Q14 > V_PITCH_GAIN_START_MAX_Q14 ) { + opus_int scale_Q14; + opus_int32 tmp; + + tmp = silk_LSHIFT( V_PITCH_GAIN_START_MAX_Q14, 14 ); + scale_Q14 = silk_DIV32( tmp, silk_max( LTP_Gain_Q14, 1 ) ); + for( i = 0; i < LTP_ORDER; i++ ) { + psPLC->LTPCoef_Q14[ i ] = silk_RSHIFT( silk_SMULBB( psPLC->LTPCoef_Q14[ i ], scale_Q14 ), 14 ); + } + } + } else { + psPLC->pitchL_Q8 = silk_LSHIFT( silk_SMULBB( psDec->fs_kHz, 18 ), 8 ); + silk_memset( psPLC->LTPCoef_Q14, 0, LTP_ORDER * sizeof( opus_int16 )); + } + + /* Save LPC coeficients */ + silk_memcpy( psPLC->prevLPC_Q12, psDecCtrl->PredCoef_Q12[ 1 ], psDec->LPC_order * sizeof( opus_int16 ) ); + psPLC->prevLTP_scale_Q14 = psDecCtrl->LTP_scale_Q14; + + /* Save last two gains */ + silk_memcpy( psPLC->prevGain_Q16, &psDecCtrl->Gains_Q16[ psDec->nb_subfr - 2 ], 2 * sizeof( opus_int32 ) ); + + psPLC->subfr_length = psDec->subfr_length; + psPLC->nb_subfr = psDec->nb_subfr; +} + +static OPUS_INLINE void silk_PLC_energy(opus_int32 *energy1, opus_int *shift1, opus_int32 *energy2, opus_int *shift2, + const opus_int32 *exc_Q14, const opus_int32 *prevGain_Q10, int subfr_length, int nb_subfr) +{ + int i, k; + VARDECL( opus_int16, exc_buf ); + opus_int16 *exc_buf_ptr; + SAVE_STACK; + ALLOC( exc_buf, 2*subfr_length, opus_int16 ); + /* Find random noise component */ + /* Scale previous excitation signal */ + exc_buf_ptr = exc_buf; + for( k = 0; k < 2; k++ ) { + for( i = 0; i < subfr_length; i++ ) { + exc_buf_ptr[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT( + silk_SMULWW( exc_Q14[ i + ( k + nb_subfr - 2 ) * subfr_length ], prevGain_Q10[ k ] ), 8 ) ); + } + exc_buf_ptr += subfr_length; + } + /* Find the subframe with lowest energy of the last two and use that as random noise generator */ + silk_sum_sqr_shift( energy1, shift1, exc_buf, subfr_length ); + silk_sum_sqr_shift( energy2, shift2, &exc_buf[ subfr_length ], subfr_length ); + RESTORE_STACK; +} + +static OPUS_INLINE void silk_PLC_conceal( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* O LPC residual signal */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +) +{ + opus_int i, j, k; + opus_int lag, idx, sLTP_buf_idx, shift1, shift2; + opus_int32 rand_seed, harm_Gain_Q15, rand_Gain_Q15, inv_gain_Q30; + opus_int32 energy1, energy2, *rand_ptr, *pred_lag_ptr; + opus_int32 LPC_pred_Q10, LTP_pred_Q12; + opus_int16 rand_scale_Q14; + opus_int16 *B_Q14; + opus_int32 *sLPC_Q14_ptr; + opus_int16 A_Q12[ MAX_LPC_ORDER ]; +#ifdef SMALL_FOOTPRINT + opus_int16 *sLTP; +#else + VARDECL( opus_int16, sLTP ); +#endif + VARDECL( opus_int32, sLTP_Q14 ); + silk_PLC_struct *psPLC = &psDec->sPLC; + opus_int32 prevGain_Q10[2]; + SAVE_STACK; + + ALLOC( sLTP_Q14, psDec->ltp_mem_length + psDec->frame_length, opus_int32 ); +#ifdef SMALL_FOOTPRINT + /* Ugly hack that breaks aliasing rules to save stack: put sLTP at the very end of sLTP_Q14. */ + sLTP = ((opus_int16*)&sLTP_Q14[psDec->ltp_mem_length + psDec->frame_length])-psDec->ltp_mem_length; +#else + ALLOC( sLTP, psDec->ltp_mem_length, opus_int16 ); +#endif + + prevGain_Q10[0] = silk_RSHIFT( psPLC->prevGain_Q16[ 0 ], 6); + prevGain_Q10[1] = silk_RSHIFT( psPLC->prevGain_Q16[ 1 ], 6); + + if( psDec->first_frame_after_reset ) { + silk_memset( psPLC->prevLPC_Q12, 0, sizeof( psPLC->prevLPC_Q12 ) ); + } + + silk_PLC_energy(&energy1, &shift1, &energy2, &shift2, psDec->exc_Q14, prevGain_Q10, psDec->subfr_length, psDec->nb_subfr); + + if( silk_RSHIFT( energy1, shift2 ) < silk_RSHIFT( energy2, shift1 ) ) { + /* First sub-frame has lowest energy */ + rand_ptr = &psDec->exc_Q14[ silk_max_int( 0, ( psPLC->nb_subfr - 1 ) * psPLC->subfr_length - RAND_BUF_SIZE ) ]; + } else { + /* Second sub-frame has lowest energy */ + rand_ptr = &psDec->exc_Q14[ silk_max_int( 0, psPLC->nb_subfr * psPLC->subfr_length - RAND_BUF_SIZE ) ]; + } + + /* Set up Gain to random noise component */ + B_Q14 = psPLC->LTPCoef_Q14; + rand_scale_Q14 = psPLC->randScale_Q14; + + /* Set up attenuation gains */ + harm_Gain_Q15 = HARM_ATT_Q15[ silk_min_int( NB_ATT - 1, psDec->lossCnt ) ]; + if( psDec->prevSignalType == TYPE_VOICED ) { + rand_Gain_Q15 = PLC_RAND_ATTENUATE_V_Q15[ silk_min_int( NB_ATT - 1, psDec->lossCnt ) ]; + } else { + rand_Gain_Q15 = PLC_RAND_ATTENUATE_UV_Q15[ silk_min_int( NB_ATT - 1, psDec->lossCnt ) ]; + } + + /* LPC concealment. Apply BWE to previous LPC */ + silk_bwexpander( psPLC->prevLPC_Q12, psDec->LPC_order, SILK_FIX_CONST( BWE_COEF, 16 ) ); + + /* Preload LPC coeficients to array on stack. Gives small performance gain */ + silk_memcpy( A_Q12, psPLC->prevLPC_Q12, psDec->LPC_order * sizeof( opus_int16 ) ); + + /* First Lost frame */ + if( psDec->lossCnt == 0 ) { + rand_scale_Q14 = 1 << 14; + + /* Reduce random noise Gain for voiced frames */ + if( psDec->prevSignalType == TYPE_VOICED ) { + for( i = 0; i < LTP_ORDER; i++ ) { + rand_scale_Q14 -= B_Q14[ i ]; + } + rand_scale_Q14 = silk_max_16( 3277, rand_scale_Q14 ); /* 0.2 */ + rand_scale_Q14 = (opus_int16)silk_RSHIFT( silk_SMULBB( rand_scale_Q14, psPLC->prevLTP_scale_Q14 ), 14 ); + } else { + /* Reduce random noise for unvoiced frames with high LPC gain */ + opus_int32 invGain_Q30, down_scale_Q30; + + invGain_Q30 = silk_LPC_inverse_pred_gain( psPLC->prevLPC_Q12, psDec->LPC_order, arch ); + + down_scale_Q30 = silk_min_32( silk_RSHIFT( (opus_int32)1 << 30, LOG2_INV_LPC_GAIN_HIGH_THRES ), invGain_Q30 ); + down_scale_Q30 = silk_max_32( silk_RSHIFT( (opus_int32)1 << 30, LOG2_INV_LPC_GAIN_LOW_THRES ), down_scale_Q30 ); + down_scale_Q30 = silk_LSHIFT( down_scale_Q30, LOG2_INV_LPC_GAIN_HIGH_THRES ); + + rand_Gain_Q15 = silk_RSHIFT( silk_SMULWB( down_scale_Q30, rand_Gain_Q15 ), 14 ); + } + } + + rand_seed = psPLC->rand_seed; + lag = silk_RSHIFT_ROUND( psPLC->pitchL_Q8, 8 ); + sLTP_buf_idx = psDec->ltp_mem_length; + + /* Rewhiten LTP state */ + idx = psDec->ltp_mem_length - lag - psDec->LPC_order - LTP_ORDER / 2; + celt_assert( idx > 0 ); + silk_LPC_analysis_filter( &sLTP[ idx ], &psDec->outBuf[ idx ], A_Q12, psDec->ltp_mem_length - idx, psDec->LPC_order, arch ); + /* Scale LTP state */ + inv_gain_Q30 = silk_INVERSE32_varQ( psPLC->prevGain_Q16[ 1 ], 46 ); + inv_gain_Q30 = silk_min( inv_gain_Q30, silk_int32_MAX >> 1 ); + for( i = idx + psDec->LPC_order; i < psDec->ltp_mem_length; i++ ) { + sLTP_Q14[ i ] = silk_SMULWB( inv_gain_Q30, sLTP[ i ] ); + } + + /***************************/ + /* LTP synthesis filtering */ + /***************************/ + for( k = 0; k < psDec->nb_subfr; k++ ) { + /* Set up pointer */ + pred_lag_ptr = &sLTP_Q14[ sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + for( i = 0; i < psDec->subfr_length; i++ ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q12 = 2; + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ 0 ], B_Q14[ 0 ] ); + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ -1 ], B_Q14[ 1 ] ); + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ -2 ], B_Q14[ 2 ] ); + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ -3 ], B_Q14[ 3 ] ); + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ -4 ], B_Q14[ 4 ] ); + pred_lag_ptr++; + + /* Generate LPC excitation */ + rand_seed = silk_RAND( rand_seed ); + idx = silk_RSHIFT( rand_seed, 25 ) & RAND_BUF_MASK; + sLTP_Q14[ sLTP_buf_idx ] = silk_LSHIFT32( silk_SMLAWB( LTP_pred_Q12, rand_ptr[ idx ], rand_scale_Q14 ), 2 ); + sLTP_buf_idx++; + } + + /* Gradually reduce LTP gain */ + for( j = 0; j < LTP_ORDER; j++ ) { + B_Q14[ j ] = silk_RSHIFT( silk_SMULBB( harm_Gain_Q15, B_Q14[ j ] ), 15 ); + } + /* Gradually reduce excitation gain */ + rand_scale_Q14 = silk_RSHIFT( silk_SMULBB( rand_scale_Q14, rand_Gain_Q15 ), 15 ); + + /* Slowly increase pitch lag */ + psPLC->pitchL_Q8 = silk_SMLAWB( psPLC->pitchL_Q8, psPLC->pitchL_Q8, PITCH_DRIFT_FAC_Q16 ); + psPLC->pitchL_Q8 = silk_min_32( psPLC->pitchL_Q8, silk_LSHIFT( silk_SMULBB( MAX_PITCH_LAG_MS, psDec->fs_kHz ), 8 ) ); + lag = silk_RSHIFT_ROUND( psPLC->pitchL_Q8, 8 ); + } + + /***************************/ + /* LPC synthesis filtering */ + /***************************/ + sLPC_Q14_ptr = &sLTP_Q14[ psDec->ltp_mem_length - MAX_LPC_ORDER ]; + + /* Copy LPC state */ + silk_memcpy( sLPC_Q14_ptr, psDec->sLPC_Q14_buf, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + + celt_assert( psDec->LPC_order >= 10 ); /* check that unrolling works */ + for( i = 0; i < psDec->frame_length; i++ ) { + /* partly unrolled */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q10 = silk_RSHIFT( psDec->LPC_order, 1 ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 1 ], A_Q12[ 0 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 2 ], A_Q12[ 1 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 3 ], A_Q12[ 2 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 4 ], A_Q12[ 3 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 5 ], A_Q12[ 4 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 6 ], A_Q12[ 5 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 7 ], A_Q12[ 6 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 8 ], A_Q12[ 7 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 9 ], A_Q12[ 8 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 10 ], A_Q12[ 9 ] ); + for( j = 10; j < psDec->LPC_order; j++ ) { + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - j - 1 ], A_Q12[ j ] ); + } + + /* Add prediction to LPC excitation */ + sLPC_Q14_ptr[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( sLPC_Q14_ptr[ MAX_LPC_ORDER + i ], + silk_LSHIFT_SAT32( LPC_pred_Q10, 4 )); + + /* Scale with Gain */ + frame[ i ] = (opus_int16)silk_SAT16( silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( sLPC_Q14_ptr[ MAX_LPC_ORDER + i ], prevGain_Q10[ 1 ] ), 8 ) ) ); + } +#ifdef ENABLE_DEEP_PLC + if ( lpcnet != NULL && lpcnet->loaded && psDec->sPLC.fs_kHz == 16 ) { + int run_deep_plc = psDec->sPLC.enable_deep_plc || lpcnet->fec_fill_pos != 0; + if( run_deep_plc ) { + for( k = 0; k < psDec->nb_subfr; k += 2 ) { + lpcnet_plc_conceal( lpcnet, frame + k * psDec->subfr_length ); + } + /* We *should* be able to copy only from psDec->frame_length-MAX_LPC_ORDER, i.e. the last MAX_LPC_ORDER samples. */ + for( i = 0; i < psDec->frame_length; i++ ) { + sLPC_Q14_ptr[ MAX_LPC_ORDER + i ] = (int)floor(.5 + frame[ i ] * (float)(1 << 24) / prevGain_Q10[ 1 ] ); + } + } else { + for( k = 0; k < psDec->nb_subfr; k += 2 ) { + lpcnet_plc_update( lpcnet, frame + k * psDec->subfr_length ); + } + } + } +#endif + + /* Save LPC state */ + silk_memcpy( psDec->sLPC_Q14_buf, &sLPC_Q14_ptr[ psDec->frame_length ], MAX_LPC_ORDER * sizeof( opus_int32 ) ); + + /**************************************/ + /* Update states */ + /**************************************/ + psPLC->rand_seed = rand_seed; + psPLC->randScale_Q14 = rand_scale_Q14; + for( i = 0; i < MAX_NB_SUBFR; i++ ) { + psDecCtrl->pitchL[ i ] = lag; + } + RESTORE_STACK; +} + +/* Glues concealed frames with new good received frames */ +void silk_PLC_glue_frames( + silk_decoder_state *psDec, /* I/O decoder state */ + opus_int16 frame[], /* I/O signal */ + opus_int length /* I length of signal */ +) +{ + opus_int i, energy_shift; + opus_int32 energy; + silk_PLC_struct *psPLC; + psPLC = &psDec->sPLC; + + if( psDec->lossCnt ) { + /* Calculate energy in concealed residual */ + silk_sum_sqr_shift( &psPLC->conc_energy, &psPLC->conc_energy_shift, frame, length ); + + psPLC->last_frame_lost = 1; + } else { + if( psDec->sPLC.last_frame_lost ) { + /* Calculate residual in decoded signal if last frame was lost */ + silk_sum_sqr_shift( &energy, &energy_shift, frame, length ); + + /* Normalize energies */ + if( energy_shift > psPLC->conc_energy_shift ) { + psPLC->conc_energy = silk_RSHIFT( psPLC->conc_energy, energy_shift - psPLC->conc_energy_shift ); + } else if( energy_shift < psPLC->conc_energy_shift ) { + energy = silk_RSHIFT( energy, psPLC->conc_energy_shift - energy_shift ); + } + + /* Fade in the energy difference */ + if( energy > psPLC->conc_energy ) { + opus_int32 frac_Q24, LZ; + opus_int32 gain_Q16, slope_Q16; + + LZ = silk_CLZ32( psPLC->conc_energy ); + LZ = LZ - 1; + psPLC->conc_energy = silk_LSHIFT( psPLC->conc_energy, LZ ); + energy = silk_RSHIFT( energy, silk_max_32( 24 - LZ, 0 ) ); + + frac_Q24 = silk_DIV32( psPLC->conc_energy, silk_max( energy, 1 ) ); + + gain_Q16 = silk_LSHIFT( silk_SQRT_APPROX( frac_Q24 ), 4 ); + slope_Q16 = silk_DIV32_16( ( (opus_int32)1 << 16 ) - gain_Q16, length ); + /* Make slope 4x steeper to avoid missing onsets after DTX */ + slope_Q16 = silk_LSHIFT( slope_Q16, 2 ); +#ifdef ENABLE_DEEP_PLC + if ( psDec->sPLC.fs_kHz != 16 ) +#endif + { + for( i = 0; i < length; i++ ) { + frame[ i ] = silk_SMULWB( gain_Q16, frame[ i ] ); + gain_Q16 += slope_Q16; + if( gain_Q16 > (opus_int32)1 << 16 ) { + break; + } + } + } + } + } + psPLC->last_frame_lost = 0; + } +} diff --git a/port/third_party/opus/silk/PLC.h b/port/third_party/opus/silk/PLC.h new file mode 100644 index 00000000..1bebb786 --- /dev/null +++ b/port/third_party/opus/silk/PLC.h @@ -0,0 +1,65 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_PLC_H +#define SILK_PLC_H + +#include "main.h" + +#define BWE_COEF 0.99 +#define V_PITCH_GAIN_START_MIN_Q14 11469 /* 0.7 in Q14 */ +#define V_PITCH_GAIN_START_MAX_Q14 15565 /* 0.95 in Q14 */ +#define MAX_PITCH_LAG_MS 18 +#define RAND_BUF_SIZE 128 +#define RAND_BUF_MASK ( RAND_BUF_SIZE - 1 ) +#define LOG2_INV_LPC_GAIN_HIGH_THRES 3 /* 2^3 = 8 dB LPC gain */ +#define LOG2_INV_LPC_GAIN_LOW_THRES 8 /* 2^8 = 24 dB LPC gain */ +#define PITCH_DRIFT_FAC_Q16 655 /* 0.01 in Q16 */ + +void silk_PLC_Reset( + silk_decoder_state *psDec /* I/O Decoder state */ +); + +void silk_PLC( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* I/O signal */ + opus_int lost, /* I Loss flag */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +); + +void silk_PLC_glue_frames( + silk_decoder_state *psDec, /* I/O decoder state */ + opus_int16 frame[], /* I/O signal */ + opus_int length /* I length of signal */ +); + +#endif + diff --git a/port/third_party/opus/silk/SigProc_FIX.h b/port/third_party/opus/silk/SigProc_FIX.h new file mode 100644 index 00000000..fbdfa82e --- /dev/null +++ b/port/third_party/opus/silk/SigProc_FIX.h @@ -0,0 +1,643 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_SIGPROC_FIX_H +#define SILK_SIGPROC_FIX_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/*#define silk_MACRO_COUNT */ /* Used to enable WMOPS counting */ + +#define SILK_MAX_ORDER_LPC 24 /* max order of the LPC analysis in schur() and k2a() */ + +#include /* for memset(), memcpy(), memmove() */ +#include "typedef.h" +#include "resampler_structs.h" +#include "macros.h" +#include "cpu_support.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +#include "x86/SigProc_FIX_sse.h" +#endif + +#if (defined(OPUS_ARM_ASM) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) +#include "arm/biquad_alt_arm.h" +#include "arm/LPC_inv_pred_gain_arm.h" +#endif + +/********************************************************************/ +/* SIGNAL PROCESSING FUNCTIONS */ +/********************************************************************/ + +/*! + * Initialize/reset the resampler state for a given pair of input/output sampling rates +*/ +opus_int silk_resampler_init( + silk_resampler_state_struct *S, /* I/O Resampler state */ + opus_int32 Fs_Hz_in, /* I Input sampling rate (Hz) */ + opus_int32 Fs_Hz_out, /* I Output sampling rate (Hz) */ + opus_int forEnc /* I If 1: encoder; if 0: decoder */ +); + +/*! + * Resampler: convert from one sampling rate to another + */ +opus_int silk_resampler( + silk_resampler_state_struct *S, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +); + +/*! +* Downsample 2x, mediocre quality +*/ +void silk_resampler_down2( + opus_int32 *S, /* I/O State vector [ 2 ] */ + opus_int16 *out, /* O Output signal [ len ] */ + const opus_int16 *in, /* I Input signal [ floor(len/2) ] */ + opus_int32 inLen /* I Number of input samples */ +); + +/*! + * Downsample by a factor 2/3, low quality +*/ +void silk_resampler_down2_3( + opus_int32 *S, /* I/O State vector [ 6 ] */ + opus_int16 *out, /* O Output signal [ floor(2*inLen/3) ] */ + const opus_int16 *in, /* I Input signal [ inLen ] */ + opus_int32 inLen /* I Number of input samples */ +); + +/*! + * second order ARMA filter; + * slower than biquad() but uses more precise coefficients + * can handle (slowly) varying coefficients + */ +void silk_biquad_alt_stride1( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [2] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +); + +void silk_biquad_alt_stride2_c( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [4] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +); + +/* Variable order MA prediction error filter. */ +void silk_LPC_analysis_filter( + opus_int16 *out, /* O Output signal */ + const opus_int16 *in, /* I Input signal */ + const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */ + const opus_int32 len, /* I Signal length */ + const opus_int32 d, /* I Filter order */ + int arch /* I Run-time architecture */ +); + +/* Chirp (bandwidth expand) LP AR filter */ +void silk_bwexpander( + opus_int16 *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I Length of ar */ + opus_int32 chirp_Q16 /* I Chirp factor (typically in the range 0 to 1) */ +); + +/* Chirp (bandwidth expand) LP AR filter */ +void silk_bwexpander_32( + opus_int32 *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I Length of ar */ + opus_int32 chirp_Q16 /* I Chirp factor in Q16 */ +); + +/* Compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +opus_int32 silk_LPC_inverse_pred_gain_c( /* O Returns inverse prediction gain in energy domain, Q30 */ + const opus_int16 *A_Q12, /* I Prediction coefficients, Q12 [order] */ + const opus_int order /* I Prediction order */ +); + +/* Split signal in two decimated bands using first-order allpass filters */ +void silk_ana_filt_bank_1( + const opus_int16 *in, /* I Input signal [N] */ + opus_int32 *S, /* I/O State vector [2] */ + opus_int16 *outL, /* O Low band [N/2] */ + opus_int16 *outH, /* O High band [N/2] */ + const opus_int32 N /* I Number of input samples */ +); + +#if !defined(OVERRIDE_silk_biquad_alt_stride2) +#define silk_biquad_alt_stride2(in, B_Q28, A_Q28, S, out, len, arch) ((void)(arch), silk_biquad_alt_stride2_c(in, B_Q28, A_Q28, S, out, len)) +#endif + +#if !defined(OVERRIDE_silk_LPC_inverse_pred_gain) +#define silk_LPC_inverse_pred_gain(A_Q12, order, arch) ((void)(arch), silk_LPC_inverse_pred_gain_c(A_Q12, order)) +#endif + +/********************************************************************/ +/* SCALAR FUNCTIONS */ +/********************************************************************/ + +/* Approximation of 128 * log2() (exact inverse of approx 2^() below) */ +/* Convert input to a log scale */ +opus_int32 silk_lin2log( + const opus_int32 inLin /* I input in linear scale */ +); + +/* Approximation of a sigmoid function */ +opus_int silk_sigm_Q15( + opus_int in_Q5 /* I */ +); + +/* Approximation of 2^() (exact inverse of approx log2() above) */ +/* Convert input to a linear scale */ +opus_int32 silk_log2lin( + const opus_int32 inLog_Q7 /* I input on log scale */ +); + +/* Compute number of bits to right shift the sum of squares of a vector */ +/* of int16s to make it fit in an int32 */ +void silk_sum_sqr_shift( + opus_int32 *energy, /* O Energy of x, after shifting to the right */ + opus_int *shift, /* O Number of bits right shift applied to energy */ + const opus_int16 *x, /* I Input vector */ + opus_int len /* I Length of input vector */ +); + +/* Calculates the reflection coefficients from the correlation sequence */ +/* Faster than schur64(), but much less accurate. */ +/* uses SMLAWB(), requiring armv5E and higher. */ +opus_int32 silk_schur( /* O Returns residual energy */ + opus_int16 *rc_Q15, /* O reflection coefficients [order] Q15 */ + const opus_int32 *c, /* I correlations [order+1] */ + const opus_int32 order /* I prediction order */ +); + +/* Calculates the reflection coefficients from the correlation sequence */ +/* Slower than schur(), but more accurate. */ +/* Uses SMULL(), available on armv4 */ +opus_int32 silk_schur64( /* O returns residual energy */ + opus_int32 rc_Q16[], /* O Reflection coefficients [order] Q16 */ + const opus_int32 c[], /* I Correlations [order+1] */ + opus_int32 order /* I Prediction order */ +); + +/* Step up function, converts reflection coefficients to prediction coefficients */ +void silk_k2a( + opus_int32 *A_Q24, /* O Prediction coefficients [order] Q24 */ + const opus_int16 *rc_Q15, /* I Reflection coefficients [order] Q15 */ + const opus_int32 order /* I Prediction order */ +); + +/* Step up function, converts reflection coefficients to prediction coefficients */ +void silk_k2a_Q16( + opus_int32 *A_Q24, /* O Prediction coefficients [order] Q24 */ + const opus_int32 *rc_Q16, /* I Reflection coefficients [order] Q16 */ + const opus_int32 order /* I Prediction order */ +); + +/* Apply sine window to signal vector. */ +/* Window types: */ +/* 1 -> sine window from 0 to pi/2 */ +/* 2 -> sine window from pi/2 to pi */ +/* every other sample of window is linearly interpolated, for speed */ +void silk_apply_sine_window( + opus_int16 px_win[], /* O Pointer to windowed signal */ + const opus_int16 px[], /* I Pointer to input signal */ + const opus_int win_type, /* I Selects a window type */ + const opus_int length /* I Window length, multiple of 4 */ +); + +/* Compute autocorrelation */ +void silk_autocorr( + opus_int32 *results, /* O Result (length correlationCount) */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *inputData, /* I Input data to correlate */ + const opus_int inputDataSize, /* I Length of input */ + const opus_int correlationCount, /* I Number of correlation taps to compute */ + int arch /* I Run-time architecture */ +); + +void silk_decode_pitch( + opus_int16 lagIndex, /* I */ + opus_int8 contourIndex, /* O */ + opus_int pitch_lags[], /* O 4 pitch values */ + const opus_int Fs_kHz, /* I sampling frequency (kHz) */ + const opus_int nb_subfr /* I number of sub frames */ +); + +opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 voiced, 1 unvoiced */ + const opus_int16 *frame, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ + opus_int *pitch_out, /* O 4 pitch lag values */ + opus_int16 *lagIndex, /* O Lag Index */ + opus_int8 *contourIndex, /* O Pitch contour Index */ + opus_int *LTPCorr_Q15, /* I/O Normalized correlation; input: value from previous frame */ + opus_int prevLag, /* I Last lag of previous frame; set to zero is unvoiced */ + const opus_int32 search_thres1_Q16, /* I First stage threshold for lag candidates 0 - 1 */ + const opus_int search_thres2_Q13, /* I Final threshold for lag candidates 0 - 1 */ + const opus_int Fs_kHz, /* I Sample frequency (kHz) */ + const opus_int complexity, /* I Complexity setting, 0-2, where 2 is highest */ + const opus_int nb_subfr, /* I number of 5 ms subframes */ + int arch /* I Run-time architecture */ +); + +/* Compute Normalized Line Spectral Frequencies (NLSFs) from whitening filter coefficients */ +/* If not all roots are found, the a_Q16 coefficients are bandwidth expanded until convergence. */ +void silk_A2NLSF( + opus_int16 *NLSF, /* O Normalized Line Spectral Frequencies in Q15 (0..2^15-1) [d] */ + opus_int32 *a_Q16, /* I/O Monic whitening filter coefficients in Q16 [d] */ + const opus_int d /* I Filter order (must be even) */ +); + +/* compute whitening filter coefficients from normalized line spectral frequencies */ +void silk_NLSF2A( + opus_int16 *a_Q12, /* O monic whitening filter coefficients in Q12, [ d ] */ + const opus_int16 *NLSF, /* I normalized line spectral frequencies in Q15, [ d ] */ + const opus_int d, /* I filter order (should be even) */ + int arch /* I Run-time architecture */ +); + +/* Convert int32 coefficients to int16 coefs and make sure there's no wrap-around */ +void silk_LPC_fit( + opus_int16 *a_QOUT, /* O Output signal */ + opus_int32 *a_QIN, /* I/O Input signal */ + const opus_int QOUT, /* I Input Q domain */ + const opus_int QIN, /* I Input Q domain */ + const opus_int d /* I Filter order */ +); + +void silk_insertion_sort_increasing( + opus_int32 *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +); + +void silk_insertion_sort_decreasing_int16( + opus_int16 *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +); + +void silk_insertion_sort_increasing_all_values_int16( + opus_int16 *a, /* I/O Unsorted / Sorted vector */ + const opus_int L /* I Vector length */ +); + +/* NLSF stabilizer, for a single input data vector */ +void silk_NLSF_stabilize( + opus_int16 *NLSF_Q15, /* I/O Unstable/stabilized normalized LSF vector in Q15 [L] */ + const opus_int16 *NDeltaMin_Q15, /* I Min distance vector, NDeltaMin_Q15[L] must be >= 1 [L+1] */ + const opus_int L /* I Number of NLSF parameters in the input vector */ +); + +/* Laroia low complexity NLSF weights */ +void silk_NLSF_VQ_weights_laroia( + opus_int16 *pNLSFW_Q_OUT, /* O Pointer to input vector weights [D] */ + const opus_int16 *pNLSF_Q15, /* I Pointer to input vector [D] */ + const opus_int D /* I Input vector dimension (even) */ +); + +/* Compute reflection coefficients from input signal */ +void silk_burg_modified_c( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +); + +/* Copy and multiply a vector by a constant */ +void silk_scale_copy_vector16( + opus_int16 *data_out, + const opus_int16 *data_in, + opus_int32 gain_Q16, /* I Gain in Q16 */ + const opus_int dataSize /* I Length */ +); + +/* Some for the LTP related function requires Q26 to work.*/ +void silk_scale_vector32_Q26_lshift_18( + opus_int32 *data1, /* I/O Q0/Q18 */ + opus_int32 gain_Q26, /* I Q26 */ + opus_int dataSize /* I length */ +); + +/********************************************************************/ +/* INLINE ARM MATH */ +/********************************************************************/ + +/* return sum( inVec1[i] * inVec2[i] ) */ + +opus_int32 silk_inner_prod_aligned( + const opus_int16 *const inVec1, /* I input vector 1 */ + const opus_int16 *const inVec2, /* I input vector 2 */ + const opus_int len, /* I vector lengths */ + int arch /* I Run-time architecture */ +); + + +opus_int32 silk_inner_prod_aligned_scale( + const opus_int16 *const inVec1, /* I input vector 1 */ + const opus_int16 *const inVec2, /* I input vector 2 */ + const opus_int scale, /* I number of bits to shift */ + const opus_int len /* I vector lengths */ +); + +opus_int64 silk_inner_prod16_c( + const opus_int16 *inVec1, /* I input vector 1 */ + const opus_int16 *inVec2, /* I input vector 2 */ + const opus_int len /* I vector lengths */ +); + +/********************************************************************/ +/* MACROS */ +/********************************************************************/ + +/* Rotate a32 right by 'rot' bits. Negative rot values result in rotating + left. Output is 32bit int. + Note: contemporary compilers recognize the C expression below and + compile it into a 'ror' instruction if available. No need for OPUS_INLINE ASM! */ +static OPUS_INLINE opus_int32 silk_ROR32( opus_int32 a32, opus_int rot ) +{ + opus_uint32 x = (opus_uint32) a32; + opus_uint32 r = (opus_uint32) rot; + opus_uint32 m = (opus_uint32) -rot; + if( rot == 0 ) { + return a32; + } else if( rot < 0 ) { + return (opus_int32) ((x << m) | (x >> (32 - m))); + } else { + return (opus_int32) ((x << (32 - r)) | (x >> r)); + } +} + +/* Allocate opus_int16 aligned to 4-byte memory address */ +#if EMBEDDED_ARM +#define silk_DWORD_ALIGN __attribute__((aligned(4))) +#else +#define silk_DWORD_ALIGN +#endif + +/* Useful Macros that can be adjusted to other platforms */ +#define silk_memcpy(dest, src, size) memcpy((dest), (src), (size)) +#define silk_memset(dest, src, size) memset((dest), (src), (size)) +#define silk_memmove(dest, src, size) memmove((dest), (src), (size)) + +/* Fixed point macros */ + +/* (a32 * b32) output have to be 32bit int */ +#define silk_MUL(a32, b32) ((a32) * (b32)) + +/* (a32 * b32) output have to be 32bit uint */ +#define silk_MUL_uint(a32, b32) silk_MUL(a32, b32) + +/* a32 + (b32 * c32) output have to be 32bit int */ +#define silk_MLA(a32, b32, c32) silk_ADD32((a32),((b32) * (c32))) + +/* a32 + (b32 * c32) output have to be 32bit uint */ +#define silk_MLA_uint(a32, b32, c32) silk_MLA(a32, b32, c32) + +/* ((a32 >> 16) * (b32 >> 16)) output have to be 32bit int */ +#define silk_SMULTT(a32, b32) (((a32) >> 16) * ((b32) >> 16)) + +/* a32 + ((a32 >> 16) * (b32 >> 16)) output have to be 32bit int */ +#define silk_SMLATT(a32, b32, c32) silk_ADD32((a32),((b32) >> 16) * ((c32) >> 16)) + +#define silk_SMLALBB(a64, b16, c16) silk_ADD64((a64),(opus_int64)((opus_int32)(b16) * (opus_int32)(c16))) + +/* (a32 * b32) */ +#define silk_SMULL(a32, b32) ((opus_int64)(a32) * /*(opus_int64)*/(b32)) + +/* Adds two signed 32-bit values in a way that can overflow, while not relying on undefined behaviour + (just standard two's complement implementation-specific behaviour) */ +#define silk_ADD32_ovflw(a, b) ((opus_int32)((opus_uint32)(a) + (opus_uint32)(b))) +/* Subtractss two signed 32-bit values in a way that can overflow, while not relying on undefined behaviour + (just standard two's complement implementation-specific behaviour) */ +#define silk_SUB32_ovflw(a, b) ((opus_int32)((opus_uint32)(a) - (opus_uint32)(b))) + +/* Multiply-accumulate macros that allow overflow in the addition (ie, no asserts in debug mode) */ +#define silk_MLA_ovflw(a32, b32, c32) silk_ADD32_ovflw((a32), (opus_uint32)(b32) * (opus_uint32)(c32)) +#define silk_SMLABB_ovflw(a32, b32, c32) (silk_ADD32_ovflw((a32) , ((opus_int32)((opus_int16)(b32))) * (opus_int32)((opus_int16)(c32)))) + +#define silk_DIV32_16(a32, b16) ((opus_int32)((a32) / (b16))) +#define silk_DIV32(a32, b32) ((opus_int32)((a32) / (b32))) + +/* These macros enables checking for overflow in silk_API_Debug.h*/ +#define silk_ADD16(a, b) ((a) + (b)) +#define silk_ADD32(a, b) ((a) + (b)) +#define silk_ADD64(a, b) ((a) + (b)) + +#define silk_SUB16(a, b) ((a) - (b)) +#define silk_SUB32(a, b) ((a) - (b)) +#define silk_SUB64(a, b) ((a) - (b)) + +#define silk_SAT8(a) ((a) > silk_int8_MAX ? silk_int8_MAX : \ + ((a) < silk_int8_MIN ? silk_int8_MIN : (a))) +#define silk_SAT16(a) ((a) > silk_int16_MAX ? silk_int16_MAX : \ + ((a) < silk_int16_MIN ? silk_int16_MIN : (a))) +#define silk_SAT32(a) ((a) > silk_int32_MAX ? silk_int32_MAX : \ + ((a) < silk_int32_MIN ? silk_int32_MIN : (a))) + +#define silk_CHECK_FIT8(a) (a) +#define silk_CHECK_FIT16(a) (a) +#define silk_CHECK_FIT32(a) (a) + +#define silk_ADD_SAT16(a, b) (opus_int16)silk_SAT16( silk_ADD32( (opus_int32)(a), (b) ) ) +#define silk_ADD_SAT64(a, b) ((((a) + (b)) & 0x8000000000000000LL) == 0 ? \ + ((((a) & (b)) & 0x8000000000000000LL) != 0 ? silk_int64_MIN : (a)+(b)) : \ + ((((a) | (b)) & 0x8000000000000000LL) == 0 ? silk_int64_MAX : (a)+(b)) ) + +#define silk_SUB_SAT16(a, b) (opus_int16)silk_SAT16( silk_SUB32( (opus_int32)(a), (b) ) ) +#define silk_SUB_SAT64(a, b) ((((a)-(b)) & 0x8000000000000000LL) == 0 ? \ + (( (a) & ((b)^0x8000000000000000LL) & 0x8000000000000000LL) ? silk_int64_MIN : (a)-(b)) : \ + ((((a)^0x8000000000000000LL) & (b) & 0x8000000000000000LL) ? silk_int64_MAX : (a)-(b)) ) + +/* Saturation for positive input values */ +#define silk_POS_SAT32(a) ((a) > silk_int32_MAX ? silk_int32_MAX : (a)) + +/* Add with saturation for positive input values */ +#define silk_ADD_POS_SAT8(a, b) ((((a)+(b)) & 0x80) ? silk_int8_MAX : ((a)+(b))) +#define silk_ADD_POS_SAT16(a, b) ((((a)+(b)) & 0x8000) ? silk_int16_MAX : ((a)+(b))) +#define silk_ADD_POS_SAT32(a, b) ((((opus_uint32)(a)+(opus_uint32)(b)) & 0x80000000) ? silk_int32_MAX : ((a)+(b))) + +#define silk_LSHIFT8(a, shift) ((opus_int8)((opus_uint8)(a)<<(shift))) /* shift >= 0, shift < 8 */ +#define silk_LSHIFT16(a, shift) ((opus_int16)((opus_uint16)(a)<<(shift))) /* shift >= 0, shift < 16 */ +#define silk_LSHIFT32(a, shift) ((opus_int32)((opus_uint32)(a)<<(shift))) /* shift >= 0, shift < 32 */ +#define silk_LSHIFT64(a, shift) ((opus_int64)((opus_uint64)(a)<<(shift))) /* shift >= 0, shift < 64 */ +#define silk_LSHIFT(a, shift) silk_LSHIFT32(a, shift) /* shift >= 0, shift < 32 */ + +#define silk_RSHIFT8(a, shift) ((a)>>(shift)) /* shift >= 0, shift < 8 */ +#define silk_RSHIFT16(a, shift) ((a)>>(shift)) /* shift >= 0, shift < 16 */ +#define silk_RSHIFT32(a, shift) ((a)>>(shift)) /* shift >= 0, shift < 32 */ +#define silk_RSHIFT64(a, shift) ((a)>>(shift)) /* shift >= 0, shift < 64 */ +#define silk_RSHIFT(a, shift) silk_RSHIFT32(a, shift) /* shift >= 0, shift < 32 */ + +/* saturates before shifting */ +#define silk_LSHIFT_SAT32(a, shift) (silk_LSHIFT32( silk_LIMIT( (a), silk_RSHIFT32( silk_int32_MIN, (shift) ), \ + silk_RSHIFT32( silk_int32_MAX, (shift) ) ), (shift) )) + +#define silk_LSHIFT_ovflw(a, shift) ((opus_int32)((opus_uint32)(a) << (shift))) /* shift >= 0, allowed to overflow */ +#define silk_LSHIFT_uint(a, shift) ((a) << (shift)) /* shift >= 0 */ +#define silk_RSHIFT_uint(a, shift) ((a) >> (shift)) /* shift >= 0 */ + +#define silk_ADD_LSHIFT(a, b, shift) ((a) + silk_LSHIFT((b), (shift))) /* shift >= 0 */ +#define silk_ADD_LSHIFT32(a, b, shift) silk_ADD32((a), silk_LSHIFT32((b), (shift))) /* shift >= 0 */ +#define silk_ADD_LSHIFT_uint(a, b, shift) ((a) + silk_LSHIFT_uint((b), (shift))) /* shift >= 0 */ +#define silk_ADD_RSHIFT(a, b, shift) ((a) + silk_RSHIFT((b), (shift))) /* shift >= 0 */ +#define silk_ADD_RSHIFT32(a, b, shift) silk_ADD32((a), silk_RSHIFT32((b), (shift))) /* shift >= 0 */ +#define silk_ADD_RSHIFT_uint(a, b, shift) ((a) + silk_RSHIFT_uint((b), (shift))) /* shift >= 0 */ +#define silk_SUB_LSHIFT32(a, b, shift) silk_SUB32((a), silk_LSHIFT32((b), (shift))) /* shift >= 0 */ +#define silk_SUB_RSHIFT32(a, b, shift) silk_SUB32((a), silk_RSHIFT32((b), (shift))) /* shift >= 0 */ + +/* Requires that shift > 0 */ +#define silk_RSHIFT_ROUND(a, shift) ((shift) == 1 ? ((a) >> 1) + ((a) & 1) : (((a) >> ((shift) - 1)) + 1) >> 1) +#define silk_RSHIFT_ROUND64(a, shift) ((shift) == 1 ? ((a) >> 1) + ((a) & 1) : (((a) >> ((shift) - 1)) + 1) >> 1) + +/* Number of rightshift required to fit the multiplication */ +#define silk_NSHIFT_MUL_32_32(a, b) ( -(31- (32-silk_CLZ32(silk_abs(a)) + (32-silk_CLZ32(silk_abs(b))))) ) +#define silk_NSHIFT_MUL_16_16(a, b) ( -(15- (16-silk_CLZ16(silk_abs(a)) + (16-silk_CLZ16(silk_abs(b))))) ) + + +#define silk_min(a, b) (((a) < (b)) ? (a) : (b)) +#define silk_max(a, b) (((a) > (b)) ? (a) : (b)) + +/* Macro to convert floating-point constants to fixed-point */ +#define SILK_FIX_CONST( C, Q ) ((opus_int32)((C) * ((opus_int64)1 << (Q)) + 0.5)) + +/* silk_min() versions with typecast in the function call */ +static OPUS_INLINE opus_int silk_min_int(opus_int a, opus_int b) +{ + return (((a) < (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int16 silk_min_16(opus_int16 a, opus_int16 b) +{ + return (((a) < (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int32 silk_min_32(opus_int32 a, opus_int32 b) +{ + return (((a) < (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int64 silk_min_64(opus_int64 a, opus_int64 b) +{ + return (((a) < (b)) ? (a) : (b)); +} + +/* silk_min() versions with typecast in the function call */ +static OPUS_INLINE opus_int silk_max_int(opus_int a, opus_int b) +{ + return (((a) > (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int16 silk_max_16(opus_int16 a, opus_int16 b) +{ + return (((a) > (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int32 silk_max_32(opus_int32 a, opus_int32 b) +{ + return (((a) > (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int64 silk_max_64(opus_int64 a, opus_int64 b) +{ + return (((a) > (b)) ? (a) : (b)); +} + +#define silk_LIMIT( a, limit1, limit2) ((limit1) > (limit2) ? ((a) > (limit1) ? (limit1) : ((a) < (limit2) ? (limit2) : (a))) \ + : ((a) > (limit2) ? (limit2) : ((a) < (limit1) ? (limit1) : (a)))) + +#define silk_LIMIT_int silk_LIMIT +#define silk_LIMIT_16 silk_LIMIT +#define silk_LIMIT_32 silk_LIMIT + +#define silk_abs(a) (((a) > 0) ? (a) : -(a)) /* Be careful, silk_abs returns wrong when input equals to silk_intXX_MIN */ +#define silk_abs_int(a) (((a) ^ ((a) >> (8 * sizeof(a) - 1))) - ((a) >> (8 * sizeof(a) - 1))) +#define silk_abs_int32(a) (((a) ^ ((a) >> 31)) - ((a) >> 31)) +#define silk_abs_int64(a) (((a) > 0) ? (a) : -(a)) + +#define silk_sign(a) ((a) > 0 ? 1 : ( (a) < 0 ? -1 : 0 )) + +/* PSEUDO-RANDOM GENERATOR */ +/* Make sure to store the result as the seed for the next call (also in between */ +/* frames), otherwise result won't be random at all. When only using some of the */ +/* bits, take the most significant bits by right-shifting. */ +#define RAND_MULTIPLIER 196314165 +#define RAND_INCREMENT 907633515 +#define silk_RAND(seed) (silk_MLA_ovflw((RAND_INCREMENT), (seed), (RAND_MULTIPLIER))) + +/* Add some multiplication functions that can be easily mapped to ARM. */ + +/* silk_SMMUL: Signed top word multiply. + ARMv6 2 instruction cycles. + ARMv3M+ 3 instruction cycles. use SMULL and ignore LSB registers.(except xM)*/ +/*#define silk_SMMUL(a32, b32) (opus_int32)silk_RSHIFT(silk_SMLAL(silk_SMULWB((a32), (b32)), (a32), silk_RSHIFT_ROUND((b32), 16)), 16)*/ +/* the following seems faster on x86 */ +#define silk_SMMUL(a32, b32) (opus_int32)silk_RSHIFT64(silk_SMULL((a32), (b32)), 32) + +#if !defined(OVERRIDE_silk_burg_modified) +#define silk_burg_modified(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch) \ + ((void)(arch), silk_burg_modified_c(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch)) +#endif + +#if !defined(OVERRIDE_silk_inner_prod16) +#define silk_inner_prod16(inVec1, inVec2, len, arch) \ + ((void)(arch),silk_inner_prod16_c(inVec1, inVec2, len)) +#endif + +#include "Inlines.h" +#include "MacroCount.h" +#include "MacroDebug.h" + +#ifdef OPUS_ARM_INLINE_ASM +#include "arm/SigProc_FIX_armv4.h" +#endif + +#ifdef OPUS_ARM_INLINE_EDSP +#include "arm/SigProc_FIX_armv5e.h" +#endif + +#if defined(MIPSr1_ASM) +#include "mips/sigproc_fix_mipsr1.h" +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_SIGPROC_FIX_H */ diff --git a/port/third_party/opus/silk/VAD.c b/port/third_party/opus/silk/VAD.c new file mode 100644 index 00000000..d0cda521 --- /dev/null +++ b/port/third_party/opus/silk/VAD.c @@ -0,0 +1,360 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/* Silk VAD noise level estimation */ +# if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE void silk_VAD_GetNoiseLevels( + const opus_int32 pX[ VAD_N_BANDS ], /* I subband energies */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +); +#endif + +/**********************************/ +/* Initialization of the Silk VAD */ +/**********************************/ +opus_int silk_VAD_Init( /* O Return value, 0 if success */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +) +{ + opus_int b, ret = 0; + + /* reset state memory */ + silk_memset( psSilk_VAD, 0, sizeof( silk_VAD_state ) ); + + /* init noise levels */ + /* Initialize array with approx pink noise levels (psd proportional to inverse of frequency) */ + for( b = 0; b < VAD_N_BANDS; b++ ) { + psSilk_VAD->NoiseLevelBias[ b ] = silk_max_32( silk_DIV32_16( VAD_NOISE_LEVELS_BIAS, b + 1 ), 1 ); + } + + /* Initialize state */ + for( b = 0; b < VAD_N_BANDS; b++ ) { + psSilk_VAD->NL[ b ] = silk_MUL( 100, psSilk_VAD->NoiseLevelBias[ b ] ); + psSilk_VAD->inv_NL[ b ] = silk_DIV32( silk_int32_MAX, psSilk_VAD->NL[ b ] ); + } + psSilk_VAD->counter = 15; + + /* init smoothed energy-to-noise ratio*/ + for( b = 0; b < VAD_N_BANDS; b++ ) { + psSilk_VAD->NrgRatioSmth_Q8[ b ] = 100 * 256; /* 100 * 256 --> 20 dB SNR */ + } + + return( ret ); +} + +/* Weighting factors for tilt measure */ +static const opus_int32 tiltWeights[ VAD_N_BANDS ] = { 30000, 6000, -12000, -12000 }; + +/***************************************/ +/* Get the speech activity level in Q8 */ +/***************************************/ +opus_int silk_VAD_GetSA_Q8_c( /* O Return value, 0 if success */ + silk_encoder_state *psEncC, /* I/O Encoder state */ + const opus_int16 pIn[] /* I PCM input */ +) +{ + opus_int SA_Q15, pSNR_dB_Q7, input_tilt; + opus_int decimated_framelength1, decimated_framelength2; + opus_int decimated_framelength; + opus_int dec_subframe_length, dec_subframe_offset, SNR_Q7, i, b, s; + opus_int32 sumSquared, smooth_coef_Q16; + opus_int16 HPstateTmp; + VARDECL( opus_int16, X ); + opus_int32 Xnrg[ VAD_N_BANDS ]; + opus_int32 NrgToNoiseRatio_Q8[ VAD_N_BANDS ]; + opus_int32 speech_nrg, x_tmp; + opus_int X_offset[ VAD_N_BANDS ]; + opus_int ret = 0; + silk_VAD_state *psSilk_VAD = &psEncC->sVAD; + SAVE_STACK; + + /* Safety checks */ + silk_assert( VAD_N_BANDS == 4 ); + celt_assert( MAX_FRAME_LENGTH >= psEncC->frame_length ); + celt_assert( psEncC->frame_length <= 512 ); + celt_assert( psEncC->frame_length == 8 * silk_RSHIFT( psEncC->frame_length, 3 ) ); + + /***********************/ + /* Filter and Decimate */ + /***********************/ + decimated_framelength1 = silk_RSHIFT( psEncC->frame_length, 1 ); + decimated_framelength2 = silk_RSHIFT( psEncC->frame_length, 2 ); + decimated_framelength = silk_RSHIFT( psEncC->frame_length, 3 ); + /* Decimate into 4 bands: + 0 L 3L L 3L 5L + - -- - -- -- + 8 8 2 4 4 + + [0-1 kHz| temp. |1-2 kHz| 2-4 kHz | 4-8 kHz | + + They're arranged to allow the minimal ( frame_length / 4 ) extra + scratch space during the downsampling process */ + X_offset[ 0 ] = 0; + X_offset[ 1 ] = decimated_framelength + decimated_framelength2; + X_offset[ 2 ] = X_offset[ 1 ] + decimated_framelength; + X_offset[ 3 ] = X_offset[ 2 ] + decimated_framelength2; + ALLOC( X, X_offset[ 3 ] + decimated_framelength1, opus_int16 ); + + /* 0-8 kHz to 0-4 kHz and 4-8 kHz */ + silk_ana_filt_bank_1( pIn, &psSilk_VAD->AnaState[ 0 ], + X, &X[ X_offset[ 3 ] ], psEncC->frame_length ); + + /* 0-4 kHz to 0-2 kHz and 2-4 kHz */ + silk_ana_filt_bank_1( X, &psSilk_VAD->AnaState1[ 0 ], + X, &X[ X_offset[ 2 ] ], decimated_framelength1 ); + + /* 0-2 kHz to 0-1 kHz and 1-2 kHz */ + silk_ana_filt_bank_1( X, &psSilk_VAD->AnaState2[ 0 ], + X, &X[ X_offset[ 1 ] ], decimated_framelength2 ); + + /*********************************************/ + /* HP filter on lowest band (differentiator) */ + /*********************************************/ + X[ decimated_framelength - 1 ] = silk_RSHIFT( X[ decimated_framelength - 1 ], 1 ); + HPstateTmp = X[ decimated_framelength - 1 ]; + for( i = decimated_framelength - 1; i > 0; i-- ) { + X[ i - 1 ] = silk_RSHIFT( X[ i - 1 ], 1 ); + X[ i ] -= X[ i - 1 ]; + } + X[ 0 ] -= psSilk_VAD->HPstate; + psSilk_VAD->HPstate = HPstateTmp; + + /*************************************/ + /* Calculate the energy in each band */ + /*************************************/ + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* Find the decimated framelength in the non-uniformly divided bands */ + decimated_framelength = silk_RSHIFT( psEncC->frame_length, silk_min_int( VAD_N_BANDS - b, VAD_N_BANDS - 1 ) ); + + /* Split length into subframe lengths */ + dec_subframe_length = silk_RSHIFT( decimated_framelength, VAD_INTERNAL_SUBFRAMES_LOG2 ); + dec_subframe_offset = 0; + + /* Compute energy per sub-frame */ + /* initialize with summed energy of last subframe */ + Xnrg[ b ] = psSilk_VAD->XnrgSubfr[ b ]; + for( s = 0; s < VAD_INTERNAL_SUBFRAMES; s++ ) { + sumSquared = 0; + for( i = 0; i < dec_subframe_length; i++ ) { + /* The energy will be less than dec_subframe_length * ( silk_int16_MIN / 8 ) ^ 2. */ + /* Therefore we can accumulate with no risk of overflow (unless dec_subframe_length > 128) */ + x_tmp = silk_RSHIFT( + X[ X_offset[ b ] + i + dec_subframe_offset ], 3 ); + sumSquared = silk_SMLABB( sumSquared, x_tmp, x_tmp ); + + /* Safety check */ + silk_assert( sumSquared >= 0 ); + } + + /* Add/saturate summed energy of current subframe */ + if( s < VAD_INTERNAL_SUBFRAMES - 1 ) { + Xnrg[ b ] = silk_ADD_POS_SAT32( Xnrg[ b ], sumSquared ); + } else { + /* Look-ahead subframe */ + Xnrg[ b ] = silk_ADD_POS_SAT32( Xnrg[ b ], silk_RSHIFT( sumSquared, 1 ) ); + } + + dec_subframe_offset += dec_subframe_length; + } + psSilk_VAD->XnrgSubfr[ b ] = sumSquared; + } + + /********************/ + /* Noise estimation */ + /********************/ + silk_VAD_GetNoiseLevels( &Xnrg[ 0 ], psSilk_VAD ); + + /***********************************************/ + /* Signal-plus-noise to noise ratio estimation */ + /***********************************************/ + sumSquared = 0; + input_tilt = 0; + for( b = 0; b < VAD_N_BANDS; b++ ) { + speech_nrg = Xnrg[ b ] - psSilk_VAD->NL[ b ]; + if( speech_nrg > 0 ) { + /* Divide, with sufficient resolution */ + if( ( Xnrg[ b ] & 0xFF800000 ) == 0 ) { + NrgToNoiseRatio_Q8[ b ] = silk_DIV32( silk_LSHIFT( Xnrg[ b ], 8 ), psSilk_VAD->NL[ b ] + 1 ); + } else { + NrgToNoiseRatio_Q8[ b ] = silk_DIV32( Xnrg[ b ], silk_RSHIFT( psSilk_VAD->NL[ b ], 8 ) + 1 ); + } + + /* Convert to log domain */ + SNR_Q7 = silk_lin2log( NrgToNoiseRatio_Q8[ b ] ) - 8 * 128; + + /* Sum-of-squares */ + sumSquared = silk_SMLABB( sumSquared, SNR_Q7, SNR_Q7 ); /* Q14 */ + + /* Tilt measure */ + if( speech_nrg < ( (opus_int32)1 << 20 ) ) { + /* Scale down SNR value for small subband speech energies */ + SNR_Q7 = silk_SMULWB( silk_LSHIFT( silk_SQRT_APPROX( speech_nrg ), 6 ), SNR_Q7 ); + } + input_tilt = silk_SMLAWB( input_tilt, tiltWeights[ b ], SNR_Q7 ); + } else { + NrgToNoiseRatio_Q8[ b ] = 256; + } + } + + /* Mean-of-squares */ + sumSquared = silk_DIV32_16( sumSquared, VAD_N_BANDS ); /* Q14 */ + + /* Root-mean-square approximation, scale to dBs, and write to output pointer */ + pSNR_dB_Q7 = (opus_int16)( 3 * silk_SQRT_APPROX( sumSquared ) ); /* Q7 */ + + /*********************************/ + /* Speech Probability Estimation */ + /*********************************/ + SA_Q15 = silk_sigm_Q15( silk_SMULWB( VAD_SNR_FACTOR_Q16, pSNR_dB_Q7 ) - VAD_NEGATIVE_OFFSET_Q5 ); + + /**************************/ + /* Frequency Tilt Measure */ + /**************************/ + psEncC->input_tilt_Q15 = silk_LSHIFT( silk_sigm_Q15( input_tilt ) - 16384, 1 ); + + /**************************************************/ + /* Scale the sigmoid output based on power levels */ + /**************************************************/ + speech_nrg = 0; + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* Accumulate signal-without-noise energies, higher frequency bands have more weight */ + speech_nrg += ( b + 1 ) * silk_RSHIFT( Xnrg[ b ] - psSilk_VAD->NL[ b ], 4 ); + } + + if( psEncC->frame_length == 20 * psEncC->fs_kHz ) { + speech_nrg = silk_RSHIFT32( speech_nrg, 1 ); + } + /* Power scaling */ + if( speech_nrg <= 0 ) { + SA_Q15 = silk_RSHIFT( SA_Q15, 1 ); + } else if( speech_nrg < 16384 ) { + speech_nrg = silk_LSHIFT32( speech_nrg, 16 ); + + /* square-root */ + speech_nrg = silk_SQRT_APPROX( speech_nrg ); + SA_Q15 = silk_SMULWB( 32768 + speech_nrg, SA_Q15 ); + } + + /* Copy the resulting speech activity in Q8 */ + psEncC->speech_activity_Q8 = silk_min_int( silk_RSHIFT( SA_Q15, 7 ), silk_uint8_MAX ); + + /***********************************/ + /* Energy Level and SNR estimation */ + /***********************************/ + /* Smoothing coefficient */ + smooth_coef_Q16 = silk_SMULWB( VAD_SNR_SMOOTH_COEF_Q18, silk_SMULWB( (opus_int32)SA_Q15, SA_Q15 ) ); + + if( psEncC->frame_length == 10 * psEncC->fs_kHz ) { + smooth_coef_Q16 >>= 1; + } + + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* compute smoothed energy-to-noise ratio per band */ + psSilk_VAD->NrgRatioSmth_Q8[ b ] = silk_SMLAWB( psSilk_VAD->NrgRatioSmth_Q8[ b ], + NrgToNoiseRatio_Q8[ b ] - psSilk_VAD->NrgRatioSmth_Q8[ b ], smooth_coef_Q16 ); + + /* signal to noise ratio in dB per band */ + SNR_Q7 = 3 * ( silk_lin2log( psSilk_VAD->NrgRatioSmth_Q8[b] ) - 8 * 128 ); + /* quality = sigmoid( 0.25 * ( SNR_dB - 16 ) ); */ + psEncC->input_quality_bands_Q15[ b ] = silk_sigm_Q15( silk_RSHIFT( SNR_Q7 - 16 * 128, 4 ) ); + } + + RESTORE_STACK; + return( ret ); +} + +/**************************/ +/* Noise level estimation */ +/**************************/ +# if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE +#endif +void silk_VAD_GetNoiseLevels( + const opus_int32 pX[ VAD_N_BANDS ], /* I subband energies */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +) +{ + opus_int k; + opus_int32 nl, nrg, inv_nrg; + opus_int coef, min_coef; + + /* Initially faster smoothing */ + if( psSilk_VAD->counter < 1000 ) { /* 1000 = 20 sec */ + min_coef = silk_DIV32_16( silk_int16_MAX, silk_RSHIFT( psSilk_VAD->counter, 4 ) + 1 ); + /* Increment frame counter */ + psSilk_VAD->counter++; + } else { + min_coef = 0; + } + + for( k = 0; k < VAD_N_BANDS; k++ ) { + /* Get old noise level estimate for current band */ + nl = psSilk_VAD->NL[ k ]; + silk_assert( nl >= 0 ); + + /* Add bias */ + nrg = silk_ADD_POS_SAT32( pX[ k ], psSilk_VAD->NoiseLevelBias[ k ] ); + silk_assert( nrg > 0 ); + + /* Invert energies */ + inv_nrg = silk_DIV32( silk_int32_MAX, nrg ); + silk_assert( inv_nrg >= 0 ); + + /* Less update when subband energy is high */ + if( nrg > silk_LSHIFT( nl, 3 ) ) { + coef = VAD_NOISE_LEVEL_SMOOTH_COEF_Q16 >> 3; + } else if( nrg < nl ) { + coef = VAD_NOISE_LEVEL_SMOOTH_COEF_Q16; + } else { + coef = silk_SMULWB( silk_SMULWW( inv_nrg, nl ), VAD_NOISE_LEVEL_SMOOTH_COEF_Q16 << 1 ); + } + + /* Initially faster smoothing */ + coef = silk_max_int( coef, min_coef ); + + /* Smooth inverse energies */ + psSilk_VAD->inv_NL[ k ] = silk_SMLAWB( psSilk_VAD->inv_NL[ k ], inv_nrg - psSilk_VAD->inv_NL[ k ], coef ); + silk_assert( psSilk_VAD->inv_NL[ k ] >= 0 ); + + /* Compute noise level by inverting again */ + nl = silk_DIV32( silk_int32_MAX, psSilk_VAD->inv_NL[ k ] ); + silk_assert( nl >= 0 ); + + /* Limit noise levels (guarantee 7 bits of head room) */ + nl = silk_min( nl, 0x00FFFFFF ); + + /* Store as part of state */ + psSilk_VAD->NL[ k ] = nl; + } +} diff --git a/port/third_party/opus/silk/VQ_WMat_EC.c b/port/third_party/opus/silk/VQ_WMat_EC.c new file mode 100644 index 00000000..245a7e4b --- /dev/null +++ b/port/third_party/opus/silk/VQ_WMat_EC.c @@ -0,0 +1,131 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Entropy constrained matrix-weighted VQ, hard-coded to 5-element vectors, for a single input data vector */ +void silk_VQ_WMat_EC_c( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int subfr_len, /* I number of samples per subframe */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + const opus_int L /* I number of vectors in codebook */ +) +{ + opus_int k, gain_tmp_Q7; + const opus_int8 *cb_row_Q7; + opus_int32 neg_xX_Q24[ 5 ]; + opus_int32 sum1_Q15, sum2_Q24; + opus_int32 bits_res_Q8, bits_tot_Q8; + + /* Negate and convert to new Q domain */ + neg_xX_Q24[ 0 ] = -silk_LSHIFT32( xX_Q17[ 0 ], 7 ); + neg_xX_Q24[ 1 ] = -silk_LSHIFT32( xX_Q17[ 1 ], 7 ); + neg_xX_Q24[ 2 ] = -silk_LSHIFT32( xX_Q17[ 2 ], 7 ); + neg_xX_Q24[ 3 ] = -silk_LSHIFT32( xX_Q17[ 3 ], 7 ); + neg_xX_Q24[ 4 ] = -silk_LSHIFT32( xX_Q17[ 4 ], 7 ); + + /* Loop over codebook */ + *rate_dist_Q8 = silk_int32_MAX; + *res_nrg_Q15 = silk_int32_MAX; + cb_row_Q7 = cb_Q7; + /* If things go really bad, at least *ind is set to something safe. */ + *ind = 0; + for( k = 0; k < L; k++ ) { + opus_int32 penalty; + gain_tmp_Q7 = cb_gain_Q7[k]; + /* Weighted rate */ + /* Quantization error: 1 - 2 * xX * cb + cb' * XX * cb */ + sum1_Q15 = SILK_FIX_CONST( 1.001, 15 ); + + /* Penalty for too large gain */ + penalty = silk_LSHIFT32( silk_max( silk_SUB32( gain_tmp_Q7, max_gain_Q7 ), 0 ), 11 ); + + /* first row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 0 ], XX_Q17[ 1 ], cb_row_Q7[ 1 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 2 ], cb_row_Q7[ 2 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 3 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 4 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 0 ], cb_row_Q7[ 0 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 0 ] ); + + /* second row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 1 ], XX_Q17[ 7 ], cb_row_Q7[ 2 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 8 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 9 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 6 ], cb_row_Q7[ 1 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 1 ] ); + + /* third row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 2 ], XX_Q17[ 13 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 14 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 12 ], cb_row_Q7[ 2 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 2 ] ); + + /* fourth row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 3 ], XX_Q17[ 19 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 18 ], cb_row_Q7[ 3 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 3 ] ); + + /* last row of XX_Q17 */ + sum2_Q24 = silk_LSHIFT32( neg_xX_Q24[ 4 ], 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 24 ], cb_row_Q7[ 4 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 4 ] ); + + /* find best */ + if( sum1_Q15 >= 0 ) { + /* Translate residual energy to bits using high-rate assumption (6 dB ==> 1 bit/sample) */ + bits_res_Q8 = silk_SMULBB( subfr_len, silk_lin2log( sum1_Q15 + penalty) - (15 << 7) ); + /* In the following line we reduce the codelength component by half ("-1"); seems to slightly improve quality */ + bits_tot_Q8 = silk_ADD_LSHIFT32( bits_res_Q8, cl_Q5[ k ], 3-1 ); + if( bits_tot_Q8 <= *rate_dist_Q8 ) { + *rate_dist_Q8 = bits_tot_Q8; + *res_nrg_Q15 = sum1_Q15 + penalty; + *ind = (opus_int8)k; + *gain_Q7 = gain_tmp_Q7; + } + } + + /* Go to next cbk vector */ + cb_row_Q7 += LTP_ORDER; + } +} diff --git a/port/third_party/opus/silk/ana_filt_bank_1.c b/port/third_party/opus/silk/ana_filt_bank_1.c new file mode 100644 index 00000000..24cfb03f --- /dev/null +++ b/port/third_party/opus/silk/ana_filt_bank_1.c @@ -0,0 +1,74 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Coefficients for 2-band filter bank based on first-order allpass filters */ +static opus_int16 A_fb1_20 = 5394 << 1; +static opus_int16 A_fb1_21 = -24290; /* (opus_int16)(20623 << 1) */ + +/* Split signal into two decimated bands using first-order allpass filters */ +void silk_ana_filt_bank_1( + const opus_int16 *in, /* I Input signal [N] */ + opus_int32 *S, /* I/O State vector [2] */ + opus_int16 *outL, /* O Low band [N/2] */ + opus_int16 *outH, /* O High band [N/2] */ + const opus_int32 N /* I Number of input samples */ +) +{ + opus_int k, N2 = silk_RSHIFT( N, 1 ); + opus_int32 in32, X, Y, out_1, out_2; + + /* Internal variables and state are in Q10 format */ + for( k = 0; k < N2; k++ ) { + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ 2 * k ], 10 ); + + /* All-pass section for even input sample */ + Y = silk_SUB32( in32, S[ 0 ] ); + X = silk_SMLAWB( Y, Y, A_fb1_21 ); + out_1 = silk_ADD32( S[ 0 ], X ); + S[ 0 ] = silk_ADD32( in32, X ); + + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ 2 * k + 1 ], 10 ); + + /* All-pass section for odd input sample, and add to output of previous section */ + Y = silk_SUB32( in32, S[ 1 ] ); + X = silk_SMULWB( Y, A_fb1_20 ); + out_2 = silk_ADD32( S[ 1 ], X ); + S[ 1 ] = silk_ADD32( in32, X ); + + /* Add/subtract, convert back to int16 and store to output */ + outL[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_ADD32( out_2, out_1 ), 11 ) ); + outH[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SUB32( out_2, out_1 ), 11 ) ); + } +} diff --git a/port/third_party/opus/silk/biquad_alt.c b/port/third_party/opus/silk/biquad_alt.c new file mode 100644 index 00000000..54566a43 --- /dev/null +++ b/port/third_party/opus/silk/biquad_alt.c @@ -0,0 +1,121 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +/* * + * silk_biquad_alt.c * + * * + * Second order ARMA filter * + * Can handle slowly varying filter coefficients * + * */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Second order ARMA filter, alternative implementation */ +void silk_biquad_alt_stride1( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [2] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +) +{ + /* DIRECT FORM II TRANSPOSED (uses 2 element state vector) */ + opus_int k; + opus_int32 inval, A0_U_Q28, A0_L_Q28, A1_U_Q28, A1_L_Q28, out32_Q14; + + /* Negate A_Q28 values and split in two parts */ + A0_L_Q28 = ( -A_Q28[ 0 ] ) & 0x00003FFF; /* lower part */ + A0_U_Q28 = silk_RSHIFT( -A_Q28[ 0 ], 14 ); /* upper part */ + A1_L_Q28 = ( -A_Q28[ 1 ] ) & 0x00003FFF; /* lower part */ + A1_U_Q28 = silk_RSHIFT( -A_Q28[ 1 ], 14 ); /* upper part */ + + for( k = 0; k < len; k++ ) { + /* S[ 0 ], S[ 1 ]: Q12 */ + inval = in[ k ]; + out32_Q14 = silk_LSHIFT( silk_SMLAWB( S[ 0 ], B_Q28[ 0 ], inval ), 2 ); + + S[ 0 ] = S[1] + silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14, A0_L_Q28 ), 14 ); + S[ 0 ] = silk_SMLAWB( S[ 0 ], out32_Q14, A0_U_Q28 ); + S[ 0 ] = silk_SMLAWB( S[ 0 ], B_Q28[ 1 ], inval); + + S[ 1 ] = silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14, A1_L_Q28 ), 14 ); + S[ 1 ] = silk_SMLAWB( S[ 1 ], out32_Q14, A1_U_Q28 ); + S[ 1 ] = silk_SMLAWB( S[ 1 ], B_Q28[ 2 ], inval ); + + /* Scale back to Q0 and saturate */ + out[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14 + (1<<14) - 1, 14 ) ); + } +} + +void silk_biquad_alt_stride2_c( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [4] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +) +{ + /* DIRECT FORM II TRANSPOSED (uses 2 element state vector) */ + opus_int k; + opus_int32 A0_U_Q28, A0_L_Q28, A1_U_Q28, A1_L_Q28, out32_Q14[ 2 ]; + + /* Negate A_Q28 values and split in two parts */ + A0_L_Q28 = ( -A_Q28[ 0 ] ) & 0x00003FFF; /* lower part */ + A0_U_Q28 = silk_RSHIFT( -A_Q28[ 0 ], 14 ); /* upper part */ + A1_L_Q28 = ( -A_Q28[ 1 ] ) & 0x00003FFF; /* lower part */ + A1_U_Q28 = silk_RSHIFT( -A_Q28[ 1 ], 14 ); /* upper part */ + + for( k = 0; k < len; k++ ) { + /* S[ 0 ], S[ 1 ], S[ 2 ], S[ 3 ]: Q12 */ + out32_Q14[ 0 ] = silk_LSHIFT( silk_SMLAWB( S[ 0 ], B_Q28[ 0 ], in[ 2 * k + 0 ] ), 2 ); + out32_Q14[ 1 ] = silk_LSHIFT( silk_SMLAWB( S[ 2 ], B_Q28[ 0 ], in[ 2 * k + 1 ] ), 2 ); + + S[ 0 ] = S[ 1 ] + silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14[ 0 ], A0_L_Q28 ), 14 ); + S[ 2 ] = S[ 3 ] + silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14[ 1 ], A0_L_Q28 ), 14 ); + S[ 0 ] = silk_SMLAWB( S[ 0 ], out32_Q14[ 0 ], A0_U_Q28 ); + S[ 2 ] = silk_SMLAWB( S[ 2 ], out32_Q14[ 1 ], A0_U_Q28 ); + S[ 0 ] = silk_SMLAWB( S[ 0 ], B_Q28[ 1 ], in[ 2 * k + 0 ] ); + S[ 2 ] = silk_SMLAWB( S[ 2 ], B_Q28[ 1 ], in[ 2 * k + 1 ] ); + + S[ 1 ] = silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14[ 0 ], A1_L_Q28 ), 14 ); + S[ 3 ] = silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14[ 1 ], A1_L_Q28 ), 14 ); + S[ 1 ] = silk_SMLAWB( S[ 1 ], out32_Q14[ 0 ], A1_U_Q28 ); + S[ 3 ] = silk_SMLAWB( S[ 3 ], out32_Q14[ 1 ], A1_U_Q28 ); + S[ 1 ] = silk_SMLAWB( S[ 1 ], B_Q28[ 2 ], in[ 2 * k + 0 ] ); + S[ 3 ] = silk_SMLAWB( S[ 3 ], B_Q28[ 2 ], in[ 2 * k + 1 ] ); + + /* Scale back to Q0 and saturate */ + out[ 2 * k + 0 ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14[ 0 ] + (1<<14) - 1, 14 ) ); + out[ 2 * k + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14[ 1 ] + (1<<14) - 1, 14 ) ); + } +} diff --git a/port/third_party/opus/silk/bwexpander.c b/port/third_party/opus/silk/bwexpander.c new file mode 100644 index 00000000..afa97907 --- /dev/null +++ b/port/third_party/opus/silk/bwexpander.c @@ -0,0 +1,51 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Chirp (bandwidth expand) LP AR filter */ +void silk_bwexpander( + opus_int16 *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I Length of ar */ + opus_int32 chirp_Q16 /* I Chirp factor (typically in the range 0 to 1) */ +) +{ + opus_int i; + opus_int32 chirp_minus_one_Q16 = chirp_Q16 - 65536; + + /* NB: Dont use silk_SMULWB, instead of silk_RSHIFT_ROUND( silk_MUL(), 16 ), below. */ + /* Bias in silk_SMULWB can lead to unstable filters */ + for( i = 0; i < d - 1; i++ ) { + ar[ i ] = (opus_int16)silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, ar[ i ] ), 16 ); + chirp_Q16 += silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, chirp_minus_one_Q16 ), 16 ); + } + ar[ d - 1 ] = (opus_int16)silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, ar[ d - 1 ] ), 16 ); +} diff --git a/port/third_party/opus/silk/bwexpander_32.c b/port/third_party/opus/silk/bwexpander_32.c new file mode 100644 index 00000000..0f32b9df --- /dev/null +++ b/port/third_party/opus/silk/bwexpander_32.c @@ -0,0 +1,51 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Chirp (bandwidth expand) LP AR filter. + This logic is reused in _celt_lpc(). Any bug fixes should also be applied there. */ +void silk_bwexpander_32( + opus_int32 *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I Length of ar */ + opus_int32 chirp_Q16 /* I Chirp factor in Q16 */ +) +{ + opus_int i; + opus_int32 chirp_minus_one_Q16 = chirp_Q16 - 65536; + + for( i = 0; i < d - 1; i++ ) { + ar[ i ] = silk_SMULWW( chirp_Q16, ar[ i ] ); + chirp_Q16 += silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, chirp_minus_one_Q16 ), 16 ); + } + ar[ d - 1 ] = silk_SMULWW( chirp_Q16, ar[ d - 1 ] ); +} + diff --git a/port/third_party/opus/silk/check_control_input.c b/port/third_party/opus/silk/check_control_input.c new file mode 100644 index 00000000..739fb01f --- /dev/null +++ b/port/third_party/opus/silk/check_control_input.c @@ -0,0 +1,106 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "control.h" +#include "errors.h" + +/* Check encoder control struct */ +opus_int check_control_input( + silk_EncControlStruct *encControl /* I Control structure */ +) +{ + celt_assert( encControl != NULL ); + + if( ( ( encControl->API_sampleRate != 8000 ) && + ( encControl->API_sampleRate != 12000 ) && + ( encControl->API_sampleRate != 16000 ) && + ( encControl->API_sampleRate != 24000 ) && + ( encControl->API_sampleRate != 32000 ) && + ( encControl->API_sampleRate != 44100 ) && + ( encControl->API_sampleRate != 48000 ) ) || + ( ( encControl->desiredInternalSampleRate != 8000 ) && + ( encControl->desiredInternalSampleRate != 12000 ) && + ( encControl->desiredInternalSampleRate != 16000 ) ) || + ( ( encControl->maxInternalSampleRate != 8000 ) && + ( encControl->maxInternalSampleRate != 12000 ) && + ( encControl->maxInternalSampleRate != 16000 ) ) || + ( ( encControl->minInternalSampleRate != 8000 ) && + ( encControl->minInternalSampleRate != 12000 ) && + ( encControl->minInternalSampleRate != 16000 ) ) || + ( encControl->minInternalSampleRate > encControl->desiredInternalSampleRate ) || + ( encControl->maxInternalSampleRate < encControl->desiredInternalSampleRate ) || + ( encControl->minInternalSampleRate > encControl->maxInternalSampleRate ) ) { + celt_assert( 0 ); + return SILK_ENC_FS_NOT_SUPPORTED; + } + if( encControl->payloadSize_ms != 10 && + encControl->payloadSize_ms != 20 && + encControl->payloadSize_ms != 40 && + encControl->payloadSize_ms != 60 ) { + celt_assert( 0 ); + return SILK_ENC_PACKET_SIZE_NOT_SUPPORTED; + } + if( encControl->packetLossPercentage < 0 || encControl->packetLossPercentage > 100 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_LOSS_RATE; + } + if( encControl->useDTX < 0 || encControl->useDTX > 1 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_DTX_SETTING; + } + if( encControl->useCBR < 0 || encControl->useCBR > 1 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_CBR_SETTING; + } + if( encControl->useInBandFEC < 0 || encControl->useInBandFEC > 1 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_INBAND_FEC_SETTING; + } + if( encControl->nChannelsAPI < 1 || encControl->nChannelsAPI > ENCODER_NUM_CHANNELS ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_NUMBER_OF_CHANNELS_ERROR; + } + if( encControl->nChannelsInternal < 1 || encControl->nChannelsInternal > ENCODER_NUM_CHANNELS ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_NUMBER_OF_CHANNELS_ERROR; + } + if( encControl->nChannelsInternal > encControl->nChannelsAPI ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_NUMBER_OF_CHANNELS_ERROR; + } + if( encControl->complexity < 0 || encControl->complexity > 10 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_COMPLEXITY_SETTING; + } + + return SILK_NO_ERROR; +} diff --git a/port/third_party/opus/silk/code_signs.c b/port/third_party/opus/silk/code_signs.c new file mode 100644 index 00000000..dfd1dca9 --- /dev/null +++ b/port/third_party/opus/silk/code_signs.c @@ -0,0 +1,115 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/*#define silk_enc_map(a) ((a) > 0 ? 1 : 0)*/ +/*#define silk_dec_map(a) ((a) > 0 ? 1 : -1)*/ +/* shifting avoids if-statement */ +#define silk_enc_map(a) ( silk_RSHIFT( (a), 15 ) + 1 ) +#define silk_dec_map(a) ( silk_LSHIFT( (a), 1 ) - 1 ) + +/* Encodes signs of excitation */ +void silk_encode_signs( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + const opus_int8 pulses[], /* I pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ +) +{ + opus_int i, j, p; + opus_uint8 icdf[ 2 ]; + const opus_int8 *q_ptr; + const opus_uint8 *icdf_ptr; + + icdf[ 1 ] = 0; + q_ptr = pulses; + i = silk_SMULBB( 7, silk_ADD_LSHIFT( quantOffsetType, signalType, 1 ) ); + icdf_ptr = &silk_sign_iCDF[ i ]; + length = silk_RSHIFT( length + SHELL_CODEC_FRAME_LENGTH/2, LOG2_SHELL_CODEC_FRAME_LENGTH ); + for( i = 0; i < length; i++ ) { + p = sum_pulses[ i ]; + if( p > 0 ) { + icdf[ 0 ] = icdf_ptr[ silk_min( p & 0x1F, 6 ) ]; + for( j = 0; j < SHELL_CODEC_FRAME_LENGTH; j++ ) { + if( q_ptr[ j ] != 0 ) { + ec_enc_icdf( psRangeEnc, silk_enc_map( q_ptr[ j ]), icdf, 8 ); + } + } + } + q_ptr += SHELL_CODEC_FRAME_LENGTH; + } +} + +/* Decodes signs of excitation */ +void silk_decode_signs( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* I/O pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ +) +{ + opus_int i, j, p; + opus_uint8 icdf[ 2 ]; + opus_int16 *q_ptr; + const opus_uint8 *icdf_ptr; + + icdf[ 1 ] = 0; + q_ptr = pulses; + i = silk_SMULBB( 7, silk_ADD_LSHIFT( quantOffsetType, signalType, 1 ) ); + icdf_ptr = &silk_sign_iCDF[ i ]; + length = silk_RSHIFT( length + SHELL_CODEC_FRAME_LENGTH/2, LOG2_SHELL_CODEC_FRAME_LENGTH ); + for( i = 0; i < length; i++ ) { + p = sum_pulses[ i ]; + if( p > 0 ) { + icdf[ 0 ] = icdf_ptr[ silk_min( p & 0x1F, 6 ) ]; + for( j = 0; j < SHELL_CODEC_FRAME_LENGTH; j++ ) { + if( q_ptr[ j ] > 0 ) { + /* attach sign */ +#if 0 + /* conditional implementation */ + if( ec_dec_icdf( psRangeDec, icdf, 8 ) == 0 ) { + q_ptr[ j ] = -q_ptr[ j ]; + } +#else + /* implementation with shift, subtraction, multiplication */ + q_ptr[ j ] *= silk_dec_map( ec_dec_icdf( psRangeDec, icdf, 8 ) ); +#endif + } + } + } + q_ptr += SHELL_CODEC_FRAME_LENGTH; + } +} diff --git a/port/third_party/opus/silk/control.h b/port/third_party/opus/silk/control.h new file mode 100644 index 00000000..f5633e62 --- /dev/null +++ b/port/third_party/opus/silk/control.h @@ -0,0 +1,161 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_CONTROL_H +#define SILK_CONTROL_H + +#include "typedef.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Decoder API flags */ +#define FLAG_DECODE_NORMAL 0 +#define FLAG_PACKET_LOST 1 +#define FLAG_DECODE_LBRR 2 + +/***********************************************/ +/* Structure for controlling encoder operation */ +/***********************************************/ +typedef struct { + /* I: Number of channels; 1/2 */ + opus_int32 nChannelsAPI; + + /* I: Number of channels; 1/2 */ + opus_int32 nChannelsInternal; + + /* I: Input signal sampling rate in Hertz; 8000/12000/16000/24000/32000/44100/48000 */ + opus_int32 API_sampleRate; + + /* I: Maximum internal sampling rate in Hertz; 8000/12000/16000 */ + opus_int32 maxInternalSampleRate; + + /* I: Minimum internal sampling rate in Hertz; 8000/12000/16000 */ + opus_int32 minInternalSampleRate; + + /* I: Soft request for internal sampling rate in Hertz; 8000/12000/16000 */ + opus_int32 desiredInternalSampleRate; + + /* I: Number of samples per packet in milliseconds; 10/20/40/60 */ + opus_int payloadSize_ms; + + /* I: Bitrate during active speech in bits/second; internally limited */ + opus_int32 bitRate; + + /* I: Uplink packet loss in percent (0-100) */ + opus_int packetLossPercentage; + + /* I: Complexity mode; 0 is lowest, 10 is highest complexity */ + opus_int complexity; + + /* I: Flag to enable in-band Forward Error Correction (FEC); 0/1 */ + opus_int useInBandFEC; + + /* I: Flag to enable in-band Deep REDundancy (DRED); 0/1 */ + opus_int useDRED; + + /* I: Flag to actually code in-band Forward Error Correction (FEC) in the current packet; 0/1 */ + opus_int LBRR_coded; + + /* I: Flag to enable discontinuous transmission (DTX); 0/1 */ + opus_int useDTX; + + /* I: Flag to use constant bitrate */ + opus_int useCBR; + + /* I: Maximum number of bits allowed for the frame */ + opus_int maxBits; + + /* I: Causes a smooth downmix to mono */ + opus_int toMono; + + /* I: Opus encoder is allowing us to switch bandwidth */ + opus_int opusCanSwitch; + + /* I: Make frames as independent as possible (but still use LPC) */ + opus_int reducedDependency; + + /* O: Internal sampling rate used, in Hertz; 8000/12000/16000 */ + opus_int32 internalSampleRate; + + /* O: Flag that bandwidth switching is allowed (because low voice activity) */ + opus_int allowBandwidthSwitch; + + /* O: Flag that SILK runs in WB mode without variable LP filter (use for switching between WB/SWB/FB) */ + opus_int inWBmodeWithoutVariableLP; + + /* O: Stereo width */ + opus_int stereoWidth_Q14; + + /* O: Tells the Opus encoder we're ready to switch */ + opus_int switchReady; + + /* O: SILK Signal type */ + opus_int signalType; + + /* O: SILK offset (dithering) */ + opus_int offset; +} silk_EncControlStruct; + +/**************************************************************************/ +/* Structure for controlling decoder operation and reading decoder status */ +/**************************************************************************/ +typedef struct { + /* I: Number of channels; 1/2 */ + opus_int32 nChannelsAPI; + + /* I: Number of channels; 1/2 */ + opus_int32 nChannelsInternal; + + /* I: Output signal sampling rate in Hertz; 8000/12000/16000/24000/32000/44100/48000 */ + opus_int32 API_sampleRate; + + /* I: Internal sampling rate used, in Hertz; 8000/12000/16000 */ + opus_int32 internalSampleRate; + + /* I: Number of samples per packet in milliseconds; 10/20/40/60 */ + opus_int payloadSize_ms; + + /* O: Pitch lag of previous frame (0 if unvoiced), measured in samples at 48 kHz */ + opus_int prevPitchLag; + + /* I: Enable Deep PLC */ + opus_int enable_deep_plc; + +#ifdef ENABLE_OSCE + /* I: OSCE method */ + opus_int osce_method; +#endif +} silk_DecControlStruct; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/silk/control_SNR.c b/port/third_party/opus/silk/control_SNR.c new file mode 100644 index 00000000..9a6db275 --- /dev/null +++ b/port/third_party/opus/silk/control_SNR.c @@ -0,0 +1,113 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "tuning_parameters.h" + +/* These tables hold SNR values divided by 21 (so they fit in 8 bits) + for different target bitrates spaced at 400 bps interval. The first + 10 values are omitted (0-4 kb/s) because they're all zeros. + These tables were obtained by running different SNRs through the + encoder and measuring the active bitrate. */ +static const unsigned char silk_TargetRate_NB_21[117 - 10] = { + 0, 15, 39, 52, 61, 68, + 74, 79, 84, 88, 92, 95, 99,102,105,108,111,114,117,119,122,124, + 126,129,131,133,135,137,139,142,143,145,147,149,151,153,155,157, + 158,160,162,163,165,167,168,170,171,173,174,176,177,179,180,182, + 183,185,186,187,189,190,192,193,194,196,197,199,200,201,203,204, + 205,207,208,209,211,212,213,215,216,217,219,220,221,223,224,225, + 227,228,230,231,232,234,235,236,238,239,241,242,243,245,246,248, + 249,250,252,253,255 +}; + +static const unsigned char silk_TargetRate_MB_21[165 - 10] = { + 0, 0, 28, 43, 52, 59, + 65, 70, 74, 78, 81, 85, 87, 90, 93, 95, 98,100,102,105,107,109, + 111,113,115,116,118,120,122,123,125,127,128,130,131,133,134,136, + 137,138,140,141,143,144,145,147,148,149,151,152,153,154,156,157, + 158,159,160,162,163,164,165,166,167,168,169,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,188,189,190, + 191,192,193,194,195,196,197,198,199,200,201,202,203,203,204,205, + 206,207,208,209,210,211,212,213,214,214,215,216,217,218,219,220, + 221,222,223,224,224,225,226,227,228,229,230,231,232,233,234,235, + 236,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250, + 251,252,253,254,255 +}; + +static const unsigned char silk_TargetRate_WB_21[201 - 10] = { + 0, 0, 0, 8, 29, 41, + 49, 56, 62, 66, 70, 74, 77, 80, 83, 86, 88, 91, 93, 95, 97, 99, + 101,103,105,107,108,110,112,113,115,116,118,119,121,122,123,125, + 126,127,129,130,131,132,134,135,136,137,138,140,141,142,143,144, + 145,146,147,148,149,150,151,152,153,154,156,157,158,159,159,160, + 161,162,163,164,165,166,167,168,169,170,171,171,172,173,174,175, + 176,177,177,178,179,180,181,181,182,183,184,185,185,186,187,188, + 189,189,190,191,192,192,193,194,195,195,196,197,198,198,199,200, + 200,201,202,203,203,204,205,206,206,207,208,209,209,210,211,211, + 212,213,214,214,215,216,216,217,218,219,219,220,221,221,222,223, + 224,224,225,226,226,227,228,229,229,230,231,232,232,233,234,234, + 235,236,237,237,238,239,240,240,241,242,243,243,244,245,246,246, + 247,248,249,249,250,251,252,253,255 +}; + +/* Control SNR of redidual quantizer */ +opus_int silk_control_SNR( + silk_encoder_state *psEncC, /* I/O Pointer to Silk encoder state */ + opus_int32 TargetRate_bps /* I Target max bitrate (bps) */ +) +{ + int id; + int bound; + const unsigned char *snr_table; + + psEncC->TargetRate_bps = TargetRate_bps; + if( psEncC->nb_subfr == 2 ) { + TargetRate_bps -= 2000 + psEncC->fs_kHz/16; + } + if( psEncC->fs_kHz == 8 ) { + bound = sizeof(silk_TargetRate_NB_21); + snr_table = silk_TargetRate_NB_21; + } else if( psEncC->fs_kHz == 12 ) { + bound = sizeof(silk_TargetRate_MB_21); + snr_table = silk_TargetRate_MB_21; + } else { + bound = sizeof(silk_TargetRate_WB_21); + snr_table = silk_TargetRate_WB_21; + } + id = (TargetRate_bps+200)/400; + id = silk_min(id - 10, bound-1); + if( id <= 0 ) { + psEncC->SNR_dB_Q7 = 0; + } else { + psEncC->SNR_dB_Q7 = snr_table[id]*21; + } + return SILK_NO_ERROR; +} diff --git a/port/third_party/opus/silk/control_audio_bandwidth.c b/port/third_party/opus/silk/control_audio_bandwidth.c new file mode 100644 index 00000000..f6d22d83 --- /dev/null +++ b/port/third_party/opus/silk/control_audio_bandwidth.c @@ -0,0 +1,132 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "tuning_parameters.h" + +/* Control internal sampling rate */ +opus_int silk_control_audio_bandwidth( + silk_encoder_state *psEncC, /* I/O Pointer to Silk encoder state */ + silk_EncControlStruct *encControl /* I Control structure */ +) +{ + opus_int fs_kHz; + opus_int orig_kHz; + opus_int32 fs_Hz; + + orig_kHz = psEncC->fs_kHz; + /* Handle a bandwidth-switching reset where we need to be aware what the last sampling rate was. */ + if( orig_kHz == 0 ) { + orig_kHz = psEncC->sLP.saved_fs_kHz; + } + fs_kHz = orig_kHz; + fs_Hz = silk_SMULBB( fs_kHz, 1000 ); + if( fs_Hz == 0 ) { + /* Encoder has just been initialized */ + fs_Hz = silk_min( psEncC->desiredInternal_fs_Hz, psEncC->API_fs_Hz ); + fs_kHz = silk_DIV32_16( fs_Hz, 1000 ); + } else if( fs_Hz > psEncC->API_fs_Hz || fs_Hz > psEncC->maxInternal_fs_Hz || fs_Hz < psEncC->minInternal_fs_Hz ) { + /* Make sure internal rate is not higher than external rate or maximum allowed, or lower than minimum allowed */ + fs_Hz = psEncC->API_fs_Hz; + fs_Hz = silk_min( fs_Hz, psEncC->maxInternal_fs_Hz ); + fs_Hz = silk_max( fs_Hz, psEncC->minInternal_fs_Hz ); + fs_kHz = silk_DIV32_16( fs_Hz, 1000 ); + } else { + /* State machine for the internal sampling rate switching */ + if( psEncC->sLP.transition_frame_no >= TRANSITION_FRAMES ) { + /* Stop transition phase */ + psEncC->sLP.mode = 0; + } + if( psEncC->allow_bandwidth_switch || encControl->opusCanSwitch ) { + /* Check if we should switch down */ + if( silk_SMULBB( orig_kHz, 1000 ) > psEncC->desiredInternal_fs_Hz ) + { + /* Switch down */ + if( psEncC->sLP.mode == 0 ) { + /* New transition */ + psEncC->sLP.transition_frame_no = TRANSITION_FRAMES; + + /* Reset transition filter state */ + silk_memset( psEncC->sLP.In_LP_State, 0, sizeof( psEncC->sLP.In_LP_State ) ); + } + if( encControl->opusCanSwitch ) { + /* Stop transition phase */ + psEncC->sLP.mode = 0; + + /* Switch to a lower sample frequency */ + fs_kHz = orig_kHz == 16 ? 12 : 8; + } else { + if( psEncC->sLP.transition_frame_no <= 0 ) { + encControl->switchReady = 1; + /* Make room for redundancy */ + encControl->maxBits -= encControl->maxBits * 5 / ( encControl->payloadSize_ms + 5 ); + } else { + /* Direction: down (at double speed) */ + psEncC->sLP.mode = -2; + } + } + } + else + /* Check if we should switch up */ + if( silk_SMULBB( orig_kHz, 1000 ) < psEncC->desiredInternal_fs_Hz ) + { + /* Switch up */ + if( encControl->opusCanSwitch ) { + /* Switch to a higher sample frequency */ + fs_kHz = orig_kHz == 8 ? 12 : 16; + + /* New transition */ + psEncC->sLP.transition_frame_no = 0; + + /* Reset transition filter state */ + silk_memset( psEncC->sLP.In_LP_State, 0, sizeof( psEncC->sLP.In_LP_State ) ); + + /* Direction: up */ + psEncC->sLP.mode = 1; + } else { + if( psEncC->sLP.mode == 0 ) { + encControl->switchReady = 1; + /* Make room for redundancy */ + encControl->maxBits -= encControl->maxBits * 5 / ( encControl->payloadSize_ms + 5 ); + } else { + /* Direction: up */ + psEncC->sLP.mode = 1; + } + } + } else { + if (psEncC->sLP.mode<0) + psEncC->sLP.mode = 1; + } + } + } + + return fs_kHz; +} diff --git a/port/third_party/opus/silk/control_codec.c b/port/third_party/opus/silk/control_codec.c new file mode 100644 index 00000000..784ffe66 --- /dev/null +++ b/port/third_party/opus/silk/control_codec.c @@ -0,0 +1,423 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef FIXED_POINT +#include "main_FIX.h" +#define silk_encoder_state_Fxx silk_encoder_state_FIX +#else +#include "main_FLP.h" +#define silk_encoder_state_Fxx silk_encoder_state_FLP +#endif +#include "stack_alloc.h" +#include "tuning_parameters.h" +#include "pitch_est_defines.h" + +static opus_int silk_setup_resamplers( + silk_encoder_state_Fxx *psEnc, /* I/O */ + opus_int fs_kHz /* I */ +); + +static opus_int silk_setup_fs( + silk_encoder_state_Fxx *psEnc, /* I/O */ + opus_int fs_kHz, /* I */ + opus_int PacketSize_ms /* I */ +); + +static opus_int silk_setup_complexity( + silk_encoder_state *psEncC, /* I/O */ + opus_int Complexity /* I */ +); + +static OPUS_INLINE opus_int silk_setup_LBRR( + silk_encoder_state *psEncC, /* I/O */ + const silk_EncControlStruct *encControl /* I */ +); + + +/* Control encoder */ +opus_int silk_control_encoder( + silk_encoder_state_Fxx *psEnc, /* I/O Pointer to Silk encoder state */ + silk_EncControlStruct *encControl, /* I Control structure */ + const opus_int allow_bw_switch, /* I Flag to allow switching audio bandwidth */ + const opus_int channelNb, /* I Channel number */ + const opus_int force_fs_kHz +) +{ + opus_int fs_kHz, ret = 0; + + psEnc->sCmn.useDTX = encControl->useDTX; + psEnc->sCmn.useCBR = encControl->useCBR; + psEnc->sCmn.API_fs_Hz = encControl->API_sampleRate; + psEnc->sCmn.maxInternal_fs_Hz = encControl->maxInternalSampleRate; + psEnc->sCmn.minInternal_fs_Hz = encControl->minInternalSampleRate; + psEnc->sCmn.desiredInternal_fs_Hz = encControl->desiredInternalSampleRate; + psEnc->sCmn.useInBandFEC = encControl->useInBandFEC; + psEnc->sCmn.nChannelsAPI = encControl->nChannelsAPI; + psEnc->sCmn.nChannelsInternal = encControl->nChannelsInternal; + psEnc->sCmn.allow_bandwidth_switch = allow_bw_switch; + psEnc->sCmn.channelNb = channelNb; + + if( psEnc->sCmn.controlled_since_last_payload != 0 && psEnc->sCmn.prefillFlag == 0 ) { + if( psEnc->sCmn.API_fs_Hz != psEnc->sCmn.prev_API_fs_Hz && psEnc->sCmn.fs_kHz > 0 ) { + /* Change in API sampling rate in the middle of encoding a packet */ + ret += silk_setup_resamplers( psEnc, psEnc->sCmn.fs_kHz ); + } + return ret; + } + + /* Beyond this point we know that there are no previously coded frames in the payload buffer */ + + /********************************************/ + /* Determine internal sampling rate */ + /********************************************/ + fs_kHz = silk_control_audio_bandwidth( &psEnc->sCmn, encControl ); + if( force_fs_kHz ) { + fs_kHz = force_fs_kHz; + } + /********************************************/ + /* Prepare resampler and buffered data */ + /********************************************/ + ret += silk_setup_resamplers( psEnc, fs_kHz ); + + /********************************************/ + /* Set internal sampling frequency */ + /********************************************/ + ret += silk_setup_fs( psEnc, fs_kHz, encControl->payloadSize_ms ); + + /********************************************/ + /* Set encoding complexity */ + /********************************************/ + ret += silk_setup_complexity( &psEnc->sCmn, encControl->complexity ); + + /********************************************/ + /* Set packet loss rate measured by farend */ + /********************************************/ + psEnc->sCmn.PacketLoss_perc = encControl->packetLossPercentage; + + /********************************************/ + /* Set LBRR usage */ + /********************************************/ + ret += silk_setup_LBRR( &psEnc->sCmn, encControl ); + + psEnc->sCmn.controlled_since_last_payload = 1; + + return ret; +} + +static opus_int silk_setup_resamplers( + silk_encoder_state_Fxx *psEnc, /* I/O */ + opus_int fs_kHz /* I */ +) +{ + opus_int ret = SILK_NO_ERROR; + SAVE_STACK; + + if( psEnc->sCmn.fs_kHz != fs_kHz || psEnc->sCmn.prev_API_fs_Hz != psEnc->sCmn.API_fs_Hz ) + { + if( psEnc->sCmn.fs_kHz == 0 ) { + /* Initialize the resampler for enc_API.c preparing resampling from API_fs_Hz to fs_kHz */ + ret += silk_resampler_init( &psEnc->sCmn.resampler_state, psEnc->sCmn.API_fs_Hz, fs_kHz * 1000, 1 ); + } else { + VARDECL( opus_int16, x_buf_API_fs_Hz ); + VARDECL( silk_resampler_state_struct, temp_resampler_state ); +#ifdef FIXED_POINT + opus_int16 *x_bufFIX = psEnc->x_buf; +#else + VARDECL( opus_int16, x_bufFIX ); + opus_int32 new_buf_samples; +#endif + opus_int32 api_buf_samples; + opus_int32 old_buf_samples; + opus_int32 buf_length_ms; + + buf_length_ms = silk_LSHIFT( psEnc->sCmn.nb_subfr * 5, 1 ) + LA_SHAPE_MS; + old_buf_samples = buf_length_ms * psEnc->sCmn.fs_kHz; + +#ifndef FIXED_POINT + new_buf_samples = buf_length_ms * fs_kHz; + ALLOC( x_bufFIX, silk_max( old_buf_samples, new_buf_samples ), + opus_int16 ); + silk_float2short_array( x_bufFIX, psEnc->x_buf, old_buf_samples ); +#endif + + /* Initialize resampler for temporary resampling of x_buf data to API_fs_Hz */ + ALLOC( temp_resampler_state, 1, silk_resampler_state_struct ); + ret += silk_resampler_init( temp_resampler_state, silk_SMULBB( psEnc->sCmn.fs_kHz, 1000 ), psEnc->sCmn.API_fs_Hz, 0 ); + + /* Calculate number of samples to temporarily upsample */ + api_buf_samples = buf_length_ms * silk_DIV32_16( psEnc->sCmn.API_fs_Hz, 1000 ); + + /* Temporary resampling of x_buf data to API_fs_Hz */ + ALLOC( x_buf_API_fs_Hz, api_buf_samples, opus_int16 ); + ret += silk_resampler( temp_resampler_state, x_buf_API_fs_Hz, x_bufFIX, old_buf_samples ); + + /* Initialize the resampler for enc_API.c preparing resampling from API_fs_Hz to fs_kHz */ + ret += silk_resampler_init( &psEnc->sCmn.resampler_state, psEnc->sCmn.API_fs_Hz, silk_SMULBB( fs_kHz, 1000 ), 1 ); + + /* Correct resampler state by resampling buffered data from API_fs_Hz to fs_kHz */ + ret += silk_resampler( &psEnc->sCmn.resampler_state, x_bufFIX, x_buf_API_fs_Hz, api_buf_samples ); + +#ifndef FIXED_POINT + silk_short2float_array( psEnc->x_buf, x_bufFIX, new_buf_samples); +#endif + } + } + + psEnc->sCmn.prev_API_fs_Hz = psEnc->sCmn.API_fs_Hz; + + RESTORE_STACK; + return ret; +} + +static opus_int silk_setup_fs( + silk_encoder_state_Fxx *psEnc, /* I/O */ + opus_int fs_kHz, /* I */ + opus_int PacketSize_ms /* I */ +) +{ + opus_int ret = SILK_NO_ERROR; + + /* Set packet size */ + if( PacketSize_ms != psEnc->sCmn.PacketSize_ms ) { + if( ( PacketSize_ms != 10 ) && + ( PacketSize_ms != 20 ) && + ( PacketSize_ms != 40 ) && + ( PacketSize_ms != 60 ) ) { + ret = SILK_ENC_PACKET_SIZE_NOT_SUPPORTED; + } + if( PacketSize_ms <= 10 ) { + psEnc->sCmn.nFramesPerPacket = 1; + psEnc->sCmn.nb_subfr = PacketSize_ms == 10 ? 2 : 1; + psEnc->sCmn.frame_length = silk_SMULBB( PacketSize_ms, fs_kHz ); + psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS_2_SF, fs_kHz ); + if( psEnc->sCmn.fs_kHz == 8 ) { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_10_ms_NB_iCDF; + } else { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_10_ms_iCDF; + } + } else { + psEnc->sCmn.nFramesPerPacket = silk_DIV32_16( PacketSize_ms, MAX_FRAME_LENGTH_MS ); + psEnc->sCmn.nb_subfr = MAX_NB_SUBFR; + psEnc->sCmn.frame_length = silk_SMULBB( 20, fs_kHz ); + psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS, fs_kHz ); + if( psEnc->sCmn.fs_kHz == 8 ) { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_NB_iCDF; + } else { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_iCDF; + } + } + psEnc->sCmn.PacketSize_ms = PacketSize_ms; + psEnc->sCmn.TargetRate_bps = 0; /* trigger new SNR computation */ + } + + /* Set internal sampling frequency */ + celt_assert( fs_kHz == 8 || fs_kHz == 12 || fs_kHz == 16 ); + celt_assert( psEnc->sCmn.nb_subfr == 2 || psEnc->sCmn.nb_subfr == 4 ); + if( psEnc->sCmn.fs_kHz != fs_kHz ) { + /* reset part of the state */ + silk_memset( &psEnc->sShape, 0, sizeof( psEnc->sShape ) ); + silk_memset( &psEnc->sCmn.sNSQ, 0, sizeof( psEnc->sCmn.sNSQ ) ); + silk_memset( psEnc->sCmn.prev_NLSFq_Q15, 0, sizeof( psEnc->sCmn.prev_NLSFq_Q15 ) ); + silk_memset( &psEnc->sCmn.sLP.In_LP_State, 0, sizeof( psEnc->sCmn.sLP.In_LP_State ) ); + psEnc->sCmn.inputBufIx = 0; + psEnc->sCmn.nFramesEncoded = 0; + psEnc->sCmn.TargetRate_bps = 0; /* trigger new SNR computation */ + + /* Initialize non-zero parameters */ + psEnc->sCmn.prevLag = 100; + psEnc->sCmn.first_frame_after_reset = 1; + psEnc->sShape.LastGainIndex = 10; + psEnc->sCmn.sNSQ.lagPrev = 100; + psEnc->sCmn.sNSQ.prev_gain_Q16 = 65536; + psEnc->sCmn.prevSignalType = TYPE_NO_VOICE_ACTIVITY; + + psEnc->sCmn.fs_kHz = fs_kHz; + if( psEnc->sCmn.fs_kHz == 8 ) { + if( psEnc->sCmn.nb_subfr == MAX_NB_SUBFR ) { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_NB_iCDF; + } else { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_10_ms_NB_iCDF; + } + } else { + if( psEnc->sCmn.nb_subfr == MAX_NB_SUBFR ) { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_iCDF; + } else { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_10_ms_iCDF; + } + } + if( psEnc->sCmn.fs_kHz == 8 || psEnc->sCmn.fs_kHz == 12 ) { + psEnc->sCmn.predictLPCOrder = MIN_LPC_ORDER; + psEnc->sCmn.psNLSF_CB = &silk_NLSF_CB_NB_MB; + } else { + psEnc->sCmn.predictLPCOrder = MAX_LPC_ORDER; + psEnc->sCmn.psNLSF_CB = &silk_NLSF_CB_WB; + } + psEnc->sCmn.subfr_length = SUB_FRAME_LENGTH_MS * fs_kHz; + psEnc->sCmn.frame_length = silk_SMULBB( psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr ); + psEnc->sCmn.ltp_mem_length = silk_SMULBB( LTP_MEM_LENGTH_MS, fs_kHz ); + psEnc->sCmn.la_pitch = silk_SMULBB( LA_PITCH_MS, fs_kHz ); + psEnc->sCmn.max_pitch_lag = silk_SMULBB( 18, fs_kHz ); + if( psEnc->sCmn.nb_subfr == MAX_NB_SUBFR ) { + psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS, fs_kHz ); + } else { + psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS_2_SF, fs_kHz ); + } + if( psEnc->sCmn.fs_kHz == 16 ) { + psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform8_iCDF; + } else if( psEnc->sCmn.fs_kHz == 12 ) { + psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform6_iCDF; + } else { + psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform4_iCDF; + } + } + + /* Check that settings are valid */ + celt_assert( ( psEnc->sCmn.subfr_length * psEnc->sCmn.nb_subfr ) == psEnc->sCmn.frame_length ); + + return ret; +} + +static opus_int silk_setup_complexity( + silk_encoder_state *psEncC, /* I/O */ + opus_int Complexity /* I */ +) +{ + opus_int ret = 0; + + /* Set encoding complexity */ + celt_assert( Complexity >= 0 && Complexity <= 10 ); + if( Complexity < 1 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MIN_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.8, 16 ); + psEncC->pitchEstimationLPCOrder = 6; + psEncC->shapingLPCOrder = 12; + psEncC->la_shape = 3 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 1; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 2; + psEncC->warping_Q16 = 0; + } else if( Complexity < 2 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.76, 16 ); + psEncC->pitchEstimationLPCOrder = 8; + psEncC->shapingLPCOrder = 14; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 1; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 3; + psEncC->warping_Q16 = 0; + } else if( Complexity < 3 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MIN_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.8, 16 ); + psEncC->pitchEstimationLPCOrder = 6; + psEncC->shapingLPCOrder = 12; + psEncC->la_shape = 3 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 2; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 2; + psEncC->warping_Q16 = 0; + } else if( Complexity < 4 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.76, 16 ); + psEncC->pitchEstimationLPCOrder = 8; + psEncC->shapingLPCOrder = 14; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 2; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 4; + psEncC->warping_Q16 = 0; + } else if( Complexity < 6 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.74, 16 ); + psEncC->pitchEstimationLPCOrder = 10; + psEncC->shapingLPCOrder = 16; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 2; + psEncC->useInterpolatedNLSFs = 1; + psEncC->NLSF_MSVQ_Survivors = 6; + psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); + } else if( Complexity < 8 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.72, 16 ); + psEncC->pitchEstimationLPCOrder = 12; + psEncC->shapingLPCOrder = 20; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 3; + psEncC->useInterpolatedNLSFs = 1; + psEncC->NLSF_MSVQ_Survivors = 8; + psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); + } else { + psEncC->pitchEstimationComplexity = SILK_PE_MAX_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.7, 16 ); + psEncC->pitchEstimationLPCOrder = 16; + psEncC->shapingLPCOrder = 24; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = MAX_DEL_DEC_STATES; + psEncC->useInterpolatedNLSFs = 1; + psEncC->NLSF_MSVQ_Survivors = 16; + psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); + } + + /* Do not allow higher pitch estimation LPC order than predict LPC order */ + psEncC->pitchEstimationLPCOrder = silk_min_int( psEncC->pitchEstimationLPCOrder, psEncC->predictLPCOrder ); + psEncC->shapeWinLength = SUB_FRAME_LENGTH_MS * psEncC->fs_kHz + 2 * psEncC->la_shape; + psEncC->Complexity = Complexity; + + celt_assert( psEncC->pitchEstimationLPCOrder <= MAX_FIND_PITCH_LPC_ORDER ); + celt_assert( psEncC->shapingLPCOrder <= MAX_SHAPE_LPC_ORDER ); + celt_assert( psEncC->nStatesDelayedDecision <= MAX_DEL_DEC_STATES ); + celt_assert( psEncC->warping_Q16 <= 32767 ); + celt_assert( psEncC->la_shape <= LA_SHAPE_MAX ); + celt_assert( psEncC->shapeWinLength <= SHAPE_LPC_WIN_MAX ); + + return ret; +} + +static OPUS_INLINE opus_int silk_setup_LBRR( + silk_encoder_state *psEncC, /* I/O */ + const silk_EncControlStruct *encControl /* I */ +) +{ + opus_int LBRR_in_previous_packet, ret = SILK_NO_ERROR; + + LBRR_in_previous_packet = psEncC->LBRR_enabled; + psEncC->LBRR_enabled = encControl->LBRR_coded; + if( psEncC->LBRR_enabled ) { + /* Set gain increase for coding LBRR excitation */ + if( LBRR_in_previous_packet == 0 ) { + /* Previous packet did not have LBRR, and was therefore coded at a higher bitrate */ + psEncC->LBRR_GainIncreases = 7; + } else { + psEncC->LBRR_GainIncreases = silk_max_int( 7 - silk_SMULWB( (opus_int32)psEncC->PacketLoss_perc, SILK_FIX_CONST( 0.2, 16 ) ), 3 ); + } + } + + return ret; +} diff --git a/port/third_party/opus/silk/debug.c b/port/third_party/opus/silk/debug.c new file mode 100644 index 00000000..46a24a47 --- /dev/null +++ b/port/third_party/opus/silk/debug.c @@ -0,0 +1,174 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +typedef int prevent_empty_translation_unit_warning; + +#include "debug.h" + +#if SILK_DEBUG || SILK_TIC_TOC +#include "SigProc_FIX.h" +#endif + +#if SILK_TIC_TOC + +#if (defined(_WIN32) || defined(_WINCE)) +#include /* timer */ +#else /* Linux or Mac*/ +#include +#endif + +#ifdef _WIN32 +unsigned long silk_GetHighResolutionTime(void) /* O time in usec*/ +{ + /* Returns a time counter in microsec */ + /* the resolution is platform dependent */ + /* but is typically 1.62 us resolution */ + LARGE_INTEGER lpPerformanceCount; + LARGE_INTEGER lpFrequency; + QueryPerformanceCounter(&lpPerformanceCount); + QueryPerformanceFrequency(&lpFrequency); + return (unsigned long)((1000000*(lpPerformanceCount.QuadPart)) / lpFrequency.QuadPart); +} +#else /* Linux or Mac*/ +unsigned long GetHighResolutionTime(void) /* O time in usec*/ +{ + struct timeval tv; + gettimeofday(&tv, 0); + return((tv.tv_sec*1000000)+(tv.tv_usec)); +} +#endif + +int silk_Timer_nTimers = 0; +int silk_Timer_depth_ctr = 0; +char silk_Timer_tags[silk_NUM_TIMERS_MAX][silk_NUM_TIMERS_MAX_TAG_LEN]; +#ifdef _WIN32 +LARGE_INTEGER silk_Timer_start[silk_NUM_TIMERS_MAX]; +#else +unsigned long silk_Timer_start[silk_NUM_TIMERS_MAX]; +#endif +unsigned int silk_Timer_cnt[silk_NUM_TIMERS_MAX]; +opus_int64 silk_Timer_min[silk_NUM_TIMERS_MAX]; +opus_int64 silk_Timer_sum[silk_NUM_TIMERS_MAX]; +opus_int64 silk_Timer_max[silk_NUM_TIMERS_MAX]; +opus_int64 silk_Timer_depth[silk_NUM_TIMERS_MAX]; + +#ifdef _WIN32 +void silk_TimerSave(char *file_name) +{ + if( silk_Timer_nTimers > 0 ) + { + int k; + FILE *fp; + LARGE_INTEGER lpFrequency; + LARGE_INTEGER lpPerformanceCount1, lpPerformanceCount2; + int del = 0x7FFFFFFF; + double avg, sum_avg; + /* estimate overhead of calling performance counters */ + for( k = 0; k < 1000; k++ ) { + QueryPerformanceCounter(&lpPerformanceCount1); + QueryPerformanceCounter(&lpPerformanceCount2); + lpPerformanceCount2.QuadPart -= lpPerformanceCount1.QuadPart; + if( (int)lpPerformanceCount2.LowPart < del ) + del = lpPerformanceCount2.LowPart; + } + QueryPerformanceFrequency(&lpFrequency); + /* print results to file */ + sum_avg = 0.0f; + for( k = 0; k < silk_Timer_nTimers; k++ ) { + if (silk_Timer_depth[k] == 0) { + sum_avg += (1e6 * silk_Timer_sum[k] / silk_Timer_cnt[k] - del) / lpFrequency.QuadPart * silk_Timer_cnt[k]; + } + } + fp = fopen(file_name, "w"); + fprintf(fp, " min avg %% max count\n"); + for( k = 0; k < silk_Timer_nTimers; k++ ) { + if (silk_Timer_depth[k] == 0) { + fprintf(fp, "%-28s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 1) { + fprintf(fp, " %-27s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 2) { + fprintf(fp, " %-26s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 3) { + fprintf(fp, " %-25s", silk_Timer_tags[k]); + } else { + fprintf(fp, " %-24s", silk_Timer_tags[k]); + } + avg = (1e6 * silk_Timer_sum[k] / silk_Timer_cnt[k] - del) / lpFrequency.QuadPart; + fprintf(fp, "%8.2f", (1e6 * (silk_max_64(silk_Timer_min[k] - del, 0))) / lpFrequency.QuadPart); + fprintf(fp, "%12.2f %6.2f", avg, 100.0 * avg / sum_avg * silk_Timer_cnt[k]); + fprintf(fp, "%12.2f", (1e6 * (silk_max_64(silk_Timer_max[k] - del, 0))) / lpFrequency.QuadPart); + fprintf(fp, "%10d\n", silk_Timer_cnt[k]); + } + fprintf(fp, " microseconds\n"); + fclose(fp); + } +} +#else +void silk_TimerSave(char *file_name) +{ + if( silk_Timer_nTimers > 0 ) + { + int k; + FILE *fp; + /* print results to file */ + fp = fopen(file_name, "w"); + fprintf(fp, " min avg max count\n"); + for( k = 0; k < silk_Timer_nTimers; k++ ) + { + if (silk_Timer_depth[k] == 0) { + fprintf(fp, "%-28s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 1) { + fprintf(fp, " %-27s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 2) { + fprintf(fp, " %-26s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 3) { + fprintf(fp, " %-25s", silk_Timer_tags[k]); + } else { + fprintf(fp, " %-24s", silk_Timer_tags[k]); + } + fprintf(fp, "%d ", silk_Timer_min[k]); + fprintf(fp, "%f ", (double)silk_Timer_sum[k] / (double)silk_Timer_cnt[k]); + fprintf(fp, "%d ", silk_Timer_max[k]); + fprintf(fp, "%10d\n", silk_Timer_cnt[k]); + } + fprintf(fp, " microseconds\n"); + fclose(fp); + } +} +#endif + +#endif /* SILK_TIC_TOC */ + +#if SILK_DEBUG +FILE *silk_debug_store_fp[ silk_NUM_STORES_MAX ]; +int silk_debug_store_count = 0; +#endif /* SILK_DEBUG */ + diff --git a/port/third_party/opus/silk/debug.h b/port/third_party/opus/silk/debug.h new file mode 100644 index 00000000..36163e47 --- /dev/null +++ b/port/third_party/opus/silk/debug.h @@ -0,0 +1,267 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_DEBUG_H +#define SILK_DEBUG_H + +/* Set to 1 to enable DEBUG_STORE_DATA() macros for dumping + * intermediate signals from the codec. + */ +#define SILK_DEBUG 0 + +/* Flag for using timers */ +#define SILK_TIC_TOC 0 + +#if SILK_DEBUG || SILK_TIC_TOC +#include "typedef.h" +#include /* strcpy, strcmp */ +#include /* file writing */ +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if SILK_TIC_TOC + +unsigned long GetHighResolutionTime(void); /* O time in usec*/ + +#if (defined(_WIN32) || defined(_WINCE)) +#include /* timer */ +#else /* Linux or Mac*/ +#include +#endif + +/*********************************/ +/* timer functions for profiling */ +/*********************************/ +/* example: */ +/* */ +/* TIC(LPC) */ +/* do_LPC(in_vec, order, acoef); // do LPC analysis */ +/* TOC(LPC) */ +/* */ +/* and call the following just before exiting (from main) */ +/* */ +/* silk_TimerSave("silk_TimingData.txt"); */ +/* */ +/* results are now in silk_TimingData.txt */ + +void silk_TimerSave(char *file_name); + +/* max number of timers (in different locations) */ +#define silk_NUM_TIMERS_MAX 50 +/* max length of name tags in TIC(..), TOC(..) */ +#define silk_NUM_TIMERS_MAX_TAG_LEN 30 + +extern int silk_Timer_nTimers; +extern int silk_Timer_depth_ctr; +extern char silk_Timer_tags[silk_NUM_TIMERS_MAX][silk_NUM_TIMERS_MAX_TAG_LEN]; +#ifdef _WIN32 +extern LARGE_INTEGER silk_Timer_start[silk_NUM_TIMERS_MAX]; +#else +extern unsigned long silk_Timer_start[silk_NUM_TIMERS_MAX]; +#endif +extern unsigned int silk_Timer_cnt[silk_NUM_TIMERS_MAX]; +extern opus_int64 silk_Timer_sum[silk_NUM_TIMERS_MAX]; +extern opus_int64 silk_Timer_max[silk_NUM_TIMERS_MAX]; +extern opus_int64 silk_Timer_min[silk_NUM_TIMERS_MAX]; +extern opus_int64 silk_Timer_depth[silk_NUM_TIMERS_MAX]; + +/* WARNING: TIC()/TOC can measure only up to 0.1 seconds at a time */ +#ifdef _WIN32 +#define TIC(TAG_NAME) { \ + static int init = 0; \ + static int ID = -1; \ + if( init == 0 ) \ + { \ + int k; \ + init = 1; \ + for( k = 0; k < silk_Timer_nTimers; k++ ) { \ + if( strcmp(silk_Timer_tags[k], #TAG_NAME) == 0 ) { \ + ID = k; \ + break; \ + } \ + } \ + if (ID == -1) { \ + ID = silk_Timer_nTimers; \ + silk_Timer_nTimers++; \ + silk_Timer_depth[ID] = silk_Timer_depth_ctr; \ + strcpy(silk_Timer_tags[ID], #TAG_NAME); \ + silk_Timer_cnt[ID] = 0; \ + silk_Timer_sum[ID] = 0; \ + silk_Timer_min[ID] = 0xFFFFFFFF; \ + silk_Timer_max[ID] = 0; \ + } \ + } \ + silk_Timer_depth_ctr++; \ + QueryPerformanceCounter(&silk_Timer_start[ID]); \ +} +#else +#define TIC(TAG_NAME) { \ + static int init = 0; \ + static int ID = -1; \ + if( init == 0 ) \ + { \ + int k; \ + init = 1; \ + for( k = 0; k < silk_Timer_nTimers; k++ ) { \ + if( strcmp(silk_Timer_tags[k], #TAG_NAME) == 0 ) { \ + ID = k; \ + break; \ + } \ + } \ + if (ID == -1) { \ + ID = silk_Timer_nTimers; \ + silk_Timer_nTimers++; \ + silk_Timer_depth[ID] = silk_Timer_depth_ctr; \ + strcpy(silk_Timer_tags[ID], #TAG_NAME); \ + silk_Timer_cnt[ID] = 0; \ + silk_Timer_sum[ID] = 0; \ + silk_Timer_min[ID] = 0xFFFFFFFF; \ + silk_Timer_max[ID] = 0; \ + } \ + } \ + silk_Timer_depth_ctr++; \ + silk_Timer_start[ID] = GetHighResolutionTime(); \ +} +#endif + +#ifdef _WIN32 +#define TOC(TAG_NAME) { \ + LARGE_INTEGER lpPerformanceCount; \ + static int init = 0; \ + static int ID = 0; \ + if( init == 0 ) \ + { \ + int k; \ + init = 1; \ + for( k = 0; k < silk_Timer_nTimers; k++ ) { \ + if( strcmp(silk_Timer_tags[k], #TAG_NAME) == 0 ) { \ + ID = k; \ + break; \ + } \ + } \ + } \ + QueryPerformanceCounter(&lpPerformanceCount); \ + lpPerformanceCount.QuadPart -= silk_Timer_start[ID].QuadPart; \ + if((lpPerformanceCount.QuadPart < 100000000) && \ + (lpPerformanceCount.QuadPart >= 0)) { \ + silk_Timer_cnt[ID]++; \ + silk_Timer_sum[ID] += lpPerformanceCount.QuadPart; \ + if( lpPerformanceCount.QuadPart > silk_Timer_max[ID] ) \ + silk_Timer_max[ID] = lpPerformanceCount.QuadPart; \ + if( lpPerformanceCount.QuadPart < silk_Timer_min[ID] ) \ + silk_Timer_min[ID] = lpPerformanceCount.QuadPart; \ + } \ + silk_Timer_depth_ctr--; \ +} +#else +#define TOC(TAG_NAME) { \ + unsigned long endTime; \ + static int init = 0; \ + static int ID = 0; \ + if( init == 0 ) \ + { \ + int k; \ + init = 1; \ + for( k = 0; k < silk_Timer_nTimers; k++ ) { \ + if( strcmp(silk_Timer_tags[k], #TAG_NAME) == 0 ) { \ + ID = k; \ + break; \ + } \ + } \ + } \ + endTime = GetHighResolutionTime(); \ + endTime -= silk_Timer_start[ID]; \ + if((endTime < 100000000) && \ + (endTime >= 0)) { \ + silk_Timer_cnt[ID]++; \ + silk_Timer_sum[ID] += endTime; \ + if( endTime > silk_Timer_max[ID] ) \ + silk_Timer_max[ID] = endTime; \ + if( endTime < silk_Timer_min[ID] ) \ + silk_Timer_min[ID] = endTime; \ + } \ + silk_Timer_depth_ctr--; \ +} +#endif + +#else /* SILK_TIC_TOC */ + +/* define macros as empty strings */ +#define TIC(TAG_NAME) +#define TOC(TAG_NAME) +#define silk_TimerSave(FILE_NAME) + +#endif /* SILK_TIC_TOC */ + + +#if SILK_DEBUG +/************************************/ +/* write data to file for debugging */ +/************************************/ +/* Example: DEBUG_STORE_DATA(testfile.pcm, &RIN[0], 160*sizeof(opus_int16)); */ + +#define silk_NUM_STORES_MAX 100 +extern FILE *silk_debug_store_fp[ silk_NUM_STORES_MAX ]; +extern int silk_debug_store_count; + +/* Faster way of storing the data */ +#define DEBUG_STORE_DATA( FILE_NAME, DATA_PTR, N_BYTES ) { \ + static opus_int init = 0, cnt = 0; \ + static FILE **fp; \ + if (init == 0) { \ + init = 1; \ + cnt = silk_debug_store_count++; \ + silk_debug_store_fp[ cnt ] = fopen(#FILE_NAME, "wb"); \ + } \ + fwrite((DATA_PTR), (N_BYTES), 1, silk_debug_store_fp[ cnt ]); \ +} + +/* Call this at the end of main() */ +#define SILK_DEBUG_STORE_CLOSE_FILES { \ + opus_int i; \ + for( i = 0; i < silk_debug_store_count; i++ ) { \ + fclose( silk_debug_store_fp[ i ] ); \ + } \ +} + +#else /* SILK_DEBUG */ + +/* define macros as empty strings */ +#define DEBUG_STORE_DATA(FILE_NAME, DATA_PTR, N_BYTES) +#define SILK_DEBUG_STORE_CLOSE_FILES + +#endif /* SILK_DEBUG */ + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_DEBUG_H */ diff --git a/port/third_party/opus/silk/dec_API.c b/port/third_party/opus/silk/dec_API.c new file mode 100644 index 00000000..c1091d13 --- /dev/null +++ b/port/third_party/opus/silk/dec_API.c @@ -0,0 +1,486 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "API.h" +#include "main.h" +#include "stack_alloc.h" +#include "os_support.h" + +#ifdef ENABLE_OSCE +#include "osce.h" +#include "osce_structs.h" +#endif + +/************************/ +/* Decoder Super Struct */ +/************************/ +typedef struct { + silk_decoder_state channel_state[ DECODER_NUM_CHANNELS ]; + stereo_dec_state sStereo; + opus_int nChannelsAPI; + opus_int nChannelsInternal; + opus_int prev_decode_only_middle; +#ifdef ENABLE_OSCE + OSCEModel osce_model; +#endif +} silk_decoder; + +/*********************/ +/* Decoder functions */ +/*********************/ + + + +opus_int silk_LoadOSCEModels(void *decState, const unsigned char *data, int len) +{ +#ifdef ENABLE_OSCE + opus_int ret = SILK_NO_ERROR; + + ret = osce_load_models(&((silk_decoder *)decState)->osce_model, data, len); + ((silk_decoder *)decState)->osce_model.loaded = (ret == 0); + return ret; +#else + (void) decState; + (void) data; + (void) len; + return SILK_NO_ERROR; +#endif +} + +opus_int silk_Get_Decoder_Size( /* O Returns error code */ + opus_int *decSizeBytes /* O Number of bytes in SILK decoder state */ +) +{ + opus_int ret = SILK_NO_ERROR; + + *decSizeBytes = sizeof( silk_decoder ); + + return ret; +} + +/* Reset decoder state */ +opus_int silk_ResetDecoder( /* O Returns error code */ + void *decState /* I/O State */ +) +{ + opus_int n, ret = SILK_NO_ERROR; + silk_decoder_state *channel_state = ((silk_decoder *)decState)->channel_state; + + for( n = 0; n < DECODER_NUM_CHANNELS; n++ ) { + ret = silk_reset_decoder( &channel_state[ n ] ); + } + silk_memset(&((silk_decoder *)decState)->sStereo, 0, sizeof(((silk_decoder *)decState)->sStereo)); + /* Not strictly needed, but it's cleaner that way */ + ((silk_decoder *)decState)->prev_decode_only_middle = 0; + + return ret; +} + + +opus_int silk_InitDecoder( /* O Returns error code */ + void *decState /* I/O State */ +) +{ + opus_int n, ret = SILK_NO_ERROR; + silk_decoder_state *channel_state = ((silk_decoder *)decState)->channel_state; +#ifdef ENABLE_OSCE + ((silk_decoder *)decState)->osce_model.loaded = 0; +#endif +#ifndef USE_WEIGHTS_FILE + /* load osce models */ + silk_LoadOSCEModels(decState, NULL, 0); +#endif + + for( n = 0; n < DECODER_NUM_CHANNELS; n++ ) { + ret = silk_init_decoder( &channel_state[ n ] ); + } + silk_memset(&((silk_decoder *)decState)->sStereo, 0, sizeof(((silk_decoder *)decState)->sStereo)); + /* Not strictly needed, but it's cleaner that way */ + ((silk_decoder *)decState)->prev_decode_only_middle = 0; + + return ret; +} + +/* Decode a frame */ +opus_int silk_Decode( /* O Returns error code */ + void* decState, /* I/O State */ + silk_DecControlStruct* decControl, /* I/O Control Structure */ + opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ + opus_int newPacketFlag, /* I Indicates first decoder call for this packet */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 *samplesOut, /* O Decoded output speech vector */ + opus_int32 *nSamplesOut, /* O Number of samples decoded */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +) +{ + opus_int i, n, decode_only_middle = 0, ret = SILK_NO_ERROR; + opus_int32 nSamplesOutDec, LBRR_symbol; + opus_int16 *samplesOut1_tmp[ 2 ]; + VARDECL( opus_int16, samplesOut1_tmp_storage1 ); + VARDECL( opus_int16, samplesOut1_tmp_storage2 ); + VARDECL( opus_int16, samplesOut2_tmp ); + opus_int32 MS_pred_Q13[ 2 ] = { 0 }; + opus_int16 *resample_out_ptr; + silk_decoder *psDec = ( silk_decoder * )decState; + silk_decoder_state *channel_state = psDec->channel_state; + opus_int has_side; + opus_int stereo_to_mono; + int delay_stack_alloc; + SAVE_STACK; + + celt_assert( decControl->nChannelsInternal == 1 || decControl->nChannelsInternal == 2 ); + + /**********************************/ + /* Test if first frame in payload */ + /**********************************/ + if( newPacketFlag ) { + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + channel_state[ n ].nFramesDecoded = 0; /* Used to count frames in packet */ + } + } + + /* If Mono -> Stereo transition in bitstream: init state of second channel */ + if( decControl->nChannelsInternal > psDec->nChannelsInternal ) { + ret += silk_init_decoder( &channel_state[ 1 ] ); + } + + stereo_to_mono = decControl->nChannelsInternal == 1 && psDec->nChannelsInternal == 2 && + ( decControl->internalSampleRate == 1000*channel_state[ 0 ].fs_kHz ); + + if( channel_state[ 0 ].nFramesDecoded == 0 ) { + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + opus_int fs_kHz_dec; + if( decControl->payloadSize_ms == 0 ) { + /* Assuming packet loss, use 10 ms */ + channel_state[ n ].nFramesPerPacket = 1; + channel_state[ n ].nb_subfr = 2; + } else if( decControl->payloadSize_ms == 10 ) { + channel_state[ n ].nFramesPerPacket = 1; + channel_state[ n ].nb_subfr = 2; + } else if( decControl->payloadSize_ms == 20 ) { + channel_state[ n ].nFramesPerPacket = 1; + channel_state[ n ].nb_subfr = 4; + } else if( decControl->payloadSize_ms == 40 ) { + channel_state[ n ].nFramesPerPacket = 2; + channel_state[ n ].nb_subfr = 4; + } else if( decControl->payloadSize_ms == 60 ) { + channel_state[ n ].nFramesPerPacket = 3; + channel_state[ n ].nb_subfr = 4; + } else { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_DEC_INVALID_FRAME_SIZE; + } + fs_kHz_dec = ( decControl->internalSampleRate >> 10 ) + 1; + if( fs_kHz_dec != 8 && fs_kHz_dec != 12 && fs_kHz_dec != 16 ) { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_DEC_INVALID_SAMPLING_FREQUENCY; + } + ret += silk_decoder_set_fs( &channel_state[ n ], fs_kHz_dec, decControl->API_sampleRate ); + } + } + + if( decControl->nChannelsAPI == 2 && decControl->nChannelsInternal == 2 && ( psDec->nChannelsAPI == 1 || psDec->nChannelsInternal == 1 ) ) { + silk_memset( psDec->sStereo.pred_prev_Q13, 0, sizeof( psDec->sStereo.pred_prev_Q13 ) ); + silk_memset( psDec->sStereo.sSide, 0, sizeof( psDec->sStereo.sSide ) ); + silk_memcpy( &channel_state[ 1 ].resampler_state, &channel_state[ 0 ].resampler_state, sizeof( silk_resampler_state_struct ) ); + } + psDec->nChannelsAPI = decControl->nChannelsAPI; + psDec->nChannelsInternal = decControl->nChannelsInternal; + + if( decControl->API_sampleRate > (opus_int32)MAX_API_FS_KHZ * 1000 || decControl->API_sampleRate < 8000 ) { + ret = SILK_DEC_INVALID_SAMPLING_FREQUENCY; + RESTORE_STACK; + return( ret ); + } + + if( lostFlag != FLAG_PACKET_LOST && channel_state[ 0 ].nFramesDecoded == 0 ) { + /* First decoder call for this payload */ + /* Decode VAD flags and LBRR flag */ + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + for( i = 0; i < channel_state[ n ].nFramesPerPacket; i++ ) { + channel_state[ n ].VAD_flags[ i ] = ec_dec_bit_logp(psRangeDec, 1); + } + channel_state[ n ].LBRR_flag = ec_dec_bit_logp(psRangeDec, 1); + } + /* Decode LBRR flags */ + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + silk_memset( channel_state[ n ].LBRR_flags, 0, sizeof( channel_state[ n ].LBRR_flags ) ); + if( channel_state[ n ].LBRR_flag ) { + if( channel_state[ n ].nFramesPerPacket == 1 ) { + channel_state[ n ].LBRR_flags[ 0 ] = 1; + } else { + LBRR_symbol = ec_dec_icdf( psRangeDec, silk_LBRR_flags_iCDF_ptr[ channel_state[ n ].nFramesPerPacket - 2 ], 8 ) + 1; + for( i = 0; i < channel_state[ n ].nFramesPerPacket; i++ ) { + channel_state[ n ].LBRR_flags[ i ] = silk_RSHIFT( LBRR_symbol, i ) & 1; + } + } + } + } + + if( lostFlag == FLAG_DECODE_NORMAL ) { + /* Regular decoding: skip all LBRR data */ + for( i = 0; i < channel_state[ 0 ].nFramesPerPacket; i++ ) { + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + if( channel_state[ n ].LBRR_flags[ i ] ) { + opus_int16 pulses[ MAX_FRAME_LENGTH ]; + opus_int condCoding; + + if( decControl->nChannelsInternal == 2 && n == 0 ) { + silk_stereo_decode_pred( psRangeDec, MS_pred_Q13 ); + if( channel_state[ 1 ].LBRR_flags[ i ] == 0 ) { + silk_stereo_decode_mid_only( psRangeDec, &decode_only_middle ); + } + } + /* Use conditional coding if previous frame available */ + if( i > 0 && channel_state[ n ].LBRR_flags[ i - 1 ] ) { + condCoding = CODE_CONDITIONALLY; + } else { + condCoding = CODE_INDEPENDENTLY; + } + silk_decode_indices( &channel_state[ n ], psRangeDec, i, 1, condCoding ); + silk_decode_pulses( psRangeDec, pulses, channel_state[ n ].indices.signalType, + channel_state[ n ].indices.quantOffsetType, channel_state[ n ].frame_length ); + } + } + } + } + } + + /* Get MS predictor index */ + if( decControl->nChannelsInternal == 2 ) { + if( lostFlag == FLAG_DECODE_NORMAL || + ( lostFlag == FLAG_DECODE_LBRR && channel_state[ 0 ].LBRR_flags[ channel_state[ 0 ].nFramesDecoded ] == 1 ) ) + { + silk_stereo_decode_pred( psRangeDec, MS_pred_Q13 ); + /* For LBRR data, decode mid-only flag only if side-channel's LBRR flag is false */ + if( ( lostFlag == FLAG_DECODE_NORMAL && channel_state[ 1 ].VAD_flags[ channel_state[ 0 ].nFramesDecoded ] == 0 ) || + ( lostFlag == FLAG_DECODE_LBRR && channel_state[ 1 ].LBRR_flags[ channel_state[ 0 ].nFramesDecoded ] == 0 ) ) + { + silk_stereo_decode_mid_only( psRangeDec, &decode_only_middle ); + } else { + decode_only_middle = 0; + } + } else { + for( n = 0; n < 2; n++ ) { + MS_pred_Q13[ n ] = psDec->sStereo.pred_prev_Q13[ n ]; + } + } + } + + /* Reset side channel decoder prediction memory for first frame with side coding */ + if( decControl->nChannelsInternal == 2 && decode_only_middle == 0 && psDec->prev_decode_only_middle == 1 ) { + silk_memset( psDec->channel_state[ 1 ].outBuf, 0, sizeof(psDec->channel_state[ 1 ].outBuf) ); + silk_memset( psDec->channel_state[ 1 ].sLPC_Q14_buf, 0, sizeof(psDec->channel_state[ 1 ].sLPC_Q14_buf) ); + psDec->channel_state[ 1 ].lagPrev = 100; + psDec->channel_state[ 1 ].LastGainIndex = 10; + psDec->channel_state[ 1 ].prevSignalType = TYPE_NO_VOICE_ACTIVITY; + psDec->channel_state[ 1 ].first_frame_after_reset = 1; + } + + /* Check if the temp buffer fits into the output PCM buffer. If it fits, + we can delay allocating the temp buffer until after the SILK peak stack + usage. We need to use a < and not a <= because of the two extra samples. */ + delay_stack_alloc = decControl->internalSampleRate*decControl->nChannelsInternal + < decControl->API_sampleRate*decControl->nChannelsAPI; + ALLOC( samplesOut1_tmp_storage1, delay_stack_alloc ? ALLOC_NONE + : decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2 ), + opus_int16 ); + if ( delay_stack_alloc ) + { + samplesOut1_tmp[ 0 ] = samplesOut; + samplesOut1_tmp[ 1 ] = samplesOut + channel_state[ 0 ].frame_length + 2; + } else { + samplesOut1_tmp[ 0 ] = samplesOut1_tmp_storage1; + samplesOut1_tmp[ 1 ] = samplesOut1_tmp_storage1 + channel_state[ 0 ].frame_length + 2; + } + + if( lostFlag == FLAG_DECODE_NORMAL ) { + has_side = !decode_only_middle; + } else { + has_side = !psDec->prev_decode_only_middle + || (decControl->nChannelsInternal == 2 && lostFlag == FLAG_DECODE_LBRR && channel_state[1].LBRR_flags[ channel_state[1].nFramesDecoded ] == 1 ); + } + channel_state[ 0 ].sPLC.enable_deep_plc = decControl->enable_deep_plc; + /* Call decoder for one frame */ + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + if( n == 0 || has_side ) { + opus_int FrameIndex; + opus_int condCoding; + + FrameIndex = channel_state[ 0 ].nFramesDecoded - n; + /* Use independent coding if no previous frame available */ + if( FrameIndex <= 0 ) { + condCoding = CODE_INDEPENDENTLY; + } else if( lostFlag == FLAG_DECODE_LBRR ) { + condCoding = channel_state[ n ].LBRR_flags[ FrameIndex - 1 ] ? CODE_CONDITIONALLY : CODE_INDEPENDENTLY; + } else if( n > 0 && psDec->prev_decode_only_middle ) { + /* If we skipped a side frame in this packet, we don't + need LTP scaling; the LTP state is well-defined. */ + condCoding = CODE_INDEPENDENTLY_NO_LTP_SCALING; + } else { + condCoding = CODE_CONDITIONALLY; + } +#ifdef ENABLE_OSCE + if ( channel_state[n].osce.method != decControl->osce_method ) { + osce_reset( &channel_state[n].osce, decControl->osce_method ); + } +#endif + ret += silk_decode_frame( &channel_state[ n ], psRangeDec, &samplesOut1_tmp[ n ][ 2 ], &nSamplesOutDec, lostFlag, condCoding, +#ifdef ENABLE_DEEP_PLC + n == 0 ? lpcnet : NULL, +#endif +#ifdef ENABLE_OSCE + &psDec->osce_model, +#endif + arch); + } else { + silk_memset( &samplesOut1_tmp[ n ][ 2 ], 0, nSamplesOutDec * sizeof( opus_int16 ) ); + } + channel_state[ n ].nFramesDecoded++; + } + + if( decControl->nChannelsAPI == 2 && decControl->nChannelsInternal == 2 ) { + /* Convert Mid/Side to Left/Right */ + silk_stereo_MS_to_LR( &psDec->sStereo, samplesOut1_tmp[ 0 ], samplesOut1_tmp[ 1 ], MS_pred_Q13, channel_state[ 0 ].fs_kHz, nSamplesOutDec ); + } else { + /* Buffering */ + silk_memcpy( samplesOut1_tmp[ 0 ], psDec->sStereo.sMid, 2 * sizeof( opus_int16 ) ); + silk_memcpy( psDec->sStereo.sMid, &samplesOut1_tmp[ 0 ][ nSamplesOutDec ], 2 * sizeof( opus_int16 ) ); + } + + /* Number of output samples */ + *nSamplesOut = silk_DIV32( nSamplesOutDec * decControl->API_sampleRate, silk_SMULBB( channel_state[ 0 ].fs_kHz, 1000 ) ); + + /* Set up pointers to temp buffers */ + ALLOC( samplesOut2_tmp, + decControl->nChannelsAPI == 2 ? *nSamplesOut : ALLOC_NONE, opus_int16 ); + if( decControl->nChannelsAPI == 2 ) { + resample_out_ptr = samplesOut2_tmp; + } else { + resample_out_ptr = samplesOut; + } + + ALLOC( samplesOut1_tmp_storage2, delay_stack_alloc + ? decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2 ) + : ALLOC_NONE, + opus_int16 ); + if ( delay_stack_alloc ) { + OPUS_COPY(samplesOut1_tmp_storage2, samplesOut, decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2)); + samplesOut1_tmp[ 0 ] = samplesOut1_tmp_storage2; + samplesOut1_tmp[ 1 ] = samplesOut1_tmp_storage2 + channel_state[ 0 ].frame_length + 2; + } + for( n = 0; n < silk_min( decControl->nChannelsAPI, decControl->nChannelsInternal ); n++ ) { + + /* Resample decoded signal to API_sampleRate */ + ret += silk_resampler( &channel_state[ n ].resampler_state, resample_out_ptr, &samplesOut1_tmp[ n ][ 1 ], nSamplesOutDec ); + + /* Interleave if stereo output and stereo stream */ + if( decControl->nChannelsAPI == 2 ) { + for( i = 0; i < *nSamplesOut; i++ ) { + samplesOut[ n + 2 * i ] = resample_out_ptr[ i ]; + } + } + } + + /* Create two channel output from mono stream */ + if( decControl->nChannelsAPI == 2 && decControl->nChannelsInternal == 1 ) { + if ( stereo_to_mono ){ + /* Resample right channel for newly collapsed stereo just in case + we weren't doing collapsing when switching to mono */ + ret += silk_resampler( &channel_state[ 1 ].resampler_state, resample_out_ptr, &samplesOut1_tmp[ 0 ][ 1 ], nSamplesOutDec ); + + for( i = 0; i < *nSamplesOut; i++ ) { + samplesOut[ 1 + 2 * i ] = resample_out_ptr[ i ]; + } + } else { + for( i = 0; i < *nSamplesOut; i++ ) { + samplesOut[ 1 + 2 * i ] = samplesOut[ 0 + 2 * i ]; + } + } + } + + /* Export pitch lag, measured at 48 kHz sampling rate */ + if( channel_state[ 0 ].prevSignalType == TYPE_VOICED ) { + int mult_tab[ 3 ] = { 6, 4, 3 }; + decControl->prevPitchLag = channel_state[ 0 ].lagPrev * mult_tab[ ( channel_state[ 0 ].fs_kHz - 8 ) >> 2 ]; + } else { + decControl->prevPitchLag = 0; + } + + if( lostFlag == FLAG_PACKET_LOST ) { + /* On packet loss, remove the gain clamping to prevent having the energy "bounce back" + if we lose packets when the energy is going down */ + for ( i = 0; i < psDec->nChannelsInternal; i++ ) + psDec->channel_state[ i ].LastGainIndex = 10; + } else { + psDec->prev_decode_only_middle = decode_only_middle; + } + RESTORE_STACK; + return ret; +} + +#if 0 +/* Getting table of contents for a packet */ +opus_int silk_get_TOC( + const opus_uint8 *payload, /* I Payload data */ + const opus_int nBytesIn, /* I Number of input bytes */ + const opus_int nFramesPerPayload, /* I Number of SILK frames per payload */ + silk_TOC_struct *Silk_TOC /* O Type of content */ +) +{ + opus_int i, flags, ret = SILK_NO_ERROR; + + if( nBytesIn < 1 ) { + return -1; + } + if( nFramesPerPayload < 0 || nFramesPerPayload > 3 ) { + return -1; + } + + silk_memset( Silk_TOC, 0, sizeof( *Silk_TOC ) ); + + /* For stereo, extract the flags for the mid channel */ + flags = silk_RSHIFT( payload[ 0 ], 7 - nFramesPerPayload ) & ( silk_LSHIFT( 1, nFramesPerPayload + 1 ) - 1 ); + + Silk_TOC->inbandFECFlag = flags & 1; + for( i = nFramesPerPayload - 1; i >= 0 ; i-- ) { + flags = silk_RSHIFT( flags, 1 ); + Silk_TOC->VADFlags[ i ] = flags & 1; + Silk_TOC->VADFlag |= flags & 1; + } + + return ret; +} +#endif diff --git a/port/third_party/opus/silk/decode_core.c b/port/third_party/opus/silk/decode_core.c new file mode 100644 index 00000000..1c352a65 --- /dev/null +++ b/port/third_party/opus/silk/decode_core.c @@ -0,0 +1,237 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/**********************************************************/ +/* Core decoder. Performs inverse NSQ operation LTP + LPC */ +/**********************************************************/ +void silk_decode_core( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I Decoder control */ + opus_int16 xq[], /* O Decoded speech */ + const opus_int16 pulses[ MAX_FRAME_LENGTH ], /* I Pulse signal */ + int arch /* I Run-time architecture */ +) +{ + opus_int i, k, lag = 0, start_idx, sLTP_buf_idx, NLSF_interpolation_flag, signalType; + opus_int16 *A_Q12, *B_Q14, *pxq, A_Q12_tmp[ MAX_LPC_ORDER ]; + VARDECL( opus_int16, sLTP ); + VARDECL( opus_int32, sLTP_Q15 ); + opus_int32 LTP_pred_Q13, LPC_pred_Q10, Gain_Q10, inv_gain_Q31, gain_adj_Q16, rand_seed, offset_Q10; + opus_int32 *pred_lag_ptr, *pexc_Q14, *pres_Q14; + VARDECL( opus_int32, res_Q14 ); + VARDECL( opus_int32, sLPC_Q14 ); + SAVE_STACK; + + silk_assert( psDec->prev_gain_Q16 != 0 ); + + ALLOC( sLTP, psDec->ltp_mem_length, opus_int16 ); + ALLOC( sLTP_Q15, psDec->ltp_mem_length + psDec->frame_length, opus_int32 ); + ALLOC( res_Q14, psDec->subfr_length, opus_int32 ); + ALLOC( sLPC_Q14, psDec->subfr_length + MAX_LPC_ORDER, opus_int32 ); + + offset_Q10 = silk_Quantization_Offsets_Q10[ psDec->indices.signalType >> 1 ][ psDec->indices.quantOffsetType ]; + + if( psDec->indices.NLSFInterpCoef_Q2 < 1 << 2 ) { + NLSF_interpolation_flag = 1; + } else { + NLSF_interpolation_flag = 0; + } + + /* Decode excitation */ + rand_seed = psDec->indices.Seed; + for( i = 0; i < psDec->frame_length; i++ ) { + rand_seed = silk_RAND( rand_seed ); + psDec->exc_Q14[ i ] = silk_LSHIFT( (opus_int32)pulses[ i ], 14 ); + if( psDec->exc_Q14[ i ] > 0 ) { + psDec->exc_Q14[ i ] -= QUANT_LEVEL_ADJUST_Q10 << 4; + } else + if( psDec->exc_Q14[ i ] < 0 ) { + psDec->exc_Q14[ i ] += QUANT_LEVEL_ADJUST_Q10 << 4; + } + psDec->exc_Q14[ i ] += offset_Q10 << 4; + if( rand_seed < 0 ) { + psDec->exc_Q14[ i ] = -psDec->exc_Q14[ i ]; + } + + rand_seed = silk_ADD32_ovflw( rand_seed, pulses[ i ] ); + } + + /* Copy LPC state */ + silk_memcpy( sLPC_Q14, psDec->sLPC_Q14_buf, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + + pexc_Q14 = psDec->exc_Q14; + pxq = xq; + sLTP_buf_idx = psDec->ltp_mem_length; + /* Loop over subframes */ + for( k = 0; k < psDec->nb_subfr; k++ ) { + pres_Q14 = res_Q14; + A_Q12 = psDecCtrl->PredCoef_Q12[ k >> 1 ]; + + /* Preload LPC coeficients to array on stack. Gives small performance gain */ + silk_memcpy( A_Q12_tmp, A_Q12, psDec->LPC_order * sizeof( opus_int16 ) ); + B_Q14 = &psDecCtrl->LTPCoef_Q14[ k * LTP_ORDER ]; + signalType = psDec->indices.signalType; + + Gain_Q10 = silk_RSHIFT( psDecCtrl->Gains_Q16[ k ], 6 ); + inv_gain_Q31 = silk_INVERSE32_varQ( psDecCtrl->Gains_Q16[ k ], 47 ); + + /* Calculate gain adjustment factor */ + if( psDecCtrl->Gains_Q16[ k ] != psDec->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( psDec->prev_gain_Q16, psDecCtrl->Gains_Q16[ k ], 16 ); + + /* Scale short term state */ + for( i = 0; i < MAX_LPC_ORDER; i++ ) { + sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, sLPC_Q14[ i ] ); + } + } else { + gain_adj_Q16 = (opus_int32)1 << 16; + } + + /* Save inv_gain */ + silk_assert( inv_gain_Q31 != 0 ); + psDec->prev_gain_Q16 = psDecCtrl->Gains_Q16[ k ]; + + /* Avoid abrupt transition from voiced PLC to unvoiced normal decoding */ + if( psDec->lossCnt && psDec->prevSignalType == TYPE_VOICED && + psDec->indices.signalType != TYPE_VOICED && k < MAX_NB_SUBFR/2 ) { + + silk_memset( B_Q14, 0, LTP_ORDER * sizeof( opus_int16 ) ); + B_Q14[ LTP_ORDER/2 ] = SILK_FIX_CONST( 0.25, 14 ); + + signalType = TYPE_VOICED; + psDecCtrl->pitchL[ k ] = psDec->lagPrev; + } + + if( signalType == TYPE_VOICED ) { + /* Voiced */ + lag = psDecCtrl->pitchL[ k ]; + + /* Re-whitening */ + if( k == 0 || ( k == 2 && NLSF_interpolation_flag ) ) { + /* Rewhiten with new A coefs */ + start_idx = psDec->ltp_mem_length - lag - psDec->LPC_order - LTP_ORDER / 2; + celt_assert( start_idx > 0 ); + + if( k == 2 ) { + silk_memcpy( &psDec->outBuf[ psDec->ltp_mem_length ], xq, 2 * psDec->subfr_length * sizeof( opus_int16 ) ); + } + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &psDec->outBuf[ start_idx + k * psDec->subfr_length ], + A_Q12, psDec->ltp_mem_length - start_idx, psDec->LPC_order, arch ); + + /* After rewhitening the LTP state is unscaled */ + if( k == 0 ) { + /* Do LTP downscaling to reduce inter-packet dependency */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, psDecCtrl->LTP_scale_Q14 ), 2 ); + } + for( i = 0; i < lag + LTP_ORDER/2; i++ ) { + sLTP_Q15[ sLTP_buf_idx - i - 1 ] = silk_SMULWB( inv_gain_Q31, sLTP[ psDec->ltp_mem_length - i - 1 ] ); + } + } else { + /* Update LTP state when Gain changes */ + if( gain_adj_Q16 != (opus_int32)1 << 16 ) { + for( i = 0; i < lag + LTP_ORDER/2; i++ ) { + sLTP_Q15[ sLTP_buf_idx - i - 1 ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ sLTP_buf_idx - i - 1 ] ); + } + } + } + } + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Set up pointer */ + pred_lag_ptr = &sLTP_Q15[ sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + for( i = 0; i < psDec->subfr_length; i++ ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q13 = 2; + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ 0 ], B_Q14[ 0 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -1 ], B_Q14[ 1 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -2 ], B_Q14[ 2 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -3 ], B_Q14[ 3 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -4 ], B_Q14[ 4 ] ); + pred_lag_ptr++; + + /* Generate LPC excitation */ + pres_Q14[ i ] = silk_ADD_LSHIFT32( pexc_Q14[ i ], LTP_pred_Q13, 1 ); + + /* Update states */ + sLTP_Q15[ sLTP_buf_idx ] = silk_LSHIFT( pres_Q14[ i ], 1 ); + sLTP_buf_idx++; + } + } else { + pres_Q14 = pexc_Q14; + } + + for( i = 0; i < psDec->subfr_length; i++ ) { + /* Short-term prediction */ + celt_assert( psDec->LPC_order == 10 || psDec->LPC_order == 16 ); + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q10 = silk_RSHIFT( psDec->LPC_order, 1 ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 1 ], A_Q12_tmp[ 0 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 2 ], A_Q12_tmp[ 1 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 3 ], A_Q12_tmp[ 2 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 4 ], A_Q12_tmp[ 3 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 5 ], A_Q12_tmp[ 4 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 6 ], A_Q12_tmp[ 5 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 7 ], A_Q12_tmp[ 6 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 8 ], A_Q12_tmp[ 7 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 9 ], A_Q12_tmp[ 8 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 10 ], A_Q12_tmp[ 9 ] ); + if( psDec->LPC_order == 16 ) { + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 11 ], A_Q12_tmp[ 10 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 12 ], A_Q12_tmp[ 11 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 13 ], A_Q12_tmp[ 12 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 14 ], A_Q12_tmp[ 13 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 15 ], A_Q12_tmp[ 14 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 16 ], A_Q12_tmp[ 15 ] ); + } + + /* Add prediction to LPC excitation */ + sLPC_Q14[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( pres_Q14[ i ], silk_LSHIFT_SAT32( LPC_pred_Q10, 4 ) ); + + /* Scale with gain */ + pxq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( sLPC_Q14[ MAX_LPC_ORDER + i ], Gain_Q10 ), 8 ) ); + } + + /* Update LPC filter state */ + silk_memcpy( sLPC_Q14, &sLPC_Q14[ psDec->subfr_length ], MAX_LPC_ORDER * sizeof( opus_int32 ) ); + pexc_Q14 += psDec->subfr_length; + pxq += psDec->subfr_length; + } + + /* Save LPC state */ + silk_memcpy( psDec->sLPC_Q14_buf, sLPC_Q14, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + RESTORE_STACK; +} diff --git a/port/third_party/opus/silk/decode_frame.c b/port/third_party/opus/silk/decode_frame.c new file mode 100644 index 00000000..9bc4ca2b --- /dev/null +++ b/port/third_party/opus/silk/decode_frame.c @@ -0,0 +1,169 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" +#include "PLC.h" + +#ifdef ENABLE_OSCE +#include "osce.h" +#endif + +/****************/ +/* Decode frame */ +/****************/ +opus_int silk_decode_frame( + silk_decoder_state *psDec, /* I/O Pointer to Silk decoder state */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pOut[], /* O Pointer to output speech frame */ + opus_int32 *pN, /* O Pointer to size of output frame */ + opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ + opus_int condCoding, /* I The type of conditional coding to use */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif +#ifdef ENABLE_OSCE + OSCEModel *osce_model, +#endif + int arch /* I Run-time architecture */ +) +{ + VARDECL( silk_decoder_control, psDecCtrl ); + opus_int L, mv_len, ret = 0; + SAVE_STACK; + + L = psDec->frame_length; + ALLOC( psDecCtrl, 1, silk_decoder_control ); + psDecCtrl->LTP_scale_Q14 = 0; + + /* Safety checks */ + celt_assert( L > 0 && L <= MAX_FRAME_LENGTH ); + + if( lostFlag == FLAG_DECODE_NORMAL || + ( lostFlag == FLAG_DECODE_LBRR && psDec->LBRR_flags[ psDec->nFramesDecoded ] == 1 ) ) + { + VARDECL( opus_int16, pulses ); +#ifdef ENABLE_OSCE + opus_int32 ec_start; + ec_start = ec_tell(psRangeDec); +#endif + ALLOC( pulses, (L + SHELL_CODEC_FRAME_LENGTH - 1) & + ~(SHELL_CODEC_FRAME_LENGTH - 1), opus_int16 ); + /*********************************************/ + /* Decode quantization indices of side info */ + /*********************************************/ + silk_decode_indices( psDec, psRangeDec, psDec->nFramesDecoded, lostFlag, condCoding ); + + /*********************************************/ + /* Decode quantization indices of excitation */ + /*********************************************/ + silk_decode_pulses( psRangeDec, pulses, psDec->indices.signalType, + psDec->indices.quantOffsetType, psDec->frame_length ); + + /********************************************/ + /* Decode parameters and pulse signal */ + /********************************************/ + silk_decode_parameters( psDec, psDecCtrl, condCoding ); + + /********************************************************/ + /* Run inverse NSQ */ + /********************************************************/ + silk_decode_core( psDec, psDecCtrl, pOut, pulses, arch ); + + /*************************/ + /* Update output buffer. */ + /*************************/ + celt_assert( psDec->ltp_mem_length >= psDec->frame_length ); + mv_len = psDec->ltp_mem_length - psDec->frame_length; + silk_memmove( psDec->outBuf, &psDec->outBuf[ psDec->frame_length ], mv_len * sizeof(opus_int16) ); + silk_memcpy( &psDec->outBuf[ mv_len ], pOut, psDec->frame_length * sizeof( opus_int16 ) ); + +#ifdef ENABLE_OSCE + /********************************************************/ + /* Run SILK enhancer */ + /********************************************************/ + osce_enhance_frame( osce_model, psDec, psDecCtrl, pOut, ec_tell(psRangeDec) - ec_start, arch ); +#endif + + /********************************************************/ + /* Update PLC state */ + /********************************************************/ + silk_PLC( psDec, psDecCtrl, pOut, 0, +#ifdef ENABLE_DEEP_PLC + lpcnet, +#endif + arch ); + + psDec->lossCnt = 0; + psDec->prevSignalType = psDec->indices.signalType; + celt_assert( psDec->prevSignalType >= 0 && psDec->prevSignalType <= 2 ); + + /* A frame has been decoded without errors */ + psDec->first_frame_after_reset = 0; + } else { + /* Handle packet loss by extrapolation */ + silk_PLC( psDec, psDecCtrl, pOut, 1, +#ifdef ENABLE_DEEP_PLC + lpcnet, +#endif + arch ); + +#ifdef ENABLE_OSCE + osce_reset( &psDec->osce, psDec->osce.method ); +#endif + /*************************/ + /* Update output buffer. */ + /*************************/ + celt_assert( psDec->ltp_mem_length >= psDec->frame_length ); + mv_len = psDec->ltp_mem_length - psDec->frame_length; + silk_memmove( psDec->outBuf, &psDec->outBuf[ psDec->frame_length ], mv_len * sizeof(opus_int16) ); + silk_memcpy( &psDec->outBuf[ mv_len ], pOut, psDec->frame_length * sizeof( opus_int16 ) ); + } + + /************************************************/ + /* Comfort noise generation / estimation */ + /************************************************/ + silk_CNG( psDec, psDecCtrl, pOut, L ); + + /****************************************************************/ + /* Ensure smooth connection of extrapolated and good frames */ + /****************************************************************/ + silk_PLC_glue_frames( psDec, pOut, L ); + + /* Update some decoder state variables */ + psDec->lagPrev = psDecCtrl->pitchL[ psDec->nb_subfr - 1 ]; + + /* Set output frame length */ + *pN = L; + + RESTORE_STACK; + return ret; +} diff --git a/port/third_party/opus/silk/decode_indices.c b/port/third_party/opus/silk/decode_indices.c new file mode 100644 index 00000000..0bb4a997 --- /dev/null +++ b/port/third_party/opus/silk/decode_indices.c @@ -0,0 +1,151 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Decode side-information parameters from payload */ +void silk_decode_indices( + silk_decoder_state *psDec, /* I/O State */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int FrameIndex, /* I Frame number */ + opus_int decode_LBRR, /* I Flag indicating LBRR data is being decoded */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i, k, Ix; + opus_int decode_absolute_lagIndex, delta_lagIndex; + opus_int16 ec_ix[ MAX_LPC_ORDER ]; + opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; + + /*******************************************/ + /* Decode signal type and quantizer offset */ + /*******************************************/ + if( decode_LBRR || psDec->VAD_flags[ FrameIndex ] ) { + Ix = ec_dec_icdf( psRangeDec, silk_type_offset_VAD_iCDF, 8 ) + 2; + } else { + Ix = ec_dec_icdf( psRangeDec, silk_type_offset_no_VAD_iCDF, 8 ); + } + psDec->indices.signalType = (opus_int8)silk_RSHIFT( Ix, 1 ); + psDec->indices.quantOffsetType = (opus_int8)( Ix & 1 ); + + /****************/ + /* Decode gains */ + /****************/ + /* First subframe */ + if( condCoding == CODE_CONDITIONALLY ) { + /* Conditional coding */ + psDec->indices.GainsIndices[ 0 ] = (opus_int8)ec_dec_icdf( psRangeDec, silk_delta_gain_iCDF, 8 ); + } else { + /* Independent coding, in two stages: MSB bits followed by 3 LSBs */ + psDec->indices.GainsIndices[ 0 ] = (opus_int8)silk_LSHIFT( ec_dec_icdf( psRangeDec, silk_gain_iCDF[ psDec->indices.signalType ], 8 ), 3 ); + psDec->indices.GainsIndices[ 0 ] += (opus_int8)ec_dec_icdf( psRangeDec, silk_uniform8_iCDF, 8 ); + } + + /* Remaining subframes */ + for( i = 1; i < psDec->nb_subfr; i++ ) { + psDec->indices.GainsIndices[ i ] = (opus_int8)ec_dec_icdf( psRangeDec, silk_delta_gain_iCDF, 8 ); + } + + /**********************/ + /* Decode LSF Indices */ + /**********************/ + psDec->indices.NLSFIndices[ 0 ] = (opus_int8)ec_dec_icdf( psRangeDec, &psDec->psNLSF_CB->CB1_iCDF[ ( psDec->indices.signalType >> 1 ) * psDec->psNLSF_CB->nVectors ], 8 ); + silk_NLSF_unpack( ec_ix, pred_Q8, psDec->psNLSF_CB, psDec->indices.NLSFIndices[ 0 ] ); + celt_assert( psDec->psNLSF_CB->order == psDec->LPC_order ); + for( i = 0; i < psDec->psNLSF_CB->order; i++ ) { + Ix = ec_dec_icdf( psRangeDec, &psDec->psNLSF_CB->ec_iCDF[ ec_ix[ i ] ], 8 ); + if( Ix == 0 ) { + Ix -= ec_dec_icdf( psRangeDec, silk_NLSF_EXT_iCDF, 8 ); + } else if( Ix == 2 * NLSF_QUANT_MAX_AMPLITUDE ) { + Ix += ec_dec_icdf( psRangeDec, silk_NLSF_EXT_iCDF, 8 ); + } + psDec->indices.NLSFIndices[ i+1 ] = (opus_int8)( Ix - NLSF_QUANT_MAX_AMPLITUDE ); + } + + /* Decode LSF interpolation factor */ + if( psDec->nb_subfr == MAX_NB_SUBFR ) { + psDec->indices.NLSFInterpCoef_Q2 = (opus_int8)ec_dec_icdf( psRangeDec, silk_NLSF_interpolation_factor_iCDF, 8 ); + } else { + psDec->indices.NLSFInterpCoef_Q2 = 4; + } + + if( psDec->indices.signalType == TYPE_VOICED ) + { + /*********************/ + /* Decode pitch lags */ + /*********************/ + /* Get lag index */ + decode_absolute_lagIndex = 1; + if( condCoding == CODE_CONDITIONALLY && psDec->ec_prevSignalType == TYPE_VOICED ) { + /* Decode Delta index */ + delta_lagIndex = (opus_int16)ec_dec_icdf( psRangeDec, silk_pitch_delta_iCDF, 8 ); + if( delta_lagIndex > 0 ) { + delta_lagIndex = delta_lagIndex - 9; + psDec->indices.lagIndex = (opus_int16)( psDec->ec_prevLagIndex + delta_lagIndex ); + decode_absolute_lagIndex = 0; + } + } + if( decode_absolute_lagIndex ) { + /* Absolute decoding */ + psDec->indices.lagIndex = (opus_int16)ec_dec_icdf( psRangeDec, silk_pitch_lag_iCDF, 8 ) * silk_RSHIFT( psDec->fs_kHz, 1 ); + psDec->indices.lagIndex += (opus_int16)ec_dec_icdf( psRangeDec, psDec->pitch_lag_low_bits_iCDF, 8 ); + } + psDec->ec_prevLagIndex = psDec->indices.lagIndex; + + /* Get countour index */ + psDec->indices.contourIndex = (opus_int8)ec_dec_icdf( psRangeDec, psDec->pitch_contour_iCDF, 8 ); + + /********************/ + /* Decode LTP gains */ + /********************/ + /* Decode PERIndex value */ + psDec->indices.PERIndex = (opus_int8)ec_dec_icdf( psRangeDec, silk_LTP_per_index_iCDF, 8 ); + + for( k = 0; k < psDec->nb_subfr; k++ ) { + psDec->indices.LTPIndex[ k ] = (opus_int8)ec_dec_icdf( psRangeDec, silk_LTP_gain_iCDF_ptrs[ psDec->indices.PERIndex ], 8 ); + } + + /**********************/ + /* Decode LTP scaling */ + /**********************/ + if( condCoding == CODE_INDEPENDENTLY ) { + psDec->indices.LTP_scaleIndex = (opus_int8)ec_dec_icdf( psRangeDec, silk_LTPscale_iCDF, 8 ); + } else { + psDec->indices.LTP_scaleIndex = 0; + } + } + psDec->ec_prevSignalType = psDec->indices.signalType; + + /***************/ + /* Decode seed */ + /***************/ + psDec->indices.Seed = (opus_int8)ec_dec_icdf( psRangeDec, silk_uniform4_iCDF, 8 ); +} diff --git a/port/third_party/opus/silk/decode_parameters.c b/port/third_party/opus/silk/decode_parameters.c new file mode 100644 index 00000000..a56a4098 --- /dev/null +++ b/port/third_party/opus/silk/decode_parameters.c @@ -0,0 +1,115 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Decode parameters from payload */ +void silk_decode_parameters( + silk_decoder_state *psDec, /* I/O State */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i, k, Ix; + opus_int16 pNLSF_Q15[ MAX_LPC_ORDER ], pNLSF0_Q15[ MAX_LPC_ORDER ]; + const opus_int8 *cbk_ptr_Q7; + + /* Dequant Gains */ + silk_gains_dequant( psDecCtrl->Gains_Q16, psDec->indices.GainsIndices, + &psDec->LastGainIndex, condCoding == CODE_CONDITIONALLY, psDec->nb_subfr ); + + /****************/ + /* Decode NLSFs */ + /****************/ + silk_NLSF_decode( pNLSF_Q15, psDec->indices.NLSFIndices, psDec->psNLSF_CB ); + + /* Convert NLSF parameters to AR prediction filter coefficients */ + silk_NLSF2A( psDecCtrl->PredCoef_Q12[ 1 ], pNLSF_Q15, psDec->LPC_order, psDec->arch ); + + /* If just reset, e.g., because internal Fs changed, do not allow interpolation */ + /* improves the case of packet loss in the first frame after a switch */ + if( psDec->first_frame_after_reset == 1 ) { + psDec->indices.NLSFInterpCoef_Q2 = 4; + } + + if( psDec->indices.NLSFInterpCoef_Q2 < 4 ) { + /* Calculation of the interpolated NLSF0 vector from the interpolation factor, */ + /* the previous NLSF1, and the current NLSF1 */ + for( i = 0; i < psDec->LPC_order; i++ ) { + pNLSF0_Q15[ i ] = psDec->prevNLSF_Q15[ i ] + silk_RSHIFT( silk_MUL( psDec->indices.NLSFInterpCoef_Q2, + pNLSF_Q15[ i ] - psDec->prevNLSF_Q15[ i ] ), 2 ); + } + + /* Convert NLSF parameters to AR prediction filter coefficients */ + silk_NLSF2A( psDecCtrl->PredCoef_Q12[ 0 ], pNLSF0_Q15, psDec->LPC_order, psDec->arch ); + } else { + /* Copy LPC coefficients for first half from second half */ + silk_memcpy( psDecCtrl->PredCoef_Q12[ 0 ], psDecCtrl->PredCoef_Q12[ 1 ], psDec->LPC_order * sizeof( opus_int16 ) ); + } + + silk_memcpy( psDec->prevNLSF_Q15, pNLSF_Q15, psDec->LPC_order * sizeof( opus_int16 ) ); + + /* After a packet loss do BWE of LPC coefs */ + if( psDec->lossCnt ) { + silk_bwexpander( psDecCtrl->PredCoef_Q12[ 0 ], psDec->LPC_order, BWE_AFTER_LOSS_Q16 ); + silk_bwexpander( psDecCtrl->PredCoef_Q12[ 1 ], psDec->LPC_order, BWE_AFTER_LOSS_Q16 ); + } + + if( psDec->indices.signalType == TYPE_VOICED ) { + /*********************/ + /* Decode pitch lags */ + /*********************/ + + /* Decode pitch values */ + silk_decode_pitch( psDec->indices.lagIndex, psDec->indices.contourIndex, psDecCtrl->pitchL, psDec->fs_kHz, psDec->nb_subfr ); + + /* Decode Codebook Index */ + cbk_ptr_Q7 = silk_LTP_vq_ptrs_Q7[ psDec->indices.PERIndex ]; /* set pointer to start of codebook */ + + for( k = 0; k < psDec->nb_subfr; k++ ) { + Ix = psDec->indices.LTPIndex[ k ]; + for( i = 0; i < LTP_ORDER; i++ ) { + psDecCtrl->LTPCoef_Q14[ k * LTP_ORDER + i ] = silk_LSHIFT( cbk_ptr_Q7[ Ix * LTP_ORDER + i ], 7 ); + } + } + + /**********************/ + /* Decode LTP scaling */ + /**********************/ + Ix = psDec->indices.LTP_scaleIndex; + psDecCtrl->LTP_scale_Q14 = silk_LTPScales_table_Q14[ Ix ]; + } else { + silk_memset( psDecCtrl->pitchL, 0, psDec->nb_subfr * sizeof( opus_int ) ); + silk_memset( psDecCtrl->LTPCoef_Q14, 0, LTP_ORDER * psDec->nb_subfr * sizeof( opus_int16 ) ); + psDec->indices.PERIndex = 0; + psDecCtrl->LTP_scale_Q14 = 0; + } +} diff --git a/port/third_party/opus/silk/decode_pitch.c b/port/third_party/opus/silk/decode_pitch.c new file mode 100644 index 00000000..fd1b6bf5 --- /dev/null +++ b/port/third_party/opus/silk/decode_pitch.c @@ -0,0 +1,77 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/*********************************************************** +* Pitch analyser function +********************************************************** */ +#include "SigProc_FIX.h" +#include "pitch_est_defines.h" + +void silk_decode_pitch( + opus_int16 lagIndex, /* I */ + opus_int8 contourIndex, /* O */ + opus_int pitch_lags[], /* O 4 pitch values */ + const opus_int Fs_kHz, /* I sampling frequency (kHz) */ + const opus_int nb_subfr /* I number of sub frames */ +) +{ + opus_int lag, k, min_lag, max_lag, cbk_size; + const opus_int8 *Lag_CB_ptr; + + if( Fs_kHz == 8 ) { + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_CB_ptr = &silk_CB_lags_stage2[ 0 ][ 0 ]; + cbk_size = PE_NB_CBKS_STAGE2_EXT; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1 ); + Lag_CB_ptr = &silk_CB_lags_stage2_10_ms[ 0 ][ 0 ]; + cbk_size = PE_NB_CBKS_STAGE2_10MS; + } + } else { + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1 ); + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + } + } + + min_lag = silk_SMULBB( PE_MIN_LAG_MS, Fs_kHz ); + max_lag = silk_SMULBB( PE_MAX_LAG_MS, Fs_kHz ); + lag = min_lag + lagIndex; + + for( k = 0; k < nb_subfr; k++ ) { + pitch_lags[ k ] = lag + matrix_ptr( Lag_CB_ptr, k, contourIndex, cbk_size ); + pitch_lags[ k ] = silk_LIMIT( pitch_lags[ k ], min_lag, max_lag ); + } +} diff --git a/port/third_party/opus/silk/decode_pulses.c b/port/third_party/opus/silk/decode_pulses.c new file mode 100644 index 00000000..a56d2d30 --- /dev/null +++ b/port/third_party/opus/silk/decode_pulses.c @@ -0,0 +1,115 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/*********************************************/ +/* Decode quantization indices of excitation */ +/*********************************************/ +void silk_decode_pulses( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* O Excitation signal */ + const opus_int signalType, /* I Sigtype */ + const opus_int quantOffsetType, /* I quantOffsetType */ + const opus_int frame_length /* I Frame length */ +) +{ + opus_int i, j, k, iter, abs_q, nLS, RateLevelIndex; + opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ], nLshifts[ MAX_NB_SHELL_BLOCKS ]; + opus_int16 *pulses_ptr; + const opus_uint8 *cdf_ptr; + + /*********************/ + /* Decode rate level */ + /*********************/ + RateLevelIndex = ec_dec_icdf( psRangeDec, silk_rate_levels_iCDF[ signalType >> 1 ], 8 ); + + /* Calculate number of shell blocks */ + silk_assert( 1 << LOG2_SHELL_CODEC_FRAME_LENGTH == SHELL_CODEC_FRAME_LENGTH ); + iter = silk_RSHIFT( frame_length, LOG2_SHELL_CODEC_FRAME_LENGTH ); + if( iter * SHELL_CODEC_FRAME_LENGTH < frame_length ) { + celt_assert( frame_length == 12 * 10 ); /* Make sure only happens for 10 ms @ 12 kHz */ + iter++; + } + + /***************************************************/ + /* Sum-Weighted-Pulses Decoding */ + /***************************************************/ + cdf_ptr = silk_pulses_per_block_iCDF[ RateLevelIndex ]; + for( i = 0; i < iter; i++ ) { + nLshifts[ i ] = 0; + sum_pulses[ i ] = ec_dec_icdf( psRangeDec, cdf_ptr, 8 ); + + /* LSB indication */ + while( sum_pulses[ i ] == SILK_MAX_PULSES + 1 ) { + nLshifts[ i ]++; + /* When we've already got 10 LSBs, we shift the table to not allow (SILK_MAX_PULSES + 1) */ + sum_pulses[ i ] = ec_dec_icdf( psRangeDec, + silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1] + ( nLshifts[ i ] == 10 ), 8 ); + } + } + + /***************************************************/ + /* Shell decoding */ + /***************************************************/ + for( i = 0; i < iter; i++ ) { + if( sum_pulses[ i ] > 0 ) { + silk_shell_decoder( &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ], psRangeDec, sum_pulses[ i ] ); + } else { + silk_memset( &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ], 0, SHELL_CODEC_FRAME_LENGTH * sizeof( pulses[0] ) ); + } + } + + /***************************************************/ + /* LSB Decoding */ + /***************************************************/ + for( i = 0; i < iter; i++ ) { + if( nLshifts[ i ] > 0 ) { + nLS = nLshifts[ i ]; + pulses_ptr = &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ]; + for( k = 0; k < SHELL_CODEC_FRAME_LENGTH; k++ ) { + abs_q = pulses_ptr[ k ]; + for( j = 0; j < nLS; j++ ) { + abs_q = silk_LSHIFT( abs_q, 1 ); + abs_q += ec_dec_icdf( psRangeDec, silk_lsb_iCDF, 8 ); + } + pulses_ptr[ k ] = abs_q; + } + /* Mark the number of pulses non-zero for sign decoding. */ + sum_pulses[ i ] |= nLS << 5; + } + } + + /****************************************/ + /* Decode and add signs to pulse signal */ + /****************************************/ + silk_decode_signs( psRangeDec, pulses, frame_length, signalType, quantOffsetType, sum_pulses ); +} diff --git a/port/third_party/opus/silk/decoder_set_fs.c b/port/third_party/opus/silk/decoder_set_fs.c new file mode 100644 index 00000000..d9a13d0f --- /dev/null +++ b/port/third_party/opus/silk/decoder_set_fs.c @@ -0,0 +1,108 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Set decoder sampling rate */ +opus_int silk_decoder_set_fs( + silk_decoder_state *psDec, /* I/O Decoder state pointer */ + opus_int fs_kHz, /* I Sampling frequency (kHz) */ + opus_int32 fs_API_Hz /* I API Sampling frequency (Hz) */ +) +{ + opus_int frame_length, ret = 0; + + celt_assert( fs_kHz == 8 || fs_kHz == 12 || fs_kHz == 16 ); + celt_assert( psDec->nb_subfr == MAX_NB_SUBFR || psDec->nb_subfr == MAX_NB_SUBFR/2 ); + + /* New (sub)frame length */ + psDec->subfr_length = silk_SMULBB( SUB_FRAME_LENGTH_MS, fs_kHz ); + frame_length = silk_SMULBB( psDec->nb_subfr, psDec->subfr_length ); + + /* Initialize resampler when switching internal or external sampling frequency */ + if( psDec->fs_kHz != fs_kHz || psDec->fs_API_hz != fs_API_Hz ) { + /* Initialize the resampler for dec_API.c preparing resampling from fs_kHz to API_fs_Hz */ + ret += silk_resampler_init( &psDec->resampler_state, silk_SMULBB( fs_kHz, 1000 ), fs_API_Hz, 0 ); + + psDec->fs_API_hz = fs_API_Hz; + } + + if( psDec->fs_kHz != fs_kHz || frame_length != psDec->frame_length ) { + if( fs_kHz == 8 ) { + if( psDec->nb_subfr == MAX_NB_SUBFR ) { + psDec->pitch_contour_iCDF = silk_pitch_contour_NB_iCDF; + } else { + psDec->pitch_contour_iCDF = silk_pitch_contour_10_ms_NB_iCDF; + } + } else { + if( psDec->nb_subfr == MAX_NB_SUBFR ) { + psDec->pitch_contour_iCDF = silk_pitch_contour_iCDF; + } else { + psDec->pitch_contour_iCDF = silk_pitch_contour_10_ms_iCDF; + } + } + if( psDec->fs_kHz != fs_kHz ) { + psDec->ltp_mem_length = silk_SMULBB( LTP_MEM_LENGTH_MS, fs_kHz ); + if( fs_kHz == 8 || fs_kHz == 12 ) { + psDec->LPC_order = MIN_LPC_ORDER; + psDec->psNLSF_CB = &silk_NLSF_CB_NB_MB; + } else { + psDec->LPC_order = MAX_LPC_ORDER; + psDec->psNLSF_CB = &silk_NLSF_CB_WB; + } + if( fs_kHz == 16 ) { + psDec->pitch_lag_low_bits_iCDF = silk_uniform8_iCDF; + } else if( fs_kHz == 12 ) { + psDec->pitch_lag_low_bits_iCDF = silk_uniform6_iCDF; + } else if( fs_kHz == 8 ) { + psDec->pitch_lag_low_bits_iCDF = silk_uniform4_iCDF; + } else { + /* unsupported sampling rate */ + celt_assert( 0 ); + } + psDec->first_frame_after_reset = 1; + psDec->lagPrev = 100; + psDec->LastGainIndex = 10; + psDec->prevSignalType = TYPE_NO_VOICE_ACTIVITY; + silk_memset( psDec->outBuf, 0, sizeof(psDec->outBuf)); + silk_memset( psDec->sLPC_Q14_buf, 0, sizeof(psDec->sLPC_Q14_buf) ); + } + + psDec->fs_kHz = fs_kHz; + psDec->frame_length = frame_length; + } + + /* Check that settings are valid */ + celt_assert( psDec->frame_length > 0 && psDec->frame_length <= MAX_FRAME_LENGTH ); + + return ret; +} + diff --git a/port/third_party/opus/silk/define.h b/port/third_party/opus/silk/define.h new file mode 100644 index 00000000..491c86f3 --- /dev/null +++ b/port/third_party/opus/silk/define.h @@ -0,0 +1,235 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_DEFINE_H +#define SILK_DEFINE_H + +#include "errors.h" +#include "typedef.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Max number of encoder channels (1/2) */ +#define ENCODER_NUM_CHANNELS 2 +/* Number of decoder channels (1/2) */ +#define DECODER_NUM_CHANNELS 2 + +#define MAX_FRAMES_PER_PACKET 3 + +/* Limits on bitrate */ +#define MIN_TARGET_RATE_BPS 5000 +#define MAX_TARGET_RATE_BPS 80000 + +/* LBRR thresholds */ +#define LBRR_NB_MIN_RATE_BPS 12000 +#define LBRR_MB_MIN_RATE_BPS 14000 +#define LBRR_WB_MIN_RATE_BPS 16000 + +/* DTX settings */ +#define NB_SPEECH_FRAMES_BEFORE_DTX 10 /* eq 200 ms */ +#define MAX_CONSECUTIVE_DTX 20 /* eq 400 ms */ +#define DTX_ACTIVITY_THRESHOLD 0.1f + +/* VAD decision */ +#define VAD_NO_DECISION -1 +#define VAD_NO_ACTIVITY 0 +#define VAD_ACTIVITY 1 + +/* Maximum sampling frequency */ +#define MAX_FS_KHZ 16 +#define MAX_API_FS_KHZ 48 + +/* Signal types */ +#define TYPE_NO_VOICE_ACTIVITY 0 +#define TYPE_UNVOICED 1 +#define TYPE_VOICED 2 + +/* Conditional coding types */ +#define CODE_INDEPENDENTLY 0 +#define CODE_INDEPENDENTLY_NO_LTP_SCALING 1 +#define CODE_CONDITIONALLY 2 + +/* Settings for stereo processing */ +#define STEREO_QUANT_TAB_SIZE 16 +#define STEREO_QUANT_SUB_STEPS 5 +#define STEREO_INTERP_LEN_MS 8 /* must be even */ +#define STEREO_RATIO_SMOOTH_COEF 0.01 /* smoothing coef for signal norms and stereo width */ + +/* Range of pitch lag estimates */ +#define PITCH_EST_MIN_LAG_MS 2 /* 2 ms -> 500 Hz */ +#define PITCH_EST_MAX_LAG_MS 18 /* 18 ms -> 56 Hz */ + +/* Maximum number of subframes */ +#define MAX_NB_SUBFR 4 + +/* Number of samples per frame */ +#define LTP_MEM_LENGTH_MS 20 +#define SUB_FRAME_LENGTH_MS 5 +#define MAX_SUB_FRAME_LENGTH ( SUB_FRAME_LENGTH_MS * MAX_FS_KHZ ) +#define MAX_FRAME_LENGTH_MS ( SUB_FRAME_LENGTH_MS * MAX_NB_SUBFR ) +#define MAX_FRAME_LENGTH ( MAX_FRAME_LENGTH_MS * MAX_FS_KHZ ) + +/* Milliseconds of lookahead for pitch analysis */ +#define LA_PITCH_MS 2 +#define LA_PITCH_MAX ( LA_PITCH_MS * MAX_FS_KHZ ) + +/* Order of LPC used in find pitch */ +#define MAX_FIND_PITCH_LPC_ORDER 16 + +/* Length of LPC window used in find pitch */ +#define FIND_PITCH_LPC_WIN_MS ( 20 + (LA_PITCH_MS << 1) ) +#define FIND_PITCH_LPC_WIN_MS_2_SF ( 10 + (LA_PITCH_MS << 1) ) +#define FIND_PITCH_LPC_WIN_MAX ( FIND_PITCH_LPC_WIN_MS * MAX_FS_KHZ ) + +/* Milliseconds of lookahead for noise shape analysis */ +#define LA_SHAPE_MS 5 +#define LA_SHAPE_MAX ( LA_SHAPE_MS * MAX_FS_KHZ ) + +/* Maximum length of LPC window used in noise shape analysis */ +#define SHAPE_LPC_WIN_MAX ( 15 * MAX_FS_KHZ ) + +/* dB level of lowest gain quantization level */ +#define MIN_QGAIN_DB 2 +/* dB level of highest gain quantization level */ +#define MAX_QGAIN_DB 88 +/* Number of gain quantization levels */ +#define N_LEVELS_QGAIN 64 +/* Max increase in gain quantization index */ +#define MAX_DELTA_GAIN_QUANT 36 +/* Max decrease in gain quantization index */ +#define MIN_DELTA_GAIN_QUANT -4 + +/* Quantization offsets (multiples of 4) */ +#define OFFSET_VL_Q10 32 +#define OFFSET_VH_Q10 100 +#define OFFSET_UVL_Q10 100 +#define OFFSET_UVH_Q10 240 + +#define QUANT_LEVEL_ADJUST_Q10 80 + +/* Maximum numbers of iterations used to stabilize an LPC vector */ +#define MAX_LPC_STABILIZE_ITERATIONS 16 +#define MAX_PREDICTION_POWER_GAIN 1e4f +#define MAX_PREDICTION_POWER_GAIN_AFTER_RESET 1e2f + +#define MAX_LPC_ORDER 16 +#define MIN_LPC_ORDER 10 + +/* Find Pred Coef defines */ +#define LTP_ORDER 5 + +/* LTP quantization settings */ +#define NB_LTP_CBKS 3 + +/* Flag to use harmonic noise shaping */ +#define USE_HARM_SHAPING 1 + +/* Max LPC order of noise shaping filters */ +#define MAX_SHAPE_LPC_ORDER 24 + +#define HARM_SHAPE_FIR_TAPS 3 + +/* Maximum number of delayed decision states */ +#define MAX_DEL_DEC_STATES 4 + +#define LTP_BUF_LENGTH 512 +#define LTP_MASK ( LTP_BUF_LENGTH - 1 ) + +#define DECISION_DELAY 40 + +/* Number of subframes for excitation entropy coding */ +#define SHELL_CODEC_FRAME_LENGTH 16 +#define LOG2_SHELL_CODEC_FRAME_LENGTH 4 +#define MAX_NB_SHELL_BLOCKS ( MAX_FRAME_LENGTH / SHELL_CODEC_FRAME_LENGTH ) + +/* Number of rate levels, for entropy coding of excitation */ +#define N_RATE_LEVELS 10 + +/* Maximum sum of pulses per shell coding frame */ +#define SILK_MAX_PULSES 16 + +#define MAX_MATRIX_SIZE MAX_LPC_ORDER /* Max of LPC Order and LTP order */ + +# define NSQ_LPC_BUF_LENGTH MAX_LPC_ORDER + +/***************************/ +/* Voice activity detector */ +/***************************/ +#define VAD_N_BANDS 4 + +#define VAD_INTERNAL_SUBFRAMES_LOG2 2 +#define VAD_INTERNAL_SUBFRAMES ( 1 << VAD_INTERNAL_SUBFRAMES_LOG2 ) + +#define VAD_NOISE_LEVEL_SMOOTH_COEF_Q16 1024 /* Must be < 4096 */ +#define VAD_NOISE_LEVELS_BIAS 50 + +/* Sigmoid settings */ +#define VAD_NEGATIVE_OFFSET_Q5 128 /* sigmoid is 0 at -128 */ +#define VAD_SNR_FACTOR_Q16 45000 + +/* smoothing for SNR measurement */ +#define VAD_SNR_SMOOTH_COEF_Q18 4096 + +/* Size of the piecewise linear cosine approximation table for the LSFs */ +#define LSF_COS_TAB_SZ_FIX 128 + +/******************/ +/* NLSF quantizer */ +/******************/ +#define NLSF_W_Q 2 +#define NLSF_VQ_MAX_VECTORS 32 +#define NLSF_QUANT_MAX_AMPLITUDE 4 +#define NLSF_QUANT_MAX_AMPLITUDE_EXT 10 +#define NLSF_QUANT_LEVEL_ADJ 0.1 +#define NLSF_QUANT_DEL_DEC_STATES_LOG2 2 +#define NLSF_QUANT_DEL_DEC_STATES ( 1 << NLSF_QUANT_DEL_DEC_STATES_LOG2 ) + +/* Transition filtering for mode switching */ +#define TRANSITION_TIME_MS 5120 /* 5120 = 64 * FRAME_LENGTH_MS * ( TRANSITION_INT_NUM - 1 ) = 64*(20*4)*/ +#define TRANSITION_NB 3 /* Hardcoded in tables */ +#define TRANSITION_NA 2 /* Hardcoded in tables */ +#define TRANSITION_INT_NUM 5 /* Hardcoded in tables */ +#define TRANSITION_FRAMES ( TRANSITION_TIME_MS / MAX_FRAME_LENGTH_MS ) +#define TRANSITION_INT_STEPS ( TRANSITION_FRAMES / ( TRANSITION_INT_NUM - 1 ) ) + +/* BWE factors to apply after packet loss */ +#define BWE_AFTER_LOSS_Q16 63570 + +/* Defines for CN generation */ +#define CNG_BUF_MASK_MAX 255 /* 2^floor(log2(MAX_FRAME_LENGTH))-1 */ +#define CNG_GAIN_SMTH_Q16 4634 /* 0.25^(1/4) */ +#define CNG_GAIN_SMTH_THRESHOLD_Q16 46396 /* -3 dB */ +#define CNG_NLSF_SMTH_Q16 16348 /* 0.25 */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/silk/enc_API.c b/port/third_party/opus/silk/enc_API.c new file mode 100644 index 00000000..369caddd --- /dev/null +++ b/port/third_party/opus/silk/enc_API.c @@ -0,0 +1,591 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "define.h" +#include "API.h" +#include "control.h" +#include "typedef.h" +#include "stack_alloc.h" +#include "structs.h" +#include "tuning_parameters.h" +#ifdef FIXED_POINT +#include "main_FIX.h" +#else +#include "main_FLP.h" +#endif + +#ifdef ENABLE_DRED +#include "dred_encoder.h" +#endif + +/***************************************/ +/* Read control structure from encoder */ +/***************************************/ +static opus_int silk_QueryEncoder( /* O Returns error code */ + const void *encState, /* I State */ + silk_EncControlStruct *encStatus /* O Encoder Status */ +); + +/****************************************/ +/* Encoder functions */ +/****************************************/ + +opus_int silk_Get_Encoder_Size( /* O Returns error code */ + opus_int *encSizeBytes /* O Number of bytes in SILK encoder state */ +) +{ + opus_int ret = SILK_NO_ERROR; + + *encSizeBytes = sizeof( silk_encoder ); + + return ret; +} + +/*************************/ +/* Init or Reset encoder */ +/*************************/ +opus_int silk_InitEncoder( /* O Returns error code */ + void *encState, /* I/O State */ + int arch, /* I Run-time architecture */ + silk_EncControlStruct *encStatus /* O Encoder Status */ +) +{ + silk_encoder *psEnc; + opus_int n, ret = SILK_NO_ERROR; + + psEnc = (silk_encoder *)encState; + + /* Reset encoder */ + silk_memset( psEnc, 0, sizeof( silk_encoder ) ); + for( n = 0; n < ENCODER_NUM_CHANNELS; n++ ) { + if( ret += silk_init_encoder( &psEnc->state_Fxx[ n ], arch ) ) { + celt_assert( 0 ); + } + } + + psEnc->nChannelsAPI = 1; + psEnc->nChannelsInternal = 1; + + /* Read control structure */ + if( ret += silk_QueryEncoder( encState, encStatus ) ) { + celt_assert( 0 ); + } + + return ret; +} + +/***************************************/ +/* Read control structure from encoder */ +/***************************************/ +static opus_int silk_QueryEncoder( /* O Returns error code */ + const void *encState, /* I State */ + silk_EncControlStruct *encStatus /* O Encoder Status */ +) +{ + opus_int ret = SILK_NO_ERROR; + silk_encoder_state_Fxx *state_Fxx; + silk_encoder *psEnc = (silk_encoder *)encState; + + state_Fxx = psEnc->state_Fxx; + + encStatus->nChannelsAPI = psEnc->nChannelsAPI; + encStatus->nChannelsInternal = psEnc->nChannelsInternal; + encStatus->API_sampleRate = state_Fxx[ 0 ].sCmn.API_fs_Hz; + encStatus->maxInternalSampleRate = state_Fxx[ 0 ].sCmn.maxInternal_fs_Hz; + encStatus->minInternalSampleRate = state_Fxx[ 0 ].sCmn.minInternal_fs_Hz; + encStatus->desiredInternalSampleRate = state_Fxx[ 0 ].sCmn.desiredInternal_fs_Hz; + encStatus->payloadSize_ms = state_Fxx[ 0 ].sCmn.PacketSize_ms; + encStatus->bitRate = state_Fxx[ 0 ].sCmn.TargetRate_bps; + encStatus->packetLossPercentage = state_Fxx[ 0 ].sCmn.PacketLoss_perc; + encStatus->complexity = state_Fxx[ 0 ].sCmn.Complexity; + encStatus->useInBandFEC = state_Fxx[ 0 ].sCmn.useInBandFEC; + encStatus->useDTX = state_Fxx[ 0 ].sCmn.useDTX; + encStatus->useCBR = state_Fxx[ 0 ].sCmn.useCBR; + encStatus->internalSampleRate = silk_SMULBB( state_Fxx[ 0 ].sCmn.fs_kHz, 1000 ); + encStatus->allowBandwidthSwitch = state_Fxx[ 0 ].sCmn.allow_bandwidth_switch; + encStatus->inWBmodeWithoutVariableLP = state_Fxx[ 0 ].sCmn.fs_kHz == 16 && state_Fxx[ 0 ].sCmn.sLP.mode == 0; + + return ret; +} + + +/**************************/ +/* Encode frame with Silk */ +/**************************/ +/* Note: if prefillFlag is set, the input must contain 10 ms of audio, irrespective of what */ +/* encControl->payloadSize_ms is set to */ +opus_int silk_Encode( /* O Returns error code */ + void *encState, /* I/O State */ + silk_EncControlStruct *encControl, /* I Control status */ + const opus_int16 *samplesIn, /* I Speech sample input vector */ + opus_int nSamplesIn, /* I Number of samples in input vector */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int32 *nBytesOut, /* I/O Number of bytes in payload (input: Max bytes) */ + const opus_int prefillFlag, /* I Flag to indicate prefilling buffers no coding */ + opus_int activity /* I Decision of Opus voice activity detector */ +) +{ + opus_int n, i, nBits, flags, tmp_payloadSize_ms = 0, tmp_complexity = 0, ret = 0; + opus_int nSamplesToBuffer, nSamplesToBufferMax, nBlocksOf10ms; + opus_int nSamplesFromInput = 0, nSamplesFromInputMax; + opus_int speech_act_thr_for_switch_Q8; + opus_int32 TargetRate_bps, MStargetRates_bps[ 2 ], channelRate_bps, LBRR_symbol, sum; + silk_encoder *psEnc = ( silk_encoder * )encState; + VARDECL( opus_int16, buf ); + opus_int transition, curr_block, tot_blocks; + SAVE_STACK; + + if (encControl->reducedDependency) + { + psEnc->state_Fxx[0].sCmn.first_frame_after_reset = 1; + psEnc->state_Fxx[1].sCmn.first_frame_after_reset = 1; + } + psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded = psEnc->state_Fxx[ 1 ].sCmn.nFramesEncoded = 0; + + /* Check values in encoder control structure */ + if( ( ret = check_control_input( encControl ) ) != 0 ) { + celt_assert( 0 ); + RESTORE_STACK; + return ret; + } + + encControl->switchReady = 0; + + if( encControl->nChannelsInternal > psEnc->nChannelsInternal ) { + /* Mono -> Stereo transition: init state of second channel and stereo state */ + ret += silk_init_encoder( &psEnc->state_Fxx[ 1 ], psEnc->state_Fxx[ 0 ].sCmn.arch ); + silk_memset( psEnc->sStereo.pred_prev_Q13, 0, sizeof( psEnc->sStereo.pred_prev_Q13 ) ); + silk_memset( psEnc->sStereo.sSide, 0, sizeof( psEnc->sStereo.sSide ) ); + psEnc->sStereo.mid_side_amp_Q0[ 0 ] = 0; + psEnc->sStereo.mid_side_amp_Q0[ 1 ] = 1; + psEnc->sStereo.mid_side_amp_Q0[ 2 ] = 0; + psEnc->sStereo.mid_side_amp_Q0[ 3 ] = 1; + psEnc->sStereo.width_prev_Q14 = 0; + psEnc->sStereo.smth_width_Q14 = SILK_FIX_CONST( 1, 14 ); + if( psEnc->nChannelsAPI == 2 ) { + silk_memcpy( &psEnc->state_Fxx[ 1 ].sCmn.resampler_state, &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, sizeof( silk_resampler_state_struct ) ); + silk_memcpy( &psEnc->state_Fxx[ 1 ].sCmn.In_HP_State, &psEnc->state_Fxx[ 0 ].sCmn.In_HP_State, sizeof( psEnc->state_Fxx[ 1 ].sCmn.In_HP_State ) ); + } + } + + transition = (encControl->payloadSize_ms != psEnc->state_Fxx[ 0 ].sCmn.PacketSize_ms) || (psEnc->nChannelsInternal != encControl->nChannelsInternal); + + psEnc->nChannelsAPI = encControl->nChannelsAPI; + psEnc->nChannelsInternal = encControl->nChannelsInternal; + + nBlocksOf10ms = silk_DIV32( 100 * nSamplesIn, encControl->API_sampleRate ); + tot_blocks = ( nBlocksOf10ms > 1 ) ? nBlocksOf10ms >> 1 : 1; + curr_block = 0; + if( prefillFlag ) { + silk_LP_state save_LP; + /* Only accept input length of 10 ms */ + if( nBlocksOf10ms != 1 ) { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_ENC_INPUT_INVALID_NO_OF_SAMPLES; + } + if ( prefillFlag == 2 ) { + save_LP = psEnc->state_Fxx[ 0 ].sCmn.sLP; + /* Save the sampling rate so the bandwidth switching code can keep handling transitions. */ + save_LP.saved_fs_kHz = psEnc->state_Fxx[ 0 ].sCmn.fs_kHz; + } + /* Reset Encoder */ + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + ret = silk_init_encoder( &psEnc->state_Fxx[ n ], psEnc->state_Fxx[ n ].sCmn.arch ); + /* Restore the variable LP state. */ + if ( prefillFlag == 2 ) { + psEnc->state_Fxx[ n ].sCmn.sLP = save_LP; + } + celt_assert( !ret ); + } + tmp_payloadSize_ms = encControl->payloadSize_ms; + encControl->payloadSize_ms = 10; + tmp_complexity = encControl->complexity; + encControl->complexity = 0; + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + psEnc->state_Fxx[ n ].sCmn.controlled_since_last_payload = 0; + psEnc->state_Fxx[ n ].sCmn.prefillFlag = 1; + } + } else { + /* Only accept input lengths that are a multiple of 10 ms */ + if( nBlocksOf10ms * encControl->API_sampleRate != 100 * nSamplesIn || nSamplesIn < 0 ) { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_ENC_INPUT_INVALID_NO_OF_SAMPLES; + } + /* Make sure no more than one packet can be produced */ + if( 1000 * (opus_int32)nSamplesIn > encControl->payloadSize_ms * encControl->API_sampleRate ) { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_ENC_INPUT_INVALID_NO_OF_SAMPLES; + } + } + + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + /* Force the side channel to the same rate as the mid */ + opus_int force_fs_kHz = (n==1) ? psEnc->state_Fxx[0].sCmn.fs_kHz : 0; + if( ( ret = silk_control_encoder( &psEnc->state_Fxx[ n ], encControl, psEnc->allowBandwidthSwitch, n, force_fs_kHz ) ) != 0 ) { + silk_assert( 0 ); + RESTORE_STACK; + return ret; + } + if( psEnc->state_Fxx[n].sCmn.first_frame_after_reset || transition ) { + for( i = 0; i < psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket; i++ ) { + psEnc->state_Fxx[ n ].sCmn.LBRR_flags[ i ] = 0; + } + } + psEnc->state_Fxx[ n ].sCmn.inDTX = psEnc->state_Fxx[ n ].sCmn.useDTX; + } + celt_assert( encControl->nChannelsInternal == 1 || psEnc->state_Fxx[ 0 ].sCmn.fs_kHz == psEnc->state_Fxx[ 1 ].sCmn.fs_kHz ); + + /* Input buffering/resampling and encoding */ + nSamplesToBufferMax = + 10 * nBlocksOf10ms * psEnc->state_Fxx[ 0 ].sCmn.fs_kHz; + nSamplesFromInputMax = + silk_DIV32_16( nSamplesToBufferMax * + psEnc->state_Fxx[ 0 ].sCmn.API_fs_Hz, + psEnc->state_Fxx[ 0 ].sCmn.fs_kHz * 1000 ); + ALLOC( buf, nSamplesFromInputMax, opus_int16 ); + while( 1 ) { + int curr_nBitsUsedLBRR = 0; + nSamplesToBuffer = psEnc->state_Fxx[ 0 ].sCmn.frame_length - psEnc->state_Fxx[ 0 ].sCmn.inputBufIx; + nSamplesToBuffer = silk_min( nSamplesToBuffer, nSamplesToBufferMax ); + nSamplesFromInput = silk_DIV32_16( nSamplesToBuffer * psEnc->state_Fxx[ 0 ].sCmn.API_fs_Hz, psEnc->state_Fxx[ 0 ].sCmn.fs_kHz * 1000 ); + /* Resample and write to buffer */ + if( encControl->nChannelsAPI == 2 && encControl->nChannelsInternal == 2 ) { + opus_int id = psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded; + for( n = 0; n < nSamplesFromInput; n++ ) { + buf[ n ] = samplesIn[ 2 * n ]; + } + /* Making sure to start both resamplers from the same state when switching from mono to stereo */ + if( psEnc->nPrevChannelsInternal == 1 && id==0 ) { + silk_memcpy( &psEnc->state_Fxx[ 1 ].sCmn.resampler_state, &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, sizeof(psEnc->state_Fxx[ 1 ].sCmn.resampler_state)); + } + + ret += silk_resampler( &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + psEnc->state_Fxx[ 0 ].sCmn.inputBufIx += nSamplesToBuffer; + + nSamplesToBuffer = psEnc->state_Fxx[ 1 ].sCmn.frame_length - psEnc->state_Fxx[ 1 ].sCmn.inputBufIx; + nSamplesToBuffer = silk_min( nSamplesToBuffer, 10 * nBlocksOf10ms * psEnc->state_Fxx[ 1 ].sCmn.fs_kHz ); + for( n = 0; n < nSamplesFromInput; n++ ) { + buf[ n ] = samplesIn[ 2 * n + 1 ]; + } + ret += silk_resampler( &psEnc->state_Fxx[ 1 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 1 ].sCmn.inputBuf[ psEnc->state_Fxx[ 1 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + + psEnc->state_Fxx[ 1 ].sCmn.inputBufIx += nSamplesToBuffer; + } else if( encControl->nChannelsAPI == 2 && encControl->nChannelsInternal == 1 ) { + /* Combine left and right channels before resampling */ + for( n = 0; n < nSamplesFromInput; n++ ) { + sum = samplesIn[ 2 * n ] + samplesIn[ 2 * n + 1 ]; + buf[ n ] = (opus_int16)silk_RSHIFT_ROUND( sum, 1 ); + } + ret += silk_resampler( &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + /* On the first mono frame, average the results for the two resampler states */ + if( psEnc->nPrevChannelsInternal == 2 && psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded == 0 ) { + ret += silk_resampler( &psEnc->state_Fxx[ 1 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 1 ].sCmn.inputBuf[ psEnc->state_Fxx[ 1 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + for( n = 0; n < psEnc->state_Fxx[ 0 ].sCmn.frame_length; n++ ) { + psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx+n+2 ] = + silk_RSHIFT(psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx+n+2 ] + + psEnc->state_Fxx[ 1 ].sCmn.inputBuf[ psEnc->state_Fxx[ 1 ].sCmn.inputBufIx+n+2 ], 1); + } + } + psEnc->state_Fxx[ 0 ].sCmn.inputBufIx += nSamplesToBuffer; + } else { + celt_assert( encControl->nChannelsAPI == 1 && encControl->nChannelsInternal == 1 ); + silk_memcpy(buf, samplesIn, nSamplesFromInput*sizeof(opus_int16)); + ret += silk_resampler( &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + psEnc->state_Fxx[ 0 ].sCmn.inputBufIx += nSamplesToBuffer; + } + + samplesIn += nSamplesFromInput * encControl->nChannelsAPI; + nSamplesIn -= nSamplesFromInput; + + /* Default */ + psEnc->allowBandwidthSwitch = 0; + + /* Silk encoder */ + if( psEnc->state_Fxx[ 0 ].sCmn.inputBufIx >= psEnc->state_Fxx[ 0 ].sCmn.frame_length ) { + /* Enough data in input buffer, so encode */ + celt_assert( psEnc->state_Fxx[ 0 ].sCmn.inputBufIx == psEnc->state_Fxx[ 0 ].sCmn.frame_length ); + celt_assert( encControl->nChannelsInternal == 1 || psEnc->state_Fxx[ 1 ].sCmn.inputBufIx == psEnc->state_Fxx[ 1 ].sCmn.frame_length ); + + /* Deal with LBRR data */ + if( psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded == 0 && !prefillFlag ) { + /* Create space at start of payload for VAD and FEC flags */ + opus_uint8 iCDF[ 2 ] = { 0, 0 }; + iCDF[ 0 ] = 256 - silk_RSHIFT( 256, ( psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket + 1 ) * encControl->nChannelsInternal ); + ec_enc_icdf( psRangeEnc, 0, iCDF, 8 ); + curr_nBitsUsedLBRR = ec_tell( psRangeEnc ); + + /* Encode any LBRR data from previous packet */ + /* Encode LBRR flags */ + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + LBRR_symbol = 0; + for( i = 0; i < psEnc->state_Fxx[ n ].sCmn.nFramesPerPacket; i++ ) { + LBRR_symbol |= silk_LSHIFT( psEnc->state_Fxx[ n ].sCmn.LBRR_flags[ i ], i ); + } + psEnc->state_Fxx[ n ].sCmn.LBRR_flag = LBRR_symbol > 0 ? 1 : 0; + if( LBRR_symbol && psEnc->state_Fxx[ n ].sCmn.nFramesPerPacket > 1 ) { + ec_enc_icdf( psRangeEnc, LBRR_symbol - 1, silk_LBRR_flags_iCDF_ptr[ psEnc->state_Fxx[ n ].sCmn.nFramesPerPacket - 2 ], 8 ); + } + } + + /* Code LBRR indices and excitation signals */ + for( i = 0; i < psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket; i++ ) { + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + if( psEnc->state_Fxx[ n ].sCmn.LBRR_flags[ i ] ) { + opus_int condCoding; + + if( encControl->nChannelsInternal == 2 && n == 0 ) { + silk_stereo_encode_pred( psRangeEnc, psEnc->sStereo.predIx[ i ] ); + /* For LBRR data there's no need to code the mid-only flag if the side-channel LBRR flag is set */ + if( psEnc->state_Fxx[ 1 ].sCmn.LBRR_flags[ i ] == 0 ) { + silk_stereo_encode_mid_only( psRangeEnc, psEnc->sStereo.mid_only_flags[ i ] ); + } + } + /* Use conditional coding if previous frame available */ + if( i > 0 && psEnc->state_Fxx[ n ].sCmn.LBRR_flags[ i - 1 ] ) { + condCoding = CODE_CONDITIONALLY; + } else { + condCoding = CODE_INDEPENDENTLY; + } + silk_encode_indices( &psEnc->state_Fxx[ n ].sCmn, psRangeEnc, i, 1, condCoding ); + silk_encode_pulses( psRangeEnc, psEnc->state_Fxx[ n ].sCmn.indices_LBRR[i].signalType, psEnc->state_Fxx[ n ].sCmn.indices_LBRR[i].quantOffsetType, + psEnc->state_Fxx[ n ].sCmn.pulses_LBRR[ i ], psEnc->state_Fxx[ n ].sCmn.frame_length ); + } + } + } + + /* Reset LBRR flags */ + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + silk_memset( psEnc->state_Fxx[ n ].sCmn.LBRR_flags, 0, sizeof( psEnc->state_Fxx[ n ].sCmn.LBRR_flags ) ); + } + curr_nBitsUsedLBRR = ec_tell( psRangeEnc ) - curr_nBitsUsedLBRR; + } + + silk_HP_variable_cutoff( psEnc->state_Fxx ); + + /* Total target bits for packet */ + nBits = silk_DIV32_16( silk_MUL( encControl->bitRate, encControl->payloadSize_ms ), 1000 ); + /* Subtract bits used for LBRR */ + if( !prefillFlag ) { + /* psEnc->nBitsUsedLBRR is an exponential moving average of the LBRR usage, + except that for the first LBRR frame it does no averaging and for the first + frame after after LBRR, it goes back to zero immediately. */ + if ( curr_nBitsUsedLBRR < 10 ) { + psEnc->nBitsUsedLBRR = 0; + } else if ( psEnc->nBitsUsedLBRR < 10) { + psEnc->nBitsUsedLBRR = curr_nBitsUsedLBRR; + } else { + psEnc->nBitsUsedLBRR = ( psEnc->nBitsUsedLBRR + curr_nBitsUsedLBRR ) / 2; + } + nBits -= psEnc->nBitsUsedLBRR; + } + /* Divide by number of uncoded frames left in packet */ + nBits = silk_DIV32_16( nBits, psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket ); + /* Convert to bits/second */ + if( encControl->payloadSize_ms == 10 ) { + TargetRate_bps = silk_SMULBB( nBits, 100 ); + } else { + TargetRate_bps = silk_SMULBB( nBits, 50 ); + } + /* Subtract fraction of bits in excess of target in previous frames and packets */ + TargetRate_bps -= silk_DIV32_16( silk_MUL( psEnc->nBitsExceeded, 1000 ), BITRESERVOIR_DECAY_TIME_MS ); + if( !prefillFlag && psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded > 0 ) { + /* Compare actual vs target bits so far in this packet */ + opus_int32 bitsBalance = ec_tell( psRangeEnc ) - psEnc->nBitsUsedLBRR - nBits * psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded; + TargetRate_bps -= silk_DIV32_16( silk_MUL( bitsBalance, 1000 ), BITRESERVOIR_DECAY_TIME_MS ); + } + /* Never exceed input bitrate */ + TargetRate_bps = silk_LIMIT( TargetRate_bps, encControl->bitRate, 5000 ); + + /* Convert Left/Right to Mid/Side */ + if( encControl->nChannelsInternal == 2 ) { + silk_stereo_LR_to_MS( &psEnc->sStereo, &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ 2 ], &psEnc->state_Fxx[ 1 ].sCmn.inputBuf[ 2 ], + psEnc->sStereo.predIx[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ], &psEnc->sStereo.mid_only_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ], + MStargetRates_bps, TargetRate_bps, psEnc->state_Fxx[ 0 ].sCmn.speech_activity_Q8, encControl->toMono, + psEnc->state_Fxx[ 0 ].sCmn.fs_kHz, psEnc->state_Fxx[ 0 ].sCmn.frame_length ); + if( psEnc->sStereo.mid_only_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] == 0 ) { + /* Reset side channel encoder memory for first frame with side coding */ + if( psEnc->prev_decode_only_middle == 1 ) { + silk_memset( &psEnc->state_Fxx[ 1 ].sShape, 0, sizeof( psEnc->state_Fxx[ 1 ].sShape ) ); + silk_memset( &psEnc->state_Fxx[ 1 ].sCmn.sNSQ, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.sNSQ ) ); + silk_memset( psEnc->state_Fxx[ 1 ].sCmn.prev_NLSFq_Q15, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.prev_NLSFq_Q15 ) ); + silk_memset( &psEnc->state_Fxx[ 1 ].sCmn.sLP.In_LP_State, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.sLP.In_LP_State ) ); + psEnc->state_Fxx[ 1 ].sCmn.prevLag = 100; + psEnc->state_Fxx[ 1 ].sCmn.sNSQ.lagPrev = 100; + psEnc->state_Fxx[ 1 ].sShape.LastGainIndex = 10; + psEnc->state_Fxx[ 1 ].sCmn.prevSignalType = TYPE_NO_VOICE_ACTIVITY; + psEnc->state_Fxx[ 1 ].sCmn.sNSQ.prev_gain_Q16 = 65536; + psEnc->state_Fxx[ 1 ].sCmn.first_frame_after_reset = 1; + } + silk_encode_do_VAD_Fxx( &psEnc->state_Fxx[ 1 ], activity ); + } else { + psEnc->state_Fxx[ 1 ].sCmn.VAD_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] = 0; + } + if( !prefillFlag ) { + silk_stereo_encode_pred( psRangeEnc, psEnc->sStereo.predIx[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] ); + if( psEnc->state_Fxx[ 1 ].sCmn.VAD_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] == 0 ) { + silk_stereo_encode_mid_only( psRangeEnc, psEnc->sStereo.mid_only_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] ); + } + } + } else { + /* Buffering */ + silk_memcpy( psEnc->state_Fxx[ 0 ].sCmn.inputBuf, psEnc->sStereo.sMid, 2 * sizeof( opus_int16 ) ); + silk_memcpy( psEnc->sStereo.sMid, &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.frame_length ], 2 * sizeof( opus_int16 ) ); + } + silk_encode_do_VAD_Fxx( &psEnc->state_Fxx[ 0 ], activity ); + + /* Encode */ + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + opus_int maxBits, useCBR; + + /* Handling rate constraints */ + maxBits = encControl->maxBits; + if( tot_blocks == 2 && curr_block == 0 ) { + maxBits = maxBits * 3 / 5; + } else if( tot_blocks == 3 ) { + if( curr_block == 0 ) { + maxBits = maxBits * 2 / 5; + } else if( curr_block == 1 ) { + maxBits = maxBits * 3 / 4; + } + } + useCBR = encControl->useCBR && curr_block == tot_blocks - 1; + + if( encControl->nChannelsInternal == 1 ) { + channelRate_bps = TargetRate_bps; + } else { + channelRate_bps = MStargetRates_bps[ n ]; + if( n == 0 && MStargetRates_bps[ 1 ] > 0 ) { + useCBR = 0; + /* Give mid up to 1/2 of the max bits for that frame */ + maxBits -= encControl->maxBits / ( tot_blocks * 2 ); + } + } + + if( channelRate_bps > 0 ) { + opus_int condCoding; + + silk_control_SNR( &psEnc->state_Fxx[ n ].sCmn, channelRate_bps ); + + /* Use independent coding if no previous frame available */ + if( psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded - n <= 0 ) { + condCoding = CODE_INDEPENDENTLY; + } else if( n > 0 && psEnc->prev_decode_only_middle ) { + /* If we skipped a side frame in this packet, we don't + need LTP scaling; the LTP state is well-defined. */ + condCoding = CODE_INDEPENDENTLY_NO_LTP_SCALING; + } else { + condCoding = CODE_CONDITIONALLY; + } + if( ( ret = silk_encode_frame_Fxx( &psEnc->state_Fxx[ n ], nBytesOut, psRangeEnc, condCoding, maxBits, useCBR ) ) != 0 ) { + silk_assert( 0 ); + } + } + psEnc->state_Fxx[ n ].sCmn.controlled_since_last_payload = 0; + psEnc->state_Fxx[ n ].sCmn.inputBufIx = 0; + psEnc->state_Fxx[ n ].sCmn.nFramesEncoded++; + } + psEnc->prev_decode_only_middle = psEnc->sStereo.mid_only_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded - 1 ]; + + /* Insert VAD and FEC flags at beginning of bitstream */ + if( *nBytesOut > 0 && psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded == psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket) { + flags = 0; + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + for( i = 0; i < psEnc->state_Fxx[ n ].sCmn.nFramesPerPacket; i++ ) { + flags = silk_LSHIFT( flags, 1 ); + flags |= psEnc->state_Fxx[ n ].sCmn.VAD_flags[ i ]; + } + flags = silk_LSHIFT( flags, 1 ); + flags |= psEnc->state_Fxx[ n ].sCmn.LBRR_flag; + } + if( !prefillFlag ) { + ec_enc_patch_initial_bits( psRangeEnc, flags, ( psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket + 1 ) * encControl->nChannelsInternal ); + } + + /* Return zero bytes if all channels DTXed */ + if( psEnc->state_Fxx[ 0 ].sCmn.inDTX && ( encControl->nChannelsInternal == 1 || psEnc->state_Fxx[ 1 ].sCmn.inDTX ) ) { + *nBytesOut = 0; + } + + psEnc->nBitsExceeded += *nBytesOut * 8; + psEnc->nBitsExceeded -= silk_DIV32_16( silk_MUL( encControl->bitRate, encControl->payloadSize_ms ), 1000 ); + psEnc->nBitsExceeded = silk_LIMIT( psEnc->nBitsExceeded, 0, 10000 ); + + /* Update flag indicating if bandwidth switching is allowed */ + speech_act_thr_for_switch_Q8 = silk_SMLAWB( SILK_FIX_CONST( SPEECH_ACTIVITY_DTX_THRES, 8 ), + SILK_FIX_CONST( ( 1 - SPEECH_ACTIVITY_DTX_THRES ) / MAX_BANDWIDTH_SWITCH_DELAY_MS, 16 + 8 ), psEnc->timeSinceSwitchAllowed_ms ); + if( psEnc->state_Fxx[ 0 ].sCmn.speech_activity_Q8 < speech_act_thr_for_switch_Q8 ) { + psEnc->allowBandwidthSwitch = 1; + psEnc->timeSinceSwitchAllowed_ms = 0; + } else { + psEnc->allowBandwidthSwitch = 0; + psEnc->timeSinceSwitchAllowed_ms += encControl->payloadSize_ms; + } + } + + if( nSamplesIn == 0 ) { + break; + } + } else { + break; + } + curr_block++; + } + + psEnc->nPrevChannelsInternal = encControl->nChannelsInternal; + + encControl->allowBandwidthSwitch = psEnc->allowBandwidthSwitch; + encControl->inWBmodeWithoutVariableLP = psEnc->state_Fxx[ 0 ].sCmn.fs_kHz == 16 && psEnc->state_Fxx[ 0 ].sCmn.sLP.mode == 0; + encControl->internalSampleRate = silk_SMULBB( psEnc->state_Fxx[ 0 ].sCmn.fs_kHz, 1000 ); + encControl->stereoWidth_Q14 = encControl->toMono ? 0 : psEnc->sStereo.smth_width_Q14; + if( prefillFlag ) { + encControl->payloadSize_ms = tmp_payloadSize_ms; + encControl->complexity = tmp_complexity; + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + psEnc->state_Fxx[ n ].sCmn.controlled_since_last_payload = 0; + psEnc->state_Fxx[ n ].sCmn.prefillFlag = 0; + } + } + + encControl->signalType = psEnc->state_Fxx[0].sCmn.indices.signalType; + encControl->offset = silk_Quantization_Offsets_Q10 + [ psEnc->state_Fxx[0].sCmn.indices.signalType >> 1 ] + [ psEnc->state_Fxx[0].sCmn.indices.quantOffsetType ]; + RESTORE_STACK; + return ret; +} + diff --git a/port/third_party/opus/silk/encode_indices.c b/port/third_party/opus/silk/encode_indices.c new file mode 100644 index 00000000..4bcbc334 --- /dev/null +++ b/port/third_party/opus/silk/encode_indices.c @@ -0,0 +1,181 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Encode side-information parameters to payload */ +void silk_encode_indices( + silk_encoder_state *psEncC, /* I/O Encoder state */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int FrameIndex, /* I Frame number */ + opus_int encode_LBRR, /* I Flag indicating LBRR data is being encoded */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i, k, typeOffset; + opus_int encode_absolute_lagIndex, delta_lagIndex; + opus_int16 ec_ix[ MAX_LPC_ORDER ]; + opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; + const SideInfoIndices *psIndices; + + if( encode_LBRR ) { + psIndices = &psEncC->indices_LBRR[ FrameIndex ]; + } else { + psIndices = &psEncC->indices; + } + + /*******************************************/ + /* Encode signal type and quantizer offset */ + /*******************************************/ + typeOffset = 2 * psIndices->signalType + psIndices->quantOffsetType; + celt_assert( typeOffset >= 0 && typeOffset < 6 ); + celt_assert( encode_LBRR == 0 || typeOffset >= 2 ); + if( encode_LBRR || typeOffset >= 2 ) { + ec_enc_icdf( psRangeEnc, typeOffset - 2, silk_type_offset_VAD_iCDF, 8 ); + } else { + ec_enc_icdf( psRangeEnc, typeOffset, silk_type_offset_no_VAD_iCDF, 8 ); + } + + /****************/ + /* Encode gains */ + /****************/ + /* first subframe */ + if( condCoding == CODE_CONDITIONALLY ) { + /* conditional coding */ + silk_assert( psIndices->GainsIndices[ 0 ] >= 0 && psIndices->GainsIndices[ 0 ] < MAX_DELTA_GAIN_QUANT - MIN_DELTA_GAIN_QUANT + 1 ); + ec_enc_icdf( psRangeEnc, psIndices->GainsIndices[ 0 ], silk_delta_gain_iCDF, 8 ); + } else { + /* independent coding, in two stages: MSB bits followed by 3 LSBs */ + silk_assert( psIndices->GainsIndices[ 0 ] >= 0 && psIndices->GainsIndices[ 0 ] < N_LEVELS_QGAIN ); + ec_enc_icdf( psRangeEnc, silk_RSHIFT( psIndices->GainsIndices[ 0 ], 3 ), silk_gain_iCDF[ psIndices->signalType ], 8 ); + ec_enc_icdf( psRangeEnc, psIndices->GainsIndices[ 0 ] & 7, silk_uniform8_iCDF, 8 ); + } + + /* remaining subframes */ + for( i = 1; i < psEncC->nb_subfr; i++ ) { + silk_assert( psIndices->GainsIndices[ i ] >= 0 && psIndices->GainsIndices[ i ] < MAX_DELTA_GAIN_QUANT - MIN_DELTA_GAIN_QUANT + 1 ); + ec_enc_icdf( psRangeEnc, psIndices->GainsIndices[ i ], silk_delta_gain_iCDF, 8 ); + } + + /****************/ + /* Encode NLSFs */ + /****************/ + ec_enc_icdf( psRangeEnc, psIndices->NLSFIndices[ 0 ], &psEncC->psNLSF_CB->CB1_iCDF[ ( psIndices->signalType >> 1 ) * psEncC->psNLSF_CB->nVectors ], 8 ); + silk_NLSF_unpack( ec_ix, pred_Q8, psEncC->psNLSF_CB, psIndices->NLSFIndices[ 0 ] ); + celt_assert( psEncC->psNLSF_CB->order == psEncC->predictLPCOrder ); + for( i = 0; i < psEncC->psNLSF_CB->order; i++ ) { + if( psIndices->NLSFIndices[ i+1 ] >= NLSF_QUANT_MAX_AMPLITUDE ) { + ec_enc_icdf( psRangeEnc, 2 * NLSF_QUANT_MAX_AMPLITUDE, &psEncC->psNLSF_CB->ec_iCDF[ ec_ix[ i ] ], 8 ); + ec_enc_icdf( psRangeEnc, psIndices->NLSFIndices[ i+1 ] - NLSF_QUANT_MAX_AMPLITUDE, silk_NLSF_EXT_iCDF, 8 ); + } else if( psIndices->NLSFIndices[ i+1 ] <= -NLSF_QUANT_MAX_AMPLITUDE ) { + ec_enc_icdf( psRangeEnc, 0, &psEncC->psNLSF_CB->ec_iCDF[ ec_ix[ i ] ], 8 ); + ec_enc_icdf( psRangeEnc, -psIndices->NLSFIndices[ i+1 ] - NLSF_QUANT_MAX_AMPLITUDE, silk_NLSF_EXT_iCDF, 8 ); + } else { + ec_enc_icdf( psRangeEnc, psIndices->NLSFIndices[ i+1 ] + NLSF_QUANT_MAX_AMPLITUDE, &psEncC->psNLSF_CB->ec_iCDF[ ec_ix[ i ] ], 8 ); + } + } + + /* Encode NLSF interpolation factor */ + if( psEncC->nb_subfr == MAX_NB_SUBFR ) { + silk_assert( psIndices->NLSFInterpCoef_Q2 >= 0 && psIndices->NLSFInterpCoef_Q2 < 5 ); + ec_enc_icdf( psRangeEnc, psIndices->NLSFInterpCoef_Q2, silk_NLSF_interpolation_factor_iCDF, 8 ); + } + + if( psIndices->signalType == TYPE_VOICED ) + { + /*********************/ + /* Encode pitch lags */ + /*********************/ + /* lag index */ + encode_absolute_lagIndex = 1; + if( condCoding == CODE_CONDITIONALLY && psEncC->ec_prevSignalType == TYPE_VOICED ) { + /* Delta Encoding */ + delta_lagIndex = psIndices->lagIndex - psEncC->ec_prevLagIndex; + if( delta_lagIndex < -8 || delta_lagIndex > 11 ) { + delta_lagIndex = 0; + } else { + delta_lagIndex = delta_lagIndex + 9; + encode_absolute_lagIndex = 0; /* Only use delta */ + } + silk_assert( delta_lagIndex >= 0 && delta_lagIndex < 21 ); + ec_enc_icdf( psRangeEnc, delta_lagIndex, silk_pitch_delta_iCDF, 8 ); + } + if( encode_absolute_lagIndex ) { + /* Absolute encoding */ + opus_int32 pitch_high_bits, pitch_low_bits; + pitch_high_bits = silk_DIV32_16( psIndices->lagIndex, silk_RSHIFT( psEncC->fs_kHz, 1 ) ); + pitch_low_bits = psIndices->lagIndex - silk_SMULBB( pitch_high_bits, silk_RSHIFT( psEncC->fs_kHz, 1 ) ); + silk_assert( pitch_low_bits < psEncC->fs_kHz / 2 ); + silk_assert( pitch_high_bits < 32 ); + ec_enc_icdf( psRangeEnc, pitch_high_bits, silk_pitch_lag_iCDF, 8 ); + ec_enc_icdf( psRangeEnc, pitch_low_bits, psEncC->pitch_lag_low_bits_iCDF, 8 ); + } + psEncC->ec_prevLagIndex = psIndices->lagIndex; + + /* Countour index */ + silk_assert( psIndices->contourIndex >= 0 ); + silk_assert( ( psIndices->contourIndex < 34 && psEncC->fs_kHz > 8 && psEncC->nb_subfr == 4 ) || + ( psIndices->contourIndex < 11 && psEncC->fs_kHz == 8 && psEncC->nb_subfr == 4 ) || + ( psIndices->contourIndex < 12 && psEncC->fs_kHz > 8 && psEncC->nb_subfr == 2 ) || + ( psIndices->contourIndex < 3 && psEncC->fs_kHz == 8 && psEncC->nb_subfr == 2 ) ); + ec_enc_icdf( psRangeEnc, psIndices->contourIndex, psEncC->pitch_contour_iCDF, 8 ); + + /********************/ + /* Encode LTP gains */ + /********************/ + /* PERIndex value */ + silk_assert( psIndices->PERIndex >= 0 && psIndices->PERIndex < 3 ); + ec_enc_icdf( psRangeEnc, psIndices->PERIndex, silk_LTP_per_index_iCDF, 8 ); + + /* Codebook Indices */ + for( k = 0; k < psEncC->nb_subfr; k++ ) { + silk_assert( psIndices->LTPIndex[ k ] >= 0 && psIndices->LTPIndex[ k ] < ( 8 << psIndices->PERIndex ) ); + ec_enc_icdf( psRangeEnc, psIndices->LTPIndex[ k ], silk_LTP_gain_iCDF_ptrs[ psIndices->PERIndex ], 8 ); + } + + /**********************/ + /* Encode LTP scaling */ + /**********************/ + if( condCoding == CODE_INDEPENDENTLY ) { + silk_assert( psIndices->LTP_scaleIndex >= 0 && psIndices->LTP_scaleIndex < 3 ); + ec_enc_icdf( psRangeEnc, psIndices->LTP_scaleIndex, silk_LTPscale_iCDF, 8 ); + } + silk_assert( !condCoding || psIndices->LTP_scaleIndex == 0 ); + } + + psEncC->ec_prevSignalType = psIndices->signalType; + + /***************/ + /* Encode seed */ + /***************/ + silk_assert( psIndices->Seed >= 0 && psIndices->Seed < 4 ); + ec_enc_icdf( psRangeEnc, psIndices->Seed, silk_uniform4_iCDF, 8 ); +} diff --git a/port/third_party/opus/silk/encode_pulses.c b/port/third_party/opus/silk/encode_pulses.c new file mode 100644 index 00000000..8a199913 --- /dev/null +++ b/port/third_party/opus/silk/encode_pulses.c @@ -0,0 +1,206 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/*********************************************/ +/* Encode quantization indices of excitation */ +/*********************************************/ + +static OPUS_INLINE opus_int combine_and_check( /* return ok */ + opus_int *pulses_comb, /* O */ + const opus_int *pulses_in, /* I */ + opus_int max_pulses, /* I max value for sum of pulses */ + opus_int len /* I number of output values */ +) +{ + opus_int k, sum; + + for( k = 0; k < len; k++ ) { + sum = pulses_in[ 2 * k ] + pulses_in[ 2 * k + 1 ]; + if( sum > max_pulses ) { + return 1; + } + pulses_comb[ k ] = sum; + } + + return 0; +} + +/* Encode quantization indices of excitation */ +void silk_encode_pulses( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I quantOffsetType */ + opus_int8 pulses[], /* I quantization indices */ + const opus_int frame_length /* I Frame length */ +) +{ + opus_int i, k, j, iter, bit, nLS, scale_down, RateLevelIndex = 0; + opus_int32 abs_q, minSumBits_Q5, sumBits_Q5; + VARDECL( opus_int, abs_pulses ); + VARDECL( opus_int, sum_pulses ); + VARDECL( opus_int, nRshifts ); + opus_int pulses_comb[ 8 ]; + opus_int *abs_pulses_ptr; + const opus_int8 *pulses_ptr; + const opus_uint8 *cdf_ptr; + const opus_uint8 *nBits_ptr; + SAVE_STACK; + + silk_memset( pulses_comb, 0, 8 * sizeof( opus_int ) ); /* Fixing Valgrind reported problem*/ + + /****************************/ + /* Prepare for shell coding */ + /****************************/ + /* Calculate number of shell blocks */ + silk_assert( 1 << LOG2_SHELL_CODEC_FRAME_LENGTH == SHELL_CODEC_FRAME_LENGTH ); + iter = silk_RSHIFT( frame_length, LOG2_SHELL_CODEC_FRAME_LENGTH ); + if( iter * SHELL_CODEC_FRAME_LENGTH < frame_length ) { + celt_assert( frame_length == 12 * 10 ); /* Make sure only happens for 10 ms @ 12 kHz */ + iter++; + silk_memset( &pulses[ frame_length ], 0, SHELL_CODEC_FRAME_LENGTH * sizeof(opus_int8)); + } + + /* Take the absolute value of the pulses */ + ALLOC( abs_pulses, iter * SHELL_CODEC_FRAME_LENGTH, opus_int ); + silk_assert( !( SHELL_CODEC_FRAME_LENGTH & 3 ) ); + for( i = 0; i < iter * SHELL_CODEC_FRAME_LENGTH; i+=4 ) { + abs_pulses[i+0] = ( opus_int )silk_abs( pulses[ i + 0 ] ); + abs_pulses[i+1] = ( opus_int )silk_abs( pulses[ i + 1 ] ); + abs_pulses[i+2] = ( opus_int )silk_abs( pulses[ i + 2 ] ); + abs_pulses[i+3] = ( opus_int )silk_abs( pulses[ i + 3 ] ); + } + + /* Calc sum pulses per shell code frame */ + ALLOC( sum_pulses, iter, opus_int ); + ALLOC( nRshifts, iter, opus_int ); + abs_pulses_ptr = abs_pulses; + for( i = 0; i < iter; i++ ) { + nRshifts[ i ] = 0; + + while( 1 ) { + /* 1+1 -> 2 */ + scale_down = combine_and_check( pulses_comb, abs_pulses_ptr, silk_max_pulses_table[ 0 ], 8 ); + /* 2+2 -> 4 */ + scale_down += combine_and_check( pulses_comb, pulses_comb, silk_max_pulses_table[ 1 ], 4 ); + /* 4+4 -> 8 */ + scale_down += combine_and_check( pulses_comb, pulses_comb, silk_max_pulses_table[ 2 ], 2 ); + /* 8+8 -> 16 */ + scale_down += combine_and_check( &sum_pulses[ i ], pulses_comb, silk_max_pulses_table[ 3 ], 1 ); + + if( scale_down ) { + /* We need to downscale the quantization signal */ + nRshifts[ i ]++; + for( k = 0; k < SHELL_CODEC_FRAME_LENGTH; k++ ) { + abs_pulses_ptr[ k ] = silk_RSHIFT( abs_pulses_ptr[ k ], 1 ); + } + } else { + /* Jump out of while(1) loop and go to next shell coding frame */ + break; + } + } + abs_pulses_ptr += SHELL_CODEC_FRAME_LENGTH; + } + + /**************/ + /* Rate level */ + /**************/ + /* find rate level that leads to fewest bits for coding of pulses per block info */ + minSumBits_Q5 = silk_int32_MAX; + for( k = 0; k < N_RATE_LEVELS - 1; k++ ) { + nBits_ptr = silk_pulses_per_block_BITS_Q5[ k ]; + sumBits_Q5 = silk_rate_levels_BITS_Q5[ signalType >> 1 ][ k ]; + for( i = 0; i < iter; i++ ) { + if( nRshifts[ i ] > 0 ) { + sumBits_Q5 += nBits_ptr[ SILK_MAX_PULSES + 1 ]; + } else { + sumBits_Q5 += nBits_ptr[ sum_pulses[ i ] ]; + } + } + if( sumBits_Q5 < minSumBits_Q5 ) { + minSumBits_Q5 = sumBits_Q5; + RateLevelIndex = k; + } + } + ec_enc_icdf( psRangeEnc, RateLevelIndex, silk_rate_levels_iCDF[ signalType >> 1 ], 8 ); + + /***************************************************/ + /* Sum-Weighted-Pulses Encoding */ + /***************************************************/ + cdf_ptr = silk_pulses_per_block_iCDF[ RateLevelIndex ]; + for( i = 0; i < iter; i++ ) { + if( nRshifts[ i ] == 0 ) { + ec_enc_icdf( psRangeEnc, sum_pulses[ i ], cdf_ptr, 8 ); + } else { + ec_enc_icdf( psRangeEnc, SILK_MAX_PULSES + 1, cdf_ptr, 8 ); + for( k = 0; k < nRshifts[ i ] - 1; k++ ) { + ec_enc_icdf( psRangeEnc, SILK_MAX_PULSES + 1, silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1 ], 8 ); + } + ec_enc_icdf( psRangeEnc, sum_pulses[ i ], silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1 ], 8 ); + } + } + + /******************/ + /* Shell Encoding */ + /******************/ + for( i = 0; i < iter; i++ ) { + if( sum_pulses[ i ] > 0 ) { + silk_shell_encoder( psRangeEnc, &abs_pulses[ i * SHELL_CODEC_FRAME_LENGTH ] ); + } + } + + /****************/ + /* LSB Encoding */ + /****************/ + for( i = 0; i < iter; i++ ) { + if( nRshifts[ i ] > 0 ) { + pulses_ptr = &pulses[ i * SHELL_CODEC_FRAME_LENGTH ]; + nLS = nRshifts[ i ] - 1; + for( k = 0; k < SHELL_CODEC_FRAME_LENGTH; k++ ) { + abs_q = (opus_int8)silk_abs( pulses_ptr[ k ] ); + for( j = nLS; j > 0; j-- ) { + bit = silk_RSHIFT( abs_q, j ) & 1; + ec_enc_icdf( psRangeEnc, bit, silk_lsb_iCDF, 8 ); + } + bit = abs_q & 1; + ec_enc_icdf( psRangeEnc, bit, silk_lsb_iCDF, 8 ); + } + } + } + + /****************/ + /* Encode signs */ + /****************/ + silk_encode_signs( psRangeEnc, pulses, frame_length, signalType, quantOffsetType, sum_pulses ); + RESTORE_STACK; +} diff --git a/port/third_party/opus/silk/errors.h b/port/third_party/opus/silk/errors.h new file mode 100644 index 00000000..45070800 --- /dev/null +++ b/port/third_party/opus/silk/errors.h @@ -0,0 +1,98 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_ERRORS_H +#define SILK_ERRORS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/******************/ +/* Error messages */ +/******************/ +#define SILK_NO_ERROR 0 + +/**************************/ +/* Encoder error messages */ +/**************************/ + +/* Input length is not a multiple of 10 ms, or length is longer than the packet length */ +#define SILK_ENC_INPUT_INVALID_NO_OF_SAMPLES -101 + +/* Sampling frequency not 8000, 12000 or 16000 Hertz */ +#define SILK_ENC_FS_NOT_SUPPORTED -102 + +/* Packet size not 10, 20, 40, or 60 ms */ +#define SILK_ENC_PACKET_SIZE_NOT_SUPPORTED -103 + +/* Allocated payload buffer too short */ +#define SILK_ENC_PAYLOAD_BUF_TOO_SHORT -104 + +/* Loss rate not between 0 and 100 percent */ +#define SILK_ENC_INVALID_LOSS_RATE -105 + +/* Complexity setting not valid, use 0...10 */ +#define SILK_ENC_INVALID_COMPLEXITY_SETTING -106 + +/* Inband FEC setting not valid, use 0 or 1 */ +#define SILK_ENC_INVALID_INBAND_FEC_SETTING -107 + +/* DTX setting not valid, use 0 or 1 */ +#define SILK_ENC_INVALID_DTX_SETTING -108 + +/* CBR setting not valid, use 0 or 1 */ +#define SILK_ENC_INVALID_CBR_SETTING -109 + +/* Internal encoder error */ +#define SILK_ENC_INTERNAL_ERROR -110 + +/* Internal encoder error */ +#define SILK_ENC_INVALID_NUMBER_OF_CHANNELS_ERROR -111 + +/**************************/ +/* Decoder error messages */ +/**************************/ + +/* Output sampling frequency lower than internal decoded sampling frequency */ +#define SILK_DEC_INVALID_SAMPLING_FREQUENCY -200 + +/* Payload size exceeded the maximum allowed 1024 bytes */ +#define SILK_DEC_PAYLOAD_TOO_LARGE -201 + +/* Payload has bit errors */ +#define SILK_DEC_PAYLOAD_ERROR -202 + +/* Payload has bit errors */ +#define SILK_DEC_INVALID_FRAME_SIZE -203 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/silk/float/LPC_analysis_filter_FLP.c b/port/third_party/opus/silk/float/LPC_analysis_filter_FLP.c new file mode 100644 index 00000000..0e1a1fed --- /dev/null +++ b/port/third_party/opus/silk/float/LPC_analysis_filter_FLP.c @@ -0,0 +1,249 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main_FLP.h" + +/************************************************/ +/* LPC analysis filter */ +/* NB! State is kept internally and the */ +/* filter always starts with zero state */ +/* first Order output samples are set to zero */ +/************************************************/ + +/* 16th order LPC analysis filter, does not write first 16 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter16_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 16; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ] + + s_ptr[ -6 ] * PredCoef[ 6 ] + + s_ptr[ -7 ] * PredCoef[ 7 ] + + s_ptr[ -8 ] * PredCoef[ 8 ] + + s_ptr[ -9 ] * PredCoef[ 9 ] + + s_ptr[ -10 ] * PredCoef[ 10 ] + + s_ptr[ -11 ] * PredCoef[ 11 ] + + s_ptr[ -12 ] * PredCoef[ 12 ] + + s_ptr[ -13 ] * PredCoef[ 13 ] + + s_ptr[ -14 ] * PredCoef[ 14 ] + + s_ptr[ -15 ] * PredCoef[ 15 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/* 12th order LPC analysis filter, does not write first 12 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter12_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 12; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ] + + s_ptr[ -6 ] * PredCoef[ 6 ] + + s_ptr[ -7 ] * PredCoef[ 7 ] + + s_ptr[ -8 ] * PredCoef[ 8 ] + + s_ptr[ -9 ] * PredCoef[ 9 ] + + s_ptr[ -10 ] * PredCoef[ 10 ] + + s_ptr[ -11 ] * PredCoef[ 11 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/* 10th order LPC analysis filter, does not write first 10 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter10_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 10; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ] + + s_ptr[ -6 ] * PredCoef[ 6 ] + + s_ptr[ -7 ] * PredCoef[ 7 ] + + s_ptr[ -8 ] * PredCoef[ 8 ] + + s_ptr[ -9 ] * PredCoef[ 9 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/* 8th order LPC analysis filter, does not write first 8 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter8_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 8; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ] + + s_ptr[ -6 ] * PredCoef[ 6 ] + + s_ptr[ -7 ] * PredCoef[ 7 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/* 6th order LPC analysis filter, does not write first 6 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter6_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 6; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/************************************************/ +/* LPC analysis filter */ +/* NB! State is kept internally and the */ +/* filter always starts with zero state */ +/* first Order output samples are set to zero */ +/************************************************/ +void silk_LPC_analysis_filter_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length, /* I Length of input signal */ + const opus_int Order /* I LPC order */ +) +{ + celt_assert( Order <= length ); + + switch( Order ) { + case 6: + silk_LPC_analysis_filter6_FLP( r_LPC, PredCoef, s, length ); + break; + + case 8: + silk_LPC_analysis_filter8_FLP( r_LPC, PredCoef, s, length ); + break; + + case 10: + silk_LPC_analysis_filter10_FLP( r_LPC, PredCoef, s, length ); + break; + + case 12: + silk_LPC_analysis_filter12_FLP( r_LPC, PredCoef, s, length ); + break; + + case 16: + silk_LPC_analysis_filter16_FLP( r_LPC, PredCoef, s, length ); + break; + + default: + celt_assert( 0 ); + break; + } + + /* Set first Order output samples to zero */ + silk_memset( r_LPC, 0, Order * sizeof( silk_float ) ); +} + diff --git a/port/third_party/opus/silk/float/LPC_inv_pred_gain_FLP.c b/port/third_party/opus/silk/float/LPC_inv_pred_gain_FLP.c new file mode 100644 index 00000000..2be2122d --- /dev/null +++ b/port/third_party/opus/silk/float/LPC_inv_pred_gain_FLP.c @@ -0,0 +1,73 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "SigProc_FLP.h" +#include "define.h" + +/* compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +/* this code is based on silk_a2k_FLP() */ +silk_float silk_LPC_inverse_pred_gain_FLP( /* O return inverse prediction gain, energy domain */ + const silk_float *A, /* I prediction coefficients [order] */ + opus_int32 order /* I prediction order */ +) +{ + opus_int k, n; + double invGain, rc, rc_mult1, rc_mult2, tmp1, tmp2; + silk_float Atmp[ SILK_MAX_ORDER_LPC ]; + + silk_memcpy( Atmp, A, order * sizeof(silk_float) ); + + invGain = 1.0; + for( k = order - 1; k > 0; k-- ) { + rc = -Atmp[ k ]; + rc_mult1 = 1.0f - rc * rc; + invGain *= rc_mult1; + if( invGain * MAX_PREDICTION_POWER_GAIN < 1.0f ) { + return 0.0f; + } + rc_mult2 = 1.0f / rc_mult1; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = Atmp[ n ]; + tmp2 = Atmp[ k - n - 1 ]; + Atmp[ n ] = (silk_float)( ( tmp1 - tmp2 * rc ) * rc_mult2 ); + Atmp[ k - n - 1 ] = (silk_float)( ( tmp2 - tmp1 * rc ) * rc_mult2 ); + } + } + rc = -Atmp[ 0 ]; + rc_mult1 = 1.0f - rc * rc; + invGain *= rc_mult1; + if( invGain * MAX_PREDICTION_POWER_GAIN < 1.0f ) { + return 0.0f; + } + return (silk_float)invGain; +} diff --git a/port/third_party/opus/silk/float/LTP_analysis_filter_FLP.c b/port/third_party/opus/silk/float/LTP_analysis_filter_FLP.c new file mode 100644 index 00000000..849b7c1c --- /dev/null +++ b/port/third_party/opus/silk/float/LTP_analysis_filter_FLP.c @@ -0,0 +1,75 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +void silk_LTP_analysis_filter_FLP( + silk_float *LTP_res, /* O LTP res MAX_NB_SUBFR*(pre_lgth+subfr_lngth) */ + const silk_float *x, /* I Input signal, with preceding samples */ + const silk_float B[ LTP_ORDER * MAX_NB_SUBFR ], /* I LTP coefficients for each subframe */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const silk_float invGains[ MAX_NB_SUBFR ], /* I Inverse quantization gains */ + const opus_int subfr_length, /* I Length of each subframe */ + const opus_int nb_subfr, /* I number of subframes */ + const opus_int pre_length /* I Preceding samples for each subframe */ +) +{ + const silk_float *x_ptr, *x_lag_ptr; + silk_float Btmp[ LTP_ORDER ]; + silk_float *LTP_res_ptr; + silk_float inv_gain; + opus_int k, i, j; + + x_ptr = x; + LTP_res_ptr = LTP_res; + for( k = 0; k < nb_subfr; k++ ) { + x_lag_ptr = x_ptr - pitchL[ k ]; + inv_gain = invGains[ k ]; + for( i = 0; i < LTP_ORDER; i++ ) { + Btmp[ i ] = B[ k * LTP_ORDER + i ]; + } + + /* LTP analysis FIR filter */ + for( i = 0; i < subfr_length + pre_length; i++ ) { + LTP_res_ptr[ i ] = x_ptr[ i ]; + /* Subtract long-term prediction */ + for( j = 0; j < LTP_ORDER; j++ ) { + LTP_res_ptr[ i ] -= Btmp[ j ] * x_lag_ptr[ LTP_ORDER / 2 - j ]; + } + LTP_res_ptr[ i ] *= inv_gain; + x_lag_ptr++; + } + + /* Update pointers */ + LTP_res_ptr += subfr_length + pre_length; + x_ptr += subfr_length; + } +} diff --git a/port/third_party/opus/silk/float/LTP_scale_ctrl_FLP.c b/port/third_party/opus/silk/float/LTP_scale_ctrl_FLP.c new file mode 100644 index 00000000..6f30ff09 --- /dev/null +++ b/port/third_party/opus/silk/float/LTP_scale_ctrl_FLP.c @@ -0,0 +1,58 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +void silk_LTP_scale_ctrl_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int round_loss; + + if( condCoding == CODE_INDEPENDENTLY ) { + /* Only scale if first frame in packet */ + round_loss = psEnc->sCmn.PacketLoss_perc * psEnc->sCmn.nFramesPerPacket; + if ( psEnc->sCmn.LBRR_flag ) { + /* LBRR reduces the effective loss. In practice, it does not square the loss because + losses aren't independent, but that still seems to work best. We also never go below 2%. */ + round_loss = 2 + silk_SMULBB( round_loss, round_loss) / 100; + } + psEnc->sCmn.indices.LTP_scaleIndex = silk_SMULBB( psEncCtrl->LTPredCodGain, round_loss ) > silk_log2lin( 2900 - psEnc->sCmn.SNR_dB_Q7 ); + psEnc->sCmn.indices.LTP_scaleIndex += silk_SMULBB( psEncCtrl->LTPredCodGain, round_loss ) > silk_log2lin( 3900 - psEnc->sCmn.SNR_dB_Q7 ); + } else { + /* Default is minimum scaling */ + psEnc->sCmn.indices.LTP_scaleIndex = 0; + } + + psEncCtrl->LTP_scale = (silk_float)silk_LTPScales_table_Q14[ psEnc->sCmn.indices.LTP_scaleIndex ] / 16384.0f; +} diff --git a/port/third_party/opus/silk/float/SigProc_FLP.h b/port/third_party/opus/silk/float/SigProc_FLP.h new file mode 100644 index 00000000..ff9281b8 --- /dev/null +++ b/port/third_party/opus/silk/float/SigProc_FLP.h @@ -0,0 +1,205 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_SIGPROC_FLP_H +#define SILK_SIGPROC_FLP_H + +#include "SigProc_FIX.h" +#include "float_cast.h" +#include "main.h" +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/********************************************************************/ +/* SIGNAL PROCESSING FUNCTIONS */ +/********************************************************************/ + +/* Chirp (bw expand) LP AR filter */ +void silk_bwexpander_FLP( + silk_float *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I length of ar */ + const silk_float chirp /* I chirp factor (typically in range (0..1) ) */ +); + +/* compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +/* this code is based on silk_FLP_a2k() */ +silk_float silk_LPC_inverse_pred_gain_FLP( /* O return inverse prediction gain, energy domain */ + const silk_float *A, /* I prediction coefficients [order] */ + opus_int32 order /* I prediction order */ +); + +silk_float silk_schur_FLP( /* O returns residual energy */ + silk_float refl_coef[], /* O reflection coefficients (length order) */ + const silk_float auto_corr[], /* I autocorrelation sequence (length order+1) */ + opus_int order /* I order */ +); + +void silk_k2a_FLP( + silk_float *A, /* O prediction coefficients [order] */ + const silk_float *rc, /* I reflection coefficients [order] */ + opus_int32 order /* I prediction order */ +); + +/* compute autocorrelation */ +void silk_autocorrelation_FLP( + silk_float *results, /* O result (length correlationCount) */ + const silk_float *inputData, /* I input data to correlate */ + opus_int inputDataSize, /* I length of input */ + opus_int correlationCount, /* I number of correlation taps to compute */ + int arch +); + +opus_int silk_pitch_analysis_core_FLP( /* O Voicing estimate: 0 voiced, 1 unvoiced */ + const silk_float *frame, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ + opus_int *pitch_out, /* O Pitch lag values [nb_subfr] */ + opus_int16 *lagIndex, /* O Lag Index */ + opus_int8 *contourIndex, /* O Pitch contour Index */ + silk_float *LTPCorr, /* I/O Normalized correlation; input: value from previous frame */ + opus_int prevLag, /* I Last lag of previous frame; set to zero is unvoiced */ + const silk_float search_thres1, /* I First stage threshold for lag candidates 0 - 1 */ + const silk_float search_thres2, /* I Final threshold for lag candidates 0 - 1 */ + const opus_int Fs_kHz, /* I sample frequency (kHz) */ + const opus_int complexity, /* I Complexity setting, 0-2, where 2 is highest */ + const opus_int nb_subfr, /* I Number of 5 ms subframes */ + int arch /* I Run-time architecture */ +); + +void silk_insertion_sort_decreasing_FLP( + silk_float *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +); + +/* Compute reflection coefficients from input signal */ +silk_float silk_burg_modified_FLP( /* O returns residual energy */ + silk_float A[], /* O prediction coefficients (length order) */ + const silk_float x[], /* I input signal, length: nb_subfr*(D+L_sub) */ + const silk_float minInvGain, /* I minimum inverse prediction gain */ + const opus_int subfr_length, /* I input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I number of subframes stacked in x */ + const opus_int D, /* I order */ + int arch +); + +/* multiply a vector by a constant */ +void silk_scale_vector_FLP( + silk_float *data1, + silk_float gain, + opus_int dataSize +); + +/* copy and multiply a vector by a constant */ +void silk_scale_copy_vector_FLP( + silk_float *data_out, + const silk_float *data_in, + silk_float gain, + opus_int dataSize +); + +/* inner product of two silk_float arrays, with result as double */ +double silk_inner_product_FLP_c( + const silk_float *data1, + const silk_float *data2, + opus_int dataSize +); + +#ifndef OVERRIDE_inner_product_FLP +#define silk_inner_product_FLP(data1, data2, dataSize, arch) ((void)arch,silk_inner_product_FLP_c(data1, data2, dataSize)) +#endif + + +/* sum of squares of a silk_float array, with result as double */ +double silk_energy_FLP( + const silk_float *data, + opus_int dataSize +); + +/********************************************************************/ +/* MACROS */ +/********************************************************************/ + +#define PI (3.1415926536f) + +#define silk_min_float( a, b ) (((a) < (b)) ? (a) : (b)) +#define silk_max_float( a, b ) (((a) > (b)) ? (a) : (b)) +#define silk_abs_float( a ) ((silk_float)fabs(a)) + +/* sigmoid function */ +static OPUS_INLINE silk_float silk_sigmoid( silk_float x ) +{ + return (silk_float)(1.0 / (1.0 + exp(-x))); +} + +/* floating-point to integer conversion (rounding) */ +static OPUS_INLINE opus_int32 silk_float2int( silk_float x ) +{ + return (opus_int32)float2int( x ); +} + +/* floating-point to integer conversion (rounding) */ +static OPUS_INLINE void silk_float2short_array( + opus_int16 *out, + const silk_float *in, + opus_int32 length +) +{ + opus_int32 k; + for( k = length - 1; k >= 0; k-- ) { + out[k] = silk_SAT16( (opus_int32)float2int( in[k] ) ); + } +} + +/* integer to floating-point conversion */ +static OPUS_INLINE void silk_short2float_array( + silk_float *out, + const opus_int16 *in, + opus_int32 length +) +{ + opus_int32 k; + for( k = length - 1; k >= 0; k-- ) { + out[k] = (silk_float)in[k]; + } +} + +/* using log2() helps the fixed-point conversion */ +static OPUS_INLINE silk_float silk_log2( double x ) +{ + return ( silk_float )( 3.32192809488736 * log10( x ) ); +} + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_SIGPROC_FLP_H */ diff --git a/port/third_party/opus/silk/float/apply_sine_window_FLP.c b/port/third_party/opus/silk/float/apply_sine_window_FLP.c new file mode 100644 index 00000000..e49e7179 --- /dev/null +++ b/port/third_party/opus/silk/float/apply_sine_window_FLP.c @@ -0,0 +1,81 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Apply sine window to signal vector */ +/* Window types: */ +/* 1 -> sine window from 0 to pi/2 */ +/* 2 -> sine window from pi/2 to pi */ +void silk_apply_sine_window_FLP( + silk_float px_win[], /* O Pointer to windowed signal */ + const silk_float px[], /* I Pointer to input signal */ + const opus_int win_type, /* I Selects a window type */ + const opus_int length /* I Window length, multiple of 4 */ +) +{ + opus_int k; + silk_float freq, c, S0, S1; + + celt_assert( win_type == 1 || win_type == 2 ); + + /* Length must be multiple of 4 */ + celt_assert( ( length & 3 ) == 0 ); + + freq = PI / ( length + 1 ); + + /* Approximation of 2 * cos(f) */ + c = 2.0f - freq * freq; + + /* Initialize state */ + if( win_type < 2 ) { + /* Start from 0 */ + S0 = 0.0f; + /* Approximation of sin(f) */ + S1 = freq; + } else { + /* Start from 1 */ + S0 = 1.0f; + /* Approximation of cos(f) */ + S1 = 0.5f * c; + } + + /* Uses the recursive equation: sin(n*f) = 2 * cos(f) * sin((n-1)*f) - sin((n-2)*f) */ + /* 4 samples at a time */ + for( k = 0; k < length; k += 4 ) { + px_win[ k + 0 ] = px[ k + 0 ] * 0.5f * ( S0 + S1 ); + px_win[ k + 1 ] = px[ k + 1 ] * S1; + S0 = c * S1 - S0; + px_win[ k + 2 ] = px[ k + 2 ] * 0.5f * ( S1 + S0 ); + px_win[ k + 3 ] = px[ k + 3 ] * S0; + S1 = c * S0 - S1; + } +} diff --git a/port/third_party/opus/silk/float/autocorrelation_FLP.c b/port/third_party/opus/silk/float/autocorrelation_FLP.c new file mode 100644 index 00000000..4253b26e --- /dev/null +++ b/port/third_party/opus/silk/float/autocorrelation_FLP.c @@ -0,0 +1,53 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "typedef.h" +#include "SigProc_FLP.h" + +/* compute autocorrelation */ +void silk_autocorrelation_FLP( + silk_float *results, /* O result (length correlationCount) */ + const silk_float *inputData, /* I input data to correlate */ + opus_int inputDataSize, /* I length of input */ + opus_int correlationCount, /* I number of correlation taps to compute */ + int arch +) +{ + opus_int i; + + if( correlationCount > inputDataSize ) { + correlationCount = inputDataSize; + } + + for( i = 0; i < correlationCount; i++ ) { + results[ i ] = (silk_float)silk_inner_product_FLP( inputData, inputData + i, inputDataSize - i, arch ); + } +} diff --git a/port/third_party/opus/silk/float/burg_modified_FLP.c b/port/third_party/opus/silk/float/burg_modified_FLP.c new file mode 100644 index 00000000..f5bef5dd --- /dev/null +++ b/port/third_party/opus/silk/float/burg_modified_FLP.c @@ -0,0 +1,187 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" +#include "tuning_parameters.h" +#include "define.h" + +#define MAX_FRAME_SIZE 384 /* subfr_length * nb_subfr = ( 0.005 * 16000 + 16 ) * 4 = 384*/ + +/* Compute reflection coefficients from input signal */ +silk_float silk_burg_modified_FLP( /* O returns residual energy */ + silk_float A[], /* O prediction coefficients (length order) */ + const silk_float x[], /* I input signal, length: nb_subfr*(D+L_sub) */ + const silk_float minInvGain, /* I minimum inverse prediction gain */ + const opus_int subfr_length, /* I input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I number of subframes stacked in x */ + const opus_int D, /* I order */ + int arch +) +{ + opus_int k, n, s, reached_max_gain; + double C0, invGain, num, nrg_f, nrg_b, rc, Atmp, tmp1, tmp2; + const silk_float *x_ptr; + double C_first_row[ SILK_MAX_ORDER_LPC ], C_last_row[ SILK_MAX_ORDER_LPC ]; + double CAf[ SILK_MAX_ORDER_LPC + 1 ], CAb[ SILK_MAX_ORDER_LPC + 1 ]; + double Af[ SILK_MAX_ORDER_LPC ]; + + celt_assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE ); + + /* Compute autocorrelations, added over subframes */ + C0 = silk_energy_FLP( x, nb_subfr * subfr_length ); + silk_memset( C_first_row, 0, SILK_MAX_ORDER_LPC * sizeof( double ) ); + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + for( n = 1; n < D + 1; n++ ) { + C_first_row[ n - 1 ] += silk_inner_product_FLP( x_ptr, x_ptr + n, subfr_length - n, arch ); + } + } + silk_memcpy( C_last_row, C_first_row, SILK_MAX_ORDER_LPC * sizeof( double ) ); + + /* Initialize */ + CAb[ 0 ] = CAf[ 0 ] = C0 + FIND_LPC_COND_FAC * C0 + 1e-9f; + invGain = 1.0f; + reached_max_gain = 0; + for( n = 0; n < D; n++ ) { + /* Update first row of correlation matrix (without first element) */ + /* Update last row of correlation matrix (without last element, stored in reversed order) */ + /* Update C * Af */ + /* Update C * flipud(Af) (stored in reversed order) */ + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + tmp1 = x_ptr[ n ]; + tmp2 = x_ptr[ subfr_length - n - 1 ]; + for( k = 0; k < n; k++ ) { + C_first_row[ k ] -= x_ptr[ n ] * x_ptr[ n - k - 1 ]; + C_last_row[ k ] -= x_ptr[ subfr_length - n - 1 ] * x_ptr[ subfr_length - n + k ]; + Atmp = Af[ k ]; + tmp1 += x_ptr[ n - k - 1 ] * Atmp; + tmp2 += x_ptr[ subfr_length - n + k ] * Atmp; + } + for( k = 0; k <= n; k++ ) { + CAf[ k ] -= tmp1 * x_ptr[ n - k ]; + CAb[ k ] -= tmp2 * x_ptr[ subfr_length - n + k - 1 ]; + } + } + tmp1 = C_first_row[ n ]; + tmp2 = C_last_row[ n ]; + for( k = 0; k < n; k++ ) { + Atmp = Af[ k ]; + tmp1 += C_last_row[ n - k - 1 ] * Atmp; + tmp2 += C_first_row[ n - k - 1 ] * Atmp; + } + CAf[ n + 1 ] = tmp1; + CAb[ n + 1 ] = tmp2; + + /* Calculate nominator and denominator for the next order reflection (parcor) coefficient */ + num = CAb[ n + 1 ]; + nrg_b = CAb[ 0 ]; + nrg_f = CAf[ 0 ]; + for( k = 0; k < n; k++ ) { + Atmp = Af[ k ]; + num += CAb[ n - k ] * Atmp; + nrg_b += CAb[ k + 1 ] * Atmp; + nrg_f += CAf[ k + 1 ] * Atmp; + } + silk_assert( nrg_f > 0.0 ); + silk_assert( nrg_b > 0.0 ); + + /* Calculate the next order reflection (parcor) coefficient */ + rc = -2.0 * num / ( nrg_f + nrg_b ); + silk_assert( rc > -1.0 && rc < 1.0 ); + + /* Update inverse prediction gain */ + tmp1 = invGain * ( 1.0 - rc * rc ); + if( tmp1 <= minInvGain ) { + /* Max prediction gain exceeded; set reflection coefficient such that max prediction gain is exactly hit */ + rc = sqrt( 1.0 - minInvGain / invGain ); + if( num > 0 ) { + /* Ensure adjusted reflection coefficients has the original sign */ + rc = -rc; + } + invGain = minInvGain; + reached_max_gain = 1; + } else { + invGain = tmp1; + } + + /* Update the AR coefficients */ + for( k = 0; k < (n + 1) >> 1; k++ ) { + tmp1 = Af[ k ]; + tmp2 = Af[ n - k - 1 ]; + Af[ k ] = tmp1 + rc * tmp2; + Af[ n - k - 1 ] = tmp2 + rc * tmp1; + } + Af[ n ] = rc; + + if( reached_max_gain ) { + /* Reached max prediction gain; set remaining coefficients to zero and exit loop */ + for( k = n + 1; k < D; k++ ) { + Af[ k ] = 0.0; + } + break; + } + + /* Update C * Af and C * Ab */ + for( k = 0; k <= n + 1; k++ ) { + tmp1 = CAf[ k ]; + CAf[ k ] += rc * CAb[ n - k + 1 ]; + CAb[ n - k + 1 ] += rc * tmp1; + } + } + + if( reached_max_gain ) { + /* Convert to silk_float */ + for( k = 0; k < D; k++ ) { + A[ k ] = (silk_float)( -Af[ k ] ); + } + /* Subtract energy of preceding samples from C0 */ + for( s = 0; s < nb_subfr; s++ ) { + C0 -= silk_energy_FLP( x + s * subfr_length, D ); + } + /* Approximate residual energy */ + nrg_f = C0 * invGain; + } else { + /* Compute residual energy and store coefficients as silk_float */ + nrg_f = CAf[ 0 ]; + tmp1 = 1.0; + for( k = 0; k < D; k++ ) { + Atmp = Af[ k ]; + nrg_f += CAf[ k + 1 ] * Atmp; + tmp1 += Atmp * Atmp; + A[ k ] = (silk_float)(-Atmp); + } + nrg_f -= FIND_LPC_COND_FAC * C0 * tmp1; + } + + /* Return residual energy */ + return (silk_float)nrg_f; +} diff --git a/port/third_party/opus/silk/float/bwexpander_FLP.c b/port/third_party/opus/silk/float/bwexpander_FLP.c new file mode 100644 index 00000000..d55a4d79 --- /dev/null +++ b/port/third_party/opus/silk/float/bwexpander_FLP.c @@ -0,0 +1,49 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* Chirp (bw expand) LP AR filter */ +void silk_bwexpander_FLP( + silk_float *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I length of ar */ + const silk_float chirp /* I chirp factor (typically in range (0..1) ) */ +) +{ + opus_int i; + silk_float cfac = chirp; + + for( i = 0; i < d - 1; i++ ) { + ar[ i ] *= cfac; + cfac *= chirp; + } + ar[ d - 1 ] *= cfac; +} diff --git a/port/third_party/opus/silk/float/corrMatrix_FLP.c b/port/third_party/opus/silk/float/corrMatrix_FLP.c new file mode 100644 index 00000000..eef6e8aa --- /dev/null +++ b/port/third_party/opus/silk/float/corrMatrix_FLP.c @@ -0,0 +1,95 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/********************************************************************** + * Correlation matrix computations for LS estimate. + **********************************************************************/ + +#include "main_FLP.h" + +/* Calculates correlation vector X'*t */ +void silk_corrVector_FLP( + const silk_float *x, /* I x vector [L+order-1] used to create X */ + const silk_float *t, /* I Target vector [L] */ + const opus_int L, /* I Length of vecors */ + const opus_int Order, /* I Max lag for correlation */ + silk_float *Xt, /* O X'*t correlation vector [order] */ + int arch +) +{ + opus_int lag; + const silk_float *ptr1; + + ptr1 = &x[ Order - 1 ]; /* Points to first sample of column 0 of X: X[:,0] */ + for( lag = 0; lag < Order; lag++ ) { + /* Calculate X[:,lag]'*t */ + Xt[ lag ] = (silk_float)silk_inner_product_FLP( ptr1, t, L, arch ); + ptr1--; /* Next column of X */ + } +} + +/* Calculates correlation matrix X'*X */ +void silk_corrMatrix_FLP( + const silk_float *x, /* I x vector [ L+order-1 ] used to create X */ + const opus_int L, /* I Length of vectors */ + const opus_int Order, /* I Max lag for correlation */ + silk_float *XX, /* O X'*X correlation matrix [order x order] */ + int arch +) +{ + opus_int j, lag; + double energy; + const silk_float *ptr1, *ptr2; + + ptr1 = &x[ Order - 1 ]; /* First sample of column 0 of X */ + energy = silk_energy_FLP( ptr1, L ); /* X[:,0]'*X[:,0] */ + matrix_ptr( XX, 0, 0, Order ) = ( silk_float )energy; + for( j = 1; j < Order; j++ ) { + /* Calculate X[:,j]'*X[:,j] */ + energy += ptr1[ -j ] * ptr1[ -j ] - ptr1[ L - j ] * ptr1[ L - j ]; + matrix_ptr( XX, j, j, Order ) = ( silk_float )energy; + } + + ptr2 = &x[ Order - 2 ]; /* First sample of column 1 of X */ + for( lag = 1; lag < Order; lag++ ) { + /* Calculate X[:,0]'*X[:,lag] */ + energy = silk_inner_product_FLP( ptr1, ptr2, L, arch ); + matrix_ptr( XX, lag, 0, Order ) = ( silk_float )energy; + matrix_ptr( XX, 0, lag, Order ) = ( silk_float )energy; + /* Calculate X[:,j]'*X[:,j + lag] */ + for( j = 1; j < ( Order - lag ); j++ ) { + energy += ptr1[ -j ] * ptr2[ -j ] - ptr1[ L - j ] * ptr2[ L - j ]; + matrix_ptr( XX, lag + j, j, Order ) = ( silk_float )energy; + matrix_ptr( XX, j, lag + j, Order ) = ( silk_float )energy; + } + ptr2--; /* Next column of X */ + } +} diff --git a/port/third_party/opus/silk/float/encode_frame_FLP.c b/port/third_party/opus/silk/float/encode_frame_FLP.c new file mode 100644 index 00000000..8a327c56 --- /dev/null +++ b/port/third_party/opus/silk/float/encode_frame_FLP.c @@ -0,0 +1,432 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main_FLP.h" +#include "tuning_parameters.h" + +/* Low Bitrate Redundancy (LBRR) encoding. Reuse all parameters but encode with lower bitrate */ +static OPUS_INLINE void silk_LBRR_encode_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float xfw[], /* I Input signal */ + opus_int condCoding /* I The type of conditional coding used so far for this frame */ +); + +void silk_encode_do_VAD_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + opus_int activity /* I Decision of Opus voice activity detector */ +) +{ + const opus_int activity_threshold = SILK_FIX_CONST( SPEECH_ACTIVITY_DTX_THRES, 8 ); + + /****************************/ + /* Voice Activity Detection */ + /****************************/ + silk_VAD_GetSA_Q8( &psEnc->sCmn, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.arch ); + /* If Opus VAD is inactive and Silk VAD is active: lower Silk VAD to just under the threshold */ + if( activity == VAD_NO_ACTIVITY && psEnc->sCmn.speech_activity_Q8 >= activity_threshold ) { + psEnc->sCmn.speech_activity_Q8 = activity_threshold - 1; + } + + /**************************************************/ + /* Convert speech activity into VAD and DTX flags */ + /**************************************************/ + if( psEnc->sCmn.speech_activity_Q8 < activity_threshold ) { + psEnc->sCmn.indices.signalType = TYPE_NO_VOICE_ACTIVITY; + psEnc->sCmn.noSpeechCounter++; + if( psEnc->sCmn.noSpeechCounter <= NB_SPEECH_FRAMES_BEFORE_DTX ) { + psEnc->sCmn.inDTX = 0; + } else if( psEnc->sCmn.noSpeechCounter > MAX_CONSECUTIVE_DTX + NB_SPEECH_FRAMES_BEFORE_DTX ) { + psEnc->sCmn.noSpeechCounter = NB_SPEECH_FRAMES_BEFORE_DTX; + psEnc->sCmn.inDTX = 0; + } + psEnc->sCmn.VAD_flags[ psEnc->sCmn.nFramesEncoded ] = 0; + } else { + psEnc->sCmn.noSpeechCounter = 0; + psEnc->sCmn.inDTX = 0; + psEnc->sCmn.indices.signalType = TYPE_UNVOICED; + psEnc->sCmn.VAD_flags[ psEnc->sCmn.nFramesEncoded ] = 1; + } +} + +/****************/ +/* Encode frame */ +/****************/ +opus_int silk_encode_frame_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + opus_int32 *pnBytesOut, /* O Number of payload bytes; */ + ec_enc *psRangeEnc, /* I/O compressor data structure */ + opus_int condCoding, /* I The type of conditional coding to use */ + opus_int maxBits, /* I If > 0: maximum number of output bits */ + opus_int useCBR /* I Flag to force constant-bitrate operation */ +) +{ + silk_encoder_control_FLP sEncCtrl; + opus_int i, iter, maxIter, found_upper, found_lower, ret = 0; + silk_float *x_frame, *res_pitch_frame; + silk_float res_pitch[ 2 * MAX_FRAME_LENGTH + LA_PITCH_MAX ]; + ec_enc sRangeEnc_copy, sRangeEnc_copy2; + silk_nsq_state sNSQ_copy, sNSQ_copy2; + opus_int32 seed_copy, nBits, nBits_lower, nBits_upper, gainMult_lower, gainMult_upper; + opus_int32 gainsID, gainsID_lower, gainsID_upper; + opus_int16 gainMult_Q8; + opus_int16 ec_prevLagIndex_copy; + opus_int ec_prevSignalType_copy; + opus_int8 LastGainIndex_copy2; + opus_int32 pGains_Q16[ MAX_NB_SUBFR ]; + opus_uint8 ec_buf_copy[ 1275 ]; + opus_int gain_lock[ MAX_NB_SUBFR ] = {0}; + opus_int16 best_gain_mult[ MAX_NB_SUBFR ]; + opus_int best_sum[ MAX_NB_SUBFR ]; + opus_int bits_margin; + + /* For CBR, 5 bits below budget is close enough. For VBR, allow up to 25% below the cap if we initially busted the budget. */ + bits_margin = useCBR ? 5 : maxBits/4; + /* This is totally unnecessary but many compilers (including gcc) are too dumb to realise it */ + LastGainIndex_copy2 = nBits_lower = nBits_upper = gainMult_lower = gainMult_upper = 0; + + psEnc->sCmn.indices.Seed = psEnc->sCmn.frameCounter++ & 3; + + /**************************************************************/ + /* Set up Input Pointers, and insert frame in input buffer */ + /**************************************************************/ + /* pointers aligned with start of frame to encode */ + x_frame = psEnc->x_buf + psEnc->sCmn.ltp_mem_length; /* start of frame to encode */ + res_pitch_frame = res_pitch + psEnc->sCmn.ltp_mem_length; /* start of pitch LPC residual frame */ + + /***************************************/ + /* Ensure smooth bandwidth transitions */ + /***************************************/ + silk_LP_variable_cutoff( &psEnc->sCmn.sLP, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length ); + + /*******************************************/ + /* Copy new frame to front of input buffer */ + /*******************************************/ + silk_short2float_array( x_frame + LA_SHAPE_MS * psEnc->sCmn.fs_kHz, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length ); + + /* Add tiny signal to avoid high CPU load from denormalized floating point numbers */ + for( i = 0; i < 8; i++ ) { + x_frame[ LA_SHAPE_MS * psEnc->sCmn.fs_kHz + i * ( psEnc->sCmn.frame_length >> 3 ) ] += ( 1 - ( i & 2 ) ) * 1e-6f; + } + + if( !psEnc->sCmn.prefillFlag ) { + /*****************************************/ + /* Find pitch lags, initial LPC analysis */ + /*****************************************/ + silk_find_pitch_lags_FLP( psEnc, &sEncCtrl, res_pitch, x_frame, psEnc->sCmn.arch ); + + /************************/ + /* Noise shape analysis */ + /************************/ + silk_noise_shape_analysis_FLP( psEnc, &sEncCtrl, res_pitch_frame, x_frame ); + + /***************************************************/ + /* Find linear prediction coefficients (LPC + LTP) */ + /***************************************************/ + silk_find_pred_coefs_FLP( psEnc, &sEncCtrl, res_pitch_frame, x_frame, condCoding ); + + /****************************************/ + /* Process gains */ + /****************************************/ + silk_process_gains_FLP( psEnc, &sEncCtrl, condCoding ); + + /****************************************/ + /* Low Bitrate Redundant Encoding */ + /****************************************/ + silk_LBRR_encode_FLP( psEnc, &sEncCtrl, x_frame, condCoding ); + + /* Loop over quantizer and entroy coding to control bitrate */ + maxIter = 6; + gainMult_Q8 = SILK_FIX_CONST( 1, 8 ); + found_lower = 0; + found_upper = 0; + gainsID = silk_gains_ID( psEnc->sCmn.indices.GainsIndices, psEnc->sCmn.nb_subfr ); + gainsID_lower = -1; + gainsID_upper = -1; + /* Copy part of the input state */ + silk_memcpy( &sRangeEnc_copy, psRangeEnc, sizeof( ec_enc ) ); + silk_memcpy( &sNSQ_copy, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + seed_copy = psEnc->sCmn.indices.Seed; + ec_prevLagIndex_copy = psEnc->sCmn.ec_prevLagIndex; + ec_prevSignalType_copy = psEnc->sCmn.ec_prevSignalType; + for( iter = 0; ; iter++ ) { + if( gainsID == gainsID_lower ) { + nBits = nBits_lower; + } else if( gainsID == gainsID_upper ) { + nBits = nBits_upper; + } else { + /* Restore part of the input state */ + if( iter > 0 ) { + silk_memcpy( psRangeEnc, &sRangeEnc_copy, sizeof( ec_enc ) ); + silk_memcpy( &psEnc->sCmn.sNSQ, &sNSQ_copy, sizeof( silk_nsq_state ) ); + psEnc->sCmn.indices.Seed = seed_copy; + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; + } + + /*****************************************/ + /* Noise shaping quantization */ + /*****************************************/ + silk_NSQ_wrapper_FLP( psEnc, &sEncCtrl, &psEnc->sCmn.indices, &psEnc->sCmn.sNSQ, psEnc->sCmn.pulses, x_frame ); + + if ( iter == maxIter && !found_lower ) { + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); + } + + /****************************************/ + /* Encode Parameters */ + /****************************************/ + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); + + /****************************************/ + /* Encode Excitation Signal */ + /****************************************/ + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); + + nBits = ec_tell( psRangeEnc ); + + /* If we still bust after the last iteration, do some damage control. */ + if ( iter == maxIter && !found_lower && nBits > maxBits ) { + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); + + /* Keep gains the same as the last frame. */ + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + psEnc->sCmn.indices.GainsIndices[ i ] = 4; + } + if (condCoding != CODE_CONDITIONALLY) { + psEnc->sCmn.indices.GainsIndices[ 0 ] = sEncCtrl.lastGainIndexPrev; + } + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; + /* Clear all pulses. */ + for ( i = 0; i < psEnc->sCmn.frame_length; i++ ) { + psEnc->sCmn.pulses[ i ] = 0; + } + + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); + + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); + + nBits = ec_tell( psRangeEnc ); + } + + if( useCBR == 0 && iter == 0 && nBits <= maxBits ) { + break; + } + } + + if( iter == maxIter ) { + if( found_lower && ( gainsID == gainsID_lower || nBits > maxBits ) ) { + /* Restore output state from earlier iteration that did meet the bitrate budget */ + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); + celt_assert( sRangeEnc_copy2.offs <= 1275 ); + silk_memcpy( psRangeEnc->buf, ec_buf_copy, sRangeEnc_copy2.offs ); + silk_memcpy( &psEnc->sCmn.sNSQ, &sNSQ_copy2, sizeof( silk_nsq_state ) ); + psEnc->sShape.LastGainIndex = LastGainIndex_copy2; + } + break; + } + + if( nBits > maxBits ) { + if( found_lower == 0 && iter >= 2 ) { + /* Adjust the quantizer's rate/distortion tradeoff and discard previous "upper" results */ + sEncCtrl.Lambda = silk_max_float(sEncCtrl.Lambda*1.5f, 1.5f); + /* Reducing dithering can help us hit the target. */ + psEnc->sCmn.indices.quantOffsetType = 0; + found_upper = 0; + gainsID_upper = -1; + } else { + found_upper = 1; + nBits_upper = nBits; + gainMult_upper = gainMult_Q8; + gainsID_upper = gainsID; + } + } else if( nBits < maxBits - bits_margin ) { + found_lower = 1; + nBits_lower = nBits; + gainMult_lower = gainMult_Q8; + if( gainsID != gainsID_lower ) { + gainsID_lower = gainsID; + /* Copy part of the output state */ + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); + celt_assert( psRangeEnc->offs <= 1275 ); + silk_memcpy( ec_buf_copy, psRangeEnc->buf, psRangeEnc->offs ); + silk_memcpy( &sNSQ_copy2, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + LastGainIndex_copy2 = psEnc->sShape.LastGainIndex; + } + } else { + /* Close enough */ + break; + } + + if ( !found_lower && nBits > maxBits ) { + int j; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + int sum=0; + for ( j = i*psEnc->sCmn.subfr_length; j < (i+1)*psEnc->sCmn.subfr_length; j++ ) { + sum += abs( psEnc->sCmn.pulses[j] ); + } + if ( iter == 0 || (sum < best_sum[i] && !gain_lock[i]) ) { + best_sum[i] = sum; + best_gain_mult[i] = gainMult_Q8; + } else { + gain_lock[i] = 1; + } + } + } + if( ( found_lower & found_upper ) == 0 ) { + /* Adjust gain according to high-rate rate/distortion curve */ + if( nBits > maxBits ) { + gainMult_Q8 = silk_min_32( 1024, gainMult_Q8*3/2 ); + } else { + gainMult_Q8 = silk_max_32( 64, gainMult_Q8*4/5 ); + } + } else { + /* Adjust gain by interpolating */ + gainMult_Q8 = gainMult_lower + ( ( gainMult_upper - gainMult_lower ) * ( maxBits - nBits_lower ) ) / ( nBits_upper - nBits_lower ); + /* New gain multplier must be between 25% and 75% of old range (note that gainMult_upper < gainMult_lower) */ + if( gainMult_Q8 > silk_ADD_RSHIFT32( gainMult_lower, gainMult_upper - gainMult_lower, 2 ) ) { + gainMult_Q8 = silk_ADD_RSHIFT32( gainMult_lower, gainMult_upper - gainMult_lower, 2 ); + } else + if( gainMult_Q8 < silk_SUB_RSHIFT32( gainMult_upper, gainMult_upper - gainMult_lower, 2 ) ) { + gainMult_Q8 = silk_SUB_RSHIFT32( gainMult_upper, gainMult_upper - gainMult_lower, 2 ); + } + } + + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + opus_int16 tmp; + if ( gain_lock[i] ) { + tmp = best_gain_mult[i]; + } else { + tmp = gainMult_Q8; + } + pGains_Q16[ i ] = silk_LSHIFT_SAT32( silk_SMULWB( sEncCtrl.GainsUnq_Q16[ i ], tmp ), 8 ); + } + + /* Quantize gains */ + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; + silk_gains_quant( psEnc->sCmn.indices.GainsIndices, pGains_Q16, + &psEnc->sShape.LastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /* Unique identifier of gains vector */ + gainsID = silk_gains_ID( psEnc->sCmn.indices.GainsIndices, psEnc->sCmn.nb_subfr ); + + /* Overwrite unquantized gains with quantized gains and convert back to Q0 from Q16 */ + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + sEncCtrl.Gains[ i ] = pGains_Q16[ i ] / 65536.0f; + } + } + } + + /* Update input buffer */ + silk_memmove( psEnc->x_buf, &psEnc->x_buf[ psEnc->sCmn.frame_length ], + ( psEnc->sCmn.ltp_mem_length + LA_SHAPE_MS * psEnc->sCmn.fs_kHz ) * sizeof( silk_float ) ); + + /* Exit without entropy coding */ + if( psEnc->sCmn.prefillFlag ) { + /* No payload */ + *pnBytesOut = 0; + return ret; + } + + /* Parameters needed for next frame */ + psEnc->sCmn.prevLag = sEncCtrl.pitchL[ psEnc->sCmn.nb_subfr - 1 ]; + psEnc->sCmn.prevSignalType = psEnc->sCmn.indices.signalType; + + /****************************************/ + /* Finalize payload */ + /****************************************/ + psEnc->sCmn.first_frame_after_reset = 0; + /* Payload size */ + *pnBytesOut = silk_RSHIFT( ec_tell( psRangeEnc ) + 7, 3 ); + + return ret; +} + +/* Low-Bitrate Redundancy (LBRR) encoding. Reuse all parameters but encode excitation at lower bitrate */ +static OPUS_INLINE void silk_LBRR_encode_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float xfw[], /* I Input signal */ + opus_int condCoding /* I The type of conditional coding used so far for this frame */ +) +{ + opus_int k; + opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; + silk_float TempGains[ MAX_NB_SUBFR ]; + SideInfoIndices *psIndices_LBRR = &psEnc->sCmn.indices_LBRR[ psEnc->sCmn.nFramesEncoded ]; + silk_nsq_state sNSQ_LBRR; + + /*******************************************/ + /* Control use of inband LBRR */ + /*******************************************/ + if( psEnc->sCmn.LBRR_enabled && psEnc->sCmn.speech_activity_Q8 > SILK_FIX_CONST( LBRR_SPEECH_ACTIVITY_THRES, 8 ) ) { + psEnc->sCmn.LBRR_flags[ psEnc->sCmn.nFramesEncoded ] = 1; + + /* Copy noise shaping quantizer state and quantization indices from regular encoding */ + silk_memcpy( &sNSQ_LBRR, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + silk_memcpy( psIndices_LBRR, &psEnc->sCmn.indices, sizeof( SideInfoIndices ) ); + + /* Save original gains */ + silk_memcpy( TempGains, psEncCtrl->Gains, psEnc->sCmn.nb_subfr * sizeof( silk_float ) ); + + if( psEnc->sCmn.nFramesEncoded == 0 || psEnc->sCmn.LBRR_flags[ psEnc->sCmn.nFramesEncoded - 1 ] == 0 ) { + /* First frame in packet or previous frame not LBRR coded */ + psEnc->sCmn.LBRRprevLastGainIndex = psEnc->sShape.LastGainIndex; + + /* Increase Gains to get target LBRR rate */ + psIndices_LBRR->GainsIndices[ 0 ] += psEnc->sCmn.LBRR_GainIncreases; + psIndices_LBRR->GainsIndices[ 0 ] = silk_min_int( psIndices_LBRR->GainsIndices[ 0 ], N_LEVELS_QGAIN - 1 ); + } + + /* Decode to get gains in sync with decoder */ + silk_gains_dequant( Gains_Q16, psIndices_LBRR->GainsIndices, + &psEnc->sCmn.LBRRprevLastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /* Overwrite unquantized gains with quantized gains and convert back to Q0 from Q16 */ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains[ k ] = Gains_Q16[ k ] * ( 1.0f / 65536.0f ); + } + + /*****************************************/ + /* Noise shaping quantization */ + /*****************************************/ + silk_NSQ_wrapper_FLP( psEnc, psEncCtrl, psIndices_LBRR, &sNSQ_LBRR, + psEnc->sCmn.pulses_LBRR[ psEnc->sCmn.nFramesEncoded ], xfw ); + + /* Restore original gains */ + silk_memcpy( psEncCtrl->Gains, TempGains, psEnc->sCmn.nb_subfr * sizeof( silk_float ) ); + } +} diff --git a/port/third_party/opus/silk/float/energy_FLP.c b/port/third_party/opus/silk/float/energy_FLP.c new file mode 100644 index 00000000..7bc7173c --- /dev/null +++ b/port/third_party/opus/silk/float/energy_FLP.c @@ -0,0 +1,59 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* sum of squares of a silk_float array, with result as double */ +double silk_energy_FLP( + const silk_float *data, + opus_int dataSize +) +{ + opus_int i; + double result; + + /* 4x unrolled loop */ + result = 0.0; + for( i = 0; i < dataSize - 3; i += 4 ) { + result += data[ i + 0 ] * (double)data[ i + 0 ] + + data[ i + 1 ] * (double)data[ i + 1 ] + + data[ i + 2 ] * (double)data[ i + 2 ] + + data[ i + 3 ] * (double)data[ i + 3 ]; + } + + /* add any remaining products */ + for( ; i < dataSize; i++ ) { + result += data[ i ] * (double)data[ i ]; + } + + silk_assert( result >= 0.0 ); + return result; +} diff --git a/port/third_party/opus/silk/float/find_LPC_FLP.c b/port/third_party/opus/silk/float/find_LPC_FLP.c new file mode 100644 index 00000000..6ccd711d --- /dev/null +++ b/port/third_party/opus/silk/float/find_LPC_FLP.c @@ -0,0 +1,105 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "define.h" +#include "main_FLP.h" +#include "tuning_parameters.h" + +/* LPC analysis */ +void silk_find_LPC_FLP( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 NLSF_Q15[], /* O NLSFs */ + const silk_float x[], /* I Input signal */ + const silk_float minInvGain, /* I Inverse of max prediction gain */ + int arch +) +{ + opus_int k, subfr_length; + silk_float a[ MAX_LPC_ORDER ]; + + /* Used only for NLSF interpolation */ + silk_float res_nrg, res_nrg_2nd, res_nrg_interp; + opus_int16 NLSF0_Q15[ MAX_LPC_ORDER ]; + silk_float a_tmp[ MAX_LPC_ORDER ]; + silk_float LPC_res[ MAX_FRAME_LENGTH + MAX_NB_SUBFR * MAX_LPC_ORDER ]; + + subfr_length = psEncC->subfr_length + psEncC->predictLPCOrder; + + /* Default: No interpolation */ + psEncC->indices.NLSFInterpCoef_Q2 = 4; + + /* Burg AR analysis for the full frame */ + res_nrg = silk_burg_modified_FLP( a, x, minInvGain, subfr_length, psEncC->nb_subfr, psEncC->predictLPCOrder, arch ); + + if( psEncC->useInterpolatedNLSFs && !psEncC->first_frame_after_reset && psEncC->nb_subfr == MAX_NB_SUBFR ) { + /* Optimal solution for last 10 ms; subtract residual energy here, as that's easier than */ + /* adding it to the residual energy of the first 10 ms in each iteration of the search below */ + res_nrg -= silk_burg_modified_FLP( a_tmp, x + ( MAX_NB_SUBFR / 2 ) * subfr_length, minInvGain, subfr_length, MAX_NB_SUBFR / 2, psEncC->predictLPCOrder, arch ); + + /* Convert to NLSFs */ + silk_A2NLSF_FLP( NLSF_Q15, a_tmp, psEncC->predictLPCOrder ); + + /* Search over interpolation indices to find the one with lowest residual energy */ + res_nrg_2nd = silk_float_MAX; + for( k = 3; k >= 0; k-- ) { + /* Interpolate NLSFs for first half */ + silk_interpolate( NLSF0_Q15, psEncC->prev_NLSFq_Q15, NLSF_Q15, k, psEncC->predictLPCOrder ); + + /* Convert to LPC for residual energy evaluation */ + silk_NLSF2A_FLP( a_tmp, NLSF0_Q15, psEncC->predictLPCOrder, psEncC->arch ); + + /* Calculate residual energy with LSF interpolation */ + silk_LPC_analysis_filter_FLP( LPC_res, a_tmp, x, 2 * subfr_length, psEncC->predictLPCOrder ); + res_nrg_interp = (silk_float)( + silk_energy_FLP( LPC_res + psEncC->predictLPCOrder, subfr_length - psEncC->predictLPCOrder ) + + silk_energy_FLP( LPC_res + psEncC->predictLPCOrder + subfr_length, subfr_length - psEncC->predictLPCOrder ) ); + + /* Determine whether current interpolated NLSFs are best so far */ + if( res_nrg_interp < res_nrg ) { + /* Interpolation has lower residual energy */ + res_nrg = res_nrg_interp; + psEncC->indices.NLSFInterpCoef_Q2 = (opus_int8)k; + } else if( res_nrg_interp > res_nrg_2nd ) { + /* No reason to continue iterating - residual energies will continue to climb */ + break; + } + res_nrg_2nd = res_nrg_interp; + } + } + + if( psEncC->indices.NLSFInterpCoef_Q2 == 4 ) { + /* NLSF interpolation is currently inactive, calculate NLSFs from full frame AR coefficients */ + silk_A2NLSF_FLP( NLSF_Q15, a, psEncC->predictLPCOrder ); + } + + celt_assert( psEncC->indices.NLSFInterpCoef_Q2 == 4 || + ( psEncC->useInterpolatedNLSFs && !psEncC->first_frame_after_reset && psEncC->nb_subfr == MAX_NB_SUBFR ) ); +} diff --git a/port/third_party/opus/silk/float/find_LTP_FLP.c b/port/third_party/opus/silk/float/find_LTP_FLP.c new file mode 100644 index 00000000..90aeeac0 --- /dev/null +++ b/port/third_party/opus/silk/float/find_LTP_FLP.c @@ -0,0 +1,65 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" +#include "tuning_parameters.h" + +void silk_find_LTP_FLP( + silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ + silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* O Weight for LTP quantization */ + const silk_float r_ptr[], /* I LPC residual */ + const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I number of subframes */ + int arch +) +{ + opus_int k; + silk_float *xX_ptr, *XX_ptr; + const silk_float *lag_ptr; + silk_float xx, temp; + + xX_ptr = xX; + XX_ptr = XX; + for( k = 0; k < nb_subfr; k++ ) { + lag_ptr = r_ptr - ( lag[ k ] + LTP_ORDER / 2 ); + silk_corrMatrix_FLP( lag_ptr, subfr_length, LTP_ORDER, XX_ptr, arch ); + silk_corrVector_FLP( lag_ptr, r_ptr, subfr_length, LTP_ORDER, xX_ptr, arch ); + xx = ( silk_float )silk_energy_FLP( r_ptr, subfr_length + LTP_ORDER ); + temp = 1.0f / silk_max( xx, LTP_CORR_INV_MAX * 0.5f * ( XX_ptr[ 0 ] + XX_ptr[ 24 ] ) + 1.0f ); + silk_scale_vector_FLP( XX_ptr, temp, LTP_ORDER * LTP_ORDER ); + silk_scale_vector_FLP( xX_ptr, temp, LTP_ORDER ); + + r_ptr += subfr_length; + XX_ptr += LTP_ORDER * LTP_ORDER; + xX_ptr += LTP_ORDER; + } +} diff --git a/port/third_party/opus/silk/float/find_pitch_lags_FLP.c b/port/third_party/opus/silk/float/find_pitch_lags_FLP.c new file mode 100644 index 00000000..1f6bd599 --- /dev/null +++ b/port/third_party/opus/silk/float/find_pitch_lags_FLP.c @@ -0,0 +1,132 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main_FLP.h" +#include "tuning_parameters.h" + +void silk_find_pitch_lags_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + silk_float res[], /* O Residual */ + const silk_float x[], /* I Speech signal */ + int arch /* I Run-time architecture */ +) +{ + opus_int buf_len; + silk_float thrhld, res_nrg; + const silk_float *x_buf_ptr, *x_buf; + silk_float auto_corr[ MAX_FIND_PITCH_LPC_ORDER + 1 ]; + silk_float A[ MAX_FIND_PITCH_LPC_ORDER ]; + silk_float refl_coef[ MAX_FIND_PITCH_LPC_ORDER ]; + silk_float Wsig[ FIND_PITCH_LPC_WIN_MAX ]; + silk_float *Wsig_ptr; + + /******************************************/ + /* Set up buffer lengths etc based on Fs */ + /******************************************/ + buf_len = psEnc->sCmn.la_pitch + psEnc->sCmn.frame_length + psEnc->sCmn.ltp_mem_length; + + /* Safety check */ + celt_assert( buf_len >= psEnc->sCmn.pitch_LPC_win_length ); + + x_buf = x - psEnc->sCmn.ltp_mem_length; + + /******************************************/ + /* Estimate LPC AR coeficients */ + /******************************************/ + + /* Calculate windowed signal */ + + /* First LA_LTP samples */ + x_buf_ptr = x_buf + buf_len - psEnc->sCmn.pitch_LPC_win_length; + Wsig_ptr = Wsig; + silk_apply_sine_window_FLP( Wsig_ptr, x_buf_ptr, 1, psEnc->sCmn.la_pitch ); + + /* Middle non-windowed samples */ + Wsig_ptr += psEnc->sCmn.la_pitch; + x_buf_ptr += psEnc->sCmn.la_pitch; + silk_memcpy( Wsig_ptr, x_buf_ptr, ( psEnc->sCmn.pitch_LPC_win_length - ( psEnc->sCmn.la_pitch << 1 ) ) * sizeof( silk_float ) ); + + /* Last LA_LTP samples */ + Wsig_ptr += psEnc->sCmn.pitch_LPC_win_length - ( psEnc->sCmn.la_pitch << 1 ); + x_buf_ptr += psEnc->sCmn.pitch_LPC_win_length - ( psEnc->sCmn.la_pitch << 1 ); + silk_apply_sine_window_FLP( Wsig_ptr, x_buf_ptr, 2, psEnc->sCmn.la_pitch ); + + /* Calculate autocorrelation sequence */ + silk_autocorrelation_FLP( auto_corr, Wsig, psEnc->sCmn.pitch_LPC_win_length, psEnc->sCmn.pitchEstimationLPCOrder + 1, arch ); + + /* Add white noise, as a fraction of the energy */ + auto_corr[ 0 ] += auto_corr[ 0 ] * FIND_PITCH_WHITE_NOISE_FRACTION + 1; + + /* Calculate the reflection coefficients using Schur */ + res_nrg = silk_schur_FLP( refl_coef, auto_corr, psEnc->sCmn.pitchEstimationLPCOrder ); + + /* Prediction gain */ + psEncCtrl->predGain = auto_corr[ 0 ] / silk_max_float( res_nrg, 1.0f ); + + /* Convert reflection coefficients to prediction coefficients */ + silk_k2a_FLP( A, refl_coef, psEnc->sCmn.pitchEstimationLPCOrder ); + + /* Bandwidth expansion */ + silk_bwexpander_FLP( A, psEnc->sCmn.pitchEstimationLPCOrder, FIND_PITCH_BANDWIDTH_EXPANSION ); + + /*****************************************/ + /* LPC analysis filtering */ + /*****************************************/ + silk_LPC_analysis_filter_FLP( res, A, x_buf, buf_len, psEnc->sCmn.pitchEstimationLPCOrder ); + + if( psEnc->sCmn.indices.signalType != TYPE_NO_VOICE_ACTIVITY && psEnc->sCmn.first_frame_after_reset == 0 ) { + /* Threshold for pitch estimator */ + thrhld = 0.6f; + thrhld -= 0.004f * psEnc->sCmn.pitchEstimationLPCOrder; + thrhld -= 0.1f * psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ); + thrhld -= 0.15f * (psEnc->sCmn.prevSignalType >> 1); + thrhld -= 0.1f * psEnc->sCmn.input_tilt_Q15 * ( 1.0f / 32768.0f ); + + /*****************************************/ + /* Call Pitch estimator */ + /*****************************************/ + if( silk_pitch_analysis_core_FLP( res, psEncCtrl->pitchL, &psEnc->sCmn.indices.lagIndex, + &psEnc->sCmn.indices.contourIndex, &psEnc->LTPCorr, psEnc->sCmn.prevLag, psEnc->sCmn.pitchEstimationThreshold_Q16 / 65536.0f, + thrhld, psEnc->sCmn.fs_kHz, psEnc->sCmn.pitchEstimationComplexity, psEnc->sCmn.nb_subfr, arch ) == 0 ) + { + psEnc->sCmn.indices.signalType = TYPE_VOICED; + } else { + psEnc->sCmn.indices.signalType = TYPE_UNVOICED; + } + } else { + silk_memset( psEncCtrl->pitchL, 0, sizeof( psEncCtrl->pitchL ) ); + psEnc->sCmn.indices.lagIndex = 0; + psEnc->sCmn.indices.contourIndex = 0; + psEnc->LTPCorr = 0; + } +} diff --git a/port/third_party/opus/silk/float/find_pred_coefs_FLP.c b/port/third_party/opus/silk/float/find_pred_coefs_FLP.c new file mode 100644 index 00000000..f3c54cf4 --- /dev/null +++ b/port/third_party/opus/silk/float/find_pred_coefs_FLP.c @@ -0,0 +1,117 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Find LPC and LTP coefficients */ +void silk_find_pred_coefs_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float res_pitch[], /* I Residual from pitch analysis */ + const silk_float x[], /* I Speech signal */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i; + silk_float XXLTP[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ]; + silk_float xXLTP[ MAX_NB_SUBFR * LTP_ORDER ]; + silk_float invGains[ MAX_NB_SUBFR ]; + /* Set to NLSF_Q15 to zero so we don't copy junk to the state. */ + opus_int16 NLSF_Q15[ MAX_LPC_ORDER ]={0}; + const silk_float *x_ptr; + silk_float *x_pre_ptr, LPC_in_pre[ MAX_NB_SUBFR * MAX_LPC_ORDER + MAX_FRAME_LENGTH ]; + silk_float minInvGain; + + /* Weighting for weighted least squares */ + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + silk_assert( psEncCtrl->Gains[ i ] > 0.0f ); + invGains[ i ] = 1.0f / psEncCtrl->Gains[ i ]; + } + + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /**********/ + /* VOICED */ + /**********/ + celt_assert( psEnc->sCmn.ltp_mem_length - psEnc->sCmn.predictLPCOrder >= psEncCtrl->pitchL[ 0 ] + LTP_ORDER / 2 ); + + /* LTP analysis */ + silk_find_LTP_FLP( XXLTP, xXLTP, res_pitch, psEncCtrl->pitchL, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); + + /* Quantize LTP gain parameters */ + silk_quant_LTP_gains_FLP( psEncCtrl->LTPCoef, psEnc->sCmn.indices.LTPIndex, &psEnc->sCmn.indices.PERIndex, + &psEnc->sCmn.sum_log_gain_Q7, &psEncCtrl->LTPredCodGain, XXLTP, xXLTP, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); + + /* Control LTP scaling */ + silk_LTP_scale_ctrl_FLP( psEnc, psEncCtrl, condCoding ); + + /* Create LTP residual */ + silk_LTP_analysis_filter_FLP( LPC_in_pre, x - psEnc->sCmn.predictLPCOrder, psEncCtrl->LTPCoef, + psEncCtrl->pitchL, invGains, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.predictLPCOrder ); + } else { + /************/ + /* UNVOICED */ + /************/ + /* Create signal with prepended subframes, scaled by inverse gains */ + x_ptr = x - psEnc->sCmn.predictLPCOrder; + x_pre_ptr = LPC_in_pre; + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + silk_scale_copy_vector_FLP( x_pre_ptr, x_ptr, invGains[ i ], + psEnc->sCmn.subfr_length + psEnc->sCmn.predictLPCOrder ); + x_pre_ptr += psEnc->sCmn.subfr_length + psEnc->sCmn.predictLPCOrder; + x_ptr += psEnc->sCmn.subfr_length; + } + silk_memset( psEncCtrl->LTPCoef, 0, psEnc->sCmn.nb_subfr * LTP_ORDER * sizeof( silk_float ) ); + psEncCtrl->LTPredCodGain = 0.0f; + psEnc->sCmn.sum_log_gain_Q7 = 0; + } + + /* Limit on total predictive coding gain */ + if( psEnc->sCmn.first_frame_after_reset ) { + minInvGain = 1.0f / MAX_PREDICTION_POWER_GAIN_AFTER_RESET; + } else { + minInvGain = (silk_float)pow( 2, psEncCtrl->LTPredCodGain / 3 ) / MAX_PREDICTION_POWER_GAIN; + minInvGain /= 0.25f + 0.75f * psEncCtrl->coding_quality; + } + + /* LPC_in_pre contains the LTP-filtered input for voiced, and the unfiltered input for unvoiced */ + silk_find_LPC_FLP( &psEnc->sCmn, NLSF_Q15, LPC_in_pre, minInvGain, psEnc->sCmn.arch ); + + /* Quantize LSFs */ + silk_process_NLSFs_FLP( &psEnc->sCmn, psEncCtrl->PredCoef, NLSF_Q15, psEnc->sCmn.prev_NLSFq_Q15 ); + + /* Calculate residual energy using quantized LPC coefficients */ + silk_residual_energy_FLP( psEncCtrl->ResNrg, LPC_in_pre, psEncCtrl->PredCoef, psEncCtrl->Gains, + psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.predictLPCOrder ); + + /* Copy to prediction struct for use in next frame for interpolation */ + silk_memcpy( psEnc->sCmn.prev_NLSFq_Q15, NLSF_Q15, sizeof( psEnc->sCmn.prev_NLSFq_Q15 ) ); +} + diff --git a/port/third_party/opus/silk/float/inner_product_FLP.c b/port/third_party/opus/silk/float/inner_product_FLP.c new file mode 100644 index 00000000..88b160ab --- /dev/null +++ b/port/third_party/opus/silk/float/inner_product_FLP.c @@ -0,0 +1,59 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* inner product of two silk_float arrays, with result as double */ +double silk_inner_product_FLP_c( + const silk_float *data1, + const silk_float *data2, + opus_int dataSize +) +{ + opus_int i; + double result; + + /* 4x unrolled loop */ + result = 0.0; + for( i = 0; i < dataSize - 3; i += 4 ) { + result += data1[ i + 0 ] * (double)data2[ i + 0 ] + + data1[ i + 1 ] * (double)data2[ i + 1 ] + + data1[ i + 2 ] * (double)data2[ i + 2 ] + + data1[ i + 3 ] * (double)data2[ i + 3 ]; + } + + /* add any remaining products */ + for( ; i < dataSize; i++ ) { + result += data1[ i ] * (double)data2[ i ]; + } + + return result; +} diff --git a/port/third_party/opus/silk/float/k2a_FLP.c b/port/third_party/opus/silk/float/k2a_FLP.c new file mode 100644 index 00000000..1448008d --- /dev/null +++ b/port/third_party/opus/silk/float/k2a_FLP.c @@ -0,0 +1,54 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* step up function, converts reflection coefficients to prediction coefficients */ +void silk_k2a_FLP( + silk_float *A, /* O prediction coefficients [order] */ + const silk_float *rc, /* I reflection coefficients [order] */ + opus_int32 order /* I prediction order */ +) +{ + opus_int k, n; + silk_float rck, tmp1, tmp2; + + for( k = 0; k < order; k++ ) { + rck = rc[ k ]; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = A[ n ]; + tmp2 = A[ k - n - 1 ]; + A[ n ] = tmp1 + tmp2 * rck; + A[ k - n - 1 ] = tmp2 + tmp1 * rck; + } + A[ k ] = -rck; + } +} diff --git a/port/third_party/opus/silk/float/main_FLP.h b/port/third_party/opus/silk/float/main_FLP.h new file mode 100644 index 00000000..2e4435cc --- /dev/null +++ b/port/third_party/opus/silk/float/main_FLP.h @@ -0,0 +1,290 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_MAIN_FLP_H +#define SILK_MAIN_FLP_H + +#include "SigProc_FLP.h" +#include "SigProc_FIX.h" +#include "structs_FLP.h" +#include "main.h" +#include "define.h" +#include "debug.h" +#include "entenc.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define silk_encoder_state_Fxx silk_encoder_state_FLP +#define silk_encode_do_VAD_Fxx silk_encode_do_VAD_FLP +#define silk_encode_frame_Fxx silk_encode_frame_FLP + +/*********************/ +/* Encoder Functions */ +/*********************/ + +/* High-pass filter with cutoff frequency adaptation based on pitch lag statistics */ +void silk_HP_variable_cutoff( + silk_encoder_state_Fxx state_Fxx[] /* I/O Encoder states */ +); + +/* Encoder main function */ +void silk_encode_do_VAD_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + opus_int activity /* I Decision of Opus voice activity detector */ +); + +/* Encoder main function */ +opus_int silk_encode_frame_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + opus_int32 *pnBytesOut, /* O Number of payload bytes; */ + ec_enc *psRangeEnc, /* I/O compressor data structure */ + opus_int condCoding, /* I The type of conditional coding to use */ + opus_int maxBits, /* I If > 0: maximum number of output bits */ + opus_int useCBR /* I Flag to force constant-bitrate operation */ +); + +/* Initializes the Silk encoder state */ +opus_int silk_init_encoder( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + int arch /* I Run-tim architecture */ +); + +/* Control the Silk encoder */ +opus_int silk_control_encoder( + silk_encoder_state_FLP *psEnc, /* I/O Pointer to Silk encoder state FLP */ + silk_EncControlStruct *encControl, /* I Control structure */ + const opus_int allow_bw_switch, /* I Flag to allow switching audio bandwidth */ + const opus_int channelNb, /* I Channel number */ + const opus_int force_fs_kHz +); + +/**************************/ +/* Noise shaping analysis */ +/**************************/ +/* Compute noise shaping coefficients and initial gain values */ +void silk_noise_shape_analysis_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float *pitch_res, /* I LPC residual from pitch analysis */ + const silk_float *x /* I Input signal [frame_length + la_shape] */ +); + +/* Autocorrelations for a warped frequency axis */ +void silk_warped_autocorrelation_FLP( + silk_float *corr, /* O Result [order + 1] */ + const silk_float *input, /* I Input data to correlate */ + const silk_float warping, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +); + +/* Calculation of LTP state scaling */ +void silk_LTP_scale_ctrl_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/**********************************************/ +/* Prediction Analysis */ +/**********************************************/ +/* Find pitch lags */ +void silk_find_pitch_lags_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + silk_float res[], /* O Residual */ + const silk_float x[], /* I Speech signal */ + int arch /* I Run-time architecture */ +); + +/* Find LPC and LTP coefficients */ +void silk_find_pred_coefs_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float res_pitch[], /* I Residual from pitch analysis */ + const silk_float x[], /* I Speech signal */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/* LPC analysis */ +void silk_find_LPC_FLP( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 NLSF_Q15[], /* O NLSFs */ + const silk_float x[], /* I Input signal */ + const silk_float minInvGain, /* I Prediction gain from LTP (dB) */ + int arch +); + +/* LTP analysis */ +void silk_find_LTP_FLP( + silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ + silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* O Weight for LTP quantization */ + const silk_float r_ptr[], /* I LPC residual */ + const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I number of subframes */ + int arch +); + +void silk_LTP_analysis_filter_FLP( + silk_float *LTP_res, /* O LTP res MAX_NB_SUBFR*(pre_lgth+subfr_lngth) */ + const silk_float *x, /* I Input signal, with preceding samples */ + const silk_float B[ LTP_ORDER * MAX_NB_SUBFR ], /* I LTP coefficients for each subframe */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const silk_float invGains[ MAX_NB_SUBFR ], /* I Inverse quantization gains */ + const opus_int subfr_length, /* I Length of each subframe */ + const opus_int nb_subfr, /* I number of subframes */ + const opus_int pre_length /* I Preceding samples for each subframe */ +); + +/* Calculates residual energies of input subframes where all subframes have LPC_order */ +/* of preceding samples */ +void silk_residual_energy_FLP( + silk_float nrgs[ MAX_NB_SUBFR ], /* O Residual energy per subframe */ + const silk_float x[], /* I Input signal */ + silk_float a[ 2 ][ MAX_LPC_ORDER ], /* I AR coefs for each frame half */ + const silk_float gains[], /* I Quantization gains */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I number of subframes */ + const opus_int LPC_order /* I LPC order */ +); + +/* 16th order LPC analysis filter */ +void silk_LPC_analysis_filter_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length, /* I Length of input signal */ + const opus_int Order /* I LPC order */ +); + +/* LTP tap quantizer */ +void silk_quant_LTP_gains_FLP( + silk_float B[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ + opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook index */ + opus_int8 *periodicity_index, /* O Periodicity index */ + opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ + silk_float *pred_gain_dB, /* O LTP prediction gain */ + const silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* I Correlation matrix */ + const silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* I Correlation vector */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +); + +/* Residual energy: nrg = wxx - 2 * wXx * c + c' * wXX * c */ +silk_float silk_residual_energy_covar_FLP( /* O Weighted residual energy */ + const silk_float *c, /* I Filter coefficients */ + silk_float *wXX, /* I/O Weighted correlation matrix, reg. out */ + const silk_float *wXx, /* I Weighted correlation vector */ + const silk_float wxx, /* I Weighted correlation value */ + const opus_int D /* I Dimension */ +); + +/* Processing of gains */ +void silk_process_gains_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/******************/ +/* Linear Algebra */ +/******************/ +/* Calculates correlation matrix X'*X */ +void silk_corrMatrix_FLP( + const silk_float *x, /* I x vector [ L+order-1 ] used to create X */ + const opus_int L, /* I Length of vectors */ + const opus_int Order, /* I Max lag for correlation */ + silk_float *XX, /* O X'*X correlation matrix [order x order] */ + int arch +); + +/* Calculates correlation vector X'*t */ +void silk_corrVector_FLP( + const silk_float *x, /* I x vector [L+order-1] used to create X */ + const silk_float *t, /* I Target vector [L] */ + const opus_int L, /* I Length of vecors */ + const opus_int Order, /* I Max lag for correlation */ + silk_float *Xt, /* O X'*t correlation vector [order] */ + int arch +); + +/* Apply sine window to signal vector. */ +/* Window types: */ +/* 1 -> sine window from 0 to pi/2 */ +/* 2 -> sine window from pi/2 to pi */ +void silk_apply_sine_window_FLP( + silk_float px_win[], /* O Pointer to windowed signal */ + const silk_float px[], /* I Pointer to input signal */ + const opus_int win_type, /* I Selects a window type */ + const opus_int length /* I Window length, multiple of 4 */ +); + +/* Wrapper functions. Call flp / fix code */ + +/* Convert AR filter coefficients to NLSF parameters */ +void silk_A2NLSF_FLP( + opus_int16 *NLSF_Q15, /* O NLSF vector [ LPC_order ] */ + const silk_float *pAR, /* I LPC coefficients [ LPC_order ] */ + const opus_int LPC_order /* I LPC order */ +); + +/* Convert NLSF parameters to AR prediction filter coefficients */ +void silk_NLSF2A_FLP( + silk_float *pAR, /* O LPC coefficients [ LPC_order ] */ + const opus_int16 *NLSF_Q15, /* I NLSF vector [ LPC_order ] */ + const opus_int LPC_order, /* I LPC order */ + int arch /* I Run-time architecture */ +); + +/* Limit, stabilize, and quantize NLSFs */ +void silk_process_NLSFs_FLP( + silk_encoder_state *psEncC, /* I/O Encoder state */ + silk_float PredCoef[ 2 ][ MAX_LPC_ORDER ], /* O Prediction coefficients */ + opus_int16 NLSF_Q15[ MAX_LPC_ORDER ], /* I/O Normalized LSFs (quant out) (0 - (2^15-1)) */ + const opus_int16 prev_NLSF_Q15[ MAX_LPC_ORDER ] /* I Previous Normalized LSFs (0 - (2^15-1)) */ +); + +/* Floating-point Silk NSQ wrapper */ +void silk_NSQ_wrapper_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + SideInfoIndices *psIndices, /* I/O Quantization indices */ + silk_nsq_state *psNSQ, /* I/O Noise Shaping Quantzation state */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const silk_float x[] /* I Prefiltered input signal */ +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/silk/float/noise_shape_analysis_FLP.c b/port/third_party/opus/silk/float/noise_shape_analysis_FLP.c new file mode 100644 index 00000000..0b5ea952 --- /dev/null +++ b/port/third_party/opus/silk/float/noise_shape_analysis_FLP.c @@ -0,0 +1,350 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" +#include "tuning_parameters.h" + +/* Compute gain to make warped filter coefficients have a zero mean log frequency response on a */ +/* non-warped frequency scale. (So that it can be implemented with a minimum-phase monic filter.) */ +/* Note: A monic filter is one with the first coefficient equal to 1.0. In Silk we omit the first */ +/* coefficient in an array of coefficients, for monic filters. */ +static OPUS_INLINE silk_float warped_gain( + const silk_float *coefs, + silk_float lambda, + opus_int order +) { + opus_int i; + silk_float gain; + + lambda = -lambda; + gain = coefs[ order - 1 ]; + for( i = order - 2; i >= 0; i-- ) { + gain = lambda * gain + coefs[ i ]; + } + return (silk_float)( 1.0f / ( 1.0f - lambda * gain ) ); +} + +/* Convert warped filter coefficients to monic pseudo-warped coefficients and limit maximum */ +/* amplitude of monic warped coefficients by using bandwidth expansion on the true coefficients */ +static OPUS_INLINE void warped_true2monic_coefs( + silk_float *coefs, + silk_float lambda, + silk_float limit, + opus_int order +) { + opus_int i, iter, ind = 0; + silk_float tmp, maxabs, chirp, gain; + + /* Convert to monic coefficients */ + for( i = order - 1; i > 0; i-- ) { + coefs[ i - 1 ] -= lambda * coefs[ i ]; + } + gain = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs[ 0 ] ); + for( i = 0; i < order; i++ ) { + coefs[ i ] *= gain; + } + + /* Limit */ + for( iter = 0; iter < 10; iter++ ) { + /* Find maximum absolute value */ + maxabs = -1.0f; + for( i = 0; i < order; i++ ) { + tmp = silk_abs_float( coefs[ i ] ); + if( tmp > maxabs ) { + maxabs = tmp; + ind = i; + } + } + if( maxabs <= limit ) { + /* Coefficients are within range - done */ + return; + } + + /* Convert back to true warped coefficients */ + for( i = 1; i < order; i++ ) { + coefs[ i - 1 ] += lambda * coefs[ i ]; + } + gain = 1.0f / gain; + for( i = 0; i < order; i++ ) { + coefs[ i ] *= gain; + } + + /* Apply bandwidth expansion */ + chirp = 0.99f - ( 0.8f + 0.1f * iter ) * ( maxabs - limit ) / ( maxabs * ( ind + 1 ) ); + silk_bwexpander_FLP( coefs, order, chirp ); + + /* Convert to monic warped coefficients */ + for( i = order - 1; i > 0; i-- ) { + coefs[ i - 1 ] -= lambda * coefs[ i ]; + } + gain = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs[ 0 ] ); + for( i = 0; i < order; i++ ) { + coefs[ i ] *= gain; + } + } + silk_assert( 0 ); +} + +static OPUS_INLINE void limit_coefs( + silk_float *coefs, + silk_float limit, + opus_int order +) { + opus_int i, iter, ind = 0; + silk_float tmp, maxabs, chirp; + + for( iter = 0; iter < 10; iter++ ) { + /* Find maximum absolute value */ + maxabs = -1.0f; + for( i = 0; i < order; i++ ) { + tmp = silk_abs_float( coefs[ i ] ); + if( tmp > maxabs ) { + maxabs = tmp; + ind = i; + } + } + if( maxabs <= limit ) { + /* Coefficients are within range - done */ + return; + } + + /* Apply bandwidth expansion */ + chirp = 0.99f - ( 0.8f + 0.1f * iter ) * ( maxabs - limit ) / ( maxabs * ( ind + 1 ) ); + silk_bwexpander_FLP( coefs, order, chirp ); + } + silk_assert( 0 ); +} + +/* Compute noise shaping coefficients and initial gain values */ +void silk_noise_shape_analysis_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float *pitch_res, /* I LPC residual from pitch analysis */ + const silk_float *x /* I Input signal [frame_length + la_shape] */ +) +{ + silk_shape_state_FLP *psShapeSt = &psEnc->sShape; + opus_int k, nSamples, nSegs; + silk_float SNR_adj_dB, HarmShapeGain, Tilt; + silk_float nrg, log_energy, log_energy_prev, energy_variation; + silk_float BWExp, gain_mult, gain_add, strength, b, warping; + silk_float x_windowed[ SHAPE_LPC_WIN_MAX ]; + silk_float auto_corr[ MAX_SHAPE_LPC_ORDER + 1 ]; + silk_float rc[ MAX_SHAPE_LPC_ORDER + 1 ]; + const silk_float *x_ptr, *pitch_res_ptr; + + /* Point to start of first LPC analysis block */ + x_ptr = x - psEnc->sCmn.la_shape; + + /****************/ + /* GAIN CONTROL */ + /****************/ + SNR_adj_dB = psEnc->sCmn.SNR_dB_Q7 * ( 1 / 128.0f ); + + /* Input quality is the average of the quality in the lowest two VAD bands */ + psEncCtrl->input_quality = 0.5f * ( psEnc->sCmn.input_quality_bands_Q15[ 0 ] + psEnc->sCmn.input_quality_bands_Q15[ 1 ] ) * ( 1.0f / 32768.0f ); + + /* Coding quality level, between 0.0 and 1.0 */ + psEncCtrl->coding_quality = silk_sigmoid( 0.25f * ( SNR_adj_dB - 20.0f ) ); + + if( psEnc->sCmn.useCBR == 0 ) { + /* Reduce coding SNR during low speech activity */ + b = 1.0f - psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ); + SNR_adj_dB -= BG_SNR_DECR_dB * psEncCtrl->coding_quality * ( 0.5f + 0.5f * psEncCtrl->input_quality ) * b * b; + } + + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Reduce gains for periodic signals */ + SNR_adj_dB += HARM_SNR_INCR_dB * psEnc->LTPCorr; + } else { + /* For unvoiced signals and low-quality input, adjust the quality slower than SNR_dB setting */ + SNR_adj_dB += ( -0.4f * psEnc->sCmn.SNR_dB_Q7 * ( 1 / 128.0f ) + 6.0f ) * ( 1.0f - psEncCtrl->input_quality ); + } + + /*************************/ + /* SPARSENESS PROCESSING */ + /*************************/ + /* Set quantizer offset */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Initially set to 0; may be overruled in process_gains(..) */ + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + /* Sparseness measure, based on relative fluctuations of energy per 2 milliseconds */ + nSamples = 2 * psEnc->sCmn.fs_kHz; + energy_variation = 0.0f; + log_energy_prev = 0.0f; + pitch_res_ptr = pitch_res; + nSegs = silk_SMULBB( SUB_FRAME_LENGTH_MS, psEnc->sCmn.nb_subfr ) / 2; + for( k = 0; k < nSegs; k++ ) { + nrg = ( silk_float )nSamples + ( silk_float )silk_energy_FLP( pitch_res_ptr, nSamples ); + log_energy = silk_log2( nrg ); + if( k > 0 ) { + energy_variation += silk_abs_float( log_energy - log_energy_prev ); + } + log_energy_prev = log_energy; + pitch_res_ptr += nSamples; + } + + /* Set quantization offset depending on sparseness measure */ + if( energy_variation > ENERGY_VARIATION_THRESHOLD_QNT_OFFSET * (nSegs-1) ) { + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + psEnc->sCmn.indices.quantOffsetType = 1; + } + } + + /*******************************/ + /* Control bandwidth expansion */ + /*******************************/ + /* More BWE for signals with high prediction gain */ + strength = FIND_PITCH_WHITE_NOISE_FRACTION * psEncCtrl->predGain; /* between 0.0 and 1.0 */ + BWExp = BANDWIDTH_EXPANSION / ( 1.0f + strength * strength ); + + /* Slightly more warping in analysis will move quantization noise up in frequency, where it's better masked */ + warping = (silk_float)psEnc->sCmn.warping_Q16 / 65536.0f + 0.01f * psEncCtrl->coding_quality; + + /********************************************/ + /* Compute noise shaping AR coefs and gains */ + /********************************************/ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + /* Apply window: sine slope followed by flat part followed by cosine slope */ + opus_int shift, slope_part, flat_part; + flat_part = psEnc->sCmn.fs_kHz * 3; + slope_part = ( psEnc->sCmn.shapeWinLength - flat_part ) / 2; + + silk_apply_sine_window_FLP( x_windowed, x_ptr, 1, slope_part ); + shift = slope_part; + silk_memcpy( x_windowed + shift, x_ptr + shift, flat_part * sizeof(silk_float) ); + shift += flat_part; + silk_apply_sine_window_FLP( x_windowed + shift, x_ptr + shift, 2, slope_part ); + + /* Update pointer: next LPC analysis block */ + x_ptr += psEnc->sCmn.subfr_length; + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Calculate warped auto correlation */ + silk_warped_autocorrelation_FLP( auto_corr, x_windowed, warping, + psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder ); + } else { + /* Calculate regular auto correlation */ + silk_autocorrelation_FLP( auto_corr, x_windowed, psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder + 1, psEnc->sCmn.arch ); + } + + /* Add white noise, as a fraction of energy */ + auto_corr[ 0 ] += auto_corr[ 0 ] * SHAPE_WHITE_NOISE_FRACTION + 1.0f; + + /* Convert correlations to prediction coefficients, and compute residual energy */ + nrg = silk_schur_FLP( rc, auto_corr, psEnc->sCmn.shapingLPCOrder ); + silk_k2a_FLP( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], rc, psEnc->sCmn.shapingLPCOrder ); + psEncCtrl->Gains[ k ] = ( silk_float )sqrt( nrg ); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Adjust gain for warping */ + psEncCtrl->Gains[ k ] *= warped_gain( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], warping, psEnc->sCmn.shapingLPCOrder ); + } + + /* Bandwidth expansion for synthesis filter shaping */ + silk_bwexpander_FLP( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], psEnc->sCmn.shapingLPCOrder, BWExp ); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Convert to monic warped prediction coefficients and limit absolute values */ + warped_true2monic_coefs( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], warping, 3.999f, psEnc->sCmn.shapingLPCOrder ); + } else { + /* Limit absolute values */ + limit_coefs( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], 3.999f, psEnc->sCmn.shapingLPCOrder ); + } + } + + /*****************/ + /* Gain tweaking */ + /*****************/ + /* Increase gains during low speech activity */ + gain_mult = (silk_float)pow( 2.0f, -0.16f * SNR_adj_dB ); + gain_add = (silk_float)pow( 2.0f, 0.16f * MIN_QGAIN_DB ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains[ k ] *= gain_mult; + psEncCtrl->Gains[ k ] += gain_add; + } + + /************************************************/ + /* Control low-frequency shaping and noise tilt */ + /************************************************/ + /* Less low frequency shaping for noisy inputs */ + strength = LOW_FREQ_SHAPING * ( 1.0f + LOW_QUALITY_LOW_FREQ_SHAPING_DECR * ( psEnc->sCmn.input_quality_bands_Q15[ 0 ] * ( 1.0f / 32768.0f ) - 1.0f ) ); + strength *= psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ); + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Reduce low frequencies quantization noise for periodic signals, depending on pitch lag */ + /*f = 400; freqz([1, -0.98 + 2e-4 * f], [1, -0.97 + 7e-4 * f], 2^12, Fs); axis([0, 1000, -10, 1])*/ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + b = 0.2f / psEnc->sCmn.fs_kHz + 3.0f / psEncCtrl->pitchL[ k ]; + psEncCtrl->LF_MA_shp[ k ] = -1.0f + b; + psEncCtrl->LF_AR_shp[ k ] = 1.0f - b - b * strength; + } + Tilt = - HP_NOISE_COEF - + (1 - HP_NOISE_COEF) * HARM_HP_NOISE_COEF * psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ); + } else { + b = 1.3f / psEnc->sCmn.fs_kHz; + psEncCtrl->LF_MA_shp[ 0 ] = -1.0f + b; + psEncCtrl->LF_AR_shp[ 0 ] = 1.0f - b - b * strength * 0.6f; + for( k = 1; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->LF_MA_shp[ k ] = psEncCtrl->LF_MA_shp[ 0 ]; + psEncCtrl->LF_AR_shp[ k ] = psEncCtrl->LF_AR_shp[ 0 ]; + } + Tilt = -HP_NOISE_COEF; + } + + /****************************/ + /* HARMONIC SHAPING CONTROL */ + /****************************/ + if( USE_HARM_SHAPING && psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Harmonic noise shaping */ + HarmShapeGain = HARMONIC_SHAPING; + + /* More harmonic noise shaping for high bitrates or noisy input */ + HarmShapeGain += HIGH_RATE_OR_LOW_QUALITY_HARMONIC_SHAPING * + ( 1.0f - ( 1.0f - psEncCtrl->coding_quality ) * psEncCtrl->input_quality ); + + /* Less harmonic noise shaping for less periodic signals */ + HarmShapeGain *= ( silk_float )sqrt( psEnc->LTPCorr ); + } else { + HarmShapeGain = 0.0f; + } + + /*************************/ + /* Smooth over subframes */ + /*************************/ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psShapeSt->HarmShapeGain_smth += SUBFR_SMTH_COEF * ( HarmShapeGain - psShapeSt->HarmShapeGain_smth ); + psEncCtrl->HarmShapeGain[ k ] = psShapeSt->HarmShapeGain_smth; + psShapeSt->Tilt_smth += SUBFR_SMTH_COEF * ( Tilt - psShapeSt->Tilt_smth ); + psEncCtrl->Tilt[ k ] = psShapeSt->Tilt_smth; + } +} diff --git a/port/third_party/opus/silk/float/pitch_analysis_core_FLP.c b/port/third_party/opus/silk/float/pitch_analysis_core_FLP.c new file mode 100644 index 00000000..0530a883 --- /dev/null +++ b/port/third_party/opus/silk/float/pitch_analysis_core_FLP.c @@ -0,0 +1,630 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/***************************************************************************** +* Pitch analyser function +******************************************************************************/ +#include "SigProc_FLP.h" +#include "SigProc_FIX.h" +#include "pitch_est_defines.h" +#include "pitch.h" + +#define SCRATCH_SIZE 22 + +/************************************************************/ +/* Internally used functions */ +/************************************************************/ +static void silk_P_Ana_calc_corr_st3( + silk_float cross_corr_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ], /* O 3 DIM correlation array */ + const silk_float frame[], /* I vector to correlate */ + opus_int start_lag, /* I start lag */ + opus_int sf_length, /* I sub frame length */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ +); + +static void silk_P_Ana_calc_energy_st3( + silk_float energies_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ], /* O 3 DIM correlation array */ + const silk_float frame[], /* I vector to correlate */ + opus_int start_lag, /* I start lag */ + opus_int sf_length, /* I sub frame length */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity /* I Complexity setting */ +); + +/************************************************************/ +/* CORE PITCH ANALYSIS FUNCTION */ +/************************************************************/ +opus_int silk_pitch_analysis_core_FLP( /* O Voicing estimate: 0 voiced, 1 unvoiced */ + const silk_float *frame, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ + opus_int *pitch_out, /* O Pitch lag values [nb_subfr] */ + opus_int16 *lagIndex, /* O Lag Index */ + opus_int8 *contourIndex, /* O Pitch contour Index */ + silk_float *LTPCorr, /* I/O Normalized correlation; input: value from previous frame */ + opus_int prevLag, /* I Last lag of previous frame; set to zero is unvoiced */ + const silk_float search_thres1, /* I First stage threshold for lag candidates 0 - 1 */ + const silk_float search_thres2, /* I Final threshold for lag candidates 0 - 1 */ + const opus_int Fs_kHz, /* I sample frequency (kHz) */ + const opus_int complexity, /* I Complexity setting, 0-2, where 2 is highest */ + const opus_int nb_subfr, /* I Number of 5 ms subframes */ + int arch /* I Run-time architecture */ +) +{ + opus_int i, k, d, j; + silk_float frame_8kHz[ PE_MAX_FRAME_LENGTH_MS * 8 ]; + silk_float frame_4kHz[ PE_MAX_FRAME_LENGTH_MS * 4 ]; + opus_int16 frame_8_FIX[ PE_MAX_FRAME_LENGTH_MS * 8 ]; + opus_int16 frame_4_FIX[ PE_MAX_FRAME_LENGTH_MS * 4 ]; + opus_int32 filt_state[ 6 ]; + silk_float threshold, contour_bias; + silk_float C[ PE_MAX_NB_SUBFR][ (PE_MAX_LAG >> 1) + 5 ]; + opus_val32 xcorr[ PE_MAX_LAG_MS * 4 - PE_MIN_LAG_MS * 4 + 1 ]; + silk_float CC[ PE_NB_CBKS_STAGE2_EXT ]; + const silk_float *target_ptr, *basis_ptr; + double cross_corr, normalizer, energy, energy_tmp; + opus_int d_srch[ PE_D_SRCH_LENGTH ]; + opus_int16 d_comp[ (PE_MAX_LAG >> 1) + 5 ]; + opus_int length_d_srch, length_d_comp; + silk_float Cmax, CCmax, CCmax_b, CCmax_new_b, CCmax_new; + opus_int CBimax, CBimax_new, lag, start_lag, end_lag, lag_new; + opus_int cbk_size; + silk_float lag_log2, prevLag_log2, delta_lag_log2_sqr; + silk_float energies_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ]; + silk_float cross_corr_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ]; + opus_int lag_counter; + opus_int frame_length, frame_length_8kHz, frame_length_4kHz; + opus_int sf_length, sf_length_8kHz, sf_length_4kHz; + opus_int min_lag, min_lag_8kHz, min_lag_4kHz; + opus_int max_lag, max_lag_8kHz, max_lag_4kHz; + opus_int nb_cbk_search; + const opus_int8 *Lag_CB_ptr; + + /* Check for valid sampling frequency */ + celt_assert( Fs_kHz == 8 || Fs_kHz == 12 || Fs_kHz == 16 ); + + /* Check for valid complexity setting */ + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + silk_assert( search_thres1 >= 0.0f && search_thres1 <= 1.0f ); + silk_assert( search_thres2 >= 0.0f && search_thres2 <= 1.0f ); + + /* Set up frame lengths max / min lag for the sampling frequency */ + frame_length = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * Fs_kHz; + frame_length_4kHz = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * 4; + frame_length_8kHz = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * 8; + sf_length = PE_SUBFR_LENGTH_MS * Fs_kHz; + sf_length_4kHz = PE_SUBFR_LENGTH_MS * 4; + sf_length_8kHz = PE_SUBFR_LENGTH_MS * 8; + min_lag = PE_MIN_LAG_MS * Fs_kHz; + min_lag_4kHz = PE_MIN_LAG_MS * 4; + min_lag_8kHz = PE_MIN_LAG_MS * 8; + max_lag = PE_MAX_LAG_MS * Fs_kHz - 1; + max_lag_4kHz = PE_MAX_LAG_MS * 4; + max_lag_8kHz = PE_MAX_LAG_MS * 8 - 1; + + /* Resample from input sampled at Fs_kHz to 8 kHz */ + if( Fs_kHz == 16 ) { + /* Resample to 16 -> 8 khz */ + opus_int16 frame_16_FIX[ 16 * PE_MAX_FRAME_LENGTH_MS ]; + silk_float2short_array( frame_16_FIX, frame, frame_length ); + silk_memset( filt_state, 0, 2 * sizeof( opus_int32 ) ); + silk_resampler_down2( filt_state, frame_8_FIX, frame_16_FIX, frame_length ); + silk_short2float_array( frame_8kHz, frame_8_FIX, frame_length_8kHz ); + } else if( Fs_kHz == 12 ) { + /* Resample to 12 -> 8 khz */ + opus_int16 frame_12_FIX[ 12 * PE_MAX_FRAME_LENGTH_MS ]; + silk_float2short_array( frame_12_FIX, frame, frame_length ); + silk_memset( filt_state, 0, 6 * sizeof( opus_int32 ) ); + silk_resampler_down2_3( filt_state, frame_8_FIX, frame_12_FIX, frame_length ); + silk_short2float_array( frame_8kHz, frame_8_FIX, frame_length_8kHz ); + } else { + celt_assert( Fs_kHz == 8 ); + silk_float2short_array( frame_8_FIX, frame, frame_length_8kHz ); + } + + /* Decimate again to 4 kHz */ + silk_memset( filt_state, 0, 2 * sizeof( opus_int32 ) ); + silk_resampler_down2( filt_state, frame_4_FIX, frame_8_FIX, frame_length_8kHz ); + silk_short2float_array( frame_4kHz, frame_4_FIX, frame_length_4kHz ); + + /* Low-pass filter */ + for( i = frame_length_4kHz - 1; i > 0; i-- ) { + frame_4kHz[ i ] = silk_ADD_SAT16( frame_4kHz[ i ], frame_4kHz[ i - 1 ] ); + } + + /****************************************************************************** + * FIRST STAGE, operating in 4 khz + ******************************************************************************/ + silk_memset(C, 0, sizeof(silk_float) * nb_subfr * ((PE_MAX_LAG >> 1) + 5)); + target_ptr = &frame_4kHz[ silk_LSHIFT( sf_length_4kHz, 2 ) ]; + for( k = 0; k < nb_subfr >> 1; k++ ) { + /* Check that we are within range of the array */ + celt_assert( target_ptr >= frame_4kHz ); + celt_assert( target_ptr + sf_length_8kHz <= frame_4kHz + frame_length_4kHz ); + + basis_ptr = target_ptr - min_lag_4kHz; + + /* Check that we are within range of the array */ + celt_assert( basis_ptr >= frame_4kHz ); + celt_assert( basis_ptr + sf_length_8kHz <= frame_4kHz + frame_length_4kHz ); + + celt_pitch_xcorr( target_ptr, target_ptr-max_lag_4kHz, xcorr, sf_length_8kHz, max_lag_4kHz - min_lag_4kHz + 1, arch ); + + /* Calculate first vector products before loop */ + cross_corr = xcorr[ max_lag_4kHz - min_lag_4kHz ]; + normalizer = silk_energy_FLP( target_ptr, sf_length_8kHz ) + + silk_energy_FLP( basis_ptr, sf_length_8kHz ) + + sf_length_8kHz * 4000.0f; + + C[ 0 ][ min_lag_4kHz ] += (silk_float)( 2 * cross_corr / normalizer ); + + /* From now on normalizer is computed recursively */ + for( d = min_lag_4kHz + 1; d <= max_lag_4kHz; d++ ) { + basis_ptr--; + + /* Check that we are within range of the array */ + silk_assert( basis_ptr >= frame_4kHz ); + silk_assert( basis_ptr + sf_length_8kHz <= frame_4kHz + frame_length_4kHz ); + + cross_corr = xcorr[ max_lag_4kHz - d ]; + + /* Add contribution of new sample and remove contribution from oldest sample */ + normalizer += + basis_ptr[ 0 ] * (double)basis_ptr[ 0 ] - + basis_ptr[ sf_length_8kHz ] * (double)basis_ptr[ sf_length_8kHz ]; + C[ 0 ][ d ] += (silk_float)( 2 * cross_corr / normalizer ); + } + /* Update target pointer */ + target_ptr += sf_length_8kHz; + } + + /* Apply short-lag bias */ + for( i = max_lag_4kHz; i >= min_lag_4kHz; i-- ) { + C[ 0 ][ i ] -= C[ 0 ][ i ] * i / 4096.0f; + } + + /* Sort */ + length_d_srch = 4 + 2 * complexity; + celt_assert( 3 * length_d_srch <= PE_D_SRCH_LENGTH ); + silk_insertion_sort_decreasing_FLP( &C[ 0 ][ min_lag_4kHz ], d_srch, max_lag_4kHz - min_lag_4kHz + 1, length_d_srch ); + + /* Escape if correlation is very low already here */ + Cmax = C[ 0 ][ min_lag_4kHz ]; + if( Cmax < 0.2f ) { + silk_memset( pitch_out, 0, nb_subfr * sizeof( opus_int ) ); + *LTPCorr = 0.0f; + *lagIndex = 0; + *contourIndex = 0; + return 1; + } + + threshold = search_thres1 * Cmax; + for( i = 0; i < length_d_srch; i++ ) { + /* Convert to 8 kHz indices for the sorted correlation that exceeds the threshold */ + if( C[ 0 ][ min_lag_4kHz + i ] > threshold ) { + d_srch[ i ] = silk_LSHIFT( d_srch[ i ] + min_lag_4kHz, 1 ); + } else { + length_d_srch = i; + break; + } + } + celt_assert( length_d_srch > 0 ); + + for( i = min_lag_8kHz - 5; i < max_lag_8kHz + 5; i++ ) { + d_comp[ i ] = 0; + } + for( i = 0; i < length_d_srch; i++ ) { + d_comp[ d_srch[ i ] ] = 1; + } + + /* Convolution */ + for( i = max_lag_8kHz + 3; i >= min_lag_8kHz; i-- ) { + d_comp[ i ] += d_comp[ i - 1 ] + d_comp[ i - 2 ]; + } + + length_d_srch = 0; + for( i = min_lag_8kHz; i < max_lag_8kHz + 1; i++ ) { + if( d_comp[ i + 1 ] > 0 ) { + d_srch[ length_d_srch ] = i; + length_d_srch++; + } + } + + /* Convolution */ + for( i = max_lag_8kHz + 3; i >= min_lag_8kHz; i-- ) { + d_comp[ i ] += d_comp[ i - 1 ] + d_comp[ i - 2 ] + d_comp[ i - 3 ]; + } + + length_d_comp = 0; + for( i = min_lag_8kHz; i < max_lag_8kHz + 4; i++ ) { + if( d_comp[ i ] > 0 ) { + d_comp[ length_d_comp ] = (opus_int16)( i - 2 ); + length_d_comp++; + } + } + + /********************************************************************************** + ** SECOND STAGE, operating at 8 kHz, on lag sections with high correlation + *************************************************************************************/ + /********************************************************************************* + * Find energy of each subframe projected onto its history, for a range of delays + *********************************************************************************/ + silk_memset( C, 0, PE_MAX_NB_SUBFR*((PE_MAX_LAG >> 1) + 5) * sizeof(silk_float)); + + if( Fs_kHz == 8 ) { + target_ptr = &frame[ PE_LTP_MEM_LENGTH_MS * 8 ]; + } else { + target_ptr = &frame_8kHz[ PE_LTP_MEM_LENGTH_MS * 8 ]; + } + for( k = 0; k < nb_subfr; k++ ) { + energy_tmp = silk_energy_FLP( target_ptr, sf_length_8kHz ) + 1.0; + for( j = 0; j < length_d_comp; j++ ) { + d = d_comp[ j ]; + basis_ptr = target_ptr - d; + cross_corr = silk_inner_product_FLP( basis_ptr, target_ptr, sf_length_8kHz, arch ); + if( cross_corr > 0.0f ) { + energy = silk_energy_FLP( basis_ptr, sf_length_8kHz ); + C[ k ][ d ] = (silk_float)( 2 * cross_corr / ( energy + energy_tmp ) ); + } else { + C[ k ][ d ] = 0.0f; + } + } + target_ptr += sf_length_8kHz; + } + + /* search over lag range and lags codebook */ + /* scale factor for lag codebook, as a function of center lag */ + + CCmax = 0.0f; /* This value doesn't matter */ + CCmax_b = -1000.0f; + + CBimax = 0; /* To avoid returning undefined lag values */ + lag = -1; /* To check if lag with strong enough correlation has been found */ + + if( prevLag > 0 ) { + if( Fs_kHz == 12 ) { + prevLag = silk_LSHIFT( prevLag, 1 ) / 3; + } else if( Fs_kHz == 16 ) { + prevLag = silk_RSHIFT( prevLag, 1 ); + } + prevLag_log2 = silk_log2( (silk_float)prevLag ); + } else { + prevLag_log2 = 0; + } + + /* Set up stage 2 codebook based on number of subframes */ + if( nb_subfr == PE_MAX_NB_SUBFR ) { + cbk_size = PE_NB_CBKS_STAGE2_EXT; + Lag_CB_ptr = &silk_CB_lags_stage2[ 0 ][ 0 ]; + if( Fs_kHz == 8 && complexity > SILK_PE_MIN_COMPLEX ) { + /* If input is 8 khz use a larger codebook here because it is last stage */ + nb_cbk_search = PE_NB_CBKS_STAGE2_EXT; + } else { + nb_cbk_search = PE_NB_CBKS_STAGE2; + } + } else { + cbk_size = PE_NB_CBKS_STAGE2_10MS; + Lag_CB_ptr = &silk_CB_lags_stage2_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE2_10MS; + } + + for( k = 0; k < length_d_srch; k++ ) { + d = d_srch[ k ]; + for( j = 0; j < nb_cbk_search; j++ ) { + CC[j] = 0.0f; + for( i = 0; i < nb_subfr; i++ ) { + /* Try all codebooks */ + CC[ j ] += C[ i ][ d + matrix_ptr( Lag_CB_ptr, i, j, cbk_size )]; + } + } + /* Find best codebook */ + CCmax_new = -1000.0f; + CBimax_new = 0; + for( i = 0; i < nb_cbk_search; i++ ) { + if( CC[ i ] > CCmax_new ) { + CCmax_new = CC[ i ]; + CBimax_new = i; + } + } + + /* Bias towards shorter lags */ + lag_log2 = silk_log2( (silk_float)d ); + CCmax_new_b = CCmax_new - PE_SHORTLAG_BIAS * nb_subfr * lag_log2; + + /* Bias towards previous lag */ + if( prevLag > 0 ) { + delta_lag_log2_sqr = lag_log2 - prevLag_log2; + delta_lag_log2_sqr *= delta_lag_log2_sqr; + CCmax_new_b -= PE_PREVLAG_BIAS * nb_subfr * (*LTPCorr) * delta_lag_log2_sqr / ( delta_lag_log2_sqr + 0.5f ); + } + + if( CCmax_new_b > CCmax_b && /* Find maximum biased correlation */ + CCmax_new > nb_subfr * search_thres2 /* Correlation needs to be high enough to be voiced */ + ) { + CCmax_b = CCmax_new_b; + CCmax = CCmax_new; + lag = d; + CBimax = CBimax_new; + } + } + + if( lag == -1 ) { + /* No suitable candidate found */ + silk_memset( pitch_out, 0, PE_MAX_NB_SUBFR * sizeof(opus_int) ); + *LTPCorr = 0.0f; + *lagIndex = 0; + *contourIndex = 0; + return 1; + } + + /* Output normalized correlation */ + *LTPCorr = (silk_float)( CCmax / nb_subfr ); + silk_assert( *LTPCorr >= 0.0f ); + + if( Fs_kHz > 8 ) { + /* Search in original signal */ + + /* Compensate for decimation */ + silk_assert( lag == silk_SAT16( lag ) ); + if( Fs_kHz == 12 ) { + lag = silk_RSHIFT_ROUND( silk_SMULBB( lag, 3 ), 1 ); + } else { /* Fs_kHz == 16 */ + lag = silk_LSHIFT( lag, 1 ); + } + + lag = silk_LIMIT_int( lag, min_lag, max_lag ); + start_lag = silk_max_int( lag - 2, min_lag ); + end_lag = silk_min_int( lag + 2, max_lag ); + lag_new = lag; /* to avoid undefined lag */ + CBimax = 0; /* to avoid undefined lag */ + + CCmax = -1000.0f; + + /* Calculate the correlations and energies needed in stage 3 */ + silk_P_Ana_calc_corr_st3( cross_corr_st3, frame, start_lag, sf_length, nb_subfr, complexity, arch ); + silk_P_Ana_calc_energy_st3( energies_st3, frame, start_lag, sf_length, nb_subfr, complexity ); + + lag_counter = 0; + silk_assert( lag == silk_SAT16( lag ) ); + contour_bias = PE_FLATCONTOUR_BIAS / lag; + + /* Set up cbk parameters according to complexity setting and frame length */ + if( nb_subfr == PE_MAX_NB_SUBFR ) { + nb_cbk_search = (opus_int)silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + } else { + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + } + + target_ptr = &frame[ PE_LTP_MEM_LENGTH_MS * Fs_kHz ]; + energy_tmp = silk_energy_FLP( target_ptr, nb_subfr * sf_length ) + 1.0; + for( d = start_lag; d <= end_lag; d++ ) { + for( j = 0; j < nb_cbk_search; j++ ) { + cross_corr = 0.0; + energy = energy_tmp; + for( k = 0; k < nb_subfr; k++ ) { + cross_corr += cross_corr_st3[ k ][ j ][ lag_counter ]; + energy += energies_st3[ k ][ j ][ lag_counter ]; + } + if( cross_corr > 0.0 ) { + CCmax_new = (silk_float)( 2 * cross_corr / energy ); + /* Reduce depending on flatness of contour */ + CCmax_new *= 1.0f - contour_bias * j; + } else { + CCmax_new = 0.0f; + } + + if( CCmax_new > CCmax && ( d + (opus_int)silk_CB_lags_stage3[ 0 ][ j ] ) <= max_lag ) { + CCmax = CCmax_new; + lag_new = d; + CBimax = j; + } + } + lag_counter++; + } + + for( k = 0; k < nb_subfr; k++ ) { + pitch_out[ k ] = lag_new + matrix_ptr( Lag_CB_ptr, k, CBimax, cbk_size ); + pitch_out[ k ] = silk_LIMIT( pitch_out[ k ], min_lag, PE_MAX_LAG_MS * Fs_kHz ); + } + *lagIndex = (opus_int16)( lag_new - min_lag ); + *contourIndex = (opus_int8)CBimax; + } else { /* Fs_kHz == 8 */ + /* Save Lags */ + for( k = 0; k < nb_subfr; k++ ) { + pitch_out[ k ] = lag + matrix_ptr( Lag_CB_ptr, k, CBimax, cbk_size ); + pitch_out[ k ] = silk_LIMIT( pitch_out[ k ], min_lag_8kHz, PE_MAX_LAG_MS * 8 ); + } + *lagIndex = (opus_int16)( lag - min_lag_8kHz ); + *contourIndex = (opus_int8)CBimax; + } + celt_assert( *lagIndex >= 0 ); + /* return as voiced */ + return 0; +} + +/*********************************************************************** + * Calculates the correlations used in stage 3 search. In order to cover + * the whole lag codebook for all the searched offset lags (lag +- 2), + * the following correlations are needed in each sub frame: + * + * sf1: lag range [-8,...,7] total 16 correlations + * sf2: lag range [-4,...,4] total 9 correlations + * sf3: lag range [-3,....4] total 8 correltions + * sf4: lag range [-6,....8] total 15 correlations + * + * In total 48 correlations. The direct implementation computed in worst + * case 4*12*5 = 240 correlations, but more likely around 120. + ***********************************************************************/ +static void silk_P_Ana_calc_corr_st3( + silk_float cross_corr_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ], /* O 3 DIM correlation array */ + const silk_float frame[], /* I vector to correlate */ + opus_int start_lag, /* I start lag */ + opus_int sf_length, /* I sub frame length */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ +) +{ + const silk_float *target_ptr; + opus_int i, j, k, lag_counter, lag_low, lag_high; + opus_int nb_cbk_search, delta, idx, cbk_size; + silk_float scratch_mem[ SCRATCH_SIZE ]; + opus_val32 xcorr[ SCRATCH_SIZE ]; + const opus_int8 *Lag_range_ptr, *Lag_CB_ptr; + + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_range_ptr = &silk_Lag_range_stage3[ complexity ][ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + nb_cbk_search = silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1); + Lag_range_ptr = &silk_Lag_range_stage3_10_ms[ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + } + + target_ptr = &frame[ silk_LSHIFT( sf_length, 2 ) ]; /* Pointer to middle of frame */ + for( k = 0; k < nb_subfr; k++ ) { + lag_counter = 0; + + /* Calculate the correlations for each subframe */ + lag_low = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + lag_high = matrix_ptr( Lag_range_ptr, k, 1, 2 ); + silk_assert(lag_high-lag_low+1 <= SCRATCH_SIZE); + celt_pitch_xcorr( target_ptr, target_ptr - start_lag - lag_high, xcorr, sf_length, lag_high - lag_low + 1, arch ); + for( j = lag_low; j <= lag_high; j++ ) { + silk_assert( lag_counter < SCRATCH_SIZE ); + scratch_mem[ lag_counter ] = xcorr[ lag_high - j ]; + lag_counter++; + } + + delta = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + for( i = 0; i < nb_cbk_search; i++ ) { + /* Fill out the 3 dim array that stores the correlations for */ + /* each code_book vector for each start lag */ + idx = matrix_ptr( Lag_CB_ptr, k, i, cbk_size ) - delta; + for( j = 0; j < PE_NB_STAGE3_LAGS; j++ ) { + silk_assert( idx + j < SCRATCH_SIZE ); + silk_assert( idx + j < lag_counter ); + cross_corr_st3[ k ][ i ][ j ] = scratch_mem[ idx + j ]; + } + } + target_ptr += sf_length; + } +} + +/********************************************************************/ +/* Calculate the energies for first two subframes. The energies are */ +/* calculated recursively. */ +/********************************************************************/ +static void silk_P_Ana_calc_energy_st3( + silk_float energies_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ], /* O 3 DIM correlation array */ + const silk_float frame[], /* I vector to correlate */ + opus_int start_lag, /* I start lag */ + opus_int sf_length, /* I sub frame length */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity /* I Complexity setting */ +) +{ + const silk_float *target_ptr, *basis_ptr; + double energy; + opus_int k, i, j, lag_counter; + opus_int nb_cbk_search, delta, idx, cbk_size, lag_diff; + silk_float scratch_mem[ SCRATCH_SIZE ]; + const opus_int8 *Lag_range_ptr, *Lag_CB_ptr; + + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_range_ptr = &silk_Lag_range_stage3[ complexity ][ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + nb_cbk_search = silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1); + Lag_range_ptr = &silk_Lag_range_stage3_10_ms[ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + } + + target_ptr = &frame[ silk_LSHIFT( sf_length, 2 ) ]; + for( k = 0; k < nb_subfr; k++ ) { + lag_counter = 0; + + /* Calculate the energy for first lag */ + basis_ptr = target_ptr - ( start_lag + matrix_ptr( Lag_range_ptr, k, 0, 2 ) ); + energy = silk_energy_FLP( basis_ptr, sf_length ) + 1e-3; + silk_assert( energy >= 0.0 ); + scratch_mem[lag_counter] = (silk_float)energy; + lag_counter++; + + lag_diff = ( matrix_ptr( Lag_range_ptr, k, 1, 2 ) - matrix_ptr( Lag_range_ptr, k, 0, 2 ) + 1 ); + for( i = 1; i < lag_diff; i++ ) { + /* remove part outside new window */ + energy -= basis_ptr[sf_length - i] * (double)basis_ptr[sf_length - i]; + silk_assert( energy >= 0.0 ); + + /* add part that comes into window */ + energy += basis_ptr[ -i ] * (double)basis_ptr[ -i ]; + silk_assert( energy >= 0.0 ); + silk_assert( lag_counter < SCRATCH_SIZE ); + scratch_mem[lag_counter] = (silk_float)energy; + lag_counter++; + } + + delta = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + for( i = 0; i < nb_cbk_search; i++ ) { + /* Fill out the 3 dim array that stores the correlations for */ + /* each code_book vector for each start lag */ + idx = matrix_ptr( Lag_CB_ptr, k, i, cbk_size ) - delta; + for( j = 0; j < PE_NB_STAGE3_LAGS; j++ ) { + silk_assert( idx + j < SCRATCH_SIZE ); + silk_assert( idx + j < lag_counter ); + energies_st3[ k ][ i ][ j ] = scratch_mem[ idx + j ]; + silk_assert( energies_st3[ k ][ i ][ j ] >= 0.0f ); + } + } + target_ptr += sf_length; + } +} diff --git a/port/third_party/opus/silk/float/process_gains_FLP.c b/port/third_party/opus/silk/float/process_gains_FLP.c new file mode 100644 index 00000000..c0da0dae --- /dev/null +++ b/port/third_party/opus/silk/float/process_gains_FLP.c @@ -0,0 +1,103 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" +#include "tuning_parameters.h" + +/* Processing of gains */ +void silk_process_gains_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + silk_shape_state_FLP *psShapeSt = &psEnc->sShape; + opus_int k; + opus_int32 pGains_Q16[ MAX_NB_SUBFR ]; + silk_float s, InvMaxSqrVal, gain, quant_offset; + + /* Gain reduction when LTP coding gain is high */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + s = 1.0f - 0.5f * silk_sigmoid( 0.25f * ( psEncCtrl->LTPredCodGain - 12.0f ) ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains[ k ] *= s; + } + } + + /* Limit the quantized signal */ + InvMaxSqrVal = ( silk_float )( pow( 2.0f, 0.33f * ( 21.0f - psEnc->sCmn.SNR_dB_Q7 * ( 1 / 128.0f ) ) ) / psEnc->sCmn.subfr_length ); + + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + /* Soft limit on ratio residual energy and squared gains */ + gain = psEncCtrl->Gains[ k ]; + gain = ( silk_float )sqrt( gain * gain + psEncCtrl->ResNrg[ k ] * InvMaxSqrVal ); + psEncCtrl->Gains[ k ] = silk_min_float( gain, 32767.0f ); + } + + /* Prepare gains for noise shaping quantization */ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + pGains_Q16[ k ] = (opus_int32)( psEncCtrl->Gains[ k ] * 65536.0f ); + } + + /* Save unquantized gains and gain Index */ + silk_memcpy( psEncCtrl->GainsUnq_Q16, pGains_Q16, psEnc->sCmn.nb_subfr * sizeof( opus_int32 ) ); + psEncCtrl->lastGainIndexPrev = psShapeSt->LastGainIndex; + + /* Quantize gains */ + silk_gains_quant( psEnc->sCmn.indices.GainsIndices, pGains_Q16, + &psShapeSt->LastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /* Overwrite unquantized gains with quantized gains and convert back to Q0 from Q16 */ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains[ k ] = pGains_Q16[ k ] / 65536.0f; + } + + /* Set quantizer offset for voiced signals. Larger offset when LTP coding gain is low or tilt is high (ie low-pass) */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + if( psEncCtrl->LTPredCodGain + psEnc->sCmn.input_tilt_Q15 * ( 1.0f / 32768.0f ) > 1.0f ) { + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + psEnc->sCmn.indices.quantOffsetType = 1; + } + } + + /* Quantizer boundary adjustment */ + quant_offset = silk_Quantization_Offsets_Q10[ psEnc->sCmn.indices.signalType >> 1 ][ psEnc->sCmn.indices.quantOffsetType ] / 1024.0f; + psEncCtrl->Lambda = LAMBDA_OFFSET + + LAMBDA_DELAYED_DECISIONS * psEnc->sCmn.nStatesDelayedDecision + + LAMBDA_SPEECH_ACT * psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ) + + LAMBDA_INPUT_QUALITY * psEncCtrl->input_quality + + LAMBDA_CODING_QUALITY * psEncCtrl->coding_quality + + LAMBDA_QUANT_OFFSET * quant_offset; + + silk_assert( psEncCtrl->Lambda > 0.0f ); + silk_assert( psEncCtrl->Lambda < 2.0f ); +} diff --git a/port/third_party/opus/silk/float/regularize_correlations_FLP.c b/port/third_party/opus/silk/float/regularize_correlations_FLP.c new file mode 100644 index 00000000..df461260 --- /dev/null +++ b/port/third_party/opus/silk/float/regularize_correlations_FLP.c @@ -0,0 +1,48 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Add noise to matrix diagonal */ +void silk_regularize_correlations_FLP( + silk_float *XX, /* I/O Correlation matrices */ + silk_float *xx, /* I/O Correlation values */ + const silk_float noise, /* I Noise energy to add */ + const opus_int D /* I Dimension of XX */ +) +{ + opus_int i; + + for( i = 0; i < D; i++ ) { + matrix_ptr( &XX[ 0 ], i, i, D ) += noise; + } + xx[ 0 ] += noise; +} diff --git a/port/third_party/opus/silk/float/residual_energy_FLP.c b/port/third_party/opus/silk/float/residual_energy_FLP.c new file mode 100644 index 00000000..1bd07b33 --- /dev/null +++ b/port/third_party/opus/silk/float/residual_energy_FLP.c @@ -0,0 +1,117 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +#define MAX_ITERATIONS_RESIDUAL_NRG 10 +#define REGULARIZATION_FACTOR 1e-8f + +/* Residual energy: nrg = wxx - 2 * wXx * c + c' * wXX * c */ +silk_float silk_residual_energy_covar_FLP( /* O Weighted residual energy */ + const silk_float *c, /* I Filter coefficients */ + silk_float *wXX, /* I/O Weighted correlation matrix, reg. out */ + const silk_float *wXx, /* I Weighted correlation vector */ + const silk_float wxx, /* I Weighted correlation value */ + const opus_int D /* I Dimension */ +) +{ + opus_int i, j, k; + silk_float tmp, nrg = 0.0f, regularization; + + /* Safety checks */ + celt_assert( D >= 0 ); + + regularization = REGULARIZATION_FACTOR * ( wXX[ 0 ] + wXX[ D * D - 1 ] ); + for( k = 0; k < MAX_ITERATIONS_RESIDUAL_NRG; k++ ) { + nrg = wxx; + + tmp = 0.0f; + for( i = 0; i < D; i++ ) { + tmp += wXx[ i ] * c[ i ]; + } + nrg -= 2.0f * tmp; + + /* compute c' * wXX * c, assuming wXX is symmetric */ + for( i = 0; i < D; i++ ) { + tmp = 0.0f; + for( j = i + 1; j < D; j++ ) { + tmp += matrix_c_ptr( wXX, i, j, D ) * c[ j ]; + } + nrg += c[ i ] * ( 2.0f * tmp + matrix_c_ptr( wXX, i, i, D ) * c[ i ] ); + } + if( nrg > 0 ) { + break; + } else { + /* Add white noise */ + for( i = 0; i < D; i++ ) { + matrix_c_ptr( wXX, i, i, D ) += regularization; + } + /* Increase noise for next run */ + regularization *= 2.0f; + } + } + if( k == MAX_ITERATIONS_RESIDUAL_NRG ) { + silk_assert( nrg == 0 ); + nrg = 1.0f; + } + + return nrg; +} + +/* Calculates residual energies of input subframes where all subframes have LPC_order */ +/* of preceding samples */ +void silk_residual_energy_FLP( + silk_float nrgs[ MAX_NB_SUBFR ], /* O Residual energy per subframe */ + const silk_float x[], /* I Input signal */ + silk_float a[ 2 ][ MAX_LPC_ORDER ], /* I AR coefs for each frame half */ + const silk_float gains[], /* I Quantization gains */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I number of subframes */ + const opus_int LPC_order /* I LPC order */ +) +{ + opus_int shift; + silk_float *LPC_res_ptr, LPC_res[ ( MAX_FRAME_LENGTH + MAX_NB_SUBFR * MAX_LPC_ORDER ) / 2 ]; + + LPC_res_ptr = LPC_res + LPC_order; + shift = LPC_order + subfr_length; + + /* Filter input to create the LPC residual for each frame half, and measure subframe energies */ + silk_LPC_analysis_filter_FLP( LPC_res, a[ 0 ], x + 0 * shift, 2 * shift, LPC_order ); + nrgs[ 0 ] = ( silk_float )( gains[ 0 ] * gains[ 0 ] * silk_energy_FLP( LPC_res_ptr + 0 * shift, subfr_length ) ); + nrgs[ 1 ] = ( silk_float )( gains[ 1 ] * gains[ 1 ] * silk_energy_FLP( LPC_res_ptr + 1 * shift, subfr_length ) ); + + if( nb_subfr == MAX_NB_SUBFR ) { + silk_LPC_analysis_filter_FLP( LPC_res, a[ 1 ], x + 2 * shift, 2 * shift, LPC_order ); + nrgs[ 2 ] = ( silk_float )( gains[ 2 ] * gains[ 2 ] * silk_energy_FLP( LPC_res_ptr + 0 * shift, subfr_length ) ); + nrgs[ 3 ] = ( silk_float )( gains[ 3 ] * gains[ 3 ] * silk_energy_FLP( LPC_res_ptr + 1 * shift, subfr_length ) ); + } +} diff --git a/port/third_party/opus/silk/float/scale_copy_vector_FLP.c b/port/third_party/opus/silk/float/scale_copy_vector_FLP.c new file mode 100644 index 00000000..20db32b3 --- /dev/null +++ b/port/third_party/opus/silk/float/scale_copy_vector_FLP.c @@ -0,0 +1,57 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* copy and multiply a vector by a constant */ +void silk_scale_copy_vector_FLP( + silk_float *data_out, + const silk_float *data_in, + silk_float gain, + opus_int dataSize +) +{ + opus_int i, dataSize4; + + /* 4x unrolled loop */ + dataSize4 = dataSize & 0xFFFC; + for( i = 0; i < dataSize4; i += 4 ) { + data_out[ i + 0 ] = gain * data_in[ i + 0 ]; + data_out[ i + 1 ] = gain * data_in[ i + 1 ]; + data_out[ i + 2 ] = gain * data_in[ i + 2 ]; + data_out[ i + 3 ] = gain * data_in[ i + 3 ]; + } + + /* any remaining elements */ + for( ; i < dataSize; i++ ) { + data_out[ i ] = gain * data_in[ i ]; + } +} diff --git a/port/third_party/opus/silk/float/scale_vector_FLP.c b/port/third_party/opus/silk/float/scale_vector_FLP.c new file mode 100644 index 00000000..108fdcbe --- /dev/null +++ b/port/third_party/opus/silk/float/scale_vector_FLP.c @@ -0,0 +1,56 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* multiply a vector by a constant */ +void silk_scale_vector_FLP( + silk_float *data1, + silk_float gain, + opus_int dataSize +) +{ + opus_int i, dataSize4; + + /* 4x unrolled loop */ + dataSize4 = dataSize & 0xFFFC; + for( i = 0; i < dataSize4; i += 4 ) { + data1[ i + 0 ] *= gain; + data1[ i + 1 ] *= gain; + data1[ i + 2 ] *= gain; + data1[ i + 3 ] *= gain; + } + + /* any remaining elements */ + for( ; i < dataSize; i++ ) { + data1[ i ] *= gain; + } +} diff --git a/port/third_party/opus/silk/float/schur_FLP.c b/port/third_party/opus/silk/float/schur_FLP.c new file mode 100644 index 00000000..8526c748 --- /dev/null +++ b/port/third_party/opus/silk/float/schur_FLP.c @@ -0,0 +1,70 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +silk_float silk_schur_FLP( /* O returns residual energy */ + silk_float refl_coef[], /* O reflection coefficients (length order) */ + const silk_float auto_corr[], /* I autocorrelation sequence (length order+1) */ + opus_int order /* I order */ +) +{ + opus_int k, n; + double C[ SILK_MAX_ORDER_LPC + 1 ][ 2 ]; + double Ctmp1, Ctmp2, rc_tmp; + + celt_assert( order >= 0 && order <= SILK_MAX_ORDER_LPC ); + + /* Copy correlations */ + k = 0; + do { + C[ k ][ 0 ] = C[ k ][ 1 ] = auto_corr[ k ]; + } while( ++k <= order ); + + for( k = 0; k < order; k++ ) { + /* Get reflection coefficient */ + rc_tmp = -C[ k + 1 ][ 0 ] / silk_max_float( C[ 0 ][ 1 ], 1e-9f ); + + /* Save the output */ + refl_coef[ k ] = (silk_float)rc_tmp; + + /* Update correlations */ + for( n = 0; n < order - k; n++ ) { + Ctmp1 = C[ n + k + 1 ][ 0 ]; + Ctmp2 = C[ n ][ 1 ]; + C[ n + k + 1 ][ 0 ] = Ctmp1 + Ctmp2 * rc_tmp; + C[ n ][ 1 ] = Ctmp2 + Ctmp1 * rc_tmp; + } + } + + /* Return residual energy */ + return (silk_float)C[ 0 ][ 1 ]; +} diff --git a/port/third_party/opus/silk/float/sort_FLP.c b/port/third_party/opus/silk/float/sort_FLP.c new file mode 100644 index 00000000..0e18f319 --- /dev/null +++ b/port/third_party/opus/silk/float/sort_FLP.c @@ -0,0 +1,83 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Insertion sort (fast for already almost sorted arrays): */ +/* Best case: O(n) for an already sorted array */ +/* Worst case: O(n^2) for an inversely sorted array */ + +#include "typedef.h" +#include "SigProc_FLP.h" + +void silk_insertion_sort_decreasing_FLP( + silk_float *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +) +{ + silk_float value; + opus_int i, j; + + /* Safety checks */ + celt_assert( K > 0 ); + celt_assert( L > 0 ); + celt_assert( L >= K ); + + /* Write start indices in index vector */ + for( i = 0; i < K; i++ ) { + idx[ i ] = i; + } + + /* Sort vector elements by value, decreasing order */ + for( i = 1; i < K; i++ ) { + value = a[ i ]; + for( j = i - 1; ( j >= 0 ) && ( value > a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + + /* If less than L values are asked check the remaining values, */ + /* but only spend CPU to ensure that the K first values are correct */ + for( i = K; i < L; i++ ) { + value = a[ i ]; + if( value > a[ K - 1 ] ) { + for( j = K - 2; ( j >= 0 ) && ( value > a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + } +} diff --git a/port/third_party/opus/silk/float/structs_FLP.h b/port/third_party/opus/silk/float/structs_FLP.h new file mode 100644 index 00000000..3150b386 --- /dev/null +++ b/port/third_party/opus/silk/float/structs_FLP.h @@ -0,0 +1,112 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_STRUCTS_FLP_H +#define SILK_STRUCTS_FLP_H + +#include "typedef.h" +#include "main.h" +#include "structs.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/********************************/ +/* Noise shaping analysis state */ +/********************************/ +typedef struct { + opus_int8 LastGainIndex; + silk_float HarmShapeGain_smth; + silk_float Tilt_smth; +} silk_shape_state_FLP; + +/********************************/ +/* Encoder state FLP */ +/********************************/ +typedef struct { + silk_encoder_state sCmn; /* Common struct, shared with fixed-point code */ + silk_shape_state_FLP sShape; /* Noise shaping state */ + + /* Buffer for find pitch and noise shape analysis */ + silk_float x_buf[ 2 * MAX_FRAME_LENGTH + LA_SHAPE_MAX ];/* Buffer for find pitch and noise shape analysis */ + silk_float LTPCorr; /* Normalized correlation from pitch lag estimator */ +} silk_encoder_state_FLP; + +/************************/ +/* Encoder control FLP */ +/************************/ +typedef struct { + /* Prediction and coding parameters */ + silk_float Gains[ MAX_NB_SUBFR ]; + silk_float PredCoef[ 2 ][ MAX_LPC_ORDER ]; /* holds interpolated and final coefficients */ + silk_float LTPCoef[LTP_ORDER * MAX_NB_SUBFR]; + silk_float LTP_scale; + opus_int pitchL[ MAX_NB_SUBFR ]; + + /* Noise shaping parameters */ + silk_float AR[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; + silk_float LF_MA_shp[ MAX_NB_SUBFR ]; + silk_float LF_AR_shp[ MAX_NB_SUBFR ]; + silk_float Tilt[ MAX_NB_SUBFR ]; + silk_float HarmShapeGain[ MAX_NB_SUBFR ]; + silk_float Lambda; + silk_float input_quality; + silk_float coding_quality; + + /* Measures */ + silk_float predGain; + silk_float LTPredCodGain; + silk_float ResNrg[ MAX_NB_SUBFR ]; /* Residual energy per subframe */ + + /* Parameters for CBR mode */ + opus_int32 GainsUnq_Q16[ MAX_NB_SUBFR ]; + opus_int8 lastGainIndexPrev; +} silk_encoder_control_FLP; + +/************************/ +/* Encoder Super Struct */ +/************************/ +typedef struct { + silk_encoder_state_FLP state_Fxx[ ENCODER_NUM_CHANNELS ]; + stereo_enc_state sStereo; + opus_int32 nBitsUsedLBRR; + opus_int32 nBitsExceeded; + opus_int nChannelsAPI; + opus_int nChannelsInternal; + opus_int nPrevChannelsInternal; + opus_int timeSinceSwitchAllowed_ms; + opus_int allowBandwidthSwitch; + opus_int prev_decode_only_middle; +} silk_encoder; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/silk/float/warped_autocorrelation_FLP.c b/port/third_party/opus/silk/float/warped_autocorrelation_FLP.c new file mode 100644 index 00000000..116dab92 --- /dev/null +++ b/port/third_party/opus/silk/float/warped_autocorrelation_FLP.c @@ -0,0 +1,75 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Autocorrelations for a warped frequency axis */ +void silk_warped_autocorrelation_FLP( + silk_float *corr, /* O Result [order + 1] */ + const silk_float *input, /* I Input data to correlate */ + const silk_float warping, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +) +{ + opus_int n, i; + double tmp1, tmp2; + double state[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + double C[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + + /* Order must be even */ + celt_assert( ( order & 1 ) == 0 ); + + /* Loop over samples */ + for( n = 0; n < length; n++ ) { + tmp1 = input[ n ]; + /* Loop over allpass sections */ + for( i = 0; i < order; i += 2 ) { + /* Output of allpass section */ + /* We voluntarily use two multiples instead of factoring the expression to + reduce the length of the dependency chain (tmp1->tmp2->tmp1... ). */ + tmp2 = state[ i ] + warping * state[ i + 1 ] - warping * tmp1; + state[ i ] = tmp1; + C[ i ] += state[ 0 ] * tmp1; + /* Output of allpass section */ + tmp1 = state[ i + 1 ] + warping * state[ i + 2 ] - warping * tmp2; + state[ i + 1 ] = tmp2; + C[ i + 1 ] += state[ 0 ] * tmp2; + } + state[ order ] = tmp1; + C[ order ] += state[ 0 ] * tmp1; + } + + /* Copy correlations in silk_float output format */ + for( i = 0; i < order + 1; i++ ) { + corr[ i ] = ( silk_float )C[ i ]; + } +} diff --git a/port/third_party/opus/silk/float/wrappers_FLP.c b/port/third_party/opus/silk/float/wrappers_FLP.c new file mode 100644 index 00000000..c0c183e3 --- /dev/null +++ b/port/third_party/opus/silk/float/wrappers_FLP.c @@ -0,0 +1,209 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Wrappers. Calls flp / fix code */ + +/* Convert AR filter coefficients to NLSF parameters */ +void silk_A2NLSF_FLP( + opus_int16 *NLSF_Q15, /* O NLSF vector [ LPC_order ] */ + const silk_float *pAR, /* I LPC coefficients [ LPC_order ] */ + const opus_int LPC_order /* I LPC order */ +) +{ + opus_int i; + opus_int32 a_fix_Q16[ MAX_LPC_ORDER ]; + + for( i = 0; i < LPC_order; i++ ) { + a_fix_Q16[ i ] = silk_float2int( pAR[ i ] * 65536.0f ); + } + + silk_A2NLSF( NLSF_Q15, a_fix_Q16, LPC_order ); +} + +/* Convert LSF parameters to AR prediction filter coefficients */ +void silk_NLSF2A_FLP( + silk_float *pAR, /* O LPC coefficients [ LPC_order ] */ + const opus_int16 *NLSF_Q15, /* I NLSF vector [ LPC_order ] */ + const opus_int LPC_order, /* I LPC order */ + int arch /* I Run-time architecture */ +) +{ + opus_int i; + opus_int16 a_fix_Q12[ MAX_LPC_ORDER ]; + + silk_NLSF2A( a_fix_Q12, NLSF_Q15, LPC_order, arch ); + + for( i = 0; i < LPC_order; i++ ) { + pAR[ i ] = ( silk_float )a_fix_Q12[ i ] * ( 1.0f / 4096.0f ); + } +} + +/******************************************/ +/* Floating-point NLSF processing wrapper */ +/******************************************/ +void silk_process_NLSFs_FLP( + silk_encoder_state *psEncC, /* I/O Encoder state */ + silk_float PredCoef[ 2 ][ MAX_LPC_ORDER ], /* O Prediction coefficients */ + opus_int16 NLSF_Q15[ MAX_LPC_ORDER ], /* I/O Normalized LSFs (quant out) (0 - (2^15-1)) */ + const opus_int16 prev_NLSF_Q15[ MAX_LPC_ORDER ] /* I Previous Normalized LSFs (0 - (2^15-1)) */ +) +{ + opus_int i, j; + opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ]; + + silk_process_NLSFs( psEncC, PredCoef_Q12, NLSF_Q15, prev_NLSF_Q15); + + for( j = 0; j < 2; j++ ) { + for( i = 0; i < psEncC->predictLPCOrder; i++ ) { + PredCoef[ j ][ i ] = ( silk_float )PredCoef_Q12[ j ][ i ] * ( 1.0f / 4096.0f ); + } + } +} + +/****************************************/ +/* Floating-point Silk NSQ wrapper */ +/****************************************/ +void silk_NSQ_wrapper_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + SideInfoIndices *psIndices, /* I/O Quantization indices */ + silk_nsq_state *psNSQ, /* I/O Noise Shaping Quantzation state */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const silk_float x[] /* I Prefiltered input signal */ +) +{ + opus_int i, j; + opus_int16 x16[ MAX_FRAME_LENGTH ]; + opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; + silk_DWORD_ALIGN opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ]; + opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ]; + opus_int LTP_scale_Q14; + + /* Noise shaping parameters */ + opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; + opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ]; /* Packs two int16 coefficients per int32 value */ + opus_int Lambda_Q10; + opus_int Tilt_Q14[ MAX_NB_SUBFR ]; + opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ]; + + /* Convert control struct to fix control struct */ + /* Noise shape parameters */ + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + for( j = 0; j < psEnc->sCmn.shapingLPCOrder; j++ ) { + AR_Q13[ i * MAX_SHAPE_LPC_ORDER + j ] = silk_float2int( psEncCtrl->AR[ i * MAX_SHAPE_LPC_ORDER + j ] * 8192.0f ); + } + } + + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + LF_shp_Q14[ i ] = silk_LSHIFT32( silk_float2int( psEncCtrl->LF_AR_shp[ i ] * 16384.0f ), 16 ) | + (opus_uint16)silk_float2int( psEncCtrl->LF_MA_shp[ i ] * 16384.0f ); + Tilt_Q14[ i ] = (opus_int)silk_float2int( psEncCtrl->Tilt[ i ] * 16384.0f ); + HarmShapeGain_Q14[ i ] = (opus_int)silk_float2int( psEncCtrl->HarmShapeGain[ i ] * 16384.0f ); + } + Lambda_Q10 = ( opus_int )silk_float2int( psEncCtrl->Lambda * 1024.0f ); + + /* prediction and coding parameters */ + for( i = 0; i < psEnc->sCmn.nb_subfr * LTP_ORDER; i++ ) { + LTPCoef_Q14[ i ] = (opus_int16)silk_float2int( psEncCtrl->LTPCoef[ i ] * 16384.0f ); + } + + for( j = 0; j < 2; j++ ) { + for( i = 0; i < psEnc->sCmn.predictLPCOrder; i++ ) { + PredCoef_Q12[ j ][ i ] = (opus_int16)silk_float2int( psEncCtrl->PredCoef[ j ][ i ] * 4096.0f ); + } + } + + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + Gains_Q16[ i ] = silk_float2int( psEncCtrl->Gains[ i ] * 65536.0f ); + silk_assert( Gains_Q16[ i ] > 0 ); + } + + if( psIndices->signalType == TYPE_VOICED ) { + LTP_scale_Q14 = silk_LTPScales_table_Q14[ psIndices->LTP_scaleIndex ]; + } else { + LTP_scale_Q14 = 0; + } + + /* Convert input to fix */ + for( i = 0; i < psEnc->sCmn.frame_length; i++ ) { + x16[ i ] = silk_float2int( x[ i ] ); + } + + /* Call NSQ */ + if( psEnc->sCmn.nStatesDelayedDecision > 1 || psEnc->sCmn.warping_Q16 > 0 ) { + silk_NSQ_del_dec( &psEnc->sCmn, psNSQ, psIndices, x16, pulses, PredCoef_Q12[ 0 ], LTPCoef_Q14, + AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, psEncCtrl->pitchL, Lambda_Q10, LTP_scale_Q14, psEnc->sCmn.arch ); + } else { + silk_NSQ( &psEnc->sCmn, psNSQ, psIndices, x16, pulses, PredCoef_Q12[ 0 ], LTPCoef_Q14, + AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, psEncCtrl->pitchL, Lambda_Q10, LTP_scale_Q14, psEnc->sCmn.arch ); + } +} + +/***********************************************/ +/* Floating-point Silk LTP quantiation wrapper */ +/***********************************************/ +void silk_quant_LTP_gains_FLP( + silk_float B[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ + opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook index */ + opus_int8 *periodicity_index, /* O Periodicity index */ + opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ + silk_float *pred_gain_dB, /* O LTP prediction gain */ + const silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* I Correlation matrix */ + const silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* I Correlation vector */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +) +{ + opus_int i, pred_gain_dB_Q7; + opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ]; + opus_int32 XX_Q17[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ]; + opus_int32 xX_Q17[ MAX_NB_SUBFR * LTP_ORDER ]; + + i = 0; + do { + XX_Q17[ i ] = (opus_int32)silk_float2int( XX[ i ] * 131072.0f ); + } while ( ++i < nb_subfr * LTP_ORDER * LTP_ORDER ); + i = 0; + do { + xX_Q17[ i ] = (opus_int32)silk_float2int( xX[ i ] * 131072.0f ); + } while ( ++i < nb_subfr * LTP_ORDER ); + + silk_quant_LTP_gains( B_Q14, cbk_index, periodicity_index, sum_log_gain_Q7, &pred_gain_dB_Q7, XX_Q17, xX_Q17, subfr_len, nb_subfr, arch ); + + for( i = 0; i < nb_subfr * LTP_ORDER; i++ ) { + B[ i ] = (silk_float)B_Q14[ i ] * ( 1.0f / 16384.0f ); + } + + *pred_gain_dB = (silk_float)pred_gain_dB_Q7 * ( 1.0f / 128.0f ); +} diff --git a/port/third_party/opus/silk/gain_quant.c b/port/third_party/opus/silk/gain_quant.c new file mode 100644 index 00000000..ee65245a --- /dev/null +++ b/port/third_party/opus/silk/gain_quant.c @@ -0,0 +1,142 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +#define OFFSET ( ( MIN_QGAIN_DB * 128 ) / 6 + 16 * 128 ) +#define SCALE_Q16 ( ( 65536 * ( N_LEVELS_QGAIN - 1 ) ) / ( ( ( MAX_QGAIN_DB - MIN_QGAIN_DB ) * 128 ) / 6 ) ) +#define INV_SCALE_Q16 ( ( 65536 * ( ( ( MAX_QGAIN_DB - MIN_QGAIN_DB ) * 128 ) / 6 ) ) / ( N_LEVELS_QGAIN - 1 ) ) + +/* Gain scalar quantization with hysteresis, uniform on log scale */ +void silk_gains_quant( + opus_int8 ind[ MAX_NB_SUBFR ], /* O gain indices */ + opus_int32 gain_Q16[ MAX_NB_SUBFR ], /* I/O gains (quantized out) */ + opus_int8 *prev_ind, /* I/O last index in previous frame */ + const opus_int conditional, /* I first gain is delta coded if 1 */ + const opus_int nb_subfr /* I number of subframes */ +) +{ + opus_int k, double_step_size_threshold; + + for( k = 0; k < nb_subfr; k++ ) { + /* Convert to log scale, scale, floor() */ + ind[ k ] = silk_SMULWB( SCALE_Q16, silk_lin2log( gain_Q16[ k ] ) - OFFSET ); + + /* Round towards previous quantized gain (hysteresis) */ + if( ind[ k ] < *prev_ind ) { + ind[ k ]++; + } + ind[ k ] = silk_LIMIT_int( ind[ k ], 0, N_LEVELS_QGAIN - 1 ); + + /* Compute delta indices and limit */ + if( k == 0 && conditional == 0 ) { + /* Full index */ + ind[ k ] = silk_LIMIT_int( ind[ k ], *prev_ind + MIN_DELTA_GAIN_QUANT, N_LEVELS_QGAIN - 1 ); + *prev_ind = ind[ k ]; + } else { + /* Delta index */ + ind[ k ] = ind[ k ] - *prev_ind; + + /* Double the quantization step size for large gain increases, so that the max gain level can be reached */ + double_step_size_threshold = 2 * MAX_DELTA_GAIN_QUANT - N_LEVELS_QGAIN + *prev_ind; + if( ind[ k ] > double_step_size_threshold ) { + ind[ k ] = double_step_size_threshold + silk_RSHIFT( ind[ k ] - double_step_size_threshold + 1, 1 ); + } + + ind[ k ] = silk_LIMIT_int( ind[ k ], MIN_DELTA_GAIN_QUANT, MAX_DELTA_GAIN_QUANT ); + + /* Accumulate deltas */ + if( ind[ k ] > double_step_size_threshold ) { + *prev_ind += silk_LSHIFT( ind[ k ], 1 ) - double_step_size_threshold; + *prev_ind = silk_min_int( *prev_ind, N_LEVELS_QGAIN - 1 ); + } else { + *prev_ind += ind[ k ]; + } + + /* Shift to make non-negative */ + ind[ k ] -= MIN_DELTA_GAIN_QUANT; + } + + /* Scale and convert to linear scale */ + gain_Q16[ k ] = silk_log2lin( silk_min_32( silk_SMULWB( INV_SCALE_Q16, *prev_ind ) + OFFSET, 3967 ) ); /* 3967 = 31 in Q7 */ + } +} + +/* Gains scalar dequantization, uniform on log scale */ +void silk_gains_dequant( + opus_int32 gain_Q16[ MAX_NB_SUBFR ], /* O quantized gains */ + const opus_int8 ind[ MAX_NB_SUBFR ], /* I gain indices */ + opus_int8 *prev_ind, /* I/O last index in previous frame */ + const opus_int conditional, /* I first gain is delta coded if 1 */ + const opus_int nb_subfr /* I number of subframes */ +) +{ + opus_int k, ind_tmp, double_step_size_threshold; + + for( k = 0; k < nb_subfr; k++ ) { + if( k == 0 && conditional == 0 ) { + /* Gain index is not allowed to go down more than 16 steps (~21.8 dB) */ + *prev_ind = silk_max_int( ind[ k ], *prev_ind - 16 ); + } else { + /* Delta index */ + ind_tmp = ind[ k ] + MIN_DELTA_GAIN_QUANT; + + /* Accumulate deltas */ + double_step_size_threshold = 2 * MAX_DELTA_GAIN_QUANT - N_LEVELS_QGAIN + *prev_ind; + if( ind_tmp > double_step_size_threshold ) { + *prev_ind += silk_LSHIFT( ind_tmp, 1 ) - double_step_size_threshold; + } else { + *prev_ind += ind_tmp; + } + } + *prev_ind = silk_LIMIT_int( *prev_ind, 0, N_LEVELS_QGAIN - 1 ); + + /* Scale and convert to linear scale */ + gain_Q16[ k ] = silk_log2lin( silk_min_32( silk_SMULWB( INV_SCALE_Q16, *prev_ind ) + OFFSET, 3967 ) ); /* 3967 = 31 in Q7 */ + } +} + +/* Compute unique identifier of gain indices vector */ +opus_int32 silk_gains_ID( /* O returns unique identifier of gains */ + const opus_int8 ind[ MAX_NB_SUBFR ], /* I gain indices */ + const opus_int nb_subfr /* I number of subframes */ +) +{ + opus_int k; + opus_int32 gainsID; + + gainsID = 0; + for( k = 0; k < nb_subfr; k++ ) { + gainsID = silk_ADD_LSHIFT32( ind[ k ], gainsID, 8 ); + } + + return gainsID; +} diff --git a/port/third_party/opus/silk/init_decoder.c b/port/third_party/opus/silk/init_decoder.c new file mode 100644 index 00000000..01bc4b7a --- /dev/null +++ b/port/third_party/opus/silk/init_decoder.c @@ -0,0 +1,84 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +#ifdef ENABLE_OSCE +#include "osce.h" +#endif + +#include "structs.h" + +/************************/ +/* Reset Decoder State */ +/************************/ +opus_int silk_reset_decoder( + silk_decoder_state *psDec /* I/O Decoder state pointer */ +) +{ + /* Clear the entire encoder state, except anything copied */ + silk_memset( &psDec->SILK_DECODER_STATE_RESET_START, 0, sizeof( silk_decoder_state ) - ((char*) &psDec->SILK_DECODER_STATE_RESET_START - (char*)psDec) ); + + /* Used to deactivate LSF interpolation */ + psDec->first_frame_after_reset = 1; + psDec->prev_gain_Q16 = 65536; + psDec->arch = opus_select_arch(); + + /* Reset CNG state */ + silk_CNG_Reset( psDec ); + + /* Reset PLC state */ + silk_PLC_Reset( psDec ); + +#ifdef ENABLE_OSCE + /* Reset OSCE state and method */ + osce_reset(&psDec->osce, OSCE_DEFAULT_METHOD); +#endif + + return 0; +} + + +/************************/ +/* Init Decoder State */ +/************************/ +opus_int silk_init_decoder( + silk_decoder_state *psDec /* I/O Decoder state pointer */ +) +{ + /* Clear the entire encoder state, except anything copied */ + silk_memset( psDec, 0, sizeof( silk_decoder_state ) ); + + silk_reset_decoder( psDec ); + + return(0); +} + diff --git a/port/third_party/opus/silk/init_encoder.c b/port/third_party/opus/silk/init_encoder.c new file mode 100644 index 00000000..10d41287 --- /dev/null +++ b/port/third_party/opus/silk/init_encoder.c @@ -0,0 +1,68 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef FIXED_POINT +#include "main_FIX.h" +#else +#include "main_FLP.h" +#endif +#include "tuning_parameters.h" +#include "cpu_support.h" + +#ifdef ENABLE_DRED +#include "dred_encoder.h" +#endif + +/*********************************/ +/* Initialize Silk Encoder state */ +/*********************************/ +opus_int silk_init_encoder( + silk_encoder_state_Fxx *psEnc, /* I/O Pointer to Silk FIX encoder state */ + int arch /* I Run-time architecture */ +) +{ + opus_int ret = 0; + + /* Clear the entire encoder state */ + silk_memset( psEnc, 0, sizeof( silk_encoder_state_Fxx ) ); + + psEnc->sCmn.arch = arch; + + psEnc->sCmn.variable_HP_smth1_Q15 = silk_LSHIFT( silk_lin2log( SILK_FIX_CONST( VARIABLE_HP_MIN_CUTOFF_HZ, 16 ) ) - ( 16 << 7 ), 8 ); + psEnc->sCmn.variable_HP_smth2_Q15 = psEnc->sCmn.variable_HP_smth1_Q15; + + /* Used to deactivate LSF interpolation, pitch prediction */ + psEnc->sCmn.first_frame_after_reset = 1; + + /* Initialize Silk VAD */ + ret += silk_VAD_Init( &psEnc->sCmn.sVAD ); + + return ret; +} diff --git a/port/third_party/opus/silk/inner_prod_aligned.c b/port/third_party/opus/silk/inner_prod_aligned.c new file mode 100644 index 00000000..257ae9e0 --- /dev/null +++ b/port/third_party/opus/silk/inner_prod_aligned.c @@ -0,0 +1,47 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +opus_int32 silk_inner_prod_aligned_scale( + const opus_int16 *const inVec1, /* I input vector 1 */ + const opus_int16 *const inVec2, /* I input vector 2 */ + const opus_int scale, /* I number of bits to shift */ + const opus_int len /* I vector lengths */ +) +{ + opus_int i; + opus_int32 sum = 0; + for( i = 0; i < len; i++ ) { + sum = silk_ADD_RSHIFT32( sum, silk_SMULBB( inVec1[ i ], inVec2[ i ] ), scale ); + } + return sum; +} diff --git a/port/third_party/opus/silk/interpolate.c b/port/third_party/opus/silk/interpolate.c new file mode 100644 index 00000000..833c28ef --- /dev/null +++ b/port/third_party/opus/silk/interpolate.c @@ -0,0 +1,51 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Interpolate two vectors */ +void silk_interpolate( + opus_int16 xi[ MAX_LPC_ORDER ], /* O interpolated vector */ + const opus_int16 x0[ MAX_LPC_ORDER ], /* I first vector */ + const opus_int16 x1[ MAX_LPC_ORDER ], /* I second vector */ + const opus_int ifact_Q2, /* I interp. factor, weight on 2nd vector */ + const opus_int d /* I number of parameters */ +) +{ + opus_int i; + + celt_assert( ifact_Q2 >= 0 ); + celt_assert( ifact_Q2 <= 4 ); + + for( i = 0; i < d; i++ ) { + xi[ i ] = (opus_int16)silk_ADD_RSHIFT( x0[ i ], silk_SMULBB( x1[ i ] - x0[ i ], ifact_Q2 ), 2 ); + } +} diff --git a/port/third_party/opus/silk/lin2log.c b/port/third_party/opus/silk/lin2log.c new file mode 100644 index 00000000..0d5155aa --- /dev/null +++ b/port/third_party/opus/silk/lin2log.c @@ -0,0 +1,46 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +/* Approximation of 128 * log2() (very close inverse of silk_log2lin()) */ +/* Convert input to a log scale */ +opus_int32 silk_lin2log( + const opus_int32 inLin /* I input in linear scale */ +) +{ + opus_int32 lz, frac_Q7; + + silk_CLZ_FRAC( inLin, &lz, &frac_Q7 ); + + /* Piece-wise parabolic approximation */ + return silk_ADD_LSHIFT32( silk_SMLAWB( frac_Q7, silk_MUL( frac_Q7, 128 - frac_Q7 ), 179 ), 31 - lz, 7 ); +} + diff --git a/port/third_party/opus/silk/log2lin.c b/port/third_party/opus/silk/log2lin.c new file mode 100644 index 00000000..b7c48e47 --- /dev/null +++ b/port/third_party/opus/silk/log2lin.c @@ -0,0 +1,58 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Approximation of 2^() (very close inverse of silk_lin2log()) */ +/* Convert input to a linear scale */ +opus_int32 silk_log2lin( + const opus_int32 inLog_Q7 /* I input on log scale */ +) +{ + opus_int32 out, frac_Q7; + + if( inLog_Q7 < 0 ) { + return 0; + } else if ( inLog_Q7 >= 3967 ) { + return silk_int32_MAX; + } + + out = silk_LSHIFT( 1, silk_RSHIFT( inLog_Q7, 7 ) ); + frac_Q7 = inLog_Q7 & 0x7F; + if( inLog_Q7 < 2048 ) { + /* Piece-wise parabolic approximation */ + out = silk_ADD_RSHIFT32( out, silk_MUL( out, silk_SMLAWB( frac_Q7, silk_SMULBB( frac_Q7, 128 - frac_Q7 ), -174 ) ), 7 ); + } else { + /* Piece-wise parabolic approximation */ + out = silk_MLA( out, silk_RSHIFT( out, 7 ), silk_SMLAWB( frac_Q7, silk_SMULBB( frac_Q7, 128 - frac_Q7 ), -174 ) ); + } + return out; +} diff --git a/port/third_party/opus/silk/macros.h b/port/third_party/opus/silk/macros.h new file mode 100644 index 00000000..3c67b6e5 --- /dev/null +++ b/port/third_party/opus/silk/macros.h @@ -0,0 +1,151 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_MACROS_H +#define SILK_MACROS_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus_types.h" +#include "opus_defines.h" +#include "arch.h" + +/* This is an OPUS_INLINE header file for general platform. */ + +/* (a32 * (opus_int32)((opus_int16)(b32))) >> 16 output have to be 32bit int */ +#if OPUS_FAST_INT64 +#define silk_SMULWB(a32, b32) ((opus_int32)(((a32) * (opus_int64)((opus_int16)(b32))) >> 16)) +#else +#define silk_SMULWB(a32, b32) ((((a32) >> 16) * (opus_int32)((opus_int16)(b32))) + ((((a32) & 0x0000FFFF) * (opus_int32)((opus_int16)(b32))) >> 16)) +#endif + +/* a32 + (b32 * (opus_int32)((opus_int16)(c32))) >> 16 output have to be 32bit int */ +#if OPUS_FAST_INT64 +#define silk_SMLAWB(a32, b32, c32) ((opus_int32)((a32) + (((b32) * (opus_int64)((opus_int16)(c32))) >> 16))) +#else +#define silk_SMLAWB(a32, b32, c32) ((a32) + ((((b32) >> 16) * (opus_int32)((opus_int16)(c32))) + ((((b32) & 0x0000FFFF) * (opus_int32)((opus_int16)(c32))) >> 16))) +#endif + +/* (a32 * (b32 >> 16)) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMULWT(a32, b32) ((opus_int32)(((a32) * (opus_int64)((b32) >> 16)) >> 16)) +#else +#define silk_SMULWT(a32, b32) (((a32) >> 16) * ((b32) >> 16) + ((((a32) & 0x0000FFFF) * ((b32) >> 16)) >> 16)) +#endif + +/* a32 + (b32 * (c32 >> 16)) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMLAWT(a32, b32, c32) ((opus_int32)((a32) + (((b32) * ((opus_int64)(c32) >> 16)) >> 16))) +#else +#define silk_SMLAWT(a32, b32, c32) ((a32) + (((b32) >> 16) * ((c32) >> 16)) + ((((b32) & 0x0000FFFF) * ((c32) >> 16)) >> 16)) +#endif + +/* (opus_int32)((opus_int16)(a3))) * (opus_int32)((opus_int16)(b32)) output have to be 32bit int */ +#define silk_SMULBB(a32, b32) ((opus_int32)((opus_int16)(a32)) * (opus_int32)((opus_int16)(b32))) + +/* a32 + (opus_int32)((opus_int16)(b32)) * (opus_int32)((opus_int16)(c32)) output have to be 32bit int */ +#define silk_SMLABB(a32, b32, c32) ((a32) + ((opus_int32)((opus_int16)(b32))) * (opus_int32)((opus_int16)(c32))) + +/* (opus_int32)((opus_int16)(a32)) * (b32 >> 16) */ +#define silk_SMULBT(a32, b32) ((opus_int32)((opus_int16)(a32)) * ((b32) >> 16)) + +/* a32 + (opus_int32)((opus_int16)(b32)) * (c32 >> 16) */ +#define silk_SMLABT(a32, b32, c32) ((a32) + ((opus_int32)((opus_int16)(b32))) * ((c32) >> 16)) + +/* a64 + (b32 * c32) */ +#define silk_SMLAL(a64, b32, c32) (silk_ADD64((a64), ((opus_int64)(b32) * (opus_int64)(c32)))) + +/* (a32 * b32) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMULWW(a32, b32) ((opus_int32)(((opus_int64)(a32) * (b32)) >> 16)) +#else +#define silk_SMULWW(a32, b32) silk_MLA(silk_SMULWB((a32), (b32)), (a32), silk_RSHIFT_ROUND((b32), 16)) +#endif + +/* a32 + ((b32 * c32) >> 16) */ +#if OPUS_FAST_INT64 +#define silk_SMLAWW(a32, b32, c32) ((opus_int32)((a32) + (((opus_int64)(b32) * (c32)) >> 16))) +#else +#define silk_SMLAWW(a32, b32, c32) silk_MLA(silk_SMLAWB((a32), (b32), (c32)), (b32), silk_RSHIFT_ROUND((c32), 16)) +#endif + +/* add/subtract with output saturated */ +#define silk_ADD_SAT32(a, b) ((((opus_uint32)(a) + (opus_uint32)(b)) & 0x80000000) == 0 ? \ + ((((a) & (b)) & 0x80000000) != 0 ? silk_int32_MIN : (a)+(b)) : \ + ((((a) | (b)) & 0x80000000) == 0 ? silk_int32_MAX : (a)+(b)) ) + +#define silk_SUB_SAT32(a, b) ((((opus_uint32)(a)-(opus_uint32)(b)) & 0x80000000) == 0 ? \ + (( (a) & ((b)^0x80000000) & 0x80000000) ? silk_int32_MIN : (a)-(b)) : \ + ((((a)^0x80000000) & (b) & 0x80000000) ? silk_int32_MAX : (a)-(b)) ) + +#if defined(MIPSr1_ASM) +#include "mips/macros_mipsr1.h" +#endif + +#include "ecintrin.h" +#ifndef OVERRIDE_silk_CLZ16 +static OPUS_INLINE opus_int32 silk_CLZ16(opus_int16 in16) +{ + return 32 - EC_ILOG(in16<<16|0x8000); +} +#endif + +#ifndef OVERRIDE_silk_CLZ32 +static OPUS_INLINE opus_int32 silk_CLZ32(opus_int32 in32) +{ + return in32 ? 32 - EC_ILOG(in32) : 32; +} +#endif + +/* Row based */ +#define matrix_ptr(Matrix_base_adr, row, column, N) \ + (*((Matrix_base_adr) + ((row)*(N)+(column)))) +#define matrix_adr(Matrix_base_adr, row, column, N) \ + ((Matrix_base_adr) + ((row)*(N)+(column))) + +/* Column based */ +#ifndef matrix_c_ptr +# define matrix_c_ptr(Matrix_base_adr, row, column, M) \ + (*((Matrix_base_adr) + ((row)+(M)*(column)))) +#endif + +#ifdef OPUS_ARM_INLINE_ASM +#include "arm/macros_armv4.h" +#endif + +#ifdef OPUS_ARM_INLINE_EDSP +#include "arm/macros_armv5e.h" +#endif + +#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR +#include "arm/macros_arm64.h" +#endif + +#endif /* SILK_MACROS_H */ + diff --git a/port/third_party/opus/silk/main.h b/port/third_party/opus/silk/main.h new file mode 100644 index 00000000..cd576d8c --- /dev/null +++ b/port/third_party/opus/silk/main.h @@ -0,0 +1,486 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_MAIN_H +#define SILK_MAIN_H + +#include "SigProc_FIX.h" +#include "define.h" +#include "structs.h" +#include "tables.h" +#include "PLC.h" +#include "control.h" +#include "debug.h" +#include "entenc.h" +#include "entdec.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +#include "x86/main_sse.h" +#endif + +#if (defined(OPUS_ARM_ASM) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) +#include "arm/NSQ_del_dec_arm.h" +#endif + +/* Convert Left/Right stereo signal to adaptive Mid/Side representation */ +void silk_stereo_LR_to_MS( + stereo_enc_state *state, /* I/O State */ + opus_int16 x1[], /* I/O Left input signal, becomes mid signal */ + opus_int16 x2[], /* I/O Right input signal, becomes side signal */ + opus_int8 ix[ 2 ][ 3 ], /* O Quantization indices */ + opus_int8 *mid_only_flag, /* O Flag: only mid signal coded */ + opus_int32 mid_side_rates_bps[], /* O Bitrates for mid and side signals */ + opus_int32 total_rate_bps, /* I Total bitrate */ + opus_int prev_speech_act_Q8, /* I Speech activity level in previous frame */ + opus_int toMono, /* I Last frame before a stereo->mono transition */ + opus_int fs_kHz, /* I Sample rate (kHz) */ + opus_int frame_length /* I Number of samples */ +); + +/* Convert adaptive Mid/Side representation to Left/Right stereo signal */ +void silk_stereo_MS_to_LR( + stereo_dec_state *state, /* I/O State */ + opus_int16 x1[], /* I/O Left input signal, becomes mid signal */ + opus_int16 x2[], /* I/O Right input signal, becomes side signal */ + const opus_int32 pred_Q13[], /* I Predictors */ + opus_int fs_kHz, /* I Samples rate (kHz) */ + opus_int frame_length /* I Number of samples */ +); + +/* Find least-squares prediction gain for one signal based on another and quantize it */ +opus_int32 silk_stereo_find_predictor( /* O Returns predictor in Q13 */ + opus_int32 *ratio_Q14, /* O Ratio of residual and mid energies */ + const opus_int16 x[], /* I Basis signal */ + const opus_int16 y[], /* I Target signal */ + opus_int32 mid_res_amp_Q0[], /* I/O Smoothed mid, residual norms */ + opus_int length, /* I Number of samples */ + opus_int smooth_coef_Q16 /* I Smoothing coefficient */ +); + +/* Quantize mid/side predictors */ +void silk_stereo_quant_pred( + opus_int32 pred_Q13[], /* I/O Predictors (out: quantized) */ + opus_int8 ix[ 2 ][ 3 ] /* O Quantization indices */ +); + +/* Entropy code the mid/side quantization indices */ +void silk_stereo_encode_pred( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int8 ix[ 2 ][ 3 ] /* I Quantization indices */ +); + +/* Entropy code the mid-only flag */ +void silk_stereo_encode_mid_only( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int8 mid_only_flag +); + +/* Decode mid/side predictors */ +void silk_stereo_decode_pred( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int32 pred_Q13[] /* O Predictors */ +); + +/* Decode mid-only flag */ +void silk_stereo_decode_mid_only( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int *decode_only_mid /* O Flag that only mid channel has been coded */ +); + +/* Encodes signs of excitation */ +void silk_encode_signs( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + const opus_int8 pulses[], /* I pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ +); + +/* Decodes signs of excitation */ +void silk_decode_signs( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* I/O pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ +); + +/* Check encoder control struct */ +opus_int check_control_input( + silk_EncControlStruct *encControl /* I Control structure */ +); + +/* Control internal sampling rate */ +opus_int silk_control_audio_bandwidth( + silk_encoder_state *psEncC, /* I/O Pointer to Silk encoder state */ + silk_EncControlStruct *encControl /* I Control structure */ +); + +/* Control SNR of redidual quantizer */ +opus_int silk_control_SNR( + silk_encoder_state *psEncC, /* I/O Pointer to Silk encoder state */ + opus_int32 TargetRate_bps /* I Target max bitrate (bps) */ +); + +/***************/ +/* Shell coder */ +/***************/ + +/* Encode quantization indices of excitation */ +void silk_encode_pulses( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I quantOffsetType */ + opus_int8 pulses[], /* I quantization indices */ + const opus_int frame_length /* I Frame length */ +); + +/* Shell encoder, operates on one shell code frame of 16 pulses */ +void silk_shell_encoder( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int *pulses0 /* I data: nonnegative pulse amplitudes */ +); + +/* Shell decoder, operates on one shell code frame of 16 pulses */ +void silk_shell_decoder( + opus_int16 *pulses0, /* O data: nonnegative pulse amplitudes */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + const opus_int pulses4 /* I number of pulses per pulse-subframe */ +); + +/* Gain scalar quantization with hysteresis, uniform on log scale */ +void silk_gains_quant( + opus_int8 ind[ MAX_NB_SUBFR ], /* O gain indices */ + opus_int32 gain_Q16[ MAX_NB_SUBFR ], /* I/O gains (quantized out) */ + opus_int8 *prev_ind, /* I/O last index in previous frame */ + const opus_int conditional, /* I first gain is delta coded if 1 */ + const opus_int nb_subfr /* I number of subframes */ +); + +/* Gains scalar dequantization, uniform on log scale */ +void silk_gains_dequant( + opus_int32 gain_Q16[ MAX_NB_SUBFR ], /* O quantized gains */ + const opus_int8 ind[ MAX_NB_SUBFR ], /* I gain indices */ + opus_int8 *prev_ind, /* I/O last index in previous frame */ + const opus_int conditional, /* I first gain is delta coded if 1 */ + const opus_int nb_subfr /* I number of subframes */ +); + +/* Compute unique identifier of gain indices vector */ +opus_int32 silk_gains_ID( /* O returns unique identifier of gains */ + const opus_int8 ind[ MAX_NB_SUBFR ], /* I gain indices */ + const opus_int nb_subfr /* I number of subframes */ +); + +/* Interpolate two vectors */ +void silk_interpolate( + opus_int16 xi[ MAX_LPC_ORDER ], /* O interpolated vector */ + const opus_int16 x0[ MAX_LPC_ORDER ], /* I first vector */ + const opus_int16 x1[ MAX_LPC_ORDER ], /* I second vector */ + const opus_int ifact_Q2, /* I interp. factor, weight on 2nd vector */ + const opus_int d /* I number of parameters */ +); + +/* LTP tap quantizer */ +void silk_quant_LTP_gains( + opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ + opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook Index */ + opus_int8 *periodicity_index, /* O Periodicity Index */ + opus_int32 *sum_gain_dB_Q7, /* I/O Cumulative max prediction gain */ + opus_int *pred_gain_dB_Q7, /* O LTP prediction gain */ + const opus_int32 XX_Q17[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ], /* I Correlation matrix in Q18 */ + const opus_int32 xX_Q17[ MAX_NB_SUBFR*LTP_ORDER ], /* I Correlation vector in Q18 */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +); + +/* Entropy constrained matrix-weighted VQ, for a single input data vector */ +void silk_VQ_WMat_EC_c( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int subfr_len, /* I number of samples per subframe */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + const opus_int L /* I number of vectors in codebook */ +); + +#if !defined(OVERRIDE_silk_VQ_WMat_EC) +#define silk_VQ_WMat_EC(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, subfr_len, max_gain_Q7, L, arch) \ + ((void)(arch),silk_VQ_WMat_EC_c(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, subfr_len, max_gain_Q7, L)) +#endif + +/************************************/ +/* Noise shaping quantization (NSQ) */ +/************************************/ + +void silk_NSQ_c( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +#if !defined(OVERRIDE_silk_NSQ) +#define silk_NSQ(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_c(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) +#endif + +/* Noise shaping using delayed decision */ +void silk_NSQ_del_dec_c( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +#if !defined(OVERRIDE_silk_NSQ_del_dec) +#define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_del_dec_c(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) +#endif + +/************/ +/* Silk VAD */ +/************/ +/* Initialize the Silk VAD */ +opus_int silk_VAD_Init( /* O Return value, 0 if success */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +); + +/* Get speech activity level in Q8 */ +opus_int silk_VAD_GetSA_Q8_c( /* O Return value, 0 if success */ + silk_encoder_state *psEncC, /* I/O Encoder state */ + const opus_int16 pIn[] /* I PCM input */ +); + +#if !defined(OVERRIDE_silk_VAD_GetSA_Q8) +#define silk_VAD_GetSA_Q8(psEnC, pIn, arch) ((void)(arch),silk_VAD_GetSA_Q8_c(psEnC, pIn)) +#endif + +/* Low-pass filter with variable cutoff frequency based on */ +/* piece-wise linear interpolation between elliptic filters */ +/* Start by setting transition_frame_no = 1; */ +void silk_LP_variable_cutoff( + silk_LP_state *psLP, /* I/O LP filter state */ + opus_int16 *frame, /* I/O Low-pass filtered output signal */ + const opus_int frame_length /* I Frame length */ +); + +/******************/ +/* NLSF Quantizer */ +/******************/ +/* Limit, stabilize, convert and quantize NLSFs */ +void silk_process_NLSFs( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ], /* O Prediction coefficients */ + opus_int16 pNLSF_Q15[ MAX_LPC_ORDER ], /* I/O Normalized LSFs (quant out) (0 - (2^15-1)) */ + const opus_int16 prev_NLSFq_Q15[ MAX_LPC_ORDER ] /* I Previous Normalized LSFs (0 - (2^15-1)) */ +); + +opus_int32 silk_NLSF_encode( /* O Returns RD value in Q25 */ + opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ + opus_int16 *pNLSF_Q15, /* I/O Quantized NLSF vector [ LPC_ORDER ] */ + const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ + const opus_int16 *pW_QW, /* I NLSF weight vector [ LPC_ORDER ] */ + const opus_int NLSF_mu_Q20, /* I Rate weight for the RD optimization */ + const opus_int nSurvivors, /* I Max survivors after first stage */ + const opus_int signalType /* I Signal type: 0/1/2 */ +); + +/* Compute quantization errors for an LPC_order element input vector for a VQ codebook */ +void silk_NLSF_VQ( + opus_int32 err_Q26[], /* O Quantization errors [K] */ + const opus_int16 in_Q15[], /* I Input vectors to be quantized [LPC_order] */ + const opus_uint8 pCB_Q8[], /* I Codebook vectors [K*LPC_order] */ + const opus_int16 pWght_Q9[], /* I Codebook weights [K*LPC_order] */ + const opus_int K, /* I Number of codebook vectors */ + const opus_int LPC_order /* I Number of LPCs */ +); + +/* Delayed-decision quantizer for NLSF residuals */ +opus_int32 silk_NLSF_del_dec_quant( /* O Returns RD value in Q25 */ + opus_int8 indices[], /* O Quantization indices [ order ] */ + const opus_int16 x_Q10[], /* I Input [ order ] */ + const opus_int16 w_Q5[], /* I Weights [ order ] */ + const opus_uint8 pred_coef_Q8[], /* I Backward predictor coefs [ order ] */ + const opus_int16 ec_ix[], /* I Indices to entropy coding tables [ order ] */ + const opus_uint8 ec_rates_Q5[], /* I Rates [] */ + const opus_int quant_step_size_Q16, /* I Quantization step size */ + const opus_int16 inv_quant_step_size_Q6, /* I Inverse quantization step size */ + const opus_int32 mu_Q20, /* I R/D tradeoff */ + const opus_int16 order /* I Number of input values */ +); + +/* Unpack predictor values and indices for entropy coding tables */ +void silk_NLSF_unpack( + opus_int16 ec_ix[], /* O Indices to entropy tables [ LPC_ORDER ] */ + opus_uint8 pred_Q8[], /* O LSF predictor [ LPC_ORDER ] */ + const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ + const opus_int CB1_index /* I Index of vector in first LSF codebook */ +); + +/***********************/ +/* NLSF vector decoder */ +/***********************/ +void silk_NLSF_decode( + opus_int16 *pNLSF_Q15, /* O Quantized NLSF vector [ LPC_ORDER ] */ + opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ + const silk_NLSF_CB_struct *psNLSF_CB /* I Codebook object */ +); + +/****************************************************/ +/* Decoder Functions */ +/****************************************************/ +opus_int silk_reset_decoder( + silk_decoder_state *psDec /* I/O Decoder state pointer */ +); + +opus_int silk_init_decoder( + silk_decoder_state *psDec /* I/O Decoder state pointer */ +); + +/* Set decoder sampling rate */ +opus_int silk_decoder_set_fs( + silk_decoder_state *psDec, /* I/O Decoder state pointer */ + opus_int fs_kHz, /* I Sampling frequency (kHz) */ + opus_int32 fs_API_Hz /* I API Sampling frequency (Hz) */ +); + +/****************/ +/* Decode frame */ +/****************/ +opus_int silk_decode_frame( + silk_decoder_state *psDec, /* I/O Pointer to Silk decoder state */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pOut[], /* O Pointer to output speech frame */ + opus_int32 *pN, /* O Pointer to size of output frame */ + opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ + opus_int condCoding, /* I The type of conditional coding to use */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif +#ifdef ENABLE_OSCE + OSCEModel *osce_model, +#endif + int arch /* I Run-time architecture */ +); + +/* Decode indices from bitstream */ +void silk_decode_indices( + silk_decoder_state *psDec, /* I/O State */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int FrameIndex, /* I Frame number */ + opus_int decode_LBRR, /* I Flag indicating LBRR data is being decoded */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/* Decode parameters from payload */ +void silk_decode_parameters( + silk_decoder_state *psDec, /* I/O State */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/* Core decoder. Performs inverse NSQ operation LTP + LPC */ +void silk_decode_core( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I Decoder control */ + opus_int16 xq[], /* O Decoded speech */ + const opus_int16 pulses[ MAX_FRAME_LENGTH ], /* I Pulse signal */ + int arch /* I Run-time architecture */ +); + +/* Decode quantization indices of excitation (Shell coding) */ +void silk_decode_pulses( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* O Excitation signal */ + const opus_int signalType, /* I Sigtype */ + const opus_int quantOffsetType, /* I quantOffsetType */ + const opus_int frame_length /* I Frame length */ +); + +/******************/ +/* CNG */ +/******************/ + +/* Reset CNG */ +void silk_CNG_Reset( + silk_decoder_state *psDec /* I/O Decoder state */ +); + +/* Updates CNG estimate, and applies the CNG when packet was lost */ +void silk_CNG( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* I/O Signal */ + opus_int length /* I Length of residual */ +); + +/* Encoding of various parameters */ +void silk_encode_indices( + silk_encoder_state *psEncC, /* I/O Encoder state */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int FrameIndex, /* I Frame number */ + opus_int encode_LBRR, /* I Flag indicating LBRR data is being encoded */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +#endif diff --git a/port/third_party/opus/silk/pitch_est_defines.h b/port/third_party/opus/silk/pitch_est_defines.h new file mode 100644 index 00000000..e1e4b5d7 --- /dev/null +++ b/port/third_party/opus/silk/pitch_est_defines.h @@ -0,0 +1,88 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_PE_DEFINES_H +#define SILK_PE_DEFINES_H + +#include "SigProc_FIX.h" + +/********************************************************/ +/* Definitions for pitch estimator */ +/********************************************************/ + +#define PE_MAX_FS_KHZ 16 /* Maximum sampling frequency used */ + +#define PE_MAX_NB_SUBFR 4 +#define PE_SUBFR_LENGTH_MS 5 /* 5 ms */ + +#define PE_LTP_MEM_LENGTH_MS ( 4 * PE_SUBFR_LENGTH_MS ) + +#define PE_MAX_FRAME_LENGTH_MS ( PE_LTP_MEM_LENGTH_MS + PE_MAX_NB_SUBFR * PE_SUBFR_LENGTH_MS ) +#define PE_MAX_FRAME_LENGTH ( PE_MAX_FRAME_LENGTH_MS * PE_MAX_FS_KHZ ) +#define PE_MAX_FRAME_LENGTH_ST_1 ( PE_MAX_FRAME_LENGTH >> 2 ) +#define PE_MAX_FRAME_LENGTH_ST_2 ( PE_MAX_FRAME_LENGTH >> 1 ) + +#define PE_MAX_LAG_MS 18 /* 18 ms -> 56 Hz */ +#define PE_MIN_LAG_MS 2 /* 2 ms -> 500 Hz */ +#define PE_MAX_LAG ( PE_MAX_LAG_MS * PE_MAX_FS_KHZ ) +#define PE_MIN_LAG ( PE_MIN_LAG_MS * PE_MAX_FS_KHZ ) + +#define PE_D_SRCH_LENGTH 24 + +#define PE_NB_STAGE3_LAGS 5 + +#define PE_NB_CBKS_STAGE2 3 +#define PE_NB_CBKS_STAGE2_EXT 11 + +#define PE_NB_CBKS_STAGE3_MAX 34 +#define PE_NB_CBKS_STAGE3_MID 24 +#define PE_NB_CBKS_STAGE3_MIN 16 + +#define PE_NB_CBKS_STAGE3_10MS 12 +#define PE_NB_CBKS_STAGE2_10MS 3 + +#define PE_SHORTLAG_BIAS 0.2f /* for logarithmic weighting */ +#define PE_PREVLAG_BIAS 0.2f /* for logarithmic weighting */ +#define PE_FLATCONTOUR_BIAS 0.05f + +#define SILK_PE_MIN_COMPLEX 0 +#define SILK_PE_MID_COMPLEX 1 +#define SILK_PE_MAX_COMPLEX 2 + +/* Tables for 20 ms frames */ +extern const opus_int8 silk_CB_lags_stage2[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE2_EXT ]; +extern const opus_int8 silk_CB_lags_stage3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ]; +extern const opus_int8 silk_Lag_range_stage3[ SILK_PE_MAX_COMPLEX + 1 ] [ PE_MAX_NB_SUBFR ][ 2 ]; +extern const opus_int8 silk_nb_cbk_searchs_stage3[ SILK_PE_MAX_COMPLEX + 1 ]; + +/* Tables for 10 ms frames */ +extern const opus_int8 silk_CB_lags_stage2_10_ms[ PE_MAX_NB_SUBFR >> 1][ 3 ]; +extern const opus_int8 silk_CB_lags_stage3_10_ms[ PE_MAX_NB_SUBFR >> 1 ][ 12 ]; +extern const opus_int8 silk_Lag_range_stage3_10_ms[ PE_MAX_NB_SUBFR >> 1 ][ 2 ]; + +#endif + diff --git a/port/third_party/opus/silk/pitch_est_tables.c b/port/third_party/opus/silk/pitch_est_tables.c new file mode 100644 index 00000000..81a8baca --- /dev/null +++ b/port/third_party/opus/silk/pitch_est_tables.c @@ -0,0 +1,99 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "typedef.h" +#include "pitch_est_defines.h" + +const opus_int8 silk_CB_lags_stage2_10_ms[ PE_MAX_NB_SUBFR >> 1][ PE_NB_CBKS_STAGE2_10MS ] = +{ + {0, 1, 0}, + {0, 0, 1} +}; + +const opus_int8 silk_CB_lags_stage3_10_ms[ PE_MAX_NB_SUBFR >> 1 ][ PE_NB_CBKS_STAGE3_10MS ] = +{ + { 0, 0, 1,-1, 1,-1, 2,-2, 2,-2, 3,-3}, + { 0, 1, 0, 1,-1, 2,-1, 2,-2, 3,-2, 3} +}; + +const opus_int8 silk_Lag_range_stage3_10_ms[ PE_MAX_NB_SUBFR >> 1 ][ 2 ] = +{ + {-3, 7}, + {-2, 7} +}; + +const opus_int8 silk_CB_lags_stage2[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE2_EXT ] = +{ + {0, 2,-1,-1,-1, 0, 0, 1, 1, 0, 1}, + {0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0}, + {0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0}, + {0,-1, 2, 1, 0, 1, 1, 0, 0,-1,-1} +}; + +const opus_int8 silk_CB_lags_stage3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ] = +{ + {0, 0, 1,-1, 0, 1,-1, 0,-1, 1,-2, 2,-2,-2, 2,-3, 2, 3,-3,-4, 3,-4, 4, 4,-5, 5,-6,-5, 6,-7, 6, 5, 8,-9}, + {0, 0, 1, 0, 0, 0, 0, 0, 0, 0,-1, 1, 0, 0, 1,-1, 0, 1,-1,-1, 1,-1, 2, 1,-1, 2,-2,-2, 2,-2, 2, 2, 3,-3}, + {0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1,-1, 1, 0, 0, 2, 1,-1, 2,-1,-1, 2,-1, 2, 2,-1, 3,-2,-2,-2, 3}, + {0, 1, 0, 0, 1, 0, 1,-1, 2,-1, 2,-1, 2, 3,-2, 3,-2,-2, 4, 4,-3, 5,-3,-4, 6,-4, 6, 5,-5, 8,-6,-5,-7, 9} +}; + +const opus_int8 silk_Lag_range_stage3[ SILK_PE_MAX_COMPLEX + 1 ] [ PE_MAX_NB_SUBFR ][ 2 ] = +{ + /* Lags to search for low number of stage3 cbks */ + { + {-5,8}, + {-1,6}, + {-1,6}, + {-4,10} + }, + /* Lags to search for middle number of stage3 cbks */ + { + {-6,10}, + {-2,6}, + {-1,6}, + {-5,10} + }, + /* Lags to search for max number of stage3 cbks */ + { + {-9,12}, + {-3,7}, + {-2,7}, + {-7,13} + } +}; + +const opus_int8 silk_nb_cbk_searchs_stage3[ SILK_PE_MAX_COMPLEX + 1 ] = +{ + PE_NB_CBKS_STAGE3_MIN, + PE_NB_CBKS_STAGE3_MID, + PE_NB_CBKS_STAGE3_MAX +}; diff --git a/port/third_party/opus/silk/process_NLSFs.c b/port/third_party/opus/silk/process_NLSFs.c new file mode 100644 index 00000000..d1308095 --- /dev/null +++ b/port/third_party/opus/silk/process_NLSFs.c @@ -0,0 +1,107 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Limit, stabilize, convert and quantize NLSFs */ +void silk_process_NLSFs( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ], /* O Prediction coefficients */ + opus_int16 pNLSF_Q15[ MAX_LPC_ORDER ], /* I/O Normalized LSFs (quant out) (0 - (2^15-1)) */ + const opus_int16 prev_NLSFq_Q15[ MAX_LPC_ORDER ] /* I Previous Normalized LSFs (0 - (2^15-1)) */ +) +{ + opus_int i, doInterpolate; + opus_int NLSF_mu_Q20; + opus_int16 i_sqr_Q15; + opus_int16 pNLSF0_temp_Q15[ MAX_LPC_ORDER ]; + opus_int16 pNLSFW_QW[ MAX_LPC_ORDER ]; + opus_int16 pNLSFW0_temp_QW[ MAX_LPC_ORDER ]; + + silk_assert( psEncC->speech_activity_Q8 >= 0 ); + silk_assert( psEncC->speech_activity_Q8 <= SILK_FIX_CONST( 1.0, 8 ) ); + celt_assert( psEncC->useInterpolatedNLSFs == 1 || psEncC->indices.NLSFInterpCoef_Q2 == ( 1 << 2 ) ); + + /***********************/ + /* Calculate mu values */ + /***********************/ + /* NLSF_mu = 0.003 - 0.0015 * psEnc->speech_activity; */ + NLSF_mu_Q20 = silk_SMLAWB( SILK_FIX_CONST( 0.003, 20 ), SILK_FIX_CONST( -0.001, 28 ), psEncC->speech_activity_Q8 ); + if( psEncC->nb_subfr == 2 ) { + /* Multiply by 1.5 for 10 ms packets */ + NLSF_mu_Q20 = silk_ADD_RSHIFT( NLSF_mu_Q20, NLSF_mu_Q20, 1 ); + } + + celt_assert( NLSF_mu_Q20 > 0 ); + silk_assert( NLSF_mu_Q20 <= SILK_FIX_CONST( 0.005, 20 ) ); + + /* Calculate NLSF weights */ + silk_NLSF_VQ_weights_laroia( pNLSFW_QW, pNLSF_Q15, psEncC->predictLPCOrder ); + + /* Update NLSF weights for interpolated NLSFs */ + doInterpolate = ( psEncC->useInterpolatedNLSFs == 1 ) && ( psEncC->indices.NLSFInterpCoef_Q2 < 4 ); + if( doInterpolate ) { + /* Calculate the interpolated NLSF vector for the first half */ + silk_interpolate( pNLSF0_temp_Q15, prev_NLSFq_Q15, pNLSF_Q15, + psEncC->indices.NLSFInterpCoef_Q2, psEncC->predictLPCOrder ); + + /* Calculate first half NLSF weights for the interpolated NLSFs */ + silk_NLSF_VQ_weights_laroia( pNLSFW0_temp_QW, pNLSF0_temp_Q15, psEncC->predictLPCOrder ); + + /* Update NLSF weights with contribution from first half */ + i_sqr_Q15 = silk_LSHIFT( silk_SMULBB( psEncC->indices.NLSFInterpCoef_Q2, psEncC->indices.NLSFInterpCoef_Q2 ), 11 ); + for( i = 0; i < psEncC->predictLPCOrder; i++ ) { + pNLSFW_QW[ i ] = silk_ADD16( silk_RSHIFT( pNLSFW_QW[ i ], 1 ), silk_RSHIFT( + silk_SMULBB( pNLSFW0_temp_QW[ i ], i_sqr_Q15 ), 16) ); + silk_assert( pNLSFW_QW[ i ] >= 1 ); + } + } + + silk_NLSF_encode( psEncC->indices.NLSFIndices, pNLSF_Q15, psEncC->psNLSF_CB, pNLSFW_QW, + NLSF_mu_Q20, psEncC->NLSF_MSVQ_Survivors, psEncC->indices.signalType ); + + /* Convert quantized NLSFs back to LPC coefficients */ + silk_NLSF2A( PredCoef_Q12[ 1 ], pNLSF_Q15, psEncC->predictLPCOrder, psEncC->arch ); + + if( doInterpolate ) { + /* Calculate the interpolated, quantized LSF vector for the first half */ + silk_interpolate( pNLSF0_temp_Q15, prev_NLSFq_Q15, pNLSF_Q15, + psEncC->indices.NLSFInterpCoef_Q2, psEncC->predictLPCOrder ); + + /* Convert back to LPC coefficients */ + silk_NLSF2A( PredCoef_Q12[ 0 ], pNLSF0_temp_Q15, psEncC->predictLPCOrder, psEncC->arch ); + + } else { + /* Copy LPC coefficients for first half from second half */ + celt_assert( psEncC->predictLPCOrder <= MAX_LPC_ORDER ); + silk_memcpy( PredCoef_Q12[ 0 ], PredCoef_Q12[ 1 ], psEncC->predictLPCOrder * sizeof( opus_int16 ) ); + } +} diff --git a/port/third_party/opus/silk/quant_LTP_gains.c b/port/third_party/opus/silk/quant_LTP_gains.c new file mode 100644 index 00000000..d6b8eff8 --- /dev/null +++ b/port/third_party/opus/silk/quant_LTP_gains.c @@ -0,0 +1,132 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "tuning_parameters.h" + +void silk_quant_LTP_gains( + opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ + opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook Index */ + opus_int8 *periodicity_index, /* O Periodicity Index */ + opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ + opus_int *pred_gain_dB_Q7, /* O LTP prediction gain */ + const opus_int32 XX_Q17[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ], /* I Correlation matrix in Q18 */ + const opus_int32 xX_Q17[ MAX_NB_SUBFR*LTP_ORDER ], /* I Correlation vector in Q18 */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +) +{ + opus_int j, k, cbk_size; + opus_int8 temp_idx[ MAX_NB_SUBFR ]; + const opus_uint8 *cl_ptr_Q5; + const opus_int8 *cbk_ptr_Q7; + const opus_uint8 *cbk_gain_ptr_Q7; + const opus_int32 *XX_Q17_ptr, *xX_Q17_ptr; + opus_int32 res_nrg_Q15_subfr, res_nrg_Q15, rate_dist_Q7_subfr, rate_dist_Q7, min_rate_dist_Q7; + opus_int32 sum_log_gain_tmp_Q7, best_sum_log_gain_Q7, max_gain_Q7; + opus_int gain_Q7; + + /***************************************************/ + /* iterate over different codebooks with different */ + /* rates/distortions, and choose best */ + /***************************************************/ + min_rate_dist_Q7 = silk_int32_MAX; + best_sum_log_gain_Q7 = 0; + for( k = 0; k < 3; k++ ) { + /* Safety margin for pitch gain control, to take into account factors + such as state rescaling/rewhitening. */ + opus_int32 gain_safety = SILK_FIX_CONST( 0.4, 7 ); + + cl_ptr_Q5 = silk_LTP_gain_BITS_Q5_ptrs[ k ]; + cbk_ptr_Q7 = silk_LTP_vq_ptrs_Q7[ k ]; + cbk_gain_ptr_Q7 = silk_LTP_vq_gain_ptrs_Q7[ k ]; + cbk_size = silk_LTP_vq_sizes[ k ]; + + /* Set up pointers to first subframe */ + XX_Q17_ptr = XX_Q17; + xX_Q17_ptr = xX_Q17; + + res_nrg_Q15 = 0; + rate_dist_Q7 = 0; + sum_log_gain_tmp_Q7 = *sum_log_gain_Q7; + for( j = 0; j < nb_subfr; j++ ) { + max_gain_Q7 = silk_log2lin( ( SILK_FIX_CONST( MAX_SUM_LOG_GAIN_DB / 6.0, 7 ) - sum_log_gain_tmp_Q7 ) + + SILK_FIX_CONST( 7, 7 ) ) - gain_safety; + silk_VQ_WMat_EC( + &temp_idx[ j ], /* O index of best codebook vector */ + &res_nrg_Q15_subfr, /* O residual energy */ + &rate_dist_Q7_subfr, /* O best weighted quantization error + mu * rate */ + &gain_Q7, /* O sum of absolute LTP coefficients */ + XX_Q17_ptr, /* I correlation matrix */ + xX_Q17_ptr, /* I correlation vector */ + cbk_ptr_Q7, /* I codebook */ + cbk_gain_ptr_Q7, /* I codebook effective gains */ + cl_ptr_Q5, /* I code length for each codebook vector */ + subfr_len, /* I number of samples per subframe */ + max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + cbk_size, /* I number of vectors in codebook */ + arch /* I Run-time architecture */ + ); + + res_nrg_Q15 = silk_ADD_POS_SAT32( res_nrg_Q15, res_nrg_Q15_subfr ); + rate_dist_Q7 = silk_ADD_POS_SAT32( rate_dist_Q7, rate_dist_Q7_subfr ); + sum_log_gain_tmp_Q7 = silk_max(0, sum_log_gain_tmp_Q7 + + silk_lin2log( gain_safety + gain_Q7 ) - SILK_FIX_CONST( 7, 7 )); + + XX_Q17_ptr += LTP_ORDER * LTP_ORDER; + xX_Q17_ptr += LTP_ORDER; + } + + if( rate_dist_Q7 <= min_rate_dist_Q7 ) { + min_rate_dist_Q7 = rate_dist_Q7; + *periodicity_index = (opus_int8)k; + silk_memcpy( cbk_index, temp_idx, nb_subfr * sizeof( opus_int8 ) ); + best_sum_log_gain_Q7 = sum_log_gain_tmp_Q7; + } + } + + cbk_ptr_Q7 = silk_LTP_vq_ptrs_Q7[ *periodicity_index ]; + for( j = 0; j < nb_subfr; j++ ) { + for( k = 0; k < LTP_ORDER; k++ ) { + B_Q14[ j * LTP_ORDER + k ] = silk_LSHIFT( cbk_ptr_Q7[ cbk_index[ j ] * LTP_ORDER + k ], 7 ); + } + } + + if( nb_subfr == 2 ) { + res_nrg_Q15 = silk_RSHIFT32( res_nrg_Q15, 1 ); + } else { + res_nrg_Q15 = silk_RSHIFT32( res_nrg_Q15, 2 ); + } + + *sum_log_gain_Q7 = best_sum_log_gain_Q7; + *pred_gain_dB_Q7 = (opus_int)silk_SMULBB( -3, silk_lin2log( res_nrg_Q15 ) - ( 15 << 7 ) ); +} diff --git a/port/third_party/opus/silk/resampler.c b/port/third_party/opus/silk/resampler.c new file mode 100644 index 00000000..1f11e508 --- /dev/null +++ b/port/third_party/opus/silk/resampler.c @@ -0,0 +1,215 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* + * Matrix of resampling methods used: + * Fs_out (kHz) + * 8 12 16 24 48 + * + * 8 C UF U UF UF + * 12 AF C UF U UF + * Fs_in (kHz) 16 D AF C UF UF + * 24 AF D AF C U + * 48 AF AF AF D C + * + * C -> Copy (no resampling) + * D -> Allpass-based 2x downsampling + * U -> Allpass-based 2x upsampling + * UF -> Allpass-based 2x upsampling followed by FIR interpolation + * AF -> AR2 filter followed by FIR interpolation + */ + +#include "resampler_private.h" + +/* Tables with delay compensation values to equalize total delay for different modes */ +static const opus_int8 delay_matrix_enc[ 5 ][ 3 ] = { +/* in \ out 8 12 16 */ +/* 8 */ { 6, 0, 3 }, +/* 12 */ { 0, 7, 3 }, +/* 16 */ { 0, 1, 10 }, +/* 24 */ { 0, 2, 6 }, +/* 48 */ { 18, 10, 12 } +}; + +static const opus_int8 delay_matrix_dec[ 3 ][ 5 ] = { +/* in \ out 8 12 16 24 48 */ +/* 8 */ { 4, 0, 2, 0, 0 }, +/* 12 */ { 0, 9, 4, 7, 4 }, +/* 16 */ { 0, 3, 12, 7, 7 } +}; + +/* Simple way to make [8000, 12000, 16000, 24000, 48000] to [0, 1, 2, 3, 4] */ +#define rateID(R) ( ( ( ((R)>>12) - ((R)>16000) ) >> ((R)>24000) ) - 1 ) + +#define USE_silk_resampler_copy (0) +#define USE_silk_resampler_private_up2_HQ_wrapper (1) +#define USE_silk_resampler_private_IIR_FIR (2) +#define USE_silk_resampler_private_down_FIR (3) + +/* Initialize/reset the resampler state for a given pair of input/output sampling rates */ +opus_int silk_resampler_init( + silk_resampler_state_struct *S, /* I/O Resampler state */ + opus_int32 Fs_Hz_in, /* I Input sampling rate (Hz) */ + opus_int32 Fs_Hz_out, /* I Output sampling rate (Hz) */ + opus_int forEnc /* I If 1: encoder; if 0: decoder */ +) +{ + opus_int up2x; + + /* Clear state */ + silk_memset( S, 0, sizeof( silk_resampler_state_struct ) ); + + /* Input checking */ + if( forEnc ) { + if( ( Fs_Hz_in != 8000 && Fs_Hz_in != 12000 && Fs_Hz_in != 16000 && Fs_Hz_in != 24000 && Fs_Hz_in != 48000 ) || + ( Fs_Hz_out != 8000 && Fs_Hz_out != 12000 && Fs_Hz_out != 16000 ) ) { + celt_assert( 0 ); + return -1; + } + S->inputDelay = delay_matrix_enc[ rateID( Fs_Hz_in ) ][ rateID( Fs_Hz_out ) ]; + } else { + if( ( Fs_Hz_in != 8000 && Fs_Hz_in != 12000 && Fs_Hz_in != 16000 ) || + ( Fs_Hz_out != 8000 && Fs_Hz_out != 12000 && Fs_Hz_out != 16000 && Fs_Hz_out != 24000 && Fs_Hz_out != 48000 ) ) { + celt_assert( 0 ); + return -1; + } + S->inputDelay = delay_matrix_dec[ rateID( Fs_Hz_in ) ][ rateID( Fs_Hz_out ) ]; + } + + S->Fs_in_kHz = silk_DIV32_16( Fs_Hz_in, 1000 ); + S->Fs_out_kHz = silk_DIV32_16( Fs_Hz_out, 1000 ); + + /* Number of samples processed per batch */ + S->batchSize = S->Fs_in_kHz * RESAMPLER_MAX_BATCH_SIZE_MS; + + /* Find resampler with the right sampling ratio */ + up2x = 0; + if( Fs_Hz_out > Fs_Hz_in ) { + /* Upsample */ + if( Fs_Hz_out == silk_MUL( Fs_Hz_in, 2 ) ) { /* Fs_out : Fs_in = 2 : 1 */ + /* Special case: directly use 2x upsampler */ + S->resampler_function = USE_silk_resampler_private_up2_HQ_wrapper; + } else { + /* Default resampler */ + S->resampler_function = USE_silk_resampler_private_IIR_FIR; + up2x = 1; + } + } else if ( Fs_Hz_out < Fs_Hz_in ) { + /* Downsample */ + S->resampler_function = USE_silk_resampler_private_down_FIR; + if( silk_MUL( Fs_Hz_out, 4 ) == silk_MUL( Fs_Hz_in, 3 ) ) { /* Fs_out : Fs_in = 3 : 4 */ + S->FIR_Fracs = 3; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR0; + S->Coefs = silk_Resampler_3_4_COEFS; + } else if( silk_MUL( Fs_Hz_out, 3 ) == silk_MUL( Fs_Hz_in, 2 ) ) { /* Fs_out : Fs_in = 2 : 3 */ + S->FIR_Fracs = 2; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR0; + S->Coefs = silk_Resampler_2_3_COEFS; + } else if( silk_MUL( Fs_Hz_out, 2 ) == Fs_Hz_in ) { /* Fs_out : Fs_in = 1 : 2 */ + S->FIR_Fracs = 1; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR1; + S->Coefs = silk_Resampler_1_2_COEFS; + } else if( silk_MUL( Fs_Hz_out, 3 ) == Fs_Hz_in ) { /* Fs_out : Fs_in = 1 : 3 */ + S->FIR_Fracs = 1; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR2; + S->Coefs = silk_Resampler_1_3_COEFS; + } else if( silk_MUL( Fs_Hz_out, 4 ) == Fs_Hz_in ) { /* Fs_out : Fs_in = 1 : 4 */ + S->FIR_Fracs = 1; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR2; + S->Coefs = silk_Resampler_1_4_COEFS; + } else if( silk_MUL( Fs_Hz_out, 6 ) == Fs_Hz_in ) { /* Fs_out : Fs_in = 1 : 6 */ + S->FIR_Fracs = 1; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR2; + S->Coefs = silk_Resampler_1_6_COEFS; + } else { + /* None available */ + celt_assert( 0 ); + return -1; + } + } else { + /* Input and output sampling rates are equal: copy */ + S->resampler_function = USE_silk_resampler_copy; + } + + /* Ratio of input/output samples */ + S->invRatio_Q16 = silk_LSHIFT32( silk_DIV32( silk_LSHIFT32( Fs_Hz_in, 14 + up2x ), Fs_Hz_out ), 2 ); + /* Make sure the ratio is rounded up */ + while( silk_SMULWW( S->invRatio_Q16, Fs_Hz_out ) < silk_LSHIFT32( Fs_Hz_in, up2x ) ) { + S->invRatio_Q16++; + } + + return 0; +} + +/* Resampler: convert from one sampling rate to another */ +/* Input and output sampling rate are at most 48000 Hz */ +opus_int silk_resampler( + silk_resampler_state_struct *S, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +) +{ + opus_int nSamples; + + /* Need at least 1 ms of input data */ + celt_assert( inLen >= S->Fs_in_kHz ); + /* Delay can't exceed the 1 ms of buffering */ + celt_assert( S->inputDelay <= S->Fs_in_kHz ); + + nSamples = S->Fs_in_kHz - S->inputDelay; + + /* Copy to delay buffer */ + silk_memcpy( &S->delayBuf[ S->inputDelay ], in, nSamples * sizeof( opus_int16 ) ); + + switch( S->resampler_function ) { + case USE_silk_resampler_private_up2_HQ_wrapper: + silk_resampler_private_up2_HQ_wrapper( S, out, S->delayBuf, S->Fs_in_kHz ); + silk_resampler_private_up2_HQ_wrapper( S, &out[ S->Fs_out_kHz ], &in[ nSamples ], inLen - S->Fs_in_kHz ); + break; + case USE_silk_resampler_private_IIR_FIR: + silk_resampler_private_IIR_FIR( S, out, S->delayBuf, S->Fs_in_kHz ); + silk_resampler_private_IIR_FIR( S, &out[ S->Fs_out_kHz ], &in[ nSamples ], inLen - S->Fs_in_kHz ); + break; + case USE_silk_resampler_private_down_FIR: + silk_resampler_private_down_FIR( S, out, S->delayBuf, S->Fs_in_kHz ); + silk_resampler_private_down_FIR( S, &out[ S->Fs_out_kHz ], &in[ nSamples ], inLen - S->Fs_in_kHz ); + break; + default: + silk_memcpy( out, S->delayBuf, S->Fs_in_kHz * sizeof( opus_int16 ) ); + silk_memcpy( &out[ S->Fs_out_kHz ], &in[ nSamples ], ( inLen - S->Fs_in_kHz ) * sizeof( opus_int16 ) ); + } + + /* Copy to delay buffer */ + silk_memcpy( S->delayBuf, &in[ inLen - S->inputDelay ], S->inputDelay * sizeof( opus_int16 ) ); + + return 0; +} diff --git a/port/third_party/opus/silk/resampler_down2.c b/port/third_party/opus/silk/resampler_down2.c new file mode 100644 index 00000000..971d7bfd --- /dev/null +++ b/port/third_party/opus/silk/resampler_down2.c @@ -0,0 +1,74 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_rom.h" + +/* Downsample by a factor 2 */ +void silk_resampler_down2( + opus_int32 *S, /* I/O State vector [ 2 ] */ + opus_int16 *out, /* O Output signal [ floor(len/2) ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 inLen /* I Number of input samples */ +) +{ + opus_int32 k, len2 = silk_RSHIFT32( inLen, 1 ); + opus_int32 in32, out32, Y, X; + + celt_assert( silk_resampler_down2_0 > 0 ); + celt_assert( silk_resampler_down2_1 < 0 ); + + /* Internal variables and state are in Q10 format */ + for( k = 0; k < len2; k++ ) { + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ 2 * k ], 10 ); + + /* All-pass section for even input sample */ + Y = silk_SUB32( in32, S[ 0 ] ); + X = silk_SMLAWB( Y, Y, silk_resampler_down2_1 ); + out32 = silk_ADD32( S[ 0 ], X ); + S[ 0 ] = silk_ADD32( in32, X ); + + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ 2 * k + 1 ], 10 ); + + /* All-pass section for odd input sample, and add to output of previous section */ + Y = silk_SUB32( in32, S[ 1 ] ); + X = silk_SMULWB( Y, silk_resampler_down2_0 ); + out32 = silk_ADD32( out32, S[ 1 ] ); + out32 = silk_ADD32( out32, X ); + S[ 1 ] = silk_ADD32( in32, X ); + + /* Add, convert back to int16 and store to output */ + out[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( out32, 11 ) ); + } +} + diff --git a/port/third_party/opus/silk/resampler_down2_3.c b/port/third_party/opus/silk/resampler_down2_3.c new file mode 100644 index 00000000..4342614d --- /dev/null +++ b/port/third_party/opus/silk/resampler_down2_3.c @@ -0,0 +1,103 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" +#include "stack_alloc.h" + +#define ORDER_FIR 4 + +/* Downsample by a factor 2/3, low quality */ +void silk_resampler_down2_3( + opus_int32 *S, /* I/O State vector [ 6 ] */ + opus_int16 *out, /* O Output signal [ floor(2*inLen/3) ] */ + const opus_int16 *in, /* I Input signal [ inLen ] */ + opus_int32 inLen /* I Number of input samples */ +) +{ + opus_int32 nSamplesIn, counter, res_Q6; + VARDECL( opus_int32, buf ); + opus_int32 *buf_ptr; + SAVE_STACK; + + ALLOC( buf, RESAMPLER_MAX_BATCH_SIZE_IN + ORDER_FIR, opus_int32 ); + + /* Copy buffered samples to start of buffer */ + silk_memcpy( buf, S, ORDER_FIR * sizeof( opus_int32 ) ); + + /* Iterate over blocks of frameSizeIn input samples */ + while( 1 ) { + nSamplesIn = silk_min( inLen, RESAMPLER_MAX_BATCH_SIZE_IN ); + + /* Second-order AR filter (output in Q8) */ + silk_resampler_private_AR2( &S[ ORDER_FIR ], &buf[ ORDER_FIR ], in, + silk_Resampler_2_3_COEFS_LQ, nSamplesIn ); + + /* Interpolate filtered signal */ + buf_ptr = buf; + counter = nSamplesIn; + while( counter > 2 ) { + /* Inner product */ + res_Q6 = silk_SMULWB( buf_ptr[ 0 ], silk_Resampler_2_3_COEFS_LQ[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 1 ], silk_Resampler_2_3_COEFS_LQ[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 2 ], silk_Resampler_2_3_COEFS_LQ[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 3 ], silk_Resampler_2_3_COEFS_LQ[ 4 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + + res_Q6 = silk_SMULWB( buf_ptr[ 1 ], silk_Resampler_2_3_COEFS_LQ[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 2 ], silk_Resampler_2_3_COEFS_LQ[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 3 ], silk_Resampler_2_3_COEFS_LQ[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 4 ], silk_Resampler_2_3_COEFS_LQ[ 2 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + + buf_ptr += 3; + counter -= 3; + } + + in += nSamplesIn; + inLen -= nSamplesIn; + + if( inLen > 0 ) { + /* More iterations to do; copy last part of filtered signal to beginning of buffer */ + silk_memcpy( buf, &buf[ nSamplesIn ], ORDER_FIR * sizeof( opus_int32 ) ); + } else { + break; + } + } + + /* Copy last part of filtered signal to the state for the next call */ + silk_memcpy( S, &buf[ nSamplesIn ], ORDER_FIR * sizeof( opus_int32 ) ); + RESTORE_STACK; +} diff --git a/port/third_party/opus/silk/resampler_private.h b/port/third_party/opus/silk/resampler_private.h new file mode 100644 index 00000000..422a7d9d --- /dev/null +++ b/port/third_party/opus/silk/resampler_private.h @@ -0,0 +1,88 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_RESAMPLER_PRIVATE_H +#define SILK_RESAMPLER_PRIVATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "SigProc_FIX.h" +#include "resampler_structs.h" +#include "resampler_rom.h" + +/* Number of input samples to process in the inner loop */ +#define RESAMPLER_MAX_BATCH_SIZE_MS 10 +#define RESAMPLER_MAX_FS_KHZ 48 +#define RESAMPLER_MAX_BATCH_SIZE_IN ( RESAMPLER_MAX_BATCH_SIZE_MS * RESAMPLER_MAX_FS_KHZ ) + +/* Description: Hybrid IIR/FIR polyphase implementation of resampling */ +void silk_resampler_private_IIR_FIR( + void *SS, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +); + +/* Description: Hybrid IIR/FIR polyphase implementation of resampling */ +void silk_resampler_private_down_FIR( + void *SS, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +); + +/* Upsample by a factor 2, high quality */ +void silk_resampler_private_up2_HQ_wrapper( + void *SS, /* I/O Resampler state (unused) */ + opus_int16 *out, /* O Output signal [ 2 * len ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 len /* I Number of input samples */ +); + +/* Upsample by a factor 2, high quality */ +void silk_resampler_private_up2_HQ( + opus_int32 *S, /* I/O Resampler state [ 6 ] */ + opus_int16 *out, /* O Output signal [ 2 * len ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 len /* I Number of input samples */ +); + +/* Second order AR filter */ +void silk_resampler_private_AR2( + opus_int32 S[], /* I/O State vector [ 2 ] */ + opus_int32 out_Q8[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + const opus_int16 A_Q14[], /* I AR coefficients, Q14 */ + opus_int32 len /* I Signal length */ +); + +#ifdef __cplusplus +} +#endif +#endif /* SILK_RESAMPLER_PRIVATE_H */ diff --git a/port/third_party/opus/silk/resampler_private_AR2.c b/port/third_party/opus/silk/resampler_private_AR2.c new file mode 100644 index 00000000..5fff2371 --- /dev/null +++ b/port/third_party/opus/silk/resampler_private_AR2.c @@ -0,0 +1,55 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" + +/* Second order AR filter with single delay elements */ +void silk_resampler_private_AR2( + opus_int32 S[], /* I/O State vector [ 2 ] */ + opus_int32 out_Q8[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + const opus_int16 A_Q14[], /* I AR coefficients, Q14 */ + opus_int32 len /* I Signal length */ +) +{ + opus_int32 k; + opus_int32 out32; + + for( k = 0; k < len; k++ ) { + out32 = silk_ADD_LSHIFT32( S[ 0 ], (opus_int32)in[ k ], 8 ); + out_Q8[ k ] = out32; + out32 = silk_LSHIFT( out32, 2 ); + S[ 0 ] = silk_SMLAWB( S[ 1 ], out32, A_Q14[ 0 ] ); + S[ 1 ] = silk_SMULWB( out32, A_Q14[ 1 ] ); + } +} + diff --git a/port/third_party/opus/silk/resampler_private_IIR_FIR.c b/port/third_party/opus/silk/resampler_private_IIR_FIR.c new file mode 100644 index 00000000..6b2b3a2e --- /dev/null +++ b/port/third_party/opus/silk/resampler_private_IIR_FIR.c @@ -0,0 +1,107 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" +#include "stack_alloc.h" + +static OPUS_INLINE opus_int16 *silk_resampler_private_IIR_FIR_INTERPOL( + opus_int16 *out, + opus_int16 *buf, + opus_int32 max_index_Q16, + opus_int32 index_increment_Q16 +) +{ + opus_int32 index_Q16, res_Q15; + opus_int16 *buf_ptr; + opus_int32 table_index; + + /* Interpolate upsampled signal and store in output array */ + for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { + table_index = silk_SMULWB( index_Q16 & 0xFFFF, 12 ); + buf_ptr = &buf[ index_Q16 >> 16 ]; + + res_Q15 = silk_SMULBB( buf_ptr[ 0 ], silk_resampler_frac_FIR_12[ table_index ][ 0 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 1 ], silk_resampler_frac_FIR_12[ table_index ][ 1 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 2 ], silk_resampler_frac_FIR_12[ table_index ][ 2 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 3 ], silk_resampler_frac_FIR_12[ table_index ][ 3 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 4 ], silk_resampler_frac_FIR_12[ 11 - table_index ][ 3 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 5 ], silk_resampler_frac_FIR_12[ 11 - table_index ][ 2 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 6 ], silk_resampler_frac_FIR_12[ 11 - table_index ][ 1 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 7 ], silk_resampler_frac_FIR_12[ 11 - table_index ][ 0 ] ); + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q15, 15 ) ); + } + return out; +} +/* Upsample using a combination of allpass-based 2x upsampling and FIR interpolation */ +void silk_resampler_private_IIR_FIR( + void *SS, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +) +{ + silk_resampler_state_struct *S = (silk_resampler_state_struct *)SS; + opus_int32 nSamplesIn; + opus_int32 max_index_Q16, index_increment_Q16; + VARDECL( opus_int16, buf ); + SAVE_STACK; + + ALLOC( buf, 2 * S->batchSize + RESAMPLER_ORDER_FIR_12, opus_int16 ); + + /* Copy buffered samples to start of buffer */ + silk_memcpy( buf, S->sFIR.i16, RESAMPLER_ORDER_FIR_12 * sizeof( opus_int16 ) ); + + /* Iterate over blocks of frameSizeIn input samples */ + index_increment_Q16 = S->invRatio_Q16; + while( 1 ) { + nSamplesIn = silk_min( inLen, S->batchSize ); + + /* Upsample 2x */ + silk_resampler_private_up2_HQ( S->sIIR, &buf[ RESAMPLER_ORDER_FIR_12 ], in, nSamplesIn ); + + max_index_Q16 = silk_LSHIFT32( nSamplesIn, 16 + 1 ); /* + 1 because 2x upsampling */ + out = silk_resampler_private_IIR_FIR_INTERPOL( out, buf, max_index_Q16, index_increment_Q16 ); + in += nSamplesIn; + inLen -= nSamplesIn; + + if( inLen > 0 ) { + /* More iterations to do; copy last part of filtered signal to beginning of buffer */ + silk_memcpy( buf, &buf[ nSamplesIn << 1 ], RESAMPLER_ORDER_FIR_12 * sizeof( opus_int16 ) ); + } else { + break; + } + } + + /* Copy last part of filtered signal to the state for the next call */ + silk_memcpy( S->sFIR.i16, &buf[ nSamplesIn << 1 ], RESAMPLER_ORDER_FIR_12 * sizeof( opus_int16 ) ); + RESTORE_STACK; +} diff --git a/port/third_party/opus/silk/resampler_private_down_FIR.c b/port/third_party/opus/silk/resampler_private_down_FIR.c new file mode 100644 index 00000000..3e8735a3 --- /dev/null +++ b/port/third_party/opus/silk/resampler_private_down_FIR.c @@ -0,0 +1,194 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" +#include "stack_alloc.h" + +static OPUS_INLINE opus_int16 *silk_resampler_private_down_FIR_INTERPOL( + opus_int16 *out, + opus_int32 *buf, + const opus_int16 *FIR_Coefs, + opus_int FIR_Order, + opus_int FIR_Fracs, + opus_int32 max_index_Q16, + opus_int32 index_increment_Q16 +) +{ + opus_int32 index_Q16, res_Q6; + opus_int32 *buf_ptr; + opus_int32 interpol_ind; + const opus_int16 *interpol_ptr; + + switch( FIR_Order ) { + case RESAMPLER_DOWN_ORDER_FIR0: + for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { + /* Integer part gives pointer to buffered input */ + buf_ptr = buf + silk_RSHIFT( index_Q16, 16 ); + + /* Fractional part gives interpolation coefficients */ + interpol_ind = silk_SMULWB( index_Q16 & 0xFFFF, FIR_Fracs ); + + /* Inner product */ + interpol_ptr = &FIR_Coefs[ RESAMPLER_DOWN_ORDER_FIR0 / 2 * interpol_ind ]; + res_Q6 = silk_SMULWB( buf_ptr[ 0 ], interpol_ptr[ 0 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 1 ], interpol_ptr[ 1 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 2 ], interpol_ptr[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 3 ], interpol_ptr[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 4 ], interpol_ptr[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 5 ], interpol_ptr[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 6 ], interpol_ptr[ 6 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 7 ], interpol_ptr[ 7 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 8 ], interpol_ptr[ 8 ] ); + interpol_ptr = &FIR_Coefs[ RESAMPLER_DOWN_ORDER_FIR0 / 2 * ( FIR_Fracs - 1 - interpol_ind ) ]; + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 17 ], interpol_ptr[ 0 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 16 ], interpol_ptr[ 1 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 15 ], interpol_ptr[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 14 ], interpol_ptr[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 13 ], interpol_ptr[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 12 ], interpol_ptr[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 11 ], interpol_ptr[ 6 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 10 ], interpol_ptr[ 7 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 9 ], interpol_ptr[ 8 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + } + break; + case RESAMPLER_DOWN_ORDER_FIR1: + for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { + /* Integer part gives pointer to buffered input */ + buf_ptr = buf + silk_RSHIFT( index_Q16, 16 ); + + /* Inner product */ + res_Q6 = silk_SMULWB( silk_ADD32( buf_ptr[ 0 ], buf_ptr[ 23 ] ), FIR_Coefs[ 0 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 1 ], buf_ptr[ 22 ] ), FIR_Coefs[ 1 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 2 ], buf_ptr[ 21 ] ), FIR_Coefs[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 3 ], buf_ptr[ 20 ] ), FIR_Coefs[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 4 ], buf_ptr[ 19 ] ), FIR_Coefs[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 5 ], buf_ptr[ 18 ] ), FIR_Coefs[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 6 ], buf_ptr[ 17 ] ), FIR_Coefs[ 6 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 7 ], buf_ptr[ 16 ] ), FIR_Coefs[ 7 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 8 ], buf_ptr[ 15 ] ), FIR_Coefs[ 8 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 9 ], buf_ptr[ 14 ] ), FIR_Coefs[ 9 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 10 ], buf_ptr[ 13 ] ), FIR_Coefs[ 10 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 11 ], buf_ptr[ 12 ] ), FIR_Coefs[ 11 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + } + break; + case RESAMPLER_DOWN_ORDER_FIR2: + for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { + /* Integer part gives pointer to buffered input */ + buf_ptr = buf + silk_RSHIFT( index_Q16, 16 ); + + /* Inner product */ + res_Q6 = silk_SMULWB( silk_ADD32( buf_ptr[ 0 ], buf_ptr[ 35 ] ), FIR_Coefs[ 0 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 1 ], buf_ptr[ 34 ] ), FIR_Coefs[ 1 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 2 ], buf_ptr[ 33 ] ), FIR_Coefs[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 3 ], buf_ptr[ 32 ] ), FIR_Coefs[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 4 ], buf_ptr[ 31 ] ), FIR_Coefs[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 5 ], buf_ptr[ 30 ] ), FIR_Coefs[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 6 ], buf_ptr[ 29 ] ), FIR_Coefs[ 6 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 7 ], buf_ptr[ 28 ] ), FIR_Coefs[ 7 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 8 ], buf_ptr[ 27 ] ), FIR_Coefs[ 8 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 9 ], buf_ptr[ 26 ] ), FIR_Coefs[ 9 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 10 ], buf_ptr[ 25 ] ), FIR_Coefs[ 10 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 11 ], buf_ptr[ 24 ] ), FIR_Coefs[ 11 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 12 ], buf_ptr[ 23 ] ), FIR_Coefs[ 12 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 13 ], buf_ptr[ 22 ] ), FIR_Coefs[ 13 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 14 ], buf_ptr[ 21 ] ), FIR_Coefs[ 14 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 15 ], buf_ptr[ 20 ] ), FIR_Coefs[ 15 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 16 ], buf_ptr[ 19 ] ), FIR_Coefs[ 16 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 17 ], buf_ptr[ 18 ] ), FIR_Coefs[ 17 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + } + break; + default: + celt_assert( 0 ); + } + return out; +} + +/* Resample with a 2nd order AR filter followed by FIR interpolation */ +void silk_resampler_private_down_FIR( + void *SS, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +) +{ + silk_resampler_state_struct *S = (silk_resampler_state_struct *)SS; + opus_int32 nSamplesIn; + opus_int32 max_index_Q16, index_increment_Q16; + VARDECL( opus_int32, buf ); + const opus_int16 *FIR_Coefs; + SAVE_STACK; + + ALLOC( buf, S->batchSize + S->FIR_Order, opus_int32 ); + + /* Copy buffered samples to start of buffer */ + silk_memcpy( buf, S->sFIR.i32, S->FIR_Order * sizeof( opus_int32 ) ); + + FIR_Coefs = &S->Coefs[ 2 ]; + + /* Iterate over blocks of frameSizeIn input samples */ + index_increment_Q16 = S->invRatio_Q16; + while( 1 ) { + nSamplesIn = silk_min( inLen, S->batchSize ); + + /* Second-order AR filter (output in Q8) */ + silk_resampler_private_AR2( S->sIIR, &buf[ S->FIR_Order ], in, S->Coefs, nSamplesIn ); + + max_index_Q16 = silk_LSHIFT32( nSamplesIn, 16 ); + + /* Interpolate filtered signal */ + out = silk_resampler_private_down_FIR_INTERPOL( out, buf, FIR_Coefs, S->FIR_Order, + S->FIR_Fracs, max_index_Q16, index_increment_Q16 ); + + in += nSamplesIn; + inLen -= nSamplesIn; + + if( inLen > 1 ) { + /* More iterations to do; copy last part of filtered signal to beginning of buffer */ + silk_memcpy( buf, &buf[ nSamplesIn ], S->FIR_Order * sizeof( opus_int32 ) ); + } else { + break; + } + } + + /* Copy last part of filtered signal to the state for the next call */ + silk_memcpy( S->sFIR.i32, &buf[ nSamplesIn ], S->FIR_Order * sizeof( opus_int32 ) ); + RESTORE_STACK; +} diff --git a/port/third_party/opus/silk/resampler_private_up2_HQ.c b/port/third_party/opus/silk/resampler_private_up2_HQ.c new file mode 100644 index 00000000..c7ec8de3 --- /dev/null +++ b/port/third_party/opus/silk/resampler_private_up2_HQ.c @@ -0,0 +1,113 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" + +/* Upsample by a factor 2, high quality */ +/* Uses 2nd order allpass filters for the 2x upsampling, followed by a */ +/* notch filter just above Nyquist. */ +void silk_resampler_private_up2_HQ( + opus_int32 *S, /* I/O Resampler state [ 6 ] */ + opus_int16 *out, /* O Output signal [ 2 * len ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 len /* I Number of input samples */ +) +{ + opus_int32 k; + opus_int32 in32, out32_1, out32_2, Y, X; + + silk_assert( silk_resampler_up2_hq_0[ 0 ] > 0 ); + silk_assert( silk_resampler_up2_hq_0[ 1 ] > 0 ); + silk_assert( silk_resampler_up2_hq_0[ 2 ] < 0 ); + silk_assert( silk_resampler_up2_hq_1[ 0 ] > 0 ); + silk_assert( silk_resampler_up2_hq_1[ 1 ] > 0 ); + silk_assert( silk_resampler_up2_hq_1[ 2 ] < 0 ); + + /* Internal variables and state are in Q10 format */ + for( k = 0; k < len; k++ ) { + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ k ], 10 ); + + /* First all-pass section for even output sample */ + Y = silk_SUB32( in32, S[ 0 ] ); + X = silk_SMULWB( Y, silk_resampler_up2_hq_0[ 0 ] ); + out32_1 = silk_ADD32( S[ 0 ], X ); + S[ 0 ] = silk_ADD32( in32, X ); + + /* Second all-pass section for even output sample */ + Y = silk_SUB32( out32_1, S[ 1 ] ); + X = silk_SMULWB( Y, silk_resampler_up2_hq_0[ 1 ] ); + out32_2 = silk_ADD32( S[ 1 ], X ); + S[ 1 ] = silk_ADD32( out32_1, X ); + + /* Third all-pass section for even output sample */ + Y = silk_SUB32( out32_2, S[ 2 ] ); + X = silk_SMLAWB( Y, Y, silk_resampler_up2_hq_0[ 2 ] ); + out32_1 = silk_ADD32( S[ 2 ], X ); + S[ 2 ] = silk_ADD32( out32_2, X ); + + /* Apply gain in Q15, convert back to int16 and store to output */ + out[ 2 * k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( out32_1, 10 ) ); + + /* First all-pass section for odd output sample */ + Y = silk_SUB32( in32, S[ 3 ] ); + X = silk_SMULWB( Y, silk_resampler_up2_hq_1[ 0 ] ); + out32_1 = silk_ADD32( S[ 3 ], X ); + S[ 3 ] = silk_ADD32( in32, X ); + + /* Second all-pass section for odd output sample */ + Y = silk_SUB32( out32_1, S[ 4 ] ); + X = silk_SMULWB( Y, silk_resampler_up2_hq_1[ 1 ] ); + out32_2 = silk_ADD32( S[ 4 ], X ); + S[ 4 ] = silk_ADD32( out32_1, X ); + + /* Third all-pass section for odd output sample */ + Y = silk_SUB32( out32_2, S[ 5 ] ); + X = silk_SMLAWB( Y, Y, silk_resampler_up2_hq_1[ 2 ] ); + out32_1 = silk_ADD32( S[ 5 ], X ); + S[ 5 ] = silk_ADD32( out32_2, X ); + + /* Apply gain in Q15, convert back to int16 and store to output */ + out[ 2 * k + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( out32_1, 10 ) ); + } +} + +void silk_resampler_private_up2_HQ_wrapper( + void *SS, /* I/O Resampler state (unused) */ + opus_int16 *out, /* O Output signal [ 2 * len ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 len /* I Number of input samples */ +) +{ + silk_resampler_state_struct *S = (silk_resampler_state_struct *)SS; + silk_resampler_private_up2_HQ( S->sIIR, out, in, len ); +} diff --git a/port/third_party/opus/silk/resampler_rom.c b/port/third_party/opus/silk/resampler_rom.c new file mode 100644 index 00000000..5e6b0447 --- /dev/null +++ b/port/third_party/opus/silk/resampler_rom.c @@ -0,0 +1,96 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Filter coefficients for IIR/FIR polyphase resampling * + * Total size: 179 Words (358 Bytes) */ + +#include "resampler_private.h" + +/* Matlab code for the notch filter coefficients: */ +/* B = [1, 0.147, 1]; A = [1, 0.107, 0.89]; G = 0.93; freqz(G * B, A, 2^14, 16e3); axis([0, 8000, -10, 1]) */ +/* fprintf('\t%6d, %6d, %6d, %6d\n', round(B(2)*2^16), round(-A(2)*2^16), round((1-A(3))*2^16), round(G*2^15)) */ +/* const opus_int16 silk_resampler_up2_hq_notch[ 4 ] = { 9634, -7012, 7209, 30474 }; */ + +/* Tables with IIR and FIR coefficients for fractional downsamplers (123 Words) */ +silk_DWORD_ALIGN const opus_int16 silk_Resampler_3_4_COEFS[ 2 + 3 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ] = { + -20694, -13867, + -49, 64, 17, -157, 353, -496, 163, 11047, 22205, + -39, 6, 91, -170, 186, 23, -896, 6336, 19928, + -19, -36, 102, -89, -24, 328, -951, 2568, 15909, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_2_3_COEFS[ 2 + 2 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ] = { + -14457, -14019, + 64, 128, -122, 36, 310, -768, 584, 9267, 17733, + 12, 128, 18, -142, 288, -117, -865, 4123, 14459, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_2_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR1 / 2 ] = { + 616, -14323, + -10, 39, 58, -46, -84, 120, 184, -315, -541, 1284, 5380, 9024, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_3_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { + 16102, -15162, + -13, 0, 20, 26, 5, -31, -43, -4, 65, 90, 7, -157, -248, -44, 593, 1583, 2612, 3271, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_4_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { + 22500, -15099, + 3, -14, -20, -15, 2, 25, 37, 25, -16, -71, -107, -79, 50, 292, 623, 982, 1288, 1464, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_6_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { + 27540, -15257, + 17, 12, 8, 1, -10, -22, -30, -32, -22, 3, 44, 100, 168, 243, 317, 381, 429, 455, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_2_3_COEFS_LQ[ 2 + 2 * 2 ] = { + -2797, -6507, + 4697, 10739, + 1567, 8276, +}; + +/* Table with interplation fractions of 1/24, 3/24, 5/24, ... , 23/24 : 23/24 (46 Words) */ +silk_DWORD_ALIGN const opus_int16 silk_resampler_frac_FIR_12[ 12 ][ RESAMPLER_ORDER_FIR_12 / 2 ] = { + { 189, -600, 617, 30567 }, + { 117, -159, -1070, 29704 }, + { 52, 221, -2392, 28276 }, + { -4, 529, -3350, 26341 }, + { -48, 758, -3956, 23973 }, + { -80, 905, -4235, 21254 }, + { -99, 972, -4222, 18278 }, + { -107, 967, -3957, 15143 }, + { -103, 896, -3487, 11950 }, + { -91, 773, -2865, 8798 }, + { -71, 611, -2143, 5784 }, + { -46, 425, -1375, 2996 }, +}; diff --git a/port/third_party/opus/silk/resampler_rom.h b/port/third_party/opus/silk/resampler_rom.h new file mode 100644 index 00000000..490b3388 --- /dev/null +++ b/port/third_party/opus/silk/resampler_rom.h @@ -0,0 +1,68 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_FIX_RESAMPLER_ROM_H +#define SILK_FIX_RESAMPLER_ROM_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "typedef.h" +#include "resampler_structs.h" + +#define RESAMPLER_DOWN_ORDER_FIR0 18 +#define RESAMPLER_DOWN_ORDER_FIR1 24 +#define RESAMPLER_DOWN_ORDER_FIR2 36 +#define RESAMPLER_ORDER_FIR_12 8 + +/* Tables for 2x downsampler */ +static const opus_int16 silk_resampler_down2_0 = 9872; +static const opus_int16 silk_resampler_down2_1 = 39809 - 65536; + +/* Tables for 2x upsampler, high quality */ +static const opus_int16 silk_resampler_up2_hq_0[ 3 ] = { 1746, 14986, 39083 - 65536 }; +static const opus_int16 silk_resampler_up2_hq_1[ 3 ] = { 6854, 25769, 55542 - 65536 }; + +/* Tables with IIR and FIR coefficients for fractional downsamplers */ +extern const opus_int16 silk_Resampler_3_4_COEFS[ 2 + 3 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ]; +extern const opus_int16 silk_Resampler_2_3_COEFS[ 2 + 2 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ]; +extern const opus_int16 silk_Resampler_1_2_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR1 / 2 ]; +extern const opus_int16 silk_Resampler_1_3_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ]; +extern const opus_int16 silk_Resampler_1_4_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ]; +extern const opus_int16 silk_Resampler_1_6_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ]; +extern const opus_int16 silk_Resampler_2_3_COEFS_LQ[ 2 + 2 * 2 ]; + +/* Table with interplation fractions of 1/24, 3/24, ..., 23/24 */ +extern const opus_int16 silk_resampler_frac_FIR_12[ 12 ][ RESAMPLER_ORDER_FIR_12 / 2 ]; + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_FIX_RESAMPLER_ROM_H */ diff --git a/port/third_party/opus/silk/resampler_structs.h b/port/third_party/opus/silk/resampler_structs.h new file mode 100644 index 00000000..9e9457d1 --- /dev/null +++ b/port/third_party/opus/silk/resampler_structs.h @@ -0,0 +1,60 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_RESAMPLER_STRUCTS_H +#define SILK_RESAMPLER_STRUCTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define SILK_RESAMPLER_MAX_FIR_ORDER 36 +#define SILK_RESAMPLER_MAX_IIR_ORDER 6 + +typedef struct _silk_resampler_state_struct{ + opus_int32 sIIR[ SILK_RESAMPLER_MAX_IIR_ORDER ]; /* this must be the first element of this struct */ + union{ + opus_int32 i32[ SILK_RESAMPLER_MAX_FIR_ORDER ]; + opus_int16 i16[ SILK_RESAMPLER_MAX_FIR_ORDER ]; + } sFIR; + opus_int16 delayBuf[ 48 ]; + opus_int resampler_function; + opus_int batchSize; + opus_int32 invRatio_Q16; + opus_int FIR_Order; + opus_int FIR_Fracs; + opus_int Fs_in_kHz; + opus_int Fs_out_kHz; + opus_int inputDelay; + const opus_int16 *Coefs; +} silk_resampler_state_struct; + +#ifdef __cplusplus +} +#endif +#endif /* SILK_RESAMPLER_STRUCTS_H */ + diff --git a/port/third_party/opus/silk/shell_coder.c b/port/third_party/opus/silk/shell_coder.c new file mode 100644 index 00000000..4af34147 --- /dev/null +++ b/port/third_party/opus/silk/shell_coder.c @@ -0,0 +1,151 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* shell coder; pulse-subframe length is hardcoded */ + +static OPUS_INLINE void combine_pulses( + opus_int *out, /* O combined pulses vector [len] */ + const opus_int *in, /* I input vector [2 * len] */ + const opus_int len /* I number of OUTPUT samples */ +) +{ + opus_int k; + for( k = 0; k < len; k++ ) { + out[ k ] = in[ 2 * k ] + in[ 2 * k + 1 ]; + } +} + +static OPUS_INLINE void encode_split( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int p_child1, /* I pulse amplitude of first child subframe */ + const opus_int p, /* I pulse amplitude of current subframe */ + const opus_uint8 *shell_table /* I table of shell cdfs */ +) +{ + if( p > 0 ) { + ec_enc_icdf( psRangeEnc, p_child1, &shell_table[ silk_shell_code_table_offsets[ p ] ], 8 ); + } +} + +static OPUS_INLINE void decode_split( + opus_int16 *p_child1, /* O pulse amplitude of first child subframe */ + opus_int16 *p_child2, /* O pulse amplitude of second child subframe */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + const opus_int p, /* I pulse amplitude of current subframe */ + const opus_uint8 *shell_table /* I table of shell cdfs */ +) +{ + if( p > 0 ) { + p_child1[ 0 ] = ec_dec_icdf( psRangeDec, &shell_table[ silk_shell_code_table_offsets[ p ] ], 8 ); + p_child2[ 0 ] = p - p_child1[ 0 ]; + } else { + p_child1[ 0 ] = 0; + p_child2[ 0 ] = 0; + } +} + +/* Shell encoder, operates on one shell code frame of 16 pulses */ +void silk_shell_encoder( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int *pulses0 /* I data: nonnegative pulse amplitudes */ +) +{ + opus_int pulses1[ 8 ], pulses2[ 4 ], pulses3[ 2 ], pulses4[ 1 ]; + + /* this function operates on one shell code frame of 16 pulses */ + silk_assert( SHELL_CODEC_FRAME_LENGTH == 16 ); + + /* tree representation per pulse-subframe */ + combine_pulses( pulses1, pulses0, 8 ); + combine_pulses( pulses2, pulses1, 4 ); + combine_pulses( pulses3, pulses2, 2 ); + combine_pulses( pulses4, pulses3, 1 ); + + encode_split( psRangeEnc, pulses3[ 0 ], pulses4[ 0 ], silk_shell_code_table3 ); + + encode_split( psRangeEnc, pulses2[ 0 ], pulses3[ 0 ], silk_shell_code_table2 ); + + encode_split( psRangeEnc, pulses1[ 0 ], pulses2[ 0 ], silk_shell_code_table1 ); + encode_split( psRangeEnc, pulses0[ 0 ], pulses1[ 0 ], silk_shell_code_table0 ); + encode_split( psRangeEnc, pulses0[ 2 ], pulses1[ 1 ], silk_shell_code_table0 ); + + encode_split( psRangeEnc, pulses1[ 2 ], pulses2[ 1 ], silk_shell_code_table1 ); + encode_split( psRangeEnc, pulses0[ 4 ], pulses1[ 2 ], silk_shell_code_table0 ); + encode_split( psRangeEnc, pulses0[ 6 ], pulses1[ 3 ], silk_shell_code_table0 ); + + encode_split( psRangeEnc, pulses2[ 2 ], pulses3[ 1 ], silk_shell_code_table2 ); + + encode_split( psRangeEnc, pulses1[ 4 ], pulses2[ 2 ], silk_shell_code_table1 ); + encode_split( psRangeEnc, pulses0[ 8 ], pulses1[ 4 ], silk_shell_code_table0 ); + encode_split( psRangeEnc, pulses0[ 10 ], pulses1[ 5 ], silk_shell_code_table0 ); + + encode_split( psRangeEnc, pulses1[ 6 ], pulses2[ 3 ], silk_shell_code_table1 ); + encode_split( psRangeEnc, pulses0[ 12 ], pulses1[ 6 ], silk_shell_code_table0 ); + encode_split( psRangeEnc, pulses0[ 14 ], pulses1[ 7 ], silk_shell_code_table0 ); +} + + +/* Shell decoder, operates on one shell code frame of 16 pulses */ +void silk_shell_decoder( + opus_int16 *pulses0, /* O data: nonnegative pulse amplitudes */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + const opus_int pulses4 /* I number of pulses per pulse-subframe */ +) +{ + opus_int16 pulses3[ 2 ], pulses2[ 4 ], pulses1[ 8 ]; + + /* this function operates on one shell code frame of 16 pulses */ + silk_assert( SHELL_CODEC_FRAME_LENGTH == 16 ); + + decode_split( &pulses3[ 0 ], &pulses3[ 1 ], psRangeDec, pulses4, silk_shell_code_table3 ); + + decode_split( &pulses2[ 0 ], &pulses2[ 1 ], psRangeDec, pulses3[ 0 ], silk_shell_code_table2 ); + + decode_split( &pulses1[ 0 ], &pulses1[ 1 ], psRangeDec, pulses2[ 0 ], silk_shell_code_table1 ); + decode_split( &pulses0[ 0 ], &pulses0[ 1 ], psRangeDec, pulses1[ 0 ], silk_shell_code_table0 ); + decode_split( &pulses0[ 2 ], &pulses0[ 3 ], psRangeDec, pulses1[ 1 ], silk_shell_code_table0 ); + + decode_split( &pulses1[ 2 ], &pulses1[ 3 ], psRangeDec, pulses2[ 1 ], silk_shell_code_table1 ); + decode_split( &pulses0[ 4 ], &pulses0[ 5 ], psRangeDec, pulses1[ 2 ], silk_shell_code_table0 ); + decode_split( &pulses0[ 6 ], &pulses0[ 7 ], psRangeDec, pulses1[ 3 ], silk_shell_code_table0 ); + + decode_split( &pulses2[ 2 ], &pulses2[ 3 ], psRangeDec, pulses3[ 1 ], silk_shell_code_table2 ); + + decode_split( &pulses1[ 4 ], &pulses1[ 5 ], psRangeDec, pulses2[ 2 ], silk_shell_code_table1 ); + decode_split( &pulses0[ 8 ], &pulses0[ 9 ], psRangeDec, pulses1[ 4 ], silk_shell_code_table0 ); + decode_split( &pulses0[ 10 ], &pulses0[ 11 ], psRangeDec, pulses1[ 5 ], silk_shell_code_table0 ); + + decode_split( &pulses1[ 6 ], &pulses1[ 7 ], psRangeDec, pulses2[ 3 ], silk_shell_code_table1 ); + decode_split( &pulses0[ 12 ], &pulses0[ 13 ], psRangeDec, pulses1[ 6 ], silk_shell_code_table0 ); + decode_split( &pulses0[ 14 ], &pulses0[ 15 ], psRangeDec, pulses1[ 7 ], silk_shell_code_table0 ); +} diff --git a/port/third_party/opus/silk/sigm_Q15.c b/port/third_party/opus/silk/sigm_Q15.c new file mode 100644 index 00000000..3c507d25 --- /dev/null +++ b/port/third_party/opus/silk/sigm_Q15.c @@ -0,0 +1,76 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Approximate sigmoid function */ + +#include "SigProc_FIX.h" + +/* fprintf(1, '%d, ', round(1024 * ([1 ./ (1 + exp(-(1:5))), 1] - 1 ./ (1 + exp(-(0:5)))))); */ +static const opus_int32 sigm_LUT_slope_Q10[ 6 ] = { + 237, 153, 73, 30, 12, 7 +}; +/* fprintf(1, '%d, ', round(32767 * 1 ./ (1 + exp(-(0:5))))); */ +static const opus_int32 sigm_LUT_pos_Q15[ 6 ] = { + 16384, 23955, 28861, 31213, 32178, 32548 +}; +/* fprintf(1, '%d, ', round(32767 * 1 ./ (1 + exp((0:5))))); */ +static const opus_int32 sigm_LUT_neg_Q15[ 6 ] = { + 16384, 8812, 3906, 1554, 589, 219 +}; + +opus_int silk_sigm_Q15( + opus_int in_Q5 /* I */ +) +{ + opus_int ind; + + if( in_Q5 < 0 ) { + /* Negative input */ + in_Q5 = -in_Q5; + if( in_Q5 >= 6 * 32 ) { + return 0; /* Clip */ + } else { + /* Linear interpolation of look up table */ + ind = silk_RSHIFT( in_Q5, 5 ); + return( sigm_LUT_neg_Q15[ ind ] - silk_SMULBB( sigm_LUT_slope_Q10[ ind ], in_Q5 & 0x1F ) ); + } + } else { + /* Positive input */ + if( in_Q5 >= 6 * 32 ) { + return 32767; /* clip */ + } else { + /* Linear interpolation of look up table */ + ind = silk_RSHIFT( in_Q5, 5 ); + return( sigm_LUT_pos_Q15[ ind ] + silk_SMULBB( sigm_LUT_slope_Q10[ ind ], in_Q5 & 0x1F ) ); + } + } +} + diff --git a/port/third_party/opus/silk/sort.c b/port/third_party/opus/silk/sort.c new file mode 100644 index 00000000..4fba16f8 --- /dev/null +++ b/port/third_party/opus/silk/sort.c @@ -0,0 +1,154 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Insertion sort (fast for already almost sorted arrays): */ +/* Best case: O(n) for an already sorted array */ +/* Worst case: O(n^2) for an inversely sorted array */ +/* */ +/* Shell short: https://en.wikipedia.org/wiki/Shell_sort */ + +#include "SigProc_FIX.h" + +void silk_insertion_sort_increasing( + opus_int32 *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +) +{ + opus_int32 value; + opus_int i, j; + + /* Safety checks */ + celt_assert( K > 0 ); + celt_assert( L > 0 ); + celt_assert( L >= K ); + + /* Write start indices in index vector */ + for( i = 0; i < K; i++ ) { + idx[ i ] = i; + } + + /* Sort vector elements by value, increasing order */ + for( i = 1; i < K; i++ ) { + value = a[ i ]; + for( j = i - 1; ( j >= 0 ) && ( value < a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + + /* If less than L values are asked for, check the remaining values, */ + /* but only spend CPU to ensure that the K first values are correct */ + for( i = K; i < L; i++ ) { + value = a[ i ]; + if( value < a[ K - 1 ] ) { + for( j = K - 2; ( j >= 0 ) && ( value < a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + } +} + +#ifdef FIXED_POINT +/* This function is only used by the fixed-point build */ +void silk_insertion_sort_decreasing_int16( + opus_int16 *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +) +{ + opus_int i, j; + opus_int value; + + /* Safety checks */ + celt_assert( K > 0 ); + celt_assert( L > 0 ); + celt_assert( L >= K ); + + /* Write start indices in index vector */ + for( i = 0; i < K; i++ ) { + idx[ i ] = i; + } + + /* Sort vector elements by value, decreasing order */ + for( i = 1; i < K; i++ ) { + value = a[ i ]; + for( j = i - 1; ( j >= 0 ) && ( value > a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + + /* If less than L values are asked for, check the remaining values, */ + /* but only spend CPU to ensure that the K first values are correct */ + for( i = K; i < L; i++ ) { + value = a[ i ]; + if( value > a[ K - 1 ] ) { + for( j = K - 2; ( j >= 0 ) && ( value > a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + } +} +#endif + +void silk_insertion_sort_increasing_all_values_int16( + opus_int16 *a, /* I/O Unsorted / Sorted vector */ + const opus_int L /* I Vector length */ +) +{ + opus_int value; + opus_int i, j; + + /* Safety checks */ + celt_assert( L > 0 ); + + /* Sort vector elements by value, increasing order */ + for( i = 1; i < L; i++ ) { + value = a[ i ]; + for( j = i - 1; ( j >= 0 ) && ( value < a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + } + a[ j + 1 ] = value; /* Write value */ + } +} diff --git a/port/third_party/opus/silk/stereo_LR_to_MS.c b/port/third_party/opus/silk/stereo_LR_to_MS.c new file mode 100644 index 00000000..751452cb --- /dev/null +++ b/port/third_party/opus/silk/stereo_LR_to_MS.c @@ -0,0 +1,229 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/* Convert Left/Right stereo signal to adaptive Mid/Side representation */ +void silk_stereo_LR_to_MS( + stereo_enc_state *state, /* I/O State */ + opus_int16 x1[], /* I/O Left input signal, becomes mid signal */ + opus_int16 x2[], /* I/O Right input signal, becomes side signal */ + opus_int8 ix[ 2 ][ 3 ], /* O Quantization indices */ + opus_int8 *mid_only_flag, /* O Flag: only mid signal coded */ + opus_int32 mid_side_rates_bps[], /* O Bitrates for mid and side signals */ + opus_int32 total_rate_bps, /* I Total bitrate */ + opus_int prev_speech_act_Q8, /* I Speech activity level in previous frame */ + opus_int toMono, /* I Last frame before a stereo->mono transition */ + opus_int fs_kHz, /* I Sample rate (kHz) */ + opus_int frame_length /* I Number of samples */ +) +{ + opus_int n, is10msFrame, denom_Q16, delta0_Q13, delta1_Q13; + opus_int32 sum, diff, smooth_coef_Q16, pred_Q13[ 2 ], pred0_Q13, pred1_Q13; + opus_int32 LP_ratio_Q14, HP_ratio_Q14, frac_Q16, frac_3_Q16, min_mid_rate_bps, width_Q14, w_Q24, deltaw_Q24; + VARDECL( opus_int16, side ); + VARDECL( opus_int16, LP_mid ); + VARDECL( opus_int16, HP_mid ); + VARDECL( opus_int16, LP_side ); + VARDECL( opus_int16, HP_side ); + opus_int16 *mid = &x1[ -2 ]; + SAVE_STACK; + + ALLOC( side, frame_length + 2, opus_int16 ); + /* Convert to basic mid/side signals */ + for( n = 0; n < frame_length + 2; n++ ) { + sum = x1[ n - 2 ] + (opus_int32)x2[ n - 2 ]; + diff = x1[ n - 2 ] - (opus_int32)x2[ n - 2 ]; + mid[ n ] = (opus_int16)silk_RSHIFT_ROUND( sum, 1 ); + side[ n ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( diff, 1 ) ); + } + + /* Buffering */ + silk_memcpy( mid, state->sMid, 2 * sizeof( opus_int16 ) ); + silk_memcpy( side, state->sSide, 2 * sizeof( opus_int16 ) ); + silk_memcpy( state->sMid, &mid[ frame_length ], 2 * sizeof( opus_int16 ) ); + silk_memcpy( state->sSide, &side[ frame_length ], 2 * sizeof( opus_int16 ) ); + + /* LP and HP filter mid signal */ + ALLOC( LP_mid, frame_length, opus_int16 ); + ALLOC( HP_mid, frame_length, opus_int16 ); + for( n = 0; n < frame_length; n++ ) { + sum = silk_RSHIFT_ROUND( silk_ADD_LSHIFT32( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 2 ); + LP_mid[ n ] = sum; + HP_mid[ n ] = mid[ n + 1 ] - sum; + } + + /* LP and HP filter side signal */ + ALLOC( LP_side, frame_length, opus_int16 ); + ALLOC( HP_side, frame_length, opus_int16 ); + for( n = 0; n < frame_length; n++ ) { + sum = silk_RSHIFT_ROUND( silk_ADD_LSHIFT32( side[ n ] + (opus_int32)side[ n + 2 ], side[ n + 1 ], 1 ), 2 ); + LP_side[ n ] = sum; + HP_side[ n ] = side[ n + 1 ] - sum; + } + + /* Find energies and predictors */ + is10msFrame = frame_length == 10 * fs_kHz; + smooth_coef_Q16 = is10msFrame ? + SILK_FIX_CONST( STEREO_RATIO_SMOOTH_COEF / 2, 16 ) : + SILK_FIX_CONST( STEREO_RATIO_SMOOTH_COEF, 16 ); + smooth_coef_Q16 = silk_SMULWB( silk_SMULBB( prev_speech_act_Q8, prev_speech_act_Q8 ), smooth_coef_Q16 ); + + pred_Q13[ 0 ] = silk_stereo_find_predictor( &LP_ratio_Q14, LP_mid, LP_side, &state->mid_side_amp_Q0[ 0 ], frame_length, smooth_coef_Q16 ); + pred_Q13[ 1 ] = silk_stereo_find_predictor( &HP_ratio_Q14, HP_mid, HP_side, &state->mid_side_amp_Q0[ 2 ], frame_length, smooth_coef_Q16 ); + /* Ratio of the norms of residual and mid signals */ + frac_Q16 = silk_SMLABB( HP_ratio_Q14, LP_ratio_Q14, 3 ); + frac_Q16 = silk_min( frac_Q16, SILK_FIX_CONST( 1, 16 ) ); + + /* Determine bitrate distribution between mid and side, and possibly reduce stereo width */ + total_rate_bps -= is10msFrame ? 1200 : 600; /* Subtract approximate bitrate for coding stereo parameters */ + if( total_rate_bps < 1 ) { + total_rate_bps = 1; + } + min_mid_rate_bps = silk_SMLABB( 2000, fs_kHz, 600 ); + silk_assert( min_mid_rate_bps < 32767 ); + /* Default bitrate distribution: 8 parts for Mid and (5+3*frac) parts for Side. so: mid_rate = ( 8 / ( 13 + 3 * frac ) ) * total_ rate */ + frac_3_Q16 = silk_MUL( 3, frac_Q16 ); + mid_side_rates_bps[ 0 ] = silk_DIV32_varQ( total_rate_bps, SILK_FIX_CONST( 8 + 5, 16 ) + frac_3_Q16, 16+3 ); + /* If Mid bitrate below minimum, reduce stereo width */ + if( mid_side_rates_bps[ 0 ] < min_mid_rate_bps ) { + mid_side_rates_bps[ 0 ] = min_mid_rate_bps; + mid_side_rates_bps[ 1 ] = total_rate_bps - mid_side_rates_bps[ 0 ]; + /* width = 4 * ( 2 * side_rate - min_rate ) / ( ( 1 + 3 * frac ) * min_rate ) */ + width_Q14 = silk_DIV32_varQ( silk_LSHIFT( mid_side_rates_bps[ 1 ], 1 ) - min_mid_rate_bps, + silk_SMULWB( SILK_FIX_CONST( 1, 16 ) + frac_3_Q16, min_mid_rate_bps ), 14+2 ); + width_Q14 = silk_LIMIT( width_Q14, 0, SILK_FIX_CONST( 1, 14 ) ); + } else { + mid_side_rates_bps[ 1 ] = total_rate_bps - mid_side_rates_bps[ 0 ]; + width_Q14 = SILK_FIX_CONST( 1, 14 ); + } + + /* Smoother */ + state->smth_width_Q14 = (opus_int16)silk_SMLAWB( state->smth_width_Q14, width_Q14 - state->smth_width_Q14, smooth_coef_Q16 ); + + /* At very low bitrates or for inputs that are nearly amplitude panned, switch to panned-mono coding */ + *mid_only_flag = 0; + if( toMono ) { + /* Last frame before stereo->mono transition; collapse stereo width */ + width_Q14 = 0; + pred_Q13[ 0 ] = 0; + pred_Q13[ 1 ] = 0; + silk_stereo_quant_pred( pred_Q13, ix ); + } else if( state->width_prev_Q14 == 0 && + ( 8 * total_rate_bps < 13 * min_mid_rate_bps || silk_SMULWB( frac_Q16, state->smth_width_Q14 ) < SILK_FIX_CONST( 0.05, 14 ) ) ) + { + /* Code as panned-mono; previous frame already had zero width */ + /* Scale down and quantize predictors */ + pred_Q13[ 0 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 0 ] ), 14 ); + pred_Q13[ 1 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 1 ] ), 14 ); + silk_stereo_quant_pred( pred_Q13, ix ); + /* Collapse stereo width */ + width_Q14 = 0; + pred_Q13[ 0 ] = 0; + pred_Q13[ 1 ] = 0; + mid_side_rates_bps[ 0 ] = total_rate_bps; + mid_side_rates_bps[ 1 ] = 0; + *mid_only_flag = 1; + } else if( state->width_prev_Q14 != 0 && + ( 8 * total_rate_bps < 11 * min_mid_rate_bps || silk_SMULWB( frac_Q16, state->smth_width_Q14 ) < SILK_FIX_CONST( 0.02, 14 ) ) ) + { + /* Transition to zero-width stereo */ + /* Scale down and quantize predictors */ + pred_Q13[ 0 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 0 ] ), 14 ); + pred_Q13[ 1 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 1 ] ), 14 ); + silk_stereo_quant_pred( pred_Q13, ix ); + /* Collapse stereo width */ + width_Q14 = 0; + pred_Q13[ 0 ] = 0; + pred_Q13[ 1 ] = 0; + } else if( state->smth_width_Q14 > SILK_FIX_CONST( 0.95, 14 ) ) { + /* Full-width stereo coding */ + silk_stereo_quant_pred( pred_Q13, ix ); + width_Q14 = SILK_FIX_CONST( 1, 14 ); + } else { + /* Reduced-width stereo coding; scale down and quantize predictors */ + pred_Q13[ 0 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 0 ] ), 14 ); + pred_Q13[ 1 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 1 ] ), 14 ); + silk_stereo_quant_pred( pred_Q13, ix ); + width_Q14 = state->smth_width_Q14; + } + + /* Make sure to keep on encoding until the tapered output has been transmitted */ + if( *mid_only_flag == 1 ) { + state->silent_side_len += frame_length - STEREO_INTERP_LEN_MS * fs_kHz; + if( state->silent_side_len < LA_SHAPE_MS * fs_kHz ) { + *mid_only_flag = 0; + } else { + /* Limit to avoid wrapping around */ + state->silent_side_len = 10000; + } + } else { + state->silent_side_len = 0; + } + + if( *mid_only_flag == 0 && mid_side_rates_bps[ 1 ] < 1 ) { + mid_side_rates_bps[ 1 ] = 1; + mid_side_rates_bps[ 0 ] = silk_max_int( 1, total_rate_bps - mid_side_rates_bps[ 1 ]); + } + + /* Interpolate predictors and subtract prediction from side channel */ + pred0_Q13 = -state->pred_prev_Q13[ 0 ]; + pred1_Q13 = -state->pred_prev_Q13[ 1 ]; + w_Q24 = silk_LSHIFT( state->width_prev_Q14, 10 ); + denom_Q16 = silk_DIV32_16( (opus_int32)1 << 16, STEREO_INTERP_LEN_MS * fs_kHz ); + delta0_Q13 = -silk_RSHIFT_ROUND( silk_SMULBB( pred_Q13[ 0 ] - state->pred_prev_Q13[ 0 ], denom_Q16 ), 16 ); + delta1_Q13 = -silk_RSHIFT_ROUND( silk_SMULBB( pred_Q13[ 1 ] - state->pred_prev_Q13[ 1 ], denom_Q16 ), 16 ); + deltaw_Q24 = silk_LSHIFT( silk_SMULWB( width_Q14 - state->width_prev_Q14, denom_Q16 ), 10 ); + for( n = 0; n < STEREO_INTERP_LEN_MS * fs_kHz; n++ ) { + pred0_Q13 += delta0_Q13; + pred1_Q13 += delta1_Q13; + w_Q24 += deltaw_Q24; + sum = silk_LSHIFT( silk_ADD_LSHIFT32( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_SMLAWB( silk_SMULWB( w_Q24, side[ n + 1 ] ), sum, pred0_Q13 ); /* Q8 */ + sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)mid[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ + x2[ n - 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); + } + + pred0_Q13 = -pred_Q13[ 0 ]; + pred1_Q13 = -pred_Q13[ 1 ]; + w_Q24 = silk_LSHIFT( width_Q14, 10 ); + for( n = STEREO_INTERP_LEN_MS * fs_kHz; n < frame_length; n++ ) { + sum = silk_LSHIFT( silk_ADD_LSHIFT32( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_SMLAWB( silk_SMULWB( w_Q24, side[ n + 1 ] ), sum, pred0_Q13 ); /* Q8 */ + sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)mid[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ + x2[ n - 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); + } + state->pred_prev_Q13[ 0 ] = (opus_int16)pred_Q13[ 0 ]; + state->pred_prev_Q13[ 1 ] = (opus_int16)pred_Q13[ 1 ]; + state->width_prev_Q14 = (opus_int16)width_Q14; + RESTORE_STACK; +} diff --git a/port/third_party/opus/silk/stereo_MS_to_LR.c b/port/third_party/opus/silk/stereo_MS_to_LR.c new file mode 100644 index 00000000..1e01bb6e --- /dev/null +++ b/port/third_party/opus/silk/stereo_MS_to_LR.c @@ -0,0 +1,85 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Convert adaptive Mid/Side representation to Left/Right stereo signal */ +void silk_stereo_MS_to_LR( + stereo_dec_state *state, /* I/O State */ + opus_int16 x1[], /* I/O Left input signal, becomes mid signal */ + opus_int16 x2[], /* I/O Right input signal, becomes side signal */ + const opus_int32 pred_Q13[], /* I Predictors */ + opus_int fs_kHz, /* I Samples rate (kHz) */ + opus_int frame_length /* I Number of samples */ +) +{ + opus_int n, denom_Q16, delta0_Q13, delta1_Q13; + opus_int32 sum, diff, pred0_Q13, pred1_Q13; + + /* Buffering */ + silk_memcpy( x1, state->sMid, 2 * sizeof( opus_int16 ) ); + silk_memcpy( x2, state->sSide, 2 * sizeof( opus_int16 ) ); + silk_memcpy( state->sMid, &x1[ frame_length ], 2 * sizeof( opus_int16 ) ); + silk_memcpy( state->sSide, &x2[ frame_length ], 2 * sizeof( opus_int16 ) ); + + /* Interpolate predictors and add prediction to side channel */ + pred0_Q13 = state->pred_prev_Q13[ 0 ]; + pred1_Q13 = state->pred_prev_Q13[ 1 ]; + denom_Q16 = silk_DIV32_16( (opus_int32)1 << 16, STEREO_INTERP_LEN_MS * fs_kHz ); + delta0_Q13 = silk_RSHIFT_ROUND( silk_SMULBB( pred_Q13[ 0 ] - state->pred_prev_Q13[ 0 ], denom_Q16 ), 16 ); + delta1_Q13 = silk_RSHIFT_ROUND( silk_SMULBB( pred_Q13[ 1 ] - state->pred_prev_Q13[ 1 ], denom_Q16 ), 16 ); + for( n = 0; n < STEREO_INTERP_LEN_MS * fs_kHz; n++ ) { + pred0_Q13 += delta0_Q13; + pred1_Q13 += delta1_Q13; + sum = silk_LSHIFT( silk_ADD_LSHIFT32( x1[ n ] + (opus_int32)x1[ n + 2 ], x1[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_SMLAWB( silk_LSHIFT( (opus_int32)x2[ n + 1 ], 8 ), sum, pred0_Q13 ); /* Q8 */ + sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)x1[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ + x2[ n + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); + } + pred0_Q13 = pred_Q13[ 0 ]; + pred1_Q13 = pred_Q13[ 1 ]; + for( n = STEREO_INTERP_LEN_MS * fs_kHz; n < frame_length; n++ ) { + sum = silk_LSHIFT( silk_ADD_LSHIFT32( x1[ n ] + (opus_int32)x1[ n + 2 ], x1[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_SMLAWB( silk_LSHIFT( (opus_int32)x2[ n + 1 ], 8 ), sum, pred0_Q13 ); /* Q8 */ + sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)x1[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ + x2[ n + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); + } + state->pred_prev_Q13[ 0 ] = pred_Q13[ 0 ]; + state->pred_prev_Q13[ 1 ] = pred_Q13[ 1 ]; + + /* Convert to left/right signals */ + for( n = 0; n < frame_length; n++ ) { + sum = x1[ n + 1 ] + (opus_int32)x2[ n + 1 ]; + diff = x1[ n + 1 ] - (opus_int32)x2[ n + 1 ]; + x1[ n + 1 ] = (opus_int16)silk_SAT16( sum ); + x2[ n + 1 ] = (opus_int16)silk_SAT16( diff ); + } +} diff --git a/port/third_party/opus/silk/stereo_decode_pred.c b/port/third_party/opus/silk/stereo_decode_pred.c new file mode 100644 index 00000000..56ba3925 --- /dev/null +++ b/port/third_party/opus/silk/stereo_decode_pred.c @@ -0,0 +1,73 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Decode mid/side predictors */ +void silk_stereo_decode_pred( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int32 pred_Q13[] /* O Predictors */ +) +{ + opus_int n, ix[ 2 ][ 3 ]; + opus_int32 low_Q13, step_Q13; + + /* Entropy decoding */ + n = ec_dec_icdf( psRangeDec, silk_stereo_pred_joint_iCDF, 8 ); + ix[ 0 ][ 2 ] = silk_DIV32_16( n, 5 ); + ix[ 1 ][ 2 ] = n - 5 * ix[ 0 ][ 2 ]; + for( n = 0; n < 2; n++ ) { + ix[ n ][ 0 ] = ec_dec_icdf( psRangeDec, silk_uniform3_iCDF, 8 ); + ix[ n ][ 1 ] = ec_dec_icdf( psRangeDec, silk_uniform5_iCDF, 8 ); + } + + /* Dequantize */ + for( n = 0; n < 2; n++ ) { + ix[ n ][ 0 ] += 3 * ix[ n ][ 2 ]; + low_Q13 = silk_stereo_pred_quant_Q13[ ix[ n ][ 0 ] ]; + step_Q13 = silk_SMULWB( silk_stereo_pred_quant_Q13[ ix[ n ][ 0 ] + 1 ] - low_Q13, + SILK_FIX_CONST( 0.5 / STEREO_QUANT_SUB_STEPS, 16 ) ); + pred_Q13[ n ] = silk_SMLABB( low_Q13, step_Q13, 2 * ix[ n ][ 1 ] + 1 ); + } + + /* Subtract second from first predictor (helps when actually applying these) */ + pred_Q13[ 0 ] -= pred_Q13[ 1 ]; +} + +/* Decode mid-only flag */ +void silk_stereo_decode_mid_only( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int *decode_only_mid /* O Flag that only mid channel has been coded */ +) +{ + /* Decode flag that only mid channel is coded */ + *decode_only_mid = ec_dec_icdf( psRangeDec, silk_stereo_only_code_mid_iCDF, 8 ); +} diff --git a/port/third_party/opus/silk/stereo_encode_pred.c b/port/third_party/opus/silk/stereo_encode_pred.c new file mode 100644 index 00000000..03becb67 --- /dev/null +++ b/port/third_party/opus/silk/stereo_encode_pred.c @@ -0,0 +1,62 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Entropy code the mid/side quantization indices */ +void silk_stereo_encode_pred( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int8 ix[ 2 ][ 3 ] /* I Quantization indices */ +) +{ + opus_int n; + + /* Entropy coding */ + n = 5 * ix[ 0 ][ 2 ] + ix[ 1 ][ 2 ]; + celt_assert( n < 25 ); + ec_enc_icdf( psRangeEnc, n, silk_stereo_pred_joint_iCDF, 8 ); + for( n = 0; n < 2; n++ ) { + celt_assert( ix[ n ][ 0 ] < 3 ); + celt_assert( ix[ n ][ 1 ] < STEREO_QUANT_SUB_STEPS ); + ec_enc_icdf( psRangeEnc, ix[ n ][ 0 ], silk_uniform3_iCDF, 8 ); + ec_enc_icdf( psRangeEnc, ix[ n ][ 1 ], silk_uniform5_iCDF, 8 ); + } +} + +/* Entropy code the mid-only flag */ +void silk_stereo_encode_mid_only( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int8 mid_only_flag +) +{ + /* Encode flag that only mid channel is coded */ + ec_enc_icdf( psRangeEnc, mid_only_flag, silk_stereo_only_code_mid_iCDF, 8 ); +} diff --git a/port/third_party/opus/silk/stereo_find_predictor.c b/port/third_party/opus/silk/stereo_find_predictor.c new file mode 100644 index 00000000..e30e90bd --- /dev/null +++ b/port/third_party/opus/silk/stereo_find_predictor.c @@ -0,0 +1,79 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Find least-squares prediction gain for one signal based on another and quantize it */ +opus_int32 silk_stereo_find_predictor( /* O Returns predictor in Q13 */ + opus_int32 *ratio_Q14, /* O Ratio of residual and mid energies */ + const opus_int16 x[], /* I Basis signal */ + const opus_int16 y[], /* I Target signal */ + opus_int32 mid_res_amp_Q0[], /* I/O Smoothed mid, residual norms */ + opus_int length, /* I Number of samples */ + opus_int smooth_coef_Q16 /* I Smoothing coefficient */ +) +{ + opus_int scale, scale1, scale2; + opus_int32 nrgx, nrgy, corr, pred_Q13, pred2_Q10; + + /* Find predictor */ + silk_sum_sqr_shift( &nrgx, &scale1, x, length ); + silk_sum_sqr_shift( &nrgy, &scale2, y, length ); + scale = silk_max_int( scale1, scale2 ); + scale = scale + ( scale & 1 ); /* make even */ + nrgy = silk_RSHIFT32( nrgy, scale - scale2 ); + nrgx = silk_RSHIFT32( nrgx, scale - scale1 ); + nrgx = silk_max_int( nrgx, 1 ); + corr = silk_inner_prod_aligned_scale( x, y, scale, length ); + pred_Q13 = silk_DIV32_varQ( corr, nrgx, 13 ); + pred_Q13 = silk_LIMIT( pred_Q13, -(1 << 14), 1 << 14 ); + pred2_Q10 = silk_SMULWB( pred_Q13, pred_Q13 ); + + /* Faster update for signals with large prediction parameters */ + smooth_coef_Q16 = (opus_int)silk_max_int( smooth_coef_Q16, silk_abs( pred2_Q10 ) ); + + /* Smoothed mid and residual norms */ + silk_assert( smooth_coef_Q16 < 32768 ); + scale = silk_RSHIFT( scale, 1 ); + mid_res_amp_Q0[ 0 ] = silk_SMLAWB( mid_res_amp_Q0[ 0 ], silk_LSHIFT( silk_SQRT_APPROX( nrgx ), scale ) - mid_res_amp_Q0[ 0 ], + smooth_coef_Q16 ); + /* Residual energy = nrgy - 2 * pred * corr + pred^2 * nrgx */ + nrgy = silk_SUB_LSHIFT32( nrgy, silk_SMULWB( corr, pred_Q13 ), 3 + 1 ); + nrgy = silk_ADD_LSHIFT32( nrgy, silk_SMULWB( nrgx, pred2_Q10 ), 6 ); + mid_res_amp_Q0[ 1 ] = silk_SMLAWB( mid_res_amp_Q0[ 1 ], silk_LSHIFT( silk_SQRT_APPROX( nrgy ), scale ) - mid_res_amp_Q0[ 1 ], + smooth_coef_Q16 ); + + /* Ratio of smoothed residual and mid norms */ + *ratio_Q14 = silk_DIV32_varQ( mid_res_amp_Q0[ 1 ], silk_max( mid_res_amp_Q0[ 0 ], 1 ), 14 ); + *ratio_Q14 = silk_LIMIT( *ratio_Q14, 0, 32767 ); + + return pred_Q13; +} diff --git a/port/third_party/opus/silk/stereo_quant_pred.c b/port/third_party/opus/silk/stereo_quant_pred.c new file mode 100644 index 00000000..d4ced6c3 --- /dev/null +++ b/port/third_party/opus/silk/stereo_quant_pred.c @@ -0,0 +1,73 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Quantize mid/side predictors */ +void silk_stereo_quant_pred( + opus_int32 pred_Q13[], /* I/O Predictors (out: quantized) */ + opus_int8 ix[ 2 ][ 3 ] /* O Quantization indices */ +) +{ + opus_int i, j, n; + opus_int32 low_Q13, step_Q13, lvl_Q13, err_min_Q13, err_Q13, quant_pred_Q13 = 0; + + /* Quantize */ + for( n = 0; n < 2; n++ ) { + /* Brute-force search over quantization levels */ + err_min_Q13 = silk_int32_MAX; + for( i = 0; i < STEREO_QUANT_TAB_SIZE - 1; i++ ) { + low_Q13 = silk_stereo_pred_quant_Q13[ i ]; + step_Q13 = silk_SMULWB( silk_stereo_pred_quant_Q13[ i + 1 ] - low_Q13, + SILK_FIX_CONST( 0.5 / STEREO_QUANT_SUB_STEPS, 16 ) ); + for( j = 0; j < STEREO_QUANT_SUB_STEPS; j++ ) { + lvl_Q13 = silk_SMLABB( low_Q13, step_Q13, 2 * j + 1 ); + err_Q13 = silk_abs( pred_Q13[ n ] - lvl_Q13 ); + if( err_Q13 < err_min_Q13 ) { + err_min_Q13 = err_Q13; + quant_pred_Q13 = lvl_Q13; + ix[ n ][ 0 ] = i; + ix[ n ][ 1 ] = j; + } else { + /* Error increasing, so we're past the optimum */ + goto done; + } + } + } + done: + ix[ n ][ 2 ] = silk_DIV32_16( ix[ n ][ 0 ], 3 ); + ix[ n ][ 0 ] -= ix[ n ][ 2 ] * 3; + pred_Q13[ n ] = quant_pred_Q13; + } + + /* Subtract second from first predictor (helps when actually applying these) */ + pred_Q13[ 0 ] -= pred_Q13[ 1 ]; +} diff --git a/port/third_party/opus/silk/structs.h b/port/third_party/opus/silk/structs.h new file mode 100644 index 00000000..38243be1 --- /dev/null +++ b/port/third_party/opus/silk/structs.h @@ -0,0 +1,357 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_STRUCTS_H +#define SILK_STRUCTS_H + +#include "typedef.h" +#include "SigProc_FIX.h" +#include "define.h" +#include "entenc.h" +#include "entdec.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet.h" +#include "lpcnet_private.h" +#endif + +#ifdef ENABLE_DRED +#include "dred_encoder.h" +#include "dred_decoder.h" +#endif + +#ifdef ENABLE_OSCE +#include "osce_config.h" +#include "osce_structs.h" +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +/************************************/ +/* Noise shaping quantization state */ +/************************************/ +typedef struct { + opus_int16 xq[ 2 * MAX_FRAME_LENGTH ]; /* Buffer for quantized output signal */ + opus_int32 sLTP_shp_Q14[ 2 * MAX_FRAME_LENGTH ]; + opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ]; + opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 sLF_AR_shp_Q14; + opus_int32 sDiff_shp_Q14; + opus_int lagPrev; + opus_int sLTP_buf_idx; + opus_int sLTP_shp_buf_idx; + opus_int32 rand_seed; + opus_int32 prev_gain_Q16; + opus_int rewhite_flag; +} silk_nsq_state; + +/********************************/ +/* VAD state */ +/********************************/ +typedef struct { + opus_int32 AnaState[ 2 ]; /* Analysis filterbank state: 0-8 kHz */ + opus_int32 AnaState1[ 2 ]; /* Analysis filterbank state: 0-4 kHz */ + opus_int32 AnaState2[ 2 ]; /* Analysis filterbank state: 0-2 kHz */ + opus_int32 XnrgSubfr[ VAD_N_BANDS ]; /* Subframe energies */ + opus_int32 NrgRatioSmth_Q8[ VAD_N_BANDS ]; /* Smoothed energy level in each band */ + opus_int16 HPstate; /* State of differentiator in the lowest band */ + opus_int32 NL[ VAD_N_BANDS ]; /* Noise energy level in each band */ + opus_int32 inv_NL[ VAD_N_BANDS ]; /* Inverse noise energy level in each band */ + opus_int32 NoiseLevelBias[ VAD_N_BANDS ]; /* Noise level estimator bias/offset */ + opus_int32 counter; /* Frame counter used in the initial phase */ +} silk_VAD_state; + +/* Variable cut-off low-pass filter state */ +typedef struct { + opus_int32 In_LP_State[ 2 ]; /* Low pass filter state */ + opus_int32 transition_frame_no; /* Counter which is mapped to a cut-off frequency */ + opus_int mode; /* Operating mode, <0: switch down, >0: switch up; 0: do nothing */ + opus_int32 saved_fs_kHz; /* If non-zero, holds the last sampling rate before a bandwidth switching reset. */ +} silk_LP_state; + +/* Structure containing NLSF codebook */ +typedef struct { + const opus_int16 nVectors; + const opus_int16 order; + const opus_int16 quantStepSize_Q16; + const opus_int16 invQuantStepSize_Q6; + const opus_uint8 *CB1_NLSF_Q8; + const opus_int16 *CB1_Wght_Q9; + const opus_uint8 *CB1_iCDF; + const opus_uint8 *pred_Q8; + const opus_uint8 *ec_sel; + const opus_uint8 *ec_iCDF; + const opus_uint8 *ec_Rates_Q5; + const opus_int16 *deltaMin_Q15; +} silk_NLSF_CB_struct; + +typedef struct { + opus_int16 pred_prev_Q13[ 2 ]; + opus_int16 sMid[ 2 ]; + opus_int16 sSide[ 2 ]; + opus_int32 mid_side_amp_Q0[ 4 ]; + opus_int16 smth_width_Q14; + opus_int16 width_prev_Q14; + opus_int16 silent_side_len; + opus_int8 predIx[ MAX_FRAMES_PER_PACKET ][ 2 ][ 3 ]; + opus_int8 mid_only_flags[ MAX_FRAMES_PER_PACKET ]; +} stereo_enc_state; + +typedef struct { + opus_int16 pred_prev_Q13[ 2 ]; + opus_int16 sMid[ 2 ]; + opus_int16 sSide[ 2 ]; +} stereo_dec_state; + +typedef struct { + opus_int8 GainsIndices[ MAX_NB_SUBFR ]; + opus_int8 LTPIndex[ MAX_NB_SUBFR ]; + opus_int8 NLSFIndices[ MAX_LPC_ORDER + 1 ]; + opus_int16 lagIndex; + opus_int8 contourIndex; + opus_int8 signalType; + opus_int8 quantOffsetType; + opus_int8 NLSFInterpCoef_Q2; + opus_int8 PERIndex; + opus_int8 LTP_scaleIndex; + opus_int8 Seed; +} SideInfoIndices; + +/********************************/ +/* Encoder state */ +/********************************/ +typedef struct { + opus_int32 In_HP_State[ 2 ]; /* High pass filter state */ + opus_int32 variable_HP_smth1_Q15; /* State of first smoother */ + opus_int32 variable_HP_smth2_Q15; /* State of second smoother */ + silk_LP_state sLP; /* Low pass filter state */ + silk_VAD_state sVAD; /* Voice activity detector state */ + silk_nsq_state sNSQ; /* Noise Shape Quantizer State */ + opus_int16 prev_NLSFq_Q15[ MAX_LPC_ORDER ]; /* Previously quantized NLSF vector */ + opus_int speech_activity_Q8; /* Speech activity */ + opus_int allow_bandwidth_switch; /* Flag indicating that switching of internal bandwidth is allowed */ + opus_int8 LBRRprevLastGainIndex; + opus_int8 prevSignalType; + opus_int prevLag; + opus_int pitch_LPC_win_length; + opus_int max_pitch_lag; /* Highest possible pitch lag (samples) */ + opus_int32 API_fs_Hz; /* API sampling frequency (Hz) */ + opus_int32 prev_API_fs_Hz; /* Previous API sampling frequency (Hz) */ + opus_int maxInternal_fs_Hz; /* Maximum internal sampling frequency (Hz) */ + opus_int minInternal_fs_Hz; /* Minimum internal sampling frequency (Hz) */ + opus_int desiredInternal_fs_Hz; /* Soft request for internal sampling frequency (Hz) */ + opus_int fs_kHz; /* Internal sampling frequency (kHz) */ + opus_int nb_subfr; /* Number of 5 ms subframes in a frame */ + opus_int frame_length; /* Frame length (samples) */ + opus_int subfr_length; /* Subframe length (samples) */ + opus_int ltp_mem_length; /* Length of LTP memory */ + opus_int la_pitch; /* Look-ahead for pitch analysis (samples) */ + opus_int la_shape; /* Look-ahead for noise shape analysis (samples) */ + opus_int shapeWinLength; /* Window length for noise shape analysis (samples) */ + opus_int32 TargetRate_bps; /* Target bitrate (bps) */ + opus_int PacketSize_ms; /* Number of milliseconds to put in each packet */ + opus_int PacketLoss_perc; /* Packet loss rate measured by farend */ + opus_int32 frameCounter; + opus_int Complexity; /* Complexity setting */ + opus_int nStatesDelayedDecision; /* Number of states in delayed decision quantization */ + opus_int useInterpolatedNLSFs; /* Flag for using NLSF interpolation */ + opus_int shapingLPCOrder; /* Filter order for noise shaping filters */ + opus_int predictLPCOrder; /* Filter order for prediction filters */ + opus_int pitchEstimationComplexity; /* Complexity level for pitch estimator */ + opus_int pitchEstimationLPCOrder; /* Whitening filter order for pitch estimator */ + opus_int32 pitchEstimationThreshold_Q16; /* Threshold for pitch estimator */ + opus_int32 sum_log_gain_Q7; /* Cumulative max prediction gain */ + opus_int NLSF_MSVQ_Survivors; /* Number of survivors in NLSF MSVQ */ + opus_int first_frame_after_reset; /* Flag for deactivating NLSF interpolation, pitch prediction */ + opus_int controlled_since_last_payload; /* Flag for ensuring codec_control only runs once per packet */ + opus_int warping_Q16; /* Warping parameter for warped noise shaping */ + opus_int useCBR; /* Flag to enable constant bitrate */ + opus_int prefillFlag; /* Flag to indicate that only buffers are prefilled, no coding */ + const opus_uint8 *pitch_lag_low_bits_iCDF; /* Pointer to iCDF table for low bits of pitch lag index */ + const opus_uint8 *pitch_contour_iCDF; /* Pointer to iCDF table for pitch contour index */ + const silk_NLSF_CB_struct *psNLSF_CB; /* Pointer to NLSF codebook */ + opus_int input_quality_bands_Q15[ VAD_N_BANDS ]; + opus_int input_tilt_Q15; + opus_int SNR_dB_Q7; /* Quality setting */ + + opus_int8 VAD_flags[ MAX_FRAMES_PER_PACKET ]; + opus_int8 LBRR_flag; + opus_int LBRR_flags[ MAX_FRAMES_PER_PACKET ]; + + SideInfoIndices indices; + opus_int8 pulses[ MAX_FRAME_LENGTH ]; + + int arch; + + /* Input/output buffering */ + opus_int16 inputBuf[ MAX_FRAME_LENGTH + 2 ]; /* Buffer containing input signal */ + opus_int inputBufIx; + opus_int nFramesPerPacket; + opus_int nFramesEncoded; /* Number of frames analyzed in current packet */ + + opus_int nChannelsAPI; + opus_int nChannelsInternal; + opus_int channelNb; + + /* Parameters For LTP scaling Control */ + opus_int frames_since_onset; + + /* Specifically for entropy coding */ + opus_int ec_prevSignalType; + opus_int16 ec_prevLagIndex; + + silk_resampler_state_struct resampler_state; + + /* DTX */ + opus_int useDTX; /* Flag to enable DTX */ + opus_int inDTX; /* Flag to signal DTX period */ + opus_int noSpeechCounter; /* Counts concecutive nonactive frames, used by DTX */ + + /* Inband Low Bitrate Redundancy (LBRR) data */ + opus_int useInBandFEC; /* Saves the API setting for query */ + opus_int LBRR_enabled; /* Depends on useInBandFRC, bitrate and packet loss rate */ + opus_int LBRR_GainIncreases; /* Gains increment for coding LBRR frames */ + SideInfoIndices indices_LBRR[ MAX_FRAMES_PER_PACKET ]; + opus_int8 pulses_LBRR[ MAX_FRAMES_PER_PACKET ][ MAX_FRAME_LENGTH ]; +} silk_encoder_state; + + +#ifdef ENABLE_OSCE +typedef struct { + OSCEFeatureState features; + OSCEState state; + int method; +} silk_OSCE_struct; +#endif + +/* Struct for Packet Loss Concealment */ +typedef struct { + opus_int32 pitchL_Q8; /* Pitch lag to use for voiced concealment */ + opus_int16 LTPCoef_Q14[ LTP_ORDER ]; /* LTP coeficients to use for voiced concealment */ + opus_int16 prevLPC_Q12[ MAX_LPC_ORDER ]; + opus_int last_frame_lost; /* Was previous frame lost */ + opus_int32 rand_seed; /* Seed for unvoiced signal generation */ + opus_int16 randScale_Q14; /* Scaling of unvoiced random signal */ + opus_int32 conc_energy; + opus_int conc_energy_shift; + opus_int16 prevLTP_scale_Q14; + opus_int32 prevGain_Q16[ 2 ]; + opus_int fs_kHz; + opus_int nb_subfr; + opus_int subfr_length; + opus_int enable_deep_plc; +} silk_PLC_struct; + +/* Struct for CNG */ +typedef struct { + opus_int32 CNG_exc_buf_Q14[ MAX_FRAME_LENGTH ]; + opus_int16 CNG_smth_NLSF_Q15[ MAX_LPC_ORDER ]; + opus_int32 CNG_synth_state[ MAX_LPC_ORDER ]; + opus_int32 CNG_smth_Gain_Q16; + opus_int32 rand_seed; + opus_int fs_kHz; +} silk_CNG_struct; + +/********************************/ +/* Decoder state */ +/********************************/ +typedef struct { +#ifdef ENABLE_OSCE + silk_OSCE_struct osce; +#endif +#define SILK_DECODER_STATE_RESET_START prev_gain_Q16 + opus_int32 prev_gain_Q16; + opus_int32 exc_Q14[ MAX_FRAME_LENGTH ]; + opus_int32 sLPC_Q14_buf[ MAX_LPC_ORDER ]; + opus_int16 outBuf[ MAX_FRAME_LENGTH + 2 * MAX_SUB_FRAME_LENGTH ]; /* Buffer for output signal */ + opus_int lagPrev; /* Previous Lag */ + opus_int8 LastGainIndex; /* Previous gain index */ + opus_int fs_kHz; /* Sampling frequency in kHz */ + opus_int32 fs_API_hz; /* API sample frequency (Hz) */ + opus_int nb_subfr; /* Number of 5 ms subframes in a frame */ + opus_int frame_length; /* Frame length (samples) */ + opus_int subfr_length; /* Subframe length (samples) */ + opus_int ltp_mem_length; /* Length of LTP memory */ + opus_int LPC_order; /* LPC order */ + opus_int16 prevNLSF_Q15[ MAX_LPC_ORDER ]; /* Used to interpolate LSFs */ + opus_int first_frame_after_reset; /* Flag for deactivating NLSF interpolation */ + const opus_uint8 *pitch_lag_low_bits_iCDF; /* Pointer to iCDF table for low bits of pitch lag index */ + const opus_uint8 *pitch_contour_iCDF; /* Pointer to iCDF table for pitch contour index */ + + /* For buffering payload in case of more frames per packet */ + opus_int nFramesDecoded; + opus_int nFramesPerPacket; + + /* Specifically for entropy coding */ + opus_int ec_prevSignalType; + opus_int16 ec_prevLagIndex; + + opus_int VAD_flags[ MAX_FRAMES_PER_PACKET ]; + opus_int LBRR_flag; + opus_int LBRR_flags[ MAX_FRAMES_PER_PACKET ]; + + silk_resampler_state_struct resampler_state; + + const silk_NLSF_CB_struct *psNLSF_CB; /* Pointer to NLSF codebook */ + + /* Quantization indices */ + SideInfoIndices indices; + + /* CNG state */ + silk_CNG_struct sCNG; + + /* Stuff used for PLC */ + opus_int lossCnt; + opus_int prevSignalType; + int arch; + + silk_PLC_struct sPLC; + +} silk_decoder_state; + +/************************/ +/* Decoder control */ +/************************/ +typedef struct { + /* Prediction and coding parameters */ + opus_int pitchL[ MAX_NB_SUBFR ]; + opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; + /* Holds interpolated and final coefficients, 4-byte aligned */ + silk_DWORD_ALIGN opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ]; + opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ]; + opus_int LTP_scale_Q14; +} silk_decoder_control; + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/silk/sum_sqr_shift.c b/port/third_party/opus/silk/sum_sqr_shift.c new file mode 100644 index 00000000..4fd0c3d7 --- /dev/null +++ b/port/third_party/opus/silk/sum_sqr_shift.c @@ -0,0 +1,83 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Compute number of bits to right shift the sum of squares of a vector */ +/* of int16s to make it fit in an int32 */ +void silk_sum_sqr_shift( + opus_int32 *energy, /* O Energy of x, after shifting to the right */ + opus_int *shift, /* O Number of bits right shift applied to energy */ + const opus_int16 *x, /* I Input vector */ + opus_int len /* I Length of input vector */ +) +{ + opus_int i, shft; + opus_uint32 nrg_tmp; + opus_int32 nrg; + + /* Do a first run with the maximum shift we could have. */ + shft = 31-silk_CLZ32(len); + /* Let's be conservative with rounding and start with nrg=len. */ + nrg = len; + for( i = 0; i < len - 1; i += 2 ) { + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg_tmp = silk_SMLABB_ovflw( nrg_tmp, x[ i + 1 ], x[ i + 1 ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + if( i < len ) { + /* One sample left to process */ + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + silk_assert( nrg >= 0 ); + /* Make sure the result will fit in a 32-bit signed integer with two bits + of headroom. */ + shft = silk_max_32(0, shft+3 - silk_CLZ32(nrg)); + nrg = 0; + for( i = 0 ; i < len - 1; i += 2 ) { + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg_tmp = silk_SMLABB_ovflw( nrg_tmp, x[ i + 1 ], x[ i + 1 ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + if( i < len ) { + /* One sample left to process */ + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + + silk_assert( nrg >= 0 ); + + /* Output arguments */ + *shift = shft; + *energy = nrg; +} + diff --git a/port/third_party/opus/silk/table_LSF_cos.c b/port/third_party/opus/silk/table_LSF_cos.c new file mode 100644 index 00000000..ec9dc639 --- /dev/null +++ b/port/third_party/opus/silk/table_LSF_cos.c @@ -0,0 +1,70 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +/* Cosine approximation table for LSF conversion */ +/* Q12 values (even) */ +const opus_int16 silk_LSFCosTab_FIX_Q12[ LSF_COS_TAB_SZ_FIX + 1 ] = { + 8192, 8190, 8182, 8170, + 8152, 8130, 8104, 8072, + 8034, 7994, 7946, 7896, + 7840, 7778, 7714, 7644, + 7568, 7490, 7406, 7318, + 7226, 7128, 7026, 6922, + 6812, 6698, 6580, 6458, + 6332, 6204, 6070, 5934, + 5792, 5648, 5502, 5352, + 5198, 5040, 4880, 4718, + 4552, 4382, 4212, 4038, + 3862, 3684, 3502, 3320, + 3136, 2948, 2760, 2570, + 2378, 2186, 1990, 1794, + 1598, 1400, 1202, 1002, + 802, 602, 402, 202, + 0, -202, -402, -602, + -802, -1002, -1202, -1400, + -1598, -1794, -1990, -2186, + -2378, -2570, -2760, -2948, + -3136, -3320, -3502, -3684, + -3862, -4038, -4212, -4382, + -4552, -4718, -4880, -5040, + -5198, -5352, -5502, -5648, + -5792, -5934, -6070, -6204, + -6332, -6458, -6580, -6698, + -6812, -6922, -7026, -7128, + -7226, -7318, -7406, -7490, + -7568, -7644, -7714, -7778, + -7840, -7896, -7946, -7994, + -8034, -8072, -8104, -8130, + -8152, -8170, -8182, -8190, + -8192 +}; diff --git a/port/third_party/opus/silk/tables.h b/port/third_party/opus/silk/tables.h new file mode 100644 index 00000000..95230c45 --- /dev/null +++ b/port/third_party/opus/silk/tables.h @@ -0,0 +1,114 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_TABLES_H +#define SILK_TABLES_H + +#include "define.h" +#include "structs.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Entropy coding tables (with size in bytes indicated) */ +extern const opus_uint8 silk_gain_iCDF[ 3 ][ N_LEVELS_QGAIN / 8 ]; /* 24 */ +extern const opus_uint8 silk_delta_gain_iCDF[ MAX_DELTA_GAIN_QUANT - MIN_DELTA_GAIN_QUANT + 1 ]; /* 41 */ + +extern const opus_uint8 silk_pitch_lag_iCDF[ 2 * ( PITCH_EST_MAX_LAG_MS - PITCH_EST_MIN_LAG_MS ) ];/* 32 */ +extern const opus_uint8 silk_pitch_delta_iCDF[ 21 ]; /* 21 */ +extern const opus_uint8 silk_pitch_contour_iCDF[ 34 ]; /* 34 */ +extern const opus_uint8 silk_pitch_contour_NB_iCDF[ 11 ]; /* 11 */ +extern const opus_uint8 silk_pitch_contour_10_ms_iCDF[ 12 ]; /* 12 */ +extern const opus_uint8 silk_pitch_contour_10_ms_NB_iCDF[ 3 ]; /* 3 */ + +extern const opus_uint8 silk_pulses_per_block_iCDF[ N_RATE_LEVELS ][ SILK_MAX_PULSES + 2 ]; /* 180 */ +extern const opus_uint8 silk_pulses_per_block_BITS_Q5[ N_RATE_LEVELS - 1 ][ SILK_MAX_PULSES + 2 ]; /* 162 */ + +extern const opus_uint8 silk_rate_levels_iCDF[ 2 ][ N_RATE_LEVELS - 1 ]; /* 18 */ +extern const opus_uint8 silk_rate_levels_BITS_Q5[ 2 ][ N_RATE_LEVELS - 1 ]; /* 18 */ + +extern const opus_uint8 silk_max_pulses_table[ 4 ]; /* 4 */ + +extern const opus_uint8 silk_shell_code_table0[ 152 ]; /* 152 */ +extern const opus_uint8 silk_shell_code_table1[ 152 ]; /* 152 */ +extern const opus_uint8 silk_shell_code_table2[ 152 ]; /* 152 */ +extern const opus_uint8 silk_shell_code_table3[ 152 ]; /* 152 */ +extern const opus_uint8 silk_shell_code_table_offsets[ SILK_MAX_PULSES + 1 ]; /* 17 */ + +extern const opus_uint8 silk_lsb_iCDF[ 2 ]; /* 2 */ + +extern const opus_uint8 silk_sign_iCDF[ 42 ]; /* 42 */ + +extern const opus_uint8 silk_uniform3_iCDF[ 3 ]; /* 3 */ +extern const opus_uint8 silk_uniform4_iCDF[ 4 ]; /* 4 */ +extern const opus_uint8 silk_uniform5_iCDF[ 5 ]; /* 5 */ +extern const opus_uint8 silk_uniform6_iCDF[ 6 ]; /* 6 */ +extern const opus_uint8 silk_uniform8_iCDF[ 8 ]; /* 8 */ + +extern const opus_uint8 silk_NLSF_EXT_iCDF[ 7 ]; /* 7 */ + +extern const opus_uint8 silk_LTP_per_index_iCDF[ 3 ]; /* 3 */ +extern const opus_uint8 * const silk_LTP_gain_iCDF_ptrs[ NB_LTP_CBKS ]; /* 3 */ +extern const opus_uint8 * const silk_LTP_gain_BITS_Q5_ptrs[ NB_LTP_CBKS ]; /* 3 */ +extern const opus_int8 * const silk_LTP_vq_ptrs_Q7[ NB_LTP_CBKS ]; /* 168 */ +extern const opus_uint8 * const silk_LTP_vq_gain_ptrs_Q7[NB_LTP_CBKS]; +extern const opus_int8 silk_LTP_vq_sizes[ NB_LTP_CBKS ]; /* 3 */ + +extern const opus_uint8 silk_LTPscale_iCDF[ 3 ]; /* 4 */ +extern const opus_int16 silk_LTPScales_table_Q14[ 3 ]; /* 6 */ + +extern const opus_uint8 silk_type_offset_VAD_iCDF[ 4 ]; /* 4 */ +extern const opus_uint8 silk_type_offset_no_VAD_iCDF[ 2 ]; /* 2 */ + +extern const opus_int16 silk_stereo_pred_quant_Q13[ STEREO_QUANT_TAB_SIZE ]; /* 32 */ +extern const opus_uint8 silk_stereo_pred_joint_iCDF[ 25 ]; /* 25 */ +extern const opus_uint8 silk_stereo_only_code_mid_iCDF[ 2 ]; /* 2 */ + +extern const opus_uint8 * const silk_LBRR_flags_iCDF_ptr[ 2 ]; /* 10 */ + +extern const opus_uint8 silk_NLSF_interpolation_factor_iCDF[ 5 ]; /* 5 */ + +extern const silk_NLSF_CB_struct silk_NLSF_CB_WB; /* 1040 */ +extern const silk_NLSF_CB_struct silk_NLSF_CB_NB_MB; /* 728 */ + +/* Quantization offsets */ +extern const opus_int16 silk_Quantization_Offsets_Q10[ 2 ][ 2 ]; /* 8 */ + +/* Interpolation points for filter coefficients used in the bandwidth transition smoother */ +extern const opus_int32 silk_Transition_LP_B_Q28[ TRANSITION_INT_NUM ][ TRANSITION_NB ]; /* 60 */ +extern const opus_int32 silk_Transition_LP_A_Q28[ TRANSITION_INT_NUM ][ TRANSITION_NA ]; /* 60 */ + +/* Rom table with cosine values */ +extern const opus_int16 silk_LSFCosTab_FIX_Q12[ LSF_COS_TAB_SZ_FIX + 1 ]; /* 258 */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/port/third_party/opus/silk/tables_LTP.c b/port/third_party/opus/silk/tables_LTP.c new file mode 100644 index 00000000..5e12c864 --- /dev/null +++ b/port/third_party/opus/silk/tables_LTP.c @@ -0,0 +1,294 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +const opus_uint8 silk_LTP_per_index_iCDF[3] = { + 179, 99, 0 +}; + +static const opus_uint8 silk_LTP_gain_iCDF_0[8] = { + 71, 56, 43, 30, 21, 12, 6, 0 +}; + +static const opus_uint8 silk_LTP_gain_iCDF_1[16] = { + 199, 165, 144, 124, 109, 96, 84, 71, + 61, 51, 42, 32, 23, 15, 8, 0 +}; + +static const opus_uint8 silk_LTP_gain_iCDF_2[32] = { + 241, 225, 211, 199, 187, 175, 164, 153, + 142, 132, 123, 114, 105, 96, 88, 80, + 72, 64, 57, 50, 44, 38, 33, 29, + 24, 20, 16, 12, 9, 5, 2, 0 +}; + +static const opus_uint8 silk_LTP_gain_BITS_Q5_0[8] = { + 15, 131, 138, 138, 155, 155, 173, 173 +}; + +static const opus_uint8 silk_LTP_gain_BITS_Q5_1[16] = { + 69, 93, 115, 118, 131, 138, 141, 138, + 150, 150, 155, 150, 155, 160, 166, 160 +}; + +static const opus_uint8 silk_LTP_gain_BITS_Q5_2[32] = { + 131, 128, 134, 141, 141, 141, 145, 145, + 145, 150, 155, 155, 155, 155, 160, 160, + 160, 160, 166, 166, 173, 173, 182, 192, + 182, 192, 192, 192, 205, 192, 205, 224 +}; + +const opus_uint8 * const silk_LTP_gain_iCDF_ptrs[NB_LTP_CBKS] = { + silk_LTP_gain_iCDF_0, + silk_LTP_gain_iCDF_1, + silk_LTP_gain_iCDF_2 +}; + +const opus_uint8 * const silk_LTP_gain_BITS_Q5_ptrs[NB_LTP_CBKS] = { + silk_LTP_gain_BITS_Q5_0, + silk_LTP_gain_BITS_Q5_1, + silk_LTP_gain_BITS_Q5_2 +}; + +static const opus_int8 silk_LTP_gain_vq_0[8][5] = +{ +{ + 4, 6, 24, 7, 5 +}, +{ + 0, 0, 2, 0, 0 +}, +{ + 12, 28, 41, 13, -4 +}, +{ + -9, 15, 42, 25, 14 +}, +{ + 1, -2, 62, 41, -9 +}, +{ + -10, 37, 65, -4, 3 +}, +{ + -6, 4, 66, 7, -8 +}, +{ + 16, 14, 38, -3, 33 +} +}; + +static const opus_int8 silk_LTP_gain_vq_1[16][5] = +{ +{ + 13, 22, 39, 23, 12 +}, +{ + -1, 36, 64, 27, -6 +}, +{ + -7, 10, 55, 43, 17 +}, +{ + 1, 1, 8, 1, 1 +}, +{ + 6, -11, 74, 53, -9 +}, +{ + -12, 55, 76, -12, 8 +}, +{ + -3, 3, 93, 27, -4 +}, +{ + 26, 39, 59, 3, -8 +}, +{ + 2, 0, 77, 11, 9 +}, +{ + -8, 22, 44, -6, 7 +}, +{ + 40, 9, 26, 3, 9 +}, +{ + -7, 20, 101, -7, 4 +}, +{ + 3, -8, 42, 26, 0 +}, +{ + -15, 33, 68, 2, 23 +}, +{ + -2, 55, 46, -2, 15 +}, +{ + 3, -1, 21, 16, 41 +} +}; + +static const opus_int8 silk_LTP_gain_vq_2[32][5] = +{ +{ + -6, 27, 61, 39, 5 +}, +{ + -11, 42, 88, 4, 1 +}, +{ + -2, 60, 65, 6, -4 +}, +{ + -1, -5, 73, 56, 1 +}, +{ + -9, 19, 94, 29, -9 +}, +{ + 0, 12, 99, 6, 4 +}, +{ + 8, -19, 102, 46, -13 +}, +{ + 3, 2, 13, 3, 2 +}, +{ + 9, -21, 84, 72, -18 +}, +{ + -11, 46, 104, -22, 8 +}, +{ + 18, 38, 48, 23, 0 +}, +{ + -16, 70, 83, -21, 11 +}, +{ + 5, -11, 117, 22, -8 +}, +{ + -6, 23, 117, -12, 3 +}, +{ + 3, -8, 95, 28, 4 +}, +{ + -10, 15, 77, 60, -15 +}, +{ + -1, 4, 124, 2, -4 +}, +{ + 3, 38, 84, 24, -25 +}, +{ + 2, 13, 42, 13, 31 +}, +{ + 21, -4, 56, 46, -1 +}, +{ + -1, 35, 79, -13, 19 +}, +{ + -7, 65, 88, -9, -14 +}, +{ + 20, 4, 81, 49, -29 +}, +{ + 20, 0, 75, 3, -17 +}, +{ + 5, -9, 44, 92, -8 +}, +{ + 1, -3, 22, 69, 31 +}, +{ + -6, 95, 41, -12, 5 +}, +{ + 39, 67, 16, -4, 1 +}, +{ + 0, -6, 120, 55, -36 +}, +{ + -13, 44, 122, 4, -24 +}, +{ + 81, 5, 11, 3, 7 +}, +{ + 2, 0, 9, 10, 88 +} +}; + +const opus_int8 * const silk_LTP_vq_ptrs_Q7[NB_LTP_CBKS] = { + (opus_int8 *)&silk_LTP_gain_vq_0[0][0], + (opus_int8 *)&silk_LTP_gain_vq_1[0][0], + (opus_int8 *)&silk_LTP_gain_vq_2[0][0] +}; + +/* Maximum frequency-dependent response of the pitch taps above, + computed as max(abs(freqz(taps))) */ +static const opus_uint8 silk_LTP_gain_vq_0_gain[8] = { + 46, 2, 90, 87, 93, 91, 82, 98 +}; + +static const opus_uint8 silk_LTP_gain_vq_1_gain[16] = { + 109, 120, 118, 12, 113, 115, 117, 119, + 99, 59, 87, 111, 63, 111, 112, 80 +}; + +static const opus_uint8 silk_LTP_gain_vq_2_gain[32] = { + 126, 124, 125, 124, 129, 121, 126, 23, + 132, 127, 127, 127, 126, 127, 122, 133, + 130, 134, 101, 118, 119, 145, 126, 86, + 124, 120, 123, 119, 170, 173, 107, 109 +}; + +const opus_uint8 * const silk_LTP_vq_gain_ptrs_Q7[NB_LTP_CBKS] = { + &silk_LTP_gain_vq_0_gain[0], + &silk_LTP_gain_vq_1_gain[0], + &silk_LTP_gain_vq_2_gain[0] +}; + +const opus_int8 silk_LTP_vq_sizes[NB_LTP_CBKS] = { + 8, 16, 32 +}; diff --git a/port/third_party/opus/silk/tables_NLSF_CB_NB_MB.c b/port/third_party/opus/silk/tables_NLSF_CB_NB_MB.c new file mode 100644 index 00000000..195d5b95 --- /dev/null +++ b/port/third_party/opus/silk/tables_NLSF_CB_NB_MB.c @@ -0,0 +1,195 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +static const opus_uint8 silk_NLSF_CB1_NB_MB_Q8[ 320 ] = { + 12, 35, 60, 83, 108, 132, 157, 180, + 206, 228, 15, 32, 55, 77, 101, 125, + 151, 175, 201, 225, 19, 42, 66, 89, + 114, 137, 162, 184, 209, 230, 12, 25, + 50, 72, 97, 120, 147, 172, 200, 223, + 26, 44, 69, 90, 114, 135, 159, 180, + 205, 225, 13, 22, 53, 80, 106, 130, + 156, 180, 205, 228, 15, 25, 44, 64, + 90, 115, 142, 168, 196, 222, 19, 24, + 62, 82, 100, 120, 145, 168, 190, 214, + 22, 31, 50, 79, 103, 120, 151, 170, + 203, 227, 21, 29, 45, 65, 106, 124, + 150, 171, 196, 224, 30, 49, 75, 97, + 121, 142, 165, 186, 209, 229, 19, 25, + 52, 70, 93, 116, 143, 166, 192, 219, + 26, 34, 62, 75, 97, 118, 145, 167, + 194, 217, 25, 33, 56, 70, 91, 113, + 143, 165, 196, 223, 21, 34, 51, 72, + 97, 117, 145, 171, 196, 222, 20, 29, + 50, 67, 90, 117, 144, 168, 197, 221, + 22, 31, 48, 66, 95, 117, 146, 168, + 196, 222, 24, 33, 51, 77, 116, 134, + 158, 180, 200, 224, 21, 28, 70, 87, + 106, 124, 149, 170, 194, 217, 26, 33, + 53, 64, 83, 117, 152, 173, 204, 225, + 27, 34, 65, 95, 108, 129, 155, 174, + 210, 225, 20, 26, 72, 99, 113, 131, + 154, 176, 200, 219, 34, 43, 61, 78, + 93, 114, 155, 177, 205, 229, 23, 29, + 54, 97, 124, 138, 163, 179, 209, 229, + 30, 38, 56, 89, 118, 129, 158, 178, + 200, 231, 21, 29, 49, 63, 85, 111, + 142, 163, 193, 222, 27, 48, 77, 103, + 133, 158, 179, 196, 215, 232, 29, 47, + 74, 99, 124, 151, 176, 198, 220, 237, + 33, 42, 61, 76, 93, 121, 155, 174, + 207, 225, 29, 53, 87, 112, 136, 154, + 170, 188, 208, 227, 24, 30, 52, 84, + 131, 150, 166, 186, 203, 229, 37, 48, + 64, 84, 104, 118, 156, 177, 201, 230 +}; + +static const opus_int16 silk_NLSF_CB1_Wght_Q9[ 320 ] = { + 2897, 2314, 2314, 2314, 2287, 2287, 2314, 2300, 2327, 2287, + 2888, 2580, 2394, 2367, 2314, 2274, 2274, 2274, 2274, 2194, + 2487, 2340, 2340, 2314, 2314, 2314, 2340, 2340, 2367, 2354, + 3216, 2766, 2340, 2340, 2314, 2274, 2221, 2207, 2261, 2194, + 2460, 2474, 2367, 2394, 2394, 2394, 2394, 2367, 2407, 2314, + 3479, 3056, 2127, 2207, 2274, 2274, 2274, 2287, 2314, 2261, + 3282, 3141, 2580, 2394, 2247, 2221, 2207, 2194, 2194, 2114, + 4096, 3845, 2221, 2620, 2620, 2407, 2314, 2394, 2367, 2074, + 3178, 3244, 2367, 2221, 2553, 2434, 2340, 2314, 2167, 2221, + 3338, 3488, 2726, 2194, 2261, 2460, 2354, 2367, 2207, 2101, + 2354, 2420, 2327, 2367, 2394, 2420, 2420, 2420, 2460, 2367, + 3779, 3629, 2434, 2527, 2367, 2274, 2274, 2300, 2207, 2048, + 3254, 3225, 2713, 2846, 2447, 2327, 2300, 2300, 2274, 2127, + 3263, 3300, 2753, 2806, 2447, 2261, 2261, 2247, 2127, 2101, + 2873, 2981, 2633, 2367, 2407, 2354, 2194, 2247, 2247, 2114, + 3225, 3197, 2633, 2580, 2274, 2181, 2247, 2221, 2221, 2141, + 3178, 3310, 2740, 2407, 2274, 2274, 2274, 2287, 2194, 2114, + 3141, 3272, 2460, 2061, 2287, 2500, 2367, 2487, 2434, 2181, + 3507, 3282, 2314, 2700, 2647, 2474, 2367, 2394, 2340, 2127, + 3423, 3535, 3038, 3056, 2300, 1950, 2221, 2274, 2274, 2274, + 3404, 3366, 2087, 2687, 2873, 2354, 2420, 2274, 2474, 2540, + 3760, 3488, 1950, 2660, 2897, 2527, 2394, 2367, 2460, 2261, + 3028, 3272, 2740, 2888, 2740, 2154, 2127, 2287, 2234, 2247, + 3695, 3657, 2025, 1969, 2660, 2700, 2580, 2500, 2327, 2367, + 3207, 3413, 2354, 2074, 2888, 2888, 2340, 2487, 2247, 2167, + 3338, 3366, 2846, 2780, 2327, 2154, 2274, 2287, 2114, 2061, + 2327, 2300, 2181, 2167, 2181, 2367, 2633, 2700, 2700, 2553, + 2407, 2434, 2221, 2261, 2221, 2221, 2340, 2420, 2607, 2700, + 3038, 3244, 2806, 2888, 2474, 2074, 2300, 2314, 2354, 2380, + 2221, 2154, 2127, 2287, 2500, 2793, 2793, 2620, 2580, 2367, + 3676, 3713, 2234, 1838, 2181, 2753, 2726, 2673, 2513, 2207, + 2793, 3160, 2726, 2553, 2846, 2513, 2181, 2394, 2221, 2181 +}; + +static const opus_uint8 silk_NLSF_CB1_iCDF_NB_MB[ 64 ] = { + 212, 178, 148, 129, 108, 96, 85, 82, + 79, 77, 61, 59, 57, 56, 51, 49, + 48, 45, 42, 41, 40, 38, 36, 34, + 31, 30, 21, 12, 10, 3, 1, 0, + 255, 245, 244, 236, 233, 225, 217, 203, + 190, 176, 175, 161, 149, 136, 125, 114, + 102, 91, 81, 71, 60, 52, 43, 35, + 28, 20, 19, 18, 12, 11, 5, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_SELECT_NB_MB[ 160 ] = { + 16, 0, 0, 0, 0, 99, 66, 36, + 36, 34, 36, 34, 34, 34, 34, 83, + 69, 36, 52, 34, 116, 102, 70, 68, + 68, 176, 102, 68, 68, 34, 65, 85, + 68, 84, 36, 116, 141, 152, 139, 170, + 132, 187, 184, 216, 137, 132, 249, 168, + 185, 139, 104, 102, 100, 68, 68, 178, + 218, 185, 185, 170, 244, 216, 187, 187, + 170, 244, 187, 187, 219, 138, 103, 155, + 184, 185, 137, 116, 183, 155, 152, 136, + 132, 217, 184, 184, 170, 164, 217, 171, + 155, 139, 244, 169, 184, 185, 170, 164, + 216, 223, 218, 138, 214, 143, 188, 218, + 168, 244, 141, 136, 155, 170, 168, 138, + 220, 219, 139, 164, 219, 202, 216, 137, + 168, 186, 246, 185, 139, 116, 185, 219, + 185, 138, 100, 100, 134, 100, 102, 34, + 68, 68, 100, 68, 168, 203, 221, 218, + 168, 167, 154, 136, 104, 70, 164, 246, + 171, 137, 139, 137, 155, 218, 219, 139 +}; + +static const opus_uint8 silk_NLSF_CB2_iCDF_NB_MB[ 72 ] = { + 255, 254, 253, 238, 14, 3, 2, 1, + 0, 255, 254, 252, 218, 35, 3, 2, + 1, 0, 255, 254, 250, 208, 59, 4, + 2, 1, 0, 255, 254, 246, 194, 71, + 10, 2, 1, 0, 255, 252, 236, 183, + 82, 8, 2, 1, 0, 255, 252, 235, + 180, 90, 17, 2, 1, 0, 255, 248, + 224, 171, 97, 30, 4, 1, 0, 255, + 254, 236, 173, 95, 37, 7, 1, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_BITS_NB_MB_Q5[ 72 ] = { + 255, 255, 255, 131, 6, 145, 255, 255, + 255, 255, 255, 236, 93, 15, 96, 255, + 255, 255, 255, 255, 194, 83, 25, 71, + 221, 255, 255, 255, 255, 162, 73, 34, + 66, 162, 255, 255, 255, 210, 126, 73, + 43, 57, 173, 255, 255, 255, 201, 125, + 71, 48, 58, 130, 255, 255, 255, 166, + 110, 73, 57, 62, 104, 210, 255, 255, + 251, 123, 65, 55, 68, 100, 171, 255 +}; + +static const opus_uint8 silk_NLSF_PRED_NB_MB_Q8[ 18 ] = { + 179, 138, 140, 148, 151, 149, 153, 151, + 163, 116, 67, 82, 59, 92, 72, 100, + 89, 92 +}; + +static const opus_int16 silk_NLSF_DELTA_MIN_NB_MB_Q15[ 11 ] = { + 250, 3, 6, 3, 3, 3, 4, 3, + 3, 3, 461 +}; + +const silk_NLSF_CB_struct silk_NLSF_CB_NB_MB = +{ + 32, + 10, + SILK_FIX_CONST( 0.18, 16 ), + SILK_FIX_CONST( 1.0 / 0.18, 6 ), + silk_NLSF_CB1_NB_MB_Q8, + silk_NLSF_CB1_Wght_Q9, + silk_NLSF_CB1_iCDF_NB_MB, + silk_NLSF_PRED_NB_MB_Q8, + silk_NLSF_CB2_SELECT_NB_MB, + silk_NLSF_CB2_iCDF_NB_MB, + silk_NLSF_CB2_BITS_NB_MB_Q5, + silk_NLSF_DELTA_MIN_NB_MB_Q15, +}; diff --git a/port/third_party/opus/silk/tables_NLSF_CB_WB.c b/port/third_party/opus/silk/tables_NLSF_CB_WB.c new file mode 100644 index 00000000..5cc9f57b --- /dev/null +++ b/port/third_party/opus/silk/tables_NLSF_CB_WB.c @@ -0,0 +1,234 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +static const opus_uint8 silk_NLSF_CB1_WB_Q8[ 512 ] = { + 7, 23, 38, 54, 69, 85, 100, 116, + 131, 147, 162, 178, 193, 208, 223, 239, + 13, 25, 41, 55, 69, 83, 98, 112, + 127, 142, 157, 171, 187, 203, 220, 236, + 15, 21, 34, 51, 61, 78, 92, 106, + 126, 136, 152, 167, 185, 205, 225, 240, + 10, 21, 36, 50, 63, 79, 95, 110, + 126, 141, 157, 173, 189, 205, 221, 237, + 17, 20, 37, 51, 59, 78, 89, 107, + 123, 134, 150, 164, 184, 205, 224, 240, + 10, 15, 32, 51, 67, 81, 96, 112, + 129, 142, 158, 173, 189, 204, 220, 236, + 8, 21, 37, 51, 65, 79, 98, 113, + 126, 138, 155, 168, 179, 192, 209, 218, + 12, 15, 34, 55, 63, 78, 87, 108, + 118, 131, 148, 167, 185, 203, 219, 236, + 16, 19, 32, 36, 56, 79, 91, 108, + 118, 136, 154, 171, 186, 204, 220, 237, + 11, 28, 43, 58, 74, 89, 105, 120, + 135, 150, 165, 180, 196, 211, 226, 241, + 6, 16, 33, 46, 60, 75, 92, 107, + 123, 137, 156, 169, 185, 199, 214, 225, + 11, 19, 30, 44, 57, 74, 89, 105, + 121, 135, 152, 169, 186, 202, 218, 234, + 12, 19, 29, 46, 57, 71, 88, 100, + 120, 132, 148, 165, 182, 199, 216, 233, + 17, 23, 35, 46, 56, 77, 92, 106, + 123, 134, 152, 167, 185, 204, 222, 237, + 14, 17, 45, 53, 63, 75, 89, 107, + 115, 132, 151, 171, 188, 206, 221, 240, + 9, 16, 29, 40, 56, 71, 88, 103, + 119, 137, 154, 171, 189, 205, 222, 237, + 16, 19, 36, 48, 57, 76, 87, 105, + 118, 132, 150, 167, 185, 202, 218, 236, + 12, 17, 29, 54, 71, 81, 94, 104, + 126, 136, 149, 164, 182, 201, 221, 237, + 15, 28, 47, 62, 79, 97, 115, 129, + 142, 155, 168, 180, 194, 208, 223, 238, + 8, 14, 30, 45, 62, 78, 94, 111, + 127, 143, 159, 175, 192, 207, 223, 239, + 17, 30, 49, 62, 79, 92, 107, 119, + 132, 145, 160, 174, 190, 204, 220, 235, + 14, 19, 36, 45, 61, 76, 91, 108, + 121, 138, 154, 172, 189, 205, 222, 238, + 12, 18, 31, 45, 60, 76, 91, 107, + 123, 138, 154, 171, 187, 204, 221, 236, + 13, 17, 31, 43, 53, 70, 83, 103, + 114, 131, 149, 167, 185, 203, 220, 237, + 17, 22, 35, 42, 58, 78, 93, 110, + 125, 139, 155, 170, 188, 206, 224, 240, + 8, 15, 34, 50, 67, 83, 99, 115, + 131, 146, 162, 178, 193, 209, 224, 239, + 13, 16, 41, 66, 73, 86, 95, 111, + 128, 137, 150, 163, 183, 206, 225, 241, + 17, 25, 37, 52, 63, 75, 92, 102, + 119, 132, 144, 160, 175, 191, 212, 231, + 19, 31, 49, 65, 83, 100, 117, 133, + 147, 161, 174, 187, 200, 213, 227, 242, + 18, 31, 52, 68, 88, 103, 117, 126, + 138, 149, 163, 177, 192, 207, 223, 239, + 16, 29, 47, 61, 76, 90, 106, 119, + 133, 147, 161, 176, 193, 209, 224, 240, + 15, 21, 35, 50, 61, 73, 86, 97, + 110, 119, 129, 141, 175, 198, 218, 237 +}; + +static const opus_int16 silk_NLSF_CB1_WB_Wght_Q9[ 512 ] = { + 3657, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2963, 2963, 2925, 2846, + 3216, 3085, 2972, 3056, 3056, 3010, 3010, 3010, 2963, 2963, 3010, 2972, 2888, 2846, 2846, 2726, + 3920, 4014, 2981, 3207, 3207, 2934, 3056, 2846, 3122, 3244, 2925, 2846, 2620, 2553, 2780, 2925, + 3516, 3197, 3010, 3103, 3019, 2888, 2925, 2925, 2925, 2925, 2888, 2888, 2888, 2888, 2888, 2753, + 5054, 5054, 2934, 3573, 3385, 3056, 3085, 2793, 3160, 3160, 2972, 2846, 2513, 2540, 2753, 2888, + 4428, 4149, 2700, 2753, 2972, 3010, 2925, 2846, 2981, 3019, 2925, 2925, 2925, 2925, 2888, 2726, + 3620, 3019, 2972, 3056, 3056, 2873, 2806, 3056, 3216, 3047, 2981, 3291, 3291, 2981, 3310, 2991, + 5227, 5014, 2540, 3338, 3526, 3385, 3197, 3094, 3376, 2981, 2700, 2647, 2687, 2793, 2846, 2673, + 5081, 5174, 4615, 4428, 2460, 2897, 3047, 3207, 3169, 2687, 2740, 2888, 2846, 2793, 2846, 2700, + 3122, 2888, 2963, 2925, 2925, 2925, 2925, 2963, 2963, 2963, 2963, 2925, 2925, 2963, 2963, 2963, + 4202, 3207, 2981, 3103, 3010, 2888, 2888, 2925, 2972, 2873, 2916, 3019, 2972, 3010, 3197, 2873, + 3760, 3760, 3244, 3103, 2981, 2888, 2925, 2888, 2972, 2934, 2793, 2793, 2846, 2888, 2888, 2660, + 3854, 4014, 3207, 3122, 3244, 2934, 3047, 2963, 2963, 3085, 2846, 2793, 2793, 2793, 2793, 2580, + 3845, 4080, 3357, 3516, 3094, 2740, 3010, 2934, 3122, 3085, 2846, 2846, 2647, 2647, 2846, 2806, + 5147, 4894, 3225, 3845, 3441, 3169, 2897, 3413, 3451, 2700, 2580, 2673, 2740, 2846, 2806, 2753, + 4109, 3789, 3291, 3160, 2925, 2888, 2888, 2925, 2793, 2740, 2793, 2740, 2793, 2846, 2888, 2806, + 5081, 5054, 3047, 3545, 3244, 3056, 3085, 2944, 3103, 2897, 2740, 2740, 2740, 2846, 2793, 2620, + 4309, 4309, 2860, 2527, 3207, 3376, 3376, 3075, 3075, 3376, 3056, 2846, 2647, 2580, 2726, 2753, + 3056, 2916, 2806, 2888, 2740, 2687, 2897, 3103, 3150, 3150, 3216, 3169, 3056, 3010, 2963, 2846, + 4375, 3882, 2925, 2888, 2846, 2888, 2846, 2846, 2888, 2888, 2888, 2846, 2888, 2925, 2888, 2846, + 2981, 2916, 2916, 2981, 2981, 3056, 3122, 3216, 3150, 3056, 3010, 2972, 2972, 2972, 2925, 2740, + 4229, 4149, 3310, 3347, 2925, 2963, 2888, 2981, 2981, 2846, 2793, 2740, 2846, 2846, 2846, 2793, + 4080, 4014, 3103, 3010, 2925, 2925, 2925, 2888, 2925, 2925, 2846, 2846, 2846, 2793, 2888, 2780, + 4615, 4575, 3169, 3441, 3207, 2981, 2897, 3038, 3122, 2740, 2687, 2687, 2687, 2740, 2793, 2700, + 4149, 4269, 3789, 3657, 2726, 2780, 2888, 2888, 3010, 2972, 2925, 2846, 2687, 2687, 2793, 2888, + 4215, 3554, 2753, 2846, 2846, 2888, 2888, 2888, 2925, 2925, 2888, 2925, 2925, 2925, 2963, 2888, + 5174, 4921, 2261, 3432, 3789, 3479, 3347, 2846, 3310, 3479, 3150, 2897, 2460, 2487, 2753, 2925, + 3451, 3685, 3122, 3197, 3357, 3047, 3207, 3207, 2981, 3216, 3085, 2925, 2925, 2687, 2540, 2434, + 2981, 3010, 2793, 2793, 2740, 2793, 2846, 2972, 3056, 3103, 3150, 3150, 3150, 3103, 3010, 3010, + 2944, 2873, 2687, 2726, 2780, 3010, 3432, 3545, 3357, 3244, 3056, 3010, 2963, 2925, 2888, 2846, + 3019, 2944, 2897, 3010, 3010, 2972, 3019, 3103, 3056, 3056, 3010, 2888, 2846, 2925, 2925, 2888, + 3920, 3967, 3010, 3197, 3357, 3216, 3291, 3291, 3479, 3704, 3441, 2726, 2181, 2460, 2580, 2607 +}; + +static const opus_uint8 silk_NLSF_CB1_iCDF_WB[ 64 ] = { + 225, 204, 201, 184, 183, 175, 158, 154, + 153, 135, 119, 115, 113, 110, 109, 99, + 98, 95, 79, 68, 52, 50, 48, 45, + 43, 32, 31, 27, 18, 10, 3, 0, + 255, 251, 235, 230, 212, 201, 196, 182, + 167, 166, 163, 151, 138, 124, 110, 104, + 90, 78, 76, 70, 69, 57, 45, 34, + 24, 21, 11, 6, 5, 4, 3, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_SELECT_WB[ 256 ] = { + 0, 0, 0, 0, 0, 0, 0, 1, + 100, 102, 102, 68, 68, 36, 34, 96, + 164, 107, 158, 185, 180, 185, 139, 102, + 64, 66, 36, 34, 34, 0, 1, 32, + 208, 139, 141, 191, 152, 185, 155, 104, + 96, 171, 104, 166, 102, 102, 102, 132, + 1, 0, 0, 0, 0, 16, 16, 0, + 80, 109, 78, 107, 185, 139, 103, 101, + 208, 212, 141, 139, 173, 153, 123, 103, + 36, 0, 0, 0, 0, 0, 0, 1, + 48, 0, 0, 0, 0, 0, 0, 32, + 68, 135, 123, 119, 119, 103, 69, 98, + 68, 103, 120, 118, 118, 102, 71, 98, + 134, 136, 157, 184, 182, 153, 139, 134, + 208, 168, 248, 75, 189, 143, 121, 107, + 32, 49, 34, 34, 34, 0, 17, 2, + 210, 235, 139, 123, 185, 137, 105, 134, + 98, 135, 104, 182, 100, 183, 171, 134, + 100, 70, 68, 70, 66, 66, 34, 131, + 64, 166, 102, 68, 36, 2, 1, 0, + 134, 166, 102, 68, 34, 34, 66, 132, + 212, 246, 158, 139, 107, 107, 87, 102, + 100, 219, 125, 122, 137, 118, 103, 132, + 114, 135, 137, 105, 171, 106, 50, 34, + 164, 214, 141, 143, 185, 151, 121, 103, + 192, 34, 0, 0, 0, 0, 0, 1, + 208, 109, 74, 187, 134, 249, 159, 137, + 102, 110, 154, 118, 87, 101, 119, 101, + 0, 2, 0, 36, 36, 66, 68, 35, + 96, 164, 102, 100, 36, 0, 2, 33, + 167, 138, 174, 102, 100, 84, 2, 2, + 100, 107, 120, 119, 36, 197, 24, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_iCDF_WB[ 72 ] = { + 255, 254, 253, 244, 12, 3, 2, 1, + 0, 255, 254, 252, 224, 38, 3, 2, + 1, 0, 255, 254, 251, 209, 57, 4, + 2, 1, 0, 255, 254, 244, 195, 69, + 4, 2, 1, 0, 255, 251, 232, 184, + 84, 7, 2, 1, 0, 255, 254, 240, + 186, 86, 14, 2, 1, 0, 255, 254, + 239, 178, 91, 30, 5, 1, 0, 255, + 248, 227, 177, 100, 19, 2, 1, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_BITS_WB_Q5[ 72 ] = { + 255, 255, 255, 156, 4, 154, 255, 255, + 255, 255, 255, 227, 102, 15, 92, 255, + 255, 255, 255, 255, 213, 83, 24, 72, + 236, 255, 255, 255, 255, 150, 76, 33, + 63, 214, 255, 255, 255, 190, 121, 77, + 43, 55, 185, 255, 255, 255, 245, 137, + 71, 43, 59, 139, 255, 255, 255, 255, + 131, 66, 50, 66, 107, 194, 255, 255, + 166, 116, 76, 55, 53, 125, 255, 255 +}; + +static const opus_uint8 silk_NLSF_PRED_WB_Q8[ 30 ] = { + 175, 148, 160, 176, 178, 173, 174, 164, + 177, 174, 196, 182, 198, 192, 182, 68, + 62, 66, 60, 72, 117, 85, 90, 118, + 136, 151, 142, 160, 142, 155 +}; + +static const opus_int16 silk_NLSF_DELTA_MIN_WB_Q15[ 17 ] = { + 100, 3, 40, 3, 3, 3, 5, 14, + 14, 10, 11, 3, 8, 9, 7, 3, + 347 +}; + +const silk_NLSF_CB_struct silk_NLSF_CB_WB = +{ + 32, + 16, + SILK_FIX_CONST( 0.15, 16 ), + SILK_FIX_CONST( 1.0 / 0.15, 6 ), + silk_NLSF_CB1_WB_Q8, + silk_NLSF_CB1_WB_Wght_Q9, + silk_NLSF_CB1_iCDF_WB, + silk_NLSF_PRED_WB_Q8, + silk_NLSF_CB2_SELECT_WB, + silk_NLSF_CB2_iCDF_WB, + silk_NLSF_CB2_BITS_WB_Q5, + silk_NLSF_DELTA_MIN_WB_Q15, +}; + diff --git a/port/third_party/opus/silk/tables_gain.c b/port/third_party/opus/silk/tables_gain.c new file mode 100644 index 00000000..37e41d89 --- /dev/null +++ b/port/third_party/opus/silk/tables_gain.c @@ -0,0 +1,63 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +const opus_uint8 silk_gain_iCDF[ 3 ][ N_LEVELS_QGAIN / 8 ] = +{ +{ + 224, 112, 44, 15, 3, 2, 1, 0 +}, +{ + 254, 237, 192, 132, 70, 23, 4, 0 +}, +{ + 255, 252, 226, 155, 61, 11, 2, 0 +} +}; + +const opus_uint8 silk_delta_gain_iCDF[ MAX_DELTA_GAIN_QUANT - MIN_DELTA_GAIN_QUANT + 1 ] = { + 250, 245, 234, 203, 71, 50, 42, 38, + 35, 33, 31, 29, 28, 27, 26, 25, + 24, 23, 22, 21, 20, 19, 18, 17, + 16, 15, 14, 13, 12, 11, 10, 9, + 8, 7, 6, 5, 4, 3, 2, 1, + 0 +}; + +#ifdef __cplusplus +} +#endif diff --git a/port/third_party/opus/silk/tables_other.c b/port/third_party/opus/silk/tables_other.c new file mode 100644 index 00000000..e34d9077 --- /dev/null +++ b/port/third_party/opus/silk/tables_other.c @@ -0,0 +1,124 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "structs.h" +#include "define.h" +#include "tables.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Tables for stereo predictor coding */ +const opus_int16 silk_stereo_pred_quant_Q13[ STEREO_QUANT_TAB_SIZE ] = { + -13732, -10050, -8266, -7526, -6500, -5000, -2950, -820, + 820, 2950, 5000, 6500, 7526, 8266, 10050, 13732 +}; +const opus_uint8 silk_stereo_pred_joint_iCDF[ 25 ] = { + 249, 247, 246, 245, 244, + 234, 210, 202, 201, 200, + 197, 174, 82, 59, 56, + 55, 54, 46, 22, 12, + 11, 10, 9, 7, 0 +}; +const opus_uint8 silk_stereo_only_code_mid_iCDF[ 2 ] = { 64, 0 }; + +/* Tables for LBRR flags */ +static const opus_uint8 silk_LBRR_flags_2_iCDF[ 3 ] = { 203, 150, 0 }; +static const opus_uint8 silk_LBRR_flags_3_iCDF[ 7 ] = { 215, 195, 166, 125, 110, 82, 0 }; +const opus_uint8 * const silk_LBRR_flags_iCDF_ptr[ 2 ] = { + silk_LBRR_flags_2_iCDF, + silk_LBRR_flags_3_iCDF +}; + +/* Table for LSB coding */ +const opus_uint8 silk_lsb_iCDF[ 2 ] = { 120, 0 }; + +/* Tables for LTPScale */ +const opus_uint8 silk_LTPscale_iCDF[ 3 ] = { 128, 64, 0 }; + +/* Tables for signal type and offset coding */ +const opus_uint8 silk_type_offset_VAD_iCDF[ 4 ] = { + 232, 158, 10, 0 +}; +const opus_uint8 silk_type_offset_no_VAD_iCDF[ 2 ] = { + 230, 0 +}; + +/* Tables for NLSF interpolation factor */ +const opus_uint8 silk_NLSF_interpolation_factor_iCDF[ 5 ] = { 243, 221, 192, 181, 0 }; + +/* Quantization offsets */ +const opus_int16 silk_Quantization_Offsets_Q10[ 2 ][ 2 ] = { + { OFFSET_UVL_Q10, OFFSET_UVH_Q10 }, { OFFSET_VL_Q10, OFFSET_VH_Q10 } +}; + +/* Table for LTPScale */ +const opus_int16 silk_LTPScales_table_Q14[ 3 ] = { 15565, 12288, 8192 }; + +/* Uniform entropy tables */ +const opus_uint8 silk_uniform3_iCDF[ 3 ] = { 171, 85, 0 }; +const opus_uint8 silk_uniform4_iCDF[ 4 ] = { 192, 128, 64, 0 }; +const opus_uint8 silk_uniform5_iCDF[ 5 ] = { 205, 154, 102, 51, 0 }; +const opus_uint8 silk_uniform6_iCDF[ 6 ] = { 213, 171, 128, 85, 43, 0 }; +const opus_uint8 silk_uniform8_iCDF[ 8 ] = { 224, 192, 160, 128, 96, 64, 32, 0 }; + +const opus_uint8 silk_NLSF_EXT_iCDF[ 7 ] = { 100, 40, 16, 7, 3, 1, 0 }; + +/* Elliptic/Cauer filters designed with 0.1 dB passband ripple, + 80 dB minimum stopband attenuation, and + [0.95 : 0.15 : 0.35] normalized cut off frequencies. */ + +/* Interpolation points for filter coefficients used in the bandwidth transition smoother */ +const opus_int32 silk_Transition_LP_B_Q28[ TRANSITION_INT_NUM ][ TRANSITION_NB ] = +{ +{ 250767114, 501534038, 250767114 }, +{ 209867381, 419732057, 209867381 }, +{ 170987846, 341967853, 170987846 }, +{ 131531482, 263046905, 131531482 }, +{ 89306658, 178584282, 89306658 } +}; + +/* Interpolation points for filter coefficients used in the bandwidth transition smoother */ +const opus_int32 silk_Transition_LP_A_Q28[ TRANSITION_INT_NUM ][ TRANSITION_NA ] = +{ +{ 506393414, 239854379 }, +{ 411067935, 169683996 }, +{ 306733530, 116694253 }, +{ 185807084, 77959395 }, +{ 35497197, 57401098 } +}; + +#ifdef __cplusplus +} +#endif + diff --git a/port/third_party/opus/silk/tables_pitch_lag.c b/port/third_party/opus/silk/tables_pitch_lag.c new file mode 100644 index 00000000..e80cc59a --- /dev/null +++ b/port/third_party/opus/silk/tables_pitch_lag.c @@ -0,0 +1,69 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +const opus_uint8 silk_pitch_lag_iCDF[ 2 * ( PITCH_EST_MAX_LAG_MS - PITCH_EST_MIN_LAG_MS ) ] = { + 253, 250, 244, 233, 212, 182, 150, 131, + 120, 110, 98, 85, 72, 60, 49, 40, + 32, 25, 19, 15, 13, 11, 9, 8, + 7, 6, 5, 4, 3, 2, 1, 0 +}; + +const opus_uint8 silk_pitch_delta_iCDF[21] = { + 210, 208, 206, 203, 199, 193, 183, 168, + 142, 104, 74, 52, 37, 27, 20, 14, + 10, 6, 4, 2, 0 +}; + +const opus_uint8 silk_pitch_contour_iCDF[34] = { + 223, 201, 183, 167, 152, 138, 124, 111, + 98, 88, 79, 70, 62, 56, 50, 44, + 39, 35, 31, 27, 24, 21, 18, 16, + 14, 12, 10, 8, 6, 4, 3, 2, + 1, 0 +}; + +const opus_uint8 silk_pitch_contour_NB_iCDF[11] = { + 188, 176, 155, 138, 119, 97, 67, 43, + 26, 10, 0 +}; + +const opus_uint8 silk_pitch_contour_10_ms_iCDF[12] = { + 165, 119, 80, 61, 47, 35, 27, 20, + 14, 9, 4, 0 +}; + +const opus_uint8 silk_pitch_contour_10_ms_NB_iCDF[3] = { + 113, 63, 0 +}; + + diff --git a/port/third_party/opus/silk/tables_pulses_per_block.c b/port/third_party/opus/silk/tables_pulses_per_block.c new file mode 100644 index 00000000..c7c01c88 --- /dev/null +++ b/port/third_party/opus/silk/tables_pulses_per_block.c @@ -0,0 +1,264 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +const opus_uint8 silk_max_pulses_table[ 4 ] = { + 8, 10, 12, 16 +}; + +const opus_uint8 silk_pulses_per_block_iCDF[ 10 ][ 18 ] = { +{ + 125, 51, 26, 18, 15, 12, 11, 10, + 9, 8, 7, 6, 5, 4, 3, 2, + 1, 0 +}, +{ + 198, 105, 45, 22, 15, 12, 11, 10, + 9, 8, 7, 6, 5, 4, 3, 2, + 1, 0 +}, +{ + 213, 162, 116, 83, 59, 43, 32, 24, + 18, 15, 12, 9, 7, 6, 5, 3, + 2, 0 +}, +{ + 239, 187, 116, 59, 28, 16, 11, 10, + 9, 8, 7, 6, 5, 4, 3, 2, + 1, 0 +}, +{ + 250, 229, 188, 135, 86, 51, 30, 19, + 13, 10, 8, 6, 5, 4, 3, 2, + 1, 0 +}, +{ + 249, 235, 213, 185, 156, 128, 103, 83, + 66, 53, 42, 33, 26, 21, 17, 13, + 10, 0 +}, +{ + 254, 249, 235, 206, 164, 118, 77, 46, + 27, 16, 10, 7, 5, 4, 3, 2, + 1, 0 +}, +{ + 255, 253, 249, 239, 220, 191, 156, 119, + 85, 57, 37, 23, 15, 10, 6, 4, + 2, 0 +}, +{ + 255, 253, 251, 246, 237, 223, 203, 179, + 152, 124, 98, 75, 55, 40, 29, 21, + 15, 0 +}, +{ + 255, 254, 253, 247, 220, 162, 106, 67, + 42, 28, 18, 12, 9, 6, 4, 3, + 2, 0 +} +}; + +const opus_uint8 silk_pulses_per_block_BITS_Q5[ 9 ][ 18 ] = { +{ + 31, 57, 107, 160, 205, 205, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}, +{ + 69, 47, 67, 111, 166, 205, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}, +{ + 82, 74, 79, 95, 109, 128, 145, 160, + 173, 205, 205, 205, 224, 255, 255, 224, + 255, 224 +}, +{ + 125, 74, 59, 69, 97, 141, 182, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}, +{ + 173, 115, 85, 73, 76, 92, 115, 145, + 173, 205, 224, 224, 255, 255, 255, 255, + 255, 255 +}, +{ + 166, 134, 113, 102, 101, 102, 107, 118, + 125, 138, 145, 155, 166, 182, 192, 192, + 205, 150 +}, +{ + 224, 182, 134, 101, 83, 79, 85, 97, + 120, 145, 173, 205, 224, 255, 255, 255, + 255, 255 +}, +{ + 255, 224, 192, 150, 120, 101, 92, 89, + 93, 102, 118, 134, 160, 182, 192, 224, + 224, 224 +}, +{ + 255, 224, 224, 182, 155, 134, 118, 109, + 104, 102, 106, 111, 118, 131, 145, 160, + 173, 131 +} +}; + +const opus_uint8 silk_rate_levels_iCDF[ 2 ][ 9 ] = +{ +{ + 241, 190, 178, 132, 87, 74, 41, 14, + 0 +}, +{ + 223, 193, 157, 140, 106, 57, 39, 18, + 0 +} +}; + +const opus_uint8 silk_rate_levels_BITS_Q5[ 2 ][ 9 ] = +{ +{ + 131, 74, 141, 79, 80, 138, 95, 104, + 134 +}, +{ + 95, 99, 91, 125, 93, 76, 123, 115, + 123 +} +}; + +const opus_uint8 silk_shell_code_table0[ 152 ] = { + 128, 0, 214, 42, 0, 235, 128, 21, + 0, 244, 184, 72, 11, 0, 248, 214, + 128, 42, 7, 0, 248, 225, 170, 80, + 25, 5, 0, 251, 236, 198, 126, 54, + 18, 3, 0, 250, 238, 211, 159, 82, + 35, 15, 5, 0, 250, 231, 203, 168, + 128, 88, 53, 25, 6, 0, 252, 238, + 216, 185, 148, 108, 71, 40, 18, 4, + 0, 253, 243, 225, 199, 166, 128, 90, + 57, 31, 13, 3, 0, 254, 246, 233, + 212, 183, 147, 109, 73, 44, 23, 10, + 2, 0, 255, 250, 240, 223, 198, 166, + 128, 90, 58, 33, 16, 6, 1, 0, + 255, 251, 244, 231, 210, 181, 146, 110, + 75, 46, 25, 12, 5, 1, 0, 255, + 253, 248, 238, 221, 196, 164, 128, 92, + 60, 35, 18, 8, 3, 1, 0, 255, + 253, 249, 242, 229, 208, 180, 146, 110, + 76, 48, 27, 14, 7, 3, 1, 0 +}; + +const opus_uint8 silk_shell_code_table1[ 152 ] = { + 129, 0, 207, 50, 0, 236, 129, 20, + 0, 245, 185, 72, 10, 0, 249, 213, + 129, 42, 6, 0, 250, 226, 169, 87, + 27, 4, 0, 251, 233, 194, 130, 62, + 20, 4, 0, 250, 236, 207, 160, 99, + 47, 17, 3, 0, 255, 240, 217, 182, + 131, 81, 41, 11, 1, 0, 255, 254, + 233, 201, 159, 107, 61, 20, 2, 1, + 0, 255, 249, 233, 206, 170, 128, 86, + 50, 23, 7, 1, 0, 255, 250, 238, + 217, 186, 148, 108, 70, 39, 18, 6, + 1, 0, 255, 252, 243, 226, 200, 166, + 128, 90, 56, 30, 13, 4, 1, 0, + 255, 252, 245, 231, 209, 180, 146, 110, + 76, 47, 25, 11, 4, 1, 0, 255, + 253, 248, 237, 219, 194, 163, 128, 93, + 62, 37, 19, 8, 3, 1, 0, 255, + 254, 250, 241, 226, 205, 177, 145, 111, + 79, 51, 30, 15, 6, 2, 1, 0 +}; + +const opus_uint8 silk_shell_code_table2[ 152 ] = { + 129, 0, 203, 54, 0, 234, 129, 23, + 0, 245, 184, 73, 10, 0, 250, 215, + 129, 41, 5, 0, 252, 232, 173, 86, + 24, 3, 0, 253, 240, 200, 129, 56, + 15, 2, 0, 253, 244, 217, 164, 94, + 38, 10, 1, 0, 253, 245, 226, 189, + 132, 71, 27, 7, 1, 0, 253, 246, + 231, 203, 159, 105, 56, 23, 6, 1, + 0, 255, 248, 235, 213, 179, 133, 85, + 47, 19, 5, 1, 0, 255, 254, 243, + 221, 194, 159, 117, 70, 37, 12, 2, + 1, 0, 255, 254, 248, 234, 208, 171, + 128, 85, 48, 22, 8, 2, 1, 0, + 255, 254, 250, 240, 220, 189, 149, 107, + 67, 36, 16, 6, 2, 1, 0, 255, + 254, 251, 243, 227, 201, 166, 128, 90, + 55, 29, 13, 5, 2, 1, 0, 255, + 254, 252, 246, 234, 213, 183, 147, 109, + 73, 43, 22, 10, 4, 2, 1, 0 +}; + +const opus_uint8 silk_shell_code_table3[ 152 ] = { + 130, 0, 200, 58, 0, 231, 130, 26, + 0, 244, 184, 76, 12, 0, 249, 214, + 130, 43, 6, 0, 252, 232, 173, 87, + 24, 3, 0, 253, 241, 203, 131, 56, + 14, 2, 0, 254, 246, 221, 167, 94, + 35, 8, 1, 0, 254, 249, 232, 193, + 130, 65, 23, 5, 1, 0, 255, 251, + 239, 211, 162, 99, 45, 15, 4, 1, + 0, 255, 251, 243, 223, 186, 131, 74, + 33, 11, 3, 1, 0, 255, 252, 245, + 230, 202, 158, 105, 57, 24, 8, 2, + 1, 0, 255, 253, 247, 235, 214, 179, + 132, 84, 44, 19, 7, 2, 1, 0, + 255, 254, 250, 240, 223, 196, 159, 112, + 69, 36, 15, 6, 2, 1, 0, 255, + 254, 253, 245, 231, 209, 176, 136, 93, + 55, 27, 11, 3, 2, 1, 0, 255, + 254, 253, 252, 239, 221, 194, 158, 117, + 76, 42, 18, 4, 3, 2, 1, 0 +}; + +const opus_uint8 silk_shell_code_table_offsets[ 17 ] = { + 0, 0, 2, 5, 9, 14, 20, 27, + 35, 44, 54, 65, 77, 90, 104, 119, + 135 +}; + +const opus_uint8 silk_sign_iCDF[ 42 ] = { + 254, 49, 67, 77, 82, 93, 99, + 198, 11, 18, 24, 31, 36, 45, + 255, 46, 66, 78, 87, 94, 104, + 208, 14, 21, 32, 42, 51, 66, + 255, 94, 104, 109, 112, 115, 118, + 248, 53, 69, 80, 88, 95, 102 +}; diff --git a/port/third_party/opus/silk/tuning_parameters.h b/port/third_party/opus/silk/tuning_parameters.h new file mode 100644 index 00000000..d70275fd --- /dev/null +++ b/port/third_party/opus/silk/tuning_parameters.h @@ -0,0 +1,155 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_TUNING_PARAMETERS_H +#define SILK_TUNING_PARAMETERS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Decay time for bitreservoir */ +#define BITRESERVOIR_DECAY_TIME_MS 500 + +/*******************/ +/* Pitch estimator */ +/*******************/ + +/* Level of noise floor for whitening filter LPC analysis in pitch analysis */ +#define FIND_PITCH_WHITE_NOISE_FRACTION 1e-3f + +/* Bandwidth expansion for whitening filter in pitch analysis */ +#define FIND_PITCH_BANDWIDTH_EXPANSION 0.99f + +/*********************/ +/* Linear prediction */ +/*********************/ + +/* LPC analysis regularization */ +#define FIND_LPC_COND_FAC 1e-5f + +/* Max cumulative LTP gain */ +#define MAX_SUM_LOG_GAIN_DB 250.0f + +/* LTP analysis defines */ +#define LTP_CORR_INV_MAX 0.03f + +/***********************/ +/* High pass filtering */ +/***********************/ + +/* Smoothing parameters for low end of pitch frequency range estimation */ +#define VARIABLE_HP_SMTH_COEF1 0.1f +#define VARIABLE_HP_SMTH_COEF2 0.015f +#define VARIABLE_HP_MAX_DELTA_FREQ 0.4f + +/* Min and max cut-off frequency values (-3 dB points) */ +#define VARIABLE_HP_MIN_CUTOFF_HZ 60 +#define VARIABLE_HP_MAX_CUTOFF_HZ 100 + +/***********/ +/* Various */ +/***********/ + +/* VAD threshold */ +#define SPEECH_ACTIVITY_DTX_THRES 0.05f + +/* Speech Activity LBRR enable threshold */ +#define LBRR_SPEECH_ACTIVITY_THRES 0.3f + +/*************************/ +/* Perceptual parameters */ +/*************************/ + +/* reduction in coding SNR during low speech activity */ +#define BG_SNR_DECR_dB 2.0f + +/* factor for reducing quantization noise during voiced speech */ +#define HARM_SNR_INCR_dB 2.0f + +/* factor for reducing quantization noise for unvoiced sparse signals */ +#define SPARSE_SNR_INCR_dB 2.0f + +/* threshold for sparseness measure above which to use lower quantization offset during unvoiced */ +#define ENERGY_VARIATION_THRESHOLD_QNT_OFFSET 0.6f + +/* warping control */ +#define WARPING_MULTIPLIER 0.015f + +/* fraction added to first autocorrelation value */ +#define SHAPE_WHITE_NOISE_FRACTION 3e-5f + +/* noise shaping filter chirp factor */ +#define BANDWIDTH_EXPANSION 0.94f + +/* harmonic noise shaping */ +#define HARMONIC_SHAPING 0.3f + +/* extra harmonic noise shaping for high bitrates or noisy input */ +#define HIGH_RATE_OR_LOW_QUALITY_HARMONIC_SHAPING 0.2f + +/* parameter for shaping noise towards higher frequencies */ +#define HP_NOISE_COEF 0.25f + +/* parameter for shaping noise even more towards higher frequencies during voiced speech */ +#define HARM_HP_NOISE_COEF 0.35f + +/* parameter for applying a high-pass tilt to the input signal */ +#define INPUT_TILT 0.05f + +/* parameter for extra high-pass tilt to the input signal at high rates */ +#define HIGH_RATE_INPUT_TILT 0.1f + +/* parameter for reducing noise at the very low frequencies */ +#define LOW_FREQ_SHAPING 4.0f + +/* less reduction of noise at the very low frequencies for signals with low SNR at low frequencies */ +#define LOW_QUALITY_LOW_FREQ_SHAPING_DECR 0.5f + +/* subframe smoothing coefficient for HarmBoost, HarmShapeGain, Tilt (lower -> more smoothing) */ +#define SUBFR_SMTH_COEF 0.4f + +/* parameters defining the R/D tradeoff in the residual quantizer */ +#define LAMBDA_OFFSET 1.2f +#define LAMBDA_SPEECH_ACT -0.2f +#define LAMBDA_DELAYED_DECISIONS -0.05f +#define LAMBDA_INPUT_QUALITY -0.1f +#define LAMBDA_CODING_QUALITY -0.2f +#define LAMBDA_QUANT_OFFSET 0.8f + +/* Compensation in bitrate calculations for 10 ms modes */ +#define REDUCE_BITRATE_10_MS_BPS 2200 + +/* Maximum time before allowing a bandwidth transition */ +#define MAX_BANDWIDTH_SWITCH_DELAY_MS 5000 + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_TUNING_PARAMETERS_H */ diff --git a/port/third_party/opus/silk/typedef.h b/port/third_party/opus/silk/typedef.h new file mode 100644 index 00000000..793d2c0c --- /dev/null +++ b/port/third_party/opus/silk/typedef.h @@ -0,0 +1,81 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the +names of specific 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 COPYRIGHT OWNER 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. +***********************************************************************/ + +#ifndef SILK_TYPEDEF_H +#define SILK_TYPEDEF_H + +#include "opus_types.h" +#include "opus_defines.h" + +#ifndef FIXED_POINT +# include +# define silk_float float +# define silk_float_MAX FLT_MAX +#endif + +#define silk_int64_MAX ((opus_int64)0x7FFFFFFFFFFFFFFFLL) /* 2^63 - 1 */ +#define silk_int64_MIN ((opus_int64)0x8000000000000000LL) /* -2^63 */ +#define silk_int32_MAX 0x7FFFFFFF /* 2^31 - 1 = 2147483647 */ +#define silk_int32_MIN ((opus_int32)0x80000000) /* -2^31 = -2147483648 */ +#define silk_int16_MAX 0x7FFF /* 2^15 - 1 = 32767 */ +#define silk_int16_MIN ((opus_int16)0x8000) /* -2^15 = -32768 */ +#define silk_int8_MAX 0x7F /* 2^7 - 1 = 127 */ +#define silk_int8_MIN ((opus_int8)0x80) /* -2^7 = -128 */ +#define silk_uint8_MAX 0xFF /* 2^8 - 1 = 255 */ + +#define silk_TRUE 1 +#define silk_FALSE 0 + +/* assertions */ +#if (defined _WIN32 && !defined _WINCE && !defined(__GNUC__) && !defined(NO_ASSERTS)) +# ifndef silk_assert +# include /* ASSERTE() */ +# define silk_assert(COND) _ASSERTE(COND) +# endif +#else +# ifdef ENABLE_ASSERTIONS +# include +# include +#define silk_fatal(str) _silk_fatal(str, __FILE__, __LINE__); +#ifdef __GNUC__ +__attribute__((noreturn)) +#endif +static OPUS_INLINE void _silk_fatal(const char *str, const char *file, int line) +{ + fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str); +#if defined(_MSC_VER) + _set_abort_behavior( 0, _WRITE_ABORT_MSG); +#endif + abort(); +} +# define silk_assert(COND) {if (!(COND)) {silk_fatal("assertion failed: " #COND);}} +# else +# define silk_assert(COND) +# endif +#endif + +#endif /* SILK_TYPEDEF_H */ diff --git a/port/third_party/opus/sources.txt b/port/third_party/opus/sources.txt new file mode 100644 index 00000000..e7115dcb --- /dev/null +++ b/port/third_party/opus/sources.txt @@ -0,0 +1,131 @@ +src/opus.c +src/opus_decoder.c +src/opus_encoder.c +src/extensions.c +src/repacketizer.c +src/analysis.c +src/mlp.c +src/mlp_data.c +celt/bands.c +celt/celt.c +celt/celt_encoder.c +celt/celt_decoder.c +celt/cwrs.c +celt/entcode.c +celt/entdec.c +celt/entenc.c +celt/kiss_fft.c +celt/laplace.c +celt/mathops.c +celt/mdct.c +celt/modes.c +celt/pitch.c +celt/celt_lpc.c +celt/quant_bands.c +celt/rate.c +celt/vq.c +silk/CNG.c +silk/code_signs.c +silk/init_decoder.c +silk/decode_core.c +silk/decode_frame.c +silk/decode_parameters.c +silk/decode_indices.c +silk/decode_pulses.c +silk/decoder_set_fs.c +silk/dec_API.c +silk/enc_API.c +silk/encode_indices.c +silk/encode_pulses.c +silk/gain_quant.c +silk/interpolate.c +silk/LP_variable_cutoff.c +silk/NLSF_decode.c +silk/NSQ.c +silk/NSQ_del_dec.c +silk/PLC.c +silk/shell_coder.c +silk/tables_gain.c +silk/tables_LTP.c +silk/tables_NLSF_CB_NB_MB.c +silk/tables_NLSF_CB_WB.c +silk/tables_other.c +silk/tables_pitch_lag.c +silk/tables_pulses_per_block.c +silk/VAD.c +silk/control_audio_bandwidth.c +silk/quant_LTP_gains.c +silk/VQ_WMat_EC.c +silk/HP_variable_cutoff.c +silk/NLSF_encode.c +silk/NLSF_VQ.c +silk/NLSF_unpack.c +silk/NLSF_del_dec_quant.c +silk/process_NLSFs.c +silk/stereo_LR_to_MS.c +silk/stereo_MS_to_LR.c +silk/check_control_input.c +silk/control_SNR.c +silk/init_encoder.c +silk/control_codec.c +silk/A2NLSF.c +silk/ana_filt_bank_1.c +silk/biquad_alt.c +silk/bwexpander_32.c +silk/bwexpander.c +silk/debug.c +silk/decode_pitch.c +silk/inner_prod_aligned.c +silk/lin2log.c +silk/log2lin.c +silk/LPC_analysis_filter.c +silk/LPC_inv_pred_gain.c +silk/table_LSF_cos.c +silk/NLSF2A.c +silk/NLSF_stabilize.c +silk/NLSF_VQ_weights_laroia.c +silk/pitch_est_tables.c +silk/resampler.c +silk/resampler_down2_3.c +silk/resampler_down2.c +silk/resampler_private_AR2.c +silk/resampler_private_down_FIR.c +silk/resampler_private_IIR_FIR.c +silk/resampler_private_up2_HQ.c +silk/resampler_rom.c +silk/sigm_Q15.c +silk/sort.c +silk/sum_sqr_shift.c +silk/stereo_decode_pred.c +silk/stereo_encode_pred.c +silk/stereo_find_predictor.c +silk/stereo_quant_pred.c +silk/LPC_fit.c +silk/float/apply_sine_window_FLP.c +silk/float/corrMatrix_FLP.c +silk/float/encode_frame_FLP.c +silk/float/find_LPC_FLP.c +silk/float/find_LTP_FLP.c +silk/float/find_pitch_lags_FLP.c +silk/float/find_pred_coefs_FLP.c +silk/float/LPC_analysis_filter_FLP.c +silk/float/LTP_analysis_filter_FLP.c +silk/float/LTP_scale_ctrl_FLP.c +silk/float/noise_shape_analysis_FLP.c +silk/float/process_gains_FLP.c +silk/float/regularize_correlations_FLP.c +silk/float/residual_energy_FLP.c +silk/float/warped_autocorrelation_FLP.c +silk/float/wrappers_FLP.c +silk/float/autocorrelation_FLP.c +silk/float/burg_modified_FLP.c +silk/float/bwexpander_FLP.c +silk/float/energy_FLP.c +silk/float/inner_product_FLP.c +silk/float/k2a_FLP.c +silk/float/LPC_inv_pred_gain_FLP.c +silk/float/pitch_analysis_core_FLP.c +silk/float/scale_copy_vector_FLP.c +silk/float/scale_vector_FLP.c +silk/float/schur_FLP.c +silk/float/sort_FLP.c diff --git a/port/third_party/opus/src/analysis.c b/port/third_party/opus/src/analysis.c new file mode 100644 index 00000000..1f580138 --- /dev/null +++ b/port/third_party/opus/src/analysis.c @@ -0,0 +1,983 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + 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 FOUNDATION 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define ANALYSIS_C + +#ifdef MLP_TRAINING +#include +#endif + +#include "mathops.h" +#include "kiss_fft.h" +#include "celt.h" +#include "modes.h" +#include "arch.h" +#include "quant_bands.h" +#include "analysis.h" +#include "mlp.h" +#include "stack_alloc.h" +#include "float_cast.h" + +#ifndef M_PI +#define M_PI 3.141592653 +#endif + +#ifndef DISABLE_FLOAT_API + +#define TRANSITION_PENALTY 10 + +static const float dct_table[128] = { + 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, + 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, + 0.351851f, 0.338330f, 0.311806f, 0.273300f, 0.224292f, 0.166664f, 0.102631f, 0.034654f, + -0.034654f,-0.102631f,-0.166664f,-0.224292f,-0.273300f,-0.311806f,-0.338330f,-0.351851f, + 0.346760f, 0.293969f, 0.196424f, 0.068975f,-0.068975f,-0.196424f,-0.293969f,-0.346760f, + -0.346760f,-0.293969f,-0.196424f,-0.068975f, 0.068975f, 0.196424f, 0.293969f, 0.346760f, + 0.338330f, 0.224292f, 0.034654f,-0.166664f,-0.311806f,-0.351851f,-0.273300f,-0.102631f, + 0.102631f, 0.273300f, 0.351851f, 0.311806f, 0.166664f,-0.034654f,-0.224292f,-0.338330f, + 0.326641f, 0.135299f,-0.135299f,-0.326641f,-0.326641f,-0.135299f, 0.135299f, 0.326641f, + 0.326641f, 0.135299f,-0.135299f,-0.326641f,-0.326641f,-0.135299f, 0.135299f, 0.326641f, + 0.311806f, 0.034654f,-0.273300f,-0.338330f,-0.102631f, 0.224292f, 0.351851f, 0.166664f, + -0.166664f,-0.351851f,-0.224292f, 0.102631f, 0.338330f, 0.273300f,-0.034654f,-0.311806f, + 0.293969f,-0.068975f,-0.346760f,-0.196424f, 0.196424f, 0.346760f, 0.068975f,-0.293969f, + -0.293969f, 0.068975f, 0.346760f, 0.196424f,-0.196424f,-0.346760f,-0.068975f, 0.293969f, + 0.273300f,-0.166664f,-0.338330f, 0.034654f, 0.351851f, 0.102631f,-0.311806f,-0.224292f, + 0.224292f, 0.311806f,-0.102631f,-0.351851f,-0.034654f, 0.338330f, 0.166664f,-0.273300f, +}; + +static const float analysis_window[240] = { + 0.000043f, 0.000171f, 0.000385f, 0.000685f, 0.001071f, 0.001541f, 0.002098f, 0.002739f, + 0.003466f, 0.004278f, 0.005174f, 0.006156f, 0.007222f, 0.008373f, 0.009607f, 0.010926f, + 0.012329f, 0.013815f, 0.015385f, 0.017037f, 0.018772f, 0.020590f, 0.022490f, 0.024472f, + 0.026535f, 0.028679f, 0.030904f, 0.033210f, 0.035595f, 0.038060f, 0.040604f, 0.043227f, + 0.045928f, 0.048707f, 0.051564f, 0.054497f, 0.057506f, 0.060591f, 0.063752f, 0.066987f, + 0.070297f, 0.073680f, 0.077136f, 0.080665f, 0.084265f, 0.087937f, 0.091679f, 0.095492f, + 0.099373f, 0.103323f, 0.107342f, 0.111427f, 0.115579f, 0.119797f, 0.124080f, 0.128428f, + 0.132839f, 0.137313f, 0.141849f, 0.146447f, 0.151105f, 0.155823f, 0.160600f, 0.165435f, + 0.170327f, 0.175276f, 0.180280f, 0.185340f, 0.190453f, 0.195619f, 0.200838f, 0.206107f, + 0.211427f, 0.216797f, 0.222215f, 0.227680f, 0.233193f, 0.238751f, 0.244353f, 0.250000f, + 0.255689f, 0.261421f, 0.267193f, 0.273005f, 0.278856f, 0.284744f, 0.290670f, 0.296632f, + 0.302628f, 0.308658f, 0.314721f, 0.320816f, 0.326941f, 0.333097f, 0.339280f, 0.345492f, + 0.351729f, 0.357992f, 0.364280f, 0.370590f, 0.376923f, 0.383277f, 0.389651f, 0.396044f, + 0.402455f, 0.408882f, 0.415325f, 0.421783f, 0.428254f, 0.434737f, 0.441231f, 0.447736f, + 0.454249f, 0.460770f, 0.467298f, 0.473832f, 0.480370f, 0.486912f, 0.493455f, 0.500000f, + 0.506545f, 0.513088f, 0.519630f, 0.526168f, 0.532702f, 0.539230f, 0.545751f, 0.552264f, + 0.558769f, 0.565263f, 0.571746f, 0.578217f, 0.584675f, 0.591118f, 0.597545f, 0.603956f, + 0.610349f, 0.616723f, 0.623077f, 0.629410f, 0.635720f, 0.642008f, 0.648271f, 0.654508f, + 0.660720f, 0.666903f, 0.673059f, 0.679184f, 0.685279f, 0.691342f, 0.697372f, 0.703368f, + 0.709330f, 0.715256f, 0.721144f, 0.726995f, 0.732807f, 0.738579f, 0.744311f, 0.750000f, + 0.755647f, 0.761249f, 0.766807f, 0.772320f, 0.777785f, 0.783203f, 0.788573f, 0.793893f, + 0.799162f, 0.804381f, 0.809547f, 0.814660f, 0.819720f, 0.824724f, 0.829673f, 0.834565f, + 0.839400f, 0.844177f, 0.848895f, 0.853553f, 0.858151f, 0.862687f, 0.867161f, 0.871572f, + 0.875920f, 0.880203f, 0.884421f, 0.888573f, 0.892658f, 0.896677f, 0.900627f, 0.904508f, + 0.908321f, 0.912063f, 0.915735f, 0.919335f, 0.922864f, 0.926320f, 0.929703f, 0.933013f, + 0.936248f, 0.939409f, 0.942494f, 0.945503f, 0.948436f, 0.951293f, 0.954072f, 0.956773f, + 0.959396f, 0.961940f, 0.964405f, 0.966790f, 0.969096f, 0.971321f, 0.973465f, 0.975528f, + 0.977510f, 0.979410f, 0.981228f, 0.982963f, 0.984615f, 0.986185f, 0.987671f, 0.989074f, + 0.990393f, 0.991627f, 0.992778f, 0.993844f, 0.994826f, 0.995722f, 0.996534f, 0.997261f, + 0.997902f, 0.998459f, 0.998929f, 0.999315f, 0.999615f, 0.999829f, 0.999957f, 1.000000f, +}; + +static const int tbands[NB_TBANDS+1] = { + 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 136, 160, 192, 240 +}; + +#define NB_TONAL_SKIP_BANDS 9 + +static opus_val32 silk_resampler_down2_hp( + opus_val32 *S, /* I/O State vector [ 2 ] */ + opus_val32 *out, /* O Output signal [ floor(len/2) ] */ + const opus_val32 *in, /* I Input signal [ len ] */ + int inLen /* I Number of input samples */ +) +{ + int k, len2 = inLen/2; + opus_val32 in32, out32, out32_hp, Y, X; + opus_val64 hp_ener = 0; + /* Internal variables and state are in Q10 format */ + for( k = 0; k < len2; k++ ) { + /* Convert to Q10 */ + in32 = in[ 2 * k ]; + + /* All-pass section for even input sample */ + Y = SUB32( in32, S[ 0 ] ); + X = MULT16_32_Q15(QCONST16(0.6074371f, 15), Y); + out32 = ADD32( S[ 0 ], X ); + S[ 0 ] = ADD32( in32, X ); + out32_hp = out32; + /* Convert to Q10 */ + in32 = in[ 2 * k + 1 ]; + + /* All-pass section for odd input sample, and add to output of previous section */ + Y = SUB32( in32, S[ 1 ] ); + X = MULT16_32_Q15(QCONST16(0.15063f, 15), Y); + out32 = ADD32( out32, S[ 1 ] ); + out32 = ADD32( out32, X ); + S[ 1 ] = ADD32( in32, X ); + + Y = SUB32( -in32, S[ 2 ] ); + X = MULT16_32_Q15(QCONST16(0.15063f, 15), Y); + out32_hp = ADD32( out32_hp, S[ 2 ] ); + out32_hp = ADD32( out32_hp, X ); + S[ 2 ] = ADD32( -in32, X ); + + hp_ener += out32_hp*(opus_val64)out32_hp; + /* Add, convert back to int16 and store to output */ + out[ k ] = HALF32(out32); + } +#ifdef FIXED_POINT + /* len2 can be up to 480, so we shift by 8 more to make it fit. */ + hp_ener = hp_ener >> (2*SIG_SHIFT + 8); +#endif + return (opus_val32)hp_ener; +} + +static opus_val32 downmix_and_resample(downmix_func downmix, const void *_x, opus_val32 *y, opus_val32 S[3], int subframe, int offset, int c1, int c2, int C, int Fs) +{ + VARDECL(opus_val32, tmp); + opus_val32 scale; + int j; + opus_val32 ret = 0; + SAVE_STACK; + + if (subframe==0) return 0; + if (Fs == 48000) + { + subframe *= 2; + offset *= 2; + } else if (Fs == 16000) { + subframe = subframe*2/3; + offset = offset*2/3; + } + ALLOC(tmp, subframe, opus_val32); + + downmix(_x, tmp, subframe, offset, c1, c2, C); +#ifdef FIXED_POINT + scale = (1<-1) + scale /= 2; + for (j=0;jarch = opus_select_arch(); + tonal->Fs = Fs; + /* Clear remaining fields. */ + tonality_analysis_reset(tonal); +} + +void tonality_analysis_reset(TonalityAnalysisState *tonal) +{ + /* Clear non-reusable fields. */ + char *start = (char*)&tonal->TONALITY_ANALYSIS_RESET_START; + OPUS_CLEAR(start, sizeof(TonalityAnalysisState) - (start - (char*)tonal)); +} + +void tonality_get_info(TonalityAnalysisState *tonal, AnalysisInfo *info_out, int len) +{ + int pos; + int curr_lookahead; + float tonality_max; + float tonality_avg; + int tonality_count; + int i; + int pos0; + float prob_avg; + float prob_count; + float prob_min, prob_max; + float vad_prob; + int mpos, vpos; + int bandwidth_span; + + pos = tonal->read_pos; + curr_lookahead = tonal->write_pos-tonal->read_pos; + if (curr_lookahead<0) + curr_lookahead += DETECT_SIZE; + + tonal->read_subframe += len/(tonal->Fs/400); + while (tonal->read_subframe>=8) + { + tonal->read_subframe -= 8; + tonal->read_pos++; + } + if (tonal->read_pos>=DETECT_SIZE) + tonal->read_pos-=DETECT_SIZE; + + /* On long frames, look at the second analysis window rather than the first. */ + if (len > tonal->Fs/50 && pos != tonal->write_pos) + { + pos++; + if (pos==DETECT_SIZE) + pos=0; + } + if (pos == tonal->write_pos) + pos--; + if (pos<0) + pos = DETECT_SIZE-1; + pos0 = pos; + OPUS_COPY(info_out, &tonal->info[pos], 1); + if (!info_out->valid) + return; + tonality_max = tonality_avg = info_out->tonality; + tonality_count = 1; + /* Look at the neighbouring frames and pick largest bandwidth found (to be safe). */ + bandwidth_span = 6; + /* If possible, look ahead for a tone to compensate for the delay in the tone detector. */ + for (i=0;i<3;i++) + { + pos++; + if (pos==DETECT_SIZE) + pos = 0; + if (pos == tonal->write_pos) + break; + tonality_max = MAX32(tonality_max, tonal->info[pos].tonality); + tonality_avg += tonal->info[pos].tonality; + tonality_count++; + info_out->bandwidth = IMAX(info_out->bandwidth, tonal->info[pos].bandwidth); + bandwidth_span--; + } + pos = pos0; + /* Look back in time to see if any has a wider bandwidth than the current frame. */ + for (i=0;iwrite_pos) + break; + info_out->bandwidth = IMAX(info_out->bandwidth, tonal->info[pos].bandwidth); + } + info_out->tonality = MAX32(tonality_avg/tonality_count, tonality_max-.2f); + + mpos = vpos = pos0; + /* If we have enough look-ahead, compensate for the ~5-frame delay in the music prob and + ~1 frame delay in the VAD prob. */ + if (curr_lookahead > 15) + { + mpos += 5; + if (mpos>=DETECT_SIZE) + mpos -= DETECT_SIZE; + vpos += 1; + if (vpos>=DETECT_SIZE) + vpos -= DETECT_SIZE; + } + + /* The following calculations attempt to minimize a "badness function" + for the transition. When switching from speech to music, the badness + of switching at frame k is + b_k = S*v_k + \sum_{i=0}^{k-1} v_i*(p_i - T) + where + v_i is the activity probability (VAD) at frame i, + p_i is the music probability at frame i + T is the probability threshold for switching + S is the penalty for switching during active audio rather than silence + the current frame has index i=0 + + Rather than apply badness to directly decide when to switch, what we compute + instead is the threshold for which the optimal switching point is now. When + considering whether to switch now (frame 0) or at frame k, we have: + S*v_0 = S*v_k + \sum_{i=0}^{k-1} v_i*(p_i - T) + which gives us: + T = ( \sum_{i=0}^{k-1} v_i*p_i + S*(v_k-v_0) ) / ( \sum_{i=0}^{k-1} v_i ) + We take the min threshold across all positive values of k (up to the maximum + amount of lookahead we have) to give us the threshold for which the current + frame is the optimal switch point. + + The last step is that we need to consider whether we want to switch at all. + For that we use the average of the music probability over the entire window. + If the threshold is higher than that average we're not going to + switch, so we compute a min with the average as well. The result of all these + min operations is music_prob_min, which gives the threshold for switching to music + if we're currently encoding for speech. + + We do the exact opposite to compute music_prob_max which is used for switching + from music to speech. + */ + prob_min = 1.f; + prob_max = 0.f; + vad_prob = tonal->info[vpos].activity_probability; + prob_count = MAX16(.1f, vad_prob); + prob_avg = MAX16(.1f, vad_prob)*tonal->info[mpos].music_prob; + while (1) + { + float pos_vad; + mpos++; + if (mpos==DETECT_SIZE) + mpos = 0; + if (mpos == tonal->write_pos) + break; + vpos++; + if (vpos==DETECT_SIZE) + vpos = 0; + if (vpos == tonal->write_pos) + break; + pos_vad = tonal->info[vpos].activity_probability; + prob_min = MIN16((prob_avg - TRANSITION_PENALTY*(vad_prob - pos_vad))/prob_count, prob_min); + prob_max = MAX16((prob_avg + TRANSITION_PENALTY*(vad_prob - pos_vad))/prob_count, prob_max); + prob_count += MAX16(.1f, pos_vad); + prob_avg += MAX16(.1f, pos_vad)*tonal->info[mpos].music_prob; + } + info_out->music_prob = prob_avg/prob_count; + prob_min = MIN16(prob_avg/prob_count, prob_min); + prob_max = MAX16(prob_avg/prob_count, prob_max); + prob_min = MAX16(prob_min, 0.f); + prob_max = MIN16(prob_max, 1.f); + + /* If we don't have enough look-ahead, do our best to make a decent decision. */ + if (curr_lookahead < 10) + { + float pmin, pmax; + pmin = prob_min; + pmax = prob_max; + pos = pos0; + /* Look for min/max in the past. */ + for (i=0;icount-1, 15);i++) + { + pos--; + if (pos < 0) + pos = DETECT_SIZE-1; + pmin = MIN16(pmin, tonal->info[pos].music_prob); + pmax = MAX16(pmax, tonal->info[pos].music_prob); + } + /* Bias against switching on active audio. */ + pmin = MAX16(0.f, pmin - .1f*vad_prob); + pmax = MIN16(1.f, pmax + .1f*vad_prob); + prob_min += (1.f-.1f*curr_lookahead)*(pmin - prob_min); + prob_max += (1.f-.1f*curr_lookahead)*(pmax - prob_max); + } + info_out->music_prob_min = prob_min; + info_out->music_prob_max = prob_max; + + /* printf("%f %f %f %f %f\n", prob_min, prob_max, prob_avg/prob_count, vad_prob, info_out->music_prob); */ +} + +static const float std_feature_bias[9] = { + 5.684947f, 3.475288f, 1.770634f, 1.599784f, 3.773215f, + 2.163313f, 1.260756f, 1.116868f, 1.918795f +}; + +#define LEAKAGE_OFFSET 2.5f +#define LEAKAGE_SLOPE 2.f + +#ifdef FIXED_POINT +/* For fixed-point, the input is +/-2^15 shifted up by SIG_SHIFT, so we need to + compensate for that in the energy. */ +#define SCALE_COMPENS (1.f/((opus_int32)1<<(15+SIG_SHIFT))) +#define SCALE_ENER(e) ((SCALE_COMPENS*SCALE_COMPENS)*(e)) +#else +#define SCALE_ENER(e) (e) +#endif + +#ifdef FIXED_POINT +static int is_digital_silence32(const opus_val32* pcm, int frame_size, int channels, int lsb_depth) +{ + int silence = 0; + opus_val32 sample_max = 0; +#ifdef MLP_TRAINING + return 0; +#endif + sample_max = celt_maxabs32(pcm, frame_size*channels); + + silence = (sample_max == 0); + (void)lsb_depth; + return silence; +} +#else +#define is_digital_silence32(pcm, frame_size, channels, lsb_depth) is_digital_silence(pcm, frame_size, channels, lsb_depth) +#endif + +static void tonality_analysis(TonalityAnalysisState *tonal, const CELTMode *celt_mode, const void *x, int len, int offset, int c1, int c2, int C, int lsb_depth, downmix_func downmix) +{ + int i, b; + const kiss_fft_state *kfft; + VARDECL(kiss_fft_cpx, in); + VARDECL(kiss_fft_cpx, out); + int N = 480, N2=240; + float * OPUS_RESTRICT A = tonal->angle; + float * OPUS_RESTRICT dA = tonal->d_angle; + float * OPUS_RESTRICT d2A = tonal->d2_angle; + VARDECL(float, tonality); + VARDECL(float, noisiness); + float band_tonality[NB_TBANDS]; + float logE[NB_TBANDS]; + float BFCC[8]; + float features[25]; + float frame_tonality; + float max_frame_tonality; + /*float tw_sum=0;*/ + float frame_noisiness; + const float pi4 = (float)(M_PI*M_PI*M_PI*M_PI); + float slope=0; + float frame_stationarity; + float relativeE; + float frame_probs[2]; + float alpha, alphaE, alphaE2; + float frame_loudness; + float bandwidth_mask; + int is_masked[NB_TBANDS+1]; + int bandwidth=0; + float maxE = 0; + float noise_floor; + int remaining; + AnalysisInfo *info; + float hp_ener; + float tonality2[240]; + float midE[8]; + float spec_variability=0; + float band_log2[NB_TBANDS+1]; + float leakage_from[NB_TBANDS+1]; + float leakage_to[NB_TBANDS+1]; + float layer_out[MAX_NEURONS]; + float below_max_pitch; + float above_max_pitch; + int is_silence; + SAVE_STACK; + + if (!tonal->initialized) + { + tonal->mem_fill = 240; + tonal->initialized = 1; + } + alpha = 1.f/IMIN(10, 1+tonal->count); + alphaE = 1.f/IMIN(25, 1+tonal->count); + /* Noise floor related decay for bandwidth detection: -2.2 dB/second */ + alphaE2 = 1.f/IMIN(100, 1+tonal->count); + if (tonal->count <= 1) alphaE2 = 1; + + if (tonal->Fs == 48000) + { + /* len and offset are now at 24 kHz. */ + len/= 2; + offset /= 2; + } else if (tonal->Fs == 16000) { + len = 3*len/2; + offset = 3*offset/2; + } + + kfft = celt_mode->mdct.kfft[0]; + tonal->hp_ener_accum += (float)downmix_and_resample(downmix, x, + &tonal->inmem[tonal->mem_fill], tonal->downmix_state, + IMIN(len, ANALYSIS_BUF_SIZE-tonal->mem_fill), offset, c1, c2, C, tonal->Fs); + if (tonal->mem_fill+len < ANALYSIS_BUF_SIZE) + { + tonal->mem_fill += len; + /* Don't have enough to update the analysis */ + RESTORE_STACK; + return; + } + hp_ener = tonal->hp_ener_accum; + info = &tonal->info[tonal->write_pos++]; + if (tonal->write_pos>=DETECT_SIZE) + tonal->write_pos-=DETECT_SIZE; + + is_silence = is_digital_silence32(tonal->inmem, ANALYSIS_BUF_SIZE, 1, lsb_depth); + + ALLOC(in, 480, kiss_fft_cpx); + ALLOC(out, 480, kiss_fft_cpx); + ALLOC(tonality, 240, float); + ALLOC(noisiness, 240, float); + for (i=0;iinmem[i]); + in[i].i = (kiss_fft_scalar)(w*tonal->inmem[N2+i]); + in[N-i-1].r = (kiss_fft_scalar)(w*tonal->inmem[N-i-1]); + in[N-i-1].i = (kiss_fft_scalar)(w*tonal->inmem[N+N2-i-1]); + } + OPUS_MOVE(tonal->inmem, tonal->inmem+ANALYSIS_BUF_SIZE-240, 240); + remaining = len - (ANALYSIS_BUF_SIZE-tonal->mem_fill); + tonal->hp_ener_accum = (float)downmix_and_resample(downmix, x, + &tonal->inmem[240], tonal->downmix_state, remaining, + offset+ANALYSIS_BUF_SIZE-tonal->mem_fill, c1, c2, C, tonal->Fs); + tonal->mem_fill = 240 + remaining; + if (is_silence) + { + /* On silence, copy the previous analysis. */ + int prev_pos = tonal->write_pos-2; + if (prev_pos < 0) + prev_pos += DETECT_SIZE; + OPUS_COPY(info, &tonal->info[prev_pos], 1); + RESTORE_STACK; + return; + } + opus_fft(kfft, in, out, tonal->arch); +#ifndef FIXED_POINT + /* If there's any NaN on the input, the entire output will be NaN, so we only need to check one value. */ + if (celt_isnan(out[0].r)) + { + info->valid = 0; + RESTORE_STACK; + return; + } +#endif + + for (i=1;iactivity = 0; + frame_noisiness = 0; + frame_stationarity = 0; + if (!tonal->count) + { + for (b=0;blowE[b] = 1e10; + tonal->highE[b] = -1e10; + } + } + relativeE = 0; + frame_loudness = 0; + /* The energy of the very first band is special because of DC. */ + { + float E = 0; + float X1r, X2r; + X1r = 2*(float)out[0].r; + X2r = 2*(float)out[0].i; + E = X1r*X1r + X2r*X2r; + for (i=1;i<4;i++) + { + float binE = out[i].r*(float)out[i].r + out[N-i].r*(float)out[N-i].r + + out[i].i*(float)out[i].i + out[N-i].i*(float)out[N-i].i; + E += binE; + } + E = SCALE_ENER(E); + band_log2[0] = .5f*1.442695f*(float)log(E+1e-10f); + } + for (b=0;bvalid = 0; + RESTORE_STACK; + return; + } +#endif + + tonal->E[tonal->E_count][b] = E; + frame_noisiness += nE/(1e-15f+E); + + frame_loudness += (float)sqrt(E+1e-10f); + logE[b] = (float)log(E+1e-10f); + band_log2[b+1] = .5f*1.442695f*(float)log(E+1e-10f); + tonal->logE[tonal->E_count][b] = logE[b]; + if (tonal->count==0) + tonal->highE[b] = tonal->lowE[b] = logE[b]; + if (tonal->highE[b] > tonal->lowE[b] + 7.5) + { + if (tonal->highE[b] - logE[b] > logE[b] - tonal->lowE[b]) + tonal->highE[b] -= .01f; + else + tonal->lowE[b] += .01f; + } + if (logE[b] > tonal->highE[b]) + { + tonal->highE[b] = logE[b]; + tonal->lowE[b] = MAX32(tonal->highE[b]-15, tonal->lowE[b]); + } else if (logE[b] < tonal->lowE[b]) + { + tonal->lowE[b] = logE[b]; + tonal->highE[b] = MIN32(tonal->lowE[b]+15, tonal->highE[b]); + } + relativeE += (logE[b]-tonal->lowE[b])/(1e-5f + (tonal->highE[b]-tonal->lowE[b])); + + L1=L2=0; + for (i=0;iE[i][b]); + L2 += tonal->E[i][b]; + } + + stationarity = MIN16(0.99f,L1/(float)sqrt(1e-15+NB_FRAMES*L2)); + stationarity *= stationarity; + stationarity *= stationarity; + frame_stationarity += stationarity; + /*band_tonality[b] = tE/(1e-15+E)*/; + band_tonality[b] = MAX16(tE/(1e-15f+E), stationarity*tonal->prev_band_tonality[b]); +#if 0 + if (b>=NB_TONAL_SKIP_BANDS) + { + frame_tonality += tweight[b]*band_tonality[b]; + tw_sum += tweight[b]; + } +#else + frame_tonality += band_tonality[b]; + if (b>=NB_TBANDS-NB_TONAL_SKIP_BANDS) + frame_tonality -= band_tonality[b-NB_TBANDS+NB_TONAL_SKIP_BANDS]; +#endif + max_frame_tonality = MAX16(max_frame_tonality, (1.f+.03f*(b-NB_TBANDS))*frame_tonality); + slope += band_tonality[b]*(b-8); + /*printf("%f %f ", band_tonality[b], stationarity);*/ + tonal->prev_band_tonality[b] = band_tonality[b]; + } + + leakage_from[0] = band_log2[0]; + leakage_to[0] = band_log2[0] - LEAKAGE_OFFSET; + for (b=1;b=0;b--) + { + float leak_slope = LEAKAGE_SLOPE*(tbands[b+1]-tbands[b])/4; + leakage_from[b] = MIN16(leakage_from[b+1]+leak_slope, leakage_from[b]); + leakage_to[b] = MAX16(leakage_to[b+1]-leak_slope, leakage_to[b]); + } + celt_assert(NB_TBANDS+1 <= LEAK_BANDS); + for (b=0;bleak_boost[b] = IMIN(255, (int)floor(.5 + 64.f*boost)); + } + for (;bleak_boost[b] = 0; + + for (i=0;ilogE[i][k] - tonal->logE[j][k]; + dist += tmp*tmp; + } + if (j!=i) + mindist = MIN32(mindist, dist); + } + spec_variability += mindist; + } + spec_variability = (float)sqrt(spec_variability/NB_FRAMES/NB_TBANDS); + bandwidth_mask = 0; + bandwidth = 0; + maxE = 0; + noise_floor = 5.7e-4f/(1<<(IMAX(0,lsb_depth-8))); + noise_floor *= noise_floor; + below_max_pitch=0; + above_max_pitch=0; + for (b=0;bmeanE[b] = MAX32((1-alphaE2)*tonal->meanE[b], E); + Em = MAX32(E, tonal->meanE[b]); + /* Consider the band "active" only if all these conditions are met: + 1) less than 90 dB below the peak band (maximal masking possible considering + both the ATH and the loudness-dependent slope of the spreading function) + 2) above the PCM quantization noise floor + We use b+1 because the first CELT band isn't included in tbands[] + */ + if (E*1e9f > maxE && (Em > 3*noise_floor*(band_end-band_start) || E > noise_floor*(band_end-band_start))) + bandwidth = b+1; + /* Check if the band is masked (see below). */ + is_masked[b] = E < (tonal->prev_bandwidth >= b+1 ? .01f : .05f)*bandwidth_mask; + /* Use a simple follower with 13 dB/Bark slope for spreading function. */ + bandwidth_mask = MAX32(.05f*bandwidth_mask, E); + } + /* Special case for the last two bands, for which we don't have spectrum but only + the energy above 12 kHz. The difficulty here is that the high-pass we use + leaks some LF energy, so we need to increase the threshold without accidentally cutting + off the band. */ + if (tonal->Fs == 48000) { + float noise_ratio; + float Em; + float E = hp_ener*(1.f/(60*60)); + noise_ratio = tonal->prev_bandwidth==20 ? 10.f : 30.f; + +#ifdef FIXED_POINT + /* silk_resampler_down2_hp() shifted right by an extra 8 bits. */ + E *= 256.f*(1.f/Q15ONE)*(1.f/Q15ONE); +#endif + above_max_pitch += E; + tonal->meanE[b] = MAX32((1-alphaE2)*tonal->meanE[b], E); + Em = MAX32(E, tonal->meanE[b]); + if (Em > 3*noise_ratio*noise_floor*160 || E > noise_ratio*noise_floor*160) + bandwidth = 20; + /* Check if the band is masked (see below). */ + is_masked[b] = E < (tonal->prev_bandwidth == 20 ? .01f : .05f)*bandwidth_mask; + } + if (above_max_pitch > below_max_pitch) + info->max_pitch_ratio = below_max_pitch/above_max_pitch; + else + info->max_pitch_ratio = 1; + /* In some cases, resampling aliasing can create a small amount of energy in the first band + being cut. So if the last band is masked, we don't include it. */ + if (bandwidth == 20 && is_masked[NB_TBANDS]) + bandwidth-=2; + else if (bandwidth > 0 && bandwidth <= NB_TBANDS && is_masked[bandwidth-1]) + bandwidth--; + if (tonal->count<=2) + bandwidth = 20; + frame_loudness = 20*(float)log10(frame_loudness); + tonal->Etracker = MAX32(tonal->Etracker-.003f, frame_loudness); + tonal->lowECount *= (1-alphaE); + if (frame_loudness < tonal->Etracker-30) + tonal->lowECount += alphaE; + + for (i=0;i<8;i++) + { + float sum=0; + for (b=0;b<16;b++) + sum += dct_table[i*16+b]*logE[b]; + BFCC[i] = sum; + } + for (i=0;i<8;i++) + { + float sum=0; + for (b=0;b<16;b++) + sum += dct_table[i*16+b]*.5f*(tonal->highE[b]+tonal->lowE[b]); + midE[i] = sum; + } + + frame_stationarity /= NB_TBANDS; + relativeE /= NB_TBANDS; + if (tonal->count<10) + relativeE = .5f; + frame_noisiness /= NB_TBANDS; +#if 1 + info->activity = frame_noisiness + (1-frame_noisiness)*relativeE; +#else + info->activity = .5*(1+frame_noisiness-frame_stationarity); +#endif + frame_tonality = (max_frame_tonality/(NB_TBANDS-NB_TONAL_SKIP_BANDS)); + frame_tonality = MAX16(frame_tonality, tonal->prev_tonality*.8f); + tonal->prev_tonality = frame_tonality; + + slope /= 8*8; + info->tonality_slope = slope; + + tonal->E_count = (tonal->E_count+1)%NB_FRAMES; + tonal->count = IMIN(tonal->count+1, ANALYSIS_COUNT_MAX); + info->tonality = frame_tonality; + + for (i=0;i<4;i++) + features[i] = -0.12299f*(BFCC[i]+tonal->mem[i+24]) + 0.49195f*(tonal->mem[i]+tonal->mem[i+16]) + 0.69693f*tonal->mem[i+8] - 1.4349f*tonal->cmean[i]; + + for (i=0;i<4;i++) + tonal->cmean[i] = (1-alpha)*tonal->cmean[i] + alpha*BFCC[i]; + + for (i=0;i<4;i++) + features[4+i] = 0.63246f*(BFCC[i]-tonal->mem[i+24]) + 0.31623f*(tonal->mem[i]-tonal->mem[i+16]); + for (i=0;i<3;i++) + features[8+i] = 0.53452f*(BFCC[i]+tonal->mem[i+24]) - 0.26726f*(tonal->mem[i]+tonal->mem[i+16]) -0.53452f*tonal->mem[i+8]; + + if (tonal->count > 5) + { + for (i=0;i<9;i++) + tonal->std[i] = (1-alpha)*tonal->std[i] + alpha*features[i]*features[i]; + } + for (i=0;i<4;i++) + features[i] = BFCC[i]-midE[i]; + + for (i=0;i<8;i++) + { + tonal->mem[i+24] = tonal->mem[i+16]; + tonal->mem[i+16] = tonal->mem[i+8]; + tonal->mem[i+8] = tonal->mem[i]; + tonal->mem[i] = BFCC[i]; + } + for (i=0;i<9;i++) + features[11+i] = (float)sqrt(tonal->std[i]) - std_feature_bias[i]; + features[18] = spec_variability - 0.78f; + features[20] = info->tonality - 0.154723f; + features[21] = info->activity - 0.724643f; + features[22] = frame_stationarity - 0.743717f; + features[23] = info->tonality_slope + 0.069216f; + features[24] = tonal->lowECount - 0.067930f; + + analysis_compute_dense(&layer0, layer_out, features); + analysis_compute_gru(&layer1, tonal->rnn_state, layer_out); + analysis_compute_dense(&layer2, frame_probs, tonal->rnn_state); + + /* Probability of speech or music vs noise */ + info->activity_probability = frame_probs[1]; + info->music_prob = frame_probs[0]; + + /*printf("%f %f %f\n", frame_probs[0], frame_probs[1], info->music_prob);*/ +#ifdef MLP_TRAINING + for (i=0;i<25;i++) + printf("%f ", features[i]); + printf("\n"); +#endif + + info->bandwidth = bandwidth; + tonal->prev_bandwidth = bandwidth; + /*printf("%d %d\n", info->bandwidth, info->opus_bandwidth);*/ + info->noisiness = frame_noisiness; + info->valid = 1; + RESTORE_STACK; +} + +void run_analysis(TonalityAnalysisState *analysis, const CELTMode *celt_mode, const void *analysis_pcm, + int analysis_frame_size, int frame_size, int c1, int c2, int C, opus_int32 Fs, + int lsb_depth, downmix_func downmix, AnalysisInfo *analysis_info) +{ + int offset; + int pcm_len; + + analysis_frame_size -= analysis_frame_size&1; + if (analysis_pcm != NULL) + { + /* Avoid overflow/wrap-around of the analysis buffer */ + analysis_frame_size = IMIN((DETECT_SIZE-5)*Fs/50, analysis_frame_size); + + pcm_len = analysis_frame_size - analysis->analysis_offset; + offset = analysis->analysis_offset; + while (pcm_len>0) { + tonality_analysis(analysis, celt_mode, analysis_pcm, IMIN(Fs/50, pcm_len), offset, c1, c2, C, lsb_depth, downmix); + offset += Fs/50; + pcm_len -= Fs/50; + } + analysis->analysis_offset = analysis_frame_size; + + analysis->analysis_offset -= frame_size; + } + + tonality_get_info(analysis, analysis_info, frame_size); +} + +#endif /* DISABLE_FLOAT_API */ diff --git a/port/third_party/opus/src/analysis.h b/port/third_party/opus/src/analysis.h new file mode 100644 index 00000000..0b66555f --- /dev/null +++ b/port/third_party/opus/src/analysis.h @@ -0,0 +1,103 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + 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 FOUNDATION 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. +*/ + +#ifndef ANALYSIS_H +#define ANALYSIS_H + +#include "celt.h" +#include "opus_private.h" +#include "mlp.h" + +#define NB_FRAMES 8 +#define NB_TBANDS 18 +#define ANALYSIS_BUF_SIZE 720 /* 30 ms at 24 kHz */ + +/* At that point we can stop counting frames because it no longer matters. */ +#define ANALYSIS_COUNT_MAX 10000 + +#define DETECT_SIZE 100 + +/* Uncomment this to print the MLP features on stdout. */ +/*#define MLP_TRAINING*/ + +typedef struct { + int arch; + int application; + opus_int32 Fs; +#define TONALITY_ANALYSIS_RESET_START angle + float angle[240]; + float d_angle[240]; + float d2_angle[240]; + opus_val32 inmem[ANALYSIS_BUF_SIZE]; + int mem_fill; /* number of usable samples in the buffer */ + float prev_band_tonality[NB_TBANDS]; + float prev_tonality; + int prev_bandwidth; + float E[NB_FRAMES][NB_TBANDS]; + float logE[NB_FRAMES][NB_TBANDS]; + float lowE[NB_TBANDS]; + float highE[NB_TBANDS]; + float meanE[NB_TBANDS+1]; + float mem[32]; + float cmean[8]; + float std[9]; + float Etracker; + float lowECount; + int E_count; + int count; + int analysis_offset; + int write_pos; + int read_pos; + int read_subframe; + float hp_ener_accum; + int initialized; + float rnn_state[MAX_NEURONS]; + opus_val32 downmix_state[3]; + AnalysisInfo info[DETECT_SIZE]; +} TonalityAnalysisState; + +/** Initialize a TonalityAnalysisState struct. + * + * This performs some possibly slow initialization steps which should + * not be repeated every analysis step. No allocated memory is retained + * by the state struct, so no cleanup call is required. + */ +void tonality_analysis_init(TonalityAnalysisState *analysis, opus_int32 Fs); + +/** Reset a TonalityAnalysisState stuct. + * + * Call this when there's a discontinuity in the data. + */ +void tonality_analysis_reset(TonalityAnalysisState *analysis); + +void tonality_get_info(TonalityAnalysisState *tonal, AnalysisInfo *info_out, int len); + +void run_analysis(TonalityAnalysisState *analysis, const CELTMode *celt_mode, const void *analysis_pcm, + int analysis_frame_size, int frame_size, int c1, int c2, int C, opus_int32 Fs, + int lsb_depth, downmix_func downmix, AnalysisInfo *analysis_info); + +#endif diff --git a/port/third_party/opus/src/extensions.c b/port/third_party/opus/src/extensions.c new file mode 100644 index 00000000..bb6c0b02 --- /dev/null +++ b/port/third_party/opus/src/extensions.c @@ -0,0 +1,315 @@ +/* Copyright (c) 2022 Amazon */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "opus_types.h" +#include "opus_defines.h" +#include "arch.h" +#include "os_support.h" +#include "opus_private.h" + + +/* Given an extension payload, advance data to the next extension and return the + length of the remaining extensions. */ +opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 *header_size) +{ + int id, L; + if (len==0) + return 0; + id = **data>>1; + L = **data&1; + if (id == 0 && L == 1) + { + *header_size = 1; + if (len < 1) + return -1; + (*data)++; + len--; + return len; + } else if (id > 0 && id < 32) + { + if (len < 1+L) + return -1; + *data += 1+L; + len -= 1+L; + *header_size = 1; + return len; + } else { + if (L==0) + { + *data += len; + *header_size = 1; + return 0; + } else { + opus_int32 bytes=0; + *header_size = 1; + do { + (*data)++; + len--; + if (len == 0) + return -1; + bytes += **data; + (*header_size)++; + } while (**data == 255); + (*data)++; + len--; + if (bytes <= len) + { + len -= bytes; + *data += bytes; + } else { + return -1; + } + return len; + } + } +} + +/* Count the number of extensions, excluding real padding and separators. */ +opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len) +{ + opus_int32 curr_len; + opus_int32 count=0; + const unsigned char *curr_data = data; + + celt_assert(len >= 0); + celt_assert(data != NULL || len == 0); + + curr_len = len; + while (curr_len > 0) + { + int id; + opus_int32 header_size; + id = *curr_data>>1; + curr_len = skip_extension(&curr_data, curr_len, &header_size); + if (curr_len < 0) + return OPUS_INVALID_PACKET; + if (id > 1) + count++; + } + return count; +} + +/* Extract extensions from Opus padding (excluding real padding and separators) */ +opus_int32 opus_packet_extensions_parse(const unsigned char *data, opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions) +{ + const unsigned char *curr_data; + opus_int32 curr_len; + int curr_frame=0; + opus_int32 count=0; + + celt_assert(len >= 0); + celt_assert(data != NULL || len == 0); + celt_assert(nb_extensions != NULL); + celt_assert(extensions != NULL || *nb_extensions == 0); + + curr_data = data; + curr_len = len; + while (curr_len > 0) + { + int id; + opus_int32 header_size; + opus_extension_data curr_ext; + id = *curr_data>>1; + if (id > 1) + { + curr_ext.id = id; + curr_ext.frame = curr_frame; + curr_ext.data = curr_data; + } else if (id == 1) + { + int L = *curr_data&1; + if (L==0) + curr_frame++; + else { + if (curr_len >= 2) + curr_frame += curr_data[1]; + /* Else we're at the end and it doesn't matter. */ + } + if (curr_frame >= 48) + { + *nb_extensions = count; + return OPUS_INVALID_PACKET; + } + } + curr_len = skip_extension(&curr_data, curr_len, &header_size); + /* printf("curr_len = %d, header_size = %d\n", curr_len, header_size); */ + if (curr_len < 0) + { + *nb_extensions = count; + return OPUS_INVALID_PACKET; + } + celt_assert(curr_data - data == len - curr_len); + if (id > 1) + { + if (count == *nb_extensions) + { + return OPUS_BUFFER_TOO_SMALL; + } + curr_ext.len = curr_data - curr_ext.data - header_size; + curr_ext.data += header_size; + extensions[count++] = curr_ext; + } + } + celt_assert(curr_len == 0); + *nb_extensions = count; + return OPUS_OK; +} + +opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data *extensions, opus_int32 nb_extensions, int pad) +{ + int max_frame=0; + opus_int32 i; + int frame; + int curr_frame = 0; + opus_int32 pos = 0; + opus_int32 written = 0; + + celt_assert(len >= 0); + + for (i=0;i 127) + return OPUS_BAD_ARG; + } + if (max_frame >= 48) return OPUS_BAD_ARG; + for (frame=0;frame<=max_frame;frame++) + { + for (i=0;i 1) + return OPUS_BAD_ARG; + if (len-pos < extensions[i].len+1) + return OPUS_BUFFER_TOO_SMALL; + if (data) data[pos] = (extensions[i].id<<1) + extensions[i].len; + pos++; + if (extensions[i].len > 0) { + if (data) data[pos] = extensions[i].data[0]; + pos++; + } + } else { + int last; + opus_int32 length_bytes; + if (extensions[i].len < 0) + return OPUS_BAD_ARG; + last = (written == nb_extensions - 1); + length_bytes = 1 + extensions[i].len/255; + if (last) + length_bytes = 0; + if (len-pos < 1 + length_bytes + extensions[i].len) + return OPUS_BUFFER_TOO_SMALL; + if (data) data[pos] = (extensions[i].id<<1) + !last; + pos++; + if (!last) + { + opus_int32 j; + for (j=0;j +int main() +{ + opus_extension_data ext[] = {{2, 0, (const unsigned char *)"a", 1}, + {32, 10, (const unsigned char *)"DRED", 4}, + {33, 1, (const unsigned char *)"NOT DRED", 8}, + {3, 4, (const unsigned char *)NULL, 0} + }; + opus_extension_data ext2[10]; + int i, len; + int nb_ext = 10; + unsigned char packet[10000]; + len = opus_packet_extensions_generate(packet, 32, ext, 4, 1); + for (i=0;i +#include "opus_types.h" +#include "opus_defines.h" +#include "arch.h" +#include "mlp.h" + +#define fmadd(a, b, c) ((a)*(b)+(c)) +static OPUS_INLINE float tansig_approx(float x) +{ + const float N0 = 952.52801514f; + const float N1 = 96.39235687f; + const float N2 = 0.60863042f; + const float D0 = 952.72399902f; + const float D1 = 413.36801147f; + const float D2 = 11.88600922f; + float X2, num, den; + X2 = x*x; + num = fmadd(fmadd(N2, X2, N1), X2, N0); + den = fmadd(fmadd(D2, X2, D1), X2, D0); + num = num*x/den; + return MAX32(-1.f, MIN32(1.f, num)); +} + +static OPUS_INLINE float sigmoid_approx(float x) +{ + return .5f + .5f*tansig_approx(.5f*x); +} + +static void gemm_accum(float *out, const opus_int8 *weights, int rows, int cols, int col_stride, const float *x) +{ + int i, j; + for (i=0;inb_inputs; + N = layer->nb_neurons; + stride = N; + for (i=0;ibias[i]; + gemm_accum(output, layer->input_weights, N, M, stride, input); + for (i=0;isigmoid) { + for (i=0;inb_inputs; + N = gru->nb_neurons; + stride = 3*N; + /* Compute update gate. */ + for (i=0;ibias[i]; + gemm_accum(z, gru->input_weights, N, M, stride, input); + gemm_accum(z, gru->recurrent_weights, N, N, stride, state); + for (i=0;ibias[N + i]; + gemm_accum(r, &gru->input_weights[N], N, M, stride, input); + gemm_accum(r, &gru->recurrent_weights[N], N, N, stride, state); + for (i=0;ibias[2*N + i]; + for (i=0;iinput_weights[2*N], N, M, stride, input); + gemm_accum(h, &gru->recurrent_weights[2*N], N, N, stride, tmp); + for (i=0;i=0) + break; + x[i*C] = x[i*C]+a*x[i*C]*x[i*C]; + } + + curr=0; + x0 = x[0]; + while(1) + { + int start, end; + float maxval; + int special=0; + int peak_pos; + for (i=curr;i1 || x[i*C]<-1) + break; + } + if (i==N) + { + a=0; + break; + } + peak_pos = i; + start=end=i; + maxval=ABS16(x[i*C]); + /* Look for first zero crossing before clipping */ + while (start>0 && x[i*C]*x[(start-1)*C]>=0) + start--; + /* Look for first zero crossing after clipping */ + while (end=0) + { + /* Look for other peaks until the next zero-crossing. */ + if (ABS16(x[end*C])>maxval) + { + maxval = ABS16(x[end*C]); + peak_pos = end; + } + end++; + } + /* Detect the special case where we clip before the first zero crossing */ + special = (start==0 && x[i*C]*x[0]>=0); + + /* Compute a such that maxval + a*maxval^2 = 1 */ + a=(maxval-1)/(maxval*maxval); + /* Slightly boost "a" by 2^-22. This is just enough to ensure -ffast-math + does not cause output values larger than +/-1, but small enough not + to matter even for 24-bit output. */ + a += a*2.4e-7f; + if (x[i*C]>0) + a = -a; + /* Apply soft clipping */ + for (i=start;i=2) + { + /* Add a linear ramp from the first sample to the signal peak. + This avoids a discontinuity at the beginning of the frame. */ + float delta; + float offset = x0-x[0]; + delta = offset / peak_pos; + for (i=curr;i>2; + return 2; + } +} + +static int parse_size(const unsigned char *data, opus_int32 len, opus_int16 *size) +{ + if (len<1) + { + *size = -1; + return -1; + } else if (data[0]<252) + { + *size = data[0]; + return 1; + } else if (len<2) + { + *size = -1; + return -1; + } else { + *size = 4*data[1] + data[0]; + return 2; + } +} + +int opus_packet_get_samples_per_frame(const unsigned char *data, + opus_int32 Fs) +{ + int audiosize; + if (data[0]&0x80) + { + audiosize = ((data[0]>>3)&0x3); + audiosize = (Fs<>3)&0x3); + if (audiosize == 3) + audiosize = Fs*60/1000; + else + audiosize = (Fs< len) + return OPUS_INVALID_PACKET; + data += bytes; + last_size = len-size[0]; + break; + /* Multiple CBR/VBR frames (from 0 to 120 ms) */ + default: /*case 3:*/ + if (len<1) + return OPUS_INVALID_PACKET; + /* Number of frames encoded in bits 0 to 5 */ + ch = *data++; + count = ch&0x3F; + if (count <= 0 || framesize*(opus_int32)count > 5760) + return OPUS_INVALID_PACKET; + len--; + /* Padding flag is bit 6 */ + if (ch&0x40) + { + int p; + do { + int tmp; + if (len<=0) + return OPUS_INVALID_PACKET; + p = *data++; + len--; + tmp = p==255 ? 254: p; + len -= tmp; + pad += tmp; + } while (p==255); + } + if (len<0) + return OPUS_INVALID_PACKET; + /* VBR flag is bit 7 */ + cbr = !(ch&0x80); + if (!cbr) + { + /* VBR case */ + last_size = len; + for (i=0;i len) + return OPUS_INVALID_PACKET; + data += bytes; + last_size -= bytes+size[i]; + } + if (last_size<0) + return OPUS_INVALID_PACKET; + } else if (!self_delimited) + { + /* CBR case */ + last_size = len/count; + if (last_size*count!=len) + return OPUS_INVALID_PACKET; + for (i=0;i len) + return OPUS_INVALID_PACKET; + data += bytes; + /* For CBR packets, apply the size to all the frames. */ + if (cbr) + { + if (size[count-1]*count > len) + return OPUS_INVALID_PACKET; + for (i=0;i last_size) + return OPUS_INVALID_PACKET; + } else + { + /* Because it's not encoded explicitly, it's possible the size of the + last packet (or all the packets, for the CBR case) is larger than + 1275. Reject them here.*/ + if (last_size > 1275) + return OPUS_INVALID_PACKET; + size[count-1] = (opus_int16)last_size; + } + + if (payload_offset) + *payload_offset = (int)(data-data0); + + for (i=0;i= 2) && !defined(__OPTIMIZE__) && !defined(OPUS_WILL_BE_SLOW) +# pragma message "You appear to be compiling without optimization, if so opus will be very slow." +#endif + +#include +#include "celt.h" +#include "opus.h" +#include "entdec.h" +#include "modes.h" +#include "API.h" +#include "stack_alloc.h" +#include "float_cast.h" +#include "opus_private.h" +#include "os_support.h" +#include "structs.h" +#include "define.h" +#include "mathops.h" +#include "cpu_support.h" + +#ifdef ENABLE_DEEP_PLC +#include "dred_rdovae_dec_data.h" +#include "dred_rdovae_dec.h" +#endif + +#ifdef ENABLE_OSCE +#include "osce.h" +#endif + +struct OpusDecoder { + int celt_dec_offset; + int silk_dec_offset; + int channels; + opus_int32 Fs; /** Sampling rate (at the API level) */ + silk_DecControlStruct DecControl; + int decode_gain; + int complexity; + int arch; +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState lpcnet; +#endif + + /* Everything beyond this point gets cleared on a reset */ +#define OPUS_DECODER_RESET_START stream_channels + int stream_channels; + + int bandwidth; + int mode; + int prev_mode; + int frame_size; + int prev_redundancy; + int last_packet_duration; +#ifndef FIXED_POINT + opus_val16 softclip_mem[2]; +#endif + + opus_uint32 rangeFinal; +}; + +#if defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS) +static void validate_opus_decoder(OpusDecoder *st) +{ + celt_assert(st->channels == 1 || st->channels == 2); + celt_assert(st->Fs == 48000 || st->Fs == 24000 || st->Fs == 16000 || st->Fs == 12000 || st->Fs == 8000); + celt_assert(st->DecControl.API_sampleRate == st->Fs); + celt_assert(st->DecControl.internalSampleRate == 0 || st->DecControl.internalSampleRate == 16000 || st->DecControl.internalSampleRate == 12000 || st->DecControl.internalSampleRate == 8000); + celt_assert(st->DecControl.nChannelsAPI == st->channels); + celt_assert(st->DecControl.nChannelsInternal == 0 || st->DecControl.nChannelsInternal == 1 || st->DecControl.nChannelsInternal == 2); + celt_assert(st->DecControl.payloadSize_ms == 0 || st->DecControl.payloadSize_ms == 10 || st->DecControl.payloadSize_ms == 20 || st->DecControl.payloadSize_ms == 40 || st->DecControl.payloadSize_ms == 60); +#ifdef OPUS_ARCHMASK + celt_assert(st->arch >= 0); + celt_assert(st->arch <= OPUS_ARCHMASK); +#endif + celt_assert(st->stream_channels == 1 || st->stream_channels == 2); +} +#define VALIDATE_OPUS_DECODER(st) validate_opus_decoder(st) +#else +#define VALIDATE_OPUS_DECODER(st) +#endif + +int opus_decoder_get_size(int channels) +{ + int silkDecSizeBytes, celtDecSizeBytes; + int ret; + if (channels<1 || channels > 2) + return 0; + ret = silk_Get_Decoder_Size( &silkDecSizeBytes ); + if(ret) + return 0; + silkDecSizeBytes = align(silkDecSizeBytes); + celtDecSizeBytes = celt_decoder_get_size(channels); + return align(sizeof(OpusDecoder))+silkDecSizeBytes+celtDecSizeBytes; +} + +int opus_decoder_init(OpusDecoder *st, opus_int32 Fs, int channels) +{ + void *silk_dec; + CELTDecoder *celt_dec; + int ret, silkDecSizeBytes; + + if ((Fs!=48000&&Fs!=24000&&Fs!=16000&&Fs!=12000&&Fs!=8000) + || (channels!=1&&channels!=2)) + return OPUS_BAD_ARG; + + OPUS_CLEAR((char*)st, opus_decoder_get_size(channels)); + /* Initialize SILK decoder */ + ret = silk_Get_Decoder_Size(&silkDecSizeBytes); + if (ret) + return OPUS_INTERNAL_ERROR; + + silkDecSizeBytes = align(silkDecSizeBytes); + st->silk_dec_offset = align(sizeof(OpusDecoder)); + st->celt_dec_offset = st->silk_dec_offset+silkDecSizeBytes; + silk_dec = (char*)st+st->silk_dec_offset; + celt_dec = (CELTDecoder*)((char*)st+st->celt_dec_offset); + st->stream_channels = st->channels = channels; + st->complexity = 0; + + st->Fs = Fs; + st->DecControl.API_sampleRate = st->Fs; + st->DecControl.nChannelsAPI = st->channels; + + /* Reset decoder */ + ret = silk_InitDecoder( silk_dec ); + if(ret)return OPUS_INTERNAL_ERROR; + + /* Initialize CELT decoder */ + ret = celt_decoder_init(celt_dec, Fs, channels); + if(ret!=OPUS_OK)return OPUS_INTERNAL_ERROR; + + celt_decoder_ctl(celt_dec, CELT_SET_SIGNALLING(0)); + + st->prev_mode = 0; + st->frame_size = Fs/400; +#ifdef ENABLE_DEEP_PLC + lpcnet_plc_init( &st->lpcnet); +#endif + st->arch = opus_select_arch(); + return OPUS_OK; +} + +OpusDecoder *opus_decoder_create(opus_int32 Fs, int channels, int *error) +{ + int ret; + OpusDecoder *st; + if ((Fs!=48000&&Fs!=24000&&Fs!=16000&&Fs!=12000&&Fs!=8000) + || (channels!=1&&channels!=2)) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + st = (OpusDecoder *)opus_alloc(opus_decoder_get_size(channels)); + if (st == NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + ret = opus_decoder_init(st, Fs, channels); + if (error) + *error = ret; + if (ret != OPUS_OK) + { + opus_free(st); + st = NULL; + } + return st; +} + +static void smooth_fade(const opus_val16 *in1, const opus_val16 *in2, + opus_val16 *out, int overlap, int channels, + const opus_val16 *window, opus_int32 Fs) +{ + int i, c; + int inc = 48000/Fs; + for (c=0;csilk_dec_offset; + celt_dec = (CELTDecoder*)((char*)st+st->celt_dec_offset); + F20 = st->Fs/50; + F10 = F20>>1; + F5 = F10>>1; + F2_5 = F5>>1; + if (frame_size < F2_5) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + /* Limit frame_size to avoid excessive stack allocations. */ + frame_size = IMIN(frame_size, st->Fs/25*3); + /* Payloads of 1 (2 including ToC) or 0 trigger the PLC/DTX */ + if (len<=1) + { + data = NULL; + /* In that case, don't conceal more than what the ToC says */ + frame_size = IMIN(frame_size, st->frame_size); + } + if (data != NULL) + { + audiosize = st->frame_size; + mode = st->mode; + bandwidth = st->bandwidth; + ec_dec_init(&dec,(unsigned char*)data,len); + } else { + audiosize = frame_size; + /* Run PLC using last used mode (CELT if we ended with CELT redundancy) */ + mode = st->prev_redundancy ? MODE_CELT_ONLY : st->prev_mode; + bandwidth = 0; + + if (mode == 0) + { + /* If we haven't got any packet yet, all we can do is return zeros */ + for (i=0;ichannels;i++) + pcm[i] = 0; + RESTORE_STACK; + return audiosize; + } + + /* Avoids trying to run the PLC on sizes other than 2.5 (CELT), 5 (CELT), + 10, or 20 (e.g. 12.5 or 30 ms). */ + if (audiosize > F20) + { + do { + int ret = opus_decode_frame(st, NULL, 0, pcm, IMIN(audiosize, F20), 0); + if (ret<0) + { + RESTORE_STACK; + return ret; + } + pcm += ret*st->channels; + audiosize -= ret; + } while (audiosize > 0); + RESTORE_STACK; + return frame_size; + } else if (audiosize < F20) + { + if (audiosize > F10) + audiosize = F10; + else if (mode != MODE_SILK_ONLY && audiosize > F5 && audiosize < F10) + audiosize = F5; + } + } + + /* In fixed-point, we can tell CELT to do the accumulation on top of the + SILK PCM buffer. This saves some stack space. */ +#ifdef FIXED_POINT + celt_accum = (mode != MODE_CELT_ONLY) && (frame_size >= F10); +#else + celt_accum = 0; +#endif + + pcm_transition_silk_size = ALLOC_NONE; + pcm_transition_celt_size = ALLOC_NONE; + if (data!=NULL && st->prev_mode > 0 && ( + (mode == MODE_CELT_ONLY && st->prev_mode != MODE_CELT_ONLY && !st->prev_redundancy) + || (mode != MODE_CELT_ONLY && st->prev_mode == MODE_CELT_ONLY) ) + ) + { + transition = 1; + /* Decide where to allocate the stack memory for pcm_transition */ + if (mode == MODE_CELT_ONLY) + pcm_transition_celt_size = F5*st->channels; + else + pcm_transition_silk_size = F5*st->channels; + } + ALLOC(pcm_transition_celt, pcm_transition_celt_size, opus_val16); + if (transition && mode == MODE_CELT_ONLY) + { + pcm_transition = pcm_transition_celt; + opus_decode_frame(st, NULL, 0, pcm_transition, IMIN(F5, audiosize), 0); + } + if (audiosize > frame_size) + { + /*fprintf(stderr, "PCM buffer too small: %d vs %d (mode = %d)\n", audiosize, frame_size, mode);*/ + RESTORE_STACK; + return OPUS_BAD_ARG; + } else { + frame_size = audiosize; + } + + /* Don't allocate any memory when in CELT-only mode */ + pcm_silk_size = (mode != MODE_CELT_ONLY && !celt_accum) ? IMAX(F10, frame_size)*st->channels : ALLOC_NONE; + ALLOC(pcm_silk, pcm_silk_size, opus_int16); + + /* SILK processing */ + if (mode != MODE_CELT_ONLY) + { + int lost_flag, decoded_samples; + opus_int16 *pcm_ptr; +#ifdef FIXED_POINT + if (celt_accum) + pcm_ptr = pcm; + else +#endif + pcm_ptr = pcm_silk; + + if (st->prev_mode==MODE_CELT_ONLY) + silk_ResetDecoder( silk_dec ); + + /* The SILK PLC cannot produce frames of less than 10 ms */ + st->DecControl.payloadSize_ms = IMAX(10, 1000 * audiosize / st->Fs); + + if (data != NULL) + { + st->DecControl.nChannelsInternal = st->stream_channels; + if( mode == MODE_SILK_ONLY ) { + if( bandwidth == OPUS_BANDWIDTH_NARROWBAND ) { + st->DecControl.internalSampleRate = 8000; + } else if( bandwidth == OPUS_BANDWIDTH_MEDIUMBAND ) { + st->DecControl.internalSampleRate = 12000; + } else if( bandwidth == OPUS_BANDWIDTH_WIDEBAND ) { + st->DecControl.internalSampleRate = 16000; + } else { + st->DecControl.internalSampleRate = 16000; + celt_assert( 0 ); + } + } else { + /* Hybrid mode */ + st->DecControl.internalSampleRate = 16000; + } + } + st->DecControl.enable_deep_plc = st->complexity >= 5; +#ifdef ENABLE_OSCE + st->DecControl.osce_method = OSCE_METHOD_NONE; +#ifndef DISABLE_LACE + if (st->complexity >= 6) {st->DecControl.osce_method = OSCE_METHOD_LACE;} +#endif +#ifndef DISABLE_NOLACE + if (st->complexity >= 7) {st->DecControl.osce_method = OSCE_METHOD_NOLACE;} +#endif +#endif + + lost_flag = data == NULL ? 1 : 2 * !!decode_fec; + decoded_samples = 0; + do { + /* Call SILK decoder */ + int first_frame = decoded_samples == 0; + silk_ret = silk_Decode( silk_dec, &st->DecControl, + lost_flag, first_frame, &dec, pcm_ptr, &silk_frame_size, +#ifdef ENABLE_DEEP_PLC + &st->lpcnet, +#endif + st->arch ); + if( silk_ret ) { + if (lost_flag) { + /* PLC failure should not be fatal */ + silk_frame_size = frame_size; + for (i=0;ichannels;i++) + pcm_ptr[i] = 0; + } else { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + } + pcm_ptr += silk_frame_size * st->channels; + decoded_samples += silk_frame_size; + } while( decoded_samples < frame_size ); + } + + start_band = 0; + if (!decode_fec && mode != MODE_CELT_ONLY && data != NULL + && ec_tell(&dec)+17+20*(mode == MODE_HYBRID) <= 8*len) + { + /* Check if we have a redundant 0-8 kHz band */ + if (mode == MODE_HYBRID) + redundancy = ec_dec_bit_logp(&dec, 12); + else + redundancy = 1; + if (redundancy) + { + celt_to_silk = ec_dec_bit_logp(&dec, 1); + /* redundancy_bytes will be at least two, in the non-hybrid + case due to the ec_tell() check above */ + redundancy_bytes = mode==MODE_HYBRID ? + (opus_int32)ec_dec_uint(&dec, 256)+2 : + len-((ec_tell(&dec)+7)>>3); + len -= redundancy_bytes; + /* This is a sanity check. It should never happen for a valid + packet, so the exact behaviour is not normative. */ + if (len*8 < ec_tell(&dec)) + { + len = 0; + redundancy_bytes = 0; + redundancy = 0; + } + /* Shrink decoder because of raw bits */ + dec.storage -= redundancy_bytes; + } + } + if (mode != MODE_CELT_ONLY) + start_band = 17; + + if (redundancy) + { + transition = 0; + pcm_transition_silk_size=ALLOC_NONE; + } + + ALLOC(pcm_transition_silk, pcm_transition_silk_size, opus_val16); + + if (transition && mode != MODE_CELT_ONLY) + { + pcm_transition = pcm_transition_silk; + opus_decode_frame(st, NULL, 0, pcm_transition, IMIN(F5, audiosize), 0); + } + + + if (bandwidth) + { + int endband=21; + + switch(bandwidth) + { + case OPUS_BANDWIDTH_NARROWBAND: + endband = 13; + break; + case OPUS_BANDWIDTH_MEDIUMBAND: + case OPUS_BANDWIDTH_WIDEBAND: + endband = 17; + break; + case OPUS_BANDWIDTH_SUPERWIDEBAND: + endband = 19; + break; + case OPUS_BANDWIDTH_FULLBAND: + endband = 21; + break; + default: + celt_assert(0); + break; + } + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_END_BAND(endband))); + } + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_CHANNELS(st->stream_channels))); + + /* Only allocation memory for redundancy if/when needed */ + redundant_audio_size = redundancy ? F5*st->channels : ALLOC_NONE; + ALLOC(redundant_audio, redundant_audio_size, opus_val16); + + /* 5 ms redundant frame for CELT->SILK*/ + if (redundancy && celt_to_silk) + { + /* If the previous frame did not use CELT (the first redundancy frame in + a transition from SILK may have been lost) then the CELT decoder is + stale at this point and the redundancy audio is not useful, however + the final range is still needed (for testing), so the redundancy is + always decoded but the decoded audio may not be used */ + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0))); + celt_decode_with_ec(celt_dec, data+len, redundancy_bytes, + redundant_audio, F5, NULL, 0); + MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_GET_FINAL_RANGE(&redundant_rng))); + } + + /* MUST be after PLC */ + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(start_band))); + + if (mode != MODE_SILK_ONLY) + { + int celt_frame_size = IMIN(F20, frame_size); + /* Make sure to discard any previous CELT state */ + if (mode != st->prev_mode && st->prev_mode > 0 && !st->prev_redundancy) + MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_RESET_STATE)); + /* Decode CELT */ + celt_ret = celt_decode_with_ec_dred(celt_dec, decode_fec ? NULL : data, + len, pcm, celt_frame_size, &dec, celt_accum +#ifdef ENABLE_DEEP_PLC + , &st->lpcnet +#endif + ); + } else { + unsigned char silence[2] = {0xFF, 0xFF}; + if (!celt_accum) + { + for (i=0;ichannels;i++) + pcm[i] = 0; + } + /* For hybrid -> SILK transitions, we let the CELT MDCT + do a fade-out by decoding a silence frame */ + if (st->prev_mode == MODE_HYBRID && !(redundancy && celt_to_silk && st->prev_redundancy) ) + { + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0))); + celt_decode_with_ec(celt_dec, silence, 2, pcm, F2_5, NULL, celt_accum); + } + } + + if (mode != MODE_CELT_ONLY && !celt_accum) + { +#ifdef FIXED_POINT + for (i=0;ichannels;i++) + pcm[i] = SAT16(ADD32(pcm[i], pcm_silk[i])); +#else + for (i=0;ichannels;i++) + pcm[i] = pcm[i] + (opus_val16)((1.f/32768.f)*pcm_silk[i]); +#endif + } + + { + const CELTMode *celt_mode; + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_GET_MODE(&celt_mode))); + window = celt_mode->window; + } + + /* 5 ms redundant frame for SILK->CELT */ + if (redundancy && !celt_to_silk) + { + MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_RESET_STATE)); + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0))); + + celt_decode_with_ec(celt_dec, data+len, redundancy_bytes, redundant_audio, F5, NULL, 0); + MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_GET_FINAL_RANGE(&redundant_rng))); + smooth_fade(pcm+st->channels*(frame_size-F2_5), redundant_audio+st->channels*F2_5, + pcm+st->channels*(frame_size-F2_5), F2_5, st->channels, window, st->Fs); + } + /* 5ms redundant frame for CELT->SILK; ignore if the previous frame did not + use CELT (the first redundancy frame in a transition from SILK may have + been lost) */ + if (redundancy && celt_to_silk && (st->prev_mode != MODE_SILK_ONLY || st->prev_redundancy)) + { + for (c=0;cchannels;c++) + { + for (i=0;ichannels*i+c] = redundant_audio[st->channels*i+c]; + } + smooth_fade(redundant_audio+st->channels*F2_5, pcm+st->channels*F2_5, + pcm+st->channels*F2_5, F2_5, st->channels, window, st->Fs); + } + if (transition) + { + if (audiosize >= F5) + { + for (i=0;ichannels*F2_5;i++) + pcm[i] = pcm_transition[i]; + smooth_fade(pcm_transition+st->channels*F2_5, pcm+st->channels*F2_5, + pcm+st->channels*F2_5, F2_5, + st->channels, window, st->Fs); + } else { + /* Not enough time to do a clean transition, but we do it anyway + This will not preserve amplitude perfectly and may introduce + a bit of temporal aliasing, but it shouldn't be too bad and + that's pretty much the best we can do. In any case, generating this + transition it pretty silly in the first place */ + smooth_fade(pcm_transition, pcm, + pcm, F2_5, + st->channels, window, st->Fs); + } + } + + if(st->decode_gain) + { + opus_val32 gain; + gain = celt_exp2(MULT16_16_P15(QCONST16(6.48814081e-4f, 25), st->decode_gain)); + for (i=0;ichannels;i++) + { + opus_val32 x; + x = MULT16_32_P16(pcm[i],gain); + pcm[i] = SATURATE(x, 32767); + } + } + + if (len <= 1) + st->rangeFinal = 0; + else + st->rangeFinal = dec.rng ^ redundant_rng; + + st->prev_mode = mode; + st->prev_redundancy = redundancy && !celt_to_silk; + + if (celt_ret>=0) + { + if (OPUS_CHECK_ARRAY(pcm, audiosize*st->channels)) + OPUS_PRINT_INT(audiosize); + } + + RESTORE_STACK; + return celt_ret < 0 ? celt_ret : audiosize; + +} + +int opus_decode_native(OpusDecoder *st, const unsigned char *data, + opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec, + int self_delimited, opus_int32 *packet_offset, int soft_clip, const OpusDRED *dred, opus_int32 dred_offset) +{ + int i, nb_samples; + int count, offset; + unsigned char toc; + int packet_frame_size, packet_bandwidth, packet_mode, packet_stream_channels; + /* 48 x 2.5 ms = 120 ms */ + opus_int16 size[48]; + VALIDATE_OPUS_DECODER(st); + if (decode_fec<0 || decode_fec>1) + return OPUS_BAD_ARG; + /* For FEC/PLC, frame_size has to be to have a multiple of 2.5 ms */ + if ((decode_fec || len==0 || data==NULL) && frame_size%(st->Fs/400)!=0) + return OPUS_BAD_ARG; +#ifdef ENABLE_DRED + if (dred != NULL && dred->process_stage == 2) { + int F10; + int features_per_frame; + int needed_feature_frames; + int init_frames; + lpcnet_plc_fec_clear(&st->lpcnet); + F10 = st->Fs/100; + /* if blend==0, the last PLC call was "update" and we need to feed two extra 10-ms frames. */ + init_frames = (st->lpcnet.blend == 0) ? 2 : 0; + features_per_frame = IMAX(1, frame_size/F10); + needed_feature_frames = init_frames + features_per_frame; + lpcnet_plc_fec_clear(&st->lpcnet); + for (i=0;idred_offset*F10/4)/F10); + if (feature_offset <= 4*dred->nb_latents-1 && feature_offset >= 0) { + lpcnet_plc_fec_add(&st->lpcnet, dred->fec_features+feature_offset*DRED_NUM_FEATURES); + } else { + if (feature_offset >= 0) lpcnet_plc_fec_add(&st->lpcnet, NULL); + } + + } + } +#else + (void)dred; + (void)dred_offset; +#endif + if (len==0 || data==NULL) + { + int pcm_count=0; + do { + int ret; + ret = opus_decode_frame(st, NULL, 0, pcm+pcm_count*st->channels, frame_size-pcm_count, 0); + if (ret<0) + return ret; + pcm_count += ret; + } while (pcm_count < frame_size); + celt_assert(pcm_count == frame_size); + if (OPUS_CHECK_ARRAY(pcm, pcm_count*st->channels)) + OPUS_PRINT_INT(pcm_count); + st->last_packet_duration = pcm_count; + return pcm_count; + } else if (len<0) + return OPUS_BAD_ARG; + + packet_mode = opus_packet_get_mode(data); + packet_bandwidth = opus_packet_get_bandwidth(data); + packet_frame_size = opus_packet_get_samples_per_frame(data, st->Fs); + packet_stream_channels = opus_packet_get_nb_channels(data); + + count = opus_packet_parse_impl(data, len, self_delimited, &toc, NULL, + size, &offset, packet_offset, NULL, NULL); + if (count<0) + return count; + + data += offset; + + if (decode_fec) + { + int duration_copy; + int ret; + /* If no FEC can be present, run the PLC (recursive call) */ + if (frame_size < packet_frame_size || packet_mode == MODE_CELT_ONLY || st->mode == MODE_CELT_ONLY) + return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL, soft_clip, NULL, 0); + /* Otherwise, run the PLC on everything except the size for which we might have FEC */ + duration_copy = st->last_packet_duration; + if (frame_size-packet_frame_size!=0) + { + ret = opus_decode_native(st, NULL, 0, pcm, frame_size-packet_frame_size, 0, 0, NULL, soft_clip, NULL, 0); + if (ret<0) + { + st->last_packet_duration = duration_copy; + return ret; + } + celt_assert(ret==frame_size-packet_frame_size); + } + /* Complete with FEC */ + st->mode = packet_mode; + st->bandwidth = packet_bandwidth; + st->frame_size = packet_frame_size; + st->stream_channels = packet_stream_channels; + ret = opus_decode_frame(st, data, size[0], pcm+st->channels*(frame_size-packet_frame_size), + packet_frame_size, 1); + if (ret<0) + return ret; + else { + if (OPUS_CHECK_ARRAY(pcm, frame_size*st->channels)) + OPUS_PRINT_INT(frame_size); + st->last_packet_duration = frame_size; + return frame_size; + } + } + + if (count*packet_frame_size > frame_size) + return OPUS_BUFFER_TOO_SMALL; + + /* Update the state as the last step to avoid updating it on an invalid packet */ + st->mode = packet_mode; + st->bandwidth = packet_bandwidth; + st->frame_size = packet_frame_size; + st->stream_channels = packet_stream_channels; + + nb_samples=0; + for (i=0;ichannels, frame_size-nb_samples, 0); + if (ret<0) + return ret; + celt_assert(ret==packet_frame_size); + data += size[i]; + nb_samples += ret; + } + st->last_packet_duration = nb_samples; + if (OPUS_CHECK_ARRAY(pcm, nb_samples*st->channels)) + OPUS_PRINT_INT(nb_samples); +#ifndef FIXED_POINT + if (soft_clip) + opus_pcm_soft_clip(pcm, nb_samples, st->channels, st->softclip_mem); + else + st->softclip_mem[0]=st->softclip_mem[1]=0; +#endif + return nb_samples; +} + +#ifdef FIXED_POINT + +int opus_decode(OpusDecoder *st, const unsigned char *data, + opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec) +{ + if(frame_size<=0) + return OPUS_BAD_ARG; + return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0, NULL, 0); +} + +#ifndef DISABLE_FLOAT_API +int opus_decode_float(OpusDecoder *st, const unsigned char *data, + opus_int32 len, float *pcm, int frame_size, int decode_fec) +{ + VARDECL(opus_int16, out); + int ret, i; + int nb_samples; + ALLOC_STACK; + + if(frame_size<=0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + if (data != NULL && len > 0 && !decode_fec) + { + nb_samples = opus_decoder_get_nb_samples(st, data, len); + if (nb_samples>0) + frame_size = IMIN(frame_size, nb_samples); + else + return OPUS_INVALID_PACKET; + } + celt_assert(st->channels == 1 || st->channels == 2); + ALLOC(out, frame_size*st->channels, opus_int16); + + ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 0, NULL, 0); + if (ret > 0) + { + for (i=0;ichannels;i++) + pcm[i] = (1.f/32768.f)*(out[i]); + } + RESTORE_STACK; + return ret; +} +#endif + + +#else +int opus_decode(OpusDecoder *st, const unsigned char *data, + opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec) +{ + VARDECL(float, out); + int ret, i; + int nb_samples; + ALLOC_STACK; + + if(frame_size<=0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + if (data != NULL && len > 0 && !decode_fec) + { + nb_samples = opus_decoder_get_nb_samples(st, data, len); + if (nb_samples>0) + frame_size = IMIN(frame_size, nb_samples); + else + return OPUS_INVALID_PACKET; + } + celt_assert(st->channels == 1 || st->channels == 2); + ALLOC(out, frame_size*st->channels, float); + + ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 1, NULL, 0); + if (ret > 0) + { + for (i=0;ichannels;i++) + pcm[i] = FLOAT2INT16(out[i]); + } + RESTORE_STACK; + return ret; +} + +int opus_decode_float(OpusDecoder *st, const unsigned char *data, + opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec) +{ + if(frame_size<=0) + return OPUS_BAD_ARG; + return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0, NULL, 0); +} + +#endif + +int opus_decoder_ctl(OpusDecoder *st, int request, ...) +{ + int ret = OPUS_OK; + va_list ap; + void *silk_dec; + CELTDecoder *celt_dec; + + silk_dec = (char*)st+st->silk_dec_offset; + celt_dec = (CELTDecoder*)((char*)st+st->celt_dec_offset); + + + va_start(ap, request); + + switch (request) + { + case OPUS_GET_BANDWIDTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->bandwidth; + } + break; + case OPUS_SET_COMPLEXITY_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>10) + { + goto bad_arg; + } + st->complexity = value; + celt_decoder_ctl(celt_dec, OPUS_SET_COMPLEXITY(value)); + } + break; + case OPUS_GET_COMPLEXITY_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->complexity; + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + opus_uint32 *value = va_arg(ap, opus_uint32*); + if (!value) + { + goto bad_arg; + } + *value = st->rangeFinal; + } + break; + case OPUS_RESET_STATE: + { + OPUS_CLEAR((char*)&st->OPUS_DECODER_RESET_START, + sizeof(OpusDecoder)- + ((char*)&st->OPUS_DECODER_RESET_START - (char*)st)); + + celt_decoder_ctl(celt_dec, OPUS_RESET_STATE); + silk_ResetDecoder( silk_dec ); + st->stream_channels = st->channels; + st->frame_size = st->Fs/400; +#ifdef ENABLE_DEEP_PLC + lpcnet_plc_reset( &st->lpcnet ); +#endif + } + break; + case OPUS_GET_SAMPLE_RATE_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->Fs; + } + break; + case OPUS_GET_PITCH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + if (st->prev_mode == MODE_CELT_ONLY) + ret = celt_decoder_ctl(celt_dec, OPUS_GET_PITCH(value)); + else + *value = st->DecControl.prevPitchLag; + } + break; + case OPUS_GET_GAIN_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->decode_gain; + } + break; + case OPUS_SET_GAIN_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<-32768 || value>32767) + { + goto bad_arg; + } + st->decode_gain = value; + } + break; + case OPUS_GET_LAST_PACKET_DURATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->last_packet_duration; + } + break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + ret = celt_decoder_ctl(celt_dec, OPUS_SET_PHASE_INVERSION_DISABLED(value)); + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + ret = celt_decoder_ctl(celt_dec, OPUS_GET_PHASE_INVERSION_DISABLED(value)); + } + break; +#ifdef USE_WEIGHTS_FILE + case OPUS_SET_DNN_BLOB_REQUEST: + { + const unsigned char *data = va_arg(ap, const unsigned char *); + opus_int32 len = va_arg(ap, opus_int32); + if(len<0 || data == NULL) + { + goto bad_arg; + } + ret = lpcnet_plc_load_model(&st->lpcnet, data, len); + ret = silk_LoadOSCEModels(silk_dec, data, len) || ret; + } + break; +#endif + default: + /*fprintf(stderr, "unknown opus_decoder_ctl() request: %d", request);*/ + ret = OPUS_UNIMPLEMENTED; + break; + } + + va_end(ap); + return ret; +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +} + +void opus_decoder_destroy(OpusDecoder *st) +{ + opus_free(st); +} + + +int opus_packet_get_bandwidth(const unsigned char *data) +{ + int bandwidth; + if (data[0]&0x80) + { + bandwidth = OPUS_BANDWIDTH_MEDIUMBAND + ((data[0]>>5)&0x3); + if (bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) + bandwidth = OPUS_BANDWIDTH_NARROWBAND; + } else if ((data[0]&0x60) == 0x60) + { + bandwidth = (data[0]&0x10) ? OPUS_BANDWIDTH_FULLBAND : + OPUS_BANDWIDTH_SUPERWIDEBAND; + } else { + bandwidth = OPUS_BANDWIDTH_NARROWBAND + ((data[0]>>5)&0x3); + } + return bandwidth; +} + +int opus_packet_get_nb_channels(const unsigned char *data) +{ + return (data[0]&0x4) ? 2 : 1; +} + +int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) +{ + int count; + if (len<1) + return OPUS_BAD_ARG; + count = packet[0]&0x3; + if (count==0) + return 1; + else if (count!=3) + return 2; + else if (len<2) + return OPUS_INVALID_PACKET; + else + return packet[1]&0x3F; +} + +int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, + opus_int32 Fs) +{ + int samples; + int count = opus_packet_get_nb_frames(packet, len); + + if (count<0) + return count; + + samples = count*opus_packet_get_samples_per_frame(packet, Fs); + /* Can't have more than 120 ms */ + if (samples*25 > Fs*3) + return OPUS_INVALID_PACKET; + else + return samples; +} + +int opus_packet_has_lbrr(const unsigned char packet[], opus_int32 len) +{ + int ret; + const unsigned char *frames[48]; + opus_int16 size[48]; + int packet_mode, packet_frame_size, packet_stream_channels; + int nb_frames=1; + int lbrr; + + packet_mode = opus_packet_get_mode(packet); + if (packet_mode == MODE_CELT_ONLY) + return 0; + packet_frame_size = opus_packet_get_samples_per_frame(packet, 48000); + if (packet_frame_size > 960) + nb_frames = packet_frame_size/960; + packet_stream_channels = opus_packet_get_nb_channels(packet); + ret = opus_packet_parse(packet, len, NULL, frames, size, NULL); + if (ret <= 0) + return ret; + lbrr = (frames[0][0] >> (7-nb_frames)) & 0x1; + if (packet_stream_channels == 2) + lbrr = lbrr || ((frames[0][0] >> (6-2*nb_frames)) & 0x1); + return lbrr; +} + +int opus_decoder_get_nb_samples(const OpusDecoder *dec, + const unsigned char packet[], opus_int32 len) +{ + return opus_packet_get_nb_samples(packet, len, dec->Fs); +} + +struct OpusDREDDecoder { +#ifdef ENABLE_DRED + RDOVAEDec model; +#endif + int loaded; + int arch; + opus_uint32 magic; +}; + +#if defined(ENABLE_DRED) && (defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS)) +static void validate_dred_decoder(OpusDREDDecoder *st) +{ + celt_assert(st->magic == 0xD8EDDEC0); +#ifdef OPUS_ARCHMASK + celt_assert(st->arch >= 0); + celt_assert(st->arch <= OPUS_ARCHMASK); +#endif +} +#define VALIDATE_DRED_DECODER(st) validate_dred_decoder(st) +#else +#define VALIDATE_DRED_DECODER(st) +#endif + + +int opus_dred_decoder_get_size(void) +{ + return sizeof(OpusDREDDecoder); +} + +#ifdef ENABLE_DRED +int dred_decoder_load_model(OpusDREDDecoder *dec, const unsigned char *data, int len) +{ + WeightArray *list; + int ret; + parse_weights(&list, data, len); + ret = init_rdovaedec(&dec->model, list); + opus_free(list); + if (ret == 0) dec->loaded = 1; + return (ret == 0) ? OPUS_OK : OPUS_BAD_ARG; +} +#endif + +int opus_dred_decoder_init(OpusDREDDecoder *dec) +{ + int ret = 0; + dec->loaded = 0; +#if defined(ENABLE_DRED) && !defined(USE_WEIGHTS_FILE) + ret = init_rdovaedec(&dec->model, rdovaedec_arrays); + if (ret == 0) dec->loaded = 1; +#endif + dec->arch = opus_select_arch(); + /* To make sure nobody forgets to init, use a magic number. */ + dec->magic = 0xD8EDDEC0; + return (ret == 0) ? OPUS_OK : OPUS_UNIMPLEMENTED; +} + +OpusDREDDecoder *opus_dred_decoder_create(int *error) +{ + int ret; + OpusDREDDecoder *dec; + dec = (OpusDREDDecoder *)opus_alloc(opus_dred_decoder_get_size()); + if (dec == NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + ret = opus_dred_decoder_init(dec); + if (error) + *error = ret; + if (ret != OPUS_OK) + { + opus_free(dec); + dec = NULL; + } + return dec; +} + +void opus_dred_decoder_destroy(OpusDREDDecoder *dec) +{ + if (dec) dec->magic = 0xDE57801D; + opus_free(dec); +} + +int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...) +{ +#ifdef ENABLE_DRED + int ret = OPUS_OK; + va_list ap; + + va_start(ap, request); + (void)dred_dec; + switch (request) + { +# ifdef USE_WEIGHTS_FILE + case OPUS_SET_DNN_BLOB_REQUEST: + { + const unsigned char *data = va_arg(ap, const unsigned char *); + opus_int32 len = va_arg(ap, opus_int32); + if(len<0 || data == NULL) + { + goto bad_arg; + } + return dred_decoder_load_model(dred_dec, data, len); + } + break; +# endif + default: + /*fprintf(stderr, "unknown opus_decoder_ctl() request: %d", request);*/ + ret = OPUS_UNIMPLEMENTED; + break; + } + va_end(ap); + return ret; +# ifdef USE_WEIGHTS_FILE +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +# endif +#else + (void)dred_dec; + (void)request; + return OPUS_UNIMPLEMENTED; +#endif +} + +#ifdef ENABLE_DRED +static int dred_find_payload(const unsigned char *data, opus_int32 len, const unsigned char **payload, int *dred_frame_offset) +{ + const unsigned char *data0; + int len0; + int frame = 0; + int ret; + const unsigned char *frames[48]; + opus_int16 size[48]; + int frame_size; + + *payload = NULL; + /* Get the padding section of the packet. */ + ret = opus_packet_parse_impl(data, len, 0, NULL, frames, size, NULL, NULL, &data0, &len0); + if (ret < 0) + return ret; + frame_size = opus_packet_get_samples_per_frame(data, 48000); + data = data0; + len = len0; + /* Scan extensions in order until we find the earliest frame with DRED data. */ + while (len > 0) + { + opus_int32 header_size; + int id, L; + len0 = len; + data0 = data; + id = *data0 >> 1; + L = *data0 & 0x1; + len = skip_extension(&data, len, &header_size); + if (len < 0) + break; + if (id == 1) + { + if (L==0) + { + frame++; + } else { + frame += data0[1]; + } + } else if (id == DRED_EXTENSION_ID) + { + const unsigned char *curr_payload; + opus_int32 curr_payload_len; + curr_payload = data0+header_size; + curr_payload_len = (data-data0)-header_size; + /* DRED position in the packet, in units of 2.5 ms like for the signaled DRED offset. */ + *dred_frame_offset = frame*frame_size/120; +#ifdef DRED_EXPERIMENTAL_VERSION + /* Check that temporary extension type and version match. + This check will be removed once extension is finalized. */ + if (curr_payload_len > DRED_EXPERIMENTAL_BYTES && curr_payload[0] == 'D' && curr_payload[1] == DRED_EXPERIMENTAL_VERSION) { + *payload = curr_payload+2; + return curr_payload_len-2; + } +#else + if (curr_payload_len > 0) { + *payload = curr_payload; + return curr_payload_len; + } +#endif + } + } + return 0; +} +#endif + +int opus_dred_get_size(void) +{ +#ifdef ENABLE_DRED + return sizeof(OpusDRED); +#else + return 0; +#endif +} + +OpusDRED *opus_dred_alloc(int *error) +{ +#ifdef ENABLE_DRED + OpusDRED *dec; + dec = (OpusDRED *)opus_alloc(opus_dred_get_size()); + if (dec == NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + return dec; +#else + if (error) + *error = OPUS_UNIMPLEMENTED; + return NULL; +#endif +} + +void opus_dred_free(OpusDRED *dec) +{ +#ifdef ENABLE_DRED + opus_free(dec); +#else + (void)dec; +#endif +} + +int opus_dred_parse(OpusDREDDecoder *dred_dec, OpusDRED *dred, const unsigned char *data, opus_int32 len, opus_int32 max_dred_samples, opus_int32 sampling_rate, int *dred_end, int defer_processing) +{ +#ifdef ENABLE_DRED + const unsigned char *payload; + opus_int32 payload_len; + int dred_frame_offset=0; + VALIDATE_DRED_DECODER(dred_dec); + if (!dred_dec->loaded) return OPUS_UNIMPLEMENTED; + dred->process_stage = -1; + payload_len = dred_find_payload(data, len, &payload, &dred_frame_offset); + if (payload_len < 0) + return payload_len; + if (payload != NULL) + { + int offset; + int min_feature_frames; + offset = 100*max_dred_samples/sampling_rate; + min_feature_frames = IMIN(2 + offset, 2*DRED_NUM_REDUNDANCY_FRAMES); + dred_ec_decode(dred, payload, payload_len, min_feature_frames, dred_frame_offset); + if (!defer_processing) + opus_dred_process(dred_dec, dred, dred); + if (dred_end) *dred_end = IMAX(0, -dred->dred_offset*sampling_rate/400); + return IMAX(0, dred->nb_latents*sampling_rate/25 - dred->dred_offset* sampling_rate/400); + } + if (dred_end) *dred_end = 0; + return 0; +#else + (void)dred_dec; + (void)dred; + (void)data; + (void)len; + (void)max_dred_samples; + (void)sampling_rate; + (void)defer_processing; + (void)dred_end; + return OPUS_UNIMPLEMENTED; +#endif +} + +int opus_dred_process(OpusDREDDecoder *dred_dec, const OpusDRED *src, OpusDRED *dst) +{ +#ifdef ENABLE_DRED + if (dred_dec == NULL || src == NULL || dst == NULL || (src->process_stage != 1 && src->process_stage != 2)) + return OPUS_BAD_ARG; + VALIDATE_DRED_DECODER(dred_dec); + if (!dred_dec->loaded) return OPUS_UNIMPLEMENTED; + if (src != dst) + OPUS_COPY(dst, src, 1); + if (dst->process_stage == 2) + return OPUS_OK; + DRED_rdovae_decode_all(&dred_dec->model, dst->fec_features, dst->state, dst->latents, dst->nb_latents, dred_dec->arch); + dst->process_stage = 2; + return OPUS_OK; +#else + (void)dred_dec; + (void)src; + (void)dst; + return OPUS_UNIMPLEMENTED; +#endif +} + +int opus_decoder_dred_decode(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, opus_int16 *pcm, opus_int32 frame_size) +{ +#ifdef ENABLE_DRED + VARDECL(float, out); + int ret, i; + ALLOC_STACK; + + if(frame_size<=0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + celt_assert(st->channels == 1 || st->channels == 2); + ALLOC(out, frame_size*st->channels, float); + + ret = opus_decode_native(st, NULL, 0, out, frame_size, 0, 0, NULL, 1, dred, dred_offset); + if (ret > 0) + { + for (i=0;ichannels;i++) + pcm[i] = FLOAT2INT16(out[i]); + } + RESTORE_STACK; + return ret; +#else + (void)st; + (void)dred; + (void)dred_offset; + (void)pcm; + (void)frame_size; + return OPUS_UNIMPLEMENTED; +#endif +} + +int opus_decoder_dred_decode_float(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, float *pcm, opus_int32 frame_size) +{ +#ifdef ENABLE_DRED + if(frame_size<=0) + return OPUS_BAD_ARG; + return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL, 0, dred, dred_offset); +#else + (void)st; + (void)dred; + (void)dred_offset; + (void)pcm; + (void)frame_size; + return OPUS_UNIMPLEMENTED; +#endif +} diff --git a/port/third_party/opus/src/opus_encoder.c b/port/third_party/opus/src/opus_encoder.c new file mode 100644 index 00000000..d18d582f --- /dev/null +++ b/port/third_party/opus/src/opus_encoder.c @@ -0,0 +1,3089 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "celt.h" +#include "entenc.h" +#include "modes.h" +#include "API.h" +#include "stack_alloc.h" +#include "float_cast.h" +#include "opus.h" +#include "arch.h" +#include "pitch.h" +#include "opus_private.h" +#include "os_support.h" +#include "cpu_support.h" +#include "analysis.h" +#include "mathops.h" +#include "tuning_parameters.h" + +#ifdef ENABLE_DRED +#include "dred_coding.h" +#endif + +#ifdef FIXED_POINT +#include "fixed/structs_FIX.h" +#else +#include "float/structs_FLP.h" +#endif +#ifdef ENABLE_OSCE_TRAINING_DATA +#include +#endif + +#define MAX_ENCODER_BUFFER 480 + +#ifndef DISABLE_FLOAT_API +#define PSEUDO_SNR_THRESHOLD 316.23f /* 10^(25/10) */ +#endif + +typedef struct { + opus_val32 XX, XY, YY; + opus_val16 smoothed_width; + opus_val16 max_follower; +} StereoWidthState; + +struct OpusEncoder { + int celt_enc_offset; + int silk_enc_offset; + silk_EncControlStruct silk_mode; +#ifdef ENABLE_DRED + DREDEnc dred_encoder; +#endif + int application; + int channels; + int delay_compensation; + int force_channels; + int signal_type; + int user_bandwidth; + int max_bandwidth; + int user_forced_mode; + int voice_ratio; + opus_int32 Fs; + int use_vbr; + int vbr_constraint; + int variable_duration; + opus_int32 bitrate_bps; + opus_int32 user_bitrate_bps; + int lsb_depth; + int encoder_buffer; + int lfe; + int arch; + int use_dtx; /* general DTX for both SILK and CELT */ + int fec_config; +#ifndef DISABLE_FLOAT_API + TonalityAnalysisState analysis; +#endif + +#define OPUS_ENCODER_RESET_START stream_channels + int stream_channels; + opus_int16 hybrid_stereo_width_Q14; + opus_int32 variable_HP_smth2_Q15; + opus_val16 prev_HB_gain; + opus_val32 hp_mem[4]; + int mode; + int prev_mode; + int prev_channels; + int prev_framesize; + int bandwidth; + /* Bandwidth determined automatically from the rate (before any other adjustment) */ + int auto_bandwidth; + int silk_bw_switch; + /* Sampling rate (at the API level) */ + int first; + opus_val16 * energy_masking; + StereoWidthState width_mem; + opus_val16 delay_buffer[MAX_ENCODER_BUFFER*2]; +#ifndef DISABLE_FLOAT_API + int detected_bandwidth; + int nb_no_activity_ms_Q1; + opus_val32 peak_signal_energy; +#endif +#ifdef ENABLE_DRED + int dred_duration; + int dred_q0; + int dred_dQ; + int dred_qmax; + int dred_target_chunks; + unsigned char activity_mem[DRED_MAX_FRAMES*4]; /* 2.5ms resolution*/ +#endif + int nonfinal_frame; /* current frame is not the final in a packet */ + opus_uint32 rangeFinal; +}; + +/* Transition tables for the voice and music. First column is the + middle (memoriless) threshold. The second column is the hysteresis + (difference with the middle) */ +static const opus_int32 mono_voice_bandwidth_thresholds[8] = { + 9000, 700, /* NB<->MB */ + 9000, 700, /* MB<->WB */ + 13500, 1000, /* WB<->SWB */ + 14000, 2000, /* SWB<->FB */ +}; +static const opus_int32 mono_music_bandwidth_thresholds[8] = { + 9000, 700, /* NB<->MB */ + 9000, 700, /* MB<->WB */ + 11000, 1000, /* WB<->SWB */ + 12000, 2000, /* SWB<->FB */ +}; +static const opus_int32 stereo_voice_bandwidth_thresholds[8] = { + 9000, 700, /* NB<->MB */ + 9000, 700, /* MB<->WB */ + 13500, 1000, /* WB<->SWB */ + 14000, 2000, /* SWB<->FB */ +}; +static const opus_int32 stereo_music_bandwidth_thresholds[8] = { + 9000, 700, /* NB<->MB */ + 9000, 700, /* MB<->WB */ + 11000, 1000, /* WB<->SWB */ + 12000, 2000, /* SWB<->FB */ +}; +/* Threshold bit-rates for switching between mono and stereo */ +static const opus_int32 stereo_voice_threshold = 19000; +static const opus_int32 stereo_music_threshold = 17000; + +/* Threshold bit-rate for switching between SILK/hybrid and CELT-only */ +static const opus_int32 mode_thresholds[2][2] = { + /* voice */ /* music */ + { 64000, 10000}, /* mono */ + { 44000, 10000}, /* stereo */ +}; + +static const opus_int32 fec_thresholds[] = { + 12000, 1000, /* NB */ + 14000, 1000, /* MB */ + 16000, 1000, /* WB */ + 20000, 1000, /* SWB */ + 22000, 1000, /* FB */ +}; + +int opus_encoder_get_size(int channels) +{ + int silkEncSizeBytes, celtEncSizeBytes; + int ret; + if (channels<1 || channels > 2) + return 0; + ret = silk_Get_Encoder_Size( &silkEncSizeBytes ); + if (ret) + return 0; + silkEncSizeBytes = align(silkEncSizeBytes); + celtEncSizeBytes = celt_encoder_get_size(channels); + return align(sizeof(OpusEncoder))+silkEncSizeBytes+celtEncSizeBytes; +} + +int opus_encoder_init(OpusEncoder* st, opus_int32 Fs, int channels, int application) +{ + void *silk_enc; + CELTEncoder *celt_enc; + int err; + int ret, silkEncSizeBytes; + + if((Fs!=48000&&Fs!=24000&&Fs!=16000&&Fs!=12000&&Fs!=8000)||(channels!=1&&channels!=2)|| + (application != OPUS_APPLICATION_VOIP && application != OPUS_APPLICATION_AUDIO + && application != OPUS_APPLICATION_RESTRICTED_LOWDELAY)) + return OPUS_BAD_ARG; + + OPUS_CLEAR((char*)st, opus_encoder_get_size(channels)); + /* Create SILK encoder */ + ret = silk_Get_Encoder_Size( &silkEncSizeBytes ); + if (ret) + return OPUS_BAD_ARG; + silkEncSizeBytes = align(silkEncSizeBytes); + st->silk_enc_offset = align(sizeof(OpusEncoder)); + st->celt_enc_offset = st->silk_enc_offset+silkEncSizeBytes; + silk_enc = (char*)st+st->silk_enc_offset; + celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); + + st->stream_channels = st->channels = channels; + + st->Fs = Fs; + + st->arch = opus_select_arch(); + + ret = silk_InitEncoder( silk_enc, st->arch, &st->silk_mode ); + if(ret)return OPUS_INTERNAL_ERROR; + + /* default SILK parameters */ + st->silk_mode.nChannelsAPI = channels; + st->silk_mode.nChannelsInternal = channels; + st->silk_mode.API_sampleRate = st->Fs; + st->silk_mode.maxInternalSampleRate = 16000; + st->silk_mode.minInternalSampleRate = 8000; + st->silk_mode.desiredInternalSampleRate = 16000; + st->silk_mode.payloadSize_ms = 20; + st->silk_mode.bitRate = 25000; + st->silk_mode.packetLossPercentage = 0; + st->silk_mode.complexity = 9; + st->silk_mode.useInBandFEC = 0; + st->silk_mode.useDRED = 0; + st->silk_mode.useDTX = 0; + st->silk_mode.useCBR = 0; + st->silk_mode.reducedDependency = 0; + + /* Create CELT encoder */ + /* Initialize CELT encoder */ + err = celt_encoder_init(celt_enc, Fs, channels, st->arch); + if(err!=OPUS_OK)return OPUS_INTERNAL_ERROR; + + celt_encoder_ctl(celt_enc, CELT_SET_SIGNALLING(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_COMPLEXITY(st->silk_mode.complexity)); + +#ifdef ENABLE_DRED + /* Initialize DRED Encoder */ + dred_encoder_init( &st->dred_encoder, Fs, channels ); +#endif + + st->use_vbr = 1; + /* Makes constrained VBR the default (safer for real-time use) */ + st->vbr_constraint = 1; + st->user_bitrate_bps = OPUS_AUTO; + st->bitrate_bps = 3000+Fs*channels; + st->application = application; + st->signal_type = OPUS_AUTO; + st->user_bandwidth = OPUS_AUTO; + st->max_bandwidth = OPUS_BANDWIDTH_FULLBAND; + st->force_channels = OPUS_AUTO; + st->user_forced_mode = OPUS_AUTO; + st->voice_ratio = -1; + st->encoder_buffer = st->Fs/100; + st->lsb_depth = 24; + st->variable_duration = OPUS_FRAMESIZE_ARG; + + /* Delay compensation of 4 ms (2.5 ms for SILK's extra look-ahead + + 1.5 ms for SILK resamplers and stereo prediction) */ + st->delay_compensation = st->Fs/250; + + st->hybrid_stereo_width_Q14 = 1 << 14; + st->prev_HB_gain = Q15ONE; + st->variable_HP_smth2_Q15 = silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ); + st->first = 1; + st->mode = MODE_HYBRID; + st->bandwidth = OPUS_BANDWIDTH_FULLBAND; + +#ifndef DISABLE_FLOAT_API + tonality_analysis_init(&st->analysis, st->Fs); + st->analysis.application = st->application; +#endif + + return OPUS_OK; +} + +static unsigned char gen_toc(int mode, int framerate, int bandwidth, int channels) +{ + int period; + unsigned char toc; + period = 0; + while (framerate < 400) + { + framerate <<= 1; + period++; + } + if (mode == MODE_SILK_ONLY) + { + toc = (bandwidth-OPUS_BANDWIDTH_NARROWBAND)<<5; + toc |= (period-2)<<3; + } else if (mode == MODE_CELT_ONLY) + { + int tmp = bandwidth-OPUS_BANDWIDTH_MEDIUMBAND; + if (tmp < 0) + tmp = 0; + toc = 0x80; + toc |= tmp << 5; + toc |= period<<3; + } else /* Hybrid */ + { + toc = 0x60; + toc |= (bandwidth-OPUS_BANDWIDTH_SUPERWIDEBAND)<<4; + toc |= (period-2)<<3; + } + toc |= (channels==2)<<2; + return toc; +} + +#ifndef FIXED_POINT +static void silk_biquad_float( + const opus_val16 *in, /* I: Input signal */ + const opus_int32 *B_Q28, /* I: MA coefficients [3] */ + const opus_int32 *A_Q28, /* I: AR coefficients [2] */ + opus_val32 *S, /* I/O: State vector [2] */ + opus_val16 *out, /* O: Output signal */ + const opus_int32 len, /* I: Signal length (must be even) */ + int stride +) +{ + /* DIRECT FORM II TRANSPOSED (uses 2 element state vector) */ + opus_int k; + opus_val32 vout; + opus_val32 inval; + opus_val32 A[2], B[3]; + + A[0] = (opus_val32)(A_Q28[0] * (1.f/((opus_int32)1<<28))); + A[1] = (opus_val32)(A_Q28[1] * (1.f/((opus_int32)1<<28))); + B[0] = (opus_val32)(B_Q28[0] * (1.f/((opus_int32)1<<28))); + B[1] = (opus_val32)(B_Q28[1] * (1.f/((opus_int32)1<<28))); + B[2] = (opus_val32)(B_Q28[2] * (1.f/((opus_int32)1<<28))); + + /* Negate A_Q28 values and split in two parts */ + + for( k = 0; k < len; k++ ) { + /* S[ 0 ], S[ 1 ]: Q12 */ + inval = in[ k*stride ]; + vout = S[ 0 ] + B[0]*inval; + + S[ 0 ] = S[1] - vout*A[0] + B[1]*inval; + + S[ 1 ] = - vout*A[1] + B[2]*inval + VERY_SMALL; + + /* Scale back to Q0 and saturate */ + out[ k*stride ] = vout; + } +} +#endif + +static void hp_cutoff(const opus_val16 *in, opus_int32 cutoff_Hz, opus_val16 *out, opus_val32 *hp_mem, int len, int channels, opus_int32 Fs, int arch) +{ + opus_int32 B_Q28[ 3 ], A_Q28[ 2 ]; + opus_int32 Fc_Q19, r_Q28, r_Q22; + (void)arch; + + silk_assert( cutoff_Hz <= silk_int32_MAX / SILK_FIX_CONST( 1.5 * 3.14159 / 1000, 19 ) ); + Fc_Q19 = silk_DIV32_16( silk_SMULBB( SILK_FIX_CONST( 1.5 * 3.14159 / 1000, 19 ), cutoff_Hz ), Fs/1000 ); + silk_assert( Fc_Q19 > 0 && Fc_Q19 < 32768 ); + + r_Q28 = SILK_FIX_CONST( 1.0, 28 ) - silk_MUL( SILK_FIX_CONST( 0.92, 9 ), Fc_Q19 ); + + /* b = r * [ 1; -2; 1 ]; */ + /* a = [ 1; -2 * r * ( 1 - 0.5 * Fc^2 ); r^2 ]; */ + B_Q28[ 0 ] = r_Q28; + B_Q28[ 1 ] = silk_LSHIFT( -r_Q28, 1 ); + B_Q28[ 2 ] = r_Q28; + + /* -r * ( 2 - Fc * Fc ); */ + r_Q22 = silk_RSHIFT( r_Q28, 6 ); + A_Q28[ 0 ] = silk_SMULWW( r_Q22, silk_SMULWW( Fc_Q19, Fc_Q19 ) - SILK_FIX_CONST( 2.0, 22 ) ); + A_Q28[ 1 ] = silk_SMULWW( r_Q22, r_Q22 ); + +#ifdef FIXED_POINT + if( channels == 1 ) { + silk_biquad_alt_stride1( in, B_Q28, A_Q28, hp_mem, out, len ); + } else { + silk_biquad_alt_stride2( in, B_Q28, A_Q28, hp_mem, out, len, arch ); + } +#else + silk_biquad_float( in, B_Q28, A_Q28, hp_mem, out, len, channels ); + if( channels == 2 ) { + silk_biquad_float( in+1, B_Q28, A_Q28, hp_mem+2, out+1, len, channels ); + } +#endif +} + +#ifdef FIXED_POINT +static void dc_reject(const opus_val16 *in, opus_int32 cutoff_Hz, opus_val16 *out, opus_val32 *hp_mem, int len, int channels, opus_int32 Fs) +{ + int c, i; + int shift; + + /* Approximates -round(log2(6.3*cutoff_Hz/Fs)) */ + shift=celt_ilog2(Fs/(cutoff_Hz*4)); + for (c=0;csilk_mode.useInBandFEC) { + dred_frac = MIN16(.7f, 3.f*st->silk_mode.packetLossPercentage/100.f); + bitrate_offset = 20000; + } else { + if (st->silk_mode.packetLossPercentage > 5) { + dred_frac = MIN16(.8f, .55f + st->silk_mode.packetLossPercentage/100.f); + } else { + dred_frac = 12*st->silk_mode.packetLossPercentage/100.f; + } + bitrate_offset = 12000; + } + /* Account for the fact that longer packets require less redundancy. */ + dred_frac = dred_frac/(dred_frac + (1-dred_frac)*(frame_size*50.f)/st->Fs); + /* Approximate fit based on a few experiments. Could probably be improved. */ + q0 = IMIN(15, IMAX(4, 51 - 3*EC_ILOG(IMAX(1, bitrate_bps-bitrate_offset)))); + dQ = bitrate_bps-bitrate_offset > 36000 ? 3 : 5; + qmax = 15; + target_dred_bitrate = IMAX(0, (int)(dred_frac*(bitrate_bps-bitrate_offset))); + if (st->dred_duration > 0) { + opus_int32 target_bits = target_dred_bitrate*frame_size/st->Fs; + max_dred_bits = estimate_dred_bitrate(q0, dQ, qmax, st->dred_duration, target_bits, &target_chunks); + } else { + max_dred_bits = 0; + target_chunks=0; + } + dred_bitrate = IMIN(target_dred_bitrate, max_dred_bits*st->Fs/frame_size); + /* If we can't afford enough bits, don't bother with DRED at all. */ + if (target_chunks < 2) + dred_bitrate = 0; + st->dred_q0 = q0; + st->dred_dQ = dQ; + st->dred_qmax = qmax; + st->dred_target_chunks = target_chunks; + return dred_bitrate; +} +#endif + +static opus_int32 user_bitrate_to_bitrate(OpusEncoder *st, int frame_size, int max_data_bytes) +{ + if(!frame_size)frame_size=st->Fs/400; + if (st->user_bitrate_bps==OPUS_AUTO) + return 60*st->Fs/frame_size + st->Fs*st->channels; + else if (st->user_bitrate_bps==OPUS_BITRATE_MAX) + return max_data_bytes*8*st->Fs/frame_size; + else + return st->user_bitrate_bps; +} + +#ifndef DISABLE_FLOAT_API +#ifdef FIXED_POINT +#define PCM2VAL(x) FLOAT2INT16(x) +#else +#define PCM2VAL(x) SCALEIN(x) +#endif + +void downmix_float(const void *_x, opus_val32 *y, int subframe, int offset, int c1, int c2, int C) +{ + const float *x; + int j; + + x = (const float *)_x; + for (j=0;j-1) + { + for (j=0;j-1) + { + for (j=0;j= OPUS_FRAMESIZE_2_5_MS && variable_duration <= OPUS_FRAMESIZE_120_MS) + { + if (variable_duration <= OPUS_FRAMESIZE_40_MS) + new_size = (Fs/400)<<(variable_duration-OPUS_FRAMESIZE_2_5_MS); + else + new_size = (variable_duration-OPUS_FRAMESIZE_2_5_MS-2)*Fs/50; + } + else + return -1; + if (new_size>frame_size) + return -1; + if (400*new_size!=Fs && 200*new_size!=Fs && 100*new_size!=Fs && + 50*new_size!=Fs && 25*new_size!=Fs && 50*new_size!=3*Fs && + 50*new_size!=4*Fs && 50*new_size!=5*Fs && 50*new_size!=6*Fs) + return -1; + return new_size; +} + +opus_val16 compute_stereo_width(const opus_val16 *pcm, int frame_size, opus_int32 Fs, StereoWidthState *mem) +{ + opus_val32 xx, xy, yy; + opus_val16 sqrt_xx, sqrt_yy; + opus_val16 qrrt_xx, qrrt_yy; + int frame_rate; + int i; + opus_val16 short_alpha; + + frame_rate = Fs/frame_size; + short_alpha = Q15ONE - MULT16_16(25, Q15ONE)/IMAX(50,frame_rate); + xx=xy=yy=0; + /* Unroll by 4. The frame size is always a multiple of 4 *except* for + 2.5 ms frames at 12 kHz. Since this setting is very rare (and very + stupid), we just discard the last two samples. */ + for (i=0;iXX += MULT16_32_Q15(short_alpha, xx-mem->XX); + mem->XY += MULT16_32_Q15(short_alpha, xy-mem->XY); + mem->YY += MULT16_32_Q15(short_alpha, yy-mem->YY); + mem->XX = MAX32(0, mem->XX); + mem->XY = MAX32(0, mem->XY); + mem->YY = MAX32(0, mem->YY); + if (MAX32(mem->XX, mem->YY)>QCONST16(8e-4f, 18)) + { + opus_val16 corr; + opus_val16 ldiff; + opus_val16 width; + sqrt_xx = celt_sqrt(mem->XX); + sqrt_yy = celt_sqrt(mem->YY); + qrrt_xx = celt_sqrt(sqrt_xx); + qrrt_yy = celt_sqrt(sqrt_yy); + /* Inter-channel correlation */ + mem->XY = MIN32(mem->XY, sqrt_xx*sqrt_yy); + corr = SHR32(frac_div32(mem->XY,EPSILON+MULT16_16(sqrt_xx,sqrt_yy)),16); + /* Approximate loudness difference */ + ldiff = MULT16_16(Q15ONE, ABS16(qrrt_xx-qrrt_yy))/(EPSILON+qrrt_xx+qrrt_yy); + width = MULT16_16_Q15(celt_sqrt(QCONST32(1.f,30)-MULT16_16(corr,corr)), ldiff); + /* Smoothing over one second */ + mem->smoothed_width += (width-mem->smoothed_width)/frame_rate; + /* Peak follower */ + mem->max_follower = MAX16(mem->max_follower-QCONST16(.02f,15)/frame_rate, mem->smoothed_width); + } + /*printf("%f %f %f %f %f ", corr/(float)Q15ONE, ldiff/(float)Q15ONE, width/(float)Q15ONE, mem->smoothed_width/(float)Q15ONE, mem->max_follower/(float)Q15ONE);*/ + return EXTRACT16(MIN32(Q15ONE, MULT16_16(20, mem->max_follower))); +} + +static int decide_fec(int useInBandFEC, int PacketLoss_perc, int last_fec, int mode, int *bandwidth, opus_int32 rate) +{ + int orig_bandwidth; + if (!useInBandFEC || PacketLoss_perc == 0 || mode == MODE_CELT_ONLY) + return 0; + orig_bandwidth = *bandwidth; + for (;;) + { + opus_int32 hysteresis; + opus_int32 LBRR_rate_thres_bps; + /* Compute threshold for using FEC at the current bandwidth setting */ + LBRR_rate_thres_bps = fec_thresholds[2*(*bandwidth - OPUS_BANDWIDTH_NARROWBAND)]; + hysteresis = fec_thresholds[2*(*bandwidth - OPUS_BANDWIDTH_NARROWBAND) + 1]; + if (last_fec == 1) LBRR_rate_thres_bps -= hysteresis; + if (last_fec == 0) LBRR_rate_thres_bps += hysteresis; + LBRR_rate_thres_bps = silk_SMULWB( silk_MUL( LBRR_rate_thres_bps, + 125 - silk_min( PacketLoss_perc, 25 ) ), SILK_FIX_CONST( 0.01, 16 ) ); + /* If loss <= 5%, we look at whether we have enough rate to enable FEC. + If loss > 5%, we decrease the bandwidth until we can enable FEC. */ + if (rate > LBRR_rate_thres_bps) + return 1; + else if (PacketLoss_perc <= 5) + return 0; + else if (*bandwidth > OPUS_BANDWIDTH_NARROWBAND) + (*bandwidth)--; + else + break; + } + /* Couldn't find any bandwidth to enable FEC, keep original bandwidth. */ + *bandwidth = orig_bandwidth; + return 0; +} + +static int compute_silk_rate_for_hybrid(int rate, int bandwidth, int frame20ms, int vbr, int fec, int channels) { + int entry; + int i; + int N; + int silk_rate; + static int rate_table[][5] = { + /* |total| |-------- SILK------------| + |-- No FEC -| |--- FEC ---| + 10ms 20ms 10ms 20ms */ + { 0, 0, 0, 0, 0}, + {12000, 10000, 10000, 11000, 11000}, + {16000, 13500, 13500, 15000, 15000}, + {20000, 16000, 16000, 18000, 18000}, + {24000, 18000, 18000, 21000, 21000}, + {32000, 22000, 22000, 28000, 28000}, + {64000, 38000, 38000, 50000, 50000} + }; + /* Do the allocation per-channel. */ + rate /= channels; + entry = 1 + frame20ms + 2*fec; + N = sizeof(rate_table)/sizeof(rate_table[0]); + for (i=1;i rate) break; + } + if (i == N) + { + silk_rate = rate_table[i-1][entry]; + /* For now, just give 50% of the extra bits to SILK. */ + silk_rate += (rate-rate_table[i-1][0])/2; + } else { + opus_int32 lo, hi, x0, x1; + lo = rate_table[i-1][entry]; + hi = rate_table[i][entry]; + x0 = rate_table[i-1][0]; + x1 = rate_table[i][0]; + silk_rate = (lo*(x1-rate) + hi*(rate-x0))/(x1-x0); + } + if (!vbr) + { + /* Tiny boost to SILK for CBR. We should probably tune this better. */ + silk_rate += 100; + } + if (bandwidth==OPUS_BANDWIDTH_SUPERWIDEBAND) + silk_rate += 300; + silk_rate *= channels; + /* Small adjustment for stereo (calibrated for 32 kb/s, haven't tried other bitrates). */ + if (channels == 2 && rate >= 12000) + silk_rate -= 1000; + return silk_rate; +} + +/* Returns the equivalent bitrate corresponding to 20 ms frames, + complexity 10 VBR operation. */ +static opus_int32 compute_equiv_rate(opus_int32 bitrate, int channels, + int frame_rate, int vbr, int mode, int complexity, int loss) +{ + opus_int32 equiv; + equiv = bitrate; + /* Take into account overhead from smaller frames. */ + if (frame_rate > 50) + equiv -= (40*channels+20)*(frame_rate - 50); + /* CBR is about a 8% penalty for both SILK and CELT. */ + if (!vbr) + equiv -= equiv/12; + /* Complexity makes about 10% difference (from 0 to 10) in general. */ + equiv = equiv * (90+complexity)/100; + if (mode == MODE_SILK_ONLY || mode == MODE_HYBRID) + { + /* SILK complexity 0-1 uses the non-delayed-decision NSQ, which + costs about 20%. */ + if (complexity<2) + equiv = equiv*4/5; + equiv -= equiv*loss/(6*loss + 10); + } else if (mode == MODE_CELT_ONLY) { + /* CELT complexity 0-4 doesn't have the pitch filter, which costs + about 10%. */ + if (complexity<5) + equiv = equiv*9/10; + } else { + /* Mode not known yet */ + /* Half the SILK loss*/ + equiv -= equiv*loss/(12*loss + 20); + } + return equiv; +} + +#ifndef DISABLE_FLOAT_API + +int is_digital_silence(const opus_val16* pcm, int frame_size, int channels, int lsb_depth) +{ + int silence = 0; + opus_val32 sample_max = 0; +#ifdef MLP_TRAINING + return 0; +#endif + sample_max = celt_maxabs16(pcm, frame_size*channels); + +#ifdef FIXED_POINT + silence = (sample_max == 0); + (void)lsb_depth; +#else + silence = (sample_max <= (opus_val16) 1 / (1 << lsb_depth)); +#endif + + return silence; +} + +#ifdef FIXED_POINT +static opus_val32 compute_frame_energy(const opus_val16 *pcm, int frame_size, int channels, int arch) +{ + int i; + opus_val32 sample_max; + int max_shift; + int shift; + opus_val32 energy = 0; + int len = frame_size*channels; + (void)arch; + /* Max amplitude in the signal */ + sample_max = celt_maxabs16(pcm, len); + + /* Compute the right shift required in the MAC to avoid an overflow */ + max_shift = celt_ilog2(len); + shift = IMAX(0, (celt_ilog2(1+sample_max) << 1) + max_shift - 28); + + /* Compute the energy */ + for (i=0; i NB_SPEECH_FRAMES_BEFORE_DTX*20*2) + { + if (*nb_no_activity_ms_Q1 <= (NB_SPEECH_FRAMES_BEFORE_DTX + MAX_CONSECUTIVE_DTX)*20*2) + /* Valid frame for DTX! */ + return 1; + else + (*nb_no_activity_ms_Q1) = NB_SPEECH_FRAMES_BEFORE_DTX*20*2; + } + } else + (*nb_no_activity_ms_Q1) = 0; + + return 0; +} + +#endif + +static int compute_redundancy_bytes(opus_int32 max_data_bytes, opus_int32 bitrate_bps, int frame_rate, int channels) +{ + int redundancy_bytes_cap; + int redundancy_bytes; + opus_int32 redundancy_rate; + int base_bits; + opus_int32 available_bits; + base_bits = (40*channels+20); + + /* Equivalent rate for 5 ms frames. */ + redundancy_rate = bitrate_bps + base_bits*(200 - frame_rate); + /* For VBR, further increase the bitrate if we can afford it. It's pretty short + and we'll avoid artefacts. */ + redundancy_rate = 3*redundancy_rate/2; + redundancy_bytes = redundancy_rate/1600; + + /* Compute the max rate we can use given CBR or VBR with cap. */ + available_bits = max_data_bytes*8 - 2*base_bits; + redundancy_bytes_cap = (available_bits*240/(240+48000/frame_rate) + base_bits)/8; + redundancy_bytes = IMIN(redundancy_bytes, redundancy_bytes_cap); + /* It we can't get enough bits for redundancy to be worth it, rely on the decoder PLC. */ + if (redundancy_bytes > 4 + 8*channels) + redundancy_bytes = IMIN(257, redundancy_bytes); + else + redundancy_bytes = 0; + return redundancy_bytes; +} + +static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, + unsigned char *data, opus_int32 max_data_bytes, + int float_api, int first_frame, +#ifdef ENABLE_DRED + opus_int32 dred_bitrate_bps, +#endif +#ifndef DISABLE_FLOAT_API + AnalysisInfo *analysis_info, int is_silence, +#endif + int redundancy, int celt_to_silk, int prefill, + opus_int32 equiv_rate, int to_celt); + +opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, + unsigned char *data, opus_int32 out_data_bytes, int lsb_depth, + const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2, + int analysis_channels, downmix_func downmix, int float_api) +{ + void *silk_enc; + CELTEncoder *celt_enc; + int i; + int ret=0; + int prefill=0; + int redundancy = 0; + int celt_to_silk = 0; + int to_celt = 0; + int voice_est; /* Probability of voice in Q7 */ + opus_int32 equiv_rate; + int frame_rate; + opus_int32 max_rate; /* Max bitrate we're allowed to use */ + int curr_bandwidth; + opus_int32 max_data_bytes; /* Max number of bytes we're allowed to use */ + opus_int32 cbr_bytes=-1; + opus_val16 stereo_width; + const CELTMode *celt_mode; +#ifndef DISABLE_FLOAT_API + AnalysisInfo analysis_info; + int analysis_read_pos_bak=-1; + int analysis_read_subframe_bak=-1; + int is_silence = 0; +#endif +#ifdef ENABLE_DRED + opus_int32 dred_bitrate_bps; +#endif + ALLOC_STACK; + + max_data_bytes = IMIN(1276, out_data_bytes); + + st->rangeFinal = 0; + if (frame_size <= 0 || max_data_bytes <= 0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + /* Cannot encode 100 ms in 1 byte */ + if (max_data_bytes==1 && st->Fs==(frame_size*10)) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + + silk_enc = (char*)st+st->silk_enc_offset; + celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); + + lsb_depth = IMIN(lsb_depth, st->lsb_depth); + + celt_encoder_ctl(celt_enc, CELT_GET_MODE(&celt_mode)); +#ifndef DISABLE_FLOAT_API + analysis_info.valid = 0; +#ifdef FIXED_POINT + if (st->silk_mode.complexity >= 10 && st->Fs>=16000) +#else + if (st->silk_mode.complexity >= 7 && st->Fs>=16000) +#endif + { + is_silence = is_digital_silence(pcm, frame_size, st->channels, lsb_depth); + analysis_read_pos_bak = st->analysis.read_pos; + analysis_read_subframe_bak = st->analysis.read_subframe; + run_analysis(&st->analysis, celt_mode, analysis_pcm, analysis_size, frame_size, + c1, c2, analysis_channels, st->Fs, + lsb_depth, downmix, &analysis_info); + + /* Track the peak signal energy */ + if (!is_silence && analysis_info.activity_probability > DTX_ACTIVITY_THRESHOLD) + st->peak_signal_energy = MAX32(MULT16_32_Q15(QCONST16(0.999f, 15), st->peak_signal_energy), + compute_frame_energy(pcm, frame_size, st->channels, st->arch)); + } else if (st->analysis.initialized) { + tonality_analysis_reset(&st->analysis); + } +#else + (void)analysis_pcm; + (void)analysis_size; + (void)c1; + (void)c2; + (void)analysis_channels; + (void)downmix; +#endif + +#ifndef DISABLE_FLOAT_API + /* Reset voice_ratio if this frame is not silent or if analysis is disabled. + * Otherwise, preserve voice_ratio from the last non-silent frame */ + if (!is_silence) + st->voice_ratio = -1; + + st->detected_bandwidth = 0; + if (analysis_info.valid) + { + int analysis_bandwidth; + if (st->signal_type == OPUS_AUTO) + { + float prob; + if (st->prev_mode == 0) + prob = analysis_info.music_prob; + else if (st->prev_mode == MODE_CELT_ONLY) + prob = analysis_info.music_prob_max; + else + prob = analysis_info.music_prob_min; + st->voice_ratio = (int)floor(.5+100*(1-prob)); + } + + analysis_bandwidth = analysis_info.bandwidth; + if (analysis_bandwidth<=12) + st->detected_bandwidth = OPUS_BANDWIDTH_NARROWBAND; + else if (analysis_bandwidth<=14) + st->detected_bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + else if (analysis_bandwidth<=16) + st->detected_bandwidth = OPUS_BANDWIDTH_WIDEBAND; + else if (analysis_bandwidth<=18) + st->detected_bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND; + else + st->detected_bandwidth = OPUS_BANDWIDTH_FULLBAND; + } +#else + st->voice_ratio = -1; +#endif + + if (st->channels==2 && st->force_channels!=1) + stereo_width = compute_stereo_width(pcm, frame_size, st->Fs, &st->width_mem); + else + stereo_width = 0; + st->bitrate_bps = user_bitrate_to_bitrate(st, frame_size, max_data_bytes); + + frame_rate = st->Fs/frame_size; + if (!st->use_vbr) + { + /* Multiply by 12 to make sure the division is exact. */ + int frame_rate12 = 12*st->Fs/frame_size; + /* We need to make sure that "int" values always fit in 16 bits. */ + cbr_bytes = IMIN( (12*st->bitrate_bps/8 + frame_rate12/2)/frame_rate12, max_data_bytes); + st->bitrate_bps = cbr_bytes*(opus_int32)frame_rate12*8/12; + /* Make sure we provide at least one byte to avoid failing. */ + max_data_bytes = IMAX(1, cbr_bytes); + } +#ifdef ENABLE_DRED + /* Allocate some of the bits to DRED if needed. */ + dred_bitrate_bps = compute_dred_bitrate(st, st->bitrate_bps, frame_size); + st->bitrate_bps -= dred_bitrate_bps; +#endif + if (max_data_bytes<3 || st->bitrate_bps < 3*frame_rate*8 + || (frame_rate<50 && (max_data_bytes*frame_rate<300 || st->bitrate_bps < 2400))) + { + /*If the space is too low to do something useful, emit 'PLC' frames.*/ + int tocmode = st->mode; + int bw = st->bandwidth == 0 ? OPUS_BANDWIDTH_NARROWBAND : st->bandwidth; + int packet_code = 0; + int num_multiframes = 0; + + if (tocmode==0) + tocmode = MODE_SILK_ONLY; + if (frame_rate>100) + tocmode = MODE_CELT_ONLY; + /* 40 ms -> 2 x 20 ms if in CELT_ONLY or HYBRID mode */ + if (frame_rate==25 && tocmode!=MODE_SILK_ONLY) + { + frame_rate = 50; + packet_code = 1; + } + + /* >= 60 ms frames */ + if (frame_rate<=16) + { + /* 1 x 60 ms, 2 x 40 ms, 2 x 60 ms */ + if (out_data_bytes==1 || (tocmode==MODE_SILK_ONLY && frame_rate!=10)) + { + tocmode = MODE_SILK_ONLY; + + packet_code = frame_rate <= 12; + frame_rate = frame_rate == 12 ? 25 : 16; + } + else + { + num_multiframes = 50/frame_rate; + frame_rate = 50; + packet_code = 3; + } + } + + if(tocmode==MODE_SILK_ONLY&&bw>OPUS_BANDWIDTH_WIDEBAND) + bw=OPUS_BANDWIDTH_WIDEBAND; + else if (tocmode==MODE_CELT_ONLY&&bw==OPUS_BANDWIDTH_MEDIUMBAND) + bw=OPUS_BANDWIDTH_NARROWBAND; + else if (tocmode==MODE_HYBRID&&bw<=OPUS_BANDWIDTH_SUPERWIDEBAND) + bw=OPUS_BANDWIDTH_SUPERWIDEBAND; + + data[0] = gen_toc(tocmode, frame_rate, bw, st->stream_channels); + data[0] |= packet_code; + + ret = packet_code <= 1 ? 1 : 2; + + max_data_bytes = IMAX(max_data_bytes, ret); + + if (packet_code==3) + data[1] = num_multiframes; + + if (!st->use_vbr) + { + ret = opus_packet_pad(data, ret, max_data_bytes); + if (ret == OPUS_OK) + ret = max_data_bytes; + else + ret = OPUS_INTERNAL_ERROR; + } + RESTORE_STACK; + return ret; + } + max_rate = frame_rate*max_data_bytes*8; + + /* Equivalent 20-ms rate for mode/channel/bandwidth decisions */ + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->channels, st->Fs/frame_size, + st->use_vbr, 0, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); + + if (st->signal_type == OPUS_SIGNAL_VOICE) + voice_est = 127; + else if (st->signal_type == OPUS_SIGNAL_MUSIC) + voice_est = 0; + else if (st->voice_ratio >= 0) + { + voice_est = st->voice_ratio*327>>8; + /* For AUDIO, never be more than 90% confident of having speech */ + if (st->application == OPUS_APPLICATION_AUDIO) + voice_est = IMIN(voice_est, 115); + } else if (st->application == OPUS_APPLICATION_VOIP) + voice_est = 115; + else + voice_est = 48; + + if (st->force_channels!=OPUS_AUTO && st->channels == 2) + { + st->stream_channels = st->force_channels; + } else { +#ifdef FUZZING + (void)stereo_music_threshold; + (void)stereo_voice_threshold; + /* Random mono/stereo decision */ + if (st->channels == 2 && (rand()&0x1F)==0) + st->stream_channels = 3-st->stream_channels; +#else + /* Rate-dependent mono-stereo decision */ + if (st->channels == 2) + { + opus_int32 stereo_threshold; + stereo_threshold = stereo_music_threshold + ((voice_est*voice_est*(stereo_voice_threshold-stereo_music_threshold))>>14); + if (st->stream_channels == 2) + stereo_threshold -= 1000; + else + stereo_threshold += 1000; + st->stream_channels = (equiv_rate > stereo_threshold) ? 2 : 1; + } else { + st->stream_channels = st->channels; + } +#endif + } + /* Update equivalent rate for channels decision. */ + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->stream_channels, st->Fs/frame_size, + st->use_vbr, 0, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); + + /* Allow SILK DTX if DTX is enabled but the generalized DTX cannot be used, + e.g. because of the complexity setting or sample rate. */ +#ifndef DISABLE_FLOAT_API + st->silk_mode.useDTX = st->use_dtx && !(analysis_info.valid || is_silence); +#else + st->silk_mode.useDTX = st->use_dtx; +#endif + + /* Mode selection depending on application and signal type */ + if (st->application == OPUS_APPLICATION_RESTRICTED_LOWDELAY) + { + st->mode = MODE_CELT_ONLY; + } else if (st->user_forced_mode == OPUS_AUTO) + { +#ifdef FUZZING + (void)stereo_width; + (void)mode_thresholds; + /* Random mode switching */ + if ((rand()&0xF)==0) + { + if ((rand()&0x1)==0) + st->mode = MODE_CELT_ONLY; + else + st->mode = MODE_SILK_ONLY; + } else { + if (st->prev_mode==MODE_CELT_ONLY) + st->mode = MODE_CELT_ONLY; + else + st->mode = MODE_SILK_ONLY; + } +#else + opus_int32 mode_voice, mode_music; + opus_int32 threshold; + + /* Interpolate based on stereo width */ + mode_voice = (opus_int32)(MULT16_32_Q15(Q15ONE-stereo_width,mode_thresholds[0][0]) + + MULT16_32_Q15(stereo_width,mode_thresholds[1][0])); + mode_music = (opus_int32)(MULT16_32_Q15(Q15ONE-stereo_width,mode_thresholds[1][1]) + + MULT16_32_Q15(stereo_width,mode_thresholds[1][1])); + /* Interpolate based on speech/music probability */ + threshold = mode_music + ((voice_est*voice_est*(mode_voice-mode_music))>>14); + /* Bias towards SILK for VoIP because of some useful features */ + if (st->application == OPUS_APPLICATION_VOIP) + threshold += 8000; + + /*printf("%f %d\n", stereo_width/(float)Q15ONE, threshold);*/ + /* Hysteresis */ + if (st->prev_mode == MODE_CELT_ONLY) + threshold -= 4000; + else if (st->prev_mode>0) + threshold += 4000; + + st->mode = (equiv_rate >= threshold) ? MODE_CELT_ONLY: MODE_SILK_ONLY; + + /* When FEC is enabled and there's enough packet loss, use SILK. + Unless the FEC is set to 2, in which case we don't switch to SILK if we're confident we have music. */ + if (st->silk_mode.useInBandFEC && st->silk_mode.packetLossPercentage > (128-voice_est)>>4 && (st->fec_config != 2 || voice_est > 25)) + st->mode = MODE_SILK_ONLY; + /* When encoding voice and DTX is enabled but the generalized DTX cannot be used, + use SILK in order to make use of its DTX. */ + if (st->silk_mode.useDTX && voice_est > 100) + st->mode = MODE_SILK_ONLY; +#endif + + /* If max_data_bytes represents less than 6 kb/s, switch to CELT-only mode */ + if (max_data_bytes < (frame_rate > 50 ? 9000 : 6000)*frame_size / (st->Fs * 8)) + st->mode = MODE_CELT_ONLY; + } else { + st->mode = st->user_forced_mode; + } + + /* Override the chosen mode to make sure we meet the requested frame size */ + if (st->mode != MODE_CELT_ONLY && frame_size < st->Fs/100) + st->mode = MODE_CELT_ONLY; + if (st->lfe) + st->mode = MODE_CELT_ONLY; + + if (st->prev_mode > 0 && + ((st->mode != MODE_CELT_ONLY && st->prev_mode == MODE_CELT_ONLY) || + (st->mode == MODE_CELT_ONLY && st->prev_mode != MODE_CELT_ONLY))) + { + redundancy = 1; + celt_to_silk = (st->mode != MODE_CELT_ONLY); + if (!celt_to_silk) + { + /* Switch to SILK/hybrid if frame size is 10 ms or more*/ + if (frame_size >= st->Fs/100) + { + st->mode = st->prev_mode; + to_celt = 1; + } else { + redundancy=0; + } + } + } + + /* When encoding multiframes, we can ask for a switch to CELT only in the last frame. This switch + * is processed above as the requested mode shouldn't interrupt stereo->mono transition. */ + if (st->stream_channels == 1 && st->prev_channels ==2 && st->silk_mode.toMono==0 + && st->mode != MODE_CELT_ONLY && st->prev_mode != MODE_CELT_ONLY) + { + /* Delay stereo->mono transition by two frames so that SILK can do a smooth downmix */ + st->silk_mode.toMono = 1; + st->stream_channels = 2; + } else { + st->silk_mode.toMono = 0; + } + + /* Update equivalent rate with mode decision. */ + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->stream_channels, st->Fs/frame_size, + st->use_vbr, st->mode, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); + + if (st->mode != MODE_CELT_ONLY && st->prev_mode == MODE_CELT_ONLY) + { + silk_EncControlStruct dummy; + silk_InitEncoder( silk_enc, st->arch, &dummy); + prefill=1; + } + + /* Automatic (rate-dependent) bandwidth selection */ + if (st->mode == MODE_CELT_ONLY || st->first || st->silk_mode.allowBandwidthSwitch) + { + const opus_int32 *voice_bandwidth_thresholds, *music_bandwidth_thresholds; + opus_int32 bandwidth_thresholds[8]; + int bandwidth = OPUS_BANDWIDTH_FULLBAND; + + if (st->channels==2 && st->force_channels!=1) + { + voice_bandwidth_thresholds = stereo_voice_bandwidth_thresholds; + music_bandwidth_thresholds = stereo_music_bandwidth_thresholds; + } else { + voice_bandwidth_thresholds = mono_voice_bandwidth_thresholds; + music_bandwidth_thresholds = mono_music_bandwidth_thresholds; + } + /* Interpolate bandwidth thresholds depending on voice estimation */ + for (i=0;i<8;i++) + { + bandwidth_thresholds[i] = music_bandwidth_thresholds[i] + + ((voice_est*voice_est*(voice_bandwidth_thresholds[i]-music_bandwidth_thresholds[i]))>>14); + } + do { + int threshold, hysteresis; + threshold = bandwidth_thresholds[2*(bandwidth-OPUS_BANDWIDTH_MEDIUMBAND)]; + hysteresis = bandwidth_thresholds[2*(bandwidth-OPUS_BANDWIDTH_MEDIUMBAND)+1]; + if (!st->first) + { + if (st->auto_bandwidth >= bandwidth) + threshold -= hysteresis; + else + threshold += hysteresis; + } + if (equiv_rate >= threshold) + break; + } while (--bandwidth>OPUS_BANDWIDTH_NARROWBAND); + /* We don't use mediumband anymore, except when explicitly requested or during + mode transitions. */ + if (bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) + bandwidth = OPUS_BANDWIDTH_WIDEBAND; + st->bandwidth = st->auto_bandwidth = bandwidth; + /* Prevents any transition to SWB/FB until the SILK layer has fully + switched to WB mode and turned the variable LP filter off */ + if (!st->first && st->mode != MODE_CELT_ONLY && !st->silk_mode.inWBmodeWithoutVariableLP && st->bandwidth > OPUS_BANDWIDTH_WIDEBAND) + st->bandwidth = OPUS_BANDWIDTH_WIDEBAND; + } + + if (st->bandwidth>st->max_bandwidth) + st->bandwidth = st->max_bandwidth; + + if (st->user_bandwidth != OPUS_AUTO) + st->bandwidth = st->user_bandwidth; + + /* This prevents us from using hybrid at unsafe CBR/max rates */ + if (st->mode != MODE_CELT_ONLY && max_rate < 15000) + { + st->bandwidth = IMIN(st->bandwidth, OPUS_BANDWIDTH_WIDEBAND); + } + + /* Prevents Opus from wasting bits on frequencies that are above + the Nyquist rate of the input signal */ + if (st->Fs <= 24000 && st->bandwidth > OPUS_BANDWIDTH_SUPERWIDEBAND) + st->bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND; + if (st->Fs <= 16000 && st->bandwidth > OPUS_BANDWIDTH_WIDEBAND) + st->bandwidth = OPUS_BANDWIDTH_WIDEBAND; + if (st->Fs <= 12000 && st->bandwidth > OPUS_BANDWIDTH_MEDIUMBAND) + st->bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + if (st->Fs <= 8000 && st->bandwidth > OPUS_BANDWIDTH_NARROWBAND) + st->bandwidth = OPUS_BANDWIDTH_NARROWBAND; +#ifndef DISABLE_FLOAT_API + /* Use detected bandwidth to reduce the encoded bandwidth. */ + if (st->detected_bandwidth && st->user_bandwidth == OPUS_AUTO) + { + int min_detected_bandwidth; + /* Makes bandwidth detection more conservative just in case the detector + gets it wrong when we could have coded a high bandwidth transparently. + When operating in SILK/hybrid mode, we don't go below wideband to avoid + more complicated switches that require redundancy. */ + if (equiv_rate <= 18000*st->stream_channels && st->mode == MODE_CELT_ONLY) + min_detected_bandwidth = OPUS_BANDWIDTH_NARROWBAND; + else if (equiv_rate <= 24000*st->stream_channels && st->mode == MODE_CELT_ONLY) + min_detected_bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + else if (equiv_rate <= 30000*st->stream_channels) + min_detected_bandwidth = OPUS_BANDWIDTH_WIDEBAND; + else if (equiv_rate <= 44000*st->stream_channels) + min_detected_bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND; + else + min_detected_bandwidth = OPUS_BANDWIDTH_FULLBAND; + + st->detected_bandwidth = IMAX(st->detected_bandwidth, min_detected_bandwidth); + st->bandwidth = IMIN(st->bandwidth, st->detected_bandwidth); + } +#endif + st->silk_mode.LBRR_coded = decide_fec(st->silk_mode.useInBandFEC, st->silk_mode.packetLossPercentage, + st->silk_mode.LBRR_coded, st->mode, &st->bandwidth, equiv_rate); + celt_encoder_ctl(celt_enc, OPUS_SET_LSB_DEPTH(lsb_depth)); + + /* CELT mode doesn't support mediumband, use wideband instead */ + if (st->mode == MODE_CELT_ONLY && st->bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) + st->bandwidth = OPUS_BANDWIDTH_WIDEBAND; + if (st->lfe) + st->bandwidth = OPUS_BANDWIDTH_NARROWBAND; + + curr_bandwidth = st->bandwidth; + + /* Chooses the appropriate mode for speech + *NEVER* switch to/from CELT-only mode here as this will invalidate some assumptions */ + if (st->mode == MODE_SILK_ONLY && curr_bandwidth > OPUS_BANDWIDTH_WIDEBAND) + st->mode = MODE_HYBRID; + if (st->mode == MODE_HYBRID && curr_bandwidth <= OPUS_BANDWIDTH_WIDEBAND) + st->mode = MODE_SILK_ONLY; + + /* Can't support higher than >60 ms frames, and >20 ms when in Hybrid or CELT-only modes */ + if ((frame_size > st->Fs/50 && (st->mode != MODE_SILK_ONLY)) || frame_size > 3*st->Fs/50) + { + int enc_frame_size; + int nb_frames; + VARDECL(unsigned char, tmp_data); + VARDECL(OpusRepacketizer, rp); + int max_header_bytes; + opus_int32 repacketize_len; + opus_int32 max_len_sum; + opus_int32 tot_size=0; + unsigned char *curr_data; + int tmp_len; + int dtx_count = 0; + + if (st->mode == MODE_SILK_ONLY) + { + if (frame_size == 2*st->Fs/25) /* 80 ms -> 2x 40 ms */ + enc_frame_size = st->Fs/25; + else if (frame_size == 3*st->Fs/25) /* 120 ms -> 2x 60 ms */ + enc_frame_size = 3*st->Fs/50; + else /* 100 ms -> 5x 20 ms */ + enc_frame_size = st->Fs/50; + } + else + enc_frame_size = st->Fs/50; + + nb_frames = frame_size/enc_frame_size; + +#ifndef DISABLE_FLOAT_API + if (analysis_read_pos_bak!= -1) + { + /* Reset analysis position to the beginning of the first frame so we + can use it one frame at a time. */ + st->analysis.read_pos = analysis_read_pos_bak; + st->analysis.read_subframe = analysis_read_subframe_bak; + } +#endif + + /* Worst cases: + * 2 frames: Code 2 with different compressed sizes + * >2 frames: Code 3 VBR */ + max_header_bytes = nb_frames == 2 ? 3 : (2+(nb_frames-1)*2); + + if (st->use_vbr || st->user_bitrate_bps==OPUS_BITRATE_MAX) + repacketize_len = out_data_bytes; + else { + celt_assert(cbr_bytes>=0); + repacketize_len = IMIN(cbr_bytes, out_data_bytes); + } + max_len_sum = nb_frames + repacketize_len - max_header_bytes; + + ALLOC(tmp_data, max_len_sum, unsigned char); + curr_data = tmp_data; + ALLOC(rp, 1, OpusRepacketizer); + opus_repacketizer_init(rp); + + + int bak_to_mono = st->silk_mode.toMono; + if (bak_to_mono) + st->force_channels = 1; + else + st->prev_channels = st->stream_channels; + + for (i=0;isilk_mode.toMono = 0; + st->nonfinal_frame = i<(nb_frames-1); + + /* When switching from SILK/Hybrid to CELT, only ask for a switch at the last frame */ + frame_to_celt = to_celt && i==nb_frames-1; + frame_redundancy = redundancy && (frame_to_celt || (!to_celt && i==0)); + + curr_max = IMIN(3*st->bitrate_bps/(3*8*st->Fs/enc_frame_size), max_len_sum/nb_frames); +#ifdef ENABLE_DRED + curr_max = IMIN(curr_max, (max_len_sum-3*dred_bitrate_bps/(3*8*st->Fs/frame_size))/nb_frames); + if (first_frame) curr_max += 3*dred_bitrate_bps/(3*8*st->Fs/frame_size); +#endif + curr_max = IMIN(max_len_sum-tot_size, curr_max); +#ifndef DISABLE_FLOAT_API + if (analysis_read_pos_bak != -1) { + is_silence = is_digital_silence(pcm, frame_size, st->channels, lsb_depth); + /* Get analysis for current frame. */ + tonality_get_info(&st->analysis, &analysis_info, enc_frame_size); + } +#endif + + tmp_len = opus_encode_frame_native(st, pcm+i*(st->channels*enc_frame_size), enc_frame_size, curr_data, curr_max, float_api, first_frame, +#ifdef ENABLE_DRED + dred_bitrate_bps, +#endif +#ifndef DISABLE_FLOAT_API + &analysis_info, + is_silence, +#endif + frame_redundancy, celt_to_silk, prefill, + equiv_rate, frame_to_celt + ); + if (tmp_len<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } else if (tmp_len==1) { + dtx_count++; + } + ret = opus_repacketizer_cat(rp, curr_data, tmp_len); + + if (ret<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + tot_size += tmp_len; + curr_data += tmp_len; + } + ret = opus_repacketizer_out_range_impl(rp, 0, nb_frames, data, repacketize_len, 0, !st->use_vbr && (dtx_count != nb_frames), NULL, 0); + if (ret<0) + { + ret = OPUS_INTERNAL_ERROR; + } + st->silk_mode.toMono = bak_to_mono; + RESTORE_STACK; + return ret; + } else { + ret = opus_encode_frame_native(st, pcm, frame_size, data, max_data_bytes, float_api, 1, +#ifdef ENABLE_DRED + dred_bitrate_bps, +#endif +#ifndef DISABLE_FLOAT_API + &analysis_info, + is_silence, +#endif + redundancy, celt_to_silk, prefill, + equiv_rate, to_celt + ); + RESTORE_STACK; + return ret; + } +} + +static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, + unsigned char *data, opus_int32 max_data_bytes, + int float_api, int first_frame, +#ifdef ENABLE_DRED + opus_int32 dred_bitrate_bps, +#endif +#ifndef DISABLE_FLOAT_API + AnalysisInfo *analysis_info, int is_silence, +#endif + int redundancy, int celt_to_silk, int prefill, + opus_int32 equiv_rate, int to_celt) +{ + void *silk_enc; + CELTEncoder *celt_enc; + const CELTMode *celt_mode; + int i; + int ret=0; + opus_int32 nBytes; + ec_enc enc; + int bytes_target; + int start_band = 0; + int redundancy_bytes = 0; /* Number of bytes to use for redundancy frame */ + int nb_compr_bytes; + opus_uint32 redundant_rng = 0; + int cutoff_Hz; + int hp_freq_smth1; + opus_val16 HB_gain; + int apply_padding; + int frame_rate; + int curr_bandwidth; + int delay_compensation; + int total_buffer; + opus_int activity = VAD_NO_DECISION; + VARDECL(opus_val16, pcm_buf); + VARDECL(opus_val16, tmp_prefill); + SAVE_STACK; + + st->rangeFinal = 0; + silk_enc = (char*)st+st->silk_enc_offset; + celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); + celt_encoder_ctl(celt_enc, CELT_GET_MODE(&celt_mode)); + curr_bandwidth = st->bandwidth; + if (st->application == OPUS_APPLICATION_RESTRICTED_LOWDELAY) + delay_compensation = 0; + else + delay_compensation = st->delay_compensation; + total_buffer = delay_compensation; + + frame_rate = st->Fs/frame_size; + +#ifndef DISABLE_FLOAT_API + if (is_silence) + { + activity = !is_silence; + } else if (analysis_info->valid) + { + activity = analysis_info->activity_probability >= DTX_ACTIVITY_THRESHOLD; + if (!activity) + { + /* Mark as active if this noise frame is sufficiently loud */ + opus_val32 noise_energy = compute_frame_energy(pcm, frame_size, st->channels, st->arch); + activity = st->peak_signal_energy < (PSEUDO_SNR_THRESHOLD * noise_energy); + } + } +#endif + + /* For the first frame at a new SILK bandwidth */ + if (st->silk_bw_switch) + { + redundancy = 1; + celt_to_silk = 1; + st->silk_bw_switch = 0; + /* Do a prefill without resetting the sampling rate control. */ + prefill=2; + } + + /* If we decided to go with CELT, make sure redundancy is off, no matter what + we decided earlier. */ + if (st->mode == MODE_CELT_ONLY) + redundancy = 0; + + if (redundancy) + { + redundancy_bytes = compute_redundancy_bytes(max_data_bytes, st->bitrate_bps, frame_rate, st->stream_channels); + if (redundancy_bytes == 0) + redundancy = 0; + } + + /* printf("%d %d %d %d\n", st->bitrate_bps, st->stream_channels, st->mode, curr_bandwidth); */ + bytes_target = IMIN(max_data_bytes-redundancy_bytes, st->bitrate_bps * frame_size / (st->Fs * 8)) - 1; + + data += 1; + + ec_enc_init(&enc, data, max_data_bytes-1); + + ALLOC(pcm_buf, (total_buffer+frame_size)*st->channels, opus_val16); + OPUS_COPY(pcm_buf, &st->delay_buffer[(st->encoder_buffer-total_buffer)*st->channels], total_buffer*st->channels); + + if (st->mode == MODE_CELT_ONLY) + hp_freq_smth1 = silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ); + else + hp_freq_smth1 = ((silk_encoder*)silk_enc)->state_Fxx[0].sCmn.variable_HP_smth1_Q15; + + st->variable_HP_smth2_Q15 = silk_SMLAWB( st->variable_HP_smth2_Q15, + hp_freq_smth1 - st->variable_HP_smth2_Q15, SILK_FIX_CONST( VARIABLE_HP_SMTH_COEF2, 16 ) ); + + /* convert from log scale to Hertz */ + cutoff_Hz = silk_log2lin( silk_RSHIFT( st->variable_HP_smth2_Q15, 8 ) ); + + if (st->application == OPUS_APPLICATION_VOIP) + { + hp_cutoff(pcm, cutoff_Hz, &pcm_buf[total_buffer*st->channels], st->hp_mem, frame_size, st->channels, st->Fs, st->arch); + +#ifdef ENABLE_OSCE_TRAINING_DATA + /* write out high pass filtered clean signal*/ + static FILE *fout =NULL; + if (fout == NULL) + { + fout = fopen("clean_hp.s16", "wb"); + } + + { + int idx; + opus_int16 tmp; + for (idx = 0; idx < frame_size; idx++) + { + tmp = (opus_int16) (32768 * pcm_buf[total_buffer + idx] + 0.5f); + fwrite(&tmp, sizeof(tmp), 1, fout); + } + } +#endif + } else { + dc_reject(pcm, 3, &pcm_buf[total_buffer*st->channels], st->hp_mem, frame_size, st->channels, st->Fs); + } +#ifndef FIXED_POINT + if (float_api) + { + opus_val32 sum; + sum = celt_inner_prod(&pcm_buf[total_buffer*st->channels], &pcm_buf[total_buffer*st->channels], frame_size*st->channels, st->arch); + /* This should filter out both NaNs and ridiculous signals that could + cause NaNs further down. */ + if (!(sum < 1e9f) || celt_isnan(sum)) + { + OPUS_CLEAR(&pcm_buf[total_buffer*st->channels], frame_size*st->channels); + st->hp_mem[0] = st->hp_mem[1] = st->hp_mem[2] = st->hp_mem[3] = 0; + } + } +#else + (void)float_api; +#endif + +#ifdef ENABLE_DRED + if ( st->dred_duration > 0 && st->dred_encoder.loaded ) { + int frame_size_400Hz; + /* DRED Encoder */ + dred_compute_latents( &st->dred_encoder, &pcm_buf[total_buffer*st->channels], frame_size, total_buffer, st->arch ); + frame_size_400Hz = frame_size*400/st->Fs; + OPUS_MOVE(&st->activity_mem[frame_size_400Hz], st->activity_mem, 4*DRED_MAX_FRAMES-frame_size_400Hz); + for (i=0;iactivity_mem[i] = activity; + } else { + st->dred_encoder.latents_buffer_fill = 0; + OPUS_CLEAR(st->activity_mem, DRED_MAX_FRAMES); + } +#endif + + /* SILK processing */ + HB_gain = Q15ONE; + if (st->mode != MODE_CELT_ONLY) + { + opus_int32 total_bitRate, celt_rate; +#ifdef FIXED_POINT + const opus_int16 *pcm_silk; +#else + VARDECL(opus_int16, pcm_silk); + ALLOC(pcm_silk, st->channels*frame_size, opus_int16); +#endif + + /* Distribute bits between SILK and CELT */ + total_bitRate = 8 * bytes_target * frame_rate; + if( st->mode == MODE_HYBRID ) { + /* Base rate for SILK */ + st->silk_mode.bitRate = compute_silk_rate_for_hybrid(total_bitRate, + curr_bandwidth, st->Fs == 50 * frame_size, st->use_vbr, st->silk_mode.LBRR_coded, + st->stream_channels); + if (!st->energy_masking) + { + /* Increasingly attenuate high band when it gets allocated fewer bits */ + celt_rate = total_bitRate - st->silk_mode.bitRate; + HB_gain = Q15ONE - SHR32(celt_exp2(-celt_rate * QCONST16(1.f/1024, 10)), 1); + } + } else { + /* SILK gets all bits */ + st->silk_mode.bitRate = total_bitRate; + } + + /* Surround masking for SILK */ + if (st->energy_masking && st->use_vbr && !st->lfe) + { + opus_val32 mask_sum=0; + opus_val16 masking_depth; + opus_int32 rate_offset; + int c; + int end = 17; + opus_int16 srate = 16000; + if (st->bandwidth == OPUS_BANDWIDTH_NARROWBAND) + { + end = 13; + srate = 8000; + } else if (st->bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) + { + end = 15; + srate = 12000; + } + for (c=0;cchannels;c++) + { + for(i=0;ienergy_masking[21*c+i], + QCONST16(.5f, DB_SHIFT)), -QCONST16(2.0f, DB_SHIFT)); + if (mask > 0) + mask = HALF16(mask); + mask_sum += mask; + } + } + /* Conservative rate reduction, we cut the masking in half */ + masking_depth = mask_sum / end*st->channels; + masking_depth += QCONST16(.2f, DB_SHIFT); + rate_offset = (opus_int32)PSHR32(MULT16_16(srate, masking_depth), DB_SHIFT); + rate_offset = MAX32(rate_offset, -2*st->silk_mode.bitRate/3); + /* Split the rate change between the SILK and CELT part for hybrid. */ + if (st->bandwidth==OPUS_BANDWIDTH_SUPERWIDEBAND || st->bandwidth==OPUS_BANDWIDTH_FULLBAND) + st->silk_mode.bitRate += 3*rate_offset/5; + else + st->silk_mode.bitRate += rate_offset; + } + + st->silk_mode.payloadSize_ms = 1000 * frame_size / st->Fs; + st->silk_mode.nChannelsAPI = st->channels; + st->silk_mode.nChannelsInternal = st->stream_channels; + if (curr_bandwidth == OPUS_BANDWIDTH_NARROWBAND) { + st->silk_mode.desiredInternalSampleRate = 8000; + } else if (curr_bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) { + st->silk_mode.desiredInternalSampleRate = 12000; + } else { + celt_assert( st->mode == MODE_HYBRID || curr_bandwidth == OPUS_BANDWIDTH_WIDEBAND ); + st->silk_mode.desiredInternalSampleRate = 16000; + } + if( st->mode == MODE_HYBRID ) { + /* Don't allow bandwidth reduction at lowest bitrates in hybrid mode */ + st->silk_mode.minInternalSampleRate = 16000; + } else { + st->silk_mode.minInternalSampleRate = 8000; + } + + st->silk_mode.maxInternalSampleRate = 16000; + if (st->mode == MODE_SILK_ONLY) + { + opus_int32 effective_max_rate = frame_rate*max_data_bytes*8; + if (frame_rate > 50) + effective_max_rate = effective_max_rate*2/3; + if (effective_max_rate < 8000) + { + st->silk_mode.maxInternalSampleRate = 12000; + st->silk_mode.desiredInternalSampleRate = IMIN(12000, st->silk_mode.desiredInternalSampleRate); + } + if (effective_max_rate < 7000) + { + st->silk_mode.maxInternalSampleRate = 8000; + st->silk_mode.desiredInternalSampleRate = IMIN(8000, st->silk_mode.desiredInternalSampleRate); + } + } + + st->silk_mode.useCBR = !st->use_vbr; + + /* Call SILK encoder for the low band */ + + /* Max bits for SILK, counting ToC, redundancy bytes, and optionally redundancy. */ + st->silk_mode.maxBits = (max_data_bytes-1)*8; + if (redundancy && redundancy_bytes >= 2) + { + /* Counting 1 bit for redundancy position and 20 bits for flag+size (only for hybrid). */ + st->silk_mode.maxBits -= redundancy_bytes*8 + 1; + if (st->mode == MODE_HYBRID) + st->silk_mode.maxBits -= 20; + } + if (st->silk_mode.useCBR) + { + /* When we're in CBR mode, but we have non-SILK data to encode, switch SILK to VBR with cap to + save on complexity. Any variations will be absorbed by CELT and/or DRED and we can still + produce a constant bitrate without wasting bits. */ +#ifdef ENABLE_DRED + if (st->mode == MODE_HYBRID || dred_bitrate_bps > 0) +#else + if (st->mode == MODE_HYBRID) +#endif + { + /* Allow SILK to steal up to 25% of the remaining bits */ + opus_int16 other_bits = IMAX(0, st->silk_mode.maxBits - st->silk_mode.bitRate * frame_size / st->Fs); + st->silk_mode.maxBits = IMAX(0, st->silk_mode.maxBits - other_bits*3/4); + st->silk_mode.useCBR = 0; + } + } else { + /* Constrained VBR. */ + if (st->mode == MODE_HYBRID) + { + /* Compute SILK bitrate corresponding to the max total bits available */ + opus_int32 maxBitRate = compute_silk_rate_for_hybrid(st->silk_mode.maxBits*st->Fs / frame_size, + curr_bandwidth, st->Fs == 50 * frame_size, st->use_vbr, st->silk_mode.LBRR_coded, + st->stream_channels); + st->silk_mode.maxBits = maxBitRate * frame_size / st->Fs; + } + } + + if (prefill) + { + opus_int32 zero=0; + int prefill_offset; + /* Use a smooth onset for the SILK prefill to avoid the encoder trying to encode + a discontinuity. The exact location is what we need to avoid leaving any "gap" + in the audio when mixing with the redundant CELT frame. Here we can afford to + overwrite st->delay_buffer because the only thing that uses it before it gets + rewritten is tmp_prefill[] and even then only the part after the ramp really + gets used (rather than sent to the encoder and discarded) */ + prefill_offset = st->channels*(st->encoder_buffer-st->delay_compensation-st->Fs/400); + gain_fade(st->delay_buffer+prefill_offset, st->delay_buffer+prefill_offset, + 0, Q15ONE, celt_mode->overlap, st->Fs/400, st->channels, celt_mode->window, st->Fs); + OPUS_CLEAR(st->delay_buffer, prefill_offset); +#ifdef FIXED_POINT + pcm_silk = st->delay_buffer; +#else + for (i=0;iencoder_buffer*st->channels;i++) + pcm_silk[i] = FLOAT2INT16(st->delay_buffer[i]); +#endif + silk_Encode( silk_enc, &st->silk_mode, pcm_silk, st->encoder_buffer, NULL, &zero, prefill, activity ); + /* Prevent a second switch in the real encode call. */ + st->silk_mode.opusCanSwitch = 0; + } + +#ifdef FIXED_POINT + pcm_silk = pcm_buf+total_buffer*st->channels; +#else + for (i=0;ichannels;i++) + pcm_silk[i] = FLOAT2INT16(pcm_buf[total_buffer*st->channels + i]); +#endif + ret = silk_Encode( silk_enc, &st->silk_mode, pcm_silk, frame_size, &enc, &nBytes, 0, activity ); + if( ret ) { + /*fprintf (stderr, "SILK encode error: %d\n", ret);*/ + /* Handle error */ + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + + /* Extract SILK internal bandwidth for signaling in first byte */ + if( st->mode == MODE_SILK_ONLY ) { + if( st->silk_mode.internalSampleRate == 8000 ) { + curr_bandwidth = OPUS_BANDWIDTH_NARROWBAND; + } else if( st->silk_mode.internalSampleRate == 12000 ) { + curr_bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + } else if( st->silk_mode.internalSampleRate == 16000 ) { + curr_bandwidth = OPUS_BANDWIDTH_WIDEBAND; + } + } else { + celt_assert( st->silk_mode.internalSampleRate == 16000 ); + } + + st->silk_mode.opusCanSwitch = st->silk_mode.switchReady && !st->nonfinal_frame; + + if (nBytes==0) + { + st->rangeFinal = 0; + data[-1] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); + RESTORE_STACK; + return 1; + } + + /* FIXME: How do we allocate the redundancy for CBR? */ + if (st->silk_mode.opusCanSwitch) + { + redundancy_bytes = compute_redundancy_bytes(max_data_bytes, st->bitrate_bps, frame_rate, st->stream_channels); + redundancy = (redundancy_bytes != 0); + celt_to_silk = 0; + st->silk_bw_switch = 1; + } + } + + /* CELT processing */ + { + int endband=21; + + switch(curr_bandwidth) + { + case OPUS_BANDWIDTH_NARROWBAND: + endband = 13; + break; + case OPUS_BANDWIDTH_MEDIUMBAND: + case OPUS_BANDWIDTH_WIDEBAND: + endband = 17; + break; + case OPUS_BANDWIDTH_SUPERWIDEBAND: + endband = 19; + break; + case OPUS_BANDWIDTH_FULLBAND: + endband = 21; + break; + } + celt_encoder_ctl(celt_enc, CELT_SET_END_BAND(endband)); + celt_encoder_ctl(celt_enc, CELT_SET_CHANNELS(st->stream_channels)); + } + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(OPUS_BITRATE_MAX)); + if (st->mode != MODE_SILK_ONLY) + { + opus_val32 celt_pred=2; + /* We may still decide to disable prediction later */ + if (st->silk_mode.reducedDependency) + celt_pred = 0; + celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(celt_pred)); + } + + ALLOC(tmp_prefill, st->channels*st->Fs/400, opus_val16); + if (st->mode != MODE_SILK_ONLY && st->mode != st->prev_mode && st->prev_mode > 0) + { + OPUS_COPY(tmp_prefill, &st->delay_buffer[(st->encoder_buffer-total_buffer-st->Fs/400)*st->channels], st->channels*st->Fs/400); + } + + if (st->channels*(st->encoder_buffer-(frame_size+total_buffer)) > 0) + { + OPUS_MOVE(st->delay_buffer, &st->delay_buffer[st->channels*frame_size], st->channels*(st->encoder_buffer-frame_size-total_buffer)); + OPUS_COPY(&st->delay_buffer[st->channels*(st->encoder_buffer-frame_size-total_buffer)], + &pcm_buf[0], + (frame_size+total_buffer)*st->channels); + } else { + OPUS_COPY(st->delay_buffer, &pcm_buf[(frame_size+total_buffer-st->encoder_buffer)*st->channels], st->encoder_buffer*st->channels); + } + /* gain_fade() and stereo_fade() need to be after the buffer copying + because we don't want any of this to affect the SILK part */ + if( st->prev_HB_gain < Q15ONE || HB_gain < Q15ONE ) { + gain_fade(pcm_buf, pcm_buf, + st->prev_HB_gain, HB_gain, celt_mode->overlap, frame_size, st->channels, celt_mode->window, st->Fs); + } + st->prev_HB_gain = HB_gain; + if (st->mode != MODE_HYBRID || st->stream_channels==1) + { + if (equiv_rate > 32000) + st->silk_mode.stereoWidth_Q14 = 16384; + else if (equiv_rate < 16000) + st->silk_mode.stereoWidth_Q14 = 0; + else + st->silk_mode.stereoWidth_Q14 = 16384 - 2048*(opus_int32)(32000-equiv_rate)/(equiv_rate-14000); + } + if( !st->energy_masking && st->channels == 2 ) { + /* Apply stereo width reduction (at low bitrates) */ + if( st->hybrid_stereo_width_Q14 < (1 << 14) || st->silk_mode.stereoWidth_Q14 < (1 << 14) ) { + opus_val16 g1, g2; + g1 = st->hybrid_stereo_width_Q14; + g2 = (opus_val16)(st->silk_mode.stereoWidth_Q14); +#ifdef FIXED_POINT + g1 = g1==16384 ? Q15ONE : SHL16(g1,1); + g2 = g2==16384 ? Q15ONE : SHL16(g2,1); +#else + g1 *= (1.f/16384); + g2 *= (1.f/16384); +#endif + stereo_fade(pcm_buf, pcm_buf, g1, g2, celt_mode->overlap, + frame_size, st->channels, celt_mode->window, st->Fs); + st->hybrid_stereo_width_Q14 = st->silk_mode.stereoWidth_Q14; + } + } + + if ( st->mode != MODE_CELT_ONLY && ec_tell(&enc)+17+20*(st->mode == MODE_HYBRID) <= 8*(max_data_bytes-1)) + { + /* For SILK mode, the redundancy is inferred from the length */ + if (st->mode == MODE_HYBRID) + ec_enc_bit_logp(&enc, redundancy, 12); + if (redundancy) + { + int max_redundancy; + ec_enc_bit_logp(&enc, celt_to_silk, 1); + if (st->mode == MODE_HYBRID) + { + /* Reserve the 8 bits needed for the redundancy length, + and at least a few bits for CELT if possible */ + max_redundancy = (max_data_bytes-1)-((ec_tell(&enc)+8+3+7)>>3); + } + else + max_redundancy = (max_data_bytes-1)-((ec_tell(&enc)+7)>>3); + /* Target the same bit-rate for redundancy as for the rest, + up to a max of 257 bytes */ + redundancy_bytes = IMIN(max_redundancy, redundancy_bytes); + redundancy_bytes = IMIN(257, IMAX(2, redundancy_bytes)); + if (st->mode == MODE_HYBRID) + ec_enc_uint(&enc, redundancy_bytes-2, 256); + } + } else { + redundancy = 0; + } + + if (!redundancy) + { + st->silk_bw_switch = 0; + redundancy_bytes = 0; + } + if (st->mode != MODE_CELT_ONLY)start_band=17; + + if (st->mode == MODE_SILK_ONLY) + { + ret = (ec_tell(&enc)+7)>>3; + ec_enc_done(&enc); + nb_compr_bytes = ret; + } else { + nb_compr_bytes = (max_data_bytes-1)-redundancy_bytes; +#ifdef ENABLE_DRED + if (st->dred_duration > 0) + { + int max_celt_bytes; + opus_int32 dred_bytes = dred_bitrate_bps/(frame_rate*8); + /* Allow CELT to steal up to 25% of the remaining bits. */ + max_celt_bytes = nb_compr_bytes - dred_bytes*3/4; + /* But try to give CELT at least 5 bytes to prevent a mismatch with + the redundancy signaling. */ + max_celt_bytes = IMAX((ec_tell(&enc)+7)/8 + 5, max_celt_bytes); + /* Subject to the original max. */ + nb_compr_bytes = IMIN(nb_compr_bytes, max_celt_bytes); + } +#endif + ec_enc_shrink(&enc, nb_compr_bytes); + } + +#ifndef DISABLE_FLOAT_API + if (redundancy || st->mode != MODE_SILK_ONLY) + celt_encoder_ctl(celt_enc, CELT_SET_ANALYSIS(analysis_info)); +#endif + if (st->mode == MODE_HYBRID) { + SILKInfo info; + info.signalType = st->silk_mode.signalType; + info.offset = st->silk_mode.offset; + celt_encoder_ctl(celt_enc, CELT_SET_SILK_INFO(&info)); + } + + /* 5 ms redundant frame for CELT->SILK */ + if (redundancy && celt_to_silk) + { + int err; + celt_encoder_ctl(celt_enc, CELT_SET_START_BAND(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(OPUS_BITRATE_MAX)); + err = celt_encode_with_ec(celt_enc, pcm_buf, st->Fs/200, data+nb_compr_bytes, redundancy_bytes, NULL); + if (err < 0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + celt_encoder_ctl(celt_enc, OPUS_GET_FINAL_RANGE(&redundant_rng)); + celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); + } + + celt_encoder_ctl(celt_enc, CELT_SET_START_BAND(start_band)); + + if (st->mode != MODE_SILK_ONLY) + { + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(st->use_vbr)); + if (st->mode == MODE_HYBRID) + { + if( st->use_vbr ) { + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps-st->silk_mode.bitRate)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(0)); + } + } else { + if (st->use_vbr) + { + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(1)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(st->vbr_constraint)); + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps)); + } + } +#ifdef ENABLE_DRED + /* When Using DRED CBR, we can actually make the CELT part VBR and have DRED pick up the slack. */ + if (!st->use_vbr && st->dred_duration > 0) + { + opus_int32 celt_bitrate = st->bitrate_bps; + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(1)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(0)); + if (st->mode == MODE_HYBRID) { + celt_bitrate -= st->silk_mode.bitRate; + } + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(celt_bitrate)); + } +#endif + if (st->mode != st->prev_mode && st->prev_mode > 0) + { + unsigned char dummy[2]; + celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); + + /* Prefilling */ + celt_encode_with_ec(celt_enc, tmp_prefill, st->Fs/400, dummy, 2, NULL); + celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(0)); + } + /* If false, we already busted the budget and we'll end up with a "PLC frame" */ + if (ec_tell(&enc) <= 8*nb_compr_bytes) + { + ret = celt_encode_with_ec(celt_enc, pcm_buf, frame_size, NULL, nb_compr_bytes, &enc); + if (ret < 0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + /* Put CELT->SILK redundancy data in the right place. */ + if (redundancy && celt_to_silk && st->mode==MODE_HYBRID && nb_compr_bytes != ret) + { + OPUS_MOVE(data+ret, data+nb_compr_bytes, redundancy_bytes); + nb_compr_bytes = ret+redundancy_bytes; + } + } + } + + /* 5 ms redundant frame for SILK->CELT */ + if (redundancy && !celt_to_silk) + { + int err; + unsigned char dummy[2]; + int N2, N4; + N2 = st->Fs/200; + N4 = st->Fs/400; + + celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); + celt_encoder_ctl(celt_enc, CELT_SET_START_BAND(0)); + celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(OPUS_BITRATE_MAX)); + + if (st->mode == MODE_HYBRID) + { + /* Shrink packet to what the encoder actually used. */ + nb_compr_bytes = ret; + ec_enc_shrink(&enc, nb_compr_bytes); + } + /* NOTE: We could speed this up slightly (at the expense of code size) by just adding a function that prefills the buffer */ + celt_encode_with_ec(celt_enc, pcm_buf+st->channels*(frame_size-N2-N4), N4, dummy, 2, NULL); + + err = celt_encode_with_ec(celt_enc, pcm_buf+st->channels*(frame_size-N2), N2, data+nb_compr_bytes, redundancy_bytes, NULL); + if (err < 0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + celt_encoder_ctl(celt_enc, OPUS_GET_FINAL_RANGE(&redundant_rng)); + } + + + + /* Signalling the mode in the first byte */ + data--; + data[0] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); + + st->rangeFinal = enc.rng ^ redundant_rng; + + if (to_celt) + st->prev_mode = MODE_CELT_ONLY; + else + st->prev_mode = st->mode; + st->prev_channels = st->stream_channels; + st->prev_framesize = frame_size; + + st->first = 0; + + /* DTX decision */ +#ifndef DISABLE_FLOAT_API + if (st->use_dtx && (analysis_info->valid || is_silence)) + { + if (decide_dtx_mode(activity, &st->nb_no_activity_ms_Q1, 2*1000*frame_size/st->Fs)) + { + st->rangeFinal = 0; + data[0] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); + RESTORE_STACK; + return 1; + } + } else { + st->nb_no_activity_ms_Q1 = 0; + } +#endif + + /* In the unlikely case that the SILK encoder busted its target, tell + the decoder to call the PLC */ + if (ec_tell(&enc) > (max_data_bytes-1)*8) + { + if (max_data_bytes < 2) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + data[1] = 0; + ret = 1; + st->rangeFinal = 0; + } else if (st->mode==MODE_SILK_ONLY&&!redundancy) + { + /*When in LPC only mode it's perfectly + reasonable to strip off trailing zero bytes as + the required range decoder behavior is to + fill these in. This can't be done when the MDCT + modes are used because the decoder needs to know + the actual length for allocation purposes.*/ + while(ret>2&&data[ret]==0)ret--; + } + /* Count ToC and redundancy */ + ret += 1+redundancy_bytes; + apply_padding = !st->use_vbr; +#ifdef ENABLE_DRED + if (st->dred_duration > 0 && st->dred_encoder.loaded && first_frame) { + opus_extension_data extension; + unsigned char buf[DRED_MAX_DATA_SIZE]; + int dred_chunks; + int dred_bytes_left; + dred_chunks = IMIN((st->dred_duration+5)/4, DRED_NUM_REDUNDANCY_FRAMES/2); + if (st->use_vbr) dred_chunks = IMIN(dred_chunks, st->dred_target_chunks); + /* Remaining space for DRED, accounting for cost the 3 extra bytes for code 3, padding length, and extension number. */ + dred_bytes_left = IMIN(DRED_MAX_DATA_SIZE, max_data_bytes-ret-3); + /* Account for the extra bytes required to signal large padding length. */ + dred_bytes_left -= (dred_bytes_left+1+DRED_EXPERIMENTAL_BYTES)/255; + /* Check whether we actually have something to encode. */ + if (dred_chunks >= 1 && dred_bytes_left >= DRED_MIN_BYTES+DRED_EXPERIMENTAL_BYTES) { + int dred_bytes; +#ifdef DRED_EXPERIMENTAL_VERSION + /* Add temporary extension type and version. + These bytes will be removed once extension is finalized. */ + buf[0] = 'D'; + buf[1] = DRED_EXPERIMENTAL_VERSION; +#endif + dred_bytes = dred_encode_silk_frame(&st->dred_encoder, buf+DRED_EXPERIMENTAL_BYTES, dred_chunks, dred_bytes_left-DRED_EXPERIMENTAL_BYTES, + st->dred_q0, st->dred_dQ, st->dred_qmax, st->activity_mem, st->arch); + if (dred_bytes > 0) { + dred_bytes += DRED_EXPERIMENTAL_BYTES; + celt_assert(dred_bytes <= dred_bytes_left); + extension.id = DRED_EXTENSION_ID; + extension.frame = 0; + extension.data = buf; + extension.len = dred_bytes; + ret = opus_packet_pad_impl(data, ret, max_data_bytes, !st->use_vbr, &extension, 1); + if (ret < 0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + apply_padding = 0; + } + } + } +#else + (void)first_frame; /* Avoids a warning about first_frame being unused. */ +#endif + if (apply_padding) + { + if (opus_packet_pad(data, ret, max_data_bytes) != OPUS_OK) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + ret = max_data_bytes; + } + RESTORE_STACK; + return ret; +} + +#ifdef FIXED_POINT + +#ifndef DISABLE_FLOAT_API +opus_int32 opus_encode_float(OpusEncoder *st, const float *pcm, int analysis_frame_size, + unsigned char *data, opus_int32 max_data_bytes) +{ + int i, ret; + int frame_size; + VARDECL(opus_int16, in); + ALLOC_STACK; + + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, st->Fs); + if (frame_size <= 0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + ALLOC(in, frame_size*st->channels, opus_int16); + + for (i=0;ichannels;i++) + in[i] = FLOAT2INT16(pcm[i]); + ret = opus_encode_native(st, in, frame_size, data, max_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_float, 1); + RESTORE_STACK; + return ret; +} +#endif + +opus_int32 opus_encode(OpusEncoder *st, const opus_int16 *pcm, int analysis_frame_size, + unsigned char *data, opus_int32 out_data_bytes) +{ + int frame_size; + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, st->Fs); + return opus_encode_native(st, pcm, frame_size, data, out_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_int, 0); +} + +#else +opus_int32 opus_encode(OpusEncoder *st, const opus_int16 *pcm, int analysis_frame_size, + unsigned char *data, opus_int32 max_data_bytes) +{ + int i, ret; + int frame_size; + VARDECL(float, in); + ALLOC_STACK; + + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, st->Fs); + if (frame_size <= 0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + ALLOC(in, frame_size*st->channels, float); + + for (i=0;ichannels;i++) + in[i] = (1.0f/32768)*pcm[i]; + ret = opus_encode_native(st, in, frame_size, data, max_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_int, 0); + RESTORE_STACK; + return ret; +} +opus_int32 opus_encode_float(OpusEncoder *st, const float *pcm, int analysis_frame_size, + unsigned char *data, opus_int32 out_data_bytes) +{ + int frame_size; + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, st->Fs); + return opus_encode_native(st, pcm, frame_size, data, out_data_bytes, 24, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_float, 1); +} +#endif + + +int opus_encoder_ctl(OpusEncoder *st, int request, ...) +{ + int ret; + CELTEncoder *celt_enc; + va_list ap; + + ret = OPUS_OK; + va_start(ap, request); + + celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); + + switch (request) + { + case OPUS_SET_APPLICATION_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if ( (value != OPUS_APPLICATION_VOIP && value != OPUS_APPLICATION_AUDIO + && value != OPUS_APPLICATION_RESTRICTED_LOWDELAY) + || (!st->first && st->application != value)) + { + ret = OPUS_BAD_ARG; + break; + } + st->application = value; +#ifndef DISABLE_FLOAT_API + st->analysis.application = value; +#endif + } + break; + case OPUS_GET_APPLICATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->application; + } + break; + case OPUS_SET_BITRATE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value != OPUS_AUTO && value != OPUS_BITRATE_MAX) + { + if (value <= 0) + goto bad_arg; + else if (value <= 500) + value = 500; + else if (value > (opus_int32)300000*st->channels) + value = (opus_int32)300000*st->channels; + } + st->user_bitrate_bps = value; + } + break; + case OPUS_GET_BITRATE_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = user_bitrate_to_bitrate(st, st->prev_framesize, 1276); + } + break; + case OPUS_SET_FORCE_CHANNELS_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if((value<1 || value>st->channels) && value != OPUS_AUTO) + { + goto bad_arg; + } + st->force_channels = value; + } + break; + case OPUS_GET_FORCE_CHANNELS_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->force_channels; + } + break; + case OPUS_SET_MAX_BANDWIDTH_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value < OPUS_BANDWIDTH_NARROWBAND || value > OPUS_BANDWIDTH_FULLBAND) + { + goto bad_arg; + } + st->max_bandwidth = value; + if (st->max_bandwidth == OPUS_BANDWIDTH_NARROWBAND) { + st->silk_mode.maxInternalSampleRate = 8000; + } else if (st->max_bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) { + st->silk_mode.maxInternalSampleRate = 12000; + } else { + st->silk_mode.maxInternalSampleRate = 16000; + } + } + break; + case OPUS_GET_MAX_BANDWIDTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->max_bandwidth; + } + break; + case OPUS_SET_BANDWIDTH_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if ((value < OPUS_BANDWIDTH_NARROWBAND || value > OPUS_BANDWIDTH_FULLBAND) && value != OPUS_AUTO) + { + goto bad_arg; + } + st->user_bandwidth = value; + if (st->user_bandwidth == OPUS_BANDWIDTH_NARROWBAND) { + st->silk_mode.maxInternalSampleRate = 8000; + } else if (st->user_bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) { + st->silk_mode.maxInternalSampleRate = 12000; + } else { + st->silk_mode.maxInternalSampleRate = 16000; + } + } + break; + case OPUS_GET_BANDWIDTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->bandwidth; + } + break; + case OPUS_SET_DTX_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->use_dtx = value; + } + break; + case OPUS_GET_DTX_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->use_dtx; + } + break; + case OPUS_SET_COMPLEXITY_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>10) + { + goto bad_arg; + } + st->silk_mode.complexity = value; + celt_encoder_ctl(celt_enc, OPUS_SET_COMPLEXITY(value)); + } + break; + case OPUS_GET_COMPLEXITY_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->silk_mode.complexity; + } + break; + case OPUS_SET_INBAND_FEC_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>2) + { + goto bad_arg; + } + st->fec_config = value; + st->silk_mode.useInBandFEC = (value != 0); + } + break; + case OPUS_GET_INBAND_FEC_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->fec_config; + } + break; + case OPUS_SET_PACKET_LOSS_PERC_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value < 0 || value > 100) + { + goto bad_arg; + } + st->silk_mode.packetLossPercentage = value; + celt_encoder_ctl(celt_enc, OPUS_SET_PACKET_LOSS_PERC(value)); + } + break; + case OPUS_GET_PACKET_LOSS_PERC_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->silk_mode.packetLossPercentage; + } + break; + case OPUS_SET_VBR_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->use_vbr = value; + st->silk_mode.useCBR = 1-value; + } + break; + case OPUS_GET_VBR_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->use_vbr; + } + break; + case OPUS_SET_VOICE_RATIO_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<-1 || value>100) + { + goto bad_arg; + } + st->voice_ratio = value; + } + break; + case OPUS_GET_VOICE_RATIO_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->voice_ratio; + } + break; + case OPUS_SET_VBR_CONSTRAINT_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->vbr_constraint = value; + } + break; + case OPUS_GET_VBR_CONSTRAINT_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->vbr_constraint; + } + break; + case OPUS_SET_SIGNAL_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value!=OPUS_AUTO && value!=OPUS_SIGNAL_VOICE && value!=OPUS_SIGNAL_MUSIC) + { + goto bad_arg; + } + st->signal_type = value; + } + break; + case OPUS_GET_SIGNAL_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->signal_type; + } + break; + case OPUS_GET_LOOKAHEAD_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->Fs/400; + if (st->application != OPUS_APPLICATION_RESTRICTED_LOWDELAY) + *value += st->delay_compensation; + } + break; + case OPUS_GET_SAMPLE_RATE_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->Fs; + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + opus_uint32 *value = va_arg(ap, opus_uint32*); + if (!value) + { + goto bad_arg; + } + *value = st->rangeFinal; + } + break; + case OPUS_SET_LSB_DEPTH_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<8 || value>24) + { + goto bad_arg; + } + st->lsb_depth=value; + } + break; + case OPUS_GET_LSB_DEPTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->lsb_depth; + } + break; + case OPUS_SET_EXPERT_FRAME_DURATION_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value != OPUS_FRAMESIZE_ARG && value != OPUS_FRAMESIZE_2_5_MS && + value != OPUS_FRAMESIZE_5_MS && value != OPUS_FRAMESIZE_10_MS && + value != OPUS_FRAMESIZE_20_MS && value != OPUS_FRAMESIZE_40_MS && + value != OPUS_FRAMESIZE_60_MS && value != OPUS_FRAMESIZE_80_MS && + value != OPUS_FRAMESIZE_100_MS && value != OPUS_FRAMESIZE_120_MS) + { + goto bad_arg; + } + st->variable_duration = value; + } + break; + case OPUS_GET_EXPERT_FRAME_DURATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->variable_duration; + } + break; + case OPUS_SET_PREDICTION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value > 1 || value < 0) + goto bad_arg; + st->silk_mode.reducedDependency = value; + } + break; + case OPUS_GET_PREDICTION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + goto bad_arg; + *value = st->silk_mode.reducedDependency; + } + break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + celt_encoder_ctl(celt_enc, OPUS_SET_PHASE_INVERSION_DISABLED(value)); + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + celt_encoder_ctl(celt_enc, OPUS_GET_PHASE_INVERSION_DISABLED(value)); + } + break; +#ifdef ENABLE_DRED + case OPUS_SET_DRED_DURATION_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>DRED_MAX_FRAMES) + { + goto bad_arg; + } + st->dred_duration = value; + st->silk_mode.useDRED = !!value; + } + break; + case OPUS_GET_DRED_DURATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->dred_duration; + } + break; +#endif + case OPUS_RESET_STATE: + { + void *silk_enc; + silk_EncControlStruct dummy; + char *start; + silk_enc = (char*)st+st->silk_enc_offset; +#ifndef DISABLE_FLOAT_API + tonality_analysis_reset(&st->analysis); +#endif + + start = (char*)&st->OPUS_ENCODER_RESET_START; + OPUS_CLEAR(start, sizeof(OpusEncoder) - (start - (char*)st)); + + celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); + silk_InitEncoder( silk_enc, st->arch, &dummy ); +#ifdef ENABLE_DRED + /* Initialize DRED Encoder */ + dred_encoder_reset( &st->dred_encoder ); +#endif + st->stream_channels = st->channels; + st->hybrid_stereo_width_Q14 = 1 << 14; + st->prev_HB_gain = Q15ONE; + st->first = 1; + st->mode = MODE_HYBRID; + st->bandwidth = OPUS_BANDWIDTH_FULLBAND; + st->variable_HP_smth2_Q15 = silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ); + } + break; + case OPUS_SET_FORCE_MODE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if ((value < MODE_SILK_ONLY || value > MODE_CELT_ONLY) && value != OPUS_AUTO) + { + goto bad_arg; + } + st->user_forced_mode = value; + } + break; + case OPUS_SET_LFE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->lfe = value; + ret = celt_encoder_ctl(celt_enc, OPUS_SET_LFE(value)); + } + break; + case OPUS_SET_ENERGY_MASK_REQUEST: + { + opus_val16 *value = va_arg(ap, opus_val16*); + st->energy_masking = value; + ret = celt_encoder_ctl(celt_enc, OPUS_SET_ENERGY_MASK(value)); + } + break; + case OPUS_GET_IN_DTX_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + if (st->silk_mode.useDTX && (st->prev_mode == MODE_SILK_ONLY || st->prev_mode == MODE_HYBRID)) { + /* DTX determined by Silk. */ + silk_encoder *silk_enc = (silk_encoder*)(void *)((char*)st+st->silk_enc_offset); + *value = silk_enc->state_Fxx[0].sCmn.noSpeechCounter >= NB_SPEECH_FRAMES_BEFORE_DTX; + /* Stereo: check second channel unless only the middle channel was encoded. */ + if(*value == 1 && st->silk_mode.nChannelsInternal == 2 && silk_enc->prev_decode_only_middle == 0) { + *value = silk_enc->state_Fxx[1].sCmn.noSpeechCounter >= NB_SPEECH_FRAMES_BEFORE_DTX; + } + } +#ifndef DISABLE_FLOAT_API + else if (st->use_dtx) { + /* DTX determined by Opus. */ + *value = st->nb_no_activity_ms_Q1 >= NB_SPEECH_FRAMES_BEFORE_DTX*20*2; + } +#endif + else { + *value = 0; + } + } + break; +#ifdef USE_WEIGHTS_FILE + case OPUS_SET_DNN_BLOB_REQUEST: + { + const unsigned char *data = va_arg(ap, const unsigned char *); + opus_int32 len = va_arg(ap, opus_int32); + if(len<0 || data == NULL) + { + goto bad_arg; + } +#ifdef ENABLE_DRED + ret = dred_encoder_load_model(&st->dred_encoder, data, len); +#endif + } + break; +#endif + case CELT_GET_MODE_REQUEST: + { + const CELTMode ** value = va_arg(ap, const CELTMode**); + if (!value) + { + goto bad_arg; + } + ret = celt_encoder_ctl(celt_enc, CELT_GET_MODE(value)); + } + break; + default: + /* fprintf(stderr, "unknown opus_encoder_ctl() request: %d", request);*/ + ret = OPUS_UNIMPLEMENTED; + break; + } + va_end(ap); + return ret; +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +} + +void opus_encoder_destroy(OpusEncoder *st) +{ + opus_free(st); +} diff --git a/port/third_party/opus/src/opus_private.h b/port/third_party/opus/src/opus_private.h new file mode 100644 index 00000000..279f5f95 --- /dev/null +++ b/port/third_party/opus/src/opus_private.h @@ -0,0 +1,223 @@ +/* Copyright (c) 2012 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + + +#ifndef OPUS_PRIVATE_H +#define OPUS_PRIVATE_H + +#include "arch.h" +#include "opus.h" +#include "celt.h" + +#include /* va_list */ +#include /* offsetof */ + +struct OpusRepacketizer { + unsigned char toc; + int nb_frames; + const unsigned char *frames[48]; + opus_int16 len[48]; + int framesize; + const unsigned char *paddings[48]; + opus_int32 padding_len[48]; +}; + +typedef struct { + int id; + int frame; + const unsigned char *data; + opus_int32 len; +} opus_extension_data; + +typedef struct ChannelLayout { + int nb_channels; + int nb_streams; + int nb_coupled_streams; + unsigned char mapping[256]; +} ChannelLayout; + +typedef enum { + MAPPING_TYPE_NONE, + MAPPING_TYPE_SURROUND, + MAPPING_TYPE_AMBISONICS +} MappingType; + +struct OpusMSEncoder { + ChannelLayout layout; + int arch; + int lfe_stream; + int application; + int variable_duration; + MappingType mapping_type; + opus_int32 bitrate_bps; + /* Encoder states go here */ + /* then opus_val32 window_mem[channels*120]; */ + /* then opus_val32 preemph_mem[channels]; */ +}; + +struct OpusMSDecoder { + ChannelLayout layout; + /* Decoder states go here */ +}; + +int opus_multistream_encoder_ctl_va_list(struct OpusMSEncoder *st, int request, + va_list ap); +int opus_multistream_decoder_ctl_va_list(struct OpusMSDecoder *st, int request, + va_list ap); + +int validate_layout(const ChannelLayout *layout); +int get_left_channel(const ChannelLayout *layout, int stream_id, int prev); +int get_right_channel(const ChannelLayout *layout, int stream_id, int prev); +int get_mono_channel(const ChannelLayout *layout, int stream_id, int prev); + +typedef void (*opus_copy_channel_in_func)( + opus_val16 *dst, + int dst_stride, + const void *src, + int src_stride, + int src_channel, + int frame_size, + void *user_data +); + +typedef void (*opus_copy_channel_out_func)( + void *dst, + int dst_stride, + int dst_channel, + const opus_val16 *src, + int src_stride, + int frame_size, + void *user_data +); + +#define MODE_SILK_ONLY 1000 +#define MODE_HYBRID 1001 +#define MODE_CELT_ONLY 1002 + +#define OPUS_SET_VOICE_RATIO_REQUEST 11018 +#define OPUS_GET_VOICE_RATIO_REQUEST 11019 + +/** Configures the encoder's expected percentage of voice + * opposed to music or other signals. + * + * @note This interface is currently more aspiration than actuality. It's + * ultimately expected to bias an automatic signal classifier, but it currently + * just shifts the static bitrate to mode mapping around a little bit. + * + * @param[in] x int: Voice percentage in the range 0-100, inclusive. + * @hideinitializer */ +#define OPUS_SET_VOICE_RATIO(x) OPUS_SET_VOICE_RATIO_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured voice ratio value, @see OPUS_SET_VOICE_RATIO + * + * @param[out] x int*: Voice percentage in the range 0-100, inclusive. + * @hideinitializer */ +#define OPUS_GET_VOICE_RATIO(x) OPUS_GET_VOICE_RATIO_REQUEST, __opus_check_int_ptr(x) + + +#define OPUS_SET_FORCE_MODE_REQUEST 11002 +#define OPUS_SET_FORCE_MODE(x) OPUS_SET_FORCE_MODE_REQUEST, __opus_check_int(x) + +typedef void (*downmix_func)(const void *, opus_val32 *, int, int, int, int, int); +void downmix_float(const void *_x, opus_val32 *sub, int subframe, int offset, int c1, int c2, int C); +void downmix_int(const void *_x, opus_val32 *sub, int subframe, int offset, int c1, int c2, int C); +int is_digital_silence(const opus_val16* pcm, int frame_size, int channels, int lsb_depth); + +int encode_size(int size, unsigned char *data); + +opus_int32 frame_size_select(opus_int32 frame_size, int variable_duration, opus_int32 Fs); + +opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, + unsigned char *data, opus_int32 out_data_bytes, int lsb_depth, + const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2, + int analysis_channels, downmix_func downmix, int float_api); + +int opus_decode_native(OpusDecoder *st, const unsigned char *data, opus_int32 len, + opus_val16 *pcm, int frame_size, int decode_fec, int self_delimited, + opus_int32 *packet_offset, int soft_clip, const OpusDRED *dred, opus_int32 dred_offset); + +/* Make sure everything is properly aligned. */ +static OPUS_INLINE int align(int i) +{ + struct foo {char c; union { void* p; opus_int32 i; opus_val32 v; } u;}; + + unsigned int alignment = offsetof(struct foo, u); + + /* Optimizing compilers should optimize div and multiply into and + for all sensible alignment values. */ + return ((i + alignment - 1) / alignment) * alignment; +} + +/* More than that is ridiculous for now (3 * max frames per packet)*/ +opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 *header_size); + +int opus_packet_parse_impl(const unsigned char *data, opus_int32 len, + int self_delimited, unsigned char *out_toc, + const unsigned char *frames[48], opus_int16 size[48], + int *payload_offset, opus_int32 *packet_offset, + const unsigned char **padding, opus_int32 *padding_len); + +opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int end, + unsigned char *data, opus_int32 maxlen, int self_delimited, int pad, + const opus_extension_data *extensions, int nb_extensions); + +int pad_frame(unsigned char *data, opus_int32 len, opus_int32 new_len); + +int opus_multistream_encode_native +( + struct OpusMSEncoder *st, + opus_copy_channel_in_func copy_channel_in, + const void *pcm, + int analysis_frame_size, + unsigned char *data, + opus_int32 max_data_bytes, + int lsb_depth, + downmix_func downmix, + int float_api, + void *user_data +); + +int opus_multistream_decode_native( + struct OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + void *pcm, + opus_copy_channel_out_func copy_channel_out, + int frame_size, + int decode_fec, + int soft_clip, + void *user_data +); + +opus_int32 opus_packet_extensions_parse(const unsigned char *data, opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions); + +opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data *extensions, opus_int32 nb_extensions, int pad); + +opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len); + +opus_int32 opus_packet_pad_impl(unsigned char *data, opus_int32 len, opus_int32 new_len, int pad, const opus_extension_data *extensions, int nb_extensions); + +#endif /* OPUS_PRIVATE_H */ diff --git a/port/third_party/opus/src/repacketizer.c b/port/third_party/opus/src/repacketizer.c new file mode 100644 index 00000000..79798b02 --- /dev/null +++ b/port/third_party/opus/src/repacketizer.c @@ -0,0 +1,464 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + 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. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus.h" +#include "opus_private.h" +#include "os_support.h" +#include "stack_alloc.h" + + +int opus_repacketizer_get_size(void) +{ + return sizeof(OpusRepacketizer); +} + +OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) +{ + rp->nb_frames = 0; + return rp; +} + +OpusRepacketizer *opus_repacketizer_create(void) +{ + OpusRepacketizer *rp; + rp=(OpusRepacketizer *)opus_alloc(opus_repacketizer_get_size()); + if(rp==NULL)return NULL; + return opus_repacketizer_init(rp); +} + +void opus_repacketizer_destroy(OpusRepacketizer *rp) +{ + opus_free(rp); +} + +static int opus_repacketizer_cat_impl(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len, int self_delimited) +{ + unsigned char tmp_toc; + int curr_nb_frames,ret; + /* Set of check ToC */ + if (len<1) return OPUS_INVALID_PACKET; + if (rp->nb_frames == 0) + { + rp->toc = data[0]; + rp->framesize = opus_packet_get_samples_per_frame(data, 8000); + } else if ((rp->toc&0xFC) != (data[0]&0xFC)) + { + /*fprintf(stderr, "toc mismatch: 0x%x vs 0x%x\n", rp->toc, data[0]);*/ + return OPUS_INVALID_PACKET; + } + curr_nb_frames = opus_packet_get_nb_frames(data, len); + if(curr_nb_frames<1) return OPUS_INVALID_PACKET; + + /* Check the 120 ms maximum packet size */ + if ((curr_nb_frames+rp->nb_frames)*rp->framesize > 960) + { + return OPUS_INVALID_PACKET; + } + + ret=opus_packet_parse_impl(data, len, self_delimited, &tmp_toc, &rp->frames[rp->nb_frames], &rp->len[rp->nb_frames], + NULL, NULL, &rp->paddings[rp->nb_frames], &rp->padding_len[rp->nb_frames]); + if(ret<1)return ret; + + /* set padding length to zero for all but the first frame */ + while (curr_nb_frames > 1) + { + rp->nb_frames++; + rp->padding_len[rp->nb_frames] = 0; + rp->paddings[rp->nb_frames] = NULL; + curr_nb_frames--; + } + rp->nb_frames++; + return OPUS_OK; +} + +int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) +{ + return opus_repacketizer_cat_impl(rp, data, len, 0); +} + +int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) +{ + return rp->nb_frames; +} + +opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int end, + unsigned char *data, opus_int32 maxlen, int self_delimited, int pad, const opus_extension_data *extensions, int nb_extensions) +{ + int i, count; + opus_int32 tot_size; + opus_int16 *len; + const unsigned char **frames; + unsigned char * ptr; + int ones_begin=0, ones_end=0; + int ext_begin=0, ext_len=0; + int ext_count, total_ext_count; + VARDECL(opus_extension_data, all_extensions); + SAVE_STACK; + + if (begin<0 || begin>=end || end>rp->nb_frames) + { + /*fprintf(stderr, "%d %d %d\n", begin, end, rp->nb_frames);*/ + RESTORE_STACK; + return OPUS_BAD_ARG; + } + count = end-begin; + + len = rp->len+begin; + frames = rp->frames+begin; + if (self_delimited) + tot_size = 1 + (len[count-1]>=252); + else + tot_size = 0; + + /* figure out total number of extensions */ + total_ext_count = nb_extensions; + for (i=begin;ipaddings[i], rp->padding_len[i]); + if (n > 0) total_ext_count += n; + } + ALLOC(all_extensions, total_ext_count ? total_ext_count : ALLOC_NONE, opus_extension_data); + /* copy over any extensions that were passed in */ + for (ext_count=0;ext_countpaddings[i], rp->padding_len[i], + &all_extensions[ext_count], &frame_ext_count); + if (ret<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + /* renumber the extension frame numbers */ + for (j=0;j maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = rp->toc&0xFC; + } else if (count==2) + { + if (len[1] == len[0]) + { + /* Code 1 */ + tot_size += 2*len[0]+1; + if (tot_size > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = (rp->toc&0xFC) | 0x1; + } else { + /* Code 2 */ + tot_size += len[0]+len[1]+2+(len[0]>=252); + if (tot_size > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = (rp->toc&0xFC) | 0x2; + ptr += encode_size(len[0], ptr); + } + } + if (count > 2 || (pad && tot_size < maxlen) || ext_count > 0) + { + /* Code 3 */ + int vbr; + int pad_amount=0; + + /* Restart the process for the padding case */ + ptr = data; + if (self_delimited) + tot_size = 1 + (len[count-1]>=252); + else + tot_size = 0; + vbr = 0; + for (i=1;i=252) + len[i]; + tot_size += len[count-1]; + + if (tot_size > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = (rp->toc&0xFC) | 0x3; + *ptr++ = count | 0x80; + } else { + tot_size += count*len[0]+2; + if (tot_size > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = (rp->toc&0xFC) | 0x3; + *ptr++ = count; + } + pad_amount = pad ? (maxlen-tot_size) : 0; + if (ext_count>0) + { + /* figure out how much space we need for the extensions */ + ext_len = opus_packet_extensions_generate(NULL, maxlen-tot_size, all_extensions, ext_count, 0); + if (ext_len < 0) return ext_len; + if (!pad) + pad_amount = ext_len + ext_len/254 + 1; + } + if (pad_amount != 0) + { + int nb_255s; + data[1] |= 0x40; + nb_255s = (pad_amount-1)/255; + if (tot_size + ext_len + nb_255s + 1 > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + ext_begin = tot_size+pad_amount-ext_len; + /* Prepend 0x01 padding */ + ones_begin = tot_size+nb_255s+1; + ones_end = tot_size+pad_amount-ext_len; + for (i=0;i 0) { + int ret = opus_packet_extensions_generate(&data[ext_begin], ext_len, all_extensions, ext_count, 0); + celt_assert(ret == ext_len); + } + for (i=ones_begin;inb_frames, data, maxlen, 0, 0, NULL, 0); +} + +opus_int32 opus_packet_pad_impl(unsigned char *data, opus_int32 len, opus_int32 new_len, int pad, const opus_extension_data *extensions, int nb_extensions) +{ + OpusRepacketizer rp; + opus_int32 ret; + VARDECL(unsigned char, copy); + SAVE_STACK; + if (len < 1) + return OPUS_BAD_ARG; + if (len==new_len) + return OPUS_OK; + else if (len > new_len) + return OPUS_BAD_ARG; + ALLOC(copy, len, unsigned char); + opus_repacketizer_init(&rp); + /* Moving payload to the end of the packet so we can do in-place padding */ + OPUS_COPY(copy, data, len); + ret = opus_repacketizer_cat(&rp, copy, len); + if (ret != OPUS_OK) + return ret; + ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, data, new_len, 0, pad, extensions, nb_extensions); + RESTORE_STACK; + return ret; +} + +int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len) +{ + opus_int32 ret; + ALLOC_STACK; + ret = opus_packet_pad_impl(data, len, new_len, 1, NULL, 0); + RESTORE_STACK; + if (ret > 0) + return OPUS_OK; + else + return ret; +} + +opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len) +{ + OpusRepacketizer rp; + opus_int32 ret; + int i; + if (len < 1) + return OPUS_BAD_ARG; + opus_repacketizer_init(&rp); + ret = opus_repacketizer_cat(&rp, data, len); + if (ret < 0) + return ret; + /* Discard all padding and extensions. */ + for (i=0;i 0 && ret <= len); + return ret; +} + +int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams) +{ + int s; + int count; + unsigned char toc; + opus_int16 size[48]; + opus_int32 packet_offset; + opus_int32 amount; + + if (len < 1) + return OPUS_BAD_ARG; + if (len==new_len) + return OPUS_OK; + else if (len > new_len) + return OPUS_BAD_ARG; + amount = new_len - len; + /* Seek to last stream */ + for (s=0;s= 4) + /* (each structure's formatted part within the table) */ + while (structure + 4 <= end && structure[1] >= 4 && structure[1] <= end - structure) { BYTE *strings = structure + structure[1]; diff --git a/port/windows/src/win32_posix.c b/port/windows/src/win32_posix.c index fe48ed00..b79c3304 100644 --- a/port/windows/src/win32_posix.c +++ b/port/windows/src/win32_posix.c @@ -308,6 +308,28 @@ int pthread_cond_broadcast(pthread_cond_t *condition) /* ---------- time */ +typedef VOID (WINAPI *system_time_proc)(LPFILETIME); + +/* the wall clock: GetSystemTimePreciseAsFileTime where Windows has it +(Windows 8 and later), else GetSystemTimeAsFileTime. Importing the precise +one directly would keep the game from starting on Windows 7. Threads that +race here store the same pointer. */ +static void system_time(FILETIME *now) +{ + static system_time_proc volatile proc; + system_time_proc get_time = proc; + + if (!get_time) + { + get_time = (system_time_proc)(void *)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), + "GetSystemTimePreciseAsFileTime"); + if (!get_time) + get_time = GetSystemTimeAsFileTime; + proc = get_time; + } + get_time(now); +} + int clock_gettime(clockid_t clock, struct timespec *time) { if (clock == CLOCK_MONOTONIC) @@ -327,7 +349,7 @@ int clock_gettime(clockid_t clock, struct timespec *time) FILETIME now; unsigned long long intervals; - GetSystemTimePreciseAsFileTime(&now); + system_time(&now); intervals = ((unsigned long long)now.dwHighDateTime << 32) | now.dwLowDateTime; intervals -= 116444736000000000ULL; time->tv_sec = (time_t)(intervals / 10000000ULL); diff --git a/source/ai/action_vehicle.c b/source/ai/action_vehicle.c index 26b7b71a..ede538e1 100644 --- a/source/ai/action_vehicle.c +++ b/source/ai/action_vehicle.c @@ -650,9 +650,9 @@ static short action_vehicle_find_impromptu_seat( struct unit_datum *vehicle = unit_get(vehicle_index); struct unit_definition *vehicle_definition = unit_definition_get(vehicle->definition_index); - real_point3d best_entry_point; - real_vector3d best_entry_facing; - real_point3d best_hint_point; + real_point3d best_entry_point = { 0 }; + real_vector3d best_entry_facing = { 0 }; + real_point3d best_hint_point = { 0 }; long best_seat_index = NONE; real best_seat_weight = 0.0f; long seat_index; @@ -688,13 +688,6 @@ static short action_vehicle_find_impromptu_seat( } } - /* BUG (preserved for exact matching): when no seat qualifies, best_seat_index stays - * NONE and the three copies below store the never-assigned best_* locals (January - * 0x40a1b0 +0xe2..+0x11d copies them with plain dword moves). Reachable whenever no - * seat qualifies; the only caller, action_vehicle_setup_impromptu, reads the outputs - * only when the returned seat index is not NONE. A corrected build should copy them - * only when best_seat_index != NONE. Source-policy approval pending (2026-09-27 audit). - */ if (entry_point) { *entry_point = best_entry_point; diff --git a/source/ai/actions.c b/source/ai/actions.c index 8d8e32f7..7c88974a 100644 --- a/source/ai/actions.c +++ b/source/ai/actions.c @@ -4011,7 +4011,7 @@ static boolean actor_action_find_escape_from_danger( boolean result = FALSE; boolean selected_is_ledge = FALSE; real evade_distance = unit_definition->unit.dive_distance; - real_vector2d alignment_vector; + real_vector2d alignment_vector = *global_forward2d; match_assert( "c:\\halo\\SOURCE\\ai\\actions.c", @@ -4175,13 +4175,6 @@ static boolean actor_action_find_escape_from_danger( system_exit(-1); } - /* BUG (preserved for exact matching): alignment_vector is assigned only when the - * unit's dive_distance is positive; otherwise it is stored below unassigned (January - * 0x40c970 +0x386..+0x38f). Reachable for any unit whose dive distance is not - * positive; escape_direction is then NONE, and the only caller, - * actor_action_handle_danger_avoidance, leaves that block without reading the vector. - * A corrected build should store a defined vector on that path. Source-policy - * approval pending (2026-09-27 audit). */ *escape_direction_reference = escape_direction; *escape_distance_reference = evade_distance; *escape_is_ledge_reference = selected_is_ledge; diff --git a/source/ai/actor_combat.c b/source/ai/actor_combat.c index 674af1b2..819e6269 100644 --- a/source/ai/actor_combat.c +++ b/source/ai/actor_combat.c @@ -1572,11 +1572,11 @@ long actor_aim_grenade( actor_combat_reaim_grenade(actor_index, origin); + aim_vector = actor->control.grenade_current_aim_vector; if (actor->input.vehicle_index == NONE) { real_vector2d horizontal_aim_vector; - aim_vector = actor->control.grenade_current_aim_vector; horizontal_aim_vector = *(real_vector2d const *)&aim_vector; if (normalize2d(&horizontal_aim_vector) > 0.0f && @@ -1609,12 +1609,6 @@ long actor_aim_grenade( } } - /* BUG (preserved for exact matching): aim_vector is assigned only on foot. - * With actor->input.vehicle_index != NONE, January branches from its vehicle - * test straight to this scale and reads the unassigned local; the 2011 HCEX - * and later /Od builds keep the same path. A corrected build should start - * from actor->control.grenade_current_aim_vector. - */ scale_vector3d( &aim_vector, actor->control.grenade_current_aim_speed, diff --git a/source/ai/actor_looking.c b/source/ai/actor_looking.c index 6b3f6dcc..4f720c40 100644 --- a/source/ai/actor_looking.c +++ b/source/ai/actor_looking.c @@ -829,8 +829,8 @@ static long actor_look_idle_timer( struct actor_datum *actor = actor_get(actor_index); struct actor_definition *definition = actor_definition_get(actor->meta.definition_index); struct weapon_definition *weapon_definition = actor_get_weapon_definition(actor_index); - real time_lower_bound; - real time_upper_bound; + real time_lower_bound = 0.0f; + real time_upper_bound = 0.0f; real time; long ticks; @@ -848,10 +848,6 @@ static long actor_look_idle_timer( time_lower_bound = looking_definition->idle_look_time_lower_bound; time_upper_bound = looking_definition->idle_look_time_upper_bound; break; - /* time_lower_bound and time_upper_bound are left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert( "c:\\halo\\SOURCE\\ai\\actor_looking.c", @@ -1116,14 +1112,10 @@ static boolean actor_look_decode_direction( switch (specification->type) { case _direction_specification_movement: - /* BUG (preserved for exact matching): January loads the point's - * z field twice (actor + 0x514). A corrected diagnostic should - * print the y field as its second point component. - */ sprintf(temporary, "denormalized %f: %smoving (p%f %f %f) (v%f %f %f)", magnitude, actor->control.moving ? "" : "not ", actor->control.moving_towards_point.x, - actor->control.moving_towards_point.z, + actor->control.moving_towards_point.y, actor->control.moving_towards_point.z, actor->control.moving_towards_vector.i, actor->control.moving_towards_vector.j, diff --git a/source/ai/actor_perception.c b/source/ai/actor_perception.c index f3a583a8..f0d8945f 100644 --- a/source/ai/actor_perception.c +++ b/source/ai/actor_perception.c @@ -1504,10 +1504,6 @@ void actor_situation_update_target_status( (target_prop->definitely_located != FALSE) + 5; break; - /* target_type is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: display_assert( NULL, @@ -3383,30 +3379,7 @@ void actor_emotion_update( actor->emotions.forced_to_charge = TRUE; } - /* BUG (preserved for exact matching): the scan starts at - * NUMBER_OF_ACTOR_THREAT_TYPES rather than NUMBER_OF_ACTOR_THREAT_TYPES-1, - * so its first iteration reads specific_threats[9] - one element past the - * end of the nine-element array, which is cumulative_threats[0]. - * Evidence: January emits `mov eax,9` at +0xf5, then the six-byte alignment - * nop 8d 9b 00 00 00 00 (`lea ebx,[ebx]`, which touches neither EAX nor - * memory), and falls into the loop head at +0x100, `movsx edx,ax` followed by - * `cmp byte ptr [edx+esi+0x1ee],0`; the `dec eax` is at +0x10d, AFTER that - * load. In the same object actor_situation_update increments - * cumulative_threats for threat types 1..8 at esi+0x1f8..esi+0x1ff and - * area_friends at esi+0x200, which fixes cumulative_threats at esi+0x1f7 and - * so specific_threats at esi+0x1ee with exactly nine elements. - * Layout is asserted, not assumed: see - * actor_perception_specific_threats_size_assert and - * actor_perception_threat_arrays_adjacent_assert above, which fail to compile - * if the two arrays ever stop being adjacent or change length. - * Consequence: none observable. cumulative_threats[_actor_threat_none] is the - * one element of that array nothing ever writes - every write in the tree - * uses _actor_threat_visible..._actor_threat_damaging_me, i.e. 1..8, and the - * single variable-index use is a read-only csprintf argument in the - * actor_debug_print_threat macro - so the stray byte is always zero, the - * early-out is never taken on the stray iteration, and the scan falls through - * to the intended starting index. */ - for (priority = NUMBER_OF_ACTOR_THREAT_TYPES; + for (priority = NUMBER_OF_ACTOR_THREAT_TYPES - 1; priority > 0 && actor->situation.specific_threats[priority] <= 0; priority--) @@ -3858,12 +3831,8 @@ void prop_status_refresh( prop->preferred_target = TRUE; break; - /* BUG (preserved for exact matching): January compares a platoon - * reference with the actor's squad index (actor+0x3a) and a squad - * reference with its platoon index (actor+0x3c), the reverse of - * actor_action_handle_vehicle_entry. A corrected build should compare - * platoon references with meta.platoon_index and squad references with - * meta.squad_index. */ + /* (as the original game, kept: a platoon reference is compared with the actor's squad, + and a squad reference with its platoon) */ case _ai_reference_type_squad: if ((short)(((unsigned long)actor->external_orders.desired_target_ai_index >> 16) & UNSIGNED_CHAR_MAX) == prop_actor->meta.platoon_index) @@ -5815,10 +5784,8 @@ static void actor_perception_refresh( } else { - /* BUG (preserved for exact matching): January walks the perceiving actor's - * swarm unit list (actor+0x24 through the actor_get(actor_index) pointer), - * not the uncached swarm actor's list. A corrected build should start from - * swarm_actor->meta.swarm_unit_index. */ + /* (as the original game, kept: the perceiving actor's swarm is walked, not the swarm + actor's) */ long unit_index = actor->meta.swarm_unit_index; while (unit_index != NONE) @@ -5940,9 +5907,7 @@ static void actor_perception_refresh( prop_position_refresh(actor_index, prop_index, &position, FALSE, FALSE); } - /* BUG (preserved for exact matching): January leaves the loop before - * advancing past the entry that reached the limit, so the discard loop - * below also deletes that entry when it was an existing prop. */ + /* (as the original game, kept: the entry that reached the limit is discarded below too) */ if (++enemies.accepted_count >= 4) { break; @@ -6540,7 +6505,7 @@ void actor_perception_update( { short knowledge_type = actor_get_perception_knowledge(actor_index, iterator.index); short awareness_speed; - real awareness_delta; + real awareness_delta = 0.0f; #line 489 "c:\\halo\\SOURCE\\ai\\actor_perception.c" assert((knowledge_type >= 0) && (knowledge_type < NUMBER_OF_ACTOR_KNOWLEDGE_TYPES)); @@ -6570,10 +6535,6 @@ void actor_perception_update( awareness_delta = 1.0f; break; - /* awareness_delta is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: #line 516 "c:\\halo\\SOURCE\\ai\\actor_perception.c" assert(!"unreachable"); diff --git a/source/ai/actors.c b/source/ai/actors.c index dc5c70b8..b8a422a3 100644 --- a/source/ai/actors.c +++ b/source/ai/actors.c @@ -696,13 +696,8 @@ static void actor_verify_unit_activation( struct unit_datum *unit = unit_get(unit_index); struct actor_datum *actor = actor_get(actor_index); - /* BUG (preserved for exact matching): January tests - * unit->unit.last_vehicle_index + 30 < game_time_get() here; the exact - * unit_exit_seat_end proves last_vehicle_index at 0x2DC with the exit time in - * the following long. A corrected build should compare - * unit->unit.game_time_at_last_vehicle_exit. */ if (unit->object.parent_object_index == NONE && - unit->unit.last_vehicle_index + 30 < game_time_get()) + unit->unit.game_time_at_last_vehicle_exit + 30 < game_time_get()) { if (actor->meta.dormant != !TEST_FLAG(object_header->flags, _object_header_active_bit)) { diff --git a/source/ai/ai.c b/source/ai/ai.c index d6ae2c79..d78094b3 100644 --- a/source/ai/ai.c +++ b/source/ai/ai.c @@ -2712,14 +2712,10 @@ void ai_handle_editing( "editing caused an overflow of MAXIMUM_SQUADS_PER_MAP (%d)", MAXIMUM_SQUADS_PER_MAP)); - /* BUG (preserved for exact matching): January passes the squad record COUNT as the - * csmemmove byte size, so only that many bytes of the trailing squad records move. - * A corrected build should multiply the count by sizeof(struct squad_datum). - */ csmemmove( &squad_array[encounter->squad_base + encounter_definition->squads.count], &squad_array[encounter->squad_base + encounter->squad_count], - last_squad_index - encounter->squad_base - encounter->squad_count); + (last_squad_index - encounter->squad_base - encounter->squad_count) * sizeof(struct squad_datum)); if (squad_count_delta > 0) { @@ -2746,14 +2742,10 @@ void ai_handle_editing( "editing caused an overflow of MAXIMUM_PLATOONS_PER_MAP (%d)", MAXIMUM_PLATOONS_PER_MAP)); - /* BUG (preserved for exact matching): January passes the platoon record COUNT as the - * csmemmove byte size, so only that many bytes of the trailing platoon records move. - * A corrected build should multiply the count by sizeof(struct platoon_datum). - */ csmemmove( &platoon_array[encounter->platoon_base + encounter_definition->platoons.count], &platoon_array[encounter->platoon_base + encounter->platoon_count], - last_platoon_index - encounter->platoon_base - encounter->platoon_count); + (last_platoon_index - encounter->platoon_base - encounter->platoon_count) * sizeof(struct platoon_datum)); if (platoon_count_delta > 0) { diff --git a/source/ai/ai_communication.c b/source/ai/ai_communication.c index 50d5dfbf..538c907d 100644 --- a/source/ai/ai_communication.c +++ b/source/ai/ai_communication.c @@ -2402,10 +2402,7 @@ static long ai_communication_find_global_actor_to_talk( } if (cause_unit_index != NONE) { - /* BUG (preserved for exact matching): January asks for the subject - * position again here and leaves cause_point uninitialized. A corrected - * build should call unit_get_head_position(cause_unit_index, &cause_point). */ - unit_get_head_position(subject_unit_index, &subject_point); + unit_get_head_position(cause_unit_index, &cause_point); } actor_iterator_new(&iterator, TRUE); diff --git a/source/ai/ai_profile.c b/source/ai/ai_profile.c index fd67d200..abe0b1b8 100644 --- a/source/ai/ai_profile.c +++ b/source/ai/ai_profile.c @@ -272,9 +272,9 @@ void ai_profile_update( meter->history_sum -= meter->history[meter->history_next_index]; } meter->history[meter->history_next_index] = meter->current_value; - /* BUG (original): January and October subtract the evicted sample but - * never add the new sample to history_sum. A corrected build should add - * current_value here before computing the average. */ + /* port: the new sample counted too (the original only took the old one + off, so the averages were wrong) */ + meter->history_sum += meter->current_value; meter->history_next_index++; meter->history_count = MAX(meter->history_count, meter->history_next_index); meter->history_next_index %= AI_METER_HISTORY_TICKS; @@ -460,8 +460,7 @@ static void ai_profile_render_actors( { short tab_stops[] = {150, 300}; - /* Preserve January's missing tab marker and final unit-count separator. */ - sprintf(profilestring, "actors %d/%d/%d|units %d/%d%d", + sprintf(profilestring, "actors %d/%d/%d|tunits %d/%d/%d", ai_profile.meters[_ai_meter_actors_active].current_value, ai_profile.meters[_ai_meter_actors_updated].current_value, ai_profile.meters[_ai_meter_actors].current_value, diff --git a/source/ai/ai_script.c b/source/ai/ai_script.c index d46d1929..3fd3ab39 100644 --- a/source/ai/ai_script.c +++ b/source/ai/ai_script.c @@ -1563,11 +1563,7 @@ void ai_scripting_follow_target_ai( char target_ai_name[512]; ai_index_to_string(ai_reference, global_scenario_get(), ai_name, sizeof(ai_name)); - /* BUG (preserved for exact matching): January formats ai_reference again instead of - * target_ai_reference, so the log prints the first name twice. A corrected build should - * pass target_ai_reference here. - */ - ai_index_to_string(ai_reference, global_scenario_get(), target_ai_name, sizeof(target_ai_name)); + ai_index_to_string(target_ai_reference, global_scenario_get(), target_ai_name, sizeof(target_ai_name)); error(_error_silent, "%s: ai_follow_target_ai %s %s", hs_runtime_get_executing_thread_name(), ai_name, target_ai_name); } @@ -2725,28 +2721,13 @@ void ai_scripting_migrate_and_speak( global_scenario_get(), target_name, sizeof(target_name)); - /* BUG (preserved for exact matching): the format has four %s conversions - * but January supplies only three values, so the fourth conversion reads - * past the end of the argument list. - * Evidence: January's .rdata holds the 34-byte literal - * "%s: ai_migrate_and_speak %s %s %s", which has four conversions, while - * its call site cleans 0x14 bytes - five dwords, i.e. only three values - - * and loads speech_type only afterwards, at +0x7d. The same defect - * survives unrepaired in the 2020 build. - * Consequence: with January's prologue the fourth slot lands on the saved - * EDI home, so vsprintf dereferences the caller's entry EDI as a char *. - * The branch is gated on ai_debug.print_migration or - * ai_debug.print_scripting. Both default to false, but they are exposed as - * the HaloScript globals ai_print_migration and ai_print_scripting, so a - * scenario script or the debug console can reach this path at runtime. - * A corrected build should pass speech_type as the fourth value. - */ error( _error_silent, "%s: ai_migrate_and_speak %s %s %s", hs_runtime_get_executing_thread_name(), source_name, - target_name); + target_name, + speech_type); } if (_stricmp(speech_type, "advance") == 0) diff --git a/source/ai/encounters.c b/source/ai/encounters.c index c3b3de1c..143bb7e6 100644 --- a/source/ai/encounters.c +++ b/source/ai/encounters.c @@ -2190,7 +2190,7 @@ void encounter_create( struct squad_definition *squad_definition = TAG_BLOCK_GET_ELEMENT( &encounter_definition->squads, squad_index, struct squad_definition); short initial_variant; - short count; + short count = 0; short actor_type; short i; @@ -2214,10 +2214,6 @@ void encounter_create( case _game_difficulty_level_impossible: count = squad_definition->max_count; break; - /* count is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert("c:\\halo\\SOURCE\\ai\\encounters.c", 1730, FALSE, NULL); } diff --git a/source/ai/path.c b/source/ai/path.c index d40891c1..fd2e78cf 100644 --- a/source/ai/path.c +++ b/source/ai/path.c @@ -1271,14 +1271,7 @@ static short build_path_edges_for_surface( 0x5EE, (edge->adjacent_surface_index >= 0) && (edge->adjacent_surface_index < bsp->surfaces.count)); } - /* BUG (preserved for exact matching): January tests adjacent_surface_index for NONE only to - * skip the range assertion (+0xb9..+0xbe) and then loads pathfinding_surfaces[adjacent] - * unconditionally (+0xeb..+0xf0); the Aug-15-2001, Sept-25-2001 and later /Od (0x4c3730) - * builds do the same. The index is NONE only for an edge with no surface on its far side (an - * open collision BSP); the byte before the array then becomes the neighbour's flags, which the - * search treats as walkable when bit 0x40 is set. No structure BSP in the shipped - * 01.10.12.2276 maps has an open edge (0 of 2,066,607 edges in 82 BSPs). - */ + /* (an open edge, with no surface beyond it, is in no shipped map) */ /* port: (and a neighbor, not NONE, that is no surface of the bsp's or has no pathfinding surface is not walkable: a map's index; nor is NONE of a bsp with no pathfinding surfaces, with no array to read diff --git a/source/ai/path_smoothing.c b/source/ai/path_smoothing.c index 98d41ba5..64cb19eb 100644 --- a/source/ai/path_smoothing.c +++ b/source/ai/path_smoothing.c @@ -118,14 +118,7 @@ static boolean surface_is_walkable( struct collision_surface const *collision_surface; byte breakable_surface_index; - /* BUG (preserved for exact matching): find_turning_point passes a collision edge's surface - * index (surface_indices[0], surface_indices[!matches_end]) with no NONE test, and January - * reads pathfinding_surfaces[surface_index] first (inlined in _find_turning_point at - * +0x99..+0x9c and +0x274..+0x278). The index is NONE only for an edge with no surface on that - * side (an open collision BSP); the byte before the array is then taken as the surface's flags. - * No structure BSP in the shipped 01.10.12.2276 maps has an open edge (0 of 2,066,607 edges in - * 82 BSPs). - */ + /* (an open edge, with no surface beyond it, is in no shipped map) */ /* port: (NONE still is; any other index past the pathfinding surfaces, a map's, is no walkable surface. The retail bsps have as many pathfinding surfaces as surfaces, and every edge's surfaces are theirs) */ diff --git a/source/ai/path_structure_bsp.c b/source/ai/path_structure_bsp.c index d745b448..e61aaca2 100644 --- a/source/ai/path_structure_bsp.c +++ b/source/ai/path_structure_bsp.c @@ -121,17 +121,7 @@ boolean structure_test_ray2d( /* port: the pathfinding surfaces' flags are read through path_pathfinding_surface, which keeps the read below for NONE and gives no walkable surface for any other index past them (a map's index) */ - /* BUG (preserved for exact matching): January loads - * pathfinding_surfaces[surface_result.enter_surface_index] (+0x5d..+0x63) and the exit - * index (+0xe6..+0xec) before any NONE test; the later /Od build does the same (0x4cfe60 - * +0x98, +0x168). For a finite distance both indices come from a crossed edge - * (collision_surface_test_line2d starts at REAL_MIN/REAL_MAX), so they are NONE only for an - * edge with no surface on its far side (an open collision BSP). The read then takes the byte - * before the array: zero, or a value adopted as next_surface_index == NONE, stops at the - * edge; with the breakable bit set and broken surfaces not ignored, TAG_BLOCK_GET_ELEMENT - * receives NONE and its index assertion halts. No structure BSP in the shipped - * 01.10.12.2276 maps has an open edge (0 of 2,066,607 edges in 82 BSPs). - */ + /* (an open edge, with no surface beyond it, is in no shipped map) */ while (TRUE) { long next_surface_index = NONE; @@ -404,14 +394,7 @@ boolean structure_test_line2d( if (cross_product2d(&p0p1, &p0e0) > 0.0f && cross_product2d(&p0e1, &p0p1) > 0.0f) { - /* BUG (preserved for exact matching): January loads - * pathfinding_surfaces[next_surface_index] (+0x207..+0x20b) before any NONE test, as - * does the later /Od build (0x4cee00 +0x330..+0x339). next_surface_index is NONE only - * for an open edge; the byte before the array then decides: walkable bit clear reports - * a collision at this edge; set, NONE reaches TAG_BLOCK_GET_ELEMENT (below, or at the - * top of the surface loop), whose index assertion halts. No structure BSP in the - * shipped 01.10.12.2276 maps has an open edge (0 of 2,066,607 edges in 82 BSPs). - */ + /* (an open edge, with no surface beyond it, is in no shipped map) */ long next_surface_index = edge->surface_indices[!on_right_side]; /* port: (path_pathfinding_surface: a map's index past the pathfinding surfaces is no walkable one) */ @@ -743,17 +726,8 @@ boolean structure_test_pill2d( best_result = NULL; } - /* BUG (preserved for exact matching): a side whose p0 surface index is NONE gets only - * its collision flag, so when p0_surface_index is NONE the copy below returns - * left_result's point, surface_index, edge_index and t unassigned (January 0x452500 - * writes only the byte [ebp-0x4c] at +0x189 and copies seven dwords from [ebp-0x4c] - * at +0x24f..+0x25a), and the function returns FALSE. actor_move_try_evasion_vector - * passes the actor's pathfinding surface as p0 and, on a FALSE return, reads - * result->point.z to accept or reject the evasion point; actor_find_pathfinding_location - * leaves that surface NONE for a non-flying actor when no ground surface is found or - * when the actor's vehicle is not a ground vehicle. A runtime occurrence was not - * traced. A corrected build should fill both side results before selecting. - * Source-policy approval pending (2026-09-27 audit). */ + /* (as the original game, kept: a side whose first surface is NONE leaves the rest of its + result unset) */ if (!best_result || distance_squared2d((real_point2d const *)&best_result->point, p1) < radius * radius) { diff --git a/source/bitmaps/bitmap_drawing.c b/source/bitmaps/bitmap_drawing.c index 238d008a..99e0ef9c 100644 --- a/source/bitmaps/bitmap_drawing.c +++ b/source/bitmaps/bitmap_drawing.c @@ -379,8 +379,8 @@ void bitmap_fill_rectangle( short alpha, inverse_alpha; short x_minimum, x_maximum, y_minimum, y_maximum; short width, y; - short mode; - long pixel; + short mode = 0; + long pixel = 0; match_assert( "c:\\halo\\SOURCE\\bitmaps\\bitmap_drawing.c", @@ -402,10 +402,6 @@ void bitmap_fill_rectangle( mode = (alpha != 255) ? _bitmap_fill_blend_r5g6b5 : _bitmap_fill_write_16bit; break; - /* mode and pixel are left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert( "c:\\halo\\SOURCE\\bitmaps\\bitmap_drawing.c", @@ -1050,8 +1046,8 @@ void bitmap_draw_line( short y_maximum = destination->height; short alpha = color >> 24; short inverse_alpha = 255 - alpha; - short mode; - long pixel; + short mode = 0; + long pixel = 0; point2d point0, point1; struct bitmap_line_state state; @@ -1079,10 +1075,6 @@ void bitmap_draw_line( mode = (alpha != 255) ? _bitmap_pixel_blend_a8r8g8b8 : _bitmap_pixel_write_32bit; break; - /* mode and pixel are left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert( "c:\\halo\\SOURCE\\bitmaps\\bitmap_drawing.c", diff --git a/source/bitmaps/bitmap_extract.c b/source/bitmaps/bitmap_extract.c index 052ef7a7..8b99551b 100644 --- a/source/bitmaps/bitmap_extract.c +++ b/source/bitmaps/bitmap_extract.c @@ -1363,7 +1363,7 @@ struct bitmap_data *extract_build_debug_plate( struct bitmap_data *converted_bitmap = NULL; struct bitmap_data *debug_bitmap = NULL; short mipmap_index; - short slice_count; + short slice_count = 0; switch (bitmap->type) { @@ -1448,10 +1448,6 @@ struct bitmap_data *extract_build_debug_plate( case _bitmap_type_cube_map: slice_count = 6; break; - /* slice_count is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert( "c:\\halo\\SOURCE\\bitmaps\\bitmap_extract.c", @@ -1510,7 +1506,7 @@ struct bitmap_data *extract_build_debug_plate( if (slice_bitmap && slice_bitmap->base_address) { - short mipmap_slice_count; + short mipmap_slice_count = 0; short slice_index; switch (converted_bitmap->type) @@ -1524,10 +1520,6 @@ struct bitmap_data *extract_build_debug_plate( case _bitmap_type_cube_map: mipmap_slice_count = 6; break; - /* mipmap_slice_count is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert( "c:\\halo\\SOURCE\\bitmaps\\bitmap_extract.c", diff --git a/source/bitmaps/bitmap_utilities.c b/source/bitmaps/bitmap_utilities.c index f4e0662e..b95bca02 100644 --- a/source/bitmaps/bitmap_utilities.c +++ b/source/bitmaps/bitmap_utilities.c @@ -1178,11 +1178,7 @@ static void bitmap_cm_smooth( match_assert("c:\\halo\\SOURCE\\bitmaps\\bitmap_utilities.c", 0x354, bitmap->type==_bitmap_type_cube_map); match_assert("c:\\halo\\SOURCE\\bitmaps\\bitmap_utilities.c", 0x355, filter_coefficients); - /* BUG (preserved for exact matching): January passes a newline as an - * unused vararg even though the format has no conversion. A corrected - * build should append the newline to the format string instead. - */ - fprintf(stdout, "### WARNING tried to smooth a cube map", "\r\n"); + fprintf(stdout, "### WARNING tried to smooth a cube map\r\n"); fflush(stdout); return; @@ -1365,8 +1361,7 @@ static void bitmap_3d_sharpen( match_assert("c:\\halo\\SOURCE\\bitmaps\\bitmap_utilities.c", 0x3E4, positive_table); match_assert("c:\\halo\\SOURCE\\bitmaps\\bitmap_utilities.c", 0x3E5, negative_table); - /* BUG (preserved for exact matching): see bitmap_cm_smooth. */ - fprintf(stdout, "### WARNING tried to sharpen a 3d bitmap", "\r\n"); + fprintf(stdout, "### WARNING tried to sharpen a 3d bitmap\r\n"); fflush(stdout); return; @@ -1383,8 +1378,7 @@ static void bitmap_cm_sharpen( match_assert("c:\\halo\\SOURCE\\bitmaps\\bitmap_utilities.c", 0x3F5, positive_table); match_assert("c:\\halo\\SOURCE\\bitmaps\\bitmap_utilities.c", 0x3F6, negative_table); - /* BUG (preserved for exact matching): see bitmap_cm_smooth. */ - fprintf(stdout, "### WARNING tried to sharpen a cube map", "\r\n"); + fprintf(stdout, "### WARNING tried to sharpen a cube map\r\n"); fflush(stdout); return; @@ -2453,10 +2447,6 @@ union rgb_color *hsv_color_to_rgb_color( } } - /* The sector switch has no default. Not reached unassigned: for a word hue, - * scaled_hue = hue * 6 / 65536 is at most 393210 / 65536 < 6 (exact in real), so - * sector is 0..5 and each of those cases assigns red, green and blue. Source-policy - * approval pending (2026-09-27 audit). */ rgb->red = (word)(long)(red * 65535.0f); rgb->green = (word)(long)(green * 65535.0f); rgb->blue = (word)(long)(blue * 65535.0f); diff --git a/source/bitmaps/bitmaps.c b/source/bitmaps/bitmaps.c index a56fe6a9..18464e2a 100644 --- a/source/bitmaps/bitmaps.c +++ b/source/bitmaps/bitmaps.c @@ -1128,18 +1128,6 @@ pixel32 bitmap_2d_get_pixel( x &= 3; y &= 3; - /* BUG (original, preserved for exact matching; admitted narrowly by the owner 2026-09-27 - no general - * varargs exception): both messages end "lod=%f" but January passes the short mipmap_index there - * (0x46c140 +0x2d2 movsx eax,[ebp-0x10] / +0x2e2 push eax, and +0x363 / +0x373; the same defect occurs - * in the August and September 2001 builds and the later /Od build). Stack at the csprintf call: six ints for - * the %d conversions, then mipmap_index, then display_assert's __FILE__ pointer pushed just before it. - * %f is the last conversion, so it consumes exactly those two pushed dwords (no later argument shifts). - * Passing an int where the format reads a double is undefined behaviour in C; the bound below is a - * property of this compiler, CRT and linked image, not of the source: for every mipmap_index value with - * January's __FILE__ address as the high dword the double is a tiny positive normal and the halt message - * shows "lod=0.000000" (at most 182 characters plus NUL in the 256-byte buffer; January's formatter has no x87 code). - * display_assert then returns into an unconditional system_exit, which never returns (halt_and_catch_fire). - * A corrected build passes lod. */ match_vassert( "c:\\halo\\SOURCE\\bitmaps\\bitmaps.c", 0x2A0, @@ -1153,7 +1141,7 @@ pixel32 bitmap_2d_get_pixel( (short)bitmap->mipmap_count, fast_ftol((real)width * point->x - 0.5f) % width, fast_ftol((real)height * point->y - 0.5f) % height, - mipmap_index)); + lod)); match_vassert( "c:\\halo\\SOURCE\\bitmaps\\bitmaps.c", 0x2A9, @@ -1167,7 +1155,7 @@ pixel32 bitmap_2d_get_pixel( (short)bitmap->mipmap_count, fast_ftol((real)width * point->x - 0.5f) % width, fast_ftol((real)height * point->y - 0.5f) % height, - mipmap_index)); + lod)); switch (bitmap->format) { @@ -1201,15 +1189,10 @@ pixel32 bitmap_2d_get_pixel( 0x2B7, FALSE, "### ERROR unsupported bitmap format"); + pixel = 0; break; } - /* BUG (original, preserved for exact matching; admitted narrowly by the owner 2026-09-27): the - * unsupported-format default arm leaves pixel unassigned and January returns it after the fatal - * assertion (0x46c140 +0x3ed mov eax,[ebp+8]). In this image the assertion is followed by an - * unconditional system_exit, which never returns (halt_and_catch_fire loops or calls _exit on re-entry). - * The uninitialised return expression would read an indeterminate value if the halt returned, but is - * not executed on this path. A corrected build assigns pixel in that arm. */ return pixel; } diff --git a/source/bungie_net/common/thread_win32.c b/source/bungie_net/common/thread_win32.c index 9f437516..088c1e62 100644 --- a/source/bungie_net/common/thread_win32.c +++ b/source/bungie_net/common/thread_win32.c @@ -140,9 +140,16 @@ boolean create_thread( else { CloseHandle(reference->handle); + reference->handle = NULL; + reference->in_use = FALSE; reference = NULL; } } + else if (reference) + { + reference->in_use = FALSE; + reference = NULL; + } *thread_reference = reference; return success; @@ -196,9 +203,14 @@ boolean create_mutex( thread_globals.mutex_index++); reference->handle = CreateMutexA(NULL, FALSE, reference->name); if (reference->handle) + { success = TRUE; + } else + { + reference->in_use = FALSE; reference = NULL; + } } *mutex_reference = reference; diff --git a/source/bungie_net/network/transport_address.c b/source/bungie_net/network/transport_address.c index aa142cfb..98aa8be7 100644 --- a/source/bungie_net/network/transport_address.c +++ b/source/bungie_net/network/transport_address.c @@ -104,11 +104,11 @@ symbols in this file: /* ---------- globals */ -#ifndef HALO_ANDROID /* Mach-O section names differ; the default is .bss anyway */ +#ifndef HALO_ARM64_GUEST /* Mach-O section names differ; the default is .bss anyway */ #pragma bss_seg(".bss") #endif static char transport_address_string[256]; -#ifndef HALO_ANDROID +#ifndef HALO_ARM64_GUEST #pragma bss_seg() #endif diff --git a/source/bungie_net/network/transport_endpoint_winsock.c b/source/bungie_net/network/transport_endpoint_winsock.c index c87e71ab..e815b2a1 100644 --- a/source/bungie_net/network/transport_endpoint_winsock.c +++ b/source/bungie_net/network/transport_endpoint_winsock.c @@ -525,7 +525,17 @@ long write_endpoint( result = send(ep->socket, buffer, length, 0); if (result == INVALID_SOCKET) { - switch (WSAGetLastError()) + /* port: the Winsock error a send failed with, which the transport's + errors don't say (logged once for each different one) */ + static int logged_error; + int code = WSAGetLastError(); + + if (code != 0x2733 && code != logged_error) + { + error(_error_silent, "write_endpoint: Winsock error %d", code); + logged_error = code; + } + switch (code) { case 0x2733: ep->error = _transport_result_operation_would_block; @@ -1224,25 +1234,31 @@ short connect_endpoint( unsigned long timeout = system_milliseconds() + 10 * MILLISECONDS_PER_SECOND; struct timeval timeval; - timeval.tv_sec = 1; - timeval.tv_usec = 0; - do { fd_set writeable; fd_set failed; + long ready; + /* (set each time: select may leave what is left of it) */ + timeval.tv_sec = 1; + timeval.tv_usec = 0; writeable.fd_array[0] = ep->socket; writeable.fd_count = 1; failed.fd_array[0] = ep->socket; failed.fd_count = 1; - /* port: a connect that failed is in the error set (not - writeable, and its would-block left as it was) */ - if (select(1, NULL, &writeable, &failed, &timeval) > 0) + /* port: a failed connect is in the error set, and a select + that times out (0) means the connection is still being made */ + ready = select(1, NULL, &writeable, &failed, &timeval); + if (ready > 0) { error = failed.fd_count ? WSAECONNREFUSED : 0; } + else if (ready == 0) + { + error = WSAEWOULDBLOCK; + } else { error = WSAGetLastError(); @@ -1489,7 +1505,10 @@ struct transport_endpoint *accept_endpoint( ep->flags |= 1; } else + { + closesocket(socket); listening_endpoint->error = _transport_error_out_of_memory; + } } else { diff --git a/source/cache/cache_files.c b/source/cache/cache_files.c index f483c69d..5af67408 100644 --- a/source/cache/cache_files.c +++ b/source/cache/cache_files.c @@ -909,7 +909,8 @@ boolean cache_file_header_verify( header->footer_signature != CACHE_FILE_FOOTER_SIGNATURE || header->file_length < 0 || header->file_length > 0x11600000 || - csstrlen(header->name) > 31) + /* port: its name ends within its field (csstrlen read on past it) */ + !memchr(header->name, 0, sizeof(header->name))) { if (fatal) { diff --git a/source/cache/cache_files_windows.c b/source/cache/cache_files_windows.c index d8c7e176..da92f265 100644 --- a/source/cache/cache_files_windows.c +++ b/source/cache/cache_files_windows.c @@ -917,7 +917,7 @@ short cache_file_read( short cache_files_precache_map_status( real *progress) { - short status; + short status = _cached_map_file_failed; match_assert( "c:\\halo\\SOURCE\\cache\\cache_files_windows.c", @@ -943,10 +943,6 @@ short cache_files_precache_map_status( status = _cached_map_file_success; break; - /* status is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert("c:\\halo\\SOURCE\\cache\\cache_files_windows.c", 1013, FALSE, NULL); break; @@ -1332,10 +1328,6 @@ static short cached_map_files_find_free_map( last_map_file_index = 2; break; - /* first_map_file_index and last_map_file_index are left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ /* port: the type is the map's header's, and a release build's assertion goes on: a type no cache file is for finds none */ default: @@ -1692,7 +1684,13 @@ static struct cached_map_file *cached_map_file_get( static void cached_map_file_invalidate( short map_file_index) { - cached_map_file_get(map_file_index)->file = INVALID_HANDLE_VALUE; + struct cached_map_file *map_file = cached_map_file_get(map_file_index); + + if (map_file->file != INVALID_HANDLE_VALUE) + { + CloseHandle(map_file->file); + } + map_file->file = INVALID_HANDLE_VALUE; return; } diff --git a/source/cache/xbox_texture_cache.c b/source/cache/xbox_texture_cache.c index 82671304..6249b078 100644 --- a/source/cache/xbox_texture_cache.c +++ b/source/cache/xbox_texture_cache.c @@ -621,8 +621,12 @@ static void texture_cache_initialize_hardware_format( (2 << D3DFORMAT_DIMENSION_SHIFT) | D3DFORMAT_BORDERSOURCE_COLOR | D3DFORMAT_DMACHANNEL_A; + /* port: the pitch rounded up, as rasterizer_xbox_bitmap_rebuild_hardware_format + pads the rows (an Xbox map's linear rows are whole multiples of it; a + Custom Edition map's need not be) */ texture->Size = - ((bitmap_mipmap_get_row_pitch(bitmap, 0) / D3DTEXTURE_PITCH_ALIGNMENT - 1) << D3DSIZE_PITCH_SHIFT) | + (((bitmap_mipmap_get_row_pitch(bitmap, 0) + D3DTEXTURE_PITCH_ALIGNMENT - 1) / D3DTEXTURE_PITCH_ALIGNMENT - 1) + << D3DSIZE_PITCH_SHIFT) | ((bitmap->height - 1) << D3DSIZE_HEIGHT_SHIFT) | (bitmap->width - 1); } diff --git a/source/camera/director.c b/source/camera/director.c index b8b3fbf0..024ab976 100644 --- a/source/camera/director.c +++ b/source/camera/director.c @@ -513,10 +513,7 @@ static void director_process_variables( &variables[variable_index]; struct director_variable_instance *instance = &director->debug_variables[variable_index]; - /* BUG (original): January tests the height definition for every - * variable. A corrected build should test definition->has_hyper_scale. - */ - real hyper_scale = variables[_variable_height].has_hyper_scale + real hyper_scale = definition->has_hyper_scale ? director->debug_input_scale : 1.f; real velocity_scale = diff --git a/source/cseries/cseries.c b/source/cseries/cseries.c index 87a713a0..a541e578 100644 --- a/source/cseries/cseries.c +++ b/source/cseries/cseries.c @@ -349,8 +349,10 @@ char *csprintf( va_start(arglist, format); /* port: no longer than the longest a string is (MAXIMUM_STRING_SIZE, - as csstrlen asserts): the caller's buffer's size isn't passed */ - vsnprintf(buffer, MAXIMUM_STRING_SIZE, format, arglist); + as csstrlen asserts): the caller's buffer's size isn't passed. But + temporary, which most callers format into (often with a map's tag + names), is only its own size */ + vsnprintf(buffer, buffer == temporary ? sizeof(temporary) : MAXIMUM_STRING_SIZE, format, arglist); va_end(arglist); return buffer; diff --git a/source/cseries/profile.c b/source/cseries/profile.c index 0ef43da2..e5072923 100644 --- a/source/cseries/profile.c +++ b/source/cseries/profile.c @@ -276,8 +276,14 @@ symbols in this file: #include "main/main.h" #include "game/players.h" +#include #include +#ifdef HALO_PROFILE +#include "profile_console.h" +#include "profile_trace.h" +#endif + /* ---------- constants */ enum @@ -448,6 +454,24 @@ boolean profile_global_enable = FALSE; boolean profile_dump_frames = FALSE; boolean profile_dump_lost_frames = FALSE; +#ifdef HALO_PROFILE +/* port: the profiling build: each section's name in the trace +(port/linux/src/profile_trace.c) by section_index, as struct +profile_section's size is fixed (profile.h's assertions); the frame +timers' names; and whether the first frame has set the recording up */ +static short profile_trace_section_names[MAXIMUM_PROFILE_SECTIONS]; +static struct +{ + int frame; + int game_tick; + int render; + int render_window; + int idle; + int texture; +} profile_trace_timer_names; +static boolean profile_trace_launched; +#endif + /* ---------- public code */ void profile_dump_to_file( @@ -1075,16 +1099,60 @@ void profile_initialize( profile_globals.lost_frame_count = 999; profile_globals.framedump_file = NULL; +#ifdef HALO_PROFILE + profile_trace_timer_names.frame = profile_trace_name("frame"); + profile_trace_timer_names.game_tick = profile_trace_name("game_tick"); + profile_trace_timer_names.render = profile_trace_name("render"); + profile_trace_timer_names.render_window = profile_trace_name("render_window"); + profile_trace_timer_names.idle = profile_trace_name("idle"); + profile_trace_timer_names.texture = profile_trace_name("texture"); + /* (many times a frame: a record each would cost more than the work it + times, so a count and a total a frame) */ + profile_trace_name_aggregate("texture"); + profile_trace_name_aggregate("render_model"); + profile_trace_name_aggregate("memory_dynamic_array_resize"); + profile_trace_name_aggregate("memory_dynamic_array_add_element"); + profile_trace_name_aggregate("memory_dynamic_array_delete_element"); + profile_trace_name_aggregate("item_update"); + profile_trace_name_aggregate("weapon_update"); + profile_trace_name_aggregate("unit_update"); + profile_trace_name_aggregate("biped_update"); + profile_trace_name_aggregate("vehicle_update"); + profile_trace_name_aggregate("projectile_update"); + profile_trace_name_aggregate("render_structure_shadows"); + profile_trace_name_aggregate("render_structure_shadows_draw"); + profile_trace_name_aggregate("render_structure_diffuse_lights"); + profile_trace_name_aggregate("render_structure_diffuse_lights_draw"); + profile_trace_name_aggregate("render_structure_specular_lights"); + profile_trace_name_aggregate("render_structure_specular_lights_draw"); +#endif + return; } void profile_frame_start( void) { +#ifdef HALO_PROFILE + if (!profile_trace_launched) + { + profile_trace_launched = TRUE; + profile_console_launch(); + } + /* (no section is open here: where a recording starts, is cut and + stops) */ + profile_trace_frame_boundary(); + profile_console_frame(); +#endif if (!profile_timebase_ticks) { profile_internal_step(); } +#ifdef HALO_PROFILE + /* (after the step, which rolls up what the sections timed last frame: + the console no longer turns it off mid-frame, console.c) */ + profile_global_enable = profile_trace_recording(); +#endif csmemset(&profile_globals.current_frame, 0, sizeof(profile_globals.current_frame)); @@ -1092,6 +1160,9 @@ void profile_frame_start( profile_globals.current_frame.vertical_blank_index = rasterizer_globals.vertical_blank_index; profile_globals.current_frame.game_tick_count = 0; profile_timesection_begin_now(&profile_globals.current_frame.frame); +#ifdef HALO_PROFILE + profile_trace_begin(profile_trace_timer_names.frame); +#endif return; } @@ -1118,6 +1189,10 @@ void profile_tick_start( timer = &profile_globals.current_frame.game_ticks[profile_globals.current_frame.game_tick_count-1]; profile_timesection_begin_now(timer); +#ifdef HALO_PROFILE + profile_trace_tick(); + profile_trace_begin(profile_trace_timer_names.game_tick); +#endif return; } @@ -1132,6 +1207,9 @@ void profile_tick_end( timer = &profile_globals.current_frame.game_ticks[profile_globals.current_frame.game_tick_count-1]; profile_timesection_end_now(timer); +#ifdef HALO_PROFILE + profile_trace_end(profile_trace_timer_names.game_tick); +#endif return; } @@ -1154,6 +1232,9 @@ void profile_render_window_start( timer = &profile_globals.current_frame.windows[profile_globals.current_frame.window_count-1]; profile_timesection_begin_now(timer); +#ifdef HALO_PROFILE + profile_trace_begin(profile_trace_timer_names.render_window); +#endif return; } @@ -1168,6 +1249,9 @@ void profile_render_window_end( timer = &profile_globals.current_frame.windows[profile_globals.current_frame.window_count-1]; profile_timesection_end_now(timer); +#ifdef HALO_PROFILE + profile_trace_end(profile_trace_timer_names.render_window); +#endif return; } @@ -1177,6 +1261,9 @@ void profile_render_start( { profile_globals.current_frame.window_count = 0; profile_timesection_begin_now(&profile_globals.current_frame.render); +#ifdef HALO_PROFILE + profile_trace_begin(profile_trace_timer_names.render); +#endif return; } @@ -1185,6 +1272,9 @@ void profile_render_end( void) { profile_timesection_end_now(&profile_globals.current_frame.render); +#ifdef HALO_PROFILE + profile_trace_end(profile_trace_timer_names.render); +#endif return; } @@ -1200,6 +1290,9 @@ void profile_texture_start( if (PROFILE_TEXTURE_TIMED()) { profile_timesection_begin_now(&profile_globals.current_frame.texture); +#ifdef HALO_PROFILE + profile_trace_begin(profile_trace_timer_names.texture); +#endif } return; @@ -1211,6 +1304,9 @@ void profile_texture_end( if (PROFILE_TEXTURE_TIMED()) { profile_timesection_end_now(&profile_globals.current_frame.texture); +#ifdef HALO_PROFILE + profile_trace_end(profile_trace_timer_names.texture); +#endif } return; @@ -1220,6 +1316,9 @@ void profile_idle_start( void) { profile_timesection_begin_now(&profile_globals.current_frame.idle); +#ifdef HALO_PROFILE + profile_trace_begin(profile_trace_timer_names.idle); +#endif return; } @@ -1228,6 +1327,9 @@ void profile_idle_end( void) { profile_timesection_end_now(&profile_globals.current_frame.idle); +#ifdef HALO_PROFILE + profile_trace_end(profile_trace_timer_names.idle); +#endif return; } @@ -1249,6 +1351,9 @@ void profile_enter_private( QUERY_TIMEBASE(timebase); section->entry_timebase = timebase; section->frame_call_count++; +#ifdef HALO_PROFILE + profile_trace_begin(profile_trace_section_names[section->section_index]); +#endif return; } @@ -1271,6 +1376,9 @@ void profile_exit_private( QUERY_TIMEBASE(timebase); section->frame_elapsed_timebase += timebase-section->entry_timebase; section->stack_depth = NONE; +#ifdef HALO_PROFILE + profile_trace_end(profile_trace_section_names[section->section_index]); +#endif } else { @@ -1302,6 +1410,26 @@ static void profile_timesection_inherit( return; } +/* (never past maximum_length, and always terminated) */ +static void profile_append( + char *string, + short maximum_length, + char const *format, + ...) +{ + long length = csstrlen(string); + va_list arguments; + + if (length >= maximum_length - 1) + return; + va_start(arguments, format); + _vsnprintf(string + length, maximum_length - length - 1, format, arguments); + va_end(arguments); + string[maximum_length - 1] = 0; + + return; +} + static void profile_describe_frame( struct profile_frame *frame, char *string, @@ -1312,14 +1440,9 @@ static void profile_describe_frame( short game_tick_index; short window_index; - /* BUG (preserved for exact matching): January repeatedly appends with - * maximum_length-strlen without handling CRT truncation or exhaustion. - * A 512-byte dump buffer can be exhausted by the legal 150-tick record. - * A corrected build should bound appends and guarantee NUL termination. - */ csstrcpy(string, ""); - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), + profile_append(string, maximum_length, "frame %5d vbl %5I64d tot%6.2f", frame->frame_index, frame->vertical_blank_index, @@ -1329,113 +1452,113 @@ static void profile_describe_frame( { if (global_frame_rate_throttle) { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "(lost%3d)", frame->lapsed_frames); + profile_append(string, maximum_length, "(lost%3d)", frame->lapsed_frames); } else { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "(free%3d)", frame->lapsed_frames); + profile_append(string, maximum_length, "(free%3d)", frame->lapsed_frames); } } else if (frame->lapsed_msec>0) { if (global_frame_rate_throttle) { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "(l.%3dms)", frame->lapsed_msec); + profile_append(string, maximum_length, "(l.%3dms)", frame->lapsed_msec); } else { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "(f.%3dms)", frame->lapsed_msec); + profile_append(string, maximum_length, "(f.%3dms)", frame->lapsed_msec); } } else { if (frame->lapsed_msec_valid) { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "(slowed) "); + profile_append(string, maximum_length, "(slowed) "); } else { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "(synced) "); + profile_append(string, maximum_length, "(synced) "); } } if (frame->idle.frame_total>0.0f) { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "idle%6.2f ", frame->idle.frame_total); + profile_append(string, maximum_length, "idle%6.2f ", frame->idle.frame_total); } else { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), " ", frame->idle.frame_total); + profile_append(string, maximum_length, " "); } game_tick_display_count = MAX(frame->game_tick_count, 8); - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "game%2d ", frame->game_tick_count); + profile_append(string, maximum_length, "game%2d ", frame->game_tick_count); - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), " ("); + profile_append(string, maximum_length, " ("); for (game_tick_index = 0; game_tick_indexgame_tick_count) { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "%6.2f%s", + profile_append(string, maximum_length, "%6.2f%s", frame->game_ticks[game_tick_index].frame_total, game_tick_indexwindow_count, local_player_count()+1); - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), " render%6.2f", frame->render.total); + profile_append(string, maximum_length, " render%6.2f", frame->render.total); - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), " ("); + profile_append(string, maximum_length, " ("); for (window_index = 0; window_indexwindow_count) { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "%s%6.2f%s", + profile_append(string, maximum_length, "%s%6.2f%s", frame->window_ids[window_index] ? "p" : "n", frame->windows[window_index].frame_total, window_indexstall.frame_total>0.0f) { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "stall%6.2f ", frame->stall.frame_total); + profile_append(string, maximum_length, "stall%6.2f ", frame->stall.frame_total); } else { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), " ", frame->stall.frame_total); + profile_append(string, maximum_length, " "); } if (frame->texture.frame_total>0.0f) { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "tex%6.2f ", frame->texture.frame_total); + profile_append(string, maximum_length, "tex%6.2f ", frame->texture.frame_total); } else { - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), " ", frame->texture.frame_total); + profile_append(string, maximum_length, " "); } - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "r-misc %6.2f ", frame->render.frame_total); + profile_append(string, maximum_length, "r-misc %6.2f ", frame->render.frame_total); - _snprintf(string+csstrlen(string), maximum_length-csstrlen(string), "f-misc %6.2f ", frame->frame.frame_total); + profile_append(string, maximum_length, "f-misc %6.2f ", frame->frame.frame_total); csstrncat(string+csstrlen(string), frame->lapsed_reason, maximum_length-csstrlen(string)); @@ -1476,6 +1599,9 @@ void profile_frame_end( short frame_index; profile_timesection_end_now(&profile_globals.current_frame.frame); +#ifdef HALO_PROFILE + profile_trace_end(profile_trace_timer_names.frame); +#endif match_assert("c:\\halo\\SOURCE\\cseries\\profile.c", 448, (profile_globals.current_frame.game_tick_count >= 0) && (profile_globals.current_frame.game_tick_count <= MAXIMUM_GAME_TICKS_PER_FRAME)); @@ -1661,17 +1787,7 @@ int compare_profile_sections( default: match_assert("c:\\halo\\SOURCE\\cseries\\profile.c", 844, !"unreachable"); - /* BUG (original, preserved for exact matching): this arm leaves result - * unassigned, and January returns it after the fatal assertion: 0x47e840 +0x61 mov eax,[ebp+8] - * reads the dead first-parameter home. The Sept-25-2001 build is identical; the Aug-15-2001 - * build reads its uninitialised [ebp-4] slot the same way. The later /Od+/RTC build attests the - * uninitialised declaration: its single exit calls _RTC_UninitUse("result"). - * The arm is unreachable in defined execution. compare_type is written only by profile_dump, - * after its sort_mode range assertion; January's two profile_dump callers pass 0/1 and 2; and - * each of the NUMBER_OF_PROFILE_SORT_MODES (3) modes has a case above that assigns result. - * Were the arm entered, display_assert returns into an unconditional system_exit, which never - * returns: halt_and_catch_fire loops, or calls exit() on re-entry. So the uninitialised return - * is not executed in January. A corrected build assigns result in this arm. */ + result = 0; break; } } @@ -1740,6 +1856,9 @@ void find_profile_section( section->section_index = profile_globals.section_count; profile_globals.section_count++; profile_globals.sections[section->section_index] = section; +#ifdef HALO_PROFILE + profile_trace_section_names[section->section_index] = (short)profile_trace_name(section->name); +#endif csmemset(section->frame_elapsed_timebase_history, 0, sizeof(section->frame_elapsed_timebase_history)); csmemset(section->frame_call_count_history, 0, sizeof(section->frame_call_count_history)); diff --git a/source/cseries/stack_walk_windows.c b/source/cseries/stack_walk_windows.c index 70c20987..ed43d437 100644 --- a/source/cseries/stack_walk_windows.c +++ b/source/cseries/stack_walk_windows.c @@ -342,7 +342,7 @@ void stack_walk_with_context( for (frame_number = levels_dumped - 1; frame_number >= levels_to_ignore; frame_number--) { -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST /* the call site (the BL before the return address), for llvm-symbolizer --obj=build/android/halo_guest.elf */ unsigned long routine_address = routine_addresses[frame_number] - 4; @@ -479,13 +479,6 @@ int load_symbol_table( { found_symbols_section = TRUE; } - else if (strstr(line, "Timestamp")) - { - /* BUG (preserved for exact matching): January performs the - * timestamp search but ignores whether it succeeds. A corrected - * build should reject a map whose timestamp does not match. */ - strstr(line, timestamp_str); - } } string_storage_size = 0; @@ -523,24 +516,9 @@ int load_symbol_table( goto corrupt_map_file; } - /* BUG (preserved for exact matching): January consumes each of - * these continuation lines without checking whether fgets failed. - * A corrected build should reject EOF before reading line[0] or - * tokenizing the buffer. */ - fgets(line, sizeof(line), map_file); - if (!isspace(line[0])) - { - goto corrupt_map_file; - } - - fgets(line, sizeof(line), map_file); - if (!strstr(line, "Static symbols")) - { - goto corrupt_map_file; - } - - fgets(line, sizeof(line), map_file); - if (!isspace(line[0])) + if (!fgets(line, sizeof(line), map_file) || !isspace(line[0]) || + !fgets(line, sizeof(line), map_file) || !strstr(line, "Static symbols") || + !fgets(line, sizeof(line), map_file) || !isspace(line[0])) { goto corrupt_map_file; } @@ -559,15 +537,13 @@ int load_symbol_table( } symbol_address = strtoul(token, &end_str, 16); - /* BUG (preserved for exact matching): January leaves symbol_name - * unchanged if the continuation entry omits its name token, then - * continues parsing. A corrected build should reject that entry. */ token = strtok(NULL, " \t\n\r"); - if (token) + if (!token) { - strncpy(symbol_name, token, sizeof(symbol_name)-1); - symbol_name[sizeof(symbol_name)-1] = 0; + goto corrupt_map_file; } + strncpy(symbol_name, token, sizeof(symbol_name)-1); + symbol_name[sizeof(symbol_name)-1] = 0; } token = strtok(NULL, " \t\n\r"); @@ -582,11 +558,8 @@ int load_symbol_table( stack_walk_globals.fixup = rva_base - (unsigned long)load_symbol_table; } - /* BUG (preserved for exact matching): January checks only whether - * strtoul assigned an end pointer, then advances five bytes without - * validating the conversion or remaining field width. A corrected - * build should validate both before advancing. */ - if (!end_str) + /* (the address's five columns, " f ", before the object name) */ + if (!end_str || strlen(end_str) < 5) { goto corrupt_map_file; } @@ -696,10 +669,8 @@ finished: sizeof(*symbol_table->symbols), symbol_sort_proc); - /* BUG (preserved for exact matching): January assumes at least one - * nonzero RVA while trimming sentinels. A corrected build should - * stop before number_of_symbols reaches zero. */ - while (symbol_table->symbols[symbol_table->number_of_symbols-1].rva_base==0) + while (symbol_table->number_of_symbols > 0 && + symbol_table->symbols[symbol_table->number_of_symbols-1].rva_base==0) { symbol_table->number_of_symbols--; } @@ -733,9 +704,10 @@ static int symbol_sort_proc( const struct debug_symbol *symbol1 = elem1; const struct debug_symbol *symbol2 = elem2; - /* BUG (preserved for exact matching): January returns 1 when both RVAs - * are zero, violating comparator antisymmetry. A corrected build should - * return 0 for two equal zero-RVA sentinel records. */ + if (symbol1->rva_base == symbol2->rva_base) + { + return 0; + } if (symbol1->rva_base==0 || symbol1->rva_base > symbol2->rva_base) { return 1; @@ -762,7 +734,7 @@ static unsigned long walk_up( if (walk_up_current_frame) { -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST /* an AArch64 frame record: the caller's frame pointer, then the return address, 8 bytes each (the upper halves are zero) */ routine_address = ((unsigned long *)walk_up_current_frame)[2]; diff --git a/source/cutscene/recorded_animation_playback.c b/source/cutscene/recorded_animation_playback.c index 6a795a2b..293b4b90 100644 --- a/source/cutscene/recorded_animation_playback.c +++ b/source/cutscene/recorded_animation_playback.c @@ -415,7 +415,7 @@ boolean recorded_animation_apply_event_stream( byte const *playback_stream_end) { struct animation_event_header const *header; - word time_delta; + word time_delta = 0; word header_size; recorded_animation_apply_proc apply; @@ -466,10 +466,6 @@ boolean recorded_animation_apply_event_stream( time_delta>UNSIGNED_CHAR_MAX); break; - /* time_delta is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_assert( "c:\\halo\\SOURCE\\cutscene\\recorded_animation_playback.c", diff --git a/source/devices/device_controls.c b/source/devices/device_controls.c index e72a8f35..7d5f6747 100644 --- a/source/devices/device_controls.c +++ b/source/devices/device_controls.c @@ -188,10 +188,6 @@ static void control_toggle( case 3: desired_value= definition->control.call_value; break; - /* desired_value is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: display_assert( NULL, diff --git a/source/effects/effects.c b/source/effects/effects.c index d00ab05a..18691f49 100644 --- a/source/effects/effects.c +++ b/source/effects/effects.c @@ -2623,16 +2623,7 @@ static boolean effect_allowed_by_environment( 888, FALSE, NULL); - /* BUG (original, preserved for exact matching): this arm leaves allowed - * unassigned, and January returns it after the fatal assertion: 0x48b160 +0x5f mov al,[ebp-1] - * reads the never-written byte slot. The later /Od+/RTC build attests the uninitialised - * declaration: its single exit calls _RTC_UninitUse("allowed"). - * The read is not executed in January: display_assert returns into an unconditional - * system_exit, which never returns (it jumps to halt_and_catch_fire, which loops, or calls - * _exit on re-entry), and no other path reaches the read. The arm itself is entered only for - * an environment value outside the four cases above, each of which assigns allowed; the - * value comes from effect tag data, so this arm is not proven unreachable. A corrected build - * assigns allowed in this arm. */ + allowed = FALSE; break; } diff --git a/source/effects/material_effects.c b/source/effects/material_effects.c index 47221aa0..acbf4268 100644 --- a/source/effects/material_effects.c +++ b/source/effects/material_effects.c @@ -45,11 +45,11 @@ enum /* ---------- globals */ -#ifndef HALO_ANDROID /* Mach-O section names differ; the default is .bss anyway */ +#ifndef HALO_ARM64_GUEST /* Mach-O section names differ; the default is .bss anyway */ #pragma bss_seg(".bss") #endif boolean debug_material_effects; -#ifndef HALO_ANDROID +#ifndef HALO_ARM64_GUEST #pragma bss_seg() #endif diff --git a/source/effects/particle_systems.c b/source/effects/particle_systems.c index b44abaf1..38c9c72b 100644 --- a/source/effects/particle_systems.c +++ b/source/effects/particle_systems.c @@ -1540,8 +1540,12 @@ static void particle_system_render( } bitmap = bitmap_group_get(state_definition->bitmaps.index); + /* port: rotational sprites use the sequence after the state's. Some + Custom Edition bitmaps don't have one (on foundation), so those + draw from the state's own sequence. */ if (type_definition->complex_sprite_render_mode == - _particle_system_type_complex_sprite_render_mode_rotational) + _particle_system_type_complex_sprite_render_mode_rotational && + state_definition->sequence_index + 1 < bitmap->sequences.count) { sequence_index = state_definition->sequence_index + 1; } diff --git a/source/game/game.c b/source/game/game.c index 83742035..21407601 100644 --- a/source/game/game.c +++ b/source/game/game.c @@ -182,6 +182,10 @@ struct game_options; #include "units/units.h" #include "units/vehicles.h" #include "network_coop.h" /* port: port/linux/game/network_coop.c */ +#ifdef HALO_PROFILE +#include "profile_console.h" /* port: port/linux/game/profile_console.c */ +#endif +#include "profile_sections.h" /* port: port/linux/include/profile_sections.h */ /* network_game_globals.c's */ boolean network_game_distributed_client(void); @@ -248,6 +252,25 @@ char const *global_game_difficulty_level_names[NUMBER_OF_GAME_DIFFICULTY_LEVELS] }; static struct profile_section game_update_section = {"game_update", NONE, TRUE}; +/* port: game_tick's steps, timed in the profiling build */ +PROFILE_SECTION(game_tick_cheats_enforce_section, "game_tick.cheats_enforce") +PROFILE_SECTION(game_tick_remove_quitting_players_section, "game_tick.remove_quitting_players") +PROFILE_SECTION(game_tick_allegiance_section, "game_tick.allegiance") +PROFILE_SECTION(game_tick_units_section, "game_tick.units") +PROFILE_SECTION(game_tick_ai_section, "game_tick.ai") +PROFILE_SECTION(game_tick_actors_drive_section, "game_tick.actors_drive") +PROFILE_SECTION(game_tick_players_before_section, "game_tick.players_before") +PROFILE_SECTION(game_tick_effects_section, "game_tick.effects") +PROFILE_SECTION(game_tick_antennas_section, "game_tick.antennas") +PROFILE_SECTION(game_tick_first_person_section, "game_tick.first_person") +PROFILE_SECTION(game_tick_game_engine_section, "game_tick.game_engine") +PROFILE_SECTION(game_tick_editor_section, "game_tick.editor") +PROFILE_SECTION(game_tick_hs_section, "game_tick.hs") +PROFILE_SECTION(game_tick_recorded_animations_section, "game_tick.recorded_animations") +PROFILE_SECTION(game_tick_objects_section, "game_tick.objects") +PROFILE_SECTION(game_tick_players_after_section, "game_tick.players_after") +PROFILE_SECTION(game_tick_hud_section, "game_tick.hud") +PROFILE_SECTION(game_tick_player_effect_section, "game_tick.player_effect") /* ---------- public code */ @@ -328,45 +351,46 @@ void game_tick( /* port: a client of another's game, the host's rules (its own cheats and game speed, set before it joined too, put back) */ - cheats_network_client_enforce(); - remove_quitting_players_from_game(); - game_allegiance_update(); - units_update(); + profile_scope(game_tick_cheats_enforce_section, cheats_network_client_enforce();) + profile_scope(game_tick_remove_quitting_players_section, remove_quitting_players_from_game();) + profile_scope(game_tick_allegiance_section, game_allegiance_update();) + profile_scope(game_tick_units_section, units_update();) /* (the host's actors drive the host's units, which a client of the distributed netcode has from the host: its own would fight the host's positions, and could place objects of their own) */ if (!network_game_distributed_client()) - ai_update(); + profile_scope(game_tick_ai_section, ai_update();) else - network_actors_drive(); - players_update_before_game(); + profile_scope(game_tick_actors_drive_section, network_actors_drive();) + profile_scope(game_tick_players_before_section, players_update_before_game();) seconds_per_tick = game_globals->players_are_double_speed ? 1.0f / (2 * TICKS_PER_SECOND) : 1.0f / TICKS_PER_SECOND; - effects_update(seconds_per_tick); + profile_scope(game_tick_effects_section, effects_update(seconds_per_tick);) /* An antenna's chain is a simulation of the Xbox's own step, an update a tick: stepped a frame at a time, its springs, its carry and its points' physics act against the frames' own movement of it, and a vehicle at speed swings it far wider than a tick's step does. The frames between the ticks are drawn from what each tick leaves (antenna.c). */ - antennas_update(seconds_per_tick); - lock_global_random_seed(); - rumble_update(); - first_person_weapons_update(); - unlock_global_random_seed(); - game_engine_update(); - editor_update(); + profile_scope(game_tick_antennas_section, antennas_update(seconds_per_tick);) + profile_scope(game_tick_first_person_section, + lock_global_random_seed(); + rumble_update(); + first_person_weapons_update(); + unlock_global_random_seed();) + profile_scope(game_tick_game_engine_section, game_engine_update();) + profile_scope(game_tick_editor_section, editor_update();) /* port: in network co-op only the host runs the scripts. A client running them would place the map's actors and objects a second time and make decisions that belong to the host. */ if (!(network_game_distributed_client() && network_coop_active())) - hs_update(); - recorded_animations_update(); - objects_update(); - players_update_after_game(); - hud_update(); - player_effect_update(); + profile_scope(game_tick_hs_section, hs_update();) + profile_scope(game_tick_recorded_animations_section, recorded_animations_update();) + profile_scope(game_tick_objects_section, objects_update();) + profile_scope(game_tick_players_after_section, players_update_after_game();) + profile_scope(game_tick_hud_section, hud_update();) + profile_scope(game_tick_player_effect_section, player_effect_update();) /* port: and at its end, before the frame draws the lights */ game_state_check_data_arrays(); lights_port_recover(); @@ -584,6 +608,12 @@ boolean game_is_cooperative( boolean game_load( struct game_options *options) { +#ifdef HALO_PROFILE + /* port: the profiling build's recording learns of the map (every map + load passes here: main_new_map's and a network game's, + network_game_manager.c) */ + profile_console_map_loaded(options->map_name); +#endif match_assert( "c:\\halo\\SOURCE\\game\\game.c", 0x192, diff --git a/source/game/game_engine.c b/source/game/game_engine.c index 0016e819..84bb011c 100644 --- a/source/game/game_engine.c +++ b/source/game/game_engine.c @@ -569,6 +569,8 @@ symbols in this file: #include "networking/network_server_manager.h" #include "networking/network_game_manager.h" #include "network_coop.h" /* port: port/linux/game/network_coop.c */ +#include "network_votekick.h" /* port: port/linux/game/network_votekick.c */ +#include "network_voice.h" /* port: port/linux/game/network_voice.c */ /* (network_server_manager_internal.h's: the host's game record) */ struct network_game *network_game_server_get_game(struct network_game_server *server); #include "objects.h" @@ -1150,7 +1152,7 @@ static void game_engine_generate_title_string( usnprintf( life_string, NUMBEROF(life_string), - format_string, + ustring_format_checked(format_string, "d"), remaining_lives); life_string[NUMBEROF(life_string) - 1] = 0; secondary_string = life_string; @@ -1292,7 +1294,7 @@ static void game_engine_generate_title_string( usnprintf( title_string, 80, - format_string, + ustring_format_checked(format_string, "sss"), team0_name, team1_name, secondary_string); @@ -1312,7 +1314,7 @@ static void game_engine_generate_title_string( usnprintf( title_string, 80, - format_string, + ustring_format_checked(format_string, "sss"), team1_name, team0_name, secondary_string); @@ -1332,7 +1334,7 @@ static void game_engine_generate_title_string( usnprintf( title_string, 80, - format_string, + ustring_format_checked(format_string, "ss"), team1_name, secondary_string); } @@ -1360,7 +1362,7 @@ static void game_engine_generate_title_string( usnprintf( title_string, 80, - format_string, + ustring_format_checked(format_string, "sss"), get_place_string(&entry), score_string, secondary_string); @@ -1381,7 +1383,7 @@ static void game_engine_generate_title_string( usnprintf( title_string, 80, - format_string, + ustring_format_checked(format_string, "sss"), get_place_string(&entry), score_string, secondary_string); @@ -1862,15 +1864,174 @@ static boolean scoreboard_team_columns( return (boolean)setting; } +/* a network game's scoreboard picks players with the mouse +(halo_scoreboard_pointer_update: a right click frees it): a player's name +picked opens a menu of what may be done to them, the players' vote to kick +them (network_votekick.c), muting their voice (network_voice.c), and the +host's Kick and Ban, Ban picked twice */ +enum +{ + _scoreboard_item_vote, + _scoreboard_item_mute, + _scoreboard_item_kick, + _scoreboard_item_ban, + _scoreboard_item_cancel, + NUMBER_OF_SCOREBOARD_ITEMS, + + /* the menu's width (the scoreboard's text, before it is scaled) */ + SCOREBOARD_MENU_WIDTH = 220, +}; + +/* the menu: the player it is for (NONE: none open), the row under whose +name it opens (from the title's), that name's left, and whether Ban was +picked once */ +static struct +{ + long player_index; + long row_index; + short left; + boolean ban_picked; +} scoreboard_menu = { NONE, 0, 0, FALSE }; + /* the scoreboard closed: its scroll forgotten, the wheel the weapons' again */ static void game_engine_scoreboard_closed( void) { if (scoreboard_open) { + struct halo_ui_pointer pointer; + scoreboard_open = FALSE; platform_scoreboard_scroll(FALSE, NULL, NULL); + halo_scoreboard_pointer_update(FALSE, &pointer); } + scoreboard_menu.player_index = NONE; +} + +/* the menu's items for the player: whether each is offered, and what it says */ +static void scoreboard_menu_items( + wchar_t (*labels)[48], + boolean *offered) +{ + struct network_votekick_status status; + short item; + + for (item = 0; item < NUMBER_OF_SCOREBOARD_ITEMS; item++) + { + offered[item] = FALSE; + labels[item][0] = 0; + } + offered[_scoreboard_item_vote] = TRUE; + if (network_votekick_get_status(&status) && + status.player_index == DATUM_INDEX_TO_ABSOLUTE_INDEX(scoreboard_menu.player_index)) + { + usprintf(labels[_scoreboard_item_vote], status.voted ? L"Voted to kick (%d of %d)" : + L"Vote to kick (%d of %d)", status.votes, status.needed); + } + else + { + usprintf(labels[_scoreboard_item_vote], L"Start a vote to kick"); + } + /* (its machine's voice: this machine's own choice) */ + { + struct player_datum *player = player_try_and_get(scoreboard_menu.player_index); + + if (player && network_voice_available()) + { + offered[_scoreboard_item_mute] = TRUE; + usprintf(labels[_scoreboard_item_mute], + network_voice_machine_muted(player->network_player_data.machine_index) ? L"Unmute voice" : + L"Mute voice"); + } + } + if (network_votekick_host()) + { + offered[_scoreboard_item_kick] = TRUE; + offered[_scoreboard_item_ban] = TRUE; + usprintf(labels[_scoreboard_item_kick], L"Kick"); + usprintf(labels[_scoreboard_item_ban], scoreboard_menu.ban_picked ? L"Ban: pick again to ban" : L"Ban"); + } + offered[_scoreboard_item_cancel] = TRUE; + usprintf(labels[_scoreboard_item_cancel], L"Cancel"); +} + +/* the menu's first row (from the title's): under the name, or above it when +there is no room below (last_row: the screen's) */ +static long scoreboard_menu_first_row( + short item_count, + long last_row) +{ + if (scoreboard_menu.row_index + 1 + item_count <= last_row) + return scoreboard_menu.row_index + 1; + return MAX(scoreboard_menu.row_index - item_count, 0); +} + +/* a rectangle of the scoreboard's text (laid out at full size, from left +and the row from the title's) as it is drawn: scaled about the title's top +left (bounds_left, top) */ +static void scoreboard_rectangle( + rectangle2d *rectangle, + short bounds_left, + short top, + long line_height, + short left, + short width, + long row_index, + long row_count) +{ + rectangle->x0 = (short)(bounds_left + (left - bounds_left) * SCOREBOARD_SCALE); + rectangle->x1 = (short)(rectangle->x0 + width * SCOREBOARD_SCALE); + rectangle->y0 = (short)(top + row_index * line_height * SCOREBOARD_SCALE); + rectangle->y1 = (short)(top + (row_index + row_count) * line_height * SCOREBOARD_SCALE); +} + +static boolean scoreboard_point_in( + rectangle2d const *rectangle, + short x, + short y) +{ + return x >= rectangle->x0 && x < rectangle->x1 && y >= rectangle->y0 && y < rectangle->y1; +} + +/* the item picked, done */ +static void scoreboard_menu_pick( + short item) +{ + short player_index = (short)DATUM_INDEX_TO_ABSOLUTE_INDEX(scoreboard_menu.player_index); + + switch (item) + { + case _scoreboard_item_vote: + network_votekick_request(player_index); + break; + case _scoreboard_item_mute: + { + struct player_datum *player = player_try_and_get(scoreboard_menu.player_index); + + if (player) + { + long machine_index = player->network_player_data.machine_index; + + network_voice_mute_machine(machine_index, !network_voice_machine_muted(machine_index)); + } + break; + } + case _scoreboard_item_kick: + network_votekick_host_kick(player_index, FALSE); + break; + case _scoreboard_item_ban: + /* (a ban lasts: picked twice) */ + if (!scoreboard_menu.ban_picked) + { + scoreboard_menu.ban_picked = TRUE; + return; + } + network_votekick_host_kick(player_index, TRUE); + break; + default: + break; + } + scoreboard_menu.player_index = NONE; } static void game_engine_rasterize_scoreboard( @@ -1910,6 +2071,24 @@ static void game_engine_rasterize_scoreboard( short top; wchar_t *column_name; wchar_t *score_name; + /* port: the pointer (1: picking; -1: none on this platform), where it + is in the drawing's coordinates, and whether its click was taken */ + struct halo_ui_pointer pointer; + int pointer_state; + short pointer_x; + short pointer_y; + short click_x; + short click_y; + boolean clicked; + struct network_votekick_status vote; + boolean voting; + long extra_rows; + long next_row; + wchar_t menu_labels[NUMBER_OF_SCOREBOARD_ITEMS][48]; + boolean menu_offered[NUMBER_OF_SCOREBOARD_ITEMS]; + short menu_item_count = 0; + long menu_first_row = 0; + long last_row; if (font_index == NONE) return; @@ -1924,8 +2103,13 @@ static void game_engine_rasterize_scoreboard( /* (laid out at full size, then drawn scaled about the title's top left: the screen holds 1/SCOREBOARD_SCALE as much) */ width = (short)((bounds.x1 - bounds.x0) / SCOREBOARD_SCALE); + /* (port: a network game's lines beneath, the vote running and how to + pick a player, have their rows) */ + pointer_state = halo_scoreboard_pointer_update(network && network_votekick_available(), &pointer); + voting = network && network_votekick_get_status(&vote); + extra_rows = (voting ? 1 : 0) + (network && pointer_state >= 0 ? 1 : 0); rows = (long)((bounds.y1 - SCOREBOARD_LAYOUT_TOP_ROWS * line_height) / SCOREBOARD_SCALE / line_height) - 2 - - SCOREBOARD_BOTTOM_ROWS; + SCOREBOARD_BOTTOM_ROWS - extra_rows; rows = MAX(rows, 1); if (campaign) { @@ -1990,6 +2174,7 @@ static void game_engine_rasterize_scoreboard( shown_rows = MIN(rows, total); if (total > page) shown_rows++; + shown_rows += extra_rows; { real height = (2 + shown_rows) * line_height * SCOREBOARD_SCALE; @@ -2018,6 +2203,50 @@ static void game_engine_rasterize_scoreboard( } rasterizer_text_set_scale(SCOREBOARD_SCALE, (real)bounds.x0, (real)top); + /* port: the pointer's click, on the menu's items (where they are + drawn), else on a player's row (below); one elsewhere closes the menu */ + pointer_x = (short)(pointer.x - render.camera.viewport_bounds.x0); + pointer_y = (short)(pointer.y - render.camera.viewport_bounds.y0); + click_x = (short)(pointer.click_x - render.camera.viewport_bounds.x0); + click_y = (short)(pointer.click_y - render.camera.viewport_bounds.y0); + clicked = pointer_state > 0 && pointer.left_clicks > 0; + /* (the menu opens below its row, beyond the scoreboard if need be, but + above the motion sensor's rows) */ + last_row = (long)((bounds.y1 - top) / (line_height * SCOREBOARD_SCALE)) - SCOREBOARD_BOTTOM_ROWS; + if (pointer_state <= 0 || (scoreboard_menu.player_index != NONE && !player_try_and_get(scoreboard_menu.player_index))) + scoreboard_menu.player_index = NONE; + if (scoreboard_menu.player_index != NONE) + { + short item; + + scoreboard_menu_items(menu_labels, menu_offered); + for (item = 0; item < NUMBER_OF_SCOREBOARD_ITEMS; item++) + menu_item_count += menu_offered[item] ? 1 : 0; + menu_first_row = scoreboard_menu_first_row(menu_item_count, last_row); + if (clicked) + { + rectangle2d rectangle; + short shown = 0; + + for (item = 0; item < NUMBER_OF_SCOREBOARD_ITEMS && clicked; item++) + { + if (!menu_offered[item]) + continue; + scoreboard_rectangle(&rectangle, bounds.x0, top, line_height, scoreboard_menu.left, + SCOREBOARD_MENU_WIDTH, menu_first_row + shown++, 1); + if (scoreboard_point_in(&rectangle, click_x, click_y)) + { + scoreboard_menu_pick(item); + clicked = FALSE; + } + } + if (clicked) + scoreboard_menu.player_index = NONE; + else if (scoreboard_menu.player_index != NONE) + scoreboard_menu_items(menu_labels, menu_offered); + } + } + team_colors[0].alpha = alpha; team_colors[0].red = 0.6f; team_colors[0].green = 0.3f; @@ -2041,7 +2270,10 @@ static void game_engine_rasterize_scoreboard( score_string[0] = 0; if (!campaign) game_engine->format_score_name(score_string); - usprintf(row_string, L"\t%s\t%s\t%s\t%s", column_name, score_name, score_string, network ? L"Ping" : L""); + /* port: bounded (the map's column names) */ + usnprintf(row_string, NUMBEROF(row_string), L"\t%s\t%s\t%s\t%s", column_name, score_name, score_string, + network ? L"Ping" : L""); + row_string[NUMBEROF(row_string) - 1] = 0; { long column; @@ -2108,14 +2340,78 @@ static void game_engine_rasterize_scoreboard( else usprintf(ping_string, L"%ld", ping); } - usprintf( + usnprintf( row_string, + NUMBEROF(row_string), L"\t%s\t%s\t%s\t%s", campaign ? L"" : get_place_string(entry), player->name, status_string, ping_string); + row_string[NUMBEROF(row_string) - 1] = 0; row_color = has_teams ? &team_colors[PIN(player->team_index, 0, 1)] : &color; + /* port: a player talking (or muted) in voice chat, its speaker just + right of the name */ + if (network && (network_voice_machine_speaking(player->network_player_data.machine_index) || + network_voice_machine_muted(player->network_player_data.machine_index))) + { + rectangle2d icon; + rectangle2d text; + short row_left = (short)(left + column * (SCOREBOARD_COLUMN_WIDTH + SCOREBOARD_COLUMN_GAP)); + short size; + short middle; + + rectangle2d ink; + rectangle2d cursor; + short name_end; + + /* (after the name's last letter, centred on its capitals: both + measured as the row is laid out, the name at its column's tab + stop, then scaled as it is drawn, as scoreboard_rectangle has it; + within the name's column) */ + text.x0 = (short)(row_left + SCOREBOARD_PLACE_WIDTH); + text.x1 = (short)(text.x0 + SCOREBOARD_NAME_WIDTH); + text.y0 = (short)(top + (2 + row) * line_height); + text.y1 = (short)(text.y0 + line_height); + draw_string_set_draw_mode(font_index, NONE, 0, 0, row_color); + draw_unicode_string_compute_bounds(&text, player->name, &ink, &cursor); + middle = (short)(top + (draw_unicode_string_capital_middle(&text, player->name) - top) * SCOREBOARD_SCALE); + scoreboard_rectangle(&icon, bounds.x0, top, line_height, text.x0, SCOREBOARD_NAME_WIDTH, 2 + row, 1); + size = (short)((icon.y1 - icon.y0) * 2 / 3); + name_end = (short)(bounds.x0 + (MAX(ink.x1, text.x0) - bounds.x0) * SCOREBOARD_SCALE); + icon.x0 = (short)MIN(name_end + 4, icon.x1 - size); + icon.x1 = (short)(icon.x0 + size); + icon.y0 = (short)(middle - size / 2); + icon.y1 = (short)(icon.y0 + size); + network_voice_draw_icon(&icon, network_voice_machine_muted(player->network_player_data.machine_index), + alpha); + } + /* port: another machine's player, picked with the pointer: lit + under it (not under the menu), and its menu opened when clicked */ + if (pointer_state > 0 && player->local_player_index == NONE) + { + rectangle2d rectangle; + rectangle2d menu_rectangle; + short row_left = (short)(left + column * (SCOREBOARD_COLUMN_WIDTH + SCOREBOARD_COLUMN_GAP)); + + scoreboard_rectangle(&rectangle, bounds.x0, top, line_height, row_left, SCOREBOARD_COLUMN_WIDTH, + 2 + row, 1); + scoreboard_rectangle(&menu_rectangle, bounds.x0, top, line_height, scoreboard_menu.left, + SCOREBOARD_MENU_WIDTH, menu_first_row, menu_item_count); + if (scoreboard_point_in(&rectangle, pointer_x, pointer_y) && + (scoreboard_menu.player_index == NONE || !scoreboard_point_in(&menu_rectangle, pointer_x, pointer_y))) + { + draw_quad(&rectangle, ((pixel32)(long)(80 * PIN(alpha, 0.0f, 1.0f)) << 24) | 0x00FFFFFF); + } + if (clicked && scoreboard_point_in(&rectangle, click_x, click_y)) + { + scoreboard_menu.player_index = entry->player_index; + scoreboard_menu.row_index = 2 + row; + scoreboard_menu.left = (short)(row_left + SCOREBOARD_PLACE_WIDTH); + scoreboard_menu.ban_picked = FALSE; + clicked = FALSE; + } + } scoreboard_draw_row( row_string, player_index == entry->player_index, @@ -2136,6 +2432,74 @@ static void game_engine_rasterize_scoreboard( usprintf(row_string, L"%ld-%ld of %ld (Page Up / Page Down, mouse wheel)", first, last, total); scoreboard_draw_row(row_string, FALSE, &color, 2 + rows, top, left, FALSE); } + /* port: the vote running, and how to pick a player */ + next_row = 2 + MIN(rows, total) + (total > page ? 1 : 0); + if (voting) + { + struct data_iterator iterator; + struct player_datum *target; + wchar_t const *name = L"?"; + wchar_t const *note = vote.voted ? L" (you voted)" : vote.may_vote ? L"" : L" (too new to vote)"; + + data_iterator_new(&iterator, player_data); + while ((target = (struct player_datum *)data_iterator_next(&iterator)) != NULL) + { + if (DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index) == vote.player_index) + { + name = target->name; + if (target->local_player_index != NONE) + note = L" (against you)"; + } + } + color.alpha = alpha; + color.red = 1.0f; + color.green = 0.75f; + color.blue = 0.3f; + usprintf(row_string, L"Vote to kick %s: %d of %d, %d s left%s", name, vote.votes, vote.needed, + vote.seconds_left, note); + scoreboard_draw_row(row_string, FALSE, &color, next_row++, top, left, FALSE); + } + if (network && pointer_state >= 0) + { + color.alpha = alpha; + color.red = color.green = color.blue = 0.6f; + if (pointer_state == 0) + usprintf(row_string, L"Right-click to pick a player"); + else + usprintf(row_string, L"Pick a player (right-click to aim)"); + scoreboard_draw_row(row_string, FALSE, &color, next_row++, top, left, FALSE); + } + /* port: the picked player's menu, over the rows, its item under the + pointer lit */ + if (scoreboard_menu.player_index != NONE && menu_item_count > 0) + { + rectangle2d rectangle; + short item; + short shown = 0; + long panel_alpha = (long)(255 * PIN(alpha, 0.0f, 1.0f)); + + scoreboard_rectangle(&rectangle, bounds.x0, top, line_height, scoreboard_menu.left, SCOREBOARD_MENU_WIDTH, + menu_first_row, menu_item_count); + draw_quad(&rectangle, ((pixel32)panel_alpha << 24) | 0x00202830); + for (item = 0; item < NUMBER_OF_SCOREBOARD_ITEMS; item++) + { + if (!menu_offered[item]) + continue; + scoreboard_rectangle(&rectangle, bounds.x0, top, line_height, scoreboard_menu.left, + SCOREBOARD_MENU_WIDTH, menu_first_row + shown, 1); + if (scoreboard_point_in(&rectangle, pointer_x, pointer_y)) + draw_quad(&rectangle, ((pixel32)(long)(90 * PIN(alpha, 0.0f, 1.0f)) << 24) | 0x00FFFFFF); + color.alpha = alpha; + color.red = color.green = color.blue = 0.9f; + if (item == _scoreboard_item_ban) + { + color.green = color.blue = 0.45f; + } + scoreboard_draw_row(menu_labels[item], FALSE, &color, menu_first_row + shown, top, + (short)(scoreboard_menu.left + 6), FALSE); + shown++; + } + } rasterizer_text_set_scale(1.0f, 0.0f, 0.0f); return; @@ -2200,7 +2564,8 @@ static void game_engine_rasterize_in_game_score( score_name = L""; game_engine->format_score_name(score_string); - usprintf(row_string, L"\t%s\t%s\t%s", column_name, score_name, score_string); + usnprintf(row_string, NUMBEROF(row_string), L"\t%s\t%s\t%s", column_name, score_name, score_string); + row_string[NUMBEROF(row_string) - 1] = 0; rasterize_in_game_score_draw_line(row_string, FALSE, &color, 1); for (entry_index = 0; entry_index < entry_count; entry_index++) @@ -2257,12 +2622,14 @@ static void game_engine_rasterize_in_game_score( place_string = get_place_string(&entries[entry_index]); - usprintf( + usnprintf( row_string, + NUMBEROF(row_string), L"\t%s\t%s\t%s", place_string, player->name, status_string); + row_string[NUMBEROF(row_string) - 1] = 0; if (has_teams) row_color = &team_colors[PIN(player->team_index, 0, 1)]; @@ -2401,7 +2768,7 @@ void game_engine_post_rasterize_post_game( usnprintf( row_string, NUMBEROF(row_string), - team_formats[team_index], + ustring_format_checked(team_formats[team_index], "s"), score_string); row_string[NUMBEROF(row_string) - 1] = 0; drawline(row_string, team_row + 4, 0); @@ -3522,6 +3889,95 @@ void game_engine_rasterize_message( return; } +/* port: who is talking in voice chat (network_voice.c), down the view's +left from below its middle: each machine's first player's name after its +speaker (this machine's own too, as it talks), VOICE_SPEAKERS_SCALE times +the HUD's text, in the colours of the names above players' heads (an ally's +or an enemy's: hud_player_name_color; in co-op every player an ally), this +machine's own white; while the scores are hidden */ +#define VOICE_SPEAKERS_SCALE 0.8f + +static void game_engine_rasterize_voice_speakers( + void) +{ + enum + { + MAXIMUM_SPEAKER_ROWS = 8, + }; + rectangle2d bounds = render.camera.window_bounds; + long font_index = hud_get_font_index(); + long machines_listed[MULTIPLAYER_MAXIMUM_PLAYERS]; + short listed_count = 0; + struct data_iterator iterator; + struct player_datum *player; + struct font_header *font; + short line_height; + short row = 0; + short left; + short top; + struct player_datum *viewer; + + if (font_index == NONE || !network_voice_available()) + return; + offset_rectangle2d(&bounds, -render.camera.viewport_bounds.x0, -render.camera.viewport_bounds.y0); + font = font_definition_get(font_index); + line_height = (short)(font->leading_height + font->descending_height + font->ascending_height); + if (line_height <= 0) + return; + /* (laid out at full size, from the list's top left, and drawn scaled + about it) */ + left = (short)(bounds.x0 + 16); + top = (short)(bounds.y0 + (bounds.y1 - bounds.y0) * 55 / 100); + rasterizer_text_set_scale(VOICE_SPEAKERS_SCALE, (real)left, (real)top); + viewer = local_player_get_next(NONE) != NONE ? + player_try_and_get(local_player_get_player_index(local_player_get_next(NONE))) : NULL; + data_iterator_new(&iterator, player_data); + while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL && row < MAXIMUM_SPEAKER_ROWS) + { + long machine_index = player->network_player_data.machine_index; + real_argb_color color; + rectangle2d icon; + rectangle2d text; + short index; + + if (player->quit_out_of_game) + continue; + for (index = 0; index < listed_count && machines_listed[index] != machine_index; index++) + ; + if (index < listed_count || listed_count >= (short)NUMBEROF(machines_listed)) + continue; + machines_listed[listed_count++] = machine_index; + if (!network_voice_machine_speaking(machine_index)) + continue; + /* (the name after the speaker, which is centred on its capitals: + where they are drawn, scaled) */ + text.x0 = (short)(left + line_height + 4); + text.x1 = (short)(left + (bounds.x1 - left) / VOICE_SPEAKERS_SCALE); + text.y0 = (short)(top + row * line_height); + text.y1 = (short)(text.y0 + line_height); + /* (allies as the names above heads have them: the same team) */ + if (player->local_player_index != NONE) + { + color.alpha = 1.0f; + color.red = color.green = color.blue = 1.0f; + } + else + { + hud_player_name_color(!game_engine || (viewer && player->team_index == viewer->team_index), &color); + } + draw_string_set_draw_mode(font_index, NONE, 0, 0, &color); + icon.x0 = left; + icon.x1 = (short)(left + line_height * VOICE_SPEAKERS_SCALE); + icon.y0 = (short)(top + (draw_unicode_string_capital_middle(&text, player->name) - top) * VOICE_SPEAKERS_SCALE - + line_height * VOICE_SPEAKERS_SCALE / 2); + icon.y1 = (short)(icon.y0 + line_height * VOICE_SPEAKERS_SCALE); + network_voice_draw_icon(&icon, FALSE, 1.0f); + rasterizer_draw_unicode_string(&text, NULL, NULL, 0, player->name); + row++; + } + rasterizer_text_set_scale(1.0f, 0.0f, 0.0f); +} + static void game_engine_post_rasterize_in_game( void) { @@ -3574,6 +4030,10 @@ static void game_engine_post_rasterize_in_game( /* (port: the full-screen scoreboard's scroll forgotten) */ game_engine_scoreboard_closed(); } + /* (port: who is talking, in the first view, while the scores are not + shown) */ + if (fade <= 0.0f && local_player_index == local_player_get_next(NONE)) + game_engine_rasterize_voice_speakers(); game_engine_globals.hud_message_timers[local_player_index] = fade; @@ -6739,73 +7199,61 @@ void game_engine_variant_cleanup( return; } -static void game_engine_predict_resources( - void) +/* port: one of the globals' three multiplayer vehicles (0 warthog, 1 ghost, +2 scorpion), or NONE for one the map's globals lack: a Custom Edition map's +may have fewer, which the original read past the end of */ +static long game_engine_multiplayer_vehicle( + long index) { - struct game_globals *game_globals; - struct game_globals_multiplayer_information *multiplayer_information; - struct game_globals_vehicle *vehicle; - long weapon_indices[10]; - long weapon_index; + struct game_globals *game_globals = scenario_get_game_globals(); + struct game_globals_multiplayer_information *information; - game_globals = scenario_get_game_globals(); - multiplayer_information = TAG_BLOCK_GET_ELEMENT( + if (game_globals->multiplayer_information.count <= 0) + return NONE; + information = TAG_BLOCK_GET_ELEMENT( &game_globals->multiplayer_information, 0, struct game_globals_multiplayer_information); + if (index >= information->vehicles.count) + return NONE; + return TAG_BLOCK_GET_ELEMENT(&information->vehicles, index, struct game_globals_vehicle)->vehicle.index; +} + +static void game_engine_predict_multiplayer_vehicle( + long index) +{ + long definition_index = game_engine_multiplayer_vehicle(index); + + if (definition_index != NONE) + object_definition_predict(definition_index); +} + +static void game_engine_predict_resources( + void) +{ + long weapon_indices[10]; + long weapon_index; - /* port: the cases below take the three multiplayer vehicles the Xbox's - globals always have; a Halo Custom Edition map's can have fewer, and then - gets no vehicle predicted (port/linux/game/custom_edition_cache.c) */ - if (multiplayer_information->vehicles.count >= 3) switch (global_variant.universal_variant.vehicle_set) { case _game_engine_vehicles_warthog: - vehicle = TAG_BLOCK_GET_ELEMENT( - &multiplayer_information->vehicles, - 0, - struct game_globals_vehicle); - object_definition_predict(vehicle->vehicle.index); + game_engine_predict_multiplayer_vehicle(0); break; case _game_engine_vehicles_ghost: - vehicle = TAG_BLOCK_GET_ELEMENT( - &multiplayer_information->vehicles, - 1, - struct game_globals_vehicle); - object_definition_predict(vehicle->vehicle.index); + game_engine_predict_multiplayer_vehicle(1); break; case _game_engine_vehicles_tank: - vehicle = TAG_BLOCK_GET_ELEMENT( - &multiplayer_information->vehicles, - 2, - struct game_globals_vehicle); - object_definition_predict(vehicle->vehicle.index); + game_engine_predict_multiplayer_vehicle(2); break; default: - { - struct tag_block *vehicles = &multiplayer_information->vehicles; - - vehicle = TAG_BLOCK_GET_ELEMENT( - vehicles, - 0, - struct game_globals_vehicle); - object_definition_predict(vehicle->vehicle.index); - vehicle = TAG_BLOCK_GET_ELEMENT( - vehicles, - 1, - struct game_globals_vehicle); - object_definition_predict(vehicle->vehicle.index); - vehicle = TAG_BLOCK_GET_ELEMENT( - vehicles, - 2, - struct game_globals_vehicle); - object_definition_predict(vehicle->vehicle.index); + game_engine_predict_multiplayer_vehicle(0); + game_engine_predict_multiplayer_vehicle(1); + game_engine_predict_multiplayer_vehicle(2); break; } - } object_definition_predict(list_index_to_weapon_definition_index(_weapon_list_frag_grenade)); object_definition_predict(list_index_to_weapon_definition_index(_weapon_list_plasma_grenade)); @@ -7215,32 +7663,16 @@ long game_engine_remap_vehicle( } if (game_engine) { - struct game_globals *game_globals; - struct game_globals_multiplayer_information *multiplayer_information; - struct tag_block *vehicles; - struct game_globals_vehicle *vehicle0; - struct game_globals_vehicle *vehicle1; - struct game_globals_vehicle *vehicle2; - struct game_globals_vehicle *vehicle; - - game_globals = scenario_get_game_globals(); - multiplayer_information = TAG_BLOCK_GET_ELEMENT( - &game_globals->multiplayer_information, - 0, - struct game_globals_multiplayer_information); - vehicle0 = TAG_BLOCK_GET_ELEMENT( - &multiplayer_information->vehicles, - 0, - struct game_globals_vehicle); - vehicles = &multiplayer_information->vehicles; - vehicle1 = TAG_BLOCK_GET_ELEMENT(vehicles, 1, struct game_globals_vehicle); - vehicle2 = TAG_BLOCK_GET_ELEMENT(vehicles, 2, struct game_globals_vehicle); + /* (port: NONE for one the map's globals lack) */ + long vehicle0 = game_engine_multiplayer_vehicle(0); + long vehicle1 = game_engine_multiplayer_vehicle(1); + long vehicle2 = game_engine_multiplayer_vehicle(2); /* (port: and the other types a gametype's sets name, which a map may have: game_engine_variant_vehicle_type) */ - if (result != vehicle0->vehicle.index && - result != vehicle1->vehicle.index && - result != vehicle2->vehicle.index && + if (result != vehicle0 && + result != vehicle1 && + result != vehicle2 && ((game_variant_options_get()->vehicle_set[0] == _game_engine_vehicles_default && game_variant_options_get()->vehicle_set[1] == _game_engine_vehicles_default) || game_engine_variant_vehicle_type(result) == NONE)) @@ -7258,29 +7690,17 @@ long game_engine_remap_vehicle( break; case _game_engine_vehicles_warthog: - vehicle = TAG_BLOCK_GET_ELEMENT( - vehicles, - 0, - struct game_globals_vehicle); - if (vehicle->vehicle.index != result) + if (vehicle0 != result) result = NONE; break; case _game_engine_vehicles_ghost: - vehicle = TAG_BLOCK_GET_ELEMENT( - vehicles, - 1, - struct game_globals_vehicle); - if (vehicle->vehicle.index != result) + if (vehicle1 != result) result = NONE; break; case _game_engine_vehicles_tank: - vehicle = TAG_BLOCK_GET_ELEMENT( - vehicles, - 2, - struct game_globals_vehicle); - if (vehicle->vehicle.index != result) + if (vehicle2 != result) result = NONE; break; } @@ -7746,12 +8166,7 @@ static void game_engine_verify_current_map( _netgame_flag_race_track, "NETGAME MAP FAILURE: duplicate race track flag [team %d]"); - /* BUG (preserved for exact matching): January passes team index zero for - * both CTF checks, and netgame_verify_spawn_points never reads that - * formal parameter. - * A corrected build should filter starting locations by an authoritatively - * recovered team-index field before reporting per-team counts. - */ + /* (the team index is not read: both checks count every ctf spawn) */ netgame_verify_spawn_points( game_engine_ctf, 0, @@ -7931,7 +8346,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "s"), player->name); break; case _game_engine_message_killed_by_unknown: @@ -7943,7 +8358,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "s"), player->name); break; case _game_engine_message_killed_by_biped: @@ -7955,7 +8370,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "s"), player->name); break; case _game_engine_message_killed_by_vehicle: @@ -7967,7 +8382,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "s"), player->name); break; case _game_engine_message_killed_by_player: @@ -7980,7 +8395,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "ss"), player->name, other_player->name); break; @@ -7994,7 +8409,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "ss"), player->name, other_player->name); break; @@ -8008,7 +8423,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "s"), other_player->name); break; case _game_engine_message_killed_by_self: @@ -8020,7 +8435,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "s"), player->name); break; case _game_engine_message_killed_friendly: @@ -8033,7 +8448,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "s"), other_player->name); break; case _game_engine_message_multi_kill: @@ -8106,7 +8521,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "s"), other_player->name); break; case _game_engine_message_multi_kill_with_score: @@ -8118,7 +8533,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "d"), score); game_engine_play_multiplayer_sound(_multiplayer_sound_killtacular_kill); break; @@ -8131,7 +8546,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "d"), score); game_engine_play_multiplayer_sound(_multiplayer_sound_triple_kill); break; @@ -8144,7 +8559,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "d"), score); game_engine_play_multiplayer_sound(_multiplayer_sound_double_kill); break; @@ -8157,7 +8572,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "d"), score); game_engine_play_multiplayer_sound(_multiplayer_sound_running_riot); break; @@ -8170,7 +8585,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "d"), score); game_engine_play_multiplayer_sound(_multiplayer_sound_killing_spree); break; @@ -8184,7 +8599,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "sd"), other_player->name, score); break; @@ -8219,7 +8634,7 @@ static boolean internal_rasterize_score( usnprintf( buffer, buffer_size, - format, + ustring_format_checked(format, "d"), message_data); break; case _game_engine_message_waiting_for_space_to_clear: diff --git a/source/game/game_engine_ctf.c b/source/game/game_engine_ctf.c index 33faa797..5caf9159 100644 --- a/source/game/game_engine_ctf.c +++ b/source/game/game_engine_ctf.c @@ -622,11 +622,6 @@ static void ctf_engine_player_update( { ctf_award_capture(player_index, player->team_index); ctf_player_drop_flag(player_index, weapon_index); - /* BUG (preserved for exact matching): January (and the - later HCEA build) call game_engine_get_variant() here - and discard the result. A corrected build should drop - this statement. */ - game_engine_get_variant(); } else { @@ -865,7 +860,8 @@ static wchar_t *ctf_get_score_header_string( else string = L""; - ustrcpy(buffer, string); + /* port: bounded (a map's text, into its callers' score_string[256]) */ + ustrncpy_terminated(buffer, string, 256); return buffer; } @@ -899,7 +895,7 @@ static boolean ctf_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "dd"), ctf_globals.scores[_team_red], ctf_globals.scores[_team_blue]); break; @@ -917,7 +913,7 @@ static boolean ctf_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "dd"), ctf_globals.scores[team_index], ctf_globals.scores[other_team_index]); break; @@ -935,7 +931,7 @@ static boolean ctf_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "dd"), ctf_globals.scores[team_index], ctf_globals.scores[other_team_index]); break; @@ -953,7 +949,7 @@ static boolean ctf_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "dd"), ctf_globals.scores[team_index], ctf_globals.scores[other_team_index]); break; diff --git a/source/game/game_engine_king.c b/source/game/game_engine_king.c index 24a180d0..8061c4e6 100644 --- a/source/game/game_engine_king.c +++ b/source/game/game_engine_king.c @@ -560,7 +560,7 @@ static boolean king_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, place_name, score); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "sd"), place_name, score); } else if (message == king_message_ally_on_the_hill) { @@ -573,7 +573,7 @@ static boolean king_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, other_player->name, score); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "sd"), other_player->name, score); } else if (message == king_message_enemy_on_the_hill) { @@ -586,7 +586,7 @@ static boolean king_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, other_player->name, score); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "sd"), other_player->name, score); } else { @@ -762,7 +762,8 @@ static wchar_t *king_get_score_header_string( { string = L""; } - ustrcpy(buffer, string); + /* port: bounded (a map's text, into its callers' score_string[256]) */ + ustrncpy_terminated(buffer, string, 256); return buffer; } diff --git a/source/game/game_engine_oddball.c b/source/game/game_engine_oddball.c index f5f46118..dd141b34 100644 --- a/source/game/game_engine_oddball.c +++ b/source/game/game_engine_oddball.c @@ -1013,11 +1013,13 @@ static wchar_t *oddball_get_score_header_string( string_index = _multiplayer_game_text_time; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); - ustrcpy( + /* port: bounded (a map's text, into its callers' score_string[256]) */ + ustrncpy_terminated( buffer, string_list_index != NONE ? unicode_string_list_get_string(string_list_index, string_index) : - L""); + L"", + 256); return buffer; } @@ -1216,7 +1218,7 @@ static boolean oddball_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, other_player->name); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "s"), other_player->name); } break; @@ -1259,7 +1261,7 @@ static boolean oddball_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, other_player->name); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "s"), other_player->name); } break; @@ -1284,7 +1286,7 @@ static boolean oddball_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, place_name, score); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "sd"), place_name, score); } else if (message == _oddball_message_ally_has_the_ball_tick) { @@ -1297,7 +1299,7 @@ static boolean oddball_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, other_player->name, score); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "sd"), other_player->name, score); } else if (message == _oddball_message_enemy_has_the_ball_tick) { @@ -1310,7 +1312,7 @@ static boolean oddball_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, other_player->name, score); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "sd"), other_player->name, score); } } break; diff --git a/source/game/game_engine_race.c b/source/game/game_engine_race.c index 94904398..9a8ff374 100644 --- a/source/game/game_engine_race.c +++ b/source/game/game_engine_race.c @@ -924,7 +924,7 @@ static boolean race_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, other_player->name); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "s"), other_player->name); } break; @@ -939,7 +939,7 @@ static boolean race_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, other_player->name); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "s"), other_player->name); } break; @@ -961,7 +961,7 @@ static boolean race_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "df"), other_player->statistics.multiplayer_statistics.race_statistics.laps + 1, lap_time); } @@ -980,7 +980,7 @@ static boolean race_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "sd"), other_player->name, other_player->statistics.multiplayer_statistics.race_statistics.laps + 1); break; @@ -998,7 +998,7 @@ static boolean race_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "sd"), other_player->name, other_player->statistics.multiplayer_statistics.race_statistics.laps); break; @@ -1018,7 +1018,7 @@ static boolean race_engine_display_score( } else string = L""; - usnprintf(buffer, buffer_size, string, best_lap_time); + usnprintf(buffer, buffer_size, ustring_format_checked(string, "f"), best_lap_time); } break; @@ -1039,7 +1039,7 @@ static boolean race_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "s"), get_place_name(game_engine_get_place(player_index, _get_score_team))); } else @@ -1056,7 +1056,7 @@ static boolean race_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "sd"), get_place_name(game_engine_get_place(player_index, _get_score_team)), other_player->statistics.multiplayer_statistics.race_statistics.laps); } @@ -1076,7 +1076,7 @@ static boolean race_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "s"), get_place_name(game_engine_get_place(player_index, _get_score_team))); } else @@ -1093,7 +1093,7 @@ static boolean race_engine_display_score( usnprintf( buffer, buffer_size, - string, + ustring_format_checked(string, "sdd"), get_place_name(game_engine_get_place(player_index, _get_score_team)), other_player->statistics.multiplayer_statistics.race_statistics.laps + 1, game_engine_get_variant()->universal_variant.score_to_win); @@ -1205,7 +1205,8 @@ static wchar_t *race_get_score_header_string( else header_string = L""; - ustrcpy(string, header_string); + /* port: bounded (a map's text, into its callers' score_string[256]) */ + ustrncpy_terminated(string, header_string, 256); return string; } diff --git a/source/game/game_engine_slayer.c b/source/game/game_engine_slayer.c index 89bc5474..5677d4d0 100644 --- a/source/game/game_engine_slayer.c +++ b/source/game/game_engine_slayer.c @@ -307,7 +307,8 @@ static wchar_t *slayer_get_score_header_string( { long string_list_index; - ustrcpy(buffer, GET_MULTIPLAYER_GAME_TEXT(_string_score)); + /* port: bounded (a map's text, into its callers' score_string[256]) */ + ustrncpy_terminated(buffer, GET_MULTIPLAYER_GAME_TEXT(_string_score), 256); return buffer; } @@ -524,7 +525,7 @@ static boolean slayer_engine_display_score( usnprintf( string, NUMBEROF(string), - GET_MULTIPLAYER_GAME_TEXT(_string_n_team_n), + ustring_format_checked(GET_MULTIPLAYER_GAME_TEXT(_string_n_team_n), "dd"), slayer_get_score(player_index, _get_score_individual), slayer_get_score(player_index, _get_score_team)); } @@ -546,7 +547,7 @@ static boolean slayer_engine_display_score( usnprintf( buffer, buffer_character_count, - GET_MULTIPLAYER_GAME_TEXT(_string_new_target_name), + ustring_format_checked(GET_MULTIPLAYER_GAME_TEXT(_string_new_target_name), "s"), target_player->name); break; @@ -559,8 +560,8 @@ static boolean slayer_engine_display_score( usnprintf( buffer, buffer_character_count, - GET_MULTIPLAYER_GAME_TEXT( - _string_name_kills_score_n_team_score_of_max), + ustring_format_checked(GET_MULTIPLAYER_GAME_TEXT( + _string_name_kills_score_n_team_score_of_max), "sddd"), place_name, slayer_get_score(player_index, _get_score_individual), slayer_get_score(player_index, _get_score_team), @@ -574,8 +575,8 @@ static boolean slayer_engine_display_score( usnprintf( buffer, buffer_character_count, - GET_MULTIPLAYER_GAME_TEXT( - _string_name_kills_score_of_max), + ustring_format_checked(GET_MULTIPLAYER_GAME_TEXT( + _string_name_kills_score_of_max), "sdd"), place_name, slayer_get_score(player_index, _get_score_team), game_engine_get_variant()->universal_variant.score_to_win); diff --git a/source/game/game_time.c b/source/game/game_time.c index 739418be..5de8ef02 100644 --- a/source/game/game_time.c +++ b/source/game/game_time.c @@ -75,9 +75,15 @@ symbols in this file: #include "networking/network_game_globals.h" #include "networking/network_server_manager.h" #include "saved games/game_state.h" +#include "profile_sections.h" /* port: port/linux/include/profile_sections.h */ /* port/linux/game/network_distributed.c's */ void network_distributed_tick(void); +/* port: the tick loop's steps, timed in the profiling build */ +PROFILE_SECTION(game_time_network_distributed_tick_section, "network_distributed_tick") +PROFILE_SECTION(game_time_render_interpolation_tick_section, "render_interpolation_tick") +PROFILE_SECTION(game_time_game_frame_section, "game_frame") + /* ---------- constants */ enum @@ -296,6 +302,11 @@ void game_time_set_speed( { match_assert("c:\\halo\\SOURCE\\game\\game_time.c", 562, game_time_globals); + /* port: a speed (a map's script's, game_speed) that is a number, not + negative and not past a hundred: a NaN or an infinity stopped the game's + time for good */ + if (!(speed >= 0.0f && speed <= 100.0f)) + return; game_time_globals->speed = speed; return; @@ -468,11 +479,11 @@ void game_time_update( for (update_index = 0; update_index < server_updates; update_index++) { game_tick(); - render_interpolation_tick(); + profile_scope(game_time_render_interpolation_tick_section, render_interpolation_tick();) game_time_globals->server_time++; game_time_globals->local_time++; /* the distributed netcode's per-tick state */ - network_distributed_tick(); + profile_scope(game_time_network_distributed_tick_section, network_distributed_tick();) } } @@ -488,7 +499,7 @@ void game_time_update( (port/linux/game/render_interpolation.c). The frames of one tick share the fraction, so what they hang off is drawn where they put it. */ render_interpolation_frame_begin(); - game_frame(game_time_get_speed()*time_delta_sec); + profile_scope(game_time_game_frame_section, game_frame(game_time_get_speed()*time_delta_sec);) render_interpolation_frame_end(); } else diff --git a/source/game/player_control.c b/source/game/player_control.c index 89bbace1..2002ebe3 100644 --- a/source/game/player_control.c +++ b/source/game/player_control.c @@ -195,6 +195,7 @@ symbols in this file: #include "cutscene/cinematics.h" #include "input/input.h" #include "input/input_abstraction.h" +#include "interface/ui_widget.h" #include "interface/player_ui.h" #include "items/weapons.h" #include "main/main.h" @@ -652,6 +653,23 @@ static void player_action_clear( return; } +/* port: whether a menu of the player's own is up (a network game's pause +menu, which pauses nothing): its controller drives the menu, not the player, +whose D-pad and sticks would otherwise move them as they move about it. The +buttons still held as it closes are let go of first (ui_widget_delete). */ +static boolean player_control_port_menu_has_controller( + short local_player_index) +{ + long player_index = local_player_get_player_index(local_player_index); + short gamepad_index; + + if (player_index == NONE) + return FALSE; + gamepad_index = player_get(player_index)->local_player_index; + return gamepad_index >= 0 && gamepad_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS && + ui_widgets_active_for_local_player(gamepad_index); +} + static void handle_one_player_input( short local_player_index, real time_delta_sec) @@ -684,7 +702,8 @@ static void handle_one_player_input( player->desired_angles.yaw); } - if (director_inhibited_input(local_player_index)) + if (director_inhibited_input(local_player_index) || + player_control_port_menu_has_controller(local_player_index)) { csmemset(&input, 0, sizeof(input)); } @@ -1251,6 +1270,8 @@ static void get_local_player_input_blob( real yaw_spin_scale; real pitch_spin_scale; real_euler_angles2d look_delta; + /* port: the touch controls' swipe or gyroscope this frame */ + boolean touching = FALSE; if (input_state->buttons[_button_scope_zoom] && controls_enable_doubled_spin) @@ -1284,6 +1305,33 @@ static void get_local_player_input_blob( constants->look_function.count, constants->look_function.address, clamped_pitch) * pitch_spin_scale * look_pitch_rate; + { + /* port: the touch controls' swipe (port/linux/src/xinput_sdl.c), + a turn this frame, made a rate as the stick's look is: the + zoom, the stun and the magnetism below act on it as on the + stick's, so that it slows over a target and follows a moving + one (input.touch_aim_assist); the swipe inverted as the stick + is, the gyroscope as the phone turns */ + extern int halo_linux_touch_look(short gamepad_index, real *yaw, real *pitch, real *gyro_yaw, + real *gyro_pitch); + real touch_yaw; + real touch_pitch; + real gyro_yaw; + real gyro_pitch; + real touch_scale = time_delta_sec * TICKS_PER_SECOND; + + if (halo_linux_touch_look(gamepad_index, &touch_yaw, &touch_pitch, &gyro_yaw, &gyro_pitch) && + touch_scale > _real_epsilon) + { + touching = TRUE; + if (input_abstraction_port_look_inverted(gamepad_index)) + { + touch_pitch = -touch_pitch; + } + look_delta.yaw += (touch_yaw + gyro_yaw) / touch_scale; + look_delta.pitch += (touch_pitch + gyro_pitch) / touch_scale; + } + } if (player->unit_index != NONE && control->zoom_level != NONE) { @@ -1341,16 +1389,19 @@ static void get_local_player_input_blob( &target_angular_position, &target_angular_velocity); { - /* no magnetism for the mouse (port/linux/src/xinput_sdl.c) */ + /* no magnetism for the mouse (port/linux/src/xinput_sdl.c), + nor for the touch controls without their aim assist */ extern int halo_linux_mouse_aiming(short gamepad_index); + extern int halo_linux_touch_aiming(short gamepad_index); - if (halo_linux_mouse_aiming(gamepad_index)) + if (halo_linux_mouse_aiming(gamepad_index) || halo_linux_touch_aiming(gamepad_index)) { control->magnetism_level = 0.f; } } if (player_magnetism_flag && control->magnetism_level > 0.f && - (fabs(clamped_yaw) > _real_epsilon || + (touching || + fabs(clamped_yaw) > _real_epsilon || fabs(clamped_pitch) > _real_epsilon || fabs(input->throttle.i) > _real_epsilon || fabs(input->throttle.j) > _real_epsilon)) diff --git a/source/game/players.c b/source/game/players.c index a3c20000..c6b13bcb 100644 --- a/source/game/players.c +++ b/source/game/players.c @@ -2525,8 +2525,15 @@ that was on another BSP. */ static boolean players_coop_room_to_spawn( void); +static long player_spawnable_beside( + struct player_datum const *player); static boolean player_place_beside_teammate( long player_index); +static long players_coop_respawn_unit( + void); +static boolean players_coop_respawn_beside( + long player_index, + long anchor_unit_index); /* How long into a level the extra players wait before spawning. The opening cutscene starts a tick or so in, and the first player may still be somewhere @@ -2580,8 +2587,14 @@ to respawn beside */ /* how often the respawn's safety test runs while someone is dead */ #define COOP_RESPAWN_CHECK_TICKS (TICKS_PER_SECOND / 2) /* after waiting this long for a safe teammate, the dead respawn beside any -teammate standing on the ground */ +teammate standing on the ground. A large game waits this long for someone +safe inside the largest group, then uses that group anyway */ #define COOP_RESPAWN_FALLBACK_TICKS (10 * TICKS_PER_SECOND) +/* this many players in a campaign game is massive co-op. Respawns go to the +largest group, and a dead host uses that same choice */ +#define COOP_MAJORITY_PLAYERS 5 +/* teammates within this distance (world units) count as one group */ +#define COOP_MAJORITY_RADIUS 30.0f /* TRUE if an enemy projectile (a grenade, a plasma bolt) is flying near the unit. Players' own shots and needles stuck in a body don't count. */ @@ -2649,7 +2662,8 @@ static boolean players_coop_unit_safe( /* the unit of a living player that passes `test`, the host's first (the team follows the host: its players are the host machine's local ones), or -NONE */ +NONE. A game of COOP_MAJORITY_PLAYERS or more respawns with +players_coop_respawn_unit instead. */ static long players_coop_unit_where( boolean (*test)(long unit_index)) { @@ -2671,6 +2685,143 @@ static long players_coop_unit_where( return found_index; } +/* how many players are in this campaign game, not counting someone who quit */ +static short players_coop_session_count( + void) +{ + struct data_iterator iterator; + struct player_datum *player; + short count = 0; + + data_iterator_new(&iterator, player_data); + while ((player = data_iterator_next(&iterator)) != NULL) + { + if (!player->quit_out_of_game) + count++; + } + + return count; +} + +/* TRUE when respawns use the largest group */ +static boolean players_coop_use_majority( + void) +{ + return players_coop_session_count() >= COOP_MAJORITY_PLAYERS; +} + +/* TRUE when a player can be stood beside: on foot, or in a vehicle a player +drives. An AI vehicle (Silent Cartographer's Pelican) is not */ +static boolean players_coop_unit_spawnable( + long unit_index) +{ + long player_index = unit_get(unit_index)->unit.player_index; + + return player_index != NONE && player_spawnable_beside(player_get(player_index)) != NONE; +} + +/* how many listed positions are within radius of the one at index, including +itself */ +static short players_coop_near_count( + real_point3d const *positions, + short count, + short index, + real radius_squared) +{ + short other_index; + short near_count = 0; + + for (other_index = 0; other_index < count; other_index++) + { + if (distance_squared3d(&positions[index], &positions[other_index]) <= radius_squared) + near_count++; + } + + return near_count; +} + +/* who a network co-op respawn is placed beside. Fewer than +COOP_MAJORITY_PLAYERS uses the host's teammate (players_coop_unit_where): +a safe one, or after COOP_RESPAWN_FALLBACK_TICKS one on the ground. A larger +game uses the largest group on the ground. The respawn is beside someone in +that group who is safe to spawn next to. When that whole group is in a +fight, it waits out COOP_RESPAWN_FALLBACK_TICKS and then uses the group +anyway. A safe player outside the group does not win. A dead host has no +unit, so the same count applies to them. NONE while the group is still +unsafe, or while nobody is on the ground. */ +static long players_coop_respawn_unit( + void) +{ + long unit_indices[NETWORK_GAME_MAXIMUM_PLAYER_COUNT]; + real_point3d positions[NETWORK_GAME_MAXIMUM_PLAYER_COUNT]; + struct data_iterator iterator; + struct player_datum *player; + real radius_squared = COOP_MAJORITY_RADIUS * COOP_MAJORITY_RADIUS; + short count = 0; + short anchor_index = 0; + short anchor_count = 0; + short safe_index = -1; + short safe_count = 0; + short index; + + if (!players_coop_use_majority()) + { + long host_index = players_coop_unit_where(players_coop_unit_safe); + + if (host_index == NONE && + game_time_get() + 1 - players_coop_state.respawn_wait_since >= COOP_RESPAWN_FALLBACK_TICKS) + { + host_index = players_coop_unit_where(players_coop_unit_grounded); + } + return host_index; + } + + data_iterator_new(&iterator, player_data); + while ((player = data_iterator_next(&iterator)) != NULL && count < NETWORK_GAME_MAXIMUM_PLAYER_COUNT) + { + if (player->unit_index == NONE || !players_coop_unit_grounded(player->unit_index)) + continue; + unit_indices[count] = player->unit_index; + positions[count] = object_get(player->unit_index)->object.bounding_sphere_center; + count++; + } + if (count == 0) + return NONE; + for (index = 0; index < count; index++) + { + short near_count = players_coop_near_count(positions, count, index, radius_squared); + + if (near_count > anchor_count) + { + anchor_count = near_count; + anchor_index = index; + } + } + for (index = 0; index < count; index++) + { + short near_count; + + if (distance_squared3d(&positions[anchor_index], &positions[index]) > radius_squared || + !players_coop_unit_safe(unit_indices[index]) || + !players_coop_unit_spawnable(unit_indices[index])) + { + continue; + } + near_count = players_coop_near_count(positions, count, index, radius_squared); + if (near_count > safe_count) + { + safe_count = near_count; + safe_index = index; + } + } + if (safe_index >= 0) + return unit_indices[safe_index]; + if (game_time_get() + 1 - players_coop_state.respawn_wait_since >= COOP_RESPAWN_FALLBACK_TICKS) + return unit_indices[anchor_index]; + + return NONE; +} + /* the teammate a network co-op respawn is for, while player_spawn makes the player (players_respawn_network_coop), else NONE */ static long players_coop_spawn_beside_index = NONE; @@ -2703,12 +2854,14 @@ static struct player_starting_location const *players_coop_spawn_location( return location; } -/* Network co-op respawn: the dead come back beside the first teammate who -is safe, or after COOP_RESPAWN_FALLBACK_TICKS beside one at least on the -ground, so a long fight can't keep them out. The campaign's own test -(players_respawn_coop) is map-wide (any projectile or enemy attack -anywhere), which with many players spread out would almost never pass. -Returns whether everyone waiting came back. */ +/* Network co-op respawn: the dead come back beside the first safe teammate, +the host's before the others, or after COOP_RESPAWN_FALLBACK_TICKS beside +one at least on the ground, so a long fight can't keep them out. With +COOP_MAJORITY_PLAYERS or more they come back beside the largest group, and +a dead host does too. The campaign's own test (players_respawn_coop) is +map-wide (any projectile or enemy attack anywhere), which with many players +spread out would almost never pass. Returns whether everyone waiting came +back. */ static boolean players_respawn_network_coop( void) { @@ -2723,12 +2876,7 @@ static boolean players_respawn_network_coop( walks all the map's projectiles, so twice a second is enough */ if (game_time_get() % COOP_RESPAWN_CHECK_TICKS != 0) return FALSE; - safe_unit_index = players_coop_unit_where(players_coop_unit_safe); - if (safe_unit_index == NONE && - game_time_get() + 1 - players_coop_state.respawn_wait_since >= COOP_RESPAWN_FALLBACK_TICKS) - { - safe_unit_index = players_coop_unit_where(players_coop_unit_grounded); - } + safe_unit_index = players_coop_respawn_unit(); if (safe_unit_index == NONE) return FALSE; data_iterator_new(&iterator, player_data); @@ -2745,14 +2893,10 @@ static boolean players_respawn_network_coop( players_coop_spawn_beside_index = NONE; if (player->unit_index == NONE) result = FALSE; - /* (beside the safe teammate, else any with room; with none, left - behind the teammate, where player_spawn put them) */ - else if (!player_teleport(iterator.datum_index, safe_unit_index, - &object_get(safe_unit_index)->object.bounding_sphere_center) && - !player_place_beside_teammate(iterator.datum_index)) - { + /* (beside that teammate, else somewhere in their group; with no + room, left behind them, where player_spawn put them) */ + else if (!players_coop_respawn_beside(iterator.datum_index, safe_unit_index)) result = FALSE; - } } if (result) players_coop_state.respawn_wait_since = 0; @@ -3043,6 +3187,49 @@ static boolean player_place_beside_teammate( return FALSE; } +/* Moves a respawned player beside the chosen teammate. In a massive game +the other spots tried are only teammates in that same group, so a failed +spot beside the anchor does not send them to a player off on their own. +Smaller games still try any teammate with room. FALSE leaves the player +where player_spawn put them, behind the anchor. */ +static boolean players_coop_respawn_beside( + long player_index, + long anchor_unit_index) +{ + real_point3d anchor_position; + struct data_iterator iterator; + struct player_datum *other; + real radius_squared; + + if (player_get(player_index)->unit_index == NONE) + return FALSE; + anchor_position = object_get(anchor_unit_index)->object.bounding_sphere_center; + if (player_teleport(player_index, anchor_unit_index, &anchor_position)) + return TRUE; + if (!players_coop_use_majority()) + return player_place_beside_teammate(player_index); + + radius_squared = COOP_MAJORITY_RADIUS * COOP_MAJORITY_RADIUS; + data_iterator_new(&iterator, player_data); + while ((other = data_iterator_next(&iterator)) != NULL) + { + if (iterator.datum_index == player_index || other->unit_index == NONE || + other->unit_index == anchor_unit_index || player_spawnable_beside(other) == NONE) + { + continue; + } + if (distance_squared3d(&anchor_position, + &object_get(other->unit_index)->object.bounding_sphere_center) <= radius_squared && + player_teleport(player_index, other->unit_index, + &object_get(other->unit_index)->object.bounding_sphere_center)) + { + return TRUE; + } + } + + return FALSE; +} + void players_note_checkpoint( void) { @@ -4189,10 +4376,6 @@ static void player_handle_powerup_equipment( powerup_index = 1; break; - /* powerup_index is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: display_assert( NULL, diff --git a/source/hs/hs.c b/source/hs/hs.c index e1f6fde6..213fd10d 100644 --- a/source/hs/hs.c +++ b/source/hs/hs.c @@ -2805,6 +2805,7 @@ symbols in this file: #include "networking/network_game_globals.h" #include "networking/network_game_manager.h" #include "networking/network_server_manager.h" +#include "network_votekick.h" /* port: port/linux/game/network_votekick.c */ #include "objects/damage.h" #include "objects/object_lights.h" #include "objects/scenery.h" @@ -2840,6 +2841,9 @@ symbols in this file: #include "units/units.h" #include "units/vehicles.h" #include "coop_scripts.h" /* port: port/linux/game/coop_scripts.c */ +#ifdef HALO_PROFILE +#include "profile_console.h" /* port: port/linux/game/profile_console.c */ +#endif /* ---------- constants */ @@ -14557,7 +14561,7 @@ static void player_effect_screen_fade_in_evaluate( double value1 = arguments->value1; double value2 = arguments->value2; -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST player_effect_screen_fade_in(*(real const *)&arguments->value0, (real)value1, (real)value2, arguments->value3); #else player_effect_screen_fade_in(arguments->value0, value1, value2, arguments->value3); @@ -14580,7 +14584,7 @@ static void player_effect_screen_fade_out_evaluate( double value1 = arguments->value1; double value2 = arguments->value2; -#ifdef HALO_ANDROID +#ifdef HALO_ARM64_GUEST player_effect_screen_fade_out(*(real const *)&arguments->value0, (real)value1, (real)value2, arguments->value3); #else player_effect_screen_fade_out(arguments->value0, value1, value2, arguments->value3); @@ -15357,10 +15361,37 @@ static boolean hs_compile_and_evaluate_command( char buffer[1024]; char expanded[1024]; +#ifdef HALO_PROFILE + /* port: the profiling build's commands change nothing of the game: a client + may give them, and they are no scripts */ + if (profile_console_command(expression)) + return TRUE; +#endif /* port: the co-op host's bringto, which brings every player to the host (players.c; a client is told it is the host's) */ if (hs_host_player_command(expression, "bringto")) return players_coop_bring_to_host(); + /* port: the players' vote to kick a player ("votekick ", + or the name's start: Tab completes it), on any machine of a network + game: the host counts (network_votekick.c) */ + { + char const *text = hs_host_player_command(expression, "votekick"); + + if (text) + { + char name[64]; + long length = 0; + + while (*text == ' ' || *text == '\t' || *text == '"') + text++; + while (*text && *text != '"' && *text != ')' && length < (long)sizeof(name) - 1) + name[length++] = *text++; + while (length > 0 && (name[length - 1] == ' ' || name[length - 1] == '\t')) + length--; + name[length] = 0; + return network_votekick_player_named(name); + } + } /* port: playing in another's game, the host decides the game: no cheats, no game speed, nothing else a command changes of the game (the game run each tick also puts back what was changed before joining, diff --git a/source/hs/hs_compile.c b/source/hs/hs_compile.c index e5acaf53..e31047e0 100644 --- a/source/hs/hs_compile.c +++ b/source/hs/hs_compile.c @@ -714,11 +714,7 @@ static long hs_parse_cond_recursive( long condition_expression_index = hs_syntax_get(expression_index)->data; struct hs_syntax_node *condition_expression = hs_syntax_get(condition_expression_index); - /* BUG (preserved for exact matching): January tests the logical negation of the - * result index against NONE, which is never true, so a clause without a result is - * not rejected here. A corrected build tests - * condition_expression->next_node_index != NONE. */ - if (!condition_expression->next_node_index != NONE) + if (condition_expression->next_node_index != NONE) { long if_then_expression_index = datum_new(hs_syntax_data); long then_value_expression_index = datum_new(hs_syntax_data); @@ -3012,13 +3008,7 @@ boolean hs_parse_begin( { if (argument_count < 1) { - /* BUG (preserved for exact matching): January passes the function name - * although this format has no conversion for it; the argument is ignored. */ - sprintf( - hs_compile_globals.error_buffer, - "a statement block must contain at least one argument.", - hs_function_get(function_index)->name); - hs_compile_globals.error = hs_compile_globals.error_buffer; + hs_compile_globals.error = "a statement block must contain at least one argument."; hs_compile_globals.error_offset = hs_syntax_get(expression_index)->source_offset; result = FALSE; } diff --git a/source/hs/hs_library_external.c b/source/hs/hs_library_external.c index 0e210b0a..73dde5cf 100644 --- a/source/hs/hs_library_external.c +++ b/source/hs/hs_library_external.c @@ -152,6 +152,10 @@ void hs_object_create_anew( short object_name_index); void hs_object_destroy( long object_index); +boolean hs_unit_can_see_flag( + long unit_index, + short cutscene_flag_index, + real degrees); /* ---------- globals */ @@ -274,6 +278,32 @@ boolean hs_unit_can_see_object( return result; } +/* port: in network co-op a test of the players (the scripts' player0) +passes for any player, as volume_test_objects_all does (coop_scripts.c), so +the a10 tutorial's panels light for whoever looks at them: whether any +player's unit sees the object, or with object_index NONE the cutscene flag */ +static boolean hs_any_player_can_see( + long object_index, + short cutscene_flag_index, + real degrees) +{ + struct data_iterator iterator; + struct player_datum *player; + + data_iterator_new(&iterator, player_data); + while ((player = data_iterator_next(&iterator)) != NULL) + { + if (player->unit_index != NONE && + (object_index != NONE ? hs_unit_can_see_object(player->unit_index, object_index, degrees) : + hs_unit_can_see_flag(player->unit_index, cutscene_flag_index, degrees))) + { + return TRUE; + } + } + + return FALSE; +} + boolean hs_objects_can_see_object( long object_list_index, long object_index, @@ -283,6 +313,8 @@ boolean hs_objects_can_see_object( long unit_index; boolean result; + if (object_index != NONE && coop_scripts_any_player_will_do(object_list_index)) + return hs_any_player_can_see(object_index, NONE, degrees); result = FALSE; unit_index = object_list_get_first(object_list_index, &reference_index); while (unit_index != NONE) @@ -332,6 +364,8 @@ boolean hs_objects_can_see_flag( long reference_index; long unit_index; + if (coop_scripts_any_player_will_do(object_list_index)) + return hs_any_player_can_see(NONE, cutscene_flag_index, degrees); unit_index = object_list_get_first(object_list_index, &reference_index); while (unit_index != NONE) { diff --git a/source/hs/hs_runtime.c b/source/hs/hs_runtime.c index 8053ac97..f9f27a75 100644 --- a/source/hs/hs_runtime.c +++ b/source/hs/hs_runtime.c @@ -1046,7 +1046,7 @@ void render_debug_trigger_volumes( { if (debug_trigger_volumes) { - real_matrix4x3 matrix; + real_matrix4x3 matrix = { 0 }; short volume_index; struct scenario *scenario = global_scenario_get(); @@ -1091,10 +1091,6 @@ void render_debug_trigger_volumes( matrix4x3_transform_vector(&matrix, &local_extent, &world_extent); break; - /* matrix is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_assert( "c:\\halo\\SOURCE\\hs\\hs_runtime.c", @@ -1658,13 +1654,7 @@ static long hs_long_to_boolean( { long result; - /* BUG: only the low byte of this uninitialised long is written, and the whole long is returned; its - upper three bytes are indeterminate. January homes result in the argument slot, so they are bits 8-31 - of n; where hs_string_to_boolean inlines this function they come from its 'push ecx' slot, which holds - hs_cast's table index 254 (0x000000fe), so they are zero there. No January consumer reads a boolean - cell beyond its low byte, but hs_return and the pass-through forms copy the whole long into hs thread - stacks and hs globals, so these bytes reach the saved-game CRC and the save files - (game_state_write_to_persistent_storage, game_state_write_to_file, game_state_write_core). */ + result = 0; *(boolean *)&result = n==0; return result; @@ -1675,12 +1665,7 @@ static long hs_short_to_boolean( { long result; - /* BUG: only the low byte of this uninitialised long is written, and the whole long is returned; its - upper three bytes are indeterminate. January homes result in the argument slot, so they are bits 8-31 - of s. No January consumer reads a boolean cell beyond its low byte, but hs_return and the pass-through - forms copy the whole long into hs thread stacks and hs globals, so these bytes reach the saved-game CRC - and the save files (game_state_write_to_persistent_storage, game_state_write_to_file, - game_state_write_core). */ + result = 0; *(boolean *)&result = (short)s==0; return result; @@ -1733,12 +1718,7 @@ static long hs_real_to_short( { long result; - /* BUG: only the low word of this uninitialised long is written, and the whole long is returned; its - upper word is indeterminate. January homes result in the argument slot, so it is the upper word of r's - bit pattern. No January consumer reads a short cell beyond its low word, but hs_return and the - pass-through forms copy the whole long into hs thread stacks and hs globals, so these bytes reach the - saved-game CRC and the save files (game_state_write_to_persistent_storage, game_state_write_to_file, - game_state_write_core). */ + result = 0; *(short *)&result = (short)*(real *)&r; return result; @@ -1755,12 +1735,7 @@ static long hs_long_to_short( { long result; - /* BUG: only the low word of this uninitialised long is written, and the whole long is returned; its - upper word is indeterminate. January homes result in the argument slot, so it is the upper word of l - (January returns l unchanged). No January consumer reads a short cell beyond its low word, but - hs_return and the pass-through forms copy the whole long into hs thread stacks and hs globals, so these - bytes reach the saved-game CRC and the save files (game_state_write_to_persistent_storage, - game_state_write_to_file, game_state_write_core). */ + result = 0; *(short *)&result = (short)l; return result; @@ -1999,13 +1974,7 @@ void hs_evaluate_equality( if (function_index==_hs_function_not_equal) equal = !equal; - /* BUG: only the low byte of this uninitialised long is written, and the whole long is passed to - hs_return; its upper three bytes are indeterminate. January reuses the function_index argument - slot, which parameter_types has just filled with the argument type twice, so the long is - (type<<16)|equal. No January consumer reads a boolean cell beyond its low byte, but hs_return and - the pass-through forms copy the whole long into hs thread stacks and hs globals, so these bytes - reach the saved-game CRC and the save files (game_state_write_to_persistent_storage, - game_state_write_to_file, game_state_write_core). */ + result_long = 0; *(boolean *)&result_long = equal; hs_return(thread_index, result_long); } @@ -2051,13 +2020,7 @@ void hs_evaluate_inequality( break; } - /* BUG: only the low byte of this uninitialised long is written, and the whole long is passed to - hs_return; its upper three bytes are indeterminate. January reuses the function_index argument - slot, where it has just spilled value1, so they are bits 8-31 of value1's bit pattern. No January - consumer reads a boolean cell beyond its low byte, but hs_return and the pass-through forms copy - the whole long into hs thread stacks and hs globals, so these bytes reach the saved-game CRC and - the save files (game_state_write_to_persistent_storage, game_state_write_to_file, - game_state_write_core). */ + result_long = 0; *(boolean *)&result_long = comparison; hs_return(thread_index, result_long); } @@ -2107,13 +2070,7 @@ void hs_evaluate_logical( } else { - /* BUG: only the low byte of this uninitialised long is written, and the whole long is passed to - hs_return; its upper three bytes are indeterminate. January reuses the initialize argument slot, - so they are the upper bytes of the dword hs_thread_main pushed for initialize (stack residue from - hs_thread_main's frame). No January consumer reads a boolean cell beyond its low byte, but - hs_return and the pass-through forms copy the whole long into hs thread stacks and hs globals, so - these bytes reach the saved-game CRC and the save files (game_state_write_to_persistent_storage, - game_state_write_to_file, game_state_write_core). */ + result_long = 0; *(boolean *)&result_long = *result; hs_return(thread_index, result_long); diff --git a/source/input/input_abstraction.c b/source/input/input_abstraction.c index 137a9845..5caa4942 100644 --- a/source/input/input_abstraction.c +++ b/source/input/input_abstraction.c @@ -265,9 +265,27 @@ static void keyboard_controls_update( /* (diagonals on the unit circle) */ real length = x && y ? 0.70710678f : 1.f; + /* port: crouched, below the full stick's 0.98 that stands a grounded + biped up (player_control.c), as a stick's crouch walk is */ + if (TEST_FLAG(held, HALO_KEYBOARD_CROUCH)) + length *= 0.97f; + state->forward_movement = y * length; state->strafe = -x * length; } + { + /* port: the touch controls' stick (port/linux/src/xinput_sdl.c) + moves the player whatever the profile's sticks do, as the keys do */ + extern int halo_linux_touch_move(short controller_index, real *forward, real *strafe); + real forward; + real strafe; + + if (halo_linux_touch_move((short)controller_index, &forward, &strafe)) + { + state->forward_movement = forward; + state->strafe = strafe; + } + } return; } @@ -318,6 +336,50 @@ real input_abstraction_port_primary_trigger( return gamepad->buttons[button_index] ? 1.f : 0.f; } +/* port: the game control on each of the controller's buttons by its +profile's mapping (NONE for none), numbered as the gamepad's buttons are +(input.h): the touch controls name their buttons by it (touch_game.c) */ +void input_abstraction_port_button_controls( + short controller_index, + short *controls) +{ + short button_index; + short control_index; + + for (button_index = 0; button_index < NUMBER_OF_GAMEPAD_BUTTONS; button_index++) + { + controls[button_index] = NONE; + } + if (controller_index < 0 || controller_index >= MAXIMUM_GAMEPADS) + return; + for (control_index = 0; control_index < NUMBER_OF_GAME_CONTROLS; control_index++) + { + byte button_index_bound = input_abstraction_globals.player_control_preferences[controller_index]. + game_control_to_xbox_buttons[control_index]; + + if (button_index_bound < NUMBER_OF_GAMEPAD_BUTTONS && controls[button_index_bound] == NONE) + { + controls[button_index_bound] = control_index; + } + } +} + +/* port: whether the controller's player looks inverted, as its right stick +does (input_abstraction_update): its profile's invert, or flying an aircraft +with its profile's aircraft invert; the touch controls' swipe follows it +(player_control.c) */ +boolean input_abstraction_port_look_inverted( + short controller_index) +{ + struct game_input_preferences const *preferences; + + if (controller_index < 0 || controller_index >= MAXIMUM_GAMEPADS) + return FALSE; + preferences = &input_abstraction_globals.player_control_preferences[controller_index]; + return preferences->invert_look || + (preferences->invert_look_aircraft_control && local_player_is_piloting_aircraft(controller_index)); +} + /* ---------- public code */ void input_abstraction_initialize( diff --git a/source/input/input_abstraction.h b/source/input/input_abstraction.h index 0724fce2..e07904f2 100644 --- a/source/input/input_abstraction.h +++ b/source/input/input_abstraction.h @@ -62,6 +62,13 @@ boolean input_abstraction_port_action_only( short controller_index); real input_abstraction_port_primary_trigger( short controller_index); +/* port: the game control on each of the controller's buttons, and the +player's look inverted as its stick is (the touch controls) */ +void input_abstraction_port_button_controls( + short controller_index, + short *controls); +boolean input_abstraction_port_look_inverted( + short controller_index); /* ---------- globals */ diff --git a/source/interface/first_person_weapons.c b/source/interface/first_person_weapons.c index 300250dc..a31bd13a 100644 --- a/source/interface/first_person_weapons.c +++ b/source/interface/first_person_weapons.c @@ -128,6 +128,7 @@ symbols in this file: #include "halo_vr.h" #include "vr_arms.h" #endif +#include "view_fov.h" /* port: port/linux/game/view_fov.c */ /* ---------- constants */ @@ -580,6 +581,9 @@ void first_person_weapon_draw( if (halo_vr_scope_pass()) return; #endif + /* port: not drawn with display.viewmodel_visible off (view_fov.c) */ + if (!viewmodel_is_visible()) + return; if (render.local_player_index!=NONE) { struct first_person_weapon *first_person_weapon= first_person_weapon_get(render.local_player_index); diff --git a/source/interface/hud.c b/source/interface/hud.c index 536d1b10..492a6f4b 100644 --- a/source/interface/hud.c +++ b/source/interface/hud.c @@ -1003,7 +1003,8 @@ static void hud_draw_players( } /* port: in multiplayer and network co-op, players' names above their heads -(display.player_names: "all", "allies", "enemies" or "none"). An ally's goes +(display.player_names: "all", "allies", "enemies" or "none"), a speaker +beside the name of one talking in voice chat. An ally's goes above the triangle the game draws over teammates; an enemy's only within the motion sensor's reach, while the view sees them and they are not camouflaged, so that it never gives away where they hide. Whose names show @@ -1014,6 +1015,10 @@ allies' if it shows only friends (game_engine_draw_object_in_motion_sensor). */ const char *config_string(const char *name); double config_real(const char *name); unsigned long config_changes(void); +/* port/linux/game/network_voice.c's: a player talking in voice chat (its +machine), and its speaker drawn */ +boolean network_voice_machine_speaking(long machine_index); +void network_voice_draw_icon(rectangle2d const *bounds, boolean muted, real alpha); enum { @@ -1106,6 +1111,27 @@ static real hud_player_name_enemy_range( return hud_globals ? hud_globals->defaults.motion_sensor_range : 0.0f; } +/* port: a player's name's colour above their head, an ally's (the HUD's +text) or an enemy's (red), whole (game_engine.c's list of who talks in +voice chat colours its names the same) */ +void hud_player_name_color( + boolean ally, + real_argb_color *color) +{ + if (ally) + { + hud_get_text_color(color); + color->alpha = 1.0f; + } + else + { + color->alpha = 1.0f; + color->red = 1.0f; + color->green = 0.3f; + color->blue = 0.25f; + } +} + static void hud_draw_player_name( long player_index, boolean ally, @@ -1212,18 +1238,15 @@ static void hud_draw_player_name( for (index = 0; index < (short)NUMBEROF(player->name); index++) name[index] = player->name[index]; name[NUMBEROF(player->name)] = 0; + hud_player_name_color(ally, &color); if (ally) { - hud_get_text_color(&color); /* (whole up to 15 world units away, then fading to 0.4 at 75) */ depth_factor = 1.0f - (-view_position.z - 15.0f) / 60.0f; color.alpha = PIN(depth_factor, 0.4f, 1.0f); } else { - color.red = 1.0f; - color.green = 0.3f; - color.blue = 0.25f; /* (whole up to four fifths of the range, then fading out) */ color.alpha = PIN((enemy_range - distance) / (0.2f * enemy_range), 0.0f, 1.0f); } @@ -1232,6 +1255,25 @@ static void hud_draw_player_name( rasterizer_text_set_scale(hud_player_name_scale(), (real)x, (real)y); rasterizer_draw_unicode_string(&bounds, NULL, NULL, 0, name); rasterizer_text_set_scale(1.0f, 0.0f, 0.0f); + /* (talking in voice chat: the speaker just left of the name, centred on + its capitals, where they are drawn, scaled; fading with it) */ + if (network_voice_machine_speaking(player->network_player_data.machine_index)) + { + real scale = hud_player_name_scale(); + rectangle2d text; + rectangle2d cursor; + rectangle2d icon; + short size = (short)((font->ascending_height + font->descending_height) * scale); + short middle; + + draw_unicode_string_compute_bounds(&bounds, name, &text, &cursor); + middle = (short)(y + (draw_unicode_string_capital_middle(&bounds, name) - y) * scale); + icon.x1 = (short)(x + (text.x0 - x) * scale - 3.0f * scale); + icon.x0 = (short)(icon.x1 - size); + icon.y0 = (short)(middle - size / 2); + icon.y1 = (short)(icon.y0 + size); + network_voice_draw_icon(&icon, FALSE, color.alpha); + } return; } diff --git a/source/interface/hud.h b/source/interface/hud.h index 46c1474a..1a511e6e 100644 --- a/source/interface/hud.h +++ b/source/interface/hud.h @@ -145,6 +145,12 @@ void hud_play_sound( word *played_flags, short maximum_sound_count); /* port: what sound_indices holds */ +/* port: a player's name's colour above their head: an ally's, or an +enemy's (hud.c) */ +void hud_player_name_color( + boolean ally, + union real_argb_color *color); + /* ---------- globals */ extern struct hud_globals_definition *hud_globals; diff --git a/source/interface/hud_draw.c b/source/interface/hud_draw.c index ce9d0807..4e6436f2 100644 --- a/source/interface/hud_draw.c +++ b/source/interface/hud_draw.c @@ -106,6 +106,7 @@ symbols in this file: #include "units/units.h" #include "cache_file_formats.h" /* port: port/linux/game/cache_file_formats.c */ #include "custom_edition_cache.h" +#include "view_fov.h" /* port: port/linux/game/view_fov.c */ /* ---------- constants */ @@ -395,7 +396,8 @@ static void hud_draw_bitmap_internal( real_rectangle2d const *bounds, real_vector2d const *xy_scale, real theta, - pixel32 color); + pixel32 color, + real reticle_scale); /* port: (display.fov, view_fov.c) */ static void hud_draw_bitmap_with_meter( void *meter_parameters, struct bitmap_data const *bitmap, @@ -787,7 +789,7 @@ static void hud_draw_multitexture_overlay( { static real theta; struct multitexture_overlay_hud_element_effector_definition *effector; - real source_value; + real source_value = 0.0f; real dest_value; real_rgb_color dest_color; @@ -839,10 +841,6 @@ static void hud_draw_multitexture_overlay( break; } - /* The switch above has no default: a source value outside the eight enumerators - * leaves source_value unassigned for the interpolation below. Not shown reachable: - * the value comes from hud tag data, which was not scanned. Source-policy approval - * pending (2026-09-27 audit). */ if (effector->in_bounds[1] == effector->in_bounds[0] || effector->out_bounds[1] == effector->out_bounds[0]) { @@ -1555,7 +1553,8 @@ static void hud_draw_bitmap_internal( real_rectangle2d const *bounds, real_vector2d const *xy_scale, real theta, - pixel32 color) + pixel32 color, + real reticle_scale) { long return_eip = get_return_eip(); long stack_buffer[STACK_BUFFER_LENGTH]; @@ -1586,6 +1585,25 @@ static void hud_draw_bitmap_internal( vertices[vertex_index].color = color; } + if (reticle_scale != 1.0f) + { + real center_x = (render.camera.window_bounds.x1 + render.camera.window_bounds.x0) / 2 - + render.camera.viewport_bounds.x0; + real center_y = (render.camera.window_bounds.y1 + render.camera.window_bounds.y0) / 2 - + render.camera.viewport_bounds.y0; + + /* port: the reticle scaled about the view's centre, where it aims, + with display.fov (view_fov.c): its offsets and separate pieces + shrink with its picture */ + for (vertex_index = 0; vertex_index < 4; vertex_index++) + { + vertices[vertex_index].position.x = center_x + + (vertices[vertex_index].position.x - center_x) * reticle_scale; + vertices[vertex_index].position.y = center_y + + (vertices[vertex_index].position.y - center_y) * reticle_scale; + } + } + csmemset(¶meters, 0, sizeof(parameters)); parameters.map_texture_scale[0].j = 1.0f; parameters.map_texture_scale[0].i = 1.0f; @@ -1669,7 +1687,10 @@ static void hud_draw_bitmap_with_meter( &bounds, &xy_scale, theta, - color); + color, + /* port: a reticle at the centre keeps to its aim (view_fov.c) */ + is_crosshair_bitmap && absolute_placement->corner == _hud_anchor_center ? + render_fov_reticle_scale(render.local_player_index) : 1.0f); match_assert_stack_frame("c:\\halo\\SOURCE\\interface\\hud_draw.c", 814); @@ -1831,7 +1852,8 @@ void hud_draw_bitmap_direct( &bounds, &xy_scale, theta, - color); + color, + 1.0f); match_assert_stack_frame("c:\\halo\\SOURCE\\interface\\hud_draw.c", 856); diff --git a/source/interface/hud_messaging.c b/source/interface/hud_messaging.c index 5eb0af3e..29248cc4 100644 --- a/source/interface/hud_messaging.c +++ b/source/interface/hud_messaging.c @@ -1685,7 +1685,17 @@ void hud_messaging_update( } else { - error(_error_silent, "help text cannot use custom icons"); + /* port: help text has no custom icons: this one + is left out (the button icons' list is not + theirs, and its index is still NONE) */ + static boolean help_custom_icon_reported = FALSE; + + if (!help_custom_icon_reported) + { + help_custom_icon_reported = TRUE; + error(_error_silent, "help text cannot use custom icons"); + } + break; } } else @@ -1693,7 +1703,7 @@ void hud_messaging_update( icon_index = element->data; } - if (icon_index < hud_globals->messaging.button_icons.count) + if (icon_index >= 0 && icon_index < hud_globals->messaging.button_icons.count) { struct icon_hud_element_definition const *icon; @@ -1875,10 +1885,15 @@ void hud_messaging_update( wchar_t formatted[256]; short value_scale = MAX(item->item.hud_message_value_scale, 1); - usprintf( - formatted, - item_text, - message->quantity / value_scale); + /* port: the item's text (the map's) is the format: as + one only if it takes the count alone, and bounded */ + if (item_text && ustring_format_takes(item_text, "d")) + { + usnprintf(formatted, NUMBEROF(formatted), item_text, message->quantity / value_scale); + formatted[NUMBEROF(formatted) - 1] = 0; + } + else + ustrncpy_terminated(formatted, item_text, NUMBEROF(formatted)); rasterizer_draw_unicode_string( &message_bounds, NULL, diff --git a/source/interface/hud_nav_points.c b/source/interface/hud_nav_points.c index c253d21f..f30b2705 100644 --- a/source/interface/hud_nav_points.c +++ b/source/interface/hud_nav_points.c @@ -912,7 +912,7 @@ void hud_render_nav_points( for (nav_point_index = 0; nav_point_indexnav_points[nav_point_index]; - real_point3d position; + real_point3d position = { 0 }; if (nav_point->nav_index==NONE || nav_point->reference_index==NONE || @@ -953,10 +953,6 @@ void hud_render_nav_points( } break; - /* position is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_assert("c:\\halo\\SOURCE\\interface\\hud_nav_points.c", 725, !"unreachable"); break; @@ -1058,11 +1054,6 @@ static void hud_update_nav_point_local_player( break; } - /* The default arm leaves position unassigned. Not reached: every store to - * nav_point->type in this file writes one of the three types handled above or NONE - * (the map-start memset and the deactivations), the nav point array is private to - * this file, and NONE entries are skipped above. Source-policy approval pending - * (2026-09-27 audit). */ position.z += nav_point->z_offset; nav_point->screen_type = hud_get_nav_point_render_type( local_player_index, diff --git a/source/interface/hud_sounds.c b/source/interface/hud_sounds.c index 85ce1263..8cb11070 100644 --- a/source/interface/hud_sounds.c +++ b/source/interface/hud_sounds.c @@ -77,7 +77,9 @@ void hud_play_sound( struct hud_sound_definition const *sound = TAG_BLOCK_GET_ELEMENT(sounds, absolute_sound_index, struct hud_sound_definition); - if (state_flags & sound->state_flags) + /* port: a HUD sound with no tag never plays (a Custom Edition map's + HUD can have one; it asserted here) */ + if (sound->sound.index != NONE && (state_flags & sound->state_flags)) { switch (sound->sound.group_tag) { diff --git a/source/interface/hud_unit.c b/source/interface/hud_unit.c index f1f2f31a..4b13657a 100644 --- a/source/interface/hud_unit.c +++ b/source/interface/hud_unit.c @@ -607,8 +607,8 @@ void hud_render_damage_indicators( { struct hud_damage_indicators_definition *definition = &hud_globals->damage_indicators; - real theta; - real_point2d screen_position; + real theta = 0.0f; + real_point2d screen_position = { 0 }; real scale = hud_globals_get_scale(local_player_count() > 1); byte damage_indicators[NUMBER_OF_HUD_DAMAGE_INDICATOR_DIRECTIONS]; short direction; @@ -671,10 +671,6 @@ void hud_render_damage_indicators( render.camera.viewport_bounds.y1) / 2); break; - /* theta and screen_position are left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_assert( "c:\\halo\\SOURCE\\interface\\hud_unit.c", diff --git a/source/interface/hud_weapon.c b/source/interface/hud_weapon.c index f1d94894..efc5ae5f 100644 --- a/source/interface/hud_weapon.c +++ b/source/interface/hud_weapon.c @@ -897,10 +897,7 @@ static void hud_update_weapon_local_player( "c:\\halo\\SOURCE\\interface\\hud_weapon.c", 0x16E, !"unreachable"); - /* BUG (preserved for exact matching): January (T+0x404) and the later /Od build (0x638ac2) - leave result unassigned in this arm. The arm is unreachable in defined execution: - crosshair_index only takes the values 0..NUMBER_OF_CROSSHAIR_STATES-1 (0..18), and each of - those 19 states has a case above that assigns result. */ + result = 0; break; } @@ -1353,7 +1350,8 @@ static void crosshairs_draw( color, in_multiplayer, interface_bitmap, - TRUE); + /* port: not scaled with display.fov (view_fov.c) */ + FALSE); } else { @@ -1369,7 +1367,10 @@ static void crosshairs_draw( color, in_multiplayer, bitmap_group->type == _bitmap_group_type_interface_bitmaps, - TRUE); + /* port: a reticle, scaled with display.fov + (view_fov.c); a scope's picture keeps its + place */ + state_index != _crosshair_state_zoom); } } } diff --git a/source/interface/motion_sensor.c b/source/interface/motion_sensor.c index 9607f857..be07f7fe 100644 --- a/source/interface/motion_sensor.c +++ b/source/interface/motion_sensor.c @@ -329,11 +329,18 @@ static void motion_sensor_update( } else { - short local_player_slots[4] = { 0 }; + /* port: each local player's blip count, by local player, and the + local players in scan order, as two arrays of four. The Xbox's kept + both in one array of four (the counts first, the order from 2) and + had two camera positions, all it needed for its two player co-op; + three or four local players (split screen) wrote past both and into + stack_buffer ("corrupt stack at 0") */ + short blip_counts[MAXIMUM_LOCAL_PLAYERS] = { 0 }; + short scan_players[MAXIMUM_LOCAL_PLAYERS]; short player_count = local_player_count(); short local_player_index = local_player_get_next(NONE); struct object_iterator iter; - real_point3d camera_positions[2]; + real_point3d camera_positions[MAXIMUM_LOCAL_PLAYERS]; boolean done = FALSE; short player_scan_index; @@ -352,7 +359,7 @@ static void motion_sensor_update( local_player_get_player_index(local_player_index))->unit_index; short blip_index; - local_player_slots[player_scan_index + 2] = local_player_index; + scan_players[player_scan_index] = local_player_index; camera_positions[local_player_index].x = 0.0f; camera_positions[local_player_index].y = 0.0f; camera_positions[local_player_index].z = 0.0f; @@ -397,7 +404,7 @@ static void motion_sensor_update( player_scan_index++) { short scan_player_index = - local_player_slots[player_scan_index + 2]; + scan_players[player_scan_index]; long player_index = local_player_get_player_index(scan_player_index); @@ -406,7 +413,7 @@ static void motion_sensor_update( local_player_get_player_index(scan_player_index)) ->unit_index != NONE) { - short blip_index = local_player_slots[scan_player_index]; + short blip_index = blip_counts[scan_player_index]; if (blip_index >= MAXIMUM_MOTION_SENSOR_BLIPS) { @@ -452,7 +459,7 @@ static void motion_sensor_update( player->unit_indices[blip_index] = iter.index; sensor->blip_count++; - local_player_slots[scan_player_index] = + blip_counts[scan_player_index] = (short)(blip_index + 1); } } diff --git a/source/interface/player_ui.c b/source/interface/player_ui.c index 9c92e7e5..76b7d158 100644 --- a/source/interface/player_ui.c +++ b/source/interface/player_ui.c @@ -367,6 +367,13 @@ struct player_profile *player_ui_get_edit_player_profile( return result; } +/* port: the saved game file being edited (NONE for none) */ +long player_ui_get_edit_profile_index( + void) +{ + return player_ui_globals.edit_profile_index; +} + struct game_variant *player_ui_get_edit_playlist_profile( void) { @@ -805,6 +812,24 @@ boolean player_ui_save_profile( player_profile_save( player_ui_globals.edit_profile_index, &player_ui_globals.edit_profile.current.player); + /* port: a local player playing with the profile gets its + changes now (its button and stick layouts, sensitivity, + inversion), not when the profile is next chosen */ + { + short local_player_index; + + for (local_player_index = 0; local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; local_player_index++) + { + if (player_ui_globals.local_players[local_player_index].active_profile_index == + player_ui_globals.edit_profile_index) + { + player_ui_set_active_player_profile( + local_player_index, + player_ui_globals.edit_profile_index, + &player_ui_globals.edit_profile.current.player); + } + } + } result = TRUE; break; @@ -1210,6 +1235,35 @@ void player0_look_invert_pitch( return; } +/* port: a saved game file deleted (delete_enumerated_saved_game_file): the +files after it moved down its memory unit's list, so the indices of the +players' profiles and the one being edited follow them, and none is left +naming the file deleted. A player profile deleted before the one in use +left that one's index naming the next file, often the next profile made, +which the profile in use was then saved over (taking its name, while its +folder kept the new profile's name, which then read as in use). */ +void player_ui_saved_game_file_removed( + long removed_index) +{ + short local_player_index; + + for (local_player_index = 0; local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; local_player_index++) + { + player_ui_globals.local_players[local_player_index].active_profile_index = + saved_game_file_index_after_removal( + player_ui_globals.local_players[local_player_index].active_profile_index, + removed_index); + } + player_ui_globals.edit_profile_index = saved_game_file_index_after_removal( + player_ui_globals.edit_profile_index, + removed_index); + player1_last_used_profile_index = saved_game_file_index_after_removal( + player1_last_used_profile_index, + removed_index); + + return; +} + static void clear_profile_edit_data( void) { diff --git a/source/interface/player_ui.h b/source/interface/player_ui.h index 59a51f6c..1a196e97 100644 --- a/source/interface/player_ui.h +++ b/source/interface/player_ui.h @@ -43,6 +43,9 @@ long player_ui_get_active_player_profile_index( short local_player_index); struct player_profile *player_ui_get_edit_player_profile( void); +/* port: the saved game file being edited (NONE for none) */ +long player_ui_get_edit_profile_index( + void); struct game_variant *player_ui_get_edit_playlist_profile( void); /* port: the PC options (game_engine.h) of the gametype being edited, and of @@ -114,6 +117,9 @@ boolean player_ui_prompt_user_to_rename_edit_profile( void); void player0_look_invert_pitch( boolean invert); +/* port: a saved game file deleted (saved_game_files.c) */ +void player_ui_saved_game_file_removed( + long removed_index); /* ---------- globals */ diff --git a/source/interface/progress_bar.c b/source/interface/progress_bar.c index 2559f3c9..1e0a3ac9 100644 --- a/source/interface/progress_bar.c +++ b/source/interface/progress_bar.c @@ -2066,6 +2066,9 @@ static void progress_bar_render( IDirect3DDevice8_SetTextureStageState(global_d3d_device, stage, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); IDirect3DDevice8_SetTexture(global_d3d_device, stage, &back_buffer_texture); } + /* (back_buffer_texture has what it needs of the surface; the + reference IDirect3DDevice8_GetBackBuffer gave is given back) */ + IDirect3DSurface8_Release(back_buffer); if (progress<0.9f) draw_fade_layer(0.f, 0.f, 0.9f); @@ -2221,6 +2224,9 @@ static void progress_bar_make_stuff_ready( IDirect3DDevice8_SetRenderTarget(global_d3d_device, back_buffer, depth_buffer); IDirect3DDevice8_Clear(global_d3d_device, 0, NULL, 0xf0, 0, 0.f, 0); IDirect3DDevice8_SetRenderTarget(global_d3d_device, front_buffer, depth_buffer); + IDirect3DSurface8_Release(back_buffer); + IDirect3DSurface8_Release(front_buffer); + IDirect3DSurface8_Release(depth_buffer); } return; diff --git a/source/interface/terminal.c b/source/interface/terminal.c index c2106b4f..93b66fd7 100644 --- a/source/interface/terminal.c +++ b/source/interface/terminal.c @@ -357,7 +357,7 @@ void terminal_printf( ...) { real_argb_color default_terminal_printf_color; - char *arglist; + va_list arglist; va_start(arglist, format); diff --git a/source/interface/ui_widget.c b/source/interface/ui_widget.c index 481f2d01..4f44815e 100644 --- a/source/interface/ui_widget.c +++ b/source/interface/ui_widget.c @@ -1370,6 +1370,14 @@ static boolean ui_widget_load_children_recursive( /* port: whether the tag is one of the menus' (port/linux/game/menu_tags.c) */ boolean pc_menu_tag( long tag_index); +/* port: whether this machine has, or is adding, a second player: co-op, +split screen, the lobby's (port/linux/game/menu_functions.c) */ +unsigned char pc_menu_split_players( + void); +/* port: local_player_count, 0 before the players' globals are made +(source/game/players.c) */ +short players_port_local_player_count( + void); /* port: where in its widget, and how large, the menus draw a frame of ui.map's that they scale (port/linux/game/menu_tags.c) */ boolean pc_menu_frame_placement( @@ -1446,8 +1454,9 @@ static wchar_t *spinner_string_list_get_string( /* ---------- globals */ /* port: text boxes' string list indices from here are the descriptions of -spinners' extra items (kills_to_win_extra_descriptions) */ -#define SPINNER_EXTRA_DESCRIPTION_BASE 0x5000 +spinners' extra items (kills_to_win_extra_descriptions), above the Custom +Edition maps' display indices (custom_edition_maps.c keeps them below 0x7000) */ +#define SPINNER_EXTRA_DESCRIPTION_BASE 0x7000 /* ... and the pixels a spinner with extra items is wider (for three digits) */ #define SPINNER_EXTRA_WIDTH 12 @@ -5597,6 +5606,10 @@ static void widget_instance_render_spinner_list( return; } +/* port/linux/game/menu_functions.c's: a text box drawn, at the bounds its +text was drawn in (the lobby's speaker icons) */ +void menu_functions_text_box_drawn(struct widget_instance *widget, rectangle2d const *bounds); + /* ---------- the mouse (desktop builds) The menus were made for a controller: the d-pad moves the focus through a @@ -5621,7 +5634,12 @@ enum ui_mouse_target_kind list steps to it, and a click then presses A */ _ui_mouse_target_list_slot, /* a button's icon and label in a screen's key: a click presses it */ - _ui_mouse_target_button + _ui_mouse_target_button, + /* the band of a list's slot rows left of its first slot (a list showing + several items): a click steps the list back by one */ + _ui_mouse_target_list_back, + /* the band right of its last slot: a click steps it forward by one */ + _ui_mouse_target_list_forward }; struct ui_mouse_target @@ -5630,13 +5648,30 @@ struct ui_mouse_target rectangle2d bounds; short kind; short button_index; - /* (a button's: where its icon begins) */ - short icon_x0; + /* where a value splits into previous / next: the middle of its box + before ui_mouse_widen_values widens it, so across the middle of the value + as drawn, where its arrows are, and up and down the middle of its row once + ui_mouse_merge_setting_rows has given it the row's height. A widened box + that a neighbour or the screen's edge holds to one side keeps it */ + short split_x; + short split_y; + /* a button's parts, kept so its area can be settled once the whole frame's + buttons are known (ui_mouse_fit_button_targets) */ + rectangle2d icon_bounds; + struct widget_instance *label; + rectangle2d label_bounds; }; static struct ui_mouse_target ui_mouse_targets[UI_MOUSE_MAXIMUM_TARGETS]; static long ui_mouse_target_count = 0; static boolean ui_mouse_noting_targets = FALSE; +/* the frame's targets are noted and settled (ui_mouse_fit_button_targets, +ui_mouse_merge_setting_rows, ui_mouse_widen_values) by one render only: +render_ui_widgets runs once per player's viewport, and once more for a +mirror (render.c), and a second render would note the same widgets again +and widen the values once more, which moves their split off the arrows. +Cleared with the targets, by ui_widgets_process_mouse */ +static boolean ui_mouse_targets_settled = FALSE; /* The presses the mouse makes, posted one a frame: the event queue keeps only the latest event posted between two frames (queue_event). What they @@ -5648,8 +5683,168 @@ static short ui_mouse_presses[UI_MOUSE_MAXIMUM_PRESSES]; static long ui_mouse_press_count = 0; static boolean ui_mouse_hover_pending = FALSE; static boolean ui_mouse_click_pending = FALSE; +/* whether the pointer moved the focus last (the d-pad clears it): a list +then doesn't scroll on at its end, which the pointer, moving, would make it +do every frame (menu_functions.c) */ +static boolean ui_mouse_focused_last = FALSE; static short ui_mouse_hover_x, ui_mouse_hover_y; static short ui_mouse_click_x, ui_mouse_click_y; +/* whether the latest pointer read was the touchscreen: the taller legend +areas, the merged setting rows and the widened values are for a finger only +(render_ui_widgets); the desktop mouse keeps their original areas */ +static boolean ui_mouse_pointer_is_touch = FALSE; + +/* a pointer coarser than a mouse: a finger, or (port) a VR controller's +ray, which points less steadily; the legend's buttons get the larger areas */ +static boolean ui_mouse_pointer_is_coarse( + void) +{ +#ifdef HALO_ANDROID + if (halo_vr_active()) + return TRUE; +#endif + return ui_mouse_pointer_is_touch; +} + +/* ---------- the debug view of the targets (debug.touch_targets) + +Drawn by render_ui_widgets in the pass that noted the targets: the list is +only valid from the render that noted them to the next +ui_widgets_process_mouse, which clears it. */ + +#define UI_DEBUG_MARK_MILLISECONDS 3000 +#define UI_DEBUG_KEYBOARD_RECTANGLES 96 + +struct ui_debug_mark +{ + boolean shown; + short x, y; + unsigned long time; +}; + +static struct ui_debug_mark ui_debug_down_mark, ui_debug_tap_mark; +/* the frames ui_widgets_process_mouse has run, and the one the latest tap arrived in */ +static long ui_debug_frame, ui_debug_click_frame; + +void platform_log(char const *format, ...); +int config_boolean(char const *name); + +/* whether debug.touch_targets is on; read once: settings do not change +while the game runs */ +static boolean ui_debug_targets_enabled( + void) +{ + static long enabled = NONE; + + if (enabled == NONE) + enabled = config_boolean("debug.touch_targets") != 0; + + return enabled != 0; +} + +/* outlines a rectangle, 2 menu pixels thick, in menu coordinates; the +caller has the menus' centering offset on (render.c sets +halo_screen_ui_offset(TRUE) around it; adding the offset inside would +double it) */ +static void ui_debug_draw_outline( + rectangle2d const *bounds, + pixel32 color) +{ + rectangle2d edge; + + edge = *bounds; + edge.y1 = edge.y0 + 2; + draw_quad(&edge, color); + edge = *bounds; + edge.y0 = edge.y1 - 2; + draw_quad(&edge, color); + edge = *bounds; + edge.x1 = edge.x0 + 2; + draw_quad(&edge, color); + edge = *bounds; + edge.x0 = edge.x1 - 2; + draw_quad(&edge, color); + + return; +} + +/* draws a cross of about 8 menu pixels at a mark that is still within its 3 seconds */ +static void ui_debug_draw_mark( + struct ui_debug_mark const *mark, + pixel32 color) +{ + rectangle2d arm; + + if (!mark->shown || + system_milliseconds() - mark->time > UI_DEBUG_MARK_MILLISECONDS) + { + return; + } + arm.x0 = mark->x - 4; + arm.x1 = mark->x + 4; + arm.y0 = mark->y - 1; + arm.y1 = mark->y + 1; + draw_quad(&arm, color); + arm.x0 = mark->x - 1; + arm.x1 = mark->x + 1; + arm.y0 = mark->y - 4; + arm.y1 = mark->y + 4; + draw_quad(&arm, color); + + return; +} + +/* outlines the targets the pointer code collected this frame and the last +finger-down and tap points; only for the render that notes the targets +(the first player's): a split-screen viewport that is not hit-tested shows +nothing or it would show outlines that no tap uses */ +static void ui_debug_draw_targets( + boolean first_players_render) +{ + long index; + + if (!first_players_render || !ui_debug_targets_enabled()) + return; + if (virtual_keyboard_active()) + { + rectangle2d rectangles[UI_DEBUG_KEYBOARD_RECTANGLES]; + long count = virtual_keyboard_target_rectangles(rectangles, UI_DEBUG_KEYBOARD_RECTANGLES); + + for (index = 0; index < count; index++) + ui_debug_draw_outline(&rectangles[index], 0xc0ffffff); + } + else + { + for (index = 0; index < ui_mouse_target_count; index++) + { + /* in the order of enum ui_mouse_target_kind; the band's two sides + share orange, which no other kind uses */ + static pixel32 const colors[] = { 0xc000ff00, 0xc04080ff, 0xc0ffff00, 0xc0ff0000, 0xc0ff8000, 0xc0ff8000 }; + short kind = ui_mouse_targets[index].kind; + + ui_debug_draw_outline(&ui_mouse_targets[index].bounds, colors[PIN(kind, 0, 5)]); + } + } + ui_debug_draw_mark(&ui_debug_down_mark, 0xffff00ff); + ui_debug_draw_mark(&ui_debug_tap_mark, 0xff00ffff); + + return; +} + +/* remembers a finger-down or tap point for the debug view, to show it +for 3 seconds */ +static void ui_debug_set_mark( + struct ui_debug_mark *mark, + short x, + short y) +{ + mark->shown = TRUE; + mark->x = x; + mark->y = y; + mark->time = system_milliseconds(); + + return; +} static void ui_mouse_press( short button_index) @@ -5676,6 +5871,11 @@ static long ui_mouse_child_index( return NONE; } +static void ui_mouse_list_directions( + struct widget_instance *widget, + short *back, + short *forward); + /* a list that shows several of its items at once */ static boolean ui_mouse_list_shows_several( struct widget_instance *widget) @@ -5756,6 +5956,105 @@ static boolean ui_mouse_widget_is_item( widget->type == _ui_widget_type_column_list; } +/* notes a target that steps a list by one, unless it is empty or the +targets are full */ +static void ui_mouse_note_list_step( + struct widget_instance *widget, + rectangle2d const *bounds, + short kind) +{ + struct ui_mouse_target *target; + + if (ui_mouse_target_count >= UI_MOUSE_MAXIMUM_TARGETS || + bounds->x0 >= bounds->x1 || + bounds->y0 >= bounds->y1) + { + return; + } + target = &ui_mouse_targets[ui_mouse_target_count++]; + target->widget = widget; + target->bounds = *bounds; + target->kind = kind; + target->button_index = NONE; + /* as ui_mouse_note_target sets them for any target that is not a + button: only a value's split and a button's parts are read, and no stale + field of an earlier frame's target stays in the slot */ + target->split_x = (bounds->x0 + bounds->x1) / 2; + target->split_y = (bounds->y0 + bounds->y1) / 2; + target->icon_bounds = *bounds; + target->label = NULL; + target->label_bounds = *bounds; + + return; +} + +/* notes the band that steps a list showing several items side by side: +left of the first slot (steps back), right of the last (steps forward). A +stock several-items list covers the whole 640x480 screen, so a tap beside +its slots cannot mean "step"; the band is limited to the slots' rows, and a +tap on the title, between slots or under them does nothing. The caller notes +nothing for the list itself, and widget_instance_render_recursive notes the +slots after this; ui_mouse_target_at takes the last target noted under a +point, so a slot would win over the band. A list the d-pad steps up and down +has no side to tap and gets no band */ +static void ui_mouse_note_slot_band( + struct widget_instance *widget, + struct ui_widget_definition const *definition, + point2d offset) +{ + struct widget_instance *child; + rectangle2d bounds; + rectangle2d slots; + short back, forward; + boolean found = FALSE; + + ui_mouse_list_directions(widget, &back, &forward); + if (back != _widget_event_dpad_left) + return; + for (child = widget->child; child; child = child->next) + { + rectangle2d slot; + + if (!child->visible) + continue; + /* where widget_instance_render_recursive draws the child: the + list's offset plus the child's own */ + slot = ui_widget_definition_get(child->definition_tag_index)->bounds; + slot.x0 += offset.x + child->horizontal_offset; + slot.x1 += offset.x + child->horizontal_offset; + slot.y0 += offset.y + child->vertical_offset; + slot.y1 += offset.y + child->vertical_offset; + if (!found) + { + slots = slot; + found = TRUE; + } + else + { + slots.x0 = MIN(slots.x0, slot.x0); + slots.x1 = MAX(slots.x1, slot.x1); + slots.y0 = MIN(slots.y0, slot.y0); + slots.y1 = MAX(slots.y1, slot.y1); + } + } + if (!found) + return; + bounds.y0 = slots.y0; + bounds.y1 = slots.y1; + bounds.x0 = definition->bounds.x0 + offset.x; + bounds.x1 = slots.x0; + ui_mouse_note_list_step(widget, &bounds, _ui_mouse_target_list_back); + bounds.x0 = slots.x1; + bounds.x1 = definition->bounds.x1 + offset.x; + ui_mouse_note_list_step(widget, &bounds, _ui_mouse_target_list_forward); + + return; +} + +/* notes a widget as a tap target of this frame, if it is one: an item, +a value, a list slot, or a legend button (kept in parts that +ui_mouse_fit_button_targets settles into the tap area); a list showing +several items notes the band beside its slots */ static void ui_mouse_note_target( struct widget_instance *widget, struct ui_widget_definition const *definition, @@ -5764,9 +6063,11 @@ static void ui_mouse_note_target( struct widget_instance *parent = widget->parent; struct ui_mouse_target *target; rectangle2d bounds = definition->bounds; + rectangle2d icon_bounds = bounds; + rectangle2d target_label_bounds = bounds; + struct widget_instance *target_label = NULL; short button_index; short kind; - short icon_x0; if (!ui_mouse_noting_targets || ui_mouse_target_count >= UI_MOUSE_MAXIMUM_TARGETS || @@ -5779,12 +6080,12 @@ static void ui_mouse_note_target( bounds.y0 += offset.y; bounds.y1 += offset.y; button_index = ui_mouse_key_button(widget); - icon_x0 = bounds.x0; if (button_index != NONE) { /* the icon and its label: the text beginning just right of it */ struct widget_instance *sibling; - rectangle2d label_bounds; + rectangle2d label_bounds = bounds; + struct widget_instance *label = NULL; short best_distance = 17; #ifdef HALO_ANDROID /* (VR: a controller points less steadily than a mouse: the label a @@ -5816,25 +6117,12 @@ static void ui_mouse_note_target( { best_distance = distance; label_bounds = sibling_bounds; + label = sibling; } } - if (best_distance < most_distance) - { - bounds.x1 = MAX(bounds.x1, label_bounds.x1); - bounds.y0 = MIN(bounds.y0, label_bounds.y0); - bounds.y1 = MAX(bounds.y1, label_bounds.y1); - } -#ifdef HALO_ANDROID - /* (and a margin about it: above and below, little to the sides, - where the next key is) */ - if (halo_vr_active()) - { - bounds.x0 -= 2; - bounds.x1 += 2; - bounds.y0 -= 4; - bounds.y1 += 4; - } -#endif + icon_bounds = bounds; + target_label = best_distance < most_distance ? label : NULL; + target_label_bounds = label_bounds; } else if (parent && ui_mouse_list_shows_several(parent)) { @@ -5848,14 +6136,18 @@ static void ui_mouse_note_target( } else if (widget->type == _ui_widget_type_spinner_list) { - /* a list showing several items is picked through them */ - if (!parent || ui_mouse_list_shows_several(widget) || !widget_instance_can_receive_events(widget)) + if (!widget_instance_can_receive_events(widget)) + return; + /* a list showing several items is picked through them, and stepped + by the band beside them */ + if (ui_mouse_list_shows_several(widget)) + { + ui_mouse_note_slot_band(widget, definition, offset); + return; + } + if (!parent) return; kind = _ui_mouse_target_value; - /* port: its arrows, drawn just outside it (the PC version's - spinners' header and footer bitmaps), its too */ - bounds.x0 -= 12; - bounds.x1 += 12; } else if (ui_mouse_widget_is_item(widget)) { @@ -5870,7 +6162,258 @@ static void ui_mouse_note_target( target->bounds = bounds; target->kind = kind; target->button_index = button_index; - target->icon_x0 = icon_x0; + target->split_x = (bounds.x0 + bounds.x1) / 2; + target->split_y = (bounds.y0 + bounds.y1) / 2; + target->icon_bounds = icon_bounds; + target->label = target_label; + target->label_bounds = target_label_bounds; + + return; +} + +/* makes a setting row its value: an item whose only value child is a value +stops being a target, and the value takes the row's height. A tap anywhere +else on the row would press A, which on a setting screen is ACCEPT. The +value keeps its width and left/right half rule. Only the value's parent is +the row; an item holding several values (a panel, a list of spinners) is not +a row and keeps its own area. Only exactly one value merges because growing +several values to the item's height would pile up boxes of which only the +last noted could be tapped. Runs after ui_mouse_fit_button_targets so the +legends are fitted against the whole row: a legend growing up into a row's +label area would press ACCEPT there */ +static void ui_mouse_merge_setting_rows( + void) +{ + long index; + long other; + long kept = 0; + + for (index = 0; index < ui_mouse_target_count; index++) + { + struct ui_mouse_target *row = &ui_mouse_targets[index]; + struct ui_mouse_target *only_value = NULL; + long value_count = 0; + + if (row->kind != _ui_mouse_target_item) + continue; + for (other = 0; other < ui_mouse_target_count; other++) + { + struct ui_mouse_target *value = &ui_mouse_targets[other]; + + if (value->kind == _ui_mouse_target_value && value->widget->parent == row->widget) + { + only_value = value; + value_count++; + } + } + if (value_count == 1) + { + only_value->bounds.y0 = row->bounds.y0; + only_value->bounds.y1 = row->bounds.y1; + row->kind = NONE; + } + } + /* in place, in order: the last target noted wins an overlap */ + for (index = 0; index < ui_mouse_target_count; index++) + { + if (ui_mouse_targets[index].kind != NONE) + ui_mouse_targets[kept++] = ui_mouse_targets[index]; + } + ui_mouse_target_count = kept; + + return; +} + +/* doubles the width of each value (a setting) around its centre: its arrows +are small for a finger. Each half grows outward by half the box's width. +The noted[] snapshot holds the boxes as noted; every measure is against them, +not as already widened, so two neighbours never take each other's room +whatever the order they were noted in. A box is not held to its row (the +last arrow of a row sits near the row's end) but stops at the edge of a +non-button target beside it that it overlaps vertically, at the midpoint of +the gap to another value facing it, and at the menu's drawable area. A box +never shrinks. Where a limit holds one side back, the split between previous +and next stays at the original centre where the arrows are. Runs after +ui_mouse_merge_setting_rows, once the rows have gone, and once a frame */ +static void ui_mouse_widen_values( + void) +{ + rectangle2d noted[UI_MOUSE_MAXIMUM_TARGETS]; + long index; + long other; + short screen_width = (short)halo_screen_width(); + + for (index = 0; index < ui_mouse_target_count; index++) + noted[index] = ui_mouse_targets[index].bounds; + for (index = 0; index < ui_mouse_target_count; index++) + { + struct ui_mouse_target *value = &ui_mouse_targets[index]; + rectangle2d const *box = ¬ed[index]; + short half = (short)((box->x1 - box->x0) / 2); + short left = (short)(box->x0 - half); + short right = (short)(box->x1 + half); + + if (value->kind != _ui_mouse_target_value) + continue; + value->split_x = (box->x0 + box->x1) / 2; + value->split_y = (box->y0 + box->y1) / 2; + left = MAX(left, (short)(-(screen_width - 640) / 2)); + right = MIN(right, (short)(640 + (screen_width - 640) / 2)); + for (other = 0; other < ui_mouse_target_count; other++) + { + struct ui_mouse_target const *beside = &ui_mouse_targets[other]; + rectangle2d const *beside_box = ¬ed[other]; + boolean is_value = beside->kind == _ui_mouse_target_value; + + if (other == index || beside->kind == _ui_mouse_target_button || + beside_box->y0 >= box->y1 || beside_box->y1 <= box->y0) + { + continue; + } + if (beside_box->x1 <= box->x0) + left = MAX(left, is_value ? (short)((beside_box->x1 + box->x0) / 2) : beside_box->x1); + else if (beside_box->x0 >= box->x1) + right = MIN(right, is_value ? (short)((box->x1 + beside_box->x0) / 2) : beside_box->x0); + } + value->bounds.x0 = MIN(left, box->x0); + value->bounds.x1 = MAX(right, box->x1); + } + + return; +} + +/* the right edge of a legend label's text as the game draws it, or NONE +when it cannot be measured (no text, no font, or icons in the string, which +the measuring does not account for) */ +static short ui_mouse_label_text_right( + struct widget_instance *label, + rectangle2d const *label_bounds) +{ + struct ui_widget_definition *definition = ui_widget_definition_get(label->definition_tag_index); + wchar_t const *text = label->parameters.text_box.text; + real_argb_color color = { 1.0f, 1.0f, 1.0f, 1.0f }; + rectangle2d bounds = *label_bounds; + rectangle2d text_bounds; + rectangle2d cursor_bounds; + + if (!text || !*text || definition->text_font.index == NONE || + definition->justification < 0 || definition->justification >= NUMBER_OF_TEXT_JUSTIFICATIONS || + string_has_icons_to_draw(text)) + { + return NONE; + } + /* as widget_instance_render_text_box places the text */ + bounds.x0 += definition->horizontal_offset; + bounds.y0 += definition->vertical_offset; + /* this leaves the label's font, justification and a white colour as the + global draw mode. That is harmless: it runs at the end of the widget + render, where the menu's own text has already left a menu font there, and + the menu, HUD, cinematic and terminal draws set their font before they + draw; the debug texts that set only a colour or format find a menu font + either way */ + draw_string_set_draw_mode(definition->text_font.index, NONE, definition->justification, 0, &color); + draw_unicode_string_compute_bounds(&bounds, text, &text_bounds, &cursor_bounds); + + return text_bounds.x1 == SHORT_MIN ? NONE : text_bounds.x1; +} + +/* settles the areas of the frame's legend buttons: each reaches its label's +drawn text (the text box can be far wider), stops before the next legend's +icon on its row, and for a touchscreen only grows up to 8 units upward (not +past an item, value or list slot above it) and in the screen's bottom strip +runs down to the screen's edge (not past an item, value or list slot below +it); recomputed from the parts kept at noting, so running again changes nothing */ +static void ui_mouse_fit_button_targets( + void) +{ + long index; + long other; + + for (index = 0; index < ui_mouse_target_count; index++) + { + struct ui_mouse_target *target = &ui_mouse_targets[index]; + short text_right; + + if (target->kind != _ui_mouse_target_button) + continue; + target->bounds = target->icon_bounds; + if (target->label) + { + target->bounds.x1 = MAX(target->bounds.x1, target->label_bounds.x1); + target->bounds.y0 = MIN(target->bounds.y0, target->label_bounds.y0); + target->bounds.y1 = MAX(target->bounds.y1, target->label_bounds.y1); + text_right = ui_mouse_label_text_right(target->label, &target->label_bounds); + if (text_right != NONE) + { + target->bounds.x1 = MAX(target->icon_bounds.x1, MIN(target->label_bounds.x1, text_right + 2)); + } + } + target->bounds.x0 = target->icon_bounds.x0; + } + for (index = 0; index < ui_mouse_target_count; index++) + { + struct ui_mouse_target *target = &ui_mouse_targets[index]; + + if (target->kind != _ui_mouse_target_button) + continue; + for (other = 0; other < ui_mouse_target_count; other++) + { + struct ui_mouse_target const *next = &ui_mouse_targets[other]; + + if (other != index && next->kind == _ui_mouse_target_button && + next->icon_bounds.x0 > target->icon_bounds.x0 && + next->bounds.y0 < target->bounds.y1 && next->bounds.y1 > target->bounds.y0 && + next->icon_bounds.x0 - 1 < target->bounds.x1) + { + target->bounds.x1 = MAX(next->icon_bounds.x0 - 1, target->icon_bounds.x1); + } + } + } + for (index = 0; index < ui_mouse_target_count; index++) + { + struct ui_mouse_target *target = &ui_mouse_targets[index]; + short top; + + if (target->kind != _ui_mouse_target_button || !ui_mouse_pointer_is_coarse()) + continue; + if (target->bounds.y1 >= 400) + { + short bottom = 480; + + /* running down to the screen's edge must not take the tap from an + item, value or list slot that a mod puts under the legend, as + growing upward must not above it: the legend is noted later, so it + would win the overlap */ + for (other = 0; other < ui_mouse_target_count; other++) + { + struct ui_mouse_target const *below = &ui_mouse_targets[other]; + + if (below->kind != _ui_mouse_target_button && + below->bounds.y0 >= target->bounds.y1 && + below->bounds.x0 < target->bounds.x1 && below->bounds.x1 > target->bounds.x0) + { + bottom = MIN(bottom, below->bounds.y0); + } + } + target->bounds.y1 = bottom; + } + /* growing upward must not take the tap from an item, value or list + slot above the legend: those keep their areas, and the legend is noted + later, so it would win the overlap */ + top = target->bounds.y0 - 8; + for (other = 0; other < ui_mouse_target_count; other++) + { + struct ui_mouse_target const *above = &ui_mouse_targets[other]; + + if (above->kind != _ui_mouse_target_button && + above->bounds.y1 <= target->bounds.y0 && + above->bounds.x0 < target->bounds.x1 && above->bounds.x1 > target->bounds.x0) + { + top = MAX(top, above->bounds.y1); + } + } + target->bounds.y0 = top; + } return; } @@ -5881,33 +6424,16 @@ static struct ui_mouse_target *ui_mouse_target_at( short y) { long index; - struct ui_mouse_target *button = NULL; for (index = ui_mouse_target_count - 1; index >= 0; index--) { - struct ui_mouse_target *target = &ui_mouse_targets[index]; - rectangle2d const *bounds = &target->bounds; + rectangle2d const *bounds = &ui_mouse_targets[index].bounds; - if (!(x >= bounds->x0 && x < bounds->x1 && y >= bounds->y0 && y < bounds->y1)) - continue; - /* port: a key's label's text box can be far wider than its text, over - the next key: of keys under the point, the one whose icon is nearest - on its left (each key from its icon to the next one's) */ - if (target->kind == _ui_mouse_target_button) - { - if (!button || - (target->icon_x0 <= x + 2 && (button->icon_x0 > x + 2 || target->icon_x0 > button->icon_x0))) - { - button = target; - } - continue; - } - if (!button) - return target; - break; + if (x >= bounds->x0 && x < bounds->x1 && y >= bounds->y0 && y < bounds->y1) + return &ui_mouse_targets[index]; } - return button; + return NULL; } static boolean ui_mouse_widget_has_focus( @@ -5929,6 +6455,7 @@ static void ui_mouse_give_focus( { struct widget_instance *ancestor; + ui_mouse_focused_last = TRUE; if (ui_mouse_widget_has_focus(widget)) return; widget_instance_give_focus_directly(widget_instance_get_topmost_parent(widget), widget); @@ -5942,6 +6469,14 @@ static void ui_mouse_give_focus( return; } +/* port: whether the pointer moved the menus' focus last, rather than the +d-pad (menu_functions.c's lists) */ +boolean ui_widget_port_pointer_focused( + void) +{ + return ui_mouse_focused_last; +} + /* the d-pad buttons that step a widget back and forward */ static void ui_mouse_list_directions( struct widget_instance *widget, @@ -6039,9 +6574,13 @@ static void ui_mouse_step_list_to_slot( } /* the widget the wheel steps: the innermost on the focus's way that the -d-pad steps through */ +d-pad steps through; a touch drag skips a list showing one value at a time +(a setting), moving between the rows, so a drag never changes a value; that +takes a tap on one of its halves; the desktop wheel keeps the game's own +d-pad reach and steps a setting's value */ static struct widget_instance *ui_mouse_wheel_widget( - struct widget_instance *root) + struct widget_instance *root, + boolean skip_settings) { struct widget_instance *result = NULL; struct widget_instance *widget; @@ -6050,6 +6589,8 @@ static struct widget_instance *ui_mouse_wheel_widget( { struct ui_widget_definition *definition = ui_widget_definition_get(widget->definition_tag_index); + if (skip_settings && widget->type == _ui_widget_type_spinner_list && !ui_mouse_list_shows_several(widget)) + continue; if (definition->flags & (FLAG(_widget_dpad_updown_tabs_thru_children_bit) | FLAG(_widget_dpad_leftright_tabs_thru_children_bit) | FLAG(_widget_dpad_updown_tabs_thru_list_items_bit) | @@ -6062,6 +6603,105 @@ static struct widget_instance *ui_mouse_wheel_widget( return result; } +/* whether the focus passes over a child of a list, which gives a press no +place to land; the PC version's lists skip their labels and hidden rows +(widget_instance_port_is_label) at both ends and wrap past them, so +counting one would let a drag step past the last usable row */ +static boolean ui_mouse_wheel_skips_child( + struct widget_instance *list, + struct widget_instance *child) +{ + return list->type == _ui_widget_type_column_list && pc_menu_tag(list->definition_tag_index) && + widget_instance_port_is_label(child); +} + +/* how many presses of a d-pad button can step the focus before it would +wrap around to the other end; the game's lists wrap, so a long touch drag +would lap a short list: the drag stops at the ends; the press goes down from +the screen to the first widget on the focus's way that tabs in its direction +(widget_instance_process_one_event_recursive), and the ends are that +widget's; 0 at the end or for a setting (changed by clicks only), NONE when +no widget tabs that way */ +static long ui_mouse_wheel_room( + struct widget_instance *root, + short button, + boolean forward) +{ + boolean horizontal = button == _widget_event_dpad_left || button == _widget_event_dpad_right; + struct widget_instance *widget; + + for (widget = root; widget; widget = widget->focused_child) + { + struct ui_widget_definition *definition = ui_widget_definition_get(widget->definition_tag_index); + boolean list = widget->type == _ui_widget_type_spinner_list || widget->type == _ui_widget_type_column_list; + struct widget_instance *child; + long room = 0; + + if (TEST_FLAG(definition->flags, horizontal ? + _widget_dpad_leftright_tabs_thru_children_bit : + _widget_dpad_updown_tabs_thru_children_bit) && + widget->focused_child) + { + /* a child the tab functions would skip gives no press a place to + land, so only the ones that take events, pass them on, or sit in + a list count */ + for (child = forward ? widget->focused_child->next : widget->focused_child->previous; + child; + child = forward ? child->next : child->previous) + { + struct ui_widget_definition *child_definition = ui_widget_definition_get(child->definition_tag_index); + + if ((child_definition->event_handlers.count > 0 || + TEST_FLAG(child_definition->flags, _widget_pass_unhandled_events_to_children_bit) || + list) && + !ui_mouse_wheel_skips_child(widget, child)) + { + room++; + } + } + + return room; + } + if (list && TEST_FLAG(definition->flags, horizontal ? + _widget_dpad_leftright_tabs_thru_list_items_bit : + _widget_dpad_updown_tabs_thru_list_items_bit)) + { + /* a setting is changed by clicks only */ + if (widget->type == _ui_widget_type_spinner_list && !ui_mouse_list_shows_several(widget)) + return 0; + if (widget->parameters.list.list_items && widget->parameters.list.number_of_items > 0) + { + room = forward ? + widget->parameters.list.number_of_items - 1 - widget->parameters.list.selected_index : + widget->parameters.list.selected_index; + } + else if (widget->focused_child) + { + /* a list of the tag's children, which the focus walks; the + focus skips a disabled child to the next that takes events, or + wraps (widget_instance_give_focus_directly): the main menu ends + with its hidden "game demos" */ + for (child = forward ? widget->focused_child->next : widget->focused_child->previous; + child; + child = forward ? child->next : child->previous) + { + if (!child->disabled && !ui_mouse_wheel_skips_child(widget, child)) + room++; + } + if (forward && widget->parameters.list.number_of_items > 0) + { + room = MIN(room, widget->parameters.list.number_of_items - 1 - + widget->parameters.list.selected_index); + } + } + + return MAX(room, 0); + } + } + + return NONE; +} + /* the menu the mouse drives: the first player's, or everyone's */ static struct widget_instance *ui_mouse_menu( void) @@ -8028,8 +8668,7 @@ boolean ui_widgets_menus_active( } #endif -/* the pointer's motion, clicks and wheel since the last frame, as the first -player's controller events */ + /* port: a row of a list to choose from (the PC version's menus' lists of gametypes, maps, profiles, levels and games: port/assets/menus) */ static boolean ui_mouse_selection_row( @@ -8039,6 +8678,49 @@ static boolean ui_mouse_selection_row( (!strncmp(widget->name, "list_item_", 10) || !strncmp(widget->name, "server_item_", 12)); } +/* logs a tap the menus resolved: where it was and the target the click acts +on; a click waits for the presses the mouse queued, so it is resolved +against the targets of a later frame than the one the tap arrived in; the +frames between are logged so the target can be read against the screen that +showed then; a value's line also gives the point where it splits into +previous and next */ +static void ui_debug_log_click( + short x, + short y, + struct ui_mouse_target const *target, + long frames) +{ + /* in the order of enum ui_mouse_target_kind */ + static char const *const kinds[] = { "item", "value", "list slot", "button", "list back", "list forward" }; + + if (target) + { + char const *kind = kinds[PIN(target->kind, 0, 5)]; + char const *name = tag_get_name(target->widget->definition_tag_index); + + /* a value steps by the side of its split the tap is on, and once + widened the split is not the middle of the logged box + (ui_mouse_widen_values) */ + if (target->kind == _ui_mouse_target_value) + { + platform_log("touch targets: tap at %d,%d hit %s %s [%d,%d,%d,%d] split %d,%d after %ld frames", x, y, + kind, name, target->bounds.x0, target->bounds.y0, target->bounds.x1, target->bounds.y1, + target->split_x, target->split_y, frames); + } + else + { + platform_log("touch targets: tap at %d,%d hit %s %s [%d,%d,%d,%d] after %ld frames", x, y, + kind, name, target->bounds.x0, target->bounds.y0, target->bounds.x1, target->bounds.y1, frames); + } + } + else + { + platform_log("touch targets: tap at %d,%d hit none after %ld frames", x, y, frames); + } + + return; +} + /* port: a press the menus post from their updates (menu_functions.c: the server browser's join, once its game is reached), posted where the mouse's are: one posted while the widgets update or draw would be overwritten by the @@ -8056,16 +8738,83 @@ void ui_widget_port_post_button( return; } +/* logs a tap while the virtual keyboard is up: the key or legend that took it */ +static void ui_debug_log_keyboard_tap( + short x, + short y, + long hit) +{ + rectangle2d rectangles[UI_DEBUG_KEYBOARD_RECTANGLES]; + long count = virtual_keyboard_target_rectangles(rectangles, UI_DEBUG_KEYBOARD_RECTANGLES); + + if (hit == NONE || hit >= count) + { + platform_log("touch targets: tap at %d,%d hit no keyboard key", x, y); + } + else + { + platform_log("touch targets: tap at %d,%d hit keyboard %s %ld [%d,%d,%d,%d]", x, y, + hit == count - 2 ? "BACK legend" : hit == count - 1 ? "ENTER legend" : "key", + hit, rectangles[hit].x0, rectangles[hit].y0, rectangles[hit].x1, rectangles[hit].y1); + } + + return; +} + +/* turns the pointer's motion, clicks and wheel since the last frame into the +first player's controller events; while the virtual keyboard is up it gets +the clicks and the menu behind gets nothing; a touch drag stops at the list's +ends and skips settings; the desktop wheel wraps as the d-pad does; it +records whether the pointer read is the touchscreen because the legends' +taller tap areas are for a finger only; with debug.touch_targets on it also +notes the finger-down and tap points for the debug view, and logs each tap: a +menu click where it is resolved, a keyboard click where it is taken; a click +on the band beside a list's slots steps the list by one; a PC list's row is +selected by its first click and used by a click on the selected row; it +forgets the frame's targets at the end, so that the next frame's render notes +and settles them anew */ static void ui_widgets_process_mouse( void) { struct halo_ui_pointer pointer; struct ui_mouse_target *target; short controller_index = 0; + boolean pointer_active; + boolean keyboard_active; - /* (the keyboard takes the pointer itself: virtual_keyboard.c) */ - if (virtual_keyboard_active() || - !halo_ui_pointer_update(ui_mouse_menus_active(), &pointer)) + ui_debug_frame++; +#ifdef HALO_ANDROID + /* port: in VR the keyboard takes the controller's pointer itself, the + key under it highlighted (virtual_keyboard.c) */ + if (halo_vr_active() && virtual_keyboard_active()) + pointer_active = FALSE; + else +#endif + pointer_active = halo_ui_pointer_update(ui_mouse_menus_active(), &pointer) != 0; + if (pointer_active) + ui_mouse_pointer_is_touch = pointer.touch != 0; + if (pointer_active && ui_debug_targets_enabled()) + { + if (pointer.downs) + ui_debug_set_mark(&ui_debug_down_mark, pointer.down_x, pointer.down_y); + if (pointer.left_clicks) + { + ui_debug_set_mark(&ui_debug_tap_mark, pointer.click_x, pointer.click_y); + ui_debug_click_frame = ui_debug_frame; + } + } + /* the virtual keyboard takes the pointer's clicks itself; a click that + closes it (Done) is not also a click on the menu behind */ + keyboard_active = virtual_keyboard_active(); + if (pointer_active && keyboard_active && pointer.left_clicks) + { + long hit; + + virtual_keyboard_click(pointer.click_x, pointer.click_y, &hit); + if (ui_debug_targets_enabled()) + ui_debug_log_keyboard_tap(pointer.click_x, pointer.click_y, hit); + } + if (!pointer_active || keyboard_active) { ui_mouse_press_count = 0; ui_mouse_hover_pending = FALSE; @@ -8076,7 +8825,12 @@ static void ui_widgets_process_mouse( #ifdef HALO_ANDROID /* (the VR port's panels over the menus take the pointer themselves) */ if (ui_vr_options_take_pointer(&pointer) || ui_join_code_take_pointer(&pointer)) + { + /* (the frame's targets forgotten all the same, as below) */ + ui_mouse_target_count = 0; + ui_mouse_targets_settled = FALSE; return; + } #endif if (pointer.moved) { @@ -8094,14 +8848,43 @@ static void ui_widgets_process_mouse( ui_mouse_press(_widget_event_b_button); if (pointer.wheel_steps && ui_mouse_menu()) { - struct widget_instance *widget = ui_mouse_wheel_widget(ui_mouse_menu()); - short back = _widget_event_dpad_up, forward = _widget_event_dpad_down; + boolean touch = pointer.touch != 0; + struct widget_instance *widget = ui_mouse_wheel_widget(ui_mouse_menu(), touch); + boolean going_forward = pointer.wheel_steps < 0; + short back = _widget_event_dpad_up, forward = _widget_event_dpad_down, button; + long room = NONE; + long index; long step; if (widget) ui_mouse_list_directions(widget, &back, &forward); - for (step = 0; step < ABS(pointer.wheel_steps) && step < 4; step++) - ui_mouse_press(pointer.wheel_steps > 0 ? back : forward); + /* a sideways PC widget (typically a screen's button bar) steps + sideways only, and the d-pad's up and down pass over it to a widget + above it on the focus's way, so a touch drag leaves it the same way */ + if (touch && widget && back == _widget_event_dpad_left && pc_menu_tag(widget->definition_tag_index) && + ui_mouse_wheel_room(ui_mouse_menu(), _widget_event_dpad_up, FALSE) != NONE) + { + back = _widget_event_dpad_up; + forward = _widget_event_dpad_down; + } + button = going_forward ? forward : back; + /* only a touch drag stops at the ends, and does nothing with no list + to step; the desktop wheel wraps as the d-pad does, and falls back + to up and down */ + if (touch) + { + room = widget ? ui_mouse_wheel_room(ui_mouse_menu(), button, going_forward) : 0; + /* the presses still waiting step the list first */ + for (index = 0; widget && room != NONE && index < ui_mouse_press_count; index++) + { + if (ui_mouse_presses[index] == button) + room = MAX(room - 1, 0); + else if (ui_mouse_presses[index] == (going_forward ? back : forward)) + room++; + } + } + for (step = 0; step < ABS(pointer.wheel_steps) && step < 4 && (room == NONE || step < room); step++) + ui_mouse_press(button); } if (!ui_mouse_press_count && ui_mouse_hover_pending) { @@ -8121,6 +8904,12 @@ static void ui_widgets_process_mouse( case _ui_mouse_target_list_slot: ui_mouse_step_list_to_slot(target->widget); break; + /* stepping is a click's action: hovering a band does nothing, so + that passing a finger over a list's side never moves its focus or + its items */ + case _ui_mouse_target_list_back: + case _ui_mouse_target_list_forward: + break; } } } @@ -8128,6 +8917,8 @@ static void ui_widgets_process_mouse( { ui_mouse_click_pending = FALSE; target = ui_mouse_target_at(ui_mouse_click_x, ui_mouse_click_y); + if (ui_debug_targets_enabled()) + ui_debug_log_click(ui_mouse_click_x, ui_mouse_click_y, target, ui_debug_frame - ui_debug_click_frame); if (target) { switch (target->kind) @@ -8152,8 +8943,8 @@ static void ui_widgets_process_mouse( ui_mouse_give_focus(target->widget); ui_mouse_list_directions(target->widget, &back, &forward); first_half = back == _widget_event_dpad_left ? - ui_mouse_click_x < (target->bounds.x0 + target->bounds.x1) / 2 : - ui_mouse_click_y < (target->bounds.y0 + target->bounds.y1) / 2; + ui_mouse_click_x < target->split_x : + ui_mouse_click_y < target->split_y; ui_mouse_press(first_half ? back : forward); break; } @@ -8164,6 +8955,23 @@ static void ui_widgets_process_mouse( case _ui_mouse_target_button: ui_mouse_press(target->button_index); break; + case _ui_mouse_target_list_back: + case _ui_mouse_target_list_forward: + { + short back, forward; + + ui_mouse_give_focus(target->widget); + /* as ui_mouse_step_list_to_slot: a list with no focused item + (empty, or one the focus left, which cleared its focused_child) + has no item to step from, so the first tap on its band only + gives it the focus, and a d-pad press would move the focus off + the list instead */ + if (!target->widget->focused_child) + break; + ui_mouse_list_directions(target->widget, &back, &forward); + ui_mouse_press(target->kind == _ui_mouse_target_list_back ? back : forward); + break; + } } } else @@ -8194,6 +9002,7 @@ static void ui_widgets_process_mouse( } } ui_mouse_target_count = 0; + ui_mouse_targets_settled = FALSE; return; } @@ -8371,6 +9180,18 @@ static void widget_instance_render_recursive( clip_rect, offset, widget_instance_text_box_is_focused(widget)); + /* port: what the menus draw beside a text (the lobby's speaker + icons: port/linux/game/menu_functions.c), its bounds as it was + drawn in them (widget_instance_render_text_box), its font and + justification still set */ + { + rectangle2d text_bounds = definition->bounds; + + offset_rectangle2d(&text_bounds, offset.x, offset.y); + text_bounds.x0 += definition->horizontal_offset; + text_bounds.y0 += definition->vertical_offset; + menu_functions_text_box_drawn(widget, &text_bounds); + } break; case _ui_widget_type_spinner_list: @@ -8491,12 +9312,17 @@ void render_ui_widgets_postgame( return; } +/* renders the active widgets for one local player's viewport (or the whole +screen), noting and settling the first player's tap targets in the frame's +first render that can, and draws the debug view of the targets and the +virtual keyboard when they are up */ void render_ui_widgets( short local_player_index, rectangle2d const *window_bounds) { rectangle2d bounds; long widget_index; + boolean first_players_render = local_player_index == NONE || local_player_index == 0; match_assert( "c:\\halo\\SOURCE\\interface\\ui_widget.c", @@ -8554,9 +9380,11 @@ void render_ui_widgets( bounds.y1 = window_bounds->y1 - window_bounds->y0; offset.x = 0; offset.y = 0; - /* the mouse drives the first player's menus */ - ui_mouse_noting_targets = widget->local_player_index == NONE || - widget->local_player_index == 0; + /* the mouse drives the first player's menus; a widget shown in + every viewport (a dialog for everyone) is noted in the first + player's render only, and once a frame */ + ui_mouse_noting_targets = first_players_render && !ui_mouse_targets_settled && + (widget->local_player_index == NONE || widget->local_player_index == 0); widget_instance_render_recursive( widget_globals.active_widgets[widget_index], &bounds, @@ -8588,6 +9416,21 @@ void render_ui_widgets( } } } + if (first_players_render && !ui_mouse_targets_settled) + { + /* fit the legends while the rows are still targets (merging + removes them); widen after merging (the merged values are + row-tall). A finger's targets only: the mouse clicks the + labels and values where CE's menus have them (port: a VR + controller's ray gets the wider values too, not the merged + rows: a click on a row's label presses A there, as before) */ + ui_mouse_fit_button_targets(); + if (ui_mouse_pointer_is_touch) + ui_mouse_merge_setting_rows(); + if (ui_mouse_pointer_is_coarse()) + ui_mouse_widen_values(); + ui_mouse_targets_settled = TRUE; + } #ifdef HALO_ANDROID ui_join_code_render(); ui_vr_options_render(); @@ -8612,6 +9455,7 @@ void render_ui_widgets( { virtual_keyboard_render(); } + ui_debug_draw_targets(first_players_render); return; } @@ -8841,6 +9685,27 @@ static void widget_instance_tab_to_previous_valid_widget( return; } +/* port: with one person playing, any controller drives the first player's +menus, not only the first port's: a phone can list a device of its own (its +touch controls) before the gamepad, which then reads port 2, and a player +picks up whichever pad is at hand. Its screens read every controller's +events (process_ui_widgets), and take them +(widget_takes_events_of_controller). With two or more players, each +controller keeps to its own player's menus */ +static boolean widget_takes_any_controller( + struct widget_instance const *widget) +{ + return widget->local_player_index == 0 && !pc_menu_split_players() && + (we_are_at_the_main_menu || players_port_local_player_count() <= 1); +} + +/* port: the controller whose events a screen reads (NONE: every one's) */ +static short widget_event_controller( + struct widget_instance const *widget) +{ + return widget_takes_any_controller(widget) ? NONE : widget->local_player_index; +} + /* port: whether a widget of the local player (NONE: any) takes the controller's events. In co-op's menus (Multiplayer's CO-OP CAMPAIGN, port/linux/game/menu_functions.c) the screens it shares with one player's @@ -8855,6 +9720,11 @@ static boolean widget_takes_events_of_controller( if (widget->local_player_index == NONE || widget->local_player_index == controller_index) return TRUE; + if (controller_index > 0 && controller_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS && + widget_takes_any_controller(widget)) + { + return TRUE; + } if (widget->local_player_index != 0 || !we_are_at_the_main_menu || player_spawn_count < 2 || controller_index < 0 || controller_index >= MAXIMUM_NUMBER_OF_LOCAL_PLAYERS) { @@ -8883,6 +9753,12 @@ static void widget_instance_process_one_event_recursive( "c:\\halo\\SOURCE\\interface\\ui_widget.c", 3067, widget && definition && event && return_widget_deleted); + if (event->type == _event_type_button && + event->data.button.index >= _widget_event_dpad_up && + event->data.button.index <= _widget_event_dpad_right) + { + ui_mouse_focused_last = FALSE; + } if (event->type == _event_type_button && event->data.button.value > 1 && event->controller_index >= 0 && @@ -9733,7 +10609,7 @@ void process_ui_widgets( struct event_record event = {0}; if (widget_globals.processing_inhibited || - !get_next_event(&event, widget->local_player_index)) + !get_next_event(&event, widget_event_controller(widget))) { /* the widget still gets one empty event so that its animation, auto-close timer and fade keep running */ @@ -9768,7 +10644,7 @@ void process_ui_widgets( if (widget != widget_globals.active_widgets[widget_index]) break; } - while (get_next_event(&event, widget->local_player_index)); + while (get_next_event(&event, widget_event_controller(widget))); } widgets_processed = TRUE; if (!widget_globals.active_widgets[widget_index] && diff --git a/source/interface/ui_widget_event_handler_functions.c b/source/interface/ui_widget_event_handler_functions.c index 41d3e288..252c4a45 100644 --- a/source/interface/ui_widget_event_handler_functions.c +++ b/source/interface/ui_widget_event_handler_functions.c @@ -3386,6 +3386,57 @@ static boolean multiplayer_profiles_list_initialize( return TRUE; } +/* port: whether a widget of a map's may not run an event handler's function. +A widget's handlers name the functions they run by their index in the +function table, which nothing checks, so a game map's widget could run the +main menu's functions (deleting player and playlist profiles, saving them, +running the demos) and the port's own (writing config.toml, quitting, +connecting), on its created event too, as its screen opens. The shipped +game maps' widgets (their pause screens) run none of these: the port's own +menus' tags (pc_menu_tag) may run the port's, and the main menu (ui.map) +the main menu's. Logged once */ +static boolean ui_widget_function_denied( + struct widget_instance *widget, + word function_index) +{ + extern boolean pc_menu_tag(long tag_index); + static short const main_menu_functions[] = + { + 41, /* mp profile change name */ + 60, /* mp profile save changes */ + 64, 65, 66, 67, /* player profile begin and end editing, change name, save changes */ + 68, 69, 70, 71, /* player profile controller settings */ + 74, 75, 76, 77, 78, 79, 80, /* profile deletion and creation */ + 86, 87, /* the demos */ + }; + static boolean logged = FALSE; + boolean denied = FALSE; + short index; + + if (pc_menu_tag(widget->definition_tag_index)) + return FALSE; + if (function_index >= PC_MENU_FUNCTION_BASE && function_index < 0x8000) + { + denied = TRUE; + } + else if (!main_menu_is_active()) + { + for (index = 0; index < (short)NUMBEROF(main_menu_functions); index++) + { + if (function_index == (word)main_menu_functions[index]) + denied = TRUE; + } + } + if (denied && !logged) + { + logged = TRUE; + error(_error_silent, "a map's widget may not run event handler function %d; it is skipped", + function_index); + } + + return denied; +} + boolean ui_widget_event_handler_function_invoke( struct widget_instance *widget, struct event_record *event, @@ -3397,6 +3448,12 @@ boolean ui_widget_event_handler_function_invoke( match_vassert("c:\\halo\\SOURCE\\interface\\ui_widget_event_handler_functions.c", 478, widget != NULL && widget_deleted != NULL, "(widget != NULL) && (widget_deleted != NULL)"); + /* port: a map's own widgets (not the menus' tags the port adds) may not + run what changes the player's files or settings: ui_widget_function_denied + (failed, as an invalid function is, so the handler opens and closes no + screens after it) */ + if (ui_widget_function_denied(widget, function_index)) + return FALSE; /* port: the menus' own functions (port/linux/game/menu_functions.c) */ if (function_index >= PC_MENU_FUNCTION_BASE && function_index < 0x8000) { @@ -5895,7 +5952,9 @@ static boolean solo_level_initialize_list_single_player( /* port: the PC version's multiplayer menus (port/linux/game/menu_functions.c), on our lists rather than the Xbox's spinners: */ -/* the multiplayer maps (the Xbox's 13), and the one used last (else 0) */ +/* the multiplayer maps (the Xbox's 13), and the one used last (else 0), +unless last_used is NULL: it is read from a file of the save root, which the +menus that name maps each frame need not do */ short ui_widget_port_multiplayer_maps( char const *const **names, short *last_used) @@ -5908,6 +5967,8 @@ short ui_widget_port_multiplayer_maps( char **levels = custom_edition_maps_level_list(event_handler_functions.multiplayer_levels, 13, &level_count); *names = (char const *const *)levels; + if (!last_used) + return level_count; *last_used = 0; if (saved_game_file_retrieve_last_used_multiplayer_map(map_name)) { diff --git a/source/interface/ui_widget_game_data_input_functions.c b/source/interface/ui_widget_game_data_input_functions.c index d863a2e2..b39fd464 100644 --- a/source/interface/ui_widget_game_data_input_functions.c +++ b/source/interface/ui_widget_game_data_input_functions.c @@ -2277,7 +2277,7 @@ static void game_options_menu_update_text_desc( struct widget_instance *extended_description; struct widget_instance *spinner_list; struct ui_widget_definition *definition; - long description_index; + long description_index = 0; match_vassert( "c:\\halo\\SOURCE\\interface\\ui_widget_game_data_input_functions.c", @@ -2339,12 +2339,6 @@ static void game_options_menu_update_text_desc( } } - /* BUG (preserved for exact matching): description_index is assigned only when the - * list has a focused child. Without one, January stores the low word of the widget - * argument slot instead (0x4e1ce0: +0xa4 branches to +0x119 mov dx,[ebp+8]; the slot - * is never written). Whether a game options list is updated without a focused child - * is not shown. A corrected build should initialise description_index to 0. - * Source-policy approval pending (2026-09-27 audit). */ extended_description->parameters.text_box.string_list_index = (short)description_index; return; } @@ -2356,7 +2350,7 @@ static void game_options_menu_update_pic_desc( struct widget_instance *extended_description; struct widget_instance *spinner_list; struct ui_widget_definition *definition; - long description_index; + long description_index = 0; match_vassert( "c:\\halo\\SOURCE\\interface\\ui_widget_game_data_input_functions.c", @@ -2408,11 +2402,6 @@ static void game_options_menu_update_pic_desc( } } - /* BUG (preserved for exact matching): as in game_options_menu_update_text_desc, - * without a focused child January stores the low word of the widget argument slot - * (0x4e1ea0 +0x119 mov dx,[ebp+8]). Whether that occurs is not shown. A corrected - * build should initialise description_index to 0. Source-policy approval pending - * (2026-09-27 audit). */ extended_description->animation.current_frame_index = (short)description_index; return; } diff --git a/source/interface/virtual_keyboard.c b/source/interface/virtual_keyboard.c index debfc33b..f97d0263 100644 --- a/source/interface/virtual_keyboard.c +++ b/source/interface/virtual_keyboard.c @@ -98,6 +98,8 @@ symbols in this file: #include "text/unicode.h" #ifdef HALO_ANDROID +#include "halo_vr.h" + /* the VR port's: a caption of its own instead of the string list's, for a keyboard it launches (ui_widget.c's join codes); NULL for the list's */ static wchar_t const *virtual_keyboard_caption_override; @@ -359,6 +361,17 @@ static rectangle2d keyboard_rect[NUMBER_OF_VIRTUAL_KEYS] = static struct virtual_keyboard_globals virtual_keyboard_globals= {0}; +/* port: the computer's keyboard types into the keyboard while it is up +(port/linux/src/xinput_sdl.c), set as it opens and closes: it is processed +only while it is up, so its processing cannot end the typing */ +static void virtual_keyboard_set_active(boolean active) +{ + extern void platform_text_typing(int typing); + + virtual_keyboard_globals.active = active; + platform_text_typing(active); +} + /* ---------- public code */ boolean virtual_keyboard_initialize( @@ -366,7 +379,7 @@ boolean virtual_keyboard_initialize( { long keyboard_index; - virtual_keyboard_globals.active = FALSE; + virtual_keyboard_set_active(FALSE); virtual_keyboard_globals.shift_active = FALSE; virtual_keyboard_globals.caps_active = FALSE; virtual_keyboard_globals.symbols_active = FALSE; @@ -411,7 +424,7 @@ boolean virtual_keyboard_initialize( void virtual_keyboard_dispose( void) { - virtual_keyboard_globals.active = FALSE; + virtual_keyboard_set_active(FALSE); virtual_keyboard_globals.shift_active = FALSE; virtual_keyboard_globals.caps_active = FALSE; virtual_keyboard_globals.symbols_active = FALSE; @@ -452,7 +465,7 @@ boolean virtual_keyboard_launch( event_manager_flush(); virtual_keyboard_globals.row = 0; virtual_keyboard_globals.column = 0; - virtual_keyboard_globals.active = TRUE; + virtual_keyboard_set_active(TRUE); virtual_keyboard_globals.text_buffer = text_buffer; virtual_keyboard_globals.cursor = text_buffer + ustrlen(text_buffer); virtual_keyboard_globals.buffer_size = buffer_size; @@ -565,7 +578,7 @@ static boolean virtual_keyboard_tab_down( static boolean virtual_keyboard_cancel( void) { - virtual_keyboard_globals.active = FALSE; + virtual_keyboard_set_active(FALSE); if (virtual_keyboard_globals.text_buffer) { ustrncpy( @@ -973,13 +986,6 @@ void virtual_keyboard_render( void virtual_keyboard_process( void) { - /* port: while it is up the computer's keyboard types into it - (port/linux/src/xinput_sdl.c) */ - { - extern void platform_text_typing(int typing); - - platform_text_typing(virtual_keyboard_globals.active); - } if (virtual_keyboard_globals.active) virtual_keyboard_process_internal(); @@ -1029,7 +1035,7 @@ static boolean virtual_keyboard_select( virtual_keyboard_globals.last_exit_saved_text = TRUE; } ui_play_audio_feedback_sound(_ui_audio_feedback_back); - virtual_keyboard_globals.active = FALSE; + virtual_keyboard_set_active(FALSE); event_manager_flush(); break; @@ -1378,7 +1384,9 @@ static void virtual_keyboard_process_internal( #ifdef HALO_ANDROID /* the VR port's: the controller's pointer (halo_ui_pointer.h) picks the - key under it, and its click types it */ + key under it, and its click types it (flat, the touchscreen's taps go + through virtual_keyboard_click: ui_widget.c) */ + if (halo_vr_active()) { struct halo_ui_pointer pointer; @@ -1461,3 +1469,94 @@ static void virtual_keyboard_process_internal( return; } + +/* The "B =BACK" and "A =ENTER" legends are part of the keyboard's background +bitmap, not widgets, so nothing else can hit-test them; measured on the +rendered screen (icon and label, with a few pixels of margin). */ +static rectangle2d const keyboard_back_legend_rect= {412, 368, 438, 462}; +static rectangle2d const keyboard_enter_legend_rect= {412, 468, 438, 572}; + +/* (virtual_keyboard.h) */ +long virtual_keyboard_target_rectangles( + rectangle2d *rectangles, + long maximum) +{ + long count = 0; + short key_index; + + for (key_index = 0; key_index < NUMBER_OF_VIRTUAL_KEYS && count < maximum; key_index++) + rectangles[count++] = keyboard_rect[key_index]; + if (count < maximum) + rectangles[count++] = keyboard_back_legend_rect; + if (count < maximum) + rectangles[count++] = keyboard_enter_legend_rect; + + return count; +} + +/* (virtual_keyboard.h) */ +boolean virtual_keyboard_click( + short x, + short y, + long *hit) +{ + short key_index, row, column; + + if (hit) + *hit = NONE; + if (!virtual_keyboard_globals.active) + return FALSE; + if (x >= keyboard_back_legend_rect.x0 && x < keyboard_back_legend_rect.x1 && + y >= keyboard_back_legend_rect.y0 && y < keyboard_back_legend_rect.y1) + { + if (hit) + *hit = NUMBER_OF_VIRTUAL_KEYS; + if (virtual_keyboard_cancel() == TRUE) + { + virtual_keyboard_globals.time_of_last_event = system_milliseconds(); + virtual_keyboard_globals.last_event = _event_cancel; + } + return TRUE; + } + if (x >= keyboard_enter_legend_rect.x0 && x < keyboard_enter_legend_rect.x1 && + y >= keyboard_enter_legend_rect.y0 && y < keyboard_enter_legend_rect.y1) + { + if (hit) + *hit = NUMBER_OF_VIRTUAL_KEYS + 1; + key_index = virtual_keyboard_layout_table[0][0]; + } + else + { + for (key_index = 0; key_index < NUMBER_OF_VIRTUAL_KEYS; key_index++) + { + rectangle2d const *rectangle = &keyboard_rect[key_index]; + + if (x >= rectangle->x0 && x < rectangle->x1 && y >= rectangle->y0 && y < rectangle->y1) + break; + } + if (key_index == NUMBER_OF_VIRTUAL_KEYS) + return FALSE; + if (hit) + *hit = key_index; + } + for (row = 0; row < VIRTUAL_KEYBOARD_ROW_COUNT; row++) + { + for (column = 0; column < VIRTUAL_KEYBOARD_COLUMN_COUNT; column++) + { + if (virtual_keyboard_layout_table[row][column] == key_index) + { + virtual_keyboard_globals.row = row; + virtual_keyboard_globals.column = column; + virtual_keyboard_globals.last_key = key_index; + if (virtual_keyboard_select() == TRUE) + { + virtual_keyboard_globals.time_of_last_event = system_milliseconds(); + virtual_keyboard_globals.last_event = _event_key_select; + } + return TRUE; + } + } + } + + return FALSE; +} diff --git a/source/interface/virtual_keyboard.h b/source/interface/virtual_keyboard.h index 7f54b485..bbc02ada 100644 --- a/source/interface/virtual_keyboard.h +++ b/source/interface/virtual_keyboard.h @@ -43,6 +43,26 @@ void virtual_keyboard_set_caption( wchar_t const *caption); #endif +/* applies a click or tap to the keyboard; a key takes the focus and is +pressed as A presses the focused key; BACK cancels as B does; ENTER goes to +Done and presses it as Start does, not as A does (a touch has no focused key +to confirm with); keys that span several cells take the focus at their first; +hit (may be NULL) receives which rectangle matched as its index in +virtual_keyboard_target_rectangles (the keys, then BACK, then ENTER), or +NONE; the debug log relies on this order; returns TRUE if the click was on a +key or the BACK or ENTER legend, which then acted */ +boolean virtual_keyboard_click( + short x, + short y, + long *hit); + +/* lists the rectangles that virtual_keyboard_click hit-tests, for the debug +view of the touch targets (debug.touch_targets); rectangles are the keys', +then BACK and ENTER legends', in that order */ +long virtual_keyboard_target_rectangles( + rectangle2d *rectangles, + long maximum); + /* ---------- globals */ /* ---------- public code */ diff --git a/source/items/items.c b/source/items/items.c index 3e2c7c0c..942008ac 100644 --- a/source/items/items.c +++ b/source/items/items.c @@ -102,6 +102,17 @@ enum /* ---------- prototypes */ +/* port: the surface an item rests on, kept in a short of the datum: a +collision surface past 32767 (a large map's) is read back unsigned, NONE +stays NONE, and a surface of 65535 or more (NONE's bits) is not kept */ +static long item_rested_surface_index( + struct item_datum const *item); +static struct collision_surface const *item_rested_surface( + struct item_datum const *item); +static void item_set_rested_surface( + struct item_datum *item, + long surface_index); + /* ---------- globals */ static struct profile_section item_update_section = { "item_update", NONE, TRUE }; @@ -282,6 +293,35 @@ void item_detonate( /* ---------- private code */ +static long item_rested_surface_index( + struct item_datum const *item) +{ + if (item->item.rested_surface_index == NONE) + return NONE; + return (unsigned short)item->item.rested_surface_index; +} + +static struct collision_surface const *item_rested_surface( + struct item_datum const *item) +{ + struct collision_bsp const *bsp = global_collision_bsp_get(); + long surface_index = item_rested_surface_index(item); + + if (!bsp || surface_index < 0 || surface_index >= bsp->surfaces.count) + return NULL; + return TAG_BLOCK_GET_ELEMENT(&bsp->surfaces, surface_index, struct collision_surface); +} + +static void item_set_rested_surface( + struct item_datum *item, + long surface_index) +{ + if (surface_index < 0 || surface_index >= 65535) + item->item.rested_surface_index = NONE; + else + item->item.rested_surface_index = (short)surface_index; +} + static void item_adjust_for_angular_velocity_change( long item_index) { @@ -356,23 +396,23 @@ void item_accelerate( real distance_above; collision_bsp = global_collision_bsp_get(); - surface = TAG_BLOCK_GET_ELEMENT( - &collision_bsp->surfaces, - item->item.rested_surface_index, - struct collision_surface); - bsp3d_get_plane_from_designator( - &collision_bsp->bsp3d, - surface->plane_designator, - &plane); + surface = item_rested_surface(item); + if (surface) + { + bsp3d_get_plane_from_designator( + &collision_bsp->bsp3d, + surface->plane_designator, + &plane); - distance_above = - 0.05f - (plane3d_distance_to_point(&plane, &marker.matrix.position)); - point_from_line3d( - &marker.matrix.position, - &plane.n, - distance_above, - &new_position); - object_translate(item_index, &new_position, NULL); + distance_above = + 0.05f - (plane3d_distance_to_point(&plane, &marker.matrix.position)); + point_from_line3d( + &marker.matrix.position, + &plane.n, + distance_above, + &new_position); + object_translate(item_index, &new_position, NULL); + } } item->object.flags &= ~FLAG(_object_at_rest_bit); @@ -679,8 +719,7 @@ boolean item_update( { case _collision_result_structure: SET_FLAG(item->item.flags, _item_on_structure_bit, TRUE); - item->item.rested_surface_index = - (short)collision.surface_index; + item_set_rested_surface(item, collision.surface_index); item->item.bsp_index = global_structure_bsp_index_get(); break; @@ -766,18 +805,18 @@ boolean item_update( 1); if (TEST_FLAG(item->item.flags, _item_on_structure_bit) && - item->item.rested_surface_index != NONE && + item_rested_surface_index(item) != NONE && item->item.bsp_index == global_structure_bsp_index_get()) { - struct collision_bsp *collision_bsp; struct collision_surface const *surface; - collision_bsp = global_collision_bsp_get(); - surface = TAG_BLOCK_GET_ELEMENT( - &collision_bsp->surfaces, - item->item.rested_surface_index, - struct collision_surface); - if (TEST_FLAG(surface->flags, _collision_surface_breakable_bit) && + surface = item_rested_surface(item); + if (!surface) + { + SET_FLAG(item->item.flags, _item_on_structure_bit, FALSE); + item->item.rested_surface_index = NONE; + } + else if (TEST_FLAG(surface->flags, _collision_surface_breakable_bit) && !breakable_surface_extant( surface->breakable_surface_index)) { diff --git a/source/items/projectiles.c b/source/items/projectiles.c index 9c057c08..cc7a286c 100644 --- a/source/items/projectiles.c +++ b/source/items/projectiles.c @@ -501,10 +501,6 @@ void projectile_export_function_values( value = 0.f; break; - /* value is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: display_assert( NULL, @@ -1230,9 +1226,7 @@ static void projectile_calculate_deceleration( definition, definition->projectile.air_damage_range_lower_bound, definition->projectile.air_damage_range_upper_bound); - /* BUG (preserved for exact matching): January loads the water upper bound - * in the air branch. A corrected build should use - * definition->projectile.air_damage_range_upper_bound. */ + /* (as the original game, kept: in the air the water damage range's upper bound is used) */ projectile->projectile.maximum_damage_distance = definition->projectile.water_damage_range_upper_bound; diff --git a/source/items/weapons.c b/source/items/weapons.c index af0849f6..55b7f407 100644 --- a/source/items/weapons.c +++ b/source/items/weapons.c @@ -235,6 +235,12 @@ symbols in this file: #include "physics/collisions.h" #endif +#ifdef HALO_PORT_MAXIMUM_NETWORK_PLAYERS +#include "bitmaps/bitmap_group.h" +#include "cache/texture_cache.h" +#include "effects/contrail_definitions.h" +#endif + /* port/linux/game/pal_tags.c's */ short pal_tags_first_person_frames(long graph_index, short animation_index, short frames); @@ -576,6 +582,58 @@ void weapons_dispose( return; } +#ifdef HALO_PORT_MAXIMUM_NETWORK_PLAYERS +/* A trail's first draw requests its bitmap asynchronously and is skipped +until that read finishes. Warm only the projectile trails of a weapon that +has been created or readied, before its first shot. Keep ordinary +streaming nonblocking and leave the original trail tags and lifetime alone. */ +static void weapon_precache_projectile_trails( + long definition_index) +{ + if (definition_index != NONE) + { + struct weapon_definition *definition = weapon_definition_get(definition_index); + short trigger_index; + + for (trigger_index = 0; trigger_index < definition->weapon.triggers.count; trigger_index++) + { + struct weapon_trigger_definition *trigger = TAG_BLOCK_GET_ELEMENT( + &definition->weapon.triggers, trigger_index, struct weapon_trigger_definition); + + if (trigger->projectile.index != NONE) + { + struct projectile_definition *projectile = projectile_definition_get(trigger->projectile.index); + short attachment_index; + + for (attachment_index = 0; attachment_index < projectile->object.attachments.count; attachment_index++) + { + struct object_attachment_definition *attachment = TAG_BLOCK_GET_ELEMENT( + &projectile->object.attachments, attachment_index, struct object_attachment_definition); + + if (attachment->type.group_tag == CONTRAIL_DEFINITION_TAG && attachment->type.index != NONE) + { + struct contrail_definition *contrail = contrail_definition_get(attachment->type.index); + + if (contrail->bitmap.index != NONE) + { + struct bitmap_group *bitmap = bitmap_group_get(contrail->bitmap.index); + short bitmap_index; + + for (bitmap_index = 0; bitmap_index < bitmap->bitmaps.count; bitmap_index++) + { + _texture_cache_bitmap_get_hardware_format( + TAG_BLOCK_GET_ELEMENT(&bitmap->bitmaps, bitmap_index, struct bitmap_data), + FALSE, TRUE); + } + } + } + } + } + } + } +} +#endif + void weapon_place( long weapon_index, struct scenario_weapon_datum *scenario_weapon) @@ -607,6 +665,9 @@ void weapon_ready( struct weapon_datum* weapon = weapon_get(weapon_index); struct weapon_definition *weapon_definition = weapon_definition_get(weapon->definition_index); +#ifdef HALO_PORT_MAXIMUM_NETWORK_PLAYERS + weapon_precache_projectile_trails(weapon->definition_index); +#endif weapon_reset(weapon_index); weapon_set_state(weapon_index, _weapon_state_ready, TRUE); first_person_weapon_message_from_weapon(weapon_index, _first_person_weapon_message_ready); @@ -818,6 +879,9 @@ boolean weapon_new( trigger->idle_ticks = 127; } +#ifdef HALO_PORT_MAXIMUM_NETWORK_PLAYERS + weapon_precache_projectile_trails(weapon->definition_index); +#endif return TRUE; } diff --git a/source/main/console.c b/source/main/console.c index 4b8a7103..8db63a2d 100644 --- a/source/main/console.c +++ b/source/main/console.c @@ -59,6 +59,7 @@ symbols in this file: #include "editor/editor_stubs.h" #include "hs/hs.h" #include "networking/network_server_manager.h" +#include "network_votekick.h" /* port: port/linux/game/network_votekick.c */ #include "input/input.h" #include "interface/terminal.h" #include "math/real_math.h" @@ -129,7 +130,10 @@ void console_open( { console_globals.input_state.result[0] = '\0'; console_globals.active = terminal_gets_begin(&console_globals.input_state); + /* port: not in the profiling build (as in console_update) */ +#ifndef HALO_PROFILE profile_global_enable = FALSE; +#endif } return; @@ -260,12 +264,13 @@ static char *console_get_text_to_autocomplete( } /* port: the text after the host's ban or kick command ("ban ", "kick "), -which completes as a player's name (network_game_server_matching_player_names); -NULL if the input is not one */ +or any player's votekick ("votekick "), which completes as a player's name +(network_game_server_matching_player_names, network_votekick_matching_player_names; +the command's index: votekick's, 2); NULL if the input is not one */ static char *console_player_command_name( - void) + short *command_index) { - static char const *const commands[] = { "ban", "kick" }; + static char const *const commands[] = { "ban", "kick", "votekick" }; char *text = console_globals.input_state.result; short command; @@ -286,6 +291,7 @@ static char *console_player_command_name( } if (index == length && text[length] == ' ') { + *command_index = command; text += length; while (*text == ' ' || *text == '"') text++; @@ -303,14 +309,19 @@ static void console_complete( /* (port: the players' names the ban and kick commands complete) */ static char player_names[64][NETWORK_GAME_SERVER_NAME_TEXT_SIZE]; - char *token = console_player_command_name(); + short command_index = NONE; + char *token = console_player_command_name(&command_index); short count; if (token) { short index; - count = network_game_server_matching_player_names(token, player_names, NUMBEROF(player_names)); + /* (the votekick's: any machine's, from the game's players) */ + if (command_index == 2) + count = network_votekick_matching_player_names(token, player_names, NUMBEROF(player_names)); + else + count = network_game_server_matching_player_names(token, player_names, NUMBEROF(player_names)); for (index = 0; index < count; index++) matching_items[index] = player_names[index]; } @@ -447,6 +458,10 @@ boolean console_update( match_assert("c:\\halo\\SOURCE\\main\\console.c", 184, key->key_code!=NONE); switch (key->key_code) { + /* port: escape closes it too */ + case _key_escape: + console_close(); + return FALSE; case _key_backquote: console_close(); break; @@ -498,7 +513,12 @@ boolean console_update( { console_globals.input_state.result[0] = '\0'; console_globals.active = terminal_gets_begin(&console_globals.input_state); + /* port: the profiling build: cleared mid-frame, a section already entered + would never exit, and its next enter assert; and opening the console + to type profile_stop would end the recording's timing */ +#ifndef HALO_PROFILE profile_global_enable = FALSE; +#endif } return console_globals.active; diff --git a/source/main/main.c b/source/main/main.c index 38c7ce11..f8f86414 100644 --- a/source/main/main.c +++ b/source/main/main.c @@ -380,6 +380,11 @@ symbols in this file: #include "networking/network_game_manager.h" #include "networking/network_server_manager.h" /* port: a co-op game's level won */ #include "network_coop.h" /* port: port/linux/game/network_coop.c */ +#include "network_voice.h" /* port: port/linux/game/network_voice.c */ +#include "profile_sections.h" /* port: port/linux/include/profile_sections.h */ +#ifdef HALO_PROFILE +#include "profile_trace.h" /* port: port/linux/src/profile_trace.c */ +#endif #include "camera/director.h" #include "camera/observer.h" #include "cutscene/cinematics.h" @@ -395,6 +400,7 @@ symbols in this file: #include "halo_vr.h" #endif #include "custom_edition_cache.h" /* port: custom_edition_level_name */ +#include "view_fov.h" /* port: display.fov, before the projection and culling */ #if defined(HALO_WINDOWS) || defined(HALO_ANDROID) || defined(__linux__) #define HALO_NATIVE_BUILD_INFO 1 @@ -1153,11 +1159,12 @@ void set_window_camera_values( window->rasterizer_camera.position = observer->position; window->rasterizer_camera.forward = observer->forward; window->rasterizer_camera.up = observer->up; + /* port: display.fov's view, the local render view only (view_fov.c) */ window->rasterizer_camera.vertical_field_of_view = - 2.0f * arctangent( + render_fov_vertical(window->local_player_index, 2.0f * arctangent( 0.75f * render_camera_get_adjusted_field_of_view_tangent( observer->field_of_view), - 1.0f); + 1.0f)); if (window->local_player_index != NONE && #ifdef HALO_ANDROID @@ -1447,6 +1454,16 @@ short main_get_window_count( return single_window ? 1 : PIN(local_player_count(), 1, MAXIMUM_WINDOWS); } +#ifdef HALO_PROFILE +/* port: whether the main menu's map is the one loaded (profile_console.c: a +recording of debug.profile_record_when = "game" waits for another) */ +boolean main_menu_is_loaded( + void) +{ + return main_globals.main_menu_scenario_loaded; +} +#endif + static void main_new_map( struct game_options *options) { @@ -2371,6 +2388,10 @@ void main_set_map_name( static void main_exit( void) { +#ifdef HALO_PROFILE + /* port: the profiling build's recording written out before the game goes */ + profile_trace_shutdown(); +#endif switch (main_globals.connection) { case _game_connection_network_server: @@ -2704,7 +2725,6 @@ void main_rasterizer_throttle( unsigned long start_milliseconds; short lapsed_frames; boolean did_throttle; - boolean precache_in_progress; boolean synchronized; char const *description; @@ -2719,15 +2739,17 @@ void main_rasterizer_throttle( if ((__int64)rasterizer_globals.frame_and_vertical_blank_index < target_index) { start_milliseconds = system_milliseconds(); - precache_in_progress = cache_files_precache_in_progress(); did_throttle = TRUE; profile_idle_start(); while ((__int64)rasterizer_globals.frame_and_vertical_blank_index < target_index) { - if (precache_in_progress) - { - Sleep(1); - } + /* port: a sleep between looks, not only while a map + precaches: the Xbox spun here, which on a port holds a core + busy for most of each frame of a game held at 30 frames a + second (display.interpolation off). A millisecond is well + inside a 33 ms frame (Windows sets its timer to 1 ms: + win32_posix.c) */ + Sleep(1); if (system_milliseconds() > start_milliseconds + 1000) { console_warning( @@ -3318,6 +3340,18 @@ static void main_game_render( return; } +/* port: the main loop's steps, timed in the profiling build */ +PROFILE_SECTION(main_input_section, "input") +PROFILE_SECTION(main_platform_events_section, "platform_events") +PROFILE_SECTION(main_network_start_frame_section, "network_start_frame") +PROFILE_SECTION(main_update_time_section, "main_update_time") +PROFILE_SECTION(main_ui_update_section, "ui_update") +PROFILE_SECTION(main_network_end_frame_section, "network_end_frame") +PROFILE_SECTION(main_game_time_update_section, "game_time_update") +PROFILE_SECTION(main_non_deterministic_update_section, "non_deterministic_update") +PROFILE_SECTION(main_throttle_section, "throttle") +PROFILE_SECTION(main_present_section, "present") + void main_loop( void) { @@ -3431,12 +3465,16 @@ void main_loop( } profile_frame_start(); + profile_scope_enter(main_input_section) input_frame_begin(); input_update(); input_abstraction_update(); + profile_scope_exit(main_input_section) + profile_scope_enter(main_platform_events_section) shell_idle(); event_manager_update(); telnet_console_process(); + profile_scope_exit(main_platform_events_section) if (!shell_application_is_paused()) { @@ -3444,6 +3482,9 @@ void main_loop( /* automated system link tests (port/linux/game/network_test.c) */ network_test_update(main_globals.main_menu_scenario_loaded, main_globals.seconds_elapsed); + /* port: voice chat, in the lobby and in game (port/linux/game/network_voice.c) */ + network_voice_update(); + profile_scope_enter(main_network_start_frame_section) connection = main_globals.connection; if (connection==_game_connection_network_client) { @@ -3471,14 +3512,22 @@ void main_loop( } else if (connection==_game_connection_film_playback) { + profile_scope_exit(main_network_start_frame_section) break; } + profile_scope_exit(main_network_start_frame_section) - main_update_time(); + profile_scope(main_update_time_section, main_update_time();) + profile_scope_enter(main_ui_update_section) process_ui_widgets(); bink_playback_update(); + profile_scope_exit(main_ui_update_section) - if ((!game_in_editor() && (input_key_is_down(_key_end) || input_key_is_down(_key_escape))) || editor_should_exit()) + /* port: not the Xbox debug keyboard's End and Escape, which stop + the movie and restart the map: this keyboard reaches the game only + through the console and the menus' text boxes, whose End and Escape + they are (port/linux/src/xinput_sdl.c) */ + if (editor_should_exit()) { main_movie_stop(); @@ -3499,16 +3548,19 @@ void main_loop( player_control_update((real)main_globals.halt_time_scale*main_globals.seconds_elapsed); connection = main_globals.connection; + profile_scope_enter(main_network_end_frame_section) if (connection>_game_connection_local && connection<=_game_connection_network_server && !network_game_client_end_frame()) { display_error_when_main_menu_loaded(1); network_game_abort(); } + profile_scope_exit(main_network_end_frame_section) #ifdef HALO_ANDROID halo_vr_phase(HALO_VR_PHASE_INPUT, 0); #endif - game_time_update((real)main_globals.halt_time_scale*main_globals.seconds_elapsed); + profile_scope(main_game_time_update_section, + game_time_update((real)main_globals.halt_time_scale*main_globals.seconds_elapsed);) #ifdef HALO_ANDROID halo_vr_phase(HALO_VR_PHASE_TICKS, game_time_get_elapsed()); #endif @@ -3521,6 +3573,7 @@ void main_loop( render_frame = main_globals.main_menu_scenario_loaded || main_globals.halt_time_scale; render_frame &= !game_engine_running() || game_time_get()>=3; + profile_scope_enter(main_non_deterministic_update_section) collision_log_continue_period(1); director_update((real)main_globals.halt_time_scale*main_globals.seconds_elapsed); observer_update((real)main_globals.halt_time_scale*main_globals.seconds_elapsed); @@ -3529,6 +3582,7 @@ void main_loop( #ifdef HALO_ANDROID halo_vr_phase(HALO_VR_PHASE_CAMERA, 0); #endif + profile_scope_exit(main_non_deterministic_update_section) } if (main_globals.saving_map) @@ -3555,11 +3609,11 @@ void main_loop( profile_render_end(); } - main_rasterizer_throttle(); + profile_scope(main_throttle_section, main_rasterizer_throttle();) if (render_frame && !debug_no_drawing) { - main_present_frame(); + profile_scope(main_present_section, main_present_frame();) } } diff --git a/source/math/geometry.c b/source/math/geometry.c index d6df49e5..15e1f9a8 100644 --- a/source/math/geometry.c +++ b/source/math/geometry.c @@ -318,20 +318,12 @@ struct geosphere *geosphere_new( &vertex_index, &triangle_strip_vertex_indices_index, vertex_subdivision_indices); } - /* BUG (preserved for exact matching): at segment_count == 1, - * January fills the allocated strip buffer but rejects equality. - * A corrected build should allow a fully filled valid buffer. - */ match_assert("c:\\halo\\SOURCE\\math\\geometry.c", 98, - triangle_strip_vertex_indices_index < (NUMBER_OF_VERTICES_PER_TRIANGLE + 1) * result->triangle_count); + triangle_strip_vertex_indices_index <= (NUMBER_OF_VERTICES_PER_TRIANGLE + 1) * result->triangle_count); match_assert("c:\\halo\\SOURCE\\math\\geometry.c", 99, vertex_index == result->vertex_count); } else { - /* BUG (preserved for exact matching): January frees allocated - * children but returns the owner with dangling member pointers. - * A corrected build should free the owner and return NULL. - */ if (result->vertices) { match_free("c:\\halo\\SOURCE\\math\\geometry.c", 103, result->vertices); @@ -340,6 +332,8 @@ struct geosphere *geosphere_new( { match_free("c:\\halo\\SOURCE\\math\\geometry.c", 104, result->triangle_strip_vertex_indices); } + match_free("c:\\halo\\SOURCE\\math\\geometry.c", 105, result); + result = NULL; } if (vertex_subdivision_indices) @@ -727,8 +721,8 @@ short convex_hull2d( real accumulated_angle = 0.f; real minimum_x = REAL_MAX; real minimum_y = REAL_MAX; - short current_index; - short best_index; + short current_index = 0; + short best_index = 0; short index; for (index = 0; index < vertex_count; index++) @@ -771,11 +765,6 @@ short convex_hull2d( break; } - /* current_index and best_index are assigned only inside the scans. For finite - * points with dimension 2 both scans assign them; a NaN or infinite coordinate can - * leave either unassigned. Not shown reachable: the callers' points (king hill - * markers, lens flare projections) were not traced for non-finite values. - * Source-policy approval pending (2026-09-27 audit). */ hull_indices[hull_count++] = current_index; for (index = 0; index < vertex_count; index++) diff --git a/source/math/periodic_functions.c b/source/math/periodic_functions.c index afc767ca..5e257468 100644 --- a/source/math/periodic_functions.c +++ b/source/math/periodic_functions.c @@ -387,10 +387,6 @@ static void transition_function_build_table( case _transition_function_cosine: result = ((real)sin(value*3.1415927f-1.5707964f)+1.0f)*0.5f; break; - /* result is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: display_assert(NULL, "c:\\halo\\SOURCE\\math\\periodic_functions.c", 411, TRUE); system_exit(-1); @@ -479,10 +475,6 @@ static void periodic_function_build_table( result = (real)fmod((double)random_x, 1.0); result *= result; break; - /* result is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: display_assert(NULL, "c:\\halo\\SOURCE\\math\\periodic_functions.c", 499, TRUE); system_exit(-1); diff --git a/source/math/random_math.c b/source/math/random_math.c index 4fc02dd6..4473f13c 100644 --- a/source/math/random_math.c +++ b/source/math/random_math.c @@ -92,11 +92,11 @@ direction3d_from_table( /* ---------- globals */ -#ifndef HALO_ANDROID /* Mach-O section names differ; the default is .bss anyway */ +#ifndef HALO_ARM64_GUEST /* Mach-O section names differ; the default is .bss anyway */ #pragma bss_seg(".bss") #endif static struct random_math_globals random_math_globals; -#ifndef HALO_ANDROID +#ifndef HALO_ARM64_GUEST #pragma bss_seg() #endif diff --git a/source/memory/byte_swapping.c b/source/memory/byte_swapping.c index f3e6fac6..909045c4 100644 --- a/source/memory/byte_swapping.c +++ b/source/memory/byte_swapping.c @@ -188,7 +188,7 @@ static void _byte_swap_data( { long code; long offset; - long code_index; + long code_index = 0; register long array_count; long repeat_count; long local_size; @@ -334,12 +334,6 @@ next_iteration: } while (repeat_count!=0); -/* A zero array count reaches done with code_index never assigned, and it is stored - * through next_code. Not reached with January's code tables: all 15 _begin_bs_array - * entries in cachebeta.exe's data sections have count 1 (scan of every aligned - * _begin_bs_array word from .rdata onwards), and every byte_swap_definition in the - * source points at a static table. Source-policy approval pending (2026-09-27 audit). - */ done: if (size) *size = offset; diff --git a/source/memory/crc.c b/source/memory/crc.c index c2f4ad14..26768af8 100644 --- a/source/memory/crc.c +++ b/source/memory/crc.c @@ -39,11 +39,11 @@ struct crc_globals /* ---------- globals */ -#ifndef HALO_ANDROID /* Mach-O section names differ; the default is .bss anyway */ +#ifndef HALO_ARM64_GUEST /* Mach-O section names differ; the default is .bss anyway */ #pragma bss_seg(".bss") #endif static struct crc_globals crc_globals; -#ifndef HALO_ANDROID +#ifndef HALO_ARM64_GUEST #pragma bss_seg() #endif diff --git a/source/memory/data_encoding.c b/source/memory/data_encoding.c index ef722a03..f4965238 100644 --- a/source/memory/data_encoding.c +++ b/source/memory/data_encoding.c @@ -528,10 +528,6 @@ void *data_decode_array( case -8: element_count = (long)data_decode_int64(state); break; - /* element_count is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: display_assert(NULL, "c:\\halo\\SOURCE\\memory\\data_encoding.c", 370, TRUE); system_exit(-1); diff --git a/source/memory/texture_page.c b/source/memory/texture_page.c index ae75bc4b..4991dc65 100644 --- a/source/memory/texture_page.c +++ b/source/memory/texture_page.c @@ -145,11 +145,8 @@ struct texture_page *texture_page_new( return texture_page; } - { - struct data_array *textures = texture_page->textures; - texture_page = NULL; - match_free("c:\\halo\\SOURCE\\memory\\texture_page.c", 56, textures); - } + match_free("c:\\halo\\SOURCE\\memory\\texture_page.c", 56, texture_page); + texture_page = NULL; } return texture_page; diff --git a/source/models/models.c b/source/models/models.c index 2c039842..1f839b63 100644 --- a/source/models/models.c +++ b/source/models/models.c @@ -83,6 +83,9 @@ symbols in this file: #include "shaders/shaders.h" #include "rasterizer/rasterizer_console_vars.h" #include "rasterizer/rasterizer_model_types.h" +/* port: model tags and static enclosure recognition at map initialization. */ +#include "cache/cache_files.h" +#include "rasterizer/rasterizer_transparent_geometry.h" /* ---------- constants */ @@ -566,7 +569,10 @@ static void render_model_parts( if (sort_filth_countnext_part_index>0 || part->previous_part_index>0)) + /* port: part zero can head a link + (models_fix_transparent_part_links); no stock + model's part has a previous part of 0 */ + (part->next_part_index>0 || part->previous_part_index>=0)) { sort_filth[sort_filth_count].part_index = part_index; sort_filth[sort_filth_count].next_part_index = part->next_part_index; @@ -639,6 +645,89 @@ static void render_model_parts( /* ---------- public code */ +/* port: a pickup's energy inside its two-sided glass shell is drawn before +the shell with the engine's own part links (render_model_parts), as Halo CE +Restored's tags link them, so that the centroid sort cannot put it after the +glass; models with links of their own, skinned ones and those with other +transparent parts are left as they are */ +static void model_geometry_fix_transparent_part_links( + struct model const *model, struct model_geometry *geometry) +{ + short i, glass = NONE, energy = NONE, transparent_count = 0; + struct model_geometry_part *parts = geometry->parts.address; + + if (model->nodes.count != 1 || geometry->parts.count < 2 || + geometry->parts.count > MAXIMUM_PARTS_PER_MODEL_GEOMETRY || !parts) + { + return; + } + for (i = 0; i < geometry->parts.count; ++i) + { + struct shader *shader; + struct model_shader_reference const *reference; + + if (parts[i].flags || parts[i].previous_part_index != NONE || parts[i].next_part_index != NONE || + !VALID_INDEX(parts[i].shader_index, model->shaders.count)) + { + return; + } + reference = TAG_BLOCK_GET_ELEMENT(&model->shaders, parts[i].shader_index, struct model_shader_reference); + shader = shader_definition_get(reference->shader.index); + if (shader_type_is_transparent(shader->base.type)) + { + ++transparent_count; + if (shader->base.type == _shader_type_transparent_glass) + { + glass = i; + } + else if (shader->base.type == _shader_type_transparent_generic) + { + energy = i; + } + } + } + if (transparent_count == 2 && glass != NONE && energy != NONE && + rasterizer_transparent_geometry_is_enclosure( + shader_definition_get(TAG_BLOCK_GET_ELEMENT(&model->shaders, parts[glass].shader_index, struct model_shader_reference)->shader.index), + &parts[glass].vertex_buffer, &parts[glass].triangle_buffer, + shader_definition_get(TAG_BLOCK_GET_ELEMENT(&model->shaders, parts[energy].shader_index, struct model_shader_reference)->shader.index), + &parts[energy].vertex_buffer)) + { + /* (a link to part zero is none: with the glass part zero, the two + parts, which have no links of their own, change places) */ + if (glass == 0) + { + struct model_geometry_part swap = parts[glass]; + parts[glass] = parts[energy]; + parts[energy] = swap; + glass = energy; + energy = 0; + } + parts[energy].next_part_index = (char)glass; + parts[glass].previous_part_index = (char)energy; + } +} + +/* port: the links of every model the map loaded (at its start: the meshes +do not change, and the next map's tags are loaded afresh) */ +void models_fix_transparent_part_links(void) +{ + struct tag_iterator iterator; + long index; + + tag_iterator_new(&iterator, MODELS_GROUP_TAG); + while ((index = tag_iterator_next(&iterator)) != NONE) + { + struct model *model = model_definition_get(index); + long geometry; + for (geometry = 0; geometry < model->geometries.count; ++geometry) + { + model_geometry_fix_transparent_part_links(model, + TAG_BLOCK_GET_ELEMENT(&model->geometries, geometry, struct model_geometry)); + } + } +} + void model_interpolate_node_orientations( struct model const *model, struct real_orientation *original_node_orientations, diff --git a/source/models/models.h b/source/models/models.h index 44c49396..bf2148b7 100644 --- a/source/models/models.h +++ b/source/models/models.h @@ -27,6 +27,9 @@ union real_vector3d; /* ---------- prototypes/MODELS.C */ +/* port: apply native transparent part links once per loaded model. */ +void models_fix_transparent_part_links(void); + void model_interpolate_node_orientations( struct model const *model, struct real_orientation *original_node_orientations, diff --git a/source/networking/network_client_manager.c b/source/networking/network_client_manager.c index 83dc585c..2542e435 100644 --- a/source/networking/network_client_manager.c +++ b/source/networking/network_client_manager.c @@ -2341,7 +2341,8 @@ static boolean network_game_client_map_name_is_valid( { /* (a scenario's tag path, of which the cache takes the name after the last backslash: letters, digits and a few more, none that a path reads - otherwise) */ + otherwise; the [ ] ( ) + that Custom Edition maps' names use, as in + [H2]_Lockout, too) */ char const *character; char const *leaf; @@ -2351,7 +2352,8 @@ static boolean network_game_client_map_name_is_valid( { if (!((*character >= 'a' && *character <= 'z') || (*character >= 'A' && *character <= 'Z') || (*character >= '0' && *character <= '9') || *character == '_' || *character == '-' || - *character == '.' || *character == ' ' || *character == '\\')) + *character == '.' || *character == ' ' || *character == '\\' || + *character == '[' || *character == ']' || *character == '(' || *character == ')' || *character == '+')) { return FALSE; } @@ -2360,6 +2362,22 @@ static boolean network_game_client_map_name_is_valid( return FALSE; leaf = strrchr(map_name, '\\'); leaf = leaf ? leaf + 1 : map_name; + /* port: and not one of Windows's devices (con, nul, com1...), whatever + follows it: maps\com1.map opens the serial port there */ + { + static char const *const devices[] = { "con", "prn", "aux", "nul", "com", "lpt" }; + long stem = (long)strcspn(leaf, ". "); + short device; + + for (device = 0; device < (short)NUMBEROF(devices); device++) + { + if (!_strnicmp(leaf, devices[device], 3) && + ((device < 4 && stem == 3) || (device >= 4 && stem == 4 && leaf[3] >= '0' && leaf[3] <= '9'))) + { + return FALSE; + } + } + } return *leaf && leaf[strspn(leaf, ". ")] != 0; } diff --git a/source/networking/network_connection.c b/source/networking/network_connection.c index 6a058d75..7a7ef880 100644 --- a/source/networking/network_connection.c +++ b/source/networking/network_connection.c @@ -486,6 +486,15 @@ boolean network_connection_connect( return FALSE; } success = TRUE; + /* port: a connection made is open, whatever came before it. A write + made before connecting (while searching for games, to no socket yet) + marked it closed, and every write of the join request then failed. */ + SET_FLAG(connection->flags, _connection_closed_bit, FALSE); + SET_FLAG(connection->flags, _connection_going_stale_bit, FALSE); + if (connection->reliable_outgoing_queue) + { + circular_queue_reset(connection->reliable_outgoing_queue); + } if (connection->unreliable_endpoint) { diff --git a/source/networking/network_server_manager.c b/source/networking/network_server_manager.c index 5241f2db..71c1559f 100644 --- a/source/networking/network_server_manager.c +++ b/source/networking/network_server_manager.c @@ -819,6 +819,11 @@ static boolean network_game_server_countdown_slowed[MAXIMUM_NETWORK_MACHINE_COUN boolean network_distributed_banned(unsigned long address, char const *hardware_id); void network_distributed_ban(long machine_index, unsigned long address, char const *names); void network_distributed_kick(char const *names); +/* port/linux/game/network_votekick.c's (the players' votes to kick) */ +void network_votekick_machine_joined(long machine_index); +boolean network_votekick_kept_out(unsigned long address, char const *hardware_id); +/* port/linux/game/network_voice.c's (voice chat: each machine's key) */ +void network_voice_machine_joined(long machine_index); /* port/linux/src/p2p.c's */ enum { @@ -1060,9 +1065,53 @@ static boolean network_game_server_named_machine( return TRUE; } +/* port: the names of a machine's players (each command's notice) */ +static void network_game_server_machine_names( + struct network_game_server *server, + long machine_index, + char *names, + long names_size) +{ + long index; + + names[0] = 0; + for (index = 0; index < MAXIMUM_NETWORK_PLAYER_COUNT; index++) + { + struct network_player const *player = &server->game.players[index]; + char name[NETWORK_GAME_SERVER_NAME_TEXT_SIZE]; + + if (!network_player_is_valid(player) || player->machine_index != machine_index) + continue; + network_game_server_player_name_text(player, name, sizeof(name)); + if (names[0] && csstrlen(names) + 2 < names_size) + csstrcat(names, ", "); + if (csstrlen(names) + csstrlen(name) < names_size) + csstrcat(names, name); + } +} + +/* port: the host's ban: the machine dropped, its address in bans.txt +(network_distributed_ban), and kept out */ +static void network_game_server_ban_machine( + long machine_index, + char const *names) +{ + network_distributed_ban(machine_index, network_game_server_client_machine_addresses[machine_index], names); + network_game_server_kick_pending[machine_index] = _kick_kept_out; +} + +/* port: the host's kick: the machine dropped, every machine told, and +nothing kept: it may join again at once */ +static void network_game_server_kick_named_machine( + long machine_index, + char const *names) +{ + network_distributed_kick(names); + network_game_server_kick_pending[machine_index] = _kick_rejoinable; +} + /* port: the host's ban command: the player's machine (as -network_game_server_named_machine finds it) dropped, its address in -bans.txt (network_distributed_ban), and kept out */ +network_game_server_named_machine finds it) banned */ boolean network_game_server_ban_player( char const *text) { @@ -1071,14 +1120,12 @@ boolean network_game_server_ban_player( if (!network_game_server_named_machine("ban", text, &machine_index, names, sizeof(names))) return FALSE; - network_distributed_ban(machine_index, network_game_server_client_machine_addresses[machine_index], names); - network_game_server_kick_pending[machine_index] = _kick_kept_out; + network_game_server_ban_machine(machine_index, names); return TRUE; } /* port: the host's kick command: the player's machine (as the ban -command's) dropped, every machine told, and nothing kept: it may join again -at once */ +command's) kicked */ boolean network_game_server_kick_player( char const *text) { @@ -1087,8 +1134,37 @@ boolean network_game_server_kick_player( if (!network_game_server_named_machine("kick", text, &machine_index, names, sizeof(names))) return FALSE; - network_distributed_kick(names); - network_game_server_kick_pending[machine_index] = _kick_rejoinable; + network_game_server_kick_named_machine(machine_index, names); + return TRUE; +} + +/* port: the host's Kick and Ban on the scoreboard (game_engine.c): the +machine of the player at the network machine index it has, kicked or +banned as the commands do; FALSE (said) for none, or the host's own */ +boolean network_game_server_kick_machine_of_player( + long machine_index, + boolean ban) +{ + struct network_game_server *server = global_network_game_server_get(); + char names[96]; + + if (!server) + { + console_warning("%s: only the host of a game does this", ban ? "ban" : "kick"); + return FALSE; + } + if (!VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) || + !network_game_server_client_machine_is_joined_to_game(server, &server->client_machines[machine_index]) || + network_game_server_client_machine_is_local(server, &server->client_machines[machine_index])) + { + console_warning("%s: not a player of the host's own machine, nor one not joined", ban ? "ban" : "kick"); + return FALSE; + } + network_game_server_machine_names(server, machine_index, names, sizeof(names)); + if (ban) + network_game_server_ban_machine(machine_index, names); + else + network_game_server_kick_named_machine(machine_index, names); return TRUE; } @@ -1332,7 +1408,11 @@ void network_game_server_dispose( network_connection_delete(server->connection); #ifdef xbox + /* port: native transports flush before closing sockets, and unregistering + an XNet key is a stub. Preserve the original grace period on other builds. */ +#if !defined(HALO_WINDOWS) && !defined(HALO_ANDROID) && !defined(__linux__) SleepEx(MILLISECONDS_PER_SECOND, FALSE); +#endif transport_server_terminate(); #endif @@ -2000,6 +2080,18 @@ boolean network_game_server_game_is_valid( return game_is_valid; } +/* port: why network_game_server_accept_client_machine_into_game last +refused a machine: a banned one's, one dropped for cheating, or one kicked +by a vote, is told it is kept out (_rejection_code_blacklisted_machine); any +other, that the game is not open */ +static short network_game_server_refusal_code = _rejection_code_game_is_closed; + +short network_game_server_last_refusal_code( + void) +{ + return network_game_server_refusal_code; +} + boolean network_game_server_accept_client_machine_into_game( struct network_game_server *server, struct network_game_server_client_machine *machine) @@ -2014,6 +2106,7 @@ boolean network_game_server_accept_client_machine_into_game( it by it), not the first free one: another connection's slot gave two machines one index */ machine_index = machine->machine_index; + network_game_server_refusal_code = _rejection_code_game_is_closed; /* port: not a machine of an address dropped for cheating */ { struct transport_address address = { { { 0 } } }; @@ -2026,6 +2119,7 @@ boolean network_game_server_accept_client_machine_into_game( { network_event("refusing a machine @ %s: dropped from this game for cheating", transport_address_to_string(&address)); + network_game_server_refusal_code = _rejection_code_blacklisted_machine; return FALSE; } } @@ -2039,6 +2133,17 @@ boolean network_game_server_accept_client_machine_into_game( network_game_server_hardware_ids[machine_index] : "")) { network_event("refusing a machine @ %s: banned (bans.txt)", transport_address_to_string(&address)); + network_game_server_refusal_code = _rejection_code_blacklisted_machine; + return FALSE; + } + /* (nor one kicked by a vote, for a while: network_votekick.c) */ + if (!network_game_server_client_machine_is_local(server, machine) && + network_votekick_kept_out(address.address.long_words[0], + VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ? + network_game_server_hardware_ids[machine_index] : "")) + { + network_event("refusing a machine @ %s: kicked by a vote", transport_address_to_string(&address)); + network_game_server_refusal_code = _rejection_code_blacklisted_machine; return FALSE; } } @@ -2105,6 +2210,10 @@ boolean network_game_server_accept_client_machine_into_game( } network_game_server_client_machine_addresses[machine_index] = address.address.long_words[0]; network_game_server_client_machine_join_times[machine_index] = system_milliseconds(); + /* (its time played for votes starts over) */ + network_votekick_machine_joined(machine_index); + /* (and its voice chat key) */ + network_voice_machine_joined(machine_index); } else { diff --git a/source/networking/network_server_manager.h b/source/networking/network_server_manager.h index c5b7eec6..6810b300 100644 --- a/source/networking/network_server_manager.h +++ b/source/networking/network_server_manager.h @@ -49,6 +49,10 @@ void network_game_generate_join_game_token( void network_game_server_kick_machine( long machine_index, boolean kept_out); +/* port: the rejection code of the last machine +network_game_server_accept_client_machine_into_game refused */ +short network_game_server_last_refusal_code( + void); /* the host's ban command (console.c, hs.c) */ enum { @@ -83,6 +87,11 @@ boolean network_game_server_ban_player( bans.txt line, the address not kept out): the player may join again at once */ boolean network_game_server_kick_player( char const *text); +/* port: the host's Kick and Ban on the scoreboard: the client machine +(its index), kicked or banned as those commands do */ +boolean network_game_server_kick_machine_of_player( + long machine_index, + boolean ban); short network_game_server_matching_player_names( char const *text, char (*names)[NETWORK_GAME_SERVER_NAME_TEXT_SIZE], diff --git a/source/networking/network_server_message_handler.c b/source/networking/network_server_message_handler.c index e01e93ba..a268d3e0 100644 --- a/source/networking/network_server_message_handler.c +++ b/source/networking/network_server_message_handler.c @@ -267,6 +267,9 @@ symbols in this file: /* port/linux/game/network_distributed.c's */ void network_distributed_handle_message(long machine_index, word const *message, word size); +/* port/linux/game/network_voice.c's: voice chat, in the lobby too */ +boolean network_voice_handles_message(word const *message, word size); +void network_voice_handle_message(long machine_index, word const *message, word size); void network_distributed_handle_stream_message(long machine_index, word const *message, word size); /* ---------- constants */ @@ -1364,6 +1367,19 @@ boolean network_game_server_handle_client_message( case _message_type_data: /* the distributed netcode's messages (port/linux/NETCODE.md) */ + /* (port: voice chat's from any machine that joined, in the + lobby too: network_voice.c) */ + if (network_voice_handles_message(message, message_buffer_size)) + { + long machine_index; + + if (network_game_server_client_machine_is_joined_to_game(server, machine) && + network_game_server_get_client_machine(server, machine, &machine_index)) + { + network_voice_handle_message(machine_index, message, message_buffer_size); + } + break; + } /* (in game, from a machine that has loaded it, as a datagram is: else dropped) */ if (network_game_server_client_machine_is_joined_to_game(server, machine) && @@ -1561,6 +1577,18 @@ boolean network_game_server_handle_datagram( case _message_type_data: /* the distributed netcode's messages (port/linux/NETCODE.md) */ + /* (port: voice chat's from any machine that joined, in the + lobby too, its key checked: network_voice.c) */ + if (network_voice_handles_message(message, datagram_size)) + { + struct network_game_server_client_machine *client_machine = + network_game_server_get_client_machine_at_address(server, source_address->address.long_words[0]); + long machine_index; + + if (client_machine && network_game_server_get_client_machine(server, client_machine, &machine_index)) + network_voice_handle_message(machine_index, message, datagram_size); + break; + } /* (from a machine in the game, which the lookup finds only among those that joined, and only in game: else dropped) */ if (network_game_server_get_state(server, NULL) == _network_game_server_state_ingame) @@ -2005,7 +2033,9 @@ static boolean network_game_server_handle_message_client_join_game_request( struct message_server_machine_rejected rejection; struct network_message *reply; - rejection.reason = _network_game_server_rejection_reason_game_not_open; + /* port: a banned machine is told so (it was told the + game is not open) */ + rejection.reason = network_game_server_last_refusal_code(); network_event( "server failed to accept valid client machine '%s' @%s into the game", join_game_request.machine_name, diff --git a/source/networking/telnet_console.c b/source/networking/telnet_console.c index 1be0f0fb..fb83db23 100644 --- a/source/networking/telnet_console.c +++ b/source/networking/telnet_console.c @@ -319,6 +319,27 @@ static void telnet_client_disconnect( return; } +/* port: whether a line is part of an HTTP request. A web page can make the +browser send one to this machine's port, whose lines would otherwise run as +scripts: such a client is dropped at its first line. No script starts with +a request's method and a path, nor holds " HTTP/". */ +static boolean telnet_line_is_http( + char const *line) +{ + static char const *const methods[] = + { + "GET ", "POST ", "PUT ", "HEAD ", "DELETE ", "OPTIONS ", "PATCH ", "CONNECT ", "TRACE ", + }; + long index; + + for (index = 0; index < (long)NUMBEROF(methods); index++) + { + if (!csstrncmp(line, methods[index], csstrlen(methods[index]))) + return TRUE; + } + return strstr(line, " HTTP/") != NULL; +} + /* FALSE when the client was lost (it is then dropped) */ static boolean process_telnet_client_buffer( char *buffer, @@ -367,6 +388,12 @@ static boolean process_telnet_client_buffer( expression[TELNET_CLIENT_BUFFER_SIZE-1] = 0; client->buffer[0] = 0; + if (telnet_line_is_http(expression)) + { + telnet_client_disconnect(client); + error(2, "dropped a telnet client that sent an HTTP request"); + return TRUE; + } if (hs_compile_and_evaluate(expression)) { telnet_client_write(client, "\r\n", 2); diff --git a/source/objects/object_types.c b/source/objects/object_types.c index 87b1b869..d6c9a90f 100644 --- a/source/objects/object_types.c +++ b/source/objects/object_types.c @@ -473,11 +473,11 @@ struct object_type_definition *object_type_definitions[NUMBER_OF_OBJECT_TYPES] = struct object_type_definition *first_object_type_definition; /* VC7 otherwise emits this tentative definition as a common symbol. */ -#ifndef HALO_ANDROID /* Mach-O section names differ; the default is .bss anyway */ +#ifndef HALO_ARM64_GUEST /* Mach-O section names differ; the default is .bss anyway */ #pragma bss_seg(".bss") #endif static word processed_bsp_flags; -#ifndef HALO_ANDROID +#ifndef HALO_ARM64_GUEST #pragma bss_seg() #endif diff --git a/source/objects/objects.c b/source/objects/objects.c index c88154ff..4c9f6a1d 100644 --- a/source/objects/objects.c +++ b/source/objects/objects.c @@ -560,7 +560,8 @@ void object_pvs_set_camera_point( void objects_port_set_activating_cluster( short cluster_index) { - if (cluster_index == NONE || cluster_index >= global_structure_bsp_get()->clusters.count) + /* (port: a co-op host sends it, so any index outside the BSP's clusters is none) */ + if (cluster_index < 0 || cluster_index >= global_structure_bsp_get()->clusters.count) { object_globals->pvs_activation_type = _pvs_activation_normal; return; @@ -3680,7 +3681,9 @@ long object_new( if (object_index==NONE && definition_index!=NONE) { char string[512]; - sprintf(string, "OUT OF OBJECTS: cannot create %s", tag_name_strip_path(tag_get_name(definition_index))); + /* port: snprintf (a map's tag's name may be any length) */ + snprintf(string, sizeof(string), "OUT OF OBJECTS: cannot create %s", + tag_name_strip_path(tag_get_name(definition_index))); console_printf(FALSE, "%s", string); error(_error_log, "%s", string); } diff --git a/source/objects/widgets/antenna.c b/source/objects/widgets/antenna.c index 6bb75b7a..e019cc28 100644 --- a/source/objects/widgets/antenna.c +++ b/source/objects/widgets/antenna.c @@ -347,14 +347,11 @@ static void antenna_update_attachment( delta.i = attachment_point->x - antenna->last_attachment_location.x; delta.j = attachment_point->y - antenna->last_attachment_location.y; delta.k = attachment_point->z - antenna->last_attachment_location.z; - /* - * BUG (original): integer truncation makes this an effective two-unit - * threshold. A non-matching correctness fix would compare fabs(delta.i), - * fabs(delta.j), and fabs(delta.k) directly against 1.0f. - */ - if ((real)abs((long)delta.i) > 1.0f || - (real)abs((long)delta.j) > 1.0f || - (real)abs((long)delta.k) > 1.0f) + /* port: the movement compared as it is (it was truncated to whole units + first, so it took two units of movement to move the vertices along) */ + if (fabsf(delta.i) > 1.0f || + fabsf(delta.j) > 1.0f || + fabsf(delta.k) > 1.0f) { short vertex_index; diff --git a/source/objects/widgets/flags.c b/source/objects/widgets/flags.c index aa026105..62ef73c1 100644 --- a/source/objects/widgets/flags.c +++ b/source/objects/widgets/flags.c @@ -476,15 +476,12 @@ void flag_update_attachment( delta.j = attachment_points->y - flag->first_attachment.y; delta.k = attachment_points->z - flag->first_attachment.z; - /* - * BUG (original): integer truncation makes this an effective two-unit - * threshold, as in antenna_update_attachment. A non-matching correctness - * fix would compare fabs(delta.i), fabs(delta.j), and fabs(delta.k) - * directly against 1.0f. - */ - if ((real)abs((long)delta.i) > 1.0f || - (real)abs((long)delta.j) > 1.0f || - (real)abs((long)delta.k) > 1.0f) + /* port: the movement compared as it is, as in antenna_update_attachment + (it was truncated to whole units first, so it took two units of movement + to move the vertices along) */ + if (fabsf(delta.i) > 1.0f || + fabsf(delta.j) > 1.0f || + fabsf(delta.k) > 1.0f) { short x; diff --git a/source/physics/breakable_surfaces.c b/source/physics/breakable_surfaces.c index 508cb3ba..7cc05f41 100644 --- a/source/physics/breakable_surfaces.c +++ b/source/physics/breakable_surfaces.c @@ -272,12 +272,7 @@ void breakable_surface_damage_area_of_effect( if (cutoff_radius > 4.0f) { - /* BUG (preserved for exact matching): %d is given a real, which the variadic call - * passes as a double (January 0x536430 +0x73..+0x79 fstp qword [esp]), so the - * message prints the double's low word. Reached for damaging area-of-effect damage - * with a cutoff radius above 4 while breakable surfaces are enabled. A corrected - * build should use %f. Source-policy approval pending (2026-09-27 audit). */ - error(_error_silent, "WARNING: area of effect breakable surface damage with radius %d", cutoff_radius); + error(_error_silent, "WARNING: area of effect breakable surface damage with radius %f", cutoff_radius); } /* port: no more than the globals hold (MAXIMUM_BREAKABLE_SURFACES_PER_MAP: diff --git a/source/rasterizer/rasterizer.c b/source/rasterizer/rasterizer.c index 15ba5dd0..68709606 100644 --- a/source/rasterizer/rasterizer.c +++ b/source/rasterizer/rasterizer.c @@ -686,12 +686,12 @@ struct rasterizer_debug_options rasterizer_debug_options = { 0 }, /* pad8A[2] */ }; /* No PDB name survives for this target-owned BSS symbol. */ -#ifndef HALO_ANDROID /* Mach-O section names differ; the default is .bss anyway */ +#ifndef HALO_ARM64_GUEST /* Mach-O section names differ; the default is .bss anyway */ #pragma bss_seg(".bss") #endif static long bss_004662ec; real_argb_color *global_rasterizer_model_ambient_reflection_tint; -#ifndef HALO_ANDROID +#ifndef HALO_ARM64_GUEST #pragma bss_seg() #endif @@ -1901,6 +1901,7 @@ void rasterizer_debug_model_vertices( { debug_vertex_count = 0; closest_debug_vertex_index = NONE; + closest_debug_vertex_dot = -1.f; match_assert( "c:\\halo\\SOURCE\\rasterizer\\rasterizer.c", 857, @@ -2032,13 +2033,6 @@ void rasterizer_debug_model_vertices( camera_dot = dot_product3d( &camera_to_vertex, &global_window_parameters.camera.forward); - /* BUG (preserved for exact matching): closest_debug_vertex_dot is never initialised - * (only closest_debug_vertex_index is), so the first vertex compares an unassigned - * real here (January 0x56cb00 reads [ebp-0x2c] at +0x496 and +0x4a3; its only store - * is +0x4b9). Reached whenever the hs global rasterizer_debug_model_vertices is set - * and the obscuring object is drawn; the value only chooses which vertex label is - * drawn red. A corrected build should initialise it to -1.0f. Source-policy approval - * pending (2026-09-27 audit). */ if ((dot_product3d(&camera_to_vertex, &normal) < 0.f && closest_debug_vertex_dot < camera_dot) || closest_debug_vertex_dot == -1.f) diff --git a/source/rasterizer/rasterizer.h b/source/rasterizer/rasterizer.h index 4882309f..40a1a120 100644 --- a/source/rasterizer/rasterizer.h +++ b/source/rasterizer/rasterizer.h @@ -40,7 +40,10 @@ enum this size: 2048 groups take 320 KB. */ RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS = 2048, RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS2 = 32, - RASTERIZER_MAXIMUM_DYNAMIC_TRIANGLES = 32768, + /* port: the structure's 32768 triangles (render.h) and, past them, as + much again as the Xbox's 16384 left a frame for its lights, shadows and + contrails (a request that would fill the buffer is refused) */ + RASTERIZER_MAXIMUM_DYNAMIC_TRIANGLES = 49152, RASTERIZER_MAXIMUM_DYNAMIC_TRIANGLE_BUFFERS = 1024, /* four per particle (build_sprites_begin), for the native builds' larger particle pool (halo_port_capacity.h) */ diff --git a/source/rasterizer/rasterizer_debug.c b/source/rasterizer/rasterizer_debug.c index abd08e5d..9b53f923 100644 --- a/source/rasterizer/rasterizer_debug.c +++ b/source/rasterizer/rasterizer_debug.c @@ -78,11 +78,6 @@ enum MAXIMUM_VERTICES_PER_DEBUG_PRIMITIVE = 3 }; -enum -{ - RASTERIZER_DEBUG_LINE_ZBIAS = 16 -}; - enum { _rasterizer_stats_none = 0, @@ -388,7 +383,7 @@ void rasterizer_debug_draw( } rasterizer_dynamic_vertices_unlock(vertex_buffer_index); - rasterizer_debug_drawing_begin(TRUE, 0); + rasterizer_debug_drawing_begin(TRUE); rasterizer_draw_dynamic_vertices( 0, triangle_count, @@ -440,7 +435,7 @@ void rasterizer_debug_draw( } rasterizer_dynamic_vertices_unlock(vertex_buffer_index); - rasterizer_debug_drawing_begin(TRUE, RASTERIZER_DEBUG_LINE_ZBIAS); + rasterizer_debug_drawing_begin(TRUE); rasterizer_draw_dynamic_vertices( 0, line_count, @@ -485,7 +480,7 @@ void rasterizer_debug_draw( primitive->vertices, primitive->vertex_count*sizeof(struct rasterizer_debug_vertex)); rasterizer_dynamic_vertices_unlock(vertex_buffer_index); - rasterizer_debug_drawing_begin(FALSE, 0); + rasterizer_debug_drawing_begin(FALSE); rasterizer_draw_dynamic_vertices( 0, 1, diff --git a/source/rasterizer/rasterizer_lights.c b/source/rasterizer/rasterizer_lights.c index 8c5b1f6d..501957c1 100644 --- a/source/rasterizer/rasterizer_lights.c +++ b/source/rasterizer/rasterizer_lights.c @@ -88,6 +88,7 @@ symbols in this file: #include "math/periodic_functions.h" #include "objects/widgets/widget_types.h" #include "render.h" +#include "render/render_cameras_internal.h" /* port: render_frustum_sphere_visible */ #include "scenario/scenario.h" #include "tag_files/tag_groups.h" #include "rasterizer_widgets.h" @@ -102,6 +103,7 @@ symbols in this file: #include #include "rasterizer/xbox/rasterizer_xbox.h" #include "main/main.h" +#include "view_fov.h" /* port: port/linux/game/view_fov.c */ /* ---------- constants */ @@ -546,6 +548,9 @@ void rasterizer_lens_flare_submit( 268, (parameters->compressed_window_index&_lens_flare_window_index_mask)==global_window_parameters.window_index); + /* port: not the first-person weapon's with it hidden (view_fov.c) */ + if (!viewmodel_draws_geometry((parameters->compressed_window_index & _lens_flare_first_person_weapon_flag) != 0)) + return; if (rasterizer_debug_options.draw_lens_flares && !screenshot_in_progress() && global_window_parameters.rasterizer_target==_rasterizer_target_render_primary) { @@ -560,9 +565,19 @@ void rasterizer_lens_flare_submit( &camera_offset); camera_distance= dot_product3d(&global_window_parameters.camera.forward, &camera_offset); + /* port: none out of view either (behind the camera, off the + screen): its occlusion test would count no pixels, so it would not + be drawn, and a level of many lights (Halo PC's maps) tested + hundreds a frame, each a draw of its own (the sphere takes in the + test's offset point and size, rasterizer_lens_flares_submit_occlusion_tests). + Not the first-person weapon's: it is drawn at the weapon's own + projection (display.viewmodel_fov, view_fov.c), not this frustum's */ if ((parameters->definition->far_fade_distance==0.0f || camera_distancedefinition->far_fade_distance) && - (parameters->compressed_light_color&LENS_FLARE_LIGHT_COLOR_ALPHA_MASK)>0) + (parameters->compressed_light_color&LENS_FLARE_LIGHT_COLOR_ALPHA_MASK)>0 && + ((parameters->compressed_window_index&_lens_flare_first_person_weapon_flag) || + render_frustum_sphere_visible(&global_window_parameters.frustum, ¶meters->position, + 3.0f * parameters->definition->occlusion_radius))) { struct rasterizer_lens_flare_submit_parameters *lens_flare_parameters= lens_flare_parameters_get((short)local_lens_flare_count++); @@ -850,9 +865,15 @@ void rasterizer_lens_flares_submit_occlusion_tests( if ((lens_flare_parameters->compressed_window_index & _lens_flare_window_index_mask) == global_window_parameters.window_index) { + boolean first_person = (lens_flare_parameters->compressed_window_index & + _lens_flare_first_person_weapon_flag) != 0; real occlusion_radius = definition->occlusion_radius; real_point3d occlusion_point; + /* port: the first-person weapon's projection (view_fov.c) */ + if (first_person) + viewmodel_projection_begin(); + switch (definition->occlusion_offset_direction) { case _lens_flare_occlusion_offset_direction_toward_viewer: @@ -886,6 +907,9 @@ void rasterizer_lens_flares_submit_occlusion_tests( &occlusion_point, occlusion_radius, lens_flare_index); + + if (first_person) + viewmodel_projection_end(); } } @@ -923,8 +947,14 @@ void rasterizer_lens_flares_draw( if ((lens_flare_parameters->compressed_window_index & _lens_flare_window_index_mask) == global_window_parameters.window_index) { + boolean first_person = (lens_flare_parameters->compressed_window_index & + _lens_flare_first_person_weapon_flag) != 0; struct lens_flare_definition *definition = lens_flare_parameters->definition; + /* port: the first-person weapon's projection (view_fov.c) */ + if (first_person) + viewmodel_projection_begin(); + if (lens_flare_parameters->internal__occlusion_pixels > 0 && LENS_FLARE_LIGHT_COLOR_ALPHA(lens_flare_parameters->compressed_light_color) > 0 && definition->reflections.count > 0) @@ -1165,6 +1195,9 @@ void rasterizer_lens_flares_draw( } } } + + if (first_person) + viewmodel_projection_end(); } } diff --git a/source/rasterizer/rasterizer_transparent_enclosure.h b/source/rasterizer/rasterizer_transparent_enclosure.h new file mode 100644 index 00000000..3fa60608 --- /dev/null +++ b/source/rasterizer/rasterizer_transparent_enclosure.h @@ -0,0 +1,160 @@ +/* port: whether a closed convex shell holds every vertex of another part +(models_fix_transparent_part_links, once as a map's tags load); a position is +the first three reals of both model vertex formats */ +#ifndef __RASTERIZER_TRANSPARENT_ENCLOSURE_H +#define __RASTERIZER_TRANSPARENT_ENCLOSURE_H + +static boolean rasterizer_transparent_encloses( + byte const *outer, long outer_count, long outer_stride, + word const *indices, long triangle_count, boolean strip, + byte const *inner, long inner_count, long inner_stride) +{ + real points[64][3], center[3] = {0}, extent = 0, epsilon; + short welded[64], triangles[128][3]; + long i, j, k, unique_count = 0, face_count = 0; + + if (!outer || !inner || !indices || outer_count < 4 || outer_count > 64 || + inner_count <= 0 || inner_count > 1024 || triangle_count < 4 || triangle_count > 128 || + outer_stride < 3 * sizeof(real) || inner_stride < 3 * sizeof(real)) + { + return FALSE; + } + for (i = 0; i < outer_count; ++i) + { + real const *p = (real const *)(outer + i * outer_stride); + for (k = 0; k < 3; ++k) + { + if (!(p[k] > -1000000.0f && p[k] < 1000000.0f)) + { + return FALSE; + } + center[k] += p[k] / outer_count; + } + } + for (i = 0; i < outer_count; ++i) + { + for (k = 0; k < 3; ++k) + { + extent = MAX(extent, fabs(((real const *)(outer + i * outer_stride))[k] - center[k])); + } + } + if (!(extent > 0.000001f)) + { + return FALSE; + } + epsilon = extent * 0.00001f; + for (i = 0; i < outer_count; ++i) + { + real const *p = (real const *)(outer + i * outer_stride); + for (j = 0; j < unique_count; ++j) + { + if (fabs(p[0] - points[j][0]) < epsilon && + fabs(p[1] - points[j][1]) < epsilon && fabs(p[2] - points[j][2]) < epsilon) + { + break; + } + } + welded[i] = (short)j; + if (j == unique_count) + { + memcpy(points[j], p, sizeof(points[j])); + ++unique_count; + } + } + for (i = 0; i < triangle_count; ++i) + { + long start = strip ? i : 3 * i; + short ids[3]; + real ab[3], ac[3], normal[3], length, distance = 0; + for (k = 0; k < 3; ++k) + { + word index = indices[start + k]; + if (index >= outer_count) + { + return FALSE; + } + ids[k] = welded[index]; + } + if (strip && (i & 1)) + { + short swap = ids[0]; + ids[0] = ids[1]; + ids[1] = swap; + } + if (ids[0] == ids[1] || ids[1] == ids[2] || ids[2] == ids[0]) + { + continue; + } + for (k = 0; k < 3; ++k) + { + ab[k] = points[ids[1]][k] - points[ids[0]][k]; + ac[k] = points[ids[2]][k] - points[ids[0]][k]; + } + normal[0] = ab[1] * ac[2] - ab[2] * ac[1]; + normal[1] = ab[2] * ac[0] - ab[0] * ac[2]; + normal[2] = ab[0] * ac[1] - ab[1] * ac[0]; + length = square_root(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]); + if (!(length > epsilon * epsilon)) + { + return FALSE; + } + for (k = 0; k < 3; ++k) + { + normal[k] /= length; + distance += normal[k] * (center[k] - points[ids[0]][k]); + } + /* Halo's model winding points inward. Require convexity and strict + containment, so intersecting or reversed meshes keep their order. */ + if (!(distance > epsilon)) + { + return FALSE; + } + for (j = 0; j < unique_count + inner_count; ++j) + { + real const *p = j < unique_count ? points[j] : (real const *)(inner + (j - unique_count) * inner_stride); + distance = 0; + for (k = 0; k < 3; ++k) + { + if (!(p[k] > -1000000.0f && p[k] < 1000000.0f)) + { + return FALSE; + } + distance += normal[k] * (p[k] - points[ids[0]][k]); + } + if (!(distance >= (j < unique_count ? -epsilon : epsilon))) + { + return FALSE; + } + } + memcpy(triangles[face_count++], ids, sizeof(ids)); + } + if (face_count < 4) + { + return FALSE; + } + /* Every welded edge needs exactly one reverse edge. */ + for (i = 0; i < face_count; ++i) + { + for (k = 0; k < 3; ++k) + { + short a = triangles[i][k], b = triangles[i][(k + 1) % 3]; + long reverse = 0, forward = 0, edge; + for (j = 0; j < face_count; ++j) + { + for (edge = 0; edge < 3; ++edge) + { + short c = triangles[j][edge], d = triangles[j][(edge + 1) % 3]; + reverse += c == b && d == a; + forward += c == a && d == b; + } + } + if (reverse != 1 || forward != 1) + { + return FALSE; + } + } + } + return TRUE; +} + +#endif diff --git a/source/rasterizer/rasterizer_transparent_geometry.c b/source/rasterizer/rasterizer_transparent_geometry.c index e6baf392..c6aa3af5 100644 --- a/source/rasterizer/rasterizer_transparent_geometry.c +++ b/source/rasterizer/rasterizer_transparent_geometry.c @@ -92,6 +92,7 @@ symbols in this file: #include "rasterizer/xbox/rasterizer_xbox_state.h" #include "shaders/shader_definitions.h" #include "shaders/shaders.h" +#include "view_fov.h" /* port: port/linux/game/view_fov.c */ /* ---------- constants */ @@ -528,6 +529,11 @@ static void rasterizer_sort_external( sizeof(*transparent_geometry_group_sorted_indices), group_sorted_indices_cmpfn); + /* port: move model packets next to their glass surface, leaving other + packets on their original side of every surface. */ + rasterizer_transparent_geometry_order_models( + transparent_geometry_group_sorted_indices, transparent_geometry_group_count); + for (group_index = 0; group_indexbase.type != _shader_type_model) + return; match_assert( "c:\\halo\\SOURCE\\rasterizer\\xbox\\rasterizer_xbox_models.c", 729, diff --git a/source/rasterizer/xbox/rasterizer_xbox_profile.c b/source/rasterizer/xbox/rasterizer_xbox_profile.c index 3f528298..d1249fab 100644 --- a/source/rasterizer/xbox/rasterizer_xbox_profile.c +++ b/source/rasterizer/xbox/rasterizer_xbox_profile.c @@ -630,17 +630,7 @@ static void profile_assert( } else { - /* BUG (preserved for exact matching): January pushes the same two - * varargs (profile, message) in both branches (target push and - * relocation order; the later /Od build at 0x8004c0 does the same), so - * this format's %s consumes the NONE profile value, not the message. - * A corrected build should pass only message here. - */ - error( - _error_silent, - "### PROFILE: %s -- tell Bernie!", - profile, - message); + error(_error_silent, "### PROFILE: %s -- tell Bernie!", message); } rasterizer_profile_error_count++; diff --git a/source/rasterizer/xbox/rasterizer_xbox_screen_effect.c b/source/rasterizer/xbox/rasterizer_xbox_screen_effect.c index 15d82169..68154707 100644 --- a/source/rasterizer/xbox/rasterizer_xbox_screen_effect.c +++ b/source/rasterizer/xbox/rasterizer_xbox_screen_effect.c @@ -492,6 +492,28 @@ void _rasterizer_screen_effect( real_rectangle2d vertex_bounds; unsigned long light_enhancement_input; unsigned long desaturation_input; + struct rasterizer_cinematic_screen_effect_parameters scaled_parameters; + + /* port: the native builds draw the screen at several pixels to the + Xbox's one (halo_screen_scale): a convolution's few copies of the + screen, apart by its radius in the Xbox's pixels, blended into a blur + at 640x480, and at four times the pixels they stand apart as sharp + ghosts (the zoom's warp). The same spread in that many times the + passes, each a step of it, blends them again. */ + if (parameters->convolution_type != _rasterizer_screen_effect_convolution_type_none && !parameters->video_on) + { + long steps = (long)(halo_screen_scale() + 0.999f); + + if (steps > 6) + steps = 6; + if (steps > 1) + { + scaled_parameters = *parameters; + scaled_parameters.convolution_radius /= (real)steps; + parameters = &scaled_parameters; + pass_count *= (short)steps; + } + } if (parameters->video_on) { diff --git a/source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c b/source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c index a6ec8edb..1c03f524 100644 --- a/source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c +++ b/source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c @@ -138,6 +138,10 @@ symbols in this file: #include "rasterizer/rasterizer_console_vars.h" #include "rasterizer/rasterizer_active_camouflage.h" #include "rasterizer/rasterizer_geometry.h" +/* port: vertex layouts for conservative world-space model bounds. */ +#include "rasterizer/rasterizer_model_types.h" +/* port: use the model's node limit when validating skinning. */ +#include "models/model_definitions.h" #include "rasterizer/rasterizer_transparent_geometry.h" #include "rasterizer/xbox/rasterizer_xbox_internal.h" #include "render/render.h" @@ -866,6 +870,57 @@ void rasterizer_transparent_geometry_dispose_aux_buffer( /* ---------- private code */ +/* port: mesh and material checks run only when model tags load. */ +#include "rasterizer/rasterizer_transparent_enclosure.h" + +boolean rasterizer_transparent_geometry_is_enclosure( + struct shader const *glass, struct vertex_buffer const *outer, + struct triangle_buffer const *triangles, + struct shader const *energy, struct vertex_buffer const *inner) +{ + struct shader_transparent_glass_definition const *shell = (void const *)glass; + struct shader_transparent_generic const *core; + byte *outer_data = NULL, *inner_data = NULL, *index_data = NULL; + boolean result; + + if (!glass || !energy || glass->base.type != _shader_type_transparent_glass || + energy->base.type != _shader_type_transparent_generic) + { + return FALSE; + } + core = &((struct shader_transparent_generic_definition const *)energy)->generic; + if (!TEST_FLAG(shell->flags, _shader_transparent_glass_flag_two_sided_bit) || + TEST_FLAG(shell->flags, _shader_transparent_glass_flag_decal_bit) || + shell->reflection_type == _shader_transparent_glass_reflection_type_dynamic_mirror || + core->flags != FLAG(_shader_transparent_flag_two_sided_bit) || + TEST_FLAG(energy->base.radiosity.flags, _shader_radiosity_FILTHY_transparent_lit_bit) || + core->framebuffer_blend_function != _framebuffer_blend_function_add || + core->framebuffer_fade_mode != _framebuffer_fade_mode_none || + core->extra_layers.count || core->lens_flare.index != NONE || + !outer || !inner || !triangles || !outer->hardware_format || !inner->hardware_format || + !triangles->hardware_format || outer->offset || inner->offset || + (outer->type != _rasterizer_vertex_type_model_compressed && + outer->type != _rasterizer_vertex_type_model_uncompressed) || + (inner->type != _rasterizer_vertex_type_model_compressed && + inner->type != _rasterizer_vertex_type_model_uncompressed) || + (triangles->type != _triangle_buffer_type_triangles && + triangles->type != _triangle_buffer_type_precompiled_strip)) + { + return FALSE; + } + IDirect3DVertexBuffer8_Lock(outer->hardware_format, 0, 0, &outer_data, D3DLOCK_READONLY); + IDirect3DVertexBuffer8_Lock(inner->hardware_format, 0, 0, &inner_data, D3DLOCK_READONLY); + IDirect3DIndexBuffer8_Lock(triangles->hardware_format, 0, 0, &index_data, D3DLOCK_READONLY); + result = rasterizer_transparent_encloses(outer_data, outer->count, + rasterizer_geometry_get_vertex_size(outer->type), (word const *)index_data, + triangles->count, triangles->type == _triangle_buffer_type_precompiled_strip, + inner_data, inner->count, rasterizer_geometry_get_vertex_size(inner->type)); + IDirect3DIndexBuffer8_Unlock(triangles->hardware_format); + IDirect3DVertexBuffer8_Unlock(inner->hardware_format); + IDirect3DVertexBuffer8_Unlock(outer->hardware_format); + return result; +} + /* port: an extra layer's draw, no deeper than the layers may nest (a map's layers could name each other in a loop); said once */ static void transparent_geometry_layer_draw( @@ -2230,7 +2285,7 @@ void rasterizer_transparent_geometry_group_draw( { real vsh_constants__texscale[3][4]; short fade_source = shader_transparent_generic->generic.framebuffer_fade_source; - unsigned long combiner_constant; + unsigned long combiner_constant = 0; vsh_constants__texscale[0][0] = 0.0f; vsh_constants__texscale[0][1] = 0.0f; @@ -2289,10 +2344,6 @@ void rasterizer_transparent_geometry_group_draw( combiner_constant = 0x05; break; - /* combiner_constant is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert( "c:\\halo\\SOURCE\\rasterizer\\xbox\\rasterizer_xbox_transparent_geometry.c", @@ -2778,7 +2829,7 @@ void rasterizer_transparent_geometry_group_draw( { real vsh_constants__texscale[3][4]; short fade_source = shader_transparent_chicago->chicago.framebuffer_fade_source; - unsigned long combiner_constant; + unsigned long combiner_constant = 0; vsh_constants__texscale[0][0] = 0.0f; vsh_constants__texscale[0][1] = 0.0f; @@ -2839,10 +2890,6 @@ void rasterizer_transparent_geometry_group_draw( combiner_constant = 0x05; break; - /* combiner_constant is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert( "c:\\halo\\SOURCE\\rasterizer\\xbox\\rasterizer_xbox_transparent_geometry.c", @@ -3696,3 +3743,368 @@ void rasterizer_transparent_geometry_group_draw( return; } + +/* port: use only a static BSP glass surface with a usable supplied plane. */ +static boolean transparent_planar_glass(struct transparent_geometry_group const *surface) +{ + real const *normal = (real const *)&surface->plane.n; + real length = 0; + short k; + + if (!surface->shader || surface->shader->base.type != _shader_type_transparent_glass || + surface->object_index || surface->source_object_index || surface->node_matrix_count || + surface->effect_type || surface->active_camouflage_transparent_source_object_index || + surface->cortana_hack || surface->previous_group_presorted_index != NONE || + surface->next_group_presorted_index != NONE || + !TEST_FLAG(surface->geometry_flags, _rasterizer_geometry_no_sort_bit) || + (surface->geometry_flags & ~(FLAG(_rasterizer_geometry_no_sort_bit) | FLAG(_rasterizer_geometry_no_fog_bit) | + FLAG(_rasterizer_geometry_atmospheric_fog_but_no_planar_fog_bit))) || + !(surface->plane.d > -1000000.0f && surface->plane.d < 1000000.0f)) + { + return FALSE; + } + if (TEST_FLAG(((struct shader_transparent_glass_definition const *)surface->shader)->flags, + _shader_transparent_glass_flag_decal_bit) || + ((struct shader_transparent_glass_definition const *)surface->shader)->reflection_type == + _shader_transparent_glass_reflection_type_dynamic_mirror) + { + return FALSE; + } + for (k = 0; k < 3; ++k) + { + if (!(normal[k] >= -1.001f && normal[k] <= 1.001f)) + { + return FALSE; + } + length += normal[k] * normal[k]; + } + return length > 0.998f && length < 1.002f; +} + +/* port: bound the actual skinned positions once per group, only when planar + glass is queued. Unsupported or invalid geometry retains centroid sorting. */ +static boolean transparent_model_world_bounds( + struct transparent_geometry_group const *group, real bounds[2][3]) +{ + struct vertex_buffer const *vertices = group->vertex_buffer; + real_matrix4x3 const *matrices = group->node_matrices; + byte *data = NULL; + long i, k, stride; + boolean valid = TRUE; + + if (!group->shader || !shader_type_is_transparent(group->shader->base.type) || + !shader_type_is_valid_for_model(group->shader->base.type) || + group->shader->base.type == _shader_type_transparent_water || + shader_is_water_decal(group->shader) || group->effect_type || + group->cortana_hack || group->active_camouflage_transparent_source_object_index || + (group->geometry_flags & ~(FLAG(_rasterizer_geometry_no_fog_bit) | + FLAG(_rasterizer_geometry_atmospheric_fog_but_no_planar_fog_bit) | + FLAG(_rasterizer_geometry_parts_define_local_nodes_bit))) || + !vertices || !vertices->hardware_format || vertices->offset || vertices->count <= 0 || + vertices->count > UNSIGNED_SHORT_MAX || !matrices || group->node_matrix_count <= 0 || + group->node_matrix_count > MAXIMUM_NODES_PER_MODEL || + (vertices->type != _rasterizer_vertex_type_model_compressed && + vertices->type != _rasterizer_vertex_type_model_uncompressed)) + { + return FALSE; + } + stride = rasterizer_geometry_get_vertex_size(vertices->type); + IDirect3DVertexBuffer8_Lock(vertices->hardware_format, 0, 0, &data, D3DLOCK_READONLY); + if (!data) + { + valid = FALSE; + } + for (i = 0; valid && i < vertices->count; ++i) + { + real_point3d const *point = (real_point3d const *)(data + i * stride); + real_point3d world; + real const *p = (real const *)&world; + if (group->node_matrix_count == 1) + { + matrix4x3_transform_point(matrices, point, &world); + } + else + { + short nodes[2], node; + real weights[2]; + world.x = world.y = world.z = 0; + if (vertices->type == _rasterizer_vertex_type_model_compressed) + { + struct model_vertex_compressed const *vertex = (void const *)point; + nodes[0] = vertex->nodes[0] / 3; + nodes[1] = vertex->nodes[1] / 3; + /* The draw declaration reads v6 as NORMSHORT1. Match GPU + skinning rather than the legacy byte-based decompressor. */ + weights[0] = (real)vertex->node_weight * (1.0f / 32767.0f); + weights[1] = 1.f - weights[0]; + if ((weights[0] > 0 && vertex->nodes[0] % 3) || + (weights[1] > 0 && vertex->nodes[1] % 3)) + { + valid = FALSE; + } + } + else + { + struct model_vertex_uncompressed const *vertex = (void const *)point; + nodes[0] = vertex->nodes[0]; + nodes[1] = vertex->nodes[1]; + weights[0] = vertex->node_weights[0]; + weights[1] = vertex->node_weights[1]; + if (!(fabs(weights[0] + weights[1] - 1.f) < 0.00001f)) + { + valid = FALSE; + } + } + for (node = 0; valid && node < 2; ++node) + { + real_point3d transformed; + if (!(weights[node] >= 0 && weights[node] <= 1)) + { + valid = FALSE; + } + else if (weights[node] > 0) + { + if (!VALID_INDEX(nodes[node], group->node_matrix_count)) + { + valid = FALSE; + break; + } + matrix4x3_transform_point(matrices + nodes[node], point, &transformed); + world.x += weights[node] * transformed.x; + world.y += weights[node] * transformed.y; + world.z += weights[node] * transformed.z; + } + } + } + for (k = 0; valid && k < 3; ++k) + { + if (!(p[k] > -1000000.0f && p[k] < 1000000.0f)) + { + valid = FALSE; + break; + } + bounds[0][k] = i ? MIN(bounds[0][k], p[k]) : p[k]; + bounds[1][k] = i ? MAX(bounds[1][k], p[k]) : p[k]; + } + } + IDirect3DVertexBuffer8_Unlock(vertices->hardware_format); + return valid; +} + +/* port: the whole model block must lie on one side. Touching the plane within + float error is allowed; intersections and cameras on the plane are ambiguous. */ +static short transparent_plane_model_order( + struct transparent_geometry_group const *surface, real const bounds[2][3]) +{ + real const *normal = (real const *)&surface->plane.n; + real const *camera = (real const *)&global_window_parameters.camera.position; + real low = -surface->plane.d, high = low, distance = low; + real epsilon = 0.0001f + 0.0000005f * fabs(surface->plane.d); + short side, k; + + for (k = 0; k < 3; ++k) + { + if (!(camera[k] > -1000000.0f && camera[k] < 1000000.0f)) + { + return 0; + } + low += normal[k] * bounds[normal[k] < 0 ? 1 : 0][k]; + high += normal[k] * bounds[normal[k] < 0 ? 0 : 1][k]; + distance += normal[k] * camera[k]; + } + side = low >= -epsilon && high > epsilon ? 1 : high <= epsilon && low < -epsilon ? -1 : 0; + if (!side || !(fabs(distance) > epsilon)) + { + return 0; + } + return (distance > 0) == (side > 0) ? -1 : 1; +} + +/* port: native links dispatch a whole chain from either queued member. */ +static short transparent_chain_root(short index, long count) +{ + long steps; + for (steps = 0; steps < count; ++steps) + { + struct transparent_geometry_group const *group = rasterizer_transparent_geometry_get_group_from_presorted_index(index); + short previous = group->previous_group_presorted_index; + if (previous == NONE) + { + return index; + } + if (!VALID_INDEX(previous, count) || + ((struct transparent_geometry_group *)rasterizer_transparent_geometry_get_group_from_presorted_index(previous))->next_group_presorted_index != index) + { + return NONE; + } + index = previous; + } + return NONE; +} + +/* port: preserve the order of surfaces and unrelated groups. Move a model + block immediately after its last required surface, or before its first. + Stable gaps avoid the old topological sort moving a floor across other + objects. Conflicting planes or malformed links leave the input untouched. */ +void rasterizer_transparent_geometry_order_models(short *order, long count) +{ + short roots[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; + short units[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; + short surfaces[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; + short original_gap[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; + short target_gap[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; + short result[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; + boolean seen[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS] = {0}; + boolean unit_seen[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS] = {0}; + long i, j, gap, phase, unit_count = 0, surface_count = 0, emitted = 0; + boolean changed = FALSE; + + if (count < 2 || count > RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS) + { + return; + } + /* Avoid reading any model vertices in scenes without eligible BSP glass. */ + for (i = 0; i < count; ++i) + { + if (transparent_planar_glass(rasterizer_transparent_geometry_get_group_from_presorted_index((short)i))) + { + ++surface_count; + } + } + if (!surface_count) + { + return; + } + surface_count = 0; + for (i = 0; i < count; ++i) + { + short root; + if (!VALID_INDEX(order[i], count) || seen[order[i]]) + { + return; + } + seen[order[i]] = TRUE; + root = transparent_chain_root(order[i], count); + if (root == NONE) + { + return; + } + roots[order[i]] = root; + if (!unit_seen[root]) + { + unit_seen[root] = TRUE; + units[unit_count++] = root; + original_gap[root] = target_gap[root] = (short)surface_count; + if (transparent_planar_glass(rasterizer_transparent_geometry_get_group_from_presorted_index(root))) + { + surfaces[surface_count++] = root; + original_gap[root] = target_gap[root] = NONE; + } + } + } + for (i = 0; i < unit_count; ++i) + { + short root = units[i], member = root; + long steps = 0, k, lower = 0, upper = surface_count; + real bounds[2][3], part_bounds[2][3]; + boolean valid = TRUE; + if (original_gap[root] == NONE) + { + continue; + } + do + { + struct transparent_geometry_group const *group; + if (!VALID_INDEX(member, count) || roots[member] != root || steps++ >= count) + { + return; + } + group = rasterizer_transparent_geometry_get_group_from_presorted_index(member); + if (group->object_index != ((struct transparent_geometry_group *)rasterizer_transparent_geometry_get_group_from_presorted_index(root))->object_index) + { + valid = FALSE; + } + if (valid && !transparent_model_world_bounds(group, part_bounds)) + { + valid = FALSE; + } + if (valid) + { + for (k = 0; k < 3; ++k) + { + bounds[0][k] = steps == 1 ? part_bounds[0][k] : MIN(bounds[0][k], part_bounds[0][k]); + bounds[1][k] = steps == 1 ? part_bounds[1][k] : MAX(bounds[1][k], part_bounds[1][k]); + } + } + member = group->next_group_presorted_index; + } + while (member != NONE); + if (!valid) + { + continue; + } + for (j = 0; j < surface_count; ++j) + { + short relation = transparent_plane_model_order( + rasterizer_transparent_geometry_get_group_from_presorted_index(surfaces[j]), bounds); + if (relation < 0) + { + lower = MAX(lower, j + 1); + } + else if (relation > 0) + { + upper = MIN(upper, j); + } + } + if (lower > upper) + { + return; + } + target_gap[root] = (short)PIN(original_gap[root], lower, upper); + changed |= target_gap[root] != original_gap[root]; + } + if (!changed) + { + return; + } + for (gap = 0; gap <= surface_count; ++gap) + { + /* Arrivals from earlier gaps, unchanged objects, arrivals from later + gaps: each category retains its original centroid order. */ + for (phase = -1; phase <= 1; ++phase) + { + for (i = 0; i < unit_count; ++i) + { + short root = units[i], member = root; + long direction = original_gap[root] < target_gap[root] ? -1 : + original_gap[root] > target_gap[root] ? 1 : 0; + if (target_gap[root] != gap || direction != phase) + { + continue; + } + do + { + if (emitted >= count) + { + return; + } + result[emitted++] = member; + member = ((struct transparent_geometry_group *)rasterizer_transparent_geometry_get_group_from_presorted_index(member))->next_group_presorted_index; + } + while (member != NONE); + } + } + if (gap < surface_count) + { + if (emitted >= count) + { + return; + } + result[emitted++] = surfaces[gap]; + } + } + if (emitted == count) + { + memcpy(order, result, count * sizeof(*order)); + } +} diff --git a/source/render/render.c b/source/render/render.c index 4fd9592d..3f952725 100644 --- a/source/render/render.c +++ b/source/render/render.c @@ -98,6 +98,8 @@ void vr_graphics_update(void); #endif #include "main/main.h" #include "structures/structures.h" +/* port: static transparent part links for the newly loaded model tags. */ +#include "models/models.h" /* ---------- constants */ @@ -198,6 +200,8 @@ void render_initialize( void render_initialize_for_new_map( void) { + /* port: preserve retail's energy-then-two-sided-glass compositing. */ + models_fix_transparent_part_links(); render_objects_initialize_for_new_map(); } diff --git a/source/render/render.h b/source/render/render.h index 98920685..01105a19 100644 --- a/source/render/render.h +++ b/source/render/render.h @@ -20,7 +20,8 @@ enum { MAXIMUM_RENDERED_DISTANT_LIGHTS = 2, MAXIMUM_RENDERED_POINT_LIGHTS = 2, - MAXIMUM_RENDERED_ENVIRONMENT_SURFACES = 16384, + /* port: twice the Xbox's 16384 structure triangles a frame */ + MAXIMUM_RENDERED_ENVIRONMENT_SURFACES = 32768, MAXIMUM_RENDERED_CLUSTERS = 128, MAXIMUM_SURFACES_PER_STRUCTURE = 0x20000, MAXIMUM_RENDERED_LIGHTS = 128, @@ -94,7 +95,8 @@ struct render_globals struct rendered_cluster rendered_clusters[MAXIMUM_RENDERED_CLUSTERS]; short rendered_cluster_count; unsigned long environment_surface_flags[MAXIMUM_SURFACES_PER_STRUCTURE]; - short environment_surface_count; + /* port: a long, as a short would wrap at the raised count */ + long environment_surface_count; long environment_surface_indices[MAXIMUM_RENDERED_ENVIRONMENT_SURFACES]; }; diff --git a/source/render/render_particles.c b/source/render/render_particles.c index 244d5daf..cfce7009 100644 --- a/source/render/render_particles.c +++ b/source/render/render_particles.c @@ -29,6 +29,7 @@ symbols in this file: #include "tag_files/tag_groups.h" #include "units/unit_definitions.h" #include "units/units.h" +#include "view_fov.h" /* port: port/linux/game/view_fov.c */ /* ---------- constants */ @@ -159,7 +160,10 @@ void render_particles( boolean owned_by_local_player = particle->local_player_index == local_player_index; + /* port: not the first-person weapon's with it hidden (view_fov.c) */ if (render_location_visible(&particle->location) && + viewmodel_draws_geometry(owned_by_local_player && + TEST_FLAG(particle->flags, _particle_datum_dont_draw_third_person_bit)) && (!TEST_FLAG( particle->flags, _particle_datum_dont_draw_first_person_bit) || @@ -198,7 +202,7 @@ void render_particles( { struct rendered_particle_datum *rendered_particle = rendered_particles; - short *current_sprite_group; + short *current_sprite_group = NULL; short previous_definition_index = NONE; short previous_cluster_index = NONE; boolean previous_attached_to_first_person_weapon = FALSE; @@ -212,12 +216,6 @@ void render_particles( rendered_particle->attached_to_first_person_weapon == previous_attached_to_first_person_weapon) { - /* current_sprite_group is not initialised. Not reached unassigned: the first - * rendered particle cannot take this arm, because its definition index differs from - * the initial NONE (particle_new returns for NONE and otherwise calls - * particle_definition_get, whose tag lookup halts unless the low word of the index - * is non-negative; the field is written nowhere else). Source-policy approval - * pending (2026-09-27 audit). */ (*current_sprite_group)++; } else @@ -392,12 +390,9 @@ void render_particles( rendered_particle++; } - /* BUG (original): a fully culled group divides zero by zero. - * A corrected non-matching build would store 0.0f when - * built_particle_count is zero. */ ((struct shader_effect_definition *)sprite_data.shader)-> - secondary_map_radius = - total_radius / built_particle_count; + secondary_map_radius = built_particle_count ? + total_radius / built_particle_count : 0.0f; build_sprites_end(&sprite_data); } } diff --git a/source/render/render_sprite.c b/source/render/render_sprite.c index 963ad649..a622cb63 100644 --- a/source/render/render_sprite.c +++ b/source/render/render_sprite.c @@ -89,6 +89,8 @@ symbols in this file: #include "cache/texture_cache.h" #include "tag_files/tag_files.h" #include "tag_files/tag_groups.h" +#include "custom_edition_cache.h" /* port: port/linux/game/custom_edition_cache.c */ +#include "view_fov.h" /* port: port/linux/game/view_fov.c */ /* ---------- constants */ @@ -213,6 +215,9 @@ void build_sprites_begin( !TEST_FLAG(flags, _build_sprites_valid_bit)); data->bitmap_group_index = bitmap_group_index; + /* port: the first-person weapon's projection (view_fov.c) */ + if (TEST_FLAG(flags, _build_sprites_first_person_bit)) + viewmodel_projection_begin(); data->flags = flags; data->shader = shader; data->group_count = 0; @@ -282,6 +287,9 @@ void build_sprites_end( } SET_FLAG(data->flags, _build_sprites_valid_bit, FALSE); + /* port: (view_fov.c) */ + if (TEST_FLAG(data->flags, _build_sprites_first_person_bit)) + viewmodel_projection_end(); return; } @@ -617,6 +625,10 @@ void build_sprite_rotational( &axis_of_rotation) - quarter_circle; fraction = angle*angle/(quarter_circle*quarter_circle); fraction = PIN(fraction, 0.f, 1.f); + /* port: the edge-on sprite is in the next sequence, which some Custom + Edition bitmaps don't have; those always draw the face-on sprite */ + if (first_sequence_index+1 >= bitmap_group_get(data->bitmap_group_index)->sequences.count) + fraction = 0.f; if (fraction>0.05f) { @@ -773,15 +785,34 @@ static short build_sprite_get_group( break; } - match_vassert( - "c:\\halo\\SOURCE\\render\\render_sprite.c", - 275, - group_index < data->group_count || - data->group_count < MAXIMUM_BUILD_SPRITE_GROUPS, - csprintf( - temporary, - "a build_sprites_begin call can accomodate at most %d bitmaps", - MAXIMUM_BUILD_SPRITE_GROUPS)); + /* port: a Halo PC map's particle may be drawn from more bitmaps than a + call takes (beavercreek_rev_beta's fluid bursts have a bitmap a frame, up + to 56), which halted a debug build: its sprites on the others are not + drawn, as a release build already did, and it is logged once. The Xbox's + maps' particles stay within the limit. */ + if (custom_edition_cache_tags_loaded()) + { + static boolean logged; + + if (group_index >= data->group_count && data->group_count >= MAXIMUM_BUILD_SPRITE_GROUPS && !logged) + { + error(_error_silent, "%s: a particle's sprites on more than %d bitmaps; those past them are not drawn", + tag_get_name(data->bitmap_group_index), MAXIMUM_BUILD_SPRITE_GROUPS); + logged = TRUE; + } + } + else + { + match_vassert( + "c:\\halo\\SOURCE\\render\\render_sprite.c", + 275, + group_index < data->group_count || + data->group_count < MAXIMUM_BUILD_SPRITE_GROUPS, + csprintf( + temporary, + "a build_sprites_begin call can accomodate at most %d bitmaps", + MAXIMUM_BUILD_SPRITE_GROUPS)); + } if (group_index < data->group_count || data->group_count < MAXIMUM_BUILD_SPRITE_GROUPS) diff --git a/source/saved games/game_state.c b/source/saved games/game_state.c index 4c9d1689..0b5a073b 100644 --- a/source/saved games/game_state.c +++ b/source/saved games/game_state.c @@ -300,7 +300,9 @@ void game_state_initialize_for_new_map( memset(game_state_globals.header, 0, sizeof(*game_state_globals.header)); name = tag_get_name(global_scenario_index); - strcpy(game_state_globals.header->map_name, name); + /* port: no longer than the header has room for (a map's scenario's + name is the map's) */ + strncpy(game_state_globals.header->map_name, name, sizeof(game_state_globals.header->map_name) - 1); strcpy(game_state_globals.header->build_number, "01.01.14.2342"); game_state_globals.header->player_count = player_spawn_count; diff --git a/source/saved games/saved_game_files.c b/source/saved games/saved_game_files.c index c179191e..642d5945 100644 --- a/source/saved games/saved_game_files.c +++ b/source/saved games/saved_game_files.c @@ -320,6 +320,7 @@ symbols in this file: #include "saved games/game_state.h" #include "saved games/player_profile.h" #include "saved games/playlist_profile.h" +#include "interface/player_ui.h" #include "text/text_group.h" #include "tag_files/tag_groups.h" /* the saved game file checksum is an XDK content signature, and enumerated @@ -851,17 +852,22 @@ void saved_game_file_remember_last_used_multiplayer_map( char const *map_name) { struct file_reference file; + /* port: the name in a buffer of the size written (callers' may be + smaller: the menus' map list's are 64 bytes), the rest zeros */ + char name[MAXIMUM_FILENAME_LENGTH+1]; match_assert( "c:\\halo\\SOURCE\\saved games\\saved_game_files.c", 1207, map_name); + csmemset(name, 0, sizeof(name)); + csstrncpy(name, map_name, sizeof(name) - 1); if (file_reference_create_from_path(&file, "z:\\lastmpmp.txt", FALSE) && file_create(&file) && file_open(&file, FLAG(_permission_write_bit))) { - if (!file_write(&file, MAXIMUM_FILENAME_LENGTH+1, map_name)) + if (!file_write(&file, MAXIMUM_FILENAME_LENGTH+1, name)) { error(_error_silent, "failed to write to '%s'", "z:\\lastmpmp.txt"); } @@ -1541,6 +1547,12 @@ boolean delete_enumerated_saved_game_file( error(_error_silent, "remove_nth_entry_in_mapfile() failed"); success = FALSE; } + else + { + /* port: the files after it moved down the list; the + indices the players hold follow them */ + player_ui_saved_game_file_removed(profile_index); + } if (memory_unit != _memory_unit_hard_drive) { @@ -1568,6 +1580,30 @@ boolean delete_enumerated_saved_game_file( return success; } +/* port: a file's index once the file of removed_index has left its memory +unit's list (delete_enumerated_saved_game_file): the files after it move +down one, so their indices do; NONE for the file removed */ +long saved_game_file_index_after_removal( + long profile_index, + long removed_index) +{ + long n; + long removed_n; + + if (profile_index == NONE || removed_index == NONE || + SAVED_GAME_FILE_INDEX_MEMORY_UNIT(profile_index) != SAVED_GAME_FILE_INDEX_MEMORY_UNIT(removed_index)) + { + return profile_index; + } + n = SAVED_GAME_FILE_INDEX_FILE_INDEX(profile_index); + removed_n = SAVED_GAME_FILE_INDEX_FILE_INDEX(removed_index); + if (n == removed_n) + return NONE; + if (n < removed_n) + return profile_index; + return (long)(((unsigned long)profile_index & ~(0xFFFUL << 16)) | ((unsigned long)(n - 1) << 16)); +} + void saved_game_file_get_useable_untitled_profile_name( wchar_t *display_name) { @@ -1593,7 +1629,7 @@ void saved_game_file_get_useable_untitled_profile_name( for (index = 0; index < MAXIMUM_UNTITLED_SAVED_GAMES; index++) { usnprintf(display_name, MAX_GAMENAME-1, - unicode_string_list_get_string(string_list_index, _saved_game_file_string_untitled_name_format), + ustring_format_checked(unicode_string_list_get_string(string_list_index, _saved_game_file_string_untitled_name_format), "d"), index+1); display_name[MAX_GAMENAME-1] = 0; @@ -1878,14 +1914,9 @@ static void enumerate_memory_units( find_data.szSaveGameName, find_data.wfd.cFileName); message[MAXIMUM_FILENAME_LENGTH] = 0; - /* BUG (preserved for exact matching): the message embeds the save's display name - * and file name and is passed as the format (January 0x5b4b00 +0x263 pushes the - * converted text as error's format), so a '%' in either is taken as a conversion. - * Reached for a saved-game directory holding neither blam.sav nor blam.lst; whether - * such a directory occurs is not shown. A corrected build should pass the text - * through "%s". Source-policy approval pending (2026-09-27 audit). */ error( _error_silent, + "%s", wide_to_ascii(message, (char *)message, sizeof(message))); file.type = NONE; } diff --git a/source/saved games/saved_game_files.h b/source/saved games/saved_game_files.h index e4ec1ba0..e397b651 100644 --- a/source/saved games/saved_game_files.h +++ b/source/saved games/saved_game_files.h @@ -89,6 +89,9 @@ long create_enumerated_saved_game_file( wchar_t *display_name); boolean delete_enumerated_saved_game_file( long profile_index); +long saved_game_file_index_after_removal( + long profile_index, + long removed_index); void saved_game_file_get_useable_untitled_profile_name( wchar_t *display_name); void saved_game_files_enumerate_available_to_local_player_index( diff --git a/source/sound/sound_definitions.c b/source/sound/sound_definitions.c index bcdc6a35..c8a5ec4a 100644 --- a/source/sound/sound_definitions.c +++ b/source/sound/sound_definitions.c @@ -162,13 +162,19 @@ void try_to_reset_permutations( struct sound_pitch_range *range) { short permutation_count = range->actual_permutation_count; - unsigned long all_permutations_mask = (FLAG(permutation_count) - 1); + /* port: the mask of no more permutations than it has bits, and the + previous one's bit only if there was one (NONE shifted by 255) */ + unsigned long all_permutations_mask = permutation_count >= 32 ? 0xFFFFFFFFUL : + permutation_count > 0 ? (FLAG(permutation_count) - 1) : 0; if ((~range->played_permutation_mask & all_permutations_mask) == 0) { range->played_permutation_mask = 0; - if (permutation_count > 1) - range->played_permutation_mask = FLAG((byte)range->previous_permutation_index); + if (permutation_count > 1 && range->previous_permutation_index >= 0 && + range->previous_permutation_index < 32) + { + range->played_permutation_mask = FLAG(range->previous_permutation_index); + } } return; diff --git a/source/sound/sound_dsound_xbox.c b/source/sound/sound_dsound_xbox.c index 91f7b23e..d940b50b 100644 --- a/source/sound/sound_dsound_xbox.c +++ b/source/sound/sound_dsound_xbox.c @@ -2430,12 +2430,16 @@ static void dsound_channel_set_properties( boolean gain_only) { struct sound_channel *channel= channel_get(channel_index); - real gain= dsound_globals.pause_gain*properties->gain; + /* port: a Custom Edition map's sound can be louder than a channel plays + (foundation's Reach sniper rifle fire has a gain modifier of 1.5); played + at full gain, as Halo PC does, rather than halting */ + real prop_gain = PIN(properties->gain, 0.f, 1.f); + real gain= dsound_globals.pause_gain*prop_gain; match_assert( "c:\\halo\\SOURCE\\sound\\sound_dsound_xbox.c", 980, - properties->gain>=0.f && properties->gain<=1.f); + prop_gain>=0.f && prop_gain<=1.f); match_assert( "c:\\halo\\SOURCE\\sound\\sound_dsound_xbox.c", 981, @@ -2936,12 +2940,7 @@ static void dsound_error( break; } - /* BUG (preserved for exact matching): the format has three conversions but January - * passes two values (0x5b93e0 +0x93..+0xa2), so "#%d" prints the next stack word. - * Reached on every DirectSound failure reported through dsound_error. A corrected - * build should pass result as the third value. Source-policy approval pending - * (2026-09-27 audit). */ - error(_error_silent, "DirectSound: '%s' (%s#%d)", message, result_name); + error(_error_silent, "DirectSound: '%s' (%s#%d)", message, result_name, (int)result); return; } diff --git a/source/sound/sound_manager.c b/source/sound/sound_manager.c index b223f2ce..138111c2 100644 --- a/source/sound/sound_manager.c +++ b/source/sound/sound_manager.c @@ -638,7 +638,8 @@ static long looping_sound_new( long definition_index, long identifier, struct sound_source const *source); -static void sound_set_definition_end( +/* port: report whether instance limiting leaves this voice alive. */ +static boolean sound_set_definition_end( long sound_index); static long update_potentially_audible_looping_sound( long definition_index, @@ -1503,7 +1504,7 @@ static long looping_sound_new( return looping_sound_index; } -static void sound_set_definition_end( +static boolean sound_set_definition_end( long sound_index) { struct sound_datum *sound = sound_get(sound_index); @@ -1544,18 +1545,23 @@ static void sound_set_definition_end( } else { - return; + return TRUE; } if (channel_index != NONE) { - sound_index = channel_get(channel_index)->sound_index; + /* port: sound_find_like_channel excludes the current voice. */ + sound_stop(channel_get(channel_index)->sound_index); + return TRUE; } + /* port: no other voice can be preempted; tell the caller this one + was retired before it writes to the freed channel. */ sound_stop(sound_index); + return FALSE; } - return; + return TRUE; } static long update_potentially_audible_looping_sound( @@ -1902,6 +1908,31 @@ static void sound_start_fade( return; } +/* port: retire every owned intro/loop voice when the primary handle changes. */ +static void sound_fade_looping_track_components( + long looping_sound_index, + short track_index, + long except_sound_index, + real seconds) +{ + long sound_index; + + for (sound_index = data_next_index(sound_data, NONE); + sound_index != NONE; + sound_index = data_next_index(sound_data, sound_index)) + { + struct sound_datum *sound = sound_get(sound_index); + + if (sound_index != except_sound_index && + (sound->type == _sound_start_track || sound->type == _sound_loop_track) && + sound->source_identifier == looping_sound_index && + sound->loop_track_index == track_index) + { + sound_start_fade(_sound_fade_mode_linear, seconds, NONE, sound_index); + } + } +} + static short channel_get_state( short channel_index) { @@ -2068,7 +2099,7 @@ static real source_distance_squared( short listener_index, struct sound_source *source) { - real distance_squared; + real distance_squared = 0.0f; switch (source->spatialization_mode) { @@ -2093,10 +2124,6 @@ static real source_distance_squared( source->location.position.z * source->location.position.z; break; - /* distance_squared is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_vassert( "c:\\halo\\SOURCE\\sound\\sound_manager.c", @@ -2658,6 +2685,14 @@ boolean sound_refresh_looping( if (refresh_state == _looping_sound_refresh_start) { + /* port: a restart must retire the old primary and pending components. */ + if (track->start_sound.index != NONE || + TEST_FLAG(track->flags, _fade_in_at_start_bit)) + { + sound_fade_looping_track_components( + looping_sound_index, track_index, NONE, + track->fade_out_duration); + } if (track->start_sound.index != NONE) { *playing_sound_index = @@ -2753,6 +2788,16 @@ boolean sound_refresh_looping( } else if (loop->state != _looping_sound_refresh_stop) { + /* port: stopping the loop also stops its other owned components. */ + if (fade_time != 0.f || + TEST_FLAG(track->flags, _fade_out_at_stop_bit) || + (track->stop_sound.index == NONE && + !TEST_FLAG(definition->flags, _looping_sound_fake_impulse_sound_bit))) + { + sound_fade_looping_track_components( + looping_sound_index, track_index, *playing_sound_index, + fade_time != 0.f ? fade_time : track->fade_out_duration); + } if (fade_time != 0.f) { sound_start_fade( @@ -2962,7 +3007,11 @@ static void update_channel_for_looping_sound( (!channel->playing_permutation || channel->playing_permutation->next_permutation_index == NONE)) { - sound_set_definition_end(channel->sound_index); + /* port: a definition transition can retire its own voice. */ + if (!sound_set_definition_end(channel->sound_index)) + { + return; + } definition = sound_definition_get(sound->definition_index); pitch_range = TAG_BLOCK_GET_ELEMENT( &definition->pitch_ranges, diff --git a/source/structures/leaf_map.c b/source/structures/leaf_map.c index 60b3b698..0e80dfdc 100644 --- a/source/structures/leaf_map.c +++ b/source/structures/leaf_map.c @@ -1311,16 +1311,6 @@ static void leaf_map_build_portal_from_leaves( short portal_designator_index0 = (short)tag_block_add_element(&leaf0->portal_designators); short portal_designator_index1 = (short)tag_block_add_element(&leaf1->portal_designators); - /* BUG (preserved for exact matching): January checks vertex_count - * against NONE only after entering the positive-count branch, where - * the assertion cannot fail. A corrected build should perform this - * check immediately after convex_hull2d_intersect returns. */ - match_vassert( - "c:\\halo\\SOURCE\\structures\\leaf_map.c", - 487, - vertex_count!=NONE, - "too many vertices in portal"); - if (portal_index != NONE && portal_designator_index0 != NONE && portal_designator_index1 != NONE) { struct leaf_portal *portal = TAG_BLOCK_GET_ELEMENT( diff --git a/source/structures/structure_render.c b/source/structures/structure_render.c index 0db163b8..8e9605b3 100644 --- a/source/structures/structure_render.c +++ b/source/structures/structure_render.c @@ -152,21 +152,21 @@ static boolean compare_surface_indices( long surface_index1, long surface_index2); static void structure_render_dynamic_triangles_from_bitvector( - short surface_count, + long surface_count, long *surface_indices, unsigned long *surface_flags, short *triangles); static void structure_render_dynamic_triangles_from_indices( - short surface_count, + long surface_count, long *surface_indices, short *triangles); static long structure_render_build_dynamic_triangles( long *surface_indices, - short surface_count, + long surface_count, unsigned long *surface_flags); static void structure_render_pass( long *surface_indices, - short surface_count, + long surface_count, long dynamic_triangles_index, void (*begin_lightmap)(struct bitmap_data const *lightmap_bitmap), void (*draw_triangles)( @@ -264,13 +264,13 @@ static boolean compare_surface_indices( } static void structure_render_dynamic_triangles_from_bitvector( - short surface_count, + long surface_count, long *surface_indices, unsigned long *surface_flags, short *triangles) { struct structure_bsp *structure_bsp = global_structure_bsp_get(); - short surface_index_index = 0; + long surface_index_index = 0; long surface_index = 0; if (structure_bsp->surfaces.count > 0) @@ -327,12 +327,12 @@ static void structure_render_dynamic_triangles_from_bitvector( } static void structure_render_dynamic_triangles_from_indices( - short surface_count, + long surface_count, long *surface_indices, short *triangles) { struct structure_bsp *structure_bsp = global_structure_bsp_get(); - short surface_index_index; + long surface_index_index; qsort_4byte(surface_indices, surface_count, compare_surface_indices); @@ -358,7 +358,7 @@ static void structure_render_dynamic_triangles_from_indices( static long structure_render_build_dynamic_triangles( long *surface_indices, - short surface_count, + long surface_count, unsigned long *surface_flags) { struct structure_bsp *structure_bsp = global_structure_bsp_get(); @@ -407,7 +407,7 @@ static long structure_render_build_dynamic_triangles( static void structure_render_pass( long *surface_indices, - short surface_count, + long surface_count, long dynamic_triangles_index, void (*begin_lightmap)(struct bitmap_data const *lightmap_bitmap), void (*draw_triangles)( @@ -489,7 +489,7 @@ static void structure_render_pass( struct structure_material *material; struct shader *shader; long *chunk_start; - short chunk_count; + long chunk_count; if (surface_index_cursor >= surface_index_end) { @@ -516,7 +516,7 @@ static void structure_render_pass( while (surface_index_cursor < surface_index_end && *surface_index_cursor < material->first_surface_index + material->surface_count); - chunk_count = (short)(surface_index_cursor - chunk_start); + chunk_count = surface_index_cursor - chunk_start; if (breakable_surface_extant(material->breakable_surface_index)) { diff --git a/source/structures/structure_visibility.c b/source/structures/structure_visibility.c index 2666e998..069c53bc 100644 --- a/source/structures/structure_visibility.c +++ b/source/structures/structure_visibility.c @@ -339,6 +339,7 @@ static boolean warned_about_portal_hull_vertices; static boolean warned_about_invalid_structure_bsp; static boolean warned_about_rendered_clusters; static boolean warned_about_structure_bsp_depth; +static boolean warned_about_environment_surfaces; static struct structure_visibility_globals structure_visibility_globals; static struct profile_section render_structure_visibility_portal_traversal = { "render_structure_visibility_portal_traversal", NONE, TRUE }; @@ -469,8 +470,10 @@ static void structure_visibility_traverse_subclusters( } surface_index_buffer = TAG_BLOCK_GET_ELEMENT(&subcluster->surface_indices, 0, long); + /* port: compared whole: a short index wraps past 32767 surfaces + and the walk never ends */ for (surface_list_index = 0; - (short)surface_list_index < subcluster->surface_indices.count; + surface_list_index < subcluster->surface_indices.count; surface_index_buffer++, surface_list_index++) { /* port: a surface index (from the map) that is no surface is @@ -1245,7 +1248,7 @@ static short structure_visibility_build_surfaces_traverse_clusters( long surface_list_index; for (surface_list_index = 0; - (short)surface_list_index < subcluster->surface_indices.count; + surface_list_index < subcluster->surface_indices.count; surface_index_buffer++, surface_list_index++) { /* port: (as in structure_visibility_traverse_subclusters) */ @@ -1999,5 +2002,14 @@ void structure_visibility_compute( structure_visibility_traverse_surface_lists(structure); } + /* port: a frame that reaches the structure triangles' limit is said once */ + if (render.environment_surface_count >= MAXIMUM_RENDERED_ENVIRONMENT_SURFACES && + !warned_about_environment_surfaces) + { + error(_error_silent, "a frame stopped at %d structure triangles", + MAXIMUM_RENDERED_ENVIRONMENT_SURFACES); + warned_about_environment_surfaces = TRUE; + } + return; } diff --git a/source/tag_files/files.c b/source/tag_files/files.c index 3d8e99d7..b6280dfc 100644 --- a/source/tag_files/files.c +++ b/source/tag_files/files.c @@ -298,7 +298,7 @@ boolean datastore_read( void *data) { struct file_reference file_ref; - boolean success; + boolean success = FALSE; unsigned long datastore_size = 0; struct datastore *datastore; @@ -356,13 +356,6 @@ boolean datastore_read( } } - /* BUG (preserved for exact matching): success is never initialised, so a missing - * file, a wrong-sized datastore or an absent field returns it unassigned (January - * 0x589370 keeps it in the high byte of the field_name argument slot, [ebp+0xf], - * written only at +0x1f5 and read at +0x19b and +0x20c). Not reached in January: - * cachebeta.exe has no call, jump or address reference to this function. A corrected - * build should initialise success to FALSE. Source-policy approval pending - * (2026-09-27 audit). */ return success; } diff --git a/source/tag_files/tag_groups.c b/source/tag_files/tag_groups.c index 7730cef8..7acc2f47 100644 --- a/source/tag_files/tag_groups.c +++ b/source/tag_files/tag_groups.c @@ -122,12 +122,19 @@ void *tag_block_get_element_with_size( match_assert("c:\\halo\\SOURCE\\tag_files\\tag_groups.c", 3085, block->count>=0); match_assert("c:\\halo\\SOURCE\\tag_files\\tag_groups.c", 3086, !block->definition || block->definition->element_size==element_size); - match_vassert("c:\\halo\\SOURCE\\tag_files\\tag_groups.c", 3089, index>=0 && indexcount, - csprintf(temporary, - "#%d is not a valid %s index in [#0,#%d)", - index, - block->definition ? block->definition->name : "", block->count)); - match_assert("c:\\halo\\SOURCE\\tag_files\\tag_groups.c", 3090, block->address); + /* port: as tag_data_get_pointer, a Custom Edition map's index past a + block's end (which Halo PC never checked: foundation@ce, 13 seconds in) + gets the empty data below without an assertion. This build's maps keep + theirs */ + if (!custom_edition_cache_tags_loaded()) + { + match_vassert("c:\\halo\\SOURCE\\tag_files\\tag_groups.c", 3089, index>=0 && indexcount, + csprintf(temporary, + "#%d is not a valid %s index in [#0,#%d)", + index, + block->definition ? block->definition->name : "", block->count)); + match_assert("c:\\halo\\SOURCE\\tag_files\\tag_groups.c", 3090, block->address); + } /* port: an element past the block (an index a map's data gave, which nothing checked) is the empty data (tag_empty_data), not whatever lies past the block */ diff --git a/source/text/draw_string.c b/source/text/draw_string.c index 4b9196b5..8f0a2f95 100644 --- a/source/text/draw_string.c +++ b/source/text/draw_string.c @@ -1459,6 +1459,51 @@ void draw_unicode_string_compute_bounds( return; } +/* port: the middle of a string's capitals as it is drawn in the bounds +(the draw mode's font: the rows of its H with ink, on the line's baseline), +for what is drawn beside it, centred on it (voice chat's speakers: +port/linux/game/network_voice.c) */ +short draw_unicode_string_capital_middle( + rectangle2d const *bounds, + wchar_t const *string) +{ + rectangle2d text_bounds; + rectangle2d cursor_bounds; + struct font_header *font; + struct font_character *capital; + short baseline; + short top = NONE; + short bottom = NONE; + short row; + + draw_unicode_string_compute_bounds(bounds, string, &text_bounds, &cursor_bounds); + font = text_bounds_globals.last_font_header; + if (!font) + return (short)((bounds->y0 + bounds->y1) / 2); + baseline = (short)(cursor_bounds.y0 + font->ascending_height); + capital = font_get_character_by_ascii_code(font, 'H'); + for (row = 0; capital && row < capital->bitmap_height; row++) + { + byte const *pixels = (byte const *)font->pixels.address + capital->pixels_offset + row * capital->bitmap_width; + short column; + + for (column = 0; column < capital->bitmap_width; column++) + { + if (pixels[column]) + { + if (top == NONE) + top = row; + bottom = row; + break; + } + } + } + /* (no H: the middle of the font's height above the baseline) */ + if (top == NONE) + return (short)(baseline - font->ascending_height / 2); + return (short)(baseline - capital->bitmap_origin_y + (top + bottom + 1) / 2); +} + short draw_string_pick( rectangle2d const *bounds, char const *string, diff --git a/source/text/draw_string.h b/source/text/draw_string.h index d33e1d7a..ea38ef1c 100644 --- a/source/text/draw_string.h +++ b/source/text/draw_string.h @@ -95,6 +95,11 @@ void draw_unicode_string_compute_bounds( wchar_t const *string, rectangle2d *text_bounds, rectangle2d *cursor_bounds); +/* port: the middle of the string's capitals as it is drawn in the bounds, +in the draw mode's font (draw_string_set_draw_mode) */ +short draw_unicode_string_capital_middle( + rectangle2d const *bounds, + wchar_t const *string); short draw_string_pick( rectangle2d const *bounds, char const *string, diff --git a/source/text/text_group.c b/source/text/text_group.c index 9b4ba50c..ecb05b5a 100644 --- a/source/text/text_group.c +++ b/source/text/text_group.c @@ -271,7 +271,9 @@ wchar_t *unicode_string_list_get_string(long tag_index, short string_index) string_index, struct string_list_entry); - if (entry->string.size > 0) + /* port: only a string of at least one character, terminated + within it (a size of 1 wrote the terminator before its data) */ + if (entry->string.size >= (long)sizeof(wchar_t)) { result = entry->string.address; result[entry->string.size / sizeof(wchar_t) - 1] = L'\0'; diff --git a/source/text/unicode.c b/source/text/unicode.c index 8c616769..a8ae5b7a 100644 --- a/source/text/unicode.c +++ b/source/text/unicode.c @@ -247,6 +247,7 @@ symbols in this file: #include #include "cseries/cseries.h" +#include "cseries/errors.h" /* port: ustring_format_checked */ #include "unicode.h" /* ---------- constants */ @@ -268,11 +269,11 @@ enum /* ---------- globals */ -#ifndef HALO_ANDROID /* Mach-O section names differ; the default is .bss anyway */ +#ifndef HALO_ARM64_GUEST /* Mach-O section names differ; the default is .bss anyway */ #pragma bss_seg(".bss") #endif static wchar_t bss_004c1a08[0x100]; -#ifndef HALO_ANDROID +#ifndef HALO_ARM64_GUEST #pragma bss_seg() #endif @@ -1341,6 +1342,107 @@ int usnprintf( return result; } +/* port: whether a format (a map's text: its string lists' are the game's +formats) takes no other arguments than conversions names, in order, if +fewer: 'd' an integer (%d, %i, %u, %x, %X, %o, %c), 'f' a real (%f, %e, +%g), 's' a string (%s, %S). %% and the flags, widths and precisions are +allowed; '*' (which takes an argument of its own) and %n are not. A format +that takes others would have the game read an argument as what it is not */ +int ustring_format_takes( + wchar_t const *format, + char const *conversions) +{ + if (!format || !conversions) + return FALSE; + while (*format) + { + char kind; + short digits; + + if (*format++ != L'%') + continue; + if (*format == L'%') + { + format++; + continue; + } + while (*format == L'-' || *format == L'+' || *format == L' ' || *format == L'#' || *format == L'0') + format++; + /* (a width, then a precision, of a few digits: the game's are, and + Windows's printf refuses what is not a conversion) */ + for (digits = 0; *format >= L'0' && *format <= L'9'; digits++) + format++; + if (digits > 3) + return FALSE; + if (*format == L'.') + { + format++; + for (digits = 0; *format >= L'0' && *format <= L'9'; digits++) + format++; + if (digits > 3) + return FALSE; + } + /* (the size prefixes the game's own formats use) */ + if (*format == L'h' || *format == L'l' || *format == L'w') + format++; + switch (*format) + { + case L'd': case L'i': case L'u': case L'x': case L'X': case L'o': case L'c': + kind = 'd'; + break; + case L'f': case L'e': case L'E': case L'g': case L'G': + kind = 'f'; + break; + case L's': case L'S': + kind = 's'; + break; + default: + return FALSE; + } + format++; + if (*conversions++ != kind) + return FALSE; + } + + return TRUE; +} + +/* port: a map's format, if it takes the arguments a caller gives it +(ustring_format_takes); else an empty one, whose message is then empty, and +which is logged once */ +wchar_t const *ustring_format_checked( + wchar_t const *format, + char const *conversions) +{ + static boolean logged = FALSE; + + if (ustring_format_takes(format, conversions)) + return format; + if (!logged) + { + logged = TRUE; + error(_error_silent, "a map's text is a format of other arguments than the game gives it (%s): it is not shown", + conversions); + } + + return L""; +} + +/* port: at most size - 1 characters of src, always terminated */ +wchar_t *ustrncpy_terminated( + wchar_t *dest, + wchar_t const *src, + unsigned long size) +{ + if (size > 0) + { + wcsncpy(dest, src ? src : L"", size - 1); + dest[size - 1] = 0; + } + + return dest; +} + int usprintf( wchar_t *string, wchar_t const *format, diff --git a/source/text/unicode.h b/source/text/unicode.h index 2f3b88eb..1c5d9031 100644 --- a/source/text/unicode.h +++ b/source/text/unicode.h @@ -223,6 +223,17 @@ int usprintf( wchar_t *string, wchar_t const *format, ...); +/* port: a map's text used as a format (its string lists' are) */ +int ustring_format_takes( + wchar_t const *format, + char const *conversions); +wchar_t const *ustring_format_checked( + wchar_t const *format, + char const *conversions); +wchar_t *ustrncpy_terminated( + wchar_t *dest, + wchar_t const *src, + unsigned long size); int uvsnprintf( wchar_t *string, unsigned long size, diff --git a/source/tool/connected_geometry.c b/source/tool/connected_geometry.c index e89bac11..1d19c83c 100644 --- a/source/tool/connected_geometry.c +++ b/source/tool/connected_geometry.c @@ -276,19 +276,7 @@ static long connected_geometry_find_or_add_edge( long point_index0, long point_index1) { - /* BUG (preserved for exact matching): January declares `direction` without an - * initializer. It is written only at the two matches (+0x47, +0x4d) and on edge - * creation (+0x67), and read once, at +0xbe. The only path that reaches that read - * without a write requires geometry->edges.count < 0 (a deleted array, whose count is - * NONE, is one way this can arise). On that path edge_index stays 0, so the - * dynamic_array_get_element call (+0x9b) runs first: its unconditional - * `array->count>=0` assertion fails, and match_assert's failure path (display_assert, - * then system_exit -> halt_and_catch_fire, which never returns: it loops on the error - * screen, or calls exit on re-entry) ends this build before `direction` is read. - * The later /Od+/RTC build attests the declaration with _RTC_UninitUse("direction"). - * A corrected build should initialize direction to TRUE. - */ - boolean direction; + boolean direction = TRUE; long edge_index; for (edge_index = 0; edge_index < geometry->edges.count; edge_index++) diff --git a/source/units/bipeds.c b/source/units/bipeds.c index b6476acb..26984245 100644 --- a/source/units/bipeds.c +++ b/source/units/bipeds.c @@ -522,6 +522,8 @@ static void biped_update_jumping( struct unit_animation_update_data *animation); static void biped_update_physics( struct biped_physics *physics); +/* units.c's: whether the unit gets the animation enhancements */ +boolean unit_animation_enhanced(long unit_index); static void biped_snap_facing( long biped_index); @@ -3340,6 +3342,14 @@ static void biped_update_turning( { boolean flaming = biped->unit.animation.base_seat_index==_unit_base_seat_flaming; + /* port: a player throwing a grenade turns with the aim, as one + meleeing does: the melee plays over the moving or idle state, the + throw is a state of its own, which the original never turned in + (game.enhanced_animations: unit_animation_enhanced) */ + boolean throwing_turn = + biped->unit.player_index!=NONE && + biped->unit.animation.state==_unit_state_throw_grenade && + unit_animation_enhanced(biped_index); real_vector3d turn_axis; real turn_error; real facing_alignment; @@ -3403,7 +3413,8 @@ static void biped_update_turning( } if (biped->biped.state==biped_state_moving || - TEST_FLAG(flags, _biped_turns_without_animating_bit)) + TEST_FLAG(flags, _biped_turns_without_animating_bit) || + throwing_turn) { if (!TEST_FLAG(biped->unit.control_flags, _unit_control_look_dont_turn_bit)) { diff --git a/source/units/unit_dialogue.c b/source/units/unit_dialogue.c index 429df104..a67a69a0 100644 --- a/source/units/unit_dialogue.c +++ b/source/units/unit_dialogue.c @@ -897,7 +897,7 @@ boolean unit_scream( short scream_type) { struct unit_datum *unit = unit_get(unit_index); - short vocalization_type; + short vocalization_type = 0; match_assert( "c:\\halo\\SOURCE\\units\\unit_dialogue.c", @@ -927,10 +927,6 @@ boolean unit_scream( case _unit_scream_resurrection: vocalization_type = _vocalization_resurrect; break; - /* vocalization_type is left unassigned only by this default arm. Not reached unassigned: the - * arm's assertion failure calls system_exit, which does not return in January - * (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit). - * Source-policy approval pending (2026-09-27 audit). */ default: match_assert( "c:\\halo\\SOURCE\\units\\unit_dialogue.c", @@ -998,12 +994,8 @@ void unit_dialogue_update( if (unit->unit.speech.damage_minor_timer > 0) unit->unit.speech.damage_minor_timer--; - /* BUG (preserved for exact matching): January decrements damage_minor_timer - * (+0x39C) a second time here and never touches damage_major_timer (+0x39E, - * the field unit_make_damage_sound tests against zero and sets to 60), so a - * unit's major pain vocalization is suppressed for good once one has played. - * A corrected build should decrement damage_major_timer in this statement. - */ + /* (as the original game, kept: the minor pain timer counts down twice and the major one + not at all, so a unit cries out in major pain once) */ if (unit->unit.speech.damage_minor_timer > 0) unit->unit.speech.damage_minor_timer--; @@ -1142,7 +1134,10 @@ static long unit_find_dialogue_variant( variant_index, struct unit_dialogue_variant); - if (variant_number == NONE || variant->variant_number == variant_number) + /* port: a variant with no dialogue is skipped (a Custom Edition map's + unit can have one, which asserted when picked) */ + if ((variant_number == NONE || variant->variant_number == variant_number) && + variant->dialogue_index != NONE) { /* port: no more than the array holds (a map's count; released maps have at most 6 variants) */ diff --git a/source/units/units.c b/source/units/units.c index 9e52791a..e3567d07 100644 --- a/source/units/units.c +++ b/source/units/units.c @@ -676,6 +676,7 @@ symbols in this file: #include "main/console.h" #include "models/model_animation_definitions.h" #include "models/model_definitions.h" +#include "models/models.h" /* port: model_find_node (the grenade throw's legs) */ #include "objects/damage.h" #include "objects/damage_effect_definitions.h" #include "objects/object_lights.h" @@ -3337,6 +3338,97 @@ void unit_impulse( return; } +/* the platform layer's (port/linux/src/port_config.c) */ +int config_boolean(const char *name); +unsigned long config_changes(void); + +/* port: the animation enhancements (game.enhanced_animations, on unless +config.toml turns them off) are for the player bipeds (cyborg and +cyborg_mp), whose animations they were made with; every other unit keeps +the original animations */ +static char const *const enhanced_animation_units[] = +{ + "cyborg", + "cyborg_mp", +}; + +boolean unit_animation_enhanced( + long unit_index) +{ + static boolean enhanced = TRUE; + static unsigned long read_at = (unsigned long)-1; + struct unit_datum *unit; + char const *name; + long name_index; + + /* (read again when the settings change) */ + if (read_at != config_changes()) + { + read_at = config_changes(); + enhanced = config_boolean("game.enhanced_animations") != 0; + } + if (!enhanced) + { + return FALSE; + } + unit = unit_get(unit_index); + name = tag_name_strip_path(tag_get_name(unit->definition_index)); + for (name_index = 0; name_index < NUMBEROF(enhanced_animation_units); name_index++) + { + if (!_stricmp(name, enhanced_animation_units[name_index])) + { + return TRUE; + } + } + + return FALSE; +} + +/* port: a seat whose graph has no reload of its own (a Scorpion rider's) +reloads with the standing seat's, the same weapon's: a reload is a +replacement animation, which moves only the arms and the weapon */ +static short unit_standing_weapon_type_animation( + struct animation_graph *animation_graph, + struct animation_graph_weapon_class *weapon_class, + struct animation_graph_weapon_type *weapon_type, + short weapon_type_animation_index) +{ + short seat_index; + + for (seat_index = 0; seat_index < animation_graph->unit_seats.count; seat_index++) + { + struct animation_graph_unit_seat *seat = TAG_BLOCK_GET_ELEMENT(&animation_graph->unit_seats, seat_index, struct animation_graph_unit_seat); + short class_index; + + if (_stricmp(seat->label, "stand")) + { + continue; + } + for (class_index = 0; class_index < seat->weapon_classes.count; class_index++) + { + struct animation_graph_weapon_class *stand_class = TAG_BLOCK_GET_ELEMENT(&seat->weapon_classes, class_index, struct animation_graph_weapon_class); + short type_index; + + if (_stricmp(stand_class->label, weapon_class->label)) + { + continue; + } + for (type_index = 0; type_index < stand_class->weapon_types.count; type_index++) + { + struct animation_graph_weapon_type *stand_type = TAG_BLOCK_GET_ELEMENT(&stand_class->weapon_types, type_index, struct animation_graph_weapon_type); + + if (!_stricmp(stand_type->label, weapon_type->label) && + weapon_type_animation_index < stand_type->animations.count) + { + return animation_graph_animation_index_get(&stand_type->animations)[weapon_type_animation_index].animation_index; + } + } + } + } + + return NONE; +} + void unit_animation_start_action( long unit_index, short action) @@ -3415,7 +3507,17 @@ void unit_animation_start_action( animation_index = NONE; } } - + /* port: a seat with no reload borrows the standing one's */ + if (animation_index == NONE && + (weapon_type_animation_index == 0 || weapon_type_animation_index == 1) && + unit_animation_enhanced(unit_index)) + { + animation_index = unit_standing_weapon_type_animation( + animation_graph, + weapon_class, + weapon_type, + weapon_type_animation_index); + } interpolation_frame_count = action_index==7 ? 0 : 6; if (animation_index!=NONE) @@ -10798,6 +10900,144 @@ done: return result; } +/* port: a unit throwing a grenade on the move keeps its lower body (the +pelvis and the legs) in the movement animation; the throw keeps the spine +and everything above it. The throw is a base animation, which sets every +node, so the legs of the original game stop while the unit slides. */ +static boolean unit_node_is_at_or_below( + struct model *model, + short node_index, + short ancestor_node_index) +{ + while (node_index != NONE) + { + if (node_index == ancestor_node_index) + { + return TRUE; + } + node_index = TAG_BLOCK_GET_ELEMENT(&model->nodes, node_index, struct model_node)->parent_node_index; + } + + return FALSE; +} + +/* the speed, in world units per second, at and above which the legs move +fully; below it they blend toward the rest pose */ +#define GRENADE_THROW_FULL_MOVE_SPEED 0.75f +/* the ticks the legs take to blend fully in or out, as the engine's own +transitions between animation states */ +#define GRENADE_THROW_BLEND_TICKS 6 +#define GRENADE_THROW_MAXIMUM_TRACKED_UNITS 2048 + +static real grenade_throw_leg_weights[GRENADE_THROW_MAXIMUM_TRACKED_UNITS]; +static long grenade_throw_leg_weight_times[GRENADE_THROW_MAXIMUM_TRACKED_UNITS]; +static real grenade_throw_air_weights[GRENADE_THROW_MAXIMUM_TRACKED_UNITS]; +static long grenade_throw_air_weight_times[GRENADE_THROW_MAXIMUM_TRACKED_UNITS]; +static long grenade_throw_ik_release_times[GRENADE_THROW_MAXIMUM_TRACKED_UNITS]; + +/* a weight eased toward its target by a step a tick; a new throw starts +at the target */ +static real unit_grenade_throw_ease( + real *weights, + long *times, + long unit_index, + real target) +{ + long absolute_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(unit_index); + long now = game_time_get(); + real weight = target; + + if (absolute_index >= 0 && absolute_index < GRENADE_THROW_MAXIMUM_TRACKED_UNITS) + { + if (times[absolute_index] == now) + { + weight = weights[absolute_index]; + } + else + { + if (times[absolute_index] == now - 1) + { + real step = 1.f / GRENADE_THROW_BLEND_TICKS; + + weight = weights[absolute_index] + PIN(target - weights[absolute_index], -step, step); + } + weights[absolute_index] = weight; + times[absolute_index] = now; + } + } + + return weight; +} + +/* port: a rider's hands leave the vehicle's grips while throwing a grenade +or playing an action (a reload, readying or putting away a weapon, a +melee), and through the interpolation back to the seat after it, which the +grips would otherwise snap */ +static boolean unit_grenade_throw_releases_vehicle_ik( + long unit_index) +{ + struct unit_datum *unit = unit_get(unit_index); + long absolute_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(unit_index); + long now = game_time_get(); + long elapsed; + boolean releasing = + unit->unit.animation.state == _unit_state_throw_grenade || + unit->unit.animation.action != 0; + + if (!unit_animation_enhanced(unit_index)) + { + return FALSE; + } + if (absolute_index < 0 || absolute_index >= GRENADE_THROW_MAXIMUM_TRACKED_UNITS) + { + return releasing; + } + if (releasing) + { + grenade_throw_ik_release_times[absolute_index] = now + 1; + return TRUE; + } + if (grenade_throw_ik_release_times[absolute_index] == 0) + { + return FALSE; + } + elapsed = now + 1 - grenade_throw_ik_release_times[absolute_index]; + + return elapsed >= 0 && elapsed <= GRENADE_THROW_BLEND_TICKS; +} + +/* one of the weapon class's base animations, NULL where the graph has none +that fits the model */ +static struct animation *unit_weapon_class_base_animation( + struct model *model, + struct animation_graph *animation_graph, + struct animation_graph_weapon_class *weapon_class, + short slot) +{ + short animation_index; + struct animation *animation; + + if (slot < 0 || slot >= weapon_class->animations.count) + { + return NULL; + } + animation_index = animation_graph_animation_index_get(&weapon_class->animations)[slot].animation_index; + if (animation_index == NONE) + { + return NULL; + } + + animation = TAG_BLOCK_GET_ELEMENT(&animation_graph->animations, animation_index, struct animation); + if (animation->type != _animation_base || + animation->frame_count < 1 || + animation->node_count != model->nodes.count) + { + return NULL; + } + + return animation; +} + /* port: the nodes a unit's orientations hold: its model's, no more than a model can have (object_new sizes them from the model) */ short unit_animation_model_node_count( @@ -10851,6 +11091,263 @@ struct animation *unit_animation_get_fitting( return animation; } +/* the animation's pose at a point (0 to 1) through its cycle */ +static void unit_animation_pose_at_phase( + struct model *model, + struct animation *animation, + real phase, + struct real_orientation *orientations) +{ + long frame_index = (long)(phase * animation->frame_count); + + frame_index = PIN(frame_index, 0, animation->frame_count - 1); + animation_get_node_orientations(model, animation, (short)frame_index, orientations); + + return; +} + +static void unit_node_orientations_blend( + short node_count, + struct real_orientation const *from, + struct real_orientation const *to, + real fraction, + struct real_orientation *result) +{ + short node_index; + + for (node_index = 0; node_index < node_count; node_index++) + { + quaternions_interpolate_and_normalize( + &from[node_index].rotation, + &to[node_index].rotation, + fraction, + &result[node_index].rotation); + points_interpolate( + &from[node_index].translation, + &to[node_index].translation, + fraction, + &result[node_index].translation); + scalars_interpolate( + from[node_index].scale, + to[node_index].scale, + fraction, + &result[node_index].scale); + } + + return; +} + +static void unit_grenade_throw_keep_legs_moving( + long unit_index, + struct real_orientation *node_orientations) +{ + struct unit_datum *unit = unit_get(unit_index); + struct unit_definition *unit_definition = unit_definition_get(unit->definition_index); + struct animation_graph *animation_graph; + struct animation_graph_unit_seat *unit_seat; + struct animation_graph_weapon_class *weapon_class; + struct model *model; + struct animation *idle_animation; + struct animation *air_animation; + struct animation *front_animation; + struct animation *forward_animation; + struct animation *side_animation; + struct real_orientation rest_orientations[MAXIMUM_NODES_PER_MODEL]; + struct real_orientation forward_orientations[MAXIMUM_NODES_PER_MODEL]; + struct real_orientation side_orientations[MAXIMUM_NODES_PER_MODEL]; + struct real_orientation move_orientations[MAXIMUM_NODES_PER_MODEL]; + struct real_orientation legs_orientations[MAXIMUM_NODES_PER_MODEL]; + struct real_orientation air_orientations[MAXIMUM_NODES_PER_MODEL]; + struct real_orientation *legs; + real_vector3d left; + real forward_speed; + real left_speed; + real target_weight; + real move_weight; + real air_weight; + real side_weight; + real phase; + long now; + boolean airborne; + boolean crouching; + boolean moving; + short spine; + short node_index; + + if (unit->unit.animation.state != _unit_state_throw_grenade || + unit->unit.animation.seat_index == NONE || + !unit_animation_enhanced(unit_index)) + { + return; + } + + animation_graph = animation_graph_definition_get(unit_definition->object.animation_graph.index); + unit_seat = TAG_BLOCK_GET_ELEMENT(&animation_graph->unit_seats, unit->unit.animation.seat_index, struct animation_graph_unit_seat); + weapon_class = TAG_BLOCK_GET_ELEMENT(&unit_seat->weapon_classes, unit->unit.animation.weapon_index, struct animation_graph_weapon_class); + model = model_definition_get(unit_definition->object.model.index); + now = game_time_get(); + if (model->nodes.count > MAXIMUM_NODES_PER_MODEL) + { + return; + } + + /* the throw keeps the spine and everything above it */ + spine = model_find_node(unit_definition->object.model.index, "bip01 spine"); + if (spine == NONE) + { + return; + } + + /* port: a rider throwing from a vehicle's seat stays seated: the lower + body keeps the seat's own idle pose, as the throw is the standing one */ + if (unit->object.parent_object_index != NONE) + { + struct animation *seated_animation = unit_weapon_class_base_animation( + model, + animation_graph, + weapon_class, + _unit_weapon_class_animation_idle); + + if (seated_animation) + { + unit_animation_pose_at_phase( + model, + seated_animation, + (real)(now % seated_animation->frame_count) / (real)seated_animation->frame_count, + rest_orientations); + for (node_index = 0; node_index < model->nodes.count; node_index++) + { + if (!unit_node_is_at_or_below(model, node_index, spine)) + { + node_orientations[node_index] = rest_orientations[node_index]; + } + } + } + + return; + } + + /* in the air the legs float as in a jump, and do not run; both ease in + and out over a few ticks */ + airborne = unit_flying_through_air(unit_index); + air_weight = unit_grenade_throw_ease( + grenade_throw_air_weights, + grenade_throw_air_weight_times, + unit_index, + airborne ? 1.f : 0.f); + + /* the unit's real speed along its facing and to its left, per second */ + cross_product3d(&unit->object.up, &unit->object.forward, &left); + forward_speed = dot_product3d(&unit->object.translational_velocity, &unit->object.forward) * TICKS_PER_SECOND; + left_speed = dot_product3d(&unit->object.translational_velocity, &left) * TICKS_PER_SECOND; + target_weight = square_root(forward_speed * forward_speed + left_speed * left_speed) / GRENADE_THROW_FULL_MOVE_SPEED; + target_weight = airborne ? 0.f : PIN(target_weight, 0.f, 1.f); + move_weight = unit_grenade_throw_ease( + grenade_throw_leg_weights, + grenade_throw_leg_weight_times, + unit_index, + target_weight); + + /* the rest pose: the crouching idle when crouched, else the throw's own legs */ + crouching = unit->unit.animation.base_seat_index == _unit_base_seat_crouch; + idle_animation = crouching ? + unit_weapon_class_base_animation(model, animation_graph, weapon_class, _unit_weapon_class_animation_idle) : + NULL; + if (!idle_animation && move_weight <= 0.f && air_weight <= 0.f) + { + return; + } + if (idle_animation) + { + unit_animation_pose_at_phase( + model, + idle_animation, + (real)(now % idle_animation->frame_count) / (real)idle_animation->frame_count, + rest_orientations); + } + else + { + for (node_index = 0; node_index < model->nodes.count; node_index++) + { + rest_orientations[node_index] = node_orientations[node_index]; + } + } + + /* the movement pose: the forward or back walk blended with the left or + right walk by how much of the movement is sideways, all at one point in + the walk cycle so the feet stay in step */ + front_animation = unit_weapon_class_base_animation(model, animation_graph, weapon_class, _unit_weapon_class_animation_moving_front); + phase = front_animation ? + (real)(now % front_animation->frame_count) / (real)front_animation->frame_count : + 0.f; + forward_animation = unit_weapon_class_base_animation( + model, + animation_graph, + weapon_class, + forward_speed >= 0.f ? _unit_weapon_class_animation_moving_front : _unit_weapon_class_animation_moving_back); + side_animation = unit_weapon_class_base_animation( + model, + animation_graph, + weapon_class, + left_speed > 0.f ? _unit_weapon_class_animation_moving_left : _unit_weapon_class_animation_moving_right); + side_weight = (real)(fabs(forward_speed) + fabs(left_speed)); + side_weight = side_weight > 0.f ? (real)fabs(left_speed) / side_weight : 0.f; + + moving = FALSE; + if (move_weight > 0.f) + { + if (forward_animation && side_animation) + { + unit_animation_pose_at_phase(model, forward_animation, phase, forward_orientations); + unit_animation_pose_at_phase(model, side_animation, phase, side_orientations); + unit_node_orientations_blend(model->nodes.count, forward_orientations, side_orientations, side_weight, move_orientations); + moving = TRUE; + } + else if (forward_animation) + { + unit_animation_pose_at_phase(model, forward_animation, phase, move_orientations); + moving = TRUE; + } + else if (side_animation) + { + unit_animation_pose_at_phase(model, side_animation, phase, move_orientations); + moving = TRUE; + } + } + + legs = rest_orientations; + if (moving) + { + unit_node_orientations_blend(model->nodes.count, rest_orientations, move_orientations, move_weight, legs_orientations); + legs = legs_orientations; + } + + /* the floating legs of a jump over all of it */ + air_animation = air_weight > 0.f ? + unit_weapon_class_base_animation(model, animation_graph, weapon_class, _unit_weapon_class_animation_airborne) : + NULL; + if (air_animation) + { + unit_animation_pose_at_phase( + model, + air_animation, + (real)(now % air_animation->frame_count) / (real)air_animation->frame_count, + air_orientations); + unit_node_orientations_blend(model->nodes.count, legs, air_orientations, air_weight, forward_orientations); + legs = forward_orientations; + } + + for (node_index = 0; node_index < model->nodes.count; node_index++) + { + if (!unit_node_is_at_or_below(model, node_index, spine)) + { + node_orientations[node_index] = legs[node_index]; + } + } + + return; +} + void unit_preprocess_node_orientations( long unit_index, struct real_orientation *node_orientations) @@ -10870,6 +11367,7 @@ void unit_preprocess_node_orientations( /* port: every animation below is one the graph has and that fits the model (unit_animation_get_fitting) */ model_node_count = unit_animation_model_node_count(unit_index); + unit_grenade_throw_keep_legs_moving(unit_index, node_orientations); if (unit->unit.animation.action_animation.index != NONE && (animation = unit_animation_get_fitting(animation_graph, @@ -11191,7 +11689,8 @@ void unit_postprocess_node_matrices( struct animation_graph_weapon_class); if (unit->object.parent_object_index!=NONE && - unit_animation_vehicle_ik(&unit->unit.animation)) + unit_animation_vehicle_ik(&unit->unit.animation) && + !unit_grenade_throw_releases_vehicle_ik(unit_index)) { ik_points = &unit_seat->ik_points; ik_point_index = 0; diff --git a/tools/android_build.py b/tools/android_build.py index f4248f6d..e3d19294 100644 --- a/tools/android_build.py +++ b/tools/android_build.py @@ -26,8 +26,8 @@ from pathlib import Path from typing import Any, Dict, List, Optional from .linux_build import (LINUX_PROFILE, MINIUPNPC_DEFINES, MINIUPNPC_DIR, MUSL_MATH_DIR, XDK_INCLUDE, - compile_launcher, game_defines_and_includes, game_sources, miniupnpc_sources, - musl_math_sources, pgo_mode, pgo_profile, + compile_launcher, configuration_defines, game_defines_and_includes, game_sources, + miniupnpc_sources, musl_math_sources, opus_cflags, opus_sources, pgo_mode, pgo_profile, profile_use_flags, updater_defines, xdk_headers) from .embed_assets import hud_assets_build, hud_configure_inputs from .ninja_syntax import Writer @@ -53,6 +53,7 @@ ZLIB_DEFINES = ("-DZ_PREFIX", "-Dz_errmsg=z_port_errmsg") MUSL_VERSION = "1.2.5" MUSL_DIR = THIRD_PARTY / f"musl-{MUSL_VERSION}" MUSL_URL = f"https://musl.libc.org/releases/musl-{MUSL_VERSION}.tar.gz" +# (as tools/windows_build.py's and tools/linux_sysroot.py's SDL_VERSION) SDL_TAG = "release-3.4.16" SDL_DIR = THIRD_PARTY / "SDL3" SDL_URL = "https://github.com/libsdl-org/SDL.git" @@ -78,6 +79,10 @@ GUEST_ABI_FLAGS = [ "-fno-define-target-os-macros", "-D__linux__=1", "-D__unix__=1", + # the ILP32 guest's code paths, the OpenGL ES renderer's, and the app's + # (in the port's sources, each its own macro) + "-DHALO_ARM64_GUEST=1", + "-DHALO_GLES=1", "-DHALO_ANDROID=1", # ARMv8.0: nothing the emulator's binary translation or an older # device could lack (Darwin targets otherwise assume pointer @@ -445,7 +450,7 @@ def generate_android_build(n: Writer, sln: Any) -> None: f"-isystem {libc_include}", f"-isystem {arch}", f"-isystem {MUSL_DIR}/arch/generic", f"-isystem {MUSL_DIR}/include", ] - guest_abi = " ".join(GUEST_ABI_FLAGS + (["-DHALO_RELEASE"] if getattr(sln, "port_release", False) else [])) + guest_abi = " ".join(GUEST_ABI_FLAGS + configuration_defines(sln)) guest_code = " ".join(GUEST_CODE_FLAGS) tool_implicit = [Path("tools/android_asm_convert.py"), *generated_headers] # profile-guided optimisation with the Linux build's profile (committed, @@ -536,6 +541,10 @@ def generate_android_build(n: Writer, sln: Any) -> None: objects.append(guest_object(EXPAT_DIR / name, platform_cflags)) # internet play's reliable streams (port/third_party/kcp; p2p.c) objects.append(guest_object(KCP_DIR / "ikcp.c", platform_cflags)) + # voice chat's codec (port/third_party/opus), with the guest's ABI and C + # library + for source in opus_sources(): + objects.append(guest_object(source, " ".join([opus_cflags(guest_abi), *libc_includes]))) # internet play's signatures, for public games' listings # (port/third_party/monocypher; p2p_crypto.c) for name in ("monocypher.c", "monocypher-ed25519.c"): diff --git a/tools/app_icon.py b/tools/app_icon.py new file mode 100644 index 00000000..a08917f7 --- /dev/null +++ b/tools/app_icon.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Makes the game's icons from its artwork, port/assets/icon: + + python tools/app_icon.py + +- opence-icon.png, the helmet on its background (512x512): the Windows + executable's icon (port/windows/opence-icon.ico, which port/windows/halo.rc + puts in halo.exe) and the desktop builds' window icon (opence-icon-256.png, + which tools/embed_assets.py embeds and sdl_platform.c gives the window); +- opence-icon-render.png, the helmet alone on transparency (1024x1024, the same + framing): the Android app's launcher icon. + +The launcher icon is an adaptive one (Android 8 and later): the render as its +foreground layer, the background's gradient as its background layer, and the +render's light parts (the armour, not the visor) as its monochrome (themed +icon) layer. Launchers mask adaptive icons to their own shapes and show only +the middle 72 dp of the 108 dp layers, so the render fills those 72 dp, as +the picture fills the other icons, and its edges are carried out to the +layers' edges for the launchers that move the layers. Writes the layers for +every screen density into port/android/app/src/main/res (mipmap-*), and the +icon's definition (mipmap-anydpi-v26/ic_launcher.xml) and background +(drawable/ic_launcher_background.xml). Run it again when the artwork +changes. Needs Pillow and NumPy. +""" + +from pathlib import Path + +import numpy as np +from PIL import Image + +ROOT = Path(__file__).resolve().parent.parent +ARTWORK = ROOT / "port/assets/icon" +ICON = ARTWORK / "opence-icon.png" +RENDER = ARTWORK / "opence-icon-render.png" +WINDOW_ICON = ARTWORK / "opence-icon-256.png" +WINDOWS_ICON = ROOT / "port/windows/opence-icon.ico" +RESOURCES = ROOT / "port/android/app/src/main/res" + +# the layers' size, and the part of them a launcher shows, in dp +LAYER_DP = 108 +VISIBLE_DP = 72 +# pixels per dp of each density +DENSITIES = {"mdpi": 1.0, "hdpi": 1.5, "xhdpi": 2.0, "xxhdpi": 3.0, "xxxhdpi": 4.0} +WINDOWS_SIZES = [16, 20, 24, 32, 40, 48, 64, 128, 256] + + +def background_gradient(icon: Image.Image, render: Image.Image) -> tuple: + """the icon's background as a line through its rows' colours (where the + render leaves it bare): the colours at the top and the bottom of the + launcher's layer, which reaches (LAYER_DP - VISIBLE_DP) / 2 beyond the + picture at each side""" + pixels = np.asarray(icon.convert("RGB"), dtype=np.float64) + bare = np.asarray(render.resize(icon.size, Image.LANCZOS).getchannel("A")) == 0 + rows, colours = [], [] + for y in range(icon.height): + if bare[y].any(): + rows.append(y) + colours.append(pixels[y][bare[y]].mean(axis=0)) + fit = np.polyfit(np.array(rows, dtype=np.float64), np.array(colours), 1) + margin = (LAYER_DP - VISIBLE_DP) / 2 / VISIBLE_DP * icon.height + + def at(y: float) -> tuple: + return tuple(int(round(min(255.0, max(0.0, fit[0][c] * y + fit[1][c])))) for c in range(3)) + + return at(-margin), at(icon.height + margin) + + +def carried_out(image: Image.Image, size: int) -> Image.Image: + """image in the middle of a square of size, its edge pixels carried out + to the square's edges""" + pad = (size - image.width) // 2 + data = np.asarray(image) + padded = np.pad(data, ((pad, size - image.height - pad), (pad, size - image.width - pad), (0, 0)), mode="edge") + return Image.fromarray(padded, "RGBA") + + +def monochrome(render: Image.Image) -> Image.Image: + """the render's light parts, white with their lightness as opacity: the + armour without the darker visor""" + data = np.asarray(render, dtype=np.float64) + lightness = (0.2126 * data[..., 0] + 0.7152 * data[..., 1] + 0.0722 * data[..., 2]) / 255.0 + alpha = np.clip(data[..., 3] / 255.0 * np.clip((lightness - 0.25) / 0.5, 0.0, 1.0), 0.0, 1.0) + out = np.zeros(data.shape, dtype=np.uint8) + out[..., :3] = 255 + out[..., 3] = np.round(alpha * 255).astype(np.uint8) + return Image.fromarray(out, "RGBA") + + +def main() -> None: + icon = Image.open(ICON).convert("RGBA") + render = Image.open(RENDER).convert("RGBA") + + # the desktop's icons + icon.resize((256, 256), Image.LANCZOS).save(WINDOW_ICON, optimize=True) + print(f"{WINDOW_ICON.relative_to(ROOT)}: 256x256") + icon.save(WINDOWS_ICON, sizes=[(size, size) for size in WINDOWS_SIZES]) + print(f"{WINDOWS_ICON.relative_to(ROOT)}: {', '.join(str(size) for size in WINDOWS_SIZES)}") + + # the launcher's layers + themed = monochrome(render) + for density, scale in DENSITIES.items(): + size = round(LAYER_DP * scale) + visible = round(VISIBLE_DP * scale) + directory = RESOURCES / f"mipmap-{density}" + directory.mkdir(parents=True, exist_ok=True) + for name, layer in (("ic_launcher_foreground", render), ("ic_launcher_monochrome", themed)): + carried_out(layer.resize((visible, visible), Image.LANCZOS), size).save( + directory / f"{name}.png", optimize=True) + print(f"{directory.relative_to(ROOT)}: {size}x{size}") + + top, bottom = background_gradient(icon, render) + drawable = RESOURCES / "drawable" + drawable.mkdir(parents=True, exist_ok=True) + (drawable / "ic_launcher_background.xml").write_text( + '\n' + "\n" + '\n' + ' \n'.format(*top, *bottom) + + "\n", encoding="utf-8") + adaptive = RESOURCES / "mipmap-anydpi-v26" + adaptive.mkdir(parents=True, exist_ok=True) + (adaptive / "ic_launcher.xml").write_text( + '\n' + "\n" + '\n' + ' \n' + ' \n' + ' \n' + "\n", encoding="utf-8") + print("background #{:02X}{:02X}{:02X} to #{:02X}{:02X}{:02X}".format(*top, *bottom)) + + +if __name__ == "__main__": + main() diff --git a/tools/ce_menus.py b/tools/ce_menus.py index c57a38e1..b3f6742b 100644 --- a/tools/ce_menus.py +++ b/tools/ce_menus.py @@ -626,6 +626,12 @@ def main() -> None: lines.append(art.port_picture(f"pause/pausebox_{piece}", [ (port_settings.pause_box_svg(piece, port_settings.pause_box_height(buttons)), 4 if piece == "center" else 16, 256) for buttons in port_settings.PAUSE_BOX_BUTTONS])) + # (voice chat's speaker, of a player who talks and of one muted: Lucide's + # icons, port/assets/icons/lucide, white for network_voice.c to tint) + lines.append(art.port_picture("voice/speaker", [ + ((ROOT / "port/assets/icons/lucide" / f"{icon}.svg").read_text().replace('stroke="currentColor"', + 'stroke="#FFFFFF"'), 24, 24) + for icon in ("volume-2", "volume-x")])) # (nothing: what a game map draws for a frame of ui.map's it has not, # menu_tags.c) lines.append(art.port_picture("blank", [ diff --git a/tools/ci_build.py b/tools/ci_build.py index fc02a92f..56a749f0 100644 --- a/tools/ci_build.py +++ b/tools/ci_build.py @@ -4,12 +4,14 @@ python tools/ci_build.py linux debug python tools/ci_build.py android release + python tools/ci_build.py linux profile Builds are portable (any x86-64 processor), so they run on other computers. Debug builds skip link-time and profile-guided optimisation, which only make the build slower; release builds use both, as a local release build does (profile-guided optimisation needs clang 22 or later, -and is skipped with an older one). CI_COMPILER_LAUNCHER (ccache, say) is +and is skipped with an older one). A profile build is a debug build with +configure.py --profile, so that the profiling build is built too. CI_COMPILER_LAUNCHER (ccache, say) is passed on as --compiler-launcher. A build of the main branch gets the run's number (HALO_BUILD_NUMBER), which its release is named after and the self-updater compares. @@ -26,7 +28,9 @@ ROOT = Path(__file__).resolve().parent.parent # what each port's build leaves, and what goes into dist/ OUTPUTS = { - "linux": ["build/linux/halo"], + # (and the SDL3 the portable build brings, tools/linux_build.py, with its + # zlib license, as the release carries the other libraries') + "linux": ["build/linux/halo", "build/linux/libSDL3.so.0", "build/linux/SDL3-LICENSE.txt"], "windows": ["build/windows/halo.exe", "build/windows/SDL3.dll"], "android": [], # the APK, below } @@ -44,7 +48,7 @@ def run(command, cwd=ROOT): def main() -> int: parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("platform", choices=sorted(OUTPUTS)) - parser.add_argument("config", choices=["debug", "release"]) + parser.add_argument("config", choices=["debug", "release", "profile"]) args = parser.parse_args() configure = [sys.executable, "configure.py", "--portable"] @@ -52,6 +56,8 @@ def main() -> int: configure.append("--release") else: configure += ["--lto=off", "--pgo=off"] + if args.config == "profile": + configure.append("--profile") launcher = os.environ.get("CI_COMPILER_LAUNCHER") if launcher: configure += ["--compiler-launcher", launcher] @@ -68,8 +74,9 @@ def main() -> int: # same name: release is signed with the debug key, not debuggable) run(["ninja", "android"]) gradlew = "gradlew.bat" if os.name == "nt" else "./gradlew" - run([gradlew, "--console=plain", "-q", f"assemble{args.config.capitalize()}"], cwd=ROOT / "port/android") - outputs = [APKS[args.config]] + variant = "release" if args.config == "release" else "debug" + run([gradlew, "--console=plain", "-q", f"assemble{variant.capitalize()}"], cwd=ROOT / "port/android") + outputs = [APKS[variant]] else: run(["ninja", args.platform]) outputs = OUTPUTS[args.platform] @@ -111,6 +118,12 @@ def main() -> int: # the menus' XML parser (port/third_party/expat), in every build, whose # MIT license asks copies to carry its notice shutil.copy2(ROOT / "port/third_party/expat/COPYING", dist / "expat-COPYING.txt") + # voice chat's codec (port/third_party/opus), in every build, whose BSD + # license asks binaries to carry its notice + shutil.copy2(ROOT / "port/third_party/opus/COPYING", dist / "opus-COPYING.txt") + # voice chat's speaker icons (port/assets/icons/lucide, drawn into the + # menus' bitmaps), whose ISC license asks copies to carry its notice + shutil.copy2(ROOT / "port/assets/icons/lucide/LICENSE", dist / "lucide-LICENSE.txt") # internet play's MQTT brokers, a file beside the game (network.brokers_file; # Android's APK has its own copy) if args.platform != "android": diff --git a/tools/embed_assets.py b/tools/embed_assets.py index fc22b4ff..bb225eda 100644 --- a/tools/embed_assets.py +++ b/tools/embed_assets.py @@ -40,6 +40,8 @@ MENU_LIST = MENU_ASSETS / "menus.json" SMAA_ASSETS = Path("port/third_party/smaa") SMAA_FILES = (("SMAA.hlsl", "xgpu_smaa_shader"), ("area_tex.zlib", "xgpu_smaa_area_texture"), ("search_tex.zlib", "xgpu_smaa_search_texture")) +# the desktop windows' icon (tools/app_icon.py; port/linux/src/sdl_platform.c) +WINDOW_ICON = Path("port/assets/icon/opence-icon-256.png") PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n" @@ -78,7 +80,8 @@ def hud_asset_inputs() -> List[Path]: inputs = [listing for listing in (LAYOUT, TITLE_LIST, FONT_LIST, MENU_LIST) if (ROOT / listing).is_file()] return [*inputs, *(folder / f"{asset['name']}.png" for folder, asset, _ in textures()), *(FONT_ASSETS / name for name in font_files()), *(MENU_ASSETS / name for name in menu_files()), - *(SMAA_ASSETS / name for name, _ in smaa_files())] + *(SMAA_ASSETS / name for name, _ in smaa_files()), + *([WINDOW_ICON] if (ROOT / WINDOW_ICON).is_file() else [])] def hud_configure_inputs() -> List[Path]: @@ -208,8 +211,8 @@ def main() -> None: lines.append("") # SMAA's shader, as text a GLSL compiler takes (ASCII, ending in a NUL), # and its lookup textures (xgpu_post.c); each of size 0 that the - # checkout does not have. Android has no SMAA. - lines.append("#ifndef HALO_ANDROID") + # checkout does not have. The OpenGL ES renderer has no SMAA. + lines.append("#ifndef HALO_GLES") present = dict(smaa_files()) for name, symbol in SMAA_FILES: data = (ROOT / SMAA_ASSETS / name).read_bytes() if name in present else b"" @@ -220,6 +223,13 @@ def main() -> None: lines.extend(words(data) if data else ["\t0,"]) lines.append("};") lines.append(f"const unsigned long {symbol}_size = {len(data)};") + # the windows' icon, a PNG (of size 0 where the checkout has none) + data = (ROOT / WINDOW_ICON).read_bytes() if (ROOT / WINDOW_ICON).is_file() else b"" + lines.append("") + lines.append("const unsigned int platform_window_icon[] = {") + lines.extend(words(data) if data else ["\t0,"]) + lines.append("};") + lines.append(f"const unsigned long platform_window_icon_size = {len(data)};") lines.append("") lines.append("#endif") output = Path(sys.argv[1]) diff --git a/tools/harness/__init__.py b/tools/harness/__init__.py index 01b8bda3..e351e53f 100644 --- a/tools/harness/__init__.py +++ b/tools/harness/__init__.py @@ -41,6 +41,19 @@ def enum_with(source, member): return source[start:source.index("};", at) + 2] +def structure(source, name): + """An actual named structure's definition, including nested unions/structures.""" + match = re.search(r"^struct\s+" + re.escape(name) + r"\s*\{", source, re.M) + if not match: + raise LookupError(f"structure not found in the sources: {name}") + end = source.index("{", match.start()) + 1 + depth = 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[match.start():source.index(";", end) + 1] + + def constant(source, name): """An enum constant's or #define's integer value, following a name it is set to (as the port's capacities are).""" match = re.search(r"\b" + re.escape(name) + r"\s*=\s*(\w+)", source) or \ @@ -76,7 +89,7 @@ def build(test, generated): executable = work / test command = [os.environ.get("CC", "clang"), "-m32", "-std=gnu99", "-O2", "-Wall", "-Werror", "-Wno-unused-function", "-Wno-unused-variable", "-I", str(HARNESS / "include"), "-I", str(work), - str(HARNESS / "tests" / f"{test}.c"), "-o", str(executable)] + str(HARNESS / "tests" / f"{test}.c"), "-o", str(executable), "-lm"] result = subprocess.run(command, capture_output=True, text=True) if result.returncode: raise RuntimeError(f"{test}.c does not compile:\n{result.stderr}") diff --git a/tools/harness/audio.py b/tools/harness/audio.py new file mode 100644 index 00000000..987e0c45 --- /dev/null +++ b/tools/harness/audio.py @@ -0,0 +1,82 @@ +"""Audio fixtures use the production structures, enums and capacities.""" + +from harness import constant, enum_with, read, structure + + +def manager_types(): + manager = read("source/sound/sound_manager.c") + # The symbol table comment contains enum spellings before their declarations. + manager = manager[manager.index("/* ---------- constants */"):] + definitions = read("source/sound/sound_definitions.h") + classes = read("source/sound/sound_classes.h") + text = "typedef unsigned short word;\nstruct sound_preferences;\n" + for source, member in [(manager, "_sound_impulse ="), (manager, "_sound_waiting_for_cache_bit"), + (manager, "_sound_channel_idle"), (manager, "_sound_fade_mode_linear"), + (manager, "_fade_in_at_start_bit"), (manager, "_sound_spatialization_mode_none"), + (definitions, "_sound_definition_linked_permutations_bit"), + (definitions, "_looping_sound_fake_impulse_sound_bit"), + (read("source/sound/sound_manager.h"), "_looping_sound_refresh_start")]: + text += enum_with(source, member) + "\n" + for source, names in [(classes, ["MAXIMUM_SOUND_INSTANCES_PER_DEFINITION", + "MAXIMUM_SOUND_INSTANCES_PER_OBJECT_PER_DEFINITION"]), + (definitions, ["MAXIMUM_DETAIL_SOUNDS_PER_LOOPING_SOUND"]), + (read("source/networking/network_connection.h"), ["MAXIMUM_NUMBER_OF_LOCAL_PLAYERS"])]: + for name in names: + text += f"#define {name} {constant(source, name)}\n" + for path, names in [ + ("source/math/real_math.h", ["real_matrix4x3"]), + ("source/tag_files/tag_groups.h", ["tag_block", "tag_reference", "tag_data"]), + ("source/objects/objects.h", ["location"]), + ("source/sound/game_sound.h", ["sound_location"]), + ("source/sound/sound_environment_definitions.h", ["sound_environment_definition"]), + ("source/sound/sound_definitions.h", ["real_bounds", "sound_permutation", "sound_pitch_range", + "sound_definition", "looping_sound_scale_modifiers", "looping_sound_track", "looping_sound_definition"]), + ("source/sound/sound_classes.h", ["sound_class_definition"]), + ("source/sound/sound_manager.c", ["sound_source", "sound_listener", "sound_channel_datum", + "sound_channel_summary", "platform_sound_channel_properties", "platform_sound_listener_properties", + "sound_datum", "looping_sound_datum", "sound_platform_definition", "sound_manager_globals"]), + ]: + for name in names: + text += structure(read(path), name) + "\n" + if name == "real_matrix4x3": + text += "typedef struct real_matrix4x3 real_matrix4x3;\n" + return text + + +def stream_types(): + backend = read("port/linux/src/dsound_sdl.c") + game = read("source/sound/sound_dsound_xbox.c") + xdk = read("port/include/xdk/xdk_pdb.h") + text = """typedef unsigned long DWORD, ULONG; +typedef long LONG, HRESULT; +typedef int BOOL; +typedef void *LPVOID, *LPDIRECTSOUND, *LPDIRECTSOUNDBUFFER; +typedef long long __int64; +typedef void (*LPFNXMEDIAOBJECTCALLBACK)(void *, void *, unsigned long); +#define CALLBACK +#define STDMETHODCALLTYPE +#define S_OK 0 +""" + statuses = read("port/include/xdk/xdk_dsound.h") + for name in ["XMEDIAPACKET_STATUS_SUCCESS", "XMEDIAPACKET_STATUS_FLUSHED", + "XMEDIAPACKET_STATUS_FAILURE", "XMEDIAPACKET_STATUS_PENDING"]: + # These SDK status expressions include casts, unlike integer capacities. + text += next(line for line in statuses.splitlines() if line.startswith("#define " + name + " ")) + "\n" + for name in ["MAXIMUM_STREAM_PACKETS", "RESAMPLER_HISTORY"]: + text += f"#define {name} {constant(backend, name)}\n" + for name in ["MAXIMUM_SOUND_CHANNELS", "NUMBER_OF_SOUND_CHANNEL_TYPES"]: + text += f"#define {name} {constant(game, name)}\n" + text += enum_with(game, "_sound_channel_idle") + "\n" + text += structure(xdk, "IDirectSoundStream") + "\n" + text += structure(xdk, "_XMEDIAPACKET") + "\n" + text += structure(xdk, "_DSCAPS") + "\n" + text += """typedef struct IDirectSoundStream IDirectSoundStream, *LPDIRECTSOUNDSTREAM; +typedef struct _XMEDIAPACKET XMEDIAPACKET; +typedef struct _DSCAPS DSCAPS; +""" + text += structure(read("source/sound/sound_environment_definitions.h"), "sound_environment_definition") + "\n" + for name in ["sound_virtual_channel", "sound_channel", "dsound_globals"]: + text += structure(game, name) + "\n" + for name in ["voice_packet", "sdl_stream"]: + text += structure(backend, name) + "\n" + return text diff --git a/tools/harness/render_types.py b/tools/harness/render_types.py new file mode 100644 index 00000000..18e8f70e --- /dev/null +++ b/tools/harness/render_types.py @@ -0,0 +1,20 @@ +"""Production layouts shared by the CPU-only transparent rendering tests.""" +from harness import enum_with, read, structure + + +def transparent_types(): + declarations = [] + for path, names in [ + ("source/tag_files/tag_groups.h", ["tag_block", "tag_reference"]), + ("source/shaders/shader_definitions.h", ["shader_radiosity_properties", "shader_physics_properties", "shader_base", "shader"]), + ("source/rasterizer/rasterizer_geometry.h", ["vertex_buffer", "triangle_buffer"]), + ("source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c", ["shader_transparent_generic", "shader_transparent_generic_definition", "shader_transparent_glass_definition", "transparent_geometry_group"]), + ]: + text = read(path) + declarations += [structure(text, name) for name in names] + text = read("source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c") + for member in ["_shader_type_screen", "_shader_transparent_flag_alpha_tested_bit", "_shader_transparent_glass_flag_alpha_tested_bit", "_shader_transparent_glass_reflection_type_bumped_cube_map", "_shader_radiosity_FILTHY_transparent_lit_bit", "_framebuffer_fade_mode_none", "_framebuffer_blend_function_alpha_blend", "_rasterizer_geometry_no_sort_bit"]: + declarations.append(enum_with(text, member)) + text = read("source/rasterizer/rasterizer_geometry.h") + declarations += [enum_with(text, "_rasterizer_vertex_type_environment_uncompressed"), enum_with(text, "_triangle_buffer_type_triangles")] + return "\n".join(declarations) diff --git a/tools/harness/tests/mixer.c b/tools/harness/tests/mixer.c new file mode 100644 index 00000000..c337600c --- /dev/null +++ b/tools/harness/tests/mixer.c @@ -0,0 +1,691 @@ +/* +MIXER.C (test) + +The real mixer of port/linux/src/dsound_sdl.c (its resampler, voices, +reverb and limiter; test_mixer.py takes them as under_test.inc) on known +signals: + - the resampler against the same Kaiser-windowed sinc worked out in + double precision, tap by tap: noise, sines and an impulse at the + game's rates (22 and 44 kHz voices, others pitched up and down, a 48 + kHz one), so a faster resampler is still the same low pass; + - sines through it measured as the ear would hear them: the gain across + the band, and what else comes out (images and aliases) under the tone; + - a voice out of earshot, which is not resampled, moves on as one heard + does, and is heard the same once it comes into earshot; + - mono voices heard as stereo ones with both channels the same are. +"render" writes voices at each rate and a scene's whole mix as raw floats +(test_mixer.py compares the resampler adding four taps at a time with it +adding tap by tap), "metrics" prints the figures and "bench" times a game's +worth of voices. +*/ + +#include "harness.h" + +#include +#include + +typedef int BOOL; +typedef unsigned long DWORD; +typedef long LONG; +typedef unsigned long ULONG; +typedef void *LPVOID; +typedef void (*LPFNXMEDIAOBJECTCALLBACK)(void *, void *, DWORD); +typedef struct { void *lpVtbl; } IDirectSoundStream; +typedef struct { void *pvBuffer; DWORD dwMaxSize; } XMEDIAPACKET; +typedef struct +{ + LONG lRoom, lRoomHF; + float flRoomRolloffFactor, flDecayTime, flDecayHFRatio; + LONG lReflections; + float flReflectionsDelay; + LONG lReverb; + float flReverbDelay, flDiffusion, flDensity, flHFReference; +} DSI3DL2LISTENER; +#define DSBVOLUME_MIN (-10000) +#define DS3DMODE_NORMAL 0x00000000 +#define DS3DMODE_HEADRELATIVE 0x00000001 +#define DS3DMODE_DISABLE 0x00000002 + +/* the players' voices (voice_audio.c): none */ +static void voice_audio_mix(float *output, unsigned long frames) +{ + (void)output; + (void)frames; +} + +#include "under_test.inc" + +#define PI 3.14159265358979323846 +#define CHUNK 512 /* frames a mix, as SDL asks on Linux */ + +/* ---------- voices */ + +/* a voice of frames frames of channels channels at rate, played at frequency +(0: its rate), a 2D voice heard at full volume in both speakers */ +static struct sdl_stream *voice_new(const short *samples, unsigned long frames, unsigned long channels, + unsigned long rate, unsigned long frequency) +{ + struct sdl_stream *stream = calloc(1, sizeof(*stream)); + + stream->channels = channels; + stream->sample_rate = rate; + stream->frequency = frequency; + stream->volume = 1.0f; + stream->mix_left = stream->mix_right = 1.0f; + stream->room = stream->room_hf = DSBVOLUME_MIN; + stream->packets[0].samples = malloc(frames * channels * sizeof(short)); + memcpy(stream->packets[0].samples, samples, frames * channels * sizeof(short)); + stream->packets[0].frames = frames; + stream->packet_count = 1; + resampler_reset(stream); + return stream; +} + +static void voice_delete(struct sdl_stream *stream) +{ + free(stream->packets[0].samples); + free(stream); +} + +/* frames of the voice mixed alone, CHUNK at a time: the left channel, or +both (stereo: interleaved) */ +static void render(struct sdl_stream *stream, float *output, unsigned long frames, BOOL stereo) +{ + static float buffer[CHUNK * 2], send[CHUNK]; + unsigned long done, frame; + + for (done = 0; done < frames; done += CHUNK) + { + unsigned long count = frames - done < CHUNK ? frames - done : CHUNK; + + memset(buffer, 0, sizeof(buffer)); + memset(send, 0, sizeof(send)); + mix_voice(stream, buffer, send, count); + for (frame = 0; frame < count; frame++) + { + if (stereo) + { + output[(done + frame) * 2] = buffer[frame * 2]; + output[(done + frame) * 2 + 1] = buffer[frame * 2 + 1]; + } + else + { + output[done + frame] = buffer[frame * 2]; + } + } + } +} + +/* ---------- the reference: the same low pass in double precision */ + +static double reference_kernel(double distance) +{ + double edge, angle, sinc; + + distance = fabs(distance); + if (distance >= RESAMPLER_ZERO_CROSSINGS) + return 0.0; + edge = distance / RESAMPLER_ZERO_CROSSINGS; + angle = PI * RESAMPLER_CUTOFF * distance; + sinc = distance > 0.0 ? sin(angle) / angle : 1.0; + return RESAMPLER_CUTOFF * sinc * bessel_i0(RESAMPLER_KAISER_BETA * sqrt(1.0 - edge * edge)) / + bessel_i0(RESAMPLER_KAISER_BETA); +} + +/* output frame n of source (frames, mono, full scale 1) resampled at step: +the frames around source time n step, weighted by the low pass (narrowed for a +step over 1, as the mixer's) */ +static double reference_frame(const double *source, long frames, double step, unsigned long n) +{ + double scale = step > 1.0 ? 1.0 / (step < RESAMPLER_MAXIMUM_STRETCH ? step : RESAMPLER_MAXIMUM_STRETCH) : 1.0; + double time = (double)n * step, sum = 0.0; + long center = (long)floor(time), width = (long)ceil(RESAMPLER_ZERO_CROSSINGS / scale), k; + + for (k = center + 1 - width; k <= center + width; k++) + { + if (k >= 0 && k < frames) + sum += source[k] * scale * reference_kernel((time - (double)k) * scale); + } + return sum; +} + +/* ---------- signals */ + +static unsigned long random_state = 1; + +static double random_uniform(void) +{ + random_state = random_state * 1103515245UL + 12345UL; + return (double)((random_state >> 8) & 0xffffff) / 16777216.0 * 2.0 - 1.0; +} + +/* frames of a signal as 16-bit samples (and as the doubles they are) */ +static void quantize(const double *signal, short *samples, double *exact, unsigned long frames) +{ + unsigned long frame; + + for (frame = 0; frame < frames; frame++) + { + long value = lround(signal[frame] * 32767.0); + + samples[frame] = (short)(value > 32767 ? 32767 : value < -32768 ? -32768 : value); + exact[frame] = samples[frame] / 32768.0; + } +} + +/* ---------- figures */ + +/* the amplitude of a sine of frequency cycles a frame in signal[first, last), +and the power of the rest relative to the sine's, in dB */ +static void tone_fit(const float *signal, unsigned long first, unsigned long last, double frequency, + double *amplitude, double *rest_db) +{ + double ss = 0, sc = 0, cc = 0, ys = 0, yc = 0, a, b, determinant, rest = 0, tone = 0; + unsigned long n; + + for (n = first; n < last; n++) + { + double s = sin(2.0 * PI * frequency * n), c = cos(2.0 * PI * frequency * n); + + ss += s * s; + sc += s * c; + cc += c * c; + ys += signal[n] * s; + yc += signal[n] * c; + } + determinant = ss * cc - sc * sc; + a = (ys * cc - yc * sc) / determinant; + b = (yc * ss - ys * sc) / determinant; + for (n = first; n < last; n++) + { + double fit = a * sin(2.0 * PI * frequency * n) + b * cos(2.0 * PI * frequency * n); + + rest += (signal[n] - fit) * (signal[n] - fit); + tone += fit * fit; + } + *amplitude = sqrt(a * a + b * b); + *rest_db = 10.0 * log10(rest / tone + 1e-30); +} + +static double decibels(double ratio) +{ + return 20.0 * log10(ratio + 1e-30); +} + +/* the mixer's output against the reference's for a signal at rate played at +frequency: the error's power relative to the reference's, in dB, and its +largest relative to the largest sample */ +static void against_reference(const double *signal, unsigned long frames, unsigned long rate, unsigned long frequency, + double *error_db, double *peak_error_db) +{ + short *samples = malloc(frames * sizeof(short)); + double *exact = malloc(frames * sizeof(double)); + double step = (double)(frequency ? frequency : rate) / OUTPUT_RATE, error = 0, power = 0, peak = 0, worst = 0; + unsigned long outputs = (unsigned long)(frames / step), n; + float *output = calloc(outputs, sizeof(float)); + struct sdl_stream *stream; + + quantize(signal, samples, exact, frames); + stream = voice_new(samples, frames, 1, rate, frequency); + render(stream, output, outputs, FALSE); + for (n = 0; n < outputs; n++) + { + double expected = reference_frame(exact, (long)frames, step, n); + double difference = output[n] - expected; + + error += difference * difference; + power += expected * expected; + peak = fabs(expected) > peak ? fabs(expected) : peak; + worst = fabs(difference) > worst ? fabs(difference) : worst; + } + *error_db = 10.0 * log10(error / power + 1e-30); + *peak_error_db = decibels(worst / peak); + voice_delete(stream); + free(output); + free(exact); + free(samples); +} + +/* a sine of hertz at rate, played at frequency: its gain through the mixer +(dB) and the power of everything else that comes out, relative to it (dB); a +sine the voice plays above the output's band is all "else", relative to the +sine put in */ +static void sine_figures(double hertz, unsigned long rate, unsigned long frequency, double *gain_db, double *rest_db) +{ + unsigned long frames = rate, outputs, n; + double step = (double)(frequency ? frequency : rate) / OUTPUT_RATE, played = hertz * step * OUTPUT_RATE / rate; + double *signal = malloc(frames * sizeof(double)), *exact = malloc(frames * sizeof(double)), amplitude; + short *samples = malloc(frames * sizeof(short)); + float *output; + struct sdl_stream *stream; + + for (n = 0; n < frames; n++) + signal[n] = 0.5 * sin(2.0 * PI * hertz * n / rate); + quantize(signal, samples, exact, frames); + outputs = (unsigned long)(frames / step); + output = calloc(outputs, sizeof(float)); + stream = voice_new(samples, frames, 1, rate, frequency); + render(stream, output, outputs, FALSE); + if (played < 0.5 * OUTPUT_RATE) + { + tone_fit(output, outputs / 4, outputs * 3 / 4, played / OUTPUT_RATE, &litude, rest_db); + *gain_db = decibels(amplitude / 0.5); + } + else + { + double power = 0; + + for (n = outputs / 4; n < outputs * 3 / 4; n++) + power += (double)output[n] * output[n]; + *gain_db = -999.0; + *rest_db = 10.0 * log10(power / (outputs / 2) / (0.5 * 0.5 / 2) + 1e-30); + } + voice_delete(stream); + free(output); + free(samples); + free(exact); + free(signal); +} + +/* a logarithmic sweep from 20 Hz to top at rate over the voice: what comes out +against the sweep itself at the output's times (an ideal resampler), in dB */ +static double sweep_figure(unsigned long rate, double top) +{ + unsigned long frames = 2 * rate, outputs = (unsigned long)((double)frames * OUTPUT_RATE / rate), n; + double *signal = malloc(frames * sizeof(double)), *exact = malloc(frames * sizeof(double)); + double duration = (double)frames / rate, k = log(top / 20.0), error = 0, power = 0; + short *samples = malloc(frames * sizeof(short)); + float *output = calloc(outputs, sizeof(float)); + struct sdl_stream *stream; + + for (n = 0; n < frames; n++) + { + double t = (double)n / rate; + + signal[n] = 0.5 * sin(2.0 * PI * 20.0 * duration / k * (exp(t / duration * k) - 1.0)); + } + quantize(signal, samples, exact, frames); + stream = voice_new(samples, frames, 1, rate, 0); + render(stream, output, outputs, FALSE); + for (n = OUTPUT_RATE / 10; n + OUTPUT_RATE / 10 < outputs; n++) + { + double t = (double)n / OUTPUT_RATE; + double ideal = 0.5 * sin(2.0 * PI * 20.0 * duration / k * (exp(t / duration * k) - 1.0)); + + error += (output[n] - ideal) * (output[n] - ideal); + power += ideal * ideal; + } + voice_delete(stream); + free(output); + free(samples); + free(exact); + free(signal); + return 10.0 * log10(error / power + 1e-30); +} + +static double *noise(unsigned long frames) +{ + double *signal = malloc(frames * sizeof(double)); + unsigned long n; + + random_state = 1; + for (n = 0; n < frames; n++) + signal[n] = 0.5 * random_uniform(); + return signal; +} + +/* ---------- a scene: a game's worth of voices, for "render" and "bench" */ + +/* voices: mono 22 kHz sounds in the world (indoors sending to the reverb, +half muffled, silent ones out of earshot), stereo 44 kHz music, a few +pitched; each plays for seconds */ +static void scene(unsigned long voices, unsigned long silent, BOOL indoor, double seconds) +{ + unsigned long frames = 22050 * 4, v, n; + short *mono = malloc(frames * sizeof(short)), *stereo = malloc(frames * 2 * sizeof(short)); + + random_state = 7; + for (n = 0; n < frames; n++) + { + mono[n] = (short)(8000 * random_uniform()); + stereo[n * 2] = (short)(8000 * random_uniform()); + stereo[n * 2 + 1] = (short)(8000 * random_uniform()); + } + if (indoor) + { + DSI3DL2LISTENER room = { -1000, -100, 0.0f, 1.49f, 0.83f, -2602, 0.007f, 200, 0.011f, 100.0f, 100.0f, 5000.0f }; + + environment = room; + environment_serial++; + } + resampler_initialize(); + resampler_phases_initialize(); + reverb_initialize(); + for (v = 0; v < voices + silent; v++) + { + struct sdl_stream *stream; + + if (v == 0) + stream = voice_new(stereo, frames, 2, 44100, 0); + else + stream = voice_new(mono, frames, 1, 22050, v % 5 == 0 ? 22050 + 2000 * (v % 3) : 0); + if (v) + { + stream->has_3d = TRUE; + stream->mode = DS3DMODE_NORMAL; + stream->position[0] = (float)(v % 7) - 3.0f; + stream->position[2] = v < voices ? 2.0f : 1000.0f; + stream->minimum_distance = 1.0f; + stream->maximum_distance = v < voices ? 50.0f : 100.0f; + stream->direct = v < voices ? 0 : DSBVOLUME_MIN; + stream->direct_hf = v % 2 ? -500 : 0; + stream->room = v < voices ? 0 : DSBVOLUME_MIN; + stream->room_hf = 0; + } + /* a packet as long as the scene */ + free(stream->packets[0].samples); + stream->packets[0].frames = (unsigned long)(seconds * 50000); + stream->packets[0].samples = malloc(stream->packets[0].frames * stream->channels * sizeof(short)); + for (n = 0; n < stream->packets[0].frames * stream->channels; n++) + stream->packets[0].samples[n] = (v == 0 ? stereo : mono)[n % frames]; + stream->next = streams; + streams = stream; + } + free(mono); + free(stereo); +} + +/* the scene's voices out of earshot come into it */ +static void scene_approach(void) +{ + struct sdl_stream *stream; + + for (stream = streams; stream; stream = stream->next) + { + if (stream->direct == DSBVOLUME_MIN) + { + stream->position[2] = 3.0f; + stream->direct = stream->room = 0; + } + } +} + +static void bench(unsigned long voices, unsigned long silent, BOOL indoor, double seconds) +{ + unsigned long chunks = (unsigned long)(seconds * OUTPUT_RATE / CHUNK), chunk; + static float output[CHUNK * 2]; + struct timespec start, end; + double elapsed; + + scene(voices, silent, indoor, seconds); + clock_gettime(CLOCK_MONOTONIC, &start); + for (chunk = 0; chunk < chunks; chunk++) + mix(output, CHUNK); + clock_gettime(CLOCK_MONOTONIC, &end); + elapsed = (double)(end.tv_sec - start.tv_sec) + (double)(end.tv_nsec - start.tv_nsec) * 1e-9; + printf("%lu voices (%lu out of earshot)%s: %.2f%% of a core, %.1f ns a frame\n", voices, silent, + indoor ? " indoors" : "", 100.0 * elapsed / seconds, elapsed * 1e9 / ((double)chunks * CHUNK)); +} + +int main(int argc, char **argv) +{ + char const *case_name = argc > 1 ? argv[1] : ""; + static const unsigned long rates[][2] = { + { 22050, 0 }, { 44100, 0 }, { 48000, 0 }, { 22050, 17000 }, { 22050, 30000 }, { 44100, 60000 }, + { 44100, 96000 }, { 32000, 0 }, + }; + unsigned long index; + + resampler_initialize(); + resampler_phases_initialize(); + + /* the resampler is the low pass worked out exactly: noise at every rate, + the error well under 16-bit's */ + CASE("reference") + { + double *signal = noise(30000); + + for (index = 0; index < sizeof(rates) / sizeof(rates[0]); index++) + { + double error_db, peak_db; + + against_reference(signal, 30000, rates[index][0], rates[index][1], &error_db, &peak_db); + CHECK(error_db < -100.0 && peak_db < -90.0, "%lu Hz played at %lu: the error %.1f dB, its peak %.1f dB", + rates[index][0], rates[index][1], error_db, peak_db); + } + free(signal); + return 0; + } + /* an impulse: the low pass itself */ + CASE("impulse") + { + double *signal = calloc(4000, sizeof(double)); + + signal[2000] = 1.0; + for (index = 0; index < sizeof(rates) / sizeof(rates[0]); index++) + { + double error_db, peak_db; + + against_reference(signal, 4000, rates[index][0], rates[index][1], &error_db, &peak_db); + CHECK(peak_db < -90.0, "%lu Hz played at %lu: the impulse's error peaks at %.1f dB", rates[index][0], + rates[index][1], peak_db); + } + free(signal); + return 0; + } + /* sines as heard: a 22 kHz voice flat within half a dB to 10 kHz, a 44 + kHz one to 20 kHz, the rest 70 dB down in the band, more past it */ + CASE("sines") + { + static const struct { unsigned long rate, frequency; double hertz, gain_db, rest_db; } sines[] = { + { 22050, 0, 100.0, 0.05, -85.0 }, { 22050, 0, 1000.0, 0.05, -85.0 }, { 22050, 0, 5000.0, 0.05, -80.0 }, + { 22050, 0, 8000.0, 0.1, -75.0 }, { 22050, 0, 10000.0, 0.5, -70.0 }, + { 44100, 0, 1000.0, 0.05, -85.0 }, { 44100, 0, 10000.0, 0.05, -80.0 }, + { 44100, 0, 16000.0, 0.1, -75.0 }, { 44100, 0, 20000.0, 0.5, -70.0 }, + { 48000, 0, 1000.0, 0.05, -85.0 }, { 48000, 0, 20000.0, 0.5, -70.0 }, + { 22050, 30000, 5000.0, 0.1, -75.0 }, + }; + + for (index = 0; index < sizeof(sines) / sizeof(sines[0]); index++) + { + double gain_db, rest_db; + + sine_figures(sines[index].hertz, sines[index].rate, sines[index].frequency, &gain_db, &rest_db); + CHECK(fabs(gain_db) < sines[index].gain_db && rest_db < sines[index].rest_db, + "%.0f Hz at %lu Hz, played at %lu: gain %.2f dB, the rest %.1f dB", sines[index].hertz, + sines[index].rate, sines[index].frequency, gain_db, rest_db); + } + return 0; + } + /* a tone a voice played faster than the output rate takes over the + output's band does not come out as an alias */ + CASE("aliases") + { + double gain_db, rest_db; + + /* 18 kHz at 44.1 kHz, played at 96 kHz: 39 kHz */ + sine_figures(18000.0, 44100, 96000, &gain_db, &rest_db); + CHECK(rest_db < -60.0, "an alias of 39 kHz %.1f dB", rest_db); + /* 15 kHz at 44.1 kHz, played at 88.2 kHz: 30 kHz */ + sine_figures(15000.0, 44100, 88200, &gain_db, &rest_db); + CHECK(rest_db < -60.0, "an alias of 30 kHz %.1f dB", rest_db); + return 0; + } + /* a voice out of earshot moves on as one heard does: once it is audible + again, both play the same */ + CASE("silent-voice") + { + double *signal = noise(60000); + short *samples = malloc(60000 * sizeof(short)); + double *exact = malloc(60000 * sizeof(double)); + float *heard = calloc(40000 * 2, sizeof(float)), *skipped = calloc(40000 * 2, sizeof(float)); + struct sdl_stream *loud, *quiet; + unsigned long n, sizes[] = { 1, 7, 512, 333, 2000, 1024 }; + + quantize(signal, samples, exact, 60000); + for (index = 0; index < 3; index++) + { + unsigned long frequency = index == 0 ? 0 : index == 1 ? 23456 : 70000, done = 0, size = 0; + + loud = voice_new(samples, 60000, 1, 22050, frequency); + quiet = voice_new(samples, 60000, 1, 22050, frequency); + /* the quiet one turned down for a while, at various mix sizes, + then up */ + quiet->volume = 0.0f; + while (done < 20000) + { + unsigned long count = sizes[size++ % 6]; + static float scratch[2000 * 2], send[2000]; + + memset(scratch, 0, sizeof(scratch)); + memset(send, 0, sizeof(send)); + mix_voice(quiet, scratch, send, count); + for (n = 0; n < count * 2; n++) + CHECK(scratch[n] == 0.0f, "a voice turned down was heard"); + memset(scratch, 0, sizeof(scratch)); + mix_voice(loud, scratch, send, count); + done += count; + } + CHECK(quiet->center == loud->center && quiet->phase == loud->phase && + quiet->history_count == loud->history_count && quiet->cursor == loud->cursor, + "played at %lu: turned down, the voice is at %lu + %f, heard at %lu + %f", frequency, + quiet->center, quiet->phase, loud->center, loud->phase); + /* (it fades in over a mix from 0; the loud one does the same) */ + quiet->volume = 1.0f; + loud->current_left = loud->current_right = 0.0f; + render(quiet, skipped, 40000, TRUE); + render(loud, heard, 40000, TRUE); + for (n = 0; n < 40000 * 2; n++) + CHECK(fabsf(heard[n] - skipped[n]) < 1.0e-6f, "played at %lu: frame %lu differs (%f, %f)", + frequency, n / 2, heard[n], skipped[n]); + voice_delete(loud); + voice_delete(quiet); + } + /* a voice that runs out of packets while it is turned down ends as + one heard does */ + loud = voice_new(samples, 3000, 1, 22050, 0); + quiet = voice_new(samples, 3000, 1, 22050, 0); + quiet->volume = 0.0f; + render(loud, heard, 20000, TRUE); + render(quiet, skipped, 20000, TRUE); + CHECK(quiet->center == loud->center && quiet->silence == loud->silence && + quiet->packets[0].finished == loud->packets[0].finished, "turned down, the voice ends at %lu (%lu), " + "heard at %lu (%lu)", quiet->center, quiet->silence, loud->center, loud->silence); + voice_delete(loud); + voice_delete(quiet); + free(heard); + free(skipped); + free(exact); + free(samples); + free(signal); + return 0; + } + /* a mono voice is heard as a stereo one with the same two channels */ + CASE("mono") + { + double *signal = noise(30000); + short *samples = malloc(30000 * sizeof(short)), *pairs = malloc(30000 * 2 * sizeof(short)); + double *exact = malloc(30000 * sizeof(double)); + float *mono = calloc(50000 * 2, sizeof(float)), *stereo = calloc(50000 * 2, sizeof(float)); + unsigned long n; + + quantize(signal, samples, exact, 30000); + for (n = 0; n < 30000; n++) + pairs[n * 2] = pairs[n * 2 + 1] = samples[n]; + for (index = 0; index < sizeof(rates) / sizeof(rates[0]); index++) + { + struct sdl_stream *one = voice_new(samples, 30000, 1, rates[index][0], rates[index][1]); + struct sdl_stream *two = voice_new(pairs, 30000, 2, rates[index][0], rates[index][1]); + + render(one, mono, 50000, TRUE); + render(two, stereo, 50000, TRUE); + for (n = 0; n < 50000 * 2; n++) + CHECK(fabsf(mono[n] - stereo[n]) < 1.0e-6f, "%lu Hz played at %lu: frame %lu differs (%f, %f)", + rates[index][0], rates[index][1], n / 2, mono[n], stereo[n]); + voice_delete(one); + voice_delete(two); + } + free(mono); + free(stereo); + free(exact); + free(pairs); + free(samples); + free(signal); + return 0; + } + CASE("metrics") + { + static const double tones[] = { 100, 1000, 5000, 8000, 10000, 10500, 15000, 18000, 20000, 21000 }; + double *signal = noise(30000), *impulse = calloc(4000, sizeof(double)); + + impulse[2000] = 1.0; + for (index = 0; index < sizeof(rates) / sizeof(rates[0]); index++) + { + unsigned long rate = rates[index][0], frequency = rates[index][1], tone; + double error_db, peak_db, impulse_db, impulse_peak_db; + + against_reference(signal, 30000, rate, frequency, &error_db, &peak_db); + against_reference(impulse, 4000, rate, frequency, &impulse_db, &impulse_peak_db); + printf("%lu Hz played at %lu: against the exact low pass, noise %.1f dB (peak %.1f), impulse peak %.1f dB\n", + rate, frequency ? frequency : rate, error_db, peak_db, impulse_peak_db); + for (tone = 0; tone < sizeof(tones) / sizeof(tones[0]); tone++) + { + double gain_db, rest_db; + + if (tones[tone] >= 0.5 * rate) + continue; + sine_figures(tones[tone], rate, frequency, &gain_db, &rest_db); + printf(" %6.0f Hz: gain %+.3f dB, the rest %.1f dB\n", tones[tone], gain_db, rest_db); + } + if (!frequency) + printf(" sweep to 0.9 of its band: %.1f dB from ideal\n", sweep_figure(rate, 0.45 * rate)); + } + free(signal); + free(impulse); + return 0; + } + /* noise, mono and stereo, at each rate through the mixer: the output as + raw floats on stdout, to compare one mixer with another */ + CASE("render") + { + static float mixed[CHUNK * 2]; + double *signal = noise(60000); + short *samples = malloc(30000 * 2 * sizeof(short)); + double *exact = malloc(60000 * sizeof(double)); + float *output = calloc(40000 * 2, sizeof(float)); + + quantize(signal, samples, exact, 60000); + for (index = 0; index < sizeof(rates) / sizeof(rates[0]); index++) + { + unsigned long channels; + + for (channels = 1; channels <= 2; channels++) + { + struct sdl_stream *stream = voice_new(samples, 30000, channels, rates[index][0], rates[index][1]); + + render(stream, output, 40000, TRUE); + fwrite(output, sizeof(float), 40000 * 2, stdout); + voice_delete(stream); + } + } + /* and a scene's whole mix, reverb and limiter, its voices out of + earshot coming into it halfway */ + scene(24, 8, TRUE, 3.0); + for (index = 0; index < 2 * OUTPUT_RATE / CHUNK; index++) + { + if (index == OUTPUT_RATE / CHUNK) + scene_approach(); + mix(mixed, CHUNK); + fwrite(mixed, sizeof(float), CHUNK * 2, stdout); + } + return 0; + } + CASE("bench") + { + bench(argc > 2 ? strtoul(argv[2], NULL, 0) : 24, argc > 3 ? strtoul(argv[3], NULL, 0) : 16, + argc > 4 && !strcmp(argv[4], "indoor"), 10.0); + return 0; + } + fprintf(stderr, "no case %s\n", case_name); + return 2; +} diff --git a/tools/harness/tests/profile_keyboard.c b/tools/harness/tests/profile_keyboard.c new file mode 100644 index 00000000..28fbfd4b --- /dev/null +++ b/tools/harness/tests/profile_keyboard.c @@ -0,0 +1,135 @@ +/* Real virtual keyboard exits and keyboard mapping over an in-memory name. */ +#include "harness.h" +#include +typedef unsigned short word; +typedef int BOOL; +typedef unsigned char BYTE; +typedef short SHORT; +#include "config.inc" + +struct platform_input_state { unsigned char keys[TEST_SCANCODE_COUNT], mouse_buttons[8]; BOOL mouse_released, menus; }; +#define SDL_SCANCODE_COUNT TEST_SCANCODE_COUNT +#define console_is_active() FALSE +#define platform_screen_keyboard(show, password) ((void)0) +typedef struct { unsigned short wButtons; BYTE bAnalogButtons[8]; SHORT sThumbLX, sThumbLY; } XINPUT_GAMEPAD; +#define SDL_BUTTON_X1 4 +static BOOL text_typing, text_typing_enter_armed, text_typing_keyboard, text_typing_field; +static struct { boolean active, shift_active, caps_active, symbols_active; + void *keyboard; short row, column; word buffer_size; short last_event, last_key, + number_of_event_repeats, caption_index; boolean last_exit_saved_text, first_key_replaces_buffer; + wchar_t *text_buffer, *cursor; unsigned long time_of_last_event; + long caret_bitmap_index; wchar_t saved_text[MAXIMUM_VIRTUAL_KEYBOARD_SAVED_TEXT_LENGTH]; +} virtual_keyboard_globals; +static boolean unique = TRUE, clean = TRUE, keyboard_available = TRUE; +static int processed, errors; +#define VIRTUAL_KEYBOARD_TAG 1 +#define BITMAP_GROUP_TAG 2 +#define tag_loaded(group, name) (keyboard_available ? 0 : NONE) +#define virtual_keyboard_definition_get(index) ((void *)1) +#define error(...) ((void)0) +#define _error_already_a_saved_game_file_with_that_name 1 +#define _error_cannot_create_saved_game_file_with_empty_name 2 +#define display_error(...) (++errors) +#define player_name_clean(buffer, count) (clean) +#define saved_game_file_name_unique(buffer) (unique) +#define event_manager_flush() ((void)0) +#define ui_play_audio_feedback_sound(sound) ((void)0) +#define system_milliseconds() 0UL +#define ustrlen wcslen +#define ustrncpy wcsncpy +#define ustrcmp wcscmp +#define ustrcpy wcscpy +#define csmemset memset +#define csmemmove memmove +#define MIN(a,b) ((a)<(b)?(a):(b)) +#define virtual_keyboard_backspace() ((void)0) +#define virtual_keyboard_free_space_in_text_buffer() 0 +#define virtual_keyboard_get_current_character() L'a' +static void virtual_keyboard_process_internal(void) { ++processed; } +static boolean virtual_keyboard_cancel(void); +#include "under_test.inc" + +static void launch(wchar_t *name) +{ + CHECK(virtual_keyboard_initialize(), "keyboard initialization failed"); + CHECK(virtual_keyboard_launch(name, 12 * sizeof(wchar_t), 8), "keyboard launch failed"); + CHECK(text_typing, "launch did not immediately enable typing"); + /* The old code enabled typing only when the active keyboard processed. */ + virtual_keyboard_process(); + CHECK(text_typing && virtual_keyboard_globals.active, "keyboard did not own typing"); + struct platform_input_state input = {0}; XINPUT_GAMEPAD pad = {0}; + keyboard_gamepad(&input, &pad); /* let go of the Enter that opened it */ + input.keys[SDL_SCANCODE_RETURN] = 1; + keyboard_gamepad(&input, &pad); + CHECK(pad.wButtons & XINPUT_GAMEPAD_START, "typing Enter stopped choosing Done"); + CHECK(!pad.bAnalogButtons[XINPUT_GAMEPAD_A], "typing Enter selected a character"); +} + +static void ordinary_input(void) +{ + struct platform_input_state input = {0}; XINPUT_GAMEPAD pad = {0}; + CHECK(!virtual_keyboard_globals.active, "keyboard remained active"); + CHECK(!text_typing, "closed keyboard left typing mode enabled"); + input.keys[SDL_SCANCODE_RETURN] = 1; + input.keys[SDL_SCANCODE_W] = 1; + keyboard_gamepad(&input, &pad); + CHECK(pad.bAnalogButtons[XINPUT_GAMEPAD_A] && !(pad.wButtons & XINPUT_GAMEPAD_START), + "menu Enter was not A after closing keyboard"); + CHECK(pad.sThumbLY == 32767, "keyboard movement still suppressed"); + memset(&pad, 0, sizeof(pad)); input.keys[SDL_SCANCODE_RETURN] = 0; + input.keys[SDL_SCANCODE_KP_ENTER] = 1; + keyboard_gamepad(&input, &pad); + CHECK(pad.bAnalogButtons[XINPUT_GAMEPAD_A] && !(pad.wButtons & XINPUT_GAMEPAD_START), + "numpad Enter was not A"); +} + +int main(int argc, char **argv) +{ + const char *case_name = argc > 1 ? argv[1] : ""; + wchar_t name[12] = L"Probe"; + launch(name); + CASE("done") { virtual_keyboard_select(); CHECK(virtual_keyboard_globals.last_exit_saved_text, "Done lost the name"); } + else CASE("cancel") { wcscpy(name, L"Changed"); virtual_keyboard_cancel(); CHECK(!wcscmp(name,L"Probe"), "cancel did not restore name"); } + else CASE("external-close") { virtual_keyboard_close(); } + else CASE("dispose") { virtual_keyboard_dispose(); } + else CASE("initialize") { virtual_keyboard_initialize(); } + else CASE("invalid-name") { clean = FALSE; virtual_keyboard_select(); CHECK(errors==1 && !virtual_keyboard_globals.last_exit_saved_text, "invalid name accepted"); } + else CASE("duplicate-name") { unique = FALSE; wcscpy(name,L"Other"); virtual_keyboard_select(); CHECK(errors==1 && !virtual_keyboard_globals.last_exit_saved_text, "duplicate name accepted"); } + else CASE("field-owner") + { + platform_text_field(TRUE, FALSE); virtual_keyboard_select(); + CHECK(!virtual_keyboard_globals.active && text_typing, "closing keyboard cleared text field ownership"); + platform_text_field(FALSE, FALSE); + } + else CASE("reopen") + { + for (int i=0; i<20; ++i) { virtual_keyboard_select(); ordinary_input(); launch(name); } + virtual_keyboard_cancel(); + } + else CASE("launch-failure") + { + virtual_keyboard_dispose(); keyboard_available=FALSE; virtual_keyboard_initialize(); + CHECK(!virtual_keyboard_launch(name,sizeof(name),8), "missing keyboard launched"); + } + else CASE("held-enter") + { + struct platform_input_state input = { .menus = TRUE }; XINPUT_GAMEPAD pad = {0}; + keys_held_over_switch(&input); + input.keys[SDL_SCANCODE_RETURN] = 1; + keys_held_over_switch(&input); keyboard_gamepad(&input,&pad); + CHECK(pad.wButtons & XINPUT_GAMEPAD_START, "Enter did not accept keyboard"); + virtual_keyboard_select(); + memset(&pad,0,sizeof(pad)); + keys_held_over_switch(&input); keyboard_gamepad(&input,&pad); + CHECK(!pad.wButtons && !pad.bAnalogButtons[XINPUT_GAMEPAD_A], "held Done also activated the next menu"); + input.keys[SDL_SCANCODE_RETURN] = 0; keys_held_over_switch(&input); + input.keys[SDL_SCANCODE_RETURN] = 1; keys_held_over_switch(&input); + keyboard_gamepad(&input,&pad); + CHECK(pad.bAnalogButtons[XINPUT_GAMEPAD_A], "fresh Enter was still suppressed"); + } + else CHECK(FALSE, "unknown case"); + ordinary_input(); + int before = processed; virtual_keyboard_process(); + CHECK(before == processed, "inactive keyboard processed input"); + return 0; +} diff --git a/tools/harness/tests/server_teardown.c b/tools/harness/tests/server_teardown.c new file mode 100644 index 00000000..20f26d08 --- /dev/null +++ b/tools/harness/tests/server_teardown.c @@ -0,0 +1,151 @@ +/* Actual server disposal and menu callbacks; the network/transport are ordered + recorders. No sockets, assets, user profile or wall-clock sleeps. */ +#include "harness.h" +#include "config.inc" +#define NETWORK_SERVER_MANAGER_FILE "server" +#define _error_silent 0 +#define csmemset memset +#define SET_FLAG(flags, bit, value) ((value) ? ((flags) |= FLAG(bit)) : ((flags) &= ~FLAG(bit))) + +struct network_connection { int unused; }; +struct network_game_server +{ + short state; + unsigned flags; + struct network_connection *connection; + unsigned long time_of_first_client_loading_completion; + boolean sent_start_game_message; +}; +struct network_game_client { int unused; }; +struct network_message { int unused; }; +struct widget_instance { int unused; }; +struct event_record { int unused; }; +static struct +{ + struct network_game_server *server; + struct network_game_client *client; + boolean quickstart_local, client_started; +} bss_004566dc; +static char network_game_server_cooperative_next_map[128]; +static boolean network_game_server_memory_do_not_use_directly_in_use; +static struct network_message message; +static struct network_game_server *disposing; +static char order[64]; +static int slept, messages, broadcasts, handled, closed, terminated, errors; +static int new_servers, clients_closed, joins_cleared, variants_cleared; +static short expected_message; +static boolean message_fails, send_fails, machines_fail; + +static void note(char event) +{ + size_t length = strlen(order); + CHECK(length + 1 < sizeof(order), "event recorder overflow"); + order[length] = event; order[length + 1] = 0; +} +static void network_event(const char *text, ...) { } +static void error(int level, const char *text, ...) { errors++; } +static struct network_message *create_network_game_message(short kind, const void *packet, long size) +{ + CHECK(kind == expected_message && size == sizeof(long) && *(const long *)packet == 0, + "graceful-exit packet changed or contained uninitialized data"); + messages++; note('M'); + return message_fails ? NULL : &message; +} +static boolean network_game_server_send_message_to_all_machines(struct network_game_server *server, struct network_message *packet) +{ + CHECK(server == disposing && packet == &message && !closed && !terminated, "exit notice sent after cleanup"); + broadcasts++; note('B'); return !send_fails; +} +static boolean network_game_server_handle_client_machines(struct network_game_server *server) +{ + CHECK(server == disposing && !server->time_of_first_client_loading_completion && !server->sent_start_game_message && + !TEST_FLAG(server->flags, _network_game_server_game_open_bit), "teardown could start another game or accept clients"); + CHECK(!network_game_server_cooperative_next_map[0], "next co-op map survived teardown"); + handled++; note('H'); return !machines_fail; +} +static void network_connection_delete(struct network_connection *connection) +{ + CHECK(connection == disposing->connection && handled == 1 && !terminated, "socket cleanup order changed"); + closed++; note('C'); +} +static void SleepEx(unsigned long milliseconds, boolean alertable) +{ + CHECK(closed == 1 && !terminated && !alertable, "legacy wait moved before connection cleanup"); + slept += milliseconds; note('S'); +} +static short transport_server_terminate(void) +{ + CHECK(closed == 1 && slept == EXPECTED_SLEEP, "transport terminated before cleanup/required legacy grace"); + terminated++; note('T'); return 0; +} +static void p2p_set_game_player_counts(int count, int maximum) +{ + struct network_game_server empty = { 0 }; + CHECK(count == 0 && maximum == 0 && terminated == 1 && + !network_game_server_memory_do_not_use_directly_in_use && !memcmp(disposing, &empty, sizeof(empty)), + "server state/counts not cleared after transport cleanup"); + note('P'); +} +static void network_game_client_dispose(struct network_game_client *client) +{ + CHECK(client == bss_004566dc.client && !messages && !handled, "local client disposal order changed"); + clients_closed++; note('L'); +} +static void player_ui_clear_multiplayer_joins(void) { joins_cleared++; note('J'); } +static void player_ui_clear_multiplayer_variant(void) { variants_cleared++; note('V'); } +static boolean network_game_start_new_server(struct widget_instance *widget, struct event_record *event, boolean *deleted) +{ + CHECK(!bss_004566dc.client && !bss_004566dc.server, "new host started before old host/client disposed"); + new_servers++; note('N'); return TRUE; +} + +#include "under_test.inc" + +int main(int argc, char **argv) +{ + const char *case_name = argc > 1 ? argv[1] : ""; + struct network_connection connection = { 0 }; + struct network_game_client client = { 0 }; + struct network_game_server host = { _network_game_server_state_pregame, + FLAG(_network_game_server_game_open_bit) | FLAG(_network_game_server_game_valid_bit), &connection, 123, TRUE }; + boolean deleted = FALSE; + disposing = &host; + bss_004566dc.server = &host; bss_004566dc.client = &client; + bss_004566dc.quickstart_local = bss_004566dc.client_started = TRUE; + network_game_server_memory_do_not_use_directly_in_use = TRUE; + strcpy(network_game_server_cooperative_next_map, "next-level"); + expected_message = _message_server_graceful_game_exit_pregame; + boolean back = !strcmp(case_name, "back"), reopen = !strcmp(case_name, "reopen"); + CASE("postgame") { host.state = _network_game_server_state_postgame; expected_message = _message_server_graceful_game_exit_postgame; } + CASE("ingame") { host.state = _network_game_server_state_ingame; } + CASE("message-failure") { message_fails = TRUE; } + CASE("send-failure") { send_fails = TRUE; } + CASE("machine-failure") { machines_fail = TRUE; } + CASE("no-server") + { + bss_004566dc.server = NULL; + CHECK(ui_widget_port_host(NULL, NULL, &deleted), "new host failed"); + CHECK(!slept && !closed && !handled && !terminated && clients_closed == 1 && new_servers == 1, + "first host creation performed nonexistent server teardown"); + return 0; + } + if (back) CHECK(clear_multiplayer_player_joins(NULL, NULL, &deleted), "back callback failed"); + else if (reopen) CHECK(ui_widget_port_host(NULL, NULL, &deleted), "reopen callback failed"); + else network_game_server_dispose(&host); + CHECK(slept == EXPECTED_SLEEP && closed == 1 && handled == 1 && terminated == 1, "wrong sleep or incomplete teardown: %s", order); + CHECK(errors == (machines_fail ? 1 : 0), "error handling changed"); + int expected_messages = strcmp(case_name, "ingame") ? 1 : 0; + CHECK(messages == expected_messages && broadcasts == (message_fails ? 0 : expected_messages), "graceful-exit notice lost"); + char expected[64] = ""; + if (back || reopen) strcat(expected, "L"); + if (expected_messages) strcat(expected, message_fails ? "M" : "MB"); + strcat(expected, EXPECTED_SLEEP ? "HCSTP" : "HCTP"); + if (back) strcat(expected, "JV"); + if (reopen) strcat(expected, "N"); + CHECK(!strcmp(order, expected), "cleanup sequence changed: %s != %s", order, expected); + if (back || reopen) + CHECK(!bss_004566dc.server && !bss_004566dc.client && !bss_004566dc.quickstart_local && !bss_004566dc.client_started && + clients_closed == 1 && new_servers == reopen && joins_cleared == back && variants_cleared == back, + "menu lifecycle state changed"); + return 0; +} diff --git a/tools/harness/tests/sound_lifecycle.c b/tools/harness/tests/sound_lifecycle.c new file mode 100644 index 00000000..cabbfd97 --- /dev/null +++ b/tools/harness/tests/sound_lifecycle.c @@ -0,0 +1,132 @@ +#include "harness.h" +#include +#include "types.inc" +#define NUMBEROF(a) (sizeof(a)/sizeof((a)[0])) +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define PIN(v,a,b) ((v)<(a)?(a):((v)>(b)?(b):(v))) +#define SET_FLAG(v,b,s) ((v)=(s)?((v)|FLAG(b)):((v)&~FLAG(b))) +#define match_vassert(...) ((void)0) +#define TAG_BLOCK_GET_ELEMENT(b,i,t) (&((t *)(b)->address)[i]) +static struct sound_manager_globals sound_manager_globals; +static struct data_array *sound_data, *looping_sound_data; +static struct looping_sound_datum loops[2]; +static struct looping_sound_track tracks[4]; +static struct looping_sound_definition test_loop_definition; +static struct sound_definition test_definition; +static struct sound_pitch_range range; +static struct sound_permutation permutation; +static boolean loud_dialog_hack; +static struct sound_datum *checked_sound(long index) { + struct sound_datum *sound=datum_get(sound_data,index); + CHECK(index!=NONE && DATUM_INDEX_TO_ABSOLUTE_INDEX(index)maximum_count && sound->identifier,"live voice"); + return sound; +} +#define sound_get(i) checked_sound(i) +#define looping_sound_get(i) (&loops[i]) +#define looping_sound_definition_get(...) (&test_loop_definition) +#define sound_definition_get(...) (&test_definition) +#define sound_permutation_get(...) (&permutation) +#define sound_definition_is_playable(...) TRUE +#define sound_is_active() TRUE +#define error(...) abort() +#define sound_definition_get_maximum_distance(...) 100.f +#define source_audible(...) 0 +#define real_seed_random_range(...) 1.f +#define sound_definition_find_pitch_range_by_pitch(...) 0 +#define sound_definition_next_permutation(...) 0 +#define sound_scale_value(a,...) (a) +#define sound_scale_random_value(...) 1.f +#define real_seed_random(...) 1.f +#define game_time_get() 0 +#define sound_definition_promote(...) _sound_promotion_dont +#define sound_travel_milliseconds(...) 0 +#define source_distance(...) 0.f +#define valid_real_normal3d(...) TRUE +#define render_debug_looping_sound(...) ((void)0) +#define player_effect_continuous_refresh(...) ((void)0) +#define csmemcpy memcpy +static int _sound_cache_sound_request(struct sound_permutation *p,int a,int b,int c) { + (void)p;(void)a;(void)b;(void)c;return TRUE; +} +static boolean track_loop_track_sound(long i,void const *d,struct sound_source *s) { + (void)i;(void)d;(void)s;return TRUE; +} +static long data_next_index(struct data_array *data,long previous) { + for(long i=previous==NONE?0:DATUM_INDEX_TO_ABSOLUTE_INDEX(previous)+1;imaximum_count;i++) { + struct datum_header *header=(void *)(data->data+i*data->size); + if(header->identifier) return ((long)(unsigned short)header->identifier<<16)|i; + } + return NONE; +} +static long looping_sound_find(long identifier) { (void)identifier;return loops[0].identifier?0:NONE; } +static long looping_sound_new(long definition_index,long identifier,struct sound_source const *source) { + (void)identifier; memset(&loops[0],0,sizeof(loops[0]));loops[0].identifier=1; + loops[0].definition_index=definition_index;loops[0].source=*source;return 0; +} +static void sound_set_definition_begin(long i,long d) { sound_get(i)->next_definition_index=d; } +#include "under_test.inc" +static void reset(void) { + sound_data=game_state_data_new("sounds",64,sizeof(struct sound_datum)); + looping_sound_data=game_state_data_new("loops",2,sizeof(struct looping_sound_datum)); + memset(loops,0,sizeof(loops));memset(tracks,0,sizeof(tracks));memset(&test_loop_definition,0,sizeof(test_loop_definition)); + test_loop_definition.tracks.count=1;test_loop_definition.tracks.address=tracks; + test_loop_definition.continuous_damage_effect.index=NONE; + for(int i=0;i<4;i++) { + tracks[i].start_sound.index=1;tracks[i].loop_sound.index=2;tracks[i].stop_sound.index=NONE; + tracks[i].alternate_loop_sound.index=tracks[i].alternate_stop_sound.index=NONE; + tracks[i].flags=FLAG(_fade_out_at_stop_bit);tracks[i].fade_out_duration=.1f; + } + sound_manager_globals.render_time=1000; + sound_manager_globals.initialized=sound_manager_globals.enabled=TRUE; + test_definition.compression=_sound_compression_xbox_adpcm;test_definition.encoding=_sound_encoding_mono; +} +static void advance(int hz,int milliseconds) { + long start=sound_manager_globals.render_time; + for(int frame=1;frame<=hz*milliseconds/1000+1;frame++) { + sound_manager_globals.render_time=start+frame*1000/hz; + for(long i=data_next_index(sound_data,NONE);i!=NONE;i=data_next_index(sound_data,i)) { + struct sound_datum *sound=sound_get(i); + if(sound_calculate_fade(i)==0.f) { + loops[sound->source_identifier].component_sound_count--; + if(loops[sound->source_identifier].tracks[sound->loop_track_index].primary_sound_index==i) + loops[sound->source_identifier].tracks[sound->loop_track_index].primary_sound_index=NONE; + datum_delete(sound_data,i); + } + } + } +} +static void refresh(int state) { struct sound_source source={.scale=1.f,.gain=1.f};sound_refresh_looping(0,123,&source,state,FALSE,0.f); } +static int live(long i) { return ((struct sound_datum *)datum_get(sound_data,i))->identifier!=0; } +int main(int argc,char **argv) { + CHECK(argc==2,"case name");char *split=strchr(argv[1],':');CHECK(split,"frame rate");*split='\0'; + const char *case_name=argv[1];int hz=atoi(split+1);reset(); + CASE("fresh-impulse") { + struct sound_source source={.scale=1.f,.gain=1.f};long i=sound_new_impulse(0,&source,0,NULL,NULL,0); + CHECK(i!=NONE && sound_calculate_fade(i)==1.f,"new impulse audible");return 0; + } + if(!strcmp(case_name,"four-tracks")) test_loop_definition.tracks.count=4; + refresh(_looping_sound_refresh_start);long primary=loops[0].tracks[0].primary_sound_index,secondary=NONE; + CASE("fresh-loop") { CHECK(sound_calculate_fade(primary)==1.f,"new loop audible");return 0; } + CASE("pending") { secondary=update_potentially_audible_looping_sound(2,0,0,_sound_loop_track); } + CASE("restart") { refresh(_looping_sound_refresh_start); } + CASE("intro-loop") { + tracks[0].flags|=FLAG(_fade_in_at_start_bit);tracks[0].fade_in_duration=.2f; + refresh(_looping_sound_refresh_start); + } + CASE("rapid") { + advance(hz,34);refresh(_looping_sound_refresh_stop);advance(hz,34); + refresh(_looping_sound_refresh_start);advance(hz,34); + } + CASE("other-owner") { loops[1].source=loops[0].source;secondary=update_potentially_audible_looping_sound(2,1,0,_sound_loop_track); } + CASE("other-track") { secondary=update_potentially_audible_looping_sound(2,0,1,_sound_loop_track); } + CASE("stop-cue") { tracks[0].stop_sound.index=3; } + CASE("fake-impulse") { test_loop_definition.flags=FLAG(_looping_sound_fake_impulse_sound_bit);tracks[0].flags=0; } + refresh(_looping_sound_refresh_stop); + CASE("repeated-stop") { for(int frame=0;frame<5;frame++) { advance(hz,20);refresh(_looping_sound_refresh_stop); } } + advance(hz,300); + CASE("other-owner") { CHECK(!live(primary) && live(secondary),"other owner survives");return 0; } + CASE("other-track") { CHECK(!live(primary) && live(secondary),"other track survives");return 0; } + CASE("stop-cue") { CHECK(!live(primary) && loops[0].component_sound_count==1,"authored stop cue survives");return 0; } + CASE("fake-impulse") { CHECK(live(primary),"ordered impulse preserves authored ending");return 0; } + CHECK(loops[0].component_sound_count==0,"all owned start/loop voices retire within their fade duration");return 0; +} diff --git a/tools/harness/tests/sound_stream_flush.c b/tools/harness/tests/sound_stream_flush.c new file mode 100644 index 00000000..fc051eec --- /dev/null +++ b/tools/harness/tests/sound_stream_flush.c @@ -0,0 +1,75 @@ +/* Both sides of the cancellation boundary use their actual structures/functions. */ +#include "harness.h" +#include "types.inc" +static struct sdl_stream stream; +static struct sdl_stream *streams=&stream; +static struct dsound_globals dsound_globals; +static int refills,unlocks,events,resets,lock_depth; +static unsigned long status[8],completed[8]; +#define mixer_lock 0 +#define pthread_mutex_lock(...) ((void)lock_depth++) +#define pthread_mutex_unlock(...) ((void)lock_depth--) +#define channel_get(i) (&dsound_globals.channels[i]) +#define sound_cache_sound_hardware_unlock(...) ((void)unlocks++) +#define interrupt_time_error(...) CHECK(FALSE,"invalid completion") +#define SetEvent(...) ((void)events++) +#define resampler_reset(...) ((void)resets++) +static void dsound_channel_fill(short i) { + (void)i; refills++; + CHECK(refills<10,"a flush repeatedly refills itself"); + stream.packet_count++; dsound_globals.channels[0].packet_count++; +} +static HRESULT stream_flush(IDirectSoundStream *object); +#define DirectSoundStopStream(s) ((void)stream_flush(s)) +/* The real callback's Xbox pointer-sized context is cast to a short channel number. */ +#pragma clang diagnostic ignored "-Wvoid-pointer-to-int-cast" +#include "under_test.inc" +static void callback(void *context,void *packet,DWORD result) { + CHECK(lock_depth==0,"callback runs without the mixer lock"); + dsound_channel_callback(context,packet,result); +} +static void reset(int count,int finished,int wrapped) { + memset(&stream,0,sizeof(stream)); memset(&dsound_globals,0,sizeof(dsound_globals)); + stream.callback=callback; stream.packet_head=wrapped?MAXIMUM_STREAM_PACKETS-2:0; + stream.packet_count=count; stream.cursor=71; + dsound_globals.actual_channel_count=1; + struct sound_channel *channel=channel_get(0); + channel->stream=&stream.object; channel->state=_sound_channel_playing; channel->packet_count=count; + channel->playing_permutation=channel->queued_permutation=(struct sound_permutation *)&stream; + for(int i=0;ifinished=finished==2 || (finished==1 && i==0); + p->samples=malloc(16); p->packet.dwMaxSize=512; + status[i]=XMEDIAPACKET_STATUS_PENDING; completed[i]=999; + p->packet.pdwStatus=&status[i]; p->packet.pdwCompletedSize=&completed[i]; + p->packet.pContext=(void *)(long)(i+1); + } + refills=unlocks=events=resets=lock_depth=0; +} +int main(int argc,char **argv) { + CHECK(argc==2,"case name"); const char *case_name=argv[1]; + int count=!strcmp(case_name,"empty")?0:3; + int finished=!strcmp(case_name,"unfinished")?0:(!strcmp(case_name,"all-finished")?2:1); + reset(count,finished,!strcmp(case_name,"wrapped")); + CASE("ordinary") { + streams_complete_finished(); + CHECK(status[0]==XMEDIAPACKET_STATUS_SUCCESS && completed[0]==512,"ordinary completion succeeds"); + CHECK(refills==1 && unlocks==1 && stream.packet_count==3,"ordinary completion refills"); + stream_flush(&stream.object); return 0; + } + CASE("paused") { dsound_globals.paused=TRUE; } + CASE("no-callback") { stream.callback=NULL; } + channel_stop(0); + CHECK(stream.packet_count==0 && stream.cursor==0 && !lock_depth,"flush empties stream and resets cursor"); + CHECK(refills==0 && resets==1,"cancellation never refills and resets resampling"); + for(int i=0;i +#include "types.inc" +#define SET_FLAG(v,b,s) ((v)=(s)?((v)|FLAG(b)):((v)&~FLAG(b))) +#define TAG_BLOCK_GET_ELEMENT(b,i,t) (&((t *)(b)->address)[i]) +static struct sound_datum sounds[2]; +static struct sound_channel_datum channels[2]; +static struct sound_definition test_definition; +static struct sound_pitch_range range; +static struct sound_permutation permutation; +static struct looping_sound_track track; +static struct looping_sound_definition loop_definition; +static struct looping_sound_datum loop; +static struct sound_class_definition sound_class; +static struct sound_manager_globals sound_manager_globals; +static int queues, property_writes, updates; +static void channel_update(short i) { (void)i; updates++; } +static struct sound_platform_definition platform = {.channel_update=channel_update}; +static real sound_pitch_range_fade_time; +static struct sound_datum *checked_sound(long i) { + CHECK(i>=0 && i<2 && sounds[i].identifier, "access to a retired voice"); return &sounds[i]; +} +#define sound_get(i) checked_sound(i) +#define channel_get(i) (&channels[i]) +#define sound_definition_get(...) (&test_definition) +#define looping_sound_get(...) (&loop) +#define looping_sound_definition_get(...) (&loop_definition) +#define sound_class_get(...) (&sound_class) +#define sound_valid_for_channel(...) TRUE +#define source_distance_squared(...) 0.f +#define sound_definition_find_pitch_range_by_pitch(...) 0 +#define sound_definition_next_permutation(...) 0 +#define sound_scale_value(a,...) (a) +#define sound_definition_get_minimum_distance(...) 0.f +#define sound_manager_master_gain(...) 1.f +#define limit_pitch(a,...) (a) +#define sound_cache_sound_loaded(...) TRUE +#define _sound_cache_sound_request(...) TRUE +#define channel_get_state(...) _sound_channel_playing +#define channel_set_properties_hardware(...) ((void)property_writes++) +#define channel_queue_sound(...) ((void)queues++) +#define update_potentially_audible_looping_sound(...) (abort(), (long)NONE) +#define sound_start_fade(...) abort() +static void sound_stop(long i) { + sounds[i].identifier=0; channels[i].sound_index=NONE; sounds[i].playing_channel_index=NONE; +} +#include "under_test.inc" +static void reset(void) { + memset(sounds,0,sizeof(sounds)); memset(channels,0,sizeof(channels)); memset(&test_definition,0,sizeof(test_definition)); + sounds[0].identifier=sounds[1].identifier=1; + sounds[0].playing_channel_index=0; sounds[1].playing_channel_index=1; + sounds[0].type=_sound_start_track; sounds[0].next_definition_index=1; sounds[0].pitch=1; + sounds[1].definition_index=1; sounds[1].start_time=2000; + sounds[0].source.gain=1; channels[0].sound_index=0; channels[1].sound_index=1; + channels[0].playing_permutation=&permutation; permutation.next_permutation_index=NONE; permutation.gain=1; + range.permutations.address=&permutation; range.permutations.count=1; range.playback_rate=range.natural_pitch=1; + test_definition.pitch_ranges.address=⦥ test_definition.pitch_ranges.count=1; test_definition.gain_modifier=1; + loop_definition.tracks.address=&track; loop_definition.tracks.count=1; track.gain=1; + loop.tracks[0].primary_sound_index=0; + sound_manager_globals.platform_definition=&platform; sound_manager_globals.channel_count=2; + sound_manager_globals.render_time=1000; + sound_class.maximum_number_per_object=1; sound_class.maximum_number_per_definition=4; + queues=property_writes=updates=0; +} +int main(int argc,char **argv) { + CHECK(argc==2,"case name"); const char *case_name=argv[1]; reset(); + CASE("no-channel") { + sounds[0].playing_channel_index=NONE; CHECK(sound_set_definition_end(0),"channel-less voice survives"); + CHECK(sounds[0].identifier && !queues && !property_writes && !updates,"no hardware writes"); return 0; + } + CASE("other-victim") { sounds[1].start_time=0; } + CASE("no-limit") { sounds[1].definition_index=3; } + CASE("definition-limit") { + sounds[1].source_identifier=1; sound_class.maximum_number_per_object=2; + sound_class.maximum_number_per_definition=1; + } + update_channel_for_looping_sound(0,1.f); + CASE("self-retirement") { + CHECK(!sounds[0].identifier && channels[0].sound_index==NONE,"self retired"); + CHECK(!queues && !property_writes && !updates,"retired voice must never refill"); return 0; + } + CASE("definition-limit") { + CHECK(!sounds[0].identifier && !queues && !property_writes && !updates,"test_definition cap retires voice"); return 0; + } + CASE("other-victim") { + CHECK(sounds[0].identifier && !sounds[1].identifier,"only other voice retired"); + CHECK(queues==1 && property_writes==1 && updates==1,"survivor still plays"); return 0; + } + CASE("no-limit") { + CHECK(sounds[0].identifier && sounds[1].identifier,"summary excludes current voice"); + CHECK(queues==1 && property_writes==1 && updates==1,"ordinary transition still plays"); return 0; + } + CHECK(FALSE,"unknown case"); +} diff --git a/tools/harness/tests/test_mixer.py b/tools/harness/tests/test_mixer.py new file mode 100644 index 00000000..63d95c45 --- /dev/null +++ b/tools/harness/tests/test_mixer.py @@ -0,0 +1,81 @@ +"""The real mixer of dsound_sdl.c on known signals (mixer.c): its resampler against the same low pass worked out in +double precision, sines through it as the ear hears them, voices out of earshot and mono voices. + + python tools/harness/tests/test_mixer.py metrics the figures + python tools/harness/tests/test_mixer.py bench [voices] [out of earshot] [indoor] a game's worth of voices, timed + python tools/harness/tests/test_mixer.py render > out.raw noise through the mixer, to compare with another's +(MIXER_SOURCE=path: of another dsound_sdl.c; MIXER_SCALAR=1: its resampler adding tap by tap) +""" + +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, mutated, read, run # noqa: E402 + +SOURCE = "port/linux/src/dsound_sdl.c" +CASES = ["reference", "impulse", "sines", "aliases", "silent-voice", "mono"] + +# a fault in the mixer, and the case that must catch it +NEGATIVE_CONTROLS = { + "taps-off-by-one": (("(stream->center + 1 - RESAMPLER_ZERO_CROSSINGS) % RESAMPLER_HISTORY", + "(stream->center + 2 - RESAMPLER_ZERO_CROSSINGS) % RESAMPLER_HISTORY"), "reference"), + "phase-not-blended": (("for (phase = 0; phase <= RESAMPLER_TABLE_STEPS; phase++)", + "for (phase = 0; phase <= RESAMPLER_TABLE_STEPS; phase += 2)"), "impulse"), + "no-stretch": (("#define RESAMPLER_MAXIMUM_STRETCH 2", "#define RESAMPLER_MAXIMUM_STRETCH 1"), "aliases"), +} + + +def generated(source=None, fault=None, scalar=False): + """the mixer: dsound_sdl.c from its constants to the end of the mixing (before the output), its resampler adding + tap by tap if scalar""" + text = source if source is not None else read(SOURCE) + code = text[text.index("#define OUTPUT_RATE"):text.index("/* ---------- output")] + if fault: + code = mutated(code, *fault) + return (("under_test.inc", ("#define RESAMPLER_SCALAR\n" if scalar else "") + code),) + + +def rendered(scalar): + """mixer.c's render: voices at each rate, then a scene's whole mix, as floats""" + import array + import subprocess + + result = subprocess.run([str(build("mixer", generated(scalar=scalar))), "render"], capture_output=True, + timeout=120, check=True) + return array.array("f", result.stdout) + + +@pytest.mark.parametrize("case", CASES) +def test_case(case): + status, output = run(build("mixer", generated()), case) + assert status == 0, output + + +def test_vectors_add_as_taps_do(): + """the resampler four taps at a time is the resampler tap by tap but for the order of its additions: the same + sound to float's rounding, through the whole mix (reverb and limiter)""" + vector, scalar = rendered(False), rendered(True) + assert len(vector) == len(scalar) > 0 + worst = max(abs(a - b) for a, b in zip(vector, scalar)) + assert 0 < worst < 1e-5, f"the vector resampler differs by {worst} (none: no vectors)" + + +@pytest.mark.parametrize("control", NEGATIVE_CONTROLS) +def test_negative_control(control): + fault, case = NEGATIVE_CONTROLS[control] + status, output = run(build("mixer", generated(fault=fault)), case) + assert status == CHECK_FAILED, f"the mixer with a fault ({control}) passed '{case}': the test cannot see it" + + +if __name__ == "__main__": + import os + import subprocess + + # (MIXER_SOURCE: another dsound_sdl.c, to compare; MIXER_SCALAR=1: its resampler tap by tap) + other = os.environ.get("MIXER_SOURCE") + executable = build("mixer", generated(Path(other).read_text(encoding="latin-1") if other else None, + scalar=bool(os.environ.get("MIXER_SCALAR")))) + sys.exit(subprocess.run([str(executable), *(sys.argv[1:] or ["metrics"])]).returncode) diff --git a/tools/harness/tests/test_profile_keyboard.py b/tools/harness/tests/test_profile_keyboard.py new file mode 100644 index 00000000..ec3e4d37 --- /dev/null +++ b/tools/harness/tests/test_profile_keyboard.py @@ -0,0 +1,58 @@ +"""Keyboard lifetime and actual Enter mapping, without maps or profile files.""" +import re +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, enum_with, function, mutated, read, run # noqa: E402 + +CASES = ['done', 'cancel', 'external-close', 'dispose', 'initialize', + 'invalid-name', 'duplicate-name', 'field-owner', 'reopen', 'launch-failure', 'held-enter'] + + +def generated(fault=False): + vk = read('source/interface/virtual_keyboard.c') + inputs = read('port/linux/src/xinput_sdl.c') + config = '\n'.join(enum_with(vk, member) for member in + ['VIRTUAL_KEYBOARD_ROW_COUNT', '_vkey_done', '_ui_audio_feedback_none']) + config += '\n' + '\n'.join(re.findall(r'^#define XINPUT_GAMEPAD_\w+ [^\n]+', + read('port/include/xdk/xdk_xbox.h'), re.M)) + keyboard = function(inputs, 'keyboard_gamepad') + names = sorted(set(re.findall(r'SDL_SCANCODE_\w+', keyboard + function(inputs, 'typing_gamepad')))) + config += '\nenum {' + ','.join(names) + ', TEST_SCANCODE_COUNT};\n' + start = vk.index('static char const virtual_keyboard_layout_table') + config += vk[start:vk.index('\n};', start)+3] + '\n' + names = ['text_typing_update', 'platform_text_typing', 'platform_text_field', + 'analog', 'typing_gamepad', 'keyboard_gamepad', 'keys_held_over_switch'] + code = '\n'.join(function(inputs, name) for name in names) + '\n' + if fault == 'held-enter': + code, count = re.subn(r' \| \(text_typing \? [24] : 0\)', '', code) + assert count == 1 + setter = function(vk, 'virtual_keyboard_set_active') + if fault is True: + setter = mutated(setter, 'platform_text_typing(active);', 'if (active) platform_text_typing(active);') + code += setter + '\n' + code += '\n'.join(function(vk, name) for name in + ['virtual_keyboard_initialize', 'virtual_keyboard_dispose', + 'virtual_keyboard_launch', 'virtual_keyboard_cancel', + 'virtual_keyboard_close', 'virtual_keyboard_select', 'virtual_keyboard_process']) + return (('config.inc', config), ('under_test.inc', code)) + + +@pytest.mark.parametrize('case', CASES) +def test_lifetime(case): + status, output = run(build('profile_keyboard', generated()), case) + assert status == 0, output + + +@pytest.mark.parametrize('case', ['done', 'cancel', 'external-close', 'dispose']) +def test_original_sticky_typing_detected(case): + status, output = run(build('profile_keyboard', generated(True)), case) + assert status == CHECK_FAILED, output + + +def test_held_enter_guard_detected(): + status, output = run(build('profile_keyboard', generated('held-enter')), 'held-enter') + assert status == CHECK_FAILED, output diff --git a/tools/harness/tests/test_server_teardown.py b/tools/harness/tests/test_server_teardown.py new file mode 100644 index 00000000..c4423b16 --- /dev/null +++ b/tools/harness/tests/test_server_teardown.py @@ -0,0 +1,66 @@ +"""Real menu and server teardown, including native/legacy platform guards.""" +import re +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, constant, enum_with, function, mutated, read, run # noqa: E402 + +PLATFORMS = { + 'windows': ('#define HALO_WINDOWS 1', 0), + 'linux': ('#define __linux__ 1', 0), + 'android': ('#define HALO_ANDROID 1', 0), + 'xbox': ('', 1000), + 'other-port': ('#define HALO_UNEXAMINED_PORT 1', 1000), +} +CASES = [(platform, case) for platform in PLATFORMS for case in ('back', 'reopen')] +CASES += [('windows', case) for case in ('postgame', 'ingame', 'message-failure', + 'send-failure', 'machine-failure', 'no-server')] +CASES += [('xbox', case) for case in ('postgame', 'ingame')] +GUARD = '#if !defined(HALO_WINDOWS) && !defined(HALO_ANDROID) && !defined(__linux__)' +CONTROLS = { + 'native-sleeps': (GUARD, '#if 1', 'windows', 'back'), + 'xbox-loses-grace': ('SleepEx(MILLISECONDS_PER_SECOND, FALSE);', ';', 'xbox', 'back'), + 'transport-not-terminated': ('transport_server_terminate();', ';', 'windows', 'back'), + 'connections-not-closed': ('network_connection_delete(server->connection);', ';', 'windows', 'reopen'), + 'server-state-not-cleared': ('csmemset(server, 0, sizeof(*server));', ';', 'windows', 'back'), +} + + +def generated(platform, control=None): + server = read('source/networking/network_server_manager.c') + code = function(server, 'network_game_server_dispose') + if control: + before, after, _, _ = CONTROLS[control] + code = mutated(code, before, after) + globals_source = read('source/networking/network_game_globals.c') + for name in ('dispose_global_network_game_server', 'dispose_global_network_game_client'): + code += '\n' + function(globals_source, name) + widgets = read('source/interface/ui_widget_event_handler_functions.c') + for name in ('clear_multiplayer_player_joins', 'ui_widget_port_host'): + code += '\n' + function(widgets, name) + defines, sleep = PLATFORMS[platform] + # The harness runs on Linux; each compiled variant selects its own target. + config = '#undef __linux__\n#define xbox 1\n' + defines + f'\n#define EXPECTED_SLEEP {sleep}\n' + config += f'#define MILLISECONDS_PER_SECOND {constant(read("source/cseries/cseries.h"), "MILLISECONDS_PER_SECOND")}\n' + for member in ('_network_game_server_state_pregame', '_network_game_server_game_open_bit'): + config += enum_with(server, member) + '\n' + config += enum_with(read('source/networking/network_messages.h'), '_message_server_graceful_game_exit_pregame') + '\n' + for name in ('message_server_graceful_game_exit_pregame', 'message_server_graceful_game_exit_postgame'): + config += re.search(r'struct ' + name + r'\s*\{[^}]+\};', server).group() + '\n' + return (('config.inc', config), ('under_test.inc', code)) + + +@pytest.mark.parametrize('platform,case', CASES) +def test_case(platform, case): + status, output = run(build('server_teardown', generated(platform)), case) + assert status == 0, output + + +@pytest.mark.parametrize('control', CONTROLS) +def test_negative_control(control): + _, _, platform, case = CONTROLS[control] + status, output = run(build('server_teardown', generated(platform, control)), case) + assert status == CHECK_FAILED, f'{control} was not caught: {output}' diff --git a/tools/harness/tests/test_sound_lifecycle.py b/tools/harness/tests/test_sound_lifecycle.py new file mode 100644 index 00000000..0f8676d7 --- /dev/null +++ b/tools/harness/tests/test_sound_lifecycle.py @@ -0,0 +1,55 @@ +"""Production fades and voice creation at multiple frame rates, including negative controls.""" +import re +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, enum_with, function, mutated, read, run +from harness.audio import manager_types + +CASES = ["fresh-loop", "fresh-impulse", "pending", "restart", "intro-loop", + "rapid", "other-owner", "other-track", "stop-cue", "fake-impulse", "repeated-stop", "four-tracks"] +NEGATIVE_CONTROLS = { + "keeps-replaced-voice": (("sound_index != except_sound_index &&", "FALSE &&"), "restart"), + "cancels-other-owner": (("sound->source_identifier == looping_sound_index &&", "TRUE &&"), "other-owner"), + "cancels-other-track": (("sound->loop_track_index == track_index)", "TRUE)"), "other-track"), +} + + +def generated(fault=None, case=None): + manager = read("source/sound/sound_manager.c") + names = ["update_potentially_audible_looping_sound", "sound_new_impulse", "sound_calculate_fade", + "sound_start_fade", "sound_fade_looping_track_components", "sound_refresh_looping"] + code = [] + for name in names: + text = function(manager, name) + if fault and ((case == "fresh-loop" and name == names[0]) or + (case == "fresh-impulse" and name == names[1]) or + (case not in ("fresh-loop", "fresh-impulse") and name in names[2:])): + if fault[0] in text: + text = mutated(text, *fault) + code.append(text) + enums = manager_types() + constants_section = manager[manager.index("/* ---------- constants */"):] + for member in ["_sound_promotion_dont", "_sound_compression_xbox_adpcm", "_sound_encoding_mono"]: + enums += enum_with(constants_section, member) + "\n" + enums += enum_with(read("source/sound/sound_classes.h"), "_sound_class_unit_dialog") + "\n" + for name in ["sound_fade_exponent", "sound_inaudible_fade_out_time", "speed_of_sound_threshold"]: + enums += re.search(r"^.*\b" + name + r"\s*=.*;", manager, re.M).group(0) + "\n" + return (("types.inc", enums), ("under_test.inc", "\n".join(code))) + + +@pytest.mark.parametrize("hz", [30, 60, 120, 240]) +@pytest.mark.parametrize("case", CASES) +def test_case(case, hz): + status, output = run(build("sound_lifecycle", generated()), f"{case}:{hz}") + assert status == 0, output + + +@pytest.mark.parametrize("control", NEGATIVE_CONTROLS) +def test_negative_control(control): + fault, case = NEGATIVE_CONTROLS[control] + status, output = run(build("sound_lifecycle", generated(fault, case)), f"{case}:240") + assert status == CHECK_FAILED, f"{control} escaped {case}: {output}" diff --git a/tools/harness/tests/test_sound_stream_flush.py b/tools/harness/tests/test_sound_stream_flush.py new file mode 100644 index 00000000..b6352a8d --- /dev/null +++ b/tools/harness/tests/test_sound_stream_flush.py @@ -0,0 +1,42 @@ +"""Cancellation drains SDL packets without refilling the original game channel.""" +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, function, mutated, read, run +from harness.audio import stream_types + +CASES = ["mixed-head", "unfinished", "all-finished", "paused", "repeat", "ordinary", + "wrapped", "no-callback", "empty"] +NEGATIVE_CONTROLS = { + "successful-cancellation": (("stream_complete_head(stream, XMEDIAPACKET_STATUS_FLUSHED,", + "stream_complete_head(stream, head->finished ? XMEDIAPACKET_STATUS_SUCCESS : XMEDIAPACKET_STATUS_FLUSHED,"), "mixed-head"), + "does-not-unlock-cache": (("sound_cache_sound_hardware_unlock(packet_context);", ""), "mixed-head"), + "does-not-reset-resampler": (("resampler_reset(stream);", ""), "repeat"), +} + + +def generated(fault=None): + backend = read("port/linux/src/dsound_sdl.c") + game = read("source/sound/sound_dsound_xbox.c") + code = function(game, "dsound_channel_callback") + "\n" + function(game, "channel_stop") + "\n" + code += "\n".join(function(backend, name) for name in ["packet_release", "stream_complete_head", + "streams_complete_finished", "stream_from_interface", "stream_flush"]) + if fault: + code = mutated(code, *fault) + return (("types.inc", stream_types()), ("under_test.inc", code)) + + +@pytest.mark.parametrize("case", CASES) +def test_case(case): + status, output = run(build("sound_stream_flush", generated()), case) + assert status == 0, output + + +@pytest.mark.parametrize("control", NEGATIVE_CONTROLS) +def test_negative_control(control): + fault, case = NEGATIVE_CONTROLS[control] + status, output = run(build("sound_stream_flush", generated(fault)), case) + assert status == CHECK_FAILED, f"{control} escaped {case}: {output}" diff --git a/tools/harness/tests/test_sound_transition.py b/tools/harness/tests/test_sound_transition.py new file mode 100644 index 00000000..b994a352 --- /dev/null +++ b/tools/harness/tests/test_sound_transition.py @@ -0,0 +1,40 @@ +"""Real instance limiting must not update a voice it retires.""" +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, function, mutated, read, run +from harness.audio import manager_types + +CASES = ["self-retirement", "other-victim", "no-limit", "definition-limit", "no-channel"] +NEGATIVE_CONTROLS = { + "continues-after-retirement": (("if (!sound_set_definition_end(channel->sound_index))", + "if ((sound_set_definition_end(channel->sound_index), FALSE))"), "self-retirement"), + "reports-retired-voice-alive": (("return FALSE;", "return TRUE;"), "self-retirement"), + "includes-own-channel": (("channel->sound_index != NONE && channel->sound_index != sound_index", + "channel->sound_index != NONE"), "no-limit"), +} + + +def generated(fault=None): + manager = read("source/sound/sound_manager.c") + code = "\n".join(function(manager, name) for name in ["sound_channel_summary_build", + "sound_find_like_channel", "sound_set_definition_end", "update_channel_for_looping_sound"]) + if fault: + code = mutated(code, *fault) + return (("types.inc", manager_types()), ("under_test.inc", code)) + + +@pytest.mark.parametrize("case", CASES) +def test_case(case): + status, output = run(build("sound_transition", generated()), case) + assert status == 0, output + + +@pytest.mark.parametrize("control", NEGATIVE_CONTROLS) +def test_negative_control(control): + fault, case = NEGATIVE_CONTROLS[control] + status, output = run(build("sound_transition", generated(fault)), case) + assert status == CHECK_FAILED, f"{control} escaped {case}: {output}" diff --git a/tools/harness/tests/test_transparent_enclosure.py b/tools/harness/tests/test_transparent_enclosure.py new file mode 100644 index 00000000..e630e9f6 --- /dev/null +++ b/tools/harness/tests/test_transparent_enclosure.py @@ -0,0 +1,57 @@ +"""Static enclosure recognition and actual native model-part linking, without GL.""" +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, constant, enum_with, function, mutated, read, run, structure +from harness.render_types import transparent_types + +CASES = ["closed", "open", "outside", "reversed", "invalid", "strips", "uncompressed", + "materials", "authored-links", "part-zero", "glass-zero", "opaque-part", "skinned", + "additional-transparent", "frame-cost", "map-reload", "render-links"] +CONTROLS = { + "revived-part-zero-gate": (("part->previous_part_index>=0", "part->previous_part_index>0"), "render-links"), + "lost-enclosure-link": (("parts[energy].next_part_index = (char)glass;", "parts[energy].next_part_index = NONE;"), "part-zero"), + "wrong-target-zero": (("if (glass == 0)", "if (FALSE)"), "glass-zero"), + "overwrites-authored-links": (("parts[i].previous_part_index != NONE || parts[i].next_part_index != NONE ||", "FALSE ||"), "authored-links"), + "accepts-open-mesh": (("reverse != 1 || forward != 1", "reverse < 0 || forward < 0"), "open"), + "accepts-other-blend": (("core->framebuffer_blend_function != _framebuffer_blend_function_add", "FALSE"), "materials"), +} + + +def generated(control=None): + models = read("source/models/models.c") + raster = read("source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c") + types = f"#define TAG_STRING_LENGTH {constant(read('source/tag_files/tag_files.h'), 'TAG_STRING_LENGTH')}\n#define UNSIGNED_SHORT_MAX {constant(read('source/cseries/cseries.h'), 'UNSIGNED_SHORT_MAX')}\n" + transparent_types() + for path, names in [ + ("source/models/model_definitions.h", ["model", "model_region", "model_region_permutation"]), + ("source/models/models.c", ["model_shader_reference", "model_geometry", "model_geometry_part", "shader_model_definition", "render_sort_filth"]), + ("source/render/render.h", ["render_skinning"]), + ("source/cache/cache_files.h", ["tag_iterator"]), + ]: + types += "\n" + "\n".join(structure(read(path), name) for name in names) + for member in ["_render_model_immediate_bit", "_render_model_pass_solid", "_model_geometry_part_stripped_bit", "_shader_model_detail_after_reflection_bit"]: + types += "\n" + enum_with(models, member) + types += "\n" + enum_with(read("source/models/model_definitions.h"), "MODELS_GROUP_TAG") + code = read("source/rasterizer/rasterizer_transparent_enclosure.h") + code += "\n" + function(raster, "rasterizer_transparent_geometry_is_enclosure") + for name in ["model_geometry_fix_transparent_part_links", "models_fix_transparent_part_links", "render_model_parts"]: + code += "\n" + function(models, name) + if control: + fault, _ = CONTROLS[control] + code = mutated(code, *fault) + return (("types.inc", types), ("under_test.inc", code)) + + +@pytest.mark.parametrize("case", CASES) +def test_case(case): + status, output = run(build("transparent_enclosure", generated()), case) + assert status == 0, output + + +@pytest.mark.parametrize("control", CONTROLS) +def test_negative_control(control): + status, output = run(build("transparent_enclosure", generated(control)), CONTROLS[control][1]) + assert status == CHECK_FAILED, f"{control} escaped its check: {output}" diff --git a/tools/harness/tests/test_transparent_plane_order.py b/tools/harness/tests/test_transparent_plane_order.py new file mode 100644 index 00000000..e65c2c44 --- /dev/null +++ b/tools/harness/tests/test_transparent_plane_order.py @@ -0,0 +1,81 @@ +"""Actual transparent queue sorting, skinning and plane ordering, without GL.""" +import sys +import re +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, constant, function, mutated, read, run, structure +from harness.render_types import transparent_types + +CASES = ["same-side", "opposite-side", "reverse-normal", "unrelated", "multiple-models", + "multiple-planes", "conflict", "intersect", "camera-plane", "invalid-plane", + "invalid-model", "materials", "rigid-transform", "skinned", "compressed-skinning", + "linked", "linked-union", "invalid-links", "full-queue", "four-views", "no-glass", + "external-sort", "fallback-flags", "invalid-order"] +CONTROLS = { + "same-side-backwards": (("? -1 : 1;", "? 1 : -1;"), "same-side"), + "camera-ignored": (("(distance > 0) == (side > 0)", "side > 0"), "opposite-side"), + "model-left-in-wrong-gap": (("(short)PIN(original_gap[root], lower, upper)", "original_gap[root]"), "unrelated"), + "not-adjacent-to-glass": (("for (phase = -1; phase <= 1; ++phase)", "for (phase = 1; phase >= -1; --phase)"), "unrelated"), + "only-final-linked-part-bounded": (("steps == 1 ?", "TRUE ?"), "linked-union"), + "skinning-ignored": (("group->node_matrix_count == 1", "TRUE"), "skinned"), + "compressed-byte-weight": (("(real)vertex->node_weight * (1.0f / 32767.0f)", "(real)(byte)vertex->node_weight * (1.0f / 255.0f)"), "compressed-skinning"), + "intersection-accepted": (("high <= epsilon && low < -epsilon", "low < -epsilon"), "intersect"), + "sort-hook-removed": (("rasterizer_transparent_geometry_order_models(\n\t\ttransparent_geometry_group_sorted_indices, transparent_geometry_group_count);", "(void)0;"), "external-sort"), +} + + +def generated(control=None): + raster = read("source/rasterizer/xbox/rasterizer_xbox_transparent_geometry.c") + core = read("source/rasterizer/rasterizer_transparent_geometry.c") + types = transparent_types() + for path, names in [ + ("source/math/real_math.h", ["real_matrix4x3"]), + ("source/rasterizer/rasterizer_model_types.h", ["model_vertex_compressed", "model_vertex_uncompressed"]), + ]: + types += "\n" + "\n".join(structure(read(path), name) for name in names) + for path, name in [("source/cseries/cseries.h", "UNSIGNED_SHORT_MAX"), + ("source/models/model_definitions.h", "MAXIMUM_NODES_PER_MODEL"), + ("source/rasterizer/rasterizer.h", "RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS")]: + types += f"\n#define {name} {constant(read(path), name)}\n" + code = function(read("source/math/matrix_math.c"), "matrix4x3_transform_point") + for name in ["shader_is_water_decal", "shader_type_is_transparent", "shader_type_is_valid_for_model"]: + code += "\n" + function(read("source/shaders/shaders.c"), name) + for name in ["transparent_planar_glass", "transparent_model_world_bounds", "transparent_plane_model_order", + "transparent_chain_root", "rasterizer_transparent_geometry_order_models"]: + code += "\n" + function(raster, name) + for name in ["rasterizer_sort_internal", "group_sorted_indices_cmpfn", "rasterizer_sort_external"]: + code += "\n" + function(core, name) + if control: + before, after = CONTROLS[control][0] + if control == "only-final-linked-part-bounded": + # Both ends of the union deliberately lose the previous parts. + assert code.count(before) == 2 + code = code.replace(before, after) + else: + code = mutated(code, before, after) + return (("types.inc", types), ("under_test.inc", code)) + + +@pytest.mark.parametrize("case", CASES) +def test_case(case): + status, output = run(build("transparent_plane_order", generated()), case) + assert status == 0, output + + +def test_compressed_weight_declaration(): + # Confirm that the CPU's signed-short weights match the actual draw stream. + source = read("source/rasterizer/xbox/rasterizer_xbox_vertex_shaders_initialize.c") + declaration = re.search(r"vertex_shader_declarations\[\]\s*=\s*\{([^}]+)\}", source).group(1) + tokens = [int(v, 16) for v in re.findall(r"0x[\dA-Fa-f]+", declaration)] + offset = int(re.search(r"vertex_shader_table\[13\]\.declaration = &vertex_shader_declarations\[(0x[\dA-Fa-f]+)", source).group(1), 16) // 4 + weight = next(v for v in tokens[offset:offset+9] if v != 0xFFFFFFFF and v & 0xFFFF == 6) + assert (weight >> 16) & 0xFF == constant(read("port/include/xdk/xdk_d3d8.h"), "D3DVSDT_NORMSHORT1") + + +@pytest.mark.parametrize("control", CONTROLS) +def test_negative_control(control): + status, output = run(build("transparent_plane_order", generated(control)), CONTROLS[control][1]) + assert status == CHECK_FAILED, f"{control} escaped its check: {output}" diff --git a/tools/harness/tests/test_voice_route.py b/tools/harness/tests/test_voice_route.py new file mode 100644 index 00000000..303760a4 --- /dev/null +++ b/tools/harness/tests/test_voice_route.py @@ -0,0 +1,59 @@ +"""The real rules of network_voice.c the host relays each voice by: who hears whom in the lobby and in each mode +(network.voice_mode), and the largest packet a quality (network.voice_*_kbps) allows.""" + +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, constant, enum_with, function, mutated, read, run # noqa: E402 + +CASES = ["lobby", "off", "team-proximity", "team-enemy-proximity", "team-global", "team-global-enemy-proximity", + "packet-limit"] + +# a fault in network_voice.c, the function it is in, and the case that must catch it +NEGATIVE_CONTROLS = { + "lobby-ignores-setting": (("return lobby_voice ? _voice_route_global : _voice_route_none;", + "return _voice_route_global;"), "voice_route", "lobby"), + "proximity-ignores-death": (("boolean near = both_alive && distance <= range;", + "boolean near = distance <= range;"), "voice_route", "team-proximity"), + "proximity-ignores-range": (("boolean near = both_alive && distance <= range;", "boolean near = both_alive;"), + "voice_route", "team-enemy-proximity"), + "team-hears-enemies": (("return teammates ? _voice_route_global : _voice_route_none;", + "return _voice_route_global;"), "voice_route", "team-global"), + "enemies-heard-anywhere": (("\t\treturn near ? _voice_route_proximity : _voice_route_none;\n\tdefault:", + "\t\treturn _voice_route_proximity;\n\tdefault:"), + "voice_route", "team-global-enemy-proximity"), + "no-packet-cap": (("return MIN(limit, VOICE_MAXIMUM_PACKET);", "return limit * 4;"), "voice_packet_limit", + "packet-limit"), +} + + +def generated(fault=None, faulty_function=None): + source = read("port/linux/game/network_voice.c") + header = read("port/linux/game/network_voice.h") + audio = read("port/linux/src/voice_audio.h") + config = enum_with(header, "_voice_mode_off") + "\n" + enum_with(header, "_voice_route_none") + "\n" + config += f"#define VOICE_FRAMES_PER_SECOND {constant(source, 'VOICE_FRAMES_PER_SECOND')}\n" + config += f"#define VOICE_MAXIMUM_PACKET {constant(audio, 'VOICE_MAXIMUM_PACKET')}\n" + text = "" + for name in ("voice_route", "voice_packet_limit"): + code = function(source, name) + if fault and name == faulty_function: + code = mutated(code, *fault) + text += code + "\n" + return (("config.inc", config), ("under_test.inc", text)) + + +@pytest.mark.parametrize("case", CASES) +def test_case(case): + status, output = run(build("voice_route", generated()), case) + assert status == 0, output + + +@pytest.mark.parametrize("control", NEGATIVE_CONTROLS) +def test_negative_control(control): + fault, faulty_function, case = NEGATIVE_CONTROLS[control] + status, output = run(build("voice_route", generated(fault, faulty_function)), case) + assert status == CHECK_FAILED, f"network_voice.c with a fault ({control}) passed '{case}': the test cannot see it" diff --git a/tools/harness/tests/test_votekick_count.py b/tools/harness/tests/test_votekick_count.py new file mode 100644 index 00000000..c2e769f5 --- /dev/null +++ b/tools/harness/tests/test_votekick_count.py @@ -0,0 +1,65 @@ +"""The real count of network_votekick.c: who may vote, whose votes are one person's (by address, and by a hardware id +that only ever takes votes away), and how many kick.""" + +import re +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from harness import CHECK_FAILED, build, constant, function, mutated, read, run # noqa: E402 + +CASES = ["majority", "even-teams", "two-never-kick", "same-address-one-vote", "same-hardware-id-one-vote", + "copied-hardware-id-lowers-nothing", "target-never-votes", "eligibility"] +FUNCTIONS = ("votekick_same_address", "votekick_same_person", "votekick_may_vote", "votekick_count", + "votekick_votes_needed") + +# a fault in network_votekick.c, the function it is in, and the case that must catch it +NEGATIVE_CONTROLS = { + "half-is-enough": (("electorate / 2 + 1", "(electorate + 1) / 2"), "votekick_votes_needed", "majority"), + "no-minimum": (("needed < MINIMUM_VOTES ? MINIMUM_VOTES : needed", "needed"), "votekick_votes_needed", + "two-never-kick"), + "target-not-counted": (("votekick_may_vote(&voters[index]) || voters[index].target;", + "votekick_may_vote(&voters[index]);"), "votekick_count", "even-teams"), + "votes-by-address-only": (("votekick_may_vote(&voters[other]) &&\n\t\t\t\tvotekick_same_person(", + "votekick_may_vote(&voters[other]) &&\n\t\t\t\tvotekick_same_address("), + "votekick_count", "same-hardware-id-one-vote"), + "electorate-by-hardware-id": (("votekick_same_address(&voters[index], &voters[other]))\n\t\t\t{\n\t\t\t\tcounted = FALSE;\n\t\t\t}\n\t\t}\n\t\tif (counted)\n\t\t\t(*electorate)++;", + "votekick_same_person(&voters[index], &voters[other]))\n\t\t\t{\n\t\t\t\tcounted = FALSE;\n\t\t\t}\n\t\t}\n\t\tif (counted)\n\t\t\t(*electorate)++;"), + "votekick_count", "copied-hardware-id-lowers-nothing"), + "target-votes": (("if (voters[other].target && votekick_same_person(", "if (0 && votekick_same_person("), + "votekick_count", "target-never-votes"), + "newcomers-vote": (("return voter->eligible && !voter->target", "return !voter->target"), "votekick_may_vote", + "eligibility"), +} + + +def generated(fault=None, faulty_function=None): + source = read("port/linux/game/network_votekick.c") + config = "" + for name in ("VOTEKICK_HARDWARE_ID_SIZE", "MINIMUM_VOTES"): + config += f"#define {name} {constant(source, name)}\n" + match = re.search(r"^struct votekick_voter\n\{.*?^\};\n", source, re.M | re.S) + assert match, "struct votekick_voter not found in network_votekick.c" + config += match.group(0) + text = "" + for name in FUNCTIONS: + code = function(source, name) + if fault and name == faulty_function: + code = mutated(code, *fault) + text += code + "\n" + return (("config.inc", config), ("under_test.inc", text)) + + +@pytest.mark.parametrize("case", CASES) +def test_case(case): + status, output = run(build("votekick_count", generated()), case) + assert status == 0, output + + +@pytest.mark.parametrize("control", NEGATIVE_CONTROLS) +def test_negative_control(control): + fault, faulty_function, case = NEGATIVE_CONTROLS[control] + status, output = run(build("votekick_count", generated(fault, faulty_function)), case) + assert status == CHECK_FAILED, f"network_votekick.c with a fault ({control}) passed '{case}': the test cannot see it" diff --git a/tools/harness/tests/transparent_enclosure.c b/tools/harness/tests/transparent_enclosure.c new file mode 100644 index 00000000..11382635 --- /dev/null +++ b/tools/harness/tests/transparent_enclosure.c @@ -0,0 +1,196 @@ +#include "harness.h" +#include +#include +typedef unsigned short word; +typedef struct { real x, y; } real_vector2d; +typedef struct { real red, green, blue; } real_rgb_color; +typedef struct { real alpha, red, green, blue; } real_argb_color; +typedef struct { real_vector3d n; real d; } real_plane3d; +typedef void real_matrix4x3; +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define VALID_INDEX(i,n) ((i) >= 0 && (i) < (n)) +#define square_root sqrt +#include "types.inc" + +static struct model model; +static struct model_geometry geometry; +static struct model_geometry_part parts[4]; +static struct model_shader_reference references[4]; +static struct shader_transparent_generic_definition energy_material; +static struct shader_transparent_glass_definition glass_material; +static struct shader opaque_material; +static struct model_region region; +static struct model_region_permutation permutation; +static int locks, submissions, iterator_position; +static short previous[32], next[32]; +static real shell[5][8] = {{-1,-1,0},{1,-1,0},{1,1,0},{-1,1,0},{0,0,2}}; +static word indices[64] = {0,1,2,0,2,3,0,4,1,1,4,2,2,4,3,3,4,0}; +static real sphere[6][8] = {{-.22f,0,.7f},{.22f,0,.7f},{0,-.22f,.7f},{0,.22f,.7f},{0,0,.48f},{0,0,.92f}}; + +#define TAG_BLOCK_GET_ELEMENT(b,i,t) ((t *)(b)->address + (i)) +#define shader_definition_get(i) ((i)==0 ? (struct shader *)&energy_material : (i)==1 ? (struct shader *)&glass_material : &opaque_material) +#define model_definition_get(i) test_model_get() +#define shader_type_is_transparent(t) ((t)>=_shader_type_transparent_generic) +#define shader_type_is_valid_for_model(t) ((t)>=_shader_type_model) +#define shader_get_and_verify_type(s,t) (s) +#define model_data_error(m,e) ((void)0) +#define render_model_no_geometry FALSE +#define matrix4x3_transform_point(m,p,o) (*(o)=*(p)) +#define rasterizer_model_draw(...) ((void)0) +#define rasterizer_environment_shadow_model_draw(...) ((void)0) +#define rasterizer_debug_model_vertices(...) ((void)0) +#define D3DLOCK_READONLY 128 + +static void tag_iterator_new(struct tag_iterator *iterator, long tag) +{ + iterator_position = 0; +} +static struct model *test_model_get(void) +{ + return &model; +} +static long tag_iterator_next(struct tag_iterator *iterator) +{ + return iterator_position++ == 0 ? 0 : NONE; +} +static long rasterizer_geometry_get_vertex_size(short type) +{ + return type == _rasterizer_vertex_type_model_compressed ? 32 : 68; +} +static void IDirect3DVertexBuffer8_Lock(void *buffer, long offset, long size, byte **out, long flags) +{ + locks++; + *out = buffer; +} +#define IDirect3DIndexBuffer8_Lock IDirect3DVertexBuffer8_Lock +#define IDirect3DVertexBuffer8_Unlock(b) ((void)0) +#define IDirect3DIndexBuffer8_Unlock(b) ((void)0) +static void rasterizer_model_transparent_geometry_submit(struct shader *shader, short permutation_index, + struct triangle_buffer const *triangles, long first_triangle, long triangle_count, + struct vertex_buffer const *vertices, long offset, real_point3d const *centroid, + struct render_sort_filth *sort) +{ + int i = submissions++; + previous[i] = next[i] = NONE; + if (sort) + { + sort->group_index = i; + sort->previous_group_presorted_index_reference = previous + i; + sort->next_group_presorted_index_reference = next + i; + } +} +#include "under_test.inc" + +static void initialize(void) +{ + int i; + memset(&model, 0, sizeof(model)); + memset(parts, 0, sizeof(parts)); + memset(&energy_material, 0, sizeof(energy_material)); + memset(&glass_material, 0, sizeof(glass_material)); + locks = 0; + energy_material.shader.type = _shader_type_transparent_generic; + energy_material.generic.flags = FLAG(_shader_transparent_flag_two_sided_bit); + energy_material.generic.framebuffer_blend_function = _framebuffer_blend_function_add; + energy_material.generic.lens_flare.index = NONE; + glass_material.shader.base.type = _shader_type_transparent_glass; + glass_material.flags = FLAG(_shader_transparent_glass_flag_two_sided_bit); + opaque_material.base.type = _shader_type_model; + model.nodes.count = 1; + model.geometries.count = 1; model.geometries.address = &geometry; + model.shaders.count = 4; model.shaders.address = references; + geometry.parts.count = 2; geometry.parts.address = parts; + for (i=0; i<4; i++) + { + parts[i].shader_index = i; + references[i].shader.index = i; + parts[i].previous_part_index = parts[i].next_part_index = NONE; + parts[i].vertex_buffer.type = _rasterizer_vertex_type_model_compressed; + } + parts[0].vertex_buffer.count = 6; parts[0].vertex_buffer.hardware_format = sphere; + parts[1].vertex_buffer.count = 5; parts[1].vertex_buffer.hardware_format = shell; + parts[1].triangle_buffer.type = _triangle_buffer_type_triangles; + parts[1].triangle_buffer.count = 6; parts[1].triangle_buffer.hardware_format = indices; + region.permutations.count = 1; region.permutations.address = &permutation; + model.regions.count = 1; model.regions.address = ®ion; + memset(&permutation, 0, sizeof(permutation)); +} +static boolean recognizes(void) +{ + return rasterizer_transparent_geometry_is_enclosure((struct shader *)&glass_material, + &parts[1].vertex_buffer, &parts[1].triangle_buffer, + (struct shader *)&energy_material, &parts[0].vertex_buffer); +} +static void draw_frame(void) +{ + struct render_skinning skinning = {NULL, 1}; + char permutation_indices[] = {0}; + submissions = 0; + render_model_parts(&model, permutation_indices, 1, &skinning, 1, 0, 0, 0); + CHECK(submissions == 2, "two transparent parts"); + CHECK(next[0] == 1 && previous[1] == 0, "native links must include part zero"); + CHECK(glass_material.flags == FLAG(_shader_transparent_glass_flag_two_sided_bit), "retail two-sided glass retained"); +} + +int main(int argc, char **argv) +{ + const char *case_name = argc > 1 ? argv[1] : "closed"; + int i; + initialize(); + CASE("closed") { CHECK(recognizes(), "closed shell"); } + CASE("open") { parts[1].triangle_buffer.count = 5; CHECK(!recognizes(), "missing face"); } + CASE("outside") { sphere[0][0] = 5; CHECK(!recognizes(), "outside core"); } + CASE("reversed") { for(i=0;i<18;i+=3) { word swap=indices[i];indices[i]=indices[i+1];indices[i+1]=swap; } CHECK(!recognizes(), "reverse winding"); } + CASE("invalid") { + indices[0]=64; CHECK(!recognizes(), "bad index"); indices[0]=0; + shell[0][0]=NAN; CHECK(!recognizes(), "invalid outer point"); shell[0][0]=-1; + sphere[0][0]=NAN; CHECK(!recognizes(), "invalid inner point"); + CHECK(!rasterizer_transparent_encloses((byte*)shell,5,0,indices,6,FALSE,(byte*)sphere,6,32), "bad stride"); + } + CASE("strips") { + word triangles[18];long cursor=0; + memcpy(triangles,indices,sizeof(triangles)); + for(i=0;i<6;i++) { + word a=triangles[3*i],b=triangles[3*i+1]; + if(i) { + if((cursor+2)&1){word swap=a;a=b;b=swap;} + indices[cursor]=indices[cursor-1];cursor++; + indices[cursor++]=a; + } + indices[cursor++]=a;indices[cursor++]=b;indices[cursor++]=triangles[3*i+2]; + } + parts[1].triangle_buffer.type=_triangle_buffer_type_precompiled_strip; + parts[1].triangle_buffer.count=cursor-2;CHECK(recognizes(), "triangle strip with degenerates"); + } + CASE("uncompressed") { + real outer[5][17],inner[6][17]; + for(i=0;i<5;i++)memcpy(outer[i],shell[i],12); + for(i=0;i<6;i++)memcpy(inner[i],sphere[i],12); + parts[1].vertex_buffer.hardware_format=outer;parts[0].vertex_buffer.hardware_format=inner; + parts[1].vertex_buffer.type=parts[0].vertex_buffer.type=_rasterizer_vertex_type_model_uncompressed; + CHECK(recognizes(), "uncompressed vertices"); + } + CASE("materials") { + energy_material.generic.framebuffer_blend_function=_framebuffer_blend_function_alpha_blend; + CHECK(!recognizes(), "other blend untouched");initialize(); + glass_material.flags=0;CHECK(!recognizes(), "one-sided glass untouched");initialize(); + glass_material.reflection_type=_shader_transparent_glass_reflection_type_dynamic_mirror; + CHECK(!recognizes(), "dynamic mirror untouched");initialize(); + energy_material.generic.extra_layers.count=1;CHECK(!recognizes(), "layers untouched"); + } + CASE("part-zero") { models_fix_transparent_part_links(); CHECK(parts[0].next_part_index==1 && parts[1].previous_part_index==0, "link from part zero"); } + CASE("glass-zero") { + struct model_geometry_part swap=parts[0];parts[0]=parts[1];parts[1]=swap; + models_fix_transparent_part_links(); + CHECK(parts[0].shader_index==0 && parts[1].shader_index==1 && parts[0].next_part_index==1, "no native link to zero"); + } + CASE("opaque-part") {geometry.parts.count=3;models_fix_transparent_part_links();CHECK(parts[0].next_part_index==1 && parts[2].previous_part_index==NONE, "opaque base retained");} + CASE("skinned") {model.nodes.count=2;models_fix_transparent_part_links();CHECK(!locks && parts[0].next_part_index==NONE, "skinned model retained");} + CASE("authored-links") {parts[0].next_part_index=2;models_fix_transparent_part_links();CHECK(!locks && parts[0].next_part_index==2, "authored links retained");} + CASE("additional-transparent") {geometry.parts.count=3;opaque_material.base.type=_shader_type_transparent_glass;models_fix_transparent_part_links();CHECK(!locks && parts[0].next_part_index==NONE, "ambiguous model retained");} + CASE("frame-cost") {models_fix_transparent_part_links();CHECK(locks==3,"one recognizer call");for(i=0;i<1000;i++)draw_frame();CHECK(locks==3,"no per-frame recognition");models_fix_transparent_part_links();CHECK(locks==3,"already linked geometry retained");} + CASE("map-reload") {models_fix_transparent_part_links();initialize();models_fix_transparent_part_links();CHECK(locks==3 && parts[0].next_part_index==1,"fresh map links");} + CASE("render-links") {models_fix_transparent_part_links();draw_frame();} + return 0; +} diff --git a/tools/harness/tests/transparent_plane_order.c b/tools/harness/tests/transparent_plane_order.c new file mode 100644 index 00000000..67bcfe39 --- /dev/null +++ b/tools/harness/tests/transparent_plane_order.c @@ -0,0 +1,432 @@ +/* CPU fake buffers and camera; production queue, skinning and math below. */ +#include "harness.h" +#include +#include + +typedef unsigned short word; +typedef struct { short x, y; } point2d; +typedef struct { real x, y; } real_point2d; +typedef struct { real i, j; } real_vector2d; +typedef struct { real red, green, blue; } real_rgb_color; +typedef struct { real alpha; real_rgb_color rgb; } real_argb_color; +typedef struct { real_vector3d n; real d; } real_plane3d; +#define TAG_STRING_LENGTH 31 +#define VALID_INDEX(i, n) ((i) >= 0 && (i) < (n)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define PIN(x, a, b) MIN(MAX(x, a), b) +#define D3DLOCK_READONLY 16 +#define __cdecl +#include "types.inc" +typedef struct real_matrix4x3 real_matrix4x3; + +static struct transparent_geometry_group storage[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; +static struct transparent_geometry_group *transparent_geometry_groups = storage; +static short order[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; +static short *transparent_geometry_group_sorted_indices = order; +static short transparent_geometry_group_count; +static struct { struct { real_point3d position; } camera; } global_window_parameters; +static struct shader_transparent_glass_definition glass; +static struct shader_transparent_generic_definition materials[NUMBER_OF_SHADER_TYPES]; +static struct vertex_buffer buffers[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS]; +static struct model_vertex_uncompressed vertices[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS][2]; +static struct model_vertex_compressed compressed[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS][2]; +static real_matrix4x3 matrices[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS][2]; +static long locks, unlocks; + +static void *rasterizer_transparent_geometry_get_group_from_presorted_index(short index) +{ + CHECK(VALID_INDEX(index, transparent_geometry_group_count), "invalid group lookup %d", index); + return storage + index; +} + +static long rasterizer_geometry_get_vertex_size(short type) +{ + return type == _rasterizer_vertex_type_model_compressed ? sizeof(struct model_vertex_compressed) : + sizeof(struct model_vertex_uncompressed); +} + +static void IDirect3DVertexBuffer8_Lock(void *buffer, long offset, long size, byte **data, long flags) +{ + CHECK(offset == 0 && size == 0 && flags == D3DLOCK_READONLY, "read-only buffer lock"); + ++locks; + *data = buffer; +} + +static void IDirect3DVertexBuffer8_Unlock(void *buffer) +{ + CHECK(buffer != NULL, "unlock valid buffer"); + ++unlocks; +} + +/* The fake Chicago material only needs its common transparent flag prefix. */ +#define SHADER_GET_TRANSPARENT_GENERIC(s) (&((struct shader_transparent_generic_definition *)(s))->generic) +#define SHADER_GET_TRANSPARENT_CHICAGO(s) SHADER_GET_TRANSPARENT_GENERIC(s) +#define _shader_transparent_draw_before_water_bit _shader_transparent_flag_draw_before_water_bit + +#include "under_test.inc" + +static real_matrix4x3 identity(void) +{ + real_matrix4x3 result = {0}; + result.scale = 1; + result.forward.i = result.left.j = result.up.k = 1; + return result; +} + +static void reset(short count) +{ + short i; + memset(storage, 0, sizeof(storage)); + memset(buffers, 0, sizeof(buffers)); + memset(vertices, 0, sizeof(vertices)); + memset(compressed, 0, sizeof(compressed)); + memset(&glass, 0, sizeof(glass)); + memset(materials, 0, sizeof(materials)); + glass.shader.base.type = _shader_type_transparent_glass; + transparent_geometry_group_count = count; + for (i = 0; i < NUMBER_OF_SHADER_TYPES; ++i) + { + materials[i].shader.type = i; + } + for (i = 0; i < count; ++i) + { + order[i] = i; + storage[i].previous_group_presorted_index = NONE; + storage[i].next_group_presorted_index = NONE; + matrices[i][0] = matrices[i][1] = identity(); + } + global_window_parameters.camera.position = (real_point3d){0, 0, 5}; + locks = unlocks = 0; +} + +static void floor_at(short index, real height) +{ + storage[index].shader = &glass.shader; + storage[index].geometry_flags = FLAG(_rasterizer_geometry_no_sort_bit); + storage[index].plane.n.k = 1; + storage[index].plane.d = height; +} + +static void model_at(short index, real low, real high) +{ + storage[index].shader = (struct shader *)&materials[_shader_type_transparent_generic]; + storage[index].object_index = index + 100; + storage[index].vertex_buffer = buffers + index; + storage[index].node_matrices = matrices[index]; + storage[index].node_matrix_count = 1; + buffers[index].type = _rasterizer_vertex_type_model_uncompressed; + buffers[index].count = 2; + buffers[index].hardware_format = vertices[index]; + vertices[index][0].position = (real_point3d){-1, -1, low}; + vertices[index][1].position = (real_point3d){1, 1, high}; + vertices[index][0].nodes[1] = vertices[index][1].nodes[1] = NONE; + vertices[index][0].node_weights[0] = vertices[index][1].node_weights[0] = 1; +} + +static void link_parts(short first, short second) +{ + storage[first].next_group_presorted_index = second; + storage[second].previous_group_presorted_index = first; + storage[second].object_index = storage[first].object_index; +} + +static void expect(short const *expected, long count) +{ + long i; + rasterizer_transparent_geometry_order_models(order, count); + for (i = 0; i < count; ++i) + { + CHECK(order[i] == expected[i], "order[%ld]=%d, expected %d", i, order[i], expected[i]); + } + CHECK(locks == unlocks, "balanced CPU buffer access"); +} + +#define EXPECT(...) do { short expected[] = {__VA_ARGS__}; expect(expected, sizeof(expected)/sizeof(*expected)); } while (0) + +int main(int argc, char **argv) +{ + char const *case_name; + short i; + CHECK(argc == 2, "one named case"); + case_name = argv[1]; + reset(2); + model_at(0, 0, 2); + floor_at(1, 0); + + CASE("same-side") { EXPECT(1, 0); return 0; } + CASE("opposite-side") + { + order[0] = 1; order[1] = 0; + global_window_parameters.camera.position.z = -5; + EXPECT(0, 1); + return 0; + } + CASE("reverse-normal") + { + storage[1].plane.n.k = -1; + EXPECT(1, 0); + return 0; + } + CASE("unrelated") + { + reset(3); floor_at(0, 0); model_at(2, 1, 2); + global_window_parameters.camera.position.z = -5; + EXPECT(2, 0, 1); /* The intervening widget stays behind the floor. */ + reset(3); model_at(0, 1, 2); floor_at(2, 0); + EXPECT(1, 2, 0); /* Opposite move: widget stays before the floor. */ + reset(3); floor_at(1, 0); model_at(2, 1, 2); + global_window_parameters.camera.position.z = -5; + EXPECT(0, 2, 1); /* Move beside the glass, after an existing widget. */ + reset(3); model_at(0, 1, 2); floor_at(1, 0); + EXPECT(1, 0, 2); /* The arriving model precedes the following widget. */ + return 0; + } + CASE("multiple-models") + { + reset(5); floor_at(0, 0); model_at(2, 1, 2); model_at(4, 1, 2); + global_window_parameters.camera.position.z = -5; + EXPECT(2, 4, 0, 1, 3); + return 0; + } + CASE("multiple-planes") + { + reset(5); floor_at(0, 0); floor_at(2, 2); model_at(4, .5f, 1.5f); + EXPECT(0, 1, 4, 2, 3); + return 0; + } + CASE("conflict") + { + reset(3); floor_at(0, 2); floor_at(1, 0); model_at(2, .5f, 1.5f); + EXPECT(0, 1, 2); + return 0; + } + CASE("intersect") + { + vertices[0][0].position.z = -1; + EXPECT(0, 1); + order[0] = 1; order[1] = 0; + EXPECT(1, 0); + return 0; + } + CASE("camera-plane") + { + global_window_parameters.camera.position.z = 0; + EXPECT(0, 1); + return 0; + } + CASE("invalid-plane") + { + real values[] = {0, .2f, 2, NAN, INFINITY}; + for (i = 0; i < 5; ++i) + { + storage[1].plane.n.k = values[i]; + EXPECT(0, 1); + } + CHECK(locks == 0, "invalid planes do not read models"); + storage[1].plane.n.k = 1; + storage[1].plane.d = NAN; + EXPECT(0, 1); + return 0; + } + CASE("invalid-model") + { + vertices[0][0].position.x = NAN; + EXPECT(0, 1); + vertices[0][0].position.x = -1; + matrices[0][0].scale = INFINITY; + EXPECT(0, 1); + matrices[0][0].scale = 1; + buffers[0].offset = 4; + EXPECT(0, 1); + buffers[0].offset = 0; + buffers[0].count = 0; + EXPECT(0, 1); + return 0; + } + CASE("materials") + { + short types[] = {_shader_type_transparent_generic, _shader_type_transparent_chicago, + _shader_type_transparent_glass, _shader_type_transparent_meter, _shader_type_transparent_plasma}; + for (i = 0; i < 5; ++i) + { + order[0] = 0; order[1] = 1; + storage[0].shader = (struct shader *)(materials + types[i]); + EXPECT(1, 0); + } + return 0; + } + CASE("rigid-transform") + { + real bounds[2][3]; + matrices[0][0].forward = (real_vector3d){0, 0, 1}; + matrices[0][0].up = (real_vector3d){-1, 0, 0}; + matrices[0][0].scale = 2; + matrices[0][0].position.z = 3; + CHECK(transparent_model_world_bounds(storage, bounds), "rotated and scaled bounds"); + CHECK(bounds[0][2] == 1 && bounds[1][2] == 5, "real matrix transform applied"); + EXPECT(1, 0); + return 0; + } + CASE("skinned") + { + real bounds[2][3]; + storage[0].node_matrix_count = 2; + matrices[0][0].position.z = -8; + matrices[0][1].position.z = 8; + for (i = 0; i < 2; ++i) + { + vertices[0][i].nodes[1] = 1; + vertices[0][i].node_weights[0] = .25f; + vertices[0][i].node_weights[1] = .75f; + } + CHECK(transparent_model_world_bounds(storage, bounds), "weighted two-node bounds"); + CHECK(bounds[0][2] == 4 && bounds[1][2] == 6, "actual weights and both nodes"); + EXPECT(1, 0); + vertices[0][0].nodes[1] = 2; + order[0] = 0; order[1] = 1; + EXPECT(0, 1); + vertices[0][0].nodes[1] = 1; + vertices[0][0].node_weights[0] = NAN; + EXPECT(0, 1); + return 0; + } + CASE("compressed-skinning") + { + real bounds[2][3]; + storage[0].node_matrix_count = 2; + matrices[0][0].position.z = -8; + matrices[0][1].position.z = 8; + buffers[0].type = _rasterizer_vertex_type_model_compressed; + buffers[0].hardware_format = compressed[0]; + for (i = 0; i < 2; ++i) + { + compressed[0][i].position = vertices[0][i].position; + compressed[0][i].nodes[1] = 3; + compressed[0][i].node_weight = 8192; + } + CHECK(transparent_model_world_bounds(storage, bounds), "compressed node encoding"); + CHECK(fabs(bounds[0][2] - (8.f - 16.f * 8192.f/32767.f)) < .00001f, "signed normalized short matches GPU declaration"); + EXPECT(1, 0); + order[0] = 0; order[1] = 1; + compressed[0][0].nodes[1] = 4; + EXPECT(0, 1); + return 0; + } + CASE("linked") + { + reset(4); model_at(0, 1, 2); floor_at(1, 0); model_at(3, 0, 3); + link_parts(0, 3); + EXPECT(1, 0, 3, 2); + reset(4); model_at(0, 1, 2); floor_at(1, 0); model_at(3, 0, 3); + link_parts(0, 3); + order[0] = 3; order[1] = 1; order[2] = 2; order[3] = 0; + EXPECT(1, 0, 3, 2); /* The earliest queued member dispatches the same chain. */ + return 0; + } + CASE("linked-union") + { + reset(3); model_at(0, -2, -1); model_at(1, 1, 2); floor_at(2, 0); + link_parts(0, 1); + EXPECT(0, 1, 2); /* Whole chain intersects; bounding only the last part is unsafe. */ + storage[0].effect_type = 1; + EXPECT(0, 1, 2); /* Unsupported first member invalidates the entire chain. */ + return 0; + } + CASE("invalid-links") + { + storage[0].previous_group_presorted_index = 5; + EXPECT(0, 1); + storage[0].previous_group_presorted_index = NONE; + storage[0].next_group_presorted_index = 5; + EXPECT(0, 1); + reset(3); floor_at(0, 0); model_at(1, 1, 2); model_at(2, 1, 2); + link_parts(1, 2); link_parts(2, 1); + EXPECT(0, 1, 2); + return 0; + } + CASE("full-queue") + { + short seen[RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS] = {0}; + reset(RASTERIZER_MAXIMUM_TRANSPARENT_GEOMETRY_GROUPS); + floor_at(0, 0); + global_window_parameters.camera.position.z = -5; + for (i = 1; i < transparent_geometry_group_count; ++i) + { + model_at(i, 1, 2); + } + rasterizer_transparent_geometry_order_models(order, transparent_geometry_group_count); + CHECK(order[transparent_geometry_group_count-1] == 0, "full queue moves models before glass"); + for (i = 0; i < transparent_geometry_group_count; ++i) + { + CHECK(VALID_INDEX(order[i], transparent_geometry_group_count) && !seen[order[i]]++, "permutation preserved"); + CHECK(i == transparent_geometry_group_count-1 || order[i] == i+1, "stable model order"); + } + CHECK(locks == transparent_geometry_group_count-1 && unlocks == locks, "one scan per model"); + return 0; + } + CASE("four-views") + { + for (i = 0; i < 4; ++i) + { + order[0] = 0; order[1] = 1; + global_window_parameters.camera.position.z = i & 1 ? -5 : 5; + if (i & 1) { EXPECT(0, 1); } else { EXPECT(1, 0); } + } + return 0; + } + CASE("no-glass") + { + storage[1].shader = NULL; + EXPECT(0, 1); + CHECK(!locks, "no glass has no model scan cost"); + return 0; + } + CASE("external-sort") + { + storage[0].z_sort = -20; + storage[1].z_sort = -10; + rasterizer_sort_external(); + CHECK(order[0] == 1 && order[1] == 0, "correction runs after actual qsort"); + CHECK(storage[1].sorted_index == 0 && storage[0].sorted_index == 1, "published sorted indices agree"); + return 0; + } + CASE("fallback-flags") + { + short flags[] = {_rasterizer_geometry_no_sort_bit, _rasterizer_geometry_no_queue_bit, + _rasterizer_geometry_no_zbuffer_bit, _rasterizer_geometry_sky_bit, + _rasterizer_geometry_viewspace_bit, _rasterizer_geometry_first_person_bit}; + for (i = 0; i < 6; ++i) + { + storage[0].geometry_flags = FLAG(flags[i]); + EXPECT(0, 1); + } + storage[0].geometry_flags = 0; + storage[0].active_camouflage_transparent_source_object_index = 100; + EXPECT(0, 1); + storage[0].active_camouflage_transparent_source_object_index = 0; + materials[_shader_type_transparent_generic].generic.flags = FLAG(_shader_transparent_flag_draw_before_water_bit); + EXPECT(0, 1); + materials[_shader_type_transparent_generic].generic.flags = 0; + storage[0].shader = (struct shader *)&materials[_shader_type_transparent_water]; + EXPECT(0, 1); + storage[0].shader = (struct shader *)&materials[_shader_type_transparent_generic]; + glass.flags = FLAG(_shader_transparent_glass_flag_decal_bit); + EXPECT(0, 1); + glass.flags = 0; + glass.reflection_type = _shader_transparent_glass_reflection_type_dynamic_mirror; + EXPECT(0, 1); + return 0; + } + CASE("invalid-order") + { + order[0] = order[1] = 0; + EXPECT(0, 0); + order[0] = 0; order[1] = 2; + EXPECT(0, 2); + CHECK(locks == 0, "invalid order rejected before buffer access"); + return 0; + } + CHECK(FALSE, "unknown case %s", case_name); + return 1; +} diff --git a/tools/harness/tests/voice_route.c b/tools/harness/tests/voice_route.c new file mode 100644 index 00000000..b89d1d1e --- /dev/null +++ b/tools/harness/tests/voice_route.c @@ -0,0 +1,104 @@ +/* +VOICE_ROUTE.C (test) + +The real rules of port/linux/game/network_voice.c that the host sends each +voice by: who hears whom in the lobby and in each of network.voice_mode's +modes, and the largest packet a quality allows. test_voice_route.py takes +the modes and routes (config.inc) and the code (under_test.inc). +*/ + +#include "harness.h" + +#define PIN(value, low, high) ((value) < (low) ? (low) : (value) > (high) ? (high) : (value)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#include "config.inc" +#include "under_test.inc" + +/* a listener's route to a speaker in a game: teammates or not, near (both +alive, within the range) or far, or one of them dead */ +static short in_game(short mode, boolean teammates, real distance, boolean both_alive) +{ + return voice_route(mode, FALSE, TRUE, teammates, both_alive, distance, 15.0f); +} + +int main(int argc, char **argv) +{ + char const *case_name = argc > 1 ? argv[1] : ""; + short mode; + + /* the lobby: everyone, or no one, whatever the game's mode */ + CASE("lobby") + { + for (mode = 0; mode < NUMBER_OF_VOICE_MODES; mode++) + { + CHECK(voice_route(mode, TRUE, TRUE, FALSE, FALSE, 1000.0f, 15.0f) == _voice_route_global, + "mode %d: the lobby's voice not heard by everyone", mode); + CHECK(voice_route(mode, TRUE, FALSE, TRUE, TRUE, 0.0f, 15.0f) == _voice_route_none, + "mode %d: the lobby heard with its voice off", mode); + } + return 0; + } + CASE("off") + { + CHECK(in_game(_voice_mode_off, TRUE, 0.0f, TRUE) == _voice_route_none, "off: a teammate heard"); + CHECK(in_game(_voice_mode_off, FALSE, 0.0f, TRUE) == _voice_route_none, "off: an enemy heard"); + return 0; + } + CASE("team-proximity") + { + CHECK(in_game(_voice_mode_team_proximity, TRUE, 10.0f, TRUE) == _voice_route_proximity, "a near teammate"); + CHECK(in_game(_voice_mode_team_proximity, TRUE, 20.0f, TRUE) == _voice_route_none, "a far teammate heard"); + CHECK(in_game(_voice_mode_team_proximity, FALSE, 1.0f, TRUE) == _voice_route_none, "a near enemy heard"); + CHECK(in_game(_voice_mode_team_proximity, TRUE, 1.0f, FALSE) == _voice_route_none, "the dead heard near"); + return 0; + } + CASE("team-enemy-proximity") + { + CHECK(in_game(_voice_mode_team_enemy_proximity, TRUE, 10.0f, TRUE) == _voice_route_proximity, + "a near teammate"); + CHECK(in_game(_voice_mode_team_enemy_proximity, FALSE, 10.0f, TRUE) == _voice_route_proximity, + "a near enemy"); + CHECK(in_game(_voice_mode_team_enemy_proximity, TRUE, 20.0f, TRUE) == _voice_route_none, + "a far teammate heard"); + CHECK(in_game(_voice_mode_team_enemy_proximity, FALSE, 20.0f, TRUE) == _voice_route_none, + "a far enemy heard"); + return 0; + } + CASE("team-global") + { + CHECK(in_game(_voice_mode_team_global, TRUE, 1000.0f, FALSE) == _voice_route_global, + "a teammate anywhere, alive or not"); + CHECK(in_game(_voice_mode_team_global, FALSE, 1.0f, TRUE) == _voice_route_none, "a near enemy heard"); + return 0; + } + CASE("team-global-enemy-proximity") + { + CHECK(in_game(_voice_mode_team_global_enemy_proximity, TRUE, 1000.0f, FALSE) == _voice_route_global, + "a teammate anywhere"); + CHECK(in_game(_voice_mode_team_global_enemy_proximity, FALSE, 10.0f, TRUE) == _voice_route_proximity, + "a near enemy"); + CHECK(in_game(_voice_mode_team_global_enemy_proximity, FALSE, 20.0f, TRUE) == _voice_route_none, + "a far enemy heard"); + CHECK(in_game(_voice_mode_team_global_enemy_proximity, FALSE, 1.0f, FALSE) == _voice_route_none, + "a dead enemy heard"); + return 0; + } + /* a quality's packets fit, and no more than twice their share */ + CASE("packet-limit") + { + short kbps; + + for (kbps = 8; kbps <= 64; kbps++) + { + long share = (long)kbps * 1000 / 8 / 50; + + CHECK(voice_packet_limit(kbps) >= share, "%d kbps: its own packets refused", kbps); + CHECK(voice_packet_limit(kbps) <= 2 * share + 16, "%d kbps: %ld bytes taken", kbps, voice_packet_limit(kbps)); + CHECK(voice_packet_limit(kbps) <= VOICE_MAXIMUM_PACKET, "%d kbps: more than a packet", kbps); + } + return 0; + } + fprintf(stderr, "unknown case: %s\n", case_name); + return 2; +} diff --git a/tools/harness/tests/votekick_count.c b/tools/harness/tests/votekick_count.c new file mode 100644 index 00000000..110fb963 --- /dev/null +++ b/tools/harness/tests/votekick_count.c @@ -0,0 +1,164 @@ +/* +VOTEKICK_COUNT.C (test) + +The real count of port/linux/game/network_votekick.c, which stands between +the game's players and a kick: who may vote, whose votes are one person's, +and how many kick. test_votekick_count.py takes the constants and the +voter's structure (config.inc) and the code (under_test.inc). +*/ + +#include "harness.h" + +#define csstrcmp strcmp + +#include "config.inc" +#include "under_test.inc" + +static struct votekick_voter voters[16]; +static short voter_count; + +static struct votekick_voter *add( + unsigned long address, + char const *hardware_id, + boolean eligible, + boolean voted) +{ + struct votekick_voter *voter = &voters[voter_count++]; + + memset(voter, 0, sizeof(*voter)); + voter->address = address; + snprintf(voter->hardware_id, sizeof(voter->hardware_id), "%s", hardware_id); + voter->eligible = eligible; + voter->voted = voted; + return voter; +} + +static void target( + unsigned long address, + char const *hardware_id) +{ + add(address, hardware_id, TRUE, FALSE)->target = TRUE; +} + +/* the count: votes, those who may vote, and the votes needed */ +static void count( + short *votes, + short *electorate, + short *needed) +{ + votekick_count(voters, voter_count, votes, electorate); + *needed = votekick_votes_needed(*electorate); +} + +int main(int argc, char **argv) +{ + char const *case_name = argc > 1 ? argv[1] : ""; + short votes, electorate, needed; + + /* more than half of everyone, the target counted */ + CASE("majority") + { + target(0x0A000001, "t"); + add(0x0A000002, "a", TRUE, TRUE); + add(0x0A000003, "b", TRUE, TRUE); + add(0x0A000004, "c", TRUE, TRUE); + add(0x0A000005, "d", TRUE, FALSE); + add(0x0A000006, "e", TRUE, FALSE); + count(&votes, &electorate, &needed); + CHECK(electorate == 6, "%d may vote, not 6", electorate); + CHECK(needed == 4, "%d needed of 6, not 4", needed); + CHECK(votes == 3, "%d votes, not 3", votes); + return 0; + } + /* one team of an even game kicks nobody from the other */ + CASE("even-teams") + { + unsigned long index; + + for (index = 0; index < 4; index++) + add(0x0A000010 + index, "", TRUE, TRUE); + target(0x0A000020, ""); + for (index = 1; index < 4; index++) + add(0x0A000020 + index, "", TRUE, FALSE); + count(&votes, &electorate, &needed); + CHECK(votes == 4 && electorate == 8, "%d votes of %d", votes, electorate); + CHECK(votes < needed, "one team of four kicked from the other (%d needed)", needed); + return 0; + } + /* two people never kick each other, nor one alone */ + CASE("two-never-kick") + { + target(0x0A000001, ""); + add(0x0A000002, "", TRUE, TRUE); + count(&votes, &electorate, &needed); + CHECK(votes < needed, "one of two kicked the other (%d of %d)", votes, needed); + CHECK(votekick_votes_needed(0) >= 2 && votekick_votes_needed(1) >= 2, "one vote kicked"); + return 0; + } + /* machines of one address: one vote, and counted once */ + CASE("same-address-one-vote") + { + target(0x0A000001, ""); + add(0x0A000002, "a", TRUE, TRUE); + add(0x0A000002, "b", TRUE, TRUE); + add(0x0A000002, "c", TRUE, TRUE); + add(0x0A000003, "d", TRUE, FALSE); + count(&votes, &electorate, &needed); + CHECK(votes == 1, "one address voted %d times", votes); + CHECK(electorate == 3, "%d may vote, not 3 addresses", electorate); + return 0; + } + /* machines of one hardware id: one vote */ + CASE("same-hardware-id-one-vote") + { + target(0x0A000001, ""); + add(0x0A000002, "abc", TRUE, TRUE); + add(0x0A000003, "abc", TRUE, TRUE); + count(&votes, &electorate, &needed); + CHECK(votes == 1, "one hardware id voted %d times", votes); + return 0; + } + /* a hardware id copied from another (anyone may tell any) never lowers + the votes needed: those who may vote are counted by address */ + CASE("copied-hardware-id-lowers-nothing") + { + target(0x0A000001, "t"); + add(0x0A000002, "a", TRUE, TRUE); + add(0x0A000003, "b", TRUE, FALSE); + add(0x0A000004, "c", TRUE, FALSE); + add(0x0A000005, "a", TRUE, FALSE); + add(0x0A000006, "b", TRUE, FALSE); + count(&votes, &electorate, &needed); + CHECK(electorate == 6, "copied hardware ids made %d who may vote, not 6", electorate); + CHECK(needed == 4, "%d needed, not 4", needed); + return 0; + } + /* the target's person never votes against it, by address or id */ + CASE("target-never-votes") + { + target(0x0A000001, "t"); + add(0x0A000001, "x", TRUE, TRUE); + add(0x0A000002, "t", TRUE, TRUE); + add(0x0A000003, "y", TRUE, TRUE); + count(&votes, &electorate, &needed); + CHECK(votes == 1, "the target's address or id voted (%d votes)", votes); + return 0; + } + /* those who have not played long enough neither vote nor count; an + address not known neither; the host's machine does */ + CASE("eligibility") + { + target(0x0A000001, ""); + add(0x0A000002, "", FALSE, TRUE); + add(0x0A000003, "", FALSE, TRUE); + add(0, "z", TRUE, TRUE); + add(0, "", TRUE, TRUE)->host = TRUE; + add(0x0A000004, "", TRUE, TRUE); + count(&votes, &electorate, &needed); + CHECK(votes == 2, "%d votes, not the host's and one other", votes); + CHECK(electorate == 3, "%d may vote, not 3", electorate); + return 0; + } + fprintf(stderr, "unknown case: %s\n", case_name); + return 2; +} diff --git a/tools/linux_build.py b/tools/linux_build.py index 5569daec..496fa950 100644 --- a/tools/linux_build.py +++ b/tools/linux_build.py @@ -29,12 +29,13 @@ def xdk_headers() -> List[Path]: def game_sources(config: Dict[str, Any]) -> List[Path]: """the game's C sources (port.json "game"): every one under its root but - those excluded""" + those excluded (a file, or a folder: a name ending in "/")""" game = config["game"] - excluded = set(game.get("exclude", [])) + excluded = game.get("exclude", []) return sorted( source for source in Path(game["root"]).rglob("*.c") - if source.as_posix() not in excluded + if not any(source.as_posix() == name or (name.endswith("/") and source.as_posix().startswith(name)) + for name in excluded) ) @@ -151,6 +152,25 @@ def updater_defines(release: bool) -> str: flavor = "release" if release else "debug" return f'-DHALO_BUILD_NUMBER={number} -DHALO_BUILD_FLAVOR=\\"{flavor}\\"' +def configuration_defines(sln: Any) -> List[str]: + """what configure.py's options define for every unit of a native build: + --release (no assertions), --profile (the profiling build's recording, + port/linux/src/profile_trace.c)""" + defines = [] + if getattr(sln, "port_release", False): + defines.append("-DHALO_RELEASE") + if getattr(sln, "port_profile", False): + defines.append("-DHALO_PROFILE") + return defines + + +def check_profile_options(profile: bool, pgo: str) -> None: + """A profiling build is optimised with the committed profiles or none: + trained, a profile would record the profiling code's own paths.""" + if profile and pgo == "train": + raise ValueError("--profile cannot be used with --pgo=train: train profiles with a normal build") + + PLATFORM_FLAGS = [ "-std=gnu11", "-D_GNU_SOURCE", @@ -192,6 +212,23 @@ PROFILE_USE_FLAGS = [ ] +# voice chat's codec (port/third_party/opus; network_voice.c): the +# floating-point library in plain C, its files as sources.txt lists them +OPUS_DIR = Path("port/third_party/opus") +OPUS_DEFINES = ("-DOPUS_BUILD", "-DVAR_ARRAYS", "-DHAVE_LRINTF", "-DHAVE_LRINT") + + +def opus_sources() -> List[Path]: + """Opus's library sources (port/third_party/opus/sources.txt)""" + return [OPUS_DIR / line.strip() for line in (OPUS_DIR / "sources.txt").read_text().splitlines() if line.strip()] + + +def opus_cflags(abi: str) -> str: + """the flags Opus's sources build with, on the platform layer's ABI""" + return " ".join([abi, "-std=gnu11", "-O2", *OPUS_DEFINES, *(f"-I{OPUS_DIR / name}" for name in + ("include", "celt", "silk", "silk/float")), "-w"]) + + def musl_math_sources() -> List[Path]: """musl's maths functions the game uses (port/third_party/musl-math)""" return sorted((MUSL_MATH_DIR / "src").glob("*.c")) @@ -211,6 +248,77 @@ def march_flag(sln: Any) -> str: return "-march=x86-64" if getattr(sln, "port_portable", False) else "-march=native" +# The portable build (configure.py --portable, as tools/ci_build.py builds) +# starts on other Linux systems than the one that built it, the Steam Deck's +# among them: it is compiled and linked against Debian 11's glibc 2.31, not +# the build machine's (an executable asks for the glibc version it was built +# with, or older), and it brings its own SDL3, libSDL3.so.0 beside the +# executable, which finds it there (an rpath of $ORIGIN), as few +# distributions have a 32-bit SDL3. tools/linux_sysroot.py downloads the +# system root and SDL's source, and tools/linux_sysroot.cmake builds SDL +# against it. +SYSROOT_SCRIPT = Path("tools/linux_sysroot.py") +SYSROOT_TOOLCHAIN = Path("tools/linux_sysroot.cmake") +# SDL's options: what it would build on a desktop distribution, its X11, +# Wayland (with libdecor's window decorations, which GNOME's Wayland asks +# for), PipeWire, PulseAudio and ALSA loaded when it starts (dlopen), less +# what needs libraries the system root does not have and the game does not +# use (KMS/DRM without a desktop, input methods, libusb, JACK, sndio, +# io_uring) and SDL's own tests +SDL_OPTIONS = [ + "-DCMAKE_BUILD_TYPE=Release", "-DSDL_SHARED=ON", "-DSDL_STATIC=OFF", "-DSDL_TEST_LIBRARY=OFF", + "-DSDL_TESTS=OFF", "-DSDL_EXAMPLES=OFF", "-DSDL_KMSDRM=OFF", "-DSDL_WAYLAND_LIBDECOR=ON", + "-DSDL_WAYLAND_LIBDECOR_SHARED=ON", "-DSDL_IBUS=OFF", "-DSDL_HIDAPI_LIBUSB=OFF", "-DSDL_JACK=OFF", + "-DSDL_SNDIO=OFF", "-DSDL_LIBURING=OFF", "-DSDL_FRIBIDI=OFF", "-DSDL_LIBTHAI=OFF", "-DSDL_X11_XTEST=OFF", +] + + +def portable_sdl(n: Writer, sln: Any, build_dir: Path, cc: str) -> Tuple[List[str], List[str], Path, Path, Path]: + """The portable build's system root and SDL3: the compiler's flags, the + linker's, what every unit waits for (the system root's stamp), the + libSDL3.so.0 that the executable links to, and SDL's license (which the + release carries beside it).""" + third_party = build_dir / "third_party" + sysroot = third_party / "sysroot" + sdl_source = third_party / "SDL3" + stamp = third_party / "stamp" + sdl_build = build_dir / "sdl3-build" + libsdl = sdl_build / "libSDL3.so.0" + n.rule( + name="linux_sysroot", + command=f"$python {SYSROOT_SCRIPT} {third_party}", + description="LINUX SYSROOT (Debian 11 i386, SDL3 source)", + restat=True, + ) + # (and SDL's license, which the release carries) + sdl_license_source = sdl_source / "LICENSE.txt" + n.build(outputs=stamp, rule="linux_sysroot", implicit=[SYSROOT_SCRIPT], implicit_outputs=[sdl_license_source]) + launcher = getattr(sln, "compiler_launcher", None) + options = SDL_OPTIONS + ([f"-DCMAKE_C_COMPILER_LAUNCHER={launcher}"] if launcher else []) + n.rule( + name="linux_sdl3", + command=(f"cmake -S {sdl_source} -B {sdl_build} -G Ninja --fresh " + f"-DCMAKE_TOOLCHAIN_FILE=$$PWD/{SYSROOT_TOOLCHAIN} -DHALO_SYSROOT=$$PWD/{sysroot} " + f"-DCMAKE_C_COMPILER={cc} {' '.join(options)} > {build_dir}/sdl3-configure.log && " + f"ninja -C {sdl_build} > {build_dir}/sdl3-build.log && " + f"$python {SYSROOT_SCRIPT} --check-sdl " + f"{sdl_build}/include-config-release/build_config/SDL_build_config.h"), + description="LINUX SDL3", + pool="console", + restat=True, + ) + n.build(outputs=libsdl, rule="linux_sdl3", implicit=[stamp, SYSROOT_TOOLCHAIN]) + cflags = [f"--sysroot={_quote(sysroot)}", f"-isystem {_quote(sdl_source / 'include')}"] + # (DT_RPATH rather than DT_RUNPATH: it comes before LD_LIBRARY_PATH, + # which Steam sets for the games it starts, so that the SDL beside the + # executable is the one loaded, whatever other SDL that path holds) + ldflags = [f"--sysroot={_quote(sysroot)}", f"-L{_quote(sdl_build)}", "'-Wl,-rpath,$$ORIGIN'", + "-Wl,--disable-new-dtags"] + sdl_license = build_dir / "SDL3-LICENSE.txt" + n.build(outputs=sdl_license, rule="linux_copy", inputs=sdl_license_source) + return cflags, ldflags, stamp, libsdl, sdl_license + + def lto_mode(sln: Any) -> str: """full, thin or off (configure.py --lto)""" return getattr(sln, "port_lto", "full") @@ -372,6 +480,8 @@ def generate_linux_build(n: Writer, sln: Any) -> None: rspfile_content="$in_newline", ) + n.rule(name="linux_copy", command="cp -L $in $out", description="LINUX COPY $out") + n.rule( name="linux_pgo_train", command="$python tools/pgo_train.py --binary $binary --work $work --output $out", @@ -382,11 +492,20 @@ def generate_linux_build(n: Writer, sln: Any) -> None: # the high-res HUD's textures (port/assets/hud; port/linux/src/hud_hires.c) embedded_assets = hud_assets_build(n, "linux", build_dir / "generated" / "hud_hires_assets.c") + # the portable build's system root and SDL3, or the build machine's + if getattr(sln, "port_portable", False): + sysroot_cflags, sysroot_ldflags, sysroot_stamp, libsdl, sdl_license = portable_sdl(n, sln, build_dir, cc) + sysroot_inputs = [sysroot_stamp] + sdl_outputs = [build_dir / libsdl.name, sdl_license] + else: + sysroot_cflags, sysroot_ldflags, sysroot_inputs, libsdl, sdl_outputs = [], [], [], None, [] + target = [march_flag(sln), *sysroot_cflags] + # (a debug build checks its stack frames, and stops at the first one # overrun, as it stops at the first failed assertion; a release build # does not, so that an overrun nobody has met cannot end a game) - abi = " ".join(LINUX_ABI_FLAGS + [march_flag(sln)] + (["-DHALO_RELEASE"] if getattr(sln, "port_release", False) - else ["-fstack-protector-strong"])) + abi = " ".join(LINUX_ABI_FLAGS + target + configuration_defines(sln) + + ([] if getattr(sln, "port_release", False) else ["-fstack-protector-strong"])) port_include = PORT_DIR / "include" sdk_flags = f"-idirafter {XDK_INCLUDE}" libs = " ".join(f"-l{lib}" for lib in config.get("libraries", [])) @@ -412,7 +531,7 @@ def generate_linux_build(n: Writer, sln: Any) -> None: # (the SDK declarations are system headers, which the depfile # leaves out) implicit=[*xdk_headers(), prefix_header, semantics_header, platform_semantics_header, - *implicit_inputs], + *sysroot_inputs, *implicit_inputs], variables={"cflags": f"{cflags} {posix_extra if posix else extra}"}, ) @@ -455,7 +574,7 @@ def generate_linux_build(n: Writer, sln: Any) -> None: "-Isource -Isource/cseries", sdk_flags, ]) - posix_cflags = " ".join(POSIX_FLAGS + [march_flag(sln), f"-I{platform_dir}"]) + posix_cflags = " ".join(POSIX_FLAGS + target + [f"-I{platform_dir}"]) mbedtls_include = f"-I{MBEDTLS_DIR / 'include'}" for source in sorted(platform_dir.glob("*.c")): if source.name == "posix_update.c": @@ -475,15 +594,16 @@ def generate_linux_build(n: Writer, sln: Any) -> None: # wcslen, which linux_link_check.py rejects: the game's wchar_t is # 16-bit) for source in sorted((MBEDTLS_DIR / "library").glob("*.c")): - add_object(source, " ".join(POSIX_FLAGS + [march_flag(sln), mbedtls_include, - f"-I{MBEDTLS_DIR / 'library'}", "-fno-builtin-wcslen", - "-w"]), posix=True) + add_object(source, " ".join(POSIX_FLAGS + target + [mbedtls_include, + f"-I{MBEDTLS_DIR / 'library'}", "-fno-builtin-wcslen", + "-w"]), posix=True) # internet play's UPnP (port/third_party/miniupnpc), with the host's # ABI as posix_upnp.c, which uses it for source in miniupnpc_sources(): - add_object(source, " ".join(POSIX_FLAGS + [march_flag(sln), *MINIUPNPC_DEFINES, - f"-I{MINIUPNPC_DIR / 'include'}", f"-I{MINIUPNPC_DIR / 'src'}", - "-fno-builtin-wcslen", "-w"]), posix=True) + add_object(source, " ".join(POSIX_FLAGS + target + [*MINIUPNPC_DEFINES, + f"-I{MINIUPNPC_DIR / 'include'}", + f"-I{MINIUPNPC_DIR / 'src'}", + "-fno-builtin-wcslen", "-w"]), posix=True) # the settings file's parser (port/third_party/tomlc17), with the # platform layer's ABI (its structs hold doubles) and nothing else add_object(TOML_DIR / "tomlc17.c", " ".join([abi, "-std=gnu11", "-w"])) @@ -492,6 +612,9 @@ def generate_linux_build(n: Writer, sln: Any) -> None: add_object(EXPAT_DIR / name, " ".join([abi, "-std=gnu11", f"-I{EXPAT_DIR}", "-w"])) # internet play's reliable streams (port/third_party/kcp; p2p.c) add_object(KCP_DIR / "ikcp.c", " ".join([abi, "-std=gnu11", "-w"])) + # voice chat's codec (port/third_party/opus) + for source in opus_sources(): + add_object(source, opus_cflags(abi)) # internet play's signatures, for public games' listings # (port/third_party/monocypher; p2p_crypto.c) for name in ("monocypher.c", "monocypher-ed25519.c"): @@ -509,14 +632,18 @@ def generate_linux_build(n: Writer, sln: Any) -> None: rule="linux_link", inputs=objects, variables={ - "ldflags": " ".join(["--target=i686-linux-gnu", "-m32", "-no-pie", "-g", *extra_ldflags, + "ldflags": " ".join(["--target=i686-linux-gnu", "-m32", "-no-pie", "-g", *sysroot_ldflags, + *extra_ldflags, # (posix_trace_marker.c's, which the GPU driver's calls must reach) *(f"-Wl,--export-dynamic-symbol={name}" for name in ("open", "open64", "openat", "openat64"))]), "libs": libs, }, - implicit=[Path("tools/linux_link_check.py")], + implicit=[Path("tools/linux_link_check.py"), *([libsdl] if libsdl else [])], ) + # (the portable build's SDL3, beside the executable) + if libsdl is not None: + n.build(outputs=output.parent / libsdl.name, rule="linux_copy", inputs=libsdl) # the tag validator (port/linux/game/tag_validate.c, tag_schema*.c) # alone on map files: the game's objects of it, and a program that @@ -529,6 +656,7 @@ def generate_linux_build(n: Writer, sln: Any) -> None: outputs=tool_object, rule="linux_cc", inputs=tool, + implicit=sysroot_inputs, variables={"cflags": " ".join([posix_cflags, f"-I{ZLIB_DIR}", *ZLIB_DEFINES, posix_extra])}, ) # (and the Custom Edition maps' loader, cache_file_formats.c) @@ -540,7 +668,8 @@ def generate_linux_build(n: Writer, sln: Any) -> None: outputs=validator, rule="linux_tool_link", inputs=tool_objects, - variables={"ldflags": " ".join(["--target=i686-linux-gnu", "-m32", "-no-pie", "-g", *extra_ldflags])}, + variables={"ldflags": " ".join(["--target=i686-linux-gnu", "-m32", "-no-pie", "-g", *sysroot_ldflags, + *extra_ldflags])}, ) # Profile-guided optimisation: with the committed profile, or with @@ -554,7 +683,8 @@ def generate_linux_build(n: Writer, sln: Any) -> None: n.build( outputs=profile, rule="linux_pgo_train", - order_only=[instrumented], + # (and the portable build's SDL3 beside it, which it loads) + order_only=[instrumented, *([instrumented.parent / libsdl.name] if libsdl else [])], variables={"binary": str(instrumented), "work": str(sln.build_dir / "pgo" / "linux")}, ) @@ -566,7 +696,7 @@ def generate_linux_build(n: Writer, sln: Any) -> None: emit(obj_dir, output, cflags, ldflags, [profile] if profile else [], validator) # internet play's MQTT brokers, a file beside the game (network.brokers_file) brokers = build_dir / "brokers.txt" - n.rule(name="linux_copy", command="cp $in $out", description="LINUX COPY $out") n.build(outputs=brokers, rule="linux_copy", inputs=Path("port/assets/network/brokers.txt")) - n.build(outputs="linux", rule="phony", inputs=[output, brokers, validator]) + n.build(outputs="linux", rule="phony", + inputs=[output, brokers, validator, *sdl_outputs]) n.newline() diff --git a/tools/linux_sysroot.cmake b/tools/linux_sysroot.cmake new file mode 100644 index 00000000..e9583f9e --- /dev/null +++ b/tools/linux_sysroot.cmake @@ -0,0 +1,18 @@ +# CMake toolchain of the portable Linux build's SDL3 (tools/linux_build.py): +# clang for 32-bit x86, against the system root of tools/linux_sysroot.py, +# given as -DHALO_SYSROOT=. The libraries SDL finds there it loads +# when it starts (dlopen), by the names it reads from them. +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR i686) +set(CMAKE_C_COMPILER_TARGET i686-linux-gnu) +set(CMAKE_SYSROOT "${HALO_SYSROOT}") +set(CMAKE_LIBRARY_ARCHITECTURE i386-linux-gnu) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +set(ENV{PKG_CONFIG_SYSROOT_DIR} "${HALO_SYSROOT}") +set(ENV{PKG_CONFIG_LIBDIR} "${HALO_SYSROOT}/usr/lib/i386-linux-gnu/pkgconfig:${HALO_SYSROOT}/usr/share/pkgconfig") +set(ENV{PKG_CONFIG_PATH} "") +# (and in the projects CMake tries compiling in) +list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES HALO_SYSROOT) diff --git a/tools/linux_sysroot.py b/tools/linux_sysroot.py new file mode 100644 index 00000000..6edc34bb --- /dev/null +++ b/tools/linux_sysroot.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python3 +"""The 32-bit x86 system root of the portable Linux build (configure.py +--portable), and the SDL3 source it builds libSDL3.so.0 from. + +An executable built against the build machine's glibc asks for the symbols +of that version (GLIBC_2.43 on an up-to-date Arch Linux) and starts on no +older system. The portable build is therefore compiled and linked against +Debian 11's i386 glibc 2.31, the baseline of the Steam Runtime 3 ("sniper"), +which SteamOS 3, the Steam Deck and every distribution since 2020 have, and +against the headers of the libraries SDL loads when it starts (X11, Wayland +and libdecor, PipeWire, PulseAudio, ALSA, D-Bus, udev), which it opens itself +(dlopen) and does not link to. + +Each package comes from the first of these that has it: archive.debian.org, +which keeps every Debian release, deb.debian.org, and last +snapshot.debian.org, whose URLs (the archive as it was at a moment when the +package was in it) do not change. SDL's source comes from its GitHub release, +or libsdl.org's. Each download is tried a few times, and checked against the +SHA-256 digests here. + + python tools/linux_sysroot.py + +makes /sysroot and /SDL3 once, and writes /stamp +(the list of what it holds) last; a folder whose stamp matches is left as it +is. Python alone: no dpkg, no root. + + python tools/linux_sysroot.py --check-sdl + +stops the build if SDL was built without one of the drivers it needs. +""" + +import hashlib +import http.client +import io +import os +import shutil +import sys +import tarfile +import time +import urllib.error +import urllib.request +from pathlib import Path +from typing import List, Tuple + +DEBIAN_MIRRORS = [ + "https://archive.debian.org/debian/", + "https://deb.debian.org/debian/", +] + +# (and the last resort: the archive as snapshot.debian.org has it at the time +# given with each package, when the package was in it) +SNAPSHOT = "https://snapshot.debian.org/archive/debian/{}/" + +# (path in the Debian archive, SHA-256, a snapshot.debian.org time that has +# it), all i386 from Debian 11 "bullseye" but PipeWire's (bullseye-backports': +# SDL needs 0.3.44 or later, bullseye has 0.3.19) and Wayland's and +# libdecor's (below). Only the C library's and libgcc's are linked to; the +# rest are SDL's headers, and the libraries it reads the names of. +PACKAGES: List[Tuple[str, str, str]] = [ + # the C library and the compiler's runtime (crtbegin.o, libgcc) + ("pool/main/g/glibc/libc6_2.31-13+deb11u11_i386.deb", + "8198abd817cbcd615ce3207fb924317b53cc0b522bfe57d689757de13fe00d66", "20240817T023924Z"), + ("pool/main/g/glibc/libc6-dev_2.31-13+deb11u11_i386.deb", + "7d10b944894d2b6ccab08ccf51aae6cef6c5f5773f36696e6d9f34c21e0365b4", "20240817T023924Z"), + ("pool/main/l/linux/linux-libc-dev_5.10.223-1_i386.deb", + "e57d6065a43e763e54a0a80afd66fa2371bcdc3b035f9f4463816e9c6fc26049", "20240815T030336Z"), + ("pool/main/g/gcc-10/libgcc-s1_10.2.1-6_i386.deb", + "0a52edec5d626f8d9acf1998248d276760aff3950f44a308d27d4a2a488fe18d", "20210111T024320Z"), + ("pool/main/g/gcc-10/libgcc-10-dev_10.2.1-6_i386.deb", + "64a544dcf5463225603cb025a05c7b79dea9876004c09d81af2bc31ffe0ccc1e", "20210111T024320Z"), + # SDL's X11 video driver + ("pool/main/libx/libx11/libx11-6_1.7.2-1+deb11u2_i386.deb", + "a0307da50139370781beac6fe6d3b173493af11d0a35c43c053c539d5d67969c", "20231012T085604Z"), + ("pool/main/libx/libx11/libx11-dev_1.7.2-1+deb11u2_i386.deb", + "6674c5517140f65addb055b2fcbab42e83c299555ecf516daf40758f938cf2d0", "20231012T085604Z"), + ("pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb", + "d5568d587d9ad2664c34c14b0ac538ccb3c567e126ee5291085a8de704a565f5", "20200415T024424Z"), + # (xcb/xcb.h, for SDL's Vulkan headers) + ("pool/main/libx/libxcb/libxcb1-dev_1.14-3_i386.deb", + "b1d522ad25e5d4f208c73c2f20e6c918a869d11171835b7f792f487ff367d788", "20210201T212616Z"), + ("pool/main/libx/libxext/libxext6_1.3.3-1.1_i386.deb", + "b93e2d194c4d0aa5ca0c056883787b1fac35941f5ce9ec4fa6baedfba4cdb5a9", "20201218T204051Z"), + ("pool/main/libx/libxext/libxext-dev_1.3.3-1.1_i386.deb", + "0f2ca4c3c5e74e6ce6c99da60cbf1a66de1665a5703ddfb725a5855cc9ef7281", "20201218T204051Z"), + ("pool/main/libx/libxcursor/libxcursor1_1.2.0-2_i386.deb", + "593d4ac35460cc397d1fa96d251b62c26eb3f807b2e0bfe21dc33abef68811eb", "20190714T093329Z"), + ("pool/main/libx/libxcursor/libxcursor-dev_1.2.0-2_i386.deb", + "c3db4042a513952624d6f06485599f1c84d4095ebfaa753ddc243fa07f4bf81c", "20190714T093329Z"), + ("pool/main/libx/libxi/libxi6_1.7.10-1_i386.deb", + "004182ad1167319b5153fa736a41b646806586739db93e6a06db857ff8553505", "20200603T030403Z"), + ("pool/main/libx/libxi/libxi-dev_1.7.10-1_i386.deb", + "7a1b570d9bdf70d7e79e8b03a87c48cbb40a68ed672920f6170c67ec270edfc2", "20200603T030403Z"), + ("pool/main/libx/libxrandr/libxrandr2_1.5.1-1_i386.deb", + "b9a2d67526ee1a6cd4eda834534a21bd4a5a3c795a4945214553933560d21ce2", "20161207T032837Z"), + ("pool/main/libx/libxrandr/libxrandr-dev_1.5.1-1_i386.deb", + "bffbcd2b6eb5459aebada2adba44b32415cfdb8e27e2ee736b908e4c29f03360", "20161207T032837Z"), + ("pool/main/libx/libxfixes/libxfixes3_5.0.3-2_i386.deb", + "2250bbc75dcf0d73fc84b69150dcaefefc071ad2a28985a5a69abfeac2290f11", "20200414T204951Z"), + ("pool/main/libx/libxfixes/libxfixes-dev_5.0.3-2_i386.deb", + "3c81c546f61ed7af6a6a30ad3709b75dc74a983dff8f00dcce30eaff5a8c18e1", "20200414T204951Z"), + ("pool/main/libx/libxrender/libxrender1_0.9.10-1_i386.deb", + "0b71b3f530a1b039b64f74c0223b433bd73fe0164916b60b25f9f3f11a6b9bfb", "20161205T212728Z"), + ("pool/main/libx/libxrender/libxrender-dev_0.9.10-1_i386.deb", + "7a4a1cbc6e2004c520dc5882e0367c8ce5c7094e07922e792992d1d27a5829a4", "20161205T212728Z"), + ("pool/main/libx/libxss/libxss1_1.2.3-1_i386.deb", + "eb50d23f8e849a7f86a6313f3e48c5c1fbf6cf82ca34ea5ef32bc99ae980c64a", "20180906T152804Z"), + ("pool/main/libx/libxss/libxss-dev_1.2.3-1_i386.deb", + "e3f4268c8c5ca5a1e7a2b549470f9eca58cf9974b0176a929dd2185026895e7e", "20180906T152804Z"), + # SDL's OpenGL (GL/gl.h, GL/glx.h) + ("pool/main/libg/libglvnd/libgl-dev_1.3.2-1_i386.deb", + "1603066c9e658adbd71296b9a5db08d771c92900ac90035b9e210b9d7e2b7a3d", "20200729T210030Z"), + ("pool/main/libg/libglvnd/libglx-dev_1.3.2-1_i386.deb", + "97c1367f7ecf6c7e103a0eb85e119b3e22d9d882d52587a53733ff93daa94c1d", "20200729T210030Z"), + # SDL's Wayland video driver: Debian 12's Wayland (1.21), whose headers + # match the protocol code that a recent wayland-scanner writes + # (wl_proxy_marshal_flags, Wayland 1.20) + ("pool/main/w/wayland/libwayland-client0_1.21.0-1_i386.deb", + "1454b74db0b175bd4ef2b14ca6e66269e5d05e1bafa4fdaa6efa15424872061a", "20220709T031205Z"), + ("pool/main/w/wayland/libwayland-cursor0_1.21.0-1_i386.deb", + "5ea0a94c004d546fc41e426a47269b4251af8a0e691db57a011d72f43c62d3b7", "20220709T031205Z"), + ("pool/main/w/wayland/libwayland-egl1_1.21.0-1_i386.deb", + "0b73ffd5ddd6b3c6fbbd74dc0b5d8259614515e89e791fafe3472168f6b0149c", "20220709T031205Z"), + ("pool/main/w/wayland/libwayland-dev_1.21.0-1_i386.deb", + "c5051589b50623bc71a0a54e006571740b066ec61476b38808ef5f93fc0c95d3", "20220709T031205Z"), + # (libdecor, the window decorations GNOME's Wayland asks clients to draw: + # Debian 12's, as it needs Wayland 1.20's headers) + ("pool/main/libd/libdecor-0/libdecor-0-0_0.1.1-2_i386.deb", + "74b39bf96b3870899883c8e3b6a16bca75e8c5828614a7e1b9d061439ee6bb35", "20230211T151657Z"), + ("pool/main/libd/libdecor-0/libdecor-0-dev_0.1.1-2_i386.deb", + "31a307669c759629853ae06d7898a4e6355a9c155278b2a1a482d583ca3eac66", "20230211T151657Z"), + ("pool/main/libf/libffi/libffi-dev_3.3-6_i386.deb", + "a699fc6b1dbeff5f3633be8b3f8d31526b69d11bf435c5865e50ac249b98f94c", "20210221T031914Z"), + ("pool/main/libx/libxkbcommon/libxkbcommon0_1.0.3-2_i386.deb", + "b6d8929647c0c182f63d1c886b7fe8b23674383b2585401b2377ff61d2fe670e", "20201126T153155Z"), + ("pool/main/libx/libxkbcommon/libxkbcommon-dev_1.0.3-2_i386.deb", + "1a88ce18baafa1404452b451b0b3ce1a8eebf6204eedd498e38b34c5d6f42c44", "20201126T153155Z"), + ("pool/main/libg/libglvnd/libegl1_1.3.2-1_i386.deb", + "aba3025fe2becdcf9ed7d3822a52cd98ba6921fd8ccdd7825ec5a1f99b437fc8", "20200729T210030Z"), + ("pool/main/libg/libglvnd/libegl-dev_1.3.2-1_i386.deb", + "5073d41e5be4bffad4b19a5ebf93c2ad43b420d00417a778e76d84f831a47a66", "20200729T210030Z"), + # SDL's audio drivers + ("pool/main/a/alsa-lib/libasound2_1.2.4-1.1_i386.deb", + "1e7da3fa4ba22b7079bceb18e8ce3494d9dced9b1cffe5c6bb9aae8e64ecc9f7", "20210104T204018Z"), + ("pool/main/a/alsa-lib/libasound2-dev_1.2.4-1.1_i386.deb", + "7ea775aaa87bc4d391b800c5cbdcacdc298bb52d9a82dff31073860dc9737309", "20210104T204018Z"), + ("pool/main/p/pulseaudio/libpulse0_14.2-2_i386.deb", + "c84c1a3dbd17355cdf6eaf23c2313db18faf3581ca7ec95cf3a2177d1c70a577", "20210227T024018Z"), + ("pool/main/p/pulseaudio/libpulse-dev_14.2-2_i386.deb", + "c530edf490a67c2ece2bf54563872693d9f02ce6069e73b2b9b5372d965ef8e8", "20210227T024018Z"), + ("pool/main/p/pipewire/libpipewire-0.3-0_0.3.65-2~bpo11+1_i386.deb", + "1d032b638081704393bc847e2e1f7a00c6894fa262ed0272fb7282962e8915a9", "20230217T033139Z"), + ("pool/main/p/pipewire/libpipewire-0.3-dev_0.3.65-2~bpo11+1_i386.deb", + "5f67d3e5e435251a34e55a10b4834a794e6b48b83d6da39aab267b35ce9aba33", "20230217T033139Z"), + ("pool/main/p/pipewire/libspa-0.2-dev_0.3.65-2~bpo11+1_i386.deb", + "5128545520aa6444af8c3f61c72e5f8d39ced7556ce07dfdedcbec291c4a5048", "20230217T033139Z"), + # the desktop's D-Bus (the screen saver, the system's settings) and + # udev (gamepads plugged in and out) + ("pool/main/d/dbus/libdbus-1-3_1.12.28-0+deb11u1_i386.deb", + "7d8d30ca9cfa726f61e5b71dc6294ca3266147208d13cdec1fbefb217b0a9d25", "20230625T033524Z"), + ("pool/main/d/dbus/libdbus-1-dev_1.12.28-0+deb11u1_i386.deb", + "8ca893c7e096f70fd87745744a042507bd335bd6c13d98ba57bb6ecfa1170210", "20230625T033524Z"), + ("pool/main/s/systemd/libudev1_247.3-7+deb11u5_i386.deb", + "07891a18db3527472eaafb26e07ba26d3bfb926dbe2c01d3282e6b5fe61454cc", "20240616T023746Z"), + ("pool/main/s/systemd/libudev-dev_247.3-7+deb11u5_i386.deb", + "634a5cc9d7bc226d75ced0414750e32b07e82531e8b2b091e41708d42978db3d", "20240616T023746Z"), +] + +# SDL3's source release (the version of the other ports' SDL), from GitHub +# or libsdl.org +# (as tools/windows_build.py's SDL_VERSION and tools/android_build.py's SDL_TAG) +SDL_VERSION = "3.4.16" +SDL_URLS = [ + f"https://github.com/libsdl-org/SDL/releases/download/release-{SDL_VERSION}/SDL3-{SDL_VERSION}.tar.gz", + f"https://www.libsdl.org/release/SDL3-{SDL_VERSION}.tar.gz", +] +SDL_SHA256 = "7322236cd12090c3eb40b9728be4d49c76f66ad17d04369584d4ecad5cf77c68" + + +# what SDL must have been built with (its SDL_build_config.h): the drivers a +# desktop needs, each loaded when SDL starts rather than linked to (libdecor +# too: without its library's name, SDL would link to it, and the Steam Deck +# has no 32-bit libdecor) +SDL_REQUIRED = [ + "SDL_VIDEO_DRIVER_X11_DYNAMIC", "SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC", "HAVE_LIBDECOR_H", + "SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_LIBDECOR", "SDL_VIDEO_OPENGL_GLX", + "SDL_VIDEO_OPENGL_EGL", "SDL_AUDIO_DRIVER_PIPEWIRE_DYNAMIC", "SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC", + "SDL_AUDIO_DRIVER_ALSA_DYNAMIC", "SDL_UDEV_DYNAMIC", "HAVE_DBUS_DBUS_H", +] + + +def check_sdl(build_config: Path) -> None: + """stops (exit status 1) if SDL was built without one of SDL_REQUIRED, + as when the system root lacks a header SDL looks for""" + defined = {line.split()[1] for line in build_config.read_text().splitlines() + if line.startswith("#define ") and len(line.split()) > 2} + missing = [name for name in SDL_REQUIRED if name not in defined] + if missing: + sys.exit(f"SDL3 was built without {', '.join(missing)}: refer to {build_config.parent.parent.parent}" + "/CMakeFiles/CMakeConfigureLog.yaml") + + +def stamp_text() -> str: + """what a complete folder's stamp holds""" + return "".join(f"{path} {digest}\n" for path, digest, _ in PACKAGES) + f"{SDL_URLS[0]} {SDL_SHA256}\n" + + +# (each URL's tries, a moment apart, before the next URL) +TRIES = 3 +RETRY_SECONDS = 2 + + +def download(urls: List[str], digest: str) -> bytes: + """the file at the first of urls that answers, checked against digest""" + errors = [] + for url in urls: + for attempt in range(1, TRIES + 1): + if attempt > 1: + time.sleep(RETRY_SECONDS * (attempt - 1)) + try: + with urllib.request.urlopen(url, timeout=60) as response: + data = response.read() + # (a connection cut short is http.client.IncompleteRead, not an + # OSError) + except (OSError, http.client.HTTPException) as error: + errors.append(f"{url}: {error!r}") + # (the mirror does not have it: no use asking again) + if isinstance(error, urllib.error.HTTPError) and error.code == 404: + break + continue + actual = hashlib.sha256(data).hexdigest() + if actual == digest: + return data + errors.append(f"{url}: SHA-256 {actual}, expected {digest}") + raise RuntimeError("cannot download " + "; ".join(errors)) + + +def deb_data(deb: bytes) -> tarfile.TarFile: + """the data.tar.* member of a .deb (an ar archive)""" + if not deb.startswith(b"!\n"): + raise RuntimeError("not a Debian package") + offset = 8 + while offset + 60 <= len(deb): + header = deb[offset:offset + 60] + name = header[:16].decode().strip().rstrip("/") + size = int(header[48:58].decode()) + body = deb[offset + 60:offset + 60 + size] + if name.startswith("data.tar"): + return tarfile.open(fileobj=io.BytesIO(body), mode="r:*") + offset += 60 + size + (size & 1) + raise RuntimeError("a Debian package without data.tar") + + +# what the packages hold that the build does not need +UNUSED = (("usr", "share", "doc"), ("usr", "share", "man"), ("usr", "share", "locale"), ("usr", "share", "lintian"), + ("usr", "share", "bug"), ("usr", "bin"), ("sbin",), ("usr", "sbin")) + + +def extract(archive: tarfile.TarFile, destination: Path, strip: int = 0) -> None: + """an archive's files, folders and links into destination, nothing + outside it (no device files, no absolute or upward names, nothing + through a link of its own that leads out of it)""" + root = destination.resolve() + for member in archive.getmembers(): + parts = [part for part in Path(member.name).parts if part not in (".", "/")][strip:] + if not parts or ".." in parts or any(tuple(parts[:len(unused)]) == unused for unused in UNUSED): + continue + target = destination.joinpath(*parts) + if not target.parent.resolve().is_relative_to(root): + continue + if member.isdir(): + target.mkdir(parents=True, exist_ok=True) + elif member.isfile() or member.islnk(): + target.parent.mkdir(parents=True, exist_ok=True) + if target.is_symlink(): + target.unlink() + with archive.extractfile(member) as source, open(target, "wb") as output: + shutil.copyfileobj(source, output) + os.chmod(target, member.mode & 0o755 | 0o600) + elif member.issym(): + target.parent.mkdir(parents=True, exist_ok=True) + if target.is_symlink() or target.exists(): + target.unlink() + link = member.linkname + # (an absolute link, /lib/i386-linux-gnu/libudev.so.1, points + # into the system root instead of the build machine's) + if link.startswith("/"): + link = os.path.relpath(destination / link.lstrip("/"), target.parent) + os.symlink(link, target) + + +def make(folder: Path) -> None: + stamp = folder / "stamp" + if stamp.is_file() and stamp.read_text() == stamp_text(): + return + for name in ("stamp", "sysroot", "SDL3"): + path = folder / name + if path.is_dir(): + shutil.rmtree(path) + elif path.exists(): + path.unlink() + sysroot = folder / "sysroot" + sysroot.mkdir(parents=True) + for path, digest, snapshot in PACKAGES: + print(f"Downloading {path.rsplit('/', 1)[-1]}", flush=True) + mirrors = DEBIAN_MIRRORS + [SNAPSHOT.format(snapshot)] + with deb_data(download([mirror + path for mirror in mirrors], digest)) as archive: + extract(archive, sysroot) + print(f"Downloading SDL3-{SDL_VERSION}.tar.gz", flush=True) + with tarfile.open(fileobj=io.BytesIO(download(SDL_URLS, SDL_SHA256)), mode="r:gz") as archive: + extract(archive, folder / "SDL3", strip=1) + stamp.write_text(stamp_text()) + + +if __name__ == "__main__": + if len(sys.argv) == 3 and sys.argv[1] == "--check-sdl": + check_sdl(Path(sys.argv[2])) + elif len(sys.argv) == 2: + make(Path(sys.argv[1])) + else: + sys.exit(__doc__) diff --git a/tools/net_report.py b/tools/net_report.py new file mode 100644 index 00000000..5e947a1c --- /dev/null +++ b/tools/net_report.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +"""Summarize CPU profiling recordings. +Usage: python tools/net_report.py RECORDING [RECORDING2 ...] [options].""" + +import argparse +import csv +import json +import re +import sys +from pathlib import Path + +FORMAT = 1 +SUMMARY_LINE_LIMIT = 300 +# (the summary file always uses this, so its length does not depend on --top) +SUMMARY_TOP = 40 +REQUIRED_HEADER = ("part", "last_part", "role", "own_machine", "build", "platform", "map", + "players", "start_utc", "start_s", "duration_s", + "stop_reason") +PER_TICK_PREFIXES = ("game_tick", "network_distributed_tick") + +COLUMNS_GLOSSARY = """\ +scope CPU scope name (a profile section or frame timer); nested names use "." +count times the scope ran in the recording +total_ms summed inclusive time (children included), milliseconds +mean_ms total_ms / count +max_ms longest single run; aggregate sections: largest summed time in one frame +per_frame count / frames recorded +per_tick_ms total_ms / ticks recorded (game_tick.* and network_distributed_tick* only) +frame frame number in the recording +at_s seconds from the start of the recording to that frame +frame_ms that frame's duration +ticks game ticks that frame ran +longest scopes (ms) the frame's three scopes, at any depth, with the most time of their own (children not counted), name and that time""" + + +class ReportError(Exception): + pass + + +# ---------- reading a recording + + +def part_paths(recording): + """the recording's name, its part files by number, and the warnings""" + path = Path(recording) + match = re.match(r"^(.*)\.part(\d+)\.json$", path.name) + base = path.parent / (match.group(1) if match else re.sub(r"\.json$", "", path.name)) + parts = {} + for candidate in base.parent.glob(base.name + ".part*.json"): + number = re.match(r"^.*\.part(\d+)\.json$", candidate.name) + if number and candidate.name.startswith(base.name + ".part"): + parts[int(number.group(1))] = candidate + if not parts: + raise ReportError(f"no parts of {base}") + warnings = [f"part {number} of {base.name} is missing" for number in range(1, max(parts) + 1) + if number not in parts] + return base, [parts[number] for number in sorted(parts)], warnings + + +def read_head(path): + """the header and cpu tables, read before the event array""" + with open(path, "r", encoding="utf-8") as file: + return read_head_stream(file, path) + + +def read_head_stream(file, path): + text = "" + marker = '"traceEvents"' + while marker not in text: + chunk = file.read(65536) + if not chunk: + raise ReportError(f"{path}: has no traceEvents") + text += chunk + text = text.split(marker, 1)[0].rstrip().rstrip(",") + "}" + try: + head = json.loads(text) + except json.JSONDecodeError as error: + raise ReportError(f"{path}: not a recording part ({error})") from None + if not isinstance(head, dict): + raise ReportError(f"{path}: not a recording part") + for key in ("halo", "cpu_summary"): + if not isinstance(head.get(key), dict): + raise ReportError(f'{path}: has no "{key}"') + _validate_head_part(head, path) + return [head] + + +def _validate_head_part(head, path): + where = str(path) + for key in ("halo", "cpu_summary"): + if not isinstance(head.get(key), dict): + raise ReportError(f'{where}: has no "{key}"') + header = head["halo"].get("header") + if not isinstance(header, dict): + raise ReportError(f'{where}: has no "header" in "halo"') + for key in REQUIRED_HEADER: + if key not in header: + raise ReportError(f'{where}: the header has no "{key}"') + + +def table_dicts(table): + columns = table["columns"] + return [dict(zip(columns, row)) for row in table["rows"]] + + +class Recording: + """a recording's parts with their CPU summaries merged""" + + def __init__(self, recording): + self.base, paths, self.warnings = part_paths(recording) + heads = [head for part in paths for head in read_head(part)] + self.name = self.base.name + self.headers = [head["halo"]["header"] for head in heads] + self.parts = [header["part"] for header in self.headers] + self.complete = any(header["last_part"] for header in self.headers) + if not self.complete: + self.warnings.append(f"{self.name} has no last part: the recording is incomplete") + self.header = self.headers[0] + self.cpu = self.merge_cpu([head["cpu_summary"] for head in heads]) + + @staticmethod + def merge_cpu(summaries): + names = {} + frames = sum(summary["frames"] for summary in summaries) + ticks = sum(summary["ticks"] for summary in summaries) + worst = [] + for summary in summaries: + for row in table_dicts(summary): + totals = names.setdefault(row["name"], {"count": 0, "total_ms": 0.0, "max_ms": 0.0}) + totals["count"] += row["count"] + totals["total_ms"] += row["total_ms"] + totals["max_ms"] = max(totals["max_ms"], row["max_ms"]) + worst.extend(table_dicts(summary["worst_frames"])) + for name, totals in names.items(): + totals["mean_ms"] = totals["total_ms"] / totals["count"] if totals["count"] else 0.0 + totals["per_frame"] = totals["count"] / frames if frames else 0.0 + totals["per_tick_ms"] = (totals["total_ms"] / ticks if ticks and name.startswith(PER_TICK_PREFIXES) + else None) + worst.sort(key=lambda row: -row["frame_ms"]) + return {"frames": frames, "ticks": ticks, "names": names, "worst": worst[:10]} + + def select(self, start=None, end=None): + """Elapsed seconds from the recorded part headers, clipped to a window.""" + seconds = 0.0 + for header in self.headers: + begin = header["start_s"] + finish = begin + header["duration_s"] + seconds += max(0.0, min(finish, end if end is not None else finish) - + max(begin, start if start is not None else begin)) + return seconds + + +# ---------- the tables + + +def cpu_table(recording): + rows = [[name, totals["count"], totals["total_ms"], totals["mean_ms"], totals["max_ms"], totals["per_frame"], + totals["per_tick_ms"]] for name, totals in recording.cpu["names"].items()] + rows.sort(key=lambda row: (-row[2], row[0])) + return ["scope", "count", "total_ms", "mean_ms", "max_ms", "per_frame", "per_tick_ms"], rows + + +def worst_frame_table(recording): + rows = [[row["frame"], row["at_s"], row["frame_ms"], row["ticks"], + ", ".join(f"{name} {ms:.1f}" for name, ms in row["longest"])] for row in recording.cpu["worst"]] + return ["frame", "at_s", "frame_ms", "ticks", "longest scopes (ms)"], rows + + +# ---------- text + + +NUMBER_FORMATS = { + "count": "{:.0f}", "total_ms": "{:.1f}", "mean_ms": "{:.3f}", "max_ms": "{:.3f}", "per_frame": "{:.2f}", + "per_tick_ms": "{:.3f}", "frame": "{:.0f}", "at_s": "{:.2f}", "frame_ms": "{:.2f}", "ticks": "{:.0f}", +} +TEXT_WIDTH = {"scope": 40, "longest scopes (ms)": 72} + + +def cell(column, value): + if value is None: + return "-" + if isinstance(value, (int, float)) and not isinstance(value, bool) and column in NUMBER_FORMATS: + return NUMBER_FORMATS[column].format(value) + text = str(value) + width = TEXT_WIDTH.get(column) + if width and len(text) > width: + text = text[:width - 1] + "~" + return text + + +def format_table(columns, rows, limit=None): + """aligned lines: names left, numbers right; past the limit a line of + how many more""" + shown = rows if limit is None else rows[:limit] + if not shown: + return ["(none)"] + cells = [[cell(column, value) for column, value in zip(columns, row)] for row in shown] + numeric = [column in NUMBER_FORMATS for column in columns] + widths = [max([len(column)] + [len(line[index]) for line in cells]) for index, column in enumerate(columns)] + + def line(values): + parts = [value.rjust(width) if is_number else value.ljust(width) + for value, width, is_number in zip(values, widths, numeric)] + return " ".join(parts).rstrip() + + lines = [line(columns)] + [line(values) for values in cells] + if limit is not None and len(rows) > limit: + lines.append(f"... {len(rows) - limit} more rows (net_report.py --top N)") + return lines + + +def problems(recording): + found = list(recording.warnings) + counters = (("unbalanced_scopes", "unbalanced scopes"), ("deep_scopes", "deep scopes"), + ("foreign_scopes", "foreign scopes"), ("dropped_scopes", "dropped scopes")) + for key, label in counters: + total = sum(header.get(key, 0) for header in recording.headers) + if total: + found.append(f"{label} {total}") + wait = sum(header.get("writer_wait_ms", 0.0) for header in recording.headers[-1:]) + if wait: + found.append(f"writer waited {wait:.0f} ms") + return found + + +def summary_lines(recording, start, end, top): + seconds = recording.select(start, end) + windowed = start is not None or end is not None + whole = recording.select() + header, last = recording.header, recording.headers[-1] + parts = f"parts {min(recording.parts)}-{max(recording.parts)}" if len(recording.parts) > 1 else "part 1" + state = "complete" if recording.complete and not any( + "missing" in warning for warning in recording.warnings) else "incomplete" + found = problems(recording) + own = header["own_machine"] if header["own_machine"] >= 0 else "-" + lines = [ + f"# halo profile summary, format {FORMAT}", + f"# recording: {recording.name}, {parts} ({state})", + f"# build: {header['build']}, platform: {header['platform']}, role: {header['role']}, own_machine: {own}", + f"# map: {header['map']}, players: {header['players']} at start, " + f"{max(item.get('players_most', item['players']) for item in recording.headers)} most", + f"# recorded: {header['start_utc']} UTC, {whole:.1f} s, {recording.cpu['ticks']} ticks, " + f"{recording.cpu['frames']} frames, stopped: {last['stop_reason'] or '-'}", + *([f"# window: {seconds:.1f} s selected (seconds {start if start is not None else 0:g} to " + f"{f'{end:g}' if end is not None else 'end'}); the cpu tables, ticks and frames cover the whole recording"] + if windowed else []), + f"# problems: {'; '.join(found) if found else 'none'}", + "# units: ms = milliseconds", "", "## columns", + ] + note = ", whole recording" if windowed else "" + sections = [(f"cpu scopes (top {min(top, 40)} by total_ms{note})", cpu_table(recording), min(top, 40)), + (f"worst frames (10{note})", worst_frame_table(recording), 10)] + printed = {column for _, (columns, rows), _ in sections if rows for column in columns} + lines.extend(line for line in COLUMNS_GLOSSARY.splitlines() if re.split(r"\s{2,}", line, maxsplit=1)[0] in printed) + for title, table, limit in sections: + lines.extend(["", f"## {title}", *format_table(*table, limit)]) + return lines + + +def write_csv(recording, directory): + directory.mkdir(parents=True, exist_ok=True) + for name, (columns, rows) in {"cpu": cpu_table(recording), "worst_frames": worst_frame_table(recording)}.items(): + with open(directory / f"{recording.name}.{name}.csv", "w", newline="", encoding="ascii", errors="replace") as file: + writer = csv.writer(file) + writer.writerow(columns) + writer.writerows([[NUMBER_FORMATS[column].format(value) + if column in NUMBER_FORMATS and isinstance(value, (int, float)) else + ("-" if value is None else value) for column, value in zip(columns, row)] for row in rows]) + + +def main(arguments=None): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("recordings", nargs="+", metavar="RECORDING") + parser.add_argument("--from", dest="start", type=float, help="from this second of the recording") + parser.add_argument("--to", dest="end", type=float, help="to this second") + parser.add_argument("--top", type=int, default=40, help="CPU rows (default 40)") + parser.add_argument("--csv", type=Path, metavar="DIR", help="every table as CSV in DIR") + parser.add_argument("--no-summary", action="store_true", help="do not write .summary.txt") + options = parser.parse_args(arguments) + try: + recordings = [Recording(name) for name in options.recordings] + except (ReportError, OSError) as error: + print(f"net_report: {error}", file=sys.stderr) + return 1 + for recording in recordings: + try: + lines = summary_lines(recording, options.start, options.end, options.top) + file_lines = lines if options.top == SUMMARY_TOP else summary_lines( + recording, options.start, options.end, SUMMARY_TOP) + except (KeyError, TypeError, IndexError, ValueError) as error: + print(f"net_report: {recording.name}: a table is not as the writer wrote it ({error!r})", file=sys.stderr) + return 1 + for warning in recording.warnings: + print(f"warning: {warning}", file=sys.stderr) + print("\n".join(lines)) + print() + if not options.no_summary: + path = recording.base.parent / f"{recording.name}.summary.txt" + path.write_text("\n".join(file_lines) + "\n", encoding="ascii", errors="replace") + if options.csv: + write_csv(recording, options.csv) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/port_settings.py b/tools/port_settings.py index f453b3af..944783f6 100644 --- a/tools/port_settings.py +++ b/tools/port_settings.py @@ -18,7 +18,7 @@ from xml.sax.saxutils import quoteattr PE = "main_menu/settings_select/player_setup/player_profile_edit" YES_NO = [("YES", "true"), ("NO", "false")] ON_OFF = [("ON", "true"), ("OFF", "false")] -SENSITIVITIES = [(f"{value:g}", f"{value:g}") for value in (0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4)] +SENSITIVITIES = [(f"{value:g}", f"{value:g}") for value in (0.1, 0.15, 0.25, 0.4, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4)] VOLUMES = [(str(step), f"{step / 10:g}") for step in range(11)] # each screen: its folder below PE, its screen's widget (the name the profile @@ -30,10 +30,11 @@ SCREENS = { "video_settings": { "screen": "video_settings_screen", "header": ("header_profile_video_settings", f"{PE}/video_settings/header_profile_video_settings"), - # (closer than the other screens' rows, and the help lower, for all - # twelve places to fit above it) - "spacing": 24, + # the other screens' 30px step. each platform packs its own rows, so + # a row hidden on this machine does not leave a gap + "spacing": 30, "help_top": 364, + "platform_places": True, # (rows in the place of the row before them: Window Size in # Resolution's, port/linux/game/menu_functions.c showing the one the # display mode chosen uses; Android's anti-aliasing in the desktop's) @@ -103,7 +104,14 @@ SCREENS = { "screen": "audio_settings_screen", "header": ("header_profile_audio_settings", f"{PE}/audio_settings/header_profile_audio_settings"), "spacing": 30, + # (the devices, desktop only, first: Android's rows from the top) + "platform_places": True, "rows": [ + # (port/linux/game/menu_tags.c adds the devices SDL finds) + ("OUTPUT DEVICE:", "audio.output_device", [("SYSTEM DEFAULT", "default")], + "Where the game's sound and the voices play.", "desktop"), + ("INPUT DEVICE:", "audio.input_device", [("SYSTEM DEFAULT", "default")], + "The microphone voice chat listens to.", "desktop"), ("MASTER VOLUME:", "audio.volume", VOLUMES, "The volume of everything.", None), ("MUSIC VOLUME:", "audio.music_volume", VOLUMES, "The music's volume.", None), ("EFFECTS VOLUME:", "audio.effects_volume", VOLUMES, @@ -112,6 +120,11 @@ SCREENS = { "Echo sounds as the place you are in does, and\nmuffle those behind walls, as the Xbox did.", None), ("SOUND:", "audio.enabled", ON_OFF, "Play sound at all; from the next time the game\nstarts.", None), + # voice chat: this machine's own (port/linux/game/network_voice.c) + ("VOICE CHAT:", "audio.voice_chat", + [("PUSH TO TALK", "push_to_talk"), ("OPEN MIC", "open_mic"), ("OFF", "off")], + "Talk in network games: while PUSH TO TALK is held\n(Controls), whenever you speak, or never.", None), + ("VOICE VOLUME:", "audio.voice_volume", VOLUMES, "The other players' voices.", None), ], }, "network_setup": { @@ -164,11 +177,71 @@ SCREENS = { }, } +# Video Setup's categories open ordinary settings screens. Each has the same +# pending edits, Defaults, OK and Cancel as the other settings screens. +_video = SCREENS["video_settings"] +_graphics = {"display.high_res_hud", "display.high_res_text", "display.anti_aliasing", + "display.shadow_resolution", "display.per_pixel_lighting"} +SCREENS["video_settings/graphics"] = { + "screen": "graphics_settings_screen", "header": _video["header"], "spacing": 30, + "same_place": ["anti_aliasing_android"], + "rows": [row for row in _video["rows"] if row[1] in _graphics], +} +_video["rows"] = [row for row in _video["rows"] if row[1] not in _graphics] +_video["categories"] = [ + ("GRAPHICS:", "video_settings/graphics", "The HUD, text, anti-aliasing, shadows and\nlighting."), + ("FOV AND VIEWMODELS:", "video_settings/fov_viewmodels", + "The field of view and the first-person\nweapon. Their defaults keep the stock view."), +] +SCREENS["video_settings/fov_viewmodels"] = { + "screen": "fov_viewmodel_settings_screen", "header": _video["header"], "spacing": 30, + "rows": [ + ("FOV:", "display.fov", [("DEFAULT", "0")] + [(str(n), str(n)) for n in range(80, 151, 5)], + "On foot, horizontal at 16:9, in degrees.\nDefault keeps the stock view.", None), + ("VIEWMODEL FOV:", "display.viewmodel_fov", [("DEFAULT", "0")] + [(str(n), str(n)) for n in range(80, 151, 5)], + "The weapon and hands, horizontal at 16:9.\nDefault keeps the weapon's stock view.", None), + ("VIEWMODELS:", "display.viewmodel_visible", ON_OFF, + "Draw first-person weapons, hands and attached\nvisuals. Gameplay and sound continue when off.", None), + ], +} + # Controls Setup: the keyboard and mouse's actions, in groups (the order of # port/linux/game/menu_functions.c's table of them) CONTROL_GROUPS = ["MOVEMENT", "WEAPONS", "ACTIONS"] CONTROL_ROWS = 7 +# the settings whose rows have the wider spinner (a device's name) +WIDE_SETTINGS = {"audio.output_device", "audio.input_device"} + +# the host's voice chat and vote kicks, on Teamplay Options +# (_teamplay_options_extras): each row's key, its label, its choices' words +# (menu_functions.c's gametype_options give their settings' values) and +# their helps +TEAMPLAY_EDIT = "main_menu/settings_select/multiplayer_setup/teamplay_options_edit" +VOICE_QUALITIES = (8, 12, 16, 24, 32, 48, 64) +TEAMPLAY_ROWS = [ + ("voice_mode", "VOICE CHAT:", ["OFF", "TEAM NEAR", "ANYONE NEAR", "TEAM", "TEAM, ENEMIES NEAR"], [ + "No voice chat during the game.", + "Players hear their teammates who are near them.", + "Players hear everyone who is near them.", + "Players hear all their teammates, wherever they\\nare.", + "Players hear all their teammates, and the enemies\\nwho are near them.", + ]), + ("voice_lobby", "LOBBY VOICE CHAT:", ["ON", "OFF"], [ + "Everyone hears everyone in the lobby, before and\\nafter the game.", + "No voice chat in the lobby.", + ]), + ("voice_kbps", "VOICE QUALITY:", [f"{kbps} KBPS" for kbps in VOICE_QUALITIES], + [f"Voices at {kbps} kilobits a second, in the lobby\\nand in the game." for kbps in VOICE_QUALITIES]), + ("voice_proximity", "VOICE NEAR DISTANCE:", ["15 M", "30 M", "45 M", "60 M", "90 M", "150 M"], + [f"Players {metres} metres apart or less are near\\nfor voice chat." for metres in (15, 30, 45, 60, 90, 150)]), + # (network_votekick.c) + ("votekick", "VOTE KICK:", ["ON", "OFF"], [ + "Players can vote to kick a player, from the\\nscoreboard.", + "Players cannot vote to kick anyone.", + ]), +] + # the profile menu's words for what its items now open STRING_OVERRIDES = { f"{PE}/profile_edit_descriptions": [ @@ -247,37 +320,78 @@ def _setting_screen(folder: str, spec: dict) -> list: base = f"{PE}/{folder}" rows, extra = [], [] place = -1 + # (platform_places: each platform's rows in places of their own, with + # no gap where the other's are; a row for both, a child for each) + places = {"desktop": -1, "android": -1} for index, (label, setting, choices, _, platform, *named) in enumerate(spec["rows"]): key = named[0] if named else setting.split(".", 1)[1] row = f"{base}/op_{key}" - if setting not in spec.get("same_place", ()) and key not in spec.get("same_place", ()): - place += 1 - rows.append((row, platform, place)) + shares = setting in spec.get("same_place", ()) or key in spec.get("same_place", ()) + if spec.get("platform_places"): + for name in places: + if platform in (None, name) and not shares: + places[name] += 1 + if platform or places["desktop"] == places["android"]: + rows.append((row, platform, places[platform or "desktop"])) + else: + rows += [(row, name, places[name]) for name in places] + else: + if setting not in spec.get("same_place", ()) and key not in spec.get("same_place", ()): + place += 1 + rows.append((row, platform, place)) + # (a device's name wants the wider spinner Server Setup's co-op rows + # have: WIDE_SETTINGS) + wide = setting in WIDE_SETTINGS extra += _widget(row, [("width", 512), ("height", 28), ("flags", "pass_unhandled_to_focused_child"), ("bitmap", "bitmaps/option_bkds"), ("color", "#FF2896FF"), ("platform", platform)], [f'', - f'']) + f'']) extra += _widget(f"{base}/{key}_label", [("type", "text"), ("controller", 1), ("width", 300), ("height", 22), ("string_list", f"{base}/labels"), ("string_index", index), ("font", "ui\\large_ui"), ("color", "#FF2896FF"), ("text_x", 13), ("text_y", 4)], []) extra += _widget(f"{base}/{key}_spinner", - [("type", "spinner"), ("left", 3), ("top", 2), ("width", 147), ("height", 20), - ("flags", "pass_unhandled_to_focused_child left_right_tabs_items"), + [("type", "spinner"), ("left", None if wide else 3), ("top", 2), ("width", 206 if wide else 147), + ("height", 20), ("flags", "pass_unhandled_to_focused_child left_right_tabs_items"), ("strings", "|".join(shown for shown, _ in choices)), ("setting", setting), ("values", "|".join(value for _, value in choices)), ("font", "ui\\large_ui"), - ("color", "#FF2896FF"), ("align", "center"), ("text_y", 1), + ("color", "#FF2896FF"), ("align", "center"), ("text_y", 4 if wide else 1), ("header_bitmap", "bitmaps/arrow_sm_left"), ("footer_bitmap", "bitmaps/arrow_sm_right"), - ("header_bounds", "7 -6 19 0"), ("footer_bounds", "7 150 19 156")], + ("header_bounds", "7 -13 19 -7" if wide else "7 -6 19 0"), + ("footer_bounds", "7 208 19 214" if wide else "7 150 19 156")], ['']) + for index, (label, category_folder, _) in enumerate(spec.get("categories", ())): + key = category_folder.rsplit("/", 1)[-1] + row = f"{base}/op_{key}" + target = f"{PE}/{category_folder}/{SCREENS[category_folder]['screen']}" + if spec.get("platform_places"): + for name in places: + places[name] += 1 + if places["desktop"] == places["android"]: + rows.append((row, None, places["desktop"])) + else: + rows += [(row, name, places[name]) for name in ("desktop", "android")] + else: + rows.append((row, None, place + 1 + index)) + extra += _widget(row, [("width", 512), ("height", 28), ("flags", "pass_unhandled_to_focused_child"), + ("bitmap", "bitmaps/option_bkds"), ("color", "#FF2896FF")], + [f'', f'', + '', + f'']) + extra += _widget(f"{base}/{key}_label", + [("type", "text"), ("controller", 1), ("width", 512), ("height", 22), + ("string_list", f"{base}/labels"), ("string_index", len(spec["rows"]) + index), + ("font", "ui\\large_ui"), ("color", "#FF2896FF"), ("text_x", 13), ("text_y", 4)], []) extra += _button(f"{base}/button_defaults", 3, ['', '']) extra += _button(f"{base}/button_ok", 1, ['', '']) - extra += _strings(f"{base}/labels", [label for label, *_ in spec["rows"]]) + extra += _strings(f"{base}/labels", [label for label, *_ in spec["rows"]] + + [label for label, *_ in spec.get("categories", ())]) # (the help of the row whose label is string n is n + 1: the buttons' is 0) extra += _strings(f"{base}/help_strings", - [""] + [help_text.replace("\n", "\\n") for _, _, _, help_text, *_ in spec["rows"]]) + [""] + [help_text.replace("\n", "\\n") for _, _, _, help_text, *_ in spec["rows"]] + + [help_text.replace("\n", "\\n") for _, _, help_text in spec.get("categories", ())]) return _screen(folder, spec, rows, ["port settings help"], [], extra) @@ -361,6 +475,10 @@ MT = "main_menu/multiplayer_type_select" # in turn: menu_functions.c's gametype_option_help) SLAYER_EDIT = "main_menu/settings_select/multiplayer_setup/playlist_edit/slayer_edit" STRING_INSERTS = { + # (Teamplay Options' voice chat and vote kick rows, after its own: + # TEAMPLAY_ROWS) + f"{TEAMPLAY_EDIT}/teamplay_options_labels": [(3, [label for _, label, *_ in TEAMPLAY_ROWS])], + f"{TEAMPLAY_EDIT}/cap_teamplay_options": [(10, [text for *_, helps in TEAMPLAY_ROWS for text in helps])], f"{SLAYER_EDIT}/var_kills_to_win": [(5, ["75", "100", "150", "200", "250", "500"])], f"{SLAYER_EDIT}/cap_slayer": [(11, [ "Seventy-five kills to win. Settle in for a long\\nfight.", @@ -510,6 +628,12 @@ WIDGET_PATCHES = { f'', f'', ]}}, + # (Teamplay Options' voice chat and vote kick rows, over its buttons: + # Server Setup's only, _teamplay_options_extras) + f"{TEAMPLAY_EDIT}/teamplay_options_menu": {"insert_before": { + f"{TEAMPLAY_EDIT}/teamplay_button_bar": [ + f'' + for index, (key, *_) in enumerate(TEAMPLAY_ROWS)]}}, # (the PC's Vehicles row's Start opened Item Options) "main_menu/settings_select/multiplayer_setup/playlist_edit/playlist_edit_vehicles_list_item": {"handlers": [ '', @@ -734,73 +858,25 @@ def _server_settings() -> list: rows.append((f"{base}/op_listing", None)) # PASSWORD (a public internet game's: menu_functions.c's server_settings_update), # which players from the server browser must type - extra += _value_row(base, "password", 15, "ss edit server password") + extra += _value_row(base, "password", 10 + len(COOP_SETUP_SCREENS), "ss edit server password") rows.append((f"{base}/op_password", None)) - # co-op's FRIENDLY FIRE (between its players), in the place of the - # gametype's rows, which co-op hides as multiplayer hides it, laid out - # as Teamplay Options' (menu_functions.c's server_settings_update); its - # choice kept in network.coop_friendly_fire - extra += _widget(f"{base}/op_friendly_fire", [("width", 512), ("height", 28), - ("flags", "pass_unhandled_to_focused_child"), - ("bitmap", "bitmaps/option_bkds"), ("color", "#FF2896FF")], - [f'', - f'']) - extra += _widget(f"{base}/friendly_fire_label", [("type", "text"), ("controller", 1), ("width", 300), - ("height", 22), ("string_list", f"{base}/labels"), - ("string_index", 10), ("font", "ui\\large_ui"), - ("color", "#FF2896FF"), ("text_x", 13), ("text_y", 4)], []) - extra += _widget(f"{base}/friendly_fire_spinner", - [("type", "spinner"), ("top", 2), ("width", 206), ("height", 20), - ("flags", "pass_unhandled_to_focused_child left_right_tabs_items"), - ("strings", "OFF|ON|SHIELD ONLY|EXPLOSIVES ONLY"), ("setting", "network.coop_friendly_fire"), - ("values", "|".join(COOP_FRIENDLY_FIRE_VALUES)), ("font", "ui\\large_ui"), - ("color", "#FF2896FF"), ("align", "center"), ("text_y", 4), - ("header_bitmap", "bitmaps/arrow_sm_left"), ("footer_bitmap", "bitmaps/arrow_sm_right"), - ("header_bounds", "7 -13 19 -7"), ("footer_bounds", "7 208 19 214")], - ['', '', - '']) - rows.append((f"{base}/op_friendly_fire", None, 5)) - # ... and its EXTRA ENEMIES (port/linux/game/coop_enemies.c), below it: - # NONE, PER PLAYER or STATIC MULTIPLIER, and below that the amount of the - # one chosen (its own row, the other hidden: menu_functions.c's - # server_settings_update); each kept in its network.coop_enemies setting - def spinner_row(key, label_index, strings, setting, values): - nonlocal extra + # co-op's options, in the place of the gametype's rows, which co-op hides + # as multiplayer hides co-op's (menu_functions.c's server_settings_update): + # rows opening their screens, as the gametype's options' do + # (_setup_option_screen; COOP_SETUP_SCREENS) + for index, (key, title, _, _) in enumerate(COOP_SETUP_SCREENS): + target = f"{base}/{key}/{key}_screen" extra += _widget(f"{base}/op_{key}", [("width", 512), ("height", 28), - ("flags", "pass_unhandled_to_focused_child"), - ("bitmap", "bitmaps/option_bkds"), ("color", "#FF2896FF")], - [f'', - f'']) - extra += _widget(f"{base}/{key}_label", [("type", "text"), ("controller", 1), ("width", 300), - ("height", 22), ("string_list", f"{base}/labels"), - ("string_index", label_index), ("font", "ui\\large_ui"), - ("color", "#FF2896FF"), ("text_x", 13), ("text_y", 4)], []) - extra += _widget(f"{base}/{key}_spinner", - [("type", "spinner"), ("top", 2), ("width", 206), ("height", 20), - ("flags", "pass_unhandled_to_focused_child left_right_tabs_items"), - ("strings", "|".join(strings)), ("setting", setting), - ("values", "|".join(str(value) for value in values)), ("font", "ui\\large_ui"), - ("color", "#FF2896FF"), ("align", "center"), ("text_y", 4), - ("header_bitmap", "bitmaps/arrow_sm_left"), ("footer_bitmap", "bitmaps/arrow_sm_right"), - ("header_bounds", "7 -13 19 -7"), ("footer_bounds", "7 208 19 214")], - ['', - '', - '']) - - spinner_row("extra_enemies", 11, ["NONE", "PER PLAYER", "STATIC MULTIPLIER"], "network.coop_enemies_mode", - COOP_ENEMIES_MODES) - rows.append((f"{base}/op_extra_enemies", None, 6)) - spinner_row("enemies_per_player", 12, [f"{value}%" for value in COOP_ENEMIES_PERCENTAGES], - "network.coop_enemies", COOP_ENEMIES_PERCENTAGES) - rows.append((f"{base}/op_enemies_per_player", None, 7)) - spinner_row("enemies_multiplier", 13, [f"{value}X" for value in COOP_ENEMIES_MULTIPLIERS], - "network.coop_enemies_multiplier", COOP_ENEMIES_MULTIPLIERS) - rows.append((f"{base}/op_enemies_multiplier", None, 7)) - # ... and its PLAYER COLLISIONS, below them (network.coop_player_collisions, - # which the host sends its players as the game begins: menu_functions.c's - # server_start) - spinner_row("player_collisions", 14, ["ON", "OFF"], "network.coop_player_collisions", ["true", "false"]) - rows.append((f"{base}/op_player_collisions", None, 8)) + ("flags", "pass_unhandled_to_focused_child"), + ("bitmap", "bitmaps/option_bkds"), ("color", "#FF2896FF")], + [f'', f'', + '', + f'']) + extra += _widget(f"{base}/{key}_label", [("type", "text"), ("controller", 1), ("width", 300), ("height", 22), + ("string_list", f"{base}/labels"), ("string_index", 10 + index), + ("font", "ui\\large_ui"), ("color", "#FF2896FF"), ("text_x", 13), + ("text_y", 4)], []) + rows.append((f"{base}/op_{key}", None, 5 + index)) # the gametype's options for this game (the gametype editor's screens, # editing a copy of the gametype chosen: "port setup edit") for index, (key, screen) in enumerate(SETUP_OPTION_SCREENS): @@ -830,8 +906,8 @@ def _server_settings() -> list: '']) extra += _strings(f"{base}/labels", ["GAME NAME:", "MAXIMUM PLAYERS:", "INVITE LINK:", "GAME TYPE:", "PLAYER OPTIONS:", "ITEM OPTIONS:", "VEHICLE OPTIONS:", "INDICATOR OPTIONS:", - "TEAMPLAY OPTIONS:", "LISTING:", "FRIENDLY FIRE:", "EXTRA ENEMIES:", - "PER PLAYER:", "MULTIPLIER:", "PLAYER COLLISIONS:", "PASSWORD:"]) + "TEAMPLAY OPTIONS:", "LISTING:", + *(f"{title}:" for _, title, _, _ in COOP_SETUP_SCREENS), "PASSWORD:"]) extra += _strings(f"{base}/help_strings", [ "", "The name the game shows in the lists of games.\\nEnter changes it.", @@ -846,26 +922,16 @@ def _server_settings() -> list: # (LISTING's, by its choice) "Anyone can see and join your game: it is listed\\nin everyone's Server Browser.", "Only players with your invite link can join.", - # (FRIENDLY FIRE's, by its choice, as Teamplay Options' words them) - "Players can not be hurt by weapons and explosives\\nfired by the other players.", - "Players can be hurt by weapons or explosives\\nfired by the other players.", - "Damage from the other players will only reduce\\nshields. Health will be unaffected.", - "Players can be hurt by damage from explosives\\nfired by the other players.", - # (EXTRA ENEMIES', by its choice: COOP_ENEMIES_MODES' order) - "Enemy squads are as the campaign has them.", - "Enemy squads grow with the players: by the amount\\nbelow for each player past the first.", - "Enemy squads are the amount below times as large,\\nhowever many players there are.", - # (PER PLAYER's and MULTIPLIER's) - "For each player past the first, enemy squads get\\nthis much more of themselves (100%: as many again).", - "Each enemy squad is this many times as large.", - # (PLAYER COLLISIONS', by its choice) - "Players bump into each other, as in the campaign.", - "Players walk through each other, so that no one\\nblocks a doorway. The AI's characters still block.", + # (co-op's options' rows: COOP_SETUP_SCREENS) + *(help_text for _, _, help_text, _ in COOP_SETUP_SCREENS), # (PASSWORD's) "Players from the Server Browser must type it to\\njoin (an invite link needs none). Enter sets it.", ]) lines = _screen(base, spec, rows, ["server settings update"], [''], extra) + # (co-op's options' screens) + for key, title, _, screen_rows in COOP_SETUP_SCREENS: + lines += _setup_option_screen(f"{base}/{key}", key, screen_rows) # (no Defaults: the bar is START and CANCEL; B cancels the name's edit # first) lines = [line.replace(f'', "") for line in lines] @@ -875,15 +941,102 @@ def _server_settings() -> list: MAXIMUM_PLAYERS = [2, 4, 8, 12, 16, 24, 32, 48, 64, 96, 128] -# network.coop_friendly_fire's values, as Server Setup's FRIENDLY FIRE shows -# them (menu_functions.c's cooperative_friendly_fire_modes, in this order) -COOP_FRIENDLY_FIRE_VALUES = ["off", "on", "shields_only", "explosives_only"] -# Server Setup's EXTRA ENEMIES: network.coop_enemies_mode's values (in -# coop_enemies.c's order), and the amounts of PER PLAYER (network.coop_enemies, -# percentages) and STATIC MULTIPLIER (network.coop_enemies_multiplier) -COOP_ENEMIES_MODES = ["none", "per_player", "multiplier"] +# CO-OP OPTIONS' amounts of PER PLAYER (network.coop_enemies, percentages) +# and STATIC MULTIPLIER (network.coop_enemies_multiplier), as menu_functions.c's +# gametype_options have their values COOP_ENEMIES_PERCENTAGES = [25, 50, 100, 150, 200] COOP_ENEMIES_MULTIPLIERS = [2, 4, 8, 16, 32] +# co-op's Server Setup options, in screens of their own as the gametype's +# are (its rows: _server_settings), each its key, its title, its row's help +# and its rows (as TEAMPLAY_ROWS: each spinner's key, label, words and the +# help of each of them; menu_functions.c's gametype_options give their +# settings' values) +COOP_SETUP_SCREENS = [ + ("coop_options", "CO-OP OPTIONS", "Friendly fire, the enemies and player collisions,\\nfor this game.", [ + ("coop_friendly_fire", "FRIENDLY FIRE:", ["OFF", "ON", "SHIELD ONLY", "EXPLOSIVES ONLY"], [ + "Players can not be hurt by weapons and explosives\\nfired by the other players.", + "Players can be hurt by weapons or explosives\\nfired by the other players.", + "Damage from the other players will only reduce\\nshields. Health will be unaffected.", + "Players can be hurt by damage from explosives\\nfired by the other players.", + ]), + ("coop_extra_enemies", "EXTRA ENEMIES:", ["NONE", "PER PLAYER", "STATIC MULTIPLIER"], [ + "Enemy squads are as the campaign has them.", + "Enemy squads grow with the players: by the amount\\nbelow for each player past the first.", + "Enemy squads are the amount below times as large,\\nhowever many players there are.", + ]), + ("coop_enemies_per_player", "PER PLAYER:", [f"{value}%" for value in COOP_ENEMIES_PERCENTAGES], + ["For each player past the first, enemy squads get\\nthis much more of themselves (100%: as many again)."] * + len(COOP_ENEMIES_PERCENTAGES)), + ("coop_enemies_multiplier", "MULTIPLIER:", [f"{value}X" for value in COOP_ENEMIES_MULTIPLIERS], + ["Each enemy squad is this many times as large."] * len(COOP_ENEMIES_MULTIPLIERS)), + ("coop_player_collisions", "PLAYER COLLISIONS:", ["ON", "OFF"], [ + "Players bump into each other, as in the campaign.", + "Players walk through each other, so that no one\\nblocks a doorway. The AI's characters still block.", + ]), + ]), + ("voice_options", "VOICE AND VOTING", "Voice chat and votes to kick a player, for this\\ngame.", TEAMPLAY_ROWS), +] +# (their screens' titles) +TITLES.update({f"{MT}/server_settings/{key}/header_{key}": title for key, title, _, _ in COOP_SETUP_SCREENS}) +# (the rows of a co-op options screen in one place, the one shown: PER +# PLAYER's and MULTIPLIER's, by EXTRA ENEMIES') +SETUP_OPTION_SAME_PLACE = {"coop_enemies_multiplier"} + + +def _setup_option_screen(base: str, key: str, rows: list) -> list: + """a screen of options laid out as the gametype's are (Teamplay + Options'), its spinners setting config.toml's settings (menu_functions.c's + "port setup options init" and "port setup options save", OK), each + choice's help shown as the gametype's are ("game settings lists text + update": the help strings, each spinner's choices' in turn)""" + lines = _widget(f"{base}/{key}_screen", [("width", 640), ("height", 480), + ("flags", "pass_unhandled_to_focused_child"), + ("bitmap", "bitmaps/gradient")], + [f'', f'']) + lines += _header(f"{base}/header_{key}", f"{base}/header_{key}") + lines += _widget(f"{base}/{key}_help", [("type", "text"), ("controller", 1), ("left", 68), ("top", 341), + ("width", 482), ("height", 79), ("string_list", f"{base}/cap_{key}"), + ("font", "ui\\large_ui"), ("color", "#FFFFFFFF")], []) + children = ['', + ''] + place = -1 + for row_key, *_ in rows: + if row_key not in SETUP_OPTION_SAME_PLACE: + place += 1 + children.append(f'') + children.append(f'') + lines += _widget(f"{base}/{key}_menu", [("type", "column_list"), ("width", 640), ("height", 480), + ("flags", "pass_unhandled_to_focused_child up_down_tabs_children"), + ("description", f"{base}/{key}_help")], children) + lines += _widget(f"{base}/{key}_button_bar", [("type", "column_list"), ("width", 640), ("height", 28), + ("flags", "pass_unhandled_to_focused_child left_right_tabs_items")], + ['', f'', + '']) + lines += _button(f"{base}/{key}_button_ok", 1, ['', + '']) + for index, (row_key, label, strings, _) in enumerate(rows): + lines += _widget(f"{base}/op_{row_key}", [("width", 512), ("height", 28), + ("flags", "pass_unhandled_to_focused_child"), + ("bitmap", "bitmaps/option_bkds"), ("color", "#FF2896FF")], + [f'', + f'']) + lines += _widget(f"{base}/{row_key}_label", [("type", "text"), ("controller", 1), ("width", 300), + ("height", 22), ("string_list", f"{base}/{key}_labels"), + ("string_index", index), ("font", "ui\\large_ui"), + ("color", "#FF2896FF"), ("text_x", 13), ("text_y", 4)], []) + lines += _widget(f"{base}/{row_key}_spinner", + [("type", "spinner"), ("top", 2), ("width", 206), ("height", 20), + ("flags", "pass_unhandled_to_focused_child left_right_tabs_items"), + ("string_list", f"{base}/var_{row_key}"), ("font", "ui\\large_ui"), + ("color", "#FF2896FF"), ("align", "center"), ("text_y", 4), + ("list_flags", "items_from_strings"), + ("header_bitmap", "bitmaps/arrow_sm_left"), ("footer_bitmap", "bitmaps/arrow_sm_right"), + ("header_bounds", "7 -13 19 -7"), ("footer_bounds", "7 208 19 214")], + ['']) + lines += _strings(f"{base}/var_{row_key}", strings) + lines += _strings(f"{base}/{key}_labels", [label for _, label, *_ in rows]) + lines += _strings(f"{base}/cap_{key}", [text for *_, helps in rows for text in helps]) + return lines # Server Setup's rows of the gametype's options: the gametype editor's # screens SETUP_OPTION_SCREENS = [ @@ -1139,6 +1292,36 @@ def _item_options_extras() -> list: return lines +def _teamplay_options_extras() -> list: + """Teamplay Options' rows of the port's: the host's voice chat + (port/linux/game/network_voice.c) and vote kicks (network_votekick.c), + in Server Setup's copy only (menu_functions.c's gametype_options_init), + kept in config.toml""" + lines = [] + for index, (key, label, strings, _) in enumerate(TEAMPLAY_ROWS): + lines += _widget(f"{TEAMPLAY_EDIT}/op_{key}", [("width", 512), ("height", 28), + ("flags", "pass_unhandled_to_focused_child"), + ("bitmap", "bitmaps/option_bkds"), ("color", "#FF2896FF")], + [f'', + f'']) + lines += _widget(f"{TEAMPLAY_EDIT}/{key}_label", [("type", "text"), ("controller", 1), ("width", 300), + ("height", 22), + ("string_list", f"{TEAMPLAY_EDIT}/teamplay_options_labels"), + ("string_index", 3 + index), ("font", "ui\\large_ui"), + ("color", "#FF2896FF"), ("text_x", 13), ("text_y", 4)], []) + lines += _widget(f"{TEAMPLAY_EDIT}/{key}_spinner", + [("type", "spinner"), ("top", 2), ("width", 206), ("height", 20), + ("flags", "pass_unhandled_to_focused_child left_right_tabs_items"), + ("string_list", f"{TEAMPLAY_EDIT}/var_{key}"), ("font", "ui\\large_ui"), + ("color", "#FF2896FF"), ("align", "center"), ("text_y", 4), + ("list_flags", "items_from_strings"), + ("header_bitmap", "bitmaps/arrow_sm_left"), ("footer_bitmap", "bitmaps/arrow_sm_right"), + ("header_bounds", "7 -13 19 -7"), ("footer_bounds", "7 208 19 214")], + ['']) + lines += _strings(f"{TEAMPLAY_EDIT}/var_{key}", strings) + return lines + + def _map_kind() -> list: """the map lists' first row (New Game's and the Map screen's), as the gametype list's chooser: a spinner of SINGLEPLAYER or MULTIPLAYER maps, @@ -1175,6 +1358,7 @@ def multiplayer_files() -> dict: f"{MT}/coop".replace("/", ".") + ".xml": head + _coop() + ["", ""], "main_menu/new_select".replace("/", ".") + ".port.xml": head + _map_kind() + ["", ""], "main_menu/settings_select/multiplayer_setup/item_options_edit".replace("/", ".") + ".port.xml": head + _item_options_extras() + ["", ""], + TEAMPLAY_EDIT.replace("/", ".") + ".port.xml": head + _teamplay_options_extras() + ["", ""], } diff --git a/tools/profile_check.c b/tools/profile_check.c new file mode 100644 index 00000000..27ed1538 --- /dev/null +++ b/tools/profile_check.c @@ -0,0 +1,1059 @@ +/* +PROFILE_CHECK.C + +Exercise the CPU recorder and writer with standalone fakes. +*/ + +#include "profile_part.h" +#include "profile_console_gametype.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ---------- fakes */ + +static int failures; +static int session_sample_calls; +static unsigned long long fake_now = 1000000000ULL; +static pthread_mutex_t fake_track_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t fake_allocator_lock = PTHREAD_MUTEX_INITIALIZER; +static long allocations_outstanding; +static long allocations_made; +static int allocations_refused; +/* allocations that succeed before the rest are refused (-1: no such limit) */ +static int allocations_allowed = -1; +static int fake_ready; +static const char *fake_role = "host"; +static long fake_players = 2; +static long fake_own_machine = -1; +static const char *fake_map = "levels\\a30\\a30"; +static const char *fake_map_name = "a30"; +static const char *fake_folder = "."; +static char last_notice[128]; +static char last_log[600]; +static int name_aggregate_child; + +static void check(int good, const char *what) +{ + if (!good) + { + failures++; + printf("FAIL: %s\n", what); + } +} + +static void gametype_checks(void) +{ + static const char *const expected[] = { "none", "ctf", "slayer", "oddball", "king", "race" }; + long engine; + for (engine = 0; engine <= 5; engine++) + check(strcmp(profile_console_engine_gametype(1, engine), expected[engine]) == 0, + "engine index maps to its engine gametype"); + check(strcmp(profile_console_engine_gametype(0, 2), "campaign") == 0, + "campaign does not reuse a stale slayer variant"); + check(strcmp(profile_console_engine_gametype(1, 6), "none") == 0 && + strcmp(profile_console_engine_gametype(1, 7), "none") == 0, "an engine past race is none"); + check(strcmp(profile_console_scenario_map("levels\\b30\\b30", "ui"), "levels\\b30\\b30") == 0, + "the pending scenario takes precedence over the previous map"); +} + +static unsigned long long fake_clock(void) +{ + return __atomic_load_n(&fake_now, __ATOMIC_RELAXED); +} + +static void advance(unsigned long long nanoseconds) +{ + __atomic_fetch_add(&fake_now, nanoseconds, __ATOMIC_RELAXED); +} + +static void fake_lock(void) +{ + pthread_mutex_lock(&fake_track_lock); +} + +static void fake_unlock(void) +{ + pthread_mutex_unlock(&fake_track_lock); +} + +static void *fake_allocate(unsigned long size) +{ + void *block; + + pthread_mutex_lock(&fake_allocator_lock); + block = allocations_refused || allocations_allowed == 0 ? NULL : malloc(size); + if (allocations_allowed > 0) + allocations_allowed--; + if (block) + { + allocations_outstanding++; + allocations_made++; + } + pthread_mutex_unlock(&fake_allocator_lock); + return block; +} + +static void fake_release(void *block) +{ + if (!block) + return; + pthread_mutex_lock(&fake_allocator_lock); + allocations_outstanding--; + pthread_mutex_unlock(&fake_allocator_lock); + free(block); +} + +static long outstanding(void) +{ + long count; + + pthread_mutex_lock(&fake_allocator_lock); + count = allocations_outstanding; + pthread_mutex_unlock(&fake_allocator_lock); + return count; +} + +static void quiet_log(const char *text) +{ + if (strstr(text, "cannot write")) + snprintf(last_log, sizeof(last_log), "%s", text); +} + +static void fake_notify(const char *text) +{ + snprintf(last_notice, sizeof(last_notice), "%s", text); +} + +static int fake_ready_test(void) +{ + return fake_ready; +} + +static void fake_session(struct profile_trace_session *session) +{ + memset(session, 0, sizeof(*session)); + snprintf(session->folder, sizeof(session->folder), "%s", fake_folder); + strcpy(session->build, "check"); + strncpy(session->role, fake_role, sizeof(session->role) - 1); + session->own_machine = fake_own_machine; + strncpy(session->map, fake_map, sizeof(session->map) - 1); + strncpy(session->map_name, fake_map_name, sizeof(session->map_name) - 1); + strcpy(session->gametype, "campaign"); + session->players = fake_players; + session->players_most = fake_players; +} + +static void fake_session_sample(struct profile_trace_session *session) +{ + session_sample_calls++; + fake_session(session); +} + +/* ---------- the fake writer: what each part held */ + +enum +{ + MAXIMUM_PARTS = 64, +}; + +static struct +{ + pthread_mutex_t lock; + int parts; + int finished; + unsigned long counts[MAXIMUM_PROFILE_TRACE_NAMES]; + unsigned long counter_records; + unsigned long long aggregate_time[MAXIMUM_PROFILE_TRACE_NAMES]; + unsigned long aggregate_count[MAXIMUM_PROFILE_TRACE_NAMES]; + long first_frame[MAXIMUM_PARTS]; + long frames[MAXIMUM_PARTS]; + unsigned long long start_ns[MAXIMUM_PARTS]; + unsigned long long end_ns[MAXIMUM_PARTS]; + int last[MAXIMUM_PARTS]; + int stop_reason[MAXIMUM_PARTS]; + char map_name[MAXIMUM_PARTS][64]; + double writer_wait_ms; + unsigned long unbalanced; + unsigned long deep; + unsigned long foreign; + unsigned long dropped; + int records_in_span; + int aggregate_child_depth; + int delay_milliseconds; +} written = { .lock = PTHREAD_MUTEX_INITIALIZER }; + +static void *fake_writer(void *unused) +{ + (void)unused; + for (;;) + { + struct profile_part *part = profile_trace_writer_next(); + int last = part->last; + unsigned long index; + int track; + + /* (a slow first part: the game thread's wait for it is on the fake + clock too) */ + if (written.delay_milliseconds && part->number == 1) + { + usleep((useconds_t)written.delay_milliseconds * 1000); + advance((unsigned long long)written.delay_milliseconds * 1000000ULL); + } + pthread_mutex_lock(&written.lock); + if (written.parts < MAXIMUM_PARTS) + { + written.first_frame[written.parts] = part->first_frame; + written.frames[written.parts] = part->frames; + written.start_ns[written.parts] = part->start_ns; + written.end_ns[written.parts] = part->end_ns; + written.last[written.parts] = part->last; + written.stop_reason[written.parts] = part->stop_reason; + strcpy(written.map_name[written.parts], part->session.map_name); + } + written.parts++; + written.writer_wait_ms = part->writer_wait_ms; + written.unbalanced += part->unbalanced_scopes; + written.deep += part->deep_scopes; + written.foreign += part->foreign_scopes; + written.dropped += part->dropped_scopes; + for (track = 0; track < NUMBER_OF_PROFILE_TRACKS; track++) + { + for (index = 0; index < part->record_counts[track]; index++) + { + const struct profile_trace_record *record = &part->records[track][index]; + + if (record->depth < MAXIMUM_PROFILE_TRACE_DEPTH) + { + written.counts[record->name]++; + if (record->name == name_aggregate_child) + written.aggregate_child_depth = record->depth; + /* (a scope that began before the part, in the p2p pass a + cut came in, is in the part it ended in) */ + if (record->start + record->duration > part->end_ns) + written.records_in_span = 0; + } + else + { + written.counter_records++; + if (record->depth == PROFILE_TRACE_DEPTH_AGGREGATE_TIME) + written.aggregate_time[record->name] += record->duration; + else if (record->depth == PROFILE_TRACE_DEPTH_AGGREGATE_COUNT) + written.aggregate_count[record->name] += record->duration; + } + } + } + pthread_mutex_unlock(&written.lock); + if (last) + { + pthread_mutex_lock(&written.lock); + written.finished++; + pthread_mutex_unlock(&written.lock); + } + /* Publish the fake writer's last observation before done makes the + recorder idle; finish() may read the observations as soon as it does. */ + profile_trace_writer_done(part); + if (last) + break; + } + return NULL; +} + +static int fake_writer_start(void) +{ + pthread_t thread; + + return pthread_create(&thread, NULL, fake_writer, NULL) == 0 && pthread_detach(thread) == 0; +} + +static void forget_written(void) +{ + pthread_mutex_lock(&written.lock); + written.parts = 0; + written.finished = 0; + memset(written.counts, 0, sizeof(written.counts)); + written.counter_records = 0; + written.unbalanced = written.deep = written.foreign = written.dropped = 0; + written.records_in_span = 1; + pthread_mutex_unlock(&written.lock); +} + +/* whether the writer has finished a recording, read through its lock */ +static int writer_finished(void) +{ + int finished; + + pthread_mutex_lock(&written.lock); + finished = written.finished; + pthread_mutex_unlock(&written.lock); + return finished; +} + +/* frame boundaries until the recording is written and the state idle */ +static void finish(void) +{ + struct profile_trace_status status; + int frames; + + for (frames = 0; frames < 100000; frames++) + { + profile_trace_frame_boundary(); + profile_trace_status(&status); + if (status.state == _profile_trace_idle || status.state == _profile_trace_armed) + { + /* (what the writer noted, seen through its lock) */ + pthread_mutex_lock(&written.lock); + pthread_mutex_unlock(&written.lock); + return; + } + usleep(100); + } + check(0, "the recording finishes"); +} + +/* ---------- checks */ + +static int name_frame, name_tick, name_a, name_b, name_c, name_texture, name_pass, name_inner_aggregate; + +static void frame(int scopes) +{ + int index; + + profile_trace_frame_boundary(); + profile_trace_begin(name_frame); + for (index = 0; index < scopes; index++) + { + profile_trace_begin(name_a); + advance(1000); + profile_trace_begin(name_b); + advance(500); + profile_trace_end(name_b); + profile_trace_end(name_a); + } + advance(1000); + profile_trace_end(name_frame); +} + +static void scope_checks(void) +{ + struct profile_trace_status status; + int index; + + forget_written(); + check(profile_trace_request_start(0.0, _profile_trace_when_now, 4) == _profile_trace_answer_armed, + "profile_record arms"); + profile_trace_status(&status); + check(status.state == _profile_trace_armed, "armed until the next frame boundary"); + check(strncmp(status.name, "profile_", 8) == 0 && strstr(status.name, "_host"), "named when asked for"); + check(!profile_trace_recording(), "not recording before the boundary"); + frame(3); + check(profile_trace_recording(), "recording from the boundary"); + /* the first map load after launch (the main menu) does not stop it */ + profile_trace_map_loaded(); + profile_trace_frame_boundary(); + check(profile_trace_recording(), "the first map load does not stop a recording"); + check(profile_trace_request_start(0.0, _profile_trace_when_now, 4) == _profile_trace_answer_already_recording, + "a second profile_record is refused"); + + /* nesting deeper than the stack: not recorded, counted, and the rest + still balances */ + profile_trace_frame_boundary(); + for (index = 0; index < MAXIMUM_PROFILE_TRACE_DEPTH + 3; index++) + profile_trace_begin(name_c); + for (index = 0; index < MAXIMUM_PROFILE_TRACE_DEPTH + 3; index++) + profile_trace_end(name_c); + /* an end that is not the top's, and one with nothing open */ + profile_trace_begin(name_a); + profile_trace_end(name_b); + profile_trace_end(name_a); + profile_trace_end(name_b); + /* aggregate scopes: summed for the frame, no records */ + profile_trace_begin(name_texture); + profile_trace_begin(name_inner_aggregate); + advance(500); + profile_trace_begin(name_aggregate_child); + advance(1000); + profile_trace_end(name_aggregate_child); + profile_trace_end(name_inner_aggregate); + advance(500); + profile_trace_end(name_texture); + for (index = 0; index < 5; index++) + { + profile_trace_begin(name_texture); + advance(2000); + profile_trace_end(name_texture); + } + /* a scope left open is dropped at the boundary, and counted */ + profile_trace_begin(name_a); + frame(1); + check(profile_trace_request_stop() == _profile_trace_answer_stopping, "profile_stop stops"); + check(profile_trace_recording(), "a stop applies at the next boundary"); + profile_trace_frame_boundary(); + check(!profile_trace_recording(), "stopped at the boundary"); + check(profile_trace_request_start(0.0, _profile_trace_when_now, 4) == _profile_trace_answer_still_writing || + writer_finished(), "profile_record while writing is refused"); + finish(); + check(written.finished == 1 && written.parts == 1, "one part written"); + check(written.counts[name_frame] == 2, "two frames recorded"); + check(written.counts[name_a] == 5 && written.counts[name_b] == 4, "every scope recorded once"); + check(written.counts[name_c] == MAXIMUM_PROFILE_TRACE_DEPTH, "scopes within the stack recorded"); + check(written.deep == 3, "scopes past the stack counted"); + check(written.unbalanced == 3, "unbalanced ends and the open scope counted"); + check(written.counts[name_texture] == 0 && written.counter_records >= 2, "aggregate names give counters"); + check(written.aggregate_child_depth == 0, "ordinary scopes inside an aggregate keep visible depth"); + check(written.aggregate_count[name_texture] == 6 && written.aggregate_time[name_texture] == 12000, + "outer aggregate totals include calls nested with another aggregate"); + check(written.aggregate_count[name_inner_aggregate] == 1 && written.aggregate_time[name_inner_aggregate] == 1500, + "nested aggregate keeps its own cpu summary count and time"); + check(written.counts[name_aggregate_child] == 1, "ordinary nested scope contributes one cpu summary count"); + check(written.stop_reason[0] == _profile_trace_stop_command, "stopped by the command"); + check(outstanding() == 0, "both arenas freed after the recording"); +} + +static void state_checks(void) +{ + struct profile_trace_status status; + + /* game: armed until a game is in progress, stopped by a map load, then + armed again */ + forget_written(); + fake_ready = 0; + fake_map = ""; + fake_map_name = ""; + check(profile_trace_request_start(0.0, _profile_trace_when_game, 4) == _profile_trace_answer_armed, "armed for a game"); + frame(1); + frame(1); + profile_trace_status(&status); + check(status.state == _profile_trace_armed, "waits for a game"); + fake_ready = 1; + frame(1); + check(profile_trace_recording(), "records once a game is in progress"); + frame(1); + fake_map = "levels\\next\\next"; + fake_map_name = "next"; + profile_trace_map_loaded(); + frame(1); + check(!profile_trace_recording(), "a map load stops it"); + finish(); + fake_map = "levels\\a30\\a30"; + fake_map_name = "a30"; + profile_trace_status(&status); + check(status.state == _profile_trace_armed, "armed again for the next game"); + check(written.stop_reason[0] == _profile_trace_stop_map_load, "stopped by the map load"); + check(strcmp(written.map_name[0], "next") != 0, "the map that stops a recording is not sampled into it"); + frame(1); + check(profile_trace_recording(), "the next game is recorded"); + check(profile_trace_request_stop() == _profile_trace_answer_stopping, "stopped by hand"); + frame(1); + finish(); + profile_trace_status(&status); + check(status.state == _profile_trace_idle, "a stop by hand does not arm again"); + check(outstanding() == 0, "nothing left allocated"); + + /* the seconds of profile_record */ + forget_written(); + profile_trace_request_start(0.5, _profile_trace_when_now, 4); + frame(1); + advance(400000000ULL); + frame(1); + check(profile_trace_recording(), "still recording before its seconds"); + advance(200000000ULL); + frame(1); + check(!profile_trace_recording(), "stopped after its seconds"); + finish(); + check(written.stop_reason[0] == _profile_trace_stop_seconds, "stopped by its seconds"); + + /* no memory: not recording, said so, nothing kept */ + allocations_refused = 1; + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + frame(1); + allocations_refused = 0; + profile_trace_status(&status); + check(status.state == _profile_trace_idle && strstr(last_notice, "no memory for 4 MB"), "no memory is reported"); + check(outstanding() == 0, "a failed allocation keeps nothing"); +} + +static volatile int p2p_running; +static unsigned long p2p_recorded; + +static void *p2p_thread(void *unused) +{ + (void)unused; + profile_trace_thread_register(_profile_track_p2p); + while (__atomic_load_n(&p2p_running, __ATOMIC_RELAXED)) + { + fake_lock(); + if (profile_trace_recording()) + p2p_recorded++; + profile_trace_begin(name_pass); + profile_trace_end(name_pass); + fake_unlock(); + /* (a pass every 50 microseconds: the p2p region holds an eighth of + the scopes, as the game's p2p thread has far fewer) */ + usleep(50); + } + return NULL; +} + +/* what profile_stop answers while a recording is only armed: a game is waited for +only by "game" mode, which stop ends too */ +static void stop_answer_checks(void) +{ + struct profile_trace_status status; + + check(profile_trace_request_start(0.0, _profile_trace_when_now, 4) == _profile_trace_answer_armed, "armed for the next frame"); + check(profile_trace_request_stop() == _profile_trace_answer_not_recording, + "stopped before it began, a recording armed for the next frame was waiting for no game"); + profile_trace_status(&status); + check(status.state == _profile_trace_idle, "stopped while armed, it does not start"); + check(profile_trace_request_start(0.0, _profile_trace_when_game, 4) == _profile_trace_answer_armed, "armed for a game"); + check(profile_trace_request_stop() == _profile_trace_answer_disarmed, "a recording armed for a game is not waited for any more"); + profile_trace_status(&status); + check(status.state == _profile_trace_idle, "stopped while armed for a game, it does not start"); +} + +static void part_checks(void) +{ + pthread_t thread; + unsigned long frames_recorded = 0; + int frame_index, part; + int continuous = 1; + + forget_written(); + p2p_recorded = 0; + p2p_running = 1; + pthread_create(&thread, NULL, p2p_thread, NULL); + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + /* (4 MB, CPU-only: about 114 000 game records a part) */ + for (frame_index = 0; frame_index < 3000; frame_index++) + { + frame(50); + if (profile_trace_recording()) + frames_recorded++; + if (frame_index % 30 == 0) + profile_trace_tick(); + advance(1000000); + } + profile_trace_request_stop(); + profile_trace_frame_boundary(); + __atomic_store_n(&p2p_running, 0, __ATOMIC_RELAXED); + pthread_join(thread, NULL); + finish(); + fake_role = "host"; + fake_players = 2; + fake_own_machine = -1; + check(written.parts >= 3, "a long recording is cut into parts"); + check(written.counts[name_frame] == frames_recorded, "every frame in exactly one part"); + check(written.counts[name_a] == frames_recorded * 50, "every scope in exactly one part"); + check(written.counts[name_pass] == p2p_recorded, "every recorded p2p pass in exactly one part"); + check(written.dropped == 0, "nothing dropped"); + for (part = 1; part < written.parts && part < MAXIMUM_PARTS; part++) + { + continuous &= written.first_frame[part] == written.first_frame[part - 1] + written.frames[part - 1]; + continuous &= written.start_ns[part] == written.end_ns[part - 1]; + continuous &= !written.last[part - 1]; + } + check(continuous, "frames and time continue across parts"); + check(written.last[written.parts - 1], "only the last part is the last"); + check(written.records_in_span, "each record within its part"); + check(outstanding() == 0, "both arenas freed after a long recording"); + + /* a cut while the writer holds the other arena waits for it */ + forget_written(); + written.delay_milliseconds = 1000; + p2p_recorded = 0; + p2p_running = 1; + pthread_create(&thread, NULL, p2p_thread, NULL); + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + frames_recorded = 0; + for (frame_index = 0; frame_index < 3000; frame_index++) + { + frame(50); + if (profile_trace_recording()) + frames_recorded++; + } + profile_trace_request_stop(); + profile_trace_frame_boundary(); + __atomic_store_n(&p2p_running, 0, __ATOMIC_RELAXED); + pthread_join(thread, NULL); + finish(); + written.delay_milliseconds = 0; + check(written.records_in_span, "each record within its part while the writer is slow"); + check(written.counts[name_pass] == p2p_recorded, "a slow writer loses no p2p pass"); + check(written.parts >= 3, "parts while the writer is slow"); + check(written.writer_wait_ms > 0.0, "the wait for the writer is counted"); + check(written.counts[name_a] == frames_recorded * 50, "a slow writer loses nothing"); + check(written.counts[profile_trace_name("profile_wait")] >= 1, "the wait is a scope"); +} + +static int straddle_begun; +static int straddle_released; + +/* a p2p pass that lets go of its lock for slow work (p2p_resolve's DNS) */ +static void *straddling_pass(void *unused) +{ + (void)unused; + profile_trace_thread_register(_profile_track_p2p); + fake_lock(); + profile_trace_begin(name_pass); + fake_unlock(); + __atomic_store_n(&straddle_begun, 1, __ATOMIC_RELEASE); + while (!__atomic_load_n(&straddle_released, __ATOMIC_ACQUIRE)) + usleep(100); + fake_lock(); + profile_trace_end(name_pass); + fake_unlock(); + return NULL; +} + +static void straddle(int cut) +{ + pthread_t thread; + struct profile_trace_status status; + + forget_written(); + straddle_begun = straddle_released = 0; + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + frame(1); + pthread_create(&thread, NULL, straddling_pass, NULL); + while (!__atomic_load_n(&straddle_begun, __ATOMIC_ACQUIRE)) + usleep(100); + if (cut) + { + /* frames until a cut comes, while the pass is out of its lock */ + do + { + frame(50); + profile_trace_status(&status); + } + while (status.part < 2); + } + else + { + profile_trace_request_stop(); + profile_trace_frame_boundary(); + } + __atomic_store_n(&straddle_released, 1, __ATOMIC_RELEASE); + pthread_join(thread, NULL); + if (cut) + { + profile_trace_request_stop(); + profile_trace_frame_boundary(); + } + finish(); +} + +/* a pass begun in one recording and ended in the next (a stop, a re-arm and +a start between): its start is before the new origin, so it is dropped, and +counted in the new recording. concurrent: it ends while the start is made */ +static void straddle_restart(int concurrent) +{ + pthread_t thread; + + forget_written(); + straddle_begun = straddle_released = 0; + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + frame(1); + pthread_create(&thread, NULL, straddling_pass, NULL); + while (!__atomic_load_n(&straddle_begun, __ATOMIC_ACQUIRE)) + usleep(100); + profile_trace_request_stop(); + profile_trace_frame_boundary(); + finish(); + forget_written(); + advance(1000000); + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + if (concurrent) + { + __atomic_store_n(&straddle_released, 1, __ATOMIC_RELEASE); + frame(1); + } + else + { + frame(1); + __atomic_store_n(&straddle_released, 1, __ATOMIC_RELEASE); + } + pthread_join(thread, NULL); + frame(1); + profile_trace_request_stop(); + profile_trace_frame_boundary(); + finish(); +} + +static void straddle_checks(void) +{ + straddle(1); + check(written.counts[name_pass] == 1, "a pass across a cut is in the part it ended in, once"); + straddle(0); + check(written.counts[name_pass] == 0, "a pass that outlives its recording is not written"); + check(outstanding() == 0, "nothing left after a pass across a stop"); + straddle_restart(0); + check(written.counts[name_pass] == 0, "a pass across a restart is dropped, not given a wrapped start"); + check(written.dropped == 1, "and counted in the new recording"); + straddle_restart(1); + check(written.counts[name_pass] == 0, "a pass ending as the next recording starts is dropped"); + check(outstanding() == 0, "nothing left after a pass across a restart"); +} + +static void lifetime_checks(void) +{ + int cycle; + + /* many recordings, cut and not, stopped every way, with nothing left */ + for (cycle = 0; cycle < 40; cycle++) + { + int frames = cycle % 4 == 0 ? 2500 : 3; + int index; + + forget_written(); + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + for (index = 0; index < frames; index++) + frame(50); + if (cycle % 3 == 0) + { + profile_trace_map_loaded(); + profile_trace_frame_boundary(); + } + else if (cycle % 3 == 1) + { + profile_trace_request_stop(); + profile_trace_frame_boundary(); + } + else + { + profile_trace_shutdown(); + } + finish(); + if (outstanding() != 0) + { + check(0, "a recording leaves nothing allocated"); + break; + } + } + check(allocations_made > 80, "the cycles allocated"); + /* exit while armed, while writing, and twice */ + profile_trace_request_start(0.0, _profile_trace_when_game, 4); + fake_ready = 0; + profile_trace_shutdown(); + profile_trace_shutdown(); + check(outstanding() == 0, "shutdown while armed keeps nothing"); + fake_ready = 1; +} + +/* a real recording through the real writer, into the folder */ +static void file_checks(const char *folder) +{ + struct profile_trace_status status; + int frame_index; + int quoted; + + fake_folder = folder; + profile_trace_set_writer(NULL); + profile_trace_set_session_sampler(fake_session_sample); + quoted = profile_trace_name("quote\"back\\slash"); + fake_map = ""; + fake_map_name = ""; + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + fake_role = "client"; + fake_players = 5; + fake_own_machine = 1; + fake_map = "levels\\a30\\a30"; + fake_map_name = "a30"; + for (frame_index = 0; frame_index < 2400; frame_index++) + { + if (frame_index == 2) + { + fake_map = "levels\\b30\\b30"; + fake_map_name = "b30"; + } + frame(40); + profile_trace_begin(name_texture); + profile_trace_begin(name_inner_aggregate); + advance(100); + profile_trace_end(name_inner_aggregate); + profile_trace_begin(name_aggregate_child); + advance(100); + profile_trace_end(name_aggregate_child); + profile_trace_end(name_texture); + profile_trace_begin(quoted); + advance(10); + profile_trace_end(quoted); + if (frame_index % 2 == 0) + { + profile_trace_begin(name_tick); + profile_trace_tick(); + advance(100); + profile_trace_end(name_tick); + } + advance(16000000); + } + profile_trace_shutdown(); + check(session_sample_calls > 0 && session_sample_calls < 2400, + "session sampling is periodic and not per frame"); + fake_role = "host"; + fake_players = 2; + fake_own_machine = -1; + fake_map = "levels\\a30\\a30"; + fake_map_name = "a30"; + check(outstanding() == 0, "the real writer frees both arenas"); + profile_trace_status(&status); + { + char name[64]; + char path[512]; + FILE *file; + + profile_json_choose_name(folder, "20261005-142233", "host", name, sizeof(name)); + check(strcmp(name, "profile_20261005-142233_host") == 0, "a free name is taken as it is"); + snprintf(path, sizeof(path), "%s/%s.part1.json", folder, name); + file = fopen(path, "wb"); + if (file) + fclose(file); + profile_json_choose_name(folder, "20261005-142233", "host", name, sizeof(name)); + check(strcmp(name, "profile_20261005-142233_host_2") == 0, "a name taken gets _2"); + remove(path); + } +} + +/* a folder that cannot be written: each part is logged and given up, its +.tmp gone, the arenas freed, and the recording still finishes */ +static void unwritable_checks(const char *folder) +{ + char missing[512]; + int frame_index; + + snprintf(missing, sizeof(missing), "%s/missing/deeper", folder); + fake_folder = missing; + last_log[0] = 0; + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + for (frame_index = 0; frame_index < 2400; frame_index++) + frame(40); + profile_trace_request_stop(); + profile_trace_frame_boundary(); + finish(); + check(strstr(last_log, "cannot write") && strstr(last_log, "missing/deeper"), "a part that cannot be written is logged"); + check(outstanding() == 0, "a part that cannot be written frees its arena"); + fake_folder = folder; +} + +static int refusing_writer_start(void) +{ + return 0; +} + +/* a start that fails after it has allocated: nothing is kept, it says so, and +a recording of "game" mode is not armed again to fail at every game */ +static void release_path_checks(void) +{ + static const char *const what[] = { "the second arena refused", "the writer thread cannot start" }; + int fail, game; + + for (fail = 0; fail < 2; fail++) + { + for (game = 0; game < 2; game++) + { + struct profile_trace_status status; + char text[96]; + + snprintf(text, sizeof(text), "%s, %s mode", what[fail], game ? "game" : "now"); + forget_written(); + fake_ready = 1; + last_notice[0] = 0; + if (fail == 0) + allocations_allowed = 1; + else + profile_trace_set_writer(refusing_writer_start); + profile_trace_request_start(0.0, game ? _profile_trace_when_game : _profile_trace_when_now, 4); + frame(1); + allocations_allowed = -1; + profile_trace_set_writer(fake_writer_start); + profile_trace_status(&status); + check(outstanding() == 0, text); + check(status.state == _profile_trace_idle, text); + check(strstr(last_notice, fail == 0 ? "no memory for 4 MB" : "the writer thread cannot start") != NULL, text); + check(!profile_trace_recording(), text); + frame(1); + profile_trace_status(&status); + check(status.state == _profile_trace_idle && outstanding() == 0, text); + } + } +} + +/* a worst frame's ticks and longest scopes come from any depth of the frame: +game_tick sits under game_time_update, and the time a scope has of its own is +what ranks it (its parents would always come first otherwise) */ +static void worst_frame_checks(void) +{ + struct profile_trace_record game[16]; + struct profile_part *part = calloc(1, sizeof(*part)); + int frame_id = profile_trace_name("frame"); + int update = profile_trace_name("game_time_update"); + int tick = profile_trace_name("game_tick"); + int objects = profile_trace_name("game_tick.objects"); + int ai = profile_trace_name("game_tick.ai"); + int render = profile_trace_name("render"); + int bsp = profile_trace_name("render.bsp"); + int texture = profile_trace_name("texture"); + char text[65536]; + unsigned long count = 0, length; + FILE *file = tmpfile(); + const char *row; + + /* (milliseconds, as nanoseconds below) */ +#define WORST_RECORD(start_ms, duration_ms, id, level) \ + do { game[count].start = (start_ms) * 1000000ULL; game[count].duration = (duration_ms) * 1000000U; \ + game[count].name = (unsigned short)(id); game[count].depth = (level); game[count].track = 0; count++; } while (0) + WORST_RECORD(0, 100, frame_id, 0); + WORST_RECORD(0, 60, update, 1); + WORST_RECORD(0, 30, tick, 2); + WORST_RECORD(0, 20, objects, 3); + WORST_RECORD(30, 25, tick, 2); + WORST_RECORD(30, 22, ai, 3); + WORST_RECORD(60, 35, render, 1); + WORST_RECORD(60, 10, bsp, 2); + /* (an aggregate's time is a counter record, not a scope) */ + WORST_RECORD(5, 99, texture, PROFILE_TRACE_DEPTH_AGGREGATE_TIME); + WORST_RECORD(100, 5, frame_id, 0); +#undef WORST_RECORD + part->records[_profile_track_game] = game; + part->record_counts[_profile_track_game] = count; + part->name_count = profile_trace_name("worst_frame_last") + 1; + part->ticks = 2; + check(file != NULL, "a scratch file for the worst frames"); + if (!file) + { + free(part); + return; + } + check(profile_json_write(part, file), "a part with nested scopes is written"); + rewind(file); + length = fread(text, 1, sizeof(text) - 1, file); + text[length] = 0; + fclose(file); + row = strstr(text, "\"worst_frames\""); + check(row && strstr(row, "[0, 0.000, 100.000, 2, [[\"render\", 25.000], [\"game_tick.ai\", 22.000], " + "[\"game_tick.objects\", 20.000]]]"), + "the worst frame runs its two ticks under game_time_update and names the scopes with the most time of their own"); + check(row && strstr(row, "[1, 0.100, 5.000, 0, []]"), "a frame with no scopes in it has no ticks and no longest"); + free(part); +} + +/* the reset race: a p2p pass begun in one recording ends, from inside +the next recording's allocator seam, before that start resets the tracks' +counts. Only the track lock orders its bump before the reset, which +ThreadSanitizer checks (the reset moved out of the lock is a race) */ +static int race_armed; +static int race_ended; + +static void *race_pass(void *unused) +{ + (void)unused; + profile_trace_thread_register(_profile_track_p2p); + fake_lock(); + profile_trace_begin(name_pass); + fake_unlock(); + __atomic_store_n(&straddle_begun, 1, __ATOMIC_RELEASE); + /* (relaxed: no happens-before from the game thread's release) */ + while (!__atomic_load_n(&straddle_released, __ATOMIC_RELAXED)) + usleep(100); + fake_lock(); + profile_trace_end(name_pass); + fake_unlock(); + __atomic_store_n(&race_ended, 1, __ATOMIC_RELAXED); + return NULL; +} + +static void *race_allocate(unsigned long size) +{ + if (__atomic_load_n(&race_armed, __ATOMIC_RELAXED)) + { + __atomic_store_n(&race_armed, 0, __ATOMIC_RELAXED); + __atomic_store_n(&straddle_released, 1, __ATOMIC_RELAXED); + while (!__atomic_load_n(&race_ended, __ATOMIC_RELAXED)) + usleep(100); + } + return fake_allocate(size); +} + +static void reset_race_checks(void) +{ + pthread_t thread; + + profile_trace_set_allocator(race_allocate, fake_release); + forget_written(); + straddle_begun = straddle_released = 0; + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + frame(1); + pthread_create(&thread, NULL, race_pass, NULL); + while (!__atomic_load_n(&straddle_begun, __ATOMIC_ACQUIRE)) + usleep(100); + profile_trace_request_stop(); + profile_trace_frame_boundary(); + finish(); + + forget_written(); + advance(1000000); + __atomic_store_n(&race_armed, 1, __ATOMIC_RELAXED); + profile_trace_request_start(0.0, _profile_trace_when_now, 4); + frame(1); + pthread_join(thread, NULL); + frame(1); + profile_trace_request_stop(); + profile_trace_frame_boundary(); + finish(); + check(written.counts[name_pass] == 0, "the pass of the last recording is not written"); + check(written.dropped == 0, "a pass ended before the start is not counted in the new recording"); + check(outstanding() == 0, "the race recordings leave nothing allocated"); + profile_trace_set_allocator(fake_allocate, fake_release); +} + +int main(int argc, char **argv) +{ + profile_trace_set_clock(fake_clock); + profile_trace_set_track_lock(fake_lock, fake_unlock); + profile_trace_set_allocator(fake_allocate, fake_release); + profile_trace_set_log(quiet_log); + profile_trace_set_notify(fake_notify); + profile_trace_set_writer(fake_writer_start); + profile_trace_set_ready(fake_ready_test); + profile_trace_set_session(fake_session); + profile_trace_set_session_sampler(fake_session_sample); + gametype_checks(); + profile_trace_thread_register(_profile_track_game); + name_frame = profile_trace_name("frame"); + name_tick = profile_trace_name("game_tick"); + name_a = profile_trace_name("game_tick.objects"); + name_b = profile_trace_name("objects_update"); + name_c = profile_trace_name("deep"); + name_pass = profile_trace_name("p2p.pass"); + name_aggregate_child = profile_trace_name("aggregate_child"); + profile_trace_name_aggregate("texture"); + profile_trace_name_aggregate("render_model"); + name_texture = profile_trace_name("texture"); + name_inner_aggregate = profile_trace_name("render_model"); + fake_ready = 1; + written.records_in_span = 1; + + scope_checks(); + state_checks(); + stop_answer_checks(); + part_checks(); + straddle_checks(); + lifetime_checks(); + worst_frame_checks(); + release_path_checks(); + reset_race_checks(); + if (argc > 1) + { + file_checks(argv[1]); + unwritable_checks(argv[1]); + } + printf("%s (%d failures)\n", failures ? "FAIL" : "PASS", failures); + return failures != 0; +} diff --git a/tools/profile_fixture.py b/tools/profile_fixture.py new file mode 100644 index 00000000..79ecc683 --- /dev/null +++ b/tools/profile_fixture.py @@ -0,0 +1,66 @@ +"""Hand-written CPU recording parts for the report tests.""" + +import json +from pathlib import Path + + +def part(number, intervals, last, frames_before, ticks_before): + frames = 60 * len(intervals) + ticks = 30 * len(intervals) + header = { + "format": 1, "build": "0 debug profile", "platform": "windows", "role": "host", "own_machine": -1, + "map": "levels\\a30\\a30", "map_name": "a30", "gametype": "campaign", + "players": 3 if number == 1 else 4, "players_most": 4, "start_utc": "2026-10-05 14:22:33", + "recording": "profile_20261005-142233_host", "part": number, "last_part": last, + "first_frame": frames_before, "frames": frames, + "first_tick": ticks_before, "ticks": ticks, "start_s": float(intervals[0]), "duration_s": float(len(intervals)), + "stop_reason": "command" if last else "", "clock_read_ns": 21.5, "memory_used": 1000, "memory_limit": 16777216, + "writer_wait_ms": 0.0, "foreign_scopes": 0, "deep_scopes": 0, "unbalanced_scopes": 0, "dropped_scopes": 0, + "dropped_scopes": 0, + } + halo = {"header": header} + cpu_rows = [ + ["frame", frames, 8.3 * frames, 8.3, 41.2, 1.0, None], + ["game_tick", ticks, 3.2 * ticks, 3.2, 6.012, 0.5, 3.2], + ["game_tick.objects", ticks, 1.4 * ticks, 1.4, 12.1, 0.5, 1.4], + ["network_distributed_tick", ticks, 0.6 * ticks, 0.6, 4.0, 0.5, 0.6], + ["render", frames, 4.0 * frames, 4.0, 9.8, 1.0, None], + ] + worst = [[frames_before + 12, frames_before / 60.0 + 0.2, 41.2 - number, 2, + [["game_tick.objects", 12.1], ["render", 9.8], ["network_distributed_tick", 4.0]]]] + cpu = {"frames": frames, "ticks": ticks, + "columns": ["name", "count", "total_ms", "mean_ms", "max_ms", "per_frame", "per_tick_ms"], + "rows": cpu_rows, "worst_frames": {"columns": ["frame", "at_s", "frame_ms", "ticks", "longest"], + "rows": worst}} + return halo, cpu + + +def write_part(path, halo, cpu): + """the writer's layout: one item per line, the events last""" + lines = ['{"halo": {', '"header": ' + json.dumps(halo["header"])] + for name, table in halo.items(): + if name == "header": + continue + lines[-1] += "," + lines.append(f'"{name}": {{"columns": {json.dumps(table["columns"])}, "rows": [') + lines.extend(json.dumps(row) + ("," if index < len(table["rows"]) - 1 else "") + for index, row in enumerate(table["rows"])) + lines.append("]}") + lines[-1] += "}," + lines.append('"cpu_summary": ' + json.dumps(cpu) + ",") + lines.append('"traceEvents": [') + lines.append('{"ph":"M","name":"process_name","pid":1,"args":{"name":"halo host windows"}}') + lines.append("],") + lines.append('"displayTimeUnit": "ms"}') + Path(path).write_text("\n".join(lines) + "\n", encoding="utf-8") + + +def write_recording(folder, name="profile_20261005-142233_host", split=True): + """three parts of two intervals each, or the same six in one part""" + folder = Path(folder) + folder.mkdir(parents=True, exist_ok=True) + spans = [[0, 1], [2, 3], [4, 5]] if split else [[0, 1, 2, 3, 4, 5]] + for index, intervals in enumerate(spans): + halo, cpu = part(index + 1, intervals, index == len(spans) - 1, intervals[0] * 60, intervals[0] * 30) + write_part(folder / f"{name}.part{index + 1}.json", halo, cpu) + return folder / name diff --git a/tools/profile_fixture.summary.txt b/tools/profile_fixture.summary.txt new file mode 100644 index 00000000..1ba3af2a --- /dev/null +++ b/tools/profile_fixture.summary.txt @@ -0,0 +1,35 @@ +# halo profile summary, format 1 +# recording: profile_20261005-142233_host, parts 1-3 (complete) +# build: 0 debug profile, platform: windows, role: host, own_machine: - +# map: levels\a30\a30, players: 3 at start, 4 most +# recorded: 2026-10-05 14:22:33 UTC, 6.0 s, 180 ticks, 360 frames, stopped: command +# problems: none +# units: ms = milliseconds + +## columns +scope CPU scope name (a profile section or frame timer); nested names use "." +count times the scope ran in the recording +total_ms summed inclusive time (children included), milliseconds +mean_ms total_ms / count +max_ms longest single run; aggregate sections: largest summed time in one frame +per_frame count / frames recorded +per_tick_ms total_ms / ticks recorded (game_tick.* and network_distributed_tick* only) +frame frame number in the recording +at_s seconds from the start of the recording to that frame +frame_ms that frame's duration +ticks game ticks that frame ran +longest scopes (ms) the frame's three scopes, at any depth, with the most time of their own (children not counted), name and that time + +## cpu scopes (top 40 by total_ms) +scope count total_ms mean_ms max_ms per_frame per_tick_ms +frame 360 2988.0 8.300 41.200 1.00 - +render 360 1440.0 4.000 9.800 1.00 - +game_tick 180 576.0 3.200 6.012 0.50 3.200 +game_tick.objects 180 252.0 1.400 12.100 0.50 1.400 +network_distributed_tick 180 108.0 0.600 4.000 0.50 0.600 + +## worst frames (10) +frame at_s frame_ms ticks longest scopes (ms) + 12 0.20 40.20 2 game_tick.objects 12.1, render 9.8, network_distributed_tick 4.0 + 132 2.20 39.20 2 game_tick.objects 12.1, render 9.8, network_distributed_tick 4.0 + 252 4.20 38.20 2 game_tick.objects 12.1, render 9.8, network_distributed_tick 4.0 diff --git a/tools/test_host_watch_hash.py b/tools/test_host_watch_hash.py new file mode 100644 index 00000000..c965e711 --- /dev/null +++ b/tools/test_host_watch_hash.py @@ -0,0 +1,34 @@ +"""Runs the C tests of the Android host's page-hash write tracking +(port/android/host/host_watch_hash.c) with the build machine's C compiler. +The unit has no Android dependency, so it compiles anywhere.""" +import shutil +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parent.parent + + +def compiler(): + for name in ("clang", "cc", "gcc"): + path = shutil.which(name) + if path: + return path + return None + + +def test_host_watch_hash(tmp_path): + cc = compiler() + if not cc: + pytest.skip("no C compiler") + binary = tmp_path / "host_watch_hash_test" + subprocess.run( + [cc, "-std=c11", "-Wall", "-Wextra", "-Werror", "-O1", + str(ROOT / "port" / "android" / "tests" / "host_watch_hash_test.c"), + str(ROOT / "port" / "android" / "host" / "host_watch_hash.c"), + "-o", str(binary)], + check=True, + ) + result = subprocess.run([str(binary)], capture_output=True, text=True) + assert result.returncode == 0, result.stdout + result.stderr diff --git a/tools/test_linux_port.py b/tools/test_linux_port.py index 5fcf7554..4f22447b 100644 --- a/tools/test_linux_port.py +++ b/tools/test_linux_port.py @@ -156,11 +156,15 @@ def test_game_sources_leave_out_the_excluded(tmp_path, monkeypatch): write(tmp_path / "source" / "a.c", "") write(tmp_path / "source" / "zlib" / "b.c", "") write(tmp_path / "source" / "zlib" / "example.c", "") + write(tmp_path / "source" / "tiff" / "c.c", "") + write(tmp_path / "source" / "tiff" / "deep" / "d.c", "") + write(tmp_path / "source" / "tiffx.c", "") write(tmp_path / "source" / "a.h", "") monkeypatch.chdir(tmp_path) - config = {"game": {"root": "source", "exclude": ["source/zlib/example.c"], + # (a folder is a name ending in "/": not a file that only starts like it) + config = {"game": {"root": "source", "exclude": ["source/zlib/example.c", "source/tiff/"], "defines": ["DEBUG"], "include_dirs": ["source", "source/saved games"]}} - assert linux_build.game_sources(config) == [Path("source/a.c"), Path("source/zlib/b.c")] + assert linux_build.game_sources(config) == [Path("source/a.c"), Path("source/tiffx.c"), Path("source/zlib/b.c")] assert linux_build.game_defines_and_includes(config) == '-DDEBUG -Isource -I"source/saved games"' diff --git a/tools/test_profile.py b/tools/test_profile.py new file mode 100644 index 00000000..d3533ecf --- /dev/null +++ b/tools/test_profile.py @@ -0,0 +1,569 @@ +"""Profiling build, CPU recorder, written parts and CPU report tests. +For local WSL runs in a Windows worktree, set HALO_GIT=git.exe. +""" + +import io +import json +import os +import re +import shlex +import shutil +import subprocess +import tarfile +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from tools import linux_build, net_report, ninja_syntax, profile_fixture + +ROOT = Path(__file__).resolve().parent.parent +GIT = os.environ.get("HALO_GIT", "git") + + +# ---------- the build switch + + +def test_configuration_defines(): + assert linux_build.configuration_defines(SimpleNamespace()) == [] + assert linux_build.configuration_defines(SimpleNamespace(port_release=True)) == ["-DHALO_RELEASE"] + assert linux_build.configuration_defines(SimpleNamespace(port_release=True, port_profile=True)) == [ + "-DHALO_RELEASE", "-DHALO_PROFILE"] + + +def test_every_native_build_takes_the_configuration_defines(): + # (the guest, the game and the platform layer of all three: one place + # decides what --release and --profile define) + for name in ("linux_build.py", "windows_build.py", "android_build.py"): + text = (ROOT / "tools" / name).read_text(encoding="utf-8") + assert "configuration_defines(sln)" in text, name + assert '["-DHALO_RELEASE"] if' not in text, name + + +def test_profile_refuses_pgo_training(): + with pytest.raises(ValueError, match="--pgo=train"): + linux_build.check_profile_options(True, "train") + linux_build.check_profile_options(True, "use") + linux_build.check_profile_options(True, "off") + linux_build.check_profile_options(False, "train") + + +def test_linux_build_compiles_with_the_profile_define(monkeypatch): + monkeypatch.chdir(ROOT) + out = io.StringIO() + linux_build.generate_linux_build(ninja_syntax.Writer(out), SimpleNamespace( + build_dir=Path("build"), linux_cc="clang", compiler_launcher=None, port_release=False, port_lto="off", + port_portable=True, port_pgo="off", port_pgo_profile=None, port_profile=True)) + # (ninja_syntax wraps long lines with " $"; Windows paths have backslashes) + text = re.sub(r" \$\n *", " ", out.getvalue()).replace("\\", "/") + for obj in ("build/linux/obj/source/game/game.o", "build/linux/obj/port/linux/src/p2p.o"): + block = text[text.index(f"build {obj}:"):] + cflags = next(line for line in block.splitlines() if line.strip().startswith("cflags = ")) + assert "-DHALO_PROFILE" in cflags, obj + + +# ---------- the recording's C units (tools/profile_check.c) + +PROFILE_SOURCES = ["port/linux/src/profile_trace.c", "port/linux/src/profile_json.c"] + + +def check_compiler(): + """a C compiler of this machine with POSIX threads, or None (Windows has + them only through the port's own layer, so the check runs on Linux and + in WSL)""" + if os.name == "nt": + return None + for name in ("clang", "cc", "gcc"): + if shutil.which(name): + return name + return None + + +def build_check(tmp_path, sanitizers): + compiler = check_compiler() + if not compiler: + pytest.skip("needs a C compiler with POSIX threads (Linux, WSL)") + program = tmp_path / ("profile_check" + "".join(f"_{name}" for name in sanitizers)) + flags = [f"-fsanitize={','.join(sanitizers)}", "-fno-omit-frame-pointer"] if sanitizers else [] + built = subprocess.run([compiler, "-std=gnu11", "-Wall", "-Werror", "-DHALO_PROFILE", "-pthread", "-g", "-O1", + *flags, "-idirafter", "port/linux/include", "-Iport/linux/src", + "-o", str(program), + "tools/profile_check.c", *PROFILE_SOURCES], + cwd=ROOT, capture_output=True, text=True) + if built.returncode != 0 and sanitizers and "sanitize" in built.stderr: + pytest.skip(f"{compiler} cannot build with {sanitizers}") + assert built.returncode == 0, built.stderr[-4000:] + return program + + +def run_check(program, folder): + folder.mkdir(exist_ok=True) + result = subprocess.run([str(program), str(folder)], capture_output=True, text=True, timeout=300, + env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1", "TSAN_OPTIONS": "halt_on_error=1"}) + assert result.returncode == 0, result.stdout + result.stderr[-4000:] + assert "PASS" in result.stdout + + +def test_check_program_passes(tmp_path): + run_check(build_check(tmp_path, []), tmp_path / "out") + + +def test_check_program_has_no_leaks_or_memory_errors(tmp_path): + """AddressSanitizer and LeakSanitizer over every recording the check + makes: arenas, the writer's files, the parts""" + run_check(build_check(tmp_path, ["address", "undefined"]), tmp_path / "out") + + +def test_check_program_has_no_data_races(tmp_path): + """ThreadSanitizer: the p2p track, the writer hand-off, the recording + flag""" + run_check(build_check(tmp_path, ["thread"]), tmp_path / "out") + + +@pytest.fixture(scope="module") +def written_parts(tmp_path_factory): + folder = tmp_path_factory.mktemp("written") + run_check(build_check(tmp_path_factory.mktemp("build"), []), folder) + recordings = {} + for path in folder.glob("*.part*.json"): + try: + header = json.loads(path.read_text(encoding="utf-8"))["halo"]["header"] + except (OSError, json.JSONDecodeError, KeyError): + continue + recordings.setdefault(header["recording"], []).append((header["part"], path)) + parts = max(recordings.values(), key=len) + assert len(parts) >= 2, "the real C recording is split into parts" + return [path for number, path in sorted(parts)] + + +def test_written_parts_are_traces(written_parts): + for path in written_parts: + text = path.read_text(encoding="utf-8") + trace = json.loads(text) + assert list(trace) == ["halo", "cpu_summary", "traceEvents", "displayTimeUnit"] + assert text.startswith('{"halo": {\n"header": {') + assert trace["halo"]["header"]["last_part"] == (path == written_parts[-1]) + header = trace["halo"]["header"] + assert (header["role"], header["own_machine"], header["players"], header["players_most"]) == ( + "host", -1, 2, 5) + assert (header["map_name"], header["gametype"]) == ("b30", "campaign") + lines = text.splitlines() + events = lines.index('"traceEvents": [') + # one event a line, the scopes in order of their start + starts = [json.loads(line.rstrip(","))["ts"] for line in lines[events + 1:-2] if '"ph":"X"' in line] + assert starts == sorted(starts) + assert '"quote\\"back\\\\slash"' in text or path != written_parts[0] + + +def test_written_parts_nest_on_tracks(written_parts): + trace = json.loads(written_parts[0].read_text(encoding="utf-8")) + names = {event["args"]["name"] for event in trace["traceEvents"] if event["ph"] == "M"} + assert {"game", "p2p"} <= names + scopes = [event for event in trace["traceEvents"] if event["ph"] == "X" and event["tid"] == 1] + frame = next(event for event in scopes if event["name"] == "frame") + inside = [event for event in scopes if frame["ts"] <= event["ts"] < frame["ts"] + frame["dur"] and event is not frame] + assert inside and all(event["ts"] + event["dur"] <= frame["ts"] + frame["dur"] + 0.001 for event in inside) + + +def test_written_parts_import_in_perfetto(written_parts): + """trace_processor's import, when the perfetto package is installed (run + by hand on a real recording otherwise)""" + trace_processor = pytest.importorskip("perfetto.trace_processor") + with trace_processor.TraceProcessor(trace=str(written_parts[0])) as processor: + errors = processor.query("select name, value from stats where severity = 'error' and value > 0") + rows = list(errors) + assert not rows, [(row.name, row.value) for row in rows] + + +def test_written_parts_read_by_the_report(written_parts): + recording = net_report.Recording(written_parts[0]) + assert recording.complete and not recording.warnings + cpu_rows = [dict(zip(summary["columns"], row)) for summary in + [json.loads(path.read_text(encoding="utf-8"))["cpu_summary"] for path in written_parts] + for row in summary["rows"]] + totals = {} + for row in cpu_rows: + totals[row["name"]] = totals.get(row["name"], 0) + row["count"] + assert totals["texture"] == 2400 + assert totals["render_model"] == 2400 + assert totals["aggregate_child"] == 2400 + frames = sum(1 for path in written_parts for event in json.loads(path.read_text(encoding="utf-8"))["traceEvents"] + if event["ph"] == "X" and event["name"] == "frame") + assert recording.cpu["frames"] == frames + + +# ---------- tools/net_report.py + +EXPECTED_SUMMARY = ROOT / "tools" / "profile_fixture.summary.txt" + + +def cpu_recording(tmp_path, role="host"): + base = tmp_path / f"compat_{role}" + for number, start, duration in ((1, 0.0, 1.25), (2, 1.25, 2.75)): + halo, cpu = profile_fixture.part(number, [number - 1], number == 2, 0, 0) + halo["header"].update(role=role, start_s=start, duration_s=duration) + profile_fixture.write_part(Path(f"{base}.part{number}.json"), {"header": halo["header"]}, cpu) + return net_report.Recording(base) + + +def test_cpu_only_duration_comes_from_each_part_header(tmp_path): + recording = cpu_recording(tmp_path) + assert recording.select() == 4.0 + assert recording.select(0.5, 3.0) == 2.5 + assert recording.select(5.0, 6.0) == 0.0 + + + +def test_cpu_report_prints_and_exports_only_cpu_measurements(tmp_path, capsys): + base = profile_fixture.write_recording(tmp_path) + directory = tmp_path / "csv" + assert net_report.main([str(base), "--no-summary", "--csv", str(directory)]) == 0 + output = capsys.readouterr().out + assert "## cpu scopes" in output and "## worst frames" in output + assert "## traffic" not in output and "## message types" not in output + assert "wire_rtt_ms" not in output and "msgs/s" not in output + assert {path.name.rsplit(".", 2)[1] for path in directory.glob("*.csv")} == {"cpu", "worst_frames"} + + +@pytest.fixture +def host(tmp_path): + return profile_fixture.write_recording(tmp_path) + + +def test_report_summary_is_the_expected_one(host): + assert net_report.main([str(host) + ".part2.json"]) == 0 + written = Path(str(host) + ".summary.txt").read_text(encoding="ascii") + assert written == EXPECTED_SUMMARY.read_text(encoding="ascii") + assert len(written.splitlines()) <= net_report.SUMMARY_LINE_LIMIT + + +def test_report_uses_players_most_and_accepts_old_headers(tmp_path): + base = profile_fixture.write_recording(tmp_path / "legacy") + paths = [Path(f"{base}.part{number}.json") for number in (1, 2, 3)] + for index, path in enumerate(paths): + data = json.loads(path.read_text(encoding="utf-8")) + header = data["halo"]["header"] + for key in ("map_name", "gametype", "players_most"): + header.pop(key, None) + header["players"] = 1 + index + profile_fixture.write_part(path, data["halo"], data["cpu_summary"]) + old_parts_report = net_report.Recording(paths[0]) + old_summary = net_report.summary_lines(old_parts_report, None, None, 40) + assert any("players: 1 at start, 3 most" in line for line in old_summary) + assert net_report.main([str(paths[0]), "--no-summary"]) == 0 + + +def test_report_summary_uses_current_players_most(tmp_path): + base = profile_fixture.write_recording(tmp_path / "current") + last = Path(f"{base}.part3.json") + data = json.loads(last.read_text(encoding="utf-8")) + data["halo"]["header"]["players_most"] = 9 + profile_fixture.write_part(last, data["halo"], data["cpu_summary"]) + recording = net_report.Recording(Path(f"{base}.part1.json")) + summary = net_report.summary_lines(recording, None, None, 40) + assert any("players: 3 at start, 9 most" in line for line in summary) + + +def test_report_merges_parts_as_one(tmp_path): + split = profile_fixture.write_recording(tmp_path / "split") + whole = profile_fixture.write_recording(tmp_path / "whole", split=False) + assert net_report.main([str(split), "--no-summary", "--csv", str(tmp_path / "split_csv")]) == 0 + assert net_report.main([str(whole), "--no-summary", "--csv", str(tmp_path / "whole_csv")]) == 0 + for table in sorted((tmp_path / "split_csv").glob("*.csv")): + if table.name.endswith(".worst_frames.csv"): + continue + assert table.read_text() == (tmp_path / "whole_csv" / table.name).read_text(), table.name + + +def test_report_warns_about_a_missing_part(host, capsys): + Path(str(host) + ".part2.json").unlink() + net_report.main([str(host), "--no-summary"]) + captured = capsys.readouterr() + assert "part 2 of profile_20261005-142233_host is missing" in captured.err + assert "(incomplete)" in captured.out + + +def test_report_warns_about_no_last_part(host, capsys): + Path(str(host) + ".part3.json").unlink() + net_report.main([str(host), "--no-summary"]) + assert "has no last part: the recording is incomplete" in capsys.readouterr().err + + +def test_summary_glossary_has_one_line_per_printed_column(host): + net_report.main([str(host)]) + lines = Path(str(host) + ".summary.txt").read_text(encoding="ascii").splitlines() + start = lines.index("## columns") + 1 + end = lines.index("", start) + glossary = [re.split(r"\s{2,}", line, maxsplit=1)[0] for line in lines[start:end]] + printed = set() + for index, line in enumerate(lines[end:], end): + if line.startswith("## ") and index + 1 < len(lines) and lines[index + 1] not in ("", "(none)"): + printed.update(re.split(r"\s{2,}", lines[index + 1].strip())) + assert printed and len(glossary) == len(set(glossary)) + assert set(glossary) == printed + + +def test_summary_file_ignores_top(host): + assert net_report.main([str(host), "--top", "1"]) == 0 + written = Path(str(host) + ".summary.txt").read_text(encoding="ascii") + assert written == EXPECTED_SUMMARY.read_text(encoding="ascii") + assert len(written.splitlines()) <= net_report.SUMMARY_LINE_LIMIT + + +def test_report_top_limits_the_printed_tables(host, capsys): + net_report.main([str(host), "--no-summary", "--top", "1"]) + assert "... 4 more rows (net_report.py --top N)" in capsys.readouterr().out + + +def test_report_says_cpu_tables_cover_the_whole_recording(host, capsys): + net_report.main([str(host), "--no-summary", "--from", "2", "--to", "4"]) + out = capsys.readouterr().out + assert "# window: 2.0 s selected (seconds 2 to 4)" in out + assert "cpu tables, ticks and frames cover the whole recording" in out + assert "## cpu scopes (top 40 by total_ms, whole recording)" in out + assert "## worst frames (10, whole recording)" in out + + +def test_report_names_the_broken_part(host, capsys): + path = Path(str(host) + ".part2.json") + path.write_text(path.read_text().replace('"cpu_summary"', '"cpu_summery"'), encoding="utf-8") + assert net_report.main([str(host), "--no-summary"]) == 1 + err = capsys.readouterr().err + assert err.startswith("net_report: ") and "part2" in err and "cpu_summary" in err + path.write_text(Path(str(host) + ".part1.json").read_text().replace('"last_part"', '"last_prt"'), encoding="utf-8") + assert net_report.main([str(host), "--no-summary"]) == 1 + assert "last_part" in capsys.readouterr().err + + +def test_windows_compile_command_parser_preserves_quoted_define_and_paths(): + command = ('clang -I"source/saved films" -include port\\windows\\include\\prefix.h ' + '-I"C:\\Program Files\\LLVM\\include" ' + '-DHALO_BUILD_FLAVOR=\\"release\\" source\\game.c -o build\\game.o') + assert parse_compile_command(command, windows=True) == [ + "clang", "-Isource/saved films", "-include", "port/windows/include/prefix.h", + "-IC:/Program Files/LLVM/include", '-DHALO_BUILD_FLAVOR="release"', + "source/game.c", "-o", "build/game.o"] + + +def test_console_words_and_record_seconds_use_the_real_parser(tmp_path): + compiler = check_compiler() + if not compiler: + pytest.skip("needs a C compiler (Linux, WSL)") + text = (ROOT / "port/linux/game/profile_console.c").read_text() + word = text[text.index("static char const *profile_console_word("):text.index("/* ---------- public code */")] + command = text[text.index("boolean profile_console_command("):text.rindex("\n#endif")] + program = tmp_path / "console_check" + code = tmp_path / "console_check.c" + # Compile the game's actual parsing functions; only their external game effects are stubbed. + code.write_text(r''' +#include +#include +#include +#include "profile_trace.h" +typedef int boolean; +#define TRUE 1 +#define FALSE 0 +static int requests; +static double accepted_seconds; +static void profile_console_install(void) {} +static long config_integer(const char *name) { (void)name; return 32; } +static void console_printf(boolean flag, const char *format, ...) { (void)flag; (void)format; } +int profile_trace_request_start(double seconds, int when, long memory) { + assert(when == _profile_trace_when_now && memory == 32); + requests++; accepted_seconds = seconds; return _profile_trace_answer_armed; +} +int profile_trace_request_stop(void) { return _profile_trace_answer_not_recording; } +void profile_trace_status(struct profile_trace_status *status) { memset(status, 0, sizeof(*status)); } +''' + word + command + r''' +int main(void) { + assert(strcmp(profile_console_word(" (PROFILE_RECORD 1.5)", "profile_record"), "1.5)") == 0); + assert(profile_console_word("profile_record_extra", "profile_record") == NULL); + assert(profile_console_command("PROFILE_RECORD 1.5")); + assert(requests == 1 && accepted_seconds == 1.5); + assert(profile_console_command("profile_record x")); + assert(requests == 1); + assert(profile_console_command("profile_record -1")); + assert(requests == 1); + assert(profile_console_command("profile_record")); + assert(requests == 2 && accepted_seconds == 0.0); + assert(!profile_console_command("profile_record_extra 1")); + return 0; +} +''') + subprocess.run([compiler, "-std=gnu11", "-Wall", "-Werror", "-DHALO_PROFILE", + "-I", str(ROOT / "port/linux/include"), str(code), "-o", str(program)], check=True) + subprocess.run([str(program)], check=True) + + +# ---------- a normal build is unchanged + +PROFILE_PREFIXES = ("profile_trace_", "profile_net_", "profile_json_", "profile_overlay_") + + +def parse_compile_command(command, windows=False): + """Split a Ninja C command, normalizing Windows path separators without changing escaped quotes.""" + if windows: + command = re.sub(r'\\(?!")', "/", command) + return shlex.split(command, posix=True) + + +def unavailable(reason): + """a run that asks for the check (HALO_PROFILE_BASE or HALO_PROFILE_OBJECTS) must not skip it silently""" + if os.environ.get("HALO_PROFILE_BASE") or os.environ.get("HALO_PROFILE_OBJECTS"): + pytest.fail(reason) + pytest.skip(reason) + + +def preprocess_flags(): + """the game's or the platform layer's compile flags for this computer's + build, as ninja has them, without HALO_PROFILE and without what makes + an object; absolute where they name a generated file""" + missing = [name for name in ("clang", "ninja") if not shutil.which(name)] + if not (ROOT / "build.ninja").is_file(): + missing.append("a configured build (build.ninja)") + if missing: + unavailable("needs " + ", ".join(missing)) + # (HALO_PROFILE_OBJECTS names another configured flavour's object folder, such as build/android/guest/obj, + # where this computer has no build of its own) + objects = os.environ.get("HALO_PROFILE_OBJECTS") or f"build/{'windows' if os.name == 'nt' else 'linux'}/obj" + + def flags(object_path): + listing = subprocess.run(["ninja", "-t", "commands", object_path], cwd=ROOT, capture_output=True, text=True) + if listing.returncode != 0: + unavailable(f"needs a build.ninja that has {object_path}") + command = listing.stdout.strip().splitlines()[-1] + words = parse_compile_command(command, windows=os.name == "nt") + while words and not words[0].startswith("-"): + words = words[1:] + # (the Android guest's command is a pipeline whose first compile makes assembly) + words = words[:words.index("&&")] if "&&" in words else words + kept, skip = [], False + for word in words: + if skip: + skip = False + elif word in ("-MF", "-o", "-c"): + skip = True + elif word in ("-MMD", "-S") or word == "-DHALO_PROFILE" or word.startswith(("-flto", "-fprofile-use")): + continue + elif word.endswith(".c") and not word.startswith("-"): + continue + elif word.startswith(("build/", "build\\")): + kept.append(str(ROOT / word)) + elif word.startswith(("-Ibuild/", "-Ibuild\\")): + kept.append("-I" + str(ROOT / word[2:])) + else: + kept.append(word) + return kept + + flag_sets = { + "game": flags(f"{objects}/source/game/game.o"), + "platform": flags(f"{objects}/port/linux/src/p2p.o"), + "port_game": flags(f"{objects}/port/linux/game/network_coop.o"), + } + # (only a missing 32-bit C library is "no build here"; any other error is a wrong command, which must not hide) + probe = subprocess.run(["clang", *flag_sets["game"], "-E", "-P", "-x", "c", "-"], cwd=ROOT, + input="#include \n#include \n", + capture_output=True, text=True) + if probe.returncode != 0: + reason = "the game's flags do not preprocess: " + next((line for line in probe.stderr.splitlines() if "error" in line), "")[:200] + if "bits/libc-header-start.h" in probe.stderr: + pytest.skip("needs a 32-bit C library (gcc-multilib); " + reason) + pytest.fail(reason) + return flag_sets + + +def flags_for(path, flag_sets): + if path.startswith("port/linux/src/"): + return flag_sets["platform"] + if path.startswith("port/linux/game/"): + return flag_sets["port_game"] + return flag_sets["game"] + + +def preprocessed(tree, path, flags): + # (path None: an empty file, which is all that the forced includes leave of a file that is empty) + arguments = ["-x", "c", "-"] if path is None else [path] + # (__DATE__ and __TIME__ pinned: main.c prints them, and the two trees can be preprocessed a second apart) + pinned = ["-Wno-builtin-macro-redefined", '-D__DATE__="Jan 1 2000"', '-D__TIME__="00:00:00"'] + result = subprocess.run(["clang", *flags, *pinned, "-E", "-P", *arguments], cwd=tree, + input="" if path is None else None, + capture_output=True, text=True) + assert result.returncode == 0, f"{path}: {result.stderr[-2000:]}" + # (only the tokens: blank lines and layout differ where #ifdef blocks were) + return " ".join(result.stdout.split()) + + +def includers_of_changed_headers(base, already): + """the C files that include a header that changed since the base: a header's #ifdef can change them too""" + def names(*arguments): + return subprocess.run([GIT, *arguments], cwd=ROOT, capture_output=True, text=True, check=True).stdout.split() + + added = set(names("diff", "--name-only", "--diff-filter=A", base, "HEAD", "--", "*.c")) + found = set() + for header in names("diff", "--name-only", "--diff-filter=M", base, "HEAD", "--", "*.h"): + pattern = r'#[ ]*include[ ]*[<"]([^">]*/)?' + re.escape(header.rsplit("/", 1)[-1]) + '[">]' + for path in names("grep", "-l", "-E", pattern, "HEAD", "--", "*.c"): + path = path.split(":", 1)[1] + if path.startswith(("source/", "port/linux/src/", "port/linux/game/")) and path not in added: + found.add(path) + return sorted(found - set(already)) + + +@pytest.mark.skipif(not os.environ.get("HALO_PROFILE_BASE"), reason="HALO_PROFILE_BASE names the base commit") +def test_normal_build_preprocesses_as_before(tmp_path): + base = os.environ["HALO_PROFILE_BASE"] + flag_sets = preprocess_flags() + changed = subprocess.run([GIT, "diff", "--name-only", "--diff-filter=M", base, "HEAD", "--", "*.c"], cwd=ROOT, + capture_output=True, text=True, check=True).stdout.split() + assert changed, "no C file changed since the base" + changed += includers_of_changed_headers(base, changed) + tree = tmp_path / "base" + tree.mkdir() + archive = subprocess.run([GIT, "archive", "--format=tar", base], cwd=ROOT, check=True, capture_output=True).stdout + with tarfile.open(fileobj=io.BytesIO(archive)) as reference: + reference.extractall(tree) + for path in changed: + flags = flags_for(path, flag_sets) + assert preprocessed(tree, path, flags) == preprocessed(ROOT, path, flags), path + + +def test_new_files_are_empty_in_a_normal_build(): + flag_sets = preprocess_flags() + # (found, not listed: a new profile_*.c that forgets its #ifdef shows up here) + paths = sorted(str(path.relative_to(ROOT)).replace("\\", "/") + for directory in ("port/linux/src", "port/linux/game") + for path in (ROOT / directory).glob("profile_*.c")) + assert set(paths) == {*PROFILE_SOURCES, "port/linux/game/profile_console.c"}, paths + for path in paths: + flags = flags_for(path, flag_sets) + assert preprocessed(ROOT, path, flags) == preprocessed(ROOT, None, flags), path + + +def test_normal_build_has_no_profiling_symbols(): + ninja_file = ROOT / "build.ninja" + if not ninja_file.is_file(): + pytest.skip("needs a configured build (build.ninja)") + if "-DHALO_PROFILE" in ninja_file.read_text(encoding="utf-8"): + pytest.skip("a profiling build is configured") + nm = shutil.which("llvm-nm") or shutil.which("nm") + if os.name == "nt" or os.environ.get("HALO_PROFILE_OBJECTS"): + folder = ROOT / (os.environ.get("HALO_PROFILE_OBJECTS") or "build/windows/obj") + objects = sorted(str(path) for path in folder.rglob("*.o")) + else: + objects = [str(ROOT / "build/linux/halo")] if (ROOT / "build/linux/halo").is_file() else [] + if not nm: + unavailable("needs nm") + if not objects: + unavailable("needs a built normal build (build/windows/obj/**/*.o, build/linux/halo, or the objects in HALO_PROFILE_OBJECTS)") + found, words, defined = set(), set(), 0 + for start in range(0, len(objects), 200): + listing = subprocess.run([nm, "--defined-only", *objects[start:start + 200]], capture_output=True, text=True) + # (an nm that cannot read the objects, such as bitcode from --lto, lists nothing and would pass) + assert listing.returncode == 0, listing.stderr[-1000:] + words |= {word.lstrip("_") for word in listing.stdout.split()} + defined += sum(len(line.split()) == 3 for line in listing.stdout.splitlines()) + found = {word for word in words if word.startswith(PROFILE_PREFIXES)} + # (a count, not a named symbol: link-time optimization inlines the functions that have one caller. The game's + # objects list thousands; an unreadable or a near-empty set lists none) + assert defined >= 300, f"nm lists {defined} defined symbols: it cannot read these objects" + assert not found, sorted(found)[:20] diff --git a/tools/test_touch_layout.py b/tools/test_touch_layout.py new file mode 100644 index 00000000..7f7ecd44 --- /dev/null +++ b/tools/test_touch_layout.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +"""Compile and run the touch layout regression checks with JDK 17+.""" +import subprocess +from pathlib import Path + +root = Path(__file__).resolve().parent.parent +classes = root / "build/touch-layout-tests" +classes.mkdir(parents=True, exist_ok=True) +subprocess.run([ + "javac", "--release", "17", "-d", str(classes), + str(root / "port/android/app/src/main/java/com/halo/decomp/TouchLayout.java"), + str(root / "port/android/app/src/main/java/com/halo/decomp/GyroscopeAim.java"), + str(root / "tools/tests/GyroscopeAimTest.java"), + str(root / "tools/tests/TouchLayoutTest.java") +], check=True) +subprocess.run(["java", "-cp", str(classes), "com.halo.decomp.TouchLayoutTest"], check=True) +subprocess.run(["java", "-cp", str(classes), "com.halo.decomp.GyroscopeAimTest"], check=True) diff --git a/tools/test_touch_menu.py b/tools/test_touch_menu.py new file mode 100644 index 00000000..5f220ba4 --- /dev/null +++ b/tools/test_touch_menu.py @@ -0,0 +1,40 @@ +"""Runs the C tests of the menus' touch gestures (port/linux/src/touch_menu.c) +with the build machine's C compiler. The unit has no SDL or game +dependency, so it compiles anywhere.""" +import shutil +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parent.parent + + +def compiler(): + """Finds a C compiler on the build machine. + + Returns the path of clang, cc or gcc (the first found), or None. + """ + for name in ("clang", "cc", "gcc"): + path = shutil.which(name) + if path: + return path + return None + + +def test_touch_menu(tmp_path): + """Builds the gesture unit with its tests, warnings as errors, and runs them.""" + cc = compiler() + if not cc: + pytest.skip("no C compiler") + binary = tmp_path / "touch_menu_test" + build = subprocess.run( + [cc, "-std=c11", "-Wall", "-Wextra", "-Werror", "-O1", + str(ROOT / "port" / "linux" / "tests" / "touch_menu_test.c"), + str(ROOT / "port" / "linux" / "src" / "touch_menu.c"), + "-lm", "-o", str(binary)], + capture_output=True, text=True, + ) + assert build.returncode == 0, build.stdout + build.stderr + result = subprocess.run([str(binary)], capture_output=True, text=True) + assert result.returncode == 0, result.stdout + result.stderr diff --git a/tools/test_wsl_sync_tree.py b/tools/test_wsl_sync_tree.py new file mode 100644 index 00000000..84a4ee89 --- /dev/null +++ b/tools/test_wsl_sync_tree.py @@ -0,0 +1,87 @@ +"""Tests of tools/wsl_sync_tree.sh, which copies a Windows working tree into +WSL for the Android build (tools/wsl_build_android.sh). Needs bash and +rsync, so it runs on Linux (WSL) only.""" +import os +import shutil +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parent.parent +SCRIPT = ROOT / "tools" / "wsl_sync_tree.sh" + +pytestmark = pytest.mark.skipif( + os.name != "posix" or not shutil.which("rsync") or not shutil.which("bash"), + reason="needs bash and rsync (Linux)") + + +def sync(source, mirror, work): + return subprocess.run(["bash", str(SCRIPT), str(source), str(mirror), str(work)], + capture_output=True, text=True) + + +@pytest.fixture +def trees(tmp_path): + source, mirror, work = tmp_path / "source", tmp_path / "mirror", tmp_path / "work" + (source / "src").mkdir(parents=True) + (source / "src" / "a.c").write_bytes(b"int a;\r\n") + (source / "src" / "b.c").write_bytes(b"int b;\n") + return source, mirror, work + + +def test_first_sync_copies_with_lf(trees): + source, mirror, work = trees + result = sync(source, mirror, work) + assert result.returncode == 0, result.stderr + assert (work / "src" / "a.c").read_bytes() == b"int a;\n" + assert (work / "src" / "b.c").read_bytes() == b"int b;\n" + + +def test_unchanged_sync_copies_nothing(trees): + source, mirror, work = trees + sync(source, mirror, work) + result = sync(source, mirror, work) + assert result.returncode == 0, result.stderr + assert "copied 0 changed files" in result.stdout + + +def test_deleted_file_goes_away(trees): + source, mirror, work = trees + sync(source, mirror, work) + (source / "src" / "b.c").unlink() + result = sync(source, mirror, work) + assert result.returncode == 0, result.stderr + assert not (work / "src" / "b.c").exists() + + +def test_change_after_failed_sync_still_arrives(trees): + source, mirror, work = trees + sync(source, mirror, work) + (source / "src" / "a.c").write_bytes(b"int a2;\r\n") + # the copy into the build tree fails after rsync has updated the mirror + (work / "src" / "a.c").chmod(0o000) + (work / "src").chmod(0o500) + failed = sync(source, mirror, work) + (work / "src").chmod(0o755) + (work / "src" / "a.c").chmod(0o644) + assert failed.returncode != 0 + result = sync(source, mirror, work) + assert result.returncode == 0, result.stderr + assert (work / "src" / "a.c").read_bytes() == b"int a2;\n" + + +def test_deletion_survives_a_failed_sync(trees): + source, mirror, work = trees + sync(source, mirror, work) + (source / "src" / "b.c").unlink() + # rsync deletes b.c from the mirror, then fails on the unreadable new file + (source / "src" / "c.c").write_bytes(b"int c;\n") + (source / "src" / "c.c").chmod(0o000) + failed = sync(source, mirror, work) + (source / "src" / "c.c").chmod(0o644) + assert failed.returncode != 0 + result = sync(source, mirror, work) + assert result.returncode == 0, result.stderr + assert not (work / "src" / "b.c").exists() + assert (work / "src" / "c.c").read_bytes() == b"int c;\n" diff --git a/tools/tests/GyroscopeAimTest.java b/tools/tests/GyroscopeAimTest.java new file mode 100644 index 00000000..afecc4ae --- /dev/null +++ b/tools/tests/GyroscopeAimTest.java @@ -0,0 +1,34 @@ +package com.halo.decomp; + +public final class GyroscopeAimTest { + private static void check(boolean ok, String message) { + if (!ok) throw new AssertionError(message); + } + private static void near(float value, float expected, String message) { + check(Math.abs(value-expected) < 0.000001f, message); + } + public static void main(String[] args) { + GyroscopeAim aim = new GyroscopeAim(); float[] delta = new float[2]; + check(!aim.sample(1000000000L, 1, 0, 1, delta), "First event must only anchor time"); + check(aim.sample(1020000000L, 1, 0, 1, delta), "Landscape yaw must produce motion"); + near(delta[0], 0.02f, "Yaw integrates angular velocity in radians"); + near(delta[1], 0, "Pure yaw does not pitch"); + check(!aim.sample(1040000000L, 1, 0, 3, delta), "Changing orientation must reset the sample"); + aim.sample(1060000000L, 1, 0, 3, delta); + near(delta[0], -0.02f, "Reverse landscape must reverse device-axis mapping"); + check(!aim.sample(2000000000L, 1, 0, 3, delta), "A resume gap must not jump the camera"); + aim.reset(); + check(!aim.sample(2100000000L, 0, 1, 1, delta), "Reset releases pending gyro motion"); + aim.sample(2120000000L, 0, 1, 1, delta); + near(delta[1], -0.02f, "Landscape pitch must follow phone tilt rather than invert it"); + aim.reset(); aim.sample(2200000000L, 0, 1, 3, delta); + aim.sample(2220000000L, 0, 1, 3, delta); + near(delta[1], 0.02f, "Reverse landscape pitch must follow the reversed display axis"); + aim.reset(); aim.sample(3000000000L, 0.005f, -0.005f, 0, delta); + check(!aim.sample(3020000000L, 0.005f, -0.005f, 0, delta), "Small stationary noise must not drift"); + check(!aim.sample(3040000000L, Float.NaN, 1, 0, delta), "Invalid sensor values are rejected"); + aim.sample(4000000000L, 1, 1, 0, delta); + check(!aim.sample(3990000000L, 1, 1, 0, delta), "Out-of-order timestamps produce no motion"); + System.out.println("Gyroscope integration, orientation and lifecycle checks passed"); + } +} diff --git a/tools/tests/TouchLayoutTest.java b/tools/tests/TouchLayoutTest.java new file mode 100644 index 00000000..e4789d36 --- /dev/null +++ b/tools/tests/TouchLayoutTest.java @@ -0,0 +1,151 @@ +package com.halo.decomp; + +/** Standalone geometry/persistence regression checks; no phone required. */ +public final class TouchLayoutTest { + private static void check(boolean condition, String message) { + if (!condition) throw new AssertionError(message); + } + public static void main(String[] args) { + TouchLayout layout = new TouchLayout(); + check(layout.size() == 18, "All 17 buttons and the movement stick must be editable"); + check(layout.x(TouchLayout.LEFT) == 115 && layout.y(TouchLayout.FIRE_LEFT) == 239, + "Existing layouts must start with the original controls"); + layout.move(TouchLayout.LEFT, -500, -500); + check(layout.x(TouchLayout.LEFT) >= 64 && layout.y(TouchLayout.LEFT) == 64, + "Dragging must keep the full stick inside the screen without a toolbar exclusion"); + layout.move(TouchLayout.FIRE_LEFT, 10000, 10000); + check(layout.x(TouchLayout.FIRE_LEFT)+39 <= 960 && layout.y(TouchLayout.FIRE_LEFT)+39 <= 540, + "Dragging beyond the bottom/right edge must keep the stick reachable"); + layout.move(0, 230, 320); + layout.move(5, 440, 390); + TouchLayout reopened = new TouchLayout(); + for (int i = 0; i < layout.size(); i++) reopened.restore(i, layout.x(i), layout.y(i)); + for (int i = 0; i < layout.size(); i++) { + check(layout.x(i) == reopened.x(i) && layout.y(i) == reopened.y(i), + "Saved coordinates must round-trip for control "+i); + } + float before = reopened.x(0); + reopened.restore(0, Float.NaN, 200); + reopened.restore(0, Float.POSITIVE_INFINITY, 200); + reopened.restore(0, -20, 200); + check(reopened.x(0) == before, "Invalid saved coordinates must not corrupt the layout"); + reopened.move(0, Float.NaN, 200); + check(reopened.x(0) == before, "Invalid drag coordinates must not corrupt the layout"); + // A saved top-row Pause button is valid even before it has been moved. + check(reopened.y(10) == 36, "Reopening must preserve unmoved top-row controls"); + reopened.bounds(1200, 540); + reopened.move(0, 9999, 1); + check(reopened.x(0) == 1164 && reopened.y(0) == 36, + "Controls must reach the full widescreen edge and top edge"); + TouchLayout wide = new TouchLayout(); + wide.restore(0, reopened.savedX(0), reopened.savedY(0)); + wide.bounds(1200, 540); + check(wide.x(0) == reopened.x(0), "Widescreen positions must survive reload"); + // Duplicates retain their action and are independently placed and hidden. + int copy = wide.duplicate(4); + check(copy == 18 && wide.type(copy) == 4, "Fire copies must retain the fire action"); + wide.setShown(4, false); + wide.move(copy, 300, 100); + check(!wide.shown(4) && wide.shown(copy), "Hiding an original must not hide its copy"); + check(wide.duplicate(TouchLayout.LEFT) == -1, "There can only be one movement stick"); + String exported = wide.exportConfiguration(2.25f); + TouchLayout.Configuration imported = TouchLayout.importConfiguration(exported); + imported.layout.bounds(1200, 540); + check(imported.sensitivity == 2.25f && imported.layout.size() == 19, + "Configuration must preserve sensitivity and duplicates"); + for (int i = 0; i < wide.size(); i++) { + check(imported.layout.type(i) == wide.type(i) && imported.layout.shown(i) == wide.shown(i) + && Math.abs(imported.layout.x(i)-wide.x(i)) < 0.001f + && Math.abs(imported.layout.y(i)-wide.y(i)) < 0.001f, + "Export/import must preserve each control on widescreen"); + } + check(wide.add(4) == 4 && wide.shown(4), "Add must restore a hidden button first"); + wide.setShown(TouchLayout.LEFT, false); + check(wide.add(TouchLayout.LEFT) == TouchLayout.LEFT, "Hidden move stick must be restorable"); + reject(exported.replace("version=3", "version=9")); + reject(exported.replace("control.18.type=4", "control.18.type=16")); + reject(exported.replace("control.18.x=240.0", "control.18.x=NaN")); + reject(exported.replace("count=19", "count=10000")); + reject(exported.replace("sensitivity=2.25", "sensitivity=Infinity")); + reject(exported.replace("control.0.visible=false", "control.0.visible=maybe") + .replace("control.0.visible=true", "control.0.visible=maybe")); + reject("not a layout"); + wide.setSize(copy, 1.6f); + wide.rumbleEnabled = false; wide.gyroscopeEnabled = true; + TouchLayout.Configuration sized = TouchLayout.importConfiguration(wide.exportConfiguration(2.25f)); + check(sized.layout.sizeScale(copy) == 1.6f && !sized.layout.rumbleEnabled && sized.layout.gyroscopeEnabled, + "Sizes, rumble and gyro settings must survive export/import"); + int sizedCopy = wide.duplicate(copy); + check(wide.sizeScale(sizedCopy) == 1.6f, "Duplicates inherit their source size"); + wide.move(copy, 99999, 99999); + wide.setSize(copy, 2f); + check(wide.x(copy)+wide.radius(copy) <= 1200 && wide.y(copy)+wide.radius(copy) <= 540, + "Growing a button at the edge must keep it reachable"); + String sizedText = sized.layout.exportConfiguration(2.25f); + reject(sizedText.replace("control.18.size=1.6", "control.18.size=NaN")); + reject(sizedText.replace("control.18.size=1.6", "control.18.size=3.0")); + reject(sizedText.replace("rumble=false", "rumble=invalid")); + reject(sizedText.replace("gyroscope=true", "gyroscope=invalid")); + String legacy = sizedText.replace("version=3", "version=1").replaceAll("(?m)^.*\\.size=.*\\R", "") + .replaceAll("(?m)^(rumble|gyroscope|opacity|floating-stick)=.*\\R", ""); + TouchLayout.Configuration old = TouchLayout.importConfiguration(legacy); + check(old.layout.sizeScale(copy) == 1 && old.layout.rumbleEnabled && !old.layout.gyroscopeEnabled, + "Legacy layouts must load with default sizes and gyro disabled"); + // Opacity (version 3) round-trips; version 2 files load fully opaque. + sized.layout.setOpacity(0.45f); + String translucent = sized.layout.exportConfiguration(2.25f); + check(TouchLayout.importConfiguration(translucent).layout.opacity == 0.45f, "Opacity must survive export/import"); + reject(translucent.replace("opacity=0.45", "opacity=0.05")); + reject(translucent.replace("opacity=0.45", "opacity=NaN")); + sized.layout.floatingStick = true; + check(TouchLayout.importConfiguration(sized.layout.exportConfiguration(2.25f)).layout.floatingStick, + "The floating stick must survive export/import"); + sized.layout.floatingStick = false; + reject(translucent.replace("floating-stick=false", "floating-stick=maybe")); + String version2 = translucent.replace("version=3", "version=2").replaceAll("(?m)^(opacity|floating-stick)=.*\\R", ""); + check(TouchLayout.importConfiguration(version2).layout.opacity == 1f + && !TouchLayout.importConfiguration(version2).layout.floatingStick, + "Version 2 layouts load fully opaque, with the stick in its place"); + reject(translucent.replaceAll("(?m)^opacity=.*\\R", "")); + try { + sized.layout.setOpacity(1.5f); + throw new AssertionError("Out-of-range opacity was accepted"); + } catch (IllegalArgumentException expected) { + // refused + } + // A finger-sized minimum radius enlarges small buttons and keeps them on the display. + TouchLayout fingers = new TouchLayout(); + fingers.setMinimumRadius(30); + check(fingers.radius(12) == 30 && fingers.radius(TouchLayout.LEFT) == 64, + "The minimum radius enlarges small controls only"); + fingers.move(12, 0, 0); + check(fingers.x(12) == 30 && fingers.y(12) == 30, "Enlarged controls stay inside the display"); + check(fingers.savedX(12) == 30, "The minimum radius does not change saved places"); + fingers.setMinimumRadius(80); + check(fingers.radius(12) == TouchLayout.MAX_MINIMUM_RADIUS, "The minimum radius is capped"); + TouchLayout dense = new TouchLayout(); + dense.setMinimumRadius(80); + for (int a = 12; a <= 15; a++) { + for (int b = a + 1; b <= 15; b++) { + check(Math.hypot(dense.x(a) - dense.x(b), dense.y(a) - dense.y(b)) >= dense.radius(a) + dense.radius(b), + "At the capped minimum radius the D-pad's buttons stay apart"); + } + } + fingers.setMinimumRadius(Float.NaN); + check(fingers.radius(12) == 25, "An invalid minimum radius is none"); + wide.resetDefaults(); + check(wide.size() == 18 && wide.shown(4) && wide.x(4) == 915*1200f/960, + "Reset restores defaults on the current display and removes all copies"); + check(wide.sizeScale(4) == 1f && !wide.rumbleEnabled && wide.gyroscopeEnabled, + "Button reset must restore sizes without changing General settings"); + while (wide.duplicate(4) >= 0) {} + check(wide.size() == TouchLayout.MAX_CONTROLS, "Duplicate count must be bounded"); + System.out.println("Touch layout, visibility, duplication and import/export checks passed"); + } + + private static void reject(String text) { + try { TouchLayout.importConfiguration(text); } + catch (IllegalArgumentException expected) { return; } + throw new AssertionError("Invalid configuration was accepted"); + } +} diff --git a/tools/windows_build.py b/tools/windows_build.py index f410e935..d0818a71 100644 --- a/tools/windows_build.py +++ b/tools/windows_build.py @@ -19,9 +19,9 @@ from pathlib import Path from typing import Any, Dict, List, Optional from .linux_build import (LINUX_PROFILE, MINIUPNPC_DIR, OPTIMISATION, WINDOWS_PROFILE, XDK_INCLUDE, lto_mode, - march_flag, miniupnpc_sources, pgo_mode, compile_launcher, game_defines_and_includes, - game_sources, musl_math_cflags, musl_math_sources, pgo_profile, profile_use_flags, - xdk_headers) + march_flag, miniupnpc_sources, pgo_mode, compile_launcher, configuration_defines, + game_defines_and_includes, game_sources, musl_math_cflags, musl_math_sources, opus_cflags, + opus_sources, pgo_profile, profile_use_flags, xdk_headers) from .embed_assets import hud_assets_build, hud_configure_inputs from .ninja_syntax import Writer @@ -30,6 +30,7 @@ PORT_DIR = Path("port/windows") PORT_CONFIG = PORT_DIR / "port.json" BUILD = Path("build/windows") +# (as tools/android_build.py's SDL_TAG and tools/linux_sysroot.py's SDL_VERSION) SDL_VERSION = "3.4.16" SDL_URL = ( f"https://github.com/libsdl-org/SDL/releases/download/release-{SDL_VERSION}/" @@ -182,6 +183,15 @@ PROFILE_RUNTIME_HEADERS = [ ] +def windows_rc(cc: str) -> str: + """LLVM's resource compiler of the clang named cc: beside it, where cc is + a path, else the one on the PATH""" + path = Path(cc) + if path.parent == Path("."): + return "llvm-rc" + return str(path.with_name("llvm-rc" + path.suffix)) + + def clang_release(cc: str) -> Optional[str]: """the release (22.1.0) of the clang named cc, or None""" try: @@ -292,6 +302,16 @@ def generate_windows_build(n: Writer, sln: Any) -> None: rspfile="$out.rsp", rspfile_content="$in_newline", ) + # the executable's resources (port/windows/halo.rc: its icon), compiled + # by LLVM's resource compiler, beside the clang that builds the game + n.variable("windows_rc", windows_rc(cc)) + n.rule( + name="windows_rc", + command="$windows_rc /no-preprocess /FO $out $in", + description="WINDOWS RC $out", + ) + resources = BUILD / "halo.res" + n.build(outputs=resources, rule="windows_rc", inputs=PORT_DIR / "halo.rc", implicit=[PORT_DIR / "opence-icon.ico"]) n.rule( name="windows_copy", command="$python -c \"import shutil,sys; shutil.copyfile(sys.argv[1], sys.argv[2])\" $in $out", @@ -304,8 +324,8 @@ def generate_windows_build(n: Writer, sln: Any) -> None: # (a debug build checks its stack frames (/GS), and stops at the first # one overrun, as at the first failed assertion; a release build does # not, so that an overrun nobody has met cannot end a game) - abi = " ".join(WINDOWS_ABI_FLAGS + [march_flag(sln)] + (["-DHALO_RELEASE"] if getattr(sln, "port_release", False) - else ["-fstack-protector-strong"])) + abi = " ".join(WINDOWS_ABI_FLAGS + [march_flag(sln)] + configuration_defines(sln) + + ([] if getattr(sln, "port_release", False) else ["-fstack-protector-strong"])) sdl_include = SDL_DIR / "include" libs = " ".join( [_quote(SDL_DIR / "lib" / "x86" / "SDL3.lib")] @@ -435,6 +455,9 @@ def generate_windows_build(n: Writer, sln: Any) -> None: add_object(EXPAT_DIR / name, " ".join([abi, "-std=gnu11", f"-I{EXPAT_DIR}", "-w"])) # internet play's reliable streams (port/third_party/kcp; p2p.c) add_object(KCP_DIR / "ikcp.c", " ".join([abi, "-std=gnu11", "-w"])) + # voice chat's codec (port/third_party/opus) + for source in opus_sources(): + add_object(source, opus_cflags(abi)) # internet play's signatures, for public games' listings # (port/third_party/monocypher; p2p_crypto.c) for name in ("monocypher.c", "monocypher-ed25519.c"): @@ -450,7 +473,7 @@ def generate_windows_build(n: Writer, sln: Any) -> None: n.build( outputs=output, rule="windows_link", - inputs=objects + extra_objects, + inputs=objects + extra_objects + [resources], variables={"ldflags": " ".join(base_ldflags + extra_ldflags), "libs": libs}, ) diff --git a/tools/wsl_build_android.sh b/tools/wsl_build_android.sh new file mode 100644 index 00000000..f8a6372a --- /dev/null +++ b/tools/wsl_build_android.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Builds the Android APK in WSL from a Windows working tree. The Android +# build needs Linux (the NDK paths and the shell commands of its ninja +# rules), and it is faster on the Linux file system, so the sources are +# copied to ~/halo-build/src first (tools/wsl_sync_tree.sh: LF line +# endings, only the changed files). build/ stays there, so builds are +# incremental. The APK is copied back to dist/android/. +# +# wsl -d Ubuntu -- bash /mnt/c/path/to/the/repository/tools/wsl_build_android.sh [--release] +set -euo pipefail + +# (not from ~/.profile: under set -u, Ubuntu's .bashrc stops at its unset PS1) +export ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}" +export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/27.2.12479018}" +SOURCE="$(cd "$(dirname "$0")/.." && pwd)" +WORK="$HOME/halo-build/src" +# (the APK is signed with the debug key in both flavors: --release only +# configures an optimised build, and names the file app-release.apk) +FLAVOR=debug +CONFIGURE=() +if [ "${1:-}" = "--release" ]; then + FLAVOR=release + CONFIGURE+=(--release) +fi + +bash "$SOURCE/tools/wsl_sync_tree.sh" "$SOURCE" "$HOME/halo-build/mirror" "$WORK" +cd "$WORK" +python3 configure.py "${CONFIGURE[@]}" +ninja android_apk +mkdir -p "$SOURCE/dist/android" +cp "port/android/app/build/outputs/apk/debug/app-debug.apk" "$SOURCE/dist/android/app-$FLAVOR.apk" +echo "APK: dist/android/app-$FLAVOR.apk" diff --git a/tools/wsl_desktop_check.sh b/tools/wsl_desktop_check.sh new file mode 100644 index 00000000..73d0ca55 --- /dev/null +++ b/tools/wsl_desktop_check.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Compiles the given platform files (port/linux/src) as the Android guest +# build does, but without HALO_ARM64_GUEST, HALO_GLES and HALO_ANDROID, +# syntax only: the desktop branches of the shared code. Warnings are off, +# but a call of an undeclared function is still an error. Only files that the guest build compiles can be checked +# (not memory_watch.c or posix_*). Run in ~/halo-build/src after a build. +set -uo pipefail +cd "$HOME/halo-build/src" +status=0 +for file in "$@"; do + object="build/android/guest/obj/port/linux/src/${file%.c}.o" + command=$(ninja -t commands "$object" | tail -1) + if [ -z "$command" ]; then echo "no command for $object"; status=1; continue; fi + # (the command chains the compile with the assembly steps: keep the compile) + command=$(echo "$command" | sed -E 's/ && .*//; s/ -DHALO_(ARM64_GUEST|GLES|ANDROID)(=[^ ]*)?//g; s/ -w( |$)/ /g; s/ -o [^ ]+//; s/ -MMD//; s/ -MF [^ ]+//') + # (__i386__: the prefix header accepts only the 32-bit x86 or Android targets) + if eval "$command -D__i386__ -fsyntax-only -w -Werror=implicit-function-declaration"; then echo "ok $file"; else echo "FAILED $file"; status=1; fi +done +exit $status diff --git a/tools/wsl_setup_android.sh b/tools/wsl_setup_android.sh new file mode 100644 index 00000000..daaea364 --- /dev/null +++ b/tools/wsl_setup_android.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Installs what the Android build needs on Ubuntu 24.04 (WSL2 or native), +# as the GitHub runner image has it (.github/workflows/build.yml): the +# compilers and tools from apt, and the Android SDK command-line tools, +# platform 35, build-tools and a pinned NDK in ~/Android/Sdk. Safe to run +# again: it installs only what is missing. +# +# bash tools/wsl_setup_android.sh +set -euo pipefail + +SDK="${ANDROID_HOME:-$HOME/Android/Sdk}" +TOOLS_ZIP=commandlinetools-linux-11076708_latest.zip +NDK_VERSION=27.2.12479018 +PACKAGES=(clang lld ninja-build cmake openjdk-17-jdk-headless curl unzip rsync git python3 python3-pytest) + +missing=() +for package in "${PACKAGES[@]}"; do + dpkg -s "$package" >/dev/null 2>&1 || missing+=("$package") +done +if [ ${#missing[@]} -gt 0 ]; then + echo "installing: ${missing[*]} (sudo asks for your password)" + sudo apt-get update + sudo apt-get install -y "${missing[@]}" +fi + +if [ ! -x "$SDK/cmdline-tools/latest/bin/sdkmanager" ]; then + mkdir -p "$SDK/cmdline-tools" + temporary=$(mktemp -d) + curl -fL -o "$temporary/tools.zip" "https://dl.google.com/android/repository/$TOOLS_ZIP" + unzip -q "$temporary/tools.zip" -d "$temporary" + rm -rf "$SDK/cmdline-tools/latest" + mv "$temporary/cmdline-tools" "$SDK/cmdline-tools/latest" + rm -rf "$temporary" +fi + +sdkmanager="$SDK/cmdline-tools/latest/bin/sdkmanager" +yes | "$sdkmanager" --sdk_root="$SDK" --licenses >/dev/null || true +"$sdkmanager" --sdk_root="$SDK" --install "platforms;android-35" "build-tools;35.0.0" "ndk;$NDK_VERSION" "platform-tools" + +profile="$HOME/.profile" +if ! grep -q 'ANDROID_HOME=' "$profile" 2>/dev/null; then + { + echo "export ANDROID_HOME=\"$SDK\"" + echo "export ANDROID_NDK_HOME=\"$SDK/ndk/$NDK_VERSION\"" + } >> "$profile" +fi +echo "Android build tools are ready (SDK $SDK, NDK $NDK_VERSION)." diff --git a/tools/wsl_sync_tree.sh b/tools/wsl_sync_tree.sh new file mode 100644 index 00000000..ec677ef0 --- /dev/null +++ b/tools/wsl_sync_tree.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Copies a Windows working tree into a Linux build tree for the Android +# build (tools/wsl_build_android.sh): +# +# bash tools/wsl_sync_tree.sh SOURCE MIRROR WORK +# +# A Windows checkout (core.autocrlf) has CRLF line endings, which break the +# build's text tools (musl's mkalltypes.sed, for one). So SOURCE is first +# mirrored as it is (MIRROR), and each file that the mirror gets new is +# copied on to WORK with LF line endings and the mirror's time. Unchanged +# files are not touched, so ninja rebuilds only what changed. --delete +# matters: the build globs *.c, so a file deleted on Windows must also go +# away here. WORK's build/ is not in SOURCE and stays. +# +# The mirror is updated before WORK is. If a run stops between the two +# (rsync fails because a file changed under it, a copy fails, Ctrl-C), the +# mirror already has files that WORK lacks, and rsync would not report +# them again. So a marker stays while a sync is under way; when the next +# run finds it, it rebuilds the mirror, and every file is compared again. +# The mirror's times are the source's, so the files that did not change +# get the same time in WORK again, and ninja does not rebuild them. +set -euo pipefail + +SOURCE="$1" +MIRROR="$2" +WORK="$3" +PENDING="$MIRROR.pending" + +# what the sync leaves alone (not source, or local data that may be large) +EXCLUDES=( + --exclude=/build/ --exclude=/dist/ --exclude=/assets/ --exclude=/original/ --exclude=/xbox/ --exclude=/.git/ + --exclude=/port/android/app/build/ --exclude=/port/android/build/ --exclude=/port/android/.gradle/ + --exclude=/port/android/local.properties + --exclude=/build.ninja --exclude='/.ninja_*' +) + +RECOVER=0 +if [ -e "$PENDING" ]; then + echo "the last sync did not finish: copying the whole tree again" + rm -rf "$MIRROR" + RECOVER=1 +fi +mkdir -p "$MIRROR" "$WORK" +touch "$PENDING" +changes=$(mktemp) +trap 'rm -f "$changes"' EXIT +rsync -a --delete --out-format='%i %n' "${EXCLUDES[@]}" "$SOURCE/" "$MIRROR/" > "$changes" +if [ "$RECOVER" = 1 ]; then + # the deletion that the failed run made in the mirror is not reported + # again, so remove from WORK what the mirror no longer has + rsync -r --delete --existing --ignore-existing "${EXCLUDES[@]}" "$MIRROR/" "$WORK/" +fi + +# %i is 11 characters, then a space, then the path +copied=0 +while IFS= read -r line; do + item="${line:0:11}" + path="${line:12}" + case "$item" in + '*deleting'*) + rm -rf "${WORK:?}/$path" + ;; + cd*) + mkdir -p "$WORK/$path" + ;; + '>f'*) + mkdir -p "$(dirname "$WORK/$path")" + if grep -Iq . "$MIRROR/$path" && grep -q $'\r' "$MIRROR/$path"; then + sed 's/\r$//' "$MIRROR/$path" > "$WORK/$path" + else + cp "$MIRROR/$path" "$WORK/$path" + fi + chmod --reference="$MIRROR/$path" "$WORK/$path" + touch -r "$MIRROR/$path" "$WORK/$path" + copied=$((copied + 1)) + ;; + esac +done < "$changes" +rm -f "$PENDING" +echo "copied $copied changed files to $WORK"