Lots of little updates behind the scenes
This commit is contained in:
parent
35358e2d75
commit
cd06d65e60
@ -1,13 +1,223 @@
|
||||
"""Settings for audio reactive LED strip"""
|
||||
"""Default settings and configuration for audio reactive LED strip"""
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
import os
|
||||
|
||||
DEVICE = 'stripless'
|
||||
"""Device used to control LED strip. Must be 'pi', 'esp8266' or 'blinkstick'
|
||||
use_defaults = {"configuration": True, # See notes below for detailed explanation
|
||||
"GUI_opts": False,
|
||||
"devices": True,
|
||||
"colors": True,
|
||||
"gradients": True}
|
||||
|
||||
settings = { # All settings are stored in this dict
|
||||
|
||||
"configuration":{ # Program configuration
|
||||
'USE_GUI': True, # Whether to display the GUI
|
||||
'DISPLAY_FPS': False, # Whether to print the FPS when running (can reduce performance)
|
||||
'MIC_RATE': 48000, # Sampling frequency of the microphone in Hz
|
||||
'FPS': 60, # Desired refresh rate of the visualization (frames per second)
|
||||
'MIN_FREQUENCY': 20, # Frequencies below this value will be removed during audio processing
|
||||
'MAX_FREQUENCY': 18000, # Frequencies above this value will be removed during audio processing
|
||||
'MAX_BRIGHTNESS': 250, # Frequencies above this value will be removed during audio processing
|
||||
'N_ROLLING_HISTORY': 4, # Number of past audio frames to include in the rolling window
|
||||
'MIN_VOLUME_THRESHOLD': 0.01 # No music visualization displayed if recorded audio volume below threshold
|
||||
#'LOGARITHMIC_SCALING': True, # Scale frequencies logarithmically to match perceived pitch of human ear
|
||||
},
|
||||
|
||||
"GUI_opts":{"Graphs":True, # Which parts of the gui to show
|
||||
"Reactive Effect Buttons":True,
|
||||
"Non Reactive Effect Buttons":True,
|
||||
"Frequency Range":True,
|
||||
"Effect Options":True},
|
||||
|
||||
# All devices and their respective settings. Indexed by name, call each one what you want.
|
||||
"devices":{"Desk Strip":{
|
||||
"configuration":{"TYPE": "esp8266", # Device type (see below for all supported boards)
|
||||
# Required configuration for device. See below for all required keys per device
|
||||
"AUTO_DETECT": True, # Set this true if you're using windows hotspot to connect (see below for more info)
|
||||
"MAC_ADDR": "2c-3a-e8-2f-2c-9f", # MAC address of the ESP8266. Only used if AUTO_DETECT is True
|
||||
"UDP_IP": "192.168.1.208", # IP address of the ESP8266. Must match IP in ws2812_controller.ino
|
||||
"UDP_PORT": 7778, # Port number used for socket communication between Python and ESP8266
|
||||
"MAX_BRIGHTNESS": 250, # Max brightness of output (0-255) (my strip sometimes bugs out with high brightness)
|
||||
# Other configuration
|
||||
"N_PIXELS": 58, # Number of pixels in the LED strip (must match ESP8266 firmware)
|
||||
"N_FFT_BINS": 24, # Number of frequency bins to use when transforming audio to frequency domain
|
||||
"current_effect": "Single" # Currently selected effect for this board, used as default when program launches
|
||||
},
|
||||
|
||||
# Configurable options for this board's effects go in this dictionary.
|
||||
# Usage: config.settings["devices"][name]["effect_opts"][effect][option]
|
||||
"effect_opts":{"Energy": {"blur": 1, # Amount of blur to apply
|
||||
"scale":0.9, # Width of effect on strip
|
||||
"r_multiplier": 1.0, # How much red
|
||||
"g_multiplier": 1.0, # How much green
|
||||
"b_multiplier": 1.0}, # How much blue
|
||||
"Wave": {"color_wave": "Red", # Colour of moving bit
|
||||
"color_flash": "White", # Colour of flashy bit
|
||||
"wipe_len":5, # Initial length of colour bit after beat
|
||||
"decay": 0.7, # How quickly the flash fades away
|
||||
"wipe_speed":2}, # Number of pixels added to colour bit every frame
|
||||
"Spectrum": {"r_multiplier": 1.0, # How much red
|
||||
"g_multiplier": 1.0, # How much green
|
||||
"b_multiplier": 1.0}, # How much blue
|
||||
"Wavelength":{"roll_speed": 0, # How fast (if at all) to cycle colour overlay across strip
|
||||
"color_mode": "Spectral", # Colour gradient to display
|
||||
"mirror": False, # Reflect output down centre of strip
|
||||
"reverse_grad": False, # Flip (LR) gradient
|
||||
"reverse_roll": False, # Reverse movement of gradient roll
|
||||
"blur": 3.0, # Amount of blur to apply
|
||||
"flip_lr":False}, # Flip output left-right
|
||||
"Scroll": {"decay": 0.995, # How quickly the colour fades away as it moves
|
||||
"speed": 1, # Speed of scroll
|
||||
"r_multiplier": 1.0, # How much red
|
||||
"g_multiplier": 1.0, # How much green
|
||||
"b_multiplier": 1.0, # How much blue
|
||||
"blur": 0.2}, # Amount of blur to apply
|
||||
"Power": {"color_mode": "Spectral", # Colour gradient to display
|
||||
"s_count": 20, # Initial number of sparks
|
||||
"s_color": "White", # Color of sparks
|
||||
"mirror": True, # Mirror output down central axis
|
||||
"flip_lr":False}, # Flip output left-right
|
||||
"Single": {"color": "Purple"}, # Static color to show
|
||||
"Beat": {"color": "Red", # Colour of beat flash
|
||||
"decay": 0.7}, # How quickly the flash fades away
|
||||
"Bars": {"resolution":4, # Number of "bars"
|
||||
"color_mode":"Spectral", # Multicolour mode to use
|
||||
"roll_speed":0, # How fast (if at all) to cycle colour colours across strip
|
||||
"mirror": False, # Mirror down centre of strip
|
||||
#"reverse_grad": False, # Flip (LR) gradient
|
||||
"reverse_roll": False, # Reverse movement of gradient roll
|
||||
"flip_lr":False}, # Flip output left-right
|
||||
"Gradient": {"color_mode":"Spectral", # Colour gradient to display
|
||||
"roll_speed": 0, # How fast (if at all) to cycle colour colours across strip
|
||||
"mirror": False, # Mirror gradient down central axis
|
||||
"reverse": False}, # Reverse movement of gradient
|
||||
"Fade": {"color_mode":"Spectral", # Colour gradient to fade through
|
||||
"roll_speed": 1, # How fast (if at all) to fade through colours
|
||||
"reverse": False}, # Reverse "direction" of fade (r->g->b or r<-g<-b)
|
||||
"Calibration":{"r": 100,
|
||||
"g": 100,
|
||||
"b": 100}
|
||||
}
|
||||
},
|
||||
"Main Strip":{
|
||||
"configuration":{"TYPE": "esp8266", # Device type (see below for all supported boards)
|
||||
# Required configuration for device. See below for all required keys per device
|
||||
"AUTO_DETECT": True, # Set this true if you're using windows hotspot to connect (see below for more info)
|
||||
"MAC_ADDR": "5c-cf-7f-f0-8c-f3", # MAC address of the ESP8266. Only used if AUTO_DETECT is True
|
||||
"UDP_IP": "192.168.1.208", # IP address of the ESP8266. Must match IP in ws2812_controller.ino
|
||||
"UDP_PORT": 7778, # Port number used for socket communication between Python and ESP8266
|
||||
"MAX_BRIGHTNESS": 180, # Max brightness of output (0-255) (my strip sometimes bugs out with high brightness)
|
||||
# Other configuration
|
||||
"N_PIXELS": 226, # Number of pixels in the LED strip (must match ESP8266 firmware)
|
||||
"N_FFT_BINS": 24, # Number of frequency bins to use when transforming audio to frequency domain
|
||||
"current_effect": "Single" # Currently selected effect for this board, used as default when program launches
|
||||
},
|
||||
|
||||
# Configurable options for this board's effects go in this dictionary.
|
||||
# Usage: config.settings["devices"][name]["effect_opts"][effect][option]
|
||||
"effect_opts":{"Energy": {"blur": 1, # Amount of blur to apply
|
||||
"scale":0.9, # Width of effect on strip
|
||||
"r_multiplier": 1.0, # How much red
|
||||
"g_multiplier": 1.0, # How much green
|
||||
"b_multiplier": 1.0}, # How much blue
|
||||
"Wave": {"color_wave": "Red", # Colour of moving bit
|
||||
"color_flash": "White", # Colour of flashy bit
|
||||
"wipe_len":5, # Initial length of colour bit after beat
|
||||
"decay": 0.7, # How quickly the flash fades away
|
||||
"wipe_speed":2}, # Number of pixels added to colour bit every frame
|
||||
"Spectrum": {"r_multiplier": 1.0, # How much red
|
||||
"g_multiplier": 1.0, # How much green
|
||||
"b_multiplier": 1.0}, # How much blue
|
||||
"Wavelength":{"roll_speed": 0, # How fast (if at all) to cycle colour overlay across strip
|
||||
"color_mode": "Spectral", # Colour gradient to display
|
||||
"mirror": False, # Reflect output down centre of strip
|
||||
"reverse_grad": False, # Flip (LR) gradient
|
||||
"reverse_roll": False, # Reverse movement of gradient roll
|
||||
"blur": 3.0, # Amount of blur to apply
|
||||
"flip_lr":False}, # Flip output left-right
|
||||
"Scroll": {"decay": 0.995, # How quickly the colour fades away as it moves
|
||||
"speed": 1, # Speed of scroll
|
||||
"r_multiplier": 1.0, # How much red
|
||||
"g_multiplier": 1.0, # How much green
|
||||
"b_multiplier": 1.0, # How much blue
|
||||
"blur": 0.2}, # Amount of blur to apply
|
||||
"Power": {"color_mode": "Spectral", # Colour gradient to display
|
||||
"s_count": 20, # Initial number of sparks
|
||||
"s_color": "White", # Color of sparks
|
||||
"mirror": True, # Mirror output down central axis
|
||||
"flip_lr":False}, # Flip output left-right
|
||||
"Single": {"color": "Purple"}, # Static color to show
|
||||
"Beat": {"color": "Red", # Colour of beat flash
|
||||
"decay": 0.7}, # How quickly the flash fades away
|
||||
"Bars": {"resolution":4, # Number of "bars"
|
||||
"color_mode":"Spectral", # Multicolour mode to use
|
||||
"roll_speed":0, # How fast (if at all) to cycle colour colours across strip
|
||||
"mirror": False, # Mirror down centre of strip
|
||||
#"reverse_grad": False, # Flip (LR) gradient
|
||||
"reverse_roll": False, # Reverse movement of gradient roll
|
||||
"flip_lr":False}, # Flip output left-right
|
||||
"Gradient": {"color_mode":"Spectral", # Colour gradient to display
|
||||
"roll_speed": 0, # How fast (if at all) to cycle colour colours across strip
|
||||
"mirror": False, # Mirror gradient down central axis
|
||||
"reverse": False}, # Reverse movement of gradient
|
||||
"Fade": {"color_mode":"Spectral", # Colour gradient to fade through
|
||||
"roll_speed": 1, # How fast (if at all) to fade through colours
|
||||
"reverse": False}, # Reverse "direction" of fade (r->g->b or r<-g<-b)
|
||||
"Calibration":{"r": 100,
|
||||
"g": 100,
|
||||
"b": 100}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
# Collection of different colours in RGB format
|
||||
"colors":{"Red":(255,0,0),
|
||||
"Orange":(255,40,0),
|
||||
"Yellow":(255,255,0),
|
||||
"Green":(0,255,0),
|
||||
"Blue":(0,0,255),
|
||||
"Light blue":(1,247,161),
|
||||
"Purple":(80,5,252),
|
||||
"Pink":(255,0,178),
|
||||
"White":(255,255,255)},
|
||||
|
||||
# Multicolour gradients. Colours must be in list above
|
||||
"gradients":{"Spectral" : ["Red", "Orange", "Yellow", "Green", "Light blue", "Blue", "Purple", "Pink"],
|
||||
"Dancefloor": ["Red", "Pink", "Purple", "Blue"],
|
||||
"Sunset" : ["Red", "Orange", "Yellow"],
|
||||
"Ocean" : ["Green", "Light blue", "Blue"],
|
||||
"Jungle" : ["Green", "Red", "Orange"],
|
||||
"Sunny" : ["Yellow", "Light blue", "Orange", "Blue"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"""
|
||||
~~ NOTES ~~
|
||||
|
||||
[use_defaults]
|
||||
|
||||
For any dicts in this file (config.py), you can add them into the use_defaults
|
||||
dict to force the program to use these values over any stored in settings.ini
|
||||
that you would have set using the GUI. At runtime, settings.ini is used to update
|
||||
the above dicts with custom set values.
|
||||
|
||||
If you're running a headless RPi, you may want to edit settings in this file, then
|
||||
specify to use the dict you wrote, rather than have the program overwrite from
|
||||
settings.ini at runtime. You could also run the program with the gui, set the
|
||||
settings that you want, then disable the gui and the custom settings will still
|
||||
be loaded. Basically it works as you would expect it to.
|
||||
|
||||
[DEVICE TYPE]
|
||||
|
||||
Device used to control LED strip.
|
||||
|
||||
'esp8266' means that you are using an ESP8266 module to control the LED strip
|
||||
and commands will be sent to the ESP8266 over WiFi.
|
||||
and commands will be sent to the ESP8266 over WiFi. You can have as many of
|
||||
these as your computer is able to handle.
|
||||
|
||||
'pi' means that you are using a Raspberry Pi as a standalone unit to process
|
||||
audio input and control the LED strip directly.
|
||||
@ -15,65 +225,45 @@ audio input and control the LED strip directly.
|
||||
'blinkstick' means that a BlinkstickPro is connected to this PC which will be used
|
||||
to control the leds connected to it.
|
||||
|
||||
'fadecandy' means that a fadecandy server is running on your computer and is connected
|
||||
via usb to a fadecandy board connected to LEDs
|
||||
|
||||
'dotstar' creates an APA102-based output device. LMK if you have any success
|
||||
getting this to work becuase i have no clue if it will.
|
||||
|
||||
'stripless' means that the program will run without sending data to a strip.
|
||||
Useful for development etc, but doesn't look half as good ;)
|
||||
"""
|
||||
|
||||
if DEVICE == 'esp8266':
|
||||
AUTO_DETECT = False
|
||||
"""Set to true if the ip address of the device changes. This is the case if it's connecting
|
||||
through windows hotspot for instance. If so, give the mac address of the device."""
|
||||
MAC_ADDR = "5c-cf-7f-f0-8c-f3"
|
||||
"""MAC address of the ESP8266."""
|
||||
UDP_IP = "192.168.1.68"
|
||||
"""IP address of the ESP8266.
|
||||
Unless using auto detect, it must match IP in ws2812_controller.ino"""
|
||||
UDP_PORT = 7778
|
||||
"""Port number used for socket communication between Python and ESP8266"""
|
||||
SOFTWARE_GAMMA_CORRECTION = False
|
||||
"""Set to False because the firmware handles gamma correction + dither"""
|
||||
[REQUIRED CONFIGURATION KEYS]
|
||||
|
||||
elif DEVICE == 'pi':
|
||||
LED_PIN = 18
|
||||
"""GPIO pin connected to the LED strip pixels (must support PWM)"""
|
||||
LED_FREQ_HZ = 800000
|
||||
"""LED signal frequency in Hz (usually 800kHz)"""
|
||||
LED_DMA = 5
|
||||
"""DMA channel used for generating PWM signal (try 5)"""
|
||||
BRIGHTNESS = 255
|
||||
"""Brightness of LED strip between 0 and 255"""
|
||||
LED_INVERT = True
|
||||
"""Set True if using an inverting logic level converter"""
|
||||
SOFTWARE_GAMMA_CORRECTION = True
|
||||
"""Set to True because Raspberry Pi doesn't use hardware dithering"""
|
||||
===== 'esp8266'
|
||||
"AUTO_DETECT" # Set this true if you're using windows hotspot to connect (see below for more info)
|
||||
"MAC_ADDR" # MAC address of the ESP8266. Only used if AUTO_DETECT is True
|
||||
"UDP_IP" # IP address of the ESP8266. Must match IP in ws2812_controller.ino
|
||||
"UDP_PORT" # Port number used for socket communication between Python and ESP8266
|
||||
===== 'pi'
|
||||
"LED_PIN" # GPIO pin connected to the LED strip pixels (must support PWM)
|
||||
"LED_FREQ_HZ" # LED signal frequency in Hz (usually 800kHz)
|
||||
"LED_DMA" # DMA channel used for generating PWM signal (try 5)
|
||||
"BRIGHTNESS" # Brightness of LED strip between 0 and 255
|
||||
"LED_INVERT" # Set True if using an inverting logic level converter
|
||||
===== 'blinkstick'
|
||||
No required configuration keys
|
||||
===== 'fadecandy'
|
||||
"SERVER" # Address of fadecandy server. (usually 'localhost:7890')
|
||||
===== 'dotstar'
|
||||
No required configuration keys
|
||||
===== 'stripless'
|
||||
No required configuration keys (heh)
|
||||
|
||||
elif DEVICE == 'blinkstick':
|
||||
SOFTWARE_GAMMA_CORRECTION = True
|
||||
"""Set to True because blinkstick doesn't use hardware dithering"""
|
||||
[AUTO_DETECT]
|
||||
|
||||
elif DEVICE == 'stripless':
|
||||
pass
|
||||
Set to true if the ip address of the device changes. This is the case if it's connecting
|
||||
through windows hotspot, for instance. If so, give the mac address of the device. This
|
||||
allows windows to look for the device's IP using "arp -a" and finding the matching
|
||||
mac address. I haven't tested this on Linux or macOS.
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid device selected. Device {} not known.".format(DEVICE))
|
||||
|
||||
USE_GUI = True
|
||||
"""Whether or not to display a PyQtGraph GUI plot of visualization"""
|
||||
|
||||
DISPLAY_FPS = False
|
||||
"""Whether to display the FPS when running (can reduce performance)"""
|
||||
|
||||
N_PIXELS = 242
|
||||
"""Number of pixels in the LED strip (must match ESP8266 firmware)"""
|
||||
|
||||
GAMMA_TABLE_PATH = os.path.join(os.path.dirname(__file__), 'gamma_table.npy')
|
||||
"""Location of the gamma correction table"""
|
||||
|
||||
MIC_RATE = 48000
|
||||
"""Sampling frequency of the microphone in Hz"""
|
||||
|
||||
FPS = 40
|
||||
"""Desired refresh rate of the visualization (frames per second)
|
||||
[FPS]
|
||||
|
||||
FPS indicates the desired refresh rate, or frames-per-second, of the audio
|
||||
visualization. The actual refresh rate may be lower if the computer cannot keep
|
||||
@ -87,21 +277,8 @@ appear "sluggish" or out of sync with the audio being played if it is too low.
|
||||
|
||||
The FPS should not exceed the maximum refresh rate of the LED strip, which
|
||||
depends on how long the LED strip is.
|
||||
"""
|
||||
_max_led_FPS = int(((N_PIXELS * 30e-6) + 50e-6)**-1.0)
|
||||
assert FPS <= _max_led_FPS, 'FPS must be <= {}'.format(_max_led_FPS)
|
||||
|
||||
MIN_FREQUENCY = 20
|
||||
"""Frequencies below this value will be removed during audio processing"""
|
||||
|
||||
MAX_FREQUENCY = 18000
|
||||
"""Frequencies above this value will be removed during audio processing"""
|
||||
|
||||
LOGARITHMIC_SCALING = True
|
||||
"""Scale frequencies logarithmically to match perceived pitch of human ear"""
|
||||
|
||||
N_FFT_BINS = 24
|
||||
"""Number of frequency bins to use when transforming audio to frequency domain
|
||||
[N_FFT_BINS]
|
||||
|
||||
Fast Fourier transforms are used to transform time-domain audio data to the
|
||||
frequency domain. The frequencies present in the audio signal are assigned
|
||||
@ -115,8 +292,28 @@ number of bins. More bins is not always better!
|
||||
There is no point using more bins than there are pixels on the LED strip.
|
||||
"""
|
||||
|
||||
N_ROLLING_HISTORY = 4
|
||||
"""Number of past audio frames to include in the rolling window"""
|
||||
for board in settings["devices"]:
|
||||
if settings["devices"][board]["configuration"]["TYPE"] == 'esp8266':
|
||||
settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False
|
||||
# Set to False because the firmware handles gamma correction + dither
|
||||
elif settings["devices"][board]["configuration"]["TYPE"] == 'pi':
|
||||
settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = True
|
||||
# Set to True because Raspberry Pi doesn't use hardware dithering
|
||||
elif settings["devices"][board]["configuration"]["TYPE"] == 'blinkstick':
|
||||
settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = True
|
||||
elif settings["devices"][board]["configuration"]["TYPE"] == 'dotstar':
|
||||
settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False
|
||||
elif settings["devices"][board]["configuration"]["TYPE"] == 'fadecandy':
|
||||
settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False
|
||||
elif settings["devices"][board]["configuration"]["TYPE"] == 'stripless':
|
||||
settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False
|
||||
else:
|
||||
raise ValueError("Invalid device selected. Device {} not known.".format(settings["devices"][board]["configuration"]["TYPE"]))
|
||||
settings["devices"][board]["effect_opts"]["Power"]["s_count"] = settings["devices"][board]["configuration"]["N_PIXELS"]//6
|
||||
# Cheeky lil fix in case the user sets an odd number of LEDs
|
||||
if settings["devices"][board]["configuration"]["N_PIXELS"] % 2:
|
||||
settings["devices"][board]["configuration"]["N_PIXELS"] -= 1
|
||||
|
||||
# Ignore these
|
||||
# settings["configuration"]['_max_led_FPS'] = int(((settings["configuration"]["N_PIXELS"] * 30e-6) + 50e-6)**-1.0)
|
||||
|
||||
MIN_VOLUME_THRESHOLD = 1e-3
|
||||
"""No music visualization displayed if recorded audio volume below threshold"""
|
||||
|
289
python/lib/devices.py
Normal file
289
python/lib/devices.py
Normal file
@ -0,0 +1,289 @@
|
||||
import time
|
||||
import numpy as np
|
||||
import lib.config as config
|
||||
|
||||
_GAMMA_TABLE = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5,
|
||||
5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11,
|
||||
11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17,
|
||||
18, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25,
|
||||
26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 35,
|
||||
35, 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46,
|
||||
47, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58,
|
||||
59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73,
|
||||
74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88,
|
||||
89, 91, 92, 93, 94, 95, 97, 98, 99, 100, 102, 103, 104,
|
||||
105, 107, 108, 109, 111, 112, 113, 115, 116, 117, 119,
|
||||
120, 121, 123, 124, 126, 127, 128, 130, 131, 133, 134,
|
||||
136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151,
|
||||
152, 154, 155, 157, 158, 160, 162, 163, 165, 166, 168,
|
||||
170, 171, 173, 175, 176, 178, 180, 181, 183, 185, 186,
|
||||
188, 190, 192, 193, 195, 197, 199, 200, 202, 204, 206,
|
||||
207, 209, 211, 213, 215, 217, 218, 220, 222, 224, 226,
|
||||
228, 230, 232, 233, 235, 237, 239, 241, 243, 245, 247,
|
||||
249, 251, 253, 255]
|
||||
_GAMMA_TABLE = np.array(_GAMMA_TABLE)
|
||||
|
||||
class LEDController:
|
||||
"""Base class for interfacing with hardware LED strip controllers
|
||||
To add support for another hardware device, simply inherit this class
|
||||
and implement the show() method.
|
||||
Example usage:
|
||||
import numpy as np
|
||||
N_pixels = 60
|
||||
pixels = np.random.random(size=(3, N_pixels))
|
||||
device = LEDController()
|
||||
device.show(pixels)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def show(self, pixels):
|
||||
"""Set LED pixels to the values given in the array
|
||||
This function accepts an array of RGB pixel values (pixels)
|
||||
and displays them on the LEDs. To add support for another
|
||||
hardware device, you should create a class that inherits from
|
||||
this class, and then implement this method.
|
||||
Parameters
|
||||
----------
|
||||
pixels: numpy.ndarray
|
||||
2D array containing RGB pixel values for each of the LEDs.
|
||||
The shape of the array is (3, n_pixels), where n_pixels is the
|
||||
number of LEDs that the device has.
|
||||
The array is formatted as shown below. There are three rows
|
||||
(axis 0) which represent the red, green, and blue color channels.
|
||||
Each column (axis 1) contains the red, green, and blue color values
|
||||
for a single pixel:
|
||||
np.array([ [r0, ..., rN], [g0, ..., gN], [b0, ..., bN]])
|
||||
Each value brightness value is an integer between 0 and 255.
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
raise NotImplementedError('Show() was not implemented')
|
||||
|
||||
def test(self, n_pixels):
|
||||
pixels = np.zeros((3, n_pixels))
|
||||
pixels[0][0] = 255
|
||||
pixels[1][1] = 255
|
||||
pixels[2][2] = 255
|
||||
print('Starting LED strip test.')
|
||||
print('Press CTRL+C to stop the test at any time.')
|
||||
print('You should see a scrolling red, green, and blue pixel.')
|
||||
while True:
|
||||
self.show(pixels)
|
||||
pixels = np.roll(pixels, 1, axis=1)
|
||||
time.sleep(0.2)
|
||||
|
||||
|
||||
class ESP8266(LEDController):
|
||||
def __init__(self, auto_detect=False,
|
||||
mac_addr="aa-bb-cc-dd-ee-ff",
|
||||
ip='192.168.0.150',
|
||||
port=7778):
|
||||
"""Initialize object for communicating with as ESP8266
|
||||
Parameters
|
||||
----------
|
||||
auto_detect: bool, optional
|
||||
Automatically search for and find devices on windows hotspot
|
||||
with given mac addresses. Windows hotspot resets the IP
|
||||
addresses of any devices on reset, meaning the IP of the
|
||||
ESP8266 changes every time you turn on the hotspot. This
|
||||
will find the IP address of the devices for you.
|
||||
mac_addr: str, optional
|
||||
The MAC address of the ESP8266 on the network. Only used if
|
||||
auto-detect is used
|
||||
ip: str, optional
|
||||
The IP address of the ESP8266 on the network. This must exactly
|
||||
match the IP address of your ESP8266 device, unless using
|
||||
the auto-detect feature.
|
||||
port: int, optional
|
||||
The port number to use when sending data to the ESP8266. This
|
||||
must exactly match the port number in the ESP8266's firmware.
|
||||
"""
|
||||
import socket
|
||||
self._mac_addr = mac_addr
|
||||
self._ip = ip
|
||||
self._port = port
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
if auto_detect:
|
||||
self.detect()
|
||||
|
||||
def detect(self):
|
||||
from subprocess import check_output
|
||||
from time import sleep
|
||||
""" Uses "arp -a" to find esp8266 on windows hotspot"""
|
||||
# Find the audio strip automagically
|
||||
ip_addr = False
|
||||
while not ip_addr:
|
||||
arp_out = check_output(['arp', '-a']).splitlines()
|
||||
for i in arp_out:
|
||||
if self._mac_addr in str(i):
|
||||
ip_addr = i.split()[0].decode("utf-8")
|
||||
break
|
||||
else:
|
||||
print("Device not found at physical address {}, retrying in 1s".format(self._mac_addr))
|
||||
sleep(1)
|
||||
print("Found device {}, with IP address {}".format(self._mac_addr, ip_addr))
|
||||
self._ip = ip_addr
|
||||
|
||||
def show(self, pixels):
|
||||
"""Sends UDP packets to ESP8266 to update LED strip values
|
||||
The ESP8266 will receive and decode the packets to determine what values
|
||||
to display on the LED strip. The communication protocol supports LED strips
|
||||
with a maximum of 256 LEDs.
|
||||
The packet encoding scheme is:
|
||||
|i|r|g|b|
|
||||
where
|
||||
i (0 to 255): Index of LED to change (zero-based)
|
||||
r (0 to 255): Red value of LED
|
||||
g (0 to 255): Green value of LED
|
||||
b (0 to 255): Blue value of LED
|
||||
"""
|
||||
message = pixels.T.clip(0, config.settings["configuration"]["MAX_BRIGHTNESS"]).astype(np.uint8).ravel().tostring()
|
||||
self._sock.sendto(message, (self._ip, self._port))
|
||||
|
||||
|
||||
class FadeCandy(LEDController):
|
||||
def __init__(self, server='localhost:7890'):
|
||||
"""Initializes object for communicating with a FadeCandy device
|
||||
Parameters
|
||||
----------
|
||||
server: str, optional
|
||||
FadeCandy server used to communicate with the FadeCandy device.
|
||||
"""
|
||||
try:
|
||||
import audioled.opc
|
||||
except ImportError as e:
|
||||
print('Unable to import audioled.opc library')
|
||||
print('You can install this library with `pip install opc`')
|
||||
raise e
|
||||
self.client = audioled.opc.Client(server)
|
||||
if self.client.can_connect():
|
||||
print('Successfully connected to FadeCandy server.')
|
||||
else:
|
||||
print('Could not connect to FadeCandy server.')
|
||||
print('Ensure that fcserver is running and try again.')
|
||||
|
||||
def show(self, pixels):
|
||||
self.client.put_pixels(pixels.T.clip(0, 255).astype(int).tolist())
|
||||
|
||||
|
||||
class BlinkStick(LEDController):
|
||||
def __init__(self):
|
||||
"""Initializes a BlinkStick controller"""
|
||||
try:
|
||||
from blinkstick import blinkstick
|
||||
except ImportError as e:
|
||||
print('Unable to import the blinkstick library')
|
||||
print('You can install this library with `pip install blinkstick`')
|
||||
raise e
|
||||
self.stick = blinkstick.find_first()
|
||||
|
||||
def show(self, pixels):
|
||||
"""Writes new LED values to the Blinkstick.
|
||||
This function updates the LED strip with new values.
|
||||
"""
|
||||
# Truncate values and cast to integer
|
||||
n_pixels = pixels.shape[1]
|
||||
pixels = pixels.clip(0, 255).astype(int)
|
||||
pixels = _GAMMA_TABLE[pixels]
|
||||
# Read the rgb values
|
||||
r = pixels[0][:].astype(int)
|
||||
g = pixels[1][:].astype(int)
|
||||
b = pixels[2][:].astype(int)
|
||||
|
||||
# Create array in which we will store the led states
|
||||
newstrip = [None] * (n_pixels * 3)
|
||||
|
||||
for i in range(n_pixels):
|
||||
# Blinkstick uses GRB format
|
||||
newstrip[i * 3] = g[i]
|
||||
newstrip[i * 3 + 1] = r[i]
|
||||
newstrip[i * 3 + 2] = b[i]
|
||||
# Send the data to the blinkstick
|
||||
self.stick.set_led_data(0, newstrip)
|
||||
|
||||
|
||||
class RaspberryPi(LEDController):
|
||||
def __init__(self, n_pixels, pin=18, invert_logic=False,
|
||||
freq=800000, dma=5):
|
||||
"""Creates a Raspberry Pi output device
|
||||
Parameters
|
||||
----------
|
||||
n_pixels: int
|
||||
Number of LED strip pixels
|
||||
pin: int, optional
|
||||
GPIO pin used to drive the LED strip (must be a PWM pin).
|
||||
Pin 18 can be used on the Raspberry Pi 2.
|
||||
invert_logic: bool, optional
|
||||
Whether or not to invert the driving logic.
|
||||
Set this to True if you are using an inverting logic level
|
||||
converter, otherwise set to False.
|
||||
freq: int, optional
|
||||
LED strip protocol frequency (Hz). For ws2812 this is 800000.
|
||||
dma: int, optional
|
||||
DMA (direct memory access) channel used to drive PWM signals.
|
||||
If you aren't sure, try 5.
|
||||
"""
|
||||
try:
|
||||
import neopixel
|
||||
except ImportError as e:
|
||||
url = 'learn.adafruit.com/neopixels-on-raspberry-pi/software'
|
||||
print('Could not import the neopixel library')
|
||||
print('For installation instructions, see {}'.format(url))
|
||||
raise e
|
||||
self.strip = neopixel.Adafruit_NeoPixel(n_pixels, pin, freq, dma,
|
||||
invert_logic, 255)
|
||||
self.strip.begin()
|
||||
|
||||
def show(self, pixels):
|
||||
"""Writes new LED values to the Raspberry Pi's LED strip
|
||||
Raspberry Pi uses the rpi_ws281x to control the LED strip directly.
|
||||
This function updates the LED strip with new values.
|
||||
"""
|
||||
# Truncate values and cast to integer
|
||||
n_pixels = pixels.shape[1]
|
||||
pixels = pixels.clip(0, 255).astype(int)
|
||||
# Optional gamma correction
|
||||
pixels = _GAMMA_TABLE[pixels]
|
||||
# Encode 24-bit LED values in 32 bit integers
|
||||
r = np.left_shift(pixels[0][:].astype(int), 8)
|
||||
g = np.left_shift(pixels[1][:].astype(int), 16)
|
||||
b = pixels[2][:].astype(int)
|
||||
rgb = np.bitwise_or(np.bitwise_or(r, g), b)
|
||||
# Update the pixels
|
||||
for i in range(n_pixels):
|
||||
self.strip._led_data[i] = rgb[i]
|
||||
self.strip.show()
|
||||
|
||||
|
||||
class DotStar(LEDController):
|
||||
def __init__(self, pixels, brightness=31):
|
||||
"""Creates an APA102-based output device
|
||||
Parameters
|
||||
----------
|
||||
pixels: int
|
||||
Number of LED strip pixels
|
||||
brightness: int, optional
|
||||
Global brightness
|
||||
"""
|
||||
try:
|
||||
import apa102
|
||||
except ImportError as e:
|
||||
url = 'https://github.com/tinue/APA102_Pi'
|
||||
print('Could not import the apa102 library')
|
||||
print('For installation instructions, see {}'.format(url))
|
||||
raise e
|
||||
self.strip = apa102.APA102(numLEDs=pixels, globalBrightness=brightness) # Initialize the strip
|
||||
led_data = np.array(self.strip.leds, dtype=np.uint8)
|
||||
# memoryview preserving the first 8 bits of LED frames (w/ global brightness)
|
||||
self.strip.leds = led_data.data
|
||||
# 2D view of led_data
|
||||
self.led_data = led_data.reshape((pixels, 4)) # or (-1, 4)
|
||||
|
||||
def show(self, pixels):
|
||||
bgr = [2,1,0]
|
||||
self.led_data[0:,1:4] = pixels[bgr].T.clip(0,255)
|
||||
self.strip.show()
|
@ -1,7 +1,7 @@
|
||||
from __future__ import print_function
|
||||
import numpy as np
|
||||
import config
|
||||
import melbank
|
||||
import lib.config as config
|
||||
import lib.melbank as melbank
|
||||
|
||||
|
||||
class ExpFilter:
|
||||
@ -24,30 +24,3 @@ class ExpFilter:
|
||||
self.value = alpha * value + (1.0 - alpha) * self.value
|
||||
return self.value
|
||||
|
||||
|
||||
def rfft(data, window=None):
|
||||
window = 1.0 if window is None else window(len(data))
|
||||
ys = np.abs(np.fft.rfft(data * window))
|
||||
xs = np.fft.rfftfreq(len(data), 1.0 / config.MIC_RATE)
|
||||
return xs, ys
|
||||
|
||||
|
||||
def fft(data, window=None):
|
||||
window = 1.0 if window is None else window(len(data))
|
||||
ys = np.fft.fft(data * window)
|
||||
xs = np.fft.fftfreq(len(data), 1.0 / config.MIC_RATE)
|
||||
return xs, ys
|
||||
|
||||
|
||||
def create_mel_bank():
|
||||
global samples, mel_y, mel_x
|
||||
samples = int(config.MIC_RATE * config.N_ROLLING_HISTORY / (2.0 * config.FPS))
|
||||
mel_y, (_, mel_x) = melbank.compute_melmat(num_mel_bands=config.N_FFT_BINS,
|
||||
freq_min=config.MIN_FREQUENCY,
|
||||
freq_max=config.MAX_FREQUENCY,
|
||||
num_fft_bands=samples,
|
||||
sample_rate=config.MIC_RATE)
|
||||
samples = None
|
||||
mel_y = None
|
||||
mel_x = None
|
||||
create_mel_bank()
|
||||
|
BIN
python/lib/gamma_table.npy
Normal file
BIN
python/lib/gamma_table.npy
Normal file
Binary file not shown.
@ -1,51 +1,276 @@
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
import time
|
||||
#from __future__ import print_function
|
||||
#from __future__ import division
|
||||
#from scipy.ndimage.filters import gaussian_filter1d
|
||||
#from collections import deque
|
||||
#import time
|
||||
#import sys
|
||||
import numpy as np
|
||||
from pyqtgraph.Qt import QtGui
|
||||
import lib.config as config
|
||||
#import microphone
|
||||
#import dsp
|
||||
#import led
|
||||
#import random
|
||||
|
||||
from lib.qrangeslider import QRangeSlider
|
||||
from lib.qfloatslider import QFloatSlider
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.dockarea import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
class GUI(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.settings = QSettings('settings.ini', QSettings.IniFormat)
|
||||
self.settings.setFallbacksEnabled(False) # File only, no fallback to registry or or.
|
||||
self.initUI()
|
||||
|
||||
def hideGraphs(self):
|
||||
print("Blah")
|
||||
|
||||
def hideOpts(self):
|
||||
print("Bleh")
|
||||
|
||||
def config.settings["configuration"]["configDialogue"](self):
|
||||
self.d = QDialog(None, Qt.WindowSystemMenuHint | Qt.WindowCloseButtonHint)
|
||||
b1 = QPushButton("ok",self.d)
|
||||
b1.move(50,50)
|
||||
self.d.setWindowTitle("Dialog")
|
||||
self.d.setWindowModality(Qt.ApplicationModal)
|
||||
self.d.show()
|
||||
|
||||
def initUI(self):
|
||||
# ==================================== Set up window and wrapping layout
|
||||
self.setWindowTitle("Visualization")
|
||||
wrapper = QVBoxLayout()
|
||||
|
||||
# ======================================================= Set up toolbar
|
||||
#toolbar_hideGraphs.setShortcut('Ctrl+H')
|
||||
toolbar_hideGraphs = QAction('GUI Properties', self)
|
||||
toolbar_hideGraphs.triggered.connect(self.config.settings["configuration"]["configDialogue"])
|
||||
toolbar_hideOpts = QAction('Hide Opts', self)
|
||||
toolbar_hideOpts.triggered.connect(self.hideOpts)
|
||||
|
||||
self.toolbar = self.addToolBar('Toolbar')
|
||||
self.toolbar.addAction(toolbar_hideGraphs)
|
||||
self.toolbar.addAction(toolbar_hideOpts)
|
||||
|
||||
# ========================================== Set up FPS and error labels
|
||||
labels_layout = QHBoxLayout()
|
||||
self.label_error = QLabel("")
|
||||
self.label_fps = QLabel("")
|
||||
self.label_latency = QLabel("")
|
||||
self.label_fps.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
self.label_latency.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
labels_layout.addWidget(self.label_error)
|
||||
labels_layout.addStretch()
|
||||
labels_layout.addWidget(self.label_latency)
|
||||
labels_layout.addWidget(self.label_fps)
|
||||
|
||||
# ================================================== Set up graph layout
|
||||
graph_view = pg.GraphicsView()
|
||||
graph_layout = pg.GraphicsLayout(border=(100,100,100))
|
||||
graph_view.setCentralItem(graph_layout)
|
||||
# Mel filterbank plot
|
||||
fft_plot = graph_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.settings["configuration"]["N_FFT_BINS"] + 1))
|
||||
self.mel_curve = pg.PlotCurveItem()
|
||||
self.mel_curve.setData(x=x_data, y=x_data*0)
|
||||
fft_plot.addItem(self.mel_curve)
|
||||
# Visualization plot
|
||||
graph_layout.nextRow()
|
||||
led_plot = graph_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
|
||||
self.r_curve = pg.PlotCurveItem(pen=r_pen)
|
||||
self.g_curve = pg.PlotCurveItem(pen=g_pen)
|
||||
self.b_curve = pg.PlotCurveItem(pen=b_pen)
|
||||
# Define x data
|
||||
x_data = np.array(range(1, config.settings["configuration"]["N_PIXELS"] + 1))
|
||||
self.r_curve.setData(x=x_data, y=x_data*0)
|
||||
self.g_curve.setData(x=x_data, y=x_data*0)
|
||||
self.b_curve.setData(x=x_data, y=x_data*0)
|
||||
# Add curves to plot
|
||||
led_plot.addItem(self.r_curve)
|
||||
led_plot.addItem(self.g_curve)
|
||||
led_plot.addItem(self.b_curve)
|
||||
|
||||
# ================================================= Set up button layout
|
||||
label_reactive = QLabel("Audio Reactive Effects")
|
||||
label_non_reactive = QLabel("Non Reactive Effects")
|
||||
reactive_button_grid = QGridLayout()
|
||||
non_reactive_button_grid = QGridLayout()
|
||||
buttons = {}
|
||||
connecting_funcs = {}
|
||||
grid_width = 4
|
||||
i = 0
|
||||
j = 0
|
||||
k = 0
|
||||
l = 0
|
||||
# Dynamically layout reactive_buttons and connect them to the visualisation effects
|
||||
def connect_generator(effect):
|
||||
def func():
|
||||
visualizer.current_effect = effect
|
||||
buttons[effect].setDown(True)
|
||||
func.__name__ = effect
|
||||
return func
|
||||
# Where the magic happens
|
||||
for effect in visualizer.effects:
|
||||
if not effect in visualizer.non_reactive_effects:
|
||||
connecting_funcs[effect] = connect_generator(effect)
|
||||
buttons[effect] = QPushButton(effect)
|
||||
buttons[effect].clicked.connect(connecting_funcs[effect])
|
||||
reactive_button_grid.addWidget(buttons[effect], j, i)
|
||||
i += 1
|
||||
if i % grid_width == 0:
|
||||
i = 0
|
||||
j += 1
|
||||
else:
|
||||
connecting_funcs[effect] = connect_generator(effect)
|
||||
buttons[effect] = QPushButton(effect)
|
||||
buttons[effect].clicked.connect(connecting_funcs[effect])
|
||||
non_reactive_button_grid.addWidget(buttons[effect], l, k)
|
||||
k += 1
|
||||
if k % grid_width == 0:
|
||||
k = 0
|
||||
l += 1
|
||||
|
||||
# ============================================== Set up frequency slider
|
||||
# Frequency range label
|
||||
label_slider = QLabel("Frequency Range")
|
||||
# Frequency slider
|
||||
def freq_slider_change(tick):
|
||||
minf = freq_slider.tickValue(0)**2.0 * (config.settings["configuration"]["MIC_RATE"] / 2.0)
|
||||
maxf = freq_slider.tickValue(1)**2.0 * (config.settings["configuration"]["MIC_RATE"] / 2.0)
|
||||
t = 'Frequency range: {:.0f} - {:.0f} Hz'.format(minf, maxf)
|
||||
freq_label.setText(t)
|
||||
config.settings["configuration"]["MIN_FREQUENCY"] = minf
|
||||
config.settings["configuration"]["MAX_FREQUENCY"] = maxf
|
||||
dsp.create_mel_bank()
|
||||
def set_freq_min():
|
||||
config.settings["configuration"]["MIN_FREQUENCY"] = freq_slider.start()
|
||||
dsp.create_mel_bank()
|
||||
def set_freq_max():
|
||||
config.settings["configuration"]["MAX_FREQUENCY"] = freq_slider.end()
|
||||
dsp.create_mel_bank()
|
||||
freq_slider = QRangeSlider()
|
||||
freq_slider.show()
|
||||
freq_slider.setMin(0)
|
||||
freq_slider.setMax(20000)
|
||||
freq_slider.setRange(config.settings["configuration"]["MIN_FREQUENCY"], config.settings["configuration"]["MAX_FREQUENCY"])
|
||||
freq_slider.setBackgroundStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #222, stop:1 #333);')
|
||||
freq_slider.setSpanStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #282, stop:1 #393);')
|
||||
freq_slider.setDrawValues(True)
|
||||
freq_slider.endValueChanged.connect(set_freq_max)
|
||||
freq_slider.startValueChanged.connect(set_freq_min)
|
||||
freq_slider.setStyleSheet("""
|
||||
QRangeSlider * {
|
||||
border: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
QRangeSlider > QSplitter::handle {
|
||||
background: #fff;
|
||||
}
|
||||
QRangeSlider > QSplitter::handle:vertical {
|
||||
height: 3px;
|
||||
}
|
||||
QRangeSlider > QSplitter::handle:pressed {
|
||||
background: #ca5;
|
||||
}
|
||||
""")
|
||||
|
||||
# ============================================ Set up option tabs layout
|
||||
label_options = QLabel("Effect Options")
|
||||
opts_tabs = QTabWidget()
|
||||
# Dynamically set up tabs
|
||||
tabs = {}
|
||||
grid_layouts = {}
|
||||
self.grid_layout_widgets = {}
|
||||
options = visualizer.effect_opts.keys()
|
||||
for effect in visualizer.effects:
|
||||
# Make the tab
|
||||
self.grid_layout_widgets[effect] = {}
|
||||
tabs[effect] = QWidget()
|
||||
grid_layouts[effect] = QGridLayout()
|
||||
tabs[effect].setLayout(grid_layouts[effect])
|
||||
opts_tabs.addTab(tabs[effect],effect)
|
||||
# These functions make functions for the dynamic ui generation
|
||||
# YOU WANT-A DYNAMIC I GIVE-A YOU DYNAMIC!
|
||||
def gen_slider_valuechanger(effect, key):
|
||||
def func():
|
||||
visualizer.effect_opts[effect][key] = self.grid_layout_widgets[effect][key].value()
|
||||
return func
|
||||
def gen_float_slider_valuechanger(effect, key):
|
||||
def func():
|
||||
visualizer.effect_opts[effect][key] = self.grid_layout_widgets[effect][key].slider_value
|
||||
return func
|
||||
def gen_combobox_valuechanger(effect, key):
|
||||
def func():
|
||||
visualizer.effect_opts[effect][key] = self.grid_layout_widgets[effect][key].currentText()
|
||||
return func
|
||||
def gen_checkbox_valuechanger(effect, key):
|
||||
def func():
|
||||
visualizer.effect_opts[effect][key] = self.grid_layout_widgets[effect][key].isChecked()
|
||||
return func
|
||||
# Dynamically generate ui for settings
|
||||
if effect in visualizer.dynamic_effects_config.settings["configuration"]["config:"]
|
||||
i = 0
|
||||
connecting_funcs[effect] = {}
|
||||
for key, label, ui_element, *opts in visualizer.dynamic_effects_config.settings["configuration"]["config[effect"]]:
|
||||
if opts: # neatest way ^^^^^ i could think of to unpack and handle an unknown number of opts (if any)
|
||||
opts = opts[0]
|
||||
if ui_element == "slider":
|
||||
connecting_funcs[effect][key] = gen_slider_valuechanger(effect, key)
|
||||
self.grid_layout_widgets[effect][key] = QSlider(Qt.Horizontal)
|
||||
self.grid_layout_widgets[effect][key].setMinimum(opts[0])
|
||||
self.grid_layout_widgets[effect][key].setMaximum(opts[1])
|
||||
self.grid_layout_widgets[effect][key].setValue(visualizer.effect_opts[effect][key])
|
||||
self.grid_layout_widgets[effect][key].valueChanged.connect(
|
||||
connecting_funcs[effect][key])
|
||||
elif ui_element == "float_slider":
|
||||
connecting_funcs[effect][key] = gen_float_slider_valuechanger(effect, key)
|
||||
self.grid_layout_widgets[effect][key] = QFloatSlider(*opts, visualizer.effect_opts[effect][key])
|
||||
self.grid_layout_widgets[effect][key].setValue(visualizer.effect_opts[effect][key])
|
||||
self.grid_layout_widgets[effect][key].valueChanged.connect(
|
||||
connecting_funcs[effect][key])
|
||||
elif ui_element == "dropdown":
|
||||
connecting_funcs[effect][key] = gen_combobox_valuechanger(effect, key)
|
||||
self.grid_layout_widgets[effect][key] = QComboBox()
|
||||
self.grid_layout_widgets[effect][key].addItems(opts)
|
||||
self.grid_layout_widgets[effect][key].currentIndexChanged.connect(
|
||||
connecting_funcs[effect][key])
|
||||
elif ui_element == "checkbox":
|
||||
connecting_funcs[effect][key] = gen_checkbox_valuechanger(effect, key)
|
||||
self.grid_layout_widgets[effect][key] = QCheckBox()
|
||||
self.grid_layout_widgets[effect][key].setCheckState(visualizer.effect_opts[effect][key])
|
||||
self.grid_layout_widgets[effect][key].stateChanged.connect(
|
||||
connecting_funcs[effect][key])
|
||||
grid_layouts[effect].addWidget(QLabel(label),i,0)
|
||||
grid_layouts[effect].addWidget(self.grid_layout_widgets[effect][key],i,1)
|
||||
i += 1
|
||||
#visualizer.effect_settings[effect]
|
||||
else:
|
||||
grid_layouts[effect].addWidget(QLabel("No customisable options for this effect :("),0,0)
|
||||
|
||||
|
||||
class GUI:
|
||||
plot = []
|
||||
curve = []
|
||||
|
||||
def __init__(self, width=800, height=450, title=''):
|
||||
# Create GUI window
|
||||
self.app = QtGui.QApplication([])
|
||||
self.win = pg.GraphicsWindow(title)
|
||||
self.win.resize(width, height)
|
||||
self.win.setWindowTitle(title)
|
||||
# Create GUI layout
|
||||
self.layout = QtGui.QVBoxLayout()
|
||||
self.win.setLayout(self.layout)
|
||||
|
||||
def add_plot(self, title):
|
||||
new_plot = pg.PlotWidget()
|
||||
self.layout.addWidget(new_plot)
|
||||
self.plot.append(new_plot)
|
||||
self.curve.append([])
|
||||
|
||||
def add_curve(self, plot_index, pen=(255, 255, 255)):
|
||||
self.curve[plot_index].append(self.plot[plot_index].plot(pen=pen))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Example test gui
|
||||
N = 48
|
||||
gui = GUI(title='Test')
|
||||
# Sin plot
|
||||
gui.add_plot(title='Sin Plot')
|
||||
gui.add_curve(plot_index=0)
|
||||
gui.win.nextRow()
|
||||
# Cos plot
|
||||
gui.add_plot(title='Cos Plot')
|
||||
gui.add_curve(plot_index=1)
|
||||
while True:
|
||||
t = time.time()
|
||||
x = np.linspace(t, 2 * np.pi + t, N)
|
||||
gui.curve[0][0].setData(x=x, y=np.sin(x))
|
||||
gui.curve[1][0].setData(x=x, y=np.cos(x))
|
||||
gui.app.processEvents()
|
||||
time.sleep(1.0 / 30.0)
|
||||
# ============================================= Add layouts into wrapper
|
||||
self.setCentralWidget(QWidget(self))
|
||||
self.centralWidget().setLayout(wrapper)
|
||||
wrapper.addLayout(labels_layout)
|
||||
wrapper.addWidget(graph_view)
|
||||
wrapper.addWidget(label_reactive)
|
||||
wrapper.addLayout(reactive_button_grid)
|
||||
wrapper.addWidget(label_non_reactive)
|
||||
wrapper.addLayout(non_reactive_button_grid)
|
||||
wrapper.addWidget(label_slider)
|
||||
wrapper.addWidget(freq_slider)
|
||||
wrapper.addWidget(label_options)
|
||||
wrapper.addWidget(opts_tabs)
|
||||
#self.show()
|
||||
|
@ -4,45 +4,45 @@ from __future__ import division
|
||||
|
||||
import platform
|
||||
import numpy as np
|
||||
import config
|
||||
import lib.config as config
|
||||
|
||||
def detect_esp8266():
|
||||
""" Uses "arp -a" to find esp8266 on windows hotspot"""
|
||||
# Find the audio strip automagically
|
||||
ip_addr = False
|
||||
while not ip_addr:
|
||||
arp_out = check_output(['arp', '-a']).splitlines()
|
||||
for i in arp_out:
|
||||
if config.settings["configuration"]["MAC_ADDR"] in str(i):
|
||||
ip_addr = i.split()[0].decode("utf-8")
|
||||
break
|
||||
else:
|
||||
print("Device not found at physical address {}, retrying in 1s".format(config.settings["configuration"]["MAC_ADDR"]))
|
||||
sleep(1)
|
||||
print("Found device {}, with IP address {}".format(config.settings["configuration"]["MAC_ADDR"], ip_addr))
|
||||
config.settings["configuration"]["UDP_IP"] = ip_addr
|
||||
|
||||
# ESP8266 uses WiFi communication
|
||||
if config.DEVICE == 'esp8266':
|
||||
if config.settings["configuration"]["DEVICE"] == 'esp8266':
|
||||
import socket
|
||||
from subprocess import check_output
|
||||
from time import sleep
|
||||
|
||||
# Find the audio strip automagically
|
||||
if config.AUTO_DETECT:
|
||||
ip_addr = False
|
||||
while not ip_addr:
|
||||
arp_out = check_output(['arp', '-a']).splitlines()
|
||||
for i in arp_out:
|
||||
if config.MAC_ADDR in str(i):
|
||||
ip_addr = i.split()[0].decode("utf-8")
|
||||
break
|
||||
else:
|
||||
print("Device not found at physical address {}, retrying in 1s".format(config.MAC_ADDR))
|
||||
sleep(1)
|
||||
print("Found device {}, with IP address {}".format(config.MAC_ADDR, ip_addr))
|
||||
config.UDP_IP = ip_addr
|
||||
|
||||
_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
_sock.settimeout(0.005)
|
||||
# Raspberry Pi controls the LED strip directly
|
||||
elif config.DEVICE == 'pi':
|
||||
elif config.settings["configuration"]["DEVICE"] == 'pi':
|
||||
import neopixel
|
||||
strip = neopixel.Adafruit_NeoPixel(config.N_PIXELS, config.LED_PIN,
|
||||
config.LED_FREQ_HZ, config.LED_DMA,
|
||||
config.LED_INVERT, config.BRIGHTNESS)
|
||||
strip = neopixel.Adafruit_NeoPixel(config.settings["configuration"]["N_PIXELS"], config.settings["configuration"]["LED_PIN"],
|
||||
config.settings["configuration"]["LED_FREQ_HZ"], config.settings["configuration"]["LED_DMA"],
|
||||
config.settings["configuration"]["LED_INVERT"], config.settings["configuration"]["BRIGHTNESS"])
|
||||
strip.begin()
|
||||
elif config.DEVICE == 'blinkstick':
|
||||
elif config.settings["configuration"]["DEVICE"] == 'blinkstick':
|
||||
from blinkstick import blinkstick
|
||||
import signal
|
||||
import sys
|
||||
#Will turn all leds off when invoked.
|
||||
def signal_handler(signal, frame):
|
||||
all_off = [0]*(config.N_PIXELS*3)
|
||||
all_off = [0]*(config.settings["configuration"]["N_PIXELS"]*3)
|
||||
stick.set_led_data(0, all_off)
|
||||
sys.exit(0)
|
||||
|
||||
@ -51,13 +51,13 @@ elif config.DEVICE == 'blinkstick':
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
_gamma = np.load(config.GAMMA_TABLE_PATH)
|
||||
_gamma = np.load(config.settings["configuration"]["GAMMA_TABLE_PATH"])
|
||||
"""Gamma lookup table used for nonlinear brightness correction"""
|
||||
|
||||
_prev_pixels = np.tile(253, (3, config.N_PIXELS))
|
||||
_prev_pixels = np.tile(253, (3, config.settings["configuration"]["N_PIXELS"]))
|
||||
"""Pixel values that were most recently displayed on the LED strip"""
|
||||
|
||||
pixels = np.tile(1, (3, config.N_PIXELS))
|
||||
pixels = np.tile(1, (3, config.settings["configuration"]["N_PIXELS"]))
|
||||
"""Pixel values for the LED strip"""
|
||||
|
||||
_is_python_2 = int(platform.python_version_tuple()[0]) == 2
|
||||
@ -79,27 +79,37 @@ def _update_esp8266():
|
||||
"""
|
||||
global pixels, _prev_pixels
|
||||
# Truncate values and cast to integer
|
||||
pixels = np.clip(pixels, 0, 200).astype(int)
|
||||
# Optionally apply gamma correc tio
|
||||
p = _gamma[pixels] if config.SOFTWARE_GAMMA_CORRECTION else np.copy(pixels)
|
||||
MAX_PIXELS_PER_PACKET = 256
|
||||
pixels = np.clip(pixels, 0, config.settings["configuration"]["MAX_BRIGHTNESS"]).astype(int)
|
||||
# Optionally apply gamma correction
|
||||
p = _gamma[pixels] if config.settings["configuration"]["SOFTWARE_GAMMA_CORRECTION"] else np.copy(pixels)
|
||||
MAX_PIXELS_PER_PACKET = 255
|
||||
# Pixel indices
|
||||
idx = range(pixels.shape[1])
|
||||
#idx = [i for i in idx if not np.array_equal(p[:, i], _prev_pixels[:, i])]
|
||||
n_packets = len(idx) // MAX_PIXELS_PER_PACKET + 1
|
||||
idx = np.array_split(idx, n_packets)
|
||||
for packet_indices in idx:
|
||||
m = '' if _is_python_2 else []
|
||||
for i in packet_indices:
|
||||
if _is_python_2:
|
||||
m += chr(i) + chr(pixels[0][i]) + chr(pixels[1][i]) + chr(pixels[2][i])
|
||||
else:
|
||||
m.append(i) # Index of pixel to change
|
||||
m.append(pixels[0][i]) # Pixel red value
|
||||
m.append(pixels[1][i]) # Pixel green value
|
||||
m.append(pixels[2][i]) # Pixel blue value
|
||||
m = m if _is_python_2 else bytes(m)
|
||||
_sock.sendto(m, (config.UDP_IP, config.UDP_PORT))
|
||||
|
||||
m = []
|
||||
for i in range(config.settings["configuration"]["N_PIXELS"]):
|
||||
#m.append(i) # Index of pixel to change
|
||||
m.append(pixels[0][i]) # Pixel red value
|
||||
m.append(pixels[1][i]) # Pixel green value
|
||||
m.append(pixels[2][i]) # Pixel blue value
|
||||
m = bytes(m)
|
||||
_sock.sendto(m, (config.settings["configuration"]["UDP_IP"], config.settings["configuration"]["UDP_PORT"]))
|
||||
|
||||
# for packet_indices in idx:
|
||||
# m = '' if _is_python_2 else []
|
||||
# for i in packet_indices:
|
||||
# if _is_python_2:
|
||||
# m += chr(i) + chr(pixels[0][i]) + chr(pixels[1][i]) + chr(pixels[2][i])
|
||||
# else:
|
||||
# m.append(i) # Index of pixel to change
|
||||
# m.append(pixels[0][i]) # Pixel red value
|
||||
# m.append(pixels[1][i]) # Pixel green value
|
||||
# m.append(pixels[2][i]) # Pixel blue value
|
||||
# m = m if _is_python_2 else bytes(m)
|
||||
# _sock.sendto(m, (config.settings["configuration"]["UDP_IP"], config.settings["configuration"]["UDP_PORT"]))
|
||||
_prev_pixels = np.copy(pixels)
|
||||
|
||||
|
||||
@ -113,14 +123,14 @@ def _update_pi():
|
||||
# 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)
|
||||
p = _gamma[pixels] if config.settings["configuration"]["SOFTWARE_GAMMA_CORRECTION"] else np.copy(pixels)
|
||||
# Encode 24-bit LED values in 32 bit integers
|
||||
r = np.left_shift(p[0][:].astype(int), 8)
|
||||
g = np.left_shift(p[1][:].astype(int), 16)
|
||||
b = p[2][:].astype(int)
|
||||
rgb = np.bitwise_or(np.bitwise_or(r, g), b)
|
||||
# Update the pixels
|
||||
for i in range(config.N_PIXELS):
|
||||
for i in range(config.settings["configuration"]["N_PIXELS"]):
|
||||
# Ignore pixels if they haven't changed (saves bandwidth)
|
||||
if np.array_equal(p[:, i], _prev_pixels[:, i]):
|
||||
continue
|
||||
@ -137,16 +147,16 @@ def _update_blinkstick():
|
||||
# Truncate values and cast to integer
|
||||
pixels = np.clip(pixels, 0, 250).astype(int)
|
||||
# Optional gamma correction
|
||||
p = _gamma[pixels] if config.SOFTWARE_GAMMA_CORRECTION else np.copy(pixels)
|
||||
p = _gamma[pixels] if config.settings["configuration"]["SOFTWARE_GAMMA_CORRECTION"] else np.copy(pixels)
|
||||
# Read the rgb values
|
||||
r = p[0][:].astype(int)
|
||||
g = p[1][:].astype(int)
|
||||
b = p[2][:].astype(int)
|
||||
|
||||
#create array in which we will store the led states
|
||||
newstrip = [None]*(config.N_PIXELS*3)
|
||||
newstrip = [None]*(config.settings["configuration"]["N_PIXELS"]*3)
|
||||
|
||||
for i in range(config.N_PIXELS):
|
||||
for i in range(config.settings["configuration"]["N_PIXELS"]):
|
||||
# blinkstick uses GRB format
|
||||
newstrip[i*3] = g[i]
|
||||
newstrip[i*3+1] = r[i]
|
||||
@ -157,13 +167,13 @@ def _update_blinkstick():
|
||||
|
||||
def update():
|
||||
"""Updates the LED strip values"""
|
||||
if config.DEVICE == 'esp8266':
|
||||
if config.settings["configuration"]["DEVICE"] == 'esp8266':
|
||||
_update_esp8266()
|
||||
elif config.DEVICE == 'pi':
|
||||
elif config.settings["configuration"]["DEVICE"] == 'pi':
|
||||
_update_pi()
|
||||
elif config.DEVICE == 'blinkstick':
|
||||
elif config.settings["configuration"]["DEVICE"] == 'blinkstick':
|
||||
_update_blinkstick()
|
||||
elif config.DEVICE == 'stripless':
|
||||
elif config.settings["configuration"]["DEVICE"] == 'stripless':
|
||||
pass
|
||||
|
||||
# Execute this file to run a LED strand test
|
||||
|
@ -1,30 +1,29 @@
|
||||
import time
|
||||
import numpy as np
|
||||
import pyaudio
|
||||
import config
|
||||
import lib.config as config
|
||||
|
||||
|
||||
def start_stream(callback):
|
||||
p = pyaudio.PyAudio()
|
||||
frames_per_buffer = int(config.MIC_RATE / config.FPS)
|
||||
frames_per_buffer = int(config.settings["configuration"]["MIC_RATE"] / config.settings["configuration"]["FPS"])
|
||||
stream = p.open(format=pyaudio.paInt16,
|
||||
channels=1,
|
||||
rate=config.MIC_RATE,
|
||||
rate=config.settings["configuration"]["MIC_RATE"],
|
||||
input=True,
|
||||
frames_per_buffer=frames_per_buffer)
|
||||
overflows = 0
|
||||
prev_ovf_time = time.time()
|
||||
while True:
|
||||
try:
|
||||
y = np.fromstring(stream.read(frames_per_buffer, exception_on_overflow=False), dtype=np.int16)
|
||||
y = np.fromstring(stream.read(frames_per_buffer), dtype=np.int16)
|
||||
y = y.astype(np.float32)
|
||||
#stream.read(get_read_available(), exception_on_overflow=False)
|
||||
callback(y)
|
||||
except IOError:
|
||||
overflows += 1
|
||||
if time.time() > prev_ovf_time + 1:
|
||||
prev_ovf_time = time.time()
|
||||
if config.USE_GUI:
|
||||
if config.settings["configuration"]["USE_GUI"]:
|
||||
gui.label_error.setText('Audio buffer has overflowed {} times'.format(overflows))
|
||||
else:
|
||||
print('Audio buffer has overflowed {} times'.format(overflows))
|
||||
|
@ -15,7 +15,7 @@ class QFloatSlider(QtWidgets.QSlider):
|
||||
"""
|
||||
def __init__(self, min_value, max_value, step, default):
|
||||
super().__init__(QtCore.Qt.Horizontal)
|
||||
self.precision = 0.001
|
||||
self.precision = 0.0001
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value
|
||||
self.step = step
|
||||
@ -30,7 +30,10 @@ class QFloatSlider(QtWidgets.QSlider):
|
||||
super().setSingleStep(1)
|
||||
super().setValue(self._float_to_int(self.default))
|
||||
super().valueChanged.connect(self._value_handler)
|
||||
self.slider_value = 2.0
|
||||
#self.slider_value = 2.0
|
||||
|
||||
def setValue(self, value):
|
||||
super().setValue(self._float_to_int(value))
|
||||
|
||||
# This is mostly disgusting python i hate floating points >:(
|
||||
def _float_divmod(self,a,b):
|
||||
|
Loading…
Reference in New Issue
Block a user