# 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 | |||