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