import tkinter as tk
from tkinter import ttk, messagebox
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import pyaudio
import threading
import time

CHUNK = 1024
FORMAT = pyaudio.paFloat32
CHANNELS = 2
RATE = 44100

class BinauralTomatisController:
    def __init__(self):
        self.is_playing = False
        self.volume = 0.5
        self.base_freq = 200
        self.theta_freq = 6
        self.min_freq = 500
        self.max_freq = 8000
        self.switch_rate = 0.5
        self.use_gate = False
        self.gate_speed = 4
        self.use_reverb = True
        self.p = pyaudio.PyAudio()
        self.stream = None
        self.phase = 0

    def start(self):
        if not self.is_playing:
            self.stream = self.p.open(format=FORMAT,
                                      channels=CHANNELS,
                                      rate=RATE,
                                      output=True,
                                      frames_per_buffer=CHUNK,
                                      stream_callback=self.callback)
            self.stream.start_stream()
            self.is_playing = True
            print("Starting Binaural Tomatis Method")

    def stop(self):
        if self.is_playing:
            self.stream.stop_stream()
            self.stream.close()
            self.is_playing = False
            print("Stopping Binaural Tomatis Method")

    def callback(self, in_data, frame_count, time_info, status):
        t = np.arange(self.phase, self.phase + frame_count) / RATE
        self.phase += frame_count

        left_channel = np.sin(2 * np.pi * self.base_freq * t)
        right_channel = np.sin(2 * np.pi * (self.base_freq + self.theta_freq) * t)
        
        data = np.column_stack((left_channel, right_channel))

        # Apply Tomatis effects
        data = self.apply_filter(data, t)
        if self.use_gate:
            data = self.apply_gating(data, t)
        if self.use_reverb:
            data = self.apply_reverb(data)

        data = data * self.volume
        return (data.astype(np.float32).tobytes(), pyaudio.paContinue)

    def apply_filter(self, data, t):
        cutoff = self.min_freq + (self.max_freq - self.min_freq) * (np.sin(2 * np.pi * self.switch_rate * t[0]) + 1) / 2
        b, a = self.butter_highpass(cutoff, RATE, order=5)
        return self.filter(b, a, data)

    def apply_gating(self, data, t):
        gate = np.sin(2 * np.pi * self.gate_speed * t) > 0
        return data * gate[:, np.newaxis]

    def apply_reverb(self, data):
        reverb = np.zeros_like(data)
        reverb[50:] = data[:-50] * 0.3
        return data + reverb

    @staticmethod
    def butter_highpass(cutoff, fs, order=5):
        nyq = 0.5 * fs
        normal_cutoff = cutoff / nyq
        b, a = np.zeros(order + 1), np.zeros(order + 1)
        b[0], b[-1] = 1, -1
        a[0], a[1] = 1, -np.exp(-2 * np.pi * normal_cutoff)
        return b, a

    @staticmethod
    def filter(b, a, data):
        return np.apply_along_axis(lambda m: np.convolve(m, b, mode='same'), axis=0, arr=data)

    def change_volume(self, volume):
        self.volume = volume
        print(f"Changing volume: {volume}")

    def change_frequency(self, base, theta):
        self.base_freq = base
        self.theta_freq = theta
        print(f"Changing frequency: Base {base}Hz, Theta {theta}Hz")

    def __del__(self):
        if self.stream:
            self.stream.stop_stream()
            self.stream.close()
        self.p.terminate()

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Binaural Tomatis Method Controller")
        self.geometry("800x700")

        self.controller = BinauralTomatisController()

        self.create_widgets()
        self.create_visualization()

    def create_widgets(self):
        frame = ttk.Frame(self)
        frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

        ttk.Button(frame, text="Start", command=self.start_audio).grid(row=0, column=0, padx=5, pady=5)
        ttk.Button(frame, text="Stop", command=self.stop_audio).grid(row=0, column=1, padx=5, pady=5)

        ttk.Label(frame, text="Volume:").grid(row=1, column=0, padx=5, pady=5)
        self.volume_scale = ttk.Scale(frame, from_=0, to=1, orient=tk.HORIZONTAL, command=self.on_volume_change)
        self.volume_scale.set(0.5)
        self.volume_scale.grid(row=1, column=1, columnspan=2, padx=5, pady=5, sticky="ew")

        ttk.Label(frame, text="Base Frequency (Hz):").grid(row=2, column=0, padx=5, pady=5)
        self.base_freq_entry = ttk.Entry(frame)
        self.base_freq_entry.insert(0, "200")
        self.base_freq_entry.grid(row=2, column=1, padx=5, pady=5)

        ttk.Label(frame, text="Theta Frequency (Hz):").grid(row=3, column=0, padx=5, pady=5)
        self.theta_freq_entry = ttk.Entry(frame)
        self.theta_freq_entry.insert(0, "6")
        self.theta_freq_entry.grid(row=3, column=1, padx=5, pady=5)

        ttk.Label(frame, text="Min Frequency (Hz):").grid(row=4, column=0, padx=5, pady=5)
        self.min_freq_entry = ttk.Entry(frame)
        self.min_freq_entry.insert(0, "500")
        self.min_freq_entry.grid(row=4, column=1, padx=5, pady=5)

        ttk.Label(frame, text="Max Frequency (Hz):").grid(row=5, column=0, padx=5, pady=5)
        self.max_freq_entry = ttk.Entry(frame)
        self.max_freq_entry.insert(0, "8000")
        self.max_freq_entry.grid(row=5, column=1, padx=5, pady=5)

        ttk.Label(frame, text="Switch Rate (Hz):").grid(row=6, column=0, padx=5, pady=5)
        self.switch_rate_entry = ttk.Entry(frame)
        self.switch_rate_entry.insert(0, "0.5")
        self.switch_rate_entry.grid(row=6, column=1, padx=5, pady=5)

        self.use_gate_var = tk.BooleanVar()
        ttk.Checkbutton(frame, text="Use Gating", variable=self.use_gate_var).grid(row=7, column=0, padx=5, pady=5)

        ttk.Label(frame, text="Gate Speed (Hz):").grid(row=8, column=0, padx=5, pady=5)
        self.gate_speed_entry = ttk.Entry(frame)
        self.gate_speed_entry.insert(0, "4")
        self.gate_speed_entry.grid(row=8, column=1, padx=5, pady=5)

        self.use_reverb_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(frame, text="Use Reverb", variable=self.use_reverb_var).grid(row=9, column=0, padx=5, pady=5)

        ttk.Button(frame, text="Update Settings", command=self.update_settings).grid(row=10, column=0, columnspan=2, padx=5, pady=5)

    def create_visualization(self):
        self.fig, self.ax = plt.subplots(figsize=(5, 3))
        self.canvas = FigureCanvasTkAgg(self.fig, master=self)
        self.canvas_widget = self.canvas.get_tk_widget()
        self.canvas_widget.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
        self.update_plot()

    def start_audio(self):
        self.controller.start()

    def stop_audio(self):
        self.controller.stop()

    def on_volume_change(self, value):
        volume = float(value)
        self.controller.change_volume(volume)

    def update_settings(self):
        try:
            base_freq = float(self.base_freq_entry.get())
            theta_freq = float(self.theta_freq_entry.get())
            self.controller.change_frequency(base_freq, theta_freq)
            
            self.controller.min_freq = float(self.min_freq_entry.get())
            self.controller.max_freq = float(self.max_freq_entry.get())
            self.controller.switch_rate = float(self.switch_rate_entry.get())
            self.controller.use_gate = self.use_gate_var.get()
            self.controller.gate_speed = float(self.gate_speed_entry.get())
            self.controller.use_reverb = self.use_reverb_var.get()
            
            self.update_plot()
            messagebox.showinfo("Success", "Settings updated successfully")
        except ValueError:
            messagebox.showerror("Error", "Invalid input values")

    def update_plot(self):
        self.ax.clear()
        t = np.linspace(0, 0.1, 1000)
        left_channel = np.sin(2 * np.pi * self.controller.base_freq * t)
        right_channel = np.sin(2 * np.pi * (self.controller.base_freq + self.controller.theta_freq) * t)
        
        self.ax.plot(t, left_channel, label='Left')
        self.ax.plot(t, right_channel, label='Right')
        self.ax.set_title('Binaural Beat Waveforms')
        self.ax.set_xlabel('Time (s)')
        self.ax.set_ylabel('Amplitude')
        self.ax.legend()
        self.canvas.draw()

if __name__ == "__main__":
    app = Application()
    app.mainloop()