Lots of little updates behind the scenes

This commit is contained in:
not-matt 2018-02-27 20:12:08 +00:00 committed by GitHub
parent 35358e2d75
commit cd06d65e60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 901 additions and 205 deletions

View File

@ -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 print_function
from __future__ import division from __future__ import division
import os import os
DEVICE = 'stripless' use_defaults = {"configuration": True, # See notes below for detailed explanation
"""Device used to control LED strip. Must be 'pi', 'esp8266' or 'blinkstick' "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 '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 'pi' means that you are using a Raspberry Pi as a standalone unit to process
audio input and control the LED strip directly. 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 'blinkstick' means that a BlinkstickPro is connected to this PC which will be used
to control the leds connected to it. 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. 'stripless' means that the program will run without sending data to a strip.
Useful for development etc, but doesn't look half as good ;) Useful for development etc, but doesn't look half as good ;)
"""
if DEVICE == 'esp8266': [REQUIRED CONFIGURATION KEYS]
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"""
elif DEVICE == 'pi': ===== 'esp8266'
LED_PIN = 18 "AUTO_DETECT" # Set this true if you're using windows hotspot to connect (see below for more info)
"""GPIO pin connected to the LED strip pixels (must support PWM)""" "MAC_ADDR" # MAC address of the ESP8266. Only used if AUTO_DETECT is True
LED_FREQ_HZ = 800000 "UDP_IP" # IP address of the ESP8266. Must match IP in ws2812_controller.ino
"""LED signal frequency in Hz (usually 800kHz)""" "UDP_PORT" # Port number used for socket communication between Python and ESP8266
LED_DMA = 5 ===== 'pi'
"""DMA channel used for generating PWM signal (try 5)""" "LED_PIN" # GPIO pin connected to the LED strip pixels (must support PWM)
BRIGHTNESS = 255 "LED_FREQ_HZ" # LED signal frequency in Hz (usually 800kHz)
"""Brightness of LED strip between 0 and 255""" "LED_DMA" # DMA channel used for generating PWM signal (try 5)
LED_INVERT = True "BRIGHTNESS" # Brightness of LED strip between 0 and 255
"""Set True if using an inverting logic level converter""" "LED_INVERT" # Set True if using an inverting logic level converter
SOFTWARE_GAMMA_CORRECTION = True ===== 'blinkstick'
"""Set to True because Raspberry Pi doesn't use hardware dithering""" 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': [AUTO_DETECT]
SOFTWARE_GAMMA_CORRECTION = True
"""Set to True because blinkstick doesn't use hardware dithering"""
elif DEVICE == 'stripless': Set to true if the ip address of the device changes. This is the case if it's connecting
pass 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: [FPS]
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 indicates the desired refresh rate, or frames-per-second, of the audio 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 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 The FPS should not exceed the maximum refresh rate of the LED strip, which
depends on how long the LED strip is. 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 [N_FFT_BINS]
"""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
Fast Fourier transforms are used to transform time-domain audio data to the Fast Fourier transforms are used to transform time-domain audio data to the
frequency domain. The frequencies present in the audio signal are assigned 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. There is no point using more bins than there are pixels on the LED strip.
""" """
N_ROLLING_HISTORY = 4 for board in settings["devices"]:
"""Number of past audio frames to include in the rolling window""" 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
View 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()

View File

@ -1,7 +1,7 @@
from __future__ import print_function from __future__ import print_function
import numpy as np import numpy as np
import config import lib.config as config
import melbank import lib.melbank as melbank
class ExpFilter: class ExpFilter:
@ -24,30 +24,3 @@ class ExpFilter:
self.value = alpha * value + (1.0 - alpha) * self.value self.value = alpha * value + (1.0 - alpha) * self.value
return 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

Binary file not shown.

View File

@ -1,51 +1,276 @@
from __future__ import print_function #from __future__ import print_function
from __future__ import division #from __future__ import division
import time #from scipy.ndimage.filters import gaussian_filter1d
#from collections import deque
#import time
#import sys
import numpy as np 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 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=''): # ============================================= Add layouts into wrapper
# Create GUI window self.setCentralWidget(QWidget(self))
self.app = QtGui.QApplication([]) self.centralWidget().setLayout(wrapper)
self.win = pg.GraphicsWindow(title) wrapper.addLayout(labels_layout)
self.win.resize(width, height) wrapper.addWidget(graph_view)
self.win.setWindowTitle(title) wrapper.addWidget(label_reactive)
# Create GUI layout wrapper.addLayout(reactive_button_grid)
self.layout = QtGui.QVBoxLayout() wrapper.addWidget(label_non_reactive)
self.win.setLayout(self.layout) wrapper.addLayout(non_reactive_button_grid)
wrapper.addWidget(label_slider)
def add_plot(self, title): wrapper.addWidget(freq_slider)
new_plot = pg.PlotWidget() wrapper.addWidget(label_options)
self.layout.addWidget(new_plot) wrapper.addWidget(opts_tabs)
self.plot.append(new_plot) #self.show()
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)

View File

@ -4,45 +4,45 @@ from __future__ import division
import platform import platform
import numpy as np import numpy as np
import config import lib.config as config
# ESP8266 uses WiFi communication
if config.DEVICE == 'esp8266':
import socket
from subprocess import check_output
from time import sleep
def detect_esp8266():
""" Uses "arp -a" to find esp8266 on windows hotspot"""
# Find the audio strip automagically # Find the audio strip automagically
if config.AUTO_DETECT:
ip_addr = False ip_addr = False
while not ip_addr: while not ip_addr:
arp_out = check_output(['arp', '-a']).splitlines() arp_out = check_output(['arp', '-a']).splitlines()
for i in arp_out: for i in arp_out:
if config.MAC_ADDR in str(i): if config.settings["configuration"]["MAC_ADDR"] in str(i):
ip_addr = i.split()[0].decode("utf-8") ip_addr = i.split()[0].decode("utf-8")
break break
else: else:
print("Device not found at physical address {}, retrying in 1s".format(config.MAC_ADDR)) print("Device not found at physical address {}, retrying in 1s".format(config.settings["configuration"]["MAC_ADDR"]))
sleep(1) sleep(1)
print("Found device {}, with IP address {}".format(config.MAC_ADDR, ip_addr)) print("Found device {}, with IP address {}".format(config.settings["configuration"]["MAC_ADDR"], ip_addr))
config.UDP_IP = ip_addr config.settings["configuration"]["UDP_IP"] = ip_addr
# ESP8266 uses WiFi communication
if config.settings["configuration"]["DEVICE"] == 'esp8266':
import socket
from subprocess import check_output
from time import sleep
_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) _sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
_sock.settimeout(0.005) _sock.settimeout(0.005)
# Raspberry Pi controls the LED strip directly # Raspberry Pi controls the LED strip directly
elif config.DEVICE == 'pi': elif config.settings["configuration"]["DEVICE"] == 'pi':
import neopixel import neopixel
strip = neopixel.Adafruit_NeoPixel(config.N_PIXELS, config.LED_PIN, strip = neopixel.Adafruit_NeoPixel(config.settings["configuration"]["N_PIXELS"], config.settings["configuration"]["LED_PIN"],
config.LED_FREQ_HZ, config.LED_DMA, config.settings["configuration"]["LED_FREQ_HZ"], config.settings["configuration"]["LED_DMA"],
config.LED_INVERT, config.BRIGHTNESS) config.settings["configuration"]["LED_INVERT"], config.settings["configuration"]["BRIGHTNESS"])
strip.begin() strip.begin()
elif config.DEVICE == 'blinkstick': elif config.settings["configuration"]["DEVICE"] == 'blinkstick':
from blinkstick import blinkstick from blinkstick import blinkstick
import signal import signal
import sys import sys
#Will turn all leds off when invoked. #Will turn all leds off when invoked.
def signal_handler(signal, frame): 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) stick.set_led_data(0, all_off)
sys.exit(0) sys.exit(0)
@ -51,13 +51,13 @@ elif config.DEVICE == 'blinkstick':
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, 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""" """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""" """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""" """Pixel values for the LED strip"""
_is_python_2 = int(platform.python_version_tuple()[0]) == 2 _is_python_2 = int(platform.python_version_tuple()[0]) == 2
@ -79,27 +79,37 @@ def _update_esp8266():
""" """
global pixels, _prev_pixels global pixels, _prev_pixels
# Truncate values and cast to integer # Truncate values and cast to integer
pixels = np.clip(pixels, 0, 200).astype(int) pixels = np.clip(pixels, 0, config.settings["configuration"]["MAX_BRIGHTNESS"]).astype(int)
# Optionally apply gamma correc tio # Optionally apply 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)
MAX_PIXELS_PER_PACKET = 256 MAX_PIXELS_PER_PACKET = 255
# Pixel indices # Pixel indices
idx = range(pixels.shape[1]) idx = range(pixels.shape[1])
#idx = [i for i in idx if not np.array_equal(p[:, i], _prev_pixels[:, i])] #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 n_packets = len(idx) // MAX_PIXELS_PER_PACKET + 1
idx = np.array_split(idx, n_packets) idx = np.array_split(idx, n_packets)
for packet_indices in idx:
m = '' if _is_python_2 else [] m = []
for i in packet_indices: for i in range(config.settings["configuration"]["N_PIXELS"]):
if _is_python_2: #m.append(i) # Index of pixel to change
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[0][i]) # Pixel red value
m.append(pixels[1][i]) # Pixel green value m.append(pixels[1][i]) # Pixel green value
m.append(pixels[2][i]) # Pixel blue value m.append(pixels[2][i]) # Pixel blue value
m = m if _is_python_2 else bytes(m) m = bytes(m)
_sock.sendto(m, (config.UDP_IP, config.UDP_PORT)) _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) _prev_pixels = np.copy(pixels)
@ -113,14 +123,14 @@ def _update_pi():
# Truncate values and cast to integer # Truncate values and cast to integer
pixels = np.clip(pixels, 0, 255).astype(int) pixels = np.clip(pixels, 0, 255).astype(int)
# Optional gamma correction # 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 # Encode 24-bit LED values in 32 bit integers
r = np.left_shift(p[0][:].astype(int), 8) r = np.left_shift(p[0][:].astype(int), 8)
g = np.left_shift(p[1][:].astype(int), 16) g = np.left_shift(p[1][:].astype(int), 16)
b = p[2][:].astype(int) b = p[2][:].astype(int)
rgb = np.bitwise_or(np.bitwise_or(r, g), b) rgb = np.bitwise_or(np.bitwise_or(r, g), b)
# Update the pixels # 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) # Ignore pixels if they haven't changed (saves bandwidth)
if np.array_equal(p[:, i], _prev_pixels[:, i]): if np.array_equal(p[:, i], _prev_pixels[:, i]):
continue continue
@ -137,16 +147,16 @@ def _update_blinkstick():
# Truncate values and cast to integer # Truncate values and cast to integer
pixels = np.clip(pixels, 0, 250).astype(int) pixels = np.clip(pixels, 0, 250).astype(int)
# Optional gamma correction # 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 # Read the rgb values
r = p[0][:].astype(int) r = p[0][:].astype(int)
g = p[1][:].astype(int) g = p[1][:].astype(int)
b = p[2][:].astype(int) b = p[2][:].astype(int)
#create array in which we will store the led states #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 # blinkstick uses GRB format
newstrip[i*3] = g[i] newstrip[i*3] = g[i]
newstrip[i*3+1] = r[i] newstrip[i*3+1] = r[i]
@ -157,13 +167,13 @@ def _update_blinkstick():
def update(): def update():
"""Updates the LED strip values""" """Updates the LED strip values"""
if config.DEVICE == 'esp8266': if config.settings["configuration"]["DEVICE"] == 'esp8266':
_update_esp8266() _update_esp8266()
elif config.DEVICE == 'pi': elif config.settings["configuration"]["DEVICE"] == 'pi':
_update_pi() _update_pi()
elif config.DEVICE == 'blinkstick': elif config.settings["configuration"]["DEVICE"] == 'blinkstick':
_update_blinkstick() _update_blinkstick()
elif config.DEVICE == 'stripless': elif config.settings["configuration"]["DEVICE"] == 'stripless':
pass pass
# Execute this file to run a LED strand test # Execute this file to run a LED strand test

