Trying to modify Kinks but have missing ports

I want to create a version of Kinks that has 3 sample and hold sections because I miss Shhhh from Southpole (I know it is apparently coming but this seemed like a nice little project to start modifying/creating plugins).

I have edited the svg and AudibleInstruments/src/Kinks.cpp so that it looks to be right according to the original but I have no ports on the two new sections. What did I do wrong?

This is what I have at the moment:

#include "AudibleInstruments.hpp"


struct Kinks : Module {
	enum ParamIds {
		NUM_PARAMS
	};
	enum InputIds {
		SH_A_INPUT,
		TRIG_A_INPUT,
        SH_B_INPUT,
		TRIG_B_INPUT,
		SH_C_INPUT,
		TRIG_C_INPUT,
		NUM_INPUTS
	};
	enum OutputIds {
		NOISE_A_OUTPUT,
		SH_A_OUTPUT,
        NOISE_B_OUTPUT,
		SH_B_OUTPUT,
        NOISE_C_OUTPUT,
		SH_C_OUTPUT,
		NUM_OUTPUTS
	};
	enum LightIds {
		SH_A_POS_LIGHT, SH_A_NEG_LIGHT,
		SH_B_POS_LIGHT, SH_B_NEG_LIGHT,
		SH_C_POS_LIGHT, SH_C_NEG_LIGHT,
		NUM_LIGHTS
	};

	dsp::SchmittTrigger trigger;
	float sample = 0.0;

	Kinks() {
		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
	}

	void process(const ProcessArgs &args) override {
		// Gaussian noise generator
		float noise = 2.0 * random::normal();

		// S&H
		if (trigger.process(inputs[TRIG_A_INPUT].getVoltage() / 0.7)) {
			sample = inputs[SH_A_INPUT].getNormalVoltage(noise);
		}
		if (trigger.process(inputs[TRIG_B_INPUT].getVoltage() / 0.7)) {
			sample = inputs[SH_B_INPUT].getNormalVoltage(noise);
		}
		if (trigger.process(inputs[TRIG_C_INPUT].getVoltage() / 0.7)) {
			sample = inputs[SH_C_INPUT].getNormalVoltage(noise);
		}

		// lights
		lights[SH_A_POS_LIGHT].setBrightness(fmaxf(0.0, sample / 5.0));
		lights[SH_A_NEG_LIGHT].setBrightness(fmaxf(0.0, -sample / 5.0));
        lights[SH_B_POS_LIGHT].setBrightness(fmaxf(0.0, sample / 5.0));
		lights[SH_B_NEG_LIGHT].setBrightness(fmaxf(0.0, -sample / 5.0));
        lights[SH_C_POS_LIGHT].setBrightness(fmaxf(0.0, sample / 5.0));
		lights[SH_C_NEG_LIGHT].setBrightness(fmaxf(0.0, -sample / 5.0));

		// outputs
		outputs[NOISE_A_OUTPUT].setVoltage(noise);
		outputs[SH_A_OUTPUT].setVoltage(sample);
        outputs[NOISE_B_OUTPUT].setVoltage(noise);
		outputs[SH_B_OUTPUT].setVoltage(sample);
        outputs[NOISE_C_OUTPUT].setVoltage(noise);
		outputs[SH_C_OUTPUT].setVoltage(sample);
	}
};


