Skip to content

Commit 023d622

Browse files
authored
Add TFT demo for invariance and audio synthesis
This script demonstrates tensor invariance and φ-locking, generating audio and visual outputs.
1 parent ba276cc commit 023d622

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

src/example.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python3
2+
"""
3+
TFT demo: invariance + φ-lock → plot + WAV
4+
- Builds SPD tensor T
5+
- Rotates: T' = R T R^T
6+
- Shows invariants unchanged
7+
- Maps eigenvalues → audio freqs, synthesizes φ-locked stereo tone
8+
- Saves: figures/phase_cube.png, figures/tensor_demo.wav
9+
"""
10+
11+
from __future__ import annotations
12+
import os
13+
import pathlib
14+
import numpy as np
15+
import matplotlib.pyplot as plt
16+
from scipy.io import wavfile # lightweight, part of scipy
17+
18+
from tft.resonance import (
19+
spd_matrix, rotation_matrix, rotate_rank2, invariants,
20+
map_to_audio, phi_lock_pair, rng
21+
)
22+
23+
ROOT = pathlib.Path(__file__).resolve().parents[1]
24+
FIGDIR = ROOT / "figures"
25+
FIGDIR.mkdir(parents=True, exist_ok=True)
26+
27+
def synth_stereo(freqs, sr=48_000, dur=2.0, seed=1337):
28+
"""Sum of cos tones, right channel is φ=π/2 quadrature of left."""
29+
g = rng(seed)
30+
t = np.linspace(0.0, dur, int(sr*dur), endpoint=False)
31+
# Left: sum cos(2π f t) with tiny random phases for richness (seeded)
32+
phases = g.uniform(0, 2*np.pi, size=len(freqs))
33+
left = np.sum([np.cos(2*np.pi*f*t + p) for f, p in zip(freqs, phases)], axis=0)
34+
# Right: quadrature partner (φ-lock) using analytic signal
35+
_, right = phi_lock_pair(left)
36+
# Normalize to avoid clipping
37+
peak = max(np.max(np.abs(left)), np.max(np.abs(right)), 1e-9)
38+
left /= peak
39+
right /= peak
40+
stereo = np.stack([left, right], axis=-1).astype(np.float32)
41+
return 48_000, stereo
42+
43+
def plot_phase_cube(freqs, savepath):
44+
"""
45+
Toy 'phase cube' visualization:
46+
- x: normalized freq index
47+
- y: frequency (Hz) normalized
48+
- z: time phase sweep (0..2π)
49+
"""
50+
n = len(freqs)
51+
idx = np.linspace(0, 1, n)
52+
f_norm = (freqs - freqs.min()) / max(freqs.ptp(), 1e-9)
53+
phi = np.linspace(0, 2*np.pi, 400)
54+
X, Z = np.meshgrid(idx, phi)
55+
Y = np.interp(X, np.linspace(0,1,n), f_norm)
56+
# Simple quadrature surface
57+
V = np.cos(2*np.pi*Y*Z) # arbitrary “vibration” field
58+
fig = plt.figure(figsize=(7, 5))
59+
ax = fig.add_subplot(111, projection="3d")
60+
ax.plot_surface(X, Y, V, linewidth=0, antialiased=True, alpha=0.9)
61+
ax.set_xlabel("Index (space)")
62+
ax.set_ylabel("Freq (conjugate)")
63+
ax.set_zlabel("Phase (time)")
64+
ax.set_title("TFT Phase Cube (toy)")
65+
fig.tight_layout()
66+
fig.savefig(savepath, dpi=140)
67+
plt.close(fig)
68+
69+
def main():
70+
seed = int(os.environ.get("TFT_SEED", "1337"))
71+
dim = int(os.environ.get("TFT_DIM", "3"))
72+
73+
T = spd_matrix(dim=dim, seed=seed)
74+
R = rotation_matrix(dim=dim, seed=seed + 1)
75+
T_rot = rotate_rank2(T, R)
76+
77+
inv0 = invariants(T)
78+
inv1 = invariants(T_rot)
79+
80+
print("=== TFT Invariants Demo ===")
81+
print(f"Frobenius norm : {inv0['fro']:.8f} -> {inv1['fro']:.8f}")
82+
print(f"Eigenvalues : {inv0['eigvals']} -> {inv1['eigvals']}")
83+
print("(Should match up to numeric noise.)")
84+
85+
# Audio mapping
86+
freqs = map_to_audio(inv0["eigvals"], fmin=220.0, fmax=880.0)
87+
sr, stereo = synth_stereo(freqs, sr=48_000, dur=2.0, seed=seed)
88+
89+
wav_out = FIGDIR / "tensor_demo.wav"
90+
wavfile.write(wav_out.as_posix(), sr, stereo)
91+
print(f"Wrote WAV: {wav_out}")
92+
93+
png_out = FIGDIR / "phase_cube.png"
94+
plot_phase_cube(freqs, png_out.as_posix())
95+
print(f"Wrote figure: {png_out}")
96+
97+
if __name__ == "__main__":
98+
main()

0 commit comments

Comments
 (0)