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

mcallegari / qlcplus / 26643984380

29 May 2026 02:45PM UTC coverage: 34.968% (-0.07%) from 35.037%
26643984380

Pull #2036

github

web-flow
Merge dd2197bec into f9fbc19ef
Pull Request #2036: engine/audio: configurable spectrum grid for Audio Triggers

17 of 188 new or added lines in 5 files covered. (9.04%)

13 existing lines in 4 files now uncovered.

18309 of 52359 relevant lines covered (34.97%)

41042.29 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 <QLabel>
21
#include <QTreeWidgetItem>
22
#include <QToolButton>
23
#include <QComboBox>
24
#include <QDoubleSpinBox>
25
#include <QSpinBox>
26
#include <qmath.h>
27

28
#include "vcaudiotriggersproperties.h"
29
#include "inputselectionwidget.h"
30
#include "channelsselection.h"
31
#include "functionselection.h"
32
#include "vcwidgetselection.h"
33
#include "vcaudiotriggers.h"
34
#include "audiocapture.h"
35
#include "spectrumgrid.h"
36
#include "qlcmacros.h"
37
#include "audiobar.h"
38

39
#define KColumnName             0
40
#define KColumnType             1
41
#define KColumnAssign           2
42
#define KColumnInfo             3
43
#define KColumnMinThreshold     4
44
#define KColumnMaxThreshold     5
45
#define KColumnDivisor          6
46

47
AudioTriggersConfiguration::AudioTriggersConfiguration(VCAudioTriggers *triggers, Doc *doc,
×
48
                                                       int bandsNumber, int maxFrequency)
×
49
    : QDialog(triggers)
50
    , m_doc(doc)
×
51
    , m_maxFrequency(maxFrequency)
×
NEW
52
    , m_gridCombo(NULL)
×
NEW
53
    , m_gammaSpin(NULL)
×
NEW
54
    , m_initialGridMode(triggers->spectrumGridMode())
×
55
{
56
    setupUi(this);
×
57

58
    m_triggers = triggers;
×
59

60
    m_nameEdit->setText(m_triggers->caption());
×
61

NEW
62
    QLabel *gridLabel = new QLabel(tr("Spectrum grid:"), this);
×
NEW
63
    m_gridCombo = new QComboBox(this);
×
NEW
64
    m_gridCombo->addItem(tr("Uniform log"), int(SpectrumGridMode::LogUniform));
×
NEW
65
    m_gridCombo->addItem(tr("Semi-log (1-2-5)"), int(SpectrumGridMode::SemiLogPreferred));
×
NEW
66
    const int gridIndex = m_gridCombo->findData(int(m_initialGridMode));
×
NEW
67
    m_gridCombo->setCurrentIndex(gridIndex >= 0 ? gridIndex : 0);
×
NEW
68
    gridLayout->addWidget(gridLabel, 1, 2, Qt::AlignRight);
×
NEW
69
    gridLayout->addWidget(m_gridCombo, 1, 3);
×
70

NEW
71
    QLabel *gammaLabel = new QLabel(tr("Low band emphasis:"), this);
×
NEW
72
    m_gammaSpin = new QDoubleSpinBox(this);
×
NEW
73
    m_gammaSpin->setRange(1.0, 4.0);
×
NEW
74
    m_gammaSpin->setSingleStep(0.1);
×
NEW
75
    m_gammaSpin->setDecimals(1);
×
NEW
76
    m_gammaSpin->setValue(triggers->spectrumLowBandGamma());
×
NEW
77
    gridLayout->addWidget(gammaLabel, 2, 2, Qt::AlignRight);
×
NEW
78
    gridLayout->addWidget(m_gammaSpin, 2, 3);
×
79

80
    m_barsNumSpin->setFixedWidth(70);
×
81
    m_barsNumSpin->setFixedHeight(30);
×
82
    m_barsNumSpin->setValue(bandsNumber);
×
83

84
    connect(m_barsNumSpin, SIGNAL(valueChanged(int)),
×
85
            this, SLOT(updateTree()));
NEW
86
    connect(m_gridCombo, SIGNAL(currentIndexChanged(int)),
×
87
            this, SLOT(slotGridModeChanged(int)));
NEW
88
    connect(m_gammaSpin, SIGNAL(valueChanged(double)),
×
89
            this, SLOT(slotGammaChanged(double)));
90

91
    /* External input */
92
    m_inputSelWidget = new InputSelectionWidget(m_doc, this);
×
93
    m_inputSelWidget->setCustomFeedbackVisibility(true);
×
94
    m_inputSelWidget->setKeySequence(m_triggers->keySequence());
×
95
    m_inputSelWidget->setInputSource(m_triggers->inputSource());
×
96
    m_inputSelWidget->setWidgetPage(m_triggers->page());
×
97
    m_inputSelWidget->show();
×
98
    m_extControlLayout->addWidget(m_inputSelWidget);
×
99

100
    m_tree->setAlternatingRowColors(true);
×
101
    m_tree->setRootIsDecorated(false);
×
102
    m_tree->setSelectionMode(QAbstractItemView::NoSelection);
×
103
    m_tree->setAllColumnsShowFocus(true);
×
104

105
    updateTree();
×
106
}
×
107

