diff --git a/doc/PolySort-chained.png b/doc/PolySort-chained.png
new file mode 100644
index 0000000..3e2b342
Binary files /dev/null and b/doc/PolySort-chained.png differ
diff --git a/doc/PolySort.png b/doc/PolySort.png
new file mode 100644
index 0000000..eba14ad
Binary files /dev/null and b/doc/PolySort.png differ
diff --git a/res/PolySort.svg b/res/PolySort.svg
new file mode 100644
index 0000000..6b0ded4
--- /dev/null
+++ b/res/PolySort.svg
@@ -0,0 +1,455 @@
+
+
diff --git a/src/PolySort.cpp b/src/PolySort.cpp
new file mode 100644
index 0000000..e0328db
--- /dev/null
+++ b/src/PolySort.cpp
@@ -0,0 +1,167 @@
+#include
+
+#include "plugin.hpp"
+#include
+
+namespace Chinenual {
+namespace PolySort {
+
+ static const int NUM_INPUTS = 10;
+
+ struct PolySort : Module {
+ enum ParamId {
+ ENUMS(LINK_PARAM, NUM_INPUTS),
+ PARAMS_LEN
+ };
+ enum InputId {
+ ENUMS(IN_INPUT, NUM_INPUTS),
+ INPUTS_LEN
+ };
+ enum OutputId {
+ ENUMS(OUT_OUTPUT, NUM_INPUTS),
+ OUTPUTS_LEN
+ };
+ enum LightId {
+ ENUMS(LINK_LIGHT, NUM_INPUTS),
+ LIGHTS_LEN
+ };
+
+ PolySort()
+ {
+ config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
+ for (int i = 0; i < NUM_INPUTS; i++) {
+ configParam(LINK_PARAM + i, 0.f, 1.f, 0.f, string::f("Use input %d's sort order", i));
+ configInput(IN_INPUT + i, string::f("%d", i + 1));
+ configOutput(OUT_OUTPUT + i, string::f("%d", i + 1));
+ }
+ onReset();
+ }
+
+ void
+ onReset() override
+ {
+ for (int i = 0; i < NUM_INPUTS; i++) {
+ params[LINK_PARAM + i].setValue(0.f);
+ }
+ }
+
+ json_t* dataToJson() override
+ {
+ json_t* rootJ = json_object();
+ json_t* arrayJ = json_array();
+ for (int i = 0; i < 8; i++) {
+ json_array_append(arrayJ, json_boolean(0.f != params[LINK_PARAM + i].getValue()));
+ }
+ json_object_set_new(rootJ, "links", arrayJ);
+ return rootJ;
+ }
+
+ void dataFromJson(json_t* rootJ) override
+ {
+ if (rootJ == 0)
+ return;
+ json_t* arrayJ = json_object_get(rootJ, "links");
+ if (arrayJ) {
+ for (int i = 0; i < 8; i++) {
+ json_t* eleJ = json_array_get(arrayJ, i);
+ if (eleJ) {
+ bool val = json_boolean_value(eleJ);
+ params[LINK_PARAM + i].setValue(val);
+ }
+ }
+ }
+ }
+
+ void process(const ProcessArgs& args) override
+ {
+ typedef struct {
+ float voltage;
+ int order;
+ } SortData;
+ std::array sorted;
+ for (int i = 0; i < NUM_INPUTS; i++) {
+ // the first input has no "link" (the link[0] param is ignored)
+ bool useLink = (i > 0 && params[LINK_PARAM + i].getValue());
+ if (i > 0) {
+ lights[LINK_LIGHT + i].setBrightness(params[LINK_PARAM + i].getValue() ? 1.0f : 0.0f);
+ }
+
+ for (int ch = 0; ch < 16; ch++) {
+ sorted[ch].voltage = inputs[IN_INPUT + i].getVoltage(ch);
+ if (useLink) {
+ // reuse the sort order from the previous input
+ } else {
+ sorted[ch].order = ch;
+ }
+ }
+ outputs[OUT_OUTPUT + i].setChannels(inputs[IN_INPUT + i].getChannels());
+ if (inputs[IN_INPUT + i].isConnected()) {
+ if (useLink) {
+ for (int ch = 0; ch < 16; ch++) {
+ int idx = sorted[ch].order;
+ outputs[OUT_OUTPUT + i].setVoltage(sorted[idx].voltage, ch);
+ }
+ } else {
+ std::sort(sorted.begin(), sorted.begin() + inputs[IN_INPUT + i].getChannels(),
+ [](const SortData& a, const SortData& b) {
+ return a.voltage < b.voltage;
+ });
+ for (int ch = 0; ch < 16; ch++) {
+ outputs[OUT_OUTPUT + i].setVoltage(sorted[ch].voltage, ch);
+ }
+ }
+ }
+ }
+ }
+ };
+
+#define FIRST_X 5.0
+#define FIRST_Y 17.0
+#define SPACING_X 10.0
+#define SPACING_Y 10.5
+#define LED_OFFSET_X 3.0
+#define LED_OFFSET_Y -9.5
+#define BUTTON_OFFSET_X -1.0
+#define BUTTON_OFFSET_Y -5.0
+
+ struct PolySortWidget : ModuleWidget {
+
+ struct SortLight : GrayModuleLightWidget {
+ SortLight()
+ {
+ addBaseColor(nvgRGB(0xff, 0x33, 0x33));
+ }
+ };
+ PolySortWidget(PolySort* module)
+ {
+ setModule(module);
+ setPanel(
+ createPanel(asset::plugin(pluginInstance, "res/PolySort.svg")));
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(
+ createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(
+ Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH,
+ RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+
+ for (int i = 0; i < NUM_INPUTS; i++) {
+ if (i > 0) {
+ addChild(createParamCentered(mm2px(Vec(FIRST_X, FIRST_Y + i * SPACING_Y)), module, PolySort::LINK_PARAM + i));
+ addChild(createLightCentered>(mm2px(Vec(FIRST_X, FIRST_Y + i * SPACING_Y)), module, PolySort::LINK_LIGHT + i));
+ }
+ addInput(createInputCentered(
+ mm2px(Vec(FIRST_X + SPACING_X, FIRST_Y + i * SPACING_Y)), module,
+ PolySort::IN_INPUT + i));
+ addOutput(createOutputCentered(
+ mm2px(Vec(FIRST_X + 2 * SPACING_X, FIRST_Y + i * SPACING_Y)), module,
+ PolySort::OUT_OUTPUT + i));
+ }
+ }
+ };
+
+} // namespace PolySort
+} // namespace Chinenual
+
+Model* modelPolySort = createModel("PolySort");