|
@@ -86,6 +86,29 @@ struct visu_packet {
|
|
|
u32_t bars;
|
|
|
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;
|
|
|
};
|
|
|
};
|
|
|
|
|
@@ -143,9 +166,11 @@ static struct {
|
|
|
u8_t *data;
|
|
|
u32_t size;
|
|
|
u16_t x, y;
|
|
|
+ bool enable;
|
|
|
} artwork;
|
|
|
|
|
|
#define MAX_BARS 32
|
|
|
+#define VISU_ESP32 0x10
|
|
|
static EXT_RAM_ATTR struct {
|
|
|
int bar_gap, bar_width, bar_border;
|
|
|
struct {
|
|
@@ -157,6 +182,11 @@ static EXT_RAM_ATTR struct {
|
|
|
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];
|
|
|
+ struct {
|
|
|
+ u8_t *frame;
|
|
|
+ int width;
|
|
|
+ bool active;
|
|
|
+ } back;
|
|
|
} visu;
|
|
|
|
|
|
#define ANIM_NONE 0x00
|
|
@@ -221,6 +251,33 @@ static void displayer_task(void* arg);
|
|
|
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
|
|
|
+*/
|
|
|
+
|
|
|
/****************************************************************************************
|
|
|
*
|
|
|
*/
|
|
@@ -242,6 +299,7 @@ bool sb_display_init(void) {
|
|
|
// create visu configuration
|
|
|
visu.bar_gap = 1;
|
|
|
visu.speed = 100;
|
|
|
+ visu.back.frame = calloc(1, (displayer.width * displayer.height) / 8);
|
|
|
dsps_fft2r_init_fc32(visu.fft, FFT_LEN);
|
|
|
dsps_wind_hann_f32(visu.hanning, FFT_LEN);
|
|
|
|
|
@@ -517,7 +575,7 @@ 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
|
|
|
- if (visu.mode && !visu.col && visu.row < SB_HEIGHT) {
|
|
|
+ if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < SB_HEIGHT) {
|
|
|
xSemaphoreGive(displayer.mutex);
|
|
|
return;
|
|
|
}
|
|
@@ -531,6 +589,16 @@ static void grfe_handler( u8_t *data, int len) {
|
|
|
|
|
|
// 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;
|
|
|
+ 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_Update(display);
|
|
|
}
|
|
@@ -570,7 +638,7 @@ static void grfs_handler(u8_t *data, int len) {
|
|
|
int size = len - sizeof(struct grfs_packet);
|
|
|
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->direction, // 1=left, 2=right
|
|
|
htonl(pkt->pause), // in ms
|
|
@@ -666,6 +734,14 @@ static void grfa_handler(u8_t *data, int len) {
|
|
|
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
|
|
@@ -679,7 +755,6 @@ static void grfa_handler(u8_t *data, int len) {
|
|
|
artwork.y = htons(pkt->y);
|
|
|
if (artwork.data) free(artwork.data);
|
|
|
artwork.data = malloc(length);
|
|
|
- artwork.size = 0;
|
|
|
}
|
|
|
|
|
|
// copy artwork data
|
|
@@ -687,7 +762,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 < 32 ? (GDS_IMAGE_RIGHT | GDS_IMAGE_TOP) : GDS_IMAGE_CENTER);
|
|
|
+ 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;
|
|
|
}
|
|
@@ -701,9 +776,11 @@ static void grfa_handler(u8_t *data, int len) {
|
|
|
static void visu_update(void) {
|
|
|
// no need to protect against no woning the display as we are playing
|
|
|
if (pthread_mutex_trylock(&visu_export.mutex)) return;
|
|
|
+
|
|
|
+ int mode = visu.mode & ~VISU_ESP32;
|
|
|
|
|
|
// 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);
|
|
|
return;
|
|
|
}
|
|
@@ -713,7 +790,7 @@ static void visu_update(void) {
|
|
|
|
|
|
if (visu_export.running) {
|
|
|
|
|
|
- if (visu.mode == VISU_VUMETER) {
|
|
|
+ if (mode == VISU_VUMETER) {
|
|
|
s16_t *iptr = visu_export.buffer;
|
|
|
|
|
|
// calculate sum(L²+R²), try to not overflow at the expense of some precision
|
|
@@ -726,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)
|
|
|
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;
|
|
|
else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
|
|
|
}
|
|
@@ -768,7 +845,7 @@ static void visu_update(void) {
|
|
|
}
|
|
|
|
|
|
// 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;
|
|
|
else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
|
|
|
}
|
|
@@ -783,6 +860,11 @@ static void visu_update(void) {
|
|
|
int clear = 0;
|
|
|
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);
|
|
|
+
|
|
|
+ // 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
|
|
|
for (int i = visu.n; --i >= 0;) {
|
|
@@ -840,31 +922,44 @@ static void visu_handler( u8_t *data, int len) {
|
|
|
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 (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.;
|
|
|
+ // 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 {
|
|
|
- // 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
|
|
|
- if (visu.mode == VISU_SPECTRUM) {
|
|
|
+ if ((visu.mode & ~VISU_ESP32) == VISU_SPECTRUM) {
|
|
|
visu.n = bars ? bars : MAX_BARS;
|
|
|
if (visu.spectrum_scale <= 0 || visu.spectrum_scale > 0.5) visu.spectrum_scale = 0.5;
|
|
|
spectrum_limits(0, visu.n, 0);
|