108
AudioTriggersConfiguration::~AudioTriggersConfiguration()
×
109
{
110
}
×
111

112
void AudioTriggersConfiguration::accept()
×
113
{
114
    m_triggers->setCaption(m_nameEdit->text());
×
115
    m_triggers->setKeySequence(m_inputSelWidget->keySequence());
×
116
    m_triggers->setInputSource(m_inputSelWidget->inputSource());
×
117

NEW
118
    const SpectrumGridMode mode = SpectrumGridMode(m_gridCombo->currentData().toInt());
×
NEW
119
    m_triggers->setSpectrumGridMode(mode);
×
NEW
120
    m_triggers->setSpectrumLowBandGamma(m_gammaSpin->value());
×
121

122
    QDialog::accept();
×
123
}
×
124

NEW
125
void AudioTriggersConfiguration::slotGammaChanged(double value)
×
126
{
NEW
127
    m_triggers->setSpectrumLowBandGamma(value);
×
NEW
128
    updateTree();
×
NEW
129
}
×
130

NEW
131
void AudioTriggersConfiguration::slotGridModeChanged(int index)
×
132
{
133
    Q_UNUSED(index)
NEW
134
    m_triggers->setSpectrumGridMode(SpectrumGridMode(m_gridCombo->currentData().toInt()));
×
NEW
135
    updateTree();
×
NEW
136
}
×
137

