• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mcallegari / qlcplus / 22499051898

27 Feb 2026 06:38PM UTC coverage: 33.971%. Remained the same
22499051898

push

github

mcallegari
engine: switch to logarithmic audio spectrum bands

Reported: https://www.qlcplus.org/forum/viewtopic.php?t=19189

0 of 15 new or added lines in 1 file covered. (0.0%)

70 existing lines in 1 file now uncovered.

17651 of 51959 relevant lines covered (33.97%)

19778.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/ui/src/virtualconsole/vcaudiotriggersproperties.cpp
1
/*
2
  Q Light Controller Plus
3
  audiotriggersconfiguration.cpp
4

5
  Copyright (c) Massimo Callegari
6

7
  Licensed under the Apache License, Version 2.0 (the "License");
8
  you may not use this file except in compliance with the License.
9
  You may obtain a copy of the License at
10

11
      http://www.apache.org/licenses/LICENSE-2.0.txt
12

13
  Unless required by applicable law or agreed to in writing, software
14
  distributed under the License is distributed on an "AS IS" BASIS,
15
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
  See the License for the specific language governing permissions and
17
  limitations under the License.
18
*/
19

20
#include <QTreeWidgetItem>
21
#include <QToolButton>
22
#include <QComboBox>
23
#include <QSpinBox>
24
#include <qmath.h>
25

26
#include "vcaudiotriggersproperties.h"
27
#include "inputselectionwidget.h"
28
#include "channelsselection.h"
29
#include "functionselection.h"
30
#include "vcwidgetselection.h"
31
#include "vcaudiotriggers.h"
32
#include "audiocapture.h"
33
#include "qlcmacros.h"
34
#include "audiobar.h"
35

36
#define KColumnName             0
37
#define KColumnType             1
38
#define KColumnAssign           2
39
#define KColumnInfo             3
40
#define KColumnMinThreshold     4
41
#define KColumnMaxThreshold     5
42
#define KColumnDivisor          6
43

UNCOV
44
AudioTriggersConfiguration::AudioTriggersConfiguration(VCAudioTriggers *triggers, Doc *doc,
×
UNCOV
45
                                                       int bandsNumber, int maxFrequency)
×
46
    : QDialog(triggers)
47
    , m_doc(doc)
×
UNCOV
48
    , m_maxFrequency(maxFrequency)
×
49
{
50
    setupUi(this);
×
51

52
    m_triggers = triggers;
×
53

54
    m_nameEdit->setText(m_triggers->caption());
×
55

56
    m_barsNumSpin->setFixedWidth(70);
×
UNCOV
57
    m_barsNumSpin->setFixedHeight(30);
×
58
    m_barsNumSpin->setValue(bandsNumber);
×
59

60
    connect(m_barsNumSpin, SIGNAL(valueChanged(int)),
×
61
            this, SLOT(updateTree()));
62

63
    /* External input */
UNCOV
64
    m_inputSelWidget = new InputSelectionWidget(m_doc, this);
×
UNCOV
65
    m_inputSelWidget->setCustomFeedbackVisibility(true);
×
66
    m_inputSelWidget->setKeySequence(m_triggers->keySequence());
×
67
    m_inputSelWidget->setInputSource(m_triggers->inputSource());
×
68
    m_inputSelWidget->setWidgetPage(m_triggers->page());
×
69
    m_inputSelWidget->show();
×
70
    m_extControlLayout->addWidget(m_inputSelWidget);
×
71

72
    m_tree->setAlternatingRowColors(true);
×
UNCOV
73
    m_tree->setRootIsDecorated(false);
×
74
    m_tree->setSelectionMode(QAbstractItemView::NoSelection);
×
75
    m_tree->setAllColumnsShowFocus(true);
×
76

77
    updateTree();
×
UNCOV
78
}
×
79

80
AudioTriggersConfiguration::~AudioTriggersConfiguration()
×
81
{
82
}
×
83

