#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(xas_spatial_coord a, xas_spatial_coord b) { return a.x * b.x + a.y * b.y + a.z * b.z; } static void diff(xas_spatial_coord *ret, xas_spatial_coord a, xas_spatial_coord b) { ret->x = a.x - b.x; ret->y = a.y - b.y; ret->z = a.z - b.z; } static float magnf(xas_spatial_coord point) { return sqrtf(point.x * point.x + point.y * point.y + point.z * point.z); } static inline float degf(float rad) { float ret = (rad / M_PI) * 180.0f; while (ret >= 360.0f) { ret -= 360.0f; } return ret; } static void rotate(xas_spatial_coord rotation, xas_spatial_coord *point) { double cosY = cos(rotation.y), sinY = sin(rotation.y), cosZ = cos(rotation.z), sinZ = sin(rotation.z), cosX = cos(rotation.x), sinX = sin(rotation.x); double Rx, Ry, Py, Pz, Yx, Yz; /* * Adjust vertex for Z axis rotation */ Rx = point->x * cosZ - point->y * sinZ; Ry = point->y * cosZ + point->x * sinZ; /* * Adjust vertex for X axis rotation */ Py = Ry * cosX - point->z * sinX; Pz = point->z * cosX - Ry * sinX; /* * Adjust vertex for Y axis rotation */ Yx = Rx * cosY - Pz * sinY; Yz = Pz * cosY - Rx * sinY; point->x = Yx; point->y = Py; point->z = Yz; } static int within_cone(xas_spatial_coord point, xas_spatial_cone cone) { xas_spatial_coord apex_to_point_vect, axis_vect, rotated = cone.apex, opposite = { .x = cone.rotation.x * -2.0f, .y = cone.rotation.y * -2.0f, .z = cone.rotation.z * -2.0f }; rotate(opposite, &rotated); diff(&apex_to_point_vect, cone.apex, point); diff(&axis_vect, cone.apex, rotated); return dotf(apex_to_point_vect, axis_vect) / magnf(apex_to_point_vect) / magnf(axis_vect) <= cosf(cone.angle / 2.0f); } 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); xas_spatial_cone cone_l = { .apex = scene->speaker_l, .rotation = { 0.0f, M_PI * 1.5f, 0.0f }, .angle = M_PI / 4.0f }, cone_r = { .apex = scene->speaker_r, .rotation = { 0.0f, M_PI * 0.5f, 0.0f }, .angle = M_PI / 4.0f }; 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; if (!within_cone(obj->point, cone_l)) { value_l /= 3.0f; } if (!within_cone(obj->point, cone_r)) { value_r /= 3.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 point, xas_spatial_coord rotation, float width) { scene->observer.point = point; scene->observer.rotation = rotation; scene->observer.width = width; } static void object_position(xas_spatial_scene *scene, xas_spatial_object *object, xas_spatial_coord point) { size_t delta_l_old = object->delta_l, delta_r_old = object->delta_r; object->point = point; object->distance_l = dist(scene->speaker_l, point); object->distance_r = dist(scene->speaker_r, point); 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->point); 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 point, xas_audio_stream *source, void *ctx) { 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->point = point; object->source = source; object->ctx = ctx; object->next = NULL; object->delta_l = 0; object->delta_r = 0; object_position(scene, object, point); 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_point(xas_spatial_object *object, xas_spatial_coord *point) { point->x = object->point.x; point->y = object->point.y; point->z = object->point.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); }