diff --git a/CHANGELOG.md b/CHANGELOG.md index dd48f0b5..20721cd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ -### 0.6.1 (in development) +### 0.6.1 (2018-06-17) - Added gamepad MIDI driver - Added computer keyboard MIDI driver - Added JACK support on Linux - Added velocity mode to MIDI-Trig - Added MIDI multiplexing so multiple MIDI modules can use the same MIDI device on Windows -- Make Module Browser layout more compact +- Made Module Browser layout more compact - Add power meter - Add icons to toolbar - [VCV Bridge](https://vcvrack.com/manual/Bridge.html) 0.6.1 diff --git a/Makefile b/Makefile index 56c515b5..859ecc52 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ RACK_DIR ?= . -VERSION = 0.6.1dev +VERSION = 0.6.1 FLAGS += \ -Iinclude \ @@ -13,6 +13,7 @@ STRIP ?= strip SOURCES += dep/nanovg/src/nanovg.c SOURCES += dep/osdialog/osdialog.c +SOURCES += $(wildcard dep/jpommier-pffft-*/pffft.c) $(wildcard dep/jpommier-pffft-*/fftpack.c) SOURCES += $(wildcard src/*.cpp src/*/*.cpp) ifdef ARCH_MAC diff --git a/dep.mk b/dep.mk index 48b3c290..6ad37f06 100644 --- a/dep.mk +++ b/dep.mk @@ -15,7 +15,7 @@ DEP_CXXFLAGS += $(DEP_FLAGS) # Commands WGET := wget -c UNTAR := tar xf -UNZIP := unzip +UNZIP := unzip -o CONFIGURE := ./configure --prefix="$(realpath $(DEP_LOCAL))" ifeq ($(ARCH), win) CMAKE := cmake -G 'MSYS Makefiles' -DCMAKE_INSTALL_PREFIX="$(realpath $(DEP_LOCAL))" diff --git a/dep/Makefile b/dep/Makefile index ae9a4305..5a9f16c4 100755 --- a/dep/Makefile +++ b/dep/Makefile @@ -49,8 +49,9 @@ nanovg = include/nanovg.h nanosvg = include/nanosvg.h 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) +DEPS += $(glew) $(glfw) $(jansson) $(libspeexdsp) $(libcurl) $(libzip) $(rtmidi) $(rtaudio) $(nanovg) $(nanosvg) $(oui-blendish) $(osdialog) $(pffft) include $(RACK_DIR)/dep.mk @@ -158,6 +159,11 @@ $(oui-blendish): $(wildcard oui-blendish/*.h) $(osdialog): $(wildcard osdialog/*.h) cp $^ include/ +$(pffft): + $(WGET) "https://bitbucket.org/jpommier/pffft/get/29e4f76ac53b.zip" + $(UNZIP) 29e4f76ac53b.zip + cp jpommier-pffft-29e4f76ac53b/*.h include/ + clean: git clean -fdx git submodule foreach git clean -fdx diff --git a/include/app.hpp b/include/app.hpp index 59a1b0d1..2e59e654 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -534,13 +534,15 @@ struct RackScene : Scene { extern std::string gApplicationName; extern std::string gApplicationVersion; extern std::string gApiHost; +extern std::string gLatestVersion; +extern bool gCheckVersion; // Easy access to "singleton" widgets extern RackScene *gRackScene; extern RackWidget *gRackWidget; extern Toolbar *gToolbar; -void appInit(); +void appInit(bool devMode); void appDestroy(); void appModuleBrowserCreate(); json_t *appModuleBrowserToJson(); diff --git a/include/settings.hpp b/include/settings.hpp index c0a8858d..5f89d5a8 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -6,11 +6,10 @@ namespace rack { +extern bool gSkipAutosaveOnLaunch; + void settingsSave(std::string filename); void settingsLoad(std::string filename); -extern bool skipAutosaveOnLaunch; - - } // namespace rack diff --git a/include/util/request.hpp b/include/util/request.hpp index 86fa2208..fad102d2 100644 --- a/include/util/request.hpp +++ b/include/util/request.hpp @@ -13,9 +13,11 @@ enum RequestMethod { METHOD_DELETE, }; -/** Requests a JSON API URL over HTTP(S), using the data as the query (GET) or the body (POST, etc) */ +/** Requests a JSON API URL over HTTP(S), using the data as the query (GET) or the body (POST, etc) +Caller must json_decref(). +*/ json_t *requestJson(RequestMethod method, std::string url, json_t *dataJ); -/** Returns the filename, blank if unsuccessful */ +/** Returns true if downloaded successfully */ bool requestDownload(std::string url, std::string filename, float *progress); /** URL-encodes `s` */ std::string requestEscape(std::string s); diff --git a/src/Core/MIDICCToCVInterface.cpp b/src/Core/MIDICCToCVInterface.cpp index ba43d335..92aaddd9 100644 --- a/src/Core/MIDICCToCVInterface.cpp +++ b/src/Core/MIDICCToCVInterface.cpp @@ -1,6 +1,7 @@ #include "Core.hpp" #include "midi.hpp" #include "dsp/filter.hpp" +#include "window.hpp" struct MIDICCToCVInterface : Module { @@ -19,19 +20,21 @@ struct MIDICCToCVInterface : Module { }; MidiInputQueue midiInput; - int8_t cvs[16]; + int8_t ccs[128]; ExponentialFilter ccFilters[16]; int learningId = -1; - uint8_t learnedCcs[16] = {}; + int learnedCcs[16] = {}; MIDICCToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { onReset(); } void onReset() override { + for (int i = 0; i < 128; i++) { + ccs[i] = 0; + } for (int i = 0; i < 16; i++) { - cvs[i] = 0; learnedCcs[i] = i; } learningId = -1; @@ -45,7 +48,8 @@ struct MIDICCToCVInterface : Module { float lambda = 100.f * engineGetSampleTime(); for (int i = 0; i < 16; i++) { - float value = rescale(cvs[i], 0, 127, 0.f, 10.f); + int learnedCc = learnedCcs[i]; + float value = rescale(clamp(ccs[learnedCc], -127, 127), 0, 127, 0.f, 10.f); ccFilters[i].lambda = lambda; outputs[CC_OUTPUT + i].value = ccFilters[i].process(value); } @@ -57,17 +61,13 @@ struct MIDICCToCVInterface : Module { case 0xb: { uint8_t cc = msg.note(); // Learn - if (learningId >= 0) { + if (learningId >= 0 && ccs[cc] != msg.data2) { learnedCcs[learningId] = cc; learningId = -1; } // Set CV - for (int i = 0; i < 16; i++) { - if (learnedCcs[i] == cc) { - // Allow CC to be negative if the 8th bit is set - cvs[i] = msg.data2; - } - } + // Allow CC to be negative if the 8th bit is set + ccs[cc] = msg.data2; } break; default: break; } @@ -107,6 +107,7 @@ struct MIDICCToCVInterface : Module { struct MidiCcChoice : GridChoice { MIDICCToCVInterface *module; int id; + int focusCc; MidiCcChoice() { box.size.y = mm2px(6.666); @@ -119,7 +120,10 @@ struct MidiCcChoice : GridChoice { void step() override { if (module->learningId == id) { - text = "LRN"; + if (0 <= focusCc) + text = stringf("%d", focusCc); + else + text = "LRN"; color.a = 0.5; } else { @@ -133,11 +137,36 @@ struct MidiCcChoice : GridChoice { void onFocus(EventFocus &e) override { e.consumed = true; module->learningId = id; + focusCc = -1; } void onDefocus(EventDefocus &e) override { + if (0 <= focusCc && focusCc < 128) { + module->learnedCcs[id] = focusCc; + } module->learningId = -1; } + + void onText(EventText &e) override { + char c = e.codepoint; + if ('0' <= c && c <= '9') { + if (focusCc < 0) + focusCc = 0; + focusCc = focusCc * 10 + (c - '0'); + } + e.consumed = true; + } + + void onKey(EventKey &e) override { + if (gFocusedWidget == this) { + if (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) { + EventDefocus eDefocus; + onDefocus(eDefocus); + gFocusedWidget = NULL; + e.consumed = true; + } + } + } }; diff --git a/src/app/RackScene.cpp b/src/app/RackScene.cpp index 2ebc9657..71ed6147 100644 --- a/src/app/RackScene.cpp +++ b/src/app/RackScene.cpp @@ -1,6 +1,7 @@ #include "app.hpp" #include "window.hpp" #include "util/request.hpp" +#include "osdialog.h" #include #include @@ -40,6 +41,17 @@ void RackScene::step() { Scene::step(); zoomWidget->box.size = gRackWidget->box.size.mult(zoomWidget->zoom); + + // Version popup message + if (!gLatestVersion.empty()) { + std::string versionMessage = stringf("Rack %s is available.\n\nYou have Rack %s.\n\nClose Rack and download new version on the website?", gLatestVersion.c_str(), gApplicationVersion.c_str()); + if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, versionMessage.c_str())) { + std::thread t(systemOpenBrowser, "https://vcvrack.com/"); + t.detach(); + windowClose(); + } + gLatestVersion = ""; + } } void RackScene::draw(NVGcontext *vg) { diff --git a/src/app/app.cpp b/src/app/app.cpp index bd94da9b..5fec6af6 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -1,23 +1,49 @@ #include "app.hpp" +#include "util/request.hpp" +#include namespace rack { -bool gDev = false; std::string gApplicationName = "VCV Rack"; std::string gApplicationVersion = TOSTRING(VERSION); std::string gApiHost = "https://api.vcvrack.com"; // std::string gApiHost = "http://localhost:8081"; +std::string gLatestVersion; +bool gCheckVersion = true; + RackWidget *gRackWidget = NULL; Toolbar *gToolbar = NULL; RackScene *gRackScene = NULL; -void appInit() { +static void checkVersion() { + json_t *resJ = requestJson(METHOD_GET, gApiHost + "/version", NULL); + + if (resJ) { + json_t *versionJ = json_object_get(resJ, "version"); + if (versionJ) { + const char *version = json_string_value(versionJ); + if (version && version != gApplicationVersion) { + gLatestVersion = version; + } + } + json_decref(resJ); + } +} + + +void appInit(bool devMode) { gRackScene = new RackScene(); gScene = gRackScene; + + // Request latest version from server + if (!devMode && gCheckVersion) { + std::thread t(checkVersion); + t.detach(); + } } void appDestroy() { diff --git a/src/engine.cpp b/src/engine.cpp index 08b850b3..63c3dc2a 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -54,8 +54,6 @@ void Light::setBrightnessSmooth(float brightness, float frames) { void Wire::step() { float value = outputModule->outputs[outputId].value; - // Assume a +/-12V power supply (like Eurorack), and prevent voltages outside the power range. - value = clamp(value, -12.f, 12.f); inputModule->inputs[inputId].value = value; } diff --git a/src/main.cpp b/src/main.cpp index 68cf3610..6bc28b6f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -58,16 +58,16 @@ int main(int argc, char* argv[]) { keyboardInit(); gamepadInit(); windowInit(); - appInit(); + appInit(devMode); settingsLoad(assetLocal("settings.json")); if (patchFile.empty()) { std::string oldLastPath = gRackWidget->lastPath; // To prevent launch crashes, if Rack crashes between now and 15 seconds from now, the "skipAutosaveOnLaunch" property will remain in settings.json, so that in the next launch, the broken autosave will not be loaded. - bool oldSkipAutosaveOnLaunch = skipAutosaveOnLaunch; - skipAutosaveOnLaunch = true; + bool oldSkipAutosaveOnLaunch = gSkipAutosaveOnLaunch; + gSkipAutosaveOnLaunch = true; settingsSave(assetLocal("settings.json")); - skipAutosaveOnLaunch = false; + gSkipAutosaveOnLaunch = false; if (oldSkipAutosaveOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, possibly caused by a faulty module in your patch. Would you like to clear your patch and start over?")) { // Do nothing. Empty patch is already loaded. } diff --git a/src/settings.cpp b/src/settings.cpp index 5f02195f..29fdd9fd 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -9,7 +9,7 @@ namespace rack { -bool skipAutosaveOnLaunch = false; +bool gSkipAutosaveOnLaunch = false; static json_t *settingsToJson() { @@ -60,7 +60,7 @@ static json_t *settingsToJson() { json_object_set_new(rootJ, "lastPath", lastPathJ); // skipAutosaveOnLaunch - if (skipAutosaveOnLaunch) { + if (gSkipAutosaveOnLaunch) { json_object_set_new(rootJ, "skipAutosaveOnLaunch", json_true()); } @@ -70,6 +70,9 @@ static json_t *settingsToJson() { // powerMeter json_object_set_new(rootJ, "powerMeter", json_boolean(gPowerMeter)); + // checkVersion + json_object_set_new(rootJ, "checkVersion", json_boolean(gCheckVersion)); + return rootJ; } @@ -132,7 +135,7 @@ static void settingsFromJson(json_t *rootJ) { // skipAutosaveOnLaunch json_t *skipAutosaveOnLaunchJ = json_object_get(rootJ, "skipAutosaveOnLaunch"); if (skipAutosaveOnLaunchJ) - skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); + gSkipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); // moduleBrowser json_t *moduleBrowserJ = json_object_get(rootJ, "moduleBrowser"); @@ -143,6 +146,11 @@ static void settingsFromJson(json_t *rootJ) { json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); if (powerMeterJ) gPowerMeter = json_boolean_value(powerMeterJ); + + // checkVersion + json_t *checkVersionJ = json_object_get(rootJ, "checkVersion"); + if (checkVersionJ) + gCheckVersion = json_boolean_value(checkVersionJ); } diff --git a/src/util/request.cpp b/src/util/request.cpp index c8368aaf..1e2324e6 100644 --- a/src/util/request.cpp +++ b/src/util/request.cpp @@ -115,6 +115,9 @@ static int xferInfoCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, } bool requestDownload(std::string url, std::string filename, float *progress) { + if (progress) + *progress = 0.f; + CURL *curl = curl_easy_init(); if (!curl) return false;