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