Browse Source

Merge remote-tracking branch 'origin/master' into master-cmake

Sebastien 5 years ago
parent
commit
70b6d54af7

+ 4 - 11
components/display/SSD1306.c

@@ -74,7 +74,7 @@ static void Update( struct GDS_Device* Device ) {
 			CurrentPage = p + 1;
 			CurrentPage = p + 1;
 			
 			
 			// actual write
 			// actual write
-			Device->WriteData( Device, Private->Shadowbuffer + p*width + first, last - first + 1 );
+			Device->WriteData( Device, Private->Shadowbuffer + p*width + first, last - first + 1);
 		}
 		}
 	}	
 	}	
 #else	
 #else	
@@ -114,10 +114,6 @@ static bool Init( struct GDS_Device* Device ) {
 	// charge pump regulator, do direct init
 	// charge pump regulator, do direct init
 	Device->WriteCommand( Device, 0x8D );
 	Device->WriteCommand( Device, 0x8D );
 	Device->WriteCommand( Device, 0x14 ); 
 	Device->WriteCommand( Device, 0x14 ); 
-	
-	// set Clocks
-    Device->WriteCommand( Device, 0xD5 );
-    Device->WriteCommand( Device, ( 0x08 << 4 ) | 0x00 );
 			
 			
 	// COM pins HW config (alternative:EN if 64, DIS if 32, remap:DIS) - some display might need something different
 	// COM pins HW config (alternative:EN if 64, DIS if 32, remap:DIS) - some display might need something different
 	Device->WriteCommand( Device, 0xDA );
 	Device->WriteCommand( Device, 0xDA );
@@ -126,12 +122,6 @@ static bool Init( struct GDS_Device* Device ) {
 	// MUX Ratio
 	// MUX Ratio
     Device->WriteCommand( Device, 0xA8 );
     Device->WriteCommand( Device, 0xA8 );
     Device->WriteCommand( Device, Device->Height - 1);
     Device->WriteCommand( Device, Device->Height - 1);
-	// Page & GDDRAM Start Column High/Low
-	/*
-    Device->WriteCommand( Device, 0x00 );
-	Device->WriteCommand( Device, 0x10 );
-	Device->WriteCommand( Device, 0xB0 );
-	*/
 	// Display Offset
 	// Display Offset
     Device->WriteCommand( Device, 0xD3 );
     Device->WriteCommand( Device, 0xD3 );
     Device->WriteCommand( Device, 0 );
     Device->WriteCommand( Device, 0 );
@@ -143,6 +133,9 @@ static bool Init( struct GDS_Device* Device ) {
 	Device->SetHFlip( Device, false );
 	Device->SetHFlip( Device, false );
 	// no Display Inversion
 	// no Display Inversion
     Device->WriteCommand( Device, 0xA6 );
     Device->WriteCommand( Device, 0xA6 );
+	// set Clocks
+    Device->WriteCommand( Device, 0xD5 );
+    Device->WriteCommand( Device, ( 0x08 << 4 ) | 0x00 );
 	// set Adressing Mode Horizontal
 	// set Adressing Mode Horizontal
 	Device->WriteCommand( Device, 0x20 );
 	Device->WriteCommand( Device, 0x20 );
 	Device->WriteCommand( Device, 0 );
 	Device->WriteCommand( Device, 0 );

+ 4 - 0
components/display/core/gds.c

@@ -60,6 +60,10 @@ 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 ) {
 void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
+	// -1 means up to width/height
+	if (x2 < 0) x2 = Device->Width - 1;
+	if (y2 < 0) y2 = Device->Height - 1;
+	
 	// driver can provide own optimized clear window
 	// driver can provide own optimized clear window
 	if (Device->ClearWindow) {
 	if (Device->ClearWindow) {
 		Device->ClearWindow( Device, x1, y1, x2, y2, Color );
 		Device->ClearWindow( Device, x1, y1, x2, y2, Color );

+ 3 - 2
components/display/core/gds_draw.c

@@ -212,6 +212,7 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
 		uint8_t *optr = Device->Framebuffer;
 		uint8_t *optr = Device->Framebuffer;
 		int LineLen = Device->Width >> 1;
 		int LineLen = Device->Width >> 1;
 		Height >>= 3;
 		Height >>= 3;
+		Color &= 0x0f;
 		for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
 		for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
 			uint8_t Byte = BitReverseTable256[*Data++];
 			uint8_t Byte = BitReverseTable256[*Data++];
 			// we need to linearize code to let compiler better optimize
 			// we need to linearize code to let compiler better optimize
@@ -223,7 +224,7 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
-				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; 
 			} else {
 			} else {
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
@@ -232,7 +233,7 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
 				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
-				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
 			}	
 			}	
 			// end of a column, move to next one
 			// end of a column, move to next one
 			if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }		
 			if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }		

+ 3 - 1
components/display/core/gds_image.c

@@ -218,8 +218,10 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int
 		
 		
 		// then place it
 		// then place it
 		if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = (Device->Width + x - Context.Width) / 2;
 		if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = (Device->Width + x - Context.Width) / 2;
+		else if (Fit & GDS_IMAGE_RIGHT) Context.XOfs = Device->Width - Context.Width;
 		if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = (Device->Height + y - Context.Height) / 2;
 		if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = (Device->Height + y - Context.Height) / 2;
-		
+		else if (Fit & GDS_IMAGE_BOTTOM) Context.YOfs = Device->Height - Context.Height;
+
 		Context.XMin = x - Context.XOfs;
 		Context.XMin = x - Context.XOfs;
 		Context.YMin = y - Context.YOfs;
 		Context.YMin = y - Context.YOfs;
 					
 					

+ 4 - 1
components/display/core/gds_image.h

@@ -9,8 +9,11 @@ struct GDS_Device;
 
 
 enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 };
 enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 };
 
 
-#define GDS_IMAGE_TOP		0x00
+#define GDS_IMAGE_LEFT		0x00
 #define GDS_IMAGE_CENTER_X	0x01
 #define GDS_IMAGE_CENTER_X	0x01
