forked from hkc/sfxd
1
0
Fork 0

Compare commits

..

No commits in common. "cached-sorting" and "master" have entirely different histories.

7 changed files with 47 additions and 530 deletions

View File

@ -1,5 +1,5 @@
CFLAGS += -Wall -Wextra -Werror CFLAGS +=
LDFLAGS := -lm -ldl -lpthread LDFLAGS := -lm
.PHONY: all .PHONY: all
all: sfxd sfxc all: sfxd sfxc
@ -14,4 +14,4 @@ sfxc: src/sfxc.c
clean: clean:
$(RM) sfxd sfxc miniaudio.o $(RM) sfxd sfxc

View File

@ -1,11 +0,0 @@
[Unit]
Description=SoundFX Daemon
After=network.target sound.target
[Service]
Type=simple
ExecStartPre=/sbin/rm -fv /tmp/sfxd-socket
ExecStart=${HOME}/.local/bin/sfxd -C ${HOME}/.config/sfxd
[Install]
WantedBy=default.target

View File

@ -1,39 +0,0 @@
#!/bin/bash
# XXX: CHANGEME
KEYBOARD_ID="AT Translated Set 2 keyboard"
b_isColon=false
xinput test "$KEYBOARD_ID" | while read -r line; do
if [[ "$line" =~ "key press" ]]; then
keycode="$(echo "$line" | awk '{print$3}')"
case "$keycode" in
36) # enter
b_isColon=false;
echo play typewriter/pc_on;; # OneShot:SE/pc_on.wav
22) # backspace
b_isColon=false;
echo play typewriter/bwomp;;
111 | 113 | 114 |116) # arrows
b_isColon=false;
echo play typewriter/arrow;; # OneShot:SE/menu_cursor.wav
112 | 117) # page up / page down
b_isColon=false;
echo play typewriter/page;; # OneShot:SE/page.wav
47) # ":"
b_isColon=true;
echo play typewriter/text;; # OneShot:SE/text.wav
12) # "3"
if [ "$b_isColon" = "true" ]; then
echo play typewriter/meow; # Minecraft:entity.cat.meow
else
echo play typewriter/text; # OneShot:SE/text.wav
fi;
;;
*)
b_isColon=false;
echo play typewriter/text;; # OneShot:SE/text.wav
esac;
fi;
done | sfxc

93
sfxd.5
View File

@ -1,93 +0,0 @@
.\" Generated by scdoc 1.11.3
.\" Complete documentation for this program is not available as a GNU info page
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.nh
.ad l
.\" Begin generated content:
.TH "sfxd" "5" "2024-04-30"
.PP
.SH NAME
.PP
sfxd - configuration file and client commands list
.PP
.SH DESCRIPTION
.PP
Both configuration file and sfxc(1) commands use the same set of instructions.\&
.PP
Configuration file is \fBnot\fR loaded automatically, unless specified with `-C [path]` flag, but it can be loaded at a run-time via \fBsource\fR command.\&
.PP
.SH COMMANDS LIST
.PP
\fBload NAME FILE\fR Loads specified sound file and stores it under given key.\&
.br
When given key is already in use, it SHOULD be replaced with the new sound file.\&
.br
\fB\fRNAME\fB\fR is a string with no spaces, does not support escaping and used as a primary key for accessing loaded sound later.\&
.br
\fB\fRFILE\fB\fR is an absolute path to a sound file to be loaded.\& Only WAV and MP3 files are supported.\&
.PP
\fBplay NAME\fR Plays the sound with default pitch and volume.\&
.br
\fB\fRNAME\fB\fR is a key, used when \fBload\fRing a file.\&
.PP
\fBplay:rs NAME\fR Plays the sound with randomized pitch.\&
.br
Pitch range is specified with \fBset:pitch\fR command.\&
.br
\fB\fRNAME\fB\fR is a key, used when \fBload\fRing a file.\&
.PP
\fBset:pitch NAME MIN MAX\fR Sets the pitch range for \fBplay:rs\fR
.br
\fB\fRNAME\fB\fR is a key, used when \fBload\fRing a file.\&
.br
\fB\fRMIN\fB\fR is the minimum pitch.\& Can'\&t be less than 0.\&
.br
\fB\fRMAX\fB\fR is the maximum pitch.\& Can'\&t be less than 0.\&
.br
NOTE: if MAX is smaller than MIN, they are swapped.\&
.PP
\fBset:volume NAME VOLUME\fR Changes loudness for \fBplay\fR and \fBplay:rs\fR commands.\&
.br
\fB\fRNAME\fB\fR is a key, used when \fBload\fRing a file.\&
.br
\fB\fRVOLUME\fB\fR is the volume to be set.\& Can'\&t be outside of 0.\&.\&1 range
.PP
\fBsource PATH\fR Loads instructions from the configuration file.\&
.br
\fB\fRPATH\fB\fR is the file path for the configuration file.\&
.br
NOTE: there is a limit on how deep inclusion can go.\& By default it'\&s set to 32.\&
.PP
\fBfind QUERY\fR Search for loaded sounds by a substring.\&
.br
\fB\fRQUERY\fB\fR is a substring to search for.\&
.PP
\fBlist [QUERY]\fR Prints out all loaded sounds that match an optional query.\&
.br
\fB\fRQUERY\fB\fR is a fnmatch-compatible pattern.\&
.PP
\fBls [QUERY]\fR - an alias for \fB\fRlist\fB\fR
.PP
.SH DEBUG COMMANDS
.PP
\fBdbg:dump\fR Dumps contents of the hashmap.\&
.br
Prints each position of allocated hashmap and corresponding contents.\&
.br
Empty areas are shown as `(unused)`, while used ones contain volume, pitch range, calculated hash, key and file path.\&
.PP
\fBdbg:counters\fR Shows internal counters.\& Can be disabled at the compile-time.\&
.br
- `.\&pool_read` - number of reads
.br
- `.\&pool_write` - number of writes
.br
- `.\&hash_misses_read` - number of missed reads due to collision
.br
- `.\&hash_misses_write` - number of collisions in the map
.br
- `.\&pool.\&use` - number of used cells
.br
- `.\&pool.\&cap` - hashmap capacity
.PP

