diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a2814..1f78696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,21 +7,35 @@ - Module [STRIP-BLOCK](./docs/Strip.md) - New module, a companion module for STRIP for blocking STRIP's expander-mechanism +## 1.12.0 (in development) + +### New modules + +- Module [MIDI-KEY](./docs/MidiKey.md) + - A helper for generating keyboard events from MIDI CC or note messages (#245) + +## 1.11.0 (in development) + +### New modules + +- Module [PRISMA](./docs/Prisma.md) + - A wave multiplier/phase shifter inspired by A-137-2 + ## 1.10.0 (in development) +### New modules + - Module [INTERMIX-FADE](./docs/Intermix.md#fade-expander) - - New expander-module for INTERMIX, helper for setting individual fade values + - Expander for INTERMIX, helper for setting individual fade values - Module [INTERMIX-ENV](./docs/Intermix.md#env-expander) - - New expander-module for INTERMIX, outputs envelopes for a seleced input-column + - Expander for INTERMIX, outputs envelopes for a seleced input-column - Module [INTERMIX-GATE](./docs/Intermix.md#gate-expander) - - New expander-module for INTERMIX, outputs a gate-signal for each active row (#228) -- Module [MIDI-KEY](./docs/MidiKey.md) - - New module, a helper for generating keyboard events from MIDI CC or note messages (#245) -- Module [PRISMA](./docs/Prisma.md) - - New module, a wave multiplier/phase shifter inspired by A-137-2 + - Expander for INTERMIX, outputs a gate-signal for each active row (#228) ### Fixes and Changes +- Modules [8FACE, 8FACEx2](./docs/EightFace.md) + - Added "Auto"-mode besides "Read" and "Write" ([manual](./docs/EightFace.md#auto-mode)) (#251) - Module [INTERMIX](./docs/Intermix.md) - Added context menu option "Scene lock" to prevent accidental changes - Module [TRANSIT](./docs/Transit.md) @@ -29,21 +43,23 @@ ## 1.9.0 +### New modules + - Modules [8FACE mk2, +8](./docs/EightFaceMk2.md) - - New modules, evolution of 8FACE and 8FACEx2 (#63 #76 #144 #154 #157 #158 #160 #162) + - Evolution of 8FACE and 8FACEx2 (#63 #76 #144 #154 #157 #158 #160 #162) - Module [CV-MAP CTX](./docs/CVMap.md#ctx-expander) - - New expander-module for CV-MAP, helper for mapping parameters by context menu (#256) + - Expander for CV-MAP, helper for mapping parameters by context menu (#256) - Module [MIDI-CAT CTX](./docs/MidiCat.md#ctx-expander) - - New expander-module for MIDI-CAT, helper for mapping parameters by context menu (#232, #250) + - Expander for MIDI-CAT, helper for mapping parameters by context menu (#232, #250) - Module [MIDI-PLUG](./docs/MidiPlug.md) - - New module, a virtual MIDI merger and splitter + - A virtual MIDI merger and splitter - MIDI "Loopback" driver for routing outgoing MIDI messages back into Rack (enabled on the context menu) - Module [ORBIT](./docs/Orbit.md) - - New module, a polyphonic stereo field spreader + - A polyphonic stereo field spreader - Module [STRIP-BAY](./docs/Strip.md#stoermelder-strip-bay) - - New module, a companion module for STRIP for keeping input/output connections while replacing strips + - A companion module for STRIP for keeping input/output connections while replacing strips - Module [ME](./docs/Me.md) - - New experimental module for "mouse enhacements", provides a screen overlay for parameters changes + - Experimental module for "mouse enhacements", provides a screen overlay for parameters changes ### Fixes and Changes diff --git a/docs/EightFace.md b/docs/EightFace.md index 871874d..5ef50c4 100644 --- a/docs/EightFace.md +++ b/docs/EightFace.md @@ -22,6 +22,10 @@ Write-mode is used to save presets in 8FACE. You enter write mode by flipping th Read-mode is enabled by default and can be selected by the switch on the bottom in "R"-position. LEDs lit in bright green signal slots in use, dim green slots are active but empty. A blue LED marks a slot which preset is currently applied to the module on the side. You can manually apply a preset with a short-press. +## Auto-mode + +Auto-mode (added in v1.10.0) stores presets automatically to the current slot right before moving on to the next slot. A typical workflow would look like this: Store a few presets using Write-mode as usual. Afterwards flip the switch to the middle "A"-position and start slow sequencing using the _SLOT_-port. Imagine slot 1 is active and slot 2 will be loaded next. Right before moving to slot 2 the current state of the module is stored into slot 1 preserving all adjustments made in the meantime. In contrast, Read-mode would simply load slot 2 and the preset stored in slot 1 will stay unchanged, discarding all changes made to the module. Note: Empty slots will stay empty, even in Auto-mode. + ## SLOT-port The fun begins when you use the port labelled "SLOT" for selecting preset slots by CV. Although there are eight slots available it is possible to use less slots for sequencing: You can adjust the number of useable slots (i.e. sequence length) by long-pressing a slot-button while in read-mode. The LED turns off completely for slots that are currently disabled. diff --git a/res-src/EightFace.afdesign b/res-src/EightFace.afdesign index 44b5651..6a4604a 100644 Binary files a/res-src/EightFace.afdesign and b/res-src/EightFace.afdesign differ diff --git a/res-src/EightFaceX2.afdesign b/res-src/EightFaceX2.afdesign index 9a66b49..dfbd71e 100644 Binary files a/res-src/EightFaceX2.afdesign and b/res-src/EightFaceX2.afdesign differ diff --git a/res-src/dark/EightFace.afdesign b/res-src/dark/EightFace.afdesign index d1efbe4..edf53bd 100644 Binary files a/res-src/dark/EightFace.afdesign and b/res-src/dark/EightFace.afdesign differ diff --git a/res-src/dark/EightFaceX2.afdesign b/res-src/dark/EightFaceX2.afdesign index 5fc8337..9e0d9a0 100644 Binary files a/res-src/dark/EightFaceX2.afdesign and b/res-src/dark/EightFaceX2.afdesign differ diff --git a/res/EightFace.svg b/res/EightFace.svg index e81e91d..74b5aff 100644 --- a/res/EightFace.svg +++ b/res/EightFace.svg @@ -5,81 +5,36 @@ - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + @@ -88,13 +43,13 @@ - + - + @@ -111,10 +66,61 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/EightFaceX2.svg b/res/EightFaceX2.svg index 6bab98f..31efd64 100644 --- a/res/EightFaceX2.svg +++ b/res/EightFaceX2.svg @@ -20,31 +20,31 @@ - + - + - + - + - + - + @@ -53,13 +53,13 @@ - + - + @@ -76,7 +76,7 @@ - + @@ -89,41 +89,47 @@ - + - + - + - + - + - + - - + + - - + + - + + + + + + + diff --git a/res/dark/EightFace.svg b/res/dark/EightFace.svg index 35ac224..af923ca 100644 --- a/res/dark/EightFace.svg +++ b/res/dark/EightFace.svg @@ -19,22 +19,22 @@ - + - + - + - + @@ -43,13 +43,13 @@ - + - + @@ -66,7 +66,7 @@ - + @@ -78,40 +78,46 @@ - + - + - + - + - + - + - - - + + + - - + + - + + + + + + + diff --git a/res/dark/EightFaceX2.svg b/res/dark/EightFaceX2.svg index 44adf14..4726c9e 100644 --- a/res/dark/EightFaceX2.svg +++ b/res/dark/EightFaceX2.svg @@ -20,31 +20,31 @@ - + - + - + - + - + - + @@ -53,13 +53,13 @@ - + - + @@ -76,7 +76,7 @@ - + @@ -89,41 +89,47 @@ - + - + - + - + - + - + - - + + - - + + - + + + + + + + diff --git a/src/EightFace.cpp b/src/EightFace.cpp index c4cdb75..a1e846c 100644 --- a/src/EightFace.cpp +++ b/src/EightFace.cpp @@ -23,16 +23,21 @@ enum class SLOTCVMODE { ARM = 3 }; -enum MODE { - MODE_LEFT = 0, - MODE_RIGHT = 1 +enum class SIDE { + LEFT = 0, + RIGHT = 1 }; +enum class CTRLMODE { + READ, + AUTO, + WRITE +}; template < int NUM_PRESETS > struct EightFaceModule : Module { enum ParamIds { - MODE_PARAM, + CTRLMODE_PARAM, ENUMS(PRESET_PARAM, NUM_PRESETS), NUM_PARAMS }; @@ -53,9 +58,11 @@ struct EightFaceModule : Module { /** [Stored to JSON] */ int panelTheme = 0; + /** Current operating mode */ + CTRLMODE ctrlMode = CTRLMODE::READ; /** [Stored to JSON] left? right? */ - MODE mode = MODE_LEFT; + SIDE side = SIDE::LEFT; /** [Stored to JSON] */ std::string pluginSlug; @@ -91,6 +98,7 @@ struct EightFaceModule : Module { std::uniform_int_distribution randDist; int connected = 0; + int presetPrev = -1; int presetNext = -1; float modeLight = 0; @@ -115,7 +123,7 @@ struct EightFaceModule : Module { EightFaceModule() { panelTheme = pluginSettings.panelThemeDefault; config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); - configParam(MODE_PARAM, 0, 1, 0, "Switch Read/write mode"); + configParam(CTRLMODE_PARAM, 0, 2, 0, "Read/Auto/Write mode"); for (int i = 0; i < NUM_PRESETS; i++) { configParam(PRESET_PARAM + i, 0, 1, 0, string::f("Preset slot %d", i + 1)); typeButtons[i].param = ¶ms[PRESET_PARAM + i]; @@ -152,6 +160,7 @@ struct EightFaceModule : Module { preset = -1; presetCount = NUM_PRESETS; + presetPrev = -1; presetNext = -1; modelSlug = ""; pluginSlug = ""; @@ -163,15 +172,17 @@ struct EightFaceModule : Module { } void process(const ProcessArgs& args) override { - Expander* exp = mode == MODE_LEFT ? &leftExpander : &rightExpander; + Expander* exp = side == SIDE::LEFT ? &leftExpander : &rightExpander; if (exp->moduleId >= 0 && exp->module) { Module* t = exp->module; bool c = modelSlug == "" || (t->model->name == modelSlug && t->model->plugin->name == pluginSlug); connected = c ? 2 : 1; if (connected == 2) { - // Read mode - if (params[MODE_PARAM].getValue() == 0.f) { + ctrlMode = (CTRLMODE)params[CTRLMODE_PARAM].getValue(); + + // Read & Auto modes + if (ctrlMode == CTRLMODE::READ || ctrlMode == CTRLMODE::AUTO) { // RESET input if (slotCvMode == SLOTCVMODE::TRIG_FWD || slotCvMode == SLOTCVMODE::TRIG_REV || slotCvMode == SLOTCVMODE::TRIG_PINGPONG) { if (inputs[RESET_INPUT].isConnected() && resetTrigger.process(inputs[RESET_INPUT].getVoltage())) { @@ -309,7 +320,7 @@ struct EightFaceModule : Module { modeLight += 0.7f * s; if (modeLight > 1.5f) modeLight = 0.f; - if (mode == MODE_LEFT) { + if (side == SIDE::LEFT) { lights[LEFT_LIGHT + 0].setBrightness(connected == 2 ? std::min(modeLight, 1.f) : 0.f); lights[LEFT_LIGHT + 1].setBrightness(connected == 1 ? 1.f : 0.f); lights[RIGHT_LIGHT + 0].setBrightness(0.f); @@ -323,7 +334,7 @@ struct EightFaceModule : Module { } for (int i = 0; i < NUM_PRESETS; i++) { - if (params[MODE_PARAM].getValue() == 0.f) { + if (ctrlMode == CTRLMODE::READ || ctrlMode == CTRLMODE::AUTO) { lights[PRESET_LIGHT + i * 3 + 0].setBrightness(presetNext == i ? 1.f : 0.f); lights[PRESET_LIGHT + i * 3 + 1].setSmoothBrightness(preset != i && presetCount > i ? (presetSlotUsed[i] ? 1.f : 0.2f) : 0.f, s); lights[PRESET_LIGHT + i * 3 + 2].setSmoothBrightness(preset == i ? 1.f : 0.f, s); @@ -343,6 +354,10 @@ struct EightFaceModule : Module { std::unique_lock lock(workerMutex); workerCondVar.wait(lock, std::bind(&EightFaceModule::workerDoProcess, this)); if (!workerIsRunning || workerPreset < 0) return; + if (ctrlMode == CTRLMODE::AUTO && presetPrev >= 0 && presetSlotUsed[presetPrev]) { + json_decref(presetSlot[presetPrev]); + presetSlot[presetPrev] = workerModuleWidget->toJson(); + } workerModuleWidget->fromJson(presetSlot[workerPreset]); workerDoProcess = false; } @@ -350,6 +365,10 @@ struct EightFaceModule : Module { void processGui() { if (workerGuiModuleWidget) { + if (ctrlMode == CTRLMODE::AUTO && presetPrev >= 0 && presetSlotUsed[presetPrev]) { + json_decref(presetSlot[presetPrev]); + presetSlot[presetPrev] = workerModuleWidget->toJson(); + } workerGuiModuleWidget->fromJson(presetSlot[workerPreset]); workerGuiModuleWidget = NULL; } @@ -361,6 +380,7 @@ struct EightFaceModule : Module { if (!isNext) { if (p != preset || force) { + presetPrev = preset; preset = p; presetNext = -1; if (!presetSlotUsed[p]) return; @@ -416,13 +436,14 @@ struct EightFaceModule : Module { void presetSetCount(int p) { if (preset >= p) preset = 0; presetCount = p; + presetPrev = -1; presetNext = -1; } json_t* dataToJson() override { json_t* rootJ = json_object(); json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); - json_object_set_new(rootJ, "mode", json_integer(mode)); + json_object_set_new(rootJ, "mode", json_integer((int)side)); json_object_set_new(rootJ, "pluginSlug", json_string(pluginSlug.c_str())); json_object_set_new(rootJ, "modelSlug", json_string(modelSlug.c_str())); json_object_set_new(rootJ, "realPluginSlug", json_string(realPluginSlug.c_str())); @@ -448,8 +469,8 @@ struct EightFaceModule : Module { void dataFromJson(json_t* rootJ) override { panelTheme = json_integer_value(json_object_get(rootJ, "panelTheme")); - json_t* modeJ = json_object_get(rootJ, "mode"); - if (modeJ) mode = (MODE)json_integer_value(modeJ); + json_t* sideJ = json_object_get(rootJ, "mode"); + if (sideJ) side = (SIDE)json_integer_value(sideJ); pluginSlug = json_string_value(json_object_get(rootJ, "pluginSlug")); modelSlug = json_string_value(json_object_get(rootJ, "modelSlug")); @@ -482,12 +503,13 @@ struct EightFaceModule : Module { presetSlot[presetIndex] = json_deep_copy(json_object_get(presetJ, "slot")); } + presetPrev = -1; if (preset >= presetCount) preset = 0; switch (autoload) { case AUTOLOAD::FIRST: { - Expander* exp = mode == MODE_LEFT ? &leftExpander : &rightExpander; + Expander* exp = side == SIDE::LEFT ? &leftExpander : &rightExpander; if (exp->moduleId >= 0 && exp->module) { Module* t = exp->module; presetLoad(t, 0, false, true); @@ -495,7 +517,7 @@ struct EightFaceModule : Module { break; } case AUTOLOAD::LASTACTIVE: { - Expander* exp = mode == MODE_LEFT ? &leftExpander : &rightExpander; + Expander* exp = side == SIDE::LEFT ? &leftExpander : &rightExpander; if (exp->moduleId >= 0 && exp->module) { Module* t = exp->module; presetLoad(t, preset, false, true); @@ -577,15 +599,13 @@ struct AutoloadMenuItem : MenuItem { }; template < typename MODULE > -struct ModeItem : MenuItem { +struct SideItem : MenuItem { MODULE* module; - void onAction(const event::Action& e) override { - module->mode = module->mode == MODE_LEFT ? MODE_RIGHT : MODE_LEFT; + module->side = module->side == SIDE::LEFT ? SIDE::RIGHT : SIDE::LEFT; } - void step() override { - rightText = module->mode == MODE_LEFT ? "Left" : "Right"; + rightText = module->side == SIDE::LEFT ? "Left" : "Right"; MenuItem::step(); } }; @@ -619,7 +639,7 @@ struct EightFaceWidgetTemplate : ModuleWidget { menu->addChild(new MenuSeparator()); menu->addChild(construct>(&MenuItem::text, "Port SLOT mode", &SlovCvModeMenuItem::module, module)); - menu->addChild(construct>(&MenuItem::text, "Module", &ModeItem::module, module)); + menu->addChild(construct>(&MenuItem::text, "Module", &SideItem::module, module)); menu->addChild(construct>(&MenuItem::text, "Autoload", &AutoloadMenuItem::module, module)); } @@ -673,7 +693,7 @@ struct EightFaceWidget : ThemedModuleWidget, EightFaceWidgetT addChild(createLightCentered>(Vec(22.5f, 281.9f), module, MODULE::PRESET_LIGHT + 6 * 3)); addChild(createLightCentered>(Vec(22.5f, 305.4f), module, MODULE::PRESET_LIGHT + 7 * 3)); - addParam(createParamCentered(Vec(22.5f, 336.2f), module, MODULE::MODE_PARAM)); + addParam(createParamCentered(Vec(22.5f, 336.2f), module, MODULE::CTRLMODE_PARAM)); } void step() override { @@ -734,7 +754,7 @@ struct EightFaceX2Widget : ThemedModuleWidget, EightFaceWidg addChild(createLightCentered>(Vec(42.3f, 281.9f), module, MODULE::PRESET_LIGHT + 14 * 3)); addChild(createLightCentered>(Vec(42.3f, 305.4f), module, MODULE::PRESET_LIGHT + 15 * 3)); - addParam(createParamCentered(Vec(30.0f, 336.2f), module, MODULE::MODE_PARAM)); + addParam(createParamCentered(Vec(30.0f, 336.2f), module, MODULE::CTRLMODE_PARAM)); } }; diff --git a/src/components.hpp b/src/components.hpp index 36dbb85..bf3735e 100644 --- a/src/components.hpp +++ b/src/components.hpp @@ -396,4 +396,25 @@ struct CKSSH : CKSS { tw->box.size = sw->box.size.flip(); box.size = tw->box.size; } +}; + +struct CKSSThreeH : CKSSThree { + CKSSThreeH() { + shadow->opacity = 0.0f; + fb->removeChild(sw); + + TransformWidget* tw = new TransformWidget(); + tw->addChild(sw); + fb->addChild(tw); + + Vec center = sw->box.getCenter(); + tw->translate(center); + tw->rotate(M_PI / 2.0f); + // Why does this not work as expected?! + tw->translate(Vec(center.y, sw->box.size.x + center.x + 1.3f).neg()); + + tw->box.size = sw->box.size.flip(); + fb->box.size = tw->box.size; + box.size = tw->box.size; + } }; \ No newline at end of file