Add detection code for legacy1 mode for patch files. Initialize patch on launch if the last launch crashed after 15 seconds.tags/v0.6.0
@@ -1,7 +1,13 @@ | |||
VERSION = 0.6.0dev | |||
FLAGS += \ | |||
-Iinclude \ | |||
-Idep/include -Idep/lib/libzip/include | |||
ifdef RELEASE | |||
FLAGS += -DRELEASE=$(RELEASE) | |||
endif | |||
SOURCES = $(wildcard src/*.cpp src/*/*.cpp) \ | |||
ext/nanovg/src/nanovg.c | |||
@@ -87,9 +93,6 @@ include compile.mk | |||
dist: all | |||
ifndef VERSION | |||
$(error VERSION must be defined when making distributables) | |||
endif | |||
rm -rf dist | |||
$(MAKE) -C plugins/Fundamental dist | |||
@@ -70,13 +70,24 @@ struct Module { | |||
/** Called when user explicitly deletes the module, not when Rack is closed or a new patch is loaded */ | |||
virtual void onDelete() {} | |||
/** Called when user clicks Initialize in the module context menu */ | |||
virtual void onReset() {} | |||
virtual void onReset() { | |||
// Call deprecated method | |||
reset(); | |||
} | |||
/** Called when user clicks Randomize in the module context menu */ | |||
virtual void onRandomize() {} | |||
virtual void onRandomize() { | |||
// Call deprecated method | |||
randomize(); | |||
} | |||
/** Override these to store extra internal data in the "data" property */ | |||
virtual json_t *toJson() { return NULL; } | |||
virtual void fromJson(json_t *root) {} | |||
/** Deprecated */ | |||
virtual void reset() {} | |||
/** Deprecated */ | |||
virtual void randomize() {} | |||
}; | |||
struct Wire { | |||
@@ -10,4 +10,7 @@ void settingsSave(std::string filename); | |||
void settingsLoad(std::string filename); | |||
extern bool skipAutosaveOnLaunch; | |||
} // namespace rack |
@@ -83,11 +83,12 @@ float randomNormal(); | |||
/** Converts a printf format string and optional arguments into a std::string */ | |||
std::string stringf(const char *format, ...); | |||
std::string tolower(std::string s); | |||
std::string toupper(std::string s); | |||
std::string lowercase(std::string s); | |||
std::string uppercase(std::string s); | |||
/** Truncates and adds "..." to a string, not exceeding `len` characters */ | |||
std::string ellipsize(std::string s, size_t len); | |||
bool startsWith(std::string str, std::string prefix); | |||
std::string extractDirectory(std::string path); | |||
std::string extractFilename(std::string path); | |||
@@ -4,12 +4,7 @@ | |||
namespace rack { | |||
std::string gApplicationName = "VCV Rack"; | |||
std::string gApplicationVersion = | |||
#ifdef VERSION | |||
TOSTRING(VERSION); | |||
#else | |||
""; | |||
#endif | |||
std::string gApplicationVersion = TOSTRING(VERSION); | |||
std::string gApiHost = "https://api.vcvrack.com"; | |||
// std::string gApiHost = "http://localhost:8081"; | |||
@@ -98,8 +98,8 @@ static bool isModelMatch(Model *model, std::string search) { | |||
str += " "; | |||
str += gTagNames[tag]; | |||
} | |||
str = tolower(str); | |||
search = tolower(search); | |||
str = lowercase(str); | |||
search = lowercase(search); | |||
return (str.find(search) != std::string::npos); | |||
} | |||
@@ -45,10 +45,10 @@ RackScene::RackScene() { | |||
scrollWidget->box.pos.y = gToolbar->box.size.y; | |||
// Check for new version | |||
if (!gApplicationVersion.empty()) { | |||
std::thread versionThread(checkVersion); | |||
versionThread.detach(); | |||
} | |||
#if defined(RELEASE) | |||
std::thread versionThread(checkVersion); | |||
versionThread.detach(); | |||
#endif | |||
} | |||
void RackScene::step() { | |||
@@ -68,7 +68,7 @@ void RackScene::step() { | |||
// Version popup message | |||
if (!newVersion.empty()) { | |||
std::string versionMessage = stringf("Rack %s is available.\n\nYou have Rack %s.\n\nWould you like to download the new version on the website?", newVersion.c_str(), gApplicationVersion.c_str()); | |||
std::string versionMessage = stringf("Rack v%s is available.\n\nYou have Rack v%s.\n\nWould you like to download the new version on the website?", newVersion.c_str(), gApplicationVersion.c_str()); | |||
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, versionMessage.c_str())) { | |||
std::thread t(openBrowser, "https://vcvrack.com/"); | |||
t.detach(); | |||
@@ -145,10 +145,8 @@ json_t *RackWidget::toJson() { | |||
json_t *rootJ = json_object(); | |||
// version | |||
if (!gApplicationVersion.empty()) { | |||
json_t *versionJ = json_string(gApplicationVersion.c_str()); | |||
json_object_set_new(rootJ, "version", versionJ); | |||
} | |||
json_t *versionJ = json_string(gApplicationVersion.c_str()); | |||
json_object_set_new(rootJ, "version", versionJ); | |||
// modules | |||
json_t *modulesJ = json_array(); | |||
@@ -210,11 +208,19 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
std::string message; | |||
// version | |||
std::string version; | |||
json_t *versionJ = json_object_get(rootJ, "version"); | |||
if (versionJ) { | |||
std::string version = json_string_value(versionJ); | |||
version = json_string_value(versionJ); | |||
if (!version.empty() && gApplicationVersion != version) | |||
message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version.c_str(), gApplicationVersion.empty() ? "dev" : gApplicationVersion.c_str()); | |||
message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version.c_str(), gApplicationVersion.c_str()); | |||
} | |||
// Detect old patches with ModuleWidget::params/inputs/outputs indices. | |||
// (We now use Module::params/inputs/outputs indices.) | |||
bool legacy1 = (startsWith(version, "0.3.") || startsWith(version, "0.4.") || startsWith(version, "0.5.") || version == "" || version == "dev"); | |||
if (legacy1) { | |||
info("Converting patch using legacy1 loader"); | |||
} | |||
// modules | |||
@@ -178,7 +178,7 @@ Toolbar::Toolbar() { | |||
xPos += margin; | |||
*/ | |||
#ifdef VERSION | |||
#if defined(RELEASE) | |||
{ | |||
Widget *pluginManager = new PluginManagerWidget(); | |||
pluginManager->box.pos = Vec(xPos, margin); | |||
@@ -26,7 +26,7 @@ namespace rack { | |||
std::string assetGlobal(std::string filename) { | |||
std::string dir; | |||
#ifdef VERSION | |||
#if defined(RELEASE) | |||
#if ARCH_MAC | |||
CFBundleRef bundle = CFBundleGetMainBundle(); | |||
assert(bundle); | |||
@@ -45,16 +45,16 @@ std::string assetGlobal(std::string filename) { | |||
// TODO For now, users should launch Rack from their terminal in the global directory | |||
dir = "."; | |||
#endif | |||
#else // VERSION | |||
#else // RELEASE | |||
dir = "."; | |||
#endif // VERSION | |||
#endif // RELEASE | |||
return dir + "/" + filename; | |||
} | |||
std::string assetLocal(std::string filename) { | |||
std::string dir; | |||
#ifdef VERSION | |||
#if defined(RELEASE) | |||
#if ARCH_MAC | |||
// Get home directory | |||
struct passwd *pw = getpwuid(getuid()); | |||
@@ -83,9 +83,9 @@ std::string assetLocal(std::string filename) { | |||
dir += "/.Rack"; | |||
mkdir(dir.c_str(), 0755); | |||
#endif | |||
#else // VERSION | |||
#else // RELEASE | |||
dir = "."; | |||
#endif // VERSION | |||
#endif // RELEASE | |||
return dir + "/" + filename; | |||
} | |||
@@ -2,7 +2,7 @@ | |||
#include <list> | |||
#include <algorithm> | |||
#include "core.hpp" | |||
#include "MidiIO.hpp" | |||
#include "midi.hpp" | |||
#include "dsp/digital.hpp" | |||
@@ -16,7 +16,7 @@ struct MidiValue { | |||
bool changed = false; // Value has been changed by midi message (only if it is in sync!) | |||
}; | |||
struct MIDIToCVInterface : MidiIO, Module { | |||
struct MIDIToCVInterface : Module { | |||
enum ParamIds { | |||
RESET_PARAM, | |||
NUM_PARAMS | |||
@@ -49,7 +49,7 @@ struct MIDIToCVInterface : MidiIO, Module { | |||
SchmittTrigger resetTrigger; | |||
MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
pitchWheel.val = 64; | |||
pitchWheel.tSmooth.set(0, 0); | |||
} | |||
@@ -253,8 +253,7 @@ MidiToCVWidget::MidiToCVWidget() { | |||
} | |||
addParam(createParam<LEDButton>(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); | |||
addChild(createLight<SmallLight<RedLight>>(Vec(7 * 15 + 5, labelHeight + 5), module, | |||
MIDIToCVInterface::RESET_LIGHT)); | |||
addChild(createLight<SmallLight<RedLight>>(Vec(7 * 15 + 5, labelHeight + 5), module, MIDIToCVInterface::RESET_LIGHT)); | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
@@ -285,8 +284,7 @@ MidiToCVWidget::MidiToCVWidget() { | |||
yPos += channelChoice->box.size.y + margin + 15; | |||
} | |||
std::string labels[MIDIToCVInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", "Pitch Wheel", | |||
"Aftertouch"}; | |||
std::string labels[MIDIToCVInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", "Pitch Wheel", "Aftertouch"}; | |||
for (int i = 0; i < MIDIToCVInterface::NUM_OUTPUTS; i++) { | |||
Label *label = new Label(); | |||
@@ -299,9 +297,4 @@ MidiToCVWidget::MidiToCVWidget() { | |||
yPos += yGap + margin; | |||
} | |||
} | |||
void MidiToCVWidget::step() { | |||
ModuleWidget::step(); | |||
} | |||
#endif |
@@ -8,8 +8,8 @@ void init(rack::Plugin *p) { | |||
#endif | |||
p->addModel(createModel<AudioInterfaceWidget>("Core", "AudioInterface", "Audio Interface", EXTERNAL_TAG)); | |||
// p->addModel(createModel<MidiToCVWidget>("Core", "MIDIToCVInterface", "MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
// p->addModel(createModel<MidiToCVWidget>("Core", "MIDIToCVInterface", "MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
// p->addModel(createModel<MIDICCToCVWidget>("Core", "MIDICCToCVInterface", "MIDI CC-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
// p->addModel(createModel<MIDIClockToCVWidget>("Core", "MIDIClockToCVInterface", "MIDI Clock-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, CLOCK_TAG)); | |||
// p->addModel(createModel<MIDITriggerToCVWidget>("Core", "MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
@@ -14,29 +14,27 @@ struct AudioInterfaceWidget : ModuleWidget { | |||
struct MidiToCVWidget : ModuleWidget { | |||
MidiToCVWidget(); | |||
void step() override; | |||
}; | |||
struct MIDICCToCVWidget : ModuleWidget { | |||
MIDICCToCVWidget(); | |||
void step() override; | |||
}; | |||
struct MIDIClockToCVWidget : ModuleWidget { | |||
MIDIClockToCVWidget(); | |||
void step() override; | |||
}; | |||
// struct MIDICCToCVWidget : ModuleWidget { | |||
// MIDICCToCVWidget(); | |||
// void step() override; | |||
// }; | |||
struct MIDITriggerToCVWidget : ModuleWidget { | |||
MIDITriggerToCVWidget(); | |||
void step() override; | |||
}; | |||
// struct MIDIClockToCVWidget : ModuleWidget { | |||
// MIDIClockToCVWidget(); | |||
// void step() override; | |||
// }; | |||
struct QuadMidiToCVWidget : ModuleWidget { | |||
QuadMidiToCVWidget(); | |||
void step() override; | |||
}; | |||
// struct MIDITriggerToCVWidget : ModuleWidget { | |||
// MIDITriggerToCVWidget(); | |||
// void step() override; | |||
// }; | |||
// struct QuadMidiToCVWidget : ModuleWidget { | |||
// QuadMidiToCVWidget(); | |||
// void step() override; | |||
// }; | |||
struct BridgeWidget : ModuleWidget { | |||
BridgeWidget(); | |||
@@ -432,10 +432,7 @@ void guiRun() { | |||
mouseButtonStickyPop(); | |||
// Set window title | |||
std::string windowTitle = gApplicationName; | |||
if (!gApplicationVersion.empty()) { | |||
windowTitle += " v" + gApplicationVersion; | |||
} | |||
std::string windowTitle = gApplicationName + " v" + gApplicationVersion; | |||
if (!gRackWidget->lastPath.empty()) { | |||
windowTitle += " - "; | |||
windowTitle += extractFilename(gRackWidget->lastPath); | |||
@@ -12,14 +12,12 @@ using namespace rack; | |||
int main(int argc, char* argv[]) { | |||
randomSeedTime(); | |||
#ifdef VERSION | |||
#ifdef RELEASE | |||
std::string logFilename = assetLocal("log.txt"); | |||
gLogFile = fopen(logFilename.c_str(), "w"); | |||
#endif | |||
if (!gApplicationVersion.empty()) { | |||
info("Rack v%s", gApplicationVersion.c_str()); | |||
} | |||
info("Rack v%s", gApplicationVersion.c_str()); | |||
{ | |||
char *cwd = getcwd(NULL, 0); | |||
@@ -35,21 +33,28 @@ int main(int argc, char* argv[]) { | |||
engineInit(); | |||
guiInit(); | |||
sceneInit(); | |||
gRackWidget->loadPatch(assetLocal("autosave.vcv")); | |||
settingsLoad(assetLocal("settings.json")); | |||
// 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; | |||
settingsSave(assetLocal("settings.json")); | |||
skipAutosaveOnLaunch = false; | |||
if (!oldSkipAutosaveOnLaunch) | |||
gRackWidget->loadPatch(assetLocal("autosave.vcv")); | |||
engineStart(); | |||
guiRun(); | |||
engineStop(); | |||
settingsSave(assetLocal("settings.json")); | |||
gRackWidget->savePatch(assetLocal("autosave.vcv")); | |||
settingsSave(assetLocal("settings.json")); | |||
sceneDestroy(); | |||
guiDestroy(); | |||
engineDestroy(); | |||
pluginDestroy(); | |||
#ifdef VERSION | |||
#ifdef RELEASE | |||
fclose(gLogFile); | |||
#endif | |||
@@ -9,6 +9,9 @@ | |||
namespace rack { | |||
bool skipAutosaveOnLaunch = false; | |||
static json_t *settingsToJson() { | |||
// root | |||
json_t *rootJ = json_object(); | |||
@@ -56,6 +59,11 @@ static json_t *settingsToJson() { | |||
json_t *lastPathJ = json_string(gRackWidget->lastPath.c_str()); | |||
json_object_set_new(rootJ, "lastPath", lastPathJ); | |||
// skipAutosaveOnLaunch | |||
if (skipAutosaveOnLaunch) { | |||
json_object_set_new(rootJ, "skipAutosaveOnLaunch", json_true()); | |||
} | |||
return rootJ; | |||
} | |||
@@ -114,22 +122,25 @@ static void settingsFromJson(json_t *rootJ) { | |||
json_t *lastPathJ = json_object_get(rootJ, "lastPath"); | |||
if (lastPathJ) | |||
gRackWidget->lastPath = json_string_value(lastPathJ); | |||
json_t *skipAutosaveOnLaunchJ = json_object_get(rootJ, "skipAutosaveOnLaunch"); | |||
if (skipAutosaveOnLaunchJ) | |||
skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); | |||
} | |||
void settingsSave(std::string filename) { | |||
info("Saving settings %s", filename.c_str()); | |||
FILE *file = fopen(filename.c_str(), "w"); | |||
if (!file) | |||
return; | |||
json_t *rootJ = settingsToJson(); | |||
if (rootJ) { | |||
FILE *file = fopen(filename.c_str(), "w"); | |||
if (!file) | |||
return; | |||
json_dumpf(rootJ, file, JSON_INDENT(2)); | |||
json_decref(rootJ); | |||
fclose(file); | |||
} | |||
fclose(file); | |||
} | |||
void settingsLoad(std::string filename) { | |||
@@ -99,12 +99,12 @@ std::string stringf(const char *format, ...) { | |||
return s; | |||
} | |||
std::string tolower(std::string s) { | |||
std::string lowercase(std::string s) { | |||
std::transform(s.begin(), s.end(), s.begin(), ::tolower); | |||
return s; | |||
} | |||
std::string toupper(std::string s) { | |||
std::string uppercase(std::string s) { | |||
std::transform(s.begin(), s.end(), s.begin(), ::toupper); | |||
return s; | |||
} | |||
@@ -116,6 +116,10 @@ std::string ellipsize(std::string s, size_t len) { | |||
return s.substr(0, len - 3) + "..."; | |||
} | |||
bool startsWith(std::string str, std::string prefix) { | |||
return str.substr(0, prefix.size()) == prefix; | |||
} | |||
std::string extractDirectory(std::string path) { | |||
char *pathDup = strdup(path.c_str()); | |||
std::string directory = dirname(pathDup); | |||