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");