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
            self.base_freq = base
            self.theta_freq = 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.source_sound = None
        self.processed_sound = None
        self.dynamic_filter = None
        self.filter_freq_lfo = None
        self.gate_lfo = None

    def create_waveform(self, wave_type, freq, mul):
        """Creates a waveform generator based on the selected type."""
        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 SuperSaw(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:
                # Load audio file or generate waveform
                file_path = settings.get('audio_file')
                wave_type = settings.get('waveform', 'Sine')
                freq = settings.get('frequency', 440)
                volume = settings.get('volume', 0.5)

                if file_path:
                    self.source_sound = SfPlayer(file_path, loop=True, mul=volume)
                else:
                    self.source_sound = self.create_waveform(wave_type, freq, volume)

                # Apply dynamic filtering
                min_freq = settings.get('min_freq', 500)
                max_freq = settings.get('max_freq', 8000)
                switch_rate = settings.get('switch_rate', 0.5)  # Hz

                # Create a square wave LFO to switch between frequencies
                self.filter_freq_lfo = LFO(freq=switch_rate, sharp=0.99, type=1,
                                           mul=(max_freq - min_freq)/2,
                                           add=(max_freq + min_freq)/2)
                self.dynamic_filter = ButBP(self.source_sound, freq=self.filter_freq_lfo)

                # Apply gating if enabled
                use_gate = settings.get('use_gate', False)
                if use_gate:
                    gate_speed = settings.get('gate_speed', 4)  # Hz
                    self.gate_lfo = Sine(freq=gate_speed, mul=0.5, add=0.5)
                    self.gated_sound = self.dynamic_filter * self.gate_lfo
                else:
                    self.gated_sound = self.dynamic_filter

                # Add reverb if enabled
                use_reverb = settings.get('use_reverb', True)
                if use_reverb:
                    self.processed_sound = Freeverb(self.gated_sound, size=0.8, damp=0.5, bal=0.3).out()
                else:
                    self.processed_sound = self.gated_sound.out()

                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.processed_sound:
                self.processed_sound.stop()
                self.processed_sound = None
            if self.gate_lfo:
                self.gate_lfo.stop()
                self.gate_lfo = None
            if self.filter_freq_lfo:
                self.filter_freq_lfo.stop()
                self.filter_freq_lfo = None
            if self.dynamic_filter:
                self.dynamic_filter.stop()
                self.dynamic_filter = None
            if self.source_sound:
                self.source_sound.stop()
                self.source_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)

        # Audio file selection
        ttk.Label(tomatis_settings_frame, text="Audio File:").pack(pady=5, padx=5, anchor='w')

        file_selection_frame = ttk.Frame(tomatis_settings_frame)
        file_selection_frame.pack(pady=5, padx=5, fill='x')

        self.audio_file_path = tk.StringVar()
        audio_file_entry = ttk.Entry(file_selection_frame, textvariable=self.audio_file_path, width=60)
        audio_file_entry.pack(side='left', padx=5, fill='x', expand=True)

        ttk.Button(file_selection_frame, text="Browse", command=self.browse_audio_file).pack(side='left', padx=5)

        # Volume
        ttk.Label(tomatis_settings_frame, text="Volume:").pack(pady=5, padx=5, anchor="w")
        self.tomatis_volume_scale = ttk.Scale(tomatis_settings_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL, command=self.on_tomatis_volume_change)
        self.tomatis_volume_scale.set(0.5)
        self.tomatis_volume_scale.pack(pady=5, padx=5, fill='x')

        # Min Frequency
        ttk.Label(tomatis_settings_frame, text="Minimum Frequency (Hz):").pack(pady=5, padx=5, anchor="w")
        self.tomatis_min_freq_entry = ttk.Entry(tomatis_settings_frame)
        self.tomatis_min_freq_entry.insert(0, "500")
        self.tomatis_min_freq_entry.pack(pady=5, padx=5, fill='x')

        # Max Frequency
        ttk.Label(tomatis_settings_frame, text="Maximum Frequency (Hz):").pack(pady=5, padx=5, anchor="w")
        self.tomatis_max_freq_entry = ttk.Entry(tomatis_settings_frame)
        self.tomatis_max_freq_entry.insert(0, "8000")
        self.tomatis_max_freq_entry.pack(pady=5, padx=5, fill='x')

        # Switch Rate
        ttk.Label(tomatis_settings_frame, text="Filter Switch Rate (Hz):").pack(pady=5, padx=5, anchor="w")
        self.tomatis_switch_rate_entry = ttk.Entry(tomatis_settings_frame)
        self.tomatis_switch_rate_entry.insert(0, "0.5")
        self.tomatis_switch_rate_entry.pack(pady=5, padx=5, fill='x')

        # Use Gate
        self.tomatis_use_gate = tk.BooleanVar()
        ttk.Checkbutton(tomatis_settings_frame, text="Use Gating", variable=self.tomatis_use_gate).pack(pady=5, padx=5, anchor="w")

        # Gate Speed
        ttk.Label(tomatis_settings_frame, text="Gate Speed (Hz):").pack(pady=5, padx=5, anchor="w")
        self.tomatis_gate_speed_entry = ttk.Entry(tomatis_settings_frame)
        self.tomatis_gate_speed_entry.insert(0, "4")
        self.tomatis_gate_speed_entry.pack(pady=5, padx=5, fill='x')

        # Use Reverb
        self.tomatis_use_reverb = tk.BooleanVar(value=True)
        ttk.Checkbutton(tomatis_settings_frame, text="Use Reverb", variable=self.tomatis_use_reverb).pack(pady=5, padx=5, anchor="w")

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

        ttk.Button(control_frame, text="Start Tomatis", command=self.start_tomatis).pack(side='left', padx=5)
        ttk.Button(control_frame, text="Stop Tomatis", command=self.stop_tomatis).pack(side='left', padx=5)

    def browse_audio_file(self):
        file_path = filedialog.askopenfilename(filetypes=[("Audio Files", "*.wav;*.mp3"), ("All Files", "*.*")],
                                               title="Select Audio File")
        if file_path:
            self.audio_file_path.set(file_path)

    def on_tomatis_volume_change(self, val):
        volume = float(val)
        if self.tomatis_controller.source_sound is not None:
            self.tomatis_controller.source_sound.mul = volume

    def start_tomatis(self):
        try:
            settings = {
                'audio_file': self.audio_file_path.get() if self.audio_file_path.get() else None,
                'waveform': 'Sine',  # Default waveform if no audio file is provided
                'frequency': 440,    # Default frequency if generating a waveform
                'volume': float(self.tomatis_volume_scale.get()),
                'min_freq': float(self.tomatis_min_freq_entry.get()),
                'max_freq': float(self.tomatis_max_freq_entry.get()),
                'switch_rate': float(self.tomatis_switch_rate_entry.get()),
                'use_gate': self.tomatis_use_gate.get(),
                'gate_speed': float(self.tomatis_gate_speed_entry.get()) if self.tomatis_use_gate.get() else None,
                'use_reverb': self.tomatis_use_reverb.get()
            }
            self.tomatis_controller.start(settings)
        except ValueError as e:
            messagebox.showerror("Invalid Input", f"Invalid input: {e}")

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

    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": {
                'audio_file': self.audio_file_path.get(),
                'volume': self.tomatis_volume_scale.get(),
                'min_freq': self.tomatis_min_freq_entry.get(),
                'max_freq': self.tomatis_max_freq_entry.get(),
                'switch_rate': self.tomatis_switch_rate_entry.get(),
                'use_gate': self.tomatis_use_gate.get(),
                'gate_speed': self.tomatis_gate_speed_entry.get(),
                'use_reverb': self.tomatis_use_reverb.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", {})
                self.audio_file_path.set(tomatis.get('audio_file', ''))
                self.tomatis_volume_scale.set(float(tomatis.get('volume', 0.5)))
                self.tomatis_min_freq_entry.delete(0, tk.END)
                self.tomatis_min_freq_entry.insert(0, tomatis.get('min_freq', 500))
                self.tomatis_max_freq_entry.delete(0, tk.END)
                self.tomatis_max_freq_entry.insert(0, tomatis.get('max_freq', 8000))
                self.tomatis_switch_rate_entry.delete(0, tk.END)
                self.tomatis_switch_rate_entry.insert(0, tomatis.get('switch_rate', 0.5))
                self.tomatis_use_gate.set(tomatis.get('use_gate', False))
                self.tomatis_gate_speed_entry.delete(0, tk.END)
                self.tomatis_gate_speed_entry.insert(0, tomatis.get('gate_speed', 4))
                self.tomatis_use_reverb.set(tomatis.get('use_reverb', True))

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

    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():
            self.binaural_controller.start()
            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)
            self.binaural_controller.stop()
            # 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)

    # Other methods remain unchanged...

