
#include "FFB.hpp"
#include "dsp/pitch.hpp"

void FFB::Engine::sampleRateChange() {
	float sr = APP->engine->getSampleRate();
	for (int i = 0; i < 14; i++) {
		_slews[i].setParams(sr, 1.0f, 1.0f);
	}
	configureBands(sr, _semitonesOffset);
}

void FFB::Engine::setSemitonesOffset(float semitonesOffset) {
	if (_semitonesOffset != semitonesOffset) {
		_semitonesOffset = semitonesOffset;
		configureBands(APP->engine->getSampleRate(), _semitonesOffset);
	}
}

void FFB::Engine::configureBands(float sr, float semitonesOffset) {
	_lowPass.setParams(
		sr,
		MultimodeFilter::BUTTERWORTH_TYPE,
		12.0f,
		MultimodeFilter::LOWPASS_MODE,
		bandFrequency(0, semitonesOffset),
		0.0f
	);
	for (int i = 0; i < 12; ++i) {
		_bandPasses[i].setParams(
			sr,
			bandFrequency(i + 1, semitonesOffset),
			0.22f / MultimodeFilter::maxBWPitch,
			MultimodeFilter::PITCH_BANDWIDTH_MODE
		);
	}
	_highPass.setParams(
		sr,
		MultimodeFilter::BUTTERWORTH_TYPE,
		12.0f,
		MultimodeFilter::HIGHPASS_MODE,
		bandFrequency(13, semitonesOffset),
		0.0f
	);
}

float FFB::Engine::bandFrequency(int i, float semitonesOffset) {
	static const float fs[14] = {
		95.0f,
		125.0f,
		175.0f,
		250.0f,
		350.0f,
		500.0f,
		700.0f,
		1000.0f,
		1400.0f,
		2000.0f,
		2800.0f,
		4000.0f,
		5600.0f,
		6900.0f
	};
	return semitoneToFrequency(frequencyToSemitone(fs[i]) + semitonesOffset);
}

void FFB::sampleRateChange() {
	for (int c = 0; c < _channels; ++c) {
		_engines[c]->sampleRateChange();
	}
}

bool FFB::active() {
	return outputs[ALL_OUTPUT].isConnected() || outputs[ODD_OUTPUT].isConnected() || outputs[EVEN_OUTPUT].isConnected();
}

int FFB::channels() {
	return inputs[IN_INPUT].getChannels();
}

void FFB::addChannel(int c) {
	_engines[c] = new Engine();
}

void FFB::removeChannel(int c) {
	delete _engines[c];
	_engines[c] = NULL;
}

void FFB::modulate() {
	for (int i = 0; i < 14; ++i) {
		_levels[i] = clamp(params[LOWPASS_PARAM + i].getValue(), 0.0f, 1.0f);
	}
}

