root/src/add-ons/media/media-add-ons/vst_host/VSTHost.cpp
/*
 * Copyright 2012, Gerasim Troeglazov (3dEyes**), 3dEyes@gmail.com.
 * All rights reserved.
 * Distributed under the terms of the MIT License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <image.h>

#include <Application.h>

#include "VSTHost.h"

static int32 VHostCallback(VSTEffect* effect, int32 opcode, int32 index,
        int32 value, void* ptr, float opt);

//Trim string
static void
TrimString(BString *string) {
        char* str = string->LockBuffer(256);
    uint32 k = 0;
    uint32 i = 0;
    for(i=0; str[i]!='\0';) {
        if (isspace(str[i])) {
            k = i;
            for(uint32 j = i; j < strlen(str) - 1; j++)
                str[j] = str[j + 1];
            str[strlen(str) - 1] = '\0';
            i = k;
        } else
            i++;
    }
    string->UnlockBuffer();
}

//VST Parameter class
VSTParameter::VSTParameter(VSTPlugin* plugin, int index)
{
        fIndex = index;
        fEffect = plugin->Effect();
        fDropList.MakeEmpty();

        char temp[256];
        //get parameter name
        temp[0] = 0;
        fEffect->dispatcher(fEffect, VST_GET_PARAM_NAME, index, 0, temp, 0);
        fName.SetTo(temp);
        TrimString(&fName);
        //get parameter label (unit)
        temp[0] = 0;
        fEffect->dispatcher(fEffect, VST_GET_PARAM_UNIT, index, 0, temp, 0);
        fUnit.SetTo(temp);
        ValidateValues(&fUnit);
        //store current value
        float val = fEffect->getParameter(fEffect, index);
        //test for minimum value
        fEffect->setParameter(fEffect, index, 0);
        temp[0] = 0;
        fEffect->dispatcher(fEffect, VST_GET_PARAM_STR, index, 0, temp, 0);
        fMinValue.SetTo(temp);
        ValidateValues(&fMinValue);
        //test for maximum value
        temp[0] = 0;
        fEffect->setParameter(fEffect, index, 1.0);
        fEffect->dispatcher(fEffect, VST_GET_PARAM_STR, index, 0, temp, 0);
        fMaxValue.SetTo(temp);
        ValidateValues(&fMaxValue);
        //test for discrete values
        char test_disp[VST_PARAM_TEST_COUNT][256];
        float test_values[VST_PARAM_TEST_COUNT];
        float delta = 1.0 / (float)VST_PARAM_TEST_COUNT;
        int test_cnt = 0;
        for(int tst_val = 0; tst_val < VST_PARAM_TEST_COUNT; tst_val++) {
                float v = (float)tst_val / (float)VST_PARAM_TEST_COUNT;

                if (tst_val >= VST_PARAM_TEST_COUNT - 1)
                        v = 1.0;

                fEffect->setParameter(fEffect, index, v);

                float new_value = fEffect->getParameter(fEffect, index);
                bool valtest = false;
                for(int i = 0; i < test_cnt; i++) {
                        if (fabs(test_values[i] - new_value) < delta) {
                                valtest = true;
                                break;
                        }
                }
                if (valtest == false) {
                        test_values[test_cnt] = new_value;
                        fEffect->dispatcher(fEffect, VST_GET_PARAM_STR, index,
                                0, test_disp[test_cnt], 0);
                        test_cnt++;
                }
        }

        //restore value
        fEffect->setParameter(fEffect, index, val);

        //detect param type
        if (test_cnt == 2) {
                fType = VST_PARAM_CHECKBOX;

                DropListValue* min_item = new DropListValue();
                min_item->Value = 0.0;
                min_item->Index = 0;
                min_item->Name = fMinValue;
                fDropList.AddItem(min_item);

                DropListValue* max_item = new DropListValue();
                max_item->Value = 1.0;
                max_item->Index = 1;
                max_item->Name = fMaxValue;
                fDropList.AddItem(max_item);
        } else if (test_cnt > 2 && test_cnt < VST_PARAM_TEST_COUNT / 2) {
                fType = VST_PARAM_DROPLIST;

                for(int i = 0; i < test_cnt; i++) {
                        DropListValue* item = new DropListValue();
                        item->Value = test_values[i];
                        item->Index = i;
                        item->Name = test_disp[i];
                        fDropList.AddItem(item);
                }
        } else {
                fType = VST_PARAM_SLIDER;
        }
        fChanged = 0LL;
}

VSTParameter::~VSTParameter()
{
}

BString*
VSTParameter::ValidateValues(BString* string)
{
        if (string->Length() == 0)
                return string;

        bool isNum = true;

        const char *ptr = string->String();
        for(; *ptr!=0; ptr++) {
                char ch = *ptr;
                if (!((ch >= '0' && ch <= '9') || ch == '.' || ch == '-')) {
                        isNum = false;
                        break;
                }
        }

        if (isNum) {
                float val = atof(string->String());

                if (val <= -pow(2, 31)) {
                        string->SetTo("-∞");
                } else if (val >= pow(2, 31)) {
                        string->SetTo("∞");
                } else {
                        char temp[256];
                        sprintf(temp, "%g", val);
                        string->SetTo(temp);
                }
        } else {
                TrimString(string);
                if (*string == "oo" || *string == "inf")
                        string->SetTo("∞");
                if (*string == "-oo" || *string == "-inf")
                        string->SetTo("-∞");

        }
        return string;
}

int
VSTParameter::ListCount(void)
{
        return fDropList.CountItems();
}

DropListValue*
VSTParameter::ListItemAt(int index)
{
        DropListValue* item = NULL;
        if (index >= 0 && index < fDropList.CountItems())
                item = (DropListValue*)fDropList.ItemAt(index);
        return item;
}


float
VSTParameter::Value()
{
        float value = fEffect->getParameter(fEffect, fIndex);
        if (fType == VST_PARAM_DROPLIST) {
                //scan for near value
                int     min_index = 0;
                float min_delta = 1.0;
                for(int i = 0; i < fDropList.CountItems(); i++) {
                        DropListValue* item = (DropListValue*)fDropList.ItemAt(i);
                        float delta     = fabs(item->Value - value);
                        if (delta <= min_delta) {
                                min_delta = delta;
                                min_index = i;
                        }
                }
                value = min_index;
        }
        fLastValue = value;
        return value;
}

void
VSTParameter::SetValue(float value)
{
        if (value == fLastValue)
                return;

        if (fType == VST_PARAM_DROPLIST) {
                //take value by index
                int index = (int)vstround(value);
                if (index >= 0 && index < fDropList.CountItems()) {
                        DropListValue *item = (DropListValue*)fDropList.ItemAt(index);
                        value = item->Value;
                        fLastValue = index;
                } else {
                        return;
                }
        } else {
                fLastValue = value;
        }
        fChanged = system_time();
        fEffect->setParameter(fEffect, fIndex, value);
}

bigtime_t
VSTParameter::LastChangeTime(void)
{
        return fChanged;
}

const char*
VSTParameter::MinimumValue(void)
{
        return fMinValue.String();
}

const char*
VSTParameter::MaximumValue(void)
{
        return fMaxValue.String();
}

const char*
VSTParameter::Unit(void)
{
        return fUnit.String();
}

int
VSTParameter::Index(void)
{
        return fIndex;
}

int
VSTParameter::Type(void)
{
        return fType;
}

const char*
VSTParameter::Name(void)
{
        return fName.String();
}

//VST Plugin class
VSTPlugin::VSTPlugin()
{
        fActive = false;
        fEffect = NULL;
        VSTMainProc = NULL;
        fInputChannels = 0;
        fOutputChannels = 0;
        fSampleRate = 44100.f;
        fBlockSize = 0;
        inputs = NULL;
        outputs = NULL;
        fParameters.MakeEmpty();
}

VSTPlugin::~VSTPlugin()
{
        fParameters.MakeEmpty();
        UnLoadModule();
}

int
VSTPlugin::LoadModule(const char *path)
{
        char effectName[256] = {0};
        char vendorString[256] = {0};
        char productString[256] = {0};

        if (fActive)
                return VST_ERR_ALREADY_LOADED;

        fPath = BPath(path);

        fModule = load_add_on(path);
        if (fModule <= 0)
                return VST_ERR_NOT_LOADED;

        if (get_image_symbol(fModule, "main_plugin", B_SYMBOL_TYPE_TEXT,
                        (void**)&VSTMainProc) != B_OK) {
                unload_add_on(fModule);
                return VST_ERR_NO_MAINPROC;
        }

        fEffect = VSTMainProc(VHostCallback);
        if (fEffect==NULL) {
                unload_add_on(fModule);
                return VST_ERR_NOT_LOADED;
        }

        fEffect->dispatcher(fEffect, VST_OPEN, 0, 0, 0, 0);

        fEffect->dispatcher(fEffect, VST_GET_EFFECT_NAME, 0, 0, effectName, 0);
        fEffectName.SetTo(effectName);
        TrimString(&fEffectName);

        fModuleName.SetTo("VST:");
        fModuleName.Append(fPath.Leaf());

        fEffect->dispatcher(fEffect, VST_GET_VENDOR_STR, 0, 0, vendorString, 0);
        fVendorString.SetTo(vendorString);
        TrimString(&fVendorString);

        fEffect->dispatcher(fEffect, VST_GET_PRODUCT_STR, 0, 0, productString, 0);
        fProductString.SetTo(productString);
        TrimString(&fProductString);

        fInputChannels = fEffect->numInputs;
        fOutputChannels = fEffect->numOutputs;

        for(int i=0; i < fEffect->numParams; i++) {
                VSTParameter *param = new VSTParameter(this, i);
                fParameters.AddItem(param);
        }

        fEffect->dispatcher(fEffect, VST_STATE_CHANGED, 0, 1, 0, 0);

        ReAllocBuffers();

        fActive = true;
        return B_OK;
}

int
VSTPlugin::UnLoadModule(void)
{
        if (!fActive || fModule <= 0)
                return VST_ERR_NOT_LOADED;

        fEffect->dispatcher(fEffect, VST_STATE_CHANGED, 0, 0, 0, 0);
        fEffect->dispatcher(fEffect, VST_CLOSE, 0, 0, 0, 0);

        unload_add_on(fModule);

        return B_OK;
}

int
VSTPlugin::Channels(int mode)
{
        switch(mode) {
                case VST_INPUT_CHANNELS:
                        return fInputChannels;
                case VST_OUTPUT_CHANNELS:
                        return fOutputChannels;
                default:
                        return 0;
        }
}

int
VSTPlugin::SetSampleRate(float rate)
{
        fSampleRate = rate;
        fEffect->dispatcher(fEffect, VST_SET_SAMPLE_RATE, 0, 0, 0, rate);
        return B_OK;
}

float
VSTPlugin::SampleRate(void)
{
        return fSampleRate;
}

int
VSTPlugin::SetBlockSize(size_t size)
{
        fBlockSize = size;
        fEffect->dispatcher(fEffect, VST_SET_BLOCK_SIZE, 0, size, 0, 0);
        ReAllocBuffers();
        return B_OK;
}

const char*
VSTPlugin::Path(void)
{
        return fPath.Path();
}

int
VSTPlugin::ReAllocBuffers(void)
{
        if (inputs != NULL) {
                for(int32 i = 0; i < fInputChannels; i++)
                        delete inputs[i];
        }

        if (outputs != NULL) {
                for(int32 i = 0; i < fOutputChannels; i++)
                        delete outputs[i];
        }

        if (fInputChannels > 0) {
                inputs = new float*[fInputChannels];
                for(int32 i = 0; i < fInputChannels; i++)       {
                        inputs[i] = new float[fBlockSize];
                        memset(inputs[i], 0, fBlockSize * sizeof(float));
                }
        }

        if (fOutputChannels > 0) {
                outputs = new float*[fOutputChannels];
                for(int32_t i = 0; i < fOutputChannels; i++) {
                        outputs[i] = new float[fBlockSize];
                        memset (outputs[i], 0, fBlockSize * sizeof(float));
                }
        }
        return B_OK;
}

size_t
VSTPlugin::BlockSize(void)
{
        return fBlockSize;
}

int
VSTPlugin::ParametersCount(void)
{
        return fParameters.CountItems();
}

VSTParameter*
VSTPlugin::Parameter(int index)
{
        VSTParameter* param = NULL;

        if (index >= 0 && index < fParameters.CountItems())
                param = (VSTParameter*)fParameters.ItemAt(index);

        return param;
}

VSTEffect*
VSTPlugin::Effect(void)
{
        return fEffect;
}

const char*
VSTPlugin::EffectName(void)
{
        return fEffectName.String();
}

const char*
VSTPlugin::ModuleName(void)
{
        return fModuleName.String();
}

const char*
VSTPlugin::Vendor(void)
{
        return fVendorString.String();
}

const char*
VSTPlugin::Product(void)
{
        return fProductString.String();
}


void
VSTPlugin::Process(float *buffer, int samples, int channels)
{
        //todo: full channels remapping needed
        float* src = buffer;

        if (channels == fInputChannels) { //channel to channel
                for(int j = 0; j < samples; j++) {
                        for(int c = 0; c < fInputChannels; c++)
                                inputs[c][j] = *src++;
                }
        } else if ( channels == 1) {    //from mone to multichannel
                for(int j = 0; j < samples; j++, src++) {
                        for(int c = 0; c < fInputChannels; c++)
                                inputs[c][j] = *src;
                }
        }

        fEffect->processReplacing(fEffect, inputs, outputs, fBlockSize);

        float* dst = buffer;

        if (channels == fOutputChannels) { //channel to channel
                for(int j = 0; j < samples; j++) {
                        for(int c = 0; c < fOutputChannels; c++)
                                *dst++ = outputs[c][j];
                }
        } else if (channels == 1) {  //from multichannel to mono
                for(int j = 0; j < samples; j++, dst++) {
                        float mix = 0;
                        for(int c = 0; c < fOutputChannels; c++)
                                mix += outputs[c][j];
                        *dst = mix / (float)fOutputChannels;
                }
        }
}

static int32
VHostCallback(VSTEffect* effect, int32 opcode, int32 index, int32 value,
        void* ptr, float opt)
{
        intptr_t result = 0;

        switch(opcode)
        {
                case VST_MASTER_PRODUCT:
                        if (ptr) {
                                strcpy((char*)ptr, "VSTHost Media AddOn");
                                result = 1;
                        }
                        break;
                case VST_MASTER_VERSION :
                        result = 2300;
                        break;
        }

        return result;
}