Skip to content

Commit

Permalink
Merge pull request #4 from Jadael/dev
Browse files Browse the repository at this point in the history
New "Sort" module
  • Loading branch information
Jadael authored May 4, 2024
2 parents dd23cce + 44babf3 commit 2e319ce
Show file tree
Hide file tree
Showing 11 changed files with 3,908 additions and 77 deletions.
Binary file modified dist/TMT/plugin.dll
Binary file not shown.
8 changes: 7 additions & 1 deletion dist/TMT/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"slug": "TMT",
"name": "T's Musical Tools",
"version": "2.1.6",
"version": "2.1.7",
"license": "MIT",
"brand": "T",
"author": "T",
Expand Down Expand Up @@ -90,6 +90,12 @@
"name": "Blankt",
"description": "A blank T's Musical Tools panel.",
"tags": []
},
{
"slug": "Sort",
"name": "Sort",
"description": "A tool to arbitrarily sort and select channels from a polyphonic cable using polyphonic CVs.",
"tags": []
}
]
}
103 changes: 70 additions & 33 deletions dist/TMT/res/stats.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@
"name": "Blankt",
"description": "A blank T's Musical Tools panel.",
"tags": []
},
{
"slug": "Sort",
"name": "Sort",
"description": "A tool to arbitrarily sort and select channels from a polyphonic cable using polyphonic CVs.",
"tags": []
}
]
}
465 changes: 465 additions & 0 deletions res/generic_small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3,069 changes: 3,069 additions & 0 deletions res/sort.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
103 changes: 70 additions & 33 deletions res/stats.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ void init(Plugin* p) {
p->addModel(modelSpellbook);
p->addModel(modelStats);
p->addModel(modelBlankt);
p->addModel(modelSort);
// Any other plugin initialization may go here.
// As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack.
}
3 changes: 2 additions & 1 deletion src/plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ extern Model* modelAppend;
extern Model* modelSight;
extern Model* modelSpellbook;
extern Model* modelStats;
extern Model* modelBlankt;
extern Model* modelBlankt;
extern Model* modelSort;
209 changes: 209 additions & 0 deletions src/sort.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#include "plugin.hpp"
#include "ports.hpp"

#define GRID_SNAP 10.16 // A 2hp grid in millimeters. 1 GRID_SNAP is just the right spacing for adjacent ports on the module

struct Timer {
// There's probably something in dsp which could handle this better,
// it was just easier to conceptualize as a simple "time since reset" which I can check however I want
float timePassed = 0.0f; // Time since timer start in seconds

void reset() { // Start timer at 0 on resets
timePassed = 0.0f;
}

void set(float seconds) { // Set the timer to something specific
timePassed = seconds;
}

void update(float deltaTime) { // Update the timer and check if the period has expired
timePassed += deltaTime;
}

float time() {// Return seconds since timer start
return timePassed;
}

bool check(float seconds) { // Return whether it's been at least <seconds> since the timer started
return timePassed >= seconds;
}
};

