# Conflicts: # .gitignorepull/1514/head^2
| @@ -12,5 +12,5 @@ | |||
| /autosave.vcv | |||
| /settings.json | |||
| /screenshots | |||
| /.vs | |||
| /_ReSharper.Caches | |||
| .vs/ | |||
| _ReSharper.Caches/ | |||
| @@ -2,6 +2,13 @@ | |||
| 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) | |||
| - Fix parameter smoothing of MIDI-Map. | |||
| - 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 | |||
| 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 | |||
| 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 ?= . | |||
| # 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 += -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) | |||
| 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 \ | |||
| $(shell pkg-config --libs gtk+-2.0) | |||
| TARGET := Rack | |||
| @@ -31,14 +31,14 @@ ifdef ARCH_MAC | |||
| SOURCES += dep/osdialog/osdialog_mac.m | |||
| LDFLAGS += -lpthread -ldl \ | |||
| -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 | |||
| endif | |||
| ifdef ARCH_WIN | |||
| SOURCES += dep/osdialog/osdialog_win.c | |||
| 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 | |||
| TARGET := Rack.exe | |||
| OBJECTS += Rack.res | |||
| @@ -10,39 +10,42 @@ ifdef ARCH_LIN | |||
| glew = lib/libGLEW.a | |||
| glfw = lib/libglfw3.a | |||
| jansson = lib/libjansson.a | |||
| libspeexdsp = lib/libspeexdsp.a | |||
| openssl = lib/libssl.a | |||
| libcurl = lib/libcurl.a | |||
| libzip = lib/libzip.a | |||
| zlib = lib/libz.a | |||
| libspeexdsp = lib/libspeexdsp.a | |||
| libsamplerate = lib/libsamplerate.a | |||
| rtmidi = lib/librtmidi.a | |||
| rtaudio = lib/librtaudio.a | |||
| openssl = lib/libssl.a | |||
| endif | |||
| ifdef ARCH_MAC | |||
| glew = lib/libGLEW.a | |||
| glfw = lib/libglfw3.a | |||
| jansson = lib/libjansson.a | |||
| libspeexdsp = lib/libspeexdsp.a | |||
| openssl = lib/libssl.a | |||
| libcurl = lib/libcurl.a | |||
| libzip = lib/libzip.a | |||
| zlib = lib/libz.a | |||
| libspeexdsp = lib/libspeexdsp.a | |||
| libsamplerate = lib/libsamplerate.a | |||
| rtmidi = lib/librtmidi.a | |||
| rtaudio = lib/librtaudio.a | |||
| openssl = lib/libssl.a | |||
| endif | |||
| ifdef ARCH_WIN | |||
| glew = lib/libglew32.a | |||
| glfw = lib/libglfw3.a | |||
| jansson = lib/libjansson.a | |||
| libspeexdsp = lib/libspeexdsp.a | |||
| openssl = lib/libssl.a | |||
| libcurl = lib/libcurl.a | |||
| libzip = lib/libzip.a | |||
| zlib = lib/libz.a | |||
| libspeexdsp = lib/libspeexdsp.a | |||
| libsamplerate = lib/libsamplerate.a | |||
| rtmidi = lib/librtmidi.a | |||
| rtaudio = lib/librtaudio.a | |||
| openssl = lib/libssl.a | |||
| endif | |||
| nanovg = include/nanovg.h | |||
| @@ -51,7 +54,20 @@ oui-blendish = include/blendish.h | |||
| osdialog = include/osdialog.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 := . | |||
| @@ -90,17 +106,6 @@ $(jansson): jansson-2.12 | |||
| $(MAKE) -C jansson-2.12 | |||
| $(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: | |||
| $(WGET) "https://www.openssl.org/source/openssl-1.1.1b.tar.gz" | |||
| $(SHA256) openssl-1.1.1b.tar.gz 5c557b023230413dfb0756f3137a13e6d726838ccd1430888ad15bfb2b43ea4b | |||
| @@ -157,6 +162,28 @@ else | |||
| $(MAKE) -C zlib-1.2.11 install | |||
| 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: | |||
| $(WGET) "http://www.music.mcgill.ca/~gary/rtmidi/release/rtmidi-4.0.0.tar.gz" | |||
| $(SHA256) rtmidi-4.0.0.tar.gz 370cfe710f43fbeba8d2b8c8bc310f314338c519c2cf2865e2d2737b251526cd | |||
| @@ -100,10 +100,10 @@ include $(RACK_DIR)/plugin.mk | |||
| using namespace rack; | |||
| // Declare the Plugin, defined in plugin.cpp | |||
| extern Plugin *pluginInstance; | |||
| extern Plugin* pluginInstance; | |||
| // 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: | |||
| f.write(plugin_hpp) | |||
| @@ -112,10 +112,10 @@ extern Plugin *pluginInstance; | |||
| plugin_cpp = """#include "plugin.hpp" | |||
| Plugin *pluginInstance; | |||
| Plugin* pluginInstance; | |||
| void init(Plugin *p) { | |||
| void init(Plugin* p) { | |||
| pluginInstance = p; | |||
| // 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 | |||
| print(f""" | |||
| To enable the module, add | |||
| extern Model *model{identifier}; | |||
| extern Model* model{identifier}; | |||
| to plugin.hpp, and add | |||
| p->addModel(model{identifier}); | |||
| to the init() function in plugin.cpp.""") | |||
| @@ -396,7 +396,7 @@ struct {identifier} : Module {{""" | |||
| source += """ | |||
| } | |||
| void process(const ProcessArgs &args) override { | |||
| void process(const ProcessArgs& args) override { | |||
| } | |||
| };""" | |||
| @@ -404,7 +404,7 @@ struct {identifier} : Module {{""" | |||
| struct {identifier}Widget : ModuleWidget {{ | |||
| {identifier}Widget({identifier} *module) {{ | |||
| {identifier}Widget({identifier}* module) {{ | |||
| setModule(module); | |||
| 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 | |||
| @@ -147,7 +147,7 @@ inline float crossfade(float a, float b, float p) { | |||
| } | |||
| /** 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) { | |||
| 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); | |||
| 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 | |||
| @@ -320,12 +320,12 @@ struct ClearButton : ui::Button { | |||
| struct BrowserSidebar : widget::Widget { | |||
| BrowserSearchField* searchField; | |||
| ClearButton* clearButton; | |||
| ui::Label* brandLabel; | |||
| ui::List* brandList; | |||
| ui::ScrollWidget* brandScroll; | |||
| ui::Label* tagLabel; | |||
| ui::List* tagList; | |||
| ui::ScrollWidget* tagScroll; | |||
| ui::Label* brandLabel; | |||
| ui::List* brandList; | |||
| ui::ScrollWidget* brandScroll; | |||
| BrowserSidebar() { | |||
| // Search | |||
| @@ -337,7 +337,28 @@ struct BrowserSidebar : widget::Widget { | |||
| clearButton->text = "Reset filters"; | |||
| 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->fontSize = 16; | |||
| brandLabel->color = nvgRGB(0x80, 0x80, 0x80); | |||
| @@ -362,27 +383,6 @@ struct BrowserSidebar : widget::Widget { | |||
| item->text = brand; | |||
| 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 { | |||
| @@ -393,20 +393,20 @@ struct BrowserSidebar : widget::Widget { | |||
| float listHeight = (box.size.y - clearButton->box.getBottom()) / 2; | |||
| 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; | |||
| tagScroll->box.pos = tagLabel->box.getBottomLeft(); | |||
| tagScroll->box.size.y = listHeight - tagLabel->box.size.y; | |||
| tagScroll->box.size.x = 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(); | |||
| } | |||
| }; | |||
| @@ -615,7 +615,7 @@ inline void TagItem::step() { | |||
| } | |||
| 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) { | |||
| case GLFW_KEY_ESCAPE: { | |||
| BrowserOverlay* overlay = getAncestorOfType<BrowserOverlay>(); | |||
| @@ -246,6 +246,7 @@ ModuleWidget::ModuleWidget() { | |||
| } | |||
| ModuleWidget::~ModuleWidget() { | |||
| clearChildren(); | |||
| setModule(NULL); | |||
| } | |||
| @@ -103,13 +103,20 @@ struct MIDI_Map : Module { | |||
| if (!filterInitialized[id]) { | |||
| valueFilters[id].out = paramQuantity->getScaledValue(); | |||
| filterInitialized[id] = true; | |||
| continue; | |||
| } | |||
| // 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 <stdexcept> | |||
| #define ZIP_STATIC | |||
| #include <zip.h> | |||
| #include <jansson.h> | |||
| #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) { | |||
| std::string message; | |||
| @@ -250,7 +180,7 @@ static void extractPackages(std::string path) { | |||
| continue; | |||
| INFO("Extracting package %s", packagePath.c_str()); | |||
| // Extract package | |||
| if (extractZip(packagePath, path)) { | |||
| if (system::unzipToFolder(packagePath, path)) { | |||
| WARN("Package %s failed to extract", packagePath.c_str()); | |||
| message += string::f("Could not extract package %s\n", packagePath.c_str()); | |||
| continue; | |||
| @@ -289,7 +219,7 @@ void init() { | |||
| std::string fundamentalDir = asset::pluginsPath + "/Fundamental"; | |||
| if (!settings::devMode && !getPlugin("Fundamental") && system::isFile(fundamentalSrc)) { | |||
| INFO("Extracting bundled Fundamental package"); | |||
| extractZip(fundamentalSrc.c_str(), asset::pluginsPath.c_str()); | |||
| system::unzipToFolder(fundamentalSrc.c_str(), asset::pluginsPath.c_str()); | |||
| loadPlugin(fundamentalDir); | |||
| } | |||
| @@ -27,6 +27,9 @@ | |||
| #include <dbghelp.h> | |||
| #endif | |||
| #define ZIP_STATIC | |||
| #include <zip.h> | |||
| namespace rack { | |||
| 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 rack | |||
| @@ -8,30 +8,31 @@ namespace tag { | |||
| 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"}, | |||
| {"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"}, | |||
| {"Digital"}, | |||
| {"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"}, | |||
| {"Effect"}, | |||
| {"Envelope follower"}, | |||
| {"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"}, | |||
| {"Filter", "VCF", "Voltage controlled filter"}, | |||
| {"Flanger"}, | |||
| {"Function generator"}, | |||
| {"Granular"}, | |||
| {"Hardware clone", "Hardware"}, // Clones the functionality *and* appearance of a real-world hardware module. | |||
| {"Limiter"}, | |||
| {"Logic"}, | |||
| {"Low-frequency oscillator", "LFO", "Low frequency oscillator"}, | |||
| @@ -44,8 +45,8 @@ const std::vector<std::vector<std::string>> tagAliases = { | |||
| {"Panning", "Pan"}, | |||
| {"Phaser"}, | |||
| {"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"}, | |||
| {"Random"}, | |||
| {"Recording"}, | |||
| @@ -55,10 +56,10 @@ const std::vector<std::vector<std::string>> tagAliases = { | |||
| {"Sampler"}, | |||
| {"Sequencer"}, | |||
| {"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"}, | |||
| {"Vocoder"}, | |||
| {"Voltage-controlled amplifier", "Amplifier", "VCA", "Voltage controlled amplifier"}, | |||
| @@ -61,16 +61,23 @@ void update() { | |||
| if (downloadUrl == "") | |||
| 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 path = asset::user(filename); | |||
| INFO("Download update %s to %s", downloadUrl.c_str(), path.c_str()); | |||
| network::requestDownload(downloadUrl, path, &progress); | |||
| #endif | |||
| #if defined ARCH_WIN | |||
| // Launch the installer | |||
| INFO("Launching update %s", path.c_str()); | |||
| 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 | |||
| // Open the browser on Mac and Linux. The user will know what to do. | |||
| system::openBrowser(downloadUrl); | |||
| #endif | |||