struct KinksWidget : ModuleWidget {
	KinksWidget(Kinks *module) {
		setModule(module);
		setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Kinks.svg")));

		addChild(createWidget<ScrewSilver>(Vec(15, 0)));
		addChild(createWidget<ScrewSilver>(Vec(15, 365)));

		addInput(createInput<PJ301MPort>(Vec(4, 278), module, Kinks::SH_A_INPUT));
		addInput(createInput<PJ301MPort>(Vec(31, 278), module, Kinks::TRIG_A_INPUT));
		addOutput(createOutput<PJ301MPort>(Vec(4, 316), module, Kinks::NOISE_A_OUTPUT));
		addOutput(createOutput<PJ301MPort>(Vec(31, 316), module, Kinks::SH_A_OUTPUT));
        
		addInput(createInput<PJ301MPort>(Vec(4, 278), module, Kinks::SH_B_INPUT));
		addInput(createInput<PJ301MPort>(Vec(31, 278), module, Kinks::TRIG_B_INPUT));
		addOutput(createOutput<PJ301MPort>(Vec(4, 316), module, Kinks::NOISE_B_OUTPUT));
		addOutput(createOutput<PJ301MPort>(Vec(31, 316), module, Kinks::SH_B_OUTPUT));

        addInput(createInput<PJ301MPort>(Vec(4, 278), module, Kinks::SH_C_INPUT));
		addInput(createInput<PJ301MPort>(Vec(31, 278), module, Kinks::TRIG_C_INPUT));
		addOutput(createOutput<PJ301MPort>(Vec(4, 316), module, Kinks::NOISE_C_OUTPUT));
		addOutput(createOutput<PJ301MPort>(Vec(31, 316), module, Kinks::SH_C_OUTPUT));

		addChild(createLight<SmallLight<GreenRedLight>>(Vec(11, 59), module, Kinks::SH_A_POS_LIGHT));
		addChild(createLight<SmallLight<GreenRedLight>>(Vec(11, 161), module, Kinks::SH_B_POS_LIGHT));
		addChild(createLight<SmallLight<GreenRedLight>>(Vec(11, 262), module, Kinks::SH_C_POS_LIGHT));
	}
};


Model *modelKinks = createModel<Kinks, KinksWidget>("Kinks");

I guess I have to put this as well according to the license:
Copyright 2016 Andrew Belt

I don’t know any of whatever code this in but I’ve played with bash, lua, python before and this seems remarkably simple to follow (or so I thought?).

You’re drawing all the ports on top of one another:

addInput(createInput<PJ301MPort>(Vec(4, 278), module, Kinks::SH_A_INPUT));
addInput(createInput<PJ301MPort>(Vec(4, 278), module, Kinks::SH_B_INPUT));

are both drawn at 4,278. Change it to this:

addInput(createInput<PJ301MPort>(Vec(4, 278), module, Kinks::SH_A_INPUT));
addInput(createInput<PJ301MPort>(Vec(20, 278), module, Kinks::SH_B_INPUT));

To see the SH_B_INPUT placed to the right of SH_A_INPUT

1 Like

Oh of course, what a silly mistake! Thank you, I will correct it and keep that in mind for the future.

1 Like

I noticed that all outputs are the same if they are fed with the same trigger, which they shouldn’t. The noise output is different in each section (or so the scope tells me) but for some reason it is outputting the same voltage over all outs when the trigger is received.

Do I need to differentiate the noise generation more than I have?

#include "AudibleInstruments.hpp"


struct Kinks : Module {
	enum ParamIds {
		NUM_PARAMS
	};
	enum InputIds {
		SH_A_INPUT,
		TRIG_A_INPUT,
        SH_B_INPUT,
		TRIG_B_INPUT,
		SH_C_INPUT,
		TRIG_C_INPUT,
		NUM_INPUTS
	};
	enum OutputIds {
		NOISE_A_OUTPUT,
		SH_A_OUTPUT,
        NOISE_B_OUTPUT,
		SH_B_OUTPUT,
        NOISE_C_OUTPUT,
		SH_C_OUTPUT,
		NUM_OUTPUTS
	};
	enum LightIds {
		SH_A_POS_LIGHT, SH_A_NEG_LIGHT,
		SH_B_POS_LIGHT, SH_B_NEG_LIGHT,
		SH_C_POS_LIGHT, SH_C_NEG_LIGHT,
		NUM_LIGHTS
	};

	dsp::SchmittTrigger trigger;
	float sample_a = 0.0;
	float sample_b = 0.0;
	float sample_c = 0.0;

	Kinks() {
		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
	}

