File indexing completed on 2024-04-28 04:52:21

0001 /*
0002     SPDX-FileCopyrightText: 2010 Marco Gittler <g.marco@freenet.de>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "audiosignal.h"
0008 
0009 #include <QElapsedTimer>
0010 #include <QPainter>
0011 
0012 #include <cmath>
0013 
0014 AudioSignal::AudioSignal(QWidget *parent)
0015     : AbstractAudioScopeWidget(false, parent)
0016 {
0017     setMinimumHeight(10);
0018     setMinimumWidth(10);
0019     m_dbscale << 0 << -1 << -2 << -3 << -4 << -5 << -6 << -8 << -10 << -20 << -40;
0020     m_menu->removeAction(m_aRealtime);
0021     connect(&m_timer, &QTimer::timeout, this, &AudioSignal::slotNoAudioTimeout);
0022     init();
0023 }
0024 
0025 AudioSignal::~AudioSignal() = default;
0026 
0027 QImage AudioSignal::renderAudioScope(uint, const audioShortVector &audioFrame, const int, const int num_channels, const int samples, const int)
0028 {
0029     QElapsedTimer timer;
0030     timer.start();
0031 
0032     int num_samples = samples > 200 ? 200 : samples;
0033 
0034     QByteArray chanAvg;
0035     for (int i = 0; i < num_channels; ++i) {
0036         long val = 0;
0037         for (int s = 0; s < num_samples; s++) {
0038             val += abs(audioFrame[i + s * num_channels] / 128);
0039         }
0040         chanAvg.append(char(val / num_samples));
0041     }
0042 
0043     if (m_peeks.count() != chanAvg.count()) {
0044         m_peeks = QByteArray(chanAvg.count(), 0);
0045         m_peekage = QByteArray(chanAvg.count(), 0);
0046     }
0047     for (int chan = 0; chan < m_peeks.count(); chan++) {
0048         m_peekage[chan] = char(m_peekage[chan] + 1);
0049         if (m_peeks.at(chan) < chanAvg.at(chan) || m_peekage.at(chan) > 50) {
0050             m_peekage[chan] = 0;
0051             m_peeks[chan] = chanAvg[chan];
0052         }
0053     }
0054 
0055     QImage image(m_scopeRect.size(), QImage::Format_ARGB32);
0056     image.fill(Qt::transparent);
0057     QPainter p(&image);
0058     p.setPen(Qt::white);
0059     p.setRenderHint(QPainter::TextAntialiasing, false);
0060     p.setRenderHint(QPainter::Antialiasing, false);
0061 
0062     int numchan = chanAvg.size();
0063     bool horiz = width() > height();
0064     int dbsize = 20;
0065     if (!horiz) {
0066         // calculate actual width of lowest=longest db scale mark based on drawing font
0067         dbsize = p.fontMetrics().horizontalAdvance(QString::number(m_dbscale.at(m_dbscale.size() - 1)));
0068     }
0069     bool showdb = width() > (dbsize + 40);
0070     // valpixel=1.0 for 127, 1.0+(1/40) for 1 short oversample, 1.0+(2/40) for longer oversample
0071     for (int i = 0; i < numchan; ++i) {
0072         // int maxx= (unsigned char)m_channels[i] * (horiz ? width() : height() ) / 127;
0073         double valpixel = valueToPixel(chanAvg[i] / 127.0);
0074         int maxx = int(height() * valpixel);
0075         int xdelta = height() / 42;
0076         int _y2 = (showdb ? width() - dbsize : width()) / numchan - 1;
0077         int _y1 = (showdb ? width() - dbsize : width()) * i / numchan;
0078         int _x2 = maxx > xdelta ? xdelta - 3 : maxx - 3;
0079         if (horiz) {
0080             dbsize = 9;
0081             showdb = height() > (dbsize);
0082             maxx = int(width() * valpixel);
0083             xdelta = width() / 42;
0084             _y2 = (showdb ? height() - dbsize : height()) / numchan - 1;
0085             _y1 = (showdb ? height() - dbsize : height()) * i / numchan;
0086             _x2 = maxx > xdelta ? xdelta - 1 : maxx - 1;
0087         }
0088 
0089         for (int x = 0; x <= 42; ++x) {
0090             int _x1 = x * xdelta;
0091             QColor sig = Qt::green;
0092             // value of actual painted digit
0093             double ival = double(_x1) / xdelta / 42.;
0094             if (ival > 40.0 / 42.0) {
0095                 sig = Qt::red;
0096             } else if (ival > 37.0 / 42.0) {
0097                 sig = Qt::darkYellow;
0098             } else if (ival > 30.0 / 42.0) {
0099                 sig = Qt::yellow;
0100             }
0101             if (maxx > 0) {
0102                 if (horiz) {
0103                     p.fillRect(_x1, _y1, _x2, _y2, QBrush(sig, Qt::SolidPattern));
0104                 } else {
0105                     p.fillRect(_y1, height() - _x1, _y2, -_x2, QBrush(sig, Qt::SolidPattern));
0106                 }
0107                 maxx -= xdelta;
0108             }
0109         }
0110         int xp = int(valueToPixel(m_peeks.at(i) / 127.)) * (horiz ? width() : height()) - 2;
0111         p.fillRect(horiz ? xp : _y1, horiz ? _y1 : height() - xdelta - xp, horiz ? 3 : _y2, horiz ? _y2 : 3, QBrush(Qt::gray, Qt::SolidPattern));
0112     }
0113     if (showdb) {
0114         // draw db value at related pixel
0115         for (int l : qAsConst(m_dbscale)) {
0116             if (!horiz) {
0117                 double xf = pow(10.0, l / 20.0) * height();
0118                 p.drawText(width() - dbsize, height() - int(xf * 40 / 42 + 20), QString::number(l));
0119             } else {
0120                 double xf = pow(10.0, l / 20.0) * width();
0121                 p.drawText(int(xf * 40 / 42 - 10), height() - 2, QString::number(l));
0122             }
0123         }
0124     }
0125     p.end();
0126     Q_EMIT signalScopeRenderingFinished(uint(timer.elapsed()), 1);
0127     return image;
0128 }
0129 
0130 QRect AudioSignal::scopeRect()
0131 {
0132     return {0, 0, width(), height()};
0133 }
0134 
0135 QImage AudioSignal::renderHUD(uint)
0136 {
0137     return QImage();
0138 }
0139 QImage AudioSignal::renderBackground(uint)
0140 {
0141     return QImage();
0142 }
0143 
0144 void AudioSignal::slotReceiveAudio(audioShortVector audioSamples, int, int num_channels, int samples)
0145 {
0146 
0147     int num_samples = samples > 200 ? 200 : samples;
0148 
0149     QByteArray chanSignal;
0150     int num_oversample = 0;
0151     for (int i = 0; i < num_channels; ++i) {
0152         long val = 0;
0153         double over1 = 0.0;
0154         double over2 = 0.0;
0155         for (int s = 0; s < num_samples; s++) {
0156             int sample = abs(audioSamples[i + s * num_channels] / 128);
0157             val += sample;
0158             if (sample == 128) {
0159                 num_oversample++;
0160             } else {
0161                 num_oversample = 0;
0162             }
0163             // if 3 samples over max => 1 peak over 0 db (0db=40.0)
0164             if (num_oversample > 3) {
0165                 over1 = 41.0 / 42.0 * 127;
0166             }
0167             // 10 samples @max => show max signal
0168             if (num_oversample > 10) {
0169                 over2 = 127;
0170             }
0171         }
0172         // max amplitude = 40/42, 3to10  oversamples=41, more then 10 oversamples=42
0173         if (over2 > 0.0) {
0174             chanSignal.append(char(over2));
0175         } else if (over1 > 0.0) {
0176             chanSignal.append(char(over1));
0177         } else {
0178             chanSignal.append(char(double(val) / num_samples * 40.0 / 42.0));
0179         }
0180     }
0181     showAudio(chanSignal);
0182     m_timer.start(1000);
0183 }
0184 
0185 void AudioSignal::slotNoAudioTimeout()
0186 {
0187     m_peeks.fill(0);
0188     showAudio(QByteArray(2, 0));
0189     m_timer.stop();
0190 }
0191 
0192 void AudioSignal::showAudio(const QByteArray &arr)
0193 {
0194     m_channels = arr;
0195     if (m_peeks.count() != m_channels.count()) {
0196         m_peeks = QByteArray(m_channels.count(), 0);
0197         m_peekage = QByteArray(m_channels.count(), 0);
0198     }
0199     for (int chan = 0; chan < m_peeks.count(); chan++) {
0200         m_peekage[chan] = char(m_peekage[chan] + 1);
0201         if (m_peeks.at(chan) < arr.at(chan) || m_peekage.at(chan) > 50) {
0202             m_peekage[chan] = 0;
0203             m_peeks[chan] = arr[chan];
0204         }
0205     }
0206     update();
0207 }
0208 
0209 double AudioSignal::valueToPixel(double in)
0210 {
0211     // in=0 -> return 0 (null length from max), in=127/127 return 1 (max length )
0212     return 1.0 - log10(in) / log10(1.0 / 127.0);
0213 }