summaryrefslogtreecommitdiffstats
path: root/spa/plugins/audioconvert
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:28:17 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:28:17 +0000
commit7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch)
treed483300dab478b994fe199a5d19d18d74153718a /spa/plugins/audioconvert
parentInitial commit. (diff)
downloadpipewire-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')
-rw-r--r--spa/plugins/audioconvert/audioadapter.c1733
-rw-r--r--spa/plugins/audioconvert/audioconvert.c2945
-rw-r--r--spa/plugins/audioconvert/benchmark-fmt-ops.c323
-rw-r--r--spa/plugins/audioconvert/benchmark-resample.c204
-rw-r--r--spa/plugins/audioconvert/biquad.c111
-rw-r--r--spa/plugins/audioconvert/biquad.h45
-rw-r--r--spa/plugins/audioconvert/channelmix-ops-c.c533
-rw-r--r--spa/plugins/audioconvert/channelmix-ops-sse.c522
-rw-r--r--spa/plugins/audioconvert/channelmix-ops.c668
-rw-r--r--spa/plugins/audioconvert/channelmix-ops.h160
-rw-r--r--spa/plugins/audioconvert/crossover.c70
-rw-r--r--spa/plugins/audioconvert/crossover.h31
-rw-r--r--spa/plugins/audioconvert/delay.h72
-rw-r--r--spa/plugins/audioconvert/fmt-ops-avx2.c1043
-rw-r--r--spa/plugins/audioconvert/fmt-ops-c.c433
-rw-r--r--spa/plugins/audioconvert/fmt-ops-neon.c487
-rw-r--r--spa/plugins/audioconvert/fmt-ops-sse2.c1438
-rw-r--r--spa/plugins/audioconvert/fmt-ops-sse41.c86
-rw-r--r--spa/plugins/audioconvert/fmt-ops-ssse3.c111
-rw-r--r--spa/plugins/audioconvert/fmt-ops.c564
-rw-r--r--spa/plugins/audioconvert/fmt-ops.h488
-rw-r--r--spa/plugins/audioconvert/hilbert.h69
-rw-r--r--spa/plugins/audioconvert/law.h2163
-rw-r--r--spa/plugins/audioconvert/meson.build206
-rw-r--r--spa/plugins/audioconvert/peaks-ops-c.c50
-rw-r--r--spa/plugins/audioconvert/peaks-ops-sse.c122
-rw-r--r--spa/plugins/audioconvert/peaks-ops.c89
-rw-r--r--spa/plugins/audioconvert/peaks-ops.h72
-rw-r--r--spa/plugins/audioconvert/plugin.c50
-rw-r--r--spa/plugins/audioconvert/resample-native-avx.c94
-rw-r--r--spa/plugins/audioconvert/resample-native-c.c65
-rw-r--r--spa/plugins/audioconvert/resample-native-impl.h191
-rw-r--r--spa/plugins/audioconvert/resample-native-neon.c218
-rw-r--r--spa/plugins/audioconvert/resample-native-sse.c94
-rw-r--r--spa/plugins/audioconvert/resample-native-ssse3.c115
-rw-r--r--spa/plugins/audioconvert/resample-native.c400
-rw-r--r--spa/plugins/audioconvert/resample-peaks.c148
-rw-r--r--spa/plugins/audioconvert/resample.h69
-rw-r--r--spa/plugins/audioconvert/spa-resample.c341
-rw-r--r--spa/plugins/audioconvert/test-audioadapter.c302
-rw-r--r--spa/plugins/audioconvert/test-audioconvert.c1158
-rw-r--r--spa/plugins/audioconvert/test-channelmix.c374
-rw-r--r--spa/plugins/audioconvert/test-fmt-ops.c798
-rw-r--r--spa/plugins/audioconvert/test-helper.h97
-rw-r--r--spa/plugins/audioconvert/test-peaks.c128
-rw-r--r--spa/plugins/audioconvert/test-resample.c177
-rw-r--r--spa/plugins/audioconvert/test-source.c931
-rw-r--r--spa/plugins/audioconvert/volume-ops-c.c45
-rw-r--r--spa/plugins/audioconvert/volume-ops-sse.c66
-rw-r--r--spa/plugins/audioconvert/volume-ops.c84
-rw-r--r--spa/plugins/audioconvert/volume-ops.h68
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, &param, &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, &param, &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, &param, &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, &param, &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, &param, &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, &param, &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