diff --git a/.travis.yml b/.travis.yml
index e207f875d..0c9dcdffa 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,29 +3,40 @@ compiler: gcc
sudo: require
dist: trusty
+matrix:
+ include:
+ - env: ARCH=x86_64 APPIMGTOOL=appimagetool-x86_64.AppImage
+ - env: ARCH=i386 APPIMGTOOL=appimagetool-i686.AppImage
+
before_install:
- sudo add-apt-repository ppa:beineri/opt-qt593-trusty -y
- - sudo sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y
+ - sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y
- sudo apt-get update -qq
install:
- - sudo apt-get -y install qt59base qt59multimedia libavformat-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev frei0r-plugins-dev frei0r-plugins
+ - if [ "$ARCH" == "x86_64" ]; then sudo apt-get -y install qt59base qt59multimedia libavformat-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev frei0r-plugins-dev frei0r-plugins fuse; fi
+ - if [ "$ARCH" == "i386" ]; then sudo apt-get -y install gcc-multilib g++-multilib qt59base:i386 qt59multimedia:i386 libavformat-dev:i386 libavcodec-dev:i386 libavfilter-dev:i386 libavutil-dev:i386 libswscale-dev:i386 libswresample-dev:i386 frei0r-plugins-dev:i386 frei0r-plugins:i386 pkg-config:i386 libgl1-mesa-dev:i386 fuse:i386; fi
- source /opt/qt*/bin/qt*-env.sh
script:
- - qmake CONFIG+=release PREFIX=/usr
+ - if [ "$ARCH" == "x86_64" ]; then qmake CONFIG+=release PREFIX=/usr; fi
+ - if [ "$ARCH" == "i386" ]; then qmake CONFIG+=release "QMAKE_CFLAGS+=-m32" "QMAKE_CXXFLAGS+=-m32" "QMAKE_LFLAGS+=-m32" PREFIX=/usr -spec linux-g++-32; fi
- make -j$(nproc)
- make INSTALL_ROOT=appdir -j$(nproc) install ; find appdir/
- mkdir -p appdir/usr/bin/ ; cp olive-editor appdir/usr/bin/ # FIXME; "make install" should do this
- wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
- chmod a+x linuxdeployqt-continuous-x86_64.AppImage
+ - wget -c -nv "https://github.com/AppImage/AppImageKit/releases/download/continuous/$APPIMGTOOL"
+ - chmod a+x $APPIMGTOOL
- unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
- export VERSION=$(git rev-parse --short HEAD) # linuxdeployqt uses this for naming the file
- ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/*.desktop -appimage
+ - rm Olive*.AppImage
+ - ./$APPIMGTOOL 'appdir' -n -g
after_success:
- find appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq
- - # curl --upload-file Olive*.AppImage https://transfer.sh/Olive-git.$(git rev-parse --short HEAD)-x86_64.AppImage
+ - # curl --upload-file Olive*.AppImage https://transfer.sh/Olive-git.$(git rev-parse --short HEAD)-$ARCH.AppImage
- wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
# only create release for master
- if [ "$TRAVIS_BRANCH" != "master" ]; then export TRAVIS_EVENT_TYPE=pull_request; fi
diff --git a/debug.cpp b/debug.cpp
index 1dced8017..18935b40c 100644
--- a/debug.cpp
+++ b/debug.cpp
@@ -37,31 +37,31 @@ void debug_message_handler(QtMsgType type, const QMessageLogContext &context, co
case QtDebugMsg:
fprintf(stderr, "[DEBUG] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
if (debug_file.isOpen()) debug_stream << QString("[DEBUG] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
- debug_info.prepend(QString("[DEBUG] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
+ debug_info.append(QString("[DEBUG] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
break;
case QtInfoMsg:
fprintf(stderr, "[INFO] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
if (debug_file.isOpen()) debug_stream << QString("[INFO] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
- debug_info.prepend(QString("[INFO] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
+ debug_info.append(QString("[INFO] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
break;
case QtWarningMsg:
fprintf(stderr, "[WARNING] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
if (debug_file.isOpen()) debug_stream << QString("[WARNING] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
- debug_info.prepend(QString("[WARNING] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
+ debug_info.append(QString("[WARNING] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
break;
case QtCriticalMsg:
fprintf(stderr, "[ERROR] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
if (debug_file.isOpen()) debug_stream << QString("[ERROR] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
- debug_info.prepend(QString("[ERROR] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
+ debug_info.append(QString("[ERROR] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
break;
case QtFatalMsg:
fprintf(stderr, "[FATAL] %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
if (debug_file.isOpen()) debug_stream << QString("[FATAL] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function);
- debug_info.prepend(QString("[FATAL] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
+ debug_info.append(QString("[FATAL] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
fflush(stderr);
// abort();
}
diff --git a/dialogs/debugdialog.cpp b/dialogs/debugdialog.cpp
index 9422e56c1..cb8559062 100644
--- a/dialogs/debugdialog.cpp
+++ b/dialogs/debugdialog.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include "debug.h"
@@ -20,6 +21,7 @@ DebugDialog::DebugDialog(QWidget *parent) : QDialog(parent) {
void DebugDialog::update_log() {
textEdit->setHtml(get_debug_str());
+ textEdit->verticalScrollBar()->setValue(textEdit->verticalScrollBar()->maximum());
}
void DebugDialog::showEvent(QShowEvent *) {
diff --git a/effects/internal/vsthost.cpp b/effects/internal/vsthost.cpp
index e56393fe8..bdd007ecd 100644
--- a/effects/internal/vsthost.cpp
+++ b/effects/internal/vsthost.cpp
@@ -1,6 +1,6 @@
#include "vsthost.h"
-#ifdef _WIN32
+#ifndef NOVST
// adapted from http://teragonaudio.com/article/How-to-make-your-own-VST-host.html
@@ -9,18 +9,33 @@
#include
#include
#include
+#include
#include "playback/audio.h"
#include "mainwindow.h"
#include "debug.h"
+#ifdef __linux__
+#include
+#endif
+
#define BLOCK_SIZE 512
#define CHANNEL_COUNT 2
+struct VSTRect {
+ int16_t top;
+ int16_t left;
+ int16_t bottom;
+ int16_t right;
+};
+
+#define effGetChunk 23
+#define effSetChunk 24
+
// C callbacks
extern "C" {
// Main host callback
- VstIntPtr VSTCALLBACK hostCallback(AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) {
+ intptr_t hostCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) {
switch(opcode) {
case audioMasterVersion:
return 2400;
@@ -29,7 +44,7 @@ extern "C" {
break;
case audioMasterGetCurrentProcessLevel:
return 0;
- // Handle other opcodes here... there will be lots of them
+ // handle other opcodes here... there will be lots of them
case audioMasterEndEdit: // change made
mainWindow->setWindowModified(true);
break;
@@ -44,33 +59,70 @@ extern "C" {
// Plugin's entry point
typedef AEffect *(*vstPluginFuncPtr)(audioMasterCallback host);
// Plugin's getParameter() method
-typedef float (*getParameterFuncPtr)(AEffect *effect, VstInt32 index);
+typedef float (*getParameterFuncPtr)(AEffect *effect, int32_t index);
// Plugin's setParameter() method
-typedef void (*setParameterFuncPtr)(AEffect *effect, VstInt32 index, float value);
+typedef void (*setParameterFuncPtr)(AEffect *effect, int32_t index, float value);
// Plugin's processEvents() method
-typedef VstInt32 (*processEventsFuncPtr)(VstEvents *events);
+typedef int32_t (*processEventsFuncPtr)(VstEvents *events);
// Plugin's process() method
-typedef void (*processFuncPtr)(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames);
+typedef void (*processFuncPtr)(AEffect *effect, float **inputs, float **outputs, int32_t sampleFrames);
void VSTHost::loadPlugin() {
QString dll_fn = file_field->get_filename(0, true);
+#if defined(__APPLE__)
+ bundle = BundleLoad(dll_fn);
+
+ if (bundle == NULL) {
+ QMessageBox::critical(nullptr, tr("Error loading VST plugin"), tr("Failed to create VST reference"));
+ return;
+ }
+
+ vstPluginFuncPtr mainEntryPoint = NULL;
+ mainEntryPoint = (vstPluginFuncPtr)CFBundleGetFunctionPointerForName(bundle, CFSTR("VSTPluginMain"));
+ // VST plugins previous to the 2.4 SDK used main_macho for the entry point name
+ if(mainEntryPoint == NULL) {
+ mainEntryPoint = (vstPluginFuncPtr)CFBundleGetFunctionPointerForName(bundle, CFSTR("main_macho"));
+ }
+ if(mainEntryPoint == NULL) {
+ qCritical() << "Couldn't get a pointer to VST plugin's main()";
+ BundleClose(bundle);
+ return;
+ }
+
+ plugin = mainEntryPoint(hostCallback);
+ if(plugin == NULL) {
+ qCritical() << "Plugin's main() returns null";
+ BundleClose(bundle);
+ return;
+ }
+#else
modulePtr = LibLoad(dll_fn);
if(modulePtr == nullptr) {
+ QString dll_error;
+
+#ifdef _WIN32
DWORD dll_err = GetLastError();
- qCritical() << "Failed to load VST" << dll_fn << "-" << dll_err;
+ dll_error = QString::number(dll_err);
+#elif __linux__
+ dll_error = dlerror();
+#endif
+ qCritical() << "Failed to load VST plugin" << dll_fn << "-" << dll_error;
+
+ QString msg_err = tr("Failed to load VST plugin \"%1\": %2").arg(dll_fn, dll_error);
#ifdef _WIN32
- QString msg_err = tr("Failed to load VST plugin \"%1\": %2").arg(dll_fn, QString::number(dll_err));
if (dll_err == 193) {
#ifdef _WIN64
msg_err += "\n\n" + tr("NOTE: You can't load 32-bit VST plugins into a 64-bit build of Olive. Please find a 64-bit version of this plugin or switch to a 32-bit build of Olive.");
#elif _WIN32
msg_err += "\n\n" + tr("NOTE: You can't load 64-bit VST plugins into a 32-bit build of Olive. Please find a 32-bit version of this plugin or switch to a 64-bit build of Olive.");
-#endif
#endif
}
- QMessageBox::critical(mainWindow, tr("Error loading VST plugin"), msg_err);
+#endif
+
+ QMessageBox::critical(nullptr, tr("Error loading VST plugin"), msg_err);
+
return;
}
@@ -78,12 +130,18 @@ void VSTHost::loadPlugin() {
reinterpret_cast(LibAddress(modulePtr, "VSTPluginMain"));
// Instantiate the plugin
plugin = mainEntryPoint(hostCallback);
+#endif
}
void VSTHost::freePlugin() {
if (plugin != nullptr) {
stopPlugin();
+#if defined(__APPLE__)
+ CFBundleUnloadExecutable(bundle);
+ CFRelease(bundle);
+#else
LibClose(modulePtr);
+#endif
plugin = nullptr;
}
}
@@ -225,7 +283,7 @@ void VSTHost::custom_load(QXmlStreamReader &stream) {
stream.readNext();
QByteArray b = QByteArray::fromBase64(stream.text().toUtf8());
if (plugin != nullptr) {
- dispatcher(plugin, effSetChunk, 0, VstInt32(b.size()), static_cast(b.data()), 0);
+ dispatcher(plugin, effSetChunk, 0, int32_t(b.size()), static_cast(b.data()), 0);
}
}
}
@@ -234,14 +292,26 @@ void VSTHost::save(QXmlStreamWriter &stream) {
Effect::save(stream);
if (plugin != nullptr) {
char* p = nullptr;
- VstInt32 length = VstInt32(dispatcher(plugin, effGetChunk, 0, 0, &p, 0));
+ int32_t length = int32_t(dispatcher(plugin, effGetChunk, 0, 0, &p, 0));
QByteArray b(p, length);
stream.writeTextElement("plugindata", b.toBase64());
}
}
void VSTHost::show_interface(bool show) {
- dialog->setVisible(show);
+ dialog->setVisible(show);
+
+ if (show) {
+#if defined(_WIN32)
+ dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0);
+#elif defined(__APPLE__)
+ dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0);
+#elif defined(__linux__)
+ dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->windowHandle()->winId()), 0);
+#endif
+ } else {
+ dispatcher(plugin, effEditClose, 0, 0, nullptr, 0);
+ }
}
void VSTHost::uncheck_show_button() {
@@ -254,13 +324,17 @@ void VSTHost::change_plugin() {
if (plugin != nullptr) {
if (configurePluginCallbacks()) {
startPlugin();
- dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->winId()), 0);
- ERect* eRect = nullptr;
+ VSTRect* eRect = nullptr;
plugin->dispatcher(plugin, effEditGetRect, 0, 0, &eRect, 0);
dialog->setFixedWidth(eRect->right);
dialog->setFixedHeight(eRect->bottom);
} else {
+#ifdef __APPLE__
+ CFBundleUnloadExecutable(bundle);
+ CFRelease(bundle);
+#else
LibClose(modulePtr);
+#endif
plugin = nullptr;
}
}
diff --git a/effects/internal/vsthost.h b/effects/internal/vsthost.h
index 13a99a816..25c4a2169 100644
--- a/effects/internal/vsthost.h
+++ b/effects/internal/vsthost.h
@@ -1,18 +1,17 @@
#ifndef VSTHOSTWIN_H
#define VSTHOSTWIN_H
-#ifdef _WIN32
+#ifndef NOVST
#include "project/effect.h"
#include "io/crossplatformlib.h"
-#include
+#include "include/vestige.h"
// Plugin's dispatcher function
-typedef VstIntPtr (*dispatcherFuncPtr)(AEffect *effect, VstInt32 opCode, VstInt32 index, VstInt32 value, void *ptr, float opt);
+typedef intptr_t (*dispatcherFuncPtr)(AEffect *effect, int32_t opCode, int32_t index, int32_t value, void *ptr, float opt);
-struct AEffect;
class QDialog;
class VSTHost : public Effect {
@@ -46,7 +45,11 @@ private:
float** outputs;
QDialog* dialog;
QPushButton* show_interface_btn;
+#if defined(__APPLE__)
+ CFBundleRef bundle;
+#else
ModulePtr modulePtr;
+#endif
};
#endif
diff --git a/effects/tile.frag b/effects/tile.frag
new file mode 100644
index 000000000..5c076b647
--- /dev/null
+++ b/effects/tile.frag
@@ -0,0 +1,43 @@
+#version 110
+
+varying vec2 vTexCoord;
+uniform sampler2D tex0; // scene buffer
+
+uniform float scale;
+uniform float centerx;
+uniform float centery;
+uniform bool mirrorx;
+uniform bool mirrory;
+
+void main(void) {
+ vec2 texCoord = vTexCoord;
+
+ /*vec2 coord = vec2(
+ mod(vTexCoord.x*tilecount, 1.0),
+ mod(vTexCoord.y*tilecount, 1.0)
+ );
+
+ if (mirrorx && mod(vTexCoord.x*tilecount, 2.0) > 1.0) {
+ coord.x = 1.0 - coord.x;
+ }
+
+ if (mirrory && mod(vTexCoord.y*tilecount, 2.0) > 1.0) {
+ coord.y = 1.0 - coord.y;
+ }*/
+
+ float adj_scale = scale*0.01;
+
+ vec2 scaled_coords = (vTexCoord/adj_scale);
+ vec2 coord = scaled_coords-vec2(0.5/adj_scale, 0.5/adj_scale)+vec2(0.5, 0.5)+vec2(-centerx*0.01, -centery*0.01);
+ vec2 modcoord = mod(coord, 1.0);
+
+ if (mirrorx && mod(coord.x, 2.0) > 1.0) {
+ modcoord.x = 1.0 - modcoord.x;
+ }
+
+ if (mirrory && mod(coord.y, 2.0) > 1.0) {
+ modcoord.y = 1.0 - modcoord.y;
+ }
+
+ gl_FragColor = vec4(texture2D(tex0, modcoord));
+}
\ No newline at end of file
diff --git a/effects/tile.xml b/effects/tile.xml
new file mode 100644
index 000000000..e002fd65d
--- /dev/null
+++ b/effects/tile.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/include/vestige.h b/include/vestige.h
new file mode 100755
index 000000000..41e78f7a0
--- /dev/null
+++ b/include/vestige.h
@@ -0,0 +1,342 @@
+/*
+ * vestige.h - simple header to allow VeSTige compilation and eventually work
+ *
+ * Copyright (c) 2006 Javier Serrano Polo
+ *
+ * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program (see COPYING); if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * This VeSTige header is included in this package in the good-faith
+ * belief that it has been cleanly and legally reverse engineered
+ * without reference to the official VST SDK and without its
+ * developer(s) having agreed to the VST SDK license agreement.
+ */
+
+#include
+#ifndef _VESTIGE_H
+#define _VESTIGE_H
+
+#define CCONST(a, b, c, d)( ( ( (int) a ) << 24 ) | \
+ ( ( (int) b ) << 16 ) | \
+ ( ( (int) c ) << 8 ) | \
+ ( ( (int) d ) << 0 ) )
+
+#define audioMasterAutomate 0
+#define audioMasterVersion 1
+#define audioMasterCurrentId 2
+#define audioMasterIdle 3
+#define audioMasterPinConnected 4
+// unsupported? 5
+#define audioMasterWantMidi 6
+#define audioMasterGetTime 7
+#define audioMasterProcessEvents 8
+#define audioMasterSetTime 9
+#define audioMasterTempoAt 10
+#define audioMasterGetNumAutomatableParameters 11
+#define audioMasterGetParameterQuantization 12
+#define audioMasterIOChanged 13
+#define audioMasterNeedIdle 14
+#define audioMasterSizeWindow 15
+#define audioMasterGetSampleRate 16
+#define audioMasterGetBlockSize 17
+#define audioMasterGetInputLatency 18
+#define audioMasterGetOutputLatency 19
+#define audioMasterGetPreviousPlug 20
+#define audioMasterGetNextPlug 21
+#define audioMasterWillReplaceOrAccumulate 22
+#define audioMasterGetCurrentProcessLevel 23
+#define audioMasterGetAutomationState 24
+#define audioMasterOfflineStart 25
+#define audioMasterOfflineRead 26
+#define audioMasterOfflineWrite 27
+#define audioMasterOfflineGetCurrentPass 28
+#define audioMasterOfflineGetCurrentMetaPass 29
+#define audioMasterSetOutputSampleRate 30
+// unsupported? 31
+#define audioMasterGetSpeakerArrangement 31 // deprecated in 2.4?
+#define audioMasterGetVendorString 32
+#define audioMasterGetProductString 33
+#define audioMasterGetVendorVersion 34
+#define audioMasterVendorSpecific 35
+#define audioMasterSetIcon 36
+#define audioMasterCanDo 37
+#define audioMasterGetLanguage 38
+#define audioMasterOpenWindow 39
+#define audioMasterCloseWindow 40
+#define audioMasterGetDirectory 41
+#define audioMasterUpdateDisplay 42
+#define audioMasterBeginEdit 43 //BeginGesture
+#define audioMasterEndEdit 44 //EndGesture
+#define audioMasterOpenFileSelector 45
+#define audioMasterCloseFileSelector 46 // currently unused
+#define audioMasterEditFile 47 // currently unused
+#define audioMasterGetChunkFile 48 // currently unused
+#define audioMasterGetInputSpeakerArrangement 49 // currently unused
+
+#define effFlagsHasEditor 1
+#define effFlagsCanReplacing (1 << 4) // very likely
+#define effFlagsIsSynth (1 << 8) // currently unused
+
+#define effOpen 0
+#define effClose 1 // currently unused
+#define effSetProgram 2 // currently unused
+#define effGetProgram 3 // currently unused
+#define effGetProgramName 5 // currently unused
+#define effGetParamName 8 // currently unused
+#define effSetSampleRate 10
+#define effSetBlockSize 11
+#define effMainsChanged 12
+#define effEditGetRect 13
+#define effEditOpen 14
+#define effEditClose 15
+#define effEditIdle 19
+#define effEditTop 20
+#define effProcessEvents 25
+// the next one from http://asseca.com/vst-24-specs/index.html
+#define effGetPlugCategory 35
+#define effGetEffectName 45
+#define effGetVendorString 47
+#define effGetProductString 48
+#define effGetVendorVersion 49
+#define effCanDo 51 // currently unused
+/* from http://asseca.com/vst-24-specs/efIdle.html */
+#define effIdle 53
+/* from http://asseca.com/vst-24-specs/efGetParameterProperties.html */
+#define effGetParameterProperties 56
+#define effGetVstVersion 58 // currently unused
+/* http://asseca.com/vst-24-specs/efShellGetNextPlugin.html */
+#define effShellGetNextPlugin 70
+/* The next two were gleaned from http://www.kvraudio.com/forum/printview.php?t=143587&start=0 */
+#define effStartProcess 71
+#define effStopProcess 72
+
+#define effBeginSetProgram 67
+#define effEndSetProgram 68
+
+#ifdef WORDS_BIGENDIAN
+// "VstP"
+#define kEffectMagic 0x50747356
+#else
+// "PtsV"
+#define kEffectMagic 0x56737450
+#endif
+
+#define kVstLangEnglish 1
+#define kVstMidiType 1
+
+struct RemoteVstPlugin;
+
+#define kVstTransportChanged 1
+#define kVstTransportPlaying (1 << 1)
+#define kVstTransportCycleActive (1 << 2)
+#define kVstTransportRecording (1 << 3)
+
+#define kVstAutomationWriting (1 << 6)
+#define kVstAutomationReading (1 << 7)
+
+#define kVstNanosValid (1 << 8)
+#define kVstPpqPosValid (1 << 9)
+#define kVstTempoValid (1 << 10)
+#define kVstBarsValid (1 << 11)
+#define kVstCyclePosValid (1 << 12)
+#define kVstTimeSigValid (1 << 13)
+#define kVstSmpteValid (1 << 14)
+#define kVstClockValid (1 << 15)
+
+struct _VstMidiEvent
+{
+ // 00
+ int type;
+ // 04
+ int byteSize;
+ // 08
+ int deltaSamples;
+ // 0c?
+ int flags;
+ // 10?
+ int noteLength;
+ // 14?
+ int noteOffset;
+ // 18
+ char midiData[4];
+ // 1c?
+ char detune;
+ // 1d?
+ char noteOffVelocity;
+ // 1e?
+ char reserved1;
+ // 1f?
+ char reserved2;
+};
+
+typedef struct _VstMidiEvent VstMidiEvent;
+
+
+struct _VstEvent
+{
+ char dump[sizeof (VstMidiEvent)];
+
+};
+
+typedef struct _VstEvent VstEvent;
+
+struct _VstEvents
+{
+ // 00
+ int numEvents;
+ // 04
+ void *reserved;
+ // 08
+ VstEvent * events[];
+};
+
+/* constants from http://www.rawmaterialsoftware.com/juceforum/viewtopic.php?t=3740&sid=183f74631fee71a493316735e2b9f28b */
+
+enum Vestige2StringConstants
+{
+ VestigeMaxNameLen = 64,
+ VestigeMaxLabelLen = 128,
+ VestigeMaxShortLabelLen = 8,
+ VestigeMaxCategLabelLen = 24,
+ VestigeMaxFileNameLen = 100
+};
+
+
+/* constants from http://asseca.com/vst-24-specs/efGetPlugCategory.html */
+
+enum VstPlugCategory
+{
+ kPlugCategUnknown = 0,
+ kPlugCategEffect,
+ kPlugCategSynth,
+ kPlugCategAnalysis,
+ kPlugCategMastering,
+ kPlugCategSpacializer,
+ kPlugCategRoomFx,
+ kPlugSurroundFx,
+ kPlugCategRestoration,
+ kPlugCategOfflineProcess,
+ kPlugCategShell,
+ kPlugCategGenerator,
+ kPlugCategMaxCount
+};
+
+typedef struct _VstEvents VstEvents;
+
+/* this struct taken from http://asseca.com/vst-24-specs/efGetParameterProperties.html */
+struct _VstParameterProperties
+{
+ float stepFloat; /* float step */
+ float smallStepFloat; /* small float step */
+ float largeStepFloat; /* large float step */
+ char label[64]; /* parameter label */
+ int32_t flags; /* @see VstParameterFlags */
+ int32_t minInteger; /* integer minimum */
+ int32_t maxInteger; /* integer maximum */
+ int32_t stepInteger; /* integer step */
+ int32_t largeStepInteger; /* large integer step */
+ char shortLabel[VestigeMaxShortLabelLen]; /* short label, recommended: 6 + delimiter */
+ int16_t displayIndex; /* index where this parameter should be displayed (starting with 0) */
+ int16_t category; /* 0: no category, else group index + 1 */
+ int16_t numParametersInCategory; /* number of parameters in category */
+ int16_t reserved; /* zero */
+ char categoryLabel[VestigeMaxCategLabelLen]; /* category label, e.g. "Osc 1" */
+ char future[16]; /* reserved for future use */
+};
+
+typedef struct _VstParameterProperties VstParameterProperties;
+
+/* this enum taken from http://asseca.com/vst-24-specs/efGetParameterProperties.html */
+enum VstParameterFlags
+{
+ kVstParameterIsSwitch = 1 << 0, /* parameter is a switch (on/off) */
+ kVstParameterUsesIntegerMinMax = 1 << 1, /* minInteger, maxInteger valid */
+ kVstParameterUsesFloatStep = 1 << 2, /* stepFloat, smallStepFloat, largeStepFloat valid */
+ kVstParameterUsesIntStep = 1 << 3, /* stepInteger, largeStepInteger valid */
+ kVstParameterSupportsDisplayIndex = 1 << 4, /* displayIndex valid */
+ kVstParameterSupportsDisplayCategory = 1 << 5, /* category, etc. valid */
+ kVstParameterCanRamp = 1 << 6 /* set if parameter value can ramp up/down */
+};
+
+struct _AEffect
+{
+ // Never use virtual functions!!!
+ // 00-03
+ int magic;
+ // dispatcher 04-07
+ intptr_t (* dispatcher) (struct _AEffect *, int, int, intptr_t, void *, float);
+ // process, quite sure 08-0b
+ void (* process) (struct _AEffect *, float **, float **, int);
+ // setParameter 0c-0f
+ void (* setParameter) (struct _AEffect *, int, float);
+ // getParameter 10-13
+ float (* getParameter) (struct _AEffect *, int);
+ // programs 14-17
+ int numPrograms;
+ // Params 18-1b
+ int numParams;
+ // Input 1c-1f
+ int numInputs;
+ // Output 20-23
+ int numOutputs;
+ // flags 24-27
+ int flags;
+ // Fill somewhere 28-2b
+ void *ptr1;
+ void *ptr2;
+ // Zeroes 2c-2f 30-33 34-37 38-3b
+ char empty3[4 + 4 + 4];
+ // 1.0f 3c-3f
+ float unkown_float;
+ // An object? pointer 40-43
+ void *ptr3;
+ // Zeroes 44-47
+ void *user;
+ // Id 48-4b
+ int32_t uniqueID;
+ // Version 4c-4f
+ int32_t version;
+ // processReplacing 50-53
+ void (* processReplacing) (struct _AEffect *, float **, float **, int);
+};
+
+typedef struct _AEffect AEffect;
+
+typedef struct _VstTimeInfo
+{
+ /* info from online documentation of VST provided by Steinberg */
+
+ double samplePos;
+ double sampleRate;
+ double nanoSeconds;
+ double ppqPos;
+ double tempo;
+ double barStartPos;
+ double cycleStartPos;
+ double cycleEndPos;
+ int32_t timeSigNumerator;
+ int32_t timeSigDenominator;
+ int32_t smpteOffset;
+ int32_t smpteFrameRate;
+ int32_t samplesToNextClock;
+ int32_t flags;
+
+} VstTimeInfo;
+
+typedef intptr_t (* audioMasterCallback) (AEffect *, int32_t, int32_t, intptr_t, void *, float);
+
+#endif
diff --git a/io/config.cpp b/io/config.cpp
index 5771f1916..97e1bf88c 100644
--- a/io/config.cpp
+++ b/io/config.cpp
@@ -45,10 +45,9 @@ Config::Config()
upcoming_queue_size(0.5),
upcoming_queue_type(FRAME_QUEUE_TYPE_SECONDS),
loop(true),
- pause_at_out_point(true),
seek_also_selects(false),
- effect_textbox_lines(3),
- use_software_fallback(false)
+ effect_textbox_lines(3),
+ use_software_fallback(false)
{}
void Config::load(QString path) {
@@ -158,9 +157,6 @@ void Config::load(QString path) {
} else if (stream.name() == "Loop") {
stream.readNext();
loop = (stream.text() == "1");
- } else if (stream.name() == "PauseAtOutPoint") {
- stream.readNext();
- pause_at_out_point = (stream.text() == "1");
} else if (stream.name() == "SeekAlsoSelects") {
stream.readNext();
seek_also_selects = (stream.text() == "1");
@@ -170,10 +166,10 @@ void Config::load(QString path) {
} else if (stream.name() == "EffectTextboxLines") {
stream.readNext();
effect_textbox_lines = stream.text().toInt();
- } else if (stream.name() == "UseSoftwareFallback") {
- stream.readNext();
- use_software_fallback = (stream.text() == "1");
- }
+ } else if (stream.name() == "UseSoftwareFallback") {
+ stream.readNext();
+ use_software_fallback = (stream.text() == "1");
+ }
}
}
if (stream.hasError()) {
@@ -230,11 +226,10 @@ void Config::save(QString path) {
stream.writeTextElement("UpcomingFrameQueueSize", QString::number(upcoming_queue_size));
stream.writeTextElement("UpcomingFrameQueueType", QString::number(upcoming_queue_type));
stream.writeTextElement("Loop", QString::number(loop));
- stream.writeTextElement("PauseAtOutPoint", QString::number(pause_at_out_point));
stream.writeTextElement("SeekAlsoSelects", QString::number(seek_also_selects));
stream.writeTextElement("CSSPath", css_path);
- stream.writeTextElement("EffectTextboxLines", QString::number(effect_textbox_lines));
- stream.writeTextElement("UseSoftwareFallback", QString::number(use_software_fallback));
+ stream.writeTextElement("EffectTextboxLines", QString::number(effect_textbox_lines));
+ stream.writeTextElement("UseSoftwareFallback", QString::number(use_software_fallback));
stream.writeEndElement(); // configuration
stream.writeEndDocument(); // doc
diff --git a/io/config.h b/io/config.h
index 6ab3078bc..67b728b21 100644
--- a/io/config.h
+++ b/io/config.h
@@ -59,7 +59,6 @@ struct Config {
double upcoming_queue_size;
int upcoming_queue_type;
bool loop;
- bool pause_at_out_point;
bool seek_also_selects;
QString css_path;
int effect_textbox_lines;
diff --git a/io/crossplatformlib.cpp b/io/crossplatformlib.cpp
index 217635f2a..8b4f45fe9 100644
--- a/io/crossplatformlib.cpp
+++ b/io/crossplatformlib.cpp
@@ -21,3 +21,24 @@ QStringList LibFilter() {
return {"*.so", "*.dylib"};
#endif
}
+
+#ifdef __APPLE__
+CFBundleRef BundleLoad(const QString &filename) {
+ CFStringRef bundle_str = CFStringCreateWithCString(NULL, filename.toUtf8(), kCFStringEncodingUTF8);
+ CFURLRef bundle_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundle_str, kCFURLPOSIXPathStyle, true);
+ CFBundleRef bundle = NULL;
+ if (bundle_url != NULL) {
+ bundle = CFBundleCreate(kCFAllocatorDefault, bundle_url);
+ } else {
+ qCritical() << "Failed to create VST URL";
+ }
+ CFRelease(bundle_url);
+ CFRelease(bundle_str);
+ return bundle;
+}
+
+void BundleClose(CFBundleRef bundle) {
+ CFBundleUnloadExecutable(bundle);
+ CFRelease(bundle);
+}
+#endif
diff --git a/io/crossplatformlib.h b/io/crossplatformlib.h
index d186f554b..d6c97c309 100644
--- a/io/crossplatformlib.h
+++ b/io/crossplatformlib.h
@@ -18,4 +18,12 @@
ModulePtr LibLoad(const QString& filename);
QStringList LibFilter();
+#ifdef __APPLE__
+#include
+class NSWindow;
+
+CFBundleRef BundleLoad(const QString& filename);
+void BundleClose(CFBundleRef bundle);
+#endif
+
#endif // CROSSPLATFORMLIB_H
diff --git a/io/exportthread.cpp b/io/exportthread.cpp
index f6b6dc134..a16a27ec7 100644
--- a/io/exportthread.cpp
+++ b/io/exportthread.cpp
@@ -351,7 +351,7 @@ void ExportThread::run() {
start_time = QDateTime::currentMSecsSinceEpoch();
if (audio_enabled) {
- compose_audio(nullptr, sequence, true);
+ compose_audio(nullptr, sequence, true, false);
}
if (video_enabled) {
do {
@@ -489,7 +489,7 @@ void ExportThread::run() {
}
void ExportThread::wake() {
- mutex.lock();
+ mutex.lock();
waitCond.wakeAll();
- mutex.unlock();
+ mutex.unlock();
}
diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp
index 50507896e..d65e65a82 100644
--- a/io/previewgenerator.cpp
+++ b/io/previewgenerator.cpp
@@ -59,6 +59,7 @@ void PreviewGenerator::parse_media() {
ms.preview_done = false;
ms.file_index = i;
ms.enabled = true;
+ ms.infinite_length = false;
bool append = false;
@@ -77,7 +78,6 @@ void PreviewGenerator::parse_media() {
&& fmt_ctx->streams[i]->codecpar->codec_id != AV_CODEC_ID_DNXHD) { // silly hack but this is the only scenario i've seen this
if (footage->url.contains('%')) {
// must be an image sequence
- ms.infinite_length = false;
ms.video_frame_rate = 25;
} else {
ms.infinite_length = true;
@@ -85,8 +85,6 @@ void PreviewGenerator::parse_media() {
ms.video_frame_rate = 0;
}
} else {
- ms.infinite_length = false;
-
// using ffmpeg's built-in heuristic
ms.video_frame_rate = av_q2d(av_guess_frame_rate(fmt_ctx, fmt_ctx->streams[i], nullptr));
diff --git a/mainwindow.cpp b/mainwindow.cpp
index 8862d93e5..34368b9e1 100644
--- a/mainwindow.cpp
+++ b/mainwindow.cpp
@@ -735,6 +735,7 @@ void MainWindow::setup_menus() {
playback_menu->addAction(tr("Go to Start"), this, SLOT(go_to_start()), QKeySequence("Home"))->setProperty("id", "gotostart");
playback_menu->addAction(tr("Previous Frame"), this, SLOT(prev_frame()), QKeySequence("Left"))->setProperty("id", "prevframe");
playback_menu->addAction(tr("Play/Pause"), this, SLOT(playpause()), QKeySequence("Space"))->setProperty("id", "playpause");
+ playback_menu->addAction(tr("Play In to Out"), this, SLOT(play_in_to_out()), QKeySequence("Shift+Space"))->setProperty("id", "playintoout");
playback_menu->addAction(tr("Next Frame"), this, SLOT(next_frame()), QKeySequence("Right"))->setProperty("id", "nextframe");
playback_menu->addAction(tr("Go to End"), this, SLOT(go_to_end()), QKeySequence("End"))->setProperty("id", "gotoend");
playback_menu->addSeparator();
@@ -744,9 +745,10 @@ void MainWindow::setup_menus() {
playback_menu->addAction(tr("Go to In Point"), this, SLOT(go_to_in()), QKeySequence("Shift+I"))->setProperty("id", "gotoin");
playback_menu->addAction(tr("Go to Out Point"), this, SLOT(go_to_out()), QKeySequence("Shift+O"))->setProperty("id", "gotoout");
playback_menu->addSeparator();
- playback_menu->addAction(tr("Increase Speed"), this, SLOT(go_to_out()), QKeySequence("L"))->setProperty("id", "incspeed");
- playback_menu->addAction(tr("Pause"), panel_sequence_viewer, SLOT(pause()), QKeySequence("K"))->setProperty("id", "pause");
- playback_menu->addAction(tr("Decrease Speed"), this, SLOT(go_to_in()), QKeySequence("J"))->setProperty("id", "decspeed");
+ playback_menu->addAction(tr("Decrease Speed"), this, SLOT(decrease_speed()), QKeySequence("J"))->setProperty("id", "decspeed");
+ playback_menu->addAction(tr("Pause"), this, SLOT(pause()), QKeySequence("K"))->setProperty("id", "pause");
+ playback_menu->addAction(tr("Increase Speed"), this, SLOT(increase_speed()), QKeySequence("L"))->setProperty("id", "incspeed");
+ playback_menu->addSeparator();
loop_action = playback_menu->addAction(tr("Loop"), this, SLOT(toggle_bool_action()));
loop_action->setProperty("id", "loop");
@@ -911,11 +913,6 @@ void MainWindow::setup_menus() {
set_name_and_marker->setCheckable(true);
set_name_and_marker->setData(reinterpret_cast(&config.set_name_with_marker));
- pause_at_out_point_action = tools_menu->addAction(tr("Pause At Out Point"), this, SLOT(toggle_bool_action()));
- pause_at_out_point_action->setProperty("id", "pauseoutpoint");
- pause_at_out_point_action->setCheckable(true);
- pause_at_out_point_action->setData(reinterpret_cast(&config.pause_at_out_point));
-
tools_menu->addSeparator();
no_autoscroll = tools_menu->addAction(tr("No Auto-Scroll"), this, SLOT(set_autoscroll()));
@@ -1109,6 +1106,15 @@ void MainWindow::prev_frame() {
}
}
+void MainWindow::play_in_to_out() {
+ QDockWidget* focused_panel = get_focused_panel();
+ if (focused_panel == panel_footage_viewer) {
+ panel_footage_viewer->play(true);
+ } else {
+ panel_sequence_viewer->play(true);
+ }
+}
+
void MainWindow::next_frame() {
QDockWidget* focused_panel = get_focused_panel();
if (focused_panel == panel_footage_viewer) {
@@ -1136,6 +1142,33 @@ void MainWindow::playpause() {
}
}
+void MainWindow::pause() {
+ QDockWidget* focused_panel = get_focused_panel();
+ if (focused_panel == panel_footage_viewer) {
+ panel_footage_viewer->pause();
+ } else {
+ panel_sequence_viewer->pause();
+ }
+}
+
+void MainWindow::increase_speed() {
+ QDockWidget* focused_panel = get_focused_panel();
+ if (focused_panel == panel_footage_viewer) {
+ panel_footage_viewer->increase_speed();
+ } else {
+ panel_sequence_viewer->increase_speed();
+ }
+}
+
+void MainWindow::decrease_speed() {
+ QDockWidget* focused_panel = get_focused_panel();
+ if (focused_panel == panel_footage_viewer) {
+ panel_footage_viewer->decrease_speed();
+ } else {
+ panel_sequence_viewer->decrease_speed();
+ }
+}
+
void MainWindow::prev_cut() {
QDockWidget* focused_panel = get_focused_panel();
if (sequence != nullptr && (panel_timeline == focused_panel || panel_sequence_viewer == focused_panel)) {
@@ -1222,7 +1255,6 @@ void MainWindow::toolMenu_About_To_Be_Shown() {
set_bool_action_checked(enable_drop_on_media_to_replace);
set_bool_action_checked(enable_hover_focus);
set_bool_action_checked(set_name_and_marker);
- set_bool_action_checked(pause_at_out_point_action);
set_bool_action_checked(seek_also_selects);
set_int_action_checked(no_autoscroll, config.autoscroll);
@@ -1494,6 +1526,9 @@ void MainWindow::nest() {
s->clips.append(copy);
}
+ // relink clips in new nested sequences
+ panel_timeline->relink_clips_using_ids(selected_clips, s->clips);
+
// add sequence to project
Media* m = panel_project->new_sequence(ca, s, false, nullptr);
diff --git a/mainwindow.h b/mainwindow.h
index 295834a15..9bff5dd22 100644
--- a/mainwindow.h
+++ b/mainwindow.h
@@ -41,7 +41,7 @@ public slots:
protected:
void closeEvent(QCloseEvent *);
- void paintEvent(QPaintEvent *event);
+ void paintEvent(QPaintEvent *event);
private slots:
void clear_undo_stack();
@@ -66,7 +66,11 @@ private slots:
void go_to_out();
void go_to_start();
void prev_frame();
+ void play_in_to_out();
void playpause();
+ void pause();
+ void increase_speed();
+ void decrease_speed();
void next_frame();
void go_to_end();
void prev_cut();
@@ -186,7 +190,6 @@ private:
QAction* enable_hover_focus;
QAction* set_name_and_marker;
QAction* loop_action;
- QAction* pause_at_out_point_action;
QAction* seek_also_selects;
// edit menu actions
diff --git a/olive.pro b/olive.pro
index 8ce6d9483..344dcc27e 100644
--- a/olive.pro
+++ b/olive.pro
@@ -132,7 +132,8 @@ SOURCES += \
effects/internal/frei0reffect.cpp \
project/effectloaders.cpp \
io/crossplatformlib.cpp \
- effects/internal/vsthost.cpp
+ effects/internal/vsthost.cpp \
+ ui/flowlayout.cpp
HEADERS += \
mainwindow.h \
@@ -232,7 +233,8 @@ HEADERS += \
effects/internal/frei0reffect.h \
project/effectloaders.h \
io/crossplatformlib.h \
- effects/internal/vsthost.h
+ effects/internal/vsthost.h \
+ ui/flowlayout.h
FORMS +=
@@ -242,7 +244,7 @@ win32 {
}
mac {
- LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample
+ LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -framework CoreFoundation
ICON = packaging/macos/olive.icns
INCLUDEPATH = /usr/local/include
}
diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp
index 505d4bb43..8dd574166 100644
--- a/panels/effectcontrols.cpp
+++ b/panels/effectcontrols.cpp
@@ -53,7 +53,7 @@ EffectControls::EffectControls(QWidget *parent) :
lblMultipleClipsSelected->setVisible(false);
- connect(keyframeView, SIGNAL(wheel_event_signal(QWheelEvent*)), effects_area, SLOT(receive_wheel_event(QWheelEvent*)));
+ connect(keyframeView, SIGNAL(wheel_event_signal(QWheelEvent*)), effects_area, SLOT(receive_wheel_event(QWheelEvent*)));
connect(horizontalScrollBar, SIGNAL(valueChanged(int)), headers, SLOT(set_scroll(int)));
connect(horizontalScrollBar, SIGNAL(resize_move(double)), keyframeView, SLOT(resize_move(double)));
connect(horizontalScrollBar, SIGNAL(valueChanged(int)), keyframeView, SLOT(set_x_scroll(int)));
@@ -150,6 +150,15 @@ void EffectControls::scroll_to_frame(long frame) {
scroll_to_frame_internal(horizontalScrollBar, frame, zoom, keyframeView->width());
}
+void EffectControls::add_effect_paste_action(QMenu *menu) {
+ QAction* paste_action = menu->addAction(tr("&Paste"), panel_timeline, SLOT(paste(bool)));
+ paste_action->setEnabled(clipboard.size() > 0 && clipboard_type == CLIPBOARD_TYPE_EFFECT);
+}
+
+void EffectControls::cut() {
+ copy(true);
+}
+
void EffectControls::show_effect_menu(int type, int subtype) {
effect_menu_type = type;
effect_menu_subtype = subtype;
@@ -291,6 +300,8 @@ void EffectControls::setup_ui() {
scrollAreaLayout->setMargin(0);
effects_area = new EffectsArea(scrollAreaWidgetContents);
+ effects_area->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(effects_area, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(effects_area_context_menu()));
QVBoxLayout* effects_area_layout = new QVBoxLayout(effects_area);
effects_area_layout->setSpacing(0);
@@ -441,17 +452,25 @@ void EffectControls::setup_ui() {
hlayout->addWidget(splitter);
- setWidget(contents);
+ setWidget(contents);
}
void EffectControls::update_scrollbar() {
- verticalScrollBar->setMaximum(qMax(0, effects_area->height() - keyframeView->height() - headers->height()));
- verticalScrollBar->setPageStep(verticalScrollBar->height());
+ verticalScrollBar->setMaximum(qMax(0, effects_area->height() - keyframeView->height() - headers->height()));
+ verticalScrollBar->setPageStep(verticalScrollBar->height());
}
void EffectControls::queue_post_update() {
- keyframeView->update();
- update_scrollbar();
+ keyframeView->update();
+ update_scrollbar();
+}
+
+void EffectControls::effects_area_context_menu() {
+ QMenu menu(this);
+
+ add_effect_paste_action(&menu);
+
+ menu.exec(QCursor::pos());
}
void EffectControls::load_effects() {
@@ -484,7 +503,7 @@ void EffectControls::load_effects() {
keyframeView->setEnabled(true);
headers->setVisible(true);
- QTimer::singleShot(50, this, SLOT(queue_post_update()));
+ QTimer::singleShot(50, this, SLOT(queue_post_update()));
}
}
}
@@ -544,7 +563,7 @@ void EffectControls::audio_transition_click() {
}
void EffectControls::resizeEvent(QResizeEvent*) {
- update_scrollbar();
+ update_scrollbar();
}
bool EffectControls::is_focused() {
@@ -569,5 +588,5 @@ EffectsArea::EffectsArea(QWidget* parent) :
{}
void EffectsArea::receive_wheel_event(QWheelEvent *e) {
- QApplication::sendEvent(this, e);
+ QApplication::sendEvent(this, e);
}
diff --git a/panels/effectcontrols.h b/panels/effectcontrols.h
index cce0b2dda..034d9572e 100644
--- a/panels/effectcontrols.h
+++ b/panels/effectcontrols.h
@@ -19,20 +19,19 @@ class QScrollBar;
class QHBoxLayout;
class EffectsArea : public QWidget {
- Q_OBJECT
+ Q_OBJECT
public:
- EffectsArea(QWidget* parent = 0);
+ EffectsArea(QWidget* parent = 0);
QScrollArea* parent_widget;
KeyframeView* keyframe_area;
TimelineHeader* header;
public slots:
- void receive_wheel_event(QWheelEvent* e);
+ void receive_wheel_event(QWheelEvent* e);
};
class EffectControls : public QDockWidget
{
Q_OBJECT
-
public:
explicit EffectControls(QWidget *parent = 0);
~EffectControls();
@@ -44,7 +43,6 @@ public:
void set_zoom(bool in);
bool keyframe_focus();
void delete_selected_keyframes();
- void copy(bool del);
bool multiple;
void scroll_to_frame(long frame);
@@ -56,7 +54,11 @@ public:
QScrollBar* verticalScrollBar;
QMutex effects_loaded;
+
+ void add_effect_paste_action(QMenu* menu);
public slots:
+ void cut();
+ void copy(bool del = false);
void update_keyframes();
private slots:
void menu_select(QAction* q);
@@ -68,8 +70,10 @@ private slots:
void deselect_all_effects(QWidget*);
- void update_scrollbar();
- void queue_post_update();
+ void update_scrollbar();
+ void queue_post_update();
+
+ void effects_area_context_menu();
protected:
void resizeEvent(QResizeEvent *event);
private:
diff --git a/panels/grapheditor.cpp b/panels/grapheditor.cpp
index 7d13a905d..faaf91235 100644
--- a/panels/grapheditor.cpp
+++ b/panels/grapheditor.cpp
@@ -52,7 +52,7 @@ GraphEditor::GraphEditor(QWidget* parent) : QDockWidget(parent), row(nullptr) {
right_tool_widget->setLayout(right_tool_layout);
tools->addWidget(right_tool_widget);
- keyframe_nav = new KeyframeNavigator();
+ keyframe_nav = new KeyframeNavigator(0, false);
keyframe_nav->enable_keyframes(true);
keyframe_nav->enable_keyframe_toggle(false);
left_tool_layout->addWidget(keyframe_nav);
@@ -162,8 +162,8 @@ void GraphEditor::set_row(EffectRow *r) {
slider_button->setCheckable(true);
slider_button->setChecked(field->is_enabled());
slider_button->setIcon(QIcon(":/icons/record.png"));
- slider_button->setProperty("field", i);
- slider_button->setIconSize(slider_button->iconSize()*0.5);
+ slider_button->setProperty("field", i);
+ slider_button->setIconSize(slider_button->iconSize()*0.5);
connect(slider_button, SIGNAL(toggled(bool)), this, SLOT(set_field_visibility(bool)));
slider_proxy_buttons.append(slider_button);
value_layout->addWidget(slider_button);
diff --git a/panels/timeline.cpp b/panels/timeline.cpp
index 939bc649c..84a9663c6 100644
--- a/panels/timeline.cpp
+++ b/panels/timeline.cpp
@@ -21,6 +21,7 @@
#include "ui/timelineheader.h"
#include "ui/resizablescrollbar.h"
#include "ui/audiomonitor.h"
+#include "ui/flowlayout.h"
#include "mainwindow.h"
#include "debug.h"
@@ -67,7 +68,6 @@ Timeline::Timeline(QWidget *parent) :
transition_tool_post_clip(-1),
hand_moving(false),
block_repaints(false),
- last_frame(0),
scroll(0)
{
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
@@ -442,11 +442,6 @@ void Timeline::repaint_timeline() {
if (sequence != nullptr) {
set_sb_max();
-
- if (last_frame != sequence->playhead) {
- audio_monitor->update();
- last_frame = sequence->playhead;
- }
}
}
}
@@ -490,7 +485,21 @@ void Timeline::select_from_playhead() {
}
void Timeline::resizeEvent(QResizeEvent *) {
+ // adjust maximum scrollbar
if (sequence != nullptr) set_sb_max();
+
+
+ // resize tool button widget to its contents
+ QList tool_button_children = tool_button_widget->findChildren();
+ int total_client_height = 0;
+ int horizontal_spacing = static_cast(tool_button_widget->layout())->horizontalSpacing();
+ int vertical_spacing = static_cast(tool_button_widget->layout())->verticalSpacing();
+ for (int i=0;iheight() + vertical_spacing;
+ }
+ int comp_height = tool_button_widget->height();
+ int cols = qCeil(double(total_client_height)/double(comp_height));
+ tool_button_widget->setFixedWidth((tool_button_children.at(0)->width())*cols + horizontal_spacing*(cols-1) + 1);
}
void Timeline::delete_in_out(bool ripple) {
@@ -571,8 +580,6 @@ void Timeline::delete_selection(QVector& selections, bool ripple_dele
}
}
- selections.clear();
-
undo_stack.push(ca);
update_ui(true);
@@ -634,14 +641,22 @@ void Timeline::snapping_clicked(bool checked) {
snapping = checked;
}
-Clip* Timeline::split_clip(ComboAction* ca, int p, long frame) {
- return split_clip(ca, p, frame, frame);
+Clip* Timeline::split_clip(ComboAction* ca, bool transitions, int p, long frame) {
+ return split_clip(ca, transitions, p, frame, frame);
}
-Clip* Timeline::split_clip(ComboAction* ca, int p, long frame, long post_in) {
+Clip* Timeline::split_clip(ComboAction* ca, bool transitions, int p, long frame, long post_in) {
Clip* pre = sequence->clips.at(p);
if (pre != nullptr && pre->timeline_in < frame && pre->timeline_out > frame) { // guard against attempts to split at in/out points
- Clip* post = pre->copy(sequence);
+ bool splitting_closing_dual_transition = false;
+
+ if (transitions
+ && pre->get_closing_transition() != nullptr
+ && pre->get_closing_transition()->secondary_clip != nullptr) {
+ splitting_closing_dual_transition = true;
+ }
+
+ Clip* post = pre->copy(sequence, transitions && !splitting_closing_dual_transition);
long new_clip_length = frame - pre->timeline_in;
@@ -651,11 +666,11 @@ Clip* Timeline::split_clip(ComboAction* ca, int p, long frame, long post_in) {
move_clip(ca, pre, pre->timeline_in, frame, pre->clip_in, pre->track);
if (pre->get_opening_transition() != nullptr) {
- /*if (frame < pre->timeline_in + pre->get_opening_transition()->length && pre->get_opening_transition()->secondary_clip != nullptr) {
+// if (frame < pre->timeline_in + pre->get_opening_transition()->length && pre->get_opening_transition()->secondary_clip != nullptr) {
// separate shared transition
- ca->append(new SetPointer((void**) &pre->get_opening_transition()->secondary_clip, nullptr));
- pre->get_opening_transition()->secondary_clip->closing_transition = pre->get_opening_transition()->copy(pre->get_opening_transition()->secondary_clip, nullptr);
- }*/
+// ca->append(new SetPointer((void**) &pre->get_opening_transition()->secondary_clip, nullptr));
+// pre->get_opening_transition()->secondary_clip->closing_transition = pre->get_opening_transition()->copy(pre->get_opening_transition()->secondary_clip, nullptr);
+// }
if (pre->get_opening_transition()->get_true_length() > new_clip_length) {
ca->append(new ModifyTransitionCommand(pre, TA_OPENING_TRANSITION, new_clip_length));
@@ -664,8 +679,24 @@ Clip* Timeline::split_clip(ComboAction* ca, int p, long frame, long post_in) {
post->sequence->hard_delete_transition(post, TA_OPENING_TRANSITION);
}
if (pre->get_closing_transition() != nullptr) {
- ca->append(new DeleteTransitionCommand(pre->sequence, pre->closing_transition));
- if (pre->get_closing_transition()->secondary_clip == nullptr) post->get_closing_transition()->set_length(qMin(long(post->get_closing_transition()->get_true_length()), post->getLength()));
+ if (splitting_closing_dual_transition) {
+ // just move closing transition to post clip
+
+ // WORKAROUND
+ ca->append(new DeleteTransitionCommand(pre->sequence, pre->closing_transition));
+ } else {
+ ca->append(new DeleteTransitionCommand(pre->sequence, pre->closing_transition));
+
+ if (post->get_closing_transition() != nullptr) {
+ if (pre->get_closing_transition()->secondary_clip == nullptr) {
+ post->get_closing_transition()->set_length(qMin(long(post->get_closing_transition()->get_true_length()), post->getLength()));
+ }
+
+ if (post->get_closing_transition()->get_length() > post->getLength()) {
+ post->get_closing_transition()->set_length(post->getLength());
+ }
+ }
+ }
}
return post;
@@ -695,7 +726,7 @@ bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool
QVector pre_clips;
QVector post_clips;
- Clip* post = split_clip(ca, clip, frame);
+ Clip* post = split_clip(ca, true, clip, frame);
// if alt is not down, split clips links too
if (post == nullptr) {
@@ -714,7 +745,7 @@ bool Timeline::split_clip_and_relink(ComboAction *ca, int clip, long frame, bool
Clip* link = sequence->clips.at(l);
if ((original_clip_is_selected && is_clip_selected(link, true)) || !original_clip_is_selected) {
split_cache.append(l);
- Clip* s = split_clip(ca, l, frame);
+ Clip* s = split_clip(ca, true, l, frame);
if (s != nullptr) {
pre_clips.append(l);
post_clips.append(s);
@@ -801,7 +832,7 @@ void Timeline::delete_areas_and_relink(ComboAction* ca, QVector& area
// middle of clip is within deletion area
// duplicate clip
- Clip* post = split_clip(ca, j, s.in, s.out);
+ Clip* post = split_clip(ca, true, j, s.in, s.out);
pre_clips.append(j);
post_clips.append(post);
@@ -830,8 +861,15 @@ void Timeline::delete_areas_and_relink(ComboAction* ca, QVector& area
}
}
}
+ }
+
+ // deselect selected clip areas
+ QVector area_copy = areas;
+ for (int i=0;iappend(new AddClipCommand(sequence, post_clips));
}
@@ -902,7 +940,9 @@ void Timeline::relink_clips_using_ids(QVector& old_clips, QVector& n
for (int j=0;jlinked.size();j++) {
for (int k=0;klinked.at(j) == old_clips.at(k)) {
- new_clips.at(i)->linked.append(k);
+ if (new_clips.at(i) != nullptr) {
+ new_clips.at(i)->linked.append(k);
+ }
}
}
}
@@ -1180,50 +1220,12 @@ bool Timeline::split_selection(ComboAction* ca) {
for (int i=0;iselections.size();i++) {
const Selection& s = sequence->selections.at(i);
if (s.track == clip->track) {
- if (clip->timeline_in < s.in && clip->timeline_out > s.out) {
- Clip* split_A = clip->copy(sequence);
- split_A->clip_in += (s.in - clip->timeline_in);
- split_A->timeline_in = s.in;
- split_A->timeline_out = s.out;
- pre_splits.append(j);
- post_splits.append(split_A);
-
- Clip* split_B = clip->copy(sequence);
- split_B->clip_in += (s.out - clip->timeline_in);
- split_B->timeline_in = s.out;
- secondary_post_splits.append(split_B);
-
- if (clip->get_opening_transition() != nullptr) {
- split_B->sequence->hard_delete_transition(split_B, TA_OPENING_TRANSITION);
- split_A->sequence->hard_delete_transition(split_A, TA_OPENING_TRANSITION);
- }
-
- if (clip->get_closing_transition() != nullptr) {
- ca->append(new DeleteTransitionCommand(clip->sequence, clip->closing_transition));
-
- split_A->sequence->hard_delete_transition(split_A, TA_CLOSING_TRANSITION);
- }
-
- move_clip(ca, clip, clip->timeline_in, s.in, clip->clip_in, clip->track);
- split = true;
- } else {
- Clip* post_a = split_clip(ca, j, s.in);
- Clip* post_b = split_clip(ca, j, s.out);
- if (post_a != nullptr) {
- pre_splits.append(j);
- post_splits.append(post_a);
- split = true;
- }
- if (post_b != nullptr) {
- if (post_a != nullptr) {
- pre_splits.append(j);
- post_splits.append(post_b);
- } else {
- secondary_post_splits.append(post_b);
- }
- split = true;
- }
- }
+ Clip* post_b = split_clip(ca, true, j, s.out);
+ Clip* post_a = split_clip(ca, post_b == nullptr, j, s.in);
+ pre_splits.append(j);
+ post_splits.append(post_a);
+ secondary_post_splits.append(post_b);
+ split = true;
}
}
}
@@ -1233,6 +1235,10 @@ bool Timeline::split_selection(ComboAction* ca) {
// relink after splitting
relink_clips_using_ids(pre_splits, post_splits);
relink_clips_using_ids(pre_splits, secondary_post_splits);
+
+ post_splits.removeAll(nullptr);
+ secondary_post_splits.removeAll(nullptr);
+
ca->append(new AddClipCommand(sequence, post_splits));
ca->append(new AddClipCommand(sequence, secondary_post_splits));
@@ -1267,7 +1273,7 @@ void Timeline::split_at_playhead() {
for (int j=0;jclips.size();j++) {
Clip* clip = sequence->clips.at(j);
if (clip != nullptr && is_clip_selected(clip, true)) {
- Clip* s = split_clip(ca, j, sequence->playhead);
+ Clip* s = split_clip(ca, true, j, sequence->playhead);
if (s != nullptr) {
pre_clips.append(j);
post_clips.append(s);
@@ -1596,14 +1602,15 @@ void Timeline::setup_ui() {
horizontalLayout->setSpacing(0);
horizontalLayout->setContentsMargins(0, 0, 0, 0);
- QWidget* tool_buttons = new QWidget(dockWidgetContents);
- tool_buttons->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
+ tool_button_widget = new QWidget(dockWidgetContents);
+ tool_button_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
- QVBoxLayout* tool_buttons_layout = new QVBoxLayout(tool_buttons);
+ FlowLayout* tool_buttons_layout = new FlowLayout(tool_button_widget);
+// tool_buttons_layout->setSizeConstraint(QLayout::SetNoConstraint);
tool_buttons_layout->setSpacing(4);
tool_buttons_layout->setContentsMargins(0, 0, 0, 0);
- toolArrowButton = new QPushButton(tool_buttons);
+ toolArrowButton = new QPushButton(tool_button_widget);
QIcon arrow_icon;
arrow_icon.addFile(QStringLiteral(":/icons/arrow.png"), QSize(), QIcon::Normal, QIcon::Off);
arrow_icon.addFile(QStringLiteral(":/icons/arrow-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off);
@@ -1614,7 +1621,7 @@ void Timeline::setup_ui() {
connect(toolArrowButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
tool_buttons_layout->addWidget(toolArrowButton);
- toolEditButton = new QPushButton(tool_buttons);
+ toolEditButton = new QPushButton(tool_button_widget);
QIcon icon1;
icon1.addFile(QStringLiteral(":/icons/beam.png"), QSize(), QIcon::Normal, QIcon::Off);
icon1.addFile(QStringLiteral(":/icons/beam-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off);
@@ -1625,7 +1632,7 @@ void Timeline::setup_ui() {
connect(toolEditButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
tool_buttons_layout->addWidget(toolEditButton);
- toolRippleButton = new QPushButton(tool_buttons);
+ toolRippleButton = new QPushButton(tool_button_widget);
QIcon icon2;
icon2.addFile(QStringLiteral(":/icons/ripple.png"), QSize(), QIcon::Normal, QIcon::Off);
icon2.addFile(QStringLiteral(":/icons/ripple-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off);
@@ -1636,7 +1643,7 @@ void Timeline::setup_ui() {
connect(toolRippleButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
tool_buttons_layout->addWidget(toolRippleButton);
- toolRazorButton = new QPushButton(tool_buttons);
+ toolRazorButton = new QPushButton(tool_button_widget);
QIcon icon4;
icon4.addFile(QStringLiteral(":/icons/razor.png"), QSize(), QIcon::Normal, QIcon::Off);
icon4.addFile(QStringLiteral(":/icons/razor-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off);
@@ -1647,7 +1654,7 @@ void Timeline::setup_ui() {
connect(toolRazorButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
tool_buttons_layout->addWidget(toolRazorButton);
- toolSlipButton = new QPushButton(tool_buttons);
+ toolSlipButton = new QPushButton(tool_button_widget);
QIcon icon5;
icon5.addFile(QStringLiteral(":/icons/slip.png"), QSize(), QIcon::Normal, QIcon::On);
icon5.addFile(QStringLiteral(":/icons/slip-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1658,7 +1665,7 @@ void Timeline::setup_ui() {
connect(toolSlipButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
tool_buttons_layout->addWidget(toolSlipButton);
- toolSlideButton = new QPushButton(tool_buttons);
+ toolSlideButton = new QPushButton(tool_button_widget);
QIcon icon6;
icon6.addFile(QStringLiteral(":/icons/slide.png"), QSize(), QIcon::Normal, QIcon::On);
icon6.addFile(QStringLiteral(":/icons/slide-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1669,7 +1676,7 @@ void Timeline::setup_ui() {
connect(toolSlideButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
tool_buttons_layout->addWidget(toolSlideButton);
- toolHandButton = new QPushButton(tool_buttons);
+ toolHandButton = new QPushButton(tool_button_widget);
QIcon icon7;
icon7.addFile(QStringLiteral(":/icons/hand.png"), QSize(), QIcon::Normal, QIcon::On);
icon7.addFile(QStringLiteral(":/icons/hand-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1680,7 +1687,7 @@ void Timeline::setup_ui() {
connect(toolHandButton, SIGNAL(clicked(bool)), this, SLOT(set_tool()));
tool_buttons_layout->addWidget(toolHandButton);
- toolTransitionButton = new QPushButton(tool_buttons);
+ toolTransitionButton = new QPushButton(tool_button_widget);
QIcon icon8;
icon8.addFile(QStringLiteral(":/icons/transition-tool.png"), QSize(), QIcon::Normal, QIcon::On);
icon8.addFile(QStringLiteral(":/icons/transition-tool-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1690,7 +1697,7 @@ void Timeline::setup_ui() {
connect(toolTransitionButton, SIGNAL(clicked(bool)), this, SLOT(transition_tool_click()));
tool_buttons_layout->addWidget(toolTransitionButton);
- snappingButton = new QPushButton(tool_buttons);
+ snappingButton = new QPushButton(tool_button_widget);
QIcon icon9;
icon9.addFile(QStringLiteral(":/icons/magnet.png"), QSize(), QIcon::Normal, QIcon::On);
icon9.addFile(QStringLiteral(":/icons/magnet-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1701,7 +1708,7 @@ void Timeline::setup_ui() {
connect(snappingButton, SIGNAL(toggled(bool)), this, SLOT(snapping_clicked(bool)));
tool_buttons_layout->addWidget(snappingButton);
- zoomInButton = new QPushButton(tool_buttons);
+ zoomInButton = new QPushButton(tool_button_widget);
QIcon icon10;
icon10.addFile(QStringLiteral(":/icons/zoomin.png"), QSize(), QIcon::Normal, QIcon::On);
icon10.addFile(QStringLiteral(":/icons/zoomin-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1710,7 +1717,7 @@ void Timeline::setup_ui() {
connect(zoomInButton, SIGNAL(clicked(bool)), this, SLOT(zoom_in()));
tool_buttons_layout->addWidget(zoomInButton);
- zoomOutButton = new QPushButton(tool_buttons);
+ zoomOutButton = new QPushButton(tool_button_widget);
QIcon icon11;
icon11.addFile(QStringLiteral(":/icons/zoomout.png"), QSize(), QIcon::Normal, QIcon::On);
icon11.addFile(QStringLiteral(":/icons/zoomout-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1719,7 +1726,7 @@ void Timeline::setup_ui() {
connect(zoomOutButton, SIGNAL(clicked(bool)), this, SLOT(zoom_out()));
tool_buttons_layout->addWidget(zoomOutButton);
- recordButton = new QPushButton(tool_buttons);
+ recordButton = new QPushButton(tool_button_widget);
QIcon icon12;
icon12.addFile(QStringLiteral(":/icons/record.png"), QSize(), QIcon::Normal, QIcon::On);
icon12.addFile(QStringLiteral(":/icons/record-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1729,7 +1736,7 @@ void Timeline::setup_ui() {
tool_buttons_layout->addWidget(recordButton);
- addButton = new QPushButton(tool_buttons);
+ addButton = new QPushButton(tool_button_widget);
QIcon icon13;
icon13.addFile(QStringLiteral(":/icons/add-button.png"), QSize(), QIcon::Normal, QIcon::On);
icon13.addFile(QStringLiteral(":/icons/add-button-disabled.png"), QSize(), QIcon::Disabled, QIcon::On);
@@ -1738,9 +1745,7 @@ void Timeline::setup_ui() {
connect(addButton, SIGNAL(clicked()), this, SLOT(add_btn_click()));
tool_buttons_layout->addWidget(addButton);
- tool_buttons_layout->addStretch();
-
- horizontalLayout->addWidget(tool_buttons);
+ horizontalLayout->addWidget(tool_button_widget);
timeline_area = new QWidget(dockWidgetContents);
QSizePolicy sizePolicy2(QSizePolicy::Minimum, QSizePolicy::Minimum);
@@ -1760,7 +1765,7 @@ void Timeline::setup_ui() {
editAreaLayout->setSpacing(0);
editAreaLayout->setContentsMargins(0, 0, 0, 0);
QSplitter* splitter = new QSplitter(editAreas);
- splitter->setChildrenCollapsible(false);
+ splitter->setChildrenCollapsible(false);
splitter->setOrientation(Qt::Vertical);
QWidget* videoContainer = new QWidget(splitter);
QHBoxLayout* videoContainerLayout = new QHBoxLayout(videoContainer);
diff --git a/panels/timeline.h b/panels/timeline.h
index 2254bfff9..3e1248fcc 100644
--- a/panels/timeline.h
+++ b/panels/timeline.h
@@ -78,9 +78,8 @@ public:
bool focused();
void set_zoom(bool in);
void copy(bool del);
- void paste(bool insert);
- Clip* split_clip(ComboAction* ca, int p, long frame);
- Clip* split_clip(ComboAction* ca, int p, long frame, long post_in);
+ Clip* split_clip(ComboAction* ca, bool transitions, int p, long frame);
+ Clip* split_clip(ComboAction* ca, bool transitions, int p, long frame, long post_in);
bool split_selection(ComboAction* ca);
bool split_all_clips_at_point(ComboAction *ca, long point);
bool split_clip_and_relink(ComboAction* ca, int clip, long frame, bool relink);
@@ -207,6 +206,7 @@ public:
void resizeEvent(QResizeEvent *event);
public slots:
+ void paste(bool insert = false);
void repaint_timeline();
void toggle_show_all();
void deselect();
@@ -231,7 +231,6 @@ private:
QVector tool_buttons;
void decheck_tool_buttons(QObject* sender);
void set_tool(int tool);
- long last_frame;
int scroll;
void set_sb_max();
@@ -249,6 +248,7 @@ private:
QPushButton* zoomOutButton;
QPushButton* recordButton;
QPushButton* addButton;
+ QWidget* tool_button_widget;
};
#endif // TIMELINE_H
diff --git a/panels/viewer.cpp b/panels/viewer.cpp
index c296be36e..42f05b7a7 100644
--- a/panels/viewer.cpp
+++ b/panels/viewer.cpp
@@ -44,7 +44,8 @@ Viewer::Viewer(QWidget *parent) :
seq(nullptr),
created_sequence(false),
minimum_zoom(1.0),
- cue_recording_internal(false)
+ cue_recording_internal(false),
+ playback_speed(0)
{
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
@@ -101,15 +102,20 @@ void Viewer::set_main_sequence() {
void Viewer::reset_all_audio() {
// reset all clip audio
if (seq != nullptr) {
- audio_ibuffer_frame = seq->playhead;
- audio_ibuffer_timecode = (double) audio_ibuffer_frame / seq->frame_rate;
-
+ long last_frame = 0;
for (int i=0;iclips.size();i++) {
Clip* c = seq->clips.at(i);
if (c != nullptr) {
c->reset_audio();
+ last_frame = qMax(last_frame, c->timeline_out);
}
}
+
+ audio_ibuffer_frame = seq->playhead;
+ if (playback_speed < 0) {
+ audio_ibuffer_frame = last_frame - audio_ibuffer_frame;
+ }
+ audio_ibuffer_timecode = double(audio_ibuffer_frame) / seq->frame_rate;
}
clear_audio_ibuffer();
}
@@ -242,7 +248,11 @@ bool frame_rate_is_droppable(float rate) {
void Viewer::seek(long p) {
pause();
- seq->playhead = p;
+ if (main_sequence) {
+ seq->playhead = p;
+ } else {
+ seq->playhead = qMin(seq->getEndFrame(), qMax(0L, p));
+ }
bool update_fx = false;
if (main_sequence) {
panel_timeline->scroll_to_frame(p);
@@ -324,15 +334,44 @@ void Viewer::toggle_play() {
}
}
-void Viewer::play() {
+void Viewer::increase_speed() {
+ int new_speed = playback_speed+1;
+ if (new_speed == 0) {
+ new_speed++;
+ }
+ set_playback_speed(new_speed);
+}
+
+void Viewer::decrease_speed() {
+ int new_speed = playback_speed-1;
+ if (new_speed == 0) {
+ new_speed--;
+ }
+ set_playback_speed(new_speed);
+}
+
+void Viewer::play(bool in_to_out) {
if (panel_sequence_viewer->playing) panel_sequence_viewer->pause();
if (panel_footage_viewer->playing) panel_footage_viewer->pause();
+ playing_in_to_out = in_to_out;
+
if (seq != nullptr) {
+
+ if (playing_in_to_out) {
+ uncue_recording();
+ }
+
+ bool seek_to_in = (seq->using_workarea && config.loop);
if (!is_recording_cued()
- && seq->playhead >= get_seq_out()
- && (config.loop || !main_sequence)) {
- seek(get_seq_in());
+ && (playing_in_to_out
+ || seq->playhead >= seq->getEndFrame()
+ || (seek_to_in && seq->playhead >= seq->workarea_out))) {
+ seek(seek_to_in ? seq->workarea_in : 0);
+ }
+
+ if (playback_speed == 0) {
+ playback_speed = 1;
}
reset_all_audio();
@@ -345,6 +384,7 @@ void Viewer::play() {
just_played = true;
set_playpause_icon(false);
start_msecs = QDateTime::currentMSecsSinceEpoch();
+
timer_update();
}
}
@@ -363,6 +403,7 @@ void Viewer::pause() {
just_played = false;
set_playpause_icon(true);
playback_updater.stop();
+ playback_speed = 0;
if (is_recording_cued()) {
uncue_recording();
@@ -432,6 +473,10 @@ void Viewer::update_parents(bool reload_fx) {
}
}
+int Viewer::get_playback_speed() {
+ return playback_speed;
+}
+
void Viewer::resizeEvent(QResizeEvent *) {
if (seq != nullptr) {
set_sb_max();
@@ -519,14 +564,22 @@ void Viewer::set_sb_max() {
headers->set_scrollbar_max(horizontal_bar, seq->getEndFrame(), headers->width());
}
+void Viewer::set_playback_speed(int s) {
+ pause();
+ playback_speed = qMin(3, qMax(s, -3));
+ if (playback_speed != 0) {
+ play();
+ }
+}
+
long Viewer::get_seq_in() {
- return (seq->using_workarea && seq->enable_workarea)
+ return ((config.loop || playing_in_to_out) && seq->using_workarea && seq->enable_workarea)
? seq->workarea_in
: 0;
}
long Viewer::get_seq_out() {
- return (seq->using_workarea && seq->enable_workarea && previous_playhead < seq->workarea_out)
+ return ((config.loop || playing_in_to_out) && seq->using_workarea && seq->enable_workarea && previous_playhead < seq->workarea_out)
? seq->workarea_out
: seq->getEndFrame();
}
@@ -560,7 +613,7 @@ void Viewer::setup_ui() {
QHBoxLayout* current_timecode_container_layout = new QHBoxLayout(current_timecode_container);
current_timecode_container_layout->setSpacing(0);
current_timecode_container_layout->setMargin(0);
- current_timecode_slider = new LabelSlider(current_timecode_container);
+ current_timecode_slider = new LabelSlider(current_timecode_container);
lower_control_layout->addWidget(current_timecode_container);
QWidget* playback_controls = new QWidget(lower_controls);
@@ -713,23 +766,28 @@ void Viewer::update_playhead() {
void Viewer::timer_update() {
previous_playhead = seq->playhead;
- seq->playhead = qRound(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate));
+ seq->playhead = qRound(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate * playback_speed));
if (config.seek_also_selects) panel_timeline->select_from_playhead();
update_parents(config.seek_also_selects);
- long end_frame = get_seq_out();
- if (!recording
- && playing
- && seq->playhead >= end_frame
- && previous_playhead < end_frame) {
- if (!config.pause_at_out_point && config.loop) {
- seek(get_seq_in());
- play();
- } else if (config.pause_at_out_point || !main_sequence) {
- pause();
+ if (playing) {
+ if (recording) {
+ if (recording_start != recording_end && seq->playhead >= recording_end) {
+ pause();
+ }
+ } else {
+ if (seq->playhead >= seq->getEndFrame()) {
+ pause();
+ }
+ if (seq->using_workarea && seq->playhead >= seq->workarea_out) {
+ if (config.loop) {
+ // loop
+ play();
+ } else if (playing_in_to_out) {
+ pause();
+ }
+ }
}
- } else if (recording && recording_start != recording_end && seq->playhead >= recording_end) {
- pause();
}
}
diff --git a/panels/viewer.h b/panels/viewer.h
index 85ddc063b..7181d426d 100644
--- a/panels/viewer.h
+++ b/panels/viewer.h
@@ -49,7 +49,7 @@ public:
// playback functions
void seek(long p);
- void play();
+ void play(bool in_to_out = false);
void pause();
bool playing;
long playhead_start;
@@ -67,6 +67,8 @@ public:
void reset_all_audio();
void update_parents(bool reload_fx = false);
+ int get_playback_speed();
+
ViewerWidget* viewer_widget;
Media* media;
@@ -80,6 +82,8 @@ public slots:
void go_to_in();
void previous_frame();
void toggle_play();
+ void increase_speed();
+ void decrease_speed();
void next_frame();
void go_to_out();
void go_to_end();
@@ -100,8 +104,10 @@ private:
long cached_end_frame;
QString panel_name;
double minimum_zoom;
+ bool playing_in_to_out;
void set_zoom_value(double d);
void set_sb_max();
+ void set_playback_speed(int s);
long get_seq_in();
long get_seq_out();
@@ -126,6 +132,7 @@ private:
QTimer recording_flasher;
long previous_playhead;
+ int playback_speed;
};
#endif // VIEWER_H
diff --git a/playback/audio.cpp b/playback/audio.cpp
index 87be6a646..b5abc9faf 100644
--- a/playback/audio.cpp
+++ b/playback/audio.cpp
@@ -33,7 +33,7 @@ bool audio_rendering = false;
bool recording = false;
qint8 audio_ibuffer[audio_ibuffer_size];
-unsigned long audio_ibuffer_read = 0;
+qint64 audio_ibuffer_read = 0;
long audio_ibuffer_frame = 0;
double audio_ibuffer_timecode = 0;
@@ -114,11 +114,12 @@ int current_audio_freq() {
return audio_rendering ? sequence->audio_frequency : audio_output->format().sampleRate();
}
-unsigned long get_buffer_offset_from_frame(double framerate, long frame) {
+qint64 get_buffer_offset_from_frame(double framerate, long frame) {
if (frame >= audio_ibuffer_frame) {
- return static_cast(((double(frame - audio_ibuffer_frame)/framerate)*current_audio_freq())*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO));
+ int multiplier = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
+ return qFloor((double(frame - audio_ibuffer_frame)/framerate)*current_audio_freq())*multiplier;
} else {
- qWarning() << "Invalid values passed to get_buffer_offset_from_frame";
+ qWarning() << "Invalid values passed to get_buffer_offset_from_frame" << frame << "<" << audio_ibuffer_frame;
return 0;
}
}
@@ -164,47 +165,32 @@ void AudioSenderThread::run() {
lock.unlock();
}
-int AudioSenderThread::send_audio_to_output(int offset, int max) {
+int AudioSenderThread::send_audio_to_output(qint64 offset, int max) {
// send audio to device
- unsigned long actual_write = audio_io_device->write((const char*) audio_ibuffer+offset, max);
+ qint64 actual_write = audio_io_device->write(reinterpret_cast(audio_ibuffer)+offset, max);
- unsigned long audio_ibuffer_limit = audio_ibuffer_read + actual_write;
+ qint64 audio_ibuffer_limit = audio_ibuffer_read + actual_write;
- // send samples to audio monitor cache
- // TODO make this work for the footage viewer - currently, enabling it causes crash due to an ASSERT
- Sequence* s = nullptr;
- /*if (panel_footage_viewer->playing) {
- s = panel_footage_viewer->seq;
- }*/
- if (panel_sequence_viewer->playing) {
- s = panel_sequence_viewer->seq;
- }
- if (s != nullptr) {
- if (panel_timeline->audio_monitor->sample_cache_offset == -1) {
- panel_timeline->audio_monitor->sample_cache_offset = s->playhead;
+ if (actual_write > 0) {
+ // average values and send to audio monitor
+ int channels = audio_output->format().channelCount();
+ qint64 lim = offset + actual_write;
+ QVector averages;
+ averages.resize(channels);
+ averages.fill(0);
+
+ int counter = 0;
+ qint16 sample;
+ for (qint64 i=offset;iaudio_layout);
- long sample_cache_playhead = panel_timeline->audio_monitor->sample_cache_offset + (panel_timeline->audio_monitor->sample_cache.size()/channel_count);
- unsigned long next_buffer_offset, buffer_offset_adjusted;
- int i;
- unsigned long buffer_offset = get_buffer_offset_from_frame(s->frame_rate, sample_cache_playhead);
- if (samples.size() != channel_count) samples.resize(channel_count);
- samples.fill(0);
-
- // TODO: I don't like this, but i'm not sure if there's a smarter way to do it
- while (static_cast(buffer_offset) < audio_ibuffer_limit) {
- sample_cache_playhead++;
- next_buffer_offset = qMin(get_buffer_offset_from_frame(s->frame_rate, sample_cache_playhead), static_cast(audio_ibuffer_limit));
- while (buffer_offset < next_buffer_offset) {
- for (i=0;iaudio_monitor->sample_cache.append(samples);
- buffer_offset = next_buffer_offset;
+ for (int i=0;iaudio_monitor->set_value(averages);
}
memset(audio_ibuffer+offset, 0, actual_write);
@@ -214,6 +200,11 @@ int AudioSenderThread::send_audio_to_output(int offset, int max) {
return actual_write;
}
+double log_volume(double linear) {
+ // expects a value between 0 and 1 (or more if amplifying)
+ return (qExp(linear)-1)/(M_E-1);
+}
+
void int32_to_char_array(qint32 i, char* array) {
memcpy(array, &i, 4);
}
@@ -278,14 +269,14 @@ void write_wave_trailer(QFile& f) {
f.seek(4);
// 4 bytes for total file size - 8 bytes
- qint32 file_size = f.size() - 8;
+ qint32 file_size = qint32(f.size()) - 8;
int32_to_char_array(file_size, arr);
f.write(arr, 4);
f.seek(40);
// 4 bytes for data chunk size (file size - header)
- file_size = f.size() - 44;
+ file_size = qint32(f.size()) - 44;
int32_to_char_array(file_size, arr);
f.write(arr, 4);
}
diff --git a/playback/audio.h b/playback/audio.h
index 980641854..8ca487fa2 100644
--- a/playback/audio.h
+++ b/playback/audio.h
@@ -27,9 +27,11 @@ public slots:
void notifyReceiver();
private:
QVector samples;
- int send_audio_to_output(int offset, int max);
+ int send_audio_to_output(qint64 offset, int max);
};
+double log_volume(double linear);
+
extern QAudioOutput* audio_output;
extern QIODevice* audio_io_device;
extern AudioSenderThread* audio_thread;
@@ -37,7 +39,7 @@ extern QMutex audio_write_lock;
#define audio_ibuffer_size 192000
extern qint8 audio_ibuffer[audio_ibuffer_size];
-extern unsigned long audio_ibuffer_read;
+extern qint64 audio_ibuffer_read;
extern long audio_ibuffer_frame;
extern double audio_ibuffer_timecode;
extern bool audio_scrub;
@@ -51,7 +53,7 @@ bool is_audio_device_set();
void init_audio();
void stop_audio();
-unsigned long get_buffer_offset_from_frame(double framerate, long frame);
+qint64 get_buffer_offset_from_frame(double framerate, long frame);
bool start_recording();
void stop_recording();
diff --git a/playback/cacher.cpp b/playback/cacher.cpp
index f7deb4334..7d7f294ca 100644
--- a/playback/cacher.cpp
+++ b/playback/cacher.cpp
@@ -86,11 +86,14 @@ void apply_audio_effects(Clip* c, double timecode_start, AVFrame* frame, int nb_
#define AUDIO_BUFFER_PADDING 2048
-void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
+void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests, int playback_speed) {
long timeline_in = c->get_timeline_in_with_transition();
long timeline_out = c->get_timeline_out_with_transition();
long target_frame = c->audio_target_frame;
+ bool temp_reverse = (playback_speed < 0);
+ bool reverse_audio = (c->reverse != temp_reverse);
+
long frame_skip = 0;
double last_fr = c->sequence->frame_rate;
if (!nests.isEmpty()) {
@@ -113,6 +116,17 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
}
}
+ if (temp_reverse) {
+ long seq_end = sequence->getEndFrame();
+ timeline_in = seq_end - timeline_in;
+ timeline_out = seq_end - timeline_out;
+ target_frame = seq_end - target_frame;
+
+ long temp = timeline_in;
+ timeline_in = timeline_out;
+ timeline_out = temp;
+ }
+
while (true) {
AVFrame* frame;
int nb_bytes = INT_MAX;
@@ -147,7 +161,7 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
if (!c->reached_end) {
int loop = 0;
- if (c->reverse && !c->audio_just_reset) {
+ if (reverse_audio && !c->audio_just_reset) {
avcodec_flush_buffers(c->codecCtx);
c->reached_end = false;
int64_t backtrack_seek = qMax(c->reverse_target - static_cast(av_q2d(av_inv_q(c->stream->time_base))), static_cast(0));
@@ -177,7 +191,7 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
dout << "reached EOF while reading";
#endif
// TODO revise usage of reached_end in audio
- if (!c->reverse) {
+ if (!reverse_audio) {
c->reached_end = true;
} else {
}
@@ -198,11 +212,11 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
#ifdef AUDIOWARNINGS
dout << "reached EOF while pulling from filtergraph";
#endif
- if (!c->reverse) break;
+ if (!reverse_audio) break;
}
}
- if (c->reverse) {
+ if (reverse_audio) {
if (loop > 1) {
AVFrame* rev_frame = c->queue.at(1);
if (ret != AVERROR_EOF) {
@@ -313,7 +327,7 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->reverse_target * timebase);
dout << "fsi-calc:" << c->frame_sample_index;
#endif
- if (c->reverse) c->frame_sample_index = nb_bytes - c->frame_sample_index;
+ if (reverse_audio) c->frame_sample_index = nb_bytes - c->frame_sample_index;
c->audio_just_reset = false;
}
@@ -343,7 +357,7 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
}
}
- if (c->reverse) frame = c->queue.at(1);
+ if (reverse_audio) frame = c->queue.at(1);
#ifdef AUDIOWARNINGS
dout << "j" << c->frame_sample_index << nb_bytes;
@@ -364,24 +378,31 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
if (frame->nb_samples == 0) {
break;
} else {
- unsigned long buffer_timeline_out = get_buffer_offset_from_frame(c->sequence->frame_rate, timeline_out);
+ qint64 buffer_timeline_out = get_buffer_offset_from_frame(c->sequence->frame_rate, timeline_out);
audio_write_lock.lock();
+ int sample_skip = 4*qMax(0, qAbs(playback_speed)-1);
+ int sample_byte_size = av_get_bytes_per_sample(static_cast(frame->format));
+
while (c->frame_sample_index < nb_bytes
&& c->audio_buffer_write < audio_ibuffer_read+(audio_ibuffer_size>>1)
&& c->audio_buffer_write < buffer_timeline_out) {
- int upper_byte_index = (c->audio_buffer_write+1)%audio_ibuffer_size;
- int lower_byte_index = (c->audio_buffer_write)%audio_ibuffer_size;
- qint16 old_sample = static_cast((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF));
- qint16 new_sample = static_cast((frame->data[0][c->frame_sample_index+1] & 0xFF) << 8 | (frame->data[0][c->frame_sample_index] & 0xFF));
- qint16 mixed_sample = mix_audio_sample(old_sample, new_sample);
+ for (int i=0;ichannels;i++) {
+ int upper_byte_index = (c->audio_buffer_write+1)%audio_ibuffer_size;
+ int lower_byte_index = (c->audio_buffer_write)%audio_ibuffer_size;
+ qint16 old_sample = static_cast((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF));
+ qint16 new_sample = static_cast((frame->data[0][c->frame_sample_index+1] & 0xFF) << 8 | (frame->data[0][c->frame_sample_index] & 0xFF));
+ qint16 mixed_sample = mix_audio_sample(old_sample, new_sample);
- audio_ibuffer[upper_byte_index] = quint8((mixed_sample >> 8) & 0xFF);
- audio_ibuffer[lower_byte_index] = quint8(mixed_sample & 0xFF);
+ audio_ibuffer[upper_byte_index] = quint8((mixed_sample >> 8) & 0xFF);
+ audio_ibuffer[lower_byte_index] = quint8(mixed_sample & 0xFF);
- c->audio_buffer_write+=2;
- c->frame_sample_index+=2;
+ c->audio_buffer_write+=sample_byte_size;
+ c->frame_sample_index+=sample_byte_size;
+ }
+
+ c->frame_sample_index += sample_skip;
if (c->audio_reset) break;
}
@@ -398,7 +419,7 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) {
if (audio_thread != nullptr) audio_thread->notifyReceiver();
}
- if (c->frame_sample_index == nb_bytes) {
+ if (c->frame_sample_index >= nb_bytes) {
c->frame_sample_index = -1;
} else {
// assume we have no more data to send
@@ -544,7 +565,7 @@ void cache_video_worker(Clip* c, long playhead) {
}
}
-void reset_cache(Clip* c, long target_frame) {
+void reset_cache(Clip* c, long target_frame, int playback_speed) {
// if we seek to a whole other place in the timeline, we'll need to reset the cache with new values
if (c->media == nullptr) {
if (c->track >= 0) {
@@ -607,9 +628,12 @@ void reset_cache(Clip* c, long target_frame) {
// seek (target_frame represents timeline timecode in frames, not clip timecode)
+// bool reverse = (c->reverse != temp_reverse);
+
int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame));
- if (c->reverse) {
+ bool temp_reverse = (playback_speed < 0);
+ if (c->reverse != temp_reverse) {
c->reverse_target = timestamp;
timestamp -= av_q2d(av_inv_q(c->stream->time_base));
#ifdef AUDIOWARNINGS
@@ -790,7 +814,8 @@ void open_clip_worker(Clip* clip) {
// set up cache
clip->queue.append(av_frame_alloc());
- if (clip->reverse) {
+// if (clip->reverse) {
+ if (true) {
AVFrame* reverse_frame = av_frame_alloc();
reverse_frame->format = sample_format;
@@ -886,22 +911,22 @@ void open_clip_worker(Clip* clip) {
qInfo() << "Clip opened on track" << clip->track;
}
-void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QVector nests) {
+void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QVector nests, int playback_speed) {
if (reset) {
// note: for video, playhead is in "internal clip" frames - for audio, it's the timeline playhead
- reset_cache(clip, playhead);
+ reset_cache(clip, playhead, playback_speed);
clip->audio_reset = false;
}
if (clip->media == nullptr) {
if (clip->track >= 0) {
- cache_audio_worker(clip, scrubbing, nests);
+ cache_audio_worker(clip, scrubbing, nests, playback_speed);
}
} else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) {
if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
cache_video_worker(clip, playhead);
} else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
- cache_audio_worker(clip, scrubbing, nests);
+ cache_audio_worker(clip, scrubbing, nests, playback_speed);
}
}
}
@@ -936,16 +961,18 @@ void Cacher::run() {
clip->open = true;
caching = true;
interrupt = false;
+ queued = false;
open_clip_worker(clip);
while (caching) {
- clip->can_cache.wait(&clip->lock);
+ if (!queued) clip->can_cache.wait(&clip->lock);
+ queued = false;
if (!caching) {
break;
} else {
while (true) {
- cache_clip_worker(clip, playhead, reset, scrubbing, nests);
+ cache_clip_worker(clip, playhead, reset, scrubbing, nests, playback_speed);
if (clip->multithreaded && clip->cacher->interrupt && clip->track < 0) {
clip->cacher->interrupt = false;
} else {
diff --git a/playback/cacher.h b/playback/cacher.h
index ad6060681..e06939c72 100644
--- a/playback/cacher.h
+++ b/playback/cacher.h
@@ -11,15 +11,17 @@ class Cacher : public QThread
// Q_OBJECT
public:
Cacher(Clip* c);
- void run();
+ void run();
bool caching;
// must be set before caching
long playhead;
bool reset;
- bool scrubbing;
- bool interrupt;
+ bool scrubbing;
+ bool interrupt;
+ bool queued;
+ int playback_speed;
QVector nests;
private:
@@ -27,7 +29,7 @@ private:
};
void open_clip_worker(Clip* clip);
-void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QVector nest);
+void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QVector nest, int playback_speed);
void close_clip_worker(Clip* clip);
#endif // CACHER_H
diff --git a/playback/playback.cpp b/playback/playback.cpp
index 4495023e3..9eb56fcfc 100644
--- a/playback/playback.cpp
+++ b/playback/playback.cpp
@@ -61,10 +61,13 @@ void open_clip(Clip* clip, bool multithreaded) {
}
} else {
clip->open = true;
+ clip->finished_opening = true;
}
}
void close_clip(Clip* clip, bool wait) {
+ clip->finished_opening = false;
+
// destroy opengl texture in main thread
if (clip->texture != nullptr) {
delete clip->texture;
@@ -94,25 +97,28 @@ void close_clip(Clip* clip, bool wait) {
close_clip_worker(clip);
}
} else {
- if (clip->media != nullptr && clip->media->get_type() == MEDIA_TYPE_SEQUENCE)
+ if (clip->media != nullptr && clip->media->get_type() == MEDIA_TYPE_SEQUENCE) {
closeActiveClips(clip->media->to_sequence());
+ }
clip->open = false;
}
}
-void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector& nests) {
+void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector& nests, int playback_speed) {
if (clip_uses_cacher(clip)) {
if (clip->multithreaded) {
clip->cacher->playhead = playhead;
clip->cacher->reset = reset;
clip->cacher->nests = nests;
clip->cacher->scrubbing = scrubbing;
+ clip->cacher->playback_speed = playback_speed;
+ clip->cacher->queued = true;
if (reset && clip->queue.size() > 0) clip->cacher->interrupt = true;
clip->can_cache.wakeAll();
} else {
- cache_clip_worker(clip, playhead, reset, scrubbing, nests);
+ cache_clip_worker(clip, playhead, reset, scrubbing, nests, playback_speed);
}
}
}
@@ -260,13 +266,13 @@ void get_clip_frame(Clip* c, long playhead, bool& texture_failed) {
uint8_t* data_buffer_1 = target_frame->data[0];
uint8_t* data_buffer_2 = nullptr;
- int frame_size;
+ size_t frame_size;
for (int i=0;ieffects.size();i++) {
Effect* e = c->effects.at(i);
if (e->enable_image && e->is_enabled()) {
if (data_buffer_1 == target_frame->data[0]) {
- frame_size = target_frame->linesize[0]*target_frame->height;
+ frame_size = size_t(target_frame->linesize[0])*size_t(target_frame->height);
data_buffer_1 = new uint8_t[frame_size];
data_buffer_2 = new uint8_t[frame_size];
@@ -278,7 +284,7 @@ void get_clip_frame(Clip* c, long playhead, bool& texture_failed) {
}
}
- c->texture->setData(0, get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8, using_db_1 ? data_buffer_1 : data_buffer_2);
+ c->texture->setData(get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8, const_cast(using_db_1 ? data_buffer_1 : data_buffer_2));
if (data_buffer_1 != target_frame->data[0]) {
delete [] data_buffer_1;
@@ -292,7 +298,7 @@ void get_clip_frame(Clip* c, long playhead, bool& texture_failed) {
// get more frames
QVector empty;
- if (cache) cache_clip(c, playhead, reset, false, empty);
+ if (cache) cache_clip(c, playhead, reset, false, empty, false);
}
}
@@ -385,8 +391,8 @@ void closeActiveClips(Sequence *s) {
if (c != nullptr) {
if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
closeActiveClips(c->media->to_sequence());
- if (c->open) close_clip(c, true);
- } else if (c->open) {
+ if (c->finished_opening) close_clip(c, true);
+ } else if (c->finished_opening) {
close_clip(c, true);
}
}
diff --git a/playback/playback.h b/playback/playback.h
index 2c0a6cd17..2758e9ead 100644
--- a/playback/playback.h
+++ b/playback/playback.h
@@ -12,12 +12,10 @@ struct AVFrame;
long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate);
bool clip_uses_cacher(Clip* clip);
void open_clip(Clip* clip, bool multithreaded);
-void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector &nests);
+void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector &nests, int playback_speed);
void close_clip(Clip* clip, bool wait);
-void cache_audio_worker(Clip* c, bool write_A);
-void cache_video_worker(Clip* c, long playhead);
void handle_media(Sequence* sequence, long playhead, bool multithreaded);
-void reset_cache(Clip* c, long target_frame);
+void reset_cache(Clip* c, long target_frame, int playback_speed);
void get_clip_frame(Clip* c, long playhead, bool &texture_failed);
double get_timecode(Clip* c, long playhead);
diff --git a/project/clip.cpp b/project/clip.cpp
index f57213b2f..57041333d 100644
--- a/project/clip.cpp
+++ b/project/clip.cpp
@@ -44,7 +44,7 @@ Clip::Clip(Sequence* s) :
reset();
}
-Clip* Clip::copy(Sequence* s) {
+Clip* Clip::copy(Sequence* s, bool duplicate_transitions) {
Clip* copy = new Clip(s);
copy->enabled = enabled;
@@ -69,8 +69,10 @@ Clip* Clip::copy(Sequence* s) {
copy->cached_fr = (this->sequence == nullptr) ? cached_fr : this->sequence->frame_rate;
- if (get_opening_transition() != nullptr && get_opening_transition()->secondary_clip == nullptr) copy->opening_transition = get_opening_transition()->copy(copy, nullptr);
- if (get_closing_transition() != nullptr && get_closing_transition()->secondary_clip == nullptr) copy->closing_transition = get_closing_transition()->copy(copy, nullptr);
+ if (duplicate_transitions) {
+ if (get_opening_transition() != nullptr && get_opening_transition()->secondary_clip == nullptr) copy->opening_transition = get_opening_transition()->copy(copy, nullptr);
+ if (get_closing_transition() != nullptr && get_closing_transition()->secondary_clip == nullptr) copy->closing_transition = get_closing_transition()->copy(copy, nullptr);
+ }
copy->recalculateMaxLength();
diff --git a/project/clip.h b/project/clip.h
index 99e896472..a2f284d80 100644
--- a/project/clip.h
+++ b/project/clip.h
@@ -35,7 +35,7 @@ struct Clip
{
Clip(Sequence* s);
~Clip();
- Clip* copy(Sequence* s);
+ Clip* copy(Sequence* s, bool duplicate_transitions = true);
void reset_audio();
void reset();
void refresh();
@@ -127,7 +127,7 @@ struct Clip
// audio playback variables
int64_t reverse_target;
int frame_sample_index;
- unsigned long audio_buffer_write;
+ qint64 audio_buffer_write;
bool audio_reset;
bool audio_just_reset;
long audio_target_frame;
diff --git a/project/effect.cpp b/project/effect.cpp
index ddf2748ad..650935174 100644
--- a/project/effect.cpp
+++ b/project/effect.cpp
@@ -16,6 +16,7 @@
#include "io/path.h"
#include "mainwindow.h"
#include "io/math.h"
+#include "io/clipboard.h"
#include "transition.h"
#include "effects/internal/transformeffect.h"
@@ -28,7 +29,7 @@
#include "effects/internal/paneffect.h"
#include "effects/internal/shakeeffect.h"
#include "effects/internal/cornerpineffect.h"
-#ifdef _WIN32
+#ifndef NOVST
#include "effects/internal/vsthost.h"
#endif
#include "effects/internal/fillleftrighteffect.h"
@@ -64,7 +65,7 @@ Effect* create_effect(Clip* c, const EffectMeta* em) {
case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em);
case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em);
case EFFECT_INTERNAL_FILLLEFTRIGHT: return new FillLeftRightEffect(c, em);
-#ifdef _WIN32
+#ifndef NOVST
case EFFECT_INTERNAL_VST: return new VSTHost(c, em);
#endif
#ifndef NOFREI0R
@@ -377,20 +378,24 @@ void Effect::show_context_menu(const QPoint& pos) {
int index = get_index_in_clip();
+ menu.addAction(tr("Cu&t"), panel_effect_controls, SLOT(cut()));
+ menu.addAction(tr("&Copy"), panel_effect_controls, SLOT(copy(bool)));
+
+ panel_effect_controls->add_effect_paste_action(&menu);
+
+ menu.addSeparator();
+
if (index > 0) {
- QAction* move_up = menu.addAction(tr("Move &Up"));
- connect(move_up, SIGNAL(triggered(bool)), this, SLOT(move_up()));
+ menu.addAction(tr("Move &Up"), this, SLOT(move_up()));
}
if (index < parent_clip->effects.size() - 1) {
- QAction* move_down = menu.addAction(tr("Move &Down"));
- connect(move_down, SIGNAL(triggered(bool)), this, SLOT(move_down()));
+ menu.addAction(tr("Move &Down"), this, SLOT(move_down()));
}
menu.addSeparator();
- QAction* del_action = menu.addAction(tr("D&elete"));
- connect(del_action, SIGNAL(triggered(bool)), this, SLOT(delete_self()));
+ menu.addAction(tr("D&elete"), this, SLOT(delete_self()));
menu.exec(container->title_bar->mapToGlobal(pos));
}
@@ -693,7 +698,7 @@ void Effect::startEffect() {
void Effect::endEffect() {
if (bound) glslProgram->release();
- bound = false;
+ bound = false;
}
void Effect::process_image(double, uint8_t *, uint8_t *, int){}
@@ -912,8 +917,3 @@ qint16 mix_audio_sample(qint16 a, qint16 b) {
mixed_sample = qMax(qMin(mixed_sample, static_cast(INT16_MAX)), static_cast(INT16_MIN));
return static_cast(mixed_sample);
}
-
-double log_volume(double linear) {
- // expects a value between 0 and 1 (or more if amplifying)
- return (qExp(linear)-1)/(M_E-1);
-}
diff --git a/project/effectloaders.cpp b/project/effectloaders.cpp
index b434eda86..716145695 100644
--- a/project/effectloaders.cpp
+++ b/project/effectloaders.cpp
@@ -34,7 +34,7 @@ void load_internal_effects() {
em.internal = EFFECT_INTERNAL_PAN;
effects.append(em);
-#ifdef _WIN32
+#ifndef NOVST
em.name = "VST Plugin 2.x";
em.internal = EFFECT_INTERNAL_VST;
effects.append(em);
@@ -214,6 +214,9 @@ void load_frei0r_effects() {
effect_dirs.prepend(QDir::homePath() + "/.frei0r-1/lib");
#endif
+ QString env_path(qgetenv("FREI0R_PATH"));
+ if (!env_path.isEmpty()) effect_dirs.append(env_path);
+
QVector loaded_names;
// search for paths
diff --git a/project/undo.cpp b/project/undo.cpp
index f37e13793..d16b481c4 100644
--- a/project/undo.cpp
+++ b/project/undo.cpp
@@ -209,7 +209,7 @@ SetTimelineInOutCommand::SetTimelineInOutCommand(Sequence *s, bool enabled, long
void SetTimelineInOutCommand::undo() {
seq->using_workarea = old_enabled;
- seq->enable_workarea = old_workarea_enabled;
+ seq->enable_workarea = old_workarea_enabled;
seq->workarea_in = old_in;
seq->workarea_out = old_out;
@@ -226,11 +226,11 @@ void SetTimelineInOutCommand::undo() {
void SetTimelineInOutCommand::redo() {
old_enabled = seq->using_workarea;
- old_workarea_enabled = seq->enable_workarea;
+ old_workarea_enabled = seq->enable_workarea;
old_in = seq->workarea_in;
old_out = seq->workarea_out;
- if (!seq->using_workarea) seq->enable_workarea = true;
+ if (!seq->using_workarea) seq->enable_workarea = true;
seq->using_workarea = new_enabled;
seq->workarea_in = new_in;
seq->workarea_out = new_out;
diff --git a/project/undo.h b/project/undo.h
index aaae9a016..c4fbf7619 100644
--- a/project/undo.h
+++ b/project/undo.h
@@ -174,7 +174,7 @@ public:
private:
Sequence* seq;
- bool old_workarea_enabled;
+ bool old_workarea_enabled;
bool old_enabled;
long old_in;
diff --git a/ui/audiomonitor.cpp b/ui/audiomonitor.cpp
index 12738aa4b..114e745b0 100644
--- a/ui/audiomonitor.cpp
+++ b/ui/audiomonitor.cpp
@@ -9,6 +9,8 @@
#include
#include
+#include
+
#define AUDIO_MONITOR_PEAK_HEIGHT 15
#define AUDIO_MONITOR_GAP 3
@@ -16,79 +18,61 @@ extern "C" {
#include "libavformat/avformat.h"
}
-AudioMonitor::AudioMonitor(QWidget *parent) : QWidget(parent) {
- reset();
+AudioMonitor::AudioMonitor(QWidget *parent) :
+ QWidget(parent)
+{
+ clear_timer.setInterval(500);
+ connect(&clear_timer, SIGNAL(timeout()), this, SLOT(clear()));
}
-void AudioMonitor::reset() {
- sample_cache_offset = -1;
- sample_cache.clear();
- update();
+void AudioMonitor::set_value(const QVector &ivalues) {
+ values = ivalues;
+ update();
+
+ QMetaObject::invokeMethod(&clear_timer, "start", Qt::QueuedConnection);
+}
+
+void AudioMonitor::clear() {
+ clear_timer.stop();
+
+ values.fill(1);
+ update();
}
void AudioMonitor::resizeEvent(QResizeEvent *e) {
- gradient = QLinearGradient(QPoint(0, rect().top()), QPoint(0, rect().bottom()));
- gradient.setColorAt(0, Qt::red);
- gradient.setColorAt(0.25, Qt::yellow);
- gradient.setColorAt(1, Qt::green);
- QWidget::resizeEvent(e);
+ gradient = QLinearGradient(QPoint(0, rect().top()), QPoint(0, rect().bottom()));
+ gradient.setColorAt(0, Qt::red);
+ gradient.setColorAt(0.25, Qt::yellow);
+ gradient.setColorAt(1, Qt::green);
+ QWidget::resizeEvent(e);
}
void AudioMonitor::paintEvent(QPaintEvent *) {
- if (sequence != nullptr) {
- QPainter p(this);
- int channel_x = AUDIO_MONITOR_GAP;
- int channel_count = av_get_channel_layout_nb_channels(sequence->audio_layout);
- /*if (peaks.size() != channel_count) {
- peaks.resize(channel_count);
- peaks.fill(false);
- }*/
- int channel_width = (width()/channel_count) - AUDIO_MONITOR_GAP;
- long playhead_offset = -1;
- int i;
- for (i=0;i 0) {
+ QPainter p(this);
+ int channel_x = AUDIO_MONITOR_GAP;
+ int channel_count = values.size();
+ int channel_width = (width()/channel_count) - AUDIO_MONITOR_GAP;
+ int i;
+ for (i=0;i 0) {
- playhead_offset = (sequence->playhead - sample_cache_offset) * channel_count;
- if (playhead_offset >= 0 && playhead_offset < sample_cache.size()) {
- double multiplier = 1 - qAbs((double) sample_cache.at(playhead_offset+i) / 32768.0); // 16-bit int divided to float
- /*if (multiplier == (double) 0) {
- peaks[i] = true;
- }*/
- r.setHeight(r.height()*multiplier);
- } else {
- reset();
- }
- }
+ bool peak = false;
- /*QRect peak_rect(channel_x, 0, channel_width, AUDIO_MONITOR_PEAK_HEIGHT);
- if (peaks.at(i)) {
- p.fillRect(peak_rect, QColor(255, 0, 0));
- } else {
- p.fillRect(peak_rect, QColor(64, 0, 0));
- }*/
+ r.setHeight(qRound(r.height()*(values.at(i))));
+ peak = (r.height() == 0);
- p.fillRect(r, QColor(0, 0, 0, 160));
+ QRect peak_rect(channel_x, 0, channel_width, AUDIO_MONITOR_PEAK_HEIGHT);
+ if (peak) {
+ p.fillRect(peak_rect, QColor(255, 0, 0));
+ } else {
+ p.fillRect(peak_rect, QColor(64, 0, 0));
+ }
- channel_x += channel_width + AUDIO_MONITOR_GAP;
- }
- if (playhead_offset > -1) {
- // clean up used samples
- bool error = false;
- while (sample_cache_offset < sequence->playhead && !error) {
- sample_cache_offset++;
- for (i=0;i
+#include
class AudioMonitor : public QWidget
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit AudioMonitor(QWidget *parent = 0);
- QVector sample_cache;
- long sample_cache_offset;
- void reset();
+ explicit AudioMonitor(QWidget *parent = 0);
+ void set_value(const QVector& values);
protected:
- void paintEvent(QPaintEvent *);
- void resizeEvent(QResizeEvent *);
+ void paintEvent(QPaintEvent *);
+ void resizeEvent(QResizeEvent *);
signals:
public slots:
private:
- QLinearGradient gradient;
-// QVector peaks;
+ QLinearGradient gradient;
+ QVector values;
+ QTimer clear_timer;
+
+private slots:
+ void clear();
};
#endif // AUDIOMONITOR_H
diff --git a/ui/flowlayout.cpp b/ui/flowlayout.cpp
new file mode 100644
index 000000000..243fd5099
--- /dev/null
+++ b/ui/flowlayout.cpp
@@ -0,0 +1,200 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include
+
+#include "flowlayout.h"
+FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
+ : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
+{
+ setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
+ : m_hSpace(hSpacing), m_vSpace(vSpacing)
+{
+ setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::~FlowLayout()
+{
+ QLayoutItem *item;
+ while ((item = takeAt(0)))
+ delete item;
+}
+
+void FlowLayout::addItem(QLayoutItem *item)
+{
+ itemList.append(item);
+}
+
+int FlowLayout::horizontalSpacing() const
+{
+ if (m_hSpace >= 0) {
+ return m_hSpace;
+ } else {
+ return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
+ }
+}
+
+int FlowLayout::verticalSpacing() const
+{
+ if (m_vSpace >= 0) {
+ return m_vSpace;
+ } else {
+ return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
+ }
+}
+
+int FlowLayout::count() const
+{
+ return itemList.size();
+}
+
+QLayoutItem *FlowLayout::itemAt(int index) const
+{
+ return itemList.value(index);
+}
+
+QLayoutItem *FlowLayout::takeAt(int index)
+{
+ if (index >= 0 && index < itemList.size())
+ return itemList.takeAt(index);
+ else
+ return 0;
+}
+
+Qt::Orientations FlowLayout::expandingDirections() const
+{
+ return 0;
+}
+
+bool FlowLayout::hasHeightForWidth() const
+{
+ return true;
+}
+
+int FlowLayout::heightForWidth(int width) const
+{
+ int height = doLayout(QRect(0, 0, width, 0), true);
+ return height;
+}
+
+void FlowLayout::setGeometry(const QRect &rect)
+{
+ QLayout::setGeometry(rect);
+ doLayout(rect, false);
+}
+
+QSize FlowLayout::sizeHint() const
+{
+ return minimumSize();
+}
+
+QSize FlowLayout::minimumSize() const
+{
+ QSize size;
+ QLayoutItem *item;
+ foreach (item, itemList)
+ size = size.expandedTo(item->minimumSize());
+
+ size += QSize(2*margin(), 2*margin());
+ return size;
+}
+
+int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
+{
+ int left, top, right, bottom;
+ getContentsMargins(&left, &top, &right, &bottom);
+ QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
+ int x = effectiveRect.x();
+ int y = effectiveRect.y();
+ int lineHeight = 0;
+
+ QLayoutItem *item;
+ foreach (item, itemList) {
+ QWidget *wid = item->widget();
+ int spaceX = horizontalSpacing();
+ if (spaceX == -1)
+ spaceX = wid->style()->layoutSpacing(
+ QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
+ int spaceY = verticalSpacing();
+ if (spaceY == -1)
+ spaceY = wid->style()->layoutSpacing(
+ QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
+ int nextX = x + item->sizeHint().width() + spaceX;
+ if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
+ x = effectiveRect.x();
+ y = y + lineHeight + spaceY;
+ nextX = x + item->sizeHint().width() + spaceX;
+ lineHeight = 0;
+ }
+
+ if (!testOnly)
+ item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
+
+ x = nextX;
+ lineHeight = qMax(lineHeight, item->sizeHint().height());
+ }
+ return y + lineHeight - rect.y() + bottom;
+}
+int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
+{
+ QObject *parent = this->parent();
+ if (!parent) {
+ return -1;
+ } else if (parent->isWidgetType()) {
+ QWidget *pw = static_cast(parent);
+ return pw->style()->pixelMetric(pm, 0, pw);
+ } else {
+ return static_cast(parent)->spacing();
+ }
+}
diff --git a/ui/flowlayout.h b/ui/flowlayout.h
new file mode 100644
index 000000000..5531318a1
--- /dev/null
+++ b/ui/flowlayout.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef FLOWLAYOUT_H
+#define FLOWLAYOUT_H
+
+#include
+#include
+#include
+class FlowLayout : public QLayout
+{
+public:
+ explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
+ explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
+ ~FlowLayout();
+
+ void addItem(QLayoutItem *item) override;
+ int horizontalSpacing() const;
+ int verticalSpacing() const;
+ Qt::Orientations expandingDirections() const override;
+ bool hasHeightForWidth() const override;
+ int heightForWidth(int) const override;
+ int count() const override;
+ QLayoutItem *itemAt(int index) const override;
+ QSize minimumSize() const override;
+ void setGeometry(const QRect &rect) override;
+ QSize sizeHint() const override;
+ QLayoutItem *takeAt(int index) override;
+
+private:
+ int doLayout(const QRect &rect, bool testOnly) const;
+ int smartSpacing(QStyle::PixelMetric pm) const;
+
+ QList itemList;
+ int m_hSpace;
+ int m_vSpace;
+};
+
+#endif // FLOWLAYOUT_H
diff --git a/ui/graphview.cpp b/ui/graphview.cpp
index 9fb8377f1..7b1783714 100644
--- a/ui/graphview.cpp
+++ b/ui/graphview.cpp
@@ -708,8 +708,6 @@ void GraphView::mouseReleaseEvent(QMouseEvent *) {
}
void GraphView::wheelEvent(QWheelEvent *event) {
- dout << x_scroll << y_scroll;
-
bool redraw = false;
bool shift = (event->modifiers() & Qt::ShiftModifier); // scroll instead of zoom
diff --git a/ui/keyframenavigator.cpp b/ui/keyframenavigator.cpp
index 228314d4c..baa87fc12 100644
--- a/ui/keyframenavigator.cpp
+++ b/ui/keyframenavigator.cpp
@@ -5,41 +5,44 @@
#include
#include
-KeyframeNavigator::KeyframeNavigator(QWidget *parent) : QWidget(parent) {
+KeyframeNavigator::KeyframeNavigator(QWidget *parent, bool addLeftPad) : QWidget(parent) {
key_controls = new QHBoxLayout();
key_controls->setSpacing(0);
key_controls->setMargin(0);
- key_controls->addStretch();
+
+ if (addLeftPad) {
+ key_controls->addStretch();
+ }
setLayout(key_controls);
left_key_nav = new QPushButton();
- left_key_nav->setIcon(QIcon(":/icons/tri-left.png"));
- left_key_nav->setIconSize(left_key_nav->iconSize()*0.5);
+ left_key_nav->setIcon(QIcon(":/icons/tri-left.png"));
+ left_key_nav->setIconSize(left_key_nav->iconSize()*0.5);
left_key_nav->setVisible(false);
key_controls->addWidget(left_key_nav);
connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_previous_key()));
connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked()));
key_addremove = new QPushButton();
- key_addremove->setIcon(QIcon(":/icons/diamond.png"));
- key_addremove->setIconSize(key_addremove->iconSize()*0.5);
+ key_addremove->setIcon(QIcon(":/icons/diamond.png"));
+ key_addremove->setIconSize(key_addremove->iconSize()*0.5);
key_addremove->setVisible(false);
key_controls->addWidget(key_addremove);
connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(toggle_key()));
connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(clicked()));
right_key_nav = new QPushButton();
- right_key_nav->setIcon(QIcon(":/icons/tri-right.png"));
- right_key_nav->setIconSize(right_key_nav->iconSize()*0.5);
+ right_key_nav->setIcon(QIcon(":/icons/tri-right.png"));
+ right_key_nav->setIconSize(right_key_nav->iconSize()*0.5);
right_key_nav->setVisible(false);
key_controls->addWidget(right_key_nav);
connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_next_key()));
connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked()));
keyframe_enable = new QPushButton(QIcon(":/icons/clock.png"), "");
- keyframe_enable->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
- keyframe_enable->setIconSize(keyframe_enable->iconSize()*0.75);
+ keyframe_enable->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
+ keyframe_enable->setIconSize(keyframe_enable->iconSize()*0.75);
keyframe_enable->setCheckable(true);
keyframe_enable->setToolTip(tr("Enable Keyframes"));
connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(keyframe_enabled_changed(bool)));
diff --git a/ui/keyframenavigator.h b/ui/keyframenavigator.h
index d81f87f26..ec2b645b8 100644
--- a/ui/keyframenavigator.h
+++ b/ui/keyframenavigator.h
@@ -10,7 +10,7 @@ class KeyframeNavigator : public QWidget
{
Q_OBJECT
public:
- KeyframeNavigator(QWidget* parent = 0);
+ KeyframeNavigator(QWidget* parent = 0, bool addLeftPad = true);
~KeyframeNavigator();
void enable_keyframes(bool);
void enable_keyframe_toggle(bool);
diff --git a/ui/renderfunctions.cpp b/ui/renderfunctions.cpp
index c719acdda..bbab2bd2b 100644
--- a/ui/renderfunctions.cpp
+++ b/ui/renderfunctions.cpp
@@ -119,7 +119,8 @@ GLuint compose_sequence(Viewer* viewer,
bool render_audio,
Effect** gizmos,
bool& texture_failed,
- bool rendering) {
+ bool rendering,
+ int playback_speed) {
GLint current_fbo = 0;
if (video) {
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
@@ -169,7 +170,7 @@ GLuint compose_sequence(Viewer* viewer,
}
clip_is_active = true;
if (c->track >= 0) audio_track_count++;
- } else if (c->open) {
+ } else if (c->finished_opening) {
close_clip(c, false);
}
} else {
@@ -181,7 +182,7 @@ GLuint compose_sequence(Viewer* viewer,
if (is_clip_active(c, playhead)) {
if (!c->open) open_clip(c, !rendering);
clip_is_active = true;
- } else if (c->open) {
+ } else if (c->finished_opening) {
close_clip(c, false);
}
}
@@ -238,7 +239,7 @@ GLuint compose_sequence(Viewer* viewer,
c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
c->texture->allocateStorage(get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8);
}
- get_clip_frame(c, playhead, texture_failed);
+ get_clip_frame(c, qMax(playhead, c->timeline_in), texture_failed);
textureID = c->texture->textureId();
break;
case MEDIA_TYPE_SEQUENCE:
@@ -258,6 +259,7 @@ GLuint compose_sequence(Viewer* viewer,
c->fbo = new QOpenGLFramebufferObject* [2];
c->fbo[0] = new QOpenGLFramebufferObject(video_width, video_height);
c->fbo[1] = new QOpenGLFramebufferObject(video_width, video_height);
+ ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
}
// clear fbos
@@ -268,6 +270,7 @@ GLuint compose_sequence(Viewer* viewer,
glClear(GL_COLOR_BUFFER_BIT);
c->fbo[1]->release();*/
+
bool fbo_switcher = false;
glViewport(0, 0, video_width, video_height);
@@ -284,7 +287,7 @@ GLuint compose_sequence(Viewer* viewer,
// for nested sequences
if (c->media->get_type()== MEDIA_TYPE_SEQUENCE) {
nests.append(c);
- textureID = compose_sequence(viewer, ctx, seq, nests, video, render_audio, gizmos, texture_failed, rendering);
+ textureID = compose_sequence(viewer, ctx, seq, nests, video, render_audio, gizmos, texture_failed, rendering, false);
nests.removeLast();
fbo_switcher = true;
}
@@ -351,20 +354,6 @@ GLuint compose_sequence(Viewer* viewer,
}
// EFFECT CODE END
- /*if (!nests.isEmpty()) {
- nests.last()->fbo[0]->bind();
- glViewport(0, 0, s->width, s->height);
- } else if (rendering) {
- glViewport(0, 0, s->width, s->height);
- } else {
- int widget_width = width();
- int widget_height = height();
-
- widget_width *= QApplication::desktop()->devicePixelRatio();
- widget_height *= QApplication::desktop()->devicePixelRatio();
-
- glViewport(0, 0, widget_width, widget_height);
- }*/
if (!nests.isEmpty()) {
nests.last()->fbo[0]->bind();
}
@@ -449,15 +438,15 @@ GLuint compose_sequence(Viewer* viewer,
motion_blur_prog++;*/
}
} else {
- if (render_audio || (config.enable_audio_scrubbing && audio_scrub)) {
+ if (render_audio || (config.enable_audio_scrubbing && audio_scrub && seq->playhead > c->timeline_in)) {
if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
nests.append(c);
- compose_sequence(viewer, ctx, seq, nests, video, render_audio, gizmos, texture_failed, rendering);
+ compose_sequence(viewer, ctx, seq, nests, video, render_audio, gizmos, texture_failed, rendering, playback_speed);
nests.removeLast();
} else {
if (c->lock.tryLock()) {
// clip is not caching, start caching audio
- cache_clip(c, playhead, c->audio_reset, !render_audio, nests);
+ cache_clip(c, playhead, c->audio_reset, !render_audio, nests, playback_speed);
c->lock.unlock();
}
}
@@ -496,8 +485,8 @@ GLuint compose_sequence(Viewer* viewer,
return 0;
}
-void compose_audio(Viewer* viewer, Sequence* seq, bool render_audio) {
+void compose_audio(Viewer* viewer, Sequence* seq, bool render_audio, int playback_speed) {
QVector nests;
bool texture_failed;
- compose_sequence(viewer, nullptr, seq, nests, false, render_audio, nullptr, texture_failed, audio_rendering);
+ compose_sequence(viewer, nullptr, seq, nests, false, render_audio, nullptr, texture_failed, audio_rendering, playback_speed);
}
diff --git a/ui/renderfunctions.h b/ui/renderfunctions.h
index de405bf8c..6523a16ba 100644
--- a/ui/renderfunctions.h
+++ b/ui/renderfunctions.h
@@ -17,9 +17,10 @@ GLuint compose_sequence(Viewer* viewer,
bool render_audio,
Effect **gizmos,
bool &texture_failed,
- bool rendering);
+ bool rendering,
+ int playback_speed);
-void compose_audio(Viewer* viewer, Sequence* seq, bool render_audio);
+void compose_audio(Viewer* viewer, Sequence* seq, bool render_audio, int playback_speed);
void viewport_render();
diff --git a/ui/renderthread.cpp b/ui/renderthread.cpp
index c1ead032d..a4ee4da23 100644
--- a/ui/renderthread.cpp
+++ b/ui/renderthread.cpp
@@ -110,7 +110,7 @@ void RenderThread::paint() {
gizmos = nullptr;
QVector nests;
- compose_sequence(nullptr, ctx, seq, nests, true, false, &gizmos, texture_failed, false);
+ compose_sequence(nullptr, ctx, seq, nests, true, false, &gizmos, texture_failed, false, temp_reverse);
if (!save_fn.isEmpty()) {
if (texture_failed) {
@@ -138,12 +138,14 @@ void RenderThread::paint() {
glDisable(GL_TEXTURE_2D);
}
-void RenderThread::start_render(QOpenGLContext *share, Sequence *s, const QString& save, GLvoid* pixels, int idivider) {
+void RenderThread::start_render(QOpenGLContext *share, Sequence *s, const QString& save, GLvoid* pixels, int idivider, bool itemp_reverse) {
seq = s;
// stall any dependent actions
texture_failed = true;
+ temp_reverse = itemp_reverse;
+
if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) {
share_ctx = share;
delete_ctx();
@@ -158,6 +160,7 @@ void RenderThread::start_render(QOpenGLContext *share, Sequence *s, const QStrin
pixel_buffer = pixels;
queued = true;
+
waitCond.wakeAll();
}
diff --git a/ui/renderthread.h b/ui/renderthread.h
index cd4f1aec3..54eecd646 100644
--- a/ui/renderthread.h
+++ b/ui/renderthread.h
@@ -22,7 +22,7 @@ public:
GLuint texColorBuffer;
Effect* gizmos;
void paint();
- void start_render(QOpenGLContext* share, Sequence* s, const QString &save = nullptr, GLvoid *pixels = nullptr, int idivider = 0);
+ void start_render(QOpenGLContext* share, Sequence* s, const QString &save = nullptr, GLvoid *pixels = nullptr, int idivider = 0, bool itemp_reverse = false);
bool did_texture_fail();
void cancel();
@@ -47,6 +47,7 @@ private:
bool queued;
bool texture_failed;
bool running;
+ bool temp_reverse;
QString save_fn;
GLvoid *pixel_buffer;
};
diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp
index afdd2c0fa..a989fa8f6 100644
--- a/ui/timelinewidget.cpp
+++ b/ui/timelinewidget.cpp
@@ -22,6 +22,7 @@
#include "dialogs/newsequencedialog.h"
#include "mainwindow.h"
#include "ui/rectangleselect.h"
+#include "playback/playback.h"
#include "debug.h"
#include "project/effect.h"
@@ -548,6 +549,14 @@ void TimelineWidget::mouseDoubleClickEvent(QMouseEvent *event) {
sequence->selections.append(s);
update_ui(false);
}
+ } else if (panel_timeline->tool == TIMELINE_TOOL_POINTER) {
+ int clip_index = getClipIndexFromCoords(panel_timeline->cursor_frame, panel_timeline->cursor_track);
+ if (clip_index >= 0) {
+ Clip* c = sequence->clips.at(clip_index);
+ if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) {
+ set_sequence(c->media->to_sequence());
+ }
+ }
}
}
diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp
index 50530edf9..196968d58 100644
--- a/ui/viewerwidget.cpp
+++ b/ui/viewerwidget.cpp
@@ -230,7 +230,7 @@ void ViewerWidget::frame_update() {
}
// render the audio
- compose_audio(viewer, viewer->seq, render_audio);
+ compose_audio(viewer, viewer->seq, render_audio, viewer->get_playback_speed());
}
}