from pyo import *
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
import threading
import time
import json
import datetime
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# Part 2: Therapy Programs and Journal
class TherapyProgram:
    def __init__(self, name, description, stages):
        self.name = name
        self.description = description
        self.stages = stages  # List of (frequency, duration) tuples

def create_default_programs():
    return [
        TherapyProgram("Relaxation", "A program to help you relax and reduce stress",
                       [(10, 300), (8, 300), (6, 300), (4, 300)]),
        TherapyProgram("Focus", "Enhance your concentration and mental clarity",
                       [(14, 300), (12, 300), (10, 300), (8, 300)]),
        TherapyProgram("Sleep", "Prepare your mind for a restful sleep",
                       [(7, 600), (5, 600), (3, 600)])
    ]

class SessionEntry:
    def __init__(self, date, duration, program, notes):
        self.date = date
        self.duration = duration
        self.program = program
        self.notes = notes

class Journal:
    def __init__(self):
        self.entries = []

    def add_entry(self, entry):
        self.entries.append(entry)

    def get_entries(self):
        return self.entries

# Part 3: Core Functions
class BinauralBeatsController:
    def __init__(self, server, ax, canvas, root):
        self.server = server
        self.ax = ax
        self.canvas = canvas
        self.root = root  # Store the root window
        self.is_playing = False
        self.left_sine = None
        self.right_sine = None
        self.env = None
        self.setup_tones()

    def setup_tones(self):
        # Parameters for Binaural Beats
        self.base_freq = 200  # Base frequency in Hz
        self.theta_freq = 6   # Theta rhythm frequency in Hz

        if self.server is not None and self.server.getIsBooted():
            # Create Sine wave generators for left and right ears
            self.left_sine = Sine(freq=self.base_freq, mul=0.5)
            self.right_sine = Sine(freq=self.base_freq + self.theta_freq, mul=0.5)

            # Envelope modulation for smooth volume changes
            self.env = Sine(freq=0.1, mul=0.1, add=0.9)
            self.left_sine.mul = self.env
            self.right_sine.mul = self.env

            # Do not connect to output yet; handle in start()
        else:
            print("Server not booted. Cannot create audio objects.")
            self.left_sine = None
            self.right_sine = None
            self.env = None

    def change_frequency(self, base, theta):
        if self.left_sine is not None and self.right_sine is not None:
            self.left_sine.freq = base
            self.right_sine.freq = base + theta

    def change_volume(self, volume):
        if self.env is not None:
            self.env.mul = volume * 0.1
            self.env.add = 0.9

    def start(self):
        if not self.is_playing and self.left_sine is not None and self.right_sine is not None:
            # Output to left and right channels
            self.left_sine.out(0)
            self.right_sine.out(1)
            self.is_playing = True
            self.update_plot()

    def stop(self):
        if self.is_playing and self.left_sine is not None and self.right_sine is not None:
            self.left_sine.stop()
            self.right_sine.stop()
            self.is_playing = False
            self.ax.clear()
            self.canvas.draw()

    def update_plot(self):
        self.ax.clear()
        t = np.linspace(0, 0.1, 1000)  # 100 ms window
        if self.left_sine is not None and self.right_sine is not None:
            freq_left = self.left_sine.freq.get()
            freq_right = self.right_sine.freq.get()
            left_wave = np.sin(2 * np.pi * freq_left * t)
            right_wave = np.sin(2 * np.pi * freq_right * t)
            self.ax.plot(t, left_wave, label='Left', color='blue')
            self.ax.plot(t, right_wave, label='Right', color='red')
            self.ax.set_title('Binaural Beat Waveforms')
            self.ax.set_xlabel('Time (s)')
            self.ax.set_ylabel('Amplitude')
            self.ax.legend()
            self.ax.set_ylim(-1, 1)
        self.canvas.draw()
        if self.is_playing:
            self.root.after(100, self.update_plot)  # Update every 100 ms using root

# Part 5: Tomatis Method
class TomatisMethodController:
    def __init__(self, server):
        self.server = server
        self.is_playing = False
        self.left_sound = None
        self.left_filter = None
        self.left_reverb = None
        self.right_sound = None
        self.right_filter = None
        self.right_reverb = None

    def create_waveform(self, wave_type, freq, mul, file_path=None):
        """Creates a waveform generator based on the selected type or loads a file."""
        if file_path:
            try:
                return SfPlayer(file_path, loop=True, mul=mul)
            except Exception as e:
                print(f"Error loading file {file_path}: {e}")
                return None
        if wave_type == "Sine":
            return Sine(freq=freq, mul=mul)
        elif wave_type == "Triangle":
            return Triangle(freq=freq, mul=mul)
        elif wave_type == "Sawtooth":
            return Saw(freq=freq, mul=mul)
        elif wave_type == "Square":
            return Square(freq=freq, mul=mul)
        else:
            print(f"Unknown waveform type: {wave_type}. Using Sine.")
            return Sine(freq=freq, mul=mul)

    def start(self, settings):
        if not self.is_playing:
            print("Starting Tomatis Method...")
            try:
                # Left Ear
                left_freq = float(settings['left_ear']['filter_freq'])
                left_q = float(settings['left_ear']['filter_q'])
                left_volume = float(settings['left_ear']['volume'])
                left_wave = settings['left_ear']['waveform']
                self.left_sound = self.create_waveform(left_wave, left_freq, left_volume)
                self.left_filter = ButBP(self.left_sound, freq=left_freq, q=left_q)
                self.left_reverb = Freeverb(self.left_filter, size=0.8, damp=0.5, bal=0.3).out(0)

                # Right Ear
                right_freq = float(settings['right_ear']['filter_freq'])
                right_q = float(settings['right_ear']['filter_q'])
                right_volume = float(settings['right_ear']['volume'])
                right_wave = settings['right_ear']['waveform']
                self.right_sound = self.create_waveform(right_wave, right_freq, right_volume)
                self.right_filter = ButBP(self.right_sound, freq=right_freq, q=right_q)
                self.right_reverb = Freeverb(self.right_filter, size=0.8, damp=0.5, bal=0.3).out(1)

                self.is_playing = True
            except Exception as e:
                print(f"Error starting Tomatis Method: {e}")

    def stop(self):
        if self.is_playing:
            print("Stopping Tomatis Method...")
            if self.left_reverb:
                self.left_reverb.stop()
                self.left_reverb = None
            if self.left_filter:
                self.left_filter.stop()
                self.left_filter = None
            if self.left_sound:
                self.left_sound.stop()
                self.left_sound = None
            if self.right_reverb:
                self.right_reverb.stop()
                self.right_reverb = None
            if self.right_filter:
                self.right_filter.stop()
                self.right_filter = None
            if self.right_sound:
                self.right_sound.stop()
                self.right_sound = None
            self.is_playing = False

    def update_settings(self, settings):
        if self.is_playing:
            self.stop()
            self.start(settings)

