Creating VCV Rack patch files from software

Lintbuddy in baconmusic already has things to make sure inputs and outputs have labels etc… so probably does some of what you want

It uses connection to determine what to prove but in combination with the create module idioms which you could write a module easily to instantiate scan and delete each module in your session and dump the result somewhere

(Edit never reply half way down a thread and thanks for saying the same @DaveVenom )

By the way, assuming @cschol and/or I can figure out why the code works with the gcc 13 tool chain and not the gcc 14 tool chain, if someone would be interested in doing that manual release to library step with a more regular cadence like monthly, and you know how to use git, dm me. (once we get the library working again of course)

Basically you look up a git hash, post to a GitHub issue, update a version, and push

This is a fun conversation. I’m glad to see it go in a lot of different directions too.

Here is an update on my project. I created a GitHub repo for this developer tool plugin called Toolbox.

You can open up any patch that has modules you are interested in. Then you insert the “Module Scanner” module from the Toolbox plugin. Then click the button:

You won’t see anything happen when you click the button (to be fixed later). But it will create a file called ModuleScanner.json in your VCV Rack user folder.

Here is a sample from one of my patches:

ModuleScanner.json (91.8 KB)

This is still a work in progress, because I’m accumulating plugins, modules, and parameters inside modules, but I still need input ports and output ports.

Here is a snippet of the information captured about Sapphire Galaxy:

    "CosineKitty-Sapphire": {
        "Galaxy": {
            "params": [
                {
                    "paramId": 0,
                    "name": "Replace",
                    "description": "",
                    "unit": "",
                    "minValue": 0.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.5,
                    "displayBase": 0.0,
                    "displayMultiplier": 1.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 1,
                    "name": "Replace attenuverter",
                    "description": "",
                    "unit": "%",
                    "minValue": -1.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.0,
                    "displayBase": 0.0,
                    "displayMultiplier": 100.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 2,
                    "name": "Brightness",
                    "description": "",
                    "unit": "",
                    "minValue": 0.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.5,
                    "displayBase": 0.0,
                    "displayMultiplier": 1.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 3,
                    "name": "Brightness attenuverter",
                    "description": "",
                    "unit": "%",
                    "minValue": -1.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.0,
                    "displayBase": 0.0,
                    "displayMultiplier": 100.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 4,
                    "name": "Detune",
                    "description": "",
                    "unit": "",
                    "minValue": 0.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.5,
                    "displayBase": 0.0,
                    "displayMultiplier": 1.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 5,
                    "name": "Detune attenuverter",
                    "description": "",
                    "unit": "%",
                    "minValue": -1.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.0,
                    "displayBase": 0.0,
                    "displayMultiplier": 100.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 6,
                    "name": "Size",
                    "description": "",
                    "unit": "",
                    "minValue": 0.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.5,
                    "displayBase": 0.0,
                    "displayMultiplier": 1.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 7,
                    "name": "Size attenuverter",
                    "description": "",
                    "unit": "%",
                    "minValue": -1.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.0,
                    "displayBase": 0.0,
                    "displayMultiplier": 100.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 8,
                    "name": "Mix",
                    "description": "",
                    "unit": "",
                    "minValue": 0.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.5,
                    "displayBase": 0.0,
                    "displayMultiplier": 1.0,
                    "displayOffset": 0.0
                },
                {
                    "paramId": 9,
                    "name": "Mix attenuverter",
                    "description": "",
                    "unit": "%",
                    "minValue": -1.0,
                    "maxValue": 1.0,
                    "defaultValue": 0.0,
                    "displayBase": 0.0,
                    "displayMultiplier": 100.0,
                    "displayOffset": 0.0
                }
            ]
        },
        // ...
    }
4 Likes

Awesome, thanks @cosinekitty. Super useful. I’ve been using LintBuddy by @baconpaul and CVMap as workarounds in the meantime. Would it be possible to share the compiled plugin files?

Other info you might want to consider capturing:

  • Plugin version
  • JSON setting names (and values, but that would be harder to gather)

I’m not sure how to handle the issue, but remember that parameter names, min, max, and default values are not always constant for every module of a given plugin version.

Yes, I will add that to my list. I just need to set up the same kind of GitHub Actions script most of us use for our official, published VCV Rack plugins. I’m glad someone else is interested enough to ask!

But first I want to finish getting it working the way I want so that it captures data in the complete and correct format I will need.

Yes, I already ran into this with Sapphire Echo. I dynamically change the names of my solo buttons between “Solo: ON” and “Solo: OFF” when its state toggles. I know you allow users to rename all kinds of things in Venom, which can be very helpful for users to document their thought process behind the patch architecture.

My gut tells me that the parameter names, even if dynamic, are enough of a clue when in their default state for me as a human to know what they mean. They are certainly better than paramId 17 or whatever. I may end up with a manual comment field in the database that I can edit for my own mnemonic purposes to have a locked-down symbol I can use for reference.

About parameter ranges changing… I have no idea how to handle that yet. My patch generator (someday, when it exists) will need to understand parameter values because that is a big part of any patch: where are the knobs and switches?

1 Like

So one thing I was thinking here was your scanner as well as “do the current patch” could have a mode where it takes all plugins you have avail creates one scans it and removes it.

Adding and removing a module from code is doable

This would be annoying to actually watch but would let you make a database more completely

Fun project

2 Likes

After some of the things I have seen around here, I think some people would actually enjoy watching it!

2 Likes

Ha! Yeah sure but then do them 8 at a time game if life style across the rack

2 Likes

I’m glad you mentioned this. I was just looking at it and realize I need to change my json schema a little bit. I will add this so that we can include the plugin versions too (and other stuff).

no, but some people think they should be!

I have greatly expanded the information collected by Module Scanner. Now it includes a lot more information about the plugins and modules in the file ModuleDatabase.json.

I also have an automated build process on GitHub, so now you can download plugin binaries:

6 Likes

Woo! Excited to check it out!

@cosinekitty Super useful. Going to use this to essentially annotate some of my favorite patches.

Got an idea, what if there was another button on the module, which when you click on it, turned into a crosshair like CVMap? Then when you click on knob on another module, it would show the param id and other info for that knob in a mini-display that could live within currently empty space of toolbox. (Sort of like a combo of LintBuddy and CVMap)

3 Likes

I love the idea, and it would be great for someone to take that idea and run with it. My code is GPLv3+, so anyone can feel free to fork the code and take it in a different direction. I’m going to stay focused on my project road map, for which Module Scanner is a bootstrapping step.

My next focus will be on a formal system for describing functional units of signal processing. It will allow you to define a concept like “Voice = something that accepts envelope and V/OCT inputs and produces a tonal audio output based on it.”

Then you can start defining examples of Voice by defining specific modules and the paramId and portId values involved; or even combinations of modules with cables between them that act like a Voice. This is the vision I have and I am obsessed with it.

You can think of it like substances can be made of an atomic element (a module) or a molecule made of many atoms stuck together with bonds (multiple modules with cables). But a substance is a substance, whether it has one atom (like helium) or multiple stuck together (water = H2O). And a “Voice” does the same kind of thing regardless of which combination of modules satisfy the definition you crafted.

You then can use Voice, and any other concepts you want to define, in a higher-level “recipe” for making patches. It will be a patch-making engine that spits out any number of patches that follow the pattern you define in the to-be-defined formal system.

Even more distant on my horizon is, once the patch generator is mature and stable, back up and think about how the same formal system (new custom language, Python package, or whatever) could end up directly generating C++ code for novel modules, based on following the same “recipes”.

5 Likes

The Helper module in the ModScript plugin does something similar, but it only displays a few bits of info in the UI. However, when you click the Dump button it copies a bunch of info in JSON format into your clipboard.