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()