diff --git a/.gitignore b/.gitignore index 8dd27f2b..32290977 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,5 @@ /autosave.vcv /settings.json /screenshots -/.vs -/_ReSharper.Caches +.vs/ +_ReSharper.Caches/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f6e05915..eddb5de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/LICENSE-dist.txt b/LICENSE-dist.txt index 57db3252..76e0db40 100644 --- a/LICENSE-dist.txt +++ b/LICENSE-dist.txt @@ -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. \ No newline at end of file +SOFTWARE. + + +# libsamplerate + +Copyright (c) 2012-2016, Erik de Castro Lopo +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. diff --git a/Makefile b/Makefile index a0df47af..bef63c93 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/dep/Makefile b/dep/Makefile index 2fe91d3e..501d1d0e 100755 --- a/dep/Makefile +++ b/dep/Makefile @@ -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 diff --git a/helper.py b/helper.py index b34dfb22..c4e388bc 100755 --- a/helper.py +++ b/helper.py @@ -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 diff --git a/include/math.hpp b/include/math.hpp index 8011ac05..2c345cee 100644 --- a/include/math.hpp +++ b/include/math.hpp @@ -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; diff --git a/include/system.hpp b/include/system.hpp index c4519498..c482f0b2 100644 --- a/include/system.hpp +++ b/include/system.hpp @@ -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 diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index 21a1386d..17432edf 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -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(); diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index b88aaec9..998ac245 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -246,6 +246,7 @@ ModuleWidget::ModuleWidget() { } ModuleWidget::~ModuleWidget() { + clearChildren(); setModule(NULL); } diff --git a/src/core/MIDI_Map.cpp b/src/core/MIDI_Map.cpp index 7435bf8d..111d964e 100644 --- a/src/core/MIDI_Map.cpp +++ b/src/core/MIDI_Map.cpp @@ -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); } } } diff --git a/src/plugin.cpp b/src/plugin.cpp index 3f80e06f..91c0d0fd 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -17,8 +17,6 @@ #include #include -#define ZIP_STATIC -#include #include #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); } diff --git a/src/system.cpp b/src/system.cpp index 920bae0f..f0d78093 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -27,6 +27,9 @@ #include #endif +#define ZIP_STATIC +#include + 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 diff --git a/src/tag.cpp b/src/tag.cpp index 78e6daaf..3557f915 100644 --- a/src/tag.cpp +++ b/src/tag.cpp @@ -8,30 +8,31 @@ namespace tag { const std::vector> 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> 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> 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"}, diff --git a/src/updater.cpp b/src/updater.cpp index 5b2f7a7f..15a93fcc 100644 --- a/src/updater.cpp +++ b/src/updater.cpp @@ -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