diff --git a/README.md b/README.md index fc597727..869a45d2 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay a - 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only will be displayed not " - <title>". +You can install the excellent plugin "Music Information Screen" which is super useful to tweak the layout for these small displays. + ### Set GPIO The parameter "set_GPIO" is use to assign GPIO to various functions. 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..1be2089f 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -133,45 +133,126 @@ 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) or plain grayscale + * 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, int RGB_Mode ) { + if (Device->DrawRGB8) { + Device->DrawRGB8( Device, Image, x, y, Width, Height, RGB_Mode ); + } else if (RGB_Mode == GDS_GRAYSCALE) { + // 8 bits pixels + int Scale = 8 - Device->Depth; + for (int r = 0; r < Height; r++) { + for (int c = 0; c < Width; c++) { + GDS_DrawPixel( Device, c + x, r + y, *Image++ >> Scale); + } + } + } 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 +309,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..b1f15c0c 100644 --- a/components/display/core/gds_image.h +++ b/components/display/core/gds_image.h @@ -7,8 +7,9 @@ struct GDS_Device; -enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 }; +enum { GDS_RGB565, GDS_RGB555, GDS_RGB444, GDS_RGB332, GDS_GRAYSCALE }; +// 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, int RGB_Mode ); \ No newline at end of file diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 0b26eaf8..811fb751 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, int RGB_Mode ); void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ); // interface-specific methods diff --git a/components/raop/rtp.c b/components/raop/rtp.c index a715785c..1cb3dc56 100644 --- a/components/raop/rtp.c +++ b/components/raop/rtp.c @@ -655,7 +655,7 @@ static void *rtp_thread_func(void *arg) { u32_t remote_gap = NTP2MS(remote - ctx->timing.remote); // try to get NTP every 3 sec or every time if we are not synced - if (!count-- || !(ctx->synchro.status && NTP_SYNC)) { + if (!count-- || !(ctx->synchro.status & NTP_SYNC)) { rtp_request_timing(ctx); count = 3; } @@ -701,9 +701,10 @@ static void *rtp_thread_func(void *arg) { u64_t remote =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20)); u32_t roundtrip = gettime_ms() - reference; - // better discard sync packets when roundtrip is suspicious and ask for another one + // better discard sync packets when roundtrip is suspicious if (roundtrip > 100) { - rtp_request_timing(ctx); + // ask for another one only if we are not synced already + if (!(ctx->synchro.status & NTP_SYNC)) rtp_request_timing(ctx); LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip); break; } diff --git a/components/squeezelite/CMakeLists.txt b/components/squeezelite/CMakeLists.txt index 4dc5a2f3..ab0d8fb9 100644 --- a/components/squeezelite/CMakeLists.txt +++ b/components/squeezelite/CMakeLists.txt @@ -12,6 +12,7 @@ idf_component_register( SRC_DIRS . external a1s tas57xx services raop display + EMBED_FILES vu.data ) 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 b20f1be5..a7e36e4f 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,35 @@ 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 8 bits grayscale + int scale = 8 - GDS_GetDepth(display); + + // use "fast" version as we are not beyond screen boundaries + for (int r = 0; r < width; r++) { + for (int c = 0; c < VU_HEIGHT; c++) { + GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale); + } + } + + // need to manually set dirty flag as DrawPixel does not do it + GDS_SetDirty(display); +} + /**************************************************************************************** * Process graphic display data */ @@ -574,12 +612,13 @@ static void grfe_handler( u8_t *data, int len) { scroller.active = false; - // we are not in control or we are displaying visu on a small screen, do not do screen update + // visu has priority when full screen on small screens if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < SB_HEIGHT) { xSemaphoreGive(displayer.mutex); return; } + // are we in control if (displayer.owned) { // did we have something that might have write on the bottom of a SB_HEIGHT+ display if (displayer.dirty) { @@ -694,9 +733,12 @@ static void grfg_handler(u8_t *data, int len) { struct grfg_packet *pkt = (struct grfg_packet*) data; LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len); + + // on small screen, visu has priority when full screen + if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < SB_HEIGHT) return; xSemaphoreTake(displayer.mutex, portMAX_DELAY); - + // size of scrollable area (less than background) scroller.width = htons(pkt->width); scroller.back.width = ((len - sizeof(struct grfg_packet)) * 8) / displayer.height; @@ -733,14 +775,21 @@ 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); - - // clean up if we are disabling previously enabled artwork - if (!artwork.enable) { - if (artwork.size) GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK); + + // just a config or an actual artwork + if (length < 32) { + if (artwork.enable) { + // this is just to specify artwork coordinates + artwork.x = htons(pkt->x); + artwork.y = htons(pkt->y); + } else if (artwork.size) GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK); + + // done in any case return; - } + } // new grfa artwork, allocate memory if (!offset) { @@ -804,7 +853,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 { @@ -846,7 +895,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; } } @@ -866,23 +915,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; - - 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].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].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); - } - } + 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); + } + } + } 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); + } } @@ -934,6 +991,7 @@ static void visu_handler( u8_t *data, int len) { pkt->row = htonl(pkt->row); pkt->col = htonl(pkt->col); + visu.style = 0; visu.width = htonl(pkt->width); visu.height = pkt->height ? pkt->height : SB_HEIGHT; visu.col = pkt->col < 0 ? displayer.width + pkt->col : pkt->col; @@ -944,27 +1002,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 style + 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 { @@ -1071,11 +1138,3 @@ static void displayer_task(void *args) { visu.wake -= sleep; } } - - - - - - - - diff --git a/components/squeezelite/vu.data b/components/squeezelite/vu.data new file mode 100644 index 00000000..88261713 Binary files /dev/null and b/components/squeezelite/vu.data differ diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index 239a3142..8edf88e5 100644 Binary files a/plugin/SqueezeESP32.zip and b/plugin/SqueezeESP32.zip differ diff --git a/plugin/SqueezeESP32/Graphics.pm b/plugin/SqueezeESP32/Graphics.pm index db62982a..5506fdf7 100644 --- a/plugin/SqueezeESP32/Graphics.pm +++ b/plugin/SqueezeESP32/Graphics.pm @@ -42,7 +42,10 @@ sub new { $display->init_accessor( modes => $display->build_modes, - vfdmodel => 'graphic-<width>x32', # doesn't matter much + # Only seems to matter for screensaver and update to decide font. Not + # any value is acceptable, so use Boom value which seems to be best + # compromise + vfdmodel => 'graphic-160x32', ); return $display; @@ -165,32 +168,49 @@ 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/Player.pm b/plugin/SqueezeESP32/Player.pm index 3ae11b17..7e638201 100644 --- a/plugin/SqueezeESP32/Player.pm +++ b/plugin/SqueezeESP32/Player.pm @@ -13,6 +13,12 @@ sub model { 'squeezeesp32' } sub modelName { 'SqueezeESP32' } sub hasIR { 0 } +sub init { + my $client = shift; + $client->SUPER::init(@_); + Plugins::SqueezeESP32::Plugin::config_artwork($client); +} + # Allow the player to define it's display width (and probably more) sub playerSettingsFrame { my $client = shift; @@ -26,13 +32,16 @@ sub playerSettingsFrame { $value = (unpack('Cn', $$data_ref))[1]; if ($value > 100 && $value < 400) { $prefs->client($client)->set('width', $value); + + my $height = (unpack('Cnn', $$data_ref))[2]; + $prefs->client($client)->set('height', $height || 0); + $client->display->modes($client->display->build_modes); $client->display->widthOverride(1, $value); $client->update; + + $log->info("Setting player $value" . "x" . "$height for ", $client->name); } - my $height = (unpack('Cnn', $$data_ref))[2]; - $prefs->client($client)->set('height', $height || 0); - $log->info("Setting player $value" . "x" . "$height for ", $client->name); } $client->SUPER::playerSettingsFrame($data_ref); diff --git a/plugin/SqueezeESP32/PlayerSettings.pm b/plugin/SqueezeESP32/PlayerSettings.pm index eb95f098..ce41c608 100644 --- a/plugin/SqueezeESP32/PlayerSettings.pm +++ b/plugin/SqueezeESP32/PlayerSettings.pm @@ -59,7 +59,7 @@ sub handler { if ($artwork->{'enable'}) { Plugins::SqueezeESP32::Plugin::update_artwork($client, 1); } else { - Plugins::SqueezeESP32::Plugin::disable_artwork($client); + Plugins::SqueezeESP32::Plugin::config_artwork($client); } } diff --git a/plugin/SqueezeESP32/Plugin.pm b/plugin/SqueezeESP32/Plugin.pm index 3e845542..36627ad0 100644 --- a/plugin/SqueezeESP32/Plugin.pm +++ b/plugin/SqueezeESP32/Plugin.pm @@ -50,7 +50,7 @@ sub onNotification { sub update_artwork { my $client = shift; - my $force = shift || 0; + my $params = { force => shift || 0 }; my $cprefs = $prefs->client($client); my $artwork = $cprefs->get('artwork'); @@ -60,17 +60,17 @@ sub update_artwork { $s = min($s, $cprefs->get('width') - $artwork->{'x'}); my $path = 'music/current/cover_' . $s . 'x' . $s . '_o.jpg'; - my $body = Slim::Web::Graphics::artworkRequest($client, $path, $force, \&send_artwork, undef, HTTP::Response->new); + my $body = Slim::Web::Graphics::artworkRequest($client, $path, $params, \&send_artwork, undef, HTTP::Response->new); send_artwork($client, undef, \$body) if $body; } sub send_artwork { - my ($client, $force, $dataref) = @_; + my ($client, $params, $dataref) = @_; # I'm not sure why we are called so often, so only send when needed my $md5 = md5($$dataref); - return if $client->pluginData('artwork_md5') eq $md5 && !$force; + return if $client->pluginData('artwork_md5') eq $md5 && !$params->{'force'}; $client->pluginData('artwork', $dataref); $client->pluginData('artwork_md5', $md5); @@ -95,9 +95,10 @@ sub send_artwork { } } -sub disable_artwork { +sub config_artwork { my ($client) = @_; - my $header = pack('N', 0); + my $artwork = $prefs->client($client)->get('artwork'); + my $header = pack('Nnn', $artwork->{'enable'}, $artwork->{'x'}, $artwork->{'y'}); $client->sendFrame( grfa => \$header ); } diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index d28e7f08..fd652e0f 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ <name>PLUGIN_SQUEEZEESP32</name> <description>PLUGIN_SQUEEZEESP32_DESC</description> <module>Plugins::SqueezeESP32::Plugin</module> - <version>0.50</version> + <version>0.61</version> <creator>Philippe</creator> </extensions> diff --git a/plugin/repo.xml b/plugin/repo.xml index b0266385..23c2d1b0 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,10 +1,10 @@ <?xml version='1.0' standalone='yes'?> <extensions> <plugins> - <plugin version="0.50" name="SqueezeESP32" minTarget="7.5" maxTarget="*"> + <plugin version="0.61" name="SqueezeESP32" minTarget="7.5" maxTarget="*"> <link>https://github.com/sle118/squeezelite-esp32</link> <creator>Philippe</creator> - <sha>47feaf69a40ad4f87c58b34212d71e60dca99d3e</sha> + <sha>5c45fed832e6f79f709bef5f2da511071d1c776e</sha> <email>philippe_44@outlook.com</email> <desc lang="EN">SqueezeESP32 additional player id (100)</desc> <url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>