diff --git a/.gitignore b/.gitignore index 6947b74..357996f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ sfxd sfxc +miniaudio.o diff --git a/Makefile b/Makefile index 72e4926..1a5ea4b 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,12 @@ LDFLAGS := -lm .PHONY: all all: sfxd sfxc -sfxd: - $(CC) $(CFLAGS) src/sfxd.c $(LDFLAGS) -o sfxd -sfxc: +miniaudio.o: src/miniaudio.c + $(CC) $(CFLAGS) -c src/miniaudio.c -o miniaudio.o + +sfxd: src/sfxd.c miniaudio.o + $(CC) $(CFLAGS) src/sfxd.c miniaudio.o $(LDFLAGS) -o sfxd +sfxc: src/sfxc.c $(CC) $(CFLAGS) src/sfxc.c $(LDFLAGS) -o sfxc diff --git a/src/common.h b/src/common.h index 6984f23..b4404a1 100644 --- a/src/common.h +++ b/src/common.h @@ -19,6 +19,25 @@ if (!(__res CONDITION)) \ PANIC_FMT("%s failed condition: %zd %s", #CODE, __res, #CONDITION); \ } +#define SOFT_PANIC_FMT(FMT, ...) _soft_panic(__LINE__, __FILE__, errno, FMT, __VA_ARGS__) +#define SOFT_PANIC(TXT) _soft_panic(__LINE__, __FILE__, errno, TXT) +#define SOFT_EXPECT(CODE, CONDITION, FAILSAFE) { \ + ssize_t __res = (CODE); \ + if (!(__res CONDITION)) { \ + SOFT_PANIC_FMT("%s failed condition: %zd %s", #CODE, __res, #CONDITION); \ + return FAILSAFE; \ + } \ +} + +static inline void _soft_panic(int line, const char *filename, int _errno, const char *fmt, ...) { + fprintf(stderr, "Soft panic at %s:%d\nMessage: ", filename, line); + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "\nerrno: %d (%s)\n", _errno, strerror(_errno)); +} static inline void _panic(int line, const char *filename, int _errno, const char *fmt, ...) { fprintf(stderr, "Panic at %s:%d\nMessage: ", filename, line); @@ -28,7 +47,6 @@ static inline void _panic(int line, const char *filename, int _errno, const char va_end(args); fprintf(stderr, "\nerrno: %d (%s)\n", _errno, strerror(_errno)); - exit(EXIT_FAILURE); } diff --git a/src/miniaudio.c b/src/miniaudio.c new file mode 100644 index 0000000..afcddad --- /dev/null +++ b/src/miniaudio.c @@ -0,0 +1,2 @@ +#define MINIAUDIO_IMPLEMENTATION +#include "miniaudio.h" diff --git a/src/sfxc.c b/src/sfxc.c index c37a0d8..6a6c1c9 100644 --- a/src/sfxc.c +++ b/src/sfxc.c @@ -23,12 +23,14 @@ int main(int argc, char **argv) { EXPECT(bind(sock_fd, (struct sockaddr *)&sa_client, sizeof(struct sockaddr_un)), == 0); for (int i = 1; i < argc; i++) { - strncat(buffer, argv[1], BUFFER_SIZE - strlen(buffer) - 1); + strncat(buffer, argv[i], BUFFER_SIZE - strlen(buffer) - 1); if (i != argc - 1) { strncat(buffer, " ", BUFFER_SIZE - strlen(buffer) - 1); } } + printf("send: '%s'\n", buffer); + EXPECT(sendto(sock_fd, buffer, strlen(buffer), 0, (struct sockaddr *)&sa_server, sizeof(sa_server)), == strlen(buffer)); int data_len; while ((data_len = recv(sock_fd, buffer, BUFFER_SIZE, 0)) > 0) { diff --git a/src/sfxd.c b/src/sfxd.c index dead19a..a5bc844 100644 --- a/src/sfxd.c +++ b/src/sfxd.c @@ -1,11 +1,17 @@ #include "common.h" +#include "miniaudio.h" +#include #include #include +#include #include #include #include #include +#define KEY_LENGTH 256 +#define MAX_SOUNDS_PER_KEY 32 + struct msg_target { FILE *file_handle; int sock_fd; @@ -13,58 +19,111 @@ struct msg_target { ssize_t sock_addr_size; }; -ssize_t send_data(struct msg_target tgt, const void *data, size_t len); -void execute_command(struct msg_target tgt, const char *command, const char *params); - -int main(int argc, char **argv) { - +struct ipc_data { struct sockaddr_un sa_server, sa_client; int sock_fd; - static char buffer[BUFFER_SIZE]; +}; + +struct audio_data { + ma_engine engine; +}; + +struct sfx_pool { + struct sfx_pool_item { + char key[KEY_LENGTH]; + ma_sound sounds[MAX_SOUNDS_PER_KEY]; + 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 ipc_init(const char *sock_path); +void ipc_loop(); + +bool audio_init(); + +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 }; + +int main(int argc, char **argv) { + sfx_pool_grow(32); + printf("audio_init()\n"); + if (!audio_init()) { + return EXIT_FAILURE; + } char *sock_path = DAEMON_SOCKET_PATH; + if (!ipc_init(sock_path)) { + unlink(sock_path); + return EXIT_FAILURE; + } - EXPECT(sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0), > 0); + ipc_loop(); + unlink(sock_path); +} - memset(&sa_server, 0, sizeof(sa_server)); - memset(&sa_client, 0, sizeof(sa_client)); +bool ipc_init(const char *sock_path) { + SOFT_EXPECT(ipc.sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0), > 0, false); - sa_server.sun_family = AF_UNIX; - strncpy(sa_server.sun_path, sock_path, sizeof(sa_server.sun_path) - 1); - - unlink(sa_server.sun_path); - EXPECT(bind(sock_fd, (struct sockaddr *)&sa_server, sizeof(struct sockaddr_un)), >= 0); + 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; +} +void ipc_loop() { + static char buffer[BUFFER_SIZE]; ssize_t data_len; - socklen_t sl_client = sizeof(sa_client); - while ((data_len = recvfrom(sock_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&sa_client, &sl_client)) != -1) { + socklen_t sl_client = sizeof(ipc.sa_client); + while ((data_len = recvfrom(ipc.sock_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&ipc.sa_client, &sl_client)) != -1) { buffer[data_len] = '\0'; - struct sockaddr_un *sau_client = &sa_client; + struct sockaddr_un *sau_client = &ipc.sa_client; char *command = buffer; - char *params = strchr(buffer, ' '); + char *params = strchr(command, ' '); if (params != NULL) { *params = '\0'; params++; } - execute_command((struct msg_target) { 0, sock_fd, (struct sockaddr *)&sa_client, sl_client }, command, params); - EXPECT(sendto(sock_fd, "OK\n", 4, 0, (struct sockaddr *)&sa_client, sl_client), == 4); - EXPECT(sendto(sock_fd, "", 0, 0, (struct sockaddr *)&sa_client, sl_client), == 0); - sl_client = sizeof(sa_client); + 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); } +} - unlink(sa_server.sun_path); +bool audio_init() { + 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) { - PANIC_FMT("fwrite(ptr=%p, size=%zd, nmemb=%zd, stream=%p) -> %zd", + 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) { - PANIC_FMT("sendto(socket=%d, message=%p, length=%zd, flags=%d, dest_addr=%p, dest_len=%zd) -> %zd", + 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); } } @@ -72,6 +131,136 @@ ssize_t send_data(struct msg_target tgt, const void *data, size_t len) { return written; } -void execute_command(struct msg_target tgt, const char *command, const char *params) { - send_data(tgt, "WAAAAAAAAAAAAAA\n", 16); +ssize_t send_txt(struct msg_target tgt, const char *fmt, ...) { + static char txtbuf[BUFFER_SIZE]; + 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, "Received '%s' with '%s'\n", command, params); + + if (0 == strcmp(command, "load")) { + const char *key = params; + char *path = strchr(params, ' '); + *path = '\0'; + path++; + struct sfx_pool_item *sound = sfx_pool_load(key, path); + if (sound == NULL) { + send_txt(tgt, "Load failed\n"); + } else { + send_txt(tgt, "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); + } +} + + +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]); + } + } + } +} + +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; + 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)) { + 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)) { + 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; + + if (sounds_pool.use / (float)sounds_pool.cap >= 0.75) { + sfx_pool_grow(sounds_pool.cap + 32); + } + + struct sfx_pool_item *sound = sfx_pool_find_place_for(key); + SOFT_EXPECT(sound == NULL, == false, NULL); + + ma_result res; + 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); + + return sound; }