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

mcallegari / qlcplus / 19144422256

06 Nov 2025 05:33PM UTC coverage: 34.256% (-0.1%) from 34.358%
19144422256

push

github

mcallegari
Back to 5.1.0 debug

17718 of 51723 relevant lines covered (34.26%)

19528.23 hits per line

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

0.0
/ui/src/showmanager/audioitem.cpp
1
/*
2
  Q Light Controller Plus
3
  audioitem.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 <QApplication>
21
#include <QPainter>
22
#include <qmath.h>
23
#include <QDebug>
24
#include <QMenu>
25

26
#include "audioitem.h"
27
#include "trackitem.h"
28
#include "audiodecoder.h"
29
#include "audioplugincache.h"
30

31
AudioItem::AudioItem(Audio *aud, ShowFunction *func)
×
32
    : ShowItem(func)
33
    , m_audio(aud)
×
34
    , m_previewLeftAction(NULL)
×
35
    , m_previewRightAction(NULL)
×
36
    , m_previewStereoAction(NULL)
×
37
    , m_preview(NULL)
×
38
{
39
    Q_ASSERT(aud != NULL);
×
40

41
    if (func->color().isValid())
×
42
        setColor(func->color());
×
43
    else
44
        setColor(ShowFunction::defaultColor(Function::AudioType));
×
45

46
    if (func->duration() == 0)
×
47
        func->setDuration(aud->totalDuration());
×
48

49
    calculateWidth();
×
50
    connect(m_audio, SIGNAL(changed(quint32)),
×
51
            this, SLOT(slotAudioChanged(quint32)));
52

53
    /* Preview actions */
54
    m_previewLeftAction = new QAction(tr("Preview Left Channel"), this);
×
55
    m_previewLeftAction->setCheckable(true);
×
56
    connect(m_previewLeftAction, SIGNAL(triggered(bool)),
×
57
            this, SLOT(slotAudioPreviewLeft()));
58
    m_previewRightAction = new QAction(tr("Preview Right Channel"), this);
×
59
    m_previewRightAction->setCheckable(true);
×
60
    connect(m_previewRightAction, SIGNAL(triggered(bool)),
×
61
            this, SLOT(slotAudioPreviewRight()));
62
    m_previewStereoAction = new QAction(tr("Preview Stereo Channels"), this);
×
63
    m_previewStereoAction->setCheckable(true);
×
64
    connect(m_previewStereoAction, SIGNAL(triggered(bool)),
×
65
            this, SLOT(slotAudioPreviewStereo()));
66
}
×
67

68
void AudioItem::calculateWidth()
×
69
{
70
    int newWidth = 0;
×
71
    qint64 audio_duration = m_audio->totalDuration();
×
72

73
    if (audio_duration != 0)
×
74
        newWidth = ((50/(float)getTimeScale()) * (float)audio_duration) / 1000;
×
75
    else
76
        newWidth = 100;
×
77

78
    if (newWidth < (50 / m_timeScale))
×
79
        newWidth = 50 / m_timeScale;
×
80
    setWidth(newWidth);
×
81
}
×
82

83
void AudioItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
×
84
{
85
    Q_UNUSED(option);
86
    Q_UNUSED(widget);
87

88
    float timeScale = 50/(float)m_timeScale;
×
89

90
    ShowItem::paint(painter, option, widget);
×
91

92
    if (m_preview != NULL)
×
93
    {
94
        // show preview here
95
        painter->drawPixmap(0, 0, m_preview->scaled(m_width, TRACK_HEIGHT - 4));
×
96
    }
97

98
    if (m_audio->fadeInSpeed() != 0)
×
99
    {
100
        int fadeXpos = (timeScale * (float)m_audio->fadeInSpeed()) / 1000;
×
101
        painter->setPen(QPen(Qt::gray, 1));
×
102
        painter->drawLine(1, TRACK_HEIGHT - 4, fadeXpos, 2);
×
103
    }
104

105
    if (m_audio->fadeOutSpeed() != 0)
×
106
    {
107
        int fadeXpos = (timeScale * (float)m_audio->fadeOutSpeed()) / 1000;
×
108
        painter->setPen(QPen(Qt::gray, 1));
×
109
        painter->drawLine(m_width - fadeXpos, 2, m_width - 1, TRACK_HEIGHT - 4);
×
110
    }
111

112
    ShowItem::postPaint(painter);
×
113
}
×
114

