import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import sounddevice as sd
import threading

# -------------------------------
# Parameters
# -------------------------------
Nx, Ny = 300, 300
x = np.linspace(0, 1, Nx)
y = np.linspace(0, 1, Ny)
Xg, Yg = np.meshgrid(x, y)

sample_rate = 44100
duration = 1.0  # seconds
audio_lock = threading.Lock()
current_stream = None

# -------------------------------
# Example cymatic params
# Replace with your tuned φ-hash prediction
# -------------------------------
cymatic_params = {
    "alpha": 3.07,
    "beta": 6.08,
    "eta": 2.17,
    "zeta": 3.35
}

# -------------------------------
# Morphing function
# -------------------------------
def morph_coords(X, Y, R, Theta, morph_val):
    # morph_val = 0 -> Cartesian, 1 -> Polar
    Xm = (1 - morph_val)*X + morph_val*R*np.cos(Theta)
    Ym = (1 - morph_val)*Y + morph_val*R*np.sin(Theta)
    return Xm, Ym

# Precompute polar coordinates
R = np.sqrt(Xg**2 + Yg**2)
Theta = np.arctan2(Yg, Xg)

# -------------------------------
# Audio playback
# -------------------------------
def play_tone(frequency, amplitude=0.2):
    global current_stream
    t = np.linspace(0, duration, int(sample_rate*duration), endpoint=False, dtype=np.float32)
    waveform = (amplitude * np.sin(2*np.pi*frequency*t)).astype(np.float32)
    with audio_lock:
        sd.stop()  # stop previous tone if still playing
        sd.play(waveform, samplerate=sample_rate)

# -------------------------------
# Initial figure
# -------------------------------
fig, ax = plt.subplots(figsize=(6,6))
plt.subplots_adjust(bottom=0.25)

Z = np.sin(cymatic_params["alpha"]*np.pi*Xg)*np.sin(cymatic_params["beta"]*np.pi*Yg) \
    + cymatic_params["eta"]*np.cos(cymatic_params["zeta"]*np.pi*(Xg+Yg))
contour_plot = ax.contour(Xg, Yg, Z, levels=[0], colors='black')
ax.set_title("Cymatic Pattern")
ax.axis('off')

# -------------------------------
# Sliders
# -------------------------------
morph_ax = plt.axes([0.15, 0.1, 0.65, 0.03])
morph_slider = Slider(morph_ax, "Morph", 0.0, 1.0, valinit=0.0)

note_ax = plt.axes([0.15, 0.05, 0.65, 0.03])
note_slider = Slider(note_ax, "Note", 36.0, 72.0, valinit=36.18)  # unlimited semitone range

# -------------------------------
# Update function
# -------------------------------
def update(val):
    morph_val = morph_slider.val
    note_val = note_slider.val
    
    # update coordinates
    Xm, Ym = morph_coords(Xg, Yg, R, Theta, morph_val)
    Z = np.sin(cymatic_params["alpha"]*np.pi*Xm)*np.sin(cymatic_params["beta"]*np.pi*Ym) \
        + cymatic_params["eta"]*np.cos(cymatic_params["zeta"]*np.pi*(Xm+Ym))
    
    # clear previous contours and draw new
    ax.cla()
    ax.contour(Xm, Ym, Z, levels=[0], colors='black')
    ax.axis('off')
    ax.set_title(f"Cymatic Pattern at Note {note_val:.2f}")
    
    # play corresponding tone (A4=440Hz, note in semitones relative to C0)
    freq = 16.35 * 2**(note_val/12)
    threading.Thread(target=play_tone, args=(freq,), daemon=True).start()
    
    fig.canvas.draw_idle()

# -------------------------------
# Connect sliders
# -------------------------------
morph_slider.on_changed(update)
note_slider.on_changed(update)

plt.show()
