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() реализуй в  скрипте вот это: метод Томатис основан на принципе “удивления” мозга, который лучше всего воспринимает внезапные изменения звуков, которые не может предвидеть. Во время сеансов фильтрованный звук определенных частот воздействует на мышцы среднего уха 
Из «обычного» звука поочередно «вырезают» то высокочастотные, то низкочастотные компоненты. Во время прослушивания такой записи мышцы уха (мышцы молоточка и стремечка) тренируются, поочередно напрягаясь и расслабляясь - задача  закодировать в коде  смоделировать такоеже воздействие