# Part 8: Main Loop and Cleanup
def main():
    err = ""
    audio_apis = ["portaudio", "jack", "alsa"]  # 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() python3 binaural_theta06.10.py 

WxPython is not found for the current python version.
Pyo will use a minimal GUI toolkit written with Tkinter (if available).
This toolkit has limited functionnalities and is no more
maintained or updated. If you want to use all of pyo's
GUI features, you should install WxPython, available here:
http://www.wxpython.org/

ALSA lib pcm_dmix.c:1032:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2664:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2664:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2664:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map
Expression 'alsa_snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 929
Expression 'alsa_snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, &lowLatency )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 929
ALSA lib pcm_oss.c:397:(_snd_pcm_oss_open) Cannot open device /dev/dsp
ALSA lib pcm_oss.c:397:(_snd_pcm_oss_open) Cannot open device /dev/dsp
ALSA lib confmisc.c:160:(snd_config_get_card) Invalid field card
ALSA lib pcm_usb_stream.c:482:(_snd_pcm_usb_stream_open) Invalid card 'card'
ALSA lib confmisc.c:160:(snd_config_get_card) Invalid field card
ALSA lib pcm_usb_stream.c:482:(_snd_pcm_usb_stream_open) Invalid card 'card'
ALSA lib pcm_dmix.c:1032:(snd_pcm_dmix_open) unable to open slave
Pyo warning: Portaudio output device `HDA ATI SB: ALC662 rev1 Analog (hw:0,0)` has fewer channels (0) than requested (2).
Portaudio error in Pa_OpenStream: Invalid number of channels
Pyo error: From portaudio, Invalid number of channels
Portaudio error in Pa_CloseStream (pa_deinit): PortAudio not initialized
Portaudio error in Pa_Terminate (pa_deinit): PortAudio not initialized
Pyo error: 
Server not booted.
Using audio API: portaudio
(pyo_env) m@m:~/musicpython$ 
напиши полный код