From 6342e7b824bb1d8e8201179b0ca92a0277f5b7a8 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 22 Mar 2020 19:59:44 -0700 Subject: [PATCH] add analogue VU - release --- components/display/core/gds.c | 2 + components/display/core/gds.h | 2 + components/display/core/gds_image.c | 108 +++++++++++++++++++---- components/display/core/gds_image.h | 7 +- components/display/core/gds_private.h | 3 +- components/squeezelite/component.mk | 1 + components/squeezelite/display.c | 118 ++++++++++++++++++++------ components/squeezelite/vu.data | Bin 0 -> 245760 bytes plugin/SqueezeESP32.zip | Bin 8123 -> 8199 bytes plugin/SqueezeESP32/Graphics.pm | 35 ++++++-- plugin/SqueezeESP32/install.xml | 2 +- plugin/repo.xml | 4 +- 12 files changed, 225 insertions(+), 57 deletions(-) create mode 100644 components/squeezelite/vu.data diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 26242a60..947cf0ce 100644 --- a/components/display/core/gds.c +++ b/components/display/core/gds.c @@ -158,7 +158,9 @@ bool GDS_Init( struct GDS_Device* Device ) { void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { if (Device->SetContrast) Device->SetContrast( Device, Contrast); } void GDS_SetHFlip( struct GDS_Device* Device, bool On ) { if (Device->SetHFlip) Device->SetHFlip( Device, On ); } void GDS_SetVFlip( struct GDS_Device* Device, bool On ) { if (Device->SetVFlip) Device->SetVFlip( Device, On ); } +void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; } int GDS_GetWidth( struct GDS_Device* Device ) { return Device->Width; } int GDS_GetHeight( struct GDS_Device* Device ) { return Device->Height; } +int GDS_GetDepth( struct GDS_Device* Device ) { return Device->Depth; } void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); } void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); } \ No newline at end of file diff --git a/components/display/core/gds.h b/components/display/core/gds.h index 49cd7c6c..6b4e58c2 100644 --- a/components/display/core/gds.h +++ b/components/display/core/gds.h @@ -35,8 +35,10 @@ void GDS_DisplayOff( struct GDS_Device* Device ); void GDS_Update( struct GDS_Device* Device ); void GDS_SetHFlip( struct GDS_Device* Device, bool On ); void GDS_SetVFlip( struct GDS_Device* Device, bool On ); +void GDS_SetDirty( struct GDS_Device* Device ); int GDS_GetWidth( struct GDS_Device* Device ); int GDS_GetHeight( struct GDS_Device* Device ); +int GDS_GetDepth( struct GDS_Device* Device ); void GDS_ClearExt( struct GDS_Device* Device, bool full, ...); void GDS_Clear( struct GDS_Device* Device, int Color ); void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ); diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c index ebd274e1..d76b564a 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -133,45 +133,118 @@ void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height) { } /**************************************************************************************** - * Simply draw a RGB565 image - * monoschrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b) + * Simply draw a RGB 16bits image + * monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b) * grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) ) */ void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) { if (Device->DrawRGB16) { - Device->DrawRGB16( Device, x, y, Width, Height, RGB_Mode, Image ); + Device->DrawRGB16( Device, Image, x, y, Width, Height, RGB_Mode ); } else { - int Scale = Device->Depth < 5 ? 5 - Device->Depth : 0; switch(RGB_Mode) { case GDS_RGB565: - for (int c = 0; c < Width; c++) { + // 6 bits pixels to be placed. Use a linearized structure for a bit of optimization + if (Device->Depth < 6) { + int Scale = 6 - Device->Depth; for (int r = 0; r < Height; r++) { - int pixel = Image[Width*r + c]; - pixel = ((pixel & 0x1f) * 11 + ((((pixel >> 5) & 0x3f) * 59) >> 1) + (pixel >> 11) * 30) / 100; - GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); + for (int c = 0; c < Width; c++) { + int pixel = *Image++; + pixel = ((((pixel & 0x1f) * 11) << 1) + ((pixel >> 5) & 0x3f) * 59 + (((pixel >> 11) * 30) << 1) + 1) / 100; + GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); + } + } + } else { + int Scale = Device->Depth - 6; + for (int r = 0; r < Height; r++) { + for (int c = 0; c < Width; c++) { + int pixel = *Image++; + pixel = ((((pixel & 0x1f) * 11) << 1) + ((pixel >> 5) & 0x3f) * 59 + (((pixel >> 11) * 30) << 1) + 1) / 100; + GDS_DrawPixel( Device, c + x, r + y, pixel << Scale); + } } } break; case GDS_RGB555: - for (int c = 0; c < Width; c++) { + // 5 bits pixels to be placed Use a linearized structure for a bit of optimization + if (Device->Depth < 5) { + int Scale = 5 - Device->Depth; for (int r = 0; r < Height; r++) { - int pixel = Image[Width*r + c]; - pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100; - GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); + for (int c = 0; c < Width; c++) { + int pixel = *Image++; + pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100; + GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); + } } + } else { + int Scale = Device->Depth - 5; + for (int r = 0; r < Height; r++) { + for (int c = 0; c < Width; c++) { + int pixel = *Image++; + pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100; + GDS_DrawPixel( Device, c + x, r + y, pixel << Scale); + } + } } break; case GDS_RGB444: - for (int c = 0; c < Width; c++) { + // 4 bits pixels to be placed + if (Device->Depth < 4) { + int Scale = 4 - Device->Depth; for (int r = 0; r < Height; r++) { - int pixel = Image[Width*r + c]; - pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30; - GDS_DrawPixel( Device, c + x, r + y, pixel >> (Scale - 1)); + for (int c = 0; c < Width; c++) { + int pixel = *Image++; + pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30; + GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); + } + } + } else { + int Scale = Device->Depth - 4; + for (int r = 0; r < Height; r++) { + for (int c = 0; c < Width; c++) { + int pixel = *Image++; + pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30; + GDS_DrawPixel( Device, c + x, r + y, pixel << Scale); + } } } break; } - } + } + + Device->Dirty = true; +} + +/**************************************************************************************** + * Simply draw a RGB 8 bits image (R:3,G:3,B:2) + * monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b) + * grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) ) + */ +void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height ) { + if (Device->DrawRGB8) { + Device->DrawRGB8( Device, Image, x, y, Width, Height ); + } else if (Device->Depth < 3) { + // 3 bits pixels to be placed + int Scale = 3 - Device->Depth; + for (int r = 0; r < Height; r++) { + for (int c = 0; c < Width; c++) { + int pixel = *Image++; + pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100; + GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); + } + } + } else { + // 3 bits pixels to be placed + int Scale = Device->Depth - 3; + for (int r = 0; r < Height; r++) { + for (int c = 0; c < Width; c++) { + int pixel = *Image++; + pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100; + GDS_DrawPixel( Device, c + x, r + y, pixel << Scale); + } + } + } + + Device->Dirty = true; } //Decode the embedded image into pixel lines that can be used with the rest of the logic. @@ -228,6 +301,7 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int // do decompress & draw Res = jd_decomp(&Decoder, OutHandlerDirect, N); if (Res == JDR_OK) { + Device->Dirty = true; Ret = true; } else { ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res); diff --git a/components/display/core/gds_image.h b/components/display/core/gds_image.h index 0becd94a..ec875fbd 100644 --- a/components/display/core/gds_image.h +++ b/components/display/core/gds_image.h @@ -9,6 +9,7 @@ struct GDS_Device; enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 }; +// Fit options for GDS_DrawJPEG #define GDS_IMAGE_LEFT 0x00 #define GDS_IMAGE_CENTER_X 0x01 #define GDS_IMAGE_RIGHT 0x04 @@ -16,11 +17,11 @@ enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 }; #define GDS_IMAGE_BOTTOM 0x08 #define GDS_IMAGE_CENTER_Y 0x02 #define GDS_IMAGE_CENTER (GDS_IMAGE_CENTER_X | GDS_IMAGE_CENTER_Y) -#define GDS_IMAGE_FIT 0x10 +#define GDS_IMAGE_FIT 0x10 // re-scale by a factor of 2^N (up to 3) // Width and Height can be NULL if you already know them (actual scaling is closest ^2) uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale); void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height); +bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit); void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode ); -// set DisplayWidth and DisplayHeight to non-zero if you want autoscale to closest factor ^2 from 0..3 -bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit); \ No newline at end of file +void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height ); \ No newline at end of file diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 816ddaf0..efb829b4 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -111,7 +111,8 @@ struct GDS_Device { void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color ); void (*DrawBitmapCBR)(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ); // may provide for optimization - void (*DrawRGB16)( struct GDS_Device* Device, int x, int y, int Width, int Height, int RGB_Mode, uint16_t *Image ); + void (*DrawRGB16)( struct GDS_Device* Device, uint16_t *Image,int x, int y, int Width, int Height, int RGB_Mode ); + void (*DrawRGB8)( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height ); void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ); // interface-specific methods diff --git a/components/squeezelite/component.mk b/components/squeezelite/component.mk index c30f0ffa..16d89dcf 100644 --- a/components/squeezelite/component.mk +++ b/components/squeezelite/component.mk @@ -21,4 +21,5 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON COMPONENT_SRCDIRS := . tas57xx a1s external COMPONENT_ADD_INCLUDEDIRS := . ./tas57xx ./a1s +COMPONENT_EMBED_FILES := vu.data diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index e5f36c82..a81da9f5 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -73,9 +73,12 @@ struct visu_packet { u8_t which; u8_t count; union { - struct { - u32_t bars; - u32_t spectrum_scale; + union { + struct { + u32_t bars; + u32_t spectrum_scale; + }; + u32_t style; } full; struct { u32_t width; @@ -137,6 +140,10 @@ static struct { #define RMS_LEN_BIT 6 #define RMS_LEN (1 << RMS_LEN_BIT) +#define VU_WIDTH 160 +#define VU_HEIGHT SB_HEIGHT +#define VU_COUNT 48 + #define DISPLAY_BW 20000 static struct scroller_s { @@ -178,7 +185,7 @@ static EXT_RAM_ATTR struct { int limit; } bars[MAX_BARS]; float spectrum_scale; - int n, col, row, height, width, border; + int n, col, row, height, width, border, style, max; 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]; @@ -189,6 +196,8 @@ static EXT_RAM_ATTR struct { } back; } visu; +extern const uint8_t vu_bitmap[] asm("_binary_vu_data_start"); + #define ANIM_NONE 0x00 #define ANIM_TRANSITION 0x01 // A transition animation has finished #define ANIM_SCROLL_ONCE 0x02 @@ -565,6 +574,49 @@ static void vfdc_handler( u8_t *_data, int bytes_read) { show_display_buffer(ddram); } +/**************************************************************************************** + * Display VU-Meter (lots of hard-coding) + */ +void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x, int y, int width) { + // VU data is by columns and vertical flip to allow block offset + data += level * VU_WIDTH * VU_HEIGHT; + + // adjust to current display window + if (width > VU_WIDTH) { + width = VU_WIDTH; + x += (width - VU_WIDTH) / 2; + } else { + data += (VU_WIDTH - width) / 2 * VU_HEIGHT; + } + + // this is RGB332, so pixel will be 3 bits deep + int depth = GDS_GetDepth(display); + + // use "fast" version as we are not beyond screen boundaries + if (depth < 3) { + int scale = 3 - depth; + for (int r = 0; r < width; r++) { + for (int c = 0; c < VU_HEIGHT; c++) { + int pixel = *data++; + pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100; + GDS_DrawPixelFast(display, r + x, c + y, pixel >> scale); + } + } + } else { + int scale = depth - 3; + for (int r = 0; r < width; r++) { + for (int c = 0; c < VU_HEIGHT; c++) { + int pixel = *data++; + pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100; + GDS_DrawPixelFast(display, r + x, c + y, pixel << scale); + } + } + } + + // need to manually set dirty flag as DrawPixel does not do it + GDS_SetDirty(display); +} + /**************************************************************************************** * Process graphic display data */ @@ -733,7 +785,8 @@ static void grfa_handler(u8_t *data, int len) { int size = len - sizeof(struct grfa_packet); int offset = htonl(pkt->offset); int length = htonl(pkt->length); - + + // when using full screen visualizer on small screen there is a brief overlay artwork.enable = (length != 0); // just a config or an actual artwork @@ -810,7 +863,7 @@ static void visu_update(void) { // 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 = SB_HEIGHT * (0.01667f*10*log10f(0.0000001f + (visu.bars[i].current >> 1)) - 0.2543f); - if (visu.bars[i].current > 31) visu.bars[i].current = 31; + if (visu.bars[i].current > visu.max) visu.bars[i].current = visu.max; else if (visu.bars[i].current < 0) visu.bars[i].current = 0; } } else { @@ -852,7 +905,7 @@ static void visu_update(void) { // convert to dB and bars, same back-off if (power) visu.bars[i].current = SB_HEIGHT * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f); - if (visu.bars[i].current > 31) visu.bars[i].current = 31; + if (visu.bars[i].current > visu.max) visu.bars[i].current = visu.max; else if (visu.bars[i].current < 0) visu.bars[i].current = 0; } } @@ -872,23 +925,31 @@ static void visu_update(void) { GDS_DrawBitmapCBR(display, visu.back.frame, visu.back.width, displayer.height, GDS_COLOR_WHITE); } - // there is much more optimization to be done here, like not redrawing bars unless needed - 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 (mode != VISU_VUMETER || !visu.style) { + // there is much more optimization to be done here, like not redrawing bars unless needed + 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--; - else if (!clear) continue; + 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--; + else if (!clear) continue; - for (int j = 0; j <= visu.bars[i].current; j += 2) - GDS_DrawLine(display, x1, y1 - j, x1 + visu.bar_width - 1, y1 - j, GDS_COLOR_WHITE); + for (int j = 0; j <= visu.bars[i].current; j += 2) + GDS_DrawLine(display, x1, y1 - j, x1 + visu.bar_width - 1, y1 - j, GDS_COLOR_WHITE); - if (visu.bars[i].max > 2) { - GDS_DrawLine(display, x1, y1 - visu.bars[i].max, x1 + visu.bar_width - 1, y1 - visu.bars[i].max, GDS_COLOR_WHITE); - GDS_DrawLine(display, x1, y1 - visu.bars[i].max + 1, x1 + visu.bar_width - 1, y1 - visu.bars[i].max + 1, GDS_COLOR_WHITE); - } - } + if (visu.bars[i].max > 2) { + GDS_DrawLine(display, x1, y1 - visu.bars[i].max, x1 + visu.bar_width - 1, y1 - visu.bars[i].max, GDS_COLOR_WHITE); + GDS_DrawLine(display, x1, y1 - visu.bars[i].max + 1, x1 + visu.bar_width - 1, y1 - visu.bars[i].max + 1, GDS_COLOR_WHITE); + } + } + } else if (displayer.width / 2 > 3 * VU_WIDTH / 4) { + draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, displayer.width / 2); + draw_VU(display, vu_bitmap, visu.bars[1].current, displayer.width / 2, visu.row, displayer.width / 2); + } else { + int level = (visu.bars[0].current + visu.bars[1].current) / 2; + draw_VU(display, vu_bitmap, level, 0, visu.row, displayer.width); + } } @@ -950,27 +1011,36 @@ static void visu_handler( u8_t *data, int len) { } else { // full screen visu, try to use bottom screen if available visu.height = GDS_GetHeight(display) > SB_HEIGHT ? GDS_GetHeight(display) - SB_HEIGHT : GDS_GetHeight(display); - bars = htonl(pkt->full.bars); - visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.; visu.row = GDS_GetHeight(display) - visu.height; + + // is this spectrum or analogue/digital + if ((visu.mode & ~VISU_ESP32) == VISU_SPECTRUM) { + bars = htonl(pkt->full.bars); + visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.; + } else { + // select analogue/digital + visu.style = htonl(pkt->full.style); + } } } else { // classical (screensaver) mode, don't try to optimize screen usage & force some params visu.row = 0; visu.height = SB_HEIGHT; visu.spectrum_scale = 0.25; - if (artwork.enable && artwork.y < SB_HEIGHT) visu.width = artwork.x - 1; if (visu.mode == VISU_SPECTRUM) bars = visu.width / (htonl(pkt->channels[0].bar_width) + htonl(pkt->channels[0].bar_space)); + else visu.style = htonl(pkt->classical_vu.style); if (bars > MAX_BARS) bars = MAX_BARS; } // try to adapt to what we have if ((visu.mode & ~VISU_ESP32) == VISU_SPECTRUM) { visu.n = bars ? bars : MAX_BARS; + visu.max = displayer.height - 1; 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; + visu.max = visu.style ? (VU_COUNT - 1) : (displayer.height - 1); } do { diff --git a/components/squeezelite/vu.data b/components/squeezelite/vu.data new file mode 100644 index 0000000000000000000000000000000000000000..882617139834dd050c31b62bf56887174aed361d GIT binary patch literal 245760 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 z%0AMQemFHZ30_VP4~z;Ir&L$S6N+ z&(rjvJ+8bU57$JuT80wk4t`@muQFJh0o_ws+8cN87PyWEf+1Fl?WQHy6JC+R4z(pHWx5Q)8zz<)jn-mb7@5_w=fxlV9 zK_^)~DiDiD1%fkwqd;*{Fox)}65)#Em{yY%^H&U8oY^_hi^&@NC$Hip>rO3kQtEZD>?G)DZGe6v*x+OV TEUWNwVqFsnHvGS${`da@ySwP; literal 0 HcmV?d00001 diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index cb9aa0b308c7906fe33bc59e285e1b59f4de127e..68d6732d572a9406f50d87acfcfadc9348f87c05 100644 GIT binary patch delta 2434 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 A-~a#s delta 2360 zcmV-83CH$_K)XMGP)h>@6aWAK2mmx+bx=nTZ6*%~002-J000XB002jFVQ^?^V{usxtyxJCjuitGwf7?4d88kc1_J@xORo_CrZ@z22Jnfwzv$0G3K!unLnANVJXBCbL z3yXqweK6=YpTB9oY7GYEC*IeAXm-Q$U-@4a+qJepl zB9gj@3k0DtJA)~qh>Dqx@5mx7prC0}$CeX#`~+(C2L?V1&vac(gCZR$O2{n9BM&Ls zLKBwhGGf+=i9g8^B%wgzd{3F!0=`Y(4=RlYEK~3(LaYL3fCnj8eJnM!v~)zz1^P|H z5L=yp$D6n+EhHib4I{_32Z}+`uEVMC&OtEd36KGJrj(4sDBvJ0&X_OEb9^RCqR=)a zBaqE3$7TrfWd%Bc1QLXpIh>nr#J~|iW`xoly37C(05$BhAn+2v6vx6OS2|?)D-tnQ z;DtQ!9KrB0trhY?AII~gT)S7DVOJV)94K*roXLp$nU|`r9-|-+Q>zLr4#XluetxPb z2t-^YlPcUqQ7%nhym5V@3a|V`0g9z%Ub%_J_o_-3YjwG+i1S=1lB#F)+Zn->>73!A zVCwi|kP`-`J2mH`9QbhVh{$xE@0g66ie*KdlfYplWuQz45;HM{uS*CpYuvGI<{N~6 z_9+AE7-=TysgZb4<<#+QHdA)^_=Ly|p*p~T0BL;c2u!FWbpceUafwu72T2l*808Oo zQ(WUZTH`(NJ8t|{Xr%s|O!KwKf zOHL@|CkvhV)J6J|LbghuBk2(|>O{{X=!kA?c5Fn}82P!`5^m?FCnb&E$FchH1FByZ z1Le=(z+A!BMDB3@cZ6$&TtO!Pav`w{N`{tp;*dMziS%*!otWWD6BqA)9UL@p z+}5fF98~EO|J19fduvJz=aPvoWl0>Iz9u%K zoY15t<>NSLp^0s7Qx8{BoKs348;mIXVx4k8?eUTpOcs&VbEmEiyaSr_`Xh zc-S*9l=Ic``C8eQ2OPCz6Lw``5n%g1kapERo1TEGhPI}IqUt5AGurrn2wrLA9K6}M z`3z4;lt9PFb&7uyCk?C2wWcKzQWpu*Tcq1dyiTk5bpc4WNZu#?S-aVJqko~JLz5G& zYGB`6BKex>PE;wQLRy3h@oDqL;|pF0b8WQ^ZQb7Kt6r<$A3SUJ)|A#RSGteR`i9O{ zyV>oxUhML*_J`^k7ptQ;k7kEHuHPa}PI!;D_*qT<}udKWVnxdzmxHDCM;|f`cd71c?c3Cbs~hHQ+iLEe!Q0w;AUv{Kv$Ai#<>l=2729D_dTCeKwbi)S zYF7z-wxa&-j=U@t^?g$Dr}q7?$!YADoGY#Jzm%W5zn7oiHt|17Q9)C$7Tf=Z(+Y*! z(NB}k2O6`V2T%ilcwco;|6D&b69E7K>Hz=%3jhEBX>N0LVQg$Jcx`NLkxNU%Koo`d zg8w1AfQwA4Ekd}RDjGx(sSI`DCXC6o4$dRaOiJ|EJ9#x0x;*E6b02eI-6$FD(K=!D zDxN2^IC7rrl1rm;75|`%*Yx}hu<@u{$ORQeP~r!#rDvXhd!(rOyTlU6f4ApQaUp5- zDWs?>%omFrQ+sL5S5g=Sq>FpfbGt)-TUAmN+=ot`9RelvYd;{pPL@CpdLno;Iu{`y z)Rc>oGAR^88#V|LZV8$zq?@Ph!{c+#Ubb2GmSt>nbqSj2(o^H~`(c?`vYZ2H)uCKrk$Yq5X82zq zgVUaj9Np{>vmgxD4h}S5bx=nTZ6*%~002-JlLZ?q2hw>lm$LuWlQ$bX0XUO$8&&~Z zlgb-e0fCbw9AN^l2a|3cACsmWP6Fx&lWrX-lO7#T0WOnm9b_MPUv*IbTt74u0RRB% z0RR9C03-ka0000003ZMW0GN~T9U2E;*LCEw|J9Qb9y)L0000Q5?7G` diff --git a/plugin/SqueezeESP32/Graphics.pm b/plugin/SqueezeESP32/Graphics.pm index bde7e74c..eb79dcae 100644 --- a/plugin/SqueezeESP32/Graphics.pm +++ b/plugin/SqueezeESP32/Graphics.pm @@ -168,31 +168,48 @@ sub build_modes { # mode 9 { desc => ['VISUALIZER_VUMETER'], bar => 0, secs => 0, width => $width, - params => [$VISUALIZER_VUMETER_ESP32] }, - # mode 10 + params => [$VISUALIZER_VUMETER_ESP32, 0] }, + # mode 10 + { desc => ['VISUALIZER_ANALOG_VUMETER'], + bar => 0, secs => 0, width => $width, + params => [$VISUALIZER_VUMETER_ESP32, 1] }, + # mode 11 { desc => ['VISUALIZER_SPECTRUM_ANALYZER'], bar => 0, secs => 0, width => $width, # extra parameters (bars) params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] }, - # mode 11 + ); + +my @extra = ( + # mode E1 { desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'], bar => 0, secs => 1, width => $width, - params => [$VISUALIZER_VUMETER_ESP32] }, - # mode 12 + params => [$VISUALIZER_VUMETER_ESP32, 0] }, + # mode E2 + { desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'ELAPSED'], + bar => 0, secs => 1, width => $width, + params => [$VISUALIZER_VUMETER_ESP32, 1] }, + # mode E3 { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'], bar => 0, secs => 1, width => $width, # extra parameters (bars) params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] }, - # mode 13 + # mode E4 { desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'], bar => 0, secs => -1, width => $width, - params => [$VISUALIZER_VUMETER_ESP32] }, - # mode 14 + params => [$VISUALIZER_VUMETER_ESP32, 0] }, + # mode E5 + { desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'REMAINING'], + bar => 0, secs => -1, width => $width, + params => [$VISUALIZER_VUMETER_ESP32, 1] }, + # mode E6 { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'], bar => 0, secs => -1, width => $width, # extra parameters (bars) params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] }, - ); + ); + + @modes = (@modes, @extra) if $cprefs->get('height') > 32; return \@modes; } diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index c716a398..40c232d4 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.51 + 0.60 Philippe diff --git a/plugin/repo.xml b/plugin/repo.xml index e3d90668..bad2f390 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,10 +1,10 @@ - + https://github.com/sle118/squeezelite-esp32 Philippe - 22551488cdbe02c7a357b2b520f8d377af9cb7d3 + fe158890790ead9a5f27a47a7c7a55b5719087fe philippe_44@outlook.com SqueezeESP32 additional player id (100) http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip