V1.0
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user