import sys import os from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileSystemModel, QTreeView, QLabel, QComboBox from PyQt5.QtCore import QDir, Qt, QThread, pyqtSignal import mido import pygame import numpy as np from mingus.midi import fluidsynth from mingus.containers import Note import soundfile as sf class MidiPlayerThread(QThread): update_signal = pyqtSignal(str) def __init__(self, file_path): super().__init__() self.file_path = file_path self.playing = True def run(self): midi_file = mido.MidiFile(self.file_path) for msg in midi_file.play(): if not self.playing: break if not msg.is_meta: if msg.type == 'note_on': fluidsynth.play_Note(Note(msg.note), msg.channel, msg.velocity) elif msg.type == 'note_off': fluidsynth.stop_Note(Note(msg.note), msg.channel) elif msg.type == 'control_change': fluidsynth.control_change(msg.channel, msg.control, msg.value) self.update_signal.emit(f"Playing: {msg}") def stop(self): self.playing = False class AudioPlayerThread(QThread): update_signal = pyqtSignal(str) def __init__(self, file_path): super().__init__() self.file_path = file_path self.playing = True def run(self): pygame.mixer.music.load(self.file_path) pygame.mixer.music.play() while pygame.mixer.music.get_busy() and self.playing: pygame.time.Clock().tick(10) self.update_signal.emit(f"Playing audio: {pygame.mixer.music.get_pos() / 1000:.2f} seconds") def stop(self): self.playing = False pygame.mixer.music.stop() class MidiPlayer(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("MIDI Player and Audio File Browser") self.setGeometry(100, 100, 800, 600) self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.layout = QHBoxLayout(self.central_widget) # File Browser self.model = QFileSystemModel() self.model.setRootPath(QDir.rootPath()) self.model.setNameFilters(["*.mid", "*.midi", "*.mp3", "*.wav", "*.sf2"]) self.model.setNameFilterDisables(False) self.tree = QTreeView() self.tree.setModel(self.model) self.tree.setRootIndex(self.model.index(QDir.homePath())) self.tree.setColumnWidth(0, 250) self.tree.setAnimated(False) self.tree.setIndentation(20) self.tree.setSortingEnabled(True) self.tree.setWindowTitle("File Browser") self.tree.clicked.connect(self.on_file_clicked) # Player controls self.player_widget = QWidget() self.player_layout = QVBoxLayout(self.player_widget) self.file_label = QLabel("No file selected") self.player_layout.addWidget(self.file_label) self.play_button = QPushButton("Play") self.play_button.clicked.connect(self.play_file) self.player_layout.addWidget(self.play_button) self.stop_button = QPushButton("Stop") self.stop_button.clicked.connect(self.stop_file) self.player_layout.addWidget(self.stop_button) self.soundfont_combo = QComboBox() self.soundfont_combo.currentIndexChanged.connect(self.change_soundfont) self.player_layout.addWidget(self.soundfont_combo) self.status_label = QLabel("") self.player_layout.addWidget(self.status_label) # Add widgets to main layout self.layout.addWidget(self.tree) self.layout.addWidget(self.player_widget) # Initialize pygame mixer pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=1024) # Initialize FluidSynth fluidsynth.init(sf2="/path/to/default/soundfont.sf2") # Adjust this path as needed self.player_thread = None def on_file_clicked(self, index): file_path = self.model.filePath(index) self.file_label.setText(os.path.basename(file_path)) if file_path.lower().endswith('.sf2'): self.load_soundfont(file_path) def load_soundfont(self, sf2_path): try: fluidsynth.init(sf2=sf2_path) self.soundfont_combo.clear() self.soundfont_combo.addItems([f"Instrument {i}" for i in range(128)]) # MIDI has 128 standard instruments except Exception as e: print(f"Error loading soundfont: {e}") def change_soundfont(self, index): fluidsynth.set_instrument(0, index) # Set instrument for channel 0 def play_file(self): file_path = self.model.filePath(self.tree.currentIndex()) if file_path.lower().endswith(('.mid', '.midi')): self.player_thread = MidiPlayerThread(file_path) elif file_path.lower().endswith(('.mp3', '.wav')): self.player_thread = AudioPlayerThread(file_path) else: return self.player_thread.update_signal.connect(self.update_status) self.player_thread.start() def stop_file(self): if self.player_thread and self.player_thread.isRunning(): self.player_thread.stop() self.player_thread.wait() pygame.mixer.stop() fluidsynth.stop_everything() def update_status(self, status): self.status_label.setText(status) if __name__ == "__main__": app = QApplication(sys.argv) player = MidiPlayer() player.show() sys.exit(app.exec_())