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>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Audio visualization plotting engine.
|
||||
Separates plotting logic from audio processing for clean GUI integration.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.colors as mcolors
|
||||
import matplotlib.cm as cm
|
||||
from matplotlib.figure import Figure
|
||||
import os
|
||||
|
||||
|
||||
class PlottingEngine:
|
||||
"""Handles all matplotlib visualization logic for audio analysis."""
|
||||
|
||||
@staticmethod
|
||||
def create_power_analysis_figure(times, rms_array, file_path, figsize=(10, 4)):
|
||||
"""
|
||||
Creates a matplotlib Figure for power analysis visualization.
|
||||
|
||||
Args:
|
||||
times: Array of time points
|
||||
rms_array: RMS power values over time
|
||||
file_path: Path to the audio file for title
|
||||
figsize: Figure size tuple
|
||||
|
||||
Returns:
|
||||
matplotlib.figure.Figure: Ready-to-embed figure
|
||||
"""
|
||||
# Determine color scale based on headroom detection
|
||||
local_max_power = np.max(rms_array)
|
||||
if local_max_power > 0.3:
|
||||
norm = mcolors.Normalize(vmin=0, vmax=0.6)
|
||||
maxpower = 0.6
|
||||
else:
|
||||
norm = mcolors.Normalize(vmin=0, vmax=0.3)
|
||||
maxpower = 0.3
|
||||
|
||||
# Create figure and axis
|
||||
fig = Figure(figsize=figsize, facecolor='white')
|
||||
ax = fig.add_subplot(111)
|
||||
|
||||
# Color map
|
||||
cmap = cm.autumn
|
||||
|
||||
# Plot power levels as colored bars
|
||||
ax.set_ylim(0., maxpower)
|
||||
for i in range(len(times)-1):
|
||||
ax.fill_between(times[i:i+2], 0, rms_array[0][i],
|
||||
color=cmap(norm(rms_array[0][i])), edgecolor='none')
|
||||
|
||||
# Add colorbar
|
||||
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
|
||||
sm.set_array([])
|
||||
cbar = fig.colorbar(sm, ax=ax, label='RMS Power')
|
||||
|
||||
# Labels and title
|
||||
ax.set_ylabel('Power')
|
||||
ax.set_xlabel('Time (seconds)')
|
||||
ax.set_title(f'{os.path.basename(file_path)}')
|
||||
|
||||
# Tight layout for better appearance in GUI
|
||||
fig.tight_layout()
|
||||
|
||||
return fig
|
||||
|
||||
@staticmethod
|
||||
def create_metadata_display_text(song_name, bpm, max_amplitude, avg_amplitude):
|
||||
"""
|
||||
Creates formatted text for metadata display.
|
||||
|
||||
Returns:
|
||||
str: Formatted metadata text
|
||||
"""
|
||||
return f"""Track: {song_name}
|
||||
BPM: {bpm:.1f}
|
||||
Max Amplitude: {max_amplitude:.3f}
|
||||
Avg Amplitude: {avg_amplitude:.3f}"""
|
||||
Reference in New Issue
Block a user