From 29196ecc58f92432c21bac47dc5a59c6408524b4 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Sun, 23 Jan 2022 22:21:29 +0100 Subject: [PATCH] Add fade in/out option to the content audio tab (#1026). --- src/lib/audio_content.cc | 60 ++++++++++ src/lib/audio_content.h | 24 ++++ src/lib/maths_util.cc | 17 +++ src/lib/maths_util.h | 15 +++ src/lib/player.cc | 28 ++++- src/wx/audio_panel.cc | 87 ++++++++++++-- src/wx/audio_panel.h | 9 +- src/wx/timecode.h | 4 + test/audio_content_test.cc | 232 +++++++++++++++++++++++++++++++++++++ test/wscript | 1 + 10 files changed, 463 insertions(+), 14 deletions(-) create mode 100644 test/audio_content_test.cc diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index 32e713ff2..101ed8f1e 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 ("AudioGain"); _delay = node->number_child ("AudioDelay"); + _fade_in = ContentTime(node->optional_number_child("AudioFadeIn").get_value_or(0)); + _fade_out = ContentTime(node->optional_number_child("AudioFadeOut").get_value_or(0)); /* Backwards compatibility */ auto r = node->optional_number_child("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(_gain)); node->add_child("AudioDelay")->add_child_text(raw_convert(_delay)); + node->add_child("AudioFadeIn")->add_child_text(raw_convert(_fade_in.get())); + node->add_child("AudioFadeOut")->add_child_text(raw_convert(_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 +AudioContent::fade (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 coeffs(length); + for (auto coeff = 0; coeff < length; ++coeff) { + coeffs[coeff] = 1.0; + if (in) { + coeffs[coeff] *= logarithmic_fade_in_curve(static_cast(in_start + coeff) / in); + } + if (out) { + coeffs[coeff] *= logarithmic_fade_out_curve(static_cast(out_start + coeff) / out); + } + } + + return coeffs; +} + diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index 963f759e8..d736fc38f 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; }; @@ -77,6 +79,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 film) const; std::vector streams () const { @@ -94,6 +109,13 @@ public: void modify_position (std::shared_ptr 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 fade (Frame frame, Frame length, int frame_rate) const; + static std::shared_ptr from_xml (Content* parent, cxml::ConstNodePtr, int version); private: @@ -102,6 +124,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 _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 @@ -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 T clamp (T val, T minimum, T maximum) diff --git a/src/lib/player.cc b/src/lib/player.cc index 9b2a86748..91daeed32 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -1049,12 +1049,28 @@ Player::audio (weak_ptr wp, AudioStreamPtr stream, ContentAudio content_a DCPOMATIC_ASSERT (content_audio.audio->frames() > 0); - /* Gain */ - - if (content->gain() != 0) { - auto gain = make_shared(content_audio.audio); - gain->apply_gain (content->gain()); - content_audio.audio = gain; + /* Gain and fade */ + + auto const fade_coeffs = content->fade (content_audio.frame, content_audio.audio->frames(), rfr); + if (content->gain() != 0 || !fade_coeffs.empty()) { + auto gain_buffers = make_shared(content_audio.audio); + if (!fade_coeffs.empty()) { + /* Apply both fade and gain */ + DCPOMATIC_ASSERT (fade_coeffs.size() == static_cast(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/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 #include -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 (this); + + _fade_out_label = create_label (this, _("Fade out"), true); + _fade_out = new Timecode (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 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 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) } } + +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 + Copyright (C) 2012-2022 Carl Hetherington 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 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* _delay; + wxStaticText* _fade_in_label; + Timecode* _fade_in; + wxStaticText* _fade_out_label; + Timecode* _fade_out; AudioMappingView* _mapping; wxStaticText* _description; AudioDialog* _audio_dialog; diff --git a/src/wx/timecode.h b/src/wx/timecode.h index c31a6740c..84ef5cc1b 100644 --- a/src/wx/timecode.h +++ b/src/wx/timecode.h @@ -18,9 +18,11 @@ */ + #ifndef DCPOMATIC_WX_TIMECODE_H #define DCPOMATIC_WX_TIMECODE_H + #include "wx_util.h" #include "lib/dcpomatic_time.h" #include "lib/types.h" @@ -28,6 +30,7 @@ #include #include + class TimecodeBase : public wxPanel { public: @@ -58,6 +61,7 @@ protected: bool _ignore_changed = false; }; + template class Timecode : public TimecodeBase { diff --git a/test/audio_content_test.cc b/test/audio_content_test.cc new file mode 100644 index 000000000..e904ce64c --- /dev/null +++ b/test/audio_content_test.cc @@ -0,0 +1,232 @@ +/* + Copyright (C) 2022 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic 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 2 of the License, or + (at your option) any later version. + + DCP-o-matic 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 DCP-o-matic. If not, see . + +*/ + + +#include "lib/audio_content.h" +#include "lib/content_factory.h" +#include "lib/maths_util.h" +#include "test.h" +#include + + +BOOST_AUTO_TEST_CASE (audio_content_fade_empty_region) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_fade_empty_region", { content }); + + BOOST_CHECK (content->audio->fade(0, 0, 48000).empty()); +} + + +BOOST_AUTO_TEST_CASE (audio_content_fade_no_fade) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_fade_no_fade", { content }); + + BOOST_CHECK (content->audio->fade(0, 2000, 48000).empty()); + BOOST_CHECK (content->audio->fade(9999, 451, 48000).empty()); + BOOST_CHECK (content->audio->fade(content->audio->stream()->length() + 100, 8000, 48000).empty()); +} + + +BOOST_AUTO_TEST_CASE (audio_content_fade_unfaded_part) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_fade_unfaded_part", { content }); + + content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000)); + content->audio->set_fade_out(dcpomatic::ContentTime::from_frames(2000, 48000)); + + BOOST_CHECK (content->audio->fade(2000, 50, 48000).empty()); + BOOST_CHECK (content->audio->fade(12000, 99, 48000).empty()); + BOOST_CHECK (content->audio->fade(content->audio->stream()->length() - 2051, 50, 48000).empty()); +} + + +BOOST_AUTO_TEST_CASE (audio_content_within_the_fade_in) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_within_the_fade_in", { content }); + + content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000)); + + auto const f1 = content->audio->fade(0, 2000, 48000); + BOOST_REQUIRE_EQUAL (f1.size(), 2000U); + for (auto i = 0; i < 2000; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], logarithmic_fade_in_curve(static_cast(i) / 2000), 0.01); + } +} + + +BOOST_AUTO_TEST_CASE (audio_content_within_the_fade_out) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_within_the_fade_out", { content }); + + content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000)); + content->audio->set_fade_out(dcpomatic::ContentTime::from_frames(2000, 48000)); + + auto const f1 = content->audio->fade(content->audio->stream()->length() - 2000, 2000, 48000); + BOOST_REQUIRE_EQUAL (f1.size(), 2000U); + for (auto i = 0; i < 2000; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], logarithmic_fade_out_curve(static_cast(i) / 2000), 0.01); + } +} + + +BOOST_AUTO_TEST_CASE (audio_content_overlapping_the_fade_in) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_overlapping_the_fade_in", { content }); + + content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000)); + content->audio->set_fade_out(dcpomatic::ContentTime::from_frames(2000, 48000)); + + auto const f1 = content->audio->fade(1500, 2000, 48000); + BOOST_REQUIRE_EQUAL (f1.size(), 2000U); + for (auto i = 0; i < 500; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], logarithmic_fade_in_curve(static_cast(i + 1500) / 2000), 0.01); + } + for (auto i = 500; i < 2000; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], 1.0f, 0.01); + } +} + + +BOOST_AUTO_TEST_CASE (audio_content_overlapping_the_fade_out) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_overlapping_the_fade_out", { content }); + + content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000)); + content->audio->set_fade_out(dcpomatic::ContentTime::from_frames(4000, 48000)); + + auto const f1 = content->audio->fade(content->audio->stream()->length() - 4100, 2000, 48000); + BOOST_REQUIRE_EQUAL (f1.size(), 2000U); + for (auto i = 0; i < 100; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], 1.0f, 0.01); + } + for (auto i = 100; i < 2000; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], logarithmic_fade_out_curve(static_cast(i - 100) / 4000), 0.01); + } +} + + +BOOST_AUTO_TEST_CASE (audio_content_fade_in_and_out) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_fade_in_and_out", { content }); + + auto const length = content->audio->stream()->length(); + + content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(length, 48000)); + content->audio->set_fade_out(dcpomatic::ContentTime::from_frames(length, 48000)); + + auto const f1 = content->audio->fade(0, 10000, 48000); + BOOST_REQUIRE_EQUAL (f1.size(), 10000U); + for (auto i = 0; i < 10000; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], logarithmic_fade_in_curve(static_cast(i) / length) * logarithmic_fade_out_curve(static_cast(i) / length), 0.01); + } +} + + +BOOST_AUTO_TEST_CASE (audio_content_fade_in_with_trim) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_fade_in_with_trim", { content }); + + content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000)); + content->audio->set_fade_out(dcpomatic::ContentTime::from_frames(1000, 48000)); + content->set_trim_start(dcpomatic::ContentTime::from_frames(5200, 48000)); + + /* In the trim */ + auto const f1 = content->audio->fade(0, 2000, 48000); + BOOST_REQUIRE_EQUAL (f1.size(), 2000); + for (auto i = 0; i < 2000; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], 0.0f, 0.01); + } + + /* In the fade */ + auto const f2 = content->audio->fade(5200, 2000, 48000); + BOOST_REQUIRE_EQUAL (f2.size(), 2000); + for (auto i = 0; i < 2000; ++i) { + BOOST_REQUIRE_CLOSE (f2[i], logarithmic_fade_in_curve(static_cast(i) / 2000), 0.01); + } +} + + +BOOST_AUTO_TEST_CASE (audio_content_fade_out_with_trim) +{ + auto content = content_factory("test/data/impulse_train.wav").front(); + auto film = new_test_film2("audio_content_fade_out_with_trim", { content }); + + auto const length = content->audio->stream()->length(); + + content->audio->set_fade_in(dcpomatic::ContentTime::from_frames(2000, 48000)); + content->audio->set_fade_out(dcpomatic::ContentTime::from_frames(1000, 48000)); + content->set_trim_start(dcpomatic::ContentTime::from_frames(5200, 48000)); + content->set_trim_end(dcpomatic::ContentTime::from_frames(9000, 48000)); + + /* In the trim */ + auto const f1 = content->audio->fade(length - 6000, 2000, 48000); + BOOST_REQUIRE_EQUAL (f1.size(), 2000); + for (auto i = 0; i < 2000; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], 0.0f, 0.01); + } + + /* In the fade */ + auto const f2 = content->audio->fade(length - 9000 - 1000, 1000, 48000); + BOOST_REQUIRE_EQUAL (f2.size(), 1000); + for (auto i = 0; i < 1000; ++i) { + BOOST_REQUIRE_CLOSE (f2[i], logarithmic_fade_out_curve(static_cast(i) / 1000), 0.01); + } +} + + +BOOST_AUTO_TEST_CASE (audio_content_fade_out_with_trim_at_44k1) +{ + /* 5s at 44.1kHz */ + auto content = content_factory("test/data/white.wav").front(); + auto film = new_test_film2("audio_content_fade_out_with_trim_at_44k1", { content }); + + /* /----- 3.5s ------|-Fade-|-Trim-\ + * | | 1s | 0.5s | + * \-----------------|------|------/ + */ + + content->audio->set_fade_out(dcpomatic::ContentTime::from_seconds(1)); + content->set_trim_end(dcpomatic::ContentTime::from_seconds(0.5)); + + /* In the trim */ + auto const f1 = content->audio->fade(std::round(48000 * 4.75), 200, 48000); + BOOST_REQUIRE_EQUAL (f1.size(), 200); + for (auto i = 0; i < 200; ++i) { + BOOST_REQUIRE_CLOSE (f1[i], 0.0f, 0.01); + } + + /* In the fade */ + auto const f2 = content->audio->fade(std::round(48000 * 3.5 + 200), 7000, 48000); + BOOST_REQUIRE_EQUAL (f2.size(), 7000); + for (auto i = 0; i < 7000; ++i) { + BOOST_REQUIRE_CLOSE (f2[i], logarithmic_fade_out_curve(static_cast(i + 200) / 48000), 0.01); + } + +} + diff --git a/test/wscript b/test/wscript index b8f490eca..f2e14165e 100644 --- a/test/wscript +++ b/test/wscript @@ -46,6 +46,7 @@ def build(bld): atmos_test.cc audio_analysis_test.cc audio_buffers_test.cc + audio_content_test.cc audio_delay_test.cc audio_filter_test.cc audio_mapping_test.cc -- 2.30.2