Productionize polished cosmoaudio library

This change introduces comsoaudio v1. We're using a new strategy when it
comes to dynamic linking of dso files and building miniaudio device code
which I think will be fast and stable in the long run. You now have your
choice of reading/writing to the internal ring buffer abstraction or you
can specify a device-driven callback function instead. It's now possible
to not open the microphone when you don't need it since touching the mic
causes security popups to happen. The DLL is now built statically, so it
only needs to depend on kernel32. Our NES terminal emulator now uses the
cosmoaudio library and is confirmed to be working on Windows, Mac, Linux
This commit is contained in:
Justine Tunney 2024-09-07 03:30:49 -07:00
parent c66abd7260
commit dc579b79cd
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
13 changed files with 640 additions and 470 deletions

8
.gitattributes vendored
View file

@ -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

View file

@ -17,13 +17,17 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "dsp/audio/cosmoaudio/cosmoaudio.h"
#include "dsp/audio/describe.h"
#include "libc/calls/calls.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"
@ -31,6 +35,10 @@
#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");
@ -53,7 +61,7 @@ static struct {
typeof(cosmoaudio_read) *read;
} g_audio;
static const char *get_tmp_dir(void) {
static const char *cosmoaudio_tmp_dir(void) {
const char *tmpdir;
if (!(tmpdir = getenv("TMPDIR")) || !*tmpdir)
if (!(tmpdir = getenv("HOME")) || !*tmpdir)
@ -61,18 +69,18 @@ static const char *get_tmp_dir(void) {
return tmpdir;
}
static bool get_app_dir(char *path, size_t size) {
strlcpy(path, get_tmp_dir(), size);
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 get_dso_path(char *path, size_t size) {
if (!get_app_dir(path, size))
static bool cosmoaudio_dso_path(char *path, size_t size) {
if (!cosmoaudio_app_dir(path, size))
return false;
strlcat(path, "cosmoaudio", size);
strlcat(path, COSMOAUDIO_DSO_NAME, size);
if (IsWindows()) {
strlcat(path, ".dll", size);
} else if (IsXnu()) {
@ -83,86 +91,7 @@ static bool get_dso_path(char *path, size_t size) {
return true;
}
static int is_file_newer_than_time(const char *path, const char *other) {
struct stat st1, st2;
if (stat(path, &st1))
// PATH should always exist when calling this function
return -1;
if (stat(other, &st2)) {
if (errno == ENOENT) {
// PATH should replace OTHER because OTHER doesn't exist yet
return true;
} else {
// some other error happened, so we can't do anything
return -1;
}
}
// PATH should replace OTHER if PATH was modified more recently
return timespec_cmp(st1.st_mtim, st2.st_mtim) > 0;
}
static int is_file_newer_than_bytes(const char *path, const char *other) {
int other_fd;
if ((other_fd = open(other, O_RDONLY | O_CLOEXEC)) == -1) {
if (errno == ENOENT) {
return true;
} else {
return -1;
}
}
int path_fd;
if ((path_fd = open(path, O_RDONLY | O_CLOEXEC)) == -1) {
close(other_fd);
return -1;
}
int res;
long i = 0;
for (;;) {
char path_buf[512];
ssize_t path_rc = pread(path_fd, path_buf, sizeof(path_buf), i);
if (path_rc == -1) {
res = -1;
break;
}
char other_buf[512];
ssize_t other_rc = pread(other_fd, other_buf, sizeof(other_buf), i);
if (other_rc == -1) {
res = -1;
break;
}
if (!path_rc || !other_rc) {
if (!path_rc && !other_rc)
res = false;
else
res = true;
break;
}
size_t size = path_rc;
if (other_rc < path_rc)
size = other_rc;
if (memcmp(path_buf, other_buf, size)) {
res = true;
break;
}
i += size;
}
if (close(path_fd))
res = -1;
if (close(other_fd))
res = -1;
return res;
}
static int is_file_newer_than(const char *path, const char *other) {
if (startswith(path, "/zip/"))
// to keep builds deterministic, embedded zip files always have
// the same timestamp from back in 2022 when it was implemented
return is_file_newer_than_bytes(path, other);
else
return is_file_newer_than_time(path, other);
}
static bool extract(const char *zip, const char *to) {
static bool cosmoaudio_extract(const char *zip, const char *to) {
int fdin, fdout;
char stage[PATH_MAX];
strlcpy(stage, to, sizeof(stage));
@ -170,9 +99,8 @@ static bool extract(const char *zip, const char *to) {
errno = ENAMETOOLONG;
return false;
}
if ((fdout = mkostemp(stage, O_CLOEXEC)) == -1) {
if ((fdout = mkostemp(stage, O_CLOEXEC)) == -1)
return false;
}
if ((fdin = open(zip, O_RDONLY | O_CLOEXEC)) == -1) {
close(fdout);
unlink(stage);
@ -200,114 +128,104 @@ static bool extract(const char *zip, const char *to) {
return true;
}
static bool deploy(const char *dso) {
switch (is_file_newer_than("/zip/dsp/audio/cosmoaudio/cosmoaudio.dll", dso)) {
case 0:
return true;
case 1:
return extract("/zip/dsp/audio/cosmoaudio/cosmoaudio.dll", dso);
default:
return false;
}
}
static bool cosmoaudio_build(const char *dso) {
static bool build(const char *dso) {
// extract source code
// extract sauce
char src[PATH_MAX];
bool needs_rebuild = false;
for (int i = 0; i < sizeof(srcs) / sizeof(*srcs); ++i) {
get_app_dir(src, PATH_MAX);
if (!cosmoaudio_app_dir(src, PATH_MAX))
return false;
strlcat(src, srcs[i].name, sizeof(src));
switch (is_file_newer_than(srcs[i].zip, src)) {
case -1:
return false;
case 0:
break;
case 1:
needs_rebuild = true;
if (!extract(srcs[i].zip, src))
return false;
break;
default:
__builtin_unreachable();
}
if (!cosmoaudio_extract(srcs[i].zip, src))
return false;
}
// determine if we need to build
if (!needs_rebuild) {
switch (is_file_newer_than(src, dso)) {
case -1:
return false;
case 0:
break;
case 1:
needs_rebuild = true;
break;
default:
__builtin_unreachable();
}
// 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;
}
// compile dynamic shared object
if (needs_rebuild) {
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 {
// 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;
}
char *args[] = {
"cc", //
"-I.", //
"-O2", //
"-fPIC", //
"-shared", //
"-pthread", //
"-DNDEBUG", //
IsAarch64() ? "-ffixed-x28" : "-DIGNORE1", //
src, //
"-o", //
tmpdso, //
"-ldl", //
"-lm", //
NULL,
};
int pid, ws;
errno_t err = posix_spawnp(&pid, "cc", NULL, NULL, args, environ);
if (err)
return false;
while (waitpid(pid, &ws, 0) == -1) {
if (errno != EINTR)
return false;
}
if (ws)
return false;
if (rename(tmpdso, dso))
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(void) {
void *handle;
if (!(handle = cosmo_dlopen("cosmoaudio.so", RTLD_LOCAL))) {
if (issetugid())
return;
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 (!get_dso_path(dso, sizeof(dso)))
if (!cosmoaudio_dso_path(dso, sizeof(dso)))
return;
if (IsWindows())
if (deploy(dso))
if ((handle = cosmo_dlopen(dso, RTLD_LOCAL)))
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;
if (!build(dso))
} else {
return;
}
}
}
if (!cosmoaudio_build(dso))
return;
if (!(handle = cosmo_dlopen(dso, RTLD_LOCAL)))
if (!(handle = cosmoaudio_dlopen(dso)))
return;
}
WeAreGood:
@ -321,33 +239,59 @@ static void cosmoaudio_init(void) {
pthread_once(&g_audio.once, cosmoaudio_setup);
}
COSMOAUDIO_ABI int cosmoaudio_open(struct CosmoAudio **cap, int sampleRate,
int channels) {
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)
return COSMOAUDIO_ERROR;
return g_audio.open(cap, sampleRate, channels);
if (g_audio.open)
status = g_audio.open(out_ca, options);
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) {
cosmoaudio_init();
if (!g_audio.close)
return COSMOAUDIO_ERROR;
return g_audio.close(ca);
int status;
char sbuf[32];
if (g_audio.close)
status = g_audio.close(ca);
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) {
cosmoaudio_init();
if (!g_audio.write)
return COSMOAUDIO_ERROR;
return g_audio.write(ca, data, frames);
int status;
char sbuf[32];
if (g_audio.write)
status = g_audio.write(ca, data, frames);
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) {
cosmoaudio_init();
if (!g_audio.read)
return COSMOAUDIO_ERROR;
return g_audio.read(ca, data, frames);
int status;
char sbuf[32];
if (g_audio.read)
status = g_audio.read(ca, data, frames);
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;
}

View file

@ -19,7 +19,7 @@ TEST_LIBS=OneCore.lib
# Compiler flags
CFLAGS_COMMON=/nologo /W4 /Gy /EHsc
CFLAGS_DEBUG=/Od /Zi /MDd /D_DEBUG
CFLAGS_RELEASE=/O2 /MD /DNDEBUG
CFLAGS_RELEASE=/O2 /MT /DNDEBUG
!IF "$(MODE)"=="debug"
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_DEBUG)

View file

@ -1,3 +1,18 @@
// 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>
@ -7,6 +22,10 @@
#define MA_STATIC
#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
#ifdef NDEBUG
#define MA_DR_MP3_NO_STDIO
#endif
@ -25,6 +44,10 @@ struct CosmoAudio {
ma_pcm_rb output;
ma_uint32 sampleRate;
ma_uint32 channels;
ma_uint32 periods;
enum CosmoAudioDeviceType deviceType;
cosmoaudio_data_callback_f* dataCallback;
void* argument;
};
static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
@ -88,8 +111,15 @@ static int write_ring_buffer(ma_pcm_rb* rb, const float* pInput,
static void data_callback_f32(ma_device* pDevice, float* pOutput,
const float* pInput, ma_uint32 frameCount) {
struct CosmoAudio* ca = (struct CosmoAudio*)pDevice->pUserData;
read_ring_buffer(&ca->output, pOutput, frameCount, ca->channels);
write_ring_buffer(&ca->input, pInput, frameCount, ca->channels);
if (ca->dataCallback) {
ca->dataCallback(ca, pOutput, pInput, frameCount, ca->channels,
ca->argument);
} else {
if (ca->deviceType & kCosmoAudioDeviceTypePlayback)
read_ring_buffer(&ca->output, pOutput, frameCount, ca->channels);
if (ca->deviceType & kCosmoAudioDeviceTypeCapture)
write_ring_buffer(&ca->input, pInput, frameCount, ca->channels);
}
}
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
@ -97,34 +127,72 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
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 cap will receive pointer to allocated CosmoAudio object on success,
* which must be freed by caller with cosmoaudio_close()
* @param sampleRate is sample rate in Hz, e.g. 44100
* @param channels is number of channels (1 for mono, 2 for stereo)
* @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** cap, int sampleRate,
int channels) {
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->periods < 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;
if (!(ca = (struct CosmoAudio*)malloc(sizeof(struct CosmoAudio))))
if (!(ca = (struct CosmoAudio*)calloc(1, sizeof(struct CosmoAudio))))
return COSMOAUDIO_ERROR;
ca->channels = channels;
ca->sampleRate = sampleRate;
ca->channels = options->channels;
ca->sampleRate = options->sampleRate;
ca->deviceType = options->deviceType;
ca->periods = options->periods ? options->periods : 10;
ca->dataCallback = options->dataCallback;
ca->argument = options->argument;
// Initialize device.
ma_result result;
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex);
deviceConfig.sampleRate = sampleRate;
deviceConfig.capture.channels = channels;
deviceConfig.capture.format = ma_format_f32;
deviceConfig.capture.shareMode = ma_share_mode_shared;
deviceConfig.playback.channels = channels;
deviceConfig.playback.format = ma_format_f32;
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(NULL, &deviceConfig, &ca->device);
@ -134,51 +202,67 @@ COSMOAUDIO_ABI int cosmoaudio_open(struct CosmoAudio** cap, int sampleRate,
}
// Initialize the speaker ring buffer.
result = ma_pcm_rb_init(ma_format_f32, channels,
ca->device.playback.internalPeriodSizeInFrames * 10,
NULL, NULL, &ca->output);
if (result != MA_SUCCESS) {
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypePlayback)) {
result = ma_pcm_rb_init(
ma_format_f32, ca->channels,
ca->device.playback.internalPeriodSizeInFrames * ca->periods, NULL,
NULL, &ca->output);
if (result != MA_SUCCESS) {
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
ma_pcm_rb_set_sample_rate(&ca->output, ca->sampleRate);
}
ma_pcm_rb_set_sample_rate(&ca->output, sampleRate);
// Initialize the microphone ring buffer.
result = ma_pcm_rb_init(ma_format_f32, channels,
ca->device.capture.internalPeriodSizeInFrames * 10,
NULL, NULL, &ca->input);
if (result != MA_SUCCESS) {
ma_pcm_rb_uninit(&ca->output);
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypeCapture)) {
result = ma_pcm_rb_init(
ma_format_f32, ca->channels,
ca->device.capture.internalPeriodSizeInFrames * ca->periods, NULL, NULL,
&ca->input);
if (result != MA_SUCCESS) {
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypePlayback))
ma_pcm_rb_uninit(&ca->output);
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
ma_pcm_rb_set_sample_rate(&ca->output, ca->sampleRate);
}
ma_pcm_rb_set_sample_rate(&ca->output, sampleRate);
// Start audio playback.
if (ma_device_start(&ca->device) != MA_SUCCESS) {
ma_pcm_rb_uninit(&ca->input);
ma_pcm_rb_uninit(&ca->output);
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypeCapture))
ma_pcm_rb_uninit(&ca->input);
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypePlayback))
ma_pcm_rb_uninit(&ca->output);
ma_device_uninit(&ca->device);
free(ca);
return COSMOAUDIO_ERROR;
}
*cap = ca;
*out_ca = ca;
return COSMOAUDIO_SUCCESS;
}
/**
* Closes audio device and frees all associated resources.
*
* Calling this function twice on the same object will result in
* undefined behavior.
*
* @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);
ma_pcm_rb_uninit(&ca->output);
ma_pcm_rb_uninit(&ca->input);
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypePlayback))
ma_pcm_rb_uninit(&ca->output);
if (!ca->dataCallback && (ca->deviceType & kCosmoAudioDeviceTypeCapture))
ma_pcm_rb_uninit(&ca->input);
free(ca);
return COSMOAUDIO_SUCCESS;
}
@ -201,6 +285,18 @@ COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
*/
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->dataCallback)
return COSMOAUDIO_EINVAL;
if (!(ca->deviceType & kCosmoAudioDeviceTypePlayback))
return COSMOAUDIO_EINVAL;
if (!frames)
return 0;
if (!data)
return COSMOAUDIO_EINVAL;
return write_ring_buffer(&ca->output, data, frames, ca->channels);
}
@ -222,6 +318,18 @@ COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data,
*/
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->dataCallback)
return COSMOAUDIO_EINVAL;
if (!(ca->deviceType & kCosmoAudioDeviceTypeCapture))
return COSMOAUDIO_EINVAL;
if (!frames)
return 0;
if (!data)
return COSMOAUDIO_EINVAL;
return read_ring_buffer(&ca->input, data, frames, ca->channels);
}

