################################################################################
#
# ZOOM-AUTOPAN (Zoom MS-70CDR Geometric Panner for MOD Dwarf)
# Standalone True Stereo Autopanner Specification & Source Package
#
################################################################################

ZOOM_AUTOPAN_VERSION = 1.0.0
ZOOM_AUTOPAN_SITE_METHOD = local
ZOOM_AUTOPAN_SITE = $(_ZOOM_AUTOPAN_SITE_ENV)
ZOOM_AUTOPAN_SOURCE =

ZOOM_AUTOPAN_BUNDLES = zoom-autopan.lv2

define ZOOM_AUTOPAN_CPP_DATA
#include <lv2/core/lv2.h>
#include <cmath>
#include <stdint.h>
#include <stdlib.h>

#define AUTOPAN_URI "https://modaudio.com/plugins/zoom-autopan"

enum Ports {
    PAN_INPUT_L  = 0,
    PAN_INPUT_R  = 1,
    PAN_OUTPUT_L = 2,
    PAN_OUTPUT_R = 3,
    PAN_RATE     = 4,
    PAN_WIDTH    = 5,
    PAN_WAVE     = 6
};

typedef struct {
    const float* input_l;
    const float* input_r;
    float* output_l;
    float* output_r;
    const float* rate;
    const float* width;
    const float* wave;

    double sample_rate;
    double lfo_phase;
} ZoomAutoPan;

static LV2_Handle instantiate(const LV2_Descriptor* descriptor, double rate, const char* bundle_path, const LV2_Feature* const* features) {
    ZoomAutoPan* self = (ZoomAutoPan*)calloc(1, sizeof(ZoomAutoPan));
    if (!self) return NULL;
    self->sample_rate = rate;
    self->lfo_phase = 0.0;
    return (LV2_Handle)self;
}

static void connect_port(LV2_Handle instance, uint32_t port, void* data) {
    ZoomAutoPan* self = (ZoomAutoPan*)instance;
    switch (port) {
        case PAN_INPUT_L:  self->input_l  = (const float*)data; break;
        case PAN_INPUT_R:  self->input_r  = (const float*)data; break;
        case PAN_OUTPUT_L: self->output_l = (float*)data;       break;
        case PAN_OUTPUT_R: self->output_r = (float*)data;       break;
        case PAN_RATE:     self->rate     = (const float*)data; break;
        case PAN_WIDTH:    self->width    = (const float*)data; break;
        case PAN_WAVE:     self->wave     = (const float*)data; break;
    }
}

static void run(LV2_Handle instance, uint32_t sample_count) {
    ZoomAutoPan* self = (ZoomAutoPan*)instance;
    
    float width_val = *(self->width) * 0.01f;
    int is_square_step = (*(self->wave) >= 0.5f);
    double phase_inc = (2.0 * M_PI * (*self->rate)) / self->sample_rate;

    for (uint32_t i = 0; i < sample_count; ++i) {
        float in_l = self->input_l[i];
        float in_r = self->input_r[i];

        self->lfo_phase += phase_inc;
        if (self->lfo_phase >= 2.0 * M_PI) self->lfo_phase -= 2.0 * M_PI;

        float pan_pos = 0.0f; // -1.0 (hard left) to +1.0 (hard right)
        
        if (is_square_step) {
            // Jagged, square stepping auto-pan profile
            pan_pos = (sinf(self->lfo_phase) >= 0.0f) ? 1.0f : -1.0f;
        } else {
            // Warm continuous rhythmic fluid sweep
            pan_pos = sinf(self->lfo_phase);
        }

        // Apply width scaling context to the crossfader parameters
        float actual_pan = pan_pos * width_val; // bound scale
        
        // Equal-power constant pan law crossfade calculation
        float angle = (actual_pan + 1.0f) * (M_PI / 4.0f);
        float pan_l = cosf(angle);
        float pan_r = sinf(angle);

        // Cross-inject stereo fields to avoid dropping audio from one channel entirely
        self->output_l[i] = (in_l * pan_l) + (in_r * (1.0f - pan_r) * 0.2f);
        self->output_r[i] = (in_r * pan_r) + (in_l * (1.0f - pan_l) * 0.2f);
    }
}

static void cleanup(LV2_Handle instance) {
    free(instance);
}

