Skip to content

Commit

Permalink
Add statistics for frozen devices
Browse files Browse the repository at this point in the history
Adds first statistic: total amount of object instances
  • Loading branch information
Mattijs Kneppers committed Jul 4, 2024
1 parent c9e554f commit d5b0fb4
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 2 deletions.
2 changes: 2 additions & 0 deletions maxdiff/frozen_device_printer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from freezing_utils import *
from get_frozen_stats import get_frozen_stats


def print_frozen_device(data: bytes) -> str:
Expand All @@ -20,6 +21,7 @@ def print_frozen_device(data: bytes) -> str:
device_entries = get_device_entries(data, footer_entries)
for entry in device_entries:
frozen_string += entry["description"] + "\n"
frozen_string += get_frozen_stats(device_entries)
return frozen_string


Expand Down
125 changes: 125 additions & 0 deletions maxdiff/get_frozen_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import json
from freezing_utils import footer_entry_with_data


def get_frozen_stats(entries: list[footer_entry_with_data]):
"""Returns statistics for this device"""

device_data = entries[0]["data"] # the first entry is always the device file

abstractions = [item for item in entries if item["file_name"].endswith(".maxpat")]

# cache names of known abstractions
file_names = [item["file_name"] for item in abstractions]

patch = get_patcher_dict(device_data)
object_count = count_objects(patch, abstractions, file_names)

summary = "\n"
summary += "Total - Counting every abstraction instance - Indicates loading time\n"
summary += f" Object instances: {object_count}\n"
summary += " Connections:\n"
summary += "Unique - Counting abstractions once - Indicates maintainability\n"
summary += " Object instances:\n"
summary += " Connections:\n"
return summary


def count_objects(patcher, entries: list[dict], file_names: list[str]):
"""Recursively counts all object instances in this patcher,
inluding in every instance of its dependencies"""
boxes = patcher["boxes"]
count = 0
for box_entry in boxes:
box = box_entry["box"]
count += 1

if "patcher" in box:
patch = box["patcher"]
if box.get("maxclass") == "bpatcher" and box.get("embed") == 1:
# get embedded bpatcher count
count += count_objects(patch, entries, file_names)
else:
# get subpatcher count
count += count_objects(patch, entries, file_names)
else:
abstraction_name = get_abstraction_name(box, file_names)
if abstraction_name is None:
continue

abstraction = [
item for item in entries if item["file_name"] == abstraction_name
][0]
abstraction_data = abstraction["data"]
abstraction_patch = get_patcher_dict(abstraction_data)
abstraction_count = count_objects(abstraction_patch, entries, file_names)

if "text" in box and box["text"].startswith("poly~"):
# get poly abstraction count
voice_count = int(box["text"].split(" ")[2])
count += abstraction_count * voice_count
else:
# get abstraction count
count += abstraction_count

return count


def get_abstraction_name(box, file_names: list[str]):
"""
Checks if this box is an abstraction and if so, return the name of the abstraction file.
- returns None if this is not an abstraction
- throws error if an abstraction name was expected but it was not found in the list of known names
"""
if "text" in box:
if box["text"].startswith("poly~"):
name = box["text"].split(" ")[1] + ".maxpat"
if name in file_names:
return name
else:
raise ValueError(
"poly~ pointing to file that is not known as a dependency: " + name
)
else:
name = box["text"].split(" ")[0] + ".maxpat"
if name in file_names:
return name

if box.get("maxclass") == "bpatcher" and box.get("embed") != 1:
if box.get("name") in file_names:
return box["name"]
else:
raise ValueError(
"Non-embedded bpatcher pointing to file that is not known as a dependency: "
+ box["name"]
)

return None


def get_patcher_dict(patch_data):
"""Returns the dict that is represents the given patcher data.
Throws errors if parsing fails"""
device_data_text = ""

