Compare commits

..

No commits in common. "master" and "3.7.1" have entirely different histories.

1576 changed files with 28313 additions and 334638 deletions

8
.gitattributes vendored
View file

@ -1,10 +1,4 @@
# -*- conf -*-
*.gz binary
*.so binary
*.dll binary
*.dylib binary
/build/bootstrap/* binary
/usr/share/terminfo/* binary
/usr/share/terminfo/*/* binary
/build/bootstrap/*.com binary
/usr/share/zoneinfo/* binary
/usr/share/zoneinfo/*/* binary

View file

@ -1,8 +1,5 @@
name: build
env:
COSMOCC_VERSION: 3.9.2
on:
push:
branches:
@ -22,48 +19,13 @@ jobs:
matrix:
mode: ["", tiny, rel, tinylinux, optlinux]
steps:
- uses: actions/checkout@v4
with:
# Full checkout needed for git-restore-mtime-bare.
fetch-depth: 0
# TODO(jart): fork this action.
- uses: chetan/git-restore-mtime-action@v2
- uses: actions/cache/restore@v4
id: cache
with:
path: |
.cosmocc
o
key: ${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}-${{ github.sha }}
restore-keys: |
${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}-
${{ env.COSMOCC_VERSION }}-
- name: Restore mtimes
if: steps.cache.outputs.cache-hit == 'true'
run: |
while read mtime file; do
[ -f "$file" ] && touch -d "@$mtime" "$file"
done < o/.mtimes
- uses: actions/checkout@v3
- name: support ape bins 1
run: sudo cp -a build/bootstrap/ape.elf /usr/bin/ape
run: sudo cp build/bootstrap/ape.elf /usr/bin/ape
- name: support ape bins 2
run: sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register"
- name: make matrix
run: V=0 make -j2 MODE=${{ matrix.mode }}
- name: Save mtimes
run: |
find o -type f -exec stat -c "%Y %n" {} \; > o/.mtimes
- uses: actions/cache/save@v4
with:
path: |
.cosmocc
o
key: ${{ env.COSMOCC_VERSION }}-${{ matrix.mode }}-${{ github.sha }}

1
.gitignore vendored
View file

@ -15,4 +15,3 @@ __pycache__
/tool/emacs/*.elc
/perf.data
/perf.data.old
/qemu*core

36
.vscode/settings.json vendored
View file

@ -1,36 +0,0 @@
{
"C_Cpp.default.compilerPath": ".cosmocc/3.9.2/bin/aarch64-linux-cosmo-c++",
"C_Cpp.default.compilerArgs": [
"-nostdinc",
"-nostdlib",
"-iquote.",
"-isystemlibc/isystem",
"-isystemthird_party/libcxx",
"-includelibc/integral/normalize.inc",
"-D_COSMO_SOURCE",
"-D__aarch64__"
],
"[c]": {
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"[cpp]": {
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"[makefile]": {
"editor.tabSize": 8,
"editor.insertSpaces": false
},
"[make]": {
"editor.tabSize": 8,
"editor.insertSpaces": false
},
"[assembly]": {
"editor.tabSize": 8,
"editor.insertSpaces": true
},
"files.associations": {
"log.h": "c"
}
}

128
Makefile
View file

@ -77,8 +77,7 @@ COMMA := ,
PWD := $(shell pwd)
# detect wsl2 running cosmopolitan binaries on the host by checking whether:
# - user ran .cosmocc/current/bin/make, in which case make's working directory
# is in wsl
# - user ran build/bootstrap/make, in which case make's working directory is in wsl
# - user ran make, in which case cocmd's working directory is in wsl
ifneq ($(findstring //wsl.localhost/,$(CURDIR) $(PWD)),)
$(warning wsl2 interop is enabled)
@ -90,7 +89,7 @@ UNAME_S := $(shell uname -s)
# apple still distributes a 17 year old version of gnu make
ifeq ($(MAKE_VERSION), 3.81)
$(error please use https://cosmo.zip/pub/cosmos/bin/make)
$(error please use build/bootstrap/make)
endif
LC_ALL = C
@ -136,7 +135,7 @@ ARCH = aarch64
HOSTS ?= pi pi5 studio freebsdarm
else
ARCH = x86_64
HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10 luna
HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10
endif
ZIPOBJ_FLAGS += -a$(ARCH)
@ -148,10 +147,10 @@ export MODE
export SOURCE_DATE_EPOCH
export TMPDIR
COSMOCC = .cosmocc/3.9.2
COSMOCC = .cosmocc/3.6.2
BOOTSTRAP = $(COSMOCC)/bin
TOOLCHAIN = $(COSMOCC)/bin/$(ARCH)-linux-cosmo-
DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.9.2 f4ff13af65fcd309f3f1cfd04275996fb7f72a4897726628a8c9cf732e850193)
DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.6.2 268aa82d9bfd774f76951b250f87b8edcefd5c754b8b409e1639641e8bd8d5bc)
IGNORE := $(shell $(MKDIR) $(TMPDIR))
@ -275,16 +274,10 @@ include libc/BUILD.mk #─┘
include libc/sock/BUILD.mk #─┐
include net/http/BUILD.mk # ├──ONLINE RUNTIME
include third_party/musl/BUILD.mk # │ You can communicate with the network
include third_party/regex/BUILD.mk # │
include third_party/tr/BUILD.mk # │
include third_party/sed/BUILD.mk # │
include libc/system/BUILD.mk # │
include libc/x/BUILD.mk # │
include dsp/scale/BUILD.mk # │
include dsp/mpeg/BUILD.mk # │
include dsp/tty/BUILD.mk # │
include dsp/audio/BUILD.mk # │
include dsp/prog/BUILD.mk # │
include dsp/BUILD.mk # │
include third_party/stb/BUILD.mk # │
include third_party/mbedtls/BUILD.mk # │
@ -298,7 +291,8 @@ include third_party/libcxx/BUILD.mk # │
include third_party/openmp/BUILD.mk # │
include third_party/pcre/BUILD.mk # │
include third_party/less/BUILD.mk # │
include net/https/BUILD.mk #─┘
include net/https/BUILD.mk # │
include third_party/regex/BUILD.mk #─┘
include third_party/tidy/BUILD.mk
include third_party/BUILD.mk
include third_party/nsync/testing/BUILD.mk
@ -317,6 +311,8 @@ include third_party/double-conversion/test/BUILD.mk
include third_party/lua/BUILD.mk
include third_party/tree/BUILD.mk
include third_party/zstd/BUILD.mk
include third_party/tr/BUILD.mk
include third_party/sed/BUILD.mk
include third_party/awk/BUILD.mk
include third_party/hiredis/BUILD.mk
include third_party/make/BUILD.mk
@ -369,7 +365,6 @@ include test/libc/fmt/BUILD.mk
include test/libc/time/BUILD.mk
include test/libc/proc/BUILD.mk
include test/libc/stdio/BUILD.mk
include test/libc/system/BUILD.mk
include test/libc/BUILD.mk
include test/net/http/BUILD.mk
include test/net/https/BUILD.mk
@ -433,71 +428,68 @@ HTAGS: o/$(MODE)/hdrs-old.txt $(filter-out third_party/libcxx/%,$(HDRS)) #o/$(MO
loc: private .UNSANDBOXED = 1
loc: o/$(MODE)/tool/build/summy
find -name \*.h -or -name \*.hpp -or -name \*.c -or -name \*.cc -or -name \*.cpp -or -name \*.S -or -name \*.mk | \
find -name \*.h -or -name \*.c -or -name \*.S | \
$(XARGS) wc -l | grep total | awk '{print $$1}' | $<
COSMOPOLITAN = \
# PLEASE: MAINTAIN TOPOLOGICAL ORDER
# FROM HIGHEST LEVEL TO LOWEST LEVEL
COSMOPOLITAN_OBJECTS = \
CTL \
DSP_AUDIO \
LIBC_CALLS \
LIBC_DLOPEN \
LIBC_ELF \
LIBC_FMT \
LIBC_INTRIN \
LIBC_IRQ \
LIBC_LOG \
LIBC_MEM \
LIBC_NEXGEN32E \
LIBC_NT_ADVAPI32 \
LIBC_NT_BCRYPTPRIMITIVES \
LIBC_NT_COMDLG32 \
LIBC_NT_GDI32 \
LIBC_NT_IPHLPAPI \
LIBC_NT_KERNEL32 \
LIBC_NT_NTDLL \
LIBC_NT_PDH \
LIBC_NT_POWRPROF \
LIBC_NT_PSAPI \
LIBC_NT_REALTIME \
LIBC_NT_SHELL32 \
LIBC_NT_SYNCHRONIZATION \
LIBC_NT_USER32 \
LIBC_NT_WS2_32 \
LIBC_PROC \
LIBC_RUNTIME \
LIBC_SOCK \
LIBC_STDIO \
LIBC_STR \
LIBC_SYSTEM \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_THREAD \
LIBC_TINYMATH \
LIBC_VGA \
LIBC_X \
NET_HTTP \
THIRD_PARTY_COMPILER_RT \
THIRD_PARTY_DLMALLOC \
THIRD_PARTY_DOUBLECONVERSION \
THIRD_PARTY_GDTOA \
THIRD_PARTY_OPENMP \
TOOL_ARGS \
NET_HTTP \
LIBC_SOCK \
LIBC_NT_WS2_32 \
LIBC_NT_IPHLPAPI \
LIBC_X \
THIRD_PARTY_GETOPT \
LIBC_LOG \
THIRD_PARTY_TZ \
THIRD_PARTY_MUSL \
THIRD_PARTY_ZLIB_GZ \
THIRD_PARTY_LIBCXXABI \
THIRD_PARTY_LIBUNWIND \
THIRD_PARTY_MUSL \
THIRD_PARTY_NSYNC \
THIRD_PARTY_NSYNC_MEM \
THIRD_PARTY_OPENMP \
THIRD_PARTY_PUFF \
LIBC_STDIO \
THIRD_PARTY_GDTOA \
THIRD_PARTY_REGEX \
THIRD_PARTY_TZ \
THIRD_PARTY_XED \
LIBC_THREAD \
LIBC_PROC \
THIRD_PARTY_NSYNC_MEM \
LIBC_MEM \
THIRD_PARTY_DLMALLOC \
LIBC_DLOPEN \
LIBC_RUNTIME \
THIRD_PARTY_NSYNC \
LIBC_ELF \
LIBC_IRQ \
LIBC_CALLS \
LIBC_SYSV_CALLS \
LIBC_VGA \
LIBC_NT_PSAPI \
LIBC_NT_POWRPROF \
LIBC_NT_PDH \
LIBC_NT_GDI32 \
LIBC_NT_COMDLG32 \
LIBC_NT_USER32 \
LIBC_NT_NTDLL \
LIBC_NT_ADVAPI32 \
LIBC_NT_SYNCHRONIZATION \
LIBC_FMT \
THIRD_PARTY_ZLIB \
THIRD_PARTY_ZLIB_GZ \
TOOL_ARGS \
THIRD_PARTY_PUFF \
THIRD_PARTY_COMPILER_RT \
LIBC_TINYMATH \
THIRD_PARTY_XED \
LIBC_STR \
LIBC_SYSV \
LIBC_INTRIN \
LIBC_NT_BCRYPTPRIMITIVES \
LIBC_NT_KERNEL32 \
LIBC_NEXGEN32E
COSMOPOLITAN_H_PKGS = \
APE \
DSP_AUDIO \
LIBC \
LIBC_CALLS \
LIBC_ELF \
@ -541,7 +533,7 @@ COSMOCC_PKGS = \
THIRD_PARTY_INTEL
o/$(MODE)/cosmopolitan.a: \
$(call reverse,$(call uniq,$(foreach x,$(COSMOPOLITAN),$($(x)))))
$(foreach x,$(COSMOPOLITAN_OBJECTS),$($(x)_A_OBJS))
COSMOCC_HDRS = \
$(wildcard libc/integral/*) \

View file

@ -3,12 +3,12 @@
[![build](https://github.com/jart/cosmopolitan/actions/workflows/build.yml/badge.svg)](https://github.com/jart/cosmopolitan/actions/workflows/build.yml)
# Cosmopolitan
[Cosmopolitan Libc](https://justine.lol/cosmopolitan/index.html) makes C/C++
[Cosmopolitan Libc](https://justine.lol/cosmopolitan/index.html) makes C
a build-once run-anywhere language, like Java, except it doesn't need an
interpreter or virtual machine. Instead, it reconfigures stock GCC and
Clang to output a POSIX-approved polyglot format that runs natively on
Linux + Mac + Windows + FreeBSD + OpenBSD 7.3 + NetBSD + BIOS with the
best possible performance and the tiniest footprint imaginable.
Linux + Mac + Windows + FreeBSD + OpenBSD + NetBSD + BIOS with the best
possible performance and the tiniest footprint imaginable.
## Background
@ -87,22 +87,15 @@ ape/apeinstall.sh
```
You can now build the mono repo with any modern version of GNU Make. To
bootstrap your build, you can install Cosmopolitan Make from this site:
https://cosmo.zip/pub/cosmos/bin/make
E.g.:
make life easier, we've included one in the cosmocc toolchain, which is
guaranteed to be compatible and furthermore includes our extensions for
doing build system sandboxing.
```sh
curl -LO https://cosmo.zip/pub/cosmos/bin/make
./make -j8
build/bootstrap/make -j8
o//examples/hello
```
After you've built the repo once, you can also use the make from your
cosmocc at `.cosmocc/current/bin/make`. You might even prefer to alias
make to `$COSMO/.cosmocc/current/bin/make`.
Since the Cosmopolitan repository is very large, you might only want to
build one particular thing. Here's an example of a target that can be
compiled relatively quickly, which is a simple POSIX test that only
@ -110,7 +103,7 @@ depends on core LIBC packages.
```sh
rm -rf o//libc o//test
.cosmocc/current/bin/make o//test/posix/signal_test
build/bootstrap/make o//test/posix/signal_test
o//test/posix/signal_test
```
@ -119,21 +112,21 @@ list out each individual one. For example if you wanted to build and run
all the unit tests in the `TEST_POSIX` package, you could say:
```sh
.cosmocc/current/bin/make o//test/posix
build/bootstrap/make o//test/posix
```
Cosmopolitan provides a variety of build modes. For example, if you want
really tiny binaries (as small as 12kb in size) then you'd say:
```sh
.cosmocc/current/bin/make m=tiny
build/bootstrap/make m=tiny
```
You can furthermore cut out the bloat of other operating systems, and
have Cosmopolitan become much more similar to Musl Libc.
```sh
.cosmocc/current/bin/make m=tinylinux
build/bootstrap/make m=tinylinux
```
For further details, see [//build/config.mk](build/config.mk).
@ -249,16 +242,16 @@ server. You're welcome to join us! <https://discord.gg/FwAVVu7eJ4>
## Support Vector
| Platform | Min Version | Circa |
| :--- | ---: | ---: |
| AMD | K8 | 2003 |
| Intel | Core | 2006 |
| Linux | 2.6.18 | 2007 |
| Windows | 8 [1] | 2012 |
| Darwin (macOS) | 23.1.0+ | 2023 |
| OpenBSD | 7.3 or earlier | 2023 |
| FreeBSD | 13 | 2020 |
| NetBSD | 9.2 | 2021 |
| Platform | Min Version | Circa |
| :--- | ---: | ---: |
| AMD | K8 | 2003 |
| Intel | Core | 2006 |
| Linux | 2.6.18 | 2007 |
| Windows | 8 [1] | 2012 |
| Darwin (macOS) | 23.1.0+ | 2023 |
| OpenBSD | 7 | 2021 |
| FreeBSD | 13 | 2020 |
| NetBSD | 9.2 | 2021 |
[1] See our [vista branch](https://github.com/jart/cosmopolitan/tree/vista)
for a community supported version of Cosmopolitan that works on Windows

View file

@ -103,8 +103,10 @@ SECTIONS {
*(.eh_frame_entry .eh_frame_entry.*)
}
__eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0;
__eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0;
.eh_frame : ONLY_IF_RO {
KEEP(*(.eh_frame))
*(.eh_frame.*)
}
.gcc_except_table : ONLY_IF_RO {
*(.gcc_except_table .gcc_except_table.*)
@ -125,11 +127,9 @@ SECTIONS {
. += CONSTANT(MAXPAGESIZE);
. = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(COMMONPAGESIZE));
.eh_frame : {
__eh_frame_start = .;
.eh_frame : ONLY_IF_RW {
KEEP(*(.eh_frame))
*(.eh_frame.*)
__eh_frame_end = .;
}
.gnu_extab : ONLY_IF_RW {
@ -259,9 +259,6 @@ SECTIONS {
.debug_ranges 0 : { *(.debug_ranges) }
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.debug_names 0 : { *(.debug_names) }
.debug_loclists 0 : { *(.debug_loclists) }
.debug_str_offsets 0 : { *(.debug_str_offsets) }
.ARM.attributes 0 : { KEEP(*(.ARM.attributes)) KEEP(*(.gnu.attributes)) }
.note.gnu.arm.ident 0 : { KEEP(*(.note.gnu.arm.ident)) }

View file

@ -16,12 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef __APPLE__
#error "ape/ape-m1.c is for apple silicon. chances you want ape/loader.c"
#endif
#ifndef __aarch64__
#error "ape/ape-m1.c is for apple silicon; you want: make o//ape/ape.macho"
#endif
#include <assert.h>
#include <dispatch/dispatch.h>
#include <dlfcn.h>

View file

@ -329,10 +329,6 @@ SECTIONS {
*(.ubsan.types)
*(.ubsan.data)
__eh_frame_hdr_start_actual = .;
*(.eh_frame_hdr)
__eh_frame_hdr_end_actual = .;
/* Legal Notices */
__notices = .;
KEEP(*(.notice))
@ -386,13 +382,6 @@ SECTIONS {
_tbss_end = .;
} :Tls
.eh_frame : {
__eh_frame_start = .;
KEEP(*(.eh_frame))
*(.eh_frame.*)
__eh_frame_end = .;
} :Ram
.data . : {
/*BEGIN: Read/Write Data */
#if SupportsWindows()
@ -441,6 +430,7 @@ SECTIONS {
KEEP(*(.piro.pad.data))
*(.igot.plt)
KEEP(*(.dataepilogue))
. = ALIGN(. != 0 ? CONSTANT(COMMONPAGESIZE) : 0);
/*END: NT FORK COPYING */
_edata = .;
@ -520,9 +510,6 @@ SECTIONS {
.debug_rnglists 0 : { *(.debug_rnglists) }
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.debug_names 0 : { *(.debug_names) }
.debug_loclists 0 : { *(.debug_loclists) }
.debug_str_offsets 0 : { *(.debug_str_offsets) }
.gnu.attributes 0 : { KEEP(*(.gnu.attributes)) }
.GCC.command.line 0 : { *(.GCC.command.line) }
@ -586,11 +573,11 @@ ape_rom_memsz = ape_rom_filesz;
ape_rom_align = CONSTANT(COMMONPAGESIZE);
ape_rom_rva = RVA(ape_rom_vaddr);
ape_ram_vaddr = ADDR(.eh_frame);
ape_ram_vaddr = ADDR(.data);
ape_ram_offset = ape_ram_vaddr - __executable_start;
ape_ram_paddr = LOADADDR(.eh_frame);
ape_ram_filesz = ADDR(.bss) - ADDR(.eh_frame);
ape_ram_memsz = _end - ADDR(.eh_frame);
ape_ram_paddr = LOADADDR(.data);
ape_ram_filesz = ADDR(.bss) - ADDR(.data);
ape_ram_memsz = _end - ADDR(.data);
ape_ram_align = CONSTANT(COMMONPAGESIZE);
ape_ram_rva = RVA(ape_ram_vaddr);
@ -600,7 +587,7 @@ ape_stack_offset = 0;
ape_stack_vaddr = DEFINED(ape_stack_vaddr) ? ape_stack_vaddr : 0x700000000000;
ape_stack_paddr = ape_ram_paddr + ape_ram_filesz;
ape_stack_filesz = 0;
ape_stack_memsz = DEFINED(ape_stack_memsz) ? ape_stack_memsz : 4 * 1024 * 1024;
ape_stack_memsz = DEFINED(ape_stack_memsz) ? ape_stack_memsz : 8 * 1024 * 1024;
ape_note_offset = ape_cod_offset + (ape_note - ape_cod_vaddr);
ape_note_filesz = ape_note_end - ape_note;
@ -614,9 +601,6 @@ ape_text_memsz = ape_text_filesz;
ape_text_align = CONSTANT(COMMONPAGESIZE);
ape_text_rva = RVA(ape_text_vaddr);
__eh_frame_hdr_start = __eh_frame_hdr_end_actual > __eh_frame_hdr_start_actual ? __eh_frame_hdr_start_actual : 0;
__eh_frame_hdr_end = __eh_frame_hdr_end_actual > __eh_frame_hdr_start_actual ? __eh_frame_hdr_end_actual : 0;
/* we roundup here because xnu wants the file load segments page-aligned */
/* but we don't want to add the nop padding to the ape program, so we'll */
/* let ape.S dd read past the end of the file into the wrapping binaries */

View file

@ -10,8 +10,8 @@ if [ ! -f ape/loader.c ]; then
cd "$COSMO" || exit
fi
if [ -x .cosmocc/current/bin/make ]; then
MAKE=.cosmocc/current/bin/make
if [ -x build/bootstrap/make ]; then
MAKE=build/bootstrap/make
else
MAKE=make
fi

Binary file not shown.

Binary file not shown.

BIN
build/bootstrap/make Executable file

Binary file not shown.

View file

@ -92,7 +92,10 @@ DEFAULT_COPTS ?= \
-fno-gnu-unique \
-fstrict-aliasing \
-fstrict-overflow \
-fno-semantic-interposition
-fno-semantic-interposition \
-fno-dwarf2-cfi-asm \
-fno-unwind-tables \
-fno-asynchronous-unwind-tables
ifeq ($(ARCH), x86_64)
# Microsoft says "[a]ny memory below the stack beyond the red zone
@ -136,6 +139,8 @@ DEFAULT_CFLAGS = \
DEFAULT_CXXFLAGS = \
-std=gnu++23 \
-fno-rtti \
-fno-exceptions \
-fuse-cxa-atexit \
-Wno-int-in-bool-context \
-Wno-narrowing \

View file

@ -99,8 +99,3 @@ rm -f cosmocc.zip cosmocc.zip.sha256sum
# commit output directory
cd "${OLDPWD}" || die
mv "${OUTPUT_TMP}" "${OUTPUT_DIR}" || die
# update current symlink
BASE=$(basename "${OUTPUT_DIR}")
DIR=$(dirname "${OUTPUT_DIR}")
ln -sfn "$BASE" "$DIR/current"

View file

@ -6,14 +6,14 @@ if [ -n "$OBJDUMP" ]; then
fi
find_objdump() {
if [ -x .cosmocc/3.9.2/bin/$1-linux-cosmo-objdump ]; then
OBJDUMP=.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump
elif [ -x .cosmocc/3.9.2/bin/$1-linux-musl-objdump ]; then
OBJDUMP=.cosmocc/3.9.2/bin/$1-linux-musl-objdump
elif [ -x "$COSMO/.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump" ]; then
OBJDUMP="$COSMO/.cosmocc/3.9.2/bin/$1-linux-cosmo-objdump"
elif [ -x "$COSMO/.cosmocc/3.9.2/bin/$1-linux-musl-objdump" ]; then
OBJDUMP="$COSMO/.cosmocc/3.9.2/bin/$1-linux-musl-objdump"
if [ -x .cosmocc/3.6.0/bin/$1-linux-cosmo-objdump ]; then
OBJDUMP=.cosmocc/3.6.0/bin/$1-linux-cosmo-objdump
elif [ -x .cosmocc/3.6.0/bin/$1-linux-musl-objdump ]; then
OBJDUMP=.cosmocc/3.6.0/bin/$1-linux-musl-objdump
elif [ -x "$COSMO/.cosmocc/3.6.0/bin/$1-linux-cosmo-objdump" ]; then
OBJDUMP="$COSMO/.cosmocc/3.6.0/bin/$1-linux-cosmo-objdump"
elif [ -x "$COSMO/.cosmocc/3.6.0/bin/$1-linux-musl-objdump" ]; then
OBJDUMP="$COSMO/.cosmocc/3.6.0/bin/$1-linux-musl-objdump"
else
echo "error: toolchain not found (try running 'cosmocc --update' or 'make' in the cosmo monorepo)" >&2
exit 1

View file

@ -4,5 +4,5 @@ UNAMES=$(uname -s)
if [ x"$UNAMES" = x"Darwin" ] && [ x"$UNAMEM" = x"arm64" ]; then
exec ape "$@"
else
exec rusage "$@"
exec "$@"
fi

View file

@ -17,9 +17,6 @@ struct conditional<false, T, F>
typedef F type;
};
template<bool B, typename T, typename F>
using conditional_t = typename conditional<B, T, F>::type;
} // namespace ctl
#endif // CTL_CONDITIONAL_H_

View file

@ -19,9 +19,6 @@ template<typename _Tp>
struct is_void : public is_void_<typename ctl::remove_cv<_Tp>::type>::type
{};
template<typename T>
inline constexpr bool is_void_v = is_void<T>::value;
} // namespace ctl
#endif // CTL_IS_VOID_H_

View file

@ -241,9 +241,8 @@ class set
private:
friend class set;
node_type* node_;
node_type* root_;
explicit reverse_iterator(node_type* node, node_type* root) : node_(node), root_(root)
explicit reverse_iterator(node_type* node) : node_(node)
{
}
};
@ -348,17 +347,17 @@ class set
reverse_iterator rbegin()
{
return reverse_iterator(rightmost(root_), root_);
return reverse_iterator(rightmost(root_));
}
const_reverse_iterator rbegin() const
{
return const_reverse_iterator(rightmost(root_), root_);
return const_reverse_iterator(rightmost(root_));
}
const_reverse_iterator crbegin() const
{
return const_reverse_iterator(rightmost(root_), root_);
return const_reverse_iterator(rightmost(root_));
}
iterator end() noexcept
@ -378,17 +377,17 @@ class set
reverse_iterator rend()
{
return reverse_iterator(nullptr, root_);
return reverse_iterator(nullptr);
}
const_reverse_iterator rend() const
{
return const_reverse_iterator(nullptr, root_);
return const_reverse_iterator(nullptr);
}
const_reverse_iterator crend() const
{
return const_reverse_iterator(nullptr, root_);
return const_reverse_iterator(nullptr);
}
void clear() noexcept

View file

