Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion maxdiff/frozen_device_printer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from freezing_utils import *
from get_frozen_stats import get_frozen_stats, get_used_files
from patch_printer import get_parameters_string_block


def print_frozen_device(data: bytes) -> str:
Expand All @@ -15,10 +16,24 @@ def print_frozen_device(data: bytes) -> str:
if footer_size != len(footer_data):
return "Error parsing footer data; recorded size does not match actual size"

frozen_string = "Device is frozen\n----- Contents -----\n"
frozen_string = "Device is frozen\n"
frozen_string += "----- Contents -----\n"

footer_entries = parse_footer(footer_data[8:])
device_entries = get_device_entries(data, footer_entries)

bundled_patchers = {}
for entry in device_entries[1:]:
file_name = str(entry.get("file_name", ""))
if file_name.endswith(".maxpat"):
patcher = get_patcher_dict(entry)
if patcher:
bundled_patchers[file_name] = patcher

main_patcher = get_patcher_dict(device_entries[0])
if "parameters" in main_patcher:
frozen_string += get_parameters_string_block(main_patcher, bundled_patchers)

used_files = get_used_files(device_entries)

i = 0
Expand Down
202 changes: 154 additions & 48 deletions maxdiff/patch_printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,7 @@ def get_box_text(box: dict) -> str:
return boxtext


def get_object_names_from_ids_recursive(
id_hierarchy: list, boxes_parent: list, indent: int = 0
):
def get_object_names_from_ids_recursive(id_hierarchy: list, boxes_parent: list):
"""Translates an object id hierarchy string in the form of "obj-n::obj-m:: etc" to a string
in the form of "<objectname1>/<objectname2/ etc", replacing the object ids with the textual
representation of these objects.
Expand All @@ -234,31 +232,113 @@ def get_object_names_from_ids_recursive(
# every entry of "boxes_parent" has a single item "box"
boxes = list(map(lambda val: val["box"], boxes_parent))

id_to_check = id_hierarchy[0] if isinstance(id_hierarchy, list) else id_hierarchy
name = ""
for box in boxes:
if "id" in box:
if id_to_check == box["id"]:
if id_hierarchy[0] == box["id"]:
name = get_box_text(box)
if "embed" in box and box["embed"] == 1:
name += " <embedded>"
name = f"[{name}]"

if isinstance(id_hierarchy, list) and len(id_hierarchy) > 1:
id_hierarchy.pop(0)
if len(id_hierarchy) > 1:
if "patcher" in box:
subpatcher_boxes = box["patcher"]["boxes"]
name += f"/{get_object_names_from_ids_recursive(id_hierarchy, subpatcher_boxes, indent + 1)}"
name += f"/{get_object_names_from_ids_recursive(id_hierarchy[1:], subpatcher_boxes)}"
else:
for id_level in id_hierarchy:
for id_level in id_hierarchy[1:]:
name += f"/[{id_level}]"

if name == "":
name = f"[{id_to_check}]"
name = f"[{id_hierarchy[0]}]"

return name


def get_param_properties_from_ids_recursive(
id_hierarchy: list, boxes_parent: list, bundled_patchers: dict = {}
):
"""Travels an object id hierarchy string in the form of "obj-n::obj-m:: etc" and finds properties
contained in valueof in saved_attribute_attributes.

If an object id cannot be found in the patch, for instance because it refers to an object
inside an abstraction, returns an empty object.
bundled_patchers maps abstraction filenames to their parsed patcher dicts, allowing traversal
into non-embedded bpatchers when the source files are available.
"""

# every entry of "boxes_parent" has a single item "box"
boxes = list(map(lambda val: val["box"], boxes_parent))

for box in boxes:
if "id" in box and id_hierarchy[0] == box["id"]:
if len(id_hierarchy) > 1:
if "patcher" in box:
subpatcher_boxes = box["patcher"]["boxes"]
return get_param_properties_from_ids_recursive(
id_hierarchy[1:], subpatcher_boxes, bundled_patchers
)
else:
abstraction_file = box.get("name") or (get_box_text(box) + ".maxpat")
if bundled_patchers and abstraction_file in bundled_patchers:
bundled_boxes = bundled_patchers[abstraction_file].get(
"boxes", []
)
return get_param_properties_from_ids_recursive(
id_hierarchy[1:], bundled_boxes, bundled_patchers
)
return {} # can't traverse into abstraction
else:
saved_attrs = box.get("saved_attribute_attributes", {})
return saved_attrs.get("valueof", {})

return {} # object not found


def get_param_annotations_from_ids_recursive(
id_hierarchy: list, boxes_parent: list, bundled_patchers: dict = {}
):
"""Travels an object id hierarchy string in the form of "obj-n::obj-m:: etc" and finds the
parameter's annotations saved as box properties

