################################################################################
#
# ZOOM-BMONOSYNTH (Zoom Bass Monophonic Synthetic Tracking Engine for MOD Dwarf)
# Standalone True Stereo Bass Synthesis Specification & Source Package
#
################################################################################

ZOOM_BMONOSYNTH_VERSION = 1.0.0
ZOOM_BMONOSYNTH_SITE_METHOD = local
ZOOM_BMONOSYNTH_SITE = $(_ZOOM_BMONOSYNTH_SITE_ENV)
ZOOM_BMONOSYNTH_SOURCE =

ZOOM_BMONOSYNTH_BUNDLES = zoom-bmonosynth.lv2

define ZOOM_BMONOSYNTH_CPP_DATA
#include <lv2/core/lv2.h>
#include <cmath>
#include <stdint.h>
#include <stdlib.h>

#define BMONOSYNTH_URI "https://modaudio.com/plugins/zoom-bmonosynth"

enum Ports {
    SYN_INPUT_L  = 0,
    SYN_INPUT_R  = 1,
    SYN_OUTPUT_L = 2,
    SYN_OUTPUT_R = 3,
    SYN_SENS     = 4,
    SYN_DECAY    = 5,
    SYN_WAVE     = 6,
    SYN_MIX      = 7
};

typedef struct {
    const float* input_l;
    const float* input_r;
    float* output_l;
    float* output_r;
    const float* sens;
    const float* decay;
    const float* wave;
    const float* mix;

    double sample_rate;
    float synth_phase_l;
    float synth_phase_r;
    float lp_env_l;
    float lp_env_r;
} ZoomBassMonoSynth;

static LV2_Handle instantiate(const LV2_Descriptor* descriptor, double rate, const char* bundle_path, const LV2_Feature* const* features) {
    ZoomBassMonoSynth* self = (ZoomBassMonoSynth*)calloc(1, sizeof(ZoomBassMonoSynth));
    if (!self) return NULL;
    self->sample_rate = rate;
    return (LV2_Handle)self;
}

static void connect_port(LV2_Handle instance, uint32_t port, void* data) {
    ZoomBassMonoSynth* self = (ZoomBassMonoSynth*)instance;
    switch (port) {
        case SYN_INPUT_L:  self->input_l  = (const float*)data; break;
        case SYN_INPUT_R:  self->input_r  = (const float*)data; break;
        case SYN_OUTPUT_L: self->output_l = (float*)data;       break;
        case SYN_OUTPUT_R: self->output_r = (float*)data;       break;
        case SYN_SENS:     self->sens     = (const float*)data; break;
        case SYN_DECAY:    self->decay    = (const float*)data; break;
        case SYN_WAVE:     self->wave     = (const float*)data; break;
        case SYN_MIX:      self->mix      = (const float*)data; break;
    }
}

static void run(LV2_Handle instance, uint32_t sample_count) {
    ZoomBassMonoSynth* self = (ZoomBassMonoSynth*)instance;
    
    float mix_val = *(self->mix) * 0.01f;
    float sensitivity = *(self->sens) * 0.01f * 4.0f;
    int is_saw = (*(self->wave) >= 0.5f);
    
    float decay_param = *(self->decay) * 0.01f;
    float env_alpha = 0.0005f + (1.0f - decay_param) * 0.03f;

    for (uint32_t i = 0; i < sample_count; ++i) {
        float in_l = self->input_l[i];
        float in_r = self->input_r[i];

        self->lp_env_l += env_alpha * ((fabsf(in_l) * sensitivity) - self->lp_env_l);
        self->lp_env_r += env_alpha * ((fabsf(in_r) * sensitivity) - self->lp_env_r);

        float freq_l = 40.0f + (self->lp_env_l * 180.0f);
        float freq_r = 40.0f + (self->lp_env_r * 180.0f);

        self->synth_phase_l += (freq_l / (float)self->sample_rate);
        if (self->synth_phase_l >= 1.0f) self->synth_phase_l -= 1.0f;

        self->synth_phase_r += (freq_r / (float)self->sample_rate);
        if (self->synth_phase_r >= 1.0f) self->synth_phase_r -= 1.0f;

        float wet_l = 0.0f;
        float wet_r = 0.0f;

        if (is_saw) {
            wet_l = (self->synth_phase_l * 2.0f) - 1.0f;
            wet_r = (self->synth_phase_r * 2.0f) - 1.0f;
        } else {
            wet_l = (self->synth_phase_l > 0.5f) ? 0.4f : -0.4f;
            wet_r = (self->synth_phase_r > 0.5f) ? 0.4f : -0.4f;
        }

        wet_l *= (self->lp_env_l * 1.8f);
        wet_r *= (self->lp_env_r * 1.8f);
        
        wet_l = (wet_l * 0.7f) + (in_l * 0.4f);
        wet_r = (wet_r * 0.7f) + (in_r * 0.4f);

        self->output_l[i] = (wet_l * mix_val) + (in_l * (1.0f - mix_val));
        self->output_r[i] = (wet_r * mix_val) + (in_r * (1.0f - mix_val));
    }
}