try:
if patch_data[len(patch_data) - 1] == 0:
device_data_text = patch_data[: len(patch_data) - 1].decode("utf-8")
else:
device_data_text = patch_data.decode("utf-8")
except Exception as e:
print(f"Error getting device patch data as text: {e}")

if device_data_text == "":
print("Device has no data")

patcher_dict = {}
try:
patcher_dict = json.loads(device_data_text)
except ValueError as e:
print(f"Error parsing device patch data as json: {e}")

if not "patcher" in patcher_dict:
print("Device content does not seem to be a patcher")

return patcher_dict["patcher"]
10 changes: 10 additions & 0 deletions maxdiff/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ def test_parse_frozen_device(self):
actual = parse(test_path)
self.assertEqual(expected, actual)

def test_parse_frozen_device(self):
self.maxDiff = None

expected_path, test_path = get_test_path_files("StatsTest.amxd")

with open(expected_path, mode="r") as expected_file:
expected = expected_file.read()
actual = parse(test_path)
self.assertEqual(expected, actual)

def test_parse_maxpat(self):
self.maxDiff = None

Expand Down
7 changes: 7 additions & 0 deletions maxdiff/tests/test_baselines/FrozenTest.amxd.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ hz-icon.svg: 484 bytes, modified at 2024/05/24 13:59:36 UTC
beat-icon.svg: 533 bytes, modified at 2024/05/24 13:59:36 UTC
fpic.png: 7094 bytes, modified at 2024/05/24 13:59:36 UTC
collContent.txt: 8 bytes, modified at 2024/05/24 13:59:36 UTC

Total - Counting every abstraction instance - Indicates loading time
Object instances: 44
Connections:
Unique - Counting abstractions once - Indicates maintainability
Object instances:
Connections:
13 changes: 13 additions & 0 deletions maxdiff/tests/test_baselines/StatsTest.amxd.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Audio Effect Device
-------------------
Device is frozen
----- Contents -----
SummaryTest.amxd: 6376 bytes, modified at 2024/06/27 07:39:44 UTC
MyAbstraction.maxpat: 2015 bytes, modified at 2024/06/24 07:11:15 UTC

Total - Counting every abstraction instance - Indicates loading time
Object instances: 27
Connections:
Unique - Counting abstractions once - Indicates maintainability
Object instances:
Connections:
25 changes: 23 additions & 2 deletions maxdiff/tests/test_files/MyAbstraction.maxpat
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"fileversion" : 1,
"appversion" : {
"major" : 8,
"minor" : 5,
"revision" : 5,
"minor" : 6,
"revision" : 2,
"architecture" : "x64",
"modernui" : 1
}
Expand Down Expand Up @@ -39,6 +39,18 @@
"subpatcher_template" : "",
"assistshowspatchername" : 0,
"boxes" : [ {
"box" : {
"id" : "obj-3",
"maxclass" : "button",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "bang" ],
"parameter_enable" : 0,
"patching_rect" : [ 113.0, 60.0, 24.0, 24.0 ]
}

}
, {
"box" : {
"comment" : "",
"id" : "obj-2",
Expand Down Expand Up @@ -67,6 +79,15 @@
"lines" : [ {
"patchline" : {
"destination" : [ "obj-2", 0 ],
"order" : 1,
"source" : [ "obj-1", 0 ]
}

}
, {
"patchline" : {
"destination" : [ "obj-3", 0 ],
"order" : 0,
"source" : [ "obj-1", 0 ]
}

Expand Down
Binary file added maxdiff/tests/test_files/StatsTest.amxd
Binary file not shown.
1 change: 1 addition & 0 deletions maxdiff/tests/test_rewrite_baselines.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def run():
rewrite_file("Test.amxd")
rewrite_file("EncryptedTest.amxd")
rewrite_file("FrozenTest.amxd")
rewrite_file("StatsTest.amxd")
rewrite_file("Test.maxpat")
rewrite_file("Test Project/Zipped.als")
rewrite_file("Test Project/Test.als")
Expand Down

0 comments on commit d5b0fb4

Please sign in to comment.