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 ¤tVolume, 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