File indexing completed on 2024-05-12 04:50:07

0001 /*
0002     SPDX-FileCopyrightText: 2003 Max Howell <max.howell@methylblue.com>
0003     SPDX-FileCopyrightText: 2009  Martin Sandsmark <sandsmark@samfundet.no>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "analyzerBase.h"
0009 #include <cmath> //interpolate()
0010 
0011 #include <QEvent> //event()
0012 #include <QPainter>
0013 
0014 // INSTRUCTIONS Base2D
0015 // 1. do anything that depends on height() in init(), Base2D will call it before you are shown
0016 // 2. otherwise you can use the constructor to initialise things
0017 // 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it
0018 // 4. if you want to manipulate the scope, reimplement transform()
0019 // 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included
0020 // TODO make an INSTRUCTIONS file
0021 // can't mod scope in analyze you have to use transform
0022 
0023 Analyzer::Base::Base(QWidget *parent, uint scopeSize)
0024     : QWidget(parent)
0025     , m_fht(new FHT(scopeSize))
0026 {
0027 }
0028 
0029 void Analyzer::Base::transform(QVector<float> &scope) // virtual
0030 {
0031     // this is a standard transformation that should give
0032     // an FFT scope that has bands for pretty analyzers
0033 
0034     // NOTE resizing here is redundant as FHT routines only calculate FHT::size() values
0035     // scope.resize( m_fht->size() );
0036 
0037     float *front = static_cast<float *>(&scope.front());
0038 
0039     float *f = new float[m_fht->size()];
0040     m_fht->copy(&f[0], front);
0041     m_fht->logSpectrum(front, &f[0]);
0042     m_fht->scale(front, 1.0 / 20);
0043 
0044     scope.resize(m_fht->size() / 2); // second half of values are rubbish
0045     delete[] f;
0046 }
0047 
0048 void Analyzer::Base::drawFrame(const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16>> &thescope)
0049 {
0050     if (thescope.isEmpty())
0051         return;
0052 
0053     QVector<float> scope(512);
0054     int i = 0;
0055 
0056     for (uint x = 0; (int)x < m_fht->size(); ++x) {
0057         if (thescope.size() == 1) { // Mono
0058             const qint16 left = thescope[Phonon::AudioDataOutput::LeftChannel].value(x, 0);
0059             scope[x] = double(left);
0060         } else { // Anything > Mono is treated as Stereo
0061             // Use .value as Phonon(GStreamer) sometimes returns too small
0062             // samples, in that case we will simply assume the remainder would be
0063             // 0.
0064             // This in particualr happens when switching from mono files to
0065             // stereo and vice versa as the first sample sent after a switch
0066             // is of the previous channel allocation but with way to small
0067             // data size.
0068             const qint16 left = thescope[Phonon::AudioDataOutput::LeftChannel].value(x, 0);
0069             const qint16 right = thescope[Phonon::AudioDataOutput::RightChannel].value(x, 0);
0070             double value = double(left + right) / (2 * (1 << 15));
0071             scope[x] = value; // Average between the channels
0072         }
0073         i += 2;
0074     }
0075 
0076     transform(scope);
0077     analyze(scope);
0078 
0079     scope.resize(m_fht->size());
0080 
0081     update();
0082 }
0083 
0084 int Analyzer::Base::resizeExponent(int exp)
0085 {
0086     if (exp < 3)
0087         exp = 3;
0088     else if (exp > 9)
0089         exp = 9;
0090 
0091     if (exp != m_fht->sizeExp()) {
0092         delete m_fht;
0093         m_fht = new FHT(exp);
0094     }
0095     return exp;
0096 }
0097 
0098 int Analyzer::Base::resizeForBands(int bands)
0099 {
0100     int exp;
0101     if (bands <= 8)
0102         exp = 4;
0103     else if (bands <= 16)
0104         exp = 5;
0105     else if (bands <= 32)
0106         exp = 6;
0107     else if (bands <= 64)
0108         exp = 7;
0109     else if (bands <= 128)
0110         exp = 8;
0111     else
0112         exp = 9;
0113 
0114     resizeExponent(exp);
0115     return m_fht->size() / 2;
0116 }
0117 
0118 void Analyzer::Base::paused() // virtual
0119 {
0120 }
0121 
0122 void Analyzer::Base::demo() // virtual
0123 {
0124     static int t = 201; // FIXME make static to namespace perhaps
0125     //    qDebug() << Q_FUNC_INFO << t;
0126 
0127     if (t > 300)
0128         t = 1; // 0 = wasted calculations
0129     if (t < 201) {
0130         QVector<float> s(512);
0131 
0132         const double dt = double(t) / 200;
0133         for (int i = 0; i < s.size(); ++i)
0134             s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0);
0135 
0136         analyze(s);
0137     } else
0138         analyze(QVector<float>(1, 0));
0139 
0140     ++t;
0141 }
0142 
0143 Analyzer::Base2D::Base2D(QWidget *parent, uint scopeSize)
0144     : Base(parent, scopeSize)
0145 {
0146     QTimer::singleShot(0, this, &Base2D::init); // needs to know the size
0147     timer.setInterval(34);
0148     timer.setSingleShot(false);
0149     connect(&timer, &QTimer::timeout, this, &Base2D::demo);
0150     timer.start();
0151 }
0152 
0153 void Analyzer::Base2D::resizeEvent(QResizeEvent *e)
0154 {
0155     QWidget::resizeEvent(e);
0156 
0157     m_canvas = QPixmap(size());
0158     m_canvas.fill(Qt::transparent);
0159 
0160     eraseCanvas(); // this is necessary, no idea why. but I trust mxcl.
0161 }
0162 
0163 void Analyzer::Base2D::paintEvent(QPaintEvent *)
0164 {
0165     if (m_canvas.isNull())
0166         return;
0167 
0168     QPainter painter(this);
0169     painter.drawPixmap(rect(), m_canvas);
0170 }
0171 
0172 void Analyzer::interpolate(const QVector<float> &inVec, QVector<float> &outVec) // static
0173 {
0174     double pos = 0.0;
0175     const double step = (double)inVec.size() / outVec.size();
0176 
0177     for (int i = 0; i < outVec.size(); ++i, pos += step) {
0178         const double error = pos - std::floor(pos);
0179         const unsigned long offset = (unsigned long)pos;
0180 
0181         long indexLeft = offset + 0;
0182 
0183         if (indexLeft >= inVec.size())
0184             indexLeft = inVec.size() - 1;
0185 
0186         long indexRight = offset + 1;
0187 
0188         if (indexRight >= inVec.size())
0189             indexRight = inVec.size() - 1;
0190 
0191         outVec[i] = inVec[indexLeft] * (1.0 - error) + inVec[indexRight] * error;
0192     }
0193 }
0194 
0195 void Analyzer::initSin(QVector<float> &v, const uint size) // static
0196 {
0197     double step = (M_PI * 2) / size;
0198     double radian = 0;
0199 
0200     for (uint i = 0; i < size; i++) {
0201         v.push_back(sin(radian));
0202         radian += step;
0203     }
0204 }
0205 
0206 #include "moc_analyzerBase.cpp"