View File

@ -1,62 +0,0 @@
sfxd(5)
# NAME
sfxd - configuration file and client commands list
# DESCRIPTION
Both configuration file and sfxc(1) commands use the same set of instructions.
Configuration file is *not* loaded automatically, unless specified with `-C [path]` flag, but it can be loaded at a run-time via *source* command.
# COMMANDS LIST
*load NAME FILE* Loads specified sound file and stores it under given key. ++
When given key is already in use, it SHOULD be replaced with the new sound file. ++
**NAME** is a string with no spaces, does not support escaping and used as a primary key for accessing loaded sound later. ++
**FILE** is an absolute path to a sound file to be loaded. Only WAV and MP3 files are supported.
*play NAME* Plays the sound with default pitch and volume. ++
**NAME** is a key, used when *load*ing a file.
*play:rs NAME* Plays the sound with randomized pitch. ++
Pitch range is specified with *set:pitch* command. ++
**NAME** is a key, used when *load*ing a file.
*set:pitch NAME MIN MAX* Sets the pitch range for *play:rs* ++
**NAME** is a key, used when *load*ing a file. ++
**MIN** is the minimum pitch. Can't be less than 0. ++
**MAX** is the maximum pitch. Can't be less than 0. ++
NOTE: if MAX is smaller than MIN, they are swapped.
*set:volume NAME VOLUME* Changes loudness for *play* and *play:rs* commands. ++
**NAME** is a key, used when *load*ing a file. ++
**VOLUME** is the volume to be set. Can't be outside of 0..1 range
*source PATH* Loads instructions from the configuration file. ++
**PATH** is the file path for the configuration file. ++
NOTE: there is a limit on how deep inclusion can go. By default it's set to 32.
*find QUERY* Search for loaded sounds by a substring. ++
**QUERY** is a substring to search for.
*list [QUERY]* Prints out all loaded sounds that match an optional query. ++
**QUERY** is a fnmatch-compatible pattern.
*ls [QUERY]* - an alias for **list**
# DEBUG COMMANDS
*dbg:dump* Dumps contents of the hashmap. ++
Prints each position of allocated hashmap and corresponding contents. ++
Empty areas are shown as `(unused)`, while used ones contain volume, pitch range, calculated hash, key and file path.
*dbg:counters* Shows internal counters. Can be disabled at the compile-time. ++
- `.pool_read` - number of reads ++
- `.pool_write` - number of writes ++
- `.hash_misses_read` - number of missed reads due to collision ++
- `.hash_misses_write` - number of collisions in the map ++
- `.pool.use` - number of used cells ++
- `.pool.cap` - hashmap capacity

View File