View File

@ -1,30 +1,29 @@
import time import time
import numpy as np import numpy as np
import pyaudio import pyaudio
import config import lib.config as config
def start_stream(callback): def start_stream(callback):
p = pyaudio.PyAudio() 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, stream = p.open(format=pyaudio.paInt16,
channels=1, channels=1,
rate=config.MIC_RATE, rate=config.settings["configuration"]["MIC_RATE"],
input=True, input=True,
frames_per_buffer=frames_per_buffer) frames_per_buffer=frames_per_buffer)
overflows = 0 overflows = 0
prev_ovf_time = time.time() prev_ovf_time = time.time()
while True: while True:
try: 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) y = y.astype(np.float32)
#stream.read(get_read_available(), exception_on_overflow=False)
callback(y) callback(y)
except IOError: except IOError:
overflows += 1 overflows += 1
if time.time() > prev_ovf_time + 1: if time.time() > prev_ovf_time + 1:
prev_ovf_time = time.time() 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)) gui.label_error.setText('Audio buffer has overflowed {} times'.format(overflows))
else: else:
print('Audio buffer has overflowed {} times'.format(overflows)) print('Audio buffer has overflowed {} times'.format(overflows))

View File

@ -15,7 +15,7 @@ class QFloatSlider(QtWidgets.QSlider):
""" """
def __init__(self, min_value, max_value, step, default): def __init__(self, min_value, max_value, step, default):
super().__init__(QtCore.Qt.Horizontal) super().__init__(QtCore.Qt.Horizontal)
self.precision = 0.001 self.precision = 0.0001
self.min_value = min_value self.min_value = min_value
self.max_value = max_value self.max_value = max_value
self.step = step self.step = step
@ -30,7 +30,10 @@ class QFloatSlider(QtWidgets.QSlider):
super().setSingleStep(1) super().setSingleStep(1)
super().setValue(self._float_to_int(self.default)) super().setValue(self._float_to_int(self.default))
super().valueChanged.connect(self._value_handler) 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 >:( # This is mostly disgusting python i hate floating points >:(
def _float_divmod(self,a,b): def _float_divmod(self,a,b):