Binary file not shown.

View file

@ -11,14 +11,16 @@
#else
#define COSMOAUDIO_API
#ifdef __x86_64__
#define COSMOAUDIO_ABI __attribute__((__ms_abi__))
#define COSMOAUDIO_ABI __attribute__((__ms_abi__, __visibility__("default")))
#else
#define COSMOAUDIO_ABI
#define COSMOAUDIO_ABI __attribute__((__visibility__("default")))
#endif
#endif
#define COSMOAUDIO_SUCCESS 0
#define COSMOAUDIO_ERROR -1
#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
#ifdef __cplusplus
extern "C" {
@ -26,13 +28,78 @@ extern "C" {
struct CosmoAudio;
COSMOAUDIO_API int cosmoaudio_open(struct CosmoAudio **, int,
int) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_close(struct CosmoAudio *) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_write(struct CosmoAudio *, const float *,
int) COSMOAUDIO_ABI;
COSMOAUDIO_API int cosmoaudio_read(struct CosmoAudio *, float *,
int) COSMOAUDIO_ABI;
typedef void cosmoaudio_data_callback_f( //
struct CosmoAudio *ca, //
float *outputSamples, //
const float *inputSamples, //
int frameCount, //
int channels, //
void *argument);
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 periods in ring buffer. Set to 0 for default. Higher
// numbers (e.g. 20) means more buffering. Lower numbers (e.g. 2)
// means less buffering. This is ignored if callback is specified.
int periods;
// If callback is NULL, then cosmoaudio_write() and cosmoaudio_read()
// should be used, which ring buffer audio to the default internal
// routine. Setting this callback to non-NULL puts CosmoAudio in
// manual mode, where the callback is responsible for copying PCM
// samples each time the device calls this.
cosmoaudio_data_callback_f *dataCallback;
// This is an arbitrary value passed to the callback.
void *argument;
};
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_read( //
struct CosmoAudio *ca, //
float *out_samples, //
int frameCount //
) COSMOAUDIO_ABI;
#ifdef __cplusplus
}