UNCOV
138
void AudioTriggersConfiguration::updateTreeItem(QTreeWidgetItem *item, int idx)
×
139
{
140
    if (item == NULL)
×
141
        return;
×
142

143
    AudioBar *bar = m_triggers->getSpectrumBar(idx);
×
144
    bar->setName(item->text(KColumnName));
×
145

146
    bar->debugInfo();
×
147
    QComboBox *currCombo = (QComboBox *)m_tree->itemWidget(item, KColumnType);
×
148
    if (currCombo != NULL)
×
149
    {
150
        disconnect(currCombo, SIGNAL(currentIndexChanged(int)),
×
151
                   this, SLOT(slotTypeComboChanged(int)));
152
        m_tree->removeItemWidget(item, KColumnType);
×
153
    }
154
    m_tree->removeItemWidget(item, KColumnAssign);
×
155
    m_tree->removeItemWidget(item, KColumnMinThreshold);
×
156
    m_tree->removeItemWidget(item, KColumnMaxThreshold);
×
157
    m_tree->removeItemWidget(item, KColumnDivisor);
×
158

159
    QComboBox *combo = new QComboBox();
×
160
    combo->addItem(QIcon(":/uncheck.png"), tr("None"), idx);
×
161
    combo->addItem(QIcon(":/intensity.png"), tr("DMX"), idx);
×
162
    combo->addItem(QIcon(":/function.png"), tr("Function"), idx);
×
163
    combo->addItem(QIcon(":/virtualconsole.png"), tr("VC Widget"), idx);
×
164
    combo->setCurrentIndex(bar->m_type);
×
165
    m_tree->setItemWidget(item, KColumnType, combo);
×
166
    connect(combo, SIGNAL(currentIndexChanged(int)),
×
167
            this, SLOT(slotTypeComboChanged(int)));
168

169
    if (bar->m_type == AudioBar::BarType::DMXBar)
×
170
    {
171
        QToolButton *btn = new QToolButton();
×
172
        btn->setIcon(QIcon(":/attach.png"));
×
173
        btn->setProperty("index", idx);
×
174
        m_tree->setItemWidget(item, KColumnAssign, btn);
×
175
        connect(btn, SIGNAL(clicked()), this, SLOT(slotDmxSelectionClicked()));
×
176
        item->setText(KColumnInfo, tr("%1 channels").arg(bar->m_dmxChannels.count()));
×
177
    }
178
    else if (bar->m_type == AudioBar::BarType::FunctionBar)
×
179
    {
180
        QToolButton *btn = new QToolButton();
×
181
        btn->setIcon(QIcon(":/attach.png"));
×
182
        btn->setProperty("index", idx);
×
183
        m_tree->setItemWidget(item, KColumnAssign, btn);
×
184
        connect(btn, SIGNAL(clicked()), this, SLOT(slotFunctionSelectionClicked()));
×
185
        if (bar->m_function != NULL)
×
186
        {
187
            item->setText(KColumnInfo, bar->m_function->name());
×
188
            item->setIcon(KColumnInfo, bar->m_function->getIcon());
×
189
        }
190
        else
191
        {
192
            item->setText(KColumnInfo, tr("No function"));
×
193
            item->setIcon(KColumnInfo, QIcon());
×
194
        }
195
    }
196
    else if (bar->m_type == AudioBar::BarType::VCWidgetBar)
×
197
    {
198
        QToolButton *btn = new QToolButton();
×
199
        btn->setIcon(QIcon(":/attach.png"));
×
200
        btn->setProperty("index", idx);
×
201
        m_tree->setItemWidget(item, KColumnAssign, btn);
×
202
        connect(btn, SIGNAL(clicked()), this, SLOT(slotWidgetSelectionClicked()));
×
203
        if (bar->widget() != NULL)
×
204
        {
205
            item->setText(KColumnInfo, bar->widget()->caption());
×
206
            item->setIcon(KColumnInfo, VCWidget::typeToIcon(bar->widget()->type()));
×
207
        }
208
        else
209
        {
210
            item->setText(KColumnInfo, tr("No widget"));
×
211
            item->setIcon(KColumnInfo, QIcon());
×
212
        }
213
    }
214
    else
215
    {
216
        item->setText(KColumnInfo, tr("Not assigned"));
×
217
        item->setIcon(KColumnInfo, QIcon());
×
218
    }
219

220
    if (bar->m_type == AudioBar::BarType::FunctionBar
×
221
        || (bar->m_type == AudioBar::BarType::VCWidgetBar && ((bar->widget() == NULL) || bar->widget()->type() != VCWidget::SliderWidget)))
×
222
    {
223
        QSpinBox *minspin = new QSpinBox();
×
224
        minspin->setMinimum(5);
×
225
        minspin->setMaximum(95);
×
226
        minspin->setSingleStep(1);
×
227
        minspin->setSuffix("%");
×
228
        minspin->setValue(SCALE(float(bar->m_minThreshold), 0.0, 255.0, 0.0, 100.0));
×
229
        minspin->setProperty("index", idx);
×
230
        connect(minspin, SIGNAL(valueChanged(int)), this, SLOT(slotMinThresholdChanged(int)));
×
231
        m_tree->setItemWidget(item, KColumnMinThreshold, minspin);
×
232

233
        QSpinBox *maxspin = new QSpinBox();
×
234
        maxspin->setMinimum(5);
×
235
        maxspin->setMaximum(95);
×
236
        maxspin->setSingleStep(1);
×
237
        maxspin->setSuffix("%");
×
238
        maxspin->setValue(SCALE(float(bar->m_maxThreshold), 0.0, 255.0, 0.0, 100.0));
×
239
        maxspin->setProperty("index", idx);
×
240
        connect(maxspin, SIGNAL(valueChanged(int)), this, SLOT(slotMaxThresholdChanged(int)));
×
241
        m_tree->setItemWidget(item, KColumnMaxThreshold, maxspin);
×
242
    }
243

244
    if (bar->m_type == AudioBar::BarType::VCWidgetBar
×
245
        && bar->widget() != NULL
×
246
        && (bar->widget()->type() == VCWidget::SpeedDialWidget || bar->widget()->type() == VCWidget::CueListWidget))
×
247
    {
248
        QSpinBox *divisor = new QSpinBox();
×
249
        divisor->setMinimum(1);
×
250
        divisor->setMaximum(64);
×
251
        divisor->setSingleStep(1);
×
252
        divisor->setValue(bar->m_divisor);
×
253
        divisor->setProperty("index", idx);
×
254
        connect(divisor, SIGNAL(valueChanged(int)), this, SLOT(slotDivisorChanged(int)));
×
255
        m_tree->setItemWidget(item, KColumnDivisor, divisor);
×
256
    }
257
}
258

