summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien Zammit <damien@zamaudio.com>2017-12-02 00:14:45 +1100
committerDamien Zammit <damien@zamaudio.com>2017-12-02 00:14:45 +1100
commit331e65d8992d86800f1689137a6bd414440ec5ec (patch)
tree4f4350b463ddb971f0127367f4877b890f65352e
parent89635316558a1128bc1a0b399ad5cd7817e74553 (diff)
ZamHeadX2: Use zita-convolver instead of poor man's convolver
-rw-r--r--plugins/ZamHeadX2/DistrhoPluginInfo.h2
-rw-r--r--plugins/ZamHeadX2/Makefile2
-rw-r--r--plugins/ZamHeadX2/ZamHeadX2Plugin.cpp441
-rw-r--r--plugins/ZamHeadX2/ZamHeadX2Plugin.hpp16
-rw-r--r--plugins/ZamHeadX2/ZamHeadX2UI.cpp4
-rw-r--r--plugins/ZamHeadX2/ZamHeadX2UI.hpp2
-rw-r--r--plugins/ZamHeadX2/convolution.cpp811
-rw-r--r--plugins/ZamHeadX2/convolution.hpp78
8 files changed, 993 insertions, 363 deletions
diff --git a/plugins/ZamHeadX2/DistrhoPluginInfo.h b/plugins/ZamHeadX2/DistrhoPluginInfo.h
index e66a7d4..45055c2 100644
--- a/plugins/ZamHeadX2/DistrhoPluginInfo.h
+++ b/plugins/ZamHeadX2/DistrhoPluginInfo.h
@@ -29,7 +29,7 @@
#define DISTRHO_PLUGIN_WANT_LATENCY 0
#define DISTRHO_PLUGIN_WANT_PROGRAMS 1
-#define DISTRHO_PLUGIN_WANT_STATE 0
+#define DISTRHO_PLUGIN_WANT_STATE 1
#define DISTRHO_PLUGIN_WANT_TIMEPOS 0
#define DISTRHO_PLUGIN_IS_RT_SAFE 1
diff --git a/plugins/ZamHeadX2/Makefile b/plugins/ZamHeadX2/Makefile
index 4e919ab..24bfe1d 100644
--- a/plugins/ZamHeadX2/Makefile
+++ b/plugins/ZamHeadX2/Makefile
@@ -13,6 +13,8 @@ NAME = ZamHeadX2
# Files to build
OBJS_DSP = \
+ ../../lib/zita-convolver-3.1.0/zita-convolver.cpp.o \
+ convolution.cpp.o \
ZamHeadX2Plugin.cpp.o
OBJS_UI = \
diff --git a/plugins/ZamHeadX2/ZamHeadX2Plugin.cpp b/plugins/ZamHeadX2/ZamHeadX2Plugin.cpp
index 17ac569..7e21f26 100644
--- a/plugins/ZamHeadX2/ZamHeadX2Plugin.cpp
+++ b/plugins/ZamHeadX2/ZamHeadX2Plugin.cpp
@@ -1,5 +1,5 @@
/*
- * ZamHeadX2 Stereo widener
+ * ZamHeadX2 HRTF simulator
* Copyright (C) 2014 Damien Zammit <damien@zamaudio.com>
*
* This program is free software; you can redistribute it and/or
@@ -16,20 +16,50 @@
*/
#include "ZamHeadX2Plugin.hpp"
+#include <assert.h>
START_NAMESPACE_DISTRHO
// -----------------------------------------------------------------------
ZamHeadX2Plugin::ZamHeadX2Plugin()
- : Plugin(paramCount, 1, 0)
+ : Plugin(paramCount, 1, 1)
{
+ swap = 0;
+ clv[swap] = new LV2convolv();
+ clv[swap]->clv_configure("convolution.ir.preset", "0", "0");
+ clv[swap]->clv_initialize(getSampleRate(), 2, 2, getBufferSize());
+
+ clv[1] = new LV2convolv();
+ clv[1]->clv_configure("convolution.ir.preset", "0", "0");
+ clv[1]->clv_initialize(getSampleRate(), 2, 2, getBufferSize());
+
+ // Extra buffer for outputs since plugin can work in place
+ tmpouts = (float **)malloc (2 * sizeof(float*));
+ tmpouts[0] = (float *)calloc (1, 8192 * sizeof(float));
+ tmpouts[1] = (float *)calloc (1, 8192 * sizeof(float));
+
+ // Extra buffer for inputs since convolve might clobber ins
+ tmpins = (float **)malloc (2 * sizeof(float*));
+ tmpins[0] = (float *)calloc (1, 8192 * sizeof(float));
+ tmpins[1] = (float *)calloc (1, 8192 * sizeof(float));
+
// set default values
loadProgram(0);
}
ZamHeadX2Plugin::~ZamHeadX2Plugin()
{
+ free(tmpouts[0]);
+ free(tmpouts[1]);
+ free(tmpouts);
+
+ free(tmpins[0]);
+ free(tmpins[1]);
+ free(tmpins);
+
+ delete clv[0];
+ delete clv[1];
}
// -----------------------------------------------------------------------
@@ -40,7 +70,6 @@ void ZamHeadX2Plugin::initParameter(uint32_t index, Parameter& parameter)
switch (index)
{
case paramAzimuth:
- parameter.hints = kParameterIsAutomable;
parameter.name = "Azimuth";
parameter.symbol = "az";
parameter.unit = " ";
@@ -49,7 +78,6 @@ void ZamHeadX2Plugin::initParameter(uint32_t index, Parameter& parameter)
parameter.ranges.max = 270.0f;
break;
case paramElevation:
- parameter.hints = kParameterIsAutomable;
parameter.name = "Elevation";
parameter.symbol = "elev";
parameter.unit = " ";
@@ -118,9 +146,11 @@ void ZamHeadX2Plugin::setParameterValue(uint32_t index, float value)
{
case paramAzimuth:
azimuth = value;
+ setState("reload", "");
break;
case paramElevation:
elevation = value;
+ setState("reload", "");
break;
case paramWidth:
width = value;
@@ -131,379 +161,76 @@ void ZamHeadX2Plugin::setParameterValue(uint32_t index, float value)
// -----------------------------------------------------------------------
// Process
-static const float fir_left[50][25][200] = {
- {
- #include "l1"
- },
- {
- #include "l2"
- },
- {
- #include "l3"
- },
- {
- #include "l4"
- },
- {
- #include "l5"
- },
- {
- #include "l6"
- },
- {
- #include "l7"
- },
- {
- #include "l8"
- },
- {
- #include "l9"
- },
- {
- #include "l10"
- },
- {
- #include "l11"
- },
- {
- #include "l12"
- },
- {
- #include "l13"
- },
- {
- #include "l14"
- },
- {
- #include "l15"
- },
- {
- #include "l16"
- },
- {
- #include "l17"
- },
- {
- #include "l18"
- },
- {
- #include "l19"
- },
- {
- #include "l20"
- },
- {
- #include "l21"
- },
- {
- #include "l22"
- },
- {
- #include "l23"
- },
- {
- #include "l24"
- },
- {
- #include "l25"
- },
- {
- #include "l26"
- },
- {
- #include "l27"
- },
- {
- #include "l28"
- },
- {
- #include "l29"
- },
- {
- #include "l30"
- },
- {
- #include "l31"
- },
- {
- #include "l32"
- },
- {
- #include "l33"
- },
- {
- #include "l34"
- },
- {
- #include "l35"
- },
- {
- #include "l36"
- },
- {
- #include "l37"
- },
- {
- #include "l38"
- },
- {
- #include "l39"
- },
- {
- #include "l40"
- },
- {
- #include "l41"
- },
- {
- #include "l42"
- },
- {
- #include "l43"
- },
- {
- #include "l44"
- },
- {
- #include "l45"
- },
- {
- #include "l46"
- },
- {
- #include "l47"
- },
- {
- #include "l48"
- },
- {
- #include "l49"
- },
- {
- #include "l50"
- }
-};
-
-static const float fir_right[50][25][200] = {
- {
- #include "r1"
- },
- {
- #include "r2"
- },
- {
- #include "r3"
- },
- {
- #include "r4"
- },
- {
- #include "r5"
- },
- {
- #include "r6"
- },
- {
- #include "r7"
- },
- {
- #include "r8"
- },
- {
- #include "r9"
- },
- {
- #include "r10"
- },
- {
- #include "r11"
- },
- {
- #include "r12"
- },
- {
- #include "r13"
- },
- {
- #include "r14"
- },
- {
- #include "r15"
- },
- {
- #include "r16"
- },
- {
- #include "r17"
- },
- {
- #include "r18"
- },
- {
- #include "r19"
- },
- {
- #include "r20"
- },
- {
- #include "r21"
- },
- {
- #include "r22"
- },
- {
- #include "r23"
- },
- {
- #include "r24"
- },
- {
- #include "r25"
- },
- {
- #include "r26"
- },
- {
- #include "r27"
- },
- {
- #include "r28"
- },
- {
- #include "r29"
- },
- {
- #include "r30"
- },
- {
- #include "r31"
- },
- {
- #include "r32"
- },
- {
- #include "r33"
- },
- {
- #include "r34"
- },
- {
- #include "r35"
- },
- {
- #include "r36"
- },
- {
- #include "r37"
- },
- {
- #include "r38"
- },
- {
- #include "r39"
- },
- {
- #include "r40"
- },
- {
- #include "r41"
- },
- {
- #include "r42"
- },
- {
- #include "r43"
- },
- {
- #include "r44"
- },
- {
- #include "r45"
- },
- {
- #include "r46"
- },
- {
- #include "r47"
- },
- {
- #include "r48"
- },
- {
- #include "r49"
- },
- {
- #include "r50"
- }
-};
-
void ZamHeadX2Plugin::activate()
{
- int i;
- for (i = 0; i < 6; i++) {
- pos[i] = 0;
- }
- for (i = 0; i < 4096+200; i++) {
- inbuf[0][i] = 0.f;
- inbuf[1][i] = 0.f;
- outbuf[0][i] = 0.f;
- outbuf[1][i] = 0.f;
- }
+ setState("reload", "");
+}
+String ZamHeadX2Plugin::getState(const char*) const
+{
+ return String("");
}
-void ZamHeadX2Plugin::pushsample(float* buf, float val, int i, uint32_t maxframes)
+void ZamHeadX2Plugin::initState(unsigned int index, String& key, String& defval)
{
- buf[pos[i]] = val;
- pos[i]--;
- if (pos[i] < 0) {
- pos[i] = (int)maxframes;
+ if (index == 0) {
+ key = String("reload");
}
+ defval = String("");
}
-float ZamHeadX2Plugin::getsample(float *buf, int i, uint32_t maxframes)
+void ZamHeadX2Plugin::setState(const char* key, const char*)
{
- float val = buf[pos[i]];
- pos[i]++;
- if (pos[i] >= (int)maxframes) {
- pos[i] = 0;
+ uint8_t other;
+ char elev[4] = { 0 };
+ char azim[4] = { 0 };
+ int az, el;
+
+ if (strcmp(key, "reload") == 0) {
+ el = (int)((elevation + 45.) * 24. / 135.);
+ if (el >= 24) el = 24;
+ if (el < 0) el = 0;
+ az = (int)((azimuth + 90.) * 49. / 360.);
+ if (az >= 49) az = 49;
+ if (az < 0) az = 0;
+ if (az > 24) az = 49 - az;
+ snprintf(elev, 3, "%d", el);
+ snprintf(azim, 3, "%d", az);
+ if ((az != azold) || (el != elold)) {
+ other = !active;
+ clv[other]->clv_release();
+ clv[other]->clv_configure("convolution.ir.preset", elev, azim);
+ clv[other]->clv_initialize(getSampleRate(), 2, 2, getBufferSize());
+ swap = other;
+ }
+ azold = az;
+ elold = el;
}
- return val;
}
+
void ZamHeadX2Plugin::run(const float** inputs, float** outputs, uint32_t frames)
{
- uint32_t i;
- int az, el;
-
- el = (int)((elevation + 45.) / 135. * 24.);
- if (el >= 24) el = 24;
- if (el < 0) el = 0;
- az = (int)((azimuth + 90.) / 360. * 49.);
- if (az >= 49) az = 49;
- if (az < 0) az = 0;
- if (az > 24) az = 49 - az;
-
- float ltmp = 0.f;
- float rtmp = 0.f;
- int k;
float m, s;
+ uint32_t i;
+ int nprocessed;
+ active = swap;
+ assert(frames < 8192);
for (i = 0; i < frames; i++) {
m = (inputs[0][i] + inputs[1][i]) * 0.5;
s = (inputs[0][i] - inputs[1][i]) * 0.5 * width;
- pushsample(&inbuf[0][0], m - s, 0, 200);
- pushsample(&inbuf[1][0], m + s, 1, 200);
- ltmp = 0.f;
- rtmp = 0.f;
- for (k = 0; k < 200; k++) {
- ltmp += getsample(&inbuf[0][0], 0, 200) *
- fir_left[el][az][(200-k+200)%200];
- rtmp += getsample(&inbuf[1][0], 1, 200) *
- fir_right[el][az][(200-k+200)%200];
- }
- outputs[0][i] = ltmp;
- outputs[1][i] = rtmp;
+ tmpins[0][i] = m - s;
+ tmpins[1][i] = m + s;
+ }
+
+ nprocessed = clv[active]->clv_convolve(tmpins, tmpouts, 2, 2, frames, from_dB(6.0));
+ if (nprocessed <= 0) {
+ memcpy(outputs[0], inputs[0], frames * sizeof(float));
+ memcpy(outputs[1], inputs[1], frames * sizeof(float));
+ } else {
+ memcpy(outputs[0], tmpouts[0], frames * sizeof(float));
+ memcpy(outputs[1], tmpouts[1], frames * sizeof(float));
}
}
diff --git a/plugins/ZamHeadX2/ZamHeadX2Plugin.hpp b/plugins/ZamHeadX2/ZamHeadX2Plugin.hpp
index 48b419a..b4655b4 100644
--- a/plugins/ZamHeadX2/ZamHeadX2Plugin.hpp
+++ b/plugins/ZamHeadX2/ZamHeadX2Plugin.hpp
@@ -1,5 +1,5 @@
/*
- * ZamHeadX2 stereo widener
+ * ZamHeadX2 HRTF simulator
* Copyright (C) 2014 Damien Zammit <damien@zamaudio.com>
*
* This program is free software; you can redistribute it and/or
@@ -17,6 +17,7 @@
#define ZAMWIDTHX2PLUGIN_HPP_INCLUDED
#include "DistrhoPlugin.hpp"
+#include "convolution.hpp"
START_NAMESPACE_DISTRHO
@@ -88,6 +89,10 @@ protected:
void setParameterValue(uint32_t index, float value) override;
void loadProgram(uint32_t index);
+ String getState(const char*) const override;
+ void initState(unsigned int index, String& key, String& defval) override;
+ void setState(const char* key, const char*) override;
+
// -------------------------------------------------------------------
// Process
@@ -117,10 +122,11 @@ protected:
private:
float elevation, azimuth, width;
- float inbuf[2][4096+200];
- float outbuf[2][4096+200];
- int pos[6];
-
+ int azold, elold;
+ int swap, active;
+ float **tmpins;
+ float **tmpouts;
+ LV2convolv *clv[2];
};
// -----------------------------------------------------------------------
diff --git a/plugins/ZamHeadX2/ZamHeadX2UI.cpp b/plugins/ZamHeadX2/ZamHeadX2UI.cpp
index 162c674..bebb6b0 100644
--- a/plugins/ZamHeadX2/ZamHeadX2UI.cpp
+++ b/plugins/ZamHeadX2/ZamHeadX2UI.cpp
@@ -121,6 +121,10 @@ void ZamHeadX2UI::onDisplay()
fImgBackground.draw();
}
+void ZamHeadX2UI::stateChanged(const char*, const char*)
+{
+}
+
// -----------------------------------------------------------------------
UI* createUI()
diff --git a/plugins/ZamHeadX2/ZamHeadX2UI.hpp b/plugins/ZamHeadX2/ZamHeadX2UI.hpp
index b390b1f..bf562c6 100644
--- a/plugins/ZamHeadX2/ZamHeadX2UI.hpp
+++ b/plugins/ZamHeadX2/ZamHeadX2UI.hpp
@@ -53,6 +53,8 @@ protected:
void onDisplay() override;
+ void stateChanged(const char*, const char*) override;
+
private:
Image fImgBackground;
ScopedPointer<ZamKnob> fKnobAzimuth, fKnobElevation, fKnobWidth;
diff --git a/plugins/ZamHeadX2/convolution.cpp b/plugins/ZamHeadX2/convolution.cpp
new file mode 100644
index 0000000..f4028f0
--- /dev/null
+++ b/plugins/ZamHeadX2/convolution.cpp
@@ -0,0 +1,811 @@
+/* LV2 convolution engine
+ *
+ * Copyright (C) 2012 Robin Gareus <robin@gareus.org>
+ *
+ * Modified for C++:
+ * Copyright (C) 2017 Damien Zammit <damien@zamaudio.com>
+ *
+ * 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, 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.
+ */
+
+/* Usage information:
+ *
+ * Non-realtime, initialization:
+ * 1) clv_alloc();
+ * 2) clv_configure(); // can be called multiple times
+ * 3) clv_initialize(); // fix settings
+ *
+ * Realtime process
+ * 4) convolve();
+ *
+ * Non-rt, cleanup
+ * 5A) clv_release(); // -> goto (2) or (3)
+ * OR
+ * 5B) clv_free(); // -> The End
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <math.h>
+#include <string.h>
+#include <stdint.h>
+#include <pthread.h>
+#include <assert.h>
+
+#include "../../lib/zita-convolver-3.1.0/zita-convolver.h"
+#include <samplerate.h>
+#include "convolution.hpp"
+
+#if ZITA_CONVOLVER_MAJOR_VERSION != 3
+# error "This programs requires zita-convolver 3.x.x"
+#endif
+
+#ifndef SRC_QUALITY
+# define SRC_QUALITY SRC_SINC_BEST_QUALITY
+#endif
+
+static pthread_mutex_t fftw_planner_lock = PTHREAD_MUTEX_INITIALIZER;
+
+#define PRESETS_SAMPLERATE 48000
+#define PRESETS_CH 2
+
+static const float fir_left[50][25][200] = {
+ {
+ #include "l1"
+ },
+ {
+ #include "l2"
+ },
+ {
+ #include "l3"
+ },
+ {
+ #include "l4"
+ },
+ {
+ #include "l5"
+ },
+ {
+ #include "l6"
+ },
+ {
+ #include "l7"
+ },
+ {
+ #include "l8"
+ },
+ {
+ #include "l9"
+ },
+ {
+ #include "l10"
+ },
+ {
+ #include "l11"
+ },
+ {
+ #include "l12"
+ },
+ {
+ #include "l13"
+ },
+ {
+ #include "l14"
+ },
+ {
+ #include "l15"
+ },
+ {
+ #include "l16"
+ },
+ {
+ #include "l17"
+ },
+ {
+ #include "l18"
+ },
+ {
+ #include "l19"
+ },
+ {
+ #include "l20"
+ },
+ {
+ #include "l21"
+ },
+ {
+ #include "l22"
+ },
+ {
+ #include "l23"
+ },
+ {
+ #include "l24"
+ },
+ {
+ #include "l25"
+ },
+ {
+ #include "l26"
+ },
+ {
+ #include "l27"
+ },
+ {
+ #include "l28"
+ },
+ {
+ #include "l29"
+ },
+ {
+ #include "l30"
+ },
+ {
+ #include "l31"
+ },
+ {
+ #include "l32"
+ },
+ {
+ #include "l33"
+ },
+ {
+ #include "l34"
+ },
+ {
+ #include "l35"
+ },
+ {
+ #include "l36"
+ },
+ {
+ #include "l37"
+ },
+ {
+ #include "l38"
+ },
+ {
+ #include "l39"
+ },
+ {
+ #include "l40"
+ },
+ {
+ #include "l41"
+ },
+ {
+ #include "l42"
+ },
+ {
+ #include "l43"
+ },
+ {
+ #include "l44"
+ },
+ {
+ #include "l45"
+ },
+ {
+ #include "l46"
+ },
+ {
+ #include "l47"
+ },
+ {
+ #include "l48"
+ },
+ {
+ #include "l49"
+ },
+ {
+ #include "l50"
+ }
+};
+
+static const float fir_right[50][25][200] = {
+ {
+ #include "r1"
+ },
+ {
+ #include "r2"
+ },
+ {
+ #include "r3"
+ },
+ {
+ #include "r4"
+ },
+ {
+ #include "r5"
+ },
+ {
+ #include "r6"
+ },
+ {
+ #include "r7"
+ },
+ {
+ #include "r8"
+ },
+ {
+ #include "r9"
+ },
+ {
+ #include "r10"
+ },
+ {
+ #include "r11"
+ },
+ {
+ #include "r12"
+ },
+ {
+ #include "r13"
+ },
+ {
+ #include "r14"
+ },
+ {
+ #include "r15"
+ },
+ {
+ #include "r16"
+ },
+ {
+ #include "r17"
+ },
+ {
+ #include "r18"
+ },
+ {
+ #include "r19"
+ },
+ {
+ #include "r20"
+ },
+ {
+ #include "r21"
+ },
+ {
+ #include "r22"
+ },
+ {
+ #include "r23"
+ },
+ {
+ #include "r24"
+ },
+ {
+ #include "r25"
+ },
+ {
+ #include "r26"
+ },
+ {
+ #include "r27"
+ },
+ {
+ #include "r28"
+ },
+ {
+ #include "r29"
+ },
+ {
+ #include "r30"
+ },
+ {
+ #include "r31"
+ },
+ {
+ #include "r32"
+ },
+ {
+ #include "r33"
+ },
+ {
+ #include "r34"
+ },
+ {
+ #include "r35"
+ },
+ {
+ #include "r36"
+ },
+ {
+ #include "r37"
+ },
+ {
+ #include "r38"
+ },
+ {
+ #include "r39"
+ },
+ {
+ #include "r40"
+ },
+ {
+ #include "r41"
+ },
+ {
+ #include "r42"
+ },
+ {
+ #include "r43"
+ },
+ {
+ #include "r44"
+ },
+ {
+ #include "r45"
+ },
+ {
+ #include "r46"
+ },
+ {
+ #include "r47"
+ },
+ {
+ #include "r48"
+ },
+ {
+ #include "r49"
+ },
+ {
+ #include "r50"
+ }
+};
+
+int LV2convolv::resample_read_presets (const float *in, unsigned int in_frames, const int sample_rate, float **buf, unsigned int *n_ch, unsigned int *n_sp)
+{
+ float resample_ratio = 1.0;
+
+ if (n_ch) *n_ch = PRESETS_CH;
+ if (n_sp) *n_sp = in_frames;
+
+ if (sample_rate != PRESETS_SAMPLERATE) {
+ fprintf(stderr, "convoLV2: samplerate mismatch preset:%d host:%d\n", PRESETS_SAMPLERATE, sample_rate);
+ resample_ratio = (float) sample_rate / (float) PRESETS_SAMPLERATE;
+ }
+
+ if (buf) {
+ const size_t frames_in = PRESETS_CH * in_frames;
+ const size_t frames_out = PRESETS_CH * ceil(in_frames * resample_ratio);
+ *buf = (float*) malloc(frames_out*sizeof(float));
+
+ float *iin;
+ if (resample_ratio != 1.0) {
+ iin = (float*)malloc(frames_in * sizeof(float));
+ memcpy(iin, in, frames_in * sizeof(float));
+ } else {
+ memcpy(*buf, in, frames_in * sizeof(float));
+ }
+
+ if (!*buf) {
+ fprintf (stderr, "convoLV2: memory allocation failed for IR audio-file buffer.\n");
+ return (-2);
+ }
+
+ if (resample_ratio != 1.0) {
+ VERBOSE_printf("convoLV2: resampling IR %ld -> %ld [frames * channels].\n",
+ (long int) frames_in,
+ (long int) frames_out);
+ SRC_STATE* src_state = src_new(SRC_QUALITY, PRESETS_CH, NULL);
+ SRC_DATA src_data;
+
+ src_data.input_frames = in_frames;
+ src_data.output_frames = in_frames * resample_ratio;
+ src_data.end_of_input = 1;
+ src_data.src_ratio = resample_ratio;
+ src_data.input_frames_used = 0;
+ src_data.output_frames_gen = 0;
+ src_data.data_in = iin;
+ src_data.data_out = *buf;
+ src_process(src_state, &src_data);
+ VERBOSE_printf("convoLV2: resampled IR %ld -> %ld [frames * channels].\n",
+ src_data.input_frames_used * PRESETS_CH,
+ src_data.output_frames_gen * PRESETS_CH);
+ if (n_sp) *n_sp = (unsigned int) src_data.output_frames_gen;
+ free(iin);
+ }
+ }
+ return (0);
+}
+
+void LV2convolv::clv_alloc (void) {
+ int i;
+ convproc = NULL;
+ for (i = 0; i < MAX_CHANNEL_MAPS; ++i) {
+ ir_chan[i] = i + 1;
+ chn_inp[i] = i + 1;
+ chn_out[i] = i + 1;
+ ir_delay[i] = 0;
+ ir_gain[i] = 0.5f;
+ }
+ ir_fn = NULL;
+ ir_preset = -1;
+ ir_presetx = -1;
+ ir_presety = -1;
+ density = 0.f;
+ size = 0x00100000;
+}
+
+void LV2convolv::clv_release (void) {
+ if (convproc) {
+ convproc->stop_process ();
+ delete convproc;
+ }
+ convproc = NULL;
+}
+
+void LV2convolv::clv_clone_settings(LV2convolv *clv_new) {
+ memcpy (clv_new, this, sizeof(LV2convolv));
+ clv_new->convproc = NULL;
+ if (ir_fn) {
+ clv_new->ir_fn = strdup (ir_fn);
+ }
+}
+
+void LV2convolv::clv_free (void) {
+ clv_release ();
+ free (ir_fn);
+}
+
+int LV2convolv::clv_configure (const char *key, const char *elev, const char* azim) {
+ if (strcasecmp (key, "convolution.ir.preset") == 0) {
+ ir_presetx = atoi(elev);
+ ir_presety = atoi(azim);
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+int LV2convolv::clv_configure (const char *key, const char *value) {
+ int n;
+ if (strcasecmp (key, "convolution.ir.file") == 0) {
+ free(ir_fn);
+ ir_fn = strdup(value);
+ } else if (strcasecmp (key, "convolution.ir.preset") == 0) {
+ ir_preset = atoi(value);
+ } else if (!strncasecmp (key, "convolution.out.source.", 23)) {
+ if (sscanf (key, "convolution.source.%d", &n) == 1) {
+ if ((0 < n) && (n <= MAX_CHANNEL_MAPS))
+ chn_inp[n] = atoi(value);
+ }
+ } else if (!strncasecmp (key, "convolution.out.source.", 23)) {
+ if (sscanf (key, "convolution.output.%d", &n) == 1) {
+ if ((0 <= n) && (n < MAX_CHANNEL_MAPS))
+ chn_out[n] = atoi(value);
+ }
+ } else if (!strncasecmp (key, "convolution.ir.channel.", 23)) {
+ if (sscanf (key, "convolution.ir.channel.%d", &n) == 1) {
+ if ((0 <= n) && (n < MAX_CHANNEL_MAPS))
+ ir_chan[n] = atoi(value);
+ }
+ } else if (!strncasecmp (key, "convolution.ir.gain.", 20)) {
+ if (sscanf (key, "convolution.ir.gain.%d", &n) == 1) {
+ if ((0 <= n) && (n < MAX_CHANNEL_MAPS))
+ ir_gain[n] = atof(value);
+ }
+ } else if (!strncasecmp (key, "convolution.ir.delay.", 21)) {
+ if (sscanf (key, "convolution.ir.delay.%d", &n) == 1) {
+ if ((0 <= n) && (n < MAX_CHANNEL_MAPS))
+ ir_delay[n] = atoi(value);
+ }
+ } else if (strcasecmp (key, "convolution.maxsize") == 0) {
+ size = atoi(value);
+ if (size > 0x00400000) {
+ size = 0x00400000;
+ }
+ if (size < 0x00001000) {
+ size = 0x00001000;
+ }
+ } else {
+ return 0;
+ }
+ return 1; // OK
+}
+
+int LV2convolv::clv_initialize (
+ const unsigned int sample_rate,
+ const unsigned int in_channel_cnt,
+ const unsigned int out_channel_cnt,
+ const unsigned int buffersize)
+{
+ unsigned int c;
+ const unsigned int n_elem = in_channel_cnt * out_channel_cnt;
+
+ /* zita-conv settings */
+ const unsigned int options = 0;
+
+ /* IR file */
+ unsigned int n_chan = 0;
+ unsigned int n_frames = 0;
+ unsigned int max_size = 0;
+
+ float *p = NULL; /* temp. IR file buffer */
+ float *gb = NULL; /* temp. gain-scaled IR file buffer */
+
+ fragment_size = buffersize;
+
+ if (zita_convolver_major_version () != ZITA_CONVOLVER_MAJOR_VERSION) {
+ fprintf (stderr, "convoLV2: Zita-convolver version does not match.\n");
+ return -1;
+ }
+
+ if (convproc) {
+ fprintf (stderr, "convoLV2: already initialized.\n");
+ return (-1);
+ }
+
+ if (!ir_fn && (ir_preset < 0) && (ir_presetx < 0) && (ir_presety < 0)) {
+ fprintf (stderr, "convoLV2: No IR file was configured.\n");
+ return -1;
+ }
+
+ pthread_mutex_lock(&fftw_planner_lock);
+
+ convproc = new Convproc();
+ convproc->set_options (options);
+ convproc->set_density (density);
+
+ float fir_coeffs_lr[400] = { 0 };
+ for (int i = 0; i < 200; i++) {
+ fir_coeffs_lr[2 * i] = fir_left[ir_presetx][ir_presety][i];
+ fir_coeffs_lr[2 * i + 1] = fir_right[ir_presetx][ir_presety][i];
+ }
+ const float *impulse = &fir_coeffs_lr[0];
+ unsigned int impulse_frames = 200;
+
+
+ if (resample_read_presets (impulse, impulse_frames, sample_rate, &p, &n_chan, &n_frames)) {
+ fprintf(stderr, "convoLV2: failed to read IR preset.\n");
+ goto errout;
+ }
+
+ if (n_frames == 0 || n_chan == 0) {
+ fprintf(stderr, "convoLV2: invalid IR file.\n");
+ goto errout;
+ }
+
+ for (c = 0; c < MAX_CHANNEL_MAPS; c++) {
+ // TODO only relevant channels
+ if (ir_delay[c] > max_size) {
+ max_size = ir_delay[c];
+ }
+ }
+
+ max_size += n_frames;
+
+ if (max_size > size) {
+ max_size = size;
+ }
+
+ VERBOSE_printf("convoLV2: max-convolution length %d samples (limit %d), period: %d samples\n", max_size, size, buffersize);
+
+
+ if (convproc->configure (
+ /*in*/ in_channel_cnt,
+ /*out*/ out_channel_cnt,
+ /*max-convolution length */ max_size,
+ /*quantum*/ buffersize,
+ /*min-part*/ buffersize,
+ /*max-part*/ buffersize
+ )) {
+ fprintf (stderr, "convoLV2: Cannot initialize convolution engine.\n");
+ goto errout;
+ }
+
+ gb = (float*) malloc (n_frames * sizeof(float));
+ if (!gb) {
+ fprintf (stderr, "convoLV2: memory allocation failed for convolution buffer.\n");
+ goto errout;
+ }
+
+ VERBOSE_printf("convoLV2: Proc: in: %d, out: %d || IR-file: %d chn, %d samples\n",
+ in_channel_cnt, out_channel_cnt, n_chan, n_frames);
+
+ // TODO use pre-configured channel-map (from state), IFF set and valid for the current file
+
+ // reset channel map
+ for (c = 0; c < MAX_CHANNEL_MAPS; ++c) {
+ ir_chan[c] = 0;
+ chn_inp[c] = 0;
+ chn_out[c] = 0;
+ }
+
+ // follow channel map conventions
+ if (n_elem == n_chan) {
+ // exact match: for every input-channel, iterate over all outputs
+ // eg. 1: L -> L , 2: L -> R, 3: R -> L, 4: R -> R
+ for (c = 0; c < n_chan && c < MAX_CHANNEL_MAPS; ++c) {
+ ir_chan[c] = 1 + c;
+ chn_inp[c] = 1 + ((c / out_channel_cnt) % in_channel_cnt);
+ chn_out[c] = 1 + (c % out_channel_cnt);
+ }
+ }
+ else if (n_elem > n_chan) {
+ VERBOSE_printf("convoLV2: IR file has too few channels for given processor config.\n");
+ // missing some channels, first assign in -> out, then x-over
+ // eg. 1: L -> L , 2: R -> R, 3: L -> R, 4: R -> L
+ // this allows to e.g load a 2-channel (stereo) IR into a
+ // 2x2 true-stereo effect instance
+ for (c = 0; c < n_chan && c < MAX_CHANNEL_MAPS; ++c) {
+ ir_chan[c] = 1 + c;
+ chn_inp[c] = 1 + (c % in_channel_cnt);
+ chn_out[c] = 1 + (((c + c / in_channel_cnt) % in_channel_cnt) % out_channel_cnt);
+ }
+ // assign mono input to 1: L -> L , 2: R -> R,
+ for (;n_chan == 1 && c < 2; ++c) {
+ ir_chan[c] = 1;
+ chn_inp[c] = 1 + (c % in_channel_cnt);
+ chn_out[c] = 1 + (c % out_channel_cnt);
+ }
+ }
+ else {
+ assert (n_elem < n_chan);
+ VERBOSE_printf("convoLV2: IR file has too many channels for given processor config.\n");
+ // allow loading a quad file to a mono-in stereo-out
+ // eg. 1: L -> L , 2: L -> R
+ for (c = 0; c < n_elem && c < MAX_CHANNEL_MAPS; ++c) {
+ ir_chan[c] = 1 + c;
+ chn_inp[c] = 1 + ((c / out_channel_cnt) % in_channel_cnt);
+ chn_out[c] = 1 + (c % out_channel_cnt);
+ }
+ }
+
+ // assign channel map to convolution engine
+ for (c = 0; c < MAX_CHANNEL_MAPS; ++c) {
+ unsigned int i;
+ if (chn_inp[c] == 0 || chn_out[c] == 0 || ir_chan[c] == 0) {
+ continue;
+ }
+
+ assert (ir_chan[c] <= n_chan);
+
+ for (i = 0; i < n_frames; ++i) {
+ // decode interleaved channels, apply gain scaling
+ gb[i] = p[i * n_chan + ir_chan[c] - 1] * ir_gain[c];
+ }
+
+ VERBOSE_printf ("convoLV2: SET in %d -> out %d [IR chn:%d gain:%+.3f dly:%d]\n",
+ chn_inp[c],
+ chn_out[c],
+ ir_chan[c],
+ ir_gain[c],
+ ir_delay[c]
+ );
+
+ convproc->impdata_create (
+ chn_inp[c] - 1,
+ chn_out[c] - 1,
+ 1, gb, ir_delay[c], ir_delay[c] + n_frames);
+ }
+
+ free(gb); gb = NULL;
+ free(p); p = NULL;
+
+#if 1 // INFO
+ convproc->print (stderr);
+#endif
+
+ if (convproc->start_process (0, 0)) {
+ fprintf(stderr, "convoLV2: Cannot start processing.\n");
+ goto errout;
+ }
+
+ pthread_mutex_unlock(&fftw_planner_lock);
+ return 0;
+
+errout:
+ free(gb);
+ free(p);
+ delete(convproc);
+ convproc = NULL;
+ pthread_mutex_unlock(&fftw_planner_lock);
+ return -1;
+}
+
+int LV2convolv::clv_is_active (void) {
+ if (!convproc || !ir_fn) {
+ return 0;
+ }
+ return 1;
+}
+
+void LV2convolv::silent_output(float * const * outbuf, size_t n_channels, size_t n_samples) {
+ unsigned int c;
+ for (c = 0; c < n_channels; ++c) {
+ memset (outbuf[c], 0, n_samples * sizeof(float));
+ }
+}
+
+int LV2convolv::clv_convolve (
+ const float * const * inbuf,
+ float * const * outbuf,
+ const unsigned int in_channel_cnt,
+ const unsigned int out_channel_cnt,
+ const unsigned int n_samples,
+ const float output_gain)
+{
+ unsigned int c;
+
+ if (!convproc) {
+ silent_output(outbuf, out_channel_cnt, n_samples);
+ return (0);
+ }
+
+ if (convproc->state () == Convproc::ST_WAIT) {
+ convproc->check_stop ();
+ }
+
+ if (fragment_size != n_samples) {
+ silent_output(outbuf, out_channel_cnt, n_samples);
+ return -1;
+ }
+
+#if 1
+ if (convproc->state () != Convproc::ST_PROC) {
+ /* This cannot happen in sync-mode, but... */
+ assert (0);
+ silent_output(outbuf, out_channel_cnt, n_samples);
+ return (n_samples);
+ }
+#endif
+
+ for (c = 0; c < in_channel_cnt; ++c)
+#if 0 // no denormal protection
+ memcpy (clv->convproc->inpdata (c), inbuf[c], n_samples * sizeof (float));
+#else // prevent denormals
+ {
+ unsigned int i;
+ float *id = convproc->inpdata(c);
+ for (i = 0; i < n_samples; ++i) {
+ id[i] = inbuf[c][i] + 1e-20f;
+ }
+ }
+#endif
+
+ int f = convproc->process (false);
+
+ if (f /*&Convproc::FL_LOAD)*/ ) {
+ /* Note this will actually never happen in sync-mode */
+ assert (0);
+ silent_output(outbuf, out_channel_cnt, n_samples);
+ return (n_samples);
+ }
+
+ for (c = 0; c < out_channel_cnt; ++c) {
+ if (output_gain == 1.0) {
+ memcpy (outbuf[c], convproc->outdata (c), n_samples * sizeof (float));
+ } else {
+ unsigned int s;
+ float const * const od = convproc->outdata (c);
+ for (s = 0; s < n_samples; ++s) {
+ outbuf[c][s] = od[s] * output_gain;
+ }
+ }
+ }
+
+ return (n_samples);
+}
+
+LV2convolv::LV2convolv()
+{
+ clv_alloc ();
+}
+
+LV2convolv::~LV2convolv()
+{
+ clv_free ();
+}
diff --git a/plugins/ZamHeadX2/convolution.hpp b/plugins/ZamHeadX2/convolution.hpp
new file mode 100644
index 0000000..ddf3d08
--- /dev/null
+++ b/plugins/ZamHeadX2/convolution.hpp
@@ -0,0 +1,78 @@
+/* LV2 convolution engine
+ *
+ * Copyright (C) 2012 Robin Gareus <robin@gareus.org>
+ *
+ * Modified for C++:
+ * Copyright (C) 2017 Damien Zammit <damien@zamaudio.com>
+ *
+ * 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, 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.
+ */
+
+#ifndef CONVOLUTION_H_
+#define CONVOLUTION_H_
+
+#include "../../lib/zita-convolver-3.1.0/zita-convolver.h"
+
+#define MAX_CHANNEL_MAPS (4)
+#define VERBOSE_printf(x, ...)
+
+class LV2convolv {
+public:
+ Convproc *convproc;
+
+ char *ir_fn;
+ int ir_preset;
+ int ir_presetx;
+ int ir_presety;
+ unsigned int chn_inp[MAX_CHANNEL_MAPS];
+ unsigned int chn_out[MAX_CHANNEL_MAPS];
+ unsigned int ir_chan[MAX_CHANNEL_MAPS];
+ unsigned int ir_delay[MAX_CHANNEL_MAPS];
+ float ir_gain[MAX_CHANNEL_MAPS];
+
+ /* convolution settings*/
+ unsigned int size;
+ float density;
+
+ /* process settings */
+ unsigned int fragment_size;
+
+ /* static methods */
+ static int resample_read_presets (const float *in, unsigned int in_frames, const int sample_rate, float **buf, unsigned int *n_ch, unsigned int *n_sp);
+ static void silent_output(float * const * outbuf, size_t n_channels, size_t n_samples);
+
+ /* convolution specific methods */
+ LV2convolv();
+ ~LV2convolv();
+ void clv_alloc(void);
+ void clv_release (void);
+ void clv_clone_settings(LV2convolv *clv_new);
+ void clv_free (void);
+ int clv_configure (const char *key, const char *value);
+ int clv_configure (const char *key, const char *elev, const char* azim);
+ int clv_initialize (
+ const unsigned int sample_rate,
+ const unsigned int in_channel_cnt,
+ const unsigned int out_channel_cnt,
+ const unsigned int buffersize
+ );
+ int clv_is_active (void);
+ int clv_convolve (
+ const float * const * inbuf,
+ float * const * outbuf,
+ const unsigned int in_channel_cnt,
+ const unsigned int out_channel_cnt,
+ const unsigned int n_samples,
+ const float output_gain
+ );
+};
+
+#endif