""" Main module for MIDI-to-Hue application. Ties together the config, MIDI controller, Hue controller, mapper, animations, and speech-to-text. """ import sys import time import threading import signal from config import ConfigManager from hue_controller import HueController from midi_controller import MidiController, DeviceMappingManager from mapper import MidiToHueMapper from animations import AnimationManager, MidiLedAnimation from speech_to_text import SpeechToText def main(): """Main application entry point.""" # Load configuration config_manager = ConfigManager() # Create animation manager animation_manager = AnimationManager() # Initialize speech-to-text if enabled stt = None if config_manager.is_stt_enabled(): print("Initializing speech-to-text...") stt = SpeechToText(config_manager.get_stt_config()) if not stt.initialize(): print("Warning: Failed to initialize speech-to-text.") # Initialize Hue controller try: hue_controller = HueController( config_manager.get_bridge_ip(), config_manager.get_update_interval_sec() ) hue_controller.list_lights() except Exception as e: print(f"Failed to initialize Hue controller: {e}") return 1 # Initialize MIDI controller midi_controller = MidiController(config_manager.get_midi_device_index()) midi_controller.list_devices() # Setup device mappings device_mappings = { "traktor_kontrol_s2": { "note_on/1/3": 'left_deck_1', "note_on/1/4": 'left_deck_2', "note_on/1/5": 'left_deck_3', "note_on/1/6": 'left_deck_4', "note_on/1/7": 'left_deck_5', "note_on/1/8": 'left_deck_6', "note_on/1/9": 'left_deck_7', "note_on/1/10": 'left_deck_8', "note_on/3/3": 'right_deck_1', "note_on/3/4": 'right_deck_2', "note_on/3/5": 'right_deck_3', "note_on/3/6": 'right_deck_4', "note_on/3/7": 'right_deck_5', "note_on/3/8": 'right_deck_6', "note_on/3/9": 'right_deck_7', "note_on/3/10": 'right_deck_8', "control_change/5/1": 'left_volume_slider', "control_change/6/1": 'right_volume_slider', } } device_mapper = DeviceMappingManager(device_mappings) device_mapper.set_active_device("traktor_kontrol_s2") # Bind the device mapper to the MIDI controller midi_controller.get_input_name = device_mapper.get_input_name # Set up speech-to-text MIDI handling if enabled if stt: midi_trigger = config_manager.get_stt_midi_trigger() stt_trigger_type = midi_trigger.get("type", "note_on") stt_channel = midi_trigger.get("channel", 1) stt_note = midi_trigger.get("note", 1) print(f"Speech-to-text trigger: {stt_trigger_type}/{stt_channel}/{stt_note}") # Define STT result callback def stt_result_callback(text): print(f"\nSpeech recognition result: {text}\n") # Set result callback stt.set_callback(stt_result_callback) # Register MIDI handler for speech-to-text def handle_stt_midi(msg): print(f"Received: {msg}") # Check if message matches our trigger if hasattr(msg, 'type') and msg.type == stt_trigger_type and hasattr(msg, 'channel') and msg.channel == stt_channel: if stt_trigger_type == "note_on" and hasattr(msg, 'note') and msg.note == stt_note: # For note_on messages, check velocity to determine press/release if hasattr(msg, 'velocity') and msg.velocity > 0: print("\nStarting speech recognition (button pressed)...") stt.start_recording() else: print("\nStopping speech recognition (button released)...") stt.stop_recording() elif stt_trigger_type == "control_change" and hasattr(msg, 'control') and msg.control == stt_note: # For control_change messages, use value threshold if hasattr(msg, 'value') and msg.value > 64: print("\nStarting speech recognition...") stt.start_recording() else: print("\nStopping speech recognition...") stt.stop_recording() # Register the handler midi_controller.register_handler(handle_stt_midi) # Create MIDI-to-Hue mapper with configuration mapper = MidiToHueMapper( hue_controller, midi_controller, config_manager.get_mappings() ) # Setup right deck animation with alternating chase directions print("Setting up right deck animation sequence...") right_deck_animation = animation_manager.create_alternating_chase_animation( name="right_deck_alternating", midi_controller=midi_controller, deck_side="right", interval=0.12, # Animation speed in seconds between steps cycles_per_direction=1 # Number of full cycles in each direction before changing ) # Open MIDI port if not midi_controller.open(): print("Failed to open MIDI port.") return 1 # Start the deck animation print("Starting right deck animation...") animation_manager.start_animation("right_deck_chase") # Handle graceful shutdown def signal_handler(sig, frame): print("\nStopping animations and exiting...") animation_manager.stop_all() midi_controller.close() # Clean up STT resources if stt: stt.cleanup() sys.exit(0) # Register signal handler for Ctrl+C signal.signal(signal.SIGINT, signal_handler) try: # Process MIDI messages in main thread midi_controller.process_messages() except KeyboardInterrupt: print("\nExiting program...") finally: animation_manager.stop_all() midi_controller.close() # Clean up STT resources if stt: stt.cleanup() return 0 if __name__ == "__main__": sys.exit(main())