static const LV2_Descriptor descriptor = { AUTOPAN_URI, instantiate, connect_port, NULL, run, NULL, cleanup, NULL };
LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index) { return (index == 0) ? &descriptor : NULL; }
endef

define ZOOM_AUTOPAN_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<https://modaudio.com/plugins/zoom-autopan> a lv2:Plugin ; lv2:binary <zoom-autopan.so> ; rdfs:seeAlso <zoom-autopan.ttl> .
endef

define ZOOM_AUTOPAN_TTL
@prefix doap:  <http://usefulinc.com/ns/doap#> .
@prefix lv2:   <http://lv2plug.in/ns/lv2core#> .
@prefix mod:   <http://moddevices.com/ns/mod#> .
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
@prefix toggles: <http://lv2plug.in/ns/ext/port-props#> .

<https://modaudio.com/plugins/zoom-autopan>
    a lv2:PannerPlugin, lv2:Plugin ;
    doap:name "Zoom AutoPan Spatializer" ;
    mod:brand "Custom" ;
    mod:label "zoom-autopan" ;

    lv2:port [
        a lv2:AudioPort, lv2:InputPort ; lv2:index 0 ; lv2:symbol "in_l" ; lv2:name "In L" ;
    ] , [
        a lv2:AudioPort, lv2:InputPort ; lv2:index 1 ; lv2:symbol "in_r" ; lv2:name "In R" ;
    ] , [
        a lv2:AudioPort, lv2:OutputPort ; lv2:index 2 ; lv2:symbol "out_l" ; lv2:name "Out L" ;
    ] , [
        a lv2:AudioPort, lv2:OutputPort ; lv2:index 3 ; lv2:symbol "out_r" ; lv2:name "Out R" ;
    ] , [
        a lv2:InputPort, lv2:ControlPort ; lv2:index 4 ; lv2:symbol "rate" ; lv2:name "Pan Velocity Rate" ;
        lv2:default 1.5 ; lv2:minimum 0.1 ; lv2:maximum 10.0 ; units:unit units:hz ;
    ] , [
        a lv2:InputPort, lv2:ControlPort ; lv2:index 5 ; lv2:symbol "width" ; lv2:name "Stereo Width Field" ;
        lv2:default 85.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; units:unit units:pc ;
    ] , [
        a lv2:InputPort, lv2:ControlPort ; lv2:index 6 ; lv2:symbol "wave" ; lv2:name "Wave: Sine / StepSquare" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ;
        lv2:portProperty lv2:integer, toggles:hasToggle ;
    ] .
endef

export ZOOM_AUTOPAN_CPP_DATA ZOOM_AUTOPAN_MANIFEST_TTL ZOOM_AUTOPAN_TTL

define ZOOM_AUTOPAN_CONFIGURE_CMDS
	mkdir -p $(@D)/src
	mkdir -p $(@D)/zoom-autopan.lv2
	printf '%s' "$$ZOOM_AUTOPAN_CPP_DATA" > $(@D)/src/zoom-autopan.cpp
endef

define ZOOM_AUTOPAN_BUILD_CMDS
	$(TARGET_CXX) $(TARGET_CXXFLAGS) $(TARGET_CPPFLAGS) $(TARGET_LDFLAGS) -O3 -Wall -fPIC -shared \
		-I$(@D) `pkg-config --cflags lv2` $(@D)/src/zoom-autopan.cpp \
		-o $(@D)/zoom-autopan.lv2/zoom-autopan.so -lm
endef

define ZOOM_AUTOPAN_INSTALL_TARGET_CMDS
	mkdir -p $ Ferguson_PKGDIR $($(PKG)_PKGDIR)/zoom-autopan.lv2
	$(INSTALL) -m 0755 $(@D)/zoom-autopan.lv2/zoom-autopan.so $($(PKG)_PKGDIR)/zoom-autopan.lv2/
	printf '%s' "$$ZOOM_AUTOPAN_MANIFEST_TTL" > $($(PKG)_PKGDIR)/zoom-autopan.lv2/manifest.ttl
	printf '%s' "$$ZOOM_AUTOPAN_TTL"          > $($(PKG)_PKGDIR)/zoom-autopan.lv2/zoom-autopan.ttl
endef

$(eval $(generic-package))
