Skip to content

pyfdn-system: one JSON document format for every pyFDN backend (design + schema sketch) - #250

Draft
SebastianJiroSchlecht wants to merge 9 commits into
mainfrom
docs/system-json-format
Draft

SebastianJiroSchlecht wants to merge 9 commits into
mainfrom
docs/system-json-format

Conversation

@SebastianJiroSchlecht

@SebastianJiroSchlecht SebastianJiroSchlecht commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator

What this is

A design doc plus a first sketch of its PR 1 (the schema). It proposes pyfdn-system v1: one JSON document that
describes an FDN well enough for any of pyFDN's backends to render it, and
well enough to re-generate or re-train rather than only replay.

Full text: docs/design/system-format.md. §8 records the decisions from review, and §9 the rejected results format.

Opened as a draft so the format can be argued about before anything is built.
The implementation is staged as follow-up PRs (§7), each independently
useful and revertable.

Schema sketch (added after #255 merged)

src/pyFDN/system.py is pure data with no backend imports, and it is not yet exported from pyFDN:

  • FDNSystemDoc holds metadata, sample_rate, render and exactly one of an unchanged pyfdn-fdn-build or a graph.
  • GraphNode plus an open leaf registry (register_node_type) with pyFDN's own names: gain, matrix, matrix_fir, delay, sos, kronecker_matrix, time_varying_kronecker_matrix, time_varying_matrix, pitch_shift, dc_blocker, identity. A leaf's params are the matching td operator's constructor arguments, minus fs.
  • build_to_graph / graph_to_build use the assemble_fdn_core node names. graph_to_build refuses a non-vanilla graph and names the node it cannot convert.
  • system_to_dict / system_from_dict and save_fdn_system / load_fdn_system. Validation errors name the path of the offending node (/root/brA/feedback_loop).
  • save_fdn_system adds metadata.created and metadata.pyfdn_version when they are missing (by @almahova).
  • pyFDN.auxiliary.graph_plot.plot_system_graph draws a system document, a GraphNode tree or an FDNBuild without building a FLAMO model or td graph. plot_flamo_graph now uses the same renderer, and its output is unchanged.
  • Tests: tests/test_system.py, including a build → graph → build round trip for every packaged preset, and tests/test_graph_plot.py.

The branch also merges in current main.

Review decisions reflected in the sketch

  • Cascaded matrices stay cascaded. A paraunitary cascade is a series of matrix and delay stages, with its design recorded on the series node. It is not expanded into a matrix_fir. delay allows zero-length lines for the per-channel shifts.
  • Channel counts. Every node is written with "channels": [in, out] and checked on load. The counts are still derived from the parameters, so a document cannot define a system in two ways. No system-level [K, N, J]: N is not defined for every graph (SDN, RES), and K and J are the root's channels.
  • Name. The document class is FDNSystemDoc. Renaming the gallery's FDNSystem tuple is left to its own small PR, because it is a breaking change.
  • render is flat: nfft, alias_decay_db, block_size.
  • FDNPreset is unchanged. A build document may carry the preset design notes, so a preset converts to a system document without loss.
  • Provenance metadata is kept. A separate results document is not adopted: any result is one line of pyFDN code away from the system document (§9).
  • Diagrams come from the description, not from a backend (§8.7).

Why

pyFDN renders the same network three ways — process_fdn, the td operator
tree, and FLAMO — and exactly one of those is saveable, only for vanilla
networks, only as an FDNBuild. A Kronecker or otherwise time-varying feedback
matrix, a shimmer FDN, an SDN, a reverberation-enhancement system, a coupled-room
model: none of them has any representation on disk.

td has no serialisation of any kind today.

The proposal in five points

  1. Two tiers, both permanent. build (the existing pyfdn-fdn-build v2,
    unchanged) for vanilla FDNs; a new graph node tree for everything else.
    build_to_graph lowers, graph_to_build lifts and refuses a non-vanilla graph by name.
  2. One type namespace, owned by pyFDN. sos, not parallelSOSFilter.
    The registry is open, and a backend that cannot build a type fails loudly, naming it.
  3. params is baked, design is how it was made — per node.
  4. Rendering settings are not the system. nfft, alias_decay_db, block_size live in one top-level render block.
  5. The loop-delay convention is fixed. The document stores the true delay lengths and graph_to_td does the block compensation.

Relationship to adac

pyFDN owns the schema and every conversion in and out of it; nothing in pyFDN
core depends on adac. adac reads pyfdn-system documents downstream through a small vendored reader. See §2.2 and §6 of the doc.

Compatibility

  • pyfdn-fdn-build v2 is unchanged, byte-for-byte; a build document embeds exactly what fdn_build_to_dict writes.
  • Every packaged preset stays valid (tested).
  • No existing public API changes. plot_flamo_graph keeps its signature and output; only its renderer moved to auxiliary/graph_plot.py.

Follow-up work

  • PR 1 — schema. Sketched here; still to decide: export from pyFDN and add to the API reference, and whether FDNPreset should merge into FDNSystemDoc.
  • PR 2 — td. graph_to_td/td_to_graph, including the block-delay compensation.
  • PR 3 — FLAMO. flamo_to_graph/graph_to_flamo, generalising extract_build/build_to_flamo.
  • PR 4 — adac (separate repo). graph_to_faust on the neutral types.
  • Rename the gallery FDNSystem named tuple (separate small PR).
  • One execution path: FDNBuild as description, td/FLAMO as the only renderers #256: one execution path (to_td, to_flamo, impulse_response) and the future of FDNBuild, after this PR.

Still open

  1. Sidecar .npz for large arrays ({"$ref": ...}): not implemented; cascaded storage removes the most likely need.
  2. Whether FDNPreset becomes a thin alias of FDNSystemDoc.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q4XLZMVqxGGYjfEDjTG6v7

pyFDN renders the same network three ways -- process_fdn, the td operator
tree, and FLAMO -- but only the vanilla FDN is saveable, and only as a
build. A Kronecker or otherwise time-varying feedback matrix, a shimmer
FDN, an SDN, a reverberation-enhancement system: none of them has any
representation on disk.

This proposes `pyfdn-system` v1: a two-tier document that keeps the flat
`pyfdn-fdn-build` unchanged for vanilla networks and adds a neutral node
graph for everything else, with baked `params` separated from `design`
provenance per node, one canonical unit per quantity, and backend-specific
rendering settings kept out of the system description. pyFDN owns the
schema; nothing in core depends on adac.

No code. The staged implementation plan is in section 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPEJQF9mkLsQJnUPwwsd91
@jeremybbq

Copy link
Copy Markdown
Member

Looks cool! I like the idea that this JSON format essentially has a pyFDN centred vocabulary.

A quick comment before I dig into the design: the name FDNSystem is taken in code already, which is a dataclass for a simple decay-mixing loop without filters. But we can change that one, I didn't like it much... 😬

@Segfault1602

Copy link
Copy Markdown
  • Regarding the FIR Matrix:

I understand that pyFDN implement those using FIR filters, but personally I much prefer the cascaded form for actual real-time implementation and I suspect Faust implementation would also prefer that form. I'm worried that storing these matrices as FIR might make downstream implementation more difficult. I know the FIR representation can be constructed from the cascaded one, but I'm not sure if the inverse is true.

  • Regarding "Does graph need explicit channel counts per node? "

I would suggest having an explicit channel count triplet [K,N,J] node where K is the input channel count, N is the feedback loop channel count and J is the output channel count. So a mono-to-stereo FDN with 6 delay lines would be [1,6,2]. This will allow explicit validation of the graph and make it easier to diagnose ill-formed graphs.

@SebastianJiroSchlecht

Copy link
Copy Markdown
Collaborator Author

@jeremybbq yes, let's change the FDNsystem name.

  • Regarding the FIR Matrix:

I understand that pyFDN implement those using FIR filters, but personally I much prefer the cascaded form for actual real-time implementation and I suspect Faust implementation would also prefer that form. I'm worried that storing these matrices as FIR might make downstream implementation more difficult. I know the FIR representation can be constructed from the cascaded one, but I'm not sure if the inverse is true.

I agree on the FIR matrix. Cacaded Matrices should be actually represented as cascded.

* Regarding "Does graph need explicit channel counts per node? "

I would suggest having an explicit channel count triplet [K,N,J] node where K is the input channel count, N is the feedback loop channel count and J is the output channel count. So a mono-to-stereo FDN with 6 delay lines would be [1,6,2]. This will allow explicit validation of the graph and make it easier to diagnose ill-formed graphs.

I understand the input and output channels for each module. But feedback loop channels might not be universally present for all modules.

Adds pyFDN.system, a pure-data module with no backend imports:

- FDNSystemDoc: metadata + sample_rate + render + exactly one of an
  unchanged pyfdn-fdn-build or a GraphNode tree.
- GraphNode and an open leaf registry named after pyFDN's own vocabulary
  (gain, matrix, delay, sos, kronecker_matrix, time_varying_matrix, ...),
  with params mirroring the td operator constructors.
- Channel counts derived from params, written per node as [in, out] and
  checked on read; errors name the node's path.
- build_to_graph / graph_to_build using the assemble_fdn_core node names;
  graph_to_build refuses a non-vanilla graph by naming the node.
- Cascaded matrices are stored as a series of matrix/delay stages, per
  review, rather than as an expanded FIR matrix.

Brings docs/design/system-format.md over from #250 with the review
decisions recorded in section 8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4XLZMVqxGGYjfEDjTG6v7
@SebastianJiroSchlecht SebastianJiroSchlecht changed the title Design doc: one JSON document format for every pyFDN backend pyfdn-system: one JSON document format for every pyFDN backend (design + schema sketch) Sep 27, 2026
…arate system and results into two files formats
@almahova

Copy link
Copy Markdown
Collaborator

Metadata + additional pyfdn-results document:

Hey everyone,
I've gone through the pyfdn-system proposal and I agree with the current design and think as well that it fits.
Moreover, I was thinking of adding 2-3 things (I pushed it recently):

  1. Adding metadata to each .json file: save_fdn_system now adds metadata.created (date and time) and metadata.pyfdn_version unless the document already has them, so re-saving keeps the original metadata values. This records which pyFDN wrote a file and when and also credits the library in every file.

  2. A separate pyfdn-results document (was added in the design doc §9):
    It can be useful also to share the result of the network and what it produces on its own.
    That supports comparing a trained network against a target, sharing with collaborators, presenting work in progress, and also a teaching-oriented use.
    If a network has results, saving writes both files by default.

  3. A simple GUI for saving, loading and viewing (plots, graphs) may be considered for a later step.

- New pyFDN.auxiliary.graph_plot: plot_system_graph draws a pyfdn-system
  document, a GraphNode tree or an FDNBuild directly from its description,
  without building a FLAMO model or a td graph. A non-summing parallel is
  drawn as a stack rather than a sum.
- plot_flamo_graph now shares that renderer (plot_node_tree); its output
  is unchanged.
- Design doc: a separate pyfdn-results document is considered and not
  adopted, because any result is one line of pyFDN code away from the
  system document (§9). Provenance metadata stays. Records the plotting
  decision as §8.7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4XLZMVqxGGYjfEDjTG6v7

Copy link
Copy Markdown
Collaborator Author

@almahova thanks for going through the proposal and for the commit. On your three points (decided with @SebastianJiroSchlecht):

  1. Provenance metadata: kept. metadata.created and metadata.pyfdn_version stay as you implemented them in §4.2, including keeping the original values on re-save.
  2. Separate pyfdn-results document: not adopted. A system document is already complete: any result is one line of pyFDN code away from it (pyFDN.build_to_impz(doc.as_build(), n) today, and a backend-neutral impulse_response is planned in One execution path: FDNBuild as description, td/FLAMO as the only renderers #256). A second file would be able to drift from the system it describes and would need its own schema and versioning. §9 of the design doc now records the proposal and this reasoning instead of the format.
  3. Viewing: on the system itself, no GUI. pyFDN's existing plotting functions already cover the rendered responses. What was missing was a diagram that doesn't require choosing a backend, so 2e3846a adds pyFDN.auxiliary.graph_plot.plot_system_graph. It draws a system document, a GraphNode tree or an FDNBuild directly from its description, without building a FLAMO model. plot_flamo_graph now uses the same renderer, so both give the same picture.

Generated by Claude Code

pyFDN.train exports a function params that shares its name with the
submodule pyFDN.train.params. Importing it from .train let mypy resolve it
to the submodule under some import orders; the new graph_plot import
cycle hit that order and the lint job failed with "Name params already
defined (by an import)". Importing from .train.params is unambiguous.
Type-checking only; no runtime change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4XLZMVqxGGYjfEDjTG6v7

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FDN presets saving

5 participants