115
void AudioItem::setTimeScale(int val)
×
116
{
117
    ShowItem::setTimeScale(val);
×
118
    calculateWidth();
×
119
}
×
120

121
void AudioItem::setDuration(quint32 msec, bool stretch)
×
122
{
123
    Q_UNUSED(msec)
124
    Q_UNUSED(stretch)
125
    // nothing to do
126
}
×
127

128
QString AudioItem::functionName()
×
129
{
130
    if (m_audio)
×
131
        return m_audio->name();
×
132
    return QString();
×
133
}
134

135
Audio *AudioItem::getAudio()
×
136
{
137
    return m_audio;
×
138
}
139

140
void AudioItem::updateWaveformPreview()
×
141
{
142
    PreviewThread *waveformThread = new PreviewThread;
×
143
    waveformThread->setAudioItem(this);
×
144
    connect(waveformThread, SIGNAL(finished()), waveformThread, SLOT(deleteLater()));
×
145
    waveformThread->start();
×
146
}
×
147

148
void AudioItem::slotAudioChanged(quint32)
×
149
{
150
    updateWaveformPreview();
×
151
    prepareGeometryChange();
×
152
    calculateWidth();
×
153
    if (m_function)
×
154
        m_function->setDuration(m_audio->totalDuration());
×
155
}
×
156

157
void AudioItem::slotAudioPreviewLeft()
×
158
{
159
    m_previewRightAction->setChecked(false);
×
160
    m_previewStereoAction->setChecked(false);
×
161
    updateWaveformPreview();
×
162
}
×
163

164
void AudioItem::slotAudioPreviewRight()
×
165
{
166
    m_previewLeftAction->setChecked(false);
×
167
    m_previewStereoAction->setChecked(false);
×
168
    updateWaveformPreview();
×
169
}
×
170

171
void AudioItem::slotAudioPreviewStereo()
×
172
{
173
    m_previewLeftAction->setChecked(false);
×
174
    m_previewRightAction->setChecked(false);
×
175
    updateWaveformPreview();
×
176
}
×
177

178
void AudioItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *)
×
179
{
180
    QMenu menu;
×
181
    QFont menuFont = qApp->font();
×
182
    menuFont.setPixelSize(14);
×
183
    menu.setFont(menuFont);
×
184

185
    if (m_audio->getAudioDecoder() != NULL)
×
186
    {
187
        AudioDecoder *ad = m_audio->getAudioDecoder();
×
188
        AudioParameters ap = ad->audioParameters();
×
189

190
        if (ap.channels() == 1)
×
191
            m_previewLeftAction->setText(tr("Preview Mono"));
×
192
        menu.addAction(m_previewLeftAction);
×
193
        if (ap.channels() == 2)
×
194
        {
195
            m_previewLeftAction->setText(tr("Preview Left Channel"));
×
196
            menu.addAction(m_previewRightAction);
×
197
            menu.addAction(m_previewStereoAction);
×
198
        }
199
        menu.addSeparator();
×
200
    }
201

202
    foreach (QAction *action, getDefaultActions())
×
203
        menu.addAction(action);
×
204

205
    menu.exec(QCursor::pos());
×
206
}
×
207

208
void PreviewThread::setAudioItem(AudioItem *item)
×
209
{
210
    m_item = item;
×
211
}
×
212

