From fb499982c2c5a40cce67189c65bce130fd87e760 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 21 Mar 2020 13:24:26 -0700 Subject: [PATCH 1/9] better screensaver handling - release --- components/squeezelite/display.c | 24 +++++++++++------------- plugin/SqueezeESP32.zip | Bin 7995 -> 8123 bytes plugin/SqueezeESP32/Graphics.pm | 5 ++++- plugin/SqueezeESP32/Player.pm | 6 ++++++ plugin/SqueezeESP32/PlayerSettings.pm | 2 +- plugin/SqueezeESP32/Plugin.pm | 13 +++++++------ plugin/SqueezeESP32/install.xml | 2 +- plugin/repo.xml | 4 ++-- 8 files changed, 32 insertions(+), 24 deletions(-) diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index b20f1be5..e5f36c82 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -735,12 +735,18 @@ static void grfa_handler(u8_t *data, int len) { int length = htonl(pkt->length); 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) { @@ -1071,11 +1077,3 @@ static void displayer_task(void *args) { visu.wake -= sleep; } } - - - - - - - - diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index 239a3142e3eebd58fd808a9ea9bf0e730d8fba4f..cb9aa0b308c7906fe33bc59e285e1b59f4de127e 100644 GIT binary patch delta 5390 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 delta 5225 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 zN3init_accessor( modes => $display->build_modes, - vfdmodel => 'graphic-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; diff --git a/plugin/SqueezeESP32/Player.pm b/plugin/SqueezeESP32/Player.pm index 3ae11b17..6b6e618c 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; 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..c716a398 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.50 + 0.51 Philippe diff --git a/plugin/repo.xml b/plugin/repo.xml index b0266385..e3d90668 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,10 +1,10 @@ - + https://github.com/sle118/squeezelite-esp32 Philippe - 47feaf69a40ad4f87c58b34212d71e60dca99d3e + 22551488cdbe02c7a357b2b520f8d377af9cb7d3 philippe_44@outlook.com SqueezeESP32 additional player id (100) http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip From f714d4fd23405fdc10a6296bb43cfe0c9190a622 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 21 Mar 2020 19:51:31 -0700 Subject: [PATCH 2/9] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0e4b41f3..8ff7d61a 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. From 6342e7b824bb1d8e8201179b0ca92a0277f5b7a8 Mon Sep 17 00:00:00 2001 From: philippe44 <philippe44@users.noreply.github.com> Date: Sun, 22 Mar 2020 19:59:44 -0700 Subject: [PATCH 3/9] 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%0AMQ<QgIj!H+`mRUkwwodH_^GYje#%j8(Sq}9NScajLh)uP8iEqv(zY{fBIQ($y- z9wQR5bjqY)zX)KRoM@%~v6QHjR!43yL8qT7B}|#k-q`|p_dYgQ7Q78k7Ql)QAJ1lO zB-rrZ`X9S$@ZR#H$HQG(C?bxtD1Q9=nq!e<z1{lXa>emFHZ30_VP4~z;Ir&L$S6N+ z&(rjvJ+8bU57$JuT80<Yhx*Ni!k+MKUFNSVit4}hztbgQKEGj{z|%gC+~t3t#pgZ8 z!rqGyYgWw+NDU{UW`-Jk>wk4t`@muQFJh0o_ws+8cN87PyWEf+1<yioAj-Lc6Pp6Z z$I=G(LpYg#!W;9w_|xsP$<4st4c`;I#XI+T`KRH34!`w3_4~SXK!>Fl?WQHy6JC<t zaFh<&<~CI9IpfMe`ip#ve{Y}RsI7k9<HaRG$=msH=GTQPsI`~qVC&b`3IFX5N*ScX zvS&#-gQ#^L;JXOFG!SpxIt7yDFrVb!T>+R4z(pHWx5Q)8zz<)jn-mb7@5_w=fxlV9 zK_^)~DiDiD1%fkwqd;*{Fox)<g2}~N`FX8i67XXRFl6oLsf(r&%vmJrSivTUYC&rr zbW)HdqdFQsXu(|QK<vXg^lR#W-pEy(7!rML#hNBCtNK)3P9ExOtN`cb;3e2;xPa*G zb8Y0LPoD>}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 zcmV-|34QjvKZihnP)h>@6aWAK2mqm+c2M<hT3lQQ004a&000XB002jFVQ^?^V{<NW zZOvGDkJ3OC|GRvOXEYYHAcyO*9uc)3k!1<E8n2Mj4$#=k&Qw6;yZhesg0d*AN8D_n zo%fq}A2S4o`PrB-=sD5E_W1Gf;7i2VH>MAIt>*FZ^Uw%?rnbrNl}Zt3z(r`AB6%4Z z`0-^{9yqppeEihrfnzK(1|xsotR%=7aVBgB1@uBTMt)oC9e;vwcL7%dLcjx{Zx%Ao zWW1~fQo$OJxicQLnkw=qyyH&*5Rcyz7Vd;f?3<T^vv%j@d%ZvGo_2NIRlipxyg573 zU+aCOHMVGfgP#86b^q*S*zUGF@7_yPa|`vh{YHOw+CM>JYZv>WbTKI~Z|osw9gYG^ z%aU?=IPA5bylOw!hr{Ya_w&#&(N)|>7Z_n^EL06t*S8oa>y_Q$aCQ(9L;iJa5!|UD zd{rv(XapXc0qR6%n2y0Ietc?=#l3XRW}aAEjF+u{IBRe{-c@}#IP2;C<6~r3PBv9i z7D-;j34+j=oWUehM8!ngvqc$}P*RkcZHXy7cnFQ=9Sz^PYd8*uK`tH1m4KNdj9gq% zmI^lwhY_(!MEpy(;1V+A_P3OXCE!`~d{k?-V3mT$5n?4c1Kdfe@@u7_rj>j2UZVfh z4YAgLxxa}j!$K@_&?vGUYbXiC?J}Ht&H^}NE(hU*YY4$Y82ce`Gi1!;##ekNO`^au z1S62eOxt1z@<a`~z5rr`m<4<_oQQ$Vfz$|uF>;s&+y^Sy$G-0-fEl)hX)bic@Si7a zOy3Pc-?cfz*ECni26YnmlWOBueU4SB#eSfFz=cdip_f@H^XdUIaxt_r!D2@&Gvvod zas`2ii$s!zMHI!-2+LP4FJ<CYA4)*EvdS|zk@!|-$#NqvcO`Ki3%Mlo8SS>mFk@=S zaFai?y$OgZ1H+ja3tsg-_-gaWaO`gwjH{AlMqx+{N0AV|R2_)T)Zm^fz{jYCwq-GY zPb0KP5s=$RGI39p#Emkhwr8=q)WgF!L}mzC0UG$Y7S3#r0kwt7ff6-Nky>mZaiSI@ z?LlkGYnGBtZ!!zY6}Wm8E?I1Bd(28|FmZ3s2o1NalDTT70u^#<?7wq6J7*zUs#eY` zXeCuCtLq69Wi|0YGILHSq_qFTFJWqb1wJ9$Rcc>uo^;V^>~fZ?Y=mOwho5(MokUcE zll?R1oUBwIE>-4H6{#z7+4}kdm+nKWN$Z&hZQhI3j+Mv?Bi}b)!S!7AB&E^%*jL}b zL;cfoDD7FvH>uMKn8Voy%N@pFgt=161!Vkx&LrkR!BEml9EJ8|Dm-j{Cq}S;R>b97 zM@KDew~e|6M|Jwfk7gZZZ*_^$LNMNA;S=A9TyKrX&Ad@uhL>RZRAORkd!M0mG5}0Q z3o|mr;`1yATL?C=fQ8N??V)k;fIvD$EU@QjZVg@vWGA~Ikp$3~m@&YdkS#QWRs~V8 z`x;n`VoIHsgim6p1qPP6MKxT1M|O6N)#6?PB3!_&n~5}-GV+fW8wvGVttGqq&06g? zaz*|owsQlIakBWi;W9vWft&}PRGkR9`hXGVw(n`!&Cz)9J?!g#<XGs1uMF6n#v-#~ zc~T6ri<>=j1F=|dpD(0txx-OP)?wEM76G=;18G$qGU*8@b7-qO$W^s}f_X+0AHi#d zoP&#noA>Z!i2`VQI8N~+F;c@ku_BYj4Yj1C3T#yMTwM{DJsQ9wF8|D&^kA~3WJ*xe zvru|q*+QlSL~e3T3B$lA&n{t31359EN+Scx8C%p%?po0Uw)+)aOQAQY`YIYEXB|in zR|j(-2CI9UhW|{t479(0F3E-+q7^0akUU6`+%lbB;&EEX$0gvhN&G&jk2~$|EA<m? z9T_35)itbplP8av>_n7QDxg6q5g&>dk570Y%%wF7(t7>V=Y4%J7(Q<IH-t8RF7yzY z&4Nt5)9wxQr@L%y{HD0p`Qqs2(eBdMUPd>5*V8u(YV`G!_RH>n%kJ}Cc2^tPZ`pkW zP!Rc~bNb{bskCjM>G<YnY5hTxZz)@%2m0%?-f-~Z^zGxbXV3Khv$Ia8i!MJXvFX%E zGx~6iZWq)!=Tba4X?HpYsWZ%0svCU-8&5I`5*657ga$}unZUsZ<(NY@R!!f5@_ndd zCm4r#(UGT-3if<|f3~qc^6|LtBEex}fsNHLS!L4=JFoj1?9>s?rOt^$r=3kt-YIx; z_b&&3$fs#v|L307;4&)BHF=XO`gxCEOf)#(0;yr{^=(A%dqn))3piKK9ei~AM;7Ze zX~l|y36O{Lt^&U-jY9&scQ)1wbEvND)I<99^!Z;^r%_OUr*S}<AE?lISxbp_kqnc^ zjhOX~^<a;qwLGZoN(9-aliw#FNu@TAB6{Q3xsgrLKRd>Mo-KM)fxXk^-)Pt<XxKU= zfqqxT|1704C*0hpyJW`icO<%ah>A&cbb%E5%PJPL=+?!u=)zk5M~Q@;DY+=+x1{(< zOPW;$FS~^lOK3dy@do}dx!rF6W~-D+je9=;lYs{svr-380|`2xc2M5TcG(h>s|p%_ z#4r?v_k#bSyMl{m)KQ__$tYSx5UGT5;ikm)ItKHoNjg-2z3FSJFw1k!m;1<tb*p5w zL+gamtN1io#F6t{S6mv6tN0sTyr#!TfUQT}K`y8$f(qYxBR%unBSp>MBvwHFx&w!b z3rXt_Aw^wdv0Pr8#!F*9lhP<4eLRqVp4%<@o4S^w<UVxj?GPxTUxoqcRdNAjuV;d{ zlXDXCQO&uiD3d}lv|&>qLG~TV;g+DeLYhBr?jN2C_Poim*DPcC`5BNdnf7<36?OOM z5L(=2>=wvK&83y88X1bqW-GKyQ})#${$T$mO$QsuKuy!{#$^`C<pM~j_T@?xi`*Ma z^YMRyj81nl^6zH9P)h>@liVE`v+xVM4i2H5c2M<hT3lQQ004a&leroz2ZB)j?YjWh zlj|Bg0fmz%8&(0RlYSdn0nU@y8({(blR6x30XLH&9VwI298LjylOY{sKsujxP~OaT z*%AQ&0O|n%01E&l000000000W0002x3IG6UZgX^DY-}!gZEOkvAOHXW000317yytr z<JZ0b)|15@I|3jKlL8)D0i}~%9%2D{lfNEX0!bH>8y{N&HX4(7A3X-^8UO$Q00I7i A-~a#s delta 2360 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 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 @@ <name>PLUGIN_SQUEEZEESP32</name> <description>PLUGIN_SQUEEZEESP32_DESC</description> <module>Plugins::SqueezeESP32::Plugin</module> - <version>0.51</version> + <version>0.60</version> <creator>Philippe</creator> </extensions> 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 @@ <?xml version='1.0' standalone='yes'?> <extensions> <plugins> - <plugin version="0.51" name="SqueezeESP32" minTarget="7.5" maxTarget="*"> + <plugin version="0.60" name="SqueezeESP32" minTarget="7.5" maxTarget="*"> <link>https://github.com/sle118/squeezelite-esp32</link> <creator>Philippe</creator> - <sha>22551488cdbe02c7a357b2b520f8d377af9cb7d3</sha> + <sha>fe158890790ead9a5f27a47a7c7a55b5719087fe</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> From 3eb2c76d6b60152726799f8b8387d5620d9a778a Mon Sep 17 00:00:00 2001 From: philippe44 <philippe44@users.noreply.github.com> Date: Sun, 22 Mar 2020 20:23:51 -0700 Subject: [PATCH 4/9] set height before building modes --- plugin/SqueezeESP32.zip | Bin 8199 -> 8209 bytes plugin/SqueezeESP32/Graphics.pm | 2 +- plugin/SqueezeESP32/Player.pm | 9 ++++++--- plugin/SqueezeESP32/install.xml | 2 +- plugin/repo.xml | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index 68d6732d572a9406f50d87acfcfadc9348f87c05..8edf88e580f84dd3167c27edd352bf3bae6cc43c 100644 GIT binary patch delta 3094 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 delta 3117 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 diff --git a/plugin/SqueezeESP32/Graphics.pm b/plugin/SqueezeESP32/Graphics.pm index eb79dcae..5506fdf7 100644 --- a/plugin/SqueezeESP32/Graphics.pm +++ b/plugin/SqueezeESP32/Graphics.pm @@ -210,7 +210,7 @@ my @extra = ( ); @modes = (@modes, @extra) if $cprefs->get('height') > 32; - + return \@modes; } diff --git a/plugin/SqueezeESP32/Player.pm b/plugin/SqueezeESP32/Player.pm index 6b6e618c..7e638201 100644 --- a/plugin/SqueezeESP32/Player.pm +++ b/plugin/SqueezeESP32/Player.pm @@ -32,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/install.xml b/plugin/SqueezeESP32/install.xml index 40c232d4..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.60</version> + <version>0.61</version> <creator>Philippe</creator> </extensions> diff --git a/plugin/repo.xml b/plugin/repo.xml index bad2f390..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.60" 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>fe158890790ead9a5f27a47a7c7a55b5719087fe</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> From 6ad532a2766640440326312364a1d2be3a3fac7d Mon Sep 17 00:00:00 2001 From: philippe44 <philippe44@users.noreply.github.com> Date: Sun, 22 Mar 2020 20:38:44 -0700 Subject: [PATCH 5/9] reset style for small visu - release --- components/squeezelite/display.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index a81da9f5..18cee3ca 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -1001,6 +1001,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; @@ -1018,7 +1019,7 @@ static void visu_handler( u8_t *data, int len) { bars = htonl(pkt->full.bars); visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.; } else { - // select analogue/digital + // select analogue/digital style visu.style = htonl(pkt->full.style); } } From cd5693a81ce23d74aa6c71500bd2a80421749521 Mon Sep 17 00:00:00 2001 From: philippe44 <philippe44@users.noreply.github.com> Date: Tue, 24 Mar 2020 15:31:13 -0700 Subject: [PATCH 6/9] VU bitmap is 8bits grayscale, not RGB332 - release --- components/display/core/gds_image.c | 14 +++++++++++--- components/display/core/gds_image.h | 4 ++-- components/display/core/gds_private.h | 2 +- components/squeezelite/display.c | 28 +++++++-------------------- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c index d76b564a..1be2089f 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -215,13 +215,21 @@ void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, in } /**************************************************************************************** - * Simply draw a RGB 8 bits image (R:3,G:3,B:2) + * 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 ) { +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 ); + 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; diff --git a/components/display/core/gds_image.h b/components/display/core/gds_image.h index ec875fbd..b1f15c0c 100644 --- a/components/display/core/gds_image.h +++ b/components/display/core/gds_image.h @@ -7,7 +7,7 @@ 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 @@ -24,4 +24,4 @@ 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 ); -void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height ); \ 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 efb829b4..42fa1a82 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -112,7 +112,7 @@ struct GDS_Device { 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, 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 (*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/squeezelite/display.c b/components/squeezelite/display.c index 18cee3ca..2af283db 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -589,29 +589,15 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x, data += (VU_WIDTH - width) / 2 * VU_HEIGHT; } - // this is RGB332, so pixel will be 3 bits deep - int depth = GDS_GetDepth(display); + // this is 8 bits grayscale + int scale = 8 - 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); - } + for (int r = 0; r < width; r++) { + for (int c = 0; c < VU_HEIGHT; c++) { + GDS_DrawPixelFast(display, r + x, c + y, *data++ >> 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); @@ -934,7 +920,7 @@ static void visu_update(void) { 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); From d2a9454200ffca6c75fef8a79e2744ecbb192d64 Mon Sep 17 00:00:00 2001 From: philippe44 <philippe44@users.noreply.github.com> Date: Tue, 24 Mar 2020 17:31:07 -0700 Subject: [PATCH 7/9] block all screen update when using full screen visu on small screens - release --- components/squeezelite/display.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index 2af283db..a7e36e4f 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -612,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) { @@ -732,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; From 6b5c9188c690a78634de879b112d63aecf50da5b Mon Sep 17 00:00:00 2001 From: philippe44 <philippe44@users.noreply.github.com> Date: Sun, 29 Mar 2020 22:36:23 -0700 Subject: [PATCH 8/9] continue the AirPlay saga - release --- components/raop/rtp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/raop/rtp.c b/components/raop/rtp.c index 536e6f1e..c8961538 100644 --- a/components/raop/rtp.c +++ b/components/raop/rtp.c @@ -654,7 +654,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; } @@ -700,9 +700,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; } From 4c546180fdbe4e95e4c630acd4f97854aee9a42f Mon Sep 17 00:00:00 2001 From: philippe44 <philippe44@users.noreply.github.com> Date: Sun, 29 Mar 2020 22:40:40 -0700 Subject: [PATCH 9/9] continue the AirPlay saga - release --- components/raop/rtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/raop/rtp.c b/components/raop/rtp.c index c8961538..58c6e7c8 100644 --- a/components/raop/rtp.c +++ b/components/raop/rtp.c @@ -703,7 +703,7 @@ static void *rtp_thread_func(void *arg) { // better discard sync packets when roundtrip is suspicious if (roundtrip > 100) { // ask for another one only if we are not synced already - if (!(ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx); + if (!(ctx->synchro.status & NTP_SYNC)) rtp_request_timing(ctx); LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip); break; }