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"