213
qint32 PreviewThread::getSample(unsigned char *data, quint32 idx, int sampleSize)
×
214
{
215
    qint32 value = 0;
×
216
    if (sampleSize == 1)
×
217
    {
218
        value = (qint32)data[idx];
×
219
    }
220
    else if (sampleSize == 2)
×
221
    {
222
        qint16 *array = (qint16 *)data;
×
223
        value = array[idx / 2];
×
224
    }
225
    else if (sampleSize == 3 || sampleSize == 4)
×
226
    {
227
        qint32 *array = (qint32 *)data;
×
228
        value = array[idx / 4] >> 16;
×
229
    }
230
    //qDebug() << "sampleValue:" << value;
231
    return value;
×
232
}
233

234
void PreviewThread::run()
×
235
{
236
    bool left = m_item->m_previewLeftAction->isChecked() || m_item->m_previewStereoAction->isChecked();
×
237
    bool right = m_item->m_previewRightAction->isChecked() || m_item->m_previewStereoAction->isChecked();
×
238

239
    if ((left || right) && m_item->m_audio->getAudioDecoder() != NULL)
×
240
    {
241
        AudioDecoder *ad = m_item->m_audio->doc()->audioPluginCache()->getDecoderForFile(m_item->m_audio->getSourceFileName());
×
242
        AudioParameters ap = ad->audioParameters();
×
243
        ad->seek(0);
×
244
        // 1- find out how many samples have to be represented on a single pixel on a 1:1 time scale
245
        int sampleSize = ap.sampleSize();
×
246
        int channels = ap.channels();
×
247
        int oneSecondSamples = ap.sampleRate() * channels;
×
248
        int onePixelSamples = oneSecondSamples / 50;
×
249

250
        qint32 maxValue = 0;
×
251
        // 24 and 32 bit samples would produce a RMS too high, so let's
252
        // work on 16bit values
253
        if (sampleSize > 2)
×
254
            sampleSize = 2;
×
255

256
        if (left && right)
×
257
            maxValue = 0x7F << (8 * (sampleSize - 1));
×
258
        else
259
            maxValue = 0x3F << (8 * (sampleSize - 1));
×
260

261
        quint32 onePixelReadLen = onePixelSamples * sampleSize;
×
262

263
        // 2- decode the whole file and fill a QPixmap with a sample block RMS value for each pixel
264
        qint64 dataRead = 1;
×
265
        unsigned char audioData[onePixelReadLen * 4];
×
266
        quint32 audioDataOffset = 0;
×
267
        QPixmap *preview = new QPixmap((50 * m_item->m_audio->totalDuration()) / 1000, 76);
×
268
        preview->fill(Qt::transparent);
×
269
        QPainter p(preview);
×
270
        int xpos = 0;
×
271

272
        qDebug() << "Audio duration:" << m_item->m_audio->totalDuration() <<
×
273
                    ", channels:" << channels << ", pixmap width:" << preview->width() <<
×
274
                    ", maxValue:" << maxValue << ", samples:" << sampleSize;
×
275
        qDebug() << "Samples per second:" << oneSecondSamples << ", for one pixel:" << onePixelSamples <<
×
276
                    ", onePixelReadLen:" << onePixelReadLen;
×
277

278
        delete m_item->m_preview;
×
279
        m_item->m_preview = NULL;
×
280
        m_item->update();
×
281

282
        while (dataRead)
×
283
        {
284
            quint32 tmpExceedData = 0;
×
285
            if (audioDataOffset < onePixelReadLen)
×
286
            {
287
                dataRead = ad->read((char *)audioData + audioDataOffset, onePixelReadLen * 2);
×
288
                if (dataRead > 0)
×
289
                {
290
                    if ((quint32)dataRead + audioDataOffset >= onePixelReadLen)
×
291
                    {
292
                        tmpExceedData = (dataRead + audioDataOffset) - onePixelReadLen;
×
293
                        dataRead = onePixelReadLen;
×
294
                    }
295
                    else
296
                    {
297
                        qDebug() << "Not enough data. Requested:" << onePixelReadLen << "got:" << dataRead;
×
298
                        audioDataOffset = dataRead;
×
299
                        continue;
×
300
                    }
301
                }
302
            }
303
            else
304
            {
305
                dataRead = onePixelReadLen;
×
306
                tmpExceedData = audioDataOffset - onePixelReadLen;
×
307
            }
308

309
            if (dataRead == onePixelReadLen)
×
310
            {
311
                quint32 i = 0;
×
312
                // calculate the RMS value (peak) for this data block
313
                qint64 rmsLeft = 0;
×
314
                qint64 rmsRight = 0;
×
315
                bool done = false;
×
316
                while (!done)
×
317
                {
318
                    if (left)
×
319
                    {
320
                        qint32 sampleVal = getSample(audioData, i, sampleSize);
×
321
                        rmsLeft += (sampleVal * sampleVal);
×
322
                    }
323
                    i += sampleSize;
×
324

325
                    if (channels == 2)
×
326
                    {
327
                        if (right)
×
328
                        {
329
                            qint32 sampleVal = getSample(audioData, i, sampleSize);
×
330
                            rmsRight += (sampleVal * sampleVal);
×
331
                        }
332
                        i += sampleSize;
×
333
                    }
334

335
                    if (i >= dataRead)
×
336
                    {
337
                        //qDebug() << "i:" << i << "xpos:" << xpos;
338
                        done = true;
×
339
                    }
340
                }
341

342
                if (left)
×
343
                    rmsLeft = sqrt(rmsLeft / onePixelSamples);
×
344
                if (right)
×
345
                    rmsRight = sqrt(rmsRight / onePixelSamples);
×
346
                //qDebug() << "sample" << i << "RMS right:" << rmsRight << ", RMS left:" << rmsLeft;
347

348
                // 3- Draw the actual waveform
349
                unsigned short lineHeightLeft = 0, lineHeightRight = 0;
×
350

351
                if (left)
×
352
                    lineHeightLeft = (76 * rmsLeft) / maxValue;
×
353
                if (right)
×
354
                    lineHeightRight = (76 * rmsRight) / maxValue;
×
355

356
                if (left && right)
×
357
                {
358
                    if (lineHeightLeft > 1)
×
359
                        p.drawLine(xpos, 19 - (lineHeightLeft / 2), xpos, 19 + (lineHeightLeft / 2));
×
360
                    else
361
                        p.drawLine(xpos, 19, xpos + 1, 19);
×
362

363
                    if (lineHeightRight > 1)
×
364
                        p.drawLine(xpos, 51 - (lineHeightRight / 2), xpos, 51 + (lineHeightRight / 2));
×
365
                    else
366
                        p.drawLine(xpos, 51, xpos + 1, 51);
×
367
                }
368
                else
369
                {
370
                    unsigned short lineHeight = left ? lineHeightLeft : lineHeightRight;
×
371

372
                    if (lineHeight > 1)
×
373
                        p.drawLine(xpos, 38 - (lineHeight / 2), xpos, 38 + (lineHeight / 2));
×
374
                    else
375
                        p.drawLine(xpos, 38, xpos + 1, 38);
×
376
                    //qDebug() << "Data read: " << dataRead << ", rms: " << rmsRight << ", line height: " << lineHeight << ", xpos = " << xpos;
377
                }
378
                xpos++;
×
379

380
                if (tmpExceedData > 0)
×
381
                {
382
                    //qDebug() << "Exceed data found: " << tmpExceedData;
383
                    memmove(audioData, audioData + onePixelReadLen, tmpExceedData);
×
384
                    audioDataOffset = tmpExceedData;
×
385
                }
386
                else
387
                    audioDataOffset = 0;
×
388
            }
389
        }
390
        //qDebug() << "Iterations done: " << xpos;
391
        delete ad;
×
392
        m_item->m_preview = preview;
×
393
    }
×
394
    else // no preview selected. Delete pixmap
395
    {
396
        delete m_item->m_preview;
×
397
        m_item->m_preview = NULL;
×
398
    }
399

400
    m_item->update();
×
401
}
×
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