tests: coverage round 7 (audio, distort, filter/keying, math/transition nodes)

- node_audio_test: PanNode/VolumeNode sample math, TimeInput, ValueNode
- node_distort_test: transform/crop/flip/cornerpin/mask/ripple/swirl/
  tile/wave inputs, shader code, value jobs, resolution autoscale math
- node_filter_keying_test: opacity/blur/dropshadow/mosaic/stroke jobs,
  chroma/color-difference key and despill value composition
- node_math_transition_test: TrigonometryNode ops, MergeNode,
  TransitionBlock offsets/progress/crossfade math, SubtitleBlock,
  traverser globals and table caching
This commit is contained in:
2026-07-17 06:24:35 +08:00
parent b98c5bd0c0
commit 4302a0aca2
5 changed files with 5190 additions and 0 deletions
+4
View File
@@ -71,6 +71,10 @@ add_executable(olive-gtest
node_generator_test.cpp node_generator_test.cpp
render_misc_test.cpp render_misc_test.cpp
multicam_serializer_test.cpp multicam_serializer_test.cpp
node_audio_test.cpp
node_distort_test.cpp
node_filter_keying_test.cpp
node_math_transition_test.cpp
timeline_marker_test.cpp timeline_marker_test.cpp
undo_stack_test.cpp undo_stack_test.cpp
plugin_support_test.cpp plugin_support_test.cpp
+802
View File
@@ -0,0 +1,802 @@
#include <gtest/gtest.h>
#include <vector>
#include <QStringList>
#include <QVector3D>
#include "node/audio/pan/pan.h"
#include "node/audio/volume/volume.h"
#include "node/color/colormanager/colormanager.h"
#include "node/globals.h"
#include "node/input/time/timeinput.h"
#include "node/input/value/valuenode.h"
#include "node/keyframe.h"
#include "node/project.h"
#include "node/traverser.h"
#include "olive/core/render/audioparams.h"
#include "olive/core/render/samplebuffer.h"
#include "olive/core/util/rational.h"
#include "render/job/samplejob.h"
#include "widget/slider/floatslider.h"
namespace
{
// Minimal node that emits a single configurable value, used to feed the
// samples input of the audio nodes without any decoder or audio hardware.
class ConstantValueNode : public olive::Node {
public:
ConstantValueNode() = default;
NODE_DEFAULT_FUNCTIONS(ConstantValueNode)
virtual QString Name() const override
{
return QStringLiteral("Test Constant");
}
virtual QString id() const override
{
return QStringLiteral("org.oak.test.constant");
}
virtual QVector<CategoryID> Category() const override
{
return { kCategoryGenerator };
}
void SetOutput(const olive::NodeValue &value)
{
output_ = value;
}
virtual void Value(const olive::NodeValueRow &value,
const olive::NodeGlobals &globals,
olive::NodeValueTable *table) const override
{
Q_UNUSED(value)
Q_UNUSED(globals)
table->Push(output_);
}
private:
olive::NodeValue output_;
};
// Traverser that resolves SampleJobs on the CPU (no audio hardware) so the
// non-static (job) paths of the audio nodes can be verified end to end.
class SampleResolvingTraverser : public olive::NodeTraverser {
public:
void Resolve(olive::NodeValue &value)
{
ResolveJobs(value);
}
protected:
virtual olive::core::SampleBuffer
CreateSampleBuffer(const olive::core::AudioParams &params,
int sample_count) override
{
return olive::core::SampleBuffer(params, size_t(sample_count));
}
virtual void ProcessSamples(olive::core::SampleBuffer &destination,
const olive::Node *node,
const olive::TimeRange &range,
const olive::SampleJob &job) override
{
Q_UNUSED(range)
for (size_t i = 0; i < destination.sample_count(); i++) {
node->ProcessSamples(job.GetValues(), job.samples(), destination,
int(i));
}
}
};
template <typename T> T *AddNode(olive::Project *project)
{
T *node = new T();
node->setParent(project);
return node;
}
ConstantValueNode *AddConstant(olive::Project *project,
const olive::NodeValue &value)
{
auto *node = new ConstantValueNode();
node->setParent(project);
node->SetOutput(value);
return node;
}
olive::NodeKeyframe *AddKey(olive::Node *node, const QString &input,
const olive::core::rational &time,
const QVariant &value)
{
auto *key = new olive::NodeKeyframe(
time, value, olive::NodeKeyframe::kLinear, 0, -1, input);
key->setParent(node);
return key;
}
// A fresh traverser per call: NodeTraverser caches tables per node+range, so
// reusing one would return stale results after the node's parameters change.
olive::NodeValueTable GenerateTableAt(const olive::Node *node,
const olive::core::rational &time)
{
olive::NodeTraverser traverser;
return traverser.GenerateTable(
node, olive::TimeRange(time, time + olive::core::rational(1, 30)));
}
olive::core::AudioParams StereoParams()
{
return olive::core::AudioParams(48000, olive::core::kChannelLayoutStereo,
olive::core::SampleFormat::F32P);
}
olive::core::AudioParams MonoParams()
{
return olive::core::AudioParams(48000, olive::core::kChannelLayoutMono,
olive::core::SampleFormat::F32P);
}
// Creates a stereo buffer with the given per-channel samples. Both channels
// must have the same number of samples.
olive::core::SampleBuffer MakeStereoBuffer(const std::vector<float> &channel0,
const std::vector<float> &channel1)
{
olive::core::SampleBuffer buffer(StereoParams(), channel0.size());
for (size_t i = 0; i < channel0.size(); i++) {
buffer.data(0)[i] = channel0[i];
}
for (size_t i = 0; i < channel1.size(); i++) {
buffer.data(1)[i] = channel1[i];
}
return buffer;
}
olive::core::SampleBuffer MakeMonoBuffer(const std::vector<float> &samples)
{
olive::core::SampleBuffer buffer(MonoParams(), samples.size());
for (size_t i = 0; i < samples.size(); i++) {
buffer.data(0)[i] = samples[i];
}
return buffer;
}
olive::NodeValue SampleValue(const olive::core::SampleBuffer &buffer)
{
return olive::NodeValue(olive::NodeValue::kSamples,
QVariant::fromValue(buffer));
}
// Connects a stereo buffer {1,2,3,4}/{5,6,7,8} to the node's samples input.
ConstantValueNode *ConnectTestSamples(olive::Project *project,
olive::Node *node,
const QString &samples_input)
{
ConstantValueNode *samples = AddConstant(
project, SampleValue(MakeStereoBuffer({ 1.0f, 2.0f, 3.0f, 4.0f },
{ 5.0f, 6.0f, 7.0f, 8.0f })));
olive::Node::ConnectEdge(samples, olive::NodeInput(node, samples_input));
return samples;
}
} // namespace
TEST(PanNode, Metadata)
{
olive::PanNode pan;
EXPECT_EQ(pan.Name(), QStringLiteral("Pan"));
EXPECT_EQ(pan.id(), QStringLiteral("org.olivevideoeditor.Olive.pan"));
EXPECT_FALSE(pan.Description().isEmpty());
EXPECT_TRUE(pan.Category().contains(olive::Node::kCategoryFilter));
// Registered as an audio effect with the samples input as effect input
EXPECT_TRUE(pan.GetFlags() & olive::Node::kAudioEffect);
EXPECT_EQ(pan.GetEffectInputID(), olive::PanNode::kSamplesInput);
}
TEST(PanNode, InputDefaults)
{
olive::PanNode pan;
EXPECT_EQ(pan.GetInputDataType(olive::PanNode::kSamplesInput),
olive::NodeValue::kSamples);
EXPECT_FALSE(pan.IsInputKeyframable(olive::PanNode::kSamplesInput));
EXPECT_TRUE(pan.IsInputConnectable(olive::PanNode::kSamplesInput));
EXPECT_EQ(pan.GetInputDataType(olive::PanNode::kPanningInput),
olive::NodeValue::kFloat);
EXPECT_TRUE(pan.IsInputKeyframable(olive::PanNode::kPanningInput));
EXPECT_DOUBLE_EQ(
pan.GetStandardValue(olive::PanNode::kPanningInput).toDouble(), 0.0);
EXPECT_DOUBLE_EQ(pan.GetInputProperty(olive::PanNode::kPanningInput,
QStringLiteral("min"))
.toDouble(),
-1.0);
EXPECT_DOUBLE_EQ(pan.GetInputProperty(olive::PanNode::kPanningInput,
QStringLiteral("max"))
.toDouble(),
1.0);
EXPECT_EQ(int(pan.GetInputProperty(olive::PanNode::kPanningInput,
QStringLiteral("view"))
.toInt()),
int(olive::FloatSlider::kPercentage));
}
TEST(PanNode, RetranslateSetsInputNames)
{
olive::PanNode pan;
pan.Retranslate();
EXPECT_EQ(pan.GetInputName(olive::PanNode::kSamplesInput),
QStringLiteral("Samples"));
EXPECT_EQ(pan.GetInputName(olive::PanNode::kPanningInput),
QStringLiteral("Pan"));
}
TEST(PanNode, StaticCenterPanLeavesSamplesUnchanged)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::PanNode *pan = AddNode<olive::PanNode>(&project);
ConnectTestSamples(&project, pan, olive::PanNode::kSamplesInput);
// Pan 0 is a no-op, but the (unmodified) buffer is still pushed
const olive::NodeValueTable table = GenerateTableAt(pan, olive::core::rational(0));
const olive::core::SampleBuffer out =
table.Get(olive::NodeValue::kSamples).toSamples();
ASSERT_TRUE(out.is_allocated());
ASSERT_EQ(out.sample_count(), 4u);
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], float(i + 1));
EXPECT_FLOAT_EQ(out.data(1)[i], float(i + 5));
}
}
TEST(PanNode, StaticRightPanAttenuatesLeftChannel)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::PanNode *pan = AddNode<olive::PanNode>(&project);
pan->SetStandardValue(olive::PanNode::kPanningInput, 0.5);
ConnectTestSamples(&project, pan, olive::PanNode::kSamplesInput);
const olive::NodeValueTable table = GenerateTableAt(pan, olive::core::rational(0));
const olive::core::SampleBuffer out =
table.Get(olive::NodeValue::kSamples).toSamples();
ASSERT_TRUE(out.is_allocated());
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], float(i + 1) * 0.5f);
EXPECT_FLOAT_EQ(out.data(1)[i], float(i + 5));
}
}
TEST(PanNode, StaticLeftPanAttenuatesRightChannel)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::PanNode *pan = AddNode<olive::PanNode>(&project);
pan->SetStandardValue(olive::PanNode::kPanningInput, -0.5);
ConnectTestSamples(&project, pan, olive::PanNode::kSamplesInput);
const olive::NodeValueTable table = GenerateTableAt(pan, olive::core::rational(0));
const olive::core::SampleBuffer out =
table.Get(olive::NodeValue::kSamples).toSamples();
ASSERT_TRUE(out.is_allocated());
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], float(i + 1));
EXPECT_FLOAT_EQ(out.data(1)[i], float(i + 5) * 0.5f);
}
}
TEST(PanNode, StaticFullRightPanSilencesLeftChannel)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::PanNode *pan = AddNode<olive::PanNode>(&project);
pan->SetStandardValue(olive::PanNode::kPanningInput, 1.0);
ConnectTestSamples(&project, pan, olive::PanNode::kSamplesInput);
const olive::NodeValueTable table = GenerateTableAt(pan, olive::core::rational(0));
const olive::core::SampleBuffer out =
table.Get(olive::NodeValue::kSamples).toSamples();
ASSERT_TRUE(out.is_allocated());
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], 0.0f);
EXPECT_FLOAT_EQ(out.data(1)[i], float(i + 5));
}
}
TEST(PanNode, NonStereoSamplesPassThroughUnchanged)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::PanNode *pan = AddNode<olive::PanNode>(&project);
pan->SetStandardValue(olive::PanNode::kPanningInput, 0.5);
ConstantValueNode *samples = AddConstant(
&project, SampleValue(MakeMonoBuffer({ 1.0f, -2.0f, 3.0f })));
olive::Node::ConnectEdge(
samples, olive::NodeInput(pan, olive::PanNode::kSamplesInput));
// Pan only supports stereo: a mono buffer is pushed through untouched
const olive::NodeValueTable table = GenerateTableAt(pan, olive::core::rational(0));
const olive::core::SampleBuffer out =
table.Get(olive::NodeValue::kSamples).toSamples();
ASSERT_TRUE(out.is_allocated());
ASSERT_EQ(out.audio_params().channel_count(), 1);
ASSERT_EQ(out.sample_count(), 3u);
EXPECT_FLOAT_EQ(out.data(0)[0], 1.0f);
EXPECT_FLOAT_EQ(out.data(0)[1], -2.0f);
EXPECT_FLOAT_EQ(out.data(0)[2], 3.0f);
}
TEST(PanNode, NoSamplesInputProducesNoOutput)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::PanNode *pan = AddNode<olive::PanNode>(&project);
pan->SetStandardValue(olive::PanNode::kPanningInput, 0.5);
// Without an allocated buffer on the samples input, Value() pushes nothing
const olive::NodeValueTable table = GenerateTableAt(pan, olive::core::rational(0));
EXPECT_EQ(table.Get(olive::NodeValue::kSamples).type(),
olive::NodeValue::kNone);
}
TEST(PanNode, KeyframedPanProducesSampleJobButLosesPanValue)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::PanNode *pan = AddNode<olive::PanNode>(&project);
ConnectTestSamples(&project, pan, olive::PanNode::kSamplesInput);
// A non-static (keyframed) pan makes Value() push a SampleJob instead of
// processing the buffer immediately
pan->SetInputIsKeyframing(olive::PanNode::kPanningInput, true);
AddKey(pan, olive::PanNode::kPanningInput, olive::core::rational(0), 1.0);
const olive::NodeValueTable table = GenerateTableAt(pan, olive::core::rational(0));
olive::NodeValue result = table.Get(olive::NodeValue::kSamples);
ASSERT_EQ(result.type(), olive::NodeValue::kSamples);
ASSERT_TRUE(result.canConvert<olive::SampleJob>());
// NOTE: Unlike VolumeNode, PanNode::Value() never inserts the panning
// value into the SampleJob, so the job's value map is empty and
// ProcessSamples() always sees a pan of 0 (a plain copy). The production
// RenderProcessor only re-evaluates inputs present in the job, so
// keyframed pan is silently ignored (suspected bug, documented here).
const olive::SampleJob job = result.value<olive::SampleJob>();
EXPECT_FALSE(job.GetValues().contains(olive::PanNode::kPanningInput));
SampleResolvingTraverser resolver;
resolver.Resolve(result);
const olive::core::SampleBuffer out = result.toSamples();
ASSERT_TRUE(out.is_allocated());
ASSERT_EQ(out.sample_count(), 4u);
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], float(i + 1));
EXPECT_FLOAT_EQ(out.data(1)[i], float(i + 5));
}
}
TEST(PanNode, ProcessSamplesAppliesPanPerSample)
{
olive::PanNode pan;
olive::NodeValueRow row;
row.insert(olive::PanNode::kPanningInput,
olive::NodeValue(olive::NodeValue::kFloat, 0.5));
olive::core::SampleBuffer input(StereoParams(), 2);
olive::core::SampleBuffer output(StereoParams(), 2);
input.data(0)[0] = 1.5f;
input.data(0)[1] = -2.0f;
input.data(1)[0] = 0.25f;
input.data(1)[1] = 8.0f;
pan.ProcessSamples(row, input, output, 0);
pan.ProcessSamples(row, input, output, 1);
// Panning right attenuates the left channel only
EXPECT_FLOAT_EQ(output.data(0)[0], 0.75f);
EXPECT_FLOAT_EQ(output.data(0)[1], -1.0f);
EXPECT_FLOAT_EQ(output.data(1)[0], 0.25f);
EXPECT_FLOAT_EQ(output.data(1)[1], 8.0f);
// Panning left attenuates the right channel only
row.insert(olive::PanNode::kPanningInput,
olive::NodeValue(olive::NodeValue::kFloat, -0.25));
pan.ProcessSamples(row, input, output, 0);
EXPECT_FLOAT_EQ(output.data(0)[0], 1.5f);
EXPECT_FLOAT_EQ(output.data(1)[0], 0.1875f);
}
TEST(PanNode, ProcessSamplesWithoutPanValueCopiesInput)
{
olive::PanNode pan;
// No panning value in the row: samples are copied unchanged
olive::NodeValueRow row;
olive::core::SampleBuffer input(StereoParams(), 1);
olive::core::SampleBuffer output(StereoParams(), 1);
input.data(0)[0] = 3.0f;
input.data(1)[0] = -4.0f;
pan.ProcessSamples(row, input, output, 0);
EXPECT_FLOAT_EQ(output.data(0)[0], 3.0f);
EXPECT_FLOAT_EQ(output.data(1)[0], -4.0f);
}
TEST(VolumeNode, Metadata)
{
olive::VolumeNode volume;
EXPECT_EQ(volume.Name(), QStringLiteral("Volume"));
EXPECT_EQ(volume.id(),
QStringLiteral("org.olivevideoeditor.Olive.volume"));
EXPECT_FALSE(volume.Description().isEmpty());
EXPECT_TRUE(volume.Category().contains(olive::Node::kCategoryFilter));
EXPECT_TRUE(volume.GetFlags() & olive::Node::kAudioEffect);
EXPECT_EQ(volume.GetEffectInputID(), olive::VolumeNode::kSamplesInput);
}
TEST(VolumeNode, InputDefaults)
{
olive::VolumeNode volume;
EXPECT_EQ(volume.GetInputDataType(olive::VolumeNode::kSamplesInput),
olive::NodeValue::kSamples);
EXPECT_FALSE(volume.IsInputKeyframable(olive::VolumeNode::kSamplesInput));
EXPECT_EQ(volume.GetInputDataType(olive::VolumeNode::kVolumeInput),
olive::NodeValue::kFloat);
EXPECT_TRUE(volume.IsInputKeyframable(olive::VolumeNode::kVolumeInput));
EXPECT_DOUBLE_EQ(
volume.GetStandardValue(olive::VolumeNode::kVolumeInput).toDouble(),
1.0);
EXPECT_DOUBLE_EQ(volume.GetInputProperty(olive::VolumeNode::kVolumeInput,
QStringLiteral("min"))
.toDouble(),
0.0);
EXPECT_EQ(int(volume.GetInputProperty(olive::VolumeNode::kVolumeInput,
QStringLiteral("view"))
.toInt()),
int(olive::FloatSlider::kDecibel));
}
TEST(VolumeNode, RetranslateSetsInputNames)
{
olive::VolumeNode volume;
volume.Retranslate();
EXPECT_EQ(volume.GetInputName(olive::VolumeNode::kSamplesInput),
QStringLiteral("Samples"));
EXPECT_EQ(volume.GetInputName(olive::VolumeNode::kVolumeInput),
QStringLiteral("Volume"));
}
TEST(VolumeNode, StaticVolumeScalesSamples)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::VolumeNode *volume = AddNode<olive::VolumeNode>(&project);
volume->SetStandardValue(olive::VolumeNode::kVolumeInput, 2.0);
ConnectTestSamples(&project, volume, olive::VolumeNode::kSamplesInput);
const olive::NodeValueTable table = GenerateTableAt(volume, olive::core::rational(0));
const olive::core::SampleBuffer out =
table.Get(olive::NodeValue::kSamples).toSamples();
ASSERT_TRUE(out.is_allocated());
ASSERT_EQ(out.sample_count(), 4u);
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], float(i + 1) * 2.0f);
EXPECT_FLOAT_EQ(out.data(1)[i], float(i + 5) * 2.0f);
}
}
TEST(VolumeNode, StaticUnityVolumeLeavesSamplesUnchanged)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::VolumeNode *volume = AddNode<olive::VolumeNode>(&project);
volume->SetStandardValue(olive::VolumeNode::kVolumeInput, 1.0);
ConnectTestSamples(&project, volume, olive::VolumeNode::kSamplesInput);
// Volume 1 is a no-op, but the (unmodified) buffer is still pushed
const olive::NodeValueTable table = GenerateTableAt(volume, olive::core::rational(0));
const olive::core::SampleBuffer out =
table.Get(olive::NodeValue::kSamples).toSamples();
ASSERT_TRUE(out.is_allocated());
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], float(i + 1));
EXPECT_FLOAT_EQ(out.data(1)[i], float(i + 5));
}
}
TEST(VolumeNode, StaticZeroVolumeSilencesSamples)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::VolumeNode *volume = AddNode<olive::VolumeNode>(&project);
volume->SetStandardValue(olive::VolumeNode::kVolumeInput, 0.0);
ConnectTestSamples(&project, volume, olive::VolumeNode::kSamplesInput);
const olive::NodeValueTable table = GenerateTableAt(volume, olive::core::rational(0));
const olive::core::SampleBuffer out =
table.Get(olive::NodeValue::kSamples).toSamples();
ASSERT_TRUE(out.is_allocated());
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], 0.0f);
EXPECT_FLOAT_EQ(out.data(1)[i], 0.0f);
}
}
TEST(VolumeNode, KeyframedVolumeProducesSampleJobWithInterpolatedValue)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::VolumeNode *volume = AddNode<olive::VolumeNode>(&project);
ConnectTestSamples(&project, volume, olive::VolumeNode::kSamplesInput);
// A non-static (keyframed) volume makes Value() push a SampleJob carrying
// the volume value instead of processing the buffer immediately
volume->SetInputIsKeyframing(olive::VolumeNode::kVolumeInput, true);
AddKey(volume, olive::VolumeNode::kVolumeInput, olive::core::rational(0),
1.0);
AddKey(volume, olive::VolumeNode::kVolumeInput, olive::core::rational(1),
3.0);
// At t=0.5 the linear ramp 1.0 -> 3.0 interpolates to 2.0
const olive::NodeValueTable table = GenerateTableAt(volume, olive::core::rational(1, 2));
olive::NodeValue result = table.Get(olive::NodeValue::kSamples);
ASSERT_EQ(result.type(), olive::NodeValue::kSamples);
ASSERT_TRUE(result.canConvert<olive::SampleJob>());
const olive::SampleJob job = result.value<olive::SampleJob>();
ASSERT_TRUE(job.GetValues().contains(olive::VolumeNode::kVolumeInput));
EXPECT_DOUBLE_EQ(job.Get(olive::VolumeNode::kVolumeInput).toDouble(), 2.0);
// Resolve the job on the CPU and verify the scaled samples
SampleResolvingTraverser resolver;
resolver.Resolve(result);
const olive::core::SampleBuffer out = result.toSamples();
ASSERT_TRUE(out.is_allocated());
ASSERT_EQ(out.sample_count(), 4u);
for (int i = 0; i < 4; i++) {
EXPECT_FLOAT_EQ(out.data(0)[i], float(i + 1) * 2.0f);
EXPECT_FLOAT_EQ(out.data(1)[i], float(i + 5) * 2.0f);
}
}
TEST(VolumeNode, ProcessSamplesMultipliesPerSample)
{
olive::VolumeNode volume;
olive::NodeValueRow row;
row.insert(olive::VolumeNode::kVolumeInput,
olive::NodeValue(olive::NodeValue::kFloat, 2.0));
olive::core::SampleBuffer input(StereoParams(), 2);
olive::core::SampleBuffer output(StereoParams(), 2);
input.data(0)[0] = 1.5f;
input.data(0)[1] = -2.0f;
input.data(1)[0] = 0.25f;
input.data(1)[1] = 8.0f;
volume.ProcessSamples(row, input, output, 0);
volume.ProcessSamples(row, input, output, 1);
EXPECT_FLOAT_EQ(output.data(0)[0], 3.0f);
EXPECT_FLOAT_EQ(output.data(0)[1], -4.0f);
EXPECT_FLOAT_EQ(output.data(1)[0], 0.5f);
EXPECT_FLOAT_EQ(output.data(1)[1], 16.0f);
}
TEST(VolumeNode, ProcessSamplesWithoutVolumeLeavesOutputUntouched)
{
olive::VolumeNode volume;
// Neither the samples nor the volume input carries a number: the output
// must not be written
olive::NodeValueRow row;
olive::core::SampleBuffer input(StereoParams(), 1);
olive::core::SampleBuffer output(StereoParams(), 1);
input.data(0)[0] = 10.0f;
output.data(0)[0] = 123.0f;
output.data(1)[0] = 45.0f;
volume.ProcessSamples(row, input, output, 0);
EXPECT_FLOAT_EQ(output.data(0)[0], 123.0f);
EXPECT_FLOAT_EQ(output.data(1)[0], 45.0f);
}
TEST(TimeInput, Metadata)
{
olive::TimeInput time;
EXPECT_EQ(time.Name(), QStringLiteral("Time"));
EXPECT_EQ(time.id(), QStringLiteral("org.olivevideoeditor.Olive.time"));
EXPECT_FALSE(time.Description().isEmpty());
EXPECT_TRUE(time.Category().contains(olive::Node::kCategoryTime));
}
TEST(TimeInput, ValuePushesCurrentTimeInSeconds)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::TimeInput *time = AddNode<olive::TimeInput>(&project);
olive::NodeValueTable table = GenerateTableAt(time, olive::core::rational(0));
olive::NodeValue result = table.Get(olive::NodeValue::kFloat);
ASSERT_EQ(result.type(), olive::NodeValue::kFloat);
EXPECT_DOUBLE_EQ(result.toDouble(), 0.0);
table = GenerateTableAt(time, olive::core::rational(5, 2));
result = table.Get(olive::NodeValue::kFloat);
ASSERT_EQ(result.type(), olive::NodeValue::kFloat);
EXPECT_DOUBLE_EQ(result.toDouble(), 2.5);
}
TEST(ValueNode, Metadata)
{
olive::ValueNode value;
EXPECT_EQ(value.Name(), QStringLiteral("Value"));
EXPECT_EQ(value.id(), QStringLiteral("org.olivevideoeditor.Olive.value"));
EXPECT_FALSE(value.Description().isEmpty());
EXPECT_TRUE(value.Category().contains(olive::Node::kCategoryGenerator));
}
TEST(ValueNode, InputDefaults)
{
olive::ValueNode value;
EXPECT_EQ(value.GetInputDataType(olive::ValueNode::kTypeInput),
olive::NodeValue::kCombo);
EXPECT_FALSE(value.IsInputConnectable(olive::ValueNode::kTypeInput));
EXPECT_FALSE(value.IsInputKeyframable(olive::ValueNode::kTypeInput));
EXPECT_EQ(value.GetStandardValue(olive::ValueNode::kTypeInput).toInt(), 0);
// The value input starts out as a float (the first supported type)
EXPECT_EQ(value.GetInputDataType(olive::ValueNode::kValueInput),
olive::NodeValue::kFloat);
EXPECT_FALSE(value.IsInputConnectable(olive::ValueNode::kValueInput));
EXPECT_TRUE(value.IsInputKeyframable(olive::ValueNode::kValueInput));
}
TEST(ValueNode, RetranslateSetsInputNamesAndTypeCombo)
{
olive::ValueNode value;
value.Retranslate();
EXPECT_EQ(value.GetInputName(olive::ValueNode::kTypeInput),
QStringLiteral("Type"));
EXPECT_EQ(value.GetInputName(olive::ValueNode::kValueInput),
QStringLiteral("Value"));
// The type combo lists the pretty name of every supported type, in order
const QStringList types =
value.GetInputProperty(olive::ValueNode::kTypeInput,
QStringLiteral("combo_str"))
.toStringList();
ASSERT_EQ(types.size(), 11);
EXPECT_EQ(types.at(0), QStringLiteral("Float"));
EXPECT_EQ(types.at(1), QStringLiteral("Integer"));
EXPECT_EQ(types.at(2), QStringLiteral("Rational"));
EXPECT_EQ(types.at(3), QStringLiteral("Vector 2D"));
EXPECT_EQ(types.at(4), QStringLiteral("Vector 3D"));
EXPECT_EQ(types.at(5), QStringLiteral("Vector 4D"));
EXPECT_EQ(types.at(6), QStringLiteral("Color"));
EXPECT_EQ(types.at(7), QStringLiteral("Text"));
EXPECT_EQ(types.at(8), QStringLiteral("Matrix"));
EXPECT_EQ(types.at(9), QStringLiteral("Font"));
EXPECT_EQ(types.at(10), QStringLiteral("Boolean"));
}
TEST(ValueNode, ChangingTypeSwitchesValueInputDataType)
{
olive::ValueNode value;
EXPECT_EQ(value.GetInputDataType(olive::ValueNode::kValueInput),
olive::NodeValue::kFloat);
value.SetStandardValue(olive::ValueNode::kTypeInput, 1);
EXPECT_EQ(value.GetInputDataType(olive::ValueNode::kValueInput),
olive::NodeValue::kInt);
value.SetStandardValue(olive::ValueNode::kTypeInput, 3);
EXPECT_EQ(value.GetInputDataType(olive::ValueNode::kValueInput),
olive::NodeValue::kVec2);
value.SetStandardValue(olive::ValueNode::kTypeInput, 6);
EXPECT_EQ(value.GetInputDataType(olive::ValueNode::kValueInput),
olive::NodeValue::kColor);
value.SetStandardValue(olive::ValueNode::kTypeInput, 10);
EXPECT_EQ(value.GetInputDataType(olive::ValueNode::kValueInput),
olive::NodeValue::kBoolean);
value.SetStandardValue(olive::ValueNode::kTypeInput, 0);
EXPECT_EQ(value.GetInputDataType(olive::ValueNode::kValueInput),
olive::NodeValue::kFloat);
}
TEST(ValueNode, ValuePassesThroughFloat)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::ValueNode *value = AddNode<olive::ValueNode>(&project);
value->SetStandardValue(olive::ValueNode::kValueInput, 3.5);
const olive::NodeValueTable table = GenerateTableAt(value, olive::core::rational(0));
const olive::NodeValue result = table.Get(olive::NodeValue::kFloat);
ASSERT_EQ(result.type(), olive::NodeValue::kFloat);
EXPECT_DOUBLE_EQ(result.toDouble(), 3.5);
}
TEST(ValueNode, ValuePassesThroughVectorAfterTypeSwitch)
{
olive::ColorManager::SetUpDefaultConfig();
olive::Project project;
project.Initialize();
olive::ValueNode *value = AddNode<olive::ValueNode>(&project);
// Switch the value input to kVec3 (index 4) and set a vector value
value->SetStandardValue(olive::ValueNode::kTypeInput, 4);
value->SetStandardValue(olive::ValueNode::kValueInput,
QVector3D(1.0f, 2.0f, 3.0f));
const olive::NodeValueTable table = GenerateTableAt(value, olive::core::rational(0));
const olive::NodeValue result = table.Get(olive::NodeValue::kVec3);
ASSERT_EQ(result.type(), olive::NodeValue::kVec3);
const QVector3D vec = result.toVec3();
EXPECT_FLOAT_EQ(vec.x(), 1.0f);
EXPECT_FLOAT_EQ(vec.y(), 2.0f);
EXPECT_FLOAT_EQ(vec.z(), 3.0f);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff