From 24cf1cf2683ed46e58b9cfacfadcf08006410486 Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 28 Feb 2013 15:14:26 +0000 Subject: [PATCH] Add NekoFilter plugin (testing) --- source/backend/native/nekofilter.c | 75 + source/backend/native/nekofilter/filter.c | 350 +++++ source/backend/native/nekofilter/filter.h | 65 + source/backend/native/nekofilter/log.c | 40 + source/backend/native/nekofilter/log.h | 86 ++ source/backend/native/nekofilter/nekofilter.c | 396 ++++++ source/backend/native/nekofilter/ui.c | 522 +++++++ source/backend/native/nekofilter/ui.py | 1236 +++++++++++++++++ 8 files changed, 2770 insertions(+) create mode 100644 source/backend/native/nekofilter.c create mode 100644 source/backend/native/nekofilter/filter.c create mode 100644 source/backend/native/nekofilter/filter.h create mode 100644 source/backend/native/nekofilter/log.c create mode 100644 source/backend/native/nekofilter/log.h create mode 100644 source/backend/native/nekofilter/nekofilter.c create mode 100644 source/backend/native/nekofilter/ui.c create mode 100755 source/backend/native/nekofilter/ui.py diff --git a/source/backend/native/nekofilter.c b/source/backend/native/nekofilter.c new file mode 100644 index 000000000..200b70648 --- /dev/null +++ b/source/backend/native/nekofilter.c @@ -0,0 +1,75 @@ +/* + * Carla Native Plugins + * Copyright (C) 2013 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or 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 General Public License for more details. + * + * For a full copy of the GNU General Public License see the GPL.txt file + */ + +#include "CarlaNative.h" + +// Plugin Code +#include "nekofilter/nekofilter.c" +#include "nekofilter/filter.c" +#include "nekofilter/log.c" + +// ----------------------------------------------------------------------- + +static const PluginDescriptor nekofilterDesc = { + .category = PLUGIN_CATEGORY_FILTER, + .hints = PLUGIN_IS_RTSAFE|PLUGIN_HAS_GUI, + .audioIns = 1, + .audioOuts = 1, + .midiIns = 0, + .midiOuts = 0, + .parameterIns = GLOBAL_PARAMETERS_COUNT + BAND_PARAMETERS_COUNT*BANDS_COUNT, + .parameterOuts = 0, + .name = "NekoFilter", + .label = "nekofilter", + .maker = "falkTX, Nedko, Fons Adriaensen", + .copyright = "GNU GPL v2+", + + .instantiate = nekofilter_instantiate, + .cleanup = nekofilter_cleanup, + + .get_parameter_count = nekofilter_get_parameter_count, + .get_parameter_info = nekofilter_get_parameter_info, + .get_parameter_value = nekofilter_get_parameter_value, + .get_parameter_text = NULL, + + .get_midi_program_count = NULL, + .get_midi_program_info = NULL, + + .set_parameter_value = nekofilter_set_parameter_value, + .set_midi_program = NULL, + .set_custom_data = NULL, + + .ui_show = nekofilter_ui_show, + .ui_idle = nekofilter_ui_idle, + + .ui_set_parameter_value = nekofilter_ui_set_parameter_value, + .ui_set_midi_program = NULL, + .ui_set_custom_data = NULL, + + .activate = NULL, + .deactivate = NULL, + .process = nekofilter_process +}; + +// ----------------------------------------------------------------------- + +void carla_register_native_plugin_nekofilter() +{ + carla_register_native_plugin(&nekofilterDesc); +} + +// ----------------------------------------------------------------------- diff --git a/source/backend/native/nekofilter/filter.c b/source/backend/native/nekofilter/filter.c new file mode 100644 index 000000000..4c0e1808c --- /dev/null +++ b/source/backend/native/nekofilter/filter.c @@ -0,0 +1,350 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + Copyright (C) 2008 Nedko Arnaudov + The DSP code is based on ladspa:1970 by Fons Adriaensen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* if NDEBUG is defined, assert checks are disabled */ +//#define NDEBUG + +#include +#include +#include +#include +#include + +#include "filter.h" + +static float exp2ap(float x) +{ + int i; + + i = (int)(floor(x)); + x -= i; +// return ldexp(1 + x * (0.66 + 0.34 * x), i); + return ldexp(1 + x * (0.6930 + x * (0.2416 + x * (0.0517 + x * 0.0137))), i); +} + +struct param_sect +{ + float f, b, g; + float s1, s2, a; + float z1, z2; +}; + +void +param_sect_init( + struct param_sect * sect_ptr) +{ + sect_ptr->f = 0.25f; + sect_ptr->b = sect_ptr->g = 1.0f; + sect_ptr->a = sect_ptr->s1 = sect_ptr->s2 = sect_ptr->z1 = sect_ptr->z2 = 0.0f; +} + +void +param_sect_proc( + struct param_sect * sect_ptr, + int k, + float * sig, + float f, + float b, + float g) +{ + float s1, s2, d1, d2, a, da, x, y; + bool u2 = false; + + s1 = sect_ptr->s1; + s2 = sect_ptr->s2; + a = sect_ptr->a; + d1 = 0; + d2 = 0; + da = 0; + + if (f != sect_ptr->f) + { + if (f < 0.5f * sect_ptr->f) f = 0.5f * sect_ptr->f; + else if (f > 2.0f * sect_ptr->f) f = 2.0f * sect_ptr->f; + sect_ptr->f = f; + sect_ptr->s1 = -cosf(6.283185f * f); + d1 = (sect_ptr->s1 - s1) / k; + u2 = true; + } + + if (g != sect_ptr->g) + { + if (g < 0.5f * sect_ptr->g) g = 0.5f * sect_ptr->g; + else if (g > 2.0f * sect_ptr->g) g = 2.0f * sect_ptr->g; + sect_ptr->g = g; + sect_ptr->a = 0.5f * (g - 1.0f); + da = (sect_ptr->a - a) / k; + u2 = true; + } + + if (b != sect_ptr->b) + { + if (b < 0.5f * sect_ptr->b) b = 0.5f * sect_ptr->b; + else if (b > 2.0f * sect_ptr->b) b = 2.0f * sect_ptr->b; + sect_ptr->b = b; + u2 = true; + } + + if (u2) + { + b *= 7 * f / sqrtf(g); + sect_ptr->s2 = (1 - b) / (1 + b); + d2 = (sect_ptr->s2 - s2) / k; + } + + while (k--) + { + s1 += d1; + s2 += d2; + a += da; + x = *sig; + y = x - s2 * sect_ptr->z2; + *sig++ -= a * (sect_ptr->z2 + s2 * y - x); + y -= s1 * sect_ptr->z1; + sect_ptr->z2 = sect_ptr->z1 + s1 * y; + sect_ptr->z1 = y + 1e-10f; + } +} + +struct filter +{ + float sample_rate; + + const float * global_parameters[GLOBAL_PARAMETERS_COUNT]; + + unsigned int bands_count; + const float ** band_parameters; /* [band_index * BAND_PARAMETERS_COUNT + parameter_index] */ + + float gain; + int fade; + struct param_sect * sect; /* [band_index] */ +}; + +bool +filter_create( + float sample_rate, + unsigned int bands_count, + filter_handle * handle_ptr) +{ + struct filter * filter_ptr; + unsigned int j; + + assert(bands_count > 0); + + filter_ptr = calloc(1, sizeof(struct filter)); + if (filter_ptr == NULL) + { + goto fail; + } + + filter_ptr->band_parameters = calloc(bands_count, sizeof(float *) * BAND_PARAMETERS_COUNT); + if (filter_ptr->band_parameters == NULL) + { + goto free_filter; + } + + filter_ptr->sect = malloc(sizeof(struct param_sect) * bands_count); + if (filter_ptr->sect == NULL) + { + goto free_band_params; + } + + filter_ptr->sample_rate = sample_rate; + filter_ptr->bands_count = bands_count; + filter_ptr->fade = 0; + filter_ptr->gain = 1.0; + + for (j = 0; j < bands_count; j++) + { + param_sect_init(filter_ptr->sect + j); + } + + *handle_ptr = (filter_handle)filter_ptr; + + return true; + +free_band_params: + free(filter_ptr->band_parameters); + +free_filter: + free(filter_ptr); + +fail: + return false; +} + +#define filter_ptr ((struct filter *)handle) + +void +filter_destroy( + filter_handle handle) +{ + free(filter_ptr->sect); + free(filter_ptr->band_parameters); + free(filter_ptr); +} + +void +filter_connect_global_parameter( + filter_handle handle, + unsigned int global_parameter, + const float * value_ptr) +{ + assert(global_parameter < GLOBAL_PARAMETERS_COUNT); + + filter_ptr->global_parameters[global_parameter] = value_ptr; +} + +void +filter_connect_band_parameter( + filter_handle handle, + unsigned int band_index, + unsigned int band_parameter, + const float * value_ptr) +{ + assert(band_index < filter_ptr->bands_count); + assert(band_parameter < BAND_PARAMETERS_COUNT); + + filter_ptr->band_parameters[band_index * BAND_PARAMETERS_COUNT + band_parameter] = value_ptr; +} + +void +filter_run( + filter_handle handle, + const float * input_buffer, + float * output_buffer, + unsigned long samples_count) +{ + int i, j, k; + const float * p; + float sig[48]; + float t, g, d; + float fgain; + float sfreq[filter_ptr->bands_count]; + float sband[filter_ptr->bands_count]; + float sgain[filter_ptr->bands_count]; + float bands_count; + + bands_count = filter_ptr->bands_count; + + fgain = exp2ap(0.1661 * *filter_ptr->global_parameters[GLOBAL_PARAMETER_GAIN]); + + for (j = 0; j < bands_count; j++) + { + t = *filter_ptr->band_parameters[BAND_PARAMETERS_COUNT * j + BAND_PARAMETER_FREQUENCY] / filter_ptr->sample_rate; + if (t < 0.0002) + { + t = 0.0002; + } + else if (t > 0.4998) + { + t = 0.4998; + } + sfreq[j] = t; + + sband[j] = *filter_ptr->band_parameters[BAND_PARAMETERS_COUNT * j + BAND_PARAMETER_BANDWIDTH]; + + if (*filter_ptr->band_parameters[BAND_PARAMETERS_COUNT * j + BAND_PARAMETER_ACTIVE] > 0.0) + { + sgain[j] = exp2ap(0.1661 * *filter_ptr->band_parameters[BAND_PARAMETERS_COUNT * j + BAND_PARAMETER_GAIN]); + } + else + { + sgain[j] = 1.0; + } + } + + while (samples_count) + { + k = (samples_count > 48) ? 32 : samples_count; + + t = fgain; + g = filter_ptr->gain; + + if (t > 1.25 * g) + { + t = 1.25 * g; + } + else if (t < 0.80 * g) + { + t = 0.80 * g; + } + + filter_ptr->gain = t; + d = (t - g) / k; + for (i = 0; i < k; i++) + { + g += d; + sig[i] = g * input_buffer[i]; + } + + for (j = 0; j < bands_count; j++) + { + param_sect_proc(filter_ptr->sect + j, k, sig, sfreq[j], sband[j], sgain[j]); + } + + j = filter_ptr->fade; + g = j / 16.0; + p = 0; + + if (*filter_ptr->global_parameters[GLOBAL_PARAMETER_ACTIVE] > 0.0) + { + if (j == 16) + { + p = sig; + } + else + { + ++j; + } + } + else + { + if (j == 0) + { + p = input_buffer; + } + else + { + --j; + } + } + + filter_ptr->fade = j; + + if (p) + { + memcpy(output_buffer, p, k * sizeof(float)); + } + else + { + d = (j / 16.0 - g) / k; + for (i = 0; i < k; i++) + { + g += d; + output_buffer[i] = g * sig[i] + (1 - g) * input_buffer[i]; + } + } + + input_buffer += k; + output_buffer += k; + samples_count -= k; + } +} diff --git a/source/backend/native/nekofilter/filter.h b/source/backend/native/nekofilter/filter.h new file mode 100644 index 000000000..a0810ffb6 --- /dev/null +++ b/source/backend/native/nekofilter/filter.h @@ -0,0 +1,65 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/* + Copyright (C) 2008 Nedko Arnaudov + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef FILTER_H__D5DC5ADF_211A_48F6_93A5_68CD3B73D6C5__INCLUDED +#define FILTER_H__D5DC5ADF_211A_48F6_93A5_68CD3B73D6C5__INCLUDED + +typedef struct {int unused; } * filter_handle; + +#define GLOBAL_PARAMETER_ACTIVE 0 +#define GLOBAL_PARAMETER_GAIN 1 +#define GLOBAL_PARAMETERS_COUNT 2 + +#define BAND_PARAMETER_ACTIVE 0 +#define BAND_PARAMETER_FREQUENCY 1 +#define BAND_PARAMETER_BANDWIDTH 2 +#define BAND_PARAMETER_GAIN 3 +#define BAND_PARAMETERS_COUNT 4 + +bool +filter_create( + float sample_rate, + unsigned int bands_count, + filter_handle * handle_ptr); + +void +filter_connect_global_parameter( + filter_handle handle, + unsigned int global_parameter, + const float * value_ptr); + +void +filter_connect_band_parameter( + filter_handle handle, + unsigned int band_index, + unsigned int band_parameter, + const float * value_ptr); + +void +filter_run( + filter_handle handle, + const float * input_buffer, + float * output_buffer, + unsigned long samples_count); + +void +filter_destroy( + filter_handle handle); + +#endif /* #ifndef FILTER_H__D5DC5ADF_211A_48F6_93A5_68CD3B73D6C5__INCLUDED */ diff --git a/source/backend/native/nekofilter/log.c b/source/backend/native/nekofilter/log.c new file mode 100644 index 000000000..f7acf32fb --- /dev/null +++ b/source/backend/native/nekofilter/log.c @@ -0,0 +1,40 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/***************************************************************************** + * + * Copyright (C) 2006,2007,2008,2009 Nedko Arnaudov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *****************************************************************************/ + +#include +#include +#include +#include + +#include "log.h" + +void nekolog(int level, const char * format, ...) +{ + va_list arglist; + + va_start(arglist, format); + vprintf(format, arglist); + va_end(arglist); + + return; + + //unused + (void)level; +} diff --git a/source/backend/native/nekofilter/log.h b/source/backend/native/nekofilter/log.h new file mode 100644 index 000000000..a3eaf7c8b --- /dev/null +++ b/source/backend/native/nekofilter/log.h @@ -0,0 +1,86 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/***************************************************************************** + * + * Copyright (C) 2006,2007,2008,2009 Nedko Arnaudov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *****************************************************************************/ + +#ifndef LOG_H__7097F6FE_4FEE_4962_9542_60375961F567__INCLUDED +#define LOG_H__7097F6FE_4FEE_4962_9542_60375961F567__INCLUDED + +void nekolog(int level, const char * format, ...); + +#define LOG_LEVEL_DEBUG 0 +#define LOG_LEVEL_INFO 1 +#define LOG_LEVEL_WARNING 2 +#define LOG_LEVEL_NOTICE 3 +#define LOG_LEVEL_ERROR 4 +#define LOG_LEVEL_FATAL 5 +#define LOG_LEVEL_BLACK_HOLE 6 + +#if !defined(LOG_LEVEL) +#define LOG_LEVEL LOG_LEVEL_WARNING +#endif + +#if LOG_LEVEL <= LOG_LEVEL_DEBUG +# define LOG_DEBUG(format, arg...) \ + nekolog(LOG_LEVEL_DEBUG, \ + format "\n", ## arg) +#else +# define LOG_DEBUG(format, arg...) +#endif + +#if LOG_LEVEL <= LOG_LEVEL_INFO +# define LOG_INFO(format, arg...) \ + nekolog(LOG_LEVEL_INFO, \ + format "\n", ## arg) +#else +# define LOG_INFO(format, arg...) +#endif + +#if LOG_LEVEL <= LOG_LEVEL_WARNING +# define LOG_WARNING(format, arg...) \ + nekolog(LOG_LEVEL_WARNING, \ + format "\n", ## arg) +#else +# define LOG_WARNING(format, arg...) +#endif + +#if LOG_LEVEL <= LOG_LEVEL_NOTICE +# define LOG_NOTICE(format, arg...) \ + nekolog(LOG_LEVEL_NOTICE, \ + format "\n", ## arg) +#else +# define LOG_NOTICE(format, arg...) +#endif + +#if LOG_LEVEL <= LOG_LEVEL_ERROR +# define LOG_ERROR(format, arg...) \ + nekolog(LOG_LEVEL_ERROR, \ + format "\n", ## arg) +#else +# define LOG_ERROR(format, arg...) +#endif + +#if LOG_LEVEL <= LOG_LEVEL_FATAL +# define LOG_FATAL(format, arg...) \ + nekolog(LOG_LEVEL_FATAL, \ + format "\n", ## arg) +#else +# define LOG_FATAL(format, arg...) +#endif + +#endif /* #ifndef LOG_H__7097F6FE_4FEE_4962_9542_60375961F567__INCLUDED */ diff --git a/source/backend/native/nekofilter/nekofilter.c b/source/backend/native/nekofilter/nekofilter.c new file mode 100644 index 000000000..99d6a0742 --- /dev/null +++ b/source/backend/native/nekofilter/nekofilter.c @@ -0,0 +1,396 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/***************************************************************************** + * + * Copyright (C) 2006,2007,2008,2009 Nedko Arnaudov + * Copyright (C) 2013 Filipe Coelho + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "CarlaNative.h" + +//#include "nekofilter.h" +#include "filter.h" +#define LOG_LEVEL LOG_LEVEL_ERROR +#include "log.h" + +#include "ui.c" + +#define BANDS_COUNT 4 + +struct nekofilter +{ + filter_handle filter; + float params_global[GLOBAL_PARAMETERS_COUNT]; + float params_bands[BAND_PARAMETERS_COUNT*BANDS_COUNT]; + HostDescriptor* host; + struct control* ui; +}; + +PluginHandle +nekofilter_instantiate( + const struct _PluginDescriptor* _this_, + HostDescriptor* host) +{ + struct nekofilter * nekofilter_ptr; + unsigned int i; + + LOG_DEBUG("nekofilter_create_plugin_instance() called."); + + nekofilter_ptr = malloc(sizeof(struct nekofilter)); + if (nekofilter_ptr == NULL) + { + goto fail; + } + + nekofilter_ptr->host = host; + nekofilter_ptr->ui = NULL; + + if (!filter_create(host->get_sample_rate(host->handle), BANDS_COUNT, &nekofilter_ptr->filter)) + { + goto fail_destroy_filter; + } + + nekofilter_ptr->params_global[GLOBAL_PARAMETER_ACTIVE] = 0.0f; + nekofilter_ptr->params_global[GLOBAL_PARAMETER_GAIN] = 0.0f; + + filter_connect_global_parameter(nekofilter_ptr->filter, + GLOBAL_PARAMETER_ACTIVE, + &nekofilter_ptr->params_global[GLOBAL_PARAMETER_ACTIVE]); + + filter_connect_global_parameter(nekofilter_ptr->filter, + GLOBAL_PARAMETER_GAIN, + &nekofilter_ptr->params_global[GLOBAL_PARAMETER_GAIN]); + + for (i=0; i < BANDS_COUNT; i++) + { + nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_ACTIVE] = 0.0f; + nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_FREQUENCY] = 0.0f; + nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_BANDWIDTH] = 1.0f; + nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_GAIN] = 0.0f; + + switch (i) + { + case 0: + nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_FREQUENCY] = 200.0f; + break; + case 1: + nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_FREQUENCY] = 400.0f; + break; + case 2: + nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_FREQUENCY] = 1000.0f; + break; + case 3: + nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_FREQUENCY] = 2000.0f; + break; + } + + filter_connect_band_parameter(nekofilter_ptr->filter, + i, + BAND_PARAMETER_ACTIVE, + &nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_ACTIVE]); + + filter_connect_band_parameter(nekofilter_ptr->filter, + i, + BAND_PARAMETER_FREQUENCY, + &nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_FREQUENCY]); + + filter_connect_band_parameter(nekofilter_ptr->filter, + i, + BAND_PARAMETER_BANDWIDTH, + &nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_BANDWIDTH]); + + filter_connect_band_parameter(nekofilter_ptr->filter, + i, + BAND_PARAMETER_GAIN, + &nekofilter_ptr->params_bands[i*BAND_PARAMETERS_COUNT + BAND_PARAMETER_GAIN]); + } + + return (PluginHandle)nekofilter_ptr; + +fail_destroy_filter: + filter_destroy(nekofilter_ptr->filter); + free(nekofilter_ptr); + +fail: + return NULL; + + // unused + (void)_this_; +} + +#define nekofilter_ptr ((struct nekofilter *)handle) + +uint32_t +nekofilter_get_parameter_count( + PluginHandle handle) +{ + return GLOBAL_PARAMETERS_COUNT + BAND_PARAMETERS_COUNT*BANDS_COUNT; + + // unused + (void)handle; +} + +const Parameter* +nekofilter_get_parameter_info( + PluginHandle handle, + uint32_t index) +{ + static Parameter param; + static bool first_init = true; + uint32_t band; + char strBuf[32]; + + if (first_init) + { + first_init = false; + param.name = NULL; + param.unit = NULL; + } + else + { + if (param.name != NULL) + { + free((void*)param.name); + param.name = NULL; + } + if (param.unit != NULL) + { + free((void*)param.unit); + param.unit = NULL; + } + } + + if (handle == NULL && index == 0xf00baa) + // internal cleanup call + return NULL; + + param.hints = PARAMETER_IS_ENABLED|PARAMETER_IS_AUTOMABLE; + param.ranges.def = 0.0f; + param.ranges.min = 0.0f; + param.ranges.max = 0.0f; + param.scalePointCount = 0; + param.scalePoints = NULL; + + switch (index) + { + case GLOBAL_PARAMETER_ACTIVE: + param.hints |= PARAMETER_IS_BOOLEAN; + param.name = strdup("Active"); + param.ranges.max = 1.0f; + goto ready; + break; + + case GLOBAL_PARAMETER_GAIN: + param.name = strdup("Gain"); + param.unit = strdup("dB"); + param.ranges.min = -20.0f; + param.ranges.max = 20.0f; + goto ready; + break; + } + + index -= GLOBAL_PARAMETERS_COUNT; + + band = index / BANDS_COUNT; + index %= BANDS_COUNT; + + sprintf(strBuf, "%i:", band); + + switch (index) + { + case BAND_PARAMETER_ACTIVE: + strcat(strBuf, "Active"); + param.hints |= PARAMETER_IS_BOOLEAN; + param.name = strdup(strBuf); + param.ranges.max = 1.0f; + break; + + case BAND_PARAMETER_FREQUENCY: + strcat(strBuf, "Frequency"); + param.hints |= PARAMETER_IS_LOGARITHMIC; + param.name = strdup(strBuf); + param.unit = strdup("Hz"); + + switch (band) + { + case 0: + param.ranges.min = 20.0f; + param.ranges.max = 2000.0f; + break; + case 1: + param.ranges.min = 40.0f; + param.ranges.max = 4000.0f; + break; + case 2: + param.ranges.min = 100.0f; + param.ranges.max = 10000.0f; + break; + case 3: + param.ranges.min = 200.0f; + param.ranges.max = 20000.0f; + break; + } + break; + + case BAND_PARAMETER_BANDWIDTH: + strcat(strBuf, "Bandwidth"); + param.hints |= PARAMETER_IS_LOGARITHMIC; + param.name = strdup(strBuf); + param.ranges.min = 0.125f; + param.ranges.max = 8.0f; + break; + + case BAND_PARAMETER_GAIN: + strcat(strBuf, "Gain"); + param.name = strdup(strBuf); + param.unit = strdup("dB"); + param.ranges.min = -20.0f; + param.ranges.max = 20.0f; + break; + } + +ready: + if (param.hints & PARAMETER_IS_BOOLEAN) + { + param.ranges.step = 1.0f; + param.ranges.stepSmall = 1.0f; + param.ranges.stepLarge = 1.0f; + } + else + { + float range = param.ranges.max - param.ranges.min; + param.ranges.step = range/100.0f; + param.ranges.stepSmall = range/1000.0f; + param.ranges.stepLarge = range/10.0f; + } + + return ¶m; +} + +float +nekofilter_get_parameter_value( + PluginHandle handle, + uint32_t index) +{ + if (index < GLOBAL_PARAMETERS_COUNT) + { + return nekofilter_ptr->params_global[index]; + } + else + { + assert(index >= GLOBAL_PARAMETERS_COUNT); + index -= GLOBAL_PARAMETERS_COUNT; + + return nekofilter_ptr->params_bands[index]; + } +} + +void +nekofilter_set_parameter_value( + PluginHandle handle, + uint32_t index, + float value) +{ + if (index < GLOBAL_PARAMETERS_COUNT) + { + nekofilter_ptr->params_global[index] = value; + } + else + { + assert(index >= GLOBAL_PARAMETERS_COUNT); + index -= GLOBAL_PARAMETERS_COUNT; + + nekofilter_ptr->params_bands[index] = value; + } +} + +void +nekofilter_process( + PluginHandle handle, + float** inBuffer, + float** outBuffer, + uint32_t frames, + uint32_t midiEventCount, + const MidiEvent* midiEvents) +{ + LOG_DEBUG("nekofilter_run"); + filter_run( + nekofilter_ptr->filter, + inBuffer[0], + outBuffer[0], + frames); + + return; + + // unused + (void)midiEventCount; + (void)midiEvents; +} + +void nekofilter_ui_show( + PluginHandle handle, + bool show) +{ + if (show) + { + if (nekofilter_ptr->ui == NULL) + nekofilter_ptr->ui = nekoui_instantiate(nekofilter_ptr->host); + nekoui_show(nekofilter_ptr->ui); + } + else if (nekofilter_ptr->ui != NULL) + nekoui_hide(nekofilter_ptr->ui); +} + +void nekofilter_ui_idle( + PluginHandle handle) +{ + if (nekofilter_ptr->ui != NULL) + nekoui_run(nekofilter_ptr->ui); +} + +void nekofilter_ui_set_parameter_value( + PluginHandle handle, + uint32_t index, + float value) +{ + if (nekofilter_ptr->ui != NULL) + nekoui_set_parameter_value(nekofilter_ptr->ui, index, value); +} + +void +nekofilter_cleanup( + PluginHandle handle) +{ + if (nekofilter_ptr->ui != NULL) + { + nekoui_quit(nekofilter_ptr->ui); + nekoui_cleanup(nekofilter_ptr->ui); + } + + filter_destroy(nekofilter_ptr->filter); + free(nekofilter_ptr); + + // cleanup static data + nekofilter_get_parameter_info(NULL, 0xf00baa); +} + +#undef nekofilter_ptr diff --git a/source/backend/native/nekofilter/ui.c b/source/backend/native/nekofilter/ui.c new file mode 100644 index 000000000..d7975a40f --- /dev/null +++ b/source/backend/native/nekofilter/ui.c @@ -0,0 +1,522 @@ +/* -*- Mode: C ; c-basic-offset: 2 -*- */ +/***************************************************************************** + * + * Copyright (C) 2009 Nedko Arnaudov + * Copyright (C) 2013 Filipe Coelho + * + * LV2 UI bundle shared library for communicating with a DSSI UI + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307, USA. + * + *****************************************************************************/ + +#define UI_EXECUTABLE "ui.py" + +#define WAIT_START_TIMEOUT 3000 /* ms */ +#define WAIT_ZOMBIE_TIMEOUT 3000 /* ms */ +#define WAIT_STEP 100 /* ms */ + +//#define FORK_TIME_MEASURE + +#define USE_VFORK +//#define USE_CLONE +//#define USE_CLONE2 + +#if defined(USE_VFORK) +#define FORK vfork +#define FORK_STR "vfork" +#elif defined(USE_CLONE) +#define FORK_STR "clone" +#elif defined(USE_CLONE2) +#define FORK_STR "clone2" +#else +#define FORK fork +#define FORK_STR "fork" +#endif + +#include +#include +#include +#include + +#include +#include +#if defined(FORK_TIME_MEASURE) +# include +#endif +#include +#if defined(USE_CLONE) || defined(USE_CLONE2) +# include +#endif +#include +#include +#include + +#include "CarlaNative.h" + +struct control +{ + HostDescriptor* host; + + bool running; /* true if UI launched and 'exiting' not received */ + bool visible; /* true if 'show' sent */ + + int send_pipe; /* the pipe end that is used for sending messages to UI */ + int recv_pipe; /* the pipe end that is used for receiving messages from UI */ + + pid_t pid; +}; + +static +char * +read_line( + struct control * control_ptr) +{ + ssize_t ret; + char ch; + char buf[100]; + char * ptr; + + ptr = buf; + +loop: + ret = read(control_ptr->recv_pipe, &ch, 1); + if (ret == 1 && ch != '\n') + { + *ptr++ = ch; + goto loop; + } + + if (ptr != buf) + { + *ptr = 0; + //printf("recv: \"%s\"\n", buf); + return strdup(buf); + } + + return NULL; +} + +static +bool +wait_child( + pid_t pid) +{ + pid_t ret; + int i; + + if (pid == -1) + { + fprintf(stderr, "Can't wait for pid -1\n"); + return false; + } + + for (i = 0; i < WAIT_ZOMBIE_TIMEOUT / WAIT_STEP; i++) + { + //printf("waitpid(%d): %d\n", (int)pid, i); + + ret = waitpid(pid, NULL, WNOHANG); + if (ret != 0) + { + if (ret == pid) + { + //printf("child zombie with pid %d was consumed.\n", (int)pid); + return true; + } + + if (ret == -1) + { + fprintf(stderr, "waitpid(%d) failed: %s\n", (int)pid, strerror(errno)); + return false; + } + + fprintf(stderr, "we have waited for child pid %d to exit but we got pid %d instead\n", (int)pid, (int)ret); + + return false; + } + + //printf("zombie wait %d ms ...\n", WAIT_STEP); + usleep(WAIT_STEP * 1000); /* wait 100 ms */ + } + + fprintf( + stderr, + "we have waited for child with pid %d to exit for %.1f seconds and we are giving up\n", + (int)pid, + (float)((float)WAIT_START_TIMEOUT / 1000)); + + return false; +} + +void +nekoui_run( + struct control * control_ptr) +{ + char * msg; + char * port_index_str; + char * port_value_str; + int index; + float value; + char * locale; + + msg = read_line(control_ptr); + if (msg == NULL) + { + return; + } + + locale = strdup(setlocale(LC_NUMERIC, NULL)); + setlocale(LC_NUMERIC, "POSIX"); + + if (!strcmp(msg, "port_value")) + { + port_index_str = read_line(control_ptr); + port_value_str = read_line(control_ptr); + + index = atoi(port_index_str); + if (sscanf(port_value_str, "%f", &value) == 1) + { + //printf("port %d = %f\n", port, value); + control_ptr->host->ui_parameter_changed(control_ptr->host->handle, index, value); + } + else + { + fprintf(stderr, "failed to convert \"%s\" to float\n", port_value_str); + } + + free(port_index_str); + free(port_value_str); + } + else if (!strcmp(msg, "close")) + { + control_ptr->visible = false; + control_ptr->host->ui_closed(control_ptr->host->handle); + } + else if (!strcmp(msg, "exiting")) + { + /* for a while wait child to exit, we dont like zombie processes */ + if (!wait_child(control_ptr->pid)) + { + fprintf(stderr, "force killing misbehaved child %d (exit)\n", (int)control_ptr->pid); + if (kill(control_ptr->pid, SIGKILL) == -1) + { + fprintf(stderr, "kill() failed: %s (exit)\n", strerror(errno)); + } + else + { + wait_child(control_ptr->pid); + } + } + + control_ptr->running = false; + control_ptr->visible = false; + control_ptr->host->ui_closed(control_ptr->host->handle); + } + else + { + printf("unknown message: \"%s\"\n", msg); + } + + setlocale(LC_NUMERIC, locale); + free(locale); + + free(msg); +} + +void +nekoui_show( + struct control * control_ptr) +{ + if (control_ptr->visible) + { + return; + } + + write(control_ptr->send_pipe, "show\n", 5); + control_ptr->visible = true; +} + +void +nekoui_hide( + struct control * control_ptr) +{ + if (!control_ptr->visible) + { + return; + } + + write(control_ptr->send_pipe, "hide\n", 5); + control_ptr->visible = false; +} + +void +nekoui_quit( + struct control * control_ptr) +{ + write(control_ptr->send_pipe, "quit\n", 5); + control_ptr->visible = false; +} + +#if defined(FORK_TIME_MEASURE) +static +uint64_t +get_current_time() +{ + struct timeval time; + + if (gettimeofday(&time, NULL) != 0) + return 0; + + return (uint64_t)time.tv_sec * 1000000 + (uint64_t)time.tv_usec; +} + +#define FORK_TIME_MEASURE_VAR_NAME ____t + +#define FORK_TIME_MEASURE_VAR uint64_t FORK_TIME_MEASURE_VAR_NAME +#define FORK_TIME_MEASURE_BEGIN FORK_TIME_MEASURE_VAR_NAME = get_current_time() +#define FORK_TIME_MEASURE_END(msg) \ + { \ + FORK_TIME_MEASURE_VAR_NAME = get_current_time() - FORK_TIME_MEASURE_VAR_NAME; \ + fprintf(stderr, msg ": %llu us\n", (unsigned long long)FORK_TIME_MEASURE_VAR_NAME); \ + } + +#else + +#define FORK_TIME_MEASURE_VAR +#define FORK_TIME_MEASURE_BEGIN +#define FORK_TIME_MEASURE_END(msg) + +#endif + +#if defined(USE_CLONE) || defined(USE_CLONE2) + +static int clone_fn(void * context) +{ + execvp(*(const char **)context, (char **)context); + return -1; +} + +#endif + +static +struct control* +nekoui_instantiate( + HostDescriptor* host) +{ + struct control * control_ptr; + char * filename; + int pipe1[2]; /* written by host process, read by plugin UI process */ + int pipe2[2]; /* written by plugin UI process, read by host process */ + char ui_recv_pipe[100]; + char ui_send_pipe[100]; + int oldflags; + FORK_TIME_MEASURE_VAR; + const char * argv[5]; + int ret; + int i; + char ch; + + control_ptr = malloc(sizeof(struct control)); + if (control_ptr == NULL) + { + goto fail; + } + + control_ptr->host = host; + + if (pipe(pipe1) != 0) + { + fprintf(stderr, "pipe1 creation failed.\n"); + } + + if (pipe(pipe2) != 0) + { + fprintf(stderr, "pipe2 creation failed.\n"); + } + + snprintf(ui_recv_pipe, sizeof(ui_recv_pipe), "%d", pipe1[0]); /* [0] means reading end */ + snprintf(ui_send_pipe, sizeof(ui_send_pipe), "%d", pipe2[1]); /* [1] means writting end */ + + // FIXME + const char* bundle_path = "/home/falktx/Personal/FOSS/GIT/Carla/source/backend/native/nekofilter/"; + + filename = malloc(strlen(bundle_path) + strlen(UI_EXECUTABLE) + 1); + if (filename == NULL) + { + goto fail_free_control; + } + + strcpy(filename, bundle_path); + strcat(filename, UI_EXECUTABLE); + + control_ptr->running = false; + control_ptr->visible = false; + + control_ptr->pid = -1; + + argv[0] = "python"; + argv[1] = filename; + argv[2] = ui_recv_pipe; /* reading end */ + argv[3] = ui_send_pipe; /* writting end */ + argv[4] = NULL; + + FORK_TIME_MEASURE_BEGIN; + +#if defined(USE_CLONE) + { + int stack[8000]; + + ret = clone(clone_fn, stack + 4000, CLONE_VFORK, argv); + if (ret == -1) + { + fprintf(stderr, "clone() failed: %s\n", strerror(errno)); + goto fail_free_control; + } + } +#elif defined(USE_CLONE2) + fprintf(stderr, "clone2() exec not implemented yet\n"); + goto fail_free_control; +#else + ret = FORK(); + switch (ret) + { + case 0: /* child process */ + /* fork duplicated the handles, close pipe ends that are used by parent process */ +#if !defined(USE_VFORK) + /* it looks we cant do this for vfork() */ + close(pipe1[1]); + close(pipe2[0]); +#endif + + execvp(argv[0], (char **)argv); + fprintf(stderr, "exec of UI failed: %s\n", strerror(errno)); + exit(1); + case -1: + fprintf(stderr, "fork() failed to create new process for plugin UI\n"); + goto fail_free_control; + } + +#endif + + FORK_TIME_MEASURE_END(FORK_STR "() time"); + + //fprintf(stderr, FORK_STR "()-ed child process: %d\n", ret); + control_ptr->pid = ret; + + /* fork duplicated the handles, close pipe ends that are used by the child process */ + close(pipe1[0]); + close(pipe2[1]); + + control_ptr->send_pipe = pipe1[1]; /* [1] means writting end */ + control_ptr->recv_pipe = pipe2[0]; /* [0] means reading end */ + + oldflags = fcntl(control_ptr->recv_pipe, F_GETFL); + fcntl(control_ptr->recv_pipe, F_SETFL, oldflags | O_NONBLOCK); + + /* wait a while for child process to confirm it is alive */ + //printf("waiting UI start\n"); + i = 0; +loop: + ret = read(control_ptr->recv_pipe, &ch, 1); + switch (ret) + { + case -1: + if (errno == EAGAIN) + { + if (i < WAIT_START_TIMEOUT / WAIT_STEP) + { + //printf("start wait %d ms ...\n", WAIT_STEP); + usleep(WAIT_STEP * 1000); + i++; + goto loop; + } + + fprintf( + stderr, + "we have waited for child with pid %d to appear for %.1f seconds and we are giving up\n", + (int)control_ptr->pid, + (float)((float)WAIT_START_TIMEOUT / 1000)); + } + else + { + fprintf(stderr, "read() failed: %s\n", strerror(errno)); + } + break; + case 1: + if (ch == '\n') + { + return control_ptr; + } + + fprintf(stderr, "read() wrong first char '%c'\n", ch); + + break; + default: + fprintf(stderr, "read() returned %d\n", ret); + } + + fprintf(stderr, "force killing misbehaved child %d (start)\n", (int)control_ptr->pid); + + if (kill(control_ptr->pid, SIGKILL) == -1) + { + fprintf(stderr, "kill() failed: %s (start)\n", strerror(errno)); + } + + /* wait a while child to exit, we dont like zombie processes */ + wait_child(control_ptr->pid); + +fail_free_control: + free(control_ptr); + +fail: + fprintf(stderr, "lv2fil UI launch failed\n"); + return NULL; +} + +void +nekoui_cleanup( + struct control * control_ptr) +{ + //printf("cleanup() called\n"); + free(control_ptr); +} + +void nekoui_set_parameter_value( + struct control * control_ptr, + uint32_t index, + float value) +{ + char buf[100]; + int len; + char * locale; + + //printf("port_event(%u, %f) called\n", (unsigned int)port_index, *(float *)buffer); + + locale = strdup(setlocale(LC_NUMERIC, NULL)); + setlocale(LC_NUMERIC, "POSIX"); + + write(control_ptr->send_pipe, "port_value\n", 11); + len = sprintf(buf, "%u\n", (unsigned int)index); + write(control_ptr->send_pipe, buf, len); + len = sprintf(buf, "%.10f\n", value); + write(control_ptr->send_pipe, buf, len); + fsync(control_ptr->send_pipe); + + setlocale(LC_NUMERIC, locale); + free(locale); +} + +#undef control_ptr diff --git a/source/backend/native/nekofilter/ui.py b/source/backend/native/nekofilter/ui.py new file mode 100755 index 000000000..392aaf9ba --- /dev/null +++ b/source/backend/native/nekofilter/ui.py @@ -0,0 +1,1236 @@ +#!/usr/bin/env python +# +# Copyright (C) 2008,2009 Nedko Arnaudov +# Copyright (C) 2006 Leonard Ritter +# Filter response code by Fons Adriaensen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys +import os +import fcntl +import gtk +import gobject +import cairo +from math import pi, sin, cos, atan2, log, sqrt, hypot, log10 +from colorsys import hls_to_rgb, rgb_to_hls + +def map_coords_linear(x,y): + return x,1.0-y + +def map_coords_spheric(x,y): + nx = cos(x * 2 * pi) * y + ny = -sin(x * 2 * pi) * y + return nx, ny + +def get_peaks(f, tolerance=0.01, maxd=0.01, mapfunc=map_coords_linear): + corners = 360 + yc = 1.0/corners + peaks = [] + x0,y0 = 0.0,0.0 + t0 = -9999.0 + i0 = 0 + for i in xrange(int(corners)): + p = i*yc + a = f(p) + x,y = mapfunc(p, a) + if i == 0: + x0,y0 = x,y + t = atan2((y0 - y), (x0 - x)) / (2*pi) + td = t - t0 + if (abs(td) >= tolerance): + t0 = t + peaks.append((x,y)) + x0,y0 = x,y + return peaks + +def make_knobshape(gaps, gapdepth): + def knobshape_func(x): + x = (x*gaps)%1.0 + w = 0.5 + g1 = 0.5 - w*0.5 + g2 = 0.5 + w*0.5 + if (x >= g1) and (x < 0.5): + x = (x-g1)/(w*0.5) + return 0.5 - gapdepth * x * 0.9 + elif (x >= 0.5) and (x < g2): + x = (x-0.5)/(w*0.5) + return 0.5 - gapdepth * (1-x) * 0.9 + else: + return 0.5 + return get_peaks(knobshape_func, 0.03, 0.05, map_coords_spheric) + +def hls_to_color(h,l,s): + r,g,b = hls_to_rgb(h,l,s) + return gtk.gdk.color_parse('#%04X%04X%04X' % (int(r*65535),int(g*65535),int(b*65535))) + +def color_to_hls(color): + string = color.to_string() + r = int(string[1:5], 16) / 65535.0 + g = int(string[5:9], 16) / 65535.0 + b = int(string[9:13], 16) / 65535.0 + return rgb_to_hls(r, g, b) + +MARKER_NONE = '' +MARKER_LINE = 'line' +MARKER_ARROW = 'arrow' +MARKER_DOT = 'dot' + +LEGEND_NONE = '' +LEGEND_DOTS = 'dots' # painted dots +LEGEND_LINES = 'lines' # painted ray-like lines +LEGEND_RULER = 'ruler' # painted ray-like lines + a circular one +LEGEND_RULER_INWARDS = 'ruler-inwards' # same as ruler, but the circle is on the outside +LEGEND_LED_SCALE = 'led-scale' # an LCD scale +LEGEND_LED_DOTS = 'led-dots' # leds around the knob + +class KnobTooltip: + def __init__(self): + self.tooltip_window = gtk.Window(gtk.WINDOW_POPUP) + self.tooltip = gtk.Label() + #self.tooltip.modify_fg(gtk.STATE_NORMAL, hls_to_color(0.0, 1.0, 0.0)) + self.tooltip_timeout = None + vbox = gtk.VBox() + vbox2 = gtk.VBox() + vbox2.add(self.tooltip) + vbox2.set_border_width(2) + vbox.add(vbox2) + self.tooltip_window.add(vbox) + vbox.connect('expose-event', self.on_tooltip_expose) + + def show_tooltip(self, knob): + text = knob.format_value() + rc = knob.get_allocation() + x,y = knob.window.get_origin() + self.tooltip_window.show_all() + w,h = self.tooltip_window.get_size() + wx,wy = x+rc.x-w, y+rc.y+rc.height/2-h/2 + self.tooltip_window.move(wx,wy) + rc = self.tooltip_window.get_allocation() + self.tooltip_window.window.invalidate_rect((0,0,rc.width,rc.height), False) + self.tooltip.set_text(text) + if self.tooltip_timeout: + gobject.source_remove(self.tooltip_timeout) + self.tooltip_timeout = gobject.timeout_add(500, self.hide_tooltip) + + def hide_tooltip(self): + self.tooltip_window.hide_all() + + def on_tooltip_expose(self, widget, event): + ctx = widget.window.cairo_create() + rc = widget.get_allocation() + #ctx.set_source_rgb(*hls_to_rgb(0.0, 0.0, 0.5)) + #ctx.paint() + ctx.set_source_rgb(*hls_to_rgb(0.0, 0.0, 0.5)) + ctx.translate(0.5, 0.5) + ctx.set_line_width(1) + ctx.rectangle(rc.x, rc.y,rc.width-1,rc.height-1) + ctx.stroke() + return False + + + +knob_tooltip = None +def get_knob_tooltip(): + global knob_tooltip + if not knob_tooltip: + knob_tooltip = KnobTooltip() + return knob_tooltip + +class SmartAdjustment(gtk.Adjustment): + def __init__(self, log=False, value=0, lower=0, upper=0, step_incr=0, page_incr=0, page_size=0): + self.log = log + gtk.Adjustment.__init__(self, value, lower, upper, step_incr, page_incr, page_size) + self.normalized_value = self.real2norm(self.value) + + def real2norm(self, value): + if self.log: + return log(value / self.lower, self.upper / self.lower) + else: + return (value - self.lower) / (self.upper - self.lower) + + def norm2real(self, value): + if self.log: + return self.lower * pow(self.upper / self.lower, value) + else: + return value * (self.upper - self.lower) + self.lower + + def set_value(self, value): + self.normalized_value = self.real2norm(value) + gtk.Adjustment.set_value(self, value) + + def get_normalized_value(self): + return self.normalized_value + + def set_normalized_value(self, value): + self.normalized_value = value + + if self.normalized_value < 0.0: + self.normalized_value = 0.0 + elif self.normalized_value > 1.0: + self.normalized_value = 1.0 + + self.set_value(self.norm2real(self.normalized_value)) + +class Knob(gtk.VBox): + def __init__(self): + gtk.VBox.__init__(self) + self.gapdepth = 4 + self.gaps = 10 + self.value = 0.0 + self.min_value = 0.0 + self.max_value = 127.0 + self.fg_hls = 0.0, 0.7, 0.0 + self.legend_hls = None + self.dragging = False + self.start = 0.0 + self.digits = 2 + self.segments = 13 + self.label = '' + self.marker = MARKER_LINE + self.angle = (3.0/4.0) * 2 * pi + self.knobshape = None + self.legend = LEGEND_DOTS + self.lsize = 2 + self.lscale = False + self.set_double_buffered(True) + self.connect('realize', self.on_realize) + self.connect("size_allocate", self.on_size_allocate) + self.connect('expose-event', self.on_expose) + self.set_border_width(6) + self.set_size_request(50, 50) + self.tooltip_enabled = False + self.adj = None + + def set_adjustment(self, adj): + self.min_value = 0.0 + self.max_value = 1.0 + self.value = adj.get_normalized_value() + if self.adj: + self.adj.disconnect(self.adj_id) + self.adj = adj + self.adj_id = adj.connect("value-changed", self.on_adj_value_changed) + + def is_sensitive(self): + return self.get_property("sensitive") + + def format_value(self): + if self.adj: + value = self.adj.value + else: + value = self.value + return ("%%.%if" % self.digits) % value + + def show_tooltip(self): + if self.tooltip_enabled: + get_knob_tooltip().show_tooltip(self) + + def on_realize(self, widget): + self.root = self.get_toplevel() + self.root.add_events(gtk.gdk.ALL_EVENTS_MASK) + self.root.connect('scroll-event', self.on_mousewheel) + self.root.connect('button-press-event', self.on_left_down) + self.root.connect('button-release-event', self.on_left_up) + self.root.connect('motion-notify-event', self.on_motion) + self.update_knobshape() + + def update_knobshape(self): + rc = self.get_allocation() + b = self.get_border_width() + size = min(rc.width, rc.height) - 2*b + gd = float(self.gapdepth*0.5) / size + self.gd = gd + self.knobshape = make_knobshape(self.gaps, gd) + + def set_legend_scale(self, scale): + self.lscale = scale + self.refresh() + + def set_legend_line_width(self, width): + self.lsize = width + self.refresh() + + def set_segments(self, segments): + self.segments = segments + self.refresh() + + def set_marker(self, marker): + self.marker = marker + self.refresh() + + def set_range(self, minvalue, maxvalue): + self.min_value = minvalue + self.max_value = maxvalue + self.set_value(self.value) + + def quantize_value(self, value): + scaler = 10**self.digits + value = int((value*scaler)+0.5) / float(scaler) + return value + + def on_adj_value_changed(self, adj): + new_value = adj.get_normalized_value() + if self.value != new_value: + self.value = new_value + self.refresh() + + def set_value(self, value): + oldval = self.value + self.value = min(max(self.quantize_value(value), self.min_value), self.max_value) + if self.value != oldval: + if self.adj: + self.adj.set_normalized_value(value) + self.refresh() + + def get_value(self): + return self.value + + def set_top_color(self, h, l, s): + self.fg_hls = h,l,s + self.refresh() + + def set_legend_color(self, h, l, s): + self.legend_hls = h,l,s + self.refresh() + + def get_top_color(self): + return self.fg_hls + + def set_gaps(self, gaps): + self.gaps = gaps + self.knobshape = None + self.refresh() + + def get_gaps(self): + return self.gaps + + def set_gap_depth(self, gapdepth): + self.gapdepth = gapdepth + self.knobshape = None + self.refresh() + + def get_gap_depth(self): + return self.gapdepth + + def set_angle(self, angle): + self.angle = angle + self.refresh() + + def get_angle(self): + return self.angle + + def set_legend(self, legend): + self.legend = legend + self.refresh() + + def get_legend(self): + return self.legend + + def on_left_down(self, widget, event): + #print "on_left_down" + + # dont drag insensitive widgets + if not self.is_sensitive(): + return False + + if not sum(self.get_allocation().intersect((int(event.x), int(event.y), 1, 1))): + return False + if event.button == 1: + #print "start draggin" + self.startvalue = self.value + self.start = event.y + self.dragging = True + self.show_tooltip() + self.grab_add() + return True + return False + + def on_left_up(self, widget, event): + #print "on_left_up" + if not self.dragging: + return False + if event.button == 1: + #print "stop draggin" + self.dragging = False + self.grab_remove() + return True + return False + + def on_motion(self, widget, event): + #print "on_motion" + + # dont drag insensitive widgets + if not self.is_sensitive(): + return False + + if self.dragging: + x,y,state = self.window.get_pointer() + rc = self.get_allocation() + range = self.max_value - self.min_value + scale = rc.height + if event.state & gtk.gdk.SHIFT_MASK: + scale = rc.height*8 + value = self.startvalue - ((y - self.start)*range)/scale + oldval = self.value + self.set_value(value) + self.show_tooltip() + if oldval != self.value: + self.start = y + self.startvalue = self.value + return True + return False + + def on_mousewheel(self, widget, event): + + # dont move insensitive widgets + if not self.is_sensitive(): + return False + + if not sum(self.get_allocation().intersect((int(event.x), int(event.y), 1, 1))): + return + range = self.max_value - self.min_value + minstep = 1.0 / (10**self.digits) + if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.BUTTON1_MASK): + step = minstep + else: + step = max(self.quantize_value(range/25.0), minstep) + value = self.value + if event.direction == gtk.gdk.SCROLL_UP: + value += step + elif event.direction == gtk.gdk.SCROLL_DOWN: + value -= step + self.set_value(value) + self.show_tooltip() + + def on_size_allocate(self, widget, allocation): + #print allocation.x, allocation.y, allocation.width, allocation.height + self.update_knobshape() + + def draw_points(self, ctx, peaks): + ctx.move_to(*peaks[0]) + for peak in peaks[1:]: + ctx.line_to(*peak) + + def draw(self, ctx): + if not self.legend_hls: + self.legend_hls = color_to_hls(self.style.fg[gtk.STATE_NORMAL]) + + if not self.knobshape: + self.update_knobshape() + startangle = pi*1.5 - self.angle*0.5 + angle = ((self.value - self.min_value) / (self.max_value - self.min_value)) * self.angle + startangle + rc = self.get_allocation() + size = min(rc.width, rc.height) + + kh = self.get_border_width() # knob height + + ps = 1.0/size # pixel size + ps2 = 1.0 / (size-(2*kh)-1) # pixel size inside knob + ss = ps * kh # shadow size + lsize = ps2 * self.lsize # legend line width + # draw spherical + ctx.translate(rc.x, rc.y) + ctx.translate(0.5,0.5) + ctx.translate(size*0.5, size*0.5) + ctx.scale(size-(2*kh)-1, size-(2*kh)-1) + if self.legend == LEGEND_DOTS: + ctx.save() + ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls)) + dots = self.segments + for i in xrange(dots): + s = float(i)/(dots-1) + a = startangle + self.angle*s + ctx.save() + ctx.rotate(a) + r = lsize*0.5 + if self.lscale: + r = max(r*s,ps2) + ctx.arc(0.5+lsize, 0.0, r, 0.0, 2*pi) + ctx.fill() + ctx.restore() + ctx.restore() + elif self.legend in (LEGEND_LINES, LEGEND_RULER, LEGEND_RULER_INWARDS): + ctx.save() + ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls)) + dots = self.segments + n = ps2*(kh-1) + for i in xrange(dots): + s = float(i)/(dots-1) + a = startangle + self.angle*s + ctx.save() + ctx.rotate(a) + r = n*0.9 + if self.lscale: + r = max(r*s,ps2) + ctx.move_to(0.5+ps2+n*0.1, 0.0) + ctx.line_to(0.5+ps2+n*0.1+r, 0.0) + ctx.set_line_width(lsize) + ctx.stroke() + ctx.restore() + ctx.restore() + if self.legend == LEGEND_RULER: + ctx.save() + ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls)) + ctx.set_line_width(lsize) + ctx.arc(0.0, 0.0, 0.5+ps2+n*0.1, startangle, startangle+self.angle) + ctx.stroke() + ctx.restore() + elif self.legend == LEGEND_RULER_INWARDS: + ctx.save() + ctx.set_source_rgb(*hls_to_rgb(*self.legend_hls)) + ctx.set_line_width(lsize) + ctx.arc(0.0, 0.0, 0.5+ps2+n, startangle, startangle+self.angle) + ctx.stroke() + + # draw shadow only for sensitive widgets that have height + if self.is_sensitive() and kh: + ctx.save() + ctx.translate(ss, ss) + ctx.rotate(angle) + self.draw_points(ctx, self.knobshape) + ctx.close_path() + ctx.restore() + ctx.set_source_rgba(0,0,0,0.3) + ctx.fill() + + if self.legend in (LEGEND_LED_SCALE, LEGEND_LED_DOTS): + ch,cl,cs = self.legend_hls + n = ps2*(kh-1) + ctx.save() + ctx.set_line_cap(cairo.LINE_CAP_ROUND) + ctx.set_source_rgb(*hls_to_rgb(ch,cl*0.2,cs)) + ctx.set_line_width(lsize) + ctx.arc(0.0, 0.0, 0.5+ps2+n*0.5, startangle, startangle+self.angle) + ctx.stroke() + ctx.set_source_rgb(*hls_to_rgb(ch,cl,cs)) + if self.legend == LEGEND_LED_SCALE: + ctx.set_line_width(lsize-ps2*2) + ctx.arc(0.0, 0.0, 0.5+ps2+n*0.5, startangle, angle) + ctx.stroke() + elif self.legend == LEGEND_LED_DOTS: + dots = self.segments + dsize = lsize-ps2*2 + seg = self.angle/dots + endangle = startangle + self.angle + for i in xrange(dots): + s = float(i)/(dots-1) + a = startangle + self.angle*s + if ((a-seg*0.5) > angle) or (angle == startangle): + break + ctx.save() + ctx.rotate(a) + r = dsize*0.5 + if self.lscale: + r = max(r*s,ps2) + ctx.arc(0.5+ps2+n*0.5, 0.0, r, 0.0, 2*pi) + ctx.fill() + ctx.restore() + ctx.restore() + pat = cairo.LinearGradient(-0.5, -0.5, 0.5, 0.5) + pat.add_color_stop_rgb(1.0, 0.2,0.2,0.2) + pat.add_color_stop_rgb(0.0, 0.3,0.3,0.3) + ctx.set_source(pat) + ctx.rotate(angle) + self.draw_points(ctx, self.knobshape) + ctx.close_path() + ctx.fill_preserve() + ctx.set_source_rgba(0.1,0.1,0.1,1) + ctx.save() + ctx.identity_matrix() + ctx.set_line_width(1.0) + ctx.stroke() + ctx.restore() + + ctx.arc(0.0, 0.0, 0.5-self.gd, 0.0, pi*2.0) + ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2])) + ctx.fill() + ctx.arc(0.0, 0.0, 0.5-self.gd-ps, 0.0, pi*2.0) + ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2])) + ctx.fill() + ctx.arc(0.0, 0.0, 0.5-self.gd-(2*ps), 0.0, pi*2.0) + ctx.set_source_rgb(*hls_to_rgb(*self.fg_hls)) + ctx.fill() + + # dont draw cap for insensitive widgets + if not self.is_sensitive(): + return + + #~ ctx.set_line_cap(cairo.LINE_CAP_ROUND) + #~ ctx.move_to(0.5-0.3-self.gd-ps, 0.0) + #~ ctx.line_to(0.5-self.gd-ps*5, 0.0) + + if self.marker == MARKER_LINE: + ctx.set_line_cap(cairo.LINE_CAP_BUTT) + ctx.move_to(0.5-0.3-self.gd-ps, 0.0) + ctx.line_to(0.5-self.gd-ps, 0.0) + ctx.save() + ctx.identity_matrix() + ctx.translate(0.5,0.5) + ctx.set_line_width(5) + ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2])) + ctx.stroke_preserve() + ctx.set_line_width(3) + ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2])) + ctx.stroke() + ctx.restore() + elif self.marker == MARKER_DOT: + ctx.arc(0.5-0.05-self.gd-ps*5, 0.0, 0.05, 0.0, 2*pi) + ctx.save() + ctx.identity_matrix() + ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2])) + ctx.stroke_preserve() + ctx.set_line_width(1) + ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2])) + ctx.fill() + ctx.restore() + elif self.marker == MARKER_ARROW: + ctx.set_line_cap(cairo.LINE_CAP_BUTT) + ctx.move_to(0.5-0.3-self.gd-ps, 0.1) + ctx.line_to(0.5-0.1-self.gd-ps, 0.0) + ctx.line_to(0.5-0.3-self.gd-ps, -0.1) + ctx.close_path() + ctx.save() + ctx.identity_matrix() + #~ ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], min(self.fg_hls[1]*1.2,1.0), self.fg_hls[2])) + #~ ctx.stroke_preserve() + ctx.set_line_width(1) + ctx.set_source_rgb(*hls_to_rgb(self.fg_hls[0], max(self.fg_hls[1]*0.4,0.0), self.fg_hls[2])) + ctx.fill() + ctx.restore() + + def refresh(self): + rect = self.get_allocation() + if self.window: + self.window.invalidate_rect(rect, False) + return True + + def on_expose(self, widget, event): + self.context = self.window.cairo_create() + self.draw(self.context) + return False + +class filter_band: + def __init__(self): + self.fsamp = 48e3 + + def set_params(self, freq, bandw, gain): + freq_ratio = freq / self.fsamp + gain2 = pow(10.0, 0.05 * gain) + b = 7 * bandw * freq_ratio / sqrt(gain2) + self.gn = 0.5 * (gain2 - 1) + self.v1 = -cos(2 * pi * freq_ratio) + self.v2 = (1 - b) / (1 + b) + self.v1 *= (1 + self.v2) + self.gn *= (1 - self.v2) + + def get_response(self, freq): + w = 2 * pi * (freq / self.fsamp) + c1 = cos(w) + s1 = sin(w) + c2 = cos(2 * w) + s2 = sin(2 * w) + + x = c2 + self.v1 * c1 + self.v2 + y = s2 + self.v1 * s1 + t1 = hypot(x, y) + x += self.gn * (c2 - 1) + y += self.gn * s2 + t2 = hypot(x, y) + + #return t2 / t1 + return 20 * log10(t2 / t1) + +class frequency_response(gtk.DrawingArea): + def __init__(self): + gtk.DrawingArea.__init__(self) + + self.connect("expose-event", self.on_expose) + self.connect("size-request", self.on_size_request) + self.connect("size_allocate", self.on_size_allocate) + + self.color_bg = gtk.gdk.Color(0,0,0) + self.color_value = gtk.gdk.Color(int(65535 * 0.8), int(65535 * 0.7), 0) + self.color_mark = gtk.gdk.Color(int(65535 * 0.3), int(65535 * 0.3), int(65535 * 0.3)) + self.color_sum = gtk.gdk.Color(int(65535 * 1.0), int(65535 * 1.0), int(65535 * 1.0)) + self.width = 0 + self.height = 0 + self.margin = 10 + self.db_range = 30 + self.master_gain = 0.0 + self.master_enabled = False + + self.filters = {} + + def on_expose(self, widget, event): + cairo_ctx = widget.window.cairo_create() + + # set a clip region for the expose event + cairo_ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) + cairo_ctx.clip() + + self.draw(cairo_ctx) + + return False + + def on_size_allocate(self, widget, allocation): + #print allocation.x, allocation.y, allocation.width, allocation.height + self.width = float(allocation.width) + self.height = float(allocation.height) + self.font_size = 10 + + def on_size_request(self, widget, requisition): + #print "size-request, %u x %u" % (requisition.width, requisition.height) + requisition.width = 150 + requisition.height = 150 + return + + def invalidate_all(self): + self.queue_draw_area(0, 0, int(self.width), int(self.height)) + + def get_x(self, hz): + width = self.width - 3.5 * self.margin + #x = self.margin + width * (hz - 20) / (20000 - 20) + x = 2.5 * self.margin + width * log(hz / 20.0, 1000.0) + #print x + return x + + def get_freq(self, x): + width = self.width - 3.5 * self.margin + return 20 * pow(1000, (x - 2.5 * self.margin) / width) + + def get_y(self, db): + height = self.height - 2.5 * self.margin + y = self.margin + height * (self.db_range - db) / (self.db_range * 2) + #print y + return y + + def draw_db_grid(self, cairo_ctx, db): + x = self.get_x(20) + y = self.get_y(db) + cairo_ctx.move_to(x, y) + cairo_ctx.line_to(self.get_x(20000), y) + + if db % 10 == 0: + x -= 20 + y += 3 + cairo_ctx.move_to(x, y) + label = "%+d" % db + if db == 0: + label = " " + label + cairo_ctx.show_text(label) + + cairo_ctx.stroke() + + def invalidate_all(self): + self.queue_draw_area(0, 0, int(self.width), int(self.height)) + + def draw(self, cairo_ctx): + cairo_ctx.select_font_face("Fixed") + + cairo_ctx.set_source_color(self.color_bg) + cairo_ctx.rectangle(0, 0, self.width, self.height) + cairo_ctx.fill() + + cairo_ctx.set_source_color(self.color_mark) + cairo_ctx.set_line_width(1); + + for hz in range(20, 101, 10) + range(100, 1001, 100) + range(1000, 10001, 1000) + range(10000, 20001, 10000): + if hz >= 10000: + label = "%dk" % int(hz / 1000) + elif hz >= 1000: + label = "%dk" % int(hz / 1000) + else: + label = "%d" % int(hz) + first_digit = int(label[0]) + if first_digit > 5 or (first_digit > 3 and (len(label) == 3)): + label = None + + x = self.get_x(hz) + cairo_ctx.move_to(x, self.get_y(self.db_range)) + y = self.get_y(-self.db_range) + cairo_ctx.line_to(x, y) + if label: + y += 10 + if hz == 20000: + x -= 15 + elif hz != 20: + x -= 3 + cairo_ctx.move_to(x, y) + cairo_ctx.show_text(label) + cairo_ctx.stroke() + + for db in range(0, self.db_range + 1, 5): + self.draw_db_grid(cairo_ctx, db) + + if db != 0: + self.draw_db_grid(cairo_ctx, -db) + + curves = [[x, {}, self.master_gain, self.get_freq(x)] for x in range(int(self.get_x(20)), int(self.get_x(20e3)))] + #print repr(curves) + + # calculate filter responses + for label, filter in self.filters.items(): + if not filter.enabled: + continue + + for point in curves: + db = filter.get_response(point[3]) + point[1][label] = [self.get_y(db), db] + + # calculate sum curve + for point in curves: + for label, filter_point in point[1].items(): + point[2] += filter_point[1] + #print point + + # draw filter curves + for label, filter in self.filters.items(): + if not filter.enabled: + continue + + cairo_ctx.set_source_color(filter.color) + cairo_ctx.move_to(curves[0][0], curves[0][1][label][0]) + for point in curves: + cairo_ctx.line_to(point[0], point[1][label][0]) + cairo_ctx.stroke() + + if self.master_enabled: + # draw sum curve + cairo_ctx.set_source_color(self.color_sum) + cairo_ctx.set_line_width(2); + cairo_ctx.move_to(curves[0][0], self.get_y(curves[0][2])) + for point in curves: + cairo_ctx.line_to(point[0], self.get_y(point[2])) + cairo_ctx.stroke() + + # draw base point markers + for label, filter in self.filters.items(): + if not filter.enabled: + continue + + cairo_ctx.set_source_color(self.color_value) + x = self.get_x(filter.adj_hz.value) + y = self.get_y(filter.adj_db.value) + + cairo_ctx.move_to(x, y) + cairo_ctx.show_text(label) + cairo_ctx.stroke() + + def add_filter(self, label, adj_hz, adj_db, adj_bw, color): + #print "filter %s added (%.2f Hz, %.2f dB, %.2f bw)" % (label, adj_hz.value, adj_db.value, adj_bw.value) + filter = filter_band() + filter.enabled = False + filter.label = label + filter.color = color + filter.set_params(adj_hz.value, adj_bw.value, adj_db.value) + adj_hz.filter = filter + adj_db.filter = filter + adj_bw.filter = filter + filter.adj_hz = adj_hz + filter.adj_db = adj_db + filter.adj_bw = adj_bw + adj_hz.connect("value-changed", self.on_value_change_request) + adj_db.connect("value-changed", self.on_value_change_request) + adj_bw.connect("value-changed", self.on_value_change_request) + self.filters[label] = filter + + def enable_filter(self, label): + filter = self.filters[label] + #print "filter %s enabled (%.2f Hz, %.2f dB, %.2f bw)" % (label, filter.adj_hz.value, filter.adj_db.value, filter.adj_bw.value) + filter.enabled = True + self.invalidate_all() + + def disable_filter(self, label): + filter = self.filters[label] + #print "filter %s disabled (%.2f Hz, %.2f dB, %.2f bw)" % (label, filter.adj_hz.value, filter.adj_db.value, filter.adj_bw.value) + filter.enabled = False + self.invalidate_all() + + def on_value_change_request(self, adj): + #print "adj changed" + adj.filter.set_params(adj.filter.adj_hz.value, adj.filter.adj_bw.value, adj.filter.adj_db.value) + self.invalidate_all() + + def master_enable(self): + self.master_enabled = True; + self.invalidate_all() + + def master_disable(self): + self.master_enabled = False; + self.invalidate_all() + + def set_master_gain(self, gain): + self.master_gain = gain; + self.invalidate_all() + +class filter_ui: + def __init__(self, argv): + self.fake = len(argv) == 1 + + if self.fake: + self.shown = False + else: + self.recv_pipe_fd = int(argv[1]) + self.send_pipe_fd = int(argv[2]) + + oldflags = fcntl.fcntl(self.recv_pipe_fd, fcntl.F_GETFL) + fcntl.fcntl(self.recv_pipe_fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) + + self.recv_pipe = os.fdopen(self.recv_pipe_fd, 'r') + self.send_pipe = os.fdopen(self.send_pipe_fd, 'w') + + self.port_base = 0 + #self.lv2logo = gtk.gdk.pixbuf_new_from_file(self.bundle_path + "/lv2logo.png") + + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + #self.window.set_size_request(600, 400) + self.window.set_title("NekoFilter (GUI)") + self.window.set_role("plugin_ui") + + self.top_vbox = gtk.VBox() + self.top_vbox.set_spacing(10) + + align = gtk.Alignment(0.5, 0.5, 1.0, 1.0) + align.set_padding(10, 10, 10, 10) + align.add(self.top_vbox) + + self.window.add(align) + + self.fr = frequency_response() + self.fr.set_size_request(400, 200) + + frame = gtk.Frame() + frame.set_shadow_type(gtk.SHADOW_ETCHED_OUT) + frame.add(self.fr) + + self.top_vbox.pack_start(frame, True, True) + + self.param_hbox = gtk.HBox() + self.top_vbox.pack_start(self.param_hbox) + + self.param_hbox.set_spacing(10) + + self.initator = False + + self.ports = [] + + misc_box = gtk.VBox() + misc_box.set_spacing(5) + + master_frame = gtk.Frame("Master") + master_frame.set_label_align(0.5, 0.5) + + master_box = gtk.VBox() + master_box.set_spacing(5) + + port = {'index': 0, 'name': 'Active', 'type': 'toggle'} + self.ports.append(port) + self.add_param_box(master_box, self.create_toggle_box(port)) + + port = {'index': 1, 'name': 'Gain', 'type': 'knob', 'min': -20.0, 'max': 20.0, 'unit': 'dB', 'log': False} + self.ports.append(port) + self.add_param_box(master_box, self.create_knob_box(port)) + + master_frame.add(master_box) + misc_box.pack_start(master_frame, False, False) + + #logo = gtk.Image() + #logo.set_from_pixbuf(self.lv2logo) + #misc_box.pack_start(logo, True, True) + + button_box = gtk.VBox() + + button = gtk.Button(stock=gtk.STOCK_ABOUT) + button.connect("clicked", self.on_about) + button_box.pack_start(button) + + button = gtk.Button(stock=gtk.STOCK_CLOSE) + button.connect("clicked", self.on_window_closed) + button_box.pack_start(button) + + align = gtk.Alignment(0.5, 1.0, 1.0, 0.0) + align.add(button_box) + misc_box.pack_start(align, True, True) + + band_parameters = [ + {'name': 'Active', 'type': 'toggle'}, + {'name': 'Frequency', 'type': 'knob', 'unit': 'Hz', 'log': True}, + {'name': 'Bandwidth', 'type': 'knob', 'min': 0.125, 'max': 8.0, 'unit': '', 'log': True}, + {'name': 'Gain', 'type': 'knob', 'min': -20.0, 'max': 20.0, 'unit': 'dB', 'log': False}] + + freq_min = [ 20.0, 40.0, 100.0, 200.0] + freq_max = [2000.0, 4000.0, 10000.0, 20000.0] + + port_index = 2 + + filter_colors = [gtk.gdk.Color(int(65535 * 1.0), int(65535 * 0.6), int(65535 * 0.0)), + gtk.gdk.Color(int(65535 * 0.6), int(65535 * 1.0), int(65535 * 0.6)), + gtk.gdk.Color(int(65535 * 0.0), int(65535 * 0.6), int(65535 * 1.0)), + gtk.gdk.Color(int(65535 * 0.9), int(65535 * 0.0), int(65535 * 0.5))] + + for i in [0, 1, 2, 3]: + band_frame = gtk.Frame("Band %d" % (i + 1)) + band_frame.set_label_align(0.5, 0.5) + + band_box = gtk.VBox() + band_box.set_spacing(5) + + for parameter in band_parameters: + + port = parameter.copy() + port['index'] = port_index + port_index += 1 + + if port['name'] == 'Frequency': + port['min'] = freq_min[i] + port['max'] = freq_max[i] + + self.ports.append(port) + + #param_box.set_spacing(5) + if port['type'] == 'knob': + self.add_param_box(band_box, self.create_knob_box(port)) + elif port['type'] == 'toggle': + self.add_param_box(band_box, self.create_toggle_box(port)) + + self.fr.add_filter( + str(i + 1), + self.ports[port_index - 3]['adj'], # frequency + self.ports[port_index - 1]['adj'], # gain + self.ports[port_index - 2]['adj'], # bandwidth + filter_colors[i]) + + band_frame.add(band_box) + + self.param_hbox.pack_start(band_frame, True, True) + + self.param_hbox.pack_start(misc_box, True, True) + + self.initator = True + + def on_about(self, widget): + about = gtk.AboutDialog() + about.set_transient_for(self.window) + about.set_name("4-band parametric filter") + #about.set_website(program_data['website']) + about.set_authors(["Nedko Arnaudov - LV2 plugin and GUI", 'Fons Adriaensen - DSP code']) + about.set_artists(["LV2 logo has been designed by Thorsten Wilms, based on a concept from Peter Shorthose."]) + #about.set_logo(self.lv2logo) + about.show() + about.run() + about.hide() + + def create_knob_box(self, port): + param_box = gtk.VBox() + step = (port['max'] - port['min']) / 100 + adj = SmartAdjustment(port['log'], port['min'], port['min'], port['max'], step, step * 20) + adj.port = port + port['adj'] = adj + + adj.connect("value-changed", self.on_adj_value_changed) + + knob = Knob() + knob.set_adjustment(adj) + align = gtk.Alignment(0.5, 0.5, 0, 0) + align.set_padding(0, 0, 20, 20) + align.add(knob) + param_box.pack_start(align, False) + + adj.label = gtk.Label(self.get_adj_value_text(adj)[0]) + param_box.pack_start(adj.label, False) + #spin = gtk.SpinButton(adj, 0.0, 2) + #param_box.pack_start(spin, False) + + label = gtk.Label(port['name']) + param_box.pack_start(label, False) + return param_box + + def create_toggle_box(self, port): + param_box = gtk.VBox() + button = gtk.CheckButton(port['name']) + button.port = port + port['widget'] = button + + button.connect("toggled", self.on_button_toggled) + + align = gtk.Alignment(0.5, 0.5, 0, 0) + align.add(button) + param_box.pack_start(align, False) + return param_box + + def add_param_box(self, container, param_box): + align = gtk.Alignment(0.5, 0.5, 1.0, 1.0) + align.set_padding(10, 10, 10, 10) + align.add(param_box) + + container.pack_start(align, True) + + def get_adj_value_text(self, adj): + value = adj.get_value() + if value >= 10000: + format = "%.0f" + elif value >= 1000: + format = "%.1f" + else: + format = "%.2f" + text = format % value + unit = adj.port['unit'] + if unit: + text += " " + unit + + return value, text + + def on_adj_value_changed(self, adj): + value, text = self.get_adj_value_text(adj) + adj.label.set_text(text) + + if adj.port['index'] == 1: + #print "Master gain = %.2f dB" % adj.get_value() + self.fr.set_master_gain(adj.get_value()) + + if self.initator: + #print adj.port, adj.get_value() + self.send_port_value(adj.port['index'] + self.port_base, value) + + def on_button_toggled(self, widget): + port_index = widget.port['index'] + band_no = (port_index - 2) / 4 + 1 + if widget.get_active(): + value = 1.0 + if band_no > 0: + self.fr.enable_filter(str(band_no)) + else: + self.fr.master_enable() + else: + value = 0.0 + if band_no > 0: + self.fr.disable_filter(str(band_no)) + else: + self.fr.master_disable() + + if self.initator: + self.send_port_value(port_index + self.port_base, value) + + def send(self, lines): + if self.fake: + return + + for line in lines: + #print 'send: "' + line + '"' + self.send_pipe.write(line + "\n") + self.send_pipe.flush() + + def send_close(self): + self.send(["close"]) + + def send_exiting(self): + self.send(["exiting"]) + + def send_port_value(self, port_index, value): + self.send(["port_value", str(port_index), "%.10f" % value]) + + def send_hi(self): + self.send([""]) # send empty line (just newline char) + + def recv_line(self): + return self.recv_pipe.readline().strip() + + def recv_command(self): + try: + msg = self.recv_line() + + if msg == "port_value": + port_index = int(self.recv_line()) + port_value = float(self.recv_line()) + #print "port_value_change recevied: %d %f" % (port_index, port_value) + self.on_port_value_changed(port_index, port_value) + elif msg == "show": + self.on_show() + elif msg == "hide": + self.on_hide() + elif msg == "quit": + self.on_quit() + else: + print 'unknown message: "' + msg + '"' + + return True + except IOError: + return False + + def on_recv(self, fd, cond): + #print "on_recv" + if cond == gobject.IO_HUP: + gtk.main_quit() + return False + + while True: + if not self.recv_command(): + break + + return True + + def run(self): + self.window.connect("destroy", self.on_window_closed) + + if self.fake: + if not self.shown: + self.shown = True + self.on_show() + else: + self.send_hi() + gobject.io_add_watch(self.recv_pipe_fd, gobject.IO_IN | gobject.IO_HUP, self.on_recv) + + gtk.main() + + def on_port_value_changed(self, port_index, port_value): + #print "port %d set to %f" % (port_index, port_value) + port_index -= self.port_base + port = self.ports[port_index] + #print repr(port) + port_type = port['type'] + if port_type == 'knob': + self.initator = False + port['adj'].set_value(port_value) + self.initator = True + elif port_type == 'toggle': + if port_value > 0.0: + toggled = True + else: + toggled = False + + self.initator = False + port['widget'].set_active(toggled) + self.initator = True + + def on_show(self): + self.window.show_all() + + def on_hide(self): + self.window.hide_all() + + def on_quit(self): + gtk.main_quit() + + def on_window_closed(self, arg): + self.window.hide_all() + self.send_close() + #self.send_exiting() + #gtk.main_quit() + +def main(): + filter_ui(sys.argv).run() + #print "main done" + +if __name__ == '__main__': + main()