@ -1,618 +0,0 @@
// -*-mode:c++;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8-*-
// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
#ifndef CTL_SHARED_PTR_H_
#define CTL_SHARED_PTR_H_
#include "exception.h"
#include "is_base_of.h"
#include "is_constructible.h"
#include "is_convertible.h"
#include "remove_extent.h"
#include "unique_ptr.h"
// XXX currently needed to use placement-new syntax (move to cxx.inc?)
void*
operator new(size_t, void*) noexcept;
namespace ctl {
class bad_weak_ptr : public exception
{
public:
const char* what() const noexcept override
{
return "ctl::bad_weak_ptr";
}
};
namespace __ {
template<typename T>
struct ptr_ref
{
using type = T&;
};
template<>
struct ptr_ref<void>
{
using type = void;
};
static inline __attribute__((always_inline)) void
incref(size_t* r) noexcept
{
#ifdef NDEBUG
__atomic_fetch_add(r, 1, __ATOMIC_RELAXED);
#else
ssize_t refs = __atomic_fetch_add(r, 1, __ATOMIC_RELAXED);
if (refs < 0)
__builtin_trap();
#endif
}
static inline __attribute__((always_inline)) bool
decref(size_t* r) noexcept
{
if (!__atomic_fetch_sub(r, 1, __ATOMIC_RELEASE)) {
__atomic_thread_fence(__ATOMIC_ACQUIRE);
return true;
}
return false;
}
class shared_ref
{
public:
constexpr shared_ref() noexcept = default;
shared_ref(const shared_ref&) = delete;
shared_ref& operator=(const shared_ref&) = delete;
virtual ~shared_ref() = default;
void keep_shared() noexcept
{
incref(&shared);
}
void drop_shared() noexcept
{
if (decref(&shared)) {
dispose();
drop_weak();
}
}
void keep_weak() noexcept
{
incref(&weak);
}
void drop_weak() noexcept
{
if (decref(&weak)) {
delete this;
}
}
size_t use_count() const noexcept
{
return __atomic_load_n(&shared, __ATOMIC_RELAXED) + 1;
}
size_t weak_count() const noexcept
{
return __atomic_load_n(&weak, __ATOMIC_RELAXED);
}
private:
virtual void dispose() noexcept = 0;
size_t shared = 0;
size_t weak = 0;
};
template<typename T, typename D>
class shared_pointer : public shared_ref
{
public:
static shared_pointer* make(T* const p, D d)
{
return make(unique_ptr<T, D>(p, move(d)));
}
static shared_pointer* make(unique_ptr<T, D> p)
{
return new shared_pointer(p.release(), move(p.get_deleter()));
}
private:
shared_pointer(T* const p, D d) noexcept : p(p), d(move(d))
{
}
void dispose() noexcept override
{
move(d)(p);
}
T* const p;
[[no_unique_address]] D d;
};
template<typename T>
class shared_emplace : public shared_ref
{
public:
union
{
T t;
};
~shared_emplace() override
{
}
template<typename... Args>
void construct(Args&&... args)
{
::new (&t) T(forward<Args>(args)...);
}
static unique_ptr<shared_emplace> make()
{
return unique_ptr(new shared_emplace());
}
private:
explicit constexpr shared_emplace() noexcept
{
}
void dispose() noexcept override
{
t.~T();
}
};
template<typename T, typename U>
concept shared_ptr_compatible = is_convertible_v<U*, T*>;
} // namespace __
template<typename T>
class weak_ptr;
template<typename T>
class shared_ptr
{
public:
using element_type = remove_extent_t<T>;
using weak_type = weak_ptr<T>;
constexpr shared_ptr() noexcept = default;
constexpr shared_ptr(nullptr_t) noexcept
{
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
explicit shared_ptr(U* const p) : shared_ptr(p, default_delete<U>())
{
}
template<typename U, typename D>
requires __::shared_ptr_compatible<T, U>
shared_ptr(U*, D);
template<typename U>
shared_ptr(const shared_ptr<U>& r, element_type* p) noexcept
: p(p), rc(r.rc)
{
if (rc)
rc->keep_shared();
}
template<typename U>
shared_ptr(shared_ptr<U>&& r, element_type* p) noexcept : p(p), rc(r.rc)
{
r.p = nullptr;
r.rc = nullptr;
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
shared_ptr(const shared_ptr<U>& r) noexcept : p(r.p), rc(r.rc)
{
if (rc)
rc->keep_shared();
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
shared_ptr(shared_ptr<U>&& r) noexcept : p(r.p), rc(r.rc)
{
r.p = nullptr;
r.rc = nullptr;
}
shared_ptr(const shared_ptr& r) noexcept : p(r.p), rc(r.rc)
{
if (rc)
rc->keep_shared();
}
shared_ptr(shared_ptr&& r) noexcept : p(r.p), rc(r.rc)
{
r.p = nullptr;
r.rc = nullptr;
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
explicit shared_ptr(const weak_ptr<U>& r) : p(r.p), rc(r.rc)
{
if (r.expired()) {
throw bad_weak_ptr();
}
rc->keep_shared();
}
template<typename U, typename D>
requires __::shared_ptr_compatible<T, U>
shared_ptr(unique_ptr<U, D>&& r)
: p(r.p), rc(__::shared_pointer<U, D>::make(move(r)))
{
}
~shared_ptr()
{
if (rc)
rc->drop_shared();
}
shared_ptr& operator=(shared_ptr r) noexcept
{
swap(r);
return *this;
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
shared_ptr& operator=(shared_ptr<U> r) noexcept
{
shared_ptr<T>(move(r)).swap(*this);
return *this;
}
void reset() noexcept
{
shared_ptr().swap(*this);
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
void reset(U* const p2)
{
shared_ptr<T>(p2).swap(*this);
}
template<typename U, typename D>
requires __::shared_ptr_compatible<T, U>
void reset(U* const p2, D d)
{
shared_ptr<T>(p2, d).swap(*this);
}
void swap(shared_ptr& r) noexcept
{
using ctl::swap;
swap(p, r.p);
swap(rc, r.rc);
}
element_type* get() const noexcept
{
return p;
}
typename __::ptr_ref<T>::type operator*() const noexcept
{
if (!p)
__builtin_trap();
return *p;
}
T* operator->() const noexcept
{
if (!p)
__builtin_trap();
return p;
}
long use_count() const noexcept
{
return rc ? rc->use_count() : 0;
}
explicit operator bool() const noexcept
{
return p;
}
template<typename U>
bool owner_before(const shared_ptr<U>& r) const noexcept
{
return rc < r.rc;
}
template<typename U>
bool owner_before(const weak_ptr<U>& r) const noexcept
{
return rc < r.rc;
}
private:
template<typename U>
friend class weak_ptr;
template<typename U>
friend class shared_ptr;
template<typename U, typename... Args>
friend shared_ptr<U> make_shared(Args&&... args);
element_type* p = nullptr;
__::shared_ref* rc = nullptr;
};
template<typename T>
class weak_ptr
{
public:
using element_type = remove_extent_t<T>;
constexpr weak_ptr() noexcept = default;
template<typename U>
requires __::shared_ptr_compatible<T, U>
weak_ptr(const shared_ptr<U>& r) noexcept : p(r.p), rc(r.rc)
{
if (rc)
rc->keep_weak();
}
weak_ptr(const weak_ptr& r) noexcept : p(r.p), rc(r.rc)
{
if (rc)
rc->keep_weak();
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
weak_ptr(const weak_ptr<U>& r) noexcept : p(r.p), rc(r.rc)
{
if (rc)
rc->keep_weak();
}
weak_ptr(weak_ptr&& r) noexcept : p(r.p), rc(r.rc)
{
r.p = nullptr;
r.rc = nullptr;
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
weak_ptr(weak_ptr<U>&& r) noexcept : p(r.p), rc(r.rc)
{
r.p = nullptr;
r.rc = nullptr;
}
~weak_ptr()
{
if (rc)
rc->drop_weak();
}
long use_count() const noexcept
{
return rc ? rc->use_count() : 0;
}
bool expired() const noexcept
{
return !use_count();
}
void reset() noexcept
{
weak_ptr().swap(*this);
}
void swap(weak_ptr& r) noexcept
{
using ctl::swap;
swap(p, r.p);
swap(rc, r.rc);
}
weak_ptr& operator=(weak_ptr r) noexcept
{
swap(r);
return *this;
}
template<typename U>
requires __::shared_ptr_compatible<T, U>
weak_ptr& operator=(weak_ptr<U> r) noexcept
{
weak_ptr<T>(move(r)).swap(*this);
}
shared_ptr<T> lock() const noexcept
{
if (expired())
return nullptr;
shared_ptr<T> r;
r.p = p;
r.rc = rc;
if (rc)
rc->keep_shared();
return r;
}
template<typename U>
bool owner_before(const weak_ptr<U>& r) const noexcept
{
return rc < r.rc;
}
template<typename U>
bool owner_before(const shared_ptr<U>& r) const noexcept
{
return rc < r.rc;
}
private:
template<typename U>
friend class shared_ptr;
template<typename U, typename... Args>
friend shared_ptr<U> make_shared(Args&&...);
element_type* p = nullptr;
__::shared_ref* rc = nullptr;
};
template<typename T>
class enable_shared_from_this
{
public:
shared_ptr<T> shared_from_this()
{
return shared_ptr<T>(weak_this);
}
shared_ptr<T const> shared_from_this() const
{
return shared_ptr<T>(weak_this);
}
weak_ptr<T> weak_from_this()
{
return weak_this;
}
weak_ptr<T const> weak_from_this() const
{
return weak_this;
}
protected:
constexpr enable_shared_from_this() noexcept = default;
enable_shared_from_this(const enable_shared_from_this& r) noexcept
{
}
~enable_shared_from_this() = default;
enable_shared_from_this& operator=(
const enable_shared_from_this& r) noexcept
{
return *this;
}
private:
template<typename U, typename... Args>
friend shared_ptr<U> make_shared(Args&&...);
template<typename U>
friend class shared_ptr;
weak_ptr<T> weak_this;
};
template<typename T>
template<typename U, typename D>
requires __::shared_ptr_compatible<T, U>
shared_ptr<T>::shared_ptr(U* const p, D d)
: p(p), rc(__::shared_pointer<U, D>::make(p, move(d)))
{
if constexpr (is_base_of_v<enable_shared_from_this<U>, U>) {
p->weak_this = *this;
}
}
// Our make_shared supports passing a weak self reference as the first parameter
// to your constructor, e.g.:
//
// struct Tree : ctl::weak_self_base
// {
// ctl::shared_ptr<Tree> l, r;
// ctl::weak_ptr<Tree> parent;
// Tree(weak_ptr<Tree> const& self, auto&& l2, auto&& r2)
// : l(ctl::forward<decltype(l2)>(l2)),
// r(ctl::forward<decltype(r2)>(r2))
// {
// if (l) l->parent = self;
// if (r) r->parent = self;
// }
// };
//
// int main() {
// auto t = ctl::make_shared<Tree>(
// ctl::make_shared<Tree>(nullptr, nullptr), nullptr);
// return t->l->parent.lock().get() == t.get() ? 0 : 1;
// }
//
// As shown, passing the parameter at object construction time lets you complete
// object construction without needing a separate Init method. But because we go
// off spec as far as the STL is concerned, there is a potential ambiguity where
// you might have a constructor with a weak_ptr first parameter that is intended
// to be something other than a self-reference. So this feature is opt-in by way
// of inheriting from the following struct.
struct weak_self_base
{};
template<typename T, typename... Args>
shared_ptr<T>
make_shared(Args&&... args)
{
unique_ptr rc = __::shared_emplace<T>::make();
if constexpr (is_base_of_v<weak_self_base, T> &&
is_constructible_v<T, const weak_ptr<T>&, Args...>) {
// A __::shared_ref has a virtual weak reference that is owned by all of
// the shared references. We can avoid some unnecessary refcount changes
// by "borrowing" that reference and passing it to the constructor, then
// promoting it to a shared reference by swapping it with the shared_ptr
// that we return.
weak_ptr<T> w;
w.p = &rc->t;
w.rc = rc.get();
try {
rc->construct(const_cast<const weak_ptr<T>&>(w),
forward<Args>(args)...);
} catch (...) {
w.p = nullptr;
w.rc = nullptr;
throw;
}
rc.release();
shared_ptr<T> r;
swap(r.p, w.p);
swap(r.rc, w.rc);
return r;
} else {
rc->construct(forward<Args>(args)...);
shared_ptr<T> r;
r.p = &rc->t;
r.rc = rc.release();
if constexpr (is_base_of_v<enable_shared_from_this<T>, T>) {
r->weak_this = r;
}
return r;
}
}
} // namespace ctl
#endif // CTL_SHARED_PTR_H_

View file

@ -383,72 +383,4 @@ string::erase(const size_t pos, size_t count) noexcept
return *this;
}
void
string::append(const ctl::string_view& s, size_t pos, size_t count) noexcept
{
append(s.substr(pos, count));
}
size_t
string::find_last_of(char c, size_t pos) const noexcept
{
const char* b = data();
size_t n = size();
if (pos > n)
pos = n;
const char* p = (const char*)memrchr(b, c, pos);
return p ? p - b : npos;
}
size_t
string::find_last_of(ctl::string_view set, size_t pos) const noexcept
{
if (empty() || set.empty())
return npos;
bool lut[256] = {};
for (char c : set)
lut[c & 255] = true;
const char* b = data();
size_t last = size() - 1;
if (pos > last)
pos = last;
for (;;) {
if (lut[b[pos] & 255])
return pos;
if (!pos)
return npos;
--pos;
}
}
size_t
string::find_first_of(char c, size_t pos) const noexcept
{
size_t n = size();
if (pos >= n)
return npos;
const char* b = data();
const char* p = (const char*)memchr(b + pos, c, n - pos);
return p ? p - b : npos;
}
size_t
string::find_first_of(ctl::string_view set, size_t pos) const noexcept
{
if (set.empty())
return npos;
bool lut[256] = {};
for (char c : set)
lut[c & 255] = true;
const char* b = data();
size_t n = size();
for (;;) {
if (pos >= n)
return npos;
if (lut[b[pos] & 255])
return pos;
++pos;
}
}
} // namespace ctl

View file

@ -125,7 +125,6 @@ class string
void append(char, size_t) noexcept;
void append(unsigned long) noexcept;
void append(const void*, size_t) noexcept;
void append(const ctl::string_view&, size_t, size_t = npos) noexcept;
string& insert(size_t, ctl::string_view) noexcept;
string& erase(size_t = 0, size_t = npos) noexcept;
string substr(size_t = 0, size_t = npos) const noexcept;
@ -137,10 +136,6 @@ class string
bool starts_with(ctl::string_view) const noexcept;
size_t find(char, size_t = 0) const noexcept;
size_t find(ctl::string_view, size_t = 0) const noexcept;
size_t find_first_of(char, size_t = 0) const noexcept;
size_t find_first_of(ctl::string_view, size_t = 0) const noexcept;
size_t find_last_of(char, size_t = npos) const noexcept;
size_t find_last_of(ctl::string_view, size_t = npos) const noexcept;
void swap(string& s) noexcept
{
@ -307,7 +302,7 @@ class string
append(ch);
}
void append(const ctl::string_view& s) noexcept
void append(const ctl::string_view s) noexcept
{
append(s.p, s.n);
}

View file

@ -108,66 +108,4 @@ string_view::starts_with(const string_view s) const noexcept
return !memcmp(p, s.p, s.n);
}
size_t
string_view::find_last_of(char c, size_t pos) const noexcept
{
const char* b = data();
size_t n = size();
if (pos > n)
pos = n;
const char* p = (const char*)memrchr(b, c, pos);
return p ? p - b : npos;
}
size_t
string_view::find_last_of(ctl::string_view set, size_t pos) const noexcept
{
if (empty() || set.empty())
return npos;
bool lut[256] = {};
for (char c : set)
lut[c & 255] = true;
const char* b = data();
size_t last = size() - 1;
if (pos > last)
pos = last;
for (;;) {
if (lut[b[pos] & 255])
return pos;
if (!pos)
return npos;
--pos;
}
}
size_t
string_view::find_first_of(char c, size_t pos) const noexcept
{
size_t n = size();
if (pos >= n)
return npos;
const char* b = data();
const char* p = (const char*)memchr(b + pos, c, n - pos);
return p ? p - b : npos;
}
size_t
string_view::find_first_of(ctl::string_view set, size_t pos) const noexcept
{
if (set.empty())
return npos;
bool lut[256] = {};
for (char c : set)
lut[c & 255] = true;
const char* b = data();
size_t n = size();
for (;;) {
if (pos >= n)
return npos;
if (lut[b[pos] & 255])
return pos;
++pos;
}
}
} // namespace ctl

View file

@ -45,10 +45,6 @@ struct string_view
string_view substr(size_t = 0, size_t = npos) const noexcept;
size_t find(char, size_t = 0) const noexcept;
size_t find(string_view, size_t = 0) const noexcept;
size_t find_first_of(char, size_t = 0) const noexcept;
size_t find_first_of(ctl::string_view, size_t = 0) const noexcept;
size_t find_last_of(char, size_t = npos) const noexcept;
size_t find_last_of(ctl::string_view, size_t = npos) const noexcept;
constexpr string_view& operator=(const string_view s) noexcept
{
@ -113,12 +109,12 @@ struct string_view
return p[n - 1];
}
constexpr const_iterator begin() const noexcept
constexpr const_iterator begin() noexcept
{
return p;
}
constexpr const_iterator end() const noexcept
constexpr const_iterator end() noexcept
{
return p + n;
}

View file

@ -2,9 +2,7 @@
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
.PHONY: o/$(MODE)/dsp
o/$(MODE)/dsp: o/$(MODE)/dsp/audio \
o/$(MODE)/dsp/core \
o/$(MODE)/dsp: o/$(MODE)/dsp/core \
o/$(MODE)/dsp/mpeg \
o/$(MODE)/dsp/scale \
o/$(MODE)/dsp/prog \
o/$(MODE)/dsp/tty

View file

@ -1,56 +0,0 @@
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
PKGS += DSP_AUDIO
DSP_AUDIO_ARTIFACTS += DSP_AUDIO_A
DSP_AUDIO = $(DSP_AUDIO_A_DEPS) $(DSP_AUDIO_A)
DSP_AUDIO_A = o/$(MODE)/dsp/audio/audio.a
DSP_AUDIO_A_FILES := $(wildcard dsp/audio/*)
DSP_AUDIO_A_HDRS = $(filter %.h,$(DSP_AUDIO_A_FILES)) dsp/audio/cosmoaudio/cosmoaudio.h
DSP_AUDIO_A_SRCS = $(filter %.c,$(DSP_AUDIO_A_FILES))
DSP_AUDIO_A_DATA = \
dsp/audio/cosmoaudio/miniaudio.h \
dsp/audio/cosmoaudio/cosmoaudio.c \
dsp/audio/cosmoaudio/cosmoaudio.h \
dsp/audio/cosmoaudio/cosmoaudio.dll \
DSP_AUDIO_A_OBJS = \
$(DSP_AUDIO_A_SRCS:%.c=o/$(MODE)/%.o) \
$(DSP_AUDIO_A_DATA:%=o/$(MODE)/%.zip.o) \
DSP_AUDIO_A_CHECKS = \
$(DSP_AUDIO_A).pkg \
$(DSP_AUDIO_A_HDRS:%=o/$(MODE)/%.ok)
DSP_AUDIO_A_DIRECTDEPS = \
LIBC_CALLS \
LIBC_DLOPEN \
LIBC_INTRIN \
LIBC_NEXGEN32E \
LIBC_STR \
LIBC_SYSV \
LIBC_PROC \
LIBC_THREAD \
DSP_AUDIO_A_DEPS := \
$(call uniq,$(foreach x,$(DSP_AUDIO_A_DIRECTDEPS),$($(x))))
$(DSP_AUDIO_A): dsp/audio/ \
$(DSP_AUDIO_A).pkg \
$(DSP_AUDIO_A_OBJS)
$(DSP_AUDIO_A).pkg: \
$(DSP_AUDIO_A_OBJS) \
$(foreach x,$(DSP_AUDIO_A_DIRECTDEPS),$($(x)_A).pkg)
DSP_AUDIO_LIBS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)))
DSP_AUDIO_SRCS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_SRCS))
DSP_AUDIO_HDRS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_HDRS))
DSP_AUDIO_CHECKS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_CHECKS))
DSP_AUDIO_OBJS = $(foreach x,$(DSP_AUDIO_ARTIFACTS),$($(x)_OBJS))
$(DSP_AUDIO_OBJS): $(BUILD_FILES) dsp/audio/BUILD.mk
.PHONY: o/$(MODE)/dsp/audio
o/$(MODE)/dsp/audio: $(DSP_AUDIO_CHECKS)

View file

@ -1,358 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2024 Justine Alexandra Roberts Tunney
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.
*/
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
#include "dsp/audio/describe.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/timespec.h"
#include "libc/dce.h"
#include "libc/dlopen/dlfcn.h"
#include "libc/errno.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/strace.h"
#include "libc/limits.h"
#include "libc/macros.h"
#include "libc/proc/posix_spawn.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/temp.h"
#include "libc/thread/thread.h"
#define COSMOAUDIO_MINIMUM_VERISON 1
#define COSMOAUDIO_DSO_NAME "cosmoaudio." STRINGIFY(COSMOAUDIO_MINIMUM_VERISON)
__static_yoink("dsp/audio/cosmoaudio/miniaudio.h");
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.h");
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.c");
__static_yoink("dsp/audio/cosmoaudio/cosmoaudio.dll");
static const struct Source {
const char *zip;
const char *name;
} srcs[] = {
{"/zip/dsp/audio/cosmoaudio/miniaudio.h", "miniaudio.h"},
{"/zip/dsp/audio/cosmoaudio/cosmoaudio.h", "cosmoaudio.h"},
{"/zip/dsp/audio/cosmoaudio/cosmoaudio.c", "cosmoaudio.c"}, // must last
};
static struct {
pthread_once_t once;
typeof(cosmoaudio_open) *open;
typeof(cosmoaudio_close) *close;
typeof(cosmoaudio_write) *write;
typeof(cosmoaudio_flush) *flush;
typeof(cosmoaudio_read) *read;
typeof(cosmoaudio_poll) *poll;
} g_audio;
static const char *cosmoaudio_tmp_dir(void) {
const char *tmpdir;
if (!(tmpdir = getenv("TMPDIR")) || !*tmpdir)
if (!(tmpdir = getenv("HOME")) || !*tmpdir)
tmpdir = ".";
return tmpdir;
}
static bool cosmoaudio_app_dir(char *path, size_t size) {
strlcpy(path, cosmoaudio_tmp_dir(), size);
strlcat(path, "/.cosmo/", size);
if (makedirs(path, 0755))
return false;
return true;
}
static bool cosmoaudio_dso_path(char *path, size_t size) {
if (!cosmoaudio_app_dir(path, size))
return false;
strlcat(path, COSMOAUDIO_DSO_NAME, size);
if (IsWindows()) {
strlcat(path, ".dll", size);
} else if (IsXnu()) {
strlcat(path, ".dylib", size);
} else {
strlcat(path, ".so", size);
}
return true;
}
static bool cosmoaudio_extract(const char *zip, const char *to) {
int fdin, fdout;
char stage[PATH_MAX];
strlcpy(stage, to, sizeof(stage));
if (strlcat(stage, ".XXXXXX", sizeof(stage)) >= sizeof(stage)) {
errno = ENAMETOOLONG;
return false;
}
if ((fdout = mkostemp(stage, O_CLOEXEC)) == -1)
return false;
if ((fdin = open(zip, O_RDONLY | O_CLOEXEC)) == -1) {
close(fdout);
unlink(stage);
return false;
}
if (copyfd(fdin, fdout, -1) == -1) {
close(fdin);
close(fdout);
unlink(stage);
return false;
}
if (close(fdout)) {
close(fdin);
unlink(stage);
return false;
}
if (close(fdin)) {
unlink(stage);
return false;
}
if (rename(stage, to)) {
unlink(stage);
return false;
}
return true;
}
static bool cosmoaudio_build(const char *dso) {
// extract sauce
char src[PATH_MAX];
for (int i = 0; i < sizeof(srcs) / sizeof(*srcs); ++i) {
if (!cosmoaudio_app_dir(src, PATH_MAX))
return false;
strlcat(src, srcs[i].name, sizeof(src));
if (!cosmoaudio_extract(srcs[i].zip, src))
return false;
}
// create temporary name for compiled dso
// it'll ensure build operation is atomic
int fd;
char tmpdso[PATH_MAX];
strlcpy(tmpdso, dso, sizeof(tmpdso));
strlcat(tmpdso, ".XXXXXX", sizeof(tmpdso));
if ((fd = mkostemp(tmpdso, O_CLOEXEC)) != -1) {
close(fd);
} else {
return false;
}
// build cosmoaudio with host c compiler
char *args[] = {
"cc", //
"-w", //
"-I.", //
"-O2", //
"-fPIC", //
"-shared", //
"-pthread", //
"-DNDEBUG", //
IsAarch64() ? "-ffixed-x28" : "-DIGNORE1", //
src, //
"-o", //
tmpdso, //
"-lm", //
IsNetbsd() ? 0 : "-ldl", //
NULL,
};
int pid, ws;
errno_t err = posix_spawnp(&pid, args[0], NULL, NULL, args, environ);
if (err)
return false;
while (waitpid(pid, &ws, 0) == -1)
if (errno != EINTR)
return false;
if (ws)
return false;
// move dso to its final destination
if (rename(tmpdso, dso))
return false;
return true;
}
static void *cosmoaudio_dlopen(const char *name) {
void *handle;
if ((handle = cosmo_dlopen(name, RTLD_NOW))) {
typeof(cosmoaudio_version) *version;
if ((version = cosmo_dlsym(handle, "cosmoaudio_version")))
if (version() >= COSMOAUDIO_MINIMUM_VERISON)
return handle;
cosmo_dlclose(handle);
}
return 0;
}
static void cosmoaudio_setup_impl(void) {
void *handle;
if (IsOpenbsd())
return; // no dlopen support yet
if (IsXnu() && !IsXnuSilicon())
return; // no dlopen support yet
if (!(handle = cosmoaudio_dlopen(COSMOAUDIO_DSO_NAME ".so")) &&
!(handle = cosmoaudio_dlopen("lib" COSMOAUDIO_DSO_NAME ".so")) &&
!(handle = cosmoaudio_dlopen("cosmoaudio.so")) &&
!(handle = cosmoaudio_dlopen("libcosmoaudio.so"))) {
char dso[PATH_MAX];
if (!cosmoaudio_dso_path(dso, sizeof(dso)))
return;
if ((handle = cosmoaudio_dlopen(dso)))
goto WeAreGood;
if (IsWindows()) {
if (cosmoaudio_extract("/zip/dsp/audio/cosmoaudio/cosmoaudio.dll", dso)) {
if ((handle = cosmoaudio_dlopen(dso))) {
goto WeAreGood;
} else {
return;
}
}
}
if (!cosmoaudio_build(dso))
return;
if (!(handle = cosmoaudio_dlopen(dso)))
return;
}
WeAreGood:
g_audio.open = cosmo_dlsym(handle, "cosmoaudio_open");
g_audio.close = cosmo_dlsym(handle, "cosmoaudio_close");
g_audio.write = cosmo_dlsym(handle, "cosmoaudio_write");
g_audio.flush = cosmo_dlsym(handle, "cosmoaudio_flush");
g_audio.read = cosmo_dlsym(handle, "cosmoaudio_read");
g_audio.poll = cosmo_dlsym(handle, "cosmoaudio_poll");
}
static void cosmoaudio_setup(void) {
BLOCK_CANCELATION;
cosmoaudio_setup_impl();
ALLOW_CANCELATION;
}
static void cosmoaudio_init(void) {
pthread_once(&g_audio.once, cosmoaudio_setup);
}
COSMOAUDIO_ABI int cosmoaudio_open(
struct CosmoAudio **out_ca, const struct CosmoAudioOpenOptions *options) {
int status;
char sbuf[32];
char dbuf[256];
cosmoaudio_init();
if (g_audio.open) {
BLOCK_SIGNALS;
status = g_audio.open(out_ca, options);
ALLOW_SIGNALS;
} else {
status = COSMOAUDIO_ELINK;
}
STRACE("cosmoaudio_open([%p], %s) → %s",
out_ca ? *out_ca : (struct CosmoAudio *)-1,
cosmoaudio_describe_open_options(dbuf, sizeof(dbuf), options),
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
return status;
}
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio *ca) {
int status;
char sbuf[32];
if (g_audio.close) {
BLOCK_SIGNALS;
status = g_audio.close(ca);
ALLOW_SIGNALS;
} else {
status = COSMOAUDIO_ELINK;
}
STRACE("cosmoaudio_close(%p) → %s", ca,
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
return status;
}
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio *ca, const float *data,
int frames) {
int status;
char sbuf[32];
if (g_audio.write) {
BLOCK_SIGNALS;
status = g_audio.write(ca, data, frames);
ALLOW_SIGNALS;
} else {
status = COSMOAUDIO_ELINK;
}
if (frames <= 0 || frames >= 160)
DATATRACE("cosmoaudio_write(%p, %p, %d) → %s", ca, data, frames,
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
return status;
}
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio *ca, float *data,
int frames) {
int status;
char sbuf[32];
if (g_audio.read) {
BLOCK_SIGNALS;
status = g_audio.read(ca, data, frames);
ALLOW_SIGNALS;
} else {
status = COSMOAUDIO_ELINK;
}
if (frames <= 0 || frames >= 160)
DATATRACE("cosmoaudio_read(%p, %p, %d) → %s", ca, data, frames,
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
return status;
}
COSMOAUDIO_ABI int cosmoaudio_flush(struct CosmoAudio *ca) {
int status;
char sbuf[32];
if (g_audio.flush) {
BLOCK_SIGNALS;
status = g_audio.flush(ca);
ALLOW_SIGNALS;
} else {
status = COSMOAUDIO_ELINK;
}
DATATRACE("cosmoaudio_flush(%p) → %s", ca,
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
return status;
}
COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio *ca,
int *in_out_readFrames,
int *in_out_writeFrames) {
int status;
char sbuf[32];
char fbuf[2][20];
if (g_audio.poll) {
BLOCK_SIGNALS;
status = g_audio.poll(ca, in_out_readFrames, in_out_writeFrames);
ALLOW_SIGNALS;
} else {
status = COSMOAUDIO_ELINK;
}
DATATRACE("cosmoaudio_poll(%p, %s, %s) → %s", ca,
cosmoaudio_describe_poll_frames(fbuf[0], sizeof(fbuf[0]),
in_out_readFrames),
cosmoaudio_describe_poll_frames(fbuf[1], sizeof(fbuf[1]),
in_out_writeFrames),
cosmoaudio_describe_status(sbuf, sizeof(sbuf), status));
return status;
}

View file

@ -1,3 +0,0 @@
*.o
/Debug
/Release

View file

@ -1,87 +0,0 @@
# Makefile for MSVC x64 Command Line Developer Tools
#
# nmake /f Makefile.msvc check
# nmake /f Makefile.msvc MODE=debug check
#
# Note: MSVC 2019 makes the DLL 64kb smaller than MSVC 2022.
# Compiler and linker
CC=cl
LINK=link
# Build mode (can be overridden from command line)
!IFNDEF MODE
MODE=release
!ENDIF
# Library dependencies.
TEST_LIBS=OneCore.lib
# Compiler flags
CFLAGS_COMMON=/nologo /W4 /Gy /EHsc
CFLAGS_DEBUG=/Od /Zi /MDd /D_DEBUG
CFLAGS_RELEASE=/O2 /MT /DNDEBUG
!IF "$(MODE)"=="debug"
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_DEBUG)
LDFLAGS=/DEBUG
OUT_DIR=Debug
!ELSE
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_RELEASE) /GL
LDFLAGS=/RELEASE /OPT:REF /OPT:ICF /LTCG /INCREMENTAL:NO
OUT_DIR=Release
!ENDIF
# Additional flags for DLL
DLL_CFLAGS=$(CFLAGS) /D_USRDLL /D_WINDLL
# Linker flags
LDFLAGS=/NOLOGO /SUBSYSTEM:CONSOLE $(LDFLAGS)
# Output file names
DLL_TARGET=$(OUT_DIR)\cosmoaudio.dll
TEST_TARGET=$(OUT_DIR)\test.exe
# Source files
DLL_SOURCES=cosmoaudio.c
TEST_SOURCES=test.c
# Object files
DLL_OBJECTS=$(OUT_DIR)\cosmoaudio.obj
TEST_OBJECTS=$(OUT_DIR)\test.obj
# Default target
all: $(OUT_DIR) $(DLL_TARGET) $(TEST_TARGET)
# Create output directory
$(OUT_DIR):
if not exist $(OUT_DIR) mkdir $(OUT_DIR)
# Rule to build the DLL
$(DLL_TARGET): $(OUT_DIR) $(DLL_OBJECTS)
$(LINK) /DLL $(LDFLAGS) /OUT:$(DLL_TARGET) $(DLL_OBJECTS)
# Rule to build the test program
$(TEST_TARGET): $(OUT_DIR) $(TEST_OBJECTS) $(DLL_TARGET)
$(LINK) $(LDFLAGS) /OUT:$(TEST_TARGET) $(TEST_OBJECTS) $(DLL_TARGET:.dll=.lib) $(TEST_LIBS)
# Rules to compile .c files to .obj files with header dependencies
{.}.c{$(OUT_DIR)}.obj:
$(CC) $(DLL_CFLAGS) /c /Fo$(OUT_DIR)\ $<
$(OUT_DIR)\test.obj: $(OUT_DIR) test.c cosmoaudio.h
$(CC) $(CFLAGS) /c /Fo$(OUT_DIR)\ test.c
$(OUT_DIR)\cosmoaudio.obj: $(OUT_DIR) cosmoaudio.c miniaudio.h cosmoaudio.h
$(CC) $(DLL_CFLAGS) /c /Fo$(OUT_DIR)\ cosmoaudio.c
# Clean target
clean:
if exist $(OUT_DIR) rmdir /s /q $(OUT_DIR)
# Run tests (now called 'check')
check: $(TEST_TARGET)
$(TEST_TARGET)
# Phony targets
.PHONY: all clean check

View file

@ -1,519 +0,0 @@
// Copyright 2024 Justine Alexandra Roberts Tunney
//
// 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.
#define COSMOAUDIO_BUILD
#include "cosmoaudio.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MA_DEBUG_OUTPUT
#define MA_DR_MP3_NO_STDIO
#define MA_NO_DECODING
#define MA_NO_ENCODING
#define MA_NO_ENGINE
#define MA_NO_GENERATION
#define MA_NO_NODE_GRAPH
#define MA_NO_RESOURCE_MANAGER
#define MA_STATIC
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
struct CosmoAudio {
enum CosmoAudioDeviceType deviceType;
ma_uint32 outputBufferFrames;
ma_uint32 inputBufferFrames;
int sampleRate;
int channels;
int isLeft;
ma_context context;
ma_device device;
ma_pcm_rb output;
ma_pcm_rb input;
ma_event event;
ma_log log;
};
static int read_ring_buffer(ma_log* log, ma_pcm_rb* rb, float* pOutput,
ma_uint32 frameCount, ma_uint32 channels) {
ma_result result;
ma_uint32 framesRead;
ma_uint32 framesToRead;
for (framesRead = 0; framesRead < frameCount; framesRead += framesToRead) {
framesToRead = frameCount - framesRead;
void* pMappedBuffer;
result = ma_pcm_rb_acquire_read(rb, &framesToRead, &pMappedBuffer);
if (result != MA_SUCCESS) {
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
"ma_pcm_rb_acquire_read failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
if (!framesToRead)
break;
memcpy(pOutput + framesRead * channels, pMappedBuffer,
framesToRead * channels * sizeof(float));
result = ma_pcm_rb_commit_read(rb, framesToRead);
if (result != MA_SUCCESS) {
if (result == MA_AT_END) {
framesRead += framesToRead;
break;
}
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
"ma_pcm_rb_commit_read failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
}
return framesRead;
}
static int write_ring_buffer(ma_log* log, ma_pcm_rb* rb, const float* pInput,
ma_uint32 frameCount, ma_uint32 channels) {
ma_result result;
ma_uint32 framesWritten;
ma_uint32 framesToWrite;
for (framesWritten = 0; framesWritten < frameCount;
framesWritten += framesToWrite) {
framesToWrite = frameCount - framesWritten;
void* pMappedBuffer;
result = ma_pcm_rb_acquire_write(rb, &framesToWrite, &pMappedBuffer);
if (result != MA_SUCCESS) {
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
"ma_pcm_rb_acquire_write failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
if (!framesToWrite)
break;
memcpy(pMappedBuffer, pInput + framesWritten * channels,
framesToWrite * channels * sizeof(float));
result = ma_pcm_rb_commit_write(rb, framesToWrite);
if (result != MA_SUCCESS) {
if (result == MA_AT_END) {
framesWritten += framesToWrite;
break;
}
ma_log_postf(log, MA_LOG_LEVEL_WARNING,
"ma_pcm_rb_commit_write failed: %s\n",
ma_result_description(result));
return COSMOAUDIO_ERROR;
}
}
return framesWritten;
}
static void data_callback_f32(ma_device* pDevice, float* pOutput,
const float* pInput, ma_uint32 frameCount) {
struct CosmoAudio* ca = (struct CosmoAudio*)pDevice->pUserData;
if (ca->deviceType & kCosmoAudioDeviceTypePlayback) {
//
// "By default, miniaudio will pre-silence the data callback's
// output buffer. If you know that you will always write valid data
// to the output buffer you can disable pre-silencing by setting
// the noPreSilence config option in the device config to true."
//
// —Quoth miniaudio documentation § 16.1. Low Level API
//
if (ca->isLeft) {
int framesCopied = read_ring_buffer(&ca->log, &ca->output, pOutput,
frameCount, ca->channels);
if (framesCopied < (int)frameCount)
ca->isLeft = 0;
} else {
// TODO(jart): Maybe we should stretch the audio too short?
int frameOffset;
int availableFrames = ma_pcm_rb_available_read(&ca->output);
if (availableFrames >= (int)frameCount) {
frameOffset = 0;
} else {
frameOffset = frameCount - availableFrames;
frameCount = availableFrames;
}
read_ring_buffer(&ca->log, &ca->output,
pOutput + frameOffset * ca->channels, frameCount,
ca->channels);
ca->isLeft = 1;
}
}
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
write_ring_buffer(&ca->log, &ca->input, pInput, frameCount, ca->channels);
ma_event_signal(&ca->event);
}
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
ma_uint32 frameCount) {
data_callback_f32(pDevice, (float*)pOutput, (const float*)pInput, frameCount);
}
/**
* Returns current version of cosmo audio library.
*/
COSMOAUDIO_ABI int cosmoaudio_version(void) {
return 1;
}
/**
* Opens access to speaker and microphone.
*
* @param out_ca will receive pointer to allocated CosmoAudio object,
* which must be freed by caller with cosmoaudio_close(); if this
* function fails, then this will receive a NULL pointer value so
* that cosmoaudio_close(), cosmoaudio_write() etc. can be called
* without crashing if no error checking is performed
* @return 0 on success, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_open( //
struct CosmoAudio** out_ca, //
const struct CosmoAudioOpenOptions* options) {
// Validate arguments.
if (!out_ca)
return COSMOAUDIO_EINVAL;
*out_ca = NULL;
if (!options)
return COSMOAUDIO_EINVAL;
if (options->sizeofThis < (int)sizeof(struct CosmoAudioOpenOptions))
return COSMOAUDIO_EINVAL;
if (options->bufferFrames < 0)
return COSMOAUDIO_EINVAL;
if (options->sampleRate < 8000)
return COSMOAUDIO_EINVAL;
if (options->channels < 1)
return COSMOAUDIO_EINVAL;
if (!options->deviceType)
return COSMOAUDIO_EINVAL;
if (options->deviceType &
~(kCosmoAudioDeviceTypePlayback | kCosmoAudioDeviceTypeCapture))
return COSMOAUDIO_EINVAL;
// Allocate cosmo audio object.
struct CosmoAudio* ca;
ca = (struct CosmoAudio*)calloc(1, sizeof(struct CosmoAudio));
if (!ca)
return COSMOAUDIO_ERROR;
ca->channels = options->channels;
ca->sampleRate = options->sampleRate;
ca->deviceType = options->deviceType;
// Create win32-style condition variable.
if (ma_event_init(&ca->event) != MA_SUCCESS) {
free(ca);
return COSMOAUDIO_ERROR;
}
// Create audio log.
if (ma_log_init(NULL, &ca->log) != MA_SUCCESS) {
ma_event_uninit(&ca->event);
free(ca);
return COSMOAUDIO_ERROR;
}
if (!options->debugLog)
ca->log.callbackCount = 0;
// Create audio context.
ma_context_config contextConfig = ma_context_config_init();
contextConfig.pLog = &ca->log;
if (ma_context_init(NULL, 0, &contextConfig, &ca->context) != MA_SUCCESS) {
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
// Initialize device.
ma_result result;
ma_device_config deviceConfig;
deviceConfig = ma_device_config_init(ca->deviceType);
deviceConfig.sampleRate = ca->sampleRate;
if (ca->deviceType & kCosmoAudioDeviceTypeCapture) {
deviceConfig.capture.channels = ca->channels;
deviceConfig.capture.format = ma_format_f32;
deviceConfig.capture.shareMode = ma_share_mode_shared;
}
if (ca->deviceType & kCosmoAudioDeviceTypePlayback) {
deviceConfig.playback.channels = ca->channels;
deviceConfig.playback.format = ma_format_f32;
}
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = ca;
result = ma_device_init(&ca->context, &deviceConfig, &ca->device);
if (result != MA_SUCCESS) {
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
// Initialize the speaker ring buffer.
int period = ca->device.playback.internalPeriodSizeInFrames;
if (!options->bufferFrames) {
ca->outputBufferFrames = period * 10;
} else if (options->bufferFrames < period * 2) {
ca->outputBufferFrames = period * 2;
} else {
ca->outputBufferFrames = options->bufferFrames;
}
if (ca->deviceType & kCosmoAudioDeviceTypePlayback) {
result = ma_pcm_rb_init(ma_format_f32, ca->channels, ca->outputBufferFrames,
NULL, NULL, &ca->output);
if (result != MA_SUCCESS) {
ma_device_uninit(&ca->device);
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
ma_pcm_rb_set_sample_rate(&ca->output, ca->sampleRate);
}
// Initialize the microphone ring buffer.
period = ca->device.capture.internalPeriodSizeInFrames;
if (!options->bufferFrames) {
ca->inputBufferFrames = period * 10;
} else if (options->bufferFrames < period * 2) {
ca->inputBufferFrames = period * 2;
} else {
ca->inputBufferFrames = options->bufferFrames;
}
if (ca->deviceType & kCosmoAudioDeviceTypeCapture) {
result = ma_pcm_rb_init(ma_format_f32, ca->channels, ca->inputBufferFrames,
NULL, NULL, &ca->input);
if (result != MA_SUCCESS) {
ma_device_uninit(&ca->device);
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
ma_pcm_rb_uninit(&ca->output);
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
ma_pcm_rb_set_sample_rate(&ca->output, ca->sampleRate);
}
// Start audio playback.
if (ma_device_start(&ca->device) != MA_SUCCESS) {
ma_device_uninit(&ca->device);
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
ma_pcm_rb_uninit(&ca->output);
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
ma_pcm_rb_uninit(&ca->input);
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_ERROR;
}
*out_ca = ca;
return COSMOAUDIO_SUCCESS;
}
/**
* Closes audio device and frees all associated resources.
*
* This function is non-blocking and will drop buffered audio. In
* playback mode, you need to call cosmoaudio_flush() to ensure data
* supplied by cosmoaudio_write() gets played on your speaker.
*
* Calling this function twice on the same object will result in
* undefined behavior. Even if this function fails, the `ca` will be
* freed to the greatest extent possible.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @return 0 on success, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
if (!ca)
return COSMOAUDIO_EINVAL;
ma_device_uninit(&ca->device); // do this first
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
ma_pcm_rb_uninit(&ca->output);
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
ma_pcm_rb_uninit(&ca->input);
ma_context_uninit(&ca->context);
ma_event_uninit(&ca->event);
ma_log_uninit(&ca->log);
free(ca);
return COSMOAUDIO_SUCCESS;
}
/**
* Writes raw audio data to speaker.
*
* The data is written to a ring buffer in real-time, which is then
* played back very soon on the audio device. This has tolerence for
* a certain amount of buffering, but expects that this function is
* repeatedly called at a regular time interval. The caller should
* have its own sleep loop for this purpose.
*
* This function never blocks. Programs that don't have their own timer
* can use cosmoaudio_poll() to wait until audio may be written.
*
* For any given CosmoAudio object, it's assumed that only a single
* thread will call this function.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param data is pointer to raw audio samples, expected to be in the range
* -1.0 to 1.0, where channels are interleaved
* @param frames is the number of frames (i.e. number of samples divided by
* number of channels) from `data` to write to audio device
* @return number of frames written, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data,
int frames) {
if (!ca)
return COSMOAUDIO_EINVAL;
if (frames < 0)
return COSMOAUDIO_EINVAL;
if (!(ca->deviceType & kCosmoAudioDeviceTypePlayback))
return COSMOAUDIO_EINVAL;
if (1u + frames > ca->outputBufferFrames)
return COSMOAUDIO_ENOBUF;
if (!frames)
return 0;
if (!data)
return COSMOAUDIO_EINVAL;
return write_ring_buffer(&ca->log, &ca->output, data, frames, ca->channels);
}
/**
* Reads raw audio data from microphone.
*
* The data is read from a ring buffer in real-time, which is then
* played back on the audio device. This has tolerence for a certain
* amount of buffering (based on the `bufferFrames` parameter passed to
* cosmoaudio_open(), which by default assumes this function will be
* called at at a regular time interval.
*
* This function never blocks. Programs that don't have their own timer
* can use cosmoaudio_poll() to wait until audio may be read.
*
* For any given CosmoAudio object, it's assumed that only a single
* thread will call this function.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param data is pointer to raw audio samples, expected to be in the range
* -1.0 to 1.0, where channels are interleaved
* @param frames is the number of frames (i.e. number of samples divided by
* number of channels) from `data` to read from microphone
* @return number of frames read, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data,
int frames) {
if (!ca)
return COSMOAUDIO_EINVAL;
if (frames < 0)
return COSMOAUDIO_EINVAL;
if (!(ca->deviceType & kCosmoAudioDeviceTypeCapture))
return COSMOAUDIO_EINVAL;
if (!frames)
return 0;
if (!data)
return COSMOAUDIO_EINVAL;
return read_ring_buffer(&ca->log, &ca->input, data, frames, ca->channels);
}
/**
* Waits until it's possible to read/write audio.
*
* This function is uninterruptible. All signals are masked throughout
* the duration of time this function may block, including cancelation
* signals, because this is not a cancelation point. Cosmopolitan Libc
* applies this masking in its dlopen wrapper.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @param in_out_readFrames if non-NULL specifies how many frames of
* capture data be immediately readable by cosmoaudio_read() before
* this can return; it must not exceed the buffer size; on return
* this will be set to the actual number of frames in the buffer;
* if the caller supplies a zero then this call is a non-blocking
* way to query buffer sizes
* @param in_out_writeFrames if non-NULL specifies how many frames of
* capture data be immediately writable by cosmoaudio_write() before
* this can return; it must not exceed the buffer size; on return
* this will be set to the actual number of frames in the buffer;
* if the caller supplies a zero then this call is a non-blocking
* way to query buffer sizes
* @return 0 on success, or negative error code on error
*/
COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio* ca,
int* in_out_readFrames,
int* in_out_writeFrames) {
if (!ca)
return COSMOAUDIO_EINVAL;
if (!in_out_readFrames && !in_out_writeFrames)
return COSMOAUDIO_EINVAL;
if (in_out_readFrames && !(ca->deviceType & kCosmoAudioDeviceTypeCapture))
return COSMOAUDIO_EINVAL;
if (in_out_writeFrames && !(ca->deviceType & kCosmoAudioDeviceTypePlayback))
return COSMOAUDIO_EINVAL;
if (in_out_readFrames && 1u + *in_out_readFrames > ca->inputBufferFrames)
return COSMOAUDIO_ENOBUF;
if (in_out_writeFrames && 1u + *in_out_writeFrames > ca->outputBufferFrames)
return COSMOAUDIO_ENOBUF;
for (;;) {
int done = 1;
ma_uint32 readable = 0;
ma_uint32 writable = 0;
if (in_out_readFrames) {
readable = ma_pcm_rb_available_read(&ca->input);
done &= readable >= (ma_uint32)*in_out_readFrames;
}
if (in_out_writeFrames) {
writable = ma_pcm_rb_available_write(&ca->output);
done &= writable >= (ma_uint32)*in_out_writeFrames;
}
if (done) {
if (in_out_readFrames)
*in_out_readFrames = readable;
if (in_out_writeFrames)
*in_out_writeFrames = writable;
return COSMOAUDIO_SUCCESS;
}
if (ma_event_wait(&ca->event) != MA_SUCCESS)
return COSMOAUDIO_ERROR;
}
}
/**
* Waits for written samples to be sent to device.
*
* This function is only valid to call in playback or duplex mode.
*
* This function is uninterruptible. All signals are masked throughout
* the duration of time this function may block, including cancelation
* signals, because this is not a cancelation point. Cosmopolitan Libc
* applies this masking in its dlopen wrapper.
*
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
* @return 0 on success, or negative error code on failure
*/
COSMOAUDIO_ABI int cosmoaudio_flush(struct CosmoAudio* ca) {
if (!ca)
return COSMOAUDIO_EINVAL;
if (!(ca->deviceType & kCosmoAudioDeviceTypePlayback))
return COSMOAUDIO_EINVAL;
for (;;) {
if (!ma_pcm_rb_available_read(&ca->output))
return COSMOAUDIO_SUCCESS;
if (ma_event_wait(&ca->event) != MA_SUCCESS)
return COSMOAUDIO_ERROR;
}
}

Binary file not shown.

View file

@ -1,104 +0,0 @@
#ifndef COSMOAUDIO_H_
#define COSMOAUDIO_H_
#ifdef _MSC_VER
#define COSMOAUDIO_ABI
#ifdef COSMOAUDIO_BUILD
#define COSMOAUDIO_API __declspec(dllexport)
#else
#define COSMOAUDIO_API __declspec(dllimport)
#endif
#else
#define COSMOAUDIO_API
#ifdef __x86_64__
#define COSMOAUDIO_ABI __attribute__((__ms_abi__, __visibility__("default")))
#else
#define COSMOAUDIO_ABI __attribute__((__visibility__("default")))
#endif
#endif
#define COSMOAUDIO_SUCCESS -0 // no error or nothing written
#define COSMOAUDIO_ERROR -1 // unspecified error
#define COSMOAUDIO_EINVAL -2 // invalid parameters passed to api
#define COSMOAUDIO_ELINK -3 // loading cosmoaudio dso failed
#define COSMOAUDIO_ENOBUF -4 // invalid buffering parameters
#ifdef __cplusplus
extern "C" {
#endif
struct CosmoAudio;
enum CosmoAudioDeviceType {
kCosmoAudioDeviceTypePlayback = 1,
kCosmoAudioDeviceTypeCapture = 2,
kCosmoAudioDeviceTypeDuplex =
kCosmoAudioDeviceTypePlayback | kCosmoAudioDeviceTypeCapture,
};
struct CosmoAudioOpenOptions {
// This field must be set to sizeof(struct CosmoAudioOpenOptions) or
// cosmoaudio_open() will return COSMOAUDIO_EINVAL.
int sizeofThis;
// Whether you want this object to open the speaker or microphone.
// Please note that asking for microphone access may cause some OSes
// like MacOS to show a popup asking the user for permission.
enum CosmoAudioDeviceType deviceType;
// The sample rate can be 44100 for CD quality, 8000 for telephone
// quality, etc. Values below 8000 are currently not supported.
int sampleRate;
// The number of audio channels in each interleaved frame. Should be 1
// for mono or 2 for stereo.
int channels;
// Number of frames in each ring buffer. A frame consists of a PCM
// sample for each channel. Set to 0 for default. If this is less than
// the device period size times two, it'll be increased to that value.
int bufferFrames;
// Enables debug logging if non-zero.
int debugLog;
};
COSMOAUDIO_API int cosmoaudio_version(void) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_open( //
struct CosmoAudio **out_ca, //
const struct CosmoAudioOpenOptions *options //
) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_close( //
struct CosmoAudio *ca //
) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_write( //
struct CosmoAudio *ca, //
const float *samples, //
int frameCount //
) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_flush( //
struct CosmoAudio *ca //
) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_read( //
struct CosmoAudio *ca, //
float *out_samples, //
int frameCount //
) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_poll( //
struct CosmoAudio *ca, //
int *in_out_readFrames, //
int *in_out_writeFrames //
) COSMOAUDIO_ABI;
#ifdef __cplusplus
}
#endif
#endif /* COSMOAUDIO_H_ */

File diff suppressed because it is too large Load diff

View file

@ -1,76 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "cosmoaudio.h"
#define SAMPLING_RATE 44100
#define WAVE_INTERVAL 440
#define CHANNELS 2
#ifndef M_PIf
#define M_PIf 3.14159265358979323846f
#endif
int main() {
struct CosmoAudioOpenOptions cao = {0};
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
cao.deviceType = kCosmoAudioDeviceTypePlayback;
cao.sampleRate = SAMPLING_RATE;
cao.channels = CHANNELS;
int status;
struct CosmoAudio *ca;
status = cosmoaudio_open(&ca, &cao);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to open audio: %d\n", status);
return 1;
}
float buf[256 * CHANNELS];
for (int g = 0; g < SAMPLING_RATE;) {
int frames = 1;
status = cosmoaudio_poll(ca, NULL, &frames);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to poll output: %d\n", status);
return 2;
}
if (frames > 256)
frames = 256;
if (frames > SAMPLING_RATE - g)
frames = SAMPLING_RATE - g;
for (int f = 0; f < frames; ++f) {
float t = (float)g++ / SAMPLING_RATE;
float s = sinf(2 * M_PIf * WAVE_INTERVAL * t);
for (int c = 0; c < CHANNELS; c++)
buf[f * CHANNELS + c] = s * .3f;
}
status = cosmoaudio_write(ca, buf, frames);
if (status != frames) {
fprintf(stderr, "failed to write output: %d\n", status);
return 3;
}
}
status = cosmoaudio_flush(ca);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to flush output: %d\n", status);
return 4;
}
status = cosmoaudio_close(ca);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to close audio: %d\n", status);
return 5;
}
}

View file

@ -1,121 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2024 Justine Alexandra Roberts Tunney
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.
*/
#include "dsp/audio/describe.h"
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.h"
#define append(...) o += ksnprintf(buf + o, n - o, __VA_ARGS__)
const char *cosmoaudio_describe_status(char *buf, int n, int status) {
switch (status) {
case COSMOAUDIO_SUCCESS:
return "COSMOAUDIO_SUCCESS";
case COSMOAUDIO_ERROR:
return "COSMOAUDIO_ERROR";
case COSMOAUDIO_EINVAL:
return "COSMOAUDIO_EINVAL";
case COSMOAUDIO_ELINK:
return "COSMOAUDIO_ELINK";
case COSMOAUDIO_ENOBUF:
return "COSMOAUDIO_ENOBUF";
default:
ksnprintf(buf, n, "%d", status);
return buf;
}
}
const char *cosmoaudio_describe_open_options(
char *buf, int n, const struct CosmoAudioOpenOptions *options) {
int o = 0;
char b128[128];
bool gotsome = false;
if (!options)
return "NULL";
if (kisdangerous(options)) {
ksnprintf(buf, n, "%p", options);
return buf;
}
append("{");
if (options->sampleRate) {
if (gotsome)
append(", ");
append(".sampleRate=%d", options->sampleRate);
gotsome = true;
}
if (options->channels) {
if (gotsome)
append(", ");
append(".channels=%d", options->channels);
gotsome = true;
}
if (options->deviceType) {
if (gotsome)
append(", ");
static struct DescribeFlags kDeviceType[] = {
{kCosmoAudioDeviceTypeDuplex, "Duplex"}, //
{kCosmoAudioDeviceTypeCapture, "Capture"}, //
{kCosmoAudioDeviceTypePlayback, "Playback"}, //
};
append(".deviceType=%s",
_DescribeFlags(b128, 128, kDeviceType, ARRAYLEN(kDeviceType),
"kCosmoAudioDeviceType", options->deviceType));
gotsome = true;
}
if (options->bufferFrames) {
if (gotsome)
append(", ");
append(".bufferFrames=%d", options->bufferFrames);
gotsome = true;
}
if (options->debugLog) {
if (gotsome)
append(", ");
append(".debugLog=%d", options->debugLog);
gotsome = true;
}
if (options->sizeofThis) {
if (gotsome)
append(", ");
append(".sizeofThis=%d", options->sizeofThis);
gotsome = true;
}
append("}");
return buf;
}
const char *cosmoaudio_describe_poll_frames(char *buf, int n,
int *in_out_frames) {
if (!in_out_frames)
return "NULL";
if (kisdangerous(in_out_frames)) {
ksnprintf(buf, n, "%p", in_out_frames);
return buf;
}
ksnprintf(buf, n, "[%d]", *in_out_frames);
return buf;
}

View file

@ -1,12 +0,0 @@
#ifndef COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_
#define COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
COSMOPOLITAN_C_START_
const char *cosmoaudio_describe_status(char *, int, int);
const char *cosmoaudio_describe_open_options(
char *, int, const struct CosmoAudioOpenOptions *);
const char *cosmoaudio_describe_poll_frames(char *, int, int *);
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_ */

View file

@ -1,2 +0,0 @@
DisableFormat: true
SortIncludes: Never

View file

@ -25,13 +25,18 @@ DSP_MPEG_A_CHECKS = \
DSP_MPEG_A_DIRECTDEPS = \
LIBC_CALLS \
LIBC_FMT \
LIBC_INTRIN \
LIBC_LOG \
LIBC_LOG \
LIBC_MEM \
LIBC_NEXGEN32E \
LIBC_RUNTIME \
LIBC_STDIO \
LIBC_STR \
LIBC_SYSV \
LIBC_TINYMATH \
THIRD_PARTY_COMPILER_RT \
THIRD_PARTY_COMPILER_RT
DSP_MPEG_A_DEPS := \
$(call uniq,$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x))))
@ -44,10 +49,9 @@ $(DSP_MPEG_A).pkg: \
$(DSP_MPEG_A_OBJS) \
$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x)_A).pkg)
o/$(MODE)/dsp/mpeg/pl_mpeg.o: private \
o/$(MODE)/dsp/mpeg/clamp4int256-k8.o: private \
CFLAGS += \
-ffunction-sections \
-fdata-sections
-Os
DSP_MPEG_LIBS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)))
DSP_MPEG_SRCS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_SRCS))