static void cleanup(LV2_Handle instance) { free(instance); }
static const LV2_Descriptor descriptor = { BMONOSYNTH_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_BMONOSYNTH_MANIFEST_TTL
@prefix lv2:  <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<https://modaudio.com/plugins/zoom-bmonosynth> a lv2:Plugin ; lv2:binary <zoom-bmonosynth.so> ; rdfs:seeAlso <zoom-bmonosynth.ttl> .
endef

define ZOOM_BMONOSYNTH_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-bmonosynth>
    a lv2:SynthPlugin, lv2:Plugin ;
    doap:name "Zoom Bass MonoSynth" ;
    mod:brand "Custom" ;
    mod:label "zoom-bmonosynth" ;

    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 "sens" ; lv2:name "Tracking Sens" ;
        lv2:default 50.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; units:unit units:pc ;
    ] , [
        a lv2:InputPort, lv2:ControlPort ; lv2:index 5 ; lv2:symbol "decay" ; lv2:name "Synth Decay" ;
        lv2:default 50.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: Square / Saw" ;
        lv2:default 0.0 ; lv2:minimum 0.0 ; lv2:maximum 1.0 ;
        lv2:portProperty lv2:integer, toggles:hasToggle ;
    ] , [
        a lv2:InputPort, lv2:ControlPort ; lv2:index 7 ; lv2:symbol "mix" ; lv2:name "Mix" ;
        lv2:default 60.0 ; lv2:minimum 0.0 ; lv2:maximum 100.0 ; units:unit units:pc ;
    ] .
endef

export ZOOM_BMONOSYNTH_CPP_DATA ZOOM_BMONOSYNTH_MANIFEST_TTL ZOOM_BMONOSYNTH_TTL

define ZOOM_BMONOSYNTH_CONFIGURE_CMDS
	mkdir -p $(@D)/src
	mkdir -p $(@D)/zoom-bmonosynth.lv2
	printf '%s' "$$ZOOM_BMONOSYNTH_CPP_DATA" > $(@D)/src/zoom-bmonosynth.cpp
endef

define ZOOM_BMONOSYNTH_BUILD_CMDS
	$(TARGET_CXX) $(TARGET_CXXFLAGS) $(TARGET_CPPFLAGS) $(TARGET_LDFLAGS) -O3 -Wall -fPIC -shared \
		-I$(@D) `pkg-config --cflags lv2` $(@D)/src/zoom-bmonosynth.cpp \
		-o $(@D)/zoom-bmonosynth.lv2/zoom-bmonosynth.so -lm
endef

define ZOOM_BMONOSYNTH_INSTALL_TARGET_CMDS
	mkdir -p $(TARGET_DIR)/usr/lib/lv2/zoom-bmonosynth.lv2
	$(INSTALL) -m 0755 $(@D)/zoom-bmonosynth.lv2/zoom-bmonosynth.so $(TARGET_DIR)/usr/lib/lv2/zoom-bmonosynth.lv2/
	printf '%s' "$$ZOOM_BMONOSYNTH_MANIFEST_TTL" > $(TARGET_DIR)/usr/lib/lv2/zoom-bmonosynth.lv2/manifest.ttl
	printf '%s' "$$ZOOM_BMONOSYNTH_TTL"          > $(TARGET_DIR)/usr/lib/lv2/zoom-bmonosynth.lv2/zoom-bmonosynth.ttl
endef

$(eval $(generic-package))
