From 6c24926b2fbc54a6b1808c534aa5690f82451b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien?= Date: Fri, 14 Feb 2020 12:32:36 -0500 Subject: [PATCH 01/15] Updated submodules cloning instructions --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 54b535f5..f64f5c99 100644 --- a/README.md +++ b/README.md @@ -314,4 +314,10 @@ See squeezlite command line, but keys options are - LINKALL (mandatory) - NO_FAAD unless you want to us faad, which currently overloads the CPU - TREMOR_ONLY (mandatory) +- When initially cloning the repo, make sure you do it recursively. For example: + - git clone --recursive https://github.com/sle118/squeezelite-esp32.git +- If you have already cloned the repository and you are getting compile errors on one of the submodules (e.g. telnet), run the following git command in the root of the repository location + - git submodule update --init --recursive + + From 11f2b0a5638142b9cea30173e2c82564830a05f3 Mon Sep 17 00:00:00 2001 From: Christian Herzog Date: Sun, 16 Feb 2020 16:59:32 +0100 Subject: [PATCH 02/15] fix test css/js --- components/wifi-manager/index.html | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/components/wifi-manager/index.html b/components/wifi-manager/index.html index 040b19c7..07a894db 100644 --- a/components/wifi-manager/index.html +++ b/components/wifi-manager/index.html @@ -4,18 +4,16 @@ - - + - - - - + + + From ed224f6b7193dbf190cb2a491ad930c7caabddff Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 16 Feb 2020 22:56:08 -0800 Subject: [PATCH 03/15] Add spectrum visualizer --- components/display/display.c | 2 +- components/display/display.h | 9 +- components/display/driver_SSD13x6.c | 206 +++---- components/display/tarablessd13x6/ssd13x6.c | 38 +- components/display/tarablessd13x6/ssd13x6.h | 2 +- .../display/tarablessd13x6/ssd13x6_draw.c | 38 +- .../display/tarablessd13x6/ssd13x6_draw.h | 1 + components/services/monitor.c | 6 + components/squeezelite/display.c | 504 +++++++++++++----- components/squeezelite/embedded.h | 11 + components/squeezelite/output_embedded.c | 3 + components/squeezelite/output_i2s.c | 5 +- components/squeezelite/output_visu.c | 76 +++ components/squeezelite/squeezelite.h | 30 +- components/squeezelite/utils.c | 1 + plugin/SqueezeESP32.zip | Bin 4621 -> 5170 bytes plugin/SqueezeESP32/Graphics.pm | 71 ++- plugin/SqueezeESP32/install.xml | 2 +- plugin/repo.xml | 4 +- 19 files changed, 737 insertions(+), 272 deletions(-) create mode 100644 components/squeezelite/output_visu.c diff --git a/components/display/display.c b/components/display/display.c index 24ea5c93..8ccfa419 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -117,7 +117,7 @@ static void displayer_task(void *args) { if (displayer.state == DISPLAYER_IDLE) display->line(2, 0, DISPLAY_CLEAR | DISPLAY_UPDATE, displayer.string); vTaskSuspend(NULL); scroll_sleep = 0; - display->clear(); + display->clear(true); display->line(1, DISPLAY_LEFT, DISPLAY_UPDATE, displayer.header); } else if (displayer.refresh) { // little trick when switching master while in IDLE and missing it diff --git a/components/display/display.h b/components/display/display.h index c8753254..31f0f5e7 100644 --- a/components/display/display.h +++ b/components/display/display.h @@ -51,8 +51,9 @@ enum displayer_time_e { DISPLAYER_ELAPSED, DISPLAYER_REMAINING }; // don't change anything there w/o changing all drivers init code extern struct display_s { int width, height; + bool dirty; bool (*init)(char *config, char *welcome); - void (*clear)(void); + void (*clear)(bool full, ...); bool (*set_font)(int num, enum display_font_e font, int space); void (*on)(bool state); void (*brightness)(uint8_t level); @@ -60,8 +61,10 @@ extern struct display_s { bool (*line)(int num, int x, int attribute, char *text); int (*stretch)(int num, char *string, int max); void (*update)(void); - void (*draw)(int x1, int y1, int x2, int y2, bool by_column, uint8_t *data); - void (*draw_cbr)(uint8_t *data, int height); // height is the # of columns in data, as oppoosed to display height (0 = display height) + void (*draw_raw)(int x1, int y1, int x2, int y2, bool by_column, bool MSb, uint8_t *data); + void (*draw_cbr)(uint8_t *data, int width, int height); // width and height is the # of rows/columns in data, as opposed to display height (0 = display width, 0 = display height) + void (*draw_line)(int x1, int y1, int x2, int y2); + void (*draw_box)( int x1, int y1, int x2, int y2, bool fill); } *display; void displayer_scroll(char *string, int speed); diff --git a/components/display/driver_SSD13x6.c b/components/display/driver_SSD13x6.c index 252225ca..baec7144 100644 --- a/components/display/driver_SSD13x6.c +++ b/components/display/driver_SSD13x6.c @@ -38,25 +38,26 @@ static const char *TAG = "display"; // handlers static bool init(char *config, char *welcome); -static void clear(void); +static void clear(bool full, ...); static bool set_font(int num, enum display_font_e font, int space); static void text(enum display_font_e font, enum display_pos_e pos, int attribute, char *text, ...); static bool line(int num, int x, int attribute, char *text); static int stretch(int num, char *string, int max); -static void draw_cbr(u8_t *data, int height); -static void draw(int x1, int y1, int x2, int y2, bool by_column, u8_t *data); +static void draw_cbr(u8_t *data, int width, int height); +static void draw_raw(int x1, int y1, int x2, int y2, bool by_column, bool MSb, u8_t *data); +static void draw_line(int x1, int y1, int x2, int y2); +static void draw_box( int x1, int y1, int x2, int y2, bool fill); static void brightness(u8_t level); static void on(bool state); static void update(void); // display structure for others to use -struct display_s SSD13x6_display = { 0, 0, +struct display_s SSD13x6_display = { 0, 0, true, init, clear, set_font, on, brightness, - text, line, stretch, update, draw, draw_cbr, NULL }; + text, line, stretch, update, draw_raw, draw_cbr, draw_line, draw_box }; // SSD13x6 specific function static struct SSD13x6_Device Display; -static SSD13x6_AddressMode AddressMode = AddressMode_Invalid; static const unsigned char BitReverseTable256[] = { @@ -150,9 +151,24 @@ static bool init(char *config, char *welcome) { /**************************************************************************************** * */ -static void clear(void) { - SSD13x6_Clear( &Display, SSD_COLOR_BLACK ); - SSD13x6_Update( &Display ); +static void clear(bool full, ...) { + bool commit = true; + + if (full) { + SSD13x6_Clear( &Display, SSD_COLOR_BLACK ); + } else { + va_list args; + va_start(args, full); + commit = va_arg(args, int); + int x1 = va_arg(args, int), y1 = va_arg(args, int), x2 = va_arg(args, int), y2 = va_arg(args, int); + if (x2 < 0) x2 = display->width - 1; + if (y2 < 0) y2 = display->height - 1; + SSD13x6_ClearWindow( &Display, x1, y1, x2, y2, SSD_COLOR_BLACK ); + va_end(args); + } + + if (commit) update(); + else SSD13x6_display.dirty = true; } /**************************************************************************************** @@ -209,12 +225,6 @@ static bool line(int num, int x, int attribute, char *text) { // counting 1..n num--; - // always horizontal mode for text display - if (AddressMode != AddressMode_Horizontal) { - AddressMode = AddressMode_Horizontal; - SSD13x6_SetDisplayAddressMode( &Display, AddressMode ); - } - SSD13x6_SetFont( &Display, lines[num].font ); if (attribute & DISPLAY_MONOSPACE) SSD13x6_FontForceMonospace( &Display, true ); @@ -237,7 +247,8 @@ static bool line(int num, int x, int attribute, char *text) { ESP_LOGD(TAG, "displaying %s line %u (x:%d, attr:%u)", text, num+1, x, attribute); // update whole display if requested - if (attribute & DISPLAY_UPDATE) SSD13x6_Update( &Display ); + if (attribute & DISPLAY_UPDATE) update(); + else SSD13x6_display.dirty = true; return width + x < Display.Width; } @@ -278,13 +289,14 @@ static int stretch(int num, char *string, int max) { static void text(enum display_font_e font, enum display_pos_e pos, int attribute, char *text, ...) { va_list args; - va_start(args, text); TextAnchor Anchor = TextAnchor_Center; if (attribute & DISPLAY_CLEAR) SSD13x6_Clear( &Display, SSD_COLOR_BLACK ); if (!text) return; + va_start(args, text); + switch(font) { case DISPLAY_FONT_LINE_1: SSD13x6_SetFont( &Display, &Font_line_1 ); @@ -327,127 +339,82 @@ static void text(enum display_font_e font, enum display_pos_e pos, int attribute ESP_LOGD(TAG, "SSDD13x6 displaying %s at %u with attribute %u", text, Anchor, attribute); - if (AddressMode != AddressMode_Horizontal) { - AddressMode = AddressMode_Horizontal; - SSD13x6_SetDisplayAddressMode( &Display, AddressMode ); - } - SSD13x6_FontDrawAnchoredString( &Display, Anchor, text, SSD_COLOR_WHITE ); - if (attribute & DISPLAY_UPDATE) SSD13x6_Update( &Display ); + if (attribute & DISPLAY_UPDATE) update(); + else SSD13x6_display.dirty = true; va_end(args); } /**************************************************************************************** - * Process graphic display data from column-oriented bytes, MSbit first + * Process graphic display data from column-oriented data (MSbit first) */ -static void draw_cbr(u8_t *data, int height) { -#ifndef FULL_REFRESH - // force addressing mode by rows - if (AddressMode != AddressMode_Horizontal) { - AddressMode = AddressMode_Horizontal; - SSD13x6_SetDisplayAddressMode( &Display, AddressMode ); +static void draw_cbr(u8_t *data, int width, int height) { + if (!height) height = Display.Height; + if (!width) width = Display.Width; + + // need to do row/col swap and bit-reverse + int rows = height / 8; + for (int r = 0; r < rows; r++) { + uint8_t *optr = Display.Framebuffer + r*Display.Width, *iptr = data + r; + for (int c = width; --c >= 0;) { + *optr++ = BitReverseTable256[*iptr];; + iptr += rows; + } } - // try to minimize I2C traffic which is very slow - int rows = (height ? height : Display.Height) / 8; - for (int r = 0; r < rows; r++) { - uint8_t first = 0, last; - uint8_t *optr = Display.Framebuffer + r*Display.Width, *iptr = data + r; - - // row/col swap, frame buffer comparison and bit-reversing - for (int c = 0; c < Display.Width; c++) { - u8_t byte = BitReverseTable256[*iptr]; - if (byte != *optr) { - if (!first) first = c + 1; - last = c ; - } - *optr++ = byte; - iptr += rows; - } - - // now update the display by "byte rows" - if (first--) { - SSD13x6_SetColumnAddress( &Display, first, last ); - SSD13x6_SetPageAddress( &Display, r, r); - SSD13x6_WriteRawData( &Display, Display.Framebuffer + r*Display.Width + first, last - first + 1); - } - } -#else - if (!height) height = Display->Height; - - SSD13x6_SetPageAddress( &Display, 0, height / 8 - 1); - - // force addressing mode by columns (if we can) - if (SSD13x6_GetCaps( &Display ) & CAPS_ADDRESS_VERTICAL) { - // just copy data in frame buffer with bit-reverse - for (int c = 0; c < Display.Width; c++) - for (int r = 0; r < height / 8; r++) - Display.Framebuffer[c*Display.Height/8 + r] = BitReverseTable256[data[c*height/8 +r]]; - - if (AddressMode != AddressMode_Vertical) { - AddressMode = AddressMode_Vertical; - SSD13x6_SetDisplayAddressMode( &Display, AddressMode ); - } - } else { - // need to do rwo/col swap and bit-reverse - int rows = (height ? height : Display.Height) / 8; - for (int r = 0; r < rows; r++) { - uint8_t *optr = Display.Framebuffer + r*Display.Width, *iptr = data + r; - for (int c = 0; c < Display.Width; c++) { - *optr++ = BitReverseTable256[*iptr];; - iptr += rows; - } - } - ESP_LOGW(TAG, "Can't set addressing mode to vertical, swapping"); - } - - SSD13x6_WriteRawData(&Display, Display.Framebuffer, Display.Width * Display.Height/8); - #endif + SSD13x6_display.dirty = true; } /**************************************************************************************** * Process graphic display data MSBit first * WARNING: this has not been tested yet */ -static void draw(int x1, int y1, int x2, int y2, bool by_column, u8_t *data) { - - if (y1 % 8 || y2 % 8) { - ESP_LOGW(TAG, "must write rows on byte boundaries (%u,%u) to (%u,%u)", x1, y1, x2, y2); - return; - } - +static void draw_raw(int x1, int y1, int x2, int y2, bool by_column, bool MSb, u8_t *data) { // default end point to display size if (x2 == -1) x2 = Display.Width - 1; if (y2 == -1) y2 = Display.Height - 1; - // set addressing mode to match data - if (by_column) { - - if (AddressMode != AddressMode_Vertical) { - AddressMode = AddressMode_Vertical; - SSD13x6_SetDisplayAddressMode( &Display, AddressMode ); - } - - // copy the window and do row/col exchange - for (int r = y1/8; r <= y2/8; r++) { - uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r; - for (int c = x1; c <= x2; c++) { - *optr++ = *iptr; - iptr += (y2-y1)/8 + 1; + display->dirty = true; + + // not a boundary draw + if (y1 % 8 || y2 % 8 || x1 % 8 | x2 % 8) { + ESP_LOGW(TAG, "can't write on non cols/rows boundaries for now"); + } else { + // set addressing mode to match data + if (by_column) { + // copy the window and do row/col exchange + for (int r = y1/8; r <= y2/8; r++) { + uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r; + for (int c = x1; c <= x2; c++) { + *optr++ = MSb ? BitReverseTable256[*iptr] : *iptr; + iptr += (y2-y1)/8 + 1; } - } - } else { - // just copy the window inside the frame buffer - for (int r = y1/8; r <= y2/8; r++) { - uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r*(x2-x1+1); - for (int c = x1; c <= x2; c++) *optr++ = *iptr++; - } - } - - SSD13x6_SetColumnAddress( &Display, x1, x2); - SSD13x6_SetPageAddress( &Display, y1/8, y2/8); - SSD13x6_WriteRawData( &Display, data, (x2-x1 + 1) * ((y2-y1)/8 + 1)); + } + } else { + // just copy the window inside the frame buffer + for (int r = y1/8; r <= y2/8; r++) { + uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r*(x2-x1+1); + for (int c = x1; c <= x2; c++) *optr++ = *iptr++; + } + } + } +} + +/**************************************************************************************** + * Draw line + */ +static void draw_line( int x1, int y1, int x2, int y2) { + SSD13x6_DrawLine( &Display, x1, y1, x2, y2, SSD_COLOR_WHITE ); + SSD13x6_display.dirty = true; +} + +/**************************************************************************************** + * Draw Box + */ +static void draw_box( int x1, int y1, int x2, int y2, bool fill) { + SSD13x6_DrawBox( &Display, x1, y1, x2, y2, SSD_COLOR_WHITE, fill ); + SSD13x6_display.dirty = true; } /**************************************************************************************** @@ -470,7 +437,8 @@ static void on(bool state) { * Update */ static void update(void) { - SSD13x6_Update( &Display ); + if (SSD13x6_display.dirty) SSD13x6_Update( &Display ); + SSD13x6_display.dirty = false; } diff --git a/components/display/tarablessd13x6/ssd13x6.c b/components/display/tarablessd13x6/ssd13x6.c index ea1a0836..0ba7139f 100644 --- a/components/display/tarablessd13x6/ssd13x6.c +++ b/components/display/tarablessd13x6/ssd13x6.c @@ -15,6 +15,8 @@ #include "ssd13x6.h" +#define SHADOW_BUFFER + // used by both but different static uint8_t SSDCmd_Set_Display_Start_Line; static uint8_t SSDCmd_Set_Display_Offset; @@ -111,8 +113,32 @@ void SSD13x6_SetDisplayAddressMode( struct SSD13x6_Device* DeviceHandle, SSD13x6 } void SSD13x6_Update( struct SSD13x6_Device* DeviceHandle ) { +#ifdef SHADOW_BUFFER + // not sure the compiler does not have to redo all calculation in for loops, so local it is + int width = DeviceHandle->Width, rows = DeviceHandle->Height / 8; + uint8_t *optr = DeviceHandle->Shadowbuffer, *iptr = DeviceHandle->Framebuffer; + + // by row, find first and last columns that have been updated + for (int r = 0; r < rows; r++) { + uint8_t first = 0, last; + for (int c = 0; c < width; c++) { + if (*iptr != *optr) { + if (!first) first = c + 1; + last = c ; + } + *optr++ = *iptr++; + } + + // now update the display by "byte rows" + if (first--) { + SSD13x6_SetColumnAddress( DeviceHandle, first, last ); + SSD13x6_SetPageAddress( DeviceHandle, r, r); + SSD13x6_WriteData( DeviceHandle, DeviceHandle->Shadowbuffer + r*width + first, last - first + 1); + } + } +#else if (DeviceHandle->Model == SH1106) { - // SH1106 requires a page-by-page update and ahs no end Page/Column + // SH1106 requires a page-by-page update and has no end Page/Column for (int i = 0; i < DeviceHandle->Height / 8 ; i++) { SSD13x6_SetPageAddress( DeviceHandle, i, 0); SSD13x6_SetColumnAddress( DeviceHandle, 0, 0); @@ -124,6 +150,7 @@ void SSD13x6_Update( struct SSD13x6_Device* DeviceHandle ) { SSD13x6_SetPageAddress( DeviceHandle, 0, DeviceHandle->Height / 8 - 1); SSD13x6_WriteData( DeviceHandle, DeviceHandle->Framebuffer, DeviceHandle->FramebufferSize ); } +#endif } void SSD13x6_WriteRawData( struct SSD13x6_Device* DeviceHandle, uint8_t* Data, size_t DataLength ) { @@ -214,6 +241,11 @@ static bool SSD13x6_Init( struct SSD13x6_Device* DeviceHandle, int Width, int He DeviceHandle->Width = Width; DeviceHandle->Height = Height; +#ifdef SHADOW_BUFFER + DeviceHandle->Shadowbuffer = heap_caps_malloc( DeviceHandle->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + memset( DeviceHandle->Shadowbuffer, 0xFF, DeviceHandle->FramebufferSize ); +#endif + SSD13x6_HWReset( DeviceHandle ); SSD13x6_DisplayOff( DeviceHandle ); @@ -308,7 +340,11 @@ bool SSD13x6_Init_SPI( struct SSD13x6_Device* DeviceHandle, int Width, int Heigh DeviceHandle->CSPin = CSPin; DeviceHandle->FramebufferSize = ( Width * Height ) / 8; +#ifdef SHADOW_BUFFER + DeviceHandle->Framebuffer = calloc( 1, DeviceHandle->FramebufferSize ); +#else DeviceHandle->Framebuffer = heap_caps_calloc( 1, DeviceHandle->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); +#endif NullCheck( DeviceHandle->Framebuffer, return false ); return SSD13x6_Init( DeviceHandle, Width, Height ); diff --git a/components/display/tarablessd13x6/ssd13x6.h b/components/display/tarablessd13x6/ssd13x6.h index 95a814a1..697eb02a 100644 --- a/components/display/tarablessd13x6/ssd13x6.h +++ b/components/display/tarablessd13x6/ssd13x6.h @@ -58,7 +58,7 @@ struct SSD13x6_Device { enum { SSD1306, SSD1326, SH1106 } Model; uint8_t ReMap; - uint8_t* Framebuffer; + uint8_t* Framebuffer, *Shadowbuffer; int FramebufferSize; WriteCommandProc WriteCommand; diff --git a/components/display/tarablessd13x6/ssd13x6_draw.c b/components/display/tarablessd13x6/ssd13x6_draw.c index 9467abf8..9a4d9f91 100644 --- a/components/display/tarablessd13x6/ssd13x6_draw.c +++ b/components/display/tarablessd13x6/ssd13x6_draw.c @@ -16,6 +16,9 @@ #include "ssd13x6.h" #include "ssd13x6_draw.h" +#undef NullCheck +#define NullCheck(X,Y) + __attribute__( ( always_inline ) ) static inline bool IsPixelVisible( struct SSD13x6_Device* DeviceHandle, int x, int y ) { bool Result = ( ( x >= 0 ) && @@ -75,7 +78,7 @@ void IRAM_ATTR SSD13x6_DrawHLine( struct SSD13x6_Device* DeviceHandle, int x, in NullCheck( DeviceHandle, return ); NullCheck( DeviceHandle->Framebuffer, return ); - for ( ; x <= XEnd; x++ ) { + for ( ; x < XEnd; x++ ) { if ( IsPixelVisible( DeviceHandle, x, y ) == true ) { SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color ); } else { @@ -90,7 +93,7 @@ void IRAM_ATTR SSD13x6_DrawVLine( struct SSD13x6_Device* DeviceHandle, int x, in NullCheck( DeviceHandle, return ); NullCheck( DeviceHandle->Framebuffer, return ); - for ( ; y <= YEnd; y++ ) { + for ( ; y < YEnd; y++ ) { if ( IsPixelVisible( DeviceHandle, x, y ) == true ) { SSD13x6_DrawPixel( DeviceHandle, x, y, Color ); } else { @@ -114,7 +117,7 @@ static inline void IRAM_ATTR DrawWideLine( struct SSD13x6_Device* DeviceHandle, Error = ( dy * 2 ) - dx; - for ( ; x <= x1; x++ ) { + for ( ; x < x1; x++ ) { if ( IsPixelVisible( DeviceHandle, x, y ) == true ) { SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color ); } @@ -219,3 +222,32 @@ void SSD13x6_Clear( struct SSD13x6_Device* DeviceHandle, int Color ) { memset( DeviceHandle->Framebuffer, Color, DeviceHandle->FramebufferSize ); } + +void SSD13x6_ClearWindow( struct SSD13x6_Device* DeviceHandle, int x1, int y1, int x2, int y2, int Color ) { + NullCheck( DeviceHandle, return ); + NullCheck( DeviceHandle->Framebuffer, return ); + +/* + int xr = ((x1 - 1) / 8) + 1 ) * 8; + int xl = (x2 / 8) * 8; + + for (int y = y1; y <= y2; y++) { + for (int x = x1; x < xr; x++) SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color); + if (xl > xr) memset( DeviceHandle->Framebuffer + (y / 8) * DeviceHandle->Width + xr, 0, xl - xr ); + for (int x = xl; x <= x2; x++) SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color); + } + + return; +*/ + + // cheap optimization on boundaries + if (x1 == 0 && x2 == DeviceHandle->Width - 1 && y1 % 8 == 0 && (y2 + 1) % 8 == 0) { + memset( DeviceHandle->Framebuffer + (y1 / 8) * DeviceHandle->Width, 0, (y2 - y1 + 1) / 8 * DeviceHandle->Width ); + } else { + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color); + } + } + } +} diff --git a/components/display/tarablessd13x6/ssd13x6_draw.h b/components/display/tarablessd13x6/ssd13x6_draw.h index 18cc35cb..63196a40 100644 --- a/components/display/tarablessd13x6/ssd13x6_draw.h +++ b/components/display/tarablessd13x6/ssd13x6_draw.h @@ -39,6 +39,7 @@ extern "C" { #define SSD_COLOR_XOR 2 void SSD13x6_Clear( struct SSD13x6_Device* DeviceHandle, int Color ); +void SSD13x6_ClearWindow( struct SSD13x6_Device* DeviceHandle, int x1, int y1, int x2, int y2, int Color ); void SSD13x6_DrawPixel( struct SSD13x6_Device* DeviceHandle, int X, int Y, int Color ); void SSD13x6_DrawPixelFast( struct SSD13x6_Device* DeviceHandle, int X, int Y, int Color ); void SSD13x6_DrawHLine( struct SSD13x6_Device* DeviceHandle, int x, int y, int Width, int Color ); diff --git a/components/services/monitor.c b/components/services/monitor.c index b68bfe88..7d421414 100644 --- a/components/services/monitor.c +++ b/components/services/monitor.c @@ -146,4 +146,10 @@ void monitor_svc_init(void) { xTimerStart(monitor_timer, portMAX_DELAY); } free(p); + + ESP_LOGI(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu)", + heap_caps_get_free_size(MALLOC_CAP_INTERNAL), + heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL), + heap_caps_get_free_size(MALLOC_CAP_SPIRAM), + heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM)); } diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index 7342f846..2a982f48 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -18,6 +18,8 @@ */ #include +#include +#include "esp_dsp.h" #include "squeezelite.h" #include "slimproto.h" #include "display.h" @@ -54,6 +56,21 @@ struct grfg_packet { u16_t width; // # of pixels of scrollable }; +struct visu_packet { + char opcode[4]; + u8_t which; + u8_t count; + union { + u32_t width; + u32_t full_bars; + }; + u32_t height; + s32_t col; + s32_t row; + u32_t border; + u32_t small_bars; +}; + struct ANIC_header { char opcode[4]; u32_t length; @@ -64,20 +81,59 @@ struct ANIC_header { extern struct outputstate output; +static struct { + TaskHandle_t task; + SemaphoreHandle_t mutex; +} displayer; + +#define LONG_WAKE (10*1000) +#define SB_HEIGHT 32 + +// lenght are number of frames, i.e. 2 channels of 16 bits +#define FFT_LEN_BIT 6 +#define FFT_LEN (1 << FFT_LEN_BIT) +#define RMS_LEN_BIT 6 +#define RMS_LEN (1 << RMS_LEN_BIT) + +// actually this is 2x the displayed BW +#define DISPLAY_BW 32000 + static struct scroller_s { // copy of grfs content - u8_t screen, direction; - u32_t pause, speed; - u16_t by, mode, full_width, window_width; - u16_t max, size; + u8_t screen; + u32_t pause, speed; + int wake; + u16_t mode; + s16_t by; // scroller management & sharing between grfg and scrolling task - TaskHandle_t task; - u8_t *scroll_frame, *back_frame; - bool active, updated; - u8_t *scroll_ptr; - int scroll_len, scroll_step; + bool active, first; + int scrolled; + struct { + u8_t *frame; + u32_t width; + u32_t max, size; + } scroll; + struct { + u8_t *frame; + u32_t width; + } back; + u8_t *frame; + u32_t width; } scroller; +#define MAX_BARS 32 +static EXT_RAM_ATTR struct { + int bar_gap, bar_width, bar_border; + struct { + int current; + int max; + } bars[MAX_BARS]; + int n, col, row, height, width, border; + enum { VISU_BLANK, VISU_VUMETER, VISU_SPECTRUM, VISU_WAVEFORM } mode; + int speed, wake; + float fft[FFT_LEN*2], samples[FFT_LEN*2], hanning[FFT_LEN]; +} visu; + #define ANIM_NONE 0x00 #define ANIM_TRANSITION 0x01 // A transition animation has finished #define ANIM_SCROLL_ONCE 0x02 @@ -91,11 +147,11 @@ static u8_t SETD_width; #define LINELEN 40 static log_level loglevel = lINFO; -static SemaphoreHandle_t display_mutex; static bool (*slimp_handler_chain)(u8_t *data, int len); static void (*slimp_loop_chain)(void); static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport); static int display_width, display_height; +static bool display_dirty = true; #define max(a,b) (((a) > (b)) ? (a) : (b)) @@ -107,7 +163,9 @@ static void grfe_handler( u8_t *data, int len); static void grfb_handler(u8_t *data, int len); static void grfs_handler(u8_t *data, int len); static void grfg_handler(u8_t *data, int len); -static void scroll_task(void* arg); +static void visu_handler( u8_t *data, int len); + +static void displayer_task(void* arg); /* scrolling undocumented information grfs @@ -151,17 +209,24 @@ bool sb_display_init(void) { // need to force height to 32 maximum display_width = display->width; - display_height = min(display->height, 32); + display_height = min(display->height, SB_HEIGHT); SETD_width = display->width; - + + // create visu configuration + visu.bar_gap = 1; + visu.speed = 100; + dsps_fft2r_init_fc32(visu.fft, FFT_LEN); + dsps_wind_hann_f32(visu.hanning, FFT_LEN); + // create scroll management task - display_mutex = xSemaphoreCreateMutex(); - scroller.task = xTaskCreateStatic( (TaskFunction_t) scroll_task, "scroll_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer); + displayer.mutex = xSemaphoreCreateMutex(); + displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer); // size scroller - scroller.max = (display_width * display_height / 8) * 10; - scroller.scroll_frame = malloc(scroller.max); - scroller.back_frame = malloc(display_width * display_height / 8); + scroller.scroll.max = (display_width * display_height / 8) * 10; + scroller.scroll.frame = malloc(scroller.scroll.max); + scroller.back.frame = malloc(display_width * display_height / 8); + scroller.frame = malloc(display_width * display_height / 8); // chain handlers slimp_handler_chain = slimp_handler; @@ -225,6 +290,8 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) { char msg[32]; sprintf(msg, "%s:%hu", inet_ntoa(ip), hport); display->text(DISPLAY_FONT_DEFAULT, DISPLAY_CENTERED, DISPLAY_CLEAR | DISPLAY_UPDATE, msg); + SETD_width = display->width; + display_dirty = true; if (notify_chain) (*notify_chain)(ip, hport, cport); } @@ -246,6 +313,8 @@ static bool handler(u8_t *data, int len){ grfs_handler(data, len); } else if (!strncmp((char*) data, "grfg", 4)) { grfg_handler(data, len); + } else if (!strncmp((char*) data, "visu", 4)) { + visu_handler(data, len); } else { res = false; } @@ -374,12 +443,27 @@ static void vfdc_handler( u8_t *_data, int bytes_read) { * Process graphic display data */ static void grfe_handler( u8_t *data, int len) { - xSemaphoreTake(display_mutex, portMAX_DELAY); + + xSemaphoreTake(displayer.mutex, portMAX_DELAY); scroller.active = false; - display->draw_cbr(data + sizeof(struct grfe_packet), display_height); - xSemaphoreGive(display_mutex); + // if we are displaying visu on a small screen, do not do screen update + if (visu.mode && !visu.col && visu.row < SB_HEIGHT) { + xSemaphoreGive(displayer.mutex); + return; + } + + // did we have something that might have write on the bottom of a SB_HEIGHT+ display + if (display_dirty) { + display->clear(true); + display_dirty = false; + } + + display->draw_cbr(data + sizeof(struct grfe_packet), display_width, display_height); + display->update(); + + xSemaphoreGive(displayer.mutex); LOG_DEBUG("grfe frame %u", len); } @@ -415,36 +499,46 @@ static void grfs_handler(u8_t *data, int len) { htonl(pkt->pause), // in ms htonl(pkt->speed), // in ms htons(pkt->by), // # of pixel of scroll step - htons(pkt->mode), // 0=continuous, 1=once and stop, 2=once and end - htons(pkt->width), // total width of animation + htons(pkt->mode), // 0=continuous, 1=once and stop, 2=once and end + htons(pkt->width), // last column of animation that contains a "full" screen htons(pkt->offset) // offset if multiple packets are sent ); // new grfs frame, build scroller info if (!offset) { // use the display as a general lock - xSemaphoreTake(display_mutex, portMAX_DELAY); + xSemaphoreTake(displayer.mutex, portMAX_DELAY); // copy & set scroll parameters scroller.screen = pkt->screen; - scroller.direction = pkt->direction; scroller.pause = htonl(pkt->pause); scroller.speed = htonl(pkt->speed); - scroller.by = htons(pkt->by); scroller.mode = htons(pkt->mode); - scroller.full_width = htons(pkt->width); - scroller.updated = scroller.active = true; + scroller.scroll.width = htons(pkt->width); + scroller.first = true; + + // background excludes space taken by visu (if any) + scroller.back.width = display_width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0); - xSemaphoreGive(display_mutex); + // set scroller steps & beginning + if (pkt->direction == 1) { + scroller.scrolled = 0; + scroller.by = htons(pkt->by); + } else { + scroller.scrolled = scroller.scroll.width; + scroller.by = -htons(pkt->by); + } + + xSemaphoreGive(displayer.mutex); } // copy scroll frame data (no semaphore needed) - if (scroller.size + size < scroller.max) { - memcpy(scroller.scroll_frame + offset, data + sizeof(struct grfs_packet), size); - scroller.size = offset + size; - LOG_INFO("scroller current size %u", scroller.size); + if (scroller.scroll.size + size < scroller.scroll.max) { + memcpy(scroller.scroll.frame + offset, data + sizeof(struct grfs_packet), size); + scroller.scroll.size = offset + size; + LOG_INFO("scroller current size %u", scroller.scroll.size); } else { - LOG_INFO("scroller too larger %u/%u", scroller.size + size, scroller.max); + LOG_INFO("scroller too larger %u/%u", scroller.scroll.size + size, scroller.scroll.max); } } @@ -456,109 +550,273 @@ static void grfg_handler(u8_t *data, int len) { LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len); - memcpy(scroller.back_frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet)); - scroller.window_width = htons(pkt->width); + xSemaphoreTake(displayer.mutex, portMAX_DELAY); - xSemaphoreTake(display_mutex, portMAX_DELAY); - - // can't be in grfs as we need full size & scroll_width - if (scroller.updated) { - scroller.scroll_len = display_width * display_height / 8 - (display_width - scroller.window_width) * display_height / 8; - if (scroller.direction == 1) { - scroller.scroll_ptr = scroller.scroll_frame; - scroller.scroll_step = scroller.by * display_height / 8; - } else { - scroller.scroll_ptr = scroller.scroll_frame + scroller.size - scroller.scroll_len; - scroller.scroll_step = -scroller.by * display_height / 8; - } + // size of scrollable area (less than background) + scroller.width = htons(pkt->width); + memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet)); - scroller.updated = false; - } + // update display asynchronously (frames are oganized by columns) + memcpy(scroller.frame, scroller.back.frame, scroller.back.width * display_height / 8); + for (int i = 0; i < scroller.width * display_height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * display_height / 8 + i]; + display->draw_cbr(scroller.frame, scroller.back.width, display_height); + display->update(); + + // now we can active scrolling, but only if we are not on a small screen + if (!visu.mode || visu.col || visu.row >= SB_HEIGHT) scroller.active = true; + + // if we just got a content update, let the scroller manage the screen + LOG_DEBUG("resuming scrolling task"); + + xSemaphoreGive(displayer.mutex); - if (!scroller.active) { - // this is a background update and scroller has been finished, so need to update here - u8_t *frame = malloc(display_width * display_height / 8); - memcpy(frame, scroller.back_frame, display_width * display_height / 8); - for (int i = 0; i < scroller.scroll_len; i++) frame[i] |= scroller.scroll_ptr[i]; - display->draw_cbr(frame, display_height); - free(frame); - LOG_DEBUG("direct drawing"); - } - else { - // if we just got a content update, let the scroller manage the screen - LOG_DEBUG("resuming scrolling task"); - vTaskResume(scroller.task); - } - - xSemaphoreGive(display_mutex); + // resume task once we have background, not in grfs + vTaskResume(displayer.task); } /**************************************************************************************** - * Scroll task + * Update visualization bars */ -static void scroll_task(void *args) { - u8_t *frame = NULL; - int len = display_width * display_height / 8; +static void visu_update(void) { + if (pthread_mutex_trylock(&visu_export.mutex)) return; + + // not enough samples + if (visu_export.level < (visu.mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) * 2 && visu_export.running) { + pthread_mutex_unlock(&visu_export.mutex); + return; + } - while (1) { - xSemaphoreTake(display_mutex, portMAX_DELAY); - - // suspend ourselves if nothing to do, grfg will wake us up - if (!scroller.active) { - xSemaphoreGive(display_mutex); - vTaskSuspend(NULL); - xSemaphoreTake(display_mutex, portMAX_DELAY); - } - - // lock screen & active status - frame = malloc(display_width * display_height / 8); - - // scroll required amount of columns (within the window) - while (scroller.direction == 1 ? (scroller.scroll_ptr <= scroller.scroll_frame + scroller.size - scroller.scroll_step - len) : - (scroller.scroll_ptr + scroller.scroll_step >= scroller.scroll_frame) ) { - - // don't do anything if we have aborted - if (!scroller.active) break; + // reset bars for all cases first + for (int i = visu.n; --i >= 0;) visu.bars[i].current = 0; + + if (visu_export.running && visu_export.running) { - // scroll required amount of columns (within the window) - memcpy(frame, scroller.back_frame, display_width * display_height / 8); - for (int i = 0; i < scroller.scroll_len; i++) frame[i] |= scroller.scroll_ptr[i]; - scroller.scroll_ptr += scroller.scroll_step; - display->draw_cbr(frame, display_height); + if (visu.mode == VISU_VUMETER) { + s16_t *iptr = visu_export.buffer; - xSemaphoreGive(display_mutex); - vTaskDelay(scroller.speed / portTICK_PERIOD_MS); - - xSemaphoreTake(display_mutex, portMAX_DELAY); - } + // calculate sum(X²), try to not overflow at the expense of some precision + for (int i = RMS_LEN; --i >= 0;) { + visu.bars[0].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1); + iptr++; + visu.bars[1].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1); + iptr++; + } - // done with scrolling cycle reset scroller ptr - scroller.scroll_ptr = scroller.scroll_frame + (scroller.direction == 2 ? scroller.size - scroller.scroll_len : 0); - - // scrolling done, update screen and see if we need to continue - if (scroller.active) { - memcpy(frame, scroller.back_frame, len); - for (int i = 0; i < scroller.scroll_len; i++) frame[i] |= scroller.scroll_ptr[i]; - display->draw_cbr(frame, display_height); - free(frame); - - // see if we need to pause or if we are done - if (scroller.mode) { - scroller.active = false; - xSemaphoreGive(display_mutex); - // can't call directly send_packet from slimproto as it's not re-entrant - ANIC_resp = ANIM_SCROLL_ONCE | ANIM_SCREEN_1; - LOG_INFO("scroll-once terminated"); - } else { - xSemaphoreGive(display_mutex); - vTaskDelay(scroller.pause / portTICK_PERIOD_MS); - LOG_DEBUG("scroll cycle done, pausing for %u (ms)", scroller.pause); + // convert to dB (1 bit remaining for getting X²/N, 60dB dynamic starting from 0dBFS = 3 bits back-off) + for (int i = visu.n; --i >= 0;) { + visu.bars[i].current = 32 * (0.01667f*10*log10f(0.0000001f + (visu.bars[i].current >> 1)) - 0.2543f); + if (visu.bars[i].current > 31) visu.bars[i].current = 31; + else if (visu.bars[i].current < 0) visu.bars[i].current = 0; } } else { - free(frame); - xSemaphoreGive(display_mutex); - LOG_INFO("scroll aborted"); + // on xtensa/esp32 the floating point FFT takes 1/2 cycles of the fixed point + for (int i = 0 ; i < FFT_LEN ; i++) { + // don't normalize here, but we are due INT16_MAX and FFT_LEN / 2 / 2 + visu.samples[i * 2 + 0] = (float) (visu_export.buffer[2*i] + visu_export.buffer[2*i + 1]) * visu.hanning[i]; + visu.samples[i * 2 + 1] = 0; + } + + // actual FFT that might be less cycle than all the crap below + dsps_fft2r_fc32_ae32(visu.samples, FFT_LEN); + dsps_bit_rev_fc32_ansi(visu.samples, FFT_LEN); + + // now arrange the result with the number of bar and sampling rate (don't want DC) + for (int i = 1, j = 1; i <= visu.n && j < (FFT_LEN / 2); i++) { + float power, count; + + // find the next point in FFT (this is real signal, so only half matters) + for (count = 0, power = 0; j * visu.n * visu_export.rate < i * (FFT_LEN / 2) * DISPLAY_BW && j < (FFT_LEN / 2); j++, count += 1) { + power += visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]; + } + // due to sample rate, we have reached the end of the available spectrum + if (j >= (FFT_LEN / 2)) { + // normalize accumulated data + if (count) power /= count * 2.; + } else if (count) { + // how much of what remains do we need to add + float ratio = j - (float) (i * DISPLAY_BW * (FFT_LEN / 2)) / (float) (visu.n * visu_export.rate); + power += (visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]) * ratio; + + // normalize accumulated data + power /= (count + ratio) * 2; + } else { + // no data for that band (sampling rate too high), just assume same as previous one + power = (visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]) / 2.; + } + + // convert to dB and bars, same back-off + if (power) visu.bars[i-1].current = 32 * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f); + if (visu.bars[i-1].current > 31) visu.bars[i-1].current = 31; + else if (visu.bars[i-1].current < 0) visu.bars[i-1].current = 0; + } + } + } + + // we took what we want, we can release the buffer + visu_export.level = 0; + pthread_mutex_unlock(&visu_export.mutex); + + display->clear(false, false, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1); + + for (int i = visu.n; --i >= 0;) { + int x1 = visu.col + visu.border + visu.bar_border + i*(visu.bar_width + visu.bar_gap); + int y1 = visu.row + visu.height - 1; + + if (visu.bars[i].current > visu.bars[i].max) visu.bars[i].max = visu.bars[i].current; + else if (visu.bars[i].max) visu.bars[i].max--; + + for (int j = 0; j <= visu.bars[i].current; j += 2) + display->draw_line( x1, y1 - j, x1 + visu.bar_width - 1, y1 - j); + + if (visu.bars[i].max > 2) { + display->draw_line( x1, y1 - visu.bars[i].max, x1 + visu.bar_width - 1, y1 - visu.bars[i].max); + display->draw_line( x1, y1 - visu.bars[i].max + 1, x1 + visu.bar_width - 1, y1 - visu.bars[i].max + 1); } + } +} + +/**************************************************************************************** + * Visu packet handler + */ +static void visu_handler( u8_t *data, int len) { + struct visu_packet *pkt = (struct visu_packet*) data; + int bars = 0; + + LOG_DEBUG("visu %u with %u parameters", pkt->which, pkt->count); + + /* + If width is specified, then respect all coordinates, otherwise we try to + use the bottom part of the display and if it is a small display, we overwrite + text + */ + + xSemaphoreTake(displayer.mutex, portMAX_DELAY); + visu.mode = pkt->which; + + // little trick to clean the taller screens when switching visu + if (visu.row >= SB_HEIGHT) display->clear(false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1); + + if (visu.mode) { + pkt->width = htonl(pkt->width); + pkt->height = htonl(pkt->height); + pkt->row = htonl(pkt->row); + pkt->col = htonl(pkt->col); + + if (pkt->width > 0 && pkt->count >= 4) { + // small visu, then go were we are told to + visu.width = pkt->width; + visu.height = pkt->height ? pkt->height : SB_HEIGHT; + visu.col = pkt->col < 0 ? display->width + pkt->col : pkt->col; + visu.row = pkt->row < 0 ? display->height + pkt->row : pkt->row; + visu.border = htonl(pkt->border); + bars = htonl(pkt->small_bars); + } else { + // full screen visu, try to use bottom screen if available + visu.width = display->width; + visu.height = display->height > SB_HEIGHT ? display->height - SB_HEIGHT : display->height; + visu.col = visu.border = 0; + visu.row = display->height - visu.height; + // already in CPU order + bars = pkt->full_bars; + } + + // try to adapt to what we have + visu.n = visu.mode == VISU_VUMETER ? 2 : (bars ? bars : MAX_BARS); + do { + visu.bar_width = (visu.width - visu.border - visu.bar_gap * (visu.n - 1)) / visu.n; + if (visu.bar_width > 0) break; + } while (--visu.n); + visu.bar_border = (visu.width - visu.border - (visu.bar_width + visu.bar_gap) * visu.n + visu.bar_gap) / 2; + + // give up if not enough space + if (visu.bar_width < 0) visu.mode = VISU_BLANK; + else vTaskResume(displayer.task); + visu.wake = 0; + + // reset bars maximum + for (int i = visu.n; --i >= 0;) visu.bars[i].max = 0; + + display->clear(false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1); + + LOG_INFO("Visualizer with %u bars of width %d:%d:%d:%d (%w:%u,h:%u,c:%u,r:%u)", visu.n, visu.bar_border, visu.bar_width, visu.bar_gap, visu.border, visu.width, visu.height, visu.col, visu.row); + } else { + LOG_INFO("Stopping visualizer"); + } + + xSemaphoreGive(displayer.mutex); +} + +/**************************************************************************************** + * Scroll task + * - with the addition of the visualizer, it's a bit a 2-headed beast not easy to + * maintain, so som better separation between the visu and scroll is probably needed + */ +static void displayer_task(void *args) { + int sleep; + + while (1) { + xSemaphoreTake(displayer.mutex, portMAX_DELAY); + + // suspend ourselves if nothing to do, grfg or visu will wake us up + if (!scroller.active && !visu.mode) { + xSemaphoreGive(displayer.mutex); + vTaskSuspend(NULL); + xSemaphoreTake(displayer.mutex, portMAX_DELAY); + scroller.wake = visu.wake = 0; + } + + // go for long sleep when either item is disabled + if (!visu.mode) visu.wake = LONG_WAKE; + if (!scroller.active) scroller.wake = LONG_WAKE; + + // scroll required amount of columns (within the window) + if (scroller.active && scroller.wake <= 0) { + // by default go for the long sleep, will change below if required + scroller.wake = LONG_WAKE; + + // do we have more to scroll (scroll.width is the last column from which we havea full zone) + if (scroller.by > 0 ? (scroller.scrolled <= scroller.scroll.width) : (scroller.scrolled >= 0)) { + memcpy(scroller.frame, scroller.back.frame, scroller.back.width * display_height / 8); + for (int i = 0; i < scroller.width * display_height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * display_height / 8 + i]; + scroller.scrolled += scroller.by; + display->draw_cbr(scroller.frame, scroller.width, display_height); + + // short sleep & don't need background update + scroller.wake = scroller.speed; + } else if (scroller.first || !scroller.mode) { + // at least one round done + scroller.first = false; + + // see if we need to pause or if we are done + if (scroller.mode) { + // can't call directly send_packet from slimproto as it's not re-entrant + ANIC_resp = ANIM_SCROLL_ONCE | ANIM_SCREEN_1; + LOG_INFO("scroll-once terminated"); + } else { + scroller.wake = scroller.pause; + LOG_DEBUG("scroll cycle done, pausing for %u (ms)", scroller.pause); + } + + // need to reset pointers for next scroll + scroller.scrolled = scroller.by < 0 ? scroller.scroll.width : 0; + } + } + + // update visu if active + if (visu.mode && visu.wake <= 0) { + visu_update(); + visu.wake = 100; + } + + display->update(); + + // release semaphore and sleep what's needed + xSemaphoreGive(displayer.mutex); + + sleep = min(visu.wake, scroller.wake); + vTaskDelay(sleep / portTICK_PERIOD_MS); + scroller.wake -= sleep; + visu.wake -= sleep; } } diff --git a/components/squeezelite/embedded.h b/components/squeezelite/embedded.h index f8fb1e03..90926dca 100644 --- a/components/squeezelite/embedded.h +++ b/components/squeezelite/embedded.h @@ -54,6 +54,17 @@ void register_external(void); void deregister_external(void); void decode_restore(int external); +// to be defined to nothing if you don't want to support these +extern struct visu_export_s { + pthread_mutex_t mutex; + u32_t level, size, rate; + s16_t *buffer; + bool running; +} visu_export; +void output_visu_export(s16_t *frames, frames_t out_frames, u32_t rate, bool silence); +void output_visu_init(log_level level); +void output_visu_close(void); + // optional, please chain if used bool (*slimp_handler)(u8_t *data, int len); void (*slimp_loop)(void); diff --git a/components/squeezelite/output_embedded.c b/components/squeezelite/output_embedded.c index c9073458..cfd1a544 100644 --- a/components/squeezelite/output_embedded.c +++ b/components/squeezelite/output_embedded.c @@ -68,6 +68,8 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz output_init_i2s(level, device, output_buf_size, params, rates, rate_delay, idle); } + output_visu_init(level); + LOG_INFO("init completed."); } @@ -75,6 +77,7 @@ void output_close_embedded(void) { LOG_INFO("close output"); if (close_cb) (*close_cb)(); output_close_common(); + output_visu_close(); } void set_volume(unsigned left, unsigned right) { diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index e36d98ec..666ec9ac 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -365,8 +365,11 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32 _scale_and_pack_frames(obuf + oframes * bytes_per_frame, optr, out_frames, gainL, gainR, output.format); #endif - oframes += out_frames; + // send data to visu + output_visu_export((s16_t*) (obuf + oframes * bytes_per_frame), out_frames, output.current_sample_rate, silence); + oframes += out_frames; + return out_frames; } diff --git a/components/squeezelite/output_visu.c b/components/squeezelite/output_visu.c new file mode 100644 index 00000000..20a9da0a --- /dev/null +++ b/components/squeezelite/output_visu.c @@ -0,0 +1,76 @@ +/* + * Squeezelite - lightweight headless squeezebox emulator + * + * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com + * Philippe_44 2020, philippe_44@outloook.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "squeezelite.h" + +#define VISUEXPORT_SIZE 2048 + +EXT_BSS struct visu_export_s visu_export; +static struct visu_export_s *visu = &visu_export; + +static log_level loglevel = lINFO; + +void output_visu_export(s16_t *frames, frames_t out_frames, u32_t rate, bool silence) { + + // no data to process + if (silence) { + visu->running = false; + return; + } + + // do not block, try to stuff data put wait for consumer to have used them + if (!pthread_mutex_trylock(&visu->mutex)) { + // don't mix sample rates + if (visu->rate != rate) visu->level = 0; + + // stuff buffer up and wait for consumer to read it (should reset level) + if (visu->level < visu->size) { + u32_t space = min(visu->size - visu->level, out_frames * 2) * 2; + memcpy(visu->buffer + visu->level, frames, space); + + visu->level += space / 2; + visu->running = true; + visu->rate = rate ? rate : 44100; + } + + // mutex must be released + pthread_mutex_unlock(&visu->mutex); + } +} + +void output_visu_close(void) { + pthread_mutex_lock(&visu->mutex); + visu->running = false; + free(visu->buffer); + pthread_mutex_unlock(&visu->mutex); +} + +void output_visu_init(log_level level) { + loglevel = level; + pthread_mutex_init(&visu->mutex, NULL); + visu->size = VISUEXPORT_SIZE; + visu->running = false; + visu->rate = 44100; + visu->buffer = malloc(VISUEXPORT_SIZE * sizeof(s16_t) * 2); + LOG_INFO("Initialize VISUEXPORT %u 16 bits samples", VISUEXPORT_SIZE); +} + diff --git a/components/squeezelite/squeezelite.h b/components/squeezelite/squeezelite.h index 2544de34..ef3a6397 100644 --- a/components/squeezelite/squeezelite.h +++ b/components/squeezelite/squeezelite.h @@ -387,6 +387,21 @@ typedef BOOL bool; #endif +typedef u32_t frames_t; +typedef int sockfd; + +// logging +typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level; + +const char *logtime(void); +void logprint(const char *fmt, ...); + +#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) +#define LOG_WARN(fmt, ...) if (loglevel >= lWARN) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) +#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) +#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) +#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) + #if EMBEDDED #include "embedded.h" #endif @@ -395,9 +410,6 @@ typedef BOOL bool; #define MSG_NOSIGNAL 0 #endif -typedef u32_t frames_t; -typedef int sockfd; - #if EVENTFD #include #define event_event int @@ -473,18 +485,6 @@ void _wake_create(event_event*); #define min(a,b) (((a) < (b)) ? (a) : (b)) -// logging -typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level; - -const char *logtime(void); -void logprint(const char *fmt, ...); - -#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) -#define LOG_WARN(fmt, ...) if (loglevel >= lWARN) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) -#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) -#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) -#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__) - // utils.c (non logging) typedef enum { EVENT_TIMEOUT = 0, EVENT_READ, EVENT_WAKE } event_type; #if WIN && USE_SSL diff --git a/components/squeezelite/utils.c b/components/squeezelite/utils.c index 1971ed65..7be5cf59 100644 --- a/components/squeezelite/utils.c +++ b/components/squeezelite/utils.c @@ -78,6 +78,7 @@ void logprint(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); + va_end(args); fflush(stderr); } diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index 1e5af7651a9fac0a2ca4a93b36f4cc2790f03ca0..626d46d9d7a95a9b744c209b23dfca85f745936a 100644 GIT binary patch delta 2466 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 dzzx}*60_b-m`XDI*x diff --git a/plugin/SqueezeESP32/Graphics.pm b/plugin/SqueezeESP32/Graphics.pm index 811e2008..42ab788e 100644 --- a/plugin/SqueezeESP32/Graphics.pm +++ b/plugin/SqueezeESP32/Graphics.pm @@ -11,6 +11,10 @@ my $prefs = preferences('plugin.squeezeesp32'); my $log = logger('plugin.squeezeesp32'); my $VISUALIZER_NONE = 0; +my $VISUALIZER_VUMETER = 1; +my $VISUALIZER_SPECTRUM_ANALYZER = 2; +my $VISUALIZER_WAVEFORM = 3; + my $width = $prefs->get('width') || 128; my @modes = ( @@ -42,6 +46,44 @@ my @modes = ( { desc => ['SETUP_SHOWBUFFERFULLNESS'], bar => 0, secs => 0, width => $width, fullness => 1, params => [$VISUALIZER_NONE] }, + # mode 7 + { desc => ['VISUALIZER_VUMETER_SMALL'], + bar => 0, secs => 0, width => $width, _width => -20, + # extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), bars, left space) + params => [$VISUALIZER_VUMETER, 20, 32, -20, 0, 2] }, + # mode 8 + { desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'], + bar => 0, secs => 0, width => $width, _width => -32, + # extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), bars, left space) + params => [$VISUALIZER_SPECTRUM_ANALYZER, 32, 32, -32, 0, 2, 6] }, + # mode 9 + { desc => ['VISUALIZER_VUMETER'], + bar => 0, secs => 0, width => $width, + params => [$VISUALIZER_VUMETER] }, + # mode 10 + { desc => ['VISUALIZER_SPECTRUM_ANALYZER'], + bar => 0, secs => 0, width => $width, + # extra parameters (bars) + params => [$VISUALIZER_SPECTRUM_ANALYZER, 16] }, + # mode 11 + { desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'], + bar => 0, secs => 1, width => $width, + params => [$VISUALIZER_VUMETER] }, + # mode 12 + { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'], + bar => 0, secs => 1, width => $width, + # extra parameters (bars) + params => [$VISUALIZER_SPECTRUM_ANALYZER, 16] }, + # mode 13 + { desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'], + bar => 0, secs => -1, width => $width, + params => [$VISUALIZER_VUMETER] }, + # mode 14 + { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'], + bar => 0, secs => -1, width => $width, + # extra parameters (bars) + params => [$VISUALIZER_SPECTRUM_ANALYZER, 16] }, + ); sub modes { @@ -52,6 +94,27 @@ sub nmodes { return $#modes; } +sub displayWidth { + my $display = shift; + my $client = $display->client; + + # if we're showing the always-on visualizer & the current buttonmode + # hasn't overridden, then use the playing display mode to index + # into the display width, otherwise, it's fullscreen. + my $mode = 0; + + if ( $display->showVisualizer() && !defined($client->modeParam('visu')) ) { + my $cprefs = preferences('server')->client($client); + $mode = $cprefs->get('playingDisplayModes')->[ $cprefs->get('playingDisplayMode') ]; + } + + if ($display->widthOverride) { + return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0); + } else { + return $display->modes->[$mode || 0]{width}; + } +} + # I don't think LMS renderer handles properly screens other than 32 pixels. It # seems that all we get is a 32 pixel-tall data with anything else padded to 0 # i.e. if we try 64 pixels height, bytes 0..3 and 4..7 will contains the same @@ -72,8 +135,12 @@ sub displayHeight { return 32; } -sub displayWidth { - return shift->widthOverride(@_) || $width; +sub updateWidth { + my ($display, $width) = @_; + + foreach my $mode (@{$display->modes}) { + $mode->{width} = $width + 1 + $mode->{_width} || 0; + } } sub vfdmodel { diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index d92cd4f3..548170f5 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.9 + 0.10 Philippe diff --git a/plugin/repo.xml b/plugin/repo.xml index 03f6c740..81067947 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,10 +1,10 @@ - + https://github.com/sle118/squeezelite-esp32 Philippe - 89c68b54ad4373df6c0cd37222a07b53013c4815 + 5a35a7b821e887baf869535f113b8b55bbc52a54 philippe_44@outlook.com SqueezeESP32 additional player id (100) http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip From 86f5a19cdd1f375c4cf00f448cdb87dea724fb03 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 16 Feb 2020 23:35:40 -0800 Subject: [PATCH 04/15] forgot to add BT in visualizer --- components/squeezelite/output_bt.c | 2 ++ components/squeezelite/output_i2s.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/squeezelite/output_bt.c b/components/squeezelite/output_bt.c index 6a3a42f0..e4c5ba1a 100644 --- a/components/squeezelite/output_bt.c +++ b/components/squeezelite/output_bt.c @@ -120,6 +120,8 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g u8_t *buf = silencebuf; memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME); } + + output_visu_export((s16_t*) (btout + oframes * BYTES_PER_FRAME), out_frames, output.current_sample_rate, silence); return (int)out_frames; } diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index 666ec9ac..3a6db561 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -365,7 +365,6 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32 _scale_and_pack_frames(obuf + oframes * bytes_per_frame, optr, out_frames, gainL, gainR, output.format); #endif - // send data to visu output_visu_export((s16_t*) (obuf + oframes * bytes_per_frame), out_frames, output.current_sample_rate, silence); oframes += out_frames; From fe5f9feeafbf4483957cc12414758eaecbbba90d Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 17 Feb 2020 00:19:15 -0800 Subject: [PATCH 05/15] stop scrolling when activating visualizer on small screens --- components/squeezelite/display.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index 2a982f48..c1ca5620 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -729,8 +729,13 @@ static void visu_handler( u8_t *data, int len) { visu.bar_border = (visu.width - visu.border - (visu.bar_width + visu.bar_gap) * visu.n + visu.bar_gap) / 2; // give up if not enough space - if (visu.bar_width < 0) visu.mode = VISU_BLANK; - else vTaskResume(displayer.task); + if (visu.bar_width < 0) { + visu.mode = VISU_BLANK; + } else { + // de-activate scroller if we are taking main screen + if (visu.row < SB_HEIGHT) scroller.active = false; + vTaskResume(displayer.task); + } visu.wake = 0; // reset bars maximum From 4e7ff0a37aedad943a4e2ac1eb505442b2fcf862 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 00:57:33 -0800 Subject: [PATCH 06/15] AirPlay co-existence improvements, couple of display issues --- components/display/display.c | 3 + components/display/display.h | 8 +- components/display/driver_SSD13x6.c | 9 +- components/raop/raop.c | 98 +++++++++-------- components/raop/raop.h | 3 +- components/raop/raop_sink.c | 3 +- components/squeezelite/decode_external.c | 18 ++- components/squeezelite/display.c | 134 +++++++++++++++-------- components/squeezelite/slimproto.c | 69 ++++++------ 9 files changed, 203 insertions(+), 142 deletions(-) diff --git a/components/display/display.c b/components/display/display.c index 8ccfa419..03713e5b 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -301,16 +301,19 @@ void displayer_control(enum displayer_cmd_e cmd, ...) { displayer.string[0] = '\0'; displayer.elapsed = displayer.duration = 0; displayer.offset = displayer.boundary = 0; + display_bus(&displayer, DISPLAY_BUS_TAKE); vTaskResume(displayer.task); break; } case DISPLAYER_SUSPEND: // task will display the line 2 from beginning and suspend displayer.state = DISPLAYER_IDLE; + display_bus(&displayer, DISPLAY_BUS_GIVE); break; case DISPLAYER_SHUTDOWN: // let the task self-suspend (we might be doing i2c_write) displayer.state = DISPLAYER_DOWN; + display_bus(&displayer, DISPLAY_BUS_GIVE); break; case DISPLAYER_TIMER_RUN: if (!displayer.timer) { diff --git a/components/display/display.h b/components/display/display.h index 31f0f5e7..da2bb4f4 100644 --- a/components/display/display.h +++ b/components/display/display.h @@ -28,6 +28,9 @@ So it can conflict with other display direct writes that have been made during sleep. Note that if DISPLAY_SHUTDOWN has been called meanwhile, it (almost) never happens + The display_bus() shall be subscribed by other displayers so that at least + when this one (the main) wants to take control over display, it can signal + that to others */ #define DISPLAY_CLEAR 0x01 @@ -67,7 +70,10 @@ extern struct display_s { void (*draw_box)( int x1, int y1, int x2, int y2, bool fill); } *display; +enum display_bus_cmd_e { DISPLAY_BUS_TAKE, DISPLAY_BUS_GIVE }; +bool (*display_bus)(void *from, enum display_bus_cmd_e cmd); + void displayer_scroll(char *string, int speed); void displayer_control(enum displayer_cmd_e cmd, ...); void displayer_metadata(char *artist, char *album, char *title); -void displayer_timer(enum displayer_time_e mode, int elapsed, int duration); \ No newline at end of file +void displayer_timer(enum displayer_time_e mode, int elapsed, int duration); diff --git a/components/display/driver_SSD13x6.c b/components/display/driver_SSD13x6.c index baec7144..f2fd8cc5 100644 --- a/components/display/driver_SSD13x6.c +++ b/components/display/driver_SSD13x6.c @@ -167,8 +167,8 @@ static void clear(bool full, ...) { va_end(args); } + SSD13x6_display.dirty = true; if (commit) update(); - else SSD13x6_display.dirty = true; } /**************************************************************************************** @@ -247,9 +247,9 @@ static bool line(int num, int x, int attribute, char *text) { ESP_LOGD(TAG, "displaying %s line %u (x:%d, attr:%u)", text, num+1, x, attribute); // update whole display if requested + SSD13x6_display.dirty = true; if (attribute & DISPLAY_UPDATE) update(); - else SSD13x6_display.dirty = true; - + return width + x < Display.Width; } @@ -340,8 +340,9 @@ static void text(enum display_font_e font, enum display_pos_e pos, int attribute ESP_LOGD(TAG, "SSDD13x6 displaying %s at %u with attribute %u", text, Anchor, attribute); SSD13x6_FontDrawAnchoredString( &Display, Anchor, text, SSD_COLOR_WHITE ); + + SSD13x6_display.dirty = true; if (attribute & DISPLAY_UPDATE) update(); - else SSD13x6_display.dirty = true; va_end(args); } diff --git a/components/raop/raop.c b/components/raop/raop.c index db6c24cb..f5d7b966 100644 --- a/components/raop/raop.c +++ b/components/raop/raop.c @@ -55,7 +55,7 @@ typedef struct raop_ctx_s { short unsigned port; // RTSP port for AirPlay int sock; // socket of the above struct in_addr peer; // IP of the iDevice (airplay sender) - bool running; + bool running, abort; #ifdef WIN32 pthread_t thread, search_thread; #else @@ -83,6 +83,7 @@ typedef struct raop_ctx_s { TaskHandle_t thread, joiner; StaticTask_t *xTaskBuffer; StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));; + SemaphoreHandle_t destroy_mutex; #endif } active_remote; void *owner; @@ -93,6 +94,7 @@ extern log_level raop_loglevel; static log_level *loglevel = &raop_loglevel; static void* rtsp_thread(void *arg); +static void abort_rtsp(raop_ctx_t *ctx); static bool handle_rtsp(raop_ctx_t *ctx, int sock); static char* rsa_apply(unsigned char *input, int inlen, int *outlen, int mode); @@ -198,6 +200,12 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name, return ctx; } +/*----------------------------------------------------------------------------*/ +void raop_abort(struct raop_ctx_s *ctx) { + LOG_INFO("[%p]: aborting RTSP session at next select() wakeup", ctx); + ctx->abort = true; +} + /*----------------------------------------------------------------------------*/ void raop_delete(struct raop_ctx_s *ctx) { #ifdef WIN32 @@ -270,7 +278,7 @@ if (!ctx) return; } /*----------------------------------------------------------------------------*/ -void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { +bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { struct sockaddr_in addr; int sock; char *command = NULL; @@ -323,7 +331,7 @@ void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { // no command to send to remote or no remote found yet if (!command || !ctx->active_remote.port) { NFREE(command); - return; + return false; } sock = socket(AF_INET, SOCK_STREAM, 0); @@ -354,6 +362,8 @@ void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { free(command); closesocket(sock); + + return true; } /*----------------------------------------------------------------------------*/ @@ -373,6 +383,7 @@ static void *rtsp_thread(void *arg) { sock = accept(ctx->sock, (struct sockaddr*) &peer, &addrlen); ctx->peer.s_addr = peer.sin_addr.s_addr; + ctx->abort = false; if (sock != -1 && ctx->running) { LOG_INFO("got RTSP connection %u", sock); @@ -383,12 +394,13 @@ static void *rtsp_thread(void *arg) { FD_SET(sock, &rfds); n = select(sock + 1, &rfds, NULL, NULL, &timeout); - - if (!n) continue; + + if (!n && !ctx->abort) continue; if (n > 0) res = handle_rtsp(ctx, sock); - if (n < 0 || !res) { + if (n < 0 || !res || ctx->abort) { + abort_rtsp(ctx); closesocket(sock); LOG_INFO("RTSP close %u", sock); sock = -1; @@ -460,27 +472,6 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) NFREE(ctx->rtsp.aeskey); NFREE(ctx->rtsp.aesiv); NFREE(ctx->rtsp.fmtp); - - // LMS might has taken over the player, leaving us with a running RTP session (should not happen) - if (ctx->rtp) { - LOG_WARN("[%p]: closing unfinished RTP session", ctx); - rtp_end(ctx->rtp); - } - - // same, should not happen unless we have missed a teardown ... - if (ctx->active_remote.running) { - ctx->active_remote.joiner = xTaskGetCurrentTaskHandle(); - ctx->active_remote.running = false; - - vTaskResume(ctx->active_remote.thread); - ulTaskNotifyTake(pdFALSE, portMAX_DELAY); - vTaskDelete(ctx->active_remote.thread); - - heap_caps_free(ctx->active_remote.xTaskBuffer); - memset(&ctx->active_remote, 0, sizeof(ctx->active_remote)); - - LOG_WARN("[%p]: closing unfinished mDNS search", ctx); - } if ((p = strcasestr(body, "rsaaeskey")) != NULL) { unsigned char *aeskey; @@ -522,6 +513,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) pthread_create(&ctx->search_thread, NULL, &search_remote, ctx); #else ctx->active_remote.running = true; + ctx->active_remote.destroy_mutex = xSemaphoreCreateMutex(); ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); ctx->active_remote.thread = xTaskCreateStatic( (TaskFunction_t) search_remote, "search_remote", SEARCH_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->active_remote.xStack, ctx->active_remote.xTaskBuffer); #endif @@ -600,11 +592,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) ctx->active_remote.joiner = xTaskGetCurrentTaskHandle(); ctx->active_remote.running = false; - // task might not need to be resumed anyway - vTaskResume(ctx->active_remote.thread); - ulTaskNotifyTake(pdFALSE, portMAX_DELAY); + xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY); vTaskDelete(ctx->active_remote.thread); - + vSemaphoreDelete(ctx->active_remote.thread); + heap_caps_free(ctx->active_remote.xTaskBuffer); LOG_INFO("[%p]: mDNS search task terminated", ctx); @@ -681,6 +672,35 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) return true; } +/*----------------------------------------------------------------------------*/ +void abort_rtsp(raop_ctx_t *ctx) { + // first stop RTP process + if (ctx->rtp) { + rtp_end(ctx->rtp); + ctx->rtp = NULL; + LOG_INFO("[%p]: RTP thread aborted", ctx); + } + + if (ctx->active_remote.running) { + // need to make sure no search is on-going and reclaim task memory + ctx->active_remote.joiner = xTaskGetCurrentTaskHandle(); + ctx->active_remote.running = false; + + xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY); + vTaskDelete(ctx->active_remote.thread); + vSemaphoreDelete(ctx->active_remote.thread); + + heap_caps_free(ctx->active_remote.xTaskBuffer); + memset(&ctx->active_remote, 0, sizeof(ctx->active_remote)); + + LOG_INFO("[%p]: Remote search thread aborted", ctx); + } + + NFREE(ctx->rtsp.aeskey); + NFREE(ctx->rtsp.aesiv); + NFREE(ctx->rtsp.fmtp); +} + /*----------------------------------------------------------------------------*/ #ifdef WIN32 bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) { @@ -746,20 +766,10 @@ static void* search_remote(void *args) { mdns_query_results_free(results); } - /* - for some reason which is beyond me, if that tasks gives the semaphore - before the RTSP tasks waits for it, then freeRTOS crashes in queue - management caused by LWIP stack once the RTSP socket is closed. I have - no clue why, but so we'll suspend the tasks as soon as we're done with - search and wait for the resume then give the semaphore - */ - // PS: I know this is not fully race-condition free - if (ctx->active_remote.running) vTaskSuspend(NULL); - xTaskNotifyGive(ctx->active_remote.joiner); - - // now our context will be deleted + // can't use xNotifyGive as it seems LWIP is using it as well + xSemaphoreGive(ctx->active_remote.destroy_mutex); vTaskSuspend(NULL); - + return NULL; } #endif diff --git a/components/raop/raop.h b/components/raop/raop.h index 5c6d0acf..5009be09 100644 --- a/components/raop/raop.h +++ b/components/raop/raop.h @@ -27,6 +27,7 @@ struct raop_ctx_s* raop_create(struct in_addr host, char *name, unsigned char mac[6], int latency, raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb); void raop_delete(struct raop_ctx_s *ctx); -void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param); +void raop_abort(struct raop_ctx_s *ctx); +bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param); #endif diff --git a/components/raop/raop_sink.c b/components/raop/raop_sink.c index 0627a794..1424e94f 100644 --- a/components/raop/raop_sink.c +++ b/components/raop/raop_sink.c @@ -178,6 +178,7 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) { void raop_disconnect(void) { LOG_INFO("forced disconnection"); displayer_control(DISPLAYER_SHUTDOWN); - raop_cmd(raop, RAOP_STOP, NULL); + // in case we can't communicate with AirPlay controller, abort session + if (!raop_cmd(raop, RAOP_STOP, NULL)) raop_abort(raop); actrls_unset(); } diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index 3cf0c698..23c0fc08 100644 --- a/components/squeezelite/decode_external.c +++ b/components/squeezelite/decode_external.c @@ -46,7 +46,7 @@ static bool enable_airplay; #define SYNC_NB 5 static raop_event_t raop_state; -static bool raop_expect_stop = false; + static struct { bool enabled, start; s32_t error[SYNC_NB]; @@ -123,9 +123,8 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args) LOG_INFO("BT sink started"); break; case BT_SINK_AUDIO_STOPPED: - // do we still need that? if (output.external == DECODE_BT) { - output.state = OUTPUT_OFF; + if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED; LOG_INFO("BT sink stopped"); } break; @@ -136,6 +135,7 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args) case BT_SINK_STOP: _buf_flush(outputbuf); output.state = OUTPUT_STOPPED; + output.stop_time = gettime_ms(); LOG_INFO("BT sink stopped"); break; case BT_SINK_PAUSE: @@ -253,18 +253,14 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args) output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE; break; case RAOP_STOP: - LOG_INFO("Stop", NULL); - output.state = OUTPUT_OFF; - output.frames_played = 0; - raop_state = event; - break; case RAOP_FLUSH: - LOG_INFO("Flush", NULL); - raop_expect_stop = true; + if (event == RAOP_FLUSH) { LOG_INFO("Flush", NULL); } + else { LOG_INFO("Stop", NULL); } raop_state = event; _buf_flush(outputbuf); - output.state = OUTPUT_STOPPED; + if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED; output.frames_played = 0; + output.stop_time = gettime_ms(); break; case RAOP_PLAY: { LOG_INFO("Play", NULL); diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index c1ca5620..bd805b28 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -84,7 +84,10 @@ extern struct outputstate output; static struct { TaskHandle_t task; SemaphoreHandle_t mutex; -} displayer; + int width, height; + bool dirty; + bool owned; +} displayer = { .dirty = true, .owned = true }; #define LONG_WAKE (10*1000) #define SB_HEIGHT 32 @@ -147,26 +150,28 @@ static u8_t SETD_width; #define LINELEN 40 static log_level loglevel = lINFO; + static bool (*slimp_handler_chain)(u8_t *data, int len); static void (*slimp_loop_chain)(void); static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport); -static int display_width, display_height; -static bool display_dirty = true; +static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd); #define max(a,b) (((a) > (b)) ? (a) : (b)) static void server(in_addr_t ip, u16_t hport, u16_t cport); static void send_server(void); static bool handler(u8_t *data, int len); +static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd); static void vfdc_handler( u8_t *_data, int bytes_read); static void grfe_handler( u8_t *data, int len); static void grfb_handler(u8_t *data, int len); static void grfs_handler(u8_t *data, int len); static void grfg_handler(u8_t *data, int len); -static void visu_handler( u8_t *data, int len); +static void visu_handler(u8_t *data, int len); static void displayer_task(void* arg); + /* scrolling undocumented information grfs B: screen number @@ -208,8 +213,8 @@ bool sb_display_init(void) { } // need to force height to 32 maximum - display_width = display->width; - display_height = min(display->height, SB_HEIGHT); + displayer.width = display->width; + displayer.height = min(display->height, SB_HEIGHT); SETD_width = display->width; // create visu configuration @@ -223,10 +228,10 @@ bool sb_display_init(void) { displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer); // size scroller - scroller.scroll.max = (display_width * display_height / 8) * 10; + scroller.scroll.max = (displayer.width * displayer.height / 8) * 10; scroller.scroll.frame = malloc(scroller.scroll.max); - scroller.back.frame = malloc(display_width * display_height / 8); - scroller.frame = malloc(display_width * display_height / 8); + scroller.back.frame = malloc(displayer.width * displayer.height / 8); + scroller.frame = malloc(displayer.width * displayer.height / 8); // chain handlers slimp_handler_chain = slimp_handler; @@ -238,9 +243,39 @@ bool sb_display_init(void) { notify_chain = server_notify; server_notify = server; + display_bus_chain = display_bus; + display_bus = display_bus_handler; + return true; } +/**************************************************************************************** + * Receive display bus commands + */ +static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd) { + // don't answer to own requests + if (from == &displayer) return false ; + + LOG_INFO("Display bus command %d", cmd); + + xSemaphoreTake(displayer.mutex, portMAX_DELAY); + + switch (cmd) { + case DISPLAY_BUS_TAKE: + displayer.owned = false; + break; + case DISPLAY_BUS_GIVE: + displayer.owned = true; + break; + } + + xSemaphoreGive(displayer.mutex); + + if (display_bus_chain) return (*display_bus_chain)(from, cmd); + else return true; +} + + /**************************************************************************************** * Send message to server (ANIC at that time) */ @@ -289,9 +324,9 @@ static void send_server(void) { static void server(in_addr_t ip, u16_t hport, u16_t cport) { char msg[32]; sprintf(msg, "%s:%hu", inet_ntoa(ip), hport); - display->text(DISPLAY_FONT_DEFAULT, DISPLAY_CENTERED, DISPLAY_CLEAR | DISPLAY_UPDATE, msg); + if (displayer.owned) display->text(DISPLAY_FONT_DEFAULT, DISPLAY_CENTERED, DISPLAY_CLEAR | DISPLAY_UPDATE, msg); SETD_width = display->width; - display_dirty = true; + displayer.dirty = true; if (notify_chain) (*notify_chain)(ip, hport, cport); } @@ -301,24 +336,21 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) { static bool handler(u8_t *data, int len){ bool res = true; - // don't do anything if we dont own the display (no lock needed) - if (!output.external || output.state < OUTPUT_STOPPED) { - if (!strncmp((char*) data, "vfdc", 4)) { - vfdc_handler(data, len); - } else if (!strncmp((char*) data, "grfe", 4)) { - grfe_handler(data, len); - } else if (!strncmp((char*) data, "grfb", 4)) { - grfb_handler(data, len); - } else if (!strncmp((char*) data, "grfs", 4)) { - grfs_handler(data, len); - } else if (!strncmp((char*) data, "grfg", 4)) { - grfg_handler(data, len); - } else if (!strncmp((char*) data, "visu", 4)) { - visu_handler(data, len); - } else { - res = false; - } - } + if (!strncmp((char*) data, "vfdc", 4)) { + vfdc_handler(data, len); + } else if (!strncmp((char*) data, "grfe", 4)) { + grfe_handler(data, len); + } else if (!strncmp((char*) data, "grfb", 4)) { + grfb_handler(data, len); + } else if (!strncmp((char*) data, "grfs", 4)) { + grfs_handler(data, len); + } else if (!strncmp((char*) data, "grfg", 4)) { + grfg_handler(data, len); + } else if (!strncmp((char*) data, "visu", 4)) { + visu_handler(data, len); + } else { + res = false; + } // chain protocol handlers (bitwise or is fine) if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len); @@ -448,20 +480,23 @@ static void grfe_handler( u8_t *data, int len) { scroller.active = false; - // if we are displaying visu on a small screen, do not do screen update + // we are not in control or we are displaying visu on a small screen, do not do screen update if (visu.mode && !visu.col && visu.row < SB_HEIGHT) { xSemaphoreGive(displayer.mutex); return; } - // did we have something that might have write on the bottom of a SB_HEIGHT+ display - if (display_dirty) { - display->clear(true); - display_dirty = false; - } + if (displayer.owned) { + // did we have something that might have write on the bottom of a SB_HEIGHT+ display + if (displayer.dirty) { + display->clear(true); + displayer.dirty = false; + } - display->draw_cbr(data + sizeof(struct grfe_packet), display_width, display_height); - display->update(); + // draw new frame + display->draw_cbr(data + sizeof(struct grfe_packet), displayer.width, displayer.height); + display->update(); + } xSemaphoreGive(displayer.mutex); @@ -518,7 +553,7 @@ static void grfs_handler(u8_t *data, int len) { scroller.first = true; // background excludes space taken by visu (if any) - scroller.back.width = display_width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0); + scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0); // set scroller steps & beginning if (pkt->direction == 1) { @@ -557,10 +592,14 @@ static void grfg_handler(u8_t *data, int len) { memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet)); // update display asynchronously (frames are oganized by columns) - memcpy(scroller.frame, scroller.back.frame, scroller.back.width * display_height / 8); - for (int i = 0; i < scroller.width * display_height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * display_height / 8 + i]; - display->draw_cbr(scroller.frame, scroller.back.width, display_height); - display->update(); + memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8); + for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i]; + + // can only write if we really own display + if (displayer.owned) { + display->draw_cbr(scroller.frame, scroller.back.width, displayer.height); + display->update(); + } // now we can active scrolling, but only if we are not on a small screen if (!visu.mode || visu.col || visu.row >= SB_HEIGHT) scroller.active = true; @@ -578,6 +617,7 @@ static void grfg_handler(u8_t *data, int len) { * Update visualization bars */ static void visu_update(void) { + // no need to protect against no woning the display as we are playing if (pthread_mutex_trylock(&visu_export.mutex)) return; // not enough samples @@ -731,6 +771,7 @@ static void visu_handler( u8_t *data, int len) { // give up if not enough space if (visu.bar_width < 0) { visu.mode = VISU_BLANK; + LOG_WARN("Not enough room for displaying visu"); } else { // de-activate scroller if we are taking main screen if (visu.row < SB_HEIGHT) scroller.active = false; @@ -781,10 +822,10 @@ static void displayer_task(void *args) { // do we have more to scroll (scroll.width is the last column from which we havea full zone) if (scroller.by > 0 ? (scroller.scrolled <= scroller.scroll.width) : (scroller.scrolled >= 0)) { - memcpy(scroller.frame, scroller.back.frame, scroller.back.width * display_height / 8); - for (int i = 0; i < scroller.width * display_height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * display_height / 8 + i]; + memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8); + for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i]; scroller.scrolled += scroller.by; - display->draw_cbr(scroller.frame, scroller.width, display_height); + if (displayer.owned) display->draw_cbr(scroller.frame, scroller.width, displayer.height); // short sleep & don't need background update scroller.wake = scroller.speed; @@ -813,7 +854,8 @@ static void displayer_task(void *args) { visu.wake = 100; } - display->update(); + // need to make sure we own display + if (displayer.owned) display->update(); // release semaphore and sleep what's needed xSemaphoreGive(displayer.mutex); diff --git a/components/squeezelite/slimproto.c b/components/squeezelite/slimproto.c index 73d59d66..af7f7bde 100644 --- a/components/squeezelite/slimproto.c +++ b/components/squeezelite/slimproto.c @@ -690,46 +690,47 @@ static void slimproto_run() { UNLOCK_D; LOCK_O; - status.output_full = _buf_used(outputbuf); - status.output_size = outputbuf->size; - status.frames_played = output.frames_played_dmp; - status.current_sample_rate = output.current_sample_rate; - status.updated = output.updated; - status.device_frames = output.device_frames; - - if (output.track_started) { - _sendSTMs = true; - output.track_started = false; - status.stream_start = output.track_start_time; - } + if (!output.external) { + status.output_full = _buf_used(outputbuf); + status.output_size = outputbuf->size; + status.frames_played = output.frames_played_dmp; + status.current_sample_rate = output.current_sample_rate; + status.updated = output.updated; + status.device_frames = output.device_frames; + + if (output.track_started) { + _sendSTMs = true; + output.track_started = false; + status.stream_start = output.track_start_time; + } #if PORTAUDIO - if (output.pa_reopen) { - _pa_open(); - output.pa_reopen = false; - } + if (output.pa_reopen) { + _pa_open(); + output.pa_reopen = false; + } #endif - if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) { - output.state = OUTPUT_BUFFER; - } - if (!output.external && output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT && - _decode_state == DECODE_STOPPED) { + if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) { + output.state = OUTPUT_BUFFER; + } + if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT && + _decode_state == DECODE_STOPPED) { - _sendSTMu = true; - sentSTMu = true; - LOG_DEBUG("output underrun"); - output.state = OUTPUT_STOPPED; - output.stop_time = now; - } - if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) { - - _sendSTMo = true; - sentSTMo = true; - } + _sendSTMu = true; + sentSTMu = true; + LOG_DEBUG("output underrun"); + output.state = OUTPUT_STOPPED; + output.stop_time = now; + } + if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) { + _sendSTMo = true; + sentSTMo = true; + } + } if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) { output.state = OUTPUT_OFF; LOG_DEBUG("output timeout"); - } - if (!output.external && output.state == OUTPUT_RUNNING && now - status.last > 1000) { + } + if (output.state == OUTPUT_RUNNING && now - status.last > 1000) { _sendSTMt = true; status.last = now; } From d78c50f78164807c795a75a319bd239889cb761f Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 18:37:03 -0800 Subject: [PATCH 07/15] "log-like" scale on spectrum visu --- components/squeezelite/display.c | 88 ++++++++++++------ plugin/SqueezeESP32.zip | Bin 5170 -> 5408 bytes plugin/SqueezeESP32/Graphics.pm | 17 +--- .../plugins/SqueezeESP32/settings/basic.html | 5 + plugin/SqueezeESP32/Plugin.pm | 1 + plugin/SqueezeESP32/Settings.pm | 3 +- plugin/SqueezeESP32/install.xml | 2 +- plugin/SqueezeESP32/strings.txt | 8 ++ plugin/repo.xml | 4 +- 9 files changed, 83 insertions(+), 45 deletions(-) diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index bd805b28..0fedb87e 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -61,14 +61,20 @@ struct visu_packet { u8_t which; u8_t count; union { - u32_t width; - u32_t full_bars; + struct { + u32_t bars; + u32_t spectrum_scale; + } full; + struct { + u32_t width; + u32_t height; + s32_t col; + s32_t row; + u32_t border; + u32_t bars; + u32_t spectrum_scale; + }; }; - u32_t height; - s32_t col; - s32_t row; - u32_t border; - u32_t small_bars; }; struct ANIC_header { @@ -98,8 +104,7 @@ static struct { #define RMS_LEN_BIT 6 #define RMS_LEN (1 << RMS_LEN_BIT) -// actually this is 2x the displayed BW -#define DISPLAY_BW 32000 +#define DISPLAY_BW 20000 static struct scroller_s { // copy of grfs content @@ -128,9 +133,10 @@ static struct scroller_s { static EXT_RAM_ATTR struct { int bar_gap, bar_width, bar_border; struct { - int current; - int max; + int current, max; + int limit; } bars[MAX_BARS]; + float spectrum_scale; int n, col, row, height, width, border; enum { VISU_BLANK, VISU_VUMETER, VISU_SPECTRUM, VISU_WAVEFORM } mode; int speed, wake; @@ -659,13 +665,14 @@ static void visu_update(void) { // actual FFT that might be less cycle than all the crap below dsps_fft2r_fc32_ae32(visu.samples, FFT_LEN); dsps_bit_rev_fc32_ansi(visu.samples, FFT_LEN); + float rate = visu_export.rate; // now arrange the result with the number of bar and sampling rate (don't want DC) - for (int i = 1, j = 1; i <= visu.n && j < (FFT_LEN / 2); i++) { + for (int i = 0, j = 1; i < visu.n && j < (FFT_LEN / 2); i++) { float power, count; // find the next point in FFT (this is real signal, so only half matters) - for (count = 0, power = 0; j * visu.n * visu_export.rate < i * (FFT_LEN / 2) * DISPLAY_BW && j < (FFT_LEN / 2); j++, count += 1) { + for (count = 0, power = 0; j * visu_export.rate < visu.bars[i].limit * FFT_LEN && j < FFT_LEN / 2; j++, count += 1) { power += visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]; } // due to sample rate, we have reached the end of the available spectrum @@ -674,7 +681,7 @@ static void visu_update(void) { if (count) power /= count * 2.; } else if (count) { // how much of what remains do we need to add - float ratio = j - (float) (i * DISPLAY_BW * (FFT_LEN / 2)) / (float) (visu.n * visu_export.rate); + float ratio = j - (visu.bars[i].limit * FFT_LEN) / rate; power += (visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]) * ratio; // normalize accumulated data @@ -685,9 +692,9 @@ static void visu_update(void) { } // convert to dB and bars, same back-off - if (power) visu.bars[i-1].current = 32 * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f); - if (visu.bars[i-1].current > 31) visu.bars[i-1].current = 31; - else if (visu.bars[i-1].current < 0) visu.bars[i-1].current = 0; + if (power) visu.bars[i].current = 32 * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f); + if (visu.bars[i].current > 31) visu.bars[i].current = 31; + else if (visu.bars[i].current < 0) visu.bars[i].current = 0; } } } @@ -715,6 +722,22 @@ static void visu_update(void) { } } + +/**************************************************************************************** + * Visu packet handler + */ +void spectrum_limits(int min, int n, int pos) { + if (n / 2) { + int i; + float step = (DISPLAY_BW - min) * visu.spectrum_scale / (n/2); + visu.bars[pos].limit = min + step; + for (i = 1; i < n/2; i++) visu.bars[pos+i].limit = visu.bars[pos+i-1].limit + step; + spectrum_limits(visu.bars[pos + n/2 - 1].limit, n/2, pos + n/2); + } else { + visu.bars[pos].limit = DISPLAY_BW; + } +} + /**************************************************************************************** * Visu packet handler */ @@ -737,31 +760,38 @@ static void visu_handler( u8_t *data, int len) { if (visu.row >= SB_HEIGHT) display->clear(false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1); if (visu.mode) { - pkt->width = htonl(pkt->width); - pkt->height = htonl(pkt->height); - pkt->row = htonl(pkt->row); - pkt->col = htonl(pkt->col); - - if (pkt->width > 0 && pkt->count >= 4) { + if (pkt->count >= 4) { // small visu, then go were we are told to - visu.width = pkt->width; + pkt->height = htonl(pkt->height); + pkt->row = htonl(pkt->row); + pkt->col = htonl(pkt->col); + + visu.width = htonl(pkt->width); visu.height = pkt->height ? pkt->height : SB_HEIGHT; visu.col = pkt->col < 0 ? display->width + pkt->col : pkt->col; visu.row = pkt->row < 0 ? display->height + pkt->row : pkt->row; visu.border = htonl(pkt->border); - bars = htonl(pkt->small_bars); + bars = htonl(pkt->bars); + visu.spectrum_scale = htonl(pkt->spectrum_scale) / 100.; } else { // full screen visu, try to use bottom screen if available visu.width = display->width; visu.height = display->height > SB_HEIGHT ? display->height - SB_HEIGHT : display->height; visu.col = visu.border = 0; visu.row = display->height - visu.height; - // already in CPU order - bars = pkt->full_bars; + bars = htonl(pkt->full.bars); + visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.; } // try to adapt to what we have - visu.n = visu.mode == VISU_VUMETER ? 2 : (bars ? bars : MAX_BARS); + if (visu.mode == VISU_SPECTRUM) { + visu.n = bars ? bars : MAX_BARS; + if (visu.spectrum_scale <= 0 || visu.spectrum_scale > 0.5) visu.spectrum_scale = 0.5; + spectrum_limits(0, visu.n, 0); + } else { + visu.n = 2; + } + do { visu.bar_width = (visu.width - visu.border - visu.bar_gap * (visu.n - 1)) / visu.n; if (visu.bar_width > 0) break; @@ -784,7 +814,7 @@ static void visu_handler( u8_t *data, int len) { display->clear(false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1); - LOG_INFO("Visualizer with %u bars of width %d:%d:%d:%d (%w:%u,h:%u,c:%u,r:%u)", visu.n, visu.bar_border, visu.bar_width, visu.bar_gap, visu.border, visu.width, visu.height, visu.col, visu.row); + LOG_INFO("Visualizer with %u bars of width %d:%d:%d:%d (%w:%u,h:%u,c:%u,r:%u,s:%.02f)", visu.n, visu.bar_border, visu.bar_width, visu.bar_gap, visu.border, visu.width, visu.height, visu.col, visu.row, visu.spectrum_scale); } else { LOG_INFO("Stopping visualizer"); } diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index 626d46d9d7a95a9b744c209b23dfca85f745936a..4942fc516a97550f16ee1b9595d03cd495c69b06 100644 GIT binary patch delta 3334 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 a!Iyo&5GIpR9^^oSnP*f0bJG6G^uGa==UI6G diff --git a/plugin/SqueezeESP32/Graphics.pm b/plugin/SqueezeESP32/Graphics.pm index 42ab788e..29b53bc2 100644 --- a/plugin/SqueezeESP32/Graphics.pm +++ b/plugin/SqueezeESP32/Graphics.pm @@ -16,6 +16,7 @@ my $VISUALIZER_SPECTRUM_ANALYZER = 2; my $VISUALIZER_WAVEFORM = 3; my $width = $prefs->get('width') || 128; +my $spectrum_scale = $prefs->get('spectrum_scale') || 0.5; my @modes = ( # mode 0 @@ -55,7 +56,7 @@ my @modes = ( { desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'], bar => 0, secs => 0, width => $width, _width => -32, # extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), bars, left space) - params => [$VISUALIZER_SPECTRUM_ANALYZER, 32, 32, -32, 0, 2, 6] }, + params => [$VISUALIZER_SPECTRUM_ANALYZER, 32, 32, -32, 0, 2, 6, $spectrum_scale] }, # mode 9 { desc => ['VISUALIZER_VUMETER'], bar => 0, secs => 0, width => $width, @@ -64,7 +65,7 @@ my @modes = ( { desc => ['VISUALIZER_SPECTRUM_ANALYZER'], bar => 0, secs => 0, width => $width, # extra parameters (bars) - params => [$VISUALIZER_SPECTRUM_ANALYZER, 16] }, + params => [$VISUALIZER_SPECTRUM_ANALYZER, 16, $spectrum_scale] }, # mode 11 { desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'], bar => 0, secs => 1, width => $width, @@ -73,7 +74,7 @@ my @modes = ( { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'], bar => 0, secs => 1, width => $width, # extra parameters (bars) - params => [$VISUALIZER_SPECTRUM_ANALYZER, 16] }, + params => [$VISUALIZER_SPECTRUM_ANALYZER, 16, $spectrum_scale] }, # mode 13 { desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'], bar => 0, secs => -1, width => $width, @@ -82,7 +83,7 @@ my @modes = ( { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'], bar => 0, secs => -1, width => $width, # extra parameters (bars) - params => [$VISUALIZER_SPECTRUM_ANALYZER, 16] }, + params => [$VISUALIZER_SPECTRUM_ANALYZER, 16, $spectrum_scale] }, ); @@ -135,14 +136,6 @@ sub displayHeight { return 32; } -sub updateWidth { - my ($display, $width) = @_; - - foreach my $mode (@{$display->modes}) { - $mode->{width} = $width + 1 + $mode->{_width} || 0; - } -} - sub vfdmodel { return 'graphic-'.$width.'x32'; } diff --git a/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html b/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html index f6cffbe7..dec653b4 100644 --- a/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html +++ b/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html @@ -6,6 +6,11 @@ [% END %] + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE" desc="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC" %] + + [% END %] + + [% PROCESS settings/footer.html %] diff --git a/plugin/SqueezeESP32/Plugin.pm b/plugin/SqueezeESP32/Plugin.pm index 893500b0..e39979bf 100644 --- a/plugin/SqueezeESP32/Plugin.pm +++ b/plugin/SqueezeESP32/Plugin.pm @@ -10,6 +10,7 @@ my $prefs = preferences('plugin.squeezeesp32'); $prefs->init({ width => 128, + spectrum_scale => 50, }); my $log = Slim::Utils::Log->addLogCategory({ diff --git a/plugin/SqueezeESP32/Settings.pm b/plugin/SqueezeESP32/Settings.pm index 0281fb07..4856383d 100644 --- a/plugin/SqueezeESP32/Settings.pm +++ b/plugin/SqueezeESP32/Settings.pm @@ -17,13 +17,14 @@ sub page { } sub prefs { - return (preferences('plugin.SqueezeESP32'), qw(width)); + return (preferences('plugin.SqueezeESP32'), qw(width spectrum_scale)); } sub handler { my ($class, $client, $params, $callback, @args) = @_; $callback->($client, $params, $class->SUPER::handler($client, $params), @args); + $client->update(); } diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index 548170f5..51365287 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.10 + 0.11 Philippe diff --git a/plugin/SqueezeESP32/strings.txt b/plugin/SqueezeESP32/strings.txt index ae441532..d51fa50e 100644 --- a/plugin/SqueezeESP32/strings.txt +++ b/plugin/SqueezeESP32/strings.txt @@ -9,3 +9,11 @@ PLUGIN_SQUEEZEESP32_DESC PLUGIN_SQUEEZEESP32_WIDTH EN Screen width + +PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE + EN Spectrum scale + +PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC + EN Sets the scale factor % of spectrum visualizer by halves for better representation. + EN For example, 50 means that 50% of spectrum is displayed in 1/2 of the screen, so it's linear... + EN But 25 means that only 25% of spectrum is displayed in 1/2 of the screen, so it's a sort of log diff --git a/plugin/repo.xml b/plugin/repo.xml index 81067947..1d31dcdf 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,10 +1,10 @@ - + https://github.com/sle118/squeezelite-esp32 Philippe - 5a35a7b821e887baf869535f113b8b55bbc52a54 + b923bd7dd412d1897eb8be2dec862f91f1e36dc4 philippe_44@outlook.com SqueezeESP32 additional player id (100) http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip From 72ff203738dd28172b6e24006fe7a2cbde526c07 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 19:17:00 -0800 Subject: [PATCH 08/15] add DSP defines --- build-scripts/ESP32-A1S-sdkconfig.defaults | 5 +++++ build-scripts/I2S-4MFlash-sdkconfig.defaults | 6 ++++++ build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults | 6 ++++++ build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults | 5 +++++ build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults | 5 +++++ build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults | 5 +++++ 6 files changed, 32 insertions(+) diff --git a/build-scripts/ESP32-A1S-sdkconfig.defaults b/build-scripts/ESP32-A1S-sdkconfig.defaults index 4c23a8fe..f7ceb09d 100644 --- a/build-scripts/ESP32-A1S-sdkconfig.defaults +++ b/build-scripts/ESP32-A1S-sdkconfig.defaults @@ -3,6 +3,11 @@ # Espressif IoT Development Framework (ESP-IDF) Project Configuration # +# DSP +CONFIG_DSP_OPTIMIZED=y +CONFIG_DSP_OPTIMIZATION=1 +CONFIG_DSP_MAX_FFT_SIZE_512=y + # ESP32-A1S defaults (with AC101 codec) CONFIG_I2S_NUM=0 CONFIG_I2C_LOCKED=y diff --git a/build-scripts/I2S-4MFlash-sdkconfig.defaults b/build-scripts/I2S-4MFlash-sdkconfig.defaults index 41a0b935..ccedb0f1 100644 --- a/build-scripts/I2S-4MFlash-sdkconfig.defaults +++ b/build-scripts/I2S-4MFlash-sdkconfig.defaults @@ -3,6 +3,12 @@ # Espressif IoT Development Framework (ESP-IDF) Project Configuration # +# DSP +CONFIG_DSP_OPTIMIZED=y +CONFIG_DSP_OPTIMIZATION=1 +CONFIG_DSP_MAX_FFT_SIZE_512=y + +# SqueezeESP32 CONFIG_DISPLAY_CONFIG="" CONFIG_I2C_CONFIG="" CONFIG_SPI_CONFIG="" diff --git a/build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults b/build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults index a783b563..aef05d9e 100644 --- a/build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults +++ b/build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults @@ -3,6 +3,12 @@ # Espressif IoT Development Framework (ESP-IDF) Project Configuration # +# DSP +CONFIG_DSP_OPTIMIZED=y +CONFIG_DSP_OPTIMIZATION=1 +CONFIG_DSP_MAX_FFT_SIZE_512=y + +# SqueezeESP32 CONFIG_DISPLAY_CONFIG="" CONFIG_I2C_CONFIG="" CONFIG_SPI_CONFIG="" diff --git a/build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults b/build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults index 4fa041ad..0baa8e9f 100644 --- a/build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults +++ b/build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults @@ -3,6 +3,11 @@ # Espressif IoT Development Framework (ESP-IDF) Project Configuration # +# DSP +CONFIG_DSP_OPTIMIZED=y +CONFIG_DSP_OPTIMIZATION=1 +CONFIG_DSP_MAX_FFT_SIZE_512=y + # SqueezeAMP defaults CONFIG_JACK_LOCKED=y CONFIG_BAT_LOCKED=y diff --git a/build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults b/build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults index 6038d934..7d5dda32 100644 --- a/build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults +++ b/build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults @@ -3,6 +3,11 @@ # Espressif IoT Development Framework (ESP-IDF) Project Configuration # +# DSP +CONFIG_DSP_OPTIMIZED=y +CONFIG_DSP_OPTIMIZATION=1 +CONFIG_DSP_MAX_FFT_SIZE_512=y + # SqueezeAMP defaults CONFIG_JACK_LOCKED=y CONFIG_BAT_LOCKED=y diff --git a/build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults b/build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults index f0a45d9a..cd87cd99 100644 --- a/build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults +++ b/build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults @@ -3,6 +3,11 @@ # Espressif IoT Development Framework (ESP-IDF) Project Configuration # +# DSP +CONFIG_DSP_OPTIMIZED=y +CONFIG_DSP_OPTIMIZATION=1 +CONFIG_DSP_MAX_FFT_SIZE_512=y + # SqueezeAMP defaults CONFIG_JACK_LOCKED=y CONFIG_BAT_LOCKED=y From 2345503f40af5f65d7d551da1f6131abaa66c3b9 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 19:21:01 -0800 Subject: [PATCH 09/15] adding submodule --- .gitmodules | 3 +++ esp-dsp | 1 + 2 files changed, 4 insertions(+) create mode 160000 esp-dsp diff --git a/.gitmodules b/.gitmodules index e0e2722a..9f498e78 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = components/telnet/libtelnet url = https://github.com/seanmiddleditch/libtelnet branch = develop +[submodule "esp-dsp"] + path = esp-dsp + url = https://github.com/philippe44/esp-dsp diff --git a/esp-dsp b/esp-dsp new file mode 160000 index 00000000..de39220f --- /dev/null +++ b/esp-dsp @@ -0,0 +1 @@ +Subproject commit de39220fb482eeaee4db5707358bf6b97b34fe59 From c666ad8d634ee9c3e64f9111f90d36c8dcd756e6 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 19:30:32 -0800 Subject: [PATCH 10/15] add esp-dsp --- Makefile | 3 ++- Makefile_std.mk | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7eef1133..22c697c5 100644 --- a/Makefile +++ b/Makefile @@ -13,5 +13,6 @@ #recovery: CPPFLAGS+=-DRECOVERY_APPLICATION=1 PROJECT_NAME?=squeezelite +EXTRA_COMPONENT_DIRS := $(PROJECT_PATH)/esp-dsp include $(IDF_PATH)/make/project.mk -CPPFLAGS+= -Wno-error=maybe-uninitialized \ No newline at end of file +CPPFLAGS+= -Wno-error=maybe-uninitialized diff --git a/Makefile_std.mk b/Makefile_std.mk index 55e809c3..b1a67a7d 100644 --- a/Makefile_std.mk +++ b/Makefile_std.mk @@ -1,2 +1,3 @@ PROJECT_NAME?= squeezelite +EXTRA_COMPONENT_DIRS := $(PROJECT_PATH)/esp-dsp include $(IDF_PATH)/make/project.mk \ No newline at end of file From dfb2ef3e3a18a37aa722e0e34233fa497484e7cb Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 21:08:12 -0800 Subject: [PATCH 11/15] project_path? --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 22c697c5..040dfc69 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,6 @@ #recovery: CPPFLAGS+=-DRECOVERY_APPLICATION=1 PROJECT_NAME?=squeezelite -EXTRA_COMPONENT_DIRS := $(PROJECT_PATH)/esp-dsp +EXTRA_COMPONENT_DIRS := $(PROJECT_PATH)/tagada/esp-dsp include $(IDF_PATH)/make/project.mk CPPFLAGS+= -Wno-error=maybe-uninitialized From ac1bd891e8e45fa9d06121ada14be60fb77d396e Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 21:44:37 -0800 Subject: [PATCH 12/15] project_path #2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 040dfc69..d35a1ae8 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,6 @@ #recovery: CPPFLAGS+=-DRECOVERY_APPLICATION=1 PROJECT_NAME?=squeezelite -EXTRA_COMPONENT_DIRS := $(PROJECT_PATH)/tagada/esp-dsp +EXTRA_COMPONENT_DIRS := esp-dsp include $(IDF_PATH)/make/project.mk CPPFLAGS+= -Wno-error=maybe-uninitialized From 2d73ad83489ed63fa619f9bc3666fde6325af210 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 23:24:33 -0800 Subject: [PATCH 13/15] plugin update --- plugin/SqueezeESP32.zip | Bin 5408 -> 5479 bytes plugin/SqueezeESP32/Graphics.pm | 2 +- .../plugins/SqueezeESP32/settings/basic.html | 4 ++++ plugin/SqueezeESP32/Settings.pm | 5 ++--- plugin/SqueezeESP32/install.xml | 2 +- plugin/SqueezeESP32/strings.txt | 6 ++++++ plugin/repo.xml | 2 +- 7 files changed, 15 insertions(+), 6 deletions(-) diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index 4942fc516a97550f16ee1b9595d03cd495c69b06..f9688f9a4295a4ff6de82dcb47d085577f43272e 100644 GIT binary patch delta 3356 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 PPvH~h)SzR6{08Me6^g6B diff --git a/plugin/SqueezeESP32/Graphics.pm b/plugin/SqueezeESP32/Graphics.pm index 29b53bc2..70909e7d 100644 --- a/plugin/SqueezeESP32/Graphics.pm +++ b/plugin/SqueezeESP32/Graphics.pm @@ -16,7 +16,7 @@ my $VISUALIZER_SPECTRUM_ANALYZER = 2; my $VISUALIZER_WAVEFORM = 3; my $width = $prefs->get('width') || 128; -my $spectrum_scale = $prefs->get('spectrum_scale') || 0.5; +my $spectrum_scale = $prefs->get('spectrum_scale') || 50; my @modes = ( # mode 0 diff --git a/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html b/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html index dec653b4..fdf77916 100644 --- a/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html +++ b/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html @@ -1,5 +1,9 @@ [% PROCESS settings/header.html %] + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_BANNER" %] +
[% "PLUGIN_SQUEEZEESP32_BANNER_TEXT" | string %]
+ [% END %] +
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %] diff --git a/plugin/SqueezeESP32/Settings.pm b/plugin/SqueezeESP32/Settings.pm index 4856383d..1ef18504 100644 --- a/plugin/SqueezeESP32/Settings.pm +++ b/plugin/SqueezeESP32/Settings.pm @@ -6,7 +6,7 @@ use strict; use Slim::Utils::Prefs; use Slim::Utils::Log; -my $log = logger('plugin.SqueezeESP32'); +my $log = logger('plugin.squeezeesp32'); sub name { return 'PLUGIN_SQUEEZEESP32'; @@ -17,14 +17,13 @@ sub page { } sub prefs { - return (preferences('plugin.SqueezeESP32'), qw(width spectrum_scale)); + return (preferences('plugin.squeezeesp32'), qw(width spectrum_scale)); } sub handler { my ($class, $client, $params, $callback, @args) = @_; $callback->($client, $params, $class->SUPER::handler($client, $params), @args); - $client->update(); } diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index 51365287..c8f6edc7 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.11 + 0.12 Philippe diff --git a/plugin/SqueezeESP32/strings.txt b/plugin/SqueezeESP32/strings.txt index d51fa50e..d00ff9b5 100644 --- a/plugin/SqueezeESP32/strings.txt +++ b/plugin/SqueezeESP32/strings.txt @@ -4,6 +4,12 @@ WELCOME_TO_SQUEEZEESP32 PLUGIN_SQUEEZEESP32 EN SqueezeESP32 +PLUGIN_SQUEEZEESP32_BANNER + EN WARNING + +PLUGIN_SQUEEZEESP32_BANNER_TEXT + EN You need to restart LMS for these parameters to be taken into account + PLUGIN_SQUEEZEESP32_DESC EN Adds a new player id (100) to enable display with SqueezeESP32 diff --git a/plugin/repo.xml b/plugin/repo.xml index 1d31dcdf..e9debf7c 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -4,7 +4,7 @@ https://github.com/sle118/squeezelite-esp32 Philippe - b923bd7dd412d1897eb8be2dec862f91f1e36dc4 + 3e60650efdff28cd0da9a7e9d0ddccf3a68d350d philippe_44@outlook.com SqueezeESP32 additional player id (100) http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip From 921084a6b7f2ee5da4376050f6a6f5a10dc2d4b9 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 23:29:31 -0800 Subject: [PATCH 14/15] Plugin update (prefs naming) --- plugin/repo.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/repo.xml b/plugin/repo.xml index e9debf7c..51d66d5c 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,7 +1,7 @@ - + https://github.com/sle118/squeezelite-esp32 Philippe 3e60650efdff28cd0da9a7e9d0ddccf3a68d350d From b9de51a12596d3102278e3a6b832e804024fc0de Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 18 Feb 2020 23:44:22 -0800 Subject: [PATCH 15/15] comments - release --- components/squeezelite/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index 0fedb87e..3e7d1b42 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -640,7 +640,7 @@ static void visu_update(void) { if (visu.mode == VISU_VUMETER) { s16_t *iptr = visu_export.buffer; - // calculate sum(X²), try to not overflow at the expense of some precision + // calculate sum(L²+R²), try to not overflow at the expense of some precision for (int i = RMS_LEN; --i >= 0;) { visu.bars[0].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1); iptr++;