Skip to content

Commit ce2fbf9

Browse files
committed
Write network audio programs
1 parent 1bfb348 commit ce2fbf9

File tree

8 files changed

+366
-4
lines changed

8 files changed

+366
-4
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ include dsp/scale/BUILD.mk # │
279279
include dsp/mpeg/BUILD.mk #
280280
include dsp/tty/BUILD.mk #
281281
include dsp/audio/BUILD.mk #
282+
include dsp/prog/BUILD.mk #
282283
include dsp/BUILD.mk #
283284
include third_party/stb/BUILD.mk #
284285
include third_party/mbedtls/BUILD.mk #

dsp/BUILD.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ o/$(MODE)/dsp: o/$(MODE)/dsp/audio \
66
o/$(MODE)/dsp/core \
77
o/$(MODE)/dsp/mpeg \
88
o/$(MODE)/dsp/scale \
9+
o/$(MODE)/dsp/prog \
910
o/$(MODE)/dsp/tty

dsp/audio/cosmoaudio/cosmoaudio.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,16 +469,16 @@ COSMOAUDIO_ABI int cosmoaudio_poll(struct CosmoAudio* ca,
469469
if (in_out_writeFrames && 1u + *in_out_writeFrames > ca->outputBufferFrames)
470470
return COSMOAUDIO_ENOBUF;
471471
for (;;) {
472-
int done = 0;
472+
int done = 1;
473473
ma_uint32 readable = 0;
474474
ma_uint32 writable = 0;
475475
if (in_out_readFrames) {
476476
readable = ma_pcm_rb_available_read(&ca->input);
477-
done |= readable >= (ma_uint32)*in_out_readFrames;
477+
done &= readable >= (ma_uint32)*in_out_readFrames;
478478
}
479479
if (in_out_writeFrames) {
480480
writable = ma_pcm_rb_available_write(&ca->output);
481-
done |= writable >= (ma_uint32)*in_out_writeFrames;
481+
done &= writable >= (ma_uint32)*in_out_writeFrames;
482482
}
483483
if (done) {
484484
if (in_out_readFrames)

dsp/prog/BUILD.mk

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
2+
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
3+
4+
PKGS += DSP_PROG
5+
6+
DSP_PROG_FILES := $(wildcard dsp/prog/*)
7+
DSP_PROG_HDRS = $(filter %.h,$(DSP_PROG_FILES))
8+
DSP_PROG_SRCS = $(filter %.c,$(DSP_PROG_FILES))
9+
DSP_PROG_OBJS = $(DSP_PROG_SRCS:%.c=o/$(MODE)/%.o)
10+
DSP_PROG_COMS = $(DSP_PROG_SRCS:%.c=o/$(MODE)/%)
11+
DSP_PROG_BINS = $(DSP_PROG_COMS) $(DSP_PROG_COMS:%=%.dbg)
12+
13+
DSP_PROG_DIRECTDEPS = \
14+
DSP_AUDIO \
15+
LIBC_CALLS \
16+
LIBC_INTRIN \
17+
LIBC_NEXGEN32E \
18+
LIBC_RUNTIME \
19+
LIBC_SOCK \
20+
LIBC_STDIO \
21+
LIBC_SYSV \
22+
LIBC_TINYMATH \
23+
THIRD_PARTY_MUSL \
24+
25+
DSP_PROG_DEPS := \
26+
$(call uniq,$(foreach x,$(DSP_PROG_DIRECTDEPS),$($(x))))
27+
28+
o/$(MODE)/dsp/prog/prog.pkg: \
29+
$(DSP_PROG_OBJS) \
30+
$(foreach x,$(DSP_PROG_DIRECTDEPS),$($(x)_A).pkg)
31+
32+
o/$(MODE)/dsp/prog/%.dbg: \
33+
$(DSP_PROG_DEPS) \
34+
o/$(MODE)/dsp/prog/prog.pkg \
35+
o/$(MODE)/dsp/prog/%.o \
36+
$(CRT) \
37+
$(APE_NO_MODIFY_SELF)
38+
@$(APELINK)
39+
40+
$(DSP_PROG_OBJS): dsp/prog/BUILD.mk
41+
42+
.PHONY: o/$(MODE)/dsp/prog
43+
o/$(MODE)/dsp/prog: $(DSP_PROG_BINS)

dsp/prog/loudness.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#ifndef COSMOPOLITAN_DSP_PROG_LOUDNESS_H_
2+
#define COSMOPOLITAN_DSP_PROG_LOUDNESS_H_
3+
#include <math.h>
4+
#include <stdio.h>
5+
6+
#define MIN_DECIBEL -60
7+
#define MAX_DECIBEL 0
8+
9+
// computes root of mean squares
10+
static double rms(float *p, int n) {
11+
double s = 0;
12+
for (int i = 0; i < n; ++i)
13+
s += p[i] * p[i];
14+
return sqrt(s / n);
15+
}
16+
17+
// converts rms to decibel
18+
static double rms_to_db(double rms) {
19+
double db = 20 * log10(rms);
20+
db = fmin(db, MAX_DECIBEL);
21+
db = fmax(db, MIN_DECIBEL);
22+
return db;
23+
}
24+
25+
// char meter[21];
26+
// format_decibel_meter(meter, 20, rms_to_db(rms(samps, count)))
27+
static char *format_decibel_meter(char *meter, int width, double db) {
28+
double range = MAX_DECIBEL - MIN_DECIBEL;
29+
int filled = (db - MIN_DECIBEL) / range * width;
30+
for (int i = 0; i < width; ++i) {
31+
if (i < filled) {
32+
meter[i] = '=';
33+
} else {
34+
meter[i] = ' ';
35+
}
36+
}
37+
meter[width] = 0;
38+
return meter;
39+
}
40+
41+
#endif /* COSMOPOLITAN_DSP_PROG_LOUDNESS_H_ */

dsp/prog/recvaudio.c

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#if 0
2+
/*─────────────────────────────────────────────────────────────────╗
3+
│ To the extent possible under law, Justine Tunney has waived │
4+
│ all copyright and related or neighboring rights to this file, │
5+
│ as it is written in the following disclaimers: │
6+
│ • http://unlicense.org/ │
7+
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
8+
╚─────────────────────────────────────────────────────────────────*/
9+
#endif
10+
#include <arpa/inet.h>
11+
#include <assert.h>
12+
#include <cosmoaudio.h>
13+
#include <errno.h>
14+
#include <math.h>
15+
#include <netinet/in.h>
16+
#include <signal.h>
17+
#include <stdint.h>
18+
#include <stdio.h>
19+
#include <stdlib.h>
20+
#include <sys/socket.h>
21+
#include <sys/types.h>
22+
#include <time.h>
23+
#include "loudness.h"
24+
25+
/**
26+
* @fileoverview plays audio from remote computer on speaker
27+
* @see dsp/prog/sendaudio.c
28+
*/
29+
30+
#define SAMPLING_RATE 44100
31+
#define FRAMES_PER_SECOND 60
32+
#define DEBUG_LOG 0
33+
#define PORT 9834
34+
35+
#define CHUNK_FRAMES (SAMPLING_RATE / FRAMES_PER_SECOND)
36+
37+
static_assert(CHUNK_FRAMES * sizeof(short) < 1472,
38+
"audio chunks won't fit in udp ethernet packet");
39+
40+
sig_atomic_t g_done;
41+
42+
void onsig(int sig) {
43+
g_done = 1;
44+
}
45+
46+
short toshort(float x) {
47+
return fmaxf(-1, fminf(1, x)) * 32767;
48+
}
49+
50+
float tofloat(short x) {
51+
return x / 32768.f;
52+
}
53+
54+
int main(int argc, char* argv[]) {
55+
56+
// listen on udp port for audio
57+
int server;
58+
if ((server = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
59+
perror("socket");
60+
return 3;
61+
}
62+
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)};
63+
if (bind(server, (struct sockaddr*)&addr, sizeof(addr))) {
64+
perror("bind");
65+
return 4;
66+
}
67+
68+
// setup signals
69+
struct sigaction sa;
70+
sa.sa_flags = 0;
71+
sa.sa_handler = onsig;
72+
sigemptyset(&sa.sa_mask);
73+
sigaction(SIGINT, &sa, 0);
74+
75+
// configure cosmo audio
76+
struct CosmoAudioOpenOptions cao = {0};
77+
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
78+
cao.deviceType = kCosmoAudioDeviceTypePlayback;
79+
cao.sampleRate = SAMPLING_RATE;
80+
cao.bufferFrames = CHUNK_FRAMES * 2;
81+
cao.debugLog = DEBUG_LOG;
82+
cao.channels = 1;
83+
84+
// connect to microphone and speaker
85+
int status;
86+
struct CosmoAudio* ca;
87+
status = cosmoaudio_open(&ca, &cao);
88+
if (status != COSMOAUDIO_SUCCESS) {
89+
fprintf(stderr, "failed to open audio: %d\n", status);
90+
return 5;
91+
}
92+
93+
while (!g_done) {
94+
// read from network
95+
ssize_t got;
96+
short buf16[CHUNK_FRAMES];
97+
if ((got = read(server, buf16, CHUNK_FRAMES * sizeof(short))) == -1) {
98+
if (errno == EINTR)
99+
continue;
100+
perror("read");
101+
return 7;
102+
}
103+
if (got != CHUNK_FRAMES * sizeof(short)) {
104+
fprintf(stderr, "warning: got partial audio frame\n");
105+
continue;
106+
}
107+
108+
// write to speaker
109+
float buf32[CHUNK_FRAMES];
110+
for (int i = 0; i < CHUNK_FRAMES; ++i)
111+
buf32[i] = tofloat(buf16[i]);
112+
cosmoaudio_poll(ca, 0, (int[]){CHUNK_FRAMES});
113+
cosmoaudio_write(ca, buf32, CHUNK_FRAMES);
114+
115+
// print loudness in ascii
116+
char meter[21];
117+
double db = rms_to_db(rms(buf32, CHUNK_FRAMES));
118+
format_decibel_meter(meter, 20, db);
119+
printf("\r%s| %+6.2f dB", meter, db);
120+
fflush(stdout);
121+
}
122+
123+
// clean up resources
124+
cosmoaudio_flush(ca);
125+
cosmoaudio_close(ca);
126+
close(server);
127+
}

dsp/prog/sendaudio.c

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#if 0
2+
/*─────────────────────────────────────────────────────────────────╗
3+
│ To the extent possible under law, Justine Tunney has waived │
4+
│ all copyright and related or neighboring rights to this file, │
5+
│ as it is written in the following disclaimers: │
6+
│ • http://unlicense.org/ │
7+
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
8+
╚─────────────────────────────────────────────────────────────────*/
9+
#endif
10+
#include <arpa/inet.h>
11+
#include <assert.h>
12+
#include <cosmoaudio.h>
13+
#include <errno.h>
14+
#include <math.h>
15+
#include <netdb.h>
16+
#include <netinet/in.h>
17+
#include <signal.h>
18+
#include <stdint.h>
19+
#include <stdio.h>
20+
#include <stdlib.h>
21+
#include <sys/socket.h>
22+
#include <sys/types.h>
23+
#include <time.h>
24+
#include "loudness.h"
25+
26+
/**
27+
* @fileoverview sends audio from microphone to remote computer
28+
* @see dsp/prog/recvaudio.c
29+
*/
30+
31+
#define SAMPLING_RATE 44100
32+
#define FRAMES_PER_SECOND 60
33+
#define DEBUG_LOG 0
34+
#define PORT 9834
35+
36+
#define CHUNK_FRAMES (SAMPLING_RATE / FRAMES_PER_SECOND)
37+
38+
static_assert(CHUNK_FRAMES * sizeof(short) < 1472,
39+
"audio chunks won't fit in udp ethernet packet");
40+
41+
sig_atomic_t g_done;
42+
43+
void onsig(int sig) {
44+
g_done = 1;
45+
}
46+
47+
short toshort(float x) {
48+
return fmaxf(-1, fminf(1, x)) * 32767;
49+
}
50+
51+
float tofloat(short x) {
52+
return x / 32768.f;
53+
}
54+
55+
uint32_t host2ip(const char* host) {
56+
uint32_t ip;
57+
if ((ip = inet_addr(host)) != -1u)
58+
return ip;
59+
int rc;
60+
struct addrinfo* ai = NULL;
61+
struct addrinfo hint = {AI_NUMERICSERV, AF_INET, SOCK_STREAM, IPPROTO_TCP};
62+
if ((rc = getaddrinfo(host, "0", &hint, &ai))) {
63+
fprintf(stderr, "%s: %s\n", host, gai_strerror(rc));
64+
exit(50 + rc);
65+
}
66+
ip = ntohl(((struct sockaddr_in*)ai->ai_addr)->sin_addr.s_addr);
67+
freeaddrinfo(ai);
68+
return ip;
69+
}
70+
71+
int main(int argc, char* argv[]) {
72+
73+
if (argc != 2) {
74+
fprintf(stderr, "%s: missing host argument\n", argv[0]);
75+
return 1;
76+
}
77+
78+
// get host argument
79+
const char* remote_host = argv[1];
80+
uint32_t ip = host2ip(remote_host);
81+
82+
// connect to server
83+
int client;
84+
if ((client = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
85+
perror(remote_host);
86+
return 3;
87+
}
88+
struct sockaddr_in addr = {.sin_family = AF_INET,
89+
.sin_port = htons(PORT),
90+
.sin_addr.s_addr = htonl(ip)};
91+
if (connect(client, (struct sockaddr*)&addr, sizeof(addr))) {
92+
perror(remote_host);
93+
return 4;
94+
}
95+
96+
// setup signals
97+
struct sigaction sa;
98+
sa.sa_flags = 0;
99+
sa.sa_handler = onsig;
100+
sigemptyset(&sa.sa_mask);
101+
sigaction(SIGINT, &sa, 0);
102+
103+
// configure cosmo audio
104+
struct CosmoAudioOpenOptions cao = {0};
105+
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
106+
cao.deviceType = kCosmoAudioDeviceTypeCapture;
107+
cao.sampleRate = SAMPLING_RATE;
108+
cao.bufferFrames = CHUNK_FRAMES * 2;
109+
cao.debugLog = DEBUG_LOG;
110+
cao.channels = 1;
111+
112+
// connect to microphone and speaker
113+
int status;
114+
struct CosmoAudio* ca;
115+
status = cosmoaudio_open(&ca, &cao);
116+
if (status != COSMOAUDIO_SUCCESS) {
117+
fprintf(stderr, "failed to open audio: %d\n", status);
118+
return 5;
119+
}
120+
121+
while (!g_done) {
122+
// read from microphone
123+
float buf32[CHUNK_FRAMES];
124+
cosmoaudio_poll(ca, (int[]){CHUNK_FRAMES}, 0);
125+
cosmoaudio_read(ca, buf32, CHUNK_FRAMES);
126+
short buf16[CHUNK_FRAMES];
127+
for (int i = 0; i < CHUNK_FRAMES; ++i)
128+
buf16[i] = toshort(buf32[i]);
129+
130+
// send to server
131+
if (write(client, buf16, CHUNK_FRAMES * sizeof(short)) == -1) {
132+
if (errno == EINTR && g_done)
133+
break;
134+
perror(remote_host);
135+
return 7;
136+
}
137+
138+
// print loudness in ascii
139+
char meter[21];
140+
double db = rms_to_db(rms(buf32, CHUNK_FRAMES));
141+
format_decibel_meter(meter, 20, db);
142+
printf("\r%s| %+6.2f dB", meter, db);
143+
fflush(stdout);
144+
}
145+
146+
// clean up resources
147+
cosmoaudio_close(ca);
148+
close(client);
149+
}

0 commit comments

Comments
 (0)