84
void AudioTriggersConfiguration::accept()
×
85
{
86
    m_triggers->setCaption(m_nameEdit->text());
×
UNCOV
87
    m_triggers->setKeySequence(m_inputSelWidget->keySequence());
×
88
    m_triggers->setInputSource(m_inputSelWidget->inputSource());
×
89

90
    /* Close dialog */
UNCOV
91
    QDialog::accept();
×
UNCOV
92
}
×
93

94
void AudioTriggersConfiguration::updateTreeItem(QTreeWidgetItem *item, int idx)
×
95
{
96
    if (item == NULL)
×
UNCOV
97
        return;
×
98

99
    AudioBar *bar = m_triggers->getSpectrumBar(idx);
×
UNCOV
100
    bar->setName(item->text(KColumnName));
×
101

102
    bar->debugInfo();
×
UNCOV
103
    QComboBox *currCombo = (QComboBox *)m_tree->itemWidget(item, KColumnType);
×
104
    if (currCombo != NULL)
×
105
    {
106
        disconnect(currCombo, SIGNAL(currentIndexChanged(int)),
×
107
                   this, SLOT(slotTypeComboChanged(int)));
108
        m_tree->removeItemWidget(item, KColumnType);
×
109
    }
110
    m_tree->removeItemWidget(item, KColumnAssign);
×
UNCOV
111
    m_tree->removeItemWidget(item, KColumnMinThreshold);
×
112
    m_tree->removeItemWidget(item, KColumnMaxThreshold);
×
113
    m_tree->removeItemWidget(item, KColumnDivisor);
×
114

115
    QComboBox *combo = new QComboBox();
×
UNCOV
116
    combo->addItem(QIcon(":/uncheck.png"), tr("None"), idx);
×
117
    combo->addItem(QIcon(":/intensity.png"), tr("DMX"), idx);
×
118
    combo->addItem(QIcon(":/function.png"), tr("Function"), idx);
×
119
    combo->addItem(QIcon(":/virtualconsole.png"), tr("VC Widget"), idx);
×
120
    combo->setCurrentIndex(bar->m_type);
×
121
    m_tree->setItemWidget(item, KColumnType, combo);
×
122
    connect(combo, SIGNAL(currentIndexChanged(int)),
×
123
            this, SLOT(slotTypeComboChanged(int)));
124

UNCOV
125
    if (bar->m_type == AudioBar::DMXBar)
×
126
    {
127
        QToolButton *btn = new QToolButton();
×
UNCOV
128
        btn->setIcon(QIcon(":/attach.png"));
×
129
        btn->setProperty("index", idx);
×
130
        m_tree->setItemWidget(item, KColumnAssign, btn);
×
131
        connect(btn, SIGNAL(clicked()), this, SLOT(slotDmxSelectionClicked()));
×
132
        item->setText(KColumnInfo, tr("%1 channels").arg(bar->m_dmxChannels.count()));
×
133
    }
134
    else if (bar->m_type == AudioBar::FunctionBar)
×
135
    {
136
        QToolButton *btn = new QToolButton();
×
UNCOV
137
        btn->setIcon(QIcon(":/attach.png"));
×
138
        btn->setProperty("index", idx);
×
139
        m_tree->setItemWidget(item, KColumnAssign, btn);
×
140
        connect(btn, SIGNAL(clicked()), this, SLOT(slotFunctionSelectionClicked()));
×
141
        if (bar->m_function != NULL)
×
142
        {
143
            item->setText(KColumnInfo, bar->m_function->name());
×
UNCOV
144
            item->setIcon(KColumnInfo, bar->m_function->getIcon());
×
145
        }
146
        else
147
        {
UNCOV
148
            item->setText(KColumnInfo, tr("No function"));
×
UNCOV
149
            item->setIcon(KColumnInfo, QIcon());
×
150
        }
151
    }
UNCOV
152
    else if (bar->m_type == AudioBar::VCWidgetBar)
×
153
    {
154
        QToolButton *btn = new QToolButton();
×
UNCOV
155
        btn->setIcon(QIcon(":/attach.png"));
×
156
        btn->setProperty("index", idx);
×
157
        m_tree->setItemWidget(item, KColumnAssign, btn);
×
158
        connect(btn, SIGNAL(clicked()), this, SLOT(slotWidgetSelectionClicked()));
×
159
        if (bar->widget() != NULL)
×
160
        {
161
            item->setText(KColumnInfo, bar->widget()->caption());
×
UNCOV
162
            item->setIcon(KColumnInfo, VCWidget::typeToIcon(bar->widget()->type()));
×
163
        }
164
        else
165
        {
UNCOV
166
            item->setText(KColumnInfo, tr("No widget"));
×
UNCOV
167
            item->setIcon(KColumnInfo, QIcon());
×
168
        }
169
    }
170
    else
171
    {
UNCOV
172
        item->setText(KColumnInfo, tr("Not assigned"));
×
UNCOV
173
        item->setIcon(KColumnInfo, QIcon());
×
174
    }
175

UNCOV
176
    if (bar->m_type == AudioBar::FunctionBar
×
UNCOV
177
        || (bar->m_type == AudioBar::VCWidgetBar && ((bar->widget() == NULL) || bar->widget()->type() != VCWidget::SliderWidget)))
×
178
    {
179
        QSpinBox *minspin = new QSpinBox();
×
UNCOV
180
        minspin->setMinimum(5);
×
181
        minspin->setMaximum(95);
×
182
        minspin->setSingleStep(1);
×
183
        minspin->setSuffix("%");
×
184
        minspin->setValue(SCALE(float(bar->m_minThreshold), 0.0, 255.0, 0.0, 100.0));
×
185
        minspin->setProperty("index", idx);
×
186
        connect(minspin, SIGNAL(valueChanged(int)), this, SLOT(slotMinThresholdChanged(int)));
×
187
        m_tree->setItemWidget(item, KColumnMinThreshold, minspin);
×
188

189
        QSpinBox *maxspin = new QSpinBox();
×
UNCOV
190
        maxspin->setMinimum(5);
×
191
        maxspin->setMaximum(95);
×
192
        maxspin->setSingleStep(1);
×
193
        maxspin->setSuffix("%");
×
194
        maxspin->setValue(SCALE(float(bar->m_maxThreshold), 0.0, 255.0, 0.0, 100.0));
×
195
        maxspin->setProperty("index", idx);
×
196
        connect(maxspin, SIGNAL(valueChanged(int)), this, SLOT(slotMaxThresholdChanged(int)));
×
197
        m_tree->setItemWidget(item, KColumnMaxThreshold, maxspin);
×
198
    }
199

UNCOV
200
    if (bar->m_type == AudioBar::VCWidgetBar
×
UNCOV
201
        && bar->widget() != NULL
×
202
        && (bar->widget()->type() == VCWidget::SpeedDialWidget || bar->widget()->type() == VCWidget::CueListWidget))
×
203
    {
204
        QSpinBox *divisor = new QSpinBox();
×
UNCOV
205
        divisor->setMinimum(1);
×
206
        divisor->setMaximum(64);
×
207
        divisor->setSingleStep(1);
×
208
        divisor->setValue(bar->m_divisor);
×
209
        divisor->setProperty("index", idx);
×
210
        connect(divisor, SIGNAL(valueChanged(int)), this, SLOT(slotDivisorChanged(int)));
×
211
        m_tree->setItemWidget(item, KColumnDivisor, divisor);
×
212
    }
213
}
214

UNCOV
215
void AudioTriggersConfiguration::updateTree()
×
216
{
217
    if (m_barsNumSpin->value() < m_barsNumSpin->minimum() ||
×
UNCOV
218
        m_barsNumSpin->value() > m_barsNumSpin->maximum())
×
219
            return;
×
220

221
    m_tree->clear();
×
UNCOV
222
    m_triggers->setSpectrumBarsNumber(m_barsNumSpin->value());
×
223

224
    // add volume item
UNCOV
225
    QTreeWidgetItem *volItem = new QTreeWidgetItem(m_tree);
×
UNCOV
226
    volItem->setText(KColumnName, tr("Volume Bar"));
×
227
    updateTreeItem(volItem, 1000);
×
228

NEW
229
    const int bandsNumber = m_barsNumSpin->value();
×
NEW
230
    const double minFreq = AudioCapture::minFrequency();
×
NEW
231
    const double maxFreq = m_maxFrequency;
×
NEW
232
    const double logRange = (bandsNumber > 0 && maxFreq > minFreq) ? qLn(maxFreq / minFreq) : 0.0;
×
233

NEW
234
    for (int i = 0; i < bandsNumber; i++)
×
235
    {
NEW
236
        double bandStartFreq = minFreq;
×
NEW
237
        double bandEndFreq = maxFreq;
×
NEW
238
        if (logRange > 0.0)
×
239
        {
NEW
240
            bandStartFreq = minFreq * qExp(logRange * (double(i) / double(bandsNumber)));
×
NEW
241
            bandEndFreq = minFreq * qExp(logRange * (double(i + 1) / double(bandsNumber)));
×
242
        }
243

NEW
244
        int bandStartHz = qCeil(bandStartFreq);
×
NEW
245
        int bandEndHz = (i == bandsNumber - 1) ? int(maxFreq) : (qCeil(bandEndFreq) - 1);
×
NEW
246
        if (bandEndHz <= bandStartHz)
×
NEW
247
            bandEndHz = bandStartHz;
×
248

UNCOV
249
        QTreeWidgetItem *barItem = new QTreeWidgetItem(m_tree);
×
NEW
250
        barItem->setText(KColumnName, tr("#%1 (%2Hz - %3Hz)").arg(i + 1).arg(bandStartHz).arg(bandEndHz));
×
251
        updateTreeItem(barItem, i);
×
252
    }
253

254
    m_tree->header()->resizeSections(QHeaderView::ResizeToContents);
×
255
}
256

