b07b363454
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>
79 lines
2.4 KiB
Python
79 lines
2.4 KiB
Python
"""
|
|
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}""" |