%23%20gallery_category%3A%20Export%20%26%20Deployment%0A%23%20gallery_title%3A%20Compile%20an%20FDN%20to%20FAUST%0A%23%20gallery_description%3A%20Compile%20a%20pyFDN%20design%20through%20FLAMO%20and%20adac%20into%20certified%20FAUST%20source%20for%20browser%2C%20offline%2C%20and%20plugin%20deployment.%0A%23%20references%3A%20Franchino2026ADAC%0A%23%20requires%3A%20adac%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.24.0%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20from%20pyFDN%20import%20paper_link%0A%0A%20%20%20%20return%20mo%2C%20paper_link%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20FDN%20to%20FAUST%3A%20compiling%20a%20pyFDN%20design%20to%20real-time%20DSP%0A%0A%20%20%20%20A%20pyFDN%20design%20lives%20as%20NumPy%20arrays%20and%2C%20through%20%60dss_to_flamo%60%2C%20as%20a%20differentiable%20FLAMO%20graph.%20Neither%20runs%20in%20a%20DAW.%20%5B%60adac%60%5D(https%3A%2F%2Fgithub.com%2Fcucuwritescode%2Fadac)%20closes%20that%20gap%3A%20it%20walks%20the%20FLAMO%20graph%2C%20extracts%20every%20delay%2C%20gain%2C%20matrix%20and%20filter%20into%20a%20JSON%20intermediate%20representation%2C%20and%20emits%20%5BFAUST%5D(https%3A%2F%2Ffaust.grame.fr)%20source%20that%20compiles%20to%20a%20plugin.%0A%0A%20%20%20%20**The%20pipeline%3A**%0A%0A%20%20%20%201.%20%60pyFDN.fdn_build_gallery%60%20%2B%20%60pyFDN.dss_to_flamo%60%20%E2%80%94%20design%20the%20FDN%3B%0A%20%20%20%202.%20%60adac.flamo_to_json%60%20%E2%80%94%20extract%20parameters%20into%20a%20JSON%20IR%3B%0A%20%20%20%203.%20%60adac.certify%60%20%E2%80%94%20small-gain%20stability%20certificate%20for%20the%20feedback%20loop%3B%0A%20%20%20%204.%20%60adac.json_to_faust%60%20%E2%80%94%20emit%20FAUST%2C%20with%20optional%20%60rt60%60%20%2F%20%60dry_wet%60%20macro%20knobs%3B%0A%20%20%20%205.%20run%20it%3A%20one%20click%20in%20the%20browser%20IDE%2C%20or%20%60faust2juce%60%20%2F%20%60adac.export_juce%60%20for%20a%20VST3.%0A%0A%20%20%20%20The%20last%20section%20renders%20the%20*compiled*%20FAUST%20and%20overlays%20it%20on%20the%20FLAMO%20reference%2C%20so%20the%20translation%20is%20checked%2C%20not%20assumed.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo%2C%20paper_link)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20Reference%3A%20*%7Bpaper_link(%22Franchino2026ADAC%22)%7D*.%0A%0A%20%20%20%20Install%3A%20%60pip%20install%20adac%60%20(NumPy%20only).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20base64%0A%20%20%20%20import%20importlib.util%0A%20%20%20%20import%20json%0A%20%20%20%20import%20shutil%0A%20%20%20%20import%20subprocess%0A%20%20%20%20import%20tempfile%0A%20%20%20%20import%20urllib.parse%0A%20%20%20%20from%20pathlib%20import%20Path%0A%0A%20%20%20%20import%20adac%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20torch%0A%0A%20%20%20%20import%20pyFDN%0A%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20Path%2C%0A%20%20%20%20%20%20%20%20adac%2C%0A%20%20%20%20%20%20%20%20base64%2C%0A%20%20%20%20%20%20%20%20importlib%2C%0A%20%20%20%20%20%20%20%20json%2C%0A%20%20%20%20%20%20%20%20np%2C%0A%20%20%20%20%20%20%20%20pyFDN%2C%0A%20%20%20%20%20%20%20%20shutil%2C%0A%20%20%20%20%20%20%20%20subprocess%2C%0A%20%20%20%20%20%20%20%20tempfile%2C%0A%20%20%20%20%20%20%20%20torch%2C%0A%20%20%20%20%20%20%20%20urllib%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%201.%20Design%20the%20FDN%0A%0A%20%20%20%20A%20six-line%20FDN%20from%20the%20gallery%2C%20mono%20in%20and%20stereo%20out%3A%20random%20orthogonal%20feedback%2C%20one-pole%20absorption%20per%20delay%20line%20giving%201.8%20s%20at%20DC%20and%200.4%20s%20at%20Nyquist%2C%20unity%20direct%20path.%0A%0A%20%20%20%20The%20output%20matrix%20%60C%60%20is%20%60(2%2C%206)%60%20with%20random%20gains%2C%20so%20the%20two%20channels%20are%20decorrelated%20mixes%20of%20the%20same%20delay%20lines%20%E2%80%94%20with%20%60io_type%3D%22ones%22%60%20both%20columns%20would%20be%20identical%20and%20the%20result%20would%20be%20dual%20mono.%0A%0A%20%20%20%20Nothing%20here%20is%20FAUST-specific%3A%20this%20is%20the%20ordinary%20pyFDN%20design%20loop%2C%20and%20the%20rest%20of%20the%20notebook%20never%20assumes%20a%20channel%20count.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pyFDN%2C%20torch)%3A%0A%20%20%20%20torch.manual_seed(42)%0A%20%20%20%20n%20%3D%206%0A%20%20%20%20fs%20%3D%2048000%0A%0A%20%20%20%20build%20%3D%20pyFDN.fdn_build_gallery(%0A%20%20%20%20%20%20%20%20n%2C%0A%20%20%20%20%20%20%20%20fs%3Dfs%2C%0A%20%20%20%20%20%20%20%20num_inputs%3D1%2C%0A%20%20%20%20%20%20%20%20num_outputs%3D2%2C%0A%20%20%20%20%20%20%20%20io_type%3D%22random%22%2C%0A%20%20%20%20%20%20%20%20direct_gain%3D1.0%2C%0A%20%20%20%20%20%20%20%20rt%3D1.8%2C%0A%20%20%20%20%20%20%20%20rt_nyquist%3D0.4%2C%0A%20%20%20%20%20%20%20%20rng%3D42%2C%0A%20%20%20%20)%0A%20%20%20%20model%20%3D%20pyFDN.dss_to_flamo(%0A%20%20%20%20%20%20%20%20build.A%2C%0A%20%20%20%20%20%20%20%20build.B%2C%0A%20%20%20%20%20%20%20%20build.C%2C%0A%20%20%20%20%20%20%20%20build.D%2C%0A%20%20%20%20%20%20%20%20build.delays%2C%0A%20%20%20%20%20%20%20%20build.fs%2C%0A%20%20%20%20%20%20%20%20nfft%3D2**18%2C%0A%20%20%20%20%20%20%20%20post_delay%3Dbuild.post_delay%2C%0A%20%20%20%20%20%20%20%20post_output%3Dbuild.post_output%2C%0A%20%20%20%20)%0A%20%20%20%20%23%20(1%20input%2C%20n_samples%2C%202%20outputs)%20-%3E%20(n_samples%2C%202)%2C%20the%20shape%20pyFDN%20plots%20take.%0A%20%20%20%20ir_flamo%20%3D%20pyFDN.flamo_time_response(model)%5B0%5D%0A%20%20%20%20print(f%22%7Bn%7D%20delay%20lines%3A%20%7Bbuild.delays.astype(int)%7D%20samples%22)%0A%20%20%20%20print(f%22%7Bbuild.B.shape%5B1%5D%7D%20in%2C%20%7Bbuild.C.shape%5B0%5D%7D%20out%20-%3E%20%7Bir_flamo.shape%7D%22)%0A%20%20%20%20return%20fs%2C%20ir_flamo%2C%20model%0A%0A%0A%40app.cell%0Adef%20_(model%2C%20pyFDN)%3A%0A%20%20%20%20%23%20The%20graph%20adac%20is%20about%20to%20traverse%3A%20B%20-%3E%20(delays%20-%3E%20absorption)%20~%20A%20-%3E%20C%2C%20plus%20D.%0A%20%20%20%20pyFDN.plot_flamo_graph(model%2C%20name%3D%22FDN%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%202.%20Extract%20the%20parameters%0A%0A%20%20%20%20%60flamo_to_json%60%20traverses%20the%20FLAMO%20graph%20and%20detaches%20every%20parameter%20into%20a%20plain%20dict.%20Extraction%20is%20map-aware%3A%20a%20matrix%20with%20a%20non-identity%20parametrisation%20(orthogonal%2C%20Householder%2C%20Hadamard)%20serialises%20the%20*effective*%20matrix%20the%20model%20applies%2C%20so%20what%20FAUST%20gets%20is%20what%20FLAMO%20ran.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(adac%2C%20fs%2C%20json%2C%20mo%2C%20model)%3A%0A%20%20%20%20config%20%3D%20adac.flamo_to_json(model%2C%20fs%2C%20name%3D%22PyFDNReverb%22)%0A%20%20%20%20_pretty%20%3D%20json.dumps(config%2C%20indent%3D1)%0A%20%20%20%20mo.accordion(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22JSON%20intermediate%20representation%20(%7Blen(_pretty)%7D%20characters)%22%3A%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%60%60%60json%5Cn%7B_pretty%5B%3A1500%5D%7D%5Cn...%5Cn%60%60%60%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%20%20%20%20return%20(config%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%203.%20Certify%20stability%0A%0A%20%20%20%20Before%20anything%20is%20emitted%2C%20%60certify%60%20bounds%20the%20loop%20gain%20around%20every%20feedback%20path%3A%20the%20product%20of%20per-element%20spectral%20norms%2C%20evaluated%20at%20the%20parameter%20values%20*as%20they%20will%20be%20written%20into%20the%20FAUST%20source*%20(single%20precision%2C%20ten%20significant%20figures).%0A%0A%20%20%20%20Below%20one%20at%20every%20frequency%20is%20a%20sufficient%20condition%20for%20BIBO%20stability%2C%20so%20a%20%60certified-stable%60%20verdict%20rules%20out%20a%20plugin%20that%20blows%20up%20after%20the%20rounding%20a%20code%20generator%20does.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(adac%2C%20config%2C%20mo)%3A%0A%20%20%20%20cert%20%3D%20adac.certify(config)%0A%20%20%20%20loop%20%3D%20cert%5B%22loops%22%5D%5B0%5D%0A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20**Verdict%3A%20%60%7Bcert%5B%22verdict%22%5D%7D%60**%0A%0A%20%20%20%20%7C%20quantity%20%7C%20value%20%7C%0A%20%20%20%20%7C---%7C---%7C%0A%20%20%20%20%7C%20loop%20gain%20bound%20(max%20over%20frequency)%20%7C%20%7Bloop%5B%22loop_gain_bound_max%22%5D%3A.4f%7D%20%7C%0A%20%20%20%20%7C%20loop%20gain%20bound%20(min%20over%20frequency)%20%7C%20%7Bloop%5B%22loop_gain_bound_min%22%5D%3A.4f%7D%20%7C%0A%20%20%20%20%7C%20feedback%20matrix%20spectral%20radius%20%7C%20%7Bloop%5B%22feedback_spectral_radius%22%5D%3A.6f%7D%20%7C%0A%20%20%20%20%7C%20implied%20RT60%20range%20%7C%20%7Bloop%5B%22rt60_estimate_s%22%5D%5B%22min%22%5D%3A.2f%7D%E2%80%93%7Bloop%5B%22rt60_estimate_s%22%5D%5B%22max%22%5D%3A.2f%7D%20s%20%7C%0A%20%20%20%20%7C%20absorption%20sections%20stable%20%7C%20%7Bcert%5B%22filters%22%5D%5B%22all_sections_stable%22%5D%7D%20%7C%0A%0A%20%20%20%20%60adac.write_certificate(config%2C%20%22PyFDNReverb.dsp%22)%60%20drops%20this%20next%20to%20the%0A%20%20%20%20generated%20source%20as%20%60PyFDNReverb.cert.json%60%3B%20%60export_juce%60%20refuses%20to%20build%0A%20%20%20%20an%20uncertified%20model%20unless%20%60strict%3DFalse%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%204.%20Generate%20FAUST%0A%0A%20%20%20%20Two%20variants%20of%20the%20same%20network%3A%0A%0A%20%20%20%20-%20**plain**%20%E2%80%94%20a%20faithful%20translation%20of%20the%20trained%20model%2C%20used%20for%20the%0A%20%20%20%20%20%20equivalence%20check%20in%20section%206%3B%0A%20%20%20%20-%20**with%20macro%20controls**%20%E2%80%94%20%60rt60%60%20and%20%60dry_wet%60%20sliders%20layered%20on%20top.%0A%20%20%20%20%20%20These%20are%20performance%20knobs%2C%20not%20trained%20parameters%3A%20the%20%60rt60%60%20slider%0A%20%20%20%20%20%20rescales%20the%20per-line%20attenuation%20against%20each%20delay%20length%2C%20leaving%20the%0A%20%20%20%20%20%20feedback%20matrix%20and%20absorption%20filters%20untouched.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(adac%2C%20config)%3A%0A%20%20%20%20faust_plain%20%3D%20adac.json_to_faust(config)%0A%20%20%20%20faust_code%20%3D%20adac.json_to_faust(config%2C%20controls%3D%7B%22rt60%22%3A%20True%2C%20%22dry_wet%22%3A%20True%7D)%0A%20%20%20%20print(f%22plain%3A%20%7Blen(faust_plain)%7D%20characters%2C%20with%20controls%3A%20%7Blen(faust_code)%7D%22)%0A%20%20%20%20return%20faust_code%2C%20faust_plain%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(faust_code%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%60%60%60faust%5Cn%7Bfaust_code%7D%5Cn%60%60%60%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20whole%20reverberator%20is%20one%20%60process%60%20line.%20The%20FDN%20core%20is%20FAUST's%20%60~%60%20feedback%20operator%20wrapped%20around%20%60delays%20%3A%20absorption%60%2C%20with%20the%20feedback%20matrix%20hoisted%20into%20a%20sum-of-products%20function%20%60fB%60.%20Note%20the%20delay%20lengths%3A%0A%20%20%20%20%60~%60%20inserts%20one%20implicit%20sample%20of%20delay%2C%20and%20adac%20subtracts%20it%20from%20every%20line%20so%20the%20network%20keeps%20the%20exact%20delays%20pyFDN%20designed.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20faust_code%2C%20faust_plain%2C%20mo%2C%20tempfile)%3A%0A%20%20%20%20work_dir%20%3D%20Path(tempfile.mkdtemp(prefix%3D%22pyfdn_faust_%22))%0A%20%20%20%20dsp_path%20%3D%20work_dir%20%2F%20%22PyFDNReverb.dsp%22%0A%20%20%20%20dsp_path.write_text(faust_code)%0A%20%20%20%20(work_dir%20%2F%20%22PyFDNReverb_plain.dsp%22).write_text(faust_plain)%0A%0A%20%20%20%20mo.vstack(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22Written%20to%20%60%7Bdsp_path%7D%60%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.download(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20faust_code.encode()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20filename%3D%22PyFDNReverb.dsp%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Download%20PyFDNReverb.dsp%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20)%0A%20%20%20%20return%20dsp_path%2C%20work_dir%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%205.%20Run%20it%0A%0A%20%20%20%20The%20shortest%20path%20to%20a%20running%20artifact%20needs%20no%20toolchain%20at%20all%3A%20the%20FAUST%20web%20IDE%20accepts%20a%20whole%20program%20as%20a%20base64%20%60inline%3D%60%20parameter%2C%20compiles%20it%20to%20WebAssembly%20in%20the%20browser%20and%20starts%20it.%20The%20link%20below%20carries%20this%20exact%20FDN%2C%20sliders%20and%20all%20%E2%80%94%20open%20it%2C%20allow%20audio%2C%20and%20the%20%60rt60%60%20and%20%60dry%2Fwet%60%20knobs%20are%20live.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(base64%2C%20faust_code%2C%20mo%2C%20urllib)%3A%0A%20%20%20%20ide_url%20%3D%20%22https%3A%2F%2Ffaustide.grame.fr%2F%3F%22%20%2B%20urllib.parse.urlencode(%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22autorun%22%3A%20%221%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22voices%22%3A%20%220%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22name%22%3A%20%22PyFDNReverb%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22inline%22%3A%20base64.b64encode(faust_code.encode()).decode()%2C%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20)%0A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20%5B**Open%20PyFDNReverb%20in%20the%20FAUST%20web%20IDE%20%E2%86%92**%5D(%7Bide_url%7D)%0A%0A%20%20%20%20(%7Blen(ide_url)%7D%20characters%20of%20URL%20%E2%80%94%20the%20entire%20reverb%20travels%20in%20the%20link.)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Native%20targets%0A%0A%20%20%20%20With%20the%20FAUST%20distribution%20installed%2C%20the%20same%20file%20goes%20to%20C%2B%2B%2C%20a%20JUCE%20plugin%20project%2C%20or%20a%20standalone%20app.%20The%20cell%20below%20runs%20the%20compiler%20if%20it%20is%20on%20%60PATH%60%20and%20otherwise%20just%20lists%20the%20commands.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(dsp_path%2C%20mo%2C%20shutil%2C%20subprocess%2C%20work_dir)%3A%0A%20%20%20%20_commands%20%3D%20f%22%22%22%0A%20%20%20%20cd%20%7Bwork_dir%7D%0A%20%20%20%20faust%20-o%20PyFDNReverb.cpp%20%7Bdsp_path.name%7D%20%20%20%23%20portable%20C%2B%2B%0A%20%20%20%20faust2juce%20-vst3%20%7Bdsp_path.name%7D%20%20%20%20%20%20%20%20%20%20%20%23%20JUCE%20plugin%20project%0A%20%20%20%20faust2caqt%20%7Bdsp_path.name%7D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20standalone%20macOS%20app%0A%20%20%20%20%22%22%22%0A%0A%20%20%20%20if%20shutil.which(%22faust%22)%20is%20None%3A%0A%20%20%20%20%20%20%20%20_blocks%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%60faust%60%20is%20not%20on%20%60PATH%60%20%E2%80%94%20install%20it%20from%20https%3A%2F%2Ffaust.grame.fr%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22to%20run%3A%5Cn%60%60%60bash%5Cn%7B_commands%7D%5Cn%60%60%60%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_cpp%20%3D%20work_dir%20%2F%20%22PyFDNReverb.cpp%22%0A%20%20%20%20%20%20%20%20subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%22faust%22%2C%20%22-o%22%2C%20str(_cpp)%2C%20str(dsp_path)%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20check%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_head%20%3D%20%22%5Cn%22.join(_cpp.read_text().splitlines()%5B%3A20%5D)%0A%20%20%20%20%20%20%20%20_blocks%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%60faust%20-o%20PyFDNReverb.cpp%60%20produced%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7B_cpp.stat().st_size%7D%20bytes%20of%20C%2B%2B%3A%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22%60%60%60cpp%5Cn%7B_head%7D%5Cn...%5Cn%60%60%60%22)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20mo.vstack(_blocks)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20And%20a%20full%20VST3%2FAU%2C%20built%20and%20installed%20in%20one%20call%20(needs%20FAUST%20%2B%20JUCE)%3A%0A%0A%20%20%20%20%60%60%60python%0A%20%20%20%20adac.export_juce(%0A%20%20%20%20%20%20%20%20config%2C%0A%20%20%20%20%20%20%20%20%22exported%2F%22%2C%0A%20%20%20%20%20%20%20%20name%3D%22PyFDNReverb%22%2C%0A%20%20%20%20%20%20%20%20controls%3D%7B%22rt60%22%3A%20True%2C%20%22dry_wet%22%3A%20True%2C%20%22pre_delay%22%3A%20True%7D%2C%0A%20%20%20%20%20%20%20%20juce_modules%3D%22~%2FJUCE%2Fmodules%22%2C%0A%20%20%20%20%20%20%20%20build%3DTrue%2C%0A%20%20%20%20)%0A%20%20%20%20%60%60%60%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%206.%20Does%20the%20plugin%20match%20the%20design%3F%0A%0A%20%20%20%20Emitting%20code%20is%20only%20useful%20if%20the%20compiled%20DSP%20is%20the%20network%20pyFDN%20designed.%20%5BDawDreamer%5D(https%3A%2F%2Fgithub.com%2FDBraun%2FDawDreamer)%20embeds%20libfaust%2C%20so%20the%20generated%20%60.dsp%60%20can%20be%20compiled%20and%20rendered%20offline%20right%20here%20and%20compared%20sample%20by%20sample%20against%20FLAMO's%20frequency-domain%20response.%0A%0A%20%20%20%20Optional%3A%20%60pip%20install%20dawdreamer%60.%20Without%20it%20the%20rest%20of%20the%20notebook%20still%20runs%3B%20this%20section%20reports%20as%20skipped.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(importlib%2C%20np)%3A%0A%20%20%20%20HAS_DAWDREAMER%20%3D%20importlib.util.find_spec(%22dawdreamer%22)%20is%20not%20None%0A%0A%20%20%20%20def%20render_faust(dsp_file%2C%20x%2C%20fs)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Compile%20%60%60dsp_file%60%60%20with%20libfaust%20and%20render%20the%20mono%20signal%20%60%60x%60%60.%0A%0A%20%20%20%20%20%20%20%20Returns%20the%20output%20as%20%60%60(n_samples%2C%20n_channels)%60%60%20%E2%80%94%20for%20this%20stereo%20FDN%2C%0A%20%20%20%20%20%20%20%20one%20column%20per%20output%20channel.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20import%20dawdreamer%0A%0A%20%20%20%20%20%20%20%20engine%20%3D%20dawdreamer.RenderEngine(int(fs)%2C%20128)%0A%20%20%20%20%20%20%20%20faust%20%3D%20engine.make_faust_processor(%22faust%22)%0A%20%20%20%20%20%20%20%20faust.set_dsp(str(dsp_file))%0A%20%20%20%20%20%20%20%20playback%20%3D%20engine.make_playback_processor(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22input%22%2C%20np.asarray(x%2C%20dtype%3Dnp.float32)%5BNone%2C%20%3A%5D%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20engine.load_graph(%5B(playback%2C%20%5B%5D)%2C%20(faust%2C%20%5B%22input%22%5D)%5D)%0A%20%20%20%20%20%20%20%20engine.render(len(x)%20%2F%20fs)%0A%20%20%20%20%20%20%20%20return%20engine.get_audio().T%0A%0A%20%20%20%20return%20HAS_DAWDREAMER%2C%20render_faust%0A%0A%0A%40app.cell%0Adef%20_(HAS_DAWDREAMER%2C%20fs%2C%20ir_flamo%2C%20np%2C%20render_faust%2C%20work_dir)%3A%0A%20%20%20%20if%20HAS_DAWDREAMER%3A%0A%20%20%20%20%20%20%20%20_impulse%20%3D%20np.zeros(len(ir_flamo)%2C%20dtype%3Dnp.float32)%0A%20%20%20%20%20%20%20%20_impulse%5B0%5D%20%3D%201.0%0A%20%20%20%20%20%20%20%20ir_faust%20%3D%20render_faust(work_dir%20%2F%20%22PyFDNReverb_plain.dsp%22%2C%20_impulse%2C%20fs)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20ir_faust%20%3D%20None%0A%20%20%20%20%20%20%20%20print(%22dawdreamer%20not%20installed%20%E2%80%94%20skipping%20the%20compiled-FAUST%20comparison%22)%0A%20%20%20%20return%20(ir_faust%2C)%0A%0A%0A%40app.cell%0Adef%20_(fs%2C%20ir_faust%2C%20ir_flamo%2C%20mo%2C%20np%2C%20pyFDN)%3A%0A%20%20%20%20if%20ir_faust%20is%20None%3A%0A%20%20%20%20%20%20%20%20_blocks%20%3D%20%5Bmo.md(%22_Install%20%60dawdreamer%60%20to%20render%20the%20compiled%20FAUST%20here._%22)%5D%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_m%20%3D%20min(len(ir_flamo)%2C%20len(ir_faust))%0A%20%20%20%20%20%20%20%20_a%2C%20_b%20%3D%20np.asarray(ir_flamo)%5B%3A_m%5D%2C%20np.asarray(ir_faust)%5B%3A_m%5D%0A%20%20%20%20%20%20%20%20%23%20Both%20are%20(n_samples%2C%202)%3A%20compare%20the%20stereo%20pair%20channel%20by%20channel.%0A%20%20%20%20%20%20%20%20_err%20%3D%20np.max(np.abs(_a%20-%20_b)%2C%20axis%3D0)%0A%20%20%20%20%20%20%20%20_err_db%20%3D%2020%20*%20np.log10(_err%20%2F%20np.max(np.abs(_a)%2C%20axis%3D0))%0A%20%20%20%20%20%20%20%20_traces%20%3D%20%5B_a%5B%3A%2C%200%5D%2C%20_b%5B%3A%2C%200%5D%2C%20_a%5B%3A%2C%201%5D%2C%20_b%5B%3A%2C%201%5D%5D%0A%20%20%20%20%20%20%20%20_labels%20%3D%20%5B%22FLAMO%20L%22%2C%20%22FAUST%20L%22%2C%20%22FLAMO%20R%22%2C%20%22FAUST%20R%22%5D%0A%0A%20%20%20%20%20%20%20%20_blocks%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Peak%20difference%20between%20the%20FLAMO%20reference%20and%20the%20compiled%20FAUST%0A%20%20%20%20%20%20%20%20%20%20%20%20plugin%3A%20**%7B_err%5B0%5D%3A.2e%7D**%20left%20(%7B_err_db%5B0%5D%3A.1f%7D%20dB%20below%20the%20channel%0A%20%20%20%20%20%20%20%20%20%20%20%20peak)%20and%20**%7B_err%5B1%5D%3A.2e%7D**%20right%20(%7B_err_db%5B1%5D%3A.1f%7D%20dB)%2C%20i.e.%0A%20%20%20%20%20%20%20%20%20%20%20%20single-precision%20arithmetic%20noise%20plus%20FLAMO's%20FFT%20time-aliasing%0A%20%20%20%20%20%20%20%20%20%20%20%20floor.%20Each%20channel%20lies%20on%20top%20of%20its%20reference%2C%20and%20so%20do%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20energy%20decay%20curves.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pyFDN.plot_impulse_response(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*_traces%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fs%3Dfs%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labels%3D_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Impulse%20response%3A%20reference%20vs%20compiled%20plugin%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pyFDN.plot_edc(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*_traces%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fs%3Dfs%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labels%3D_labels%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%3D%22Energy%20decay%20curve%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20mo.vstack(_blocks)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Listen%0A%0A%20%20%20%20A%20dry%20synth%20phrase%20through%20the%20FLAMO%20model%20and%2C%20when%20DawDreamer%20is%20available%2C%20through%20the%20compiled%20FAUST%20plugin.%20Same%20network%2C%20two%20runtimes.%20The%20phrase%20is%20trimmed%20to%20what%20fits%20in%20one%20%60nfft%60%20block%20alongside%20the%202%20s%20tail%2C%20so%20both%20runtimes%20get%20the%20same%20excerpt.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(HAS_DAWDREAMER%2C%20fs%2C%20mo%2C%20model%2C%20np%2C%20pyFDN%2C%20render_faust%2C%20work_dir)%3A%0A%20%20%20%20_tail%20%3D%202%20*%20fs%0A%20%20%20%20%23%20FLAMO%20convolves%20inside%20a%20single%20nfft%20block%2C%20so%20only%20nfft%20minus%20the%20tail%0A%20%20%20%20%23%20is%20usable%20input.%20Trim%20the%20phrase%20to%20that%20window%3A%20otherwise%20flamo_process%0A%20%20%20%20%23%20silently%20drops%20whatever%20does%20not%20fit%20and%20the%20two%20runtimes%20are%20compared%20on%0A%20%20%20%20%23%20different%20excerpts.%0A%20%20%20%20_usable%20%3D%20int(model.get_inputLayer().nfft)%20-%20_tail%0A%20%20%20%20dry%2C%20_%20%3D%20pyFDN.load_audio(%22synth_dry.wav%22%2C%20fs%3Dfs)%0A%20%20%20%20dry%20%3D%20np.asanyarray(dry%2C%20dtype%3Dnp.float32)%5B%3A_usable%5D%0A%20%20%20%20_n%20%3D%20len(dry)%20%2B%20_tail%0A%20%20%20%20wet_flamo%20%3D%20np.asarray(%0A%20%20%20%20%20%20%20%20pyFDN.flamo_process(model%2C%20dry%2C%20fs%3Dfs%2C%20tail_seconds%3D_tail%20%2F%20fs)%0A%20%20%20%20)%5B%3A_n%5D%0A%0A%20%20%20%20%23%20mo.audio%20wants%20(n_channels%2C%20n_samples)%2C%20the%20transpose%20of%20the%20plotting%20shape.%0A%20%20%20%20_players%20%3D%20%5B%0A%20%20%20%20%20%20%20%20pyFDN.labeled_audio(%22Dry%22%2C%20dry%2C%20fs%3Dfs)%2C%0A%20%20%20%20%20%20%20%20pyFDN.labeled_audio(%22Wet%20%E2%80%94%20FLAMO%22%2C%20wet_flamo.T%2C%20fs%3Dfs)%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20if%20HAS_DAWDREAMER%3A%0A%20%20%20%20%20%20%20%20%23%20Same%20trailing%20silence%20as%20flamo_process%2C%20so%20the%20tail%20is%20not%20cut.%0A%20%20%20%20%20%20%20%20_padded%20%3D%20np.zeros(_n%2C%20dtype%3Dnp.float32)%0A%20%20%20%20%20%20%20%20_padded%5B%3A%20len(dry)%5D%20%3D%20dry%0A%20%20%20%20%20%20%20%20wet_faust%20%3D%20render_faust(work_dir%20%2F%20%22PyFDNReverb_plain.dsp%22%2C%20_padded%2C%20fs)%0A%20%20%20%20%20%20%20%20_players.append(pyFDN.labeled_audio(%22Wet%20%E2%80%94%20compiled%20FAUST%22%2C%20wet_faust.T%2C%20fs%3Dfs))%0A%0A%20%20%20%20mo.hstack(_players)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Where%20this%20lands%0A%0A%20%20%20%20The%20design%20stays%20in%20Python%20%E2%80%94%20gallery%20matrices%2C%20absorption%20fitting%2C%20optimisation%2C%20the%20analysis%20tools%20in%20the%20rest%20of%20this%20gallery%20%E2%80%94%20and%20the%20deployment%20artifact%20is%20generated%2C%20not%20re-implemented%20by%20hand.%0A%0A%20%20%20%20A%20colourless%20FDN%20trained%20with%20%60pyFDN.train%60%2C%20or%20an%20FDN%20fitted%20to%20a%20measured%20RIR%20by%20%60example_rir_to_fdn%60%2C%20compiles%20through%20exactly%20the%20same%20three%20calls%2C%20because%20the%20compiler%20reads%20the%20FLAMO%20graph%20rather%20than%20any%20particular%20design%20recipe.%0A%0A%20%20%20%20%60adac.HotReload%60%20closes%20the%20loop%20further%3A%20it%20republishes%20the%20model%20to%20a%20running%20FAUST%20plugin%20during%20training%2C%20so%20the%20optimisation%20is%20audible%20while%20it%20runs.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
0f8ceae52e959fd5ec865d4e73079569