Browse Source

Parse plugin metadata from plugin.json

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
217faca052
29 changed files with 308 additions and 257 deletions
  1. +3
    -3
      Makefile
  2. +0
    -5
      compile.mk
  3. +3
    -1
      include/engine/Param.hpp
  4. +10
    -10
      include/helpers.hpp
  5. +3
    -3
      include/network.hpp
  6. +3
    -0
      include/plugin.hpp
  7. +8
    -8
      include/plugin/Model.hpp
  8. +13
    -5
      include/plugin/Plugin.hpp
  9. +10
    -8
      include/string.hpp
  10. +6
    -6
      include/system.hpp
  11. +0
    -75
      include/tags.hpp
  12. +4
    -4
      plugin.mk
  13. +1
    -1
      src/Core/AudioInterface.cpp
  14. +1
    -1
      src/Core/Blank.cpp
  15. +1
    -1
      src/Core/MIDICCToCVInterface.cpp
  16. +1
    -1
      src/Core/MIDIToCVInterface.cpp
  17. +1
    -1
      src/Core/MIDITriggerToCVInterface.cpp
  18. +1
    -1
      src/Core/Notes.cpp
  19. +1
    -1
      src/Core/QuadMIDIToCVInterface.cpp
  20. +29
    -27
      src/app/ModuleBrowser.cpp
  21. +1
    -1
      src/app/ModuleWidget.cpp
  22. +0
    -2
      src/main.cpp
  23. +3
    -3
      src/network.cpp
  24. +94
    -7
      src/plugin.cpp
  25. +27
    -0
      src/plugin/Model.cpp
  26. +71
    -0
      src/plugin/Plugin.cpp
  27. +7
    -9
      src/string.cpp
  28. +6
    -6
      src/system.cpp
  29. +0
    -67
      src/tags.cpp

+ 3
- 3
Makefile View File

@@ -1,9 +1,9 @@
RACK_DIR ?= . RACK_DIR ?= .
VERSION = 1.dev VERSION = 1.dev


FLAGS += \
-Iinclude \
-Idep/include -Idep/lib/libzip/include
FLAGS += -DVERSION=$(VERSION)
FLAGS += -Iinclude
FLAGS += -Idep/include -Idep/lib/libzip/include


include arch.mk include arch.mk




+ 0
- 5
compile.mk View File

@@ -2,15 +2,10 @@ ifndef RACK_DIR
$(error RACK_DIR is not defined) $(error RACK_DIR is not defined)
endif endif


ifndef VERSION
$(error VERSION is not defined)
endif

include $(RACK_DIR)/arch.mk include $(RACK_DIR)/arch.mk


OBJCOPY ?= objcopy OBJCOPY ?= objcopy


FLAGS += -DVERSION=$(VERSION)
# Generate dependency files alongside the object files # Generate dependency files alongside the object files
FLAGS += -MMD -MP FLAGS += -MMD -MP
FLAGS += -g FLAGS += -g


+ 3
- 1
include/engine/Param.hpp View File