257
void AudioTriggersConfiguration::slotTypeComboChanged(int comboIndex)
×
258
{
UNCOV
259
    QComboBox *combo = (QComboBox *)sender();
×
260
    int index = combo->itemData(comboIndex).toInt();
×
261
    QTreeWidgetItem *item = NULL;
×
262
    if (index == 1000)
×
263
        item = m_tree->topLevelItem(0);
×
264
    else
265
        item = m_tree->topLevelItem(index + 1);
×
266

267
    m_triggers->setSpectrumBarType(index, comboIndex);
×
268

UNCOV
269
    updateTreeItem(item, index);
×
270
}
×
271

UNCOV
272
void AudioTriggersConfiguration::slotDmxSelectionClicked()
×
273
{
UNCOV
274
    QToolButton *btn = (QToolButton *)sender();
×
275
    QVariant prop = btn->property("index");
×
276
    if (prop.isValid())
×
277
    {
278
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
279
        ChannelsSelection cfg(m_doc, this);
×
UNCOV
280
        if (bar != NULL)
×
281
            cfg.setChannelsList(bar->m_dmxChannels);
×
UNCOV
282
        if (cfg.exec() == QDialog::Rejected)
×
283
            return; // User pressed cancel
×
284

285
        QList<SceneValue> dmxList = cfg.channelsList();
×
286
        if (bar != NULL)
×
UNCOV
287
            bar->attachDmxChannels(m_doc, dmxList);
×
288
        QTreeWidgetItem *item = NULL;
×
UNCOV
289
        if (prop.toInt() == 1000)
×
290
            item = m_tree->topLevelItem(0);
×
291
        else
292
            item = m_tree->topLevelItem(prop.toInt() + 1);
×
UNCOV
293
        updateTreeItem(item, prop.toInt());
×
294
    }
×
295
}
×
296

