/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <stdlib.h>
#include "cras_dsp_module.h"
#include "drc.h"
#include "dsp_util.h"
#include "eq.h"
#include "eq2.h"

/*
 *  empty module functions (for source and sink)
 */
static int empty_instantiate(struct dsp_module *module,
			     unsigned long sample_rate)
{
	return 0;
}

static void empty_connect_port(struct dsp_module *module, unsigned long port,
			       float *data_location) {}

static int empty_get_delay(struct dsp_module *module)
{
	return 0;
}

static void empty_run(struct dsp_module *module, unsigned long sample_count) {}

static void empty_deinstantiate(struct dsp_module *module) {}

static void empty_free_module(struct dsp_module *module)
{
	free(module);
}

static int empty_get_properties(struct dsp_module *module) { return 0; }

static void empty_init_module(struct dsp_module *module)
{
	module->instantiate = &empty_instantiate;
	module->connect_port = &empty_connect_port;
	module->get_delay = &empty_get_delay;
	module->run = &empty_run;
	module->deinstantiate = &empty_deinstantiate;
	module->free_module = &empty_free_module;
	module->get_properties = &empty_get_properties;
}

/*
 *  invert_lr module functions
 */
static int invert_lr_instantiate(struct dsp_module *module,
				 unsigned long sample_rate)
{
	module->data = calloc(4, sizeof(float*));
	return 0;
}

static void invert_lr_connect_port(struct dsp_module *module,
				   unsigned long port, float *data_location)
{
	float **ports;
	ports = (float **)module->data;
	ports[port] = data_location;
}

static void invert_lr_run(struct dsp_module *module,
			  unsigned long sample_count)
{
	size_t i;
	float **ports = (float **)module->data;

	for (i = 0; i < sample_count; i++) {
		ports[2][i] = -ports[0][i];
		ports[3][i] = ports[1][i];
	}
}

static void invert_lr_deinstantiate(struct dsp_module *module)
{
	free(module->data);
}

static void invert_lr_init_module(struct dsp_module *module)
{
	module->instantiate = &invert_lr_instantiate;
	module->connect_port = &invert_lr_connect_port;
	module->get_delay = &empty_get_delay;
	module->run = &invert_lr_run;
	module->deinstantiate = &invert_lr_deinstantiate;
	module->free_module = &empty_free_module;
	module->get_properties = &empty_get_properties;
}

/*
 *  mix_stereo module functions
 */
static int mix_stereo_instantiate(struct dsp_module *module,
				  unsigned long sample_rate)
{
	module->data = calloc(4, sizeof(float*));
	return 0;
}

static void mix_stereo_connect_port(struct dsp_module *module,
				    unsigned long port, float *data_location)
{
	float **ports;
	ports = (float **)module->data;
	ports[port] = data_location;
}

static void mix_stereo_run(struct dsp_module *module,
			   unsigned long sample_count)
{
	size_t i;
	float tmp;
	float **ports = (float **)module->data;

	for (i = 0; i < sample_count; i++) {
		tmp = ports[0][i] + ports[1][i];
		ports[2][i] = tmp;
		ports[3][i] = tmp;
	}
}

static void mix_stereo_deinstantiate(struct dsp_module *module)
{
	free(module->data);
}

static void mix_stereo_init_module(struct dsp_module *module)
{
	module->instantiate = &mix_stereo_instantiate;
	module->connect_port = &mix_stereo_connect_port;
	module->get_delay = &empty_get_delay;
	module->run = &mix_stereo_run;
	module->deinstantiate = &mix_stereo_deinstantiate;
	module->free_module = &empty_free_module;
	module->get_properties = &empty_get_properties;
}

/*
 *  eq module functions
 */
struct eq_data {
	int sample_rate;
	struct eq *eq;  /* Initialized in the first call of eq_run() */

	/* One port for input, one for output, and 4 parameters per eq */
	float *ports[2 + MAX_BIQUADS_PER_EQ * 4];
};

static int eq_instantiate(struct dsp_module *module, unsigned long sample_rate)
{
	struct eq_data *data;

	module->data = calloc(1, sizeof(struct eq_data));
	data = (struct eq_data *) module->data;
	data->sample_rate = (int) sample_rate;
	return 0;
}

