summaryrefslogtreecommitdiff
path: root/src
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
parent8eb951b71fa90e54c8da64e54cf5ddf6bf0809cf (diff)
Add fade in/out option to the content audio tab (#1026).
Diffstat (limited to 'src')
-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
-rw-r--r--src/wx/audio_panel.cc87
-rw-r--r--src/wx/audio_panel.h9
8 files changed, 227 insertions, 14 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 {
diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc
index af030e915..6ac673f4d 100644
--- a/src/wx/audio_panel.cc
+++ b/src/wx/audio_panel.cc
@@ -28,30 +28,32 @@
#include "gain_calculator_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "lib/audio_content.h"
+#include "lib/cinema_sound_processor.h"
#include "lib/config.h"
+#include "lib/dcp_content.h"
#include "lib/ffmpeg_audio_stream.h"
#include "lib/ffmpeg_content.h"
-#include "lib/cinema_sound_processor.h"
#include "lib/job_manager.h"
-#include "lib/dcp_content.h"
-#include "lib/audio_content.h"
#include "lib/maths_util.h"
#include <wx/spinctrl.h>
#include <iostream>
-using std::vector;
using std::cout;
-using std::string;
+using std::dynamic_pointer_cast;
using std::list;
-using std::pair;
using std::make_shared;
-using std::dynamic_pointer_cast;
+using std::pair;
+using std::set;
using std::shared_ptr;
+using std::string;
+using std::vector;
using boost::optional;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
#endif
+using namespace dcpomatic;
AudioPanel::AudioPanel (ContentPanel* p)
@@ -102,6 +104,12 @@ AudioPanel::create ()
/// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
_delay_ms_label = create_label (this, _("ms"), false);
+ _fade_in_label = create_label (this, _("Fade in"), true);
+ _fade_in = new Timecode<ContentTime> (this);
+
+ _fade_out_label = create_label (this, _("Fade out"), true);
+ _fade_out = new Timecode<ContentTime> (this);
+
_mapping = new AudioMappingView (this, _("Content"), _("content"), _("DCP"), _("DCP"));
_sizer->Add (_mapping, 1, wxEXPAND | wxALL, 6);
@@ -123,6 +131,9 @@ AudioPanel::create ()
_show->Bind (wxEVT_BUTTON, boost::bind (&AudioPanel::show_clicked, this));
_gain_calculate_button->Bind (wxEVT_BUTTON, boost::bind (&AudioPanel::gain_calculate_button_clicked, this));
+ _fade_in->Changed.connect (boost::bind(&AudioPanel::fade_in_changed, this));
+ _fade_out->Changed.connect (boost::bind(&AudioPanel::fade_out_changed, this));
+
_mapping_connection = _mapping->Changed.connect (boost::bind (&AudioPanel::mapping_changed, this, _1));
_active_jobs_connection = JobManager::instance()->ActiveJobsChanged.connect (boost::bind (&AudioPanel::active_jobs_changed, this, _1, _2));
@@ -131,6 +142,7 @@ AudioPanel::create ()
_sizer->Layout ();
}
+
void
AudioPanel::add_to_grid ()
{
@@ -163,8 +175,17 @@ AudioPanel::add_to_grid ()
s->Add (_delay_ms_label, 0, wxALIGN_CENTER_VERTICAL);
_grid->Add (s, wxGBPosition(r, 1));
++r;
+
+ add_label_to_sizer (_grid, _fade_in_label, true, wxGBPosition(r, 0));
+ _grid->Add (_fade_in, wxGBPosition(r, 1), wxGBSpan(1, 3));
+ ++r;
+
+ add_label_to_sizer (_grid, _fade_out_label, true, wxGBPosition(r, 0));
+ _grid->Add (_fade_out, wxGBPosition(r, 1), wxGBSpan(1, 3));
+ ++r;
}
+
AudioPanel::~AudioPanel ()
{
if (_audio_dialog) {
@@ -242,6 +263,34 @@ AudioPanel::film_content_changed (int property)
setup_sensitivity ();
} else if (property == ContentProperty::VIDEO_FRAME_RATE) {
setup_description ();
+ } else if (property == AudioContentProperty::FADE_IN) {
+ set<Frame> check;
+ for (auto i: ac) {
+ check.insert (i->audio->fade_in().get());
+ }
+
+ if (check.size() == 1) {
+ _fade_in->set (
+ ac.front()->audio->fade_in(),
+ ac.front()->active_video_frame_rate(_parent->film())
+ );
+ } else {
+ _fade_in->clear ();
+ }
+ } else if (property == AudioContentProperty::FADE_OUT) {
+ set<Frame> check;
+ for (auto i: ac) {
+ check.insert (i->audio->fade_out().get());
+ }
+
+ if (check.size() == 1) {
+ _fade_out->set (
+ ac.front()->audio->fade_out(),
+ ac.front()->active_video_frame_rate(_parent->film())
+ );
+ } else {
+ _fade_out->clear ();
+ }
}
}
@@ -307,6 +356,8 @@ AudioPanel::content_selection_changed ()
film_content_changed (AudioContentProperty::STREAMS);
film_content_changed (AudioContentProperty::GAIN);
+ film_content_changed (AudioContentProperty::FADE_IN);
+ film_content_changed (AudioContentProperty::FADE_OUT);
film_content_changed (DCPContentProperty::REFERENCE_AUDIO);
setup_sensitivity ();
@@ -447,3 +498,25 @@ AudioPanel::set_film (shared_ptr<Film>)
}
}
+
+void
+AudioPanel::fade_in_changed ()
+{
+ auto const hmsf = _fade_in->get();
+ for (auto i: _parent->selected_audio()) {
+ auto const vfr = i->active_video_frame_rate(_parent->film());
+ i->audio->set_fade_in (dcpomatic::ContentTime(hmsf, vfr));
+ }
+}
+
+
+void
+AudioPanel::fade_out_changed ()
+{
+ auto const hmsf = _fade_out->get();
+ for (auto i: _parent->selected_audio()) {
+ auto const vfr = i->active_video_frame_rate (_parent->film());
+ i->audio->set_fade_out (dcpomatic::ContentTime(hmsf, vfr));
+ }
+}
+
diff --git a/src/wx/audio_panel.h b/src/wx/audio_panel.h
index 5e8f92597..bc3bd9755 100644
--- a/src/wx/audio_panel.h
+++ b/src/wx/audio_panel.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2022 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -22,6 +22,7 @@
#include "lib/audio_mapping.h"
#include "content_sub_panel.h"
#include "content_widget.h"
+#include "timecode.h"
class wxSpinCtrlDouble;
@@ -56,6 +57,8 @@ private:
void reference_clicked ();
void add_to_grid () override;
boost::optional<float> peak () const;
+ void fade_in_changed ();
+ void fade_out_changed ();
wxCheckBox* _reference;
wxStaticText* _reference_note;
@@ -68,6 +71,10 @@ private:
wxStaticText* _delay_label;
wxStaticText* _delay_ms_label;
ContentSpinCtrl<AudioContent>* _delay;
+ wxStaticText* _fade_in_label;
+ Timecode<dcpomatic::ContentTime>* _fade_in;
+ wxStaticText* _fade_out_label;
+ Timecode<dcpomatic::ContentTime>* _fade_out;
AudioMappingView* _mapping;
wxStaticText* _description;
AudioDialog* _audio_dialog;