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