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