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.
 

269 lines
10 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)
self.midi_device_combo.setMaxVisibleItems(10) # Show more items
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)