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.
 

114 lines
4.6 KiB

#!/usr/bin/env python3
"""
MIDI Arpeggiator - Main Application Entry Point
A modular MIDI arpeggiator with lighting control and Native Instruments Maschine integration
"""
import sys
import os
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QTimer
from gui.main_window import MainWindow
from core.output_manager import OutputManager
from core.arpeggiator_engine import ArpeggiatorEngine
from core.midi_channel_manager import MIDIChannelManager
from core.synth_router import SynthRouter
from core.volume_pattern_engine import VolumePatternEngine
from simulator.simulator_engine import SimulatorEngine
from config.configuration import Configuration
from maschine.maschine_controller import MaschineController
class ArpeggiatorApp:
def __init__(self):
self.app = QApplication(sys.argv)
self.config = Configuration()
# Initialize core modules
self.channel_manager = MIDIChannelManager()
self.volume_engine = VolumePatternEngine()
self.synth_router = SynthRouter(self.channel_manager)
self.simulator = SimulatorEngine()
self.output_manager = OutputManager(self.simulator)
self.arpeggiator = ArpeggiatorEngine(
self.channel_manager,
self.synth_router,
self.volume_engine,
self.output_manager
)
# Initialize Maschine controller
self.maschine_controller = MaschineController(
self.arpeggiator,
self.channel_manager,
self.volume_engine,
self.synth_router,
self.output_manager
)
# Initialize GUI
self.main_window = MainWindow(
self.arpeggiator,
self.channel_manager,
self.volume_engine,
self.output_manager,
self.simulator,
self.maschine_controller
)
# Volume changes are now handled directly in update_systems for active channels only
self.previous_active_channels = set()
# Setup update timer for real-time updates
self.update_timer = QTimer()
self.update_timer.timeout.connect(self.update_systems)
self.update_timer.start(16) # ~60 FPS
# Volume updates are now handled directly in update_systems for active channels only
def update_systems(self):
"""Update all systems that need regular refresh"""
self.arpeggiator.update()
self.simulator.update_lighting_display()
# Update volume patterns if arpeggiator is playing
if self.arpeggiator.is_playing:
# Advance pattern position (16ms delta at 60fps)
self.volume_engine.update_pattern(0.016)
# Only update volumes for channels that have active notes
active_channels = set([ch for ch, voices in self.channel_manager.active_voices.items() if voices])
if active_channels:
# Update volume patterns for active channels only
for channel in active_channels:
volume = self.volume_engine.get_channel_volume(channel, len(active_channels))
midi_volume = int(volume * 127)
self.output_manager.send_volume_change(channel, midi_volume)
# Handle channels that just became inactive
newly_inactive = self.previous_active_channels - active_channels
for channel in newly_inactive:
# Send one CC7 message to reset to default volume
self.output_manager.send_volume_change(channel, 100)
# Dim the visual display
if hasattr(self.main_window.arp_controls, 'simulator_display'):
self.main_window.arp_controls.simulator_display.on_midi_volume_changed(channel, 20)
# Update previous active channels
self.previous_active_channels = active_channels.copy()
else:
# No active channels - reset all previously active ones
for channel in self.previous_active_channels:
self.output_manager.send_volume_change(channel, 100)
if hasattr(self.main_window.arp_controls, 'simulator_display'):
self.main_window.arp_controls.simulator_display.on_midi_volume_changed(channel, 20)
self.previous_active_channels = set()
def run(self):
self.main_window.show()
return self.app.exec_()
if __name__ == "__main__":
app = ArpeggiatorApp()
sys.exit(app.run())