159 lines
5.5 KiB
Python
159 lines
5.5 KiB
Python
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_()) |