gen-rack

Create VCV Rack modules from gen~ exports
Log | Files | Refs | README | LICENSE

module.in (10897B)


      1 #include "plugin.hpp"
      2 #include "GEN_RACK_DISPLAY_NAME.h"
      3 #include <array>
      4 
      5 using namespace GEN_RACK_DISPLAY_NAME;
      6 
      7 
      8 /// Processing
      9 
     10 struct GEN_RACK_NAME : Module {
     11 	CommonState *moduleState;
     12 	t_sample **inputBuffers;
     13 	t_sample **outputBuffers;
     14 
     15 	std::array<int, 10> validBufferSizes = { 1, 4, 16, 32, 64, 128, 256, 512, 1024, 2048 };
     16 	int currentBufferSize = 256;
     17 	int currentSampleRate = 44100;
     18 
     19 	int numParams;
     20 	int numInputs;
     21 	int numOutputs;
     22 
     23 	int count = 0;
     24 
     25 	GEN_RACK_NAME() {
     26 		moduleState = (CommonState *)create(currentSampleRate, currentBufferSize);
     27 		reset(moduleState);
     28 
     29 		numParams = num_params();
     30 		numInputs = num_inputs();
     31 		numOutputs = num_outputs();
     32 
     33 		// Initialize sample buffers
     34 		inputBuffers = new t_sample *[numInputs];
     35 		for (int i = 0; i < numInputs; i++) {
     36 			inputBuffers[i] = new t_sample[currentBufferSize];
     37 		}
     38 
     39 		outputBuffers = new t_sample *[numOutputs];
     40 		for (int i = 0; i < numOutputs; i++) {
     41 			outputBuffers[i] = new t_sample[currentBufferSize];
     42 		}
     43 
     44 
     45 		// Configure parameters
     46 		config(numParams, numInputs + numParams, numOutputs, 0);
     47 		for (int i = 0; i < numParams; i++) {
     48 			std::string name = std::string(getparametername(moduleState, i));
     49 			std::string units = std::string(getparameterunits(moduleState, i));
     50 			float min = 0.0;
     51 			float max = 1.0;
     52 			if (getparameterhasminmax(moduleState, i)) {
     53 				min = getparametermin(moduleState, i);
     54 				max = getparametermax(moduleState, i);
     55 			}
     56 			configParam(i, min, max, min, name, units);
     57 		}
     58 	}
     59 
     60 
     61 	~GEN_RACK_NAME() {
     62 		destroy(moduleState);
     63 		deleteBuffers();
     64 	}
     65 
     66 	void deleteBuffers() {
     67 		for (int i = 0; i < numInputs; i++) {
     68 			if (inputBuffers[i]) {
     69 				delete inputBuffers[i];
     70 			}
     71 		}
     72 
     73 		for (int i = 0; i < numOutputs; i++) {
     74 			if (outputBuffers[i]) {
     75 				delete outputBuffers[i];
     76 			}
     77 		}
     78 	}
     79 
     80 	void setBufferSize(long bufferSize) {
     81 		if (bufferSize > currentBufferSize) {
     82 			for (int i = 0; i < numInputs; i++) {
     83 				if (inputBuffers[i]) {
     84 					delete inputBuffers[i];
     85 				}
     86 				inputBuffers[i] = new t_sample[bufferSize];
     87 			}
     88 
     89 			for (int i = 0; i < numOutputs; i++) {
     90 				if (outputBuffers[i]) {
     91 					delete outputBuffers[i];
     92 				}
     93 				outputBuffers[i] = new t_sample[bufferSize];
     94 			}
     95 		}
     96 		currentBufferSize = bufferSize;
     97 		moduleState->vs = currentBufferSize;
     98 		count = 0;
     99 	}
    100 
    101 
    102 	void process(const ProcessArgs& args) override {
    103 		if (args.sampleRate != currentSampleRate) {
    104 			moduleState->sr = args.sampleRate;
    105 			currentSampleRate = args.sampleRate;
    106 		}
    107 
    108 		if (count >= currentBufferSize) {
    109 			count = 0;
    110 		}
    111 		
    112 		// Fill inputs
    113 		for (int i = 0; i < numInputs; i++) {
    114 			if (inputs[i].isConnected()) {
    115 				inputBuffers[i][count] = inputs[i].getVoltage() / 5.f;
    116 			}
    117 			else {
    118 				inputBuffers[i][count] = 0.f;
    119 			}
    120 		}
    121 
    122 		// Set output
    123 		for (int i = 0; i < numOutputs; i++) {
    124 			outputs[i].setVoltage(outputBuffers[i][count] * 5.f);
    125 		}
    126 
    127 		// Step forward
    128 		count++;
    129 
    130 		// Perform when we've filled the buffer
    131 		if (count == currentBufferSize) {
    132 			// Update any parameters
    133 			for (int i = 0; i < numParams; i++) {
    134 				// Get VCV inputs
    135 				float knobVal = params[i].getValue();  // Already scaled to range that genlib will understand
    136 				float cvVal = inputs[i + numInputs].isConnected() ? inputs[i + numInputs].getVoltage() / 5.f : 0.f;  // Normalize to -1..1
    137 
    138 				// Scale to range of parameter
    139 				t_param min = getparametermin(moduleState, i);
    140 				t_param max = getparametermax(moduleState, i);
    141 				t_param range = fabs(max - min);
    142 				t_param val = clamp(knobVal + cvVal * range, min, max); // Offset the knobVal by the CV input
    143 
    144 				setparameter(moduleState, i, val, NULL);
    145 			}
    146 
    147 			// Fill the buffers
    148 			perform(moduleState, inputBuffers, numInputs, outputBuffers, numOutputs, currentBufferSize);
    149 		}
    150 	}
    151 };
    152 
    153 
    154 /// Main module UI
    155 
    156 struct BufferSizeMenuItem : MenuItem {
    157 	GEN_RACK_NAME* module;
    158 	int bufferSize;
    159 	void onAction(const event::Action& e) override {
    160 		if (!module) return;
    161 		module->setBufferSize(bufferSize);
    162 	}
    163 };
    164 
    165 struct GEN_RACK_NAMEWidget : ModuleWidget {
    166 	int numParams;
    167 	int numInputs;
    168 	int numOutputs;
    169 
    170 	std::vector<std::string> inputLabels;
    171 	std::vector<std::string> outputLabels;
    172 	std::vector<std::string> paramLabels;
    173 
    174 	// Each column of ports has a certain number of "cells" that contain a port and label. 
    175 	int ports_per_col = 6;
    176 
    177 	// Each column of params has a certain number of "cells" that contain a port, a label, and a knob.
    178 	int params_per_col = 4;
    179 	
    180 	// Box off the actual section of "cells" with a margin
    181 	int l_margin = RACK_GRID_WIDTH;
    182 	int r_margin = RACK_GRID_WIDTH;
    183 	int bot_margin = 2 * RACK_GRID_WIDTH;
    184 	// The title and top margin together make up the top band of margin
    185 	int top_margin = RACK_GRID_WIDTH;
    186 	int h_title = 3 * RACK_GRID_WIDTH;
    187 	// The height of the actual part that will contain ports and knobs
    188 	int active_box_height = RACK_GRID_HEIGHT - bot_margin - h_title - top_margin;
    189 
    190 	// A column will take up 3HP
    191 	int w_col = 3 * RACK_GRID_WIDTH;
    192 
    193 	// Offset from the top of a cell to the knobs, ports, and labels
    194 	float port_center_offset = active_box_height / ports_per_col * 0.25f;
    195 	float label_port_offset = active_box_height / ports_per_col * 0.55f;
    196 	float param_knob_center_offset = active_box_height / params_per_col * 0.25f;
    197 	float param_port_center_offset = active_box_height / params_per_col * 0.65f;
    198 	float param_label_offset = active_box_height / params_per_col * 0.85f;
    199 
    200 	int module_hp = 12;
    201 	
    202 	genrack::Panel *panel;
    203 	bool dirty = false;
    204 
    205 
    206 	GEN_RACK_NAMEWidget(GEN_RACK_NAME* module) {
    207 		setModule(module);
    208 		box.size = Vec(RACK_GRID_WIDTH * module_hp, RACK_GRID_HEIGHT);
    209 
    210 		// Default background and title for module browser - will be drawn over when step() is called
    211 		panel = new genrack::Panel(40, 40, 40);
    212 		addChild(panel);
    213 		panel->box.size = box.size;
    214 		genrack::Title *title = new genrack::Title(box.size.x / 2, top_margin, box.size.x, "GEN_RACK_DISPLAY_NAME");
    215 		addChild(title);
    216 
    217 		if (module) {
    218 			// Make these publically accessible to the widget
    219 			numParams = module->numParams;
    220 			numInputs = module->numInputs;
    221 			numOutputs = module->numOutputs;
    222 
    223 			for (int i = 0; i < numInputs; i++) {
    224 				std::string inputLabel = std::string("in ") + std::to_string(i + 1);
    225 				inputLabels.push_back(inputLabel);
    226 			}
    227 
    228 			for (int i = 0; i < numOutputs; i++) {
    229 				std::string outputLabel = std::string("out ") + std::to_string(i + 1);
    230 				outputLabels.push_back(outputLabel);
    231 			}
    232 
    233 			for (int i = 0; i < numParams; i++) {
    234 				std::string paramLabel = std::string(getparametername(module->moduleState, i));
    235 				paramLabel.resize(10);
    236 				paramLabels.push_back(paramLabel);
    237 			}
    238 
    239 			// Figure out the width of the module
    240 			module_hp = 2 + 3 * (genrack::util::int_div_round_up(numInputs, ports_per_col)
    241 						  + genrack::util::int_div_round_up(numOutputs, ports_per_col)
    242 						  + genrack::util::int_div_round_up(numParams, params_per_col));
    243 
    244 			box.size = Vec(RACK_GRID_WIDTH * module_hp, RACK_GRID_HEIGHT);
    245 
    246 			// Draw on the next step
    247 			dirty = true;
    248 			step();
    249 		}
    250 	}
    251 
    252 
    253 	// Runs with every UI frame update
    254 	void step() override {
    255 
    256 		// The widget will be dirtied after the module is registered in the constructor
    257 		if (dirty) {
    258 			// Background panel
    259 			panel = new genrack::Panel(40, 40, 40);
    260 			addChild(panel);
    261 			panel->box.size = box.size;
    262 
    263 			// Title text
    264 			genrack::Title *title = new genrack::Title(box.size.x / 2, top_margin, box.size.x, "GEN_RACK_DISPLAY_NAME");
    265 			addChild(title);
    266 
    267 			// Screws
    268 			addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
    269 			addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 0)));
    270 			addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 365)));
    271 			addChild(createWidget<ScrewSilver>(Vec(box.size.x - 30, 365)));
    272 
    273 			// PORTS, PARAMS, LABELS
    274 			for (int i = 0; i < numInputs; i++) {
    275 				float left_x = l_margin
    276 								 + int(i / ports_per_col) * w_col;
    277 				float center_x = left_x + w_col / 2;
    278 
    279 				float top_y = top_margin + h_title + (i % ports_per_col) * (active_box_height / ports_per_col);
    280 				float port_center_y = top_y + port_center_offset;
    281 				float label_center_y = top_y + label_port_offset;
    282 
    283 				addInput(createInputCentered<PJ301MPort>(Vec(center_x, port_center_y), module, i));
    284 
    285 				genrack::TextLabel *label = new genrack::TextLabel(center_x, label_center_y, left_x, inputLabels[i].c_str(), nvgRGB(230, 230, 230), 10);
    286 				addChild(label);
    287 			}
    288 			
    289 			for (int i = 0; i < numParams; i++) {
    290 				float left_x = l_margin 
    291 								+ genrack::util::int_div_round_up(numInputs, ports_per_col) * w_col 
    292 								+ int(i / params_per_col) * w_col;				
    293 				float center_x = left_x + w_col / 2;
    294 
    295 				float top_y = top_margin + h_title + (i % params_per_col) * (active_box_height / params_per_col);
    296 
    297 				float knob_center_y = top_y + param_knob_center_offset;
    298 				float port_center_y = top_y + param_port_center_offset;
    299 				float label_center_y = top_y + param_label_offset;
    300 
    301 				addParam(createParamCentered<RoundSmallBlackKnob>(Vec(center_x, knob_center_y), module, i));
    302 				addInput(createInputCentered<PJ301MPort>(Vec(center_x, port_center_y), module, i + numInputs));
    303 
    304 				genrack::TextLabel *label = new genrack::TextLabel(center_x, label_center_y, left_x, paramLabels[i].c_str(), nvgRGB(230, 230, 230), 10);
    305 				addChild(label);
    306 			}
    307 			
    308 			for (int i = 0; i < numOutputs; i++) {
    309 				float left_x = l_margin 
    310 								+ genrack::util::int_div_round_up(numInputs, ports_per_col) * w_col 
    311 								+ genrack::util::int_div_round_up(numParams, params_per_col) * w_col 
    312 								+ int(i / ports_per_col) * w_col;
    313 
    314 				float center_x = left_x + w_col / 2;
    315 
    316 				float top_y = top_margin + h_title + (i % ports_per_col) * (active_box_height / ports_per_col);
    317 				float port_center_y = top_y + port_center_offset;
    318 				float label_center_y = top_y + label_port_offset;
    319 
    320 				addOutput(createOutputCentered<PJ301MPort>(Vec(center_x, port_center_y), module, i));
    321 
    322 				genrack::TextLabel *label = new genrack::TextLabel(center_x, label_center_y, left_x, outputLabels[i].c_str(), nvgRGB(230, 230, 230), 10);
    323 				addChild(label);
    324 			}
    325 
    326 			dirty = false;
    327 		}
    328 
    329 		ModuleWidget::step();
    330 	}
    331 
    332 
    333 	void appendContextMenu(Menu* menu) override {
    334 		GEN_RACK_NAME* module = dynamic_cast<GEN_RACK_NAME*>(this->module);
    335 
    336 		// Buffer sizes selection
    337 		menu->addChild(new MenuSeparator());
    338 		MenuItem* bufferSizeLabel = new MenuItem;
    339 		bufferSizeLabel->disabled = true;
    340 		bufferSizeLabel->text = "Buffer size";
    341 		menu->addChild(bufferSizeLabel);
    342 
    343 		for (int i = 0; i < (int) module->validBufferSizes.size(); i++) {
    344 			BufferSizeMenuItem* item = new BufferSizeMenuItem;
    345 			item->module = module;
    346 			item->text = std::to_string(module->validBufferSizes[i]).c_str();
    347 			item->rightText = CHECKMARK(module->currentBufferSize == module->validBufferSizes[i]);
    348 			item->bufferSize = module->validBufferSizes[i];
    349 			menu->addChild(item);
    350 		}
    351 	}
    352 };
    353 
    354 
    355 /// Register the model
    356 Model* modelGEN_RACK_NAME = createModel<GEN_RACK_NAME, GEN_RACK_NAMEWidget>("GEN_RACK_DISPLAY_NAME");