visualizer continues to fall when playback is paused. moved visualizer code into AudioVisualizer component

This commit is contained in:
billy 2025-04-09 10:45:56 -04:00
parent b8195cd3dd
commit 47f4359cef
2 changed files with 99 additions and 75 deletions

View File

@ -1,16 +1,22 @@
from PyQt5.QtCore import QTimer
from PyQt5.QtMultimedia import QAudioProbe, QMediaPlayer
import numpy as np
from PyQt5 import QtWidgets
from pyqtgraph.Qt.QtWidgets import QWidget
from utils import FFTAnalyser
from pyqtgraph import mkBrush
class AudioVisualizer(QtWidgets.QWidget):
"""Audio Visualizer component"""
def __init__(self, media_player):
def __init__(self, player, probe, PlotWidget):
super().__init__()
self.media_player = media_player
self.player: QMediaPlayer = player
self.probe: QAudioProbe = probe
self.PlotWidget: QWidget = PlotWidget
self.x_resolution = 100
self.fft_analyser = FFTAnalyser(self.media_player, self.x_resolution)
self.fft_analyser = FFTAnalyser(self.player, self.x_resolution)
self.fft_analyser.calculatedVisual.connect(self.set_amplitudes)
self.fft_analyser.calculatedVisualRs.connect(self.set_rs)
self.fft_analyser.start()
@ -26,6 +32,48 @@ class AudioVisualizer(QtWidgets.QWidget):
np.log10(self.min_freq), np.log10(self.max_freq), self.x_resolution
)
# Audio probe for processing audio signal in real time
self.probe.setSource(self.player)
self.probe.audioBufferProbed.connect(self.process_probe)
self.is_playing = False
self.visualizer_timer = QTimer(self)
self.visualizer_timer.setInterval(20) # Update every 100ms (adjust as needed)
self.visualizer_timer.timeout.connect(self.update_audio_visualization)
# Graphics plot
# Make sure PlotWidget doesn't exceed album art height
# Adjust to leave room for playback controls
self.PlotWidget.setFixedHeight(225)
# x range
self.PlotWidget.setXRange(
0, self.get_x_resolution(), padding=0
)
# y axis range for decibals (-96db to 0db)
self.PlotWidget.setYRange(-96, 0, padding=0)
# Logarithmic x-axis for frequency display
self.PlotWidget.setLogMode(x=False, y=False)
self.PlotWidget.setMouseEnabled(x=False, y=False)
self.PlotWidget.showGrid(x=True, y=True)
# Performance optimizations
self.PlotWidget.setAntialiasing(False)
self.PlotWidget.setDownsampling(auto=True, mode="peak")
self.PlotWidget.setClipToView(True)
# Add tick marks for common decibel values (expanded range)
y_ticks = [
(-84, "-84dB"),
(-60, "-60dB"),
(-36, "-36dB"),
(-12, "-12dB"),
(0, "0dB"),
]
self.PlotWidget.getAxis("left").setTicks([y_ticks])
# Add frequency ticks on x-axis
freq_ticks = self.get_frequency_ticks()
self.PlotWidget.getAxis("bottom").setTicks([freq_ticks])
def get_x_resolution(self):
"""Returns the resolution for the graphics plot"""
return self.x_resolution
@ -71,7 +119,7 @@ class AudioVisualizer(QtWidgets.QWidget):
With a noise floor cutoff at around -96dB (for very small values)
"""
# Avoid log(0) by adding a small epsilon
epsilon = 1e-30
epsilon = 1e-7
amplitudes = np.maximum(self.amps, epsilon)
# Convert to decibels (20*log10 is the standard formula for amplitude to dB)
db_values = 20 * np.log10(amplitudes)
@ -90,3 +138,46 @@ class AudioVisualizer(QtWidgets.QWidget):
# self.amps = np.maximum(np.array(amps), 1e-12) # Set a very small threshold
# print(self.amps)
self.amps = np.array(amps)
def process_probe(self, buff):
"""Audio visualizer buffer processing"""
# buff.startTime() # what is this? why need? dont need...
self.is_playing = True
# If music is playing, reset the timer
if not self.visualizer_timer.isActive():
self.visualizer_timer.start()
def update_audio_visualization(self):
"""Update the visualization for audio signal"""
if not self.is_playing:
# If music stopped, continue updating visualizer for a few seconds
self.visualizer_timer.stop() # Stop the timer once it's done
# Assuming y is the decibel data
y = self.get_decibels()
if len(y) == 0:
return
# Clear the previous plot
self.PlotWidget.clear()
# Plot new values
self._plot_item = self.PlotWidget.plot(
self._x_data,
y,
pen="b",
fillLevel=-96 if self.use_decibels else 0,
fillBrush=mkBrush("b"),
)
# If the visualizer is done, stop updating
if all(val == -96 for val in y):
self.is_playing = False
self.visualizer_timer.stop()
def clear_audio_visualization(self) -> None:
self.PlotWidget.clear()
self._plot_item = None

75
main.py
View File

@ -172,9 +172,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# widget bits
self.album_art_scene: QGraphicsScene = QGraphicsScene()
self.player: QMediaPlayer = QMediaPlayer() # Audio player object
self.probe: QAudioProbe = QAudioProbe() # Gets audio data
self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player)
self.timer = QTimer(self) # Audio timing things
self.probe: QAudioProbe = QAudioProbe() # Gets audio buffer data
self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player, self.probe, self.PlotWidget)
self.timer = QTimer(self) # for playback slider and such
# sharing functions with other classes and that
self.tableView.load_qapp(self)
@ -186,9 +186,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.volumeLabel.setText(str(self.current_volume))
self.volumeSlider.setValue(self.current_volume)
# Audio probe for processing audio signal in real time
self.probe.setSource(self.player)
self.probe.audioBufferProbed.connect(self.process_probe)
# Slider Timer (realtime playback feedback horizontal bar)
self.timer.start(100)
self.timer.timeout.connect(self.move_slider)
@ -196,39 +193,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# Set fixed size for album art
self.albumGraphicsView.setFixedSize(250, 250)
# Graphics plot
# Make sure PlotWidget doesn't exceed album art height
# Adjust to leave room for playback controls
self.PlotWidget.setFixedHeight(225)
# x range
self.PlotWidget.setXRange(
0, self.audio_visualizer.get_x_resolution(), padding=0
)
# y axis range for decibals (-96db to 0db)
self.PlotWidget.setYRange(-96, 0, padding=0)
# Logarithmic x-axis for frequency display
self.PlotWidget.setLogMode(x=False, y=False)
self.PlotWidget.setMouseEnabled(x=False, y=False)
self.PlotWidget.showGrid(x=True, y=True)
# Performance optimizations
self.PlotWidget.setAntialiasing(False)
self.PlotWidget.setDownsampling(auto=True, mode="peak")
self.PlotWidget.setClipToView(True)
# Add tick marks for common decibel values (expanded range)
y_ticks = [
(-84, "-84dB"),
(-60, "-60dB"),
(-36, "-36dB"),
(-12, "-12dB"),
(0, "0dB"),
]
self.PlotWidget.getAxis("left").setTicks([y_ticks])
# Add frequency ticks on x-axis
freq_ticks = self.audio_visualizer.get_frequency_ticks()
self.PlotWidget.getAxis("bottom").setTicks([freq_ticks])
# Connections
self.playbackSlider.sliderReleased.connect(
lambda: self.player.setPosition(self.playbackSlider.value())
@ -237,6 +201,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.speedSlider.sliderMoved.connect(
lambda: self.on_speed_changed(self.speedSlider.value())
)
# self.speedSlider.doubleClicked.connect(lambda: self.on_speed_changed(1))
self.playButton.clicked.connect(self.on_play_clicked) # Click to play/pause
self.previousButton.clicked.connect(self.on_previous_clicked)
self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song
@ -482,38 +447,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
album_art_data = get_album_art(None)
self.albumGraphicsView.load_album_art(album_art_data)
def process_probe(self, buff) -> None:
"""Audio visualizer buffer processing"""
buff.startTime()
self.update_audio_visualization()
def update_audio_visualization(self) -> None:
"""Handles updating points on the pyqtgraph visual"""
# Use decibel values instead of raw amplitudes
y = self.audio_visualizer.get_decibels()
if len(y) == 0:
return
# if self.audio_visualizer._plot_item is None:
# thanks cursor sonnet whatever
self.PlotWidget.clear()
# Use the actual frequency values for x-axis
self.audio_visualizer._plot_item = self.PlotWidget.plot(
self.audio_visualizer._x_data, # We'll keep using indices for drawing
y,
pen="b", # Use pen instead of fill for better performance
fillLevel=-96
if self.audio_visualizer.use_decibels
else 0, # Fill from -96dB for decibel scale
fillBrush=mkBrush("b"),
)
# else:
# self.audio_visualizer._plot_item.setData(self.audio_visualizer._x_data, y)
def clear_audio_visualization(self) -> None:
self.PlotWidget.clear()
self.audio_visualizer._plot_item = None
def move_slider(self) -> None:
"""Handles moving the playback slider"""
if self.player.state() == QMediaPlayer.State.StoppedState: