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
178 changes: 131 additions & 47 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,89 @@ 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):
"""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.
"""

# 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
)
else:
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):
"""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, returns an empty object.
"""

# 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
)
else:
return {} # can't traverse into abstraction
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 +335,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 +347,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 @@ -373,54 +429,82 @@ def get_parameters_string_block(patcher: dict) -> str:
Non-overridden parameter attributes are already shown with the parameter objects.
"""
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)}"
object_names = index
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}"

if index.startswith("obj"):
id_tokens = index.split("::")
param_properties = get_param_properties_from_ids_recursive(
id_tokens, patcher["boxes"]
)

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"]
)

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>"

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
8 changes: 4 additions & 4 deletions maxdiff/tests/test_baselines/FrozenTest.amxd.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ MIDI Effect Device
-------------------
Device is frozen
----- Contents -----
Test.amxd: 36531 bytes, modified at 2026/01/28 10:27:01 UTC <= Device
ParamAbstraction.maxpat: 956 bytes, modified at 2026/01/28 10:25:56 UTC, 2 instances
MyAbstraction.maxpat: 1369 bytes, modified at 2026/01/28 10:26:01 UTC, 6 instances
AbstractionWithParameter.maxpat: 1569 bytes, modified at 2024/05/24 13:59:36 UTC, 2 instances
Test.amxd: 57392 bytes, modified at 2026/02/26 15:45:49 UTC <= Device
MyAbstraction.maxpat: 2027 bytes, modified at 2026/02/26 15:38:57 UTC, 6 instances
ParamAbstraction.maxpat: 1491 bytes, modified at 2026/02/26 15:40:50 UTC, 2 instances
AbstractionWithParameter.maxpat: 1334 bytes, modified at 2026/02/26 15:38:57 UTC, 2 instances
hz-icon.svg: 484 bytes, modified at 2024/05/24 13:59:36 UTC, 3 instances
beat-icon.svg: 533 bytes, modified at 2024/05/24 13:59:36 UTC, 3 instances
fpic.png: 7094 bytes, modified at 2024/05/24 13:59:36 UTC, 5 instances
Expand Down
Loading