You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
9.9 KiB
268 lines
9.9 KiB
"""
|
|
Output Controls GUI
|
|
|
|
Interface for managing MIDI output mode and device selection.
|
|
"""
|
|
|
|
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
|
|
QGroupBox, QComboBox, QPushButton, QLabel,
|
|
QRadioButton, QButtonGroup, QFrame)
|
|
from PyQt5.QtCore import Qt, pyqtSlot
|
|
|
|
class OutputControls(QWidget):
|
|
"""Control panel for MIDI output management"""
|
|
|
|
def __init__(self, output_manager):
|
|
super().__init__()
|
|
self.output_manager = output_manager
|
|
self.setup_ui()
|
|
self.connect_signals()
|
|
self.refresh_midi_devices()
|
|
|
|
def setup_ui(self):
|
|
"""Set up the user interface"""
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Output Mode Selection
|
|
mode_group = self.create_mode_selection()
|
|
layout.addWidget(mode_group)
|
|
|
|
# Hardware MIDI Settings
|
|
midi_group = self.create_midi_settings()
|
|
layout.addWidget(midi_group)
|
|
|
|
# Status and Controls
|
|
status_group = self.create_status_controls()
|
|
layout.addWidget(status_group)
|
|
|
|
layout.addStretch()
|
|
|
|
def create_mode_selection(self) -> QGroupBox:
|
|
"""Create output mode selection controls"""
|
|
group = QGroupBox("Output Mode")
|
|
layout = QVBoxLayout(group)
|
|
|
|
# Radio buttons for mode selection
|
|
self.mode_group = QButtonGroup()
|
|
|
|
self.simulator_radio = QRadioButton("Simulator Mode")
|
|
self.simulator_radio.setChecked(True) # Default
|
|
self.simulator_radio.setToolTip("Use internal audio synthesis and visual lighting simulation")
|
|
|
|
self.hardware_radio = QRadioButton("Hardware Mode")
|
|
self.hardware_radio.setToolTip("Send MIDI to external hardware synths")
|
|
|
|
self.mode_group.addButton(self.simulator_radio, 0)
|
|
self.mode_group.addButton(self.hardware_radio, 1)
|
|
|
|
layout.addWidget(self.simulator_radio)
|
|
layout.addWidget(self.hardware_radio)
|
|
|
|
# Mode description
|
|
self.mode_description = QLabel("Using internal simulator with audio synthesis and lighting visualization.")
|
|
self.mode_description.setWordWrap(True)
|
|
self.mode_description.setStyleSheet("color: #888888; font-style: italic;")
|
|
layout.addWidget(self.mode_description)
|
|
|
|
return group
|
|
|
|
def create_midi_settings(self) -> QGroupBox:
|
|
"""Create MIDI device settings"""
|
|
group = QGroupBox("Hardware MIDI Settings")
|
|
layout = QGridLayout(group)
|
|
|
|
# MIDI Output Device
|
|
layout.addWidget(QLabel("MIDI Output:"), 0, 0)
|
|
self.midi_device_combo = QComboBox()
|
|
self.midi_device_combo.setMinimumWidth(200)
|
|
layout.addWidget(self.midi_device_combo, 0, 1)
|
|
|
|
self.refresh_button = QPushButton("Refresh")
|
|
self.refresh_button.clicked.connect(self.refresh_midi_devices)
|
|
layout.addWidget(self.refresh_button, 0, 2)
|
|
|
|
# Connection status
|
|
layout.addWidget(QLabel("Status:"), 1, 0)
|
|
self.connection_status = QLabel("Not Connected")
|
|
self.connection_status.setStyleSheet("color: #aa6600;")
|
|
layout.addWidget(self.connection_status, 1, 1)
|
|
|
|
# Initially disable MIDI settings (simulator mode default)
|
|
self.set_midi_controls_enabled(False)
|
|
|
|
return group
|
|
|
|
def create_status_controls(self) -> QGroupBox:
|
|
"""Create status display and control buttons"""
|
|
group = QGroupBox("Controls")
|
|
layout = QGridLayout(group)
|
|
|
|
# Panic button
|
|
self.panic_button = QPushButton("🚨 Panic (All Notes Off)")
|
|
self.panic_button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #5a2d5a;
|
|
color: white;
|
|
font-weight: bold;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #6a3d6a;
|
|
}
|
|
""")
|
|
layout.addWidget(self.panic_button, 0, 0, 1, 2)
|
|
|
|
# Test button
|
|
self.test_button = QPushButton("Test Output")
|
|
self.test_button.setToolTip("Send a test note to verify output is working")
|
|
layout.addWidget(self.test_button, 1, 0)
|
|
|
|
# Status display
|
|
status_frame = QFrame()
|
|
status_frame.setFrameStyle(QFrame.Box)
|
|
status_layout = QVBoxLayout(status_frame)
|
|
|
|
self.output_info = QLabel("Mode: Simulator\\nDevice: Internal\\nStatus: Ready")
|
|
self.output_info.setStyleSheet("font-family: monospace;")
|
|
status_layout.addWidget(self.output_info)
|
|
|
|
layout.addWidget(status_frame, 2, 0, 1, 2)
|
|
|
|
return group
|
|
|
|
def connect_signals(self):
|
|
"""Connect signals and slots"""
|
|
# Mode selection
|
|
self.mode_group.buttonClicked.connect(self.on_mode_changed)
|
|
|
|
# MIDI device selection
|
|
self.midi_device_combo.currentTextChanged.connect(self.on_midi_device_changed)
|
|
|
|
# Control buttons
|
|
self.panic_button.clicked.connect(self.on_panic_clicked)
|
|
self.test_button.clicked.connect(self.on_test_clicked)
|
|
|
|
# Output manager signals
|
|
self.output_manager.mode_changed.connect(self.on_output_mode_changed)
|
|
self.output_manager.midi_device_changed.connect(self.on_output_device_changed)
|
|
self.output_manager.error_occurred.connect(self.on_output_error)
|
|
|
|
def set_midi_controls_enabled(self, enabled: bool):
|
|
"""Enable/disable MIDI hardware controls"""
|
|
self.midi_device_combo.setEnabled(enabled)
|
|
self.refresh_button.setEnabled(enabled)
|
|
|
|
if enabled:
|
|
self.connection_status.setText("Ready for connection")
|
|
self.connection_status.setStyleSheet("color: #aaaa00;")
|
|
else:
|
|
self.connection_status.setText("Simulator Mode")
|
|
self.connection_status.setStyleSheet("color: #00aa00;")
|
|
|
|
@pyqtSlot()
|
|
def refresh_midi_devices(self):
|
|
"""Refresh MIDI device list"""
|
|
self.output_manager.refresh_midi_devices()
|
|
devices = self.output_manager.get_available_outputs()
|
|
|
|
self.midi_device_combo.clear()
|
|
if devices:
|
|
for device in devices:
|
|
self.midi_device_combo.addItem(device)
|
|
else:
|
|
self.midi_device_combo.addItem("No MIDI devices found")
|
|
|
|
# Update status
|
|
self.update_status_display()
|
|
|
|
@pyqtSlot()
|
|
def on_mode_changed(self):
|
|
"""Handle output mode change"""
|
|
if self.simulator_radio.isChecked():
|
|
self.output_manager.set_mode("simulator")
|
|
self.set_midi_controls_enabled(False)
|
|
self.mode_description.setText(
|
|
"Using internal simulator with audio synthesis and lighting visualization."
|
|
)
|
|
else:
|
|
self.output_manager.set_mode("hardware")
|
|
self.set_midi_controls_enabled(True)
|
|
self.mode_description.setText(
|
|
"Sending MIDI to external hardware synthesizers. "
|
|
"Select MIDI output device below."
|
|
)
|
|
|
|
@pyqtSlot(str)
|
|
def on_midi_device_changed(self, device_name: str):
|
|
"""Handle MIDI device selection change"""
|
|
if device_name and device_name != "No MIDI devices found":
|
|
success = self.output_manager.set_midi_output(device_name)
|
|
if success:
|
|
self.connection_status.setText("Connected")
|
|
self.connection_status.setStyleSheet("color: #00aa00;")
|
|
else:
|
|
self.connection_status.setText("Connection Failed")
|
|
self.connection_status.setStyleSheet("color: #aa0000;")
|
|
|
|
self.update_status_display()
|
|
|
|
@pyqtSlot()
|
|
def on_panic_clicked(self):
|
|
"""Handle panic button click"""
|
|
self.output_manager.send_panic()
|
|
|
|
@pyqtSlot()
|
|
def on_test_clicked(self):
|
|
"""Handle test button click"""
|
|
# Send a test note (Middle C for 500ms)
|
|
self.output_manager.send_note_on(1, 60, 80) # Channel 1, Middle C, velocity 80
|
|
|
|
# Schedule note off after 500ms
|
|
from PyQt5.QtCore import QTimer
|
|
QTimer.singleShot(500, lambda: self.output_manager.send_note_off(1, 60))
|
|
|
|
@pyqtSlot(str)
|
|
def on_output_mode_changed(self, mode: str):
|
|
"""Handle mode change from output manager"""
|
|
if mode == "simulator":
|
|
self.simulator_radio.setChecked(True)
|
|
else:
|
|
self.hardware_radio.setChecked(True)
|
|
|
|
self.update_status_display()
|
|
|
|
@pyqtSlot(str)
|
|
def on_output_device_changed(self, device: str):
|
|
"""Handle device change from output manager"""
|
|
# Find and select the device in combo box
|
|
index = self.midi_device_combo.findText(device)
|
|
if index >= 0:
|
|
self.midi_device_combo.setCurrentIndex(index)
|
|
|
|
self.update_status_display()
|
|
|
|
@pyqtSlot(str)
|
|
def on_output_error(self, error_message: str):
|
|
"""Handle output error"""
|
|
self.connection_status.setText("Error")
|
|
self.connection_status.setStyleSheet("color: #aa0000;")
|
|
self.connection_status.setToolTip(error_message)
|
|
|
|
def update_status_display(self):
|
|
"""Update the status information display"""
|
|
status_info = self.output_manager.get_status_info()
|
|
|
|
mode = status_info.get('mode', 'Unknown')
|
|
connected = status_info.get('connected', False)
|
|
device = status_info.get('selected_output', 'None')
|
|
|
|
if mode == "simulator":
|
|
device_text = "Internal Simulator"
|
|
status_text = "Ready"
|
|
else:
|
|
device_text = device if device else "None Selected"
|
|
status_text = "Connected" if connected else "Disconnected"
|
|
|
|
info_text = f"Mode: {mode.title()}\\nDevice: {device_text}\\nStatus: {status_text}"
|
|
self.output_info.setText(info_text)
|