Merge remote-tracking branch 'upstream/master' into pull_requests
This commit is contained in:
+15
-4
@@ -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
|
||||
|
||||
@@ -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("<b>[DEBUG]</b> %1 (%2:%3, %4)<br>").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
|
||||
debug_info.append(QString("<b>[DEBUG]</b> %1 (%2:%3, %4)<br>").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("<b>[INFO]</b> %1 (%2:%3, %4)<br>").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
|
||||
debug_info.append(QString("<b>[INFO]</b> %1 (%2:%3, %4)<br>").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("<font color='yellow'><b>[WARNING]</b> %1 (%2:%3, %4)</font><br>").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
|
||||
debug_info.append(QString("<font color='yellow'><b>[WARNING]</b> %1 (%2:%3, %4)</font><br>").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("<font color='red'><b>[ERROR]</b> %1 (%2:%3, %4)</font><br>").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
|
||||
debug_info.append(QString("<font color='red'><b>[ERROR]</b> %1 (%2:%3, %4)</font><br>").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("<font color='red'><b>[FATAL]</b> %1 (%2:%3, %4)</font><br>").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
|
||||
debug_info.append(QString("<font color='red'><b>[FATAL]</b> %1 (%2:%3, %4)</font><br>").arg(localMsg.constData(), context.file, QString::number(context.line), context.function));
|
||||
fflush(stderr);
|
||||
// abort();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QScrollBar>
|
||||
|
||||
#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 *) {
|
||||
|
||||
@@ -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 <QMessageBox>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamWriter>
|
||||
#include <QWindow>
|
||||
|
||||
#include "playback/audio.h"
|
||||
#include "mainwindow.h"
|
||||
#include "debug.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <X11/X.h>
|
||||
#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<vstPluginFuncPtr>(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<void*>(b.data()), 0);
|
||||
dispatcher(plugin, effSetChunk, 0, int32_t(b.size()), static_cast<void*>(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<HWND>(dialog->windowHandle()->winId()), 0);
|
||||
#elif defined(__APPLE__)
|
||||
dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast<NSWindow*>(dialog->windowHandle()->winId()), 0);
|
||||
#elif defined(__linux__)
|
||||
dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast<void*>(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<HWND>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
#ifndef VSTHOSTWIN_H
|
||||
#define VSTHOSTWIN_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOVST
|
||||
|
||||
#include "project/effect.h"
|
||||
|
||||
#include "io/crossplatformlib.h"
|
||||
|
||||
#include <vst/aeffectx.h>
|
||||
#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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<effect name="Tile" category="Distort">
|
||||
<row name="Scale">
|
||||
<field type="double" min="0" default="50" id="scale"/>
|
||||
</row>
|
||||
<row name="Center">
|
||||
<field type="double" default="0" id="centerx"/>
|
||||
<field type="double" default="0" id="centery"/>
|
||||
</row>
|
||||
<row name="Mirror X">
|
||||
<field type="bool" default="0" id="mirrorx"/>
|
||||
</row>
|
||||
<row name="Mirror Y">
|
||||
<field type="bool" default="0" id="mirrory"/>
|
||||
</row>
|
||||
<shader vert="common.vert" frag="tile.frag"/>
|
||||
</effect>
|
||||
Executable
+342
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* vestige.h - simple header to allow VeSTige compilation and eventually work
|
||||
*
|
||||
* Copyright (c) 2006 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
#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
|
||||
+8
-13
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,4 +18,12 @@
|
||||
ModulePtr LibLoad(const QString& filename);
|
||||
QStringList LibFilter();
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
class NSWindow;
|
||||
|
||||
CFBundleRef BundleLoad(const QString& filename);
|
||||
void BundleClose(CFBundleRef bundle);
|
||||
#endif
|
||||
|
||||
#endif // CROSSPLATFORMLIB_H
|
||||
|
||||
+3
-3
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
+44
-9
@@ -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<quintptr>(&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<quintptr>(&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);
|
||||
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+11
-7
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
+92
-87
@@ -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<QWidget*> tool_button_children = tool_button_widget->findChildren<QWidget*>();
|
||||
int total_client_height = 0;
|
||||
int horizontal_spacing = static_cast<FlowLayout*>(tool_button_widget->layout())->horizontalSpacing();
|
||||
int vertical_spacing = static_cast<FlowLayout*>(tool_button_widget->layout())->verticalSpacing();
|
||||
for (int i=0;i<tool_button_children.size();i++) {
|
||||
total_client_height += tool_button_children.at(i)->height() + 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<Selection>& 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<int> pre_clips;
|
||||
QVector<Clip*> 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<Selection>& 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<Selection>& area
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deselect selected clip areas
|
||||
QVector<Selection> area_copy = areas;
|
||||
for (int i=0;i<area_copy.size();i++) {
|
||||
const Selection& s = area_copy.at(i);
|
||||
deselect_area(s.in, s.out, s.track);
|
||||
}
|
||||
|
||||
relink_clips_using_ids(pre_clips, post_clips);
|
||||
ca->append(new AddClipCommand(sequence, post_clips));
|
||||
}
|
||||
@@ -902,7 +940,9 @@ void Timeline::relink_clips_using_ids(QVector<int>& old_clips, QVector<Clip*>& n
|
||||
for (int j=0;j<oc->linked.size();j++) {
|
||||
for (int k=0;k<old_clips.size();k++) { // find clip with that ID
|
||||
if (oc->linked.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;i<sequence->selections.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;j<sequence->clips.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);
|
||||
|
||||
+4
-4
@@ -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<QPushButton*> 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
|
||||
|
||||
+83
-25
@@ -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;i<seq->clips.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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -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
|
||||
|
||||
+33
-42
@@ -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<unsigned long>(((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<const char*>(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<double> averages;
|
||||
averages.resize(channels);
|
||||
averages.fill(0);
|
||||
|
||||
int counter = 0;
|
||||
qint16 sample;
|
||||
for (qint64 i=offset;i<lim;i+=2) {
|
||||
sample = qint16(((audio_ibuffer[i+1] & 0xFF) << 8) | (audio_ibuffer[i] & 0xFF));
|
||||
averages[counter] = qMax((double(sample)/32768.0), averages[counter]);
|
||||
counter = (counter+1)%channels;
|
||||
}
|
||||
int channel_count = av_get_channel_layout_nb_channels(s->audio_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<unsigned long>(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<unsigned long>(audio_ibuffer_limit));
|
||||
while (buffer_offset < next_buffer_offset) {
|
||||
for (i=0;i<samples.size();i++) {
|
||||
buffer_offset_adjusted = buffer_offset%audio_ibuffer_size;
|
||||
samples[i] = qMax(qAbs(qint16(((audio_ibuffer[buffer_offset_adjusted+1] & 0xFF) << 8) | (audio_ibuffer[buffer_offset_adjusted] & 0xFF))), samples[i]);
|
||||
buffer_offset += 2;
|
||||
}
|
||||
}
|
||||
panel_timeline->audio_monitor->sample_cache.append(samples);
|
||||
buffer_offset = next_buffer_offset;
|
||||
for (int i=0;i<channels;i++) {
|
||||
averages[i] = log_volume(1.0-(averages[i]));
|
||||
}
|
||||
|
||||
panel_timeline->audio_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);
|
||||
}
|
||||
|
||||
+5
-3
@@ -27,9 +27,11 @@ public slots:
|
||||
void notifyReceiver();
|
||||
private:
|
||||
QVector<qint16> 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();
|
||||
|
||||
+54
-27
@@ -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<Clip*>& nests) {
|
||||
void cache_audio_worker(Clip* c, bool scrubbing, QVector<Clip*>& 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<Clip*>& 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<Clip*>& 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<int64_t>(av_q2d(av_inv_q(c->stream->time_base))), static_cast<int64_t>(0));
|
||||
@@ -177,7 +191,7 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector<Clip*>& 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<Clip*>& 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<Clip*>& 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<Clip*>& 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<Clip*>& 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<AVSampleFormat>(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<qint16>((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF));
|
||||
qint16 new_sample = static_cast<qint16>((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;i<frame->channels;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<qint16>((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF));
|
||||
qint16 new_sample = static_cast<qint16>((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<Clip*>& 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<Clip*> nests) {
|
||||
void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QVector<Clip*> 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 {
|
||||
|
||||
+6
-4
@@ -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<Clip*> 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<Clip *> nest);
|
||||
void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QVector<Clip *> nest, int playback_speed);
|
||||
void close_clip_worker(Clip* clip);
|
||||
|
||||
#endif // CACHER_H
|
||||
|
||||
+15
-9
@@ -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<Clip*>& nests) {
|
||||
void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector<Clip*>& 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;i<c->effects.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<const uint8_t*>(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<Clip*> 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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -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<Clip *> &nests);
|
||||
void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector<Clip *> &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);
|
||||
|
||||
|
||||
+5
-3
@@ -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();
|
||||
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+14
-14
@@ -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<qint32>(INT16_MAX)), static_cast<qint32>(INT16_MIN));
|
||||
return static_cast<qint16>(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);
|
||||
}
|
||||
|
||||
@@ -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<QString> loaded_names;
|
||||
|
||||
// search for paths
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ public:
|
||||
private:
|
||||
Sequence* seq;
|
||||
|
||||
bool old_workarea_enabled;
|
||||
bool old_workarea_enabled;
|
||||
|
||||
bool old_enabled;
|
||||
long old_in;
|
||||
|
||||
+47
-63
@@ -9,6 +9,8 @@
|
||||
#include <QLinearGradient>
|
||||
#include <QtMath>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<double> &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<channel_count;i++) {
|
||||
QRect r(channel_x, AUDIO_MONITOR_PEAK_HEIGHT + AUDIO_MONITOR_GAP, channel_width, height());
|
||||
p.fillRect(r, gradient);
|
||||
if (sequence != nullptr && values.size() > 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<channel_count;i++) {
|
||||
QRect r(channel_x, AUDIO_MONITOR_PEAK_HEIGHT + AUDIO_MONITOR_GAP, channel_width, height());
|
||||
p.fillRect(r, gradient);
|
||||
|
||||
if (sample_cache_offset != -1 && sample_cache.size() > 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<channel_count;i++) {
|
||||
if (sample_cache.isEmpty()) {
|
||||
reset();
|
||||
error = true;
|
||||
break;
|
||||
} else {
|
||||
sample_cache.removeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
p.fillRect(r, QColor(0, 0, 0, 160));
|
||||
|
||||
channel_x += channel_width + AUDIO_MONITOR_GAP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-9
@@ -2,27 +2,30 @@
|
||||
#define AUDIOMONITOR_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTimer>
|
||||
|
||||
class AudioMonitor : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AudioMonitor(QWidget *parent = 0);
|
||||
QVector<qint16> sample_cache;
|
||||
long sample_cache_offset;
|
||||
void reset();
|
||||
explicit AudioMonitor(QWidget *parent = 0);
|
||||
void set_value(const QVector<double>& values);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *);
|
||||
void resizeEvent(QResizeEvent *);
|
||||
void paintEvent(QPaintEvent *);
|
||||
void resizeEvent(QResizeEvent *);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
private:
|
||||
QLinearGradient gradient;
|
||||
// QVector<bool> peaks;
|
||||
QLinearGradient gradient;
|
||||
QVector<double> values;
|
||||
QTimer clear_timer;
|
||||
|
||||
private slots:
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif // AUDIOMONITOR_H
|
||||
|
||||
@@ -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 <QtWidgets>
|
||||
|
||||
#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<QWidget *>(parent);
|
||||
return pw->style()->pixelMetric(pm, 0, pw);
|
||||
} else {
|
||||
return static_cast<QLayout *>(parent)->spacing();
|
||||
}
|
||||
}
|
||||
@@ -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 <QLayout>
|
||||
#include <QRect>
|
||||
#include <QStyle>
|
||||
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<QLayoutItem *> itemList;
|
||||
int m_hSpace;
|
||||
int m_vSpace;
|
||||
};
|
||||
|
||||
#endif // FLOWLAYOUT_H
|
||||
@@ -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
|
||||
|
||||
|
||||
+13
-10
@@ -5,41 +5,44 @@
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
|
||||
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)));
|
||||
|
||||
@@ -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);
|
||||
|
||||
+13
-24
@@ -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<Clip*> 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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
+5
-2
@@ -110,7 +110,7 @@ void RenderThread::paint() {
|
||||
|
||||
gizmos = nullptr;
|
||||
QVector<Clip*> 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();
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user