mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-08-03 08:20:28 +00:00
Compare commits
260 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f1e83d5240 | ||
|
2fe8338f92 | ||
|
4ca513cba2 | ||
|
455910e8f2 | ||
|
9c68bc19b5 | ||
|
66d1050af6 | ||
|
fbc4fcbb71 | ||
|
afc986f741 | ||
|
5eb7cd6643 | ||
|
a8ed4fdd09 | ||
|
7b69652854 | ||
|
b235492e71 | ||
|
fc81fd8d16 | ||
|
38930de8e0 | ||
|
0e557d041d | ||
|
1d676b36e6 | ||
|
10a92cee94 | ||
|
42a9ed0131 | ||
|
12cb0669fb | ||
|
7f6a7d6fff | ||
|
9f6bf6ea71 | ||
|
102edf4ea2 | ||
|
21968acf99 | ||
|
98861b23fc | ||
|
dab6d7a345 | ||
|
90119c422c | ||
|
5907304049 | ||
|
035b0e2a62 | ||
|
7b67b20dae | ||
|
f0b0f926bf | ||
|
29eb7e67bb | ||
|
f71f61cd40 | ||
|
53c6edfd18 | ||
|
42a3bb729a | ||
|
c97a858470 | ||
|
4acd12a514 | ||
|
b734eec836 | ||
|
fe01642a20 | ||
|
e939659b70 | ||
|
ed6d133a27 | ||
|
97fc2aab41 | ||
|
662e7b217f | ||
|
27f2777cc6 | ||
|
538ce338f4 | ||
|
a15958edc6 | ||
|
8db646f6b2 | ||
|
fde03f8487 | ||
|
f24c854b28 | ||
|
0b3c81dd4e | ||
|
98c5847727 | ||
|
fd7da586b5 | ||
|
a51ccc8fb1 | ||
|
c7e3d9f7ff | ||
|
9ba5b227d9 | ||
|
aca4214ff6 | ||
|
379cd77078 | ||
|
36e5861b0c | ||
|
cc8a9eb93c | ||
|
0158579493 | ||
|
2de3845b25 | ||
|
93e22c581f | ||
|
ec2db4e40e | ||
|
55b7aa1632 | ||
|
4705705548 | ||
|
c8e10eef30 | ||
|
624573207e | ||
|
906bd06a5a | ||
|
c8c81af0c7 | ||
|
af7bd80430 | ||
|
26c051c297 | ||
|
9cc1bd04b2 | ||
|
69402f4d78 | ||
|
838b54f906 | ||
|
2d43d400c6 | ||
|
c22b413ac4 | ||
|
22094ae9ca | ||
|
bda2a4d55e | ||
|
b490e23d63 | ||
|
b40140e6c5 | ||
|
3142758675 | ||
|
cf9252f429 | ||
|
5fae582e82 | ||
|
ef00a7d0c2 | ||
|
746660066f | ||
|
fd15b2d7a3 | ||
|
e228aa3e14 | ||
|
9ddbfd921e | ||
|
729f7045e3 | ||
|
e47d67ba9b | ||
|
2477677c85 | ||
|
abdf6c9c26 | ||
|
5c3f854acb | ||
|
ad0a7c67c4 | ||
|
1312f60245 | ||
|
cafdb456ed | ||
|
4e9566cd33 | ||
|
5ce5fb6f2a | ||
|
d3279d3c0d | ||
|
e62ff3e19c | ||
|
913b573661 | ||
|
9add248c9b | ||
|
beb090b83f | ||
|
107d335c0d | ||
|
bd6630d62d | ||
|
a120bc7149 | ||
|
baad1df71d | ||
|
4e44517c9c | ||
|
26663dea9c | ||
|
23da0d75a5 | ||
|
4b2a00fd4a | ||
|
2f4e6e8d77 | ||
|
dd249ff5d4 | ||
|
4abcba8d8f | ||
|
dc1afc968b | ||
|
5edc0819c0 | ||
|
706cb66310 | ||
|
a8bc7ac119 | ||
|
d8fac40f55 | ||
|
000d6dbb0f | ||
|
17a85e4790 | ||
|
ad11fc32ad | ||
|
dcf9596620 | ||
|
85c58be942 | ||
|
e4d6eb382a | ||
|
fef24d622a | ||
|
12cc2de22e | ||
|
70603fa6ea | ||
|
9255113011 | ||
|
333c3d1f0a | ||
|
518eabadf5 | ||
|
556a294363 | ||
|
80804ccfff | ||
|
4a7dd31567 | ||
|
d730fc668c | ||
|
e975245102 | ||
|
126a44dc49 | ||
|
476926790a | ||
|
dd8c4dbd7d | ||
|
0e59afb403 | ||
|
f68fc1f815 | ||
|
6107eb38f9 | ||
|
d50f4c02f6 | ||
|
0d74673213 | ||
|
8527462b95 | ||
|
8313dca982 | ||
|
87a6669900 | ||
|
ce2fbf9325 | ||
|
1bfb348403 | ||
|
aaed879ec7 | ||
|
8201ef2b3d | ||
|
b1c9801897 | ||
|
f7754ab608 | ||
|
96abe91c29 | ||
|
bb7942e557 | ||
|
b14dddcc18 | ||
|
65e425fbca | ||
|
774c67fcd3 | ||
|
3c58ecd00c | ||
|
5aa970bc4e | ||
|
56ca00b022 | ||
|
ecbf453464 | ||
|
c3482af66d | ||
|
b73673e984 | ||
|
e260d90096 | ||
|
81bc8d0963 | ||
|
ef62730ae4 | ||
|
6397999fca | ||
|
b55e4d61a9 | ||
|
e65fe614b7 | ||
|
949c398327 | ||
|
baf70af780 | ||
|
c260144843 | ||
|
37e2660c7f | ||
|
675abfa029 | ||
|
e3d28de8a6 | ||
|
7f21547122 | ||
|
19563d37c1 | ||
|
ed1f992cb7 | ||
|
7d2c363963 | ||
|
462ba6909e | ||
|
b5fcb59a85 | ||
|
6b10f4d0b6 | ||
|
1260f9d0ed | ||
|
e142124730 | ||
|
5469202ea8 | ||
|
acd6c32184 | ||
|
6f868fe1de | ||
|
0f3457c172 | ||
|
a5c0189bf6 | ||
|
deb5e07b5a | ||
|
4d05060aac | ||
|
51c0f44d1c | ||
|
fbdf9d028c | ||
|
cceddd21b2 | ||
|
a0a404a431 | ||
|
2f48a02b44 | ||
|
58d252f3db | ||
|
95fee8614d | ||
|
d50d954a3c | ||
|
d99f066114 | ||
|
4754f200ee | ||
|
f882887178 | ||
|
dc579b79cd | ||
|
c66abd7260 | ||
|
d1157d471f | ||
|
5d3b91d8b9 | ||
|
07fde68d52 | ||
|
41fc76c2b8 | ||
|
1e9902af8b | ||
|
df04ab846a | ||
|
0d6ff04b87 | ||
|
03875beadb | ||
|
dd8544c3bd | ||
|
8f8145105c | ||
|
3c61a541bd | ||
|
79516bf08e | ||
|
90460ceb3c | ||
|
2ec413b5a9 | ||
|
39e7f24947 | ||
|
a089c07ddc | ||
|
ae57fa2c4e | ||
|
48b703b3f6 | ||
|
389d565d46 | ||
|
75e161b27b | ||
|
cca0edd62b | ||
|
7c83f4abc8 | ||
|
e1528a71e2 | ||
|
a6fe62cf13 | ||
|
c9152b6f14 | ||
|
5b9862907c | ||
|
c2420860e6 | ||
|
6baf6cdb10 | ||
|
06a1193b4d | ||
|
884d89235f | ||
|
610c951f71 | ||
|
12ecaf8650 | ||
|
185e957696 | ||
|
ebe1cbb1e3 | ||
|
e7b586e7f8 | ||
|
111ec9a989 | ||
|
f3ce684aef | ||
|
908b7a82ca | ||
|
bb06230f1e | ||
|
38cc4b3c68 | ||
|
37ca1badaf | ||
|
1a9f82bc9f | ||
|
df1aee7ce5 | ||
|
2d44142444 | ||
|
4bbc16e2cc | ||
|
863c704684 | ||
|
60e697f7b2 | ||
|
4389f4709a | ||
|
ca2c30c977 | ||
|
eb6e96f036 | ||
|
77be460290 | ||
|
8e14b27749 | ||
|
1d532ba3f8 | ||
|
b2a1811c01 | ||
|
2eda50929b | ||
|
098638cc6c |
1587 changed files with 334859 additions and 28534 deletions
8
.gitattributes
vendored
8
.gitattributes
vendored
|
@ -1,4 +1,10 @@
|
|||
# -*- conf -*-
|
||||
*.gz binary
|
||||
/build/bootstrap/*.com binary
|
||||
*.so binary
|
||||
*.dll binary
|
||||
*.dylib binary
|
||||
/build/bootstrap/* binary
|
||||
/usr/share/terminfo/* binary
|
||||
/usr/share/terminfo/*/* binary
|
||||
/usr/share/zoneinfo/* binary
|
||||
/usr/share/zoneinfo/*/* binary
|
||||
|
|
42
.github/workflows/build.yml
vendored
42
.github/workflows/build.yml
vendored
|
@ -1,5 +1,8 @@
|
|||
name: build
|
||||
|
||||
env:
|
||||
COSMOCC_VERSION: 3.9.2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
@ -19,13 +22,48 @@ jobs:
|
|||
matrix:
|
||||
mode: ["", tiny, rel, tinylinux, optlinux]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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
|
||||
|
||||
- name: support ape bins 1
|
||||
run: sudo cp build/bootstrap/ape.elf /usr/bin/ape
|
||||
run: sudo cp -a 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
1
.gitignore
vendored
|
@ -15,3 +15,4 @@ __pycache__
|
|||
/tool/emacs/*.elc
|
||||
/perf.data
|
||||
/perf.data.old
|
||||
/qemu*core
|
||||
|
|
36
.vscode/settings.json
vendored
Normal file
36
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"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
128
Makefile
|
@ -77,7 +77,8 @@ COMMA := ,
|
|||
PWD := $(shell pwd)
|
||||
|
||||
# detect wsl2 running cosmopolitan binaries on the host by checking whether:
|
||||
# - user ran build/bootstrap/make, in which case make's working directory is in wsl
|
||||
# - user ran .cosmocc/current/bin/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)
|
||||
|
@ -89,7 +90,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 build/bootstrap/make)
|
||||
$(error please use https://cosmo.zip/pub/cosmos/bin/make)
|
||||
endif
|
||||
|
||||
LC_ALL = C
|
||||
|
@ -135,7 +136,7 @@ ARCH = aarch64
|
|||
HOSTS ?= pi pi5 studio freebsdarm
|
||||
else
|
||||
ARCH = x86_64
|
||||
HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10
|
||||
HOSTS ?= freebsd rhel7 xnu openbsd netbsd win10 luna
|
||||
endif
|
||||
|
||||
ZIPOBJ_FLAGS += -a$(ARCH)
|
||||
|
@ -147,10 +148,10 @@ export MODE
|
|||
export SOURCE_DATE_EPOCH
|
||||
export TMPDIR
|
||||
|
||||
COSMOCC = .cosmocc/3.6.2
|
||||
COSMOCC = .cosmocc/3.9.2
|
||||
BOOTSTRAP = $(COSMOCC)/bin
|
||||
TOOLCHAIN = $(COSMOCC)/bin/$(ARCH)-linux-cosmo-
|
||||
DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.6.2 268aa82d9bfd774f76951b250f87b8edcefd5c754b8b409e1639641e8bd8d5bc)
|
||||
DOWNLOAD := $(shell build/download-cosmocc.sh $(COSMOCC) 3.9.2 f4ff13af65fcd309f3f1cfd04275996fb7f72a4897726628a8c9cf732e850193)
|
||||
|
||||
IGNORE := $(shell $(MKDIR) $(TMPDIR))
|
||||
|
||||
|
@ -274,10 +275,16 @@ 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 # │
|
||||
|
@ -291,8 +298,7 @@ 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 third_party/regex/BUILD.mk #─┘
|
||||
include net/https/BUILD.mk #─┘
|
||||
include third_party/tidy/BUILD.mk
|
||||
include third_party/BUILD.mk
|
||||
include third_party/nsync/testing/BUILD.mk
|
||||
|
@ -311,8 +317,6 @@ 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
|
||||
|
@ -365,6 +369,7 @@ 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
|
||||
|
@ -428,68 +433,71 @@ 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 \*.c -or -name \*.S | \
|
||||
find -name \*.h -or -name \*.hpp -or -name \*.c -or -name \*.cc -or -name \*.cpp -or -name \*.S -or -name \*.mk | \
|
||||
$(XARGS) wc -l | grep total | awk '{print $$1}' | $<
|
||||
|
||||
# PLEASE: MAINTAIN TOPOLOGICAL ORDER
|
||||
# FROM HIGHEST LEVEL TO LOWEST LEVEL
|
||||
COSMOPOLITAN_OBJECTS = \
|
||||
COSMOPOLITAN = \
|
||||
CTL \
|
||||
THIRD_PARTY_DOUBLECONVERSION \
|
||||
THIRD_PARTY_OPENMP \
|
||||
TOOL_ARGS \
|
||||
NET_HTTP \
|
||||
LIBC_SOCK \
|
||||
LIBC_NT_WS2_32 \
|
||||
LIBC_NT_IPHLPAPI \
|
||||
LIBC_X \
|
||||
THIRD_PARTY_GETOPT \
|
||||
DSP_AUDIO \
|
||||
LIBC_CALLS \
|
||||
LIBC_DLOPEN \
|
||||
LIBC_ELF \
|
||||
LIBC_FMT \
|
||||
LIBC_INTRIN \
|
||||
LIBC_IRQ \
|
||||
LIBC_LOG \
|
||||
THIRD_PARTY_TZ \
|
||||
THIRD_PARTY_MUSL \
|
||||
THIRD_PARTY_ZLIB_GZ \
|
||||
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_GETOPT \
|
||||
THIRD_PARTY_LIBCXXABI \
|
||||
THIRD_PARTY_LIBUNWIND \
|
||||
LIBC_STDIO \
|
||||
THIRD_PARTY_GDTOA \
|
||||
THIRD_PARTY_REGEX \
|
||||
LIBC_THREAD \
|
||||
LIBC_PROC \
|
||||
THIRD_PARTY_NSYNC_MEM \
|
||||
LIBC_MEM \
|
||||
THIRD_PARTY_DLMALLOC \
|
||||
LIBC_DLOPEN \
|
||||
LIBC_RUNTIME \
|
||||
THIRD_PARTY_MUSL \
|
||||
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_NSYNC_MEM \
|
||||
THIRD_PARTY_OPENMP \
|
||||
THIRD_PARTY_PUFF \
|
||||
THIRD_PARTY_COMPILER_RT \
|
||||
LIBC_TINYMATH \
|
||||
THIRD_PARTY_REGEX \
|
||||
THIRD_PARTY_TZ \
|
||||
THIRD_PARTY_XED \
|
||||
LIBC_STR \
|
||||
LIBC_SYSV \
|
||||
LIBC_INTRIN \
|
||||
LIBC_NT_BCRYPTPRIMITIVES \
|
||||
LIBC_NT_KERNEL32 \
|
||||
LIBC_NEXGEN32E
|
||||
THIRD_PARTY_ZLIB \
|
||||
THIRD_PARTY_ZLIB_GZ \
|
||||
TOOL_ARGS \
|
||||
|
||||
COSMOPOLITAN_H_PKGS = \
|
||||
APE \
|
||||
DSP_AUDIO \
|
||||
LIBC \
|
||||
LIBC_CALLS \
|
||||
LIBC_ELF \
|
||||
|
@ -533,7 +541,7 @@ COSMOCC_PKGS = \
|
|||
THIRD_PARTY_INTEL
|
||||
|
||||
o/$(MODE)/cosmopolitan.a: \
|
||||
$(foreach x,$(COSMOPOLITAN_OBJECTS),$($(x)_A_OBJS))
|
||||
$(call reverse,$(call uniq,$(foreach x,$(COSMOPOLITAN),$($(x)))))
|
||||
|
||||
COSMOCC_HDRS = \
|
||||
$(wildcard libc/integral/*) \
|
||||
|
|
31
README.md
31
README.md
|
@ -3,12 +3,12 @@
|
|||
[](https://github.com/jart/cosmopolitan/actions/workflows/build.yml)
|
||||
# Cosmopolitan
|
||||
|
||||
[Cosmopolitan Libc](https://justine.lol/cosmopolitan/index.html) makes C
|
||||
[Cosmopolitan Libc](https://justine.lol/cosmopolitan/index.html) makes C/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 + NetBSD + BIOS with the best
|
||||
possible performance and the tiniest footprint imaginable.
|
||||
Linux + Mac + Windows + FreeBSD + OpenBSD 7.3 + NetBSD + BIOS with the
|
||||
best possible performance and the tiniest footprint imaginable.
|
||||
|
||||
## Background
|
||||
|
||||
|
@ -87,15 +87,22 @@ ape/apeinstall.sh
|
|||
```
|
||||
|
||||
You can now build the mono repo with any modern version of GNU Make. To
|
||||
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.
|
||||
bootstrap your build, you can install Cosmopolitan Make from this site:
|
||||
|
||||
https://cosmo.zip/pub/cosmos/bin/make
|
||||
|
||||
E.g.:
|
||||
|
||||
```sh
|
||||
build/bootstrap/make -j8
|
||||
curl -LO https://cosmo.zip/pub/cosmos/bin/make
|
||||
./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
|
||||
|
@ -103,7 +110,7 @@ depends on core LIBC packages.
|
|||
|
||||
```sh
|
||||
rm -rf o//libc o//test
|
||||
build/bootstrap/make o//test/posix/signal_test
|
||||
.cosmocc/current/bin/make o//test/posix/signal_test
|
||||
o//test/posix/signal_test
|
||||
```
|
||||
|
||||
|
@ -112,21 +119,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
|
||||
build/bootstrap/make o//test/posix
|
||||
.cosmocc/current/bin/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
|
||||
build/bootstrap/make m=tiny
|
||||
.cosmocc/current/bin/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
|
||||
build/bootstrap/make m=tinylinux
|
||||
.cosmocc/current/bin/make m=tinylinux
|
||||
```
|
||||
|
||||
For further details, see [//build/config.mk](build/config.mk).
|
||||
|
@ -249,7 +256,7 @@ server. You're welcome to join us! <https://discord.gg/FwAVVu7eJ4>
|
|||
| Linux | 2.6.18 | 2007 |
|
||||
| Windows | 8 [1] | 2012 |
|
||||
| Darwin (macOS) | 23.1.0+ | 2023 |
|
||||
| OpenBSD | 7 | 2021 |
|
||||
| OpenBSD | 7.3 or earlier | 2023 |
|
||||
| FreeBSD | 13 | 2020 |
|
||||
| NetBSD | 9.2 | 2021 |
|
||||
|
||||
|
|
|
@ -103,10 +103,8 @@ SECTIONS {
|
|||
*(.eh_frame_entry .eh_frame_entry.*)
|
||||
}
|
||||
|
||||
.eh_frame : ONLY_IF_RO {
|
||||
KEEP(*(.eh_frame))
|
||||
*(.eh_frame.*)
|
||||
}
|
||||
__eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0;
|
||||
__eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0;
|
||||
|
||||
.gcc_except_table : ONLY_IF_RO {
|
||||
*(.gcc_except_table .gcc_except_table.*)
|
||||
|
@ -127,9 +125,11 @@ SECTIONS {
|
|||
. += CONSTANT(MAXPAGESIZE);
|
||||
. = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(COMMONPAGESIZE));
|
||||
|
||||
.eh_frame : ONLY_IF_RW {
|
||||
.eh_frame : {
|
||||
__eh_frame_start = .;
|
||||
KEEP(*(.eh_frame))
|
||||
*(.eh_frame.*)
|
||||
__eh_frame_end = .;
|
||||
}
|
||||
|
||||
.gnu_extab : ONLY_IF_RW {
|
||||
|
@ -259,6 +259,9 @@ 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)) }
|
||||
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
│ 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>
|
||||
|
|
28
ape/ape.lds
28
ape/ape.lds
|
@ -329,6 +329,10 @@ SECTIONS {
|
|||
*(.ubsan.types)
|
||||
*(.ubsan.data)
|
||||
|
||||
__eh_frame_hdr_start_actual = .;
|
||||
*(.eh_frame_hdr)
|
||||
__eh_frame_hdr_end_actual = .;
|
||||
|
||||
/* Legal Notices */
|
||||
__notices = .;
|
||||
KEEP(*(.notice))
|
||||
|
@ -382,6 +386,13 @@ 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()
|
||||
|
@ -430,7 +441,6 @@ SECTIONS {
|
|||
KEEP(*(.piro.pad.data))
|
||||
*(.igot.plt)
|
||||
KEEP(*(.dataepilogue))
|
||||
|
||||
. = ALIGN(. != 0 ? CONSTANT(COMMONPAGESIZE) : 0);
|
||||
/*END: NT FORK COPYING */
|
||||
_edata = .;
|
||||
|
@ -510,6 +520,9 @@ 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) }
|
||||
|
||||
|
@ -573,11 +586,11 @@ ape_rom_memsz = ape_rom_filesz;
|
|||
ape_rom_align = CONSTANT(COMMONPAGESIZE);
|
||||
ape_rom_rva = RVA(ape_rom_vaddr);
|
||||
|
||||
ape_ram_vaddr = ADDR(.data);
|
||||
ape_ram_vaddr = ADDR(.eh_frame);
|
||||
ape_ram_offset = ape_ram_vaddr - __executable_start;
|
||||
ape_ram_paddr = LOADADDR(.data);
|
||||
ape_ram_filesz = ADDR(.bss) - ADDR(.data);
|
||||
ape_ram_memsz = _end - ADDR(.data);
|
||||
ape_ram_paddr = LOADADDR(.eh_frame);
|
||||
ape_ram_filesz = ADDR(.bss) - ADDR(.eh_frame);
|
||||
ape_ram_memsz = _end - ADDR(.eh_frame);
|
||||
ape_ram_align = CONSTANT(COMMONPAGESIZE);
|
||||
ape_ram_rva = RVA(ape_ram_vaddr);
|
||||
|
||||
|
@ -587,7 +600,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 : 8 * 1024 * 1024;
|
||||
ape_stack_memsz = DEFINED(ape_stack_memsz) ? ape_stack_memsz : 4 * 1024 * 1024;
|
||||
|
||||
ape_note_offset = ape_cod_offset + (ape_note - ape_cod_vaddr);
|
||||
ape_note_filesz = ape_note_end - ape_note;
|
||||
|
@ -601,6 +614,9 @@ 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 */
|
||||
|
|
|
@ -10,8 +10,8 @@ if [ ! -f ape/loader.c ]; then
|
|||
cd "$COSMO" || exit
|
||||
fi
|
||||
|
||||
if [ -x build/bootstrap/make ]; then
|
||||
MAKE=build/bootstrap/make
|
||||
if [ -x .cosmocc/current/bin/make ]; then
|
||||
MAKE=.cosmocc/current/bin/make
|
||||
else
|
||||
MAKE=make
|
||||
fi
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -92,10 +92,7 @@ DEFAULT_COPTS ?= \
|
|||
-fno-gnu-unique \
|
||||
-fstrict-aliasing \
|
||||
-fstrict-overflow \
|
||||
-fno-semantic-interposition \
|
||||
-fno-dwarf2-cfi-asm \
|
||||
-fno-unwind-tables \
|
||||
-fno-asynchronous-unwind-tables
|
||||
-fno-semantic-interposition
|
||||
|
||||
ifeq ($(ARCH), x86_64)
|
||||
# Microsoft says "[a]ny memory below the stack beyond the red zone
|
||||
|
@ -139,8 +136,6 @@ DEFAULT_CFLAGS = \
|
|||
|
||||
DEFAULT_CXXFLAGS = \
|
||||
-std=gnu++23 \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-fuse-cxa-atexit \
|
||||
-Wno-int-in-bool-context \
|
||||
-Wno-narrowing \
|
||||
|
|
|
@ -99,3 +99,8 @@ 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"
|
||||
|
|
|
@ -6,14 +6,14 @@ if [ -n "$OBJDUMP" ]; then
|
|||
fi
|
||||
|
||||
find_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"
|
||||
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"
|
||||
else
|
||||
echo "error: toolchain not found (try running 'cosmocc --update' or 'make' in the cosmo monorepo)" >&2
|
||||
exit 1
|
||||
|
|
|
@ -4,5 +4,5 @@ UNAMES=$(uname -s)
|
|||
if [ x"$UNAMES" = x"Darwin" ] && [ x"$UNAMEM" = x"arm64" ]; then
|
||||
exec ape "$@"
|
||||
else
|
||||
exec "$@"
|
||||
exec rusage "$@"
|
||||
fi
|
||||
|
|
|
@ -17,6 +17,9 @@ 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_
|
||||
|
|
|
@ -19,6 +19,9 @@ 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_
|
||||
|
|
15
ctl/set.h
15
ctl/set.h
|
@ -241,8 +241,9 @@ class set
|
|||
private:
|
||||
friend class set;
|
||||
node_type* node_;
|
||||
node_type* root_;
|
||||
|
||||
explicit reverse_iterator(node_type* node) : node_(node)
|
||||
explicit reverse_iterator(node_type* node, node_type* root) : node_(node), root_(root)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -347,17 +348,17 @@ class set
|
|||
|
||||
reverse_iterator rbegin()
|
||||
{
|
||||
return reverse_iterator(rightmost(root_));
|
||||
return reverse_iterator(rightmost(root_), root_);
|
||||
}
|
||||
|
||||
const_reverse_iterator rbegin() const
|
||||
{
|
||||
return const_reverse_iterator(rightmost(root_));
|
||||
return const_reverse_iterator(rightmost(root_), root_);
|
||||
}
|
||||
|
||||
const_reverse_iterator crbegin() const
|
||||
{
|
||||
return const_reverse_iterator(rightmost(root_));
|
||||
return const_reverse_iterator(rightmost(root_), root_);
|
||||
}
|
||||
|
||||
iterator end() noexcept
|
||||
|
@ -377,17 +378,17 @@ class set
|
|||
|
||||
reverse_iterator rend()
|
||||
{
|
||||
return reverse_iterator(nullptr);
|
||||
return reverse_iterator(nullptr, root_);
|
||||
}
|
||||
|
||||
const_reverse_iterator rend() const
|
||||
{
|
||||
return const_reverse_iterator(nullptr);
|
||||
return const_reverse_iterator(nullptr, root_);
|
||||
}
|
||||
|
||||
const_reverse_iterator crend() const
|
||||
{
|
||||
return const_reverse_iterator(nullptr);
|
||||
return const_reverse_iterator(nullptr, root_);
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
|
|
618
ctl/shared_ptr.h
Normal file
618
ctl/shared_ptr.h
Normal file
|
@ -0,0 +1,618 @@
|
|||
// -*-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_
|
|
@ -383,4 +383,72 @@ 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
|
||||
|
|
|
@ -125,6 +125,7 @@ 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;
|
||||
|
@ -136,6 +137,10 @@ 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
|
||||
{
|
||||
|
@ -302,7 +307,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);
|
||||
}
|
||||
|
|
|
@ -108,4 +108,66 @@ 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
|
||||
|
|
|
@ -45,6 +45,10 @@ 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
|
||||
{
|
||||
|
@ -109,12 +113,12 @@ struct string_view
|
|||
return p[n - 1];
|
||||
}
|
||||
|
||||
constexpr const_iterator begin() noexcept
|
||||
constexpr const_iterator begin() const noexcept
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
constexpr const_iterator end() noexcept
|
||||
constexpr const_iterator end() const noexcept
|
||||
{
|
||||
return p + n;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
|
||||
|
||||
.PHONY: o/$(MODE)/dsp
|
||||
o/$(MODE)/dsp: o/$(MODE)/dsp/core \
|
||||
o/$(MODE)/dsp: o/$(MODE)/dsp/audio \
|
||||
o/$(MODE)/dsp/core \
|
||||
o/$(MODE)/dsp/mpeg \
|
||||
o/$(MODE)/dsp/scale \
|
||||
o/$(MODE)/dsp/prog \
|
||||
o/$(MODE)/dsp/tty
|
||||
|
|
56
dsp/audio/BUILD.mk
Normal file
56
dsp/audio/BUILD.mk
Normal file
|
@ -0,0 +1,56 @@
|
|||
#-*-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)
|
358
dsp/audio/audio.c
Normal file
358
dsp/audio/audio.c
Normal file
|
@ -0,0 +1,358 @@
|
|||
/*-*- 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;
|
||||
}
|
3
dsp/audio/cosmoaudio/.gitignore
vendored
Normal file
3
dsp/audio/cosmoaudio/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
/Debug
|
||||
/Release
|
87
dsp/audio/cosmoaudio/Makefile.msvc
Normal file
87
dsp/audio/cosmoaudio/Makefile.msvc
Normal file
|
@ -0,0 +1,87 @@
|
|||
# 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
|
519
dsp/audio/cosmoaudio/cosmoaudio.c
Normal file
519
dsp/audio/cosmoaudio/cosmoaudio.c
Normal file
|
@ -0,0 +1,519 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
BIN
dsp/audio/cosmoaudio/cosmoaudio.dll
Normal file
BIN
dsp/audio/cosmoaudio/cosmoaudio.dll
Normal file
Binary file not shown.
104
dsp/audio/cosmoaudio/cosmoaudio.h
Normal file
104
dsp/audio/cosmoaudio/cosmoaudio.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
#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_ */
|
92621
dsp/audio/cosmoaudio/miniaudio.h
Normal file
92621
dsp/audio/cosmoaudio/miniaudio.h
Normal file
File diff suppressed because it is too large
Load diff
76
dsp/audio/cosmoaudio/test.c
Normal file
76
dsp/audio/cosmoaudio/test.c
Normal file
|
@ -0,0 +1,76 @@
|
|||
#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;
|
||||
}
|
||||
}
|
121
dsp/audio/describe.c
Normal file
121
dsp/audio/describe.c
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*-*- 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;
|
||||
}
|
12
dsp/audio/describe.h
Normal file
12
dsp/audio/describe.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#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_ */
|
2
dsp/mpeg/.clang-format
Normal file
2
dsp/mpeg/.clang-format
Normal file
|
@ -0,0 +1,2 @@
|
|||
DisableFormat: true
|
||||
SortIncludes: Never
|
|
@ -25,18 +25,13 @@ 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))))
|
||||
|
@ -49,9 +44,10 @@ $(DSP_MPEG_A).pkg: \
|
|||
$(DSP_MPEG_A_OBJS) \
|
||||
$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x)_A).pkg)
|
||||
|
||||
o/$(MODE)/dsp/mpeg/clamp4int256-k8.o: private \
|
||||
o/$(MODE)/dsp/mpeg/pl_mpeg.o: private \
|
||||
CFLAGS += \
|
||||
-Os
|
||||
-ffunction-sections \
|
||||
-fdata-sections
|
||||
|
||||
DSP_MPEG_LIBS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)))
|
||||
DSP_MPEG_SRCS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_SRCS))
|
||||
|
|
17
dsp/mpeg/README.cosmo
Normal file
17
dsp/mpeg/README.cosmo
Normal file
|
@ -0,0 +1,17 @@
|
|||
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
|
68
dsp/mpeg/README.md
Executable file
68
dsp/mpeg/README.md
Executable file
|
@ -0,0 +1,68 @@
|
|||
# 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.
|
|
@ -1,92 +0,0 @@
|
|||
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.
|
|
@ -1,20 +0,0 @@
|
|||
#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_ */
|
|
@ -1,153 +0,0 @@
|
|||
/*-*- 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;
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
#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_ */
|
203
dsp/mpeg/demux.c
203
dsp/mpeg/demux.c
|
@ -1,203 +0,0 @@
|
|||
/*-*- 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;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
#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
101
dsp/mpeg/idct.c
|
@ -1,101 +0,0 @@
|
|||
/*-*- 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;
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#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_ */
|
|
@ -1,171 +0,0 @@
|
|||
/*-*- 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
769
dsp/mpeg/mp2.c
|
@ -1,769 +0,0 @@
|
|||
/*-*- 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
447
dsp/mpeg/mpeg.h
|
@ -1,447 +0,0 @@
|
|||
#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
1110
dsp/mpeg/mpeg1.c
File diff suppressed because it is too large
Load diff
|
@ -2,3 +2,8 @@ __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"
|
4379
dsp/mpeg/pl_mpeg.h
Executable file
4379
dsp/mpeg/pl_mpeg.h
Executable file
File diff suppressed because it is too large
Load diff
332
dsp/mpeg/plm.c
332
dsp/mpeg/plm.c
|
@ -1,332 +0,0 @@
|
|||
/*-*- 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/*-*- 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;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
#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_ */
|
43
dsp/prog/BUILD.mk
Normal file
43
dsp/prog/BUILD.mk
Normal file
|
@ -0,0 +1,43 @@
|
|||
#-*-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)
|
41
dsp/prog/loudness.h
Normal file
41
dsp/prog/loudness.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#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_ */
|
127
dsp/prog/recvaudio.c
Normal file
127
dsp/prog/recvaudio.c
Normal file
|
@ -0,0 +1,127 @@
|
|||
#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);
|
||||
}
|
149
dsp/prog/sendaudio.c
Normal file
149
dsp/prog/sendaudio.c
Normal file
|
@ -0,0 +1,149 @@
|
|||
#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);
|
||||
}
|
|
@ -45,6 +45,12 @@ $(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 \
|
||||
|
|
|
@ -16,17 +16,20 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#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/macros.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
|
||||
|
@ -37,62 +40,107 @@
|
|||
#define LOOKAHEAD (SPREAD - LOOKBEHIND)
|
||||
#define SCALE 5
|
||||
#define ROUND (1 << (SCALE - 1))
|
||||
|
||||
/**
|
||||
* 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];
|
||||
__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;
|
||||
unsigned long i, j, w;
|
||||
if (n >= STRIDE) {
|
||||
i = 0;
|
||||
w = (n + RATIO / 2) / RATIO;
|
||||
memset(in1, A[0], sizeof(in1));
|
||||
memset(in2, A[n - 1], 16);
|
||||
memcpy(in2, A, MIN(16, n));
|
||||
in1 = _mm_set1_epi8(A[0]);
|
||||
in2 = _mm_set1_epi8(A[n - 1]);
|
||||
_mm_storeu_si128((__m128i *)&in2, _mm_loadu_si128((__m128i *)A));
|
||||
for (; i < w; i += STRIDE) {
|
||||
j = i * RATIO + 16;
|
||||
if (j + 16 <= n) {
|
||||
memcpy(in3, &A[j], 16);
|
||||
in3 = _mm_loadu_si128((__m128i *)&A[j]);
|
||||
} else {
|
||||
memset(in3, A[n - 1], 16);
|
||||
in3 = _mm_set1_epi8(A[n - 1]);
|
||||
if (j < n) {
|
||||
memcpy(in3, &A[j], n - j);
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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,12 +164,19 @@ 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 ? 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];
|
||||
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 (dx = 0; dx < dxn; ++dx) {
|
||||
|
@ -180,12 +187,19 @@ 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 ? 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];
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ EXAMPLES_BINS = \
|
|||
|
||||
EXAMPLES_DIRECTDEPS = \
|
||||
CTL \
|
||||
DSP_AUDIO \
|
||||
DSP_CORE \
|
||||
DSP_SCALE \
|
||||
DSP_TTY \
|
||||
|
@ -53,8 +54,8 @@ EXAMPLES_DIRECTDEPS = \
|
|||
LIBC_NEXGEN32E \
|
||||
LIBC_NT_ADVAPI32 \
|
||||
LIBC_NT_IPHLPAPI \
|
||||
LIBC_NT_MEMORY \
|
||||
LIBC_NT_KERNEL32 \
|
||||
LIBC_NT_MEMORY \
|
||||
LIBC_NT_NTDLL \
|
||||
LIBC_NT_USER32 \
|
||||
LIBC_NT_WS2_32 \
|
||||
|
@ -63,6 +64,7 @@ EXAMPLES_DIRECTDEPS = \
|
|||
LIBC_SOCK \
|
||||
LIBC_STDIO \
|
||||
LIBC_STR \
|
||||
LIBC_SYSTEM \
|
||||
LIBC_SYSV \
|
||||
LIBC_SYSV_CALLS \
|
||||
LIBC_TESTLIB \
|
||||
|
@ -80,6 +82,8 @@ 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 \
|
||||
|
@ -93,11 +97,10 @@ 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))))
|
||||
|
@ -148,6 +151,10 @@ 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 \
|
||||
|
|
80
examples/a440.c
Normal file
80
examples/a440.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
#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;
|
||||
}
|
||||
}
|
125
examples/aba.c
Normal file
125
examples/aba.c
Normal file
|
@ -0,0 +1,125 @@
|
|||
#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();
|
||||
}
|
353
examples/art.c
Normal file
353
examples/art.c
Normal file
|
@ -0,0 +1,353 @@
|
|||
#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);
|
||||
}
|
353
examples/asteroids.c
Normal file
353
examples/asteroids.c
Normal file
|
@ -0,0 +1,353 @@
|
|||
// -*- 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;
|
||||
}
|
|
@ -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")
|
||||
#define MATH __target_clones("avx512f,fma,avx")
|
||||
|
||||
namespace {
|
||||
namespace ansiBLAS {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "libc/calls/calls.h"
|
||||
#include <unistd.h>
|
||||
|
||||
// clears teletypewriter display
|
||||
//
|
||||
|
|
|
@ -7,12 +7,7 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#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"
|
||||
#include <cosmo.h>
|
||||
|
||||
/**
|
||||
* @fileoverview How to print backtraces and cpu state on crash.
|
||||
|
|
141
examples/ctrlc.c
141
examples/ctrlc.c
|
@ -7,20 +7,43 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#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"
|
||||
#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"
|
||||
//
|
||||
|
||||
volatile bool gotsig;
|
||||
|
||||
|
@ -34,23 +57,41 @@ 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[]) {
|
||||
|
||||
printf("echoing stdin until ctrl+c is pressed\n");
|
||||
WRITE("echoing stdin until ctrl+c is pressed\n");
|
||||
|
||||
// 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
|
||||
// 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
|
||||
sigaction(SIGINT, &(struct sigaction){.sa_handler = SignalHandler}, 0);
|
||||
|
||||
for (;;) {
|
||||
|
||||
// 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) {
|
||||
// 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) {
|
||||
if (gotsig) {
|
||||
printf("\rgot ctrl+c asynchronously\n");
|
||||
WRITE("\rgot ctrl+c asynchronously\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -71,14 +112,18 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
// read data from standard input
|
||||
//
|
||||
// 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");
|
||||
// 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");
|
||||
int got = read(0, buf, sizeof(buf));
|
||||
|
||||
// check if the read operation failed
|
||||
|
@ -94,10 +139,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) {
|
||||
printf("\rgot ctrl+c via i/o eintr\n");
|
||||
WRITE("\rgot ctrl+c via i/o eintr\n");
|
||||
exit(0);
|
||||
} else {
|
||||
printf("\rgot spurious eintr\n");
|
||||
WRITE("\rgot spurious eintr\n");
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
|
@ -109,16 +154,34 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
// check if the user typed ctrl-d which closes the input handle
|
||||
if (!got) {
|
||||
printf("got eof\n");
|
||||
WRITE("got eof\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// relay read data to standard output
|
||||
// visualize line data returned by canonical mode to standard output
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
write(1, "got: ", 5);
|
||||
write(1, buf, got);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,11 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#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"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @fileoverview High performance ISO-8601 timestamp formatter.
|
||||
|
@ -27,6 +20,8 @@
|
|||
* Consider using something like this instead for your loggers.
|
||||
*/
|
||||
|
||||
#define ABS(X) ((X) >= 0 ? (X) : -(X))
|
||||
|
||||
char *GetTimestamp(void) {
|
||||
int x;
|
||||
struct timespec ts;
|
||||
|
|
|
@ -7,11 +7,9 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#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"
|
||||
#include <cosmo.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* @fileoverview cosmopolitan dynamic runtime linking demo
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#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
|
||||
*/
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
fprintf(stderr, "%s (%s)\n", argv[0], GetProgramExecutableName());
|
||||
for (char** p = environ; *p; ++p) {
|
||||
printf("%s\n", *p);
|
||||
}
|
||||
for (char** p = environ; *p; ++p)
|
||||
puts(*p);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
#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
|
||||
|
@ -339,7 +337,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));
|
||||
unassert(!pthread_attr_setstacksize(&attr, 65536 - getpagesize()));
|
||||
unassert(!pthread_attr_setguardsize(&attr, getpagesize()));
|
||||
unassert(!pthread_attr_setsigmask_np(&attr, &block));
|
||||
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0));
|
||||
|
|
|
@ -36,14 +36,10 @@
|
|||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#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"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
// clang-format off
|
||||
|
||||
#define DICT "usr/share/dict/hangman"
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("hello world\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "libc/calls/calls.h"
|
||||
#include <unistd.h>
|
||||
|
||||
int main() {
|
||||
write(1, "hello world\n", 12);
|
||||
|
|
|
@ -1,15 +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 "libc/time.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int64_t t = 0;
|
||||
localtime(&t);
|
||||
}
|
133
examples/loudness.c
Normal file
133
examples/loudness.c
Normal file
|
@ -0,0 +1,133 @@
|
|||
#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);
|
||||
}
|
|
@ -1,83 +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 "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;
|
||||
}
|
|
@ -7,25 +7,16 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#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"
|
||||
#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
|
||||
|
||||
/**
|
||||
* @fileoverview netcat clone
|
||||
|
@ -36,12 +27,14 @@
|
|||
* 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 "telnet"
|
||||
* Once upon time we called this command basically "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;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
/* 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"
|
||||
|
@ -12,7 +13,6 @@
|
|||
#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,20 +35,17 @@
|
|||
#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"
|
||||
|
||||
|
@ -111,7 +108,9 @@ AUTHORS\n\
|
|||
#define DYN 240
|
||||
#define DXN 256
|
||||
#define FPS 60.0988
|
||||
#define HZ 1789773
|
||||
#define CPUHZ 1789773
|
||||
#define SRATE 44100
|
||||
#define ABUFZ ((int)(SRATE / FPS) + 1)
|
||||
#define GAMMA 2.2
|
||||
#define CTRL(C) ((C) ^ 0100)
|
||||
#define ALT(C) ((033 << 010) | (C))
|
||||
|
@ -121,25 +120,11 @@ 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];
|
||||
|
@ -150,32 +135,33 @@ struct ZipGames {
|
|||
char** p;
|
||||
};
|
||||
|
||||
static int frame_;
|
||||
static int playfd_;
|
||||
static int playpid_;
|
||||
static size_t vtsize_;
|
||||
static const struct timespec kNesFps = {0, 1. / FPS * 1e9};
|
||||
|
||||
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_ = kTtyQuantTrue;
|
||||
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 int Clamp(int v) {
|
||||
return MAX(0, MIN(255, v));
|
||||
|
@ -229,73 +215,39 @@ 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);
|
||||
if (playpid_) {
|
||||
kill(playpid_, SIGKILL);
|
||||
close(playfd_);
|
||||
playfd_ = -1;
|
||||
}
|
||||
cosmoaudio_close(ca_);
|
||||
ca_ = 0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -318,11 +270,6 @@ 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;
|
||||
}
|
||||
|
@ -330,11 +277,7 @@ 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_);
|
||||
|
@ -471,81 +414,29 @@ ssize_t ReadKeyboard(void) {
|
|||
return rc;
|
||||
}
|
||||
|
||||
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) {
|
||||
void ScaleVideoFrameToTeletypewriter(unsigned char (*pixels)[3][DYN][DXN]) {
|
||||
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] =
|
||||
|
@ -562,57 +453,104 @@ void KeyCountdown(struct Action* a) {
|
|||
}
|
||||
}
|
||||
|
||||
void PollAndSynchronize(void) {
|
||||
do {
|
||||
if (ReadKeyboard() == -1) {
|
||||
if (errno != EINTR)
|
||||
Exit(1);
|
||||
if (exited_)
|
||||
Exit(0);
|
||||
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;
|
||||
}
|
||||
}
|
||||
free(ansi);
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
} 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 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) {
|
||||
if (!timeout_) {
|
||||
Raster();
|
||||
}
|
||||
timeout_ = false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1494,8 +1432,7 @@ 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; };
|
||||
short sample =
|
||||
30000 *
|
||||
float sample =
|
||||
(v(95.88f, (100.f + v(8128.f, s(0) + s(1), -100.f)), 0.f) +
|
||||
v(159.79f,
|
||||
(100.f +
|
||||
|
@ -1504,7 +1441,19 @@ void Tick() { // Invoked at CPU's rate.
|
|||
0.5f);
|
||||
#undef s
|
||||
|
||||
audio_.p[audio_.i = (audio_.i + 1) & (ARRAYLEN(audio_.p) - 1)] = sample;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace APU
|
||||
|
@ -1745,9 +1694,6 @@ 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"))) {
|
||||
|
@ -1760,46 +1706,28 @@ 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
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
// Read the ROM file header
|
||||
u8 rom16count = fgetc(fp);
|
||||
|
@ -1907,9 +1835,8 @@ 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;
|
||||
|
@ -1932,9 +1859,8 @@ 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;
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
#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) \
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/sigaction.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/str/str.h"
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
volatile int g_sig;
|
||||
|
||||
|
@ -21,16 +21,13 @@ 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
|
||||
char ibuf[12];
|
||||
FormatInt32(ibuf, getpid());
|
||||
tinyprint(2, "waiting for signal to be sent to ", ibuf, "\n", NULL);
|
||||
printf("waiting for signal to be sent to my pid %d\n", getpid());
|
||||
pause();
|
||||
|
||||
// report the signal
|
||||
tinyprint(1, "got ", strsignal(g_sig), "\n", NULL);
|
||||
printf("got %s\n", strsignal(g_sig));
|
||||
}
|
||||
|
|
|
@ -32,12 +32,9 @@
|
|||
* . Formatted as per Cosmopolitan's standards.
|
||||
*/
|
||||
|
||||
#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"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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 };
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include <cosmo.h>
|
||||
|
||||
int main() {
|
||||
__printargs("");
|
||||
|
|
1022
examples/romanize.c
Normal file
1022
examples/romanize.c
Normal file
File diff suppressed because it is too large
Load diff
322
examples/rote.c
Normal file
322
examples/rote.c
Normal file
|
@ -0,0 +1,322 @@
|
|||
#/*────────────────────────────────────────────────────────────────╗
|
||||
┌┘ 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");
|
||||
}
|
|
@ -29,39 +29,30 @@
|
|||
│ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF │
|
||||
│ SUCH DAMAGE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#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"
|
||||
#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>
|
||||
// clang-format off
|
||||
|
||||
/**
|
||||
* @fileoverview Terminal Screencast Recorder / Player, e.g.
|
||||
*
|
||||
* make o//examples/script.com
|
||||
* o//examples/script.com -r
|
||||
* o//examples/script.com -w80 -h24 -r recording.tty
|
||||
* # type stuff..
|
||||
* # CTRL-D
|
||||
* o//examples/script.com -p typescript
|
||||
* o//examples/script.com -p recording.tty
|
||||
*
|
||||
* @note works on Linux, OpenBSD, NetBSD, FreeBSD, MacOS
|
||||
* @see https://asciinema.org/
|
||||
|
@ -112,9 +103,9 @@ main(int argc, char *argv[])
|
|||
fd_set rfd;
|
||||
int fm_fd;
|
||||
int aflg, Fflg, kflg, pflg, ch, k, n;
|
||||
int flushtime, readstdin;
|
||||
int flushtime, readstdin, width, height;
|
||||
|
||||
aflg = Fflg = kflg = pflg = 0;
|
||||
aflg = Fflg = kflg = pflg = height = width = 0;
|
||||
usesleep = 1;
|
||||
rawout = 0;
|
||||
flushtime = 30;
|
||||
|
@ -124,7 +115,7 @@ main(int argc, char *argv[])
|
|||
|
||||
(void)fm_fd;
|
||||
|
||||
while ((ch = getopt(argc, argv, "adeFfkpqrt:")) != -1)
|
||||
while ((ch = getopt(argc, argv, "adeFfkpqrt:w:h:")) != -1)
|
||||
switch(ch) {
|
||||
case 'a':
|
||||
aflg = 1;
|
||||
|
@ -154,6 +145,12 @@ 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();
|
||||
|
@ -181,6 +178,10 @@ 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;
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include <cosmo.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* @fileoverview Prints sequence of numbers.
|
||||
|
|
|
@ -7,12 +7,9 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#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"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ucontext.h>
|
||||
|
||||
/**
|
||||
* @fileoverview swapcontext() and makecontext() example
|
||||
|
@ -33,18 +30,16 @@ 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");
|
||||
}
|
||||
|
||||
|
@ -52,17 +47,15 @@ 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 */
|
||||
|
@ -70,9 +63,8 @@ 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);
|
||||
|
|
|
@ -7,17 +7,15 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#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"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @fileoverview interval timer tutorial
|
||||
*/
|
||||
|
||||
volatile bool gotalrm;
|
||||
|
||||
|
|
366
examples/spawn.c
Normal file
366
examples/spawn.c
Normal file
|
@ -0,0 +1,366 @@
|
|||
#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);
|
||||
}
|
||||
}
|
|
@ -7,24 +7,20 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#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"
|
||||
#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>
|
||||
|
||||
#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;
|
||||
|
@ -117,6 +113,11 @@ 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 {
|
||||
|
|
|
@ -7,9 +7,13 @@
|
|||
│ • 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"
|
||||
|
||||
|
@ -67,8 +71,18 @@ int main(int argc, char *argv[]) {
|
|||
Append((uintptr_t)&__auxv[i + 1],
|
||||
xasprintf("&auxv[%d] = %#lx", i + 1, __auxv[i + 1]));
|
||||
}
|
||||
qsort(things.p, things.n, sizeof(*things.p), Compare);
|
||||
for (int i = 0; i < things.n; ++i) {
|
||||
printf("%012lx %s\n", things.p[i].i, things.p[i].s);
|
||||
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)
|
||||
printf("%012lx %s\n", things.p[i].i, things.p[i].s);
|
||||
}
|
||||
|
|
|
@ -7,19 +7,12 @@
|
|||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include "libc/calls/struct/stat.h"
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/log/check.h"
|
||||
#include "libc/log/log.h"
|
||||
#include "libc/mem/gc.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/s.h"
|
||||
#include "libc/time.h"
|
||||
#include <assert.h>
|
||||
#include <cosmo.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
/**
|
||||
* @fileoverview File metadata viewer.
|
||||
|
@ -72,9 +65,15 @@ void PrintFileMetadata(const char *pathname, struct stat *st) {
|
|||
printf("\n%s:", pathname);
|
||||
if (numeric) {
|
||||
fd = atoi(pathname);
|
||||
CHECK_NE(-1, fstat(fd, st), "fd=%d", fd);
|
||||
if (fstat(fd, st)) {
|
||||
perror(pathname);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
CHECK_NE(-1, stat(pathname, st), "pathname=%s", pathname);
|
||||
if (stat(pathname, st)) {
|
||||
perror(pathname);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
printf("\n"
|
||||
"%-32s%,ld\n"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue