This is not a request for help, just me dropping some notes about something I’m experimenting with. Maybe some of my fellow plugin developers will find this interesting.
I’m intrigued by the idea of writing programs that output VCV Rack patches. I have this vague idea that I could define “recipes” for patches based on my own experience as a modular synth enthusiast, then the program could generate variations on the recipe.
In cooking, making gravy is basically the same thing as making pudding: they both involve cooking flour in a little fat and gradually stirring in a water-soluble base. The result can be very different depending on whether you add savory stuff (gravy) or sweet stuff (pudding).
In the same vein, we modular synth people all have recipes, basic stuff like “VCO goes through VCA, with VCA level controlled by ADSR envelope, …”. We have conceptual patterns in our minds, from which we can derive an astronomical number of actual patches by substituting specific modules for general ideas.
So my first idea is to experiment with reading/writing the VCV file format. Here is a working Python program that opens a vcv file, extracts the patch JSON out of it, then turns around and re-creates another vcv file.
#!/usr/bin/env python3
import zstandard as zstd
import io
import tarfile
import json
from typing import Optional, Any, Dict, cast
def ExtractVcvPatchJson(inVcvFileName:str) -> Optional[Dict[str, Any]]:
# Decompress the vcv file into a tar image.
with open(inVcvFileName, 'rb') as infile:
decomp = zstd.ZstdDecompressor()
reader = decomp.stream_reader(infile)
buffer = io.BytesIO(reader.read())
# Find the json contents inside the tar image.
with tarfile.open(fileobj=buffer) as archive:
for member in archive.getmembers():
if member.name == './patch.json':
if extracted := archive.extractfile(member):
return cast(Dict[str,Any], json.load(extracted))
# Unable to find patch.json inside the tar image.
return None
class Patch:
def __init__(self, patch: Dict[str, Any]) -> None:
self.dict = patch
def writeJson(self, outFileName:str) -> None:
with open(outFileName, 'wt') as outfile:
outfile.write(json.dumps(self.dict, indent=4))
def writeVcv(self, outFileName:str) -> None:
# Create in-memory tar archive
tarbuf = io.BytesIO()
with tarfile.open(fileobj=tarbuf, mode='w') as tar:
# Add patch.json
binary = json.dumps(self.dict, separators=(',', ':')).encode('utf-8')
patch_info = tarfile.TarInfo(name='patch.json')
patch_info.size = len(binary)
tar.addfile(patch_info, io.BytesIO(binary))
# Add empty modules/ directory
modules_info = tarfile.TarInfo(name='modules/')
modules_info.type = tarfile.DIRTYPE
tar.addfile(modules_info)
# Compress tar archive with Zstandard
tarbuf.seek(0)
compressor = zstd.ZstdCompressor(level=3)
with open(outFileName, 'wb') as outfile:
outfile.write(compressor.compress(tarbuf.read()))
@staticmethod
def ReadVcv(inFileName:str) -> 'Patch':
pdict = ExtractVcvPatchJson(inFileName)
if pdict is None:
raise Exception('Cannot load VCV Rack patch from file: ' + inFileName)
return Patch(pdict)
if __name__ == '__main__':
patch = Patch.ReadVcv('input/beetle_breakfast.vcv')
patch.writeJson('output/beetle_breakfast.json')
patch.writeVcv('output/beetle_breakfast.vcv')
I thought I would share this in case it inspires anyone else to write programs to read or write vcv format.
Also, I’m interested to hear from anyone else who has written code to generate novel patch files. Again, I’m not yet sure where this is leading, but my intuition is strongly pulling me in this direction, and I’ve learned to trust that feeling…