Files
uj-mastering-master/audio_visualization_widget.py
T
Mikkeli Matlock b07b363454 Implement modular GUI architecture with embedded matplotlib
Major refactor from popup-based to persistent PyQt5 interface:
- Extract plotting logic from AudioFile class into separate PlottingEngine
- Create AudioVisualizationWidget with embedded matplotlib canvas
- Add AnalysisResultsManager as bridge between processing and GUI
- Replace simple drag-drop widget with professional splitter layout
- Preserve legacy batch processing mode with execution guard

Features:
- Drag-and-drop audio analysis (.mp3/.wav/.flac support)
- File list with metadata display (BPM, amplitudes, track info)
- Persistent visualization area (no more matplotlib popups)
- Multi-file support with click-to-view functionality
- Threading-ready architecture for future background processing

Technical improvements:
- Clean separation of concerns (analysis/visualization/GUI)
- Qt signal-slot communication pattern
- Modular component design ready for multithreading
- Proper import guards prevent legacy code interference

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 22:43:11 +09:00

126 lines
4.2 KiB
Python

"""
Audio visualization widget with embedded matplotlib canvas.
Pure display responsibility - receives plotting data and shows graphs.
"""
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
class AudioVisualizationWidget(QWidget):
"""Widget for displaying audio analysis graphs with embedded matplotlib."""
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
def initUI(self):
"""Initialize the UI components."""
layout = QVBoxLayout()
# Create matplotlib canvas
self.figure = Figure(figsize=(10, 4), facecolor='white')
self.canvas = FigureCanvas(self.figure)
# Add canvas to layout
layout.addWidget(self.canvas)
# Status label for feedback
self.status_label = QLabel("Ready for audio analysis...")
layout.addWidget(self.status_label)
self.setLayout(layout)
# Initialize with empty plot
self._create_empty_plot()
def _create_empty_plot(self):
"""Creates an empty placeholder plot."""
self.figure.clear()
ax = self.figure.add_subplot(111)
ax.text(0.5, 0.5, 'Drop an audio file to see analysis',
ha='center', va='center', transform=ax.transAxes,
fontsize=14, alpha=0.7)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set_xticks([])
ax.set_yticks([])
self.canvas.draw()
def display_analysis_figure(self, figure):
"""
Display a matplotlib figure in the widget.
Args:
figure: matplotlib.figure.Figure to display
"""
# Clear current figure
self.figure.clear()
# Copy the provided figure to our canvas
# Get the subplot from the provided figure
source_ax = figure.get_axes()[0]
# Create new subplot in our figure
ax = self.figure.add_subplot(111)
# Copy all the plot elements
for child in source_ax.get_children():
if hasattr(child, 'get_data'):
# Copy line plots
try:
x_data, y_data = child.get_data()
ax.plot(x_data, y_data, color=child.get_color(),
linewidth=child.get_linewidth())
except:
pass
# Copy collections (fill_between creates PolyCollection)
for collection in source_ax.collections:
ax.add_collection(collection)
# Copy axis properties
ax.set_xlim(source_ax.get_xlim())
ax.set_ylim(source_ax.get_ylim())
ax.set_xlabel(source_ax.get_xlabel())
ax.set_ylabel(source_ax.get_ylabel())
ax.set_title(source_ax.get_title())
# Copy colorbar if it exists
if hasattr(figure, '_colorbar') or len(figure.get_axes()) > 1:
# Try to copy colorbar
try:
cbar = figure.colorbar(source_ax.collections[-1], ax=ax, label='RMS Power')
except:
pass
self.figure.tight_layout()
self.canvas.draw()
self.status_label.setText("Analysis complete - displaying power graph")
def display_figure_direct(self, figure):
"""
Display a figure by replacing our canvas figure entirely.
More reliable than copying elements.
Args:
figure: matplotlib.figure.Figure to display
"""
# Remove old canvas
layout = self.layout()
layout.removeWidget(self.canvas)
self.canvas.deleteLater()
# Create new canvas with the provided figure
self.figure = figure
self.canvas = FigureCanvas(self.figure)
layout.insertWidget(0, self.canvas) # Insert at position 0 (before status label)
self.canvas.draw()
self.status_label.setText("Analysis complete - displaying power graph")
def set_status(self, message):
"""Update the status label."""
self.status_label.setText(message)