visualizer continues to fall when playback is paused. moved visualizer code into AudioVisualizer component
This commit is contained in:
parent
b8195cd3dd
commit
47f4359cef
@ -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
75
main.py
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user