ship-controller/main.py
2025-06-30 03:40:36 +02:00

170 lines
6.2 KiB
Python

"""
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):
# 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())