BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

SampleHold.cpp (10056B)


      1 
      2 #include "SampleHold.hpp"
      3 
      4 #define POLY_INPUT "poly_input"
      5 #define NOISE_TYPE "noise_type"
      6 #define RANGE_OFFSET "range_offset"
      7 #define RANGE_SCALE "range_scale"
      8 #define SMOOTHING_MS "smoothing_ms"
      9 
     10 void SampleHold::reset() {
     11 	for (int i = 0; i < maxChannels; ++i) {
     12 		_trigger1[i].reset();
     13 		_value1[i] = 0.0f;
     14 		_trigger2[i].reset();
     15 		_value2[i] = 0.0f;
     16 	}
     17 }
     18 
     19 json_t* SampleHold::saveToJson(json_t* root) {
     20 	json_object_set_new(root, POLY_INPUT, json_integer(_polyInputID));
     21 	json_object_set_new(root, NOISE_TYPE, json_integer((int)_noiseType));
     22 	json_object_set_new(root, RANGE_OFFSET, json_real(_rangeOffset));
     23 	json_object_set_new(root, RANGE_SCALE, json_real(_rangeScale));
     24 	json_object_set_new(root, SMOOTHING_MS, json_real(_smoothMS));
     25 	return root;
     26 }
     27 
     28 void SampleHold::loadFromJson(json_t* root) {
     29 	json_t* p = json_object_get(root, POLY_INPUT);
     30 	if (p) {
     31 		_polyInputID = json_integer_value(p);
     32 	}
     33 
     34 	json_t* nt = json_object_get(root, NOISE_TYPE);
     35 	if (nt) {
     36 		_noiseType = (NoiseType)json_integer_value(nt);
     37 	}
     38 
     39 	json_t* ro = json_object_get(root, RANGE_OFFSET);
     40 	if (ro) {
     41 		_rangeOffset = json_real_value(ro);
     42 	}
     43 
     44 	json_t* rs = json_object_get(root, RANGE_SCALE);
     45 	if (rs) {
     46 		_rangeScale = json_real_value(rs);
     47 	}
     48 
     49 	json_t* s = json_object_get(root, SMOOTHING_MS);
     50 	if (s) {
     51 		_smoothMS = json_real_value(s);
     52 	}
     53 }
     54 
     55 void SampleHold::modulate() {
     56 	modulateSection(
     57 		inputs[TRIGGER1_INPUT],
     58 		NULL,
     59 		inputs[IN1_INPUT],
     60 		_outputSL1
     61 	);
     62 	modulateSection(
     63 		inputs[TRIGGER2_INPUT],
     64 		&inputs[TRIGGER1_INPUT],
     65 		inputs[IN2_INPUT],
     66 		_outputSL2
     67 	);
     68 }
     69 
     70 void SampleHold::processAll(const ProcessArgs& args) {
     71 	processSection(
     72 		params[TRACK1_PARAM],
     73 		params[INVERT1_PARAM],
     74 		_trigger1,
     75 		params[TRIGGER1_PARAM],
     76 		inputs[TRIGGER1_INPUT],
     77 		NULL,
     78 		inputs[IN1_INPUT],
     79 		_value1,
     80 		_outputSL1,
     81 		outputs[OUT1_OUTPUT]
     82 	);
     83 	processSection(
     84 		params[TRACK2_PARAM],
     85 		params[INVERT2_PARAM],
     86 		_trigger2,
     87 		params[TRIGGER2_PARAM],
     88 		inputs[TRIGGER2_INPUT],
     89 		&inputs[TRIGGER1_INPUT],
     90 		inputs[IN2_INPUT],
     91 		_value2,
     92 		_outputSL2,
     93 		outputs[OUT2_OUTPUT]
     94 	);
     95 }
     96 
     97 int SampleHold::sectionChannels(
     98 	Input& triggerInput,
     99 	Input* altTriggerInput,
    100 	Input& in
    101 ) {
    102 	int n = 0;
    103 	if (_polyInputID == IN1_INPUT) {
    104 		n = in.getChannels();
    105 	}
    106 	else if (triggerInput.isConnected()) {
    107 		n = triggerInput.getChannels();
    108 	} else if (altTriggerInput) {
    109 		n = altTriggerInput->getChannels();
    110 	}
    111 	return std::max(1, n);
    112 }
    113 
    114 void SampleHold::modulateSection(
    115 	Input& triggerInput,
    116 	Input* altTriggerInput,
    117 	Input& in,
    118 	SlewLimiter* outputSL
    119 ) {
    120 	int n = sectionChannels(triggerInput, altTriggerInput, in);
    121 	for (int i = 0; i < n; ++i) {
    122 		outputSL[i].setParams(APP->engine->getSampleRate(), _smoothMS, 10.0f);
    123 	}
    124 }
    125 
    126 void SampleHold::processSection(
    127 	Param& trackParam,
    128 	Param& invertParam,
    129 	Trigger* trigger,
    130 	Param& triggerParam,
    131 	Input& triggerInput,
    132 	Input* altTriggerInput,
    133 	Input& in,
    134 	float* value,
    135 	SlewLimiter* outputSL,
    136 	Output& out
    137 ) {
    138 	int n = sectionChannels(triggerInput, altTriggerInput, in);
    139 	out.setChannels(n);
    140 
    141 	for (int i = 0; i < n; ++i) {
    142 		float triggerIn = 0.0f;
    143 		if (triggerInput.isConnected()) {
    144 			triggerIn = triggerInput.getPolyVoltage(i);
    145 		} else if (altTriggerInput) {
    146 			triggerIn = altTriggerInput->getPolyVoltage(i);
    147 		}
    148 
    149 		bool track = trackParam.getValue() > 0.5f;
    150 		bool triggered = trigger[i].process(triggerParam.getValue() + triggerIn);
    151 		if (track ? trigger[i].isHigh() : triggered) {
    152 			if (in.isConnected()) {
    153 				value[i] = in.getPolyVoltage(i);
    154 			}
    155 			else {
    156 				value[i] = (noise() + _rangeOffset) * _rangeScale;
    157 			}
    158 		}
    159 
    160 		float o = value[i];
    161 		if (invertParam.getValue() > 0.5f) {
    162 			o = -o;
    163 		}
    164 		if (!track) {
    165 			o = outputSL[i].next(o);
    166 		}
    167 		out.setVoltage(o, i);
    168 	}
    169 }
    170 
    171 float SampleHold::noise() {
    172 	switch (_noiseType) {
    173 		case BLUE_NOISE_TYPE: {
    174 			return clamp(2.0f * _blue.next(), -1.0f, 1.0f);
    175 		}
    176 		case PINK_NOISE_TYPE: {
    177 			return clamp(1.5f * _pink.next(), -1.0f, 1.0f);
    178 		}
    179 		case RED_NOISE_TYPE: {
    180 			return clamp(2.0f * _red.next(), -1.0f, 1.0f);
    181 		}
    182 		default: {
    183 			return clamp(_white.next(), -1.0f, 1.0f);
    184 		}
    185 	}
    186 }
    187 
    188 struct SampleHoldWidget : BGModuleWidget {
    189 	static constexpr int hp = 3;
    190 
    191 	SampleHoldWidget(SampleHold* module) {
    192 		setModule(module);
    193 		box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
    194 		setPanel(box.size, "SampleHold");
    195 		createScrews();
    196 
    197 		// generated by svg_widgets.rb
    198 		auto trigger1ParamPosition = Vec(13.5, 27.0);
    199 		auto track1ParamPosition = Vec(26.5, 122.7);
    200 		auto invert1ParamPosition = Vec(26.5, 133.7);
    201 		auto trigger2ParamPosition = Vec(13.5, 190.0);
    202 		auto track2ParamPosition = Vec(26.5, 285.7);
    203 		auto invert2ParamPosition = Vec(26.5, 296.7);
    204 
    205 		auto trigger1InputPosition = Vec(10.5, 49.0);
    206 		auto in1InputPosition = Vec(10.5, 86.0);
    207 		auto trigger2InputPosition = Vec(10.5, 212.0);
    208 		auto in2InputPosition = Vec(10.5, 249.0);
    209 
    210 		auto out1OutputPosition = Vec(10.5, 147.0);
    211 		auto out2OutputPosition = Vec(10.5, 310.0);
    212 		// end generated by svg_widgets.rb
    213 
    214 		addParam(createParam<Button18>(trigger1ParamPosition, module, SampleHold::TRIGGER1_PARAM));
    215 		addParam(createParam<Button18>(trigger2ParamPosition, module, SampleHold::TRIGGER2_PARAM));
    216 		addParam(createParam<IndicatorButtonGreen9>(track1ParamPosition, module, SampleHold::TRACK1_PARAM));
    217 		addParam(createParam<IndicatorButtonGreen9>(track2ParamPosition, module, SampleHold::TRACK2_PARAM));
    218 		addParam(createParam<IndicatorButtonGreen9>(invert1ParamPosition, module, SampleHold::INVERT1_PARAM));
    219 		addParam(createParam<IndicatorButtonGreen9>(invert2ParamPosition, module, SampleHold::INVERT2_PARAM));
    220 
    221 		addInput(createInput<Port24>(trigger1InputPosition, module, SampleHold::TRIGGER1_INPUT));
    222 		addInput(createInput<Port24>(in1InputPosition, module, SampleHold::IN1_INPUT));
    223 		addInput(createInput<Port24>(trigger2InputPosition, module, SampleHold::TRIGGER2_INPUT));
    224 		addInput(createInput<Port24>(in2InputPosition, module, SampleHold::IN2_INPUT));
    225 
    226 		addOutput(createOutput<Port24>(out1OutputPosition, module, SampleHold::OUT1_OUTPUT));
    227 		addOutput(createOutput<Port24>(out2OutputPosition, module, SampleHold::OUT2_OUTPUT));
    228 	}
    229 
    230 	struct RangeOptionMenuItem : OptionMenuItem {
    231 		RangeOptionMenuItem(SampleHold* module, const char* label, float offset, float scale)
    232 		: OptionMenuItem(
    233 			label,
    234 			[=]() { return module->_rangeOffset == offset && module->_rangeScale == scale; },
    235 			[=]() {
    236 				module->_rangeOffset = offset;
    237 				module->_rangeScale = scale;
    238 			}
    239 		)
    240 		{}
    241 	};
    242 
    243 	struct SmoothQuantity : Quantity {
    244 		SampleHold* _module;
    245 
    246 		SmoothQuantity(SampleHold* m) : _module(m) {}
    247 
    248 		void setValue(float value) override {
    249 			value = clamp(value, getMinValue(), getMaxValue());
    250 			if (_module) {
    251 				_module->_smoothMS = valueToMs(value);
    252 			}
    253 		}
    254 
    255 		float getValue() override {
    256 			if (_module) {
    257 				return msToValue(_module->_smoothMS);
    258 			}
    259 			return getDefaultValue();
    260 		}
    261 
    262 		float getMinValue() override { return 0.0f; }
    263 		float getMaxValue() override { return 1.0f; }
    264 		float getDefaultValue() override { return getMinValue(); }
    265 		float getDisplayValue() override { return roundf(valueToMs(getValue())); }
    266 		void setDisplayValue(float displayValue) override { setValue(msToValue(displayValue)); }
    267 		std::string getLabel() override { return "Smoothing"; }
    268 		std::string getUnit() override { return "ms"; }
    269 		float valueToMs(float v) { return v * v * SampleHold::maxSmoothMS; }
    270 		float msToValue(float ms) { return sqrtf(ms / SampleHold::maxSmoothMS); };
    271 	};
    272 
    273 	struct SmoothSlider : ui::Slider {
    274 		SmoothSlider(SmoothQuantity* q) {
    275 			quantity = q; // q now owned.
    276 			box.size.x = 200.0f;
    277 		}
    278 		virtual ~SmoothSlider() {
    279 			delete quantity;
    280 		}
    281 	};
    282 
    283 	struct SmoothMenuItem : MenuItem {
    284 		SampleHold* _module;
    285 
    286 		SmoothMenuItem(SampleHold* m) : _module(m) {
    287 			this->text = "Glide";
    288 			this->rightText = "▸";
    289 		}
    290 
    291 		Menu* createChildMenu() override {
    292 			Menu* menu = new Menu;
    293 			menu->addChild(new SmoothSlider(new SmoothQuantity(_module)));
    294 			return menu;
    295 		}
    296 	};
    297 
    298 	void contextMenu(Menu* menu) override {
    299 		auto m = dynamic_cast<SampleHold*>(module);
    300 		assert(m);
    301 		{
    302 			OptionsMenuItem* p = new OptionsMenuItem("Polyphony channels from");
    303 			p->addItem(OptionMenuItem("GATE input", [m]() { return m->_polyInputID == SampleHold::TRIGGER1_INPUT; }, [m]() { m->_polyInputID = SampleHold::TRIGGER1_INPUT; }));
    304 			p->addItem(OptionMenuItem("IN input", [m]() { return m->_polyInputID == SampleHold::IN1_INPUT; }, [m]() { m->_polyInputID = SampleHold::IN1_INPUT; }));
    305 			OptionsMenuItem::addToMenu(p, menu);
    306 		}
    307 		{
    308 			OptionsMenuItem* mi = new OptionsMenuItem("Normal noise");
    309 			mi->addItem(OptionMenuItem("Blue", [m]() { return m->_noiseType == SampleHold::BLUE_NOISE_TYPE; }, [m]() { m->_noiseType = SampleHold::BLUE_NOISE_TYPE; }));
    310 			mi->addItem(OptionMenuItem("White", [m]() { return m->_noiseType == SampleHold::WHITE_NOISE_TYPE; }, [m]() { m->_noiseType = SampleHold::WHITE_NOISE_TYPE; }));
    311 			mi->addItem(OptionMenuItem("Pink", [m]() { return m->_noiseType == SampleHold::PINK_NOISE_TYPE; }, [m]() { m->_noiseType = SampleHold::PINK_NOISE_TYPE; }));
    312 			mi->addItem(OptionMenuItem("Red", [m]() { return m->_noiseType == SampleHold::RED_NOISE_TYPE; }, [m]() { m->_noiseType = SampleHold::RED_NOISE_TYPE; }));
    313 			OptionsMenuItem::addToMenu(mi, menu);
    314 		}
    315 		{
    316 			OptionsMenuItem* mi = new OptionsMenuItem("Normal range");
    317 			mi->addItem(RangeOptionMenuItem(m, "+/-10V", 0.0f, 10.0f));
    318 			mi->addItem(RangeOptionMenuItem(m, "+/-5V", 0.0f, 5.0f));
    319 			mi->addItem(RangeOptionMenuItem(m, "+/-3V", 0.0f, 3.0f));
    320 			mi->addItem(RangeOptionMenuItem(m, "+/-1V", 0.0f, 1.0f));
    321 			mi->addItem(RangeOptionMenuItem(m, "0V-10V", 1.0f, 5.0f));
    322 			mi->addItem(RangeOptionMenuItem(m, "0V-5V", 1.0f, 2.5f));
    323 			mi->addItem(RangeOptionMenuItem(m, "0V-3V", 1.0f, 1.5f));
    324 			mi->addItem(RangeOptionMenuItem(m, "0V-1V", 1.0f, 0.5f));
    325 			OptionsMenuItem::addToMenu(mi, menu);
    326 		}
    327 		menu->addChild(new SmoothMenuItem(m));
    328 	}
    329 };
    330 
    331 Model* modelSampleHold = bogaudio::createModel<SampleHold, SampleHoldWidget>("Bogaudio-SampleHold", "S&H", "Dual sample (or track) and hold", "Sample and hold", "Dual", "Polyphonic");