diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
commit | 7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch) | |
tree | d483300dab478b994fe199a5d19d18d74153718a /spa/plugins/audioconvert | |
parent | Initial commit. (diff) | |
download | pipewire-0bfb2679f751193be0325ef92c84c3863d22ac84.tar.xz pipewire-0bfb2679f751193be0325ef92c84c3863d22ac84.zip |
Adding upstream version 0.3.65.upstream/0.3.65upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'spa/plugins/audioconvert')
51 files changed, 20851 insertions, 0 deletions
diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c new file mode 100644 index 0000000..2ccc2b7 --- /dev/null +++ b/spa/plugins/audioconvert/audioadapter.c @@ -0,0 +1,1733 @@ +/* SPA + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/support/plugin.h> +#include <spa/support/log.h> +#include <spa/support/cpu.h> + +#include <spa/node/node.h> +#include <spa/node/io.h> +#include <spa/node/utils.h> +#include <spa/node/keys.h> +#include <spa/utils/names.h> +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/buffer/alloc.h> +#include <spa/pod/parser.h> +#include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> +#include <spa/param/param.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/latency-utils.h> +#include <spa/debug/format.h> +#include <spa/debug/pod.h> +#include <spa/debug/log.h> + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT log_topic +static struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.audioadapter"); + +#define DEFAULT_ALIGN 16 + +#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) + +/** \cond */ + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + struct spa_cpu *cpu; + + uint32_t max_align; + enum spa_direction direction; + + struct spa_node *target; + + struct spa_node *follower; + struct spa_hook follower_listener; + uint32_t follower_flags; + struct spa_audio_info follower_current_format; + struct spa_audio_info default_format; + + struct spa_handle *hnd_convert; + struct spa_node *convert; + struct spa_hook convert_listener; + uint32_t convert_flags; + + uint32_t n_buffers; + struct spa_buffer **buffers; + + struct spa_io_buffers io_buffers; + struct spa_io_rate_match io_rate_match; + struct spa_io_position *io_position; + + uint64_t info_all; + struct spa_node_info info; +#define IDX_EnumFormat 0 +#define IDX_PropInfo 1 +#define IDX_Props 2 +#define IDX_Format 3 +#define IDX_EnumPortConfig 4 +#define IDX_PortConfig 5 +#define IDX_Latency 6 +#define IDX_ProcessLatency 7 +#define N_NODE_PARAMS 8 + struct spa_param_info params[N_NODE_PARAMS]; + uint32_t convert_params_flags[N_NODE_PARAMS]; + uint32_t follower_params_flags[N_NODE_PARAMS]; + + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + + unsigned int add_listener:1; + unsigned int have_format:1; + unsigned int started:1; + unsigned int driver:1; + unsigned int async:1; + unsigned int passthrough:1; + unsigned int follower_removing:1; +}; + +/** \endcond */ + +static int follower_enum_params(struct impl *this, + uint32_t id, + uint32_t idx, + struct spa_result_node_params *result, + const struct spa_pod *filter, + struct spa_pod_builder *builder) +{ + int res; + if (result->next < 0x100000) { + if ((res = spa_node_enum_params_sync(this->convert, + id, &result->next, filter, &result->param, builder)) == 1) + return res; + result->next = 0x100000; + } + if (result->next < 0x200000 && this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { + result->next &= 0xfffff; + if ((res = spa_node_enum_params_sync(this->follower, + id, &result->next, filter, &result->param, builder)) == 1) { + result->next |= 0x100000; + return res; + } + result->next = 0x200000; + } + return 0; +} + +static int convert_enum_port_config(struct impl *this, + int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter, struct spa_pod_builder *builder) +{ + struct spa_pod *f1, *f2 = NULL; + int res; + + f1 = spa_pod_builder_add_object(builder, + SPA_TYPE_OBJECT_ParamPortConfig, id, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction)); + + if (filter) { + if ((res = spa_pod_filter(builder, &f2, f1, filter)) < 0) + return res; + } + else { + f2 = f1; + } + return spa_node_enum_params(this->convert, seq, id, start, num, f2); +} + +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + uint8_t buffer[4096]; + struct spa_pod_dynamic_builder b; + struct spa_result_node_params result; + uint32_t count = 0; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; +next: + result.index = result.next; + + spa_log_debug(this->log, "%p: %d id:%u", this, seq, id); + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + + switch (id) { + case SPA_PARAM_EnumPortConfig: + return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); + case SPA_PARAM_PortConfig: + if (this->passthrough) { + switch (result.index) { + case 0: + result.param = spa_pod_builder_add_object(&b.b, + SPA_TYPE_OBJECT_ParamPortConfig, id, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id( + SPA_PARAM_PORT_CONFIG_MODE_passthrough)); + result.next++; + break; + default: + return 0; + } + } else { + return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); + } + break; + case SPA_PARAM_PropInfo: + res = follower_enum_params(this, + id, IDX_PropInfo, &result, filter, &b.b); + break; + case SPA_PARAM_Props: + res = follower_enum_params(this, + id, IDX_Props, &result, filter, &b.b); + break; + case SPA_PARAM_ProcessLatency: + res = follower_enum_params(this, + id, IDX_ProcessLatency, &result, filter, &b.b); + break; + case SPA_PARAM_EnumFormat: + case SPA_PARAM_Format: + case SPA_PARAM_Latency: + res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + id, &result.next, filter, &result.param, &b.b); + break; + default: + return -ENOENT; + } + + if (res == 1) { + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + count++; + } + spa_pod_dynamic_builder_clean(&b); + + if (res != 1) + return res; + + if (count != num) + goto next; + + return 0; +} + +static int link_io(struct impl *this) +{ + int res; + + if (this->convert == NULL) + return 0; + + spa_log_debug(this->log, "%p: controls", this); + + spa_zero(this->io_rate_match); + this->io_rate_match.rate = 1.0; + + if ((res = spa_node_port_set_io(this->follower, + this->direction, 0, + SPA_IO_RateMatch, + &this->io_rate_match, sizeof(this->io_rate_match))) < 0) { + spa_log_debug(this->log, "%p: set RateMatch on follower disabled %d %s", this, + res, spa_strerror(res)); + } + else if ((res = spa_node_port_set_io(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_IO_RateMatch, + &this->io_rate_match, sizeof(this->io_rate_match))) < 0) { + spa_log_warn(this->log, "%p: set RateMatch on convert failed %d %s", this, + res, spa_strerror(res)); + } + + this->io_buffers = SPA_IO_BUFFERS_INIT; + + if ((res = spa_node_port_set_io(this->follower, + this->direction, 0, + SPA_IO_Buffers, + &this->io_buffers, sizeof(this->io_buffers))) < 0) { + spa_log_warn(this->log, "%p: set Buffers on follower failed %d %s", this, + res, spa_strerror(res)); + return res; + } + else if ((res = spa_node_port_set_io(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_IO_Buffers, + &this->io_buffers, sizeof(this->io_buffers))) < 0) { + spa_log_warn(this->log, "%p: set Buffers on convert failed %d %s", this, + res, spa_strerror(res)); + return res; + } + return 0; +} + +static void emit_node_info(struct impl *this, bool full) +{ + uint32_t i; + uint64_t old = full ? this->info.change_mask : 0; + + spa_log_debug(this->log, "%p: info full:%d change:%08"PRIx64, + this, full, this->info.change_mask); + + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < this->info.n_params; i++) { + if (this->params[i].user > 0) { + this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[i].user = 0; + spa_log_debug(this->log, "param %d flags:%08x", + i, this->params[i].flags); + } + } + } + spa_node_emit_info(&this->hooks, &this->info); + this->info.change_mask = old; + } +} + +static int debug_params(struct impl *this, struct spa_node *node, + enum spa_direction direction, uint32_t port_id, uint32_t id, struct spa_pod *filter, + const char *debug, int err) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + uint32_t state; + struct spa_pod *param; + int res, count = 0; + + spa_log_error(this->log, "params %s: %d:%d (%s) %s", + spa_debug_type_find_name(spa_type_param, id), + direction, port_id, debug, err ? spa_strerror(err) : "no matching params"); + if (err == -EBUSY) + return 0; + + if (filter) { + spa_log_error(this->log, "with this filter:"); + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 2, NULL, filter); + } else { + spa_log_error(this->log, "there was no filter"); + } + + state = 0; + while (true) { + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + res = spa_node_port_enum_params_sync(node, + direction, port_id, + id, &state, + NULL, ¶m, &b); + if (res != 1) { + if (res < 0) + spa_log_error(this->log, " error: %s", spa_strerror(res)); + break; + } + spa_log_error(this->log, "unmatched %s %d:", debug, count); + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 2, NULL, param); + count++; + } + if (count == 0) + spa_log_error(this->log, "could not get any %s", debug); + + return 0; +} + +static int negotiate_buffers(struct impl *this) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint32_t state; + struct spa_pod *param; + int res; + bool follower_alloc, conv_alloc; + uint32_t i, size, buffers, blocks, align, flags, stride = 0; + uint32_t *aligns; + struct spa_data *datas; + uint32_t follower_flags, conv_flags; + + spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); + + if (this->target == this->follower) + return 0; + + if (this->n_buffers > 0) + return 0; + + state = 0; + param = NULL; + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_Buffers, &state, + param, ¶m, &b)) < 0) { + if (res == -ENOENT) + param = NULL; + else { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_Buffers, param, "follower buffers", res); + return res; + } + } + + state = 0; + if ((res = spa_node_port_enum_params_sync(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, &state, + param, ¶m, &b)) != 1) { + debug_params(this, this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, param, "convert buffers", res); + return -ENOTSUP; + } + if (param == NULL) + return -ENOTSUP; + + spa_pod_fixate(param); + + follower_flags = this->follower_flags; + conv_flags = this->convert_flags; + + follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); + conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); + + flags = 0; + if (conv_alloc || follower_alloc) { + flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; + if (conv_alloc) + follower_alloc = false; + } + + align = DEFAULT_ALIGN; + + if ((res = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamBuffers, NULL, + SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(&stride), + SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align))) < 0) + return res; + + spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", + this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); + + align = SPA_MAX(align, this->max_align); + + datas = alloca(sizeof(struct spa_data) * blocks); + memset(datas, 0, sizeof(struct spa_data) * blocks); + aligns = alloca(sizeof(uint32_t) * blocks); + for (i = 0; i < blocks; i++) { + datas[i].type = SPA_DATA_MemPtr; + datas[i].flags = SPA_DATA_FLAG_READWRITE | SPA_DATA_FLAG_DYNAMIC; + datas[i].maxsize = size; + aligns[i] = align; + } + + free(this->buffers); + this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); + if (this->buffers == NULL) + return -errno; + this->n_buffers = buffers; + + if ((res = spa_node_port_use_buffers(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + this->buffers, this->n_buffers)) < 0) + return res; + + if ((res = spa_node_port_use_buffers(this->follower, + this->direction, 0, + follower_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + this->buffers, this->n_buffers)) < 0) + return res; + + return 0; +} + +static int configure_format(struct impl *this, uint32_t flags, const struct spa_pod *format) +{ + uint8_t buffer[4096]; + int res; + + if (format == NULL && !this->have_format) + return 0; + + spa_log_debug(this->log, "%p: configure format:", this); + if (format) + spa_debug_log_format(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); + + if ((res = spa_node_port_set_param(this->follower, + this->direction, 0, + SPA_PARAM_Format, flags, + format)) < 0) + return res; + + if (res > 0) { + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint32_t state = 0; + struct spa_pod *fmt; + + /* format was changed to nearest compatible format */ + + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_Format, &state, + NULL, &fmt, &b)) != 1) + return -EIO; + + format = fmt; + } + + if (this->target != this->follower && this->convert) { + if ((res = spa_node_port_set_param(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Format, flags, + format)) < 0) + return res; + } + + this->have_format = format != NULL; + if (format == NULL) { + this->n_buffers = 0; + } else { + res = negotiate_buffers(this); + } + + return res; +} + +static int configure_convert(struct impl *this, uint32_t mode) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + spa_log_debug(this->log, "%p: configure convert %p", this, this->target); + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode)); + + return spa_node_set_param(this->convert, SPA_PARAM_PortConfig, 0, param); +} + +extern const struct spa_handle_factory spa_audioconvert_factory; + +static const struct spa_node_events follower_node_events; + +static int reconfigure_mode(struct impl *this, bool passthrough, + enum spa_direction direction, struct spa_pod *format) +{ + int res = 0; + struct spa_hook l; + + spa_log_info(this->log, "%p: passthrough mode %d", this, passthrough); + + if (this->passthrough != passthrough) { + if (passthrough) { + /* remove converter split/merge ports */ + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); + } else { + /* remove follower ports */ + this->follower_removing = true; + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + this->follower_removing = false; + } + } + + /* set new target */ + this->target = passthrough ? this->follower : this->convert; + + if ((res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format)) < 0) + return res; + + if (this->passthrough != passthrough) { + this->passthrough = passthrough; + if (passthrough) { + /* add follower ports */ + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + } else { + /* add converter ports */ + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); + link_io(this); + } + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; + this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; + this->params[IDX_Props].user++; + + emit_node_info(this, false); + + return 0; +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + int res = 0, res2 = 0; + struct impl *this = object; + struct spa_audio_info info = { 0 }; + + spa_log_debug(this->log, "%p: set param %d", this, id); + + switch (id) { + case SPA_PARAM_Format: + if (this->started) + return -EIO; + if (param == NULL) + return -EINVAL; + + if ((res = spa_format_parse(param, &info.media_type, &info.media_subtype)) < 0) + return res; + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + if (spa_format_audio_raw_parse(param, &info.info.raw) < 0) + return -EINVAL; + + this->follower_current_format = info; + break; + + case SPA_PARAM_PortConfig: + { + enum spa_direction dir; + enum spa_param_port_config_mode mode; + struct spa_pod *format = NULL; + + if (this->started) { + spa_log_error(this->log, "was started"); + return -EIO; + } + + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamPortConfig, NULL, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&dir), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) + return -EINVAL; + + if (format) { + struct spa_audio_info info; + + spa_zero(info); + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return res; + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -ENOTSUP; + + if (spa_format_audio_raw_parse(format, &info.info.raw) >= 0) { + info.info.raw.rate = 0; + this->default_format = info; + } + } + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_none: + return -ENOTSUP; + case SPA_PARAM_PORT_CONFIG_MODE_passthrough: + if ((res = reconfigure_mode(this, true, dir, format)) < 0) + return res; + break; + case SPA_PARAM_PORT_CONFIG_MODE_convert: + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + if ((res = reconfigure_mode(this, false, dir, NULL)) < 0) + return res; + break; + default: + return -EINVAL; + } + + if (this->target != this->follower) { + if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) + return res; + } + break; + } + + case SPA_PARAM_Props: + if (this->target != this->follower) + res = spa_node_set_param(this->target, id, flags, param); + res2 = spa_node_set_param(this->follower, id, flags, param); + if (res < 0 && res2 < 0) + return res; + res = 0; + break; + case SPA_PARAM_ProcessLatency: + res = spa_node_set_param(this->follower, id, flags, param); + break; + default: + res = -ENOTSUP; + break; + } + return res; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct impl *this = object; + int res = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Position: + this->io_position = data; + break; + default: + break; + } + + if (this->target) + res = spa_node_set_io(this->target, id, data, size); + + if (this->target != this->follower) + res = spa_node_set_io(this->follower, id, data, size); + + return res; +} + +static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder *b, uint32_t id, + struct spa_pod_object *o1, struct spa_pod_object *o2) +{ + const struct spa_pod_prop *p1, *p2; + struct spa_pod_frame f; + struct spa_pod_builder_state state; + int res = 0; + + if (o2 == NULL || SPA_POD_TYPE(o1) != SPA_POD_TYPE(o2)) + return (struct spa_pod*)o1; + + spa_pod_builder_push_object(b, &f, o1->body.type, o1->body.id); + p2 = NULL; + SPA_POD_OBJECT_FOREACH(o1, p1) { + p2 = spa_pod_object_find_prop(o2, p2, p1->key); + if (p2 != NULL) { + spa_pod_builder_get_state(b, &state); + res = spa_pod_filter_prop(b, p1, p2); + if (res < 0) + spa_pod_builder_reset(b, &state); + } + if (p2 == NULL || res < 0) + spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); + } + p1 = NULL; + SPA_POD_OBJECT_FOREACH(o2, p2) { + p1 = spa_pod_object_find_prop(o1, p1, p2->key); + if (p1 != NULL) + continue; + spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); + } + return spa_pod_builder_pop(b, &f); +} + +static int negotiate_format(struct impl *this) +{ + uint32_t state; + struct spa_pod *format, *def; + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + int res; + + spa_log_debug(this->log, "%p: have_format:%d", this, this->have_format); + + if (this->have_format) + return 0; + + if (this->target == this->follower) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + + spa_node_send_command(this->follower, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); + + state = 0; + format = NULL; + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_EnumFormat, &state, + format, &format, &b)) < 0) { + if (res == -ENOENT) + format = NULL; + else { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_EnumFormat, format, "follower format", res); + goto done; + } + } + if (this->convert) { + state = 0; + if ((res = spa_node_port_enum_params_sync(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, &state, + format, &format, &b)) != 1) { + debug_params(this, this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, format, "convert format", res); + res = -ENOTSUP; + goto done; + } + } + if (format == NULL) { + res = -ENOTSUP; + goto done; + } + + def = spa_format_audio_raw_build(&b, + SPA_PARAM_Format, &this->default_format.info.raw); + + format = merge_objects(this, &b, SPA_PARAM_Format, + (struct spa_pod_object*)format, + (struct spa_pod_object*)def); + + spa_pod_fixate(format); + + res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format); + +done: + spa_node_send_command(this->follower, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamEnd)); + + return res; +} + + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct impl *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_debug(this->log, "%p: command %d", this, SPA_NODE_COMMAND_ID(command)); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + spa_log_debug(this->log, "%p: starting %d", this, this->started); + if (this->started) + return 0; + if ((res = negotiate_format(this)) < 0) + return res; + if ((res = negotiate_buffers(this)) < 0) + return res; + this->started = true; + break; + case SPA_NODE_COMMAND_Suspend: + this->started = false; + spa_log_debug(this->log, "%p: suspending", this); + break; + case SPA_NODE_COMMAND_Pause: + this->started = false; + spa_log_debug(this->log, "%p: pausing", this); + break; + case SPA_NODE_COMMAND_Flush: + spa_log_debug(this->log, "%p: flushing", this); + this->io_buffers.status = SPA_STATUS_OK; + break; + default: + break; + } + + if ((res = spa_node_send_command(this->target, command)) < 0) { + spa_log_error(this->log, "%p: can't send command %d: %s", + this, SPA_NODE_COMMAND_ID(command), + spa_strerror(res)); + return res; + } + + if (this->target != this->follower) { + if ((res = spa_node_send_command(this->follower, command)) < 0) { + spa_log_error(this->log, "%p: can't send command %d: %s", + this, SPA_NODE_COMMAND_ID(command), + spa_strerror(res)); + return res; + } + } + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + spa_log_debug(this->log, "%p: started", this); + break; + case SPA_NODE_COMMAND_Suspend: + configure_format(this, 0, NULL); + spa_log_debug(this->log, "%p: suspended", this); + break; + case SPA_NODE_COMMAND_Pause: + spa_log_debug(this->log, "%p: paused", this); + break; + case SPA_NODE_COMMAND_Flush: + spa_log_debug(this->log, "%p: flushed", this); + break; + } + return res; +} + +static void convert_node_info(void *data, const struct spa_node_info *info) +{ + struct impl *this = data; + uint32_t i; + + spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, + info->change_mask); + + if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_EnumPortConfig: + idx = IDX_EnumPortConfig; + break; + case SPA_PARAM_PortConfig: + idx = IDX_PortConfig; + break; + case SPA_PARAM_PropInfo: + idx = IDX_PropInfo; + break; + case SPA_PARAM_Props: + idx = IDX_Props; + break; + default: + continue; + } + if (!this->add_listener && + this->convert_params_flags[idx] == info->params[i].flags) + continue; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->convert_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (this->add_listener) + continue; + + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + emit_node_info(this, false); +} + +static void convert_port_info(void *data, + enum spa_direction direction, uint32_t port_id, + const struct spa_port_info *info) +{ + struct impl *this = data; + + if (direction != this->direction) { + if (port_id == 0) + return; + else + port_id--; + } + + spa_log_debug(this->log, "%p: port info %d:%d", this, + direction, port_id); + + if (this->target != this->follower) + spa_node_emit_port_info(&this->hooks, direction, port_id, info); +} + +static void convert_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct impl *this = data; + + if (this->target == this->follower) + return; + + spa_log_trace(this->log, "%p: result %d %d", this, seq, res); + spa_node_emit_result(&this->hooks, seq, res, type, result); +} + +static const struct spa_node_events convert_node_events = { + SPA_VERSION_NODE_EVENTS, + .info = convert_node_info, + .port_info = convert_port_info, + .result = convert_result, +}; + +static void follower_info(void *data, const struct spa_node_info *info) +{ + struct impl *this = data; + uint32_t i; + + spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, + info->change_mask); + + if (this->follower_removing) + return; + + this->async = (info->flags & SPA_NODE_FLAG_ASYNC) != 0; + + if (info->max_input_ports > 0) + this->direction = SPA_DIRECTION_INPUT; + else + this->direction = SPA_DIRECTION_OUTPUT; + + if (this->direction == SPA_DIRECTION_INPUT) { + this->info.flags |= SPA_NODE_FLAG_IN_PORT_CONFIG; + this->info.max_input_ports = MAX_PORTS; + } else { + this->info.flags |= SPA_NODE_FLAG_OUT_PORT_CONFIG; + this->info.max_output_ports = MAX_PORTS; + } + + spa_log_debug(this->log, "%p: follower info %s", this, + this->direction == SPA_DIRECTION_INPUT ? + "Input" : "Output"); + + if (info->change_mask & SPA_NODE_CHANGE_MASK_PROPS) { + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + this->info.props = info->props; + } + if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_PropInfo: + idx = IDX_PropInfo; + break; + case SPA_PARAM_Props: + idx = IDX_Props; + break; + case SPA_PARAM_ProcessLatency: + idx = IDX_ProcessLatency; + break; + default: + continue; + } + if (!this->add_listener && + this->follower_params_flags[idx] == info->params[i].flags) + continue; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->follower_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (this->add_listener) + continue; + + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + emit_node_info(this, false); + + spa_zero(this->info.props); + this->info.change_mask &= ~SPA_NODE_CHANGE_MASK_PROPS; + +} + +static int recalc_latency(struct impl *this, enum spa_direction direction, uint32_t port_id) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + uint32_t index = 0; + struct spa_latency_info latency; + int res; + + spa_log_debug(this->log, "%p: ", this); + + if (this->target == this->follower) + return 0; + + while (true) { + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if ((res = spa_node_port_enum_params_sync(this->follower, + direction, port_id, SPA_PARAM_Latency, + &index, NULL, ¶m, &b)) != 1) + return res; + if ((res = spa_latency_parse(param, &latency)) < 0) + return res; + if (latency.direction == direction) + break; + } + if ((res = spa_node_port_set_param(this->target, + SPA_DIRECTION_REVERSE(direction), 0, + SPA_PARAM_Latency, 0, param)) < 0) + return res; + + return 0; +} + +static void follower_port_info(void *data, + enum spa_direction direction, uint32_t port_id, + const struct spa_port_info *info) +{ + struct impl *this = data; + uint32_t i; + int res; + + if (this->follower_removing) { + spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); + return; + } + + spa_log_debug(this->log, "%p: follower port info %s %p %08"PRIx64, this, + this->direction == SPA_DIRECTION_INPUT ? + "Input" : "Output", info, info->change_mask); + + if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_EnumFormat: + idx = IDX_EnumFormat; + break; + case SPA_PARAM_Format: + idx = IDX_Format; + break; + case SPA_PARAM_Latency: + idx = IDX_Latency; + break; + default: + continue; + } + + if (!this->add_listener && + this->follower_params_flags[idx] == info->params[i].flags) + continue; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->follower_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (this->add_listener) + continue; + + if (idx == IDX_Latency) { + res = recalc_latency(this, direction, port_id); + spa_log_debug(this->log, "latency: %d (%s)", res, + spa_strerror(res)); + } + if (idx == IDX_EnumFormat) { + spa_log_debug(this->log, "new formats"); + configure_format(this, 0, NULL); + } + + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + emit_node_info(this, false); + + if (this->target == this->follower) + spa_node_emit_port_info(&this->hooks, direction, port_id, info); +} + +static void follower_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct impl *this = data; + + if (this->target != this->follower) + return; + + spa_log_trace(this->log, "%p: result %d %d", this, seq, res); + spa_node_emit_result(&this->hooks, seq, res, type, result); +} + +static void follower_event(void *data, const struct spa_event *event) +{ + struct impl *this = data; + + spa_log_trace(this->log, "%p: event %d", this, SPA_EVENT_TYPE(event)); + + switch (SPA_NODE_EVENT_ID(event)) { + case SPA_NODE_EVENT_Error: + /* Forward errors */ + spa_node_emit_event(&this->hooks, event); + break; + default: + /* Ignore other events */ + break; + } +} + +static const struct spa_node_events follower_node_events = { + SPA_VERSION_NODE_EVENTS, + .info = follower_info, + .port_info = follower_port_info, + .result = follower_result, + .event = follower_event, +}; + +static int follower_ready(void *data, int status) +{ + struct impl *this = data; + + spa_log_trace_fp(this->log, "%p: ready %d", this, status); + + if (!this->started) { + spa_log_info(this->log, "%p: ready stopped node", this); + return -EIO; + } + + if (this->target != this->follower) { + this->driver = true; + + if (this->direction == SPA_DIRECTION_OUTPUT) { + int retry = 8; + while (retry--) { + status = spa_node_process(this->convert); + if (status & SPA_STATUS_HAVE_DATA) + break; + + if (status & SPA_STATUS_NEED_DATA) { + status = spa_node_process(this->follower); + if (!(status & SPA_STATUS_HAVE_DATA)) + break; + } + } + + } + } + + return spa_node_call_ready(&this->callbacks, status); +} + +static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_id) +{ + int res; + struct impl *this = data; + + if (this->target != this->follower && this->convert) + res = spa_node_port_reuse_buffer(this->convert, port_id, buffer_id); + else + res = spa_node_call_reuse_buffer(&this->callbacks, port_id, buffer_id); + + return res; +} + +static int follower_xrun(void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info) +{ + struct impl *this = data; + return spa_node_call_xrun(&this->callbacks, trigger, delay, info); +} + +static const struct spa_node_callbacks follower_node_callbacks = { + SPA_VERSION_NODE_CALLBACKS, + .ready = follower_ready, + .reuse_buffer = follower_reuse_buffer, + .xrun = follower_xrun, +}; + +static int impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct impl *this = object; + struct spa_hook l; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_trace(this->log, "%p: add listener %p", this, listener); + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + + if (events->info || events->port_info) { + this->add_listener = true; + + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + + if (this->convert) { + spa_zero(l); + spa_node_add_listener(this->convert, &l, &convert_node_events, this); + spa_hook_remove(&l); + } + this->add_listener = false; + + emit_node_info(this, true); + } + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int +impl_node_sync(void *object, int seq) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + return spa_node_sync(this->follower, seq); +} + +static int +impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, + const struct spa_dict *props) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + if (direction != this->direction) + return -EINVAL; + + return spa_node_add_port(this->target, direction, port_id, props); +} + +static int +impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + if (direction != this->direction) + return -EINVAL; + + return spa_node_remove_port(this->target, direction, port_id); +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + if (direction != this->direction) + port_id++; + + spa_log_debug(this->log, "%p: %d %u", this, seq, id); + + return spa_node_port_enum_params(this->target, seq, direction, port_id, id, + start, num, filter); +} + +static int +impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_debug(this->log, " %d %d %d %d", port_id, id, direction, this->direction); + + if (direction != this->direction) + port_id++; + + if ((res = spa_node_port_set_param(this->target, direction, port_id, id, + flags, param)) < 0) + return res; + + if ((id == SPA_PARAM_Latency) && + direction == this->direction) { + if ((res = spa_node_port_set_param(this->follower, direction, 0, id, + flags, param)) < 0) + return res; + } + + return res; +} + +static int +impl_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + void *data, size_t size) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_debug(this->log, "set io %d %d %d %d", port_id, id, direction, this->direction); + + if (direction != this->direction) + port_id++; + + return spa_node_port_set_io(this->target, direction, port_id, id, data, size); +} + +static int +impl_node_port_use_buffers(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + struct impl *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + if (direction != this->direction) + port_id++; + + spa_log_debug(this->log, "%p: %d %d:%d", this, + n_buffers, direction, port_id); + + if ((res = spa_node_port_use_buffers(this->target, + direction, port_id, flags, buffers, n_buffers)) < 0) + return res; + + return res; +} + +static int +impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + return spa_node_port_reuse_buffer(this->target, port_id, buffer_id); +} + +static int impl_node_process(void *object) +{ + struct impl *this = object; + int status = 0, fstatus, retry = 8; + + if (!this->started) { + spa_log_warn(this->log, "%p: scheduling stopped node", this); + return -EIO; + } + + spa_log_trace_fp(this->log, "%p: process convert:%p driver:%d", + this, this->convert, this->driver); + + if (this->target == this->follower) { + if (this->io_position) + this->io_rate_match.size = this->io_position->clock.duration; + return spa_node_process(this->follower); + } + + if (this->direction == SPA_DIRECTION_INPUT) { + /* an input node (sink). + * First we run the converter to process the input for the follower + * then if it produced data, we run the follower. */ + while (retry--) { + status = this->convert ? spa_node_process(this->convert) : 0; + /* schedule the follower when the converter needed + * a recycled buffer */ + if (status == -EPIPE || status == 0) + status = SPA_STATUS_HAVE_DATA; + else if (status < 0) + break; + + if (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) { + /* as long as the converter produced something or + * is drained, process the follower. */ + fstatus = spa_node_process(this->follower); + if (fstatus < 0) { + status = fstatus; + break; + } + /* if the follower doesn't need more data or is + * drained we can stop */ + if ((fstatus & SPA_STATUS_NEED_DATA) == 0 || + (fstatus & SPA_STATUS_DRAINED)) + break; + } + /* the converter needs more data */ + if ((status & SPA_STATUS_NEED_DATA)) + break; + } + } else if (!this->driver) { + bool done = false; + while (retry--) { + /* output node (source). First run the converter to make + * sure we push out any queued data. Then when it needs + * more data, schedule the follower. */ + status = this->convert ? spa_node_process(this->convert) : 0; + if (status == 0) + status = SPA_STATUS_NEED_DATA; + else if (status < 0) + break; + + done = (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)); + + /* when not async, we can return the data when we are done. + * In async mode we might first need to wake up the follower + * to asynchronously provide more data for the next round. */ + if (!this->async && done) + break; + + if (status & SPA_STATUS_NEED_DATA) { + /* the converter needs more data, schedule the + * follower */ + fstatus = spa_node_process(this->follower); + if (fstatus < 0) { + status = fstatus; + break; + } + /* if the follower didn't produce more data or is + * not drained we can stop now */ + if ((fstatus & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) == 0) + break; + } + /* converter produced something or is drained and we + * scheduled the follower above, we can stop now*/ + if (done) + break; + } + if (!done) + spa_node_call_xrun(&this->callbacks, 0, 0, NULL); + + } else { + status = spa_node_process(this->follower); + } + spa_log_trace_fp(this->log, "%p: process status:%d", this, status); + + this->driver = false; + + return status; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .sync = impl_node_sync, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .add_port = impl_node_add_port, + .remove_port = impl_node_remove_port, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .port_reuse_buffer = impl_node_port_reuse_buffer, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + this = (struct impl *) handle; + + spa_hook_remove(&this->follower_listener); + spa_node_set_callbacks(this->follower, NULL, NULL); + + spa_handle_clear(this->hnd_convert); + + if (this->buffers) + free(this->buffers); + this->buffers = NULL; + + return 0; +} + + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + size_t size; + + size = spa_handle_factory_get_size(&spa_audioconvert_factory, params); + size += sizeof(struct impl); + + return size; +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + void *iface; + const char *str; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(this->log, log_topic); + + this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); + + if (info == NULL || + (str = spa_dict_lookup(info, "audio.adapt.follower")) == NULL) + return -EINVAL; + + sscanf(str, "pointer:%p", &this->follower); + if (this->follower == NULL) + return -EINVAL; + + if (this->cpu) + this->max_align = spa_cpu_get_max_align(this->cpu); + + spa_hook_list_init(&this->hooks); + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + this->hnd_convert = SPA_PTROFF(this, sizeof(struct impl), struct spa_handle); + spa_handle_factory_init(&spa_audioconvert_factory, + this->hnd_convert, + info, support, n_support); + + spa_handle_get_interface(this->hnd_convert, SPA_TYPE_INTERFACE_Node, &iface); + this->convert = iface; + this->target = this->convert; + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.flags = SPA_NODE_FLAG_RT | + SPA_NODE_FLAG_NEED_CONFIGURE; + this->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); + this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); + this->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + this->params[IDX_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = N_NODE_PARAMS; + + spa_node_add_listener(this->follower, + &this->follower_listener, &follower_node_events, this); + spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); + + spa_node_add_listener(this->convert, + &this->convert_listener, &convert_node_events, this); + + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); + + link_io(this); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + { SPA_TYPE_INTERFACE_Node, }, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +const struct spa_handle_factory spa_audioadapter_factory = { + .version = SPA_VERSION_HANDLE_FACTORY, + .name = SPA_NAME_AUDIO_ADAPT, + .get_size = impl_get_size, + .init = impl_init, + .enum_interface_info = impl_enum_interface_info, +}; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c new file mode 100644 index 0000000..970df16 --- /dev/null +++ b/spa/plugins/audioconvert/audioconvert.c @@ -0,0 +1,2945 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> + +#include <spa/support/plugin.h> +#include <spa/support/cpu.h> +#include <spa/support/log.h> +#include <spa/utils/result.h> +#include <spa/utils/list.h> +#include <spa/utils/json.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/node/node.h> +#include <spa/node/io.h> +#include <spa/node/utils.h> +#include <spa/node/keys.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/param.h> +#include <spa/param/latency-utils.h> +#include <spa/pod/filter.h> +#include <spa/debug/types.h> + +#include "volume-ops.h" +#include "fmt-ops.h" +#include "channelmix-ops.h" +#include "resample.h" + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT log_topic +static struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.audioconvert"); + +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 + +#define MAX_ALIGN FMT_OPS_MAX_ALIGN +#define MAX_BUFFERS 32 +#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS +#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) + +#define DEFAULT_MUTE false +#define DEFAULT_VOLUME VOLUME_NORM + +struct volumes { + bool mute; + uint32_t n_volumes; + float volumes[SPA_AUDIO_MAX_CHANNELS]; +}; + +static void init_volumes(struct volumes *vol) +{ + uint32_t i; + vol->mute = DEFAULT_MUTE; + vol->n_volumes = 0; + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + vol->volumes[i] = DEFAULT_VOLUME; +} + +struct props { + float volume; + uint32_t n_channels; + uint32_t channel_map[SPA_AUDIO_MAX_CHANNELS]; + struct volumes channel; + struct volumes soft; + struct volumes monitor; + unsigned int have_soft_volume:1; + unsigned int mix_disabled:1; + unsigned int resample_disabled:1; + unsigned int resample_quality; + double rate; +}; + +static void props_reset(struct props *props) +{ + uint32_t i; + props->volume = DEFAULT_VOLUME; + props->n_channels = 0; + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + init_volumes(&props->channel); + init_volumes(&props->soft); + init_volumes(&props->monitor); + props->have_soft_volume = false; + props->mix_disabled = false; + props->resample_disabled = false; + props->resample_quality = RESAMPLE_DEFAULT_QUALITY; + props->rate = 1.0; +} + +struct buffer { + uint32_t id; +#define BUFFER_FLAG_QUEUED (1<<0) + uint32_t flags; + struct spa_list link; + struct spa_buffer *buf; + void *datas[MAX_DATAS]; +}; + +struct port { + uint32_t direction; + uint32_t id; + + struct spa_io_buffers *io; + + uint64_t info_all; + struct spa_port_info info; +#define IDX_EnumFormat 0 +#define IDX_Meta 1 +#define IDX_IO 2 +#define IDX_Format 3 +#define IDX_Buffers 4 +#define IDX_Latency 5 +#define N_PORT_PARAMS 6 + struct spa_param_info params[N_PORT_PARAMS]; + char position[16]; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + + struct spa_audio_info format; + unsigned int have_format:1; + unsigned int is_dsp:1; + unsigned int is_monitor:1; + unsigned int is_control:1; + + uint32_t blocks; + uint32_t stride; + + const struct spa_pod_sequence *ctrl; + uint32_t ctrl_offset; + + struct spa_list queue; +}; + +struct dir { + struct port *ports[MAX_PORTS]; + uint32_t n_ports; + + enum spa_direction direction; + enum spa_param_port_config_mode mode; + + struct spa_audio_info format; + unsigned int have_format:1; + unsigned int have_profile:1; + struct spa_latency_info latency; + + uint32_t remap[MAX_PORTS]; + + struct convert conv; + unsigned int need_remap:1; + unsigned int is_passthrough:1; + unsigned int control:1; +}; + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + struct spa_cpu *cpu; + + uint32_t cpu_flags; + uint32_t max_align; + uint32_t quantum_limit; + enum spa_direction direction; + + struct props props; + + struct spa_io_position *io_position; + struct spa_io_rate_match *io_rate_match; + + uint64_t info_all; + struct spa_node_info info; +#define IDX_EnumPortConfig 0 +#define IDX_PortConfig 1 +#define IDX_PropInfo 2 +#define IDX_Props 3 +#define N_NODE_PARAMS 4 + struct spa_param_info params[N_NODE_PARAMS]; + + struct spa_hook_list hooks; + + unsigned int monitor:1; + unsigned int monitor_channel_volumes:1; + + struct dir dir[2]; + struct channelmix mix; + struct resample resample; + struct volume volume; + double rate_scale; + + uint32_t in_offset; + uint32_t out_offset; + unsigned int started:1; + unsigned int setup:1; + unsigned int resample_peaks:1; + unsigned int is_passthrough:1; + unsigned int drained:1; + unsigned int rate_adjust:1; + + uint32_t empty_size; + float *empty; + float *scratch; + float *tmp[2]; + float *tmp_datas[2][MAX_PORTS]; +}; + +#define CHECK_PORT(this,d,p) ((p) < this->dir[d].n_ports) +#define GET_PORT(this,d,p) (this->dir[d].ports[p]) +#define GET_IN_PORT(this,p) GET_PORT(this,SPA_DIRECTION_INPUT,p) +#define GET_OUT_PORT(this,p) GET_PORT(this,SPA_DIRECTION_OUTPUT,p) + +#define PORT_IS_DSP(this,d,p) (GET_PORT(this,d,p)->is_dsp) +#define PORT_IS_CONTROL(this,d,p) (GET_PORT(this,d,p)->is_control) + +static void set_volume(struct impl *this); + +static void emit_node_info(struct impl *this, bool full) +{ + uint64_t old = full ? this->info.change_mask : 0; + + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { + if (p->user > 0) { + p->flags ^= SPA_PARAM_INFO_SERIAL; + p->user = 0; + } + } + } + spa_node_emit_info(&this->hooks, &this->info); + this->info.change_mask = old; + } +} + +static void emit_port_info(struct impl *this, struct port *port, bool full) +{ + uint64_t old = full ? port->info.change_mask : 0; + + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + struct spa_dict_item items[3]; + uint32_t n_items = 0; + + if (PORT_IS_DSP(this, port->direction, port->id)) { + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float mono audio"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNEL, port->position); + if (port->is_monitor) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_MONITOR, "true"); + } else if (PORT_IS_CONTROL(this, port->direction, port->id)) { + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); + } + port->info.props = &SPA_DICT_INIT(items, n_items); + + if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + SPA_FOR_EACH_ELEMENT_VAR(port->params, p) { + if (p->user > 0) { + p->flags ^= SPA_PARAM_INFO_SERIAL; + p->user = 0; + } + } + } + spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); + port->info.change_mask = old; + } +} + +static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, + uint32_t position, bool is_dsp, bool is_monitor, bool is_control) +{ + struct port *port = GET_PORT(this, direction, port_id); + const char *name; + + spa_assert(port_id < MAX_PORTS); + + if (port == NULL) { + port = calloc(1, sizeof(struct port)); + if (port == NULL) + return -errno; + this->dir[direction].ports[port_id] = port; + } + port->direction = direction; + port->id = port_id; + + name = spa_debug_type_find_short_name(spa_type_audio_channel, position); + snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK"); + + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PROPS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_NO_REF | + SPA_PORT_FLAG_DYNAMIC_DATA; + port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + port->info.params = port->params; + port->info.n_params = N_PORT_PARAMS; + + port->n_buffers = 0; + port->have_format = false; + port->is_monitor = is_monitor; + port->is_dsp = is_dsp; + if (port->is_dsp) { + port->format.media_type = SPA_MEDIA_TYPE_audio; + port->format.media_subtype = SPA_MEDIA_SUBTYPE_dsp; + port->format.info.dsp.format = SPA_AUDIO_FORMAT_DSP_F32; + port->blocks = 1; + port->stride = 4; + } + port->is_control = is_control; + if (port->is_control) { + port->format.media_type = SPA_MEDIA_TYPE_application; + port->format.media_subtype = SPA_MEDIA_SUBTYPE_control; + port->blocks = 1; + port->stride = 1; + } + spa_list_init(&port->queue); + + spa_log_info(this->log, "%p: add port %d:%d position:%s %d %d %d", + this, direction, port_id, port->position, is_dsp, is_monitor, is_control); + emit_port_info(this, port, true); + + return 0; +} + +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumPortConfig: + { + struct dir *dir; + switch (result.index) { + case 0: + dir = &this->dir[SPA_DIRECTION_INPUT];; + break; + case 1: + dir = &this->dir[SPA_DIRECTION_OUTPUT];; + break; + default: + return 0; + } + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, id, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, + SPA_PARAM_PORT_CONFIG_MODE_none, + SPA_PARAM_PORT_CONFIG_MODE_none, + SPA_PARAM_PORT_CONFIG_MODE_dsp, + SPA_PARAM_PORT_CONFIG_MODE_convert), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_CHOICE_Bool(false), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_CHOICE_Bool(false)); + break; + } + case SPA_PARAM_PortConfig: + { + struct dir *dir; + struct spa_pod_frame f[1]; + + switch (result.index) { + case 0: + dir = &this->dir[SPA_DIRECTION_INPUT];; + break; + case 1: + dir = &this->dir[SPA_DIRECTION_OUTPUT];; + break; + default: + return 0; + } + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); + spa_pod_builder_add(&b, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(this->monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(dir->control), + 0); + + if (dir->have_format) { + spa_pod_builder_prop(&b, SPA_PARAM_PORT_CONFIG_format, 0); + spa_format_audio_raw_build(&b, SPA_PARAM_PORT_CONFIG_format, + &dir->format.info.raw); + } + param = spa_pod_builder_pop(&b, &f[0]); + break; + } + case SPA_PARAM_PropInfo: + { + struct props *p = &this->props; + struct spa_pod_frame f[2]; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), + SPA_PROP_INFO_description, SPA_POD_String("Volume"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0)); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), + SPA_PROP_INFO_description, SPA_POD_String("Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->channel.mute)); + break; + case 2: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Channel Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 3: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelMap), + SPA_PROP_INFO_description, SPA_POD_String("Channel Map"), + SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_CHANNEL_UNKNOWN), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 4: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorMute), + SPA_PROP_INFO_description, SPA_POD_String("Monitor Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->monitor.mute)); + break; + case 5: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Monitor Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 6: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softMute), + SPA_PROP_INFO_description, SPA_POD_String("Soft Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->soft.mute)); + break; + case 7: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Soft Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 8: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("monitor.channel-volumes"), + SPA_PROP_INFO_description, SPA_POD_String("Monitor channel volume"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + this->monitor_channel_volumes), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 9: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Channel mixing"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mix_disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 10: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.normalize"), + SPA_PROP_INFO_description, SPA_POD_String("Normalize Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 11: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.mix-lfe"), + SPA_PROP_INFO_description, SPA_POD_String("Mix LFE into channels"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 12: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix"), + SPA_PROP_INFO_description, SPA_POD_String("Enable upmixing"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 13: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-cutoff"), + SPA_PROP_INFO_description, SPA_POD_String("LFE cutoff frequency"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.lfe_cutoff, 0.0, 1000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 14: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.fc-cutoff"), + SPA_PROP_INFO_description, SPA_POD_String("FC cutoff frequency (Hz)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.fc_cutoff, 0.0, 48000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 15: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.rear-delay"), + SPA_PROP_INFO_description, SPA_POD_String("Rear channels delay (ms)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.rear_delay, 0.0, 1000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 16: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.stereo-widen"), + SPA_PROP_INFO_description, SPA_POD_String("Stereo widen"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.widen, 0.0, 1.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 17: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"), + SPA_PROP_INFO_description, SPA_POD_String("Taps for phase shift of rear"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( + this->mix.hilbert_taps, 0, MAX_TAPS), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 18: + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); + spa_pod_builder_add(&b, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"), + SPA_PROP_INFO_description, SPA_POD_String("Upmix method to use"), + SPA_PROP_INFO_type, SPA_POD_String( + channelmix_upmix_info[this->mix.upmix].label), + SPA_PROP_INFO_params, SPA_POD_Bool(true), + 0); + + spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(&b, &f[1]); + SPA_FOR_EACH_ELEMENT_VAR(channelmix_upmix_info, i) { + spa_pod_builder_string(&b, i->label); + spa_pod_builder_string(&b, i->description); + } + spa_pod_builder_pop(&b, &f[1]); + param = spa_pod_builder_pop(&b, &f[0]); + break; + case 19: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate), + SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0)); + break; + case 20: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), + SPA_PROP_INFO_name, SPA_POD_String("resample.quality"), + SPA_PROP_INFO_description, SPA_POD_String("Resample Quality"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->resample_quality, 0, 14), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 21: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Resampling"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->resample_disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 22: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), + SPA_PROP_INFO_description, SPA_POD_String("Add noise bits"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir[1].conv.noise_bits, 0, 16), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 23: + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); + spa_pod_builder_add(&b, + SPA_PROP_INFO_name, SPA_POD_String("dither.method"), + SPA_PROP_INFO_description, SPA_POD_String("The dithering method"), + SPA_PROP_INFO_type, SPA_POD_String( + dither_method_info[this->dir[1].conv.method].label), + SPA_PROP_INFO_params, SPA_POD_Bool(true), + 0); + spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(&b, &f[1]); + SPA_FOR_EACH_ELEMENT_VAR(dither_method_info, i) { + spa_pod_builder_string(&b, i->label); + spa_pod_builder_string(&b, i->description); + } + spa_pod_builder_pop(&b, &f[1]); + param = spa_pod_builder_pop(&b, &f[0]); + break; + default: + return 0; + } + break; + } + + case SPA_PARAM_Props: + { + struct props *p = &this->props; + struct spa_pod_frame f[2]; + + switch (result.index) { + case 0: + spa_pod_builder_push_object(&b, &f[0], + SPA_TYPE_OBJECT_Props, id); + spa_pod_builder_add(&b, + SPA_PROP_volume, SPA_POD_Float(p->volume), + SPA_PROP_mute, SPA_POD_Bool(p->channel.mute), + SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->channel.n_volumes, + p->channel.volumes), + SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, + p->n_channels, + p->channel_map), + SPA_PROP_softMute, SPA_POD_Bool(p->soft.mute), + SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->soft.n_volumes, + p->soft.volumes), + SPA_PROP_monitorMute, SPA_POD_Bool(p->monitor.mute), + SPA_PROP_monitorVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->monitor.n_volumes, + p->monitor.volumes), + 0); + spa_pod_builder_prop(&b, SPA_PROP_params, 0); + spa_pod_builder_push_struct(&b, &f[1]); + spa_pod_builder_string(&b, "monitor.channel-volumes"); + spa_pod_builder_bool(&b, this->monitor_channel_volumes); + spa_pod_builder_string(&b, "channelmix.disable"); + spa_pod_builder_bool(&b, this->props.mix_disabled); + spa_pod_builder_string(&b, "channelmix.normalize"); + spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_NORMALIZE)); + spa_pod_builder_string(&b, "channelmix.mix-lfe"); + spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_MIX_LFE)); + spa_pod_builder_string(&b, "channelmix.upmix"); + spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_UPMIX)); + spa_pod_builder_string(&b, "channelmix.lfe-cutoff"); + spa_pod_builder_float(&b, this->mix.lfe_cutoff); + spa_pod_builder_string(&b, "channelmix.fc-cutoff"); + spa_pod_builder_float(&b, this->mix.fc_cutoff); + spa_pod_builder_string(&b, "channelmix.rear-delay"); + spa_pod_builder_float(&b, this->mix.rear_delay); + spa_pod_builder_string(&b, "channelmix.stereo-widen"); + spa_pod_builder_float(&b, this->mix.widen); + spa_pod_builder_string(&b, "channelmix.hilbert-taps"); + spa_pod_builder_int(&b, this->mix.hilbert_taps); + spa_pod_builder_string(&b, "channelmix.upmix-method"); + spa_pod_builder_string(&b, channelmix_upmix_info[this->mix.upmix].label); + spa_pod_builder_string(&b, "resample.quality"); + spa_pod_builder_int(&b, p->resample_quality); + spa_pod_builder_string(&b, "resample.disable"); + spa_pod_builder_bool(&b, p->resample_disabled); + spa_pod_builder_string(&b, "dither.noise"); + spa_pod_builder_int(&b, this->dir[1].conv.noise_bits); + spa_pod_builder_string(&b, "dither.method"); + spa_pod_builder_string(&b, dither_method_info[this->dir[1].conv.method].label); + spa_pod_builder_pop(&b, &f[1]); + param = spa_pod_builder_pop(&b, &f[0]); + break; + default: + return 0; + } + break; + } + default: + return 0; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); + + switch (id) { + case SPA_IO_Position: + this->io_position = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int audioconvert_set_param(struct impl *this, const char *k, const char *s) +{ + if (spa_streq(k, "monitor.channel-volumes")) + this->monitor_channel_volumes = spa_atob(s); + else if (spa_streq(k, "channelmix.disable")) + this->props.mix_disabled = spa_atob(s); + else if (spa_streq(k, "channelmix.normalize")) + SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_NORMALIZE, spa_atob(s)); + else if (spa_streq(k, "channelmix.mix-lfe")) + SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_MIX_LFE, spa_atob(s)); + else if (spa_streq(k, "channelmix.upmix")) + SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_UPMIX, spa_atob(s)); + else if (spa_streq(k, "channelmix.lfe-cutoff")) + spa_atof(s, &this->mix.lfe_cutoff); + else if (spa_streq(k, "channelmix.fc-cutoff")) + spa_atof(s, &this->mix.fc_cutoff); + else if (spa_streq(k, "channelmix.rear-delay")) + spa_atof(s, &this->mix.rear_delay); + else if (spa_streq(k, "channelmix.stereo-widen")) + spa_atof(s, &this->mix.widen); + else if (spa_streq(k, "channelmix.hilbert-taps")) + spa_atou32(s, &this->mix.hilbert_taps, 0); + else if (spa_streq(k, "channelmix.upmix-method")) + this->mix.upmix = channelmix_upmix_from_label(s); + else if (spa_streq(k, "resample.quality")) + this->props.resample_quality = atoi(s); + else if (spa_streq(k, "resample.disable")) + this->props.resample_disabled = spa_atob(s); + else if (spa_streq(k, "dither.noise")) + spa_atou32(s, &this->dir[1].conv.noise_bits, 0); + else if (spa_streq(k, "dither.method")) + this->dir[1].conv.method = dither_method_from_label(s); + else + return 0; + return 1; +} + +static int parse_prop_params(struct impl *this, struct spa_pod *params) +{ + struct spa_pod_parser prs; + struct spa_pod_frame f; + int changed = 0; + + spa_pod_parser_pod(&prs, params); + if (spa_pod_parser_push_struct(&prs, &f) < 0) + return 0; + + while (true) { + const char *name; + struct spa_pod *pod; + char value[512]; + + if (spa_pod_parser_get_string(&prs, &name) < 0) + break; + + if (spa_pod_parser_get_pod(&prs, &pod) < 0) + break; + + if (spa_pod_is_string(pod)) { + spa_pod_copy_string(pod, sizeof(value), value); + } else if (spa_pod_is_float(pod)) { + spa_dtoa(value, sizeof(value), + SPA_POD_VALUE(struct spa_pod_float, pod)); + } else if (spa_pod_is_double(pod)) { + spa_dtoa(value, sizeof(value), + SPA_POD_VALUE(struct spa_pod_double, pod)); + } else if (spa_pod_is_int(pod)) { + snprintf(value, sizeof(value), "%d", + SPA_POD_VALUE(struct spa_pod_int, pod)); + } else if (spa_pod_is_bool(pod)) { + snprintf(value, sizeof(value), "%s", + SPA_POD_VALUE(struct spa_pod_bool, pod) ? + "true" : "false"); + } else if (spa_pod_is_none(pod)) { + spa_zero(value); + } else + continue; + + spa_log_info(this->log, "key:'%s' val:'%s'", name, value); + changed += audioconvert_set_param(this, name, value); + } + if (changed) { + channelmix_init(&this->mix); + } + return changed; +} + +static int apply_props(struct impl *this, const struct spa_pod *param) +{ + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct props *p = &this->props; + bool have_channel_volume = false; + bool have_soft_volume = false; + int changed = 0; + uint32_t n; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_volume: + if (spa_pod_get_float(&prop->value, &p->volume) == 0) + changed++; + break; + case SPA_PROP_mute: + if (spa_pod_get_bool(&prop->value, &p->channel.mute) == 0) { + have_channel_volume = true; + changed++; + } + break; + case SPA_PROP_channelVolumes: + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + have_channel_volume = true; + p->channel.n_volumes = n; + changed++; + } + break; + case SPA_PROP_channelMap: + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, + p->channel_map, SPA_AUDIO_MAX_CHANNELS)) > 0) { + p->n_channels = n; + changed++; + } + break; + case SPA_PROP_softMute: + if (spa_pod_get_bool(&prop->value, &p->soft.mute) == 0) { + have_soft_volume = true; + changed++; + } + break; + case SPA_PROP_softVolumes: + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + p->soft.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + have_soft_volume = true; + p->soft.n_volumes = n; + changed++; + } + break; + case SPA_PROP_monitorMute: + if (spa_pod_get_bool(&prop->value, &p->monitor.mute) == 0) + changed++; + break; + case SPA_PROP_monitorVolumes: + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + p->monitor.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + p->monitor.n_volumes = n; + changed++; + } + break; + case SPA_PROP_rate: + spa_pod_get_double(&prop->value, &p->rate); + if (!this->rate_adjust && p->rate != 1.0) { + this->rate_adjust = true; + spa_log_info(this->log, "%p: activating adaptive resampler", + this); + } + break; + case SPA_PROP_params: + changed += parse_prop_params(this, &prop->value); + break; + default: + break; + } + } + if (changed) { + if (have_soft_volume) + p->have_soft_volume = true; + else if (have_channel_volume) + p->have_soft_volume = false; + + set_volume(this); + } + return changed; +} + +static int apply_midi(struct impl *this, const struct spa_pod *value) +{ + const uint8_t *val = SPA_POD_BODY(value); + uint32_t size = SPA_POD_BODY_SIZE(value); + struct props *p = &this->props; + + if (size < 3) + return -EINVAL; + + if ((val[0] & 0xf0) != 0xb0 || val[1] != 7) + return 0; + + p->volume = val[2] / 127.0f; + set_volume(this); + return 1; +} + +static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, + enum spa_direction direction, bool monitor, bool control, struct spa_audio_info *info) +{ + struct dir *dir; + uint32_t i; + + dir = &this->dir[direction]; + + if (dir->have_profile && this->monitor == monitor && dir->mode == mode && + dir->control == control && + (info == NULL || memcmp(&dir->format, info, sizeof(*info)) == 0)) + return 0; + + spa_log_info(this->log, "%p: port config direction:%d monitor:%d control:%d mode:%d %d", this, + direction, monitor, control, mode, dir->n_ports); + + for (i = 0; i < dir->n_ports; i++) { + spa_node_emit_port_info(&this->hooks, direction, i, NULL); + if (this->monitor && direction == SPA_DIRECTION_INPUT) + spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, i+1, NULL); + } + + this->monitor = monitor; + this->setup = false; + dir->control = control; + dir->have_profile = true; + dir->mode = mode; + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + { + if (info) { + dir->n_ports = info->info.raw.channels; + dir->format = *info; + dir->format.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; + dir->format.info.raw.rate = 0; + dir->have_format = true; + } else { + dir->n_ports = 0; + } + + if (this->monitor && direction == SPA_DIRECTION_INPUT) + this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; + + for (i = 0; i < dir->n_ports; i++) { + init_port(this, direction, i, info->info.raw.position[i], true, false, false); + if (this->monitor && direction == SPA_DIRECTION_INPUT) + init_port(this, SPA_DIRECTION_OUTPUT, i+1, + info->info.raw.position[i], true, true, false); + } + break; + } + case SPA_PARAM_PORT_CONFIG_MODE_convert: + { + dir->n_ports = 1; + dir->have_format = false; + init_port(this, direction, 0, 0, false, false, false); + break; + } + case SPA_PARAM_PORT_CONFIG_MODE_none: + break; + default: + return -ENOTSUP; + } + if (direction == SPA_DIRECTION_INPUT && dir->control) { + i = dir->n_ports++; + init_port(this, direction, i, 0, false, false, true); + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; + this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; + this->params[IDX_Props].user++; + this->params[IDX_PortConfig].user++; + return 0; +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + if (param == NULL) + return 0; + + switch (id) { + case SPA_PARAM_PortConfig: + { + struct spa_audio_info info = { 0, }, *infop = NULL; + struct spa_pod *format = NULL; + enum spa_direction direction; + enum spa_param_port_config_mode mode; + bool monitor = false, control = false; + int res; + + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamPortConfig, NULL, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) + return -EINVAL; + + if (format) { + if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) + return -EINVAL; + + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return res; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) + return -EINVAL; + + if (info.info.raw.format == 0 || + info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + + infop = &info; + } + + if ((res = reconfigure_mode(this, mode, direction, monitor, control, infop)) < 0) + return res; + + emit_node_info(this, false); + break; + } + case SPA_PARAM_Props: + if (apply_props(this, param) > 0) + emit_node_info(this, false); + break; + default: + return -ENOENT; + } + return 0; +} + +static int int32_cmp(const void *v1, const void *v2) +{ + int32_t a1 = *(int32_t*)v1; + int32_t a2 = *(int32_t*)v2; + if (a1 == 0 && a2 != 0) + return 1; + if (a2 == 0 && a1 != 0) + return -1; + return a1 - a2; +} + +static int setup_in_convert(struct impl *this) +{ + uint32_t i, j; + struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; + struct spa_audio_info src_info, dst_info; + int res; + bool remap = false; + + src_info = in->format; + dst_info = src_info; + dst_info.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; + + spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, + spa_debug_type_find_name(spa_type_audio_format, src_info.info.raw.format), + src_info.info.raw.channels, + src_info.info.raw.rate, + spa_debug_type_find_name(spa_type_audio_format, dst_info.info.raw.format), + dst_info.info.raw.channels, + dst_info.info.raw.rate); + + qsort(dst_info.info.raw.position, dst_info.info.raw.channels, + sizeof(uint32_t), int32_cmp); + + for (i = 0; i < src_info.info.raw.channels; i++) { + for (j = 0; j < dst_info.info.raw.channels; j++) { + if (src_info.info.raw.position[i] != + dst_info.info.raw.position[j]) + continue; + in->remap[i] = j; + if (i != j) + remap = true; + spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, + i, in->remap[i], j, + spa_debug_type_find_short_name(spa_type_audio_channel, + src_info.info.raw.position[i]), + spa_debug_type_find_short_name(spa_type_audio_channel, + dst_info.info.raw.position[j])); + dst_info.info.raw.position[j] = -1; + break; + } + } + if (in->conv.free) + convert_free(&in->conv); + + in->conv.src_fmt = src_info.info.raw.format; + in->conv.dst_fmt = dst_info.info.raw.format; + in->conv.n_channels = dst_info.info.raw.channels; + in->conv.cpu_flags = this->cpu_flags; + in->need_remap = remap; + + if ((res = convert_init(&in->conv)) < 0) + return res; + + spa_log_debug(this->log, "%p: got converter features %08x:%08x passthrough:%d remap:%d %s", this, + this->cpu_flags, in->conv.cpu_flags, in->conv.is_passthrough, + remap, in->conv.func_name); + + return 0; +} + +static void fix_volumes(struct impl *this, struct volumes *vols, uint32_t channels) +{ + float s; + uint32_t i; + spa_log_debug(this->log, "%p %d -> %d", this, vols->n_volumes, channels); + if (vols->n_volumes > 0) { + s = 0.0f; + for (i = 0; i < vols->n_volumes; i++) + s += vols->volumes[i]; + s /= vols->n_volumes; + } else { + s = 1.0f; + } + vols->n_volumes = channels; + for (i = 0; i < vols->n_volumes; i++) + vols->volumes[i] = s; +} + +static int remap_volumes(struct impl *this, const struct spa_audio_info *info) +{ + struct props *p = &this->props; + uint32_t i, j, target = info->info.raw.channels; + + for (i = 0; i < p->n_channels; i++) { + for (j = i; j < target; j++) { + spa_log_debug(this->log, "%d %d: %d <-> %d", i, j, + p->channel_map[i], info->info.raw.position[j]); + if (p->channel_map[i] != info->info.raw.position[j]) + continue; + if (i != j) { + SPA_SWAP(p->channel_map[i], p->channel_map[j]); + SPA_SWAP(p->channel.volumes[i], p->channel.volumes[j]); + SPA_SWAP(p->soft.volumes[i], p->soft.volumes[j]); + SPA_SWAP(p->monitor.volumes[i], p->monitor.volumes[j]); + } + break; + } + } + p->n_channels = target; + for (i = 0; i < p->n_channels; i++) + p->channel_map[i] = info->info.raw.position[i]; + + if (target == 0) + return 0; + if (p->channel.n_volumes != target) + fix_volumes(this, &p->channel, target); + if (p->soft.n_volumes != target) + fix_volumes(this, &p->soft, target); + if (p->monitor.n_volumes != target) + fix_volumes(this, &p->monitor, target); + + return 1; +} + +static void set_volume(struct impl *this) +{ + struct volumes *vol; + uint32_t i; + float volumes[SPA_AUDIO_MAX_CHANNELS]; + struct dir *dir = &this->dir[this->direction]; + + spa_log_debug(this->log, "%p have_format:%d", this, dir->have_format); + + if (dir->have_format) + remap_volumes(this, &dir->format); + + if (this->mix.set_volume == NULL) + return; + + if (this->props.have_soft_volume) + vol = &this->props.soft; + else + vol = &this->props.channel; + + for (i = 0; i < vol->n_volumes; i++) + volumes[i] = vol->volumes[dir->remap[i]]; + + channelmix_set_volume(&this->mix, this->props.volume, vol->mute, + vol->n_volumes, volumes); + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->params[IDX_Props].user++; +} + +static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position) +{ + uint32_t i, idx = 0; + for (i = 0; i < channels; i++) + idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", + spa_debug_type_find_short_name(spa_type_audio_channel, + position[i])); + return str; +} + +static int setup_channelmix(struct impl *this) +{ + struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; + struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; + uint32_t i, src_chan, dst_chan, p; + uint64_t src_mask, dst_mask; + char str[1024]; + int res; + + src_chan = in->format.info.raw.channels; + dst_chan = out->format.info.raw.channels; + + for (i = 0, src_mask = 0; i < src_chan; i++) { + p = in->format.info.raw.position[i]; + src_mask |= 1ULL << (p < 64 ? p : 0); + } + for (i = 0, dst_mask = 0; i < dst_chan; i++) { + p = out->format.info.raw.position[i]; + dst_mask |= 1ULL << (p < 64 ? p : 0); + } + + spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), + src_chan, in->format.info.raw.position), src_mask); + spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), + dst_chan, out->format.info.raw.position), dst_mask); + + spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d %08"PRIx64":%08"PRIx64, this, + spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), + src_chan, + in->format.info.raw.rate, + spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), + dst_chan, + in->format.info.raw.rate, + src_mask, dst_mask); + + this->mix.src_chan = src_chan; + this->mix.src_mask = src_mask; + this->mix.dst_chan = dst_chan; + this->mix.dst_mask = dst_mask; + this->mix.cpu_flags = this->cpu_flags; + this->mix.log = this->log; + this->mix.freq = in->format.info.raw.rate; + + if ((res = channelmix_init(&this->mix)) < 0) + return res; + + set_volume(this); + + spa_log_debug(this->log, "%p: got channelmix features %08x:%08x flags:%08x %s", + this, this->cpu_flags, this->mix.cpu_flags, + this->mix.flags, this->mix.func_name); + return 0; +} + +static int setup_resample(struct impl *this) +{ + struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; + struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; + int res; + + spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, + spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), + out->format.info.raw.channels, + in->format.info.raw.rate, + spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), + out->format.info.raw.channels, + out->format.info.raw.rate); + + if (this->resample.free) + resample_free(&this->resample); + + this->resample.channels = out->format.info.raw.channels; + this->resample.i_rate = in->format.info.raw.rate; + this->resample.o_rate = out->format.info.raw.rate; + this->resample.log = this->log; + this->resample.quality = this->props.resample_quality; + this->resample.cpu_flags = this->cpu_flags; + + this->rate_adjust = this->props.rate != 1.0; + + if (this->resample_peaks) + res = resample_peaks_init(&this->resample); + else + res = resample_native_init(&this->resample); + + spa_log_debug(this->log, "%p: got resample features %08x:%08x %s", + this, this->cpu_flags, this->resample.cpu_flags, + this->resample.func_name); + return res; +} + +static int calc_width(struct spa_audio_info *info) +{ + switch (info->info.raw.format) { + case SPA_AUDIO_FORMAT_U8: + case SPA_AUDIO_FORMAT_U8P: + case SPA_AUDIO_FORMAT_S8: + case SPA_AUDIO_FORMAT_S8P: + case SPA_AUDIO_FORMAT_ULAW: + case SPA_AUDIO_FORMAT_ALAW: + return 1; + case SPA_AUDIO_FORMAT_S16P: + case SPA_AUDIO_FORMAT_S16: + case SPA_AUDIO_FORMAT_S16_OE: + return 2; + case SPA_AUDIO_FORMAT_S24P: + case SPA_AUDIO_FORMAT_S24: + case SPA_AUDIO_FORMAT_S24_OE: + return 3; + case SPA_AUDIO_FORMAT_F64P: + case SPA_AUDIO_FORMAT_F64: + case SPA_AUDIO_FORMAT_F64_OE: + return 8; + default: + return 4; + } +} + +static int setup_out_convert(struct impl *this) +{ + uint32_t i, j; + struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; + struct spa_audio_info src_info, dst_info; + int res; + bool remap = false; + + dst_info = out->format; + src_info = dst_info; + src_info.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; + + spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, + spa_debug_type_find_name(spa_type_audio_format, src_info.info.raw.format), + src_info.info.raw.channels, + src_info.info.raw.rate, + spa_debug_type_find_name(spa_type_audio_format, dst_info.info.raw.format), + dst_info.info.raw.channels, + dst_info.info.raw.rate); + + qsort(src_info.info.raw.position, src_info.info.raw.channels, + sizeof(uint32_t), int32_cmp); + + for (i = 0; i < src_info.info.raw.channels; i++) { + for (j = 0; j < dst_info.info.raw.channels; j++) { + if (src_info.info.raw.position[i] != + dst_info.info.raw.position[j]) + continue; + out->remap[i] = j; + if (i != j) + remap = true; + + spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, + i, out->remap[i], j, + spa_debug_type_find_short_name(spa_type_audio_channel, + src_info.info.raw.position[i]), + spa_debug_type_find_short_name(spa_type_audio_channel, + dst_info.info.raw.position[j])); + dst_info.info.raw.position[j] = -1; + break; + } + } + if (out->conv.free) + convert_free(&out->conv); + + out->conv.src_fmt = src_info.info.raw.format; + out->conv.dst_fmt = dst_info.info.raw.format; + out->conv.rate = dst_info.info.raw.rate; + out->conv.n_channels = dst_info.info.raw.channels; + out->conv.cpu_flags = this->cpu_flags; + out->need_remap = remap; + + if ((res = convert_init(&out->conv)) < 0) + return res; + + spa_log_debug(this->log, "%p: got converter features %08x:%08x quant:%d:%d" + " passthrough:%d remap:%d %s", this, + this->cpu_flags, out->conv.cpu_flags, out->conv.method, + out->conv.noise_bits, out->conv.is_passthrough, remap, out->conv.func_name); + + return 0; +} + +static int setup_convert(struct impl *this) +{ + struct dir *in, *out; + uint32_t i, rate; + int res; + + in = &this->dir[SPA_DIRECTION_INPUT]; + out = &this->dir[SPA_DIRECTION_OUTPUT]; + + spa_log_debug(this->log, "%p: setup:%d in_format:%d out_format:%d", this, + this->setup, in->have_format, out->have_format); + + if (this->setup) + return 0; + + if (!in->have_format || !out->have_format) + return -EINVAL; + + rate = this->io_position ? this->io_position->clock.rate.denom : DEFAULT_RATE; + + /* in DSP mode we always convert to the DSP rate */ + if (in->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) + in->format.info.raw.rate = rate; + if (out->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) + out->format.info.raw.rate = rate; + + /* try to passthrough the rates */ + if (in->format.info.raw.rate == 0) + in->format.info.raw.rate = out->format.info.raw.rate; + else if (out->format.info.raw.rate == 0) + out->format.info.raw.rate = in->format.info.raw.rate; + + /* try to passthrough the channels */ + if (in->format.info.raw.channels == 0) + in->format.info.raw.channels = out->format.info.raw.channels; + else if (out->format.info.raw.channels == 0) + out->format.info.raw.channels = in->format.info.raw.channels; + + if (in->format.info.raw.rate == 0 || out->format.info.raw.rate == 0) + return -EINVAL; + if (in->format.info.raw.channels == 0 || out->format.info.raw.channels == 0) + return -EINVAL; + + if ((res = setup_in_convert(this)) < 0) + return res; + if ((res = setup_channelmix(this)) < 0) + return res; + if ((res = setup_resample(this)) < 0) + return res; + if ((res = setup_out_convert(this)) < 0) + return res; + + for (i = 0; i < MAX_PORTS; i++) { + this->tmp_datas[0][i] = SPA_PTROFF(this->tmp[0], this->empty_size * i, void); + this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp_datas[0][i], MAX_ALIGN, void); + this->tmp_datas[1][i] = SPA_PTROFF(this->tmp[1], this->empty_size * i, void); + this->tmp_datas[1][i] = SPA_PTR_ALIGN(this->tmp_datas[1][i], MAX_ALIGN, void); + } + this->setup = true; + + emit_node_info(this, false); + + return 0; +} + +static void reset_node(struct impl *this) +{ + if (this->resample.reset) + resample_reset(&this->resample); + this->in_offset = 0; + this->out_offset = 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct impl *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + if (this->started) + return 0; + if ((res = setup_convert(this)) < 0) + return res; + this->started = true; + break; + case SPA_NODE_COMMAND_Suspend: + this->setup = false; + SPA_FALLTHROUGH; + case SPA_NODE_COMMAND_Pause: + this->started = false; + break; + case SPA_NODE_COMMAND_Flush: + reset_node(this); + break; + default: + return -ENOTSUP; + } + return 0; +} + +static int +impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct impl *this = object; + uint32_t i; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_trace(this->log, "%p: add listener %p", this, listener); + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_node_info(this, true); + for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { + emit_port_info(this, GET_IN_PORT(this, i), true); + } + for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { + emit_port_info(this, GET_OUT_PORT(this, i), true); + } + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *user_data) +{ + return 0; +} + +static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, + const struct spa_dict *props) +{ + return -ENOTSUP; +} + +static int +impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int port_enum_formats(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t index, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct impl *this = object; + + switch (index) { + case 0: + if (PORT_IS_DSP(this, direction, port_id)) { + struct spa_audio_info_dsp info; + info.format = SPA_AUDIO_FORMAT_DSP_F32; + *param = spa_format_audio_dsp_build(builder, + SPA_PARAM_EnumFormat, &info); + } else if (PORT_IS_CONTROL(this, direction, port_id)) { + *param = spa_pod_builder_add_object(builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + } else { + uint32_t rate = this->io_position ? + this->io_position->clock.rate.denom : DEFAULT_RATE; + + *param = spa_pod_builder_add_object(builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(25, + SPA_AUDIO_FORMAT_F32P, + SPA_AUDIO_FORMAT_F32P, + SPA_AUDIO_FORMAT_F32, + SPA_AUDIO_FORMAT_F32_OE, + SPA_AUDIO_FORMAT_F64P, + SPA_AUDIO_FORMAT_F64, + SPA_AUDIO_FORMAT_F64_OE, + SPA_AUDIO_FORMAT_S32P, + SPA_AUDIO_FORMAT_S32, + SPA_AUDIO_FORMAT_S32_OE, + SPA_AUDIO_FORMAT_S24_32P, + SPA_AUDIO_FORMAT_S24_32, + SPA_AUDIO_FORMAT_S24_32_OE, + SPA_AUDIO_FORMAT_S24P, + SPA_AUDIO_FORMAT_S24, + SPA_AUDIO_FORMAT_S24_OE, + SPA_AUDIO_FORMAT_S16P, + SPA_AUDIO_FORMAT_S16, + SPA_AUDIO_FORMAT_S16_OE, + SPA_AUDIO_FORMAT_S8P, + SPA_AUDIO_FORMAT_S8, + SPA_AUDIO_FORMAT_U8P, + SPA_AUDIO_FORMAT_U8, + SPA_AUDIO_FORMAT_ULAW, + SPA_AUDIO_FORMAT_ALAW), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( + rate, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_CHANNELS, 1, SPA_AUDIO_MAX_CHANNELS)); + } + break; + default: + return 0; + } + return 1; +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct port *port; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[2048]; + struct spa_result_node_params result; + uint32_t count = 0; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", + this, direction, port_id, seq, id); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumFormat: + if ((res = port_enum_formats(object, direction, port_id, result.index, ¶m, &b)) <= 0) + return res; + break; + case SPA_PARAM_Format: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + if (PORT_IS_DSP(this, direction, port_id)) + param = spa_format_audio_dsp_build(&b, id, &port->format.info.dsp); + else if (PORT_IS_CONTROL(this, direction, port_id)) + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, id, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + else + param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); + break; + case SPA_PARAM_Buffers: + { + uint32_t size; + + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + if (PORT_IS_DSP(this, direction, port_id)) { + /* DSP ports always use the quantum_limit as the buffer + * size. */ + size = this->quantum_limit; + } else { + uint32_t irate, orate; + struct dir *dir = &this->dir[direction]; + + /* Convert ports are scaled so that they can always + * provide one quantum of data */ + irate = dir->format.info.raw.rate; + + /* collect the other port rate */ + dir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; + if (dir->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) + orate = this->io_position ? this->io_position->clock.rate.denom : DEFAULT_RATE; + else + orate = dir->format.info.raw.rate; + + /* always keep some extra room for adaptive resampling */ + size = this->quantum_limit * 2; + /* scale the buffer size when we can. */ + if (irate != 0 && orate != 0) + size = SPA_SCALE32_UP(size, irate, orate); + } + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + size * port->stride, + 16 * port->stride, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); + break; + } + case SPA_PARAM_Meta: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + break; + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + default: + return 0; + } + break; + case SPA_PARAM_Latency: + switch (result.index) { + case 0: case 1: + param = spa_latency_build(&b, id, &this->dir[result.index].latency); + break; + default: + return 0; + } + break; + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int clear_buffers(struct impl *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_log_debug(this->log, "%p: clear buffers %p", this, port); + port->n_buffers = 0; + spa_list_init(&port->queue); + } + return 0; +} + +static int port_set_latency(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *latency) +{ + struct impl *this = object; + struct port *port, *oport; + enum spa_direction other = SPA_DIRECTION_REVERSE(direction); + uint32_t i; + + spa_log_debug(this->log, "%p: set latency direction:%d id:%d", + this, direction, port_id); + + port = GET_PORT(this, direction, port_id); + if (port->is_monitor) + return 0; + + if (latency == NULL) { + this->dir[other].latency = SPA_LATENCY_INFO(other); + } else { + struct spa_latency_info info; + if (spa_latency_parse(latency, &info) < 0 || + info.direction != other) + return -EINVAL; + this->dir[other].latency = info; + } + + for (i = 0; i < this->dir[other].n_ports; i++) { + oport = GET_PORT(this, other, i); + oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + oport->params[IDX_Latency].user++; + emit_port_info(this, oport, false); + } + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[IDX_Latency].user++; + emit_port_info(this, port, false); + return 0; +} + +static int port_set_format(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *format) +{ + struct impl *this = object; + struct port *port; + int res; + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: set format", this); + + if (format == NULL) { + port->have_format = false; + clear_buffers(this, port); + } else { + struct spa_audio_info info = { 0 }; + + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) { + spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); + return res; + } + if (PORT_IS_DSP(this, direction, port_id)) { + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) { + spa_log_error(this->log, "unexpected types %d/%d", + info.media_type, info.media_subtype); + return -EINVAL; + } + if ((res = spa_format_audio_dsp_parse(format, &info.info.dsp)) < 0) { + spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); + return res; + } + if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) { + spa_log_error(this->log, "unexpected format %d<->%d", + info.info.dsp.format, SPA_AUDIO_FORMAT_DSP_F32); + return -EINVAL; + } + port->blocks = 1; + port->stride = 4; + } + else if (PORT_IS_CONTROL(this, direction, port_id)) { + if (info.media_type != SPA_MEDIA_TYPE_application || + info.media_subtype != SPA_MEDIA_SUBTYPE_control) { + spa_log_error(this->log, "unexpected types %d/%d", + info.media_type, info.media_subtype); + return -EINVAL; + } + port->blocks = 1; + port->stride = 1; + } + else { + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { + spa_log_error(this->log, "unexpected types %d/%d", + info.media_type, info.media_subtype); + return -EINVAL; + } + if ((res = spa_format_audio_raw_parse(format, &info.info.raw)) < 0) { + spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); + return res; + } + if (info.info.raw.format == 0 || + info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) { + spa_log_error(this->log, "invalid format:%d rate:%d channels:%d", + info.info.raw.format, info.info.raw.rate, + info.info.raw.channels); + return -EINVAL; + } + port->stride = calc_width(&info); + if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { + port->blocks = info.info.raw.channels; + } else { + port->stride *= info.info.raw.channels; + port->blocks = 1; + } + this->dir[direction].format = info; + this->dir[direction].have_format = true; + this->setup = false; + } + port->format = info; + port->have_format = true; + + spa_log_debug(this->log, "%p: %d %d %d", this, + port_id, port->stride, port->blocks); + } + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + } else { + port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); + + return 0; +} + + +static int +impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_debug(this->log, "%p: set param port %d.%d %u", + this, direction, port_id, id); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + switch (id) { + case SPA_PARAM_Latency: + return port_set_latency(this, direction, port_id, flags, param); + case SPA_PARAM_Format: + return port_set_format(this, direction, port_id, flags, param); + default: + return -ENOENT; + } +} + +static void queue_buffer(struct impl *this, struct port *port, uint32_t id) +{ + struct buffer *b = &port->buffers[id]; + + spa_log_trace_fp(this->log, "%p: queue buffer %d on port %d %d", + this, id, port->id, b->flags); + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) + return; + + spa_list_append(&port->queue, &b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); +} + +static struct buffer *peek_buffer(struct impl *this, struct port *port) +{ + struct buffer *b; + + if (spa_list_is_empty(&port->queue)) + return NULL; + + b = spa_list_first(&port->queue, struct buffer, link); + spa_log_trace_fp(this->log, "%p: peek buffer %d on port %d %u", + this, b->id, port->id, b->flags); + return b; +} + +static void dequeue_buffer(struct impl *this, struct port *port, struct buffer *b) +{ + spa_list_remove(&b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); + spa_log_trace_fp(this->log, "%p: dequeue buffer %d on port %d %u", + this, b->id, port->id, b->flags); +} + +static int +impl_node_port_use_buffers(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + struct impl *this = object; + struct port *port; + uint32_t i, j, maxsize; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: use buffers %d on port %d:%d", + this, n_buffers, direction, port_id); + + clear_buffers(this, port); + + if (n_buffers > 0 && !port->have_format) + return -EIO; + if (n_buffers > MAX_BUFFERS) + return -ENOSPC; + + maxsize = this->quantum_limit * sizeof(float); + + for (i = 0; i < n_buffers; i++) { + struct buffer *b; + uint32_t n_datas = buffers[i]->n_datas; + struct spa_data *d = buffers[i]->datas; + + b = &port->buffers[i]; + b->id = i; + b->flags = 0; + b->buf = buffers[i]; + + if (n_datas != port->blocks) { + spa_log_error(this->log, "%p: invalid blocks %d on buffer %d", + this, n_datas, i); + return -EINVAL; + } + + for (j = 0; j < n_datas; j++) { + if (d[j].data == NULL) { + spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", + this, j, i, d[j].type, d[j].data); + return -EINVAL; + } + if (!SPA_IS_ALIGNED(d[j].data, this->max_align)) { + spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", + this, j, i); + } + if (direction == SPA_DIRECTION_OUTPUT && + !SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_DYNAMIC)) + this->is_passthrough = false; + + b->datas[j] = d[j].data; + + maxsize = SPA_MAX(maxsize, d[j].maxsize); + } + if (direction == SPA_DIRECTION_OUTPUT) + queue_buffer(this, port, i); + } + if (maxsize > this->empty_size) { + this->empty = realloc(this->empty, maxsize + MAX_ALIGN); + this->scratch = realloc(this->scratch, maxsize + MAX_ALIGN); + this->tmp[0] = realloc(this->tmp[0], (maxsize + MAX_ALIGN) * MAX_PORTS); + this->tmp[1] = realloc(this->tmp[1], (maxsize + MAX_ALIGN) * MAX_PORTS); + if (this->empty == NULL || this->scratch == NULL || + this->tmp[0] == NULL || this->tmp[1] == NULL) + return -errno; + memset(this->empty, 0, maxsize + MAX_ALIGN); + this->empty_size = maxsize; + } + port->n_buffers = n_buffers; + + return 0; +} + +static int +impl_node_port_set_io(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, void *data, size_t size) +{ + struct impl *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_debug(this->log, "%p: set io %d on port %d:%d %p", + this, id, direction, port_id, data); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; + case SPA_IO_RateMatch: + this->io_rate_match = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + struct impl *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); + + port = GET_OUT_PORT(this, port_id); + queue_buffer(this, port, buffer_id); + + return 0; +} + +static int channelmix_process_control(struct impl *this, struct port *ctrlport, + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + struct spa_pod_control *c, *prev = NULL; + uint32_t avail_samples = n_samples; + uint32_t i; + const float *s[MAX_PORTS], **ss = (const float**) src; + float *d[MAX_PORTS], **sd = (float **) dst; + const struct spa_pod_sequence_body *body = &(ctrlport->ctrl)->body; + uint32_t size = SPA_POD_BODY_SIZE(ctrlport->ctrl); + bool end = false; + + c = spa_pod_control_first(body); + while (true) { + uint32_t chunk; + + if (c == NULL || !spa_pod_control_is_inside(body, size, c)) { + c = NULL; + end = true; + } + if (avail_samples == 0) + break; + + /* ignore old control offsets */ + if (c != NULL) { + if (c->offset <= ctrlport->ctrl_offset) { + prev = c; + if (c != NULL) + c = spa_pod_control_next(c); + continue; + } + chunk = SPA_MIN(avail_samples, c->offset - ctrlport->ctrl_offset); + spa_log_trace_fp(this->log, "%p: process %d-%d %d/%d", this, + ctrlport->ctrl_offset, c->offset, chunk, avail_samples); + } else { + chunk = avail_samples; + spa_log_trace_fp(this->log, "%p: process remain %d", this, chunk); + } + + if (prev) { + switch (prev->type) { + case SPA_CONTROL_Midi: + apply_midi(this, &prev->value); + break; + case SPA_CONTROL_Properties: + apply_props(this, &prev->value); + break; + default: + continue; + } + } + if (ss == (const float**)src && chunk != avail_samples) { + for (i = 0; i < this->mix.src_chan; i++) + s[i] = ss[i]; + for (i = 0; i < this->mix.dst_chan; i++) + d[i] = sd[i]; + ss = s; + sd = d; + } + + channelmix_process(&this->mix, (void**)sd, (const void**)ss, chunk); + + if (chunk != avail_samples) { + for (i = 0; i < this->mix.src_chan; i++) + ss[i] += chunk; + for (i = 0; i < this->mix.dst_chan; i++) + sd[i] += chunk; + } + avail_samples -= chunk; + ctrlport->ctrl_offset += chunk; + } + return end ? 1 : 0; +} + +static uint32_t resample_get_in_size(struct impl *this, bool passthrough, uint32_t out_size) +{ + uint32_t match_size = passthrough ? out_size : resample_in_len(&this->resample, out_size); + spa_log_trace_fp(this->log, "%p: current match %u", this, match_size); + return match_size; +} + +static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, uint32_t out_size, uint32_t in_queued) +{ + uint32_t delay, match_size; + + if (passthrough) { + delay = 0; + match_size = out_size; + } else { + double rate = this->rate_scale / this->props.rate; + if (this->io_rate_match && + SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) + rate *= this->io_rate_match->rate; + resample_update_rate(&this->resample, rate); + delay = resample_delay(&this->resample); + match_size = resample_in_len(&this->resample, out_size); + } + match_size -= SPA_MIN(match_size, in_queued); + + spa_log_trace_fp(this->log, "%p: next match %u", this, match_size); + + if (this->io_rate_match) { + this->io_rate_match->delay = delay; + this->io_rate_match->size = match_size; + } + return match_size; +} + +static inline bool resample_is_passthrough(struct impl *this) +{ + return this->resample.i_rate == this->resample.o_rate && this->rate_scale == 1.0 && + !this->rate_adjust && (this->io_rate_match == NULL || + !SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)); +} + +static int impl_node_process(void *object) +{ + struct impl *this = object; + const void *src_datas[MAX_PORTS], **in_datas; + void *dst_datas[MAX_PORTS], *remap_src_datas[MAX_PORTS], *remap_dst_datas[MAX_PORTS]; + void **out_datas, **dst_remap; + uint32_t i, j, n_src_datas = 0, n_dst_datas = 0, n_mon_datas = 0, remap; + uint32_t n_samples, max_in, n_out, max_out, quant_samples; + struct port *port, *ctrlport = NULL; + struct buffer *buf, *out_bufs[MAX_PORTS]; + struct spa_data *bd; + struct dir *dir; + int tmp = 0, res = 0; + bool in_passthrough, mix_passthrough, resample_passthrough, out_passthrough; + bool in_avail = false, flush_in = false, flush_out = false, draining = false, in_empty = true; + struct spa_io_buffers *io, *ctrlio = NULL; + const struct spa_pod_sequence *ctrl = NULL; + + /* calculate quantum scale, this is how many samples we need to produce or + * consume. Also update the rate scale, this is sent to the resampler to adjust + * the rate, either when the graph clock changed or when the user adjusted the + * rate. */ + if (SPA_LIKELY(this->io_position)) { + double r = this->rate_scale; + + quant_samples = this->io_position->clock.duration; + if (this->direction == SPA_DIRECTION_INPUT) { + if (this->io_position->clock.rate.denom != this->resample.o_rate) + r = (double) this->io_position->clock.rate.denom / this->resample.o_rate; + else + r = 1.0; + } else { + if (this->io_position->clock.rate.denom != this->resample.i_rate) + r = (double) this->resample.i_rate / this->io_position->clock.rate.denom; + else + r = 1.0; + } + if (this->rate_scale != r) { + spa_log_info(this->log, "scale %f->%f", this->rate_scale, r); + this->rate_scale = r; + } + } + else + quant_samples = this->quantum_limit; + + dir = &this->dir[SPA_DIRECTION_INPUT]; + in_passthrough = dir->conv.is_passthrough; + max_in = UINT32_MAX; + + /* collect input port data */ + for (i = 0; i < dir->n_ports; i++) { + port = GET_IN_PORT(this, i); + + if (SPA_UNLIKELY((io = port->io) == NULL)) { + spa_log_trace_fp(this->log, "%p: no io on input port %d", + this, port->id); + buf = NULL; + } else if (SPA_UNLIKELY(io->status != SPA_STATUS_HAVE_DATA)) { + if (io->status & SPA_STATUS_DRAINED) { + spa_log_debug(this->log, "%p: port %d drained", this, port->id); + in_avail = flush_in = draining = true; + } else { + spa_log_trace_fp(this->log, "%p: empty input port %d %p %d %d %d", + this, port->id, io, io->status, io->buffer_id, + port->n_buffers); + this->drained = false; + } + buf = NULL; + } else if (SPA_UNLIKELY(io->buffer_id >= port->n_buffers)) { + spa_log_trace_fp(this->log, "%p: invalid input buffer port %d %p %d %d %d", + this, port->id, io, io->status, io->buffer_id, + port->n_buffers); + io->status = -EINVAL; + buf = NULL; + } else { + spa_log_trace_fp(this->log, "%p: input buffer port %d io:%p status:%d id:%d n:%d", + this, port->id, io, io->status, io->buffer_id, + port->n_buffers); + buf = &port->buffers[io->buffer_id]; + } + + if (SPA_UNLIKELY(buf == NULL)) { + for (j = 0; j < port->blocks; j++) { + if (port->is_control) { + spa_log_trace_fp(this->log, "%p: empty control %d", this, + i * port->blocks + j); + } else { + remap = n_src_datas++; + src_datas[remap] = SPA_PTR_ALIGN(this->empty, MAX_ALIGN, void); + spa_log_trace_fp(this->log, "%p: empty input %d->%d", this, + i * port->blocks + j, remap); + max_in = SPA_MIN(max_in, this->empty_size / port->stride); + } + } + } else { + in_avail = true; + for (j = 0; j < port->blocks; j++) { + uint32_t offs, size; + + bd = &buf->buf->datas[j]; + + offs = SPA_MIN(bd->chunk->offset, bd->maxsize); + size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); + if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) + in_empty = false; + + if (SPA_UNLIKELY(port->is_control)) { + spa_log_trace_fp(this->log, "%p: control %d", this, + i * port->blocks + j); + ctrlport = port; + ctrlio = io; + ctrl = spa_pod_from_data(bd->data, bd->maxsize, + bd->chunk->offset, bd->chunk->size); + if (ctrl && !spa_pod_is_sequence(&ctrl->pod)) + ctrl = NULL; + if (ctrl != ctrlport->ctrl) { + ctrlport->ctrl = ctrl; + ctrlport->ctrl_offset = 0; + } + } else { + max_in = SPA_MIN(max_in, size / port->stride); + + remap = n_src_datas++; + offs += this->in_offset * port->stride; + src_datas[remap] = SPA_PTROFF(bd->data, offs, void); + + spa_log_trace_fp(this->log, "%p: input %d:%d:%d %d %d %d->%d", this, + offs, size, port->stride, this->in_offset, max_in, + i * port->blocks + j, remap); + } + } + } + } + + /* calculate how many samples we are going to produce. */ + if (this->direction == SPA_DIRECTION_INPUT) { + /* in split mode we need to output exactly the size of the + * duration so we don't try to flush early */ + max_out = quant_samples; + flush_out = false; + } else { + /* in merge mode we consume one duration of samples and + * always output the resulting data */ + max_out = this->quantum_limit; + flush_out = true; + } + + dir = &this->dir[SPA_DIRECTION_OUTPUT]; + /* collect output ports and monitor ports data */ + for (i = 0; i < dir->n_ports; i++) { + port = GET_OUT_PORT(this, i); + + if (SPA_UNLIKELY((io = port->io) == NULL || + io->status == SPA_STATUS_HAVE_DATA)) { + buf = NULL; + } + else { + if (SPA_LIKELY(io->buffer_id < port->n_buffers)) + queue_buffer(this, port, io->buffer_id); + + buf = peek_buffer(this, port); + } + out_bufs[i] = buf; + + if (SPA_UNLIKELY(buf == NULL)) { + for (j = 0; j < port->blocks; j++) { + if (port->is_monitor) { + remap = n_mon_datas++; + spa_log_trace_fp(this->log, "%p: empty monitor %d", this, + remap); + } else if (port->is_control) { + spa_log_trace_fp(this->log, "%p: empty control %d", this, j); + } else { + remap = n_dst_datas++; + dst_datas[remap] = SPA_PTR_ALIGN(this->scratch, MAX_ALIGN, void); + spa_log_trace_fp(this->log, "%p: empty output %d->%d", this, + i * port->blocks + j, remap); + max_out = SPA_MIN(max_out, this->empty_size / port->stride); + } + } + } else { + for (j = 0; j < port->blocks; j++) { + bd = &buf->buf->datas[j]; + + bd->chunk->offset = 0; + bd->chunk->size = 0; + if (port->is_monitor) { + float volume; + uint32_t mon_max; + + remap = n_mon_datas++; + volume = this->props.monitor.mute ? 0.0f : this->props.monitor.volumes[remap]; + if (this->monitor_channel_volumes) + volume *= this->props.channel.mute ? 0.0f : + this->props.channel.volumes[remap]; + + mon_max = SPA_MIN(bd->maxsize / port->stride, max_in); + + volume_process(&this->volume, bd->data, src_datas[remap], + volume, mon_max); + + bd->chunk->size = mon_max * port->stride; + bd->chunk->stride = port->stride; + + spa_log_trace_fp(this->log, "%p: monitor %d %d", this, + remap, mon_max); + + dequeue_buffer(this, port, buf); + io->status = SPA_STATUS_HAVE_DATA; + io->buffer_id = buf->id; + res |= SPA_STATUS_HAVE_DATA; + } else if (SPA_UNLIKELY(port->is_control)) { + spa_log_trace_fp(this->log, "%p: control %d", this, j); + } else { + remap = n_dst_datas++; + dst_datas[remap] = SPA_PTROFF(bd->data, + this->out_offset * port->stride, void); + max_out = SPA_MIN(max_out, bd->maxsize / port->stride); + + spa_log_trace_fp(this->log, "%p: output %d offs:%d %d->%d", this, + max_out, this->out_offset, + i * port->blocks + j, remap); + } + } + } + } + + + /* calculate how many samples at most we are going to consume. If we're + * draining, we consume as much as we can. Otherwise we consume what is + * left. */ + if (SPA_UNLIKELY(draining)) + n_samples = SPA_MIN(max_in, this->quantum_limit); + else { + n_samples = max_in - SPA_MIN(max_in, this->in_offset); + } + /* we only need to output the remaining samples */ + n_out = max_out - SPA_MIN(max_out, this->out_offset); + + resample_passthrough = resample_is_passthrough(this); + + /* calculate how many samples we are going to consume. */ + if (this->direction == SPA_DIRECTION_INPUT) { + if (!in_avail || this->drained) { + /* no input, ask for more, update rate-match first */ + resample_update_rate_match(this, resample_passthrough, n_out, 0); + spa_log_trace_fp(this->log, "%p: no input drained:%d", this, this->drained); + res |= this->drained ? SPA_STATUS_DRAINED : SPA_STATUS_NEED_DATA; + return res; + } + /* else figure out how much input samples we need to consume */ + n_samples = SPA_MIN(n_samples, + resample_get_in_size(this, resample_passthrough, n_out)); + } else { + /* in merge mode we consume one duration of samples */ + n_samples = SPA_MIN(n_samples, quant_samples); + flush_in = true; + } + + mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && + (ctrlport == NULL || ctrlport->ctrl == NULL); + + out_passthrough = dir->conv.is_passthrough; + if (in_passthrough && mix_passthrough && resample_passthrough) + out_passthrough = false; + + if (out_passthrough && dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_dst_datas[i] = dst_datas[dir->remap[i]]; + spa_log_trace_fp(this->log, "%p: output remap %d -> %d", this, i, dir->remap[i]); + } + dst_remap = (void **)remap_dst_datas; + } else { + dst_remap = (void **)dst_datas; + } + + dir = &this->dir[SPA_DIRECTION_INPUT]; + if (!in_passthrough) { + if (mix_passthrough && resample_passthrough && out_passthrough) + out_datas = (void **)dst_remap; + else + out_datas = (void **)this->tmp_datas[(tmp++) & 1]; + + if (dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_src_datas[i] = out_datas[dir->remap[i]]; + spa_log_trace_fp(this->log, "%p: input remap %d -> %d", this, dir->remap[i], i); + } + } else { + for (i = 0; i < dir->conv.n_channels; i++) + remap_src_datas[i] = out_datas[i]; + } + + spa_log_trace_fp(this->log, "%p: input convert %d", this, n_samples); + convert_process(&dir->conv, remap_src_datas, src_datas, n_samples); + } else { + if (dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_src_datas[dir->remap[i]] = (void *)src_datas[i]; + spa_log_trace_fp(this->log, "%p: input remap %d -> %d", this, dir->remap[i], i); + } + out_datas = (void **)remap_src_datas; + } else { + out_datas = (void **)src_datas; + } + } + + if (!mix_passthrough) { + in_datas = (const void**)out_datas; + if (resample_passthrough && out_passthrough) { + out_datas = (void **)dst_remap; + n_samples = SPA_MIN(n_samples, n_out); + } else { + out_datas = (void **)this->tmp_datas[(tmp++) & 1]; + } + spa_log_trace_fp(this->log, "%p: channelmix %d %d %d", this, n_samples, + resample_passthrough, out_passthrough); + if (ctrlport != NULL && ctrlport->ctrl != NULL) { + if (channelmix_process_control(this, ctrlport, out_datas, + in_datas, n_samples) == 1) { + ctrlio->status = SPA_STATUS_OK; + ctrlport->ctrl = NULL; + } + } else { + channelmix_process(&this->mix, out_datas, in_datas, n_samples); + } + } + if (!resample_passthrough) { + uint32_t in_len, out_len; + + in_datas = (const void**)out_datas; + if (out_passthrough) + out_datas = (void **)dst_remap; + else + out_datas = (void **)this->tmp_datas[(tmp++) & 1]; + + in_len = n_samples; + out_len = n_out; + resample_process(&this->resample, in_datas, &in_len, out_datas, &out_len); + spa_log_trace_fp(this->log, "%p: resample %d/%d -> %d/%d %d", this, + n_samples, in_len, n_out, out_len, out_passthrough); + this->in_offset += in_len; + n_samples = out_len; + } else { + n_samples = SPA_MIN(n_samples, n_out); + this->in_offset += n_samples; + } + this->out_offset += n_samples; + + if (!out_passthrough) { + dir = &this->dir[SPA_DIRECTION_OUTPUT]; + if (dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_dst_datas[dir->remap[i]] = out_datas[i]; + spa_log_trace_fp(this->log, "%p: output remap %d -> %d", this, i, dir->remap[i]); + } + in_datas = (const void**)remap_dst_datas; + } else { + in_datas = (const void**)out_datas; + } + spa_log_trace_fp(this->log, "%p: output convert %d", this, n_samples); + convert_process(&dir->conv, dst_datas, in_datas, n_samples); + } + + spa_log_trace_fp(this->log, "%d/%d %d/%d %d->%d", this->in_offset, max_in, + this->out_offset, max_out, n_samples, n_out); + + dir = &this->dir[SPA_DIRECTION_INPUT]; + if (SPA_LIKELY(this->in_offset >= max_in || flush_in)) { + /* return input buffers */ + for (i = 0; i < dir->n_ports; i++) { + port = GET_IN_PORT(this, i); + if (port->is_control) + continue; + if (SPA_UNLIKELY((io = port->io) == NULL)) + continue; + spa_log_trace_fp(this->log, "return: input %d %d", port->id, io->buffer_id); + if (!draining) + io->status = SPA_STATUS_NEED_DATA; + } + this->in_offset = 0; + max_in = 0; + res |= SPA_STATUS_NEED_DATA; + } + + dir = &this->dir[SPA_DIRECTION_OUTPUT]; + if (SPA_LIKELY(n_samples > 0 && (this->out_offset >= max_out || flush_out))) { + /* queue output buffers */ + for (i = 0; i < dir->n_ports; i++) { + port = GET_OUT_PORT(this, i); + if (SPA_UNLIKELY(port->is_monitor || port->is_control)) + continue; + if (SPA_UNLIKELY((io = port->io) == NULL)) + continue; + + if (SPA_UNLIKELY((buf = out_bufs[i]) == NULL)) + continue; + + dequeue_buffer(this, port, buf); + + for (j = 0; j < port->blocks; j++) { + bd = &buf->buf->datas[j]; + bd->chunk->size = this->out_offset * port->stride; + bd->chunk->stride = port->stride; + SPA_FLAG_UPDATE(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY, in_empty); + spa_log_trace_fp(this->log, "out: offs:%d stride:%d size:%d", + this->out_offset, port->stride, bd->chunk->size); + } + io->status = SPA_STATUS_HAVE_DATA; + io->buffer_id = buf->id; + } + res |= SPA_STATUS_HAVE_DATA; + this->drained = draining; + this->out_offset = 0; + } + else if (n_samples == 0 && this->resample_peaks) { + for (i = 0; i < dir->n_ports; i++) { + port = GET_OUT_PORT(this, i); + if (port->is_monitor || port->is_control) + continue; + if (SPA_UNLIKELY((io = port->io) == NULL)) + continue; + + io->status = SPA_STATUS_HAVE_DATA; + io->buffer_id = SPA_ID_INVALID; + res |= SPA_STATUS_HAVE_DATA; + spa_log_trace_fp(this->log, "%p: no output buffer", this); + } + } + if (resample_update_rate_match(this, resample_passthrough, + max_out - this->out_offset, + max_in - this->in_offset) > 0) + res |= SPA_STATUS_NEED_DATA; + + return res; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .add_port = impl_node_add_port, + .remove_port = impl_node_remove_port, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .port_reuse_buffer = impl_node_port_reuse_buffer, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this; + uint32_t i; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + this = (struct impl *) handle; + + for (i = 0; i < MAX_PORTS; i++) + free(this->dir[SPA_DIRECTION_INPUT].ports[i]); + for (i = 0; i < MAX_PORTS; i++) + free(this->dir[SPA_DIRECTION_OUTPUT].ports[i]); + free(this->empty); + free(this->scratch); + free(this->tmp[0]); + free(this->tmp[1]); + + if (this->resample.free) + resample_free(&this->resample); + if (this->dir[0].conv.free) + convert_free(&this->dir[0].conv); + if (this->dir[1].conv.free) + convert_free(&this->dir[1].conv); + + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static uint32_t channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channel[i].name; i++) { + if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) + return spa_type_audio_channel[i].type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static inline uint32_t parse_position(uint32_t *pos, const char *val, size_t len) +{ + struct spa_json it[2]; + char v[256]; + uint32_t i = 0; + + spa_json_init(&it[0], val, len); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], val, len); + + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + i < SPA_AUDIO_MAX_CHANNELS) { + pos[i++] = channel_from_name(v); + } + return i; +} + + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + uint32_t i; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(this->log, log_topic); + + this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); + if (this->cpu) { + this->cpu_flags = spa_cpu_get_flags(this->cpu); + this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); + } + + props_reset(&this->props); + + this->mix.options = CHANNELMIX_OPTION_UPMIX | CHANNELMIX_OPTION_MIX_LFE; + this->mix.upmix = CHANNELMIX_UPMIX_PSD; + this->mix.log = this->log; + this->mix.lfe_cutoff = 150.0f; + this->mix.fc_cutoff = 12000.0f; + this->mix.rear_delay = 12.0f; + this->mix.widen = 0.0f; + + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + if (spa_streq(k, "clock.quantum-limit")) + spa_atou32(s, &this->quantum_limit, 0); + else if (spa_streq(k, "resample.peaks")) + this->resample_peaks = spa_atob(s); + else if (spa_streq(k, "resample.prefill")) + SPA_FLAG_UPDATE(this->resample.options, + RESAMPLE_OPTION_PREFILL, spa_atob(s)); + else if (spa_streq(k, "factory.mode")) { + if (spa_streq(s, "merge")) + this->direction = SPA_DIRECTION_OUTPUT; + else + this->direction = SPA_DIRECTION_INPUT; + } + else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { + if (s != NULL) + this->props.n_channels = parse_position(this->props.channel_map, s, strlen(s)); + } + else + audioconvert_set_param(this, k, s); + } + + this->props.channel.n_volumes = this->props.n_channels; + this->props.soft.n_volumes = this->props.n_channels; + this->props.monitor.n_volumes = this->props.n_channels; + + this->dir[SPA_DIRECTION_INPUT].direction = SPA_DIRECTION_INPUT; + this->dir[SPA_DIRECTION_INPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + this->dir[SPA_DIRECTION_OUTPUT].direction = SPA_DIRECTION_OUTPUT; + this->dir[SPA_DIRECTION_OUTPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + spa_hook_list_init(&this->hooks); + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_input_ports = MAX_PORTS; + this->info.max_output_ports = MAX_PORTS; + this->info.flags = SPA_NODE_FLAG_RT | + SPA_NODE_FLAG_IN_PORT_CONFIG | + SPA_NODE_FLAG_OUT_PORT_CONFIG | + SPA_NODE_FLAG_NEED_CONFIGURE; + this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); + this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); + this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = N_NODE_PARAMS; + + this->volume.cpu_flags = this->cpu_flags; + volume_init(&this->volume); + + this->rate_scale = 1.0; + + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_INPUT, false, false, NULL); + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_OUTPUT, false, false, NULL); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +const struct spa_handle_factory spa_audioconvert_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_AUDIO_CONVERT, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/audioconvert/benchmark-fmt-ops.c b/spa/plugins/audioconvert/benchmark-fmt-ops.c new file mode 100644 index 0000000..2a0d4e8 --- /dev/null +++ b/spa/plugins/audioconvert/benchmark-fmt-ops.c @@ -0,0 +1,323 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include "test-helper.h" +#include "fmt-ops.h" + +static uint32_t cpu_flags; + +typedef void (*convert_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples); + +struct stats { + uint32_t n_samples; + uint32_t n_channels; + uint64_t perf; + const char *name; + const char *impl; +}; + +#define MAX_SAMPLES 4096 +#define MAX_CHANNELS 11 + +#define MAX_COUNT 100 + +static uint8_t samp_in[MAX_SAMPLES * MAX_CHANNELS * 4]; +static uint8_t samp_out[MAX_SAMPLES * MAX_CHANNELS * 4]; + +static const int sample_sizes[] = { 0, 1, 128, 513, 4096 }; +static const int channel_counts[] = { 1, 2, 4, 6, 8, 11 }; + +#define MAX_RESULTS SPA_N_ELEMENTS(sample_sizes) * SPA_N_ELEMENTS(channel_counts) * 70 + +static uint32_t n_results = 0; +static struct stats results[MAX_RESULTS]; + +static void run_test1(const char *name, const char *impl, bool in_packed, bool out_packed, + convert_func_t func, int n_channels, int n_samples) +{ + int i, j; + const void *ip[n_channels]; + void *op[n_channels]; + struct timespec ts; + uint64_t count, t1, t2; + struct convert conv; + + conv.n_channels = n_channels; + + for (j = 0; j < n_channels; j++) { + ip[j] = &samp_in[j * n_samples * 4]; + op[j] = &samp_out[j * n_samples * 4]; + } + + clock_gettime(CLOCK_MONOTONIC, &ts); + t1 = SPA_TIMESPEC_TO_NSEC(&ts); + + count = 0; + for (i = 0; i < MAX_COUNT; i++) { + func(&conv, op, ip, n_samples); + count++; + } + clock_gettime(CLOCK_MONOTONIC, &ts); + t2 = SPA_TIMESPEC_TO_NSEC(&ts); + + spa_assert(n_results < MAX_RESULTS); + + results[n_results++] = (struct stats) { + .n_samples = n_samples, + .n_channels = n_channels, + .perf = count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1), + .name = name, + .impl = impl + }; +} + +static void run_testc(const char *name, const char *impl, bool in_packed, bool out_packed, convert_func_t func, + int channel_count) +{ + SPA_FOR_EACH_ELEMENT_VAR(sample_sizes, s) { + run_test1(name, impl, in_packed, out_packed, func, channel_count, + (*s + (channel_count -1)) / channel_count); + } +} + +static void run_test(const char *name, const char *impl, bool in_packed, bool out_packed, convert_func_t func) +{ + SPA_FOR_EACH_ELEMENT_VAR(sample_sizes, s) { + SPA_FOR_EACH_ELEMENT_VAR(channel_counts, c) { + run_test1(name, impl, in_packed, out_packed, func, *c, (*s + (*c -1)) / *c); + } + } +} + +static void test_f32_u8(void) +{ + run_test("test_f32_u8", "c", true, true, conv_f32_to_u8_c); + run_test("test_f32d_u8", "c", false, true, conv_f32d_to_u8_c); + run_test("test_f32_u8d", "c", true, false, conv_f32_to_u8d_c); + run_test("test_f32d_u8d", "c", false, false, conv_f32d_to_u8d_c); +} + +static void test_u8_f32(void) +{ + run_test("test_u8_f32", "c", true, true, conv_u8_to_f32_c); + run_test("test_u8d_f32", "c", false, true, conv_u8d_to_f32_c); + run_test("test_u8_f32d", "c", true, false, conv_u8_to_f32d_c); + run_test("test_u8d_f32d", "c", false, false, conv_u8d_to_f32d_c); +} + +static void test_f32_s16(void) +{ + run_test("test_f32_s16", "c", true, true, conv_f32_to_s16_c); + run_test("test_f32d_s16", "c", false, true, conv_f32d_to_s16_c); +#if defined (HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f32d_s16", "sse2", false, true, conv_f32d_to_s16_sse2); + run_testc("test_f32d_s16_2", "sse2", false, true, conv_f32d_to_s16_2_sse2, 2); + } +#endif +#if defined (HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_f32d_s16", "avx2", false, true, conv_f32d_to_s16_avx2); + run_testc("test_f32d_s16_2", "avx2", false, true, conv_f32d_to_s16_2_avx2, 2); + run_testc("test_f32d_s16_4", "avx2", false, true, conv_f32d_to_s16_4_avx2, 4); + } +#endif + run_test("test_f32_s16d", "c", true, false, conv_f32_to_s16d_c); + run_test("test_f32d_s16d", "c", false, false, conv_f32d_to_s16d_c); +} + +static void test_s16_f32(void) +{ + run_test("test_s16_f32", "c", true, true, conv_s16_to_f32_c); + run_test("test_s16d_f32", "c", false, true, conv_s16d_to_f32_c); + run_test("test_s16_f32d", "c", true, false, conv_s16_to_f32d_c); +#if defined (HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_s16_f32d", "sse2", true, false, conv_s16_to_f32d_sse2); + run_testc("test_s16_f32d_2", "sse2", true, false, conv_s16_to_f32d_2_sse2, 2); + } +#endif +#if defined (HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s16_f32d", "avx2", true, false, conv_s16_to_f32d_avx2); + run_testc("test_s16_f32d_2", "avx2", true, false, conv_s16_to_f32d_2_avx2, 2); + } +#endif + run_test("test_s16d_f32d", "c", false, false, conv_s16d_to_f32d_c); +} + +static void test_f32_s32(void) +{ + run_test("test_f32_s32", "c", true, true, conv_f32_to_s32_c); + run_test("test_f32d_s32", "c", false, true, conv_f32d_to_s32_c); +#if defined (HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f32d_s32", "sse2", false, true, conv_f32d_to_s32_sse2); + } +#endif +#if defined (HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_f32d_s32", "avx2", false, true, conv_f32d_to_s32_avx2); + } +#endif + run_test("test_f32_s32d", "c", true, false, conv_f32_to_s32d_c); + run_test("test_f32d_s32d", "c", false, false, conv_f32d_to_s32d_c); +} + +static void test_s32_f32(void) +{ + run_test("test_s32_f32", "c", true, true, conv_s32_to_f32_c); + run_test("test_s32d_f32", "c", false, true, conv_s32d_to_f32_c); +#if defined (HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_s32_f32d", "sse2", true, false, conv_s32_to_f32d_sse2); + } +#endif +#if defined (HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s32_f32d", "avx2", true, false, conv_s32_to_f32d_avx2); + } +#endif + run_test("test_s32_f32d", "c", true, false, conv_s32_to_f32d_c); + run_test("test_s32d_f32d", "c", false, false, conv_s32d_to_f32d_c); +} + +static void test_f32_s24(void) +{ + run_test("test_f32_s24", "c", true, true, conv_f32_to_s24_c); + run_test("test_f32d_s24", "c", false, true, conv_f32d_to_s24_c); + run_test("test_f32_s24d", "c", true, false, conv_f32_to_s24d_c); + run_test("test_f32d_s24d", "c", false, false, conv_f32d_to_s24d_c); +} + +static void test_s24_f32(void) +{ + run_test("test_s24_f32", "c", true, true, conv_s24_to_f32_c); + run_test("test_s24d_f32", "c", false, true, conv_s24d_to_f32_c); + run_test("test_s24_f32d", "c", true, false, conv_s24_to_f32d_c); +#if defined (HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_s24_f32d", "sse2", true, false, conv_s24_to_f32d_sse2); + } +#endif +#if defined (HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s24_f32d", "avx2", true, false, conv_s24_to_f32d_avx2); + } +#endif +#if defined (HAVE_SSSE3) + if (cpu_flags & SPA_CPU_FLAG_SSSE3) { + run_test("test_s24_f32d", "ssse3", true, false, conv_s24_to_f32d_ssse3); + } +#endif +#if defined (HAVE_SSE41) + if (cpu_flags & SPA_CPU_FLAG_SSE41) { + run_test("test_s24_f32d", "sse41", true, false, conv_s24_to_f32d_sse41); + } +#endif + run_test("test_s24d_f32d", "c", false, false, conv_s24d_to_f32d_c); +} + +static void test_f32_s24_32(void) +{ + run_test("test_f32_s24_32", "c", true, true, conv_f32_to_s24_32_c); + run_test("test_f32d_s24_32", "c", false, true, conv_f32d_to_s24_32_c); + run_test("test_f32_s24_32d", "c", true, false, conv_f32_to_s24_32d_c); + run_test("test_f32d_s24_32d", "c", false, false, conv_f32d_to_s24_32d_c); +} + +static void test_s24_32_f32(void) +{ + run_test("test_s24_32_f32", "c", true, true, conv_s24_32_to_f32_c); + run_test("test_s24_32d_f32", "c", false, true, conv_s24_32d_to_f32_c); + run_test("test_s24_32_f32d", "c", true, false, conv_s24_32_to_f32d_c); + run_test("test_s24_32d_f32d", "c", false, false, conv_s24_32d_to_f32d_c); +} + +static void test_interleave(void) +{ + run_test("test_8d_to_8", "c", false, true, conv_8d_to_8_c); + run_test("test_16d_to_16", "c", false, true, conv_16d_to_16_c); + run_test("test_24d_to_24", "c", false, true, conv_24d_to_24_c); + run_test("test_32d_to_32", "c", false, true, conv_32d_to_32_c); +} + +static void test_deinterleave(void) +{ + run_test("test_8_to_8d", "c", true, false, conv_8_to_8d_c); + run_test("test_16_to_16d", "c", true, false, conv_16_to_16d_c); + run_test("test_24_to_24d", "c", true, false, conv_24_to_24d_c); + run_test("test_32_to_32d", "c", true, false, conv_32_to_32d_c); +} + +static int compare_func(const void *_a, const void *_b) +{ + const struct stats *a = _a, *b = _b; + int diff; + if ((diff = strcmp(a->name, b->name)) != 0) return diff; + if ((diff = a->n_samples - b->n_samples) != 0) return diff; + if ((diff = a->n_channels - b->n_channels) != 0) return diff; + if ((diff = b->perf - a->perf) != 0) return diff; + return 0; +} + +int main(int argc, char *argv[]) +{ + uint32_t i; + + cpu_flags = get_cpu_flags(); + printf("got get CPU flags %d\n", cpu_flags); + + test_f32_u8(); + test_u8_f32(); + test_f32_s16(); + test_s16_f32(); + test_f32_s32(); + test_s32_f32(); + test_f32_s24(); + test_s24_f32(); + test_f32_s24_32(); + test_s24_32_f32(); + test_interleave(); + test_deinterleave(); + + qsort(results, n_results, sizeof(struct stats), compare_func); + + for (i = 0; i < n_results; i++) { + struct stats *s = &results[i]; + fprintf(stderr, "%-12."PRIu64" \t%-32.32s %s \t samples %d, channels %d\n", + s->perf, s->name, s->impl, s->n_samples, s->n_channels); + } + return 0; +} diff --git a/spa/plugins/audioconvert/benchmark-resample.c b/spa/plugins/audioconvert/benchmark-resample.c new file mode 100644 index 0000000..597f9ac --- /dev/null +++ b/spa/plugins/audioconvert/benchmark-resample.c @@ -0,0 +1,204 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include "test-helper.h" +#include "resample.h" + +#define MAX_SAMPLES 4096 +#define MAX_CHANNELS 11 + +#define MAX_COUNT 200 + +static uint32_t cpu_flags; + +struct stats { + uint32_t in_rate; + uint32_t out_rate; + uint32_t n_samples; + uint32_t n_channels; + uint64_t perf; + const char *name; + const char *impl; +}; + +static float samp_in[MAX_SAMPLES * MAX_CHANNELS]; +static float samp_out[MAX_SAMPLES * MAX_CHANNELS]; + +static const int sample_sizes[] = { 0, 1, 128, 513, 4096 }; +static const int in_rates[] = { 44100, 44100, 48000, 96000, 22050, 96000 }; +static const int out_rates[] = { 44100, 48000, 44100, 48000, 48000, 44100 }; + + +#define MAX_RESAMPLER 5 +#define MAX_SIZES SPA_N_ELEMENTS(sample_sizes) +#define MAX_RATES SPA_N_ELEMENTS(in_rates) +#define MAX_RESULTS MAX_RESAMPLER * MAX_SIZES * MAX_RATES + +static uint32_t n_results = 0; +static struct stats results[MAX_RESULTS]; + +static void run_test1(const char *name, const char *impl, struct resample *r, int n_samples) +{ + uint32_t i, j; + const void *ip[MAX_CHANNELS]; + void *op[MAX_CHANNELS]; + struct timespec ts; + uint64_t count, t1, t2; + uint32_t in_len, out_len; + + for (j = 0; j < r->channels; j++) { + ip[j] = &samp_in[j * MAX_SAMPLES]; + op[j] = &samp_out[j * MAX_SAMPLES]; + } + + clock_gettime(CLOCK_MONOTONIC, &ts); + t1 = SPA_TIMESPEC_TO_NSEC(&ts); + + count = 0; + for (i = 0; i < MAX_COUNT; i++) { + in_len = n_samples; + out_len = MAX_SAMPLES; + resample_process(r, ip, &in_len, op, &out_len); + count++; + } + clock_gettime(CLOCK_MONOTONIC, &ts); + t2 = SPA_TIMESPEC_TO_NSEC(&ts); + + spa_assert(n_results < MAX_RESULTS); + + results[n_results++] = (struct stats) { + .in_rate = r->i_rate, + .out_rate = r->o_rate, + .n_samples = n_samples, + .n_channels = r->channels, + .perf = count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1), + .name = name, + .impl = impl + }; +} + +static void run_test(const char *name, const char *impl, struct resample *r) +{ + size_t i; + for (i = 0; i < SPA_N_ELEMENTS(sample_sizes); i++) + run_test1(name, impl, r, sample_sizes[i]); +} + +static int compare_func(const void *_a, const void *_b) +{ + const struct stats *a = _a, *b = _b; + int diff; + + if ((diff = a->in_rate - b->in_rate) != 0) return diff; + if ((diff = a->out_rate - b->out_rate) != 0) return diff; + if ((diff = a->n_samples - b->n_samples) != 0) return diff; + if ((diff = a->n_channels - b->n_channels) != 0) return diff; + if ((diff = b->perf - a->perf) != 0) return diff; + return 0; +} + +int main(int argc, char *argv[]) +{ + struct resample r; + uint32_t i; + + cpu_flags = get_cpu_flags(); + printf("got get CPU flags %d\n", cpu_flags); + + for (i = 0; i < SPA_N_ELEMENTS(in_rates); i++) { + spa_zero(r); + r.channels = 2; + r.cpu_flags = 0; + r.i_rate = in_rates[i]; + r.o_rate = out_rates[i]; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + run_test("native", "c", &r); + resample_free(&r); + } +#if defined (HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + for (i = 0; i < SPA_N_ELEMENTS(in_rates); i++) { + spa_zero(r); + r.channels = 2; + r.cpu_flags = SPA_CPU_FLAG_SSE; + r.i_rate = in_rates[i]; + r.o_rate = out_rates[i]; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + run_test("native", "sse", &r); + resample_free(&r); + } + } +#endif +#if defined (HAVE_SSSE3) + if (cpu_flags & SPA_CPU_FLAG_SSSE3) { + for (i = 0; i < SPA_N_ELEMENTS(in_rates); i++) { + spa_zero(r); + r.channels = 2; + r.cpu_flags = SPA_CPU_FLAG_SSSE3 | SPA_CPU_FLAG_SLOW_UNALIGNED; + r.i_rate = in_rates[i]; + r.o_rate = out_rates[i]; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + run_test("native", "ssse3", &r); + resample_free(&r); + } + } +#endif +#if defined (HAVE_AVX) && defined(HAVE_FMA) + if (SPA_FLAG_IS_SET(cpu_flags, SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_FMA3)) { + for (i = 0; i < SPA_N_ELEMENTS(in_rates); i++) { + spa_zero(r); + r.channels = 2; + r.cpu_flags = SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_FMA3; + r.i_rate = in_rates[i]; + r.o_rate = out_rates[i]; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + run_test("native", "avx", &r); + resample_free(&r); + } + } +#endif + + qsort(results, n_results, sizeof(struct stats), compare_func); + + for (i = 0; i < n_results; i++) { + struct stats *s = &results[i]; + fprintf(stderr, "%-12."PRIu64" \t%-16.16s %s \t%d->%d samples %d, channels %d\n", + s->perf, s->name, s->impl, s->in_rate, s->out_rate, + s->n_samples, s->n_channels); + } + return 0; +} diff --git a/spa/plugins/audioconvert/biquad.c b/spa/plugins/audioconvert/biquad.c new file mode 100644 index 0000000..409b673 --- /dev/null +++ b/spa/plugins/audioconvert/biquad.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Copyright (C) 2010 Google Inc. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE.WEBKIT file. + */ + + +#include <spa/utils/defs.h> + +#include <math.h> +#include "biquad.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 +#endif + +static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, + double a0, double a1, double a2) +{ + double a0_inv = 1 / a0; + bq->b0 = b0 * a0_inv; + bq->b1 = b1 * a0_inv; + bq->b2 = b2 * a0_inv; + bq->a1 = a1 * a0_inv; + bq->a2 = a2 * a0_inv; +} + +static void biquad_lowpass(struct biquad *bq, double cutoff) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = SPA_CLAMP(cutoff, 0.0, 1.0); + + if (cutoff >= 1.0) { + /* When cutoff is 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } else if (cutoff > 0) { + /* Compute biquad coefficients for lowpass filter */ + double theta = M_PI * cutoff; + double sn = 0.5 * M_SQRT2 * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma_coeff = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta - gamma_coeff); + + double b0 = 2 * alpha; + double b1 = 2 * 2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma_coeff; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); + } else { + /* When cutoff is zero, nothing gets through the filter, so set + * coefficients up correctly. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } +} + +static void biquad_highpass(struct biquad *bq, double cutoff) +{ + /* Limit cutoff to 0 to 1. */ + cutoff = SPA_CLAMP(cutoff, 0.0, 1.0); + + if (cutoff >= 1.0) { + /* The z-transform is 0. */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + } else if (cutoff > 0) { + /* Compute biquad coefficients for highpass filter */ + double theta = M_PI * cutoff; + double sn = 0.5 * M_SQRT2 * sin(theta); + double beta = 0.5 * (1 - sn) / (1 + sn); + double gamma_coeff = (0.5 + beta) * cos(theta); + double alpha = 0.25 * (0.5 + beta + gamma_coeff); + + double b0 = 2 * alpha; + double b1 = 2 * -2 * alpha; + double b2 = 2 * alpha; + double a1 = 2 * -gamma_coeff; + double a2 = 2 * beta; + + set_coefficient(bq, b0, b1, b2, 1, a1, a2); + } else { + /* When cutoff is zero, we need to be careful because the above + * gives a quadratic divided by the same quadratic, with poles + * and zeros on the unit circle in the same place. When cutoff + * is zero, the z-transform is 1. + */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + } +} + +void biquad_set(struct biquad *bq, enum biquad_type type, double freq) +{ + + switch (type) { + case BQ_LOWPASS: + biquad_lowpass(bq, freq); + break; + case BQ_HIGHPASS: + biquad_highpass(bq, freq); + break; + } +} diff --git a/spa/plugins/audioconvert/biquad.h b/spa/plugins/audioconvert/biquad.h new file mode 100644 index 0000000..8b7eccc --- /dev/null +++ b/spa/plugins/audioconvert/biquad.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef BIQUAD_H_ +#define BIQUAD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) + * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs + * are stored in x1 and x2, and the previous two outputs are stored in y1 and + * y2. + * + * We use double during the coefficients calculation for better accuracy, but + * float is used during the actual filtering for faster computation. + */ +struct biquad { + float b0, b1, b2; + float a1, a2; +}; + +/* The type of the biquad filters */ +enum biquad_type { + BQ_LOWPASS, + BQ_HIGHPASS, +}; + +/* Initialize a biquad filter parameters from its type and parameters. + * Args: + * bq - The biquad filter we want to set. + * type - The type of the biquad filter. + * frequency - The value should be in the range [0, 1]. It is relative to + * half of the sampling rate. + */ +void biquad_set(struct biquad *bq, enum biquad_type type, double freq); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* BIQUAD_H_ */ diff --git a/spa/plugins/audioconvert/channelmix-ops-c.c b/spa/plugins/audioconvert/channelmix-ops-c.c new file mode 100644 index 0000000..f12f35f --- /dev/null +++ b/spa/plugins/audioconvert/channelmix-ops-c.c @@ -0,0 +1,533 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "channelmix-ops.h" + +static inline void clear_c(float *d, uint32_t n_samples) +{ + memset(d, 0, n_samples * sizeof(float)); +} + +static inline void copy_c(float *d, const float *s, uint32_t n_samples) +{ + spa_memcpy(d, s, n_samples * sizeof(float)); +} + +static inline void vol_c(float *d, const float *s, float vol, uint32_t n_samples) +{ + uint32_t n; + if (vol == 0.0f) { + clear_c(d, n_samples); + } else if (vol == 1.0f) { + copy_c(d, s, n_samples); + } else { + for (n = 0; n < n_samples; n++) + d[n] = s[n] * vol; + } +} +static inline void conv_c(float *d, const float **s, float *c, uint32_t n_c, uint32_t n_samples) +{ + uint32_t n, j; + for (n = 0; n < n_samples; n++) { + float sum = 0.0f; + for (j = 0; j < n_c; j++) + sum += s[j][n] * c[j]; + d[n] = sum; + } +} + +static inline void avg_c(float *d, const float *s0, const float *s1, uint32_t n_samples) +{ + uint32_t n; + for (n = 0; n < n_samples; n++) + d[n] = (s0[n] + s1[n]) * 0.5f; +} + +static inline void sub_c(float *d, const float *s0, const float *s1, uint32_t n_samples) +{ + uint32_t n; + for (n = 0; n < n_samples; n++) + d[n] = s0[n] - s1[n]; +} + +void +channelmix_copy_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + for (i = 0; i < n_dst; i++) + vol_c(d[i], s[i], mix->matrix[i][i], n_samples); +} + +#define _M(ch) (1UL << SPA_AUDIO_CHANNEL_ ## ch) + +void +channelmix_f32_n_m_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, j, n_dst = mix->dst_chan, n_src = mix->src_chan; + float **d = (float **) dst; + const float **s = (const float **) src; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_COPY)) { + uint32_t copy = SPA_MIN(n_dst, n_src); + for (i = 0; i < copy; i++) + copy_c(d[i], s[i], n_samples); + for (; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + for (i = 0; i < n_dst; i++) { + float *di = d[i]; + float mj[n_src]; + const float *sj[n_src]; + uint32_t n_j = 0; + + for (j = 0; j < n_src; j++) { + if (mix->matrix[i][j] == 0.0f) + continue; + mj[n_j] = mix->matrix[i][j]; + sj[n_j++] = s[j]; + } + if (n_j == 0) { + clear_c(di, n_samples); + } else if (n_j == 1) { + lr4_process(&mix->lr4[i], di, sj[0], mj[0], n_samples); + } else { + conv_c(di, sj, mj, n_j, n_samples); + lr4_process(&mix->lr4[i], di, di, 1.0f, n_samples); + } + } + } +} + +#define MASK_MONO _M(FC)|_M(MONO)|_M(UNKNOWN) +#define MASK_STEREO _M(FL)|_M(FR)|_M(UNKNOWN) + +void +channelmix_f32_1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + float **d = (float **)dst; + const float **s = (const float **)src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][0]; + + vol_c(d[0], s[0], v0, n_samples); + vol_c(d[1], s[0], v1, n_samples); +} + +void +channelmix_f32_2_1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t n; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[0][1]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + clear_c(d[0], n_samples); + } else if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_EQUAL)) { + for (n = 0; n < n_samples; n++) + d[0][n] = (s[0][n] + s[1][n]) * v0; + } + else { + for (n = 0; n < n_samples; n++) + d[0][n] = s[0][n] * v0 + s[1][n] * v1; + } +} + +void +channelmix_f32_4_1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t n; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[0][1]; + const float v2 = mix->matrix[0][2]; + const float v3 = mix->matrix[0][3]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + clear_c(d[0], n_samples); + } + else if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_EQUAL)) { + for (n = 0; n < n_samples; n++) + d[0][n] = (s[0][n] + s[1][n] + s[2][n] + s[3][n]) * v0; + } + else { + for (n = 0; n < n_samples; n++) + d[0][n] = s[0][n] * v0 + s[1][n] * v1 + + s[2][n] * v2 + s[3][n] * v3; + } +} + +#define MASK_QUAD _M(FL)|_M(FR)|_M(RL)|_M(RR)|_M(UNKNOWN) + +void +channelmix_f32_2_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float v2 = mix->matrix[2][0]; + const float v3 = mix->matrix[3][1]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + vol_c(d[0], s[0], v0, n_samples); + vol_c(d[1], s[1], v1, n_samples); + if (mix->upmix != CHANNELMIX_UPMIX_PSD) { + vol_c(d[2], s[0], v2, n_samples); + vol_c(d[3], s[1], v3, n_samples); + } else { + sub_c(d[2], s[0], s[1], n_samples); + + delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[3], d[2], -v3, n_samples); + delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[2], d[2], v2, n_samples); + } + } +} + +#define MASK_3_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE) +void +channelmix_f32_2_3p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float v2 = (mix->matrix[2][0] + mix->matrix[2][1]) * 0.5f; + const float v3 = (mix->matrix[3][0] + mix->matrix[3][1]) * 0.5f; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + if (mix->widen == 0.0f) { + vol_c(d[0], s[0], v0, n_samples); + vol_c(d[1], s[1], v1, n_samples); + avg_c(d[2], s[0], s[1], n_samples); + } else { + for (n = 0; n < n_samples; n++) { + float c = s[0][n] + s[1][n]; + float w = c * mix->widen; + d[0][n] = (s[0][n] - w) * v0; + d[1][n] = (s[1][n] - w) * v1; + d[2][n] = c * 0.5f; + } + } + lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); + lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + } +} + +#define MASK_5_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) +void +channelmix_f32_2_5p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v4 = mix->matrix[4][0]; + const float v5 = mix->matrix[5][1]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + channelmix_f32_2_3p1_c(mix, dst, src, n_samples); + + if (mix->upmix != CHANNELMIX_UPMIX_PSD) { + vol_c(d[4], s[0], v4, n_samples); + vol_c(d[5], s[1], v5, n_samples); + } else { + sub_c(d[4], s[0], s[1], n_samples); + + delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[5], d[4], -v5, n_samples); + delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[4], d[4], v4, n_samples); + } + } +} + +void +channelmix_f32_2_7p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v4 = mix->matrix[4][0]; + const float v5 = mix->matrix[5][1]; + const float v6 = mix->matrix[6][0]; + const float v7 = mix->matrix[7][1]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + channelmix_f32_2_3p1_c(mix, dst, src, n_samples); + + vol_c(d[4], s[0], v4, n_samples); + vol_c(d[5], s[1], v5, n_samples); + + if (mix->upmix != CHANNELMIX_UPMIX_PSD) { + vol_c(d[6], s[0], v6, n_samples); + vol_c(d[7], s[1], v7, n_samples); + } else { + sub_c(d[6], s[0], s[1], n_samples); + + delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[7], d[6], -v7, n_samples); + delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[6], d[6], v6, n_samples); + } + } +} + +/* FL+FR+FC+LFE -> FL+FR */ +void +channelmix_f32_3p1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t n; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; + const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + clear_c(d[0], n_samples); + clear_c(d[1], n_samples); + } + else { + for (n = 0; n < n_samples; n++) { + const float ctr = clev * s[2][n] + llev * s[3][n]; + d[0][n] = s[0][n] * v0 + ctr; + d[1][n] = s[1][n] * v1 + ctr; + } + } +} + +/* FL+FR+FC+LFE+SL+SR -> FL+FR */ +void +channelmix_f32_5p1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t n; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; + const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; + const float slev0 = mix->matrix[0][4]; + const float slev1 = mix->matrix[1][5]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + clear_c(d[0], n_samples); + clear_c(d[1], n_samples); + } + else { + for (n = 0; n < n_samples; n++) { + const float ctr = clev * s[2][n] + llev * s[3][n]; + d[0][n] = s[0][n] * v0 + ctr + (slev0 * s[4][n]); + d[1][n] = s[1][n] * v1 + ctr + (slev1 * s[5][n]); + } + } +} + +/* FL+FR+FC+LFE+SL+SR -> FL+FR+FC+LFE*/ +void +channelmix_f32_5p1_3p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n, n_dst = mix->dst_chan; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float v2 = mix->matrix[2][2]; + const float v3 = mix->matrix[3][3]; + const float v4 = mix->matrix[0][4]; + const float v5 = mix->matrix[1][5]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + for (n = 0; n < n_samples; n++) { + d[0][n] = s[0][n] * v0 + s[4][n] * v4; + d[1][n] = s[1][n] * v1 + s[5][n] * v5; + } + vol_c(d[2], s[2], v2, n_samples); + vol_c(d[3], s[3], v3, n_samples); + } +} + +/* FL+FR+FC+LFE+SL+SR -> FL+FR+RL+RR*/ +void +channelmix_f32_5p1_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v4 = mix->matrix[2][4]; + const float v5 = mix->matrix[3][5]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + channelmix_f32_3p1_2_c(mix, dst, src, n_samples); + + vol_c(d[2], s[4], v4, n_samples); + vol_c(d[3], s[5], v5, n_samples); + } +} + +#define MASK_7_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) + +/* FL+FR+FC+LFE+SL+SR+RL+RR -> FL+FR */ +void +channelmix_f32_7p1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t n; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; + const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; + const float slev0 = mix->matrix[0][4]; + const float slev1 = mix->matrix[1][5]; + const float rlev0 = mix->matrix[0][6]; + const float rlev1 = mix->matrix[1][7]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + clear_c(d[0], n_samples); + clear_c(d[1], n_samples); + } + else { + for (n = 0; n < n_samples; n++) { + const float ctr = clev * s[2][n] + llev * s[3][n]; + d[0][n] = s[0][n] * v0 + ctr + s[4][n] * slev0 + s[6][n] * rlev0; + d[1][n] = s[1][n] * v1 + ctr + s[5][n] * slev1 + s[7][n] * rlev1; + } + } +} + +/* FL+FR+FC+LFE+SL+SR+RL+RR -> FL+FR+FC+LFE*/ +void +channelmix_f32_7p1_3p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n, n_dst = mix->dst_chan; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float v2 = mix->matrix[2][2]; + const float v3 = mix->matrix[3][3]; + const float v4 = (mix->matrix[0][4] + mix->matrix[0][6]) * 0.5f; + const float v5 = (mix->matrix[1][5] + mix->matrix[1][7]) * 0.5f; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + for (n = 0; n < n_samples; n++) { + d[0][n] = s[0][n] * v0 + (s[4][n] + s[6][n]) * v4; + d[1][n] = s[1][n] * v1 + (s[5][n] + s[7][n]) * v5; + } + vol_c(d[2], s[2], v2, n_samples); + vol_c(d[3], s[3], v3, n_samples); + } +} + +/* FL+FR+FC+LFE+SL+SR+RL+RR -> FL+FR+RL+RR*/ +void +channelmix_f32_7p1_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n, n_dst = mix->dst_chan; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; + const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; + const float slev0 = mix->matrix[2][4]; + const float slev1 = mix->matrix[3][5]; + const float rlev0 = mix->matrix[2][6]; + const float rlev1 = mix->matrix[3][7]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + for (n = 0; n < n_samples; n++) { + const float ctr = s[2][n] * clev + s[3][n] * llev; + const float sl = s[4][n] * slev0; + const float sr = s[5][n] * slev1; + d[0][n] = s[0][n] * v0 + ctr + sl; + d[1][n] = s[1][n] * v1 + ctr + sr; + d[2][n] = s[6][n] * rlev0 + sl; + d[3][n] = s[7][n] * rlev1 + sr; + } + } +} diff --git a/spa/plugins/audioconvert/channelmix-ops-sse.c b/spa/plugins/audioconvert/channelmix-ops-sse.c new file mode 100644 index 0000000..8311fb4 --- /dev/null +++ b/spa/plugins/audioconvert/channelmix-ops-sse.c @@ -0,0 +1,522 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "channelmix-ops.h" + +#include <xmmintrin.h> + +static inline void clear_sse(float *d, uint32_t n_samples) +{ + memset(d, 0, n_samples * sizeof(float)); +} + +static inline void copy_sse(float *d, const float *s, uint32_t n_samples) +{ + spa_memcpy(d, s, n_samples * sizeof(float)); +} + +static inline void vol_sse(float *d, const float *s, float vol, uint32_t n_samples) +{ + uint32_t n, unrolled; + if (vol == 0.0f) { + clear_sse(d, n_samples); + } else if (vol == 1.0f) { + copy_sse(d, s, n_samples); + } else { + __m128 t[4]; + const __m128 v = _mm_set1_ps(vol); + + if (SPA_IS_ALIGNED(d, 16) && + SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~15; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 16) { + t[0] = _mm_load_ps(&s[n]); + t[1] = _mm_load_ps(&s[n+4]); + t[2] = _mm_load_ps(&s[n+8]); + t[3] = _mm_load_ps(&s[n+12]); + _mm_store_ps(&d[n], _mm_mul_ps(t[0], v)); + _mm_store_ps(&d[n+4], _mm_mul_ps(t[1], v)); + _mm_store_ps(&d[n+8], _mm_mul_ps(t[2], v)); + _mm_store_ps(&d[n+12], _mm_mul_ps(t[3], v)); + } + for(; n < n_samples; n++) + _mm_store_ss(&d[n], _mm_mul_ss(_mm_load_ss(&s[n]), v)); + } +} + +static inline void conv_sse(float *d, const float **s, float *c, uint32_t n_c, uint32_t n_samples) +{ + __m128 mi[n_c], sum[2]; + uint32_t n, j, unrolled; + bool aligned = true; + + for (j = 0; j < n_c; j++) { + mi[j] = _mm_set1_ps(c[j]); + aligned &= SPA_IS_ALIGNED(s[j], 16); + } + + if (aligned && SPA_IS_ALIGNED(d, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for (n = 0; n < unrolled; n += 8) { + sum[0] = sum[1] = _mm_setzero_ps(); + for (j = 0; j < n_c; j++) { + sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(_mm_load_ps(&s[j][n + 0]), mi[j])); + sum[1] = _mm_add_ps(sum[1], _mm_mul_ps(_mm_load_ps(&s[j][n + 4]), mi[j])); + } + _mm_store_ps(&d[n + 0], sum[0]); + _mm_store_ps(&d[n + 4], sum[1]); + } + for (; n < n_samples; n++) { + sum[0] = _mm_setzero_ps(); + for (j = 0; j < n_c; j++) + sum[0] = _mm_add_ss(sum[0], _mm_mul_ss(_mm_load_ss(&s[j][n]), mi[j])); + _mm_store_ss(&d[n], sum[0]); + } +} + +static inline void avg_sse(float *d, const float *s0, const float *s1, uint32_t n_samples) +{ + uint32_t n, unrolled; + __m128 half = _mm_set1_ps(0.5f); + + if (SPA_IS_ALIGNED(d, 16) && + SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for (n = 0; n < unrolled; n += 8) { + _mm_store_ps(&d[n + 0], + _mm_mul_ps( + _mm_add_ps( + _mm_load_ps(&s0[n + 0]), + _mm_load_ps(&s1[n + 0])), + half)); + _mm_store_ps(&d[n + 4], + _mm_mul_ps( + _mm_add_ps( + _mm_load_ps(&s0[n + 4]), + _mm_load_ps(&s1[n + 4])), + half)); + } + + for (; n < n_samples; n++) + _mm_store_ss(&d[n], + _mm_mul_ss( + _mm_add_ss( + _mm_load_ss(&s0[n]), + _mm_load_ss(&s1[n])), + half)); +} + +static inline void sub_sse(float *d, const float *s0, const float *s1, uint32_t n_samples) +{ + uint32_t n, unrolled; + + if (SPA_IS_ALIGNED(d, 16) && + SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for (n = 0; n < unrolled; n += 8) { + _mm_store_ps(&d[n + 0], + _mm_sub_ps(_mm_load_ps(&s0[n + 0]), _mm_load_ps(&s1[n + 0]))); + _mm_store_ps(&d[n + 4], + _mm_sub_ps(_mm_load_ps(&s0[n + 4]), _mm_load_ps(&s1[n + 4]))); + } + for (; n < n_samples; n++) + _mm_store_ss(&d[n], + _mm_sub_ss(_mm_load_ss(&s0[n]), _mm_load_ss(&s1[n]))); +} + +void channelmix_copy_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + for (i = 0; i < n_dst; i++) + vol_sse(d[i], s[i], mix->matrix[i][i], n_samples); +} + +void +channelmix_f32_n_m_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + float **d = (float **) dst; + const float **s = (const float **) src; + uint32_t i, j, n_dst = mix->dst_chan, n_src = mix->src_chan; + + for (i = 0; i < n_dst; i++) { + float *di = d[i]; + float mj[n_src]; + const float *sj[n_src]; + uint32_t n_j = 0; + + for (j = 0; j < n_src; j++) { + if (mix->matrix[i][j] == 0.0f) + continue; + mj[n_j] = mix->matrix[i][j]; + sj[n_j++] = s[j]; + } + if (n_j == 0) { + clear_sse(di, n_samples); + } else if (n_j == 1) { + if (mix->lr4[i].active) + lr4_process(&mix->lr4[i], di, sj[0], mj[0], n_samples); + else + vol_sse(di, sj[0], mj[0], n_samples); + } else { + conv_sse(di, sj, mj, n_j, n_samples); + lr4_process(&mix->lr4[i], di, di, 1.0f, n_samples); + } + } +} + +void +channelmix_f32_2_3p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n, unrolled, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float v2 = (mix->matrix[2][0] + mix->matrix[2][1]) * 0.5f; + const float v3 = (mix->matrix[3][0] + mix->matrix[3][1]) * 0.5f; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_sse(d[i], n_samples); + } + else { + if (mix->widen == 0.0f) { + vol_sse(d[0], s[0], v0, n_samples); + vol_sse(d[1], s[1], v1, n_samples); + avg_sse(d[2], s[0], s[1], n_samples); + } else { + const __m128 mv0 = _mm_set1_ps(mix->matrix[0][0]); + const __m128 mv1 = _mm_set1_ps(mix->matrix[1][1]); + const __m128 mw = _mm_set1_ps(mix->widen); + const __m128 mh = _mm_set1_ps(0.5f); + __m128 t0[1], t1[1], w[1], c[1]; + + if (SPA_IS_ALIGNED(s[0], 16) && + SPA_IS_ALIGNED(s[1], 16) && + SPA_IS_ALIGNED(d[0], 16) && + SPA_IS_ALIGNED(d[1], 16) && + SPA_IS_ALIGNED(d[2], 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + t0[0] = _mm_load_ps(&s[0][n]); + t1[0] = _mm_load_ps(&s[1][n]); + c[0] = _mm_add_ps(t0[0], t1[0]); + w[0] = _mm_mul_ps(c[0], mw); + _mm_store_ps(&d[0][n], _mm_mul_ps(_mm_sub_ps(t0[0], w[0]), mv0)); + _mm_store_ps(&d[1][n], _mm_mul_ps(_mm_sub_ps(t1[0], w[0]), mv1)); + _mm_store_ps(&d[2][n], _mm_mul_ps(c[0], mh)); + } + for (; n < n_samples; n++) { + t0[0] = _mm_load_ss(&s[0][n]); + t1[0] = _mm_load_ss(&s[1][n]); + c[0] = _mm_add_ss(t0[0], t1[0]); + w[0] = _mm_mul_ss(c[0], mw); + _mm_store_ss(&d[0][n], _mm_mul_ss(_mm_sub_ss(t0[0], w[0]), mv0)); + _mm_store_ss(&d[1][n], _mm_mul_ss(_mm_sub_ss(t1[0], w[0]), mv1)); + _mm_store_ss(&d[2][n], _mm_mul_ss(c[0], mh)); + } + } + lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); + lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + } +} + +void +channelmix_f32_2_5p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v4 = mix->matrix[4][0]; + const float v5 = mix->matrix[5][1]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_sse(d[i], n_samples); + } + else { + channelmix_f32_2_3p1_sse(mix, dst, src, n_samples); + + if (mix->upmix != CHANNELMIX_UPMIX_PSD) { + vol_sse(d[4], s[0], v4, n_samples); + vol_sse(d[5], s[1], v5, n_samples); + } else { + sub_sse(d[4], s[0], s[1], n_samples); + + delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[5], d[4], -v5, n_samples); + delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[4], d[4], v4, n_samples); + } + } +} + +void +channelmix_f32_2_7p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **)dst; + const float **s = (const float **)src; + const float v4 = mix->matrix[4][0]; + const float v5 = mix->matrix[5][1]; + const float v6 = mix->matrix[6][0]; + const float v7 = mix->matrix[7][1]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_sse(d[i], n_samples); + } + else { + channelmix_f32_2_3p1_sse(mix, dst, src, n_samples); + + vol_sse(d[4], s[0], v4, n_samples); + vol_sse(d[5], s[1], v5, n_samples); + + if (mix->upmix != CHANNELMIX_UPMIX_PSD) { + vol_sse(d[6], s[0], v6, n_samples); + vol_sse(d[7], s[1], v7, n_samples); + } else { + sub_sse(d[6], s[0], s[1], n_samples); + + delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[7], d[6], -v7, n_samples); + delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[6], d[6], v6, n_samples); + } + } +} +/* FL+FR+FC+LFE -> FL+FR */ +void +channelmix_f32_3p1_2_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + float **d = (float **) dst; + const float **s = (const float **) src; + const float m0 = mix->matrix[0][0]; + const float m1 = mix->matrix[1][1]; + const float m2 = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; + const float m3 = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; + + if (m0 == 0.0f && m1 == 0.0f && m2 == 0.0f && m3 == 0.0f) { + clear_sse(d[0], n_samples); + clear_sse(d[1], n_samples); + } + else { + uint32_t n, unrolled; + const __m128 v0 = _mm_set1_ps(m0); + const __m128 v1 = _mm_set1_ps(m1); + const __m128 clev = _mm_set1_ps(m2); + const __m128 llev = _mm_set1_ps(m3); + __m128 ctr; + + if (SPA_IS_ALIGNED(s[0], 16) && + SPA_IS_ALIGNED(s[1], 16) && + SPA_IS_ALIGNED(s[2], 16) && + SPA_IS_ALIGNED(s[3], 16) && + SPA_IS_ALIGNED(d[0], 16) && + SPA_IS_ALIGNED(d[1], 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + ctr = _mm_add_ps( + _mm_mul_ps(_mm_load_ps(&s[2][n]), clev), + _mm_mul_ps(_mm_load_ps(&s[3][n]), llev)); + _mm_store_ps(&d[0][n], _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[0][n]), v0), ctr)); + _mm_store_ps(&d[1][n], _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[1][n]), v1), ctr)); + } + for(; n < n_samples; n++) { + ctr = _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[2][n]), clev), + _mm_mul_ss(_mm_load_ss(&s[3][n]), llev)); + _mm_store_ss(&d[0][n], _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[0][n]), v0), ctr)); + _mm_store_ss(&d[1][n], _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[1][n]), v1), ctr)); + } + } +} + +/* FL+FR+FC+LFE+SL+SR -> FL+FR */ +void +channelmix_f32_5p1_2_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t n, unrolled; + float **d = (float **) dst; + const float **s = (const float **) src; + const float m00 = mix->matrix[0][0]; + const float m11 = mix->matrix[1][1]; + const __m128 clev = _mm_set1_ps((mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f); + const __m128 llev = _mm_set1_ps((mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f); + const __m128 slev0 = _mm_set1_ps(mix->matrix[0][4]); + const __m128 slev1 = _mm_set1_ps(mix->matrix[1][5]); + __m128 in, ctr; + + if (SPA_IS_ALIGNED(s[0], 16) && + SPA_IS_ALIGNED(s[1], 16) && + SPA_IS_ALIGNED(s[2], 16) && + SPA_IS_ALIGNED(s[3], 16) && + SPA_IS_ALIGNED(s[4], 16) && + SPA_IS_ALIGNED(s[5], 16) && + SPA_IS_ALIGNED(d[0], 16) && + SPA_IS_ALIGNED(d[1], 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + clear_sse(d[0], n_samples); + clear_sse(d[1], n_samples); + } + else { + const __m128 v0 = _mm_set1_ps(m00); + const __m128 v1 = _mm_set1_ps(m11); + for(n = 0; n < unrolled; n += 4) { + ctr = _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[2][n]), clev), + _mm_mul_ps(_mm_load_ps(&s[3][n]), llev)); + in = _mm_mul_ps(_mm_load_ps(&s[4][n]), slev0); + in = _mm_add_ps(in, ctr); + in = _mm_add_ps(in, _mm_mul_ps(_mm_load_ps(&s[0][n]), v0)); + _mm_store_ps(&d[0][n], in); + in = _mm_mul_ps(_mm_load_ps(&s[5][n]), slev1); + in = _mm_add_ps(in, ctr); + in = _mm_add_ps(in, _mm_mul_ps(_mm_load_ps(&s[1][n]), v1)); + _mm_store_ps(&d[1][n], in); + } + for(; n < n_samples; n++) { + ctr = _mm_mul_ss(_mm_load_ss(&s[2][n]), clev); + ctr = _mm_add_ss(ctr, _mm_mul_ss(_mm_load_ss(&s[3][n]), llev)); + in = _mm_mul_ss(_mm_load_ss(&s[4][n]), slev0); + in = _mm_add_ss(in, ctr); + in = _mm_add_ss(in, _mm_mul_ss(_mm_load_ss(&s[0][n]), v0)); + _mm_store_ss(&d[0][n], in); + in = _mm_mul_ss(_mm_load_ss(&s[5][n]), slev1); + in = _mm_add_ss(in, ctr); + in = _mm_add_ss(in, _mm_mul_ss(_mm_load_ss(&s[1][n]), v1)); + _mm_store_ss(&d[1][n], in); + } + } +} + +/* FL+FR+FC+LFE+SL+SR -> FL+FR+FC+LFE*/ +void +channelmix_f32_5p1_3p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n, unrolled, n_dst = mix->dst_chan; + float **d = (float **) dst; + const float **s = (const float **) src; + + if (SPA_IS_ALIGNED(s[0], 16) && + SPA_IS_ALIGNED(s[1], 16) && + SPA_IS_ALIGNED(s[2], 16) && + SPA_IS_ALIGNED(s[3], 16) && + SPA_IS_ALIGNED(s[4], 16) && + SPA_IS_ALIGNED(s[5], 16) && + SPA_IS_ALIGNED(d[0], 16) && + SPA_IS_ALIGNED(d[1], 16) && + SPA_IS_ALIGNED(d[2], 16) && + SPA_IS_ALIGNED(d[3], 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_sse(d[i], n_samples); + } + else { + const __m128 v0 = _mm_set1_ps(mix->matrix[0][0]); + const __m128 v1 = _mm_set1_ps(mix->matrix[1][1]); + const __m128 slev0 = _mm_set1_ps(mix->matrix[0][4]); + const __m128 slev1 = _mm_set1_ps(mix->matrix[1][5]); + + for(n = 0; n < unrolled; n += 4) { + _mm_store_ps(&d[0][n], _mm_add_ps( + _mm_mul_ps(_mm_load_ps(&s[0][n]), v0), + _mm_mul_ps(_mm_load_ps(&s[4][n]), slev0))); + + _mm_store_ps(&d[1][n], _mm_add_ps( + _mm_mul_ps(_mm_load_ps(&s[1][n]), v1), + _mm_mul_ps(_mm_load_ps(&s[5][n]), slev1))); + } + for(; n < n_samples; n++) { + _mm_store_ss(&d[0][n], _mm_add_ss( + _mm_mul_ss(_mm_load_ss(&s[0][n]), v0), + _mm_mul_ss(_mm_load_ss(&s[4][n]), slev0))); + + _mm_store_ss(&d[1][n], _mm_add_ss( + _mm_mul_ss(_mm_load_ss(&s[1][n]), v1), + _mm_mul_ss(_mm_load_ss(&s[5][n]), slev1))); + } + vol_sse(d[2], s[2], mix->matrix[2][2], n_samples); + vol_sse(d[3], s[3], mix->matrix[3][3], n_samples); + } +} + +/* FL+FR+FC+LFE+SL+SR -> FL+FR+RL+RR*/ +void +channelmix_f32_5p1_4_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t i, n_dst = mix->dst_chan; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v4 = mix->matrix[2][4]; + const float v5 = mix->matrix[3][5]; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_sse(d[i], n_samples); + } + else { + channelmix_f32_3p1_2_sse(mix, dst, src, n_samples); + + vol_sse(d[2], s[4], v4, n_samples); + vol_sse(d[3], s[5], v5, n_samples); + } +} diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c new file mode 100644 index 0000000..527763b --- /dev/null +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -0,0 +1,668 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <spa/param/audio/format-utils.h> +#include <spa/support/cpu.h> +#include <spa/support/log.h> +#include <spa/utils/defs.h> +#include <spa/debug/types.h> + +#include "channelmix-ops.h" +#include "hilbert.h" + +#define ANY ((uint32_t)-1) +#define EQ ((uint32_t)-2) + +typedef void (*channelmix_func_t) (struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples); + +#define MAKE(sc,sm,dc,dm,func,...) \ + { sc, sm, dc, dm, func, #func, __VA_ARGS__ } + +static const struct channelmix_info { + uint32_t src_chan; + uint64_t src_mask; + uint32_t dst_chan; + uint64_t dst_mask; + + channelmix_func_t process; + const char *name; + + uint32_t cpu_flags; +} channelmix_table[] = +{ +#if defined (HAVE_SSE) + MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_sse, SPA_CPU_FLAG_SSE), + MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_sse, SPA_CPU_FLAG_SSE), + MAKE(EQ, 0, EQ, 0, channelmix_copy_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_c), + MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_c), + MAKE(EQ, 0, EQ, 0, channelmix_copy_c), + + MAKE(1, MASK_MONO, 2, MASK_STEREO, channelmix_f32_1_2_c), + MAKE(2, MASK_STEREO, 1, MASK_MONO, channelmix_f32_2_1_c), + MAKE(4, MASK_QUAD, 1, MASK_MONO, channelmix_f32_4_1_c), + MAKE(4, MASK_3_1, 1, MASK_MONO, channelmix_f32_4_1_c), + MAKE(2, MASK_STEREO, 4, MASK_QUAD, channelmix_f32_2_4_c), +#if defined (HAVE_SSE) + MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_c), +#if defined (HAVE_SSE) + MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_c), +#if defined (HAVE_SSE) + MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_c), +#if defined (HAVE_SSE) + MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_c), +#if defined (HAVE_SSE) + MAKE(6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_c), +#if defined (HAVE_SSE) + MAKE(6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_c), + +#if defined (HAVE_SSE) + MAKE(6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_c), + + MAKE(8, MASK_7_1, 2, MASK_STEREO, channelmix_f32_7p1_2_c), + MAKE(8, MASK_7_1, 4, MASK_QUAD, channelmix_f32_7p1_4_c), + MAKE(8, MASK_7_1, 4, MASK_3_1, channelmix_f32_7p1_3p1_c), + +#if defined (HAVE_SSE) + MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_c), +}; +#undef MAKE + +#define MATCH_CHAN(a,b) ((a) == ANY || (a) == (b)) +#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) +#define MATCH_MASK(a,b) ((a) == 0 || ((a) & (b)) == (b)) + +static const struct channelmix_info *find_channelmix_info(uint32_t src_chan, uint64_t src_mask, + uint32_t dst_chan, uint64_t dst_mask, uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(channelmix_table, info) { + if (!MATCH_CPU_FLAGS(info->cpu_flags, cpu_flags)) + continue; + + if (src_chan == dst_chan && src_mask == dst_mask) + return info; + + if (MATCH_CHAN(info->src_chan, src_chan) && + MATCH_CHAN(info->dst_chan, dst_chan) && + MATCH_MASK(info->src_mask, src_mask) && + MATCH_MASK(info->dst_mask, dst_mask)) + return info; + } + return NULL; +} + +#define SQRT3_2 1.224744871f /* sqrt(3/2) */ +#define SQRT1_2 0.707106781f +#define SQRT2 1.414213562f + +#define MATRIX_NORMAL 0 +#define MATRIX_DOLBY 1 +#define MATRIX_DPLII 2 + +#define _CH(ch) ((SPA_AUDIO_CHANNEL_ ## ch)-3) +#define _MASK(ch) (1ULL << _CH(ch)) +#define FRONT (_MASK(FC)) +#define STEREO (_MASK(FL)|_MASK(FR)) +#define REAR (_MASK(RL)|_MASK(RR)) +#define SIDE (_MASK(SL)|_MASK(SR)) + +static int make_matrix(struct channelmix *mix) +{ + float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS] = {{ 0.0f }}; + uint64_t src_mask = mix->src_mask; + uint64_t dst_mask = mix->dst_mask; + uint32_t src_chan = mix->src_chan; + uint32_t dst_chan = mix->dst_chan; + uint64_t unassigned, keep; + uint32_t i, j, ic, jc, matrix_encoding = MATRIX_NORMAL; + float clev = SQRT1_2; + float slev = SQRT1_2; + float llev = 0.5f; + float maxsum = 0.0f; + bool filter_fc = false, filter_lfe = false; +#define _MATRIX(s,d) matrix[_CH(s)][_CH(d)] + + spa_log_debug(mix->log, "src-mask:%08"PRIx64" dst-mask:%08"PRIx64 + " options:%08x", src_mask, dst_mask, mix->options); + + /* move the MONO mask to FRONT so that the lower bits can be shifted + * away. */ + if ((src_mask & (1Ull << SPA_AUDIO_CHANNEL_MONO)) != 0) { + if (src_chan == 1) + src_mask = 0; + else + src_mask |= (1ULL << SPA_AUDIO_CHANNEL_FC); + } + if ((dst_mask & (1Ull << SPA_AUDIO_CHANNEL_MONO)) != 0) + dst_mask |= (1ULL << SPA_AUDIO_CHANNEL_FC); + + /* shift so that bit 0 is FL */ + src_mask >>= 3; + dst_mask >>= 3; + + /* unknown channels or just 1 channel */ + if (src_mask == 0 || dst_mask == 0) { + if (src_chan == 1) { + /* one FC/MONO src goes everywhere */ + spa_log_debug(mix->log, "distribute FC/MONO (%f)", 1.0f); + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + matrix[i][0]= 1.0f; + } else if (dst_chan == 1) { + /* one FC/MONO dst get average of everything */ + spa_log_debug(mix->log, "average FC/MONO (%f)", 1.0f / src_chan); + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + matrix[0][i]= 1.0f / src_chan; + } else { + /* just pair channels */ + spa_log_debug(mix->log, "pairing channels (%f)", 1.0f); + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + matrix[i][i]= 1.0f; + } + if (dst_mask & FRONT) + filter_fc = true; + if (dst_mask & _MASK(LFE)) + filter_lfe = true; + src_mask = dst_mask = ~0LU; + goto done; + } else { + spa_log_debug(mix->log, "matching channels"); + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + if ((src_mask & dst_mask & (1ULL << i))) { + spa_log_debug(mix->log, "matched channel %u (%f)", i, 1.0f); + matrix[i][i]= 1.0f; + } + } + } + + unassigned = src_mask & ~dst_mask; + keep = dst_mask & ~src_mask; + + if (!SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_UPMIX)) { + keep = 0; + } else { + if (mix->upmix == CHANNELMIX_UPMIX_NONE) + keep = 0; + keep |= FRONT; + if (mix->lfe_cutoff > 0.0f) + keep |= _MASK(LFE); + else + keep &= ~_MASK(LFE); + } + + spa_log_debug(mix->log, "unassigned downmix %08" PRIx64 " %08" PRIx64, unassigned, keep); + + if (unassigned & FRONT){ + if ((dst_mask & STEREO) == STEREO){ + if(src_mask & STEREO) { + spa_log_debug(mix->log, "assign FC to STEREO (%f)", clev); + _MATRIX(FL,FC) += clev; + _MATRIX(FR,FC) += clev; + } else { + spa_log_debug(mix->log, "assign FC to STEREO (%f)", SQRT1_2); + _MATRIX(FL,FC) += SQRT1_2; + _MATRIX(FR,FC) += SQRT1_2; + } + } else { + spa_log_warn(mix->log, "can't assign FC"); + } + } + + if (unassigned & STEREO){ + if (dst_mask & FRONT) { + spa_log_debug(mix->log, "assign STEREO to FC (%f)", SQRT1_2); + _MATRIX(FC,FL) += SQRT1_2; + _MATRIX(FC,FR) += SQRT1_2; + if (src_mask & FRONT) { + spa_log_debug(mix->log, "assign FC to FC (%f)", clev * SQRT2); + _MATRIX(FC,FC) = clev * SQRT2; + } + keep &= ~FRONT; + } else { + spa_log_warn(mix->log, "can't assign STEREO"); + } + } + + if (unassigned & _MASK(RC)) { + if (dst_mask & REAR){ + spa_log_debug(mix->log, "assign RC to RL+RR (%f)", SQRT1_2); + _MATRIX(RL,RC) += SQRT1_2; + _MATRIX(RR,RC) += SQRT1_2; + } else if (dst_mask & SIDE) { + spa_log_debug(mix->log, "assign RC to SL+SR (%f)", SQRT1_2); + _MATRIX(SL,RC) += SQRT1_2; + _MATRIX(SR,RC) += SQRT1_2; + } else if(dst_mask & STEREO) { + spa_log_debug(mix->log, "assign RC to FL+FR"); + if (matrix_encoding == MATRIX_DOLBY || + matrix_encoding == MATRIX_DPLII) { + if (unassigned & (_MASK(RL)|_MASK(RR))) { + _MATRIX(FL,RC) -= slev * SQRT1_2; + _MATRIX(FR,RC) += slev * SQRT1_2; + } else { + _MATRIX(FL,RC) -= slev; + _MATRIX(FR,RC) += slev; + } + } else { + _MATRIX(FL,RC) += slev * SQRT1_2; + _MATRIX(FR,RC) += slev * SQRT1_2; + } + } else if (dst_mask & FRONT) { + spa_log_debug(mix->log, "assign RC to FC (%f)", slev * SQRT1_2); + _MATRIX(FC,RC) += slev * SQRT1_2; + } else { + spa_log_warn(mix->log, "can't assign RC"); + } + } + + if (unassigned & REAR) { + if (dst_mask & _MASK(RC)) { + spa_log_debug(mix->log, "assign RL+RR to RC"); + _MATRIX(RC,RL) += SQRT1_2; + _MATRIX(RC,RR) += SQRT1_2; + } else if (dst_mask & SIDE) { + spa_log_debug(mix->log, "assign RL+RR to SL+SR"); + if (src_mask & SIDE) { + _MATRIX(SL,RL) += SQRT1_2; + _MATRIX(SR,RR) += SQRT1_2; + } else { + _MATRIX(SL,RL) += 1.0f; + _MATRIX(SR,RR) += 1.0f; + } + keep &= ~SIDE; + } else if (dst_mask & STEREO) { + spa_log_debug(mix->log, "assign RL+RR to FL+FR (%f)", slev); + if (matrix_encoding == MATRIX_DOLBY) { + _MATRIX(FL,RL) -= slev * SQRT1_2; + _MATRIX(FL,RR) -= slev * SQRT1_2; + _MATRIX(FR,RL) += slev * SQRT1_2; + _MATRIX(FR,RR) += slev * SQRT1_2; + } else if (matrix_encoding == MATRIX_DPLII) { + _MATRIX(FL,RL) -= slev * SQRT3_2; + _MATRIX(FL,RR) -= slev * SQRT1_2; + _MATRIX(FR,RL) += slev * SQRT1_2; + _MATRIX(FR,RR) += slev * SQRT3_2; + } else { + _MATRIX(FL,RL) += slev; + _MATRIX(FR,RR) += slev; + } + } else if (dst_mask & FRONT) { + spa_log_debug(mix->log, "assign RL+RR to FC (%f)", + slev * SQRT1_2); + _MATRIX(FC,RL)+= slev * SQRT1_2; + _MATRIX(FC,RR)+= slev * SQRT1_2; + } else { + spa_log_warn(mix->log, "can't assign RL"); + } + } + + if (unassigned & SIDE) { + if (dst_mask & REAR) { + if (src_mask & _MASK(RL)) { + spa_log_debug(mix->log, "assign SL+SR to RL+RR (%f)", SQRT1_2); + _MATRIX(RL,SL) += SQRT1_2; + _MATRIX(RR,SR) += SQRT1_2; + } else { + spa_log_debug(mix->log, "assign SL+SR to RL+RR (%f)", 1.0f); + _MATRIX(RL,SL) += 1.0f; + _MATRIX(RR,SR) += 1.0f; + } + keep &= ~REAR; + } else if (dst_mask & _MASK(RC)) { + spa_log_debug(mix->log, "assign SL+SR to RC (%f)", SQRT1_2); + _MATRIX(RC,SL)+= SQRT1_2; + _MATRIX(RC,SR)+= SQRT1_2; + } else if (dst_mask & STEREO) { + if (matrix_encoding == MATRIX_DOLBY) { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f)", + slev * SQRT1_2); + _MATRIX(FL,SL) -= slev * SQRT1_2; + _MATRIX(FL,SR) -= slev * SQRT1_2; + _MATRIX(FR,SL) += slev * SQRT1_2; + _MATRIX(FR,SR) += slev * SQRT1_2; + } else if (matrix_encoding == MATRIX_DPLII) { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f / %f)", + slev * SQRT3_2, slev * SQRT1_2); + _MATRIX(FL,SL) -= slev * SQRT3_2; + _MATRIX(FL,SR) -= slev * SQRT1_2; + _MATRIX(FR,SL) += slev * SQRT1_2; + _MATRIX(FR,SR) += slev * SQRT3_2; + } else { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f)", slev); + _MATRIX(FL,SL) += slev; + _MATRIX(FR,SR) += slev; + } + } else if (dst_mask & FRONT) { + spa_log_debug(mix->log, "assign SL+SR to FC (%f)", slev * SQRT1_2); + _MATRIX(FC,SL) += slev * SQRT1_2; + _MATRIX(FC,SR) += slev * SQRT1_2; + } else { + spa_log_warn(mix->log, "can't assign SL"); + } + } + + if (unassigned & _MASK(FLC)) { + if (dst_mask & STEREO) { + spa_log_debug(mix->log, "assign FLC+FRC to FL+FR (%f)", 1.0f); + _MATRIX(FL,FLC)+= 1.0f; + _MATRIX(FR,FRC)+= 1.0f; + } else if(dst_mask & FRONT) { + spa_log_debug(mix->log, "assign FLC+FRC to FC (%f)", SQRT1_2); + _MATRIX(FC,FLC)+= SQRT1_2; + _MATRIX(FC,FRC)+= SQRT1_2; + } else { + spa_log_warn(mix->log, "can't assign FLC"); + } + } + if (unassigned & _MASK(LFE) && + SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_MIX_LFE)) { + if (dst_mask & FRONT) { + spa_log_debug(mix->log, "assign LFE to FC (%f)", llev); + _MATRIX(FC,LFE) += llev; + } else if (dst_mask & STEREO) { + spa_log_debug(mix->log, "assign LFE to FL+FR (%f)", + llev * SQRT1_2); + _MATRIX(FL,LFE) += llev * SQRT1_2; + _MATRIX(FR,LFE) += llev * SQRT1_2; + } else { + spa_log_warn(mix->log, "can't assign LFE"); + } + } + + unassigned = dst_mask & ~src_mask & keep; + + spa_log_debug(mix->log, "unassigned upmix %08"PRIx64" lfe:%f", + unassigned, mix->lfe_cutoff); + + if (unassigned & STEREO) { + if ((src_mask & FRONT) == FRONT) { + spa_log_debug(mix->log, "produce STEREO from FC (%f)", clev); + _MATRIX(FL,FC) += clev; + _MATRIX(FR,FC) += clev; + } else { + spa_log_warn(mix->log, "can't produce STEREO"); + } + } + if (unassigned & FRONT) { + if ((src_mask & STEREO) == STEREO) { + spa_log_debug(mix->log, "produce FC from STEREO (%f)", clev); + _MATRIX(FC,FL) += clev; + _MATRIX(FC,FR) += clev; + filter_fc = true; + } else { + spa_log_warn(mix->log, "can't produce FC"); + } + } + if (unassigned & _MASK(LFE)) { + if ((src_mask & STEREO) == STEREO) { + spa_log_debug(mix->log, "produce LFE from STEREO (%f)", llev); + _MATRIX(LFE,FL) += llev; + _MATRIX(LFE,FR) += llev; + filter_lfe = true; + } else if ((src_mask & FRONT) == FRONT) { + spa_log_debug(mix->log, "produce LFE from FC (%f)", llev); + _MATRIX(LFE,FC) += llev; + filter_lfe = true; + } else { + spa_log_warn(mix->log, "can't produce LFE"); + } + } + if (unassigned & SIDE) { + if ((src_mask & REAR) == REAR) { + spa_log_debug(mix->log, "produce SIDE from REAR (%f)", 1.0f); + _MATRIX(SL,RL) += 1.0f; + _MATRIX(SR,RR) += 1.0f; + } else if ((src_mask & STEREO) == STEREO) { + spa_log_debug(mix->log, "produce SIDE from STEREO (%f)", slev); + _MATRIX(SL,FL) += slev; + _MATRIX(SR,FR) += slev; + } else if ((src_mask & FRONT) == FRONT && + mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { + spa_log_debug(mix->log, "produce SIDE from FC (%f)", clev); + _MATRIX(SL,FC) += clev; + _MATRIX(SR,FC) += clev; + } else { + spa_log_debug(mix->log, "won't produce SIDE"); + } + } + if (unassigned & REAR) { + if ((src_mask & SIDE) == SIDE) { + spa_log_debug(mix->log, "produce REAR from SIDE (%f)", 1.0f); + _MATRIX(RL,SL) += 1.0f; + _MATRIX(RR,SR) += 1.0f; + } else if ((src_mask & STEREO) == STEREO) { + spa_log_debug(mix->log, "produce REAR from STEREO (%f)", slev); + _MATRIX(RL,FL) += slev; + _MATRIX(RR,FR) += slev; + } else if ((src_mask & FRONT) == FRONT && + mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { + spa_log_debug(mix->log, "produce REAR from FC (%f)", clev); + _MATRIX(RL,FC) += clev; + _MATRIX(RR,FC) += clev; + } else { + spa_log_debug(mix->log, "won't produce SIDE"); + } + } + if (unassigned & _MASK(RC)) { + if ((src_mask & REAR) == REAR) { + spa_log_debug(mix->log, "produce RC from REAR (%f)", 0.5f); + _MATRIX(RC,RL) += 0.5f; + _MATRIX(RC,RR) += 0.5f; + } else if ((src_mask & SIDE) == SIDE) { + spa_log_debug(mix->log, "produce RC from SIDE (%f)", 0.5f); + _MATRIX(RC,SL) += 0.5f; + _MATRIX(RC,SR) += 0.5f; + } else if ((src_mask & STEREO) == STEREO) { + spa_log_debug(mix->log, "produce RC from STEREO (%f)", 0.5f); + _MATRIX(RC,FL) += 0.5f; + _MATRIX(RC,FR) += 0.5f; + } else if ((src_mask & FRONT) == FRONT && + mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { + spa_log_debug(mix->log, "produce RC from FC (%f)", slev); + _MATRIX(RC,FC) += slev; + } else { + spa_log_debug(mix->log, "won't produce RC"); + } + } + +done: + for (jc = 0, ic = 0, i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + float sum = 0.0f; + char str[1024], str2[1024]; + int idx = 0, idx2 = 0; + if ((dst_mask & (1UL << i)) == 0) + continue; + for (jc = 0, j = 0; j < SPA_AUDIO_MAX_CHANNELS; j++) { + if ((src_mask & (1UL << j)) == 0) + continue; + if (ic >= dst_chan || jc >= src_chan) + continue; + + if (ic == 0) + idx2 += snprintf(str2 + idx2, sizeof(str2) - idx2, "%-4.4s ", + src_mask == ~0LU ? "MONO" : + spa_debug_type_find_short_name(spa_type_audio_channel, j + 3)); + + mix->matrix_orig[ic][jc++] = matrix[i][j]; + sum += fabs(matrix[i][j]); + + if (matrix[i][j] == 0.0f) + idx += snprintf(str + idx, sizeof(str) - idx, " "); + else + idx += snprintf(str + idx, sizeof(str) - idx, "%1.3f ", matrix[i][j]); + } + if (idx2 > 0) + spa_log_info(mix->log, " %s", str2); + if (idx > 0) { + spa_log_info(mix->log, "%-4.4s %s %f", + dst_mask == ~0LU ? "MONO" : + spa_debug_type_find_short_name(spa_type_audio_channel, i + 3), + str, sum); + } + + maxsum = SPA_MAX(maxsum, sum); + if (i == _CH(LFE) && mix->lfe_cutoff > 0.0f && filter_lfe) { + spa_log_info(mix->log, "channel %d is LFE cutoff:%f", ic, mix->lfe_cutoff); + lr4_set(&mix->lr4[ic], BQ_LOWPASS, mix->lfe_cutoff / mix->freq); + } else if (i == _CH(FC) && mix->fc_cutoff > 0.0f && filter_fc) { + spa_log_info(mix->log, "channel %d is FC cutoff:%f", ic, mix->fc_cutoff); + lr4_set(&mix->lr4[ic], BQ_LOWPASS, mix->fc_cutoff / mix->freq); + } else { + mix->lr4[ic].active = false; + } + ic++; + } + if (SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_NORMALIZE) && + maxsum > 1.0f) { + spa_log_debug(mix->log, "normalize %f", maxsum); + for (i = 0; i < dst_chan; i++) + for (j = 0; j < src_chan; j++) + mix->matrix_orig[i][j] /= maxsum; + } + return 0; +} + +static void impl_channelmix_set_volume(struct channelmix *mix, float volume, bool mute, + uint32_t n_channel_volumes, float *channel_volumes) +{ + float volumes[SPA_AUDIO_MAX_CHANNELS]; + float vol = mute ? 0.0f : volume, t; + uint32_t i, j; + uint32_t src_chan = mix->src_chan; + uint32_t dst_chan = mix->dst_chan; + + spa_log_debug(mix->log, "volume:%f mute:%d n_volumes:%d", volume, mute, n_channel_volumes); + + /** apply global volume to channels */ + for (i = 0; i < n_channel_volumes; i++) { + volumes[i] = channel_volumes[i] * vol; + spa_log_debug(mix->log, "%d: %f * %f = %f", i, channel_volumes[i], vol, volumes[i]); + } + + /** apply volumes per channel */ + if (n_channel_volumes == src_chan) { + for (i = 0; i < dst_chan; i++) { + for (j = 0; j < src_chan; j++) { + mix->matrix[i][j] = mix->matrix_orig[i][j] * volumes[j]; + } + } + } else if (n_channel_volumes == dst_chan) { + for (i = 0; i < dst_chan; i++) { + for (j = 0; j < src_chan; j++) { + mix->matrix[i][j] = mix->matrix_orig[i][j] * volumes[i]; + } + } + } else if (n_channel_volumes == 0) { + for (i = 0; i < dst_chan; i++) { + for (j = 0; j < src_chan; j++) { + mix->matrix[i][j] = mix->matrix_orig[i][j] * vol; + } + } + } + + SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_ZERO); + SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_EQUAL); + SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_COPY); + + t = 0.0; + for (i = 0; i < dst_chan; i++) { + for (j = 0; j < src_chan; j++) { + float v = mix->matrix[i][j]; + spa_log_debug(mix->log, "%d %d: %f", i, j, v); + if (i == 0 && j == 0) + t = v; + else if (t != v) + SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_EQUAL); + if (v != 0.0) + SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_ZERO); + if ((i == j && v != 1.0f) || + (i != j && v != 0.0f)) + SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_COPY); + } + } + SPA_FLAG_UPDATE(mix->flags, CHANNELMIX_FLAG_IDENTITY, + dst_chan == src_chan && SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_COPY)); + + spa_log_debug(mix->log, "flags:%08x", mix->flags); +} + +static void impl_channelmix_free(struct channelmix *mix) +{ + mix->process = NULL; +} + +int channelmix_init(struct channelmix *mix) +{ + const struct channelmix_info *info; + + if (mix->src_chan > SPA_AUDIO_MAX_CHANNELS || + mix->dst_chan > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + + info = find_channelmix_info(mix->src_chan, mix->src_mask, mix->dst_chan, mix->dst_mask, + mix->cpu_flags); + if (info == NULL) + return -ENOTSUP; + + mix->free = impl_channelmix_free; + mix->process = info->process; + mix->set_volume = impl_channelmix_set_volume; + mix->cpu_flags = info->cpu_flags; + mix->delay = mix->rear_delay * mix->freq / 1000.0f; + mix->func_name = info->name; + + spa_log_debug(mix->log, "selected %s delay:%d options:%08x", info->name, mix->delay, + mix->options); + + if (mix->hilbert_taps > 0) { + mix->n_taps = SPA_CLAMP(mix->hilbert_taps, 15u, 255u) | 1; + blackman_window(mix->taps, mix->n_taps); + hilbert_generate(mix->taps, mix->n_taps); + } else { + mix->n_taps = 1; + mix->taps[0] = 1.0f; + } + return make_matrix(mix); +} diff --git a/spa/plugins/audioconvert/channelmix-ops.h b/spa/plugins/audioconvert/channelmix-ops.h new file mode 100644 index 0000000..c134a96 --- /dev/null +++ b/spa/plugins/audioconvert/channelmix-ops.h @@ -0,0 +1,160 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> + +#include <spa/utils/defs.h> +#include <spa/utils/string.h> +#include <spa/param/audio/raw.h> + +#include "crossover.h" +#include "delay.h" + +#define VOLUME_MIN 0.0f +#define VOLUME_NORM 1.0f + +#define _M(ch) (1UL << SPA_AUDIO_CHANNEL_ ## ch) +#define MASK_MONO _M(FC)|_M(MONO)|_M(UNKNOWN) +#define MASK_STEREO _M(FL)|_M(FR)|_M(UNKNOWN) +#define MASK_QUAD _M(FL)|_M(FR)|_M(RL)|_M(RR)|_M(UNKNOWN) +#define MASK_3_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE) +#define MASK_5_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) +#define MASK_7_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) + +#define BUFFER_SIZE 4096 +#define MAX_TAPS 255 + +struct channelmix { + uint32_t src_chan; + uint32_t dst_chan; + uint64_t src_mask; + uint64_t dst_mask; + uint32_t cpu_flags; +#define CHANNELMIX_OPTION_MIX_LFE (1<<0) /**< mix LFE */ +#define CHANNELMIX_OPTION_NORMALIZE (1<<1) /**< normalize volumes */ +#define CHANNELMIX_OPTION_UPMIX (1<<2) /**< do simple upmixing */ + uint32_t options; +#define CHANNELMIX_UPMIX_NONE 0 /**< disable upmixing */ +#define CHANNELMIX_UPMIX_SIMPLE 1 /**< simple upmixing */ +#define CHANNELMIX_UPMIX_PSD 2 /**< Passive Surround Decoding upmixing */ + uint32_t upmix; + + struct spa_log *log; + const char *func_name; + +#define CHANNELMIX_FLAG_ZERO (1<<0) /**< all zero components */ +#define CHANNELMIX_FLAG_IDENTITY (1<<1) /**< identity matrix */ +#define CHANNELMIX_FLAG_EQUAL (1<<2) /**< all values are equal */ +#define CHANNELMIX_FLAG_COPY (1<<3) /**< 1 on diagonal, can be nxm */ + uint32_t flags; + float matrix_orig[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS]; + float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS]; + + float freq; /* sample frequency */ + float lfe_cutoff; /* in Hz, 0 is disabled */ + float fc_cutoff; /* in Hz, 0 is disabled */ + float rear_delay; /* in ms, 0 is disabled */ + float widen; /* stereo widen. 0 is disabled */ + uint32_t hilbert_taps; /* to phase shift, 0 disabled */ + struct lr4 lr4[SPA_AUDIO_MAX_CHANNELS]; + + float buffer[2][BUFFER_SIZE]; + uint32_t pos[2]; + uint32_t delay; + float taps[MAX_TAPS]; + uint32_t n_taps; + + void (*process) (struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples); + void (*set_volume) (struct channelmix *mix, float volume, bool mute, + uint32_t n_channel_volumes, float *channel_volumes); + void (*free) (struct channelmix *mix); + + void *data; +}; + +int channelmix_init(struct channelmix *mix); + +static const struct channelmix_upmix_info { + const char *label; + const char *description; + uint32_t upmix; +} channelmix_upmix_info[] = { + [CHANNELMIX_UPMIX_NONE] = { "none", "Disabled", CHANNELMIX_UPMIX_NONE }, + [CHANNELMIX_UPMIX_SIMPLE] = { "simple", "Simple upmixing", CHANNELMIX_UPMIX_SIMPLE }, + [CHANNELMIX_UPMIX_PSD] = { "psd", "Passive Surround Decoding", CHANNELMIX_UPMIX_PSD } +}; + +static inline uint32_t channelmix_upmix_from_label(const char *label) +{ + SPA_FOR_EACH_ELEMENT_VAR(channelmix_upmix_info, i) { + if (spa_streq(i->label, label)) + return i->upmix; + } + return CHANNELMIX_UPMIX_NONE; +} + +#define channelmix_process(mix,...) (mix)->process(mix, __VA_ARGS__) +#define channelmix_set_volume(mix,...) (mix)->set_volume(mix, __VA_ARGS__) +#define channelmix_free(mix) (mix)->free(mix) + +#define DEFINE_FUNCTION(name,arch) \ +void channelmix_##name##_##arch(struct channelmix *mix, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples); + +#define CHANNELMIX_OPS_MAX_ALIGN 16 + +DEFINE_FUNCTION(copy, c); +DEFINE_FUNCTION(f32_n_m, c); +DEFINE_FUNCTION(f32_1_2, c); +DEFINE_FUNCTION(f32_2_1, c); +DEFINE_FUNCTION(f32_4_1, c); +DEFINE_FUNCTION(f32_2_4, c); +DEFINE_FUNCTION(f32_2_3p1, c); +DEFINE_FUNCTION(f32_2_5p1, c); +DEFINE_FUNCTION(f32_2_7p1, c); +DEFINE_FUNCTION(f32_3p1_2, c); +DEFINE_FUNCTION(f32_5p1_2, c); +DEFINE_FUNCTION(f32_5p1_3p1, c); +DEFINE_FUNCTION(f32_5p1_4, c); +DEFINE_FUNCTION(f32_7p1_2, c); +DEFINE_FUNCTION(f32_7p1_3p1, c); +DEFINE_FUNCTION(f32_7p1_4, c); + +#if defined (HAVE_SSE) +DEFINE_FUNCTION(copy, sse); +DEFINE_FUNCTION(f32_n_m, sse); +DEFINE_FUNCTION(f32_2_3p1, sse); +DEFINE_FUNCTION(f32_2_5p1, sse); +DEFINE_FUNCTION(f32_2_7p1, sse); +DEFINE_FUNCTION(f32_3p1_2, sse); +DEFINE_FUNCTION(f32_5p1_2, sse); +DEFINE_FUNCTION(f32_5p1_3p1, sse); +DEFINE_FUNCTION(f32_5p1_4, sse); +DEFINE_FUNCTION(f32_7p1_4, sse); +#endif + +#undef DEFINE_FUNCTION diff --git a/spa/plugins/audioconvert/crossover.c b/spa/plugins/audioconvert/crossover.c new file mode 100644 index 0000000..7575833 --- /dev/null +++ b/spa/plugins/audioconvert/crossover.c @@ -0,0 +1,70 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <float.h> +#include <string.h> + +#include "crossover.h" + +void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq) +{ + biquad_set(&lr4->bq, type, freq); + lr4->x1 = 0; + lr4->x2 = 0; + lr4->y1 = 0; + lr4->y2 = 0; + lr4->z1 = 0; + lr4->z2 = 0; + lr4->active = true; +} + +void lr4_process(struct lr4 *lr4, float *dst, const float *src, const float vol, int samples) +{ + float lx1 = lr4->x1; + float lx2 = lr4->x2; + float ly1 = lr4->y1; + float ly2 = lr4->y2; + float lz1 = lr4->z1; + float lz2 = lr4->z2; + float lb0 = lr4->bq.b0; + float lb1 = lr4->bq.b1; + float lb2 = lr4->bq.b2; + float la1 = lr4->bq.a1; + float la2 = lr4->bq.a2; + int i; + + if (vol == 0.0f) { + memset(dst, 0, samples * sizeof(float)); + return; + } else if (!lr4->active) { + if (src != dst || vol != 1.0f) { + for (i = 0; i < samples; i++) + dst[i] = src[i] * vol; + } + return; + } + + for (i = 0; i < samples; i++) { + float x, y, z; + x = src[i]; + y = lb0*x + lb1*lx1 + lb2*lx2 - la1*ly1 - la2*ly2; + z = lb0*y + lb1*ly1 + lb2*ly2 - la1*lz1 - la2*lz2; + lx2 = lx1; + lx1 = x; + ly2 = ly1; + ly1 = y; + lz2 = lz1; + lz1 = z; + dst[i] = z * vol; + } +#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x)) + lr4->x1 = F(lx1); + lr4->x2 = F(lx2); + lr4->y1 = F(ly1); + lr4->y2 = F(ly2); + lr4->z1 = F(lz1); + lr4->z2 = F(lz2); +#undef F +} diff --git a/spa/plugins/audioconvert/crossover.h b/spa/plugins/audioconvert/crossover.h new file mode 100644 index 0000000..b6f458b --- /dev/null +++ b/spa/plugins/audioconvert/crossover.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef CROSSOVER_H_ +#define CROSSOVER_H_ + +#include <stdbool.h> + +#include "biquad.h" +/* An LR4 filter is two biquads with the same parameters connected in series: + * + * x -- [BIQUAD] -- y -- [BIQUAD] -- z + * + * Both biquad filter has the same parameter b[012] and a[12], + * The variable [xyz][12] keep the history values. + */ +struct lr4 { + struct biquad bq; + float x1, x2; + float y1, y2; + float z1, z2; + bool active; +}; + +void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq); + +void lr4_process(struct lr4 *lr4, float *dst, const float *src, const float vol, int samples); + +#endif /* CROSSOVER_H_ */ diff --git a/spa/plugins/audioconvert/delay.h b/spa/plugins/audioconvert/delay.h new file mode 100644 index 0000000..16e189f --- /dev/null +++ b/spa/plugins/audioconvert/delay.h @@ -0,0 +1,72 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DELAY_H +#define DELAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +static inline void delay_run(float *buffer, uint32_t *pos, + uint32_t n_buffer, uint32_t delay, + float *dst, const float *src, const float vol, uint32_t n_samples) +{ + uint32_t i; + uint32_t p = *pos; + + for (i = 0; i < n_samples; i++) { + buffer[p] = src[i]; + dst[i] = buffer[(p - delay) & (n_buffer-1)] * vol; + p = (p + 1) & (n_buffer-1); + } + *pos = p; +} + +static inline void delay_convolve_run(float *buffer, uint32_t *pos, + uint32_t n_buffer, uint32_t delay, + const float *taps, uint32_t n_taps, + float *dst, const float *src, const float vol, uint32_t n_samples) +{ + uint32_t i, j; + uint32_t p = *pos; + + for (i = 0; i < n_samples; i++) { + float sum = 0.0f; + + buffer[p] = src[i]; + for (j = 0; j < n_taps; j++) + sum += (taps[j] * buffer[((p - delay) - j) & (n_buffer-1)]); + dst[i] = sum * vol; + + p = (p + 1) & (n_buffer-1); + } + *pos = p; +} + +#ifdef __cplusplus +} +#endif + +#endif /* DELAY_H */ diff --git a/spa/plugins/audioconvert/fmt-ops-avx2.c b/spa/plugins/audioconvert/fmt-ops-avx2.c new file mode 100644 index 0000000..087f027 --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops-avx2.c @@ -0,0 +1,1043 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "fmt-ops.h" + +#include <immintrin.h> +// GCC: workaround for missing AVX intrinsic: "_mm256_setr_m128()" +// (see https://stackoverflow.com/questions/32630458/setting-m256i-to-the-value-of-two-m128i-values) +#ifndef _mm256_setr_m128i +# ifndef _mm256_set_m128i +# define _mm256_set_m128i(v0, v1) _mm256_insertf128_si256(_mm256_castsi128_si256(v1), (v0), 1) +# endif +# define _mm256_setr_m128i(v0, v1) _mm256_set_m128i((v1), (v0)) +#endif + +#define _MM_CLAMP_PS(r,min,max) \ + _mm_min_ps(_mm_max_ps(r, min), max) + +#define _MM256_CLAMP_PS(r,min,max) \ + _mm256_min_ps(_mm256_max_ps(r, min), max) + +#define _MM_CLAMP_SS(r,min,max) \ + _mm_min_ss(_mm_max_ss(r, min), max) + +static void +conv_s16_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int16_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m256i in = _mm256_setzero_si256(); + __m256 out, factor = _mm256_set1_ps(1.0f / S16_SCALE); + + if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 32))) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in = _mm256_insert_epi16(in, s[0*n_channels], 1); + in = _mm256_insert_epi16(in, s[1*n_channels], 3); + in = _mm256_insert_epi16(in, s[2*n_channels], 5); + in = _mm256_insert_epi16(in, s[3*n_channels], 7); + in = _mm256_insert_epi16(in, s[4*n_channels], 9); + in = _mm256_insert_epi16(in, s[5*n_channels], 11); + in = _mm256_insert_epi16(in, s[6*n_channels], 13); + in = _mm256_insert_epi16(in, s[7*n_channels], 15); + + in = _mm256_srai_epi32(in, 16); + out = _mm256_cvtepi32_ps(in); + out = _mm256_mul_ps(out, factor); + _mm256_store_ps(&d0[n], out); + s += 8*n_channels; + } + for(; n < n_samples; n++) { + __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); + out = _mm_cvtsi32_ss(factor, s[0]); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += n_channels; + } +} + +void +conv_s16_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int16_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i < n_channels; i++) + conv_s16_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); +} + +void +conv_s16_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int16_t *s = src[0]; + float *d0 = dst[0], *d1 = dst[1]; + uint32_t n, unrolled; + __m256i in[2], t[4]; + __m256 out[4], factor = _mm256_set1_ps(1.0f / S16_SCALE); + + if (SPA_IS_ALIGNED(s, 32) && + SPA_IS_ALIGNED(d0, 32) && + SPA_IS_ALIGNED(d1, 32)) + unrolled = n_samples & ~15; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 16) { + in[0] = _mm256_load_si256((__m256i*)(s + 0)); + in[1] = _mm256_load_si256((__m256i*)(s + 16)); + + t[0] = _mm256_slli_epi32(in[0], 16); + t[0] = _mm256_srai_epi32(t[0], 16); + out[0] = _mm256_cvtepi32_ps(t[0]); + out[0] = _mm256_mul_ps(out[0], factor); + + t[1] = _mm256_srai_epi32(in[0], 16); + out[1] = _mm256_cvtepi32_ps(t[1]); + out[1] = _mm256_mul_ps(out[1], factor); + + t[2] = _mm256_slli_epi32(in[1], 16); + t[2] = _mm256_srai_epi32(t[2], 16); + out[2] = _mm256_cvtepi32_ps(t[2]); + out[2] = _mm256_mul_ps(out[2], factor); + + t[3] = _mm256_srai_epi32(in[1], 16); + out[3] = _mm256_cvtepi32_ps(t[3]); + out[3] = _mm256_mul_ps(out[3], factor); + + _mm256_store_ps(&d0[n + 0], out[0]); + _mm256_store_ps(&d1[n + 0], out[1]); + _mm256_store_ps(&d0[n + 8], out[2]); + _mm256_store_ps(&d1[n + 8], out[3]); + + s += 32; + } + for(; n < n_samples; n++) { + __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); + out[0] = _mm_cvtsi32_ss(factor, s[0]); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_cvtsi32_ss(factor, s[1]); + out[1] = _mm_mul_ss(out[1], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + s += 2; + } +} + +void +conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int8_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m128i in; + __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); + __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); + + if (SPA_IS_ALIGNED(d0, 16) && n_samples > 0) { + unrolled = n_samples & ~3; + if ((n_samples & 3) == 0) + unrolled -= 4; + } + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in = _mm_i32gather_epi32((int*)s, mask1, 1); + in = _mm_slli_epi32(in, 8); + in = _mm_srai_epi32(in, 8); + out = _mm_cvtepi32_ps(in); + out = _mm_mul_ps(out, factor); + _mm_store_ps(&d0[n], out); + s += 12 * n_channels; + } + for(; n < n_samples; n++) { + out = _mm_cvtsi32_ss(factor, s24_to_s32(*(int24_t*)s)); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += 3 * n_channels; + } +} + +static void +conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int8_t *s = src; + float *d0 = dst[0], *d1 = dst[1]; + uint32_t n, unrolled; + __m128i in[2]; + __m128 out[2], factor = _mm_set1_ps(1.0f / S24_SCALE); + __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); + + if (SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16) && + n_samples > 0) { + unrolled = n_samples & ~3; + if ((n_samples & 3) == 0) + unrolled -= 4; + } + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_i32gather_epi32((int*)&s[0], mask1, 1); + in[1] = _mm_i32gather_epi32((int*)&s[3], mask1, 1); + + in[0] = _mm_slli_epi32(in[0], 8); + in[1] = _mm_slli_epi32(in[1], 8); + + in[0] = _mm_srai_epi32(in[0], 8); + in[1] = _mm_srai_epi32(in[1], 8); + + out[0] = _mm_cvtepi32_ps(in[0]); + out[1] = _mm_cvtepi32_ps(in[1]); + + out[0] = _mm_mul_ps(out[0], factor); + out[1] = _mm_mul_ps(out[1], factor); + + _mm_store_ps(&d0[n], out[0]); + _mm_store_ps(&d1[n], out[1]); + + s += 12 * n_channels; + } + for(; n < n_samples; n++) { + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+0))); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+1))); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_mul_ss(out[1], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + s += 3 * n_channels; + } +} +static void +conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int8_t *s = src; + float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; + uint32_t n, unrolled; + __m128i in[4]; + __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); + __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); + + if (SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16) && + SPA_IS_ALIGNED(d2, 16) && + SPA_IS_ALIGNED(d3, 16) && + n_samples > 0) { + unrolled = n_samples & ~3; + if ((n_samples & 3) == 0) + unrolled -= 4; + } + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_i32gather_epi32((int*)&s[0], mask1, 1); + in[1] = _mm_i32gather_epi32((int*)&s[3], mask1, 1); + in[2] = _mm_i32gather_epi32((int*)&s[6], mask1, 1); + in[3] = _mm_i32gather_epi32((int*)&s[9], mask1, 1); + + in[0] = _mm_slli_epi32(in[0], 8); + in[1] = _mm_slli_epi32(in[1], 8); + in[2] = _mm_slli_epi32(in[2], 8); + in[3] = _mm_slli_epi32(in[3], 8); + + in[0] = _mm_srai_epi32(in[0], 8); + in[1] = _mm_srai_epi32(in[1], 8); + in[2] = _mm_srai_epi32(in[2], 8); + in[3] = _mm_srai_epi32(in[3], 8); + + out[0] = _mm_cvtepi32_ps(in[0]); + out[1] = _mm_cvtepi32_ps(in[1]); + out[2] = _mm_cvtepi32_ps(in[2]); + out[3] = _mm_cvtepi32_ps(in[3]); + + out[0] = _mm_mul_ps(out[0], factor); + out[1] = _mm_mul_ps(out[1], factor); + out[2] = _mm_mul_ps(out[2], factor); + out[3] = _mm_mul_ps(out[3], factor); + + _mm_store_ps(&d0[n], out[0]); + _mm_store_ps(&d1[n], out[1]); + _mm_store_ps(&d2[n], out[2]); + _mm_store_ps(&d3[n], out[3]); + + s += 12 * n_channels; + } + for(; n < n_samples; n++) { + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+0))); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+1))); + out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+2))); + out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+3))); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_mul_ss(out[1], factor); + out[2] = _mm_mul_ss(out[2], factor); + out[3] = _mm_mul_ss(out[3], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + _mm_store_ss(&d2[n], out[2]); + _mm_store_ss(&d3[n], out[3]); + s += 3 * n_channels; + } +} + +void +conv_s24_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int8_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_s24_to_f32d_4s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples); + for(; i + 1 < n_channels; i += 2) + conv_s24_to_f32d_2s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_s24_to_f32d_1s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples); +} + + +void +conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int32_t *s = src; + float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; + uint32_t n, unrolled; + __m256i in[4]; + __m256 out[4], factor = _mm256_set1_ps(1.0f / S24_SCALE); + __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, + 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); + + if (SPA_IS_ALIGNED(d0, 32) && + SPA_IS_ALIGNED(d1, 32) && + SPA_IS_ALIGNED(d2, 32) && + SPA_IS_ALIGNED(d3, 32)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm256_i32gather_epi32((int*)&s[0], mask1, 4); + in[1] = _mm256_i32gather_epi32((int*)&s[1], mask1, 4); + in[2] = _mm256_i32gather_epi32((int*)&s[2], mask1, 4); + in[3] = _mm256_i32gather_epi32((int*)&s[3], mask1, 4); + + in[0] = _mm256_srai_epi32(in[0], 8); + in[1] = _mm256_srai_epi32(in[1], 8); + in[2] = _mm256_srai_epi32(in[2], 8); + in[3] = _mm256_srai_epi32(in[3], 8); + + out[0] = _mm256_cvtepi32_ps(in[0]); + out[1] = _mm256_cvtepi32_ps(in[1]); + out[2] = _mm256_cvtepi32_ps(in[2]); + out[3] = _mm256_cvtepi32_ps(in[3]); + + out[0] = _mm256_mul_ps(out[0], factor); + out[1] = _mm256_mul_ps(out[1], factor); + out[2] = _mm256_mul_ps(out[2], factor); + out[3] = _mm256_mul_ps(out[3], factor); + + _mm256_store_ps(&d0[n], out[0]); + _mm256_store_ps(&d1[n], out[1]); + _mm256_store_ps(&d2[n], out[2]); + _mm256_store_ps(&d3[n], out[3]); + + s += 8*n_channels; + } + for(; n < n_samples; n++) { + __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); + out[0] = _mm_cvtsi32_ss(factor, s[0] >> 8); + out[1] = _mm_cvtsi32_ss(factor, s[1] >> 8); + out[2] = _mm_cvtsi32_ss(factor, s[2] >> 8); + out[3] = _mm_cvtsi32_ss(factor, s[3] >> 8); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_mul_ss(out[1], factor); + out[2] = _mm_mul_ss(out[2], factor); + out[3] = _mm_mul_ss(out[3], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + _mm_store_ss(&d2[n], out[2]); + _mm_store_ss(&d3[n], out[3]); + s += n_channels; + } +} + +void +conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int32_t *s = src; + float *d0 = dst[0], *d1 = dst[1]; + uint32_t n, unrolled; + __m256i in[4]; + __m256 out[4], factor = _mm256_set1_ps(1.0f / S24_SCALE); + __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, + 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); + + if (SPA_IS_ALIGNED(d0, 32) && + SPA_IS_ALIGNED(d1, 32)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm256_i32gather_epi32((int*)&s[0], mask1, 4); + in[1] = _mm256_i32gather_epi32((int*)&s[1], mask1, 4); + + in[0] = _mm256_srai_epi32(in[0], 8); + in[1] = _mm256_srai_epi32(in[1], 8); + + out[0] = _mm256_cvtepi32_ps(in[0]); + out[1] = _mm256_cvtepi32_ps(in[1]); + + out[0] = _mm256_mul_ps(out[0], factor); + out[1] = _mm256_mul_ps(out[1], factor); + + _mm256_store_ps(&d0[n], out[0]); + _mm256_store_ps(&d1[n], out[1]); + + s += 8*n_channels; + } + for(; n < n_samples; n++) { + __m128 out[2], factor = _mm_set1_ps(1.0f / S24_SCALE); + out[0] = _mm_cvtsi32_ss(factor, s[0] >> 8); + out[1] = _mm_cvtsi32_ss(factor, s[1] >> 8); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_mul_ss(out[1], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + s += n_channels; + } +} + +void +conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int32_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m256i in[2]; + __m256 out[2], factor = _mm256_set1_ps(1.0f / S24_SCALE); + __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, + 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); + + if (SPA_IS_ALIGNED(d0, 32)) + unrolled = n_samples & ~15; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 16) { + in[0] = _mm256_i32gather_epi32(&s[0*n_channels], mask1, 4); + in[1] = _mm256_i32gather_epi32(&s[8*n_channels], mask1, 4); + + in[0] = _mm256_srai_epi32(in[0], 8); + in[1] = _mm256_srai_epi32(in[1], 8); + + out[0] = _mm256_cvtepi32_ps(in[0]); + out[1] = _mm256_cvtepi32_ps(in[1]); + + out[0] = _mm256_mul_ps(out[0], factor); + out[1] = _mm256_mul_ps(out[1], factor); + + _mm256_store_ps(&d0[n+0], out[0]); + _mm256_store_ps(&d0[n+8], out[1]); + + s += 16*n_channels; + } + for(; n < n_samples; n++) { + __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); + out = _mm_cvtsi32_ss(factor, s[0] >> 8); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += n_channels; + } +} + +void +conv_s32_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int32_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_s32_to_f32d_4s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); + for(; i + 1 < n_channels; i += 2) + conv_s32_to_f32d_2s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_s32_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); +} + +static void +conv_f32d_to_s32_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0]; + int32_t *d = dst; + uint32_t n, unrolled; + __m128 in[1]; + __m128i out[4]; + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_max = _mm_set1_ps(S24_MAX); + __m128 int_min = _mm_set1_ps(S24_MIN); + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); + out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); + out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); + out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); + + d[0*n_channels] = _mm_cvtsi128_si32(out[0]); + d[1*n_channels] = _mm_cvtsi128_si32(out[1]); + d[2*n_channels] = _mm_cvtsi128_si32(out[2]); + d[3*n_channels] = _mm_cvtsi128_si32(out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_load_ss(&s0[n]); + in[0] = _mm_mul_ss(in[0], scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]) << 8; + d += n_channels; + } +} + +static void +conv_f32d_to_s32_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + int32_t *d = dst; + uint32_t n, unrolled; + __m256 in[2]; + __m256i out[2], t[2]; + __m256 scale = _mm256_set1_ps(S24_SCALE); + __m256 int_min = _mm256_set1_ps(S24_MIN); + __m256 int_max = _mm256_set1_ps(S24_MAX); + + if (SPA_IS_ALIGNED(s0, 32) && + SPA_IS_ALIGNED(s1, 32)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), scale); + + in[0] = _MM256_CLAMP_PS(in[0], int_min, int_max); + in[1] = _MM256_CLAMP_PS(in[1], int_min, int_max); + + out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out[0] = _mm256_slli_epi32(out[0], 8); + out[1] = _mm256_slli_epi32(out[1], 8); + + t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ + t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ + +#ifdef __x86_64__ + *((int64_t*)(d + 0*n_channels)) = _mm256_extract_epi64(t[0], 0); + *((int64_t*)(d + 1*n_channels)) = _mm256_extract_epi64(t[0], 1); + *((int64_t*)(d + 2*n_channels)) = _mm256_extract_epi64(t[1], 0); + *((int64_t*)(d + 3*n_channels)) = _mm256_extract_epi64(t[1], 1); + *((int64_t*)(d + 4*n_channels)) = _mm256_extract_epi64(t[0], 2); + *((int64_t*)(d + 5*n_channels)) = _mm256_extract_epi64(t[0], 3); + *((int64_t*)(d + 6*n_channels)) = _mm256_extract_epi64(t[1], 2); + *((int64_t*)(d + 7*n_channels)) = _mm256_extract_epi64(t[1], 3); +#else + _mm_storel_pi((__m64*)(d + 0*n_channels), (__m128)_mm256_extracti128_si256(t[0], 0)); + _mm_storeh_pi((__m64*)(d + 1*n_channels), (__m128)_mm256_extracti128_si256(t[0], 0)); + _mm_storel_pi((__m64*)(d + 2*n_channels), (__m128)_mm256_extracti128_si256(t[1], 0)); + _mm_storeh_pi((__m64*)(d + 3*n_channels), (__m128)_mm256_extracti128_si256(t[1], 0)); + _mm_storel_pi((__m64*)(d + 4*n_channels), (__m128)_mm256_extracti128_si256(t[0], 1)); + _mm_storeh_pi((__m64*)(d + 5*n_channels), (__m128)_mm256_extracti128_si256(t[0], 1)); + _mm_storel_pi((__m64*)(d + 6*n_channels), (__m128)_mm256_extracti128_si256(t[1], 1)); + _mm_storeh_pi((__m64*)(d + 7*n_channels), (__m128)_mm256_extracti128_si256(t[1], 1)); +#endif + d += 8*n_channels; + } + for(; n < n_samples; n++) { + __m128 in[2]; + __m128i out[2]; + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); + + in[0] = _mm_load_ss(&s0[n]); + in[1] = _mm_load_ss(&s1[n]); + + in[0] = _mm_unpacklo_ps(in[0], in[1]); + + in[0] = _mm_mul_ps(in[0], scale); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); + _mm_storel_epi64((__m128i*)d, out[0]); + d += n_channels; + } +} + +static void +conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; + int32_t *d = dst; + uint32_t n, unrolled; + __m256 in[4]; + __m256i out[4], t[4]; + __m256 scale = _mm256_set1_ps(S24_SCALE); + __m256 int_min = _mm256_set1_ps(S24_MIN); + __m256 int_max = _mm256_set1_ps(S24_MAX); + + if (SPA_IS_ALIGNED(s0, 32) && + SPA_IS_ALIGNED(s1, 32) && + SPA_IS_ALIGNED(s2, 32) && + SPA_IS_ALIGNED(s3, 32)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), scale); + in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), scale); + in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), scale); + + in[0] = _MM256_CLAMP_PS(in[0], int_min, int_max); + in[1] = _MM256_CLAMP_PS(in[1], int_min, int_max); + in[2] = _MM256_CLAMP_PS(in[2], int_min, int_max); + in[3] = _MM256_CLAMP_PS(in[3], int_min, int_max); + + out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out[2] = _mm256_cvtps_epi32(in[2]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ + out[3] = _mm256_cvtps_epi32(in[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + out[0] = _mm256_slli_epi32(out[0], 8); + out[1] = _mm256_slli_epi32(out[1], 8); + out[2] = _mm256_slli_epi32(out[2], 8); + out[3] = _mm256_slli_epi32(out[3], 8); + + t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ + t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ + t[2] = _mm256_unpacklo_epi32(out[2], out[3]); /* c0 d0 c1 d1 c4 d4 c5 d5 */ + t[3] = _mm256_unpackhi_epi32(out[2], out[3]); /* c2 d2 c3 d3 c6 d6 c7 d7 */ + + out[0] = _mm256_unpacklo_epi64(t[0], t[2]); /* a0 b0 c0 d0 a4 b4 c4 d4 */ + out[1] = _mm256_unpackhi_epi64(t[0], t[2]); /* a1 b1 c1 d1 a5 b5 c5 d5 */ + out[2] = _mm256_unpacklo_epi64(t[1], t[3]); /* a2 b2 c2 d2 a6 b6 c6 d6 */ + out[3] = _mm256_unpackhi_epi64(t[1], t[3]); /* a3 b3 c3 d3 a7 b7 c7 d7 */ + + _mm_storeu_si128((__m128i*)(d + 0*n_channels), _mm256_extracti128_si256(out[0], 0)); + _mm_storeu_si128((__m128i*)(d + 1*n_channels), _mm256_extracti128_si256(out[1], 0)); + _mm_storeu_si128((__m128i*)(d + 2*n_channels), _mm256_extracti128_si256(out[2], 0)); + _mm_storeu_si128((__m128i*)(d + 3*n_channels), _mm256_extracti128_si256(out[3], 0)); + _mm_storeu_si128((__m128i*)(d + 4*n_channels), _mm256_extracti128_si256(out[0], 1)); + _mm_storeu_si128((__m128i*)(d + 5*n_channels), _mm256_extracti128_si256(out[1], 1)); + _mm_storeu_si128((__m128i*)(d + 6*n_channels), _mm256_extracti128_si256(out[2], 1)); + _mm_storeu_si128((__m128i*)(d + 7*n_channels), _mm256_extracti128_si256(out[3], 1)); + d += 8*n_channels; + } + for(; n < n_samples; n++) { + __m128 in[4]; + __m128i out[4]; + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); + + in[0] = _mm_load_ss(&s0[n]); + in[1] = _mm_load_ss(&s1[n]); + in[2] = _mm_load_ss(&s2[n]); + in[3] = _mm_load_ss(&s3[n]); + + in[0] = _mm_unpacklo_ps(in[0], in[2]); + in[1] = _mm_unpacklo_ps(in[1], in[3]); + in[0] = _mm_unpacklo_ps(in[0], in[1]); + + in[0] = _mm_mul_ps(in[0], scale); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); + _mm_storeu_si128((__m128i*)d, out[0]); + d += n_channels; + } +} + +void +conv_f32d_to_s32_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int32_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_f32d_to_s32_4s_avx2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i + 1 < n_channels; i += 2) + conv_f32d_to_s32_2s_avx2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_f32d_to_s32_1s_avx2(conv, &d[i], &src[i], n_channels, n_samples); +} + +static void +conv_f32d_to_s16_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0]; + int16_t *d = dst; + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_packs_epi32(out[0], out[1]); + + d[0*n_channels] = _mm_extract_epi16(out[0], 0); + d[1*n_channels] = _mm_extract_epi16(out[0], 1); + d[2*n_channels] = _mm_extract_epi16(out[0], 2); + d[3*n_channels] = _mm_extract_epi16(out[0], 3); + d[4*n_channels] = _mm_extract_epi16(out[0], 4); + d[5*n_channels] = _mm_extract_epi16(out[0], 5); + d[6*n_channels] = _mm_extract_epi16(out[0], 6); + d[7*n_channels] = _mm_extract_epi16(out[0], 7); + d += 8*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]); + d += n_channels; + } +} + +static void +conv_f32d_to_s16_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + int16_t *d = dst; + uint32_t n, unrolled; + __m256 in[2]; + __m256i out[4], t[2]; + __m256 int_scale = _mm256_set1_ps(S16_SCALE); + + if (SPA_IS_ALIGNED(s0, 32) && + SPA_IS_ALIGNED(s1, 32)) + unrolled = n_samples & ~15; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_scale); + + out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + + t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ + t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ + + out[0] = _mm256_packs_epi32(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ + + *((int32_t*)(d + 0*n_channels)) = _mm256_extract_epi32(out[0],0); + *((int32_t*)(d + 1*n_channels)) = _mm256_extract_epi32(out[0],1); + *((int32_t*)(d + 2*n_channels)) = _mm256_extract_epi32(out[0],2); + *((int32_t*)(d + 3*n_channels)) = _mm256_extract_epi32(out[0],3); + *((int32_t*)(d + 4*n_channels)) = _mm256_extract_epi32(out[0],4); + *((int32_t*)(d + 5*n_channels)) = _mm256_extract_epi32(out[0],5); + *((int32_t*)(d + 6*n_channels)) = _mm256_extract_epi32(out[0],6); + *((int32_t*)(d + 7*n_channels)) = _mm256_extract_epi32(out[0],7); + + d += 8*n_channels; + } + for(; n < n_samples; n++) { + __m128 in[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + d[0] = _mm_cvtss_si32(in[0]); + d[1] = _mm_cvtss_si32(in[1]); + d += n_channels; + } +} + +static void +conv_f32d_to_s16_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; + int16_t *d = dst; + uint32_t n, unrolled; + __m256 in[4]; + __m256i out[4], t[4]; + __m256 int_scale = _mm256_set1_ps(S16_SCALE); + + if (SPA_IS_ALIGNED(s0, 32) && + SPA_IS_ALIGNED(s1, 32) && + SPA_IS_ALIGNED(s2, 32) && + SPA_IS_ALIGNED(s3, 32)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), int_scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), int_scale); + in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), int_scale); + in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), int_scale); + + t[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + t[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + t[2] = _mm256_cvtps_epi32(in[2]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ + t[3] = _mm256_cvtps_epi32(in[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + + t[0] = _mm256_packs_epi32(t[0], t[2]); /* a0 a1 a2 a3 c0 c1 c2 c3 a4 a5 a6 a7 c4 c5 c6 c7 */ + t[1] = _mm256_packs_epi32(t[1], t[3]); /* b0 b1 b2 b3 d0 d1 d2 d3 b4 b5 b6 b7 d4 d5 d6 d7 */ + + out[0] = _mm256_unpacklo_epi16(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ + out[1] = _mm256_unpackhi_epi16(t[0], t[1]); /* c0 d0 c1 d1 c2 d2 c3 d3 c4 d4 c5 d5 c6 d6 c7 d7 */ + + out[2] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 c0 d0 a1 b1 c1 d1 a4 b4 c4 d4 a5 b5 c5 d5 */ + out[3] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 c2 d2 a3 b3 c3 d3 a6 b6 c6 d6 a7 b7 c7 d7 */ + +#ifdef __x86_64__ + *(int64_t*)(d + 0*n_channels) = _mm256_extract_epi64(out[2], 0); /* a0 b0 c0 d0 */ + *(int64_t*)(d + 1*n_channels) = _mm256_extract_epi64(out[2], 1); /* a1 b1 c1 d1 */ + *(int64_t*)(d + 2*n_channels) = _mm256_extract_epi64(out[3], 0); /* a2 b2 c2 d2 */ + *(int64_t*)(d + 3*n_channels) = _mm256_extract_epi64(out[3], 1); /* a3 b3 c3 d3 */ + *(int64_t*)(d + 4*n_channels) = _mm256_extract_epi64(out[2], 2); /* a4 b4 c4 d4 */ + *(int64_t*)(d + 5*n_channels) = _mm256_extract_epi64(out[2], 3); /* a5 b5 c5 d5 */ + *(int64_t*)(d + 6*n_channels) = _mm256_extract_epi64(out[3], 2); /* a6 b6 c6 d6 */ + *(int64_t*)(d + 7*n_channels) = _mm256_extract_epi64(out[3], 3); /* a7 b7 c7 d7 */ +#else + _mm_storel_pi((__m64*)(d + 0*n_channels), (__m128)_mm256_extracti128_si256(out[2], 0)); + _mm_storeh_pi((__m64*)(d + 1*n_channels), (__m128)_mm256_extracti128_si256(out[2], 0)); + _mm_storel_pi((__m64*)(d + 2*n_channels), (__m128)_mm256_extracti128_si256(out[3], 0)); + _mm_storeh_pi((__m64*)(d + 3*n_channels), (__m128)_mm256_extracti128_si256(out[3], 0)); + _mm_storel_pi((__m64*)(d + 4*n_channels), (__m128)_mm256_extracti128_si256(out[2], 1)); + _mm_storeh_pi((__m64*)(d + 5*n_channels), (__m128)_mm256_extracti128_si256(out[2], 1)); + _mm_storel_pi((__m64*)(d + 6*n_channels), (__m128)_mm256_extracti128_si256(out[3], 1)); + _mm_storeh_pi((__m64*)(d + 7*n_channels), (__m128)_mm256_extracti128_si256(out[3], 1)); +#endif + + d += 8*n_channels; + } + for(; n < n_samples; n++) { + __m128 in[4]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); + in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); + d[0] = _mm_cvtss_si32(in[0]); + d[1] = _mm_cvtss_si32(in[1]); + d[2] = _mm_cvtss_si32(in[2]); + d[3] = _mm_cvtss_si32(in[3]); + d += n_channels; + } +} + +void +conv_f32d_to_s16_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int16_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_f32d_to_s16_4s_avx2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i + 1 < n_channels; i += 2) + conv_f32d_to_s16_2s_avx2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_f32d_to_s16_1s_avx2(conv, &d[i], &src[i], n_channels, n_samples); +} + +void +conv_f32d_to_s16_4_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; + int16_t *d = dst[0]; + uint32_t n, unrolled; + __m256 in[4]; + __m256i out[4], t[4]; + __m256 int_scale = _mm256_set1_ps(S16_SCALE); + + if (SPA_IS_ALIGNED(s0, 32) && + SPA_IS_ALIGNED(s1, 32) && + SPA_IS_ALIGNED(s2, 32) && + SPA_IS_ALIGNED(s3, 32)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), int_scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), int_scale); + in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), int_scale); + in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), int_scale); + + t[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + t[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + t[2] = _mm256_cvtps_epi32(in[2]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ + t[3] = _mm256_cvtps_epi32(in[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + + t[0] = _mm256_packs_epi32(t[0], t[2]); /* a0 a1 a2 a3 c0 c1 c2 c3 a4 a5 a6 a7 c4 c5 c6 c7 */ + t[1] = _mm256_packs_epi32(t[1], t[3]); /* b0 b1 b2 b3 d0 d1 d2 d3 b4 b5 b6 b7 d4 d5 d6 d7 */ + + out[0] = _mm256_unpacklo_epi16(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ + out[1] = _mm256_unpackhi_epi16(t[0], t[1]); /* c0 d0 c1 d1 c2 d2 c3 d3 c4 d4 c5 d5 c6 d6 c7 d7 */ + + t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 c0 d0 a1 b1 c1 d1 a4 b4 c4 d4 a5 b5 c5 d5 */ + t[2] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 c2 d2 a3 b3 c3 d3 a6 b6 c6 d6 a7 b7 c7 d7 */ + + out[0] = _mm256_inserti128_si256(t[0], _mm256_extracti128_si256(t[2], 0), 1); + out[2] = _mm256_inserti128_si256(t[2], _mm256_extracti128_si256(t[0], 1), 0); + + _mm256_store_si256((__m256i*)(d+0), out[0]); + _mm256_store_si256((__m256i*)(d+16), out[2]); + d += 32; + } + for(; n < n_samples; n++) { + __m128 in[4]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); + in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); + d[0] = _mm_cvtss_si32(in[0]); + d[1] = _mm_cvtss_si32(in[1]); + d[2] = _mm_cvtss_si32(in[2]); + d[3] = _mm_cvtss_si32(in[3]); + d += 4; + } +} +void +conv_f32d_to_s16_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + int16_t *d = dst[0]; + uint32_t n, unrolled; + __m256 in[4]; + __m256i out[4], t[4]; + __m256 int_scale = _mm256_set1_ps(S16_SCALE); + + if (SPA_IS_ALIGNED(s0, 32) && + SPA_IS_ALIGNED(s1, 32)) + unrolled = n_samples & ~15; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 16) { + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_scale); + in[2] = _mm256_mul_ps(_mm256_load_ps(&s0[n+8]), int_scale); + in[3] = _mm256_mul_ps(_mm256_load_ps(&s1[n+8]), int_scale); + + out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out[2] = _mm256_cvtps_epi32(in[2]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out[3] = _mm256_cvtps_epi32(in[3]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + + t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ + t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ + t[2] = _mm256_unpacklo_epi32(out[2], out[3]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ + t[3] = _mm256_unpackhi_epi32(out[2], out[3]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ + + out[0] = _mm256_packs_epi32(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ + out[1] = _mm256_packs_epi32(t[2], t[3]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ + + _mm256_store_si256((__m256i*)(d+0), out[0]); + _mm256_store_si256((__m256i*)(d+16), out[1]); + + d += 32; + } + for(; n < n_samples; n++) { + __m128 in[4]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + d[0] = _mm_cvtss_si32(in[0]); + d[1] = _mm_cvtss_si32(in[1]); + d += 2; + } +} diff --git a/spa/plugins/audioconvert/fmt-ops-c.c b/spa/plugins/audioconvert/fmt-ops-c.c new file mode 100644 index 0000000..92ecb5a --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops-c.c @@ -0,0 +1,433 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <spa/support/cpu.h> +#include <spa/utils/defs.h> +#include <spa/param/audio/format-utils.h> + +#include "fmt-ops.h" +#include "law.h" + +#define MAKE_COPY(size) \ +void conv_copy ##size## d_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t i, n_channels = conv->n_channels; \ + for (i = 0; i < n_channels; i++) \ + spa_memcpy(dst[i], src[i], n_samples * (size>>3)); \ +} \ +void conv_copy ##size## _c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + spa_memcpy(dst[0], src[0], n_samples * conv->n_channels * (size>>3)); \ +} + +MAKE_COPY(8); +MAKE_COPY(16); +MAKE_COPY(24); +MAKE_COPY(32); +MAKE_COPY(64); + +#define MAKE_D_TO_D(sname,stype,dname,dtype,func) \ +void conv_ ##sname## d_to_ ##dname## d_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t i, j, n_channels = conv->n_channels; \ + for (i = 0; i < n_channels; i++) { \ + const stype *s = src[i]; \ + dtype *d = dst[i]; \ + for (j = 0; j < n_samples; j++) \ + d[j] = func (s[j]); \ + } \ +} + +#define MAKE_I_TO_I(sname,stype,dname,dtype,func) \ +void conv_ ##sname## _to_ ##dname## _c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t j; \ + const stype *s = src[0]; \ + dtype *d = dst[0]; \ + n_samples *= conv->n_channels; \ + for (j = 0; j < n_samples; j++) \ + d[j] = func (s[j]); \ +} + +#define MAKE_I_TO_D(sname,stype,dname,dtype,func) \ +void conv_ ##sname## _to_ ##dname## d_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + const stype *s = src[0]; \ + dtype **d = (dtype**)dst; \ + uint32_t i, j, n_channels = conv->n_channels; \ + for (j = 0; j < n_samples; j++) { \ + for (i = 0; i < n_channels; i++) \ + d[i][j] = func (*s++); \ + } \ +} + +#define MAKE_D_TO_I(sname,stype,dname,dtype,func) \ +void conv_ ##sname## d_to_ ##dname## _c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + const stype **s = (const stype **)src; \ + dtype *d = dst[0]; \ + uint32_t i, j, n_channels = conv->n_channels; \ + for (j = 0; j < n_samples; j++) { \ + for (i = 0; i < n_channels; i++) \ + *d++ = func (s[i][j]); \ + } \ +} + +/* to f32 */ +MAKE_D_TO_D(u8, uint8_t, f32, float, U8_TO_F32); +MAKE_I_TO_I(u8, uint8_t, f32, float, U8_TO_F32); +MAKE_I_TO_D(u8, uint8_t, f32, float, U8_TO_F32); +MAKE_D_TO_I(u8, uint8_t, f32, float, U8_TO_F32); + +MAKE_D_TO_D(s8, int8_t, f32, float, S8_TO_F32); +MAKE_I_TO_I(s8, int8_t, f32, float, S8_TO_F32); +MAKE_I_TO_D(s8, int8_t, f32, float, S8_TO_F32); +MAKE_D_TO_I(s8, int8_t, f32, float, S8_TO_F32); + +MAKE_I_TO_D(alaw, uint8_t, f32, float, alaw_to_f32); +MAKE_I_TO_D(ulaw, uint8_t, f32, float, ulaw_to_f32); + +MAKE_I_TO_I(u16, uint16_t, f32, float, U16_TO_F32); +MAKE_I_TO_D(u16, uint16_t, f32, float, U16_TO_F32); + +MAKE_D_TO_D(s16, int16_t, f32, float, S16_TO_F32); +MAKE_I_TO_I(s16, int16_t, f32, float, S16_TO_F32); +MAKE_I_TO_D(s16, int16_t, f32, float, S16_TO_F32); +MAKE_D_TO_I(s16, int16_t, f32, float, S16_TO_F32); +MAKE_I_TO_D(s16s, uint16_t, f32, float, S16S_TO_F32); + +MAKE_I_TO_I(u32, uint32_t, f32, float, U32_TO_F32); +MAKE_I_TO_D(u32, uint32_t, f32, float, U32_TO_F32); + +MAKE_D_TO_D(s32, int32_t, f32, float, S32_TO_F32); +MAKE_I_TO_I(s32, int32_t, f32, float, S32_TO_F32); +MAKE_I_TO_D(s32, int32_t, f32, float, S32_TO_F32); +MAKE_D_TO_I(s32, int32_t, f32, float, S32_TO_F32); +MAKE_I_TO_D(s32s, uint32_t, f32, float, S32S_TO_F32); + +MAKE_I_TO_I(u24, uint24_t, f32, float, U24_TO_F32); +MAKE_I_TO_D(u24, uint24_t, f32, float, U24_TO_F32); + +MAKE_D_TO_D(s24, int24_t, f32, float, S24_TO_F32); +MAKE_I_TO_I(s24, int24_t, f32, float, S24_TO_F32); +MAKE_I_TO_D(s24, int24_t, f32, float, S24_TO_F32); +MAKE_D_TO_I(s24, int24_t, f32, float, S24_TO_F32); +MAKE_I_TO_D(s24s, int24_t, f32, float, S24S_TO_F32); + +MAKE_I_TO_I(u24_32, uint32_t, f32, float, U24_32_TO_F32); +MAKE_I_TO_D(u24_32, uint32_t, f32, float, U24_32_TO_F32); + +MAKE_D_TO_D(s24_32, int32_t, f32, float, S24_32_TO_F32); +MAKE_I_TO_I(s24_32, int32_t, f32, float, S24_32_TO_F32); +MAKE_I_TO_D(s24_32, int32_t, f32, float, S24_32_TO_F32); +MAKE_D_TO_I(s24_32, int32_t, f32, float, S24_32_TO_F32); +MAKE_I_TO_D(s24_32s, uint32_t, f32, float, S24_32S_TO_F32); + +MAKE_D_TO_D(f64, double, f32, float, (float)); +MAKE_I_TO_I(f64, double, f32, float, (float)); +MAKE_I_TO_D(f64, double, f32, float, (float)); +MAKE_D_TO_I(f64, double, f32, float, (float)); +MAKE_I_TO_D(f64s, uint64_t, f32, float, (float)F64S_TO_F64); + +/* from f32 */ +MAKE_D_TO_D(f32, float, u8, uint8_t, F32_TO_U8); +MAKE_I_TO_I(f32, float, u8, uint8_t, F32_TO_U8); +MAKE_I_TO_D(f32, float, u8, uint8_t, F32_TO_U8); +MAKE_D_TO_I(f32, float, u8, uint8_t, F32_TO_U8); + +MAKE_D_TO_D(f32, float, s8, int8_t, F32_TO_S8); +MAKE_I_TO_I(f32, float, s8, int8_t, F32_TO_S8); +MAKE_I_TO_D(f32, float, s8, int8_t, F32_TO_S8); +MAKE_D_TO_I(f32, float, s8, int8_t, F32_TO_S8); + +MAKE_D_TO_I(f32, float, alaw, uint8_t, f32_to_alaw); +MAKE_D_TO_I(f32, float, ulaw, uint8_t, f32_to_ulaw); + +MAKE_I_TO_I(f32, float, u16, uint16_t, F32_TO_U16); +MAKE_D_TO_I(f32, float, u16, uint16_t, F32_TO_U16); + +MAKE_D_TO_D(f32, float, s16, int16_t, F32_TO_S16); +MAKE_I_TO_I(f32, float, s16, int16_t, F32_TO_S16); +MAKE_I_TO_D(f32, float, s16, int16_t, F32_TO_S16); +MAKE_D_TO_I(f32, float, s16, int16_t, F32_TO_S16); +MAKE_D_TO_I(f32, float, s16s, uint16_t, F32_TO_S16S); + +MAKE_I_TO_I(f32, float, u32, uint32_t, F32_TO_U32); +MAKE_D_TO_I(f32, float, u32, uint32_t, F32_TO_U32); + +MAKE_D_TO_D(f32, float, s32, int32_t, F32_TO_S32); +MAKE_I_TO_I(f32, float, s32, int32_t, F32_TO_S32); +MAKE_I_TO_D(f32, float, s32, int32_t, F32_TO_S32); +MAKE_D_TO_I(f32, float, s32, int32_t, F32_TO_S32); +MAKE_D_TO_I(f32, float, s32s, uint32_t, F32_TO_S32S); + +MAKE_I_TO_I(f32, float, u24, uint24_t, F32_TO_U24); +MAKE_D_TO_I(f32, float, u24, uint24_t, F32_TO_U24); + +MAKE_D_TO_D(f32, float, s24, int24_t, F32_TO_S24); +MAKE_I_TO_I(f32, float, s24, int24_t, F32_TO_S24); +MAKE_I_TO_D(f32, float, s24, int24_t, F32_TO_S24); +MAKE_D_TO_I(f32, float, s24, int24_t, F32_TO_S24); +MAKE_D_TO_I(f32, float, s24s, int24_t, F32_TO_S24S); + +MAKE_I_TO_I(f32, float, u24_32, uint32_t, F32_TO_U24_32); +MAKE_D_TO_I(f32, float, u24_32, uint32_t, F32_TO_U24_32); + +MAKE_D_TO_D(f32, float, s24_32, int32_t, F32_TO_S24_32); +MAKE_I_TO_I(f32, float, s24_32, int32_t, F32_TO_S24_32); +MAKE_I_TO_D(f32, float, s24_32, int32_t, F32_TO_S24_32); +MAKE_D_TO_I(f32, float, s24_32, int32_t, F32_TO_S24_32); +MAKE_D_TO_I(f32, float, s24_32s, uint32_t, F32_TO_S24_32S); + +MAKE_D_TO_D(f32, float, f64, double, (double)); +MAKE_I_TO_I(f32, float, f64, double, (double)); +MAKE_I_TO_D(f32, float, f64, double, (double)); +MAKE_D_TO_I(f32, float, f64, double, (double)); +MAKE_D_TO_I(f32, float, f64s, uint64_t, F64_TO_F64S); + + +static inline int32_t +lcnoise(uint32_t *state) +{ + *state = (*state * 96314165) + 907633515; + return (int32_t)(*state); +} + +void conv_noise_none_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + memset(noise, 0, n_samples * sizeof(float)); +} + +void conv_noise_rect_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + uint32_t *state = &conv->random[0]; + const float scale = conv->scale; + + for (n = 0; n < n_samples; n++) + noise[n] = lcnoise(state) * scale; +} + +void conv_noise_tri_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const float scale = conv->scale; + uint32_t *state = &conv->random[0]; + + for (n = 0; n < n_samples; n++) + noise[n] = (lcnoise(state) - lcnoise(state)) * scale; +} + +void conv_noise_tri_hf_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const float scale = conv->scale; + uint32_t *state = &conv->random[0]; + int32_t *prev = &conv->prev[0], old, new; + + old = *prev; + for (n = 0; n < n_samples; n++) { + new = lcnoise(state); + noise[n] = (new - old) * scale; + old = new; + } + *prev = old; +} + +void conv_noise_pattern_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const float scale = conv->scale; + int32_t *prev = &conv->prev[0], old; + + old = *prev; + for (n = 0; n < n_samples; n++) + noise[n] = scale * (1-((old++>>10)&1)); + *prev = old; +} + +#define MAKE_D_noise(dname,dtype,func) \ +void conv_f32d_to_ ##dname## d_noise_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + float *noise = conv->noise; \ + convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ + for (i = 0; i < n_channels; i++) { \ + const float *s = src[i]; \ + dtype *d = dst[i]; \ + for (j = 0; j < n_samples;) { \ + chunk = SPA_MIN(n_samples - j, noise_size); \ + for (k = 0; k < chunk; k++, j++) \ + d[j] = func (s[j], noise[k]); \ + } \ + } \ +} + +#define MAKE_I_noise(dname,dtype,func) \ +void conv_f32d_to_ ##dname## _noise_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + const float **s = (const float **) src; \ + dtype *d = dst[0]; \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + float *noise = conv->noise; \ + convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ + for (j = 0; j < n_samples;) { \ + chunk = SPA_MIN(n_samples - j, noise_size); \ + for (k = 0; k < chunk; k++, j++) { \ + for (i = 0; i < n_channels; i++) \ + *d++ = func (s[i][j], noise[k]); \ + } \ + } \ +} + +MAKE_D_noise(u8, uint8_t, F32_TO_U8_D); +MAKE_I_noise(u8, uint8_t, F32_TO_U8_D); +MAKE_D_noise(s8, int8_t, F32_TO_S8_D); +MAKE_I_noise(s8, int8_t, F32_TO_S8_D); +MAKE_D_noise(s16, int16_t, F32_TO_S16_D); +MAKE_I_noise(s16, int16_t, F32_TO_S16_D); +MAKE_I_noise(s16s, uint16_t, F32_TO_S16S_D); +MAKE_D_noise(s32, int32_t, F32_TO_S32_D); +MAKE_I_noise(s32, int32_t, F32_TO_S32_D); +MAKE_I_noise(s32s, uint32_t, F32_TO_S32S_D); +MAKE_D_noise(s24, int24_t, F32_TO_S24_D); +MAKE_I_noise(s24, int24_t, F32_TO_S24_D); +MAKE_I_noise(s24s, int24_t, F32_TO_S24_D); +MAKE_D_noise(s24_32, int32_t, F32_TO_S24_32_D); +MAKE_I_noise(s24_32, int32_t, F32_TO_S24_32_D); +MAKE_I_noise(s24_32s, int32_t, F32_TO_S24_32S_D); + +#define SHAPER(type,s,scale,offs,sh,min,max,d) \ +({ \ + type t; \ + float v = s * scale + offs; \ + for (n = 0; n < n_ns; n++) \ + v += sh->e[idx + n] * ns[n]; \ + t = FTOI(type, v, 1.0f, 0.0f, d, min, max); \ + idx = (idx - 1) & NS_MASK; \ + sh->e[idx] = sh->e[idx + NS_MAX] = v - t; \ + t; \ +}) + +#define F32_TO_U8_SH(s,sh,d) SHAPER(uint8_t, s, U8_SCALE, U8_OFFS, sh, U8_MIN, U8_MAX, d) +#define F32_TO_S8_SH(s,sh,d) SHAPER(int8_t, s, S8_SCALE, 0, sh, S8_MIN, S8_MAX, d) +#define F32_TO_S16_SH(s,sh,d) SHAPER(int16_t, s, S16_SCALE, 0, sh, S16_MIN, S16_MAX, d) +#define F32_TO_S16S_SH(s,sh,d) bswap_16(F32_TO_S16_SH(s,sh,d)) + +#define MAKE_D_shaped(dname,dtype,func) \ +void conv_f32d_to_ ##dname## d_shaped_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + float *noise = conv->noise; \ + const float *ns = conv->ns; \ + uint32_t n, n_ns = conv->n_ns; \ + convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ + for (i = 0; i < n_channels; i++) { \ + const float *s = src[i]; \ + dtype *d = dst[i]; \ + struct shaper *sh = &conv->shaper[i]; \ + uint32_t idx = sh->idx; \ + for (j = 0; j < n_samples;) { \ + chunk = SPA_MIN(n_samples - j, noise_size); \ + for (k = 0; k < chunk; k++, j++) \ + d[j] = func (s[j], sh, noise[k]); \ + } \ + sh->idx = idx; \ + } \ +} + +#define MAKE_I_shaped(dname,dtype,func) \ +void conv_f32d_to_ ##dname## _shaped_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + dtype *d0 = dst[0]; \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + float *noise = conv->noise; \ + const float *ns = conv->ns; \ + uint32_t n, n_ns = conv->n_ns; \ + convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ + for (i = 0; i < n_channels; i++) { \ + const float *s = src[i]; \ + dtype *d = &d0[i]; \ + struct shaper *sh = &conv->shaper[i]; \ + uint32_t idx = sh->idx; \ + for (j = 0; j < n_samples;) { \ + chunk = SPA_MIN(n_samples - j, noise_size); \ + for (k = 0; k < chunk; k++, j++) \ + d[j*n_channels] = func (s[j], sh, noise[k]); \ + } \ + sh->idx = idx; \ + } \ +} + +MAKE_D_shaped(u8, uint8_t, F32_TO_U8_SH); +MAKE_I_shaped(u8, uint8_t, F32_TO_U8_SH); +MAKE_D_shaped(s8, int8_t, F32_TO_S8_SH); +MAKE_I_shaped(s8, int8_t, F32_TO_S8_SH); +MAKE_D_shaped(s16, int16_t, F32_TO_S16_SH); +MAKE_I_shaped(s16, int16_t, F32_TO_S16_SH); +MAKE_I_shaped(s16s, uint16_t, F32_TO_S16S_SH); + +#define MAKE_DEINTERLEAVE(size1,size2, type,func) \ + MAKE_I_TO_D(size1,type,size2,type,func) + +MAKE_DEINTERLEAVE(8, 8, uint8_t, (uint8_t)); +MAKE_DEINTERLEAVE(16, 16, uint16_t, (uint16_t)); +MAKE_DEINTERLEAVE(24, 24, uint24_t, (uint24_t)); +MAKE_DEINTERLEAVE(32, 32, uint32_t, (uint32_t)); +MAKE_DEINTERLEAVE(32s, 32, uint32_t, bswap_32); +MAKE_DEINTERLEAVE(64, 64, uint64_t, (uint64_t)); + +#define MAKE_INTERLEAVE(size1,size2,type,func) \ + MAKE_D_TO_I(size1,type,size2,type,func) + +MAKE_INTERLEAVE(8, 8, uint8_t, (uint8_t)); +MAKE_INTERLEAVE(16, 16, uint16_t, (uint16_t)); +MAKE_INTERLEAVE(24, 24, uint24_t, (uint24_t)); +MAKE_INTERLEAVE(32, 32, uint32_t, (uint32_t)); +MAKE_INTERLEAVE(32, 32s, uint32_t, bswap_32); +MAKE_INTERLEAVE(64, 64, uint64_t, (uint64_t)); diff --git a/spa/plugins/audioconvert/fmt-ops-neon.c b/spa/plugins/audioconvert/fmt-ops-neon.c new file mode 100644 index 0000000..e6c8b84 --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops-neon.c @@ -0,0 +1,487 @@ +/* Spa + * + * Copyright © 2020 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <arm_neon.h> + +#include "fmt-ops.h" + +void +conv_s16_to_f32d_2_neon(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int16_t *s = src[0]; + float *d0 = dst[0], *d1 = dst[1]; + unsigned int remainder = n_samples & 7; + n_samples -= remainder; + +#ifdef __aarch64__ + asm volatile( + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " ld2 {v2.8h, v3.8h}, [%[s]], #32\n" + " subs %w[n_samples], %w[n_samples], #8\n" + " sxtl v0.4s, v2.4h\n" + " sxtl2 v1.4s, v2.8h\n" + " sxtl v2.4s, v3.4h\n" + " sxtl2 v3.4s, v3.8h\n" + " scvtf v0.4s, v0.4s, #15\n" + " scvtf v1.4s, v1.4s, #15\n" + " scvtf v2.4s, v2.4s, #15\n" + " scvtf v3.4s, v3.4s, #15\n" + " st1 {v0.4s, v1.4s}, [%[d0]], #32\n" + " st1 {v2.4s, v3.4s}, [%[d1]], #32\n" + " b.ne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " ld2 { v0.h, v1.h }[0], [%[s]], #4\n" + " subs %[remainder], %[remainder], #1\n" + " sshll v2.4s, v0.4h, #0\n" + " sshll v3.4s, v1.4h, #0\n" + " scvtf v0.4s, v2.4s, #15\n" + " scvtf v1.4s, v3.4s, #15\n" + " st1 { v0.s }[0], [%[d0]], #4\n" + " st1 { v1.s }[0], [%[d1]], #4\n" + " bne 3b\n" + "4:" + : [d0] "+r" (d0), [d1] "+r" (d1), [s] "+r" (s), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : : "v0", "v1", "v2", "v3", "memory", "cc"); +#else + asm volatile( + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " vld2.16 {d0-d3}, [%[s]]!\n" + " subs %[n_samples], #8\n" + " vmovl.s16 q3, d3\n" + " vmovl.s16 q2, d2\n" + " vmovl.s16 q1, d1\n" + " vmovl.s16 q0, d0\n" + " vcvt.f32.s32 q3, q3, #15\n" + " vcvt.f32.s32 q2, q2, #15\n" + " vcvt.f32.s32 q1, q1, #15\n" + " vcvt.f32.s32 q0, q0, #15\n" + " vst1.32 {d4-d7}, [%[d1]]!\n" + " vst1.32 {d0-d3}, [%[d0]]!\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " vld2.16 { d0[0], d1[0] }, [%[s]]!\n" + " subs %[remainder], %[remainder], #1\n" + " vmovl.s16 q1, d1\n" + " vmovl.s16 q0, d0\n" + " vcvt.f32.s32 q1, q1, #15\n" + " vcvt.f32.s32 q0, q0, #15\n" + " vst1.32 { d2[0] }, [%[d1]]!\n" + " vst1.32 { d0[0] }, [%[d0]]!\n" + " bne 3b\n" + "4:" + : [d0] "+r" (d0), [d1] "+r" (d1), [s] "+r" (s), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : : "q0", "q1", "q2", "q3", "memory", "cc"); +#endif +} + +static void +conv_s16_to_f32d_2s_neon(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int16_t *s = src; + float *d0 = dst[0], *d1 = dst[1]; + uint32_t stride = n_channels << 1; + unsigned int remainder = n_samples & 3; + n_samples -= remainder; + +#ifdef __aarch64__ + asm volatile( + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " ld2 { v0.h, v1.h }[0], [%[s]], %[stride]\n" + " ld2 { v0.h, v1.h }[1], [%[s]], %[stride]\n" + " ld2 { v0.h, v1.h }[2], [%[s]], %[stride]\n" + " ld2 { v0.h, v1.h }[3], [%[s]], %[stride]\n" + " subs %[n_samples], %[n_samples], #4\n" + " sshll v2.4s, v0.4h, #0\n" + " sshll v3.4s, v1.4h, #0\n" + " scvtf v0.4s, v2.4s, #15\n" + " scvtf v1.4s, v3.4s, #15\n" + " st1 { v0.4s }, [%[d0]], #16\n" + " st1 { v1.4s }, [%[d1]], #16\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " ld2 { v0.h, v1.h }[0], [%[s]], %[stride]\n" + " subs %[remainder], %[remainder], #1\n" + " sshll v2.4s, v0.4h, #0\n" + " sshll v3.4s, v1.4h, #0\n" + " scvtf v0.4s, v2.4s, #15\n" + " scvtf v1.4s, v3.4s, #15\n" + " st1 { v0.s }[0], [%[d0]], #4\n" + " st1 { v1.s }[0], [%[d1]], #4\n" + " bne 3b\n" + "4:" + : [d0] "+r" (d0), [d1] "+r" (d1), [s] "+r" (s), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : [stride] "r" (stride) + : "cc", "v0", "v1", "v2", "v3"); +#else + asm volatile( + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " vld2.16 { d0[0], d1[0] }, [%[s]], %[stride]\n" + " vld2.16 { d0[1], d1[1] }, [%[s]], %[stride]\n" + " vld2.16 { d0[2], d1[2] }, [%[s]], %[stride]\n" + " vld2.16 { d0[3], d1[3] }, [%[s]], %[stride]\n" + " subs %[n_samples], %[n_samples], #4\n" + " vmovl.s16 q1, d1\n" + " vmovl.s16 q0, d0\n" + " vcvt.f32.s32 q0, q0, #15\n" + " vcvt.f32.s32 q1, q1, #15\n" + " vst1.32 { q0 }, [%[d0]]!\n" + " vst1.32 { q1 }, [%[d1]]!\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " vld2.16 { d0[0], d1[0] }, [%[s]], %[stride]\n" + " subs %[remainder], %[remainder], #1\n" + " vmovl.s16 q1, d1\n" + " vmovl.s16 q0, d0\n" + " vcvt.f32.s32 q0, q0, #15\n" + " vcvt.f32.s32 q1, q1, #15\n" + " vst1.32 { d0[0] }, [%[d0]]!\n" + " vst1.32 { d2[0] }, [%[d1]]!\n" + " bne 3b\n" + "4:" + : [d0] "+r" (d0), [d1] "+r" (d1), [s] "+r" (s), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : [stride] "r" (stride) + : "cc", "q0", "q1"); +#endif +} + +static void +conv_s16_to_f32d_1s_neon(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int16_t *s = src; + float *d = dst[0]; + uint32_t stride = n_channels << 1; + uint32_t remainder = n_samples & 3; + n_samples -= remainder; + +#ifdef __aarch64__ + asm volatile( + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " ld1 { v0.h }[0], [%[s]], %[stride]\n" + " ld1 { v0.h }[1], [%[s]], %[stride]\n" + " ld1 { v0.h }[2], [%[s]], %[stride]\n" + " ld1 { v0.h }[3], [%[s]], %[stride]\n" + " subs %[n_samples], %[n_samples], #4\n" + " sshll v1.4s, v0.4h, #0\n" + " scvtf v0.4s, v1.4s, #15\n" + " st1 { v0.4s }, [%[d]], #16\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " ld1 { v0.h }[0], [%[s]], %[stride]\n" + " subs %[remainder], %[remainder], #1\n" + " sshll v1.4s, v0.4h, #0\n" + " scvtf v0.4s, v1.4s, #15\n" + " st1 { v0.s }[0], [%[d]], #4\n" + " bne 3b\n" + "4:" + : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : [stride] "r" (stride) + : "cc", "v0", "v1"); +#else + asm volatile( + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " vld1.16 { d0[0] }, [%[s]], %[stride]\n" + " vld1.16 { d0[1] }, [%[s]], %[stride]\n" + " vld1.16 { d0[2] }, [%[s]], %[stride]\n" + " vld1.16 { d0[3] }, [%[s]], %[stride]\n" + " subs %[n_samples], %[n_samples], #4\n" + " vmovl.s16 q0, d0\n" + " vcvt.f32.s32 q0, q0, #15\n" + " vst1.32 { q0 }, [%[d]]!\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " vld1.16 { d0[0] }, [%[s]], %[stride]\n" + " subs %[remainder], %[remainder], #1\n" + " vmovl.s16 q0, d0\n" + " vcvt.f32.s32 q0, q0, #15\n" + " vst1.32 { d0[0] }, [%[d]]!\n" + " bne 3b\n" + "4:" + : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : [stride] "r" (stride) + : "cc", "q0"); +#endif +} + +void +conv_s16_to_f32d_neon(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int16_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 1 < n_channels; i += 2) + conv_s16_to_f32d_2s_neon(conv, &dst[i], &s[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_s16_to_f32d_1s_neon(conv, &dst[i], &s[i], n_channels, n_samples); +} + +static void +conv_f32d_to_s16_2s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + int16_t *d = dst; + uint32_t stride = n_channels << 1; + uint32_t remainder = n_samples & 3; + n_samples -= remainder; + +#ifdef __aarch64__ + asm volatile( + " dup v2.4s, %w[scale]\n" + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " ld1 { v0.4s }, [%[s0]], #16\n" + " ld1 { v1.4s }, [%[s1]], #16\n" + " subs %[n_samples], %[n_samples], #4\n" + " sqadd v0.4s, v0.4s, v2.4s\n" + " sqadd v1.4s, v1.4s, v2.4s\n" + " fcvtns v0.4s, v0.4s\n" + " fcvtns v1.4s, v1.4s\n" + " sqxtn v0.4h, v0.4s\n" + " sqxtn v1.4h, v1.4s\n" + " st2 { v0.h, v1.h }[0], [%[d]], %[stride]\n" + " st2 { v0.h, v1.h }[1], [%[d]], %[stride]\n" + " st2 { v0.h, v1.h }[2], [%[d]], %[stride]\n" + " st2 { v0.h, v1.h }[3], [%[d]], %[stride]\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " ld1 { v0.s }[0], [%[s0]], #4\n" + " ld1 { v2.s }[0], [%[s1]], #4\n" + " subs %[remainder], %[remainder], #1\n" + " sqadd v0.4s, v0.4s, v2.4s\n" + " sqadd v1.4s, v1.4s, v2.4s\n" + " fcvtns v0.4s, v0.4s\n" + " fcvtns v1.4s, v1.4s\n" + " sqxtn v0.4h, v0.4s\n" + " sqxtn v1.4h, v1.4s\n" + " st2 { v0.h, v1.h }[0], [%[d]], %[stride]\n" + " bne 3b\n" + "4:" + : [d] "+r" (d), [s0] "+r" (s0), [s1] "+r" (s1), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : [stride] "r" (stride), + [scale] "r" (15 << 23) + : "cc", "v0", "v1"); +#else + float32x4_t pos = vdupq_n_f32(0.4999999f / S16_SCALE); + float32x4_t neg = vdupq_n_f32(-0.4999999f / S16_SCALE); + + asm volatile( + " veor q2, q2, q2\n" + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " vld1.32 { q0 }, [%[s0]]!\n" + " vld1.32 { q1 }, [%[s1]]!\n" + " subs %[n_samples], %[n_samples], #4\n" + " vcgt.f32 q3, q0, q2\n" + " vcgt.f32 q4, q0, q2\n" + " vbsl q3, %q[pos], %q[neg]\n" + " vbsl q4, %q[pos], %q[neg]\n" + " vadd.f32 q0, q0, q3\n" + " vadd.f32 q1, q1, q4\n" + " vcvt.s32.f32 q0, q0, #15\n" + " vcvt.s32.f32 q1, q1, #15\n" + " vqmovn.s32 d0, q0\n" + " vqmovn.s32 d1, q1\n" + " vst2.16 { d0[0], d1[0] }, [%[d]], %[stride]\n" + " vst2.16 { d0[1], d1[1] }, [%[d]], %[stride]\n" + " vst2.16 { d0[2], d1[2] }, [%[d]], %[stride]\n" + " vst2.16 { d0[3], d1[3] }, [%[d]], %[stride]\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " vld1.32 { d0[0] }, [%[s0]]!\n" + " vld1.32 { d2[0] }, [%[s1]]!\n" + " subs %[remainder], %[remainder], #1\n" + " vcgt.f32 q3, q0, q2\n" + " vcgt.f32 q4, q0, q2\n" + " vbsl q3, %q[pos], %q[neg]\n" + " vbsl q4, %q[pos], %q[neg]\n" + " vadd.f32 q0, q0, q3\n" + " vadd.f32 q1, q1, q4\n" + " vcvt.s32.f32 q0, q0, #15\n" + " vcvt.s32.f32 q1, q1, #15\n" + " vqmovn.s32 d0, q0\n" + " vqmovn.s32 d1, q1\n" + " vst2.16 { d0[0], d1[0] }, [%[d]], %[stride]\n" + " bne 3b\n" + "4:" + : [d] "+r" (d), [s0] "+r" (s0), [s1] "+r" (s1), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : [stride] "r" (stride), + [pos]"w"(pos), + [neg]"w"(neg) + : "cc", "q0", "q1", "q2", "q3", "q4"); +#endif +} + +static void +conv_f32d_to_s16_1s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src[0]; + int16_t *d = dst; + uint32_t stride = n_channels << 1; + uint32_t remainder = n_samples & 3; + n_samples -= remainder; + +#ifdef __aarch64__ + asm volatile( + " dup v2.4s, %w[scale]\n" + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " ld1 { v0.4s }, [%[s]], #16\n" + " subs %[n_samples], %[n_samples], #4\n" + " sqadd v0.4s, v0.4s, v2.4s\n" + " fcvtns v0.4s, v0.4s\n" + " sqxtn v0.4h, v0.4s\n" + " st1 { v0.h }[0], [%[d]], %[stride]\n" + " st1 { v0.h }[1], [%[d]], %[stride]\n" + " st1 { v0.h }[2], [%[d]], %[stride]\n" + " st1 { v0.h }[3], [%[d]], %[stride]\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " ld1 { v0.s }[0], [%[s]], #4\n" + " subs %[remainder], %[remainder], #1\n" + " sqadd v0.4s, v0.4s, v2.4s\n" + " fcvtns v0.4s, v0.4s\n" + " sqxtn v0.4h, v0.4s\n" + " st1 { v0.h }[0], [%[d]], %[stride]\n" + " bne 3b\n" + "4:" + : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : [stride] "r" (stride), + [scale] "r" (15 << 23) + : "cc", "v0"); +#else + float32x4_t pos = vdupq_n_f32(0.4999999f / S16_SCALE); + float32x4_t neg = vdupq_n_f32(-0.4999999f / S16_SCALE); + + asm volatile( + " veor q1, q1, q1\n" + " cmp %[n_samples], #0\n" + " beq 2f\n" + "1:" + " vld1.32 { q0 }, [%[s]]!\n" + " subs %[n_samples], %[n_samples], #4\n" + " vcgt.f32 q2, q0, q1\n" + " vbsl q2, %q[pos], %q[neg]\n" + " vadd.f32 q0, q0, q2\n" + " vcvt.s32.f32 q0, q0, #15\n" + " vqmovn.s32 d0, q0\n" + " vst1.16 { d0[0] }, [%[d]], %[stride]\n" + " vst1.16 { d0[1] }, [%[d]], %[stride]\n" + " vst1.16 { d0[2] }, [%[d]], %[stride]\n" + " vst1.16 { d0[3] }, [%[d]], %[stride]\n" + " bne 1b\n" + "2:" + " cmp %[remainder], #0\n" + " beq 4f\n" + "3:" + " vld1.32 { d0[0] }, [%[s]]!\n" + " subs %[remainder], %[remainder], #1\n" + " vcgt.f32 q2, q0, q1\n" + " vbsl q2, %q[pos], %q[neg]\n" + " vadd.f32 q0, q0, q2\n" + " vcvt.s32.f32 q0, q0, #15\n" + " vqmovn.s32 d0, q0\n" + " vst1.16 { d0[0] }, [%[d]], %[stride]\n" + " bne 3b\n" + "4:" + : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), + [remainder] "+r" (remainder) + : [stride] "r" (stride), + [pos]"w"(pos), + [neg]"w"(neg) + : "cc", "q0", "q1", "q2"); +#endif +} + +void +conv_f32d_to_s16_neon(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int16_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 1 < n_channels; i += 2) + conv_f32d_to_s16_2s_neon(conv, &d[i], &src[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_f32d_to_s16_1s_neon(conv, &d[i], &src[i], n_channels, n_samples); +} diff --git a/spa/plugins/audioconvert/fmt-ops-sse2.c b/spa/plugins/audioconvert/fmt-ops-sse2.c new file mode 100644 index 0000000..4e2fce7 --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops-sse2.c @@ -0,0 +1,1438 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "fmt-ops.h" + +#include <emmintrin.h> + +#define _MM_CLAMP_PS(r,min,max) \ + _mm_min_ps(_mm_max_ps(r, min), max) + +#define _MM_CLAMP_SS(r,min,max) \ + _mm_min_ss(_mm_max_ss(r, min), max) + +static void +conv_s16_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int16_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m128i in = _mm_setzero_si128(); + __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); + + if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 16))) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in = _mm_insert_epi16(in, s[0*n_channels], 1); + in = _mm_insert_epi16(in, s[1*n_channels], 3); + in = _mm_insert_epi16(in, s[2*n_channels], 5); + in = _mm_insert_epi16(in, s[3*n_channels], 7); + in = _mm_srai_epi32(in, 16); + out = _mm_cvtepi32_ps(in); + out = _mm_mul_ps(out, factor); + _mm_store_ps(&d0[n], out); + s += 4*n_channels; + } + for(; n < n_samples; n++) { + out = _mm_cvtsi32_ss(factor, s[0]); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += n_channels; + } +} + +void +conv_s16_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int16_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i < n_channels; i++) + conv_s16_to_f32d_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); +} + +void +conv_s16_to_f32d_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int16_t *s = src[0]; + float *d0 = dst[0], *d1 = dst[1]; + uint32_t n, unrolled; + __m128i in[2], t[4]; + __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); + + if (SPA_IS_ALIGNED(s, 16) && + SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_load_si128((__m128i*)(s + 0)); + in[1] = _mm_load_si128((__m128i*)(s + 8)); + + t[0] = _mm_slli_epi32(in[0], 16); + t[0] = _mm_srai_epi32(t[0], 16); + out[0] = _mm_cvtepi32_ps(t[0]); + out[0] = _mm_mul_ps(out[0], factor); + + t[1] = _mm_srai_epi32(in[0], 16); + out[1] = _mm_cvtepi32_ps(t[1]); + out[1] = _mm_mul_ps(out[1], factor); + + t[2] = _mm_slli_epi32(in[1], 16); + t[2] = _mm_srai_epi32(t[2], 16); + out[2] = _mm_cvtepi32_ps(t[2]); + out[2] = _mm_mul_ps(out[2], factor); + + t[3] = _mm_srai_epi32(in[1], 16); + out[3] = _mm_cvtepi32_ps(t[3]); + out[3] = _mm_mul_ps(out[3], factor); + + _mm_store_ps(&d0[n + 0], out[0]); + _mm_store_ps(&d1[n + 0], out[1]); + _mm_store_ps(&d0[n + 4], out[2]); + _mm_store_ps(&d1[n + 4], out[3]); + + s += 16; + } + for(; n < n_samples; n++) { + out[0] = _mm_cvtsi32_ss(factor, s[0]); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_cvtsi32_ss(factor, s[1]); + out[1] = _mm_mul_ss(out[1], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + s += 2; + } +} + +void +conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int24_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m128i in; + __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); + + if (SPA_IS_ALIGNED(d0, 16) && n_samples > 0) { + unrolled = n_samples & ~3; + if ((n_samples & 3) == 0) + unrolled -= 4; + } + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in = _mm_setr_epi32( + *((uint32_t*)&s[0 * n_channels]), + *((uint32_t*)&s[1 * n_channels]), + *((uint32_t*)&s[2 * n_channels]), + *((uint32_t*)&s[3 * n_channels])); + in = _mm_slli_epi32(in, 8); + in = _mm_srai_epi32(in, 8); + out = _mm_cvtepi32_ps(in); + out = _mm_mul_ps(out, factor); + _mm_store_ps(&d0[n], out); + s += 4 * n_channels; + } + for(; n < n_samples; n++) { + out = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += n_channels; + } +} + +static void +conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int24_t *s = src; + float *d0 = dst[0], *d1 = dst[1]; + uint32_t n, unrolled; + __m128i in[2]; + __m128 out[2], factor = _mm_set1_ps(1.0f / S24_SCALE); + + if (SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16) && + n_samples > 0) { + unrolled = n_samples & ~3; + if ((n_samples & 3) == 0) + unrolled -= 4; + } + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_setr_epi32( + *((uint32_t*)&s[0 + 0*n_channels]), + *((uint32_t*)&s[0 + 1*n_channels]), + *((uint32_t*)&s[0 + 2*n_channels]), + *((uint32_t*)&s[0 + 3*n_channels])); + in[1] = _mm_setr_epi32( + *((uint32_t*)&s[1 + 0*n_channels]), + *((uint32_t*)&s[1 + 1*n_channels]), + *((uint32_t*)&s[1 + 2*n_channels]), + *((uint32_t*)&s[1 + 3*n_channels])); + + in[0] = _mm_slli_epi32(in[0], 8); + in[1] = _mm_slli_epi32(in[1], 8); + + in[0] = _mm_srai_epi32(in[0], 8); + in[1] = _mm_srai_epi32(in[1], 8); + + out[0] = _mm_cvtepi32_ps(in[0]); + out[1] = _mm_cvtepi32_ps(in[1]); + + out[0] = _mm_mul_ps(out[0], factor); + out[1] = _mm_mul_ps(out[1], factor); + + _mm_store_ps(&d0[n], out[0]); + _mm_store_ps(&d1[n], out[1]); + + s += 4 * n_channels; + } + for(; n < n_samples; n++) { + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_mul_ss(out[1], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + s += n_channels; + } +} +static void +conv_s24_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int24_t *s = src; + float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; + uint32_t n, unrolled; + __m128i in[4]; + __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); + + if (SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16) && + SPA_IS_ALIGNED(d2, 16) && + SPA_IS_ALIGNED(d3, 16) && + n_samples > 0) { + unrolled = n_samples & ~3; + if ((n_samples & 3) == 0) + unrolled -= 4; + } + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_setr_epi32( + *((uint32_t*)&s[0 + 0*n_channels]), + *((uint32_t*)&s[0 + 1*n_channels]), + *((uint32_t*)&s[0 + 2*n_channels]), + *((uint32_t*)&s[0 + 3*n_channels])); + in[1] = _mm_setr_epi32( + *((uint32_t*)&s[1 + 0*n_channels]), + *((uint32_t*)&s[1 + 1*n_channels]), + *((uint32_t*)&s[1 + 2*n_channels]), + *((uint32_t*)&s[1 + 3*n_channels])); + in[2] = _mm_setr_epi32( + *((uint32_t*)&s[2 + 0*n_channels]), + *((uint32_t*)&s[2 + 1*n_channels]), + *((uint32_t*)&s[2 + 2*n_channels]), + *((uint32_t*)&s[2 + 3*n_channels])); + in[3] = _mm_setr_epi32( + *((uint32_t*)&s[3 + 0*n_channels]), + *((uint32_t*)&s[3 + 1*n_channels]), + *((uint32_t*)&s[3 + 2*n_channels]), + *((uint32_t*)&s[3 + 3*n_channels])); + + in[0] = _mm_slli_epi32(in[0], 8); + in[1] = _mm_slli_epi32(in[1], 8); + in[2] = _mm_slli_epi32(in[2], 8); + in[3] = _mm_slli_epi32(in[3], 8); + + in[0] = _mm_srai_epi32(in[0], 8); + in[1] = _mm_srai_epi32(in[1], 8); + in[2] = _mm_srai_epi32(in[2], 8); + in[3] = _mm_srai_epi32(in[3], 8); + + out[0] = _mm_cvtepi32_ps(in[0]); + out[1] = _mm_cvtepi32_ps(in[1]); + out[2] = _mm_cvtepi32_ps(in[2]); + out[3] = _mm_cvtepi32_ps(in[3]); + + out[0] = _mm_mul_ps(out[0], factor); + out[1] = _mm_mul_ps(out[1], factor); + out[2] = _mm_mul_ps(out[2], factor); + out[3] = _mm_mul_ps(out[3], factor); + + _mm_store_ps(&d0[n], out[0]); + _mm_store_ps(&d1[n], out[1]); + _mm_store_ps(&d2[n], out[2]); + _mm_store_ps(&d3[n], out[3]); + + s += 4 * n_channels; + } + for(; n < n_samples; n++) { + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); + out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2))); + out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3))); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_mul_ss(out[1], factor); + out[2] = _mm_mul_ss(out[2], factor); + out[3] = _mm_mul_ss(out[3], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + _mm_store_ss(&d2[n], out[2]); + _mm_store_ss(&d3[n], out[3]); + s += n_channels; + } +} + +void +conv_s24_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int8_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_s24_to_f32d_4s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); + for(; i + 1 < n_channels; i += 2) + conv_s24_to_f32d_2s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_s24_to_f32d_1s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); +} + + +void +conv_s32_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int32_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m128i in; + __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); + + if (SPA_IS_ALIGNED(d0, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in = _mm_setr_epi32(s[0*n_channels], + s[1*n_channels], + s[2*n_channels], + s[3*n_channels]); + in = _mm_srai_epi32(in, 8); + out = _mm_cvtepi32_ps(in); + out = _mm_mul_ps(out, factor); + _mm_store_ps(&d0[n], out); + s += 4*n_channels; + } + for(; n < n_samples; n++) { + out = _mm_cvtsi32_ss(factor, s[0]>>8); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += n_channels; + } +} + +void +conv_s32_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int32_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i < n_channels; i++) + conv_s32_to_f32d_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); +} + +static void +conv_f32d_to_s32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0]; + int32_t *d = dst; + uint32_t n, unrolled; + __m128 in[1]; + __m128i out[4]; + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); + out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); + out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); + out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); + + d[0*n_channels] = _mm_cvtsi128_si32(out[0]); + d[1*n_channels] = _mm_cvtsi128_si32(out[1]); + d[2*n_channels] = _mm_cvtsi128_si32(out[2]); + d[3*n_channels] = _mm_cvtsi128_si32(out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_load_ss(&s0[n]); + in[0] = _mm_mul_ss(in[0], scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]) << 8; + d += n_channels; + } +} + +static void +conv_f32d_to_s32_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + int32_t *d = dst; + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[2], t[2]; + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); + + if (SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), scale); + + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_PS(in[1], int_min, int_max); + + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_slli_epi32(out[0], 8); + out[1] = _mm_slli_epi32(out[1], 8); + + t[0] = _mm_unpacklo_epi32(out[0], out[1]); + t[1] = _mm_unpackhi_epi32(out[0], out[1]); + + _mm_storel_pd((double*)(d + 0*n_channels), (__m128d)t[0]); + _mm_storeh_pd((double*)(d + 1*n_channels), (__m128d)t[0]); + _mm_storel_pd((double*)(d + 2*n_channels), (__m128d)t[1]); + _mm_storeh_pd((double*)(d + 3*n_channels), (__m128d)t[1]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_load_ss(&s0[n]); + in[1] = _mm_load_ss(&s1[n]); + + in[0] = _mm_unpacklo_ps(in[0], in[1]); + + in[0] = _mm_mul_ps(in[0], scale); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); + _mm_storel_epi64((__m128i*)d, out[0]); + d += n_channels; + } +} + +static void +conv_f32d_to_s32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; + int32_t *d = dst; + uint32_t n, unrolled; + __m128 in[4]; + __m128i out[4]; + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); + + if (SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16) && + SPA_IS_ALIGNED(s2, 16) && + SPA_IS_ALIGNED(s3, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), scale); + in[2] = _mm_mul_ps(_mm_load_ps(&s2[n]), scale); + in[3] = _mm_mul_ps(_mm_load_ps(&s3[n]), scale); + + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_PS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_PS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_PS(in[3], int_min, int_max); + + _MM_TRANSPOSE4_PS(in[0], in[1], in[2], in[3]); + + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[2] = _mm_cvtps_epi32(in[2]); + out[3] = _mm_cvtps_epi32(in[3]); + out[0] = _mm_slli_epi32(out[0], 8); + out[1] = _mm_slli_epi32(out[1], 8); + out[2] = _mm_slli_epi32(out[2], 8); + out[3] = _mm_slli_epi32(out[3], 8); + + _mm_storeu_si128((__m128i*)(d + 0*n_channels), out[0]); + _mm_storeu_si128((__m128i*)(d + 1*n_channels), out[1]); + _mm_storeu_si128((__m128i*)(d + 2*n_channels), out[2]); + _mm_storeu_si128((__m128i*)(d + 3*n_channels), out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_load_ss(&s0[n]); + in[1] = _mm_load_ss(&s1[n]); + in[2] = _mm_load_ss(&s2[n]); + in[3] = _mm_load_ss(&s3[n]); + + in[0] = _mm_unpacklo_ps(in[0], in[2]); + in[1] = _mm_unpacklo_ps(in[1], in[3]); + in[0] = _mm_unpacklo_ps(in[0], in[1]); + + in[0] = _mm_mul_ps(in[0], scale); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); + _mm_storeu_si128((__m128i*)d, out[0]); + d += n_channels; + } +} + +void +conv_f32d_to_s32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int32_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_f32d_to_s32_4s_sse2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i + 1 < n_channels; i += 2) + conv_f32d_to_s32_2s_sse2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_f32d_to_s32_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); +} + +/* 32 bit xorshift PRNG, see https://en.wikipedia.org/wiki/Xorshift */ +#define _MM_XORSHIFT_EPI32(r) \ +({ \ + __m128i i, t; \ + i = _mm_load_si128((__m128i*)r); \ + t = _mm_slli_epi32(i, 13); \ + i = _mm_xor_si128(i, t); \ + t = _mm_srli_epi32(i, 17); \ + i = _mm_xor_si128(i, t); \ + t = _mm_slli_epi32(i, 5); \ + i = _mm_xor_si128(i, t); \ + _mm_store_si128((__m128i*)r, i); \ + i; \ +}) + +void conv_noise_rect_sse2(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const uint32_t *r = conv->random; + __m128 scale = _mm_set1_ps(conv->scale); + __m128i in[1]; + __m128 out[1]; + + for (n = 0; n < n_samples; n += 4) { + in[0] = _MM_XORSHIFT_EPI32(r); + out[0] = _mm_cvtepi32_ps(in[0]); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); + } +} + +void conv_noise_tri_sse2(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const uint32_t *r = conv->random; + __m128 scale = _mm_set1_ps(conv->scale); + __m128i in[1]; + __m128 out[1]; + + for (n = 0; n < n_samples; n += 4) { + in[0] = _mm_sub_epi32( _MM_XORSHIFT_EPI32(r), _MM_XORSHIFT_EPI32(r)); + out[0] = _mm_cvtepi32_ps(in[0]); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); + } +} + +void conv_noise_tri_hf_sse2(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + int32_t *p = conv->prev; + const uint32_t *r = conv->random; + __m128 scale = _mm_set1_ps(conv->scale); + __m128i in[1], old[1], new[1]; + __m128 out[1]; + + old[0] = _mm_load_si128((__m128i*)p); + for (n = 0; n < n_samples; n += 4) { + new[0] = _MM_XORSHIFT_EPI32(r); + in[0] = _mm_sub_epi32(old[0], new[0]); + old[0] = new[0]; + out[0] = _mm_cvtepi32_ps(in[0]); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); + } + _mm_store_si128((__m128i*)p, old[0]); +} + +static void +conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + float *noise, uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src; + int32_t *d = dst; + uint32_t n, unrolled; + __m128 in[1]; + __m128i out[4]; + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); + + if (SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), scale); + in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); + out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); + out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); + out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); + + d[0*n_channels] = _mm_cvtsi128_si32(out[0]); + d[1*n_channels] = _mm_cvtsi128_si32(out[1]); + d[2*n_channels] = _mm_cvtsi128_si32(out[2]); + d[3*n_channels] = _mm_cvtsi128_si32(out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_load_ss(&s[n]); + in[0] = _mm_mul_ss(in[0], scale); + in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]) << 8; + d += n_channels; + } +} + +void +conv_f32d_to_s32_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int32_t *d = dst[0]; + uint32_t i, k, chunk, n_channels = conv->n_channels; + float *noise = conv->noise; + + convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); + + for(i = 0; i < n_channels; i++) { + const float *s = src[i]; + for(k = 0; k < n_samples; k += chunk) { + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32d_to_s32_1s_noise_sse2(conv, &d[i + k*n_channels], + &s[k], noise, n_channels, chunk); + } + } +} + +static void +conv_interleave_32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const int32_t *s0 = src[0]; + int32_t *d = dst; + uint32_t n, unrolled; + __m128i out[4]; + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + out[0] = _mm_load_si128((__m128i*)&s0[n]); + out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); + out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); + out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); + + d[0*n_channels] = _mm_cvtsi128_si32(out[0]); + d[1*n_channels] = _mm_cvtsi128_si32(out[1]); + d[2*n_channels] = _mm_cvtsi128_si32(out[2]); + d[3*n_channels] = _mm_cvtsi128_si32(out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + *d = s0[n]; + d += n_channels; + } +} +static void +conv_interleave_32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; + float *d = dst; + uint32_t n, unrolled; + __m128 out[4]; + + if (SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16) && + SPA_IS_ALIGNED(s2, 16) && + SPA_IS_ALIGNED(s3, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + out[0] = _mm_load_ps(&s0[n]); + out[1] = _mm_load_ps(&s1[n]); + out[2] = _mm_load_ps(&s2[n]); + out[3] = _mm_load_ps(&s3[n]); + + _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); + + _mm_storeu_ps((d + 0*n_channels), out[0]); + _mm_storeu_ps((d + 1*n_channels), out[1]); + _mm_storeu_ps((d + 2*n_channels), out[2]); + _mm_storeu_ps((d + 3*n_channels), out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + out[0] = _mm_setr_ps(s0[n], s1[n], s2[n], s3[n]); + _mm_storeu_ps(d, out[0]); + d += n_channels; + } +} + +void +conv_32d_to_32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int32_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_interleave_32_4s_sse2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_interleave_32_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); +} + +#define _MM_BSWAP_EPI32(x) \ +({ \ + __m128i a = _mm_or_si128( \ + _mm_slli_epi16(x, 8), \ + _mm_srli_epi16(x, 8)); \ + a = _mm_shufflelo_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \ + a = _mm_shufflehi_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \ +}) + +static void +conv_interleave_32s_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const int32_t *s0 = src[0]; + int32_t *d = dst; + uint32_t n, unrolled; + __m128i out[4]; + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + out[0] = _mm_load_si128((__m128i*)&s0[n]); + out[0] = _MM_BSWAP_EPI32(out[0]); + out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); + out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); + out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); + + d[0*n_channels] = _mm_cvtsi128_si32(out[0]); + d[1*n_channels] = _mm_cvtsi128_si32(out[1]); + d[2*n_channels] = _mm_cvtsi128_si32(out[2]); + d[3*n_channels] = _mm_cvtsi128_si32(out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + *d = bswap_32(s0[n]); + d += n_channels; + } +} +static void +conv_interleave_32s_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; + float *d = dst; + uint32_t n, unrolled; + __m128 out[4]; + + if (SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16) && + SPA_IS_ALIGNED(s2, 16) && + SPA_IS_ALIGNED(s3, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + out[0] = _mm_load_ps(&s0[n]); + out[1] = _mm_load_ps(&s1[n]); + out[2] = _mm_load_ps(&s2[n]); + out[3] = _mm_load_ps(&s3[n]); + + _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); + + out[0] = (__m128) _MM_BSWAP_EPI32((__m128i)out[0]); + out[1] = (__m128) _MM_BSWAP_EPI32((__m128i)out[1]); + out[2] = (__m128) _MM_BSWAP_EPI32((__m128i)out[2]); + out[3] = (__m128) _MM_BSWAP_EPI32((__m128i)out[3]); + + _mm_storeu_ps(&d[0*n_channels], out[0]); + _mm_storeu_ps(&d[1*n_channels], out[1]); + _mm_storeu_ps(&d[2*n_channels], out[2]); + _mm_storeu_ps(&d[3*n_channels], out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + out[0] = _mm_setr_ps(s0[n], s1[n], s2[n], s3[n]); + out[0] = (__m128) _MM_BSWAP_EPI32((__m128i)out[0]); + _mm_storeu_ps(d, out[0]); + d += n_channels; + } +} + +void +conv_32d_to_32s_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int32_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_interleave_32s_4s_sse2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_interleave_32s_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); +} + +static void +conv_deinterleave_32_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m128 out; + + if (SPA_IS_ALIGNED(d0, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + out = _mm_setr_ps(s[0*n_channels], + s[1*n_channels], + s[2*n_channels], + s[3*n_channels]); + _mm_store_ps(&d0[n], out); + s += 4*n_channels; + } + for(; n < n_samples; n++) { + d0[n] = *s; + s += n_channels; + } +} + +static void +conv_deinterleave_32_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src; + float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; + uint32_t n, unrolled; + __m128 out[4]; + + if (SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16) && + SPA_IS_ALIGNED(d2, 16) && + SPA_IS_ALIGNED(d3, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + out[0] = _mm_loadu_ps(&s[0 * n_channels]); + out[1] = _mm_loadu_ps(&s[1 * n_channels]); + out[2] = _mm_loadu_ps(&s[2 * n_channels]); + out[3] = _mm_loadu_ps(&s[3 * n_channels]); + + _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); + + _mm_store_ps(&d0[n], out[0]); + _mm_store_ps(&d1[n], out[1]); + _mm_store_ps(&d2[n], out[2]); + _mm_store_ps(&d3[n], out[3]); + s += 4 * n_channels; + } + for(; n < n_samples; n++) { + d0[n] = s[0]; + d1[n] = s[1]; + d2[n] = s[2]; + d3[n] = s[3]; + s += n_channels; + } +} + +void +conv_32_to_32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const float *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_deinterleave_32_4s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_deinterleave_32_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); +} + +static void +conv_deinterleave_32s_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m128 out; + + if (SPA_IS_ALIGNED(d0, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + out = _mm_setr_ps(s[0*n_channels], + s[1*n_channels], + s[2*n_channels], + s[3*n_channels]); + out = (__m128) _MM_BSWAP_EPI32((__m128i)out); + _mm_store_ps(&d0[n], out); + s += 4*n_channels; + } + for(; n < n_samples; n++) { + d0[n] = bswap_32(*s); + s += n_channels; + } +} + +static void +conv_deinterleave_32s_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src; + float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; + uint32_t n, unrolled; + __m128 out[4]; + + if (SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16) && + SPA_IS_ALIGNED(d2, 16) && + SPA_IS_ALIGNED(d3, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + out[0] = _mm_loadu_ps(&s[0 * n_channels]); + out[1] = _mm_loadu_ps(&s[1 * n_channels]); + out[2] = _mm_loadu_ps(&s[2 * n_channels]); + out[3] = _mm_loadu_ps(&s[3 * n_channels]); + + _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); + + out[0] = (__m128) _MM_BSWAP_EPI32((__m128i)out[0]); + out[1] = (__m128) _MM_BSWAP_EPI32((__m128i)out[1]); + out[2] = (__m128) _MM_BSWAP_EPI32((__m128i)out[2]); + out[3] = (__m128) _MM_BSWAP_EPI32((__m128i)out[3]); + + _mm_store_ps(&d0[n], out[0]); + _mm_store_ps(&d1[n], out[1]); + _mm_store_ps(&d2[n], out[2]); + _mm_store_ps(&d3[n], out[3]); + s += 4 * n_channels; + } + for(; n < n_samples; n++) { + d0[n] = bswap_32(s[0]); + d1[n] = bswap_32(s[1]); + d2[n] = bswap_32(s[2]); + d3[n] = bswap_32(s[3]); + s += n_channels; + } +} + +void +conv_32s_to_32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const float *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_deinterleave_32s_4s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_deinterleave_32s_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); +} + +static void +conv_f32_to_s16_1_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + uint32_t n_samples) +{ + const float *s = src; + int16_t *d = dst; + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s[n+4]), int_scale); + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_packs_epi32(out[0], out[1]); + _mm_storeu_si128((__m128i*)(d+0), out[0]); + d += 8; + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d++ = _mm_cvtss_si32(in[0]); + } +} + +void +conv_f32d_to_s16d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + uint32_t i, n_channels = conv->n_channels; + for(i = 0; i < n_channels; i++) + conv_f32_to_s16_1_sse2(conv, dst[i], src[i], n_samples); +} + +void +conv_f32_to_s16_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + conv_f32_to_s16_1_sse2(conv, dst[0], src[0], n_samples * conv->n_channels); +} + +static void +conv_f32d_to_s16_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0]; + int16_t *d = dst; + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_packs_epi32(out[0], out[1]); + + d[0*n_channels] = _mm_extract_epi16(out[0], 0); + d[1*n_channels] = _mm_extract_epi16(out[0], 1); + d[2*n_channels] = _mm_extract_epi16(out[0], 2); + d[3*n_channels] = _mm_extract_epi16(out[0], 3); + d[4*n_channels] = _mm_extract_epi16(out[0], 4); + d[5*n_channels] = _mm_extract_epi16(out[0], 5); + d[6*n_channels] = _mm_extract_epi16(out[0], 6); + d[7*n_channels] = _mm_extract_epi16(out[0], 7); + d += 8*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]); + d += n_channels; + } +} + +static void +conv_f32d_to_s16_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + int16_t *d = dst; + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[4], t[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), int_scale); + + t[0] = _mm_cvtps_epi32(in[0]); + t[1] = _mm_cvtps_epi32(in[1]); + + t[0] = _mm_packs_epi32(t[0], t[0]); + t[1] = _mm_packs_epi32(t[1], t[1]); + + out[0] = _mm_unpacklo_epi16(t[0], t[1]); + out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); + out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); + out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); + + *((int32_t*)(d + 0*n_channels)) = _mm_cvtsi128_si32(out[0]); + *((int32_t*)(d + 1*n_channels)) = _mm_cvtsi128_si32(out[1]); + *((int32_t*)(d + 2*n_channels)) = _mm_cvtsi128_si32(out[2]); + *((int32_t*)(d + 3*n_channels)) = _mm_cvtsi128_si32(out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + d[0] = _mm_cvtss_si32(in[0]); + d[1] = _mm_cvtss_si32(in[1]); + d += n_channels; + } +} + +static void +conv_f32d_to_s16_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; + int16_t *d = dst; + uint32_t n, unrolled; + __m128 in[4]; + __m128i out[4], t[4]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16) && + SPA_IS_ALIGNED(s2, 16) && + SPA_IS_ALIGNED(s3, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), int_scale); + in[2] = _mm_mul_ps(_mm_load_ps(&s2[n]), int_scale); + in[3] = _mm_mul_ps(_mm_load_ps(&s3[n]), int_scale); + + t[0] = _mm_cvtps_epi32(in[0]); + t[1] = _mm_cvtps_epi32(in[1]); + t[2] = _mm_cvtps_epi32(in[2]); + t[3] = _mm_cvtps_epi32(in[3]); + + t[0] = _mm_packs_epi32(t[0], t[2]); + t[1] = _mm_packs_epi32(t[1], t[3]); + + out[0] = _mm_unpacklo_epi16(t[0], t[1]); + out[1] = _mm_unpackhi_epi16(t[0], t[1]); + out[2] = _mm_unpacklo_epi32(out[0], out[1]); + out[3] = _mm_unpackhi_epi32(out[0], out[1]); + + _mm_storel_pi((__m64*)(d + 0*n_channels), (__m128)out[2]); + _mm_storeh_pi((__m64*)(d + 1*n_channels), (__m128)out[2]); + _mm_storel_pi((__m64*)(d + 2*n_channels), (__m128)out[3]); + _mm_storeh_pi((__m64*)(d + 3*n_channels), (__m128)out[3]); + + d += 4*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); + in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); + d[0] = _mm_cvtss_si32(in[0]); + d[1] = _mm_cvtss_si32(in[1]); + d[2] = _mm_cvtss_si32(in[2]); + d[3] = _mm_cvtss_si32(in[3]); + d += n_channels; + } +} + +void +conv_f32d_to_s16_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int16_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_f32d_to_s16_4s_sse2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i + 1 < n_channels; i += 2) + conv_f32d_to_s16_2s_sse2(conv, &d[i], &src[i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_f32d_to_s16_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); +} + +static void +conv_f32d_to_s16_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + const float *noise, uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src; + int16_t *d = dst; + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); + in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); + in[1] = _mm_add_ps(in[1], _mm_load_ps(&noise[n+4])); + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_packs_epi32(out[0], out[1]); + + d[0*n_channels] = _mm_extract_epi16(out[0], 0); + d[1*n_channels] = _mm_extract_epi16(out[0], 1); + d[2*n_channels] = _mm_extract_epi16(out[0], 2); + d[3*n_channels] = _mm_extract_epi16(out[0], 3); + d[4*n_channels] = _mm_extract_epi16(out[0], 4); + d[5*n_channels] = _mm_extract_epi16(out[0], 5); + d[6*n_channels] = _mm_extract_epi16(out[0], 6); + d[7*n_channels] = _mm_extract_epi16(out[0], 7); + d += 8*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]); + d += n_channels; + } +} + +void +conv_f32d_to_s16_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int16_t *d = dst[0]; + uint32_t i, k, chunk, n_channels = conv->n_channels; + float *noise = conv->noise; + + convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); + + for(i = 0; i < n_channels; i++) { + const float *s = src[i]; + for(k = 0; k < n_samples; k += chunk) { + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32d_to_s16_1s_noise_sse2(conv, &d[i + k*n_channels], + &s[k], noise, n_channels, chunk); + } + } +} + +static void +conv_f32_to_s16_1_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + const float *noise, uint32_t n_samples) +{ + const float *s = src; + int16_t *d = dst; + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s[n+4]), int_scale); + in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); + in[1] = _mm_add_ps(in[1], _mm_load_ps(&noise[n+4])); + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_packs_epi32(out[0], out[1]); + _mm_storeu_si128((__m128i*)(&d[n]), out[0]); + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s[n]), int_scale); + in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + d[n] = _mm_cvtss_si32(in[0]); + } +} + +void +conv_f32d_to_s16d_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + uint32_t i, k, chunk, n_channels = conv->n_channels; + float *noise = conv->noise; + + convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); + + for(i = 0; i < n_channels; i++) { + const float *s = src[i]; + int16_t *d = dst[i]; + for(k = 0; k < n_samples; k += chunk) { + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32_to_s16_1_noise_sse2(conv, &d[k], &s[k], noise, chunk); + } + } +} + +void +conv_f32d_to_s16_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + int16_t *d = dst[0]; + uint32_t n, unrolled; + __m128 in[4]; + __m128i out[4]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s0, 16) && + SPA_IS_ALIGNED(s1, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n+0]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s1[n+0]), int_scale); + in[2] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); + in[3] = _mm_mul_ps(_mm_load_ps(&s1[n+4]), int_scale); + + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[2] = _mm_cvtps_epi32(in[2]); + out[3] = _mm_cvtps_epi32(in[3]); + + out[0] = _mm_packs_epi32(out[0], out[2]); + out[1] = _mm_packs_epi32(out[1], out[3]); + + out[2] = _mm_unpacklo_epi16(out[0], out[1]); + out[3] = _mm_unpackhi_epi16(out[0], out[1]); + + _mm_storeu_si128((__m128i*)(d+0), out[2]); + _mm_storeu_si128((__m128i*)(d+8), out[3]); + + d += 16; + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + d[0] = _mm_cvtss_si32(in[0]); + d[1] = _mm_cvtss_si32(in[1]); + d += 2; + } +} diff --git a/spa/plugins/audioconvert/fmt-ops-sse41.c b/spa/plugins/audioconvert/fmt-ops-sse41.c new file mode 100644 index 0000000..042294d --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops-sse41.c @@ -0,0 +1,86 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "fmt-ops.h" + +#include <smmintrin.h> + +static void +conv_s24_to_f32d_1s_sse41(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int24_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m128i in = _mm_setzero_si128(); + __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); + + if (SPA_IS_ALIGNED(d0, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in = _mm_insert_epi32(in, *((uint32_t*)&s[0 * n_channels]), 0); + in = _mm_insert_epi32(in, *((uint32_t*)&s[1 * n_channels]), 1); + in = _mm_insert_epi32(in, *((uint32_t*)&s[2 * n_channels]), 2); + in = _mm_insert_epi32(in, *((uint32_t*)&s[3 * n_channels]), 3); + in = _mm_slli_epi32(in, 8); + in = _mm_srai_epi32(in, 8); + out = _mm_cvtepi32_ps(in); + out = _mm_mul_ps(out, factor); + _mm_store_ps(&d0[n], out); + s += 4 * n_channels; + } + for(; n < n_samples; n++) { + out = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += n_channels; + } +} + +extern void conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples); +extern void conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples); + +void +conv_s24_to_f32d_sse41(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int8_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + +#if defined (HAVE_SSSE3) + for(; i + 3 < n_channels; i += 4) + conv_s24_to_f32d_4s_ssse3(conv, &dst[i], &s[3*i], n_channels, n_samples); +#endif +#if defined (HAVE_SSE2) + for(; i + 1 < n_channels; i += 2) + conv_s24_to_f32d_2s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); +#endif + for(; i < n_channels; i++) + conv_s24_to_f32d_1s_sse41(conv, &dst[i], &s[3*i], n_channels, n_samples); +} diff --git a/spa/plugins/audioconvert/fmt-ops-ssse3.c b/spa/plugins/audioconvert/fmt-ops-ssse3.c new file mode 100644 index 0000000..c35c7c9 --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops-ssse3.c @@ -0,0 +1,111 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "fmt-ops.h" + +#include <tmmintrin.h> + +static void +conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const int24_t *s = src; + float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; + uint32_t n, unrolled; + __m128i in[4]; + __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); + const __m128i mask = _mm_setr_epi8(-1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11); + //const __m128i mask = _mm_set_epi8(15, 14, 13, -1, 12, 11, 10, -1, 9, 8, 7, -1, 6, 5, 4, -1); + + if (SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16) && + SPA_IS_ALIGNED(d2, 16) && + SPA_IS_ALIGNED(d3, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_loadu_si128((__m128i*)(s + 0*n_channels)); + in[1] = _mm_loadu_si128((__m128i*)(s + 1*n_channels)); + in[2] = _mm_loadu_si128((__m128i*)(s + 2*n_channels)); + in[3] = _mm_loadu_si128((__m128i*)(s + 3*n_channels)); + in[0] = _mm_shuffle_epi8(in[0], mask); + in[1] = _mm_shuffle_epi8(in[1], mask); + in[2] = _mm_shuffle_epi8(in[2], mask); + in[3] = _mm_shuffle_epi8(in[3], mask); + in[0] = _mm_srai_epi32(in[0], 8); + in[1] = _mm_srai_epi32(in[1], 8); + in[2] = _mm_srai_epi32(in[2], 8); + in[3] = _mm_srai_epi32(in[3], 8); + out[0] = _mm_cvtepi32_ps(in[0]); + out[1] = _mm_cvtepi32_ps(in[1]); + out[2] = _mm_cvtepi32_ps(in[2]); + out[3] = _mm_cvtepi32_ps(in[3]); + out[0] = _mm_mul_ps(out[0], factor); + out[1] = _mm_mul_ps(out[1], factor); + out[2] = _mm_mul_ps(out[2], factor); + out[3] = _mm_mul_ps(out[3], factor); + + _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); + + _mm_store_ps(&d0[n], out[0]); + _mm_store_ps(&d1[n], out[1]); + _mm_store_ps(&d2[n], out[2]); + _mm_store_ps(&d3[n], out[3]); + s += 4 * n_channels; + } + for(; n < n_samples; n++) { + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); + out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2))); + out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3))); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_mul_ss(out[1], factor); + out[2] = _mm_mul_ss(out[2], factor); + out[3] = _mm_mul_ss(out[3], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + _mm_store_ss(&d2[n], out[2]); + _mm_store_ss(&d3[n], out[3]); + s += n_channels; + } +} + +void +conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples); + +void +conv_s24_to_f32d_ssse3(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const int8_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i + 3 < n_channels; i += 4) + conv_s24_to_f32d_4s_ssse3(conv, &dst[i], &s[3*i], n_channels, n_samples); + for(; i < n_channels; i++) + conv_s24_to_f32d_1s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); +} diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c new file mode 100644 index 0000000..4999a20 --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -0,0 +1,564 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <spa/support/cpu.h> +#include <spa/utils/defs.h> +#include <spa/param/audio/format-utils.h> + +#include "fmt-ops.h" + +#define NOISE_SIZE (1<<10) +#define RANDOM_SIZE (16) + +typedef void (*convert_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples); + +struct conv_info { + uint32_t src_fmt; + uint32_t dst_fmt; + uint32_t n_channels; + + convert_func_t process; + const char *name; + + uint32_t cpu_flags; +#define CONV_NOISE (1<<0) +#define CONV_SHAPE (1<<1) + uint32_t conv_flags; +}; + +#define MAKE(fmt1,fmt2,chan,func,...) \ + { SPA_AUDIO_FORMAT_ ##fmt1, SPA_AUDIO_FORMAT_ ##fmt2, chan, func, #func , __VA_ARGS__ } + +static struct conv_info conv_table[] = +{ + /* to f32 */ + MAKE(U8, F32, 0, conv_u8_to_f32_c), + MAKE(U8, F32, 0, conv_u8_to_f32_c), + MAKE(U8P, F32P, 0, conv_u8d_to_f32d_c), + MAKE(U8, F32P, 0, conv_u8_to_f32d_c), + MAKE(U8P, F32, 0, conv_u8d_to_f32_c), + + MAKE(S8, F32, 0, conv_s8_to_f32_c), + MAKE(S8P, F32P, 0, conv_s8d_to_f32d_c), + MAKE(S8, F32P, 0, conv_s8_to_f32d_c), + MAKE(S8P, F32, 0, conv_s8d_to_f32_c), + + MAKE(ALAW, F32P, 0, conv_alaw_to_f32d_c), + MAKE(ULAW, F32P, 0, conv_ulaw_to_f32d_c), + + MAKE(U16, F32, 0, conv_u16_to_f32_c), + MAKE(U16, F32P, 0, conv_u16_to_f32d_c), + + MAKE(S16, F32, 0, conv_s16_to_f32_c), + MAKE(S16P, F32P, 0, conv_s16d_to_f32d_c), +#if defined (HAVE_NEON) + MAKE(S16, F32P, 2, conv_s16_to_f32d_2_neon, SPA_CPU_FLAG_NEON), + MAKE(S16, F32P, 0, conv_s16_to_f32d_neon, SPA_CPU_FLAG_NEON), +#endif +#if defined (HAVE_AVX2) + MAKE(S16, F32P, 2, conv_s16_to_f32d_2_avx2, SPA_CPU_FLAG_AVX2), + MAKE(S16, F32P, 0, conv_s16_to_f32d_avx2, SPA_CPU_FLAG_AVX2), +#endif +#if defined (HAVE_SSE2) + MAKE(S16, F32P, 2, conv_s16_to_f32d_2_sse2, SPA_CPU_FLAG_SSE2), + MAKE(S16, F32P, 0, conv_s16_to_f32d_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(S16, F32P, 0, conv_s16_to_f32d_c), + MAKE(S16P, F32, 0, conv_s16d_to_f32_c), + + MAKE(S16_OE, F32P, 0, conv_s16s_to_f32d_c), + + MAKE(F32, F32, 0, conv_copy32_c), + MAKE(F32P, F32P, 0, conv_copy32d_c), +#if defined (HAVE_SSE2) + MAKE(F32, F32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(F32, F32P, 0, conv_32_to_32d_c), +#if defined (HAVE_SSE2) + MAKE(F32P, F32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(F32P, F32, 0, conv_32d_to_32_c), + +#if defined (HAVE_SSE2) + MAKE(F32_OE, F32P, 0, conv_32s_to_32d_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(F32_OE, F32P, 0, conv_32s_to_32d_c), +#if defined (HAVE_SSE2) + MAKE(F32P, F32_OE, 0, conv_32d_to_32s_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(F32P, F32_OE, 0, conv_32d_to_32s_c), + + MAKE(U32, F32, 0, conv_u32_to_f32_c), + MAKE(U32, F32P, 0, conv_u32_to_f32d_c), + +#if defined (HAVE_AVX2) + MAKE(S32, F32P, 0, conv_s32_to_f32d_avx2, SPA_CPU_FLAG_AVX2), +#endif +#if defined (HAVE_SSE2) + MAKE(S32, F32P, 0, conv_s32_to_f32d_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(S32, F32, 0, conv_s32_to_f32_c), + MAKE(S32P, F32P, 0, conv_s32d_to_f32d_c), + MAKE(S32, F32P, 0, conv_s32_to_f32d_c), + MAKE(S32P, F32, 0, conv_s32d_to_f32_c), + + MAKE(S32_OE, F32P, 0, conv_s32s_to_f32d_c), + + MAKE(U24, F32, 0, conv_u24_to_f32_c), + MAKE(U24, F32P, 0, conv_u24_to_f32d_c), + + MAKE(S24, F32, 0, conv_s24_to_f32_c), + MAKE(S24P, F32P, 0, conv_s24d_to_f32d_c), +#if defined (HAVE_AVX2) + MAKE(S24, F32P, 0, conv_s24_to_f32d_avx2, SPA_CPU_FLAG_AVX2), +#endif +#if defined (HAVE_SSSE3) +// MAKE(S24, F32P, 0, conv_s24_to_f32d_ssse3, SPA_CPU_FLAG_SSSE3), +#endif +#if defined (HAVE_SSE41) + MAKE(S24, F32P, 0, conv_s24_to_f32d_sse41, SPA_CPU_FLAG_SSE41), +#endif +#if defined (HAVE_SSE2) + MAKE(S24, F32P, 0, conv_s24_to_f32d_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(S24, F32P, 0, conv_s24_to_f32d_c), + MAKE(S24P, F32, 0, conv_s24d_to_f32_c), + + MAKE(S24_OE, F32P, 0, conv_s24s_to_f32d_c), + + MAKE(U24_32, F32, 0, conv_u24_32_to_f32_c), + MAKE(U24_32, F32P, 0, conv_u24_32_to_f32d_c), + + MAKE(S24_32, F32, 0, conv_s24_32_to_f32_c), + MAKE(S24_32P, F32P, 0, conv_s24_32d_to_f32d_c), + MAKE(S24_32, F32P, 0, conv_s24_32_to_f32d_c), + MAKE(S24_32P, F32, 0, conv_s24_32d_to_f32_c), + + MAKE(S24_32_OE, F32P, 0, conv_s24_32s_to_f32d_c), + + MAKE(F64, F32, 0, conv_f64_to_f32_c), + MAKE(F64P, F32P, 0, conv_f64d_to_f32d_c), + MAKE(F64, F32P, 0, conv_f64_to_f32d_c), + MAKE(F64P, F32, 0, conv_f64d_to_f32_c), + + MAKE(F64_OE, F32P, 0, conv_f64s_to_f32d_c), + + /* from f32 */ + MAKE(F32, U8, 0, conv_f32_to_u8_c), + MAKE(F32P, U8P, 0, conv_f32d_to_u8d_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, U8P, 0, conv_f32d_to_u8d_noise_c, 0, CONV_NOISE), + MAKE(F32P, U8P, 0, conv_f32d_to_u8d_c), + MAKE(F32, U8P, 0, conv_f32_to_u8d_c), + MAKE(F32P, U8, 0, conv_f32d_to_u8_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, U8, 0, conv_f32d_to_u8_noise_c, 0, CONV_NOISE), + MAKE(F32P, U8, 0, conv_f32d_to_u8_c), + + MAKE(F32, S8, 0, conv_f32_to_s8_c), + MAKE(F32P, S8P, 0, conv_f32d_to_s8d_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, S8P, 0, conv_f32d_to_s8d_noise_c, 0, CONV_NOISE), + MAKE(F32P, S8P, 0, conv_f32d_to_s8d_c), + MAKE(F32, S8P, 0, conv_f32_to_s8d_c), + MAKE(F32P, S8, 0, conv_f32d_to_s8_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, S8, 0, conv_f32d_to_s8_noise_c, 0, CONV_NOISE), + MAKE(F32P, S8, 0, conv_f32d_to_s8_c), + + MAKE(F32P, ALAW, 0, conv_f32d_to_alaw_c), + MAKE(F32P, ULAW, 0, conv_f32d_to_ulaw_c), + + MAKE(F32, U16, 0, conv_f32_to_u16_c), + MAKE(F32P, U16, 0, conv_f32d_to_u16_c), + +#if defined (HAVE_SSE2) + MAKE(F32, S16, 0, conv_f32_to_s16_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(F32, S16, 0, conv_f32_to_s16_c), + + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_shaped_c, 0, CONV_SHAPE), +#if defined (HAVE_SSE2) + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), +#endif + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_c, 0, CONV_NOISE), +#if defined (HAVE_SSE2) + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_c), + + MAKE(F32, S16P, 0, conv_f32_to_s16d_c), + + MAKE(F32P, S16, 0, conv_f32d_to_s16_shaped_c, 0, CONV_SHAPE), +#if defined (HAVE_SSE2) + MAKE(F32P, S16, 0, conv_f32d_to_s16_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), +#endif + MAKE(F32P, S16, 0, conv_f32d_to_s16_noise_c, 0, CONV_NOISE), +#if defined (HAVE_NEON) + MAKE(F32P, S16, 0, conv_f32d_to_s16_neon, SPA_CPU_FLAG_NEON), +#endif +#if defined (HAVE_AVX2) + MAKE(F32P, S16, 4, conv_f32d_to_s16_4_avx2, SPA_CPU_FLAG_AVX2), + MAKE(F32P, S16, 2, conv_f32d_to_s16_2_avx2, SPA_CPU_FLAG_AVX2), + MAKE(F32P, S16, 0, conv_f32d_to_s16_avx2, SPA_CPU_FLAG_AVX2), +#endif +#if defined (HAVE_SSE2) + MAKE(F32P, S16, 2, conv_f32d_to_s16_2_sse2, SPA_CPU_FLAG_SSE2), + MAKE(F32P, S16, 0, conv_f32d_to_s16_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(F32P, S16, 0, conv_f32d_to_s16_c), + + MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_noise_c, 0, CONV_NOISE), + MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_c), + + MAKE(F32, U32, 0, conv_f32_to_u32_c), + MAKE(F32P, U32, 0, conv_f32d_to_u32_c), + + MAKE(F32, S32, 0, conv_f32_to_s32_c), + MAKE(F32P, S32P, 0, conv_f32d_to_s32d_noise_c, 0, CONV_NOISE), + MAKE(F32P, S32P, 0, conv_f32d_to_s32d_c), + MAKE(F32, S32P, 0, conv_f32_to_s32d_c), + +#if defined (HAVE_SSE2) + MAKE(F32P, S32, 0, conv_f32d_to_s32_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), +#endif + MAKE(F32P, S32, 0, conv_f32d_to_s32_noise_c, 0, CONV_NOISE), + +#if defined (HAVE_AVX2) + MAKE(F32P, S32, 0, conv_f32d_to_s32_avx2, SPA_CPU_FLAG_AVX2), +#endif +#if defined (HAVE_SSE2) + MAKE(F32P, S32, 0, conv_f32d_to_s32_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(F32P, S32, 0, conv_f32d_to_s32_c), + + MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_noise_c, 0, CONV_NOISE), + MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_c), + + MAKE(F32, U24, 0, conv_f32_to_u24_c), + MAKE(F32P, U24, 0, conv_f32d_to_u24_c), + + MAKE(F32, S24, 0, conv_f32_to_s24_c), + MAKE(F32P, S24P, 0, conv_f32d_to_s24d_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24P, 0, conv_f32d_to_s24d_c), + MAKE(F32, S24P, 0, conv_f32_to_s24d_c), + MAKE(F32P, S24, 0, conv_f32d_to_s24_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24, 0, conv_f32d_to_s24_c), + + MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_c), + + MAKE(F32, U24_32, 0, conv_f32_to_u24_32_c), + MAKE(F32P, U24_32, 0, conv_f32d_to_u24_32_c), + + MAKE(F32, S24_32, 0, conv_f32_to_s24_32_c), + MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_c), + MAKE(F32, S24_32P, 0, conv_f32_to_s24_32d_c), + MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_c), + + MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_c), + + MAKE(F32, F64, 0, conv_f32_to_f64_c), + MAKE(F32P, F64P, 0, conv_f32d_to_f64d_c), + MAKE(F32, F64P, 0, conv_f32_to_f64d_c), + MAKE(F32P, F64, 0, conv_f32d_to_f64_c), + + MAKE(F32P, F64_OE, 0, conv_f32d_to_f64s_c), + + /* u8 */ + MAKE(U8, U8, 0, conv_copy8_c), + MAKE(U8P, U8P, 0, conv_copy8d_c), + MAKE(U8, U8P, 0, conv_8_to_8d_c), + MAKE(U8P, U8, 0, conv_8d_to_8_c), + + /* s8 */ + MAKE(S8, S8, 0, conv_copy8_c), + MAKE(S8P, S8P, 0, conv_copy8d_c), + MAKE(S8, S8P, 0, conv_8_to_8d_c), + MAKE(S8P, S8, 0, conv_8d_to_8_c), + + /* alaw */ + MAKE(ALAW, ALAW, 0, conv_copy8_c), + /* ulaw */ + MAKE(ULAW, ULAW, 0, conv_copy8_c), + + /* s16 */ + MAKE(S16, S16, 0, conv_copy16_c), + MAKE(S16P, S16P, 0, conv_copy16d_c), + MAKE(S16, S16P, 0, conv_16_to_16d_c), + MAKE(S16P, S16, 0, conv_16d_to_16_c), + + /* s32 */ + MAKE(S32, S32, 0, conv_copy32_c), + MAKE(S32P, S32P, 0, conv_copy32d_c), +#if defined (HAVE_SSE2) + MAKE(S32, S32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(S32, S32P, 0, conv_32_to_32d_c), +#if defined (HAVE_SSE2) + MAKE(S32P, S32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(S32P, S32, 0, conv_32d_to_32_c), + + /* s24 */ + MAKE(S24, S24, 0, conv_copy24_c), + MAKE(S24P, S24P, 0, conv_copy24d_c), + MAKE(S24, S24P, 0, conv_24_to_24d_c), + MAKE(S24P, S24, 0, conv_24d_to_24_c), + + /* s24_32 */ + MAKE(S24_32, S24_32, 0, conv_copy32_c), + MAKE(S24_32P, S24_32P, 0, conv_copy32d_c), +#if defined (HAVE_SSE2) + MAKE(S24_32, S24_32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(S24_32, S24_32P, 0, conv_32_to_32d_c), +#if defined (HAVE_SSE2) + MAKE(S24_32P, S24_32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(S24_32P, S24_32, 0, conv_32d_to_32_c), + + /* F64 */ + MAKE(F64, F64, 0, conv_copy64_c), + MAKE(F64P, F64P, 0, conv_copy64d_c), + MAKE(F64, F64P, 0, conv_64_to_64d_c), + MAKE(F64P, F64, 0, conv_64d_to_64_c), +}; +#undef MAKE + +#define MATCH_CHAN(a,b) ((a) == 0 || (a) == (b)) +#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) +#define MATCH_DITHER(a,b) ((a) == 0 || ((a) & (b)) == a) + +static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt, + uint32_t n_channels, uint32_t cpu_flags, uint32_t conv_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(conv_table, c) { + if (c->src_fmt == src_fmt && + c->dst_fmt == dst_fmt && + MATCH_CHAN(c->n_channels, n_channels) && + MATCH_CPU_FLAGS(c->cpu_flags, cpu_flags) && + MATCH_DITHER(c->conv_flags, conv_flags)) + return c; + } + return NULL; +} + +typedef void (*noise_func_t) (struct convert *conv, float * noise, uint32_t n_samples); + +struct noise_info { + uint32_t method; + + noise_func_t noise; + const char *name; + + uint32_t cpu_flags; +}; + +#define MAKE(method,func,...) \ + { NOISE_METHOD_ ##method, func, #func , __VA_ARGS__ } + +static struct noise_info noise_table[] = +{ +#if defined (HAVE_SSE2) + MAKE(RECTANGULAR, conv_noise_rect_sse2, SPA_CPU_FLAG_SSE2), + MAKE(TRIANGULAR, conv_noise_tri_sse2, SPA_CPU_FLAG_SSE2), + MAKE(TRIANGULAR_HF, conv_noise_tri_hf_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(NONE, conv_noise_none_c), + MAKE(RECTANGULAR, conv_noise_rect_c), + MAKE(TRIANGULAR, conv_noise_tri_c), + MAKE(TRIANGULAR_HF, conv_noise_tri_hf_c), + MAKE(PATTERN, conv_noise_pattern_c), +}; +#undef MAKE + +static const struct noise_info *find_noise_info(uint32_t method, + uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(noise_table, t) { + if (t->method == method && + MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; + } + return NULL; +} + +static void impl_convert_free(struct convert *conv) +{ + conv->process = NULL; + free(conv->data); + conv->data = NULL; +} + +static bool need_dither(uint32_t format) +{ + switch (format) { + case SPA_AUDIO_FORMAT_U8: + case SPA_AUDIO_FORMAT_U8P: + case SPA_AUDIO_FORMAT_S8: + case SPA_AUDIO_FORMAT_S8P: + case SPA_AUDIO_FORMAT_ULAW: + case SPA_AUDIO_FORMAT_ALAW: + case SPA_AUDIO_FORMAT_S16P: + case SPA_AUDIO_FORMAT_S16: + case SPA_AUDIO_FORMAT_S16_OE: + return true; + } + return false; +} + +/* filters based on F-weighted curves + * from 'Psychoacoustically Optimal Noise Shaping' (**) + * this filter is the "F-Weighted" noise filter described by Wannamaker + * It is designed to produce minimum audibility: */ +static const float wan3[] = { /* Table 3; 3 Coefficients */ + 1.623f, -0.982f, 0.109f +}; +/* Noise shaping coefficients from[1], moves most power of the + * error noise into inaudible frequency ranges. + * + * [1] + * "Minimally Audible Noise Shaping", Stanley P. Lipshitz, + * John Vanderkooy, and Robert A. Wannamaker, + * J. Audio Eng. Soc., Vol. 39, No. 11, November 1991. */ +static const float lips44[] = { /* improved E-weighted (appendix: 5) */ + 2.033f, -2.165f, 1.959f, -1.590f, 0.6149f +}; + +static const struct dither_info { + uint32_t method; + uint32_t noise_method; + uint32_t rate; + const float *ns; + uint32_t n_ns; +} dither_info[] = { + { DITHER_METHOD_NONE, NOISE_METHOD_NONE, }, + { DITHER_METHOD_RECTANGULAR, NOISE_METHOD_RECTANGULAR, }, + { DITHER_METHOD_TRIANGULAR, NOISE_METHOD_TRIANGULAR, }, + { DITHER_METHOD_TRIANGULAR_HF, NOISE_METHOD_TRIANGULAR_HF, }, + { DITHER_METHOD_WANNAMAKER_3, NOISE_METHOD_TRIANGULAR_HF, 44100, wan3, SPA_N_ELEMENTS(wan3) }, + { DITHER_METHOD_LIPSHITZ, NOISE_METHOD_TRIANGULAR, 44100, lips44, SPA_N_ELEMENTS(lips44) } +}; + +static const struct dither_info *find_dither_info(uint32_t method, uint32_t rate) +{ + SPA_FOR_EACH_ELEMENT_VAR(dither_info, di) { + if (di->method != method) + continue; + /* don't use shaped for too low rates, it moves the noise to + * audible ranges */ + if (di->ns != NULL && rate < di->rate * 3 / 4) + return find_dither_info(DITHER_METHOD_TRIANGULAR_HF, rate); + return di; + } + return NULL; +} + +int convert_init(struct convert *conv) +{ + const struct conv_info *info; + const struct dither_info *dinfo; + const struct noise_info *ninfo; + uint32_t i, conv_flags, data_size[3]; + + conv->scale = 1.0f / (float)(INT32_MAX); + + /* disable dither if not needed */ + if (!need_dither(conv->dst_fmt)) + conv->method = DITHER_METHOD_NONE; + + dinfo = find_dither_info(conv->method, conv->rate); + if (dinfo == NULL) + return -EINVAL; + + conv->noise_method = dinfo->noise_method; + if (conv->noise_bits > 0) { + switch (conv->noise_method) { + case NOISE_METHOD_NONE: + conv->noise_method = NOISE_METHOD_PATTERN; + conv->scale = -1.0f * (1 << (conv->noise_bits-1)); + break; + case NOISE_METHOD_RECTANGULAR: + conv->noise_method = NOISE_METHOD_TRIANGULAR; + SPA_FALLTHROUGH; + case NOISE_METHOD_TRIANGULAR: + case NOISE_METHOD_TRIANGULAR_HF: + conv->scale *= (1 << (conv->noise_bits-1)); + break; + } + } + if (conv->noise_method < NOISE_METHOD_TRIANGULAR) + conv->scale *= 0.5f; + + conv_flags = 0; + if (conv->noise_method != NOISE_METHOD_NONE) + conv_flags |= CONV_NOISE; + if (dinfo->n_ns > 0) { + conv_flags |= CONV_SHAPE; + conv->n_ns = dinfo->n_ns; + conv->ns = dinfo->ns; + } + + info = find_conv_info(conv->src_fmt, conv->dst_fmt, conv->n_channels, + conv->cpu_flags, conv_flags); + if (info == NULL) + return -ENOTSUP; + + ninfo = find_noise_info(conv->noise_method, conv->cpu_flags); + if (ninfo == NULL) + return -ENOTSUP; + + conv->noise_size = NOISE_SIZE; + + data_size[0] = SPA_ROUND_UP(conv->noise_size * sizeof(float), FMT_OPS_MAX_ALIGN); + data_size[1] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(uint32_t), FMT_OPS_MAX_ALIGN); + data_size[2] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(int32_t), FMT_OPS_MAX_ALIGN); + + conv->data = calloc(FMT_OPS_MAX_ALIGN + + data_size[0] + data_size[1] + data_size[2], 1); + if (conv->data == NULL) + return -errno; + + conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float); + conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t); + conv->prev = SPA_PTROFF(conv->random, data_size[1], int32_t); + + for (i = 0; i < RANDOM_SIZE; i++) + conv->random[i] = random(); + + conv->is_passthrough = conv->src_fmt == conv->dst_fmt; + conv->cpu_flags = info->cpu_flags; + conv->update_noise = ninfo->noise; + conv->process = info->process; + conv->free = impl_convert_free; + conv->func_name = info->name; + + return 0; +} diff --git a/spa/plugins/audioconvert/fmt-ops.h b/spa/plugins/audioconvert/fmt-ops.h new file mode 100644 index 0000000..c2afaa2 --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops.h @@ -0,0 +1,488 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <math.h> +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#include <sys/endian.h> +#define bswap_16 bswap16 +#define bswap_32 bswap32 +#define bswap_64 bswap64 +#else +#include <byteswap.h> +#endif + +#include <spa/utils/defs.h> +#include <spa/utils/string.h> + +#define f32_round(a) lrintf(a) + +#define ITOF(type,v,scale,offs) \ + (((type)(v)) * (1.0f / (scale)) - (offs)) +#define FTOI(type,v,scale,offs,noise,min,max) \ + (type)f32_round(SPA_CLAMPF((v) * (scale) + (offs) + (noise), min, max)) + +#define FMT_OPS_MAX_ALIGN 32 + +#define U8_MIN 0u +#define U8_MAX 255u +#define U8_SCALE 128.f +#define U8_OFFS 128.f +#define U8_TO_F32(v) ITOF(uint8_t, v, U8_SCALE, 1.0f) +#define F32_TO_U8_D(v,d) FTOI(uint8_t, v, U8_SCALE, U8_OFFS, d, U8_MIN, U8_MAX) +#define F32_TO_U8(v) F32_TO_U8_D(v, 0.0f) + +#define S8_MIN -128 +#define S8_MAX 127 +#define S8_SCALE 128.0f +#define S8_TO_F32(v) ITOF(int8_t, v, S8_SCALE, 0.0f) +#define F32_TO_S8_D(v,d) FTOI(int8_t, v, S8_SCALE, 0.0f, d, S8_MIN, S8_MAX) +#define F32_TO_S8(v) F32_TO_S8_D(v, 0.0f); + +#define U16_MIN 0u +#define U16_MAX 65535u +#define U16_SCALE 32768.f +#define U16_OFFS 32768.f +#define U16_TO_F32(v) ITOF(uint16_t, v, U16_SCALE, 1.0f) +#define U16S_TO_F32(v) U16_TO_F32(bswap_16(v)) +#define F32_TO_U16_D(v,d) FTOI(uint16_t, v, U16_SCALE, U16_OFFS, d, U16_MIN, U16_MAX) +#define F32_TO_U16(v) F32_TO_U16_D(v, 0.0f); +#define F32_TO_U16S_D(v,d) bswap_16(F32_TO_U16_D(v,d)) +#define F32_TO_U16S(v) bswap_16(F32_TO_U16(v)) + +#define S16_MIN -32768 +#define S16_MAX 32767 +#define S16_SCALE 32768.0f +#define S16_TO_F32(v) ITOF(int16_t, v, S16_SCALE, 0.0f) +#define S16S_TO_F32(v) S16_TO_F32(bswap_16(v)) +#define F32_TO_S16_D(v,d) FTOI(int16_t, v, S16_SCALE, 0.0f, d, S16_MIN, S16_MAX) +#define F32_TO_S16(v) F32_TO_S16_D(v, 0.0f) +#define F32_TO_S16S_D(v,d) bswap_16(F32_TO_S16_D(v,d)) +#define F32_TO_S16S(v) bswap_16(F32_TO_S16(v)) + +#define U24_MIN 0u +#define U24_MAX 16777215u +#define U24_SCALE 8388608.f +#define U24_OFFS 8388608.f +#define U24_TO_F32(v) ITOF(uint32_t, u24_to_u32(v), U24_SCALE, 1.0f) +#define F32_TO_U24_D(v,d) u32_to_u24(FTOI(uint32_t, v, U24_SCALE, U24_OFFS, d, U24_MIN, U24_MAX)) +#define F32_TO_U24(v) F32_TO_U24_D(v, 0.0f) + +#define S24_MIN -8388608 +#define S24_MAX 8388607 +#define S24_SCALE 8388608.0f +#define S24_TO_F32(v) ITOF(int32_t, s24_to_s32(v), S24_SCALE, 0.0f) +#define S24S_TO_F32(v) S24_TO_F32(bswap_s24(v)) +#define F32_TO_S24_D(v,d) s32_to_s24(FTOI(int32_t, v, S24_SCALE, 0.0f, d, S24_MIN, S24_MAX)) +#define F32_TO_S24(v) F32_TO_S24_D(v, 0.0f) +#define F32_TO_S24S(v) bswap_s24(F32_TO_S24(v)) + +#define U24_32_TO_U32(v) (((uint32_t)(v)) << 8) + +#define U24_32_TO_F32(v) U32_TO_F32(U24_32_TO_U32(v)) +#define U24_32S_TO_F32(v) U24_32_TO_F32(bswap_32(v)) +#define F32_TO_U24_32_D(v,d) FTOI(uint32_t, v, U24_SCALE, U24_OFFS, d, U24_MIN, U24_MAX) +#define F32_TO_U24_32(v) F32_TO_U24_32_D(v, 0.0f) +#define F32_TO_U24_32S(v) bswap_32(F32_TO_U24_32(v)) +#define F32_TO_U24_32S_D(v,d) bswap_32(F32_TO_U24_32_D(v,d)) + +#define U32_TO_U24_32(v) (((uint32_t)(v)) >> 8) + +#define U32_MIN 0u +#define U32_MAX 4294967295u +#define U32_SCALE 2147483648.f +#define U32_OFFS 2147483648.f +#define U32_TO_F32(v) ITOF(uint32_t, U32_TO_U24_32(v), U24_SCALE, 1.0f) +#define F32_TO_U32(v) U24_32_TO_U32(F32_TO_U24_32(v)) +#define F32_TO_U32_D(v,d) U24_32_TO_U32(F32_TO_U24_32_D(v,d)) + +#define S24_32_TO_S32(v) ((int32_t)(((uint32_t)(v)) << 8)) + +#define S24_32_TO_F32(v) S32_TO_F32(S24_32_TO_S32(v)) +#define S24_32S_TO_F32(v) S24_32_TO_F32(bswap_32(v)) +#define F32_TO_S24_32_D(v,d) FTOI(int32_t, v, S24_SCALE, 0.0f, d, S24_MIN, S24_MAX) +#define F32_TO_S24_32(v) F32_TO_S24_32_D(v, 0.0f) +#define F32_TO_S24_32S(v) bswap_32(F32_TO_S24_32(v)) +#define F32_TO_S24_32S_D(v,d) bswap_32(F32_TO_S24_32_D(v,d)) + +#define S32_TO_S24_32(v) (((int32_t)(v)) >> 8) + +#define S32_MIN (S24_MIN * 256) +#define S32_MAX (S24_MAX * 256) +#define S32_TO_F32(v) ITOF(int32_t, S32_TO_S24_32(v), S24_SCALE, 0.0f) +#define S32S_TO_F32(v) S32_TO_F32(bswap_32(v)) +#define F32_TO_S32(v) S24_32_TO_S32(F32_TO_S24_32(v)) +#define F32_TO_S32_D(v,d) S24_32_TO_S32(F32_TO_S24_32_D(v,d)) +#define F32_TO_S32S(v) bswap_32(F32_TO_S32(v)) +#define F32_TO_S32S_D(v,d) bswap_32(F32_TO_S32_D(v,d)) + +typedef struct { +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t v3; + uint8_t v2; + uint8_t v1; +#else + uint8_t v1; + uint8_t v2; + uint8_t v3; +#endif +} __attribute__ ((packed)) uint24_t; + +typedef struct { +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t v3; + uint8_t v2; + int8_t v1; +#else + int8_t v1; + uint8_t v2; + uint8_t v3; +#endif +} __attribute__ ((packed)) int24_t; + +static inline uint32_t u24_to_u32(uint24_t src) +{ + return ((uint32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; +} + +#define U32_TO_U24(s) (uint24_t) { .v1 = (uint8_t)(((uint32_t)s) >> 16), \ + .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } + +static inline uint24_t u32_to_u24(uint32_t src) +{ + return U32_TO_U24(src); +} + +static inline int32_t s24_to_s32(int24_t src) +{ + return ((int32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; +} + +#define S32_TO_S24(s) (int24_t) { .v1 = (int8_t)(((int32_t)s) >> 16), \ + .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } + +static inline int24_t s32_to_s24(int32_t src) +{ + return S32_TO_S24(src); +} + +static inline uint24_t bswap_u24(uint24_t src) +{ + return (uint24_t) { .v1 = src.v3, .v2 = src.v2, .v3 = src.v1 }; +} +static inline int24_t bswap_s24(int24_t src) +{ + return (int24_t) { .v1 = src.v3, .v2 = src.v2, .v3 = src.v1 }; +} + +#define F32_TO_F32S(v) \ + bswap_32((union { uint32_t i; float f; }){ .f = (v) }.i) +#define F32S_TO_F32(v) \ + ((union { uint32_t i; float f; }){ .i = bswap_32(v) }.f) + +#define F64_TO_F64S(v) \ + bswap_32((union { uint64_t i; double d; }){ .d = (v) }.i) +#define F64S_TO_F64(v) \ + ((union { uint64_t i; double d; }){ .i = bswap_32(v) }.d) + +#define NS_MAX 8 +#define NS_MASK (NS_MAX-1) + +struct shaper { + float e[NS_MAX * 2]; + uint32_t idx; + float r; +}; + +struct convert { + uint32_t noise_bits; +#define DITHER_METHOD_NONE 0 +#define DITHER_METHOD_RECTANGULAR 1 +#define DITHER_METHOD_TRIANGULAR 2 +#define DITHER_METHOD_TRIANGULAR_HF 3 +#define DITHER_METHOD_WANNAMAKER_3 4 +#define DITHER_METHOD_LIPSHITZ 5 + uint32_t method; + + uint32_t src_fmt; + uint32_t dst_fmt; + uint32_t n_channels; + uint32_t rate; + uint32_t cpu_flags; + const char *func_name; + + unsigned int is_passthrough:1; + + float scale; + uint32_t *random; + int32_t *prev; +#define NOISE_METHOD_NONE 0 +#define NOISE_METHOD_RECTANGULAR 1 +#define NOISE_METHOD_TRIANGULAR 2 +#define NOISE_METHOD_TRIANGULAR_HF 3 +#define NOISE_METHOD_PATTERN 4 + uint32_t noise_method; + float *noise; + uint32_t noise_size; + const float *ns; + uint32_t n_ns; + struct shaper shaper[64]; + + void (*update_noise) (struct convert *conv, float *noise, uint32_t n_samples); + void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples); + void (*free) (struct convert *conv); + + void *data; +}; + +int convert_init(struct convert *conv); + +static const struct dither_method_info { + uint32_t method; + const char *label; + const char *description; +} dither_method_info[] = { + [DITHER_METHOD_NONE] = { DITHER_METHOD_NONE, + "none", "Disabled", }, + [DITHER_METHOD_RECTANGULAR] = { DITHER_METHOD_RECTANGULAR, + "rectangular", "Rectangular dithering", }, + [DITHER_METHOD_TRIANGULAR] = { DITHER_METHOD_TRIANGULAR, + "triangular", "Triangular dithering", }, + [DITHER_METHOD_TRIANGULAR_HF] = { DITHER_METHOD_TRIANGULAR_HF, + "triangular-hf", "Sloped Triangular dithering", }, + [DITHER_METHOD_WANNAMAKER_3] = { DITHER_METHOD_WANNAMAKER_3, + "wannamaker3", "Wannamaker 3 dithering", }, + [DITHER_METHOD_LIPSHITZ] = { DITHER_METHOD_LIPSHITZ, + "shaped5", "Lipshitz 5 dithering", }, +}; + +static inline uint32_t dither_method_from_label(const char *label) +{ + SPA_FOR_EACH_ELEMENT_VAR(dither_method_info, i) { + if (spa_streq(i->label, label)) + return i->method; + } + return DITHER_METHOD_NONE; +} + +#define convert_update_noise(conv,...) (conv)->update_noise(conv, __VA_ARGS__) +#define convert_process(conv,...) (conv)->process(conv, __VA_ARGS__) +#define convert_free(conv) (conv)->free(conv) + +#define DEFINE_NOISE_FUNCTION(name,arch) \ +void conv_noise_##name##_##arch(struct convert *conv, float *noise, \ + uint32_t n_samples) + +DEFINE_NOISE_FUNCTION(none, c); +DEFINE_NOISE_FUNCTION(rect, c); +DEFINE_NOISE_FUNCTION(tri, c); +DEFINE_NOISE_FUNCTION(tri_hf, c); +DEFINE_NOISE_FUNCTION(pattern, c); +#if defined(HAVE_SSE2) +DEFINE_NOISE_FUNCTION(rect, sse2); +DEFINE_NOISE_FUNCTION(tri, sse2); +DEFINE_NOISE_FUNCTION(tri_hf, sse2); +#endif + +#undef DEFINE_NOISE_FUNCTION + +#define DEFINE_FUNCTION(name,arch) \ +void conv_##name##_##arch(struct convert *conv, void * SPA_RESTRICT dst[], \ + const void * SPA_RESTRICT src[], uint32_t n_samples) + +DEFINE_FUNCTION(copy8d, c); +DEFINE_FUNCTION(copy8, c); +DEFINE_FUNCTION(copy16d, c); +DEFINE_FUNCTION(copy16, c); +DEFINE_FUNCTION(copy24d, c); +DEFINE_FUNCTION(copy24, c); +DEFINE_FUNCTION(copy32d, c); +DEFINE_FUNCTION(copy32, c); +DEFINE_FUNCTION(copy64d, c); +DEFINE_FUNCTION(copy64, c); +DEFINE_FUNCTION(u8d_to_f32d, c); +DEFINE_FUNCTION(u8_to_f32, c); +DEFINE_FUNCTION(u8_to_f32d, c); +DEFINE_FUNCTION(u8d_to_f32, c); +DEFINE_FUNCTION(s8d_to_f32d, c); +DEFINE_FUNCTION(s8_to_f32, c); +DEFINE_FUNCTION(s8_to_f32d, c); +DEFINE_FUNCTION(s8d_to_f32, c); +DEFINE_FUNCTION(ulaw_to_f32d, c); +DEFINE_FUNCTION(alaw_to_f32d, c); +DEFINE_FUNCTION(u16_to_f32, c); +DEFINE_FUNCTION(u16_to_f32d, c); +DEFINE_FUNCTION(s16d_to_f32d, c); +DEFINE_FUNCTION(s16_to_f32, c); +DEFINE_FUNCTION(s16_to_f32d, c); +DEFINE_FUNCTION(s16s_to_f32d, c); +DEFINE_FUNCTION(s16d_to_f32, c); +DEFINE_FUNCTION(u32_to_f32, c); +DEFINE_FUNCTION(u32_to_f32d, c); +DEFINE_FUNCTION(s32d_to_f32d, c); +DEFINE_FUNCTION(s32_to_f32, c); +DEFINE_FUNCTION(s32_to_f32d, c); +DEFINE_FUNCTION(s32s_to_f32d, c); +DEFINE_FUNCTION(s32d_to_f32, c); +DEFINE_FUNCTION(u24_to_f32, c); +DEFINE_FUNCTION(u24_to_f32d, c); +DEFINE_FUNCTION(s24d_to_f32d, c); +DEFINE_FUNCTION(s24_to_f32, c); +DEFINE_FUNCTION(s24_to_f32d, c); +DEFINE_FUNCTION(s24s_to_f32d, c); +DEFINE_FUNCTION(s24d_to_f32, c); +DEFINE_FUNCTION(u24_32_to_f32, c); +DEFINE_FUNCTION(u24_32_to_f32d, c); +DEFINE_FUNCTION(s24_32d_to_f32d, c); +DEFINE_FUNCTION(s24_32_to_f32, c); +DEFINE_FUNCTION(s24_32_to_f32d, c); +DEFINE_FUNCTION(s24_32s_to_f32d, c); +DEFINE_FUNCTION(s24_32d_to_f32, c); +DEFINE_FUNCTION(f64d_to_f32d, c); +DEFINE_FUNCTION(f64_to_f32, c); +DEFINE_FUNCTION(f64_to_f32d, c); +DEFINE_FUNCTION(f64s_to_f32d, c); +DEFINE_FUNCTION(f64d_to_f32, c); +DEFINE_FUNCTION(f32d_to_u8d, c); +DEFINE_FUNCTION(f32d_to_u8d_noise, c); +DEFINE_FUNCTION(f32d_to_u8d_shaped, c); +DEFINE_FUNCTION(f32_to_u8, c); +DEFINE_FUNCTION(f32_to_u8d, c); +DEFINE_FUNCTION(f32d_to_u8, c); +DEFINE_FUNCTION(f32d_to_u8_noise, c); +DEFINE_FUNCTION(f32d_to_u8_shaped, c); +DEFINE_FUNCTION(f32d_to_s8d, c); +DEFINE_FUNCTION(f32d_to_s8d_noise, c); +DEFINE_FUNCTION(f32d_to_s8d_shaped, c); +DEFINE_FUNCTION(f32_to_s8, c); +DEFINE_FUNCTION(f32_to_s8d, c); +DEFINE_FUNCTION(f32d_to_s8, c); +DEFINE_FUNCTION(f32d_to_s8_noise, c); +DEFINE_FUNCTION(f32d_to_s8_shaped, c); +DEFINE_FUNCTION(f32d_to_alaw, c); +DEFINE_FUNCTION(f32d_to_ulaw, c); +DEFINE_FUNCTION(f32_to_u16, c); +DEFINE_FUNCTION(f32d_to_u16, c); +DEFINE_FUNCTION(f32d_to_s16d, c); +DEFINE_FUNCTION(f32d_to_s16d_noise, c); +DEFINE_FUNCTION(f32d_to_s16d_shaped, c); +DEFINE_FUNCTION(f32_to_s16, c); +DEFINE_FUNCTION(f32_to_s16d, c); +DEFINE_FUNCTION(f32d_to_s16, c); +DEFINE_FUNCTION(f32d_to_s16_noise, c); +DEFINE_FUNCTION(f32d_to_s16_shaped, c); +DEFINE_FUNCTION(f32d_to_s16s, c); +DEFINE_FUNCTION(f32d_to_s16s_noise, c); +DEFINE_FUNCTION(f32d_to_s16s_shaped, c); +DEFINE_FUNCTION(f32_to_u32, c); +DEFINE_FUNCTION(f32d_to_u32, c); +DEFINE_FUNCTION(f32d_to_s32d, c); +DEFINE_FUNCTION(f32d_to_s32d_noise, c); +DEFINE_FUNCTION(f32_to_s32, c); +DEFINE_FUNCTION(f32_to_s32d, c); +DEFINE_FUNCTION(f32d_to_s32, c); +DEFINE_FUNCTION(f32d_to_s32_noise, c); +DEFINE_FUNCTION(f32d_to_s32s, c); +DEFINE_FUNCTION(f32d_to_s32s_noise, c); +DEFINE_FUNCTION(f32_to_u24, c); +DEFINE_FUNCTION(f32d_to_u24, c); +DEFINE_FUNCTION(f32d_to_s24d, c); +DEFINE_FUNCTION(f32d_to_s24d_noise, c); +DEFINE_FUNCTION(f32_to_s24, c); +DEFINE_FUNCTION(f32_to_s24d, c); +DEFINE_FUNCTION(f32d_to_s24, c); +DEFINE_FUNCTION(f32d_to_s24_noise, c); +DEFINE_FUNCTION(f32d_to_s24s, c); +DEFINE_FUNCTION(f32d_to_s24s_noise, c); +DEFINE_FUNCTION(f32_to_u24_32, c); +DEFINE_FUNCTION(f32d_to_u24_32, c); +DEFINE_FUNCTION(f32d_to_s24_32d, c); +DEFINE_FUNCTION(f32d_to_s24_32d_noise, c); +DEFINE_FUNCTION(f32_to_s24_32, c); +DEFINE_FUNCTION(f32_to_s24_32d, c); +DEFINE_FUNCTION(f32d_to_s24_32, c); +DEFINE_FUNCTION(f32d_to_s24_32_noise, c); +DEFINE_FUNCTION(f32d_to_s24_32s, c); +DEFINE_FUNCTION(f32d_to_s24_32s_noise, c); +DEFINE_FUNCTION(f32d_to_f64d, c); +DEFINE_FUNCTION(f32_to_f64, c); +DEFINE_FUNCTION(f32_to_f64d, c); +DEFINE_FUNCTION(f32d_to_f64, c); +DEFINE_FUNCTION(f32d_to_f64s, c); +DEFINE_FUNCTION(8_to_8d, c); +DEFINE_FUNCTION(16_to_16d, c); +DEFINE_FUNCTION(24_to_24d, c); +DEFINE_FUNCTION(32_to_32d, c); +DEFINE_FUNCTION(32s_to_32d, c); +DEFINE_FUNCTION(64_to_64d, c); +DEFINE_FUNCTION(64s_to_64sd, c); +DEFINE_FUNCTION(8d_to_8, c); +DEFINE_FUNCTION(16d_to_16, c); +DEFINE_FUNCTION(24d_to_24, c); +DEFINE_FUNCTION(32d_to_32, c); +DEFINE_FUNCTION(32d_to_32s, c); +DEFINE_FUNCTION(64d_to_64, c); +DEFINE_FUNCTION(64sd_to_64s, c); + +#if defined(HAVE_NEON) +DEFINE_FUNCTION(s16_to_f32d_2, neon); +DEFINE_FUNCTION(s16_to_f32d, neon); +DEFINE_FUNCTION(f32d_to_s16, neon); +#endif +#if defined(HAVE_SSE2) +DEFINE_FUNCTION(s16_to_f32d_2, sse2); +DEFINE_FUNCTION(s16_to_f32d, sse2); +DEFINE_FUNCTION(s24_to_f32d, sse2); +DEFINE_FUNCTION(s32_to_f32d, sse2); +DEFINE_FUNCTION(f32d_to_s32, sse2); +DEFINE_FUNCTION(f32d_to_s32_noise, sse2); +DEFINE_FUNCTION(f32_to_s16, sse2); +DEFINE_FUNCTION(f32d_to_s16_2, sse2); +DEFINE_FUNCTION(f32d_to_s16, sse2); +DEFINE_FUNCTION(f32d_to_s16_noise, sse2); +DEFINE_FUNCTION(f32d_to_s16d, sse2); +DEFINE_FUNCTION(f32d_to_s16d_noise, sse2); +DEFINE_FUNCTION(32_to_32d, sse2); +DEFINE_FUNCTION(32s_to_32d, sse2); +DEFINE_FUNCTION(32d_to_32, sse2); +DEFINE_FUNCTION(32d_to_32s, sse2); +#endif +#if defined(HAVE_SSSE3) +DEFINE_FUNCTION(s24_to_f32d, ssse3); +#endif +#if defined(HAVE_SSE41) +DEFINE_FUNCTION(s24_to_f32d, sse41); +#endif +#if defined(HAVE_AVX2) +DEFINE_FUNCTION(s16_to_f32d_2, avx2); +DEFINE_FUNCTION(s16_to_f32d, avx2); +DEFINE_FUNCTION(s24_to_f32d, avx2); +DEFINE_FUNCTION(s32_to_f32d, avx2); +DEFINE_FUNCTION(f32d_to_s32, avx2); +DEFINE_FUNCTION(f32d_to_s16_4, avx2); +DEFINE_FUNCTION(f32d_to_s16_2, avx2); +DEFINE_FUNCTION(f32d_to_s16, avx2); +#endif + +#undef DEFINE_FUNCTION diff --git a/spa/plugins/audioconvert/hilbert.h b/spa/plugins/audioconvert/hilbert.h new file mode 100644 index 0000000..103e6e9 --- /dev/null +++ b/spa/plugins/audioconvert/hilbert.h @@ -0,0 +1,69 @@ +/* Hilbert function + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef HILBERT_H +#define HILBERT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <errno.h> +#include <stddef.h> +#include <math.h> + +static inline void blackman_window(float *taps, int n_taps) +{ + int n; + for (n = 0; n < n_taps; n++) { + float w = 2 * M_PI * n / (n_taps-1); + taps[n] = 0.3635819 - 0.4891775 * cos(w) + + 0.1365995 * cos(2 * w) - 0.0106411 * cos(3 * w); + } +} + +static inline int hilbert_generate(float *taps, int n_taps) +{ + int i; + + if ((n_taps & 1) == 0) + return -EINVAL; + + for (i = 0; i < n_taps; i++) { + int k = -(n_taps / 2) + i; + if (k & 1) { + float pk = M_PI * k; + taps[i] *= (1.0f - cosf(pk)) / pk; + } else { + taps[i] = 0.0f; + } + } + return 0; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* HILBERT_H */ diff --git a/spa/plugins/audioconvert/law.h b/spa/plugins/audioconvert/law.h new file mode 100644 index 0000000..f5273d8 --- /dev/null +++ b/spa/plugins/audioconvert/law.h @@ -0,0 +1,2163 @@ +/* Spa + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +static inline float alaw_to_f32(uint8_t alawbyte) +{ + static int16_t alaw_decode[256] = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, + -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, + -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, + -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848 + }; + return S16_TO_F32(alaw_decode[alawbyte]); +} + +static inline float ulaw_to_f32(uint8_t ulawbyte) +{ + static int16_t ulaw_decode[256] = { + -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, + -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, + -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, + -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 + }; + return S16_TO_F32(ulaw_decode[ulawbyte]); +} + +static inline uint8_t f32_to_alaw(float val) +{ + static uint8_t alaw_encode[0x2000] = { + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, + 0x6b, 0x6b, 0x6b, 0x6b, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6e, 0x6e, 0x6e, 0x6e, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, + 0x6d, 0x6d, 0x6d, 0x6d, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x60, 0x60, 0x60, 0x60, + 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, + 0x67, 0x67, 0x67, 0x67, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x7a, 0x7a, 0x7a, 0x7a, + 0x7b, 0x7b, 0x7b, 0x7b, 0x78, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x79, + 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, + 0x7d, 0x7d, 0x7d, 0x7d, 0x72, 0x72, 0x72, 0x72, 0x73, 0x73, 0x73, 0x73, + 0x70, 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x71, 0x76, 0x76, 0x76, 0x76, + 0x77, 0x77, 0x77, 0x77, 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x75, + 0x4a, 0x4a, 0x4b, 0x4b, 0x48, 0x48, 0x49, 0x49, 0x4e, 0x4e, 0x4f, 0x4f, + 0x4c, 0x4c, 0x4d, 0x4d, 0x42, 0x42, 0x43, 0x43, 0x40, 0x40, 0x41, 0x41, + 0x46, 0x46, 0x47, 0x47, 0x44, 0x44, 0x45, 0x45, 0x5a, 0x5a, 0x5b, 0x5b, + 0x58, 0x58, 0x59, 0x59, 0x5e, 0x5e, 0x5f, 0x5f, 0x5c, 0x5c, 0x5d, 0x5d, + 0x52, 0x52, 0x53, 0x53, 0x50, 0x50, 0x51, 0x51, 0x56, 0x56, 0x57, 0x57, + 0x54, 0x54, 0x55, 0x55, 0xd5, 0xd5, 0xd4, 0xd4, 0xd7, 0xd7, 0xd6, 0xd6, + 0xd1, 0xd1, 0xd0, 0xd0, 0xd3, 0xd3, 0xd2, 0xd2, 0xdd, 0xdd, 0xdc, 0xdc, + 0xdf, 0xdf, 0xde, 0xde, 0xd9, 0xd9, 0xd8, 0xd8, 0xdb, 0xdb, 0xda, 0xda, + 0xc5, 0xc5, 0xc4, 0xc4, 0xc7, 0xc7, 0xc6, 0xc6, 0xc1, 0xc1, 0xc0, 0xc0, + 0xc3, 0xc3, 0xc2, 0xc2, 0xcd, 0xcd, 0xcc, 0xcc, 0xcf, 0xcf, 0xce, 0xce, + 0xc9, 0xc9, 0xc8, 0xc8, 0xcb, 0xcb, 0xca, 0xca, 0xf5, 0xf5, 0xf5, 0xf5, + 0xf4, 0xf4, 0xf4, 0xf4, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xf6, + 0xf1, 0xf1, 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf3, 0xf3, 0xf3, 0xf3, + 0xf2, 0xf2, 0xf2, 0xf2, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xf9, 0xf9, 0xf9, 0xf9, + 0xf8, 0xf8, 0xf8, 0xf8, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, + 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, + 0xe4, 0xe4, 0xe4, 0xe4, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, + 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe1, 0xe1, 0xe1, 0xe1, + 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, + 0xe2, 0xe2, 0xe2, 0xe2, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, + 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xef, 0xef, 0xef, 0xef, + 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, + 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, + 0xe8, 0xe8, 0xe8, 0xe8, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, + 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa + }; + return alaw_encode[(F32_TO_S16(val)>>3) + 0x1000]; +} + +static inline uint8_t f32_to_ulaw(float val) +{ + static uint8_t ulaw_encode[0x4000] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, + 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, + 0x43, 0x43, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, + 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, + 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, + 0x46, 0x46, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, + 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, + 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, + 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c, + 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, + 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, + 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, + 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, + 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, + 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, + 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, + 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, + 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, + 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, + 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x60, + 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63, + 0x63, 0x63, 0x64, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x65, 0x66, 0x66, + 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, + 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, 0x6c, 0x6c, + 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, + 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x74, 0x74, + 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, + 0x7b, 0x7b, 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0xff, 0xfe, 0xfe, 0xfd, + 0xfd, 0xfc, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9, 0xf9, 0xf8, 0xf8, 0xf7, + 0xf7, 0xf6, 0xf6, 0xf5, 0xf5, 0xf4, 0xf4, 0xf3, 0xf3, 0xf2, 0xf2, 0xf1, + 0xf1, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xed, + 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb, 0xeb, 0xea, + 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, + 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, + 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe1, + 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, + 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, + 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xda, + 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, + 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd7, + 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, + 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4, + 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, + 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1, + 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, + 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, + 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xce, 0xce, + 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, + 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, + 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, + 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, + 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, + 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc7, + 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, + 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, + 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc4, + 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, + 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, + 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80 }; + return ulaw_encode[(F32_TO_S16(val)>>2) + 0x2000]; +} diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build new file mode 100644 index 0000000..f08527a --- /dev/null +++ b/spa/plugins/audioconvert/meson.build @@ -0,0 +1,206 @@ +audioconvert_sources = [ + 'audioadapter.c', + 'audioconvert.c', + 'plugin.c' +] + +simd_cargs = [] +simd_dependencies = [] + +audioconvert_c = static_library('audioconvert_c', + [ 'channelmix-ops-c.c', + 'biquad.c', + 'crossover.c', + 'volume-ops-c.c', + 'peaks-ops-c.c', + 'resample-native-c.c', + 'fmt-ops-c.c' ], + c_args : ['-Ofast', '-ffast-math'], + dependencies : [ spa_dep ], + install : false + ) +simd_dependencies += audioconvert_c + +if have_sse + audioconvert_sse = static_library('audioconvert_sse', + ['resample-native-sse.c', + 'volume-ops-sse.c', + 'peaks-ops-sse.c', + 'channelmix-ops-sse.c' ], + c_args : [sse_args, '-Ofast', '-DHAVE_SSE'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_SSE'] + simd_dependencies += audioconvert_sse +endif +if have_sse2 + audioconvert_sse2 = static_library('audioconvert_sse2', + ['fmt-ops-sse2.c' ], + c_args : [sse2_args, '-O3', '-DHAVE_SSE2'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_SSE2'] + simd_dependencies += audioconvert_sse2 +endif +if have_ssse3 + audioconvert_ssse3 = static_library('audioconvert_ssse3', + ['fmt-ops-ssse3.c', + 'resample-native-ssse3.c' ], + c_args : [ssse3_args, '-O3', '-DHAVE_SSSE3'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_SSSE3'] + simd_dependencies += audioconvert_ssse3 +endif +if have_sse41 + audioconvert_sse41 = static_library('audioconvert_sse41', + ['fmt-ops-sse41.c'], + c_args : [sse41_args, '-O3', '-DHAVE_SSE41'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_SSE41'] + simd_dependencies += audioconvert_sse41 +endif +if have_avx and have_fma + audioconvert_avx = static_library('audioconvert_avx', + ['resample-native-avx.c'], + c_args : [avx_args, fma_args, '-O3', '-DHAVE_AVX', '-DHAVE_FMA'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_AVX', '-DHAVE_FMA'] + simd_dependencies += audioconvert_avx +endif +if have_avx2 + audioconvert_avx2 = static_library('audioconvert_avx2', + ['fmt-ops-avx2.c'], + c_args : [avx2_args, '-O3', '-DHAVE_AVX2'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_AVX2'] + simd_dependencies += audioconvert_avx2 +endif + +if have_neon + audioconvert_neon = static_library('audioconvert_neon', + ['resample-native-neon.c', + 'fmt-ops-neon.c' ], + c_args : [neon_args, '-O3', '-DHAVE_NEON'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_NEON'] + simd_dependencies += audioconvert_neon +endif + +audioconvert_lib = static_library('audioconvert', + ['fmt-ops.c', + 'channelmix-ops.c', + 'peaks-ops.c', + 'resample-native.c', + 'resample-peaks.c', + 'volume-ops.c' ], + c_args : [ simd_cargs, '-O3'], + link_with : simd_dependencies, + include_directories : [configinc], + dependencies : [ spa_dep ], + install : false + ) +audioconvert_dep = declare_dependency(link_with: audioconvert_lib) + +spa_audioconvert_lib = shared_library('spa-audioconvert', + audioconvert_sources, + c_args : simd_cargs, + dependencies : [ spa_dep, mathlib, audioconvert_dep ], + install : true, + install_dir : spa_plugindir / 'audioconvert') +spa_audioconvert_dep = declare_dependency(link_with: spa_audioconvert_lib) + +test_lib = static_library('test_lib', + ['test-source.c' ], + c_args : ['-O3'], + dependencies : [ spa_dep ], + install : false + ) + +test_apps = [ + 'test-audioadapter', + 'test-audioconvert', + 'test-channelmix', + 'test-fmt-ops', + 'test-peaks', + 'test-resample', + ] + +foreach a : test_apps + test(a, + executable(a, a + '.c', + dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, audioconvert_dep, spa_audioconvert_dep ], + include_directories : [ configinc ], + link_with : [ test_lib ], + install_rpath : spa_plugindir / 'audioconvert', + c_args : [ simd_cargs ], + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'audioconvert'), + env : [ + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ]) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'audioconvert' / a) + configure_file( + input: installed_tests_template, + output: a + '.test', + install_dir: installed_tests_metadir / 'audioconvert', + configuration: test_conf + ) + endif +endforeach + +benchmark_apps = [ + 'benchmark-fmt-ops', + 'benchmark-resample', + ] + +foreach a : benchmark_apps + benchmark(a, + executable(a, a + '.c', + dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, audioconvert_dep, spa_audioconvert_dep ], + include_directories : [ configinc ], + c_args : [ simd_cargs ], + install_rpath : spa_plugindir / 'audioconvert', + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'audioconvert'), + env : [ + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ]) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'audioconvert' / a) + configure_file( + input: installed_tests_template, + output: a + '.test', + install_dir: installed_tests_metadir / 'audioconvert', + configuration: test_conf + ) + endif +endforeach + +if sndfile_dep.found() + sparesample_sources = [ + 'spa-resample.c', + ] + executable('spa-resample', + sparesample_sources, + link_with : [ test_lib ], + dependencies : [ spa_dep, sndfile_dep, mathlib, audioconvert_dep ], + install : true, + ) +endif diff --git a/spa/plugins/audioconvert/peaks-ops-c.c b/spa/plugins/audioconvert/peaks-ops-c.c new file mode 100644 index 0000000..45ab1dc --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops-c.c @@ -0,0 +1,50 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <math.h> + +#include "peaks-ops.h" + +void peaks_min_max_c(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max) +{ + uint32_t n; + float t, mi = *min, ma = *max; + for (n = 0; n < n_samples; n++) { + t = src[n]; + mi = fminf(mi, t); + ma = fmaxf(ma, t); + } + *min = mi; + *max = ma; +} + +float peaks_abs_max_c(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max) +{ + uint32_t n; + for (n = 0; n < n_samples; n++) + max = fmaxf(fabsf(src[n]), max); + return max; +} diff --git a/spa/plugins/audioconvert/peaks-ops-sse.c b/spa/plugins/audioconvert/peaks-ops-sse.c new file mode 100644 index 0000000..7ceb2a8 --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops-sse.c @@ -0,0 +1,122 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <math.h> + +#include <xmmintrin.h> + +#include "peaks-ops.h" + +static inline float hmin_ps(__m128 val) +{ + __m128 t = _mm_movehl_ps(val, val); + t = _mm_min_ps(t, val); + val = _mm_shuffle_ps(t, t, 0x55); + val = _mm_min_ss(t, val); + return _mm_cvtss_f32(val); +} + +static inline float hmax_ps(__m128 val) +{ + __m128 t = _mm_movehl_ps(val, val); + t = _mm_max_ps(t, val); + val = _mm_shuffle_ps(t, t, 0x55); + val = _mm_max_ss(t, val); + return _mm_cvtss_f32(val); +} + +void peaks_min_max_sse(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max) +{ + uint32_t n; + __m128 in; + __m128 mi = _mm_set1_ps(*min); + __m128 ma = _mm_set1_ps(*max); + + for (n = 0; n < n_samples; n++) { + if (SPA_IS_ALIGNED(&src[n], 16)) + break; + in = _mm_set1_ps(src[n]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + for (; n + 15 < n_samples; n += 16) { + in = _mm_load_ps(&src[n + 0]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 4]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 8]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 12]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + for (; n < n_samples; n++) { + in = _mm_set1_ps(src[n]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + *min = hmin_ps(mi); + *max = hmax_ps(ma); +} + +float peaks_abs_max_sse(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max) +{ + uint32_t n; + __m128 in; + __m128 ma = _mm_set1_ps(max); + const __m128 mask = _mm_set1_ps(-0.0f); + + for (n = 0; n < n_samples; n++) { + if (SPA_IS_ALIGNED(&src[n], 16)) + break; + in = _mm_set1_ps(src[n]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + for (; n + 15 < n_samples; n += 16) { + in = _mm_load_ps(&src[n + 0]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 4]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 8]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 12]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + for (; n < n_samples; n++) { + in = _mm_set1_ps(src[n]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + return hmax_ps(ma); +} diff --git a/spa/plugins/audioconvert/peaks-ops.c b/spa/plugins/audioconvert/peaks-ops.c new file mode 100644 index 0000000..b5cb252 --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops.c @@ -0,0 +1,89 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <errno.h> + +#include <spa/support/cpu.h> +#include <spa/support/log.h> +#include <spa/utils/defs.h> + +#include "peaks-ops.h" + +typedef void (*peaks_min_max_func_t) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max); +typedef float (*peaks_abs_max_func_t) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max); + +#define MAKE(min_max,abs_max,...) \ + { min_max, abs_max, #min_max , __VA_ARGS__ } + +static const struct peaks_info { + peaks_min_max_func_t min_max; + peaks_abs_max_func_t abs_max; + const char *name; + uint32_t cpu_flags; +} peaks_table[] = +{ +#if defined (HAVE_SSE) + MAKE(peaks_min_max_sse, peaks_abs_max_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(peaks_min_max_c, peaks_abs_max_c), +}; +#undef MAKE + +#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) + +static const struct peaks_info *find_peaks_info(uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(peaks_table, t) { + if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; + } + return NULL; +} + +static void impl_peaks_free(struct peaks *peaks) +{ + peaks->min_max = NULL; + peaks->abs_max = NULL; +} + +int peaks_init(struct peaks *peaks) +{ + const struct peaks_info *info; + + info = find_peaks_info(peaks->cpu_flags); + if (info == NULL) + return -ENOTSUP; + + peaks->cpu_flags = info->cpu_flags; + peaks->func_name = info->name; + peaks->free = impl_peaks_free; + peaks->min_max = info->min_max; + peaks->abs_max = info->abs_max; + return 0; +} diff --git a/spa/plugins/audioconvert/peaks-ops.h b/spa/plugins/audioconvert/peaks-ops.h new file mode 100644 index 0000000..29da794 --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops.h @@ -0,0 +1,72 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> + +#include <spa/utils/defs.h> + +struct peaks { + uint32_t cpu_flags; + const char *func_name; + + struct spa_log *log; + + uint32_t flags; + + void (*min_max) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max); + float (*abs_max) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max); + + void (*free) (struct peaks *peaks); +}; + +int peaks_init(struct peaks *peaks); + +#define peaks_min_max(peaks,...) (peaks)->min_max(peaks, __VA_ARGS__) +#define peaks_abs_max(peaks,...) (peaks)->abs_max(peaks, __VA_ARGS__) +#define peaks_free(peaks) (peaks)->free(peaks) + +#define DEFINE_MIN_MAX_FUNCTION(arch) \ +void peaks_min_max_##arch(struct peaks *peaks, \ + const float * SPA_RESTRICT src, \ + uint32_t n_samples, float *min, float *max); + +#define DEFINE_ABS_MAX_FUNCTION(arch) \ +float peaks_abs_max_##arch(struct peaks *peaks, \ + const float * SPA_RESTRICT src, \ + uint32_t n_samples, float max); + +#define PEAKS_OPS_MAX_ALIGN 16 + +DEFINE_MIN_MAX_FUNCTION(c); +DEFINE_ABS_MAX_FUNCTION(c); + +#if defined (HAVE_SSE) +DEFINE_MIN_MAX_FUNCTION(sse); +DEFINE_ABS_MAX_FUNCTION(sse); +#endif + +#undef DEFINE_FUNCTION diff --git a/spa/plugins/audioconvert/plugin.c b/spa/plugins/audioconvert/plugin.c new file mode 100644 index 0000000..03c206f --- /dev/null +++ b/spa/plugins/audioconvert/plugin.c @@ -0,0 +1,50 @@ +/* Spa Audioconvert plugin + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> + +#include <spa/support/plugin.h> + +extern const struct spa_handle_factory spa_audioconvert_factory; +extern const struct spa_handle_factory spa_audioadapter_factory; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &spa_audioconvert_factory; + break; + case 1: + *factory = &spa_audioadapter_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/audioconvert/resample-native-avx.c b/spa/plugins/audioconvert/resample-native-avx.c new file mode 100644 index 0000000..136d6cb --- /dev/null +++ b/spa/plugins/audioconvert/resample-native-avx.c @@ -0,0 +1,94 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "resample-native-impl.h" + +#include <assert.h> +#include <immintrin.h> + +static inline void inner_product_avx(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT taps, uint32_t n_taps) +{ + __m256 sy[2] = { _mm256_setzero_ps(), _mm256_setzero_ps() }, ty; + __m128 sx[2], tx; + uint32_t i = 0; + uint32_t n_taps4 = n_taps & ~0xf; + + for (; i < n_taps4; i += 16) { + ty = (__m256)_mm256_lddqu_si256((__m256i*)(s + i + 0)); + sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(taps + i + 0), sy[0]); + ty = (__m256)_mm256_lddqu_si256((__m256i*)(s + i + 8)); + sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(taps + i + 8), sy[1]); + } + sy[0] = _mm256_add_ps(sy[1], sy[0]); + sx[1] = _mm256_extractf128_ps(sy[0], 1); + sx[0] = _mm256_extractf128_ps(sy[0], 0); + for (; i < n_taps; i += 8) { + tx = (__m128)_mm_lddqu_si128((__m128i*)(s + i + 0)); + sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(taps + i + 0), sx[0]); + tx = (__m128)_mm_lddqu_si128((__m128i*)(s + i + 4)); + sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(taps + i + 4), sx[1]); + } + sx[0] = _mm_add_ps(sx[0], sx[1]); + sx[0] = _mm_hadd_ps(sx[0], sx[0]); + sx[0] = _mm_hadd_ps(sx[0], sx[0]); + _mm_store_ss(d, sx[0]); +} + +static inline void inner_product_ip_avx(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, + uint32_t n_taps) +{ + __m256 sy[2] = { _mm256_setzero_ps(), _mm256_setzero_ps() }, ty; + __m128 sx[2], tx; + uint32_t i, n_taps4 = n_taps & ~0xf; + + for (i = 0; i < n_taps4; i += 16) { + ty = (__m256)_mm256_lddqu_si256((__m256i*)(s + i + 0)); + sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(t0 + i + 0), sy[0]); + sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(t1 + i + 0), sy[1]); + ty = (__m256)_mm256_lddqu_si256((__m256i*)(s + i + 8)); + sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(t0 + i + 8), sy[0]); + sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(t1 + i + 8), sy[1]); + } + sx[0] = _mm_add_ps(_mm256_extractf128_ps(sy[0], 0), _mm256_extractf128_ps(sy[0], 1)); + sx[1] = _mm_add_ps(_mm256_extractf128_ps(sy[1], 0), _mm256_extractf128_ps(sy[1], 1)); + + for (; i < n_taps; i += 8) { + tx = (__m128)_mm_lddqu_si128((__m128i*)(s + i + 0)); + sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(t0 + i + 0), sx[0]); + sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(t1 + i + 0), sx[1]); + tx = (__m128)_mm_lddqu_si128((__m128i*)(s + i + 4)); + sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(t0 + i + 4), sx[0]); + sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(t1 + i + 4), sx[1]); + } + sx[1] = _mm_mul_ps(_mm_sub_ps(sx[1], sx[0]), _mm_load1_ps(&x)); + sx[0] = _mm_add_ps(sx[0], sx[1]); + sx[0] = _mm_hadd_ps(sx[0], sx[0]); + sx[0] = _mm_hadd_ps(sx[0], sx[0]); + _mm_store_ss(d, sx[0]); +} + +MAKE_RESAMPLER_FULL(avx); +MAKE_RESAMPLER_INTER(avx); diff --git a/spa/plugins/audioconvert/resample-native-c.c b/spa/plugins/audioconvert/resample-native-c.c new file mode 100644 index 0000000..ce6c57d --- /dev/null +++ b/spa/plugins/audioconvert/resample-native-c.c @@ -0,0 +1,65 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "resample-native-impl.h" + +static inline void inner_product_c(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT taps, uint32_t n_taps) +{ + float sum = 0.0f; +#if 1 + uint32_t i, j, nt2 = n_taps/2; + for (i = 0, j = n_taps-1; i < nt2; i++, j--) + sum += s[i] * taps[i] + s[j] * taps[j]; +#else + uint32_t i; + for (i = 0; i < n_taps; i++) + sum += s[i] * taps[i]; +#endif + *d = sum; +} + +static inline void inner_product_ip_c(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, + uint32_t n_taps) +{ + float sum[2] = { 0.0f, 0.0f }; + uint32_t i; +#if 1 + uint32_t j, nt2 = n_taps/2; + for (i = 0, j = n_taps-1; i < nt2; i++, j--) { + sum[0] += s[i] * t0[i] + s[j] * t0[j]; + sum[1] += s[i] * t1[i] + s[j] * t1[j]; + } +#else + for (i = 0; i < n_taps; i++) { + sum[0] += s[i] * t0[i]; + sum[1] += s[i] * t1[i]; + } +#endif + *d = (sum[1] - sum[0]) * x + sum[0]; +} + +MAKE_RESAMPLER_FULL(c); +MAKE_RESAMPLER_INTER(c); diff --git a/spa/plugins/audioconvert/resample-native-impl.h b/spa/plugins/audioconvert/resample-native-impl.h new file mode 100644 index 0000000..5dfc40e --- /dev/null +++ b/spa/plugins/audioconvert/resample-native-impl.h @@ -0,0 +1,191 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <math.h> + +#include <spa/utils/defs.h> + +#include "resample.h" + +typedef void (*resample_func_t)(struct resample *r, + const void * SPA_RESTRICT src[], uint32_t ioffs, uint32_t *in_len, + void * SPA_RESTRICT dst[], uint32_t ooffs, uint32_t *out_len); + +struct resample_info { + uint32_t format; + resample_func_t process_copy; + const char *copy_name; + resample_func_t process_full; + const char *full_name; + resample_func_t process_inter; + const char *inter_name; + uint32_t cpu_flags; +}; + +struct native_data { + double rate; + uint32_t n_taps; + uint32_t n_phases; + uint32_t in_rate; + uint32_t out_rate; + uint32_t phase; + uint32_t inc; + uint32_t frac; + uint32_t filter_stride; + uint32_t filter_stride_os; + uint32_t hist; + float **history; + resample_func_t func; + float *filter; + float *hist_mem; + const struct resample_info *info; +}; + +#define DEFINE_RESAMPLER(type,arch) \ +void do_resample_##type##_##arch(struct resample *r, \ + const void * SPA_RESTRICT src[], uint32_t ioffs, uint32_t *in_len, \ + void * SPA_RESTRICT dst[], uint32_t ooffs, uint32_t *out_len) + +#define MAKE_RESAMPLER_COPY(arch) \ +DEFINE_RESAMPLER(copy,arch) \ +{ \ + struct native_data *data = r->data; \ + uint32_t index, n_taps = data->n_taps, n_taps2 = n_taps/2; \ + uint32_t c, olen = *out_len, ilen = *in_len; \ + \ + if (r->channels == 0) \ + return; \ + \ + index = ioffs; \ + if (ooffs < olen && index + n_taps <= ilen) { \ + uint32_t to_copy = SPA_MIN(olen - ooffs, \ + ilen - (index + n_taps) + 1); \ + for (c = 0; c < r->channels; c++) { \ + const float *s = src[c]; \ + float *d = dst[c]; \ + spa_memcpy(&d[ooffs], &s[index + n_taps2], \ + to_copy * sizeof(float)); \ + } \ + index += to_copy; \ + ooffs += to_copy; \ + } \ + *in_len = index; \ + *out_len = ooffs; \ +} + +#define INC(index,phase,n_phases) \ + index += inc; \ + phase += frac; \ + if (phase >= n_phases) { \ + phase -= n_phases; \ + index += 1; \ + } + +#define MAKE_RESAMPLER_FULL(arch) \ +DEFINE_RESAMPLER(full,arch) \ +{ \ + struct native_data *data = r->data; \ + uint32_t n_taps = data->n_taps, stride = data->filter_stride_os; \ + uint32_t index, phase, n_phases = data->out_rate; \ + uint32_t c, o, olen = *out_len, ilen = *in_len; \ + uint32_t inc = data->inc, frac = data->frac; \ + \ + if (r->channels == 0) \ + return; \ + \ + for (c = 0; c < r->channels; c++) { \ + const float *s = src[c]; \ + float *d = dst[c]; \ + \ + index = ioffs; \ + phase = data->phase; \ + \ + for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ + inner_product_##arch(&d[o], &s[index], \ + &data->filter[phase * stride], \ + n_taps); \ + INC(index, phase, n_phases); \ + } \ + } \ + *in_len = index; \ + *out_len = o; \ + data->phase = phase; \ +} + +#define MAKE_RESAMPLER_INTER(arch) \ +DEFINE_RESAMPLER(inter,arch) \ +{ \ + struct native_data *data = r->data; \ + uint32_t index, phase, stride = data->filter_stride; \ + uint32_t n_phases = data->n_phases, out_rate = data->out_rate; \ + uint32_t n_taps = data->n_taps; \ + uint32_t c, o, olen = *out_len, ilen = *in_len; \ + uint32_t inc = data->inc, frac = data->frac; \ + \ + if (r->channels == 0) \ + return; \ + \ + for (c = 0; c < r->channels; c++) { \ + const float *s = src[c]; \ + float *d = dst[c]; \ + \ + index = ioffs; \ + phase = data->phase; \ + \ + for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ + float ph = (float)phase * n_phases / out_rate; \ + uint32_t offset = floorf(ph); \ + inner_product_ip_##arch(&d[o], &s[index], \ + &data->filter[(offset + 0) * stride], \ + &data->filter[(offset + 1) * stride], \ + ph - offset, n_taps); \ + INC(index, phase, out_rate); \ + } \ + } \ + *in_len = index; \ + *out_len = o; \ + data->phase = phase; \ +} + + +DEFINE_RESAMPLER(copy,c); +DEFINE_RESAMPLER(full,c); +DEFINE_RESAMPLER(inter,c); + +#if defined (HAVE_NEON) +DEFINE_RESAMPLER(full,neon); +DEFINE_RESAMPLER(inter,neon); +#endif +#if defined (HAVE_SSE) +DEFINE_RESAMPLER(full,sse); +DEFINE_RESAMPLER(inter,sse); +#endif +#if defined (HAVE_SSSE3) +DEFINE_RESAMPLER(full,ssse3); +DEFINE_RESAMPLER(inter,ssse3); +#endif +#if defined (HAVE_AVX) && defined(HAVE_FMA) +DEFINE_RESAMPLER(full,avx); +DEFINE_RESAMPLER(inter,avx); +#endif diff --git a/spa/plugins/audioconvert/resample-native-neon.c b/spa/plugins/audioconvert/resample-native-neon.c new file mode 100644 index 0000000..079152a --- /dev/null +++ b/spa/plugins/audioconvert/resample-native-neon.c @@ -0,0 +1,218 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "resample-native-impl.h" + +#include <arm_neon.h> + +static inline void inner_product_neon(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT taps, uint32_t n_taps) +{ + unsigned int remainder = n_taps % 16; + n_taps = n_taps - remainder; + +#ifdef __aarch64__ + asm volatile( + " cmp %[n_taps], #0\n" + " bne 1f\n" + " ld1 {v4.4s}, [%[taps]], #16\n" + " ld1 {v8.4s}, [%[s]], #16\n" + " subs %[remainder], %[remainder], #4\n" + " fmul v0.4s, v4.4s, v8.4s\n" + " bne 4f\n" + " b 5f\n" + "1:" + " ld1 {v4.4s, v5.4s, v6.4s, v7.4s}, [%[taps]], #64\n" + " ld1 {v8.4s, v9.4s, v10.4s, v11.4s}, [%[s]], #64\n" + " subs %[n_taps], %[n_taps], #16\n" + " fmul v0.4s, v4.4s, v8.4s\n" + " fmul v1.4s, v5.4s, v9.4s\n" + " fmul v2.4s, v6.4s, v10.4s\n" + " fmul v3.4s, v7.4s, v11.4s\n" + " beq 3f\n" + "2:" + " ld1 {v4.4s, v5.4s, v6.4s, v7.4s}, [%[taps]], #64\n" + " ld1 {v8.4s, v9.4s, v10.4s, v11.4s}, [%[s]], #64\n" + " subs %[n_taps], %[n_taps], #16\n" + " fmla v0.4s, v4.4s, v8.4s\n" + " fmla v1.4s, v5.4s, v9.4s\n" + " fmla v2.4s, v6.4s, v10.4s\n" + " fmla v3.4s, v7.4s, v11.4s\n" + " bne 2b\n" + "3:" + " fadd v4.4s, v0.4s, v1.4s\n" + " fadd v5.4s, v2.4s, v3.4s\n" + " cmp %[remainder], #0\n" + " fadd v0.4s, v4.4s, v5.4s\n" + " beq 5f\n" + "4:" + " ld1 {v6.4s}, [%[taps]], #16\n" + " ld1 {v10.4s}, [%[s]], #16\n" + " subs %[remainder], %[remainder], #4\n" + " fmla v0.4s, v6.4s, v10.4s\n" + " bne 4b\n" + "5:" + " faddp v0.4s, v0.4s, v0.4s\n" + " faddp v0.2s, v0.2s, v0.2s\n" + " str s0, [%[d]]\n" + : [d] "+r" (d), [s] "+r" (s), [taps] "+r" (taps), + [n_taps] "+r" (n_taps), [remainder] "+r" (remainder) + : + : "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", + "v9", "v10", "v11"); +#else + asm volatile ( + " cmp %[n_taps], #0\n" + " bne 1f\n" + " vld1.32 {q4}, [%[taps] :128]!\n" + " vld1.32 {q8}, [%[s]]!\n" + " subs %[remainder], %[remainder], #4\n" + " vmul.f32 q0, q4, q8\n" + " bne 4f\n" + " b 5f\n" + "1:" + " vld1.32 {q4, q5}, [%[taps] :128]!\n" + " vld1.32 {q8, q9}, [%[s]]!\n" + " vld1.32 {q6, q7}, [%[taps] :128]!\n" + " vld1.32 {q10, q11}, [%[s]]!\n" + " subs %[n_taps], %[n_taps], #16\n" + " vmul.f32 q0, q4, q8\n" + " vmul.f32 q1, q5, q9\n" + " vmul.f32 q2, q6, q10\n" + " vmul.f32 q3, q7, q11\n" + " beq 3f\n" + "2:" + " vld1.32 {q4, q5}, [%[taps] :128]!\n" + " vld1.32 {q8, q9}, [%[s]]!\n" + " vld1.32 {q6, q7}, [%[taps] :128]!\n" + " vld1.32 {q10, q11}, [%[s]]!\n" + " subs %[n_taps], %[n_taps], #16\n" + " vmla.f32 q0, q4, q8\n" + " vmla.f32 q1, q5, q9\n" + " vmla.f32 q2, q6, q10\n" + " vmla.f32 q3, q7, q11\n" + " bne 2b\n" + "3:" + " vadd.f32 q4, q0, q1\n" + " vadd.f32 q5, q2, q3\n" + " cmp %[remainder], #0\n" + " vadd.f32 q0, q4, q5\n" + " beq 5f\n" + "4:" + " vld1.32 {q6}, [%[taps] :128]!\n" + " vld1.32 {q10}, [%[s]]!\n" + " subs %[remainder], %[remainder], #4\n" + " vmla.f32 q0, q6, q10\n" + " bne 4b\n" + "5:" + " vadd.f32 d0, d0, d1\n" + " vpadd.f32 d0, d0, d0\n" + " vstr d0, [%[d]]\n" + : [d] "+r" (d), [s] "+r" (s), [taps] "+r" (taps), + [n_taps] "+l" (n_taps), [remainder] "+l" (remainder) + : + : "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", + "q9", "q10", "q11"); +#endif +} + +static inline void inner_product_ip_neon(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, + uint32_t n_taps) +{ +#ifdef __aarch64__ + asm volatile( + " dup v10.4s, %w[x]\n" + " ld1 {v4.4s, v5.4s}, [%[t0]], #32\n" + " ld1 {v8.4s, v9.4s}, [%[s]], #32\n" + " ld1 {v6.4s, v7.4s}, [%[t1]], #32\n" + " subs %[n_taps], %[n_taps], #8\n" + " fmul v0.4s, v4.4s, v8.4s\n" + " fmul v1.4s, v5.4s, v9.4s\n" + " fmul v2.4s, v6.4s, v8.4s\n" + " fmul v3.4s, v7.4s, v9.4s\n" + " beq 3f\n" + "2:" + " ld1 {v4.4s, v5.4s}, [%[t0]], #32\n" + " ld1 {v8.4s, v9.4s}, [%[s]], #32\n" + " ld1 {v6.4s, v7.4s}, [%[t1]], #32\n" + " subs %[n_taps], %[n_taps], #8\n" + " fmla v0.4s, v4.4s, v8.4s\n" + " fmla v1.4s, v5.4s, v9.4s\n" + " fmla v2.4s, v6.4s, v8.4s\n" + " fmla v3.4s, v7.4s, v9.4s\n" + " bne 2b\n" + "3:" + " fadd v0.4s, v0.4s, v1.4s\n" /* sum[0] */ + " fadd v2.4s, v2.4s, v3.4s\n" /* sum[1] */ + " fsub v2.4s, v2.4s, v0.4s\n" /* sum[1] -= sum[0] */ + " fmla v0.4s, v2.4s, v10.4s\n" /* sum[0] += sum[1] * x */ + " faddp v0.4s, v0.4s, v0.4s\n" + " faddp v0.2s, v0.2s, v0.2s\n" + " str s0, [%[d]]\n" + : [d] "+r" (d), [s] "+r" (s), [t0] "+r" (t0), [t1] "+r" (t1), + [n_taps] "+r" (n_taps), [x] "+r" (x) + : + : "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", + "v9", "v10"); +#else + asm volatile( + " vdup.32 q10, %[x]\n" + " vld1.32 {q4, q5}, [%[t0] :128]!\n" + " vld1.32 {q8, q9}, [%[s]]!\n" + " vld1.32 {q6, q7}, [%[t1] :128]!\n" + " subs %[n_taps], %[n_taps], #8\n" + " vmul.f32 q0, q4, q8\n" + " vmul.f32 q1, q5, q9\n" + " vmul.f32 q2, q6, q8\n" + " vmul.f32 q3, q7, q9\n" + " beq 3f\n" + "2:" + " vld1.32 {q4, q5}, [%[t0] :128]!\n" + " vld1.32 {q8, q9}, [%[s]]!\n" + " vld1.32 {q6, q7}, [%[t1] :128]!\n" + " subs %[n_taps], %[n_taps], #8\n" + " vmla.f32 q0, q4, q8\n" + " vmla.f32 q1, q5, q9\n" + " vmla.f32 q2, q6, q8\n" + " vmla.f32 q3, q7, q9\n" + " bne 2b\n" + "3:" + " vadd.f32 q0, q0, q1\n" /* sum[0] */ + " vadd.f32 q2, q2, q3\n" /* sum[1] */ + " vsub.f32 q2, q2, q0\n" /* sum[1] -= sum[0] */ + " vmla.f32 q0, q2, q10\n" /* sum[0] += sum[1] * x */ + " vadd.f32 d0, d0, d1\n" + " vpadd.f32 d0, d0, d0\n" + " vstr d0, [%[d]]\n" + : [d] "+r" (d), [s] "+r" (s), [t0] "+r" (t0), [t1] "+r" (t1), + [n_taps] "+l" (n_taps), [x] "+l" (x) + : + : "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", + "q9", "q10"); +#endif +} + +MAKE_RESAMPLER_FULL(neon); +MAKE_RESAMPLER_INTER(neon); diff --git a/spa/plugins/audioconvert/resample-native-sse.c b/spa/plugins/audioconvert/resample-native-sse.c new file mode 100644 index 0000000..fcdb32c --- /dev/null +++ b/spa/plugins/audioconvert/resample-native-sse.c @@ -0,0 +1,94 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "resample-native-impl.h" + +#include <xmmintrin.h> + +static inline void inner_product_sse(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT taps, uint32_t n_taps) +{ + __m128 sum = _mm_setzero_ps(); + uint32_t i = 0; +#if 0 + uint32_t unrolled = n_taps & ~15; + + for (i = 0; i < unrolled; i += 16) { + sum = _mm_add_ps(sum, + _mm_mul_ps( + _mm_loadu_ps(s + i + 0), + _mm_load_ps(taps + i + 0))); + sum = _mm_add_ps(sum, + _mm_mul_ps( + _mm_loadu_ps(s + i + 4), + _mm_load_ps(taps + i + 4))); + sum = _mm_add_ps(sum, + _mm_mul_ps( + _mm_loadu_ps(s + i + 8), + _mm_load_ps(taps + i + 8))); + sum = _mm_add_ps(sum, + _mm_mul_ps( + _mm_loadu_ps(s + i + 12), + _mm_load_ps(taps + i + 12))); + } +#endif + for (; i < n_taps; i += 8) { + sum = _mm_add_ps(sum, + _mm_mul_ps( + _mm_loadu_ps(s + i + 0), + _mm_load_ps(taps + i + 0))); + sum = _mm_add_ps(sum, + _mm_mul_ps( + _mm_loadu_ps(s + i + 4), + _mm_load_ps(taps + i + 4))); + } + sum = _mm_add_ps(sum, _mm_movehl_ps(sum, sum)); + sum = _mm_add_ss(sum, _mm_shuffle_ps(sum, sum, 0x55)); + _mm_store_ss(d, sum); +} + +static inline void inner_product_ip_sse(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, + uint32_t n_taps) +{ + __m128 sum[2] = { _mm_setzero_ps (), _mm_setzero_ps () }, t; + uint32_t i; + + for (i = 0; i < n_taps; i += 8) { + t = _mm_loadu_ps(s + i + 0); + sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(t, _mm_load_ps(t0 + i + 0))); + sum[1] = _mm_add_ps(sum[1], _mm_mul_ps(t, _mm_load_ps(t1 + i + 0))); + t = _mm_loadu_ps(s + i + 4); + sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(t, _mm_load_ps(t0 + i + 4))); + sum[1] = _mm_add_ps(sum[1], _mm_mul_ps(t, _mm_load_ps(t1 + i + 4))); + } + sum[1] = _mm_mul_ps(_mm_sub_ps(sum[1], sum[0]), _mm_load1_ps(&x)); + sum[0] = _mm_add_ps(sum[0], sum[1]); + sum[0] = _mm_add_ps(sum[0], _mm_movehl_ps(sum[0], sum[0])); + sum[0] = _mm_add_ss(sum[0], _mm_shuffle_ps(sum[0], sum[0], 0x55)); + _mm_store_ss(d, sum[0]); +} + +MAKE_RESAMPLER_FULL(sse); +MAKE_RESAMPLER_INTER(sse); diff --git a/spa/plugins/audioconvert/resample-native-ssse3.c b/spa/plugins/audioconvert/resample-native-ssse3.c new file mode 100644 index 0000000..ac3675f --- /dev/null +++ b/spa/plugins/audioconvert/resample-native-ssse3.c @@ -0,0 +1,115 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "resample-native-impl.h" + +#include <tmmintrin.h> + +static inline void inner_product_ssse3(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT taps, uint32_t n_taps) +{ + __m128 sum = _mm_setzero_ps(); + __m128 t0, t1; + uint32_t i; + + switch (SPA_PTR_ALIGNMENT(s, 16)) { + case 0: + for (i = 0; i < n_taps; i += 8) { + sum = _mm_add_ps(sum, + _mm_mul_ps( + _mm_load_ps(s + i + 0), + _mm_load_ps(taps + i + 0))); + sum = _mm_add_ps(sum, + _mm_mul_ps( + _mm_load_ps(s + i + 4), + _mm_load_ps(taps + i + 4))); + } + break; + case 4: + t0 = _mm_load_ps(s - 1); + for (i = 0; i < n_taps; i += 8) { + t1 = _mm_load_ps(s + i + 3); + t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 4); + sum = _mm_add_ps(sum, + _mm_mul_ps(t0, _mm_load_ps(taps + i + 0))); + t0 = t1; + t1 = _mm_load_ps(s + i + 7); + t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 4); + sum = _mm_add_ps(sum, + _mm_mul_ps(t0, _mm_load_ps(taps + i + 4))); + t0 = t1; + } + break; + case 8: + t0 = _mm_load_ps(s - 2); + for (i = 0; i < n_taps; i += 8) { + t1 = _mm_load_ps(s + i + 2); + t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 8); + sum = _mm_add_ps(sum, + _mm_mul_ps(t0, _mm_load_ps(taps + i + 0))); + t0 = t1; + t1 = _mm_load_ps(s + i + 6); + t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 8); + sum = _mm_add_ps(sum, + _mm_mul_ps(t0, _mm_load_ps(taps + i + 4))); + t0 = t1; + } + break; + case 12: + t0 = _mm_load_ps(s - 3); + for (i = 0; i < n_taps; i += 8) { + t1 = _mm_load_ps(s + i + 1); + t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 12); + sum = _mm_add_ps(sum, + _mm_mul_ps(t0, _mm_load_ps(taps + i + 0))); + t0 = t1; + t1 = _mm_load_ps(s + i + 5); + t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 12); + sum = _mm_add_ps(sum, + _mm_mul_ps(t0, _mm_load_ps(taps + i + 4))); + t0 = t1; + } + break; + } + sum = _mm_add_ps(sum, _mm_movehdup_ps(sum)); + sum = _mm_add_ss(sum, _mm_movehl_ps(sum, sum)); + _mm_store_ss(d, sum); +} + +static inline void inner_product_ip_ssse3(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, + uint32_t n_taps) +{ + float sum[2] = { 0.0f, 0.0f }; + uint32_t i; + + for (i = 0; i < n_taps; i++) { + sum[0] += s[i] * t0[i]; + sum[1] += s[i] * t1[i]; + } + *d = (sum[1] - sum[0]) * x + sum[0]; +} + +MAKE_RESAMPLER_FULL(ssse3); +MAKE_RESAMPLER_INTER(ssse3); diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c new file mode 100644 index 0000000..05ce54b --- /dev/null +++ b/spa/plugins/audioconvert/resample-native.c @@ -0,0 +1,400 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> + +#include <spa/param/audio/format.h> + +#include "resample-native-impl.h" + +struct quality { + uint32_t n_taps; + double cutoff; +}; + +static const struct quality window_qualities[] = { + { 8, 0.53, }, + { 16, 0.67, }, + { 24, 0.75, }, + { 32, 0.80, }, + { 48, 0.85, }, /* default */ + { 64, 0.88, }, + { 80, 0.895, }, + { 96, 0.910, }, + { 128, 0.936, }, + { 144, 0.945, }, + { 160, 0.950, }, + { 192, 0.960, }, + { 256, 0.970, }, + { 896, 0.990, }, + { 1024, 0.995, }, +}; + +static inline double sinc(double x) +{ + if (x < 1e-6) return 1.0; + x *= M_PI; + return sin(x) / x; +} + +static inline double window_blackman(double x, double n_taps) +{ + double alpha = 0.232, r; + x = 2.0 * M_PI * x / n_taps; + r = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) + + (alpha / 2.0) * cos(2.0 * x); + return r; +} + +static inline double window_cosh(double x, double n_taps) +{ + double r; + double A = 16.97789; + double x2; + x = 2.0 * x / n_taps; + x2 = x * x; + if (x2 >= 1.0) + return 0.0; + /* doi:10.1109/RME.2008.4595727 with tweak */ + r = (exp(A * sqrt(1 - x2)) - 1) / (exp(A) - 1); + return r; +} + +#define window (1 ? window_cosh : window_blackman) + +static int build_filter(float *taps, uint32_t stride, uint32_t n_taps, uint32_t n_phases, double cutoff) +{ + uint32_t i, j, n_taps12 = n_taps/2; + + for (i = 0; i <= n_phases; i++) { + double t = (double) i / (double) n_phases; + for (j = 0; j < n_taps12; j++, t += 1.0) { + /* exploit symmetry in filter taps */ + taps[(n_phases - i) * stride + n_taps12 + j] = + taps[i * stride + (n_taps12 - j - 1)] = + cutoff * sinc(t * cutoff) * window(t, n_taps); + } + } + return 0; +} + +MAKE_RESAMPLER_COPY(c); + +#define MAKE(fmt,copy,full,inter,...) \ + { SPA_AUDIO_FORMAT_ ##fmt, do_resample_ ##copy, #copy, \ + do_resample_ ##full, #full, do_resample_ ##inter, #inter, __VA_ARGS__ } + +static struct resample_info resample_table[] = +{ +#if defined (HAVE_NEON) + MAKE(F32, copy_c, full_neon, inter_neon, SPA_CPU_FLAG_NEON), +#endif +#if defined(HAVE_AVX) && defined(HAVE_FMA) + MAKE(F32, copy_c, full_avx, inter_avx, SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_FMA3), +#endif +#if defined (HAVE_SSSE3) + MAKE(F32, copy_c, full_ssse3, inter_ssse3, SPA_CPU_FLAG_SSSE3 | SPA_CPU_FLAG_SLOW_UNALIGNED), +#endif +#if defined (HAVE_SSE) + MAKE(F32, copy_c, full_sse, inter_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(F32, copy_c, full_c, inter_c), +}; +#undef MAKE + +#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) +static const struct resample_info *find_resample_info(uint32_t format, uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(resample_table, t) { + if (t->format == format && + MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; + } + return NULL; +} + +static void impl_native_free(struct resample *r) +{ + spa_log_debug(r->log, "native %p: free", r); + free(r->data); + r->data = NULL; +} + +static inline uint32_t calc_gcd(uint32_t a, uint32_t b) +{ + while (b != 0) { + uint32_t temp = a; + a = b; + b = temp % b; + } + return a; +} + +static void impl_native_update_rate(struct resample *r, double rate) +{ + struct native_data *data = r->data; + uint32_t in_rate, out_rate, phase, gcd, old_out_rate; + + if (SPA_LIKELY(data->rate == rate)) + return; + + old_out_rate = data->out_rate; + in_rate = r->i_rate / rate; + out_rate = r->o_rate; + phase = data->phase; + + gcd = calc_gcd(in_rate, out_rate); + in_rate /= gcd; + out_rate /= gcd; + + data->rate = rate; + data->phase = phase * out_rate / old_out_rate; + data->in_rate = in_rate; + data->out_rate = out_rate; + + data->inc = data->in_rate / data->out_rate; + data->frac = data->in_rate % data->out_rate; + + if (data->in_rate == data->out_rate) { + data->func = data->info->process_copy; + r->func_name = data->info->copy_name; + } + else if (rate == 1.0) { + data->func = data->info->process_full; + r->func_name = data->info->full_name; + } + else { + data->func = data->info->process_inter; + r->func_name = data->info->inter_name; + } + + spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d phase:%d inc:%d frac:%d", r, + rate, data->in_rate, data->out_rate, data->phase, data->inc, data->frac); + +} + +static uint32_t impl_native_in_len(struct resample *r, uint32_t out_len) +{ + struct native_data *data = r->data; + uint32_t in_len; + + in_len = (data->phase + out_len * data->frac) / data->out_rate; + in_len += out_len * data->inc + (data->n_taps - data->hist); + + spa_log_trace_fp(r->log, "native %p: hist:%d %d->%d", r, data->hist, out_len, in_len); + + return in_len; +} + +static void impl_native_process(struct resample *r, + const void * SPA_RESTRICT src[], uint32_t *in_len, + void * SPA_RESTRICT dst[], uint32_t *out_len) +{ + struct native_data *data = r->data; + uint32_t n_taps = data->n_taps; + float **history = data->history; + const float **s = (const float **)src; + uint32_t c, refill, hist, in, out, remain; + + hist = data->hist; + refill = 0; + + if (SPA_LIKELY(hist)) { + /* first work on the history if any. */ + if (SPA_UNLIKELY(hist <= n_taps)) { + /* we need at least n_taps to completely process the + * history before we can work on the new input. When + * we have less, refill the history. */ + refill = SPA_MIN(*in_len, n_taps-1); + for (c = 0; c < r->channels; c++) + spa_memcpy(&history[c][hist], s[c], refill * sizeof(float)); + + if (SPA_UNLIKELY(hist + refill < n_taps)) { + /* not enough in the history, keep the input in + * the history and produce no output */ + data->hist = hist + refill; + *in_len = refill; + *out_len = 0; + return; + } + } + /* now we have at least n_taps of data in the history + * and we try to process it */ + in = hist + refill; + out = *out_len; + data->func(r, (const void**)history, 0, &in, dst, 0, &out); + spa_log_trace_fp(r->log, "native %p: in:%d/%d out %d/%d hist:%d", + r, hist + refill, in, *out_len, out, hist); + } else { + out = in = 0; + } + + if (SPA_LIKELY(in >= hist)) { + int skip = in - hist; + /* we are past the history and can now work on the new + * input data */ + in = *in_len; + data->func(r, src, skip, &in, dst, out, out_len); + + spa_log_trace_fp(r->log, "native %p: in:%d/%d out %d/%d skip:%d", + r, *in_len, in, *out_len, out, skip); + + remain = *in_len - in; + if (remain > 0 && remain <= n_taps) { + /* not enough input data remaining for more output, + * copy to history */ + for (c = 0; c < r->channels; c++) + spa_memcpy(history[c], &s[c][in], remain * sizeof(float)); + } else { + /* we have enough input data remaining to produce + * more output ask to resubmit. */ + remain = 0; + *in_len = in; + } + } else { + /* we are still working on the history */ + *out_len = out; + remain = hist - in; + if (*in_len < n_taps) { + /* not enough input data, add it to the history because + * resubmitting it is not going to make progress. + * We copied this into the history above. */ + remain += refill; + } else { + /* input has enough data to possibly produce more output + * from the history so ask to resubmit */ + *in_len = 0; + } + if (remain) { + /* move history */ + for (c = 0; c < r->channels; c++) + spa_memmove(history[c], &history[c][in], remain * sizeof(float)); + } + spa_log_trace_fp(r->log, "native %p: in:%d remain:%d", r, in, remain); + + } + data->hist = remain; + return; +} + +static void impl_native_reset (struct resample *r) +{ + struct native_data *d = r->data; + if (d == NULL) + return; + memset(d->hist_mem, 0, r->channels * sizeof(float) * d->n_taps * 2); + if (r->options & RESAMPLE_OPTION_PREFILL) + d->hist = d->n_taps - 1; + else + d->hist = (d->n_taps / 2) - 1; + d->phase = 0; +} + +static uint32_t impl_native_delay (struct resample *r) +{ + struct native_data *d = r->data; + return d->n_taps / 2; +} + +int resample_native_init(struct resample *r) +{ + struct native_data *d; + const struct quality *q; + double scale; + uint32_t c, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride; + uint32_t history_stride, history_size, oversample; + + r->quality = SPA_CLAMP(r->quality, 0, (int) SPA_N_ELEMENTS(window_qualities) - 1); + r->free = impl_native_free; + r->update_rate = impl_native_update_rate; + r->in_len = impl_native_in_len; + r->process = impl_native_process; + r->reset = impl_native_reset; + r->delay = impl_native_delay; + + q = &window_qualities[r->quality]; + + gcd = calc_gcd(r->i_rate, r->o_rate); + + in_rate = r->i_rate / gcd; + out_rate = r->o_rate / gcd; + + scale = SPA_MIN(q->cutoff * out_rate / in_rate, q->cutoff); + + /* multiple of 8 taps to ease simd optimizations */ + n_taps = SPA_ROUND_UP_N((uint32_t)ceil(q->n_taps / scale), 8); + n_taps = SPA_MIN(n_taps, 1u << 18); + + /* try to get at least 256 phases so that interpolation is + * accurate enough when activated */ + n_phases = out_rate; + oversample = (255 + n_phases) / n_phases; + n_phases *= oversample; + + filter_stride = SPA_ROUND_UP_N(n_taps * sizeof(float), 64); + filter_size = filter_stride * (n_phases + 1); + history_stride = SPA_ROUND_UP_N(2 * n_taps * sizeof(float), 64); + history_size = r->channels * history_stride; + + d = calloc(1, sizeof(struct native_data) + + filter_size + + history_size + + (r->channels * sizeof(float*)) + + 64); + + if (d == NULL) + return -errno; + + r->data = d; + d->n_taps = n_taps; + d->n_phases = n_phases; + d->in_rate = in_rate; + d->out_rate = out_rate; + d->filter = SPA_PTROFF_ALIGN(d, sizeof(struct native_data), 64, float); + d->hist_mem = SPA_PTROFF_ALIGN(d->filter, filter_size, 64, float); + d->history = SPA_PTROFF(d->hist_mem, history_size, float*); + d->filter_stride = filter_stride / sizeof(float); + d->filter_stride_os = d->filter_stride * oversample; + for (c = 0; c < r->channels; c++) + d->history[c] = SPA_PTROFF(d->hist_mem, c * history_stride, float); + + build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale); + + d->info = find_resample_info(SPA_AUDIO_FORMAT_F32, r->cpu_flags); + if (SPA_UNLIKELY(d->info == NULL)) { + spa_log_error(r->log, "failed to find suitable resample format!"); + return -ENOTSUP; + } + + spa_log_debug(r->log, "native %p: q:%d in:%d out:%d n_taps:%d n_phases:%d features:%08x:%08x", + r, r->quality, in_rate, out_rate, n_taps, n_phases, + r->cpu_flags, d->info->cpu_flags); + + r->cpu_flags = d->info->cpu_flags; + + impl_native_reset(r); + impl_native_update_rate(r, 1.0); + + return 0; +} diff --git a/spa/plugins/audioconvert/resample-peaks.c b/spa/plugins/audioconvert/resample-peaks.c new file mode 100644 index 0000000..c151d60 --- /dev/null +++ b/spa/plugins/audioconvert/resample-peaks.c @@ -0,0 +1,148 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <math.h> +#include <errno.h> + +#include <spa/param/audio/format.h> + +#include "peaks-ops.h" +#include "resample.h" + +struct peaks_data { + uint32_t o_count; + uint32_t i_count; + struct peaks peaks; + float max_f[]; +}; + +static void resample_peaks_process(struct resample *r, + const void * SPA_RESTRICT src[], uint32_t *in_len, + void * SPA_RESTRICT dst[], uint32_t *out_len) +{ + struct peaks_data *pd = r->data; + uint32_t c, i, o, end, chunk, i_count, o_count; + + if (SPA_UNLIKELY(r->channels == 0)) + return; + + for (c = 0; c < r->channels; c++) { + const float *s = src[c]; + float *d = dst[c], m = pd->max_f[c]; + + o_count = pd->o_count; + i_count = pd->i_count; + o = i = 0; + + while (i < *in_len && o < *out_len) { + end = ((uint64_t) (o_count + 1) + * r->i_rate) / r->o_rate; + end = end > i_count ? end - i_count : 0; + chunk = SPA_MIN(end, *in_len); + + m = peaks_abs_max(&pd->peaks, &s[i], chunk - i, m); + + i += chunk; + + if (i == end) { + d[o++] = m; + m = 0.0f; + o_count++; + } + } + pd->max_f[c] = m; + } + *out_len = o; + *in_len = i; + pd->o_count = o_count; + pd->i_count = i_count + i; + + while (pd->i_count >= r->i_rate) { + pd->i_count -= r->i_rate; + pd->o_count -= r->o_rate; + } +} + +static void impl_peaks_free(struct resample *r) +{ + struct peaks_data *d = r->data; + if (d != NULL) { + peaks_free(&d->peaks); + free(d); + } + r->data = NULL; +} + +static void impl_peaks_update_rate(struct resample *r, double rate) +{ +} + +static uint32_t impl_peaks_delay (struct resample *r) +{ + return 0; +} + +static uint32_t impl_peaks_in_len(struct resample *r, uint32_t out_len) +{ + return out_len; +} + +static void impl_peaks_reset (struct resample *r) +{ + struct peaks_data *d = r->data; + d->i_count = d->o_count = 0; +} + +int resample_peaks_init(struct resample *r) +{ + struct peaks_data *d; + int res; + + r->free = impl_peaks_free; + r->update_rate = impl_peaks_update_rate; + + d = calloc(1, sizeof(struct peaks_data) + sizeof(float) * r->channels); + if (d == NULL) + return -errno; + + d->peaks.log = r->log; + d->peaks.cpu_flags = r->cpu_flags; + if ((res = peaks_init(&d->peaks)) < 0) { + free(d); + return res; + } + + r->data = d; + r->process = resample_peaks_process; + r->reset = impl_peaks_reset; + r->delay = impl_peaks_delay; + r->in_len = impl_peaks_in_len; + + spa_log_debug(r->log, "peaks %p: in:%d out:%d features:%08x:%08x", r, + r->i_rate, r->o_rate, r->cpu_flags, d->peaks.cpu_flags); + + r->cpu_flags = d->peaks.cpu_flags; + d->i_count = d->o_count = 0; + return 0; +} diff --git a/spa/plugins/audioconvert/resample.h b/spa/plugins/audioconvert/resample.h new file mode 100644 index 0000000..b1c89d5 --- /dev/null +++ b/spa/plugins/audioconvert/resample.h @@ -0,0 +1,69 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef RESAMPLE_H +#define RESAMPLE_H + +#include <spa/support/cpu.h> +#include <spa/support/log.h> + +#define RESAMPLE_DEFAULT_QUALITY 4 + +struct resample { + struct spa_log *log; +#define RESAMPLE_OPTION_PREFILL (1<<0) + uint32_t options; + uint32_t cpu_flags; + const char *func_name; + + uint32_t channels; + uint32_t i_rate; + uint32_t o_rate; + double rate; + int quality; + + void (*free) (struct resample *r); + void (*update_rate) (struct resample *r, double rate); + uint32_t (*in_len) (struct resample *r, uint32_t out_len); + uint32_t (*out_len) (struct resample *r, uint32_t in_len); + void (*process) (struct resample *r, + const void * SPA_RESTRICT src[], uint32_t *in_len, + void * SPA_RESTRICT dst[], uint32_t *out_len); + void (*reset) (struct resample *r); + uint32_t (*delay) (struct resample *r); + void *data; +}; + +#define resample_free(r) (r)->free(r) +#define resample_update_rate(r,...) (r)->update_rate(r,__VA_ARGS__) +#define resample_in_len(r,...) (r)->in_len(r,__VA_ARGS__) +#define resample_out_len(r,...) (r)->out_len(r,__VA_ARGS__) +#define resample_process(r,...) (r)->process(r,__VA_ARGS__) +#define resample_reset(r) (r)->reset(r) +#define resample_delay(r) (r)->delay(r) + +int resample_native_init(struct resample *r); +int resample_peaks_init(struct resample *r); + +#endif /* RESAMPLE_H */ diff --git a/spa/plugins/audioconvert/spa-resample.c b/spa/plugins/audioconvert/spa-resample.c new file mode 100644 index 0000000..4efb718 --- /dev/null +++ b/spa/plugins/audioconvert/spa-resample.c @@ -0,0 +1,341 @@ +/* Spa + * + * Copyright © 2020 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> +#include <getopt.h> + +#include <spa/support/log-impl.h> +#include <spa/debug/mem.h> +#include <spa/utils/string.h> +#include <spa/utils/result.h> + +#include <sndfile.h> + +SPA_LOG_IMPL(logger); + +#include "resample.h" + +#define DEFAULT_QUALITY RESAMPLE_DEFAULT_QUALITY + +#define MAX_SAMPLES 4096u + +struct data { + bool verbose; + int rate; + int format; + int quality; + int cpu_flags; + + const char *iname; + SF_INFO iinfo; + SNDFILE *ifile; + + const char *oname; + SF_INFO oinfo; + SNDFILE *ofile; +}; + +#define STR_FMTS "(s8|s16|s32|f32|f64)" + +#define OPTIONS "hvr:f:q:c:" +static const struct option long_options[] = { + { "help", no_argument, NULL, 'h'}, + { "verbose", no_argument, NULL, 'v'}, + + { "rate", required_argument, NULL, 'r' }, + { "format", required_argument, NULL, 'f' }, + { "quality", required_argument, NULL, 'q' }, + { "cpuflags", required_argument, NULL, 'c' }, + + { NULL, 0, NULL, 0 } +}; + +static void show_usage(const char *name, bool is_error) +{ + FILE *fp; + + fp = is_error ? stderr : stdout; + + fprintf(fp, "%s [options] <infile> <outfile>\n", name); + fprintf(fp, + " -h, --help Show this help\n" + " -v --verbose Be verbose\n" + "\n"); + fprintf(fp, + " -r --rate Output sample rate (default as input)\n" + " -f --format Output sample format %s (default as input)\n" + " -q --quality Resampler quality (default %u)\n" + " -c --cpuflags CPU flags (default 0)\n" + "\n", + STR_FMTS, DEFAULT_QUALITY); +} + +static inline const char * +sf_fmt_to_str(int fmt) +{ + switch(fmt & SF_FORMAT_SUBMASK) { + case SF_FORMAT_PCM_S8: + return "s8"; + case SF_FORMAT_PCM_16: + return "s16"; + case SF_FORMAT_PCM_24: + return "s24"; + case SF_FORMAT_PCM_32: + return "s32"; + case SF_FORMAT_FLOAT: + return "f32"; + case SF_FORMAT_DOUBLE: + return "f64"; + default: + return "unknown"; + } +} + +static inline int +sf_str_to_fmt(const char *str) +{ + if (!str) + return -1; + if (spa_streq(str, "s8")) + return SF_FORMAT_PCM_S8; + if (spa_streq(str, "s16")) + return SF_FORMAT_PCM_16; + if (spa_streq(str, "s24")) + return SF_FORMAT_PCM_24; + if (spa_streq(str, "s32")) + return SF_FORMAT_PCM_32; + if (spa_streq(str, "f32")) + return SF_FORMAT_FLOAT; + if (spa_streq(str, "f64")) + return SF_FORMAT_DOUBLE; + return -1; +} + +static int open_files(struct data *d) +{ + d->ifile = sf_open(d->iname, SFM_READ, &d->iinfo); + if (d->ifile == NULL) { + fprintf(stderr, "error: failed to open input file \"%s\": %s\n", + d->iname, sf_strerror(NULL)); + return -EIO; + } + + d->oinfo.channels = d->iinfo.channels; + d->oinfo.samplerate = d->rate > 0 ? d->rate : d->iinfo.samplerate; + d->oinfo.format = d->format > 0 ? d->format : d->iinfo.format; + d->oinfo.format |= SF_FORMAT_WAV; + + d->ofile = sf_open(d->oname, SFM_WRITE, &d->oinfo); + if (d->ofile == NULL) { + fprintf(stderr, "error: failed to open output file \"%s\": %s\n", + d->oname, sf_strerror(NULL)); + return -EIO; + } + if (d->verbose) { + fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n", + d->iname, d->iinfo.channels, d->iinfo.samplerate, + sf_fmt_to_str(d->iinfo.format)); + fprintf(stdout, "output '%s': channels:%d rate:%d format:%s\n", + d->oname, d->oinfo.channels, d->oinfo.samplerate, + sf_fmt_to_str(d->oinfo.format)); + } + return 0; +} + +static int close_files(struct data *d) +{ + if (d->ifile) + sf_close(d->ifile); + if (d->ofile) + sf_close(d->ofile); + return 0; +} + +static int do_conversion(struct data *d) +{ + struct resample r; + int channels = d->iinfo.channels; + float in[MAX_SAMPLES * channels]; + float out[MAX_SAMPLES * channels]; + float ibuf[MAX_SAMPLES * channels]; + float obuf[MAX_SAMPLES * channels]; + uint32_t in_len, out_len, queued; + uint32_t pin_len, pout_len; + size_t read, written; + const void *src[channels]; + void *dst[channels]; + uint32_t i; + int res, j, k; + uint32_t flushing = UINT32_MAX; + + spa_zero(r); + r.cpu_flags = d->cpu_flags; + r.log = &logger.log; + r.channels = channels; + r.i_rate = d->iinfo.samplerate; + r.o_rate = d->oinfo.samplerate; + r.quality = d->quality < 0 ? DEFAULT_QUALITY : d->quality; + if ((res = resample_native_init(&r)) < 0) { + fprintf(stderr, "can't init converter: %s\n", spa_strerror(res)); + return res; + } + + for (j = 0; j < channels; j++) + src[j] = &in[MAX_SAMPLES * j]; + for (j = 0; j < channels; j++) + dst[j] = &out[MAX_SAMPLES * j]; + + read = written = queued = 0; + while (true) { + pout_len = out_len = MAX_SAMPLES; + in_len = SPA_MIN(MAX_SAMPLES, resample_in_len(&r, out_len)); + in_len -= SPA_MIN(queued, in_len); + + if (in_len > 0) { + pin_len = in_len = sf_readf_float(d->ifile, &ibuf[queued * channels], in_len); + + read += pin_len; + + if (pin_len == 0) { + if (flushing == 0) + break; + if (flushing == UINT32_MAX) + flushing = resample_delay(&r); + + pin_len = in_len = SPA_MIN(MAX_SAMPLES, flushing); + flushing -= in_len; + + for (k = 0, i = 0; i < pin_len; i++) { + for (j = 0; j < channels; j++) + ibuf[k++] = 0.0; + } + } + } + in_len += queued; + pin_len = in_len; + + for (k = 0, i = 0; i < pin_len; i++) { + for (j = 0; j < channels; j++) { + in[MAX_SAMPLES * j + i] = ibuf[k++]; + } + } + resample_process(&r, src, &pin_len, dst, &pout_len); + + queued = in_len - pin_len; + if (queued) + memmove(ibuf, &ibuf[pin_len * channels], queued * channels * sizeof(float)); + + if (pout_len > 0) { + for (k = 0, i = 0; i < pout_len; i++) { + for (j = 0; j < channels; j++) { + obuf[k++] = out[MAX_SAMPLES * j + i]; + } + } + pout_len = sf_writef_float(d->ofile, obuf, pout_len); + + written += pout_len; + } + } + if (d->verbose) + fprintf(stdout, "read %zu samples, wrote %zu samples\n", read, written); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int c; + int longopt_index = 0, ret; + struct data data; + + spa_zero(data); + + logger.log.level = SPA_LOG_LEVEL_DEBUG; + + data.quality = -1; + while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { + switch (c) { + case 'h': + show_usage(argv[0], false); + return EXIT_SUCCESS; + case 'v': + data.verbose = true; + break; + case 'r': + ret = atoi(optarg); + if (ret <= 0) { + fprintf(stderr, "error: bad rate %s\n", optarg); + goto error_usage; + } + data.rate = ret; + break; + case 'f': + ret = sf_str_to_fmt(optarg); + if (ret < 0) { + fprintf(stderr, "error: bad format %s\n", optarg); + goto error_usage; + } + data.format = ret; + break; + case 'q': + ret = atoi(optarg); + if (ret < 0) { + fprintf(stderr, "error: bad quality %s\n", optarg); + goto error_usage; + } + data.quality = ret; + break; + case 'c': + data.cpu_flags = strtol(optarg, NULL, 0); + break; + default: + fprintf(stderr, "error: unknown option '%c'\n", c); + goto error_usage; + } + } + if (optind + 1 >= argc) { + fprintf(stderr, "error: filename arguments missing (%d %d)\n", optind, argc); + goto error_usage; + } + data.iname = argv[optind++]; + data.oname = argv[optind++]; + + if (open_files(&data) < 0) + return EXIT_FAILURE; + + do_conversion(&data); + + close_files(&data); + + return 0; + +error_usage: + show_usage(argv[0], true); + return EXIT_FAILURE; +} diff --git a/spa/plugins/audioconvert/test-audioadapter.c b/spa/plugins/audioconvert/test-audioadapter.c new file mode 100644 index 0000000..a51feee --- /dev/null +++ b/spa/plugins/audioconvert/test-audioadapter.c @@ -0,0 +1,302 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/support/plugin.h> +#include <spa/param/param.h> +#include <spa/param/audio/format.h> +#include <spa/param/audio/format-utils.h> +#include <spa/node/node.h> +#include <spa/debug/mem.h> +#include <spa/support/log-impl.h> + +SPA_LOG_IMPL(logger); + +extern const struct spa_handle_factory test_source_factory; + +struct context { + struct spa_handle *follower_handle; + struct spa_node *follower_node; + + struct spa_handle *adapter_handle; + struct spa_node *adapter_node; +}; + +static const struct spa_handle_factory *find_factory(const char *name) +{ + uint32_t index = 0; + const struct spa_handle_factory *factory; + + while (spa_handle_factory_enum(&factory, &index) == 1) { + if (spa_streq(factory->name, name)) + return factory; + } + return NULL; +} + +static int setup_context(struct context *ctx) +{ + size_t size; + int res; + struct spa_support support[1]; + struct spa_dict_item items[1]; + const struct spa_handle_factory *factory; + char value[32]; + void *iface; + + logger.log.level = SPA_LOG_LEVEL_TRACE; + support[0] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, &logger.log); + + /* make follower */ + factory = &test_source_factory; + size = spa_handle_factory_get_size(factory, NULL); + ctx->follower_handle = calloc(1, size); + spa_assert_se(ctx->follower_handle != NULL); + + res = spa_handle_factory_init(factory, + ctx->follower_handle, + NULL, support, 1); + spa_assert_se(res >= 0); + + res = spa_handle_get_interface(ctx->follower_handle, + SPA_TYPE_INTERFACE_Node, &iface); + spa_assert_se(res >= 0); + + ctx->follower_node = iface; + + /* make adapter */ + factory = find_factory(SPA_NAME_AUDIO_ADAPT); + spa_assert_se(factory != NULL); + + size = spa_handle_factory_get_size(factory, NULL); + + ctx->adapter_handle = calloc(1, size); + spa_assert_se(ctx->adapter_handle != NULL); + + snprintf(value, sizeof(value), "pointer:%p", ctx->follower_node); + items[0] = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); + + res = spa_handle_factory_init(factory, + ctx->adapter_handle, + &SPA_DICT_INIT(items, 1), + support, 1); + spa_assert_se(res >= 0); + + res = spa_handle_get_interface(ctx->adapter_handle, + SPA_TYPE_INTERFACE_Node, &iface); + spa_assert_se(res >= 0); + ctx->adapter_node = iface; + + return 0; +} + +static int clean_context(struct context *ctx) +{ + spa_handle_clear(ctx->adapter_handle); + spa_handle_clear(ctx->follower_handle); + free(ctx->adapter_handle); + free(ctx->follower_handle); + return 0; +} + +static void node_info(void *data, const struct spa_node_info *info) +{ + fprintf(stderr, "input %d, output %d\n", + info->max_input_ports, + info->max_output_ports); + + spa_assert_se(info->max_input_ports == 0); + spa_assert_se(info->max_output_ports > 0); +} + +static void port_info_none(void *data, + enum spa_direction direction, uint32_t port, + const struct spa_port_info *info) +{ + spa_assert_not_reached(); +} + + +static int test_init_state(struct context *ctx) +{ + struct spa_hook listener; + static const struct spa_node_events init_events = { + SPA_VERSION_NODE_EVENTS, + .info = node_info, + .port_info = port_info_none, + }; + + spa_zero(listener); + spa_node_add_listener(ctx->adapter_node, + &listener, &init_events, ctx); + spa_hook_remove(&listener); + + return 0; +} + +static void port_info_5_1(void *data, + enum spa_direction direction, uint32_t port, + const struct spa_port_info *info) +{ + spa_assert_se(direction == SPA_DIRECTION_OUTPUT); + spa_assert_se(port < 6); +} + +static void port_info_1_1(void *data, + enum spa_direction direction, uint32_t port, + const struct spa_port_info *info) +{ + spa_assert_se(direction == SPA_DIRECTION_OUTPUT); + spa_assert_se(port < 2); +} + +static int test_split_setup(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + struct spa_hook listener; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .port_info = port_info_5_1, + }; + + /* external format */ + spa_zero(info); + info.format = SPA_AUDIO_FORMAT_F32P; + info.channels = 6; + info.rate = 48000; + info.position[0] = SPA_AUDIO_CHANNEL_FL; + info.position[1] = SPA_AUDIO_CHANNEL_FR; + info.position[2] = SPA_AUDIO_CHANNEL_FC; + info.position[3] = SPA_AUDIO_CHANNEL_LFE; + info.position[4] = SPA_AUDIO_CHANNEL_SL; + info.position[5] = SPA_AUDIO_CHANNEL_SR; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + spa_log_debug(&logger.log, "set profile %d@%d", info.channels, info.rate); + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + + res = spa_node_set_param(ctx->adapter_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + spa_zero(listener); + spa_node_add_listener(ctx->adapter_node, + &listener, &node_events, ctx); + spa_hook_remove(&listener); + + /* internal format */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + spa_zero(info); + info.format = SPA_AUDIO_FORMAT_S16; + info.rate = 44100; + info.channels = 2; + info.position[0] = SPA_AUDIO_CHANNEL_FL; + info.position[1] = SPA_AUDIO_CHANNEL_FR; + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + spa_log_debug(&logger.log, "set format %d@%d", info.channels, info.rate); + res = spa_node_set_param(ctx->adapter_node, SPA_PARAM_Format, 0, param); + spa_log_debug(&logger.log, "result %d", res); + spa_assert_se(res >= 0); + + return 0; +} + +static int test_passthrough_setup(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + struct spa_hook listener; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .port_info = port_info_1_1, + }; + + /* internal format */ + spa_zero(info); + info.format = SPA_AUDIO_FORMAT_S16; + info.channels = 2; + info.rate = 44100; + info.position[0] = SPA_AUDIO_CHANNEL_FL; + info.position[1] = SPA_AUDIO_CHANNEL_FR; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + spa_log_debug(&logger.log, "set profile %d@%d", info.channels, info.rate); + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_passthrough), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + + res = spa_node_set_param(ctx->adapter_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + spa_zero(listener); + spa_node_add_listener(ctx->adapter_node, + &listener, &node_events, ctx); + spa_hook_remove(&listener); + + return 0; +} + + +int main(int argc, char *argv[]) +{ + struct context ctx; + + spa_zero(ctx); + + setup_context(&ctx); + + test_init_state(&ctx); + test_split_setup(&ctx); + test_passthrough_setup(&ctx); + + clean_context(&ctx); + + return 0; +} diff --git a/spa/plugins/audioconvert/test-audioconvert.c b/spa/plugins/audioconvert/test-audioconvert.c new file mode 100644 index 0000000..99ba866 --- /dev/null +++ b/spa/plugins/audioconvert/test-audioconvert.c @@ -0,0 +1,1158 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/support/plugin.h> +#include <spa/param/param.h> +#include <spa/param/audio/format.h> +#include <spa/param/audio/format-utils.h> +#include <spa/node/node.h> +#include <spa/node/io.h> +#include <spa/debug/mem.h> +#include <spa/support/log-impl.h> + +SPA_LOG_IMPL(logger); + +extern const struct spa_handle_factory test_source_factory; + +#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) + +struct context { + struct spa_handle *convert_handle; + struct spa_node *convert_node; + + bool got_node_info; + uint32_t n_port_info[2]; + bool got_port_info[2][MAX_PORTS]; +}; + +static const struct spa_handle_factory *find_factory(const char *name) +{ + uint32_t index = 0; + const struct spa_handle_factory *factory; + + while (spa_handle_factory_enum(&factory, &index) == 1) { + if (spa_streq(factory->name, name)) + return factory; + } + return NULL; +} + +static int setup_context(struct context *ctx) +{ + size_t size; + int res; + struct spa_support support[1]; + struct spa_dict_item items[2]; + const struct spa_handle_factory *factory; + void *iface; + + logger.log.level = SPA_LOG_LEVEL_TRACE; + support[0] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, &logger); + + /* make convert */ + factory = find_factory(SPA_NAME_AUDIO_CONVERT); + spa_assert_se(factory != NULL); + + size = spa_handle_factory_get_size(factory, NULL); + + ctx->convert_handle = calloc(1, size); + spa_assert_se(ctx->convert_handle != NULL); + + items[0] = SPA_DICT_ITEM_INIT("clock.quantum-limit", "8192"); + + res = spa_handle_factory_init(factory, + ctx->convert_handle, + &SPA_DICT_INIT(items, 1), + support, 1); + spa_assert_se(res >= 0); + + res = spa_handle_get_interface(ctx->convert_handle, + SPA_TYPE_INTERFACE_Node, &iface); + spa_assert_se(res >= 0); + ctx->convert_node = iface; + + return 0; +} + +static int clean_context(struct context *ctx) +{ + spa_handle_clear(ctx->convert_handle); + free(ctx->convert_handle); + return 0; +} + +static void node_info_check(void *data, const struct spa_node_info *info) +{ + struct context *ctx = data; + + fprintf(stderr, "input %d, output %d\n", + info->max_input_ports, + info->max_output_ports); + + spa_assert_se(info->max_input_ports == MAX_PORTS); + spa_assert_se(info->max_output_ports == MAX_PORTS); + + ctx->got_node_info = true; +} + +static void port_info_check(void *data, + enum spa_direction direction, uint32_t port, + const struct spa_port_info *info) +{ + struct context *ctx = data; + + fprintf(stderr, "port %d %d %p\n", direction, port, info); + + ctx->got_port_info[direction][port] = true; + ctx->n_port_info[direction]++; +} + +static int test_init_state(struct context *ctx) +{ + struct spa_hook listener; + static const struct spa_node_events init_events = { + SPA_VERSION_NODE_EVENTS, + .info = node_info_check, + .port_info = port_info_check, + }; + + spa_zero(ctx->got_node_info); + spa_zero(ctx->n_port_info); + spa_zero(ctx->got_port_info); + + spa_zero(listener); + spa_node_add_listener(ctx->convert_node, + &listener, &init_events, ctx); + spa_hook_remove(&listener); + + spa_assert_se(ctx->got_node_info); + spa_assert_se(ctx->n_port_info[0] == 1); + spa_assert_se(ctx->n_port_info[1] == 1); + spa_assert_se(ctx->got_port_info[0][0] == true); + spa_assert_se(ctx->got_port_info[1][0] == true); + + return 0; +} + +static int test_set_in_format(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + + /* other format */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + info = (struct spa_audio_info_raw) { + .format = SPA_AUDIO_FORMAT_S16, + .rate = 44100, + .channels = 2, + .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, } + }; + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + res = spa_node_port_set_param(ctx->convert_node, SPA_DIRECTION_INPUT, 0, + SPA_PARAM_Format, 0, param); + spa_assert_se(res == 0); + + return 0; +} + +static int test_split_setup1(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + struct spa_hook listener; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .port_info = port_info_check, + }; + + spa_zero(listener); + spa_node_add_listener(ctx->convert_node, + &listener, &node_events, ctx); + + /* port config, output as DSP */ + spa_zero(info); + info.format = SPA_AUDIO_FORMAT_F32P; + info.rate = 48000; + info.channels = 6; + info.position[0] = SPA_AUDIO_CHANNEL_FL; + info.position[1] = SPA_AUDIO_CHANNEL_FR; + info.position[2] = SPA_AUDIO_CHANNEL_FC; + info.position[3] = SPA_AUDIO_CHANNEL_LFE; + info.position[4] = SPA_AUDIO_CHANNEL_SL; + info.position[5] = SPA_AUDIO_CHANNEL_SR; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + + res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + spa_hook_remove(&listener); + + return 0; +} + +static int test_split_setup2(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + struct spa_hook listener; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .port_info = port_info_check, + }; + + spa_zero(listener); + spa_node_add_listener(ctx->convert_node, + &listener, &node_events, ctx); + + /* port config, output as DSP */ + spa_zero(info); + info.format = SPA_AUDIO_FORMAT_F32P; + info.rate = 48000; + info.channels = 4; + info.position[0] = SPA_AUDIO_CHANNEL_FL; + info.position[1] = SPA_AUDIO_CHANNEL_FR; + info.position[2] = SPA_AUDIO_CHANNEL_RL; + info.position[3] = SPA_AUDIO_CHANNEL_RR; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + + res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + spa_hook_remove(&listener); + + return 0; +} + +static int test_convert_setup1(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + int res; + struct spa_hook listener; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .port_info = port_info_check, + }; + + spa_zero(listener); + spa_node_add_listener(ctx->convert_node, + &listener, &node_events, ctx); + + /* port config, output convert */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert)); + + res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + spa_hook_remove(&listener); + + return 0; +} + +static int test_set_out_format(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + + /* out format */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + info = (struct spa_audio_info_raw) { + .format = SPA_AUDIO_FORMAT_S32P, + .rate = 96000, + .channels = 8, + .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, + SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, } + }; + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + res = spa_node_port_set_param(ctx->convert_node, SPA_DIRECTION_OUTPUT, 0, + SPA_PARAM_Format, 0, param); + spa_assert_se(res == 0); + + return 0; +} + +static int test_merge_setup1(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + struct spa_hook listener; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .port_info = port_info_check, + }; + + spa_zero(listener); + spa_node_add_listener(ctx->convert_node, + &listener, &node_events, ctx); + + /* port config, output as DSP */ + spa_zero(info); + info.format = SPA_AUDIO_FORMAT_F32P; + info.rate = 48000; + info.channels = 6; + info.position[0] = SPA_AUDIO_CHANNEL_FL; + info.position[1] = SPA_AUDIO_CHANNEL_FR; + info.position[2] = SPA_AUDIO_CHANNEL_FC; + info.position[3] = SPA_AUDIO_CHANNEL_LFE; + info.position[4] = SPA_AUDIO_CHANNEL_RL; + info.position[5] = SPA_AUDIO_CHANNEL_RR; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + + res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + spa_hook_remove(&listener); + + return 0; +} + +static int test_set_out_format2(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + + /* out format */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + info = (struct spa_audio_info_raw) { + .format = SPA_AUDIO_FORMAT_S16, + .rate = 32000, + .channels = 2, + .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, } + }; + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + res = spa_node_port_set_param(ctx->convert_node, SPA_DIRECTION_OUTPUT, 0, + SPA_PARAM_Format, 0, param); + spa_assert_se(res == 0); + + return 0; +} + +static int test_merge_setup2(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + struct spa_hook listener; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .port_info = port_info_check, + }; + + spa_zero(listener); + spa_node_add_listener(ctx->convert_node, + &listener, &node_events, ctx); + + /* port config, output as DSP */ + spa_zero(info); + info.format = SPA_AUDIO_FORMAT_F32P; + info.rate = 96000; + info.channels = 4; + info.position[0] = SPA_AUDIO_CHANNEL_FL; + info.position[1] = SPA_AUDIO_CHANNEL_FR; + info.position[2] = SPA_AUDIO_CHANNEL_FC; + info.position[3] = SPA_AUDIO_CHANNEL_LFE; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); + + res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + spa_hook_remove(&listener); + + return 0; +} + +static int test_convert_setup2(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + int res; + struct spa_hook listener; + static const struct spa_node_events node_events = { + SPA_VERSION_NODE_EVENTS, + .port_info = port_info_check, + }; + + spa_zero(listener); + spa_node_add_listener(ctx->convert_node, + &listener, &node_events, ctx); + + /* port config, input convert */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert)); + + res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + spa_hook_remove(&listener); + + return 0; +} + +static int test_set_in_format2(struct context *ctx) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_audio_info_raw info; + int res; + + /* other format */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + info = (struct spa_audio_info_raw) { + .format = SPA_AUDIO_FORMAT_S24, + .rate = 48000, + .channels = 3, + .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_LFE, } + }; + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + + res = spa_node_port_set_param(ctx->convert_node, SPA_DIRECTION_INPUT, 0, + SPA_PARAM_Format, 0, param); + spa_assert_se(res == 0); + + return 0; +} + +static int setup_direction(struct context *ctx, enum spa_direction direction, uint32_t mode, + struct spa_audio_info_raw *info) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param, *format; + int res; + uint32_t i; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + format = spa_format_audio_raw_build(&b, SPA_PARAM_Format, info); + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(format)); + break; + + case SPA_PARAM_PORT_CONFIG_MODE_convert: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode)); + break; + default: + return -EINVAL; + } + res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_convert: + res = spa_node_port_set_param(ctx->convert_node, direction, 0, + SPA_PARAM_Format, 0, format); + spa_assert_se(res == 0); + break; + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + format = spa_format_audio_dsp_build(&b, SPA_PARAM_Format, + &SPA_AUDIO_INFO_DSP_INIT( + .format = SPA_AUDIO_FORMAT_F32P)); + for (i = 0; i < info->channels; i++) { + res = spa_node_port_set_param(ctx->convert_node, direction, i, + SPA_PARAM_Format, 0, format); + spa_assert_se(res == 0); + } + break; + default: + return -EINVAL; + } + return 0; +} + +struct buffer { + struct spa_buffer buffer; + struct spa_data datas[MAX_PORTS]; + struct spa_chunk chunks[MAX_PORTS]; +}; + +struct data { + uint32_t mode; + struct spa_audio_info_raw info; + uint32_t ports; + uint32_t planes; + const void *data[MAX_PORTS]; + uint32_t size; +}; + +static int run_convert(struct context *ctx, struct data *in_data, + struct data *out_data) +{ + struct spa_command cmd; + int res; + uint32_t i, j, k; + struct buffer in_buffers[in_data->ports]; + struct buffer out_buffers[out_data->ports]; + struct spa_io_buffers in_io[in_data->ports]; + struct spa_io_buffers out_io[out_data->ports]; + + setup_direction(ctx, SPA_DIRECTION_INPUT, in_data->mode, &in_data->info); + setup_direction(ctx, SPA_DIRECTION_OUTPUT, out_data->mode, &out_data->info); + + cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); + res = spa_node_send_command(ctx->convert_node, &cmd); + spa_assert_se(res == 0); + + for (i = 0, k = 0; i < in_data->ports; i++) { + struct buffer *b = &in_buffers[i]; + struct spa_buffer *buffers[1]; + spa_zero(*b); + b->buffer.datas = b->datas; + b->buffer.n_datas = in_data->planes; + + for (j = 0; j < in_data->planes; j++, k++) { + b->datas[j].type = SPA_DATA_MemPtr; + b->datas[j].flags = 0; + b->datas[j].fd = -1; + b->datas[j].mapoffset = 0; + b->datas[j].maxsize = in_data->size; + b->datas[j].data = (void *)in_data->data[k]; + b->datas[j].chunk = &b->chunks[j]; + b->datas[j].chunk->offset = 0; + b->datas[j].chunk->size = in_data->size; + b->datas[j].chunk->stride = 0; + } + buffers[0] = &b->buffer; + res = spa_node_port_use_buffers(ctx->convert_node, SPA_DIRECTION_INPUT, i, + 0, buffers, 1); + spa_assert_se(res == 0); + + in_io[i].status = SPA_STATUS_HAVE_DATA; + in_io[i].buffer_id = 0; + + res = spa_node_port_set_io(ctx->convert_node, SPA_DIRECTION_INPUT, i, + SPA_IO_Buffers, &in_io[i], sizeof(in_io[i])); + spa_assert_se(res == 0); + } + for (i = 0; i < out_data->ports; i++) { + struct buffer *b = &out_buffers[i]; + struct spa_buffer *buffers[1]; + spa_zero(*b); + b->buffer.datas = b->datas; + b->buffer.n_datas = out_data->planes; + + for (j = 0; j < out_data->planes; j++) { + b->datas[j].type = SPA_DATA_MemPtr; + b->datas[j].flags = 0; + b->datas[j].fd = -1; + b->datas[j].mapoffset = 0; + b->datas[j].maxsize = out_data->size; + b->datas[j].data = calloc(1, out_data->size); + b->datas[j].chunk = &b->chunks[j]; + b->datas[j].chunk->offset = 0; + b->datas[j].chunk->size = 0; + b->datas[j].chunk->stride = 0; + } + buffers[0] = &b->buffer; + res = spa_node_port_use_buffers(ctx->convert_node, + SPA_DIRECTION_OUTPUT, i, 0, buffers, 1); + spa_assert_se(res == 0); + + out_io[i].status = SPA_STATUS_NEED_DATA; + out_io[i].buffer_id = -1; + + res = spa_node_port_set_io(ctx->convert_node, SPA_DIRECTION_OUTPUT, i, + SPA_IO_Buffers, &out_io[i], sizeof(out_io[i])); + spa_assert_se(res == 0); + } + + res = spa_node_process(ctx->convert_node); + spa_assert_se(res == (SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA)); + + for (i = 0, k = 0; i < out_data->ports; i++) { + struct buffer *b = &out_buffers[i]; + + spa_assert_se(out_io[i].status == SPA_STATUS_HAVE_DATA); + spa_assert_se(out_io[i].buffer_id == 0); + + for (j = 0; j < out_data->planes; j++, k++) { + spa_assert_se(b->datas[j].chunk->offset == 0); + spa_assert_se(b->datas[j].chunk->size == out_data->size); + + res = memcmp(b->datas[j].data, out_data->data[k], out_data->size); + if (res != 0) { + fprintf(stderr, "error port %d plane %d\n", i, j); + spa_debug_mem(0, b->datas[j].data, out_data->size); + spa_debug_mem(0, out_data->data[k], out_data->size); + } + spa_assert_se(res == 0); + + free(b->datas[j].data); + } + } + cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend); + res = spa_node_send_command(ctx->convert_node, &cmd); + spa_assert_se(res == 0); + + return 0; +} + +static const float data_f32p_1[] = { 0.1f, 0.1f, 0.1f, 0.1f }; +static const float data_f32p_2[] = { 0.2f, 0.2f, 0.2f, 0.2f }; +static const float data_f32p_3[] = { 0.3f, 0.3f, 0.3f, 0.3f }; +static const float data_f32p_4[] = { 0.4f, 0.4f, 0.4f, 0.4f }; +static const float data_f32p_5[] = { 0.5f, 0.5f, 0.5f, 0.5f }; +static const float data_f32p_5_6p1[] = { 0.953553438f, 0.953553438f, 0.953553438f, 0.953553438f }; +static const float data_f32p_6[] = { 0.6f, 0.6f, 0.6f, 0.6f }; +static const float data_f32p_6_6p1[] = { 1.053553343f, 1.053553343f, 1.053553343f, 1.053553343f }; +static const float data_f32p_7[] = { 0.7f, 0.7f, 0.7f, 0.7f }; +static const float data_f32p_8[] = { 0.8f, 0.8f, 0.8f, 0.8f }; + +static const float data_f32_5p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f }; +static const float data_f32_6p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f }; +static const float data_f32_6p1_from_5p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f }; + +static const float data_f32_7p1_remapped[] = { 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f }; +static const float data_f32_5p1_remapped[] = { 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f }; + +struct data dsp_5p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, }, + .size = sizeof(float) * 4 +}; + +struct data dsp_5p1_from_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5_6p1, data_f32p_6_6p1, }, + .size = sizeof(float) * 4 +}; + + +struct data dsp_5p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_5, data_f32p_6, data_f32p_3, data_f32p_4, }, + .size = sizeof(float) * 4 +}; + +struct data dsp_5p1_remapped_from_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_5_6p1, data_f32p_6_6p1, data_f32p_3, data_f32p_4, }, + .size = sizeof(float) * 4 +}; + +struct data dsp_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 7, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, + .size = sizeof(float) * 4 +}; + +struct data dsp_6p1_side = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + }), + .ports = 7, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, + .size = sizeof(float) * 4 +}; + +struct data dsp_7p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 8, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + }), + .ports = 8, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_7, data_f32p_8, data_f32p_5, data_f32p_6 }, + .size = sizeof(data_f32p_1) +}; + +struct data dsp_5p1_remapped_2 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FL, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_2, data_f32p_1, }, + .size = sizeof(float) * 4 +}; + +struct data conv_f32_48000_5p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_5p1 }, + .size = sizeof(data_f32_5p1) +}; + +struct data conv_f32_48000_5p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_5p1_remapped }, + .size = sizeof(data_f32_5p1_remapped) +}; + +struct data conv_f32p_48000_5p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32P, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 6, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, }, + .size = sizeof(float) * 4 +}; + +struct data conv_f32_48000_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_6p1 }, + .size = sizeof(data_f32_6p1) +}; + +struct data conv_f32_48000_6p1_from_5p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_6p1_from_5p1 }, + .size = sizeof(data_f32_6p1_from_5p1) +}; + +struct data conv_f32_48000_6p1_side = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_6p1 }, + .size = sizeof(data_f32_6p1) +}; + +struct data conv_f32p_48000_6p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32P, + .rate = 48000, + .channels = 7, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RC, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 7, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, + .size = sizeof(float) * 4 +}; + +struct data conv_f32p_48000_5p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32P, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 1, + .planes = 6, + .data = { data_f32p_1, data_f32p_2, data_f32p_5, data_f32p_6, data_f32p_3, data_f32p_4, }, + .size = sizeof(float) * 4 +}; + +struct data conv_f32_48000_7p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 8, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_7p1_remapped, }, + .size = sizeof(data_f32_7p1_remapped) +}; + +static int test_convert_remap_dsp(struct context *ctx) +{ + run_convert(ctx, &dsp_5p1, &conv_f32_48000_5p1); + run_convert(ctx, &dsp_5p1, &conv_f32p_48000_5p1); + run_convert(ctx, &dsp_5p1, &conv_f32_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1, &conv_f32p_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped, &conv_f32_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped, &conv_f32p_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped, &conv_f32_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped, &conv_f32p_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1_remapped); + run_convert(ctx, &dsp_6p1, &conv_f32p_48000_6p1); + run_convert(ctx, &dsp_6p1, &conv_f32_48000_6p1); + run_convert(ctx, &dsp_6p1_side, &conv_f32_48000_6p1_side); + + run_convert(ctx, &dsp_5p1, &conv_f32_48000_6p1_from_5p1); + return 0; +} + +static int test_convert_remap_conv(struct context *ctx) +{ + run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1); + run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1_remapped_2); + run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1); + run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1_remapped_2); + run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1); + run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped_2); + run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1); + run_convert(ctx, &conv_f32p_48000_6p1, &dsp_6p1); + run_convert(ctx, &conv_f32_48000_6p1, &dsp_6p1); + run_convert(ctx, &conv_f32_48000_6p1_side, &dsp_6p1_side); + run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_7p1_remapped, &dsp_7p1_remapped); + run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped_2); + + run_convert(ctx, &conv_f32_48000_6p1, &dsp_5p1_from_6p1); + run_convert(ctx, &conv_f32_48000_6p1_side, &dsp_5p1_from_6p1); + run_convert(ctx, &conv_f32_48000_6p1, &dsp_5p1_remapped_from_6p1); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct context ctx; + + spa_zero(ctx); + + setup_context(&ctx); + + test_init_state(&ctx); + test_set_in_format(&ctx); + test_split_setup1(&ctx); + test_split_setup2(&ctx); + test_convert_setup1(&ctx); + test_set_out_format(&ctx); + test_merge_setup1(&ctx); + test_set_out_format2(&ctx); + test_merge_setup2(&ctx); + test_convert_setup2(&ctx); + test_set_in_format2(&ctx); + test_set_out_format(&ctx); + + test_convert_remap_dsp(&ctx); + test_convert_remap_conv(&ctx); + + clean_context(&ctx); + + return 0; +} diff --git a/spa/plugins/audioconvert/test-channelmix.c b/spa/plugins/audioconvert/test-channelmix.c new file mode 100644 index 0000000..9e052e2 --- /dev/null +++ b/spa/plugins/audioconvert/test-channelmix.c @@ -0,0 +1,374 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/support/log-impl.h> +#include <spa/debug/mem.h> + +static uint32_t cpu_flags; + +SPA_LOG_IMPL(logger); + +#define MATRIX(...) (float[]) { __VA_ARGS__ } + +#include "test-helper.h" +#include "channelmix-ops.c" + +#define CLOSE_ENOUGH(a,b) (fabs((a)-(b)) < 0.000001f) + +static void dump_matrix(struct channelmix *mix, float *coeff) +{ + uint32_t i, j; + + for (i = 0; i < mix->dst_chan; i++) { + for (j = 0; j < mix->src_chan; j++) { + float v = mix->matrix[i][j]; + spa_log_debug(mix->log, "%d %d: %f <-> %f", i, j, v, *coeff); + spa_assert_se(CLOSE_ENOUGH(v, *coeff)); + coeff++; + } + } +} + +static void test_mix(uint32_t src_chan, uint32_t src_mask, uint32_t dst_chan, uint32_t dst_mask, uint32_t options, float *coeff) +{ + struct channelmix mix; + + spa_log_debug(&logger.log, "start %d->%d (%08x -> %08x)", src_chan, dst_chan, src_mask, dst_mask); + + spa_zero(mix); + mix.options = options; + mix.src_chan = src_chan; + mix.dst_chan = dst_chan; + mix.src_mask = src_mask; + mix.dst_mask = dst_mask; + mix.log = &logger.log; + + spa_assert_se(channelmix_init(&mix) == 0); + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + dump_matrix(&mix, coeff); +} + +static void test_1_N_MONO(void) +{ + test_mix(1, _M(MONO), 2, _M(FL)|_M(FR), 0, + MATRIX(1.0, 1.0)); + test_mix(1, _M(MONO), 3, _M(FL)|_M(FR)|_M(LFE), 0, + MATRIX(1.0, 1.0, 1.0)); + test_mix(1, _M(MONO), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, + MATRIX(1.0, 1.0, 1.0, 1.0)); + test_mix(1, _M(MONO), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 1.0, 1.0, 1.0)); + test_mix(1, _M(MONO), 12, 0, 0, + MATRIX(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)); +} + +static void test_1_N_FC(void) +{ + test_mix(1, _M(FC), 2, _M(FL)|_M(FR), 0, + MATRIX(0.707107, 0.707107)); + test_mix(1, _M(FC), 3, _M(FL)|_M(FR)|_M(LFE), 0, + MATRIX(0.707107, 0.707107, 0.0)); + test_mix(1, _M(FC), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, + MATRIX(0.0, 0.0, 1.0, 0.0)); + test_mix(1, _M(FC), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, + MATRIX(0.707107, 0.707107, 0.0, 0.0)); + test_mix(1, _M(FC), 12, 0, 0, + MATRIX(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)); +} + +static void test_N_1(void) +{ + test_mix(1, _M(MONO), 1, _M(MONO), 0, + MATRIX(1.0)); + test_mix(1, _M(MONO), 1, _M(FC), 0, + MATRIX(1.0)); + test_mix(1, _M(FC), 1, _M(MONO), 0, + MATRIX(1.0)); + test_mix(1, _M(FC), 1, _M(FC), 0, + MATRIX(1.0)); + test_mix(2, _M(FL)|_M(FR), 1, _M(MONO), 0, + MATRIX(0.707107, 0.707107)); + test_mix(12, 0, 1, _M(MONO), 0, + MATRIX(0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.083333, + 0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.0833333)); +} + +static void test_3p1_N(void) +{ + test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 1, _M(MONO), 0, + MATRIX(0.707107, 0.707107, 1.0, 0.0)); + test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 2, _M(FL)|_M(FR), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.707107, 0.0 )); + test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 3, _M(FL)|_M(FR)|_M(LFE), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.707107, 0.0, + 0.0, 0.0, 0.0, 1.0 )); + test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0,)); + test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.707107, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0,)); +} + +static void test_4_N(void) +{ + test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 1, _M(MONO), 0, + MATRIX(0.707107, 0.707107, 0.5, 0.5)); + test_mix(4, _M(FL)|_M(FR)|_M(SL)|_M(SR), 1, _M(MONO), 0, + MATRIX(0.707107, 0.707107, 0.5, 0.5)); + test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 2, _M(FL)|_M(FR), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.0, 0.707107)); + test_mix(4, _M(FL)|_M(FR)|_M(SL)|_M(SR), 2, _M(FL)|_M(FR), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.0, 0.707107)); + test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 3, _M(FL)|_M(FR)|_M(LFE), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 0.0)); + test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0)); + test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0)); + test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), CHANNELMIX_OPTION_UPMIX, + MATRIX(1.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.0, 0.707107, + 0.707107, 0.707107, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0)); +} + +static void test_5p1_N(void) +{ + test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 1, _M(MONO), 0, + MATRIX(0.707107, 0.707107, 1.0, 0.0, 0.5, 0.5)); + test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 2, _M(FL)|_M(FR), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107)); + test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RL)|_M(RR), 2, _M(FL)|_M(FR), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107)); + test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 3, _M(FL)|_M(FR)|_M(LFE), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0)); + test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.707107, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0)); + test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.707107, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)); + test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 5, _M(FL)|_M(FR)|_M(FC)|_M(SL)|_M(SR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)); + test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)); +} + +static void test_6p1_N(void) +{ + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RC)|_M(SL)|_M(SR), 1, _M(MONO), 0, + MATRIX(0.707107, 0.707107, 1.0, 0.0, 0.5, 0.5, 0.5)); + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), + 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.707107)); + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), + 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.707107)); + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RC)|_M(RL)|_M(RR), + 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.707107, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.707107, 0.0, 1.0)); + test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), + 8, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RL)|_M(RR), 0, + MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.707107, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.707107)); +} + +static void test_7p1_N(void) +{ + test_mix(8, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RL)|_M(RR), 1, _M(MONO), 0, + MATRIX(0.707107, 0.707107, 1.0, 0.0, 0.5, 0.5, 0.5, 0.5)); + test_mix(8, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RL)|_M(RR), 2, _M(FL)|_M(FR), 0, + MATRIX(1.0, 0.0, 0.707107, 0.0, 0.707107, 0.0, 0.707107, 0.0, + 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107, 0.0, 0.707107)); +} + +static void check_samples(float **s1, float **s2, uint32_t n_s, uint32_t n_samples) +{ + uint32_t i, j; + for (i = 0; i < n_s; i++) { + for (j = 0; j < n_samples; j++) { + spa_assert_se(CLOSE_ENOUGH(s1[i][j], s2[i][j])); + } + } +} + +static void run_n_m_impl(struct channelmix *mix, const void **src, uint32_t n_samples) +{ + uint32_t dst_chan = mix->dst_chan, i; + float dst_c_data[dst_chan][n_samples]; + float dst_x_data[dst_chan][n_samples]; + void *dst_c[dst_chan], *dst_x[dst_chan]; + + for (i = 0; i < dst_chan; i++) { + dst_c[i] = dst_c_data[i]; + dst_x[i] = dst_x_data[i]; + } + + channelmix_f32_n_m_c(mix, dst_c, src, n_samples); + + channelmix_f32_n_m_c(mix, dst_x, src, n_samples); + check_samples((float**)dst_c, (float**)dst_x, dst_chan, n_samples); + +#if defined(HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + channelmix_f32_n_m_sse(mix, dst_x, src, n_samples); + check_samples((float**)dst_c, (float**)dst_x, dst_chan, n_samples); + } +#endif +} + +static void test_n_m_impl(void) +{ + struct channelmix mix; + unsigned int i, j; +#define N_SAMPLES 251 + float src_data[16][N_SAMPLES], *src[16]; + + spa_log_debug(&logger.log, "start"); + + for (i = 0; i < 16; i++) { + for (j = 0; j < N_SAMPLES; j++) + src_data[i][j] = (drand48() - 0.5f) * 2.5f; + src[i] = src_data[i]; + } + + spa_zero(mix); + mix.src_chan = 16; + mix.dst_chan = 12; + mix.log = &logger.log; + mix.cpu_flags = cpu_flags; + spa_assert_se(channelmix_init(&mix) == 0); + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + + /* identity matrix */ + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); + + /* some zero destination */ + mix.matrix_orig[2][2] = 0.0f; + mix.matrix_orig[7][7] = 0.0f; + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); + + /* random matrix */ + for (i = 0; i < mix.dst_chan; i++) { + for (j = 0; j < mix.src_chan; j++) { + mix.matrix_orig[i][j] = drand48() - 0.5f; + } + } + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); +} + +int main(int argc, char *argv[]) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + srand48(SPA_TIMESPEC_TO_NSEC(&ts)); + + logger.log.level = SPA_LOG_LEVEL_TRACE; + + cpu_flags = get_cpu_flags(); + printf("got CPU flags %d\n", cpu_flags); + + test_1_N_MONO(); + test_1_N_FC(); + test_N_1(); + test_3p1_N(); + test_4_N(); + test_5p1_N(); + test_6p1_N(); + test_7p1_N(); + + test_n_m_impl(); + + return 0; +} diff --git a/spa/plugins/audioconvert/test-fmt-ops.c b/spa/plugins/audioconvert/test-fmt-ops.c new file mode 100644 index 0000000..8c8d4cd --- /dev/null +++ b/spa/plugins/audioconvert/test-fmt-ops.c @@ -0,0 +1,798 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/debug/mem.h> + +#include "test-helper.h" +#include "fmt-ops.c" + +#define N_SAMPLES 253 +#define N_CHANNELS 11 + +static uint32_t cpu_flags; + +static uint8_t samp_in[N_SAMPLES * 8]; +static uint8_t samp_out[N_SAMPLES * 8]; +static uint8_t temp_in[N_SAMPLES * N_CHANNELS * 8]; +static uint8_t temp_out[N_SAMPLES * N_CHANNELS * 8]; + +static void compare_mem(int i, int j, const void *m1, const void *m2, size_t size) +{ + int res = memcmp(m1, m2, size); + if (res != 0) { + fprintf(stderr, "%d %d %zd:\n", i, j, size); + spa_debug_mem(0, m1, size); + spa_debug_mem(0, m2, size); + } + spa_assert_se(res == 0); +} + +static void run_test(const char *name, + const void *in, size_t in_size, const void *out, size_t out_size, size_t n_samples, + bool in_packed, bool out_packed, convert_func_t func) +{ + const void *ip[N_CHANNELS]; + void *tp[N_CHANNELS]; + int i, j; + const uint8_t *in8 = in, *out8 = out; + struct convert conv; + + conv.n_channels = N_CHANNELS; + + for (j = 0; j < N_SAMPLES; j++) { + memcpy(&samp_in[j * in_size], &in8[(j % n_samples) * in_size], in_size); + memcpy(&samp_out[j * out_size], &out8[(j % n_samples) * out_size], out_size); + } + + for (j = 0; j < N_CHANNELS; j++) + ip[j] = samp_in; + + if (in_packed) { + tp[0] = temp_in; + switch(in_size) { + case 1: + conv_8d_to_8_c(&conv, tp, ip, N_SAMPLES); + break; + case 2: + conv_16d_to_16_c(&conv, tp, ip, N_SAMPLES); + break; + case 3: + conv_24d_to_24_c(&conv, tp, ip, N_SAMPLES); + break; + case 4: + conv_32d_to_32_c(&conv, tp, ip, N_SAMPLES); + break; + case 8: + conv_64d_to_64_c(&conv, tp, ip, N_SAMPLES); + break; + default: + fprintf(stderr, "unknown size %zd\n", in_size); + return; + } + ip[0] = temp_in; + } + + spa_zero(temp_out); + for (j = 0; j < N_CHANNELS; j++) + tp[j] = &temp_out[j * N_SAMPLES * out_size]; + + fprintf(stderr, "test %s:\n", name); + func(&conv, tp, ip, N_SAMPLES); + + if (out_packed) { + const uint8_t *d = tp[0], *s = samp_out; + for (i = 0; i < N_SAMPLES; i++) { + for (j = 0; j < N_CHANNELS; j++) { + compare_mem(i, j, d, s, out_size); + d += out_size; + } + s += out_size; + } + } else { + for (j = 0; j < N_CHANNELS; j++) { + compare_mem(0, j, tp[j], samp_out, N_SAMPLES * out_size); + } + } +} + +static void test_f32_s8(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/160.f, 1.0f/256.f, -1.0f/160.f, -1.0f/256.f }; + static const int8_t out[] = { 0, 127, -128, 64, 192, 127, -128, 1, 0, -1, 0 }; + + run_test("test_f32_s8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_s8_c); + run_test("test_f32d_s8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s8_c); + run_test("test_f32_s8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_f32_to_s8d_c); + run_test("test_f32d_s8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_s8d_c); +} + +static void test_s8_f32(void) +{ + static const int8_t in[] = { 0, 127, -128, 64, 192, }; + static const float out[] = { 0.0f, 0.9921875f, -1.0f, 0.5f, -0.5f, }; + + run_test("test_s8_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_s8_to_f32_c); + run_test("test_s8d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_s8d_to_f32_c); + run_test("test_s8_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s8_to_f32d_c); + run_test("test_s8d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_s8d_to_f32d_c); +} + +static void test_f32_u8(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/160.f, 1.0f/256.f, -1.0f/160.f, -1.0f/256.f }; + static const uint8_t out[] = { 128, 255, 0, 192, 64, 255, 0, 129, 128, 127, 128 }; + + run_test("test_f32_u8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_u8_c); + run_test("test_f32d_u8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_u8_c); + run_test("test_f32_u8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_f32_to_u8d_c); + run_test("test_f32d_u8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_u8d_c); +} + +static void test_u8_f32(void) +{ + static const uint8_t in[] = { 128, 255, 0, 192, 64, }; + static const float out[] = { 0.0f, 0.9921875f, -1.0f, 0.5f, -0.5f, }; + + run_test("test_u8_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_u8_to_f32_c); + run_test("test_u8d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_u8d_to_f32_c); + run_test("test_u8_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_u8_to_f32d_c); + run_test("test_u8d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_u8d_to_f32d_c); +} + +static void test_f32_u16(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/49152.f, 1.0f/65536.f, -1.0f/49152.f, -1.0f/65536.f }; + static const uint16_t out[] = { 32768, 65535, 0, 49152, 16384, 65535, 0, + 32769, 32768, 32767, 32768 }; + + run_test("test_f32_u16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_u16_c); + run_test("test_f32d_u16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_u16_c); +} + +static void test_u16_f32(void) +{ + static const uint16_t in[] = { 32768, 65535, 0, 49152, 16384, }; + static const float out[] = { 0.0f, 0.999969482422f, -1.0f, 0.5f, -0.5f }; + + run_test("test_u16_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_u16_to_f32d_c); + run_test("test_u16_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_u16_to_f32_c); +} + +static void test_f32_s16(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/49152.f, 1.0f/65536.f, -1.0f/49152.f, -1.0f/65536.f }; + static const int16_t out[] = { 0, 32767, -32768, 16384, -16384, 32767, -32768, + 1, 0, -1, 0 }; + + run_test("test_f32_s16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_s16_c); + run_test("test_f32d_s16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s16_c); + run_test("test_f32_s16d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_f32_to_s16d_c); + run_test("test_f32d_s16d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_s16d_c); +#if defined(HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f32_s16_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_s16_sse2); + run_test("test_f32d_s16_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s16_sse2); + run_test("test_f32d_s16d_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_s16d_sse2); + } +#endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_f32d_s16_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s16_avx2); + } +#endif +#if defined(HAVE_NEON) + if (cpu_flags & SPA_CPU_FLAG_NEON) { + run_test("test_f32d_s16_neon", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s16_neon); + } +#endif +} + +static void test_s16_f32(void) +{ + static const int16_t in[] = { 0, 32767, -32768, 16384, -16384, }; + static const float out[] = { 0.0f, 0.999969482422f, -1.0f, 0.5f, -0.5f }; + + run_test("test_s16_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s16_to_f32d_c); + run_test("test_s16d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_s16d_to_f32_c); + run_test("test_s16_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_s16_to_f32_c); + run_test("test_s16d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_s16d_to_f32d_c); +#if defined(HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_s16_f32d_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s16_to_f32d_sse2); + } +#endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s16_f32d_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s16_to_f32d_avx2); + } +#endif +#if defined(HAVE_NEON) + if (cpu_flags & SPA_CPU_FLAG_NEON) { + run_test("test_s16_f32d_neon", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s16_to_f32d_neon); + } +#endif +} + +static void test_f32_u32(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const uint32_t out[] = { 0x80000000, 0xffffff00, 0x0, 0xc0000000, 0x40000000, + 0xffffff00, 0x0, + 0x80000100, 0x80000000, 0x7fffff00, 0x80000000 }; + + run_test("test_f32_u32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_u32_c); + run_test("test_f32d_u32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_u32_c); +} + +static void test_u32_f32(void) +{ + static const uint32_t in[] = { 0x80000000, 0xffffff00, 0x0, 0xc0000000, 0x40000000 }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; + + run_test("test_u32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_u32_to_f32d_c); + run_test("test_u32_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_u32_to_f32_c); +} + +static void test_f32_s32(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const int32_t out[] = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000, + 0x7fffff00, 0x80000000, + 0x00000100, 0x00000000, 0xffffff00, 0x00000000 }; + + run_test("test_f32_s32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_s32_c); + run_test("test_f32d_s32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s32_c); + run_test("test_f32_s32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_f32_to_s32d_c); + run_test("test_f32d_s32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_s32d_c); +#if defined(HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f32d_s32_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s32_sse2); + } +#endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_f32d_s32_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s32_avx2); + } +#endif +} + +static void test_s32_f32(void) +{ + static const int32_t in[] = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000 }; + static const float out[] = { 0.0f, 0.999999880791, -1.0f, 0.5, -0.5, }; + + run_test("test_s32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s32_to_f32d_c); + run_test("test_s32d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_s32d_to_f32_c); + run_test("test_s32_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_s32_to_f32_c); + run_test("test_s32d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_s32d_to_f32d_c); +#if defined(HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_s32_f32d_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s32_to_f32d_sse2); + } +#endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s32_f32d_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s32_to_f32d_avx2); + } +#endif +} + +static void test_f32_u24(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const uint24_t out[] = { U32_TO_U24(0x00800000), U32_TO_U24(0xffffff), + U32_TO_U24(0x000000), U32_TO_U24(0xc00000), U32_TO_U24(0x400000), + U32_TO_U24(0xffffff), U32_TO_U24(0x000000), + U32_TO_U24(0x800001), U32_TO_U24(0x800000), U32_TO_U24(0x7fffff), + U32_TO_U24(0x800000) }; + + run_test("test_f32_u24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), + true, true, conv_f32_to_u24_c); + run_test("test_f32d_u24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), + false, true, conv_f32d_to_u24_c); +} + +static void test_u24_f32(void) +{ + static const uint24_t in[] = { U32_TO_U24(0x00800000), U32_TO_U24(0xffffff), + U32_TO_U24(0x000000), U32_TO_U24(0xc00000), U32_TO_U24(0x400000) }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5, -0.5, }; + + run_test("test_u24_f32d", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_u24_to_f32d_c); + run_test("test_u24_f32", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_u24_to_f32_c); +} + +static void test_f32_s24(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const int24_t out[] = { S32_TO_S24(0), S32_TO_S24(0x7fffff), + S32_TO_S24(0xff800000), S32_TO_S24(0x400000), S32_TO_S24(0xc00000), + S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), + S32_TO_S24(0x000001), S32_TO_S24(0x000000), S32_TO_S24(0xffffffff), + S32_TO_S24(0x000000) }; + + run_test("test_f32_s24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), + true, true, conv_f32_to_s24_c); + run_test("test_f32d_s24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), + false, true, conv_f32d_to_s24_c); + run_test("test_f32_s24d", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), + true, false, conv_f32_to_s24d_c); + run_test("test_f32d_s24d", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), + false, false, conv_f32d_to_s24d_c); +} + +static void test_s24_f32(void) +{ + static const int24_t in[] = { S32_TO_S24(0), S32_TO_S24(0x7fffff), + S32_TO_S24(0xff800000), S32_TO_S24(0x400000), S32_TO_S24(0xc00000) }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; + + run_test("test_s24_f32d", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s24_to_f32d_c); + run_test("test_s24d_f32", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_s24d_to_f32_c); + run_test("test_s24_f32", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_s24_to_f32_c); + run_test("test_s24d_f32d", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_s24d_to_f32d_c); +#if defined(HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_s24_f32d_sse2", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s24_to_f32d_sse2); + } +#endif +#if defined(HAVE_SSSE3) + if (cpu_flags & SPA_CPU_FLAG_SSSE3) { + run_test("test_s24_f32d_ssse3", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s24_to_f32d_ssse3); + } +#endif +#if defined(HAVE_SSE41) + if (cpu_flags & SPA_CPU_FLAG_SSE41) { + run_test("test_s24_f32d_sse41", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s24_to_f32d_sse41); + } +#endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s24_f32d_avx2", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s24_to_f32d_avx2); + } +#endif +} + +static void test_f32_u24_32(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const uint32_t out[] = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000, + 0xffffff, 0x000000, + 0x800001, 0x800000, 0x7fffff, 0x800000 }; + + run_test("test_f32_u24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_u24_32_c); + run_test("test_f32d_u24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_u24_32_c); +} + +static void test_u24_32_f32(void) +{ + static const uint32_t in[] = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000, 0x11000000 }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, -1.0f }; + + run_test("test_u24_32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_u24_32_to_f32d_c); + run_test("test_u24_32_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_u24_32_to_f32_c); +} + +static void test_f32_s24_32(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const int32_t out[] = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000, + 0x7fffff, 0xff800000, + 0x000001, 0x000000, 0xffffffff, 0x000000 }; + + run_test("test_f32_s24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_s24_32_c); + run_test("test_f32d_s24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s24_32_c); + run_test("test_f32_s24_32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_f32_to_s24_32d_c); + run_test("test_f32d_s24_32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_s24_32d_c); +} + +static void test_s24_32_f32(void) +{ + static const int32_t in[] = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000, 0x66800000 }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, -1.0f }; + + run_test("test_s24_32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s24_32_to_f32d_c); + run_test("test_s24_32d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_s24_32d_to_f32_c); + run_test("test_s24_32_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_s24_32_to_f32_c); + run_test("test_s24_32d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_s24_32d_to_f32d_c); +} + +static void test_f64_f32(void) +{ + static const double in[] = { 0.0, 1.0, -1.0, 0.5, -0.5, }; + static const float out[] = { 0.0, 1.0, -1.0, 0.5, -0.5, }; + + run_test("test_f64_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_f64_to_f32d_c); + run_test("test_f64d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f64d_to_f32_c); + run_test("test_f64_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f64_to_f32_c); + run_test("test_f64d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f64d_to_f32d_c); +} + +static void test_f32_f64(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; + static const double out[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; + + run_test("test_f32_f64", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_f64_c); + run_test("test_f32d_f64", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_f64_c); + run_test("test_f32_f64d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_f32_to_f64d_c); + run_test("test_f32d_f64d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_f64d_c); +} + +static void test_lossless_s8(void) +{ + int8_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S8_MIN; i < S8_MAX; i+=1) { + float v = S8_TO_F32(i); + int8_t t = F32_TO_S8(v); + spa_assert_se(i == t); + } +} + +static void test_lossless_u8(void) +{ + uint8_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = U8_MIN; i < U8_MAX; i+=1) { + float v = U8_TO_F32(i); + uint8_t t = F32_TO_U8(v); + spa_assert_se(i == t); + } +} +static void test_lossless_s16(void) +{ + int16_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S16_MIN; i < S16_MAX; i+=3) { + float v = S16_TO_F32(i); + int16_t t = F32_TO_S16(v); + spa_assert_se(i == t); + } +} + +static void test_lossless_u16(void) +{ + uint32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = U16_MIN; i < U16_MAX; i+=3) { + float v = U16_TO_F32(i); + uint16_t t = F32_TO_U16(v); + spa_assert_se(i == t); + } +} + +static void test_lossless_s24(void) +{ + int32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S24_MIN; i < S24_MAX; i+=13) { + float v = S24_TO_F32(s32_to_s24(i)); + int32_t t = s24_to_s32(F32_TO_S24(v)); + spa_assert_se(i == t); + } +} + +static void test_lossless_u24(void) +{ + uint32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = U24_MIN; i < U24_MAX; i+=11) { + float v = U24_TO_F32(u32_to_u24(i)); + uint32_t t = u24_to_u32(F32_TO_U24(v)); + spa_assert_se(i == t); + } +} + +static void test_lossless_s32(void) +{ + int32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S32_MIN; i < S32_MAX; i+=255) { + float v = S32_TO_F32(i); + int32_t t = F32_TO_S32(v); + spa_assert_se(SPA_ABS(i - t) <= 256); + } +} + +static void test_lossless_u32(void) +{ + uint32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = U32_MIN; i < U32_MAX; i+=255) { + float v = U32_TO_F32(i); + uint32_t t = F32_TO_U32(v); + spa_assert_se(i > t ? (i - t) <= 256 : (t - i) <= 256); + } +} + +static void test_swaps(void) +{ + { + uint24_t v = U32_TO_U24(0x123456); + uint24_t t = U32_TO_U24(0x563412); + uint24_t s = bswap_u24(v); + spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); + } + { + int24_t v = S32_TO_S24(0xfffe1dc0); + int24_t t = S32_TO_S24(0xffc01dfe); + int24_t s = bswap_s24(v); + spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); + } + { + int24_t v = S32_TO_S24(0x123456); + int24_t t = S32_TO_S24(0x563412); + int24_t s = bswap_s24(v); + spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); + } +} + +static void run_test_noise(uint32_t fmt, uint32_t noise, uint32_t flags) +{ + struct convert conv; + const void *ip[N_CHANNELS]; + void *op[N_CHANNELS]; + uint32_t i, range; + bool all_zero; + + spa_zero(conv); + + conv.noise_bits = noise; + conv.src_fmt = SPA_AUDIO_FORMAT_F32P; + conv.dst_fmt = fmt; + conv.n_channels = 2; + conv.rate = 44100; + conv.cpu_flags = flags; + spa_assert_se(convert_init(&conv) == 0); + fprintf(stderr, "test noise %s:\n", conv.func_name); + + memset(samp_in, 0, sizeof(samp_in)); + for (i = 0; i < conv.n_channels; i++) { + ip[i] = samp_in; + op[i] = samp_out; + } + convert_process(&conv, op, ip, N_SAMPLES); + + range = 1 << conv.noise_bits; + + all_zero = true; + for (i = 0; i < conv.n_channels * N_SAMPLES; i++) { + switch (fmt) { + case SPA_AUDIO_FORMAT_S8: + { + int8_t *d = (int8_t *)samp_out; + if (d[i] != 0) + all_zero = false; + spa_assert_se(SPA_ABS(d[i] - 0) <= (int8_t)range); + break; + } + case SPA_AUDIO_FORMAT_U8: + { + uint8_t *d = (uint8_t *)samp_out; + if (d[i] != 0x80) + all_zero = false; + spa_assert_se((int8_t)SPA_ABS(d[i] - 0x80) <= (int8_t)(range<<1)); + break; + } + case SPA_AUDIO_FORMAT_S16: + { + int16_t *d = (int16_t *)samp_out; + if (d[i] != 0) + all_zero = false; + spa_assert_se(SPA_ABS(d[i] - 0) <= (int16_t)range); + break; + } + case SPA_AUDIO_FORMAT_S24: + { + int24_t *d = (int24_t *)samp_out; + int32_t t = s24_to_s32(d[i]); + if (t != 0) + all_zero = false; + spa_assert_se(SPA_ABS(t - 0) <= (int32_t)range); + break; + } + case SPA_AUDIO_FORMAT_S32: + { + int32_t *d = (int32_t *)samp_out; + if (d[i] != 0) + all_zero = false; + spa_assert_se(SPA_ABS(d[i] - 0) <= (int32_t)(range << 8)); + break; + } + default: + spa_assert_not_reached(); + break; + } + } + spa_assert_se(all_zero == false); + convert_free(&conv); +} + +static void test_noise(void) +{ + run_test_noise(SPA_AUDIO_FORMAT_S8, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S8, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_U8, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_U8, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S16, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S16, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S24, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S24, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S32, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S32, 2, 0); +} + +int main(int argc, char *argv[]) +{ + cpu_flags = get_cpu_flags(); + printf("got CPU flags %d\n", cpu_flags); + + test_f32_s8(); + test_s8_f32(); + test_f32_u8(); + test_u8_f32(); + test_f32_u16(); + test_u16_f32(); + test_f32_s16(); + test_s16_f32(); + test_f32_u32(); + test_u32_f32(); + test_f32_s32(); + test_s32_f32(); + test_f32_u24(); + test_u24_f32(); + test_f32_s24(); + test_s24_f32(); + test_f32_u24_32(); + test_u24_32_f32(); + test_f32_s24_32(); + test_s24_32_f32(); + test_f32_f64(); + test_f64_f32(); + + test_lossless_s8(); + test_lossless_u8(); + test_lossless_s16(); + test_lossless_u16(); + test_lossless_s24(); + test_lossless_u24(); + test_lossless_s32(); + test_lossless_u32(); + + test_swaps(); + + test_noise(); + + return 0; +} diff --git a/spa/plugins/audioconvert/test-helper.h b/spa/plugins/audioconvert/test-helper.h new file mode 100644 index 0000000..8c789bd --- /dev/null +++ b/spa/plugins/audioconvert/test-helper.h @@ -0,0 +1,97 @@ +#include <dlfcn.h> + +#include <spa/support/plugin.h> +#include <spa/utils/type.h> +#include <spa/utils/result.h> +#include <spa/support/cpu.h> +#include <spa/utils/names.h> + +static inline const struct spa_handle_factory *get_factory(spa_handle_factory_enum_func_t enum_func, + const char *name, uint32_t version) +{ + uint32_t i; + int res; + const struct spa_handle_factory *factory; + + for (i = 0;;) { + if ((res = enum_func(&factory, &i)) <= 0) { + if (res < 0) + errno = -res; + break; + } + if (factory->version >= version && + !strcmp(factory->name, name)) + return factory; + } + return NULL; +} + +static inline struct spa_handle *load_handle(const struct spa_support *support, + uint32_t n_support, const char *lib, const char *name) +{ + int res, len; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + const struct spa_handle_factory *factory; + struct spa_handle *handle; + const char *str; + char *path; + + if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) + str = PLUGINDIR; + + len = strlen(str) + strlen(lib) + 2; + path = alloca(len); + snprintf(path, len, "%s/%s", str, lib); + + if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { + fprintf(stderr, "can't load %s: %s\n", lib, dlerror()); + res = -ENOENT; + goto error; + } + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + fprintf(stderr, "can't find enum function\n"); + res = -ENXIO; + goto error_close; + } + + if ((factory = get_factory(enum_func, name, SPA_VERSION_HANDLE_FACTORY)) == NULL) { + fprintf(stderr, "can't find factory\n"); + res = -ENOENT; + goto error_close; + } + handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); + if ((res = spa_handle_factory_init(factory, handle, + NULL, support, n_support)) < 0) { + fprintf(stderr, "can't make factory instance: %d\n", res); + goto error_close; + } + return handle; + +error_close: + dlclose(hnd); +error: + errno = -res; + return NULL; +} + +static inline uint32_t get_cpu_flags(void) +{ + struct spa_handle *handle; + uint32_t flags; + void *iface; + int res; + + handle = load_handle(NULL, 0, "support/libspa-support.so", SPA_NAME_SUPPORT_CPU); + if (handle == NULL) + return 0; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_CPU, &iface)) < 0) { + fprintf(stderr, "can't get CPU interface %s\n", spa_strerror(res)); + return 0; + } + flags = spa_cpu_get_flags((struct spa_cpu*)iface); + + free(handle); + + return flags; +} diff --git a/spa/plugins/audioconvert/test-peaks.c b/spa/plugins/audioconvert/test-peaks.c new file mode 100644 index 0000000..3f7d093 --- /dev/null +++ b/spa/plugins/audioconvert/test-peaks.c @@ -0,0 +1,128 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/support/log-impl.h> +#include <spa/debug/mem.h> + +SPA_LOG_IMPL(logger); + +static uint32_t cpu_flags; + +#include "test-helper.h" + +#include "peaks-ops.c" + +static void test_impl(void) +{ + struct peaks peaks; + unsigned int i; + float vals[1038]; + float min[2] = { 0.0f, 0.0f }, max[2] = { 0.0f, 0.0f }, absmax[2] = { 0.0f, 0.0f }; + + for (i = 0; i < SPA_N_ELEMENTS(vals); i++) + vals[i] = (drand48() - 0.5f) * 2.5f; + + peaks_min_max_c(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, &min[0], &max[0]); + printf("c peaks min:%f max:%f\n", min[0], max[0]); + + absmax[0] = peaks_abs_max_c(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, 0.0f); + printf("c peaks abs-max:%f\n", absmax[0]); + +#if defined(HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + peaks_min_max_sse(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, &min[1], &max[1]); + printf("sse peaks min:%f max:%f\n", min[1], max[1]); + + absmax[1] = peaks_abs_max_sse(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, 0.0f); + printf("sse peaks abs-max:%f\n", absmax[1]); + + spa_assert(min[0] == min[1]); + spa_assert(max[0] == max[1]); + spa_assert(absmax[0] == absmax[1]); + } +#endif + +} + +static void test_min_max(void) +{ + struct peaks peaks; + const float vals[] = { 0.0f, 0.5f, -0.5f, 0.0f, 0.6f, -0.8f, -0.5f, 0.0f }; + float min = 0.0f, max = 0.0f; + + spa_zero(peaks); + peaks.log = &logger.log; + peaks.cpu_flags = cpu_flags; + peaks_init(&peaks); + + peaks_min_max(&peaks, vals, SPA_N_ELEMENTS(vals), &min, &max); + + spa_assert(min == -0.8f); + spa_assert(max == 0.6f); +} + +static void test_abs_max(void) +{ + struct peaks peaks; + const float vals[] = { 0.0f, 0.5f, -0.5f, 0.0f, 0.6f, -0.8f, -0.5f, 0.0f }; + float max = 0.0f; + + spa_zero(peaks); + peaks.log = &logger.log; + peaks.cpu_flags = cpu_flags; + peaks_init(&peaks); + + max = peaks_abs_max(&peaks, vals, SPA_N_ELEMENTS(vals), max); + + spa_assert(max == 0.8f); +} + +int main(int argc, char *argv[]) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + srand48(SPA_TIMESPEC_TO_NSEC(&ts)); + + logger.log.level = SPA_LOG_LEVEL_TRACE; + + cpu_flags = get_cpu_flags(); + printf("got CPU flags %d\n", cpu_flags); + + test_impl(); + + test_min_max(); + test_abs_max(); + + return 0; +} diff --git a/spa/plugins/audioconvert/test-resample.c b/spa/plugins/audioconvert/test-resample.c new file mode 100644 index 0000000..4f11f53 --- /dev/null +++ b/spa/plugins/audioconvert/test-resample.c @@ -0,0 +1,177 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/support/log-impl.h> +#include <spa/debug/mem.h> + +SPA_LOG_IMPL(logger); + +#include "resample.h" + +#define N_SAMPLES 253 +#define N_CHANNELS 11 + +static float samp_in[N_SAMPLES * 4]; +static float samp_out[N_SAMPLES * 4]; + +static void feed_1(struct resample *r) +{ + uint32_t i; + const void *src[1]; + void *dst[1]; + + spa_zero(samp_out); + src[0] = samp_in; + dst[0] = samp_out; + + for (i = 0; i < 500; i++) { + uint32_t in, out; + + in = out = 1; + samp_in[0] = i; + resample_process(r, src, &in, dst, &out); + fprintf(stderr, "%d %d %f %d\n", i, in, samp_out[0], out); + } +} + +static void test_native(void) +{ + struct resample r; + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 44100; + r.o_rate = 44100; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + + feed_1(&r); + resample_free(&r); + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 44100; + r.o_rate = 48000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + + feed_1(&r); + resample_free(&r); +} + +static void pull_blocks(struct resample *r, uint32_t first, uint32_t size) +{ + uint32_t i; + float in[SPA_MAX(size, first) * 2]; + float out[SPA_MAX(size, first) * 2]; + const void *src[1]; + void *dst[1]; + uint32_t in_len, out_len; + uint32_t pin_len, pout_len; + + src[0] = in; + dst[0] = out; + + for (i = 0; i < 500; i++) { + pout_len = out_len = i == 0 ? first : size; + pin_len = in_len = resample_in_len(r, out_len); + + resample_process(r, src, &pin_len, dst, &pout_len); + + fprintf(stderr, "%d: %d %d %d %d %d\n", i, + in_len, pin_len, out_len, pout_len, + resample_in_len(r, size)); + + spa_assert_se(in_len == pin_len); + spa_assert_se(out_len == pout_len); + } +} + +static void test_in_len(void) +{ + struct resample r; + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 32000; + r.o_rate = 48000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + + pull_blocks(&r, 1024, 1024); + resample_free(&r); + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 44100; + r.o_rate = 48000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + + pull_blocks(&r, 1024, 1024); + resample_free(&r); + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 48000; + r.o_rate = 44100; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + + pull_blocks(&r, 1024, 1024); + resample_free(&r); + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 44100; + r.o_rate = 48000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + + pull_blocks(&r, 513, 64); + resample_free(&r); +} + +int main(int argc, char *argv[]) +{ + logger.log.level = SPA_LOG_LEVEL_TRACE; + + test_native(); + test_in_len(); + + return 0; +} diff --git a/spa/plugins/audioconvert/test-source.c b/spa/plugins/audioconvert/test-source.c new file mode 100644 index 0000000..b352323 --- /dev/null +++ b/spa/plugins/audioconvert/test-source.c @@ -0,0 +1,931 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <string.h> +#include <stdio.h> + +#include <spa/support/plugin.h> +#include <spa/support/log.h> +#include <spa/support/cpu.h> +#include <spa/utils/list.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/node/node.h> +#include <spa/node/io.h> +#include <spa/node/utils.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/param.h> +#include <spa/pod/filter.h> +#include <spa/debug/types.h> + +#define NAME "test-source" + +#define DEFAULT_RATE 44100 +#define DEFAULT_CHANNELS 2 + +#define MAX_BUFFERS 32 + +struct impl; + +#define DEFAULT_MUTE false +#define DEFAULT_VOLUME 1.0f + +struct props { + float volume; + bool mute; +}; + +static void props_reset(struct props *props) +{ + props->mute = DEFAULT_MUTE; + props->volume = DEFAULT_VOLUME; +} + +struct buffer { + uint32_t id; +#define BUFFER_FLAG_OUT (1 << 0) + uint32_t flags; + struct spa_list link; + struct spa_buffer *outbuf; + struct spa_meta_header *h; +}; + +struct port { + uint32_t direction; + uint32_t id; + + uint64_t info_all; + struct spa_port_info info; + struct spa_param_info params[8]; + + struct spa_io_buffers *io; + + struct spa_audio_info format; + uint32_t stride; + uint32_t blocks; + uint32_t size; + unsigned int have_format:1; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + + struct spa_list queue; +}; + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + + uint32_t quantum_limit; + + struct spa_hook_list hooks; + + uint64_t info_all; + struct spa_node_info info; + struct props props; + struct spa_param_info params[8]; + + struct spa_io_clock *clock; + struct spa_io_position *position; + + struct port out_port; + + unsigned int started:1; +}; + +#define CHECK_PORT(this,d,id) (d == SPA_DIRECTION_OUTPUT && id == 0) +#define GET_OUT_PORT(this,id) (&this->out_port) +#define GET_PORT(this,d,id) (d == SPA_DIRECTION_OUTPUT ? GET_OUT_PORT(this,id) : NULL) + +static void emit_info(struct impl *this, bool full) +{ + uint64_t old = full ? this->info.change_mask : 0; + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + spa_node_emit_info(&this->hooks, &this->info); + this->info.change_mask = old; + } +} + +static void emit_port_info(struct impl *this, struct port *port, bool full) +{ + uint64_t old = full ? port->info.change_mask : 0; + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + spa_node_emit_port_info(&this->hooks, + port->direction, port->id, &port->info); + port->info.change_mask = old; + } +} + +static int +impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct impl *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_trace(this->log, NAME" %p: add listener %p", this, listener); + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_info(this, true); + emit_port_info(this, GET_OUT_PORT(this, 0), true); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *user_data) +{ + return 0; +} + +static int impl_node_sync(void *object, int seq) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); + + return 0; +} + +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_PropInfo: + { + struct props *p = &this->props; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), + SPA_PROP_INFO_description, SPA_POD_String("Volume"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0)); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), + SPA_PROP_INFO_description, SPA_POD_String("Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mute)); + break; + default: + return 0; + } + break; + } + case SPA_PARAM_Props: + { + struct props *p = &this->props; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_volume, SPA_POD_Float(p->volume), + SPA_PROP_mute, SPA_POD_Bool(p->mute)); + break; + default: + return 0; + } + break; + } + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int apply_props(struct impl *this, const struct spa_pod *param) +{ + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct props *p = &this->props; + int changed = 0; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_volume: + if (spa_pod_get_float(&prop->value, &p->volume) == 0) + changed++; + break; + case SPA_PROP_mute: + if (spa_pod_get_bool(&prop->value, &p->mute) == 0) + changed++; + break; + default: + break; + } + } + return changed; +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_Props: + if (param == NULL) { + props_reset(&this->props); + return 0; + } + if (apply_props(this, param) > 0) { + this->info.change_mask = SPA_NODE_CHANGE_MASK_PARAMS; + this->params[1].flags ^= SPA_PARAM_INFO_SERIAL; + emit_info(this, false); + } + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Clock: + this->clock = data; + break; + case SPA_IO_Position: + this->position = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct impl *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + port = GET_OUT_PORT(this, 0); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + + if (!port->have_format) + return -EIO; + if (port->n_buffers == 0) + return -EIO; + + this->started = true; + break; + case SPA_NODE_COMMAND_Pause: + this->started = false; + break; + default: + return -ENOTSUP; + } + return 0; +} + +static int impl_node_add_port(void *object, + enum spa_direction direction, uint32_t port_id, + const struct spa_dict *props) +{ + return -ENOTSUP; +} + +static int impl_node_remove_port(void *object, + enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int port_enum_formats(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t index, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + switch (index) { + case 0: + *param = spa_pod_builder_add_object(builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(DEFAULT_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(DEFAULT_CHANNELS, 1, INT32_MAX)); + break; + case 1: + *param = spa_pod_builder_add_object(builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S32), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(DEFAULT_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(DEFAULT_CHANNELS, 1, INT32_MAX)); + break; + default: + return 0; + } + return 1; +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct port *port; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_result_node_params result; + uint32_t count = 0; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumFormat: + if ((res = port_enum_formats(this, direction, port_id, + result.index, ¶m, &b)) <= 0) + return res; + break; + + case SPA_PARAM_Format: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); + break; + + case SPA_PARAM_Buffers: + { + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + this->quantum_limit * port->stride, + 16 * port->stride, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); + break; + } + case SPA_PARAM_Meta: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); + break; + default: + return 0; + } + break; + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int clear_buffers(struct impl *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_log_debug(this->log, NAME " %p: clear buffers %p", this, port); + port->n_buffers = 0; + spa_list_init(&port->queue); + } + return 0; +} + +static int calc_width(struct spa_audio_info *info) +{ + switch (info->info.raw.format) { + case SPA_AUDIO_FORMAT_U8P: + case SPA_AUDIO_FORMAT_U8: + return 1; + case SPA_AUDIO_FORMAT_S16P: + case SPA_AUDIO_FORMAT_S16: + case SPA_AUDIO_FORMAT_S16_OE: + return 2; + case SPA_AUDIO_FORMAT_S24P: + case SPA_AUDIO_FORMAT_S24: + case SPA_AUDIO_FORMAT_S24_OE: + return 3; + case SPA_AUDIO_FORMAT_S32P: + case SPA_AUDIO_FORMAT_S32: + case SPA_AUDIO_FORMAT_S32_OE: + return 4; + default: + return 0; + } +} + +static int port_set_format(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *format) +{ + struct impl *this = object; + struct port *port; + int res; + + port = GET_PORT(this, direction, port_id); + + if (format == NULL) { + if (port->have_format) { + port->have_format = false; + clear_buffers(this, port); + } + } else { + struct spa_audio_info info = { 0 }; + + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return res; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if ((res = spa_format_audio_raw_parse(format, &info.info.raw)) < 0) + return res; + + port->stride = calc_width(&info); + if (port->stride == 0) + return -EINVAL; + if (info.info.raw.rate == 0 || + info.info.raw.channels == 0) + return -EINVAL; + + if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { + port->blocks = info.info.raw.channels; + } + else { + port->stride *= info.info.raw.channels; + port->blocks = 1; + } + port->have_format = true; + port->format = info; + + spa_log_debug(this->log, NAME " %p: set format on port %d %d", this, port_id, res); + } + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + } else { + port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); + + return 0; +} + +static int +impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + int res; + + spa_return_val_if_fail(object != NULL, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(object, direction, port_id), -EINVAL); + + spa_log_debug(this->log, NAME" %p: set param %d", this, id); + + switch (id) { + case SPA_PARAM_Format: + res = port_set_format(object, direction, port_id, flags, param); + break; + default: + res = -ENOENT; + } + return res; +} + +static void recycle_buffer(struct impl *this, struct port *port, struct buffer *b) +{ + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { + spa_list_append(&port->queue, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); + spa_log_trace_fp(this->log, NAME " %p: recycle buffer %d", this, b->id); + } +} + +static int +impl_node_port_use_buffers(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct impl *this = object; + struct port *port; + uint32_t i, j, size = SPA_ID_INVALID; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_return_val_if_fail(port->have_format, -EIO); + + spa_log_debug(this->log, NAME " %p: use buffers %d on port %d", this, n_buffers, port_id); + + clear_buffers(this, port); + + for (i = 0; i < n_buffers; i++) { + struct buffer *b; + uint32_t n_datas = buffers[i]->n_datas; + struct spa_data *d = buffers[i]->datas; + + b = &port->buffers[i]; + b->id = i; + b->flags = BUFFER_FLAG_OUT; + b->outbuf = buffers[i]; + b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); + + for (j = 0; j < n_datas; j++) { + if (size == SPA_ID_INVALID) + size = d[j].maxsize; + else if (size != d[j].maxsize) + return -EINVAL; + + if (d[j].data == NULL) { + spa_log_error(this->log, NAME " %p: invalid memory on buffer %p", this, + buffers[i]); + return -EINVAL; + } + if (!SPA_IS_ALIGNED(d[j].data, 16)) { + spa_log_warn(this->log, NAME " %p: memory %d on buffer %d not aligned", + this, j, i); + } + } + recycle_buffer(this, port, b); + } + port->n_buffers = n_buffers; + port->size = size; + + return 0; +} + +static int +impl_node_port_set_io(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, void *data, size_t size) +{ + struct impl *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static struct buffer *dequeue_buffer(struct impl *this, struct port *port) +{ + struct buffer *b; + + if (spa_list_is_empty(&port->queue)) + return NULL; + + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + + return b; +} + + +static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + struct impl *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); + + port = GET_PORT(this, SPA_DIRECTION_OUTPUT, port_id); + if (buffer_id < port->n_buffers) + recycle_buffer(this, port, &port->buffers[buffer_id]); + + return 0; +} + +static int impl_node_process(void *object) +{ + struct impl *this = object; + struct port *port; + struct spa_io_buffers *io; + struct buffer *buf; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = GET_OUT_PORT(this, 0); + if ((io = port->io) == NULL) + return -EIO; + + spa_log_trace_fp(this->log, NAME " %p: status %d", this, io->status); + + if (io->status == SPA_STATUS_HAVE_DATA) + goto done; + + /* recycle */ + if (io->buffer_id < port->n_buffers) { + recycle_buffer(this, port, &port->buffers[io->buffer_id]); + io->buffer_id = SPA_ID_INVALID; + } + + if ((buf = dequeue_buffer(this, port)) == NULL) + return io->status = -EPIPE; + + io->status = SPA_STATUS_HAVE_DATA; + io->buffer_id = buf->id; + + done: + return SPA_STATUS_HAVE_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .sync = impl_node_sync, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .add_port = impl_node_add_port, + .remove_port = impl_node_remove_port, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .port_reuse_buffer = impl_node_port_reuse_buffer, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + if (spa_streq(k, "clock.quantum-limit")) + spa_atou32(s, &this->quantum_limit, 0); + } + + spa_log_debug(this->log, NAME " %p: init", this); + spa_hook_list_init(&this->hooks); + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_output_ports = 1; + this->info.flags = SPA_NODE_FLAG_RT; + this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = 2; + props_reset(&this->props); + + port = GET_OUT_PORT(this, 0); + port->direction = SPA_DIRECTION_OUTPUT; + port->id = 0; + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; + port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->info.params = port->params; + port->info.n_params = 5; + spa_list_init(&port->queue); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +const struct spa_handle_factory test_source_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_AUDIO_PROCESS_CHANNELMIX, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/audioconvert/volume-ops-c.c b/spa/plugins/audioconvert/volume-ops-c.c new file mode 100644 index 0000000..0cc6f5f --- /dev/null +++ b/spa/plugins/audioconvert/volume-ops-c.c @@ -0,0 +1,45 @@ +/* Spa + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "volume-ops.h" + +void +volume_f32_c(struct volume *vol, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, float volume, uint32_t n_samples) +{ + uint32_t n; + float *d = (float*)dst; + const float *s = (const float*)src; + + if (volume == VOLUME_MIN) { + memset(d, 0, n_samples * sizeof(float)); + } + else if (volume == VOLUME_NORM) { + spa_memcpy(d, s, n_samples * sizeof(float)); + } + else { + for (n = 0; n < n_samples; n++) + d[n] = s[n] * volume; + } +} diff --git a/spa/plugins/audioconvert/volume-ops-sse.c b/spa/plugins/audioconvert/volume-ops-sse.c new file mode 100644 index 0000000..cd1f3cc --- /dev/null +++ b/spa/plugins/audioconvert/volume-ops-sse.c @@ -0,0 +1,66 @@ +/* Spa + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "volume-ops.h" + +#include <xmmintrin.h> + +void +volume_f32_sse(struct volume *vol, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, float volume, uint32_t n_samples) +{ + uint32_t n, unrolled; + float *d = (float*)dst; + const float *s = (const float*)src; + + if (volume == VOLUME_MIN) { + memset(d, 0, n_samples * sizeof(float)); + } + else if (volume == VOLUME_NORM) { + spa_memcpy(d, s, n_samples * sizeof(float)); + } + else { + __m128 t[4]; + const __m128 vol = _mm_set1_ps(volume); + + if (SPA_IS_ALIGNED(d, 16) && + SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~15; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 16) { + t[0] = _mm_load_ps(&s[n]); + t[1] = _mm_load_ps(&s[n+4]); + t[2] = _mm_load_ps(&s[n+8]); + t[3] = _mm_load_ps(&s[n+12]); + _mm_store_ps(&d[n], _mm_mul_ps(t[0], vol)); + _mm_store_ps(&d[n+4], _mm_mul_ps(t[1], vol)); + _mm_store_ps(&d[n+8], _mm_mul_ps(t[2], vol)); + _mm_store_ps(&d[n+12], _mm_mul_ps(t[3], vol)); + } + for(; n < n_samples; n++) + _mm_store_ss(&d[n], _mm_mul_ss(_mm_load_ss(&s[n]), vol)); + } +} diff --git a/spa/plugins/audioconvert/volume-ops.c b/spa/plugins/audioconvert/volume-ops.c new file mode 100644 index 0000000..6890cfa --- /dev/null +++ b/spa/plugins/audioconvert/volume-ops.c @@ -0,0 +1,84 @@ +/* Spa + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <spa/param/audio/format-utils.h> +#include <spa/support/cpu.h> +#include <spa/support/log.h> +#include <spa/utils/defs.h> + +#include "volume-ops.h" + +typedef void (*volume_func_t) (struct volume *vol, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, float volume, uint32_t n_samples); + +#define MAKE(func,...) \ + { func, #func , __VA_ARGS__ } + +static const struct volume_info { + volume_func_t process; + const char *name; + uint32_t cpu_flags; +} volume_table[] = +{ +#if defined (HAVE_SSE) + MAKE(volume_f32_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(volume_f32_c), +}; +#undef MAKE + +#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) + +static const struct volume_info *find_volume_info(uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(volume_table, t) { + if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) + return t; + } + return NULL; +} + +static void impl_volume_free(struct volume *vol) +{ + vol->process = NULL; +} + +int volume_init(struct volume *vol) +{ + const struct volume_info *info; + + info = find_volume_info(vol->cpu_flags); + if (info == NULL) + return -ENOTSUP; + + vol->cpu_flags = info->cpu_flags; + vol->func_name = info->name; + vol->free = impl_volume_free; + vol->process = info->process; + return 0; +} diff --git a/spa/plugins/audioconvert/volume-ops.h b/spa/plugins/audioconvert/volume-ops.h new file mode 100644 index 0000000..0825712 --- /dev/null +++ b/spa/plugins/audioconvert/volume-ops.h @@ -0,0 +1,68 @@ +/* Spa + * + * Copyright © 2021 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> + +#include <spa/utils/defs.h> +#include <spa/param/audio/raw.h> + +#define VOLUME_MIN 0.0f +#define VOLUME_NORM 1.0f + +struct volume { + uint32_t cpu_flags; + const char *func_name; + + struct spa_log *log; + + uint32_t flags; + + void (*process) (struct volume *vol, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src, float volume, uint32_t n_samples); + void (*free) (struct volume *vol); + + void *data; +}; + +int volume_init(struct volume *vol); + +#define volume_process(vol,...) (vol)->process(vol, __VA_ARGS__) +#define volume_free(vol) (vol)->free(vol) + +#define DEFINE_FUNCTION(name,arch) \ +void volume_##name##_##arch(struct volume *vol, \ + void * SPA_RESTRICT dst, \ + const void * SPA_RESTRICT src, \ + float volume, uint32_t n_samples); + +#define VOLUME_OPS_MAX_ALIGN 16 + +DEFINE_FUNCTION(f32, c); + +#if defined (HAVE_SSE) +DEFINE_FUNCTION(f32, sse); +#endif + +#undef DEFINE_FUNCTION |