Procházet zdrojové kódy

add SSD1322 + few tweaks - release

Philippe G před 5 roky
rodič
revize
26330ee69e

+ 203 - 0
components/display/SSD1322.c

@@ -0,0 +1,203 @@
+/**
+ * Copyright (c) 2017-2018 Tara Keeling
+ *				 2020 Philippe G.
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <esp_heap_caps.h>
+#include <esp_log.h>
+
+#include "gds.h"
+#include "gds_private.h"
+
+#define SHADOW_BUFFER
+#define PAGE_BLOCK	1024
+
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+
+static char TAG[] = "SSD1322";
+
+struct PrivateSpace {
+	uint8_t *iRAM, *Shadowbuffer;
+	uint8_t ReMap, PageSize;
+	uint8_t Offset;
+};
+
+// Functions are not declared to minimize # of lines
+
+static void WriteDataByte( struct GDS_Device* Device, uint8_t Data ) {
+	Device->WriteData( Device, &Data, 1);
+}
+
+static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
+	Device->WriteCommand( Device, 0x15 );
+	Device->WriteData( Device, &Start, 1 );
+	Device->WriteData( Device, &End, 1 );
+}
+static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
+	Device->WriteCommand( Device, 0x75 );
+	Device->WriteData( Device, &Start, 1 );
+	Device->WriteData( Device, &End, 1 );
+}
+
+static void Update( struct GDS_Device* Device ) {
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+		
+	// RAM is by columns of 4 pixels ...
+	SetColumnAddress( Device, Private->Offset, Private->Offset + Device->Width / 4 - 1);
+	
+#ifdef SHADOW_BUFFER
+	uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
+	bool dirty = false;
+	
+	for (int r = 0, page = 0; r < Device->Height; r++) {
+		// look for change and update shadow (cheap optimization = width always / by 2)
+		for (int c = Device->Width / 2 / 2; --c >= 0;) {
+			if (*optr != *iptr) {
+				dirty = true;
+				*optr = *iptr;
+			}
+			iptr++; optr++;
+		}
+		
+		// one line done, check for page boundary
+		if (++page == Private->PageSize) {
+			if (dirty) {
+				uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Private->Shadowbuffer + (r - page + 1) * Device->Width / 2);
+				SetRowAddress( Device, r - page + 1, r );
+				for (int i = page * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
+				//memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
+				Device->WriteCommand( Device, 0x5c );
+				Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
+				dirty = false;
+			}	
+			page = 0;
+		}	
+	}	
+#else
+	for (int r = 0; r < Device->Height; r += Private->PageSize) {
+		SetRowAddress( Device, r, r + Private->PageSize - 1 );
+		Device->WriteCommand( Device, 0x5c );
+		if (Private->iRAM) {
+			uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2);
+			for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
+			//memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
+			Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
+		} else	{
+			Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
+		}	
+	}	
+#endif	
+}
+
+static void SetHFlip( struct GDS_Device* Device, bool On ) { 
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+	Private->ReMap = On ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
+	Device->WriteCommand( Device, 0xA0 );
+	Device->WriteData( Device, &Private->ReMap, 1 );
+	WriteDataByte( Device, 0x11 );		
+}	
+
+static void SetVFlip( struct GDS_Device *Device, bool On ) { 
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+	Private->ReMap = On ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
+	Device->WriteCommand( Device, 0xA0 );
+	Device->WriteData( Device, &Private->ReMap, 1 );
+	WriteDataByte( Device, 0x11 );		
+}	
+	
+static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
+static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
+
+static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
+    Device->WriteCommand( Device, 0xC1 );
+    Device->WriteData( Device, &Contrast, 1 );
+}
+
+static bool Init( struct GDS_Device* Device ) {
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+	
+	// these displays seems to be layout centered (1 column = 4 pixels of 4 bits each, little endian)
+	Private->Offset = (480 - Device->Width) / 4 / 2;
+	
+	// find a page size that is not too small is an integer of height
+	Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
+	Private->PageSize = Device->Height / (Device->Height / Private->PageSize) ;	
+	
+#ifdef SHADOW_BUFFER	
+	Private->Shadowbuffer = malloc( Device->FramebufferSize );	
+	memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
+#endif
+	Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+
+	ESP_LOGI(TAG, "SSD1322 with offset %u, page %u, iRAM %p", Private->Offset, Private->PageSize, Private->iRAM);
+			
+	// need to be off and disable display RAM
+	Device->DisplayOff( Device );
+    Device->WriteCommand( Device, 0xA5 );
+	
+	// Display Offset
+    Device->WriteCommand( Device, 0xA2 );
+    WriteDataByte( Device, 0 );
+	
+	// Display Start Line
+    Device->WriteCommand( Device, 0xA1 );
+	WriteDataByte( Device, 0x00 );
+	
+	// set flip modes
+	Private->ReMap = 0;
+	Device->SetVFlip( Device, false );
+	Device->SetHFlip( Device, false );
+	
+	// set Clocks
+    Device->WriteCommand( Device, 0xB3 );
+	WriteDataByte( Device, 0x91 );
+	
+	// set MUX
+	Device->WriteCommand( Device, 0xCA );
+	WriteDataByte( Device, Device->Height - 1 );
+	
+	// phase 1 & 2 period (needed?)	
+	Device->WriteCommand( Device, 0xB1 );
+	WriteDataByte( Device, 0xE2 );
+	
+	// set pre-charge V (needed?°)
+	Device->WriteCommand( Device, 0xBB );
+	WriteDataByte( Device, 0x1F );
+	
+	// set COM deselect voltage (needed?)
+	Device->WriteCommand( Device, 0xBE );
+	WriteDataByte( Device, 0x07 );
+	
+	// no Display Inversion
+    Device->WriteCommand( Device, 0xA6 );
+	
+	// gone with the wind
+	Device->DisplayOn( Device );
+	Device->Update( Device );
+	
+	return true;
+}	
+
+static const struct GDS_Device SSD1322 = {
+	.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
+	.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
+	.Update = Update, .Init = Init,
+};	
+
+struct GDS_Device* SSD1322_Detect(char *Driver, struct GDS_Device* Device) {
+	if (!strcasestr(Driver, "SSD1322")) return NULL;
+		
+	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
+	
+	*Device = SSD1322;	
+	Device->Depth = 4;
+		
+	return Device;
+}

+ 2 - 2
components/display/display.c

@@ -59,8 +59,8 @@ static EXT_RAM_ATTR struct {
 static void displayer_task(void *args);
 
 struct GDS_Device *display;   
-extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect;
-GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, NULL };
+extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect;
+GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, NULL };
 
 /****************************************************************************************
  * 

+ 38 - 30
components/squeezelite/display.c

@@ -73,12 +73,15 @@ struct visu_packet {
 	u8_t which;
 	u8_t count;
 	union {
-		union {
-			struct {
+		struct {
+			u32_t width;
+			union {
+				struct {
 				u32_t bars;
 				u32_t spectrum_scale;
-			};
-			u32_t style;	
+				};
+				u32_t style;	
+			};	
 		} full;	
 		struct {	
 			u32_t width;
@@ -613,22 +616,22 @@ static void grfe_handler( u8_t *data, int len) {
 	scroller.active = false;
 	
 	// visu has priority when full screen on small screens
-	if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < SB_HEIGHT) {
+	if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.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) {
-			GDS_ClearExt(display, true);
+		// draw new frame, it might be less than full screen (small visu)
+		int width = ((len - sizeof(struct grfe_packet)) * 8) / displayer.height;
+
+		// did we have something that might have written on the bottom of a displayer's height + display
+		if (displayer.dirty || (artwork.enable && width == displayer.width && artwork.y < displayer.height)) {
+			GDS_Clear(display, GDS_COLOR_BLACK);
 			displayer.dirty = false;
 		}	
 	
-		// draw new frame, it might be less than full screen (small visu)
-		int width = ((len - sizeof(struct grfe_packet)) * 8) / displayer.height;
-		
 		// when doing screensaver, that frame becomes a visu background
 		if (!(visu.mode & VISU_ESP32)) {
 			visu.back.width = width;
@@ -735,7 +738,7 @@ static void grfg_handler(u8_t *data, int len) {
 	LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
 
 	// on small screen, visu has priority when full screen	
-	if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < SB_HEIGHT) return;
+	if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) return;
 	
 	xSemaphoreTake(displayer.mutex, portMAX_DELAY);
 		
@@ -755,7 +758,7 @@ static void grfg_handler(u8_t *data, int len) {
 	}	
 		
 	// now we can active scrolling, but only if we are not on a small screen
-	if (!visu.mode || visu.col || visu.row >= SB_HEIGHT) scroller.active = true;
+	if (!visu.mode || visu.col || visu.row >= displayer.height) scroller.active = true;
 		
 	// if we just got a content update, let the scroller manage the screen
 	LOG_DEBUG("resuming scrolling task");
@@ -811,7 +814,7 @@ static void grfa_handler(u8_t *data, int len) {
 	artwork.size += size;
 	if (artwork.size == length) {
 		GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK);
-		GDS_DrawJPEG(display, artwork.data, artwork.x, artwork.y, artwork.y < SB_HEIGHT ? (GDS_IMAGE_RIGHT | GDS_IMAGE_TOP) : GDS_IMAGE_CENTER);
+		GDS_DrawJPEG(display, artwork.data, artwork.x, artwork.y, artwork.y < displayer.height ? (GDS_IMAGE_RIGHT | GDS_IMAGE_TOP) : GDS_IMAGE_CENTER);
 		free(artwork.data);
 		artwork.data = NULL;
 	} 
@@ -852,7 +855,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);
+				visu.bars[i].current = visu.max * (0.01667f*10*log10f(0.0000001f + (visu.bars[i].current >> 1)) - 0.2543f);
 				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;
 			}
@@ -894,7 +897,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 (power) visu.bars[i].current = visu.max * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f);
 				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;
 			}	
@@ -934,11 +937,11 @@ static void visu_update(void) {
 			}	
 		}
 	} 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);
+		draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.width / 2);
+		draw_VU(display, vu_bitmap, visu.bars[1].current, visu.width / 2, visu.row, visu.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);		
+		draw_VU(display, vu_bitmap, level, 0, visu.row, visu.width);		
 	}	
 }
 
@@ -976,12 +979,11 @@ static void visu_handler( u8_t *data, int len) {
 	visu.mode = pkt->which;
 	
 	// little trick to clean the taller screens when switching visu 
-	if (visu.row >= SB_HEIGHT) GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
+	if (visu.row >= displayer.height) GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
 	
 	if (visu.mode) {
 		// these will be overidden if necessary
 		visu.col = visu.border = 0;
-		visu.width = displayer.width;				
 		
 		// what type of visu
 		if (visu.mode & VISU_ESP32) {
@@ -993,7 +995,7 @@ static void visu_handler( u8_t *data, int len) {
 
 				visu.style = 0;
 				visu.width = htonl(pkt->width);
-				visu.height = pkt->height ? pkt->height : SB_HEIGHT;
+				visu.height = pkt->height ? pkt->height : displayer.height;
 				visu.col = pkt->col < 0 ? displayer.width + pkt->col : pkt->col;
 				visu.row = pkt->row < 0 ? GDS_GetHeight(display) + pkt->row : pkt->row;
 				visu.border =  htonl(pkt->border);
@@ -1001,7 +1003,8 @@ static void visu_handler( u8_t *data, int len) {
 				visu.spectrum_scale = htonl(pkt->spectrum_scale) / 100.;
 			} 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);
+				visu.width = htonl(pkt->full.width);
+				visu.height = GDS_GetHeight(display) > displayer.height ? GDS_GetHeight(display) - displayer.height : GDS_GetHeight(display);
 				visu.row = GDS_GetHeight(display) - visu.height;			
 				
 				// is this spectrum or analogue/digital
@@ -1016,22 +1019,27 @@ static void visu_handler( u8_t *data, int len) {
 		} else {
 			// classical (screensaver) mode, don't try to optimize screen usage & force some params
 			visu.row = 0;
-			visu.height = SB_HEIGHT;
+			visu.height = GDS_GetHeight(display);
+			visu.width = displayer.width;				
 			visu.spectrum_scale = 0.25;				
-			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 (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 (visu.style) visu.row = visu.height - VU_HEIGHT;
+			}	
 			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;
+			visu.max = visu.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);
+			visu.max = visu.style ? (VU_COUNT - 1) : (visu.height - 1);
 		}	
 		
 		do {
@@ -1039,14 +1047,14 @@ static void visu_handler( u8_t *data, int len) {
 			if (visu.bar_width > 0) break;
 		} while (--visu.n);	
 		visu.bar_border = (visu.width - visu.border - (visu.bar_width + visu.bar_gap) * visu.n + visu.bar_gap) / 2;
-		
+
 		// give up if not enough space
 		if (visu.bar_width < 0)	{
 			visu.mode = VISU_BLANK;
 			LOG_WARN("Not enough room for displaying visu");
 		} else {
 			// de-activate scroller if we are taking main screen
-			if (visu.row < SB_HEIGHT) scroller.active = false;
+			if (visu.row < displayer.height) scroller.active = false;
 			vTaskResume(displayer.task);
 		}	
 		visu.wake = 0;

binární
plugin/SqueezeESP32.zip


+ 23 - 10
plugin/SqueezeESP32/Graphics.pm

@@ -77,7 +77,7 @@ sub displayWidth {
 	
 	if ($display->widthOverride) {
 		my $artwork = $prefs->client($client)->get('artwork');
-		if ($artwork->{'enable'} && $artwork->{'y'} < 32 && $client->isPlaying) {
+		if ($artwork->{'enable'} && $artwork->{'y'} < 32 && ($client->isPlaying || $client->isPaused)) {
 			return $artwork->{x} + ($display->modes->[$mode || 0]{_width} || 0);
 		} else {
 			return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0);
@@ -168,45 +168,58 @@ sub build_modes {
 		# mode 9	 
 		{ desc => ['VISUALIZER_VUMETER'],
 		bar => 0, secs => 0,  width => $width,
-		params => [$VISUALIZER_VUMETER_ESP32, 0] },
+		params => [$VISUALIZER_VUMETER_ESP32, $width, 0] },
 		# mode 10	
 		{ desc => ['VISUALIZER_ANALOG_VUMETER'],
 		bar => 0, secs => 0,  width => $width,
-		params => [$VISUALIZER_VUMETER_ESP32, 1] },
+		params => [$VISUALIZER_VUMETER_ESP32, $width, 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}] },	  
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
 	);
 
 my @extra = (
 		# mode E1
 		{ desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'],
 		bar => 0, secs => 1,  width => $width,
-		params => [$VISUALIZER_VUMETER_ESP32, 0] },
+		params => [$VISUALIZER_VUMETER_ESP32, $width, 0] },
 		# mode E2	 
 		{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'ELAPSED'],
 		bar => 0, secs => 1,  width => $width,
-		params => [$VISUALIZER_VUMETER_ESP32, 1] },
+		params => [$VISUALIZER_VUMETER_ESP32, $width, 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}] },	  
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
 		# mode E4	 
 		{ desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'],
 		bar => 0, secs => -1,  width => $width,
-		params => [$VISUALIZER_VUMETER_ESP32, 0] },
+		params => [$VISUALIZER_VUMETER_ESP32, $width, 0] },
 		# mode E5
 		{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'REMAINING'],
 		bar => 0, secs => -1,  width => $width,
-		params => [$VISUALIZER_VUMETER_ESP32, 1] },
+		params => [$VISUALIZER_VUMETER_ESP32, $width, 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}] },	
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	
+		# mode E7	 
+		{ desc => ['VISUALIZER_VUMETER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
+		bar => 1, secs => -1,  width => $width,
+		params => [$VISUALIZER_VUMETER_ESP32, $width, 0] },
+		# mode E8
+		{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
+		bar => 1, secs => -1,  width => $width,
+		params => [$VISUALIZER_VUMETER_ESP32, $width, 1] },
+		# mode E9
+		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
+		bar => 1, secs => -1,  width => $width,
+		# extra parameters (bars)
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	
 	);		
 	
 	@modes = (@modes, @extra) if $cprefs->get('height') > 32;

+ 15 - 2
plugin/SqueezeESP32/Plugin.pm

@@ -35,8 +35,22 @@ sub initPlugin {
 	
 	Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['newmetadata'] ] );
 	Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
+	Slim::Control::Request::subscribe( \&onStopClear, [ ['playlist'], ['stop', 'clear'] ]);
 }
 
+sub onStopClear {
+    my $request = shift;
+    my $client  = $request->client;
+	my $artwork = $prefs->client($client)->get('artwork');
+	
+	if ($client->model eq 'squeezeesp32' && $artwork->{'enable'}) {
+		my $reqstr = $request->getRequestString();
+		$log->info("artwork stop/clear $reqstr");
+		$client->pluginData('artwork_md5', '')
+	}	
+}
+
+
 sub onNotification {
     my $request = shift;
     my $client  = $request->client;
@@ -56,8 +70,7 @@ sub update_artwork {
 		
 	return unless $client->model eq 'squeezeesp32' && $artwork->{'enable'};
 
-	my $s = $artwork->{'y'} >= 32 ? $cprefs->get('height') - $artwork->{'y'} : 32;
-	$s = min($s, $cprefs->get('width') - $artwork->{'x'});
+	my $s = min($cprefs->get('height') - $artwork->{'y'}, $cprefs->get('width') - $artwork->{'x'});
 	
 	my $path = 'music/current/cover_' . $s . 'x' . $s . '_o.jpg';
 	my $body = Slim::Web::Graphics::artworkRequest($client, $path, $params, \&send_artwork, undef, HTTP::Response->new);

+ 1 - 1
plugin/SqueezeESP32/install.xml

@@ -10,6 +10,6 @@
   <name>PLUGIN_SQUEEZEESP32</name>
   <description>PLUGIN_SQUEEZEESP32_DESC</description>
   <module>Plugins::SqueezeESP32::Plugin</module>
-    <version>0.61</version>
+    <version>0.70</version>
   <creator>Philippe</creator>
 </extensions>

+ 2 - 2
plugin/repo.xml

@@ -1,10 +1,10 @@
 <?xml version='1.0' standalone='yes'?>
 <extensions>
   <plugins>
-    <plugin version="0.61" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
+    <plugin version="0.70" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
       <link>https://github.com/sle118/squeezelite-esp32</link>
       <creator>Philippe</creator>
-      <sha>5c45fed832e6f79f709bef5f2da511071d1c776e</sha>
+      <sha>2a8cb954928e0cd8f521acd94b90ce9f34a7a1f0</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>