@ -1,36 +1,18 @@
#include "common.h" #include "common.h"
#include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h> #include <unistd.h>
#include <sys/select.h>
struct sockaddr_un sa_server, sa_client;
void cleanup(void) {
fprintf(stderr, "[client:dbg] Removing receiving socket.\n");
if (sa_client.sun_path[0] != '\0') {
unlink(sa_client.sun_path);
}
}
void on_sigint(int sig) {
(void)sig;
cleanup();
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
struct sockaddr_un sa_server, sa_client;
int sock_fd; int sock_fd;
static char buffer[BUFFER_SIZE]; static char buffer[BUFFER_SIZE];
EXPECT(sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0), > 0); EXPECT(sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0), > 0);
signal(SIGINT, on_sigint);
atexit(cleanup);
memset(&sa_server, 0, sizeof(sa_server)); memset(&sa_server, 0, sizeof(sa_server));
memset(&sa_client, 0, sizeof(sa_client)); memset(&sa_client, 0, sizeof(sa_client));
@ -40,52 +22,21 @@ int main(int argc, char **argv) {
EXPECT(bind(sock_fd, (struct sockaddr *)&sa_client, sizeof(struct sockaddr_un)), == 0); EXPECT(bind(sock_fd, (struct sockaddr *)&sa_client, sizeof(struct sockaddr_un)), == 0);
if (argc == 1) { for (int i = 1; i < argc; i++) {
fprintf(stderr, "[client:inf] You're in the interactive mode. Press Ctrl+C or Ctrl+D to exit.\n"); strncat(buffer, argv[i], BUFFER_SIZE - strlen(buffer) - 1);
if (i != argc - 1) {
struct timeval timeout; strncat(buffer, " ", BUFFER_SIZE - strlen(buffer) - 1);
fd_set fds;
int retval, data_len;
while (1) {
timeout.tv_sec = 1;
timeout.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
FD_SET(sock_fd, &fds);
EXPECT(retval = select(sock_fd + 1, &fds, 0, 0, &timeout), != -1);
if (retval > 0) {
if (FD_ISSET(STDIN_FILENO, &fds)) {
EXPECT(data_len = read(STDIN_FILENO, buffer, BUFFER_SIZE), != -1);
if (data_len == 0) {
break;
}
buffer[data_len - 1] = '\0';
EXPECT(sendto(sock_fd, buffer, strlen(buffer), 0, (struct sockaddr *)&sa_server, sizeof(sa_server)), == (ssize_t)strlen(buffer));
}
if (FD_ISSET(sock_fd, &fds)) {
while ((data_len = recv(sock_fd, buffer, BUFFER_SIZE, 0)) > 0) {
printf("%.*s", data_len, buffer);
}
}
}
}
} else {
for (int i = 1; i < argc; i++) {
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)), == (ssize_t)strlen(buffer));
int data_len;
while ((data_len = recv(sock_fd, buffer, BUFFER_SIZE, 0)) > 0) {
printf("%.*s", data_len, buffer);
} }
} }
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) {
printf("%.*s", data_len, buffer);
}
unlink(sa_client.sun_path);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }

View File

