aboutsummaryrefslogtreecommitdiffstats
path: root/level.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'level.cpp')
-rw-r--r--level.cpp438
1 files changed, 438 insertions, 0 deletions
diff --git a/level.cpp b/level.cpp
new file mode 100644
index 0000000..e8560d8
--- /dev/null
+++ b/level.cpp
@@ -0,0 +1,438 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+#include <limits.h>
+
+#include <sys/types.h>
+
+#include <qwidget.h>
+#include <qpixmap.h>
+#include <qpainter.h>
+#include <qcolor.h>
+#include <qtimer.h>
+
+#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 */