diff options
-rw-r--r-- | examples/Makefile | 2 | ||||
-rw-r--r-- | examples/spatial.c | 166 | ||||
-rw-r--r-- | include/xas/spatial.h | 49 | ||||
-rw-r--r-- | src/Makefile | 4 | ||||
-rw-r--r-- | src/spatial.c | 223 |
5 files changed, 411 insertions, 33 deletions
diff --git a/examples/Makefile b/examples/Makefile index 537d103..5604297 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -7,7 +7,7 @@ INCLUDE_PATH = ../include CFLAGS += -I$(INCLUDE_PATH) LDFLAGS += -L../src -lxas -lm -EXAMPLES = test open say +EXAMPLES = test open say spatial all: $(EXAMPLES) diff --git a/examples/spatial.c b/examples/spatial.c new file mode 100644 index 0000000..9985ffe --- /dev/null +++ b/examples/spatial.c @@ -0,0 +1,166 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <sysexits.h> +#include <fcntl.h> +#include <math.h> + +#include <xas/spatial.h> +#include <xas/synth.h> +#include <xas/audio.h> +#include <xas/riff.h> + +#define SYNTH_STATUS_CLEAR 0 +#define SYNTH_STATUS_ON (1 << 0) + +typedef struct _synth_sine { + int flags; + float phase; + + size_t frequency; +} synth_sine; + +static void usage(int argc, char **argv, const char *message, ...) { + va_list args; + + va_start(args, message); + + if (message) { + vfprintf(stderr, message, args); + fputc('\n', stderr); + } + + va_end(args); + + fprintf(stderr, "usage: %s output.wav\n", argv[0]); + + exit(EX_USAGE); +} + +static int16_t sine_sample(xas_synth *synth, synth_sine *sine) { + int16_t ret; + static float tau = 2.0f * M_PI; + + if (sine->flags & SYNTH_STATUS_ON) { + ret = (int16_t)roundf((INT16_MAX >> 2) * sinf(sine->phase)); + + sine->phase += tau / (synth->format.sample_rate / sine->frequency); + + if (sine->phase > tau) { + sine->phase -= tau; + } + } else { + ret = 0; + } + + return ret; +} + +static void sine_cleanup(xas_synth *synth, synth_sine *sine) { + return; +} + +int main(int argc, char **argv) { + xas_spatial_scene *scene; + + xas_audio_stream *synth, + *output, + *wave; + + synth_sine sine_channels[2] = { + { SYNTH_STATUS_ON, 0.0f, 220 }, + { SYNTH_STATUS_ON, 0.0f, 420 }, + }; + + xas_audio_format format = { + .channels = XAS_AUDIO_STEREO, + .sample_size = XAS_AUDIO_PCM_16_BIT, + .sample_rate = 44100 + }; + + size_t buffer_size = 4096, + duration_s = 60, + i; + + xas_spatial_coord speakers[2] = { + { -0.09, 0.0, 0.0 }, + { 0.09, 0.0, 0.0 } + }; + + if (argc != 2) { + usage(argc, argv, "No output file provided"); + } + + if ((wave = xas_riff_new_file(argv[1], + format, + O_WRONLY | O_CREAT | O_TRUNC)) == NULL) { + goto error_riff_new_file; + } + + if ((synth = xas_synth_new((xas_synth_callback_sample)sine_sample, + (xas_synth_callback_cleanup)sine_cleanup, + format, + buffer_size, + &sine_channels[0])) == NULL) { + goto error_synth_new; + } + + if ((scene = xas_spatial_scene_new(format, + speakers[0], + speakers[1])) == NULL) { + goto error_spatial_scene_new; + } + + if ((output = xas_spatial_scene_new_stream(scene, + buffer_size)) == NULL) { + goto error_spatial_scene_new_stream; + } + + if (xas_spatial_scene_add_object(scene, + (xas_spatial_coord){ 0.0, 0.0, 20.0 }, + synth) == NULL) { + goto error_spatial_scene_add_object; + } + + for (i=0; i<duration_s; i++) { + void *buf; + ssize_t readlen; + + if ((readlen = xas_audio_stream_read(output, + &buf, + buffer_size)) < 0) { + goto error_audio_stream_read; + } + + if (xas_audio_stream_write(wave, buf, readlen) < 0) { + goto error_audio_stream_write; + } + } + + xas_audio_stream_flush(wave); + + xas_audio_stream_destroy(output); + xas_spatial_scene_destroy(scene); + xas_audio_stream_destroy(synth); + xas_audio_stream_destroy(wave); + + return EX_OK; + +error_audio_stream_read: +error_audio_stream_write: +error_spatial_scene_add_object: + xas_audio_stream_destroy(output); + +error_spatial_scene_new_stream: + xas_spatial_scene_destroy(scene); + +error_spatial_scene_new: + xas_audio_stream_destroy(synth); + +error_synth_new: + xas_audio_stream_destroy(wave); + +error_riff_new_file: + return EX_OSERR; +} diff --git a/include/xas/spatial.h b/include/xas/spatial.h index 8b1ca05..a715b65 100644 --- a/include/xas/spatial.h +++ b/include/xas/spatial.h @@ -4,14 +4,28 @@ #include <xas/audio.h> #define XAS_SPATIAL_DEFAULT_OBSERVER_WIDTH 0.18f -#define XAS_SPATIAL_DEFAULT_SPEED 343.0f /* m/s */ +#define XAS_SPATIAL_DEFAULT_RADIUS 4000.0f /* metres */ +#define XAS_SPATIAL_DEFAULT_SPEED 343.0f /* m/s */ typedef struct _xas_spatial_coord { float x, y, z; } xas_spatial_coord; typedef struct _xas_spatial_rotation { - float pitch, roll, yaw; + union { + float x; + float pitch; + }; + + union { + float y; + float roll; + }; + + union { + float z; + float yaw; + }; } xas_spatial_rotation; typedef struct _xas_spatial_observer { @@ -23,11 +37,16 @@ typedef struct _xas_spatial_observer { typedef struct _xas_spatial_object xas_spatial_object; struct _xas_spatial_object { - float x, y, z; + xas_spatial_coord coord; xas_audio_stream *source; xas_spatial_object *next; }; +typedef struct _xas_spatial_buffer { + size_t index, + size; +} xas_spatial_buffer; + typedef struct _xas_spatial_scene { xas_audio_format format; @@ -36,13 +55,13 @@ typedef struct _xas_spatial_scene { xas_spatial_coord speaker_l, speaker_r; - float speed; + float speed, + radius; xas_spatial_object *first, *last; - void *buf; - size_t buflen; + xas_spatial_buffer *buffer; } xas_spatial_scene; xas_spatial_scene *xas_spatial_scene_new(xas_audio_format format, @@ -51,18 +70,21 @@ xas_spatial_scene *xas_spatial_scene_new(xas_audio_format format, void xas_spatial_scene_destroy(xas_spatial_scene *scene); -int xas_spatial_scene_set_observer(xas_spatial_scene *scene, - xas_spatial_coord coord, - xas_spatial_rotation rotation, - float width); +void xas_spatial_scene_set_observer(xas_spatial_scene *scene, + xas_spatial_coord coord, + xas_spatial_rotation rotation, + float width); void xas_spatial_scene_set_speaker_coords(xas_spatial_scene *scene, xas_spatial_coord speaker_l, xas_spatial_coord speaker_r); -void xas_spatial_scene_set_speed(xas_spatial_scene *scene, float speed); +int xas_spatial_scene_set_speed(xas_spatial_scene *scene, float speed); + +int xas_spatial_scene_set_radius(xas_spatial_scene *scene, float radius); -xas_spatial_object *xas_spatial_scene_add_object(xas_spatial_coord coord, +xas_spatial_object *xas_spatial_scene_add_object(xas_spatial_scene *scene, + xas_spatial_coord coord, xas_audio_stream *source); void xas_spatial_object_get_coord(xas_spatial_object *object, @@ -71,6 +93,7 @@ void xas_spatial_object_get_coord(xas_spatial_object *object, void xas_spatial_object_set_coord(xas_spatial_object *object, xas_spatial_coord coord); -xas_audio_stream *xas_spatial_scene_new_stream(xas_spatial_scene *scene); +xas_audio_stream *xas_spatial_scene_new_stream(xas_spatial_scene *scene, + size_t buffer_size); #endif /* _XAS_SPATIAL_H */ diff --git a/src/Makefile b/src/Makefile index b9e9150..7c26ac9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,9 +7,9 @@ CC = $(CROSS)cc CFLAGS += -I$(INCLUDE_PATH) LDFLAGS += -HEADERS = audio.h riff.h mixer.h synth.h vox.h bank.h +HEADERS = audio.h riff.h mixer.h synth.h vox.h bank.h spatial.h -OBJS = audio.o riff.o mixer.o synth.o vox.o bank.o +OBJS = audio.o riff.o mixer.o synth.o vox.o bank.o spatial.o VERSION_MAJOR = 0 VERSION_MINOR = 0.1 diff --git a/src/spatial.c b/src/spatial.c index 809fce1..18173aa 100644 --- a/src/spatial.c +++ b/src/spatial.c @@ -10,24 +10,146 @@ static inline float dist(xas_spatial_coord a, xas_spatial_coord b) { + powf(b.z - a.z, 2.0f), 0.5f); } -static int buf_realloc(xas_spatial_scene *scene, void *buf) { - float width = scene->observer.width, - speed = scene->speed; +static int buffer_realloc(xas_spatial_scene *scene, + xas_spatial_buffer *buffer) { + float seconds = scene->radius / scene->speed; size_t sample_rate = scene->format.sample_rate, stride = scene->format.channels * scene->format.sample_size, - count = floorf(width / (speed / sample_rate)); + count = sample_rate * ceilf(seconds), + total = sizeof(xas_spatial_buffer) + stride * count; - if ((buf = realloc(buf, stride * count)) == NULL) { - goto error_realloc; + if ((buffer = realloc(buffer, total)) == NULL) { + goto error_realloc_buffer; } - scene->buf = buf; - scene->buflen = count; + memset(buffer + 1, '\0', stride * count); + + scene->buffer = buffer; + scene->buffer->index = 0; + scene->buffer->size = count; return 0; -error_realloc: +error_realloc_buffer: + return -1; +} + +static inline size_t sample_index(xas_spatial_scene *scene, float distance) { + size_t index = floorf(distance / scene->speed / scene->format.sample_rate); + + return (scene->buffer->index + index) % scene->buffer->size; +} + +static inline int16_t sample_scale(int16_t value, float distance) { + return (int16_t)roundf((float)value * (1.0f / powf(distance, 2.0))); +} + +static void buffer_zero(xas_spatial_buffer *buffer, size_t count) { + int16_t *dest = (int16_t *)buffer + 1; + size_t i = buffer->index; + + if (count > buffer->size) { + count = buffer->size; + } + + while (count--) { + if (i == buffer->size) { + i = 0; + } + + dest[i*XAS_AUDIO_STEREO] = 0; + dest[i*XAS_AUDIO_STEREO+1] = 0; + + i++; + } +} + +static void buffer_copy(xas_spatial_buffer *buffer, + int16_t *dest, + size_t count) { + int16_t *src = (int16_t *)buffer + 1; + size_t i = buffer->index; + + if (count > buffer->size) { + count = buffer->size; + } + + while (count--) { + if (i == buffer->size) { + i = 0; + } + + dest[i*XAS_AUDIO_STEREO] = src[i*XAS_AUDIO_STEREO]; + dest[i*XAS_AUDIO_STEREO+1] = src[i*XAS_AUDIO_STEREO+1]; + } +} + +ssize_t scene_fill(xas_spatial_scene *scene, + int16_t *output, + size_t count, + xas_audio_stream *source) { + xas_spatial_buffer *buffer = scene->buffer; + xas_spatial_object *obj = scene->first; + + size_t index_old = buffer->index; + + int16_t *dest = (int16_t *)(scene->buffer + 1); + + buffer_zero(buffer, count); + + while (obj) { + int16_t *src; + + float distance_l = dist(scene->speaker_l, obj->coord), + distance_r = dist(scene->speaker_r, obj->coord); + + size_t index_l, + index_r; + + ssize_t readlen, + i; + + if (distance_l > scene->radius || distance_r > scene->radius) { + continue; + } + + if ((readlen = xas_audio_stream_read(obj->source, + (void **)&src, + count)) < 0) { + goto error_audio_stream_read; + } + + if (readlen > (ssize_t)buffer->size) { + readlen = buffer->size; + } + + index_l = sample_index(scene, distance_l); + index_r = sample_index(scene, distance_r); + + for (i=0; i<readlen; i++) { + int16_t value_l = sample_scale(src[i], distance_l), + value_r = sample_scale(src[i], distance_r); + + if (buffer->index == buffer->size) { + buffer->index = 0; + } + + dest[XAS_AUDIO_STEREO*index_l+i] += value_l; + dest[XAS_AUDIO_STEREO*index_r+i+1] += value_r; + + buffer->index++; + } + + buffer->index = index_old; + obj = obj->next; + } + + buffer_copy(buffer, output, count); + + return count; + +error_audio_stream_read: return -1; } @@ -47,13 +169,13 @@ xas_spatial_scene *xas_spatial_scene_new(xas_audio_format format, scene->speaker_r = speaker_r; scene->speed = XAS_SPATIAL_DEFAULT_SPEED; - if (buf_realloc(scene, NULL) < 0) { - goto error_buf_realloc; + if (buffer_realloc(scene, NULL) < 0) { + goto error_buffer_realloc; } return scene; -error_buf_realloc: +error_buffer_realloc: free(scene); error_malloc_scene: @@ -61,19 +183,28 @@ error_malloc_scene: } void xas_spatial_scene_destroy(xas_spatial_scene *scene) { - free(scene->buf); + xas_spatial_object *object = scene->first; + + while (object) { + xas_spatial_object *next = object->next; + + free(object); + + object = next; + } + + free(scene->buffer); free(scene); } -int xas_spatial_scene_set_observer(xas_spatial_scene *scene, + +void xas_spatial_scene_set_observer(xas_spatial_scene *scene, xas_spatial_coord coord, xas_spatial_rotation rotation, float width) { scene->observer.coord = coord; scene->observer.rotation = rotation; scene->observer.width = width; - - return buf_realloc(scene, scene->buf); } void xas_spatial_scene_set_speaker_coords(xas_spatial_scene *scene, @@ -83,6 +214,64 @@ void xas_spatial_scene_set_speaker_coords(xas_spatial_scene *scene, scene->speaker_r = speaker_r; } -void xas_spatial_scene_set_speed(xas_spatial_scene *scene, float speed) { +int xas_spatial_scene_set_speed(xas_spatial_scene *scene, float speed) { scene->speed = speed; + + return buffer_realloc(scene, scene->buffer); +} + +int xas_spatial_scene_set_radius(xas_spatial_scene *scene, float radius) { + scene->radius = radius; + + return buffer_realloc(scene, scene->buffer); +} + +xas_spatial_object *xas_spatial_scene_add_object(xas_spatial_scene *scene, + xas_spatial_coord coord, + xas_audio_stream *source) { + xas_spatial_object *object; + + if ((object = malloc(sizeof(*object))) == NULL) { + goto error_malloc_object; + } + + object->coord = coord; + object->source = source; + object->next = NULL; + + if (scene->first == NULL) { + scene->first = object; + } + + if (scene->last) { + scene->last->next = object; + } + + scene->last = object; + + return object; + +error_malloc_object: + return NULL; +} + +void xas_spatial_object_get_coord(xas_spatial_object *object, + xas_spatial_coord *coord) { + coord->x = object->coord.x; + coord->y = object->coord.y; + coord->z = object->coord.z; +} + +void xas_spatial_object_set_coord(xas_spatial_object *object, + xas_spatial_coord coord) { + object->coord = coord; +} + +xas_audio_stream *xas_spatial_scene_new_stream(xas_spatial_scene *scene, + size_t buffer_size) { + return xas_audio_stream_new_source((xas_audio_fill)scene_fill, + NULL, + scene->format, + buffer_size, + scene); } |