1 #include "engine/enginevumeter.h"
2 
3 #include "control/controlpotmeter.h"
4 #include "control/controlproxy.h"
5 #include "moc_enginevumeter.cpp"
6 #include "util/sample.h"
7 
8 namespace {
9 
10 // Rate at which the vumeter is updated (using a sample rate of 44100 Hz):
11 constexpr int kVuUpdateRate = 30;  // in 1/s, fits to display frame rate
12 constexpr int kPeakDuration = 500; // in ms
13 
14 // Smoothing Factors
15 // Must be from 0-1 the lower the factor, the more smoothing that is applied
16 constexpr CSAMPLE kAttackSmoothing = 1.0f; // .85
17 constexpr CSAMPLE kDecaySmoothing = 0.1f;  //.16//.4
18 
19 } // namespace
20 
EngineVuMeter(const QString & group)21 EngineVuMeter::EngineVuMeter(const QString& group) {
22     // The VUmeter widget is controlled via a controlpotmeter, which means
23     // that it should react on the setValue(int) signal.
24     m_ctrlVuMeter = new ControlPotmeter(ConfigKey(group, "VuMeter"), 0., 1.);
25     // left channel VU meter
26     m_ctrlVuMeterL = new ControlPotmeter(ConfigKey(group, "VuMeterL"), 0., 1.);
27     // right channel VU meter
28     m_ctrlVuMeterR = new ControlPotmeter(ConfigKey(group, "VuMeterR"), 0., 1.);
29 
30     // Used controlpotmeter as the example used it :/ perhaps someone with more
31     // knowledge could use something more suitable...
32     m_ctrlPeakIndicator = new ControlPotmeter(ConfigKey(group, "PeakIndicator"),
33                                               0., 1.);
34     m_ctrlPeakIndicatorL = new ControlPotmeter(ConfigKey(group, "PeakIndicatorL"),
35                                               0., 1.);
36     m_ctrlPeakIndicatorR = new ControlPotmeter(ConfigKey(group, "PeakIndicatorR"),
37                                               0., 1.);
38 
39     m_pSampleRate = new ControlProxy("[Master]", "samplerate", this);
40 
41     // Initialize the calculation:
42     reset();
43 }
44 
~EngineVuMeter()45 EngineVuMeter::~EngineVuMeter()
46 {
47     delete m_ctrlVuMeter;
48     delete m_ctrlVuMeterL;
49     delete m_ctrlVuMeterR;
50     delete m_ctrlPeakIndicator;
51     delete m_ctrlPeakIndicatorL;
52     delete m_ctrlPeakIndicatorR;
53 }
54 
process(CSAMPLE * pIn,const int iBufferSize)55 void EngineVuMeter::process(CSAMPLE* pIn, const int iBufferSize) {
56     CSAMPLE fVolSumL, fVolSumR;
57 
58     int sampleRate = (int)m_pSampleRate->get();
59 
60     SampleUtil::CLIP_STATUS clipped = SampleUtil::sumAbsPerChannel(&fVolSumL,
61             &fVolSumR, pIn, iBufferSize);
62     m_fRMSvolumeSumL += fVolSumL;
63     m_fRMSvolumeSumR += fVolSumR;
64 
65     m_iSamplesCalculated += iBufferSize / 2;
66 
67     // Are we ready to update the VU meter?:
68     if (m_iSamplesCalculated > (sampleRate / kVuUpdateRate)) {
69         doSmooth(m_fRMSvolumeL,
70                 std::log10(SHRT_MAX * m_fRMSvolumeSumL / (m_iSamplesCalculated * 1000) + 1));
71         doSmooth(m_fRMSvolumeR,
72                 std::log10(SHRT_MAX * m_fRMSvolumeSumR / (m_iSamplesCalculated * 1000) + 1));
73 
74         const double epsilon = .0001;
75 
76         // Since VU meters are a rolling sum of audio, the no-op checks in
77         // ControlObject will not prevent us from causing tons of extra
78         // work. Because of this, we use an epsilon here to be gentle on the GUI
79         // and MIDI controllers.
80         if (fabs(m_fRMSvolumeL - m_ctrlVuMeterL->get()) > epsilon) {
81             m_ctrlVuMeterL->set(m_fRMSvolumeL);
82         }
83         if (fabs(m_fRMSvolumeR - m_ctrlVuMeterR->get()) > epsilon) {
84             m_ctrlVuMeterR->set(m_fRMSvolumeR);
85         }
86 
87         double fRMSvolume = (m_fRMSvolumeL + m_fRMSvolumeR) / 2.0;
88         if (fabs(fRMSvolume - m_ctrlVuMeter->get()) > epsilon) {
89             m_ctrlVuMeter->set(fRMSvolume);
90         }
91 
92         // Reset calculation:
93         m_iSamplesCalculated = 0;
94         m_fRMSvolumeSumL = 0;
95         m_fRMSvolumeSumR = 0;
96     }
97 
98     if (clipped & SampleUtil::CLIPPING_LEFT) {
99         m_ctrlPeakIndicatorL->set(1.);
100         m_peakDurationL = kPeakDuration * sampleRate / iBufferSize / 2000;
101     } else if (m_peakDurationL <= 0) {
102         m_ctrlPeakIndicatorL->set(0.);
103     } else {
104         --m_peakDurationL;
105     }
106 
107     if (clipped & SampleUtil::CLIPPING_RIGHT) {
108         m_ctrlPeakIndicatorR->set(1.);
109         m_peakDurationR = kPeakDuration * sampleRate / iBufferSize / 2000;
110     } else if (m_peakDurationR <= 0) {
111         m_ctrlPeakIndicatorR->set(0.);
112     } else {
113         --m_peakDurationR;
114     }
115 
116     m_ctrlPeakIndicator->set(
117             (m_ctrlPeakIndicatorR->toBool() || m_ctrlPeakIndicatorL->toBool())
118                     ? 1.0
119                     : 0.0);
120 }
121 
doSmooth(CSAMPLE & currentVolume,CSAMPLE newVolume)122 void EngineVuMeter::doSmooth(CSAMPLE &currentVolume, CSAMPLE newVolume)
123 {
124     if (currentVolume > newVolume) {
125         currentVolume -= kDecaySmoothing * (currentVolume - newVolume);
126     } else {
127         currentVolume += kAttackSmoothing * (newVolume - currentVolume);
128     }
129     if (currentVolume < 0) {
130         currentVolume=0;
131     }
132     if (currentVolume > 1.0) {
133         currentVolume=1.0;
134     }
135 }
136 
reset()137 void EngineVuMeter::reset() {
138     m_ctrlVuMeter->set(0);
139     m_ctrlVuMeterL->set(0);
140     m_ctrlVuMeterR->set(0);
141     m_ctrlPeakIndicator->set(0);
142     m_ctrlPeakIndicatorL->set(0);
143     m_ctrlPeakIndicatorR->set(0);
144 
145     m_iSamplesCalculated = 0;
146     m_fRMSvolumeL = 0;
147     m_fRMSvolumeSumL = 0;
148     m_fRMSvolumeR = 0;
149     m_fRMSvolumeSumR = 0;
150     m_peakDurationL = 0;
151     m_peakDurationR = 0;
152 }
153