Skip to content

Commit

Permalink
Update example and demo video
Browse files Browse the repository at this point in the history
  • Loading branch information
smu160 committed Apr 9, 2024
1 parent 29e065c commit d1701ea
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 34 deletions.
Binary file modified pyphastft/32_bins_demo.mov
Binary file not shown.
6 changes: 4 additions & 2 deletions pyphastft/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ def main():
# Find the next lower power of 2 for the number of samples
n_samples = 2 ** int(np.log2(t_max * fs))

t = np.linspace(0, n_samples / fs, n_samples, endpoint=False) # Adjusted time vector
t = np.linspace(
0, n_samples / fs, n_samples, endpoint=False
) # Adjusted time vector

# Generate the signal
s_re = 2 * np.sin(2 * np.pi * t) + np.sin(2 * np.pi * 10 * t)
Expand Down Expand Up @@ -49,4 +51,4 @@ def main():


if __name__ == "__main__":
main()
main()
80 changes: 48 additions & 32 deletions pyphastft/vis_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
import numpy as np
import pyaudio
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtWidgets, QtCore
from pyqtgraph.Qt import QtWidgets, QtCore
import sys


class RealTimeAudioSpectrum(QtWidgets.QWidget):
def __init__(self, parent=None):
super(RealTimeAudioSpectrum, self).__init__(parent)
self.n_fft_bins = 32 # FFT size (number of bins)
self.n_fft_bins = 1024 # Increased FFT size for better frequency resolution
self.n_display_bins = 32 # Maintain the same number of bars in the display
self.sample_rate = 44100
self.smoothing_factor = 0.1 # Smaller value for more smoothing
self.ema_fft_data = np.zeros(self.n_fft_bins // 2) # Initialize EMA array
self.ema_fft_data = np.zeros(
self.n_display_bins
) # Adjusted to the number of display bins
self.init_ui()
self.init_audio_stream()

Expand All @@ -22,36 +25,39 @@ def init_ui(self):
self.layout.addWidget(self.plot_widget)

# Customize plot aesthetics
self.plot_widget.setBackground('k')
self.plot_widget.setBackground("k")
self.plot_item = self.plot_widget.getPlotItem()
self.plot_item.setTitle("PyPhastFT RT Input Audio Spectrum Visualizer", color="w", size="16pt")

# self.plot_item.getAxis('left').setLabel("Magnitude", color="w", size="14pt")
# self.plot_item.getAxis('bottom').setLabel("Frequency (Hz)", color="w", size="14pt")

# Hide x and y axis lines, ticks, and numbers
# self.plot_item.getAxis('left').setStyle(showValues=False)
# self.plot_item.getAxis('left').setPen(pg.mkPen(None)) # Hides the axis line
# self.plot_item.getAxis('bottom').setStyle(showValues=False)
# self.plot_item.getAxis('bottom').setPen(pg.mkPen(None)) # Hides the axis line
self.plot_item.setTitle(
"Real-Time Audio Spectrum Visualizer\npowered by PhastFT",
color="w",
size="16pt",
)

# Hide axis labels
self.plot_item.getAxis('left').hide()
self.plot_item.getAxis('bottom').hide()
self.plot_item.getAxis("left").hide()
self.plot_item.getAxis("bottom").hide()

# Set fixed ranges for the x and y axes to prevent them from jumping
self.plot_item.setXRange(0, self.sample_rate / 2, padding=0)
self.plot_item.setYRange(0, 1, padding=0) # You may want to adjust this based on your FFT data
self.plot_item.setYRange(0, 1, padding=0)

self.bar_width = (self.sample_rate / 2) / (self.n_fft_bins // 2) * 0.90 # Width based on frequency spacing
self.bar_width = (
(self.sample_rate / 2) / self.n_display_bins * 0.90
) # Adjusted width for display bins

# Calculate bar positions so they are centered with respect to their frequency values
self.freqs = np.linspace(0 + self.bar_width/2, self.sample_rate / 2 - self.bar_width/2, self.n_fft_bins // 2)

# Adjust x-axis range to account for the full width of the first and last bars
self.plot_item.setXRange(-self.bar_width, self.sample_rate / 2 + self.bar_width, padding=0)
self.freqs = np.linspace(
0 + self.bar_width / 2,
self.sample_rate / 2 - self.bar_width / 2,
self.n_display_bins,
)

self.bar_graph = pg.BarGraphItem(x=self.freqs, height=np.zeros(self.n_fft_bins // 2), width=self.bar_width, brush=pg.mkBrush('m'))
self.bar_graph = pg.BarGraphItem(
x=self.freqs,
height=np.zeros(self.n_display_bins),
width=self.bar_width,
brush=pg.mkBrush("m"),
)
self.plot_item.addItem(self.bar_graph)

self.timer = QtCore.QTimer()
Expand All @@ -65,25 +71,35 @@ def init_audio_stream(self):
channels=1,
rate=self.sample_rate,
input=True,
frames_per_buffer=self.n_fft_bins,
frames_per_buffer=self.n_fft_bins, # This should match the FFT size
stream_callback=self.audio_callback,
)
self.stream.start_stream()

def audio_callback(self, in_data, frame_count, time_info, status):
audio_data = np.frombuffer(in_data, dtype=np.float32).astype(np.float64)
reals = audio_data
imaginaries = np.zeros(self.n_fft_bins, dtype=np.float64)
fft(reals, imaginaries, direction='f')
new_fft_data = np.sqrt(reals**2 + imaginaries**2)[:self.n_fft_bins // 2]
audio_data = np.frombuffer(in_data, dtype=np.float32)
reals = np.zeros(self.n_fft_bins)
imaginaries = np.zeros(self.n_fft_bins)
reals[: len(audio_data)] = audio_data # Fill the reals array with audio data
fft(reals, imaginaries, direction="f")
fft_magnitude = np.sqrt(reals**2 + imaginaries**2)[: self.n_fft_bins // 2]

# Aggregate or interpolate FFT data to fit into display bins
new_fft_data = np.interp(
np.linspace(0, len(fft_magnitude), self.n_display_bins),
np.arange(len(fft_magnitude)),
fft_magnitude,
)

# Apply exponential moving average filter
self.ema_fft_data = self.ema_fft_data * self.smoothing_factor + \
new_fft_data * (1 - self.smoothing_factor)
self.ema_fft_data = self.ema_fft_data * self.smoothing_factor + new_fft_data * (
1 - self.smoothing_factor
)
return (in_data, pyaudio.paContinue)

def update(self):
self.bar_graph.setOpts(height=self.ema_fft_data, width=self.bar_width)
if hasattr(self, "ema_fft_data"):
self.bar_graph.setOpts(height=self.ema_fft_data, width=self.bar_width)

def closeEvent(self, event):
self.stream.stop_stream()
Expand Down

0 comments on commit d1701ea

Please sign in to comment.