
#include "Sine.hpp"

#define WAVE "wave"
#define FM_MODE "fm_mode"
#define LINEAR_MODE "linear_mode"

json_t* Sine::saveToJson(json_t* root) {
	root = VCOBase::saveToJson(root);
	json_object_set_new(root, WAVE, json_integer((int)_wave));
	json_object_set_new(root, FM_MODE, json_boolean(_fmLinearMode));
	json_object_set_new(root, LINEAR_MODE, json_boolean(_linearMode));
	return root;
}

void Sine::loadFromJson(json_t* root) {
	VCOBase::loadFromJson(root);

	json_t* w = json_object_get(root, WAVE);
	if (w) {
		_wave = (Wave)json_integer_value(w);
		if (!(_wave == TRIANGLE_WAVE || _wave == SAW_WAVE || _wave == RAMP_WAVE || _wave == SQUARE_WAVE || _wave == PULSE_25_WAVE || _wave == PULSE_10_WAVE)) {
			_wave = SINE_WAVE;
		}
	}

	json_t* fm = json_object_get(root, FM_MODE);
	if (fm) {
		_fmLinearMode = json_is_true(fm);
	}

	json_t* l = json_object_get(root, LINEAR_MODE);
	if (l) {
		_linearMode = json_is_true(l);
	}
}

bool Sine::active() {
	return outputs[OUT_OUTPUT].isConnected();
}

void Sine::modulate() {
	_slowMode = params[SLOW_PARAM].getValue() > 0.5f;
	_fmDepth = params[FM_DEPTH_PARAM].getValue();
}

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

	_outputScale = 1.0f;
	e.sawActive = false;
	e.squareActive = false;
	switch (_wave) {
		case SAW_WAVE: {
			e.sawActive = true;
			break;
		}
		case RAMP_WAVE: {
			e.sawActive = true;
			_outputScale = -1.0f;
			break;
		}
		case SQUARE_WAVE: {
			e.squareActive = true;
			e.square.setPulseWidth(e.squarePulseWidthSL.next(0.5f), _dcCorrection);
			break;
		}
		case PULSE_25_WAVE: {
			e.squareActive = true;
			e.square.setPulseWidth(e.squarePulseWidthSL.next(0.25f), _dcCorrection);
			break;
		}
		case PULSE_10_WAVE: {
			e.squareActive = true;
			e.square.setPulseWidth(e.squarePulseWidthSL.next(0.1f), _dcCorrection);
			break;
		}
		default: {
		}
	}
	e.triangleActive = _wave == TRIANGLE_WAVE;
	e.sineActive = _wave == SINE_WAVE;
}

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

	float phaseOffset = params[PHASE_PARAM].getValue();
	if (inputs[PHASE_INPUT].isConnected()) {
		phaseOffset *= clamp(inputs[PHASE_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f);
	}
	e.additionalPhaseOffset = -phaseOffset * 0.5f * Phasor::cyclePhase;

	VCOBase::processChannel(args, c);

	outputs[OUT_OUTPUT].setChannels(_channels);
	outputs[OUT_OUTPUT].setVoltage(_outputScale * (e.squareOut + e.sawOut + e.triangleOut + e.sineOut), c);
}

struct SineWidget : VCOBaseModuleWidget {
	static constexpr int hp = 3;

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

		// generated by svg_widgets.rb
		auto frequencyParamPosition = Vec(9.5, 27.0);
		auto slowParamPosition = Vec(31.0, 62.0);
		auto fmDepthParamPosition = Vec(14.5, 92.5);
		auto phaseParamPosition = Vec(14.5, 134.5);

		auto pitchInputPosition = Vec(10.5, 161.0);
		auto fmInputPosition = Vec(10.5, 196.0);
		auto phaseInputPosition = Vec(10.5, 231.0);
		auto syncInputPosition = Vec(10.5, 266.0);

		auto outOutputPosition = Vec(10.5, 304.0);
		// end generated by svg_widgets.rb

