forked from hkc/sfxd
1
0
Fork 0
sfxd/src/sfxd.c

456 lines
14 KiB
C

#include "common.h"
#include "miniaudio.h"
#include <getopt.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define KEY_LENGTH 256
#define MAX_SOUNDS_PER_KEY 32
#define MAX_SOURCE_DEPTH 32
struct msg_target {
FILE *file_handle;
int sock_fd;
struct sockaddr *sock_addr;
ssize_t sock_addr_size;
};
struct ipc_data {
struct sockaddr_un sa_server, sa_client;
int sock_fd;
};
struct audio_data {
ma_engine engine;
};
struct sfx_pool {
struct sfx_pool_item {
char key[KEY_LENGTH];
char path[PATH_MAX];
ma_sound *sounds;
int last_index;
float volume;
float pitch_min, pitch_max;
} *sounds;
int cap, use;
};
ssize_t send_data(struct msg_target tgt, const void *data, size_t len);
ssize_t send_txt(struct msg_target tgt, const char *fmt, ...);
void execute_command(struct msg_target tgt, const char *command, const char *params);
bool execute_file(struct msg_target tgt, const char *path, int depth);
bool ipc_init(const char *sock_path);
void ipc_loop(void);
bool audio_init(void);
void sfx_pool_clear(void);
void sfx_pool_grow(int size);
struct sfx_pool_item *sfx_pool_lookup(const char *key);
struct sfx_pool_item *sfx_pool_find_place_for(const char *key);
struct sfx_pool_item *sfx_pool_load(const char *key, const char *path);
struct ipc_data ipc = { 0 };
struct audio_data audio = { 0 };
struct sfx_pool sounds_pool = { 0, 0, 0 };
#ifndef NO_COUNTERS
struct global_counters {
uint64_t pool_read;
uint64_t pool_write;
uint64_t hash_misses_write;
uint64_t hash_misses_read;
} global_counters = { 0 };
#endif
void usage(int argc, char **argv) {
// TODO
}
int main(int argc, char **argv) {
char *sock_path = DAEMON_SOCKET_PATH;
sfx_pool_grow(32);
if (!audio_init()) {
return EXIT_FAILURE;
}
int c;
while ((c = getopt(argc, argv, "hC:S:")) != -1) {
switch (c) {
case 'h':
usage(argc, argv);
return EXIT_SUCCESS;
break;
case 'S':
sock_path = optarg;
break;
case 'C':
EXPECT(execute_file((struct msg_target) { stdout, 0, 0, 0 }, optarg, 0), == true);
break;
}
}
if (!ipc_init(sock_path)) {
unlink(sock_path);
return EXIT_FAILURE;
}
ipc_loop();
unlink(sock_path);
}
bool ipc_init(const char *sock_path) {
SOFT_EXPECT(ipc.sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0), > 0, false);
memset(&ipc.sa_server, 0, sizeof(ipc.sa_server));
memset(&ipc.sa_client, 0, sizeof(ipc.sa_client));
ipc.sa_server.sun_family = AF_UNIX;
strncpy(ipc.sa_server.sun_path, sock_path, sizeof(ipc.sa_server.sun_path) - 1);
unlink(ipc.sa_server.sun_path);
SOFT_EXPECT(bind(ipc.sock_fd, (struct sockaddr *)&ipc.sa_server, sizeof(struct sockaddr_un)), >= 0, false);
return true;
}
static char buffer_ipc_loop[BUFFER_SIZE];
void ipc_loop(void) {
ssize_t data_len;
socklen_t sl_client = sizeof(ipc.sa_client);
while ((data_len = recvfrom(ipc.sock_fd, buffer_ipc_loop, sizeof(buffer_ipc_loop) - 1, 0, (struct sockaddr *)&ipc.sa_client, &sl_client)) != -1) {
buffer_ipc_loop[data_len] = '\0';
struct sockaddr_un *sau_client = &ipc.sa_client;
char *command = buffer_ipc_loop;
char *params = strchr(command, ' ');
if (params != NULL) {
*params = '\0';
params++;
}
execute_command((struct msg_target) { 0, ipc.sock_fd, (struct sockaddr *)&ipc.sa_client, sl_client }, command, params);
SOFT_EXPECT(sendto(ipc.sock_fd, "OK\n", 4, 0, (struct sockaddr *)&ipc.sa_client, sl_client), == 4,);
SOFT_EXPECT(sendto(ipc.sock_fd, "", 0, 0, (struct sockaddr *)&ipc.sa_client, sl_client), == 0,);
sl_client = sizeof(ipc.sa_client);
}
}
bool audio_init(void) {
ma_result result;
SOFT_EXPECT((result = ma_engine_init(NULL, &audio.engine)), == MA_SUCCESS, false);
return true;
}
ssize_t send_data(struct msg_target tgt, const void *data, size_t len) {
ssize_t written;
if (tgt.file_handle) {
if ((written = fwrite(data, 1, len, tgt.file_handle)) != len) {
SOFT_PANIC_FMT("fwrite(ptr=%p, size=%zd, nmemb=%zd, stream=%p) -> %zd",
data, 1, len, tgt.file_handle, written);
}
} else {
if ((written = sendto(tgt.sock_fd, data, len, 0, tgt.sock_addr, tgt.sock_addr_size)) != len) {
SOFT_PANIC_FMT("sendto(socket=%d, message=%p, length=%zd, flags=%d, dest_addr=%p, dest_len=%zd) -> %zd",
tgt.sock_fd, data, len, 0, tgt.sock_addr, tgt.sock_addr_size, written);
}
}
return written;
}
static char txtbuf[BUFFER_SIZE];
ssize_t send_txt(struct msg_target tgt, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vsnprintf(txtbuf, BUFFER_SIZE - 1, fmt, args);
va_end(args);
return send_data(tgt, txtbuf, strlen(txtbuf) + 1);
}
void execute_command(struct msg_target tgt, const char *command, const char *params) {
send_txt(tgt, "INF: Received '%s' with '%s'\n", command, params);
if (0 == strcmp(command, "load")) {
const char *key = params;
char *path = strchr(params, ' ');
*path = '\0';
path++;
if (sounds_pool.use / (float)sounds_pool.cap >= 0.75) {
send_txt(tgt, "DBG: pool overflow: %d/%d (%.3f%%)\n",
sounds_pool.use, sounds_pool.cap,
100.0 * sounds_pool.use / (float)sounds_pool.cap);
sfx_pool_grow(sounds_pool.cap * 2);
}
send_txt(tgt, "Loading audio key=%s from %s\n", key, path);
struct sfx_pool_item *sound = sfx_pool_load(key, path);
if (sound == NULL) {
send_txt(tgt, "ERR: Load failed\n");
} else {
send_txt(tgt, "OK: Loaded as %08x\n", adler32(key, strlen(key)));
}
} else if (0 == strcmp(command, "play")) {
struct sfx_pool_item *sound = sfx_pool_lookup(params);
if (!sound) {
send_txt(tgt, "No such sound: '%s'\n", params);
return;
}
ma_sound *sfx = &sound->sounds[(sound->last_index++) % MAX_SOUNDS_PER_KEY];
ma_sound_set_pitch(sfx, sound->pitch_min);
ma_sound_set_volume(sfx, sound->volume);
ma_sound_start(sfx);
} else if (0 == strcmp(command, "play:rs") || 0 == strcmp(command, "play_rs")) {
struct sfx_pool_item *sound = sfx_pool_lookup(params);
if (!sound) {
send_txt(tgt, "ERR: No such sound: '%s'\n", params);
return;
}
ma_sound *sfx = &sound->sounds[(sound->last_index++) % MAX_SOUNDS_PER_KEY];
float pitch = sound->pitch_min + (rand() / (float)RAND_MAX) * (sound->pitch_max - sound->pitch_min);
ma_sound_set_pitch(sfx, pitch);
ma_sound_set_volume(sfx, sound->volume);
ma_sound_start(sfx);
} else if (0 == strcmp(command, "set:pitch") || 0 == strcmp(command, "setpitchrange")) {
float min, max;
char *key;
sscanf(params, "%ms %f %f", &key, &min, &max);
struct sfx_pool_item *sound = sfx_pool_lookup(key);
if (!sound) {
send_txt(tgt, "ERR: No such sound: '%s'\n", key);
return;
}
if (min < 0) send_txt(tgt, "WARN: min pitch is too small: %f < 0\n", min);
if (max < 0) send_txt(tgt, "WARN: max pitch is too small: %f < 0\n", max);
if (max < min) {
send_txt(tgt, "WARN: max < min: %f < %f\n", max, min);
float tmp = max;
max = min;
min = tmp;
}
if (min < 0 || max < 0) {
send_txt(tgt, "ERR: either min or max are too small\n");
return;
}
sound->pitch_min = min;
sound->pitch_max = max;
} else if (0 == strcmp(command, "set:volume") || 0 == strcmp(command, "volume")) {
float vol;
char *key;
sscanf(params, "%ms %f", &key, &vol);
struct sfx_pool_item *sound = sfx_pool_lookup(key);
if (!sound) {
send_txt(tgt, "ERR: No such sound: '%s'\n", key);
return;
}
if (vol < 0 || vol > 1) {
send_txt(tgt, "ERR: Volume out of range: 0 <= %f < 1\n", vol);
}
sound->volume = vol;
} else if (0 == strcmp(command, "dump")) {
for (int i = 0; i < sounds_pool.cap; i++) {
struct sfx_pool_item item = sounds_pool.sounds[i];
if (item.key[0] == '\0') {
send_txt(tgt, "%3d (unused)\n", i);
} else {
send_txt(tgt, "%3d vol=%7.5f pitch=%7.5f..%7.5f @%2d (0x%08x) \"%s\"\n",
i, item.volume, item.pitch_min, item.pitch_max, item.last_index, adler32(item.key, strlen(item.key)), item.key);
}
}
} else if (0 == strcmp(command, "source")) {
execute_file(tgt, params, 0);
} else if (0 == strcmp(command, "dbg:counters")) {
#ifdef NO_COUNTERS
send_txt(tgt, "ERR: counters are disabled at compile-time with NO_COUNTERS feature flag.");
#else
send_txt(tgt, ".pool_read = %ld\n", global_counters.pool_read);
send_txt(tgt, ".pool_write = %ld\n", global_counters.pool_write);
send_txt(tgt, ".hash_misses_read = %ld\n", global_counters.hash_misses_read);
send_txt(tgt, ".hash_misses_write = %ld\n", global_counters.hash_misses_write);
send_txt(tgt, ".pool.use = %d\n", sounds_pool.use);
send_txt(tgt, ".pool.cap = %d\n", sounds_pool.cap);
#endif
} else if (0 == strcmp(command, "")) {
} else {
send_txt(tgt, "ERR: unknown command: %s\n", command);
} // commands
}
static char buffer_exec_file[BUFFER_SIZE];
bool execute_file(struct msg_target tgt, const char *path, int depth) {
send_txt(tgt, "DBG: soucing file %s at depth %d\n", path, depth);
char *newline;
FILE *fp = fopen(path, "rt");
if (!fp) {
send_txt(tgt, "ERR: failed to open file: %d %s\n", errno, strerror(errno));
return false;
}
for (int lineno = 1; fgets(buffer_exec_file, BUFFER_SIZE, fp); lineno++) {
if ((newline = strchr(buffer_exec_file, '\n')) != NULL) {
*newline = '\0';
}
if (buffer_exec_file[0] == '#') {
send_txt(tgt, "DBG: comment: %s\n", buffer_exec_file);
continue;
}
char *cmd = buffer_exec_file;
char *args = strchr(buffer_exec_file, ' ');
if (args != NULL) {
*args = '\0';
args++;
}
send_txt(tgt, "DBG: running: %s %s\n", cmd, args);
if (0 == strcmp(cmd, "source")) {
if (depth >= MAX_SOURCE_DEPTH) {
send_txt(tgt, "ERR: recursion error: %d deep in %s:%d\n", depth, path, lineno);
fclose(fp);
return false;
}
execute_file(tgt, args, depth + 1);
} else {
execute_command(tgt, cmd, args);
}
}
fclose(fp);
return true;
}
void sfx_pool_clear() {
for (int i = 0; i < sounds_pool.cap; i++) {
if (sounds_pool.sounds[i].key[0]) {
for (int j = 0; j < MAX_SOUNDS_PER_KEY; j++) {
ma_sound_uninit(&sounds_pool.sounds[i].sounds[j]);
}
}
free(sounds_pool.sounds[i].sounds);
}
sounds_pool.use = 0;
}
void sfx_pool_grow(int size) {
if (size <= sounds_pool.cap) return;
// TODO: move to realloc?
struct sfx_pool_item *new_items = calloc(size, sizeof(struct sfx_pool_item));
EXPECT(new_items == NULL, == false);
int used = 0;
for (int i = 0; i < sounds_pool.cap; i++) {
if (sounds_pool.sounds[i].key[0] == '\0') continue;
uint32_t new_hash = adler32(sounds_pool.sounds[i].key, strlen(sounds_pool.sounds[i].key));
for (int offset = 0; offset < size; offset++) {
int index = (new_hash + offset) % size;
#ifndef NO_COUNTERS
global_counters.pool_write++;
if (offset != 0) {
global_counters.hash_misses_write++;
}
#endif
if (new_items[index].key[0] == '\0') {
memcpy(&new_items[index], &sounds_pool.sounds[i], sizeof(struct sfx_pool_item));
used++;
break;
}
}
}
free(sounds_pool.sounds);
sounds_pool.use = used;
sounds_pool.cap = size;
sounds_pool.sounds = new_items;
}
struct sfx_pool_item *sfx_pool_lookup(const char *key) {
if (key == NULL) return NULL;
uint32_t hash = adler32(key, strlen(key));
for (int offset = 0; offset < sounds_pool.cap; offset++) {
int index = (hash + offset) % sounds_pool.cap;
if (0 == strncmp(key, sounds_pool.sounds[index].key, KEY_LENGTH)) {
#ifndef NO_COUNTERS
global_counters.pool_read++;
if (offset != 0) {
global_counters.hash_misses_read++;
}
#endif
return &sounds_pool.sounds[index];
}
}
return NULL;
}
struct sfx_pool_item *sfx_pool_find_place_for(const char *key) {
uint32_t hash = adler32(key, strlen(key));
for (int offset = 0; offset < sounds_pool.cap; offset++) {
int index = (hash + offset) % sounds_pool.cap;
if (sounds_pool.sounds[index].key[0] == '\0' || 0 == strncmp(sounds_pool.sounds[index].key, key, KEY_LENGTH)) {
#ifndef NO_COUNTERS
global_counters.pool_write++;
if (offset != 0) {
global_counters.hash_misses_write++;
}
#endif
return &sounds_pool.sounds[index];
}
}
return NULL;
}
struct sfx_pool_item *sfx_pool_load(const char *key, const char *path) {
if (key == NULL || path == NULL) return NULL;
struct sfx_pool_item *sound = sfx_pool_find_place_for(key);
SOFT_EXPECT(sound == NULL, == false, NULL);
ma_result res;
ma_sound *sounds = calloc(MAX_SOUNDS_PER_KEY, sizeof(ma_sound));
sound->sounds = sounds;
SOFT_EXPECT(res = ma_sound_init_from_file(&audio.engine, path, 0, 0, 0, &sound->sounds[0]), == MA_SUCCESS, NULL);
bool keep_parameters = false;
if ((keep_parameters = (0 == strncmp(sound->key, key, KEY_LENGTH)))) {
for (int i = 0; i < MAX_SOUNDS_PER_KEY; i++) {
ma_sound_uninit(&sound->sounds[i]);
}
}
for (int i = 1; i < MAX_SOUNDS_PER_KEY; i++) {
SOFT_EXPECT(res = ma_sound_init_copy(&audio.engine, &sound->sounds[0], 0, 0, &sound->sounds[i]), == MA_SUCCESS, NULL);
}
if (!keep_parameters) {
sound->last_index = 0;
sound->pitch_max = 1.0;
sound->pitch_min = 1.0;
sound->volume = 1.0;
}
strncpy(sound->key, key, KEY_LENGTH);
strncpy(sound->path, path, PATH_MAX);
sounds_pool.use++;
return sound;
}