Files
oak-editor/project/effect.cpp
T

925 lines
29 KiB
C++

#include "effect.h"
#include "panels/panels.h"
#include "panels/viewer.h"
#include "ui/viewerwidget.h"
#include "ui/collapsiblewidget.h"
#include "panels/project.h"
#include "project/undo.h"
#include "project/sequence.h"
#include "project/clip.h"
#include "panels/timeline.h"
#include "panels/effectcontrols.h"
#include "ui/checkboxex.h"
#include "debug.h"
#include "io/path.h"
#include "mainwindow.h"
#include "io/math.h"
#include "transition.h"
#include "effects/internal/transformeffect.h"
#include "effects/internal/texteffect.h"
#include "effects/internal/solideffect.h"
#include "effects/internal/audionoiseeffect.h"
#include "effects/internal/toneeffect.h"
#include "effects/internal/volumeeffect.h"
#include "effects/internal/paneffect.h"
#include "effects/internal/shakeeffect.h"
#include "effects/internal/cornerpineffect.h"
#include <QCheckBox>
#include <QGridLayout>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QMessageBox>
#include <QOpenGLContext>
#include <QDir>
#include <QPainter>
#include <QtMath>
#include <QMenu>
QVector<EffectMeta> video_effects;
QVector<EffectMeta> audio_effects;
QMutex effects_loaded;
Effect* create_effect(Clip* c, const EffectMeta* em) {
if (!em->filename.isEmpty()) {
// load effect from file
return new Effect(c, em);
} else if (em->internal >= 0 && em->internal < EFFECT_INTERNAL_COUNT) {
// must be an internal effect
switch (em->internal) {
case EFFECT_INTERNAL_TRANSFORM: return new TransformEffect(c, em);
case EFFECT_INTERNAL_TEXT: return new TextEffect(c, em);
case EFFECT_INTERNAL_SOLID: return new SolidEffect(c, em);
case EFFECT_INTERNAL_NOISE: return new AudioNoiseEffect(c, em);
case EFFECT_INTERNAL_VOLUME: return new VolumeEffect(c, em);
case EFFECT_INTERNAL_PAN: return new PanEffect(c, em);
case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em);
case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em);
case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em);
}
} else {
dout << "[ERROR] Invalid effect data";
QMessageBox::critical(mainWindow, "Invalid effect", "No candidate for effect '" + em->name + "'. This effect may be corrupt. Try reinstalling it or Olive.");
}
return NULL;
}
const EffectMeta* get_internal_meta(int internal_id) {
for (int i=0;i<audio_effects.size();i++) {
if (audio_effects.at(i).internal == internal_id) {
return &audio_effects.at(i);
}
}
for (int i=0;i<video_effects.size();i++) {
if (video_effects.at(i).internal == internal_id) {
return &video_effects.at(i);
}
}
return NULL;
}
void load_internal_effects() {
EffectMeta em;
// internal effects
em.type = EFFECT_TYPE_EFFECT;
em.name = "Volume";
em.internal = EFFECT_INTERNAL_VOLUME;
audio_effects.append(em);
em.name = "Pan";
em.internal = EFFECT_INTERNAL_PAN;
audio_effects.append(em);
em.name = "Tone";
em.internal = EFFECT_INTERNAL_TONE;
audio_effects.append(em);
em.name = "Noise";
em.internal = EFFECT_INTERNAL_NOISE;
audio_effects.append(em);
em.name = "Transform";
em.category = "Distort";
em.internal = EFFECT_INTERNAL_TRANSFORM;
video_effects.append(em);
em.name = "Corner Pin";
em.category = "Distort";
em.internal = EFFECT_INTERNAL_CORNERPIN;
video_effects.append(em);
em.name = "Text";
em.category = "Render";
em.internal = EFFECT_INTERNAL_TEXT;
video_effects.append(em);
em.name = "Solid";
em.category = "Render";
em.internal = EFFECT_INTERNAL_SOLID;
video_effects.append(em);
em.name = "Shake";
em.category = "Distort";
em.internal = EFFECT_INTERNAL_SHAKE;
video_effects.append(em);
// internal transitions
em.type = EFFECT_TYPE_TRANSITION;
em.category = "";
em.name = "Cross Dissolve";
em.internal = TRANSITION_INTERNAL_CROSSDISSOLVE;
video_effects.append(em);
em.name = "Linear Fade";
em.internal = TRANSITION_INTERNAL_LINEARFADE;
audio_effects.append(em);
em.name = "Exponential Fade";
em.internal = TRANSITION_INTERNAL_EXPONENTIALFADE;
audio_effects.append(em);
em.name = "Logarithmic Fade";
em.internal = TRANSITION_INTERNAL_LOGARITHMICFADE;
audio_effects.append(em);
}
void load_shader_effects() {
QString effects_path = get_effects_dir();
QDir effects_dir(effects_path);
if (effects_dir.exists()) {
QList<QString> entries = effects_dir.entryList(QStringList("*.xml"), QDir::Files);
for (int i=0;i<entries.size();i++) {
QFile file(effects_path + "/" + entries.at(i));
if (!file.open(QIODevice::ReadOnly)) {
dout << "[ERROR] Could not open" << entries.at(i);
return;
}
QXmlStreamReader reader(&file);
while (!reader.atEnd()) {
if (reader.name() == "effect") {
QString effect_name = "";
QString effect_cat = "";
const QXmlStreamAttributes attr = reader.attributes();
for (int j=0;j<attr.size();j++) {
if (attr.at(j).name() == "name") {
effect_name = attr.at(j).value().toString();
} else if (attr.at(j).name() == "category") {
effect_cat = attr.at(j).value().toString();
}
}
if (!effect_name.isEmpty()) {
EffectMeta em;
em.type = EFFECT_TYPE_EFFECT;
em.name = effect_name;
em.category = effect_cat;
em.filename = entries.at(i);
em.internal = -1;
video_effects.append(em);
} else {
dout << "[ERROR] Invalid effect found in" << entries.at(i);
}
break;
}
reader.readNext();
}
file.close();
}
}
}
void load_vst_effects() {
}
void init_effects() {
EffectInit* init_thread = new EffectInit();
QObject::connect(init_thread, SIGNAL(finished()), init_thread, SLOT(deleteLater()));
init_thread->start();
}
EffectInit::EffectInit() {
effects_loaded.lock();
}
void EffectInit::run() {
dout << "[INFO] Initializing effects...";
load_internal_effects();
load_shader_effects();
load_vst_effects();
effects_loaded.unlock();
dout << "[INFO] Finished initializing effects";
}
Effect::Effect(Clip* c, const EffectMeta *em) :
parent_clip(c),
meta(em),
length(30),
enable_shader(false),
enable_coords(false),
enable_superimpose(false),
glslProgram(NULL),
texture(NULL),
isOpen(false),
bound(false),
enable_always_update(false)
{
// set up base UI
container = new CollapsibleWidget();
connect(container->enabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed()));
ui = new QWidget();
ui_layout = new QGridLayout();
ui_layout->setSpacing(4);
ui->setLayout(ui_layout);
container->setContents(ui);
connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&)));
// set up UI from effect file
container->setText(em->name);
if (!em->filename.isEmpty()) {
QFile effect_file(get_effects_dir() + "/" + em->filename);
if (effect_file.open(QFile::ReadOnly)) {
QXmlStreamReader reader(&effect_file);
while (!reader.atEnd()) {
if (reader.name() == "row" && reader.isStartElement()) {
QString row_name;
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "name") {
row_name = attr.value().toString();
}
}
if (!row_name.isEmpty()) {
EffectRow* row = add_row(row_name + ":");
while (!reader.atEnd() && !(reader.name() == "row" && reader.isEndElement())) {
reader.readNext();
if (reader.name() == "field" && reader.isStartElement()) {
int type = EFFECT_TYPE_VIDEO;
QString id;
// get field type
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "type") {
QString comp = attr.value().toString().toUpper();
if (comp == "DOUBLE") {
type = EFFECT_FIELD_DOUBLE;
} else if (comp == "BOOL") {
type = EFFECT_FIELD_BOOL;
} else if (comp == "COLOR") {
type = EFFECT_FIELD_COLOR;
} else if (comp == "COMBO") {
type = EFFECT_FIELD_COMBO;
} else if (comp == "FONT") {
type = EFFECT_FIELD_FONT;
} else if (comp == "STRING") {
type = EFFECT_FIELD_STRING;
}
} else if (attr.name() == "id") {
id = attr.value().toString();
}
}
if (id.isEmpty()) {
dout << "[ERROR] Couldn't load field from" << em->filename << "- ID cannot be empty.";
} else if (type > -1) {
EffectField* field = row->add_field(type, id);
connect(field, SIGNAL(changed()), this, SLOT(field_changed()));
switch (type) {
case EFFECT_FIELD_DOUBLE:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_double_default_value(attr.value().toDouble());
} else if (attr.name() == "min") {
field->set_double_minimum_value(attr.value().toDouble());
} else if (attr.name() == "max") {
field->set_double_maximum_value(attr.value().toDouble());
}
}
break;
case EFFECT_FIELD_COLOR:
{
QColor color;
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "r") {
color.setRed(attr.value().toInt());
} else if (attr.name() == "g") {
color.setGreen(attr.value().toInt());
} else if (attr.name() == "b") {
color.setBlue(attr.value().toInt());
} else if (attr.name() == "rf") {
color.setRedF(attr.value().toFloat());
} else if (attr.name() == "gf") {
color.setGreenF(attr.value().toFloat());
} else if (attr.name() == "bf") {
color.setBlueF(attr.value().toFloat());
} else if (attr.name() == "hex") {
color.setNamedColor(attr.value().toString());
}
}
field->set_color_value(color);
}
break;
case EFFECT_FIELD_STRING:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_string_value(attr.value().toString());
}
}
break;
case EFFECT_FIELD_BOOL:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_bool_value(attr.value() == "1");
}
}
break;
case EFFECT_FIELD_COMBO:
{
int combo_index = 0;
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
combo_index = attr.value().toInt();
break;
}
}
while (!reader.atEnd() && !(reader.name() == "field" && reader.isEndElement())) {
reader.readNext();
if (reader.name() == "option" && reader.isStartElement()) {
reader.readNext();
field->add_combo_item(reader.text().toString(), 0);
}
}
field->set_combo_index(combo_index);
}
break;
case EFFECT_FIELD_FONT:
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "default") {
field->set_font_name(attr.value().toString());
}
}
break;
}
}
}
}
}
} else if (reader.name() == "shader" && reader.isStartElement()) {
enable_shader = true;
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "vert") {
vertPath = attr.value().toString();
} else if (attr.name() == "frag") {
fragPath = attr.value().toString();
}
}
} else if (reader.name() == "superimpose" && reader.isStartElement()) {
enable_superimpose = true;
const QXmlStreamAttributes& attributes = reader.attributes();
for (int i=0;i<attributes.size();i++) {
const QXmlStreamAttribute& attr = attributes.at(i);
if (attr.name() == "script") {
QFile script_file(get_effects_dir() + "/" + attr.value().toString());
if (script_file.open(QFile::ReadOnly)) {
script = script_file.readAll();
} else {
dout << "[ERROR] Failed to open superimpose script file for" << em->filename;
enable_superimpose = false;
}
break;
}
}
}
reader.readNext();
}
effect_file.close();
} else {
dout << "[ERROR] Failed to open effect file" << em->filename;
}
}
}
Effect::~Effect() {
if (isOpen) {
close();
}
for (int i=0;i<rows.size();i++) {
delete rows.at(i);
}
}
void Effect::copy_field_keyframes(Effect* e) {
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
EffectRow* copy_row = e->rows.at(i);
copy_row->setKeyframing(row->isKeyframing());
copy_row->keyframe_times = row->keyframe_times;
copy_row->keyframe_types = row->keyframe_types;
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
copy_row->field(j)->set_current_data(field->get_current_data());
copy_row->field(j)->keyframe_data = field->keyframe_data;
}
}
}
EffectRow* Effect::add_row(const QString& name) {
EffectRow* row = new EffectRow(this, ui_layout, name, rows.size());
rows.append(row);
return row;
}
EffectRow* Effect::row(int i) {
return rows.at(i);
}
int Effect::row_count() {
return rows.size();
}
void Effect::refresh() {}
void Effect::field_changed() {
panel_sequence_viewer->viewer_widget->update();
}
void Effect::show_context_menu(const QPoint& pos) {
if (meta->type == EFFECT_TYPE_EFFECT) {
QMenu menu(mainWindow);
int index = get_index_in_clip();
if (index > 0) {
QAction* move_up = menu.addAction("Move &Up");
connect(move_up, SIGNAL(triggered(bool)), this, SLOT(move_up()));
}
if (index < parent_clip->effects.size() - 1) {
QAction* move_down = menu.addAction("Move &Down");
connect(move_down, SIGNAL(triggered(bool)), this, SLOT(move_down()));
}
menu.addSeparator();
QAction* del_action = menu.addAction("D&elete");
connect(del_action, SIGNAL(triggered(bool)), this, SLOT(delete_self()));
menu.exec(container->title_bar->mapToGlobal(pos));
}
}
void Effect::delete_self() {
EffectDeleteCommand* command = new EffectDeleteCommand();
command->clips.append(parent_clip);
command->fx.append(get_index_in_clip());
undo_stack.push(command);
update_ui(true);
}
void Effect::move_up() {
MoveEffectCommand* command = new MoveEffectCommand();
command->clip = parent_clip;
command->from = get_index_in_clip();
command->to = command->from - 1;
undo_stack.push(command);
update_ui(true);
}
void Effect::move_down() {
MoveEffectCommand* command = new MoveEffectCommand();
command->clip = parent_clip;
command->from = get_index_in_clip();
command->to = command->from + 1;
undo_stack.push(command);
update_ui(true);
}
int Effect::get_index_in_clip() {
if (parent_clip != NULL) {
for (int i=0;i<parent_clip->effects.size();i++) {
if (parent_clip->effects.at(i) == this) {
return i;
}
}
}
return -1;
}
bool Effect::is_enabled() {
return container->enabled_check->isChecked();
}
void Effect::set_enabled(bool b) {
container->enabled_check->setChecked(b);
}
QVariant load_data_from_string(int type, const QString& string) {
switch (type) {
case EFFECT_FIELD_DOUBLE: return string.toDouble(); break;
case EFFECT_FIELD_COLOR: return QColor(string); break;
case EFFECT_FIELD_STRING: return string; break;
case EFFECT_FIELD_BOOL: return (string == "1"); break;
case EFFECT_FIELD_COMBO: return string.toInt(); break;
case EFFECT_FIELD_FONT: return string; break;
}
return QVariant();
}
QString save_data_to_string(int type, const QVariant& data) {
switch (type) {
case EFFECT_FIELD_DOUBLE: return QString::number(data.toDouble()); break;
case EFFECT_FIELD_COLOR: return data.value<QColor>().name(); break;
case EFFECT_FIELD_STRING: return data.toString(); break;
case EFFECT_FIELD_BOOL: return QString::number(data.toBool()); break;
case EFFECT_FIELD_COMBO: return QString::number(data.toInt()); break;
case EFFECT_FIELD_FONT: return data.toString(); break;
}
return QString();
}
void Effect::load(QXmlStreamReader& stream) {
int row_count = 0;
QString tag = stream.name().toString();
while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) {
stream.readNext();
if (stream.name() == "row" && stream.isStartElement()) {
if (row_count < rows.size()) {
EffectRow* row = rows.at(row_count);
int field_count = 0;
while (!stream.atEnd() && !(stream.name() == "row" && stream.isEndElement())) {
stream.readNext();
// read keyframes
if (stream.name() == "keyframes" && stream.isStartElement()) {
for (int k=0;k<stream.attributes().size();k++) {
const QXmlStreamAttribute& attr = stream.attributes().at(k);
if (attr.name() == "enabled") {
row->setKeyframing(attr.value() == "1");
}
}
if (row->isKeyframing()) {
stream.readNext();
while (!stream.atEnd() && !(stream.name() == "keyframes" && stream.isEndElement())) {
if (stream.name() == "key" && stream.isStartElement()) {
long keyframe_frame;
int keyframe_type;
for (int k=0;k<stream.attributes().size();k++) {
const QXmlStreamAttribute& attr = stream.attributes().at(k);
if (attr.name() == "frame") {
keyframe_frame = attr.value().toLong();
} else if (attr.name() == "type") {
keyframe_type = attr.value().toInt();
}
}
row->keyframe_times.append(keyframe_frame);
row->keyframe_types.append(keyframe_type);
}
stream.readNext();
}
}
stream.readNext();
}
// read field
if (stream.name() == "field" && stream.isStartElement()) {
if (field_count < row->fieldCount()) {
// match field using ID
bool found_field_by_id = false;
int field_number = field_count;
for (int k=0;k<stream.attributes().size();k++) {
const QXmlStreamAttribute& attr = stream.attributes().at(k);
if (attr.name() == "id") {
for (int l=0;l<row->fieldCount();l++) {
if (row->field(l)->id == attr.value()) {
field_number = l;
found_field_by_id = true;
dout << "[INFO] Found field by ID";
break;
}
}
break;
}
}
// TODO DEPRECATED, only used for backwards compatibility with 180820
if (!found_field_by_id) dout << "[INFO] Found field by field number";
EffectField* field = row->field(field_number);
// get current field value
for (int k=0;k<stream.attributes().size();k++) {
const QXmlStreamAttribute& attr = stream.attributes().at(k);
if (attr.name() == "value") {
field->set_current_data(load_data_from_string(field->type, attr.value().toString()));
break;
}
}
while (!stream.atEnd() && !(stream.name() == "field" && stream.isEndElement())) {
stream.readNext();
// read all keyframes
if (stream.name() == "key" && stream.isStartElement()) {
stream.readNext();
field->keyframe_data.append(load_data_from_string(field->type, stream.text().toString()));
}
}
} else {
dout << "[ERROR] Too many fields for effect" << id << "row" << row_count << ". Project might be corrupt. (Got" << field_count << ", expected <" << row->fieldCount()-1 << ")";
}
field_count++;
}
}
} else {
dout << "[ERROR] Too many rows for effect" << id << ". Project might be corrupt. (Got" << row_count << ", expected <" << rows.size()-1 << ")";
}
row_count++;
}
}
}
void Effect::save(QXmlStreamWriter& stream) {
stream.writeAttribute("name", meta->name);
stream.writeAttribute("enabled", QString::number(is_enabled()));
stream.writeAttribute("length", QString::number(length));
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
stream.writeStartElement("row"); // row
stream.writeStartElement("keyframes"); // keyframes
stream.writeAttribute("enabled", QString::number(row->isKeyframing()));
for (int j=0;j<row->keyframe_times.size();j++) {
stream.writeStartElement("key"); // key
stream.writeAttribute("frame", QString::number(row->keyframe_times.at(j)));
stream.writeAttribute("type", QString::number(row->keyframe_types.at(j)));
stream.writeEndElement(); // key
}
stream.writeEndElement(); // keyframes
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
stream.writeStartElement("field"); // field
stream.writeAttribute("id", field->id);
stream.writeAttribute("value", save_data_to_string(field->type, field->get_current_data()));
for (int k=0;k<field->keyframe_data.size();k++) {
stream.writeTextElement("key", save_data_to_string(field->type, field->keyframe_data.at(k)));
}
stream.writeEndElement(); // field
}
stream.writeEndElement(); // row
}
}
void Effect::open() {
if (isOpen) {
dout << "[WARNING] Tried to open an effect that was already open";
close();
}
if (enable_shader) {
if (QOpenGLContext::currentContext() == NULL) {
dout << "[WARNING] No current context to create a shader program for - will retry next repaint";
} else {
glslProgram = new QOpenGLShaderProgram();
if (!vertPath.isEmpty()) glslProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, get_effects_dir() + "/" + vertPath);
if (!fragPath.isEmpty()) glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, get_effects_dir() + "/" + fragPath);
glslProgram->link();
isOpen = true;
}
} else {
isOpen = true;
}
if (enable_superimpose) {
texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
}
}
void Effect::close() {
if (!isOpen) {
dout << "[WARNING] Tried to close an effect that was already closed";
}
if (glslProgram != NULL) {
delete glslProgram;
glslProgram = NULL;
}
delete_texture();
isOpen = false;
}
void Effect::startEffect() {
if (!isOpen) {
open();
dout << "[WARNING] Tried to start a closed effect - opening";
}
if (enable_shader) bound = glslProgram->bind();
}
void Effect::endEffect() {
if (bound) glslProgram->release();
bound = false;
}
Effect* Effect::copy(Clip* c) {
Effect* copy = create_effect(c, meta);
copy->set_enabled(is_enabled());
copy_field_keyframes(copy);
return copy;
}
void Effect::process_shader(double timecode, GLTextureCoords&) {
glslProgram->setUniformValue("resolution", parent_clip->getWidth(), parent_clip->getHeight());
glslProgram->setUniformValue("time", (GLfloat) timecode);
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
if (!field->id.isEmpty()) {
switch (field->type) {
case EFFECT_FIELD_DOUBLE:
glslProgram->setUniformValue(field->id.toLatin1().constData(), (GLfloat) field->get_double_value(timecode));
break;
case EFFECT_FIELD_COLOR:
glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_color_value(timecode).redF(), field->get_color_value(timecode).greenF(), field->get_color_value(timecode).blueF());
break;
case EFFECT_FIELD_STRING: break; // can you even send a string to a uniform value?
case EFFECT_FIELD_BOOL:
glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_bool_value(timecode));
break;
case EFFECT_FIELD_COMBO:
glslProgram->setUniformValue(field->id.toLatin1().constData(), field->get_combo_index(timecode));
break;
case EFFECT_FIELD_FONT: break; // can you even send a string to a uniform value?
}
}
}
}
}
void Effect::process_coords(double, GLTextureCoords&, int data) {}
GLuint Effect::process_superimpose(double timecode) {
bool recreate_texture = false;
int width = parent_clip->getWidth();
int height = parent_clip->getHeight();
if (width != img.width() || height != img.height()) {
img = QImage(width, height, QImage::Format_RGBA8888);
recreate_texture = true;
}
if (valueHasChanged(timecode) || recreate_texture || enable_always_update) {
redraw(timecode);
}
if (texture != NULL) {
if (recreate_texture || texture->width() != img.width() || texture->height() != img.height()) {
delete_texture();
texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
texture->setData(img);
} else {
texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, img.constBits());
}
return texture->textureId();
}
return 0;
}
//void Effect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count) {
void Effect::process_audio(double, double, quint8*, int, int) {
// only volume/pan, hand off to AU and VST for all other cases
/*double interval = (timecode_end-timecode_start)/nb_bytes;
for (int i=0;i<nb_bytes;i+=2) {
qint32 samp = (qint16) (((samples[i+1] & 0xFF) << 8) | (samples[i] & 0xFF));
jsEngine.globalObject().setProperty("sample", samp);
jsEngine.globalObject().setProperty("volume", row(0)->field(0)->get_double_value(timecode_start+(interval*i), true));
QJSValue result = eval.call();
samp = result.toInt();
QJSValueList args;
args << samples << nb_bytes;
samples[i+1] = (quint8) (samp >> 8);
samples[i] = (quint8) samp;
}*/
}
void Effect::redraw(double) {
/*
// run javascript
QPainter p(&img);
painter_wrapper.img = &img;
painter_wrapper.painter = &p;
jsEngine.globalObject().setProperty("painter", wrapper_obj);
jsEngine.globalObject().setProperty("width", parent_clip->getWidth());
jsEngine.globalObject().setProperty("height", parent_clip->getHeight());
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
if (!field->id.isEmpty()) {
switch (field->type) {
case EFFECT_FIELD_DOUBLE:
jsEngine.globalObject().setProperty(field->id, field->get_double_value(timecode));
break;
case EFFECT_FIELD_COLOR:
jsEngine.globalObject().setProperty(field->id, field->get_color_value(timecode).name());
break;
case EFFECT_FIELD_STRING:
jsEngine.globalObject().setProperty(field->id, field->get_string_value(timecode));
break;
case EFFECT_FIELD_BOOL:
jsEngine.globalObject().setProperty(field->id, field->get_bool_value(timecode));
break;
case EFFECT_FIELD_COMBO:
jsEngine.globalObject().setProperty(field->id, field->get_combo_index(timecode));
break;
case EFFECT_FIELD_FONT:
jsEngine.globalObject().setProperty(field->id, field->get_font_name(timecode));
break;
}
}
}
}
jsEngine.evaluate(script);
*/
}
bool Effect::valueHasChanged(double timecode) {
if (cachedValues.size() == 0) {
for (int i=0;i<row_count();i++) {
EffectRow* crow = row(i);
for (int j=0;j<crow->fieldCount();j++) {
cachedValues.append(crow->field(j)->get_current_data());
}
}
return true;
} else {
bool changed = false;
int index = 0;
for (int i=0;i<row_count();i++) {
EffectRow* crow = row(i);
for (int j=0;j<crow->fieldCount();j++) {
EffectField* field = crow->field(j);
field->validate_keyframe_data(timecode);
if (cachedValues.at(index) != field->get_current_data()) {
changed = true;
}
cachedValues[index] = field->get_current_data();
index++;
}
}
return changed;
}
}
void Effect::delete_texture() {
if (texture != NULL) {
texture->destroy();
delete texture;
texture = NULL;
}
}
qint16 mix_audio_sample(qint16 a, qint16 b) {
qint32 mixed_sample = static_cast<qint32>(a) + static_cast<qint32>(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);
}