# Part 7: GUI Setup
class Application(tk.Tk):
    def __init__(self, server, programs, journal):
        super().__init__()
        self.server = server
        self.programs = programs
        self.journal = journal
        self.title("Advanced Binaural Beats and Tomatis Method Controller")
        self.geometry("1000x700")

        # Initialize Matplotlib Figure
        self.fig, self.ax = plt.subplots(figsize=(6, 4))
        self.canvas = FigureCanvasTkAgg(self.fig, master=self)
        self.canvas_widget = self.canvas.get_tk_widget()

        # Initialize Controllers
        self.binaural_controller = BinauralBeatsController(server, self.ax, self.canvas, self)  # Pass self (root)
        self.tomatis_controller = TomatisMethodController(server)

        # Create notebook for tabs
        notebook = ttk.Notebook(self)
        notebook.pack(fill=tk.BOTH, expand=True)

        # Binaural Beats Tab
        self.create_binaural_tab(notebook)

        # Tomatis Method Tab
        self.create_tomatis_tab(notebook)

        # Session Journal Tab
        self.create_journal_tab(notebook)

        # Settings Tab
        self.create_settings_tab(notebook)

    def create_binaural_tab(self, notebook):
        binaural_tab = ttk.Frame(notebook)
        notebook.add(binaural_tab, text='Binaural Beats')

        # Binaural Controls
        controls_frame = ttk.LabelFrame(binaural_tab, text="Controls")
        controls_frame.pack(pady=10, padx=10, fill="x")

        # Base Frequency
        ttk.Label(controls_frame, text="Base Frequency (Hz):").grid(row=0, column=0, sticky="w", pady=5, padx=5)
        self.base_freq_entry = ttk.Entry(controls_frame, width=10)
        self.base_freq_entry.insert(0, "200")
        self.base_freq_entry.grid(row=0, column=1, pady=5, padx=5)

        # Theta Frequency
        ttk.Label(controls_frame, text="Theta Frequency (Hz):").grid(row=1, column=0, sticky="w", pady=5, padx=5)
        self.theta_freq_entry = ttk.Entry(controls_frame, width=10)
        self.theta_freq_entry.insert(0, "6")
        self.theta_freq_entry.grid(row=1, column=1, pady=5, padx=5)

        # Volume
        ttk.Label(controls_frame, text="Volume:").grid(row=2, column=0, sticky="w", pady=5, padx=5)
        self.volume_scale = ttk.Scale(controls_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL, command=self.on_binaural_volume_change)
        self.volume_scale.set(0.5)
        self.volume_scale.grid(row=2, column=1, pady=5, padx=5, sticky="we")

        # Update and Control Buttons
        button_frame = ttk.Frame(controls_frame)
        button_frame.grid(row=3, column=0, columnspan=2, pady=10)

        ttk.Button(button_frame, text="Update Parameters", command=self.update_binaural_params).grid(row=0, column=0, padx=5)
        ttk.Button(button_frame, text="Start", command=self.binaural_controller.start).grid(row=0, column=1, padx=5)
        ttk.Button(button_frame, text="Stop", command=self.binaural_controller.stop).grid(row=0, column=2, padx=5)

        # Therapy Programs
        therapy_frame = ttk.LabelFrame(binaural_tab, text="Therapy Programs")
        therapy_frame.pack(pady=10, padx=10, fill="x")

        ttk.Label(therapy_frame, text="Select Program:").grid(row=0, column=0, sticky="w", pady=5, padx=5)
        self.program_dropdown = ttk.Combobox(therapy_frame, values=[p.name for p in self.programs], state="readonly")
        self.program_dropdown.set(self.programs[0].name)
        self.program_dropdown.grid(row=0, column=1, pady=5, padx=5)

        ttk.Button(therapy_frame, text="Run Program", command=self.run_therapy_program).grid(row=0, column=2, pady=5, padx=5)

        # Visualization
        self.canvas_widget.pack(pady=10)  # Matplotlib plot

    def on_binaural_volume_change(self, val):
        volume = float(val)
        self.binaural_controller.change_volume(volume)

    def update_binaural_params(self):
        try:
            base = float(self.base_freq_entry.get())
            theta = float(self.theta_freq_entry.get())
            volume = float(self.volume_scale.get())
            self.binaural_controller.change_frequency(base, theta)
            self.binaural_controller.change_volume(volume)
        except ValueError as e:
            messagebox.showerror("Invalid Input", f"Invalid input: {e}")

    def create_tomatis_tab(self, notebook):
        tomatis_tab = ttk.Frame(notebook)
        notebook.add(tomatis_tab, text='Tomatis Method')

        tomatis_settings_frame = ttk.LabelFrame(tomatis_tab, text="Tomatis Method Settings")
        tomatis_settings_frame.pack(pady=10, padx=10, fill="both", expand=True)

        # Left Ear Settings
        left_ear_frame = ttk.LabelFrame(tomatis_settings_frame, text="Left Ear Settings")
        left_ear_frame.pack(pady=5, padx=5, fill="x")

        # Waveform
        ttk.Label(left_ear_frame, text="Waveform:").grid(row=0, column=0, sticky="w", pady=5, padx=5)
        self.tomatis_left_waveform = ttk.Combobox(left_ear_frame, values=["Sine", "Triangle", "Sawtooth", "Square"], state="readonly")
        self.tomatis_left_waveform.set("Sine")
        self.tomatis_left_waveform.grid(row=0, column=1, pady=5, padx=5)

        # Filter Frequency
        ttk.Label(left_ear_frame, text="Filter Frequency (Hz):").grid(row=1, column=0, sticky="w", pady=5, padx=5)
        self.tomatis_left_freq_entry = ttk.Entry(left_ear_frame, width=10)
        self.tomatis_left_freq_entry.insert(0, "200")
        self.tomatis_left_freq_entry.grid(row=1, column=1, pady=5, padx=5)

        # Filter Q Factor
        ttk.Label(left_ear_frame, text="Filter Q Factor:").grid(row=2, column=0, sticky="w", pady=5, padx=5)
        self.tomatis_left_q_entry = ttk.Entry(left_ear_frame, width=10)
        self.tomatis_left_q_entry.insert(0, "1.0")
        self.tomatis_left_q_entry.grid(row=2, column=1, pady=5, padx=5)

        # Volume
        ttk.Label(left_ear_frame, text="Volume:").grid(row=3, column=0, sticky="w", pady=5, padx=5)
        self.tomatis_left_volume_scale = ttk.Scale(left_ear_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL, command=self.on_tomatis_left_volume_change)
        self.tomatis_left_volume_scale.set(0.3)
        self.tomatis_left_volume_scale.grid(row=3, column=1, pady=5, padx=5, sticky="we")

        # Right Ear Settings
        right_ear_frame = ttk.LabelFrame(tomatis_settings_frame, text="Right Ear Settings")
        right_ear_frame.pack(pady=5, padx=5, fill="x")

        # Waveform
        ttk.Label(right_ear_frame, text="Waveform:").grid(row=0, column=0, sticky="w", pady=5, padx=5)
        self.tomatis_right_waveform = ttk.Combobox(right_ear_frame, values=["Sine", "Triangle", "Sawtooth", "Square"], state="readonly")
        self.tomatis_right_waveform.set("Sine")
        self.tomatis_right_waveform.grid(row=0, column=1, pady=5, padx=5)

        # Filter Frequency
        ttk.Label(right_ear_frame, text="Filter Frequency (Hz):").grid(row=1, column=0, sticky="w", pady=5, padx=5)
        self.tomatis_right_freq_entry = ttk.Entry(right_ear_frame, width=10)
        self.tomatis_right_freq_entry.insert(0, "800")
        self.tomatis_right_freq_entry.grid(row=1, column=1, pady=5, padx=5)

        # Filter Q Factor
        ttk.Label(right_ear_frame, text="Filter Q Factor:").grid(row=2, column=0, sticky="w", pady=5, padx=5)
        self.tomatis_right_q_entry = ttk.Entry(right_ear_frame, width=10)
        self.tomatis_right_q_entry.insert(0, "1.0")
        self.tomatis_right_q_entry.grid(row=2, column=1, pady=5, padx=5)

        # Volume
        ttk.Label(right_ear_frame, text="Volume:").grid(row=3, column=0, sticky="w", pady=5, padx=5)
        self.tomatis_right_volume_scale = ttk.Scale(right_ear_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL, command=self.on_tomatis_right_volume_change)
        self.tomatis_right_volume_scale.set(0.3)
        self.tomatis_right_volume_scale.grid(row=3, column=1, pady=5, padx=5, sticky="we")

        # Control Buttons
        control_frame = ttk.Frame(tomatis_settings_frame)
        control_frame.pack(pady=10)

        ttk.Button(control_frame, text="Update Settings", command=self.update_tomatis_settings).grid(row=0, column=0, padx=5, pady=5)
        ttk.Button(control_frame, text="Start Tomatis", command=self.start_tomatis).grid(row=0, column=1, padx=5, pady=5)
        ttk.Button(control_frame, text="Stop Tomatis", command=self.stop_tomatis).grid(row=0, column=2, padx=5, pady=5)

    def on_tomatis_left_volume_change(self, val):
        volume = float(val)
        # React to left ear volume change
        if self.tomatis_controller.left_sound is not None:
            self.tomatis_controller.left_sound.mul = volume

    def on_tomatis_right_volume_change(self, val):
        volume = float(val)
        # React to right ear volume change
        if self.tomatis_controller.right_sound is not None:
            self.tomatis_controller.right_sound.mul = volume

    def run_therapy_program(self):
        program_name = self.program_dropdown.get()
        program = next((p for p in self.programs if p.name == program_name), None)
        if program is None:
            messagebox.showerror("Error", "Selected program not found.")
            return

        def run_program():
            total_duration = 0
            for theta, duration in program.stages:
                self.binaural_controller.change_frequency(self.binaural_controller.base_freq, theta)
                time.sleep(duration)
                total_duration += duration
            # After program ends, reset frequencies and save session
            self.binaural_controller.change_frequency(self.binaural_controller.base_freq, self.binaural_controller.theta_freq)
            # Schedule save_session to be called in the main thread
            self.root.after(0, lambda: self.save_session(total_duration / 60, program.name))  # duration in minutes

        threading.Thread(target=run_program, daemon=True).start()

    def save_session(self, duration, program):
        notes = simpledialog.askstring("Session Notes", "How do you feel after this session?")
        if notes is None:  # User cancelled the dialog
            notes = ""
        entry = SessionEntry(datetime.datetime.now(), duration, program, notes)
        self.journal.add_entry(entry)

    def create_journal_tab(self, notebook):
        journal_tab = ttk.Frame(notebook)
        notebook.add(journal_tab, text='Session Journal')

        tk.Button(journal_tab, text="View Journal", command=self.view_journal).pack(pady=10)

    def view_journal(self):
        journal_window = tk.Toplevel(self)
        journal_window.title("Session Journal")
        journal_window.geometry("600x500")

        # Create a scrollable frame
        canvas = tk.Canvas(journal_window)
        scrollbar = ttk.Scrollbar(journal_window, orient="vertical", command=canvas.yview)
        scrollable_frame = ttk.Frame(canvas)

        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(
                scrollregion=canvas.bbox("all")
            )
        )

        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        for entry in self.journal.get_entries():
            entry_frame = ttk.Frame(scrollable_frame, relief=tk.RIDGE, borderwidth=1)
            entry_frame.pack(padx=10, pady=5, fill="x")

            date_label = ttk.Label(entry_frame, text=f"Date: {entry.date.strftime('%Y-%m-%d %H:%M:%S')}", font=("Helvetica", 10, "bold"))
            date_label.pack(anchor="w", padx=5, pady=2)

            program_label = ttk.Label(entry_frame, text=f"Program: {entry.program}", font=("Helvetica", 10))
            program_label.pack(anchor="w", padx=5, pady=2)

            duration_label = ttk.Label(entry_frame, text=f"Duration: {entry.duration:.2f} minutes", font=("Helvetica", 10))
            duration_label.pack(anchor="w", padx=5, pady=2)

            notes_label = ttk.Label(entry_frame, text=f"Notes: {entry.notes}", font=("Helvetica", 10), wraplength=550)
            notes_label.pack(anchor="w", padx=5, pady=2)

            separator = ttk.Separator(scrollable_frame, orient='horizontal')
            separator.pack(fill='x', padx=10, pady=5)

    def create_settings_tab(self, notebook):
        settings_tab = ttk.Frame(notebook)
        notebook.add(settings_tab, text='Settings')

        settings_frame = ttk.Frame(settings_tab)
        settings_frame.pack(pady=20, padx=20)

        ttk.Button(settings_frame, text="Save Settings", command=self.save_settings, width=20).pack(pady=10)
        ttk.Button(settings_frame, text="Load Settings", command=self.load_settings, width=20).pack(pady=10)

    def save_settings(self):
        settings = {
            "binaural_beats": {
                "base_freq": self.base_freq_entry.get(),
                "theta_freq": self.theta_freq_entry.get(),
                "volume": self.volume_scale.get()
            },
            "tomatis_method": {
                "left_ear": {
                    "waveform": self.tomatis_left_waveform.get(),
                    "filter_freq": self.tomatis_left_freq_entry.get(),
                    "filter_q": self.tomatis_left_q_entry.get(),
                    "volume": self.tomatis_left_volume_scale.get()
                },
                "right_ear": {
                    "waveform": self.tomatis_right_waveform.get(),
                    "filter_freq": self.tomatis_right_freq_entry.get(),
                    "filter_q": self.tomatis_right_q_entry.get(),
                    "volume": self.tomatis_right_volume_scale.get()
                }
            }
        }
        file_path = filedialog.asksaveasfilename(defaultextension=".json",
                                                 filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
                                                 title="Save Settings")
        if file_path:
            try:
                with open(file_path, 'w') as f:
                    json.dump(settings, f, indent=4)
                messagebox.showinfo("Success", "Settings saved successfully.")
            except Exception as e:
                messagebox.showerror("Error", f"Failed to save settings: {e}")

    def load_settings(self):
        file_path = filedialog.askopenfilename(defaultextension=".json",
                                               filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
                                               title="Load Settings")
        if file_path:
            try:
                with open(file_path, 'r') as f:
                    settings = json.load(f)
                # Binaural Beats Settings
                binaural = settings.get("binaural_beats", {})
                self.base_freq_entry.delete(0, tk.END)
                self.base_freq_entry.insert(0, binaural.get("base_freq", 200))
                self.theta_freq_entry.delete(0, tk.END)
                self.theta_freq_entry.insert(0, binaural.get("theta_freq", 6))
                self.volume_scale.set(float(binaural.get("volume", 0.5)))
                self.binaural_controller.change_frequency(float(binaural.get("base_freq", 200)), float(binaural.get("theta_freq", 6)))
                self.binaural_controller.change_volume(float(binaural.get("volume", 0.5)))

                # Tomatis Method Settings
                tomatis = settings.get("tomatis_method", {})
                left_ear = tomatis.get("left_ear", {})
                self.tomatis_left_waveform.set(left_ear.get("waveform", "Sine"))
                self.tomatis_left_freq_entry.delete(0, tk.END)
                self.tomatis_left_freq_entry.insert(0, left_ear.get("filter_freq", 200))
                self.tomatis_left_q_entry.delete(0, tk.END)
                self.tomatis_left_q_entry.insert(0, left_ear.get("filter_q", 1.0))
                self.tomatis_left_volume_scale.set(float(left_ear.get("volume", 0.3)))

                right_ear = tomatis.get("right_ear", {})
                self.tomatis_right_waveform.set(right_ear.get("waveform", "Sine"))
                self.tomatis_right_freq_entry.delete(0, tk.END)
                self.tomatis_right_freq_entry.insert(0, right_ear.get("filter_freq", 800))
                self.tomatis_right_q_entry.delete(0, tk.END)
                self.tomatis_right_q_entry.insert(0, right_ear.get("filter_q", 1.0))
                self.tomatis_right_volume_scale.set(float(right_ear.get("volume", 0.3)))

                messagebox.showinfo("Success", "Settings loaded successfully.")
            except Exception as e:
                messagebox.showerror("Error", f"Failed to load settings: {e}")

    def start_tomatis(self):
        settings = {
            "left_ear": {
                "waveform": self.tomatis_left_waveform.get(),
                "filter_freq": self.tomatis_left_freq_entry.get(),
                "filter_q": self.tomatis_left_q_entry.get(),
                "volume": self.tomatis_left_volume_scale.get()
            },
            "right_ear": {
                "waveform": self.tomatis_right_waveform.get(),
                "filter_freq": self.tomatis_right_freq_entry.get(),
                "filter_q": self.tomatis_right_q_entry.get(),
                "volume": self.tomatis_right_volume_scale.get()
            }
        }
        self.tomatis_controller.start(settings)

    def stop_tomatis(self):
        self.tomatis_controller.stop()

    def update_tomatis_settings(self):
        settings = {
            "left_ear": {
                "waveform": self.tomatis_left_waveform.get(),
                "filter_freq": self.tomatis_left_freq_entry.get(),
                "filter_q": self.tomatis_left_q_entry.get(),
                "volume": self.tomatis_left_volume_scale.get()
            },
            "right_ear": {
                "waveform": self.tomatis_right_waveform.get(),
                "filter_freq": self.tomatis_right_freq_entry.get(),
                "filter_q": self.tomatis_right_q_entry.get(),
                "volume": self.tomatis_right_volume_scale.get()
            }
        }
        self.tomatis_controller.update_settings(settings)

