Full Code of SpektroAudio/Patchbook for AI

master ce839dcd0f1b cached
6 files
27.6 KB
7.4k tokens
17 symbols
1 requests
Download .txt
Repository: SpektroAudio/Patchbook
Branch: master
Commit: ce839dcd0f1b
Files: 6
Total size: 27.6 KB

Directory structure:
gitextract_l6elw5r5/

├── Examples/
│   ├── patch1.txt
│   ├── patch2.txt
│   └── syncpll.txt
├── LICENSE
├── README.md
└── patchbook.py

================================================
FILE CONTENTS
================================================

================================================
FILE: Examples/patch1.txt
================================================
VOICE 1:
	- Metropolis (Pitch) p> Braids (1voct) [weight=3]
	- Metropolis (Gate) g> Function (Trigger)
	- Braids (Out) -> Optomix (Ch1 Signal)
	- Function (PosOut) >> Optomix (Ch1 CV)
	- Function (NegOut) >> Braids (Timbre CV)
	* Metropolis:
	| BPM = 124
	| Swing = 0
	| Root = F
	| Scale = Minor
	| Mode = F. Forward
	| Stages = 16
	
	* Braids:
	| Mode = Fold
	| Timbre = 30%
	| Timbre CV = -20%
	| Color = 0%

	* Function: Rise = 50% | Fall = 50% | Curve = 30%
	* Optomix: Damp = 0% | Control = 100%


================================================
FILE: Examples/patch2.txt
================================================
VOICE 1:
	- Metropolis (Pitch) p> Lizard2 (CV)
	- Lizard2 (Out 2) -> Multifilter (Input)
	- Multifilter (LPF) -> Optomix (Ch1 Signal)
	- Metropolis (Pitch) p> Multifilter (1V/oct)
	- Metropolis (Gate) g> Z4000 (Gate)
	- Z4000 (Out) >> Optomix (Ch1 CV)
	- Metropolis (Gate) g> Function (Gate)
	- Function (Pos Out) >> Multifilter (CV Input)

VOICE 2:
	- Metropolis (Clock) c> SequentialVoltage (CLK In)
	- Metropolis (Reset) t> SequentialVoltage (Reset)
	- SequentialVoltage (Out) >> Braids (1V/Oct)
	- Braids (Out) -> A124 (Input)
	- e350 (XY) >> A124 (CV2)
	- MultiLFO (LFO 2) >> Braids (Timbre)

================================================
FILE: Examples/syncpll.txt
================================================
VOICE 1:

	- Metropolis (Pitch) p> Aether VCO (CV)
	- Metropolis (Gate) g> Maths (Ch 1 Trigger)
	- Metropolis (Gate) g> Maths (Ch 4 Trigger)
	
	* Aether VCO: LFO Freq = 5 | LFO PWM = 7
	- Aether VCO (Pulse) -> Mixer (Ch1)
	- Aether VCO (Sub 1) -> Tides (Clk)
	- Tides (Bi) -> Mixer (Ch2)
	- Aether VCO (Sub 2) -> Z3000 (HSync)
	- Z3000 (Saw) -> Mixer (Ch3)
	
	- MultiLFO (LFO 1) >> Tides (Smoothness)
	- MultiLFO (LFO 2 Triangle) >> Tides (Shape)
	- MultiLFO (LFO 3 Triangle) >> Z3000 (PWM)
	* MultiLFO:
	| LFO 1 Freq = 3.8
	| LFO 1 Shape = Sine
	| LFO 1 S&H = 0
	| LFO 2 Freq = 1
	| LFO 3 Freq = 1
	* Tides: PLL Mode = True | Freq = 60% | Smoothness = 70%
	* Z3000: Freq = 1pm
	
	- Maths (Ch 1) >> Multifilter (CV)
	- Maths (Ch 4) >> uVCA (Ch1 CV)
	
	- Mixer (Output) -> Multifilter (Input)
	- Multifilter (LPF) -> uVCA (Ch1 Input)
	- uVCA (Ch1 Output) -> AUDIO INTERFACE (In 3)

	

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Spektro Audio

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
![Patchbook Logo](/Images/patchbook-logo.jpg)

