ir.lv2-1.3.2/convert4chan.c0000644000175000017500000001260311636627117014051 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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. */ /* convert4chan utility * * Convert pairs of stereo files named *[LR].wav to four-channel files * named *4.wav. Recursively convert all suitable files under current * working directory, or full directory path supplied as first * argument. * * Compile with: 'make convert4chan' */ #include #include #include #include #include #define BUFLEN 4096 float bufL[2*BUFLEN]; float bufR[2*BUFLEN]; float buf4[4*BUFLEN]; /* exclude hidden dirs */ int dirname_filter(const char * file) { if (!file) { return 0; } if (strlen(file) < 1) { return 0; } if (file[0] == '.') { return 0; } return 1; } int filename_filter(const char * file) { if (!file) { return 0; } if (strlen(file) < 6) { return 0; } const char * ext = file + strlen(file)-5; if (strcmp(ext, "L.wav") == 0) { return 1; } if (strcmp(ext, "L.WAV") == 0) { return 1; } return 0; } void really_convert(char * file_L, char * file_R, char * file_4) { SNDFILE * FL; SNDFILE * FR; SNDFILE * F4; SF_INFO SL; SF_INFO SR; SF_INFO S4; int length; FL = sf_open(file_L, SFM_READ, &SL); if (!FL) { fprintf(stderr, "*** unable to read input file %s\n", file_L); return; } FR = sf_open(file_R, SFM_READ, &SR); if (!FR) { fprintf(stderr, "*** unable to read input file %s\n", file_R); goto close_FL; } length = SL.frames; if (SR.frames > length) { length = SR.frames; } if (SL.samplerate != SR.samplerate) { fprintf(stderr, "*** source samplerates differ!\n"); goto close_FR; } if (SL.format != SR.format) { printf("warning: sample formats differ, using left file's format for output\n"); } if (SL.channels != 2) { fprintf(stderr, "*** source L is not stereo!\n"); goto close_FR; } if (SR.channels != 2) { fprintf(stderr, "*** source R is not stereo!\n"); goto close_FR; } S4.channels = 4; S4.samplerate = SL.samplerate; S4.format = SL.format; F4 = sf_open(file_4, SFM_WRITE, &S4); if (!F4) { fprintf(stderr, "*** unable to write file %s\n", file_4); goto close_FR; } int readL = 1; int readR = 1; while (length && (readL || readR)) { int n = (length > BUFLEN) ? BUFLEN : length; int n1, n2; memset(bufL, 0, sizeof(bufL)); memset(bufR, 0, sizeof(bufR)); memset(buf4, 0, sizeof(buf4)); if (readL) { n1 = sf_readf_float(FL, bufL, n); if (n1 < 0) { fprintf(stderr, "error reading file %s\n", file_L); goto close; } else if (n1 == 0) { readL = 0; } } else { n1 = 0; } if (readR) { n2 = sf_readf_float(FR, bufR, n); if (n2 < 0) { fprintf(stderr, "error reading file %s\n", file_R); goto close; } else if (n2 == 0) { readR = 0; } } else { n2 = 0; } int min = n1; if (n2 < min) { min = n2; } int i; for (i = 0; i < min; i++) { buf4[4*i] = bufL[2*i]; buf4[4*i + 1] = bufL[2*i + 1]; buf4[4*i + 2] = bufR[2*i]; buf4[4*i + 3] = bufR[2*i + 1]; } if (n1 > min) { for (; i < n1; i++) { buf4[4*i] = bufL[2*i]; buf4[4*i + 1] = bufL[2*i + 1]; buf4[4*i + 2] = 0; buf4[4*i + 3] = 0; } } else { for (; i < n2; i++) { buf4[4*i] = 0; buf4[4*i + 1] = 0; buf4[4*i + 2] = bufR[2*i]; buf4[4*i + 3] = bufR[2*i + 1]; } } length -= i; int nw = sf_writef_float(F4, buf4, i); if (nw < 0) { fprintf(stderr, "error writing file %s\n", file_4); goto close; } } close: sf_close(F4); close_FR: sf_close(FR); close_FL: sf_close(FL); } void convert_files(char * file_L) { char * file_R = strdup(file_L); char * file_4; file_R[strlen(file_R)-5] = 'R'; printf("converting pair: %s\n", file_L); printf(" %s\n", file_R); if (!g_file_test(file_R, G_FILE_TEST_IS_REGULAR)) { fprintf(stderr, "*** %s is not a regular file, skipping pair\n", file_R); free(file_R); return; } file_4 = strdup(file_L); file_4[strlen(file_4)-5] = '4'; printf(" to output: %s\n", file_4); really_convert(file_L, file_R, file_4); printf("\n"); free(file_R); free(file_4); } void iterate_over_dir(char * dirname) { GDir * dir = g_dir_open(dirname, 0, NULL); if (!dir) { free(dirname); return; } const char * file; while ((file = g_dir_read_name(dir))) { char * filepath = g_build_filename(dirname, file, NULL); if (g_file_test(filepath, G_FILE_TEST_IS_DIR) && dirname_filter(file)) { iterate_over_dir(filepath); } else if ((g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) && filename_filter(file)) { convert_files(filepath); } g_free(filepath); } g_dir_close(dir); } int main(int argc, char ** argv) { char * dirname; if (argc > 1) { dirname = strdup(argv[1]); } else { dirname = g_get_current_dir(); } iterate_over_dir(dirname); free(dirname); return 0; } ir.lv2-1.3.2/ir_meter.cc0000644000175000017500000001170611636627117013427 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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. */ #include #include #include #include "ir_meter.h" #include "ir_utils.h" G_DEFINE_TYPE(IRMeter, ir_meter, GTK_TYPE_DRAWING_AREA); typedef struct _IRMeterPrivate IRMeterPrivate; #define IR_METER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), IR_METER_TYPE, IRMeterPrivate)) struct _IRMeterPrivate { GdkPixmap * pixmap; float level; }; static gboolean ir_meter_expose(GtkWidget * widget, GdkEventExpose * event); static gboolean ir_meter_configure(GtkWidget * widget, GdkEventConfigure * event); static void ir_meter_destroy(GtkObject * object); static void ir_meter_class_init(IRMeterClass * cls) { GtkObjectClass * obj_class = GTK_OBJECT_CLASS(cls); /* changed G_ to GTK_ */ GtkWidgetClass * widget_class = GTK_WIDGET_CLASS(cls); widget_class->expose_event = ir_meter_expose; widget_class->configure_event = ir_meter_configure; obj_class->destroy = ir_meter_destroy; g_type_class_add_private(obj_class, sizeof(IRMeterPrivate)); } static void ir_meter_init(IRMeter * w) { IRMeterPrivate * p = IR_METER_GET_PRIVATE(w); p->pixmap = NULL; p->level = 0.0; } static void ir_meter_destroy(GtkObject * object) { IRMeter * w = IR_METER(object); IRMeterPrivate * p = IR_METER_GET_PRIVATE(w); if (p->pixmap) { gdk_pixmap_unref(p->pixmap); p->pixmap = NULL; } } static void draw_fullscale(GtkWidget * widget) { IRMeterPrivate * p = IR_METER_GET_PRIVATE(widget); int w = widget->allocation.width; int h = widget->allocation.height; cairo_t * cr = gdk_cairo_create(p->pixmap); float fzero = 1.0f + 90.0f/96.0f; /* result of convert_real_to_scale(ADJ_*_GAIN, 0) */ fzero = exp10(fzero); fzero = (fzero - 10.0f) / 90.0f; int zero = h * (1.0 - fzero); cairo_rectangle(cr, 0, 0, w, zero); cairo_set_source_rgb(cr, 1, 0, 0); cairo_fill_preserve(cr); cairo_set_source_rgb(cr, 1, 0, 0); cairo_stroke(cr); int n = h - zero - 1; int i; for (i = 0; i < n/2; i++) { cairo_set_source_rgb(cr, 0, 1.0, 2.0*(float)i/n); cairo_move_to(cr, 0, zero + i + 1); cairo_line_to(cr, w, zero + i + 1); cairo_stroke(cr); } for (; i < n; i++) { cairo_set_source_rgb(cr, 0, 1.0 - 2.0*(float)(i-n/2)/n, 1.0); cairo_move_to(cr, 0, zero + i + 1); cairo_line_to(cr, w, zero + i + 1); cairo_stroke(cr); } cairo_destroy(cr); } static void draw(GtkWidget * widget) { IRMeterPrivate * p = IR_METER_GET_PRIVATE(widget); int w = widget->allocation.width; int h = widget->allocation.height; int pos = h * (1.0f - (p->level - 10.0f) / 90.0f); cairo_t * cr = gdk_cairo_create(widget->window); gdk_cairo_set_source_pixmap(cr, p->pixmap, 0, 0); cairo_rectangle(cr, 0, 0, w, pos); cairo_set_source_rgb(cr, 0, 0, 0); cairo_fill_preserve(cr); cairo_set_source_rgb(cr, 0, 0, 0); cairo_stroke(cr); cairo_destroy(cr); } static gboolean ir_meter_configure(GtkWidget * widget, GdkEventConfigure * event) { IRMeterPrivate * p = IR_METER_GET_PRIVATE(widget); int w = widget->allocation.width; int h = widget->allocation.height; if (p->pixmap) { gdk_pixmap_unref(p->pixmap); } p->pixmap = gdk_pixmap_new(widget->window, w, h, -1); draw_fullscale(widget); draw(widget); return TRUE; } static gboolean ir_meter_expose(GtkWidget * widget, GdkEventExpose * event) { IRMeterPrivate * p = IR_METER_GET_PRIVATE(widget); gdk_draw_drawable(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], p->pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); draw(widget); return FALSE; } void ir_meter_redraw(IRMeter * w) { GtkWidget * widget; GdkRegion * region; widget = GTK_WIDGET(w); if (!widget->window) { return; } region = gdk_drawable_get_clip_region(widget->window); gdk_window_invalidate_region(widget->window, region, TRUE); gdk_window_process_updates(widget->window, TRUE); gdk_region_destroy(region); } void ir_meter_redraw_all(IRMeter * w) { GtkWidget * widget = GTK_WIDGET(w); if (!widget->window) { return; } draw_fullscale(widget); ir_meter_redraw(w); } void ir_meter_set_level(IRMeter * w, float level) { if (!w || !GTK_IS_WIDGET(w)) { return; } IRMeterPrivate * p = IR_METER_GET_PRIVATE(w); p->level = level; ir_meter_redraw(w); } GtkWidget * ir_meter_new(void) { return (GtkWidget *)g_object_new(IR_METER_TYPE, NULL); } ir.lv2-1.3.2/Makefile0000644000175000017500000000421411636627117012746 0ustar miramira# Please edit PREFIX and/or INSTDIR to your liking, # then issue 'make install' as root (or 'sudo make install'). # Issue 'make convert4chan' to compile the 4-channel converter utility # (for local use, not installed by make install) PREFIX = /usr INSTDIR = $(PREFIX)/lib/lv2/ir.lv2 INST_FILES = ir.so ir_gui.so ir.ttl manifest.ttl # change "-O2 -ffast-math" to "-g -O0" below if you want to debug the plugin CPPFLAGS += -Wall -I. -I/usr/include `pkg-config --cflags gtk+-2.0` `pkg-config --cflags gthread-2.0` -D__STDC_FORMAT_MACROS -O2 -ffast-math LIBS += -lc -lm -lzita-convolver -lsamplerate -lsndfile `pkg-config --libs gthread-2.0` `pkg-config --libs gtk+-2.0` ifeq ($(shell pkg-config --atleast-version='2.16' gtk+-2.0; echo $$?), 1) $(error "At least GTK+ version 2.16 is needed to build IR.") endif ifeq ($(shell pkg-config --atleast-version='2.20' gtk+-2.0; echo $$?), 0) CPPFLAGS += -D_HAVE_GTK_ATLEAST_2_20 endif C4CFLAGS = -Wall -I. -I/usr/include `pkg-config --cflags gthread-2.0` -O2 -ffast-math C4LIBS = -lsndfile `pkg-config --libs gthread-2.0` all: ir.so ir_gui.so ir.o: ir.cc ir.h ir_utils.h g++ ir.cc $(CPPFLAGS) -c -fPIC -o ir.o ir_gui.o: ir_gui.cc ir.h ir_utils.h ir_wavedisplay.h g++ ir_gui.cc $(CPPFLAGS) -c -fPIC -o ir_gui.o ir_utils.o: ir_utils.cc ir_utils.h ir.h g++ ir_utils.cc $(CPPFLAGS) -c -fPIC -o ir_utils.o ir_meter.o: ir_meter.cc ir_meter.h ir.h ir_utils.h g++ ir_meter.cc $(CPPFLAGS) -c -fPIC -o ir_meter.o ir_modeind.o: ir_modeind.cc ir_modeind.h ir.h ir_utils.h g++ ir_modeind.cc $(CPPFLAGS) -c -fPIC -o ir_modeind.o ir_wavedisplay.o: ir_wavedisplay.cc ir_wavedisplay.h ir.h ir_utils.h g++ ir_wavedisplay.cc $(CPPFLAGS) -c -fPIC -o ir_wavedisplay.o ir.so: ir.o ir_utils.o g++ $(LDFLAGS) ir.o ir_utils.o $(LIBS) -shared -o ir.so ir_gui.so: ir_gui.o ir_utils.o ir_meter.o ir_modeind.o ir_wavedisplay.o g++ $(LDFLAGS) ir_gui.o ir_utils.o ir_meter.o ir_modeind.o ir_wavedisplay.o $(LIBS) -shared -z nodelete -o ir_gui.so convert4chan: convert4chan.c gcc $(C4CFLAGS) convert4chan.c $(C4LIBS) -o convert4chan install: all mkdir -p $(INSTDIR) cp $(INST_FILES) $(INSTDIR) clean: rm -f *~ *.o ir.so ir_gui.so convert4chan ir.lv2-1.3.2/ir_meter.h0000644000175000017500000000310611636627117013264 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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 _IR_METER_H #define _IR_METER_H #include G_BEGIN_DECLS #define IR_METER_TYPE (ir_meter_get_type()) #define IR_METER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), IR_METER_TYPE, IRMeter)) #define IR_METER_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST((obj), IR_METER, IRMeterClass)) #define IR_METER_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS ((obj), IR_METER_TYPE, IRMeterClass)) typedef struct _IRMeter IRMeter; typedef struct _IRMeterClass IRMeterClass; struct _IRMeter { GtkDrawingArea parent; /* < private > */ }; struct _IRMeterClass { GtkDrawingAreaClass parent_class; static void (*destroy)(GtkObject * object); }; GType ir_meter_get_type(); GtkWidget * ir_meter_new(void); void ir_meter_redraw(IRMeter * w); void ir_meter_redraw_all(IRMeter * w); void ir_meter_set_level(IRMeter * w, float level); G_END_DECLS #endif /* _IR_METER_H */ ir.lv2-1.3.2/ir_wavedisplay.cc0000644000175000017500000002147411636627117014646 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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. */ #include #include #include #include "ir_wavedisplay.h" #include "ir_utils.h" G_DEFINE_TYPE(IRWaveDisplay, ir_wavedisplay, GTK_TYPE_DRAWING_AREA); typedef struct _IRWaveDisplayPrivate IRWaveDisplayPrivate; #define IR_WAVEDISPLAY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), IR_WAVEDISPLAY_TYPE, IRWaveDisplayPrivate)) struct _IRWaveDisplayPrivate { GdkPixmap * pixmap; const char * msg; float progress; float * wave; /* owned by widget (copy of original data) */ int wave_length; int logarithmic; int attack_time_s; float attack_pc; float env_pc; float length_pc; int reverse; }; static gboolean ir_wavedisplay_expose(GtkWidget * widget, GdkEventExpose * event); static gboolean ir_wavedisplay_configure(GtkWidget * widget, GdkEventConfigure * event); static void ir_wavedisplay_destroy(GtkObject * object); static void ir_wavedisplay_class_init(IRWaveDisplayClass * cls) { GtkObjectClass * obj_class = GTK_OBJECT_CLASS(cls); /* changed G_ to GTK_ */ GtkWidgetClass * widget_class = GTK_WIDGET_CLASS(cls); widget_class->expose_event = ir_wavedisplay_expose; widget_class->configure_event = ir_wavedisplay_configure; obj_class->destroy = ir_wavedisplay_destroy; g_type_class_add_private(obj_class, sizeof(IRWaveDisplayPrivate)); } static void ir_wavedisplay_init(IRWaveDisplay * w) { IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(w); p->pixmap = NULL; p->msg = NULL; p->progress = -1.0; p->wave = NULL; p->wave_length = 0; p->logarithmic = 0; p->attack_time_s = 0; p->attack_pc = 0.0f; p->env_pc = 0.0f; p->length_pc = 0.0f; } static void ir_wavedisplay_destroy(GtkObject * object) { IRWaveDisplay * w = IR_WAVEDISPLAY(object); IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(w); if (p->pixmap) { gdk_pixmap_unref(p->pixmap); p->pixmap = NULL; } if (p->wave) { free(p->wave); p->wave = NULL; } } float y_transform(float value, int logarithmic) { float ret = value; if (logarithmic) { ret = 20.0*log10f(ret); if (ret < DISPLAY_DB_MIN) { ret = DISPLAY_DB_MIN; } ret /= DISPLAY_DB_MIN; } else { ret = 1 - ret; } return ret; } static void draw_wave(GtkWidget * widget) { IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(widget); int w = widget->allocation.width; int h = widget->allocation.height; cairo_t * cr = gdk_cairo_create(p->pixmap); cairo_rectangle(cr, 0, 0, w, h); cairo_set_source_rgb(cr, 0, 0, 0); cairo_fill_preserve(cr); cairo_set_source_rgb(cr, 0, 0, 0); cairo_stroke(cr); if (!p->wave || !p->wave_length) { cairo_destroy(cr); return; } int logarithmic = p->logarithmic; /* draw IR waveform */ float samples_per_pixel = (float)p->wave_length / w; float * f = p->wave; for (int i = 0; i < w; i++) { int index_0 = i * samples_per_pixel; int index_1 = (i+1) * samples_per_pixel; float rms = 0.0f, max = 0.0f, v, a; for (int j = index_0; j < index_1; j++) { v = *f++; rms += v*v; if ((a = fabs(v)) > max) { max = a; } } rms = sqrt(rms / samples_per_pixel); max = y_transform(max, logarithmic); rms = y_transform(rms, logarithmic); cairo_set_source_rgb(cr, 0, 0.25, 0.8); cairo_move_to(cr, i, h); cairo_line_to(cr, i, h * max); cairo_stroke(cr); cairo_set_source_rgb(cr, 0, 0.2, 0.6); cairo_move_to(cr, i, h); cairo_line_to(cr, i, h * rms); cairo_stroke(cr); } cairo_destroy(cr); } static void draw_envelope(GtkWidget * widget) { IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(widget); int w = widget->allocation.width; int h = widget->allocation.height; cairo_t * cr = gdk_cairo_create(widget->window); gdk_cairo_set_source_pixmap(cr, p->pixmap, 0, 0); int logarithmic = p->logarithmic; if (p->wave && p->wave_length) { /* draw envelope */ cairo_set_source_rgb(cr, 1, 0.375, 0); float * envelope = (float *)malloc(w * sizeof(float)); for (int i = 0; i < w; i++) { envelope[i] = 1.0f; } int attack_time_s = p->attack_time_s * w / p->wave_length; compute_envelope(&envelope, 1, w, attack_time_s, p->attack_pc, p->env_pc, p->length_pc); if (p->reverse) { float tmp; for (int i = 0, j = w-1; i < w/2; i++, j--) { tmp = envelope[i]; envelope[i] = envelope[j]; envelope[j] = tmp; } } float y = y_transform(envelope[0], logarithmic); for (int i = 0; i < w; i++) { float y_new = y_transform(envelope[i], logarithmic); cairo_move_to(cr, i-1, h * y); cairo_line_to(cr, i, h * y_new); cairo_stroke(cr); y = y_new; } free(envelope); } /* draw progress bar */ if (p->progress >= 0.0) { cairo_rectangle(cr, 1, h - 10, (w-1) * p->progress, 9); cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.6); cairo_fill_preserve(cr); cairo_stroke(cr); } /* draw message */ if (p->msg) { cairo_set_source_rgb(cr, 1, 1, 1); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 20.0); draw_centered_text(cr, p->msg, w/2, h/2); cairo_stroke(cr); } cairo_destroy(cr); } static gboolean ir_wavedisplay_configure(GtkWidget * widget, GdkEventConfigure * event) { IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(widget); int w = widget->allocation.width; int h = widget->allocation.height; if (p->pixmap) { gdk_pixmap_unref(p->pixmap); } p->pixmap = gdk_pixmap_new(widget->window, w, h, -1); draw_wave(widget); gdk_draw_drawable(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], p->pixmap, 0, 0, 0, 0, w, h); return TRUE; } static gboolean ir_wavedisplay_expose(GtkWidget * widget, GdkEventExpose * event) { IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(widget); gdk_draw_drawable(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], p->pixmap, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); draw_envelope(widget); return FALSE; } void ir_wavedisplay_redraw(IRWaveDisplay * w) { if (!w || !GTK_IS_WIDGET(w)) { return; } GtkWidget * widget; GdkRegion * region; widget = GTK_WIDGET(w); if (!widget->window) { return; } region = gdk_drawable_get_clip_region(widget->window); gdk_window_invalidate_region(widget->window, region, TRUE); gdk_window_process_updates(widget->window, TRUE); gdk_region_destroy(region); } void ir_wavedisplay_redraw_all(IRWaveDisplay * w) { if (!w || !GTK_IS_WIDGET(w)) { return; } GtkWidget * widget = GTK_WIDGET(w); if (!widget->window) { return; } draw_wave(widget); ir_wavedisplay_redraw(w); } void ir_wavedisplay_set_message(IRWaveDisplay * w, const char * msg) { if (!w || !GTK_IS_WIDGET(w)) { return; } IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(w); p->msg = msg; ir_wavedisplay_redraw(w); } void ir_wavedisplay_set_progress(IRWaveDisplay * w, const float progress) { if (!w || !GTK_IS_WIDGET(w)) { return; } IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(w); if (p->progress != progress) { p->progress = progress; ir_wavedisplay_redraw(w); } } void ir_wavedisplay_set_wave(IRWaveDisplay * w, float * values, int length) { if (!w || !GTK_IS_WIDGET(w) || !values || !length) { return; } IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(w); p->msg = NULL; if (p->wave) { free(p->wave); } p->wave = (float*)malloc(length * sizeof(float)); p->wave_length = length; for (int i = 0; i < length; i++) { p->wave[i] = values[i]; } ir_wavedisplay_redraw_all(w); } void ir_wavedisplay_set_logarithmic(IRWaveDisplay * w, int yes) { if (!w || !GTK_IS_WIDGET(w)) { return; } IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(w); int y = (yes) ? 1 : 0; if (p->logarithmic != y) { p->logarithmic = y; ir_wavedisplay_redraw_all(w); } } void ir_wavedisplay_set_envparams(IRWaveDisplay * w, int attack_time_s, float attack_pc, float env_pc, float length_pc, int reverse) { if (!w || !GTK_IS_WIDGET(w)) { return; } IRWaveDisplayPrivate * p = IR_WAVEDISPLAY_GET_PRIVATE(w); p->attack_time_s = attack_time_s; p->attack_pc = attack_pc; p->env_pc = env_pc; p->length_pc = length_pc; p->reverse = reverse; ir_wavedisplay_redraw(w); } GtkWidget * ir_wavedisplay_new(void) { return (GtkWidget *)g_object_new(IR_WAVEDISPLAY_TYPE, NULL); } ir.lv2-1.3.2/README0000644000175000017500000002563411774361056012177 0ustar miramiraIR: LV2 Convolution Reverb ========================== Author : Tom Szilagyi License : GNU GPL v2 Format : LV2 audio processing plugin Homepage: http://factorial.hu/plugins/lv2/ir IR is a no-latency/low-latency, realtime, high performance signal convolver especially for creating reverb effects. Supports impulse responses with 1, 2 or 4 channels, in any soundfile format supported by libsndfile. Developed by the author for their own needs, this plugin is released to the public in the hope that it will be useful to others, but strictly on an AS IS basis. There is ABSOLUTELY NO WARRANTY and there is NO SUPPORT. High quality bug reports and well-tested patches are nevertheless welcome. Other correspondence may be mercilessly ignored; thank you for your understanding. Features -------- * Realtime convolution of impulse responses with stereo audio * Supports Mono, Stereo and 'True Stereo' (4-channel) impulses * Very reasonable CPU consumption * Maximum impulse length: 1M samples (~22 seconds @ 48kHz) * Loads a large number of audio file formats * High quality sample rate conversion of impulse responses * Stretch control (via high quality SRC in one step integrated with impulse loading) * Pre-delay control (0-2000 ms) * Stereo width control of input signal & impulse response (0-150%) * Envelope alteration with immediate visual feedback: Attack time/percent, Envelope, Length * Reverse impulse response * Autogain: change impulses without having to adjust 'Wet gain' * Impulse response visualization (linear/logarithmic scale, peak & RMS) * Easy interface for fast browsing and loading impulse responses * Free software released under the GNU GPL v2 Please note that resampling long impulses (several hundreds of thousands of samples) on impulse loading may take several seconds. For this reason it may be a good idea to convert one's impulse library to all commonly used sample rates in advance, and use the plugin with the appropriate impulse library on each session. (Hint: use Bookmarks!) This is an LV2 plugin with its own GUI (GtkUI). You need an appropriate LV2 host that supports the following extensions: * uiext: http://lv2plug.in/ns/extensions/ui/ * instance-access: http://lv2plug.in/ns/ext/instance-access/ The only seriously tested host at this time is Ardour 2.8.x (x >= 11); Ardour 3 should also work (moderately tested). Other LV2 hosts should also work, but YMMV. Compiled and tested on Linux only. Make sure to read the Known issues section below. Please note that due to close coupling between the plugin and its GUI, bare-bones use of the plugin (without GUI) is not feasible. Available versions ------------------ Two versions are available for download. Version 1.2.x is absolutely zero-latency, however its run() should only be called with the same power-of-two buffer sizes (eg. always 512 samples, or always 2048, etc) during the plugin's lifecycle, otherwise bad things happen. In practice this means that you should NEVER EVER use this version with its parameters automated (even those listed as suitable for automation). Otherwise it works as you would expect. Version 1.3.x uses additional internal buffering so automation and the seemingly random buffer sizes that occur when automating the plugin (calling run() with buffer lengths eg. 1785, 263, 761, 1287 etc.) is not an issue anymore. It works with automation (but see the note about parameters suitable for automation below), at the expense of introducing extra buffering. The resulting latency is reported to the host via standard LV2 mechanism. However, if this causes problems for you, feel free to use version 1.2.x (and keep in mind that BOTH versions are completely unsupported by the author). Furthermore, there is a split of versions based on zita-convolver library compatibility. The last versions to compile against and work with zita-convolver 2 are 1.2.1 and 1.3.1. Later versions in both lines (starting with 1.2.2 and 1.3.2) are compatible with zita-convolver 3.x.x only. Known issues ------------ * When a plugin instance is created bypassed in Ardour (for example when copy&pasting from another IR instance), you have to un-bypass the new instance to be able to configure it. This is because the plugin needs to be run for at least one audio block in order to get properly initialised (...and this is because the LV2 run() function is the first place where a plugin can be certain about its input control values). * Ardour2 version needs to be recent enough (2.8.12 or later), otherwise you will likely run into this problem with plugin automation: http://tracker.ardour.org/view.php?id=3214 (This applies to IR 1.3. You don't want to automate IR 1.2 at all.) If you would like to build the plugin for debugging, please see the Makefile for instructions. Automation ---------- The following plugin controls are internally smoothed (their effects are protected from zipper noise) and thus are suitable for host automation: * Stereo width of input (but not of impulse response!) * Dry gain fader & on/off switch * Wet gain fader & on/off switch * Autogain on/off switch Changing other controls result in an IR vector recalculation and reinitialisation of the convolution engine. Those controls are *not* suited for automation. Changes to non-automatable controls are blissfully ignored when coming from automation (as opposed to real user interaction). Getting impulse responses ------------------------- Any good IR files should work, but if you don't yet have any favorites, try this: * http://www.echochamber.ch/index.php/impulseresponses There used to be a very good collection here, but seem like its gone: * http://rhythminmind.net/1313/?cat=182 (in particular, 'True M7' is a good selection of True Stereo impulses) Note that besides mono and stereo impulse response files, IR supports so-called 'True Stereo' impulses. These are four channel impulses that describe a full convolution matrix (the four channels contain, in order, the convolution paths L->L, L->R, R->L, R->R). You can sometimes find a pair of stereo impulse response files with similar names that end with L.wav and R.wav. In this case you have a good chance of having found a 'True Stereo' response. To load it into IR, you need to convert it to a single 4-channel file. You can do this via sndfile-deinterleave and sndfile-interleave, but most probably you will want to recursively convert a full directory tree of such files. This is easiest done using the 'convert4chan' utility supplied with this plugin. It recursively converts a directory tree containing *[LR].wav files to 4-channel files (*4.wav). Build the utility by issuing 'make convert4chan'. The program takes zero or one argument. In the former case, the directory tree under the current working directory is converted, in the latter case the argument is used as root directory. The conversion does not remove the source files. Note: a third file with a name that ends in C is often supplied along the L and R files. Most probably this is a regular stereo version of the same reverb. You will want to keep this besides the converted (4-channel) True Stereo version. Preset handling --------------- Along with the usual floating point control data, the IR plugin needs to store the impulse response filename as part of its configuration. Unfortunately, there is no usable preset save/restore mechanism standardized (yet) within the scope of the LV2 plugin format standard. Because the LV2 host only saves and restores plugin control port data (32-bit floating point numbers) as a means to session persistence, it is necessary to implement a custom session-handling mechanism to ensure that plugin instances get correctly saved and automatically reloaded (even if the plugin UI is not readily opened after a session reload). The problem is solved by saving a 64-bit hash of the filename across three floating point control ports. The corresponding hash-table (along with global IR configuration data, eg. bookmarks) is stored in the user's home directory in a file called ~/.ir_save in human readable form (however, modify it only if you know what you are doing, and make sure to back it up first). The important thing to remember is that this file contains information needed to restore global IR settings, and IR plugin settings for any and all plugin instances. When transferring a session to another machine or making a backup, don't forget to copy or backup this file as well. Compile-time dependencies ------------------------- The plugin uses the zita-convolver library as a backend for efficient zero-latency realtime convolution computation. The libsamplerate library is used for fast, high quality resampling of impulse responses to the operational sample rate, taking into account the 'Stretch' parameter (only one resampling is done). The well-known libsndfile library is used for reading the impulse response sound files. zita-convolver is written by Fons Adriaensen and can be downloaded from: http://kokkinizita.linuxaudio.org/linuxaudio/downloads/index.html Please note that zita-convolver-2.0.0 is required for IR versions up to 1.2.1 and 1.3.1; IR 1.2.2 / 1.3.2 and later versions require the newer zita-convolver-3.x.x library (these are incompatible library versions). The libsndfile and libsamplerate libraries are written by Erik de Castro Lopo and are widely used throughout the Linux Audio community. They should be available on any recent Linux distribution. Recent versions of these libraries should work fine. The plugin GUI uses the GTK toolkit. Version 2.16 or later is required. Compiling and installing ------------------------ If you have the above dependencies installed, a 'make' and (as root) 'make install' is all you need to use the plugin. The LV2 plugin bundle is installed under /usr/lib/lv2/ by default (please edit the Makefile to suit your needs if this is not what you want). Issue 'make convert4chan' to build the convert4chan utility described above. This is for local use only, it is not installed by 'make install' (and is not built by a simple 'make'). In case of doubt, please consult the Makefile. You should be able to figure it out from there. If you run into stability problems with running the plugin, please consult the top of ir.cc and see if changing the parameters helps you. Look for these definitions (after a block of explanatory comments): #define CONVPROC_SCHEDULER_PRIORITY 0 #define CONVPROC_SCHEDULER_CLASS SCHED_FIFO #define THREAD_SYNC_MODE true After changing these, simple recompile and reinstall the plugin. Show your love -------------- I have spent 3 weeks of my spare time developing this plugin (instead of making music or making a living) because I desperately needed something like this. I hope you also find it highly useful, and just like me, find pleasure in the quality of reverb that takes your mixes to the next level. In this case please consider buying me an item on my Amazon wishlist: http://www.amazon.co.uk/wishlist/8KRTCLLQMIP7 ... or donate a small amount to tomszilagyi@gmail.com via PayPal. Thank you! Tom ir.lv2-1.3.2/ir_utils.h0000644000175000017500000000422711636627117013315 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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 _IR_UTILS_H #define _IR_UTILS_H #include #include #include "ir.h" #define IR_SAVE_FILE ".ir_save" #define GROUP_FHASH "file-hashes" #define GROUP_BOOKMARKS "bookmarks" uint64_t fhash(char *str); uint64_t fhash_from_ports(float *port0, float *port1, float *port2); void ports_from_fhash(uint64_t fhash, float *port0, float *port1, float *port2); GKeyFile * load_keyfile(void); void save_keyfile(GKeyFile * keyfile); char * get_path_from_key(GKeyFile * keyfile, uint64_t fhash); void save_path(GKeyFile * keyfile, char * path); void load_bookmarks(GKeyFile * keyfile, GtkListStore * store); char * lookup_bookmark_in_store(GtkTreeModel * model, const char * bookmark); void store_bookmark(GKeyFile * keyfile, char * bookmark, char * fullpath); void delete_bookmark(GKeyFile * keyfile, char * bookmark); void load_files(GtkListStore * store, char * dirpath); void select_entry(GtkTreeModel * model, GtkTreeSelection * select, char * filename); void compute_envelope(float ** samples, int nchan, int nfram, int attack_time_s, float attack_pc, float env_pc, float length_pc); void draw_centered_text(cairo_t * cr, const char * text, int x , int y); double get_adjustment(GtkAdjustment * adj); void set_adjustment(GtkAdjustment * adj, double value); GtkAdjustment * create_adjustment(gdouble def, gdouble min, gdouble max, gpointer data, int logarithmic); #endif /* _IR_UTILS_H */ ir.lv2-1.3.2/ir_modeind.cc0000644000175000017500000001430611636627117013731 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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. */ #include #include #include #include "ir_modeind.h" #include "ir_utils.h" G_DEFINE_TYPE(IRModeInd, ir_modeind, GTK_TYPE_DRAWING_AREA); typedef struct _IRModeIndPrivate IRModeIndPrivate; #define IR_MODEIND_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), IR_MODEIND_TYPE, IRModeIndPrivate)) struct _IRModeIndPrivate { int channels; }; static gboolean ir_modeind_expose(GtkWidget * ir_modeind, GdkEventExpose * event); static void ir_modeind_class_init(IRModeIndClass * cls) { GObjectClass * obj_class = G_OBJECT_CLASS(cls); GtkWidgetClass * widget_class = GTK_WIDGET_CLASS(cls); widget_class->expose_event = ir_modeind_expose; g_type_class_add_private(obj_class, sizeof(IRModeIndPrivate)); } static void ir_modeind_init(IRModeInd * w) { IRModeIndPrivate * p = IR_MODEIND_GET_PRIVATE(w); p->channels = 0; } void draw_line(cairo_t * cr, int x1, int y1, int x2, int y2) { cairo_move_to(cr, x1, y1); cairo_line_to(cr, x2, y2); } static void draw(GtkWidget * widget, cairo_t * cr) { IRModeIndPrivate * p = IR_MODEIND_GET_PRIVATE(widget); int w = widget->allocation.width; int h = widget->allocation.height; cairo_rectangle(cr, 0, 0, w, h); cairo_set_source_rgb(cr, 0, 0, 0); cairo_fill_preserve(cr); cairo_set_source_rgb(cr, 0, 0, 0); cairo_stroke(cr); int nchan = p->channels; if (!nchan) { return; } const char * text = NULL; switch (nchan) { case 1: text = "Mono"; break; case 2: text = "Stereo"; break; case 4: text = "True Stereo"; break; } cairo_set_source_rgb(cr, 1, 1, 1); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 10.0); draw_centered_text(cr, text, w/2, h*7/8); draw_centered_text(cr, "L", w * 2/16, h * 7/32); draw_centered_text(cr, "R", w * 2/16, h * 19/32); draw_centered_text(cr, "in", w * 2/16, h * 13/32); draw_centered_text(cr, "L", w * 14/16, h * 7/32); draw_centered_text(cr, "R", w * 14/16, h * 19/32); draw_centered_text(cr, "out", w * 14/16, h * 13/32); /* draw lines */ switch (nchan) { case 1: case 2: draw_line(cr, w * 7/32, h * 7/32, w * 14/32, h * 7/32); draw_line(cr, w * 7/32, h * 19/32, w * 14/32, h * 19/32); draw_line(cr, w * 18/32, h * 7/32, w * 25/32, h * 7/32); draw_line(cr, w * 18/32, h * 19/32, w * 25/32, h * 19/32); break; case 4: draw_line(cr, w * 7/32, h * 7/32, w * 14/32, h * 2/16); draw_line(cr, w * 7/32, h * 7/32, w * 14/32, h * 5/16); draw_line(cr, w * 7/32, h * 19/32, w * 14/32, h * 8/16); draw_line(cr, w * 7/32, h * 19/32, w * 14/32, h * 11/16); draw_line(cr, w * 18/32, h * 2/16, w * 25/32, h * 7/32); draw_line(cr, w * 18/32, h * 8/16, w * 25/32, h * 7/32); draw_line(cr, w * 18/32, h * 5/16, w * 25/32, h * 19/32); draw_line(cr, w * 18/32, h * 11/16, w * 25/32, h * 19/32); break; } cairo_stroke(cr); /* draw two or four boxes atop each other */ switch (nchan) { case 1: cairo_set_source_rgb(cr, 0, 0.25, 0.8); cairo_rectangle(cr, w * 7/16, h * 5/32, w/8, h/8); cairo_fill_preserve(cr); cairo_stroke(cr); cairo_rectangle(cr, w * 7/16, h * 17/32, w/8, h/8); cairo_fill_preserve(cr); cairo_stroke(cr); cairo_set_source_rgb(cr, 0, 0, 0); draw_centered_text(cr, "1", w/2, h * 7/32); draw_centered_text(cr, "1", w/2, h * 19/32); cairo_stroke(cr); break; case 2: cairo_set_source_rgb(cr, 0, 0.25, 0.8); cairo_rectangle(cr, w * 7/16, h * 5/32, w/8, h/8); cairo_fill_preserve(cr); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.25, 0.8, 0); cairo_rectangle(cr, w * 7/16, h * 17/32, w/8, h/8); cairo_fill_preserve(cr); cairo_stroke(cr); cairo_set_source_rgb(cr, 0, 0, 0); draw_centered_text(cr, "1", w/2, h * 7/32); draw_centered_text(cr, "2", w/2 - 1, h * 19/32); cairo_stroke(cr); break; case 4: cairo_set_source_rgb(cr, 0, 0.25, 0.8); cairo_rectangle(cr, w * 7/16, h * 1/16, w/8, h/8); cairo_fill_preserve(cr); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.25, 0.8, 0); cairo_rectangle(cr, w * 7/16, h * 4/16, w/8, h/8); cairo_fill_preserve(cr); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.8, 0.2, 0.4); cairo_rectangle(cr, w * 7/16, h * 7/16, w/8, h/8); cairo_fill_preserve(cr); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.7, 0.7, 0.4); cairo_rectangle(cr, w * 7/16, h * 10/16, w/8, h/8); cairo_fill_preserve(cr); cairo_stroke(cr); cairo_set_source_rgb(cr, 0, 0, 0); draw_centered_text(cr, "1", w/2, h * 2/16); draw_centered_text(cr, "2", w/2 - 1, h * 5/16); draw_centered_text(cr, "3", w/2 - 1, h * 8/16); draw_centered_text(cr, "4", w/2 - 1, h * 11/16); cairo_stroke(cr); break; } } static gboolean ir_modeind_expose(GtkWidget * widget, GdkEventExpose * event) { cairo_t * cr = gdk_cairo_create(widget->window); cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height); cairo_clip(cr); draw(widget, cr); cairo_destroy(cr); return FALSE; } void ir_modeind_redraw(IRModeInd * w) { GtkWidget * widget; GdkRegion * region; widget = GTK_WIDGET(w); if (!widget->window) { return; } region = gdk_drawable_get_clip_region(widget->window); gdk_window_invalidate_region(widget->window, region, TRUE); gdk_window_process_updates(widget->window, TRUE); gdk_region_destroy(region); } void ir_modeind_set_channels(IRModeInd * w, int channels) { if (!w || !GTK_IS_WIDGET(w)) { return; } IRModeIndPrivate * p = IR_MODEIND_GET_PRIVATE(w); p->channels = channels; ir_modeind_redraw(w); } GtkWidget * ir_modeind_new(void) { return (GtkWidget *)g_object_new(IR_MODEIND_TYPE, NULL); } ir.lv2-1.3.2/ir_gui.cc0000644000175000017500000015446311772074300013076 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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. */ #include #include #include #include #include #include #include #include #include #include "lv2_ui.h" #include "instance-access.h" #include "ir.h" #include "ir_utils.h" #include "ir_meter.h" #include "ir_modeind.h" #include "ir_wavedisplay.h" #define IR_UI_URI "http://factorial.hu/plugins/lv2/ir/gui" #define PAD 2 #define ADJ_PREDELAY 0 #define ADJ_ATTACK 1 #define ADJ_ATTACKTIME 2 #define ADJ_ENVELOPE 3 #define ADJ_LENGTH 4 #define ADJ_STRETCH 5 #define ADJ_STEREO_IN 6 #define ADJ_STEREO_IR 7 #define ADJ_DRY_GAIN 8 #define ADJ_WET_GAIN 9 #define N_ADJUSTMENTS 10 typedef struct { int id; gdouble def; gdouble min; gdouble max; int log; } adj_descr_t; #define LIN 0 #define LOG 1 #define INVLOG 2 static adj_descr_t adj_descr_table[N_ADJUSTMENTS] = { {ADJ_PREDELAY, 0.0, 0.0, 2000.0, INVLOG}, {ADJ_ATTACK, 0.0, 0.0, 100.0, LIN}, {ADJ_ATTACKTIME, 0.0, 0.0, 300.0, LIN}, {ADJ_ENVELOPE, 100.0, 0.0, 100.0, LIN}, {ADJ_LENGTH, 100.0, 0.0, 100.0, LIN}, {ADJ_STRETCH, 100.0, 50.0, 150.0, LIN}, {ADJ_STEREO_IN, 100.0, 0.0, 150.0, LIN}, {ADJ_STEREO_IR, 100.0, 0.0, 150.0, LIN}, {ADJ_DRY_GAIN, 0.0, -90.0, 6.0, LOG}, {ADJ_WET_GAIN, -6.0, -90.0, 6.0, LOG}, }; struct control { LV2UI_Controller controller; LV2UI_Write_Function write_function; IR * ir; float port_buffer[IR_N_PORTS]; GSList * port_event_q; GtkWidget * vbox_top; GtkWidget * hbox_waitplugin; float predelay; float attack; float attacktime; float envelope; float length; float stretch; float stereo_ir; GtkAdjustment * adj_predelay; GtkAdjustment * adj_attack; GtkAdjustment * adj_attacktime; GtkAdjustment * adj_envelope; GtkAdjustment * adj_length; GtkAdjustment * adj_stretch; GtkAdjustment * adj_stereo_in; GtkAdjustment * adj_stereo_ir; GtkAdjustment * adj_dry_gain; GtkAdjustment * adj_wet_gain; GtkWidget * scale_predelay; GtkWidget * scale_attack; GtkWidget * scale_attacktime; GtkWidget * scale_envelope; GtkWidget * scale_length; GtkWidget * scale_stretch; GtkWidget * scale_stereo_in; GtkWidget * scale_stereo_ir; GtkWidget * label_predelay; GtkWidget * label_attack; GtkWidget * label_envelope; GtkWidget * label_length; GtkWidget * label_stretch; GtkWidget * label_stereo; GtkWidget * label_dry_gain; GtkWidget * label_wet_gain; GtkWidget * toggle_reverse; gulong toggle_reverse_cbid; GtkWidget * toggle_agc_sw; GtkWidget * toggle_dry_sw; GtkWidget * toggle_wet_sw; GtkWidget * meter_L_dry; GtkWidget * meter_R_dry; GtkWidget * meter_L_wet; GtkWidget * meter_R_wet; GtkWidget * chan_toggle[4]; gulong chan_toggle_cbid[4]; GtkWidget * log_toggle; gulong log_toggle_cbid; GtkWidget * wave_display; GtkWidget * wave_annot_label; int disp_chan; GtkWidget * mode_ind; GtkTreeModel * model_bookmarks; /* GtkTreeModelSortable on ir->store_bookmarks */ GtkListStore * store_files; GtkWidget * tree_bookmarks; GtkWidget * tree_files; int bookmarks_realized; int files_realized; gulong files_sel_cbid; gulong bookmarks_sel_cbid; guint timeout_tag; guint gui_load_timeout_tag; guint reinit_timeout_tag; guint waitplugin_timeout_tag; int interrupt_threads; GThread * gui_load_thread; GThread * reinit_thread; int key_pressed; }; typedef struct { uint32_t port_index; float value; } port_event_t; static void reset_values(struct control * cp); /* adjustments with linear or logarithmic scale */ #define LOG_SCALE_MIN 1.0 #define LOG_SCALE_MAX 2.0 #define INVLOG_SCALE_MIN 10.0 #define INVLOG_SCALE_MAX 100.0 static void adjustment_changed_cb(GtkAdjustment * adj, gpointer data); /* return 1 if value has actually changed */ static int set_port_value(struct control * cp, int port_index, float value) { if (fabs(cp->port_buffer[port_index] - value) < 0.000001) { return 0; } cp->port_buffer[port_index] = value; return 1; } static void send_port_value_to_host(struct control * cp, int port_index, float value) { if (set_port_value(cp, port_index, value)) { cp->write_function(cp->controller, port_index, sizeof(float), 0 /* default format */, &value); } } static int get_adj_index(struct control * cp, GtkAdjustment * adj) { if (adj == cp->adj_predelay) { return ADJ_PREDELAY; } else if (adj == cp->adj_attack) { return ADJ_ATTACK; } else if (adj == cp->adj_attacktime) { return ADJ_ATTACKTIME; } else if (adj == cp->adj_envelope) { return ADJ_ENVELOPE; } else if (adj == cp->adj_length) { return ADJ_LENGTH; } else if (adj == cp->adj_stretch) { return ADJ_STRETCH; } else if (adj == cp->adj_stereo_in) { return ADJ_STEREO_IN; } else if (adj == cp->adj_stereo_ir) { return ADJ_STEREO_IR; } else if (adj == cp->adj_dry_gain) { return ADJ_DRY_GAIN; } else if (adj == cp->adj_wet_gain) { return ADJ_WET_GAIN; } return -1; } static double convert_scale_to_real(int idx, double scale) { int log = adj_descr_table[idx].log; double y; double min = adj_descr_table[idx].min; double max = adj_descr_table[idx].max; double real = 0.0; if (log == LIN) { real = scale; } else if (log == LOG) { y = log10(scale); real = min + (y - LOG_SCALE_MIN) / (LOG_SCALE_MAX - LOG_SCALE_MIN) * (max - min); real = round(10.0 * real) / 10.0; /* one decimal digit */ } else if (log == INVLOG) { y = exp10(scale); real = min + (y - INVLOG_SCALE_MIN) / (INVLOG_SCALE_MAX - INVLOG_SCALE_MIN) * (max - min); real = round(10.0 * real) / 10.0; /* one decimal digit */ } return real; } static double convert_real_to_scale(int idx, double real) { int log = adj_descr_table[idx].log; double min = adj_descr_table[idx].min; double max = adj_descr_table[idx].max; double scale = 0.0; if (log == LIN) { scale = real; } else if (log == LOG) { scale = (real - min) / (max - min) * (LOG_SCALE_MAX - LOG_SCALE_MIN) + LOG_SCALE_MIN; scale = exp10(scale); } else if (log == INVLOG) { scale = (real - min) / (max - min) * (INVLOG_SCALE_MAX - INVLOG_SCALE_MIN) + INVLOG_SCALE_MIN; scale = log10(scale); } return scale; } static double get_adjustment(struct control * cp, GtkAdjustment * adj) { double y = gtk_adjustment_get_value(adj); int idx = get_adj_index(cp, adj); return convert_scale_to_real(idx, y); } static void set_adjustment(struct control * cp, GtkAdjustment * adj, double x) { int idx = get_adj_index(cp, adj); gtk_adjustment_set_value(adj, convert_real_to_scale(idx, x)); } static GtkAdjustment * create_adjustment(int idx, gpointer data) { GtkObject * adj = NULL; gdouble def = adj_descr_table[idx].def; gdouble min = adj_descr_table[idx].min; gdouble max = adj_descr_table[idx].max; int log = adj_descr_table[idx].log; if ((log == LOG) || (log == INVLOG)) { adj = gtk_adjustment_new(convert_real_to_scale(idx, def), convert_real_to_scale(idx, min), convert_real_to_scale(idx, max) + 1.0, 0.01, 1.0, 1.0); } else { adj = gtk_adjustment_new(def, min, max + 1.0, 0.1, 1.0, 1.0); } g_signal_connect(adj, "value_changed", G_CALLBACK(adjustment_changed_cb), data); return (GtkAdjustment *)adj; } static void set_agc_label(struct control * cp) { if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cp->toggle_agc_sw))) { char str[32]; snprintf(str, 32, "Autogain %+.1f dB", cp->ir->autogain_new); gtk_button_set_label(GTK_BUTTON(cp->toggle_agc_sw), str); } else { gtk_button_set_label(GTK_BUTTON(cp->toggle_agc_sw), "Autogain off"); } } #define S1 "" #define S2 "" #define XS1 "" #define XS2 "" static void set_label(struct control * cp, int idx) { char str[1024]; GtkWidget * label = NULL; float v; switch (idx) { case ADJ_PREDELAY: label = cp->label_predelay; v = get_adjustment(cp, cp->adj_predelay); snprintf(str, 1024, S1 "Predelay" S2 "\n" XS1 "%0.1fms" XS2, fabs(v)); /* kill the spurious negative zero */ break; case ADJ_ATTACK: case ADJ_ATTACKTIME: label = cp->label_attack; /* spaces eliminate label-positioning problem */ snprintf(str, 1024, S1 " Attack" S2 "\n" XS1 "%0.0f%% %0.0fms" XS2, get_adjustment(cp, cp->adj_attack), get_adjustment(cp, cp->adj_attacktime)); break; case ADJ_ENVELOPE: label = cp->label_envelope; snprintf(str, 1024, S1 "Envelope" S2 "\n" XS1 "%0.1f%%" XS2, get_adjustment(cp, cp->adj_envelope)); break; case ADJ_LENGTH: label = cp->label_length; snprintf(str, 1024, S1 "Length" S2 "\n" XS1"%0.1f%%" XS2, get_adjustment(cp, cp->adj_length)); break; case ADJ_STRETCH: label = cp->label_stretch; snprintf(str, 1024, S1 "Stretch" S2 "\n" XS1 "%0.1f%%" XS2, get_adjustment(cp, cp->adj_stretch)); break; case ADJ_STEREO_IN: case ADJ_STEREO_IR: label = cp->label_stereo; snprintf(str, 1024, S1 "Stereo in/IR" S2 "\n" XS1 "%0.0f%% / %0.0f%%" XS2, get_adjustment(cp, cp->adj_stereo_in), get_adjustment(cp, cp->adj_stereo_ir)); break; case ADJ_DRY_GAIN: label = cp->label_dry_gain; v = get_adjustment(cp, cp->adj_dry_gain); if (v > 0.0) { snprintf(str, 1024, S1 "%+0.1f dB" S2, v); } else if (v == 0.0) { snprintf(str, 1024, S1 "0.0 dB" S2); } else if (v > -90.0) { snprintf(str, 1024, S1 "%+0.1f dB" S2, v); } else { snprintf(str, 1024, S1 "mute" S2); } break; case ADJ_WET_GAIN: label = cp->label_wet_gain; v = get_adjustment(cp, cp->adj_wet_gain); if (v > 0.0) { snprintf(str, 1024, S1 "%+0.1f dB" S2, v); } else if (v == 0.0) { snprintf(str, 1024, S1 "0.0 dB" S2); } else if (v > -90.0) { snprintf(str, 1024, S1 "%+0.1f dB" S2, v); } else { snprintf(str, 1024, S1 "mute" S2); } break; } gtk_label_set_markup(GTK_LABEL(label), str); } static void refresh_gui_on_load(struct control * cp) { char str[1024]; const char * chn = (cp->ir->nchan > 1) ? "channels" : "channel"; float secs = (float)cp->ir->source_nfram / cp->ir->source_samplerate; char * filename_esc = g_markup_escape_text(cp->ir->source_path, -1); if (cp->ir->source_samplerate == (unsigned int)cp->ir->sample_rate) { snprintf(str, 1024, XS1 "%s" XS2 "\n" S1 "%d %s, %d samples, %d Hz, %.3f seconds" S2, filename_esc, cp->ir->nchan, chn, cp->ir->source_nfram, cp->ir->source_samplerate, secs); } else { snprintf(str, 1024, XS1 "%s" XS2 "\n" S1 "%d %s, %d samples, %d Hz (resampled to %d Hz), %.3f seconds" S2, filename_esc, cp->ir->nchan, chn, cp->ir->source_nfram, cp->ir->source_samplerate, (int)cp->ir->sample_rate, secs); } free(filename_esc); gtk_label_set_markup(GTK_LABEL(cp->wave_annot_label), str); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cp->chan_toggle[0]), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cp->chan_toggle[0]), TRUE); gtk_widget_set_sensitive(cp->chan_toggle[0], cp->ir->nchan > 1); for (int i = 1; i < 4; i++) { gtk_widget_set_sensitive(cp->chan_toggle[i], i < cp->ir->nchan); } set_agc_label(cp); ir_modeind_set_channels(IR_MODEIND(cp->mode_ind), cp->ir->nchan); } static gpointer gui_load_thread(gpointer data) { struct control * cp = (struct control*)data; int r = cp->ir->resample_init(cp->ir); if (r == 0) { while (r == 0) { r = cp->ir->resample_do(cp->ir); if (cp->interrupt_threads) { break; } } cp->ir->resample_cleanup(cp->ir); } if (r >= 0) { cp->ir->prepare_convdata(cp->ir); cp->ir->init_conv(cp->ir); } cp->ir->reinit_running = 0; return NULL; } static gint gui_load_timeout_callback(gpointer data) { struct control * cp = (struct control*)data; if (cp->ir->reinit_running) { ir_wavedisplay_set_progress(IR_WAVEDISPLAY(cp->wave_display), cp->ir->src_progress); return TRUE; } g_thread_join(cp->gui_load_thread); cp->gui_load_thread = NULL; ir_wavedisplay_set_progress(IR_WAVEDISPLAY(cp->wave_display), -1.0); ir_wavedisplay_set_message(IR_WAVEDISPLAY(cp->wave_display), NULL); refresh_gui_on_load(cp); reset_values(cp); cp->gui_load_timeout_tag = 0; return FALSE; } static void gui_load_sndfile(struct control * cp, char * filename) { if (cp->ir->reinit_running || cp->gui_load_thread) { return; } if (cp->ir->source_path) { free(cp->ir->source_path); } cp->ir->source_path = strdup(filename); ir_wavedisplay_set_message(IR_WAVEDISPLAY(cp->wave_display), "Loading..."); ir_wavedisplay_set_progress(IR_WAVEDISPLAY(cp->wave_display), 0.0); if (cp->ir->load_sndfile(cp->ir) < 0) { fprintf(stderr, "IR: load_sndfile error\n"); ir_wavedisplay_set_message(IR_WAVEDISPLAY(cp->wave_display), NULL); } else { uint64_t hash = fhash(filename); float value0, value1, value2; ports_from_fhash(hash, &value0, &value1, &value2); cp->write_function(cp->controller, IR_PORT_FHASH_0, sizeof(float), 0 /* default format */, &value0); cp->write_function(cp->controller, IR_PORT_FHASH_1, sizeof(float), 0 /* default format */, &value1); cp->write_function(cp->controller, IR_PORT_FHASH_2, sizeof(float), 0 /* default format */, &value2); cp->ir->reinit_running = 1; cp->gui_load_thread = g_thread_create(gui_load_thread, cp, TRUE, NULL); cp->gui_load_timeout_tag = g_timeout_add(100, gui_load_timeout_callback, cp); } } static void browse_button_clicked(GtkWidget * w, gpointer data) { struct control * cp = (struct control *)data; GtkWidget * dialog; if (cp->ir->reinit_running) { return; } dialog = gtk_file_chooser_dialog_new("Open File", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); GtkFileFilter * filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, "All files"); gtk_file_filter_add_pattern(filter, "*"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, "Soundfiles"); gtk_file_filter_add_pattern(filter, "*.wav"); gtk_file_filter_add_pattern(filter, "*.WAV"); gtk_file_filter_add_pattern(filter, "*.aiff"); gtk_file_filter_add_pattern(filter, "*.AIFF"); gtk_file_filter_add_pattern(filter, "*.au"); gtk_file_filter_add_pattern(filter, "*.AU"); gtk_file_filter_add_pattern(filter, "*.flac"); gtk_file_filter_add_pattern(filter, "*.FLAC"); gtk_file_filter_add_pattern(filter, "*.ogg"); gtk_file_filter_add_pattern(filter, "*.OGG"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { char * filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog)); gui_load_sndfile(cp, filename); char * dirname = g_path_get_dirname(filename); load_files(cp->store_files, dirname); GtkTreeSelection * select = gtk_tree_view_get_selection(GTK_TREE_VIEW(cp->tree_bookmarks)); g_signal_handler_block(select, cp->bookmarks_sel_cbid); select_entry(cp->model_bookmarks, select, dirname); g_signal_handler_unblock(select, cp->bookmarks_sel_cbid); select = gtk_tree_view_get_selection(GTK_TREE_VIEW(cp->tree_files)); g_signal_handler_block(select, cp->files_sel_cbid); select_entry(GTK_TREE_MODEL(cp->store_files), select, filename); g_signal_handler_unblock(select, cp->files_sel_cbid); g_free(filename); g_free(dirname); } gtk_widget_destroy(dialog); } static int key_pressed_cb(GtkWidget * widget, GdkEventKey * event, gpointer data) { struct control * cp = (struct control *)data; if (cp->ir->reinit_running) { return FALSE; } cp->key_pressed = 1; return FALSE; } static void save_value(struct control * cp, int port, float value) { switch (port) { case IR_PORT_PREDELAY: cp->predelay = value; break; case IR_PORT_ATTACK: cp->attack = value; break; case IR_PORT_ATTACKTIME: cp->attacktime = value; break; case IR_PORT_ENVELOPE: cp->envelope = value; break; case IR_PORT_LENGTH: cp->length = value; break; case IR_PORT_STRETCH: cp->stretch = value; break; case IR_PORT_STEREO_IR: cp->stereo_ir = value; break; } } static void reset_values(struct control * cp) { set_adjustment(cp, cp->adj_predelay, cp->predelay); set_adjustment(cp, cp->adj_attack, cp->attack); set_adjustment(cp, cp->adj_attacktime, cp->attacktime); set_adjustment(cp, cp->adj_envelope, cp->envelope); set_adjustment(cp, cp->adj_length, cp->length); set_adjustment(cp, cp->adj_stretch, cp->stretch); set_adjustment(cp, cp->adj_stereo_ir, cp->stereo_ir); } static void reset_value(struct control * cp, GtkAdjustment * adj) { if (adj == cp->adj_predelay) { set_adjustment(cp, adj, cp->predelay); } else if (adj == cp->adj_attack) { set_adjustment(cp, adj, cp->attack); } else if (adj == cp->adj_attacktime) { set_adjustment(cp, adj, cp->attacktime); } else if (adj == cp->adj_envelope) { set_adjustment(cp, adj, cp->envelope); } else if (adj == cp->adj_length) { set_adjustment(cp, adj, cp->length); } else if (adj == cp->adj_stretch) { set_adjustment(cp, adj, cp->stretch); } else if (adj == cp->adj_stereo_ir) { set_adjustment(cp, adj, cp->stereo_ir); } } static int key_released_cb(GtkWidget * widget, GdkEventKey * event, gpointer data) { struct control * cp = (struct control *)data; GtkAdjustment * adj = NULL; int port = 0; cp->key_pressed = 0; if (cp->ir->reinit_running) { return FALSE; } if (widget == cp->scale_predelay) { adj = cp->adj_predelay; port = IR_PORT_PREDELAY; } else if (widget == cp->scale_attack) { adj = cp->adj_attack; port = IR_PORT_ATTACK; } else if (widget == cp->scale_attacktime) { adj = cp->adj_attacktime; port = IR_PORT_ATTACKTIME; } else if (widget == cp->scale_envelope) { adj = cp->adj_envelope; port = IR_PORT_ENVELOPE; } else if (widget == cp->scale_length) { adj = cp->adj_length; port = IR_PORT_LENGTH; } else if (widget == cp->scale_stretch) { adj = cp->adj_stretch; port = IR_PORT_STRETCH; cp->ir->resample_pending = 1; } else if (widget == cp->scale_stereo_ir) { adj = cp->adj_stereo_ir; port = IR_PORT_STEREO_IR; } if (port == 0) { return FALSE; } float value = get_adjustment(cp, adj); save_value(cp, port, value); //printf("on button_release adj value = %f\n", value); send_port_value_to_host(cp, port, value); cp->ir->run = 0; cp->ir->reinit_pending = 1; return FALSE; } static void update_envdisplay(struct control * cp) { int attack_time_s = get_adjustment(cp, cp->adj_attacktime) * (float)cp->ir->sample_rate / 1000.0; float attack_pc = get_adjustment(cp, cp->adj_attack); float env_pc = get_adjustment(cp, cp->adj_envelope); float length_pc = get_adjustment(cp, cp->adj_length); int reverse = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cp->toggle_reverse)) ? 1 : 0; ir_wavedisplay_set_envparams(IR_WAVEDISPLAY(cp->wave_display), attack_time_s, attack_pc, env_pc, length_pc, reverse); } static void adjustment_changed_cb(GtkAdjustment * adj, gpointer data) { struct control * cp = (struct control *)data; float value = get_adjustment(cp, adj); int port = 0; int update_ui = 0; int label_idx = 0; if (adj == cp->adj_predelay) { label_idx = ADJ_PREDELAY; } else if (adj == cp->adj_attack) { label_idx = ADJ_ATTACK; update_ui = 1; } else if (adj == cp->adj_attacktime) { label_idx = ADJ_ATTACK; update_ui = 1; } else if (adj == cp->adj_envelope) { label_idx = ADJ_ENVELOPE; update_ui = 1; } else if (adj == cp->adj_length) { label_idx = ADJ_LENGTH; update_ui = 1; } else if (adj == cp->adj_stretch) { label_idx = ADJ_STRETCH; } else if (adj == cp->adj_stereo_in) { label_idx = ADJ_STEREO_IN; port = IR_PORT_STEREO_IN; } else if (adj == cp->adj_stereo_ir) { label_idx = ADJ_STEREO_IR; } else if (adj == cp->adj_dry_gain) { label_idx = ADJ_DRY_GAIN; port = IR_PORT_DRY_GAIN; } else if (adj == cp->adj_wet_gain) { label_idx = ADJ_WET_GAIN; port = IR_PORT_WET_GAIN; } if (cp->ir->reinit_running && !port) { return; } set_label(cp, label_idx); if (port == 0) { if (cp->key_pressed) { save_value(cp, port, value); if (update_ui) { update_envdisplay(cp); } } else { reset_value(cp, adj); } return; } //printf("adj changed to %f\n", value); send_port_value_to_host(cp, port, value); } static void toggle_button_cb(GtkWidget * widget, gpointer data) { struct control * cp = (struct control *)data; if (cp->ir->reinit_running && (widget == cp->toggle_reverse)) { g_signal_handler_block(widget, cp->toggle_reverse_cbid); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), !(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))); g_signal_handler_unblock(widget, cp->toggle_reverse_cbid); return; } int port = 0; float value; const char * text; if (widget == cp->toggle_dry_sw) { port = IR_PORT_DRY_SW; } else if (widget == cp->toggle_wet_sw) { port = IR_PORT_WET_SW; } else if (widget == cp->toggle_reverse) { port = IR_PORT_REVERSE; } if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { value = 1.0f; text = "ON"; } else { value = 0.0f; text = "off"; } send_port_value_to_host(cp, port, value); if (port == IR_PORT_REVERSE) { cp->ir->run = 0; cp->ir->reinit_pending = 1; update_envdisplay(cp); } else if ((port == IR_PORT_DRY_SW) || (port == IR_PORT_WET_SW)) { gtk_button_set_label(GTK_BUTTON(widget), text); } } static void irctrl_add_row(GtkWidget * table, int row, GtkWidget ** label, GtkAdjustment * adj, GtkWidget ** scale, int idx, gpointer data) { struct control * cp = (struct control *)data; *label = gtk_label_new(""); gtk_label_set_justify(GTK_LABEL(*label), GTK_JUSTIFY_RIGHT); set_label(cp, idx); gtk_misc_set_alignment(GTK_MISC(*label), 1.0f, 0.0f); gtk_table_attach(GTK_TABLE(table), *label, 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); *scale = gtk_hscale_new(adj); gtk_scale_set_draw_value(GTK_SCALE(*scale), FALSE); gtk_widget_add_events(*scale, GDK_BUTTON_RELEASE_MASK); g_signal_connect(*scale, "button_press_event", G_CALLBACK(key_pressed_cb), data); g_signal_connect(*scale, "button_release_event", G_CALLBACK(key_released_cb), data); gtk_table_attach_defaults(GTK_TABLE(table), *scale, 1, 3, row, row + 1); } static void irctrl_add_row2(GtkWidget * table, int row, GtkWidget ** label, GtkAdjustment * adj1, GtkAdjustment * adj2, GtkWidget ** scale1, GtkWidget ** scale2, int idx, gpointer data) { struct control * cp = (struct control *)data; *label = gtk_label_new(""); gtk_label_set_justify(GTK_LABEL(*label), GTK_JUSTIFY_RIGHT); set_label(cp, idx); gtk_misc_set_alignment(GTK_MISC(*label), 1.0f, 0.0f); gtk_table_attach(GTK_TABLE(table), *label, 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); *scale1 = gtk_hscale_new(adj1); gtk_scale_set_draw_value(GTK_SCALE(*scale1), FALSE); gtk_widget_add_events(*scale1, GDK_BUTTON_RELEASE_MASK); g_signal_connect(*scale1, "button_press_event", G_CALLBACK(key_pressed_cb), data); g_signal_connect(*scale1, "button_release_event", G_CALLBACK(key_released_cb), data); gtk_table_attach_defaults(GTK_TABLE(table), *scale1, 1, 2, row, row + 1); *scale2 = gtk_hscale_new(adj2); gtk_scale_set_draw_value(GTK_SCALE(*scale2), FALSE); gtk_widget_add_events(*scale2, GDK_BUTTON_RELEASE_MASK); g_signal_connect(*scale2, "button_press_event", G_CALLBACK(key_pressed_cb), data); g_signal_connect(*scale2, "button_release_event", G_CALLBACK(key_released_cb), data); gtk_table_attach_defaults(GTK_TABLE(table), *scale2, 2, 3, row, row + 1); } static GtkWidget * make_irctrl_frame(struct control * cp) { GtkWidget * frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); GtkWidget * table = gtk_table_new(6, 3, FALSE); gtk_table_set_row_spacings(GTK_TABLE(table), PAD); gtk_table_set_col_spacings(GTK_TABLE(table), 2*PAD); gtk_container_add(GTK_CONTAINER(frame), table); /* Predelay */ cp->adj_predelay = create_adjustment(ADJ_PREDELAY, cp); irctrl_add_row(table, 0, &cp->label_predelay, cp->adj_predelay, &cp->scale_predelay, ADJ_PREDELAY, cp); /* Attack */ cp->adj_attack = create_adjustment(ADJ_ATTACK, cp); cp->adj_attacktime = create_adjustment(ADJ_ATTACKTIME, cp); irctrl_add_row2(table, 1, &cp->label_attack, cp->adj_attack, cp->adj_attacktime, &cp->scale_attack, &cp->scale_attacktime, ADJ_ATTACK, cp); /* Envelope */ cp->adj_envelope = create_adjustment(ADJ_ENVELOPE, cp); irctrl_add_row(table, 2, &cp->label_envelope, cp->adj_envelope, &cp->scale_envelope, ADJ_ENVELOPE, cp); /* Length */ cp->adj_length = create_adjustment(ADJ_LENGTH, cp); irctrl_add_row(table, 3, &cp->label_length, cp->adj_length, &cp->scale_length, ADJ_LENGTH, cp); /* Stretch */ cp->adj_stretch = create_adjustment(ADJ_STRETCH, cp); irctrl_add_row(table, 4, &cp->label_stretch, cp->adj_stretch, &cp->scale_stretch, ADJ_STRETCH, cp); /* Stereo width in/IR */ cp->adj_stereo_in = create_adjustment(ADJ_STEREO_IN, cp); cp->adj_stereo_ir = create_adjustment(ADJ_STEREO_IR, cp); irctrl_add_row2(table, 5, &cp->label_stereo, cp->adj_stereo_in, cp->adj_stereo_ir, &cp->scale_stereo_in, &cp->scale_stereo_ir, ADJ_STEREO_IN, cp); gtk_scale_add_mark(GTK_SCALE(cp->scale_stretch), 100.0, GTK_POS_BOTTOM, XS1 " " XS2); gtk_scale_add_mark(GTK_SCALE(cp->scale_stereo_in), 100.0, GTK_POS_BOTTOM, XS1 " " XS2); gtk_scale_add_mark(GTK_SCALE(cp->scale_stereo_ir), 100.0, GTK_POS_BOTTOM, XS1 " " XS2); gtk_widget_set_size_request(cp->scale_attack, 125, -1); gtk_widget_set_size_request(cp->scale_attacktime, 125, -1); return frame; } static void agc_toggle_cb(GtkWidget * widget, gpointer data) { struct control * cp = (struct control *)data; float value; if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { value = 1.0f; } else { value = 0.0f; } send_port_value_to_host(cp, IR_PORT_AGC_SW, value); set_agc_label(cp); } static GtkWidget * make_mixer_frame(struct control * cp) { GtkWidget * frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); GtkWidget * vbox_top = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(frame), vbox_top); GtkWidget * hbox = gtk_hbox_new(TRUE, PAD); gtk_box_pack_start(GTK_BOX(vbox_top), hbox, TRUE, TRUE, PAD); cp->adj_dry_gain = create_adjustment(ADJ_DRY_GAIN, cp); cp->adj_wet_gain = create_adjustment(ADJ_WET_GAIN, cp); /* Dry */ GtkWidget * vbox = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, PAD); GtkWidget * label = gtk_label_new(""); gtk_label_set_markup(GTK_LABEL(label), S1 "Dry" S2); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, PAD); GtkWidget * hbox_i = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox_i, TRUE, TRUE, PAD); GtkWidget * scale; scale = gtk_vscale_new(cp->adj_dry_gain); gtk_range_set_inverted(GTK_RANGE(scale), TRUE); gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); gtk_scale_add_mark(GTK_SCALE(scale), convert_real_to_scale(ADJ_DRY_GAIN, 0.0), GTK_POS_RIGHT, XS1 " " XS2); gtk_box_pack_start(GTK_BOX(hbox_i), scale, TRUE, TRUE, 0); cp->meter_L_dry = ir_meter_new(); gtk_widget_set_size_request(cp->meter_L_dry, 5, -1); gtk_box_pack_start(GTK_BOX(hbox_i), cp->meter_L_dry, FALSE, TRUE, 1); cp->meter_R_dry = ir_meter_new(); gtk_widget_set_size_request(cp->meter_R_dry, 5, -1); gtk_box_pack_start(GTK_BOX(hbox_i), cp->meter_R_dry, FALSE, TRUE, 0); cp->label_dry_gain = gtk_label_new("0.0 dB"); gtk_box_pack_start(GTK_BOX(vbox), cp->label_dry_gain, FALSE, TRUE, PAD); cp->toggle_dry_sw = gtk_toggle_button_new_with_label("off"); g_signal_connect(cp->toggle_dry_sw, "toggled", G_CALLBACK(toggle_button_cb), cp); gtk_box_pack_start(GTK_BOX(vbox), cp->toggle_dry_sw, FALSE, FALSE, PAD); /* Wet */ vbox = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, PAD); label = gtk_label_new(""); gtk_label_set_markup(GTK_LABEL(label), S1 "Wet" S2); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, PAD); hbox_i = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox_i, TRUE, TRUE, PAD); scale = gtk_vscale_new(cp->adj_wet_gain); gtk_range_set_inverted(GTK_RANGE(scale), TRUE); gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); gtk_scale_add_mark(GTK_SCALE(scale), convert_real_to_scale(ADJ_WET_GAIN, 0.0), GTK_POS_RIGHT, XS1 " " XS2); gtk_box_pack_start(GTK_BOX(hbox_i), scale, TRUE, TRUE, 0); cp->meter_L_wet = ir_meter_new(); gtk_widget_set_size_request(cp->meter_L_wet, 5, -1); gtk_box_pack_start(GTK_BOX(hbox_i), cp->meter_L_wet, FALSE, TRUE, 1); cp->meter_R_wet = ir_meter_new(); gtk_widget_set_size_request(cp->meter_R_wet, 5, -1); gtk_box_pack_start(GTK_BOX(hbox_i), cp->meter_R_wet, FALSE, TRUE, 0); cp->label_wet_gain = gtk_label_new("-6.0 dB"); gtk_box_pack_start(GTK_BOX(vbox), cp->label_wet_gain, FALSE, TRUE, PAD); cp->toggle_wet_sw = gtk_toggle_button_new_with_label("off"); g_signal_connect(cp->toggle_wet_sw, "toggled", G_CALLBACK(toggle_button_cb), cp); gtk_box_pack_start(GTK_BOX(vbox), cp->toggle_wet_sw, FALSE, FALSE, PAD); /* Autogain */ hbox = gtk_hbox_new(FALSE, PAD); gtk_box_pack_start(GTK_BOX(vbox_top), hbox, FALSE, TRUE, 0); cp->toggle_agc_sw = gtk_toggle_button_new_with_label("Autogain"); g_signal_connect(cp->toggle_agc_sw, "toggled", G_CALLBACK(agc_toggle_cb), cp); gtk_box_pack_start(GTK_BOX(hbox), cp->toggle_agc_sw, TRUE, TRUE, PAD); return frame; } static void add_bookmark_button_clicked(GtkWidget * w, gpointer data) { struct control * cp = (struct control *)data; GtkWidget * dialog; dialog = gtk_file_chooser_dialog_new("Select directory", NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); GtkWidget * hbox = gtk_hbox_new(FALSE, PAD); GtkWidget * label = gtk_label_new("Bookmark name (optional):"); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, PAD); GtkWidget * entry = gtk_entry_new(); gtk_widget_show(entry); gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, PAD); gtk_widget_show(hbox); gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), hbox); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { char * filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); const gchar * bookmark = gtk_entry_get_text(GTK_ENTRY(entry)); char * name; if ((bookmark != NULL) && (strlen(bookmark) > 0)) { name = strdup(bookmark); } else { /* use last path component as name */ name = g_path_get_basename(filename); } char * path = lookup_bookmark_in_store(cp->model_bookmarks, name); if (path) { fprintf(stderr, "IR: bookmark already exists!\n"); g_free(path); } else { GtkTreeIter iter; gtk_list_store_append(cp->ir->store_bookmarks, &iter); gtk_list_store_set(cp->ir->store_bookmarks, &iter, 0, name, 1, filename, -1); store_bookmark(cp->ir->keyfile, name, filename); } g_free(name); g_free(filename); } gtk_widget_destroy(dialog); } static void del_bookmark_button_clicked(GtkWidget * w, gpointer data) { struct control * cp = (struct control *)data; GtkTreeIter iter; /* on sorted model */ GtkTreeIter real_iter; /* on underlying model */ GtkTreeModel * model; GtkTreeSelection * select; gchar * key; select = gtk_tree_view_get_selection(GTK_TREE_VIEW(cp->tree_bookmarks)); if (gtk_tree_selection_get_selected(select, &model, &iter)) { gtk_tree_model_get(model, &iter, 0, &key, -1); delete_bookmark(cp->ir->keyfile, key); gtk_tree_model_sort_convert_iter_to_child_iter( GTK_TREE_MODEL_SORT(cp->model_bookmarks), &real_iter, &iter); gtk_list_store_remove(cp->ir->store_bookmarks, &real_iter); g_free(key); } } static void bookmarks_selection_changed_cb(GtkTreeSelection * selection, gpointer data) { struct control * cp = (struct control *)data; GtkTreeIter iter; GtkTreeModel * model; gchar * bookmark; gchar * dirpath; if (gtk_tree_selection_get_selected(selection, &model, &iter)) { gtk_tree_model_get(model, &iter, 0, &bookmark, 1, &dirpath, -1); load_files(cp->store_files, dirpath); g_free(bookmark); g_free(dirpath); } } static void files_selection_changed_cb(GtkTreeSelection * selection, gpointer data) { struct control * cp = (struct control *)data; GtkTreeIter iter; GtkTreeModel * model; gchar * filename; if (gtk_tree_selection_get_selected(selection, &model, &iter)) { gtk_tree_model_get(model, &iter, 1, &filename, -1); if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { load_files(cp->store_files, filename); /* clear bookmark selection so it is clickable again * for fast upwards navigation */ gtk_tree_selection_unselect_all( gtk_tree_view_get_selection(GTK_TREE_VIEW(cp->tree_bookmarks))); } else { gui_load_sndfile(cp, filename); } g_free(filename); } } static void tree_view_realized_cb(GtkWidget * widget, gpointer data) { struct control * cp = (struct control *)data; if (widget == cp->tree_bookmarks) { cp->bookmarks_realized = 1; } else if (widget == cp->tree_files) { cp->files_realized = 1; } if (cp->bookmarks_realized && cp->files_realized && cp->ir->source_path) { /* select appropriate entries if plugin loaded */ char * dirpath = g_path_get_dirname(cp->ir->source_path); load_files(cp->store_files, dirpath); GtkTreeSelection * select = gtk_tree_view_get_selection(GTK_TREE_VIEW(cp->tree_bookmarks)); g_signal_handler_block(select, cp->bookmarks_sel_cbid); select_entry(cp->model_bookmarks, select, dirpath); g_signal_handler_unblock(select, cp->bookmarks_sel_cbid); select = gtk_tree_view_get_selection(GTK_TREE_VIEW(cp->tree_files)); g_signal_handler_block(select, cp->files_sel_cbid); select_entry(GTK_TREE_MODEL(cp->store_files), select, cp->ir->source_path); g_signal_handler_unblock(select, cp->files_sel_cbid); g_free(dirpath); refresh_gui_on_load(cp); } } static GtkWidget * make_lists_box(struct control * cp) { GtkWidget * hbox = gtk_hbox_new(FALSE, 0); GtkWidget * vbox = gtk_vbox_new(FALSE, 0); GtkTreeModel * model = GTK_TREE_MODEL(cp->ir->store_bookmarks); cp->model_bookmarks = gtk_tree_model_sort_new_with_model(model); cp->tree_bookmarks = gtk_tree_view_new_with_model(cp->model_bookmarks); g_signal_connect(G_OBJECT(cp->tree_bookmarks), "realize", G_CALLBACK(tree_view_realized_cb), cp); GtkWidget * scroll_win = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_win), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); gtk_container_add(GTK_CONTAINER(scroll_win), cp->tree_bookmarks); gtk_box_pack_start(GTK_BOX(vbox), scroll_win, TRUE, TRUE, 0); GtkCellRenderer * renderer; GtkTreeViewColumn * column; GtkTreeSelection * select_b; renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "scale", 0.8, NULL); g_object_set(renderer, "scale-set", TRUE, NULL); column = gtk_tree_view_column_new_with_attributes("Bookmarks", renderer, "text", 0, NULL); gtk_tree_view_column_set_sort_column_id(column, 0); gtk_tree_view_column_set_sort_indicator(column, TRUE); gtk_tree_view_column_set_sort_order(column, GTK_SORT_ASCENDING); gtk_tree_view_append_column(GTK_TREE_VIEW(cp->tree_bookmarks), column); gtk_tree_view_column_clicked(column); select_b = gtk_tree_view_get_selection(GTK_TREE_VIEW(cp->tree_bookmarks)); gtk_tree_selection_set_mode(select_b, GTK_SELECTION_SINGLE); cp->bookmarks_sel_cbid = g_signal_connect(G_OBJECT(select_b), "changed", G_CALLBACK(bookmarks_selection_changed_cb), cp); GtkWidget * hbox_1 = gtk_hbox_new(TRUE, PAD); gtk_box_pack_start(GTK_BOX(vbox), hbox_1, FALSE, FALSE, PAD); GtkWidget * button = gtk_button_new_with_label("Add..."); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(add_bookmark_button_clicked), cp); gtk_box_pack_start(GTK_BOX(hbox_1), button, TRUE, TRUE, PAD); button = gtk_button_new_with_label("Remove"); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(del_bookmark_button_clicked), cp); gtk_box_pack_start(GTK_BOX(hbox_1), button, TRUE, TRUE, PAD); gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, PAD); vbox = gtk_vbox_new(FALSE, 0); cp->store_files = gtk_list_store_new(2, G_TYPE_STRING, /* visible name (key) */ G_TYPE_STRING); /* full pathname (value) */ cp->tree_files = gtk_tree_view_new_with_model(GTK_TREE_MODEL(cp->store_files)); g_signal_connect(G_OBJECT(cp->tree_files), "realize", G_CALLBACK(tree_view_realized_cb), cp); scroll_win = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_win), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); gtk_container_add(GTK_CONTAINER(scroll_win), cp->tree_files); gtk_box_pack_start(GTK_BOX(vbox), scroll_win, TRUE, TRUE, 0); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "scale", 0.8, NULL); g_object_set(renderer, "scale-set", TRUE, NULL); column = gtk_tree_view_column_new_with_attributes("Files", renderer, "text", 0, NULL); gtk_tree_view_column_set_sort_column_id(column, 0); gtk_tree_view_column_set_sort_indicator(column, TRUE); gtk_tree_view_column_set_sort_order(column, GTK_SORT_ASCENDING); gtk_tree_view_append_column(GTK_TREE_VIEW(cp->tree_files), column); gtk_tree_view_column_clicked(column); GtkTreeSelection * select_f; select_f = gtk_tree_view_get_selection(GTK_TREE_VIEW(cp->tree_files)); gtk_tree_selection_set_mode(select_f, GTK_SELECTION_SINGLE); cp->files_sel_cbid = g_signal_connect(G_OBJECT(select_f), "changed", G_CALLBACK(files_selection_changed_cb), cp); GtkWidget * browse_button = gtk_button_new_with_label("Open File..."); gtk_box_pack_start(GTK_BOX(vbox), browse_button, FALSE, FALSE, PAD); g_signal_connect(G_OBJECT(browse_button), "clicked", G_CALLBACK(browse_button_clicked), cp); gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, PAD); return hbox; } static gpointer reinit_thread(gpointer data) { struct control * cp = (struct control*)data; if (cp->ir->resample_pending) { int r = cp->ir->resample_init(cp->ir); if (r == 0) { while (r == 0) { r = cp->ir->resample_do(cp->ir); if (cp->interrupt_threads) { break; } } cp->ir->resample_cleanup(cp->ir); } cp->ir->resample_pending = 0; } cp->ir->prepare_convdata(cp->ir); cp->ir->init_conv(cp->ir); cp->ir->reinit_pending = 0; cp->ir->reinit_running = 0; return NULL; } static gint reinit_timeout_callback(gpointer data) { struct control * cp = (struct control*)data; if (!cp->ir->ir_samples || !cp->ir->ir_nfram) { ir_wavedisplay_set_message(IR_WAVEDISPLAY(cp->wave_display), NULL); cp->reinit_timeout_tag = 0; return FALSE; } if (cp->ir->reinit_running) { if (cp->ir->resample_pending) { ir_wavedisplay_set_progress(IR_WAVEDISPLAY(cp->wave_display), cp->ir->src_progress); } return TRUE; } g_thread_join(cp->reinit_thread); cp->reinit_thread = NULL; ir_wavedisplay_set_progress(IR_WAVEDISPLAY(cp->wave_display), -1.0); ir_wavedisplay_set_message(IR_WAVEDISPLAY(cp->wave_display), NULL); ir_wavedisplay_set_wave(IR_WAVEDISPLAY(cp->wave_display), cp->ir->ir_samples[cp->disp_chan], cp->ir->ir_nfram); reset_values(cp); cp->reinit_timeout_tag = 0; return FALSE; } static gint timeout_callback(gpointer data) { struct control * cp = (struct control*)data; if (cp->interrupt_threads) { cp->timeout_tag = 0; return FALSE; } if (cp->ir->reinit_running) { return TRUE; } if (cp->ir->run && cp->ir->reinit_pending) { if (cp->ir->resample_pending) { ir_wavedisplay_set_progress(IR_WAVEDISPLAY(cp->wave_display), 0.0); } ir_wavedisplay_set_message(IR_WAVEDISPLAY(cp->wave_display), "Calculating..."); cp->ir->reinit_running = 1; cp->reinit_thread = g_thread_create(reinit_thread, cp, TRUE, NULL); cp->reinit_timeout_tag = g_timeout_add(100, reinit_timeout_callback, cp); cp->ir->run = 0; } return TRUE; } static void chan_toggle_cb(GtkWidget * widget, gpointer data) { struct control * cp = (struct control *)data; int i; for (i = 0; i < 4; i++) { if (widget == cp->chan_toggle[i]) { break; } } if (cp->ir->reinit_running) { g_signal_handler_block(widget, cp->chan_toggle_cbid[i]); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), !(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))); g_signal_handler_unblock(widget, cp->chan_toggle_cbid[i]); return; } if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { for (int j = 0; j < 4; j++) { if (i != j) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cp->chan_toggle[j]), FALSE); } } cp->disp_chan = i; if (cp->ir->ir_nfram) { ir_wavedisplay_set_wave(IR_WAVEDISPLAY(cp->wave_display), cp->ir->ir_samples[i], cp->ir->ir_nfram); } } } static void log_toggle_cb(GtkWidget * widget, gpointer data) { struct control * cp = (struct control *)data; if (cp->ir->reinit_running) { g_signal_handler_block(widget, cp->log_toggle_cbid); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), !(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))); g_signal_handler_unblock(widget, cp->log_toggle_cbid); return; } if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { gtk_button_set_label(GTK_BUTTON(widget), " log "); ir_wavedisplay_set_logarithmic(IR_WAVEDISPLAY(cp->wave_display), 1); } else { gtk_button_set_label(GTK_BUTTON(widget), " lin "); ir_wavedisplay_set_logarithmic(IR_WAVEDISPLAY(cp->wave_display), 0); } } static void about_button_cb(GtkWidget * about_button, gpointer data) { GtkWidget * dialog; GtkWidget * frame = gtk_frame_new(NULL); GtkWidget * label = gtk_label_new(""); GtkWidget * content_area; dialog = gtk_dialog_new_with_buttons("About IR", NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_NONE, NULL); content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); gtk_label_set_markup(GTK_LABEL(label), "" "IR: LV2 Convolution Reverb\n" "" S1 "version 1.3.2" S2 "\n\nCopyright (C) 2011-2012 Tom Szilagyi\n" XS1 "\nIR is free software under the GNU GPL. There is ABSOLUTELY\n" "NO WARRANTY, not even for MERCHANTABILITY or FITNESS\n" "FOR A PARTICULAR PURPOSE." XS2 "\n\n" "Homepage: http://factorial.hu/plugins/lv2/ir\n\n" "If you find this plugin useful, please\n" "consider buying me an item on my Amazon\n" "wishlist:\n" "\nhttp://www.amazon.co.uk/wishlist/8KRTCLLQMIP7\n\n" "... or donate a small amount to\n" "tomszilagyi@gmail.com via PayPal.\n\n" "Thank you!"); gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_container_add(GTK_CONTAINER(frame), label); gtk_container_set_border_width(GTK_CONTAINER(frame), 2*PAD); g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); gtk_container_add(GTK_CONTAINER(content_area), frame); gtk_container_set_border_width(GTK_CONTAINER(dialog), 2*PAD); gtk_widget_show_all(dialog); } static GtkWidget * make_top_hbox(struct control * cp) { GtkWidget * hbox = gtk_hbox_new(FALSE, PAD); GtkWidget * frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, PAD); GtkWidget * vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(frame), vbox); GtkWidget * hbox_wave = gtk_hbox_new(FALSE, PAD); gtk_box_pack_start(GTK_BOX(vbox), hbox_wave, TRUE, TRUE, PAD); GtkWidget * vbox_toggle = gtk_vbox_new(TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox_wave), vbox_toggle, FALSE, TRUE, PAD); for (int i = 0; i < 4; i++) { char str[4]; snprintf(str, 4, " %d ", i+1); cp->chan_toggle[i] = gtk_toggle_button_new_with_label(str); cp->chan_toggle_cbid[i] = g_signal_connect(cp->chan_toggle[i], "toggled", G_CALLBACK(chan_toggle_cb), cp); gtk_box_pack_start(GTK_BOX(vbox_toggle), cp->chan_toggle[i], TRUE, TRUE, PAD); gtk_widget_set_sensitive(cp->chan_toggle[i], FALSE); } cp->wave_display = ir_wavedisplay_new(); gtk_box_pack_start(GTK_BOX(hbox_wave), cp->wave_display, TRUE, TRUE, 0); cp->mode_ind = ir_modeind_new(); gtk_widget_set_size_request(cp->mode_ind, 100, -1); gtk_box_pack_start(GTK_BOX(hbox_wave), cp->mode_ind, FALSE, FALSE, PAD); GtkWidget * hbox2 = gtk_hbox_new(FALSE, PAD); gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, PAD); cp->log_toggle = gtk_toggle_button_new_with_label(" lin "); cp->log_toggle_cbid = g_signal_connect(cp->log_toggle, "toggled", G_CALLBACK(log_toggle_cb), cp); gtk_widget_set_size_request(cp->log_toggle, 50, -1); gtk_box_pack_start(GTK_BOX(hbox2), cp->log_toggle, FALSE, TRUE, PAD); cp->wave_annot_label = gtk_label_new(""); gtk_misc_set_alignment(GTK_MISC(cp->wave_annot_label), 0.0f, 0.5f); gtk_box_pack_start(GTK_BOX(hbox2), cp->wave_annot_label, TRUE, TRUE, PAD); GtkWidget * about_button = gtk_button_new_with_label(" About "); g_signal_connect(about_button, "clicked", G_CALLBACK(about_button_cb), cp); gtk_box_pack_start(GTK_BOX(hbox2), about_button, FALSE, TRUE, PAD); return hbox; } static void make_gui_proper(struct control * cp) { GtkWidget * vbox_top = cp->vbox_top; cp->toggle_reverse = gtk_toggle_button_new_with_label("Reverse"); cp->toggle_reverse_cbid = g_signal_connect(cp->toggle_reverse, "toggled", G_CALLBACK(toggle_button_cb), cp); /* upper half */ gtk_box_pack_start(GTK_BOX(vbox_top), make_top_hbox(cp), TRUE, TRUE, PAD); GtkWidget * hbox = gtk_hbox_new(FALSE, PAD); gtk_box_pack_start(GTK_BOX(vbox_top), hbox, TRUE, TRUE, 0); GtkWidget * hpaned = gtk_hpaned_new(); GtkWidget * hbox_1 = gtk_hbox_new(FALSE, PAD); gtk_paned_pack1(GTK_PANED(hpaned), hbox_1, TRUE, TRUE); gtk_box_pack_start(GTK_BOX(hbox_1), make_lists_box(cp), TRUE, TRUE, 0); GtkWidget * hbox_2 = gtk_hbox_new(FALSE, 0); gtk_paned_pack2(GTK_PANED(hpaned), hbox_2, TRUE, FALSE); GtkWidget * vbox = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), make_irctrl_frame(cp), TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), cp->toggle_reverse, FALSE, TRUE, PAD); gtk_box_pack_start(GTK_BOX(hbox_2), vbox, TRUE, TRUE, PAD); gtk_box_pack_start(GTK_BOX(hbox_2), make_mixer_frame(cp), FALSE, TRUE, PAD); gtk_box_pack_start(GTK_BOX(hbox), hpaned, TRUE, TRUE, 0); cp->timeout_tag = g_timeout_add(100, timeout_callback, cp); gtk_widget_show_all(vbox_top); } static void port_event(LV2UI_Handle ui, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void * buffer); static void replay_func(gpointer data, gpointer user_data) { port_event_t * pe = (port_event_t *)data; struct control * cp = (struct control*)user_data; port_event((LV2UI_Handle)cp, pe->port_index, 0, 0, &pe->value); free(pe); } static void replay_port_events(struct control * cp) { GSList * q = cp->port_event_q; g_slist_foreach(q, replay_func, cp); g_slist_free(q); } static gint waitplugin_timeout_callback(gpointer data) { struct control * cp = (struct control*)data; if (cp->ir->first_conf_done) { gtk_widget_destroy(cp->hbox_waitplugin); make_gui_proper(cp); replay_port_events(cp); cp->waitplugin_timeout_tag = 0; return FALSE; } if (cp->interrupt_threads) { cp->waitplugin_timeout_tag = 0; return FALSE; } return TRUE; } static GtkWidget * make_gui(struct control * cp) { cp->toggle_reverse = gtk_toggle_button_new_with_label("Reverse"); g_signal_connect(cp->toggle_reverse, "toggled", G_CALLBACK(toggle_button_cb), cp); cp->vbox_top = gtk_vbox_new(FALSE, PAD); if (cp->ir->first_conf_done) { make_gui_proper(cp); } else { cp->hbox_waitplugin = gtk_hbox_new(FALSE, PAD); gtk_box_pack_start(GTK_BOX(cp->vbox_top), cp->hbox_waitplugin, TRUE, TRUE, PAD); #ifdef _HAVE_GTK_ATLEAST_2_20 GtkWidget * spinner = gtk_spinner_new(); gtk_spinner_start(GTK_SPINNER(spinner)); gtk_box_pack_start(GTK_BOX(cp->hbox_waitplugin), spinner, TRUE, TRUE, PAD); #endif /* _HAVE_GTK_ATLEAST_2_20 */ GtkWidget * label = gtk_label_new(""); gtk_label_set_markup(GTK_LABEL(label), "" " Please wait while plugin is initialised... " "\n" XS1 " If the plugin is in BYPASS (Deactivated), please un-BYPASS (Activate) it." XS2); gtk_box_pack_start(GTK_BOX(cp->hbox_waitplugin), label, TRUE, TRUE, PAD); cp->waitplugin_timeout_tag = g_timeout_add(100, waitplugin_timeout_callback, cp); gtk_widget_show_all(cp->vbox_top); } return cp->vbox_top; } /* join any threads and wait for timeouts to exit */ static void join_timeouts(struct control * cp) { cp->interrupt_threads = 1; while (cp->timeout_tag || cp->gui_load_timeout_tag || cp->reinit_timeout_tag || cp->waitplugin_timeout_tag) { gtk_main_iteration_do(FALSE); } } static void cleanup(LV2UI_Handle ui) { //printf("cleanup()\n"); struct control * cp = (struct control *)ui; join_timeouts(cp); if (cp->store_files) { g_object_unref(cp->store_files); cp->store_files = 0; } free(cp); } static LV2UI_Handle instantiate(const struct _LV2UI_Descriptor * descriptor, const char * plugin_uri, const char * bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget * widget, const LV2_Feature * const * features) { int instance_access_found = 0; struct control * cp; //printf("instantiate('%s', '%s') called\n", plugin_uri, bundle_path); if (strcmp(plugin_uri, IR_URI) != 0) { fprintf(stderr, "IR_UI error: this GUI does not support plugin with URI %s\n", plugin_uri); goto fail; } cp = (struct control*)calloc(1, sizeof(struct control)); if (cp == NULL) { goto fail; } if (features != NULL) { int i = 0; while (features[i] != NULL) { if (strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0) { cp->ir = (IR *)(features[i]->data); instance_access_found = 1; } ++i; } if (!instance_access_found) { goto fail_free; } } else { goto fail_free; } if (cp->ir == NULL) { goto fail_free; } cp->controller = controller; cp->write_function = write_function; *widget = (LV2UI_Widget)make_gui(cp); return (LV2UI_Handle)cp; fail_free: if (!instance_access_found) { fprintf(stderr, "IR UI: error: required LV2 feature %s missing!\n", LV2_INSTANCE_ACCESS_URI); } free(cp); fail: return NULL; } static void port_event(LV2UI_Handle ui, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void * buffer) { struct control * cp = (struct control *)ui; float * pval = (float *)buffer; //printf("port_event(%u, %f) called\n", (unsigned int)port_index, *(float *)buffer); if (format != 0) { return; } if ((port_index < 0) || (port_index >= IR_N_PORTS)) { return; } if (!set_port_value(cp, port_index, *pval)) { return; } cp->port_buffer[port_index] = *pval; if (!cp->ir->first_conf_done) { port_event_t * pe = (port_event_t *)malloc(sizeof(port_event_t)); pe->port_index = port_index; pe->value = *pval; cp->port_event_q = g_slist_prepend(cp->port_event_q, pe); return; } int update_ui = 0; if (port_index == IR_PORT_REVERSE) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cp->toggle_reverse), (*pval > 0.0f)); update_ui = 1; } else if (port_index == IR_PORT_PREDELAY) { cp->predelay = *pval; set_adjustment(cp, cp->adj_predelay, *pval); update_ui = 1; } else if (port_index == IR_PORT_ATTACK) { cp->attack = *pval; set_adjustment(cp, cp->adj_attack, *pval); update_ui = 1; } else if (port_index == IR_PORT_ATTACKTIME) { cp->attacktime = *pval; set_adjustment(cp, cp->adj_attacktime, *pval); update_ui = 1; } else if (port_index == IR_PORT_ENVELOPE) { cp->envelope = *pval; set_adjustment(cp, cp->adj_envelope, *pval); update_ui = 1; } else if (port_index == IR_PORT_LENGTH) { cp->length = *pval; set_adjustment(cp, cp->adj_length, *pval); update_ui = 1; } else if (port_index == IR_PORT_STRETCH) { cp->stretch = *pval; set_adjustment(cp, cp->adj_stretch, *pval); update_ui = 1; } else if (port_index == IR_PORT_STEREO_IN) { set_adjustment(cp, cp->adj_stereo_in, *pval); } else if (port_index == IR_PORT_STEREO_IR) { cp->stereo_ir = *pval; set_adjustment(cp, cp->adj_stereo_ir, *pval); } else if (port_index == IR_PORT_AGC_SW) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cp->toggle_agc_sw), (*pval > 0.0f)); } else if (port_index == IR_PORT_DRY_SW) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cp->toggle_dry_sw), (*pval > 0.0f)); } else if (port_index == IR_PORT_DRY_GAIN) { set_adjustment(cp, cp->adj_dry_gain, *pval); } else if (port_index == IR_PORT_WET_SW) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cp->toggle_wet_sw), (*pval > 0.0f)); } else if (port_index == IR_PORT_WET_GAIN) { set_adjustment(cp, cp->adj_wet_gain, *pval); } else if (port_index == IR_PORT_FHASH_0) { /* NOP: plugin itself handles IR loading on session resume */ } else if (port_index == IR_PORT_FHASH_1) { /* NOP */ } else if (port_index == IR_PORT_FHASH_2) { /* NOP */ } else if (port_index == IR_PORT_METER_DRY_L) { ir_meter_set_level(IR_METER(cp->meter_L_dry), convert_real_to_scale(ADJ_DRY_GAIN, CO_DB(*pval))); } else if (port_index == IR_PORT_METER_DRY_R) { ir_meter_set_level(IR_METER(cp->meter_R_dry), convert_real_to_scale(ADJ_DRY_GAIN, CO_DB(*pval))); } else if (port_index == IR_PORT_METER_WET_L) { ir_meter_set_level(IR_METER(cp->meter_L_wet), convert_real_to_scale(ADJ_WET_GAIN, CO_DB(*pval))); } else if (port_index == IR_PORT_METER_WET_R) { ir_meter_set_level(IR_METER(cp->meter_R_wet), convert_real_to_scale(ADJ_WET_GAIN, CO_DB(*pval))); } if (update_ui) { update_envdisplay(cp); } } static LV2UI_Descriptor descriptors[] = { {IR_UI_URI, instantiate, cleanup, port_event, NULL} }; const LV2UI_Descriptor * lv2ui_descriptor(uint32_t index) { //printf("lv2ui_descriptor(%u) called\n", (unsigned int)index); if (index >= sizeof(descriptors) / sizeof(descriptors[0])) { return NULL; } return descriptors + index; } ir.lv2-1.3.2/ir_utils.cc0000644000175000017500000001621611636627117013454 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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. */ #include #include #include #include #include #include #include "ir.h" #include "ir_utils.h" /* Hash function to obtain key to IR file path. * This is the djb2 algorithm taken from: * http://www.cse.yorku.ca/~oz/hash.html */ uint64_t fhash(char *str) { uint64_t hash = 5381; int c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ } return hash; } uint64_t fhash_from_ports(float *port0, float *port1, float *port2) { uint64_t val0 = ((uint64_t)*port0) & 0xffff; uint64_t val1 = ((uint64_t)*port1) & 0xffffff; uint64_t val2 = ((uint64_t)*port2) & 0xffffff; return (val0 << 48) + (val1 << 24) + val2; } void ports_from_fhash(uint64_t fhash, float *port0, float *port1, float *port2) { *port0 = (float)((fhash >> 48) & 0xffff); *port1 = (float)((fhash >> 24) & 0xffffff); *port2 = (float)(fhash & 0xffffff); } GKeyFile * load_keyfile(void) { GKeyFile * keyfile = g_key_file_new(); gchar * ir_save_path = g_build_filename(g_get_home_dir(), IR_SAVE_FILE, NULL); if (g_file_test(ir_save_path, G_FILE_TEST_EXISTS) && g_file_test(ir_save_path, G_FILE_TEST_IS_REGULAR)) { if (!g_key_file_load_from_file(keyfile, ir_save_path, G_KEY_FILE_NONE, NULL)) { fprintf(stderr, "IR: could not load configuration data from %s\n", ir_save_path); } } g_free(ir_save_path); return keyfile; } void save_keyfile(GKeyFile * keyfile) { gchar * ir_save_path = g_build_filename(g_get_home_dir(), IR_SAVE_FILE, NULL); gchar * file_contents = g_key_file_to_data(keyfile, NULL, NULL); if (!g_file_set_contents(ir_save_path, file_contents, -1, NULL)) { fprintf(stderr, "IR: error saving configuration data to %s\n", ir_save_path); } g_free(ir_save_path); g_free(file_contents); } char * get_path_from_key(GKeyFile * keyfile, uint64_t fhash) { char key[20]; snprintf(key, 20, "%016" PRIx64, fhash); char * path = g_key_file_get_string(keyfile, GROUP_FHASH, key, NULL); return path; } void save_path(GKeyFile * keyfile, char * path) { uint64_t hash = fhash(path); char key[20]; snprintf(key, 20, "%016" PRIx64, hash); g_key_file_set_string(keyfile, GROUP_FHASH, key, path); } void load_bookmarks(GKeyFile * keyfile, GtkListStore * store) { gchar ** keys = g_key_file_get_keys(keyfile, GROUP_BOOKMARKS, NULL, NULL); gchar ** k = keys; while (k && *k) { gchar * str = g_key_file_get_string(keyfile, GROUP_BOOKMARKS, *k, NULL); GtkTreeIter iter; gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, *k, 1, str, -1); free(str); ++k; } g_strfreev(keys); } /* returns path if found, NULL if not found */ char * lookup_bookmark_in_store(GtkTreeModel * model, const char * bookmark) { GtkTreeIter iter; if (!gtk_tree_model_get_iter_first(model, &iter)) { return NULL; } do { char * key; char * path; gtk_tree_model_get(model, &iter, 0, &key, 1, &path, -1); if (strcmp(key, bookmark) == 0) { g_free(key); return path; } else { g_free(key); g_free(path); } } while (gtk_tree_model_iter_next(model, &iter)); return NULL; } void store_bookmark(GKeyFile * keyfile, char * bookmark, char * fullpath) { g_key_file_set_string(keyfile, GROUP_BOOKMARKS, bookmark, fullpath); } void delete_bookmark(GKeyFile * keyfile, char * bookmark) { g_key_file_remove_key(keyfile, GROUP_BOOKMARKS, bookmark, NULL); } int filename_filter(const char * file) { if (!file) { return 0; } if (strlen(file) < 5) { return 0; } const char * ext = file + strlen(file)-4; if (strcmp(ext, ".wav") == 0) { return 1; } if (strcmp(ext, ".WAV") == 0) { return 1; } if (strcmp(ext, ".aiff") == 0) { return 1; } if (strcmp(ext, ".AIFF") == 0) { return 1; } if (strcmp(ext, ".au") == 0) { return 1; } if (strcmp(ext, ".AU") == 0) { return 1; } if (strcmp(ext, ".flac") == 0) { return 1; } if (strcmp(ext, ".FLAC") == 0) { return 1; } if (strcmp(ext, ".ogg") == 0) { return 1; } if (strcmp(ext, ".OGG") == 0) { return 1; } return 0; } int dirname_filter(const char * file) { if (!file) { return 0; } if (strlen(file) < 1) { return 0; } if (file[0] == '.') { return 0; } return 1; } void load_files(GtkListStore * store, char * dirpath) { gtk_list_store_clear(store); GDir * dir = g_dir_open(dirpath, 0, NULL); if (!dir) { return; } const char * file; while ((file = g_dir_read_name(dir))) { char * filepath = g_build_filename(dirpath, file, NULL); if ((g_file_test(filepath, G_FILE_TEST_IS_DIR) && dirname_filter(file)) || filename_filter(file)) { GtkTreeIter iter; gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, file, 1, filepath, -1); } g_free(filepath); } g_dir_close(dir); } void select_entry(GtkTreeModel * model, GtkTreeSelection * select, char * filename) { GtkTreeIter iter; if (!gtk_tree_model_get_iter_first(model, &iter)) { return; } do { char * path; gtk_tree_model_get(model, &iter, 1, &path, -1); if (strcmp(filename, path) == 0) { gtk_tree_selection_select_iter(select, &iter); g_free(path); return; } } while (gtk_tree_model_iter_next(model, &iter)); gtk_tree_selection_unselect_all(select); } #define TAU 0.25 /* attack & envelope curves where t = 0..1 */ #define ATTACK_FN(t) (exp(((t) - 1.0) / TAU)) #define ENVELOPE_FN(t) (exp(-1.0 * (t) / TAU)) void compute_envelope(float ** samples, int nchan, int nfram, int attack_time_s, float attack_pc, float env_pc, float length_pc) { float gain; if (attack_time_s > nfram) { attack_time_s = nfram; } for (int j = 0; j < attack_time_s; j++) { gain = attack_pc / 100.0 + (100.0 - attack_pc) / 100.0 * ATTACK_FN((double)j / attack_time_s); for (int i = 0; i < nchan; i++) { samples[i][j] *= gain; } } int length_s = length_pc / 100.0 * (nfram - attack_time_s); for (int j = attack_time_s, k = 0; j < attack_time_s + length_s; j++, k++) { gain = env_pc / 100.0 + (100.0 - env_pc) / 100.0 * ENVELOPE_FN((double)k / length_s); for (int i = 0; i < nchan; i++) { samples[i][j] *= gain; } } for (int j = attack_time_s + length_s; j < nfram; j++) { for (int i = 0; i < nchan; i++) { samples[i][j] = 0.0f; } } } void draw_centered_text(cairo_t * cr, const char * text, int x , int y) { cairo_text_extents_t extents; cairo_text_extents(cr, text, &extents); int text_x = x - (extents.width/2 + extents.x_bearing); int text_y = y -(extents.height/2 + extents.y_bearing); cairo_move_to(cr, text_x, text_y); cairo_show_text(cr, text); } ir.lv2-1.3.2/manifest.ttl0000644000175000017500000000045411636627117013643 0ustar miramira# LV2 Plugin Manifest # Lists where plugins' data files and shared objects reside. @prefix lv2: . @prefix rdfs: . a lv2:Plugin; lv2:binary ; rdfs:seeAlso . ir.lv2-1.3.2/ChangeLog0000644000175000017500000000465411774341442013065 0ustar miramiraIR: LV2 Convolution Reverb Version 1.2.2 and 1.3.2 - 2012.07.02 18:05 CEST This pair of versions contains the migration to use zita-convolver-3.x.x in place of zita-convolver-2.0.0. This change guarantees that there are no data overruns while running the plugin non-realtime (freewheeling / Ardour export). NOTE: please read the plugin's webpage if you are confused about the dual versioning of IR (1.2.x vs 1.3.x). Version 1.2.1 and 1.3.1 - 2011.07.10 17:10 CEST Fixed Makefile from David Robillard. The change is that the module now relies on the GNU linker (via -z nodelete) to prevent unloading instead of reliance on the LV2 host obeying lv2:requiredFeature uiext:makeResident. This fixes http://tracker.ardour.org/view.php?id=4112 Note: IR 1.2.1 and 1.3.1 are versions with the same fix applied to IR 1.2 and 1.3, respectively. This dual versioning is necessary because 1.2 and 1.3 are in their own right both valid, but incompatible, versions. You have to choose which one you need - please see the plugin webpage for details. Version 1.3 - 2011.02.04 18:24 CET bugfix release * fix when being run with randomly varying block sizes * fix issues with automation: http://tracker.ardour.org/view.php?id=3214 * with very small block sizes zita-convolver doesn't seem to work Due to added buffering, these fixes necessitate processing latency (reported to host via standard LV2 mechanism). Latency is 2^N samples where N >= 10, its actual value depends on the JACK period size. Version 1.2 - 2011.01.25 17:35 CET bugfix & compatibility enhancements release: * fix ir.ttl typo: "doap:license" instead of "doap:licence" * visual feedback of the progress of resampling operations * resampling operations are interruptible; you can no longer crash the plugin by deleting it while resampling a long impulse file * correct GTK2 version requirement and check it in the Makefile: at least GTK 2.16 is needed Version 1.1 - 2011.01.14 21:12 CET bugfix release: * really use the user's homedir for storing the .ir_save file * proper GType and GThread initialization, at the proper place (loading of the plugin shared library) * fix ir.ttl: add missing namespace prefix "rdf" * fix usage of zita-convolver (proper stopping and deallocating of Convproc instances) * fix crash when removing a never-unbypassed plugin instance Version 1.0 - 2011.01.13. 23:08 CET initial public release ir.lv2-1.3.2/COPYING0000644000175000017500000004311011636627117012337 0ustar miramira GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ir.lv2-1.3.2/ir_wavedisplay.h0000644000175000017500000000420011636627117014474 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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 _IR_WAVEDISPLAY_H #define _IR_WAVEDISPLAY_H #include G_BEGIN_DECLS #define IR_WAVEDISPLAY_TYPE (ir_wavedisplay_get_type()) #define IR_WAVEDISPLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), IR_WAVEDISPLAY_TYPE, IRWaveDisplay)) #define IR_WAVEDISPLAY_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST((obj), IR_WAVEDISPLAY, IRWaveDisplayClass)) #define IR_WAVEDISPLAY_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS ((obj), IR_WAVEDISPLAY_TYPE, IRWavedisplayClass)) typedef struct _IRWaveDisplay IRWaveDisplay; typedef struct _IRWaveDisplayClass IRWaveDisplayClass; struct _IRWaveDisplay { GtkDrawingArea parent; /* < private > */ }; struct _IRWaveDisplayClass { GtkDrawingAreaClass parent_class; static void (*destroy)(GtkObject * object); }; GType ir_wavedisplay_get_type(); #define DISPLAY_DB_MIN -120.0f GtkWidget * ir_wavedisplay_new(void); void ir_wavedisplay_redraw(IRWaveDisplay * w); void ir_wavedisplay_redraw_all(IRWaveDisplay * w); void ir_wavedisplay_set_message(IRWaveDisplay * w, const char * msg); void ir_wavedisplay_set_progress(IRWaveDisplay * w, const float progress); void ir_wavedisplay_set_wave(IRWaveDisplay * w, float * values, int length); void ir_wavedisplay_set_logarithmic(IRWaveDisplay * w, int yes); void ir_wavedisplay_set_envparams(IRWaveDisplay * w, int attack_time_s, float attack_pc, float env_pc, float length_pc, int reverse); G_END_DECLS #endif /* _IR_WAVEDISPLAY_H */ ir.lv2-1.3.2/ir.ttl0000644000175000017500000001601211772057773012452 0ustar miramira@prefix rdf: . @prefix rdfs: . @prefix lv2: . @prefix doap: . @prefix llext: . @prefix foaf: . @prefix ue: . @prefix epp: . @prefix uiext: . a uiext:GtkUI; uiext:binary ; lv2:requiredFeature ; lv2:requiredFeature uiext:makeResident. a lv2:Plugin; a lv2:ReverbPlugin; doap:name "IR"; doap:license ; doap:maintainer [ foaf:name "Tom Szilagyi"; foaf:homepage ; foaf:mbox ; ]; uiext:ui ; ########## Audio I/O ports ########## lv2:port [ a lv2:InputPort, lv2:AudioPort; lv2:datatype lv2:float; lv2:index 0; lv2:symbol "in_L"; lv2:name "Left audio input"; ] , [ a lv2:InputPort, lv2:AudioPort; lv2:datatype lv2:float; lv2:index 1; lv2:symbol "in_R"; lv2:name "Right audio input"; ] , [ a lv2:OutputPort, lv2:AudioPort; lv2:datatype lv2:float; lv2:index 2; lv2:symbol "out_L"; lv2:name "Left audio output"; ] , [ a lv2:OutputPort, lv2:AudioPort; lv2:datatype lv2:float; lv2:index 3; lv2:symbol "out_R"; lv2:name "Right audio output"; ] , ########## Control ports ########## [ a lv2:InputPort, lv2:ControlPort ; lv2:index 4 ; lv2:symbol "port_reverse" ; lv2:name "Reverse IR" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; lv2:portProperty epp:hasStrictBounds; lv2:portProperty lv2:toggled; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 5 ; lv2:symbol "port_predelay" ; lv2:name "Predelay" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 2000.0 ; lv2:portProperty epp:hasStrictBounds; ue:unit ue:ms; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 6 ; lv2:symbol "port_attack" ; lv2:name "Attack" ; lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; lv2:portProperty epp:hasStrictBounds; ue:unit ue:pc; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 7 ; lv2:symbol "port_attacktime" ; lv2:name "Attack time" ; lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 300.0 ; lv2:portProperty epp:hasStrictBounds; ue:unit ue:ms; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 8 ; lv2:symbol "port_envelope" ; lv2:name "Envelope" ; lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; lv2:portProperty epp:hasStrictBounds; ue:unit ue:pc; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 9 ; lv2:symbol "port_length" ; lv2:name "Length" ; lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; lv2:portProperty epp:hasStrictBounds; ue:unit ue:pc; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 10 ; lv2:symbol "port_stretch" ; lv2:name "Stretch" ; lv2:default 100.0 ; lv2:minimum 50.0 ; lv2:maximum 150.0 ; lv2:portProperty epp:hasStrictBounds; ue:unit ue:pc; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 11 ; lv2:symbol "port_stereo_in" ; lv2:name "Stereo width in" ; lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 150.0 ; lv2:portProperty epp:hasStrictBounds; ue:unit ue:pc; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 12 ; lv2:symbol "port_stereo_ir" ; lv2:name "Stereo width IR" ; lv2:default 100.0 ; lv2:minimum 0.0 ; lv2:maximum 150.0 ; lv2:portProperty epp:hasStrictBounds; ue:unit ue:pc; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 13 ; lv2:symbol "port_agc_sw" ; lv2:name "Autogain" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; lv2:portProperty epp:hasStrictBounds; lv2:portProperty lv2:toggled; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 14 ; lv2:symbol "port_dry_sw" ; lv2:name "Dry" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; lv2:portProperty epp:hasStrictBounds; lv2:portProperty lv2:toggled; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 15 ; lv2:symbol "port_dry_gain" ; lv2:name "Dry gain" ; lv2:default 0.0 ; lv2:minimum -90.0 ; lv2:maximum 6.0 ; lv2:scalePoint [ rdfs:label "-inf"; rdf:value -90.0; ]; lv2:portProperty epp:hasStrictBounds; ue:unit ue:db; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 16 ; lv2:symbol "port_wet_sw" ; lv2:name "Wet" ; lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ; lv2:portProperty epp:hasStrictBounds; lv2:portProperty lv2:toggled; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 17 ; lv2:symbol "port_wet_gain" ; lv2:name "Wet gain" ; lv2:default -10.0 ; lv2:minimum -90.0 ; lv2:maximum 6.0 ; lv2:scalePoint [ rdfs:label "-inf"; rdf:value -90.0; ]; lv2:portProperty epp:hasStrictBounds; ue:unit ue:db; ] , ########## Save/Restore ports ########## [ a lv2:InputPort, lv2:ControlPort ; lv2:index 18 ; lv2:symbol "port_fhash_0" ; lv2:name "FileHash0" ; lv2:default 0.0 ; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 19 ; lv2:symbol "port_fhash_1" ; lv2:name "FileHash1" ; lv2:default 0.0 ; ] , [ a lv2:InputPort, lv2:ControlPort ; lv2:index 20 ; lv2:symbol "port_fhash_2" ; lv2:name "FileHash2" ; lv2:default 0.0 ; ] , ########## Meter ports ########## [ a lv2:OutputPort, lv2:ControlPort ; lv2:index 21 ; lv2:symbol "port_meter_dry_L" ; lv2:name "Dry L meter" ; ] , [ a lv2:OutputPort, lv2:ControlPort ; lv2:index 22 ; lv2:symbol "port_meter_dry_R" ; lv2:name "Dry R meter" ; ] , [ a lv2:OutputPort, lv2:ControlPort ; lv2:index 23 ; lv2:symbol "port_meter_wet_L" ; lv2:name "Wet L meter" ; ] , [ a lv2:OutputPort, lv2:ControlPort ; lv2:index 24 ; lv2:symbol "port_meter_wet_R" ; lv2:name "Wet R meter" ; ] , ########## Latency reporting ########## [ a lv2:OutputPort, lv2:ControlPort ; lv2:index 25 ; lv2:symbol "port_latency" ; lv2:name "Latency" ; lv2:portProperty lv2:reportsLatency ; ] . ir.lv2-1.3.2/ir.cc0000644000175000017500000005552511772074205012235 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "ir.h" #include "ir_utils.h" #if ZITA_CONVOLVER_MAJOR_VERSION != 3 #error "This version of IR requires zita-convolver 3.x.x" #endif /* You may need to change these to match your JACK server setup! * * Priority should match -P parameter passed to jackd. * Sched.class: either SCHED_FIFO or SCHED_RR (I think Jack uses SCHED_FIFO). * * THREAD_SYNC_MODE must be true if you want to use the plugin in Jack * freewheeling mode (eg. while exporting in Ardour). You may only use * false if you *only* run the plugin realtime. */ #define CONVPROC_SCHEDULER_PRIORITY 0 #define CONVPROC_SCHEDULER_CLASS SCHED_FIFO #define THREAD_SYNC_MODE true static LV2_Descriptor * IR_Descriptor = NULL; static GKeyFile * keyfile = NULL; static GtkListStore * store_bookmarks = NULL; G_LOCK_DEFINE_STATIC(conv_configure_lock); static void connectPortIR(LV2_Handle instance, uint32_t port, void * data) { IR * ir = (IR *)instance; switch (port) { /* Audio I/O */ case IR_PORT_INPUT_L: ir->in_L = (const float*)data; break; case IR_PORT_INPUT_R: ir->in_R = (const float*)data; break; case IR_PORT_OUTPUT_L: ir->out_L = (float*)data; break; case IR_PORT_OUTPUT_R: ir->out_R = (float*)data; break; /* Control */ case IR_PORT_REVERSE: ir->port_reverse = (float*)data; break; case IR_PORT_PREDELAY: ir->port_predelay = (float*)data; break; case IR_PORT_ATTACK: ir->port_attack = (float*)data; break; case IR_PORT_ATTACKTIME: ir->port_attacktime = (float*)data; break; case IR_PORT_ENVELOPE: ir->port_envelope = (float*)data; break; case IR_PORT_LENGTH: ir->port_length = (float*)data; break; case IR_PORT_STRETCH: ir->port_stretch = (float*)data; break; case IR_PORT_STEREO_IN: ir->port_stereo_in = (float*)data; break; case IR_PORT_STEREO_IR: ir->port_stereo_ir = (float*)data; break; case IR_PORT_AGC_SW: ir->port_agc_sw = (float*)data; break; case IR_PORT_DRY_SW: ir->port_dry_sw = (float*)data; break; case IR_PORT_DRY_GAIN: ir->port_dry_gain = (float*)data; break; case IR_PORT_WET_SW: ir->port_wet_sw = (float*)data; break; case IR_PORT_WET_GAIN: ir->port_wet_gain = (float*)data; break; /* Save/Restore */ case IR_PORT_FHASH_0: ir->port_fhash_0 = (float*)data; break; case IR_PORT_FHASH_1: ir->port_fhash_1 = (float*)data; break; case IR_PORT_FHASH_2: ir->port_fhash_2 = (float*)data; break; /* Meter ports */ case IR_PORT_METER_DRY_L: ir->port_meter_dry_L = (float*)data; break; case IR_PORT_METER_DRY_R: ir->port_meter_dry_R = (float*)data; break; case IR_PORT_METER_WET_L: ir->port_meter_wet_L = (float*)data; break; case IR_PORT_METER_WET_R: ir->port_meter_wet_R = (float*)data; break; /* Latency port */ case IR_PORT_LATENCY: ir->port_latency = (float*)data; break; } } static void free_ir_samples(IR * ir) { if (ir->ir_samples != 0) { float **p = ir->ir_samples; while (*p) { free(*p++); } free(ir->ir_samples); ir->ir_samples = 0; } } static void free_conv_safely(Convproc * conv) { unsigned int state; if (!conv) { return; } state = conv->state(); if (state != Convproc::ST_STOP) { conv->stop_process(); } conv->cleanup(); delete conv; } static void free_convproc(IR * ir) { free_conv_safely(ir->conv_0); ir->conv_0 = 0; free_conv_safely(ir->conv_1); ir->conv_1 = 0; } static void cleanupIR(LV2_Handle instance) { IR * ir = (IR*)instance; if (!ir->first_conf_done) { ir->conf_thread_exit = 1; g_thread_join(ir->conf_thread); } free_convproc(ir); if (ir->source_samples != NULL) { free(ir->source_samples); ir->source_samples = NULL; } if (ir->resampled_samples != NULL) { free(ir->resampled_samples); ir->resampled_samples = NULL; } free_ir_samples(ir); if (ir->source_path && (strlen(ir->source_path) > 0)) { save_path(keyfile, ir->source_path); free(ir->source_path); } free(instance); } /* Read IR audio file * input data: source_path * output data: nchan, source_samples * return value: 0 OK, < 0 error */ static int load_sndfile(IR * ir) { int length = 0; int offset = 0; float * buff; if (!(ir->source_path) || *ir->source_path != '/') { fprintf(stderr, "IR: load_sndfile error: %s is not an absolute path\n", ir->source_path); return -1; } ir->Finp = sf_open(ir->source_path, SFM_READ, &ir->Sinp); if (!ir->Finp) { fprintf(stderr, "IR: unable to read IR input file '%s'\n", ir->source_path); return -1; } ir->source_samplerate = ir->Sinp.samplerate; ir->nchan = ir->Sinp.channels; ir->source_nfram = ir->Sinp.frames; if ((ir->nchan != 1) && (ir->nchan != 2) && (ir->nchan != 4)) { fprintf(stderr, "IR: channel count %d of '%s' not supported.\n", ir->nchan, ir->source_path); sf_close(ir->Finp); return -1; } length = ir->source_nfram; if (ir->source_samples != NULL) { free(ir->source_samples); } ir->source_samples = (float*)malloc(ir->nchan * length * sizeof(float)); buff = new float[BSIZE * ir->nchan]; while (length) { int n = (length > BSIZE) ? BSIZE : length; n = sf_readf_float(ir->Finp, buff, n); if (n < 0) { fprintf(stderr, "IR: error reading file %s\n", ir->source_path); sf_close(ir->Finp); delete[] buff; return -1; } if (n) { for (int i = 0; i < n * ir->nchan; i++) { ir->source_samples[offset + i] = buff[i]; } offset += n * ir->nchan; length -= n; } } delete[] buff; sf_close(ir->Finp); return 0; } /* Resample the IR samples, taking stretch into account * input: source_nfram, source_samples * output: ir_nfram, resampled_samples * This function sets up the resampling operation * return: 0: OK, 1: OK, no SRC needed, -1: error */ static int resample_init(IR * ir) { float stretch = *ir->port_stretch / 100.0; float fs_out = ir->sample_rate * stretch; if (!ir->source_samples || !ir->source_nfram || !ir->nchan) { return -1; } if (ir->source_samplerate == (unsigned int)fs_out) { ir->ir_nfram = ir->source_nfram; if (ir->resampled_samples != NULL) { free(ir->resampled_samples); } ir->resampled_samples = (float*)calloc(ir->nchan * ir->ir_nfram, sizeof(float)); for (int i = 0; i < ir->nchan * ir->ir_nfram; i++) { ir->resampled_samples[i] = ir->source_samples[i]; } return 1; } ir->ir_nfram = ir->source_nfram * fs_out / ir->source_samplerate + 1; //printf("IR Resampler: fs_in=%d fs_out=%f\n", ir->source_samplerate, fs_out); //printf(" samples_in=%d samples_out=%d\n", ir->source_nfram, ir->ir_nfram); if (ir->resampled_samples != NULL) { free(ir->resampled_samples); } ir->resampled_samples = (float*)calloc(ir->nchan * ir->ir_nfram, sizeof(float)); int src_error; ir->src_state = src_new(SRC_SINC_BEST_QUALITY, ir->nchan, &src_error); if (ir->src_state == NULL) { fprintf(stderr, "IR: src_new() error: %s\n", src_strerror(src_error)); return -1; } src_error = src_set_ratio(ir->src_state, fs_out / ir->source_samplerate); if (src_error) { fprintf(stderr, "IR: src_set_ratio() error: %s, new_ratio = %g\n", src_strerror(src_error), fs_out / ir->source_samplerate); src_delete(ir->src_state); return -1; } ir->src_progress = 0.0; ir->src_in_frames = ir->source_nfram; ir->src_out_frames = 0; ir->src_data.data_in = ir->source_samples; ir->src_data.data_out = ir->resampled_samples; ir->src_data.input_frames_used = 0; ir->src_data.output_frames_gen = 0; ir->src_data.src_ratio = fs_out / ir->source_samplerate; /* really needed? */ ir->src_data.end_of_input = 0; return 0; } /* Do a chunk of resample processing * return: 0: OK, not ready (call it again); 1: ready; -1: error * ir->src_progress can be used to track progress of resampling */ static int resample_do(IR * ir) { if (!ir->src_in_frames) { return 1; } ir->src_data.input_frames = (ir->src_in_frames > BSIZE_SR) ? BSIZE_SR : ir->src_in_frames; ir->src_data.output_frames = ir->ir_nfram - ir->src_out_frames; //printf("src_progress %f\n", ir->src_progress); int src_error = src_process(ir->src_state, &ir->src_data); if (src_error != 0) { fprintf(stderr, "IR: src_process() error: %s\n", src_strerror(src_error)); src_delete(ir->src_state); return -1; } ir->src_data.data_in += ir->nchan * ir->src_data.input_frames_used; ir->src_data.data_out += ir->nchan * ir->src_data.output_frames_gen; ir->src_in_frames -= ir->src_data.input_frames_used; ir->src_out_frames += ir->src_data.output_frames_gen; ir->src_progress = (float)ir->src_out_frames / ir->ir_nfram; return ir->src_in_frames ? 0 : 1; } /* Finish resampling; call this after resample_do returned 1 */ static void resample_cleanup(IR * ir) { if (ir->src_out_frames < ir->ir_nfram) { ir->ir_nfram = ir->src_out_frames; } ir->src_progress = 1.0; src_delete(ir->src_state); } /* In place processing on ir_samples[] */ static void process_envelopes(IR * ir) { int attack_time_s = (int)*ir->port_attacktime * ir->sample_rate / 1000; float attack_pc = *ir->port_attack; float length_pc = *ir->port_length; float env_pc = *ir->port_envelope; compute_envelope(ir->ir_samples, ir->nchan, ir->ir_nfram, attack_time_s, attack_pc, env_pc, length_pc); } /* Mid-Side based Stereo width effect */ static void ms_stereo(float width, float * lp, float * rp, int length) { float w = width / 100.0f; float x = (1.0 - w) / (1.0 + w); /* M-S coeff.; L_out = L + x*R; R_out = x*L + R */ float L, R; for (int i = 0; i < length; i++) { L = *lp; R = *rp; *lp++ = L + x * R; *rp++ = R + x * L; } } /* Prepare samples to be loaded into convolution engine * input: ir_nfram, resampled_samples * output: ir_samples * parameters: all plugin parameters except stretch, * stereo_in, dry_*, wet_* */ static void prepare_convdata(IR * ir) { if (!ir->resampled_samples || !ir->ir_nfram || !ir->nchan) { return; } free_ir_samples(ir); ir->ir_samples = (float**)malloc((1 + ir->nchan) * sizeof(float*)); for (int i = 0; i < ir->nchan; i++) { ir->ir_samples[i] = (float*)malloc(ir->ir_nfram * sizeof(float)); } ir->ir_samples[ir->nchan] = NULL; /* de-interleave resampled_samples to ir_samples */ for (int ch = 0; ch < ir->nchan; ch++) { float * p = ir->resampled_samples + ch; float * q = ir->ir_samples[ch]; int nch = ir->nchan; int nfram = ir->ir_nfram; for (int i = 0; i < nfram; i++) { q[i] = p[i * nch]; } } /* Autogain calculation */ float pow = 0; for (int ch = 0; ch < ir->nchan; ch++) { float * p = ir->ir_samples[ch]; for (int i = 0; i < ir->ir_nfram; i++) { pow += p[i] * p[i]; } } pow /= ir->nchan; ir->autogain_new = -10.0 * log10f(pow / 6.0); /* IR stereo width */ if (ir->nchan == 2) { ms_stereo(*ir->port_stereo_ir, ir->ir_samples[0], ir->ir_samples[1], ir->ir_nfram); } else if (ir->nchan == 4) { ms_stereo(*ir->port_stereo_ir, ir->ir_samples[0], ir->ir_samples[1], ir->ir_nfram); ms_stereo(*ir->port_stereo_ir, ir->ir_samples[2], ir->ir_samples[3], ir->ir_nfram); } process_envelopes(ir); /* reverse ir vector if needed */ int reverse = (*ir->port_reverse > 0.0f) ? 1 : 0; if (reverse) { float tmp; int nfram = ir->ir_nfram; for (int ch = 0; ch < ir->nchan; ch++) { float * p = ir->ir_samples[ch]; for (int i = 0, j = nfram-1; i < nfram/2; i++, j--) { tmp = p[i]; p[i] = p[j]; p[j] = tmp; } } } } /* Initialise (the next) convolution engine to use */ static void init_conv(IR * ir) { Convproc * conv; int req_to_use; if (!ir->ir_samples || !ir->ir_nfram || !ir->nchan) { return; } if (ir->conv_in_use != ir->conv_req_to_use) { fprintf(stderr, "IR init_conv: error, engine still in use!\n"); return; } if (ir->conv_in_use == 1) { /* new one will be 0th */ free_conv_safely(ir->conv_0); ir->conv_0 = new Convproc; conv = ir->conv_0; req_to_use = 0; } else { /* new one will be 1st */ free_conv_safely(ir->conv_1); ir->conv_1 = new Convproc; conv = ir->conv_1; req_to_use = 1; } uint32_t predelay_samples = (int)*ir->port_predelay * ir->sample_rate / 1000; uint32_t length = ir->maxsize; int nfram; if (predelay_samples + ir->ir_nfram > length) { fprintf(stderr, "IR: warning: truncated IR to %d samples\n", length); nfram = length - predelay_samples; } else { nfram = ir->ir_nfram; length = predelay_samples + ir->ir_nfram; } if (length < ir->block_length) { length = ir->block_length; } G_LOCK(conv_configure_lock); //printf("configure length=%d ir->block_length=%d\n", length, ir->block_length); if (ir->nchan == 4) { conv->set_density(1); } int ret = conv->configure(2, // n_inputs 2, // n_outputs length, ir->block_length, ir->block_length, Convproc::MAXPART); G_UNLOCK(conv_configure_lock); if (ret != 0) { fprintf(stderr, "IR: can't initialise zita-convolver engine, Convproc::configure returned %d\n", ret); free_conv_safely(conv); if (req_to_use == 0) { ir->conv_0 = NULL; } else { ir->conv_1 = NULL; } return; } switch (ir->nchan) { case 1: /* both input channels are convolved with the same IR */ conv->impdata_create(0, 0, 1, ir->ir_samples[0], predelay_samples, nfram + predelay_samples); conv->impdata_copy(0, 0, 1, 1); break; case 2: /* left channel convolved with left IR channel yields left output same for the right channel */ conv->impdata_create(0, 0, 1, ir->ir_samples[0], predelay_samples, nfram + predelay_samples); conv->impdata_create(1, 1, 1, ir->ir_samples[1], predelay_samples, nfram + predelay_samples); break; case 4: /* IR is a full matrix: / in_L -> out_L in_L -> out_R \ \ in_R -> out_L in_R -> out_R / */ conv->impdata_create(0, 0, 1, ir->ir_samples[0], predelay_samples, nfram + predelay_samples); conv->impdata_create(0, 1, 1, ir->ir_samples[1], predelay_samples, nfram + predelay_samples); conv->impdata_create(1, 0, 1, ir->ir_samples[2], predelay_samples, nfram + predelay_samples); conv->impdata_create(1, 1, 1, ir->ir_samples[3], predelay_samples, nfram + predelay_samples); break; default: printf("IR init_conv: error, impossible value: ir->nchan = %d\n", ir->nchan); } conv->start_process(CONVPROC_SCHEDULER_PRIORITY, CONVPROC_SCHEDULER_CLASS); ir->conv_req_to_use = req_to_use; } static gpointer IR_configurator_thread(gpointer data) { IR * ir = (IR *)data; struct timespec treq; struct timespec trem; while (!ir->conf_thread_exit) { if ((ir->run > 0) && !ir->first_conf_done) { uint64_t fhash = fhash_from_ports(ir->port_fhash_0, ir->port_fhash_1, ir->port_fhash_2); //printf("IR confthread: fhash = %016" PRIx64 "\n", fhash); if (fhash) { char * filename = get_path_from_key(keyfile, fhash); if (filename) { //printf(" load filename=%s\n", filename); ir->source_path = filename; if (load_sndfile(ir) == 0) { int r = resample_init(ir); if (r == 0) { while ((r == 0) && (!ir->conf_thread_exit)) { r = resample_do(ir); } resample_cleanup(ir); } if (r >= 0) { prepare_convdata(ir); init_conv(ir); } } else { free(ir->source_path); ir->source_path = NULL; } } else { fprintf(stderr, "IR: fhash=%016" PRIx64 " was not found in DB\n", fhash); } } ir->first_conf_done = 1; return NULL; } /* sleep 100 ms before checking again */ treq.tv_sec = 0; treq.tv_nsec = 100000000; nanosleep(&treq, &trem); } return NULL; } static LV2_Handle instantiateIR(const LV2_Descriptor *descriptor, double sample_rate, const char *path, const LV2_Feature *const *features) { IR * ir = (IR *)calloc(1, sizeof(IR)); ir->sample_rate = sample_rate; ir->reinit_pending = 0; ir->maxsize = MAXSIZE; ir->block_length = IR_DEFAULT_JACK_BUFLEN; ir->bufconv_pos = 0; ir->run = -5; /* do nothing for the first 5 run() calls */ ir->load_sndfile = load_sndfile; ir->resample_init = resample_init; ir->resample_do = resample_do; ir->resample_cleanup = resample_cleanup; ir->prepare_convdata = prepare_convdata; ir->init_conv = init_conv; ir->keyfile = keyfile; ir->store_bookmarks = store_bookmarks; ir->conf_thread = g_thread_create(IR_configurator_thread, (gpointer)ir, TRUE, NULL); return (LV2_Handle)ir; } #define ACC_MAX(m,v) (((fabs(v))>(m))?fabs(v):(m)) static void runIR(LV2_Handle instance, uint32_t n) { IR * ir = (IR *)instance; Convproc * conv; const float * const in_L = ir->in_L; const float * const in_R = ir->in_R; float * const out_L = ir->out_L; float * const out_R = ir->out_R; float dry_L_meter = 0.0; float dry_R_meter = 0.0; float wet_L_meter = 0.0; float wet_R_meter = 0.0; float width = ir->width; float dry_gain = ir->dry_gain; float wet_gain = ir->wet_gain; if (ir->run < 0) { /* XXX safety measure: wait until buffer size stabilizes, bypass until then */ if ((in_L != out_L) || (in_R != out_R)) { for (unsigned int j = 0; j < n; j++) { out_L[j] = in_L[j]; out_R[j] = in_R[j]; } } ir->run++; *ir->port_latency = ir->block_length; return; } if (ir->conv_in_use != ir->conv_req_to_use) { /* call stop_process() on the conv being switched away from * so it can be deallocated next time it is needed */ Convproc * conv_from; if (ir->conv_in_use == 0) { conv_from = ir->conv_0; } else { conv_from = ir->conv_1; } if (conv_from) { conv_from->stop_process(); } ir->conv_in_use = ir->conv_req_to_use; ir->autogain = ir->autogain_new; //printf("IR engine switching to conv_%d autogain = %f\n", ir->conv_in_use, ir->autogain); /* fade it up */ wet_gain = 0.0; } if (ir->conv_in_use == 0) { conv = ir->conv_0; } else { conv = ir->conv_1; } if (n > ir->block_length) { /* MUST range from IR_DEFAULT_JACK_BUFLEN<<1 to IR_MAXIMUM_JACK_BUFLEN */ if ((n == 2048) || (n == 4096) || (n = 8192) || (n = 16384)) { /* block size seems to be a valid JACK buffer size */ ir->block_length = n; ir->reinit_pending = 1; ir->bufconv_pos = 0; conv = 0; //printf("IR block_length = %d\n", n); } } if (n > IR_MAXIMUM_JACK_BUFLEN) { fprintf(stderr, "IR: being run() with buffer length %d greater than largest supported length %d, bypassing...\n", n, IR_MAXIMUM_JACK_BUFLEN); if ((in_L != out_L) || (in_R != out_R)) { for (unsigned int j = 0; j < n; j++) { out_L[j] = in_L[j]; out_R[j] = in_R[j]; } } return; } float agc_gain_raw = (*ir->port_agc_sw > 0.0f) ? DB_CO(ir->autogain) : 1.0f; float width_raw = *ir->port_stereo_in / 100.0f; /* stereo width */ float dry_sw = (*ir->port_dry_sw > 0.0f) ? 1.0f : 0.0f; float wet_sw = (*ir->port_wet_sw > 0.0f) ? 1.0f : 0.0f; float dry_gain_raw = DB_CO(*ir->port_dry_gain) * dry_sw; float wet_gain_raw = DB_CO(*ir->port_wet_gain) * wet_sw * agc_gain_raw; float * pi, * qi, * po, * qo, * dL, * dR; float dry_L, dry_R, wet_L, wet_R; unsigned int bcp = ir->bufconv_pos; if (conv != 0) { pi = conv->inpdata(0); qi = conv->inpdata(1); po = conv->outdata(0); qo = conv->outdata(1); dL = ir->drybuf_L; dR = ir->drybuf_R; float x; for (unsigned int j = 0; j < n; j++) { width = width * SMOOTH_CO_1 + width_raw * SMOOTH_CO_0; x = (1.0 - width) / (1.0 + width); /* M-S coeff. */ pi[bcp] = in_L[j] + x * in_R[j]; qi[bcp] = in_R[j] + x * in_L[j]; dry_gain = SMOOTH_CO_1 * dry_gain + SMOOTH_CO_0 * dry_gain_raw; wet_gain = SMOOTH_CO_1 * wet_gain + SMOOTH_CO_0 * wet_gain_raw; dry_L = dL[bcp]; dry_R = dR[bcp]; dL[bcp] = in_L[j] * dry_gain; dR[bcp] = in_R[j] * dry_gain; wet_L = po[bcp] * wet_gain; wet_R = qo[bcp] * wet_gain; dry_L_meter = ACC_MAX(dry_L_meter, dry_L); dry_R_meter = ACC_MAX(dry_R_meter, dry_R); wet_L_meter = ACC_MAX(wet_L_meter, wet_L); wet_R_meter = ACC_MAX(wet_R_meter, wet_R); out_L[j] = dry_L + wet_L; out_R[j] = dry_R + wet_R; if (++bcp == ir->block_length) { bcp = 0; conv->process(THREAD_SYNC_MODE); } } } else { /* convolution engine not available */ dL = ir->drybuf_L; dR = ir->drybuf_R; for (unsigned int j = 0; j < n; j++) { dry_gain = SMOOTH_CO_1 * dry_gain + SMOOTH_CO_0 * dry_gain_raw; dry_L = dL[bcp]; dry_R = dR[bcp]; dL[bcp] = in_L[j] * dry_gain; dR[bcp] = in_R[j] * dry_gain; dry_L_meter = ACC_MAX(dry_L_meter, dry_L); dry_R_meter = ACC_MAX(dry_R_meter, dry_R); out_L[j] = dry_L; out_R[j] = dry_R; if (++bcp == ir->block_length) { bcp = 0; } } } ir->width = width; ir->dry_gain = dry_gain; ir->wet_gain = wet_gain; ir->bufconv_pos = bcp; *ir->port_meter_dry_L = dry_L_meter; *ir->port_meter_dry_R = dry_R_meter; *ir->port_meter_wet_L = wet_L_meter; *ir->port_meter_wet_R = wet_R_meter; *ir->port_latency = ir->block_length; ir->run = 1; } const void * extdata_IR(const char * uri) { return NULL; } void __attribute__ ((constructor)) init() { if (zita_convolver_major_version () != ZITA_CONVOLVER_MAJOR_VERSION) { fprintf(stderr, "IR: compile-time & runtime library versions of zita-convolver do not match!\n"); IR_Descriptor = NULL; return; } g_type_init(); if (!g_thread_supported()) { printf("IR: initializing GThread.\n"); g_thread_init(NULL); if (!g_thread_supported()) { fprintf(stderr, "IR: error initialising GThread. This plugin requires a working GLib with GThread.\n"); IR_Descriptor = NULL; return; } } IR_Descriptor = (LV2_Descriptor *)malloc(sizeof(LV2_Descriptor)); IR_Descriptor->URI = IR_URI; IR_Descriptor->instantiate = instantiateIR; IR_Descriptor->cleanup = cleanupIR; IR_Descriptor->activate = NULL; IR_Descriptor->deactivate = NULL; IR_Descriptor->connect_port = connectPortIR; IR_Descriptor->run = runIR; IR_Descriptor->extension_data = extdata_IR; keyfile = load_keyfile(); store_bookmarks = gtk_list_store_new(2, G_TYPE_STRING, /* visible name (key) */ G_TYPE_STRING); /* full pathname (value) */ load_bookmarks(keyfile, store_bookmarks); } void __attribute__ ((destructor)) fini() { save_keyfile(keyfile); g_key_file_free(keyfile); g_object_unref(store_bookmarks); free(IR_Descriptor); } LV2_SYMBOL_EXPORT const LV2_Descriptor * lv2_descriptor(uint32_t index) { switch (index) { case 0: return IR_Descriptor; default: return NULL; } } ir.lv2-1.3.2/ir.h0000644000175000017500000001261211772057774012101 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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 _IR_H #define _IR_H #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #define IR_URI "http://factorial.hu/plugins/lv2/ir" #define BSIZE 0x4000 /* Blocksize for soundfile data access */ #define BSIZE_SR 0x1000 /* Blocksize for SRC */ #define MAXSIZE 0x00100000 /* Max. available convolver size of zita-convolver */ #define DB_CO(g) ((g) > -90.0f ? exp10f((g) * 0.05f) : 0.0f) #define CO_DB(g) ((g) > 0.0f ? 20.0f * log10f(g) : -90.0f) #define SMOOTH_CO_0 0.01 #define SMOOTH_CO_1 0.99 /* Audio I/O ports */ #define IR_PORT_INPUT_L 0 #define IR_PORT_INPUT_R 1 #define IR_PORT_OUTPUT_L 2 #define IR_PORT_OUTPUT_R 3 /* Control ports */ #define IR_PORT_REVERSE 4 /* Reverse impulse response [on/off] */ #define IR_PORT_PREDELAY 5 /* Predelay [ms] */ #define IR_PORT_ATTACK 6 /* Attack [%] */ #define IR_PORT_ATTACKTIME 7 /* Attack time [ms] */ #define IR_PORT_ENVELOPE 8 /* Envelope [%] */ #define IR_PORT_LENGTH 9 /* Length [%] */ #define IR_PORT_STRETCH 10 /* Stretch [%] */ #define IR_PORT_STEREO_IN 11 /* Stereo width In [%] */ #define IR_PORT_STEREO_IR 12 /* Stereo width IR [%] */ #define IR_PORT_AGC_SW 13 /* Autogain switch [on/off] */ #define IR_PORT_DRY_SW 14 /* Dry switch [on/off] */ #define IR_PORT_DRY_GAIN 15 /* Dry gain [dB] */ #define IR_PORT_WET_SW 16 /* Wet switch [on/off] */ #define IR_PORT_WET_GAIN 17 /* Wet gain [dB] */ /* Save/Restore ports */ #define IR_PORT_FHASH_0 18 #define IR_PORT_FHASH_1 19 #define IR_PORT_FHASH_2 20 /* Meter ports (output) */ #define IR_PORT_METER_DRY_L 21 #define IR_PORT_METER_DRY_R 22 #define IR_PORT_METER_WET_L 23 #define IR_PORT_METER_WET_R 24 /* Latency reporting port */ #define IR_PORT_LATENCY 25 #define IR_N_PORTS 26 #define IR_DEFAULT_JACK_BUFLEN 1024 #define IR_MAXIMUM_JACK_BUFLEN 16384 typedef struct _ir { /* Audio I/O ports */ const float * in_L; const float * in_R; float * out_L; float * out_R; unsigned int bufconv_pos; float drybuf_L[IR_MAXIMUM_JACK_BUFLEN]; float drybuf_R[IR_MAXIMUM_JACK_BUFLEN]; /* convproc's internal buffer acts as the wetbuf */ /* Control ports */ float * port_reverse; float * port_predelay; float * port_attack; float * port_attacktime; float * port_envelope; float * port_length; float * port_stretch; float * port_stereo_in; float * port_stereo_ir; float * port_agc_sw; float * port_dry_sw; float * port_dry_gain; float * port_wet_sw; float * port_wet_gain; float * port_fhash_0; float * port_fhash_1; float * port_fhash_2; float * port_meter_dry_L; float * port_meter_dry_R; float * port_meter_wet_L; float * port_meter_wet_R; float * port_latency; /* Thread that loads and computes configurations */ GThread * conf_thread; int conf_thread_exit; int first_conf_done; /* Run notify flag */ int run; /* plugin sets this to 1 in each run(), UI or conf_thread may set it to 0 and watch */ /* Configuration state */ char * source_path; /* path of IR audio file */ SNDFILE * Finp; SF_INFO Sinp; uint32_t source_samplerate; int nchan; /* valid values are 1, 2, 4 */ int source_nfram; /* length of source_samples */ float * source_samples; /* IR audio file loads into this array INTERLEAVED */ int ir_nfram; /* length of resampled & ir_samples */ float * resampled_samples; /* Resampled IR samples INTERLEAVED */ float ** ir_samples; /* de-interleaved, processed samples loaded into Conv */ float autogain; /* dB */ float autogain_new; /* dB */ float src_progress; SRC_STATE * src_state; SRC_DATA src_data; int src_in_frames; int src_out_frames; /* Processing state */ float width; /* internal */ float dry_gain; /* (smoothed) */ float wet_gain; /* parameters */ double sample_rate; uint32_t maxsize; /* maximum size of IR supported by Convproc instance */ uint32_t block_length; /* defaults to IR_DEFAULT_JACK_BUFLEN, but may grow till IR_MAXIMUM_JACK_BUFLEN */ Convproc * conv_0; /* zita-convolver engine class instances */ Convproc * conv_1; int conv_in_use; int conv_req_to_use; int resample_pending; int reinit_pending; int reinit_running; /* These reference IR lib globals so GUI has access to them */ GKeyFile * keyfile; GtkListStore * store_bookmarks; /* Function pointers for GUI */ int (*load_sndfile)(struct _ir *); int (*resample_init)(struct _ir *); int (*resample_do)(struct _ir *); void (*resample_cleanup)(struct _ir *); void (*prepare_convdata)(struct _ir *); void (*init_conv)(struct _ir *); } IR; #endif /* _IR_H */ ir.lv2-1.3.2/ir_modeind.h0000644000175000017500000000305511636627117013572 0ustar miramira/* Copyright (C) 2011 Tom Szilagyi 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 _IR_MODEIND_H #define _IR_MODEIND_H #include G_BEGIN_DECLS #define IR_MODEIND_TYPE (ir_modeind_get_type()) #define IR_MODEIND(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), IR_MODEIND_TYPE, IRModeInd)) #define IR_MODEIND_CLASS(obj) (G_TYPE_CHECK_CLASS_CAST((obj), IR_MODEIND, IRModeIndClass)) #define IR_MODEIND_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS ((obj), IR_MODEIND_TYPE, IRWavedisplayClass)) typedef struct _IRModeInd IRModeInd; typedef struct _IRModeIndClass IRModeIndClass; struct _IRModeInd { GtkDrawingArea parent; /* < private > */ }; struct _IRModeIndClass { GtkDrawingAreaClass parent_class; }; GType ir_modeind_get_type(); GtkWidget * ir_modeind_new(void); void ir_modeind_redraw(IRModeInd * w); void ir_modeind_set_channels(IRModeInd * w, int channels); G_END_DECLS #endif /* _IR_MODEIND_H */ ir.lv2-1.3.2/0000755000175000017500000000000012030567575011305 5ustar miramira