import numpy as np
from vispy import app, scene
from vispy.color import Colormap
import sounddevice as sd

# -------------------------------
# Audio
# -------------------------------
sample_rate = 44100
duration = 0.1

class AudioPlayer:
    def __init__(self):
        self.current_freq = None
        self.playing = False

    def play_tone(self, frequency):
        if self.current_freq != frequency or not self.playing:
            sd.stop()
            self.current_freq = frequency
            self.playing = True
            t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
            waveform = 0.2 * np.sin(2 * np.pi * frequency * t)
            sd.play(waveform, samplerate=sample_rate, loop=True)

    def stop(self):
        self.playing = False
        sd.stop()

audio_player = AudioPlayer()

def note_to_freq(note_val):
    return 220.0 * 2 ** (note_val / 12)

# -------------------------------
# Simple cartesian/polar generation
# -------------------------------
Nx, Ny = 100, 100
x = np.linspace(-1, 1, Nx)
y = np.linspace(-1, 1, Ny)
Xg, Yg = np.meshgrid(x, y)

phi = (1 + 5**0.5)/2

def generate_cartesian(X, Y, t):
    return np.sin(phi * np.pi * X + t) * np.cos(phi * np.pi * Y + t)

def generate_polar(X, Y, t):
    R = np.sqrt(X**2 + Y**2)
    Theta = np.arctan2(Y, X)
    return np.sin(R * 6 + t) * np.cos(Theta * 3 + t)

# -------------------------------
# VisPy Scene Setup
# -------------------------------
canvas = scene.SceneCanvas(keys='interactive', show=True, bgcolor='black', size=(800,800))
view = canvas.central_widget.add_view()
view.camera = scene.TurntableCamera(fov=45, azimuth=30, elevation=30, distance=4)

# Initial surface
Z = generate_cartesian(Xg, Yg, 0)
surface = scene.visuals.SurfacePlot(x=Xg, y=Yg, z=Z, color=(0.3,0.6,1,1), shading='smooth', parent=view.scene)

# -------------------------------
# Animation loop
# -------------------------------
t = 0.0
morph = 0.0
note_val = 36.0

def update(event):
    global t, morph, note_val
    t += 0.03
    morph += 0.002  # auto-scan morph
    if morph > 1.0: morph = 0.0

    f_query = note_to_freq(note_val)
    audio_player.play_tone(f_query)

    Z_cart = generate_cartesian(Xg, Yg, t)
    Z_polar = generate_polar(Xg, Yg, t)
    Z_new = (1 - morph) * Z_cart + morph * Z_polar
    surface.set_data(z=Z_new)

timer = app.Timer(interval=1/60, connect=update, start=True)
app.run()
audio_player.stop()
