From c23a432b349a2f6bfbfe4331c1140745eab1961c Mon Sep 17 00:00:00 2001 From: Stamenov96 Date: Sat, 28 Aug 2021 03:21:49 +0300 Subject: [PATCH 1/7] Adding functionality for using WS2801 LED Controller --- python/config.py | 4 +++ python/install/setup.py | 4 +-- python/led.py | 58 ++++++++++++++++++++++++++++++++++------- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/python/config.py b/python/config.py index 65e21378..69f18a33 100644 --- a/python/config.py +++ b/python/config.py @@ -38,6 +38,10 @@ """Set True if using an inverting logic level converter""" SOFTWARE_GAMMA_CORRECTION = True """Set to True because Raspberry Pi doesn't use hardware dithering""" + LED_CONTROLLER = 'ws281x' + """There are different types of led controllers. Default is ws281x. + Another option is WS2801. + """ if DEVICE == 'blinkstick': SOFTWARE_GAMMA_CORRECTION = True diff --git a/python/install/setup.py b/python/install/setup.py index aaf58a0d..a50195ed 100644 --- a/python/install/setup.py +++ b/python/install/setup.py @@ -10,5 +10,5 @@ download_url="https://github.com/naztronaut/dancyPi-audio-reactive-led", description="Audio Reactive Raspberry Pi with WS2812b LEDs.", license="MIT", - install_requires=['numpy', 'pyaudio', 'pyqtgraph', 'scipy==1.4.1', 'rpi_ws281x'] -) \ No newline at end of file + install_requires=['numpy', 'pyaudio', 'pyqtgraph', 'scipy==1.4.1', 'rpi_ws281x', 'adafruit-ws2801'] +) diff --git a/python/led.py b/python/led.py index d96f3c5b..df8fa3f6 100644 --- a/python/led.py +++ b/python/led.py @@ -11,11 +11,25 @@ _sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Raspberry Pi controls the LED strip directly elif config.DEVICE == 'pi': - from rpi_ws281x import * - strip = Adafruit_NeoPixel(config.N_PIXELS, config.LED_PIN, - config.LED_FREQ_HZ, config.LED_DMA, - config.LED_INVERT, config.BRIGHTNESS) - strip.begin() + if config.LED_CONTROLLER == 'ws281x': + from rpi_ws281x import * + strip = Adafruit_NeoPixel(config.N_PIXELS, config.LED_PIN, + config.LED_FREQ_HZ, config.LED_DMA, + config.LED_INVERT, config.BRIGHTNESS) + strip.begin() + elif config.LED_CONTROLLER == 'ws2801': + # Make Raspberry pinout addresible + import RPi.GPIO as GPIO + + # Import the WS2801 module. + import Adafruit_WS2801 + import Adafruit_GPIO.SPI as SPI + + # Alternatively specify a hardware SPI connection on /dev/spidev0.0: + spi_port = 0 + spi_device = 0 + strip = Adafruit_WS2801.WS2801Pixels(config.N_PIXELS, spi=SPI.SpiDev(spi_port,spi_device), gpio=GPIO) + elif config.DEVICE == 'blinkstick': from blinkstick import blinkstick import signal @@ -82,8 +96,7 @@ def _update_esp8266(): _sock.sendto(m, (config.UDP_IP, config.UDP_PORT)) _prev_pixels = np.copy(p) - -def _update_pi(): +def _update_ws281x(): """Writes new LED values to the Raspberry Pi's LED strip Raspberry Pi uses the rpi_ws281x to control the LED strip directly. @@ -104,17 +117,44 @@ def _update_pi(): # Ignore pixels if they haven't changed (saves bandwidth) if np.array_equal(p[:, i], _prev_pixels[:, i]): continue - + strip._led_data[i] = int(rgb[i]) _prev_pixels = np.copy(p) strip.show() +def _update_ws2801(): + """Writes new LED values to the Raspberry Pi's LED strip + This function updates the LED strip with new values. + """ + global pixels, _prev_pixels + # Truncate values and cast to integer + pixels = np.clip(pixels, 0, 255).astype(int) + # Optional gamma correction + p = _gamma[pixels] if config.SOFTWARE_GAMMA_CORRECTION else np.copy(pixels) + r = [int(val) for val in p[0]] + g = [int(val) for val in p[1]] + b = [int(val) for val in p[2]] + # Update the pixels + for i in range(config.N_PIXELS): + # Ignore pixels if they haven't changed (saves bandwidth) + if np.array_equal(p[:, i], _prev_pixels[:, i]): + continue + + strip.set_pixel(i, Adafruit_WS2801.RGB_to_color(r[i],g[i],b[i])) + _prev_pixels = np.copy(p) + strip.show() + +def _update_pi(): + if config.LED_CONTROLLER == 'ws281x': + _update_ws281x() + elif config.LED_CONTROLLER == 'ws2801': + _update_ws2801() + def _update_blinkstick(): """Writes new LED values to the Blinkstick. This function updates the LED strip with new values. """ global pixels - # Truncate values and cast to integer pixels = np.clip(pixels, 0, 255).astype(int) # Optional gamma correction From b9cb8f9b6950bbd82239fb69e8b9fed8ef7fa0bd Mon Sep 17 00:00:00 2001 From: Stamenov96 Date: Mon, 27 Sep 2021 22:55:08 +0300 Subject: [PATCH 2/7] make config sutable for my case --- python/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/config.py b/python/config.py index 69f18a33..1ae80b87 100644 --- a/python/config.py +++ b/python/config.py @@ -38,7 +38,7 @@ """Set True if using an inverting logic level converter""" SOFTWARE_GAMMA_CORRECTION = True """Set to True because Raspberry Pi doesn't use hardware dithering""" - LED_CONTROLLER = 'ws281x' + LED_CONTROLLER = 'ws2801' """There are different types of led controllers. Default is ws281x. Another option is WS2801. """ @@ -53,7 +53,7 @@ DISPLAY_FPS = True """Whether to display the FPS when running (can reduce performance)""" -N_PIXELS = 144 +N_PIXELS = 160 """Number of pixels in the LED strip (must match ESP8266 firmware)""" GAMMA_TABLE_PATH = os.path.join(os.path.dirname(__file__), 'gamma_table.npy') @@ -105,5 +105,5 @@ N_ROLLING_HISTORY = 2 """Number of past audio frames to include in the rolling window""" -MIN_VOLUME_THRESHOLD = 1e-7 +MIN_VOLUME_THRESHOLD = 1e-6 """No music visualization displayed if recorded audio volume below threshold""" From 6da75542c9cae1534f28bf77850712ac41e13ef4 Mon Sep 17 00:00:00 2001 From: Stamenov96 Date: Mon, 27 Sep 2021 22:57:49 +0300 Subject: [PATCH 3/7] Fixing some bug that breaks the loop because the stream is closing --- python/microphone.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/python/microphone.py b/python/microphone.py index 0903f952..0d4bb7a7 100644 --- a/python/microphone.py +++ b/python/microphone.py @@ -16,7 +16,15 @@ def start_stream(callback): prev_ovf_time = time.time() while True: try: - y = np.fromstring(stream.read(frames_per_buffer, exception_on_overflow=False), dtype=np.int16) + try: + y = np.fromstring(stream.read(frames_per_buffer, exception_on_overflow=False), dtype=np.int16) + except OSError as ex: + if ex.errno == -9999: + stream = p.open(format=pyaudio.paInt16, + channels=1, + rate=config.MIC_RATE, + input=True, + frames_per_buffer=frames_per_buffer) y = y.astype(np.float32) stream.read(stream.get_read_available(), exception_on_overflow=False) callback(y) From 6d1cbb696ef4cf01aa2c1d0e5d459d87aec5f3ad Mon Sep 17 00:00:00 2001 From: Stamenov96 Date: Mon, 27 Sep 2021 22:58:25 +0300 Subject: [PATCH 4/7] Extract visualization in another function --- python/visualization.py | 194 ++++++++++++++++++++-------------------- 1 file changed, 98 insertions(+), 96 deletions(-) diff --git a/python/visualization.py b/python/visualization.py index 4802299d..fcb29a7d 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -263,105 +263,107 @@ def microphone_update(audio_samples): visualization_effect = visualization_type """Visualization effect to display on the LED strip""" +def use_gui(): + import pyqtgraph as pg + from pyqtgraph.Qt import QtGui, QtCore + # Create GUI window + app = QtGui.QApplication([]) + view = pg.GraphicsView() + layout = pg.GraphicsLayout(border=(100,100,100)) + view.setCentralItem(layout) + view.show() + view.setWindowTitle('Visualization') + view.resize(800,600) + # Mel filterbank plot + fft_plot = layout.addPlot(title='Filterbank Output', colspan=3) + fft_plot.setRange(yRange=[-0.1, 1.2]) + fft_plot.disableAutoRange(axis=pg.ViewBox.YAxis) + x_data = np.array(range(1, config.N_FFT_BINS + 1)) + mel_curve = pg.PlotCurveItem() + mel_curve.setData(x=x_data, y=x_data*0) + fft_plot.addItem(mel_curve) + # Visualization plot + layout.nextRow() + led_plot = layout.addPlot(title='Visualization Output', colspan=3) + led_plot.setRange(yRange=[-5, 260]) + led_plot.disableAutoRange(axis=pg.ViewBox.YAxis) + # Pen for each of the color channel curves + r_pen = pg.mkPen((255, 30, 30, 200), width=4) + g_pen = pg.mkPen((30, 255, 30, 200), width=4) + b_pen = pg.mkPen((30, 30, 255, 200), width=4) + # Color channel curves + r_curve = pg.PlotCurveItem(pen=r_pen) + g_curve = pg.PlotCurveItem(pen=g_pen) + b_curve = pg.PlotCurveItem(pen=b_pen) + # Define x data + x_data = np.array(range(1, config.N_PIXELS + 1)) + r_curve.setData(x=x_data, y=x_data*0) + g_curve.setData(x=x_data, y=x_data*0) + b_curve.setData(x=x_data, y=x_data*0) + # Add curves to plot + led_plot.addItem(r_curve) + led_plot.addItem(g_curve) + led_plot.addItem(b_curve) + # Frequency range label + freq_label = pg.LabelItem('') + # Frequency slider + def freq_slider_change(tick): + minf = freq_slider.tickValue(0)**2.0 * (config.MIC_RATE / 2.0) + maxf = freq_slider.tickValue(1)**2.0 * (config.MIC_RATE / 2.0) + t = 'Frequency range: {:.0f} - {:.0f} Hz'.format(minf, maxf) + freq_label.setText(t) + config.MIN_FREQUENCY = minf + config.MAX_FREQUENCY = maxf + dsp.create_mel_bank() + freq_slider = pg.TickSliderItem(orientation='bottom', allowAdd=False) + freq_slider.addTick((config.MIN_FREQUENCY / (config.MIC_RATE / 2.0))**0.5) + freq_slider.addTick((config.MAX_FREQUENCY / (config.MIC_RATE / 2.0))**0.5) + freq_slider.tickMoveFinished = freq_slider_change + freq_label.setText('Frequency range: {} - {} Hz'.format( + config.MIN_FREQUENCY, + config.MAX_FREQUENCY)) + # Effect selection + active_color = '#16dbeb' + inactive_color = '#FFFFFF' + def energy_click(x): + global visualization_effect + visualization_effect = visualize_energy + energy_label.setText('Energy', color=active_color) + scroll_label.setText('Scroll', color=inactive_color) + spectrum_label.setText('Spectrum', color=inactive_color) + def scroll_click(x): + global visualization_effect + visualization_effect = visualize_scroll + energy_label.setText('Energy', color=inactive_color) + scroll_label.setText('Scroll', color=active_color) + spectrum_label.setText('Spectrum', color=inactive_color) + def spectrum_click(x): + global visualization_effect + visualization_effect = visualize_spectrum + energy_label.setText('Energy', color=inactive_color) + scroll_label.setText('Scroll', color=inactive_color) + spectrum_label.setText('Spectrum', color=active_color) + # Create effect "buttons" (labels with click event) + energy_label = pg.LabelItem('Energy') + scroll_label = pg.LabelItem('Scroll') + spectrum_label = pg.LabelItem('Spectrum') + energy_label.mousePressEvent = energy_click + scroll_label.mousePressEvent = scroll_click + spectrum_label.mousePressEvent = spectrum_click + energy_click(0) + # Layout + layout.nextRow() + layout.addItem(freq_label, colspan=3) + layout.nextRow() + layout.addItem(freq_slider, colspan=3) + layout.nextRow() + layout.addItem(energy_label) + layout.addItem(scroll_label) + layout.addItem(spectrum_label) if __name__ == '__main__': if config.USE_GUI: - import pyqtgraph as pg - from pyqtgraph.Qt import QtGui, QtCore - # Create GUI window - app = QtGui.QApplication([]) - view = pg.GraphicsView() - layout = pg.GraphicsLayout(border=(100,100,100)) - view.setCentralItem(layout) - view.show() - view.setWindowTitle('Visualization') - view.resize(800,600) - # Mel filterbank plot - fft_plot = layout.addPlot(title='Filterbank Output', colspan=3) - fft_plot.setRange(yRange=[-0.1, 1.2]) - fft_plot.disableAutoRange(axis=pg.ViewBox.YAxis) - x_data = np.array(range(1, config.N_FFT_BINS + 1)) - mel_curve = pg.PlotCurveItem() - mel_curve.setData(x=x_data, y=x_data*0) - fft_plot.addItem(mel_curve) - # Visualization plot - layout.nextRow() - led_plot = layout.addPlot(title='Visualization Output', colspan=3) - led_plot.setRange(yRange=[-5, 260]) - led_plot.disableAutoRange(axis=pg.ViewBox.YAxis) - # Pen for each of the color channel curves - r_pen = pg.mkPen((255, 30, 30, 200), width=4) - g_pen = pg.mkPen((30, 255, 30, 200), width=4) - b_pen = pg.mkPen((30, 30, 255, 200), width=4) - # Color channel curves - r_curve = pg.PlotCurveItem(pen=r_pen) - g_curve = pg.PlotCurveItem(pen=g_pen) - b_curve = pg.PlotCurveItem(pen=b_pen) - # Define x data - x_data = np.array(range(1, config.N_PIXELS + 1)) - r_curve.setData(x=x_data, y=x_data*0) - g_curve.setData(x=x_data, y=x_data*0) - b_curve.setData(x=x_data, y=x_data*0) - # Add curves to plot - led_plot.addItem(r_curve) - led_plot.addItem(g_curve) - led_plot.addItem(b_curve) - # Frequency range label - freq_label = pg.LabelItem('') - # Frequency slider - def freq_slider_change(tick): - minf = freq_slider.tickValue(0)**2.0 * (config.MIC_RATE / 2.0) - maxf = freq_slider.tickValue(1)**2.0 * (config.MIC_RATE / 2.0) - t = 'Frequency range: {:.0f} - {:.0f} Hz'.format(minf, maxf) - freq_label.setText(t) - config.MIN_FREQUENCY = minf - config.MAX_FREQUENCY = maxf - dsp.create_mel_bank() - freq_slider = pg.TickSliderItem(orientation='bottom', allowAdd=False) - freq_slider.addTick((config.MIN_FREQUENCY / (config.MIC_RATE / 2.0))**0.5) - freq_slider.addTick((config.MAX_FREQUENCY / (config.MIC_RATE / 2.0))**0.5) - freq_slider.tickMoveFinished = freq_slider_change - freq_label.setText('Frequency range: {} - {} Hz'.format( - config.MIN_FREQUENCY, - config.MAX_FREQUENCY)) - # Effect selection - active_color = '#16dbeb' - inactive_color = '#FFFFFF' - def energy_click(x): - global visualization_effect - visualization_effect = visualize_energy - energy_label.setText('Energy', color=active_color) - scroll_label.setText('Scroll', color=inactive_color) - spectrum_label.setText('Spectrum', color=inactive_color) - def scroll_click(x): - global visualization_effect - visualization_effect = visualize_scroll - energy_label.setText('Energy', color=inactive_color) - scroll_label.setText('Scroll', color=active_color) - spectrum_label.setText('Spectrum', color=inactive_color) - def spectrum_click(x): - global visualization_effect - visualization_effect = visualize_spectrum - energy_label.setText('Energy', color=inactive_color) - scroll_label.setText('Scroll', color=inactive_color) - spectrum_label.setText('Spectrum', color=active_color) - # Create effect "buttons" (labels with click event) - energy_label = pg.LabelItem('Energy') - scroll_label = pg.LabelItem('Scroll') - spectrum_label = pg.LabelItem('Spectrum') - energy_label.mousePressEvent = energy_click - scroll_label.mousePressEvent = scroll_click - spectrum_label.mousePressEvent = spectrum_click - energy_click(0) - # Layout - layout.nextRow() - layout.addItem(freq_label, colspan=3) - layout.nextRow() - layout.addItem(freq_slider, colspan=3) - layout.nextRow() - layout.addItem(energy_label) - layout.addItem(scroll_label) - layout.addItem(spectrum_label) + use_gui() # Initialize LEDs led.update() # Start listening to live audio stream From 52edce9f516063bf004a474e1fdeb4db9609f45f Mon Sep 17 00:00:00 2001 From: Stamenov96 Date: Mon, 27 Sep 2021 23:17:54 +0300 Subject: [PATCH 5/7] More configurations --- python/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/config.py b/python/config.py index 1ae80b87..44eed50d 100644 --- a/python/config.py +++ b/python/config.py @@ -59,7 +59,7 @@ GAMMA_TABLE_PATH = os.path.join(os.path.dirname(__file__), 'gamma_table.npy') """Location of the gamma correction table""" -MIC_RATE = 48000 +MIC_RATE = 44100 """Sampling frequency of the microphone in Hz""" FPS = 50 From 97e452899ff63a71fd46ca184c968a6086258811 Mon Sep 17 00:00:00 2001 From: Stamenov96 Date: Mon, 27 Sep 2021 23:26:14 +0300 Subject: [PATCH 6/7] Adding two new effects from no_mic branch --- python/visualization.py | 53 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/python/visualization.py b/python/visualization.py index fcb29a7d..e45731f1 100644 --- a/python/visualization.py +++ b/python/visualization.py @@ -10,6 +10,7 @@ import sys visualization_type = sys.argv[1] +scroll_divisor_config = 4 if sys.argv[1] == "scroll_quad" else 2 _time_prev = time.time() * 1000.0 """The previous time that the frames_per_second() function was called""" @@ -100,7 +101,9 @@ def interpolate(y, new_length): alpha_decay=0.99, alpha_rise=0.01) p_filt = dsp.ExpFilter(np.tile(1, (3, config.N_PIXELS // 2)), alpha_decay=0.1, alpha_rise=0.99) -p = np.tile(1.0, (3, config.N_PIXELS // 2)) +# scroll_divisor_config config is set to 2 if scroll_quad is sent in the arg +p = np.tile(1.0, (3, config.N_PIXELS // scroll_divisor_config)) + gain = dsp.ExpFilter(np.tile(0.01, config.N_FFT_BINS), alpha_decay=0.001, alpha_rise=0.99) @@ -126,6 +129,49 @@ def visualize_scroll(y): # Update the LED strip return np.concatenate((p[:, ::-1], p), axis=1) +def visualize_scroll_quad(y): + """Effect that originates in two center points and scrolls outwards + Should only be used with LED count that is divisible by 4 + """ + global p + y = y**2.0 + gain.update(y) + y /= gain.value + y *= 255.0 + r = int(np.max(y[:len(y) // 3])) + g = int(np.max(y[len(y) // 3: 2 * len(y) // 3])) + b = int(np.max(y[2 * len(y) // 3:])) + # Scrolling effect window + p[:, 1:] = p[:, :-1] + p *= 0.98 + p = gaussian_filter1d(p, sigma=0.2) + # Create new color originating at the center + p[0, 0] = r + p[1, 0] = g + p[2, 0] = b + # Update the LED strip + return np.concatenate((p[:, ::-1], p, p[:, ::-1], p), axis=1) + +def visualize_scroll_in(y): + """Effect that originates in the outside and scrolls inwards""" + global p + y = y**2.0 + gain.update(y) + y /= gain.value + y *= 255.0 + r = int(np.max(y[:len(y) // 3])) + g = int(np.max(y[len(y) // 3: 2 * len(y) // 3])) + b = int(np.max(y[2 * len(y) // 3:])) + # Scrolling effect window + p[:, 1:] = p[:, :-1] + p *= 0.98 + p = gaussian_filter1d(p, sigma=0.2) + # Create new color originating at the center + p[0, 0] = r + p[1, 0] = g + p[2, 0] = b + # Update the LED strip + return np.concatenate((p[:, :], p[:, ::-1]), axis=1) def visualize_energy(y): """Effect that expands from the center with increasing sound energy""" @@ -257,6 +303,11 @@ def microphone_update(audio_samples): visualization_type = visualize_energy elif sys.argv[1] == "scroll": visualization_type = visualize_scroll +elif sys.argv[1] == "scroll_in": + visualization_type = visualize_scroll_in +elif sys.argv[1] == "scroll_quad": + visualization_type = visualize_scroll_quad + else: visualization_type = visualize_spectrum From e803452109f79f0100ca8b8c61c79d38a7ba7d68 Mon Sep 17 00:00:00 2001 From: Stamenov96 Date: Tue, 28 Sep 2021 01:03:38 +0300 Subject: [PATCH 7/7] more configs --- python/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/config.py b/python/config.py index 44eed50d..45c7c944 100644 --- a/python/config.py +++ b/python/config.py @@ -53,7 +53,7 @@ DISPLAY_FPS = True """Whether to display the FPS when running (can reduce performance)""" -N_PIXELS = 160 +N_PIXELS = 158 """Number of pixels in the LED strip (must match ESP8266 firmware)""" GAMMA_TABLE_PATH = os.path.join(os.path.dirname(__file__), 'gamma_table.npy') @@ -105,5 +105,5 @@ N_ROLLING_HISTORY = 2 """Number of past audio frames to include in the rolling window""" -MIN_VOLUME_THRESHOLD = 1e-6 +MIN_VOLUME_THRESHOLD = 1e-7 """No music visualization displayed if recorded audio volume below threshold"""