struct Sort : Module {
enum ParamId {
TOGGLE_SWITCH,
PARAMS_LEN
};
enum InputId {
DATA_INPUT,
SORT_INPUT,
SELECT_INPUT,
INPUTS_LEN
};
enum OutputId {
PASSTHRU_OUTPUT,
SORTED_OUTPUT,
SELECTED_OUTPUT,
SORTED_AND_SELECTED_OUTPUT,
SELECTED_AND_SORTED_OUTPUT,
ASCENDING_OUTPUT,
DESCENDING_OUTPUT,
OUTPUTS_LEN
};
enum LightId {
LIGHTS_LEN
};

Timer timeSinceUpdate;
// Put some sort of "three column" data structure here we can update and manipulate extremely efficiently (i.e. hopefully at audio-rate >=48kHz)

Sort() {
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
configParam(TOGGLE_SWITCH, 0.f, 1.f, 0.f, "Alt Mode: Process at audio rate (CPU heavy)");
configInput(DATA_INPUT, "Polyphonic Data");
configInput(SORT_INPUT, "Ordering");
configInput(SELECT_INPUT, "Selection");
configOutput(PASSTHRU_OUTPUT, "Pass through data");
configOutput(SORTED_OUTPUT, "Sorted data");
configOutput(SELECTED_OUTPUT, "Selected data");
configOutput(SORTED_AND_SELECTED_OUTPUT, "Sorted then selected data");
configOutput(SELECTED_AND_SORTED_OUTPUT, "Selected then sorted data");
configOutput(ASCENDING_OUTPUT, "Ascending sorted data");
configOutput(DESCENDING_OUTPUT, "Descending sorted data");
timeSinceUpdate.reset();
}


void process(const ProcessArgs& args) override {
timeSinceUpdate.update(args.sampleTime); // Advance the timer

if (!inputs[DATA_INPUT].isConnected()) {
for (int i = 0; i < OUTPUTS_LEN; ++i) {
outputs[i].setChannels(1);
outputs[i].setVoltage(0.0f, 0);
}
return; // Exit early if there's no input
}

if (!timeSinceUpdate.check(0.01f) && params[TOGGLE_SWITCH].getValue() < 0.5f) {
return; // Break early if we haven't reached our throttle time, unless we're in Alt mode
}

timeSinceUpdate.reset(); // Reset the timer

int maxChannels = std::max({inputs[DATA_INPUT].getChannels(), inputs[SORT_INPUT].getChannels(), inputs[SELECT_INPUT].getChannels()});

std::vector<float> dataValues(maxChannels);
std::vector<float> sortValues(maxChannels);
std::vector<float> selectValues(maxChannels);

for (int i = 0; i < maxChannels; i++) {
dataValues[i] = inputs[DATA_INPUT].getVoltage(i);
sortValues[i] = inputs[SORT_INPUT].isConnected() ? inputs[SORT_INPUT].getVoltage(i) : 0.0f;
selectValues[i] = inputs[SELECT_INPUT].isConnected() ? inputs[SELECT_INPUT].getVoltage(i) : 0.0f;
}

// Create a sorting index based on sortValues
std::vector<int> index(maxChannels);
std::iota(index.begin(), index.end(), 0);
std::stable_sort(index.begin(), index.end(), [&sortValues](int i, int j) { return sortValues[i] < sortValues[j]; });

// Apply sort to dataValues
std::vector<float> sortedData(maxChannels);
for (int i = 0; i < maxChannels; i++) {
sortedData[i] = dataValues[index[i]];
}

// Apply selection
std::vector<float> selectedData;
std::vector<int> selectedIndex;
for (int i = 0; i < maxChannels; i++) {
if (selectValues[i] > 5.0f) {
selectedData.push_back(dataValues[i]);
selectedIndex.push_back(index[i]);
}
}

// Sort selected data
std::vector<float> sortedSelectedData(selectedIndex.size());
for (size_t i = 0; i < selectedIndex.size(); i++) {
sortedSelectedData[i] = dataValues[selectedIndex[i]];
}
std::stable_sort(sortedSelectedData.begin(), sortedSelectedData.end());

// Ascending and Descending sorting of dataValues
std::vector<float> ascendingData = dataValues;
std::sort(ascendingData.begin(), ascendingData.end());
std::vector<float> descendingData = ascendingData; // Copy already sorted data
std::reverse(descendingData.begin(), descendingData.end()); // Reverse for descending order

// Outputs
outputs[PASSTHRU_OUTPUT].setChannels(maxChannels);
outputs[SORTED_OUTPUT].setChannels(maxChannels);
outputs[SELECTED_OUTPUT].setChannels(selectedData.size());
outputs[SORTED_AND_SELECTED_OUTPUT].setChannels(selectedData.size());
outputs[SELECTED_AND_SORTED_OUTPUT].setChannels(sortedSelectedData.size());
outputs[ASCENDING_OUTPUT].setChannels(maxChannels);
outputs[DESCENDING_OUTPUT].setChannels(maxChannels);

for (int i = 0; i < maxChannels; i++) {
outputs[PASSTHRU_OUTPUT].setVoltage(dataValues[i], i);
outputs[SORTED_OUTPUT].setVoltage(sortedData[i], i);
outputs[ASCENDING_OUTPUT].setVoltage(ascendingData[i], i);
outputs[DESCENDING_OUTPUT].setVoltage(descendingData[i], i);
}
for (size_t i = 0; i < selectedData.size(); i++) {
outputs[SELECTED_OUTPUT].setVoltage(selectedData[i], i);
outputs[SORTED_AND_SELECTED_OUTPUT].setVoltage(selectedData[i], i);
}
for (size_t i = 0; i < sortedSelectedData.size(); i++) {
outputs[SELECTED_AND_SORTED_OUTPUT].setVoltage(sortedSelectedData[i], i);
}
}
};

