diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 01fd923c..f599c511 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -79,6 +79,24 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_ return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } +// more accurate integer version of map() - based on map3() proposed in https://forum.arduino.cc/t/how-map-loses-precision-and-how-to-fix-it/371026/3 +// rounding instead of truncation, better handling of inverted ranges +static long map2(long x, long in_min, long in_max, long out_min, long out_max) +{ + long out_range = out_max - out_min; + if (out_range > 0) out_range ++; + else if (out_range < 0) out_range --; + else return out_min; // output range is 0 + + long in_range = in_max - in_min; + if (in_range > 0) in_range++; + else if (in_range < 0) in_range --; + else return out_min; // input range is 0 - Result is actually infinity but long has no such thing. The least negative long is another choice. + + return ((x - in_min) * out_range) / in_range + out_min; +} + + // effect functions /* @@ -1986,7 +2004,7 @@ uint16_t mode_partyjerk() { if (SEGENV.aux1 > 254) { SEGENV.aux1 = 0; } - if (SEGENV.aux0 > map(SEGMENT.custom1, 0, 255, 0, 14)) { + if (SEGENV.aux0 > map2(SEGMENT.custom1, 0, 255, 0, 14)) { SEGENV.aux0 = 0; SEGENV.aux1++; } @@ -1995,7 +2013,7 @@ uint16_t mode_partyjerk() { uint16_t counter = 0; if (volumeSmth * 2 > (255 - SEGMENT.intensity)) { - speed = SEGMENT.speed * map(SEGMENT.custom2, 0, 255, 0, 100); + speed = SEGMENT.speed * map2(SEGMENT.custom2, 0, 255, 0, 100); } else { speed = SEGMENT.speed; }; @@ -4768,7 +4786,7 @@ uint16_t mode_aurora(void) { if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) { //Intensity slider changed or first call - SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); + SEGENV.aux1 = map2(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); SEGENV.aux0 = SEGMENT.intensity; if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 16 segment ESP8266 @@ -5187,9 +5205,9 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: bool allColors = SEGMENT.check1; bool overlayBG = SEGMENT.check2; bool wrap = SEGMENT.check3; - byte blur = map(SEGMENT.custom1, 0, 255, 255, 0); + byte blur = map2(SEGMENT.custom1, 0, 255, 255, 0); bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode - byte bgBlur = map(SEGMENT.custom1 - 220, 0, 35, 255, 128); + byte bgBlur = map2(SEGMENT.custom1 - 220, 0, 35, 255, 128); uint32_t bgColor = SEGCOLOR(1); uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); uint16_t cIndex; @@ -5748,7 +5766,7 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } float adjustHeight = mapf(rows, 8, maxRows, 28, minScale); // maybe use mapf() ??? // WLEDMM yes! - uint16_t adjScale = map(cols, 8, 64, 310, 63); + uint16_t adjScale = map2(cols, 8, 64, 310, 63); adjustHeight = max(min(adjustHeight, 28.0f), minScale); // WLEDMM bugfix for larger fixtures adjScale = max(min(adjScale, uint16_t(310)), uint16_t(63)); // WLEDMM @@ -5767,8 +5785,8 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } } */ - uint16_t _scale = map(SEGMENT.intensity, 0, 255, 30, adjScale); - byte _speed = map(SEGMENT.speed, 0, 255, 128, 16); + uint16_t _scale = map2(SEGMENT.intensity, 0, 255, 30, adjScale); + byte _speed = map2(SEGMENT.speed, 0, 255, 128, 16); //WLEDMM add SuperSync control uint16_t xStart, xEnd, yStart, yEnd; @@ -6439,6 +6457,10 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; volumeSmth = *(float*) um_data->u_data[0]; volumeRaw = *(int16_t*) um_data->u_data[1]; fftResult = (uint8_t*) um_data->u_data[2]; +or + uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; + if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM to buffer curent values + samplePeak = *(uint8_t*) um_data->u_data[3]; FFT_MajorPeak = *(float*) um_data->u_data[4]; my_magnitude = *(float*) um_data->u_data[5]; @@ -6456,6 +6478,8 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; // a few constants needed for AudioReactive effects +#define NUM_GEQ_CHANNELS 16 // number of audioreactive frequency channels. + // for 22Khz sampling #define MIN_FREQUENCY 80 // 80 HZ - due to lower resolution #define MIN_FREQ_LOG10 1.90309f // log10(MIN_FREQUENCY) @@ -7018,7 +7042,7 @@ uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} // WLEDMM use lossless getPixelColor() //uint8_t fadeRate = map(SEGMENT.speed,0,255,224,255); - uint8_t fadeRate = map(SEGMENT.speed,0,255,200,254); + uint8_t fadeRate = map2(SEGMENT.speed,0,255,200,254); SEGMENT.fade_out(fadeRate); float tmpSound2 = volumeRaw * 2.0 * (float)SEGMENT.intensity / 255.0; @@ -7128,7 +7152,7 @@ static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. uint16_t size = 0; - uint8_t fadeVal = map(SEGMENT.speed,0,255, 224, 254); + uint8_t fadeVal = map2(SEGMENT.speed,0,255, 224, 254); uint16_t pos = random16(SEGLEN); // Set a random starting position. um_data_t *um_data; @@ -7174,7 +7198,7 @@ static const char _data_FX_MODE_PUDDLEPEAK[] PROGMEM = "Puddlepeak@Fade rate,Pud ////////////////////// uint16_t mode_puddles(void) { // Puddles. By Andrew Tuline. uint16_t size = 0; - uint8_t fadeVal = map(SEGMENT.speed, 0, 255, 224, 254); + uint8_t fadeVal = map2(SEGMENT.speed, 0, 255, 224, 254); uint16_t pos = random16(SEGLEN); // Set a random starting position. if (SEGENV.call == 0) { @@ -7380,7 +7404,7 @@ uint16_t mode_DJLight(void) { // Written by Stefan Petrick, Ad //if (color.getLuma() > 12) color.maximizeBrightness(); // for testing //SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4))); // 0.13.x fade -> 180hz-260hz - uint8_t fadeVal = map(fftResult[3], 0, 255, 255, 4); // 0.14.x fade -> 216hz-301hz + uint8_t fadeVal = map2(fftResult[3], 0, 255, 255, 4); // 0.14.x fade -> 216hz-301hz if (SEGENV.check1) fadeVal = constrain(fadeVal, 0, 176); // "candy factory" mode - avoid complete fade-out SEGMENT.setPixelColor(mid, color.fadeToBlackBy(fadeVal)); @@ -7684,7 +7708,8 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; + if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values if (SEGENV.call == 0) { SEGENV.setUpLeds(); // WLEDMM use lossless getPixelColor() @@ -7694,11 +7719,11 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli int fadeoutDelay = (256 - SEGMENT.speed) / 96; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(4+ SEGMENT.speed/4); - uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. + uint8_t numBins = map2(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. for (int i=0; i 1)) ? map(band, 0, NUM_BANDS - 1, 0, 15):band; // always use full range. comment out this line to get the previous behaviour. + uint8_t frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map2(band, 0, NUM_BANDS - 1, 0, 15):band; // always use full range. comment out this line to get the previous behaviour. // frBand = constrain(frBand, 0, 15); //WLEDMM can never be out of bounds (I think...) uint16_t colorIndex = frBand * 17; //WLEDMM 0.255 uint16_t bandHeight = fftResult[frBand]; // WLEDMM we use the original ffResult, to preserve accuracy @@ -7872,7 +7899,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. // get height of next (right side) bar uint8_t nextband = (remaining < 1)? band +1: band; nextband = constrain(nextband, 0, 15); // just to be sure - frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map(nextband, 0, NUM_BANDS - 1, 0, 15):nextband; // always use full range. comment out this line to get the previous behaviour. + frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map2(nextband, 0, NUM_BANDS - 1, 0, 15):nextband; // always use full range. comment out this line to get the previous behaviour. uint16_t nextBandHeight = fftResult[frBand]; // smooth Band height bandHeight = (7*bandHeight + 3*lastBandHeight + 3*nextBandHeight) / 12; // yeees, its 12 not 13 (10% amplification) @@ -7920,7 +7947,7 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - int NUMB_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); + int NUMB_BANDS = map2(SEGMENT.custom1, 0, 255, 1, 16); int barWidth = (cols / NUMB_BANDS); int bandInc = 1; if (barWidth == 0) { @@ -7934,7 +7961,8 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; + if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -7949,7 +7977,7 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil int b = 0; for (int band = 0; band < NUMB_BANDS; band += bandInc, b++) { int hue = fftResult[band % 16]; - int v = map(fftResult[band % 16], 0, 255, 10, 255); + int v = map2(fftResult[band % 16], 0, 255, 10, 255); for (int w = 0; w < barWidth; w++) { int xpos = (barWidth * b) + w; SEGMENT.setPixelColorXY(xpos, 0, CHSV(hue, 255, v)); @@ -8021,11 +8049,12 @@ uint16_t mode_2DAkemi(void) { const float lightFactor = 0.15f; const float normalFactor = 0.4f; - um_data_t *um_data; + um_data_t *um_data = nullptr; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); } - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; + if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values float base = fftResult[0]/255.0f; //draw and color Akemi @@ -8047,7 +8076,7 @@ uint16_t mode_2DAkemi(void) { default: color = BLACK; break; } - if (SEGMENT.intensity > 128 && fftResult && fftResult[0] > 128) { //dance if base is high + if (SEGMENT.intensity > 128 && um_data && fftResult[0] > 128) { //dance if base is high SEGMENT.setPixelColorXY(x, 0, BLACK); SEGMENT.setPixelColorXY(x, y+1, color); } else @@ -8055,11 +8084,11 @@ uint16_t mode_2DAkemi(void) { } //add geq left and right - if (um_data && fftResult) { + if (um_data) { for (int x=0; x < cols/8; x++) { uint16_t band = x * cols/8; band = constrain(band, 0, 15); - uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); + uint16_t barHeight = map2(fftResult[band], 0, 255, 0, 17*rows/32); CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); for (int y=0; y < barHeight; y++) {