File indexing completed on 2024-05-19 04:55:07

0001 /*
0002     SPDX-FileCopyrightText: 2015-2016 Meltytech LLC
0003     SPDX-FileCopyrightText: 2019 Jean-Baptiste Mardelle <jb@kdenlive.org>
0004 
0005     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "bin/projectitemmodel.h"
0009 #include "capture/mediacapture.h"
0010 #include "core.h"
0011 #include "kdenlivesettings.h"
0012 #include <QElapsedTimer>
0013 #include <QPainter>
0014 #include <QPainterPath>
0015 #include <QQuickPaintedItem>
0016 #include <QtMath>
0017 #include <cmath>
0018 
0019 class TimelineTriangle : public QQuickPaintedItem
0020 {
0021     Q_OBJECT
0022     Q_PROPERTY(QColor fillColor MEMBER m_color)
0023 public:
0024     TimelineTriangle(QQuickItem *parent = nullptr)
0025         : QQuickPaintedItem(parent)
0026     {
0027         setAntialiasing(true);
0028     }
0029     void paint(QPainter *painter) override
0030     {
0031         QPainterPath path;
0032         path.moveTo(0, 0);
0033         path.lineTo(width(), 0);
0034         path.lineTo(0, height());
0035         painter->fillPath(path, m_color);
0036         painter->setPen(Qt::white);
0037         painter->drawLine(int(width()), 0, 0, int(height()));
0038     }
0039 
0040 private:
0041     QColor m_color;
0042 };
0043 
0044 class TimelinePlayhead : public QQuickPaintedItem
0045 {
0046     Q_OBJECT
0047     Q_PROPERTY(QColor fillColor MEMBER m_color NOTIFY colorChanged)
0048 
0049 public:
0050     TimelinePlayhead(QQuickItem *parent = nullptr)
0051         : QQuickPaintedItem(parent)
0052     {
0053         connect(this, &TimelinePlayhead::colorChanged, this, [&](const QColor &) { update(); });
0054     }
0055 
0056     void paint(QPainter *painter) override
0057     {
0058         QPainterPath path;
0059         path.moveTo(width(), 0);
0060         path.lineTo(width() / 2.0, height());
0061         path.lineTo(0, 0);
0062         painter->fillPath(path, m_color);
0063     }
0064 Q_SIGNALS:
0065     void colorChanged(const QColor &);
0066 
0067 private:
0068     QColor m_color;
0069 };
0070 
0071 class TimelineWaveform : public QQuickPaintedItem
0072 {
0073     Q_OBJECT
0074     Q_PROPERTY(QColor fillColor0 MEMBER m_bgColor NOTIFY propertyChanged)
0075     Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged)
0076     Q_PROPERTY(QColor fillColor2 MEMBER m_color2 NOTIFY propertyChanged)
0077     Q_PROPERTY(int waveInPoint MEMBER m_inPoint NOTIFY propertyChanged)
0078     Q_PROPERTY(int channels MEMBER m_channels NOTIFY propertyChanged)
0079     Q_PROPERTY(int ix MEMBER m_index)
0080     Q_PROPERTY(QString binId MEMBER m_binId NOTIFY levelsChanged)
0081     Q_PROPERTY(int waveOutPoint MEMBER m_outPoint)
0082     Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged)
0083     Q_PROPERTY(int audioStream MEMBER m_stream)
0084     Q_PROPERTY(double scaleFactor MEMBER m_scale)
0085     Q_PROPERTY(double speed MEMBER m_speed)
0086     Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged)
0087     Q_PROPERTY(bool enforceRepaint MEMBER m_repaint NOTIFY propertyChanged)
0088     Q_PROPERTY(bool normalize MEMBER m_normalize NOTIFY normalizeChanged)
0089     Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk)
0090     Q_PROPERTY(bool isOpaque MEMBER m_opaquePaint)
0091 
0092 public:
0093     TimelineWaveform(QQuickItem *parent = nullptr)
0094         : QQuickPaintedItem(parent)
0095         , m_repaint(false)
0096         , m_speed(1.)
0097         , m_opaquePaint(false)
0098     {
0099         setAntialiasing(false);
0100         setOpaquePainting(m_opaquePaint);
0101         setEnabled(false);
0102         m_precisionFactor = 1;
0103         // setRenderTarget(QQuickPaintedItem::FramebufferObject);
0104         // setMipmap(true);
0105         // setTextureSize(QSize(1, 1));
0106         connect(this, &TimelineWaveform::levelsChanged, [&]() {
0107             if (!m_binId.isEmpty()) {
0108                 if (m_audioLevels.isEmpty() && m_stream >= 0) {
0109                     update();
0110                 } else {
0111                     // Clip changed, reset levels
0112                     m_audioLevels.clear();
0113                 }
0114             }
0115         });
0116         connect(this, &TimelineWaveform::normalizeChanged, [&]() {
0117             m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0;
0118             update();
0119         });
0120         connect(this, &TimelineWaveform::propertyChanged, this, static_cast<void (QQuickItem::*)()>(&QQuickItem::update));
0121     }
0122 
0123     void paint(QPainter *painter) override
0124     {
0125         if (m_binId.isEmpty()) {
0126             return;
0127         }
0128         if (m_audioLevels.isEmpty() && m_stream >= 0) {
0129             m_audioLevels = pCore->projectItemModel()->getAudioLevelsByBinID(m_binId, m_stream);
0130             if (m_audioLevels.isEmpty()) {
0131                 return;
0132             }
0133             m_audioMax = KdenliveSettings::normalizechannels() ? pCore->projectItemModel()->getAudioMaxLevel(m_binId, m_stream) : 0;
0134         }
0135 
0136         if (m_outPoint == m_inPoint) {
0137             return;
0138         }
0139         QRectF bgRect(0, 0, width(), height());
0140         if (m_opaquePaint) {
0141             painter->fillRect(bgRect, m_bgColor);
0142         }
0143         QPen pen(painter->pen());
0144         double increment = qMax(1., m_scale / m_channels);           // qMax(1., 1. / qAbs(indicesPrPixel));
0145         qreal indicesPrPixel = m_channels / m_scale * qAbs(m_speed); // qreal(m_outPoint - m_inPoint) / width() * m_precisionFactor;
0146         int h = int(height());
0147         double offset = 0;
0148         bool pathDraw = increment > 1.2;
0149         if (increment > 1. && !pathDraw) {
0150             pen.setWidth(int(ceil(increment)));
0151             offset = pen.width() / 2.;
0152             pen.setColor(m_color);
0153             pen.setCapStyle(Qt::FlatCap);
0154         } else if (pathDraw) {
0155             pen.setWidth(0);
0156             painter->setBrush(m_color);
0157             pen.setColor(m_bgColor.darker(200));
0158         } else {
0159             pen.setColor(m_color);
0160         }
0161         painter->setPen(pen);
0162         double scaleFactor = 255;
0163         if (m_audioMax > 1) {
0164             scaleFactor = m_audioMax;
0165         }
0166         bool reverse = m_speed < 0;
0167         int maxLength = m_audioLevels.length();
0168         if (reverse) {
0169             m_inPoint = qMin(m_inPoint, maxLength - m_channels);
0170         }
0171         int startPos = int(m_inPoint / indicesPrPixel);
0172         if (!KdenliveSettings::displayallchannels()) {
0173             // Draw merged channels
0174             double i = 0;
0175             int j = 0;
0176             int idx = 0;
0177             QPainterPath path;
0178             if (pathDraw) {
0179                 path.moveTo(j - 1, height());
0180             }
0181             for (; i <= width(); j++) {
0182                 double level;
0183                 i = j * increment;
0184                 if (reverse) {
0185                     idx = qCeil((startPos - i) * indicesPrPixel);
0186                     idx -= idx % m_channels;
0187                 } else {
0188                     idx = qCeil((startPos + i) * indicesPrPixel);
0189                     idx += idx % m_channels;
0190                 }
0191                 i -= offset;
0192                 if (idx + m_channels >= maxLength || idx < 0) {
0193                     break;
0194                 }
0195                 level = m_audioLevels.at(idx) / scaleFactor;
0196                 for (int k = 1; k < m_channels; k++) {
0197                     level = qMax(level, m_audioLevels.at(idx + k) / scaleFactor);
0198                 }
0199                 if (pathDraw) {
0200                     double val = height() - level * height();
0201                     path.lineTo(i, val);
0202                     path.lineTo((j + 1) * increment - offset, val);
0203                 } else {
0204                     painter->drawLine(int(i), h, int(i), int(h - (h * level)));
0205                 }
0206             }
0207             if (pathDraw) {
0208                 path.lineTo(i, height());
0209                 painter->drawPath(path);
0210             }
0211         } else {
0212             double channelHeight = height() / m_channels;
0213             QPen pen(painter->pen());
0214             // Draw separate channels
0215             scaleFactor = channelHeight / (2 * scaleFactor);
0216             bgRect.setHeight(channelHeight);
0217             // Path for vector drawing
0218             for (int channel = 0; channel < m_channels; channel++) {
0219                 double level;
0220                 // y is channel median pos
0221                 double y = (channel * channelHeight) + channelHeight / 2;
0222                 QPainterPath path;
0223                 path.moveTo(-1, y);
0224                 if (channel % 2 == 0) {
0225                     // Add dark background on odd channels
0226                     painter->setOpacity(0.2);
0227                     bgRect.moveTo(0, channel * channelHeight);
0228                     painter->fillRect(bgRect, Qt::black);
0229                 }
0230                 // Draw channel median line
0231                 pen.setColor(channel % 2 == 0 ? m_color : m_color2);
0232                 painter->setBrush(channel % 2 == 0 ? m_color : m_color2);
0233                 painter->setOpacity(0.5);
0234                 pen.setWidthF(0);
0235                 painter->setPen(pen);
0236                 painter->drawLine(QLineF(0., y, width(), y));
0237                 pen.setWidth(int(ceil(increment)));
0238                 painter->setPen(pathDraw ? Qt::NoPen : pen);
0239                 painter->setOpacity(1);
0240                 double i = 0;
0241                 int j = 0;
0242                 int idx = 0;
0243                 for (; i <= width(); j++) {
0244                     i = j * increment;
0245                     if (reverse) {
0246                         idx = qCeil((startPos - i) * indicesPrPixel);
0247                         idx -= idx % m_channels;
0248                     } else {
0249                         idx = qCeil((startPos + i) * indicesPrPixel);
0250                         idx += idx % m_channels;
0251                     }
0252                     i -= offset;
0253                     idx += channel;
0254                     if (idx >= maxLength || idx < 0) break;
0255                     if (pathDraw) {
0256                         level = m_audioLevels.at(idx) * scaleFactor;
0257                         path.lineTo(i, y - level);
0258                     } else {
0259                         level = m_audioLevels.at(idx) * scaleFactor; // divide height by 510 (2*255) to get height
0260                         painter->drawLine(int(i), int(y - level), int(i), int(y + level));
0261                     }
0262                 }
0263                 if (pathDraw) {
0264                     path.lineTo(i, y);
0265                     painter->drawPath(path);
0266                     QTransform tr(1, 0, 0, -1, 0, 2 * y);
0267                     painter->drawPath(tr.map(path));
0268                 }
0269                 if (m_firstChunk && m_channels > 1 && m_channels < 7) {
0270                     const QStringList chanelNames{"L", "R", "C", "LFE", "BL", "BR"};
0271                     painter->drawText(2, int(y + channelHeight / 2), chanelNames[channel]);
0272                 }
0273             }
0274         }
0275     }
0276 
0277 Q_SIGNALS:
0278     void levelsChanged();
0279     void propertyChanged();
0280     void normalizeChanged();
0281     void inPointChanged();
0282     void audioChannelsChanged();
0283 
0284 private:
0285     QVector<uint8_t> m_audioLevels;
0286     int m_inPoint;
0287     int m_outPoint;
0288     QString m_binId;
0289     QColor m_bgColor;
0290     QColor m_color;
0291     QColor m_color2;
0292     bool m_format;
0293     bool m_repaint;
0294     bool m_normalize;
0295     int m_channels;
0296     int m_precisionFactor;
0297     int m_stream;
0298     double m_scale;
0299     double m_speed;
0300     double m_audioMax;
0301     bool m_firstChunk;
0302     bool m_opaquePaint;
0303     int m_index;
0304 };
0305 
0306 class TimelineRecWaveform : public QQuickPaintedItem
0307 {
0308     Q_OBJECT
0309     Q_PROPERTY(QColor fillColor0 MEMBER m_bgColor NOTIFY propertyChanged)
0310     Q_PROPERTY(QColor fillColor1 MEMBER m_color NOTIFY propertyChanged)
0311     Q_PROPERTY(QColor fillColor2 MEMBER m_color2 NOTIFY propertyChanged)
0312     Q_PROPERTY(int waveInPoint MEMBER m_inPoint NOTIFY propertyChanged)
0313     Q_PROPERTY(int channels MEMBER m_channels NOTIFY propertyChanged)
0314     Q_PROPERTY(int ix MEMBER m_index)
0315     Q_PROPERTY(int waveOutPoint MEMBER m_outPoint)
0316     Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged)
0317     Q_PROPERTY(double scaleFactor MEMBER m_scale)
0318     Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged)
0319     Q_PROPERTY(bool enforceRepaint MEMBER m_repaint NOTIFY propertyChanged)
0320     Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk)
0321     Q_PROPERTY(bool isOpaque MEMBER m_opaquePaint)
0322 
0323 public:
0324     TimelineRecWaveform(QQuickItem *parent = nullptr)
0325         : QQuickPaintedItem(parent)
0326         , m_repaint(false)
0327         , m_opaquePaint(false)
0328     {
0329         setAntialiasing(false);
0330         setOpaquePainting(m_opaquePaint);
0331         setEnabled(false);
0332         m_precisionFactor = 1;
0333         // setRenderTarget(QQuickPaintedItem::FramebufferObject);
0334         // setMipmap(true);
0335         // setTextureSize(QSize(1, 1));
0336         connect(this, &TimelineRecWaveform::propertyChanged, this, static_cast<void (QQuickItem::*)()>(&QQuickItem::update));
0337     }
0338 
0339     void paint(QPainter *painter) override
0340     {
0341         const QVector<double> &audioLevels = pCore->getAudioDevice()->recLevels();
0342         if (audioLevels.isEmpty()) {
0343             return;
0344         }
0345 
0346         if (m_outPoint == m_inPoint) {
0347             return;
0348         }
0349         QRectF bgRect(0, 0, width(), height());
0350         if (m_opaquePaint) {
0351             painter->fillRect(bgRect, m_bgColor);
0352         }
0353         QPen pen(painter->pen());
0354         int maxLength = audioLevels.length();
0355         double increment = 1 / m_scale;
0356         qreal indicesPrPixel = m_channels / m_scale; // qreal(m_outPoint - m_inPoint) / width() * m_precisionFactor;
0357         int h = int(height());
0358         double offset = 0;
0359         bool pathDraw = increment > 1.2;
0360         if (increment > 1. && !pathDraw) {
0361             pen.setWidth(int(ceil(increment)));
0362             offset = pen.width() / 2.;
0363             pen.setColor(m_color);
0364             pen.setCapStyle(Qt::FlatCap);
0365         } else if (pathDraw) {
0366             pen.setWidth(0);
0367             painter->setBrush(m_color);
0368             pen.setColor(m_bgColor.darker(200));
0369         } else {
0370             pen.setColor(m_color);
0371         }
0372         painter->setPen(pen);
0373         int startPos = int(m_inPoint / indicesPrPixel);
0374         // Draw merged channels
0375         double i = 0;
0376         int j = 0;
0377         int idx = 0;
0378         QPainterPath path;
0379         if (pathDraw) {
0380             path.moveTo(j - 1, height());
0381         }
0382         for (; i <= width(); j++) {
0383             double level;
0384             i = j * increment;
0385             idx = qCeil((startPos + i) * indicesPrPixel);
0386             idx += idx % m_channels;
0387             i -= offset;
0388             if (idx + m_channels >= maxLength || idx < 0) {
0389                 break;
0390             }
0391             level = audioLevels.at(idx);
0392             if (pathDraw) {
0393                 double val = height() - level * height();
0394                 path.lineTo(i, val);
0395                 path.lineTo((j + 1) * increment - offset, val);
0396             } else {
0397                 painter->drawLine(int(i), h, int(i), int(h - (h * level)));
0398             }
0399         }
0400         if (pathDraw) {
0401             path.lineTo(i, height());
0402             painter->drawPath(path);
0403         }
0404     }
0405 
0406 Q_SIGNALS:
0407     void levelsChanged();
0408     void propertyChanged();
0409     void inPointChanged();
0410     void audioChannelsChanged();
0411 
0412 private:
0413     int m_inPoint;
0414     int m_outPoint;
0415     QColor m_bgColor;
0416     QColor m_color;
0417     QColor m_color2;
0418     bool m_format;
0419     bool m_repaint;
0420     int m_channels;
0421     int m_precisionFactor;
0422     double m_scale;
0423     bool m_firstChunk;
0424     bool m_opaquePaint;
0425     int m_index;
0426 };
0427 
0428 void registerTimelineItems()
0429 {
0430     qmlRegisterType<TimelineTriangle>("Kdenlive.Controls", 1, 0, "TimelineTriangle");
0431     qmlRegisterType<TimelinePlayhead>("Kdenlive.Controls", 1, 0, "TimelinePlayhead");
0432     qmlRegisterType<TimelineWaveform>("Kdenlive.Controls", 1, 0, "TimelineWaveform");
0433     qmlRegisterType<TimelineRecWaveform>("Kdenlive.Controls", 1, 0, "TimelineRecWaveform");
0434 }
0435 
0436 #include "timelineitems.moc"