#include #include #include #include #include #include static inline float dist(xas_spatial_coord a, xas_spatial_coord b) { return powf(powf(b.x - a.x, 2.0f) + powf(b.y - a.y, 2.0f) + powf(b.z - a.z, 2.0f), 0.5f); } static float dotf(float *sets, size_t count, size_t len) { float ret = 0.0f; size_t x, y; if (count == 0 || len == 0) { return 0.0f; } for (x=0; xx * cosZ - coord->y * sinZ; Ry = coord->y * cosZ + coord->x * sinZ; /* * Adjust vertex for X axis rotation */ Py = Ry * cosX - coord->z * sinX; Pz = coord->z * cosX - Ry * sinX; /* * Adjust vertex for Y axis rotation */ Yx = Rx * cosY - Pz * sinY; Yz = Pz * cosY - Rx * sinY; coord->x = Yx; coord->y = Py; coord->z = Yz; } static inline float degf(float rad) { float ret = (rad / M_PI) * 180.0f; while (ret >= 360.0f) { ret -= 360.0f; } return ret; } static int within_cone(xas_spatial_coord coord, xas_spatial_cone cone) { xas_spatial_rotation rotation = { .pitch = -cone.rotation.x / 2.0f, .roll = -cone.rotation.y / 2.0f, .yaw = -cone.rotation.z / 2.0f }; float radius; coord.x -= cone.coord.x; coord.y -= cone.coord.y; coord.z -= cone.coord.z; rotate(rotation, &coord); cone.coord.x = 0; cone.coord.y = 0; cone.coord.z = 0; /* * If the point is to the left of the cone point, then the point is not * within the cone. */ if (coord.x < 0) { return 0; } radius = tanf(cone.angle / 2.0f) * coord.x; /* * If the given point is outside the cone radius at its X coordinate, then * it is outside of the cone. */ if (fabs(coord.y) < radius || fabs(coord.z) < radius) { return 0; } return 1; } 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 = sample_rate * ceilf(seconds), total = sizeof(xas_spatial_buffer) + stride * count; if ((buffer = realloc(buffer, total)) == NULL) { goto error_realloc_buffer; } scene->buffer = buffer; scene->buffer->index = 0; scene->buffer->size = count; memset(buffer + 1, '\0', stride * count); return 0; error_realloc_buffer: return -1; } static inline size_t sample_delta(xas_spatial_scene *scene, float distance) { return floorf((distance / scene->speed) * scene->format.sample_rate); } static inline int16_t sample_scale(int16_t value, float distance) { return (int16_t)roundf((float)value * (1.0f / distance)); } 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[XAS_AUDIO_STEREO*i] = 0; dest[XAS_AUDIO_STEREO*i+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, o; if (count > buffer->size) { count = buffer->size; } for (o=0; osize) { i = 0; } dest[XAS_AUDIO_STEREO*o] = src[XAS_AUDIO_STEREO*i]; dest[XAS_AUDIO_STEREO*o+1] = src[XAS_AUDIO_STEREO*i+1]; i++; } } 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; ssize_t readlen, i; 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; } for (i=0; idistance_l), value_r = sample_scale(src[i], obj->distance_r); size_t index_l, index_r; xas_spatial_cone cone_l = { .coord = scene->speaker_l, .rotation = { .x = 1.75f * M_PI, .y = 0.0f, .z = 0.0f }, .angle = M_PI / 4.0f }, cone_r = { .coord = scene->speaker_r, .rotation = { .x = 0.25f * M_PI, .y = 0.0f, .z = 0.0f }, .angle = M_PI / 4.0f }; if (!within_cone(obj->coord, cone_l)) { value_l /= 2.0f; } if (!within_cone(obj->coord, cone_r)) { value_r /= 2.0f; } if (buffer->index == buffer->size) { buffer->index = 0; } index_l = index_r = buffer->index; if (obj->shift_l < 0) { obj->shift_l++; } else { if (obj->shift_l > 0) { index_l += obj->delta_l; obj->shift_l--; } dest[XAS_AUDIO_STEREO*index_l] += value_l; } if (obj->shift_r < 0) { obj->shift_r++; } else { if (obj->shift_r > 0) { index_r += obj->delta_r; obj->shift_r--; } dest[XAS_AUDIO_STEREO*index_r+1] += value_r; } buffer->index++; } buffer->index = index_old; obj = obj->next; } buffer_copy(buffer, output, count); buffer->index = index_old + count; buffer->index %= buffer->size; return count; error_audio_stream_read: return -1; } xas_spatial_scene *xas_spatial_scene_new(xas_audio_format format, xas_spatial_coord speaker_l, xas_spatial_coord speaker_r) { xas_spatial_scene *scene; if ((scene = malloc(sizeof(*scene))) == NULL) { goto error_malloc_scene; } memset(scene, '\0', sizeof(*scene)); scene->format = format; scene->speaker_l = speaker_l; scene->speaker_r = speaker_r; scene->radius = XAS_SPATIAL_DEFAULT_RADIUS; scene->speed = XAS_SPATIAL_DEFAULT_SPEED; if (buffer_realloc(scene, NULL) < 0) { goto error_buffer_realloc; } return scene; error_buffer_realloc: free(scene); error_malloc_scene: return NULL; } void xas_spatial_scene_destroy(xas_spatial_scene *scene) { xas_spatial_object *object = scene->first; while (object) { xas_spatial_object *next = object->next; free(object); object = next; } free(scene->buffer); free(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; } static void object_position(xas_spatial_scene *scene, xas_spatial_object *object, xas_spatial_coord coord) { size_t delta_l_old = object->delta_l, delta_r_old = object->delta_r; object->coord = coord; object->distance_l = dist(scene->speaker_l, coord); object->distance_r = dist(scene->speaker_r, coord); object->delta_l = sample_delta(scene, object->distance_l); object->delta_r = sample_delta(scene, object->distance_r); object->shift_l = object->delta_l - delta_l_old; object->shift_r = object->delta_r - delta_r_old; } void xas_spatial_scene_set_speaker_coords(xas_spatial_scene *scene, xas_spatial_coord speaker_l, xas_spatial_coord speaker_r) { xas_spatial_object *obj = scene->first; scene->speaker_l = speaker_l; scene->speaker_r = speaker_r; while (obj) { xas_spatial_object *next = obj->next; object_position(scene, obj, obj->coord); obj = next; } } 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 (source->format.channels != XAS_AUDIO_MONO) { errno = EINVAL; goto error_invalid_source; } if ((object = malloc(sizeof(*object))) == NULL) { goto error_malloc_object; } object->coord = coord; object->source = source; object->next = NULL; object->delta_l = 0; object->delta_r = 0; object_position(scene, object, coord); if (scene->first == NULL) { scene->first = object; } if (scene->last) { scene->last->next = object; } scene->last = object; return object; error_malloc_object: error_invalid_source: 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; } 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); }