Skip to content
Merged
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
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ the secrets exist, and attaches zips + SHA256s to a draft release.
C++17), as TapTools-Max does.
- **Keep in sync when behavior changes:** `ReadMe.md`, the object's min metadata
(`MIN_DESCRIPTION`, argument and message descriptions — min regenerates
`docs/tap.python~.maxref.xml` from them whenever the external is newer, so never hand-edit the
page; commit the regenerated one), `help/tap.python~.maxhelp`, the examples and their notebook
`docs/tap.python~.maxref.xml` from them when Max loads an external newer than the page, so never
hand-edit the page: rebuild, run Max once — `runtime-tests/run.py` says when the page was
rewritten — and commit it), `help/tap.python~.maxhelp`, the examples and their notebook
(committed executed), and the plan.
- **Implement from documentation and published sources only** — the CPython C-API docs, the Max SDK
docs — never by reverse-engineering another product.
15 changes: 11 additions & 4 deletions docs/PRODUCTION-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,8 @@ passed; a third ran the soak (6.2) and measured performance (6.3). To continue:
only to check a universal build), then `cmake -S . -B build -DCMAKE_BUILD_TYPE=Release &&
cmake --build build && ctest --test-dir build`. The external lands in `externals/`. When Max
loads an external newer than `docs/tap.python~.maxref.xml`, min rewrites the page from the
object's metadata; the committed one was generated against the mock kernel, so if Max's
differs, commit Max's. (A symlinked package works for externals, but Max loads a package's
object's metadata (6.8: the build dates the `.mxo` for it, and `run.py` says when it happened);
commit Max's page when it differs. (A symlinked package works for externals, but Max loads a package's
*extensions* only from a real folder — why `run.py` installs the harness as a copy.)
2. *5.2 — the help patcher.* Open `help/tap.python~.maxhelp`: its new boxes were added by hand
(as JSON, in Max's layout), so check they sit sensibly and every message box works, then
Expand Down Expand Up @@ -419,12 +419,19 @@ passed; a third ran the soak (6.2) and measured performance (6.3). To continue:
nothing, and the external's own reload line is gone. Errors particular to an instance are
unchanged. Pinned by a core test (two processors, a change, an unchanged reload) and a runtime
test (five objects in Max: the class's diagnostic once per run of the file).
- [ ] **6.8 The reference page from Max** — runbook step 1 expects min to rewrite
- [x] **6.8 The reference page from Max** — runbook step 1 expects min to rewrite
`docs/tap.python~.maxref.xml` when Max loads an external newer than it; in the Mac sessions it
did not. Max's standard output had "file not found" and "failed to get date modified" lines at
start-up — probably min's `doc_update` failing to resolve a path, not yet shown to come from this
object. Find out why, and whether the committed page (generated against the mock kernel) is the
one Max would write.
one Max would write. *Done:* min's `doc_update` dates the external by its `.mxo` folder, and a
rebuild changes only the files inside it — the checkout's folder dated from its first build
(2026-08-05), older than the page, so the page never looked stale; a freshly unzipped release
has a new folder, which is why Max rewrote the installed package's page. The macOS build now
touches the folder after each link (a Windows `.mxe64` is one file, dated by its link already),
and `run.py` says when Max has rewritten the page, to commit it. Max's page matches the committed
one but for the description, which predated 2.4's text — now committed. The start-up lines are
not this object's: they still appear while its page is written.
- [ ] **6.9 The help patcher and `numpy_allpass.py`** — the help patcher points to the `numpy_gain`
and `allpass` examples but not to `numpy_allpass`, the one that shows what the block path is for;
add it in Max (and re-save), perhaps with the measured comparison.
Expand Down
2 changes: 1 addition & 1 deletion docs/tap.python~.maxref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<c74object name='tap.python~' category='programming'>

<digest>Process audio with a Python class</digest>
<description>Process audio with a Python class. The argument names a file in the package's python folder, which must define a class of the same name. The class's type-annotated public fields become attributes (int, float, bool and str; Optional allowed), and its public methods become messages called according to their signatures, with methods named int, float, symbol and bang answering those standard messages. Its process() method runs on the signal: once per signal vector when annotated with numpy arrays, or once per sample when annotated with float. An optional prepare(sample_rate, vector_size) method receives the audio settings before audio starts and whenever they change. The file is watched and reloaded when saved, keeping attribute values; errors are printed to the Max console and never take Max down. </description>
<description>Process audio with a Python class. The argument names a file in the package's python folder, which must define a class of the same name. The class's type-annotated public fields become attributes (int, float, bool and str; Optional allowed), and its public methods become messages called according to their signatures, with methods named int, float, symbol and bang answering those standard messages. Its process() method runs on the signal: once per signal vector when annotated with numpy arrays, or once per sample when annotated with float; its parameters are the object's signal inlets, and its return hint its outlets (a tuple of values for more than one). An optional prepare(sample_rate, vector_size) method receives the audio settings before audio starts and whenever they change. The file is watched and reloaded when saved, keeping attribute values; a save that changes process()'s inputs or outputs changes the inlets and outlets to match, keeping the patch cords of those that stay. Errors are printed to the Max console and never take Max down. </description>


<!--METADATA-->
Expand Down
6 changes: 6 additions & 0 deletions runtime-tests/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
SUPPORT = PACKAGE / "support"
SUPPORT_ASIDE = PACKAGE / "support.maxtest-aside"
EXTERNAL = PACKAGE / "externals" / "tap.python~.mxo"
# The reference page min rewrites from the object's descriptions (plan 6.8)
REFPAGE = ROOT / "docs" / "tap.python~.maxref.xml"
OSCAR = HARNESS / "extensions" / "oscar.mxo"

# Must match misc/max-test-config.json: Max listens on one port and sends to the other.
Expand Down Expand Up @@ -556,6 +558,10 @@ def main() -> int:
link.unlink(missing_ok=True)

print()
if PACKAGE == ROOT and REFPAGE.exists() and subprocess.run(
["git", "-C", str(ROOT), "diff", "--quiet", "--", str(REFPAGE)]).returncode == 1:
# min rewrites it when Max loads an external newer than it (plan 6.8)
print(f"Max rewrote {REFPAGE.relative_to(ROOT)} from the object's descriptions: review and commit it.")
if failures:
print(f"{len(failures)} failure(s):")
for failure in failures:
Expand Down
6 changes: 6 additions & 0 deletions source/projects/tap.python_tilde/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ if (APPLE)
string(REGEX REPLACE "[^A-Za-z0-9.-]" "-" BUNDLE_IDENTIFIER "${PROJECT_NAME}")
set_target_properties(${PROJECT_NAME} PROPERTIES
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${AUTHOR_DOMAIN}.${BUNDLE_IDENTIFIER}")

# min rewrites docs/tap.python~.maxref.xml when Max loads an external newer than the page, and
# dates the external by its .mxo folder — which a rebuild leaves alone (only files inside it
# change), so the page was never rewritten from a checkout (plan 6.8). Date the folder with
# each link.
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND touch "$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>")
endif ()


Expand Down
Loading