+#define GDS_IMAGE_RIGHT		0x04
+#define GDS_IMAGE_TOP		0x00
+#define GDS_IMAGE_BOTTOM	0x08
 #define GDS_IMAGE_CENTER_Y	0x02
 #define GDS_IMAGE_CENTER_Y	0x02
 #define GDS_IMAGE_CENTER	(GDS_IMAGE_CENTER_X | GDS_IMAGE_CENTER_Y)
 #define GDS_IMAGE_CENTER	(GDS_IMAGE_CENTER_X | GDS_IMAGE_CENTER_Y)
 #define GDS_IMAGE_FIT		0x10
 #define GDS_IMAGE_FIT		0x10

+ 1 - 1
components/display/core/gds_private.h

@@ -167,7 +167,7 @@ static inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int
 	uint8_t* FBOffset;
 	uint8_t* FBOffset;
 
 
     FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
     FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
-	*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color  & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
+	*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
 }
 }
 
 
 static inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
 static inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {

+ 18 - 14
components/squeezelite/decode_external.c

@@ -53,7 +53,7 @@ static raop_event_t	raop_state;
 static EXT_RAM_ATTR struct {
 static EXT_RAM_ATTR struct {
 	bool enabled;
 	bool enabled;
 	int sum, count, win, errors[SYNC_WIN_RUN];
 	int sum, count, win, errors[SYNC_WIN_RUN];
-	u32_t len;
+	s32_t len;
 	u32_t start_time, playtime;
 	u32_t start_time, playtime;
 } raop_sync;
 } raop_sync;
 
 
@@ -210,20 +210,21 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 				// how many ms have we really played
 				// how many ms have we really played
 				ms = now - output.updated + ((u64_t) (output.frames_played_dmp - output.device_frames) * 1000) / RAOP_SAMPLE_RATE;
 				ms = now - output.updated + ((u64_t) (output.frames_played_dmp - output.device_frames) * 1000) / RAOP_SAMPLE_RATE;
 				error = ms - (now - raop_sync.start_time);
 				error = ms - (now - raop_sync.start_time);
+				
 				LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, now - raop_sync.start_time, ms - (now - raop_sync.start_time));
 				LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, now - raop_sync.start_time, ms - (now - raop_sync.start_time));
 			} else {	
 			} else {	
+				u32_t level = _buf_used(outputbuf);
+				
 				// in how many ms will the most recent block play 
 				// in how many ms will the most recent block play 
-				ms = ((u64_t) ((_buf_used(outputbuf) - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 1000) / RAOP_SAMPLE_RATE - (now - output.updated);
-				error = (raop_sync.playtime - now) - ms;
+				ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 10) / (RAOP_SAMPLE_RATE / 100) - (s32_t) (now - output.updated);
 				
 				
-				// make sure impact of erroneous point is limited, but taken into account 
-				if (abs(error) > 1000) {
-					LOG_INFO("limiting erroneous sync point %d", error);
-					error = (error > 0) ? SYNC_WIN_RUN : -SYNC_WIN_RUN;
-				}
-								
-				LOG_DEBUG("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, error);
-				LOG_DEBUG("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
+				// when outputbuf is empty, it means we have a network black-out or something
+				error = level ? (raop_sync.playtime - now) - ms : 0;
+				
+				if (loglevel == lDEBUG || !level) {
+					LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
+					LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
+				}	
 			}	
 			}	
 			
 			
 			// calculate sum, error and update sliding window
 			// calculate sum, error and update sliding window
@@ -232,7 +233,10 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 			error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
 			error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
 			
 			
 			// move to normal mode if possible
 			// move to normal mode if possible
-			if (raop_sync.win == SYNC_WIN_START && raop_sync.count >= SYNC_WIN_START && abs(error) < 10) raop_sync.win = SYNC_WIN_RUN;
+			if (raop_sync.win == SYNC_WIN_START && raop_sync.count >= SYNC_WIN_START && abs(error) < 10) {
+				raop_sync.win = SYNC_WIN_RUN;
+				LOG_INFO("switching to slow sync mode %u", raop_sync.win);
+			}	
 
 
 			// wait till e have enough data or there is a strong deviation
 			// wait till e have enough data or there is a strong deviation
 			if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) { 
 			if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) { 
@@ -241,11 +245,11 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 				if (error < 0) {
 				if (error < 0) {
 					output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
 					output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
 					output.state = OUTPUT_SKIP_FRAMES;					
 					output.state = OUTPUT_SKIP_FRAMES;					
-					LOG_INFO("skipping %u frames", output.skip_frames);
+					LOG_INFO("skipping %u frames (count:%d)", output.skip_frames, raop_sync.count);
 				} else {
 				} else {
 					output.pause_frames = (error * RAOP_SAMPLE_RATE) / 1000;
 					output.pause_frames = (error * RAOP_SAMPLE_RATE) / 1000;
 					output.state = OUTPUT_PAUSE_FRAMES;
 					output.state = OUTPUT_PAUSE_FRAMES;
-					LOG_INFO("pausing for %u frames", output.pause_frames);
+					LOG_INFO("pausing for %u frames (count: %d)", output.pause_frames, raop_sync.count);
 				}
 				}
 
 
 				// reset sliding window		
 				// reset sliding window		

+ 194 - 43
components/squeezelite/display.c

@@ -26,6 +26,7 @@
 #include "gds.h"
 #include "gds.h"
 #include "gds_text.h"
 #include "gds_text.h"
 #include "gds_draw.h"
 #include "gds_draw.h"
+#include "gds_image.h"
 
 
 #pragma pack(push, 1)
 #pragma pack(push, 1)
 
 
@@ -59,6 +60,14 @@ struct grfg_packet {
 	u16_t  width;		// # of pixels of scrollable
 	u16_t  width;		// # of pixels of scrollable
 };
 };
 
 
+struct grfa_packet {
+	char  opcode[4];
+	u32_t length;
+	u16_t x;
+	u16_t y;	
+	u32_t offset;
+};
+
 struct visu_packet {
 struct visu_packet {
 	char  opcode[4];
 	char  opcode[4];
 	u8_t which;
 	u8_t which;
@@ -77,6 +86,29 @@ struct visu_packet {
 			u32_t bars;
 			u32_t bars;
 			u32_t spectrum_scale;
 			u32_t spectrum_scale;
 		};	
 		};	
+		struct {
+			u32_t mono;
+			u32_t bandwidth;
+			u32_t preemph;	
+			struct {
+				u32_t pos;
+				u32_t width;
+				u32_t orient;
+				u32_t bar_width;
+				u32_t bar_space;
+				u32_t clipping;
+				u32_t bar_intens;
+				u32_t bar_cap_intens;
+			} channels[2];
+		};
+		struct {
+			u32_t mono;
+			u32_t style;
+			struct {
+				u32_t pos;
+				u32_t width;
+			} channels[2];
+		} classical_vu;	
 	};	
 	};	
 };
 };
 
 