View file

@ -1,17 +0,0 @@
DESCRIPTION
pl_mpeg lets you decode .mpg files
ORIGIN
https://github.com/phoboslab/pl_mpeg/
9e40dd6536269d788728e32c39bfacf2ab7a0866
LICENSE
MIT
LOCAL CHANGES
- Added API for extracting pixel aspect ratio
https://github.com/phoboslab/pl_mpeg/pull/42

View file

@ -1,68 +0,0 @@
# PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Single-file MIT licensed library for C/C++
See [pl_mpeg.h](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg.h) for
the documentation.
## Why?
This is meant as a simple way to get video playback into your app or game. Other
solutions, such as ffmpeg require huge libraries and a lot of glue code.
MPEG1 is an old and inefficient codec, but it's still good enough for many use
cases. All patents related to MPEG1 and MP2 have expired, so it's completely
free now.
This library does not make use of any SIMD instructions, but because of
the relative simplicity of the codec it still manages to decode 4k60fps video
on a single CPU core (on my i7-6700k at least).
## Compilation on Linux
Use a GCC invocation like the following to build the example `pl_mpeg_player`
program:
```shell
gcc -o pl_mpeg_player pl_mpeg_player.c $(pkg-config --cflags --libs sdl2 glew)
```
## Example Usage
- [pl_mpeg_extract_frames.c](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg_extract_frames.c)
extracts all frames from a video and saves them as PNG.
- [pl_mpeg_player.c](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg_player.c)
implements a video player using SDL2 and OpenGL for rendering.
## Encoding for PL_MPEG
Most [MPEG-PS](https://en.wikipedia.org/wiki/MPEG_program_stream) (`.mpg`) files
containing MPEG1 Video ("mpeg1") and MPEG1 Audio Layer II ("mp2") streams should
work with PL_MPEG. Note that `.mpg` files can also contain MPEG2 Video, which is
not supported by this library.
You can encode video in a suitable format using ffmpeg:
```
ffmpeg -i input.mp4 -c:v mpeg1video -q:v 0 -c:a mp2 -format mpeg output.mpg
```
`-q:v` sets a fixed video quality with a variable bitrate, where `0` is the
highest. You may use `-b:v` to set a fixed bitrate instead; e.g.
`-b:v 2000k` for 2000 kbit/s. Please refer to the
[ffmpeg documentation](http://ffmpeg.org/ffmpeg.html#Options) for more details.
If you just want to quickly test the library, try this file:
https://phoboslab.org/files/bjork-all-is-full-of-love.mpg
## Limitations
- no error reporting. PL_MPEG will silently ignore any invalid data.
- the pts (presentation time stamp) for packets in the MPEG-PS container is
ignored. This may cause sync issues with some files.
- bugs, probably.

92
dsp/mpeg/README.txt Normal file
View file

@ -0,0 +1,92 @@
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org
-- Synopsis
// This function gets called for each decoded video frame
void my_video_callback(plm_t *plm, plm_frame_t *frame, void *user) {
// Do something with frame->y.data, frame->cr.data, frame->cb.data
}
// This function gets called for each decoded audio frame
void my_audio_callback(plm_t *plm, plm_samples_t *frame, void *user) {
// Do something with samples->interleaved
}
// Load a .mpg (MPEG Program Stream) file
plm_t *plm = plm_create_with_filename("some-file.mpg");
// Install the video & audio decode callbacks
plm_set_video_decode_callback(plm, my_video_callback, my_data);
plm_set_audio_decode_callback(plm, my_audio_callback, my_data);
// Decode
do {
plm_decode(plm, time_since_last_call);
} while (!plm_has_ended(plm));
// All done
plm_destroy(plm);
-- Documentation
This library provides several interfaces to load, demux and decode MPEG video
and audio data. A high-level API combines the demuxer, video & audio decoders
in an easy to use wrapper.
Lower-level APIs for accessing the demuxer, video decoder and audio decoder,
as well as providing different data sources are also available.
Interfaces are written in an object orientet style, meaning you create object
instances via various different constructor functions (plm_*create()),
do some work on them and later dispose them via plm_*destroy().
plm_* -- the high-level interface, combining demuxer and decoders
plm_buffer_* -- the data source used by all interfaces
plm_demux_* -- the MPEG-PS demuxer
plm_video_* -- the MPEG1 Video ("mpeg1") decoder
plm_audio_* -- the MPEG1 Audio Layer II ("mp2") decoder
This library uses malloc(), realloc() and free() to manage memory. Typically
all allocation happens up-front when creating the interface. However, the
default buffer size may be too small for certain inputs. In these cases plmpeg
will realloc() the buffer with a larger size whenever needed. You can configure
the default buffer size by defining PLM_BUFFER_DEFAULT_SIZE *before*
including this library.
With the high-level interface you have two options to decode video & audio:
1) Use plm_decode() and just hand over the delta time since the last call.
It will decode everything needed and call your callbacks (specified through
plm_set_{video|audio}_decode_callback()) any number of times.
2) Use plm_decode_video() and plm_decode_audio() to decode exactly one
frame of video or audio data at a time. How you handle the synchronization of
both streams is up to you.
If you only want to decode video *or* audio through these functions, you should
disable the other stream (plm_set_{video|audio}_enabled(false))
Video data is decoded into a struct with all 3 planes (Y, Cr, Cb) stored in
separate buffers. You can either convert this to RGB on the CPU (slow) via the
plm_frame_to_rgb() function or do it on the GPU with the following matrix:
mat4 rec601 = mat4(
1.16438, 0.00000, 1.59603, -0.87079,
1.16438, -0.39176, -0.81297, 0.52959,
1.16438, 2.01723, 0.00000, -1.08139,
0, 0, 0, 1
);
gl_FragColor = vec4(y, cb, cr, 1.0) * rec601;
Audio data is decoded into a struct with either one single float array with the
samples for the left and right channel interleaved, or if the
PLM_AUDIO_SEPARATE_CHANNELS is defined *before* including this library, into
two separate float arrays - one for each channel.
See below for detailed the API documentation.

20
dsp/mpeg/blockset.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_
#define COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_
#define PLM_BLOCK_SET(DEST, DEST_INDEX, DEST_WIDTH, SOURCE_INDEX, \
SOURCE_WIDTH, BLOCK_SIZE, OP) \
do { \
int dest_scan = DEST_WIDTH - BLOCK_SIZE; \
int source_scan = SOURCE_WIDTH - BLOCK_SIZE; \
for (int y = 0; y < BLOCK_SIZE; y++) { \
for (int x = 0; x < BLOCK_SIZE; x++) { \
DEST[DEST_INDEX] = OP; \
SOURCE_INDEX++; \
DEST_INDEX++; \
} \
SOURCE_INDEX += source_scan; \
DEST_INDEX += dest_scan; \
} \
} while (false)
#endif /* COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_ */

153
dsp/mpeg/buffer.c Normal file
View file

@ -0,0 +1,153 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org │
The MIT License(MIT)
Copyright(c) 2019 Dominic Szablewski
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.
*/
#include "dsp/mpeg/buffer.h"
#include "dsp/mpeg/mpeg.h"
#include "libc/calls/calls.h"
#include "libc/log/check.h"
#include "libc/mem/mem.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/madv.h"
__static_yoink("pl_mpeg_notice");
/* clang-format off */
// -----------------------------------------------------------------------------
// plm_buffer implementation
plm_buffer_t *plm_buffer_create_with_filename(const char *filename) {
FILE *fh = fopen(filename, "rb");
if (!fh) {
return NULL;
}
fadvise(fileno(fh), 0, 0, MADV_SEQUENTIAL);
return plm_buffer_create_with_file(fh, true);
}
plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done) {
plm_buffer_t *b;
b = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE);
b->fh = fh;
b->close_when_done = close_when_done;
b->mode = PLM_BUFFER_MODE_FILE;
plm_buffer_set_load_callback(b, plm_buffer_load_file_callback, NULL);
return b;
}
plm_buffer_t *plm_buffer_create_with_memory(unsigned char *bytes, size_t length, int free_when_done) {
plm_buffer_t *b;
b = memalign(_Alignof(plm_buffer_t), sizeof(plm_buffer_t));
memset(b, 0, sizeof(plm_buffer_t));
b->capacity = length;
b->length = length;
b->free_when_done = free_when_done;
b->bytes = bytes;
b->mode = PLM_BUFFER_MODE_FIXED_MEM;
return b;
}
plm_buffer_t * plm_buffer_create_with_capacity(size_t capacity) {
plm_buffer_t *b;
b = memalign(_Alignof(plm_buffer_t), sizeof(plm_buffer_t));
memset(b, 0, sizeof(plm_buffer_t));
b->capacity = capacity;
b->free_when_done = true;
b->bytes = (unsigned char *)malloc(capacity);
b->mode = PLM_BUFFER_MODE_DYNAMIC_MEM;
return b;
}
void plm_buffer_destroy(plm_buffer_t *self) {
if (self->fh && self->close_when_done) {
fclose(self->fh);
}
if (self->free_when_done) {
free(self->bytes);
}
free(self);
}
size_t plm_buffer_write(plm_buffer_t *self, unsigned char *bytes, size_t length) {
if (self->mode == PLM_BUFFER_MODE_FIXED_MEM) {
return 0;
}
// This should be a ring buffer, but instead it just shifts all unread data
// to the beginning of the buffer and appends new data at the end. Seems
// to be good enough.
plm_buffer_discard_read_bytes(self);
// Do we have to resize to fit the new data?
size_t bytes_available = self->capacity - self->length;
if (bytes_available < length) {
size_t new_size = self->capacity;
do {
new_size *= 2;
} while (new_size - self->length < length);
self->bytes = (unsigned char *)realloc(self->bytes, new_size);
self->capacity = new_size;
}
memcpy(self->bytes + self->length, bytes, length);
self->length += length;
return length;
}
void plm_buffer_set_load_callback(plm_buffer_t *self, plm_buffer_load_callback fp, void *user) {
self->load_callback = fp;
self->load_callback_user_data = user;
}
void plm_buffer_rewind(plm_buffer_t *self) {
if (self->fh) {
fseek(self->fh, 0, SEEK_SET);
self->length = 0;
}
if (self->mode != PLM_BUFFER_MODE_FIXED_MEM) {
self->length = 0;
}
self->bit_index = 0;
}
void plm_buffer_discard_read_bytes(plm_buffer_t *self) {
size_t byte_pos = self->bit_index >> 3;
if (byte_pos == self->length) {
self->bit_index = 0;
self->length = 0;
}
else if (byte_pos > 0) {
memmove(self->bytes, self->bytes + byte_pos, self->length - byte_pos);
self->bit_index -= byte_pos << 3;
self->length -= byte_pos;
}
}
void plm_buffer_load_file_callback(plm_buffer_t *self, void *user) {
plm_buffer_discard_read_bytes(self);
unsigned bytes_available = self->capacity - self->length;
unsigned bytes_read = fread(self->bytes + self->length, 1, bytes_available, self->fh);
self->length += bytes_read;
}

160
dsp/mpeg/buffer.h Normal file
View file

@ -0,0 +1,160 @@
#ifndef COSMOPOLITAN_DSP_MPEG_BUFFER_H_
#define COSMOPOLITAN_DSP_MPEG_BUFFER_H_
#include "dsp/mpeg/mpeg.h"
COSMOPOLITAN_C_START_
enum plm_buffer_mode {
PLM_BUFFER_MODE_FILE,
PLM_BUFFER_MODE_FIXED_MEM,
PLM_BUFFER_MODE_DYNAMIC_MEM
};
typedef struct plm_buffer_t {
unsigned bit_index;
unsigned capacity;
unsigned length;
int free_when_done;
int close_when_done;
FILE *fh;
plm_buffer_load_callback load_callback;
void *load_callback_user_data;
unsigned char *bytes;
enum plm_buffer_mode mode;
} plm_buffer_t;
typedef struct {
int16_t index;
int16_t value;
} plm_vlc_t;
typedef struct {
int16_t index;
uint16_t value;
} plm_vlc_uint_t;
/* bool plm_buffer_has(plm_buffer_t *, size_t); */
/* int plm_buffer_read(plm_buffer_t *, int); */
/* void plm_buffer_align(plm_buffer_t *); */
/* void plm_buffer_skip(plm_buffer_t *, size_t); */
/* int plm_buffer_skip_bytes(plm_buffer_t *, unsigned char); */
/* int plm_buffer_next_start_code(plm_buffer_t *); */
/* int plm_buffer_find_start_code(plm_buffer_t *, int); */
/* int plm_buffer_no_start_code(plm_buffer_t *); */
/* int16_t plm_buffer_read_vlc(plm_buffer_t *, const plm_vlc_t *); */
/* uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *, const plm_vlc_uint_t *); */
void plm_buffer_discard_read_bytes(plm_buffer_t *);
relegated void plm_buffer_load_file_callback(plm_buffer_t *, void *);
forceinline bool plm_buffer_has(plm_buffer_t *b, size_t bits) {
unsigned have;
have = b->length;
have <<= 3;
have -= b->bit_index;
if (bits <= have) {
return true;
} else {
if (b->load_callback) {
b->load_callback(b, b->load_callback_user_data);
return ((b->length << 3) - b->bit_index) >= bits;
} else {
return false;
}
}
}
forceinline int plm_buffer_read(plm_buffer_t *self, int count) {
if (!plm_buffer_has(self, count))
return 0;
int value = 0;
while (count) {
int current_byte = self->bytes[self->bit_index >> 3];
int remaining = 8 - (self->bit_index & 7); // Remaining bits in byte
int read = remaining < count ? remaining : count; // Bits in self run
int shift = remaining - read;
int mask = (0xff >> (8 - read));
value = (value << read) | ((current_byte & (mask << shift)) >> shift);
self->bit_index += read;
count -= read;
}
return value;
}
forceinline void plm_buffer_align(plm_buffer_t *self) {
self->bit_index = ((self->bit_index + 7) >> 3) << 3;
}
forceinline void plm_buffer_skip(plm_buffer_t *self, size_t count) {
if (plm_buffer_has(self, count)) {
self->bit_index += count;
}
}
forceinline int plm_buffer_skip_bytes(plm_buffer_t *self, unsigned char v) {
unsigned skipped;
plm_buffer_align(self);
skipped = 0;
while (plm_buffer_has(self, 8)) {
if (v == self->bytes[self->bit_index >> 3]) {
self->bit_index += 8;
++skipped;
} else {
break;
}
}
return skipped;
}
forceinline int plm_buffer_next_start_code(plm_buffer_t *self) {
plm_buffer_align(self);
while (plm_buffer_has(self, (5 << 3))) {
size_t byte_index = (self->bit_index) >> 3;
if (self->bytes[byte_index] == 0x00 &&
self->bytes[byte_index + 1] == 0x00 &&
self->bytes[byte_index + 2] == 0x01) {
self->bit_index = (byte_index + 4) << 3;
return self->bytes[byte_index + 3];
}
self->bit_index += 8;
}
self->bit_index = (self->length << 3);
return -1;
}
forceinline int plm_buffer_find_start_code(plm_buffer_t *self, int code) {
int current = 0;
while (true) {
current = plm_buffer_next_start_code(self);
if (current == code || current == -1) {
return current;
}
}
return -1;
}
forceinline int plm_buffer_no_start_code(plm_buffer_t *self) {
if (!plm_buffer_has(self, (5 << 3))) {
return false;
}
size_t byte_index = ((self->bit_index + 7) >> 3);
return !(self->bytes[byte_index] == 0x00 &&
self->bytes[byte_index + 1] == 0x00 &&
self->bytes[byte_index + 2] == 0x01);
}
forceinline int16_t plm_buffer_read_vlc(plm_buffer_t *self,
const plm_vlc_t *table) {
plm_vlc_t state = {0, 0};
do {
state = table[state.index + plm_buffer_read(self, 1)];
} while (state.index > 0);
return state.value;
}
forceinline uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *self,
const plm_vlc_uint_t *table) {
return (uint16_t)plm_buffer_read_vlc(self, (plm_vlc_t *)table);
}
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_DSP_MPEG_BUFFER_H_ */

View file

@ -0,0 +1,30 @@
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
vi: set noet ft=asm ts=8 sw=8 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
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.
*/
#include "libc/macros.h"
clamp4int256$core:
.leafprologue
pxor %xmm1,%xmm1
pmaxsd %xmm1,%xmm0
pminsd 0f(%rip),%xmm0
.leafepilogue
.endfn clamp4int256$core,globl
.rodata.cst16
0: .long 255,255,255,255

203
dsp/mpeg/demux.c Normal file
View file

@ -0,0 +1,203 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org │
The MIT License(MIT)
Copyright(c) 2019 Dominic Szablewski
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.
*/
#include "dsp/mpeg/demux.h"
#include "dsp/mpeg/buffer.h"
#include "dsp/mpeg/mpeg.h"
#include "libc/mem/mem.h"
#include "libc/str/str.h"
__static_yoink("pl_mpeg_notice");
/* clang-format off */
// ----------------------------------------------------------------------------
// plm_demux implementation
plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done) {
plm_demux_t *self = (plm_demux_t *)malloc(sizeof(plm_demux_t));
memset(self, 0, sizeof(plm_demux_t));
self->buffer = buffer;
self->destroy_buffer_when_done = destroy_when_done;
if (plm_buffer_find_start_code(self->buffer, START_PACK) != -1) {
plm_demux_decode_pack_header(self);
}
if (plm_buffer_find_start_code(self->buffer, START_SYSTEM) != -1) {
plm_demux_decode_system_header(self);
}
return self;
}
void plm_demux_destroy(plm_demux_t *self) {
if (self->destroy_buffer_when_done) {
plm_buffer_destroy(self->buffer);
}
free(self);
}
int plm_demux_get_num_video_streams(plm_demux_t *self) {
return self->num_video_streams;
}
int plm_demux_get_num_audio_streams(plm_demux_t *self) {
return self->num_audio_streams;
}
void plm_demux_rewind(plm_demux_t *self) {
plm_buffer_rewind(self->buffer);
}
plm_packet_t *plm_demux_decode(plm_demux_t *self) {
if (self->current_packet.length) {
size_t bits_till_next_packet = self->current_packet.length << 3;
if (!plm_buffer_has(self->buffer, bits_till_next_packet)) {
return NULL;
}
plm_buffer_skip(self->buffer, bits_till_next_packet);
self->current_packet.length = 0;
}
if (!self->has_pack_header) {
if (plm_buffer_find_start_code(self->buffer, START_PACK) != -1) {
plm_demux_decode_pack_header(self);
}
else {
return NULL;
}
}
if (!self->has_system_header) {
if (plm_buffer_find_start_code(self->buffer, START_SYSTEM) != -1) {
plm_demux_decode_system_header(self);
}
else {
return NULL;
}
}
// pending packet just waiting for data?
if (self->next_packet.length) {
return plm_demux_get_packet(self);
}
int code;
do {
code = plm_buffer_next_start_code(self->buffer);
if (
code == PLM_DEMUX_PACKET_VIDEO_1 ||
code == PLM_DEMUX_PACKET_PRIVATE ||
(code >= PLM_DEMUX_PACKET_AUDIO_1 && code <= PLM_DEMUX_PACKET_AUDIO_4)
) {
return plm_demux_decode_packet(self, code);
}
} while (code != -1);
return NULL;
}
double plm_demux_read_time(plm_demux_t *self) {
int64_t clock = plm_buffer_read(self->buffer, 3) << 30;
plm_buffer_skip(self->buffer, 1);
clock |= plm_buffer_read(self->buffer, 15) << 15;
plm_buffer_skip(self->buffer, 1);
clock |= plm_buffer_read(self->buffer, 15);
plm_buffer_skip(self->buffer, 1);
return (double)clock / 90000.0;
}
void plm_demux_decode_pack_header(plm_demux_t *self) {
if (plm_buffer_read(self->buffer, 4) != 0x02) {
return; // invalid
}
self->system_clock_ref = plm_demux_read_time(self);
plm_buffer_skip(self->buffer, 1);
plm_buffer_skip(self->buffer, 22); // mux_rate * 50
plm_buffer_skip(self->buffer, 1);
self->has_pack_header = true;
}
void plm_demux_decode_system_header(plm_demux_t *self) {
plm_buffer_skip(self->buffer, 16); // header_length
plm_buffer_skip(self->buffer, 24); // rate bound
self->num_audio_streams = plm_buffer_read(self->buffer, 6);
plm_buffer_skip(self->buffer, 5); // misc flags
self->num_video_streams = plm_buffer_read(self->buffer, 5);
self->has_system_header = true;
}
plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int start_code) {
if (!plm_buffer_has(self->buffer, 8 << 3)) {
return NULL;
}
self->next_packet.type = start_code;
self->next_packet.length = plm_buffer_read(self->buffer, 16);
self->next_packet.length -= plm_buffer_skip_bytes(self->buffer, 0xff); // stuffing
// skip P-STD
if (plm_buffer_read(self->buffer, 2) == 0x01) {
plm_buffer_skip(self->buffer, 16);
self->next_packet.length -= 2;
}
int pts_dts_marker = plm_buffer_read(self->buffer, 2);
if (pts_dts_marker == 0x03) {
self->next_packet.pts = plm_demux_read_time(self);
plm_buffer_skip(self->buffer, 40); // skip dts
self->next_packet.length -= 10;
}
else if (pts_dts_marker == 0x02) {
self->next_packet.pts = plm_demux_read_time(self);
self->next_packet.length -= 5;
}
else if (pts_dts_marker == 0x00) {
self->next_packet.pts = 0;
plm_buffer_skip(self->buffer, 4);
self->next_packet.length -= 1;
}
else {
return NULL; // invalid
}
return plm_demux_get_packet(self);
}
plm_packet_t *plm_demux_get_packet(plm_demux_t *self) {
if (!plm_buffer_has(self->buffer, self->next_packet.length << 3)) {
return NULL;
}
self->current_packet.data = self->buffer->bytes + (self->buffer->bit_index >> 3);
self->current_packet.length = self->next_packet.length;
self->current_packet.type = self->next_packet.type;
self->current_packet.pts = self->next_packet.pts;
self->next_packet.length = 0;
return &self->current_packet;
}

29
dsp/mpeg/demux.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef COSMOPOLITAN_DSP_MPEG_DEMUX_H_
#define COSMOPOLITAN_DSP_MPEG_DEMUX_H_
#include "dsp/mpeg/mpeg.h"
COSMOPOLITAN_C_START_
#define START_PACK 0xBA
#define START_END 0xB9
#define START_SYSTEM 0xBB
typedef struct plm_demux_t {
plm_buffer_t *buffer;
int destroy_buffer_when_done;
double system_clock_ref;
int has_pack_header;
int has_system_header;
int num_audio_streams;
int num_video_streams;
plm_packet_t current_packet;
plm_packet_t next_packet;
} plm_demux_t;
double plm_demux_read_time(plm_demux_t *self);
void plm_demux_decode_pack_header(plm_demux_t *self);
void plm_demux_decode_system_header(plm_demux_t *self);
plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int start_code);
plm_packet_t *plm_demux_get_packet(plm_demux_t *self);
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_DSP_MPEG_DEMUX_H_ */

101
dsp/mpeg/idct.c Normal file
View file

