Fbrowser/python-src/testmidi.py
stan44 565be4e1e7 Migrate Fbrowser to Rust and Tauri desktop app
- Replace the Python/Docker setup with a Rust workspace and Tauri frontend
- Add core crates for archive, audio, MIDI, plugin, and desktop UI layers
- Refresh the app scaffolding, build config, and documentation
2026-03-30 16:18:26 -05:00

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