# Conflicts: # .gitignorepull/1514/head^2
@@ -12,5 +12,5 @@ | |||||
/autosave.vcv | /autosave.vcv | ||||
/settings.json | /settings.json | ||||
/screenshots | /screenshots | ||||
/.vs | |||||
/_ReSharper.Caches | |||||
.vs/ | |||||
_ReSharper.Caches/ |
@@ -2,6 +2,13 @@ | |||||
In this document, Mod is Ctrl on Windows/Linux and Cmd on Mac. | In this document, Mod is Ctrl on Windows/Linux and Cmd on Mac. | ||||
### 1.1.5 (in development) | |||||
- Swap order of tags and brands in Module Browser. | |||||
- Disable smoothing for MIDI CC buttons in MIDI-Map. | |||||
- Automatically unzip update on Mac. | |||||
- API | |||||
- Add libsamplerate library. | |||||
### 1.1.4 (2019-08-22) | ### 1.1.4 (2019-08-22) | ||||
- Fix parameter smoothing of MIDI-Map. | - Fix parameter smoothing of MIDI-Map. | ||||
- Sort modules within plugin in the Module Browser according to plugin rather than alphabetically. | - Sort modules within plugin in the Module Browser according to plugin rather than alphabetically. | ||||
@@ -536,4 +536,33 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
SOFTWARE. | |||||
SOFTWARE. | |||||
# libsamplerate | |||||
Copyright (c) 2012-2016, Erik de Castro Lopo <erikd@mega-nerd.com> | |||||
All rights reserved. | |||||
Redistribution and use in source and binary forms, with or without | |||||
modification, are permitted provided that the following conditions are | |||||
met: | |||||
1. Redistributions of source code must retain the above copyright | |||||
notice, this list of conditions and the following disclaimer. | |||||
2. Redistributions in binary form must reproduce the above copyright | |||||
notice, this list of conditions and the following disclaimer in the | |||||
documentation and/or other materials provided with the distribution. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS | |||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A | |||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | |||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -1,6 +1,6 @@ | |||||
RACK_DIR ?= . | RACK_DIR ?= . | ||||
# VERSION := 1.dev.$(shell git rev-parse --short HEAD) | |||||
VERSION := 1.1.4 | |||||
VERSION := 1.dev.$(shell git rev-parse --short HEAD) | |||||
# VERSION := 1.1.4 | |||||
FLAGS += -DVERSION=$(VERSION) | FLAGS += -DVERSION=$(VERSION) | ||||
FLAGS += -Iinclude -Idep/include | FLAGS += -Iinclude -Idep/include | ||||
@@ -21,7 +21,7 @@ ifdef ARCH_LIN | |||||
build/dep/osdialog/osdialog_gtk2.c.o: FLAGS += $(shell pkg-config --cflags gtk+-2.0) | build/dep/osdialog/osdialog_gtk2.c.o: FLAGS += $(shell pkg-config --cflags gtk+-2.0) | ||||
LDFLAGS += -rdynamic \ | LDFLAGS += -rdynamic \ | ||||
dep/lib/libglfw3.a dep/lib/libGLEW.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libzip.a dep/lib/libz.a dep/lib/librtmidi.a dep/lib/librtaudio.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a \ | |||||
dep/lib/libGLEW.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/librtmidi.a dep/lib/librtaudio.a \ | |||||
-lpthread -lGL -ldl -lX11 -lasound -ljack \ | -lpthread -lGL -ldl -lX11 -lasound -ljack \ | ||||
$(shell pkg-config --libs gtk+-2.0) | $(shell pkg-config --libs gtk+-2.0) | ||||
TARGET := Rack | TARGET := Rack | ||||
@@ -31,14 +31,14 @@ ifdef ARCH_MAC | |||||
SOURCES += dep/osdialog/osdialog_mac.m | SOURCES += dep/osdialog/osdialog_mac.m | ||||
LDFLAGS += -lpthread -ldl \ | LDFLAGS += -lpthread -ldl \ | ||||
-framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -framework CoreAudio -framework CoreMIDI \ | -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -framework CoreAudio -framework CoreMIDI \ | ||||
dep/lib/libglfw3.a dep/lib/libGLEW.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libzip.a dep/lib/libz.a dep/lib/librtaudio.a dep/lib/librtmidi.a dep/lib/libcrypto.a dep/lib/libssl.a dep/lib/libcurl.a | |||||
dep/lib/libGLEW.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/librtmidi.a dep/lib/librtaudio.a | |||||
TARGET := Rack | TARGET := Rack | ||||
endif | endif | ||||
ifdef ARCH_WIN | ifdef ARCH_WIN | ||||
SOURCES += dep/osdialog/osdialog_win.c | SOURCES += dep/osdialog/osdialog_win.c | ||||
LDFLAGS += -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows \ | LDFLAGS += -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows \ | ||||
dep/lib/libglew32.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/librtaudio.a dep/lib/librtmidi.a \ | |||||
dep/lib/libglew32.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/librtaudio.a dep/lib/librtmidi.a \ | |||||
-lpthread -lopengl32 -lgdi32 -lws2_32 -lcomdlg32 -lole32 -ldsound -lwinmm -lksuser -lshlwapi -lmfplat -lmfuuid -lwmcodecdspuuid -ldbghelp | -lpthread -lopengl32 -lgdi32 -lws2_32 -lcomdlg32 -lole32 -ldsound -lwinmm -lksuser -lshlwapi -lmfplat -lmfuuid -lwmcodecdspuuid -ldbghelp | ||||
TARGET := Rack.exe | TARGET := Rack.exe | ||||
OBJECTS += Rack.res | OBJECTS += Rack.res | ||||
@@ -10,39 +10,42 @@ ifdef ARCH_LIN | |||||
glew = lib/libGLEW.a | glew = lib/libGLEW.a | ||||
glfw = lib/libglfw3.a | glfw = lib/libglfw3.a | ||||
jansson = lib/libjansson.a | jansson = lib/libjansson.a | ||||
libspeexdsp = lib/libspeexdsp.a | |||||
openssl = lib/libssl.a | |||||
libcurl = lib/libcurl.a | libcurl = lib/libcurl.a | ||||
libzip = lib/libzip.a | libzip = lib/libzip.a | ||||
zlib = lib/libz.a | zlib = lib/libz.a | ||||
libspeexdsp = lib/libspeexdsp.a | |||||
libsamplerate = lib/libsamplerate.a | |||||
rtmidi = lib/librtmidi.a | rtmidi = lib/librtmidi.a | ||||
rtaudio = lib/librtaudio.a | rtaudio = lib/librtaudio.a | ||||
openssl = lib/libssl.a | |||||
endif | endif | ||||
ifdef ARCH_MAC | ifdef ARCH_MAC | ||||
glew = lib/libGLEW.a | glew = lib/libGLEW.a | ||||
glfw = lib/libglfw3.a | glfw = lib/libglfw3.a | ||||
jansson = lib/libjansson.a | jansson = lib/libjansson.a | ||||
libspeexdsp = lib/libspeexdsp.a | |||||
openssl = lib/libssl.a | |||||
libcurl = lib/libcurl.a | libcurl = lib/libcurl.a | ||||
libzip = lib/libzip.a | libzip = lib/libzip.a | ||||
zlib = lib/libz.a | zlib = lib/libz.a | ||||
libspeexdsp = lib/libspeexdsp.a | |||||
libsamplerate = lib/libsamplerate.a | |||||
rtmidi = lib/librtmidi.a | rtmidi = lib/librtmidi.a | ||||
rtaudio = lib/librtaudio.a | rtaudio = lib/librtaudio.a | ||||
openssl = lib/libssl.a | |||||
endif | endif | ||||
ifdef ARCH_WIN | ifdef ARCH_WIN | ||||
glew = lib/libglew32.a | glew = lib/libglew32.a | ||||
glfw = lib/libglfw3.a | glfw = lib/libglfw3.a | ||||
jansson = lib/libjansson.a | jansson = lib/libjansson.a | ||||
libspeexdsp = lib/libspeexdsp.a | |||||
openssl = lib/libssl.a | |||||
libcurl = lib/libcurl.a | libcurl = lib/libcurl.a | ||||
libzip = lib/libzip.a | libzip = lib/libzip.a | ||||
zlib = lib/libz.a | zlib = lib/libz.a | ||||
libspeexdsp = lib/libspeexdsp.a | |||||
libsamplerate = lib/libsamplerate.a | |||||
rtmidi = lib/librtmidi.a | rtmidi = lib/librtmidi.a | ||||
rtaudio = lib/librtaudio.a | rtaudio = lib/librtaudio.a | ||||
openssl = lib/libssl.a | |||||
endif | endif | ||||
nanovg = include/nanovg.h | nanovg = include/nanovg.h | ||||
@@ -51,7 +54,20 @@ oui-blendish = include/blendish.h | |||||
osdialog = include/osdialog.h | osdialog = include/osdialog.h | ||||
pffft = include/pffft.h | pffft = include/pffft.h | ||||
DEPS += $(glew) $(glfw) $(jansson) $(libspeexdsp) $(libcurl) $(libzip) $(rtmidi) $(rtaudio) $(nanovg) $(nanosvg) $(oui-blendish) $(osdialog) $(pffft) | |||||
DEPS += $(glew) | |||||
DEPS += $(glfw) | |||||
DEPS += $(jansson) | |||||
DEPS += $(libcurl) | |||||
DEPS += $(libzip) | |||||
DEPS += $(libspeexdsp) | |||||
DEPS += $(libsamplerate) | |||||
DEPS += $(rtmidi) | |||||
DEPS += $(rtaudio) | |||||
DEPS += $(nanovg) | |||||
DEPS += $(nanosvg) | |||||
DEPS += $(oui-blendish) | |||||
DEPS += $(osdialog) | |||||
DEPS += $(pffft) | |||||
DEP_LOCAL := . | DEP_LOCAL := . | ||||
@@ -90,17 +106,6 @@ $(jansson): jansson-2.12 | |||||
$(MAKE) -C jansson-2.12 | $(MAKE) -C jansson-2.12 | ||||
$(MAKE) -C jansson-2.12 install | $(MAKE) -C jansson-2.12 install | ||||
speexdsp-SpeexDSP-1.2rc3: | |||||
$(WGET) "https://vcvrack.com/downloads/dep/speexdsp-SpeexDSP-1.2rc3.tgz" | |||||
$(SHA256) speexdsp-SpeexDSP-1.2rc3.tgz c8dded1454747f65956f981c95e7f89a06abdaa2a53e8aeaa66bab2a3d59cebd | |||||
$(UNTAR) speexdsp-SpeexDSP-1.2rc3.tgz | |||||
rm speexdsp-SpeexDSP-1.2rc3.tgz | |||||
$(libspeexdsp): speexdsp-SpeexDSP-1.2rc3 | |||||
cd speexdsp-SpeexDSP-1.2rc3 && $(CONFIGURE) | |||||
$(MAKE) -C speexdsp-SpeexDSP-1.2rc3 | |||||
$(MAKE) -C speexdsp-SpeexDSP-1.2rc3 install | |||||
openssl-1.1.1b: | openssl-1.1.1b: | ||||
$(WGET) "https://www.openssl.org/source/openssl-1.1.1b.tar.gz" | $(WGET) "https://www.openssl.org/source/openssl-1.1.1b.tar.gz" | ||||
$(SHA256) openssl-1.1.1b.tar.gz 5c557b023230413dfb0756f3137a13e6d726838ccd1430888ad15bfb2b43ea4b | $(SHA256) openssl-1.1.1b.tar.gz 5c557b023230413dfb0756f3137a13e6d726838ccd1430888ad15bfb2b43ea4b | ||||
@@ -157,6 +162,28 @@ else | |||||
$(MAKE) -C zlib-1.2.11 install | $(MAKE) -C zlib-1.2.11 install | ||||
endif | endif | ||||
speexdsp-SpeexDSP-1.2rc3: | |||||
$(WGET) "https://vcvrack.com/downloads/dep/speexdsp-SpeexDSP-1.2rc3.tgz" | |||||
$(SHA256) speexdsp-SpeexDSP-1.2rc3.tgz c8dded1454747f65956f981c95e7f89a06abdaa2a53e8aeaa66bab2a3d59cebd | |||||
$(UNTAR) speexdsp-SpeexDSP-1.2rc3.tgz | |||||
rm speexdsp-SpeexDSP-1.2rc3.tgz | |||||
$(libspeexdsp): speexdsp-SpeexDSP-1.2rc3 | |||||
cd speexdsp-SpeexDSP-1.2rc3 && $(CONFIGURE) | |||||
$(MAKE) -C speexdsp-SpeexDSP-1.2rc3 | |||||
$(MAKE) -C speexdsp-SpeexDSP-1.2rc3 install | |||||
libsamplerate-0.1.9: | |||||
$(WGET) "http://www.mega-nerd.com/SRC/libsamplerate-0.1.9.tar.gz" | |||||
$(SHA256) libsamplerate-0.1.9.tar.gz 0a7eb168e2f21353fb6d84da152e4512126f7dc48ccb0be80578c565413444c1 | |||||
$(UNTAR) libsamplerate-0.1.9.tar.gz | |||||
rm libsamplerate-0.1.9.tar.gz | |||||
$(libsamplerate): libsamplerate-0.1.9 | |||||
cd libsamplerate-0.1.9 && $(CONFIGURE) --disable-fftw --disable-sndfile | |||||
$(MAKE) -C libsamplerate-0.1.9 | |||||
$(MAKE) -C libsamplerate-0.1.9 install | |||||
rtmidi-4.0.0: | rtmidi-4.0.0: | ||||
$(WGET) "http://www.music.mcgill.ca/~gary/rtmidi/release/rtmidi-4.0.0.tar.gz" | $(WGET) "http://www.music.mcgill.ca/~gary/rtmidi/release/rtmidi-4.0.0.tar.gz" | ||||
$(SHA256) rtmidi-4.0.0.tar.gz 370cfe710f43fbeba8d2b8c8bc310f314338c519c2cf2865e2d2737b251526cd | $(SHA256) rtmidi-4.0.0.tar.gz 370cfe710f43fbeba8d2b8c8bc310f314338c519c2cf2865e2d2737b251526cd | ||||
@@ -100,10 +100,10 @@ include $(RACK_DIR)/plugin.mk | |||||
using namespace rack; | using namespace rack; | ||||
// Declare the Plugin, defined in plugin.cpp | // Declare the Plugin, defined in plugin.cpp | ||||
extern Plugin *pluginInstance; | |||||
extern Plugin* pluginInstance; | |||||
// Declare each Model, defined in each module source file | // Declare each Model, defined in each module source file | ||||
// extern Model *modelMyModule; | |||||
// extern Model* modelMyModule; | |||||
""" | """ | ||||
with open(os.path.join(plugin_dir, "src/plugin.hpp"), "w") as f: | with open(os.path.join(plugin_dir, "src/plugin.hpp"), "w") as f: | ||||
f.write(plugin_hpp) | f.write(plugin_hpp) | ||||
@@ -112,10 +112,10 @@ extern Plugin *pluginInstance; | |||||
plugin_cpp = """#include "plugin.hpp" | plugin_cpp = """#include "plugin.hpp" | ||||
Plugin *pluginInstance; | |||||
Plugin* pluginInstance; | |||||
void init(Plugin *p) { | |||||
void init(Plugin* p) { | |||||
pluginInstance = p; | pluginInstance = p; | ||||
// Add modules here | // Add modules here | ||||
@@ -245,7 +245,7 @@ def create_module(slug, panel_filename=None, source_filename=None): | |||||
# Tell user to add model to plugin.hpp and plugin.cpp | # Tell user to add model to plugin.hpp and plugin.cpp | ||||
print(f""" | print(f""" | ||||
To enable the module, add | To enable the module, add | ||||
extern Model *model{identifier}; | |||||
extern Model* model{identifier}; | |||||
to plugin.hpp, and add | to plugin.hpp, and add | ||||
p->addModel(model{identifier}); | p->addModel(model{identifier}); | ||||
to the init() function in plugin.cpp.""") | to the init() function in plugin.cpp.""") | ||||
@@ -396,7 +396,7 @@ struct {identifier} : Module {{""" | |||||
source += """ | source += """ | ||||
} | } | ||||
void process(const ProcessArgs &args) override { | |||||
void process(const ProcessArgs& args) override { | |||||
} | } | ||||
};""" | };""" | ||||
@@ -404,7 +404,7 @@ struct {identifier} : Module {{""" | |||||
struct {identifier}Widget : ModuleWidget {{ | struct {identifier}Widget : ModuleWidget {{ | ||||
{identifier}Widget({identifier} *module) {{ | |||||
{identifier}Widget({identifier}* module) {{ | |||||
setModule(module); | setModule(module); | ||||
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/{slug}.svg"))); | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/{slug}.svg"))); | ||||
@@ -475,7 +475,7 @@ struct {identifier}Widget : ModuleWidget {{ | |||||
}}; | }}; | ||||
Model *model{identifier} = createModel<{identifier}, {identifier}Widget>("{slug}");""" | |||||
Model* model{identifier} = createModel<{identifier}, {identifier}Widget>("{slug}");""" | |||||
return source | return source | ||||
@@ -147,7 +147,7 @@ inline float crossfade(float a, float b, float p) { | |||||
} | } | ||||
/** Linearly interpolates an array `p` with index `x`. | /** Linearly interpolates an array `p` with index `x`. | ||||
Assumes that the array at `p` is of length at least `floor(x) + 1`. | |||||
The array at `p` must be at least length `floor(x) + 2`. | |||||
*/ | */ | ||||
inline float interpolateLinear(const float* p, float x) { | inline float interpolateLinear(const float* p, float x) { | ||||
int xi = x; | int xi = x; | ||||
@@ -47,6 +47,11 @@ The launched process will continue running if the current process is closed. | |||||
*/ | */ | ||||
void runProcessDetached(const std::string& path); | void runProcessDetached(const std::string& path); | ||||
std::string getOperatingSystemInfo(); | std::string getOperatingSystemInfo(); | ||||
/** Unzips a ZIP file to a folder. | |||||
The folder must exist. | |||||
Returns 0 if successful. | |||||
*/ | |||||
int unzipToFolder(const std::string& zipPath, const std::string& dir); | |||||
} // namespace system | } // namespace system | ||||
@@ -320,12 +320,12 @@ struct ClearButton : ui::Button { | |||||
struct BrowserSidebar : widget::Widget { | struct BrowserSidebar : widget::Widget { | ||||
BrowserSearchField* searchField; | BrowserSearchField* searchField; | ||||
ClearButton* clearButton; | ClearButton* clearButton; | ||||
ui::Label* brandLabel; | |||||
ui::List* brandList; | |||||
ui::ScrollWidget* brandScroll; | |||||
ui::Label* tagLabel; | ui::Label* tagLabel; | ||||
ui::List* tagList; | ui::List* tagList; | ||||
ui::ScrollWidget* tagScroll; | ui::ScrollWidget* tagScroll; | ||||
ui::Label* brandLabel; | |||||
ui::List* brandList; | |||||
ui::ScrollWidget* brandScroll; | |||||
BrowserSidebar() { | BrowserSidebar() { | ||||
// Search | // Search | ||||
@@ -337,7 +337,28 @@ struct BrowserSidebar : widget::Widget { | |||||
clearButton->text = "Reset filters"; | clearButton->text = "Reset filters"; | ||||
addChild(clearButton); | addChild(clearButton); | ||||
// Bbrand label | |||||
// Tag label | |||||
tagLabel = new ui::Label; | |||||
// tagLabel->fontSize = 16; | |||||
tagLabel->color = nvgRGB(0x80, 0x80, 0x80); | |||||
tagLabel->text = "Tags"; | |||||
addChild(tagLabel); | |||||
// Tag list | |||||
tagScroll = new ui::ScrollWidget; | |||||
addChild(tagScroll); | |||||
tagList = new ui::List; | |||||
tagScroll->container->addChild(tagList); | |||||
for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) { | |||||
TagItem* item = new TagItem; | |||||
item->text = tag::tagAliases[tagId][0]; | |||||
item->tagId = tagId; | |||||
tagList->addChild(item); | |||||
} | |||||
// Brand label | |||||
brandLabel = new ui::Label; | brandLabel = new ui::Label; | ||||
// brandLabel->fontSize = 16; | // brandLabel->fontSize = 16; | ||||
brandLabel->color = nvgRGB(0x80, 0x80, 0x80); | brandLabel->color = nvgRGB(0x80, 0x80, 0x80); | ||||
@@ -362,27 +383,6 @@ struct BrowserSidebar : widget::Widget { | |||||
item->text = brand; | item->text = brand; | ||||
brandList->addChild(item); | brandList->addChild(item); | ||||
} | } | ||||
// Tag label | |||||
tagLabel = new ui::Label; | |||||
// tagLabel->fontSize = 16; | |||||
tagLabel->color = nvgRGB(0x80, 0x80, 0x80); | |||||
tagLabel->text = "Tags"; | |||||
addChild(tagLabel); | |||||
// Tag list | |||||
tagScroll = new ui::ScrollWidget; | |||||
addChild(tagScroll); | |||||
tagList = new ui::List; | |||||
tagScroll->container->addChild(tagList); | |||||
for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) { | |||||
TagItem* item = new TagItem; | |||||
item->text = tag::tagAliases[tagId][0]; | |||||
item->tagId = tagId; | |||||
tagList->addChild(item); | |||||
} | |||||
} | } | ||||
void step() override { | void step() override { | ||||
@@ -393,20 +393,20 @@ struct BrowserSidebar : widget::Widget { | |||||
float listHeight = (box.size.y - clearButton->box.getBottom()) / 2; | float listHeight = (box.size.y - clearButton->box.getBottom()) / 2; | ||||
listHeight = std::floor(listHeight); | listHeight = std::floor(listHeight); | ||||
brandLabel->box.pos = clearButton->box.getBottomLeft(); | |||||
brandLabel->box.size.x = box.size.x; | |||||
brandScroll->box.pos = brandLabel->box.getBottomLeft(); | |||||
brandScroll->box.size.y = listHeight - brandLabel->box.size.y; | |||||
brandScroll->box.size.x = box.size.x; | |||||
brandList->box.size.x = brandScroll->box.size.x; | |||||
tagLabel->box.pos = brandScroll->box.getBottomLeft(); | |||||
tagLabel->box.pos = clearButton->box.getBottomLeft(); | |||||
tagLabel->box.size.x = box.size.x; | tagLabel->box.size.x = box.size.x; | ||||
tagScroll->box.pos = tagLabel->box.getBottomLeft(); | tagScroll->box.pos = tagLabel->box.getBottomLeft(); | ||||
tagScroll->box.size.y = listHeight - tagLabel->box.size.y; | tagScroll->box.size.y = listHeight - tagLabel->box.size.y; | ||||
tagScroll->box.size.x = box.size.x; | tagScroll->box.size.x = box.size.x; | ||||
tagList->box.size.x = tagScroll->box.size.x; | tagList->box.size.x = tagScroll->box.size.x; | ||||
brandLabel->box.pos = tagScroll->box.getBottomLeft(); | |||||
brandLabel->box.size.x = box.size.x; | |||||
brandScroll->box.pos = brandLabel->box.getBottomLeft(); | |||||
brandScroll->box.size.y = listHeight - brandLabel->box.size.y; | |||||
brandScroll->box.size.x = box.size.x; | |||||
brandList->box.size.x = brandScroll->box.size.x; | |||||
Widget::step(); | Widget::step(); | ||||
} | } | ||||
}; | }; | ||||
@@ -615,7 +615,7 @@ inline void TagItem::step() { | |||||
} | } | ||||
inline void BrowserSearchField::onSelectKey(const event::SelectKey& e) { | inline void BrowserSearchField::onSelectKey(const event::SelectKey& e) { | ||||
if (e.action == GLFW_PRESS) { | |||||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||||
switch (e.key) { | switch (e.key) { | ||||
case GLFW_KEY_ESCAPE: { | case GLFW_KEY_ESCAPE: { | ||||
BrowserOverlay* overlay = getAncestorOfType<BrowserOverlay>(); | BrowserOverlay* overlay = getAncestorOfType<BrowserOverlay>(); | ||||
@@ -246,6 +246,7 @@ ModuleWidget::ModuleWidget() { | |||||
} | } | ||||
ModuleWidget::~ModuleWidget() { | ModuleWidget::~ModuleWidget() { | ||||
clearChildren(); | |||||
setModule(NULL); | setModule(NULL); | ||||
} | } | ||||
@@ -103,13 +103,20 @@ struct MIDI_Map : Module { | |||||
if (!filterInitialized[id]) { | if (!filterInitialized[id]) { | ||||
valueFilters[id].out = paramQuantity->getScaledValue(); | valueFilters[id].out = paramQuantity->getScaledValue(); | ||||
filterInitialized[id] = true; | filterInitialized[id] = true; | ||||
continue; | |||||
} | } | ||||
// Set param if value has been initialized | // Set param if value has been initialized | ||||
if (values[cc] >= 0) { | |||||
float v = values[cc] / 127.f; | |||||
v = valueFilters[id].process(args.sampleTime * divider.getDivision(), v); | |||||
paramQuantity->setScaledValue(v); | |||||
float value = values[cc] / 127.f; | |||||
// Detect behavior from MIDI buttons. | |||||
if (std::fabs(valueFilters[id].out - value) >= 1.f) { | |||||
// Jump value | |||||
valueFilters[id].out = value; | |||||
} | |||||
else { | |||||
// Smooth value with filter | |||||
valueFilters[id].process(args.sampleTime * divider.getDivision(), value); | |||||
} | } | ||||
paramQuantity->setScaledValue(valueFilters[id].out); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -17,8 +17,6 @@ | |||||
#include <map> | #include <map> | ||||
#include <stdexcept> | #include <stdexcept> | ||||
#define ZIP_STATIC | |||||
#include <zip.h> | |||||
#include <jansson.h> | #include <jansson.h> | ||||
#if defined ARCH_WIN | #if defined ARCH_WIN | ||||
@@ -174,74 +172,6 @@ static void loadPlugins(std::string path) { | |||||
} | } | ||||
} | } | ||||
/** Returns 0 if successful */ | |||||
static int extractZipHandle(zip_t* za, std::string dir) { | |||||
int err; | |||||
for (int i = 0; i < zip_get_num_entries(za, 0); i++) { | |||||
zip_stat_t zs; | |||||
err = zip_stat_index(za, i, 0, &zs); | |||||
if (err) { | |||||
WARN("zip_stat_index() failed: error %d", err); | |||||
return err; | |||||
} | |||||
std::string path = dir + "/" + zs.name; | |||||
if (path[path.size() - 1] == '/') { | |||||
system::createDirectory(path); | |||||
// HACK | |||||
// Create and delete file to update the directory's mtime. | |||||
std::string tmpPath = path + "/.tmp"; | |||||
FILE* tmpFile = fopen(tmpPath.c_str(), "w"); | |||||
fclose(tmpFile); | |||||
std::remove(tmpPath.c_str()); | |||||
} | |||||
else { | |||||
zip_file_t* zf = zip_fopen_index(za, i, 0); | |||||
if (!zf) { | |||||
WARN("zip_fopen_index() failed"); | |||||
return -1; | |||||
} | |||||
FILE* outFile = fopen(path.c_str(), "wb"); | |||||
if (!outFile) | |||||
continue; | |||||
while (1) { | |||||
char buffer[1 << 15]; | |||||
int len = zip_fread(zf, buffer, sizeof(buffer)); | |||||
if (len <= 0) | |||||
break; | |||||
fwrite(buffer, 1, len, outFile); | |||||
} | |||||
err = zip_fclose(zf); | |||||
if (err) { | |||||
WARN("zip_fclose() failed: error %d", err); | |||||
return err; | |||||
} | |||||
fclose(outFile); | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
/** Returns 0 if successful */ | |||||
static int extractZip(std::string filename, std::string path) { | |||||
int err; | |||||
zip_t* za = zip_open(filename.c_str(), 0, &err); | |||||
if (!za) { | |||||
WARN("Could not open zip %s: error %d", filename.c_str(), err); | |||||
return err; | |||||
} | |||||
DEFER({ | |||||
zip_close(za); | |||||
}); | |||||
err = extractZipHandle(za, path); | |||||
return err; | |||||
} | |||||
static void extractPackages(std::string path) { | static void extractPackages(std::string path) { | ||||
std::string message; | std::string message; | ||||
@@ -250,7 +180,7 @@ static void extractPackages(std::string path) { | |||||
continue; | continue; | ||||
INFO("Extracting package %s", packagePath.c_str()); | INFO("Extracting package %s", packagePath.c_str()); | ||||
// Extract package | // Extract package | ||||
if (extractZip(packagePath, path)) { | |||||
if (system::unzipToFolder(packagePath, path)) { | |||||
WARN("Package %s failed to extract", packagePath.c_str()); | WARN("Package %s failed to extract", packagePath.c_str()); | ||||
message += string::f("Could not extract package %s\n", packagePath.c_str()); | message += string::f("Could not extract package %s\n", packagePath.c_str()); | ||||
continue; | continue; | ||||
@@ -289,7 +219,7 @@ void init() { | |||||
std::string fundamentalDir = asset::pluginsPath + "/Fundamental"; | std::string fundamentalDir = asset::pluginsPath + "/Fundamental"; | ||||
if (!settings::devMode && !getPlugin("Fundamental") && system::isFile(fundamentalSrc)) { | if (!settings::devMode && !getPlugin("Fundamental") && system::isFile(fundamentalSrc)) { | ||||
INFO("Extracting bundled Fundamental package"); | INFO("Extracting bundled Fundamental package"); | ||||
extractZip(fundamentalSrc.c_str(), asset::pluginsPath.c_str()); | |||||
system::unzipToFolder(fundamentalSrc.c_str(), asset::pluginsPath.c_str()); | |||||
loadPlugin(fundamentalDir); | loadPlugin(fundamentalDir); | ||||
} | } | ||||
@@ -27,6 +27,9 @@ | |||||
#include <dbghelp.h> | #include <dbghelp.h> | ||||
#endif | #endif | ||||
#define ZIP_STATIC | |||||
#include <zip.h> | |||||
namespace rack { | namespace rack { | ||||
namespace system { | namespace system { | ||||
@@ -324,5 +327,73 @@ std::string getOperatingSystemInfo() { | |||||
} | } | ||||
int unzipToFolder(const std::string& zipPath, const std::string& dir) { | |||||
int err; | |||||
// Open ZIP file | |||||
zip_t* za = zip_open(zipPath.c_str(), 0, &err); | |||||
if (!za) { | |||||
WARN("Could not open ZIP file %s: error %d", zipPath.c_str(), err); | |||||
return err; | |||||
} | |||||
DEFER({ | |||||
zip_close(za); | |||||
}); | |||||
// Iterate ZIP entries | |||||
for (int i = 0; i < zip_get_num_entries(za, 0); i++) { | |||||
zip_stat_t zs; | |||||
err = zip_stat_index(za, i, 0, &zs); | |||||
if (err) { | |||||
WARN("zip_stat_index() failed: error %d", err); | |||||
return err; | |||||
} | |||||
std::string path = dir + "/" + zs.name; | |||||
if (path[path.size() - 1] == '/') { | |||||
// Create directory | |||||
system::createDirectory(path); | |||||
// HACK | |||||
// Create and delete file to update the directory's mtime. | |||||
std::string tmpPath = path + "/.tmp"; | |||||
FILE* tmpFile = fopen(tmpPath.c_str(), "w"); | |||||
fclose(tmpFile); | |||||
std::remove(tmpPath.c_str()); | |||||
} | |||||
else { | |||||
// Open ZIP entry | |||||
zip_file_t* zf = zip_fopen_index(za, i, 0); | |||||
if (!zf) { | |||||
WARN("zip_fopen_index() failed"); | |||||
return -1; | |||||
} | |||||
DEFER({ | |||||
zip_fclose(zf); | |||||
}); | |||||
// Create file | |||||
FILE* outFile = fopen(path.c_str(), "wb"); | |||||
if (!outFile) { | |||||
WARN("Could not create file %s", path.c_str()); | |||||
return -1; | |||||
} | |||||
DEFER({ | |||||
fclose(outFile); | |||||
}); | |||||
// Read buffer and copy to file | |||||
while (true) { | |||||
char buffer[1 << 15]; | |||||
int len = zip_fread(zf, buffer, sizeof(buffer)); | |||||
if (len <= 0) | |||||
break; | |||||
fwrite(buffer, 1, len, outFile); | |||||
} | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
} // namespace system | } // namespace system | ||||
} // namespace rack | } // namespace rack |
@@ -8,30 +8,31 @@ namespace tag { | |||||
const std::vector<std::vector<std::string>> tagAliases = { | const std::vector<std::vector<std::string>> tagAliases = { | ||||
{"Arpeggiator"}, | |||||
{"Attenuator"}, // With a level knob and not much else. | |||||
{"Blank"}, // No parameters or ports. Serves no purpose except visual. | |||||
{"Arpeggiator"}, // With a level knob and not much else. | |||||
{"Attenuator"}, // No parameters or ports. Serves no purpose except visual. | |||||
{"Blank"}, | |||||
{"Chorus"}, | {"Chorus"}, | ||||
{"Clock generator", "Clock"}, | |||||
{"Clock modulator"}, // Clock dividers, multipliers, etc. | |||||
{"Compressor"}, // With threshold, ratio, knee, etc parameters. | |||||
{"Controller"}, // Use only if the artist "performs" with this module. Simply having knobs is not enough. Examples: on-screen keyboard, XY pad. | |||||
{"Clock generator", "Clock"}, // Clock dividers, multipliers, etc. | |||||
{"Clock modulator"}, // With threshold, ratio, knee, etc parameters. | |||||
{"Compressor"}, // Use only if the artist "performs" with this module. Simply having knobs is not enough. Examples: on-screen keyboard, XY pad. | |||||
{"Controller"}, | |||||
{"Delay"}, | {"Delay"}, | ||||
{"Digital"}, | {"Digital"}, | ||||
{"Distortion"}, | {"Distortion"}, | ||||
{"Drum", "Drums", "Percussion"}, | |||||
{"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. | |||||
{"Drum", "Drums", "Percussion"}, // 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. | |||||
{"Dual"}, | |||||
{"Dynamics"}, | {"Dynamics"}, | ||||
{"Effect"}, | {"Effect"}, | ||||
{"Envelope follower"}, | {"Envelope follower"}, | ||||
{"Envelope generator"}, | {"Envelope generator"}, | ||||
{"Equalizer", "EQ"}, | |||||
{"Expander"}, // Expands the functionality of a "mother" module when placed next to it. Expanders should inherit the tags of its mother module. | |||||
{"Equalizer", "EQ"}, // Expands the functionality of a "mother" module when placed next to it. Expanders should inherit the tags of its mother module. | |||||
{"Expander"}, | |||||
{"External"}, | {"External"}, | ||||
{"Filter", "VCF", "Voltage controlled filter"}, | {"Filter", "VCF", "Voltage controlled filter"}, | ||||
{"Flanger"}, | {"Flanger"}, | ||||
{"Function generator"}, | {"Function generator"}, | ||||
{"Granular"}, | {"Granular"}, | ||||
{"Hardware clone", "Hardware"}, // Clones the functionality *and* appearance of a real-world hardware module. | |||||
{"Limiter"}, | {"Limiter"}, | ||||
{"Logic"}, | {"Logic"}, | ||||
{"Low-frequency oscillator", "LFO", "Low frequency oscillator"}, | {"Low-frequency oscillator", "LFO", "Low frequency oscillator"}, | ||||
@@ -44,8 +45,8 @@ const std::vector<std::vector<std::string>> tagAliases = { | |||||
{"Panning", "Pan"}, | {"Panning", "Pan"}, | ||||
{"Phaser"}, | {"Phaser"}, | ||||
{"Physical modeling"}, | {"Physical modeling"}, | ||||
{"Polyphonic", "Poly"}, | |||||
{"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. | |||||
{"Polyphonic", "Poly"}, // 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. | |||||
{"Quad"}, | |||||
{"Quantizer"}, | {"Quantizer"}, | ||||
{"Random"}, | {"Random"}, | ||||
{"Recording"}, | {"Recording"}, | ||||
@@ -55,10 +56,10 @@ const std::vector<std::vector<std::string>> tagAliases = { | |||||
{"Sampler"}, | {"Sampler"}, | ||||
{"Sequencer"}, | {"Sequencer"}, | ||||
{"Slew limiter"}, | {"Slew limiter"}, | ||||
{"Switch"}, | |||||
{"Synth voice"}, // A synth voice must have, at the minimum, a built-in oscillator and envelope. | |||||
{"Tuner"}, | |||||
{"Utility"}, // Serves only extremely basic functions, like inverting, max, min, multiplying by 2, etc. | |||||
{"Switch"}, // A synth voice must have, at the minimum, a built-in oscillator and envelope. | |||||
{"Synth voice"}, | |||||
{"Tuner"}, // Serves only extremely basic functions, like inverting, max, min, multiplying by 2, etc. | |||||
{"Utility"}, | |||||
{"Visual"}, | {"Visual"}, | ||||
{"Vocoder"}, | {"Vocoder"}, | ||||
{"Voltage-controlled amplifier", "Amplifier", "VCA", "Voltage controlled amplifier"}, | {"Voltage-controlled amplifier", "Amplifier", "VCA", "Voltage controlled amplifier"}, | ||||
@@ -61,16 +61,23 @@ void update() { | |||||
if (downloadUrl == "") | if (downloadUrl == "") | ||||
return; | return; | ||||
#if defined ARCH_WIN | |||||
// Download and launch the installer on Windows | |||||
#if defined ARCH_WIN || defined ARCH_MAC | |||||
// Download update | |||||
std::string filename = string::filename(network::urlPath(downloadUrl)); | std::string filename = string::filename(network::urlPath(downloadUrl)); | ||||
std::string path = asset::user(filename); | std::string path = asset::user(filename); | ||||
INFO("Download update %s to %s", downloadUrl.c_str(), path.c_str()); | INFO("Download update %s to %s", downloadUrl.c_str(), path.c_str()); | ||||
network::requestDownload(downloadUrl, path, &progress); | network::requestDownload(downloadUrl, path, &progress); | ||||
#endif | |||||
#if defined ARCH_WIN | |||||
// Launch the installer | |||||
INFO("Launching update %s", path.c_str()); | INFO("Launching update %s", path.c_str()); | ||||
system::runProcessDetached(path); | system::runProcessDetached(path); | ||||
#elif defined ARCH_MAC | |||||
// Unzip app using Apple's unzipper, since Rack's unzipper doesn't handle the metadata stuff correctly. | |||||
std::string cmd = "open \"" + path + "\""; | |||||
std::system(cmd.c_str()); | |||||
#else | #else | ||||
// Open the browser on Mac and Linux. The user will know what to do. | |||||
system::openBrowser(downloadUrl); | system::openBrowser(downloadUrl); | ||||
#endif | #endif | ||||