diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt
index 85e367ba1..68bb5194f 100644
--- a/app/node/CMakeLists.txt
+++ b/app/node/CMakeLists.txt
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+add_subdirectory(audio)
add_subdirectory(block)
add_subdirectory(distort)
add_subdirectory(input)
diff --git a/app/node/audio/CMakeLists.txt b/app/node/audio/CMakeLists.txt
new file mode 100644
index 000000000..e8bfe8647
--- /dev/null
+++ b/app/node/audio/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2019 Olive Team
+#
+# 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 3 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. If not, see .
+
+add_subdirectory(pan)
+add_subdirectory(volume)
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ PARENT_SCOPE
+)
diff --git a/app/node/audio/pan/CMakeLists.txt b/app/node/audio/pan/CMakeLists.txt
new file mode 100644
index 000000000..66384293d
--- /dev/null
+++ b/app/node/audio/pan/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2019 Olive Team
+#
+# 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 3 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. If not, see .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ node/audio/pan/pan.h
+ node/audio/pan/pan.cpp
+ PARENT_SCOPE
+)
diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp
new file mode 100644
index 000000000..a289eb8c8
--- /dev/null
+++ b/app/node/audio/pan/pan.cpp
@@ -0,0 +1,67 @@
+#include "pan.h"
+
+PanNode::PanNode()
+{
+ samples_input_ = new NodeInput("samples_in");
+ samples_input_->set_data_type(NodeParam::kSamples);
+ AddInput(samples_input_);
+
+ panning_input_ = new NodeInput("panning_in");
+ panning_input_->set_data_type(NodeParam::kFloat);
+ panning_input_->set_minimum(-1);
+ panning_input_->set_maximum(1);
+ panning_input_->set_value_at_time(0, 0);
+ AddInput(panning_input_);
+}
+
+Node *PanNode::copy() const
+{
+ return new PanNode();
+}
+
+QString PanNode::Name() const
+{
+ return tr("Pan");
+}
+
+QString PanNode::id() const
+{
+ return "org.olivevideoeditor.Olive.pan";
+}
+
+QString PanNode::Category() const
+{
+ return tr("Audio");
+}
+
+QString PanNode::Description() const
+{
+ return tr("Adjust the stereo panning of an audio source.");
+}
+
+bool PanNode::ProcessesSamples() const
+{
+ return true;
+}
+
+void PanNode::ProcessSamples(const NodeValueDatabase &values, const AudioRenderingParams ¶ms, const float *input, float *output, int index) const
+{
+ if (params.channel_count() != 2) {
+ // This node currently only works for stereo audio
+ return;
+ }
+
+ float pan_val = values[panning_input_].Get(NodeParam::kFloat).toFloat();
+
+ if (index%2 == 0) {
+ // Sample is left channel
+ if (pan_val > 0) {
+ output[index] = input[index] * (1.0F - pan_val);
+ }
+ } else {
+ // Sample is right channel
+ if (pan_val < 0) {
+ output[index] = input[index] * (1.0F - qAbs(pan_val));
+ }
+ }
+}
diff --git a/app/node/audio/pan/pan.h b/app/node/audio/pan/pan.h
new file mode 100644
index 000000000..a999cdf16
--- /dev/null
+++ b/app/node/audio/pan/pan.h
@@ -0,0 +1,27 @@
+#ifndef PANNODE_H
+#define PANNODE_H
+
+#include "node/node.h"
+
+class PanNode : public Node
+{
+public:
+ PanNode();
+
+ virtual Node* copy() const override;
+
+ virtual QString Name() const override;
+ virtual QString id() const override;
+ virtual QString Category() const override;
+ virtual QString Description() const override;
+
+ virtual bool ProcessesSamples() const override;
+ virtual void ProcessSamples(const NodeValueDatabase& values, const AudioRenderingParams& params, const float* input, float* output, int index) const override;
+
+private:
+ NodeInput* samples_input_;
+ NodeInput* panning_input_;
+
+};
+
+#endif // PANNODE_H
diff --git a/app/node/audio/volume/CMakeLists.txt b/app/node/audio/volume/CMakeLists.txt
new file mode 100644
index 000000000..6956ebd0c
--- /dev/null
+++ b/app/node/audio/volume/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2019 Olive Team
+#
+# 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 3 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. If not, see .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ node/audio/volume/volume.h
+ node/audio/volume/volume.cpp
+ PARENT_SCOPE
+)
diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp
new file mode 100644
index 000000000..87c4c23f3
--- /dev/null
+++ b/app/node/audio/volume/volume.cpp
@@ -0,0 +1,52 @@
+#include "volume.h"
+
+VolumeNode::VolumeNode()
+{
+ samples_input_ = new NodeInput("samples_in");
+ samples_input_->set_data_type(NodeParam::kSamples);
+ AddInput(samples_input_);
+
+ volume_input_ = new NodeInput("volume_in");
+ volume_input_->set_data_type(NodeParam::kFloat);
+ volume_input_->set_minimum(0);
+ volume_input_->set_maximum(1);
+ volume_input_->set_value_at_time(0, 1);
+ AddInput(volume_input_);
+}
+
+Node *VolumeNode::copy() const
+{
+ return new VolumeNode();
+}
+
+QString VolumeNode::Name() const
+{
+ return tr("Volume");
+}
+
+QString VolumeNode::id() const
+{
+ return "org.olivevideoeditor.Olive.volume";
+}
+
+QString VolumeNode::Category() const
+{
+ return tr("Audio");
+}
+
+QString VolumeNode::Description() const
+{
+ return tr("Adjusts the volume of an audio source.");
+}
+
+bool VolumeNode::ProcessesSamples() const
+{
+ return true;
+}
+
+void VolumeNode::ProcessSamples(const NodeValueDatabase& values, const AudioRenderingParams& params, const float* input, float* output, int index) const
+{
+ float volume_val = values[volume_input_].Get(NodeParam::kFloat).toFloat();
+
+ output[index] = input[index] * volume_val;
+}
diff --git a/app/node/audio/volume/volume.h b/app/node/audio/volume/volume.h
new file mode 100644
index 000000000..c7c1f2cf6
--- /dev/null
+++ b/app/node/audio/volume/volume.h
@@ -0,0 +1,27 @@
+#ifndef VOLUMENODE_H
+#define VOLUMENODE_H
+
+#include "node/node.h"
+
+class VolumeNode : public Node
+{
+public:
+ VolumeNode();
+
+ virtual Node* copy() const override;
+
+ virtual QString Name() const override;
+ virtual QString id() const override;
+ virtual QString Category() const override;
+ virtual QString Description() const override;
+
+ virtual bool ProcessesSamples() const override;
+ virtual void ProcessSamples(const NodeValueDatabase& values, const AudioRenderingParams& params, const float* input, float* output, int index) const override;
+
+private:
+ NodeInput* samples_input_;
+ NodeInput* volume_input_;
+
+};
+
+#endif // VOLUMENODE_H
diff --git a/app/node/factory.cpp b/app/node/factory.cpp
index c7af032fe..e1a808816 100644
--- a/app/node/factory.cpp
+++ b/app/node/factory.cpp
@@ -1,5 +1,7 @@
#include "factory.h"
+#include "audio/pan/pan.h"
+#include "audio/volume/volume.h"
#include "block/clip/clip.h"
#include "block/gap/gap.h"
#include "block/transition/externaltransition.h"
@@ -131,6 +133,10 @@ Node *NodeFactory::CreateInternal(const NodeFactory::InternalID &id)
return new TrackOutput();
case kViewerOutput:
return new ViewerOutput();
+ case kAudioVolume:
+ return new VolumeNode();
+ case kAudioPanning:
+ return new PanNode();
case kInternalNodeCount:
break;
diff --git a/app/node/factory.h b/app/node/factory.h
index b2607ed6a..c1fabffa8 100644
--- a/app/node/factory.h
+++ b/app/node/factory.h
@@ -18,6 +18,8 @@ public:
kVideoInput,
kTimelineOutput,
kTrackOutput,
+ kAudioVolume,
+ kAudioPanning,
// Count value
kInternalNodeCount
diff --git a/app/node/node.cpp b/app/node/node.cpp
index 10254bda5..1ce66da7b 100644
--- a/app/node/node.cpp
+++ b/app/node/node.cpp
@@ -390,6 +390,20 @@ NodeInput *Node::AcceleratedCodeIterativeInput() const
return nullptr;
}
+bool Node::ProcessesSamples() const
+{
+ return false;
+}
+
+void Node::ProcessSamples(const NodeValueDatabase &values, const AudioRenderingParams ¶ms, const float *input, float *output, int index) const
+{
+ Q_UNUSED(values)
+ Q_UNUSED(params)
+ Q_UNUSED(input)
+ Q_UNUSED(output)
+ Q_UNUSED(index)
+}
+
NodeParam *Node::GetParameterWithID(const QString &id) const
{
foreach (NodeParam* param, params_) {
diff --git a/app/node/node.h b/app/node/node.h
index b5917fc87..b9fa15cde 100644
--- a/app/node/node.h
+++ b/app/node/node.h
@@ -32,6 +32,7 @@
#include "node/inputarray.h"
#include "node/output.h"
#include "node/value.h"
+#include "render/audioparams.h"
/**
* @brief A single processing unit that can be connected with others to create intricate processing systems
@@ -156,6 +157,16 @@ public:
*/
virtual NodeInput* AcceleratedCodeIterativeInput() const;
+ /**
+ * @brief Return whether this node processes samples or not
+ */
+ virtual bool ProcessesSamples() const;
+
+ /**
+ * @brief If ProcessesSamples() is true, this is the function that will process them.
+ */
+ virtual void ProcessSamples(const NodeValueDatabase& values, const AudioRenderingParams& params, const float* input, float* output, int index) const;
+
/**
* @brief Returns the parameter with the specified ID (or nullptr if it doesn't exist)
*/
diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp
index 4b07e2fc4..0d34f1783 100644
--- a/app/render/backend/exporter.cpp
+++ b/app/render/backend/exporter.cpp
@@ -145,7 +145,7 @@ void Exporter::FrameRendered(const rational &time, QVariant value)
} while (cached_frames_.contains(waiting_for_frame_));
if (waiting_for_frame_ >= viewer_node_->Length()) {
-S ExportSucceeded();
+ ExportSucceeded();
}
} else {
cached_frames_.insert(time, value);