1 /*
2     SPDX-FileCopyrightText: 2003 Max Howell <max.howell@methylblue.com>
3     SPDX-FileCopyrightText: 2009  Martin Sandsmark <sandsmark@samfundet.no>
4 
5     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6 */
7 
8 #include "analyzerBase.h"
9 #include <cmath>        //interpolate()
10 
11 #include <QEvent>     //event()
12 #include <QPainter>
13 
14 #include <phonon/audiodataoutput.h>
15 
16 
17 // INSTRUCTIONS Base2D
18 // 1. do anything that depends on height() in init(), Base2D will call it before you are shown
19 // 2. otherwise you can use the constructor to initialise things
20 // 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it
21 // 4. if you want to manipulate the scope, reimplement transform()
22 // 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included
23 // TODO make an INSTRUCTIONS file
24 //can't mod scope in analyze you have to use transform
25 
26 
Base(QWidget * parent,uint scopeSize)27 Analyzer::Base::Base(QWidget *parent, uint scopeSize)
28     : QWidget(parent)
29     , m_fht(new FHT(scopeSize))
30 {}
31 
transform(QVector<float> & scope)32 void Analyzer::Base::transform(QVector<float> &scope ) //virtual
33 {
34     //this is a standard transformation that should give
35     //an FFT scope that has bands for pretty analyzers
36 
37     //NOTE resizing here is redundant as FHT routines only calculate FHT::size() values
38     //scope.resize( m_fht->size() );
39 
40     float *front = static_cast<float*>( &scope.front() );
41 
42     float* f = new float[ m_fht->size() ];
43     m_fht->copy( &f[0], front );
44     m_fht->logSpectrum( front, &f[0] );
45     m_fht->scale( front, 1.0 / 20 );
46 
47     scope.resize( m_fht->size() / 2 ); //second half of values are rubbish
48     delete [] f;
49 }
50 
drawFrame(const QMap<Phonon::AudioDataOutput::Channel,QVector<qint16>> & thescope)51 void Analyzer::Base::drawFrame(const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &thescope)
52 {
53     if (thescope.isEmpty())
54         return;
55 
56     QVector<float> scope( 512 );
57     int i = 0;
58 
59     for (uint x = 0; (int)x < m_fht->size(); ++x) {
60         if (thescope.size() == 1) { // Mono
61             const qint16 left = thescope[Phonon::AudioDataOutput::LeftChannel].value(x, 0);
62             scope[x] = double(left);
63         } else { // Anything > Mono is treated as Stereo
64             // Use .value as Phonon(GStreamer) sometimes returns too small
65             // samples, in that case we will simply assume the remainder would be
66             // 0.
67             // This in particualr happens when switching from mono files to
68             // stereo and vice versa as the first sample sent after a switch
69             // is of the previous channel allocation but with way to small
70             // data size.
71             const qint16 left = thescope[Phonon::AudioDataOutput::LeftChannel].value(x, 0);
72             const qint16 right = thescope[Phonon::AudioDataOutput::RightChannel].value(x, 0);
73             double value = double(left + right) / (2*(1<<15));
74             scope[x] = value; // Average between the channels
75         }
76         i += 2;
77     }
78 
79     transform(scope);
80     analyze(scope);
81 
82     scope.resize( m_fht->size() );
83 
84     update();
85 }
86 
resizeExponent(int exp)87 int Analyzer::Base::resizeExponent( int exp )
88 {
89     if ( exp < 3 )
90         exp = 3;
91     else if ( exp > 9 )
92         exp = 9;
93 
94     if ( exp != m_fht->sizeExp() ) {
95         delete m_fht;
96         m_fht = new FHT( exp );
97     }
98     return exp;
99 }
100 
resizeForBands(int bands)101 int Analyzer::Base::resizeForBands(int bands)
102 {
103     int exp;
104     if ( bands <= 8 )
105         exp = 4;
106     else if ( bands <= 16 )
107         exp = 5;
108     else if ( bands <= 32 )
109         exp = 6;
110     else if ( bands <= 64 )
111         exp = 7;
112     else if ( bands <= 128 )
113         exp = 8;
114     else
115         exp = 9;
116 
117     resizeExponent(exp);
118     return m_fht->size() / 2;
119 }
120 
paused()121 void Analyzer::Base::paused() //virtual
122 {}
123 
demo()124 void Analyzer::Base::demo() //virtual
125 {
126     static int t = 201; //FIXME make static to namespace perhaps
127     //    qDebug() << Q_FUNC_INFO << t;
128 
129     if( t > 300 ) t = 1; //0 = wasted calculations
130     if( t < 201 )
131     {
132         QVector<float> s( 512 );
133 
134         const double dt = double(t) / 200;
135         for(int i = 0; i < s.size(); ++i)
136             s[i] = dt * (sin( M_PI + (i * M_PI) / s.size() ) + 1.0);
137 
138         analyze(s);
139     }
140     else analyze(QVector<float>( 1, 0));
141 
142     ++t;
143 }
144 
145 
146 
Base2D(QWidget * parent,uint scopeSize)147 Analyzer::Base2D::Base2D(QWidget *parent, uint scopeSize)
148     : Base(parent, scopeSize)
149 {
150     QTimer::singleShot(0, this, &Base2D::init); // needs to know the size
151     timer.setInterval(34);
152     timer.setSingleShot(false);
153     connect(&timer, &QTimer::timeout, this, &Base2D::demo);
154     timer.start();
155 }
156 
resizeEvent(QResizeEvent * e)157 void Analyzer::Base2D::resizeEvent(QResizeEvent *e)
158 {
159     QWidget::resizeEvent(e);
160 
161     m_canvas = QPixmap(size());
162     m_canvas.fill(Qt::transparent);
163 
164     eraseCanvas(); //this is necessary, no idea why. but I trust mxcl.
165 }
166 
paintEvent(QPaintEvent *)167 void Analyzer::Base2D::paintEvent(QPaintEvent*)
168 {
169     if(m_canvas.isNull())
170         return;
171 
172     QPainter painter(this);
173     painter.drawPixmap(rect(), m_canvas);
174 }
175 
interpolate(const QVector<float> & inVec,QVector<float> & outVec)176 void Analyzer::interpolate( const QVector<float> &inVec, QVector<float> &outVec ) //static
177 {
178     double pos = 0.0;
179     const double step = (double)inVec.size() / outVec.size();
180 
181     for (int i = 0; i < outVec.size(); ++i, pos += step)
182     {
183         const double error = pos - std::floor( pos );
184         const unsigned long offset = (unsigned long)pos;
185 
186         long indexLeft = offset + 0;
187 
188         if (indexLeft >= inVec.size())
189             indexLeft = inVec.size() - 1;
190 
191         long indexRight = offset + 1;
192 
193         if (indexRight >= inVec.size())
194             indexRight = inVec.size() - 1;
195 
196         outVec[i] = inVec[indexLeft ] * ( 1.0 - error ) +
197                     inVec[indexRight] * error;
198     }
199 }
200 
initSin(QVector<float> & v,const uint size)201 void Analyzer::initSin( QVector<float> &v, const uint size ) //static
202 {
203     double step = ( M_PI * 2 ) / size;
204     double radian = 0;
205 
206     for ( uint i = 0; i < size; i++ )
207     {
208         v.push_back( sin( radian ) );
209         radian += step;
210     }
211 }
212