If an object id cannot be found in the patch, for instance because it refers to an object
inside an abstraction, we look in the bundled_patchers dict (only available when parsing
a frozen device). This dict maps abstraction filenames to their parsed patcher dicts, allowing
traversal into external abstractions.
"""

# every entry of "boxes_parent" has a single item "box"
boxes = list(map(lambda val: val["box"], boxes_parent))

for box in boxes:
if "id" in box and id_hierarchy[0] == box["id"]:
if len(id_hierarchy) > 1:
if "patcher" in box:
subpatcher_boxes = box["patcher"]["boxes"]
return get_param_annotations_from_ids_recursive(
id_hierarchy[1:], subpatcher_boxes, bundled_patchers
)
else:
abstraction_file = box.get("name") or (get_box_text(box) + ".maxpat")
if bundled_patchers and abstraction_file in bundled_patchers:
bundled_boxes = bundled_patchers[abstraction_file].get(
"boxes", []
)
return get_param_annotations_from_ids_recursive(
id_hierarchy[1:], bundled_boxes, bundled_patchers
)
return {} # abstraction file not found in available bundled_patchers
else:
annotation = box.get("annotation", {})
annotation_name = box.get("annotation_name", {})
return {
"annotation": annotation,
"annotation_name": annotation_name,
}

return {} # object not found


def get_properties_to_print(
box_or_patcher: dict, default: dict, skip_properties: list[str]
) -> dict:
Expand All @@ -279,7 +359,7 @@ def get_properties_to_print(
continue

if key == "saved_attribute_attributes":
# We take the attributes out or saved_attribute_attributes and present them as properties
# We take the attributes out of saved_attribute_attributes and present them as properties
attributes = get_saved_attribute_attributes(value)
attributes_to_print = get_properties_to_print(
attributes, default, skip_properties
Expand All @@ -291,7 +371,7 @@ def get_properties_to_print(
continue

if key == "saved_object_attributes":
# We take the attributes out or saved_object_attributes and present them as properties
# We take the attributes out of saved_object_attributes and present them as properties
attributes = get_saved_object_attributes(value)
attributes_to_print = get_properties_to_print(
attributes, default, skip_properties
Expand Down Expand Up @@ -368,59 +448,85 @@ def get_saved_object_attributes(value: dict) -> dict:
return result


def get_parameters_string_block(patcher: dict) -> str:
def get_parameters_string_block(patcher: dict, bundled_patchers: dict = {}) -> str:
"""Produce a string that lists overridden parameters in a patcher.
Non-overridden parameter attributes are already shown with the parameter objects.
bundled_patchers maps abstraction filenames to their parsed patcher dicts; when provided,
properties and annotations for bpatcher parameters are resolved from the bundled files.
"""
parameters = patcher["parameters"]

param_overrides = (
parameters["parameter_overrides"] if "parameter_overrides" in parameters else {}
)

param_banks = parameters["parameterbanks"] if "parameterbanks" in parameters else {}

parameters_string = ""
for bankindex, bank in parameters.items():
if bankindex in ["parameter_overrides", "parameterbanks"]:
for index, item in parameters.items():
if index in ["parameter_overrides", "parameterbanks"]:
continue

parsed_key = bankindex
if bankindex.startswith("obj"):
id_tokens = bankindex.split("::")
parsed_key = get_object_names_from_ids_recursive(id_tokens, patcher["boxes"])

parameters_string += f"\t{parsed_key}: {bank}"

if (
"parameter_overrides" in parameters
and bankindex in parameters["parameter_overrides"]
):
override = parameters["parameter_overrides"][bankindex]
override_print = [
override.get("parameter_longname", "-"),
override.get("parameter_shortname", "-"),
str(override.get("parameter_linknames", "-")),
]

for key, value in override.items():
if key in [
"parameter_longname",
"parameter_shortname",
"parameter_linknames",
]:
continue
override_print.append(f"{key}: {value}")

parameters_string += f" > override > {str(override_print)}"
if index.startswith("obj"):
id_tokens = index.split("::")
object_names = get_object_names_from_ids_recursive(
id_tokens, patcher["boxes"]
)
parameters_string += f"\t{object_names}: {item}"

param_properties = get_param_properties_from_ids_recursive(
id_tokens, patcher["boxes"], bundled_patchers
)
for key, value in param_properties.items():
key_text = (
key[len("parameter_") :] if key.startswith("parameter_") else key
)
parameters_string += f"\n\t\t{key_text}: {value}"

param_annotations = get_param_annotations_from_ids_recursive(
id_tokens, patcher["boxes"], bundled_patchers
)
if param_annotations != {}:
parameters_string += "\n\t\t____Info____"
parameters_string += f"\n\t\t{param_annotations['annotation_name'] if 'annotation_name' in param_annotations and param_annotations['annotation_name'] != {} else '<no info title>'}"
if (
"annotation" in param_annotations
and param_annotations["annotation"] != {}
):
value_text = "\n\t\t".join(
str(param_annotations["annotation"]).splitlines()
)
parameters_string += f"\n\t\t{value_text}"
else:
parameters_string += "\n\t\t<no info text>"
else:
parameters_string += f"\t{index}: {item}"

if index in param_overrides:
param_override = param_overrides[index]

parameters_string += f"\n\toverrides:"
for key, value in param_override.items():
key_text = (
key[len("parameter_") :] if key.startswith("parameter_") else key
)
parameters_string += f"\n\t\t{key_text}: {value}"

parameters_string += "\n"

if "parameterbanks" in parameters:
if param_banks != {}:
parameters_string += "banks:\n"
for bankindex, bank in parameters["parameterbanks"].items():
for key, value in bank.items():
for index, item in param_banks.items():
for key, value in item.items():
parameters_string += "\t"
if key == "index":
parameters_string += f"{value}:"
elif key == "name":
parameters_string += f"({value})" if value != "" else ""
elif key == "parameters":
parameters_string += f"encoders: {bank['parameters']}"
parameters_string += f"encoders: {item['parameters']}"
elif key == "buttons":
parameters_string += f"buttons: {bank['buttons']}"
parameters_string += f"buttons: {item['buttons']}"
else:
parameters_string += f"{value}"
parameters_string += "\n"
Expand Down
Loading
Loading