<commit_before>#include "Core.hpp"
#include "midi.hpp"
#include "dsp/filter.hpp"


struct MIDICCToCVInterface : Module {
	enum ParamIds {
		NUM_PARAMS
	};
	enum InputIds {
		NUM_INPUTS
	};
	enum OutputIds {
		ENUMS(CC_OUTPUT, 16),
		NUM_OUTPUTS
	};
	enum LightIds {
		NUM_LIGHTS
	};

	MidiInputQueue midiInput;
	int8_t cvs[16];
	ExponentialFilter ccFilters[16];

	int learningId = -1;
	uint8_t learnedCcs[16] = {};

	MIDICCToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
		onReset();
	}

	void onReset() override {
		for (int i = 0; i < 16; i++) {
			cvs[i] = 0;
			learnedCcs[i] = i;
		}
		learningId = -1;
	}

	void step() override {
		MidiMessage msg;
		while (midiInput.shift(&msg)) {
			processMessage(msg);
		}

		float lambda = 100.f * engineGetSampleTime();
		for (int i = 0; i < 16; i++) {
			float value = rescale(cvs[i], 0, 127, 0.f, 10.f);
			ccFilters[i].lambda = lambda;
			outputs[CC_OUTPUT + i].value = ccFilters[i].process(value);
		}
	}

	void processMessage(MidiMessage msg) {
		switch (msg.status()) {
			// cc
			case 0xb: {
				uint8_t cc = msg.note();
				// Learn
				if (learningId >= 0) {
					learnedCcs[learningId] = cc;
					learningId = -1;
				}
				// Set CV
				for (int i = 0; i < 16; i++) {
					if (learnedCcs[i] == cc) {
						// Allow CC to be negative if the 8th bit is set
						cvs[i] = msg.data2;
					}
				}
			} break;
			default: break;
		}
	}

	json_t *toJson() override {
		json_t *rootJ = json_object();

		json_t *ccsJ = json_array();
		for (int i = 0; i < 16; i++) {
			json_t *ccJ = json_integer(learnedCcs[i]);
			json_array_append_new(ccsJ, ccJ);
		}
		json_object_set_new(rootJ, "ccs", ccsJ);

		json_object_set_new(rootJ, "midi", midiInput.toJson());
		return rootJ;
	}

	void fromJson(json_t *rootJ) override {
		json_t *ccsJ = json_object_get(rootJ, "ccs");
		if (ccsJ) {
			for (int i = 0; i < 16; i++) {
				json_t *ccJ = json_array_get(ccsJ, i);
				if (ccJ)
					learnedCcs[i] = json_integer_value(ccJ);<commit_msg>Learn CC only if value changes<commit_after>#include "Core.hpp"
#include "midi.hpp"
#include "dsp/filter.hpp"


struct MIDICCToCVInterface : Module {
	enum ParamIds {
		NUM_PARAMS
	};
	enum InputIds {
		NUM_INPUTS
	};
	enum OutputIds {
		ENUMS(CC_OUTPUT, 16),
		NUM_OUTPUTS
	};
	enum LightIds {
		NUM_LIGHTS
	};

	MidiInputQueue midiInput;
	int8_t ccs[128];
	ExponentialFilter ccFilters[16];

	int learningId = -1;
	int learnedCcs[16] = {};

	MIDICCToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
		onReset();
	}

	void onReset() override {
		for (int i = 0; i < 128; i++) {
			ccs[i] = 0;
		}
		for (int i = 0; i < 16; i++) {
			learnedCcs[i] = i;
		}
		learningId = -1;
	}

	void step() override {
		MidiMessage msg;
		while (midiInput.shift(&msg)) {
			processMessage(msg);
		}

		float lambda = 100.f * engineGetSampleTime();
		for (int i = 0; i < 16; i++) {
			int learnedCc = learnedCcs[i];
			float value = rescale(clamp(ccs[learnedCc], -127, 127), 0, 127, 0.f, 10.f);
			ccFilters[i].lambda = lambda;
			outputs[CC_OUTPUT + i].value = ccFilters[i].process(value);
		}
	}

	void processMessage(MidiMessage msg) {
		switch (msg.status()) {
			// cc
			case 0xb: {
				uint8_t cc = msg.note();
				// Learn
				if (learningId >= 0 && ccs[cc] != msg.data2) {
					learnedCcs[learningId] = cc;
					learningId = -1;
				}
				// Set CV
				// Allow CC to be negative if the 8th bit is set
				ccs[cc] = msg.data2;
			} break;
			default: break;
		}
	}

	json_t *toJson() override {
		json_t *rootJ = json_object();

		json_t *ccsJ = json_array();
		for (int i = 0; i < 16; i++) {
			json_t *ccJ = json_integer(learnedCcs[i]);
			json_array_append_new(ccsJ, ccJ);
		}
		json_object_set_new(rootJ, "ccs", ccsJ);

		json_object_set_new(rootJ, "midi", midiInput.toJson());
		return rootJ;
	}

	void fromJson(json_t *rootJ) override {
		json_t *ccsJ = json_object_get(rootJ, "ccs");
		if (ccsJ) {
			for (int i = 0; i < 16; i++) {
				json_t *ccJ = json_array_get(ccsJ, i);
				if (ccJ)
					learnedCcs[i] = json_integer_value(ccJ);
{'fraction_non_alphanumeric': 0.15259660349495446, 'fraction_numerical': 0.02362786118631553, 'mean_word_length': 3.1258883248730966, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1664444', 'n_tokens_mistral': 2001, 'n_tokens_neox': 1695, 'n_words': 384}