@ -0,0 +1,101 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
vi: set et ft=c ts=4 sw=4 fenc=utf-8 :vi
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org │
The MIT License(MIT)
Copyright(c) 2019 Dominic Szablewski
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.
*/
#include "dsp/core/half.h"
__static_yoink("pl_mpeg_notice");
/**
* Computes Fixed-Point 8x8 Inverse Discrete Cosine Transform.
*
* @note discovered by Nasir Ahmed
*/
void plm_video_idct(int block[8][8]) {
int i, t1, t2, m0;
int b1, b3, b4, b6, b7;
int y3, y4, y5, y6, y7;
int x0, x1, x2, x3, x4;
for (i = 0; i < 8; ++i) {
b1 = block[4][i];
b3 = block[2][i] + block[6][i];
b4 = block[5][i] - block[3][i];
t1 = block[1][i] + block[7][i];
t2 = block[3][i] + block[5][i];
b6 = block[1][i] - block[7][i];
b7 = t1 + t2;
m0 = block[0][i];
x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7;
x0 = x4 - (((t1 - t2) * 362 + 128) >> 8);
x1 = m0 - b1;
x2 = (((block[2][i] - block[6][i]) * 362 + 128) >> 8) - b3;
x3 = m0 + b1;
y3 = x1 + x2;
y4 = x3 + b3;
y5 = x1 - x2;
y6 = x3 - b3;
y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8);
block[0][i] = b7 + y4;
block[1][i] = x4 + y3;
block[2][i] = y5 - x0;
block[3][i] = y6 - y7;
block[4][i] = y6 + y7;
block[5][i] = x0 + y5;
block[6][i] = y3 - x4;
block[7][i] = y4 - b7;
}
for (i = 0; i < 8; ++i) {
b1 = block[i][4];
b3 = block[i][2] + block[i][6];
b4 = block[i][5] - block[i][3];
t1 = block[i][1] + block[i][7];
t2 = block[i][3] + block[i][5];
b6 = block[i][1] - block[i][7];
b7 = t1 + t2;
m0 = block[i][0];
x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7;
x0 = x4 - (((t1 - t2) * 362 + 128) >> 8);
x1 = m0 - b1;
x2 = (((block[i][2] - block[i][6]) * 362 + 128) >> 8) - b3;
x3 = m0 + b1;
y3 = x1 + x2;
y4 = x3 + b3;
y5 = x1 - x2;
y6 = x3 - b3;
y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8);
block[i][0] = (b7 + y4 + 128) >> 8;
block[i][1] = (x4 + y3 + 128) >> 8;
block[i][2] = (y5 - x0 + 128) >> 8;
block[i][3] = (y6 - y7 + 128) >> 8;
block[i][4] = (y6 + y7 + 128) >> 8;
block[i][5] = (x0 + y5 + 128) >> 8;
block[i][6] = (y3 - x4 + 128) >> 8;
block[i][7] = (y4 - b7 + 128) >> 8;
}
}

8
dsp/mpeg/idct.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef COSMOPOLITAN_DSP_MPEG_IDCT_H_
#define COSMOPOLITAN_DSP_MPEG_IDCT_H_
COSMOPOLITAN_C_START_
void plm_video_idct(int *);
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_DSP_MPEG_IDCT_H_ */

171
dsp/mpeg/macroblock.c Normal file
View file

@ -0,0 +1,171 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
vi: set et ft=c ts=4 sw=4 fenc=utf-8 :vi
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org │
The MIT License(MIT)
Copyright(c) 2019 Dominic Szablewski
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.
*/
#include "dsp/mpeg/mpeg.h"
#include "dsp/mpeg/video.h"
#include "libc/log/check.h"
forceinline void plm_video_process_macroblock(plm_video_t *self, uint8_t *d,
uint8_t *s, int motion_h,
int motion_v, bool interpolate,
unsigned BW) {
unsigned si, di, max_address;
int y, x, dest_scan, source_scan, dw, hp, vp, odd_h, odd_v;
dw = self->mb_width * BW;
hp = motion_h >> 1;
vp = motion_v >> 1;
odd_h = (motion_h & 1) == 1;
odd_v = (motion_v & 1) == 1;
si = ((self->mb_row * BW) + vp) * dw + (self->mb_col * BW) + hp;
di = (self->mb_row * dw + self->mb_col) * BW;
max_address = (dw * (self->mb_height * BW - BW + 1) - BW);
if (si > max_address || di > max_address)
return;
d += di;
s += si;
switch (((interpolate << 2) | (odd_h << 1) | (odd_v)) & 7) {
case 0:
dest_scan = dw - BW;
source_scan = dw - BW;
for (y = 0; y < BW; y++) {
for (x = 0; x < BW; x++) {
*d++ = *s++;
}
s += source_scan;
d += dest_scan;
}
break;
case 1:
dest_scan = dw - BW;
source_scan = dw - BW;
for (y = 0; y < BW; y++) {
for (x = 0; x < BW; x++) {
*d++ = (s[0] + s[dw] + 1) >> 1;
s++;
}
s += source_scan;
d += dest_scan;
}
break;
case 2:
dest_scan = dw - BW;
source_scan = dw - BW;
for (y = 0; y < BW; y++) {
for (x = 0; x < BW; x++) {
*d++ = (s[0] + s[1] + 1) >> 1;
s++;
}
s += source_scan;
d += dest_scan;
}
break;
case 3:
dest_scan = dw - BW;
source_scan = dw - BW;
for (y = 0; y < BW; y++) {
for (x = 0; x < BW; x++) {
*d++ = (s[0] + s[1] + s[dw] + s[dw + 1] + 2) >> 2;
s++;
}
s += source_scan;
d += dest_scan;
}
break;
case 4:
dest_scan = dw - BW;
source_scan = dw - BW;
for (y = 0; y < BW; y++) {
for (x = 0; x < BW; x++) {
d[0] = (d[0] + (s[0]) + 1) >> 1;
d++;
s++;
}
s += source_scan;
d += dest_scan;
}
break;
case 5:
dest_scan = dw - BW;
source_scan = dw - BW;
for (y = 0; y < BW; y++) {
for (x = 0; x < BW; x++) {
d[0] = (d[0] + ((s[0] + s[dw] + 1) >> 1) + 1) >> 1;
d++;
s++;
}
s += source_scan;
d += dest_scan;
}
break;
case 6:
dest_scan = dw - BW;
source_scan = dw - BW;
for (y = 0; y < BW; y++) {
for (x = 0; x < BW; x++) {
d[0] = (d[0] + ((s[0] + s[1] + 1) >> 1) + 1) >> 1;
d++;
s++;
}
s += source_scan;
d += dest_scan;
}
break;
case 7:
dest_scan = dw - BW;
source_scan = dw - BW;
for (y = 0; y < BW; y++) {
for (x = 0; x < BW; x++) {
d[0] = (d[0] + ((s[0] + s[1] + s[dw] + s[dw + 1] + 2) >> 2) + 1) >> 1;
d++;
s++;
}
s += source_scan;
d += dest_scan;
}
break;
default:
break;
}
}
void plm_video_process_macroblock_8(plm_video_t *self, uint8_t *d, uint8_t *s,
int motion_h, int motion_v,
bool interpolate) {
DCHECK_ALIGNED(8, d);
DCHECK_ALIGNED(8, s);
plm_video_process_macroblock(self, d, s, motion_h, motion_v, interpolate, 8);
}
void plm_video_process_macroblock_16(plm_video_t *self, uint8_t *d, uint8_t *s,
int motion_h, int motion_v,
bool interpolate) {
DCHECK_ALIGNED(16, d);
DCHECK_ALIGNED(16, s);
plm_video_process_macroblock(self, d, s, motion_h, motion_v, interpolate, 16);
}

769
dsp/mpeg/mp2.c Normal file
View file

@ -0,0 +1,769 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org │
The MIT License(MIT)
Copyright(c) 2019 Dominic Szablewski
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.
*/
#include "dsp/mpeg/buffer.h"
#include "dsp/mpeg/mpeg.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/str/str.h"
/* clang-format off */
// -----------------------------------------------------------------------------
// plm_audio implementation
// Based on kjmp2 by Martin J. Fiedler
// http://keyj.emphy.de/kjmp2/
#define PLM_AUDIO_FRAME_SYNC 0x7ff
#define PLM_AUDIO_MPEG_2_5 0x0
#define PLM_AUDIO_MPEG_2 0x2
#define PLM_AUDIO_MPEG_1 0x3
#define PLM_AUDIO_LAYER_III 0x1
#define PLM_AUDIO_LAYER_II 0x2
#define PLM_AUDIO_LAYER_I 0x3
#define PLM_AUDIO_MODE_STEREO 0x0
#define PLM_AUDIO_MODE_JOINT_STEREO 0x1
#define PLM_AUDIO_MODE_DUAL_CHANNEL 0x2
#define PLM_AUDIO_MODE_MONO 0x3
static const unsigned short PLM_AUDIO_SAMPLE_RATE[] = {
44100, 48000, 32000, 0, // MPEG-1
22050, 24000, 16000, 0 // MPEG-2
};
static const short PLM_AUDIO_BIT_RATE[] = {
32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, // MPEG-1
8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 // MPEG-2
};
static const int PLM_AUDIO_SCALEFACTOR_BASE[] = {
0x02000000, 0x01965FEA, 0x01428A30
};
static const float PLM_AUDIO_SYNTHESIS_WINDOW[] = {
0.0, -0.5, -0.5, -0.5, -0.5, -0.5,
-0.5, -1.0, -1.0, -1.0, -1.0, -1.5,
-1.5, -2.0, -2.0, -2.5, -2.5, -3.0,
-3.5, -3.5, -4.0, -4.5, -5.0, -5.5,
-6.5, -7.0, -8.0, -8.5, -9.5, -10.5,
-12.0, -13.0, -14.5, -15.5, -17.5, -19.0,
-20.5, -22.5, -24.5, -26.5, -29.0, -31.5,
-34.0, -36.5, -39.5, -42.5, -45.5, -48.5,
-52.0, -55.5, -58.5, -62.5, -66.0, -69.5,
-73.5, -77.0, -80.5, -84.5, -88.0, -91.5,
-95.0, -98.0, -101.0, -104.0, 106.5, 109.0,
111.0, 112.5, 113.5, 114.0, 114.0, 113.5,
112.0, 110.5, 107.5, 104.0, 100.0, 94.5,
88.5, 81.5, 73.0, 63.5, 53.0, 41.5,
28.5, 14.5, -1.0, -18.0, -36.0, -55.5,
-76.5, -98.5, -122.0, -147.0, -173.5, -200.5,
-229.5, -259.5, -290.5, -322.5, -355.5, -389.5,
-424.0, -459.5, -495.5, -532.0, -568.5, -605.0,
-641.5, -678.0, -714.0, -749.0, -783.5, -817.0,
-849.0, -879.5, -908.5, -935.0, -959.5, -981.0,
-1000.5, -1016.0, -1028.5, -1037.5, -1042.5, -1043.5,
-1040.0, -1031.5, 1018.5, 1000.0, 976.0, 946.5,
911.0, 869.5, 822.0, 767.5, 707.0, 640.0,
565.5, 485.0, 397.0, 302.5, 201.0, 92.5,
-22.5, -144.0, -272.5, -407.0, -547.5, -694.0,
-846.0, -1003.0, -1165.0, -1331.5, -1502.0, -1675.5,
-1852.5, -2031.5, -2212.5, -2394.0, -2576.5, -2758.5,
-2939.5, -3118.5, -3294.5, -3467.5, -3635.5, -3798.5,
-3955.0, -4104.5, -4245.5, -4377.5, -4499.0, -4609.5,
-4708.0, -4792.5, -4863.5, -4919.0, -4958.0, -4979.5,
-4983.0, -4967.5, -4931.5, -4875.0, -4796.0, -4694.5,
-4569.5, -4420.0, -4246.0, -4046.0, -3820.0, -3567.0,
3287.0, 2979.5, 2644.0, 2280.5, 1888.0, 1467.5,
1018.5, 541.0, 35.0, -499.0, -1061.0, -1650.0,
-2266.5, -2909.0, -3577.0, -4270.0, -4987.5, -5727.5,
-6490.0, -7274.0, -8077.5, -8899.5, -9739.0, -10594.5,
-11464.5, -12347.0, -13241.0, -14144.5, -15056.0, -15973.5,
-16895.5, -17820.0, -18744.5, -19668.0, -20588.0, -21503.0,
-22410.5, -23308.5, -24195.0, -25068.5, -25926.5, -26767.0,
-27589.0, -28389.0, -29166.5, -29919.0, -30644.5, -31342.0,
-32009.5, -32645.0, -33247.0, -33814.5, -34346.0, -34839.5,
-35295.0, -35710.0, -36084.5, -36417.5, -36707.5, -36954.0,
-37156.5, -37315.0, -37428.0, -37496.0, 37519.0, 37496.0,
37428.0, 37315.0, 37156.5, 36954.0, 36707.5, 36417.5,
36084.5, 35710.0, 35295.0, 34839.5, 34346.0, 33814.5,
33247.0, 32645.0, 32009.5, 31342.0, 30644.5, 29919.0,
29166.5, 28389.0, 27589.0, 26767.0, 25926.5, 25068.5,
24195.0, 23308.5, 22410.5, 21503.0, 20588.0, 19668.0,
18744.5, 17820.0, 16895.5, 15973.5, 15056.0, 14144.5,
13241.0, 12347.0, 11464.5, 10594.5, 9739.0, 8899.5,
8077.5, 7274.0, 6490.0, 5727.5, 4987.5, 4270.0,
3577.0, 2909.0, 2266.5, 1650.0, 1061.0, 499.0,
-35.0, -541.0, -1018.5, -1467.5, -1888.0, -2280.5,
-2644.0, -2979.5, 3287.0, 3567.0, 3820.0, 4046.0,
4246.0, 4420.0, 4569.5, 4694.5, 4796.0, 4875.0,
4931.5, 4967.5, 4983.0, 4979.5, 4958.0, 4919.0,
4863.5, 4792.5, 4708.0, 4609.5, 4499.0, 4377.5,
4245.5, 4104.5, 3955.0, 3798.5, 3635.5, 3467.5,
3294.5, 3118.5, 2939.5, 2758.5, 2576.5, 2394.0,
2212.5, 2031.5, 1852.5, 1675.5, 1502.0, 1331.5,
1165.0, 1003.0, 846.0, 694.0, 547.5, 407.0,
272.5, 144.0, 22.5, -92.5, -201.0, -302.5,
-397.0, -485.0, -565.5, -640.0, -707.0, -767.5,
-822.0, -869.5, -911.0, -946.5, -976.0, -1000.0,
1018.5, 1031.5, 1040.0, 1043.5, 1042.5, 1037.5,
1028.5, 1016.0, 1000.5, 981.0, 959.5, 935.0,
908.5, 879.5, 849.0, 817.0, 783.5, 749.0,
714.0, 678.0, 641.5, 605.0, 568.5, 532.0,
495.5, 459.5, 424.0, 389.5, 355.5, 322.5,
290.5, 259.5, 229.5, 200.5, 173.5, 147.0,
122.0, 98.5, 76.5, 55.5, 36.0, 18.0,
1.0, -14.5, -28.5, -41.5, -53.0, -63.5,
-73.0, -81.5, -88.5, -94.5, -100.0, -104.0,
-107.5, -110.5, -112.0, -113.5, -114.0, -114.0,
-113.5, -112.5, -111.0, -109.0, 106.5, 104.0,
101.0, 98.0, 95.0, 91.5, 88.0, 84.5,
80.5, 77.0, 73.5, 69.5, 66.0, 62.5,
58.5, 55.5, 52.0, 48.5, 45.5, 42.5,
39.5, 36.5, 34.0, 31.5, 29.0, 26.5,
24.5, 22.5, 20.5, 19.0, 17.5, 15.5,
14.5, 13.0, 12.0, 10.5, 9.5, 8.5,
8.0, 7.0, 6.5, 5.5, 5.0, 4.5,
4.0, 3.5, 3.5, 3.0, 2.5, 2.5,
2.0, 2.0, 1.5, 1.5, 1.0, 1.0,
1.0, 1.0, 0.5, 0.5, 0.5, 0.5,
0.5, 0.5
};
// Quantizer lookup, step 1: bitrate classes
static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_1[2][16] = {
// 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384 <- bitrate
{ 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, // mono
// 16, 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192 <- bitrate / chan
{ 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2 } // stereo
};
// Quantizer lookup, step 2: bitrate class, sample rate -> B2 table idx, sblimit
static const uint8_t PLM_AUDIO_QUANT_TAB_A = (27 | 64); // Table 3-B.2a: high-rate, sblimit = 27
static const uint8_t PLM_AUDIO_QUANT_TAB_B = (30 | 64); // Table 3-B.2b: high-rate, sblimit = 30
static const uint8_t PLM_AUDIO_QUANT_TAB_C = 8; // Table 3-B.2c: low-rate, sblimit = 8
static const uint8_t PLM_AUDIO_QUANT_TAB_D = 12; // Table 3-B.2d: low-rate, sblimit = 12
static const uint8_t QUANT_LUT_STEP_2[3][3] = {
// 44.1 kHz, 48 kHz, 32 kHz
{ PLM_AUDIO_QUANT_TAB_C, PLM_AUDIO_QUANT_TAB_C, PLM_AUDIO_QUANT_TAB_D }, // 32 - 48 kbit/sec/ch
{ PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_A }, // 56 - 80 kbit/sec/ch
{ PLM_AUDIO_QUANT_TAB_B, PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_B } // 96+ kbit/sec/ch
};
// Quantizer lookup, step 3: B2 table, subband -> nbal, row index
// (upper 4 bits: nbal, lower 4 bits: row index)
static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_3[3][32] = {
// Low-rate table (3-B.2c and 3-B.2d)
{
0x44,0x44,
0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34
},
// High-rate table (3-B.2a and 3-B.2b)
{
0x43,0x43,0x43,
0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,
0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,
0x20,0x20,0x20,0x20,0x20,0x20,0x20
},
// MPEG-2 LSR table (B.2 in ISO 13818-3)
{
0x45,0x45,0x45,0x45,
0x34,0x34,0x34,0x34,0x34,0x34,0x34,
0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,
0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24
}
};
// Quantizer lookup, step 4: table row, allocation[] value -> quant table index
static const uint8_t PLM_AUDIO_QUANT_LUT_STEP4[6][16] = {
{ 0, 1, 2, 17 },
{ 0, 1, 2, 3, 4, 5, 6, 17 },
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17 },
{ 0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 },
{ 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17 },
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
};
typedef struct plm_quantizer_spec_t {
unsigned short levels;
unsigned char group;
unsigned char bits;
} plm_quantizer_spec_t;
static const plm_quantizer_spec_t PLM_AUDIO_QUANT_TAB[] = {
{ 3, 1, 5 }, // 1
{ 5, 1, 7 }, // 2
{ 7, 0, 3 }, // 3
{ 9, 1, 10 }, // 4
{ 15, 0, 4 }, // 5
{ 31, 0, 5 }, // 6
{ 63, 0, 6 }, // 7
{ 127, 0, 7 }, // 8
{ 255, 0, 8 }, // 9
{ 511, 0, 9 }, // 10
{ 1023, 0, 10 }, // 11
{ 2047, 0, 11 }, // 12
{ 4095, 0, 12 }, // 13
{ 8191, 0, 13 }, // 14
{ 16383, 0, 14 }, // 15
{ 32767, 0, 15 }, // 16
{ 65535, 0, 16 } // 17
};
struct plm_audio_t {
double time;
int samples_decoded;
int samplerate_index;
int bitrate_index;
int version;
int layer;
int mode;
int bound;
int v_pos;
int next_frame_data_size;
plm_buffer_t *buffer;
int destroy_buffer_when_done;
const plm_quantizer_spec_t *allocation[2][32];
uint8_t scale_factor_info[2][32];
int scale_factor[2][32][3];
int sample[2][32][3];
plm_samples_t samples;
float D[1024];
float V[1024];
float U[32];
} forcealign(64);
typedef plm_audio_t plm_audio_t;
int plm_audio_decode_header(plm_audio_t *self);
void plm_audio_decode_frame(plm_audio_t *self);
const plm_quantizer_spec_t *plm_audio_read_allocation(plm_audio_t *self, int sb, int tab3);
void plm_audio_read_samples(plm_audio_t *self, int ch, int sb, int part);
void plm_audio_matrix_transform(int s[32][3], int ss, float *d, int dp);
plm_audio_t *plm_audio_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done) {
plm_audio_t *self = (plm_audio_t *)memalign(_Alignof(plm_audio_t), sizeof(plm_audio_t));
memset(self, 0, sizeof(plm_audio_t));
self->samples.count = PLM_AUDIO_SAMPLES_PER_FRAME;
self->buffer = buffer;
self->destroy_buffer_when_done = destroy_when_done;
self->samplerate_index = 3; // indicates 0 samplerate
memcpy(self->D, PLM_AUDIO_SYNTHESIS_WINDOW, 512 * sizeof(float));
memcpy(self->D + 512, PLM_AUDIO_SYNTHESIS_WINDOW, 512 * sizeof(float));
// Decode first header
if (plm_buffer_has(self->buffer, 48)) {
self->next_frame_data_size = plm_audio_decode_header(self);
}
return self;
}
void plm_audio_destroy(plm_audio_t *self) {
if (self->destroy_buffer_when_done) {
plm_buffer_destroy(self->buffer);
}
free(self);
}
int plm_audio_get_samplerate(plm_audio_t *self) {
return PLM_AUDIO_SAMPLE_RATE[self->samplerate_index];
}
double plm_audio_get_time(plm_audio_t *self) {
return self->time;
}
void plm_audio_rewind(plm_audio_t *self) {
plm_buffer_rewind(self->buffer);
self->time = 0;
self->samples_decoded = 0;
self->next_frame_data_size = 0;
// TODO: needed?
memset(self->V, 0, sizeof(self->V));
memset(self->U, 0, sizeof(self->U));
}
plm_samples_t *plm_audio_decode(plm_audio_t *self) {
DEBUGF("%s", "plm_audio_decode");
// Do we have at least enough information to decode the frame header?
if (!self->next_frame_data_size) {
if (!plm_buffer_has(self->buffer, 48)) {
return NULL;
}
self->next_frame_data_size = plm_audio_decode_header(self);
}
if (
self->next_frame_data_size == 0 ||
!plm_buffer_has(self->buffer, self->next_frame_data_size << 3)
) {
return NULL;
}
plm_audio_decode_frame(self);
self->next_frame_data_size = 0;
self->samples.time = self->time;
self->samples_decoded += PLM_AUDIO_SAMPLES_PER_FRAME;
self->time = (double)self->samples_decoded /
(double)PLM_AUDIO_SAMPLE_RATE[self->samplerate_index];
return &self->samples;
}
int plm_audio_decode_header(plm_audio_t *self) {
// Check for valid header: syncword OK, MPEG-Audio Layer 2
plm_buffer_skip_bytes(self->buffer, 0x00);
int sync = plm_buffer_read(self->buffer, 11);
self->version = plm_buffer_read(self->buffer, 2);
self->layer = plm_buffer_read(self->buffer, 2);
int hasCRC = !plm_buffer_read(self->buffer, 1);
if (
sync != PLM_AUDIO_FRAME_SYNC ||
self->version != PLM_AUDIO_MPEG_1 ||
self->layer != PLM_AUDIO_LAYER_II
) {
return false; // Invalid header or unsupported version
}
self->bitrate_index = plm_buffer_read(self->buffer, 4) - 1;
if (self->bitrate_index > 13) {
return false; // Invalid bit rate or 'free format'
}
self->samplerate_index = plm_buffer_read(self->buffer, 2);
if (self->samplerate_index == 3) {
return false; // Invalid sample rate
}
if (self->version == PLM_AUDIO_MPEG_2) {
self->samplerate_index += 4;
self->bitrate_index += 14;
}
int padding = plm_buffer_read(self->buffer, 1);
plm_buffer_skip(self->buffer, 1); // f_private
self->mode = plm_buffer_read(self->buffer, 2);
// Parse the mode_extension, set up the stereo bound
self->bound = 0;
if (self->mode == PLM_AUDIO_MODE_JOINT_STEREO) {
self->bound = (plm_buffer_read(self->buffer, 2) + 1) << 2;
}
else {
plm_buffer_skip(self->buffer, 2);
self->bound = (self->mode == PLM_AUDIO_MODE_MONO) ? 0 : 32;
}
// Discard the last 4 bits of the header and the CRC value, if present
plm_buffer_skip(self->buffer, 4);
if (hasCRC) {
plm_buffer_skip(self->buffer, 16);
}
// Compute frame size, check if we have enough data to decode the whole
// frame.
int bitrate = PLM_AUDIO_BIT_RATE[self->bitrate_index];
int samplerate = PLM_AUDIO_SAMPLE_RATE[self->samplerate_index];
int frame_size = (144000 * bitrate / samplerate) + padding;
return frame_size - (hasCRC ? 6 : 4);
}
void plm_audio_decode_frame(plm_audio_t *self) {
// Prepare the quantizer table lookups
int tab3 = 0;
int sblimit = 0;
if (self->version == PLM_AUDIO_MPEG_2) {
// MPEG-2 (LSR)
tab3 = 2;
sblimit = 30;
}
else {
// MPEG-1
int tab1 = (self->mode == PLM_AUDIO_MODE_MONO) ? 0 : 1;
int tab2 = PLM_AUDIO_QUANT_LUT_STEP_1[tab1][self->bitrate_index];
tab3 = QUANT_LUT_STEP_2[tab2][self->samplerate_index];
sblimit = tab3 & 63;
tab3 >>= 6;
}
if (self->bound > sblimit) {
self->bound = sblimit;
}
// Read the allocation information
for (int sb = 0; sb < self->bound; sb++) {
self->allocation[0][sb] = plm_audio_read_allocation(self, sb, tab3);
self->allocation[1][sb] = plm_audio_read_allocation(self, sb, tab3);
}
for (int sb = self->bound; sb < sblimit; sb++) {
self->allocation[0][sb] =
self->allocation[1][sb] =
plm_audio_read_allocation(self, sb, tab3);
}
// Read scale factor selector information
int channels = (self->mode == PLM_AUDIO_MODE_MONO) ? 1 : 2;
for (int sb = 0; sb < sblimit; sb++) {
for (int ch = 0; ch < channels; ch++) {
if (self->allocation[ch][sb]) {
self->scale_factor_info[ch][sb] = plm_buffer_read(self->buffer, 2);
}
}
if (self->mode == PLM_AUDIO_MODE_MONO) {
self->scale_factor_info[1][sb] = self->scale_factor_info[0][sb];
}
}
// Read scale factors
for (int sb = 0; sb < sblimit; sb++) {
for (int ch = 0; ch < channels; ch++) {
if (self->allocation[ch][sb]) {
int *sf = self->scale_factor[ch][sb];
switch (self->scale_factor_info[ch][sb]) {
case 0:
sf[0] = plm_buffer_read(self->buffer, 6);
sf[1] = plm_buffer_read(self->buffer, 6);
sf[2] = plm_buffer_read(self->buffer, 6);
break;
case 1:
sf[0] =
sf[1] = plm_buffer_read(self->buffer, 6);
sf[2] = plm_buffer_read(self->buffer, 6);
break;
case 2:
sf[0] =
sf[1] =
sf[2] = plm_buffer_read(self->buffer, 6);
break;
case 3:
sf[0] = plm_buffer_read(self->buffer, 6);
sf[1] =
sf[2] = plm_buffer_read(self->buffer, 6);
break;
}
}
}
if (self->mode == PLM_AUDIO_MODE_MONO) {
self->scale_factor[1][sb][0] = self->scale_factor[0][sb][0];
self->scale_factor[1][sb][1] = self->scale_factor[0][sb][1];
self->scale_factor[1][sb][2] = self->scale_factor[0][sb][2];
}
}
// Coefficient input and reconstruction
int out_pos = 0;
for (int part = 0; part < 3; part++) {
for (int granule = 0; granule < 4; granule++) {
// Read the samples
for (int sb = 0; sb < self->bound; sb++) {
plm_audio_read_samples(self, 0, sb, part);
plm_audio_read_samples(self, 1, sb, part);
}
for (int sb = self->bound; sb < sblimit; sb++) {
plm_audio_read_samples(self, 0, sb, part);
self->sample[1][sb][0] = self->sample[0][sb][0];
self->sample[1][sb][1] = self->sample[0][sb][1];
self->sample[1][sb][2] = self->sample[0][sb][2];
}
for (int sb = sblimit; sb < 32; sb++) {
self->sample[0][sb][0] = 0;
self->sample[0][sb][1] = 0;
self->sample[0][sb][2] = 0;
self->sample[1][sb][0] = 0;
self->sample[1][sb][1] = 0;
self->sample[1][sb][2] = 0;
}
// Synthesis loop
for (int p = 0; p < 3; p++) {
// Shifting step
self->v_pos = (self->v_pos - 64) & 1023;
for (int ch = 0; ch < 2; ch++) {
plm_audio_matrix_transform(self->sample[ch], p, self->V, self->v_pos);
// Build U, windowing, calculate output
memset(self->U, 0, sizeof(self->U));
int d_index = 512 - (self->v_pos >> 1);
int v_index = (self->v_pos % 128) >> 1;
while (v_index < 1024) {
for (int i = 0; i < 32; ++i) {
self->U[i] += self->D[d_index++] * self->V[v_index++];
}
v_index += 128 - 32;
d_index += 64 - 32;
}
d_index -= (512 - 32);
v_index = (128 - 32 + 1024) - v_index;
while (v_index < 1024) {
for (int i = 0; i < 32; ++i) {
self->U[i] += self->D[d_index++] * self->V[v_index++];
}
v_index += 128 - 32;
d_index += 64 - 32;
}
// Output samples
#ifdef PLM_AUDIO_SEPARATE_CHANNELS
float *out_channel = ch == 0
? self->samples.left
: self->samples.right;
for (int j = 0; j < 32; j++) {
out_channel[out_pos + j] = self->U[j] / 2147418112.0f;
}
#else
for (int j = 0; j < 32; j++) {
self->samples.interleaved[((out_pos + j) << 1) + ch] =
self->U[j] / 2147418112.0f;
}
#endif
} // End of synthesis channel loop
out_pos += 32;
} // End of synthesis sub-block loop
} // Decoding of the granule finished
}
plm_buffer_align(self->buffer);
}
const plm_quantizer_spec_t *plm_audio_read_allocation(plm_audio_t *self, int sb, int tab3) {
int tab4 = PLM_AUDIO_QUANT_LUT_STEP_3[tab3][sb];
int qtab = PLM_AUDIO_QUANT_LUT_STEP4[tab4 & 15][plm_buffer_read(self->buffer, tab4 >> 4)];
return qtab ? (&PLM_AUDIO_QUANT_TAB[qtab - 1]) : 0;
}
void plm_audio_read_samples(plm_audio_t *self, int ch, int sb, int part) {
const plm_quantizer_spec_t *q = self->allocation[ch][sb];
int sf = self->scale_factor[ch][sb][part];
int *sample = self->sample[ch][sb];
int val = 0;
if (!q) {
// No bits allocated for this subband
sample[0] = sample[1] = sample[2] = 0;
return;
}
// Resolve scalefactor
if (sf == 63) {
sf = 0;
}
else {
int shift = (sf / 3) | 0;
sf = (PLM_AUDIO_SCALEFACTOR_BASE[sf % 3] + ((1u << shift) >> 1)) >> shift;
}
// Decode samples
int adj = q->levels;
if (q->group) {
// Decode grouped samples
val = plm_buffer_read(self->buffer, q->bits);
sample[0] = val % adj;
val /= adj;
sample[1] = val % adj;
sample[2] = val / adj;
}
else {
// Decode direct samples
sample[0] = plm_buffer_read(self->buffer, q->bits);
sample[1] = plm_buffer_read(self->buffer, q->bits);
sample[2] = plm_buffer_read(self->buffer, q->bits);
}
// Postmultiply samples
int scale = 65536 / (adj + 1);
adj = ((adj + 1) >> 1) - 1;
val = (adj - sample[0]) * scale;
sample[0] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
val = (adj - sample[1]) * scale;
sample[1] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
val = (adj - sample[2]) * scale;
sample[2] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
}
void plm_audio_matrix_transform(int s[32][3], int ss, float *d, int dp) {
float t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12,
t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24,
t25, t26, t27, t28, t29, t30, t31, t32, t33;
t01 = (float)(s[0][ss] + s[31][ss]); t02 = (float)(s[0][ss] - s[31][ss]) * 0.500602998235f;
t03 = (float)(s[1][ss] + s[30][ss]); t04 = (float)(s[1][ss] - s[30][ss]) * 0.505470959898f;
t05 = (float)(s[2][ss] + s[29][ss]); t06 = (float)(s[2][ss] - s[29][ss]) * 0.515447309923f;
t07 = (float)(s[3][ss] + s[28][ss]); t08 = (float)(s[3][ss] - s[28][ss]) * 0.53104259109f;
t09 = (float)(s[4][ss] + s[27][ss]); t10 = (float)(s[4][ss] - s[27][ss]) * 0.553103896034f;
t11 = (float)(s[5][ss] + s[26][ss]); t12 = (float)(s[5][ss] - s[26][ss]) * 0.582934968206f;
t13 = (float)(s[6][ss] + s[25][ss]); t14 = (float)(s[6][ss] - s[25][ss]) * 0.622504123036f;
t15 = (float)(s[7][ss] + s[24][ss]); t16 = (float)(s[7][ss] - s[24][ss]) * 0.674808341455f;
t17 = (float)(s[8][ss] + s[23][ss]); t18 = (float)(s[8][ss] - s[23][ss]) * 0.744536271002f;
t19 = (float)(s[9][ss] + s[22][ss]); t20 = (float)(s[9][ss] - s[22][ss]) * 0.839349645416f;
t21 = (float)(s[10][ss] + s[21][ss]); t22 = (float)(s[10][ss] - s[21][ss]) * 0.972568237862f;
t23 = (float)(s[11][ss] + s[20][ss]); t24 = (float)(s[11][ss] - s[20][ss]) * 1.16943993343f;
t25 = (float)(s[12][ss] + s[19][ss]); t26 = (float)(s[12][ss] - s[19][ss]) * 1.48416461631f;
t27 = (float)(s[13][ss] + s[18][ss]); t28 = (float)(s[13][ss] - s[18][ss]) * 2.05778100995f;
t29 = (float)(s[14][ss] + s[17][ss]); t30 = (float)(s[14][ss] - s[17][ss]) * 3.40760841847f;
t31 = (float)(s[15][ss] + s[16][ss]); t32 = (float)(s[15][ss] - s[16][ss]) * 10.1900081235f;
t33 = t01 + t31; t31 = (t01 - t31) * 0.502419286188f;
t01 = t03 + t29; t29 = (t03 - t29) * 0.52249861494f;
t03 = t05 + t27; t27 = (t05 - t27) * 0.566944034816f;
t05 = t07 + t25; t25 = (t07 - t25) * 0.64682178336f;
t07 = t09 + t23; t23 = (t09 - t23) * 0.788154623451f;
t09 = t11 + t21; t21 = (t11 - t21) * 1.06067768599f;
t11 = t13 + t19; t19 = (t13 - t19) * 1.72244709824f;
t13 = t15 + t17; t17 = (t15 - t17) * 5.10114861869f;
t15 = t33 + t13; t13 = (t33 - t13) * 0.509795579104f;
t33 = t01 + t11; t01 = (t01 - t11) * 0.601344886935f;
t11 = t03 + t09; t09 = (t03 - t09) * 0.899976223136f;
t03 = t05 + t07; t07 = (t05 - t07) * 2.56291544774f;
t05 = t15 + t03; t15 = (t15 - t03) * 0.541196100146f;
t03 = t33 + t11; t11 = (t33 - t11) * 1.30656296488f;
t33 = t05 + t03; t05 = (t05 - t03) * 0.707106781187f;
t03 = t15 + t11; t15 = (t15 - t11) * 0.707106781187f;
t03 += t15;
t11 = t13 + t07; t13 = (t13 - t07) * 0.541196100146f;
t07 = t01 + t09; t09 = (t01 - t09) * 1.30656296488f;
t01 = t11 + t07; t07 = (t11 - t07) * 0.707106781187f;
t11 = t13 + t09; t13 = (t13 - t09) * 0.707106781187f;
t11 += t13; t01 += t11;
t11 += t07; t07 += t13;
t09 = t31 + t17; t31 = (t31 - t17) * 0.509795579104f;
t17 = t29 + t19; t29 = (t29 - t19) * 0.601344886935f;
t19 = t27 + t21; t21 = (t27 - t21) * 0.899976223136f;
t27 = t25 + t23; t23 = (t25 - t23) * 2.56291544774f;
t25 = t09 + t27; t09 = (t09 - t27) * 0.541196100146f;
t27 = t17 + t19; t19 = (t17 - t19) * 1.30656296488f;
t17 = t25 + t27; t27 = (t25 - t27) * 0.707106781187f;
t25 = t09 + t19; t19 = (t09 - t19) * 0.707106781187f;
t25 += t19;
t09 = t31 + t23; t31 = (t31 - t23) * 0.541196100146f;
t23 = t29 + t21; t21 = (t29 - t21) * 1.30656296488f;
t29 = t09 + t23; t23 = (t09 - t23) * 0.707106781187f;
t09 = t31 + t21; t31 = (t31 - t21) * 0.707106781187f;
t09 += t31; t29 += t09; t09 += t23; t23 += t31;
t17 += t29; t29 += t25; t25 += t09; t09 += t27;
t27 += t23; t23 += t19; t19 += t31;
t21 = t02 + t32; t02 = (t02 - t32) * 0.502419286188f;
t32 = t04 + t30; t04 = (t04 - t30) * 0.52249861494f;
t30 = t06 + t28; t28 = (t06 - t28) * 0.566944034816f;
t06 = t08 + t26; t08 = (t08 - t26) * 0.64682178336f;
t26 = t10 + t24; t10 = (t10 - t24) * 0.788154623451f;
t24 = t12 + t22; t22 = (t12 - t22) * 1.06067768599f;
t12 = t14 + t20; t20 = (t14 - t20) * 1.72244709824f;
t14 = t16 + t18; t16 = (t16 - t18) * 5.10114861869f;
t18 = t21 + t14; t14 = (t21 - t14) * 0.509795579104f;
t21 = t32 + t12; t32 = (t32 - t12) * 0.601344886935f;
t12 = t30 + t24; t24 = (t30 - t24) * 0.899976223136f;
t30 = t06 + t26; t26 = (t06 - t26) * 2.56291544774f;
t06 = t18 + t30; t18 = (t18 - t30) * 0.541196100146f;
t30 = t21 + t12; t12 = (t21 - t12) * 1.30656296488f;
t21 = t06 + t30; t30 = (t06 - t30) * 0.707106781187f;
t06 = t18 + t12; t12 = (t18 - t12) * 0.707106781187f;
t06 += t12;
t18 = t14 + t26; t26 = (t14 - t26) * 0.541196100146f;
t14 = t32 + t24; t24 = (t32 - t24) * 1.30656296488f;
t32 = t18 + t14; t14 = (t18 - t14) * 0.707106781187f;
t18 = t26 + t24; t24 = (t26 - t24) * 0.707106781187f;
t18 += t24; t32 += t18;
t18 += t14; t26 = t14 + t24;
t14 = t02 + t16; t02 = (t02 - t16) * 0.509795579104f;
t16 = t04 + t20; t04 = (t04 - t20) * 0.601344886935f;
t20 = t28 + t22; t22 = (t28 - t22) * 0.899976223136f;
t28 = t08 + t10; t10 = (t08 - t10) * 2.56291544774f;
t08 = t14 + t28; t14 = (t14 - t28) * 0.541196100146f;
t28 = t16 + t20; t20 = (t16 - t20) * 1.30656296488f;
t16 = t08 + t28; t28 = (t08 - t28) * 0.707106781187f;
t08 = t14 + t20; t20 = (t14 - t20) * 0.707106781187f;
t08 += t20;
t14 = t02 + t10; t02 = (t02 - t10) * 0.541196100146f;
t10 = t04 + t22; t22 = (t04 - t22) * 1.30656296488f;
t04 = t14 + t10; t10 = (t14 - t10) * 0.707106781187f;
t14 = t02 + t22; t02 = (t02 - t22) * 0.707106781187f;
t14 += t02; t04 += t14; t14 += t10; t10 += t02;
t16 += t04; t04 += t08; t08 += t14; t14 += t28;
t28 += t10; t10 += t20; t20 += t02; t21 += t16;
t16 += t32; t32 += t04; t04 += t06; t06 += t08;
t08 += t18; t18 += t14; t14 += t30; t30 += t28;
t28 += t26; t26 += t10; t10 += t12; t12 += t20;
t20 += t24; t24 += t02;
d[dp + 48] = -t33;
d[dp + 49] = d[dp + 47] = -t21;
d[dp + 50] = d[dp + 46] = -t17;
d[dp + 51] = d[dp + 45] = -t16;
d[dp + 52] = d[dp + 44] = -t01;
d[dp + 53] = d[dp + 43] = -t32;
d[dp + 54] = d[dp + 42] = -t29;
d[dp + 55] = d[dp + 41] = -t04;
d[dp + 56] = d[dp + 40] = -t03;
d[dp + 57] = d[dp + 39] = -t06;
d[dp + 58] = d[dp + 38] = -t25;
d[dp + 59] = d[dp + 37] = -t08;
d[dp + 60] = d[dp + 36] = -t11;
d[dp + 61] = d[dp + 35] = -t18;
d[dp + 62] = d[dp + 34] = -t09;
d[dp + 63] = d[dp + 33] = -t14;
d[dp + 32] = -t05;
d[dp + 0] = t05; d[dp + 31] = -t30;
d[dp + 1] = t30; d[dp + 30] = -t27;
d[dp + 2] = t27; d[dp + 29] = -t28;
d[dp + 3] = t28; d[dp + 28] = -t07;
d[dp + 4] = t07; d[dp + 27] = -t26;
d[dp + 5] = t26; d[dp + 26] = -t23;
d[dp + 6] = t23; d[dp + 25] = -t10;
d[dp + 7] = t10; d[dp + 24] = -t15;
d[dp + 8] = t15; d[dp + 23] = -t12;
d[dp + 9] = t12; d[dp + 22] = -t19;
d[dp + 10] = t19; d[dp + 21] = -t20;
d[dp + 11] = t20; d[dp + 20] = -t13;
d[dp + 12] = t13; d[dp + 19] = -t24;
d[dp + 13] = t24; d[dp + 18] = -t31;
d[dp + 14] = t31; d[dp + 17] = -t02;
d[dp + 15] = t02; d[dp + 16] = 0.0;
};

