1 /*
2  * Copyright (c) 2015-2016 Meltytech, LLC
3  * Author: Brian Matherly <code@brianmatherly.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "audiospectrumscopewidget.h"
20 #include "widgets/audiometerwidget.h"
21 #include <Logger.h>
22 #include <QPainter>
23 #include <QtAlgorithms>
24 #include <QVBoxLayout>
25 #include <MltProfile.h>
26 #include <cmath>
27 
28 static const int WINDOW_SIZE = 8000; // 6 Hz FFT bins at 48kHz
29 
30 struct band
31 {
32     float low;    // Low frequency
33     float center; // Center frequency
34     float high;   // High frequency
35     const char* label;
36 };
37 
38 // Preferred frequencies from ISO R 266-1997 / ANSI S1.6-1984
39 static const band BAND_TAB[] =
40 {
41 //     Low      Preferred  High                Band
42 //     Freq      Center    Freq     Label       Num
43     {     1.12,     1.25,     1.41, "1.25"  }, //  1
44     {     1.41,     1.60,     1.78, "1.6"   }, //  2
45     {     1.78,     2.00,     2.24, "2.0"   }, //  3
46     {     2.24,     2.50,     2.82, "2.5"   }, //  4
47     {     2.82,     3.15,     3.55, "3.15"  }, //  5
48     {     3.55,     4.00,     4.44, "4.0"   }, //  6
49     {     4.44,     5.00,     6.00, "5.0"   }, //  7
50     {     6.00,     6.30,     7.00, "6.3"   }, //  8
51     {     7.00,     8.00,     9.00, "8.0"   }, //  9
52     {     9.00,    10.00,    11.00, "10"    }, // 10
53     {    11.00,    12.50,    14.00, "12.5"  }, // 11
54     {    14.00,    16.00,    18.00, "16"    }, // 12
55     {    18.00,    20.00,    22.00, "20"    }, // 13 - First in audible range
56     {    22.00,    25.00,    28.00, "25"    }, // 14
57     {    28.00,    31.50,    35.00, "31"    }, // 15
58     {    35.00,    40.00,    45.00, "40"    }, // 16
59     {    45.00,    50.00,    56.00, "50"    }, // 17
60     {    56.00,    63.00,    71.00, "63"    }, // 18
61     {    71.00,    80.00,    90.00, "80"    }, // 19
62     {    90.00,   100.00,   112.00, "100"   }, // 20
63     {   112.00,   125.00,   140.00, "125"   }, // 21
64     {   140.00,   160.00,   179.00, "160"   }, // 22
65     {   179.00,   200.00,   224.00, "200"   }, // 23
66     {   224.00,   250.00,   282.00, "250"   }, // 24
67     {   282.00,   315.00,   353.00, "315"   }, // 25
68     {   353.00,   400.00,   484.00, "400"   }, // 26
69     {   484.00,   500.00,   560.00, "500"   }, // 27
70     {   560.00,   630.00,   706.00, "630"   }, // 28
71     {   706.00,   800.00,   897.00, "800"   }, // 29
72     {   897.00,  1000.00,  1121.00, "1k"    }, // 30
73     {  1121.00,  1250.00,  1401.00, "1.3k"  }, // 31
74     {  1401.00,  1600.00,  1794.00, "1.6k"  }, // 32
75     {  1794.00,  2000.00,  2242.00, "2k"    }, // 33
76     {  2242.00,  2500.00,  2803.00, "2.5k"  }, // 34
77     {  2803.00,  3150.00,  3531.00, "3.2k"  }, // 35
78     {  3531.00,  4000.00,  4484.00, "4k"    }, // 36
79     {  4484.00,  5000.00,  5605.00, "5k"    }, // 37
80     {  5605.00,  6300.00,  7062.00, "6.3k"  }, // 38
81     {  7062.00,  8000.00,  8908.00, "8k"    }, // 39
82     {  8908.00, 10000.00, 11210.00, "10k"   }, // 40
83     { 11210.00, 12500.00, 14012.00, "13k"   }, // 41
84     { 14012.00, 16000.00, 17936.00, "16k"   }, // 42
85     { 17936.00, 20000.00, 22421.00, "20k"   }, // 43 - Last in audible range
86 };
87 
88 static const int FIRST_AUDIBLE_BAND_INDEX = 12;
89 static const int LAST_AUDIBLE_BAND_INDEX = 42;
90 static const int AUDIBLE_BAND_COUNT = LAST_AUDIBLE_BAND_INDEX - FIRST_AUDIBLE_BAND_INDEX + 1;
91 
AudioSpectrumScopeWidget()92 AudioSpectrumScopeWidget::AudioSpectrumScopeWidget()
93   : ScopeWidget("AudioSpectrum")
94   , m_audioMeter(0)
95 {
96     LOG_DEBUG() << "begin";
97 
98     // Setup this widget
99     qRegisterMetaType< QVector<double> >("QVector<double>");
100 
101     // Create the FFT filter
102     Mlt::Profile profile;
103     m_filter = new Mlt::Filter(profile, "fft");
104     m_filter->set("window_size", WINDOW_SIZE);
105 
106     // Add the audio signal widget
107     QVBoxLayout *vlayout = new QVBoxLayout(this);
108     vlayout->setContentsMargins(4, 4, 4, 4);
109     m_audioMeter = new AudioMeterWidget(this);
110     m_audioMeter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
111     QVector<int> dbscale;
112     dbscale << -50 << -40 << -35 << -30 << -25 << -20 << -15 << -10 << -5 << 0;
113     m_audioMeter->setDbLabels(dbscale);
114     QStringList freqLabels;
115     for (int i = FIRST_AUDIBLE_BAND_INDEX; i <= LAST_AUDIBLE_BAND_INDEX; i++) {
116         freqLabels << BAND_TAB[i].label;
117     }
118     m_audioMeter->setChannelLabels(freqLabels);
119     m_audioMeter->setChannelLabelUnits("Hz");
120     vlayout->addWidget(m_audioMeter);
121 
122     // Config the size.
123     m_audioMeter->setOrientation(Qt::Vertical);
124     m_audioMeter->setMinimumSize(200, 80);
125     m_audioMeter->setMaximumSize(600, 600);
126     setMinimumSize(204, 84);
127     setMaximumSize(604, 604);
128 
129     LOG_DEBUG() << "end";
130 }
131 
~AudioSpectrumScopeWidget()132 AudioSpectrumScopeWidget::~AudioSpectrumScopeWidget()
133 {
134     delete m_filter;
135 }
136 
processSpectrum()137 void AudioSpectrumScopeWidget::processSpectrum()
138 {
139     QVector<double> bands(AUDIBLE_BAND_COUNT);
140     float* bins = (float*)m_filter->get_data("bins");
141     int bin_count = m_filter->get_int("bin_count");
142     double bin_width = m_filter->get_double("bin_width");
143 
144     int band = 0;
145     bool firstBandFound = false;
146     for (int bin = 0; bin < bin_count; bin++) {
147         // Loop through all the FFT bins and align bin frequencies with
148         // band frequencies.
149         double F = bin_width * (double)bin;
150 
151         if (!firstBandFound) {
152             // Skip bins that come before the first band.
153             if (BAND_TAB[band + FIRST_AUDIBLE_BAND_INDEX].low > F) {
154                 continue;
155             } else {
156                 firstBandFound = true;
157                 bands[band] = bins[bin];
158             }
159         } else if (BAND_TAB[band + FIRST_AUDIBLE_BAND_INDEX].high < F) {
160             // This bin is outside of this band - move to the next band.
161             band++;
162             if ((band + FIRST_AUDIBLE_BAND_INDEX) > LAST_AUDIBLE_BAND_INDEX) {
163                 // Skip bins that come after the last band.
164                 break;
165             }
166             bands[band] = bins[bin];
167         } else if (bands[band] < bins[bin] ) {
168             // Pick the highest bin level within this band to represent the
169             // whole band.
170             bands[band] = bins[bin];
171         }
172     }
173 
174     // At this point, bands contains the magnitude of the signal for each
175     // band. Convert to dB.
176     for (band = 0; band < bands.size(); band++) {
177         double mag = bands[band];
178         double dB = mag > 0.0 ? 20 * log10( mag ) : -1000.0;
179         bands[band] = dB;
180     }
181 
182     // Update the audio signal widget
183     QMetaObject::invokeMethod(m_audioMeter, "showAudio", Qt::QueuedConnection, Q_ARG(const QVector<double>&, bands));
184 }
185 
refreshScope(const QSize &,bool)186 void AudioSpectrumScopeWidget::refreshScope(const QSize& /*size*/, bool /*full*/)
187 {
188     bool refresh = false;
189     SharedFrame sFrame;
190 
191     while (m_queue.count() > 0) {
192         sFrame = m_queue.pop();
193         if (sFrame.is_valid() && sFrame.get_audio_samples() > 0) {
194             mlt_audio_format format = mlt_audio_s16;
195             int channels = sFrame.get_audio_channels();
196             int frequency = sFrame.get_audio_frequency();
197             int samples = sFrame.get_audio_samples();
198             Mlt::Frame mFrame = sFrame.clone(true, false, false);
199             m_filter->process(mFrame);
200             mFrame.get_audio( format, frequency, channels, samples );
201             refresh = true;
202         }
203     }
204 
205     if (refresh) {
206         processSpectrum();
207     }
208 }
209 
getTitle()210 QString AudioSpectrumScopeWidget::getTitle()
211 {
212    return tr("Audio Spectrum");
213 }
214