| @@ -8,7 +8,7 @@ run: doxygen | |||
| http-server html | |||
| upload: doxygen | |||
| rsync html/ vcvrack.com:vcvrack.com/docs/ -ruvz --delete | |||
| rsync html/ vcvrack.com:vcvrack.com/docs/ -ruz --info=progress2 --delete | |||
| clean: | |||
| rm -rfv html | |||
| @@ -1,5 +1,11 @@ | |||
| #pragma once | |||
| #include "rack.hpp" | |||
| #include "app/SvgKnob.hpp" | |||
| #include "app/SvgSlider.hpp" | |||
| #include "app/SvgPort.hpp" | |||
| #include "app/ModuleLightWidget.hpp" | |||
| #include "app/SvgSwitch.hpp" | |||
| #include "app/SvgScrew.hpp" | |||
| #include "asset.hpp" | |||
| namespace rack { | |||
| @@ -294,7 +300,7 @@ struct SynthTechAlco : app::SvgKnob { | |||
| minAngle = -0.82*M_PI; | |||
| maxAngle = 0.82*M_PI; | |||
| setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/SynthTechAlco.svg"))); | |||
| SvgWidget *cap = new SvgWidget; | |||
| widget::SvgWidget *cap = new widget::SvgWidget; | |||
| cap->setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/SynthTechAlco_cap.svg"))); | |||
| addChild(cap); | |||
| } | |||
| @@ -344,8 +350,8 @@ struct BefacoSlidePot : app::SvgSlider { | |||
| struct LEDSlider : app::SvgSlider { | |||
| LEDSlider() { | |||
| maxHandlePos = mm2px(math::Vec(0.738, 0.738).plus(math::Vec(2, 0))); | |||
| minHandlePos = mm2px(math::Vec(0.738, 22.078).plus(math::Vec(2, 0))); | |||
| maxHandlePos = app::mm2px(math::Vec(0.738, 0.738).plus(math::Vec(2, 0))); | |||
| minHandlePos = app::mm2px(math::Vec(0.738, 22.078).plus(math::Vec(2, 0))); | |||
| setBackgroundSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/LEDSlider.svg"))); | |||
| } | |||
| }; | |||
| @@ -467,7 +473,7 @@ struct RGBLight : app::ModuleLightWidget { | |||
| template <typename BASE> | |||
| struct LargeLight : BASE { | |||
| LargeLight() { | |||
| this->box.size = mm2px(math::Vec(5.179, 5.179)); | |||
| this->box.size = app::mm2px(math::Vec(5.179, 5.179)); | |||
| } | |||
| }; | |||
| @@ -475,7 +481,7 @@ struct LargeLight : BASE { | |||
| template <typename BASE> | |||
| struct MediumLight : BASE { | |||
| MediumLight() { | |||
| this->box.size = mm2px(math::Vec(3.176, 3.176)); | |||
| this->box.size = app::mm2px(math::Vec(3.176, 3.176)); | |||
| } | |||
| }; | |||
| @@ -483,7 +489,7 @@ struct MediumLight : BASE { | |||
| template <typename BASE> | |||
| struct SmallLight : BASE { | |||
| SmallLight() { | |||
| this->box.size = mm2px(math::Vec(2.176, 2.176)); | |||
| this->box.size = app::mm2px(math::Vec(2.176, 2.176)); | |||
| } | |||
| }; | |||
| @@ -491,7 +497,7 @@ struct SmallLight : BASE { | |||
| template <typename BASE> | |||
| struct TinyLight : BASE { | |||
| TinyLight() { | |||
| this->box.size = mm2px(math::Vec(1.088, 1.088)); | |||
| this->box.size = app::mm2px(math::Vec(1.088, 1.088)); | |||
| } | |||
| }; | |||
| @@ -500,18 +506,18 @@ template <typename BASE> | |||
| struct LEDBezelLight : BASE { | |||
| LEDBezelLight() { | |||
| this->bgColor = color::BLACK_TRANSPARENT; | |||
| this->box.size = mm2px(math::Vec(6.0, 6.0)); | |||
| this->box.size = app::mm2px(math::Vec(6.0, 6.0)); | |||
| } | |||
| }; | |||
| /** A light to displayed over PB61303. Must add a color by subclassing or templating. | |||
| Don't add this as a child of the PB61303 itself. Instead, just place it over it as a sibling in the scene graph, offset by mm2px(math::Vec(0.5, 0.5)). | |||
| Don't add this as a child of the PB61303 itself. Instead, just place it over it as a sibling in the scene graph, offset by app::mm2px(math::Vec(0.5, 0.5)). | |||
| */ | |||
| template <typename BASE> | |||
| struct PB61303Light : BASE { | |||
| PB61303Light() { | |||
| this->bgColor = color::BLACK_TRANSPARENT; | |||
| this->box.size = mm2px(math::Vec(9.0, 9.0)); | |||
| this->box.size = app::mm2px(math::Vec(9.0, 9.0)); | |||
| } | |||
| }; | |||
| @@ -18,8 +18,8 @@ void alignedDelete(T *p) { | |||
| } | |||
| /** Real-valued FFT context | |||
| Wrapper for PFFFT (https://bitbucket.org/jpommier/pffft/) | |||
| /** Real-valued FFT context. | |||
| Wrapper for [PFFFT](https://bitbucket.org/jpommier/pffft/) | |||
| `length` must be a multiple of 32. | |||
| */ | |||
| struct RealFFT { | |||
| @@ -72,8 +72,7 @@ struct RealFFT { | |||
| pffft_transform_ordered(setup, input, output, NULL, PFFFT_BACKWARD); | |||
| } | |||
| /** Scales the RFFT so that | |||
| scale(IFFT(FFT(x))) = x | |||
| /** Scales the RFFT so that `scale(IFFT(FFT(x))) = x`. | |||
| */ | |||
| void scale(float *x) { | |||
| float a = 1.f / length; | |||
| @@ -84,7 +83,7 @@ struct RealFFT { | |||
| }; | |||
| /** Complex-valued FFT context | |||
| /** Complex-valued FFT context. | |||
| `length` must be a multiple of 16. | |||
| */ | |||
| struct ComplexFFT { | |||
| @@ -6,7 +6,7 @@ namespace rack { | |||
| namespace dsp { | |||
| /** Deprecated. Use VuMeter2. */ | |||
| /** Deprecated. Use VuMeter2 instead. */ | |||
| struct VuMeter { | |||
| /** Decibel level difference between adjacent meter lights */ | |||
| float dBInterval = 3.0; | |||
| @@ -34,17 +34,18 @@ DEPRECATED typedef VuMeter VUMeter; | |||
| /** Models a VU meter with smoothing. | |||
| Supports peak and RMS (root-mean-square) metering | |||
| Supports peak and RMS (root-mean-square) metering. | |||
| Usage example for a strip of lights with 3dB increments: | |||
| // Update VuMeter state every frame. | |||
| vuMeter.process(deltaTime, output); | |||
| // Iterate lights every ~512 frames (less than a screen refresh). | |||
| for (int i = 0; i < 6; i++) { | |||
| float b = vuMeter.getBrightness(-3.f * (i + 1), -3.f * i); | |||
| // No need to use setSmoothBrightness() since VuMeter2 smooths the value for you. | |||
| lights[i].setBrightness(b); | |||
| } | |||
| ``` | |||
| // Update VuMeter state every frame. | |||
| vuMeter.process(deltaTime, output); | |||
| // Iterate lights every ~512 frames (less than a screen refresh). | |||
| for (int i = 0; i < 6; i++) { | |||
| float b = vuMeter.getBrightness(-3.f * (i + 1), -3.f * i); | |||
| // No need to use setSmoothBrightness() since VuMeter2 smooths the value for you. | |||
| lights[i].setBrightness(b); | |||
| } | |||
| ``` | |||
| */ | |||
| struct VuMeter2 { | |||
| enum Mode { | |||
| @@ -1,7 +1,7 @@ | |||
| #pragma once | |||
| #include "common.hpp" | |||
| #include <jansson.h> | |||
| #include <list> | |||
| #include <vector> | |||
| namespace rack { | |||
| @@ -14,7 +14,7 @@ struct Model; | |||
| // Subclass this and return a pointer to a new one when init() is called | |||
| struct Plugin { | |||
| /** A list of the models available by this plugin, add with addModel() */ | |||
| std::list<Model*> models; | |||
| std::vector<Model*> models; | |||
| /** The file path of the plugin's directory */ | |||
| std::string path; | |||
| /** OS-dependent library handle */ | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| // Include most Rack headers for convenience | |||
| #include "common.hpp" | |||
| #include "math.hpp" | |||
| @@ -11,6 +12,7 @@ | |||
| #include "app.hpp" | |||
| #include "midi.hpp" | |||
| #include "helpers.hpp" | |||
| #include "component.hpp" | |||
| #include "widget/Widget.hpp" | |||
| #include "widget/TransparentWidget.hpp" | |||
| @@ -90,9 +92,12 @@ | |||
| #include "dsp/vumeter.hpp" | |||
| #include "dsp/window.hpp" | |||
| namespace rack { | |||
| /** Define this macro before including this header to prevent common namespaces from being included in the main `rack::` namespace. */ | |||
| #ifndef RACK_FLATTEN_NAMESPACES | |||
| // Import some namespaces for convenience | |||
| using namespace math; | |||
| using namespace widget; | |||
| @@ -101,9 +106,8 @@ using namespace app; | |||
| using plugin::Plugin; | |||
| using plugin::Model; | |||
| using namespace engine; | |||
| namespace component {} | |||
| using namespace component; | |||
| #endif | |||
| } // namespace rack | |||
| @@ -6,6 +6,7 @@ | |||
| #include "app.hpp" | |||
| #include "patch.hpp" | |||
| #include "settings.hpp" | |||
| #include "engine/Port.hpp" | |||
| namespace rack { | |||
| @@ -219,7 +220,7 @@ void CableWidget::draw(const DrawArgs &args) { | |||
| float thickness = 5; | |||
| if (isComplete()) { | |||
| Output *output = &cable->outputModule->outputs[cable->outputId]; | |||
| engine::Output *output = &cable->outputModule->outputs[cable->outputId]; | |||
| // Draw opaque if mouse is hovering over a connected port | |||
| if (output->channels > 1) { | |||
| // Increase thickness if output port is polyphonic | |||
| @@ -79,7 +79,7 @@ struct BrowserOverlay : widget::OpaqueWidget { | |||
| }; | |||
| static const float MODEL_BOX_ZOOM = 0.5f; | |||
| static const float MODEL_BOX_ZOOM = 1.0f; | |||
| struct ModelBox : widget::OpaqueWidget { | |||
| @@ -92,20 +92,23 @@ struct ModelBox : widget::OpaqueWidget { | |||
| bool selected = false; | |||
| ModelBox() { | |||
| box.size.x = 0.f; | |||
| box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM); | |||
| // Approximate size as 10HP before we know the actual size. | |||
| // We need a nonzero size, otherwise the parent widget will consider it not in the draw bounds, so its preview will not be lazily created. | |||
| box.size.x = 10 * RACK_GRID_WIDTH * MODEL_BOX_ZOOM; | |||
| box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM; | |||
| box.size = box.size.ceil(); | |||
| } | |||
| void setModel(plugin::Model *model) { | |||
| this->model = model; | |||
| infoWidget = new widget::Widget; | |||
| infoWidget->box.size.x = 140; | |||
| infoWidget->box.size.y = box.size.y; | |||
| infoWidget->hide(); | |||
| addChild(infoWidget); | |||
| math::Vec pos; | |||
| // Name label | |||
| ui::Label *nameLabel = new ui::Label; | |||
| // nameLabel->box.size.x = infoWidget->box.size.x; | |||
| nameLabel->box.pos = pos; | |||
| @@ -113,6 +116,7 @@ struct ModelBox : widget::OpaqueWidget { | |||
| infoWidget->addChild(nameLabel); | |||
| pos = nameLabel->box.getBottomLeft(); | |||
| // Plugin label | |||
| ui::Label *pluginLabel = new ui::Label; | |||
| // pluginLabel->box.size.x = infoWidget->box.size.x; | |||
| pluginLabel->box.pos = pos; | |||
| @@ -127,24 +131,23 @@ struct ModelBox : widget::OpaqueWidget { | |||
| infoWidget->addChild(descriptionLabel); | |||
| pos = descriptionLabel->box.getBottomLeft(); | |||
| pos.y = infoWidget->box.size.y; | |||
| for (const std::string &tag : model->tags) { | |||
| ui::Button *tagButton = new ui::Button; | |||
| tagButton->box.size.x = infoWidget->box.size.x; | |||
| tagButton->box.pos = pos; | |||
| tagButton->box.pos.y -= tagButton->box.size.y; | |||
| tagButton->text = tag; | |||
| infoWidget->addChild(tagButton); | |||
| pos = tagButton->box.getTopLeft(); | |||
| } | |||
| ui::Button *favoriteButton = new ui::Button; | |||
| favoriteButton->box.size.x = infoWidget->box.size.x; | |||
| favoriteButton->box.pos = pos; | |||
| favoriteButton->box.pos.y -= favoriteButton->box.size.y; | |||
| favoriteButton->text = "★"; | |||
| infoWidget->addChild(favoriteButton); | |||
| pos = favoriteButton->box.getTopLeft(); | |||
| // for (const std::string &tag : model->tags) { | |||
| // ui::Button *tagButton = new ui::Button; | |||
| // tagButton->box.size.x = infoWidget->box.size.x; | |||
| // tagButton->box.pos = pos; | |||
| // tagButton->text = tag; | |||
| // infoWidget->addChild(tagButton); | |||
| // pos = tagButton->box.getTopLeft(); | |||
| // } | |||
| // // Favorite button | |||
| // ui::Button *favoriteButton = new ui::Button; | |||
| // favoriteButton->box.size.x = box.size.x; | |||
| // favoriteButton->box.pos = pos; | |||
| // favoriteButton->box.pos.y -= favoriteButton->box.size.y; | |||
| // favoriteButton->text = "★"; | |||
| // addChild(favoriteButton); | |||
| // pos = favoriteButton->box.getTopLeft(); | |||
| } | |||
| void createPreview() { | |||
| @@ -171,9 +174,8 @@ struct ModelBox : widget::OpaqueWidget { | |||
| zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM; | |||
| previewWidget->box.size.x = std::ceil(zoomWidget->box.size.x); | |||
| // Reposition infoWidget | |||
| infoWidget->box.pos.x = previewWidget->box.size.x; | |||
| box.size.x = previewWidget->box.size.x + infoWidget->box.size.x; | |||
| infoWidget->box.size = previewWidget->box.size; | |||
| box.size.x = previewWidget->box.size.x; | |||
| } | |||
| void deletePreview() { | |||
| @@ -197,6 +199,16 @@ struct ModelBox : widget::OpaqueWidget { | |||
| createPreview(); | |||
| } | |||
| // Draw shadow | |||
| nvgBeginPath(args.vg); | |||
| float r = 10; // Blur radius | |||
| float c = 10; // Corner radius | |||
| nvgRect(args.vg, -r, -r, box.size.x + 2*r, box.size.y + 2*r); | |||
| NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.5); | |||
| NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0); | |||
| nvgFillPaint(args.vg, nvgBoxGradient(args.vg, 0, 0, box.size.x, box.size.y, c, r, shadowColor, transparentColor)); | |||
| nvgFill(args.vg); | |||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | |||
| widget::OpaqueWidget::draw(args); | |||
| nvgResetScissor(args.vg); | |||
| @@ -261,17 +273,18 @@ struct BrowserSidebar : widget::Widget { | |||
| searchField = new BrowserSearchField; | |||
| addChild(searchField); | |||
| // Plugin list | |||
| pluginScroll = new ui::ScrollWidget; | |||
| pluginScroll->box.pos = searchField->box.getBottomLeft(); | |||
| addChild(pluginScroll); | |||
| pluginList = new ui::List; | |||
| pluginScroll->container->addChild(pluginList); | |||
| std::set<std::string> pluginNames; | |||
| std::vector<std::string> pluginNames; | |||
| for (plugin::Plugin *plugin : plugin::plugins) { | |||
| pluginNames.insert(plugin->name); | |||
| pluginNames.push_back(plugin->name); | |||
| } | |||
| std::sort(pluginNames.begin(), pluginNames.end(), string::CaseInsensitiveCompare()); | |||
| for (const std::string &pluginName : pluginNames) { | |||
| ui::MenuItem *item = new ui::MenuItem; | |||
| @@ -279,8 +292,8 @@ struct BrowserSidebar : widget::Widget { | |||
| pluginList->addChild(item); | |||
| } | |||
| // Tag list | |||
| tagScroll = new ui::ScrollWidget; | |||
| tagScroll->box.pos = searchField->box.getBottomLeft(); | |||
| addChild(tagScroll); | |||
| tagList = new ui::List; | |||
| @@ -295,11 +308,14 @@ struct BrowserSidebar : widget::Widget { | |||
| void step() override { | |||
| searchField->box.size.x = box.size.x; | |||
| pluginScroll->box.size.y = box.size.y - searchField->box.size.y; | |||
| pluginList->box.size.x = pluginScroll->box.size.x = box.size.x / 2; | |||
| tagScroll->box.pos.x = box.size.x / 2; | |||
| tagScroll->box.size.y = box.size.y - searchField->box.size.y; | |||
| tagList->box.size.x = tagScroll->box.size.x = box.size.x / 2; | |||
| pluginScroll->box.pos = searchField->box.getBottomLeft(); | |||
| pluginScroll->box.size.y = (box.size.y - searchField->box.size.y) / 2; | |||
| pluginScroll->box.size.x = box.size.x; | |||
| pluginList->box.size.x = pluginScroll->box.size.x; | |||
| tagScroll->box.pos = pluginScroll->box.getBottomLeft().floor(); | |||
| tagScroll->box.size.y = (box.size.y - searchField->box.size.y) / 2; | |||
| tagScroll->box.size.x = box.size.x; | |||
| tagList->box.size.x = tagScroll->box.size.x; | |||
| widget::Widget::step(); | |||
| } | |||
| }; | |||
| @@ -313,18 +329,18 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
| ModuleBrowser() { | |||
| sidebar = new BrowserSidebar; | |||
| sidebar->box.size.x = 300; | |||
| sidebar->box.size.x = 200; | |||
| addChild(sidebar); | |||
| modelScroll = new ui::ScrollWidget; | |||
| addChild(modelScroll); | |||
| modelMargin = new ui::MarginLayout; | |||
| modelMargin->margin = math::Vec(20, 20); | |||
| modelMargin->margin = math::Vec(10, 10); | |||
| modelScroll->container->addChild(modelMargin); | |||
| modelContainer = new ui::SequentialLayout; | |||
| modelContainer->spacing = math::Vec(20, 20); | |||
| modelContainer->spacing = math::Vec(10, 10); | |||
| modelMargin->addChild(modelContainer); | |||
| for (plugin::Plugin *plugin : plugin::plugins) { | |||
| @@ -527,7 +527,7 @@ Model *getModel(const std::string &pluginSlug, const std::string &modelSlug) { | |||
| } | |||
| /** List of allowed tags in human display form. | |||
| /** List of allowed tags in human display form, alphabetized. | |||
| All tags here should be in sentence caps for display consistency. | |||
| However, tags are case-insensitive in plugin metadata. | |||
| */ | |||
| @@ -13,8 +13,9 @@ void List::step() { | |||
| for (widget::Widget *child : children) { | |||
| if (!child->visible) | |||
| continue; | |||
| // Increment height, set position of child | |||
| // Set position of child | |||
| child->box.pos = math::Vec(0.0, box.size.y); | |||
| // Increment height | |||
| box.size.y += child->box.size.y; | |||
| // Resize width of child | |||
| child->box.size.x = box.size.x; | |||