import sounddevice as sd
import numpy as np
import threading
import tkinter as tk
from tkinter import ttk
import re

playing_threads = {}

def list_dante_devices():
    """List all unique Dante Virtual Soundcard output devices sorted by channel order."""
    devices = sd.query_devices()
    seen = {}
    
    for d in devices:
        if "DVS Transmit" in d['name'] and d['max_output_channels'] == 2:
            match = re.search(r'(\d+)-', d['name'])  # Extract first number before '-'
            if match:
                channel_number = int(match.group(1))
                if channel_number not in seen:
                    seen[channel_number] = d
    
    return [seen[key] for key in sorted(seen.keys())]

def generate_tone(frequency=440, sample_rate=48000):
    """Generate a continuous sine wave tone."""
    t = np.linspace(0, 1, sample_rate, False)
    wave = 0.5 * np.sin(2 * np.pi * frequency * t)
    return wave.astype(np.float32)

def play_sound_on_device(device_index, channel, frequency=440, sample_rate=48000):
    """Continuously play a sine wave on a specific stereo Dante Virtual Soundcard channel using OutputStream."""
    if channel not in [1, 2]:
        print("Invalid channel. Must be 1 (Left) or 2 (Right).")
        return
    
    tone = generate_tone(frequency, sample_rate)
    output = np.zeros((len(tone), 2), dtype=np.float32)  # Stereo output
    output[:, channel - 1] = tone  # Assign tone to the selected channel
    
    def callback(outdata, frames, time, status):
        if status:
            print(status)
        outdata[:frames] = output[:frames]
    
    stream = sd.OutputStream(device=device_index, samplerate=sample_rate, channels=2, callback=callback)
    playing_threads[(device_index, channel)] = stream
    stream.start()

def stop_sound(device_index, channel):
    """Stop playing sound on a given device and channel."""
    stream = playing_threads.pop((device_index, channel), None)
    if stream:
        stream.stop()
        stream.close()

def stop_all_sounds():
    """Stop all currently playing sounds."""
    for key in list(playing_threads.keys()):
        stop_sound(*key)

def play_all_sounds(frequency):
    """Play sound on all available Dante channels."""
    devices = list_dante_devices()
    for device in devices:
        for channel in [1, 2]:
            play_sound_on_device(device['index'], channel, frequency)

def start_gui():
    devices = list_dante_devices()
    root = tk.Tk()
    root.title("Dante Virtual Soundcard Player")
    
    frame = ttk.Frame(root)
    frame.pack(padx=10, pady=10)
    
    ttk.Label(frame, text="Frequency (Hz):").grid(row=0, column=1)
    frequency_entry = ttk.Entry(frame)
    frequency_entry.grid(row=0, column=2)
    frequency_entry.insert(0, "440")
    
    def toggle_all_play():
        freq = float(frequency_entry.get())
        play_all_sounds(freq)
    
    def toggle_all_stop():
        stop_all_sounds()
    
    all_on_btn = ttk.Button(frame, text="All ON", command=toggle_all_play)
    all_on_btn.grid(row=1, column=3, padx=5)
    
    all_off_btn = ttk.Button(frame, text="All OFF", command=toggle_all_stop)
    all_off_btn.grid(row=1, column=4, padx=5)
    
    buttons = []
    
    for idx, device in enumerate(devices):
        device_name = device['name']
        device_index = device['index']
        ttk.Label(frame, text=device_name).grid(row=idx + 2, column=0, sticky="w")
        
        for channel in [1, 2]:
            btn = ttk.Button(frame, text=f"Ch {channel} ON", width=10)
            btn.grid(row=idx + 2, column=channel, padx=5)
            
            def toggle_play(dev_idx=device_index, ch=channel, button=btn):
                if (dev_idx, ch) in playing_threads:
                    stop_sound(dev_idx, ch)
                    button.config(text=f"Ch {ch} ON")
                else:
                    freq = float(frequency_entry.get())
                    play_sound_on_device(dev_idx, ch, freq)
                    button.config(text=f"Ch {ch} OFF")
            
            btn.config(command=toggle_play)
            buttons.append(btn)
    
    root.mainloop()

if __name__ == "__main__":
    start_gui()