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
|
import numpy as np
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
|
from pyqtgraph.Qt.QtWidgets import QWidget
|
||||||
from utils import FFTAnalyser
|
from utils import FFTAnalyser
|
||||||
|
from pyqtgraph import mkBrush
|
||||||
|
|
||||||
|
|
||||||
class AudioVisualizer(QtWidgets.QWidget):
|
class AudioVisualizer(QtWidgets.QWidget):
|
||||||
"""Audio Visualizer component"""
|
"""Audio Visualizer component"""
|
||||||
|
|
||||||
def __init__(self, media_player):
|
def __init__(self, player, probe, PlotWidget):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.media_player = media_player
|
self.player: QMediaPlayer = player
|
||||||
|
self.probe: QAudioProbe = probe
|
||||||
|
self.PlotWidget: QWidget = PlotWidget
|
||||||
self.x_resolution = 100
|
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.calculatedVisual.connect(self.set_amplitudes)
|
||||||
self.fft_analyser.calculatedVisualRs.connect(self.set_rs)
|
self.fft_analyser.calculatedVisualRs.connect(self.set_rs)
|
||||||
self.fft_analyser.start()
|
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
|
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):
|
def get_x_resolution(self):
|
||||||
"""Returns the resolution for the graphics plot"""
|
"""Returns the resolution for the graphics plot"""
|
||||||
return self.x_resolution
|
return self.x_resolution
|
||||||
@ -71,7 +119,7 @@ class AudioVisualizer(QtWidgets.QWidget):
|
|||||||
With a noise floor cutoff at around -96dB (for very small values)
|
With a noise floor cutoff at around -96dB (for very small values)
|
||||||
"""
|
"""
|
||||||
# Avoid log(0) by adding a small epsilon
|
# Avoid log(0) by adding a small epsilon
|
||||||
epsilon = 1e-30
|
epsilon = 1e-7
|
||||||
amplitudes = np.maximum(self.amps, epsilon)
|
amplitudes = np.maximum(self.amps, epsilon)
|
||||||
# Convert to decibels (20*log10 is the standard formula for amplitude to dB)
|
# Convert to decibels (20*log10 is the standard formula for amplitude to dB)
|
||||||
db_values = 20 * np.log10(amplitudes)
|
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
|
# self.amps = np.maximum(np.array(amps), 1e-12) # Set a very small threshold
|
||||||
# print(self.amps)
|
# print(self.amps)
|
||||||
self.amps = np.array(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
|
# widget bits
|
||||||
self.album_art_scene: QGraphicsScene = QGraphicsScene()
|
self.album_art_scene: QGraphicsScene = QGraphicsScene()
|
||||||
self.player: QMediaPlayer = QMediaPlayer() # Audio player object
|
self.player: QMediaPlayer = QMediaPlayer() # Audio player object
|
||||||
self.probe: QAudioProbe = QAudioProbe() # Gets audio data
|
self.probe: QAudioProbe = QAudioProbe() # Gets audio buffer data
|
||||||
self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player)
|
self.audio_visualizer: AudioVisualizer = AudioVisualizer(self.player, self.probe, self.PlotWidget)
|
||||||
self.timer = QTimer(self) # Audio timing things
|
self.timer = QTimer(self) # for playback slider and such
|
||||||
|
|
||||||
# sharing functions with other classes and that
|
# sharing functions with other classes and that
|
||||||
self.tableView.load_qapp(self)
|
self.tableView.load_qapp(self)
|
||||||
@ -186,9 +186,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
|||||||
self.volumeLabel.setText(str(self.current_volume))
|
self.volumeLabel.setText(str(self.current_volume))
|
||||||
self.volumeSlider.setValue(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)
|
# Slider Timer (realtime playback feedback horizontal bar)
|
||||||
self.timer.start(100)
|
self.timer.start(100)
|
||||||
self.timer.timeout.connect(self.move_slider)
|
self.timer.timeout.connect(self.move_slider)
|
||||||
@ -196,39 +193,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
|||||||
# Set fixed size for album art
|
# Set fixed size for album art
|
||||||
self.albumGraphicsView.setFixedSize(250, 250)
|
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
|
# Connections
|
||||||
self.playbackSlider.sliderReleased.connect(
|
self.playbackSlider.sliderReleased.connect(
|
||||||
lambda: self.player.setPosition(self.playbackSlider.value())
|
lambda: self.player.setPosition(self.playbackSlider.value())
|
||||||
@ -237,6 +201,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
|||||||
self.speedSlider.sliderMoved.connect(
|
self.speedSlider.sliderMoved.connect(
|
||||||
lambda: self.on_speed_changed(self.speedSlider.value())
|
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.playButton.clicked.connect(self.on_play_clicked) # Click to play/pause
|
||||||
self.previousButton.clicked.connect(self.on_previous_clicked)
|
self.previousButton.clicked.connect(self.on_previous_clicked)
|
||||||
self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song
|
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)
|
album_art_data = get_album_art(None)
|
||||||
self.albumGraphicsView.load_album_art(album_art_data)
|
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:
|
def move_slider(self) -> None:
|
||||||
"""Handles moving the playback slider"""
|
"""Handles moving the playback slider"""
|
||||||
if self.player.state() == QMediaPlayer.State.StoppedState:
|
if self.player.state() == QMediaPlayer.State.StoppedState:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user