		addParam(createParam<Knob26>(frequencyParamPosition, module, Sine::FREQUENCY_PARAM));
		addParam(createParam<IndicatorButtonGreen9>(slowParamPosition, module, Sine::SLOW_PARAM));
		addParam(createParam<Knob16>(fmDepthParamPosition, module, Sine::FM_DEPTH_PARAM));
		addParam(createParam<Knob16>(phaseParamPosition, module, Sine::PHASE_PARAM));

		addInput(createInput<Port24>(pitchInputPosition, module, Sine::PITCH_INPUT));
		addInput(createInput<Port24>(fmInputPosition, module, Sine::FM_INPUT));
		addInput(createInput<Port24>(phaseInputPosition, module, Sine::PHASE_INPUT));
		addInput(createInput<Port24>(syncInputPosition, module, Sine::SYNC_INPUT));

		addOutput(createOutput<Port24>(outOutputPosition, module, Sine::OUT_OUTPUT));
	}

	void contextMenu(Menu* menu) override {
		auto m = dynamic_cast<Sine*>(module);
		assert(m);

		OptionsMenuItem* w = new OptionsMenuItem("Waveform");
		w->addItem(OptionMenuItem("Sine", [m]() { return m->_wave == Sine::SINE_WAVE; }, [m]() { m->_wave = Sine::SINE_WAVE; }));
		w->addItem(OptionMenuItem("Triangle", [m]() { return m->_wave == Sine::TRIANGLE_WAVE; }, [m]() { m->_wave = Sine::TRIANGLE_WAVE; }));
		w->addItem(OptionMenuItem("Saw", [m]() { return m->_wave == Sine::SAW_WAVE; }, [m]() { m->_wave = Sine::SAW_WAVE; }));
		w->addItem(OptionMenuItem("Ramp", [m]() { return m->_wave == Sine::RAMP_WAVE; }, [m]() { m->_wave = Sine::RAMP_WAVE; }));
		w->addItem(OptionMenuItem("Square", [m]() { return m->_wave == Sine::SQUARE_WAVE; }, [m]() { m->_wave = Sine::SQUARE_WAVE; }));
		w->addItem(OptionMenuItem("25% pulse", [m]() { return m->_wave == Sine::PULSE_25_WAVE; }, [m]() { m->_wave = Sine::PULSE_25_WAVE; }));
		w->addItem(OptionMenuItem("10% pulse", [m]() { return m->_wave == Sine::PULSE_10_WAVE; }, [m]() { m->_wave = Sine::PULSE_10_WAVE; }));
		OptionsMenuItem::addToMenu(w, menu);

		OptionsMenuItem* fm = new OptionsMenuItem("FM mode");
		fm->addItem(OptionMenuItem("Exponential", [m]() { return !m->_fmLinearMode; }, [m]() { m->_fmLinearMode = false; }));
		fm->addItem(OptionMenuItem("Linear", [m]() { return m->_fmLinearMode; }, [m]() { m->_fmLinearMode = true; }));
		OptionsMenuItem::addToMenu(fm, menu);

		menu->addChild(new BoolOptionMenuItem("Linear frequency mode", [m]() { return &m->_linearMode; }));

		OptionsMenuItem* p = new OptionsMenuItem("Polyphony channels from");
		p->addItem(OptionMenuItem("V/OCT input", [m]() { return m->_polyInputID == Sine::PITCH_INPUT; }, [m]() { m->_polyInputID = Sine::PITCH_INPUT; }));
		p->addItem(OptionMenuItem("FM input", [m]() { return m->_polyInputID == Sine::FM_INPUT; }, [m]() { m->_polyInputID = Sine::FM_INPUT; }));
		OptionsMenuItem::addToMenu(p, menu);

		VCOBaseModuleWidget::contextMenu(menu);
	}
};

Model* modelSine = createModel<Sine, SineWidget>("Bogaudio-Sine", "SINE", "Sine oscillator with phase offset", "Oscillator", "Polyphonic");
