%23%20gallery_category%3A%20FDN%20Design%20%26%20Analysis%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.23.13%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%20return%20(mo%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%20Colorless%20FDN%2C%20trained%20in-notebook%0A%0A%20%20%20%20The%20companion%20to%20**Colorless%20FDN**%2C%20which%20*loads*%20pre-optimized%20parameters%0A%20%20%20%20from%20%60.mat%60%20files.%20Here%20we%20run%20the%20optimization%20ourselves%20with%20%60pyFDN%60's%0A%20%20%20%20training%20API%2C%20following%20*%22Differentiable%20Feedback%20Delay%20Network%20for%0A%20%20%20%20Colorless%20Reverberation%22%2C%20Dal%20Santo%2C%20Prawda%2C%20Schlecht%2C%20V%C3%A4lim%C3%A4ki%2C%20DAFx23*%0A%20%20%20%20(and%20its%20%22tiny%20colorless%20FDN%22%20follow-up)%3A%0A%0A%20%20%20%201.%20%60pyFDN.build_fdn%60%20--%20a%20standard%20FDN%20skeleton%20with%20random%20orthogonal%20feedback%0A%20%20%20%20%20%20%20matrix.%0A%20%20%20%202.%20%60pyFDN.train_fdn(model%2C%20%22colorless%22)%60%20--%20optimize%20the%20feedback%20matrix%20and%0A%20%20%20%20%20%20%20gains%20for%20a%20flat%20magnitude%20(magnitude%20MSE%20%2B%20a%20feedback-matrix%20sparsity%0A%20%20%20%20%20%20%20penalty)%2C%20in%20place.%0A%20%20%20%203.%20%60pyFDN.extract_build%60%20--%20read%20both%20the%20initial%20and%0A%20%20%20%20%20%20%20optimized%20FDNs%20back%20out.%0A%0A%20%20%20%20Delays%20stay%20fixed.%20We%20then%20add%20homogeneous%20decay%20so%20the%20result%20is%20audible.%0A%20%20%20%20Flatness%20is%20measured%20on%20the%20magnitude%20response%20at%20the%20**training**%20FFT%20bins%20--%0A%20%20%20%20the%20right%20colorless%20measure%20for%20a%20lossless%20FDN%2C%20whose%20time%20response%20never%0A%20%20%20%20decays.%20Training%20is%20kept%20short%20so%20the%20example%20runs%20in%20a%20few%20seconds.%0A%0A%20%20%20%20-%20pyFDN%20training%20pipeline%3A%20Jeremy%20B.%20Bai%2C%202026-06-19%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%20numpy%20as%20np%0A%0A%20%20%20%20import%20pyFDN%0A%0A%20%20%20%20def%20flatness(magnitude)%3A%0A%20%20%20%20%20%20%20%20%23%20Spectral%20flatness%20(geometric%2Farithmetic%20mean%20of%20power%2C%20DC%20excluded)%3B%0A%20%20%20%20%20%20%20%20%23%201.0%20is%20perfectly%20flat.%20Inlined%20here%20so%20the%20example%20needs%20no%20metrics%20API.%0A%20%20%20%20%20%20%20%20power%20%3D%20np.abs(magnitude).ravel()%5B1%3A%5D%20**%202%0A%20%20%20%20%20%20%20%20power%20%3D%20power%5Bpower%20%3E%200%5D%0A%20%20%20%20%20%20%20%20if%20power.size%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%200.0%0A%20%20%20%20%20%20%20%20return%20float(np.exp(np.mean(np.log(power)))%20%2F%20np.mean(power))%0A%0A%20%20%20%20return%20flatness%2C%20np%2C%20pyFDN%0A%0A%0A%40app.cell%0Adef%20_(flatness%2C%20np%2C%20pyFDN)%3A%0A%20%20%20%20fs%20%3D%2048000%0A%20%20%20%20nfft%20%3D%202**12%0A%0A%20%20%20%20%23%201.%20build%20a%20small%20%22tiny%20colorless%22%20lossless%20skeleton.%0A%20%20%20%20delays%20%3D%20pyFDN.sample_delay_lengths(%0A%20%20%20%20%20%20%20%208%2C%20(800%2C%203200)%2C%20distribution%3D%22geometric%22%2C%20coprime%3DTrue%2C%20rng%3D1%0A%20%20%20%20)%0A%20%20%20%20model%20%3D%20pyFDN.build_fdn(%0A%20%20%20%20%20%20%20%20delays%3Ddelays%2C%20rt%3DNone%2C%20nfft%3Dnfft%2C%20output%3D%22magnitude%22%2C%20device%3D%22cpu%22%2C%20rng%3D1%0A%20%20%20%20)%0A%20%20%20%20init_build%20%3D%20pyFDN.extract_build(model)%20%20%23%20random%20init%2C%20before%20training%0A%20%20%20%20mag_init%20%3D%20np.abs(pyFDN.flamo_freq_response(model%2C%20fs%3Dfs).squeeze())%0A%0A%20%20%20%20%23%202.%20train%20in%20place%20for%20a%20flat%20(%22colorless%22)%20magnitude%3B%20then%20extract.%0A%20%20%20%20log%20%3D%20pyFDN.train_fdn(%0A%20%20%20%20%20%20%20%20model%2C%0A%20%20%20%20%20%20%20%20%22colorless%22%2C%0A%20%20%20%20%20%20%20%20optimizer%3D%22lbfgs%22%2C%0A%20%20%20%20%20%20%20%20max_steps%3D2000%2C%0A%20%20%20%20%20%20%20%20lr%3D1e-3%2C%0A%20%20%20%20%20%20%20%20device%3D%22cpu%22%2C%0A%20%20%20%20%20%20%20%20rng%3D1%2C%0A%20%20%20%20)%0A%20%20%20%20mag_opt%20%3D%20np.abs(pyFDN.flamo_freq_response(model%2C%20fs%3Dfs).squeeze())%0A%20%20%20%20opt_build%20%3D%20pyFDN.extract_build(model)%0A%0A%20%20%20%20print(%0A%20%20%20%20%20%20%20%20f%22spectral%20flatness%20%20init%20%7Bflatness(mag_init)%3A.4f%7D%22%0A%20%20%20%20%20%20%20%20f%22%20%20%20colorless%20%7Bflatness(mag_opt)%3A.4f%7D%22%0A%20%20%20%20)%0A%20%20%20%20return%20fs%2C%20init_build%2C%20log%2C%20mag_init%2C%20mag_opt%2C%20nfft%2C%20opt_build%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%20Magnitude%20response%20and%20training%20loss%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(flatness%2C%20fs%2C%20log%2C%20mag_init%2C%20mag_opt%2C%20mo%2C%20nfft%2C%20np%2C%20pyFDN)%3A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%0A%20%20%20%20_freqs%20%3D%20np.fft.rfftfreq(nfft%2C%201.0%20%2F%20fs)%0A%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(11%2C%203.2))%0A%20%20%20%20_axes%5B0%5D.plot(%0A%20%20%20%20%20%20%20%20_freqs%2C%0A%20%20%20%20%20%20%20%20pyFDN.lin_to_db(mag_init)%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.5%2C%0A%20%20%20%20%20%20%20%20label%3Df%22init%20(%7Bflatness(mag_init)%3A.3f%7D)%22%2C%0A%20%20%20%20)%0A%20%20%20%20_axes%5B0%5D.plot(%0A%20%20%20%20%20%20%20%20_freqs%2C%0A%20%20%20%20%20%20%20%20pyFDN.lin_to_db(mag_opt)%2C%0A%20%20%20%20%20%20%20%20label%3Df%22colorless%20(%7Bflatness(mag_opt)%3A.3f%7D)%22%2C%0A%20%20%20%20)%0A%20%20%20%20_axes%5B0%5D.set(%0A%20%20%20%20%20%20%20%20xlabel%3D%22frequency%20%5BHz%5D%22%2C%0A%20%20%20%20%20%20%20%20ylabel%3D%22magnitude%20%5BdB%5D%22%2C%0A%20%20%20%20%20%20%20%20xscale%3D%22log%22%2C%0A%20%20%20%20%20%20%20%20title%3D%22Magnitude%20response%20(flatness%20in%20legend)%22%2C%0A%20%20%20%20)%0A%20%20%20%20_axes%5B0%5D.legend(fontsize%3D8)%0A%20%20%20%20_axes%5B0%5D.grid(True%2C%20alpha%3D0.3)%0A%0A%20%20%20%20_axes%5B1%5D.plot(log.train_loss%2C%20%22-o%22%2C%20ms%3D2)%0A%20%20%20%20_axes%5B1%5D.set(xlabel%3D%22epoch%22%2C%20ylabel%3D%22loss%22%2C%20yscale%3D%22log%22%2C%20title%3D%22Training%20loss%22)%0A%20%20%20%20_axes%5B1%5D.grid(True%2C%20alpha%3D0.3)%0A%20%20%20%20_fig.tight_layout()%0A%20%20%20%20mo.as_html(_fig)%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%20Listen%3A%20random%20init%20vs%20colorless%0A%0A%20%20%20%20Two%20renderings%20of%20each%20FDN%2C%20built%20end%20to%20end%20through%20the%20render%20API%0A%20%20%20%20(%60pyFDN.with_decay%60%20-%3E%20%60pyFDN.build_to_flamo%60%20-%3E%20%60pyFDN.flamo_time_response%60)%2C%0A%20%20%20%20peak-normalized%20so%20the%20A%2FB%20compares%20*timbre*%2C%20not%20level%3A%0A%0A%20%20%20%20*%20**Long%20tail%20(hear%20the%20colour)**%20--%20a%20very%20long%20reverberation%20time%2C%20so%20the%20FDN%0A%20%20%20%20%20%20rings%20with%20almost%20no%20decay%20and%20you%20hear%20its%20colour%20directly%3A%20the%20random%20init%0A%20%20%20%20%20%20is%20tonal%2Fmetallic%20(sharp%20modal%20resonances)%2C%20the%20colorless%20one%20is%20noise-like%0A%20%20%20%20%20%20(flat%20spectrum).%20A%20*truly*%20lossless%20FDN%20(poles%20on%20the%20unit%20circle)%20can't%20be%0A%20%20%20%20%20%20rendered%20by%20the%20FFT-based%20%60flamo_time_response%60%20without%20time-aliasing%20the%0A%20%20%20%20%20%20colour%20away%2C%20so%20we%20use%20a%20long%20finite%20RT%20instead.%0A%20%20%20%20*%20**Reverb%20tail**%20--%20a%20short%20homogeneous%20%24T_%7B60%7D%24%2C%20giving%20an%20audible%20decay.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(fs%2C%20init_build%2C%20np%2C%20opt_build%2C%20pyFDN)%3A%0A%20%20%20%20%23%20A%20long%20%22ring%22%20RT%20keeps%20the%20colour%20audible%20with%20little%20decay%3B%20a%20short%20RT%20gives%0A%20%20%20%20%23%20an%20audible%20reverb%20tail.%0A%20%20%20%20rt_ring%2C%20rt_rev%20%3D%2060.0%2C%202.0%0A%20%20%20%20n_samples%20%3D%20int(2.0%20*%20fs)%0A%0A%20%20%20%20def%20render(build%2C%20rt)%3A%0A%20%20%20%20%20%20%20%20%22%22%22build%20-%3E%20FLAMO%20model%20with%20decay%20-%3E%20peak-normalized%20NumPy%20IR.%22%22%22%0A%20%20%20%20%20%20%20%20model%20%3D%20pyFDN.build_to_flamo(%0A%20%20%20%20%20%20%20%20%20%20%20%20pyFDN.with_decay(build%2C%20rt)%2C%20nfft%3Dn_samples%2C%20device%3D%22cpu%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20ir%20%3D%20pyFDN.flamo_time_response(model%2C%20fs%3Dfs).squeeze()%0A%20%20%20%20%20%20%20%20%23%20peak-normalize%20so%20the%20A%2FB%20compares%20timbre%20at%20matched%20level%2C%20not%20loudness.%0A%20%20%20%20%20%20%20%20return%20np.asarray(ir)%20%2F%20(np.max(np.abs(ir))%20%2B%201e-12)%0A%0A%20%20%20%20_fade%20%3D%20np.ones(n_samples)%0A%20%20%20%20_fade%5B-2048%3A%5D%20%3D%20np.linspace(1.0%2C%200.0%2C%202048)%0A%20%20%20%20init_noise%20%3D%20render(init_build%2C%20rt_ring)%20*%20_fade%0A%20%20%20%20opt_noise%20%3D%20render(opt_build%2C%20rt_ring)%20*%20_fade%0A%0A%20%20%20%20%23%20Reverb%3A%20a%20short%20homogeneous%20T60%20gives%20an%20audible%20decaying%20tail.%0A%20%20%20%20init_decay%20%3D%20render(init_build%2C%20rt_rev)%0A%20%20%20%20opt_decay%20%3D%20render(opt_build%2C%20rt_rev)%0A%20%20%20%20return%20init_decay%2C%20init_noise%2C%20opt_decay%2C%20opt_noise%0A%0A%0A%40app.cell%0Adef%20_(fs%2C%20init_decay%2C%20init_noise%2C%20mo%2C%20opt_decay%2C%20opt_noise%2C%20pyFDN)%3A%0A%0A%20%20%20%20def%20_clip(label%2C%20sig)%3A%0A%20%20%20%20%20%20%20%20return%20mo.vstack(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bmo.Html(label).style(%7B%22font-size%22%3A%20%221.1em%22%7D)%2C%20mo.audio(sig%2C%20rate%3Dint(fs))%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20gap%3D0%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_plot%20%3D%20pyFDN.plot_impulse_response(%0A%20%20%20%20%20%20%20%20opt_decay%2C%20init_decay%2C%20fs%3Dfs%2C%20labels%3D%5B%22Colorless%22%2C%20%22Random%20init%22%5D%0A%20%20%20%20)%0A%20%20%20%20_audio%20%3D%20mo.hstack(%0A%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20mo.vstack(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.Html(%22%3Cb%3ELong%20tail%20(hear%20the%20colour)%3C%2Fb%3E%22).style(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%22font-size%22%3A%20%221.2em%22%7D%0A%20%20%20%20%20%20%20%20%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%20%20%20%20%20%20%20%20%20_clip(%22Random%20init%22%2C%20init_noise)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_clip(%22Colorless%22%2C%20opt_noise)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gap%3D1%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%20mo.vstack(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mo.Html(%22%3Cb%3EReverb%20tail%3C%2Fb%3E%22).style(%7B%22font-size%22%3A%20%221.2em%22%7D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_clip(%22Random%20init%22%2C%20init_decay)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_clip(%22Colorless%22%2C%20opt_decay)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gap%3D1%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%2C%0A%20%20%20%20%20%20%20%20gap%3D2%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B_plot%2C%20_audio%5D%2C%20gap%3D3)%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
7c4e69c52becb4169fb9bbe465f9955d