Pattern for using DynamicTextDisplay with VCV based MM plugins

Goal: I’d like to add text to Noise Plethora to show the LCD display

Background: I can follow how the DynamicTextDisplay is used in the Braids example, there it is straightforward because the AudibleInstruments plugins use the core modules style description of the UI. That is, they own(?) and construct the list of Elements so adding the display is “easy” (see link here).

I’d like to generalise this to VCV native plugins, where the UI is instead built by “hooking” into the addParam, addChild calls and extracting relevant pieces to create Elements. Whatever the solution, this will probably need to be custom code that is not compiled for the main VCV plugin (for Rack).

What I’ve managed so far:

  • added 2 rack::ui::TextField widgets (just a container for the module_widget_adaptor to work with) to Noise PLethora
  • added a void ModuleWidget::addChild(ui::TextField *widget) overload that calls the adaptor (note this need a corresponding check to metamodule-rack-interface in ModuleWidget.hpp that I’ve not pushed because it’s a submodule)
  • added a Element make_element(rack::ui::TextField *widget) overload that creates a dynamic text display

This PR shows where it’s at

Status / Questions

This sort of works (see video on PR) with a few issues

  • Questions:
    • I can’t work out how to distinguish different instances, i.e. what to put for the ElementCount::Indices part (Experiments to wire in NoisePlethora text displays by hemmer · Pull Request #381 · 4ms/metamodule · GitHub). Do I need to assign light ids for textdisplays also?
    • It’s a bit of a cludge to abuse the TextField widget - I did this because it’s just another Rack widget type, but doesn’t have concept of font
    • Is there a better more general way to approach this? Could we add an addChild(Element) specialisation and VCV widget adds the Element directly?
    • Can I make the font smaller (reducing box.size just crops it)?
    • Seperate issue but can we add visible to BaseElement, and let the module adaptor copy status of hidden elements? I tried to make hidden parameters to add the long button press actions.

Great!

Yes. Displays elements are “lights” on the MM, so they need an ID. It doesn’t have to be a LightId that VCV knows about, but somehow it’ll need to be passed to the Element when its created.
Also, the display light IDs share the same space as the normal light IDs. So in the NP I see there are three lights (in the fork): 0,1, and 2, so the displays would need to be IDs 3 and 4.

Yeah, I think we should have our own type.
It’d be neat if the existing calls to addChild(displayA); and addChild(displayB); would result in the right thing happening. I think if NoisePlethoraLEDDisplay derived from some new class MetaModuleDisplay just in the MM build, that could keep the amount of differences between VCV and MM minimal. Maybe? We’d have to try it in practice.
The new class would have a field for the light ID, which you’d have to set, but ModuleWidget::addChild could read that, like you did for TextField.

Yes. The fonts are in LVGL format, so we have to use their tool to generate a new one at each pixel height. I can just go and do that for every possible height. We also could eventually load fonts from files in the plugin or built-in assets dir, like we do for PNG files.

It’s possible but I’m not understanding what not-visible implies? Is it just not drawn but it still shows up in the list of Elements on the ModuleView Page?
We have AltParams, maybe it ties into that?

1 Like

Ah great, that’s working now.

This was actually straightforward to do using their online tool, I just created a font using the actual font from Noise PLethora.

It took a few attempts, but in the latest version of the PR this should be how it’s set up, hopefully it’s relatively generic.

Ah ok I wasn’t aware of AltParams, which covers this use case mainly. As I’ve learnt more about MM API, I can see that visible wouldn’t work as a dynamic thing because the GUI rendering is totally seperate (i.e. the ModuleWidget contructor of a VCV module is just briefly hooked into to get widget positions etc, but isn’t dynamically updated e.g. via the Rack step() call).

However in the PR I’ve proposed a mechansim in the PR where hidden ParamWidgets can be intercepted and marked as the appropriate AltParam. For now it’s just “hidden momentary button” → AltParamMomentary, but could be “hidden knob” → AltParamContinuous and so on? The AltParams are great, but it’s not otherwise immediately obvious how VCV-centric MM plugins might use them.

This PR(s):

  • adds the exact font used by NoisePlethora
  • adds a AltParamMomentary Element type
  • adds a MetaModuleDisplay widget that VCV rack plugins can use to link to MM text displays

main PR: https://github.com/4ms/metamodule/pull/381/files
MetaModuleDisplay PR: https://github.com/4ms/metamodule-rack-interface/pull/2/files

Let me know if this is in the right direction, and if so I can clean things up for a proper PR. Noise Plethora is now working (i.e. has functioning segment display, and hidden param to switch between program/bank mode, and hidden param to switch between top and bottom).

OK wow, great!

The MetaModuleDisplay widget type and the ModuleWidget handler for it look good. It’s definitely the right direction. I would suggest putting MetaModuleDisplay in the MetaModule namespace instead of the rack::widget namespace. Maybe it needs to change its name in that case, too (MetaModule::VCVDisplay? or MetaModule::VCVTextDisplay?) Also probably should put the file in include/metamodule instead of mixing in with rack file (thinking ahead for future Rack API updates…).

I think there’s a commit missing/not pushed for metamodule-core-interface, or at least git submodule update can’t find a ref.

Regarding the AltParamMomentary: we need a clear interface for VCV-ports to create AltParams easily, I appreciate you making headway on this.
It looks like how you have it, a module would set visible = false and that would make it an AltParam (at least for a momentary SvgSwitch). Could this backfire if there was a VCV module that had a switch that was sometimes hidden and sometimes visible when used on VCV? When porting it on MM the switch would be an AltParam if the switch is initially hidden, or a regular switch if initially visible, which seems like a point of confusion. I might not be understanding how visible flag is used, though. It would simplify things to not need to expose a new class, if we can just use some unused flag inside existing rack classes to signal if it’s Alt or Normal param. But only if that flag doesn’t have other uses…

Another way to do make an API for AltParams is more like how you did MetaModuleDisplay, just a new class like MetaModule::VCVAltParam or something. The idea of AltParams is similar to right-click menu options (specifically, menu options that don’t have an equivalent on the faceplate). I would love to have some way of tying in the rack Menu classes to AltParams, but I haven’t figured out a way to do it yet.

Back to the Noise Plethora: I understand now what you mean about adding the hidden flag to BaseElement! It makes sense, or probably adding it to ImageElement actually. So, if we added that, then we’d just have the redraw() functions return early if that flag is set. Default value is false, and that’s probably an easy way to handle this.

Another idea (I’m on a roll…), you could make the bank and section params be normal momentary button parameters, but just have their PNG be fully transparent. The XY position could be over the bank/section lights, so it looks right when highlighted in the ModuleView. Then they would be “invisible” but would be shown next to the encoder parameter in the element list.

In any case… this is exciting!

1 Like

Thanks for the detailed feedback, and happy to be a guinea pig for iterating towards useful VCV plugin centric API elements! It will take me a bit longer than it would you, but actually it’s be very useful for me learning the codebase, setting up debugging etc etc. And worth getting these things right. :smiley:

Yes that all sounds sensible, it’s awkward where it is.

I think the only time visible state currently persists is in the Widget constructor when the adaptor is doing a pass, so shouldn’t really change in theory. Example where it does are e.g. when muxlicer changes from 8to1 to 1to8 (so inputs/outputs are hidden/shown), but this is part of the rack ui draw which doesn’t get called at present. However, to be honest the mechanism in the PR isn’t very “discoverable” so some explicit interface for VCV-ports is better I think. I’ll give it some thought.

I can see that working, however again the only meaningful place visible state can change is in the UI thread (i.e. draw, which is currently no-op).

This is a great idea, and works well :slight_smile: It actually invalidates the need for AltParamMomentary in this instance, though I can still see that being generically useful.

I’ve now cleaned up the pull request a bit to remove the AltParam stuff (we can continue that in a new issue I think), and to just include the minimal set of changes. I’ve also merged up recently.

Let me know what you think, and feel free to make changes yourself on those branches if it makes life easier!

I thought another neat option here might be to just look for ParamQuantities that don’t have matching ParamWidgets. So there is a param, but it’s not been added to the UI. An example unfinished parser to find these, e.g. in ModuleWidgetAdaptor could be

	void populate_alt_params_indices(std::vector<MetaModule::Element> &elements,
									 std::vector<ElementCount::Indices> &indices, 
									 rack::engine::Module *module_) {
		for (int paramId = 0; paramId < module_->getNumParams(); ++paramId) {
			bool found = false;
			for (auto index : indices) {
				if (index.param_idx == paramId) {
					found = true;
					break;
				}
			}

			if (!found) {
				auto paramQuantity = module_->getParamQuantity(paramId);
				pr_warn("param %d not found, treating as AltParam\n", paramId);
				// TODO: process and populate
			}

		}
	}

However some modules have this (e.g. hetrickcv/src/PhasorSwing.cpp at f66ee21981611bab914031619e1293de6f037db9 · mhetrick/hetrickcv · GitHub) and it’s not clear if they’ve just been depreceated or forgotton - again not a very discoverable method. Plus, you can’t tell things like is it a momentary or not - that’s a UI concept. So the thinking must continue…

This is really great. It runs nicely, and I think you have it pretty well sorted out.

I’m adding a few error checks, minor stuff… I’ll push and follow up on the PR issue page.

Exciting that we’ll have an example project for other modules to use!

1 Like

Turns out it’s simple to allow modules to include their own fonts and access them by filesystem path name. So I’m making some examples of that with OrangeLine and CountModula. So far, it’s been a breeze porting the screens on these modules (finding a good set of fonts that look good on a small screen has been the only speedbump)

2 Likes