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,124 @@
|
||||
"""
|
||||
Analysis Results Manager - Bridge between audio processing and GUI.
|
||||
Manages analysis queue and coordinates between components.
|
||||
"""
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
import os
|
||||
|
||||
from master_core import AudioFile
|
||||
from plotting_engine import PlottingEngine
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalysisResult:
|
||||
"""Container for audio analysis results."""
|
||||
file_path: str
|
||||
song_name: str
|
||||
bpm: float
|
||||
max_amplitude: float
|
||||
avg_amplitude: float
|
||||
times: list
|
||||
rms_array: list
|
||||
analysis_successful: bool = True
|
||||
error_message: str = ""
|
||||
|
||||
|
||||
class AnalysisResultsManager(QObject):
|
||||
"""
|
||||
Manages audio file analysis and coordinates between processing and GUI.
|
||||
Threading-ready architecture for future background processing.
|
||||
"""
|
||||
|
||||
# Signals for GUI communication
|
||||
analysisStarted = pyqtSignal(str) # file_path
|
||||
analysisCompleted = pyqtSignal(str, object) # file_path, AnalysisResult
|
||||
analysisError = pyqtSignal(str, str) # file_path, error_message
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.results_cache = {} # Store analysis results
|
||||
self.plotting_engine = PlottingEngine()
|
||||
|
||||
def analyze_file(self, file_path: str, window: int = 10, hop: int = 2):
|
||||
"""
|
||||
Analyze an audio file and emit results.
|
||||
Currently synchronous - ready for threading later.
|
||||
|
||||
Args:
|
||||
file_path: Path to audio file
|
||||
window: RMS analysis window size in seconds
|
||||
hop: Analysis hop size in seconds
|
||||
"""
|
||||
if not os.path.exists(file_path):
|
||||
error_msg = f"File not found: {file_path}"
|
||||
self.analysisError.emit(file_path, error_msg)
|
||||
return
|
||||
|
||||
# Emit analysis started signal
|
||||
self.analysisStarted.emit(file_path)
|
||||
|
||||
try:
|
||||
# Create AudioFile and perform analysis
|
||||
audio_file = AudioFile(file_path)
|
||||
|
||||
# Get RMS analysis data
|
||||
audio_file.get_energy_levels_over_time(window=window, hop=hop)
|
||||
|
||||
# Extract analysis results
|
||||
result = AnalysisResult(
|
||||
file_path=file_path,
|
||||
song_name=audio_file.song_name,
|
||||
bpm=audio_file.get_bpm(),
|
||||
max_amplitude=audio_file.max_amplitude,
|
||||
avg_amplitude=audio_file.avg_amplitude,
|
||||
times=audio_file._get_times(), # We'll need to add this method
|
||||
rms_array=audio_file.rms_array,
|
||||
analysis_successful=True
|
||||
)
|
||||
|
||||
# Cache the result
|
||||
self.results_cache[file_path] = result
|
||||
|
||||
# Emit completion signal
|
||||
self.analysisCompleted.emit(file_path, result)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Analysis failed: {str(e)}"
|
||||
self.analysisError.emit(file_path, error_msg)
|
||||
|
||||
def get_analysis_figure(self, file_path: str):
|
||||
"""
|
||||
Get matplotlib figure for a previously analyzed file.
|
||||
|
||||
Returns:
|
||||
matplotlib.figure.Figure or None
|
||||
"""
|
||||
if file_path not in self.results_cache:
|
||||
return None
|
||||
|
||||
result = self.results_cache[file_path]
|
||||
return self.plotting_engine.create_power_analysis_figure(
|
||||
result.times, result.rms_array, result.file_path
|
||||
)
|
||||
|
||||
def get_metadata_text(self, file_path: str) -> str:
|
||||
"""Get formatted metadata text for a file."""
|
||||
if file_path not in self.results_cache:
|
||||
return "No analysis data available"
|
||||
|
||||
result = self.results_cache[file_path]
|
||||
return self.plotting_engine.create_metadata_display_text(
|
||||
result.song_name, result.bpm,
|
||||
result.max_amplitude, result.avg_amplitude
|
||||
)
|
||||
|
||||
def clear_cache(self):
|
||||
"""Clear all cached analysis results."""
|
||||
self.results_cache.clear()
|
||||
|
||||
def is_file_analyzed(self, file_path: str) -> bool:
|
||||
"""Check if a file has been analyzed."""
|
||||
return file_path in self.results_cache
|
||||
@@ -0,0 +1,126 @@
|
||||
"""
|
||||
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)
|
||||
@@ -1,41 +1,171 @@
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel
|
||||
import os
|
||||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||
QHBoxLayout, QSplitter, QLabel, QListWidget,
|
||||
QTextEdit, QListWidgetItem)
|
||||
from PyQt5.QtCore import Qt
|
||||
import master_core
|
||||
|
||||
class AudioDragDropWidget(QWidget):
|
||||
from audio_visualization_widget import AudioVisualizationWidget
|
||||
from analysis_results_manager import AnalysisResultsManager
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
"""Main application window with modular audio analysis interface."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.analysis_manager = AnalysisResultsManager()
|
||||
self.initUI()
|
||||
self.connect_signals()
|
||||
|
||||
def initUI(self):
|
||||
self.setWindowTitle('Drag and Drop Audio Analysis')
|
||||
self.setGeometry(100, 100, 400, 200) # x, y, width, height
|
||||
"""Initialize the user interface."""
|
||||
self.setWindowTitle('Audio Mastering Analysis Toolkit')
|
||||
self.setGeometry(100, 100, 1200, 700)
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
# Layout and label for displaying messages
|
||||
layout = QVBoxLayout()
|
||||
self.label = QLabel('Drag and drop an audio file here', self)
|
||||
self.label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(self.label)
|
||||
self.setLayout(layout)
|
||||
# Create central widget with splitter
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
|
||||
# Main horizontal layout
|
||||
main_layout = QHBoxLayout(central_widget)
|
||||
splitter = QSplitter(Qt.Horizontal)
|
||||
main_layout.addWidget(splitter)
|
||||
|
||||
# Left panel - File list and metadata
|
||||
left_panel = self.create_left_panel()
|
||||
splitter.addWidget(left_panel)
|
||||
|
||||
# Right panel - Visualization
|
||||
self.visualization_widget = AudioVisualizationWidget()
|
||||
splitter.addWidget(self.visualization_widget)
|
||||
|
||||
# Set splitter proportions
|
||||
splitter.setSizes([300, 900]) # Left panel narrower than visualization
|
||||
|
||||
def create_left_panel(self):
|
||||
"""Create the left panel with file list and metadata."""
|
||||
panel = QWidget()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# File list
|
||||
self.file_list_label = QLabel("Analyzed Files:")
|
||||
layout.addWidget(self.file_list_label)
|
||||
|
||||
self.file_list = QListWidget()
|
||||
self.file_list.itemClicked.connect(self.on_file_selected)
|
||||
layout.addWidget(self.file_list)
|
||||
|
||||
# Metadata display
|
||||
self.metadata_label = QLabel("File Information:")
|
||||
layout.addWidget(self.metadata_label)
|
||||
|
||||
self.metadata_display = QTextEdit()
|
||||
self.metadata_display.setReadOnly(True)
|
||||
self.metadata_display.setMaximumHeight(150)
|
||||
layout.addWidget(self.metadata_display)
|
||||
|
||||
# Instructions
|
||||
instructions = QLabel(
|
||||
"Drag and drop audio files (.mp3, .wav) onto this window to analyze them."
|
||||
)
|
||||
instructions.setWordWrap(True)
|
||||
instructions.setStyleSheet("color: gray; font-style: italic;")
|
||||
layout.addWidget(instructions)
|
||||
|
||||
return panel
|
||||
|
||||
def connect_signals(self):
|
||||
"""Connect analysis manager signals to GUI updates."""
|
||||
self.analysis_manager.analysisStarted.connect(self.on_analysis_started)
|
||||
self.analysis_manager.analysisCompleted.connect(self.on_analysis_completed)
|
||||
self.analysis_manager.analysisError.connect(self.on_analysis_error)
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
"""Handle drag enter event for file drops."""
|
||||
if event.mimeData().hasUrls():
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
# Check if any files have audio extensions
|
||||
urls = event.mimeData().urls()
|
||||
for url in urls:
|
||||
file_path = url.toLocalFile()
|
||||
if file_path.lower().endswith(('.mp3', '.wav', '.flac')):
|
||||
event.accept()
|
||||
return
|
||||
event.ignore()
|
||||
|
||||
def dropEvent(self, event):
|
||||
"""Handle file drop event."""
|
||||
files = [u.toLocalFile() for u in event.mimeData().urls()]
|
||||
for file_path in files:
|
||||
self.label.setText(f'File dropped: {file_path}')
|
||||
# Here, you would call your plot function with the dropped file path
|
||||
# For example: plot_macro_time_power_graph_colormap(file_path)
|
||||
break # This example only processes the first dropped file
|
||||
audio_files = [f for f in files if f.lower().endswith(('.mp3', '.wav', '.flac'))]
|
||||
|
||||
if audio_files:
|
||||
# Analyze the first audio file
|
||||
# TODO: Add support for multiple file queue
|
||||
file_path = audio_files[0]
|
||||
self.analysis_manager.analyze_file(file_path)
|
||||
else:
|
||||
self.visualization_widget.set_status("No audio files detected in drop")
|
||||
|
||||
def on_analysis_started(self, file_path):
|
||||
"""Called when analysis starts."""
|
||||
filename = os.path.basename(file_path)
|
||||
self.visualization_widget.set_status(f"Analyzing: {filename}...")
|
||||
|
||||
def on_analysis_completed(self, file_path, result):
|
||||
"""Called when analysis completes successfully."""
|
||||
filename = os.path.basename(file_path)
|
||||
|
||||
# Add to file list if not already there
|
||||
existing_items = [self.file_list.item(i).text()
|
||||
for i in range(self.file_list.count())]
|
||||
if filename not in existing_items:
|
||||
item = QListWidgetItem(filename)
|
||||
item.setData(Qt.UserRole, file_path) # Store full path
|
||||
self.file_list.addItem(item)
|
||||
|
||||
# Get and display the analysis figure
|
||||
figure = self.analysis_manager.get_analysis_figure(file_path)
|
||||
if figure:
|
||||
self.visualization_widget.display_figure_direct(figure)
|
||||
|
||||
# Update metadata display
|
||||
metadata_text = self.analysis_manager.get_metadata_text(file_path)
|
||||
self.metadata_display.setText(metadata_text)
|
||||
|
||||
# Select the analyzed file in the list
|
||||
for i in range(self.file_list.count()):
|
||||
item = self.file_list.item(i)
|
||||
if item.data(Qt.UserRole) == file_path:
|
||||
self.file_list.setCurrentItem(item)
|
||||
break
|
||||
|
||||
def on_analysis_error(self, file_path, error_message):
|
||||
"""Called when analysis fails."""
|
||||
filename = os.path.basename(file_path)
|
||||
self.visualization_widget.set_status(f"Error analyzing {filename}: {error_message}")
|
||||
|
||||
def on_file_selected(self, item):
|
||||
"""Called when a file is selected from the list."""
|
||||
file_path = item.data(Qt.UserRole)
|
||||
|
||||
# Display the analysis figure
|
||||
figure = self.analysis_manager.get_analysis_figure(file_path)
|
||||
if figure:
|
||||
self.visualization_widget.display_figure_direct(figure)
|
||||
|
||||
# Update metadata display
|
||||
metadata_text = self.analysis_manager.get_metadata_text(file_path)
|
||||
self.metadata_display.setText(metadata_text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
ex = AudioDragDropWidget()
|
||||
ex.show()
|
||||
|
||||
# Set application style
|
||||
app.setStyle('Fusion') # Modern cross-platform style
|
||||
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
+33
-19
@@ -70,6 +70,12 @@ class AudioFile:
|
||||
# Calculate RMS over the rolling windows
|
||||
self.rms_array = librosa.feature.rms(y=self.y, frame_length=window_samples, hop_length=hop_samples)
|
||||
|
||||
def _get_times(self):
|
||||
"""Get time array for RMS data. Internal method for GUI integration."""
|
||||
if not hasattr(self, 'rms_array'):
|
||||
self.get_energy_levels_over_time()
|
||||
return librosa.frames_to_time(np.arange(self.rms_array.shape[1]), sr=self.sr, hop_length=self.hop*self.sr)
|
||||
|
||||
def plot_energy_levels_over_time(self, display='window'):
|
||||
"""_summary_
|
||||
|
||||
@@ -202,24 +208,32 @@ def find_mp3_files(directory):
|
||||
return mp3_files
|
||||
|
||||
|
||||
# Replace 'path/to/your/audiofile.mp3' with the path to your audio file
|
||||
file_path = []
|
||||
with open('./files.txt', 'r') as f:
|
||||
for line in f:
|
||||
if line[0] != '#' and line[0] != ';':
|
||||
file_path.append(line.strip())
|
||||
if __name__ == '__main__':
|
||||
# Legacy batch processing mode - runs when master_core.py is executed directly
|
||||
# For GUI usage, run main.py instead
|
||||
|
||||
for file in file_path:
|
||||
# max_amplitude, avg_amplitude, avg_power, avg_power_stft = analyze_track_librosa(file)
|
||||
# # read_mp3_tags(file)
|
||||
# print(f"Maximum Amplitude: {max_amplitude:.2f} dBFS")
|
||||
# print(f"Average Amplitude: {avg_amplitude:.2f} dBFS")
|
||||
# print(f"Average Power: {avg_power:.2f} dBFS")
|
||||
# print(f"Average Power (STFT): {avg_power_stft:.2f} dBFS")
|
||||
currentsong = AudioFile(file)
|
||||
currentsong.display_song_name()
|
||||
print(f"BPM: {currentsong.get_bpm()}")
|
||||
currentsong.plot_energy_levels_over_time()
|
||||
# plot_macro_time_power_graph(file)
|
||||
print("Running legacy batch analysis mode...")
|
||||
print("For the new GUI interface, please run: python main.py")
|
||||
print()
|
||||
|
||||
plt.show()
|
||||
# Replace 'path/to/your/audiofile.mp3' with the path to your audio file
|
||||
file_path = []
|
||||
with open('./files.txt', 'r') as f:
|
||||
for line in f:
|
||||
if line[0] != '#' and line[0] != ';':
|
||||
file_path.append(line.strip())
|
||||
|
||||
for file in file_path:
|
||||
# max_amplitude, avg_amplitude, avg_power, avg_power_stft = analyze_track_librosa(file)
|
||||
# # read_mp3_tags(file)
|
||||
# print(f"Maximum Amplitude: {max_amplitude:.2f} dBFS")
|
||||
# print(f"Average Amplitude: {avg_amplitude:.2f} dBFS")
|
||||
# print(f"Average Power: {avg_power:.2f} dBFS")
|
||||
# print(f"Average Power (STFT): {avg_power_stft:.2f} dBFS")
|
||||
currentsong = AudioFile(file)
|
||||
currentsong.display_song_name()
|
||||
print(f"BPM: {currentsong.get_bpm()}")
|
||||
currentsong.plot_energy_levels_over_time()
|
||||
# plot_macro_time_power_graph(file)
|
||||
|
||||
plt.show()
|
||||
|
||||
@@ -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