void FFB::modulateChannel(int c) {
	Engine& e = *_engines[c];

	for (int i = 0; i < 14; ++i) {
		float level = e._slews[i].next(_levels[i]);
		level = 1.0f - level;
		level *= Amplifier::minDecibels;
		e._amplifiers[i].setLevel(level);
	}

	float semitones = clamp(params[CV_PARAM].getValue(), -1.0f, 1.0f);
	if (inputs[CV_INPUT].isConnected()) {
		semitones *= clamp(inputs[CV_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f);
	}
	semitones *= 12.0f;
	e.setSemitonesOffset(semitones);
}

void FFB::processChannel(const ProcessArgs& args, int c) {
	Engine& e = *_engines[c];

	float in = inputs[IN_INPUT].getVoltage(c);
	float outAll = 0.0f;
	outAll += e._amplifiers[0].next(e._lowPass.next(in));
	outAll += e._amplifiers[13].next(e._highPass.next(in));
	float outOdd = outAll;
	float outEven = outAll;
	for (int i = 1; i <= 12; ++i) {
		float out = e._amplifiers[i].next(e._bandPasses[i - 1].next(in));
		outAll += out;
		outOdd += (i % 2 == 1) * out;
		outEven += (i % 2 == 0) * out;
	}

	outputs[ALL_OUTPUT].setChannels(_channels);
	outputs[ALL_OUTPUT].setVoltage(outAll, c);
	outputs[ODD_OUTPUT].setChannels(_channels);
	outputs[ODD_OUTPUT].setVoltage(outOdd, c);
	outputs[EVEN_OUTPUT].setChannels(_channels);
	outputs[EVEN_OUTPUT].setVoltage(outEven, c);
}

struct FFBWidget : BGModuleWidget {
	static constexpr int hp = 8;

	FFBWidget(FFB* module) {
		setModule(module);
		box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
		setPanel(box.size, "FFB");
		createScrews();

		// generated by svg_widgets.rb
		auto band1ParamPosition = Vec(7.5, 37.5);
		auto band5ParamPosition = Vec(47.0, 37.5);
		auto band9ParamPosition = Vec(86.5, 37.5);
		auto band2ParamPosition = Vec(7.5, 88.5);
		auto band6ParamPosition = Vec(47.0, 88.5);
		auto band10ParamPosition = Vec(86.5, 88.5);
		auto band3ParamPosition = Vec(7.5, 139.5);
		auto band7ParamPosition = Vec(47.0, 139.5);
		auto band11ParamPosition = Vec(86.5, 139.5);
		auto band4ParamPosition = Vec(7.5, 190.5);
		auto band8ParamPosition = Vec(47.0, 190.5);
		auto band12ParamPosition = Vec(86.5, 190.5);
		auto lowpassParamPosition = Vec(7.5, 241.5);
		auto cvParamPosition = Vec(52.0, 246.5);
		auto highpassParamPosition = Vec(86.5, 241.5);

		auto inInputPosition = Vec(32.5, 282.0);
		auto cvInputPosition = Vec(63.5, 282.0);

		auto allOutputPosition = Vec(17.0, 324.0);
		auto oddOutputPosition = Vec(48.0, 324.0);
		auto evenOutputPosition = Vec(79.0, 324.0);
		// end generated by svg_widgets.rb

		addParam(createParam<Knob26>(band1ParamPosition, module, FFB::BAND_1_PARAM));
		addParam(createParam<Knob26>(band5ParamPosition, module, FFB::BAND_5_PARAM));
		addParam(createParam<Knob26>(band9ParamPosition, module, FFB::BAND_9_PARAM));
		addParam(createParam<Knob26>(band2ParamPosition, module, FFB::BAND_2_PARAM));
		addParam(createParam<Knob26>(band6ParamPosition, module, FFB::BAND_6_PARAM));
		addParam(createParam<Knob26>(band10ParamPosition, module, FFB::BAND_10_PARAM));
		addParam(createParam<Knob26>(band3ParamPosition, module, FFB::BAND_3_PARAM));
		addParam(createParam<Knob26>(band7ParamPosition, module, FFB::BAND_7_PARAM));
		addParam(createParam<Knob26>(band11ParamPosition, module, FFB::BAND_11_PARAM));
		addParam(createParam<Knob26>(band4ParamPosition, module, FFB::BAND_4_PARAM));
		addParam(createParam<Knob26>(band8ParamPosition, module, FFB::BAND_8_PARAM));
		addParam(createParam<Knob26>(band12ParamPosition, module, FFB::BAND_12_PARAM));
		addParam(createParam<Knob26>(lowpassParamPosition, module, FFB::LOWPASS_PARAM));
		addParam(createParam<Knob16>(cvParamPosition, module, FFB::CV_PARAM));
		addParam(createParam<Knob26>(highpassParamPosition, module, FFB::HIGHPASS_PARAM));

		addInput(createInput<Port24>(inInputPosition, module, FFB::IN_INPUT));
		addInput(createInput<Port24>(cvInputPosition, module, FFB::CV_INPUT));

		addOutput(createOutput<Port24>(allOutputPosition, module, FFB::ALL_OUTPUT));
		addOutput(createOutput<Port24>(oddOutputPosition, module, FFB::ODD_OUTPUT));
		addOutput(createOutput<Port24>(evenOutputPosition, module, FFB::EVEN_OUTPUT));
	}
};

Model* modelFFB = createModel<FFB, FFBWidget>("Bogaudio-FFB", "FFB", "Fixed filter bank", "Filter", "Polyphonic");
