diff options
author | kraxel <kraxel> | 2004-11-03 12:53:56 +0000 |
---|---|---|
committer | kraxel <kraxel> | 2004-11-03 12:53:56 +0000 |
commit | f172e7016a13f435adfbb93a85d5e22b2237a377 (patch) | |
tree | 98e523f7d77185af2a7092f2421eb35b1a976073 /level.cpp | |
parent | a811c9986cad3e9a5c108fceb760e7e76a7dd65a (diff) | |
download | krecord-f172e7016a13f435adfbb93a85d5e22b2237a377.tar.gz |
- add files.
Diffstat (limited to 'level.cpp')
-rw-r--r-- | level.cpp | 438 |
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 */ |