// SPDX-FileCopyrightText: 2011-2025 Filipe Coelho // SPDX-License-Identifier: GPL-2.0-or-later #include "CarlaNative.h" #include #include #include #include // ----------------------------------------------------------------------- typedef struct { float a0, b1, z1; } Filter; static inline void set_filter_sample_rate(Filter* const filter, const float sampleRate) { const float frequency = 30.0f / sampleRate; filter->b1 = expf(-2.0f * (float)M_PI * frequency); filter->a0 = 1.0f - filter->b1; filter->z1 = 0.0f; } // ----------------------------------------------------------------------- typedef enum { PARAM_LEFT_RIGHT = 0, PARAM_FRONT_REAR, PARAM_COUNT } QuadPannerParams; typedef struct { Filter lowpass[PARAM_COUNT * 2]; float params[PARAM_COUNT]; } QuadPannerHandle; // ----------------------------------------------------------------------- static NativePluginHandle quadpanner_instantiate(const NativeHostDescriptor* host) { QuadPannerHandle* const handle = calloc(1, sizeof(QuadPannerHandle)); if (handle == NULL) return NULL; const float sampleRate = host->get_sample_rate(host->handle); for (unsigned i = 0; i < PARAM_COUNT * 2; ++i) set_filter_sample_rate(&handle->lowpass[i], sampleRate); return handle; } #define handlePtr ((QuadPannerHandle*)handle) static void quadpanner_cleanup(NativePluginHandle handle) { free(handlePtr); } static uint32_t quadpanner_get_parameter_count(NativePluginHandle handle) { return PARAM_COUNT; // unused (void)handle; } static const NativeParameter* quadpanner_get_parameter_info(NativePluginHandle handle, uint32_t index) { if (index > PARAM_COUNT) return NULL; static NativeParameter param; param.hints = NATIVE_PARAMETER_IS_ENABLED|NATIVE_PARAMETER_IS_AUTOMATABLE; param.unit = "%"; param.ranges.def = 0.0f; param.ranges.min = -100.0f; param.ranges.max = 100.0f; param.ranges.step = 1; param.ranges.stepSmall = 1; param.ranges.stepLarge = 10; switch (index) { case PARAM_LEFT_RIGHT: param.name = "Left/Right"; break; case PARAM_FRONT_REAR: param.name = "Front/Rear"; break; } return ¶m; // unused (void)handle; } static float quadpanner_get_parameter_value(NativePluginHandle handle, uint32_t index) { return handlePtr->params[index]; } static void quadpanner_set_parameter_value(NativePluginHandle handle, uint32_t index, float value) { handlePtr->params[index] = value; } static inline void handle_audio_buffers(const float* inBuffer, float* outBuffer, Filter* const filter, const float gain, const uint32_t frames) { const float a0 = filter->a0; const float b1 = filter->b1; float z1 = filter->z1; for (uint32_t i = 0; i < frames; ++i) { z1 = gain * a0 + z1 * b1; *outBuffer++ = *inBuffer++ * z1; } filter->z1 = z1; } // FIXME for v3.0, use const for the input buffer static void quadpanner_process(NativePluginHandle handle, float** inBuffer, float** outBuffer, uint32_t frames, const NativeMidiEvent* midiEvents, uint32_t midiEventCount) { float v1, v2, tmp; // left/right if ((tmp = handlePtr->params[PARAM_LEFT_RIGHT]) < 0.f) { v1 = 1.f; v2 = 1.f - tmp * -0.01f; } else { v1 = 1.f - tmp * 0.01f; v2 = 1.f; } handle_audio_buffers(inBuffer[0], outBuffer[0], &handlePtr->lowpass[0], v1, frames); handle_audio_buffers(inBuffer[1], outBuffer[1], &handlePtr->lowpass[1], v2, frames); // front/rear if ((tmp = handlePtr->params[PARAM_FRONT_REAR]) < 0.f) { v1 = 1.f; v2 = 1.f - tmp * -0.01f; } else { v1 = 1.f - tmp * 0.01f; v2 = 1.f; } handle_audio_buffers(inBuffer[2], outBuffer[2], &handlePtr->lowpass[2], v1, frames); handle_audio_buffers(inBuffer[3], outBuffer[3], &handlePtr->lowpass[3], v2, frames); return; // unused (void)midiEvents; (void)midiEventCount; } static intptr_t quadpanner_dispatcher(NativePluginHandle handle, NativePluginDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt) { switch (opcode) { case NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED: for (unsigned i = 0; i < PARAM_COUNT * 2; ++i) set_filter_sample_rate(&handlePtr->lowpass[i], opt); break; default: break; } return 0; // unused (void)index; (void)value; (void)ptr; } static const char* quadpanner_get_buffer_port_name(NativePluginHandle handle, uint32_t index, bool isOutput) { static const char* const kInNames[4] = { "In-Left", "In-Right", "In-Front", "In-Rear", }; static const char* const kOutNames[4] = { "Out-Left", "Out-Right", "Out-Front", "Out-Rear", }; return isOutput ? kOutNames[index] : kInNames[index]; // unused (void)handle; } // ----------------------------------------------------------------------- static const NativePluginDescriptor quadpannerDesc = { .category = NATIVE_PLUGIN_CATEGORY_UTILITY, .hints = NATIVE_PLUGIN_IS_RTSAFE, .supports = NATIVE_PLUGIN_SUPPORTS_NOTHING, .audioIns = 4, .audioOuts = 4, .cvIns = 0, .cvOuts = 0, .midiIns = 0, .midiOuts = 0, .paramIns = PARAM_COUNT, .paramOuts = 0, .name = "Quad-Panner", .label = "quadpanner", .maker = "falkTX", .copyright = "GNU GPL v2+", .instantiate = quadpanner_instantiate, .cleanup = quadpanner_cleanup, .get_parameter_count = quadpanner_get_parameter_count, .get_parameter_info = quadpanner_get_parameter_info, .get_parameter_value = quadpanner_get_parameter_value, .get_midi_program_count = NULL, .get_midi_program_info = NULL, .set_parameter_value = quadpanner_set_parameter_value, .set_midi_program = NULL, .set_custom_data = NULL, .ui_show = NULL, .ui_idle = NULL, .ui_set_parameter_value = NULL, .ui_set_midi_program = NULL, .ui_set_custom_data = NULL, .activate = NULL, .deactivate = NULL, .process = quadpanner_process, .get_state = NULL, .set_state = NULL, .dispatcher = quadpanner_dispatcher, .render_inline_display = NULL, .get_buffer_port_name = quadpanner_get_buffer_port_name, .get_buffer_port_range = NULL, }; // ----------------------------------------------------------------------- void carla_register_native_plugin_quadpanner(void); void carla_register_native_plugin_quadpanner(void) { carla_register_native_plugin(&quadpannerDesc); } // -----------------------------------------------------------------------