struct SortWidget : ModuleWidget {
SortWidget(Sort* module) {
setModule(module);
setPanel(createPanel(asset::plugin(pluginInstance, "res/sort.svg")));

// CONTROLS --------
addParam(createParamCentered<BrassToggle>(mm2px(Vec(15, 6)), module, Sort::TOGGLE_SWITCH));
// Toggle between sample rate and throttled rate

// INPUTS --------
addInput(createInputCentered<BrassPort>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*1.5)), module, Sort::DATA_INPUT));
// The polyphonic cable we are treating like our "data column"

addInput(createInputCentered<BrassPort>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*2.5)), module, Sort::SORT_INPUT));
// The polyphonic cable we are treating as our "index" or "ordering key column"

addInput(createInputCentered<BrassPort>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*3.5)), module, Sort::SELECT_INPUT));
// The polyphonic cable we are treating as our "crtieria" or "mask column"

// OUTPUTS --------
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*5)), module, Sort::PASSTHRU_OUTPUT));
// Pass through DATA_INPUT as-is

addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*6)), module, Sort::SORTED_OUTPUT));
// Use as the "index" or "sort key" when sorting data (i.e. for an Excel style rank() index)

addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*7)), module, Sort::SELECTED_OUTPUT));
// As as a "criteria" or "mask" for data - unselected channells are removed from the output set, not just muted (i.e. for an Excel style filter() criteria, where these voltages are treated like booleans)

addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*8)), module, Sort::SORTED_AND_SELECTED_OUTPUT));
// Sort the data according to the key, THEN apply the selection, and output a set of channels size to that new set

addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*9)), module, Sort::SELECTED_AND_SORTED_OUTPUT));
// Apply the selection, THEN sort the data according to the key, and output a set of channels size to that new set

addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*10)), module, Sort::ASCENDING_OUTPUT));
// The data sorted by its own ascending order, for convenience

addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*11)), module, Sort::DESCENDING_OUTPUT));
// The data sorted by its own descending order, for convenience
}
};


Model* modelSort = createModel<Sort, SortWidget>("Sort");
18 changes: 9 additions & 9 deletions src/stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,15 @@ struct StatsWidget : ModuleWidget {
addInput(createInputCentered<BrassPort>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*1.5)), module, Stats::POLY_INPUT));
// The input will be the upper leftmost port, and I think we could fit a column of 10 below it, with either labels to the right, or a second column of outputs if we can think of that many different stats to output

addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*2.5)), module, Stats::MEAN_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*3.5)), module, Stats::MEDIAN_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*4.5)), module, Stats::MODE_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*5.5)), module, Stats::GEOMETRIC_MEAN_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*6.5)), module, Stats::PRODUCT_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*7.5)), module, Stats::COUNT_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*8.5)), module, Stats::SUM_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*9.5)), module, Stats::ASCENDING_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*10.5)), module, Stats::DISTINCT_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*3)), module, Stats::MEAN_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*4)), module, Stats::MEDIAN_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*5)), module, Stats::MODE_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*6)), module, Stats::GEOMETRIC_MEAN_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*7)), module, Stats::PRODUCT_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*8)), module, Stats::COUNT_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*9)), module, Stats::SUM_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*10)), module, Stats::ASCENDING_OUTPUT));
addOutput(createOutputCentered<BrassPortOut>(mm2px(Vec(GRID_SNAP*1, GRID_SNAP*11)), module, Stats::DISTINCT_OUTPUT));
}
};

Expand Down

0 comments on commit 2e319ce

Please sign in to comment.