//filter-slfi-fade.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2012-2015 * * This file is part of roard a part of RoarAudio, * a cross-platform sound system for both, home and professional use. * See README for details. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 * as published by the Free Software Foundation. * * RoarAudio 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include #include #include enum slfi_function { FUNC_STEP = 0, FUNC_LIN, FUNC_COS // FUNC_LOWPASS }; struct slfi_channel { // config: ssize_t index; enum slfi_function func; double exponent; int32_t time_base; ssize_t time_channel; // if not -1: time_end = time_base * valueof(time_channel)/255. ssize_t time_channelblack; // if not -1: time_black = time_end * valueof(time_channelblack)/255. double time_black_point; int32_t time_end; int32_t time_black; ssize_t trigger_channel; uint8_t trigger_event; // state: int32_t t; uint8_t value_old; uint8_t value_trigger; }; static enum slfi_function __str2func (const char * str) { if ( !strcasecmp(str, "step") ) { return FUNC_STEP; } else if ( !strcasecmp(str, "lin") ) { return FUNC_LIN; } else if ( !strcasecmp(str, "cos") ) { return FUNC_COS; /* } else if ( !strcasecmp(str, "lowpass") ) { return FUNC_LOWPASS; */ } else { ROAR_WARN("__str2func(str='%s'): Unknown function, defaulting to linear fade", str); return FUNC_LIN; } } static void __init_chan(struct slfi_channel * chan) { memset(chan, 0, sizeof(*chan)); chan->index = -1; chan->func = FUNC_LIN; chan->exponent = 1.; chan->time_base = 1000000L; chan->time_channel = -1; chan->time_channelblack = -1; chan->time_black_point = -1; chan->time_end = chan->time_base; chan->time_black = -1; chan->trigger_channel = -1; chan->trigger_event = ROAR_ROARDMX_EVENT_STEP; chan->t = -1; chan->value_old = 0; chan->value_trigger = 0; } static int __push_chan(struct slfi_channel * chan, struct slfi_channel ** array, size_t * arraylen, size_t * arrayptr) { struct slfi_channel * new; int err; size_t i; if ( (*arrayptr + 1) >= *arraylen ) { new = roar_mm_realloc(*array, sizeof(struct slfi_channel)*(*arraylen + 8)); err = roar_error; if ( new == NULL ) { roar_mm_free(array); roar_err_set(err); *array = NULL; *arraylen = 0; *arrayptr = 0; return -1; } for (i = *arrayptr + 1; i < (*arrayptr + 8); i++) __init_chan(&(new[i])); *array = new; *arraylen += 8; } (*array)[*arrayptr] = *chan; (*arrayptr)++; return 0; } static int __init(struct roar_slfi_inst * inst, const struct roar_keyval * para, ssize_t paralen) { const struct roar_keyval * kv; ssize_t i; struct slfi_channel chan; struct slfi_channel * array = NULL; size_t arraylen = 0; size_t arrayptr = 0; __init_chan(&chan); for (i = 0; i < paralen; i++) { kv = &(para[i]); if ( kv->key == NULL || kv->value == NULL ) continue; if ( !strcmp(kv->key, "channel") ) { if ( chan.index != -1 ) { if ( __push_chan(&chan, &array, &arraylen, &arrayptr) == -1 ) { ROAR_ERR("__init(*): Can not add more channels: %s", roar_errorstring); return -1; } } chan.index = atoi(kv->value); } else if ( !strcmp(kv->key, "exponent") ) { chan.exponent = atof(kv->value); } else if ( !strcmp(kv->key, "timebase") ) { chan.time_base = roar_str2usec(kv->value); } else if ( !strcmp(kv->key, "time") ) { chan.time_end = roar_str2usec(kv->value); } else if ( !strcmp(kv->key, "time_black_point") ) { if ( !strcmp(kv->value, "none") ) { chan.time_black_point = -1; } else { chan.time_black_point = atof(kv->value); if ( chan.time_black_point <= 0. ) chan.time_black_point = -1.; if ( chan.time_black_point >= 1. ) chan.time_black_point = -1.; } } else if ( !strcmp(kv->key, "timechannel") ) { chan.time_channel = atoi(kv->value); } else if ( !strcmp(kv->key, "timechannelblack") ) { chan.time_channelblack = atoi(kv->value); } else if ( !strcmp(kv->key, "triggerchannel") ) { chan.trigger_channel = atoi(kv->value); } else if ( !strcmp(kv->key, "triggerevent") ) { chan.trigger_event = roar_roardmx_str2event(kv->value); } else if ( !strcmp(kv->key, "function") ) { chan.func = __str2func(kv->value); } else { ROAR_WARN("__init(*): Unknown parameter: %s", kv->key); } } if ( chan.index != -1 ) { if ( __push_chan(&chan, &array, &arraylen, &arrayptr) == -1 ) { ROAR_ERR("__init(*): Can not add more channels: %s", roar_errorstring); return -1; } } inst->userdata = array; return 0; } static double __func_calc(enum slfi_function func, double a) { switch (func) { case FUNC_STEP: return a > 0.999 ? 1. : 0.; // allow some error here. Error should be 1/255. break; case FUNC_LIN: return a; break; case FUNC_COS: return (1.-cos(M_PI*a))/2.; break; } // error. return 1.; } static double __time_back_to_t1(int32_t t) { if ( t > 0 ) return (double)t; return 0; } static uint8_t __channel_calc(struct slfi_channel * chan, uint8_t value, int32_t usecspassed, const uint8_t * event, size_t eventlen, uint8_t * universe, ssize_t size_of_universe) { int have_trigger = 0; size_t i; double value_old, value_new; double a; double t1; if ( chan->trigger_channel >= 0 ) { if ( chan->trigger_channel >= size_of_universe ) { ROAR_WARN("__channel_calc(*): Universe too small for filter."); have_trigger = 0; } else { if ( universe[chan->trigger_channel] != chan->value_trigger ) { chan->value_trigger = universe[chan->trigger_channel]; have_trigger = 1; } } } else { for (i = 0; i < eventlen; i++) { if ( event[i] == chan->trigger_event || event[i] == (chan->trigger_event | ROAR_ROARDMX_ETYPE_ON) ) { have_trigger = 1; break; } } } ROAR_DBG("__channel_calc(*): chan={.time=%li, .time_end=%li, ...}, have_trigger=%i", (long int)chan->t, (long int)chan->time_end, have_trigger); // are we currently fading? if ( chan->t < 0 && !have_trigger ) return chan->value_old; // no, just return current value. // yes, keep calcing: if ( have_trigger && chan->t < 0 ) chan->t = 0; // recaculate timings. if ( chan->time_channel >= 0 ) { if ( chan->time_channel >= size_of_universe ) { ROAR_WARN("__channel_calc(*): Universe too small for filter."); chan->time_end = 0; } else { chan->time_end = (chan->time_base * (int32_t)universe[chan->time_channel]) / 255; } } if ( chan->time_channelblack != -1 ) { if ( chan->time_channelblack >= size_of_universe ) { ROAR_WARN("__channel_calc(*): Universe too small for filter."); chan->time_black = -1; } else { chan->time_black = (chan->time_end * (int32_t)universe[chan->time_channelblack]) / 255; } } else { chan->time_black = chan->time_end * chan->time_black_point; } ROAR_DBG("__channel_calc(*): chan->time_black=%li", (long int)chan->time_black); // calculate value. if ( chan->t >= chan->time_end ) { chan->t = -1; a = 1.; value_old = value_new = chan->value_old = value; } else if ( chan->t < chan->time_black ) { a = (double)chan->t / (double)chan->time_black; value_old = chan->value_old; value_new = 0.; } else { t1 = __time_back_to_t1(chan->time_black); a = ((double)chan->t - t1) / ((double)chan->time_end - t1); value_old = chan->time_black > 0 ? 0 : chan->value_old; value_new = value; if ( a > 1. ) a = 1.; } if ( a > 1. ) { a = 1.; } if ( 0 ) { // inverse mode a = 2.*a - __func_calc(chan->func, a); } else { // normal mode a = __func_calc(chan->func, a); } a = pow(a, chan->exponent); value = value_new = (double)(value_old * (1. - a) + value_new * a); if ( value_new > 255. ) value = 255; if ( value_new < 0. ) value = 0; if ( have_trigger ) { chan->value_old = value; chan->t = 0; } if ( chan->t != -1 ) chan->t += usecspassed; return value; } static int __update(struct roar_slfi_inst * inst, uint8_t * universe, ssize_t size_of_universe, int32_t usecspassed, const uint8_t * event, size_t eventlen) { struct slfi_channel * chan = inst->userdata; if ( chan == NULL ) return 0; while (chan->index != -1) { ROAR_DBG("__update(*): chan(%p)->index=%i", chan, (int)chan->index); if ( chan->index < 0 && chan->index >= size_of_universe ) { ROAR_WARN("__update(*): Universe too small for filter."); continue; } universe[chan->index] = __channel_calc(chan, universe[chan->index], usecspassed, event, eventlen, universe, size_of_universe); chan++; } return 0; } static const struct roar_slfi_filter filter[1] = { { .name = "fade", .description = "Fade SLFI filter", .flags = ROAR_SLFI_FLAG_ON_UPDATE, .init = __init, .uninit = NULL, .update = __update, .ctl = NULL } }; ROAR_DL_PLUGIN_REG_SLFI(filter); // This is the plugin control block. ROAR_DL_PLUGIN_START(filter_slfi_fade) { // Here we set the name and vendor of our plugin. // If you have no Vendor ID you need to use ROAR_DL_PLUGIN_META_PRODUCT_NV(). ROAR_DL_PLUGIN_META_PRODUCT_NIV("filter-slfi-fade", ROAR_VID_ROARAUDIO, ROAR_VNAME_ROARAUDIO); // This sets the version of your plugin. ROAR_DL_PLUGIN_META_VERSION(ROAR_VERSION_STRING); // This sets the license of your plugin. // If there is no tag for the license you use you can just // use ROAR_DL_PLUGIN_META_LICENSE(). ROAR_DL_PLUGIN_META_LICENSE_TAG(GPLv3_0); // This sets the author and contact infos. // There are several other macros to do this with other parameters. // See ROAR_DL_PLUGIN_META_CONTACT*() in the header or documentation. ROAR_DL_PLUGIN_META_CONTACT_FLNE("Philipp", "Schafft", "ph3-der-loewe", "lion@lion.leolix.org"); // This sets the description for your plugin. ROAR_DL_PLUGIN_META_DESC("This plugin allows to fade between settings"); // Load filters. ROAR_DL_PLUGIN_REG_FNFUNC(ROAR_DL_FN_FILTER); // This is the end of the control block. } ROAR_DL_PLUGIN_END //ll