@@ -20,7 +20,7 @@ struct Param {
std::string label; std::string label;
std::string unit; std::string unit;


void setup(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", int displayPrecision = 2) {
void setup(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", int displayPrecision = 2, float displayBase = 0.f, float displayMultiplier = 1.f) {
this->value = defaultValue; this->value = defaultValue;
this->minValue = minValue; this->minValue = minValue;
this->maxValue = maxValue; this->maxValue = maxValue;
@@ -28,6 +28,8 @@ struct Param {
this->label = label; this->label = label;
this->unit = unit; this->unit = unit;
this->displayPrecision = displayPrecision; this->displayPrecision = displayPrecision;
this->displayBase = displayBase;
this->displayMultiplier = displayMultiplier;
} }


json_t *toJson(); json_t *toJson();


+ 10
- 10
include/helpers.hpp View File

@@ -12,7 +12,7 @@ namespace rack {




template <class TModule, class TModuleWidget, typename... Tags> template <class TModule, class TModuleWidget, typename... Tags>
Model *createModel(std::string author, std::string slug, std::string name, Tags... tags) {
Model *createModel(std::string slug) {
struct TModel : Model { struct TModel : Model {
Module *createModule() override { Module *createModule() override {
TModule *o = new TModule; TModule *o = new TModule;
@@ -32,10 +32,7 @@ Model *createModel(std::string author, std::string slug, std::string name, Tags.
}; };


Model *o = new TModel; Model *o = new TModel;
o->author = author;
o->slug = slug; o->slug = slug;
o->name = name;
o->tags = {tags...};
return o; return o;
} }


@@ -46,25 +43,28 @@ TWidget *createWidget(math::Vec pos) {
return o; return o;
} }


template <class TWidget>
TWidget *createWidgetCentered(math::Vec pos) {
TWidget *o = new TWidget;
o->box.pos = pos.minus(o->box.size.div(2));;
return o;
}

template <class TParamWidget> template <class TParamWidget>
TParamWidget *createParam(math::Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) {
TParamWidget *createParam(math::Vec pos, Module *module, int paramId) {
TParamWidget *o = new TParamWidget; TParamWidget *o = new TParamWidget;
o->box.pos = pos; o->box.pos = pos;
o->quantity->module = module; o->quantity->module = module;
o->quantity->paramId = paramId; o->quantity->paramId = paramId;
o->setLimits(minValue, maxValue);
o->setDefaultValue(defaultValue);
return o; return o;
} }


template <class TParamWidget> template <class TParamWidget>
TParamWidget *createParamCentered(math::Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) {
TParamWidget *createParamCentered(math::Vec pos, Module *module, int paramId) {
TParamWidget *o = new TParamWidget; TParamWidget *o = new TParamWidget;
o->box.pos = pos.minus(o->box.size.div(2)); o->box.pos = pos.minus(o->box.size.div(2));
o->quantity->module = module; o->quantity->module = module;
o->quantity->paramId = paramId; o->quantity->paramId = paramId;
o->setLimits(minValue, maxValue);
o->setDefaultValue(defaultValue);
return o; return o;
} }




+ 3
- 3
include/network.hpp View File

@@ -19,11 +19,11 @@ Caller must json_decref().
*/ */
json_t *requestJson(Method method, std::string url, json_t *dataJ); json_t *requestJson(Method method, std::string url, json_t *dataJ);
/** Returns true if downloaded successfully */ /** Returns true if downloaded successfully */
bool requestDownload(std::string url, std::string filename, float *progress);
bool requestDownload(std::string url, const std::string &filename, float *progress);
/** URL-encodes `s` */ /** URL-encodes `s` */
std::string encodeUrl(std::string s);
std::string encodeUrl(const std::string &s);
/** Computes the SHA256 of the file at `filename` */ /** Computes the SHA256 of the file at `filename` */
std::string computeSHA256File(std::string filename);
std::string computeSHA256File(const std::string &filename);




} // namespace network } // namespace network


+ 3
- 0
include/plugin.hpp View File

@@ -2,6 +2,7 @@
#include "common.hpp" #include "common.hpp"
#include "plugin/Plugin.hpp" #include "plugin/Plugin.hpp"
#include "plugin/Model.hpp" #include "plugin/Model.hpp"
#include <vector>
#include <list> #include <list>




@@ -19,6 +20,7 @@ void cancelDownload();
bool isLoggedIn(); bool isLoggedIn();
Plugin *getPlugin(std::string pluginSlug); Plugin *getPlugin(std::string pluginSlug);
Model *getModel(std::string pluginSlug, std::string modelSlug); Model *getModel(std::string pluginSlug, std::string modelSlug);
std::string getAllowedTag(std::string tag);




extern std::list<Plugin*> plugins; extern std::list<Plugin*> plugins;
@@ -27,6 +29,7 @@ extern bool isDownloading;
extern float downloadProgress; extern float downloadProgress;
extern std::string downloadName; extern std::string downloadName;
extern std::string loginStatus; extern std::string loginStatus;
extern const std::vector<std::string> allowedTags;




} // namespace plugin } // namespace plugin


+ 8
- 8
include/plugin/Model.hpp View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "common.hpp" #include "common.hpp"
#include "plugin/Plugin.hpp" #include "plugin/Plugin.hpp"
#include "tags.hpp"
#include <jansson.h>
#include <list> #include <list>




@@ -14,19 +14,17 @@ struct Module;


struct Model { struct Model {
Plugin *plugin = NULL; Plugin *plugin = NULL;

/** An identifier for the model, e.g. "VCO". Used for saving patches. /** An identifier for the model, e.g. "VCO". Used for saving patches.
The model slug must be unique in your plugin, but it doesn't need to be unique among different plugins. The model slug must be unique in your plugin, but it doesn't need to be unique among different plugins.
*/ */
std::string slug; std::string slug;
/** Human readable name for your model, e.g. "Voltage Controlled Oscillator" */ /** Human readable name for your model, e.g. "Voltage Controlled Oscillator" */
std::string name; std::string name;
/** The author name of the module.
This might be different than the plugin slug. For example, if you create multiple plugins but want them to be branded similarly, you may use the same author in multiple plugins.
You may even have multiple authors in one plugin, although this property will be moved to Plugin for Rack 1.0.
*/
std::string author;
/** List of tags representing the function(s) of the module (optional) */
std::list<ModelTag> tags;
/** A one-line summary of the module's purpose */
std::string description;
/** List of tags representing the function(s) of the module */
std::list<std::string> tags;


virtual ~Model() {} virtual ~Model() {}
/** Creates a headless Module */ /** Creates a headless Module */
@@ -35,6 +33,8 @@ struct Model {
virtual ModuleWidget *createModuleWidget() { return NULL; } virtual ModuleWidget *createModuleWidget() { return NULL; }
/** Creates a ModuleWidget with no Module, useful for previews */ /** Creates a ModuleWidget with no Module, useful for previews */
virtual ModuleWidget *createModuleWidgetNull() { return NULL; } virtual ModuleWidget *createModuleWidgetNull() { return NULL; }

void fromJson(json_t *rootJ);
}; };






+ 13
- 5
include/plugin/Plugin.hpp View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "common.hpp" #include "common.hpp"
#include <jansson.h>
#include <list> #include <list>




@@ -22,19 +23,26 @@ struct Plugin {
To guarantee uniqueness, it is a good idea to prefix the slug by your "company name" if available, e.g. "MyCompany-MyPlugin" To guarantee uniqueness, it is a good idea to prefix the slug by your "company name" if available, e.g. "MyCompany-MyPlugin"
*/ */
std::string slug; std::string slug;

/** The version of your plugin /** The version of your plugin
Plugins should follow the versioning scheme described at https://github.com/VCVRack/Rack/issues/266 Plugins should follow the versioning scheme described at https://github.com/VCVRack/Rack/issues/266
Do not include the "v" in "v1.0" for example. Do not include the "v" in "v1.0" for example.
*/ */
std::string version; std::string version;

/** Deprecated, do not use. */
std::string website;
std::string manual;
/** Human readable name for your plugin, e.g. "Voltage Controlled Oscillator" */
std::string name;
std::string author;
std::string license;
std::string authorEmail;
std::string pluginUrl;
std::string authorUrl;
std::string manualUrl;
std::string sourceUrl;
std::string donateUrl;


virtual ~Plugin(); virtual ~Plugin();
void addModel(Model *model); void addModel(Model *model);
Model *getModel(std::string slug);
void fromJson(json_t *rootJ);
}; };






+ 10
- 8
include/string.hpp View File

@@ -6,23 +6,25 @@ namespace rack {
namespace string { namespace string {




/** Converts a printf format string and optional arguments into a std::string */
/** Converts a `printf()` format string and optional arguments into a std::string
Remember that "%s" must reference a `char *`, so use `.c_str()` for `std::string`s.
*/
std::string f(const char *format, ...); std::string f(const char *format, ...);
/** Replaces all characters to lowercase letters */ /** Replaces all characters to lowercase letters */
std::string lowercase(std::string s); std::string lowercase(std::string s);
/** Replaces all characters to uppercase letters */ /** Replaces all characters to uppercase letters */
std::string uppercase(std::string s); std::string uppercase(std::string s);
/** Truncates and adds "..." to a string, not exceeding `len` characters */ /** Truncates and adds "..." to a string, not exceeding `len` characters */
std::string ellipsize(std::string s, size_t len);
bool startsWith(std::string str, std::string prefix);
bool endsWith(std::string str, std::string suffix);
std::string ellipsize(const std::string &s, size_t len);
bool startsWith(const std::string &str, const std::string &prefix);
bool endsWith(const std::string &str, const std::string &suffix);
/** Extracts portions of a path */ /** Extracts portions of a path */
std::string directory(std::string path);
std::string filename(std::string path);
std::string directory(const std::string &path);
std::string filename(const std::string &path);
/** Extracts the portion of a path without the extension */ /** Extracts the portion of a path without the extension */
std::string basename(std::string path);
std::string basename(const std::string &path);
/** Extracts the extension of a path */ /** Extracts the extension of a path */
std::string extension(std::string path);
std::string extension(const std::string &path);


struct CaseInsensitiveCompare { struct CaseInsensitiveCompare {
bool operator()(const std::string &a, const std::string &b) const { bool operator()(const std::string &a, const std::string &b) const {


+ 6
- 6
include/system.hpp View File

@@ -7,17 +7,17 @@ namespace rack {
namespace system { namespace system {




std::list<std::string> listEntries(std::string path);
bool isFile(std::string path);
bool isDirectory(std::string path);
void copyFile(std::string srcPath, std::string destPath);
void createDirectory(std::string path);
std::list<std::string> listEntries(const std::string &path);
bool isFile(const std::string &path);
bool isDirectory(const std::string &path);
void copyFile(const std::string &srcPath, const std::string &destPath);
void createDirectory(const std::string &path);


/** Opens a URL, also happens to work with PDFs and folders. /** Opens a URL, also happens to work with PDFs and folders.
Shell injection is possible, so make sure the URL is trusted or hard coded. Shell injection is possible, so make sure the URL is trusted or hard coded.
May block, so open in a new thread. May block, so open in a new thread.
*/ */
void openBrowser(std::string url);
void openBrowser(const std::string &url);




} // namespace system } // namespace system


+ 0
- 75
include/tags.hpp View File

@@ -1,75 +0,0 @@
#pragma once
#include "common.hpp"


namespace rack {


/** Describes the type(s) of each module
To see comments, turn word wrap on. I'm using inline comments so I can automatically sort the list when more tags are added.
*/
enum ModelTag {
NO_TAG, // Don't use this in `Model::create(...)`. Instead, just omit the tags entirely.
AMPLIFIER_TAG,
ARPEGGIATOR_TAG,
ATTENUATOR_TAG,
BLANK_TAG,
CHORUS_TAG,
CLOCK_MODULATOR_TAG, // Clock dividers, multipliers, etc.
CLOCK_TAG,
COMPRESSOR_TAG,
CONTROLLER_TAG, // Use only if the artist "performs" with this module. Knobs are not sufficient. Examples: on-screen keyboard, XY pad.
DELAY_TAG,
DIGITAL_TAG,
DISTORTION_TAG,
DRUM_TAG,
DUAL_TAG, // The core functionality times two. If multiple channels are a requirement for the module to exist (ring modulator, mixer, etc), it is not a Dual module.
DYNAMICS_TAG,
EFFECT_TAG,
ENVELOPE_FOLLOWER_TAG,
ENVELOPE_GENERATOR_TAG,
EQUALIZER_TAG,
EXTERNAL_TAG,
FILTER_TAG,
FLANGER_TAG,
FUNCTION_GENERATOR_TAG,
GRANULAR_TAG,
LFO_TAG,
LIMITER_TAG,
LOGIC_TAG,
LOW_PASS_GATE_TAG,
MIDI_TAG,
MIXER_TAG,
MULTIPLE_TAG,
NOISE_TAG,
OSCILLATOR_TAG,
PANNING_TAG,
PHASER_TAG,
PHYSICAL_MODELING_TAG,
QUAD_TAG, // The core functionality times four. If multiple channels are a requirement for the module to exist (ring modulator, mixer, etc), it is not a Quad module.
QUANTIZER_TAG,
RANDOM_TAG,
RECORDING_TAG,
REVERB_TAG,
RING_MODULATOR_TAG,
SAMPLE_AND_HOLD_TAG,
SAMPLER_TAG,
SEQUENCER_TAG,
SLEW_LIMITER_TAG,
SWITCH_TAG,
SYNTH_VOICE_TAG, // A synth voice must have an envelope built-in.
TUNER_TAG,
UTILITY_TAG, // Serves only extremely basic functions, like inverting, max, min, multiplying by 2, etc.
VISUAL_TAG,
VOCODER_TAG,
WAVESHAPER_TAG,
NUM_TAGS
};


void tagsInit();

extern std::string gTagNames[NUM_TAGS];


} // namespace rack

+ 4
- 4
plugin.mk View File

@@ -2,13 +2,13 @@ ifndef RACK_DIR
$(error RACK_DIR is not defined) $(error RACK_DIR is not defined)
endif endif


ifndef SLUG
$(error SLUG is not defined)
endif
SLUG := $(shell jq ".slug" plugin.json)
VERSION := $(shell jq ".version" plugin.json)


STRIP ?= strip STRIP ?= strip


FLAGS += -DSLUG=$(SLUG)
DISTRIBUTABLES += plugin.json

FLAGS += -fPIC FLAGS += -fPIC
FLAGS += -I$(RACK_DIR)/include -I$(RACK_DIR)/dep/include FLAGS += -I$(RACK_DIR)/include -I$(RACK_DIR)/dep/include




+ 1
- 1
src/Core/AudioInterface.cpp View File

@@ -282,4 +282,4 @@ struct AudioInterfaceWidget : ModuleWidget {
}; };




Model *modelAudioInterface = createModel<AudioInterface, AudioInterfaceWidget>("Core", "AudioInterface", "Audio", EXTERNAL_TAG);
Model *modelAudioInterface = createModel<AudioInterface, AudioInterfaceWidget>("AudioInterface");

+ 1
- 1
src/Core/Blank.cpp View File

@@ -121,4 +121,4 @@ struct BlankWidget : ModuleWidget {
}; };




Model *modelBlank = createModel<Module, BlankWidget>("Core", "Blank", "Blank", BLANK_TAG);
Model *modelBlank = createModel<Module, BlankWidget>("Blank");

+ 1
- 1
src/Core/MIDICCToCVInterface.cpp View File

@@ -216,4 +216,4 @@ struct MIDICCToCVInterfaceWidget : ModuleWidget {
}; };




Model *modelMIDICCToCVInterface = createModel<MIDICCToCVInterface, MIDICCToCVInterfaceWidget>("Core", "MIDICCToCVInterface", "MIDI-CC", MIDI_TAG, EXTERNAL_TAG);
Model *modelMIDICCToCVInterface = createModel<MIDICCToCVInterface, MIDICCToCVInterfaceWidget>("MIDICCToCVInterface");

+ 1
- 1
src/Core/MIDIToCVInterface.cpp View File

@@ -328,4 +328,4 @@ struct MIDIToCVInterfaceWidget : ModuleWidget {
}; };




Model *modelMIDIToCVInterface = createModel<MIDIToCVInterface, MIDIToCVInterfaceWidget>("Core", "MIDIToCVInterface", "MIDI-1", MIDI_TAG, EXTERNAL_TAG);
Model *modelMIDIToCVInterface = createModel<MIDIToCVInterface, MIDIToCVInterfaceWidget>("MIDIToCVInterface");

+ 1
- 1
src/Core/MIDITriggerToCVInterface.cpp View File

@@ -251,4 +251,4 @@ struct MIDITriggerToCVInterfaceWidget : ModuleWidget {
}; };




Model *modelMIDITriggerToCVInterface = createModel<MIDITriggerToCVInterface, MIDITriggerToCVInterfaceWidget>("Core", "MIDITriggerToCVInterface", "MIDI-Trig", MIDI_TAG, EXTERNAL_TAG);
Model *modelMIDITriggerToCVInterface = createModel<MIDITriggerToCVInterface, MIDITriggerToCVInterfaceWidget>("MIDITriggerToCVInterface");

+ 1
- 1
src/Core/Notes.cpp View File

@@ -41,4 +41,4 @@ struct NotesWidget : ModuleWidget {
}; };




Model *modelNotes = createModel<Module, NotesWidget>("Core", "Notes", "Notes", BLANK_TAG);
Model *modelNotes = createModel<Module, NotesWidget>("Notes");

+ 1
- 1
src/Core/QuadMIDIToCVInterface.cpp View File

@@ -366,5 +366,5 @@ struct QuadMIDIToCVInterfaceWidget : ModuleWidget {
}; };




Model *modelQuadMIDIToCVInterface = createModel<QuadMIDIToCVInterface, QuadMIDIToCVInterfaceWidget>("Core", "QuadMIDIToCVInterface", "MIDI-4", MIDI_TAG, EXTERNAL_TAG, QUAD_TAG);
Model *modelQuadMIDIToCVInterface = createModel<QuadMIDIToCVInterface, QuadMIDIToCVInterfaceWidget>("QuadMIDIToCVInterface");



+ 29
- 27
src/app/ModuleBrowser.cpp View File

@@ -22,7 +22,7 @@ namespace rack {


static std::set<Model*> sFavoriteModels; static std::set<Model*> sFavoriteModels;
static std::string sAuthorFilter; static std::string sAuthorFilter;
static ModelTag sTagFilter = NO_TAG;
static std::string sTagFilter;






@@ -38,14 +38,17 @@ static bool isModelMatch(Model *model, std::string search) {
std::string s; std::string s;
s += model->plugin->slug; s += model->plugin->slug;
s += " "; s += " ";
s += model->author;
s += model->plugin->author;
s += " "; s += " ";
s += model->name; s += model->name;
s += " "; s += " ";
s += model->slug; s += model->slug;
for (ModelTag tag : model->tags) {
s += " ";
s += gTagNames[tag];
for (std::string tag : model->tags) {
std::string allowedTag = plugin::getAllowedTag(tag);
if (!allowedTag.empty()) {
s += " ";
s += allowedTag;
}
} }
return isMatch(s, search); return isMatch(s, search);
} }
@@ -185,16 +188,16 @@ struct AuthorItem : BrowserListItem {




struct TagItem : BrowserListItem { struct TagItem : BrowserListItem {
ModelTag tag;
std::string tag;


void setTag(ModelTag tag) {
void setTag(std::string tag) {
clearChildren(); clearChildren();
this->tag = tag; this->tag = tag;
Label *tagLabel = createWidget<Label>(math::Vec(0, 0 + itemMargin)); Label *tagLabel = createWidget<Label>(math::Vec(0, 0 + itemMargin));
if (tag == NO_TAG)
if (tag.empty())
tagLabel->text = "Show all tags"; tagLabel->text = "Show all tags";
else else
tagLabel->text = gTagNames[tag];
tagLabel->text = tag;
addChild(tagLabel); addChild(tagLabel);
} }


@@ -299,12 +302,12 @@ struct ModuleBrowser : OpaqueWidget {
ScrollWidget *moduleScroll; ScrollWidget *moduleScroll;
BrowserList *moduleList; BrowserList *moduleList;
std::set<std::string, string::CaseInsensitiveCompare> availableAuthors; std::set<std::string, string::CaseInsensitiveCompare> availableAuthors;
std::set<ModelTag> availableTags;
std::set<std::string> availableTags;


ModuleBrowser() { ModuleBrowser() {
box.size.x = 450; box.size.x = 450;
sAuthorFilter = ""; sAuthorFilter = "";
sTagFilter = NO_TAG;
sTagFilter = "";


// Search // Search
searchField = new SearchModuleField; searchField = new SearchModuleField;
@@ -324,13 +327,14 @@ struct ModuleBrowser : OpaqueWidget {


// Collect authors // Collect authors
for (Plugin *plugin : plugin::plugins) { for (Plugin *plugin : plugin::plugins) {
// Insert author
if (!plugin->author.empty())
availableAuthors.insert(plugin->author);
for (Model *model : plugin->models) { for (Model *model : plugin->models) {
// Insert author
if (!model->author.empty())
availableAuthors.insert(model->author);
// Insert tag // Insert tag
for (ModelTag tag : model->tags) {
if (tag != NO_TAG)
for (std::string tag : model->tags) {
std::string allowedTag = plugin::getAllowedTag(tag);
if (!allowedTag.empty())
availableTags.insert(tag); availableTags.insert(tag);
} }
} }
@@ -350,12 +354,10 @@ struct ModuleBrowser : OpaqueWidget {
} }


bool isModelFiltered(Model *model) { bool isModelFiltered(Model *model) {
if (!sAuthorFilter.empty() && model->author != sAuthorFilter)
if (!sAuthorFilter.empty() && model->plugin->author != sAuthorFilter)
return false; return false;
if (sTagFilter != NO_TAG) {
auto it = std::find(model->tags.begin(), model->tags.end(), sTagFilter);
if (it == model->tags.end())
return false;
if (!sTagFilter.empty()) {
// TODO filter tags
} }
return true; return true;
} }
@@ -364,7 +366,7 @@ struct ModuleBrowser : OpaqueWidget {
std::string search = searchField->text; std::string search = searchField->text;
moduleList->clearChildren(); moduleList->clearChildren();
moduleList->selected = 0; moduleList->selected = 0;
bool filterPage = !(sAuthorFilter.empty() && sTagFilter == NO_TAG);
bool filterPage = !(sAuthorFilter.empty() && sTagFilter.empty());


if (!filterPage) { if (!filterPage) {
// Favorites // Favorites
@@ -399,8 +401,8 @@ struct ModuleBrowser : OpaqueWidget {
item->setText("Tags"); item->setText("Tags");
moduleList->addChild(item); moduleList->addChild(item);
} }
for (ModelTag tag : availableTags) {
if (isMatch(gTagNames[tag], search)) {
for (std::string tag : availableTags) {
if (isMatch(tag, search)) {
TagItem *item = new TagItem; TagItem *item = new TagItem;
item->setTag(tag); item->setTag(tag);
moduleList->addChild(item); moduleList->addChild(item);
@@ -423,8 +425,8 @@ struct ModuleBrowser : OpaqueWidget {
SeparatorItem *item = new SeparatorItem; SeparatorItem *item = new SeparatorItem;
if (!sAuthorFilter.empty()) if (!sAuthorFilter.empty())
item->setText(sAuthorFilter); item->setText(sAuthorFilter);
else if (sTagFilter != NO_TAG)
item->setText("Tag: " + gTagNames[sTagFilter]);
else if (!sTagFilter.empty())
item->setText("Tag: " + sTagFilter);
moduleList->addChild(item); moduleList->addChild(item);
} }
// Modules // Modules
@@ -474,7 +476,7 @@ void TagItem::onAction(event::Action &e) {
void ClearFilterItem::onAction(event::Action &e) { void ClearFilterItem::onAction(event::Action &e) {
ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>(); ModuleBrowser *moduleBrowser = getAncestorOfType<ModuleBrowser>();
sAuthorFilter = ""; sAuthorFilter = "";
sTagFilter = NO_TAG;
sTagFilter = "";
moduleBrowser->refreshSearch(); moduleBrowser->refreshSearch();
e.target = this; e.target = this;
} }


+ 1
- 1
src/app/ModuleWidget.cpp View File

@@ -450,7 +450,7 @@ Menu *ModuleWidget::createContextMenu() {
Menu *menu = createMenu(); Menu *menu = createMenu();


MenuLabel *menuLabel = new MenuLabel; MenuLabel *menuLabel = new MenuLabel;
menuLabel->text = model->author + " " + model->name + " " + model->plugin->version;
menuLabel->text = model->plugin->author + " " + model->name + " " + model->plugin->version;
menu->addChild(menuLabel); menu->addChild(menuLabel);


ModuleResetItem *resetItem = new ModuleResetItem; ModuleResetItem *resetItem = new ModuleResetItem;


+ 0
- 2
src/main.cpp View File

@@ -9,7 +9,6 @@
#include "settings.hpp" #include "settings.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "app/Scene.hpp" #include "app/Scene.hpp"
#include "tags.hpp"
#include "plugin.hpp" #include "plugin.hpp"
#include "context.hpp" #include "context.hpp"
#include "ui.hpp" #include "ui.hpp"
@@ -64,7 +63,6 @@ int main(int argc, char *argv[]) {
random::init(); random::init();
asset::init(devMode); asset::init(devMode);
logger::init(devMode); logger::init(devMode);
tagsInit();
midi::init(); midi::init();
rtmidiInit(); rtmidiInit();
bridgeInit(); bridgeInit();


+ 3
- 3
src/network.cpp View File

@@ -115,7 +115,7 @@ static int xferInfoCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
return 0; return 0;
} }


bool requestDownload(std::string url, std::string filename, float *progress) {
bool requestDownload(std::string url, const std::string &filename, float *progress) {
if (progress) if (progress)
*progress = 0.f; *progress = 0.f;


@@ -150,7 +150,7 @@ bool requestDownload(std::string url, std::string filename, float *progress) {
return res == CURLE_OK; return res == CURLE_OK;
} }


std::string encodeUrl(std::string s) {
std::string encodeUrl(const std::string &s) {
CURL *curl = curl_easy_init(); CURL *curl = curl_easy_init();
assert(curl); assert(curl);
char *escaped = curl_easy_escape(curl, s.c_str(), s.size()); char *escaped = curl_easy_escape(curl, s.c_str(), s.size());
@@ -160,7 +160,7 @@ std::string encodeUrl(std::string s) {
return ret; return ret;
} }


std::string computeSHA256File(std::string filename) {
std::string computeSHA256File(const std::string &filename) {
FILE *f = fopen(filename.c_str(), "rb"); FILE *f = fopen(filename.c_str(), "rb");
if (!f) if (!f)
return ""; return "";


+ 94
- 7
src/plugin.cpp View File

@@ -40,6 +40,28 @@ namespace plugin {
//////////////////// ////////////////////


static bool loadPlugin(std::string path) { static bool loadPlugin(std::string path) {
// Load plugin.json
std::string metadataFilename = path + "/plugin.json";
FILE *file = fopen(metadataFilename.c_str(), "r");
if (!file) {
WARN("Plugin metadata file %s does not exist", metadataFilename.c_str());
return false;
}
DEFER({
fclose(file);
});

json_error_t error;
json_t *rootJ = json_loadf(file, 0, &error);
if (!rootJ) {
WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
return false;
}
DEFER({
json_decref(rootJ);
});

// Load plugin library
std::string libraryFilename; std::string libraryFilename;
#if ARCH_LIN #if ARCH_LIN
libraryFilename = path + "/" + "plugin.so"; libraryFilename = path + "/" + "plugin.so";
@@ -91,6 +113,7 @@ static bool loadPlugin(std::string path) {
plugin->path = path; plugin->path = path;
plugin->handle = handle; plugin->handle = handle;
initCallback(plugin); initCallback(plugin);
plugin->fromJson(rootJ);


// Reject plugin if slug already exists // Reject plugin if slug already exists
Plugin *oldPlugin = getPlugin(plugin->slug); Plugin *oldPlugin = getPlugin(plugin->slug);
@@ -473,14 +496,21 @@ Plugin *getPlugin(std::string pluginSlug) {


Model *getModel(std::string pluginSlug, std::string modelSlug) { Model *getModel(std::string pluginSlug, std::string modelSlug) {
Plugin *plugin = getPlugin(pluginSlug); Plugin *plugin = getPlugin(pluginSlug);
if (plugin) {
for (Model *model : plugin->models) {
if (model->slug == modelSlug) {
return model;
}
}
if (!plugin)
return NULL;
Model *model = plugin->getModel(modelSlug);
if (!model)
return NULL;
return model;
}

std::string getAllowedTag(std::string tag) {
tag = string::lowercase(tag);
for (std::string allowedTag : allowedTags) {
if (tag == string::lowercase(allowedTag))
return allowedTag;
} }
return NULL;
return "";
} }




@@ -492,5 +522,62 @@ std::string downloadName;
std::string loginStatus; std::string loginStatus;




const std::vector<std::string> allowedTags = {
"VCA",
"Arpeggiator",
"Attenuator",
"Blank",
"Chorus",
"Clock Modulator", // Clock dividers, multipliers, etc.
"Clock",
"Compressor",
"Controller", // Use only if the artist "performs" with this module. Knobs are not sufficient. Examples: on-screen keyboard, XY pad.
"Delay",
"Digital",
"Distortion",
"Drum",
"Dual", // The core functionality times two. If multiple channels are a requirement for the module to exist (ring modulator, mixer, etc), it is not a Dual module.
"Dynamics",
"Effect",
"Envelope Follower",
"Envelope Generator",
"Equalizer",
"External",
"Filter",
"Flanger",
"Function Generator",
"Granular",
"LFO",
"Limiter",
"Logic",
"Low Pass Gate",
"MIDI",
"Mixer",
"Multiple",
"Noise",
"VCO",
"Panning",
"Phaser",
"Physical Modeling",
"Quad", // The core functionality times four. If multiple channels are a requirement for the module to exist (ring modulator, mixer, etc), it is not a Quad module.
"Quantizer",
"Random",
"Recording",
"Reverb",
"Ring Modulator",
"Sample and Hold",
"Sampler",
"Sequencer",
"Slew Limiter",
"Switch",
"Synth Voice", // A synth voice must have an envelope built-in.
"Tuner",
"Utility", // Serves only extremely basic functions, like inverting, max, min, multiplying by 2, etc.
"Visual",
"Vocoder",
"Waveshaper",
};


} // namespace plugin } // namespace plugin
} // namespace rack } // namespace rack

+ 27
- 0
src/plugin/Model.cpp View File

@@ -0,0 +1,27 @@
#include "plugin/Model.hpp"
#include "logger.hpp"


namespace rack {


void Model::fromJson(json_t *rootJ) {
json_t *nameJ = json_object_get(rootJ, "name");
if (nameJ)
name = json_string_value(nameJ);

DEBUG("name: %s", name.c_str());

json_t *tagsJ = json_object_get(rootJ, "tags");
if (tagsJ) {
size_t i;
json_t *tagJ;
json_array_foreach(tagsJ, i, tagJ) {
std::string tag = json_string_value(tagJ);
DEBUG("tag: %s", tag.c_str());
}
}
}


} // namespace rack

+ 71
- 0
src/plugin/Plugin.cpp View File

@@ -1,5 +1,6 @@
#include "plugin/Plugin.hpp" #include "plugin/Plugin.hpp"
#include "plugin/Model.hpp" #include "plugin/Model.hpp"
#include "logger.hpp"




namespace rack { namespace rack {
@@ -17,5 +18,75 @@ void Plugin::addModel(Model *model) {
models.push_back(model); models.push_back(model);
} }


Model *Plugin::getModel(std::string slug) {
for (Model *model : models) {
if (model->slug == slug) {
return model;
}
}
return NULL;
}

void Plugin::fromJson(json_t *rootJ) {
json_t *slugJ = json_object_get(rootJ, "slug");
if (slugJ)
slug = json_string_value(slugJ);

json_t *versionJ = json_object_get(rootJ, "version");
if (versionJ)
version = json_string_value(versionJ);

json_t *nameJ = json_object_get(rootJ, "name");
if (nameJ)
name = json_string_value(nameJ);

json_t *authorJ = json_object_get(rootJ, "author");
if (authorJ)
author = json_string_value(authorJ);

json_t *licenseJ = json_object_get(rootJ, "license");
if (licenseJ)
license = json_string_value(licenseJ);

json_t *authorEmailJ = json_object_get(rootJ, "authorEmail");
if (authorEmailJ)
authorEmail = json_string_value(authorEmailJ);

json_t *pluginUrlJ = json_object_get(rootJ, "pluginUrl");
if (pluginUrlJ)
pluginUrl = json_string_value(pluginUrlJ);

json_t *authorUrlJ = json_object_get(rootJ, "authorUrl");
if (authorUrlJ)
authorUrl = json_string_value(authorUrlJ);

json_t *manualUrlJ = json_object_get(rootJ, "manualUrl");
if (manualUrlJ)
manualUrl = json_string_value(manualUrlJ);

json_t *sourceUrlJ = json_object_get(rootJ, "sourceUrl");
if (sourceUrlJ)
sourceUrl = json_string_value(sourceUrlJ);

json_t *donateUrlJ = json_object_get(rootJ, "donateUrl");
if (donateUrlJ)
donateUrl = json_string_value(donateUrlJ);

json_t *modulesJ = json_object_get(rootJ, "modules");
if (modulesJ) {
const char *slug;
json_t *moduleJ;
json_object_foreach(modulesJ, slug, moduleJ) {
Model *model = getModel(slug);
if (!model) {
WARN("Metadata references module \"%s\" but it is not registered in the plugin library", slug);
continue;
}

model->fromJson(moduleJ);
}
}
}



} // namespace rack } // namespace rack

+ 7
- 9
src/string.cpp View File

@@ -34,43 +34,41 @@ std::string uppercase(std::string s) {
return s; return s;
} }


/** Truncates and adds "..." to a string, not exceeding `len` characters */
std::string ellipsize(std::string s, size_t len) {
std::string ellipsize(const std::string &s, size_t len) {
if (s.size() <= len) if (s.size() <= len)
return s; return s;
else else
return s.substr(0, len - 3) + "..."; return s.substr(0, len - 3) + "...";
} }


bool startsWith(std::string str, std::string prefix) {
bool startsWith(const std::string &str, const std::string &prefix) {
return str.substr(0, prefix.size()) == prefix; return str.substr(0, prefix.size()) == prefix;
} }


bool endsWith(std::string str, std::string suffix) {
bool endsWith(const std::string &str, const std::string &suffix) {
return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
} }


/** Extracts portions of a path */
std::string directory(std::string path) {
std::string directory(const std::string &path) {
char *pathDup = strdup(path.c_str()); char *pathDup = strdup(path.c_str());
std::string directory = dirname(pathDup); std::string directory = dirname(pathDup);
free(pathDup); free(pathDup);
return directory; return directory;
} }


std::string filename(std::string path) {
std::string filename(const std::string &path) {
char *pathDup = strdup(path.c_str()); char *pathDup = strdup(path.c_str());
std::string filename = basename(pathDup); std::string filename = basename(pathDup);
free(pathDup); free(pathDup);
return filename; return filename;
} }


std::string basename(std::string path) {
std::string basename(const std::string &path) {
size_t pos = path.rfind('.'); size_t pos = path.rfind('.');
return std::string(path, 0, pos); return std::string(path, 0, pos);
} }


std::string extension(std::string path) {
std::string extension(const std::string &path) {
size_t pos = path.rfind('.'); size_t pos = path.rfind('.');
return std::string(path, pos); return std::string(path, pos);
} }


+ 6
- 6
src/system.cpp View File

@@ -12,7 +12,7 @@ namespace rack {
namespace system { namespace system {




std::list<std::string> listEntries(std::string path) {
std::list<std::string> listEntries(const std::string &path) {
std::list<std::string> filenames; std::list<std::string> filenames;
DIR *dir = opendir(path.c_str()); DIR *dir = opendir(path.c_str());
if (dir) { if (dir) {
@@ -28,21 +28,21 @@ std::list<std::string> listEntries(std::string path) {
return filenames; return filenames;
} }


bool isFile(std::string path) {
bool isFile(const std::string &path) {
struct stat statbuf; struct stat statbuf;
if (stat(path.c_str(), &statbuf)) if (stat(path.c_str(), &statbuf))
return false; return false;
return S_ISREG(statbuf.st_mode); return S_ISREG(statbuf.st_mode);
} }


bool isDirectory(std::string path) {
bool isDirectory(const std::string &path) {
struct stat statbuf; struct stat statbuf;
if (stat(path.c_str(), &statbuf)) if (stat(path.c_str(), &statbuf))
return false; return false;
return S_ISDIR(statbuf.st_mode); return S_ISDIR(statbuf.st_mode);
} }


void copyFile(std::string srcPath, std::string destPath) {
void copyFile(const std::string &srcPath, const std::string &destPath) {
// Open files // Open files
FILE *source = fopen(srcPath.c_str(), "rb"); FILE *source = fopen(srcPath.c_str(), "rb");
if (!source) if (!source)
@@ -69,7 +69,7 @@ void copyFile(std::string srcPath, std::string destPath) {
} }
} }


void createDirectory(std::string path) {
void createDirectory(const std::string &path) {
#if ARCH_WIN #if ARCH_WIN
CreateDirectory(path.c_str(), NULL); CreateDirectory(path.c_str(), NULL);
#else #else
@@ -77,7 +77,7 @@ void createDirectory(std::string path) {
#endif #endif
} }


void openBrowser(std::string url) {
void openBrowser(const std::string &url) {
#if ARCH_LIN #if ARCH_LIN
std::string command = "xdg-open " + url; std::string command = "xdg-open " + url;
(void) std::system(command.c_str()); (void) std::system(command.c_str());


+ 0
- 67
src/tags.cpp View File

@@ -1,67 +0,0 @@
#include "tags.hpp"


namespace rack {


std::string gTagNames[NUM_TAGS];


void tagsInit() {
gTagNames[AMPLIFIER_TAG] = "Amplifier/VCA";
gTagNames[ATTENUATOR_TAG] = "Attenuator";
gTagNames[ARPEGGIATOR_TAG] = "Arpeggiator";
gTagNames[BLANK_TAG] = "Blank";
gTagNames[CHORUS_TAG] = "Chorus";
gTagNames[CLOCK_TAG] = "Clock";
gTagNames[CLOCK_MODULATOR_TAG] = "Clock Modulator";
gTagNames[COMPRESSOR_TAG] = "Compressor";
gTagNames[CONTROLLER_TAG] = "Controller";
gTagNames[DELAY_TAG] = "Delay";
gTagNames[DIGITAL_TAG] = "Digital";
gTagNames[DISTORTION_TAG] = "Distortion";
gTagNames[DRUM_TAG] = "Drum";
gTagNames[DUAL_TAG] = "Dual/Stereo";
gTagNames[DYNAMICS_TAG] = "Dynamics";
gTagNames[EFFECT_TAG] = "Effect";
gTagNames[ENVELOPE_FOLLOWER_TAG] = "Envelope Follower";
gTagNames[ENVELOPE_GENERATOR_TAG] = "Envelope Generator";
gTagNames[EQUALIZER_TAG] = "Equalizer";
gTagNames[EXTERNAL_TAG] = "External";
gTagNames[FILTER_TAG] = "Filter/VCF";
gTagNames[FLANGER_TAG] = "Flanger";
gTagNames[FUNCTION_GENERATOR_TAG] = "Function Generator";
gTagNames[GRANULAR_TAG] = "Granular";
gTagNames[LFO_TAG] = "LFO";
gTagNames[LIMITER_TAG] = "Limiter";
gTagNames[LOGIC_TAG] = "Logic";
gTagNames[LOW_PASS_GATE_TAG] = "Low Pass Gate";
gTagNames[MIDI_TAG] = "MIDI";
gTagNames[MIXER_TAG] = "Mixer";
gTagNames[MULTIPLE_TAG] = "Multiple";
gTagNames[NOISE_TAG] = "Noise";
gTagNames[OSCILLATOR_TAG] = "Oscillator/VCO";
gTagNames[PANNING_TAG] = "Panning";
gTagNames[PHASER_TAG] = "Phaser";
gTagNames[PHYSICAL_MODELING_TAG] = "Physical Modeling";
gTagNames[QUAD_TAG] = "Quad";
gTagNames[QUANTIZER_TAG] = "Quantizer";
gTagNames[RANDOM_TAG] = "Random";
gTagNames[RECORDING_TAG] = "Recording";
gTagNames[REVERB_TAG] = "Reverb";
gTagNames[RING_MODULATOR_TAG] = "Ring Modulator";
gTagNames[SAMPLE_AND_HOLD_TAG] = "Sample and Hold";
gTagNames[SAMPLER_TAG] = "Sampler";
gTagNames[SEQUENCER_TAG] = "Sequencer";
gTagNames[SLEW_LIMITER_TAG] = "Slew Limiter";
gTagNames[SWITCH_TAG] = "Switch";
gTagNames[SYNTH_VOICE_TAG] = "Synth Voice";
gTagNames[TUNER_TAG] = "Tuner";
gTagNames[UTILITY_TAG] = "Utility";
gTagNames[VISUAL_TAG] = "Visual";
gTagNames[VOCODER_TAG] = "Vocoder";
gTagNames[WAVESHAPER_TAG] = "Waveshaper";
}


} // namespace rack

Loading…
Cancel
Save