1 /*
2 Based on Shotcut, SPDX-FileCopyrightText: 2015-2016 Meltytech LLC
3 SPDX-FileCopyrightText: 2019 Jean-Baptiste Mardelle <jb@kdenlive.org>
4 This file is part of Kdenlive. See www.kdenlive.org.
5 
6 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
7 */
8 
9 #include "kdenlivesettings.h"
10 #include "core.h"
11 #include "bin/projectitemmodel.h"
12 #include <QPainter>
13 #include <QPainterPath>
14 #include <QQuickPaintedItem>
15 #include <QElapsedTimer>
16 #include <QtMath>
17 #include <cmath>
18 #include "kdenlivesettings.h"
19 
20 const QStringList chanelNames{"L", "R", "C", "LFE", "BL", "BR"};
21 
22 class TimelineTriangle : public QQuickPaintedItem
23 {
24     Q_OBJECT
25     Q_PROPERTY(QColor fillColor MEMBER m_color)
26 public:
TimelineTriangle()27     TimelineTriangle() { setAntialiasing(true); }
paint(QPainter * painter)28     void paint(QPainter *painter) override
29     {
30         QPainterPath path;
31         path.moveTo(0, 0);
32         path.lineTo(width(), 0);
33         path.lineTo(0, height());
34         painter->fillPath(path, m_color);
35         painter->setPen(Qt::white);
36         painter->drawLine(int(width()), 0, 0, int(height()));
37     }
38 
39 private:
40     QColor m_color;
41 };
42 
43 class TimelinePlayhead : public QQuickPaintedItem
44 {
45     Q_OBJECT
46     Q_PROPERTY(QColor fillColor MEMBER m_color NOTIFY colorChanged)
47 
48 public:
TimelinePlayhead()49     TimelinePlayhead() { connect(this, SIGNAL(colorChanged(QColor)), this, SLOT(update())); }
50 
paint(QPainter * painter)51     void paint(QPainter *painter) override
52     {
53         QPainterPath path;
54         path.moveTo(width(), 0);
55         path.lineTo(width() / 2.0, height());
56         path.lineTo(0, 0);
57         painter->fillPath(path, m_color);
58     }
59 signals:
60     void colorChanged(const QColor &);
61 
62 private:
63     QColor m_color;
64 };
65 
66 class TimelineWaveform : public QQuickPaintedItem
67 {
68     Q_OBJECT
69     Q_PROPERTY(QColor fillColor0 MEMBER m_bgColor NOTIFY propertyChanged)
70     Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged)
71     Q_PROPERTY(QColor fillColor2 MEMBER m_color2 NOTIFY propertyChanged)
72     Q_PROPERTY(int waveInPoint MEMBER m_inPoint NOTIFY propertyChanged)
73     Q_PROPERTY(int channels MEMBER m_channels NOTIFY propertyChanged)
74     Q_PROPERTY(int ix MEMBER m_index)
75     Q_PROPERTY(QString binId MEMBER m_binId NOTIFY levelsChanged)
76     Q_PROPERTY(int waveOutPoint MEMBER m_outPoint)
77     Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged)
78     Q_PROPERTY(int audioStream MEMBER m_stream)
79     Q_PROPERTY(double scaleFactor MEMBER m_scale)
80     Q_PROPERTY(double speed MEMBER m_speed)
81     Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged)
82     Q_PROPERTY(bool normalize MEMBER m_normalize NOTIFY normalizeChanged)
83     Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk)
84     Q_PROPERTY(bool isOpaque MEMBER m_opaquePaint)
85 
86 public:
TimelineWaveform(QQuickItem * parent=nullptr)87     TimelineWaveform(QQuickItem *parent = nullptr)
88         : QQuickPaintedItem(parent)
89         , m_speed(1.)
90         , m_opaquePaint(false)
91     {
92         setAntialiasing(false);
93         setOpaquePainting(m_opaquePaint);
94         setEnabled(false);
95         m_precisionFactor = 1;
96         //setRenderTarget(QQuickPaintedItem::FramebufferObject);
97         //setMipmap(true);
98         //setTextureSize(QSize(1, 1));
99         connect(this, &TimelineWaveform::levelsChanged, [&]() {
100             if (!m_binId.isEmpty()) {
101                 if (m_audioLevels.isEmpty() && m_stream >= 0) {
102                     update();
103                 } else {
104                     // Clip changed, reset levels
105                     m_audioLevels.clear();
106                 }
107             }
108         });
109         connect(this, &TimelineWaveform::normalizeChanged, [&]() {
110             m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0;
111             update();
112         });
113         connect(this, &TimelineWaveform::propertyChanged, this, static_cast<void (QQuickItem::*)()>(&QQuickItem::update));
114     }
115 
paint(QPainter * painter)116     void paint(QPainter *painter) override
117     {
118         if (m_binId.isEmpty()) {
119             return;
120         }
121         if (m_audioLevels.isEmpty() && m_stream >= 0) {
122             m_audioLevels = pCore->projectItemModel()->getAudioLevelsByBinID(m_binId, m_stream);
123             if (m_audioLevels.isEmpty()) {
124                 return;
125             }
126             m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0;
127         }
128 
129         if (m_outPoint == m_inPoint) {
130             return;
131         }
132         QRectF bgRect(0, 0, width(), height());
133         if (m_opaquePaint) {
134             painter->fillRect(bgRect, m_bgColor);
135         }
136         QPen pen(painter->pen());
137         double increment = qMax(1., m_scale / m_channels); //qMax(1., 1. / qAbs(indicesPrPixel));
138         qreal indicesPrPixel = m_channels / m_scale * qAbs(m_speed); //qreal(m_outPoint - m_inPoint) / width() * m_precisionFactor;
139         int h = int(height());
140         double offset = 0;
141         bool pathDraw = increment > 1.2;
142         if (increment > 1. && !pathDraw) {
143             pen.setWidth(int(ceil(increment)));
144             offset = pen.width() / 2.;
145             pen.setColor(m_color);
146             pen.setCapStyle(Qt::FlatCap);
147         } else if (pathDraw) {
148             pen.setWidth(0);
149             painter->setBrush(m_color);
150             pen.setColor(m_bgColor.darker(200));
151         } else {
152             pen.setColor(m_color);
153         }
154         painter->setPen(pen);
155         double scaleFactor = 255;
156         if (m_audioMax > 1) {
157             scaleFactor = m_audioMax;
158         }
159         int startPos = int(m_inPoint / indicesPrPixel);
160         if (!KdenliveSettings::displayallchannels()) {
161             // Draw merged channels
162             double i = 0;
163             double level;
164             int j = 0;
165             QPainterPath path;
166             if (pathDraw) {
167                 path.moveTo(j - 1, height());
168             }
169             for (; i <= width(); j++) {
170                 double level;
171                 i = j * increment;
172                 int idx = qCeil((startPos + i) * indicesPrPixel);
173                 idx += idx % m_channels;
174                 i -= offset;
175                 if (idx + m_channels >= m_audioLevels.length() || idx < 0) {
176                     break;
177                 }
178                 level = m_audioLevels.at(idx) / scaleFactor;
179                 for (int k = 1; k < m_channels; k++) {
180                     level = qMax(level, m_audioLevels.at(idx + k) / scaleFactor);
181                 }
182                 if (pathDraw) {
183                     double val = height() - level * height();
184                     path.lineTo(i, val);
185                     path.lineTo(( j + 1) * increment - offset, val);
186                 } else {
187                     painter->drawLine(int(i), h, int(i), int(h - (h * level)));
188                 }
189             }
190             if (pathDraw) {
191                 path.lineTo(i, height());
192                 painter->drawPath(path);
193             }
194         } else {
195             double channelHeight = height() / m_channels;
196             QPen pen(painter->pen());
197             // Draw separate channels
198             scaleFactor = channelHeight / (2 * scaleFactor);
199             double i = 0;
200             double level;
201             bgRect.setHeight(channelHeight);
202             // Path for vector drawing
203             for (int channel = 0; channel < m_channels; channel++) {
204                 // y is channel median pos
205                 double y = (channel * channelHeight) + channelHeight / 2;
206                 QPainterPath path;
207                 path.moveTo(-1, y);
208                 if (channel % 2 == 0) {
209                     // Add dark background on odd channels
210                     painter->setOpacity(0.2);
211                     bgRect.moveTo(0, channel * channelHeight);
212                     painter->fillRect(bgRect, Qt::black);
213                 }
214                 // Draw channel median line
215                 pen.setColor(channel % 2 == 0 ? m_color : m_color2);
216                 painter->setBrush(channel % 2 == 0 ? m_color : m_color2);
217                 painter->setOpacity(0.5);
218                 pen.setWidthF(0);
219                 painter->setPen(pen);
220                 painter->drawLine(QLineF(0., y, width(), y));
221                 pen.setWidth(int(ceil(increment)));
222                 painter->setPen(pathDraw ? Qt::NoPen : pen);
223                 painter->setOpacity(1);
224                 i = 0;
225                 int j = 0;
226                 for (; i <= width(); j++) {
227                     i = j * increment;
228                     int idx = int(ceil((startPos + i) * indicesPrPixel));
229                     idx += idx % m_channels;
230                     i -= offset;
231                     idx += channel;
232                     if (idx >= m_audioLevels.length() || idx < 0) break;
233                     if (pathDraw) {
234                         level = m_audioLevels.at(idx) * scaleFactor;
235                         path.lineTo(i, y - level);
236                     } else {
237                         level = m_audioLevels.at(idx) * scaleFactor; // divide height by 510 (2*255) to get height
238                         painter->drawLine(int(i), int(y - level), int(i), int(y + level));
239                     }
240                 }
241                 if (pathDraw) {
242                     path.lineTo(i, y);
243                     painter->drawPath(path);
244                     QTransform tr(1, 0, 0, -1, 0, 2 * y);
245                     painter->drawPath(tr.map(path));
246                 }
247                 if (m_firstChunk && m_channels > 1 && m_channels < 7) {
248                     painter->drawText(2, int(y + channelHeight / 2), chanelNames[channel]);
249                 }
250             }
251         }
252     }
253 
254 signals:
255     void levelsChanged();
256     void propertyChanged();
257     void normalizeChanged();
258     void inPointChanged();
259     void audioChannelsChanged();
260 
261 private:
262     QVector<uint8_t> m_audioLevels;
263     int m_inPoint;
264     int m_outPoint;
265     QString m_binId;
266     QColor m_bgColor;
267     QColor m_color;
268     QColor m_color2;
269     bool m_format;
270     bool m_normalize;
271     int m_channels;
272     int m_precisionFactor;
273     int m_stream;
274     double m_scale;
275     double m_speed;
276     double m_audioMax;
277     bool m_firstChunk;
278     bool m_opaquePaint;
279     int m_index;
280 };
281 
registerTimelineItems()282 void registerTimelineItems()
283 {
284     qmlRegisterType<TimelineTriangle>("Kdenlive.Controls", 1, 0, "TimelineTriangle");
285     qmlRegisterType<TimelinePlayhead>("Kdenlive.Controls", 1, 0, "TimelinePlayhead");
286     qmlRegisterType<TimelineWaveform>("Kdenlive.Controls", 1, 0, "TimelineWaveform");
287 }
288 
289 #include "timelineitems.moc"
290