447
dsp/mpeg/mpeg.h Normal file
View file

@ -0,0 +1,447 @@
#ifndef COSMOPOLITAN_DSP_MPEG_MPEG_H_
#define COSMOPOLITAN_DSP_MPEG_MPEG_H_
#include "libc/stdio/stdio.h"
COSMOPOLITAN_C_START_
typedef struct plm_t plm_t;
typedef struct plm_buffer_t plm_buffer_t;
typedef struct plm_demux_t plm_demux_t;
typedef struct plm_video_t plm_video_t;
typedef struct plm_audio_t plm_audio_t;
/**
* Demuxed MPEG PS packet
*
* The type maps directly to the various MPEG-PES start codes. pts is
* the presentation time stamp of the packet in seconds. Not all packets
* have a pts value.
*/
typedef struct plm_packet_t {
int type;
double pts;
size_t length;
uint8_t *data;
} plm_packet_t;
/**
* Decoded Video Plane
*
* The byte length of the data is width * height. Note that different
* planes have different sizes: the Luma plane (Y) is double the size of
* each of the two Chroma planes (Cr, Cb) - i.e. 4 times the byte
* length. Also note that the size of the plane does *not* denote the
* size of the displayed frame. The sizes of planes are always rounded
* up to the nearest macroblock (16px).
*/
typedef struct plm_plane_t {
unsigned int width;
unsigned int height;
uint8_t *data;
} plm_plane_t;
/**
* Decoded Video Frame
*
* Width and height denote the desired display size of the frame. This
* may be different from the internal size of the 3 planes.
*/
typedef struct plm_frame_t {
double time;
unsigned int width;
unsigned int height;
plm_plane_t y;
plm_plane_t cr;
plm_plane_t cb;
} plm_frame_t;
/**
* Callback function type for decoded video frames used by the high-level
* plm_* interface
*/
typedef void (*plm_video_decode_callback)(plm_t *self, plm_frame_t *frame,
void *user);
/**
* Decoded Audio Samples
*
* Samples are stored as normalized (-1, 1) float either interleaved, or if
* PLM_AUDIO_SEPARATE_CHANNELS is defined, in two separate arrays.
* The `count` is always PLM_AUDIO_SAMPLES_PER_FRAME and just there for
* convenience.
*/
#define PLM_AUDIO_SAMPLES_PER_FRAME 1152
struct plm_samples_t {
double time;
unsigned int count;
#ifdef PLM_AUDIO_SEPARATE_CHANNELS
float left[PLM_AUDIO_SAMPLES_PER_FRAME] forcealign(32);
float right[PLM_AUDIO_SAMPLES_PER_FRAME] forcealign(32);
#else
float interleaved[PLM_AUDIO_SAMPLES_PER_FRAME * 2] forcealign(32);
#endif
} forcealign(32);
typedef struct plm_samples_t plm_samples_t;
/**
* Callback function type for decoded audio samples used by the high-level
* plm_* interface
*/
typedef void (*plm_audio_decode_callback)(plm_t *self, plm_samples_t *samples,
void *user);
/**
* Callback function for plm_buffer when it needs more data
*/
typedef void (*plm_buffer_load_callback)(plm_buffer_t *self, void *user);
/**
* -----------------------------------------------------------------------------
* plm_* public API
* High-Level API for loading/demuxing/decoding MPEG-PS data
*
* Create a plmpeg instance with a filename. Returns NULL if the file could not
* be opened.
*/
plm_t *plm_create_with_filename(const char *filename);
/**
* Create a plmpeg instance with file handle. Pass true to close_when_done
* to let plmpeg call fclose() on the handle when plm_destroy() is
* called.
*/
plm_t *plm_create_with_file(FILE *fh, int close_when_done);
/**
* Create a plmpeg instance with pointer to memory as source. This assumes the
* whole file is in memory. Pass true to free_when_done to let plmpeg call
* free() on the pointer when plm_destroy() is called.
*/
plm_t *plm_create_with_memory(uint8_t *bytes, size_t length,
int free_when_done);
/**
* Create a plmpeg instance with a plm_buffer as source. This is also
* called internally by all the above constructor functions.
*/
plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done);
/**
* Destroy a plmpeg instance and free all data
*/
void plm_destroy(plm_t *self);
/**
* Get or set whether video decoding is enabled.
*/
int plm_get_video_enabled(plm_t *self);
void plm_set_video_enabled(plm_t *self, int enabled);
/**
* Get or set whether audio decoding is enabled. When enabling, you can set the
* desired audio stream (0-3) to decode.
*/
int plm_get_audio_enabled(plm_t *self);
void plm_set_audio_enabled(plm_t *self, int enabled, int stream_index);
/**
* Get the display width/height of the video stream
*/
int plm_get_width(plm_t *self);
int plm_get_height(plm_t *self);
double plm_get_pixel_aspect_ratio(plm_t *);
/**
* Get the framerate of the video stream in frames per second
*/
double plm_get_framerate(plm_t *self);
/**
* Get the number of available audio streams in the file
*/
int plm_get_num_audio_streams(plm_t *self);
/**
* Get the samplerate of the audio stream in samples per second
*/
int plm_get_samplerate(plm_t *self);
/**
* Get or set the audio lead time in seconds - the time in which audio samples
* are decoded in advance (or behind) the video decode time. Default 0.
*/
double plm_get_audio_lead_time(plm_t *self);
void plm_set_audio_lead_time(plm_t *self, double lead_time);
/**
* Get the current internal time in seconds
*/
double plm_get_time(plm_t *self);
/**
* Rewind all buffers back to the beginning.
*/
void plm_rewind(plm_t *self);
/**
* Get or set looping. Default false.
*/
int plm_get_loop(plm_t *self);
void plm_set_loop(plm_t *self, int loop);
/**
* Get whether the file has ended. If looping is enabled, this will always
* return false.
*/
int plm_has_ended(plm_t *self);
/**
* Set the callback for decoded video frames used with plm_decode(). If no
* callback is set, video data will be ignored and not be decoded.
*/
void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp,
void *user);
/**
* Set the callback for decoded audio samples used with plm_decode(). If no
* callback is set, audio data will be ignored and not be decoded.
*/
void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp,
void *user);
/**
* Advance the internal timer by seconds and decode video/audio up to
* this time. Returns true/false whether anything was decoded.
*/
int plm_decode(plm_t *self, double seconds);
/**
* Decode and return one video frame. Returns NULL if no frame could be decoded
* (either because the source ended or data is corrupt). If you only want to
* decode video, you should disable audio via plm_set_audio_enabled().
* The returned plm_frame_t is valid until the next call to
* plm_decode_video call or until the plm_destroy is called.
*/
plm_frame_t *plm_decode_video(plm_t *self);
/**
* Decode and return one audio frame. Returns NULL if no frame could be decoded
* (either because the source ended or data is corrupt). If you only want to
* decode audio, you should disable video via plm_set_video_enabled().
* The returned plm_samples_t is valid until the next call to
* plm_decode_video or until the plm_destroy is called.
*/
plm_samples_t *plm_decode_audio(plm_t *self);
/* -----------------------------------------------------------------------------
* plm_buffer public API
* Provides the data source for all other plm_* interfaces
*
* The default size for buffers created from files or by the high-level API
*/
#ifndef PLM_BUFFER_DEFAULT_SIZE
#define PLM_BUFFER_DEFAULT_SIZE (128 * 1024)
#endif
/**
* Create a buffer instance with a filename. Returns NULL if the file could not
* be opened.
*/
plm_buffer_t *plm_buffer_create_with_filename(const char *filename);
/**
* Create a buffer instance with file handle. Pass true to close_when_done
* to let plmpeg call fclose() on the handle when plm_destroy() is
* called.
*/
plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done);
/**
* Create a buffer instance with a pointer to memory as source. This assumes
* the whole file is in memory. Pass 1 to free_when_done to let plmpeg call
* free() on the pointer when plm_destroy() is called.
*/
plm_buffer_t *plm_buffer_create_with_memory(uint8_t *bytes, size_t length,
int free_when_done);
/**
* Create an empty buffer with an initial capacity. The buffer will grow
* as needed.
*/
plm_buffer_t *plm_buffer_create_with_capacity(size_t capacity);
/**
* Destroy a buffer instance and free all data
*/
void plm_buffer_destroy(plm_buffer_t *self);
/**
* Copy data into the buffer. If the data to be written is larger than the
* available space, the buffer will realloc() with a larger capacity.
* Returns the number of bytes written. This will always be the same as the
* passed in length, except when the buffer was created _with_memory() for
* which _write() is forbidden.
*/
size_t plm_buffer_write(plm_buffer_t *self, uint8_t *bytes, size_t length);
/**
* Set a callback that is called whenever the buffer needs more data
*/
void plm_buffer_set_load_callback(plm_buffer_t *self,
plm_buffer_load_callback fp, void *user);
/**
* Rewind the buffer back to the beginning. When loading from a file handle,
* this also seeks to the beginning of the file.
*/
void plm_buffer_rewind(plm_buffer_t *self);
/**
* -----------------------------------------------------------------------------
* plm_demux public API
* Demux an MPEG Program Stream (PS) data into separate packages
*
* Various Packet Types
*/
#define PLM_DEMUX_PACKET_PRIVATE 0xBD
#define PLM_DEMUX_PACKET_AUDIO_1 0xC0
#define PLM_DEMUX_PACKET_AUDIO_2 0xC1
#define PLM_DEMUX_PACKET_AUDIO_3 0xC2
#define PLM_DEMUX_PACKET_AUDIO_4 0xC2
#define PLM_DEMUX_PACKET_VIDEO_1 0xE0
/**
* Create a demuxer with a plm_buffer as source
*/
plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done);
/**
* Destroy a demuxer and free all data
*/
void plm_demux_destroy(plm_demux_t *self);
/**
* Returns the number of video streams found in the system header.
*/
int plm_demux_get_num_video_streams(plm_demux_t *self);
/**
* Returns the number of audio streams found in the system header.
*/
int plm_demux_get_num_audio_streams(plm_demux_t *self);
/**
* Rewinds the internal buffer. See plm_buffer_rewind().
*/
void plm_demux_rewind(plm_demux_t *self);
/**
* Decode and return the next packet. The returned packet_t is valid until
* the next call to plm_demux_decode() or until the demuxer is destroyed.
*/
plm_packet_t *plm_demux_decode(plm_demux_t *self);
/* -----------------------------------------------------------------------------
* plm_video public API
* Decode MPEG1 Video ("mpeg1") data into raw YCrCb frames
*/
/**
* Create a video decoder with a plm_buffer as source
*/
plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer,
int destroy_when_done);
/**
* Destroy a video decoder and free all data
*/
void plm_video_destroy(plm_video_t *self);
/**
* Get the framerate in frames per second
*/
double plm_video_get_framerate(plm_video_t *);
double plm_video_get_pixel_aspect_ratio(plm_video_t *);
/**
* Get the display width/height
*/
int plm_video_get_width(plm_video_t *);
int plm_video_get_height(plm_video_t *);
/**
* Set "no delay" mode. When enabled, the decoder assumes that the video does
* *not* contain any B-Frames. This is useful for reducing lag when streaming.
*/
void plm_video_set_no_delay(plm_video_t *self, int no_delay);
/**
* Get the current internal time in seconds
*/
double plm_video_get_time(plm_video_t *self);
/**
* Rewinds the internal buffer. See plm_buffer_rewind().
*/
void plm_video_rewind(plm_video_t *self);
/**
* Decode and return one frame of video and advance the internal time by
* 1/framerate seconds. The returned frame_t is valid until the next call of
* plm_video_decode() or until the video decoder is destroyed.
*/
plm_frame_t *plm_video_decode(plm_video_t *self);
/**
* Convert the YCrCb data of a frame into an interleaved RGB buffer. The buffer
* pointed to by *rgb must have a size of (frame->width * frame->height * 3)
* bytes.
*/
void plm_frame_to_rgb(plm_frame_t *frame, uint8_t *rgb);
/* -----------------------------------------------------------------------------
* plm_audio public API
* Decode MPEG-1 Audio Layer II ("mp2") data into raw samples
*/
/**
* Create an audio decoder with a plm_buffer as source.
*/
plm_audio_t *plm_audio_create_with_buffer(plm_buffer_t *buffer,
int destroy_when_done);
/**
* Destroy an audio decoder and free all data
*/
void plm_audio_destroy(plm_audio_t *self);
/**
* Get the samplerate in samples per second
*/
int plm_audio_get_samplerate(plm_audio_t *self);
/**
* Get the current internal time in seconds
*/
double plm_audio_get_time(plm_audio_t *self);
/**
* Rewinds the internal buffer. See plm_buffer_rewind().
*/
void plm_audio_rewind(plm_audio_t *self);
/**
* Decode and return one "frame" of audio and advance the internal time by
* (PLM_AUDIO_SAMPLES_PER_FRAME/samplerate) seconds. The returned samples_t
* is valid until the next call of plm_audio_decode() or until the audio
* decoder is destroyed.
*/
plm_samples_t *plm_audio_decode(plm_audio_t *self);
extern long plmpegdecode_latency_;
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_DSP_MPEG_MPEG_H_ */

1110
dsp/mpeg/mpeg1.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,3 @@ __notice(pl_mpeg_notice, "\
PL_MPEG (MIT License)\n\
Copyright(c) 2019 Dominic Szablewski\n\
https://phoboslab.org");
long plmpegdecode_latency_;
#define PL_MPEG_IMPLEMENTATION
#include "pl_mpeg.h"

File diff suppressed because it is too large Load diff

332
dsp/mpeg/plm.c Normal file
View file

@ -0,0 +1,332 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org │
The MIT License(MIT)
Copyright(c) 2019 Dominic Szablewski
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.
*/
#include "dsp/mpeg/mpeg.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
__static_yoink("pl_mpeg_notice");
/* clang-format off */
// -----------------------------------------------------------------------------
// plm (high-level interface) implementation
typedef struct plm_t {
plm_demux_t *demux;
double time;
int has_ended;
int loop;
int video_packet_type;
plm_buffer_t *video_buffer;
plm_video_t *video_decoder;
int audio_packet_type;
double audio_lead_time;
plm_buffer_t *audio_buffer;
plm_audio_t *audio_decoder;
plm_video_decode_callback video_decode_callback;
void *video_decode_callback_user_data;
plm_audio_decode_callback audio_decode_callback;
void *audio_decode_callback_user_data;
} plm_t;
void plm_handle_end(plm_t *self);
void plm_read_video_packet(plm_buffer_t *buffer, void *user);
void plm_read_audio_packet(plm_buffer_t *buffer, void *user);
void plm_read_packets(plm_t *self, int requested_type);
plm_t *plm_create_with_filename(const char *filename) {
plm_buffer_t *buffer = plm_buffer_create_with_filename(filename);
if (!buffer) {
return NULL;
}
return plm_create_with_buffer(buffer, true);
}
plm_t *plm_create_with_file(FILE *fh, int close_when_done) {
plm_buffer_t *buffer = plm_buffer_create_with_file(fh, close_when_done);
return plm_create_with_buffer(buffer, true);
}
plm_t *plm_create_with_memory(uint8_t *bytes, size_t length, int free_when_done) {
plm_buffer_t *buffer = plm_buffer_create_with_memory(bytes, length, free_when_done);
return plm_create_with_buffer(buffer, true);
}
plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done) {
plm_t *self = (plm_t *)malloc(sizeof(plm_t));
memset(self, 0, sizeof(plm_t));
self->demux = plm_demux_create(buffer, destroy_when_done);
// In theory we should check plm_demux_get_num_video_streams() and
// plm_demux_get_num_audio_streams() here, but older files typically
// do not specify these correctly. So we just assume we have a video and
// audio stream and create the decoders.
self->video_packet_type = PLM_DEMUX_PACKET_VIDEO_1;
self->video_buffer = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE);
plm_buffer_set_load_callback(self->video_buffer, plm_read_video_packet, self);
self->audio_packet_type = PLM_DEMUX_PACKET_AUDIO_1;
self->audio_buffer = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE);
plm_buffer_set_load_callback(self->audio_buffer, plm_read_audio_packet, self);
self->video_decoder = plm_video_create_with_buffer(self->video_buffer, true);
self->audio_decoder = plm_audio_create_with_buffer(self->audio_buffer, true);
return self;
}
void plm_destroy(plm_t *self) {
plm_video_destroy(self->video_decoder);
plm_audio_destroy(self->audio_decoder);
plm_demux_destroy(self->demux);
free(self);
}
int plm_get_audio_enabled(plm_t *self) {
return (self->audio_packet_type != 0);
}
void plm_set_audio_enabled(plm_t *self, int enabled, int stream_index) {
/* int num_streams = plm_demux_get_num_audio_streams(self->demux); */
self->audio_packet_type = (enabled && stream_index >= 0 && stream_index < 4)
? PLM_DEMUX_PACKET_AUDIO_1 + stream_index
: 0;
}
int plm_get_video_enabled(plm_t *self) {
return (self->video_packet_type != 0);
}
void plm_set_video_enabled(plm_t *self, int enabled) {
self->video_packet_type = (enabled)
? PLM_DEMUX_PACKET_VIDEO_1
: 0;
}
int plm_get_width(plm_t *self) {
return plm_video_get_width(self->video_decoder);
}
double plm_get_pixel_aspect_ratio(plm_t *self) {
return plm_video_get_pixel_aspect_ratio(self->video_decoder);
}
int plm_get_height(plm_t *self) {
return plm_video_get_height(self->video_decoder);
}
double plm_get_framerate(plm_t *self) {
return plm_video_get_framerate(self->video_decoder);
}
int plm_get_num_audio_streams(plm_t *self) {
// Some files do not specify the number of audio streams in the system header.
// If the reported number of streams is 0, we check if we have a samplerate,
// indicating at least one audio stream.
int num_streams = plm_demux_get_num_audio_streams(self->demux);
return num_streams == 0 && plm_get_samplerate(self) ? 1 : num_streams;
}
int plm_get_samplerate(plm_t *self) {
return plm_audio_get_samplerate(self->audio_decoder);
}
double plm_get_audio_lead_time(plm_t *self) {
return self->audio_lead_time;
}
void plm_set_audio_lead_time(plm_t *self, double lead_time) {
self->audio_lead_time = lead_time;
}
double plm_get_time(plm_t *self) {
return self->time;
}
void plm_rewind(plm_t *self) {
plm_video_rewind(self->video_decoder);
plm_audio_rewind(self->audio_decoder);
plm_demux_rewind(self->demux);
self->time = 0;
}
int plm_get_loop(plm_t *self) {
return self->loop;
}
void plm_set_loop(plm_t *self, int loop) {
self->loop = loop;
}
int plm_has_ended(plm_t *self) {
return self->has_ended;
}
void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp, void *user) {
self->video_decode_callback = fp;
self->video_decode_callback_user_data = user;
}
void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp, void *user) {
self->audio_decode_callback = fp;
self->audio_decode_callback_user_data = user;
}
int plm_decode(plm_t *self, double tick) {
DEBUGF("%s", "plm_decode");
int decode_video = (self->video_decode_callback && self->video_packet_type);
int decode_audio = (self->audio_decode_callback && self->audio_packet_type);
if (!decode_video && !decode_audio) {
// Nothing to do here
return false;
}
int did_decode = false;
int video_ended = false;
int audio_ended = false;
double video_target_time = self->time + tick;
double audio_target_time = self->time + tick;
if (self->audio_lead_time > 0 && decode_audio) {
video_target_time -= self->audio_lead_time;
}
else {
audio_target_time -= self->audio_lead_time;
}
do {
did_decode = false;
if (decode_video && plm_video_get_time(self->video_decoder) < video_target_time) {
plm_frame_t *frame = plm_video_decode(self->video_decoder);
if (frame) {
self->video_decode_callback(self, frame, self->video_decode_callback_user_data);
did_decode = true;
}
else {
video_ended = true;
}
}
if (decode_audio && plm_audio_get_time(self->audio_decoder) < audio_target_time) {
plm_samples_t *samples = plm_audio_decode(self->audio_decoder);
if (samples) {
self->audio_decode_callback(self, samples, self->audio_decode_callback_user_data);
did_decode = true;
}
else {
audio_ended = true;
}
}
} while (did_decode);
// We wanted to decode something but failed -> the source must have ended
if ((!decode_video || video_ended) && (!decode_audio || audio_ended)) {
plm_handle_end(self);
}
else {
self->time += tick;
}
return did_decode ? true : false;
}
plm_frame_t *plm_decode_video(plm_t *self) {
if (!self->video_packet_type) {
return NULL;
}
plm_frame_t *frame = plm_video_decode(self->video_decoder);
if (frame) {
self->time = frame->time;
}
else {
plm_handle_end(self);
}
return frame;
}
plm_samples_t *plm_decode_audio(plm_t *self) {
if (!self->audio_packet_type) {
return NULL;
}
plm_samples_t *samples = plm_audio_decode(self->audio_decoder);
if (samples) {
self->time = samples->time;
}
else {
plm_handle_end(self);
}
return samples;
}
void plm_handle_end(plm_t *self) {
if (self->loop) {
plm_rewind(self);
}
else {
self->has_ended = true;
}
}
void plm_read_video_packet(plm_buffer_t *buffer, void *user) {
plm_t *self = (plm_t *)user;
plm_read_packets(self, self->video_packet_type);
}
void plm_read_audio_packet(plm_buffer_t *buffer, void *user) {
plm_t *self = (plm_t *)user;
plm_read_packets(self, self->audio_packet_type);
}
void plm_read_packets(plm_t *self, int requested_type) {
plm_packet_t *packet;
while ((packet = plm_demux_decode(self->demux))) {
if (packet->type == self->video_packet_type) {
plm_buffer_write(self->video_buffer, packet->data, packet->length);
}
else if (packet->type == self->audio_packet_type) {
plm_buffer_write(self->audio_buffer, packet->data, packet->length);
}
if (packet->type == requested_type) {
return;
}
}
}

83
dsp/mpeg/slowrgb.c Normal file
View file

@ -0,0 +1,83 @@
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│
vi: set et ft=c ts=4 sw=4 fenc=utf-8 :vi
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org │
The MIT License(MIT)
Copyright(c) 2019 Dominic Szablewski
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.
*/
#include "dsp/mpeg/mpeg.h"
#include "libc/macros.h"
__static_yoink("pl_mpeg_notice");
/**
* @see YCbCr2RGB() in tool/viz/lib/ycbcr2rgb.c
*/
void plm_frame_to_rgb(plm_frame_t *frame, uint8_t *rgb) {
// Chroma values are the same for each block of 4 pixels, so we process
// 2 lines at a time, 2 neighboring pixels each.
int w = frame->y.width, w2 = w >> 1;
int y_index1 = 0, y_index2 = w, y_next_2_lines = w + (w - frame->width);
int c_index = 0, c_next_line = w2 - (frame->width >> 1);
int rgb_index1 = 0, rgb_index2 = frame->width * 3,
rgb_next_2_lines = frame->width * 3;
int cols = frame->width >> 1, rows = frame->height >> 1;
int ccb, ccr, r, g, b;
uint8_t *y = frame->y.data, *cb = frame->cb.data, *cr = frame->cr.data;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
ccb = cb[c_index];
ccr = cr[c_index];
c_index++;
r = (ccr + ((ccr * 103) >> 8)) - 179;
g = ((ccb * 88) >> 8) - 44 + ((ccr * 183) >> 8) - 91;
b = (ccb + ((ccb * 198) >> 8)) - 227;
// Line 1
int y1 = y[y_index1++];
int y2 = y[y_index1++];
rgb[rgb_index1 + 0] = MAX(0, MIN(255, y1 + r));
rgb[rgb_index1 + 1] = MAX(0, MIN(255, y1 - g));
rgb[rgb_index1 + 2] = MAX(0, MIN(255, y1 + b));
rgb[rgb_index1 + 3] = MAX(0, MIN(255, y2 + r));
rgb[rgb_index1 + 4] = MAX(0, MIN(255, y2 - g));
rgb[rgb_index1 + 5] = MAX(0, MIN(255, y2 + b));
rgb_index1 += 6;
// Line 2
int y3 = y[y_index2++];
int y4 = y[y_index2++];
rgb[rgb_index2 + 0] = MAX(0, MIN(255, y3 + r));
rgb[rgb_index2 + 1] = MAX(0, MIN(255, y3 - g));
rgb[rgb_index2 + 2] = MAX(0, MIN(255, y3 + b));
rgb[rgb_index2 + 3] = MAX(0, MIN(255, y4 + r));
rgb[rgb_index2 + 4] = MAX(0, MIN(255, y4 - g));
rgb[rgb_index2 + 5] = MAX(0, MIN(255, y4 + b));
rgb_index2 += 6;
}
y_index1 += y_next_2_lines;
y_index2 += y_next_2_lines;
rgb_index1 += rgb_next_2_lines;
rgb_index2 += rgb_next_2_lines;
c_index += c_next_line;
}
}

60
dsp/mpeg/video.h Normal file
View file

@ -0,0 +1,60 @@
#ifndef COSMOPOLITAN_DSP_MPEG_VIDEO_H_
#define COSMOPOLITAN_DSP_MPEG_VIDEO_H_
#include "dsp/mpeg/mpeg.h"
COSMOPOLITAN_C_START_
typedef struct {
int full_px;
int is_set;
int r_size;
int h;
int v;
} plm_video_motion_t;
typedef struct plm_video_t {
double framerate;
double time;
double pixel_aspect_ratio;
int frames_decoded;
int width;
int height;
int mb_width;
int mb_height;
int mb_size;
int luma_width;
int luma_height;
int chroma_width;
int chroma_height;
int start_code;
int picture_type;
plm_video_motion_t motion_forward;
plm_video_motion_t motion_backward;
int has_sequence_header;
int quantizer_scale;
int slice_begin;
int macroblock_address;
int mb_row;
int mb_col;
int macroblock_type;
int macroblock_intra;
int dc_predictor[3];
plm_buffer_t *buffer;
int destroy_buffer_when_done;
plm_frame_t frame_current;
plm_frame_t frame_forward;
plm_frame_t frame_backward;
uint8_t *frames_data;
int block_data[64];
uint8_t intra_quant_matrix[64];
uint8_t non_intra_quant_matrix[64];
int has_reference_frame;
int assume_no_b_frames;
} plm_video_t;
void plm_video_process_macroblock_8(plm_video_t *, uint8_t *, uint8_t *, int,
int, bool);
void plm_video_process_macroblock_16(plm_video_t *, uint8_t *, uint8_t *, int,
int, bool);
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_DSP_MPEG_VIDEO_H_ */

View file

@ -1,43 +0,0 @@
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
PKGS += DSP_PROG
DSP_PROG_FILES := $(wildcard dsp/prog/*)
DSP_PROG_HDRS = $(filter %.h,$(DSP_PROG_FILES))
DSP_PROG_SRCS = $(filter %.c,$(DSP_PROG_FILES))
DSP_PROG_OBJS = $(DSP_PROG_SRCS:%.c=o/$(MODE)/%.o)
DSP_PROG_COMS = $(DSP_PROG_SRCS:%.c=o/$(MODE)/%)
DSP_PROG_BINS = $(DSP_PROG_COMS) $(DSP_PROG_COMS:%=%.dbg)
DSP_PROG_DIRECTDEPS = \
DSP_AUDIO \
LIBC_CALLS \
LIBC_INTRIN \
LIBC_NEXGEN32E \
LIBC_RUNTIME \
LIBC_SOCK \
LIBC_STDIO \
LIBC_SYSV \
LIBC_TINYMATH \
THIRD_PARTY_MUSL \
DSP_PROG_DEPS := \
$(call uniq,$(foreach x,$(DSP_PROG_DIRECTDEPS),$($(x))))
o/$(MODE)/dsp/prog/prog.pkg: \
$(DSP_PROG_OBJS) \
$(foreach x,$(DSP_PROG_DIRECTDEPS),$($(x)_A).pkg)
o/$(MODE)/dsp/prog/%.dbg: \
$(DSP_PROG_DEPS) \
o/$(MODE)/dsp/prog/prog.pkg \
o/$(MODE)/dsp/prog/%.o \
$(CRT) \
$(APE_NO_MODIFY_SELF)
@$(APELINK)
$(DSP_PROG_OBJS): dsp/prog/BUILD.mk
.PHONY: o/$(MODE)/dsp/prog
o/$(MODE)/dsp/prog: $(DSP_PROG_BINS)

View file

@ -1,41 +0,0 @@
#ifndef COSMOPOLITAN_DSP_PROG_LOUDNESS_H_
#define COSMOPOLITAN_DSP_PROG_LOUDNESS_H_
#include <math.h>
#include <stdio.h>
#define MIN_DECIBEL -60
#define MAX_DECIBEL 0
// computes root of mean squares
static double rms(float *p, int n) {
double s = 0;
for (int i = 0; i < n; ++i)
s += p[i] * p[i];
return sqrt(s / n);
}
// converts rms to decibel
static double rms_to_db(double rms) {
double db = 20 * log10(rms);
db = fmin(db, MAX_DECIBEL);
db = fmax(db, MIN_DECIBEL);
return db;
}
// char meter[21];
// format_decibel_meter(meter, 20, rms_to_db(rms(samps, count)))
static char *format_decibel_meter(char *meter, int width, double db) {
double range = MAX_DECIBEL - MIN_DECIBEL;
int filled = (db - MIN_DECIBEL) / range * width;
for (int i = 0; i < width; ++i) {
if (i < filled) {
meter[i] = '=';
} else {
meter[i] = ' ';
}
}
meter[width] = 0;
return meter;
}
#endif /* COSMOPOLITAN_DSP_PROG_LOUDNESS_H_ */

View file

@ -1,127 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <arpa/inet.h>
#include <assert.h>
#include <cosmoaudio.h>
#include <errno.h>
#include <math.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include "loudness.h"
/**
* @fileoverview plays audio from remote computer on speaker
* @see dsp/prog/sendaudio.c
*/
#define SAMPLING_RATE 44100
#define FRAMES_PER_SECOND 60
#define DEBUG_LOG 0
#define PORT 9834
#define CHUNK_FRAMES (SAMPLING_RATE / FRAMES_PER_SECOND)
static_assert(CHUNK_FRAMES * sizeof(short) < 1472,
"audio chunks won't fit in udp ethernet packet");
sig_atomic_t g_done;
void onsig(int sig) {
g_done = 1;
}
short toshort(float x) {
return fmaxf(-1, fminf(1, x)) * 32767;
}
float tofloat(short x) {
return x / 32768.f;
}
int main(int argc, char* argv[]) {
// listen on udp port for audio
int server;
if ((server = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
return 3;
}
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)};
if (bind(server, (struct sockaddr*)&addr, sizeof(addr))) {
perror("bind");
return 4;
}
// setup signals
struct sigaction sa;
sa.sa_flags = 0;
sa.sa_handler = onsig;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, 0);
// configure cosmo audio
struct CosmoAudioOpenOptions cao = {0};
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
cao.deviceType = kCosmoAudioDeviceTypePlayback;
cao.sampleRate = SAMPLING_RATE;
cao.bufferFrames = CHUNK_FRAMES * 2;
cao.debugLog = DEBUG_LOG;
cao.channels = 1;
// connect to microphone and speaker
int status;
struct CosmoAudio* ca;
status = cosmoaudio_open(&ca, &cao);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to open audio: %d\n", status);
return 5;
}
while (!g_done) {
// read from network
ssize_t got;
short buf16[CHUNK_FRAMES];
if ((got = read(server, buf16, CHUNK_FRAMES * sizeof(short))) == -1) {
if (errno == EINTR)
continue;
perror("read");
return 7;
}
if (got != CHUNK_FRAMES * sizeof(short)) {
fprintf(stderr, "warning: got partial audio frame\n");
continue;
}
// write to speaker
float buf32[CHUNK_FRAMES];
for (int i = 0; i < CHUNK_FRAMES; ++i)
buf32[i] = tofloat(buf16[i]);
cosmoaudio_poll(ca, 0, (int[]){CHUNK_FRAMES});
cosmoaudio_write(ca, buf32, CHUNK_FRAMES);
// print loudness in ascii
char meter[21];
double db = rms_to_db(rms(buf32, CHUNK_FRAMES));
format_decibel_meter(meter, 20, db);
printf("\r%s| %+6.2f dB", meter, db);
fflush(stdout);
}
// clean up resources
cosmoaudio_flush(ca);
cosmoaudio_close(ca);
close(server);
}