# Part 8: Main Loop and Cleanup
def main():
    err = ""
    audio_apis = ["alsa", "jack", "portaudio"]  # Preferred order
    server = None

    for api in audio_apis:
        try:
            server = Server(audio=api).boot()
            if api == "portaudio":
                server.setOutputDevice(0)  # Explicitly set output to avoid possible channel issues
            print(f"Using audio API: {api}")
            break  # Server started successfully
        except Exception as e:
            err += f"Error initializing {api}: {e}\n"

    if server is None or not server.getIsBooted():
        root = tk.Tk()  # Create a temporary root window for the message box
        root.withdraw()  # Hide the main window
        messagebox.showerror("Error", f"Could not initialize any audio server:\n{err}")
        root.destroy()
        return

    try:
        server.start()
    except Exception as e:
        root = tk.Tk()  # Create a temporary root window for the message box
        root.withdraw()  # Hide the main window
        messagebox.showerror("Error", f"Could not start audio server:\n{e}")
        root.destroy()
        return

    programs = create_default_programs()
    journal = Journal()
    app = Application(server, programs, journal)
    app.mainloop()

    # Cleanup after closing the app
    server.stop()
    server.shutdown()

if __name__ == "__main__":
    main() улучши ииновационно гененрацию звуков томатис и исправь ошибки в скрипте