From: Martin Kepplinger <martin.kepplinger@xxxxxxxxxxxxx> Fixes #86 --- I found this very nice filter and thought why not implement it for us. It's actually a must-have I think. It's really cheap in resourses and very effective, and quite fun to play with. Choose 9/10 and see for yourself. I'd be happy for feedback. Posted to the list for future reference too, to demonstrate how to add a new filter. README.md | 21 ++++ configure.ac | 1 + etc/ts.conf | 3 + plugins/Makefile.am | 14 +++ plugins/iir.c | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++ plugins/plugins.h | 1 + src/Makefile.am | 4 + src/ts_load_module.c | 3 + 8 files changed, 314 insertions(+) create mode 100644 plugins/iir.c diff --git a/README.md b/README.md index 8e1423a..ff1dbb7 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,27 @@ Parameters: Maximum pressure value for a sample to be valid. +### module: iir + +#### Description: + Infinite impulse response filter. Similar to dejitter, this is a smoothing + filter to remove low-level noise. There is a trade-off between noise removal + (smoothing) and responsiveness. The parameters N and D specify the level of + smoothing in the form of a fraction (N/D). + + [Wikipedia](https://en.wikipedia.org/wiki/Infinite_impulse_response) has some + general theory. + + +Parameters: +* `N` + + numerator of the smoothing fraction +* `D` + + denominator of the smoothing fraction + + ### module: dejitter #### Description: diff --git a/configure.ac b/configure.ac index 122b80e..153cbc1 100644 --- a/configure.ac +++ b/configure.ac @@ -97,6 +97,7 @@ TSLIB_CHECK_MODULE([median], [yes], [Enable building of median filter]) TSLIB_CHECK_MODULE([pthres], [yes], [Enable building of pthres filter]) TSLIB_CHECK_MODULE([debounce], [yes], [Enable building of debounce filter]) TSLIB_CHECK_MODULE([skip], [yes], [Enable building of skip filter]) +TSLIB_CHECK_MODULE([iir], [yes], [Enable building of IIR filter]) # hardware access modules if test "$build_linux" = "yes"; then diff --git a/etc/ts.conf b/etc/ts.conf index c63ff12..4f69b1a 100644 --- a/etc/ts.conf +++ b/etc/ts.conf @@ -44,6 +44,9 @@ module pthres pmin=1 # Uncomment if needed to filter spikes # module median depth=5 +# Uncomment to enable smoothing of fraction N/D +# module iir N=6 D=10 + # Uncomment if needed (single touch only) # module variance delta=30 diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 8c999ba..7c640b4 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -59,6 +59,12 @@ else SKIP_MODULE = endif +if ENABLE_IIR_MODULE +IIR_MODULE = iir.la +else +IIR_MODULE = +endif + if ENABLE_UCB1X00_MODULE UCB1X00_MODULE = ucb1x00.la else @@ -152,6 +158,7 @@ pluginexec_LTLIBRARIES = \ $(PTHRES_MODULE) \ $(DEBOUNCE_MODULE) \ $(SKIP_MODULE) \ + $(IIR_MODULE) \ $(UCB1X00_MODULE) \ $(CORGI_MODULE) \ $(COLLIE_MODULE) \ @@ -223,6 +230,13 @@ linear_h2200_la_LDFLAGS += -no-undefined endif linear_h2200_la_LIBADD = $(top_builddir)/src/libts.la +iir_la_SOURCES = iir.c +iir_la_LDFLAGS = -module $(LTVSN) +if WINDOWS +iir_la_LDFLAGS += -no-undefined +endif +iir_la_LIBADD = $(top_builddir)/src/libts.la + # hw access corgi_la_SOURCES = corgi-raw.c corgi_la_LDFLAGS = -module $(LTVSN) diff --git a/plugins/iir.c b/plugins/iir.c new file mode 100644 index 0000000..af3ecd2 --- /dev/null +++ b/plugins/iir.c @@ -0,0 +1,267 @@ +/* + * tslib/plugins/iir.c + * + * Copyright (C) 2017 Martin Kepplinger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * + * Similar to dejitter, this is a smoothing filter to remove low-level noise. + * See https://en.wikipedia.org/wiki/Infinite_impulse_response for some theory. + * There is a trade-off between noise removal (smoothing) and responsiveness: + * The parameters N and D specify the level of smoothing in the form of a + * fraction (N/D). + * + * The discrete formula we use is found at http://dlbeer.co.nz/articles/tsf.html + */ +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <stdint.h> + +#include <stdio.h> + +#include "config.h" +#include "tslib.h" +#include "tslib-filter.h" + +struct tslib_iir { + struct tslib_module_info module; + uint32_t D; + uint32_t N; + int32_t slots; + uint32_t nr; + int32_t s; + int32_t t; + uint8_t last_active; /* for finding pen-down */ + + int32_t *s_mt; + int32_t *t_mt; + uint8_t *last_active_mt; /* for finding pen-down */ +}; + +/* legacy read interface */ +static int iir_read(struct tslib_module_info *info, struct ts_sample *samp, + int nr) +{ + struct tslib_iir *iir = (struct tslib_iir *)info; + int32_t ret; + int32_t i; + + ret = info->next->ops->read(info->next, samp, nr); + if (ret < 0) + return ret; + + for (i = 0; i < ret; i++, samp++) { + if (samp->pressure == 0) { /* reset */ + iir->s = samp->x; + iir->t = samp->y; + iir->last_active = 0; + continue; + } else if (iir->last_active == 0) { + iir->s = samp->x; + iir->t = samp->y; + iir->last_active = 1; + continue; + } + + iir->s = (iir->N * iir->s + (iir->D - iir->N) * samp->x + iir->D / 2) / iir->D; + #ifdef DEBUG + printf("IIR: X: %d -> %d\n", samp->x, iir->s); + #endif + samp->x = iir->s; + + iir->t = (iir->N * iir->t + (iir->D - iir->N) * samp->y + iir->D / 2) / iir->D; + #ifdef DEBUG + printf("IIR: Y: %d -> %d\n", samp->y, iir->t); + #endif + samp->y = iir->t; + } + + return ret; +} + +static int iir_read_mt(struct tslib_module_info *info, + struct ts_sample_mt **samp, int max_slots, int nr) +{ + struct tslib_iir *iir = (struct tslib_iir *)info; + int32_t ret; + int32_t i, j; + + if (!iir->s_mt || max_slots > iir->slots) { + if (iir->s_mt) + free(iir->s_mt); + + if (iir->t_mt) + free(iir->t_mt); + + if (iir->last_active_mt) + free(iir->last_active_mt); + + iir->s_mt = calloc(max_slots, sizeof(int32_t)); + if (!iir->s_mt) + return -ENOMEM; + + iir->t_mt = calloc(max_slots, sizeof(int32_t)); + if (!iir->t_mt) + return -ENOMEM; + + iir->last_active_mt = calloc(max_slots, sizeof(uint8_t)); + if (!iir->last_active_mt) + return -ENOMEM; + + iir->slots = max_slots; + } + + ret = info->next->ops->read_mt(info->next, samp, max_slots, nr); + if (ret < 0) + return ret; + + for (i = 0; i < ret; i++) { + for (j = 0; j < max_slots; j++) { + if (samp[i][j].valid != 1) + continue; + /* reset */ + if (samp[i][j].pressure == 0) { /* pen-up */ + iir->s_mt[j] = samp[i][j].x; + iir->t_mt[j] = samp[i][j].y; + iir->last_active_mt[j] = 0; + continue; + } else if (iir->last_active_mt[j] == 0) { /* pen-down */ + iir->s_mt[j] = samp[i][j].x; + iir->t_mt[j] = samp[i][j].y; + iir->last_active_mt[j] = 1; + continue; + } + + iir->s_mt[j] = (iir->N * iir->s_mt[j] + + (iir->D - iir->N) * samp[i][j].x + + iir->D / 2) + / iir->D; + #ifdef DEBUG + printf("IIR: (slot %d) X: %d -> %d\n", + j, samp[i][j].x, iir->s_mt[j]); + #endif + samp[i][j].x = iir->s_mt[j]; + + iir->t_mt[j] = (iir->N * iir->t_mt[j] + + (iir->D - iir->N) * samp[i][j].y + + iir->D / 2) + / iir->D; + #ifdef DEBUG + printf("IIR: (slot %d) Y: %d -> %d\n", + j, samp[i][j].y, iir->t_mt[j]); + #endif + samp[i][j].y = iir->t_mt[j]; + } + } + + return ret; +} + +static int iir_fini(struct tslib_module_info *info) +{ + struct tslib_iir *iir = (struct tslib_iir *)info; + + if (iir->s_mt) + free(iir->s_mt); + + if (iir->t_mt) + free(iir->t_mt); + + free(info); + + return 0; +} + +static const struct tslib_ops iir_ops = { + .read = iir_read, + .read_mt = iir_read_mt, + .fini = iir_fini, +}; + +static int iir_opt(struct tslib_module_info *inf, char *str, void *data) +{ + struct tslib_iir *iir = (struct tslib_iir *)inf; + unsigned long v; + int32_t err = errno; + + v = strtoul(str, NULL, 0); + + if (v == ULONG_MAX && errno == ERANGE) + return -1; + + errno = err; + switch ((int)(intptr_t)data) { + case 1: + iir->N = v; + break; + + case 2: + iir->D = v; + if (iir->D == 0) { + printf("IIR: avoid division by zero: D=1 set\n"); + iir->D = 1; + } + break; + + default: + return -1; + } + + + return 0; +} + +static const struct tslib_vars iir_vars[] = { + { "N", (void *)1, iir_opt }, + { "D", (void *)2, iir_opt }, +}; + +#define NR_VARS (sizeof(iir_vars) / sizeof(iir_vars[0])) + +TSAPI struct tslib_module_info *iir_mod_init(__attribute__ ((unused)) struct tsdev *dev, + const char *params) +{ + struct tslib_iir *iir; + + iir = malloc(sizeof(struct tslib_iir)); + if (iir == NULL) + return NULL; + + memset(iir, 0, sizeof(struct tslib_iir)); + iir->module.ops = &iir_ops; + + iir->N = 0; + iir->D = 0; + iir->slots = 0; + iir->nr = 0; + iir->s = 0; + iir->t = 0; + iir->s_mt = NULL; + iir->t_mt = NULL; + iir->last_active = 0; + + if (tslib_parse_vars(&iir->module, iir_vars, NR_VARS, params)) { + free(iir); + return NULL; + } + + return &iir->module; +} + +#ifndef TSLIB_STATIC_DEJITTER_MODULE + TSLIB_MODULE_INIT(iir_mod_init); +#endif diff --git a/plugins/plugins.h b/plugins/plugins.h index 01d39a2..8f9f5d2 100644 --- a/plugins/plugins.h +++ b/plugins/plugins.h @@ -9,6 +9,7 @@ TSLIB_DECLARE_MODULE(median); TSLIB_DECLARE_MODULE(pthres); TSLIB_DECLARE_MODULE(debounce); TSLIB_DECLARE_MODULE(skip); +TSLIB_DECLARE_MODULE(iir); TSLIB_DECLARE_MODULE(ucb1x00); TSLIB_DECLARE_MODULE(corgi); diff --git a/src/Makefile.am b/src/Makefile.am index 8aa80c4..ec0f539 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -86,6 +86,10 @@ if ENABLE_STATIC_SKIP_MODULE libts_la_SOURCES += $(top_srcdir)/plugins/skip.c endif +if ENABLE_STATIC_IIR_MODULE +libts_la_SOURCES += $(top_srcdir)/plugins/iir.c +endif + if ENABLE_STATIC_WAVESHARE_MODULE libts_la_SOURCES += $(top_srcdir)/plugins/waveshare-raw.c endif diff --git a/src/ts_load_module.c b/src/ts_load_module.c index a178d19..c0e1f39 100644 --- a/src/ts_load_module.c +++ b/src/ts_load_module.c @@ -77,6 +77,9 @@ static const struct { #ifdef TSLIB_STATIC_SKIP_MODULE { "skip", skip_mod_init }, #endif +#ifdef TSLIB_STATIC_IIR_MODULE + { "iir", iir_mod_init }, +#endif }; #define countof(arr) (sizeof(arr) / sizeof((arr)[0])) -- 2.1.4