@@ -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; | |||