View file

@ -1,5 +1,12 @@
#include <errno.h>
#include <limits.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 <math.h>
#include <stdio.h>
#include <time.h>
@ -9,28 +16,43 @@
#define M_PIf 3.14159265358979323846f
#endif
int g_hz = 44100;
int g_channels = 2;
int g_generation = 0;
int g_freq = 440;
void data_callback(struct CosmoAudio *ca, float *outputSamples,
const float *inputSamples, int frameCount, int channels,
void *argument) {
for (int i = 0; i < frameCount; i++) {
float t = (float)g_generation++ / g_hz;
if (g_generation == g_hz)
g_generation = 0;
float s = sinf(2 * M_PIf * g_freq * t);
for (int j = 0; j < channels; j++)
outputSamples[i * channels + j] = s;
}
(void)inputSamples;
(void)argument;
(void)ca;
}
int main() {
int hz = 44100;
int channels = 2;
struct CosmoAudioOpenOptions cao = {};
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
cao.deviceType = kCosmoAudioDeviceTypePlayback;
cao.sampleRate = g_hz;
cao.channels = g_channels;
cao.dataCallback = data_callback;
struct CosmoAudio *ca;
if (cosmoaudio_open(&ca, hz, channels) != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "%s: failed to open audio\n", argv[0]);
if (cosmoaudio_open(&ca, &cao) != COSMOAUDIO_SUCCESS) {
fprintf(stderr, "failed to open audio\n");
return 1;
}
int n = 1000;
int sample = 0;
float *buf = (float *)malloc(sizeof(float) * channels * n);
for (;;) {
for (int i = 0; i < 128; i++) {
float freq = 440;
float t = (float)sample++ / hz;
if (sample == hz)
sample = 0;
buf[i * channels] = sinf(freq * 2.f * M_PIf * t);
buf[i * channels + 1] = sinf(freq * 2.f * M_PIf * t);
}
cosmoaudio_write(ca, buf, 128);
}
fgetc(stdin);
cosmoaudio_close(ca);
}

