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 }