From ce2fbf93252e0a9b53adf6b8393fa821bf867f53 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 18 Sep 2024 17:17:02 -0700 Subject: [PATCH] Write network audio programs --- Makefile | 1 + dsp/BUILD.mk | 1 + dsp/audio/cosmoaudio/cosmoaudio.c | 6 +- dsp/prog/BUILD.mk | 43 +++++++++ dsp/prog/loudness.h | 41 ++++++++ dsp/prog/recvaudio.c | 127 +++++++++++++++++++++++++ dsp/prog/sendaudio.c | 149 ++++++++++++++++++++++++++++++ tool/cosmocc/bin/cosmocc | 2 +- 8 files changed, 366 insertions(+), 4 deletions(-) create mode 100644 dsp/prog/BUILD.mk create mode 100644 dsp/prog/loudness.h create mode 100644 dsp/prog/recvaudio.c create mode 100644 dsp/prog/sendaudio.c diff --git a/Makefile b/Makefile index 181021d01..cd93cdeb9 100644 --- a/Makefile +++ b/Makefile @@ -279,6 +279,7 @@ 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 # │ diff --git a/dsp/BUILD.mk b/dsp/BUILD.mk index 9fff28d51..ae2150196 100644 --- a/dsp/BUILD.mk +++ b/dsp/BUILD.mk @@ -6,4 +6,5 @@ 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 diff --git a/dsp/audio/cosmoaudio/cosmoaudio.c b/dsp/audio/cosmoaudio/cosmoaudio.c index bff7d14cf..e518a4852 100644 --- a/dsp/audio/cosmoaudio/cosmoaudio.c +++ b/dsp/audio/cosmoaudio/cosmoaudio.c @@ -469,16 +469,16 @@ COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio* ca, if (in_out_writeFrames && 1u + *in_out_writeFrames > ca->outputBufferFrames) return COSMOAUDIO_ENOBUF; for (;;) { - int done = 0; + 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; + 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; + done &= writable >= (ma_uint32)*in_out_writeFrames; } if (done) { if (in_out_readFrames) diff --git a/dsp/prog/BUILD.mk b/dsp/prog/BUILD.mk new file mode 100644 index 000000000..adc0668ee --- /dev/null +++ b/dsp/prog/BUILD.mk @@ -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) diff --git a/dsp/prog/loudness.h b/dsp/prog/loudness.h new file mode 100644 index 000000000..75a1e9518 --- /dev/null +++ b/dsp/prog/loudness.h @@ -0,0 +1,41 @@ +#ifndef COSMOPOLITAN_DSP_PROG_LOUDNESS_H_ +#define COSMOPOLITAN_DSP_PROG_LOUDNESS_H_ +#include +#include + +#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_ */ diff --git a/dsp/prog/recvaudio.c b/dsp/prog/recvaudio.c new file mode 100644 index 000000000..85ef98ed8 --- /dev/null +++ b/dsp/prog/recvaudio.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/dsp/prog/sendaudio.c b/dsp/prog/sendaudio.c new file mode 100644 index 000000000..436bbfcdb --- /dev/null +++ b/dsp/prog/sendaudio.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/tool/cosmocc/bin/cosmocc b/tool/cosmocc/bin/cosmocc index fba76f91d..3c27aa952 100755 --- a/tool/cosmocc/bin/cosmocc +++ b/tool/cosmocc/bin/cosmocc @@ -96,7 +96,7 @@ use_clang() { TARGET_X86_64="--target=x86_64" TARGET_AARCH64="--target=aarch64" FPORTCOSMO= - FNO_INLINE_FUNCTIONS_CALLED_ONCE="-fno-inline-functions-called-once" + FNO_INLINE_FUNCTIONS_CALLED_ONCE= } use_gcc