@ -1,7 +1,5 @@
#include "common.h" #include "common.h"
#include "miniaudio.h" #include "miniaudio.h"
#include <errno.h>
#include <fnmatch.h>
#include <getopt.h> #include <getopt.h>
#include <limits.h> #include <limits.h>
#include <stdarg.h> #include <stdarg.h>
@ -11,10 +9,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h> #include <unistd.h>
#include <wordexp.h>
#define KEY_LENGTH 256 #define KEY_LENGTH 256
#define MAX_SOUNDS_PER_KEY 32 #define MAX_SOUNDS_PER_KEY 32
@ -46,20 +42,14 @@ struct sfx_pool {
float volume; float volume;
float pitch_min, pitch_max; float pitch_min, pitch_max;
int bst_parent, bst_left, bst_right;
bool inside_bst;
} *sounds; } *sounds;
int cap, use; int cap, use;
int bst_root;
}; };
ssize_t send_data(struct msg_target tgt, const void *data, ssize_t len); 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, ...); ssize_t send_txt(struct msg_target tgt, const char *fmt, ...);
void execute_command(struct msg_target tgt, const char *command, const char *params); 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 execute_file(struct msg_target tgt, const char *path, int depth);
void send_help(struct msg_target tgt);
bool ipc_init(const char *sock_path); bool ipc_init(const char *sock_path);
void ipc_loop(void); void ipc_loop(void);
@ -74,99 +64,19 @@ struct sfx_pool_item *sfx_pool_load(const char *key, const char *path);
struct ipc_data ipc = { 0 }; struct ipc_data ipc = { 0 };
struct audio_data audio = { 0 }; struct audio_data audio = { 0 };
struct sfx_pool sounds_pool = { 0, 0, 0, .bst_root = -1 }; struct sfx_pool sounds_pool = { 0, 0, 0 };
#ifndef NO_COUNTERS #ifndef NO_COUNTERS
struct global_counters { struct global_counters {
uint64_t pool_read; uint64_t pool_read;
uint64_t pool_write; uint64_t pool_write;
uint64_t hash_misses_read;
uint64_t hash_misses_write; uint64_t hash_misses_write;
uint64_t hash_misses_read_acc; uint64_t hash_misses_read;
uint64_t hash_misses_write_acc;
} global_counters = { 0 }; } global_counters = { 0 };
#endif #endif
void sfx_pool_bst_insert(struct sfx_pool_item* lst, int *root_idx_ptr, int new_idx) {
// TODO balanced
struct sfx_pool_item *new_item = lst + new_idx;
int *new_idx_ptr = root_idx_ptr;
int parent_idx = -1;
int depth = 0;
int next_parent_idx;
while ((next_parent_idx = *new_idx_ptr) >= 0) {
depth++;
parent_idx = next_parent_idx;
struct sfx_pool_item *parent_item = lst + parent_idx;
if (strcmp(new_item->key, parent_item->key) < 0) {
new_idx_ptr = &parent_item->bst_left;
} else {
new_idx_ptr = &parent_item->bst_right;
}
}
*new_idx_ptr = new_idx;
new_item->bst_parent = parent_idx;
new_item->bst_left = -1;
new_item->bst_right = -1;
new_item->inside_bst = true;
send_txt((struct msg_target){ stdout, 0, 0, 0 }, "INF: Sorting element at index %d with key '%s' under the element at index %d with depth of %d\n", new_idx, lst[new_idx].key, parent_idx, depth);
}
// We don't need deletion support
void sfx_pool_bst_insert_all(struct sfx_pool_item *lst, int *root_idx_ptr, int size) {
for (int i = 0; i < size; i++) {
if (!lst[i].inside_bst && lst[i].key[0] != '\0') {
sfx_pool_bst_insert(lst, root_idx_ptr, i);
}
}
}
int sfx_pool_bst_leftmost(const struct sfx_pool_item *lst, int parent_idx) {
if (parent_idx < 0) {
return parent_idx;
}
int next_idx;
while ((next_idx = lst[parent_idx].bst_left) >= 0) {
parent_idx = next_idx;
}
return parent_idx;
}
int sfx_pool_bst_go_right(const struct sfx_pool_item *lst, int cur_idx) {
if (cur_idx < 0) {
return cur_idx;
}
const struct sfx_pool_item *cur_item = lst + cur_idx;
if (cur_item->bst_right >= 0) {
return sfx_pool_bst_leftmost(lst, cur_item->bst_right);
}
while (true) {
int parent_idx = cur_item->bst_parent;
if (parent_idx < 0) {
// We are a root => end iterator
return parent_idx;
}
if (lst[parent_idx].bst_left == cur_idx) {
// We are a left child
return parent_idx;
}
// We are a right child
cur_idx = parent_idx;
cur_item = lst + cur_idx;
}
}
void usage(int argc, char **argv) { void usage(int argc, char **argv) {
// TODO // TODO
(void)argc;
(void)argv;
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
@ -193,13 +103,6 @@ int main(int argc, char **argv) {
} }
} }
{
struct stat statbuf;
int status = stat(sock_path, &statbuf);
if (status == 0 && !S_ISSOCK(statbuf.st_mode)) {
PANIC("Specified sock_path is not a socket");
}
}
if (!ipc_init(sock_path)) { if (!ipc_init(sock_path)) {
unlink(sock_path); unlink(sock_path);
@ -228,17 +131,16 @@ void ipc_loop(void) {
socklen_t sl_client = sizeof(ipc.sa_client); 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) { 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'; buffer_ipc_loop[data_len] = '\0';
struct sockaddr *sa_client = (struct sockaddr *)&ipc.sa_client; struct sockaddr_un *sau_client = &ipc.sa_client;
char *command = buffer_ipc_loop; char *command = buffer_ipc_loop;
char *params = strchr(command, ' '); char *params = strchr(command, ' ');
if (params != NULL) { if (params != NULL) {
*params = '\0'; *params = '\0';
params++; params++;
} else {
params = "";
} }
execute_command((struct msg_target) { 0, ipc.sock_fd, sa_client, sl_client }, command, 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, "", 0, 0, sa_client, sl_client), == 0,); 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); sl_client = sizeof(ipc.sa_client);
} }
} }
@ -249,7 +151,7 @@ bool audio_init(void) {
return true; return true;
} }
ssize_t send_data(struct msg_target tgt, const void *data, ssize_t len) { ssize_t send_data(struct msg_target tgt, const void *data, size_t len) {
ssize_t written; ssize_t written;
if (tgt.file_handle) { if (tgt.file_handle) {
if ((written = fwrite(data, 1, len, tgt.file_handle)) != len) { if ((written = fwrite(data, 1, len, tgt.file_handle)) != len) {
@ -277,19 +179,10 @@ ssize_t send_txt(struct msg_target tgt, const char *fmt, ...) {
void execute_command(struct msg_target tgt, const char *command, const char *params) { void execute_command(struct msg_target tgt, const char *command, const char *params) {
send_txt(tgt, "INF: Received '%s' with '%s'\n", command, params); send_txt(tgt, "INF: Received '%s' with '%s'\n", command, params);
send_txt((struct msg_target){ stdout, 0, 0, 0 }, "INF: Received '%s' with '%s'\n", command, params);
if (0 == strcmp(command, "load")) { if (0 == strcmp(command, "load")) {
if (strlen(params) == 0) {
send_txt(tgt, "ERR: argument required: KEY\n");
return;
}
const char *key = params; const char *key = params;
char *path = strchr(params, ' '); char *path = strchr(params, ' ');
if (path == NULL) {
send_txt(tgt, "ERR: argument required: PATH\n");
return;
}
*path = '\0'; *path = '\0';
path++; path++;
@ -309,48 +202,32 @@ void execute_command(struct msg_target tgt, const char *command, const char *par
send_txt(tgt, "OK: Loaded as %08x\n", adler32(key, strlen(key))); send_txt(tgt, "OK: Loaded as %08x\n", adler32(key, strlen(key)));
} }
} else if (0 == strcmp(command, "play")) { } else if (0 == strcmp(command, "play")) {
if (strlen(params) == 0) {
send_txt(tgt, "ERR: argument required: KEY\n");
return;
}
struct sfx_pool_item *sound = sfx_pool_lookup(params); struct sfx_pool_item *sound = sfx_pool_lookup(params);
if (!sound) { if (!sound) {
send_txt(tgt, "ERR: No such sound: '%s'\n", params); send_txt(tgt, "No such sound: '%s'\n", params);
return; return;
} }
sound->last_index = (sound->last_index + 1) % MAX_SOUNDS_PER_KEY; ma_sound *sfx = &sound->sounds[(sound->last_index++) % MAX_SOUNDS_PER_KEY];
ma_sound *sfx = &sound->sounds[sound->last_index];
ma_sound_set_pitch(sfx, sound->pitch_min); ma_sound_set_pitch(sfx, sound->pitch_min);
ma_sound_set_volume(sfx, sound->volume); ma_sound_set_volume(sfx, sound->volume);
ma_sound_start(sfx); ma_sound_start(sfx);
} else if (0 == strcmp(command, "play:rs")) { } else if (0 == strcmp(command, "play:rs") || 0 == strcmp(command, "play_rs")) {
if (strlen(params) == 0) {
send_txt(tgt, "ERR: argument required: KEY\n");
return;
}
struct sfx_pool_item *sound = sfx_pool_lookup(params); struct sfx_pool_item *sound = sfx_pool_lookup(params);
if (!sound) { if (!sound) {
send_txt(tgt, "ERR: No such sound: '%s'\n", params); send_txt(tgt, "ERR: No such sound: '%s'\n", params);
return; return;
} }
sound->last_index = (sound->last_index + 1) % MAX_SOUNDS_PER_KEY; ma_sound *sfx = &sound->sounds[(sound->last_index++) % MAX_SOUNDS_PER_KEY];
ma_sound *sfx = &sound->sounds[sound->last_index];
float pitch = sound->pitch_min + (rand() / (float)RAND_MAX) * (sound->pitch_max - sound->pitch_min); float pitch = sound->pitch_min + (rand() / (float)RAND_MAX) * (sound->pitch_max - sound->pitch_min);
ma_sound_set_pitch(sfx, pitch); ma_sound_set_pitch(sfx, pitch);
ma_sound_set_volume(sfx, sound->volume); ma_sound_set_volume(sfx, sound->volume);
ma_sound_start(sfx); ma_sound_start(sfx);
} else if (0 == strcmp(command, "set:pitch")) { } else if (0 == strcmp(command, "set:pitch") || 0 == strcmp(command, "setpitchrange")) {
float min, max; float min, max;
char *key; char *key;
int retval = sscanf(params, "%ms %f %f", &key, &min, &max); sscanf(params, "%ms %f %f", &key, &min, &max);
if (retval != 3) {
send_txt(tgt, "ERR: not enough arguments: KEY MIN MAX needed\n");
return;
}
struct sfx_pool_item *sound = sfx_pool_lookup(key); struct sfx_pool_item *sound = sfx_pool_lookup(key);
if (!sound) { if (!sound) {
@ -373,14 +250,10 @@ void execute_command(struct msg_target tgt, const char *command, const char *par
sound->pitch_min = min; sound->pitch_min = min;
sound->pitch_max = max; sound->pitch_max = max;
} else if (0 == strcmp(command, "set:volume")) { } else if (0 == strcmp(command, "set:volume") || 0 == strcmp(command, "volume")) {
float vol; float vol;
char *key; char *key;
int retval = sscanf(params, "%ms %f", &key, &vol); sscanf(params, "%ms %f", &key, &vol);
if (retval != 2) {
send_txt(tgt, "ERR: not enough arguments: KEY VOLUME\n");
return;
}
struct sfx_pool_item *sound = sfx_pool_lookup(key); struct sfx_pool_item *sound = sfx_pool_lookup(key);
if (!sound) { if (!sound) {
@ -389,67 +262,22 @@ void execute_command(struct msg_target tgt, const char *command, const char *par
} }
if (vol < 0 || vol > 1) { if (vol < 0 || vol > 1) {
send_txt(tgt, "ERR: Volume out of range: 0 <= %f <= 1\n", vol); send_txt(tgt, "ERR: Volume out of range: 0 <= %f < 1\n", vol);
} }
sound->volume = vol; sound->volume = vol;
} else if (0 == strcmp(command, "find")) { } else if (0 == strcmp(command, "dump")) {
if (strlen(params) == 0) {
send_txt(tgt, "ERR: argument required: QUERY\n");
return;
}
for (int i = 0; i < sounds_pool.cap; i++) {
struct sfx_pool_item item = sounds_pool.sounds[i];
if (item.key[0] != '\0' && strcasestr(item.key, params)) {
send_txt(tgt, "%s\n", item.key);
}
}
} else if (0 == strcmp(command, "list") || 0 == strcmp(command, "ls")) {
sfx_pool_bst_insert_all(sounds_pool.sounds, &sounds_pool.bst_root, sounds_pool.cap);
for (int i = sfx_pool_bst_leftmost(sounds_pool.sounds, sounds_pool.bst_root); i >= 0; i = sfx_pool_bst_go_right(sounds_pool.sounds, i)) {
struct sfx_pool_item *item = &sounds_pool.sounds[i];
if (item->key[0] == '\0') continue;
if (0 == fnmatch(params, item->key, 0) || strlen(params) == 0) {
send_txt(tgt, "%s\n", item->key);
}
}
} else if (0 == strcmp(command, "source")) {
if (strlen(params) == 0) {
send_txt(tgt, "ERR: argument required: PATH\n");
return;
}
wordexp_t p;
int retval = wordexp(params, &p, 0);
#define _WORDEXP_ERROR(ERR_CODE) case ERR_CODE: send_txt(tgt, "ERR: wordexp: " #ERR_CODE); return;
switch (retval) {
case 0: break;
_WORDEXP_ERROR(WRDE_BADCHAR);
_WORDEXP_ERROR(WRDE_BADVAL);
_WORDEXP_ERROR(WRDE_CMDSUB);
_WORDEXP_ERROR(WRDE_NOSPACE);
_WORDEXP_ERROR(WRDE_SYNTAX);
default: send_txt(tgt, "ERR: wordexp: Unknown error: %d\n", retval); return;
}
#undef _WORDEXP_ERROR
for (size_t i = 0; i < p.we_wordc; i++) {
execute_file(tgt, p.we_wordv[i], 0);
}
wordfree(&p);
} else if (0 == strcmp(command, "help")) {
send_help(tgt);
} else if (0 == strcmp(command, "meow")) {
send_txt(tgt, "mrr~\n");
} else if (0 == strcmp(command, "dbg:dump")) {
for (int i = 0; i < sounds_pool.cap; i++) { for (int i = 0; i < sounds_pool.cap; i++) {
struct sfx_pool_item item = sounds_pool.sounds[i]; struct sfx_pool_item item = sounds_pool.sounds[i];
if (item.key[0] == '\0') { if (item.key[0] == '\0') {
send_txt(tgt, "%3d (unused)\n", i); send_txt(tgt, "%3d (unused)\n", i);
} else { } else {
send_txt(tgt, "%3d vol=%7.5f pitch=%7.5f..%7.5f (0x%08x) \"%s\" from %s\n", 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, adler32(item.key, strlen(item.key)), item.key, item.path); 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")) { } else if (0 == strcmp(command, "dbg:counters")) {
#ifdef NO_COUNTERS #ifdef NO_COUNTERS
send_txt(tgt, "ERR: counters are disabled at compile-time with NO_COUNTERS feature flag."); send_txt(tgt, "ERR: counters are disabled at compile-time with NO_COUNTERS feature flag.");
@ -458,48 +286,15 @@ void execute_command(struct msg_target tgt, const char *command, const char *par
send_txt(tgt, ".pool_write = %ld\n", global_counters.pool_write); 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_read = %ld\n", global_counters.hash_misses_read);
send_txt(tgt, ".hash_misses_write = %ld\n", global_counters.hash_misses_write); send_txt(tgt, ".hash_misses_write = %ld\n", global_counters.hash_misses_write);
send_txt(tgt, ".hash_misses_read_acc = %ld\n", global_counters.hash_misses_read_acc);
send_txt(tgt, ".hash_misses_write_acc = %ld\n", global_counters.hash_misses_write_acc);
send_txt(tgt, ".pool.use = %d\n", sounds_pool.use); send_txt(tgt, ".pool.use = %d\n", sounds_pool.use);
send_txt(tgt, ".pool.cap = %d\n", sounds_pool.cap); send_txt(tgt, ".pool.cap = %d\n", sounds_pool.cap);
#endif #endif
} else if (0 == strcmp(command, "dbg:map")) {
int n_hits = 0, n_misses = 0;
for (int i = 0; i < sounds_pool.cap; i++) {
struct sfx_pool_item *item = &sounds_pool.sounds[i];
if ((i % 16) == 0) {
send_txt(tgt, "# ");
}
if (item->key[0] == '\0') {
send_txt(tgt, "-- ");
} else {
uint32_t hash = adler32(item->key, strlen(item->key));
int index = hash % sounds_pool.cap;
send_txt(tgt, "%02d ", i - index);
}
if ((i % 16) == 15) {
send_txt(tgt, "\n");
}
}
send_txt(tgt, ".used = %ld\n.size = %ld\n", sounds_pool.use, sounds_pool.cap);
send_txt(tgt, ".hits = %d\n.misses = %d\n", n_hits, n_misses);
} else if (0 == strcmp(command, "")) { } else if (0 == strcmp(command, "")) {
} else { } else {
send_txt(tgt, "ERR: unknown command: %s\n", command); send_txt(tgt, "ERR: unknown command: %s\n", command);
} // commands } // commands
} }
void send_help(struct msg_target tgt) {
send_txt(tgt, "load NAME PATH load PATH sound as NAME\n");
send_txt(tgt, "play NAME play previously loaded NAME\n");
send_txt(tgt, "play:rs NAME play _ _ _ with random pitch\n");
send_txt(tgt, "set:pitch NAME MIN MAX set pitch range for NAME\n");
send_txt(tgt, "set:volume NAME VOLUME set loudness for NAME\n");
send_txt(tgt, "source PATH load instructions from PATH\n");
send_txt(tgt, "find QUERY search for keys by QUERY\n");
send_txt(tgt, "list [QUERY] basically ls\n");
}
static char buffer_exec_file[BUFFER_SIZE]; static char buffer_exec_file[BUFFER_SIZE];
bool execute_file(struct msg_target tgt, const char *path, int depth) { 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); send_txt(tgt, "DBG: soucing file %s at depth %d\n", path, depth);
@ -534,24 +329,7 @@ bool execute_file(struct msg_target tgt, const char *path, int depth) {
fclose(fp); fclose(fp);
return false; return false;
} }
wordexp_t p; execute_file(tgt, args, depth + 1);
int retval = wordexp(args, &p, 0);
#define _WORDEXP_ERROR(ERR_CODE) case ERR_CODE: send_txt(tgt, "ERR: wordexp: " #ERR_CODE); return false;
switch (retval) {
case 0: break;
_WORDEXP_ERROR(WRDE_BADCHAR);
_WORDEXP_ERROR(WRDE_BADVAL);
_WORDEXP_ERROR(WRDE_CMDSUB);
_WORDEXP_ERROR(WRDE_NOSPACE);
_WORDEXP_ERROR(WRDE_SYNTAX);
default: send_txt(tgt, "ERR: wordexp: Unknown error: %d\n", retval); return false;
}
#undef _WORDEXP_ERROR
for (size_t i = 0; i < p.we_wordc; i++) {
send_txt(tgt, "DBG: executing file %s\n", p.we_wordv[i]);
execute_file(tgt, p.we_wordv[i], depth + 1);
}
wordfree(&p);
} else { } else {
execute_command(tgt, cmd, args); execute_command(tgt, cmd, args);
} }
@ -583,16 +361,15 @@ void sfx_pool_grow(int size) {
for (int i = 0; i < sounds_pool.cap; i++) { for (int i = 0; i < sounds_pool.cap; i++) {
if (sounds_pool.sounds[i].key[0] == '\0') continue; 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)); uint32_t new_hash = adler32(sounds_pool.sounds[i].key, strlen(sounds_pool.sounds[i].key));
global_counters.pool_write++;
for (int offset = 0; offset < size; offset++) { for (int offset = 0; offset < size; offset++) {
int index = (new_hash + offset) % size; int index = (new_hash + offset) % size;
if (new_items[index].key[0] == '\0') {
#ifndef NO_COUNTERS #ifndef NO_COUNTERS
global_counters.hash_misses_write_acc++; global_counters.pool_write++;
if (offset != 0) { if (offset != 0) {
global_counters.hash_misses_write++; global_counters.hash_misses_write++;
} }
#endif #endif
if (new_items[index].key[0] == '\0') {
memcpy(&new_items[index], &sounds_pool.sounds[i], sizeof(struct sfx_pool_item)); memcpy(&new_items[index], &sounds_pool.sounds[i], sizeof(struct sfx_pool_item));
used++; used++;
break; break;
@ -608,14 +385,12 @@ void sfx_pool_grow(int size) {
struct sfx_pool_item *sfx_pool_lookup(const char *key) { struct sfx_pool_item *sfx_pool_lookup(const char *key) {
if (key == NULL) return NULL; if (key == NULL) return NULL;
if (strlen(key) == 0) return NULL;
uint32_t hash = adler32(key, strlen(key)); uint32_t hash = adler32(key, strlen(key));
global_counters.pool_read++;
for (int offset = 0; offset < sounds_pool.cap; offset++) { for (int offset = 0; offset < sounds_pool.cap; offset++) {
int index = (hash + offset) % sounds_pool.cap; int index = (hash + offset) % sounds_pool.cap;
if (0 == strncmp(key, sounds_pool.sounds[index].key, KEY_LENGTH)) { if (0 == strncmp(key, sounds_pool.sounds[index].key, KEY_LENGTH)) {
#ifndef NO_COUNTERS #ifndef NO_COUNTERS
global_counters.hash_misses_read_acc++; global_counters.pool_read++;
if (offset != 0) { if (offset != 0) {
global_counters.hash_misses_read++; global_counters.hash_misses_read++;
} }
@ -627,15 +402,12 @@ 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_find_place_for(const char *key) {
if (key == NULL) return NULL;
if (strlen(key) == 0) return NULL;
uint32_t hash = adler32(key, strlen(key)); uint32_t hash = adler32(key, strlen(key));
global_counters.pool_write++;
for (int offset = 0; offset < sounds_pool.cap; offset++) { for (int offset = 0; offset < sounds_pool.cap; offset++) {
int index = (hash + offset) % sounds_pool.cap; 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)) { if (sounds_pool.sounds[index].key[0] == '\0' || 0 == strncmp(sounds_pool.sounds[index].key, key, KEY_LENGTH)) {
#ifndef NO_COUNTERS #ifndef NO_COUNTERS
global_counters.hash_misses_write_acc++; global_counters.pool_write++;
if (offset != 0) { if (offset != 0) {
global_counters.hash_misses_write++; global_counters.hash_misses_write++;
} }
@ -648,7 +420,6 @@ 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 sfx_pool_item *sfx_pool_load(const char *key, const char *path) {
if (key == NULL || path == NULL) return NULL; if (key == NULL || path == NULL) return NULL;
if (strlen(key) == 0 || strlen(path) == 0) return NULL;
struct sfx_pool_item *sound = sfx_pool_find_place_for(key); struct sfx_pool_item *sound = sfx_pool_find_place_for(key);
SOFT_EXPECT(sound == NULL, == false, NULL); SOFT_EXPECT(sound == NULL, == false, NULL);