Files
2025-07-10 15:47:38 +01:00

121 lines
4.1 KiB
Python

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