transitions: extended transition logic
General code and functionality improvements. Moved more code out of the OpenGL backend for portability. Implemented audio transitions. Implemented basic transition animation curve settings.
This commit is contained in:
@@ -59,4 +59,31 @@ ShaderCode CrossDissolveTransition::GetShaderCode(const QString &shader_id) cons
|
||||
return ShaderCode(Node::ReadFileAsString(":/shaders/crossdissolve.frag"), QString());
|
||||
}
|
||||
|
||||
void CrossDissolveTransition::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
|
||||
{
|
||||
for (int i=0; i<out_samples->sample_count(); i++) {
|
||||
double this_sample_time = out_samples->audio_params().samples_to_time(i).toDouble() + time_in;
|
||||
double progress = GetTotalProgress(this_sample_time);
|
||||
|
||||
for (int j=0; j<out_samples->audio_params().channel_count(); j++) {
|
||||
out_samples->data()[j][i] = 0;
|
||||
|
||||
if (from_samples) {
|
||||
if (i < from_samples->sample_count()) {
|
||||
out_samples->data()[j][i] += from_samples->data()[j][i] * TransformCurve(1.0 - progress);
|
||||
}
|
||||
}
|
||||
|
||||
if (to_samples) {
|
||||
// Offset input samples from the end
|
||||
int in_index = i - (out_samples->sample_count() - to_samples->sample_count());
|
||||
|
||||
if (in_index >= 0) {
|
||||
out_samples->data()[j][i] += to_samples->data()[j][in_index] * TransformCurve(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -41,6 +41,9 @@ public:
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
|
||||
|
||||
protected:
|
||||
virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -28,17 +28,22 @@ TransitionBlock::TransitionBlock() :
|
||||
connected_out_block_(nullptr),
|
||||
connected_in_block_(nullptr)
|
||||
{
|
||||
out_block_input_ = new NodeInput("out_block_in", NodeParam::kBuffer);
|
||||
out_block_input_ = new NodeInput(QStringLiteral("out_block_in"), NodeParam::kBuffer);
|
||||
out_block_input_->set_is_keyframable(false);
|
||||
connect(out_block_input_, &NodeParam::EdgeAdded, this, &TransitionBlock::BlockConnected);
|
||||
connect(out_block_input_, &NodeParam::EdgeRemoved, this, &TransitionBlock::BlockDisconnected);
|
||||
AddInput(out_block_input_);
|
||||
|
||||
in_block_input_ = new NodeInput("in_block_in", NodeParam::kBuffer);
|
||||
in_block_input_ = new NodeInput(QStringLiteral("in_block_in"), NodeParam::kBuffer);
|
||||
in_block_input_->set_is_keyframable(false);
|
||||
connect(in_block_input_, &NodeParam::EdgeAdded, this, &TransitionBlock::BlockConnected);
|
||||
connect(in_block_input_, &NodeParam::EdgeRemoved, this, &TransitionBlock::BlockDisconnected);
|
||||
AddInput(in_block_input_);
|
||||
|
||||
curve_input_ = new NodeInput(QStringLiteral("curve_in"), NodeParam::kCombo);
|
||||
curve_input_->set_is_keyframable(false);
|
||||
curve_input_->set_connectable(false);
|
||||
AddInput(curve_input_);
|
||||
}
|
||||
|
||||
Block::Type TransitionBlock::type() const
|
||||
@@ -62,6 +67,10 @@ void TransitionBlock::Retranslate()
|
||||
|
||||
out_block_input_->set_name(tr("From"));
|
||||
in_block_input_->set_name(tr("To"));
|
||||
curve_input_->set_name(tr("Curve"));
|
||||
|
||||
// These must correspond to the CurveType enum
|
||||
curve_input_->set_combobox_strings({ tr("Linear"), tr("Exponential"), tr("Logarithmic") });
|
||||
}
|
||||
|
||||
rational TransitionBlock::in_offset() const
|
||||
@@ -106,12 +115,12 @@ Block *TransitionBlock::connected_in_block() const
|
||||
return connected_in_block_;
|
||||
}
|
||||
|
||||
double TransitionBlock::GetTotalProgress(const rational &time) const
|
||||
double TransitionBlock::GetTotalProgress(const double &time) const
|
||||
{
|
||||
return GetInternalTransitionTime(time) / length().toDouble();
|
||||
}
|
||||
|
||||
double TransitionBlock::GetOutProgress(const rational &time) const
|
||||
double TransitionBlock::GetOutProgress(const double &time) const
|
||||
{
|
||||
if (out_offset() == 0) {
|
||||
return 0;
|
||||
@@ -120,7 +129,7 @@ double TransitionBlock::GetOutProgress(const rational &time) const
|
||||
return clamp(1.0 - (GetInternalTransitionTime(time) / out_offset().toDouble()), 0.0, 1.0);
|
||||
}
|
||||
|
||||
double TransitionBlock::GetInProgress(const rational &time) const
|
||||
double TransitionBlock::GetInProgress(const double &time) const
|
||||
{
|
||||
if (in_offset() == 0) {
|
||||
return 0;
|
||||
@@ -133,18 +142,34 @@ void TransitionBlock::Hash(QCryptographicHash &hash, const rational &time) const
|
||||
{
|
||||
Node::Hash(hash, time);
|
||||
|
||||
double all_prog = GetTotalProgress(time);
|
||||
double in_prog = GetInProgress(time);
|
||||
double out_prog = GetOutProgress(time);
|
||||
double time_dbl = time.toDouble();
|
||||
double all_prog = GetTotalProgress(time_dbl);
|
||||
double in_prog = GetInProgress(time_dbl);
|
||||
double out_prog = GetOutProgress(time_dbl);
|
||||
|
||||
hash.addData(reinterpret_cast<const char*>(&all_prog), sizeof(double));
|
||||
hash.addData(reinterpret_cast<const char*>(&in_prog), sizeof(double));
|
||||
hash.addData(reinterpret_cast<const char*>(&out_prog), sizeof(double));
|
||||
}
|
||||
|
||||
double TransitionBlock::GetInternalTransitionTime(const rational &time) const
|
||||
double TransitionBlock::GetInternalTransitionTime(const double &time) const
|
||||
{
|
||||
return time.toDouble() - in().toDouble();
|
||||
return time - in().toDouble();
|
||||
}
|
||||
|
||||
void TransitionBlock::InsertTransitionTimes(AcceleratedJob *job, const double &time) const
|
||||
{
|
||||
// Provides total transition progress from 0.0 (start) - 1.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_all"),
|
||||
NodeValue(NodeParam::kFloat, GetTotalProgress(time), this));
|
||||
|
||||
// Provides progress of out section from 1.0 (start) - 0.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_out"),
|
||||
NodeValue(NodeParam::kFloat, GetOutProgress(time), this));
|
||||
|
||||
// Provides progress of in section from 0.0 (start) - 1.0 (end)
|
||||
job->InsertValue(QStringLiteral("ove_tprog_in"),
|
||||
NodeValue(NodeParam::kFloat, GetInProgress(time), this));
|
||||
}
|
||||
|
||||
void TransitionBlock::BlockConnected(NodeEdgePtr edge)
|
||||
@@ -173,16 +198,62 @@ void TransitionBlock::BlockDisconnected(NodeEdgePtr edge)
|
||||
|
||||
NodeValueTable TransitionBlock::Value(NodeValueDatabase &value) const
|
||||
{
|
||||
ShaderJob job;
|
||||
NodeParam::DataType data_type;
|
||||
|
||||
job.InsertValue(out_block_input(), value);
|
||||
job.InsertValue(in_block_input(), value);
|
||||
job.SetAlphaChannelRequired(true);
|
||||
if (out_block_input()->is_connected()) {
|
||||
data_type = value[out_block_input()].GetWithMeta(NodeParam::kBuffer).type();
|
||||
} else if (in_block_input()->is_connected()) {
|
||||
data_type = value[in_block_input()].GetWithMeta(NodeParam::kBuffer).type();
|
||||
} else {
|
||||
data_type = NodeParam::kNone;
|
||||
}
|
||||
|
||||
ShaderJobEvent(value, job);
|
||||
NodeParam::DataType job_type;
|
||||
QVariant push_job;
|
||||
|
||||
if (data_type == NodeParam::kTexture) {
|
||||
// This must be a visual transition
|
||||
ShaderJob job;
|
||||
|
||||
job.InsertValue(out_block_input(), value);
|
||||
job.InsertValue(in_block_input(), value);
|
||||
job.InsertValue(curve_input_, value);
|
||||
job.SetAlphaChannelRequired(true);
|
||||
|
||||
double time = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_in")).toDouble();
|
||||
InsertTransitionTimes(&job, time);
|
||||
|
||||
ShaderJobEvent(value, job);
|
||||
|
||||
job_type = NodeParam::kShaderJob;
|
||||
push_job = QVariant::fromValue(job);
|
||||
} else if (data_type == NodeParam::kSamples) {
|
||||
// This must be an audio transition
|
||||
SampleBufferPtr from_samples = value[out_block_input()].Take(NodeParam::kBuffer).value<SampleBufferPtr>();
|
||||
SampleBufferPtr to_samples = value[in_block_input()].Take(NodeParam::kBuffer).value<SampleBufferPtr>();
|
||||
|
||||
if (from_samples || to_samples) {
|
||||
double time_in = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_in")).toDouble();
|
||||
double time_out = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_out")).toDouble();
|
||||
|
||||
const AudioParams& params = (from_samples) ? from_samples->audio_params() : to_samples->audio_params();
|
||||
|
||||
int nb_samples = params.time_to_samples(time_out - time_in);
|
||||
|
||||
SampleBufferPtr out_samples = SampleBuffer::CreateAllocated(params, nb_samples);
|
||||
SampleJobEvent(from_samples, to_samples, out_samples, time_in);
|
||||
|
||||
job_type = NodeParam::kSamples;
|
||||
push_job = QVariant::fromValue(out_samples);
|
||||
}
|
||||
}
|
||||
|
||||
NodeValueTable table = value.Merge();
|
||||
table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this);
|
||||
|
||||
if (!push_job.isNull()) {
|
||||
table.Push(job_type, push_job, this);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
@@ -192,4 +263,28 @@ void TransitionBlock::ShaderJobEvent(NodeValueDatabase &value, ShaderJob &job) c
|
||||
Q_UNUSED(job)
|
||||
}
|
||||
|
||||
void TransitionBlock::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
|
||||
{
|
||||
Q_UNUSED(from_samples)
|
||||
Q_UNUSED(to_samples)
|
||||
Q_UNUSED(out_samples)
|
||||
Q_UNUSED(time_in)
|
||||
}
|
||||
|
||||
double TransitionBlock::TransformCurve(double linear) const
|
||||
{
|
||||
switch (static_cast<CurveType>(curve_input_->get_standard_value().toInt())) {
|
||||
case kLinear:
|
||||
break;
|
||||
case kExponential:
|
||||
linear *= linear;
|
||||
break;
|
||||
case kLogarithmic:
|
||||
linear = qSqrt(linear);
|
||||
break;
|
||||
}
|
||||
|
||||
return linear;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -43,9 +43,9 @@ public:
|
||||
Block* connected_out_block() const;
|
||||
Block* connected_in_block() const;
|
||||
|
||||
double GetTotalProgress(const rational& time) const;
|
||||
double GetOutProgress(const rational& time) const;
|
||||
double GetInProgress(const rational& time) const;
|
||||
double GetTotalProgress(const double &time) const;
|
||||
double GetOutProgress(const double &time) const;
|
||||
double GetInProgress(const double &time) const;
|
||||
|
||||
virtual void Hash(QCryptographicHash& hash, const rational &time) const override;
|
||||
|
||||
@@ -54,13 +54,27 @@ public:
|
||||
protected:
|
||||
virtual void ShaderJobEvent(NodeValueDatabase &value, ShaderJob& job) const;
|
||||
|
||||
virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const;
|
||||
|
||||
double TransformCurve(double linear) const;
|
||||
|
||||
private:
|
||||
double GetInternalTransitionTime(const rational& time) const;
|
||||
enum CurveType {
|
||||
kLinear,
|
||||
kExponential,
|
||||
kLogarithmic
|
||||
};
|
||||
|
||||
double GetInternalTransitionTime(const double &time) const;
|
||||
|
||||
void InsertTransitionTimes(AcceleratedJob* job, const double& time) const;
|
||||
|
||||
NodeInput* out_block_input_;
|
||||
|
||||
NodeInput* in_block_input_;
|
||||
|
||||
NodeInput* curve_input_;
|
||||
|
||||
Block* connected_out_block_;
|
||||
|
||||
Block* connected_in_block_;
|
||||
|
||||
+10
-5
@@ -41,11 +41,7 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa
|
||||
database.Insert(input, ProcessInput(input, input_time));
|
||||
}
|
||||
|
||||
// Insert global variables
|
||||
NodeValueTable global;
|
||||
global.Push(NodeParam::kFloat, range.in().toDouble(), nullptr, QStringLiteral("time_in"));
|
||||
global.Push(NodeParam::kFloat, range.out().toDouble(), nullptr, QStringLiteral("time_out"));
|
||||
database.Insert(QStringLiteral("global"), global);
|
||||
AddGlobalsToDatabase(database, range);
|
||||
|
||||
return database;
|
||||
}
|
||||
@@ -156,6 +152,15 @@ QVariant NodeTraverser::GetCachedFrame(const Node *node, const rational &time)
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void NodeTraverser::AddGlobalsToDatabase(NodeValueDatabase &db, const TimeRange& range)
|
||||
{
|
||||
// Insert global variables
|
||||
NodeValueTable global;
|
||||
global.Push(NodeParam::kFloat, range.in().toDouble(), nullptr, QStringLiteral("time_in"));
|
||||
global.Push(NodeParam::kFloat, range.out().toDouble(), nullptr, QStringLiteral("time_out"));
|
||||
db.Insert(QStringLiteral("global"), global);
|
||||
}
|
||||
|
||||
void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params)
|
||||
{
|
||||
bool got_cached_frame = false;
|
||||
|
||||
@@ -56,6 +56,8 @@ protected:
|
||||
|
||||
virtual QVariant GetCachedFrame(const Node *node, const rational &time);
|
||||
|
||||
static void AddGlobalsToDatabase(NodeValueDatabase& db, const TimeRange &range);
|
||||
|
||||
private:
|
||||
void PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user