From 48cb1018e932a993449cd62760f2e031782a528a Mon Sep 17 00:00:00 2001 From: Matthew Bowley Date: Sun, 4 Mar 2018 20:21:34 +0000 Subject: [PATCH] Fixed effects and ws2812 controller fw Final push to this fork unless other critical errors are found. --- .../ws2812_controller/ws2812_controller.ino | 210 ++++++---- python/lib/config.py | 105 +++-- python/lib/settings.ini | 6 + python/main.py | 374 +++++++++++++----- 4 files changed, 492 insertions(+), 203 deletions(-) create mode 100644 python/lib/settings.ini diff --git a/arduino/ws2812_controller/ws2812_controller.ino b/arduino/ws2812_controller/ws2812_controller.ino index 19d7bf6..ecbe5af 100644 --- a/arduino/ws2812_controller/ws2812_controller.ino +++ b/arduino/ws2812_controller/ws2812_controller.ino @@ -1,79 +1,131 @@ -#include -#include -//#include -//#include -#include -#include - -// Set to the number of LEDs in your LED strip -#define NUM_LEDS 242 -// Maximum number of packets to hold in the buffer. Don't change this. -#define BUFFER_LEN 726 -// Toggles FPS output (1 = print FPS over serial, 0 = disable output) -#define PRINT_FPS 1 - -//NeoPixelBus settings -const uint8_t PixelPin = 3; // make sure to set this to the correct pin, ignored for Esp8266(set to 3 by default for DMA) - -// Wifi and socket settings -const char* ssid = "YOUR_WIFI_SSID"; -const char* password = "YOUR_WIFI_PASSWORD"; -unsigned int localPort = 7778; -byte packetBuffer[BUFFER_LEN]; -RgbColor ledDataBuffer[NUM_LEDS]; - -// LED strip -NeoPixelBus ledstrip(NUM_LEDS, PixelPin); - -WiFiUDP port; - -// Network information -// IP must match the IP in config.py -IPAddress ip(192, 168, 137, 200); -// Set gateway to your router's gateway -IPAddress gateway(192, 168, 137, 1); -IPAddress subnet(255, 255, 255, 0); - -void setup() { - Serial.begin(115200); - //WiFi.config(ip, gateway, subnet); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - Serial.println(""); - // Connect to wifi and print the IP address over serial - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println(""); - Serial.print("Connected to "); - Serial.println(ssid); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - port.begin(localPort); - ledstrip.Begin();//Begin output - ledstrip.Show();//Clear the strip for use -} - -uint8_t N = 0; -#if PRINT_FPS - uint16_t fpsCounter = 0; - uint32_t secondTimer = 0; -#endif - -void loop() { - // Read data over socket - int packetSize = port.parsePacket(); - // If packets have been received, interpret the command - if (packetSize) { - int len = port.read(packetBuffer, BUFFER_LEN); - for(int i = 0; i < len; i+=4) { - packetBuffer[len] = 0; - N = packetBuffer[i]; - RgbColor pixel((uint8_t)packetBuffer[i+1], (uint8_t)packetBuffer[i+2], (uint8_t)packetBuffer[i+3]); - ledstrip.SetPixelColor(N, pixel); - } - ledstrip.Show(); - Serial.print("/"); - } -} +#include +#include +#include +#include + +#define FASTLED_ESP8266_DMA // better control for ESP8266 will output or RX pin requires fork https://github.com/coryking/FastLED +#include "FastLED.h" + +/************ Network Information (CHANGE THESE FOR YOUR SETUP) ************************/ +const char* ssid = "WIFI_SSID"; +const char* password = "WIFI_PASSWORD"; + +const char* sensor_name = "TEST_SENSOR_HOSTNAME"; +const char* ota_password = "OTA_PASSWORD"; + +const bool static_ip = true; +IPAddress ip(192, 168, 1, 112); +IPAddress gateway(192, 168, 1, 1); +IPAddress subnet(255, 255, 255, 0); + +const int udp_port = 7778; + +/*********************************** FastLED Defintions ********************************/ +#define NUM_LEDS 250 +#define DATA_PIN 5 +//#define CLOCK_PIN 2 +#define CHIPSET WS2812B +#define COLOR_ORDER GRB + +/*********************************** Globals *******************************************/ +WiFiUDP port; +CRGB leds[NUM_LEDS]; + +/********************************** Start Setup ****************************************/ +void setup() { + Serial.begin(115200); + + // Setup FastLED + #ifdef CLOCK_PIN + FastLED.addLeds(leds, NUM_LEDS); + #else + FastLED.addLeds(leds, NUM_LEDS); + #endif + + // Setup the wifi connection + setup_wifi(); + + // Setup OTA firmware updates + setup_ota(); + + // Initialize the UDP port + port.begin(udp_port); +} + +void setup_wifi() { + delay(10); + + Serial.println(); + Serial.print("Connecting to "); + Serial.print(ssid); + + if (static_ip) { + WiFi.config(ip, gateway, subnet); + } + + WiFi.hostname(sensor_name); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected!"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); +} + +void setup_ota() { + ArduinoOTA.setHostname(sensor_name); + ArduinoOTA.setPassword(ota_password); + + ArduinoOTA.onStart([]() { + Serial.println("Starting"); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); +} + +void loop() { + + if (WiFi.status() != WL_CONNECTED) { + delay(1); + Serial.print("WIFI Disconnected. Attempting reconnection."); + setup_wifi(); + return; + } + + ArduinoOTA.handle(); + + // TODO: Hookup either a more elaborate protocol, or a secondary + // communication channel (i.e. mqtt) for functional control. This + // will also give the ability to have some non-reative effects to + // be driven completely locally making them less glitchy. + + // Handle UDP data + int packetSize = port.parsePacket(); + if (packetSize == sizeof(leds)) { + port.read((char*)leds, sizeof(leds)); + FastLED.show(); + } else if (packetSize) { + Serial.printf("Invalid packet size: %u (expected %u)\n", packetSize, sizeof(leds)); + port.flush(); + return; + } +} \ No newline at end of file diff --git a/python/lib/config.py b/python/lib/config.py index 2eb9337..3ff9901 100644 --- a/python/lib/config.py +++ b/python/lib/config.py @@ -20,7 +20,7 @@ settings = { # All settings '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 + 'MIN_VOLUME_THRESHOLD': 0.001 # No music visualization displayed if recorded audio volume below threshold #'LOGARITHMIC_SCALING': True, # Scale frequencies logarithmically to match perceived pitch of human ear }, @@ -32,7 +32,7 @@ settings = { # All settings # 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) + "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 @@ -42,7 +42,7 @@ settings = { # All settings # 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 + "current_effect": "Energy" # Currently selected effect for this board, used as default when program launches }, # Configurable options for this board's effects go in this dictionary. @@ -67,7 +67,10 @@ settings = { # All settings "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 + "Scroll": {"lows_color": "Red", # Colour of low frequencies + "mids_color": "Green", # Colour of mid frequencies + "high_color": "Blue", # Colour of high frequencies + "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 @@ -101,7 +104,7 @@ settings = { # All settings } }, "Main Strip":{ - "configuration":{"TYPE": "esp8266", # Device type (see below for all supported boards) + "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 @@ -136,7 +139,10 @@ settings = { # All settings "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 + "Scroll": {"lows_color": "Red", # Colour of low frequencies + "mids_color": "Green", # Colour of mid frequencies + "high_color": "Blue", # Colour of high frequencies + "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 @@ -190,11 +196,56 @@ settings = { # All settings "Sunset" : ["Red", "Orange", "Yellow"], "Ocean" : ["Green", "Light blue", "Blue"], "Jungle" : ["Green", "Red", "Orange"], - "Sunny" : ["Yellow", "Light blue", "Orange", "Blue"] + "Sunny" : ["Yellow", "Light blue", "Orange", "Blue"], + "Fruity" : ["Orange", "Blue"], + "Peach" : ["Orange", "Pink"], + "Rust" : ["Orange", "Red"] } } + +device_req_config = {"Stripless" : None, # duh + "BlinkStick" : None, + "DotStar" : None, + "ESP8266" : {"AUTO_DETECT": ["Auto Detect", + "Automatically detect device on network using MAC address", + "checkbox", + True], + "MAC_ADDR" : ["Mac Address", + "Hardware address of device, used for auto-detection", + "textbox", + "aa-bb-cc-dd-ee-ff"], + "UDP_IP" : ["IP Address", + "IP address of device, used if auto-detection isn't active", + "textbox", + "xxx.xxx.xxx.xxx"], + "UDP_PORT" : ["Port", + "Port used to communicate with device", + "textbox", + "7778"]}, + "RaspberryPi" : {"LED_PIN" : ["LED Pin", + "GPIO pin connected to the LED strip RaspberryPi (must support PWM)", + "textbox", + "10"], + "LED_FREQ_HZ": ["LED Frequency", + "LED signal frequency in Hz", + "textbox", + "800000"], + "LED_DMA" : ["DMA Channel", + "DMA channel used for generating PWM signal", + "textbox", + "5"], + "LED_INVERT" : ["Invert LEDs", + "Set True if using an inverting logic level converter", + "checkbox", + True]}, + "Fadecandy" : {"SERVER" : ["Server Address", + "Address of Fadecandy server", + "textbox", + "localhost:7890"]} + } + """ ~~ NOTES ~~ @@ -215,45 +266,45 @@ be loaded. Basically it works as you would expect it to. 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. 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 +'RaspberryPi' means that you are using a Raspberry Pi as a standalone unit to process 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. -'fadecandy' means that a fadecandy server is running on your computer and is connected -via usb to a fadecandy board connected to LEDs +'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 +'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 ;) [REQUIRED CONFIGURATION KEYS] -===== 'esp8266' +===== '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' +===== 'RaspberryPi' "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' +===== 'BlinkStick' No required configuration keys -===== 'fadecandy' - "SERVER" # Address of fadecandy server. (usually 'localhost:7890') -===== 'dotstar' +===== 'Fadecandy' + "SERVER" # Address of Fadecandy server. (usually 'localhost:7890') +===== 'DotStar' No required configuration keys -===== 'stripless' +===== 'Stripless' No required configuration keys (heh) [AUTO_DETECT] @@ -293,19 +344,19 @@ There is no point using more bins than there are pixels on the LED strip. """ for board in settings["devices"]: - if settings["devices"][board]["configuration"]["TYPE"] == 'esp8266': + 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': + elif settings["devices"][board]["configuration"]["TYPE"] == 'RaspberryPi': 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': + elif settings["devices"][board]["configuration"]["TYPE"] == 'BlinkStick': settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = True - elif settings["devices"][board]["configuration"]["TYPE"] == 'dotstar': + elif settings["devices"][board]["configuration"]["TYPE"] == 'DotStar': settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False - elif settings["devices"][board]["configuration"]["TYPE"] == 'fadecandy': + elif settings["devices"][board]["configuration"]["TYPE"] == 'Fadecandy': settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False - elif settings["devices"][board]["configuration"]["TYPE"] == 'stripless': + 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"])) diff --git a/python/lib/settings.ini b/python/lib/settings.ini new file mode 100644 index 0000000..769e003 --- /dev/null +++ b/python/lib/settings.ini @@ -0,0 +1,6 @@ +[General] +settings_dict="@Variant(\0\0\0\b\0\0\0\x5\0\0\0\x12\0g\0r\0\x61\0\x64\0i\0\x65\0n\0t\0s\0\0\0\b\0\0\0\t\0\0\0\f\0S\0u\0n\0s\0\x65\0t\0\0\0\t\0\0\0\x3\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\n\0\0\0\f\0O\0r\0\x61\0n\0g\0\x65\0\0\0\n\0\0\0\f\0Y\0\x65\0l\0l\0o\0w\0\0\0\n\0S\0u\0n\0n\0y\0\0\0\t\0\0\0\x4\0\0\0\n\0\0\0\f\0Y\0\x65\0l\0l\0o\0w\0\0\0\n\0\0\0\x14\0L\0i\0g\0h\0t\0 \0\x62\0l\0u\0\x65\0\0\0\n\0\0\0\f\0O\0r\0\x61\0n\0g\0\x65\0\0\0\n\0\0\0\b\0\x42\0l\0u\0\x65\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\t\0\0\0\b\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\n\0\0\0\f\0O\0r\0\x61\0n\0g\0\x65\0\0\0\n\0\0\0\f\0Y\0\x65\0l\0l\0o\0w\0\0\0\n\0\0\0\n\0G\0r\0\x65\0\x65\0n\0\0\0\n\0\0\0\x14\0L\0i\0g\0h\0t\0 \0\x62\0l\0u\0\x65\0\0\0\n\0\0\0\b\0\x42\0l\0u\0\x65\0\0\0\n\0\0\0\f\0P\0u\0r\0p\0l\0\x65\0\0\0\n\0\0\0\b\0P\0i\0n\0k\0\0\0\b\0R\0u\0s\0t\0\0\0\t\0\0\0\x2\0\0\0\n\0\0\0\f\0O\0r\0\x61\0n\0g\0\x65\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\n\0P\0\x65\0\x61\0\x63\0h\0\0\0\t\0\0\0\x2\0\0\0\n\0\0\0\f\0O\0r\0\x61\0n\0g\0\x65\0\0\0\n\0\0\0\b\0P\0i\0n\0k\0\0\0\n\0O\0\x63\0\x65\0\x61\0n\0\0\0\t\0\0\0\x3\0\0\0\n\0\0\0\n\0G\0r\0\x65\0\x65\0n\0\0\0\n\0\0\0\x14\0L\0i\0g\0h\0t\0 \0\x62\0l\0u\0\x65\0\0\0\n\0\0\0\b\0\x42\0l\0u\0\x65\0\0\0\f\0J\0u\0n\0g\0l\0\x65\0\0\0\t\0\0\0\x3\0\0\0\n\0\0\0\n\0G\0r\0\x65\0\x65\0n\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\n\0\0\0\f\0O\0r\0\x61\0n\0g\0\x65\0\0\0\f\0\x46\0r\0u\0i\0t\0y\0\0\0\t\0\0\0\x2\0\0\0\n\0\0\0\f\0O\0r\0\x61\0n\0g\0\x65\0\0\0\n\0\0\0\b\0\x42\0l\0u\0\x65\0\0\0\x14\0\x44\0\x61\0n\0\x63\0\x65\0\x66\0l\0o\0o\0r\0\0\0\t\0\0\0\x4\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\n\0\0\0\b\0P\0i\0n\0k\0\0\0\n\0\0\0\f\0P\0u\0r\0p\0l\0\x65\0\0\0\n\0\0\0\b\0\x42\0l\0u\0\x65\0\0\0\xe\0\x64\0\x65\0v\0i\0\x63\0\x65\0s\0\0\0\b\0\0\0\x2\0\0\0\x14\0M\0\x61\0i\0n\0 \0S\0t\0r\0i\0p\0\0\0\b\0\0\0\x2\0\0\0\x16\0\x65\0\x66\0\x66\0\x65\0\x63\0t\0_\0o\0p\0t\0s\0\0\0\b\0\0\0\f\0\0\0\x14\0W\0\x61\0v\0\x65\0l\0\x65\0n\0g\0t\0h\0\0\0\b\0\0\0\a\0\0\0\x14\0r\0o\0l\0l\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\0\0\0\0\x18\0r\0\x65\0v\0\x65\0r\0s\0\x65\0_\0r\0o\0l\0l\0\0\0\x1\0\0\0\0\x18\0r\0\x65\0v\0\x65\0r\0s\0\x65\0_\0g\0r\0\x61\0\x64\0\0\0\x1\0\0\0\0\f\0m\0i\0r\0r\0o\0r\0\0\0\x1\0\0\0\0\xe\0\x66\0l\0i\0p\0_\0l\0r\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\b\0\x62\0l\0u\0r\0\0\0\x6@\b\0\0\0\0\0\0\0\0\0\b\0W\0\x61\0v\0\x65\0\0\0\b\0\0\0\x5\0\0\0\x14\0w\0i\0p\0\x65\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\x2\0\0\0\x10\0w\0i\0p\0\x65\0_\0l\0\x65\0n\0\0\0\x2\0\0\0\x5\0\0\0\n\0\x64\0\x65\0\x63\0\x61\0y\0\0\0\x6?\xe6\x66\x66\x66\x66\x66\x66\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0w\0\x61\0v\0\x65\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\x16\0\x63\0o\0l\0o\0r\0_\0\x66\0l\0\x61\0s\0h\0\0\0\n\0\0\0\n\0W\0h\0i\0t\0\x65\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0u\0m\0\0\0\b\0\0\0\x3\0\0\0\x18\0r\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x18\0g\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x18\0\x62\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\f\0S\0i\0n\0g\0l\0\x65\0\0\0\b\0\0\0\x1\0\0\0\n\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\f\0P\0u\0r\0p\0l\0\x65\0\0\0\f\0S\0\x63\0r\0o\0l\0l\0\0\0\b\0\0\0\t\0\0\0\n\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\x1\0\0\0\x18\0r\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x14\0m\0i\0\x64\0s\0_\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\n\0G\0r\0\x65\0\x65\0n\0\0\0\x14\0l\0o\0w\0s\0_\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\x14\0h\0i\0g\0h\0_\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\b\0\x42\0l\0u\0\x65\0\0\0\x18\0g\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\n\0\x64\0\x65\0\x63\0\x61\0y\0\0\0\x6?\xef\xd7\n=p\xa3\xd7\0\0\0\b\0\x62\0l\0u\0r\0\0\0\x6?\xc9\x99\x99\x99\x99\x99\x9a\0\0\0\x18\0\x62\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\n\0P\0o\0w\0\x65\0r\0\0\0\b\0\0\0\x5\0\0\0\xe\0s\0_\0\x63\0o\0u\0n\0t\0\0\0\x2\0\0\0%\0\0\0\xe\0s\0_\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\n\0W\0h\0i\0t\0\x65\0\0\0\f\0m\0i\0r\0r\0o\0r\0\0\0\x1\x1\0\0\0\xe\0\x66\0l\0i\0p\0_\0l\0r\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\x10\0G\0r\0\x61\0\x64\0i\0\x65\0n\0t\0\0\0\b\0\0\0\x4\0\0\0\x14\0r\0o\0l\0l\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\0\0\0\0\xe\0r\0\x65\0v\0\x65\0r\0s\0\x65\0\0\0\x1\0\0\0\0\f\0m\0i\0r\0r\0o\0r\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\b\0\x46\0\x61\0\x64\0\x65\0\0\0\b\0\0\0\x3\0\0\0\x14\0r\0o\0l\0l\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\x1\0\0\0\xe\0r\0\x65\0v\0\x65\0r\0s\0\x65\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\f\0\x45\0n\0\x65\0r\0g\0y\0\0\0\b\0\0\0\x5\0\0\0\n\0s\0\x63\0\x61\0l\0\x65\0\0\0\x6?\xec\xcc\xcc\xcc\xcc\xcc\xcd\0\0\0\x18\0r\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x18\0g\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\b\0\x62\0l\0u\0r\0\0\0\x2\0\0\0\x1\0\0\0\x18\0\x62\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x16\0\x43\0\x61\0l\0i\0\x62\0r\0\x61\0t\0i\0o\0n\0\0\0\b\0\0\0\x3\0\0\0\x2\0r\0\0\0\x2\0\0\0\x64\0\0\0\x2\0g\0\0\0\x2\0\0\0\x64\0\0\0\x2\0\x62\0\0\0\x2\0\0\0\x64\0\0\0\b\0\x42\0\x65\0\x61\0t\0\0\0\b\0\0\0\x2\0\0\0\n\0\x64\0\x65\0\x63\0\x61\0y\0\0\0\x6?\xe6\x66\x66\x66\x66\x66\x66\0\0\0\n\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\b\0\x42\0\x61\0r\0s\0\0\0\b\0\0\0\x6\0\0\0\x14\0r\0o\0l\0l\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\0\0\0\0\x18\0r\0\x65\0v\0\x65\0r\0s\0\x65\0_\0r\0o\0l\0l\0\0\0\x1\0\0\0\0\x14\0r\0\x65\0s\0o\0l\0u\0t\0i\0o\0n\0\0\0\x2\0\0\0\x4\0\0\0\f\0m\0i\0r\0r\0o\0r\0\0\0\x1\0\0\0\0\xe\0\x66\0l\0i\0p\0_\0l\0r\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\x1a\0\x63\0o\0n\0\x66\0i\0g\0u\0r\0\x61\0t\0i\0o\0n\0\0\0\b\0\0\0\n\0\0\0\x1c\0\x63\0u\0r\0r\0\x65\0n\0t\0_\0\x65\0\x66\0\x66\0\x65\0\x63\0t\0\0\0\n\0\0\0\f\0S\0i\0n\0g\0l\0\x65\0\0\0\x10\0U\0\x44\0P\0_\0P\0O\0R\0T\0\0\0\x2\0\0\x1e\x62\0\0\0\f\0U\0\x44\0P\0_\0I\0P\0\0\0\n\0\0\0\x1a\0\x31\0\x39\0\x32\0.\0\x31\0\x36\0\x38\0.\0\x31\0.\0\x32\0\x30\0\x38\0\0\0\b\0T\0Y\0P\0\x45\0\0\0\n\0\0\0\xe\0\x45\0S\0P\0\x38\0\x32\0\x36\0\x36\0\0\0\x32\0S\0O\0\x46\0T\0W\0\x41\0R\0\x45\0_\0G\0\x41\0M\0M\0\x41\0_\0\x43\0O\0R\0R\0\x45\0\x43\0T\0I\0O\0N\0\0\0\x1\0\0\0\0\x10\0N\0_\0P\0I\0X\0\x45\0L\0S\0\0\0\x2\0\0\0\xe2\0\0\0\x14\0N\0_\0\x46\0\x46\0T\0_\0\x42\0I\0N\0S\0\0\0\x2\0\0\0\x18\0\0\0\x1c\0M\0\x41\0X\0_\0\x42\0R\0I\0G\0H\0T\0N\0\x45\0S\0S\0\0\0\x2\0\0\0\xb4\0\0\0\x10\0M\0\x41\0\x43\0_\0\x41\0\x44\0\x44\0R\0\0\0\n\0\0\0\"\0\x35\0\x63\0-\0\x63\0\x66\0-\0\x37\0\x66\0-\0\x66\0\x30\0-\0\x38\0\x63\0-\0\x66\0\x33\0\0\0\x16\0\x41\0U\0T\0O\0_\0\x44\0\x45\0T\0\x45\0\x43\0T\0\0\0\x1\x1\0\0\0\x14\0\x44\0\x65\0s\0k\0 \0S\0t\0r\0i\0p\0\0\0\b\0\0\0\x2\0\0\0\x16\0\x65\0\x66\0\x66\0\x65\0\x63\0t\0_\0o\0p\0t\0s\0\0\0\b\0\0\0\f\0\0\0\x14\0W\0\x61\0v\0\x65\0l\0\x65\0n\0g\0t\0h\0\0\0\b\0\0\0\a\0\0\0\x14\0r\0o\0l\0l\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\0\0\0\0\x18\0r\0\x65\0v\0\x65\0r\0s\0\x65\0_\0r\0o\0l\0l\0\0\0\x1\0\0\0\0\x18\0r\0\x65\0v\0\x65\0r\0s\0\x65\0_\0g\0r\0\x61\0\x64\0\0\0\x1\0\0\0\0\f\0m\0i\0r\0r\0o\0r\0\0\0\x1\0\0\0\0\xe\0\x66\0l\0i\0p\0_\0l\0r\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\b\0\x62\0l\0u\0r\0\0\0\x6@\b\0\0\0\0\0\0\0\0\0\b\0W\0\x61\0v\0\x65\0\0\0\b\0\0\0\x5\0\0\0\x14\0w\0i\0p\0\x65\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\x2\0\0\0\x10\0w\0i\0p\0\x65\0_\0l\0\x65\0n\0\0\0\x2\0\0\0\x5\0\0\0\n\0\x64\0\x65\0\x63\0\x61\0y\0\0\0\x6?\xe6\x66\x66\x66\x66\x66\x66\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0w\0\x61\0v\0\x65\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\x16\0\x63\0o\0l\0o\0r\0_\0\x66\0l\0\x61\0s\0h\0\0\0\n\0\0\0\n\0W\0h\0i\0t\0\x65\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0u\0m\0\0\0\b\0\0\0\x3\0\0\0\x18\0r\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x18\0g\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x18\0\x62\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\f\0S\0i\0n\0g\0l\0\x65\0\0\0\b\0\0\0\x1\0\0\0\n\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\f\0P\0u\0r\0p\0l\0\x65\0\0\0\f\0S\0\x63\0r\0o\0l\0l\0\0\0\b\0\0\0\t\0\0\0\n\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\x1\0\0\0\x18\0r\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x14\0m\0i\0\x64\0s\0_\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\n\0G\0r\0\x65\0\x65\0n\0\0\0\x14\0l\0o\0w\0s\0_\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\x14\0h\0i\0g\0h\0_\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\b\0\x42\0l\0u\0\x65\0\0\0\x18\0g\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\n\0\x64\0\x65\0\x63\0\x61\0y\0\0\0\x6?\xef\xd7\n=p\xa3\xd7\0\0\0\b\0\x62\0l\0u\0r\0\0\0\x6?\xc9\x99\x99\x99\x99\x99\x9a\0\0\0\x18\0\x62\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\n\0P\0o\0w\0\x65\0r\0\0\0\b\0\0\0\x5\0\0\0\xe\0s\0_\0\x63\0o\0u\0n\0t\0\0\0\x2\0\0\0\t\0\0\0\xe\0s\0_\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\n\0W\0h\0i\0t\0\x65\0\0\0\f\0m\0i\0r\0r\0o\0r\0\0\0\x1\x1\0\0\0\xe\0\x66\0l\0i\0p\0_\0l\0r\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\x10\0G\0r\0\x61\0\x64\0i\0\x65\0n\0t\0\0\0\b\0\0\0\x4\0\0\0\x14\0r\0o\0l\0l\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\0\0\0\0\xe\0r\0\x65\0v\0\x65\0r\0s\0\x65\0\0\0\x1\0\0\0\0\f\0m\0i\0r\0r\0o\0r\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\b\0\x46\0\x61\0\x64\0\x65\0\0\0\b\0\0\0\x3\0\0\0\x14\0r\0o\0l\0l\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\x1\0\0\0\xe\0r\0\x65\0v\0\x65\0r\0s\0\x65\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\f\0\x45\0n\0\x65\0r\0g\0y\0\0\0\b\0\0\0\x5\0\0\0\n\0s\0\x63\0\x61\0l\0\x65\0\0\0\x6?\xec\xcc\xcc\xcc\xcc\xcc\xcd\0\0\0\x18\0r\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x18\0g\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\b\0\x62\0l\0u\0r\0\0\0\x2\0\0\0\x1\0\0\0\x18\0\x62\0_\0m\0u\0l\0t\0i\0p\0l\0i\0\x65\0r\0\0\0\x6?\xf0\0\0\0\0\0\0\0\0\0\x16\0\x43\0\x61\0l\0i\0\x62\0r\0\x61\0t\0i\0o\0n\0\0\0\b\0\0\0\x3\0\0\0\x2\0r\0\0\0\x2\0\0\0\x64\0\0\0\x2\0g\0\0\0\x2\0\0\0\x64\0\0\0\x2\0\x62\0\0\0\x2\0\0\0\x64\0\0\0\b\0\x42\0\x65\0\x61\0t\0\0\0\b\0\0\0\x2\0\0\0\n\0\x64\0\x65\0\x63\0\x61\0y\0\0\0\x6?\xe6\x66\x66\x66\x66\x66\x66\0\0\0\n\0\x63\0o\0l\0o\0r\0\0\0\n\0\0\0\x6\0R\0\x65\0\x64\0\0\0\b\0\x42\0\x61\0r\0s\0\0\0\b\0\0\0\x6\0\0\0\x14\0r\0o\0l\0l\0_\0s\0p\0\x65\0\x65\0\x64\0\0\0\x2\0\0\0\0\0\0\0\x18\0r\0\x65\0v\0\x65\0r\0s\0\x65\0_\0r\0o\0l\0l\0\0\0\x1\0\0\0\0\x14\0r\0\x65\0s\0o\0l\0u\0t\0i\0o\0n\0\0\0\x2\0\0\0\x4\0\0\0\f\0m\0i\0r\0r\0o\0r\0\0\0\x1\0\0\0\0\xe\0\x66\0l\0i\0p\0_\0l\0r\0\0\0\x1\0\0\0\0\x14\0\x63\0o\0l\0o\0r\0_\0m\0o\0\x64\0\x65\0\0\0\n\0\0\0\x10\0S\0p\0\x65\0\x63\0t\0r\0\x61\0l\0\0\0\x1a\0\x63\0o\0n\0\x66\0i\0g\0u\0r\0\x61\0t\0i\0o\0n\0\0\0\b\0\0\0\n\0\0\0\x1c\0\x63\0u\0r\0r\0\x65\0n\0t\0_\0\x65\0\x66\0\x66\0\x65\0\x63\0t\0\0\0\n\0\0\0\f\0S\0\x63\0r\0o\0l\0l\0\0\0\x10\0U\0\x44\0P\0_\0P\0O\0R\0T\0\0\0\x2\0\0\x1e\x62\0\0\0\f\0U\0\x44\0P\0_\0I\0P\0\0\0\n\0\0\0\x1a\0\x31\0\x39\0\x32\0.\0\x31\0\x36\0\x38\0.\0\x31\0.\0\x32\0\x30\0\x38\0\0\0\b\0T\0Y\0P\0\x45\0\0\0\n\0\0\0\xe\0\x45\0S\0P\0\x38\0\x32\0\x36\0\x36\0\0\0\x32\0S\0O\0\x46\0T\0W\0\x41\0R\0\x45\0_\0G\0\x41\0M\0M\0\x41\0_\0\x43\0O\0R\0R\0\x45\0\x43\0T\0I\0O\0N\0\0\0\x1\0\0\0\0\x10\0N\0_\0P\0I\0X\0\x45\0L\0S\0\0\0\x2\0\0\0:\0\0\0\x14\0N\0_\0\x46\0\x46\0T\0_\0\x42\0I\0N\0S\0\0\0\x2\0\0\0\x18\0\0\0\x1c\0M\0\x41\0X\0_\0\x42\0R\0I\0G\0H\0T\0N\0\x45\0S\0S\0\0\0\x2\0\0\0\xfa\0\0\0\x10\0M\0\x41\0\x43\0_\0\x41\0\x44\0\x44\0R\0\0\0\n\0\0\0\"\0\x32\0\x63\0-\0\x33\0\x61\0-\0\x65\0\x38\0-\0\x32\0\x66\0-\0\x32\0\x63\0-\0\x39\0\x66\0\0\0\x16\0\x41\0U\0T\0O\0_\0\x44\0\x45\0T\0\x45\0\x43\0T\0\0\0\x1\x1\0\0\0\x1a\0\x63\0o\0n\0\x66\0i\0g\0u\0r\0\x61\0t\0i\0o\0n\0\0\0\b\0\0\0\t\0\0\0\xe\0U\0S\0\x45\0_\0G\0U\0I\0\0\0\x1\x1\0\0\0\"\0N\0_\0R\0O\0L\0L\0I\0N\0G\0_\0H\0I\0S\0T\0O\0R\0Y\0\0\0\x2\0\0\0\x4\0\0\0(\0M\0I\0N\0_\0V\0O\0L\0U\0M\0\x45\0_\0T\0H\0R\0\x45\0S\0H\0O\0L\0\x44\0\0\0\x6?PbM\xd2\xf1\xa9\xfc\0\0\0\x1a\0M\0I\0N\0_\0\x46\0R\0\x45\0Q\0U\0\x45\0N\0\x43\0Y\0\0\0\x2\0\0\0\x14\0\0\0\x10\0M\0I\0\x43\0_\0R\0\x41\0T\0\x45\0\0\0\x2\0\0\xbb\x80\0\0\0\x1a\0M\0\x41\0X\0_\0\x46\0R\0\x45\0Q\0U\0\x45\0N\0\x43\0Y\0\0\0\x2\0\0\x46P\0\0\0\x1c\0M\0\x41\0X\0_\0\x42\0R\0I\0G\0H\0T\0N\0\x45\0S\0S\0\0\0\x2\0\0\0\xfa\0\0\0\x6\0\x46\0P\0S\0\0\0\x2\0\0\0<\0\0\0\x16\0\x44\0I\0S\0P\0L\0\x41\0Y\0_\0\x46\0P\0S\0\0\0\x1\0\0\0\0\f\0\x63\0o\0l\0o\0r\0s\0\0\0\b\0\0\0\t\0\0\0\f\0Y\0\x65\0l\0l\0o\0w\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3K\xffK\xffK\0\x87q\0.\0\0\0\n\0W\0h\0i\0t\0\x65\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3K\xffK\xffK\xff\x87q\0.\0\0\0\x6\0R\0\x65\0\x64\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3K\xffK\0K\0\x87q\0.\0\0\0\f\0P\0u\0r\0p\0l\0\x65\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3KPK\x5K\xfc\x87q\0.\0\0\0\b\0P\0i\0n\0k\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3K\xffK\0K\xb2\x87q\0.\0\0\0\f\0O\0r\0\x61\0n\0g\0\x65\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3K\xffK(K\0\x87q\0.\0\0\0\x14\0L\0i\0g\0h\0t\0 \0\x62\0l\0u\0\x65\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3K\x1K\xf7K\xa1\x87q\0.\0\0\0\n\0G\0r\0\x65\0\x65\0n\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3K\0K\xffK\0\x87q\0.\0\0\0\b\0\x42\0l\0u\0\x65\0\0\0\x7f\0\0\0\xePyQt_PyObject\0\0\0\0\f\x80\x3K\0K\0K\xff\x87q\0.\0\0\0\x10\0G\0U\0I\0_\0o\0p\0t\0s\0\0\0\b\0\0\0\x5\0\0\0.\0R\0\x65\0\x61\0\x63\0t\0i\0v\0\x65\0 \0\x45\0\x66\0\x66\0\x65\0\x63\0t\0 \0\x42\0u\0t\0t\0o\0n\0s\0\0\0\x1\x1\0\0\0\x36\0N\0o\0n\0 \0R\0\x65\0\x61\0\x63\0t\0i\0v\0\x65\0 \0\x45\0\x66\0\x66\0\x65\0\x63\0t\0 \0\x42\0u\0t\0t\0o\0n\0s\0\0\0\x1\x1\0\0\0\f\0G\0r\0\x61\0p\0h\0s\0\0\0\x1\x1\0\0\0\x1e\0\x46\0r\0\x65\0q\0u\0\x65\0n\0\x63\0y\0 \0R\0\x61\0n\0g\0\x65\0\0\0\x1\x1\0\0\0\x1c\0\x45\0\x66\0\x66\0\x65\0\x63\0t\0 \0O\0p\0t\0i\0o\0n\0s\0\0\0\x1\x1)" + +[MainWindow] +geometry="@ByteArray(\x1\xd9\xd0\xcb\0\x2\0\0\xff\xff\xff\xf8\xff\xff\xff\xf8\0\0\a\x87\0\0\x4?\0\0\x2\x80\0\0\x1,\0\0\x5)\0\0\x3\xfb\0\0\0\0\x2\0\0\0\a\x80)" +state=@ByteArray(\0\0\0\xff\0\0\0\0\xfd\0\0\0\0\0\0\a\x80\0\0\x3\xf6\0\0\0\x4\0\0\0\x4\0\0\0\b\0\0\0\b\xfc\0\0\0\x1\0\0\0\x2\0\0\0\x1\0\0\0\x16\0t\0o\0p\0_\0t\0o\0o\0l\0\x62\0\x61\0r\x1\0\0\0\0\xff\xff\xff\xff\0\0\0\0\0\0\0\0) diff --git a/python/main.py b/python/main.py index f883434..2aabf59 100644 --- a/python/main.py +++ b/python/main.py @@ -47,7 +47,8 @@ class Visualizer(): self.beat_count = 0 self.freq_channels = [deque(maxlen=self.freq_channel_history) for i in range(config.settings["devices"][self.board]["configuration"]["N_FFT_BINS"])] self.prev_output = np.array([[0 for i in range(config.settings["devices"][self.board]["configuration"]["N_PIXELS"])] for i in range(3)]) - self.prev_spectrum = [0 for i in range(config.settings["devices"][self.board]["configuration"]["N_PIXELS"]//2)] + self.output = np.array([[0 for i in range(config.settings["devices"][self.board]["configuration"]["N_PIXELS"])] for i in range(3)]) + self.prev_spectrum = np.array([config.settings["devices"][self.board]["configuration"]["N_PIXELS"] // 2]) self.current_freq_detects = {"beat":False, "low":False, "mid":False, @@ -118,12 +119,12 @@ class Visualizer(): ["reverse_grad", "Reverse Gradient", "checkbox"], ["reverse_roll", "Reverse Roll", "checkbox"], ["flip_lr", "Flip LR", "checkbox"]], - "Scroll":[["blur", "Blur", "float_slider", (0.05,4.0,0.05)], + "Scroll":[["lows_color", "Lows Color", "dropdown", config.settings["colors"]], + ["mids_color", "Mids Color", "dropdown", config.settings["colors"]], + ["high_color", "Highs Color", "dropdown", config.settings["colors"]], + ["blur", "Blur", "float_slider", (0.05,4.0,0.05)], ["decay", "Decay", "float_slider", (0.97,1.0,0.0005)], - ["speed", "Speed", "slider", (1,5,1)], - ["r_multiplier", "Red", "float_slider", (0.05,1.0,0.05)], - ["g_multiplier", "Green", "float_slider", (0.05,1.0,0.05)], - ["b_multiplier", "Blue", "float_slider", (0.05,1.0,0.05)]], + ["speed", "Speed", "slider", (1,5,1)]], "Power":[["color_mode", "Color Mode", "dropdown", config.settings["gradients"]], ["s_color", "Spark Color ", "dropdown", config.settings["colors"]], ["s_count", "Spark Amount", "slider", (0,config.settings["devices"][self.board]["configuration"]["N_PIXELS"]//6,1)], @@ -149,8 +150,9 @@ class Visualizer(): ["g", "Green value", "slider", (0,255,1)], ["b", "Blue value", "slider", (0,255,1)]] } - # Setup for latency timer - self.latency_deque = deque(maxlen=1000) + # Setup for fps counter + self.frame_counter = 0 + self.start_time = time.time() # Setup for "Wave" (don't change these) self.wave_wipe_count = 0 # Setup for "Power" (don't change these) @@ -270,17 +272,23 @@ class Visualizer(): def get_vis(self, y, audio_input): self.update_freq_channels(y) self.detect_freqs() - time1 = time.time() if config.settings["devices"][self.board]["configuration"]["current_effect"] in self.non_reactive_effects: self.prev_output = self.effects[config.settings["devices"][self.board]["configuration"]["current_effect"]]() elif audio_input: self.prev_output = self.effects[config.settings["devices"][self.board]["configuration"]["current_effect"]](y) else: self.prev_output = np.multiply(self.prev_output, 0.95) - time2 = time.time() - self.latency_deque.append(1000*(time2-time1)) - if config.settings["configuration"]["USE_GUI"]: - gui.label_latency.setText("{} ms Processing Latency ".format(int(sum(self.latency_deque)/len(self.latency_deque)))) + self.frame_counter += 1 + elapsed = time.time() - self.start_time + if elapsed >= 1.0: + self.start_time = time.time() + fps = self.frame_counter//elapsed + latency = elapsed/self.frame_counter + self.frame_counter = 0 + + if config.settings["configuration"]["USE_GUI"]: + gui.label_latency.setText("{:0.3f} ms Processing Latency ".format(latency)) + gui.label_fps.setText('{:.0f} / {:.0f} FPS '.format(fps, config.settings["configuration"]["FPS"])) return self.prev_output def _split_equal(self, value, parts): @@ -315,54 +323,76 @@ class Visualizer(): def visualize_scroll(self, y): """Effect that originates in the center and scrolls outwards""" - global p y = y**4.0 - signal_processers[self.board].gain.update(y) - y /= signal_processers[self.board].gain.value - y *= 255.0 - r = int(np.max(y[:len(y) // 3])*config.settings["devices"][self.board]["effect_opts"]["Scroll"]["r_multiplier"]) - g = int(np.max(y[len(y) // 3: 2 * len(y) // 3])*config.settings["devices"][self.board]["effect_opts"]["Scroll"]["g_multiplier"]) - b = int(np.max(y[2 * len(y) // 3:])*config.settings["devices"][self.board]["effect_opts"]["Scroll"]["b_multiplier"]) + # signal_processers[self.board].gain.update(y) + # y /= signal_processers[self.board].gain.value + # y *= 255.0 + n_pixels = config.settings["devices"][self.board]["configuration"]["N_PIXELS"] + y = np.copy(interpolate(y, n_pixels // 2)) + signal_processers[self.board].common_mode.update(y) + diff = y - self.prev_spectrum + self.prev_spectrum = np.copy(y) + # split spectrum up + # r = signal_processers[self.board].r_filt.update(y - signal_processers[self.board].common_mode.value) + # g = np.abs(diff) + # b = signal_processers[self.board].b_filt.update(np.copy(y)) + y = np.clip(y, 0, 1) + lows = y[:len(y) // 6] + mids = y[len(y) // 6: 2 * len(y) // 5] + high = y[2 * len(y) // 5:] + # max values + lows_max = np.max(lows)#*config.settings["devices"][self.board]["effect_opts"]["Scroll"]["lows_multiplier"]) + mids_max = float(np.max(mids))#*config.settings["devices"][self.board]["effect_opts"]["Scroll"]["mids_multiplier"]) + high_max = float(np.max(high))#*config.settings["devices"][self.board]["effect_opts"]["Scroll"]["high_multiplier"]) + # indexes of max values + # map to colour gradient + lows_val = (np.array(config.settings["colors"][config.settings["devices"][self.board]["effect_opts"]["Scroll"]["lows_color"]]) * lows_max).astype(int) + mids_val = (np.array(config.settings["colors"][config.settings["devices"][self.board]["effect_opts"]["Scroll"]["mids_color"]]) * mids_max).astype(int) + high_val = (np.array(config.settings["colors"][config.settings["devices"][self.board]["effect_opts"]["Scroll"]["high_color"]]) * high_max).astype(int) # Scrolling effect window speed = config.settings["devices"][self.board]["effect_opts"]["Scroll"]["speed"] - p[:, speed:] = p[:, :-speed] - p *= config.settings["devices"][self.board]["effect_opts"]["Scroll"]["decay"] - p = gaussian_filter1d(p, sigma=config.settings["devices"][self.board]["effect_opts"]["Scroll"]["blur"]) + self.output[:, speed:] = self.output[:, :-speed] + self.output = (self.output * config.settings["devices"][self.board]["effect_opts"]["Scroll"]["decay"]).astype(int) + self.output = gaussian_filter1d(self.output, sigma=config.settings["devices"][self.board]["effect_opts"]["Scroll"]["blur"]) # Create new color originating at the center - p[0, :speed] = r - p[1, :speed] = g - p[2, :speed] = b + self.output[0, :speed] = lows_val[0] + mids_val[0] + high_val[0] + self.output[1, :speed] = lows_val[1] + mids_val[1] + high_val[1] + self.output[2, :speed] = lows_val[2] + mids_val[2] + high_val[2] # Update the LED strip - return np.concatenate((p[:, ::-1], p), axis=1) + #return np.concatenate((self.prev_spectrum[:, ::-speed], self.prev_spectrum), axis=1) + return self.output def visualize_energy(self, y): """Effect that expands from the center with increasing sound energy""" - global p y = np.copy(y) signal_processers[self.board].gain.update(y) y /= signal_processers[self.board].gain.value scale = config.settings["devices"][self.board]["effect_opts"]["Energy"]["scale"] # Scale by the width of the LED strip y *= float((config.settings["devices"][self.board]["configuration"]["N_PIXELS"] * scale) - 1) + y = np.copy(interpolate(y, config.settings["devices"][self.board]["configuration"]["N_PIXELS"] // 2)) # Map color channels according to energy in the different freq bands - r = int(np.mean(y[:len(y) // 3]**scale)*config.settings["devices"][self.board]["effect_opts"]["Energy"]["r_multiplier"]) - g = int(np.mean(y[len(y) // 3: 2 * len(y) // 3]**scale)*config.settings["devices"][self.board]["effect_opts"]["Energy"]["g_multiplier"]) - b = int(np.mean(y[2 * len(y) // 3:]**scale)*config.settings["devices"][self.board]["effect_opts"]["Energy"]["b_multiplier"]) + #y = np.copy(interpolate(y, config.settings["devices"][self.board]["configuration"]["N_PIXELS"] // 2)) + diff = y - self.prev_spectrum + self.prev_spectrum = np.copy(y) + spectrum = np.copy(self.prev_spectrum) + spectrum = np.array([j for i in zip(spectrum,spectrum) for j in i]) + # Color channel mappings + r = int(np.mean(spectrum[:len(spectrum) // 3]**scale)*config.settings["devices"][self.board]["effect_opts"]["Energy"]["r_multiplier"]) + g = int(np.mean(spectrum[len(spectrum) // 3: 2 * len(spectrum) // 3]**scale)*config.settings["devices"][self.board]["effect_opts"]["Energy"]["g_multiplier"]) + b = int(np.mean(spectrum[2 * len(spectrum) // 3:]**scale)*config.settings["devices"][self.board]["effect_opts"]["Energy"]["b_multiplier"]) # Assign color to different frequency regions - p[0, :r] = 255.0 - p[0, r:] = 0.0 - p[1, :g] = 255.0 - p[1, g:] = 0.0 - p[2, :b] = 255.0 - p[2, b:] = 0.0 - signal_processers[self.board].p_filt.update(p) - p = np.round(signal_processers[self.board].p_filt.value) + self.output[0, :r] = 255 + self.output[0, r:] = 0 + self.output[1, :g] = 255 + self.output[1, g:] = 0 + self.output[2, :b] = 255 + self.output[2, b:] = 0 # Apply blur to smooth the edges - p[0, :] = gaussian_filter1d(p[0, :], sigma=config.settings["devices"][self.board]["effect_opts"]["Energy"]["blur"]) - p[1, :] = gaussian_filter1d(p[1, :], sigma=config.settings["devices"][self.board]["effect_opts"]["Energy"]["blur"]) - p[2, :] = gaussian_filter1d(p[2, :], sigma=config.settings["devices"][self.board]["effect_opts"]["Energy"]["blur"]) - # Set the new pixel value - return np.concatenate((p[:, ::-1], p), axis=1) + self.output[0, :] = gaussian_filter1d(self.output[0, :], sigma=config.settings["devices"][self.board]["effect_opts"]["Energy"]["blur"]) + self.output[1, :] = gaussian_filter1d(self.output[1, :], sigma=config.settings["devices"][self.board]["effect_opts"]["Energy"]["blur"]) + self.output[2, :] = gaussian_filter1d(self.output[2, :], sigma=config.settings["devices"][self.board]["effect_opts"]["Energy"]["blur"]) + return self.output def visualize_wavelength(self, y): y = np.copy(interpolate(y, config.settings["devices"][self.board]["configuration"]["N_PIXELS"] // 2)) @@ -371,7 +401,7 @@ class Visualizer(): self.prev_spectrum = np.copy(y) # Color channel mappings r = signal_processers[self.board].r_filt.update(y - signal_processers[self.board].common_mode.value) - #g = np.abs(diff) + g = np.abs(diff) b = signal_processers[self.board].b_filt.update(np.copy(y)) r = np.array([j for i in zip(r,r) for j in i]) output = np.array([self.multicolor_modes[config.settings["devices"][self.board]["effect_opts"]["Wavelength"]["color_mode"]][0][ @@ -399,7 +429,6 @@ class Visualizer(): def visualize_spectrum(self, y): """Effect that maps the Mel filterbank frequencies onto the LED strip""" - global p #print(len(y)) #print(y) y = np.copy(interpolate(y, config.settings["devices"][self.board]["configuration"]["N_PIXELS"] // 2)) @@ -541,7 +570,7 @@ class Visualizer(): return output def visualize_pulse(self, y): - """fckin dope ass visuals that's what""" + """dope ass visuals that's what""" config.settings["devices"][self.board]["effect_opts"]["Pulse"]["bar_color"] config.settings["devices"][self.board]["effect_opts"]["Pulse"]["bar_speed"] config.settings["devices"][self.board]["effect_opts"]["Pulse"]["bar_length"] @@ -602,7 +631,7 @@ class GUI(QMainWindow): self.updateUIVisibleItems() def initMainWindow(self): - # ==================================== Set up window and wrapping layout + # Set up window and wrapping layout self.setWindowTitle("Visualization") # Initial window size/pos last saved if available settings.beginGroup("MainWindow") @@ -613,19 +642,22 @@ class GUI(QMainWindow): settings.endGroup() self.main_wrapper = QVBoxLayout() - # ======================================================= Set up toolbar + # Set up toolbar #toolbar_guiDialogue.setShortcut('Ctrl+H') + toolbar_deviceDialogue = QAction('LED Strip Manager', self) + toolbar_deviceDialogue.triggered.connect(self.deviceDialogue) toolbar_guiDialogue = QAction('GUI Properties', self) toolbar_guiDialogue.triggered.connect(self.guiDialogue) - #toolbar_configDialogue = QAction('GUI Properties', self) - #toolbar_configDialogue.triggered.connect(self.configDialogue) + toolbar_saveDialogue = QAction('Save Settings', self) + toolbar_saveDialogue.triggered.connect(self.saveDialogue) self.toolbar = self.addToolBar('top_toolbar') self.toolbar.setObjectName('top_toolbar') self.toolbar.addAction(toolbar_guiDialogue) - # self.toolbar.addAction(toolbar_configDialogue) + self.toolbar.addAction(toolbar_saveDialogue) + self.toolbar.addAction(toolbar_deviceDialogue) - # ========================================== Set up FPS and error labels + # Set up FPS and error labels self.statusbar = QStatusBar() self.setStatusBar(self.statusbar) self.label_error = QLabel("") @@ -637,8 +669,8 @@ class GUI(QMainWindow): self.statusbar.addPermanentWidget(self.label_latency) self.statusbar.addPermanentWidget(self.label_fps) - # ==================================================== Set up board tabs - self.label_boards = QLabel("Boards") + # Set up board tabs + self.label_boards = QLabel("LED Strips") self.boardsTabWidget = QTabWidget() # Dynamically set up boards tabs self.board_tabs = {} # contains all the tabs for each board @@ -648,9 +680,9 @@ class GUI(QMainWindow): self.addBoard(board) self.main_wrapper.addWidget(self.label_boards) self.main_wrapper.addWidget(self.boardsTabWidget) - self.setLayout(self.main_wrapper) + #self.setLayout(self.main_wrapper) - # =========================================== Set wrapper as main widget + # Set wrapper as main widget self.setCentralWidget(QWidget(self)) self.centralWidget().setLayout(self.main_wrapper) self.show() @@ -686,10 +718,185 @@ class GUI(QMainWindow): event.ignore() def updateUIVisibleItems(self): + #print("-UPDATE-") for section in self.gui_widgets: for widget in self.gui_widgets[section]: + #print(widget.isVisible(), " ", config.settings["GUI_opts"][section]) widget.setVisible(config.settings["GUI_opts"][section]) + def deviceDialogue(self): + def show_hide_addBoard_interface(): + current_device = device_type_cbox.currentText() + for device in config.device_req_config: + for req_config_setting in widgets[device]: + if req_config_setting is not "no_config": + for widget in widgets[device][req_config_setting]: + widget.setVisible(device == current_device) + else: + # doesn't make sense i know i know + widgets[device][req_config_setting].setVisible(device == current_device) + + def validate_input(): + import re + current_device = device_type_cbox.currentText() + tests = [] + print("testing") + if current_device == "ESP8266": + for req_config_setting in config.device_req_config[current_device]: + test = widgets[current_device][req_config_setting][1].text() + if req_config_setting == "MAC_ADDR": + # Validate MAC + tests.append(True if re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", test.lower()) else False) + elif req_config_setting == "UDP_IP": + # Validate IP + try: + pieces = test.split('.') + if len(pieces) != 4: return False + tests.append(all(0<=int(self.prev_output)<256 for self.prev_output in pieces)) + except: + tests.append(False) + elif req_config_setting == "UDP_PORT": + # Validate port + print(test) + try: + int(test) + if test > 0: + test.append(True) + except: + tests.append(False) + + + + + + #pass + + + # Validate port + elif current_device == "RaspberryPi": + pass + # Validate LED Pin + # Validate LED Freq + # Validate LED DMA + elif current_device == "Fadecandy": + pass + # Validate server + elif not config.req_config_setting[current_device]: + pass + print(tests) + + # def lineEdit(labelText, defaultText): + # wrapper = QWidget() + # hLayout = QHBoxLayout() + # wrapper.setLayout(hLayout) + # label = QLabel(labelText) + # lEdit = QLineEdit() + # lEdit.setPlaceholderText(defaultText) + # hLayout.addWidget(label) + # hLayout.addWidget(lEdit) + # return wrapper + + # Set up window and layout + self.device_dialogue = QDialog(None, Qt.WindowSystemMenuHint | Qt.WindowCloseButtonHint) + self.device_dialogue.setWindowTitle("LED Strip Manager") + self.device_dialogue.setWindowModality(Qt.ApplicationModal) + layout = QVBoxLayout() + self.device_dialogue.setLayout(layout) + + # Set up tab layouts + tabs = QTabWidget() + layout.addWidget(tabs) + addDeviceTab = QWidget() + remDeviceTab = QWidget() + addDeviceTabLayout = QVBoxLayout() + remDeviceTabLayout = QVBoxLayout() + addDeviceTabButtonLayout = QGridLayout() + remDeviceTabButtonLayout = QGridLayout() + addDeviceTab.setLayout(addDeviceTabLayout) + remDeviceTab.setLayout(remDeviceTabLayout) + tabs.addTab(addDeviceTab, "Add Device") + tabs.addTab(remDeviceTab, "Remove Device") + + # Set up "Add Device" tab + device_type_cbox = QComboBox() + device_type_cbox.addItems(config.device_req_config.keys()) + device_type_cbox.currentIndexChanged.connect(show_hide_addBoard_interface) + addDeviceTabLayout.addWidget(device_type_cbox) + + # Set up "Add Device" widgets + widgets = {} + addDeviceTabLayout.addLayout(addDeviceTabButtonLayout) + remDeviceTabLayout.addLayout(remDeviceTabButtonLayout) + # if the new board has required config + for device in config.device_req_config: + # Make the widgets + widgets[device] = {} + if config.device_req_config[device]: + for req_config_setting in config.device_req_config[device]: + label = config.device_req_config[device][req_config_setting][0] + guide = config.device_req_config[device][req_config_setting][1] + wType = config.device_req_config[device][req_config_setting][2] + deflt = config.device_req_config[device][req_config_setting][3] + wLabel = QLabel(label) + #wGuide = QLabel(guide) + if wType == "textbox": + wEdit = QLineEdit() + wEdit.setPlaceholderText(deflt) + wEdit.textChanged.connect(validate_input) + elif wType == "checkbox": + wEdit = QCheckBox() + wEdit.setCheckState(Qt.Checked if deflt else Qt.Unchecked) + widgets[device][req_config_setting] = [wLabel, wEdit] + # Add widgets to layout + i = 0 + for req_config in widgets[device]: + addDeviceTabButtonLayout.addWidget(widgets[device][req_config][0], i, 0) + addDeviceTabButtonLayout.addWidget(widgets[device][req_config][1], i, 1) + #addDeviceTabButtonLayout.addWidget(widget_set[2], i+1, 0, 1, 2) + i += 1 + else: + no_setup = QLabel("Device requires no additional setup here! :)") + widgets[device]["no_config"] = no_setup + addDeviceTabButtonLayout.addWidget(no_setup, 0, 0) + + # Show appropriate widgets + show_hide_addBoard_interface() + + # self.gui_vis_checkboxes = {} + # for section in self.gui_widgets: + # self.gui_vis_checkboxes[section] = QCheckBox(section) + # self.gui_vis_checkboxes[section].setCheckState( + # Qt.Checked if config.settings["GUI_opts"][section] else Qt.Unchecked) + # self.gui_vis_checkboxes[section].stateChanged.connect(update_visibilty_dict) + # addDeviceTabLayout.addWidget(self.gui_vis_checkboxes[section]) + self.add_device_button = QPushButton("Add Device") + addDeviceTabLayout.addWidget(self.add_device_button) + + # Set up "Remove Device" tab + + # Set up ok/cancel buttons + self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) + self.buttons.accepted.connect(self.device_dialogue.accept) + self.buttons.rejected.connect(self.device_dialogue.reject) + layout.addWidget(self.buttons) + + self.device_dialogue.show() + + def saveDialogue(self): + # Save window state + settings.beginGroup("MainWindow") + settings.setValue("geometry", self.saveGeometry()) + settings.setValue('state', self.saveState()) + settings.endGroup() + # save all settings + settings.setValue("settings_dict", config.settings) + # save and close + settings.sync() + # Confirmation message + self.conf_dialogue = QMessageBox() + self.conf_dialogue.setText("Settings saved.\nSettings are also automatically saved when program closes.") + self.conf_dialogue.show() + def guiDialogue(self): def update_visibilty_dict(): for checkbox in self.gui_vis_checkboxes: @@ -697,32 +904,7 @@ class GUI(QMainWindow): self.updateUIVisibleItems() self.gui_dialogue = QDialog(None, Qt.WindowSystemMenuHint | Qt.WindowCloseButtonHint) - self.gui_dialogue.setWindowTitle("GUI Properties") - self.gui_dialogue.setWindowModality(Qt.ApplicationModal) - layout = QGridLayout() - self.gui_dialogue.setLayout(layout) - # OK button - self.buttons = QDialogButtonBox(QDialogButtonBox.Ok, Qt.Horizontal, self) - self.buttons.accepted.connect(self.gui_dialogue.accept) - - self.gui_vis_checkboxes = {} - for section in self.gui_widgets: - self.gui_vis_checkboxes[section] = QCheckBox(section) - self.gui_vis_checkboxes[section].setCheckState( - Qt.Checked if config.settings["GUI_opts"][section] else Qt.Unchecked) - self.gui_vis_checkboxes[section].stateChanged.connect(update_visibilty_dict) - layout.addWidget(self.gui_vis_checkboxes[section]) - layout.addWidget(self.buttons) - self.gui_dialogue.show() - - def configDialogue(self): - def update_visibilty_dict(): - for checkbox in self.gui_vis_checkboxes: - config.settings["GUI_opts"][checkbox] = self.gui_vis_checkboxes[checkbox].isChecked() - self.updateUIVisibleItems() - - self.gui_dialogue = QDialog(None, Qt.WindowSystemMenuHint | Qt.WindowCloseButtonHint) - self.gui_dialogue.setWindowTitle("GUI Properties") + self.gui_dialogue.setWindowTitle("Show/hide Interface Sections") self.gui_dialogue.setWindowModality(Qt.ApplicationModal) layout = QGridLayout() self.gui_dialogue.setLayout(layout) @@ -742,10 +924,10 @@ class GUI(QMainWindow): def initBoardUI(self, board): self.board = board - # =============================================== Set up wrapping layout + # Set up wrapping layout self.board_tabs_widgets[board]["wrapper"] = QVBoxLayout() - # ================================================== Set up graph layout + # Set up graph layout self.board_tabs_widgets[board]["graph_view"] = pg.GraphicsView() graph_layout = pg.GraphicsLayout(border=(100,100,100)) self.board_tabs_widgets[board]["graph_view"].setCentralItem(graph_layout) @@ -780,7 +962,7 @@ class GUI(QMainWindow): led_plot.addItem(self.board_tabs_widgets[board]["g_curve"]) led_plot.addItem(self.board_tabs_widgets[board]["b_curve"]) - # ================================================= Set up button layout + # Set up button layout self.board_tabs_widgets[board]["label_reactive"] = QLabel("Audio Reactive Effects") self.board_tabs_widgets[board]["label_non_reactive"] = QLabel("Non Reactive Effects") self.board_tabs_widgets[board]["reactive_button_grid_wrap"] = QWidget() @@ -824,7 +1006,7 @@ class GUI(QMainWindow): k = 0 l += 1 - # ============================================== Set up frequency slider + # Set up frequency slider # Frequency range label self.board_tabs_widgets[board]["label_slider"] = QLabel("Frequency Range") # Frequency slider @@ -868,7 +1050,7 @@ class GUI(QMainWindow): } """) - # ============================================ Set up option tabs layout + # Set up option tabs layout self.board_tabs_widgets[board]["label_options"] = QLabel("Effect Options") self.board_tabs_widgets[board]["opts_tabs"] = QTabWidget() # Dynamically set up tabs @@ -944,7 +1126,7 @@ class GUI(QMainWindow): - # ============================================= Add layouts into self.board_tabs_widgets[board]["wrapper"] + # Add layouts into self.board_tabs_widgets[board]["wrapper"] self.board_tabs_widgets[board]["wrapper"].addWidget(self.board_tabs_widgets[board]["graph_view"]) self.board_tabs_widgets[board]["wrapper"].addWidget(self.board_tabs_widgets[board]["label_reactive"]) self.board_tabs_widgets[board]["wrapper"].addWidget(self.board_tabs_widgets[board]["reactive_button_grid_wrap"]) @@ -1174,8 +1356,6 @@ def microphone_update(audio_samples): gui.label_error.setText("") else: gui.label_error.setText("No audio input. Volume below threshold.") - # Update fps counter - gui.label_fps.setText('{:.0f} / {:.0f} FPS'.format(fps, config.settings["configuration"]["FPS"])) app.processEvents() # Left in just in case prople dont use the gui @@ -1194,27 +1374,27 @@ visualizers = {} boards = {} for board in config.settings["devices"]: visualizers[board] = Visualizer(board) - if config.settings["devices"][board]["configuration"]["TYPE"] == 'esp8266': + if config.settings["devices"][board]["configuration"]["TYPE"] == 'ESP8266': boards[board] = devices.ESP8266( auto_detect=config.settings["devices"][board]["configuration"]["AUTO_DETECT"], mac_addr=config.settings["devices"][board]["configuration"]["MAC_ADDR"], ip=config.settings["devices"][board]["configuration"]["UDP_IP"], port=config.settings["devices"][board]["configuration"]["UDP_PORT"]) - elif config.settings["devices"][board]["configuration"]["TYPE"] == 'pi': + elif config.settings["devices"][board]["configuration"]["TYPE"] == 'RaspberryPi': boards[board] = devices.RaspberryPi( n_pixels=config.settings["devices"][board]["configuration"]["N_PIXELS"], pin=config.settings["devices"][board]["configuration"]["LED_PIN"], invert_logic=config.settings["devices"][board]["configuration"]["LED_INVERT"], freq=config.settings["devices"][board]["configuration"]["LED_FREQ_HZ"], dma=config.settings["devices"][board]["configuration"]["LED_DMA"]) - elif config.settings["devices"][board]["configuration"]["TYPE"] == 'fadecandy': + elif config.settings["devices"][board]["configuration"]["TYPE"] == 'Fadecandy': boards[board] = devices.FadeCandy( server=config.settings["devices"][board]["configuration"]["SERVER"]) - elif config.settings["devices"][board]["configuration"]["TYPE"] == 'blinkstick': + elif config.settings["devices"][board]["configuration"]["TYPE"] == 'BlinkStick': boards[board] = devices.BlinkStick() - elif config.settings["devices"][board]["configuration"]["TYPE"] == 'dotstar': + elif config.settings["devices"][board]["configuration"]["TYPE"] == 'DotStar': boards[board] = devices.DotStar() - elif config.settings["devices"][board]["configuration"]["TYPE"] == 'stripless': + elif config.settings["devices"][board]["configuration"]["TYPE"] == 'Stripless': pass # Initialise DSP