259
void AudioTriggersConfiguration::updateTree()
×
260
{
261
    if (m_barsNumSpin->value() < m_barsNumSpin->minimum() ||
×
262
        m_barsNumSpin->value() > m_barsNumSpin->maximum())
×
263
            return;
×
264

265
    m_tree->clear();
×
266
    m_triggers->setSpectrumBarsNumber(m_barsNumSpin->value());
×
267

268
    // add volume item
269
    QTreeWidgetItem *volItem = new QTreeWidgetItem(m_tree);
×
270
    volItem->setText(KColumnName, tr("Volume Bar"));
×
271
    updateTreeItem(volItem, 1000);
×
272

273
    const int bandsNumber = m_barsNumSpin->value();
×
NEW
274
    const double minFreq = qMax(1.0, double(AudioCapture::minFrequency()));
×
NEW
275
    const double maxFreq = double(m_maxFrequency);
×
NEW
276
    const SpectrumGridMode gridMode = SpectrumGridMode(m_gridCombo->currentData().toInt());
×
NEW
277
    const double gamma = m_gammaSpin ? m_gammaSpin->value() : 1.0;
×
NEW
278
    const QVector<double> edges = computeSpectrumBandEdges(bandsNumber, minFreq, maxFreq, gridMode, gamma);
×
279

280
    for (int i = 0; i < bandsNumber; i++)
×
281
    {
NEW
282
        int bandStartHz = int(qCeil(edges[i]));
×
NEW
283
        int bandEndHz = (i == bandsNumber - 1) ? int(maxFreq) : (qCeil(edges[i + 1]) - 1);
×
284
        if (bandEndHz <= bandStartHz)
×
285
            bandEndHz = bandStartHz;
×
286

287
        QTreeWidgetItem *barItem = new QTreeWidgetItem(m_tree);
×
288
        barItem->setText(KColumnName, tr("#%1 (%2Hz - %3Hz)").arg(i + 1).arg(bandStartHz).arg(bandEndHz));
×
289
        updateTreeItem(barItem, i);
×
290
    }
291

292
    m_tree->header()->resizeSections(QHeaderView::ResizeToContents);
×
UNCOV
293
}
×
294

295
void AudioTriggersConfiguration::slotTypeComboChanged(int comboIndex)
×
296
{
297
    QComboBox *combo = (QComboBox *)sender();
×
298
    int index = combo->itemData(comboIndex).toInt();
×
299
    QTreeWidgetItem *item = NULL;
×
300
    if (index == 1000)
×
301
        item = m_tree->topLevelItem(0);
×
302
    else
303
        item = m_tree->topLevelItem(index + 1);
×
304

305
    m_triggers->setSpectrumBarType(index, comboIndex);
×
306

307
    updateTreeItem(item, index);
×
308
}
×
309

310
void AudioTriggersConfiguration::slotDmxSelectionClicked()
×
311
{
312
    QToolButton *btn = (QToolButton *)sender();
×
313
    QVariant prop = btn->property("index");
×
314
    if (prop.isValid())
×
315
    {
316
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
317
        ChannelsSelection cfg(m_doc, this);
×
318
        if (bar != NULL)
×
319
            cfg.setChannelsList(bar->m_dmxChannels);
×
320
        if (cfg.exec() == QDialog::Rejected)
×
321
            return; // User pressed cancel
×
322

323
        QList<SceneValue> dmxList = cfg.channelsList();
×
324
        if (bar != NULL)
×
325
            bar->attachDmxChannels(m_doc, dmxList);
×
326
        QTreeWidgetItem *item = NULL;
×
327
        if (prop.toInt() == 1000)
×
328
            item = m_tree->topLevelItem(0);
×
329
        else
330
            item = m_tree->topLevelItem(prop.toInt() + 1);
×
331
        updateTreeItem(item, prop.toInt());
×
332
    }
×
333
}
×
334