static void eq_connect_port(struct dsp_module *module,
			    unsigned long port, float *data_location)
{
	struct eq_data *data = (struct eq_data *) module->data;
	data->ports[port] = data_location;
}

static void eq_run(struct dsp_module *module, unsigned long sample_count)
{
	struct eq_data *data = (struct eq_data *) module->data;
	if (!data->eq) {
		float nyquist = data->sample_rate / 2;
		int i;

		data->eq = eq_new();
		for (i = 2; i < 2 + MAX_BIQUADS_PER_EQ * 4; i += 4) {
			if (!data->ports[i])
				break;
			int type = (int) *data->ports[i];
			float freq = *data->ports[i+1];
			float Q = *data->ports[i+2];
			float gain = *data->ports[i+3];
			eq_append_biquad(data->eq, type, freq / nyquist, Q,
					 gain);
		}
	}
	if (data->ports[0] != data->ports[1])
		memcpy(data->ports[1], data->ports[0],
		       sizeof(float) * sample_count);
	eq_process(data->eq, data->ports[1], (int) sample_count);
}

static void eq_deinstantiate(struct dsp_module *module)
{
	struct eq_data *data = (struct eq_data *) module->data;
	if (data->eq)
		eq_free(data->eq);
	free(data);
}

static void eq_init_module(struct dsp_module *module)
{
	module->instantiate = &eq_instantiate;
	module->connect_port = &eq_connect_port;
	module->get_delay = &empty_get_delay;
	module->run = &eq_run;
	module->deinstantiate = &eq_deinstantiate;
	module->free_module = &empty_free_module;
	module->get_properties = &empty_get_properties;
}

/*
 *  eq2 module functions
 */
struct eq2_data {
	int sample_rate;
	struct eq2 *eq2;  /* Initialized in the first call of eq2_run() */

	/* Two ports for input, two for output, and 8 parameters per eq pair */
	float *ports[4 + MAX_BIQUADS_PER_EQ2 * 8];
};

static int eq2_instantiate(struct dsp_module *module, unsigned long sample_rate)
{
	struct eq2_data *data;

	module->data = calloc(1, sizeof(struct eq2_data));
	data = (struct eq2_data *) module->data;
	data->sample_rate = (int) sample_rate;
	return 0;
}

static void eq2_connect_port(struct dsp_module *module,
			     unsigned long port, float *data_location)
{
	struct eq2_data *data = (struct eq2_data *) module->data;
	data->ports[port] = data_location;
}

static void eq2_run(struct dsp_module *module, unsigned long sample_count)
{
	struct eq2_data *data = (struct eq2_data *) module->data;
	if (!data->eq2) {
		float nyquist = data->sample_rate / 2;
		int i, channel;

		data->eq2 = eq2_new();
		for (i = 4; i < 4 + MAX_BIQUADS_PER_EQ2 * 8; i += 8) {
			if (!data->ports[i])
				break;
			for (channel = 0; channel < 2; channel++) {
				int k = i + channel * 4;
				int type = (int) *data->ports[k];
				float freq = *data->ports[k+1];
				float Q = *data->ports[k+2];
				float gain = *data->ports[k+3];
				eq2_append_biquad(data->eq2, channel, type,
						  freq / nyquist, Q, gain);
			}
		}
	}


	if (data->ports[0] != data->ports[2])
		memcpy(data->ports[2], data->ports[0],
		       sizeof(float) * sample_count);
	if (data->ports[3] != data->ports[1])
		memcpy(data->ports[3], data->ports[1],
		       sizeof(float) * sample_count);

	eq2_process(data->eq2, data->ports[2], data->ports[3],
		    (int) sample_count);
}

static void eq2_deinstantiate(struct dsp_module *module)
{
	struct eq2_data *data = (struct eq2_data *) module->data;
	if (data->eq2)
		eq2_free(data->eq2);
	free(data);
}

static void eq2_init_module(struct dsp_module *module)
{
	module->instantiate = &eq2_instantiate;
	module->connect_port = &eq2_connect_port;
	module->get_delay = &empty_get_delay;
	module->run = &eq2_run;
	module->deinstantiate = &eq2_deinstantiate;
	module->free_module = &empty_free_module;
	module->get_properties = &empty_get_properties;
}

