Modules & Persistence¶
krach sessions can be captured, serialized, and replayed. The module system turns a live session into a frozen specification (GraphIr) that can be saved, loaded, shared, and composed.
Capture & instantiate¶
# Build a session
bass = kr.node("bass", bass_fn, gain=0.3)
verb = kr.node("verb", reverb_fn, gain=0.3)
bass >> (verb, 0.4)
bass @ krp.seq("A2", "D3", None, "E2").over(2)
kr.tempo = 128
# Capture the entire state as frozen IR
ir = kr.capture()
# Later: replay on a fresh mixer
kr.replay(ir)
capture() returns a GraphIr — a frozen, serializable snapshot of everything: nodes (with their DspGraph IR), routing, patterns, controls, transport, and mute state.
replay(ir) replays it: creates nodes, connects routing, plays patterns, sets controls.
Save & recall (in-memory scenes)¶
kr.save("verse") # capture() + store by name
kr.save("chorus")
kr.recall("verse") # clear + instantiate the saved scene
kr.recall("chorus")
kr.scenes # → ["verse", "chorus"]
JSON serialization¶
import json
# Serialize
ir = kr.capture()
data = ir.to_dict()
json.dump(data, open("session.json", "w"))
# Deserialize
from krach.ir.module import GraphIr
data = json.load(open("session.json"))
ir = GraphIr.from_dict(data)
kr.replay(ir)
The serialized format embeds DspGraphs inline — the full signal computation IR for each node. When you load and instantiate, the DSP is re-transpiled to FAUST and JIT-compiled. Nodes with identical DspGraphs (same graph_key) share compiled binaries.
Export to Python script¶
Generates a self-contained Python script that recreates the session:
# Generated by kr.export()
import json
from krach.pattern.pattern import Pattern
from krach.pattern.serialize import dict_to_pattern_node
with kr.batch():
kr.node("bass", bass_fn, gain=0.3)
kr.node("verb", reverb_fn, gain=0.3)
kr.connect("bass", "verb", level=0.4)
kr.tempo = 128
# ... patterns, controls, etc.
Tracing proxy¶
For programmatic module construction without starting audio:
proxy = kr.trace()
proxy.node("kick", "faust:kick", gain=0.8)
proxy.node("hat", "faust:hat", gain=0.3)
proxy.send("kick", "verb", level=0.2)
proxy.play("kick", krp.hit() * 4)
proxy.tempo = 128
ir = proxy.build() # → GraphIr (no audio started)
kr.replay(ir) # now play it
The proxy records calls as GraphIr without connecting to the engine. Useful for building reusable module templates.
@kr.graph decorator¶
Define reusable modules as traced functions. The first parameter is a GraphProxy — it records calls without starting audio:
@kr.graph
def drums(m, tempo=128):
g.node("kick", "faust:kick", gain=0.8)
g.node("hat", "faust:hat", gain=0.3)
g.send("kick", "hat", level=0.2)
g.play("kick", krp.hit() * 4)
g.tempo = tempo
g.outputs("kick", "hat")
ir = drums(tempo=140) # → GraphIr (no audio)
The decorated function returns a frozen GraphIr. Call it with any extra arguments — the proxy parameter is injected automatically.
GraphHandle¶
kr.instantiate(ir, prefix) replays a GraphIr with namespaced nodes and returns a GraphHandle:
d = kr.instantiate(drums_ir, "drums")
# Operator DSL — delegates to first declared input/output
d >> verb # route output to verb
bass >> d # route bass into input
d @ pattern # play on first input
d["kick/cutoff"] = 1200 # control access
# Properties
d.input # → NodeHandle for first declared input
d.output # → NodeHandle for first declared output
d.nodes # → {"kick": NodeHandle, "hat": NodeHandle}
d.prefix # → "drums"
All node names are prefixed: "kick" becomes "drums/kick". The existing / path addressing works naturally.
kr.scene(name) and kr.replay(ir)¶
kr.scene(name) retrieves a saved scene by name (returns GraphIr):
kr.replay(ir) replays a GraphIr onto the mixer (session replay). It flattens any sub_graphs automatically:
Module composition with g.sub()¶
Compose modules by nesting them inside a @kr.graph definition:
@kr.graph
def kit(g):
g.node("kick", "faust:kick", gain=0.8)
g.node("hat", "faust:hat", gain=0.3)
g.outputs("kick", "hat")
@kr.graph
def full_band(m):
g.node("bass", "faust:bass", gain=0.5)
drums = g.sub("drums", kit()) # nest kit as "drums/*"
g.send(drums.output("kick"), "bass", level=0.3)
g.outputs("bass")
ir = full_band()
band = kr.instantiate(ir, "band")
# Nodes: band/bass, band/drums/kick, band/drums/hat
g.sub(prefix, ir) returns a SubGraphRef with .input(name) and .output(name) for validated path strings. Typos are caught at trace time, not instantiation time.
prefix_ir() and flatten() (advanced)¶
For manual composition without the decorator:
from krach.ir.module import prefix_ir, flatten
# Namespace all names in an IR
namespaced = prefix_ir(drums_ir, "drums")
# "kick" → "drums/kick", controls and routes follow
# Resolve sub_graphs into flat nodes
flat = flatten(parent_ir)
# Recursively prefixes and merges all sub_module nodes
prefix_ir(ir, prefix) rewrites all node names, routes, patterns, controls, and automations with the prefix. flatten(ir) recursively resolves sub_graphs into a single flat IR (parent-wins for transport settings).