diff --git a/examples/fx_cab/android/Makefile b/examples/fx_cab/android/Makefile
new file mode 100644
index 0000000..c00cabf
--- /dev/null
+++ b/examples/fx_cab/android/Makefile
@@ -0,0 +1,6 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+
+NAME := bw_example_fx_cab
+SOURCES = ${SOURCES_COMMON} ${ROOT_DIR}/../src/bw_example_fx_cab.c
+
+include ${ROOT_DIR}/../../common/android/android.mk
diff --git a/examples/fx_cab/daisy-seed/Makefile b/examples/fx_cab/daisy-seed/Makefile
new file mode 100644
index 0000000..96e045c
--- /dev/null
+++ b/examples/fx_cab/daisy-seed/Makefile
@@ -0,0 +1,7 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+
+TARGET = bw_example_fx_cab
+
+C_SOURCES += ${ROOT_DIR}/../src/bw_example_fx_cab.c
+
+include ${ROOT_DIR}/../../common/daisy-seed/daisy-seed.mk
diff --git a/examples/fx_cab/daisy-seed/config_daisy_seed.h b/examples/fx_cab/daisy-seed/config_daisy_seed.h
new file mode 100644
index 0000000..8aa4923
--- /dev/null
+++ b/examples/fx_cab/daisy-seed/config_daisy_seed.h
@@ -0,0 +1,37 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File authors: Stefano D'Angelo
+ */
+
+#ifndef _CONFIG_DAISY_SEED_H
+#define _CONFIG_DAISY_SEED_H
+
+struct config_pin {
+ int param_index;
+ int pin;
+};
+
+#define NUM_PINS 3
+
+static struct config_pin config_pins[NUM_PINS] = {
+ { 0, 15 },
+ { 1, 16 },
+ { 2, 17 }
+};
+
+#endif
diff --git a/examples/fx_cab/ios/Makefile b/examples/fx_cab/ios/Makefile
new file mode 100644
index 0000000..c462aea
--- /dev/null
+++ b/examples/fx_cab/ios/Makefile
@@ -0,0 +1,6 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+
+NAME := bw_example_fx_cab
+SOURCE := ${NAME}.c
+
+include ${ROOT_DIR}/../../common/ios/ios.mk
diff --git a/examples/fx_cab/src/bw_example_fx_cab.c b/examples/fx_cab/src/bw_example_fx_cab.c
new file mode 100644
index 0000000..f09f481
--- /dev/null
+++ b/examples/fx_cab/src/bw_example_fx_cab.c
@@ -0,0 +1,57 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File author: Stefano D'Angelo
+ */
+
+#include "bw_example_fx_cab.h"
+
+void bw_example_fx_cab_init(bw_example_fx_cab *instance) {
+ bw_cab_init(&instance->cab_coeffs);
+}
+
+void bw_example_fx_cab_set_sample_rate(bw_example_fx_cab *instance, float sample_rate) {
+ bw_cab_set_sample_rate(&instance->cab_coeffs, sample_rate);
+}
+
+void bw_example_fx_cab_reset(bw_example_fx_cab *instance) {
+ bw_cab_reset_coeffs(&instance->cab_coeffs);
+ bw_cab_reset_state(&instance->cab_coeffs, &instance->cab_state, 0.f);
+}
+
+void bw_example_fx_cab_process(bw_example_fx_cab *instance, const float** x, float** y, int n_samples) {
+ bw_cab_process(&instance->cab_coeffs, &instance->cab_state, x[0], y[0], n_samples);
+}
+
+void bw_example_fx_cab_set_parameter(bw_example_fx_cab *instance, int index, float value) {
+ instance->params[index] = value;
+ switch (index) {
+ case p_cutoff_low:
+ bw_cab_set_cutoff_low(&instance->cab_coeffs, value);
+ break;
+ case p_cutoff_high:
+ bw_cab_set_cutoff_high(&instance->cab_coeffs, value);
+ break;
+ case p_tone:
+ bw_cab_set_tone(&instance->cab_coeffs, value);
+ break;
+ }
+}
+
+float bw_example_fx_cab_get_parameter(bw_example_fx_cab *instance, int index) {
+ return instance->params[index];
+}
diff --git a/examples/fx_cab/src/bw_example_fx_cab.h b/examples/fx_cab/src/bw_example_fx_cab.h
new file mode 100644
index 0000000..a4497ad
--- /dev/null
+++ b/examples/fx_cab/src/bw_example_fx_cab.h
@@ -0,0 +1,60 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File author: Stefano D'Angelo
+ */
+
+#ifndef _BW_EXAMPLE_FX_CAB_H
+#define _BW_EXAMPLE_FX_CAB_H
+
+#include "platform.h"
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum {
+ p_cutoff_low,
+ p_cutoff_high,
+ p_tone,
+ p_n
+};
+
+struct _bw_example_fx_cab {
+ // Sub-components
+ bw_cab_coeffs cab_coeffs;
+ bw_cab_state cab_state;
+
+ // Parameters
+ float params[p_n];
+};
+typedef struct _bw_example_fx_cab bw_example_fx_cab;
+
+void bw_example_fx_cab_init(bw_example_fx_cab *instance);
+void bw_example_fx_cab_set_sample_rate(bw_example_fx_cab *instance, float sample_rate);
+void bw_example_fx_cab_reset(bw_example_fx_cab *instance);
+void bw_example_fx_cab_process(bw_example_fx_cab *instance, const float** x, float** y, int n_samples);
+void bw_example_fx_cab_set_parameter(bw_example_fx_cab *instance, int index, float value);
+float bw_example_fx_cab_get_parameter(bw_example_fx_cab *instance, int index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/examples/fx_cab/src/config.h b/examples/fx_cab/src/config.h
new file mode 100644
index 0000000..88ff0fa
--- /dev/null
+++ b/examples/fx_cab/src/config.h
@@ -0,0 +1,89 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File authors: Stefano D'Angelo
+ */
+
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+// Definitions
+
+#define IO_MONO 1
+#define IO_STEREO (1<<1)
+
+struct config_io_bus {
+ const char *name;
+ char out;
+ char aux;
+ char cv;
+ char configs;
+};
+
+struct config_parameter {
+ const char *name;
+ const char *shortName;
+ const char *units;
+ char out;
+ char bypass;
+ int steps;
+ float defaultValueUnmapped;
+};
+
+// Data
+
+#define COMPANY_NAME "Orastron"
+#define COMPANY_WEBSITE "https://www.orastron.com/"
+#define COMPANY_MAILTO "mailto:info@orastron.com"
+
+#define PLUGIN_NAME "bw_example_fx_cab"
+#define PLUGIN_VERSION "1.0.0"
+
+#define NUM_BUSES_IN 1
+#define NUM_BUSES_OUT 1
+#define NUM_CHANNELS_IN 1
+#define NUM_CHANNELS_OUT 1
+
+static struct config_io_bus config_buses_in[NUM_BUSES_IN] = {
+ { "Audio in", 0, 0, 0, IO_MONO }
+};
+
+static struct config_io_bus config_buses_out[NUM_BUSES_OUT] = {
+ { "Audio out", 1, 0, 0, IO_MONO }
+};
+
+#define NUM_PARAMETERS 3
+
+static struct config_parameter config_parameters[NUM_PARAMETERS] = {
+ { "Cutoff low", "Low", "", 0, 0, 0, 0.5f },
+ { "Cutoff high", "High", "", 0, 0, 0, 0.5f },
+ { "Tone", "Tone", "", 0, 0, 0, 0.5f }
+};
+
+// Internal API
+
+#include "bw_example_fx_cab.h"
+
+#define P_TYPE bw_example_fx_cab
+#define P_INIT bw_example_fx_cab_init
+#define P_SET_SAMPLE_RATE bw_example_fx_cab_set_sample_rate
+#define P_RESET bw_example_fx_cab_reset
+#define P_PROCESS bw_example_fx_cab_process
+#define P_SET_PARAMETER bw_example_fx_cab_set_parameter
+#define P_GET_PARAMETER bw_example_fx_cab_get_parameter
+
+#endif
diff --git a/examples/fx_cab/src/config.js b/examples/fx_cab/src/config.js
new file mode 100644
index 0000000..7b3ec0e
--- /dev/null
+++ b/examples/fx_cab/src/config.js
@@ -0,0 +1,48 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File author: Stefano D'Angelo
+ */
+
+var buses = [
+ {
+ stereo: false,
+ output: false
+ },
+ {
+ stereo: false,
+ output: true
+ }
+];
+
+var parameters = [
+ {
+ name: "Cutoff low",
+ output: false,
+ defaultValue: 0.5
+ },
+ {
+ name: "Cutoff high",
+ output: false,
+ defaultValue: 0.5
+ },
+ {
+ name: "Tone",
+ output: false,
+ defaultValue: 0.5
+ }
+];
diff --git a/examples/fx_cab/vst3/Makefile b/examples/fx_cab/vst3/Makefile
new file mode 100644
index 0000000..92d1792
--- /dev/null
+++ b/examples/fx_cab/vst3/Makefile
@@ -0,0 +1,9 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+
+NAME := bw_example_fx_cab
+SOURCE := bw_example_fx_cab.c
+
+include ${ROOT_DIR}/../../common/vst3/vst3.mk
+
+CXXFLAGS += -DRELEASE=1 -DNDEBUG -DBW_NO_DEBUG
+#CXXFLAGS += -DDEVELOPMENT=1 -DBW_DEBUG_DEEP
diff --git a/examples/fx_cab/vst3/config_vst3.h b/examples/fx_cab/vst3/config_vst3.h
new file mode 100644
index 0000000..08f3734
--- /dev/null
+++ b/examples/fx_cab/vst3/config_vst3.h
@@ -0,0 +1,36 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File authors: Stefano D'Angelo, Paolo Marrone
+ */
+
+#ifndef _VST3_CONFIG_H
+#define _VST3_CONFIG_H
+
+#define PLUGIN_SUBCATEGORY "Fx|Filter"
+
+#define PLUGIN_GUID_1 0x55dfa68a
+#define PLUGIN_GUID_2 0x343f4cfe
+#define PLUGIN_GUID_3 0x9ab7e2f5
+#define PLUGIN_GUID_4 0x75f4ddd0
+
+#define CTRL_GUID_1 0x828749ce
+#define CTRL_GUID_2 0x6a1d4f2d
+#define CTRL_GUID_3 0x87f464a5
+#define CTRL_GUID_4 0x37de42e0
+
+#endif
diff --git a/examples/fx_cab/web/Makefile b/examples/fx_cab/web/Makefile
new file mode 100644
index 0000000..b3419d8
--- /dev/null
+++ b/examples/fx_cab/web/Makefile
@@ -0,0 +1,4 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+SOURCES = ${SOURCES_COMMON} ${ROOT_DIR}/../src/bw_example_fx_cab.c
+
+include ${ROOT_DIR}/../../common/web/web.mk
diff --git a/examples/fxpp_cab/android/Makefile b/examples/fxpp_cab/android/Makefile
new file mode 100644
index 0000000..ee19bc1
--- /dev/null
+++ b/examples/fxpp_cab/android/Makefile
@@ -0,0 +1,6 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+
+NAME := bw_example_fxpp_cab
+SOURCES = ${SOURCES_COMMON} ${ROOT_DIR}/../src/bw_example_fxpp_cab.cpp
+
+include ${ROOT_DIR}/../../common/android/android.mk
diff --git a/examples/fxpp_cab/daisy-seed/Makefile b/examples/fxpp_cab/daisy-seed/Makefile
new file mode 100644
index 0000000..7bf1ff0
--- /dev/null
+++ b/examples/fxpp_cab/daisy-seed/Makefile
@@ -0,0 +1,7 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+
+TARGET = bw_example_fxpp_cab
+
+CPP_SOURCES_EXTRA = ${ROOT_DIR}/../src/bw_example_fxpp_cab.cpp
+
+include ${ROOT_DIR}/../../common/daisy-seed/daisy-seed.mk
diff --git a/examples/fxpp_cab/daisy-seed/config_daisy_seed.h b/examples/fxpp_cab/daisy-seed/config_daisy_seed.h
new file mode 100644
index 0000000..8aa4923
--- /dev/null
+++ b/examples/fxpp_cab/daisy-seed/config_daisy_seed.h
@@ -0,0 +1,37 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File authors: Stefano D'Angelo
+ */
+
+#ifndef _CONFIG_DAISY_SEED_H
+#define _CONFIG_DAISY_SEED_H
+
+struct config_pin {
+ int param_index;
+ int pin;
+};
+
+#define NUM_PINS 3
+
+static struct config_pin config_pins[NUM_PINS] = {
+ { 0, 15 },
+ { 1, 16 },
+ { 2, 17 }
+};
+
+#endif
diff --git a/examples/fxpp_cab/ios/Makefile b/examples/fxpp_cab/ios/Makefile
new file mode 100644
index 0000000..e3d5314
--- /dev/null
+++ b/examples/fxpp_cab/ios/Makefile
@@ -0,0 +1,6 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+
+NAME := bw_example_fxpp_cab
+SOURCE := ${NAME}.cpp
+
+include ${ROOT_DIR}/../../common/ios/ios.mk
diff --git a/examples/fxpp_cab/src/bw_example_fxpp_cab.cpp b/examples/fxpp_cab/src/bw_example_fxpp_cab.cpp
new file mode 100644
index 0000000..b263926
--- /dev/null
+++ b/examples/fxpp_cab/src/bw_example_fxpp_cab.cpp
@@ -0,0 +1,56 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File author: Stefano D'Angelo
+ */
+
+#include "bw_example_fxpp_cab.h"
+
+void bw_example_fxpp_cab_init(bw_example_fxpp_cab *instance) {
+ (void)instance;
+}
+
+void bw_example_fxpp_cab_set_sample_rate(bw_example_fxpp_cab *instance, float sample_rate) {
+ instance->cab.setSampleRate(sample_rate);
+}
+
+void bw_example_fxpp_cab_reset(bw_example_fxpp_cab *instance) {
+ instance->cab.reset();
+}
+
+void bw_example_fxpp_cab_process(bw_example_fxpp_cab *instance, const float** x, float** y, int n_samples) {
+ instance->cab.process({x[0]}, {y[0]}, n_samples);
+}
+
+void bw_example_fxpp_cab_set_parameter(bw_example_fxpp_cab *instance, int index, float value) {
+ instance->params[index] = value;
+ switch (index) {
+ case p_cutoff_low:
+ instance->cab.setCutoffLow(value);
+ break;
+ case p_cutoff_high:
+ instance->cab.setCutoffHigh(value);
+ break;
+ case p_tone:
+ instance->cab.setTone(value);
+ break;
+ }
+}
+
+float bw_example_fxpp_cab_get_parameter(bw_example_fxpp_cab *instance, int index) {
+ return instance->params[index];
+}
diff --git a/examples/fxpp_cab/src/bw_example_fxpp_cab.h b/examples/fxpp_cab/src/bw_example_fxpp_cab.h
new file mode 100644
index 0000000..f3c7f43
--- /dev/null
+++ b/examples/fxpp_cab/src/bw_example_fxpp_cab.h
@@ -0,0 +1,57 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File author: Stefano D'Angelo
+ */
+
+#ifndef _BW_EXAMPLE_FXPP_CAB_H
+#define _BW_EXAMPLE_FXPP_CAB_H
+
+#include "platform.h"
+
+#include
+
+using namespace Brickworks;
+
+extern "C" {
+
+enum {
+ p_cutoff_low,
+ p_cutoff_high,
+ p_tone,
+ p_n
+};
+
+struct _bw_example_fxpp_cab {
+ // Sub-components
+ Cab<1> cab;
+
+ // Parameters
+ float params[p_n];
+};
+typedef struct _bw_example_fxpp_cab bw_example_fxpp_cab;
+
+void bw_example_fxpp_cab_init(bw_example_fxpp_cab *instance);
+void bw_example_fxpp_cab_set_sample_rate(bw_example_fxpp_cab *instance, float sample_rate);
+void bw_example_fxpp_cab_reset(bw_example_fxpp_cab *instance);
+void bw_example_fxpp_cab_process(bw_example_fxpp_cab *instance, const float** x, float** y, int n_samples);
+void bw_example_fxpp_cab_set_parameter(bw_example_fxpp_cab *instance, int index, float value);
+float bw_example_fxpp_cab_get_parameter(bw_example_fxpp_cab *instance, int index);
+
+}
+
+#endif
diff --git a/examples/fxpp_cab/src/config.h b/examples/fxpp_cab/src/config.h
new file mode 100644
index 0000000..bfcade1
--- /dev/null
+++ b/examples/fxpp_cab/src/config.h
@@ -0,0 +1,89 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File authors: Stefano D'Angelo
+ */
+
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+// Definitions
+
+#define IO_MONO 1
+#define IO_STEREO (1<<1)
+
+struct config_io_bus {
+ const char *name;
+ char out;
+ char aux;
+ char cv;
+ char configs;
+};
+
+struct config_parameter {
+ const char *name;
+ const char *shortName;
+ const char *units;
+ char out;
+ char bypass;
+ int steps;
+ float defaultValueUnmapped;
+};
+
+// Data
+
+#define COMPANY_NAME "Orastron"
+#define COMPANY_WEBSITE "https://www.orastron.com/"
+#define COMPANY_MAILTO "mailto:info@orastron.com"
+
+#define PLUGIN_NAME "bw_example_fxpp_cab"
+#define PLUGIN_VERSION "1.0.0"
+
+#define NUM_BUSES_IN 1
+#define NUM_BUSES_OUT 1
+#define NUM_CHANNELS_IN 1
+#define NUM_CHANNELS_OUT 1
+
+static struct config_io_bus config_buses_in[NUM_BUSES_IN] = {
+ { "Audio in", 0, 0, 0, IO_MONO }
+};
+
+static struct config_io_bus config_buses_out[NUM_BUSES_OUT] = {
+ { "Audio out", 1, 0, 0, IO_MONO }
+};
+
+#define NUM_PARAMETERS 3
+
+static struct config_parameter config_parameters[NUM_PARAMETERS] = {
+ { "Cutoff low", "Low", "", 0, 0, 0, 0.5f },
+ { "Cutoff high", "High", "", 0, 0, 0, 0.5f },
+ { "Tone", "Tone", "", 0, 0, 0, 0.5f }
+};
+
+// Internal API
+
+#include "bw_example_fxpp_cab.h"
+
+#define P_TYPE bw_example_fxpp_cab
+#define P_INIT bw_example_fxpp_cab_init
+#define P_SET_SAMPLE_RATE bw_example_fxpp_cab_set_sample_rate
+#define P_RESET bw_example_fxpp_cab_reset
+#define P_PROCESS bw_example_fxpp_cab_process
+#define P_SET_PARAMETER bw_example_fxpp_cab_set_parameter
+#define P_GET_PARAMETER bw_example_fxpp_cab_get_parameter
+
+#endif
diff --git a/examples/fxpp_cab/src/config.js b/examples/fxpp_cab/src/config.js
new file mode 100644
index 0000000..7b3ec0e
--- /dev/null
+++ b/examples/fxpp_cab/src/config.js
@@ -0,0 +1,48 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File author: Stefano D'Angelo
+ */
+
+var buses = [
+ {
+ stereo: false,
+ output: false
+ },
+ {
+ stereo: false,
+ output: true
+ }
+];
+
+var parameters = [
+ {
+ name: "Cutoff low",
+ output: false,
+ defaultValue: 0.5
+ },
+ {
+ name: "Cutoff high",
+ output: false,
+ defaultValue: 0.5
+ },
+ {
+ name: "Tone",
+ output: false,
+ defaultValue: 0.5
+ }
+];
diff --git a/examples/fxpp_cab/vst3/Makefile b/examples/fxpp_cab/vst3/Makefile
new file mode 100644
index 0000000..99a0f61
--- /dev/null
+++ b/examples/fxpp_cab/vst3/Makefile
@@ -0,0 +1,9 @@
+ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
+
+NAME := bw_example_fxpp_cab
+SOURCE := bw_example_fxpp_cab.cpp
+
+include ${ROOT_DIR}/../../common/vst3/vst3.mk
+
+CXXFLAGS += -DRELEASE=1 -DNDEBUG -DBW_NO_DEBUG
+#CXXFLAGS += -DDEVELOPMENT=1 -DBW_DEBUG_DEEP
diff --git a/examples/fxpp_cab/vst3/config_vst3.h b/examples/fxpp_cab/vst3/config_vst3.h
new file mode 100644
index 0000000..17d03f4
--- /dev/null
+++ b/examples/fxpp_cab/vst3/config_vst3.h
@@ -0,0 +1,36 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File authors: Stefano D'Angelo, Paolo Marrone
+ */
+
+#ifndef _VST3_CONFIG_H
+#define _VST3_CONFIG_H
+
+#define PLUGIN_SUBCATEGORY "Fx|Filter"
+
+#define PLUGIN_GUID_1 0x5a6af39b
+#define PLUGIN_GUID_2 0x3d3d4d8d
+#define PLUGIN_GUID_3 0xb770d068
+#define PLUGIN_GUID_4 0x970eb848
+
+#define CTRL_GUID_1 0x29ea612e
+#define CTRL_GUID_2 0x754d401b
+#define CTRL_GUID_3 0xa6164ba3
+#define CTRL_GUID_4 0x931dc498
+
+#endif
diff --git a/include/bw_cab.h b/include/bw_cab.h
new file mode 100644
index 0000000..fe6032c
--- /dev/null
+++ b/include/bw_cab.h
@@ -0,0 +1,787 @@
+/*
+ * Brickworks
+ *
+ * Copyright (C) 2023 Orastron Srl unipersonale
+ *
+ * Brickworks is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * Brickworks 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 Brickworks. If not, see .
+ *
+ * File author: Stefano D'Angelo
+ */
+
+/*!
+ * module_type {{{ dsp }}}
+ * version {{{ 1.0.0 }}}
+ * requires {{{ bw_common bw_gain bw_math bw_one_pole bw_svf }}}
+ * description {{{
+ * Cab simulator effect.
+ *
+ * This is a simple digital effect made of a bunch of filters, not an
+ * emulation of any analog circuit nor a simulation of an actual cabinet.
+ * }}}
+ * changelog {{{
+ *
+ * - Version 1.0.0:
+ *
+ *
+ *
+ * }}}
+ */
+
+#ifndef BW_CAB_H
+#define BW_CAB_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*! api {{{
+ * #### bw_cab_coeffs
+ * ```>>> */
+typedef struct bw_cab_coeffs bw_cab_coeffs;
+/*! <<<```
+ * Coefficients and related.
+ *
+ * #### bw_cab_state
+ * ```>>> */
+typedef struct bw_cab_state bw_cab_state;
+/*! <<<```
+ * Internal state and related.
+ *
+ * #### bw_cab_init()
+ * ```>>> */
+static inline void bw_cab_init(
+ bw_cab_coeffs * BW_RESTRICT coeffs);
+/*! <<<```
+ * Initializes input parameter values in `coeffs`.
+ *
+ * #### bw_cab_set_sample_rate()
+ * ```>>> */
+static inline void bw_cab_set_sample_rate(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ float sample_rate);
+/*! <<<```
+ * Sets the `sample_rate` (Hz) value in `coeffs`.
+ *
+ * #### bw_cab_reset_coeffs()
+ * ```>>> */
+static inline void bw_cab_reset_coeffs(
+ bw_cab_coeffs * BW_RESTRICT coeffs);
+/*! <<<```
+ * Resets coefficients in `coeffs` to assume their target values.
+ *
+ * #### bw_cab_reset_state()
+ * ```>>> */
+static inline float bw_cab_reset_state(
+ const bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT state,
+ float x_0);
+/*! <<<```
+ * Resets the given `state` to its initial values using the given `coeffs`
+ * and the initial input value `x_0`.
+ *
+ * Returns the corresponding initial output value.
+ *
+ * #### bw_cab_reset_state_multi()
+ * ```>>> */
+static inline void bw_cab_reset_state_multi(
+ const bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT const * BW_RESTRICT state,
+ const float * x_0,
+ float * y_0,
+ size_t n_channels);
+/*! <<<```
+ * Resets each of the `n_channels` `state`s to its initial values using the
+ * given `coeffs` and the corresponding initial input value in the `x_0`
+ * array.
+ *
+ * The corresponding initial output values are written into the `y_0` array,
+ * if not `NULL`.
+ *
+ * #### bw_cab_update_coeffs_ctrl()
+ * ```>>> */
+static inline void bw_cab_update_coeffs_ctrl(
+ bw_cab_coeffs * BW_RESTRICT coeffs);
+/*! <<<```
+ * Triggers control-rate update of coefficients in `coeffs`.
+ *
+ * #### bw_cab_update_coeffs_audio()
+ * ```>>> */
+static inline void bw_cab_update_coeffs_audio(
+ bw_cab_coeffs * BW_RESTRICT coeffs);
+/*! <<<```
+ * Triggers audio-rate update of coefficients in `coeffs`.
+ *
+ * #### bw_cab_process1()
+ * ```>>> */
+static inline float bw_cab_process1(
+ const bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT state,
+ float x);
+/*! <<<```
+ * Processes one input sample `x` using `coeffs`, while using and updating
+ * `state`. Returns the corresponding output sample.
+ *
+ * #### bw_cab_process()
+ * ```>>> */
+static inline void bw_cab_process(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT state,
+ const float * x,
+ float * y,
+ size_t n_samples);
+/*! <<<```
+ * Processes the first `n_samples` of the input buffer `x` and fills the
+ * first `n_samples` of the output buffer `y`, while using and updating both
+ * `coeffs` and `state` (control and audio rate).
+ *
+ * #### bw_cab_process_multi()
+ * ```>>> */
+static inline void bw_cab_process_multi(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT const * BW_RESTRICT state,
+ const float * const * x,
+ float * const * y,
+ size_t n_channels,
+ size_t n_samples);
+/*! <<<```
+ * Processes the first `n_samples` of the `n_channels` input buffers `x` and
+ * fills the first `n_samples` of the `n_channels` output buffers `y`, while
+ * using and updating both the common `coeffs` and each of the `n_channels`
+ * `state`s (control and audio rate).
+ *
+ * #### bw_cab_set_cutoff_low()
+ * ```>>> */
+static inline void bw_cab_set_cutoff_low(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ float value);
+/*! <<<```
+ * Sets the low cutoff frequency to the given (relative) `value` in `coeffs`.
+ *
+ * Valid range: [`0.f`, `1.f`].
+ *
+ * Default value: `0.5f`.
+ *
+ * #### bw_cab_set_cutoff_high()
+ * ```>>> */
+static inline void bw_cab_set_cutoff_high(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ float value);
+/*! <<<```
+ * Sets the high cutoff frequency to the given (relative) `value` in
+ * `coeffs`.
+ *
+ * Valid range: [`0.f`, `1.f`].
+ *
+ * Default value: `0.5f`.
+ *
+ * #### bw_cab_set_tone()
+ * ```>>> */
+static inline void bw_cab_set_tone(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ float value);
+/*! <<<```
+ * Sets the tone to the given `value` in `coeffs`.
+ *
+ * Valid range: [`0.f` (dark), `1.f` (bright)].
+ *
+ * Default value: `0.5f`.
+ *
+ * #### bw_cab_coeffs_is_valid()
+ * ```>>> */
+static inline char bw_cab_coeffs_is_valid(
+ const bw_cab_coeffs * BW_RESTRICT coeffs);
+/*! <<<```
+ * Tries to determine whether `coeffs` is valid and returns non-`0` if it
+ * seems to be the case and `0` if it is certainly not. False positives are
+ * possible, false negatives are not.
+ *
+ * `coeffs` must at least point to a readable memory block of size greater
+ * than or equal to that of `bw_cab_coeffs`.
+ *
+ * #### bw_cab_state_is_valid()
+ * ```>>> */
+static inline char bw_cab_state_is_valid(
+ const bw_cab_coeffs * BW_RESTRICT coeffs,
+ const bw_cab_state * BW_RESTRICT state);
+/*! <<<```
+ * Tries to determine whether `state` is valid and returns non-`0` if it
+ * seems to be the case and `0` if it is certainly not. False positives are
+ * possible, false negatives are not.
+ *
+ * If `coeffs` is not `NULL` extra cross-checks might be performed (`state`
+ * is supposed to be associated to `coeffs`).
+ *
+ * `state` must at least point to a readable memory block of size greater
+ * than or equal to that of `bw_cab_state`.
+ * }}} */
+
+#ifdef __cplusplus
+}
+#endif
+
+/*** Implementation ***/
+
+/* WARNING: This part of the file is not part of the public API. Its content may
+ * change at any time in future versions. Please, do not use it directly. */
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef BW_DEBUG_DEEP
+enum bw_cab_coeffs_state {
+ bw_cab_coeffs_state_invalid,
+ bw_cab_coeffs_state_init,
+ bw_cab_coeffs_state_set_sample_rate,
+ bw_cab_coeffs_state_reset_coeffs
+};
+#endif
+
+struct bw_cab_coeffs {
+#ifdef BW_DEBUG_DEEP
+ uint32_t hash;
+ enum bw_cab_coeffs_state state;
+ uint32_t reset_id;
+#endif
+
+ // Sub-components
+ bw_svf_coeffs lp_coeffs;
+ bw_svf_coeffs hp_coeffs;
+ bw_svf_coeffs bpl_coeffs;
+ bw_svf_coeffs bph_coeffs;
+ bw_gain_coeffs gain_bpl_coeffs;
+ bw_gain_coeffs gain_bph_coeffs;
+};
+
+struct bw_cab_state {
+#ifdef BW_DEBUG_DEEP
+ uint32_t hash;
+ uint32_t coeffs_reset_id;
+#endif
+
+ // Sub-components
+ bw_svf_state lp_state;
+ bw_svf_state hp_state;
+ bw_svf_state bpl_state;
+ bw_svf_state bph_state;
+};
+
+static inline void bw_cab_init(
+ bw_cab_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != NULL);
+
+ bw_svf_init(&coeffs->lp_coeffs);
+ bw_svf_init(&coeffs->hp_coeffs);
+ bw_svf_init(&coeffs->bpl_coeffs);
+ bw_svf_init(&coeffs->bph_coeffs);
+ bw_gain_init(&coeffs->gain_bpl_coeffs);
+ bw_gain_init(&coeffs->gain_bph_coeffs);
+
+ bw_svf_set_cutoff(&coeffs->lp_coeffs, 4e3f);
+ bw_svf_set_cutoff(&coeffs->hp_coeffs, 100.f);
+ bw_svf_set_cutoff(&coeffs->bpl_coeffs, 100.f);
+ bw_svf_set_cutoff(&coeffs->bph_coeffs, 4e3f);
+ bw_gain_set_gain_lin(&coeffs->gain_bpl_coeffs, 2.25f);
+ bw_gain_set_gain_lin(&coeffs->gain_bph_coeffs, 3.75f);
+
+#ifdef BW_DEBUG_DEEP
+ coeffs->hash = bw_hash_sdbm("bw_cab_coeffs");
+ coeffs->state = bw_cab_coeffs_state_init;
+ coeffs->reset_id = coeffs->hash + 1;
+#endif
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state == bw_cab_coeffs_state_init);
+}
+
+static inline void bw_cab_set_sample_rate(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ float sample_rate) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_init);
+ BW_ASSERT(bw_is_finite(sample_rate) && sample_rate > 0.f);
+
+ bw_svf_set_sample_rate(&coeffs->lp_coeffs, sample_rate);
+ bw_svf_set_sample_rate(&coeffs->hp_coeffs, sample_rate);
+ bw_svf_set_sample_rate(&coeffs->bpl_coeffs, sample_rate);
+ bw_svf_set_sample_rate(&coeffs->bph_coeffs, sample_rate);
+ bw_gain_set_sample_rate(&coeffs->gain_bpl_coeffs, sample_rate);
+ bw_gain_set_sample_rate(&coeffs->gain_bph_coeffs, sample_rate);
+
+#ifdef BW_DEBUG_DEEP
+ coeffs->state = bw_cab_coeffs_state_set_sample_rate;
+#endif
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state == bw_cab_coeffs_state_set_sample_rate);
+}
+
+static inline void bw_cab_reset_coeffs(
+ bw_cab_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_set_sample_rate);
+
+ bw_svf_reset_coeffs(&coeffs->lp_coeffs);
+ bw_svf_reset_coeffs(&coeffs->hp_coeffs);
+ bw_svf_reset_coeffs(&coeffs->bpl_coeffs);
+ bw_svf_reset_coeffs(&coeffs->bph_coeffs);
+ bw_gain_reset_coeffs(&coeffs->gain_bpl_coeffs);
+ bw_gain_reset_coeffs(&coeffs->gain_bph_coeffs);
+
+#ifdef BW_DEBUG_DEEP
+ coeffs->state = bw_cab_coeffs_state_reset_coeffs;
+ coeffs->reset_id++;
+#endif
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state == bw_cab_coeffs_state_reset_coeffs);
+}
+
+static inline float bw_cab_reset_state(
+ const bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT state,
+ float x_0) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT(state != NULL);
+ BW_ASSERT(bw_is_finite(x_0));
+
+ float v_lp, v_hp, v_bp;
+ bw_svf_reset_state(&coeffs->lp_coeffs, &state->lp_state, x_0, &v_lp, &v_bp, &v_hp);
+ bw_svf_reset_state(&coeffs->hp_coeffs, &state->hp_state, v_lp, &v_lp, &v_bp, &v_hp);
+ float v_bpl, v_bph;
+ float y = v_hp;
+ bw_svf_reset_state(&coeffs->bpl_coeffs, &state->bpl_state, y, &v_lp, &v_bpl, &v_hp);
+ bw_svf_reset_state(&coeffs->bph_coeffs, &state->bph_state, y, &v_lp, &v_bph, &v_hp);
+ y = bw_gain_get_gain_cur(&coeffs->gain_bpl_coeffs) * v_bpl
+ + bw_gain_get_gain_cur(&coeffs->gain_bph_coeffs) * v_bph
+ + 0.45f * y;
+
+#ifdef BW_DEBUG_DEEP
+ state->hash = bw_hash_sdbm("bw_cab_state");
+ state->coeffs_reset_id = coeffs->reset_id;
+#endif
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT_DEEP(bw_cab_state_is_valid(coeffs, state));
+ BW_ASSERT(bw_is_finite(y));
+
+ return y;
+}
+
+static inline void bw_cab_reset_state_multi(
+ const bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT const * BW_RESTRICT state,
+ const float * x_0,
+ float * y_0,
+ size_t n_channels) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT(state != NULL);
+#ifndef BW_NO_DEBUG
+ for (size_t i = 0; i < n_channels; i++)
+ for (size_t j = i + 1; j < n_channels; j++)
+ BW_ASSERT(state[i] != state[j]);
+#endif
+ BW_ASSERT(x_0 != NULL);
+
+ if (y_0 != NULL)
+ for (size_t i = 0; i < n_channels; i++)
+ y_0[i] = bw_cab_reset_state(coeffs, state[i], x_0[i]);
+ else
+ for (size_t i = 0; i < n_channels; i++)
+ bw_cab_reset_state(coeffs, state[i], x_0[i]);
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT_DEEP(y_0 != NULL ? bw_has_only_finite(y_0, n_channels) : 1);
+}
+
+static inline void bw_cab_update_coeffs_ctrl(
+ bw_cab_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+
+ bw_svf_update_coeffs_ctrl(&coeffs->lp_coeffs);
+ bw_svf_update_coeffs_ctrl(&coeffs->hp_coeffs);
+ bw_svf_update_coeffs_ctrl(&coeffs->bpl_coeffs);
+ bw_svf_update_coeffs_ctrl(&coeffs->bph_coeffs);
+ bw_gain_update_coeffs_ctrl(&coeffs->gain_bpl_coeffs);
+ bw_gain_update_coeffs_ctrl(&coeffs->gain_bph_coeffs);
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+}
+
+static inline void bw_cab_update_coeffs_audio(
+ bw_cab_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+
+ bw_svf_update_coeffs_audio(&coeffs->lp_coeffs);
+ bw_svf_update_coeffs_audio(&coeffs->hp_coeffs);
+ bw_svf_update_coeffs_audio(&coeffs->bpl_coeffs);
+ bw_svf_update_coeffs_audio(&coeffs->bph_coeffs);
+ bw_gain_update_coeffs_audio(&coeffs->gain_bpl_coeffs);
+ bw_gain_update_coeffs_audio(&coeffs->gain_bph_coeffs);
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+}
+
+static inline float bw_cab_process1(
+ const bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT state,
+ float x) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT(state != NULL);
+ BW_ASSERT_DEEP(bw_cab_state_is_valid(coeffs, state));
+ BW_ASSERT(bw_is_finite(x));
+
+ float v_lp, v_hp, v_bp;
+ bw_svf_process1(&coeffs->lp_coeffs, &state->lp_state, x, &v_lp, &v_bp, &v_hp);
+ bw_svf_process1(&coeffs->hp_coeffs, &state->hp_state, v_lp, &v_lp, &v_bp, &v_hp);
+ float v_bpl, v_bph;
+ float y = v_hp;
+ bw_svf_process1(&coeffs->bpl_coeffs, &state->bpl_state, y, &v_lp, &v_bpl, &v_hp);
+ bw_svf_process1(&coeffs->bph_coeffs, &state->bph_state, y, &v_lp, &v_bph, &v_hp);
+ y = bw_gain_process1(&coeffs->gain_bpl_coeffs, v_bpl)
+ + bw_gain_process1(&coeffs->gain_bph_coeffs, v_bph)
+ + 0.45f * y;
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT_DEEP(bw_cab_state_is_valid(coeffs, state));
+ BW_ASSERT(bw_is_finite(y));
+
+ return y;
+}
+
+static inline void bw_cab_process(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT state,
+ const float * x,
+ float * y,
+ size_t n_samples) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT(state != NULL);
+ BW_ASSERT_DEEP(bw_cab_state_is_valid(coeffs, state));
+ BW_ASSERT(x != NULL);
+ BW_ASSERT_DEEP(bw_has_only_finite(x, n_samples));
+ BW_ASSERT(y != NULL);
+
+ bw_cab_update_coeffs_ctrl(coeffs);
+ for (size_t i = 0; i < n_samples; i++) {
+ bw_cab_update_coeffs_audio(coeffs);
+ y[i] = bw_cab_process1(coeffs, state, x[i]);
+ }
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT_DEEP(bw_cab_state_is_valid(coeffs, state));
+ BW_ASSERT_DEEP(bw_has_only_finite(y, n_samples));
+}
+
+static inline void bw_cab_process_multi(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ bw_cab_state * BW_RESTRICT const * BW_RESTRICT state,
+ const float * const * x,
+ float * const * y,
+ size_t n_channels,
+ size_t n_samples) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+ BW_ASSERT(state != NULL);
+#ifndef BW_NO_DEBUG
+ for (size_t i = 0; i < n_channels; i++)
+ for (size_t j = i + 1; j < n_channels; j++)
+ BW_ASSERT(state[i] != state[j]);
+#endif
+ BW_ASSERT(x != NULL);
+ BW_ASSERT(y != NULL);
+#ifndef BW_NO_DEBUG
+ for (size_t i = 0; i < n_channels; i++)
+ for (size_t j = i + 1; j < n_channels; j++)
+ BW_ASSERT(y[i] != y[j]);
+#endif
+
+ bw_cab_update_coeffs_ctrl(coeffs);
+ for (size_t i = 0; i < n_samples; i++) {
+ bw_cab_update_coeffs_audio(coeffs);
+ for (size_t j = 0; j < n_channels; j++)
+ y[j][i] = bw_cab_process1(coeffs, state[j], x[j][i]);
+ }
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_reset_coeffs);
+}
+
+static inline void bw_cab_set_cutoff_low(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ float value) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_init);
+ BW_ASSERT(bw_is_finite(value));
+ BW_ASSERT(value >= 0.f && value <= 1.f);
+
+ const float f = 50.f + value * (50.f + 100.f * value);
+ bw_svf_set_cutoff(&coeffs->hp_coeffs, f);
+ bw_svf_set_cutoff(&coeffs->bpl_coeffs, f);
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_init);
+}
+
+static inline void bw_cab_set_cutoff_high(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ float value) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_init);
+ BW_ASSERT(bw_is_finite(value));
+ BW_ASSERT(value >= 0.f && value <= 1.f);
+
+ const float f = 2e3f + value * (2e3f + 4e3f * value);
+ bw_svf_set_cutoff(&coeffs->lp_coeffs, f);
+ bw_svf_set_cutoff(&coeffs->bph_coeffs, f);
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_init);
+}
+
+static inline void bw_cab_set_tone(
+ bw_cab_coeffs * BW_RESTRICT coeffs,
+ float value) {
+ BW_ASSERT(coeffs != NULL);
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_init);
+ BW_ASSERT(bw_is_finite(value));
+ BW_ASSERT(value >= 0.f && value <= 1.f);
+
+ bw_gain_set_gain_lin(&coeffs->gain_bpl_coeffs, 3.f - 1.5f * value);
+ bw_gain_set_gain_lin(&coeffs->gain_bph_coeffs, 3.f + 1.5f * value);
+
+ BW_ASSERT_DEEP(bw_cab_coeffs_is_valid(coeffs));
+ BW_ASSERT_DEEP(coeffs->state >= bw_cab_coeffs_state_init);
+}
+
+static inline char bw_cab_coeffs_is_valid(
+ const bw_cab_coeffs * BW_RESTRICT coeffs) {
+ BW_ASSERT(coeffs != NULL);
+
+#ifdef BW_DEBUG_DEEP
+ if (coeffs->hash != bw_hash_sdbm("bw_cab_coeffs"))
+ return 0;
+ if (coeffs->state < bw_cab_coeffs_state_init || coeffs->state > bw_cab_coeffs_state_reset_coeffs)
+ return 0;
+#endif
+
+ return bw_svf_coeffs_is_valid(&coeffs->lp_coeffs)
+ && bw_svf_coeffs_is_valid(&coeffs->hp_coeffs)
+ && bw_svf_coeffs_is_valid(&coeffs->bpl_coeffs)
+ && bw_svf_coeffs_is_valid(&coeffs->bph_coeffs)
+ && bw_gain_coeffs_is_valid(&coeffs->gain_bpl_coeffs)
+ && bw_gain_coeffs_is_valid(&coeffs->gain_bph_coeffs);
+}
+
+static inline char bw_cab_state_is_valid(
+ const bw_cab_coeffs * BW_RESTRICT coeffs,
+ const bw_cab_state * BW_RESTRICT state) {
+ BW_ASSERT(state != NULL);
+
+#ifdef BW_DEBUG_DEEP
+ if (state->hash != bw_hash_sdbm("bw_cab_state"))
+ return 0;
+
+ if (coeffs != NULL && coeffs->reset_id != state->coeffs_reset_id)
+ return 0;
+#endif
+
+ return bw_svf_state_is_valid(coeffs ? &coeffs->lp_coeffs : NULL, &state->lp_state)
+ && bw_svf_state_is_valid(coeffs ? &coeffs->hp_coeffs : NULL, &state->hp_state)
+ && bw_svf_state_is_valid(coeffs ? &coeffs->bpl_coeffs : NULL, &state->bpl_state)
+ && bw_svf_state_is_valid(coeffs ? &coeffs->bph_coeffs : NULL, &state->bph_state);
+}
+
+#ifdef __cplusplus
+}
+
+#include
+
+namespace Brickworks {
+
+/*** Public C++ API ***/
+
+/*! api_cpp {{{
+ * ##### Brickworks::Cab
+ * ```>>> */
+template
+class Cab {
+public:
+ Cab();
+
+ void setSampleRate(
+ float sampleRate);
+
+ void reset(
+ float x0 = 0.f,
+ float * BW_RESTRICT y0 = nullptr);
+
+ void reset(
+ float x0,
+ std::array * BW_RESTRICT y0);
+
+ void reset(
+ const float * x0,
+ float * y0 = nullptr);
+
+ void reset(
+ std::array x0,
+ std::array * BW_RESTRICT y0 = nullptr);
+
+ void process(
+ const float * const * x,
+ float * const * y,
+ size_t nSamples);
+
+ void process(
+ std::array x,
+ std::array y,
+ size_t nSamples);
+
+ void setCutoffLow(
+ float value);
+
+ void setCutoffHigh(
+ float value);
+
+ void setTone(
+ float value);
+/*! <<<...
+ * }
+ * ```
+ * }}} */
+
+/*** Implementation ***/
+
+/* WARNING: This part of the file is not part of the public API. Its content may
+ * change at any time in future versions. Please, do not use it directly. */
+
+private:
+ bw_cab_coeffs coeffs;
+ bw_cab_state states[N_CHANNELS];
+ bw_cab_state * BW_RESTRICT statesP[N_CHANNELS];
+};
+
+template
+inline Cab::Cab() {
+ bw_cab_init(&coeffs);
+ for (size_t i = 0; i < N_CHANNELS; i++)
+ statesP[i] = states + i;
+}
+
+template
+inline void Cab::setSampleRate(
+ float sampleRate) {
+ bw_cab_set_sample_rate(&coeffs, sampleRate);
+}
+
+template
+inline void Cab::reset(
+ float x0,
+ float * BW_RESTRICT y0) {
+ bw_cab_reset_coeffs(&coeffs);
+ if (y0 != nullptr)
+ for (size_t i = 0; i < N_CHANNELS; i++)
+ y0[i] = bw_cab_reset_state(&coeffs, states + i, x0);
+ else
+ for (size_t i = 0; i < N_CHANNELS; i++)
+ bw_cab_reset_state(&coeffs, states + i, x0);
+}
+
+template
+inline void Cab::reset(
+ float x0,
+ std::array * BW_RESTRICT y0) {
+ reset(x0, y0 != nullptr ? y0->data() : nullptr);
+}
+
+template
+inline void Cab::reset(
+ const float * x0,
+ float * y0) {
+ bw_cab_reset_coeffs(&coeffs);
+ bw_cab_reset_state_multi(&coeffs, statesP, x0, y0, N_CHANNELS);
+}
+
+template
+inline void Cab::reset(
+ std::array x0,
+ std::array * BW_RESTRICT y0) {
+ reset(x0.data(), y0 != nullptr ? y0->data() : nullptr);
+}
+
+template
+inline void Cab::process(
+ const float * const * x,
+ float * const * y,
+ size_t nSamples) {
+ bw_cab_process_multi(&coeffs, statesP, x, y, N_CHANNELS, nSamples);
+}
+
+template
+inline void Cab::process(
+ std::array x,
+ std::array y,
+ size_t nSamples) {
+ process(x.data(), y.data(), nSamples);
+}
+
+template
+inline void Cab::setCutoffLow(
+ float value) {
+ bw_cab_set_cutoff_low(&coeffs, value);
+}
+
+template
+inline void Cab::setCutoffHigh(
+ float value) {
+ bw_cab_set_cutoff_high(&coeffs, value);
+}
+
+template
+inline void Cab::setTone(
+ float value) {
+ bw_cab_set_tone(&coeffs, value);
+}
+
+}
+#endif
+
+#endif