113
dsp/audio/describe.c Normal file
View file

@ -0,0 +1,113 @@
/*-*- 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";
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->dataCallback) {
if (gotsome)
append(", ");
append(".dataCallback=%t", options->dataCallback);
gotsome = true;
if (options->argument) {
if (gotsome)
append(", ");
append(".argument=%p", options->argument);
gotsome = true;
}
} else {
if (options->periods) {
if (gotsome)
append(", ");
append(".periods=%d", options->periods);
gotsome = true;
}
}
if (options->sizeofThis) {
if (gotsome)
append(", ");
append(".sizeofThis=%d", options->sizeofThis);
gotsome = true;
}
append("}");
return buf;
}

11
dsp/audio/describe.h Normal file
View file

@ -0,0 +1,11 @@
#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 *);
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_DSP_AUDIO_DESCRIBE_H_ */

View file

@ -3216,7 +3216,7 @@ int plm_video_decode_motion_vector(plm_video_t *self, int r_size, int motion) {
if (motion > (fscale << 4) - 1) {
motion -= fscale << 5;
}
else if (motion < ((-fscale) << 4)) {
else if (motion < (int)((unsigned)(-fscale) << 4)) { // [jart]
motion += fscale << 5;
}
@ -3404,7 +3404,7 @@ void plm_video_decode_block(plm_video_t *self, int block) {
n++;
// Dequantize, oddify, clip
level <<= 1;
level = (unsigned)level << 1; // [jart]
if (!self->macroblock_intra) {
level += (level < 0 ? -1 : 1);
}

View file

@ -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"
@ -111,7 +112,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))
@ -135,11 +138,6 @@ struct Action {
int wait;
};
struct Audio {
size_t i;
int16_t p[65536];
};
struct Status {
int wait;
char text[80];
@ -151,18 +149,16 @@ struct ZipGames {
};
static int frame_;
static int playfd_;
static int playpid_;
static size_t vtsize_;
static bool artifacts_;
static long tyn_, txn_;
static struct Frame vf_[2];
static struct Audio audio_;
static const char* inputfn_;
static struct Status status_;
static volatile bool exited_;
static volatile bool timeout_;
static volatile bool resized_;
static struct CosmoAudio* ca_;
static struct TtyRgb* ttyrgb_;
static unsigned char *R, *G, *B;
static struct ZipGames zipgames_;
@ -175,7 +171,7 @@ 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 int Clamp(int v) {
return MAX(0, MIN(255, v));
@ -229,9 +225,8 @@ 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);
}
}
}
}
@ -253,20 +248,16 @@ static void WriteString(const char* 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) {
@ -287,9 +278,8 @@ void OnPiped(void) {
void OnSigChld(void) {
waitpid(-1, 0, WNOHANG);
close(playfd_);
playpid_ = 0;
playfd_ = -1;
cosmoaudio_close(ca_);
ca_ = 0;
}
void InitFrame(struct Frame* f) {
@ -479,10 +469,6 @@ 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_];
@ -503,26 +489,6 @@ void TransmitVideo(void) {
}
}
void TransmitAudio(void) {
ssize_t rc;
if (!playpid_)
return;
if (!audio_.i)
return;
if (playfd_ == -1)
return;
if ((rc = Write(playfd_, audio_.p, audio_.i * sizeof(short))) != -1) {
rc /= sizeof(short);
memmove(audio_.p, audio_.p + rc, (audio_.i - rc) * sizeof(short));
audio_.i -= rc;
} else if (errno == EPIPE) {
kill(playpid_, SIGKILL);
close(playfd_);
playfd_ = -1;
Exit(0);
}
}
void ScaleVideoFrameToTeletypewriter(void) {
long y, x, yn, xn;
yn = DYN, xn = DXN;
@ -574,7 +540,6 @@ void PollAndSynchronize(void) {
}
} while (!timeout_);
TransmitVideo();
TransmitAudio();
timeout_ = false;
KeyCountdown(&arrow_);
KeyCountdown(&button_);
@ -601,9 +566,8 @@ void Raster(void) {
void FlushScanline(unsigned py) {
if (py == DYN - 1) {
if (!timeout_) {
if (!timeout_)
Raster();
}
timeout_ = false;
}
}
@ -1494,8 +1458,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 +1467,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
@ -1716,8 +1691,8 @@ void Op() {
if (!nmi_now)
nmi_edge_detected = false;
// Define function pointers for each opcode (00..FF) and each interrupt
// (100,101,102)
// Define function pointers for each opcode (00..FF) and each interrupt
// (100,101,102)
#define c(n) Ins<0x##n>, Ins<0x##n + 1>,
#define o(n) c(n) c(n + 2) c(n + 4) c(n + 6)
static void (*const i[0x108])() = {
@ -1745,9 +1720,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"))) {
@ -1763,43 +1735,12 @@ int PlayGame(const char* romfile, const char* opt_tasfile) {
InitPalette();
// 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);
// Read the ROM file header
u8 rom16count = fgetc(fp);
@ -1907,9 +1848,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 +1872,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;

View file

@ -136,19 +136,16 @@ static _Thread_local char dlerror_buf[128];
static const char *get_tmp_dir(void) {
const char *tmpdir;
if (!(tmpdir = getenv("TMPDIR")) || !*tmpdir) {
if (!(tmpdir = getenv("HOME")) || !*tmpdir) {
if (!(tmpdir = getenv("TMPDIR")) || !*tmpdir)
if (!(tmpdir = getenv("HOME")) || !*tmpdir)
tmpdir = ".";
}
}
return tmpdir;
}
static int is_file_newer_than(const char *path, const char *other) {
struct stat st1, st2;
if (stat(path, &st1)) {
if (stat(path, &st1))
return -1;
}
if (stat(other, &st2)) {
if (errno == ENOENT) {
return 2;
@ -191,29 +188,24 @@ static char *elf_map(int fd, Elf64_Ehdr *ehdr, Elf64_Phdr *phdr, long pagesz,
Elf64_Addr maxva = 0;
Elf64_Addr minva = -1;
for (Elf64_Phdr *p = phdr; p < phdr + ehdr->e_phnum; p++) {
if (p->p_type != PT_LOAD) {
if (p->p_type != PT_LOAD)
continue;
}
if (p->p_vaddr < minva) {
if (p->p_vaddr < minva)
minva = p->p_vaddr & -pagesz;
}
if (p->p_vaddr + p->p_memsz > maxva) {
if (p->p_vaddr + p->p_memsz > maxva)
maxva = p->p_vaddr + p->p_memsz;
}
}
uint8_t *base =
__sys_mmap(0, maxva - minva, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0, 0);
if (base == MAP_FAILED) {
if (base == MAP_FAILED)
return MAP_FAILED;
}
for (Elf64_Phdr *p = phdr; p < phdr + ehdr->e_phnum; p++) {
if (p->p_type != PT_LOAD) {
if (p->p_type == PT_INTERP && interp_size &&
(p->p_filesz >= interp_size - 1 ||
pread(fd, interp_path, p->p_filesz, p->p_offset) != p->p_filesz)) {
pread(fd, interp_path, p->p_filesz, p->p_offset) != p->p_filesz))
return MAP_FAILED;
}
continue;
}
Elf64_Addr skew = p->p_vaddr & (pagesz - 1);
@ -228,29 +220,24 @@ static char *elf_map(int fd, Elf64_Ehdr *ehdr, Elf64_Phdr *phdr, long pagesz,
prot1 &= ~PROT_EXEC;
}
if (__sys_mmap(base + p->p_vaddr - skew, skew + p->p_filesz, prot1,
MAP_FIXED | MAP_PRIVATE, fd, off, off) == MAP_FAILED) {
MAP_FIXED | MAP_PRIVATE, fd, off, off) == MAP_FAILED)
return MAP_FAILED;
}
if (b > a) {
if (b > a)
bzero(base + a, b - a);
}
if (c > b && __sys_mmap(base + b, c - b, prot2,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0,
0) == MAP_FAILED) {
0) == MAP_FAILED)
return MAP_FAILED;
}
if (prot1 != prot2 &&
sys_mprotect(base + p->p_vaddr - skew, skew + p->p_filesz, prot2)) {
sys_mprotect(base + p->p_vaddr - skew, skew + p->p_filesz, prot2))
return MAP_FAILED;
}
}
return (void *)base;
}
static bool elf_slurp(struct Loaded *l, int fd, const char *file) {
if (pread(fd, &l->eh, 64, 0) != 64) {
if (pread(fd, &l->eh, 64, 0) != 64)
return false;
}
if (!IsElf64Binary(&l->eh, 64) || //
l->eh.e_phnum > sizeof(l->ph) / sizeof(*l->ph) || //
l->eh.e_machine != get_host_elf_machine()) {
@ -258,9 +245,8 @@ static bool elf_slurp(struct Loaded *l, int fd, const char *file) {
return false;
}
int bytes = l->eh.e_phnum * sizeof(l->ph[0]);
if (pread(fd, l->ph, bytes, l->eh.e_phoff) != bytes) {
if (pread(fd, l->ph, bytes, l->eh.e_phoff) != bytes)
return false;
}
l->entry = (char *)l->eh.e_entry;
return true;
}
@ -268,9 +254,8 @@ static bool elf_slurp(struct Loaded *l, int fd, const char *file) {
static dontinline bool elf_load(struct Loaded *l, const char *file, long pagesz,
char *interp_path, size_t interp_size) {
int fd;
if ((fd = open(file, O_RDONLY | O_CLOEXEC)) == -1) {
if ((fd = open(file, O_RDONLY | O_CLOEXEC)) == -1)
return false;
}
if (!elf_slurp(l, fd, file)) {
close(fd);
return false;
@ -308,15 +293,13 @@ static dontinline void elf_exec(const char *file, char **envp) {
// load helper executable into address space
struct Loaded prog;
char interp_path[256] = {0};
if (!elf_load(&prog, file, pagesz, interp_path, sizeof(interp_path))) {
if (!elf_load(&prog, file, pagesz, interp_path, sizeof(interp_path)))
return;
}
// load platform c library into address space
struct Loaded interp;
if (!elf_load(&interp, interp_path, pagesz, 0, 0)) {
if (!elf_load(&interp, interp_path, pagesz, 0, 0))
return;
}
// count environment variables
int envc = 0;
@ -432,9 +415,8 @@ static dontinline char *foreign_alloc_block(void) {
if (!IsWindows()) {
p = __sys_mmap(0, sz, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT, -1, 0, 0);
if (p == MAP_FAILED) {
if (p == MAP_FAILED)
p = 0;
}
} else {
uintptr_t h;
if ((h = CreateFileMapping(-1, 0, kNtPageExecuteReadwrite, 0, sz, 0))) {
@ -455,11 +437,9 @@ static dontinline void *foreign_alloc(size_t n) {
static char *block;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
if (!block || READ32LE(block) + n > 65536) {
if (!(block = foreign_alloc_block())) {
if (!block || READ32LE(block) + n > 65536)
if (!(block = foreign_alloc_block()))
return 0;
}
}
res = block + READ32LE(block);
WRITE32LE(block, READ32LE(block) + n);
pthread_mutex_unlock(&lock);
@ -470,9 +450,8 @@ static uint8_t *movimm(uint8_t p[static 16], int reg, uint64_t val) {
#ifdef __x86_64__
int rex;
rex = AMD_REXW;
if (reg & 8) {
if (reg & 8)
rex |= AMD_REXB;
}
*p++ = rex;
*p++ = AMD_MOV_IMM | (reg & 7);
p = WRITE64LE(p, val);
@ -572,9 +551,8 @@ static dontinline bool foreign_compile(char exe[hasatleast PATH_MAX]) {
// construct path
strlcpy(exe, get_tmp_dir(), PATH_MAX);
strlcat(exe, "/.cosmo/", PATH_MAX);
if (mkdir(exe, 0755) && errno != EEXIST) {
if (mkdir(exe, 0755) && errno != EEXIST)
return false;
}
strlcat(exe, "dlopen-helper", PATH_MAX);
// skip build if helper exists and this program is older
@ -605,9 +583,8 @@ static dontinline bool foreign_compile(char exe[hasatleast PATH_MAX]) {
ssize_t got = pread(fd, sauce, sizeof(HELPER), 0);
close(fd);
if (got == sizeof(HELPER) - 1 &&
!memcmp(sauce, HELPER, sizeof(HELPER) - 1)) {
!memcmp(sauce, HELPER, sizeof(HELPER) - 1))
return true;
}
}
}
@ -615,9 +592,8 @@ static dontinline bool foreign_compile(char exe[hasatleast PATH_MAX]) {
char tmp[PATH_MAX];
strlcpy(tmp, src, PATH_MAX);
strlcat(tmp, ".XXXXXX", PATH_MAX);
if ((fd = mkostemp(tmp, O_CLOEXEC)) == -1) {
if ((fd = mkostemp(tmp, O_CLOEXEC)) == -1)
return false;
}
if (write(fd, HELPER, sizeof(HELPER) - 1) != sizeof(HELPER) - 1) {
close(fd);
unlink(tmp);
@ -635,9 +611,8 @@ static dontinline bool foreign_compile(char exe[hasatleast PATH_MAX]) {
// create executable
strlcpy(tmp, exe, PATH_MAX);
strlcat(tmp, ".XXXXXX", PATH_MAX);
if ((fd = mkostemp(tmp, O_CLOEXEC)) == -1) {
if ((fd = mkostemp(tmp, O_CLOEXEC)) == -1)
return false;
}
int pid, ws;
char *args[] = {
"cc",
@ -706,9 +681,8 @@ static void foreign_once(void) {
static bool foreign_init(void) {
bool res;
cosmo_once(&__foreign.once, foreign_once);
if (!(res = __foreign.is_supported)) {
if (!(res = __foreign.is_supported))
dlerror_set("dlopen() isn't supported on this platform");
}
return res;
}
@ -744,9 +718,8 @@ static void *dlopen_nt(const char *path, int mode) {
path16[n + 0] = 'l';
path16[n + 1] = 0;
}
if (!(handle = LoadLibrary(path16))) {
if (!(handle = LoadLibrary(path16)))
dlerror_set("library not found");
}
return (void *)handle;
}
@ -765,18 +738,14 @@ static void *dlopen_silicon(const char *path, int mode) {
int n;
int xnu_mode = 0;
char path2[PATH_MAX + 5];
if (mode & ~(RTLD_LOCAL | RTLD_LAZY | RTLD_NOW | RTLD_GLOBAL)) {
if (mode & ~(RTLD_LOCAL | RTLD_LAZY | RTLD_NOW | RTLD_GLOBAL))
xnu_mode = -1; // punt error to system dlerror() impl
}
if (!(mode & RTLD_GLOBAL)) {
if (!(mode & RTLD_GLOBAL))
xnu_mode |= XNU_RTLD_LOCAL; // unlike Linux, XNU defaults to RTLD_GLOBAL
}
if (mode & RTLD_NOW) {
if (mode & RTLD_NOW)
xnu_mode |= XNU_RTLD_NOW;
}
if (mode & RTLD_LAZY) {
if (mode & RTLD_LAZY)
xnu_mode |= XNU_RTLD_LAZY;
}
if ((n = strlen(path)) < PATH_MAX && n > 3 && //
path[n - 3] == '.' && //
path[n - 2] == 's' && //

View file

@ -172,9 +172,9 @@ and Lucida Console.\n\
#define TIMEIT(OUT_NANOS, FORM) \
do { \
struct timespec Start = timespec_real(); \
struct timespec Start = timespec_mono(); \
FORM; \
(OUT_NANOS) = timespec_tonanos(timespec_sub(timespec_real(), Start)); \
(OUT_NANOS) = timespec_tonanos(timespec_sub(timespec_mono(), Start)); \
} while (0)
typedef bool (*openspeaker_f)(void);
@ -279,16 +279,14 @@ static void OnResize(void) {
}
static struct timespec GetGraceTime(void) {
return timespec_sub(deadline_, timespec_real());
return timespec_sub(deadline_, timespec_mono());
}
static char *strntoupper(char *s, size_t n) {
size_t i;
for (i = 0; s[i] && i < n; ++i) {
if ('a' <= s[i] && s[i] <= 'z') {
for (i = 0; s[i] && i < n; ++i)
if ('a' <= s[i] && s[i] <= 'z')
s[i] -= 'a' - 'A';
}
}
return s;
}
@ -301,11 +299,9 @@ static int GetNamedVector(const struct NamedVector *choices, size_t n,
strncpy(name, s, sizeof(name));
#pragma GCC pop_options
strntoupper(name, sizeof(name));
for (i = 0; i < n; ++i) {
if (memcmp(choices[i].name, name, sizeof(name)) == 0) {
for (i = 0; i < n; ++i)
if (memcmp(choices[i].name, name, sizeof(name)) == 0)
return i;
}
}
return -1;
}
@ -318,8 +314,8 @@ static int GetLighting(const char *s) {
}
static void CloseSpeaker(void) {
if (ca_)
cosmoaudio_close(ca_);
cosmoaudio_close(ca_);
ca_ = 0;
}
static void ResizeVtFrame(struct VtFrame *f, size_t yn, size_t xn) {
@ -333,7 +329,7 @@ static float timespec_tofloat(struct timespec ts) {
static void RecordFactThatFrameWasFullyRendered(void) {
fcring_.p[fcring_.i] =
timespec_tofloat(timespec_sub(timespec_real(), starttime_));
timespec_tofloat(timespec_sub(timespec_mono(), starttime_));
fcring_.n += 1;
fcring_.i += 1;
fcring_.i &= ARRAYLEN(fcring_.p) - 1;
@ -383,9 +379,8 @@ static void DimensionDisplay(void) {
wsize_.ws_row = 25;
wsize_.ws_col = 80;
wsize_ = (struct winsize){.ws_row = 40, .ws_col = 80};
if (tcgetwinsize(outfd_, &wsize_) == -1) {
if (tcgetwinsize(outfd_, &wsize_) == -1)
tcgetwinsize(0, &wsize_);
}
dh_ = wsize_.ws_row * 2;
dw_ = wsize_.ws_col * 2;
}
@ -409,17 +404,21 @@ static void DimensionDisplay(void) {
ResizeVtFrame(&vtframe_[1], (g2_->yn), g2_->xn);
f1_ = &vtframe_[0];
f2_ = &vtframe_[1];
if (ttymode_) {
if (ttymode_)
homerow_ = MIN(wsize_.ws_row - HALF(g2_->yn),
HALF(wsize_.ws_row - HALF(g2_->yn)));
}
lastrow_ = homerow_ + HALF(g2_->yn);
ComputeColoringSolution();
} while (resized_);
}
static bool OpenSpeaker(void) {
return cosmoaudio_open(&ca_, srate_, chans_) == COSMOAUDIO_SUCCESS;
struct CosmoAudioOpenOptions cao = {};
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
cao.deviceType = kCosmoAudioDeviceTypePlayback;
cao.sampleRate = srate_;
cao.channels = chans_;
return cosmoaudio_open(&ca_, &cao) == COSMOAUDIO_SUCCESS;
}
static void OnAudio(plm_t *mpeg, plm_samples_t *samples, void *user) {
@ -432,12 +431,10 @@ static void OnAudio(plm_t *mpeg, plm_samples_t *samples, void *user) {
}
static void DescribeAlgorithms(char *p) {
if (dither_ && TTYQUANT()->alg != kTtyQuantTrue) {
if (dither_ && TTYQUANT()->alg != kTtyQuantTrue)
p = stpcpy(p, " ithered");
}
if (yonly_) {
if (yonly_)
p = stpcpy(p, " grayscaled");
}
p += sprintf(p, " magikarp:%d:%d", lumakernel_, chromakernel_);
switch (TTYQUANT()->alg) {
case kTtyQuantTrue:
@ -699,9 +696,8 @@ static void TranscodeVideo(plm_frame_t *pf) {
default:
break;
}
if (dither_ && TTYQUANT()->alg != kTtyQuantTrue) {
if (dither_ && TTYQUANT()->alg != kTtyQuantTrue)
dither(g2_->yn, g2_->xn, g2_->b, g2_->yn, g2_->xn);
}
});
if (ShouldUseFrameBuffer()) {
@ -768,13 +764,12 @@ static ssize_t WriteVideoCall(void) {
amt = min(4096 * 4, f1_->n - f1_->i);
if ((rc = write(outfd_, f1_->bytes + f1_->i, amt)) != -1) {
if ((f1_->i += rc) == f1_->n) {
if (plm_get_audio_enabled(plm_)) {
if (plm_get_audio_enabled(plm_))
plm_set_audio_lead_time(
plm_,
max(0,
min(timespec_tofloat(timespec_sub(timespec_real(), f1_start_)),
min(timespec_tofloat(timespec_sub(timespec_mono(), f1_start_)),
plm_get_samplerate(plm_) / PLM_AUDIO_SAMPLES_PER_FRAME)));
}
f1_start_ = f2_start_;
f1_->i = f1_->n = 0;
struct VtFrame *t = f1_;
@ -790,9 +785,8 @@ static void DrainVideo(void) {
ttywrite(outfd_, f1_->bytes + f1_->i, f1_->n - f1_->i);
f1_->i = f1_->n = 0;
}
if (f2_ && f2_->n) {
if (f2_ && f2_->n)
f2_->i = f2_->n = 0;
}
}
static void WriteVideo(void) {
@ -1200,20 +1194,19 @@ static void PrintVideo(void) {
dura_ = timespec_frommicros(min(MAX_FRAMERATE, 1 / plm_get_framerate(plm_)) *
1e6);
INFOF("framerate=%f dura=%f", plm_get_framerate(plm_), dura_);
next_tick = deadline_ = decode_last = timespec_real();
next_tick = deadline_ = decode_last = timespec_mono();
next_tick = timespec_add(next_tick, dura_);
deadline_ = timespec_add(deadline_, dura_);
do {
DEBUGF("plm_decode [grace=%,ldns]", timespec_tonanos(GetGraceTime()));
decode_start_ = timespec_real();
decode_start_ = timespec_mono();
plm_decode(plm_,
timespec_tofloat(timespec_sub(decode_start_, decode_last)));
decode_last = decode_start_;
decode_end = timespec_real();
decode_end = timespec_mono();
lag = timespec_sub(decode_end, decode_start_);
while (timespec_cmp(timespec_add(decode_end, lag), next_tick) > 0) {
while (timespec_cmp(timespec_add(decode_end, lag), next_tick) > 0)
next_tick = timespec_add(next_tick, dura_);
}
deadline_ = timespec_sub(next_tick, lag);
if (gotvideo_ || !plm_get_video_enabled(plm_)) {
gotvideo_ = false;
@ -1221,9 +1214,8 @@ static void PrintVideo(void) {
timespec_tonanos(lag), timespec_tonanos(GetGraceTime()));
}
do {
if (!setjmp(jbi_)) {
if (!setjmp(jbi_))
PerformBestEffortIo();
}
HandleSignals();
} while (timespec_tomillis(GetGraceTime()) > 0);
} while (plm_ && !plm_has_ended(plm_));
@ -1304,9 +1296,8 @@ static void PickDefaults(void) {
*
* strcmp(nulltoempty(getenv("TERM")), "xterm-direct") == 0
*/
if (strcmp(nulltoempty(getenv("TERM")), "xterm-kitty") == 0) {
if (strcmp(nulltoempty(getenv("TERM")), "xterm-kitty") == 0)
ttyquantsetup(kTtyQuantTrue, TTYQUANT()->chans, kTtyBlocksUnicode);
}
}
#define FBIOGET_VSCREENINFO 0x4600
@ -1434,7 +1425,7 @@ int main(int argc, char *argv[]) {
__cxa_atexit((void *)OnExit, NULL, NULL);
__log_file = fopen(logpath_, "a");
if (ischardev(infd_) && ischardev(outfd_)) {
/* CHECK_NE(-1, fcntl(infd_, F_SETFL, O_NONBLOCK)); */
/* CHECK_NE(-1, fcntl(outfd_, F_SETFL, O_NONBLOCK)); */
} else if (infd_ != outfd_) {
infd_ = -1;
}
@ -1444,7 +1435,7 @@ int main(int argc, char *argv[]) {
longjmp(jb_, 1);
OpenVideo();
DimensionDisplay();
starttime_ = timespec_real();
starttime_ = timespec_mono();
PrintVideo();
}
INFOF("jb_ triggered");