	void process(const ProcessArgs &args) override {
		// Gaussian noise generator
		float noise_a = 2.0 * random::normal();
		float noise_b = 2.0 * random::normal();
		float noise_c = 2.0 * random::normal();

		// S&H
		if (trigger.process(inputs[TRIG_A_INPUT].getVoltage() / 0.7)) {
			sample_a = inputs[SH_A_INPUT].getNormalVoltage(noise_a);
		}
		if (trigger.process(inputs[TRIG_B_INPUT].getVoltage() / 0.7)) {
			sample_b = inputs[SH_B_INPUT].getNormalVoltage(noise_b);
		}
		if (trigger.process(inputs[TRIG_C_INPUT].getVoltage() / 0.7)) {
			sample_c = inputs[SH_C_INPUT].getNormalVoltage(noise_c);
		}

		// lights
		lights[SH_A_POS_LIGHT].setBrightness(fmaxf(0.0, sample_a / 5.0));
		lights[SH_A_NEG_LIGHT].setBrightness(fmaxf(0.0, -sample_a / 5.0));
        lights[SH_B_POS_LIGHT].setBrightness(fmaxf(0.0, sample_b / 5.0));
		lights[SH_B_NEG_LIGHT].setBrightness(fmaxf(0.0, -sample_b / 5.0));
        lights[SH_C_POS_LIGHT].setBrightness(fmaxf(0.0, sample_c / 5.0));
		lights[SH_C_NEG_LIGHT].setBrightness(fmaxf(0.0, -sample_c / 5.0));

		// outputs
		outputs[NOISE_A_OUTPUT].setVoltage(noise_a);
		outputs[SH_A_OUTPUT].setVoltage(sample_a);
        outputs[NOISE_B_OUTPUT].setVoltage(noise_b);
		outputs[SH_B_OUTPUT].setVoltage(sample_a);
        outputs[NOISE_C_OUTPUT].setVoltage(noise_c);
		outputs[SH_C_OUTPUT].setVoltage(sample_c);
	}
};


struct KinksWidget : ModuleWidget {
	KinksWidget(Kinks *module) {
		setModule(module);
		setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Kinks.svg")));

		addChild(createWidget<ScrewSilver>(Vec(15, 0)));
		addChild(createWidget<ScrewSilver>(Vec(15, 365)));

		addInput(createInput<PJ301MPort>(Vec(4, 75), module, Kinks::SH_A_INPUT));
		addInput(createInput<PJ301MPort>(Vec(31, 75), module, Kinks::TRIG_A_INPUT));
		addOutput(createOutput<PJ301MPort>(Vec(4, 113), module, Kinks::NOISE_A_OUTPUT));
		addOutput(createOutput<PJ301MPort>(Vec(31, 113), module, Kinks::SH_A_OUTPUT));
        
		addInput(createInput<PJ301MPort>(Vec(4, 177), module, Kinks::SH_B_INPUT));
		addInput(createInput<PJ301MPort>(Vec(31, 177), module, Kinks::TRIG_B_INPUT));
		addOutput(createOutput<PJ301MPort>(Vec(4, 214), module, Kinks::NOISE_B_OUTPUT));
		addOutput(createOutput<PJ301MPort>(Vec(31, 214), module, Kinks::SH_B_OUTPUT));

        addInput(createInput<PJ301MPort>(Vec(4, 278), module, Kinks::SH_C_INPUT));
		addInput(createInput<PJ301MPort>(Vec(31, 278), module, Kinks::TRIG_C_INPUT));
		addOutput(createOutput<PJ301MPort>(Vec(4, 316), module, Kinks::NOISE_C_OUTPUT));
		addOutput(createOutput<PJ301MPort>(Vec(31, 316), module, Kinks::SH_C_OUTPUT));

		addChild(createLight<SmallLight<GreenRedLight>>(Vec(11, 59), module, Kinks::SH_A_POS_LIGHT));
		addChild(createLight<SmallLight<GreenRedLight>>(Vec(11, 161), module, Kinks::SH_B_POS_LIGHT));
		addChild(createLight<SmallLight<GreenRedLight>>(Vec(11, 262), module, Kinks::SH_C_POS_LIGHT));
	}
};


