summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2022-01-23 22:21:29 +0100
committerCarl Hetherington <cth@carlh.net>2022-04-04 23:09:12 +0200
commit0e164ad80f0ceff9d643f3b466690d013c3be19d (patch)
treec656a136c12ff3c5d9bf8f7331610162de0c0592 /src/lib
parent8eb951b71fa90e54c8da64e54cf5ddf6bf0809cf (diff)
Add fade in/out option to the content audio tab (#1026).
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/audio_content.cc60
-rw-r--r--src/lib/audio_content.h24
-rw-r--r--src/lib/maths_util.cc17
-rw-r--r--src/lib/maths_util.h15
-rw-r--r--src/lib/player.cc28
-rw-r--r--src/lib/video_content.cc1
6 files changed, 139 insertions, 6 deletions
diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc
index 32e713ff2..ad8d7df0f 100644
--- a/src/lib/audio_content.cc
+++ b/src/lib/audio_content.cc
@@ -53,6 +53,8 @@ using namespace dcpomatic;
int const AudioContentProperty::STREAMS = 200;
int const AudioContentProperty::GAIN = 201;
int const AudioContentProperty::DELAY = 202;
+int const AudioContentProperty::FADE_IN = 203;
+int const AudioContentProperty::FADE_OUT = 204;
AudioContent::AudioContent (Content* parent)
@@ -90,6 +92,8 @@ AudioContent::AudioContent (Content* parent, cxml::ConstNodePtr node)
{
_gain = node->number_child<double> ("AudioGain");
_delay = node->number_child<int> ("AudioDelay");
+ _fade_in = ContentTime(node->optional_number_child<ContentTime::Type>("AudioFadeIn").get_value_or(0));
+ _fade_out = ContentTime(node->optional_number_child<ContentTime::Type>("AudioFadeOut").get_value_or(0));
/* Backwards compatibility */
auto r = node->optional_number_child<double>("AudioVideoFrameRate");
@@ -127,6 +131,8 @@ AudioContent::as_xml (xmlpp::Node* node) const
boost::mutex::scoped_lock lm (_mutex);
node->add_child("AudioGain")->add_child_text(raw_convert<string>(_gain));
node->add_child("AudioDelay")->add_child_text(raw_convert<string>(_delay));
+ node->add_child("AudioFadeIn")->add_child_text(raw_convert<string>(_fade_in.get()));
+ node->add_child("AudioFadeOut")->add_child_text(raw_convert<string>(_fade_out.get()));
}
@@ -420,3 +426,57 @@ AudioContent::modify_trim_start (ContentTime& trim) const
/* XXX: we're in trouble if streams have different rates */
trim = trim.round (_streams.front()->frame_rate());
}
+
+
+void
+AudioContent::set_fade_in (ContentTime t)
+{
+ maybe_set (_fade_in, t, AudioContentProperty::FADE_IN);
+}
+
+
+void
+AudioContent::set_fade_out (ContentTime t)
+{
+ maybe_set (_fade_out, t, AudioContentProperty::FADE_OUT);
+}
+
+
+vector<float>
+AudioContent::fade (AudioStreamPtr stream, Frame frame, Frame length, int frame_rate) const
+{
+ auto const in = fade_in().frames_round(frame_rate);
+ auto const out = fade_out().frames_round(frame_rate);
+
+ /* Where the start trim ends, at frame_rate */
+ auto const trim_start = _parent->trim_start().frames_round(frame_rate);
+ /* Where the end trim starts within the whole length of the content, at frame_rate */
+ auto const trim_end = ContentTime(ContentTime::from_frames(stream->length(), stream->frame_rate()) - _parent->trim_end()).frames_round(frame_rate);
+
+ if (
+ (in == 0 || (frame >= (trim_start + in))) &&
+ (out == 0 || ((frame + length) < (trim_end - out)))
+ ) {
+ /* This section starts after the fade in and ends before the fade out */
+ return {};
+ }
+
+ /* Start position relative to the start of the fade in */
+ auto in_start = frame - trim_start;
+ /* Start position relative to the start of the fade out */
+ auto out_start = frame - (trim_end - out);
+
+ vector<float> coeffs(length);
+ for (auto coeff = 0; coeff < length; ++coeff) {
+ coeffs[coeff] = 1.0;
+ if (in) {
+ coeffs[coeff] *= logarithmic_fade_in_curve(static_cast<float>(in_start + coeff) / in);
+ }
+ if (out) {
+ coeffs[coeff] *= logarithmic_fade_out_curve(static_cast<float>(out_start + coeff) / out);
+ }
+ }
+
+ return coeffs;
+}
+
diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h
index 4dc45f114..ba998a5ad 100644
--- a/src/lib/audio_content.h
+++ b/src/lib/audio_content.h
@@ -42,6 +42,8 @@ public:
static int const STREAMS;
static int const GAIN;
static int const DELAY;
+ static int const FADE_IN;
+ static int const FADE_OUT;
};
@@ -76,6 +78,19 @@ public:
return _delay;
}
+ dcpomatic::ContentTime fade_in () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _fade_in;
+ }
+
+ dcpomatic::ContentTime fade_out () const {
+ boost::mutex::scoped_lock lm (_mutex);
+ return _fade_out;
+ }
+
+ void set_fade_in (dcpomatic::ContentTime time);
+ void set_fade_out (dcpomatic::ContentTime time);
+
std::string processing_description (std::shared_ptr<const Film> film) const;
std::vector<AudioStreamPtr> streams () const {
@@ -93,6 +108,13 @@ public:
void modify_position (std::shared_ptr<const Film> film, dcpomatic::DCPTime& pos) const;
void modify_trim_start (dcpomatic::ContentTime& pos) const;
+ /** @param frame frame within the whole (untrimmed) content.
+ * @param frame_rate The frame rate of the audio (it may have been resampled).
+ * @return a fade coefficient for @ref length samples starting at an offset @frame within
+ * the content, or an empty vector if the given section has no fade.
+ */
+ std::vector<float> fade (AudioStreamPtr stream, Frame frame, Frame length, int frame_rate) const;
+
static std::shared_ptr<AudioContent> from_xml (Content* parent, cxml::ConstNodePtr, int version);
private:
@@ -101,6 +123,8 @@ private:
double _gain = 0;
/** Delay to apply to audio (positive moves audio later) in milliseconds */
int _delay = 0;
+ dcpomatic::ContentTime _fade_in;
+ dcpomatic::ContentTime _fade_out;
std::vector<AudioStreamPtr> _streams;
};
diff --git a/src/lib/maths_util.cc b/src/lib/maths_util.cc
index 35e3879c4..76681afb6 100644
--- a/src/lib/maths_util.cc
+++ b/src/lib/maths_util.cc
@@ -19,6 +19,7 @@
*/
+#include "maths_util.h"
#include <cmath>
@@ -35,3 +36,19 @@ linear_to_db (double linear)
return 20 * log10(linear);
}
+
+float
+logarithmic_fade_in_curve (float t)
+{
+ auto const c = clamp(t, 0.0f, 1.0f);
+ return std::exp(2 * (c - 1)) * c;
+}
+
+
+float
+logarithmic_fade_out_curve (float t)
+{
+ auto const c = clamp(t, 0.0f, 1.0f);
+ return std::exp(-2 * c) * (1 - c);
+}
+
diff --git a/src/lib/maths_util.h b/src/lib/maths_util.h
index 8adefbbc4..24c4b547f 100644
--- a/src/lib/maths_util.h
+++ b/src/lib/maths_util.h
@@ -30,6 +30,21 @@
extern double db_to_linear (double db);
extern double linear_to_db (double linear);
+/** @return linear gain according to a logarithmic curve, for fading in.
+ * t < 0: linear gain of 0
+ * 0 >= t >= 1: logarithmic fade in curve
+ * t > 1: linear gain of 1
+ */
+extern float logarithmic_fade_in_curve (float t);
+
+
+/** @return linear gain according to a logarithmic curve, for fading out.
+ * t > 1: linear gain of 0
+ * 0 >= t >= 1: logarithmic fade out curve
+ * t < 0: linear gain of 1
+ */
+extern float logarithmic_fade_out_curve (float t);
+
template <class T>
T clamp (T val, T minimum, T maximum)
diff --git a/src/lib/player.cc b/src/lib/player.cc
index abd051bed..12a53bc63 100644
--- a/src/lib/player.cc
+++ b/src/lib/player.cc
@@ -1049,12 +1049,28 @@ Player::audio (weak_ptr<Piece> wp, AudioStreamPtr stream, ContentAudio content_a
DCPOMATIC_ASSERT (content_audio.audio->frames() > 0);
- /* Gain */
-
- if (content->gain() != 0) {
- auto gain = make_shared<AudioBuffers>(content_audio.audio);
- gain->apply_gain (content->gain());
- content_audio.audio = gain;
+ /* Gain and fade */
+
+ auto const fade_coeffs = content->fade (stream, content_audio.frame, content_audio.audio->frames(), rfr);
+ if (content->gain() != 0 || !fade_coeffs.empty()) {
+ auto gain_buffers = make_shared<AudioBuffers>(content_audio.audio);
+ if (!fade_coeffs.empty()) {
+ /* Apply both fade and gain */
+ DCPOMATIC_ASSERT (fade_coeffs.size() == static_cast<size_t>(gain_buffers->frames()));
+ auto const channels = gain_buffers->channels();
+ auto const frames = fade_coeffs.size();
+ auto data = gain_buffers->data();
+ auto const gain = db_to_linear (content->gain());
+ for (auto channel = 0; channel < channels; ++channel) {
+ for (auto frame = 0U; frame < frames; ++frame) {
+ data[channel][frame] *= gain * fade_coeffs[frame];
+ }
+ }
+ } else {
+ /* Just apply gain */
+ gain_buffers->apply_gain (content->gain());
+ }
+ content_audio.audio = gain_buffers;
}
/* Remap */
diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc
index 9ff35ffdf..853204575 100644
--- a/src/lib/video_content.cc
+++ b/src/lib/video_content.cc
@@ -173,6 +173,7 @@ VideoContent::VideoContent (Content* parent, cxml::ConstNodePtr node, int versio
_yuv = node->optional_bool_child("YUV").get_value_or (true);
if (version >= 32) {
+ /* These should be VideoFadeIn and VideoFadeOut but we'll leave them like this until 2.18.x */
_fade_in = node->number_child<Frame> ("FadeIn");
_fade_out = node->number_child<Frame> ("FadeOut");
} else {