#!/usr/bin/env python3 """ Windows installation script with fallback options for problematic packages. """ import subprocess import sys import os def run_pip_install(packages): """Run pip install with error handling""" for package in packages: try: print(f"Installing {package}...") result = subprocess.run([sys.executable, '-m', 'pip', 'install', package], capture_output=True, text=True, check=True) print(f"✓ Successfully installed {package}") except subprocess.CalledProcessError as e: print(f"✗ Failed to install {package}: {e}") print("STDOUT:", e.stdout) print("STDERR:", e.stderr) return False return True def install_dependencies(): """Install dependencies with Windows-specific handling""" # Core packages that usually install fine core_packages = [ "PyQt5>=5.15.0", "numpy>=1.21.0", "pygame>=2.1.0", "mido>=1.2.10" ] print("Installing core packages...") if not run_pip_install(core_packages): print("Failed to install some core packages") return False # Try to install python-rtmidi with different approaches rtmidi_packages = [ "python-rtmidi>=1.5.8", # Newer version with better Windows support "python-rtmidi", # Latest version "rtmidi-python>=1.1.0" # Alternative package ] rtmidi_installed = False for package in rtmidi_packages: print(f"Trying to install {package}...") if run_pip_install([package]): rtmidi_installed = True break print(f"Failed to install {package}, trying next option...") if not rtmidi_installed: print("\n⚠️ Warning: Could not install any RTMIDI package.") print("The application will still work in simulator mode.") print("For hardware MIDI support, you may need to:") print("1. Install Visual Studio Build Tools") print("2. Try: pip install --no-cache-dir python-rtmidi") print("3. Or use the simulator mode only") print("\n✓ Installation completed!") return True def create_fallback_rtmidi(): """Create a fallback rtmidi module using pygame.midi""" fallback_code = '''""" Fallback RTMIDI implementation using pygame.midi for Windows compatibility. """ import pygame.midi import time from typing import List, Optional, Callable class MidiOut: def __init__(self, device_id): pygame.midi.init() self.device_id = device_id self.midi_out = pygame.midi.Output(device_id) def send_message(self, message): """Send MIDI message""" if hasattr(message, 'bytes'): # mido message data = message.bytes() else: # Raw bytes data = message if len(data) == 3: self.midi_out.write_short(data[0], data[1], data[2]) elif len(data) == 2: self.midi_out.write_short(data[0], data[1]) def close(self): if hasattr(self, 'midi_out'): self.midi_out.close() class MidiIn: def __init__(self, device_id, callback=None): pygame.midi.init() self.device_id = device_id self.midi_in = pygame.midi.Input(device_id) self.callback = callback def set_callback(self, callback): self.callback = callback def poll(self): """Poll for MIDI input (call this regularly)""" if self.midi_in.poll() and self.callback: midi_events = self.midi_in.read(10) for event in midi_events: # Convert pygame midi event to mido-like message if self.callback: self.callback(event) def close(self): if hasattr(self, 'midi_in'): self.midi_in.close() def get_output_names() -> List[str]: """Get available MIDI output device names""" pygame.midi.init() devices = [] for i in range(pygame.midi.get_count()): info = pygame.midi.get_device_info(i) if info[3]: # is_output devices.append(info[1].decode()) return devices def get_input_names() -> List[str]: """Get available MIDI input device names""" pygame.midi.init() devices = [] for i in range(pygame.midi.get_count()): info = pygame.midi.get_device_info(i) if info[2]: # is_input devices.append(info[1].decode()) return devices def open_output(name: str) -> MidiOut: """Open MIDI output by name""" pygame.midi.init() for i in range(pygame.midi.get_count()): info = pygame.midi.get_device_info(i) if info[3] and info[1].decode() == name: # is_output and name matches return MidiOut(i) raise ValueError(f"MIDI output '{name}' not found") def open_input(name: str, callback=None) -> MidiIn: """Open MIDI input by name""" pygame.midi.init() for i in range(pygame.midi.get_count()): info = pygame.midi.get_device_info(i) if info[2] and info[1].decode() == name: # is_input and name matches return MidiIn(i, callback) raise ValueError(f"MIDI input '{name}' not found") ''' # Create fallback directory fallback_dir = os.path.join(os.path.dirname(__file__), 'fallback') os.makedirs(fallback_dir, exist_ok=True) # Write fallback rtmidi module with open(os.path.join(fallback_dir, 'rtmidi_fallback.py'), 'w') as f: f.write(fallback_code) with open(os.path.join(fallback_dir, '__init__.py'), 'w') as f: f.write('# Fallback MIDI implementations') if __name__ == "__main__": print("Windows MIDI Arpeggiator Installation Script") print("=" * 50) # Update pip first print("Updating pip...") subprocess.run([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'], capture_output=True) # Install dependencies success = install_dependencies() # Create fallback MIDI implementation create_fallback_rtmidi() if success: print("\n🎉 Installation completed successfully!") print("\nYou can now run the application with:") print(" python run.py") print("\nIf you have MIDI hardware issues, the app will work in simulator mode.") else: print("\n⚠️ Installation completed with warnings.") print("Some packages failed to install, but the app should still work in simulator mode.")