Model *modelKinks = createModel<Kinks, KinksWidget>("Kinks");

SchmittTriggers are stateful, so you need one per input.

Like this?

	dsp::SchmittTrigger trigger_a;
	float sample_a = 0.0;
    dsp::SchmittTrigger trigger_b;
	float sample_b = 0.0;
	dsp::SchmittTrigger trigger_c;
	float sample_c = 0.0;

	Kinks() {
		config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
	}

	void process(const ProcessArgs &args) override {
		// Gaussian noise generator
		float noise_a = 2.0 * random::normal();
		float noise_b = 2.0 * random::normal();
		float noise_c = 2.0 * random::normal();

		// S&H
		if (trigger_a.process(inputs[TRIG_A_INPUT].getVoltage() / 0.7)) {
			sample_a = inputs[SH_A_INPUT].getNormalVoltage(noise_a);
		}
		if (trigger_b.process(inputs[TRIG_B_INPUT].getVoltage() / 0.7)) {
			sample_b = inputs[SH_B_INPUT].getNormalVoltage(noise_b);
		}
		if (trigger_c.process(inputs[TRIG_C_INPUT].getVoltage() / 0.7)) {
			sample_c = inputs[SH_C_INPUT].getNormalVoltage(noise_c);
		}

Where do you learn about these functions?

The only documentation I’d trust is the Rack header files: https://github.com/VCVRack/Rack/tree/v1/include

The SchmittTrigger is defined here:

1 Like

Yep, that’ll work for separating the triggers.

As a personal preference I would probably use camelCase when naming stuff e.g.triggerA.

trigger_a.process kind of gets lost but triggerA.process stands out a lot more. If at a later time you decide to go back to the code it is much easier to see/read.

1 Like

Maybe think about using arrays?

For example…

dsp::SchmittTrigger trigger_a;
float sample_a = 0.0;
dsp::SchmittTrigger trigger_b;
float sample_b = 0.0;
dsp::SchmittTrigger trigger_c;
float sample_c = 0.0;

…could be condensed to…

dsp::SchmittTrigger triggers[3];
float samples[3] = {0.f};

And then use for loops to process them instead of repeating the code for each one?

This is probably overkill for three triggers and sample voltages, but might help if you want to go nuts with a bajillion S&Hs in the future? :slight_smile:

1 Like

Just to expand on this. trigger[size of array] which in this case is 3 values to store, when you want to call these 3 values the positions start from 0. So an array of 3 would have values in positions 0, 1 and 2 (3 digits = 3 values)

A for loop repeats code a certain number of times e.g.

for(int i = 0; i < 3; i++) {code}

i is starting point and 3 is the end point, i++ is the shorthand way of saying i = i + i; or i += i; so in the case of the array the index (0,1,2) will not go out of bounds as the loop stops at the last position in the array (2 is less than 3)

trigger[i].process would assign each index position in the for loop. Each loop/repeat int i gets reassigned i = i + 1 or i += 1 which is what i++ would be doing.

2 Likes

@Coirt and @immortaljellyfish that’s very useful and elegant, thank you. I can sort of follow what you are saying but I think I need to learn some c++, I’m familiar with arrays from when I was trying to learn Pure Data but this is pushing my current knowledge and ability.

Meanwhile, separating the triggers doesn’t seem to have solved it, I’m still getting the same voltage out but I can’t see anything that is shared. Will try to have another play tonight.

make clean will clean the build, they’re probably still assigned to the old values

make

That did it! Thanks, I should have remembered do that, it’s been a while since I’ve complied much.

So it is working. I had missed a a/b/c change on the outputs but it’s all fine now. Thank you all again for the great help, it’s been very informative.

Is there a quick and easy way to create a new module so I can have kinks original as well? Can I just sed all instances of Kinks to Kinks2 and AudibleInstruments to whatever?

I wouldn’t mind replacing that S&H text with a slide button to change range but will have to think about that a bit more.

1 Like

Or you could make the text the slider :stuck_out_tongue_winking_eye:

Should be easy enough just multiple the output by the param value.

1 Like