View file

@ -1,149 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <arpa/inet.h>
#include <assert.h>
#include <cosmoaudio.h>
#include <errno.h>
#include <math.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include "loudness.h"
/**
* @fileoverview sends audio from microphone to remote computer
* @see dsp/prog/recvaudio.c
*/
#define SAMPLING_RATE 44100
#define FRAMES_PER_SECOND 60
#define DEBUG_LOG 0
#define PORT 9834
#define CHUNK_FRAMES (SAMPLING_RATE / FRAMES_PER_SECOND)
static_assert(CHUNK_FRAMES * sizeof(short) < 1472,
"audio chunks won't fit in udp ethernet packet");
sig_atomic_t g_done;
void onsig(int sig) {
g_done = 1;
}
short toshort(float x) {
return fmaxf(-1, fminf(1, x)) * 32767;
}
float tofloat(short x) {
return x / 32768.f;
}
uint32_t host2ip(const char* host) {
uint32_t ip;
if ((ip = inet_addr(host)) != -1u)
return ip;
int rc;
struct addrinfo* ai = NULL;
struct addrinfo hint = {AI_NUMERICSERV, AF_INET, SOCK_STREAM, IPPROTO_TCP};
if ((rc = getaddrinfo(host, "0", &hint, &ai))) {
fprintf(stderr, "%s: %s\n", host, gai_strerror(rc));
exit(50 + rc);
}
ip = ntohl(((struct sockaddr_in*)ai->ai_addr)->sin_addr.s_addr);
freeaddrinfo(ai);
return ip;
}
int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr, "%s: missing host argument\n", argv[0]);
return 1;
}
// get host argument
const char* remote_host = argv[1];
uint32_t ip = host2ip(remote_host);
// connect to server
int client;
if ((client = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror(remote_host);
return 3;
}
struct sockaddr_in addr = {.sin_family = AF_INET,
.sin_port = htons(PORT),
.sin_addr.s_addr = htonl(ip)};
if (connect(client, (struct sockaddr*)&addr, sizeof(addr))) {
perror(remote_host);
return 4;
}
// setup signals
struct sigaction sa;
sa.sa_flags = 0;
sa.sa_handler = onsig;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, 0);
// configure cosmo audio
struct CosmoAudioOpenOptions cao = {0};
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
cao.deviceType = kCosmoAudioDeviceTypeCapture;
cao.sampleRate = SAMPLING_RATE;
cao.bufferFrames = CHUNK_FRAMES * 2;
cao.debugLog = DEBUG_LOG;
cao.channels = 1;
// connect to microphone and speaker
int status;
struct CosmoAudio* ca;
status = cosmoaudio_open(&ca, &cao);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to open audio: %d\n", status);
return 5;
}
while (!g_done) {
// read from microphone
float buf32[CHUNK_FRAMES];
cosmoaudio_poll(ca, (int[]){CHUNK_FRAMES}, 0);
cosmoaudio_read(ca, buf32, CHUNK_FRAMES);
short buf16[CHUNK_FRAMES];
for (int i = 0; i < CHUNK_FRAMES; ++i)
buf16[i] = toshort(buf32[i]);
// send to server
if (write(client, buf16, CHUNK_FRAMES * sizeof(short)) == -1) {
if (errno == EINTR && g_done)
break;
perror(remote_host);
return 7;
}
// print loudness in ascii
char meter[21];
double db = rms_to_db(rms(buf32, CHUNK_FRAMES));
format_decibel_meter(meter, 20, db);
printf("\r%s| %+6.2f dB", meter, db);
fflush(stdout);
}
// clean up resources
cosmoaudio_close(ca);
close(client);
}

View file

@ -45,12 +45,6 @@ $(DSP_SCALE_A).pkg: \
$(DSP_SCALE_A_OBJS) \
$(foreach x,$(DSP_SCALE_A_DIRECTDEPS),$($(x)_A).pkg)
ifeq ($(ARCH),x86_64)
o/$(MODE)/dsp/scale/cdecimate2xuint8x8.o: private \
CFLAGS += \
-mssse3
endif
o/$(MODE)/dsp/scale/cdecimate2xuint8x8.o \
o/$(MODE)/dsp/scale/gyarados.o \
o/$(MODE)/dsp/scale/magikarp.o \

View file

@ -16,20 +16,17 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/macros.h"
#include "libc/assert.h"
#include "libc/intrin/packuswb.h"
#include "libc/intrin/paddw.h"
#include "libc/intrin/palignr.h"
#include "libc/intrin/pmaddubsw.h"
#include "libc/intrin/psraw.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/nexgen32e/x86feature.h"
#include "libc/str/str.h"
#include "third_party/intel/immintrin.internal.h"
/**
* Performs 2D Motion Picture Convolution Acceleration by Leveraging SSSE3.
*
* @note H/T John Costella, Jean-Baptiste Joseph Fourier
* @note RIP Huixiang Chen
*/
void *cDecimate2xUint8x8(unsigned long n, unsigned char A[n],
const signed char K[8]) {
#ifdef __x86_64__
#define TAPS 8
#define RATIO 2
#define OFFSET 3
@ -40,107 +37,62 @@ void *cDecimate2xUint8x8(unsigned long n, unsigned char A[n],
#define LOOKAHEAD (SPREAD - LOOKBEHIND)
#define SCALE 5
#define ROUND (1 << (SCALE - 1))
__m128i kRound = _mm_set1_epi16(ROUND);
__m128i kMadd1 = _mm_set_epi8(K[1], K[0], K[1], K[0], K[1], K[0], K[1], K[0],
K[1], K[0], K[1], K[0], K[1], K[0], K[1], K[0]);
__m128i kMadd2 = _mm_set_epi8(K[3], K[2], K[3], K[2], K[3], K[2], K[3], K[2],
K[3], K[2], K[3], K[2], K[3], K[2], K[3], K[2]);
__m128i kMadd3 = _mm_set_epi8(K[5], K[4], K[5], K[4], K[5], K[4], K[5], K[4],
K[5], K[4], K[5], K[4], K[5], K[4], K[5], K[4]);
__m128i kMadd4 = _mm_set_epi8(K[7], K[6], K[7], K[6], K[7], K[6], K[7], K[6],
K[7], K[6], K[7], K[6], K[7], K[6], K[7], K[6]);
__m128i bv0, bv1, bv2, bv3;
__m128i in1, in2, in3;
__m128i wv0, wv1, wv2, wv3;
/**
* Performs 2D Motion Picture Convolution Acceleration by Leveraging SSSE3.
*
* @note H/T John Costella, Jean-Baptiste Joseph Fourier
* @note RIP Huixiang Chen
*/
void *cDecimate2xUint8x8(unsigned long n, unsigned char A[n],
const signed char K[8]) {
short kRound[8] = {ROUND, ROUND, ROUND, ROUND, ROUND, ROUND, ROUND, ROUND};
signed char kMadd1[16] = {K[0], K[1], K[0], K[1], K[0], K[1], K[0], K[1],
K[0], K[1], K[0], K[1], K[0], K[1], K[0], K[1]};
signed char kMadd2[16] = {K[2], K[3], K[2], K[3], K[2], K[3], K[2], K[3],
K[2], K[3], K[2], K[3], K[2], K[3], K[2], K[3]};
signed char kMadd3[16] = {K[4], K[5], K[4], K[5], K[4], K[5], K[4], K[5],
K[4], K[5], K[4], K[5], K[4], K[5], K[4], K[5]};
signed char kMadd4[16] = {K[6], K[7], K[6], K[7], K[6], K[7], K[6], K[7],
K[6], K[7], K[6], K[7], K[6], K[7], K[6], K[7]};
unsigned char bv0[16], bv1[16], bv2[16], bv3[16];
unsigned char in1[16], in2[16], in3[16];
short wv0[8], wv1[8], wv2[8], wv3[8];
unsigned long i, j, w;
if (n >= STRIDE) {
i = 0;
w = (n + RATIO / 2) / RATIO;
in1 = _mm_set1_epi8(A[0]);
in2 = _mm_set1_epi8(A[n - 1]);
_mm_storeu_si128((__m128i *)&in2, _mm_loadu_si128((__m128i *)A));
memset(in1, A[0], sizeof(in1));
memset(in2, A[n - 1], 16);
memcpy(in2, A, MIN(16, n));
for (; i < w; i += STRIDE) {
j = i * RATIO + 16;
if (j + 16 <= n) {
in3 = _mm_loadu_si128((__m128i *)&A[j]);
memcpy(in3, &A[j], 16);
} else {
in3 = _mm_set1_epi8(A[n - 1]);
memset(in3, A[n - 1], 16);
if (j < n) {
// SSSE3-compatible way to handle partial loads
__m128i mask = _mm_loadu_si128((__m128i *)&A[j]);
__m128i shuffle_mask = _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7,
6, 5, 4, 3, 2, 1, 0);
__m128i index = _mm_set1_epi8(n - j);
__m128i cmp = _mm_cmplt_epi8(shuffle_mask, index);
in3 = _mm_or_si128(_mm_and_si128(cmp, mask),
_mm_andnot_si128(cmp, in3));
memcpy(in3, &A[j], n - j);
}
}
bv0 = _mm_alignr_epi8(in2, in1, 13);
bv1 = _mm_alignr_epi8(in2, in1, 15);
bv2 = _mm_alignr_epi8(in3, in2, 1);
bv3 = _mm_alignr_epi8(in3, in2, 3);
wv0 = _mm_maddubs_epi16(bv0, kMadd1);
wv1 = _mm_maddubs_epi16(bv1, kMadd2);
wv2 = _mm_maddubs_epi16(bv2, kMadd3);
wv3 = _mm_maddubs_epi16(bv3, kMadd4);
wv0 = _mm_add_epi16(wv0, kRound);
wv0 = _mm_add_epi16(wv0, wv1);
wv0 = _mm_add_epi16(wv0, wv2);
wv0 = _mm_add_epi16(wv0, wv3);
wv0 = _mm_srai_epi16(wv0, SCALE);
bv2 = _mm_packus_epi16(wv0, wv0);
_mm_storel_epi64((__m128i *)&A[i], bv2);
in1 = in2;
in2 = in3;
palignr(bv0, in2, in1, 13);
palignr(bv1, in2, in1, 15);
palignr(bv2, in3, in2, 1);
palignr(bv3, in3, in2, 3);
pmaddubsw(wv0, bv0, kMadd1);
pmaddubsw(wv1, bv1, kMadd2);
pmaddubsw(wv2, bv2, kMadd3);
pmaddubsw(wv3, bv3, kMadd4);
paddw(wv0, wv0, kRound);
paddw(wv0, wv0, wv1);
paddw(wv0, wv0, wv2);
paddw(wv0, wv0, wv3);
psraw(wv0, wv0, SCALE);
packuswb(bv2, wv0, wv0);
memcpy(&A[i], bv2, STRIDE);
memcpy(in1, in2, 16);
memcpy(in2, in3, 16);
}
}
return A;
#else
long h, i;
if (n < 2)
return A;
unsigned char M[3 + n + 4];
unsigned char *q = M;
q[0] = A[0];
q[1] = A[0];
q[2] = A[0];
memcpy(q + 3, A, n);
q[3 + n + 0] = A[n - 1];
q[3 + n + 1] = A[n - 1];
q[3 + n + 2] = A[n - 1];
q[3 + n + 3] = A[n - 1];
q += 3;
h = (n + 1) >> 1;
for (i = 0; i < h; ++i) {
short x0, x1, x2, x3, x4, x5, x6, x7;
x0 = q[i * 2 - 3];
x1 = q[i * 2 - 2];
x2 = q[i * 2 - 1];
x3 = q[i * 2 + 0];
x4 = q[i * 2 + 1];
x5 = q[i * 2 + 2];
x6 = q[i * 2 + 3];
x7 = q[i * 2 + 4];
x0 *= K[0];
x1 *= K[1];
x2 *= K[2];
x3 *= K[3];
x4 *= K[4];
x5 *= K[5];
x6 *= K[6];
x7 *= K[7];
x0 += x1;
x2 += x3;
x4 += x5;
x6 += x7;
x0 += x2;
x4 += x6;
x0 += x4;
x0 += 1 << 4;
x0 >>= 5;
A[i] = MIN(255, MAX(0, x0));
}
return A;
#endif
}

View file

@ -53,11 +53,11 @@ struct SamplingSolution {
static double ComputeWeight(double x) {
if (-1.5 < x && x < 1.5) {
if (-.5 < x && x < .5) {
return .75 - SQR(x);
return.75 - SQR(x);
} else if (x < 0) {
return .5 * SQR(x + 1.5);
return.5 * SQR(x + 1.5);
} else {
return .5 * SQR(x - 1.5);
return.5 * SQR(x - 1.5);
}
} else {
return 0;
@ -164,19 +164,12 @@ static void GyaradosImpl(long dyw, long dxw, int dst[dyw][dxw], long syw,
tmp0[dy][sx] = QRS(M, eax);
}
}
if (sharpen) {
for (dy = 0; dy < dyn; ++dy) {
for (sx = 0; sx < sxn; ++sx) {
tmp1[dy][sx] =
Sharpen(tmp0[MIN(dyn - 1, MAX(0, dy - 1))][sx], tmp0[dy][sx],
tmp0[MIN(dyn - 1, MAX(0, dy + 1))][sx]);
}
}
} else {
for (dy = 0; dy < dyn; ++dy) {
for (sx = 0; sx < sxn; ++sx) {
tmp1[dy][sx] = tmp0[dy][sx];
}
for (dy = 0; dy < dyn; ++dy) {
for (sx = 0; sx < sxn; ++sx) {
tmp1[dy][sx] = sharpen ? Sharpen(tmp0[MIN(dyn - 1, MAX(0, dy - 1))][sx],
tmp0[dy][sx],
tmp0[MIN(dyn - 1, MAX(0, dy + 1))][sx])
: tmp0[dy][sx];
}
}
for (dx = 0; dx < dxn; ++dx) {
@ -187,19 +180,12 @@ static void GyaradosImpl(long dyw, long dxw, int dst[dyw][dxw], long syw,
tmp2[dy][dx] = QRS(M, eax);
}
}
if (sharpen) {
for (dx = 0; dx < dxn; ++dx) {
for (dy = 0; dy < dyn; ++dy) {
dst[dy][dx] =
Sharpen(tmp2[dy][MIN(dxn - 1, MAX(0, dx - 1))], tmp2[dy][dx],
tmp2[dy][MIN(dxn - 1, MAX(0, dx + 1))]);
}
}
} else {
for (dx = 0; dx < dxn; ++dx) {
for (dy = 0; dy < dyn; ++dy) {
dst[dy][dx] = tmp2[dy][dx];
}
for (dx = 0; dx < dxn; ++dx) {
for (dy = 0; dy < dyn; ++dy) {
dst[dy][dx] = sharpen ? Sharpen(tmp2[dy][MIN(dxn - 1, MAX(0, dx - 1))],
tmp2[dy][dx],
tmp2[dy][MIN(dxn - 1, MAX(0, dx + 1))])
: tmp2[dy][dx];
}
}
}

View file

@ -40,7 +40,6 @@ EXAMPLES_BINS = \
EXAMPLES_DIRECTDEPS = \
CTL \
DSP_AUDIO \
DSP_CORE \
DSP_SCALE \
DSP_TTY \
@ -54,8 +53,8 @@ EXAMPLES_DIRECTDEPS = \
LIBC_NEXGEN32E \
LIBC_NT_ADVAPI32 \
LIBC_NT_IPHLPAPI \
LIBC_NT_KERNEL32 \
LIBC_NT_MEMORY \
LIBC_NT_KERNEL32 \
LIBC_NT_NTDLL \
LIBC_NT_USER32 \
LIBC_NT_WS2_32 \
@ -64,7 +63,6 @@ EXAMPLES_DIRECTDEPS = \
LIBC_SOCK \
LIBC_STDIO \
LIBC_STR \
LIBC_SYSTEM \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_TESTLIB \
@ -82,8 +80,6 @@ EXAMPLES_DIRECTDEPS = \
THIRD_PARTY_GETOPT \
THIRD_PARTY_HIREDIS \
THIRD_PARTY_LIBCXX \
THIRD_PARTY_LIBCXXABI \
THIRD_PARTY_LIBUNWIND \
THIRD_PARTY_LINENOISE \
THIRD_PARTY_LUA \
THIRD_PARTY_MBEDTLS \
@ -97,10 +93,11 @@ EXAMPLES_DIRECTDEPS = \
THIRD_PARTY_TZ \
THIRD_PARTY_VQSORT \
THIRD_PARTY_XED \
THIRD_PARTY_LIBCXXABI \
THIRD_PARTY_ZLIB \
TOOL_ARGS \
TOOL_BUILD_LIB \
TOOL_VIZ_LIB \
TOOL_VIZ_LIB
EXAMPLES_DEPS := \
$(call uniq,$(foreach x,$(EXAMPLES_DIRECTDEPS),$($(x))))
@ -151,10 +148,6 @@ o/$(MODE)/examples/picol.o: private \
CPPFLAGS += \
-DSTACK_FRAME_UNLIMITED
o/$(MODE)/examples/nesemu1.o: private \
CPPFLAGS += \
-O3
o/$(MODE)/examples/picol.dbg: \
$(EXAMPLES_DEPS) \
o/$(MODE)/examples/picol.o \

View file

@ -1,80 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <cosmoaudio.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/**
* @fileoverview plays pure A.440 tone on speakers for 1 second
* @see https://en.wikipedia.org/wiki/A440_%28pitch_standard%29
*/
#define SAMPLING_RATE 44100
#define WAVE_INTERVAL 440
#define CHANNELS 2
#define LOUDNESS .3
#define DEBUG_LOG 1
int main() {
struct CosmoAudioOpenOptions cao = {0};
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
cao.deviceType = kCosmoAudioDeviceTypePlayback;
cao.sampleRate = SAMPLING_RATE;
cao.debugLog = DEBUG_LOG;
cao.channels = CHANNELS;
int status;
struct CosmoAudio *ca;
status = cosmoaudio_open(&ca, &cao);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to open audio: %d\n", status);
return 1;
}
float buf[256 * CHANNELS];
for (int g = 0; g < SAMPLING_RATE;) {
int frames = 1;
status = cosmoaudio_poll(ca, NULL, &frames);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to poll output: %d\n", status);
return 2;
}
if (frames > 256)
frames = 256;
if (frames > SAMPLING_RATE - g)
frames = SAMPLING_RATE - g;
for (int f = 0; f < frames; ++f) {
float t = (float)g++ / SAMPLING_RATE;
float s = sinf(2 * M_PIf * WAVE_INTERVAL * t);
for (int c = 0; c < CHANNELS; c++)
buf[f * CHANNELS + c] = s * LOUDNESS;
}
status = cosmoaudio_write(ca, buf, frames);
if (status != frames) {
fprintf(stderr, "failed to write output: %d\n", status);
return 3;
}
}
status = cosmoaudio_flush(ca);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to flush output: %d\n", status);
return 4;
}
status = cosmoaudio_close(ca);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to close audio: %d\n", status);
return 5;
}
}

View file

@ -1,125 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <assert.h>
#include <cosmo.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// lockless push / pop tutorial
//
// this file demonstrates how to create a singly linked list that can be
// pushed and popped across multiple threads, using only atomics. atomic
// operations (rather than using a mutex) make push/pop go faster and it
// ensures asynchronous signal safety too. therefore it will be safe for
// use in a variety of contexts, such as signal handlers.
#define THREADS 128
#define ITERATIONS 10000
// adjust mask based on alignment of list struct
//
// - 0x00fffffffffffff0 may be used if List* is always 16-byte aligned.
// We know that's the case here, because we call malloc() to create
// every List* object, and malloc() results are always max aligned.
//
// - 0x00fffffffffffff8 may be used if List* is always 8-byte aligned.
// This might be the case if you're pushing and popping stuff that was
// allocated from an array, to avoid malloc() calls. This has one
// fewer byte of safeguards against the ABA problem though.
//
// - 0x00fffffffffff000 may be used if List* is always page aligned.
// This is a good choice if you use mmap() to allocate each List*
// element, since it offers maximum protection against ABA.
//
// - only the highest byte of a 64-bit pointer is safe to use on our
// supported platforms. on most x86 and arm systems, it's possible to
// use the top sixteen bits. however that's not the case on more
// recent high end x86-64 systems that have pml5t.
//
#define MASQUE 0x00fffffffffffff0
#define PTR(x) ((uintptr_t)(x) & MASQUE)
#define TAG(x) ROL((uintptr_t)(x) & ~MASQUE, 8)
#define ABA(p, t) ((uintptr_t)(p) | (ROR((uintptr_t)(t), 8) & ~MASQUE))
#define ROL(x, n) (((x) << (n)) | ((x) >> (64 - (n))))
#define ROR(x, n) (((x) >> (n)) | ((x) << (64 - (n))))
struct List {
struct List* next;
int count;
};
atomic_uintptr_t list;
void push(struct List* elem) {
uintptr_t tip;
assert(!TAG(elem));
for (tip = atomic_load_explicit(&list, memory_order_relaxed);;) {
elem->next = (struct List*)PTR(tip);
if (atomic_compare_exchange_weak_explicit(
&list, &tip, ABA(elem, TAG(tip) + 1), memory_order_release,
memory_order_relaxed))
break;
pthread_pause_np();
}
}
struct List* pop(void) {
uintptr_t tip;
struct List* elem;
tip = atomic_load_explicit(&list, memory_order_relaxed);
while ((elem = (struct List*)PTR(tip))) {
if (atomic_compare_exchange_weak_explicit(
&list, &tip, ABA(elem->next, TAG(tip) + 1), memory_order_acquire,
memory_order_relaxed))
break;
pthread_pause_np();
}
return elem;
}
void* tester(void* arg) {
struct List* elem;
for (int i = 0; i < ITERATIONS; ++i) {
while (!(elem = pop())) {
elem = malloc(sizeof(*elem));
elem->count = 0;
push(elem);
}
elem->count++;
push(elem);
}
return 0;
}
int main() {
printf("testing aba problem...");
fflush(stdout);
pthread_t th[THREADS];
for (int i = 0; i < THREADS; ++i)
pthread_create(&th[i], 0, tester, 0);
for (int i = 0; i < THREADS; ++i)
pthread_join(th[i], 0);
int sum = 0;
struct List* elem;
while ((elem = pop())) {
printf(" %d", elem->count);
sum += elem->count;
free(elem);
}
printf("\n");
assert(sum == ITERATIONS * THREADS);
printf("you are the dancing queen\n");
CheckForMemoryLeaks();
}

View file

@ -1,353 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <iconv.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
/**
* @fileoverview program for viewing bbs art files
* @see https://github.com/blocktronics/artpacks
* @see http://www.textfiles.com/art/
*/
#define HELP \
"Usage:\n\
art [-b %d] [-f %s] [-t %s] FILE...\n\
\n\
Flags:\n\
-b NUMBER specifies simulated modem baud rate, which defaults to\n\
2400 since that was the most common modem speed in the\n\
later half of the 1980s during the BBS golden age; you\n\
could also say 300 for the slowest experience possible\n\
or you could say 14.4k to get more of a 90's feel, and\n\
there's also the infamous 56k to bring you back to y2k\n\
-f CHARSET specifies charset of input bytes, where the default is\n\
cp347 which means IBM Code Page 347 a.k.a. DOS\n\
-t CHARSET specifies output charset used by your terminal, and it\n\
defaults to utf8 a.k.a. thompson-pike encoding\n\
\n\
Supported charsets:\n\
utf8, ascii, wchar_t, ucs2be, ucs2le, utf16be, utf16le, ucs4be,\n\
ucs4le, utf16, ucs4, ucs2, eucjp, shiftjis, iso2022jp, gb18030, gbk,\n\
gb2312, big5, euckr, iso88591, latin1, iso88592, iso88593, iso88594,\n\
iso88595, iso88596, iso88597, iso88598, iso88599, iso885910,\n\
iso885911, iso885913, iso885914, iso885915, iso885916, cp1250,\n\
windows1250, cp1251, windows1251, cp1252, windows1252, cp1253,\n\
windows1253, cp1254, windows1254, cp1255, windows1255, cp1256,\n\
windows1256, cp1257, windows1257, cp1258, windows1258, koi8r, koi8u,\n\
cp437, cp850, cp866, ibm1047, cp1047.\n\
\n\
See also:\n\
http://www.textfiles.com/art/\n\
https://github.com/blocktronics/artpacks\n\
\n"
#define INBUFSZ 256
#define OUBUFSZ (INBUFSZ * 6)
#define SLIT(s) ((unsigned)s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0])
// "When new technology comes out, people don't all buy it right away.
// If what they have works, some will wait until it doesn't. A few
// people do get the latest though. In 1984 2400 baud modems became
// available, so some people had them, but many didn't. A BBS list
// from 1986 shows operators were mostly 300 and 1200, but some were
// using 2400. The next 5 years were the hayday of the 2400."
//
// https://forum.vcfed.org/index.php?threads/the-2400-baud-modem.44241/
int baud_rate = 2400; // -b 2400
const char* from_charset = "CP437"; // -f CP437
const char* to_charset = "UTF-8"; // -t UTF-8
volatile sig_atomic_t done;
void on_signal(int sig) {
done = 1;
(void)sig;
}
void print(const char* s) {
(void)!write(STDOUT_FILENO, s, strlen(s));
}
int encode_character(char output[8], const char* codec, wchar_t character) {
size_t inbytesleft = sizeof(wchar_t);
size_t outbytesleft = 7;
char* inbuf = (char*)&character;
char* outbuf = output;
iconv_t cd = iconv_open(codec, "wchar_t");
if (cd == (iconv_t)-1)
return -1;
size_t result = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
iconv_close(cd);
if (result == (size_t)-1)
return -1;
*outbuf = '\0';
return 7 - outbytesleft;
}
void append_replacement_character(char** b) {
int n = encode_character(*b, to_charset, 0xFFFD);
if (n == -1)
n = encode_character(*b, to_charset, '?');
if (n != -1)
*b += n;
}
int compare_time(struct timespec a, struct timespec b) {
int cmp;
if (!(cmp = (a.tv_sec > b.tv_sec) - (a.tv_sec < b.tv_sec)))
cmp = (a.tv_nsec > b.tv_nsec) - (a.tv_nsec < b.tv_nsec);
return cmp;
}
struct timespec add_time(struct timespec x, struct timespec y) {
x.tv_sec += y.tv_sec;
x.tv_nsec += y.tv_nsec;
if (x.tv_nsec >= 1000000000) {
x.tv_nsec -= 1000000000;
x.tv_sec += 1;
}
return x;
}
struct timespec subtract_time(struct timespec a, struct timespec b) {
a.tv_sec -= b.tv_sec;
if (a.tv_nsec < b.tv_nsec) {
a.tv_nsec += 1000000000;
a.tv_sec--;
}
a.tv_nsec -= b.tv_nsec;
return a;
}
struct timespec fromnanos(long long x) {
struct timespec ts;
ts.tv_sec = x / 1000000000;
ts.tv_nsec = x % 1000000000;
return ts;
}
void process_file(const char* path, int fd, iconv_t cd) {
size_t carry = 0;
struct timespec next;
char input_buffer[INBUFSZ];
clock_gettime(CLOCK_MONOTONIC, &next);
for (;;) {
// read from file
ssize_t bytes_read = read(fd, input_buffer + carry, INBUFSZ - carry);
if (!bytes_read)
return;
if (bytes_read == -1) {
perror(path);
done = 1;
return;
}
// modernize character set
char* input_ptr = input_buffer;
size_t input_left = carry + bytes_read;
char output_buffer[OUBUFSZ];
char* output_ptr = output_buffer;
size_t output_left = OUBUFSZ;
size_t ir = iconv(cd, &input_ptr, &input_left, &output_ptr, &output_left);
carry = 0;
if (ir == (size_t)-1) {
if (errno == EINVAL) {
// incomplete multibyte sequence encountered
memmove(input_buffer, input_ptr, input_left);
carry = input_left;
} else if (errno == EILSEQ && input_left) {
// EILSEQ means either
// 1. illegal input sequence encountered
// 2. code not encodable in output codec
//
// so we skip one byte of input, and insert <20> or ? in the output
// this isn't the most desirable behavior, but it is the best we
// can do, since we don't know specifics about the codecs in use
//
// unlike glibc cosmo's iconv implementation may handle case (2)
// automatically by inserting an asterisk in place of a sequence
++input_ptr;
--input_left;
memmove(input_buffer, input_ptr, input_left);
carry = input_left;
if (output_left >= 8)
append_replacement_character(&output_ptr);
} else {
perror(path);
done = 1;
return;
}
}
// write to terminal
for (char* p = output_buffer; p < output_ptr; p++) {
if (done)
return;
(void)!write(STDOUT_FILENO, p, 1);
// allow arrow keys to change baud rate
int have;
if (ioctl(STDIN_FILENO, FIONREAD, &have)) {
perror("ioctl");
done = 1;
return;
}
if (have > 0) {
char key[4] = {0};
if (read(STDIN_FILENO, key, sizeof(key)) > 0) {
if (SLIT(key) == SLIT("\33[A") || // up
SLIT(key) == SLIT("\33[C")) { // right
baud_rate *= 1.4;
} else if (SLIT(key) == SLIT("\33[B") || // down
SLIT(key) == SLIT("\33[D")) { // left
baud_rate *= 0.6;
}
if (baud_rate < 3)
baud_rate = 3;
if (baud_rate > 1000000000)
baud_rate = 1000000000;
}
}
// insert artificial delay for one byte. we divide by 10 to convert
// bits to bytes, because that is how many bits 8-N-1 encoding used
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
next = add_time(next, fromnanos(1e9 / (baud_rate / 10.)));
if (compare_time(next, now) > 0) {
struct timespec sleep = subtract_time(next, now);
nanosleep(&sleep, 0);
}
}
}
}
int main(int argc, char* argv[]) {
int opt;
while ((opt = getopt(argc, argv, "hb:f:t:")) != -1) {
switch (opt) {
case 'b': {
char* endptr;
double rate = strtod(optarg, &endptr);
if (*endptr == 'k') {
rate *= 1e3;
++endptr;
} else if (*endptr == 'm') {
rate *= 1e6;
++endptr;
}
if (*endptr || baud_rate <= 0) {
fprintf(stderr, "%s: invalid baud rate: %s\n", argv[0], optarg);
exit(1);
}
baud_rate = rate;
break;
}
case 'f':
from_charset = optarg;
break;
case 't':
to_charset = optarg;
break;
case 'h':
fprintf(stderr, HELP, baud_rate, from_charset, to_charset);
exit(0);
default:
fprintf(stderr, "protip: pass the -h flag for help\n");
exit(1);
}
}
if (optind == argc) {
fprintf(stderr, "%s: missing operand\n", argv[0]);
exit(1);
}
// create character transcoder
iconv_t cd = iconv_open(to_charset, from_charset);
if (cd == (iconv_t)-1) {
fprintf(stderr, "error: conversion from %s to %s not supported\n",
from_charset, to_charset);
exit(1);
}
// catch ctrl-c
signal(SIGINT, on_signal);
// don't wait until newline to read() keystrokes
struct termios t;
if (!tcgetattr(STDIN_FILENO, &t)) {
struct termios t2 = t;
t2.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &t2);
}
// Process each file specified on the command line
for (int i = optind; i < argc && !done; i++) {
// open file
int fd = open(argv[i], O_RDONLY);
if (fd == -1) {
perror(argv[i]);
break;
}
// wait between files
if (i > optind)
sleep(1);
print("\33[?25l"); // hide cursor
print("\33[H"); // move cursor to top-left
print("\33[J"); // erase display forward
print("\33[1;24r"); // set scrolling region to first 24 lines
print("\33[?7h"); // enable auto-wrap mode
print("\33[?3l"); // 80 column mode (deccolm) vt100
print("\33[H"); // move cursor to top-left, again
// get busy
process_file(argv[i], fd, cd);
close(fd);
}
// cleanup
iconv_close(cd);
print("\33[s"); // save cursor position
print("\33[?25h"); // show cursor
print("\33[0m"); // reset text attributes (color, bold, etc.)
print("\33[?1049l"); // exit alternate screen mode
print("\33(B"); // exit line drawing and other alt charset modes
print("\33[r"); // reset scrolling region
print("\33[?2004l"); // turn off bracketed paste mode
print("\33[4l"); // exit insert mode
print("\33[?1l\33>"); // exit application keypad mode
print("\33[?7h"); // reset text wrapping mode
print("\33[?12l"); // reset cursor blinking mode
print("\33[?6l"); // reset origin mode
print("\33[20l"); // reset auto newline mode
print("\33[u"); // restore cursor position
// restore terminal
tcsetattr(STDIN_FILENO, TCSANOW, &t);
}

View file

@ -1,353 +0,0 @@
// -*- mode:c; indent-tabs-mode:nil; c-basic-offset:4 -*-
// vi: set et ft=c ts=4 sts=4 sw=4 fenc=utf-8
// asteroids by tsotchke
// https://github.com/tsotchke/asteroids
// clang-format off
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <termios.h>
#include <sys/select.h>
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 24
#define MAX_ASTEROIDS 5
#define MAX_BULLETS 5
typedef struct {
float x, y;
} Vector2;
typedef struct {
Vector2 position;
Vector2 velocity;
float angle;
float radius;
} GameObject;
GameObject spaceship;
GameObject asteroids[MAX_ASTEROIDS];
GameObject bullets[MAX_BULLETS];
int score = 0;
time_t startTime;
int isGameOver = 0;
int shouldExit = 0;
int finalTime = 0; // To store final time at game over
char display[SCREEN_HEIGHT][SCREEN_WIDTH];
// Function to clear the screen buffer
void clearDisplay() {
memset(display, ' ', sizeof(display));
}
// Function to draw a pixel on the screen
void drawPixel(int x, int y) {
if (x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT) {
display[y][x] = '*';
}
}
// Function to draw a line using Bresenham's algorithm
void drawLine(int x1, int y1, int x2, int y2) {
int dx = abs(x2 - x1), sx = (x1 < x2) ? 1 : -1;
int dy = -abs(y2 - y1), sy = (y1 < y2) ? 1 : -1;
int error = dx + dy, e2;
while (1) {
drawPixel(x1, y1);
if (x1 == x2 && y1 == y2) break;
e2 = 2 * error;
if (e2 >= dy) { error += dy; x1 += sx; }
if (e2 <= dx) { error += dx; y1 += sy; }
}
}
// Function to draw a circle
void drawCircle(int centerX, int centerY, int radius) {
int x = radius - 1, y = 0, dx = 1, dy = 1, err = dx - (radius << 1);
while (x >= y) {
drawPixel(centerX + x, centerY + y);
drawPixel(centerX + y, centerY + x);
drawPixel(centerX - y, centerY + x);
drawPixel(centerX - x, centerY + y);
drawPixel(centerX - x, centerY - y);
drawPixel(centerX - y, centerY - x);
drawPixel(centerX + y, centerY - x);
drawPixel(centerX + x, centerY - y);
if (err <= 0) {
y++;
err += dy;
dy += 2;
}
if (err > 0) {
x--;
dx += 2;
err += dx - (radius << 1);
}
}
}
// Initialize a game object
void initializeGameObject(GameObject *obj, float x, float y, float angle, float radius) {
obj->position = (Vector2){x, y};
obj->velocity = (Vector2){0, 0};
obj->angle = angle;
obj->radius = radius;
}
// Wrap position of the spaceship and asteroids within screen bounds
void wrapPosition(Vector2 *pos) {
if (pos->x < 0) pos->x = SCREEN_WIDTH - 1;
if (pos->x >= SCREEN_WIDTH) pos->x = 0;
if (pos->y < 0) pos->y = SCREEN_HEIGHT - 1;
if (pos->y >= SCREEN_HEIGHT) pos->y = 0;
}
// Check if two game objects are colliding
int checkCollision(GameObject *a, GameObject *b) {
float deltaX = a->position.x - b->position.x;
float deltaY = a->position.y - b->position.y;
return sqrt(deltaX * deltaX + deltaY * deltaY) < (a->radius + b->radius);
}
// Initialize game state
void initGame() {
score = 0; // Reset the score
initializeGameObject(&spaceship, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0, 2);
for (int i = 0; i < MAX_ASTEROIDS; i++) {
initializeGameObject(&asteroids[i],
rand() % SCREEN_WIDTH,
rand() % SCREEN_HEIGHT,
0,
2 + rand() % 3);
asteroids[i].velocity.x = ((float)rand() / RAND_MAX) * 2 - 1;
asteroids[i].velocity.y = ((float)rand() / RAND_MAX) * 2 - 1;
}
for (int i = 0; i < MAX_BULLETS; i++) {
bullets[i].position.x = -1; // Mark bullet as inactive
bullets[i].position.y = -1;
}
startTime = time(NULL);
isGameOver = 0;
finalTime = 0; // Reset final time
}
// Draw the spaceship on the screen
void drawSpaceship() {
int x = (int)spaceship.position.x;
int y = (int)spaceship.position.y;
int size = 3;
float cosAngle = cos(spaceship.angle);
float sinAngle = sin(spaceship.angle);
int x1 = x + size * cosAngle;
int y1 = y + size * sinAngle;
int x2 = x + size * cos(spaceship.angle + 2.5);
int y2 = y + size * sin(spaceship.angle + 2.5);
int x3 = x + size * cos(spaceship.angle - 2.5);
int y3 = y + size * sin(spaceship.angle - 2.5);
drawLine(x1, y1, x2, y2);
drawLine(x2, y2, x3, y3);
drawLine(x3, y3, x1, y1);
}
// Draw all entities on the screen
void drawEntities(GameObject *entities, int count, void (*drawFunc)(GameObject *)) {
for (int i = 0; i < count; i++) {
drawFunc(&entities[i]);
}
}
// Draw a bullet on the screen
void drawBullet(GameObject *bullet) { // Changed to non-const
if (bullet->position.x >= 0) {
drawPixel((int)bullet->position.x, (int)bullet->position.y);
}
}
// Draw an asteroid on the screen
void drawAsteroid(GameObject *asteroid) { // Changed to non-const
drawCircle((int)asteroid->position.x, (int)asteroid->position.y, (int)asteroid->radius);
}
// Refresh the display
void updateDisplay() {
clearDisplay();
if (!isGameOver) {
drawSpaceship();
drawEntities(asteroids, MAX_ASTEROIDS, drawAsteroid);
drawEntities(bullets, MAX_BULLETS, drawBullet);
}
// Print the screen buffer
printf("\033[H");
for (int y = 0; y < SCREEN_HEIGHT; y++) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
putchar(display[y][x]);
}
putchar('\n');
}
// Display score and elapsed time
time_t currentTime = time(NULL);
int elapsedTime = isGameOver ? finalTime : (currentTime - startTime);
printf("Score: %d | Time: %02d:%02d | %s\n", score, elapsedTime / 60, elapsedTime % 60, isGameOver ? "Game Over!" : " ");
}
// Update the position of game objects
void updateGameObject(GameObject *obj, int isBullet) {
obj->position.x += obj->velocity.x;
obj->position.y += obj->velocity.y;
// If it's a bullet, check if it's out of bounds
if (isBullet) {
if (obj->position.x < 0 || obj->position.x >= SCREEN_WIDTH || obj->position.y < 0 || obj->position.y >= SCREEN_HEIGHT) {
obj->position.x = -1; // Deactivate bullet
obj->position.y = -1;
}
} else {
wrapPosition(&obj->position);
}
}
// Update the game state
void updateGame() {
if (isGameOver) return;
// Update spaceship and apply friction
updateGameObject(&spaceship, 0); // 0 indicates it's not a bullet
spaceship.velocity.x *= 0.98;
spaceship.velocity.y *= 0.98;
// Move asteroids and check for collisions
for (int i = 0; i < MAX_ASTEROIDS; i++) {
updateGameObject(&asteroids[i], 0);
if (checkCollision(&spaceship, &asteroids[i])) {
isGameOver = 1;
finalTime = time(NULL) - startTime;
return;
}
}
// Update bullet positions
for (int i = 0; i < MAX_BULLETS; i++) {
if (bullets[i].position.x >= 0) {
updateGameObject(&bullets[i], 1); // 1 indicates it's a bullet
}
}
// Check for bullet collisions with asteroids
for (int i = 0; i < MAX_BULLETS; i++) {
if (bullets[i].position.x >= 0) {
for (int j = 0; j < MAX_ASTEROIDS; j++) {
if (checkCollision(&bullets[i], &asteroids[j])) {
bullets[i].position.x = -1; // Deactivate bullet
bullets[i].position.y = -1;
asteroids[j].position.x = rand() % SCREEN_WIDTH;
asteroids[j].position.y = rand() % SCREEN_HEIGHT;
score += 100;
}
}
}
}
}
// Fire a bullet
void shootBullet() {
for (int i = 0; i < MAX_BULLETS; i++) {
if (bullets[i].position.x < 0) {
bullets[i].position = spaceship.position;
bullets[i].velocity.x = cos(spaceship.angle) * 2;
bullets[i].velocity.y = sin(spaceship.angle) * 2;
break;
}
}
}
// Check if a key was hit
int isKeyHit() {
struct timeval tv = { 0L, 0L };
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds);
return select(1, &fds, NULL, NULL, &tv);
}
// Configure terminal settings
void configureTerminal(struct termios *old_tio, struct termios *new_tio) {
tcgetattr(STDIN_FILENO, old_tio);
*new_tio = *old_tio;
new_tio->c_lflag &= (~ICANON & ~ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, new_tio);
}
// Restore terminal settings
void restoreTerminal(struct termios *old_tio) {
tcsetattr(STDIN_FILENO, TCSANOW, old_tio);
}
void onSignal(int sig) {
shouldExit = 1;
}
// Main game loop
int main() {
signal(SIGINT, onSignal); // Capture ^C
srand(time(NULL)); // Seed the random number generator
initGame(); // Initialize the game state
struct termios old_tio, new_tio;
configureTerminal(&old_tio, &new_tio);
printf("\033[?25l"); // Hide the cursor
while (!shouldExit) {
if (isKeyHit()) {
char input = getchar();
if (input == 27) { // ESC key
if (getchar() == '[') { // Handle arrow keys
switch (getchar()) {
case 'A': // Up arrow
spaceship.velocity.x += cos(spaceship.angle) * 0.2;
spaceship.velocity.y += sin(spaceship.angle) * 0.2;
break;
case 'B': // Down arrow
spaceship.velocity.x -= cos(spaceship.angle) * 0.2;
spaceship.velocity.y -= sin(spaceship.angle) * 0.2;
break;
case 'D': spaceship.angle -= 0.2; break; // Left arrow
case 'C': spaceship.angle += 0.2; break; // Right arrow
}
}
} else if (input == ' ') {
shootBullet(); // Fire a bullet
} else if (input == 'q') {
break; // Quit the game
} else if (input == 'r' && isGameOver) {
initGame(); // Restart the game
}
}
updateGame(); // Update game state
updateDisplay(); // Refresh the display
usleep(50000); // Wait for 50ms (20 FPS)
}
printf("\033[?25h"); // Show the cursor
restoreTerminal(&old_tio); // Restore terminal settings
return 0;
}