297
void AudioTriggersConfiguration::slotFunctionSelectionClicked()
×
298
{
299
    QToolButton *btn = (QToolButton *)sender();
×
UNCOV
300
    QVariant prop = btn->property("index");
×
301
    if (prop.isValid())
×
302
    {
303
        FunctionSelection fs(this, m_doc);
×
304
        fs.setMultiSelection(false);
×
305
        if (fs.exec() == QDialog::Rejected || fs.selection().size() == 0)
×
306
            return; // User pressed cancel or made an invalid selection
×
UNCOV
307
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
308
        Function *f = m_doc->function(fs.selection().first());
×
309
        if (bar != NULL && f != NULL)
×
310
            bar->attachFunction(f);
×
311

UNCOV
312
        QTreeWidgetItem *item = NULL;
×
313
        if (prop.toInt() == 1000)
×
UNCOV
314
            item = m_tree->topLevelItem(0);
×
315
        else
316
            item = m_tree->topLevelItem(prop.toInt() + 1);
×
317
        updateTreeItem(item, prop.toInt());
×
UNCOV
318
    }
×
319
}
×
320

321
void AudioTriggersConfiguration::slotWidgetSelectionClicked()
×
322
{
323
    QToolButton *btn = (QToolButton *)sender();
×
324
    QVariant prop = btn->property("index");
×
325
    if (prop.isValid())
×
326
    {
UNCOV
327
        QList<int> filters;
×
328
        filters.append(VCWidget::SliderWidget);
×
329
        filters.append(VCWidget::ButtonWidget);
×
330
        filters.append(VCWidget::SpeedDialWidget);
×
UNCOV
331
        filters.append(VCWidget::CueListWidget);
×
332
        VCWidgetSelection ws(filters, this);
×
333
        if (ws.exec() == QDialog::Rejected || ws.getSelectedWidget() == 0)
×
334
            return; // User pressed cancel or did not select any widget
×
335
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
UNCOV
336
        if (bar != NULL)
×
337
        {
UNCOV
338
            bar->attachWidget(ws.getSelectedWidget()->id());
×
339
        }
340

341
        QTreeWidgetItem *item = NULL;
×
UNCOV
342
        if (prop.toInt() == 1000)
×
343
            item = m_tree->topLevelItem(0);
×
344
        else
345
            item = m_tree->topLevelItem(prop.toInt() + 1);
×
346
        updateTreeItem(item, prop.toInt());
×
347
    }
×
348
}
×
349

350
void AudioTriggersConfiguration::slotMinThresholdChanged(int val)
×
351
{
352
    QSpinBox *spin = (QSpinBox *)sender();
×
UNCOV
353
    QVariant prop = spin->property("index");
×
354
    if (prop.isValid())
×
355
    {
UNCOV
356
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
357
        uchar scaledVal = SCALE(float(val), 0.0, 100.0, 0.0, 255.0);
×
358
        if (bar != NULL)
×
359
            bar->setMinThreshold(scaledVal);
×
360
    }
361
}
×
362

363
void AudioTriggersConfiguration::slotMaxThresholdChanged(int val)
×
364
{
UNCOV
365
    QSpinBox *spin = (QSpinBox *)sender();
×
366
    QVariant prop = spin->property("index");
×
UNCOV
367
    if (prop.isValid())
×
368
    {
369
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
370
        uchar scaledVal = SCALE(float(val), 0.0, 100.0, 0.0, 255.0);
×
UNCOV
371
        if (bar != NULL)
×
372
            bar->setMaxThreshold(scaledVal);
×
373
    }
374
}
×
375

UNCOV
376
void AudioTriggersConfiguration::slotDivisorChanged(int val)
×
377
{
UNCOV
378
    QSpinBox *spin = (QSpinBox *)sender();
×
379
    QVariant prop = spin->property("index");
×
UNCOV
380
    if (prop.isValid())
×
381
    {
382
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
383
        if (bar != NULL)
×
UNCOV
384
            bar->setDivisor(val);
×
385
    }
386
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc