Commit db2025fc authored by Simon Eugster's avatar Simon Eugster

First preparations for audio spectrum scopes, using kiss_fft

svn path=/trunk/kdenlive/; revision=5108
parent 1e8b41a1
......@@ -3,11 +3,14 @@ project(kdenlive)
set(BASE_VERSION 0.7.9)
set(LIBMLT_REQUIRED_VERSION 0.5.10)
#set(CMAKE_C_FLAGS " -std=c99 " ${CMAKE_C_FLAGS} " -std=c99")
option(RELEASE_BUILD "Remove compilation date from program version (use for stable releases)" OFF)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules)
# Search packages used by KDE.
find_package(KDE4 REQUIRED)
set(CMAKE_C_FLAGS " --std=c99") # To compile kiss_fft
include(KDE4Defaults)
include(MacroLibrary)
include(FindGettext)
......@@ -61,3 +64,6 @@ add_subdirectory(po)
add_subdirectory(man)
configure_file(${CMAKE_SOURCE_DIR}/kdenlive-config.h.cmake kdenlive-config.h @ONLY)
message("C flags are now: " ${CMAKE_C_FLAGS})
......@@ -1080,9 +1080,9 @@
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.061871844"
inkscape:cx="7437.5969"
inkscape:cy="3161.6811"
inkscape:zoom="0.49497475"
inkscape:cx="7848.5254"
inkscape:cy="5017.1078"
inkscape:document-units="px"
inkscape:current-layer="layer3"
showgrid="false"
......@@ -2041,6 +2041,17 @@
x="-3421.3643"
id="tspan13226-0-8"
sodipodi:role="line">External programs</tspan></text>
<text
sodipodi:linespacing="125%"
id="text3284"
y="-171.68839"
x="899.27069"
style="font-size:144px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;opacity:0.2017544;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans Bold"
xml:space="preserve"><tspan
y="-171.68839"
x="899.27069"
id="tspan3286"
sodipodi:role="line">Keyboard Shortcuts</tspan></text>
</g>
<g
inkscape:groupmode="layer"
......@@ -2396,5 +2407,27 @@
id="tspan13264-5-65"
x="4651.2607"
y="-163.50342">Color Correction layout</tspan></text>
<text
sodipodi:linespacing="125%"
id="text3288"
y="-71.055161"
x="2257.0693"
style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#eeeeee;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"
xml:space="preserve"><tspan
y="-71.055161"
x="2257.0693"
id="tspan3290"
sodipodi:role="line">Make customizable</tspan></text>
<text
xml:space="preserve"
style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#eeeeee;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"
x="1620.6732"
y="1.675822"
id="text3292"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3294"
x="1620.6732"
y="1.675822">Support workflow</tspan></text>
</g>
</svg>
add_subdirectory(widgets)
add_subdirectory(mimetypes)
add_subdirectory(kiss_fft)
macro_optional_find_package(Nepomuk)
include(FindQImageBlitz)
......@@ -16,8 +17,11 @@ include_directories(
${CMAKE_SOURCE_DIR}/src/widgets
${CMAKE_SOURCE_DIR}/src/colorcorrection
${CMAKE_SOURCE_DIR}/src/onmonitoritems
${CMAKE_SOURCE_DIR}/src/kiss_fft
${CMAKE_SOURCE_DIR}/src/audioscopes
)
if(Nepomuk_FOUND)
include_directories(NEPOMUK_INCLUDES)
set(optional_libs ${NEPOMUK_LIBRARIES})
......@@ -44,6 +48,7 @@ else(APPLE)
macro_optional_find_package(OpenGL)
endif(APPLE)
link_libraries(
${LIBMLT_LIBRARY}
${LIBMLTPLUS_LIBRARY}
......@@ -111,9 +116,11 @@ kde4_add_ui_files(kdenlive_UI
widgets/stopmotion_ui.ui
widgets/monitorscenecontrolwidget_ui.ui
widgets/cornerswidget_ui.ui
widgets/audiospectrum_ui.ui
)
set(kdenlive_SRCS
${kdenlive_SRCS}
addclipcommand.cpp
main.cpp
mainwindow.cpp
......@@ -213,6 +220,8 @@ set(kdenlive_SRCS
tracksconfigdialog.cpp
configtrackscommand.cpp
abstractscopewidget.cpp
audioscopes/abstractaudioscopewidget.cpp
audioscopes/audiospectrum.cpp
rebuildgroupcommand.cpp
histogram.cpp
rgbparade.cpp
......@@ -241,6 +250,9 @@ set(kdenlive_SRCS
onmonitoritems/onmonitorcornersitem.cpp
monitorscenecontrolwidget.cpp
cornerswidget.cpp
kiss_fft/_kiss_fft_guts.h
kiss_fft/kiss_fft.c
kiss_fft/tools/kiss_fftr.c
)
add_subdirectory(${CMAKE_SOURCE_DIR}/src/colorcorrection)
......
......@@ -222,7 +222,7 @@ private:
/** The semaphores ensure that the QFutures for the HUD/Scope/Background threads cannot
be assigned a new thread while it is still running. (Could cause deadlocks and other
nasty things known from parallelism. */
nasty things known from parallelism.) */
QSemaphore m_semaphoreHUD;
QSemaphore m_semaphoreScope;
QSemaphore m_semaphoreBackground;
......
/***************************************************************************
* Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) *
* This file is part of kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "qtconcurrentrun.h"
#include "abstractaudioscopewidget.h"
#include "renderer.h"
#include "monitor.h"
#include <QDebug>
#include <QFuture>
#include <QColor>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
const int REALTIME_FPS = 30;
const QColor light(250, 238, 226, 255);
const QColor dark(40, 40, 39, 255);
const QColor dark2(25, 25, 23, 255);
const QPen AbstractAudioScopeWidget::penThick(QBrush(QColor(250, 250, 250)), 2, Qt::SolidLine);
const QPen AbstractAudioScopeWidget::penThin(QBrush(QColor(250, 250, 250)), 1, Qt::SolidLine);
const QPen AbstractAudioScopeWidget::penLight(QBrush(QColor(200, 200, 250, 150)), 1, Qt::SolidLine);
const QPen AbstractAudioScopeWidget::penDark(QBrush(QColor(0, 0, 20, 250)), 1, Qt::SolidLine);
AbstractAudioScopeWidget::AbstractAudioScopeWidget(Monitor *projMonitor, Monitor *clipMonitor, bool trackMouse, QWidget *parent) :
QWidget(parent),
m_projMonitor(projMonitor),
m_clipMonitor(clipMonitor),
m_mousePos(0, 0),
m_mouseWithinWidget(false),
offset(5),
m_accelFactorHUD(1),
m_accelFactorScope(1),
m_accelFactorBackground(1),
m_semaphoreHUD(1),
m_semaphoreScope(1),
m_semaphoreBackground(1),
initialDimensionUpdateDone(false),
m_requestForcedUpdate(false)
{
m_scopePalette = QPalette();
m_scopePalette.setBrush(QPalette::Window, QBrush(dark2));
m_scopePalette.setBrush(QPalette::Base, QBrush(dark));
m_scopePalette.setBrush(QPalette::Button, QBrush(dark));
m_scopePalette.setBrush(QPalette::Text, QBrush(light));
m_scopePalette.setBrush(QPalette::WindowText, QBrush(light));
m_scopePalette.setBrush(QPalette::ButtonText, QBrush(light));
this->setPalette(m_scopePalette);
this->setAutoFillBackground(true);
m_aAutoRefresh = new QAction(i18n("Auto Refresh"), this);
m_aAutoRefresh->setCheckable(true);
m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this);
m_aRealtime->setCheckable(true);
m_menu = new QMenu(this);
m_menu->setPalette(m_scopePalette);
m_menu->addAction(m_aAutoRefresh);
m_menu->addAction(m_aRealtime);
this->setContextMenuPolicy(Qt::CustomContextMenu);
m_activeRender = (m_clipMonitor->isActive()) ? m_clipMonitor->render : m_projMonitor->render;
bool b = true;
b &= connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint)));
//b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
b &= connect(m_activeRender, SIGNAL(frameUpdated(QImage)), this, SLOT(slotRenderZoneUpdated(QImage)));
b &= connect(this, SIGNAL(signalHUDRenderingFinished(uint, uint)), this, SLOT(slotHUDRenderingFinished(uint, uint)));
b &= connect(this, SIGNAL(signalScopeRenderingFinished(uint, uint)), this, SLOT(slotScopeRenderingFinished(uint, uint)));
b &= connect(this, SIGNAL(signalBackgroundRenderingFinished(uint, uint)), this, SLOT(slotBackgroundRenderingFinished(uint, uint)));
b &= connect(m_aRealtime, SIGNAL(toggled(bool)), this, SLOT(slotResetRealtimeFactor(bool)));
b &= connect(m_aAutoRefresh, SIGNAL(toggled(bool)), this, SLOT(slotAutoRefreshToggled(bool)));
Q_ASSERT(b);
// Enable mouse tracking if desired.
// Causes the mouseMoved signal to be emitted when the mouse moves inside the
// widget, even when no mouse button is pressed.
this->setMouseTracking(trackMouse);
}
AbstractAudioScopeWidget::~AbstractAudioScopeWidget()
{
writeConfig();
delete m_menu;
delete m_aAutoRefresh;
delete m_aRealtime;
}
void AbstractAudioScopeWidget::init()
{
m_widgetName = widgetName();
readConfig();
}
void AbstractAudioScopeWidget::readConfig()
{
KSharedConfigPtr config = KGlobal::config();
KConfigGroup scopeConfig(config, configName());
m_aAutoRefresh->setChecked(scopeConfig.readEntry("autoRefresh", true));
m_aRealtime->setChecked(scopeConfig.readEntry("realtime", false));
scopeConfig.sync();
}
void AbstractAudioScopeWidget::writeConfig()
{
KSharedConfigPtr config = KGlobal::config();
KConfigGroup scopeConfig(config, configName());
scopeConfig.writeEntry("autoRefresh", m_aAutoRefresh->isChecked());
scopeConfig.writeEntry("realtime", m_aRealtime->isChecked());
scopeConfig.sync();
}
QString AbstractAudioScopeWidget::configName()
{
return "Scope_" + m_widgetName;
}
void AbstractAudioScopeWidget::prodHUDThread()
{
if (this->visibleRegion().isEmpty()) {
// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating HUD.";
} else {
if (m_semaphoreHUD.tryAcquire(1)) {
Q_ASSERT(!m_threadHUD.isRunning());
m_newHUDFrames.fetchAndStoreRelaxed(0);
m_newHUDUpdates.fetchAndStoreRelaxed(0);
m_threadHUD = QtConcurrent::run(this, &AbstractAudioScopeWidget::renderHUD, m_accelFactorHUD);
// qDebug() << "HUD thread started in " << m_widgetName;
} else {
// qDebug() << "HUD semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadHUD.isRunning();
}
}
}
void AbstractAudioScopeWidget::prodScopeThread()
{
// Only start a new thread if the scope is actually visible
// and not hidden by another widget on the stack and if user want the scope to update.
if (this->visibleRegion().isEmpty() || (!m_aAutoRefresh->isChecked() && !m_requestForcedUpdate)) {
// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating scope.";
} else {
// Try to acquire the semaphore. This must only succeed if m_threadScope is not running
// anymore. Therefore the semaphore must NOT be released before m_threadScope ends.
// If acquiring the semaphore fails, the thread is still running.
if (m_semaphoreScope.tryAcquire(1)) {
Q_ASSERT(!m_threadScope.isRunning());
m_newScopeFrames.fetchAndStoreRelaxed(0);
m_newScopeUpdates.fetchAndStoreRelaxed(0);
Q_ASSERT(m_accelFactorScope > 0);
// See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about
// running member functions in a thread
m_threadScope = QtConcurrent::run(this, &AbstractAudioScopeWidget::renderScope,
m_accelFactorScope, m_audioFrame, m_freq, m_nChannels, m_nSamples);
m_requestForcedUpdate = false;
// qDebug() << "Scope thread started in " << m_widgetName;
} else {
// qDebug() << "Scope semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadScope.isRunning();
}
}
}
void AbstractAudioScopeWidget::prodBackgroundThread()
{
if (this->visibleRegion().isEmpty()) {
// qDebug() << "Scope " << m_widgetName << " is not visible. Not calculating background.";
} else {
if (m_semaphoreBackground.tryAcquire(1)) {
Q_ASSERT(!m_threadBackground.isRunning());
m_newBackgroundFrames.fetchAndStoreRelaxed(0);
m_newBackgroundUpdates.fetchAndStoreRelaxed(0);
m_threadBackground = QtConcurrent::run(this, &AbstractAudioScopeWidget::renderBackground, m_accelFactorBackground);
// qDebug() << "Background thread started in " << m_widgetName;
} else {
// qDebug() << "Background semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadBackground.isRunning();
}
}
}
void AbstractAudioScopeWidget::forceUpdate(bool doUpdate)
{
// qDebug() << "Force update called in " << widgetName() << ". Arg: " << doUpdate;
if (!doUpdate) {
return;
}
m_requestForcedUpdate = true;
m_newHUDUpdates.fetchAndAddRelaxed(1);
m_newScopeUpdates.fetchAndAddRelaxed(1);
m_newBackgroundUpdates.fetchAndAddRelaxed(1);
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
}
void AbstractAudioScopeWidget::forceUpdateHUD()
{
m_newHUDUpdates.fetchAndAddRelaxed(1);
prodHUDThread();
}
void AbstractAudioScopeWidget::forceUpdateScope()
{
m_newScopeUpdates.fetchAndAddRelaxed(1);
m_requestForcedUpdate = true;
prodScopeThread();
}
void AbstractAudioScopeWidget::forceUpdateBackground()
{
m_newBackgroundUpdates.fetchAndAddRelaxed(1);
prodBackgroundThread();
}
///// Events /////
void AbstractAudioScopeWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (!m_aAutoRefresh->isChecked()) {
m_requestForcedUpdate = true;
m_activeRender->sendFrameUpdate();
}
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
QWidget::mouseReleaseEvent(event);
}
void AbstractAudioScopeWidget::resizeEvent(QResizeEvent *event)
{
// Update the dimension of the available rect for painting
m_scopeRect = scopeRect();
forceUpdate();
QWidget::resizeEvent(event);
}
void AbstractAudioScopeWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
m_scopeRect = scopeRect();
}
void AbstractAudioScopeWidget::paintEvent(QPaintEvent *)
{
QPainter davinci(this);
davinci.drawImage(m_scopeRect.topLeft(), m_imgBackground);
davinci.drawImage(m_scopeRect.topLeft(), m_imgScope);
davinci.drawImage(m_scopeRect.topLeft(), m_imgHUD);
}
void AbstractAudioScopeWidget::mouseMoveEvent(QMouseEvent *event)
{
m_mousePos = event->pos();
m_mouseWithinWidget = true;
emit signalMousePositionChanged();
}
void AbstractAudioScopeWidget::leaveEvent(QEvent *)
{
m_mouseWithinWidget = false;
emit signalMousePositionChanged();
}
void AbstractAudioScopeWidget::customContextMenuRequested(const QPoint &pos)
{
m_menu->exec(this->mapToGlobal(pos));
}
uint AbstractAudioScopeWidget::calculateAccelFactorHUD(uint oldMseconds, uint)
{
return ceil((float)oldMseconds*REALTIME_FPS / 1000);
}
uint AbstractAudioScopeWidget::calculateAccelFactorScope(uint oldMseconds, uint)
{
return ceil((float)oldMseconds*REALTIME_FPS / 1000);
}
uint AbstractAudioScopeWidget::calculateAccelFactorBackground(uint oldMseconds, uint)
{
return ceil((float)oldMseconds*REALTIME_FPS / 1000);
}
///// Slots /////
void AbstractAudioScopeWidget::slotHUDRenderingFinished(uint mseconds, uint oldFactor)
{
// qDebug() << "HUD rendering has finished, waiting for termination in " << m_widgetName;
m_threadHUD.waitForFinished();
m_imgHUD = m_threadHUD.result();
m_semaphoreHUD.release(1);
this->update();
int accel;
if (m_aRealtime->isChecked()) {
accel = calculateAccelFactorHUD(mseconds, oldFactor);
if (m_accelFactorHUD < 1) {
accel = 1;
}
m_accelFactorHUD = accel;
}
if ((m_newHUDFrames > 0 && m_aAutoRefresh->isChecked()) || m_newHUDUpdates > 0) {
// qDebug() << "Trying to start a new HUD thread for " << m_widgetName
// << ". New frames/updates: " << m_newHUDFrames << "/" << m_newHUDUpdates;
prodHUDThread();;
}
}
void AbstractAudioScopeWidget::slotScopeRenderingFinished(uint mseconds, uint oldFactor)
{
// The signal can be received before the thread has really finished. So we
// need to wait until it has really finished before starting a new thread.
// qDebug() << "Scope rendering has finished, waiting for termination in " << m_widgetName;
m_threadScope.waitForFinished();
m_imgScope = m_threadScope.result();
// The scope thread has finished. Now we can release the semaphore, allowing a new thread.
// See prodScopeThread where the semaphore is acquired again.
m_semaphoreScope.release(1);
this->update();
// Calculate the acceleration factor hint to get «realtime» updates.
int accel;
if (m_aRealtime->isChecked()) {
accel = calculateAccelFactorScope(mseconds, oldFactor);
if (accel < 1) {
// If mseconds happens to be 0.
accel = 1;
}
// Don't directly calculate with m_accelFactorScope as we are dealing with concurrency.
// If m_accelFactorScope is set to 0 at the wrong moment, who knows what might happen
// then :) Therefore use a local variable.
m_accelFactorScope = accel;
}
if ((m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) {
// qDebug() << "Trying to start a new scope thread for " << m_widgetName
// << ". New frames/updates: " << m_newScopeFrames << "/" << m_newScopeUpdates;
prodScopeThread();
}
}
void AbstractAudioScopeWidget::slotBackgroundRenderingFinished(uint mseconds, uint oldFactor)
{
// qDebug() << "Background rendering has finished, waiting for termination in " << m_widgetName;
m_threadBackground.waitForFinished();
m_imgBackground = m_threadBackground.result();
m_semaphoreBackground.release(1);
this->update();
int accel;
if (m_aRealtime->isChecked()) {
accel = calculateAccelFactorBackground(mseconds, oldFactor);
if (m_accelFactorBackground < 1) {
accel = 1;
}
m_accelFactorBackground = accel;
}
if ((m_newBackgroundFrames > 0 && m_aAutoRefresh->isChecked()) || m_newBackgroundUpdates > 0) {
// qDebug() << "Trying to start a new background thread for " << m_widgetName
// << ". New frames/updates: " << m_newBackgroundFrames << "/" << m_newBackgroundUpdates;
prodBackgroundThread();;
}
}
void AbstractAudioScopeWidget::slotActiveMonitorChanged(bool isClipMonitor)
{
// qDebug() << "Active monitor has changed in " << m_widgetName << ". Is the clip monitor active now? " << isClipMonitor;
bool b = m_activeRender->disconnect(this);
Q_ASSERT(b);
m_activeRender = (isClipMonitor) ? m_clipMonitor->render : m_projMonitor->render;
//b &= connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
b &= connect(m_activeRender, SIGNAL(frameUpdated(QImage)), this, SLOT(slotRenderZoneUpdated(QImage)));
Q_ASSERT(b);
// Update the scope for the new monitor.
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
}
void AbstractAudioScopeWidget::slotRenderZoneUpdated()
{
m_newHUDFrames.fetchAndAddRelaxed(1);
m_newScopeFrames.fetchAndAddRelaxed(1);
m_newBackgroundFrames.fetchAndAddRelaxed(1);
// qDebug() << "Monitor incoming. New frames total HUD/Scope/Background: " << m_newHUDFrames
// << "/" << m_newScopeFrames << "/" << m_newBackgroundFrames;
if (this->visibleRegion().isEmpty()) {
// qDebug() << "Scope of widget " << m_widgetName << " is not at the top, not rendering.";
} else {
if (m_aAutoRefresh->isChecked()) {
prodHUDThread();
prodScopeThread();
prodBackgroundThread();
}
}
}
void AbstractAudioScopeWidget::slotRenderZoneUpdated(QImage frame)
{
m_scopeImage = frame;
slotRenderZoneUpdated();
}
void AbstractAudioScopeWidget::slotReceiveAudio(const QVector<int16_t> sampleData, const int freq, const int num_channels, const int num_samples)
{
qDebug() << "Received audio. Size is " << (int) sampleData.size() << ".";
if (sampleData.size() > 0) {
qDebug() << sampleData.data()[0];
}
//TODO
}
void AbstractAudioScopeWidget::slotResetRealtimeFactor(bool realtimeChecked)
{
if (!realtimeChecked) {
m_accelFactorHUD = 1;
m_accelFactorScope = 1;
m_accelFactorBackground = 1;
}
}
bool AbstractAudioScopeWidget::autoRefreshEnabled()
{
return m_aAutoRefresh->isChecked();
}
void AbstractAudioScopeWidget::slotAutoRefreshToggled(bool autoRefresh)
{
if (isVisible()) emit requestAutoRefresh(autoRefresh);
// TODO only if depends on input
if (autoRefresh) {
//forceUpdate();
m_requestForcedUpdate = true;
m_activeRender->sendFrameUpdate();
}
}
/***************************************************************************
* Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) *
* This file is part of kdenlive. See www.kdenlive.org. *
* *