├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── README.md ├── arduino └── ws2812_controller │ ├── ws2812.h │ ├── ws2812_controller.ino │ ├── ws2812_defs.h │ ├── ws2812_dma.c │ ├── ws2812_dma.h │ ├── ws2812_gamma.cpp │ ├── ws2812_gamma.h │ ├── ws2812_i2s.cpp │ └── ws2812_i2s.h ├── images ├── FeatherHuzzah-small.png ├── NodeMCUv3-small.png ├── audio-source.png ├── block-diagram.png ├── breadboard-led-strip.jpg ├── description-cropped.gif ├── description.gif ├── esp8266-block-diagram.png ├── led-effect-demo.gif ├── raspberry-pi-block-diagram.png ├── scroll-effect-demo.gif ├── stereo-enable.png ├── stereo-show.png └── visualization-gui.png └── python ├── Output └── visualize.log ├── lib ├── config.py ├── devices.py ├── dsp.py ├── gamma_table.npy ├── gui.py ├── led.py ├── melbank.py ├── microphone.py ├── qfloatslider.py ├── qrangeslider.py └── settings.ini └── main.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | *.pyc 49 | *.pdf 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2016] [Scott Lawson] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## This is an old project! 4 | 5 | Audio reactive LED software has come on in leaps and bounds since I started working on this project. 6 | I'm currently workong on LedFx, which support multiple devices and better effects. 7 | 8 | [Check out LedFx for the latest and greatest!](https://github.com/ahodges9/LedFx/) 9 | 10 | ## Introduction 11 | 12 | All in one lighting solution for your computer. Mood lighting? Club effects? 13 | 14 | Lighting your room or office should be a simple procedure, with flexibilty to transition between moods. 15 | 16 | Get yourself some LED strips, and you can set this up yourself. Follow the simple steps outlines below to tune your space to a mood that fits you. 17 | 18 | ## Installation 19 | 20 | First of all, download this zip and run main.py using Python. 21 | 22 | [Refer to the original fork for more detailed instructions.](https://github.com/scottlawsonbc/audio-reactive-led-strip) 23 | -------------------------------------------------------------------------------- /arduino/ws2812_controller/ws2812.h: -------------------------------------------------------------------------------- 1 | // ws2812.h 2 | 3 | #ifndef __WS2812_H__ 4 | #define __WS2812_H__ 5 | 6 | // Temporal Dithering 7 | // Dithering preserves color and light when brightness is low. 8 | // Sometimes this can cause undesirable flickering. 9 | // 1 = Disable temporal dithering 10 | // 2, 6, 8 = Enable temporal dithering (larger values = more dithering) 11 | #define WS2812_DITHER_NUM (8) 12 | 13 | #define WS2812_USE_INTERRUPT (0) // not supported yet 14 | 15 | #endif 16 | 17 | // end of file 18 | -------------------------------------------------------------------------------- /arduino/ws2812_controller/ws2812_controller.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define FASTLED_ESP8266_DMA // better control for ESP8266 will output or RX pin requires fork https://github.com/coryking/FastLED 7 | #include "FastLED.h" 8 | 9 | /************ Network Information (CHANGE THESE FOR YOUR SETUP) ************************/ 10 | const char* ssid = "WIFI_SSID"; 11 | const char* password = "WIFI_PASSWORD"; 12 | 13 | const char* sensor_name = "TEST_SENSOR_HOSTNAME"; 14 | const char* ota_password = "OTA_PASSWORD"; 15 | 16 | const bool static_ip = true; 17 | IPAddress ip(192, 168, 1, 112); 18 | IPAddress gateway(192, 168, 1, 1); 19 | IPAddress subnet(255, 255, 255, 0); 20 | 21 | const int udp_port = 7778; 22 | 23 | /*********************************** FastLED Defintions ********************************/ 24 | #define NUM_LEDS 250 25 | #define DATA_PIN 5 26 | //#define CLOCK_PIN 2 27 | #define CHIPSET WS2812B 28 | #define COLOR_ORDER GRB 29 | 30 | /*********************************** Globals *******************************************/ 31 | WiFiUDP port; 32 | CRGB leds[NUM_LEDS]; 33 | 34 | /********************************** Start Setup ****************************************/ 35 | void setup() { 36 | Serial.begin(115200); 37 | 38 | // Setup FastLED 39 | #ifdef CLOCK_PIN 40 | FastLED.addLeds(leds, NUM_LEDS); 41 | #else 42 | FastLED.addLeds(leds, NUM_LEDS); 43 | #endif 44 | 45 | // Setup the wifi connection 46 | setup_wifi(); 47 | 48 | // Setup OTA firmware updates 49 | setup_ota(); 50 | 51 | // Initialize the UDP port 52 | port.begin(udp_port); 53 | } 54 | 55 | void setup_wifi() { 56 | delay(10); 57 | 58 | Serial.println(); 59 | Serial.print("Connecting to "); 60 | Serial.print(ssid); 61 | 62 | if (static_ip) { 63 | WiFi.config(ip, gateway, subnet); 64 | } 65 | 66 | WiFi.hostname(sensor_name); 67 | WiFi.mode(WIFI_STA); 68 | WiFi.begin(ssid, password); 69 | 70 | while (WiFi.status() != WL_CONNECTED) { 71 | delay(500); 72 | Serial.print("."); 73 | } 74 | 75 | Serial.println(""); 76 | Serial.println("WiFi connected!"); 77 | Serial.print("IP address: "); 78 | Serial.println(WiFi.localIP()); 79 | } 80 | 81 | void setup_ota() { 82 | ArduinoOTA.setHostname(sensor_name); 83 | ArduinoOTA.setPassword(ota_password); 84 | 85 | ArduinoOTA.onStart([]() { 86 | Serial.println("Starting"); 87 | }); 88 | ArduinoOTA.onEnd([]() { 89 | Serial.println("\nEnd"); 90 | }); 91 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 92 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 93 | }); 94 | ArduinoOTA.onError([](ota_error_t error) { 95 | Serial.printf("Error[%u]: ", error); 96 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); 97 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); 98 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); 99 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); 100 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); 101 | }); 102 | ArduinoOTA.begin(); 103 | } 104 | 105 | void loop() { 106 | 107 | if (WiFi.status() != WL_CONNECTED) { 108 | delay(1); 109 | Serial.print("WIFI Disconnected. Attempting reconnection."); 110 | setup_wifi(); 111 | return; 112 | } 113 | 114 | ArduinoOTA.handle(); 115 | 116 | // TODO: Hookup either a more elaborate protocol, or a secondary 117 | // communication channel (i.e. mqtt) for functional control. This 118 | // will also give the ability to have some non-reative effects to 119 | // be driven completely locally making them less glitchy. 120 | 121 | // Handle UDP data 122 | int packetSize = port.parsePacket(); 123 | if (packetSize == sizeof(leds)) { 124 | port.read((char*)leds, sizeof(leds)); 125 | FastLED.show(); 126 | } else if (packetSize) { 127 | Serial.printf("Invalid packet size: %u (expected %u)\n", packetSize, sizeof(leds)); 128 | port.flush(); 129 | return; 130 | } 131 | } -------------------------------------------------------------------------------- /arduino/ws2812_controller/ws2812_defs.h: -------------------------------------------------------------------------------- 1 | // w2812_defs.h 2 | // 3 | // contains material from Charles Lohr, but changed by me. 4 | 5 | #ifndef __WS2812_I2S_DEFS_H__ 6 | #define __WS2812_I2S_DEFS_H__ 7 | 8 | #include 9 | 10 | // include file from project folder 11 | #include "ws2812.h" 12 | 13 | // ----------------------------------------------------- 14 | 15 | // Helper macro's 16 | 17 | #define NUM_RGB_BYTES (num_leds * 3) 18 | #define NUM_I2S_PIXEL_BYTES (num_leds * 3 * 4) // (#leds) * (RGB) * (4 i2s bits) 19 | #define NUM_I2S_PIXEL_WORDS (num_leds * 3) 20 | 21 | // ----------------------------------------------------- 22 | 23 | // I2S timing parameters 24 | 25 | // Creates an I2S SR of 93,750 Hz, or 3 MHz Bitclock (.333us/sample) 26 | // Measured on scope : 4 bitclock-ticks every 1200ns --> 300ns bitclock ??? 27 | // 12000000L/(div*bestbck*2) 28 | 29 | #define WS_I2S_BCK (17) 30 | #define WS_I2S_DIV (4) 31 | #define NUM_I2S_ZERO_BYTES (28) // WS2812 LED Treset = 67us (must be >50us) 32 | #define NUM_I2S_ZERO_WORDS (NUM_I2S_ZERO_BYTES / 4) 33 | 34 | // ----------------------------------------------------- 35 | 36 | #ifndef i2c_bbpll 37 | #define i2c_bbpll 0x67 38 | #define i2c_bbpll_en_audio_clock_out 4 39 | #define i2c_bbpll_en_audio_clock_out_msb 7 40 | #define i2c_bbpll_en_audio_clock_out_lsb 7 41 | #define i2c_bbpll_hostid 4 42 | #endif 43 | 44 | // ----------------------------------------------------- 45 | 46 | #define i2c_writeReg_Mask(block, host_id, reg_add, Msb, Lsb, indata) rom_i2c_writeReg_Mask(block, host_id, reg_add, Msb, Lsb, indata) 47 | #define i2c_readReg_Mask(block, host_id, reg_add, Msb, Lsb) rom_i2c_readReg_Mask(block, host_id, reg_add, Msb, Lsb) 48 | #define i2c_writeReg_Mask_def(block, reg_add, indata) i2c_writeReg_Mask(block, block##_hostid, reg_add, reg_add##_msb, reg_add##_lsb, indata) 49 | #define i2c_readReg_Mask_def(block, reg_add) i2c_readReg_Mask(block, block##_hostid, reg_add, reg_add##_msb, reg_add##_lsb) 50 | 51 | // ----------------------------------------------------- 52 | 53 | #ifndef ETS_SLC_INUM 54 | #define ETS_SLC_INUM 1 55 | #endif 56 | 57 | // ----------------------------------------------------- 58 | 59 | // from i2s_reg.h 60 | 61 | #define DR_REG_I2S_BASE (0x60000e00) 62 | 63 | #define I2STXFIFO (DR_REG_I2S_BASE + 0x0000) 64 | #define I2SRXFIFO (DR_REG_I2S_BASE + 0x0004) 65 | #define I2SCONF (DR_REG_I2S_BASE + 0x0008) 66 | #define I2S_BCK_DIV_NUM 0x0000003F 67 | #define I2S_BCK_DIV_NUM_S 22 68 | #define I2S_CLKM_DIV_NUM 0x0000003F 69 | #define I2S_CLKM_DIV_NUM_S 16 70 | #define I2S_BITS_MOD 0x0000000F 71 | #define I2S_BITS_MOD_S 12 72 | #define I2S_RECE_MSB_SHIFT (BIT(11)) 73 | #define I2S_TRANS_MSB_SHIFT (BIT(10)) 74 | #define I2S_I2S_RX_START (BIT(9)) 75 | #define I2S_I2S_TX_START (BIT(8)) 76 | #define I2S_MSB_RIGHT (BIT(7)) 77 | #define I2S_RIGHT_FIRST (BIT(6)) 78 | #define I2S_RECE_SLAVE_MOD (BIT(5)) 79 | #define I2S_TRANS_SLAVE_MOD (BIT(4)) 80 | #define I2S_I2S_RX_FIFO_RESET (BIT(3)) 81 | #define I2S_I2S_TX_FIFO_RESET (BIT(2)) 82 | #define I2S_I2S_RX_RESET (BIT(1)) 83 | #define I2S_I2S_TX_RESET (BIT(0)) 84 | #define I2S_I2S_RESET_MASK 0xf 85 | 86 | #define I2SINT_RAW (DR_REG_I2S_BASE + 0x000c) 87 | #define I2S_I2S_TX_REMPTY_INT_RAW (BIT(5)) 88 | #define I2S_I2S_TX_WFULL_INT_RAW (BIT(4)) 89 | #define I2S_I2S_RX_REMPTY_INT_RAW (BIT(3)) 90 | #define I2S_I2S_RX_WFULL_INT_RAW (BIT(2)) 91 | #define I2S_I2S_TX_PUT_DATA_INT_RAW (BIT(1)) 92 | #define I2S_I2S_RX_TAKE_DATA_INT_RAW (BIT(0)) 93 | 94 | #define I2SINT_ST (DR_REG_I2S_BASE + 0x0010) 95 | #define I2S_I2S_TX_REMPTY_INT_ST (BIT(5)) 96 | #define I2S_I2S_TX_WFULL_INT_ST (BIT(4)) 97 | #define I2S_I2S_RX_REMPTY_INT_ST (BIT(3)) 98 | #define I2S_I2S_RX_WFULL_INT_ST (BIT(2)) 99 | #define I2S_I2S_TX_PUT_DATA_INT_ST (BIT(1)) 100 | #define I2S_I2S_RX_TAKE_DATA_INT_ST (BIT(0)) 101 | 102 | #define I2SINT_ENA (DR_REG_I2S_BASE + 0x0014) 103 | #define I2S_I2S_TX_REMPTY_INT_ENA (BIT(5)) 104 | #define I2S_I2S_TX_WFULL_INT_ENA (BIT(4)) 105 | #define I2S_I2S_RX_REMPTY_INT_ENA (BIT(3)) 106 | #define I2S_I2S_RX_WFULL_INT_ENA (BIT(2)) 107 | #define I2S_I2S_TX_PUT_DATA_INT_ENA (BIT(1)) 108 | #define I2S_I2S_RX_TAKE_DATA_INT_ENA (BIT(0)) 109 | 110 | #define I2SINT_CLR (DR_REG_I2S_BASE + 0x0018) 111 | #define I2S_I2S_TX_REMPTY_INT_CLR (BIT(5)) 112 | #define I2S_I2S_TX_WFULL_INT_CLR (BIT(4)) 113 | #define I2S_I2S_RX_REMPTY_INT_CLR (BIT(3)) 114 | #define I2S_I2S_RX_WFULL_INT_CLR (BIT(2)) 115 | #define I2S_I2S_PUT_DATA_INT_CLR (BIT(1)) 116 | #define I2S_I2S_TAKE_DATA_INT_CLR (BIT(0)) 117 | 118 | #define I2STIMING (DR_REG_I2S_BASE + 0x001c) 119 | #define I2S_TRANS_BCK_IN_INV (BIT(22)) 120 | #define I2S_RECE_DSYNC_SW (BIT(21)) 121 | #define I2S_TRANS_DSYNC_SW (BIT(20)) 122 | #define I2S_RECE_BCK_OUT_DELAY 0x00000003 123 | #define I2S_RECE_BCK_OUT_DELAY_S 18 124 | #define I2S_RECE_WS_OUT_DELAY 0x00000003 125 | #define I2S_RECE_WS_OUT_DELAY_S 16 126 | #define I2S_TRANS_SD_OUT_DELAY 0x00000003 127 | #define I2S_TRANS_SD_OUT_DELAY_S 14 128 | #define I2S_TRANS_WS_OUT_DELAY 0x00000003 129 | #define I2S_TRANS_WS_OUT_DELAY_S 12 130 | #define I2S_TRANS_BCK_OUT_DELAY 0x00000003 131 | #define I2S_TRANS_BCK_OUT_DELAY_S 10 132 | #define I2S_RECE_SD_IN_DELAY 0x00000003 133 | #define I2S_RECE_SD_IN_DELAY_S 8 134 | #define I2S_RECE_WS_IN_DELAY 0x00000003 135 | #define I2S_RECE_WS_IN_DELAY_S 6 136 | #define I2S_RECE_BCK_IN_DELAY 0x00000003 137 | #define I2S_RECE_BCK_IN_DELAY_S 4 138 | #define I2S_TRANS_WS_IN_DELAY 0x00000003 139 | #define I2S_TRANS_WS_IN_DELAY_S 2 140 | #define I2S_TRANS_BCK_IN_DELAY 0x00000003 141 | #define I2S_TRANS_BCK_IN_DELAY_S 0 142 | 143 | #define I2S_FIFO_CONF (DR_REG_I2S_BASE + 0x0020) 144 | #define I2S_I2S_RX_FIFO_MOD 0x00000007 145 | #define I2S_I2S_RX_FIFO_MOD_S 16 146 | #define I2S_I2S_TX_FIFO_MOD 0x00000007 147 | #define I2S_I2S_TX_FIFO_MOD_S 13 148 | #define I2S_I2S_DSCR_EN (BIT(12)) 149 | #define I2S_I2S_TX_DATA_NUM 0x0000003F 150 | #define I2S_I2S_TX_DATA_NUM_S 6 151 | #define I2S_I2S_RX_DATA_NUM 0x0000003F 152 | #define I2S_I2S_RX_DATA_NUM_S 0 153 | 154 | #define I2SRXEOF_NUM (DR_REG_I2S_BASE + 0x0024) 155 | #define I2S_I2S_RX_EOF_NUM 0xFFFFFFFF 156 | #define I2S_I2S_RX_EOF_NUM_S 0 157 | 158 | #define I2SCONF_SIGLE_DATA (DR_REG_I2S_BASE + 0x0028) 159 | #define I2S_I2S_SIGLE_DATA 0xFFFFFFFF 160 | #define I2S_I2S_SIGLE_DATA_S 0 161 | 162 | #define I2SCONF_CHAN (DR_REG_I2S_BASE + 0x002c) 163 | #define I2S_RX_CHAN_MOD 0x00000003 164 | #define I2S_RX_CHAN_MOD_S 3 165 | #define I2S_TX_CHAN_MOD 0x00000007 166 | #define I2S_TX_CHAN_MOD_S 0 167 | 168 | // ----------------------------------------------------- 169 | 170 | #endif 171 | 172 | // end of file 173 | -------------------------------------------------------------------------------- /arduino/ws2812_controller/ws2812_dma.c: -------------------------------------------------------------------------------- 1 | // ws2812_init.c 2 | 3 | // C-based helper function for initilalizing 4 | // the I2S system 5 | 6 | #include 7 | #include "slc_register.h" 8 | #include "user_interface.h" 9 | #include "ws2812_defs.h" 10 | #include "ws2812_dma.h" 11 | 12 | 13 | #if WS2812_USE_INTERRUPT == 1 14 | // for debugging purposes 15 | static volatile uint32_t interrupt_count = 0; 16 | 17 | static void ws2812_isr(void) 18 | { 19 | //clear all intr flags 20 | WRITE_PERI_REG(SLC_INT_CLR, 0xffffffff);//slc_intr_status); 21 | 22 | interrupt_count++; 23 | } 24 | #endif 25 | 26 | 27 | void ws2812_dma(sdio_queue_t *i2s_pixels_queue) 28 | { 29 | // Reset DMA 30 | SET_PERI_REG_MASK(SLC_CONF0, SLC_RXLINK_RST); //|SLC_TXLINK_RST); 31 | CLEAR_PERI_REG_MASK(SLC_CONF0, SLC_RXLINK_RST); //|SLC_TXLINK_RST); 32 | 33 | // Clear DMA int flags 34 | SET_PERI_REG_MASK(SLC_INT_CLR, 0xffffffff); 35 | CLEAR_PERI_REG_MASK(SLC_INT_CLR, 0xffffffff); 36 | 37 | // Enable and configure DMA 38 | CLEAR_PERI_REG_MASK(SLC_CONF0,(SLC_MODE< 6 | #include "ws2812_gamma.h" 7 | 8 | static const uint8_t gamma0[] = { 9 | 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10 | 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 11 | 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 12 | 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 13 | 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 14 | 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 34, 35, 36, 15 | 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 16 | 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 17 | 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 80, 81, 18 | 82, 83, 84, 85, 86, 88, 89, 90, 91, 92, 94, 95, 96, 97, 98, 100, 19 | 101, 102, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115, 117, 118, 119, 121, 20 | 122, 123, 125, 126, 128, 129, 130, 132, 133, 135, 136, 138, 139, 141, 142, 144, 21 | 145, 147, 148, 150, 151, 153, 154, 156, 157, 159, 161, 162, 164, 165, 167, 169, 22 | 170, 172, 173, 175, 177, 178, 180, 182, 183, 185, 187, 189, 190, 192, 194, 196, 23 | 197, 199, 201, 203, 204, 206, 208, 210, 212, 213, 215, 217, 219, 221, 223, 225, 24 | 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 255 }; 25 | 26 | static const uint8_t gamma1[] = { 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 28 | 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 29 | 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 30 | 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 31 | 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 32 | 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 35, 35, 33 | 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 47, 48, 34 | 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, 60, 61, 62, 63, 35 | 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 80, 36 | 81, 82, 84, 85, 86, 87, 88, 89, 91, 92, 93, 94, 95, 97, 98, 99, 37 | 100, 102, 103, 104, 105, 107, 108, 109, 111, 112, 113, 115, 116, 117, 119, 120, 38 | 121, 123, 124, 126, 127, 128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 39 | 145, 146, 148, 149, 151, 152, 154, 155, 157, 158, 160, 162, 163, 165, 166, 168, 40 | 170, 171, 173, 175, 176, 178, 180, 181, 183, 185, 186, 188, 190, 192, 193, 195, 41 | 197, 199, 200, 202, 204, 206, 207, 209, 211, 213, 215, 217, 218, 220, 222, 224, 42 | 226, 228, 230, 232, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }; 43 | 44 | static const uint8_t gamma2[] = { 45 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 46 | 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 47 | 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 48 | 9, 10, 10, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 49 | 16, 17, 17, 18, 18, 19, 19, 20, 21, 21, 22, 22, 23, 24, 24, 25, 50 | 25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 33, 34, 35, 36, 51 | 36, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 48, 49, 52 | 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 53 | 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 54 | 82, 83, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 96, 97, 98, 99, 55 | 101, 102, 103, 104, 106, 107, 108, 110, 111, 112, 114, 115, 116, 118, 119, 120, 56 | 122, 123, 125, 126, 127, 129, 130, 132, 133, 134, 136, 137, 139, 140, 142, 143, 57 | 145, 146, 148, 149, 151, 152, 154, 156, 157, 159, 160, 162, 163, 165, 167, 168, 58 | 170, 172, 173, 175, 177, 178, 180, 182, 183, 185, 187, 188, 190, 192, 194, 195, 59 | 197, 199, 201, 202, 204, 206, 208, 210, 211, 213, 215, 217, 219, 221, 222, 224, 60 | 226, 228, 230, 232, 234, 236, 238, 240, 241, 243, 245, 247, 249, 251, 253, 255 }; 61 | 62 | static const uint8_t gamma3[] = { 63 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 64 | 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 65 | 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 66 | 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 67 | 16, 16, 17, 17, 18, 18, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 68 | 25, 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 69 | 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 70 | 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 71 | 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 77, 78, 79, 80, 72 | 81, 82, 83, 84, 86, 87, 88, 89, 90, 92, 93, 94, 95, 96, 98, 99, 73 | 100, 101, 103, 104, 105, 107, 108, 109, 110, 112, 113, 114, 116, 117, 118, 120, 74 | 121, 123, 124, 125, 127, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143, 75 | 144, 146, 147, 149, 150, 152, 153, 155, 157, 158, 160, 161, 163, 165, 166, 168, 76 | 169, 171, 173, 174, 176, 178, 179, 181, 183, 184, 186, 188, 190, 191, 193, 195, 77 | 197, 198, 200, 202, 204, 205, 207, 209, 211, 213, 214, 216, 218, 220, 222, 224, 78 | 226, 228, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }; 79 | 80 | static const uint8_t gamma4[] = { 81 | 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 82 | 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 83 | 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 84 | 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 85 | 16, 17, 17, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 86 | 25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, 87 | 37, 37, 38, 39, 40, 40, 41, 42, 43, 44, 44, 45, 46, 47, 48, 49, 88 | 50, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 89 | 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 81, 90 | 82, 83, 84, 85, 86, 87, 89, 90, 91, 92, 93, 95, 96, 97, 98, 100, 91 | 101, 102, 103, 105, 106, 107, 108, 110, 111, 112, 114, 115, 116, 118, 119, 120, 92 | 122, 123, 125, 126, 127, 129, 130, 132, 133, 135, 136, 138, 139, 140, 142, 143, 93 | 145, 146, 148, 149, 151, 153, 154, 156, 157, 159, 160, 162, 164, 165, 167, 168, 94 | 170, 172, 173, 175, 177, 178, 180, 182, 183, 185, 187, 188, 190, 192, 194, 195, 95 | 197, 199, 201, 202, 204, 206, 208, 210, 211, 213, 215, 217, 219, 221, 223, 224, 96 | 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 245, 247, 249, 251, 253, 255 }; 97 | 98 | static const uint8_t gamma5[] = { 99 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 100 | 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 101 | 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 102 | 9, 9, 10, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 14, 15, 15, 103 | 16, 16, 17, 17, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 104 | 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 35, 35, 105 | 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 45, 46, 46, 47, 48, 106 | 49, 50, 51, 52, 53, 54, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 107 | 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78, 79, 80, 108 | 81, 82, 83, 85, 86, 87, 88, 89, 90, 92, 93, 94, 95, 97, 98, 99, 109 | 100, 102, 103, 104, 105, 107, 108, 109, 111, 112, 113, 115, 116, 117, 119, 120, 110 | 121, 123, 124, 126, 127, 128, 130, 131, 133, 134, 136, 137, 138, 140, 141, 143, 111 | 144, 146, 147, 149, 151, 152, 154, 155, 157, 158, 160, 161, 163, 165, 166, 168, 112 | 170, 171, 173, 174, 176, 178, 179, 181, 183, 185, 186, 188, 190, 191, 193, 195, 113 | 197, 198, 200, 202, 204, 206, 207, 209, 211, 213, 215, 216, 218, 220, 222, 224, 114 | 226, 228, 230, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }; 115 | 116 | static const uint8_t gamma6[] = { 117 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 118 | 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 119 | 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 120 | 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 16, 121 | 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 122 | 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 33, 33, 34, 35, 36, 123 | 36, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 124 | 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 58, 59, 60, 61, 62, 63, 125 | 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 126 | 81, 83, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 96, 97, 98, 99, 127 | 101, 102, 103, 104, 106, 107, 108, 109, 111, 112, 113, 115, 116, 117, 119, 120, 128 | 122, 123, 124, 126, 127, 129, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 129 | 145, 146, 148, 149, 151, 152, 154, 155, 157, 159, 160, 162, 163, 165, 167, 168, 130 | 170, 171, 173, 175, 176, 178, 180, 181, 183, 185, 186, 188, 190, 192, 193, 195, 131 | 197, 199, 200, 202, 204, 206, 208, 209, 211, 213, 215, 217, 219, 220, 222, 224, 132 | 226, 228, 230, 232, 234, 236, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }; 133 | 134 | static const uint8_t gamma7[] = { 135 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 136 | 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 137 | 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 138 | 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 139 | 16, 16, 17, 17, 18, 18, 19, 19, 20, 21, 21, 22, 22, 23, 23, 24, 140 | 25, 25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 141 | 36, 37, 37, 38, 39, 40, 40, 41, 42, 43, 44, 45, 45, 46, 47, 48, 142 | 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 143 | 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 78, 79, 80, 144 | 81, 82, 83, 84, 86, 87, 88, 89, 90, 91, 93, 94, 95, 96, 98, 99, 145 | 100, 101, 103, 104, 105, 106, 108, 109, 110, 112, 113, 114, 116, 117, 118, 120, 146 | 121, 122, 124, 125, 127, 128, 130, 131, 132, 134, 135, 137, 138, 140, 141, 143, 147 | 144, 146, 147, 149, 150, 152, 153, 155, 156, 158, 160, 161, 163, 164, 166, 168, 148 | 169, 171, 173, 174, 176, 178, 179, 181, 183, 184, 186, 188, 189, 191, 193, 195, 149 | 196, 198, 200, 202, 203, 205, 207, 209, 211, 213, 214, 216, 218, 220, 222, 224, 150 | 226, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }; 151 | 152 | 153 | #if WS2812_DITHER_NUM == 1 154 | const uint8_t *gamma_dither[WS2812_DITHER_NUM] = {gamma0}; 155 | #elif WS2812_DITHER_NUM == 2 156 | const uint8_t *gamma_dither[WS2812_DITHER_NUM] = {gamma0,gamma1}; 157 | #elif WS2812_DITHER_NUM == 4 158 | const uint8_t *gamma_dither[WS2812_DITHER_NUM] = {gamma0,gamma1,gamma2,gamma3}; 159 | #elif WS2812_DITHER_NUM == 8 160 | const uint8_t *gamma_dither[WS2812_DITHER_NUM] = {gamma0,gamma1,gamma2,gamma3,gamma4,gamma5,gamma6,gamma7}; 161 | #else 162 | #error Invalid WS2812_DITHER_NUM value. Allowed values are 1, 2, 4, 8 163 | #endif 164 | 165 | // end of file 166 | -------------------------------------------------------------------------------- /arduino/ws2812_controller/ws2812_gamma.h: -------------------------------------------------------------------------------- 1 | // ws2812_gamma.h 2 | 3 | #ifndef __WS2812_GAMMA_H__ 4 | #define __WS2812_GAMMA_H__ 5 | 6 | #include 7 | #include "ws2812.h" 8 | 9 | extern const uint8_t *gamma_dither[WS2812_DITHER_NUM]; 10 | 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /arduino/ws2812_controller/ws2812_i2s.cpp: -------------------------------------------------------------------------------- 1 | // ws2812_lib.cpp 2 | // 3 | // main library file / contains class implementation 4 | // 5 | // Need to give credits to Charles Lohr (https://github.com/cnlohr due 6 | // to his work on his software for driving ws2812 led-strips using 7 | // the i2s interface of the ESP8266. 8 | // 9 | // This inspired me to create an ESP8266 library for the Arduino IDE 10 | // I've added temporal dithering & gamma-correction 11 | // for a 'more natural' light intensity profile. 12 | // 13 | // No pin-definitions/mapings are required/possible as the library used the I2S 14 | // output-pin. This pin in it's turn is shared with GPIO3 & RXD0 15 | // 16 | 17 | #include 18 | #include 19 | #include "ws2812_i2s.h" 20 | #include "ws2812_defs.h" 21 | #include "ws2812_gamma.h" 22 | 23 | // include C-style header 24 | extern "C" 25 | { 26 | #include "ws2812_dma.h" 27 | }; 28 | 29 | // class constructor 30 | WS2812::WS2812(void) 31 | { 32 | // empty for now 33 | } 34 | 35 | // class de-constructor 36 | WS2812::~WS2812(void) 37 | { 38 | // empty for now 39 | // TODO : should implement switching of DMA 40 | } 41 | 42 | // Init led-string / memory buffers etc. 43 | void WS2812::init(uint16_t _num_leds) 44 | { 45 | uint8_t i; 46 | uint16_t j; 47 | 48 | num_leds = _num_leds; 49 | 50 | // clear zero buffer 51 | for(j=0; j>4) & 0x0f ]; 164 | } 165 | } 166 | 167 | } 168 | 169 | // end of file 170 | -------------------------------------------------------------------------------- /arduino/ws2812_controller/ws2812_i2s.h: -------------------------------------------------------------------------------- 1 | // ws2812_lib.h 2 | 3 | #ifndef __WS2812_I2S_H__ 4 | #define __WS2812_I2S_H__ 5 | 6 | #include 7 | #include "ws2812_defs.h" 8 | 9 | // include C-style header 10 | extern "C" 11 | { 12 | #include "ws2812_dma.h" 13 | }; 14 | 15 | typedef struct 16 | { 17 | uint8_t G; // G,R,B order is determined by WS2812B 18 | uint8_t R; 19 | uint8_t B; 20 | } Pixel_t; 21 | 22 | 23 | class WS2812 24 | { 25 | public: 26 | WS2812(void); 27 | ~WS2812(void); 28 | void init(uint16_t num_leds); 29 | void show(Pixel_t *); 30 | 31 | private: 32 | uint16_t num_leds; 33 | uint32_t *i2s_pixels_buffer[WS2812_DITHER_NUM]; 34 | uint32_t i2s_zeros_buffer[NUM_I2S_ZERO_WORDS]; 35 | sdio_queue_t i2s_zeros_queue[WS2812_DITHER_NUM]; 36 | sdio_queue_t i2s_pixels_queue[WS2812_DITHER_NUM]; 37 | }; 38 | 39 | #endif 40 | 41 | // end of file 42 | -------------------------------------------------------------------------------- /images/FeatherHuzzah-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/FeatherHuzzah-small.png -------------------------------------------------------------------------------- /images/NodeMCUv3-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/NodeMCUv3-small.png -------------------------------------------------------------------------------- /images/audio-source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/audio-source.png -------------------------------------------------------------------------------- /images/block-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/block-diagram.png -------------------------------------------------------------------------------- /images/breadboard-led-strip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/breadboard-led-strip.jpg -------------------------------------------------------------------------------- /images/description-cropped.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/description-cropped.gif -------------------------------------------------------------------------------- /images/description.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/description.gif -------------------------------------------------------------------------------- /images/esp8266-block-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/esp8266-block-diagram.png -------------------------------------------------------------------------------- /images/led-effect-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/led-effect-demo.gif -------------------------------------------------------------------------------- /images/raspberry-pi-block-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/raspberry-pi-block-diagram.png -------------------------------------------------------------------------------- /images/scroll-effect-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/scroll-effect-demo.gif -------------------------------------------------------------------------------- /images/stereo-enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/stereo-enable.png -------------------------------------------------------------------------------- /images/stereo-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/stereo-show.png -------------------------------------------------------------------------------- /images/visualization-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/images/visualization-gui.png -------------------------------------------------------------------------------- /python/lib/config.py: -------------------------------------------------------------------------------- 1 | """Default settings and configuration for audio reactive LED strip""" 2 | from __future__ import print_function 3 | from __future__ import division 4 | import os 5 | 6 | use_defaults = {"configuration": True, # See notes below for detailed explanation 7 | "GUI_opts": False, 8 | "devices": True, 9 | "colors": True, 10 | "gradients": True} 11 | 12 | settings = { # All settings are stored in this dict 13 | 14 | "configuration":{ # Program configuration 15 | 'USE_GUI': True, # Whether to display the GUI 16 | 'DISPLAY_FPS': False, # Whether to print the FPS when running (can reduce performance) 17 | 'MIC_RATE': 48000, # Sampling frequency of the microphone in Hz 18 | 'FPS': 60, # Desired refresh rate of the visualization (frames per second) 19 | 'MIN_FREQUENCY': 20, # Frequencies below this value will be removed during audio processing 20 | 'MAX_FREQUENCY': 18000, # Frequencies above this value will be removed during audio processing 21 | 'MAX_BRIGHTNESS': 250, # Frequencies above this value will be removed during audio processing 22 | 'N_ROLLING_HISTORY': 4, # Number of past audio frames to include in the rolling window 23 | 'MIN_VOLUME_THRESHOLD': 0.001 # No music visualization displayed if recorded audio volume below threshold 24 | #'LOGARITHMIC_SCALING': True, # Scale frequencies logarithmically to match perceived pitch of human ear 25 | }, 26 | 27 | "GUI_opts":{"Graphs":True, # Which parts of the gui to show 28 | "Reactive Effect Buttons":True, 29 | "Non Reactive Effect Buttons":True, 30 | "Frequency Range":True, 31 | "Effect Options":True}, 32 | 33 | # All devices and their respective settings. Indexed by name, call each one what you want. 34 | "devices":{"Desk Strip":{ 35 | "configuration":{"TYPE": "ESP8266", # Device type (see below for all supported boards) 36 | # Required configuration for device. See below for all required keys per device 37 | "AUTO_DETECT": True, # Set this true if you're using windows hotspot to connect (see below for more info) 38 | "MAC_ADDR": "2c-3a-e8-2f-2c-9f", # MAC address of the ESP8266. Only used if AUTO_DETECT is True 39 | "UDP_IP": "192.168.1.208", # IP address of the ESP8266. Must match IP in ws2812_controller.ino 40 | "UDP_PORT": 7778, # Port number used for socket communication between Python and ESP8266 41 | "MAX_BRIGHTNESS": 250, # Max brightness of output (0-255) (my strip sometimes bugs out with high brightness) 42 | # Other configuration 43 | "N_PIXELS": 58, # Number of pixels in the LED strip (must match ESP8266 firmware) 44 | "N_FFT_BINS": 24, # Number of frequency bins to use when transforming audio to frequency domain 45 | "current_effect": "Energy" # Currently selected effect for this board, used as default when program launches 46 | }, 47 | 48 | # Configurable options for this board's effects go in this dictionary. 49 | # Usage: config.settings["devices"][name]["effect_opts"][effect][option] 50 | "effect_opts":{"Energy": {"blur": 1, # Amount of blur to apply 51 | "scale":0.9, # Width of effect on strip 52 | "r_multiplier": 1.0, # How much red 53 | "g_multiplier": 1.0, # How much green 54 | "b_multiplier": 1.0}, # How much blue 55 | "Wave": {"color_wave": "Red", # Colour of moving bit 56 | "color_flash": "White", # Colour of flashy bit 57 | "wipe_len":5, # Initial length of colour bit after beat 58 | "decay": 0.7, # How quickly the flash fades away 59 | "wipe_speed":2}, # Number of pixels added to colour bit every frame 60 | "Spectrum": {"r_multiplier": 1.0, # How much red 61 | "g_multiplier": 1.0, # How much green 62 | "b_multiplier": 1.0}, # How much blue 63 | "Wavelength":{"roll_speed": 0, # How fast (if at all) to cycle colour overlay across strip 64 | "color_mode": "Spectral", # Colour gradient to display 65 | "mirror": False, # Reflect output down centre of strip 66 | "reverse_grad": False, # Flip (LR) gradient 67 | "reverse_roll": False, # Reverse movement of gradient roll 68 | "blur": 3.0, # Amount of blur to apply 69 | "flip_lr":False}, # Flip output left-right 70 | "Scroll": {"lows_color": "Red", # Colour of low frequencies 71 | "mids_color": "Green", # Colour of mid frequencies 72 | "high_color": "Blue", # Colour of high frequencies 73 | "decay": 0.995, # How quickly the colour fades away as it moves 74 | "speed": 1, # Speed of scroll 75 | "r_multiplier": 1.0, # How much red 76 | "g_multiplier": 1.0, # How much green 77 | "b_multiplier": 1.0, # How much blue 78 | "blur": 0.2}, # Amount of blur to apply 79 | "Power": {"color_mode": "Spectral", # Colour gradient to display 80 | "s_count": 20, # Initial number of sparks 81 | "s_color": "White", # Color of sparks 82 | "mirror": True, # Mirror output down central axis 83 | "flip_lr":False}, # Flip output left-right 84 | "Single": {"color": "Purple"}, # Static color to show 85 | "Beat": {"color": "Red", # Colour of beat flash 86 | "decay": 0.7}, # How quickly the flash fades away 87 | "Bars": {"resolution":4, # Number of "bars" 88 | "color_mode":"Spectral", # Multicolour mode to use 89 | "roll_speed":0, # How fast (if at all) to cycle colour colours across strip 90 | "mirror": False, # Mirror down centre of strip 91 | #"reverse_grad": False, # Flip (LR) gradient 92 | "reverse_roll": False, # Reverse movement of gradient roll 93 | "flip_lr":False}, # Flip output left-right 94 | "Gradient": {"color_mode":"Spectral", # Colour gradient to display 95 | "roll_speed": 0, # How fast (if at all) to cycle colour colours across strip 96 | "mirror": False, # Mirror gradient down central axis 97 | "reverse": False}, # Reverse movement of gradient 98 | "Fade": {"color_mode":"Spectral", # Colour gradient to fade through 99 | "roll_speed": 1, # How fast (if at all) to fade through colours 100 | "reverse": False}, # Reverse "direction" of fade (r->g->b or r<-g<-b) 101 | "Calibration":{"r": 100, 102 | "g": 100, 103 | "b": 100} 104 | } 105 | }, 106 | "Main Strip":{ 107 | "configuration":{"TYPE": "ESP8266", # Device type (see below for all supported boards) 108 | # Required configuration for device. See below for all required keys per device 109 | "AUTO_DETECT": True, # Set this true if you're using windows hotspot to connect (see below for more info) 110 | "MAC_ADDR": "5c-cf-7f-f0-8c-f3", # MAC address of the ESP8266. Only used if AUTO_DETECT is True 111 | "UDP_IP": "192.168.1.208", # IP address of the ESP8266. Must match IP in ws2812_controller.ino 112 | "UDP_PORT": 7778, # Port number used for socket communication between Python and ESP8266 113 | "MAX_BRIGHTNESS": 180, # Max brightness of output (0-255) (my strip sometimes bugs out with high brightness) 114 | # Other configuration 115 | "N_PIXELS": 226, # Number of pixels in the LED strip (must match ESP8266 firmware) 116 | "N_FFT_BINS": 24, # Number of frequency bins to use when transforming audio to frequency domain 117 | "current_effect": "Single" # Currently selected effect for this board, used as default when program launches 118 | }, 119 | 120 | # Configurable options for this board's effects go in this dictionary. 121 | # Usage: config.settings["devices"][name]["effect_opts"][effect][option] 122 | "effect_opts":{"Energy": {"blur": 1, # Amount of blur to apply 123 | "scale":0.9, # Width of effect on strip 124 | "r_multiplier": 1.0, # How much red 125 | "g_multiplier": 1.0, # How much green 126 | "b_multiplier": 1.0}, # How much blue 127 | "Wave": {"color_wave": "Red", # Colour of moving bit 128 | "color_flash": "White", # Colour of flashy bit 129 | "wipe_len":5, # Initial length of colour bit after beat 130 | "decay": 0.7, # How quickly the flash fades away 131 | "wipe_speed":2}, # Number of pixels added to colour bit every frame 132 | "Spectrum": {"r_multiplier": 1.0, # How much red 133 | "g_multiplier": 1.0, # How much green 134 | "b_multiplier": 1.0}, # How much blue 135 | "Wavelength":{"roll_speed": 0, # How fast (if at all) to cycle colour overlay across strip 136 | "color_mode": "Spectral", # Colour gradient to display 137 | "mirror": False, # Reflect output down centre of strip 138 | "reverse_grad": False, # Flip (LR) gradient 139 | "reverse_roll": False, # Reverse movement of gradient roll 140 | "blur": 3.0, # Amount of blur to apply 141 | "flip_lr":False}, # Flip output left-right 142 | "Scroll": {"lows_color": "Red", # Colour of low frequencies 143 | "mids_color": "Green", # Colour of mid frequencies 144 | "high_color": "Blue", # Colour of high frequencies 145 | "decay": 0.995, # How quickly the colour fades away as it moves 146 | "speed": 1, # Speed of scroll 147 | "r_multiplier": 1.0, # How much red 148 | "g_multiplier": 1.0, # How much green 149 | "b_multiplier": 1.0, # How much blue 150 | "blur": 0.2}, # Amount of blur to apply 151 | "Power": {"color_mode": "Spectral", # Colour gradient to display 152 | "s_count": 20, # Initial number of sparks 153 | "s_color": "White", # Color of sparks 154 | "mirror": True, # Mirror output down central axis 155 | "flip_lr":False}, # Flip output left-right 156 | "Single": {"color": "Purple"}, # Static color to show 157 | "Beat": {"color": "Red", # Colour of beat flash 158 | "decay": 0.7}, # How quickly the flash fades away 159 | "Bars": {"resolution":4, # Number of "bars" 160 | "color_mode":"Spectral", # Multicolour mode to use 161 | "roll_speed":0, # How fast (if at all) to cycle colour colours across strip 162 | "mirror": False, # Mirror down centre of strip 163 | #"reverse_grad": False, # Flip (LR) gradient 164 | "reverse_roll": False, # Reverse movement of gradient roll 165 | "flip_lr":False}, # Flip output left-right 166 | "Gradient": {"color_mode":"Spectral", # Colour gradient to display 167 | "roll_speed": 0, # How fast (if at all) to cycle colour colours across strip 168 | "mirror": False, # Mirror gradient down central axis 169 | "reverse": False}, # Reverse movement of gradient 170 | "Fade": {"color_mode":"Spectral", # Colour gradient to fade through 171 | "roll_speed": 1, # How fast (if at all) to fade through colours 172 | "reverse": False}, # Reverse "direction" of fade (r->g->b or r<-g<-b) 173 | "Calibration":{"r": 100, 174 | "g": 100, 175 | "b": 100} 176 | } 177 | } 178 | }, 179 | 180 | 181 | 182 | # Collection of different colours in RGB format 183 | "colors":{"Red":(255,0,0), 184 | "Orange":(255,40,0), 185 | "Yellow":(255,255,0), 186 | "Green":(0,255,0), 187 | "Blue":(0,0,255), 188 | "Light blue":(1,247,161), 189 | "Purple":(80,5,252), 190 | "Pink":(255,0,178), 191 | "White":(255,255,255)}, 192 | 193 | # Multicolour gradients. Colours must be in list above 194 | "gradients":{"Spectral" : ["Red", "Orange", "Yellow", "Green", "Light blue", "Blue", "Purple", "Pink"], 195 | "Dancefloor": ["Red", "Pink", "Purple", "Blue"], 196 | "Sunset" : ["Red", "Orange", "Yellow"], 197 | "Ocean" : ["Green", "Light blue", "Blue"], 198 | "Jungle" : ["Green", "Red", "Orange"], 199 | "Sunny" : ["Yellow", "Light blue", "Orange", "Blue"], 200 | "Fruity" : ["Orange", "Blue"], 201 | "Peach" : ["Orange", "Pink"], 202 | "Rust" : ["Orange", "Red"] 203 | } 204 | 205 | } 206 | 207 | 208 | device_req_config = {"Stripless" : None, # duh 209 | "BlinkStick" : None, 210 | "DotStar" : None, 211 | "ESP8266" : {"AUTO_DETECT": ["Auto Detect", 212 | "Automatically detect device on network using MAC address", 213 | "checkbox", 214 | True], 215 | "MAC_ADDR" : ["Mac Address", 216 | "Hardware address of device, used for auto-detection", 217 | "textbox", 218 | "aa-bb-cc-dd-ee-ff"], 219 | "UDP_IP" : ["IP Address", 220 | "IP address of device, used if auto-detection isn't active", 221 | "textbox", 222 | "xxx.xxx.xxx.xxx"], 223 | "UDP_PORT" : ["Port", 224 | "Port used to communicate with device", 225 | "textbox", 226 | "7778"]}, 227 | "RaspberryPi" : {"LED_PIN" : ["LED Pin", 228 | "GPIO pin connected to the LED strip RaspberryPi (must support PWM)", 229 | "textbox", 230 | "10"], 231 | "LED_FREQ_HZ": ["LED Frequency", 232 | "LED signal frequency in Hz", 233 | "textbox", 234 | "800000"], 235 | "LED_DMA" : ["DMA Channel", 236 | "DMA channel used for generating PWM signal", 237 | "textbox", 238 | "5"], 239 | "LED_INVERT" : ["Invert LEDs", 240 | "Set True if using an inverting logic level converter", 241 | "checkbox", 242 | True]}, 243 | "Fadecandy" : {"SERVER" : ["Server Address", 244 | "Address of Fadecandy server", 245 | "textbox", 246 | "localhost:7890"]} 247 | } 248 | 249 | """ 250 | ~~ NOTES ~~ 251 | 252 | [use_defaults] 253 | 254 | For any dicts in this file (config.py), you can add them into the use_defaults 255 | dict to force the program to use these values over any stored in settings.ini 256 | that you would have set using the GUI. At runtime, settings.ini is used to update 257 | the above dicts with custom set values. 258 | 259 | If you're running a headless RPi, you may want to edit settings in this file, then 260 | specify to use the dict you wrote, rather than have the program overwrite from 261 | settings.ini at runtime. You could also run the program with the gui, set the 262 | settings that you want, then disable the gui and the custom settings will still 263 | be loaded. Basically it works as you would expect it to. 264 | 265 | [DEVICE TYPE] 266 | 267 | Device used to control LED strip. 268 | 269 | 'ESP8266' means that you are using an ESP8266 module to control the LED strip 270 | and commands will be sent to the ESP8266 over WiFi. You can have as many of 271 | these as your computer is able to handle. 272 | 273 | 'RaspberryPi' means that you are using a Raspberry Pi as a standalone unit to process 274 | audio input and control the LED strip directly. 275 | 276 | 'BlinkStick' means that a BlinkstickPro is connected to this PC which will be used 277 | to control the leds connected to it. 278 | 279 | 'Fadecandy' means that a Fadecandy server is running on your computer and is connected 280 | via usb to a Fadecandy board connected to LEDs 281 | 282 | 'DotStar' creates an APA102-based output device. LMK if you have any success 283 | getting this to work becuase i have no clue if it will. 284 | 285 | 'Stripless' means that the program will run without sending data to a strip. 286 | Useful for development etc, but doesn't look half as good ;) 287 | 288 | [REQUIRED CONFIGURATION KEYS] 289 | 290 | ===== 'ESP8266' 291 | "AUTO_DETECT" # Set this true if you're using windows hotspot to connect (see below for more info) 292 | "MAC_ADDR" # MAC address of the ESP8266. Only used if AUTO_DETECT is True 293 | "UDP_IP" # IP address of the ESP8266. Must match IP in ws2812_controller.ino 294 | "UDP_PORT" # Port number used for socket communication between Python and ESP8266 295 | ===== 'RaspberryPi' 296 | "LED_PIN" # GPIO pin connected to the LED strip pixels (must support PWM) 297 | "LED_FREQ_HZ" # LED signal frequency in Hz (usually 800kHz) 298 | "LED_DMA" # DMA channel used for generating PWM signal (try 5) 299 | "BRIGHTNESS" # Brightness of LED strip between 0 and 255 300 | "LED_INVERT" # Set True if using an inverting logic level converter 301 | ===== 'BlinkStick' 302 | No required configuration keys 303 | ===== 'Fadecandy' 304 | "SERVER" # Address of Fadecandy server. (usually 'localhost:7890') 305 | ===== 'DotStar' 306 | No required configuration keys 307 | ===== 'Stripless' 308 | No required configuration keys (heh) 309 | 310 | [AUTO_DETECT] 311 | 312 | Set to true if the ip address of the device changes. This is the case if it's connecting 313 | through windows hotspot, for instance. If so, give the mac address of the device. This 314 | allows windows to look for the device's IP using "arp -a" and finding the matching 315 | mac address. I haven't tested this on Linux or macOS. 316 | 317 | [FPS] 318 | 319 | FPS indicates the desired refresh rate, or frames-per-second, of the audio 320 | visualization. The actual refresh rate may be lower if the computer cannot keep 321 | up with desired FPS value. 322 | 323 | Higher framerates improve "responsiveness" and reduce the latency of the 324 | visualization but are more computationally expensive. 325 | 326 | Low framerates are less computationally expensive, but the visualization may 327 | appear "sluggish" or out of sync with the audio being played if it is too low. 328 | 329 | The FPS should not exceed the maximum refresh rate of the LED strip, which 330 | depends on how long the LED strip is. 331 | 332 | [N_FFT_BINS] 333 | 334 | Fast Fourier transforms are used to transform time-domain audio data to the 335 | frequency domain. The frequencies present in the audio signal are assigned 336 | to their respective frequency bins. This value indicates the number of 337 | frequency bins to use. 338 | 339 | A small number of bins reduces the frequency resolution of the visualization 340 | but improves amplitude resolution. The opposite is true when using a large 341 | number of bins. More bins is not always better! 342 | 343 | There is no point using more bins than there are pixels on the LED strip. 344 | """ 345 | 346 | for board in settings["devices"]: 347 | if settings["devices"][board]["configuration"]["TYPE"] == 'ESP8266': 348 | settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False 349 | # Set to False because the firmware handles gamma correction + dither 350 | elif settings["devices"][board]["configuration"]["TYPE"] == 'RaspberryPi': 351 | settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = True 352 | # Set to True because Raspberry Pi doesn't use hardware dithering 353 | elif settings["devices"][board]["configuration"]["TYPE"] == 'BlinkStick': 354 | settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = True 355 | elif settings["devices"][board]["configuration"]["TYPE"] == 'DotStar': 356 | settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False 357 | elif settings["devices"][board]["configuration"]["TYPE"] == 'Fadecandy': 358 | settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False 359 | elif settings["devices"][board]["configuration"]["TYPE"] == 'Stripless': 360 | settings["devices"][board]["configuration"]["SOFTWARE_GAMMA_CORRECTION"] = False 361 | else: 362 | raise ValueError("Invalid device selected. Device {} not known.".format(settings["devices"][board]["configuration"]["TYPE"])) 363 | settings["devices"][board]["effect_opts"]["Power"]["s_count"] = settings["devices"][board]["configuration"]["N_PIXELS"]//6 364 | # Cheeky lil fix in case the user sets an odd number of LEDs 365 | if settings["devices"][board]["configuration"]["N_PIXELS"] % 2: 366 | settings["devices"][board]["configuration"]["N_PIXELS"] -= 1 367 | 368 | # Ignore these 369 | # settings["configuration"]['_max_led_FPS'] = int(((settings["configuration"]["N_PIXELS"] * 30e-6) + 50e-6)**-1.0) 370 | 371 | -------------------------------------------------------------------------------- /python/lib/devices.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | import lib.config as config 4 | 5 | _GAMMA_TABLE = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 6 | 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 7 | 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 8 | 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 9 | 18, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 10 | 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 35, 11 | 35, 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 12 | 47, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 13 | 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 14 | 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 15 | 89, 91, 92, 93, 94, 95, 97, 98, 99, 100, 102, 103, 104, 16 | 105, 107, 108, 109, 111, 112, 113, 115, 116, 117, 119, 17 | 120, 121, 123, 124, 126, 127, 128, 130, 131, 133, 134, 18 | 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, 19 | 152, 154, 155, 157, 158, 160, 162, 163, 165, 166, 168, 20 | 170, 171, 173, 175, 176, 178, 180, 181, 183, 185, 186, 21 | 188, 190, 192, 193, 195, 197, 199, 200, 202, 204, 206, 22 | 207, 209, 211, 213, 215, 217, 218, 220, 222, 224, 226, 23 | 228, 230, 232, 233, 235, 237, 239, 241, 243, 245, 247, 24 | 249, 251, 253, 255] 25 | _GAMMA_TABLE = np.array(_GAMMA_TABLE) 26 | 27 | class LEDController: 28 | """Base class for interfacing with hardware LED strip controllers 29 | To add support for another hardware device, simply inherit this class 30 | and implement the show() method. 31 | Example usage: 32 | import numpy as np 33 | N_pixels = 60 34 | pixels = np.random.random(size=(3, N_pixels)) 35 | device = LEDController() 36 | device.show(pixels) 37 | """ 38 | 39 | def __init__(self): 40 | pass 41 | 42 | def show(self, pixels): 43 | """Set LED pixels to the values given in the array 44 | This function accepts an array of RGB pixel values (pixels) 45 | and displays them on the LEDs. To add support for another 46 | hardware device, you should create a class that inherits from 47 | this class, and then implement this method. 48 | Parameters 49 | ---------- 50 | pixels: numpy.ndarray 51 | 2D array containing RGB pixel values for each of the LEDs. 52 | The shape of the array is (3, n_pixels), where n_pixels is the 53 | number of LEDs that the device has. 54 | The array is formatted as shown below. There are three rows 55 | (axis 0) which represent the red, green, and blue color channels. 56 | Each column (axis 1) contains the red, green, and blue color values 57 | for a single pixel: 58 | np.array([ [r0, ..., rN], [g0, ..., gN], [b0, ..., bN]]) 59 | Each value brightness value is an integer between 0 and 255. 60 | Returns 61 | ------- 62 | None 63 | """ 64 | raise NotImplementedError('Show() was not implemented') 65 | 66 | def test(self, n_pixels): 67 | pixels = np.zeros((3, n_pixels)) 68 | pixels[0][0] = 255 69 | pixels[1][1] = 255 70 | pixels[2][2] = 255 71 | print('Starting LED strip test.') 72 | print('Press CTRL+C to stop the test at any time.') 73 | print('You should see a scrolling red, green, and blue pixel.') 74 | while True: 75 | self.show(pixels) 76 | pixels = np.roll(pixels, 1, axis=1) 77 | time.sleep(0.2) 78 | 79 | 80 | class ESP8266(LEDController): 81 | def __init__(self, auto_detect=False, 82 | mac_addr="aa-bb-cc-dd-ee-ff", 83 | ip='192.168.0.150', 84 | port=7778): 85 | """Initialize object for communicating with as ESP8266 86 | Parameters 87 | ---------- 88 | auto_detect: bool, optional 89 | Automatically search for and find devices on windows hotspot 90 | with given mac addresses. Windows hotspot resets the IP 91 | addresses of any devices on reset, meaning the IP of the 92 | ESP8266 changes every time you turn on the hotspot. This 93 | will find the IP address of the devices for you. 94 | mac_addr: str, optional 95 | The MAC address of the ESP8266 on the network. Only used if 96 | auto-detect is used 97 | ip: str, optional 98 | The IP address of the ESP8266 on the network. This must exactly 99 | match the IP address of your ESP8266 device, unless using 100 | the auto-detect feature. 101 | port: int, optional 102 | The port number to use when sending data to the ESP8266. This 103 | must exactly match the port number in the ESP8266's firmware. 104 | """ 105 | import socket 106 | self._mac_addr = mac_addr 107 | self._ip = ip 108 | self._port = port 109 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 110 | if auto_detect: 111 | self.detect() 112 | 113 | def detect(self): 114 | from subprocess import check_output 115 | from time import sleep 116 | """ Uses "arp -a" to find esp8266 on windows hotspot""" 117 | # Find the audio strip automagically 118 | ip_addr = False 119 | while not ip_addr: 120 | arp_out = check_output(['arp', '-a']).splitlines() 121 | for i in arp_out: 122 | if self._mac_addr in str(i): 123 | ip_addr = i.split()[0].decode("utf-8") 124 | break 125 | else: 126 | print("Device not found at physical address {}, retrying in 1s".format(self._mac_addr)) 127 | sleep(1) 128 | print("Found device {}, with IP address {}".format(self._mac_addr, ip_addr)) 129 | self._ip = ip_addr 130 | 131 | def show(self, pixels): 132 | """Sends UDP packets to ESP8266 to update LED strip values 133 | The ESP8266 will receive and decode the packets to determine what values 134 | to display on the LED strip. The communication protocol supports LED strips 135 | with a maximum of 256 LEDs. 136 | The packet encoding scheme is: 137 | |i|r|g|b| 138 | where 139 | i (0 to 255): Index of LED to change (zero-based) 140 | r (0 to 255): Red value of LED 141 | g (0 to 255): Green value of LED 142 | b (0 to 255): Blue value of LED 143 | """ 144 | message = pixels.T.clip(0, config.settings["configuration"]["MAX_BRIGHTNESS"]).astype(np.uint8).ravel().tostring() 145 | self._sock.sendto(message, (self._ip, self._port)) 146 | 147 | 148 | class FadeCandy(LEDController): 149 | def __init__(self, server='localhost:7890'): 150 | """Initializes object for communicating with a FadeCandy device 151 | Parameters 152 | ---------- 153 | server: str, optional 154 | FadeCandy server used to communicate with the FadeCandy device. 155 | """ 156 | try: 157 | import audioled.opc 158 | except ImportError as e: 159 | print('Unable to import audioled.opc library') 160 | print('You can install this library with `pip install opc`') 161 | raise e 162 | self.client = audioled.opc.Client(server) 163 | if self.client.can_connect(): 164 | print('Successfully connected to FadeCandy server.') 165 | else: 166 | print('Could not connect to FadeCandy server.') 167 | print('Ensure that fcserver is running and try again.') 168 | 169 | def show(self, pixels): 170 | self.client.put_pixels(pixels.T.clip(0, 255).astype(int).tolist()) 171 | 172 | 173 | class BlinkStick(LEDController): 174 | def __init__(self): 175 | """Initializes a BlinkStick controller""" 176 | try: 177 | from blinkstick import blinkstick 178 | except ImportError as e: 179 | print('Unable to import the blinkstick library') 180 | print('You can install this library with `pip install blinkstick`') 181 | raise e 182 | self.stick = blinkstick.find_first() 183 | 184 | def show(self, pixels): 185 | """Writes new LED values to the Blinkstick. 186 | This function updates the LED strip with new values. 187 | """ 188 | # Truncate values and cast to integer 189 | n_pixels = pixels.shape[1] 190 | pixels = pixels.clip(0, 255).astype(int) 191 | pixels = _GAMMA_TABLE[pixels] 192 | # Read the rgb values 193 | r = pixels[0][:].astype(int) 194 | g = pixels[1][:].astype(int) 195 | b = pixels[2][:].astype(int) 196 | 197 | # Create array in which we will store the led states 198 | newstrip = [None] * (n_pixels * 3) 199 | 200 | for i in range(n_pixels): 201 | # Blinkstick uses GRB format 202 | newstrip[i * 3] = g[i] 203 | newstrip[i * 3 + 1] = r[i] 204 | newstrip[i * 3 + 2] = b[i] 205 | # Send the data to the blinkstick 206 | self.stick.set_led_data(0, newstrip) 207 | 208 | 209 | class RaspberryPi(LEDController): 210 | def __init__(self, n_pixels, pin=18, invert_logic=False, 211 | freq=800000, dma=5): 212 | """Creates a Raspberry Pi output device 213 | Parameters 214 | ---------- 215 | n_pixels: int 216 | Number of LED strip pixels 217 | pin: int, optional 218 | GPIO pin used to drive the LED strip (must be a PWM pin). 219 | Pin 18 can be used on the Raspberry Pi 2. 220 | invert_logic: bool, optional 221 | Whether or not to invert the driving logic. 222 | Set this to True if you are using an inverting logic level 223 | converter, otherwise set to False. 224 | freq: int, optional 225 | LED strip protocol frequency (Hz). For ws2812 this is 800000. 226 | dma: int, optional 227 | DMA (direct memory access) channel used to drive PWM signals. 228 | If you aren't sure, try 5. 229 | """ 230 | try: 231 | import neopixel 232 | except ImportError as e: 233 | url = 'learn.adafruit.com/neopixels-on-raspberry-pi/software' 234 | print('Could not import the neopixel library') 235 | print('For installation instructions, see {}'.format(url)) 236 | raise e 237 | self.strip = neopixel.Adafruit_NeoPixel(n_pixels, pin, freq, dma, 238 | invert_logic, 255) 239 | self.strip.begin() 240 | 241 | def show(self, pixels): 242 | """Writes new LED values to the Raspberry Pi's LED strip 243 | Raspberry Pi uses the rpi_ws281x to control the LED strip directly. 244 | This function updates the LED strip with new values. 245 | """ 246 | # Truncate values and cast to integer 247 | n_pixels = pixels.shape[1] 248 | pixels = pixels.clip(0, 255).astype(int) 249 | # Optional gamma correction 250 | pixels = _GAMMA_TABLE[pixels] 251 | # Encode 24-bit LED values in 32 bit integers 252 | r = np.left_shift(pixels[0][:].astype(int), 8) 253 | g = np.left_shift(pixels[1][:].astype(int), 16) 254 | b = pixels[2][:].astype(int) 255 | rgb = np.bitwise_or(np.bitwise_or(r, g), b) 256 | # Update the pixels 257 | for i in range(n_pixels): 258 | self.strip._led_data[i] = rgb[i] 259 | self.strip.show() 260 | 261 | 262 | class DotStar(LEDController): 263 | def __init__(self, pixels, brightness=31): 264 | """Creates an APA102-based output device 265 | Parameters 266 | ---------- 267 | pixels: int 268 | Number of LED strip pixels 269 | brightness: int, optional 270 | Global brightness 271 | """ 272 | try: 273 | import apa102 274 | except ImportError as e: 275 | url = 'https://github.com/tinue/APA102_Pi' 276 | print('Could not import the apa102 library') 277 | print('For installation instructions, see {}'.format(url)) 278 | raise e 279 | self.strip = apa102.APA102(numLEDs=pixels, globalBrightness=brightness) # Initialize the strip 280 | led_data = np.array(self.strip.leds, dtype=np.uint8) 281 | # memoryview preserving the first 8 bits of LED frames (w/ global brightness) 282 | self.strip.leds = led_data.data 283 | # 2D view of led_data 284 | self.led_data = led_data.reshape((pixels, 4)) # or (-1, 4) 285 | 286 | def show(self, pixels): 287 | bgr = [2,1,0] 288 | self.led_data[0:,1:4] = pixels[bgr].T.clip(0,255) 289 | self.strip.show() -------------------------------------------------------------------------------- /python/lib/dsp.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import numpy as np 3 | import lib.config as config 4 | import lib.melbank as melbank 5 | 6 | 7 | class ExpFilter: 8 | """Simple exponential smoothing filter""" 9 | def __init__(self, val=0.0, alpha_decay=0.5, alpha_rise=0.5): 10 | """Small rise / decay factors = more smoothing""" 11 | assert 0.0 < alpha_decay < 1.0, 'Invalid decay smoothing factor' 12 | assert 0.0 < alpha_rise < 1.0, 'Invalid rise smoothing factor' 13 | self.alpha_decay = alpha_decay 14 | self.alpha_rise = alpha_rise 15 | self.value = val 16 | 17 | def update(self, value): 18 | if isinstance(self.value, (list, np.ndarray, tuple)): 19 | alpha = value - self.value 20 | alpha[alpha > 0.0] = self.alpha_rise 21 | alpha[alpha <= 0.0] = self.alpha_decay 22 | else: 23 | alpha = self.alpha_rise if value > self.value else self.alpha_decay 24 | self.value = alpha * value + (1.0 - alpha) * self.value 25 | return self.value 26 | 27 | -------------------------------------------------------------------------------- /python/lib/gamma_table.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/not-matt/audio-reactive-led-strip/e686d9d6d4daf77842d956991b7960ecede90aa9/python/lib/gamma_table.npy -------------------------------------------------------------------------------- /python/lib/gui.py: -------------------------------------------------------------------------------- 1 | #from __future__ import print_function 2 | #from __future__ import division 3 | #from scipy.ndimage.filters import gaussian_filter1d 4 | #from collections import deque 5 | #import time 6 | #import sys 7 | import numpy as np 8 | import lib.config as config 9 | #import microphone 10 | #import dsp 11 | #import led 12 | #import random 13 | 14 | from lib.qrangeslider import QRangeSlider 15 | from lib.qfloatslider import QFloatSlider 16 | import pyqtgraph as pg 17 | from PyQt5.QtCore import * 18 | from PyQt5.QtWidgets import * 19 | 20 | class GUI(QMainWindow): 21 | def __init__(self): 22 | super().__init__() 23 | self.settings = QSettings('settings.ini', QSettings.IniFormat) 24 | self.settings.setFallbacksEnabled(False) # File only, no fallback to registry or or. 25 | self.initUI() 26 | 27 | def hideGraphs(self): 28 | print("Blah") 29 | 30 | def hideOpts(self): 31 | print("Bleh") 32 | 33 | def config.settings["configuration"]["configDialogue"](self): 34 | self.d = QDialog(None, Qt.WindowSystemMenuHint | Qt.WindowCloseButtonHint) 35 | b1 = QPushButton("ok",self.d) 36 | b1.move(50,50) 37 | self.d.setWindowTitle("Dialog") 38 | self.d.setWindowModality(Qt.ApplicationModal) 39 | self.d.show() 40 | 41 | def initUI(self): 42 | # ==================================== Set up window and wrapping layout 43 | self.setWindowTitle("Visualization") 44 | wrapper = QVBoxLayout() 45 | 46 | # ======================================================= Set up toolbar 47 | #toolbar_hideGraphs.setShortcut('Ctrl+H') 48 | toolbar_hideGraphs = QAction('GUI Properties', self) 49 | toolbar_hideGraphs.triggered.connect(self.config.settings["configuration"]["configDialogue"]) 50 | toolbar_hideOpts = QAction('Hide Opts', self) 51 | toolbar_hideOpts.triggered.connect(self.hideOpts) 52 | 53 | self.toolbar = self.addToolBar('Toolbar') 54 | self.toolbar.addAction(toolbar_hideGraphs) 55 | self.toolbar.addAction(toolbar_hideOpts) 56 | 57 | # ========================================== Set up FPS and error labels 58 | labels_layout = QHBoxLayout() 59 | self.label_error = QLabel("") 60 | self.label_fps = QLabel("") 61 | self.label_latency = QLabel("") 62 | self.label_fps.setAlignment(Qt.AlignRight | Qt.AlignVCenter) 63 | self.label_latency.setAlignment(Qt.AlignRight | Qt.AlignVCenter) 64 | labels_layout.addWidget(self.label_error) 65 | labels_layout.addStretch() 66 | labels_layout.addWidget(self.label_latency) 67 | labels_layout.addWidget(self.label_fps) 68 | 69 | # ================================================== Set up graph layout 70 | graph_view = pg.GraphicsView() 71 | graph_layout = pg.GraphicsLayout(border=(100,100,100)) 72 | graph_view.setCentralItem(graph_layout) 73 | # Mel filterbank plot 74 | fft_plot = graph_layout.addPlot(title='Filterbank Output', colspan=3) 75 | fft_plot.setRange(yRange=[-0.1, 1.2]) 76 | fft_plot.disableAutoRange(axis=pg.ViewBox.YAxis) 77 | x_data = np.array(range(1, config.settings["configuration"]["N_FFT_BINS"] + 1)) 78 | self.mel_curve = pg.PlotCurveItem() 79 | self.mel_curve.setData(x=x_data, y=x_data*0) 80 | fft_plot.addItem(self.mel_curve) 81 | # Visualization plot 82 | graph_layout.nextRow() 83 | led_plot = graph_layout.addPlot(title='Visualization Output', colspan=3) 84 | led_plot.setRange(yRange=[-5, 260]) 85 | led_plot.disableAutoRange(axis=pg.ViewBox.YAxis) 86 | # Pen for each of the color channel curves 87 | r_pen = pg.mkPen((255, 30, 30, 200), width=4) 88 | g_pen = pg.mkPen((30, 255, 30, 200), width=4) 89 | b_pen = pg.mkPen((30, 30, 255, 200), width=4) 90 | # Color channel curves 91 | self.r_curve = pg.PlotCurveItem(pen=r_pen) 92 | self.g_curve = pg.PlotCurveItem(pen=g_pen) 93 | self.b_curve = pg.PlotCurveItem(pen=b_pen) 94 | # Define x data 95 | x_data = np.array(range(1, config.settings["configuration"]["N_PIXELS"] + 1)) 96 | self.r_curve.setData(x=x_data, y=x_data*0) 97 | self.g_curve.setData(x=x_data, y=x_data*0) 98 | self.b_curve.setData(x=x_data, y=x_data*0) 99 | # Add curves to plot 100 | led_plot.addItem(self.r_curve) 101 | led_plot.addItem(self.g_curve) 102 | led_plot.addItem(self.b_curve) 103 | 104 | # ================================================= Set up button layout 105 | label_reactive = QLabel("Audio Reactive Effects") 106 | label_non_reactive = QLabel("Non Reactive Effects") 107 | reactive_button_grid = QGridLayout() 108 | non_reactive_button_grid = QGridLayout() 109 | buttons = {} 110 | connecting_funcs = {} 111 | grid_width = 4 112 | i = 0 113 | j = 0 114 | k = 0 115 | l = 0 116 | # Dynamically layout reactive_buttons and connect them to the visualisation effects 117 | def connect_generator(effect): 118 | def func(): 119 | visualizer.current_effect = effect 120 | buttons[effect].setDown(True) 121 | func.__name__ = effect 122 | return func 123 | # Where the magic happens 124 | for effect in visualizer.effects: 125 | if not effect in visualizer.non_reactive_effects: 126 | connecting_funcs[effect] = connect_generator(effect) 127 | buttons[effect] = QPushButton(effect) 128 | buttons[effect].clicked.connect(connecting_funcs[effect]) 129 | reactive_button_grid.addWidget(buttons[effect], j, i) 130 | i += 1 131 | if i % grid_width == 0: 132 | i = 0 133 | j += 1 134 | else: 135 | connecting_funcs[effect] = connect_generator(effect) 136 | buttons[effect] = QPushButton(effect) 137 | buttons[effect].clicked.connect(connecting_funcs[effect]) 138 | non_reactive_button_grid.addWidget(buttons[effect], l, k) 139 | k += 1 140 | if k % grid_width == 0: 141 | k = 0 142 | l += 1 143 | 144 | # ============================================== Set up frequency slider 145 | # Frequency range label 146 | label_slider = QLabel("Frequency Range") 147 | # Frequency slider 148 | def freq_slider_change(tick): 149 | minf = freq_slider.tickValue(0)**2.0 * (config.settings["configuration"]["MIC_RATE"] / 2.0) 150 | maxf = freq_slider.tickValue(1)**2.0 * (config.settings["configuration"]["MIC_RATE"] / 2.0) 151 | t = 'Frequency range: {:.0f} - {:.0f} Hz'.format(minf, maxf) 152 | freq_label.setText(t) 153 | config.settings["configuration"]["MIN_FREQUENCY"] = minf 154 | config.settings["configuration"]["MAX_FREQUENCY"] = maxf 155 | dsp.create_mel_bank() 156 | def set_freq_min(): 157 | config.settings["configuration"]["MIN_FREQUENCY"] = freq_slider.start() 158 | dsp.create_mel_bank() 159 | def set_freq_max(): 160 | config.settings["configuration"]["MAX_FREQUENCY"] = freq_slider.end() 161 | dsp.create_mel_bank() 162 | freq_slider = QRangeSlider() 163 | freq_slider.show() 164 | freq_slider.setMin(0) 165 | freq_slider.setMax(20000) 166 | freq_slider.setRange(config.settings["configuration"]["MIN_FREQUENCY"], config.settings["configuration"]["MAX_FREQUENCY"]) 167 | freq_slider.setBackgroundStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #222, stop:1 #333);') 168 | freq_slider.setSpanStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #282, stop:1 #393);') 169 | freq_slider.setDrawValues(True) 170 | freq_slider.endValueChanged.connect(set_freq_max) 171 | freq_slider.startValueChanged.connect(set_freq_min) 172 | freq_slider.setStyleSheet(""" 173 | QRangeSlider * { 174 | border: 0px; 175 | padding: 0px; 176 | } 177 | QRangeSlider > QSplitter::handle { 178 | background: #fff; 179 | } 180 | QRangeSlider > QSplitter::handle:vertical { 181 | height: 3px; 182 | } 183 | QRangeSlider > QSplitter::handle:pressed { 184 | background: #ca5; 185 | } 186 | """) 187 | 188 | # ============================================ Set up option tabs layout 189 | label_options = QLabel("Effect Options") 190 | opts_tabs = QTabWidget() 191 | # Dynamically set up tabs 192 | tabs = {} 193 | grid_layouts = {} 194 | self.grid_layout_widgets = {} 195 | options = visualizer.effect_opts.keys() 196 | for effect in visualizer.effects: 197 | # Make the tab 198 | self.grid_layout_widgets[effect] = {} 199 | tabs[effect] = QWidget() 200 | grid_layouts[effect] = QGridLayout() 201 | tabs[effect].setLayout(grid_layouts[effect]) 202 | opts_tabs.addTab(tabs[effect],effect) 203 | # These functions make functions for the dynamic ui generation 204 | # YOU WANT-A DYNAMIC I GIVE-A YOU DYNAMIC! 205 | def gen_slider_valuechanger(effect, key): 206 | def func(): 207 | visualizer.effect_opts[effect][key] = self.grid_layout_widgets[effect][key].value() 208 | return func 209 | def gen_float_slider_valuechanger(effect, key): 210 | def func(): 211 | visualizer.effect_opts[effect][key] = self.grid_layout_widgets[effect][key].slider_value 212 | return func 213 | def gen_combobox_valuechanger(effect, key): 214 | def func(): 215 | visualizer.effect_opts[effect][key] = self.grid_layout_widgets[effect][key].currentText() 216 | return func 217 | def gen_checkbox_valuechanger(effect, key): 218 | def func(): 219 | visualizer.effect_opts[effect][key] = self.grid_layout_widgets[effect][key].isChecked() 220 | return func 221 | # Dynamically generate ui for settings 222 | if effect in visualizer.dynamic_effects_config.settings["configuration"]["config:"] 223 | i = 0 224 | connecting_funcs[effect] = {} 225 | for key, label, ui_element, *opts in visualizer.dynamic_effects_config.settings["configuration"]["config[effect"]]: 226 | if opts: # neatest way ^^^^^ i could think of to unpack and handle an unknown number of opts (if any) 227 | opts = opts[0] 228 | if ui_element == "slider": 229 | connecting_funcs[effect][key] = gen_slider_valuechanger(effect, key) 230 | self.grid_layout_widgets[effect][key] = QSlider(Qt.Horizontal) 231 | self.grid_layout_widgets[effect][key].setMinimum(opts[0]) 232 | self.grid_layout_widgets[effect][key].setMaximum(opts[1]) 233 | self.grid_layout_widgets[effect][key].setValue(visualizer.effect_opts[effect][key]) 234 | self.grid_layout_widgets[effect][key].valueChanged.connect( 235 | connecting_funcs[effect][key]) 236 | elif ui_element == "float_slider": 237 | connecting_funcs[effect][key] = gen_float_slider_valuechanger(effect, key) 238 | self.grid_layout_widgets[effect][key] = QFloatSlider(*opts, visualizer.effect_opts[effect][key]) 239 | self.grid_layout_widgets[effect][key].setValue(visualizer.effect_opts[effect][key]) 240 | self.grid_layout_widgets[effect][key].valueChanged.connect( 241 | connecting_funcs[effect][key]) 242 | elif ui_element == "dropdown": 243 | connecting_funcs[effect][key] = gen_combobox_valuechanger(effect, key) 244 | self.grid_layout_widgets[effect][key] = QComboBox() 245 | self.grid_layout_widgets[effect][key].addItems(opts) 246 | self.grid_layout_widgets[effect][key].currentIndexChanged.connect( 247 | connecting_funcs[effect][key]) 248 | elif ui_element == "checkbox": 249 | connecting_funcs[effect][key] = gen_checkbox_valuechanger(effect, key) 250 | self.grid_layout_widgets[effect][key] = QCheckBox() 251 | self.grid_layout_widgets[effect][key].setCheckState(visualizer.effect_opts[effect][key]) 252 | self.grid_layout_widgets[effect][key].stateChanged.connect( 253 | connecting_funcs[effect][key]) 254 | grid_layouts[effect].addWidget(QLabel(label),i,0) 255 | grid_layouts[effect].addWidget(self.grid_layout_widgets[effect][key],i,1) 256 | i += 1 257 | #visualizer.effect_settings[effect] 258 | else: 259 | grid_layouts[effect].addWidget(QLabel("No customisable options for this effect :("),0,0) 260 | 261 | 262 | 263 | # ============================================= Add layouts into wrapper 264 | self.setCentralWidget(QWidget(self)) 265 | self.centralWidget().setLayout(wrapper) 266 | wrapper.addLayout(labels_layout) 267 | wrapper.addWidget(graph_view) 268 | wrapper.addWidget(label_reactive) 269 | wrapper.addLayout(reactive_button_grid) 270 | wrapper.addWidget(label_non_reactive) 271 | wrapper.addLayout(non_reactive_button_grid) 272 | wrapper.addWidget(label_slider) 273 | wrapper.addWidget(freq_slider) 274 | wrapper.addWidget(label_options) 275 | wrapper.addWidget(opts_tabs) 276 | #self.show() 277 | -------------------------------------------------------------------------------- /python/lib/led.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import division 3 | 4 | 5 | import platform 6 | import numpy as np 7 | import lib.config as config 8 | 9 | def detect_esp8266(): 10 | """ Uses "arp -a" to find esp8266 on windows hotspot""" 11 | # Find the audio strip automagically 12 | ip_addr = False 13 | while not ip_addr: 14 | arp_out = check_output(['arp', '-a']).splitlines() 15 | for i in arp_out: 16 | if config.settings["configuration"]["MAC_ADDR"] in str(i): 17 | ip_addr = i.split()[0].decode("utf-8") 18 | break 19 | else: 20 | print("Device not found at physical address {}, retrying in 1s".format(config.settings["configuration"]["MAC_ADDR"])) 21 | sleep(1) 22 | print("Found device {}, with IP address {}".format(config.settings["configuration"]["MAC_ADDR"], ip_addr)) 23 | config.settings["configuration"]["UDP_IP"] = ip_addr 24 | 25 | # ESP8266 uses WiFi communication 26 | if config.settings["configuration"]["DEVICE"] == 'esp8266': 27 | import socket 28 | from subprocess import check_output 29 | from time import sleep 30 | _sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 31 | _sock.settimeout(0.005) 32 | # Raspberry Pi controls the LED strip directly 33 | elif config.settings["configuration"]["DEVICE"] == 'pi': 34 | import neopixel 35 | strip = neopixel.Adafruit_NeoPixel(config.settings["configuration"]["N_PIXELS"], config.settings["configuration"]["LED_PIN"], 36 | config.settings["configuration"]["LED_FREQ_HZ"], config.settings["configuration"]["LED_DMA"], 37 | config.settings["configuration"]["LED_INVERT"], config.settings["configuration"]["BRIGHTNESS"]) 38 | strip.begin() 39 | elif config.settings["configuration"]["DEVICE"] == 'blinkstick': 40 | from blinkstick import blinkstick 41 | import signal 42 | import sys 43 | #Will turn all leds off when invoked. 44 | def signal_handler(signal, frame): 45 | all_off = [0]*(config.settings["configuration"]["N_PIXELS"]*3) 46 | stick.set_led_data(0, all_off) 47 | sys.exit(0) 48 | 49 | stick = blinkstick.find_first() 50 | # Create a listener that turns the leds off when the program terminates 51 | signal.signal(signal.SIGTERM, signal_handler) 52 | signal.signal(signal.SIGINT, signal_handler) 53 | 54 | _gamma = np.load(config.settings["configuration"]["GAMMA_TABLE_PATH"]) 55 | """Gamma lookup table used for nonlinear brightness correction""" 56 | 57 | _prev_pixels = np.tile(253, (3, config.settings["configuration"]["N_PIXELS"])) 58 | """Pixel values that were most recently displayed on the LED strip""" 59 | 60 | pixels = np.tile(1, (3, config.settings["configuration"]["N_PIXELS"])) 61 | """Pixel values for the LED strip""" 62 | 63 | _is_python_2 = int(platform.python_version_tuple()[0]) == 2 64 | 65 | def _update_esp8266(): 66 | """Sends UDP packets to ESP8266 to update LED strip values 67 | 68 | The ESP8266 will receive and decode the packets to determine what values 69 | to display on the LED strip. The communication protocol supports LED strips 70 | with a maximum of 256 LEDs. 71 | 72 | The packet encoding scheme is: 73 | |i|r|g|b| 74 | where 75 | i (0 to 255): Index of LED to change (zero-based) 76 | r (0 to 255): Red value of LED 77 | g (0 to 255): Green value of LED 78 | b (0 to 255): Blue value of LED 79 | """ 80 | global pixels, _prev_pixels 81 | # Truncate values and cast to integer 82 | pixels = np.clip(pixels, 0, config.settings["configuration"]["MAX_BRIGHTNESS"]).astype(int) 83 | # Optionally apply gamma correction 84 | p = _gamma[pixels] if config.settings["configuration"]["SOFTWARE_GAMMA_CORRECTION"] else np.copy(pixels) 85 | MAX_PIXELS_PER_PACKET = 255 86 | # Pixel indices 87 | idx = range(pixels.shape[1]) 88 | #idx = [i for i in idx if not np.array_equal(p[:, i], _prev_pixels[:, i])] 89 | n_packets = len(idx) // MAX_PIXELS_PER_PACKET + 1 90 | idx = np.array_split(idx, n_packets) 91 | 92 | m = [] 93 | for i in range(config.settings["configuration"]["N_PIXELS"]): 94 | #m.append(i) # Index of pixel to change 95 | m.append(pixels[0][i]) # Pixel red value 96 | m.append(pixels[1][i]) # Pixel green value 97 | m.append(pixels[2][i]) # Pixel blue value 98 | m = bytes(m) 99 | _sock.sendto(m, (config.settings["configuration"]["UDP_IP"], config.settings["configuration"]["UDP_PORT"])) 100 | 101 | # for packet_indices in idx: 102 | # m = '' if _is_python_2 else [] 103 | # for i in packet_indices: 104 | # if _is_python_2: 105 | # m += chr(i) + chr(pixels[0][i]) + chr(pixels[1][i]) + chr(pixels[2][i]) 106 | # else: 107 | # m.append(i) # Index of pixel to change 108 | # m.append(pixels[0][i]) # Pixel red value 109 | # m.append(pixels[1][i]) # Pixel green value 110 | # m.append(pixels[2][i]) # Pixel blue value 111 | # m = m if _is_python_2 else bytes(m) 112 | # _sock.sendto(m, (config.settings["configuration"]["UDP_IP"], config.settings["configuration"]["UDP_PORT"])) 113 | _prev_pixels = np.copy(pixels) 114 | 115 | 116 | def _update_pi(): 117 | """Writes new LED values to the Raspberry Pi's LED strip 118 | 119 | Raspberry Pi uses the rpi_ws281x to control the LED strip directly. 120 | This function updates the LED strip with new values. 121 | """ 122 | global pixels, _prev_pixels 123 | # Truncate values and cast to integer 124 | pixels = np.clip(pixels, 0, 255).astype(int) 125 | # Optional gamma correction 126 | p = _gamma[pixels] if config.settings["configuration"]["SOFTWARE_GAMMA_CORRECTION"] else np.copy(pixels) 127 | # Encode 24-bit LED values in 32 bit integers 128 | r = np.left_shift(p[0][:].astype(int), 8) 129 | g = np.left_shift(p[1][:].astype(int), 16) 130 | b = p[2][:].astype(int) 131 | rgb = np.bitwise_or(np.bitwise_or(r, g), b) 132 | # Update the pixels 133 | for i in range(config.settings["configuration"]["N_PIXELS"]): 134 | # Ignore pixels if they haven't changed (saves bandwidth) 135 | if np.array_equal(p[:, i], _prev_pixels[:, i]): 136 | continue 137 | strip._led_data[i] = rgb[i] 138 | _prev_pixels = np.copy(p) 139 | strip.show() 140 | 141 | def _update_blinkstick(): 142 | """Writes new LED values to the Blinkstick. 143 | This function updates the LED strip with new values. 144 | """ 145 | global pixels 146 | 147 | # Truncate values and cast to integer 148 | pixels = np.clip(pixels, 0, 250).astype(int) 149 | # Optional gamma correction 150 | p = _gamma[pixels] if config.settings["configuration"]["SOFTWARE_GAMMA_CORRECTION"] else np.copy(pixels) 151 | # Read the rgb values 152 | r = p[0][:].astype(int) 153 | g = p[1][:].astype(int) 154 | b = p[2][:].astype(int) 155 | 156 | #create array in which we will store the led states 157 | newstrip = [None]*(config.settings["configuration"]["N_PIXELS"]*3) 158 | 159 | for i in range(config.settings["configuration"]["N_PIXELS"]): 160 | # blinkstick uses GRB format 161 | newstrip[i*3] = g[i] 162 | newstrip[i*3+1] = r[i] 163 | newstrip[i*3+2] = b[i] 164 | #send the data to the blinkstick 165 | stick.set_led_data(0, newstrip) 166 | 167 | 168 | def update(): 169 | """Updates the LED strip values""" 170 | if config.settings["configuration"]["DEVICE"] == 'esp8266': 171 | _update_esp8266() 172 | elif config.settings["configuration"]["DEVICE"] == 'pi': 173 | _update_pi() 174 | elif config.settings["configuration"]["DEVICE"] == 'blinkstick': 175 | _update_blinkstick() 176 | elif config.settings["configuration"]["DEVICE"] == 'stripless': 177 | pass 178 | 179 | # Execute this file to run a LED strand test 180 | # If everything is working, you should see a red, green, and blue pixel scroll 181 | # across the LED strip continously 182 | if __name__.endswith('__main__'): 183 | import time 184 | # Turn all pixels off 185 | pixels *= 0 186 | pixels[0, 0] = 255 # Set 1st pixel red 187 | pixels[1, 1] = 255 # Set 2nd pixel green 188 | pixels[2, 2] = 255 # Set 3rd pixel blue 189 | print('Starting LED strand test') 190 | while True: 191 | pixels = np.roll(pixels, 1, axis=1) 192 | update() 193 | time.sleep(.1) 194 | -------------------------------------------------------------------------------- /python/lib/melbank.py: -------------------------------------------------------------------------------- 1 | """This module implements a Mel Filter Bank. 2 | In other words it is a filter bank with triangular shaped bands 3 | arnged on the mel frequency scale. 4 | An example ist shown in the following figure: 5 | .. plot:: 6 | from pylab import plt 7 | import melbank 8 | f1, f2 = 1000, 8000 9 | melmat, (melfreq, fftfreq) = melbank.compute_melmat(6, f1, f2, num_fft_bands=4097) 10 | fig, ax = plt.subplots(figsize=(8, 3)) 11 | ax.plot(fftfreq, melmat.T) 12 | ax.grid(True) 13 | ax.set_ylabel('Weight') 14 | ax.set_xlabel('Frequency / Hz') 15 | ax.set_xlim((f1, f2)) 16 | ax2 = ax.twiny() 17 | ax2.xaxis.set_ticks_position('top') 18 | ax2.set_xlim((f1, f2)) 19 | ax2.xaxis.set_ticks(melbank.mel_to_hertz(melfreq)) 20 | ax2.xaxis.set_ticklabels(['{:.0f}'.format(mf) for mf in melfreq]) 21 | ax2.set_xlabel('Frequency / mel') 22 | plt.tight_layout() 23 | fig, ax = plt.subplots() 24 | ax.matshow(melmat) 25 | plt.axis('equal') 26 | plt.axis('tight') 27 | plt.title('Mel Matrix') 28 | plt.tight_layout() 29 | Functions 30 | --------- 31 | """ 32 | 33 | from numpy import abs, append, arange, insert, linspace, log10, round, zeros 34 | from math import log 35 | 36 | 37 | def hertz_to_mel(freq): 38 | """Returns mel-frequency from linear frequency input. 39 | Parameter 40 | --------- 41 | freq : scalar or ndarray 42 | Frequency value or array in Hz. 43 | Returns 44 | ------- 45 | mel : scalar or ndarray 46 | Mel-frequency value or ndarray in Mel 47 | """ 48 | #return 2595.0 * log10(1 + (freq / 700.0)) 49 | return 3340.0 * log(1 + (freq / 250.0), 9) 50 | 51 | 52 | def mel_to_hertz(mel): 53 | """Returns frequency from mel-frequency input. 54 | Parameter 55 | --------- 56 | mel : scalar or ndarray 57 | Mel-frequency value or ndarray in Mel 58 | Returns 59 | ------- 60 | freq : scalar or ndarray 61 | Frequency value or array in Hz. 62 | """ 63 | #return 700.0 * (10**(mel / 2595.0)) - 700.0 64 | return 250.0 * (9**(mel / 3340.0)) - 250.0 65 | 66 | 67 | def melfrequencies_mel_filterbank(num_bands, freq_min, freq_max, num_fft_bands): 68 | """Returns centerfrequencies and band edges for a mel filter bank 69 | Parameters 70 | ---------- 71 | num_bands : int 72 | Number of mel bands. 73 | freq_min : scalar 74 | Minimum frequency for the first band. 75 | freq_max : scalar 76 | Maximum frequency for the last band. 77 | num_fft_bands : int 78 | Number of fft bands. 79 | Returns 80 | ------- 81 | center_frequencies_mel : ndarray 82 | lower_edges_mel : ndarray 83 | upper_edges_mel : ndarray 84 | """ 85 | 86 | mel_max = hertz_to_mel(freq_max) 87 | mel_min = hertz_to_mel(freq_min) 88 | delta_mel = abs(mel_max - mel_min) / (num_bands + 1.0) 89 | frequencies_mel = mel_min + delta_mel * arange(0, num_bands + 2) 90 | lower_edges_mel = frequencies_mel[:-2] 91 | upper_edges_mel = frequencies_mel[2:] 92 | center_frequencies_mel = frequencies_mel[1:-1] 93 | return center_frequencies_mel, lower_edges_mel, upper_edges_mel 94 | 95 | 96 | def compute_melmat(num_mel_bands=12, freq_min=64, freq_max=8000, 97 | num_fft_bands=513, sample_rate=16000): 98 | """Returns tranformation matrix for mel spectrum. 99 | Parameters 100 | ---------- 101 | num_mel_bands : int 102 | Number of mel bands. Number of rows in melmat. 103 | Default: 24 104 | freq_min : scalar 105 | Minimum frequency for the first band. 106 | Default: 64 107 | freq_max : scalar 108 | Maximum frequency for the last band. 109 | Default: 8000 110 | num_fft_bands : int 111 | Number of fft-frequenc bands. This ist NFFT/2+1 ! 112 | number of columns in melmat. 113 | Default: 513 (this means NFFT=1024) 114 | sample_rate : scalar 115 | Sample rate for the signals that will be used. 116 | Default: 44100 117 | Returns 118 | ------- 119 | melmat : ndarray 120 | Transformation matrix for the mel spectrum. 121 | Use this with fft spectra of num_fft_bands_bands length 122 | and multiply the spectrum with the melmat 123 | this will tranform your fft-spectrum 124 | to a mel-spectrum. 125 | frequencies : tuple (ndarray , ndarray ) 126 | Center frequencies of the mel bands, center frequencies of fft spectrum. 127 | """ 128 | center_frequencies_mel, lower_edges_mel, upper_edges_mel = \ 129 | melfrequencies_mel_filterbank( 130 | num_mel_bands, 131 | freq_min, 132 | freq_max, 133 | num_fft_bands 134 | ) 135 | 136 | center_frequencies_hz = mel_to_hertz(center_frequencies_mel) 137 | lower_edges_hz = mel_to_hertz(lower_edges_mel) 138 | upper_edges_hz = mel_to_hertz(upper_edges_mel) 139 | freqs = linspace(0.0, sample_rate / 2.0, num_fft_bands) 140 | melmat = zeros((num_mel_bands, num_fft_bands)) 141 | 142 | for imelband, (center, lower, upper) in enumerate(zip( 143 | center_frequencies_hz, lower_edges_hz, upper_edges_hz)): 144 | 145 | left_slope = (freqs >= lower) == (freqs <= center) 146 | melmat[imelband, left_slope] = ( 147 | (freqs[left_slope] - lower) / (center - lower) 148 | ) 149 | 150 | right_slope = (freqs >= center) == (freqs <= upper) 151 | melmat[imelband, right_slope] = ( 152 | (upper - freqs[right_slope]) / (upper - center) 153 | ) 154 | return melmat, (center_frequencies_mel, freqs) 155 | -------------------------------------------------------------------------------- /python/lib/microphone.py: -------------------------------------------------------------------------------- 1 | import time 2 | import numpy as np 3 | import pyaudio 4 | import lib.config as config 5 | 6 | 7 | def start_stream(callback): 8 | p = pyaudio.PyAudio() 9 | frames_per_buffer = int(config.settings["configuration"]["MIC_RATE"] / config.settings["configuration"]["FPS"]) 10 | stream = p.open(format=pyaudio.paInt16, 11 | channels=1, 12 | rate=config.settings["configuration"]["MIC_RATE"], 13 | input=True, 14 | frames_per_buffer=frames_per_buffer) 15 | overflows = 0 16 | prev_ovf_time = time.time() 17 | while True: 18 | try: 19 | y = np.fromstring(stream.read(frames_per_buffer), dtype=np.int16) 20 | y = y.astype(np.float32) 21 | callback(y) 22 | except IOError: 23 | overflows += 1 24 | if time.time() > prev_ovf_time + 1: 25 | prev_ovf_time = time.time() 26 | if config.settings["configuration"]["USE_GUI"]: 27 | gui.label_error.setText('Audio buffer has overflowed {} times'.format(overflows)) 28 | else: 29 | print('Audio buffer has overflowed {} times'.format(overflows)) 30 | stream.stop_stream() 31 | stream.close() 32 | p.terminate() 33 | -------------------------------------------------------------------------------- /python/lib/qfloatslider.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore, QtGui, QtWidgets 2 | 3 | __all__ = ['QFloatSlider'] 4 | 5 | 6 | class QFloatSlider(QtWidgets.QSlider): 7 | """ 8 | Subclass of QtWidgets.QSlider 9 | Horizontal slider giving floating point values. 10 | Usage: QFloatSlider(min, max, step, default) 11 | where min = minimum value of slider 12 | max = maximum value of slider 13 | step = interval between values. Must be a factor of (max-min) 14 | default = default (starting) value of slider 15 | """ 16 | def __init__(self, min_value, max_value, step, default): 17 | super().__init__(QtCore.Qt.Horizontal) 18 | self.precision = 0.0001 19 | self.min_value = min_value 20 | self.max_value = max_value 21 | self.step = step 22 | self.default = default 23 | self.quotient, self.remainder = self._float_divmod(\ 24 | self.max_value-self.min_value, self.step) 25 | if self.remainder: 26 | raise ValueError("{} does not fit evenly between {} and {}"\ 27 | .format(step, min_value, max_value)) 28 | super().setMinimum(0) 29 | super().setMaximum(self.quotient) 30 | super().setSingleStep(1) 31 | super().setValue(self._float_to_int(self.default)) 32 | super().valueChanged.connect(self._value_handler) 33 | #self.slider_value = 2.0 34 | 35 | def setValue(self, value): 36 | super().setValue(self._float_to_int(value)) 37 | 38 | # This is mostly disgusting python i hate floating points >:( 39 | def _float_divmod(self,a,b): 40 | """ 41 | Basically the divmod function but it works for floats (try 0.3 % 0.1 smh) 42 | Returns the quotient, and a remainder. 43 | """ 44 | a = abs(a) 45 | b = abs(b) 46 | n = 1 47 | while True: 48 | c = a - b 49 | c = abs(c) 50 | if c < self.precision: 51 | return (n, 0) 52 | elif c > a: 53 | return (n-1, a) 54 | a = c 55 | n += 1 56 | 57 | def _float_to_int(self, a): 58 | return int(round(a/self.step)) 59 | 60 | def _int_to_float(self, a): 61 | return self.min_value+a*self.step 62 | 63 | def _value_handler(self): 64 | self.slider_value = self._int_to_float(super().value()) 65 | -------------------------------------------------------------------------------- /python/lib/qrangeslider.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | from PyQt5 import QtCore, QtGui, QtWidgets 3 | 4 | __all__ = ['QRangeSlider'] 5 | 6 | DEFAULT_CSS = """ 7 | QRangeSlider * { 8 | border: 0px; 9 | padding: 0px; 10 | } 11 | QRangeSlider #Head { 12 | background: #222; 13 | } 14 | QRangeSlider #Span { 15 | background: #393; 16 | } 17 | QRangeSlider #Span:active { 18 | background: #282; 19 | } 20 | QRangeSlider #Tail { 21 | background: #222; 22 | } 23 | QRangeSlider > QSplitter::handle { 24 | background: #393; 25 | } 26 | QRangeSlider > QSplitter::handle:vertical { 27 | height: 4px; 28 | } 29 | QRangeSlider > QSplitter::handle:pressed { 30 | background: #ca5; 31 | } 32 | """ 33 | 34 | def scale(val, src, dst): 35 | return int(((val - src[0]) / float(src[1]-src[0])) * (dst[1]-dst[0]) + dst[0]) 36 | 37 | 38 | class Ui_Form(object): 39 | def setupUi(self, Form): 40 | Form.setObjectName("QRangeSlider") 41 | Form.resize(300, 30) 42 | Form.setStyleSheet(DEFAULT_CSS) 43 | self.gridLayout = QtWidgets.QGridLayout(Form) 44 | self.gridLayout.setContentsMargins(0, 0, 0, 0) 45 | self.gridLayout.setSpacing(0) 46 | self.gridLayout.setObjectName("gridLayout") 47 | self._splitter = QtWidgets.QSplitter(Form) 48 | self._splitter.setMinimumSize(QtCore.QSize(0, 0)) 49 | self._splitter.setMaximumSize(QtCore.QSize(16777215, 16777215)) 50 | self._splitter.setOrientation(QtCore.Qt.Horizontal) 51 | self._splitter.setObjectName("splitter") 52 | self._head = QtWidgets.QGroupBox(self._splitter) 53 | self._head.setTitle("") 54 | self._head.setObjectName("Head") 55 | self._handle = QtWidgets.QGroupBox(self._splitter) 56 | self._handle.setTitle("") 57 | self._handle.setObjectName("Span") 58 | self._tail = QtWidgets.QGroupBox(self._splitter) 59 | self._tail.setTitle("") 60 | self._tail.setObjectName("Tail") 61 | self.gridLayout.addWidget(self._splitter, 0, 0, 1, 1) 62 | self.retranslateUi(Form) 63 | QtCore.QMetaObject.connectSlotsByName(Form) 64 | 65 | def retranslateUi(self, Form): 66 | _translate = QtCore.QCoreApplication.translate 67 | Form.setWindowTitle(_translate("QRangeSlider", "QRangeSlider")) 68 | 69 | 70 | class Element(QtWidgets.QGroupBox): 71 | def __init__(self, parent, main): 72 | super(Element, self).__init__(parent) 73 | self.main = main 74 | 75 | def setStyleSheet(self, style): 76 | self.parent().setStyleSheet(style) 77 | 78 | def textColor(self): 79 | return getattr(self, '__textColor', QtGui.QColor(125, 125, 125)) 80 | 81 | def setTextColor(self, color): 82 | if type(color) == tuple and len(color) == 3: 83 | color = QtGui.QColor(color[0], color[1], color[2]) 84 | elif type(color) == int: 85 | color = QtGui.QColor(color, color, color) 86 | setattr(self, '__textColor', color) 87 | 88 | def paintEvent(self, event): 89 | qp = QtGui.QPainter() 90 | qp.begin(self) 91 | if self.main.drawValues(): 92 | self.drawText(event, qp) 93 | qp.end() 94 | 95 | 96 | class Head(Element): 97 | def __init__(self, parent, main): 98 | super(Head, self).__init__(parent, main) 99 | 100 | def drawText(self, event, qp): 101 | qp.setPen(self.textColor()) 102 | qp.setFont(QtGui.QFont('Arial', 10)) 103 | qp.drawText(event.rect(), QtCore.Qt.AlignLeft, str(self.main.min())) 104 | 105 | 106 | class Tail(Element): 107 | def __init__(self, parent, main): 108 | super(Tail, self).__init__(parent, main) 109 | 110 | def drawText(self, event, qp): 111 | qp.setPen(self.textColor()) 112 | qp.setFont(QtGui.QFont('Arial', 10)) 113 | qp.drawText(event.rect(), QtCore.Qt.AlignRight, str(self.main.max())) 114 | 115 | 116 | class Handle(Element): 117 | def __init__(self, parent, main): 118 | super(Handle, self).__init__(parent, main) 119 | 120 | def drawText(self, event, qp): 121 | qp.setPen(self.textColor()) 122 | qp.setFont(QtGui.QFont('Arial', 10)) 123 | qp.drawText(event.rect(), QtCore.Qt.AlignLeft, str(self.main.start())) 124 | qp.drawText(event.rect(), QtCore.Qt.AlignRight, str(self.main.end())) 125 | 126 | def mouseMoveEvent(self, event): 127 | event.accept() 128 | mx = event.globalX() 129 | _mx = getattr(self, '__mx', None) 130 | if not _mx: 131 | setattr(self, '__mx', mx) 132 | dx = 0 133 | else: 134 | dx = mx - _mx 135 | setattr(self, '__mx', mx) 136 | if dx == 0: 137 | event.ignore() 138 | return 139 | elif dx > 0: 140 | dx = 1 141 | elif dx < 0: 142 | dx = -1 143 | s = self.main.start() + dx 144 | e = self.main.end() + dx 145 | if s >= self.main.min() and e <= self.main.max(): 146 | self.main.setRange(s, e) 147 | 148 | 149 | class QRangeSlider(QtWidgets.QWidget, Ui_Form): 150 | endValueChanged = QtCore.pyqtSignal(int) 151 | maxValueChanged = QtCore.pyqtSignal(int) 152 | minValueChanged = QtCore.pyqtSignal(int) 153 | startValueChanged = QtCore.pyqtSignal(int) 154 | minValueChanged = QtCore.pyqtSignal(int) 155 | maxValueChanged = QtCore.pyqtSignal(int) 156 | startValueChanged = QtCore.pyqtSignal(int) 157 | endValueChanged = QtCore.pyqtSignal(int) 158 | 159 | _SPLIT_START = 1 160 | _SPLIT_END = 2 161 | 162 | def __init__(self, parent=None): 163 | super(QRangeSlider, self).__init__(parent) 164 | self.setupUi(self) 165 | self.setMouseTracking(False) 166 | self._splitter.splitterMoved.connect(self._handleMoveSplitter) 167 | self._head_layout = QtWidgets.QHBoxLayout() 168 | self._head_layout.setSpacing(0) 169 | self._head_layout.setContentsMargins(0, 0, 0, 0) 170 | self._head.setLayout(self._head_layout) 171 | self.head = Head(self._head, main=self) 172 | self._head_layout.addWidget(self.head) 173 | self._handle_layout = QtWidgets.QHBoxLayout() 174 | self._handle_layout.setSpacing(0) 175 | self._handle_layout.setContentsMargins(0, 0, 0, 0) 176 | self._handle.setLayout(self._handle_layout) 177 | self.handle = Handle(self._handle, main=self) 178 | self.handle.setTextColor((150, 255, 150)) 179 | self._handle_layout.addWidget(self.handle) 180 | self._tail_layout = QtWidgets.QHBoxLayout() 181 | self._tail_layout.setSpacing(0) 182 | self._tail_layout.setContentsMargins(0, 0, 0, 0) 183 | self._tail.setLayout(self._tail_layout) 184 | self.tail = Tail(self._tail, main=self) 185 | self._tail_layout.addWidget(self.tail) 186 | self.setMin(0) 187 | self.setMax(99) 188 | self.setStart(0) 189 | self.setEnd(99) 190 | self.setDrawValues(True) 191 | 192 | def min(self): 193 | return getattr(self, '__min', None) 194 | 195 | def max(self): 196 | return getattr(self, '__max', None) 197 | 198 | def setMin(self, value): 199 | setattr(self, '__min', value) 200 | self.minValueChanged.emit(value) 201 | 202 | def setMax(self, value): 203 | setattr(self, '__max', value) 204 | self.maxValueChanged.emit(value) 205 | 206 | def start(self): 207 | return getattr(self, '__start', None) 208 | 209 | def end(self): 210 | return getattr(self, '__end', None) 211 | 212 | def _setStart(self, value): 213 | setattr(self, '__start', value) 214 | self.startValueChanged.emit(value) 215 | 216 | def setStart(self, value): 217 | v = self._valueToPos(value) 218 | self._splitter.splitterMoved.disconnect() 219 | self._splitter.moveSplitter(v, self._SPLIT_START) 220 | self._splitter.splitterMoved.connect(self._handleMoveSplitter) 221 | self._setStart(value) 222 | 223 | def _setEnd(self, value): 224 | setattr(self, '__end', value) 225 | self.endValueChanged.emit(value) 226 | 227 | def setEnd(self, value): 228 | v = self._valueToPos(value) 229 | self._splitter.splitterMoved.disconnect() 230 | self._splitter.moveSplitter(v, self._SPLIT_END) 231 | self._splitter.splitterMoved.connect(self._handleMoveSplitter) 232 | self._setEnd(value) 233 | 234 | def drawValues(self): 235 | return getattr(self, '__drawValues', None) 236 | 237 | def setDrawValues(self, draw): 238 | setattr(self, '__drawValues', draw) 239 | 240 | def getRange(self): 241 | return (self.start(), self.end()) 242 | 243 | def setRange(self, start, end): 244 | self.setStart(start) 245 | self.setEnd(end) 246 | 247 | def keyPressEvent(self, event): 248 | key = event.key() 249 | if key == QtCore.Qt.Key_Left: 250 | s = self.start()-1 251 | e = self.end()-1 252 | elif key == QtCore.Qt.Key_Right: 253 | s = self.start()+1 254 | e = self.end()+1 255 | else: 256 | event.ignore() 257 | return 258 | event.accept() 259 | if s >= self.min() and e <= self.max(): 260 | self.setRange(s, e) 261 | 262 | def setBackgroundStyle(self, style): 263 | self._tail.setStyleSheet(style) 264 | self._head.setStyleSheet(style) 265 | 266 | def setSpanStyle(self, style): 267 | self._handle.setStyleSheet(style) 268 | 269 | def _valueToPos(self, value): 270 | return scale(value, (self.min(), self.max()), (0, self.width())) 271 | 272 | def _posToValue(self, xpos): 273 | return scale(xpos, (0, self.width()), (self.min(), self.max())) 274 | 275 | def _handleMoveSplitter(self, xpos, index): 276 | hw = self._splitter.handleWidth() 277 | def _lockWidth(widget): 278 | width = widget.size().width() 279 | widget.setMinimumWidth(width) 280 | widget.setMaximumWidth(width) 281 | def _unlockWidth(widget): 282 | widget.setMinimumWidth(0) 283 | widget.setMaximumWidth(16777215) 284 | v = self._posToValue(xpos) 285 | if index == self._SPLIT_START: 286 | _lockWidth(self._tail) 287 | if v >= self.end(): 288 | return 289 | offset = -20 290 | w = xpos + offset 291 | self._setStart(v) 292 | elif index == self._SPLIT_END: 293 | _lockWidth(self._head) 294 | if v <= self.start(): 295 | return 296 | offset = -40 297 | w = self.width() - xpos + offset 298 | self._setEnd(v) 299 | _unlockWidth(self._tail) 300 | _unlockWidth(self._head) 301 | _unlockWidth(self._handle) 302 | 303 | if __name__ == '__main__': 304 | 305 | app = QtWidgets.QApplication(sys.argv) 306 | rs = QRangeSlider() 307 | rs.show() 308 | rs.setRange(15, 35) 309 | rs.setBackgroundStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #222, stop:1 #333);') 310 | rs.handle.setStyleSheet('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #282, stop:1 #393);') 311 | app.exec_() 312 | -------------------------------------------------------------------------------- /python/lib/settings.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | 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)" 3 | 4 | [MainWindow] 5 | 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)" 6 | 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) 7 | --------------------------------------------------------------------------------