# About PatchBook:

PatchBook is a markup language and parser for writing and distributing patches for modular synthesizers created by [Spektro Audio](http://spektroaudio.com/).
The markup language is designed to be easily readable and writeable by humans while the parser can process .txt files written in the PatchBook format and output a JSON file that can be used by other applications to display and process the patch's data.

**Patchbook Version:** 1.2
**Parser version:** b3

**Table of Content:**

<!-- MarkdownTOC -->

- Markup Description
	- Voices
	- Connections
	- Parameters
	- Comments:
	- Examples
- Parser
	- Requirements
	- How to Use
	- Data Structure

<!-- /MarkdownTOC -->


---

# Markup Description

---

## Voices

**Voices** must be written in all caps, without any spaces before the name and followed by a colon.  
Examples:

- BASS 1:
- VOICE 1:
- LEAD:

Every connection described after a voice annotation will be assigned to that voice.

---

## Connections

Every connection (patch cable) must be annotated using the following format: **- Output Module (Output Label) >> Input Module (Input Label)**.  
Examples:  

```
- Maths (Ch. 1 Unity) >> Polaris (CV 2)
- Tides (Bi) >> Braids (Timbre)
```

While the >> indicator can be used to indicate a standard connection, it could (and should) also be replaced by more specific indicators according to the kind of signal being sent from the Output Module to the Input Module:  

- \>> for CV
- -> for Audio
- p> for Pitch (1v/oct or Hz/V)
- g> for Gate
- t> for Trigger
- c> for Clock

Examples:

```
- Metropolis (Pitch) p> Braids (1 V/Oct)
- Pamela's Workout (1) c> Penta (Clk)
- Braids (Out) -> Polaris (Input)
```

**Additional info:**

- The manufacturer's name should only be included if the module's name is too generic (example: VB Modular ADSR).
- Non-modular equipment (such as audio interfaces, recorders, and other synths) should be written in all caps: NAME OF GEAR (Input or Output).
- While specific module names are preferable, they can also be replaced by more generic names such as VCA, ADSR, Oscillator, etc.

**Extra arguments:**

Additional GraphViz arguments such as color, weight, and style can be appended to the connection line in between brackets and separated by commas.

Example:
```
- Metropolis (Pitch) p> Braids (1 V/Oct) [color=red, weight=3]
```

Supported GraphViz arguments: color, weight, style, dir, and arrowtail.

---

## Parameters

Parameters can be annotated in 2 different ways: single line or multiline. Every parameter annotation must start with an asterisk character before the module name.

**Single-line**  
```
* Function: Rise = 50% | Fall = 50% | Curve = 30%
```

**Multi-Line**
```
* Braids:  
	| Mode = CSAW  
	| Color = 50%  
	| Timbre = 50%  
```

**Additional info**

- Parameter values can be written as knob / fader position (percentage), specific value followed by unit (5Hz, 10ms, etc.), or as a descriptive value (fast, slow, simple, complex, short, long).
- Parameters are not assigned to any voice since the same module can be used in multiple voices.

---

## Comments:

Comments can be added to the patch by prepending two forward slashes (//).

Example:

```
// This is a nice comment
```

---

## Examples

---

### Example 1

```
VOICE 1:
	- Metropolis (Pitch) p> Braids (1v/oct) [weight=3]
	- Metropolis (Gate) g> Function (Trigger)
	- Braids (Out) -> Optomix (Ch1 Signal)
	- Function (+ Out) >> Optomix (Ch1 CV)
	- Function (- Out) >> Braids (Timbre CV)
	- Optomix (Out 1) -> AUDIO INTERFACE (input)

	* Metropolis:
	| BPM = 124
	| Swing = 0
	| Root = F
	| Scale = Minor
	| Mode = F. Forward
	| Stages = 16

	* Braids:
	| Mode = Fold
	| Timbre = 30%
	| Timbre CV = -20%
	| Color = 0%

	* Function: Rise = 50% | Fall = 50% | Curve = 30%
	* Optomix: Damp = 0% | Control = 100%
```

### Example 2

```
VOICE 1:

	- Metropolis (Pitch) p> Aether VCO (CV)
	- Metropolis (Gate) g> Maths (Ch 1 Trigger)
	- Metropolis (Gate) g> Maths (Ch 4 Trigger)

	* Aether VCO: LFO Freq = 5 | LFO PWM = 7
	- Aether VCO (Pulse) -> Mixer (Ch1)
	- Aether VCO (Sub 1) -> Tides (Clk)
	- Tides (Bi) -> Mixer (Ch2)
	- Aether VCO (Sub 2) -> Z3000 (HSync)
	- Z3000 (Saw) -> Mixer (Ch3)

	- MultiLFO (LFO 1) >> Tides (Smoothness)
	- MultiLFO (LFO 2 Triangle) >> Tides (Shape)
	- MultiLFO (LFO 3 Triangle) >> Z3000 (PWM)
	* MultiLFO:
	| LFO 1 Freq = 3.8
	| LFO 1 Shape = Sine
	| LFO 1 S&H = 0
	| LFO 2 Freq = 1
	| LFO 3 Freq = 1
	* Tides: PLL Mode = True | Freq = 60% | Smoothness = 70%
	* Z3000: Freq = 1pm

	- Maths (Ch 1) >> Multifilter (CV)
	- Maths (Ch 4) >> uVCA (Ch1 CV)

	- Mixer (Output) -> Multifilter (Input)
	- Multifilter (LPF) -> uVCA (Ch1 Input)
	- uVCA (Ch1 Output) -> AUDIO INTERFACE (In 3)

```

----

# Parser

The PatchBook parser is a Python program that can read text files written in the PatchBook format and generate a JSON file.

---
## Requirements

-  Python 3

---
## How to Use

To use the parser, download the python script, open the terminal and use the command:

```
python3 path/to/script/patchbook.py -file /path/to/textfile.txt
```

After loading the text file into the parser, you can use the following commands to process it:

- **module**: Outputs a list of connections and parameters for a specific module.
- **connections**: Prints a list of all connections organized by type (pitch, gate, clock, etc.).
- **export**: Generates a JSON file based on the input text file.
- **graph**: Generates a code that can be copied to pasted into the [Graphiz Online editor](https://dreampuf.github.io/GraphvizOnline/) to generate a signal flow chart for the patch (that can be downloaded as a SVG or PNG file). Non-programmers have the option to use the [Patchbook to GraphViz Online Converter](https://patchbook-converter.herokuapp.com) to create flowcharts without having to install Python and use the parser.

![Example syncpll signal flow generated using GraphViz](/Images/graphviz-signal-flow.png?raw=true)

Alternatively, any of the above commands may be invoked on the command line with a dash prefix, in which case the text file is parsed, the command is executed, and the program exits. This makes it possible to produce (for example) an SVG file in one step like so:

```
python3 path/to/script/patchbook.py -file /path/to/textfile.txt -graph | dot -Tsvg > /path/to/svgfile.svg
```

Graphs are constructed horizontally left-to-right by default, but you may use the **-dir** option to change the direction of the graph to vertical top-to-bottom like so:

```
python3 path/to/script/patchbook.py -file /path/to/textfile.txt -graph -dir DN
```


------
## Data Structure

![Patchbook Data Structure](/Images/datastructure.png?raw=true)

----

Patchbook was created by Ícaro Ferre / Spektro Audio.  
Twitter: @icaroferre / @spektroaudio  
http://spektroaudio.com/  


================================================
FILE: patchbook.py
================================================
#!/usr/bin/env python3
"""
PATCHBOOK MARKUP LANGUAGE & PARSER
CREATED BY SPEKTRO AUDIO
http://spektroaudio.com/
"""

import sys
import re
import os
import argparse
import json

# Parser INFO
parserVersion = "b3"

# Reset main dictionary
mainDict = {
    "info": {"patchbook_version": parserVersion},
    "modules": {},
    "comments": []
}

# Available connection types
connectionTypes = {
    "->": "audio",
    ">>": "cv",
    "p>": "pitch",
    "g>": "gate",
    "t>": "trigger",
    "c>": "clock"
}


# Reset global variables
lastModuleProcessed = ""
lastVoiceProcessed = ""

# Parse script arguments
parser = argparse.ArgumentParser()
parser.add_argument("-file", type=str, default="",
                    help="Name of the text file that will be parsed (including extension)")
parser.add_argument("-debug", type=int, default=0,
                    help="Enable Debugging Mode")
parser.add_argument("-dir", type=str, default="LR",
                    help="Graph direction: LR (left-to-right) or DN (top-to-bottom)")
parser.add_argument("-modules", action="store_const", const="modules", dest="command",
                    help="Print all modules")
parser.add_argument("-print", action="store_const", const="print", dest="command",
                    help="Print data structure")
parser.add_argument("-export", action="store_const", const="export", dest="command",
                    help="Print JSON")
parser.add_argument("-connections", action="store_const", const="connections", dest="command",
                    help="Print connections")
parser.add_argument("-graph", action="store_const", const="graph", dest="command",
                    help="Print dot code for graph")
args = parser.parse_args()
filename = args.file
debugMode = args.debug
direction = args.dir
if args.command:
    one_shot_command = args.command
    quiet = True
else:
    one_shot_command = None
    quiet = False

connectionID = 0

# Set up debugMode
if args.debug == 1:
    debugMode = True
else:
    debugMode = False


def initial_print():
    global quiet
    if not quiet:
        print()
        print("██████████████████████████████")
        print("       PATCHBOOK PARSER       ")
        print("   Created by Spektro Audio   ")
        print("██████████████████████████████")
        print()
        print("Version " + parserVersion)
        print()


def get_script_path():
    # Get path to python script
    return os.path.dirname(os.path.realpath(sys.argv[0]))


def getFilePath(filename):
    try:
        # Append script path to the filename
        base_dir = get_script_path()
        filepath = os.path.join(base_dir, filename)
        if debugMode:
            print("File path: " + filepath)
        return filepath
    except IndexError:
        pass


def parseFile(filename):
    # This function reads the txt file and process each line.
    global quiet
    lines = []
    try:
        if not quiet: print("Loading file: " + filename)
        with open(filename, "r") as file:
            for l in file:
                lines.append(l)
                regexLine(l)
    except TypeError:
        print("ERROR. Please add text file path after the script.")
    except FileNotFoundError:
        print("ERROR. File not found.")
    if not quiet:
        print("File successfully processed.")
        print()


def regexLine(line):
    global lastModuleProcessed
    global lastVoiceProcessed

    if debugMode:
        print()
    if debugMode:
        print("Processing: " + line)

    # CHECK FOR COMMENTS
    if debugMode:
        print("Checking input for comments...")
    re_filter = re.compile(r"^\/\/\s?(.+)$")  # Regex for "// Comments"
    re_results = re_filter.search(line.strip())
    try:
        comment = re_results.group().replace("//", "").strip()
        if debugMode:
            print("New comment found: " + comment)
            addComment(comment)
        return
    except AttributeError:
        pass

    # CHECK FOR VOICES
    if debugMode:
        print("Cheking input for voices...")
    re_filter = re.compile(r"^(.+)\:$")  # Regex for "VOICE 1:"
    re_results = re_filter.search(line)
    try:
        # For some reason the Regex filter was still detecting parameter declarations as voices,
        # so I'm also running the results through an if statement.
        results = re_results.group().replace(":", "")
        if "*" not in results and "-" not in results and "|" not in results:
            if debugMode:
                print("New voice found: " + results.upper())
            lastVoiceProcessed = results.upper()
            return
    except AttributeError:
        pass

    # CHECK FOR CONNECTIONS
    if debugMode:
        print("Cheking input for connections...")
    re_filter = re.compile(
        r"\-\s(.+)[(](.+)[)]\s(\>\>|\-\>|[a-z]\>)\s(.+)[(](.+)[)]\s(\[.+\])?$")
    re_results = re_filter.search(line)
    try:
        results = re_results.groups()
        voice = lastVoiceProcessed
        if len(results) == 6:
            if debugMode:
                print("New connection found, parsing info...")
            # args = parseArguments(results[5])
            # results = results[:5]
            addConnection(results, voice)
            return
    except AttributeError:
        pass

    # CHECK PARAMETERS
    if debugMode:
        print("Checking for parameters...")
    # If single-line parameter declaration:
    re_filter = re.compile(r"^\*\s(.+)\:\s?(.+)?$")
    re_results = re_filter.search(line.strip())
    try:
        # Get module name
        results = re_results.groups()
        module = results[0].strip().lower()
        if debugMode:
            print("New module found: " + module)
        if results[1] != None:
            # If parameters are also declared
            parameters = results[1].split(" | ")
            for p in parameters:
                p = p.split(" = ")
                addParameter(module, p[0].strip().lower(), p[1].strip())
            return
        elif results[1] == None:
            if debugMode:
                print("No parameters found. Storing module as global variable...")
            lastModuleProcessed = module
            return
    except AttributeError:
        pass

    # If multi-line parameter declaration:
    if "|" in line and "=" in line and "*" not in line:
        module = lastModuleProcessed.lower()
        if debugMode:
            print("Using global variable: " + module)
        parameter = line.split(" = ")[0].replace("|", "").strip().lower()
        value = line.split(" = ")[1].strip()
        addParameter(module, parameter, value)
        return


def parseArguments(args):
    # This method takes an arguments string like "[color = blue]" and converts it to a dictionary
    args_string = args.replace("[", "").replace("]", "")
    args_array = args_string.split(",")
    args_dict = {}

    if debugMode:
        print("Parsing arguments: " + args)

    for item in args_array:
        item = item.split("=")
        name = item[0].strip()
        value = item[1].strip()
        args_dict[name] = value
        if debugMode:
            print(name + " = " + value)

    if debugMode:
        print("All arguments processes.")

    return args_dict


def addConnection(list, voice="none"):
    global mainDict
    global connectionTypes
    global connectionID

    connectionID += 1

    if debugMode:
        print("Adding new connection...")
        print("-----")

    output_module = list[0].lower().strip()
    output_port = list[1].lower().strip()

    if debugMode:
        print("Output module: " + output_module)
        print("Output port: " + output_port)

    try:
        connection_type = connectionTypes[list[2].lower()]
        if debugMode:
            print("Matched connection type: " + connection_type)
    except KeyError:
        print("Invalid connection: " + list[2])
        connection_type = "cv"

    input_module = list[3].lower().strip()
    input_port = list[4].lower().strip()

    if list[5] is not None:
        arguments = parseArguments(list[5])
    else:
        arguments = {}

    if debugMode:
        print("Input module: " + input_module)
        print("Input port: " + output_port)

    checkModuleExistance(output_module, output_port, "out")
    checkModuleExistance(input_module, input_port, "in")

    if debugMode:
        print("Appending output and input connections to mainDict...")

    output_dict = {
        "input_module": input_module,
        "input_port": input_port,
        "connection_type": connection_type,
        "voice": voice,
        "id": connectionID}

    input_dict = {
        "output_module": output_module,
        "output_port": output_port,
        "connection_type": connection_type,
        "voice": voice,
        "id": connectionID}

    for key in arguments:
        output_dict[key] = arguments[key]
        input_dict[key] = arguments[key]

    mainDict["modules"][output_module]["connections"]["out"][output_port].append(
        output_dict)
    mainDict["modules"][input_module]["connections"]["in"][input_port] = input_dict
    if debugMode:
        print("-----")


def checkModuleExistance(module, port="port", direction=""):
    global mainDict

    if debugMode:
        print("Checking if module already existing in main dictionary: " + module)

    # Check if module exists in main dictionary
    if module not in mainDict["modules"]:
        mainDict["modules"][module] = {
            "parameters": {},
            "connections": {"out": {}, "in": {}}
        }

    # If it exists, check if the port exists
    if direction == "in":
        if port not in mainDict["modules"][module]["connections"]["in"]:
            mainDict["modules"][module]["connections"]["in"][port] = []

    if direction == "out":
        if port not in mainDict["modules"][module]["connections"]["out"]:
            mainDict["modules"][module]["connections"]["out"][port] = []


def addParameter(module, name, value):
    checkModuleExistance(module)
    # Add parameter to mainDict
    if debugMode:
        print("Adding parameter: " + module + " - " + name + " - " + value)
    mainDict["modules"][module]["parameters"][name] = value


def addComment(value):
    mainDict["comments"].append(value)


def askCommand(command=None):
    global one_shot_command
    if one_shot_command:
        command = one_shot_command
    if not command:
        command = input("> ").lower().strip()

    if command == "module":
        detailModule()
    elif command == "modules":
        detailModule(all=True)
    elif command == "print":
        printDict()
    elif command == "export":
        exportJSON()
    elif command == "connections":
        printConnections()
    elif command == "graph":
        graphviz()
    else:
        print("Invalid command, please try again.")

    if one_shot_command:
        return
    askCommand()

def _print_module(module):
    global mainDict, quiet
    print("-------")
    print("Showing information for module: " + module.upper())
    print()
    print("Inputs:")
    for c in mainDict["modules"][module]["connections"]["in"]:
        keyvalue = mainDict["modules"][module]["connections"]["in"][c]
        print(keyvalue["output_module"].title() + " (" + keyvalue["output_port"].title(
        ) + ") > " + c.title() + " - " + keyvalue["connection_type"].title())
    print()

    print("Outputs:")
    for x in mainDict["modules"][module]["connections"]["out"]:
        port = mainDict["modules"][module]["connections"]["out"][x]
        for c in port:
            keyvalue = c
            print(x.title() + " > " + keyvalue["input_module"].title() + " (" + keyvalue["input_port"].title(
            ) + ") " + " - " + keyvalue["connection_type"].title() + " - " + keyvalue["voice"])
    print()

    print("Parameters:")
    for p in mainDict["modules"][module]["parameters"]:
        value = mainDict["modules"][module]["parameters"][p]
        print(p.title() + " = " + value)
    print()

    if not quiet: print("-------")

def detailModule(all=False):
    global mainDict
    if not all:
        module = input("Enter module name: ").lower()
        if module in mainDict["modules"]:
            _print_module(module)
    else:
        for module in mainDict["modules"]:
            _print_module(module)


def printConnections():
    print()
    print("Printing all connections by type...")
    print()

    for ctype in connectionTypes:
        ctype_name = connectionTypes[ctype]
        print("Connection type: " + ctype_name)
        # For each module
        for module in mainDict["modules"]:
            # Get all outgoing connections:
            connections = mainDict["modules"][module]["connections"]["out"]
            for c in connections:
                connection = connections[c]
                for subc in connection:
                    # print(connection)
                    if subc["connection_type"] == ctype_name:
                        print(module.title(
                        ) + " > " + subc["input_module"].title() + " (" + subc["input_port"].title() + ") ")
        print()


def exportJSON():
    # Exports mainDict as json file
    # name = filename.split(".")[0]
    # filepath = getFilePath(name + '.json')
    # print("Exporting dictionary as file: " + filepath)
    # with open(filepath, 'w') as fp:
    #     json.dump(mainDict, fp)
    print(json.dumps(mainDict))


def graphviz():
    global quiet, direction
    linetypes = {
        "audio": {"style": "bold"},
        "cv": {"color": "gray"},
        "gate": {"color": "red", "style": "dashed"},
        "trigger": {"color": "orange", "style": "dashed"},
        "pitch": {"color": "blue"},
        "clock": {"color": "purple", "style": "dashed"}
    }
    if direction == "DN":
        rank_dir_token = "rankdir = BT;\n"
        from_token = ":s  -> "
        to_token = ":n  "
    else:
        rank_dir_token = "rankdir = LR;\n"
        from_token = ":e  -> "
        to_token = ":w  "
    if not quiet:
        print("Generating signal flow code for GraphViz.")
        print("Copy the code between the line break and paste it into https://dreampuf.github.io/GraphvizOnline/ to download a SVG / PNG chart.")
    conn = []
    total_string = ""
    if not quiet: print("-------------------------")
    print("digraph G{\n" + rank_dir_token + "splines = polyline;\nordering=out;")
    total_string += "digraph G{\n" + rank_dir_token + "splines = polyline;\nordering=out;\n"
    for module in sorted(mainDict["modules"]):
        # Get all outgoing connections:
        outputs = mainDict["modules"][module]["connections"]["out"]
        module_outputs = ""
        out_count = 0
        for out in sorted(outputs):
            out_count += 1
            out_formatted = "_" + re.sub('[^A-Za-z0-9]+', '', out)
            module_outputs += "<" + out_formatted + "> " + out.upper()
            if out_count < len(outputs.keys()):
                module_outputs += " | "
            connections = outputs[out]
            for c in connections:
                line_style_array = []
                graphviz_parameters = [
                    "color", "weight", "style", "arrowtail", "dir"]
                for param in graphviz_parameters:
                    if param in c:
                        line_style_array.append(param + "=" + c[param])
                    elif param in linetypes[c["connection_type"]]:
                        line_style_array.append(
                            param + "=" + linetypes[c["connection_type"]][param])
                if len(line_style_array) > 0:
                    line_style = "[" + ', '.join(line_style_array) + "]"
                else:
                    line_style = ""
                in_formatted = "_" + \
                    re.sub('[^A-Za-z0-9]+', '', c["input_port"])
                connection_line = module.replace(" ", "") + ":" + out_formatted + from_token + \
                    c["input_module"].replace(
                        " ", "") + ":" + in_formatted + to_token + line_style
                conn.append([c["input_port"], connection_line])

        # Get all incoming connections:
        inputs = mainDict["modules"][module]["connections"]["in"]
        module_inputs = ""
        in_count = 0
        for inp in sorted(inputs):
            inp_formatted = "_" + re.sub('[^A-Za-z0-9]+', '', inp)
            in_count += 1
            module_inputs += "<" + inp_formatted + "> " + inp.upper()
            if in_count < len(inputs.keys()):
                module_inputs += " | "

        # Get all parameters:
        params = mainDict["modules"][module]["parameters"]
        module_params = ""
        param_count = 0
        for par in sorted(params):
            param_count += 1
            module_params += par.title() + " = " + params[par]
            if param_count < len(params.keys()):
                module_params += r'\n'

        # If module contains parameters
        if module_params != "":
            # Add them below module name
            middle = "{{" + module.upper() + "}|{" + module_params + "}}"
        else:
            # Otherwise just display module name
            middle = module.upper()

        final_box = module.replace(
            " ", "") + "[label=\"{ {" + module_inputs + "}|" + middle + "| {" + module_outputs + "}}\"  shape=Mrecord]"
        print(final_box)
        total_string += final_box + "; "

    # Print Connections
    for c in sorted(conn):
        print(c[1])
        total_string += c[1] + "; "

    if len(mainDict["comments"]) != 0:
        format_comments = ""
        comments_count = 0
        for comment in mainDict["comments"]:
            comments_count += 1
            format_comments += "{" + comment + "}"
            if comments_count < len(mainDict["comments"]):
                format_comments += "|"
        format_comments = "comments[label=<{{{<b>PATCH COMMENTS</b>}|" + format_comments + "}}>  shape=Mrecord]"
        print(format_comments)

    print("}")
    total_string += "}"

    if not quiet:
        print("-------------------------")
        print()
    return total_string


def printDict():
    global mainDict
    for key in mainDict["modules"]:
        print(key.title() + ": " + str(mainDict["modules"][key]))


if __name__ == "__main__":
    initial_print()
    parseFile(filename)
    askCommand()
Download .txt
gitextract_l6elw5r5/

├── Examples/
│   ├── patch1.txt
│   ├── patch2.txt
│   └── syncpll.txt
├── LICENSE
├── README.md
└── patchbook.py
Download .txt
SYMBOL INDEX (17 symbols across 1 files)

FILE: patchbook.py
  function initial_print (line 77) | def initial_print():
  function get_script_path (line 90) | def get_script_path():
  function getFilePath (line 95) | def getFilePath(filename):
  function parseFile (line 107) | def parseFile(filename):
  function regexLine (line 126) | def regexLine(line):
  function parseArguments (line 223) | def parseArguments(args):
  function addConnection (line 246) | def addConnection(list, voice="none"):
  function checkModuleExistance (line 315) | def checkModuleExistance(module, port="port", direction=""):
  function addParameter (line 338) | def addParameter(module, name, value):
  function addComment (line 346) | def addComment(value):
  function askCommand (line 350) | def askCommand(command=None):
  function _print_module (line 376) | def _print_module(module):
  function detailModule (line 405) | def detailModule(all=False):
  function printConnections (line 416) | def printConnections():
  function exportJSON (line 438) | def exportJSON():
  function graphviz (line 448) | def graphviz():
  function printDict (line 566) | def printDict():
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (30K chars).
[
  {
    "path": "Examples/patch1.txt",
    "chars": 502,
    "preview": "VOICE 1:\n\t- Metropolis (Pitch) p> Braids (1voct) [weight=3]\n\t- Metropolis (Gate) g> Function (Trigger)\n\t- Braids (Out) -"
  },
  {
    "path": "Examples/patch2.txt",
    "chars": 596,
    "preview": "VOICE 1:\n\t- Metropolis (Pitch) p> Lizard2 (CV)\n\t- Lizard2 (Out 2) -> Multifilter (Input)\n\t- Multifilter (LPF) -> Optomix"
  },
  {
    "path": "Examples/syncpll.txt",
    "chars": 882,
    "preview": "VOICE 1:\n\n\t- Metropolis (Pitch) p> Aether VCO (CV)\n\t- Metropolis (Gate) g> Maths (Ch 1 Trigger)\n\t- Metropolis (Gate) g> "
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2017 Spektro Audio\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 6858,
    "preview": "![Patchbook Logo](/Images/patchbook-logo.jpg)\n\n# About PatchBook:\n\nPatchBook is a markup language and parser for writing"
  },
  {
    "path": "patchbook.py",
    "chars": 18340,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nPATCHBOOK MARKUP LANGUAGE & PARSER\nCREATED BY SPEKTRO AUDIO\nhttp://spektroaudio.com/\n\"\"\"\n\nimp"
  }
]

About this extraction

This page contains the full source code of the SpektroAudio/Patchbook GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (27.6 KB), approximately 7.4k tokens, and a symbol index with 17 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!