View file

@ -14,16 +14,16 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <unistd.h>
#include <cassert>
#include <cinttypes>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include "libc/assert.h"
// high performance high accuracy matrix multiplication in ansi c
#define MATH __target_clones("avx512f,fma,avx")
#define MATH __target_clones("avx512f,fma")
namespace {
namespace ansiBLAS {

View file

@ -7,7 +7,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <unistd.h>
#include "libc/calls/calls.h"
// clears teletypewriter display
//

View file

@ -7,7 +7,12 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <cosmo.h>
#include "libc/calls/calls.h"
#include "libc/intrin/kprintf.h"
#include "libc/math.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/stdio/stdio.h"
/**
* @fileoverview How to print backtraces and cpu state on crash.

View file

@ -7,43 +7,20 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
// this program is used by jart for manually testing teletype interrupts
// and canonical mode line editing. this file documents the hidden depth
// of 1960's era computer usage, that's entrenched in primitive i/o apis
//
// manual testing checklist:
//
// - "hello" enter echos "got: hello^J"
//
// - "hello" ctrl-d echos "got: hello"
//
// - "hello" ctrl-r echos "^R\nhello"
//
// - "hello" ctrl-u enter echos "got: ^J"
//
// - ctrl-d during i/o task prints "got eof" and exits
//
// - ctrl-d during cpu task gets delayed until read() is called
//
// - ctrl-c during cpu task echos ^C, then calls SignalHandler()
// asynchronously, and program exits
//
// - ctrl-c during i/o task echos ^C, then calls SignalHandler()
// asynchronously, read() raises EINTR, and program exits
//
// - ctrl-v ctrl-c should echo "^\b" then echo "^C" and insert "\3"
//
// - ctrl-v ctrl-d should echo "^\b" then echo "^D" and insert "\4"
//
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/errno.h"
#include "libc/limits.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/limits.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/sig.h"
volatile bool gotsig;
@ -57,41 +34,23 @@ void SignalHandler(int sig) {
gotsig = true;
}
// this is the easiest way to write a string literal to standard output,
// without formatting. printf() has an enormous binary footprint so it's
// nice to avoid linking that when it is not needed.
#define WRITE(sliteral) write(1, sliteral, sizeof(sliteral) - 1)
int main(int argc, char *argv[]) {
WRITE("echoing stdin until ctrl+c is pressed\n");
printf("echoing stdin until ctrl+c is pressed\n");
// when you type ctrl-c, by default it'll kill the process, unless you
// define a SIGINT handler. there's multiple ways to do it. the common
// way is to say signal(SIGINT, func) which is normally defined to put
// the signal handler in Berkeley-style SA_RESTART mode. that means if
// a signal handler is called while inside a function like read() then
// the read operation will keep going afterwards like nothing happened
// which can make it difficult to break your event loop. to avoid this
// we can use sigaction() without specifying SA_RESTART in sa_flag and
// that'll put the signal in system v mode. this means that whenever a
// signal handler function in your program is called during an i/o op,
// that i/o op will return an EINTR error, so you can churn your loop.
// don't take that error too seriously though since SIGINT can also be
// delivered asynchronously, during the times you're crunching numbers
// rather than performing i/o which means you get no EINTR to warn you
// you need to set your signal handler using sigaction() rather than
// signal(), since the latter uses .sa_flags=SA_RESTART, which means
// read will restart itself after signals, rather than raising EINTR
sigaction(SIGINT, &(struct sigaction){.sa_handler = SignalHandler}, 0);
for (;;) {
// asynchronous signals are needed to interrupt math, which we shall
// simulate here. signals can happen any time any place. that's only
// not the case when you use sigprocmask() to block signals which is
// useful for kicking the can down the road.
WRITE("doing cpu task...\n");
for (volatile int i = 0; i < INT_MAX / 3; ++i) {
// some programs are blocked on cpu rather than i/o
// such programs shall rely on asynchronous signals
printf("doing cpu task...\n");
for (volatile int i = 0; i < INT_MAX / 5; ++i) {
if (gotsig) {
WRITE("\rgot ctrl+c asynchronously\n");
printf("\rgot ctrl+c asynchronously\n");
exit(0);
}
}
@ -112,18 +71,14 @@ int main(int argc, char *argv[]) {
// read data from standard input
//
// assuming you started this program in your terminal standard input
// will be plugged into your termios driver, which cosmpolitan codes
// in libc/calls/read-nt.c on windows. your read() function includes
// a primitive version of readline/linenoise called "canonical mode"
// which lets you edit the data that'll be returned by read() before
// it's actually returned. for example, if you type hello and enter,
// then "hello\n" will be returned. if you type hello and then ^D or
// ctrl-d, then "hello" will be returned. the ctrl-d keystroke is in
// fact an ascii control code whose special behavior can be bypassed
// if you type ctrl-v ctrl-d and then enter, in which case "\3\n" is
// returned, also known as ^D^J.
WRITE("doing read i/o task...\n");
// since this is a blocking operation and we're not performing a
// cpu-bound operation it is almost with absolute certainty that
// when the ctrl-c signal gets delivered, it'll happen in read()
//
// it's possible to be more precise if we were building library
// code. for example, you can block signals using sigprocmask()
// and then use pselect() to do the waiting.
printf("doing read i/o task...\n");
int got = read(0, buf, sizeof(buf));
// check if the read operation failed
@ -139,10 +94,10 @@ int main(int argc, char *argv[]) {
// the \r character is needed so when the line is printed
// it'll overwrite the ^C that got echo'd with the ctrl-c
if (gotsig) {
WRITE("\rgot ctrl+c via i/o eintr\n");
printf("\rgot ctrl+c via i/o eintr\n");
exit(0);
} else {
WRITE("\rgot spurious eintr\n");
printf("\rgot spurious eintr\n");
continue;
}
} else {
@ -154,34 +109,16 @@ int main(int argc, char *argv[]) {
// check if the user typed ctrl-d which closes the input handle
if (!got) {
WRITE("got eof\n");
printf("got eof\n");
exit(0);
}
// visualize line data returned by canonical mode to standard output
// relay read data to standard output
//
// it's usually safe to ignore the return code of write; your system
// will send SIGPIPE if there's any problem, which kills by default.
//
// it's possible to use keyboard shortcuts to embed control codes in
// the line. so we visualize them using the classic tty notation. it
// is also possible to type the ascii representation, so we use bold
// to visually distinguish ascii codes. see also o//examples/ttyinfo
// it's usually safe to ignore the return code of write. the
// operating system will send SIGPIPE if there's any problem
// which kills the process by default
write(1, "got: ", 5);
for (int i = 0; i < got; ++i) {
if (isascii(buf[i])) {
if (iscntrl(buf[i])) {
char ctl[2];
ctl[0] = '^';
ctl[1] = buf[i] ^ 0100;
WRITE("\033[1m");
write(1, ctl, 2);
WRITE("\033[0m");
} else {
write(1, &buf[i], 1);
}
}
}
WRITE("\n");
write(1, buf, got);
}
}

View file

@ -7,11 +7,18 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <time.h>
#include <unistd.h>
#include "libc/calls/calls.h"
#include "libc/calls/struct/timespec.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.h"
#include "libc/nt/enum/timezoneid.h"
#include "libc/nt/struct/timezoneinformation.h"
#include "libc/nt/time.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/thread/threads.h"
#include "libc/time.h"
/**
* @fileoverview High performance ISO-8601 timestamp formatter.
@ -20,8 +27,6 @@
* Consider using something like this instead for your loggers.
*/
#define ABS(X) ((X) >= 0 ? (X) : -(X))
char *GetTimestamp(void) {
int x;
struct timespec ts;

View file

@ -7,9 +7,11 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <cosmo.h>
#include <dlfcn.h>
#include <stdlib.h>
#include "libc/calls/calls.h"
#include "libc/dlopen/dlfcn.h"
#include "libc/fmt/itoa.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/runtime/runtime.h"
/**
* @fileoverview cosmopolitan dynamic runtime linking demo

View file

@ -1,21 +1,10 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <stdio.h>
#include <stdlib.h>
/**
* @fileoverview prints environment variables
*/
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
int main(int argc, char* argv[]) {
for (char** p = environ; *p; ++p)
puts(*p);
fprintf(stderr, "%s (%s)\n", argv[0], GetProgramExecutableName());
for (char** p = environ; *p; ++p) {
printf("%s\n", *p);
}
return 0;
}

View file

@ -23,6 +23,8 @@
#include <sys/auxv.h>
#include <sys/socket.h>
#include <time.h>
#include "libc/mem/leaks.h"
#include "libc/runtime/runtime.h"
/**
* @fileoverview greenbean lightweight threaded web server
@ -337,7 +339,7 @@ int main(int argc, char *argv[]) {
sigaddset(&block, SIGQUIT);
pthread_attr_t attr;
unassert(!pthread_attr_init(&attr));
unassert(!pthread_attr_setstacksize(&attr, 65536 - getpagesize()));
unassert(!pthread_attr_setstacksize(&attr, 65536));
unassert(!pthread_attr_setguardsize(&attr, getpagesize()));
unassert(!pthread_attr_setsigmask_np(&attr, &block));
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0));

View file

@ -36,10 +36,14 @@
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/rand.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/time.h"
#include "third_party/zlib/zlib.h"
// clang-format off
#define DICT "usr/share/dict/hangman"

View file

@ -7,8 +7,9 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <stdio.h>
#include "libc/stdio/stdio.h"
int main() {
printf("hello world\n");
return 0;
}

View file

@ -7,7 +7,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <unistd.h>
#include "libc/calls/calls.h"
int main() {
write(1, "hello world\n", 12);

15
examples/localtime.c Normal file
View file

@ -0,0 +1,15 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include "libc/time.h"
int main(int argc, char *argv[]) {
int64_t t = 0;
localtime(&t);
}

View file

@ -1,133 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <cosmoaudio.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/**
* @fileoverview prints ascii meter of microphone loudness
*
* 0. -60 dB is nearly silent, barely audible, even in a quiet room
* 1. -50 dB is very quiet background sounds
* 2. -40 dB is quiet ambient noise
* 3. -30 dB is clear but soft sounds
* 4. -20 dB is moderate volume, comfortable for extended listening
* 5. -10 dB is fairly loud, but not uncomfortable
* 6. -6 dB is loud, but not at full volume
* 7. -3 dB is very loud, approaching system limits
* 8. -1 dB is extremely loud, just below maximum
* 9. -0 dB is maximum volume without distortion
*/
#define SAMPLING_RATE 44100
#define ASCII_METER_WIDTH 20
#define FRAMES_PER_SECOND 30
#define MIN_DECIBEL -60
#define MAX_DECIBEL 0
#define DEBUG_LOG 1
sig_atomic_t g_done;
void on_signal(int sig) {
g_done = 1;
}
// computes root of mean squares
double rms(float* p, int n) {
double s = 0;
for (int i = 0; i < n; ++i)
s += p[i] * p[i];
return sqrt(s / n);
}
// converts rms to decibel
double rms_to_db(double rms) {
double db = 20 * log10(rms);
db = fmin(db, MAX_DECIBEL);
db = fmax(db, MIN_DECIBEL);
return db;
}
int main() {
signal(SIGINT, on_signal);
// how many samples should we process at once
int chunkFrames = SAMPLING_RATE / FRAMES_PER_SECOND;
// configure cosmo audio
struct CosmoAudioOpenOptions cao = {0};
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
cao.deviceType = kCosmoAudioDeviceTypeCapture;
cao.sampleRate = SAMPLING_RATE;
cao.bufferFrames = chunkFrames * 2;
cao.debugLog = DEBUG_LOG;
cao.channels = 1;
// connect to microphone
int status;
struct CosmoAudio* ca;
status = cosmoaudio_open(&ca, &cao);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to open microphone: %d\n", status);
return 1;
}
// allocate memory for audio work area
float* chunk = malloc(chunkFrames * sizeof(float));
if (!chunk) {
fprintf(stderr, "out of memory\n");
return 1;
}
while (!g_done) {
// wait for full chunk of audio to become available
int need_in_frames = chunkFrames;
status = cosmoaudio_poll(ca, &need_in_frames, NULL);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to poll microphone: %d\n", status);
return 2;
}
// read audio frames from microphone ring buffer
status = cosmoaudio_read(ca, chunk, chunkFrames);
if (status != chunkFrames) {
fprintf(stderr, "failed to read microphone: %d\n", status);
return 3;
}
// convert audio chunk to to ascii meter
char s[ASCII_METER_WIDTH + 1] = {0};
double db = rms_to_db(rms(chunk, chunkFrames));
double db_range = MAX_DECIBEL - MIN_DECIBEL;
int filled_length = (db - MIN_DECIBEL) / db_range * ASCII_METER_WIDTH;
for (int i = 0; i < ASCII_METER_WIDTH; ++i) {
if (i < filled_length) {
s[i] = '=';
} else {
s[i] = ' ';
}
}
printf("\r%s| %+6.2f dB", s, db);
fflush(stdout);
}
printf("\n");
// clean up resources
status = cosmoaudio_close(ca);
if (status != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to close microphone: %d\n", status);
return 5;
}
free(chunk);
}

83
examples/ls.c Normal file
View file

@ -0,0 +1,83 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include "libc/calls/calls.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/stat.h"
#include "libc/log/check.h"
#include "libc/mem/gc.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/dt.h"
#include "libc/sysv/consts/s.h"
#include "libc/x/xasprintf.h"
struct stat st;
const char *TypeToString(uint8_t type) {
switch (type) {
case DT_UNKNOWN:
return "DT_UNKNOWN";
case DT_FIFO:
return "DT_FIFO";
case DT_CHR:
return "DT_CHR";
case DT_DIR:
return "DT_DIR";
case DT_BLK:
return "DT_BLK";
case DT_REG:
return "DT_REG";
case DT_LNK:
return "DT_LNK";
case DT_SOCK:
return "DT_SOCK";
default:
return "UNKNOWN";
}
}
void List(const char *path) {
DIR *d;
struct dirent *e;
const char *vpath;
if (strcmp(path, ".") == 0) {
vpath = "";
} else if (!endswith(path, "/")) {
vpath = gc(xasprintf("%s/", path));
} else {
vpath = path;
}
if (stat(path, &st) != -1) {
if (S_ISDIR(st.st_mode)) {
CHECK((d = opendir(path)));
while ((e = readdir(d))) {
printf("0x%016x 0x%016x %-10s %s%s\n", e->d_ino, e->d_off,
TypeToString(e->d_type), vpath, e->d_name);
}
closedir(d);
} else {
printf("%s\n", path);
}
} else {
fprintf(stderr, "not found: %s\n", path);
}
}
int main(int argc, char *argv[]) {
int i;
if (argc == 1) {
List(".");
} else {
for (i = 1; i < argc; ++i) {
List(argv[i]);
}
}
return 0;
}

View file

@ -7,16 +7,25 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <cosmo.h>
#include <getopt.h>
#include <netdb.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
// clang-format off
#include "libc/calls/calls.h"
#include "libc/fmt/conv.h"
#include "libc/log/log.h"
#include "libc/macros.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/linger.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/shut.h"
#include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h"
#include "third_party/getopt/getopt.internal.h"
#include "third_party/musl/netdb.h"
/**
* @fileoverview netcat clone
@ -27,14 +36,12 @@
* Here's an example usage:
*
* make -j8 o//examples/nc.com
* printf 'GET /\r\nHost: justine.lol\r\n\r\n' | o//examples/nc.com justine.lol 80
* printf 'GET /\r\nHost: justine.lol\r\n\r\n' | o//examples/nc.com
* justine.lol 80
*
* Once upon time we called this command basically "telnet"
* Once upon time we called this command "telnet"
*/
#define ARRAYLEN(A) \
((sizeof(A) / sizeof(*(A))) / ((unsigned)!(sizeof(A) % sizeof(*(A)))))
int main(int argc, char *argv[]) {
ssize_t rc;
size_t i, got;

View file

@ -3,7 +3,6 @@
/* PORTED TO TELETYPEWRITERS IN YEAR 2020 BY JUSTINE ALEXANDRA ROBERTS TUNNEY */
/* TRADEMARKS ARE OWNED BY THEIR RESPECTIVE OWNERS LAWYERCATS LUV TAUTOLOGIES */
/* https://bisqwit.iki.fi/jutut/kuvat/programming_examples/nesemu1/nesemu1.cc */
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
#include "dsp/core/core.h"
#include "dsp/core/half.h"
#include "dsp/core/illumination.h"
@ -13,6 +12,7 @@
#include "dsp/tty/tty.h"
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/winsize.h"
#include "libc/calls/termios.h"
@ -35,17 +35,20 @@
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/prio.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/w.h"
#include "libc/thread/thread.h"
#include "libc/time.h"
#include "libc/x/xasprintf.h"
#include "libc/x/xsigaction.h"
#include "libc/zip.h"
#include "third_party/getopt/getopt.internal.h"
#include "third_party/libcxx/__atomic/atomic.h"
#include "third_party/libcxx/vector"
#include "tool/viz/lib/knobs.h"
@ -108,9 +111,7 @@ AUTHORS\n\
#define DYN 240
#define DXN 256
#define FPS 60.0988
#define CPUHZ 1789773
#define SRATE 44100
#define ABUFZ ((int)(SRATE / FPS) + 1)
#define HZ 1789773
#define GAMMA 2.2
#define CTRL(C) ((C) ^ 0100)
#define ALT(C) ((033 << 010) | (C))
@ -120,11 +121,25 @@ typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
static const struct itimerval kNesFps = {
{0, 1. / FPS * 1e6},
{0, 1. / FPS * 1e6},
};
struct Frame {
char *p, *w, *mem;
};
struct Action {
int code;
int wait;
};
struct Audio {
size_t i;
int16_t p[65536];
};
struct Status {
int wait;
char text[80];
@ -135,33 +150,32 @@ struct ZipGames {
char** p;
};
static const struct timespec kNesFps = {0, 1. / FPS * 1e9};
static int frame_;
static int playfd_;
static int playpid_;
static size_t vtsize_;
static bool artifacts_;
static long tyn_, txn_;
static struct Frame vf_[2];
static struct Audio audio_;
static const char* inputfn_;
static struct Status status_;
static volatile bool exited_;
static volatile bool timeout_;
static volatile bool resized_;
static struct CosmoAudio* ca_;
static struct TtyRgb* ttyrgb_;
static unsigned char *R, *G, *B;
static struct ZipGames zipgames_;
static struct Action arrow_, button_;
static struct SamplingSolution* ssy_;
static struct SamplingSolution* ssx_;
static unsigned char (*pixels_)[3][DYN][DXN];
static unsigned char pixels_[3][DYN][DXN];
static unsigned char palette_[3][64][512][3];
static int joy_current_[2], joy_next_[2], joypos_[2];
static int keyframes_ = 10;
static enum TtyBlocksSelection blocks_ = kTtyBlocksUnicode;
static enum TtyQuantizationAlgorithm quant_ = kTtyQuantXterm256;
static struct timespec deadline_;
static std::atomic<void*> pixels_ready_;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static enum TtyQuantizationAlgorithm quant_ = kTtyQuantTrue;
static int Clamp(int v) {
return MAX(0, MIN(255, v));
@ -215,39 +229,73 @@ void InitPalette(void) {
rgbc[u] = FixGamma(y / 1980. + i * A[u] / 9e6 + q * B[u] / 9e6);
}
matvmul3(rgbd65, lightbulb, rgbc);
for (u = 0; u < 3; ++u)
for (u = 0; u < 3; ++u) {
palette_[o][p1][p0][u] = Clamp(rgbd65[u] * 255);
}
}
}
}
}
static ssize_t Write(int fd, const void* p, size_t n) {
int rc;
sigset_t ss, oldss;
sigfillset(&ss);
sigprocmask(SIG_SETMASK, &ss, &oldss);
rc = write(fd, p, n);
sigprocmask(SIG_SETMASK, &oldss, 0);
return rc;
}
static void WriteString(const char* s) {
write(STDOUT_FILENO, s, strlen(s));
Write(STDOUT_FILENO, s, strlen(s));
}
void Exit(int rc) {
WriteString("\r\n\e[0m\e[J");
if (rc && errno)
if (rc && errno) {
fprintf(stderr, "%s%s\r\n", "error: ", strerror(errno));
}
exit(rc);
}
void Cleanup(void) {
ttyraw((enum TtyRawFlags)(-1u));
ttyshowcursor(STDOUT_FILENO);
cosmoaudio_close(ca_);
ca_ = 0;
if (playpid_) {
kill(playpid_, SIGKILL);
close(playfd_);
playfd_ = -1;
}
}
void OnCtrlC(void) {
exited_ = true;
}
void OnTimer(void) {
timeout_ = true;
}
void OnResize(void) {
resized_ = true;
}
void OnPiped(void) {
exited_ = true;
}
void OnSigChld(void) {
waitpid(-1, 0, WNOHANG);
close(playfd_);
playpid_ = 0;
playfd_ = -1;
}
void InitFrame(struct Frame* f) {
f->p = f->w = f->mem = (char*)realloc(f->mem, vtsize_);
}
long ChopAxis(long dn, long sn) {
while (HALF(sn) > dn) {
sn = HALF(sn);
@ -270,6 +318,11 @@ void GetTermSize(void) {
G = (unsigned char*)realloc(G, tyn_ * txn_);
B = (unsigned char*)realloc(B, tyn_ * txn_);
ttyrgb_ = (struct TtyRgb*)realloc(ttyrgb_, tyn_ * txn_ * 4);
vtsize_ = ((tyn_ * txn_ * strlen("\e[48;2;255;48;2;255m▄")) +
(tyn_ * strlen("\e[0m\r\n")) + 128);
frame_ = 0;
InitFrame(&vf_[0]);
InitFrame(&vf_[1]);
WriteString("\e[0m\e[H\e[J");
resized_ = false;
}
@ -277,7 +330,11 @@ void GetTermSize(void) {
void IoInit(void) {
GetTermSize();
xsigaction(SIGINT, (void*)OnCtrlC, 0, 0, NULL);
xsigaction(SIGPIPE, (void*)OnPiped, 0, 0, NULL);
xsigaction(SIGWINCH, (void*)OnResize, 0, 0, NULL);
xsigaction(SIGALRM, (void*)OnTimer, 0, 0, NULL);
xsigaction(SIGCHLD, (void*)OnSigChld, 0, 0, NULL);
setitimer(ITIMER_REAL, &kNesFps, NULL);
ttyhidecursor(STDOUT_FILENO);
ttyraw(kTtySigs);
ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
@ -414,29 +471,81 @@ ssize_t ReadKeyboard(void) {
return rc;
}
void ScaleVideoFrameToTeletypewriter(unsigned char (*pixels)[3][DYN][DXN]) {
bool HasVideo(struct Frame* f) {
return f->w < f->p;
}
bool HasPendingVideo(void) {
return HasVideo(&vf_[0]) || HasVideo(&vf_[1]);
}
bool HasPendingAudio(void) {
return playpid_ && audio_.i;
}
struct Frame* FlipFrameBuffer(void) {
frame_ = !frame_;
return &vf_[frame_];
}
void TransmitVideo(void) {
ssize_t rc;
struct Frame* f;
f = &vf_[frame_];
if (!HasVideo(f))
f = FlipFrameBuffer();
if ((rc = Write(STDOUT_FILENO, f->w, f->p - f->w)) != -1) {
f->w += rc;
} else if (errno == EAGAIN) {
// slow teletypewriter
} else if (errno == EPIPE) {
Exit(0);
}
}
void TransmitAudio(void) {
ssize_t rc;
if (!playpid_)
return;
if (!audio_.i)
return;
if (playfd_ == -1)
return;
if ((rc = Write(playfd_, audio_.p, audio_.i * sizeof(short))) != -1) {
rc /= sizeof(short);
memmove(audio_.p, audio_.p + rc, (audio_.i - rc) * sizeof(short));
audio_.i -= rc;
} else if (errno == EPIPE) {
kill(playpid_, SIGKILL);
close(playfd_);
playfd_ = -1;
Exit(0);
}
}
void ScaleVideoFrameToTeletypewriter(void) {
long y, x, yn, xn;
yn = DYN, xn = DXN;
while (HALF(yn) > tyn_ || HALF(xn) > txn_) {
if (HALF(xn) > txn_) {
Magikarp2xX(DYN, DXN, (*pixels)[0], yn, xn);
Magikarp2xX(DYN, DXN, (*pixels)[1], yn, xn);
Magikarp2xX(DYN, DXN, (*pixels)[2], yn, xn);
Magikarp2xX(DYN, DXN, pixels_[0], yn, xn);
Magikarp2xX(DYN, DXN, pixels_[1], yn, xn);
Magikarp2xX(DYN, DXN, pixels_[2], yn, xn);
xn = HALF(xn);
}
if (HALF(yn) > tyn_) {
Magikarp2xY(DYN, DXN, (*pixels)[0], yn, xn);
Magikarp2xY(DYN, DXN, (*pixels)[1], yn, xn);
Magikarp2xY(DYN, DXN, (*pixels)[2], yn, xn);
Magikarp2xY(DYN, DXN, pixels_[0], yn, xn);
Magikarp2xY(DYN, DXN, pixels_[1], yn, xn);
Magikarp2xY(DYN, DXN, pixels_[2], yn, xn);
yn = HALF(yn);
}
}
GyaradosUint8(tyn_, txn_, R, DYN, DXN, (*pixels)[0], tyn_, txn_, yn, xn, 0,
255, ssy_, ssx_, true);
GyaradosUint8(tyn_, txn_, G, DYN, DXN, (*pixels)[1], tyn_, txn_, yn, xn, 0,
255, ssy_, ssx_, true);
GyaradosUint8(tyn_, txn_, B, DYN, DXN, (*pixels)[2], tyn_, txn_, yn, xn, 0,
255, ssy_, ssx_, true);
GyaradosUint8(tyn_, txn_, R, DYN, DXN, pixels_[0], tyn_, txn_, yn, xn, 0, 255,
ssy_, ssx_, true);
GyaradosUint8(tyn_, txn_, G, DYN, DXN, pixels_[1], tyn_, txn_, yn, xn, 0, 255,
ssy_, ssx_, true);
GyaradosUint8(tyn_, txn_, B, DYN, DXN, pixels_[2], tyn_, txn_, yn, xn, 0, 255,
ssy_, ssx_, true);
for (y = 0; y < tyn_; ++y) {
for (x = 0; x < txn_; ++x) {
ttyrgb_[y * txn_ + x] =
@ -453,104 +562,57 @@ void KeyCountdown(struct Action* a) {
}
}
void Raster(unsigned char (*pixels)[3][DYN][DXN]) {
struct TtyRgb bg = {0x12, 0x34, 0x56, 0};
struct TtyRgb fg = {0x12, 0x34, 0x56, 0};
ScaleVideoFrameToTeletypewriter(pixels);
char* ansi = (char*)malloc((tyn_ * txn_ * strlen("\e[48;2;255;48;2;255m▄")) +
(tyn_ * strlen("\e[0m\r\n")) + 128);
char* p = ansi;
p = stpcpy(p, "\e[0m\e[H");
p = ttyraster(p, ttyrgb_, tyn_, txn_, bg, fg);
free(pixels);
if (status_.wait) {
status_.wait--;
p = stpcpy(p, "\e[0m\e[H");
p = stpcpy(p, status_.text);
}
size_t n = p - ansi;
ssize_t wrote;
for (size_t i = 0; i < n; i += wrote) {
if ((wrote = write(STDOUT_FILENO, ansi + i, n - i)) == -1) {
exited_ = true;
break;
void PollAndSynchronize(void) {
do {
if (ReadKeyboard() == -1) {
if (errno != EINTR)
Exit(1);
if (exited_)
Exit(0);
if (resized_)
GetTermSize();
}
}
free(ansi);
} while (!timeout_);
TransmitVideo();
TransmitAudio();
timeout_ = false;
KeyCountdown(&arrow_);
KeyCountdown(&button_);
joy_next_[0] = arrow_.code | button_.code;
joy_next_[1] = arrow_.code | button_.code;
}
void* RasterThread(void* arg) {
sigset_t ss;
sigemptyset(&ss);
sigaddset(&ss, SIGINT);
sigaddset(&ss, SIGHUP);
sigaddset(&ss, SIGQUIT);
sigaddset(&ss, SIGTERM);
sigaddset(&ss, SIGPIPE);
sigprocmask(SIG_SETMASK, &ss, 0);
for (;;) {
unsigned char(*pixels)[3][DYN][DXN];
pthread_mutex_lock(&lock);
while (!(pixels = (unsigned char(*)[3][DYN][DXN])pixels_ready_.load()))
pthread_cond_wait(&cond, &lock);
pixels_ready_.store(0);
pthread_mutex_unlock(&lock);
if (resized_)
GetTermSize();
Raster(pixels);
void Raster(void) {
struct Frame* f;
struct TtyRgb bg = {0x12, 0x34, 0x56, 0};
struct TtyRgb fg = {0x12, 0x34, 0x56, 0};
ScaleVideoFrameToTeletypewriter();
f = &vf_[!frame_];
f->p = f->w = f->mem;
f->p = stpcpy(f->p, "\e[0m\e[H");
f->p = ttyraster(f->p, ttyrgb_, tyn_, txn_, bg, fg);
if (status_.wait) {
status_.wait--;
f->p = stpcpy(f->p, "\e[0m\e[H");
f->p = stpcpy(f->p, status_.text);
}
PollAndSynchronize();
}
void FlushScanline(unsigned py) {
if (py != DYN - 1)
return;
pthread_mutex_lock(&lock);
if (!pixels_ready_) {
pixels_ready_.store(pixels_);
pixels_ = 0;
pthread_cond_signal(&cond);
if (py == DYN - 1) {
if (!timeout_) {
Raster();
}
timeout_ = false;
}
pthread_mutex_unlock(&lock);
if (!pixels_)
pixels_ = (unsigned char(*)[3][DYN][DXN])malloc(3 * DYN * DXN);
if (exited_)
Exit(0);
do {
struct timespec now = timespec_mono();
struct timespec remain = timespec_subz(deadline_, now);
int remain_ms = timespec_tomillis(remain);
struct pollfd fds[] = {{STDIN_FILENO, POLLIN}};
int got = poll(fds, 1, remain_ms);
if (got == -1) {
if (errno == EINTR)
continue;
Exit(1);
}
if (got == 1) {
do {
if (ReadKeyboard() == -1) {
if (errno == EINTR)
continue;
Exit(1);
}
} while (0);
}
KeyCountdown(&arrow_);
KeyCountdown(&button_);
joy_next_[0] = arrow_.code | button_.code;
joy_next_[1] = arrow_.code | button_.code;
now = timespec_mono();
do
deadline_ = timespec_add(deadline_, kNesFps);
while (timespec_cmp(deadline_, now) <= 0);
} while (0);
}
static void PutPixel(unsigned px, unsigned py, unsigned pixel, int offset) {
static unsigned prev;
(*pixels_)[0][py][px] = palette_[offset][prev % 64][pixel][2];
(*pixels_)[1][py][px] = palette_[offset][prev % 64][pixel][1];
(*pixels_)[2][py][px] = palette_[offset][prev % 64][pixel][0];
pixels_[0][py][px] = palette_[offset][prev % 64][pixel][2];
pixels_[1][py][px] = palette_[offset][prev % 64][pixel][1];
pixels_[2][py][px] = palette_[offset][prev % 64][pixel][0];
prev = pixel;
}
@ -1432,7 +1494,8 @@ void Tick() { // Invoked at CPU's rate.
// Mix the audio: Get the momentary sample from each channel and mix them.
#define s(c) channels[c].Tick<c == 1 ? 0 : c>()
auto v = [](float m, float n, float d) { return n != 0.f ? m / n : d; };
float sample =
short sample =
30000 *
(v(95.88f, (100.f + v(8128.f, s(0) + s(1), -100.f)), 0.f) +
v(159.79f,
(100.f +
@ -1441,19 +1504,7 @@ void Tick() { // Invoked at CPU's rate.
0.5f);
#undef s
// Relay audio to speaker.
static int buffer_position = 0;
static float audio_buffer[ABUFZ];
static double sample_counter = 0.0;
sample_counter += (double)SRATE / CPUHZ;
while (sample_counter >= 1.0) {
audio_buffer[buffer_position++] = sample;
sample_counter -= 1.0;
if (buffer_position == ABUFZ) {
cosmoaudio_write(ca_, audio_buffer, buffer_position);
buffer_position = 0;
}
}
audio_.p[audio_.i = (audio_.i + 1) & (ARRAYLEN(audio_.p) - 1)] = sample;
}
} // namespace APU
@ -1665,8 +1716,8 @@ void Op() {
if (!nmi_now)
nmi_edge_detected = false;
// Define function pointers for each opcode (00..FF) and each interrupt
// (100,101,102)
// Define function pointers for each opcode (00..FF) and each interrupt
// (100,101,102)
#define c(n) Ins<0x##n>, Ins<0x##n + 1>,
#define o(n) c(n) c(n + 2) c(n + 4) c(n + 6)
static void (*const i[0x108])() = {
@ -1694,6 +1745,9 @@ char* GetLine(void) {
int PlayGame(const char* romfile, const char* opt_tasfile) {
FILE* fp;
int devnull;
int pipefds[2];
const char* ffplay;
inputfn_ = opt_tasfile;
if (!(fp = fopen(romfile, "rb"))) {
@ -1706,28 +1760,46 @@ int PlayGame(const char* romfile, const char* opt_tasfile) {
return 3;
}
// initialize screen
pixels_ = (unsigned char(*)[3][DYN][DXN])malloc(3 * DYN * DXN);
InitPalette();
// start raster thread
errno_t err;
pthread_t th;
if ((err = pthread_create(&th, 0, RasterThread, 0))) {
fprintf(stderr, "pthread_create: %s\n", strerror(err));
exit(1);
}
// open speaker
struct CosmoAudioOpenOptions cao = {};
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
cao.deviceType = kCosmoAudioDeviceTypePlayback;
cao.sampleRate = SRATE;
cao.channels = 1;
cosmoaudio_open(&ca_, &cao);
// initialize time
deadline_ = timespec_add(timespec_mono(), kNesFps);
// todo: this needs plenty of work
if (!IsWindows()) {
if ((ffplay = commandvenv("FFPLAY", "ffplay"))) {
devnull = open("/dev/null", O_WRONLY | O_CLOEXEC);
pipe2(pipefds, O_CLOEXEC);
if (!(playpid_ = fork())) {
const char* const args[] = {
ffplay, //
"-nodisp", //
"-loglevel", "quiet", //
"-ac", "1", //
"-ar", "1789773", //
"-f", "s16le", //
"pipe:", //
NULL,
};
dup2(pipefds[0], 0);
dup2(devnull, 1);
dup2(devnull, 2);
execv(ffplay, (char* const*)args);
abort();
}
close(pipefds[0]);
playfd_ = pipefds[1];
} else {
fputs("\nWARNING\n\
\n\
Need `ffplay` command to play audio\n\
Try `sudo apt install ffmpeg` on Linux\n\
You can specify it on `PATH` or in `FFPLAY`\n\
\n\
Press enter to continue without sound: ",
stdout);
fflush(stdout);
GetLine();
}
}
// Read the ROM file header
u8 rom16count = fgetc(fp);
@ -1835,8 +1907,9 @@ int SelectGameFromZip(void) {
int i, rc;
char *line, *uri;
fputs("\nCOSMOPOLITAN NESEMU1\n\n", stdout);
for (i = 0; i < (int)zipgames_.i; ++i)
for (i = 0; i < (int)zipgames_.i; ++i) {
printf(" [%d] %s\n", i, zipgames_.p[i]);
}
fputs("\nPlease choose a game (or CTRL-C to quit) [default 0]: ", stdout);
fflush(stdout);
rc = 0;
@ -1859,8 +1932,9 @@ int main(int argc, char** argv) {
} else if (optind < argc) {
rc = PlayGame(argv[optind], NULL);
} else {
if (!FindZipGames())
if (!FindZipGames()) {
PrintUsage(0, stderr);
}
rc = SelectGameFromZip();
}
return rc;

View file

@ -1,12 +1,3 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <stdio.h>
#define PARSE_AND_PRINT(type, scan_fmt, print_fmt, str) \

View file

@ -7,10 +7,10 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/fmt/itoa.h"
#include "libc/str/str.h"
volatile int g_sig;
@ -21,13 +21,16 @@ void OnSig(int sig) {
int main(int argc, char *argv[]) {
// listen for all signals
for (int sig = 1; sig <= NSIG; ++sig)
for (int sig = 1; sig <= NSIG; ++sig) {
signal(sig, OnSig);
}
// wait for a signal
printf("waiting for signal to be sent to my pid %d\n", getpid());
char ibuf[12];
FormatInt32(ibuf, getpid());
tinyprint(2, "waiting for signal to be sent to ", ibuf, "\n", NULL);
pause();
// report the signal
printf("got %s\n", strsignal(g_sig));
tinyprint(1, "got ", strsignal(g_sig), "\n", NULL);
}

View file

@ -32,9 +32,12 @@
* . Formatted as per Cosmopolitan's standards.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libc/fmt/conv.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
enum { PICOL_OK, PICOL_ERR, PICOL_RETURN, PICOL_BREAK, PICOL_CONTINUE };
enum { PT_ESC, PT_STR, PT_CMD, PT_VAR, PT_SEP, PT_EOL, PT_EOF };

View file

@ -7,7 +7,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <cosmo.h>
#include "libc/runtime/runtime.h"
int main() {
__printargs("");

File diff suppressed because it is too large Load diff

View file

@ -1,322 +0,0 @@
#/*────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#include <ctype.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
/**
* @fileoverview cosmopolitan flash cards viewer
*/
struct Card {
char* qa[2];
};
atomic_int g_done;
void onsig(int sig) {
g_done = 1;
}
void* xmalloc(int n) {
void* p;
if ((p = malloc(n)))
return p;
perror("malloc");
exit(1);
}
void* xrealloc(void* p, int n) {
if ((p = realloc(p, n)))
return p;
perror("realloc");
exit(1);
}
char* xstrcat(const char* a, const char* b) {
char* p;
size_t n, m;
n = strlen(a);
m = strlen(b);
p = xmalloc(n + m + 1);
memcpy(p, a, n);
memcpy(p + n, b, m + 1);
return p;
}
void shuffle(struct Card* a, int n) {
while (n > 1) {
int i = rand() % n--;
struct Card t = a[i];
a[i] = a[n];
a[n] = t;
}
}
char* trim(char* s) {
int i;
if (s) {
while (isspace(*s))
++s;
for (i = strlen(s); i--;) {
if (isspace(s[i])) {
s[i] = 0;
} else {
break;
}
}
}
return s;
}
char* readline(FILE* f) {
for (;;) {
char* line = trim(fgetln(f, 0));
if (!line)
return 0;
if (*line != '#')
if (*line)
return line;
}
}
char* fill(const char* text, int max_line_width, int* out_line_count) {
int text_len = strlen(text);
char* result = xmalloc(text_len * 2 + 1);
int result_pos = 0;
int line_start = 0;
int line_count = 1;
int i = 0;
while (i < text_len && isspace(text[i]))
i++;
while (i < text_len) {
int word_end = i;
while (word_end < text_len && !isspace(text[word_end]))
word_end++;
int word_length = word_end - i;
if ((result_pos - line_start) + (result_pos > line_start ? 1 : 0) +
word_length >
max_line_width) {
if (result_pos > line_start) {
++line_count;
result[result_pos++] = '\n';
line_start = result_pos;
}
} else if (result_pos > line_start) {
result[result_pos++] = ' ';
}
memcpy(result + result_pos, text + i, word_length);
result_pos += word_length;
i = word_end;
while (i < text_len && isspace(text[i]))
i++;
}
result[result_pos] = '\0';
result = xrealloc(result, result_pos + 1);
if (out_line_count)
*out_line_count = line_count;
return result;
}
void show(const char* text, int i, int n) {
// get pseudoteletypewriter dimensions
struct winsize ws = {80, 25};
tcgetwinsize(1, &ws);
int width = ws.ws_col;
if (width > (int)(ws.ws_col * .9))
width = ws.ws_col * .9;
if (width > 80)
width = 80;
width &= -2;
// clear display
printf("\033[H\033[J");
// display flash card text in middle of display
char buf[32];
int line_count;
char* lines = fill(text, width, &line_count);
sprintf(buf, "%d/%d\r\n\r\n", i + 1, n);
line_count += 2;
char* extra = xstrcat(buf, lines);
free(lines);
char* tokens = extra;
for (int j = 0;; ++j) {
char* line = strtok(tokens, "\n");
tokens = 0;
if (!line)
break;
printf("\033[%d;%dH%s", ws.ws_row / 2 - line_count / 2 + j + 1,
ws.ws_col / 2 - strlen(line) / 2 + 1, line);
}
free(extra);
fflush(stdout);
}
void usage(FILE* f, const char* prog) {
fprintf(f,
"usage: %s FILE\n"
"\n"
"here's an example of what your file should look like:\n"
"\n"
" # cosmopolitan flash cards\n"
" # california dmv drivers test\n"
" \n"
" which of the following point totals could result in "
"your license being suspended by the dmv?\n"
" 4 points in 12 months (middle)\n"
" \n"
" at 55 mph under good conditions a passenger vehicle can stop "
"within\n"
" 300 feet (not 200, not 400, middle)\n"
" \n"
" two sets of solid double yellow lines spaced two or more feet "
"apart indicate\n"
" a BARRIER (do not cross unless there's an opening)\n"
"\n"
"more specifically, empty lines are ignored, lines starting with\n"
"a hash are ignored, then an even number of lines must remain,\n"
"where each two lines is a card, holding question and answer.\n",
prog);
}
int main(int argc, char* argv[]) {
// show help
if (argc != 2) {
usage(stderr, argv[0]);
return 1;
}
if (!strcmp(argv[1], "-?") || //
!strcmp(argv[1], "-h") || //
!strcmp(argv[1], "--help")) {
usage(stdout, argv[0]);
return 0;
}
// teletypewriter is required
if (!isatty(0) || !isatty(1)) {
perror("isatty");
return 2;
}
// load cards
FILE* f = fopen(argv[1], "r");
if (!f) {
perror(argv[1]);
return 3;
}
int count = 0;
struct Card* cards = 0;
for (;;) {
struct Card card;
if (!(card.qa[0] = readline(f)))
break;
card.qa[0] = strdup(card.qa[0]);
if (!(card.qa[1] = readline(f))) {
fprintf(stderr, "%s: flash card file has odd number of lines\n", argv[1]);
exit(1);
}
card.qa[1] = strdup(card.qa[1]);
cards = xrealloc(cards, (count + 1) * sizeof(struct Card));
cards[count++] = card;
}
fclose(f);
// randomize
srand(time(0));
shuffle(cards, count);
// catch ctrl-c
struct sigaction sa;
sa.sa_flags = 0;
sa.sa_handler = onsig;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, 0);
// enter raw mode
struct termios ot;
tcgetattr(1, &ot);
struct termios nt = ot;
cfmakeraw(&nt);
nt.c_lflag |= ISIG;
tcsetattr(1, TCSANOW, &nt);
printf("\033[?25l");
// show flash cards
int i = 0;
while (!g_done) {
show(cards[i / 2].qa[i % 2], i / 2, count);
// press any key
char b[8] = {0};
read(0, b, sizeof(b));
// q quits
if (b[0] == 'q')
break;
// b or ctrl-b goes backward
if (b[0] == 'b' || //
b[0] == ('B' ^ 0100)) {
if (--i < 0)
i = count * 2 - 1;
i &= -2;
continue;
}
// p or ctrl-p goes backward
if (b[0] == 'p' || //
b[0] == ('P' ^ 0100)) {
if (--i < 0)
i = count * 2 - 1;
i &= -2;
continue;
}
// up arrow goes backward
if (b[0] == 033 && //
b[1] == '[' && //
b[2] == 'A') {
if (--i < 0)
i = count * 2 - 1;
i &= -2;
continue;
}
// left arrow goes backward
if (b[0] == 033 && //
b[1] == '[' && //
b[2] == 'D') {
if (--i < 0)
i = count * 2 - 1;
i &= -2;
continue;
}
// only advance
if (++i == count * 2)
i = 0;
}
// free memory
for (int i = 0; i < count; ++i)
for (int j = 0; j < 2; ++j)
free(cards[i].qa[j]);
free(cards);
// cleanup terminal and show cursor
tcsetattr(1, TCSANOW, &ot);
printf("\033[?25h");
printf("\n");
}

View file

@ -29,30 +29,39 @@
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
#include <err.h>
#include <errno.h>
#include <paths.h>
#include <pty.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include "libc/calls/calls.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/termios.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/struct/winsize.h"
#include "libc/calls/termios.h"
#include "libc/calls/weirdtypes.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/bswap.h"
#include "libc/log/bsd.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/select.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/s.h"
#include "libc/sysv/consts/termios.h"
#include "libc/time.h"
#include "third_party/getopt/getopt.internal.h"
// clang-format off
/**
* @fileoverview Terminal Screencast Recorder / Player, e.g.
*
* make o//examples/script.com
* o//examples/script.com -w80 -h24 -r recording.tty
* o//examples/script.com -r
* # type stuff..
* # CTRL-D
* o//examples/script.com -p recording.tty
* o//examples/script.com -p typescript
*
* @note works on Linux, OpenBSD, NetBSD, FreeBSD, MacOS
* @see https://asciinema.org/
@ -103,9 +112,9 @@ main(int argc, char *argv[])
fd_set rfd;
int fm_fd;
int aflg, Fflg, kflg, pflg, ch, k, n;
int flushtime, readstdin, width, height;
int flushtime, readstdin;
aflg = Fflg = kflg = pflg = height = width = 0;
aflg = Fflg = kflg = pflg = 0;
usesleep = 1;
rawout = 0;
flushtime = 30;
@ -115,7 +124,7 @@ main(int argc, char *argv[])
(void)fm_fd;
while ((ch = getopt(argc, argv, "adeFfkpqrt:w:h:")) != -1)
while ((ch = getopt(argc, argv, "adeFfkpqrt:")) != -1)
switch(ch) {
case 'a':
aflg = 1;
@ -145,12 +154,6 @@ main(int argc, char *argv[])
if (flushtime < 0)
err(1, "invalid flush time %d", flushtime);
break;
case 'w':
width = atoi(optarg);
break;
case 'h':
height = atoi(optarg);
break;
case '?':
default:
usage();
@ -178,10 +181,6 @@ main(int argc, char *argv[])
if (openpty(&master, &slave, NULL, NULL, NULL) == -1)
err(1, "openpty");
} else {
if (width)
win.ws_col = width;
if (height)
win.ws_row = height;
if (openpty(&master, &slave, NULL, &tt, &win) == -1)
err(1, "openpty");
ttyflg = 1;

View file

@ -7,8 +7,9 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <cosmo.h>
#include <stdlib.h>
#include "libc/calls/calls.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/itoa.h"
/**
* @fileoverview Prints sequence of numbers.

View file

@ -7,9 +7,12 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include "libc/calls/calls.h"
#include "libc/calls/ucontext.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/exit.h"
/**
* @fileoverview swapcontext() and makecontext() example
@ -30,16 +33,18 @@ static ucontext_t uctx_func2;
static void func1(void) {
say("func1: started\n");
say("func1: swapcontext(&uctx_func1, &uctx_func2)\n");
if (swapcontext(&uctx_func1, &uctx_func2) == -1)
if (swapcontext(&uctx_func1, &uctx_func2) == -1) {
handle_error("swapcontext");
}
say("func1: returning\n");
}
static void func2(void) {
say("func2: started\n");
say("func2: swapcontext(&uctx_func2, &uctx_func1)\n");
if (swapcontext(&uctx_func2, &uctx_func1) == -1)
if (swapcontext(&uctx_func2, &uctx_func1) == -1) {
handle_error("swapcontext");
}
say("func2: returning\n");
}
@ -47,15 +52,17 @@ int main(int argc, char *argv[]) {
char func1_stack[8192];
char func2_stack[8192];
if (getcontext(&uctx_func1) == -1)
if (getcontext(&uctx_func1) == -1) {
handle_error("getcontext");
}
uctx_func1.uc_stack.ss_sp = func1_stack;
uctx_func1.uc_stack.ss_size = sizeof(func1_stack);
uctx_func1.uc_link = &uctx_main;
makecontext(&uctx_func1, func1, 0);
if (getcontext(&uctx_func2) == -1)
if (getcontext(&uctx_func2) == -1) {
handle_error("getcontext");
}
uctx_func2.uc_stack.ss_sp = func2_stack;
uctx_func2.uc_stack.ss_size = sizeof(func2_stack);
/* Successor context is f1(), unless argc > 1 */
@ -63,8 +70,9 @@ int main(int argc, char *argv[]) {
makecontext(&uctx_func2, func2, 0);
say("main: swapcontext(&uctx_main, &uctx_func2)\n");
if (swapcontext(&uctx_main, &uctx_func2) == -1)
if (swapcontext(&uctx_main, &uctx_func2) == -1) {
handle_error("swapcontext");
}
say("main: exiting\n");
exit(EXIT_SUCCESS);

View file

@ -7,15 +7,17 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
/**
* @fileoverview interval timer tutorial
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/ucontext.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/time.h"
volatile bool gotalrm;

View file

@ -1,366 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
// posix_spawn() example
//
// This program demonstrates the use of posix_spawn() to run the command
// `ls --dired` and capture its output. It teaches several key features:
//
// - Changing the working directory for the child process
// - Redirecting stdout and stderr to pipes
// - Handling the output from the child process
//
// The primary advantage of using posix_spawn() instead of the
// traditional fork()/execve() combination for launching processes is
// safety, efficiency, and cross-platform compatibility.
//
// 1. On Linux, FreeBSD, and NetBSD:
//
// Cosmopolitan Libc's posix_spawn() uses vfork() under the hood on
// these platforms automatically, since it's faster than fork(). It's
// because vfork() creates a child process without needing to copy
// the parent's page tables, making it more efficient, especially for
// large processes. Furthermore, vfork() avoids the need to acquire
// every single mutex (see pthread_atfork() for more details) which
// makes it scalable in multi-threaded apps, since the other threads
// in your app can keep going while the spawning thread waits for the
// subprocess to call execve(). Normally vfork() is error-prone since
// there exists few functions that are @vforksafe. the posix_spawn()
// API is designed to offer maximum assurance that you can't shoot
// yourself in the foot. If you do, then file a bug with Cosmo.
//
// 2. On Windows:
//
// posix_spawn() avoids fork() entirely. Windows doesn't natively
// support fork(), and emulating it can be slow and memory-intensive.
// By using posix_spawn(), we get a much faster process creation on
// Windows systems, because it only needs to call CreateProcess().
// Your file actions are replayed beforehand in a simulated way. Only
// Cosmopolitan Libc offers this level of quality. With Cygwin you'd
// have to use its proprietary APIs to achieve the same performance.
//
// 3. Simplified error handling:
//
// posix_spawn() combines process creation and program execution in a
// single call, reducing the points of failure and simplifying error
// handling. One important thing that happens with Cosmopolitan's
// posix_spawn() implementation is that the error code of execve()
// inside your subprocess, should it fail, will be propagated to your
// parent process. This will happen efficiently via vfork() shared
// memory in the event your Linux environment supports this. If it
// doesn't, then Cosmopolitan will fall back to a throwaway pipe().
// The pipe is needed on platforms like XNU and OpenBSD which do not
// support vfork(). It's also needed under QEMU User.
//
// 4. Signal safety:
//
// posix_spawn() guarantees your signal handler callback functions
// won't be executed in the child process. By default, it'll remove
// sigaction() callbacks atomically. This ensures that if something
// like a SIGTERM or SIGHUP is sent to the child process before it's
// had a chance to call execve(), then the child process will simply
// be terminated (like the spawned process would) instead of running
// whatever signal handlers the spawning process has installed. If
// you've set some signals to SIG_IGN, then that'll be preserved for
// the child process by posix_spawn(), unless you explicitly call
// posix_spawnattr_setsigdefault() to reset them.
//
// 5. Portability:
//
// posix_spawn() is part of the POSIX standard, making it more
// portable across different UNIX-like systems and Windows (with
// appropriate libraries). Even the non-POSIX APIs we use here are
// portable; e.g. posix_spawn_file_actions_addchdir_np() is supported
// by glibc, musl, freebsd, and apple too.
//
// These benefits make posix_spawn() a preferred choice for efficient
// and portable process creation in many scenarios, especially when
// launching many processes or on systems where process creation
// performance is critical.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <unistd.h>
#define max(X, Y) ((Y) < (X) ? (X) : (Y))
#define USE_SELECT 0 // want poll() or select()? they both work great
#define PIPE_READ 0
#define PIPE_WRITE 1
int main() {
errno_t err;
// Create spawn attributes object.
posix_spawnattr_t attr;
err = posix_spawnattr_init(&attr);
if (err != 0) {
fprintf(stderr, "posix_spawnattr_init failed: %s\n", strerror(err));
exit(1);
}
// Explicitly request vfork() from posix_spawn() implementation.
//
// This is currently the default for Cosmopolitan Libc, however you
// may want to set this anyway, for portability with other platforms.
// Please note that vfork() isn't officially specified by POSIX, so
// portable code may want to omit this and just use the default.
err = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK);
if (err != 0) {
fprintf(stderr, "posix_spawnattr_setflags: %s\n", strerror(err));
exit(2);
}
// Create file actions object.
posix_spawn_file_actions_t actions;
err = posix_spawn_file_actions_init(&actions);
if (err != 0) {
fprintf(stderr, "posix_spawn_file_actions_init: %s\n", strerror(err));
exit(3);
}
// Change directory to root directory in child process.
err = posix_spawn_file_actions_addchdir_np(&actions, "/");
if (err != 0) {
fprintf(stderr, "posix_spawn_file_actions_addchdir_np: %s\n",
strerror(err));
exit(4);
}
// Disable stdin in child process.
//
// By default, if you launch this example in your terminal, then child
// processes can read from your teletypewriter's keyboard too. You can
// avoid this by assigning /dev/null to standard input so if the child
// tries to read input, read() will return zero, indicating eof.
if ((err = posix_spawn_file_actions_addopen(&actions, STDIN_FILENO,
"/dev/null", O_RDONLY, 0644))) {
fprintf(stderr, "posix_spawn_file_actions_addopen: %s\n", strerror(err));
exit(5);
}
// Create pipes for stdout and stderr.
//
// Using O_DIRECT puts the pipe in message mode. This way we have some
// visibility into how the child process is using write(). It can also
// help ensure that logged lines won't be chopped up here, which could
// happen more frequently on platforms like Windows, which is somewhat
// less sophisticated than Linux with how it performs buffering.
//
// You can also specify O_CLOEXEC, which is a nice touch that lets you
// avoid needing to call posix_spawn_file_actions_addclose() later on.
// That's because all file descriptors are inherited by child programs
// by default. This is even the case with Cosmopolitan Libc on Windows
//
// XXX: We assume that stdin/stdout/stderr exist in this process. It's
// possible for a rogue parent process to launch this example, in
// a way where the following spawn logic will break.
int pipe_stdout[2];
int pipe_stderr[2];
if (pipe2(pipe_stdout, O_DIRECT) == -1 ||
pipe2(pipe_stderr, O_DIRECT) == -1) {
perror("pipe");
exit(6);
}
// Redirect child's stdout/stderr to pipes
if ((err = posix_spawn_file_actions_adddup2(&actions, pipe_stdout[PIPE_WRITE],
STDOUT_FILENO)) ||
(err = posix_spawn_file_actions_adddup2(&actions, pipe_stderr[PIPE_WRITE],
STDERR_FILENO))) {
fprintf(stderr, "posix_spawn_file_actions_adddup2: %s\n", strerror(err));
exit(7);
}
// Close unwanted write ends of pipes in the child process
if ((err = posix_spawn_file_actions_addclose(&actions,
pipe_stdout[PIPE_READ])) ||
(err = posix_spawn_file_actions_addclose(&actions,
pipe_stderr[PIPE_READ]))) {
fprintf(stderr, "posix_spawn_file_actions_addclose: %s\n", strerror(err));
exit(8);
};
// Asynchronously launch the child process.
pid_t pid;
char *const argv[] = {"ls", "--dired", NULL};
printf("** Launching `ls --dired` in root directory\n");
err = posix_spawnp(&pid, argv[0], &actions, NULL, argv, NULL);
if (err) {
fprintf(stderr, "posix_spawn: %s\n", strerror(err));
exit(9);
}
// Close unused write ends of pipes in the parent process
close(pipe_stdout[PIPE_WRITE]);
close(pipe_stderr[PIPE_WRITE]);
// we need poll() or select() because we're multiplexing output
// both poll() and select() work across all supported platforms
#if USE_SELECT
// Relay output from child process using select()
char buffer[512];
ssize_t got_stdout = 1;
ssize_t got_stderr = 1;
while (got_stdout > 0 || got_stderr > 0) {
fd_set rfds;
FD_ZERO(&rfds);
if (got_stdout > 0)
FD_SET(pipe_stdout[PIPE_READ], &rfds);
if (got_stderr > 0)
FD_SET(pipe_stderr[PIPE_READ], &rfds);
int nfds = max(pipe_stdout[PIPE_READ], pipe_stderr[PIPE_READ]) + 1;
if (select(nfds, &rfds, 0, 0, 0) == -1) {
perror("select");
exit(10);
}
if (FD_ISSET(pipe_stdout[PIPE_READ], &rfds)) {
got_stdout = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer));
printf("\n");
if (got_stdout > 0) {
printf("** Got stdout from child process:\n");
fflush(stdout);
write(STDOUT_FILENO, buffer, got_stdout);
} else if (!got_stdout) {
printf("** Got stdout EOF from child process\n");
} else {
printf("** Got stdout read() error from child process: %s\n",
strerror(errno));
}
}
if (FD_ISSET(pipe_stderr[PIPE_READ], &rfds)) {
got_stderr = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer));
printf("\n");
if (got_stderr > 0) {
printf("** Got stderr from child process:\n");
fflush(stdout);
write(STDOUT_FILENO, buffer, got_stderr);
} else if (!got_stderr) {
printf("** Got stderr EOF from child process\n");
} else {
printf("** Got stderr read() error from child process: %s\n",
strerror(errno));
}
}
}
#else
// Relay output from child process using poll()
char buffer[512];
ssize_t got_stdout = 1;
ssize_t got_stderr = 1;
while (got_stdout > 0 || got_stderr > 0) {
struct pollfd fds[2];
fds[0].fd = got_stdout > 0 ? pipe_stdout[PIPE_READ] : -1;
fds[0].events = POLLIN; // POLLHUP, POLLNVAL, and POLLERR are implied
fds[1].fd = got_stderr > 0 ? pipe_stderr[PIPE_READ] : -1;
fds[1].events = POLLIN; // POLLHUP, POLLNVAL, and POLLERR are implied
if (poll(fds, 2, -1) == -1) {
perror("select");
exit(10);
}
if (fds[0].revents) {
printf("\n");
if (fds[0].revents & POLLIN)
printf("** Got POLLIN on stdout from child process\n");
if (fds[0].revents & POLLHUP)
printf("** Got POLLHUP on stdout from child process\n");
if (fds[0].revents & POLLERR)
printf("** Got POLLERR on stdout from child process\n");
if (fds[0].revents & POLLNVAL)
printf("** Got POLLNVAL on stdout from child process\n");
got_stdout = read(pipe_stdout[PIPE_READ], buffer, sizeof(buffer));
if (got_stdout > 0) {
printf("** Got stdout from child process:\n");
fflush(stdout);
write(STDOUT_FILENO, buffer, got_stdout);
} else if (!got_stdout) {
printf("** Got stdout EOF from child process\n");
} else {
printf("** Got stdout read() error from child process: %s\n",
strerror(errno));
}
}
if (fds[1].revents) {
printf("\n");
if (fds[1].revents & POLLIN)
printf("** Got POLLIN on stderr from child process\n");
if (fds[1].revents & POLLHUP)
printf("** Got POLLHUP on stderr from child process\n");
if (fds[1].revents & POLLERR)
printf("** Got POLLERR on stderr from child process\n");
if (fds[1].revents & POLLNVAL)
printf("** Got POLLNVAL on stderr from child process\n");
got_stderr = read(pipe_stderr[PIPE_READ], buffer, sizeof(buffer));
if (got_stderr > 0) {
printf("** Got stderr from child process:\n");
fflush(stdout);
write(STDOUT_FILENO, buffer, got_stderr);
} else if (!got_stderr) {
printf("** Got stderr EOF from child process\n");
} else {
printf("** Got stderr read() error from child process: %s\n",
strerror(errno));
}
}
}
#endif
// Wait for child process to die.
int wait_status;
if (waitpid(pid, &wait_status, 0) == -1) {
perror("waitpid");
exit(11);
}
// Clean up resources.
posix_spawn_file_actions_destroy(&actions);
posix_spawnattr_destroy(&attr);
close(pipe_stdout[PIPE_READ]);
close(pipe_stderr[PIPE_READ]);
// Report wait status.
//
// When a process dies, it's almost always due to calling _Exit() or
// being killed due to an unhandled signal. On both UNIX and Windows
// this information will be propagated to the parent. That status is
// able to be propagated to the parent of this process too.
printf("\n");
if (WIFEXITED(wait_status)) {
printf("** Child process exited with exit code %d\n",
WEXITSTATUS(wait_status));
exit(WEXITSTATUS(wait_status));
} else if (WIFSIGNALED(wait_status)) {
printf("** Child process terminated with signal %s\n",
strsignal(WTERMSIG(wait_status)));
fflush(stdout);
sigset_t sm;
sigemptyset(&sm);
sigaddset(&sm, WTERMSIG(wait_status));
sigprocmask(SIG_UNBLOCK, &sm, 0);
signal(SIGABRT, SIG_DFL);
raise(WTERMSIG(wait_status));
exit(128 + WTERMSIG(wait_status));
} else {
printf("** Child process exited weirdly with wait status 0x%08x\n",
wait_status);
exit(12);
}
}

View file

@ -7,20 +7,24 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include <spawn.h>
#include <stdalign.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/weirdtypes.h"
#include "libc/mem/mem.h"
#include "libc/proc/posix_spawn.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/clock.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#define ITERATIONS 10
alignas(128) int a;
alignas(128) int b;
alignas(128) atomic_int lock;
_Alignas(128) int a;
_Alignas(128) int b;
_Alignas(128) atomic_int lock;
static struct timespec SubtractTime(struct timespec a, struct timespec b) {
a.tv_sec -= b.tv_sec;
@ -113,11 +117,6 @@ int main(int argc, char *argv[]) {
void *p;
const char *prog;
// if you need the tiny64 program for windows:
//
// make -j o//tool/hello/life-pe.ape
// scp o//tool/hello/life-pe.ape windows:tiny64
//
if (argc <= 1) {
prog = "tiny64";
} else {

View file

@ -7,13 +7,9 @@
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include "libc/dce.h"
#include "libc/intrin/maps.h"
#include "libc/mem/alg.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/winargs.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/x/xasprintf.h"
@ -71,18 +67,8 @@ int main(int argc, char *argv[]) {
Append((uintptr_t)&__auxv[i + 1],
xasprintf("&auxv[%d] = %#lx", i + 1, __auxv[i + 1]));
}
if (!IsWindows()) {
struct AddrSize stak = __get_main_stack();
Append((intptr_t)stak.addr + stak.size, "top of stack");
Append((intptr_t)stak.addr, "bottom of stack");
} else {
#ifdef __x86_64__
Append(GetStaticStackAddr(0) + GetStaticStackSize(), "top of stack");
Append(GetStaticStackAddr(0) + GetGuardSize(), "bottom of stack");
Append(GetStaticStackAddr(0), "bottom of guard region");
#endif
}
qsort(things.p, things.n, sizeof(*things.p), Compare);
for (int i = 0; i < things.n; ++i)
for (int i = 0; i < things.n; ++i) {
printf("%012lx %s\n", things.p[i].i, things.p[i].s);
}
}

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