/* * level.cpp. Part of krecord by Gerd Knorr. * * Displays the input level. * * Copyright (C) 1998 Florian Kolbe * * History: * * Jun 04 1998 Florian Kolbe * Created * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "sound.h" #include "level.moc" #ifndef min #define min(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef max #define max(a,b) ((a) > (b) ? (a) : (b)) #endif /* ---------------------------------------------------------------------- */ LevelWindow::LevelWindow(QWidget *parent, char *name):QWidget(parent,name,0) { /* which type of vu-meter ? */ PowervsMax=1; LogvsLinear=1; /* did not get sound-params yet. */ init = FALSE; sdata = NULL; /* no peaks initially. */ peak[0] = 0; peak[1] = 0; clipLeft=false; clipRight=false; /* will use output buffer, so no background necessary. */ setBackgroundMode(NoBackground); orange = QColor("orangered"); /* Initialize output buffer. */ buffer = new QPixmap(size()); /* Initialize timers to reset peaks. */ timer[0] = new QTimer(); timer[1] = new QTimer(); connect(timer[0], SIGNAL(timeout()), this, SLOT(resetPeakLeft())); connect(timer[1], SIGNAL(timeout()), this, SLOT(resetPeakRight())); } /* LevelWindow */ LevelWindow::~LevelWindow(void) { delete buffer; delete timer[0]; delete timer[1]; } void LevelWindow::resizeEvent(QResizeEvent*) { /* Fix size of output buffer. */ delete buffer; buffer = new QPixmap(size()); } void LevelWindow::resetPeakLeft(void) { peak[0] = 0; clipLeft = false; repaint(); } void LevelWindow::resetPeakRight(void) { peak[1] = 0; clipRight = false; repaint(); } void LevelWindow::drawBar(QPainter& painter, int channel, float level, int size, bool drawRed) { int xLevel = (int)(((float)width())*level); /* x-pos of current level */ int x80 = width()*80/100; /* x-pos of 80% level */ int y,x; /* Left/mono top, right bottom. */ if (channel == 0) { y = 0; } else { y = height()/2; } const QColor *colortodraw=&darkGreen; if (drawRed) { colortodraw=&red; } /* Green: 0%-[level|80%] */ painter.fillRect(0, y+1, max(1, min(xLevel, x80)), size-2, *colortodraw); /* Yellow part. */ if (!drawRed) { colortodraw=&darkYellow; } if (level > 0.8) { painter.fillRect(x80, y+1, max(1, xLevel-x80), size-2, *colortodraw); } /* Current peak is either reached again or pushed. */ if (level >= peak[channel]) { peak[channel] = level; timer[channel]->start(1000, TRUE); } /* Draw peak if greater than current level. */ if (peak[channel] >= level) { /* 0- 80: green 80- 98: yellow 99-100: orange */ painter.setPen(green); if (peak[channel] > 0.80) { painter.setPen(yellow); } if (peak[channel] >= 0.99) { painter.setPen(orange); } x = (int)min(width()*peak[channel]-1,width()-1); painter.drawLine(x, y+1, x, y+size-2); } } /* drawBar */ void LevelWindow::paintEvent(QPaintEvent*) { int maxLeft = 0; int maxRight = 0; int i; QPainter painter; int maxAmp; int64_t powerLeft=0; int64_t powerRight=0; float floatPowerLeft=0; float floatPowerRight=0; char buf[32]; #ifndef NO_COMPENSATE_BIAS int64_t bLeft=0; int64_t bRight=0; #endif if ((init == FALSE) || !sdata) return; /* if true then calculate Power else calculate Max */ if (PowervsMax) { /* Calculate power of the signal depending on format. Since the signal may not have an average value of 0 precisely, we shouldn't simply calculate: sum_for_all_samples (pulse_value²) / number_of_samples but this formula assumes that the average is zero, which is not always true (for example, in 8 bits on a Sound Blaster 64, there is always a shift by one unit. We could calculate in two passes, first the average, then the power of the measure minus the average. But we can do this in one pass. Let measure = signal + bias, where measure is the pulse value, signal is what we want, bias is a constant, such that the average of signal is zero. What we want is the value of: power = sum_for_all_samples (signal²) Let's calculate in the same pass: a=sum_for_all_samples (measure²) and b=sum_for_all_samples (measure) Then a and b are equivalent to: a = sum_for_all_samples (measure²) = sum_for_all_samples ((signal + bias)²) = sum_for_all_samples (signal² + bias²) = sum_for_all_samples (signal²) + number_of_samples * bias² and b = sum_for_all_samples (measure) = bias * number_of_samples that is, number_of_samples * bias² = b² / number_of_samples So a = power + b² / number_of_samples And power = a - b² / number_of_samples So we've got the correct power of the signal in one pass. */ #ifndef NO_COMPENSATE_BIAS bLeft=0; bRight=0; #endif if (afmt == FMT_16BIT) { maxAmp = 32768; if (channels == 1) { for (i = 0; i < samples; i++) { /* Since we calculate the square of something that can be as big as +-32767 we assume a width of at least 32 bits for a signed int. Moreover, we add a thousand of these to calculate power, so 32 bits aren't enough. I chose 64 bits unsigned int for precision. We could have switched to float or double instead... */ signed int thispulse=(sdata[i]); /* Note: we calculate max value anyway, to detect clipping */ if (abs(thispulse) > maxLeft) maxLeft = abs(sdata[i]); powerLeft+=(thispulse*thispulse); #ifndef NO_COMPENSATE_BIAS bLeft+=thispulse; #endif } } else if (channels == 2) { for (i = 0; i < samples; i++) { signed int thispulse=(sdata[i*2]); if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse); powerLeft+=(thispulse*thispulse); thispulse=(sdata[i*2+1]); if (abs(thispulse) > maxRight) maxRight = abs(thispulse); powerRight+=(thispulse*thispulse); #ifndef NO_COMPENSATE_BIAS bRight+=thispulse; #endif } } } else { unsigned char* bdata = (unsigned char*)sdata; maxAmp = 128; if (channels == 1) { for (i = 0; i < samples; i++) { signed int thispulse=(bdata[i]-128); if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse); powerLeft+=(thispulse*thispulse); #ifndef NO_COMPENSATE_BIAS bLeft+=thispulse; #endif } } else if (channels == 2) { for (i = 0; i < samples; i++) { signed int thispulse=(bdata[i*2]-128); if (abs(thispulse) > maxLeft) maxLeft = abs(thispulse); powerLeft+=(thispulse*thispulse); thispulse=(bdata[i*2+1]-128); if (abs(thispulse) > maxRight) maxRight = abs(thispulse); powerRight+=(thispulse*thispulse); #ifndef NO_COMPENSATE_BIAS bRight+=thispulse; #endif } } } /* Ok for raw power. Now normalize it. */ #ifndef NO_COMPENSATE_BIAS powerLeft-=bLeft*bLeft/samples; powerRight-=bRight*bRight/samples; //fprintf(stderr, "bLeft: %lld\tbiais: %f\t", bLeft, ((float)bLeft*(float)maxAmp/(float)samples)); #endif floatPowerLeft=((float)powerLeft)/((float)maxAmp)/((float)maxAmp)/((float)samples); floatPowerRight=((float)powerLeft)/((float)maxAmp)/((float)maxAmp)/((float)samples); //fprintf(stderr, "brute: %lld,\tnormalisée: %f\t", powerLeft, floatPowerLeft); } else { /* Find max amplitude depending on format. */ if (afmt == FMT_16BIT) { maxAmp = 32768; if (channels == 1) { for (i = 0; i < samples; i++) if (abs(sdata[i]) > maxLeft) maxLeft = abs(sdata[i]); } else if (channels == 2) { for (i = 0; i < samples; i++) { if (abs(sdata[i*2]) > maxLeft) maxLeft = abs(sdata[i*2]); if (abs(sdata[i*2+1]) > maxRight) maxRight = abs(sdata[i*2+1]); } } } else { unsigned char* bdata = (unsigned char*)sdata; maxAmp = 128; if (channels == 1) { for (i = 0; i < samples; i++) if (abs(bdata[i]-128) > maxLeft) maxLeft = abs(bdata[i]-128); } else if (channels == 2) { for (i = 0; i < samples; i++) { if (abs(bdata[i*2]-128) > maxLeft) maxLeft = abs(bdata[i*2]-128); if (abs(bdata[i*2+1]-128) > maxRight) maxRight = abs(bdata[i*2+1]-128); } } } } if (maxLeft>=(maxAmp-1)) clipLeft=true; if (maxRight>=(maxAmp-1)) clipRight=true; /* Draw bars. */ buffer->fill(black /* backgroundColor() */); painter.begin(buffer); if (PowervsMax) { /* Power */ if (LogvsLinear) { /* Log */ /* we want leftmost to be 100dB (though signal-to-noise ratio can't be more than 96.33dB in power) and rightmost to be 0dB (maximum power) */ float dBvalue=1+0.1*log10(floatPowerLeft); /* 10/100 = 0.1 */ //fprintf(stderr, "dB: %f\r", -10*log10(floatPowerLeft)); sprintf(buf, "%.2f dB", -10*log10(floatPowerLeft)); emit setvalue(buf); drawBar(painter, 0, dBvalue, height()/channels, clipLeft); if (channels == 2) { dBvalue=1+0.1*log10(floatPowerRight); drawBar(painter, 1, dBvalue, height()/channels, clipRight); } } else { /* Linear */ drawBar(painter, 0, floatPowerLeft, height()/channels, clipLeft); if (channels == 2) { drawBar(painter, 1, floatPowerRight, height()/channels, clipRight); } } } else { /* Max */ if (LogvsLinear) { /* Log */ /* we want leftmost to be 50dB (though signal-to-noise ratio can't be more than 48.16dB in amplitude) and rightmost to be 0dB (clipping trheshold reached!) */ float logvalue=1+0.2*log10((float)maxLeft/(float)maxAmp); /* 10/50 = 0.2 */ drawBar(painter, 0, logvalue, height()/channels, clipLeft); if (channels == 2) { logvalue=1+0.2*log10((float)maxRight/(float)maxAmp); drawBar(painter, 1, logvalue, height()/channels, clipRight); } } else { /* Linear */ float value=((float)maxLeft/(float)maxAmp); drawBar(painter, 0, value, height()/channels, clipLeft); if (channels == 2) { value=((float)maxRight/(float)maxAmp); drawBar(painter, 1, value, height()/channels, clipRight); } } }; painter.end(); bitBlt(this, 0, 0, buffer); } /* paintEvent */ void LevelWindow::new_params(struct SOUNDPARAMS *p) { afmt = p->format; channels = p->channels; samples = p->blocksize/channels/(afmt == FMT_16BIT ? 2 : 1); init = TRUE; } /* new_params */ void LevelWindow::new_data(void *data) { sdata = (short int*)data; repaint(); } /* new_data */