#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; 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]; 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; } 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; } void ipc_loop() { static char buffer[BUFFER_SIZE]; ssize_t data_len; 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 = &ipc.sa_client; char *command = buffer; 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() { 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; } 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; }