summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/Makefile2
-rw-r--r--examples/spatial.c166
-rw-r--r--include/xas/spatial.h49
-rw-r--r--src/Makefile4
-rw-r--r--src/spatial.c223
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);
}