335
void AudioTriggersConfiguration::slotFunctionSelectionClicked()
×
336
{
337
    QToolButton *btn = (QToolButton *)sender();
×
338
    QVariant prop = btn->property("index");
×
339
    if (prop.isValid())
×
340
    {
341
        FunctionSelection fs(this, m_doc);
×
342
        fs.setMultiSelection(false);
×
343
        if (fs.exec() == QDialog::Rejected || fs.selection().size() == 0)
×
344
            return; // User pressed cancel or made an invalid selection
×
345
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
346
        Function *f = m_doc->function(fs.selection().first());
×
347
        if (bar != NULL && f != NULL)
×
348
            bar->attachFunction(f);
×
349

350
        QTreeWidgetItem *item = NULL;
×
351
        if (prop.toInt() == 1000)
×
352
            item = m_tree->topLevelItem(0);
×
353
        else
354
            item = m_tree->topLevelItem(prop.toInt() + 1);
×
355
        updateTreeItem(item, prop.toInt());
×
356
    }
×
357
}
×
358

359
void AudioTriggersConfiguration::slotWidgetSelectionClicked()
×
360
{
361
    QToolButton *btn = (QToolButton *)sender();
×
362
    QVariant prop = btn->property("index");
×
363
    if (prop.isValid())
×
364
    {
365
        QList<int> filters;
×
366
        filters.append(VCWidget::SliderWidget);
×
367
        filters.append(VCWidget::ButtonWidget);
×
368
        filters.append(VCWidget::SpeedDialWidget);
×
369
        filters.append(VCWidget::CueListWidget);
×
370
        VCWidgetSelection ws(filters, this);
×
371
        if (ws.exec() == QDialog::Rejected || ws.getSelectedWidget() == 0)
×
372
            return; // User pressed cancel or did not select any widget
×
373
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
374
        if (bar != NULL)
×
375
        {
376
            bar->attachWidget(ws.getSelectedWidget()->id());
×
377
        }
378

379
        QTreeWidgetItem *item = NULL;
×
380
        if (prop.toInt() == 1000)
×
381
            item = m_tree->topLevelItem(0);
×
382
        else
383
            item = m_tree->topLevelItem(prop.toInt() + 1);
×
384
        updateTreeItem(item, prop.toInt());
×
385
    }
×
386
}
×
387

388
void AudioTriggersConfiguration::slotMinThresholdChanged(int val)
×
389
{
390
    QSpinBox *spin = (QSpinBox *)sender();
×
391
    QVariant prop = spin->property("index");
×
392
    if (prop.isValid())
×
393
    {
394
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
395
        uchar scaledVal = SCALE(float(val), 0.0, 100.0, 0.0, 255.0);
×
396
        if (bar != NULL)
×
397
            bar->setMinThreshold(scaledVal);
×
398
    }
399
}
×
400

401
void AudioTriggersConfiguration::slotMaxThresholdChanged(int val)
×
402
{
403
    QSpinBox *spin = (QSpinBox *)sender();
×
404
    QVariant prop = spin->property("index");
×
405
    if (prop.isValid())
×
406
    {
407
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
408
        uchar scaledVal = SCALE(float(val), 0.0, 100.0, 0.0, 255.0);
×
409
        if (bar != NULL)
×
410
            bar->setMaxThreshold(scaledVal);
×
411
    }
412
}
×
413

414
void AudioTriggersConfiguration::slotDivisorChanged(int val)
×
415
{
416
    QSpinBox *spin = (QSpinBox *)sender();
×
417
    QVariant prop = spin->property("index");
×
418
    if (prop.isValid())
×
419
    {
420
        AudioBar *bar = m_triggers->getSpectrumBar(prop.toInt());
×
421
        if (bar != NULL)
×
422
            bar->setDivisor(val);
×
423
    }
424
}
×
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