From 2ad9133ff79122d89e5b0ebec3a1e27f78b6d2e9 Mon Sep 17 00:00:00 2001 From: silbermantel Date: Fri, 24 Apr 2020 19:38:31 +0200 Subject: [PATCH 1/5] Update README.md --- README.md | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/README.md b/README.md index 123e0d4..da23463 100644 --- a/README.md +++ b/README.md @@ -1,35 +1 @@ -# Patchlets (Beta 0.5) - -## Tools for extracting and manipulating patchlets from VCV Rack patches. - -When using VCV Rack, I have sometimes wished that there was a way to copy some large combination of modules from another patch into the current patch I'm working on. Maybe I made a complicated LFO / sample&hold / quantized / transposed melody generator in another patch, and I'd like to use that in my current patch but don't want to bother building it again from scratch. Or maybe I have a complex, multi-module synth voice that I'd like to duplicate four times (assuming polyphony is not an option in this case). - -The idea of **Patchlets** is to organize patches in rows, each row corresponding to some collection of modules and connected cables that could be repurposed in some other patch. A collection of patchlets can be like a toolbox of go-to solutions that can be combined in different ways to form new patches. - -I've found these tools helpful for my Rack workflow and I hope some others may also benefit from them. - -Currently Patchlets is implemented as two different python scripts: - -- **extractPatchlets.py** takes a v1-compatible .vcv patch and splits it into multiple *patchlets*. - -- **combinePatchlets.py** takes multiple v1-compatible patchlets and combines them to make a new patch. - -These python scripts can be run from any python environment, but standalone .exe files can easily be created with pyinstaller. The only packages they use are *os*, *random* and *re*. I've tested it on both MacOs and Windows, but not Linux. - -**Read the [Instructions](https://github.com/millxing/Patchlets/blob/master/Instructions.md) before using.** - -### Plans for this project - -- Clean up the code: I'm not an experienced python programmer. This is my first python project and I've been learning as I worked on it. The code is well-commented but does not follow standard python norms. The code can be made much more efficient, but it seems to work fine for these tasks. I do plan to improve the quality of the code. - -- Testing: I've tested these scripts a bunch of times, but I'm sure I've missed some combination of conditions that could make these scripts fail. - -- Add more tools: The logical one to add next would be **appendPatchlet**, so you could just add a patchlet to the bottom of an existing patch. Currently you can do the same thing by splitting the existing patch into patchlets with **extractPatchlets** and then recombining the patchlets and adding the new one with **combinePatchlets**. - -- GUI: I've been playing with making a gui and a file browser dialog using Tkinter. - -- Executables: I have standalone .exe versions available and will add them to this repository shortly. - -- Known bugs: Crashes when two patchlets with Core Audio are combined. - -- Partnership: If anyone wants to help with any of this, just let me know. +this is just a personal fork to play around with, please chjeckout the upstream project From 63db456ea86428f04afd47be68293764aa725cc7 Mon Sep 17 00:00:00 2001 From: silbermantel Date: Fri, 24 Apr 2020 19:50:21 +0200 Subject: [PATCH 2/5] remove whitespaces, no functional change --- combinePatchlets.py | 148 ++++++++++++++++++------------------ extractPatchlets.py | 180 +++++++++++++++++++------------------------- 2 files changed, 153 insertions(+), 175 deletions(-) diff --git a/combinePatchlets.py b/combinePatchlets.py index 9847245..f4f58e7 100644 --- a/combinePatchlets.py +++ b/combinePatchlets.py @@ -23,180 +23,180 @@ import random import os -print('') +print('') print("Current working directory is: " + os.getcwd()) -print('') +print('') #os.chdir(path) ## Get name of new combined patch -fname = input("What is name of the new combined patch? ") -print('') +fname = input("What is name of the new combined patch? ") +print('') # Get names and contents of patchlets to combine print("Input names of patchlets to combine (leave off '.vcv')") print("Put all patchlets in the current directory or add the full path") print("Hit [Enter] to finish") -print("") +print("") done = 0 pl = 0 patchlets = [] -while done==0: +while done==0: temp = input("Name of patchlet #" + str(pl+1) + " ") - if temp=="": + if temp=="": done = 1 - else: + else: if os.path.exists(temp+".vcv"): - patchlets.append(temp) + patchlets.append(temp) pl = pl + 1 else: print("Error: file not found") - + newPatch = []; newModules = []; newCables = []; modIdMap = [] # Loop through patchlets, adding modules to new module block and cables to new cable block -for k in range(0,len(patchlets)): - - print(patchlets[k] + '.vcv') - +for k in range(0,len(patchlets)): + + print(patchlets[k] + '.vcv') + # Read contents of patchlet f = open(patchlets[k] + '.vcv') A = [] for line in f: A.append(line) - f.close() - + f.close() + # Create the first 3 lines of a new patch ("{","version: #.#.#", "modules: ["}) - if k==0: - newPatch.append(A[0]) + if k==0: + newPatch.append(A[0]) newPatch.append(A[1]) newPatch.append(A[2]) - - # Extract the module block (moduleBlock) + + # Extract the module block (moduleBlock) openBracket = -99 - for i in range(0,len(A)): + for i in range(0,len(A)): if ('"modules":' in A[i]): moduleBlockStart = i+1 openBracket = 1 else: - if ('[' in A[i]): openBracket = openBracket + 1 - if (']' in A[i]): openBracket = openBracket - 1 + if ('[' in A[i]): openBracket = openBracket + 1 + if (']' in A[i]): openBracket = openBracket - 1 if openBracket==0: moduleBlockEnd = i openBracket=99 - moduleBlock = A[moduleBlockStart:moduleBlockEnd] - + moduleBlock = A[moduleBlockStart:moduleBlockEnd] + # Change the row to the patchlet number # Change the module id to a random number - for i in range(0,len(moduleBlock)): + for i in range(0,len(moduleBlock)): if (moduleBlock[i][:14] == ' "pos": ['): moduleBlock[i+2] = ' '+str(k) - if (moduleBlock[i][:11]== ' "id":'): - temp = moduleBlock[i][12:]; temp = temp.split(","); idnum = int(temp[0]) + if (moduleBlock[i][:11]== ' "id":'): + temp = moduleBlock[i][12:]; temp = temp.split(","); idnum = int(temp[0]) temp = random.randint(1,65536) - moduleBlock[i] = (' "id": ' + str(temp)+',\n') + moduleBlock[i] = (' "id": ' + str(temp)+',\n') modIdMap.append((idnum,temp,k)) - + # Ensure that all the modules end with a comma if len(moduleBlock)>0: - moduleBlock[-1] = ' },\n' - - # Extract the cable block + moduleBlock[-1] = ' },\n' + + # Extract the cable block openBracket = -99 - for i in range(moduleBlockEnd+1,len(A)): + for i in range(moduleBlockEnd+1,len(A)): if ('"cables":' in A[i]): cableBlockStart = i+1 openBracket = 1 else: - if ('[' in A[i]): openBracket = openBracket + 1 - if (']' in A[i]): openBracket = openBracket - 1 + if ('[' in A[i]): openBracket = openBracket + 1 + if (']' in A[i]): openBracket = openBracket - 1 if openBracket==0: cableBlockEnd = i openBracket=99 - cableBlock = A[cableBlockStart:cableBlockEnd] - + cableBlock = A[cableBlockStart:cableBlockEnd] + # Ensure that all the cables end with a comma if len(cableBlock)>0: cableBlock[-1] = ' },\n' - - # Add this module block to newModules + + # Add this module block to newModules for i in range(0,len(moduleBlock)): newModules.append(moduleBlock[i]) - + # Add this cable block to newCables for i in range(0,len(cableBlock)): newCables.append(cableBlock[i]) - + # Ensure that there is no comma at the end of the new module and cable blocks newModules[-1] = ' }\n' -if len(newCables)>0: +if len(newCables)>0: newCables[-1] = ' }\n' # Close module block with bracket and comma -newModules.append(' ],\n') +newModules.append(' ],\n') # Close cable block with bracket (no comma) newCables.append(' ]\n') - + # Add the new module block to the new patch for i in range(0,len(newModules)): newPatch.append(newModules[i]) # Add the new cable block to the new patch -newPatch.append(' "cables": [\n') +newPatch.append(' "cables": [\n') for i in range(0,len(newCables)): newPatch.append(newCables[i]) -# Close patch with brace +# Close patch with brace newPatch.append('}\n') - -# change the module ids in the cables to match the random module ids -id = [] -for i in range(0,len(newPatch)): + +# change the module ids in the cables to match the random module ids +id = [] +for i in range(0,len(newPatch)): if (newPatch[i][:11]== ' "id":'): - temp = newPatch[i][12:]; temp = temp.split(","); + temp = newPatch[i][12:]; temp = temp.split(","); id.append(int(temp[0])) -for i in range(0,len(newPatch)): - if (newPatch[i][:22]== ' "rightModuleId":'): +for i in range(0,len(newPatch)): + if (newPatch[i][:22]== ' "rightModuleId":'): #print("in " + newPatch[i]) temp = newPatch[i][23:]; temp = temp.split(","); idnum = int(temp[0]) for j in range(0,len(modIdMap)): - if modIdMap[j][0]==idnum: - newPatch[i] = (' "rightModuleId" :'+ str(modIdMap[j][1]) + ',\n') - #print(str(modIdMap[j][0])+" "+str(modIdMap[j][1])) + if modIdMap[j][0]==idnum: + newPatch[i] = (' "rightModuleId" :'+ str(modIdMap[j][1]) + ',\n') + #print(str(modIdMap[j][0])+" "+str(modIdMap[j][1])) #print(idnum) - #print("out " + newPatch[i]) - if (newPatch[i][:21]== ' "leftModuleId":'): - temp = newPatch[i][22:]; temp = temp.split(","); idnum = int(temp[0]) + #print("out " + newPatch[i]) + if (newPatch[i][:21]== ' "leftModuleId":'): + temp = newPatch[i][22:]; temp = temp.split(","); idnum = int(temp[0]) for j in range(0,len(modIdMap)): - if modIdMap[j][0]==idnum: + if modIdMap[j][0]==idnum: newPatch[i] = (' "leftModuleId": '+ str(modIdMap[j][1]) + ',\n') - if (newPatch[i][:23]== ' "outputModuleId":'): - temp = newPatch[i][24:]; temp = temp.split(","); idnum = int(temp[0]) + if (newPatch[i][:23]== ' "outputModuleId":'): + temp = newPatch[i][24:]; temp = temp.split(","); idnum = int(temp[0]) for j in range(0,len(modIdMap)): - if modIdMap[j][0]==idnum: + if modIdMap[j][0]==idnum: newPatch[i] = (' "outputModuleId": '+ str(modIdMap[j][1]) + ',\n') - if (newPatch[i][:22]== ' "inputModuleId":'): - temp = newPatch[i][23:]; temp = temp.split(","); idnum = int(temp[0]) + if (newPatch[i][:22]== ' "inputModuleId":'): + temp = newPatch[i][23:]; temp = temp.split(","); idnum = int(temp[0]) for j in range(0,len(modIdMap)): - if modIdMap[j][0]==idnum: + if modIdMap[j][0]==idnum: newPatch[i] = (' "inputModuleId": '+ str(modIdMap[j][1]) + ',\n') - + ' "rightModuleId": 269,' - -# Write new patch to file + +# Write new patch to file f = open(fname+'.vcv','w') for j in range(0,len(newPatch)): f.write(newPatch[j]) -f.close() -print("") +f.close() +print("") print(fname + '.vcv created') -print("") +print("") print('Merge Completed') - + # latest problem: when combining multiple copies of the same patch, the cables and left/right modules are messed up diff --git a/extractPatchlets.py b/extractPatchlets.py index 4ea9ec6..1a1220d 100644 --- a/extractPatchlets.py +++ b/extractPatchlets.py @@ -23,10 +23,10 @@ import re import os -print('') +print('') print("Current working directory is: " + os.getcwd()) -print('') -print('') +print('') +print('') print('Which .vcv file would you like to create patchlets from?') fname = input("? ") print("") @@ -43,186 +43,164 @@ temp = re.findall(r'\"(.+?)\"',A[1]) rackVersion = temp[1]; -# Extract the module block (moduleBlock) +# Extract the module block (moduleBlock) openBracket = -99 -for i in range(0,len(A)): +for i in range(0,len(A)): if (A[i]== ' "modules": [\n'): moduleBlockStart = i+1 openBracket = 1 else: - if ('[' in A[i]): openBracket = openBracket + 1 - if (']' in A[i]): openBracket = openBracket - 1 + if ('[' in A[i]): openBracket = openBracket + 1 + if (']' in A[i]): openBracket = openBracket - 1 if openBracket==0: moduleBlockEnd = i openBracket=99 -moduleBlock = A[moduleBlockStart:moduleBlockEnd] - -# Extract the cable block (cableBlock) +moduleBlock = A[moduleBlockStart:moduleBlockEnd] + +# Extract the cable block (cableBlock) openBracket = -99 -for i in range(moduleBlockEnd+1,len(A)): +for i in range(moduleBlockEnd+1,len(A)): if (A[i]== ' "cables": [\n'): cableBlockStart = i+1 openBracket = 1 else: - if ('[' in A[i]): openBracket = openBracket + 1 - if (']' in A[i]): openBracket = openBracket - 1 + if ('[' in A[i]): openBracket = openBracket + 1 + if (']' in A[i]): openBracket = openBracket - 1 if openBracket==0: cableBlockEnd = i openBracket=99 -cableBlock = A[cableBlockStart:cableBlockEnd] +cableBlock = A[cableBlockStart:cableBlockEnd] # Create list of all the modules modules = []; numModules = 0; openbracket = 0 -for i in range(0,len(moduleBlock)): +for i in range(0,len(moduleBlock)): if ('{' in moduleBlock[i]): if openbracket==0: cc = [] - openbracket = openbracket + 1 - if openbracket>0: - cc.append(moduleBlock[i]) - if ('}' in moduleBlock[i]): openbracket = openbracket - 1 - if openbracket == 0: + openbracket = openbracket + 1 + if openbracket>0: + cc.append(moduleBlock[i]) + if ('}' in moduleBlock[i]): openbracket = openbracket - 1 + if openbracket == 0: numModules = numModules + 1 modules.append(cc) - + # Create list of all the cables cables = []; opencable = 0; numCables = 0 -for i in range(0,len(cableBlock)): - if (cableBlock[i]== ' {\n'): - opencable = 1 +for i in range(0,len(cableBlock)): + if (cableBlock[i]== ' {\n'): + opencable = 1 cc = [] - if opencable == 1: - cc.append(cableBlock[i]) - if (' }' in cableBlock[i]): - opencable = 0 + if opencable == 1: + cc.append(cableBlock[i]) + if (' }' in cableBlock[i]): + opencable = 0 numCables = numCables + 1 cables.append(cc) # Create list of all module IDs, module rows, module columns -col = []; row = []; rowrow = []; colrow = []; id = [] -for i in range(0,numModules): +col = []; row = []; rowrow = []; colrow = []; id = [] +for i in range(0,numModules): A = modules[i] - for j in range(0,len(A)): - if (A[j][:11] == ' "id":'): + for j in range(0,len(A)): + if (A[j][:11] == ' "id":'): temp = re.findall(r'\d',A[j]) temp = int("".join(list(map(str,temp)))) - id.append(temp) + id.append(temp) if A[j][:14] == ' "pos": [': temp = re.findall(r'\d',A[j+1]) temp1 = int("".join(list(map(str,temp)))) temp = re.findall(r'\d',A[j+2]) temp2 = int("".join(list(map(str,temp)))) - col.append(temp1) + col.append(temp1) row.append(temp2) - colrow.append(j+1) - rowrow.append(j+2) - + colrow.append(j+1) + rowrow.append(j+2) + # Create list of all cable inputs and outputs -outMod = []; inMod = []; outId = []; inId = [] -for i in range(0,numCables): +outMod = []; inMod = []; outId = []; inId = [] +for i in range(0,numCables): A = cables[i] - for j in range(0,len(A)): - if (('outputModuleId') in A[j]): + for j in range(0,len(A)): + if (('outputModuleId') in A[j]): temp = re.findall(r'\d',A[j]) temp = int("".join(list(map(str,temp)))) outMod.append(temp) - if (('inputModuleId') in A[j]): + if (('inputModuleId') in A[j]): temp = re.findall(r'\d',A[j]) temp = int("".join(list(map(str,temp)))) inMod.append(temp) - if (('outputId') in A[j]): + if (('outputId') in A[j]): temp = re.findall(r'\d',A[j]) temp = int("".join(list(map(str,temp)))) - outId.append(temp) - if (('inputId') in A[j]): + outId.append(temp) + if (('inputId') in A[j]): temp = re.findall(r'\d',A[j]) temp = int("".join(list(map(str,temp)))) inId.append(temp) -# Create patchlets +# Create patchlets for k in range(0,max(row)+1): - + # Create the first 3 lines of a new patchlet ("{","version: #.#.#", "modules: ["}) - new_file = original[0:3] - + new_file = original[0:3] + # Find leftmost x-coordinate in the row mincol = 9999 for u in range(0,len(modules)): if (row[u]==k): if col[u]0: - new_cables[-1][-1] = ' }\n' - - # Add module block to new patchlet + new_cables.append(cables[u]) + + if len(new_cables)>0: + new_cables[-1][-1] = ' }\n' + + # Add module block to new patchlet for u in range(0,len(new_modules)): for j in range(0,len(new_modules[u])): - new_file.append(new_modules[u][j]) + new_file.append(new_modules[u][j]) # Close module block with bracket and comma - new_file.append(' ],\n') - + new_file.append(' ],\n') + # Open cable block - new_file.append('"cables": [\n') - for u in range(0,len(new_cables)): - for j in range(0,len(new_cables[u])): - new_file.append(new_cables[u][j]) - + new_file.append('"cables": [\n') + for u in range(0,len(new_cables)): + for j in range(0,len(new_cables[u])): + new_file.append(new_cables[u][j]) + # Close cable block with bracket (no comma) new_file.append(' ]\n') - + # Close patch with brace new_file.append('}\n') - + # Save each new patchlet plet_name = fname + "_" + str(k+1) + '.vcv' f = open(plet_name,'w') for j in range(0,len(new_file)): f.write(new_file[j]) - f.close() - print('Row ' + str(k+1) + " -> " + plet_name) - -print('Extraction Completed') - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + f.close() + print('Row ' + str(k+1) + " -> " + plet_name) + +print('Extraction Completed') From 054febb22bce75d28d0c5bce04709417619cf129 Mon Sep 17 00:00:00 2001 From: silbermantel Date: Fri, 24 Apr 2020 20:03:22 +0200 Subject: [PATCH 3/5] Revert "Update README.md" This reverts commit 2ad9133ff79122d89e5b0ebec3a1e27f78b6d2e9. --- README.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index da23463..123e0d4 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ -this is just a personal fork to play around with, please chjeckout the upstream project +# Patchlets (Beta 0.5) + +## Tools for extracting and manipulating patchlets from VCV Rack patches. + +When using VCV Rack, I have sometimes wished that there was a way to copy some large combination of modules from another patch into the current patch I'm working on. Maybe I made a complicated LFO / sample&hold / quantized / transposed melody generator in another patch, and I'd like to use that in my current patch but don't want to bother building it again from scratch. Or maybe I have a complex, multi-module synth voice that I'd like to duplicate four times (assuming polyphony is not an option in this case). + +The idea of **Patchlets** is to organize patches in rows, each row corresponding to some collection of modules and connected cables that could be repurposed in some other patch. A collection of patchlets can be like a toolbox of go-to solutions that can be combined in different ways to form new patches. + +I've found these tools helpful for my Rack workflow and I hope some others may also benefit from them. + +Currently Patchlets is implemented as two different python scripts: + +- **extractPatchlets.py** takes a v1-compatible .vcv patch and splits it into multiple *patchlets*. + +- **combinePatchlets.py** takes multiple v1-compatible patchlets and combines them to make a new patch. + +These python scripts can be run from any python environment, but standalone .exe files can easily be created with pyinstaller. The only packages they use are *os*, *random* and *re*. I've tested it on both MacOs and Windows, but not Linux. + +**Read the [Instructions](https://github.com/millxing/Patchlets/blob/master/Instructions.md) before using.** + +### Plans for this project + +- Clean up the code: I'm not an experienced python programmer. This is my first python project and I've been learning as I worked on it. The code is well-commented but does not follow standard python norms. The code can be made much more efficient, but it seems to work fine for these tasks. I do plan to improve the quality of the code. + +- Testing: I've tested these scripts a bunch of times, but I'm sure I've missed some combination of conditions that could make these scripts fail. + +- Add more tools: The logical one to add next would be **appendPatchlet**, so you could just add a patchlet to the bottom of an existing patch. Currently you can do the same thing by splitting the existing patch into patchlets with **extractPatchlets** and then recombining the patchlets and adding the new one with **combinePatchlets**. + +- GUI: I've been playing with making a gui and a file browser dialog using Tkinter. + +- Executables: I have standalone .exe versions available and will add them to this repository shortly. + +- Known bugs: Crashes when two patchlets with Core Audio are combined. + +- Partnership: If anyone wants to help with any of this, just let me know. From 665ff5a4a93bd81eef724860b456eaccfe5b4d9b Mon Sep 17 00:00:00 2001 From: silbermantel Date: Fri, 24 Apr 2020 21:55:21 +0200 Subject: [PATCH 4/5] 5 provide POC vcvreader --- vcvreader.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 vcvreader.py diff --git a/vcvreader.py b/vcvreader.py new file mode 100644 index 0000000..6fee63f --- /dev/null +++ b/vcvreader.py @@ -0,0 +1,28 @@ +import re +import os +import json + + + +def get_file_name(): + print('') + print("Current working directory is: " + os.getcwd()) + print('') + print('') + print('Which .vcv file would you like to create patchlets from?') + fname = input("? ") + print("") + return fname + +def read_contents_of_patch(): + with open(get_file_name() + '.vcv') as json_file: + data = json.load(json_file) + for current_module in data['modules']: + print('plugin: ' + current_module['plugin']) + print('model: ' + current_module['model']) + print('version: ' + current_module['version']) + print('pos1: ' + str(current_module['pos'][0])) + print('pos2: ' + str(current_module['pos'][1])) + print('') + +read_contents_of_patch() From db002fe88edaeb7af5233003063f4d0d861897ec Mon Sep 17 00:00:00 2001 From: silbermantel Date: Fri, 24 Apr 2020 23:38:21 +0200 Subject: [PATCH 5/5] improve POC the new POC prints the plugin and model of each module for all patchlets which is getting closer to what the extract script intends to do --- vcvreader.py | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/vcvreader.py b/vcvreader.py index 6fee63f..9c0a4e0 100644 --- a/vcvreader.py +++ b/vcvreader.py @@ -2,8 +2,6 @@ import os import json - - def get_file_name(): print('') print("Current working directory is: " + os.getcwd()) @@ -14,15 +12,38 @@ def get_file_name(): print("") return fname -def read_contents_of_patch(): - with open(get_file_name() + '.vcv') as json_file: + +def get_patchlets(modules): + mlist = [] + for current_module in modules: + mlist.append(current_module['pos'][1]) + h=max(mlist) + parray=[] + for x in range(0,h+1): + parray.append([]) + for current_module in modules: + parray[current_module['pos'][1]].append(current_module['id']) + return (parray) + + +def crawl_file(c_module,all_modules,keys): + for module in all_modules: + if c_module == module['id']: + r_dict={} + for c_key in keys: + r_dict[str(c_key)]=module[str(c_key)] + + return (r_dict) + + +def print_patchlets(): +# with open(get_file_name() + '.vcv') as json_file: + with open('generative.vcv') as json_file: data = json.load(json_file) - for current_module in data['modules']: - print('plugin: ' + current_module['plugin']) - print('model: ' + current_module['model']) - print('version: ' + current_module['version']) - print('pos1: ' + str(current_module['pos'][0])) - print('pos2: ' + str(current_module['pos'][1])) - print('') - -read_contents_of_patch() +# patchlet_array=get_number_of_patchlets(data['modules']) + for current_patchlet in get_patchlets(data['modules']): + print ("new patchlet") + for current_module in current_patchlet: + print (crawl_file(current_module,data['modules'],["plugin","model"])) + +print_patchlets()