/*
 *  drc module functions
 */
struct drc_data {
	int sample_rate;
	struct drc *drc;  /* Initialized in the first call of drc_run() */

	/* Two ports for input, two for output, one for disable_emphasis,
	 * and 8 parameters each band */
	float *ports[4 + 1 + 8 * 3];
};

static int drc_instantiate(struct dsp_module *module, unsigned long sample_rate)
{
	struct drc_data *data;

	module->data = calloc(1, sizeof(struct drc_data));
	data = (struct drc_data *) module->data;
	data->sample_rate = (int) sample_rate;
	return 0;
}

static void drc_connect_port(struct dsp_module *module,
			    unsigned long port, float *data_location)
{
	struct drc_data *data = (struct drc_data *) module->data;
	data->ports[port] = data_location;
}

static int drc_get_delay(struct dsp_module *module)
{
	struct drc_data *data = (struct drc_data *) module->data;
	return DRC_DEFAULT_PRE_DELAY * data->sample_rate;
}

static void drc_run(struct dsp_module *module, unsigned long sample_count)
{
	struct drc_data *data = (struct drc_data *) module->data;
	if (!data->drc) {
		int i;
		float nyquist = data->sample_rate / 2;
		struct drc *drc = drc_new(data->sample_rate);

		data->drc = drc;
		drc->emphasis_disabled = (int) *data->ports[4];
		for (i = 0; i < 3; i++) {
			int k = 5 + i * 8;
			float f = *data->ports[k];
			float enable = *data->ports[k+1];
			float threshold = *data->ports[k+2];
			float knee = *data->ports[k+3];
			float ratio = *data->ports[k+4];
			float attack = *data->ports[k+5];
			float release = *data->ports[k+6];
			float boost = *data->ports[k+7];
			drc_set_param(drc, i, PARAM_CROSSOVER_LOWER_FREQ,
				      f / nyquist);
			drc_set_param(drc, i, PARAM_ENABLED, enable);
			drc_set_param(drc, i, PARAM_THRESHOLD, threshold);
			drc_set_param(drc, i, PARAM_KNEE, knee);
			drc_set_param(drc, i, PARAM_RATIO, ratio);
			drc_set_param(drc, i, PARAM_ATTACK, attack);
			drc_set_param(drc, i, PARAM_RELEASE, release);
			drc_set_param(drc, i, PARAM_POST_GAIN, boost);
		}
		drc_init(drc);
	}
	if (data->ports[0] != data->ports[2])
		memcpy(data->ports[2], data->ports[0],
		       sizeof(float) * sample_count);
	if (data->ports[1] != data->ports[3])
		memcpy(data->ports[3], data->ports[1],
		       sizeof(float) * sample_count);

	drc_process(data->drc, &data->ports[2], (int) sample_count);
}

static void drc_deinstantiate(struct dsp_module *module)
{
	struct drc_data *data = (struct drc_data *) module->data;
	if (data->drc)
		drc_free(data->drc);
	free(data);
}

static void drc_init_module(struct dsp_module *module)
{
	module->instantiate = &drc_instantiate;
	module->connect_port = &drc_connect_port;
	module->get_delay = &drc_get_delay;
	module->run = &drc_run;
	module->deinstantiate = &drc_deinstantiate;
	module->free_module = &empty_free_module;
	module->get_properties = &empty_get_properties;
}

/*
 *  builtin module dispatcher
 */
struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin)
{
	struct dsp_module *module;
	if (strcmp(plugin->library, "builtin") != 0)
		return NULL;

	module = calloc(1, sizeof(struct dsp_module));

	if (strcmp(plugin->label, "mix_stereo") == 0) {
		mix_stereo_init_module(module);
	} else if (strcmp(plugin->label, "invert_lr") == 0) {
		invert_lr_init_module(module);
	} else if (strcmp(plugin->label, "eq") == 0) {
		eq_init_module(module);
	} else if (strcmp(plugin->label, "eq2") == 0) {
		eq2_init_module(module);
	} else if (strcmp(plugin->label, "drc") == 0) {
		drc_init_module(module);
	} else {
		empty_init_module(module);
	}

	return module;
}