@@ -130,7 +162,15 @@ static struct scroller_s {
 	u32_t width;
 	u32_t width;
 } scroller;
 } scroller;
 
 
+static struct {
+	u8_t *data;
+	u32_t size;
+	u16_t x, y;
+	bool enable;
+} artwork;
+
 #define MAX_BARS	32
 #define MAX_BARS	32
+#define VISU_ESP32	0x10
 static EXT_RAM_ATTR struct {
 static EXT_RAM_ATTR struct {
 	int bar_gap, bar_width, bar_border;
 	int bar_gap, bar_width, bar_border;
 	struct {
 	struct {
@@ -142,6 +182,11 @@ static EXT_RAM_ATTR struct {
 	enum { VISU_BLANK, VISU_VUMETER, VISU_SPECTRUM, VISU_WAVEFORM } mode;
 	enum { VISU_BLANK, VISU_VUMETER, VISU_SPECTRUM, VISU_WAVEFORM } mode;
 	int speed, wake;	
 	int speed, wake;	
 	float fft[FFT_LEN*2], samples[FFT_LEN*2], hanning[FFT_LEN];
 	float fft[FFT_LEN*2], samples[FFT_LEN*2], hanning[FFT_LEN];
+	struct {
+		u8_t *frame;
+		int width;
+		bool active;
+	} back;		
 } visu;
 } visu;
 
 
 #define ANIM_NONE		  0x00
 #define ANIM_NONE		  0x00
@@ -174,6 +219,7 @@ static void grfe_handler( u8_t *data, int len);
 static void grfb_handler(u8_t *data, int len);
 static void grfb_handler(u8_t *data, int len);
 static void grfs_handler(u8_t *data, int len);
 static void grfs_handler(u8_t *data, int len);
 static void grfg_handler(u8_t *data, int len);
 static void grfg_handler(u8_t *data, int len);
+static void grfa_handler(u8_t *data, int len);
 static void visu_handler(u8_t *data, int len);
 static void visu_handler(u8_t *data, int len);
 
 
 static void displayer_task(void* arg);
 static void displayer_task(void* arg);
@@ -205,6 +251,33 @@ static void displayer_task(void* arg);
 			ANIM_SCREEN_2     0x08 # For scrollonce only, screen 2 was scrolling
 			ANIM_SCREEN_2     0x08 # For scrollonce only, screen 2 was scrolling
 */
 */
 
 
+/* classical visu not our specific version)
+ Parameters for the spectrum analyzer:
+   0 - Channels: stereo == 0, mono == 1
+   1 - Bandwidth: 0..22050Hz == 0, 0..11025Hz == 1
+   2 - Preemphasis in dB per KHz
+ Left channel parameters:
+   3 - Position in pixels
+   4 - Width in pixels
+   5 - orientation: left to right == 0, right to left == 1
+   6 - Bar width in pixels
+   7 - Bar spacing in pixels
+   8 - Clipping: show all subbands == 0, clip higher subbands == 1
+   9 - Bar intensity (greyscale): 1-3
+   10 - Bar cap intensity (greyscale): 1-3
+ Right channel parameters (not required for mono):
+   11-18 - same as left channel parameters
+
+ Parameters for the vumeter:
+   0 - Channels: stereo == 0, mono == 1
+   1 - Style: digital == 0, analog == 1
+ Left channel parameters:
+   2 - Position in pixels
+   3 - Width in pixels
+ Right channel parameters (not required for mono):
+   4-5 - same as left channel parameters
+*/
+
 /****************************************************************************************
 /****************************************************************************************
  * 
  * 
  */
  */
@@ -226,6 +299,7 @@ bool sb_display_init(void) {
 	// create visu configuration
 	// create visu configuration
 	visu.bar_gap = 1;
 	visu.bar_gap = 1;
 	visu.speed = 100;
 	visu.speed = 100;
+	visu.back.frame = calloc(1, (displayer.width * displayer.height) / 8);
 	dsps_fft2r_init_fc32(visu.fft, FFT_LEN);
 	dsps_fft2r_init_fc32(visu.fft, FFT_LEN);
 	dsps_wind_hann_f32(visu.hanning, FFT_LEN);
 	dsps_wind_hann_f32(visu.hanning, FFT_LEN);
 		
 		
@@ -300,7 +374,7 @@ static void send_server(void) {
 		pkt_header.length = htonl(sizeof(pkt_header) - 8);
 		pkt_header.length = htonl(sizeof(pkt_header) - 8);
 		pkt_header.mode = ANIC_resp;
 		pkt_header.mode = ANIC_resp;
 
 
-		send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
+		send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
 		
 		
 		ANIC_resp = ANIM_NONE;
 		ANIC_resp = ANIM_NONE;
 	}	
 	}	
@@ -308,18 +382,22 @@ static void send_server(void) {
 	if (SETD_width) {
 	if (SETD_width) {
 		struct SETD_header pkt_header;
 		struct SETD_header pkt_header;
 		
 		
-		LOG_INFO("sending width %u", SETD_width);	
-		
 		memset(&pkt_header, 0, sizeof(pkt_header));
 		memset(&pkt_header, 0, sizeof(pkt_header));
 		memcpy(&pkt_header.opcode, "SETD", 4);
 		memcpy(&pkt_header.opcode, "SETD", 4);
 
 
 		pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2
 		pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2
-		pkt_header.length = htonl(sizeof(pkt_header) +  2 - 8);
+		pkt_header.length = htonl(sizeof(pkt_header) +  4 - 8);
+		
+		u16_t height = GDS_GetHeight(display);
+		LOG_INFO("sending dimension %ux%u", SETD_width, height);	
 
 
 		SETD_width = htons(SETD_width);
 		SETD_width = htons(SETD_width);
-		send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
-		send_packet((uint8_t*) &SETD_width, 2);
-
+		height = htons(height);
+		
+		send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
+		send_packet((uint8_t *) &SETD_width, 2);
+		send_packet((uint8_t *) &height, 2);
+		
 		SETD_width = 0;
 		SETD_width = 0;
 	}	
 	}	
 	
 	
@@ -360,6 +438,8 @@ static bool handler(u8_t *data, int len){
 		grfs_handler(data, len);		
 		grfs_handler(data, len);		
 	} else if (!strncmp((char*) data, "grfg", 4)) {
 	} else if (!strncmp((char*) data, "grfg", 4)) {
 		grfg_handler(data, len);
 		grfg_handler(data, len);
+	} else if (!strncmp((char*) data, "grfa", 4)) {
+		grfa_handler(data, len);		
 	} else if (!strncmp((char*) data, "visu", 4)) {
 	} else if (!strncmp((char*) data, "visu", 4)) {
 		visu_handler(data, len);
 		visu_handler(data, len);
 	} else {
 	} else {
@@ -495,7 +575,7 @@ static void grfe_handler( u8_t *data, int len) {
 	scroller.active = false;
 	scroller.active = false;
 	
 	
 	// we are not in control or we are displaying visu on a small screen, do not do screen update
 	// we are not in control or we are displaying visu on a small screen, do not do screen update
-	if (visu.mode && !visu.col && visu.row < SB_HEIGHT) {
+	if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < SB_HEIGHT) {
 		xSemaphoreGive(displayer.mutex);
 		xSemaphoreGive(displayer.mutex);
 		return;
 		return;
 	}	
 	}	
@@ -509,6 +589,16 @@ static void grfe_handler( u8_t *data, int len) {
 	
 	
 		// draw new frame, it might be less than full screen (small visu)
 		// draw new frame, it might be less than full screen (small visu)
 		int width = ((len - sizeof(struct grfe_packet)) * 8) / displayer.height;
 		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;
+			memset(visu.back.frame, 0, (displayer.width * displayer.height) / 8);
+			memcpy(visu.back.frame, data + sizeof(struct grfe_packet), (width * displayer.height) / 8);
+			// this is a bit tricky but basically that checks if frame if full of 0
+			visu.back.active = *visu.back.frame || memcmp(visu.back.frame, visu.back.frame + 1, width - 1);
+		}
+		
 		GDS_DrawBitmapCBR(display, data + sizeof(struct grfe_packet), width, displayer.height, GDS_COLOR_WHITE);
 		GDS_DrawBitmapCBR(display, data + sizeof(struct grfe_packet), width, displayer.height, GDS_COLOR_WHITE);
 		GDS_Update(display);
 		GDS_Update(display);
 	}	
 	}	
@@ -548,7 +638,7 @@ static void grfs_handler(u8_t *data, int len) {
 	int size = len - sizeof(struct grfs_packet);
 	int size = len - sizeof(struct grfs_packet);
 	int offset = htons(pkt->offset);
 	int offset = htons(pkt->offset);
 	
 	
-	LOG_DEBUG("gfrs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu", 
+	LOG_DEBUG("grfs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu", 
 				(int) pkt->screen,
 				(int) pkt->screen,
 				(int) pkt->direction,	// 1=left, 2=right
 				(int) pkt->direction,	// 1=left, 2=right
 				htonl(pkt->pause),		// in ms	
 				htonl(pkt->pause),		// in ms	
@@ -573,9 +663,6 @@ static void grfs_handler(u8_t *data, int len) {
 		scroller.first = true;
 		scroller.first = true;
 		scroller.overflow = false;
 		scroller.overflow = false;
 		
 		
-		// background excludes space taken by visu (if any)
-		scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0);
-		
 		// set scroller steps & beginning
 		// set scroller steps & beginning
 		if (pkt->direction == 1) {
 		if (pkt->direction == 1) {
 			scroller.scrolled = 0;
 			scroller.scrolled = 0;
@@ -612,6 +699,7 @@ static void grfg_handler(u8_t *data, int len) {
 	
 	
 	// size of scrollable area (less than background)
 	// size of scrollable area (less than background)
 	scroller.width = htons(pkt->width);
 	scroller.width = htons(pkt->width);
+	scroller.back.width = ((len - sizeof(struct grfg_packet)) * 8) / displayer.height;
 	memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
 	memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
 		
 		
 	// update display asynchronously (frames are organized by columns)
 	// update display asynchronously (frames are organized by columns)
@@ -636,15 +724,63 @@ static void grfg_handler(u8_t *data, int len) {
 	vTaskResume(displayer.task);
 	vTaskResume(displayer.task);
 }
 }
 
 
+
+/****************************************************************************************
+ * Artwork
+ */
+static void grfa_handler(u8_t *data, int len) {
+	struct grfa_packet *pkt = (struct grfa_packet*) data;
+	int size = len - sizeof(struct grfa_packet);
+	int offset = htonl(pkt->offset);
+	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);
+		return;
+	}	
+	
+	// new grfa artwork, allocate memory
+	if (!offset) {	
+		// same trick to clean current/previous window
+		if (artwork.size) {
+			GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK);
+			artwork.size = 0;
+		}
+		
+		// now use new parameters
+		artwork.x = htons(pkt->x);
+		artwork.y = htons(pkt->y);
+		if (artwork.data) free(artwork.data);
+		artwork.data = malloc(length);
+	}	
+	
+	// copy artwork data
+	memcpy(artwork.data + offset, data + sizeof(struct grfa_packet), size);
+	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);
+		free(artwork.data);
+		artwork.data = NULL;
+	} 
+	
+	LOG_INFO("gfra l:%u x:%hu, y:%hu, o:%u s:%u", length, artwork.x, artwork.y, offset, size);
+}
+
 /****************************************************************************************
 /****************************************************************************************
  * Update visualization bars
  * Update visualization bars
  */
  */
 static void visu_update(void) {
 static void visu_update(void) {
 	// no need to protect against no woning the display as we are playing	
 	// no need to protect against no woning the display as we are playing	
 	if (pthread_mutex_trylock(&visu_export.mutex)) return;
 	if (pthread_mutex_trylock(&visu_export.mutex)) return;
+	
+	int mode = visu.mode & ~VISU_ESP32;
 				
 				
 	// not enough samples
 	// not enough samples
-	if (visu_export.level < (visu.mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) * 2 && visu_export.running) {
+	if (visu_export.level < (mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) * 2 && visu_export.running) {
 		pthread_mutex_unlock(&visu_export.mutex);
 		pthread_mutex_unlock(&visu_export.mutex);
 		return;
 		return;
 	}
 	}
@@ -654,7 +790,7 @@ static void visu_update(void) {
 	
 	
 	if (visu_export.running) {
 	if (visu_export.running) {
 					
 					
-		if (visu.mode == VISU_VUMETER) {
+		if (mode == VISU_VUMETER) {
 			s16_t *iptr = visu_export.buffer;
 			s16_t *iptr = visu_export.buffer;
 			
 			
 			// calculate sum(L²+R²), try to not overflow at the expense of some precision
 			// calculate sum(L²+R²), try to not overflow at the expense of some precision
@@ -667,7 +803,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)
 			// 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;) {	 
 			for (int i = visu.n; --i >= 0;) {	 
-				visu.bars[i].current = 32 * (0.01667f*10*log10f(0.0000001f + (visu.bars[i].current >> 1)) - 0.2543f);
+				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 > 31) visu.bars[i].current = 31;
 				else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
 				else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
 			}
 			}
@@ -709,7 +845,7 @@ static void visu_update(void) {
 				}	
 				}	
 			
 			
 				// convert to dB and bars, same back-off
 				// convert to dB and bars, same back-off
-				if (power) visu.bars[i].current = 32 * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f);
+				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 > 31) visu.bars[i].current = 31;
 				else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
 				else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
 			}	
 			}	
@@ -724,6 +860,11 @@ static void visu_update(void) {
 	int clear = 0;
 	int clear = 0;
 	for (int i = visu.n; --i >= 0;) clear = max(clear, visu.bars[i].max);
 	for (int i = visu.n; --i >= 0;) clear = max(clear, visu.bars[i].max);
 	if (clear) GDS_ClearExt(display, false, false, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
 	if (clear) GDS_ClearExt(display, false, false, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
+	
+	// draw background if we are in screensaver mode
+	if (!(visu.mode & VISU_ESP32) && visu.back.active) {
+		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
 	// there is much more optimization to be done here, like not redrawing bars unless needed
 	for (int i = visu.n; --i >= 0;) {
 	for (int i = visu.n; --i >= 0;) {
@@ -778,37 +919,47 @@ static void visu_handler( u8_t *data, int len) {
 	visu.mode = pkt->which;
 	visu.mode = pkt->which;
 	
 	
 	// little trick to clean the taller screens when switching visu 
 	// 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 >= SB_HEIGHT) GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
 	
 	
 	if (visu.mode) {
 	if (visu.mode) {
-		if (pkt->count >= 4) {
-			// small visu, then go were we are told to
-			pkt->height = htonl(pkt->height);
-			pkt->row = htonl(pkt->row);
-			pkt->col = htonl(pkt->col);
-
-			visu.width = htonl(pkt->width);
-			visu.height = pkt->height ? pkt->height : SB_HEIGHT;
-			visu.col = pkt->col < 0 ? displayer.width + pkt->col : pkt->col;
-			visu.row = pkt->row < 0 ? GDS_GetHeight(display) + pkt->row : pkt->row;
-			visu.border =  htonl(pkt->border);
-			bars = htonl(pkt->bars);
-			visu.spectrum_scale = htonl(pkt->spectrum_scale) / 100.;
-			
-			// might have a race condition with scroller message, so update width in case
-			if (scroller.active) scroller.back.width = displayer.width - visu.width;
+		// these will be overidden if necessary
+		visu.col = visu.border = 0;
+		visu.width = displayer.width;				
+		
+		// what type of visu
+		if (visu.mode & VISU_ESP32) {
+			if (pkt->count >= 4) {
+				// more than 4 parameters, this is small visu, then go were we are told to
+				pkt->height = htonl(pkt->height);
+				pkt->row = htonl(pkt->row);
+				pkt->col = htonl(pkt->col);
+
+				visu.width = htonl(pkt->width);
+				visu.height = pkt->height ? pkt->height : SB_HEIGHT;
+				visu.col = pkt->col < 0 ? displayer.width + pkt->col : pkt->col;
+				visu.row = pkt->row < 0 ? GDS_GetHeight(display) + pkt->row : pkt->row;
+				visu.border =  htonl(pkt->border);
+				bars = htonl(pkt->bars);
+				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);
+				bars = htonl(pkt->full.bars);
+				visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.;
+				visu.row = GDS_GetHeight(display) - visu.height;			
+			}	
 		} else {
 		} else {
-			// full screen visu, try to use bottom screen if available
-			visu.width = displayer.width;
-			visu.height = GDS_GetHeight(display) > SB_HEIGHT ? GDS_GetHeight(display) - SB_HEIGHT : GDS_GetHeight(display);
-			visu.col = visu.border = 0;
-			visu.row = GDS_GetHeight(display) - visu.height;			
-			bars = htonl(pkt->full.bars);
-			visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.;
-		}
+			// 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));
+			if (bars > MAX_BARS) bars = MAX_BARS;
+		}	
 		
 		
 		// try to adapt to what we have
 		// try to adapt to what we have
-		if (visu.mode == VISU_SPECTRUM) {
+		if ((visu.mode & ~VISU_ESP32) == VISU_SPECTRUM) {
 			visu.n = bars ? bars : MAX_BARS;
 			visu.n = bars ? bars : MAX_BARS;
 			if (visu.spectrum_scale <= 0 || visu.spectrum_scale > 0.5) visu.spectrum_scale = 0.5;
 			if (visu.spectrum_scale <= 0 || visu.spectrum_scale > 0.5) visu.spectrum_scale = 0.5;
 			spectrum_limits(0, visu.n, 0);
 			spectrum_limits(0, visu.n, 0);
@@ -836,7 +987,7 @@ static void visu_handler( u8_t *data, int len) {
 		// reset bars maximum
 		// reset bars maximum
 		for (int i = visu.n; --i >= 0;) visu.bars[i].max = 0;
 		for (int i = visu.n; --i >= 0;) visu.bars[i].max = 0;
 				
 				
-		GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1);
+		GDS_ClearExt(display, false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
 		
 		
 		LOG_INFO("Visualizer with %u bars of width %d:%d:%d:%d (%w:%u,h:%u,c:%u,r:%u,s:%.02f)", visu.n, visu.bar_border, visu.bar_width, visu.bar_gap, visu.border, visu.width, visu.height, visu.col, visu.row, visu.spectrum_scale);
 		LOG_INFO("Visualizer with %u bars of width %d:%d:%d:%d (%w:%u,h:%u,c:%u,r:%u,s:%.02f)", visu.n, visu.bar_border, visu.bar_width, visu.bar_gap, visu.border, visu.width, visu.height, visu.col, visu.row, visu.spectrum_scale);
 	} else {
 	} else {

BIN
plugin/SqueezeESP32.zip


+ 32 - 13
plugin/SqueezeESP32/Graphics.pm

@@ -14,6 +14,8 @@ my $VISUALIZER_NONE = 0;
 my $VISUALIZER_VUMETER = 1;
 my $VISUALIZER_VUMETER = 1;
 my $VISUALIZER_SPECTRUM_ANALYZER = 2;
 my $VISUALIZER_SPECTRUM_ANALYZER = 2;
 my $VISUALIZER_WAVEFORM = 3;
 my $VISUALIZER_WAVEFORM = 3;
+my $VISUALIZER_VUMETER_ESP32 = 0x11;
+my $VISUALIZER_SPECTRUM_ANALYZER_ESP32 = 0x12;
 
 
 {
 {
 	#__PACKAGE__->mk_accessor('array', 'modes');
 	#__PACKAGE__->mk_accessor('array', 'modes');
@@ -71,7 +73,12 @@ sub displayWidth {
 	}
 	}
 	
 	
 	if ($display->widthOverride) {
 	if ($display->widthOverride) {
-		return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0);
+		my $artwork = $prefs->client($client)->get('artwork');
+		if ($artwork->{'enable'} && $artwork->{'y'} < 32 && $client->isPlaying) {
+			return $artwork->{x} + ($display->modes->[$mode || 0]{_width} || 0);
+		} else {
+			return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0);
+		}	
 	} else {
 	} else {
 		return $display->modes->[$mode || 0]{width};
 		return $display->modes->[$mode || 0]{width};
 	}	
 	}	
@@ -100,10 +107,22 @@ sub build_modes {
 	my $client = shift->client;
 	my $client = shift->client;
 	my $cprefs = $prefs->client($client);
 	my $cprefs = $prefs->client($client);
 	
 	
-	my $width = shift || $cprefs->get('width') || 128;
+	my $width = $cprefs->get('width') || 128;
+	my $artwork = $cprefs->get('artwork');
+	
+	# if artwork is in main display, reduce width
+	$width = $artwork->{'x'} if $artwork->{'enable'} && $artwork->{y} < 32;
+	
 	my $small_VU = $cprefs->get('small_VU');
 	my $small_VU = $cprefs->get('small_VU');
 	my $spectrum = $cprefs->get('spectrum');
 	my $spectrum = $cprefs->get('spectrum');
 	
 	
+	my $small_spectrum_pos = { x => $width - int ($spectrum->{small}->{size} * $width / 100), 
+						 width => int ($spectrum->{small}->{size} * $width / 100),
+			};
+	my $small_VU_pos = { x => $width - int ($small_VU * $width / 100), 
+						 width => int ($small_VU * $width / 100),
+			};		
+	
 	my @modes = (
 	my @modes = (
 		# mode 0
 		# mode 0
 		{ desc => ['BLANK'],
 		{ desc => ['BLANK'],
@@ -135,41 +154,41 @@ sub build_modes {
 		params => [$VISUALIZER_NONE] },
 		params => [$VISUALIZER_NONE] },
 		# mode 7
 		# mode 7
 		{ desc => ['VISUALIZER_VUMETER_SMALL'],
 		{ desc => ['VISUALIZER_VUMETER_SMALL'],
-		bar => 0, secs => 0,  width => $width, _width => int -($small_VU*$width/100),
+		bar => 0, secs => 0,  width => $width, _width => -$small_VU_pos->{'width'},
 		# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space)
 		# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space)
-		params => [$VISUALIZER_VUMETER, int ($small_VU*$width/100), 32, int -($small_VU*$width/100), 0, 2] },
+		params => [$VISUALIZER_VUMETER_ESP32, $small_VU_pos->{'width'}, 32, $small_VU_pos->{'x'}, 0, 2] },
 		# mode 8
 		# mode 8
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'],
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'],
-		bar => 0, secs => 0,  width => $width, _width => int -($spectrum->{small}->{size}*$width/100),
-		# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space, bars)
-		params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($spectrum->{small}->{size}*$width/100), 32, int -($spectrum->{small}->{size}*$width/100), 0, 2, int ($spectrum->{small}->{size}/100*$width/$spectrum->{small}->{band}), $spectrum->{scale}] },  
+		bar => 0, secs => 0,  width => $width, _width => -$small_spectrum_pos->{'width'},
+		# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space, #bars, scale)
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $small_spectrum_pos->{width}, 32, $small_spectrum_pos->{'x'}, 0, 2, $small_spectrum_pos->{'width'} / $spectrum->{small}->{band}, $spectrum->{scale}] },  
 		# mode 9	 
 		# mode 9	 
 		{ desc => ['VISUALIZER_VUMETER'],
 		{ desc => ['VISUALIZER_VUMETER'],
 		bar => 0, secs => 0,  width => $width,
 		bar => 0, secs => 0,  width => $width,
-		params => [$VISUALIZER_VUMETER] },
+		params => [$VISUALIZER_VUMETER_ESP32] },
 		# mode 10
 		# mode 10
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER'],
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER'],
 		bar => 0, secs => 0,  width => $width,
 		bar => 0, secs => 0,  width => $width,
 		# extra parameters (bars)
 		# extra parameters (bars)
-		params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
 		# mode 11	 
 		# mode 11	 
 		{ desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'],
 		{ desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'],
 		bar => 0, secs => 1,  width => $width,
 		bar => 0, secs => 1,  width => $width,
-		params => [$VISUALIZER_VUMETER] },
+		params => [$VISUALIZER_VUMETER_ESP32] },
 		# mode 12
 		# mode 12
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'],
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'],
 		bar => 0, secs => 1,  width => $width,
 		bar => 0, secs => 1,  width => $width,
 		# extra parameters (bars)
 		# extra parameters (bars)
-		params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	  
 		# mode 13	 
 		# mode 13	 
 		{ desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'],
 		{ desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'],
 		bar => 0, secs => -1,  width => $width,
 		bar => 0, secs => -1,  width => $width,
-		params => [$VISUALIZER_VUMETER] },
+		params => [$VISUALIZER_VUMETER_ESP32] },
 		# mode 14
 		# mode 14
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'],
 		{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'],
 		bar => 0, secs => -1,  width => $width,
 		bar => 0, secs => -1,  width => $width,
 		# extra parameters (bars)
 		# extra parameters (bars)
-		params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	
+		params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },	
 	);
 	);
 	
 	
 	return \@modes;
 	return \@modes;

+ 0 - 20
plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/basic.html

@@ -1,20 +0,0 @@
-[% PROCESS settings/header.html %]
-
-	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_BANNER" %]
-		<div>[% "PLUGIN_SQUEEZEESP32_BANNER_TEXT" | string %]</div>
-	[% END %]
-
-	<div class="prefDesc">
-	
-	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %]
-		<input type="text" class="stdedit" name="pref_width" id="pref_width" value="[% prefs.pref_width %]" size="3">
-	[% END %]
-	
-	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE" desc="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC" %]
-		<input type="number" min="10" max= "50" step="5" class="stdedit" name="pref_spectrum_scale" id="pref_spectrum_scale" value="[% prefs.pref_spectrum_scale %]" size="3">
-	[% END %]
-	
-	
-	</div>
-
-[% PROCESS settings/footer.html %]

+ 9 - 0
plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html

@@ -24,5 +24,14 @@
 		<input type="text" class="stdedit" name="pref_spectrum_full_band" id="spectrum_full_band" value="[% prefs.pref_spectrum.full.band %]" size="3">
 		<input type="text" class="stdedit" name="pref_spectrum_full_band" id="spectrum_full_band" value="[% prefs.pref_spectrum.full.band %]" size="3">
 	[% END %]
 	[% END %]
 	
 	
+	[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_ARTWORK" desc="PLUGIN_SQUEEZEESP32_ARTWORK_DESC" %]
+		[% "PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE" | string %]&nbsp
+		<input type="checkbox" name="pref_artwork_enable" [% IF prefs.pref_artwork.enable %] checked [% END %]>
+		[% "PLUGIN_SQUEEZEESP32_ARTWORK_X" | string %]&nbsp
+		<input type="text" class="stdedit" name="pref_artwork_x" id="artwork_x" value="[% prefs.pref_artwork.x %]" size="2">
+		[% "PLUGIN_SQUEEZEESP32_ARTWORK_Y" | string %]&nbsp
+		<input type="text" class="stdedit" name="pref_artwork_y" id="artwork_y" value="[% prefs.pref_artwork.y %]" size="2">
+	[% END %]
+	
 
 
 [% PROCESS settings/footer.html %]
 [% PROCESS settings/footer.html %]

+ 13 - 5
plugin/SqueezeESP32/Player.pm

@@ -20,24 +20,32 @@ sub playerSettingsFrame {
 	
 	
 	my $value;
 	my $value;
 	my $id = unpack('C', $$data_ref);
 	my $id = unpack('C', $$data_ref);
-		        
-	# New SETD command 0xfe for display width
+	
+	# New SETD command 0xfe for display width & height
 	if ($id == 0xfe) { 
 	if ($id == 0xfe) { 
 		$value = (unpack('Cn', $$data_ref))[1];
 		$value = (unpack('Cn', $$data_ref))[1];
 		if ($value > 100 && $value < 400) {
 		if ($value > 100 && $value < 400) {
 			$prefs->client($client)->set('width', $value);
 			$prefs->client($client)->set('width', $value);
-			$client->display->modes($client->display->build_modes($value));
+			$client->display->modes($client->display->build_modes);
 			$client->display->widthOverride(1, $value);
 			$client->display->widthOverride(1, $value);
 			$client->update;
 			$client->update;
 		} 
 		} 
-		$log->info("Setting player width $value 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);
 	$client->SUPER::playerSettingsFrame($data_ref);
 }
 }
 
 
-sub hasScrolling  {
+sub hasScrolling {
 	return 1;
 	return 1;
 }
 }
 
 
+sub reconnect {
+	my $client = shift;
+	$client->pluginData('artwork_md5', '');
+	$client->SUPER::reconnect(@_);
+}	
+
 1;
 1;

+ 15 - 2
plugin/SqueezeESP32/PlayerSettings.pm

@@ -30,7 +30,7 @@ sub page {
 
 
 sub prefs {
 sub prefs {
 	my ($class, $client) = @_;
 	my ($class, $client) = @_;
-	my @prefs = qw(width small_VU spectrum);
+	my @prefs = qw(width small_VU spectrum artwork);
 	return ($prefs->client($client), @prefs);
 	return ($prefs->client($client), @prefs);
 }
 }
 
 
@@ -47,8 +47,20 @@ sub handler {
 							full  => { 	band => $paramRef->{'pref_spectrum_full_band'} },
 							full  => { 	band => $paramRef->{'pref_spectrum_full_band'} },
 				};
 				};
 		$cprefs->set('spectrum', $spectrum);
 		$cprefs->set('spectrum', $spectrum);
+		my $artwork =	{	enable => $paramRef->{'pref_artwork_enable'},
+							x => $paramRef->{'pref_artwork_x'}, 
+							y => $paramRef->{'pref_artwork_y'},
+				};
+		$cprefs->set('artwork', $artwork);				
 		$client->display->modes($client->display->build_modes);
 		$client->display->modes($client->display->build_modes);
 		$client->display->update;
 		$client->display->update;
+		
+		# force update or disable artwork
+		if ($artwork->{'enable'}) {
+			Plugins::SqueezeESP32::Plugin::update_artwork($client, 1);
+		} else {
+			Plugins::SqueezeESP32::Plugin::disable_artwork($client);
+		}	
 	}
 	}
 	
 	
 	# as there is nothing captured, we need to re-set these variables
 	# as there is nothing captured, we need to re-set these variables
@@ -56,9 +68,10 @@ sub handler {
 	
 	
 	# here I don't know why you need to set again spectrum which is a reference
 	# here I don't know why you need to set again spectrum which is a reference
 	# to a hash. Using $paramRef->{prefs} does not work either. It seems that 
 	# to a hash. Using $paramRef->{prefs} does not work either. It seems that 
-	# soem are copies of value, some are references, can't figure out.This whole
+	# some are copies of value, some are references, can't figure out. This whole
 	# logic of "Settings" is beyond me and I really hate it
 	# logic of "Settings" is beyond me and I really hate it
 	$paramRef->{'pref_spectrum'} = $cprefs->get('spectrum');
 	$paramRef->{'pref_spectrum'} = $cprefs->get('spectrum');
+	$paramRef->{'pref_artwork'} = $cprefs->get('artwork');
 	
 	
 	return $class->SUPER::handler($client, $paramRef);
 	return $class->SUPER::handler($client, $paramRef);
 }
 }

+ 71 - 0
plugin/SqueezeESP32/Plugin.pm

@@ -3,8 +3,12 @@ package Plugins::SqueezeESP32::Plugin;
 use strict;
 use strict;
 
 
 use base qw(Slim::Plugin::Base);
 use base qw(Slim::Plugin::Base);
+
+use Digest::MD5 qw(md5);
+use List::Util qw(min);
 use Slim::Utils::Prefs;
 use Slim::Utils::Prefs;
 use Slim::Utils::Log;
 use Slim::Utils::Log;
+use Slim::Web::ImageProxy;
 
 
 my $prefs = preferences('plugin.squeezeesp32');
 my $prefs = preferences('plugin.squeezeesp32');
 
 
@@ -28,6 +32,73 @@ sub initPlugin {
 	$class->SUPER::initPlugin(@_);
 	$class->SUPER::initPlugin(@_);
 	Slim::Networking::Slimproto::addPlayerClass($class, 100, 'squeezeesp32', { client => 'Plugins::SqueezeESP32::Player', display => 'Plugins::SqueezeESP32::Graphics' });
 	Slim::Networking::Slimproto::addPlayerClass($class, 100, 'squeezeesp32', { client => 'Plugins::SqueezeESP32::Player', display => 'Plugins::SqueezeESP32::Graphics' });
 	$log->info("Added class 100 for SqueezeESP32");
 	$log->info("Added class 100 for SqueezeESP32");
+	
+	Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['newmetadata'] ] );
+	Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
+}
+
+sub onNotification {
+    my $request = shift;
+    my $client  = $request->client;
+	
+	my $reqstr     = $request->getRequestString();
+	$log->info("artwork update notification $reqstr");
+	#my $path = $request->getParam('_path');
+
+	update_artwork($client);
+}
+
+sub update_artwork {
+    my $client  = shift;
+	my $force = shift || 0;
+	my $cprefs = $prefs->client($client);
+	my $artwork = $cprefs->get('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 $path = 'music/current/cover_' . $s . 'x' . $s . '_o.jpg';
+	my $body = Slim::Web::Graphics::artworkRequest($client, $path, $force, \&send_artwork, undef, HTTP::Response->new);
+	
+	send_artwork($client, undef, \$body) if $body;
+}
+
+sub send_artwork {
+	my ($client, $force, $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;
+	
+	$client->pluginData('artwork', $dataref);
+	$client->pluginData('artwork_md5', $md5);
+	
+	my $artwork = $prefs->client($client)->get('artwork');
+	my $length = length $$dataref;
+	my $offset = 0;
+	
+	$log->info("got resized artwork (length: ", length $$dataref, ")");
+	
+	my $header = pack('Nnn', $length, $artwork->{'x'}, $artwork->{'y'});
+	
+	while ($length > 0) {
+		$length = 1280 if $length > 1280;
+		$log->info("sending grfa $length");
+			
+		my $data = $header . pack('N', $offset) . substr( $$dataref, 0, $length, '' );
+			
+		$client->sendFrame( grfa => \$data );
+		$offset += $length;			
+		$length = length $$dataref;
+	}
+}	
+
+sub disable_artwork {
+	my ($client) = @_;
+	my $header = pack('N', 0);
+	$client->sendFrame( grfa => \$header );
 }
 }
 
 
 1;
 1;

+ 0 - 30
plugin/SqueezeESP32/Settings.pm

@@ -1,30 +0,0 @@
-package Plugins::SqueezeESP32::Settings;
-use base qw(Slim::Web::Settings);
-
-use strict;
-
-use Slim::Utils::Prefs;
-use Slim::Utils::Log;
-
-my $log = logger('plugin.squeezeesp32');
-
-sub name {
-	return 'PLUGIN_SQUEEZEESP32';
-}
-
-sub page {
-	return 'plugins/SqueezeESP32/settings/basic.html';
-}
-
-sub prefs {
-	return (preferences('plugin.squeezeesp32'), qw(width spectrum_scale));
-}
-
-sub handler {
-	my ($class, $client, $params, $callback, @args) = @_;
-	
-	$callback->($client, $params, $class->SUPER::handler($client, $params), @args);
-}
-
-	
-1;

+ 1 - 1
plugin/SqueezeESP32/install.xml

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

+ 16 - 0
plugin/SqueezeESP32/strings.txt

@@ -53,3 +53,19 @@ PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND
 		
 		
 PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC
 PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC
 	EN	The number of bands is the width of the screen divided by this factor
 	EN	The number of bands is the width of the screen divided by this factor
+	
+PLUGIN_SQUEEZEESP32_ARTWORK
+	EN	Artwork
+	
+PLUGIN_SQUEEZEESP32_ARTWORK_DESC
+	EN	When Y position is less than 32, then artwork is display at the right of the main screen and x defines the starting position
+	EN	Using artwork on less than 16-levels grayscale display if really poor quality
+
+PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE
+	EN	Enable
+	
+PLUGIN_SQUEEZEESP32_ARTWORK_X
+	EN	X
+
+PLUGIN_SQUEEZEESP32_ARTWORK_Y
+	EN	Y

+ 2 - 2
plugin/repo.xml

@@ -1,10 +1,10 @@
 <?xml version='1.0' standalone='yes'?>
 <?xml version='1.0' standalone='yes'?>
 <extensions>
 <extensions>
   <plugins>
   <plugins>
-    <plugin version="0.31" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
+    <plugin version="0.50" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
       <link>https://github.com/sle118/squeezelite-esp32</link>
       <link>https://github.com/sle118/squeezelite-esp32</link>
       <creator>Philippe</creator>
       <creator>Philippe</creator>
-      <sha>6dc35a0f9f9b287d205f7532cbb642b08407a284</sha>
+      <sha>47feaf69a40ad4f87c58b34212d71e60dca99d3e</sha>
       <email>philippe_44@outlook.com</email>
       <email>philippe_44@outlook.com</email>
       <desc lang="EN">SqueezeESP32 additional player id (100)</desc>
       <desc lang="EN">SqueezeESP32 additional player id (100)</desc>
       <url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>
       <url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>