/* abGate - LV2 Noise Gate Plugin
 *
 * Copyright 2011 Antanas Bružas
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdlib.h>
#include <lv2.h>
#include <cmath>

#include "gate_const.h"

#define CLOSED 1
#define ATTACK 2
#define OPENED 3
#define DECAY 4

static LV2_Descriptor *gateDescriptor = NULL;

class Gate {
	public:
		Gate() {
			state = CLOSED;
			gate = 0;
			holding = 0;
			input_db = 0;
		}
		virtual ~Gate();

		bool bypass;
		float *switch_button, *threshold, *attack, *hold, *decay, *range, input_db, sample_rate, gate, range_coef, attack_coef, decay_coef, *output;
		const float *input;
		int state, holding;
};

static LV2_Handle instantiateGate(const _LV2_Descriptor *descriptor, double s_rate, const char *path, const LV2_Feature * const* features) {
	Gate *plugin_data = (Gate *)malloc(sizeof(Gate));
	plugin_data->sample_rate = s_rate;
	return (LV2_Handle)plugin_data;
}

static void connectPortGate(LV2_Handle instance, uint32_t port, void *data) {
	Gate *plugin = (Gate *)instance;

	switch (port) {
		case p_switch:
			plugin->switch_button = (float*)data;
			break;
		case p_threshold:
			plugin->threshold = (float*)data;
			break;
		case p_attack:
			plugin->attack = (float*)data;
			break;
		case p_hold:
			plugin->hold = (float*)data;
			break;
		case p_decay:
			plugin->decay = (float*)data;
			break;
		case p_gaterange:
			plugin->range = (float*)data;
			break;
		case p_input:
			plugin->input = (const float*)data;
			break;
		case p_output:
			plugin->output = (float*)data;
			break;
	}
}

static void runGate(LV2_Handle instance, uint32_t sample_count) {

	Gate *plugin_data = (Gate *)instance;

	float * const output = plugin_data->output;
	const float * const input = plugin_data->input;

	// Checking bypass state
	float switch_button = *(plugin_data->switch_button);
	switch_button = switch_button < 0 ? 0 : switch_button;
	switch_button = switch_button > 1 ? 1 : switch_button;

	bool bypass = switch_button > 0 ? true : false;
	if (bypass != false) {

		// Getting port values
		const float threshold = *(plugin_data->threshold);
		const float attack = *(plugin_data->attack);
		const float hold = *(plugin_data->hold);
		const float decay = *(plugin_data->decay);
		const float range = *(plugin_data->range);

		float sample_rate = plugin_data->sample_rate;
		int state = plugin_data->state;
		float gate = plugin_data->gate;
		int holding = plugin_data->holding;
		float input_db = plugin_data->input_db;

		float range_coef = range > -90 ? pow(10, range * 0.05) : 0;
		float attack_coef = 1000 / (attack * sample_rate);
		float decay_coef = 1000 / (decay * sample_rate);
		
		for (uint32_t i = 0; i < sample_count; ++i) {

			// Counting input dB
          		input_db = 20 * log10(fabs(input[i]));
			switch (state){
				case CLOSED:
					if (input_db >= threshold) { state = ATTACK; }
					break;
				case ATTACK:
					// attacking :)
			 		gate += attack_coef;
					if (gate >= 1) {
						gate = 1;
						state = OPENED;
						holding = round(hold * sample_rate * 0.001);
					}
			    		break;
			  	case OPENED:
			    		if (holding <= 0) {
						if (input_db < threshold) { state = DECAY; }
					}
					else { holding--; }
			    		break;
			  	case DECAY:
			    		gate -= decay_coef;
					if (input_db >= threshold) { state = ATTACK; }
					else if (gate <= 0) {
						gate = 0;
						state = CLOSED;
					}
			    		break;
			  	default:
			    		state = CLOSED;
			}
			output[i] = input[i] * (range_coef * (1 - gate) + gate);
		}	
		plugin_data->input_db = input_db;
      		plugin_data->gate = gate;
      		plugin_data->state = state;
      		plugin_data->holding = holding;	
	}
	else {
		// Bypassing
		for (uint32_t i = 0; i < sample_count; ++i) { output[i] = input[i]; }
	}
}

static void cleanupGate(LV2_Handle instance) {
	free(instance);
}

static void init() {

	gateDescriptor = (LV2_Descriptor *)malloc(sizeof(LV2_Descriptor));
	gateDescriptor->URI = p_uri;
	gateDescriptor->instantiate = instantiateGate;
	gateDescriptor->connect_port = connectPortGate;
	gateDescriptor->activate = NULL;
	gateDescriptor->run = runGate;
	gateDescriptor->deactivate = NULL;
	gateDescriptor->cleanup = cleanupGate;
	gateDescriptor->extension_data = NULL;
}

LV2_SYMBOL_EXPORT
const LV2_Descriptor *lv2_descriptor(uint32_t index) {

	if (!gateDescriptor) { init(); }

	switch (index) {
		case 0:
			return gateDescriptor;
		default:
			return NULL;
	}
}
