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()); } }