diff --git a/build/Aegisub/Aegisub.vcxproj b/build/Aegisub/Aegisub.vcxproj
index ce9b19969..7d6b05077 100644
--- a/build/Aegisub/Aegisub.vcxproj
+++ b/build/Aegisub/Aegisub.vcxproj
@@ -205,6 +205,7 @@
+
@@ -234,7 +235,7 @@
-
+
@@ -244,7 +245,7 @@
-
+
@@ -282,6 +283,7 @@
+
@@ -395,6 +397,7 @@
+
@@ -426,7 +429,6 @@
-
@@ -436,7 +438,7 @@
-
+
diff --git a/build/Aegisub/Aegisub.vcxproj.filters b/build/Aegisub/Aegisub.vcxproj.filters
index c29884ca8..75c0d3d9f 100644
--- a/build/Aegisub/Aegisub.vcxproj.filters
+++ b/build/Aegisub/Aegisub.vcxproj.filters
@@ -348,10 +348,7 @@
Features\Update checker
-
- Video\Providers
-
-
+
Video
@@ -624,6 +621,12 @@
Features\Resolution resampler
+
+ Video\Providers
+
+
+ Main UI
+
@@ -950,10 +953,7 @@
Features\Update checker
-
- Video\Providers
-
-
+
Video
@@ -1181,9 +1181,15 @@
Features\Resolution resampler
+
+ Video\Providers
+
+
+ Main UI
+
-
\ No newline at end of file
+
diff --git a/libaegisub/include/libaegisub/signal.h b/libaegisub/include/libaegisub/signal.h
index bf465ac53..50394896f 100644
--- a/libaegisub/include/libaegisub/signal.h
+++ b/libaegisub/include/libaegisub/signal.h
@@ -12,10 +12,6 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-/// @file signal.h
-/// @brief
-/// @ingroup libaegisub
-
#pragma once
#include
@@ -45,11 +41,29 @@ namespace detail {
};
}
+/// A connection which is not automatically closed
+///
+/// Connections initially start out owned by the signal. If a slot knows that it
+/// will outlive a signal and does not need to be able to block a connection, it
+/// can simply ignore the return value of Connect.
+///
+/// If a slot needs to be able to disconnect from a signal, it should store the
+/// returned connection in a Connection, which transfers ownership of the
+/// connection to the slot. If there is any chance that the signal will outlive
+/// the slot, this must be done.
+class UnscopedConnection {
+ friend class Connection;
+ detail::ConnectionToken *token;
+public:
+ UnscopedConnection(detail::ConnectionToken *token) : token(token) { }
+};
+
/// Object representing a connection to a signal
class Connection {
std::unique_ptr token;
public:
Connection() = default;
+ Connection(UnscopedConnection src) BOOST_NOEXCEPT : token(src.token) { token->claimed = true; }
Connection(Connection&& that) BOOST_NOEXCEPT : token(std::move(that.token)) { }
Connection(detail::ConnectionToken *token) BOOST_NOEXCEPT : token(token) { token->claimed = true; }
Connection& operator=(Connection&& that) BOOST_NOEXCEPT { token = std::move(that.token); return *this; }
@@ -69,23 +83,6 @@ public:
void Unblock() { if (token) token->blocked = false; }
};
-/// A connection which is not automatically closed
-///
-/// Connections initially start out owned by the signal. If a slot knows that it
-/// will outlive a signal and does not need to be able to block a connection, it
-/// can simply ignore the return value of Connect.
-///
-/// If a slot needs to be able to disconnect from a signal, it should store the
-/// returned connection in a Connection, which transfers ownership of the
-/// connection to the slot. If there is any chance that the signal will outlive
-/// the slot, this must be done.
-class UnscopedConnection {
- detail::ConnectionToken *token;
-public:
- UnscopedConnection(detail::ConnectionToken *token) : token(token) { }
- operator Connection() { return Connection(token); }
-};
-
namespace detail {
/// Polymorphic base class for slots
///
@@ -198,6 +195,15 @@ public:
}
};
+/// Create a vector of scoped connections from an initializer list
+///
+/// Required due to that initializer lists copy their input, and trying to pass
+/// an initializer list directly to a vector results in a
+/// std::initializer_list, which can't be copied.
+inline std::vector make_vector(std::initializer_list connections) {
+ return std::vector(std::begin(connections), std::end(connections));
+}
+
} }
/// @brief Define functions which forward their arguments to the connect method
diff --git a/src/Makefile b/src/Makefile
index 32b187536..e6d056023 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -122,6 +122,7 @@ SRC += \
ass_style.cpp \
ass_style_storage.cpp \
ass_time.cpp \
+ async_video_provider.cpp \
audio_box.cpp \
audio_colorscheme.cpp \
audio_controller.cpp \
@@ -205,6 +206,7 @@ SRC += \
persist_location.cpp \
preferences.cpp \
preferences_base.cpp \
+ project.cpp \
resolution_resampler.cpp \
scintilla_text_ctrl.cpp \
search_replace_engine.cpp \
@@ -233,7 +235,6 @@ SRC += \
text_file_writer.cpp \
text_selection_controller.cpp \
thesaurus.cpp \
- threaded_frame_source.cpp \
timeedit_ctrl.cpp \
toggle_bitmap.cpp \
toolbar.cpp \
@@ -243,7 +244,7 @@ SRC += \
vector2d.cpp \
version.cpp \
video_box.cpp \
- video_context.cpp \
+ video_controller.cpp \
video_display.cpp \
video_frame.cpp \
video_out_gl.cpp \
diff --git a/src/ass_exporter.cpp b/src/ass_exporter.cpp
index 33739c6fb..1935284cb 100644
--- a/src/ass_exporter.cpp
+++ b/src/ass_exporter.cpp
@@ -38,8 +38,8 @@
#include "ass_file.h"
#include "compat.h"
#include "include/aegisub/context.h"
+#include "project.h"
#include "subtitle_format.h"
-#include "video_context.h"
#include
#include
@@ -51,7 +51,7 @@ void AssExporter::DrawSettings(wxWindow *parent, wxSizer *target_sizer) {
for (auto& filter : *AssExportFilterChain::GetFilterList()) {
// Make sure to construct static box sizer first, so it won't overlap
// the controls on wxMac.
- wxSizer *box = new wxStaticBoxSizer(wxVERTICAL, parent, to_wx(filter.GetName()));
+ auto box = new wxStaticBoxSizer(wxVERTICAL, parent, to_wx(filter.GetName()));
wxWindow *window = filter.GetConfigDialogWindow(parent, c);
if (window) {
box->Add(window, 0, wxEXPAND, 0);
@@ -92,7 +92,7 @@ void AssExporter::Export(agi::fs::path const& filename, std::string const& chars
if (!writer)
throw "Unknown file type.";
- writer->WriteFile(&subs, filename, c->videoController->FPS(), charset);
+ writer->WriteFile(&subs, filename, c->project->Timecodes(), charset);
}
wxSizer *AssExporter::GetSettingsSizer(std::string const& name) {
diff --git a/src/threaded_frame_source.cpp b/src/async_video_provider.cpp
similarity index 80%
rename from src/threaded_frame_source.cpp
rename to src/async_video_provider.cpp
index 16f3eb880..b57ff95c0 100644
--- a/src/threaded_frame_source.cpp
+++ b/src/async_video_provider.cpp
@@ -14,18 +14,12 @@
//
// Aegisub Project http://www.aegisub.org/
-/// @file threaded_frame_source.cpp
-/// @see threaded_frame_source.h
-/// @ingroup video
-///
-
-#include "threaded_frame_source.h"
+#include "async_video_provider.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "export_fixstyle.h"
#include "include/aegisub/subtitles_provider.h"
-#include "include/aegisub/video_provider.h"
#include "video_frame.h"
#include "video_provider_manager.h"
@@ -40,11 +34,11 @@ enum {
SUBS_FILE_ALREADY_LOADED = -2
};
-std::shared_ptr ThreadedFrameSource::ProcFrame(int frame_number, double time, bool raw) {
+std::shared_ptr AsyncVideoProvider::ProcFrame(int frame_number, double time, bool raw) {
std::shared_ptr frame;
try {
- frame = video_provider->GetFrame(frame_number);
+ frame = source_provider->GetFrame(frame_number);
}
catch (VideoProviderError const& err) { throw VideoProviderErrorEvent(err); }
@@ -89,20 +83,20 @@ static std::unique_ptr get_subs_provider(wxEvtHandler *evt_ha
}
}
-ThreadedFrameSource::ThreadedFrameSource(agi::fs::path const& video_filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br)
+AsyncVideoProvider::AsyncVideoProvider(agi::fs::path const& video_filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br)
: worker(agi::dispatch::Create())
, subs_provider(get_subs_provider(parent, br))
-, video_provider(VideoProviderFactory::GetProvider(video_filename, colormatrix, br))
+, source_provider(VideoProviderFactory::GetProvider(video_filename, colormatrix, br))
, parent(parent)
{
}
-ThreadedFrameSource::~ThreadedFrameSource() {
+AsyncVideoProvider::~AsyncVideoProvider() {
// Block until all currently queued jobs are complete
worker->Sync([]{});
}
-void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
+void AsyncVideoProvider::LoadSubtitles(const AssFile *new_subs) throw() {
uint_fast32_t req_version = ++version;
auto copy = new AssFile(*new_subs);
@@ -113,7 +107,7 @@ void ThreadedFrameSource::LoadSubtitles(const AssFile *new_subs) throw() {
});
}
-void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::set const& changes) throw() {
+void AsyncVideoProvider::UpdateSubtitles(const AssFile *new_subs, std::set const& changes) throw() {
uint_fast32_t req_version = ++version;
// Copy just the lines which were changed, then replace the lines at the
@@ -141,7 +135,7 @@ void ThreadedFrameSource::UpdateSubtitles(const AssFile *new_subs, std::setAsync([=]{
@@ -151,7 +145,7 @@ void ThreadedFrameSource::RequestFrame(int new_frame, double new_time) throw() {
});
}
-void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) {
+void AsyncVideoProvider::ProcAsync(uint_fast32_t req_version) {
// Only actually produce the frame if there's no queued changes waiting
if (req_version < version || frame_number < 0) return;
@@ -166,14 +160,14 @@ void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) {
}
}
-std::shared_ptr ThreadedFrameSource::GetFrame(int frame, double time, bool raw) {
+std::shared_ptr AsyncVideoProvider::GetFrame(int frame, double time, bool raw) {
std::shared_ptr ret;
worker->Sync([&]{ ret = ProcFrame(frame, time, raw); });
return ret;
}
-void ThreadedFrameSource::SetColorSpace(std::string const& matrix) {
- worker->Async([=] { video_provider->SetColorSpace(matrix); });
+void AsyncVideoProvider::SetColorSpace(std::string const& matrix) {
+ worker->Async([=] { source_provider->SetColorSpace(matrix); });
}
wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent);
diff --git a/src/threaded_frame_source.h b/src/async_video_provider.h
similarity index 80%
rename from src/threaded_frame_source.h
rename to src/async_video_provider.h
index e347f4cd4..317c8cabb 100644
--- a/src/threaded_frame_source.h
+++ b/src/async_video_provider.h
@@ -14,19 +14,14 @@
//
// Aegisub Project http://www.aegisub.org/
-/// @file threaded_frame_source.h
-/// @see threaded_frame_source.cpp
-/// @ingroup video
-///
+#include "include/aegisub/video_provider.h"
#include
#include
#include
-#include
#include
#include
-
#include
class AssDialogue;
@@ -40,16 +35,15 @@ namespace agi {
namespace dispatch { class Queue; }
}
-/// @class ThreadedFrameSource
-/// @brief An asynchronous video decoding and subtitle rendering wrapper
-class ThreadedFrameSource {
+/// An asynchronous video decoding and subtitle rendering wrapper
+class AsyncVideoProvider {
/// Asynchronous work queue
std::unique_ptr worker;
/// Subtitles provider
std::unique_ptr subs_provider;
/// Video provider
- std::unique_ptr video_provider;
+ std::unique_ptr source_provider;
/// Event handler to send FrameReady events to
wxEvtHandler *parent;
@@ -103,17 +97,26 @@ public:
/// @brief raw Get raw frame without subtitles
std::shared_ptr GetFrame(int frame, double time, bool raw = false);
- /// Get a reference to the video provider this is using
- VideoProvider *GetVideoProvider() const { return video_provider.get(); }
-
/// Ask the video provider to change YCbCr matricies
void SetColorSpace(std::string const& matrix);
+ int GetFrameCount() const { return source_provider->GetFrameCount(); }
+ int GetWidth() const { return source_provider->GetWidth(); }
+ int GetHeight() const { return source_provider->GetHeight(); }
+ double GetDAR() const { return source_provider->GetDAR(); }
+ agi::vfr::Framerate GetFPS() const { return source_provider->GetFPS(); }
+ std::vector GetKeyFrames() const { return source_provider->GetKeyFrames(); }
+ std::string GetColorSpace() const { return source_provider->GetColorSpace(); }
+ std::string GetRealColorSpace() const { return source_provider->GetRealColorSpace(); }
+ std::string GetWarning() const { return source_provider->GetWarning(); }
+ std::string GetDecoderName() const { return source_provider->GetDecoderName(); }
+ bool ShouldSetVideoProperties() const { return source_provider->ShouldSetVideoProperties(); }
+
/// @brief Constructor
/// @param videoFileName File to open
/// @param parent Event handler to send FrameReady events to
- ThreadedFrameSource(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br);
- ~ThreadedFrameSource();
+ AsyncVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, wxEvtHandler *parent, agi::BackgroundRunner *br);
+ ~AsyncVideoProvider();
};
/// Event which signals that a requested frame is ready
diff --git a/src/audio_box.cpp b/src/audio_box.cpp
index f21e79238..61947d910 100644
--- a/src/audio_box.cpp
+++ b/src/audio_box.cpp
@@ -61,6 +61,7 @@
#include "command/command.h"
#include "libresrc/libresrc.h"
#include "options.h"
+#include "project.h"
#include "toggle_bitmap.h"
#include "selection_controller.h"
#include "utils.h"
@@ -75,7 +76,7 @@ AudioBox::AudioBox(wxWindow *parent, agi::Context *context)
: wxSashWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxSW_3D | wxCLIP_CHILDREN)
, controller(context->audioController.get())
, context(context)
-, audio_open_connection(controller->AddAudioOpenListener(&AudioBox::OnAudioOpen, this))
+, audio_open_connection(context->project->AddAudioProviderListener(&AudioBox::OnAudioOpen, this))
, panel(new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_RAISED))
, audioDisplay(new AudioDisplay(panel, context->audioController.get(), context))
, HorizontalZoom(new wxSlider(panel, Audio_Horizontal_Zoom, -OPT_GET("Audio/Zoom/Horizontal")->GetInt(), -50, 30, wxDefaultPosition, wxSize(-1, 20), wxSL_VERTICAL|wxSL_BOTH))
diff --git a/src/audio_controller.cpp b/src/audio_controller.cpp
index b37ba6952..709f1067d 100644
--- a/src/audio_controller.cpp
+++ b/src/audio_controller.cpp
@@ -27,36 +27,23 @@
//
// Aegisub Project http://www.aegisub.org/
-/// @file audio_controller.cpp
-/// @brief Manage open audio and abstract state away from display
-/// @ingroup audio_ui
-///
-
#include "audio_controller.h"
-#include "ass_file.h"
#include "audio_timing.h"
-#include "compat.h"
-#include "dialog_progress.h"
#include "include/aegisub/audio_player.h"
#include "include/aegisub/audio_provider.h"
#include "include/aegisub/context.h"
-#include "pen.h"
#include "options.h"
+#include "project.h"
#include "selection_controller.h"
-#include "subs_controller.h"
#include "utils.h"
-#include "video_context.h"
-
-#include
-#include
#include
AudioController::AudioController(agi::Context *context)
: context(context)
-, subtitle_save_slot(context->subsController->AddFileSaveListener(&AudioController::OnSubtitlesSave, this))
, playback_timer(this)
+, provider_connection(context->project->AddAudioProviderListener(&AudioController::OnAudioProvider, this))
{
Bind(wxEVT_TIMER, &AudioController::OnPlaybackTimer, this, playback_timer.GetId());
@@ -66,25 +53,18 @@ AudioController::AudioController(agi::Context *context)
#endif
OPT_SUB("Audio/Player", &AudioController::OnAudioPlayerChanged, this);
- OPT_SUB("Audio/Provider", &AudioController::OnAudioProviderChanged, this);
- OPT_SUB("Audio/Cache/Type", &AudioController::OnAudioProviderChanged, this);
-
-#ifdef WITH_FFMS2
- // As with the video ones, it'd be nice to figure out a decent way to move
- // this to the provider itself
- OPT_SUB("Provider/Audio/FFmpegSource/Decode Error Handling", &AudioController::OnAudioProviderChanged, this);
-#endif
}
AudioController::~AudioController()
{
- CloseAudio();
+ Stop();
}
void AudioController::OnPlaybackTimer(wxTimerEvent &)
{
- int64_t pos = player->GetCurrentPosition();
+ if (!player) return;
+ int64_t pos = player->GetCurrentPosition();
if (!player->IsPlaying() ||
(playback_mode != PM_ToEnd && pos >= player->GetEndPosition()+200))
{
@@ -107,113 +87,40 @@ void AudioController::OnComputerSuspending(wxPowerEvent &)
void AudioController::OnComputerResuming(wxPowerEvent &)
{
- if (provider)
- {
- try
- {
- player = AudioPlayerFactory::GetAudioPlayer(provider.get(), context->parent);
- }
- catch (...)
- {
- CloseAudio();
- }
- }
+ OnAudioPlayerChanged();
}
#endif
void AudioController::OnAudioPlayerChanged()
{
- if (!IsAudioOpen()) return;
+ if (!provider) return;
Stop();
-
player.reset();
try
{
- player = AudioPlayerFactory::GetAudioPlayer(provider.get(), context->parent);
+ player = AudioPlayerFactory::GetAudioPlayer(provider, context->parent);
}
catch (...)
{
- CloseAudio();
- throw;
+ context->project->CloseAudio();
}
}
-void AudioController::OnAudioProviderChanged()
-{
- if (IsAudioOpen())
- // url is cloned because CloseAudio clears it and OpenAudio takes a const reference
- OpenAudio(agi::fs::path(audio_url));
-}
-
-void AudioController::OpenAudio(agi::fs::path const& url)
-{
- if (url.empty())
- throw agi::InternalError("AudioController::OpenAudio() was passed an empty string. This must not happen.", nullptr);
-
- std::unique_ptr new_provider;
- try {
- DialogProgress progress(context->parent);
- new_provider = AudioProviderFactory::GetProvider(url, &progress);
- config::path->SetToken("?audio", url);
- }
- catch (agi::UserCancelException const&) {
- throw;
- }
- catch (...) {
- config::mru->Remove("Audio", url);
- throw;
- }
-
- CloseAudio();
-
- player = AudioPlayerFactory::GetAudioPlayer(new_provider.get(), context->parent);
- provider = std::move(new_provider);
-
- audio_url = url;
-
- config::mru->Add("Audio", url);
-
- try
- {
- AnnounceAudioOpen(provider.get());
- }
- catch (...)
- {
- CloseAudio();
- throw;
- }
-}
-
-void AudioController::CloseAudio()
+void AudioController::OnAudioProvider(AudioProvider *new_provider)
{
+ provider = new_provider;
Stop();
-
player.reset();
- provider.reset();
- player = nullptr;
- provider = nullptr;
-
- audio_url.clear();
-
- config::path->SetToken("?audio", "");
-
- AnnounceAudioClose();
-}
-
-bool AudioController::IsAudioOpen() const
-{
- return player && provider;
+ OnAudioPlayerChanged();
}
void AudioController::SetTimingController(std::unique_ptr new_controller)
{
timing_controller = std::move(new_controller);
if (timing_controller)
- {
timing_controller->AddUpdatedPrimaryRangeListener(&AudioController::OnTimingControllerUpdatedPrimaryRange, this);
- }
AnnounceTimingControllerChanged();
}
@@ -224,17 +131,9 @@ void AudioController::OnTimingControllerUpdatedPrimaryRange()
player->SetEndPosition(SamplesFromMilliseconds(timing_controller->GetPrimaryPlaybackRange().end()));
}
-void AudioController::OnSubtitlesSave()
-{
- if (IsAudioOpen())
- context->ass->SetScriptInfo("Audio URI", config::path->MakeRelative(audio_url, "?script").generic_string());
- else
- context->ass->SetScriptInfo("Audio URI", "");
-}
-
void AudioController::PlayRange(const TimeRange &range)
{
- if (!IsAudioOpen()) return;
+ if (!player) return;
player->Play(SamplesFromMilliseconds(range.begin()), SamplesFromMilliseconds(range.length()));
playback_mode = PM_Range;
@@ -252,8 +151,6 @@ void AudioController::PlayPrimaryRange()
void AudioController::PlayToEndOfPrimary(int start_ms)
{
- if (!IsAudioOpen()) return;
-
PlayRange(TimeRange(start_ms, GetPrimaryPlaybackRange().end()));
if (playback_mode == PM_Range)
playback_mode = PM_PrimaryRange;
@@ -261,7 +158,7 @@ void AudioController::PlayToEndOfPrimary(int start_ms)
void AudioController::PlayToEnd(int start_ms)
{
- if (!IsAudioOpen()) return;
+ if (!player) return;
int64_t start_sample = SamplesFromMilliseconds(start_ms);
player->Play(start_sample, provider->GetNumSamples()-start_sample);
@@ -273,7 +170,7 @@ void AudioController::PlayToEnd(int start_ms)
void AudioController::Stop()
{
- if (!IsAudioOpen()) return;
+ if (!player) return;
player->Stop();
playback_mode = PM_NotPlaying;
@@ -284,7 +181,7 @@ void AudioController::Stop()
bool AudioController::IsPlaying()
{
- return IsAudioOpen() && playback_mode != PM_NotPlaying;
+ return player && playback_mode != PM_NotPlaying;
}
int AudioController::GetPlaybackPosition()
@@ -297,88 +194,31 @@ int AudioController::GetPlaybackPosition()
int AudioController::GetDuration() const
{
if (!provider) return 0;
-
return (provider->GetNumSamples() * 1000 + provider->GetSampleRate() - 1) / provider->GetSampleRate();
}
TimeRange AudioController::GetPrimaryPlaybackRange() const
{
if (timing_controller)
- {
return timing_controller->GetPrimaryPlaybackRange();
- }
else
- {
- return TimeRange(0, 0);
- }
+ return TimeRange{0, 0};
}
void AudioController::SetVolume(double volume)
{
- if (!IsAudioOpen()) return;
+ if (!player) return;
player->SetVolume(volume);
}
int64_t AudioController::SamplesFromMilliseconds(int64_t ms) const
{
- /// @todo There might be some subtle rounding errors here.
-
if (!provider) return 0;
-
- int64_t sr = provider->GetSampleRate();
-
- int64_t millisamples = ms * sr;
-
- return (millisamples + 999) / 1000;
+ return (ms * provider->GetSampleRate() + 999) / 1000;
}
int64_t AudioController::MillisecondsFromSamples(int64_t samples) const
{
- /// @todo There might be some subtle rounding errors here.
-
if (!provider) return 0;
-
- int64_t sr = provider->GetSampleRate();
-
- int64_t millisamples = samples * 1000;
-
- return millisamples / sr;
-}
-
-void AudioController::SaveClip(agi::fs::path const& filename, TimeRange const& range) const
-{
- int64_t start_sample = SamplesFromMilliseconds(range.begin());
- int64_t end_sample = SamplesFromMilliseconds(range.end());
- if (filename.empty() || start_sample > provider->GetNumSamples() || range.length() == 0) return;
-
- agi::io::Save outfile(filename, true);
- std::ostream& out(outfile.Get());
-
- size_t bytes_per_sample = provider->GetBytesPerSample() * provider->GetChannels();
- size_t bufsize = (end_sample - start_sample) * bytes_per_sample;
-
- int intval;
- short shortval;
-
- out << "RIFF";
- out.write((char*)&(intval=bufsize+36),4);
- out<< "WAVEfmt ";
- out.write((char*)&(intval=16),4);
- out.write((char*)&(shortval=1),2);
- out.write((char*)&(shortval=provider->GetChannels()),2);
- out.write((char*)&(intval=provider->GetSampleRate()),4);
- out.write((char*)&(intval=provider->GetSampleRate()*provider->GetChannels()*provider->GetBytesPerSample()),4);
- out.write((char*)&(intval=provider->GetChannels()*provider->GetBytesPerSample()),2);
- out.write((char*)&(shortval=provider->GetBytesPerSample()<<3),2);
- out << "data";
- out.write((char*)&bufsize,4);
-
- //samples per read
- size_t spr = 65536 / bytes_per_sample;
- std::vector buf(bufsize);
- for(int64_t i = start_sample; i < end_sample; i += spr) {
- size_t len = std::min(spr, end_sample - i);
- provider->GetAudio(&buf[0], i, len);
- out.write(&buf[0], len * bytes_per_sample);
- }
+ return samples * 1000 / provider->GetSampleRate();
}
diff --git a/src/audio_controller.h b/src/audio_controller.h
index d2687d2a9..87f3dcdcd 100644
--- a/src/audio_controller.h
+++ b/src/audio_controller.h
@@ -27,24 +27,15 @@
//
// Aegisub Project http://www.aegisub.org/
-/// @file audio_controller.h
-/// @see audio_controller.cpp
-/// @ingroup audio_ui
-
-#pragma once
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-
#include
#include
#include
+#include
+#include
+#include
+#include
+
class AudioPlayer;
class AudioProvider;
class AudioTimingController;
@@ -52,17 +43,10 @@ namespace agi { struct Context; }
class TimeRange;
/// @class AudioController
-/// @brief Manage an open audio stream
+/// @brief Manage playback of an open audio stream
///
-/// Creates and destroys audio providers and players. This behaviour should at
-/// some point be moved to a separate class, as it adds too many
-/// responsibilities to this class, but at the time of writing, it would extend
-/// the scope of reworking components too much.
-///
-/// There is not supposed to be a way to get direct access to the audio
-/// providers or players owned by a controller. If some operation that isn't
-/// possible in the existing design is needed, the controller should be
-/// extended in some way to allow it.
+/// AudioController owns an AudioPlayer and uses it to play audio from the
+/// project's current audio provider.
class AudioController final : public wxEvtHandler {
/// Project context this controller belongs to
agi::Context *context;
@@ -70,12 +54,6 @@ class AudioController final : public wxEvtHandler {
/// Slot for subtitles save signal
agi::signal::Connection subtitle_save_slot;
- /// A new audio stream was opened (and any previously open was closed)
- agi::signal::Signal AnnounceAudioOpen;
-
- /// The current audio stream was closed
- agi::signal::Signal<> AnnounceAudioClose;
-
/// Playback is in progress and the current position was updated
agi::signal::Signal AnnouncePlaybackPosition;
@@ -88,16 +66,9 @@ class AudioController final : public wxEvtHandler {
/// The audio output object
std::unique_ptr player;
- /// The audio provider
- std::unique_ptr provider;
-
/// The current timing mode, if any; owned by the audio controller
std::unique_ptr timing_controller;
- /// The URL of the currently open audio, if any
- agi::fs::path audio_url;
-
-
enum PlaybackMode {
PM_NotPlaying,
PM_Range,
@@ -110,6 +81,12 @@ class AudioController final : public wxEvtHandler {
/// Timer used for playback position updates
wxTimer playback_timer;
+ /// The audio provider
+ AudioProvider *provider = nullptr;
+ agi::signal::Connection provider_connection;
+
+ void OnAudioProvider(AudioProvider *new_provider);
+
/// Event handler for the playback timer
void OnPlaybackTimer(wxTimerEvent &event);
@@ -119,15 +96,9 @@ class AudioController final : public wxEvtHandler {
/// @brief Timing controller signals that the rendering style ranges have changed
void OnTimingControllerUpdatedStyleRanges();
- /// Subtitles save slot which adds the audio uri to the subtitles
- void OnSubtitlesSave();
-
/// Handler for the current audio player changing
void OnAudioPlayerChanged();
- /// Handler for the current audio provider changing
- void OnAudioProviderChanged();
-
#ifdef wxHAS_POWER_EVENTS
/// Handle computer going into suspend mode by stopping audio and closing device
void OnComputerSuspending(wxPowerEvent &event);
@@ -146,31 +117,9 @@ class AudioController final : public wxEvtHandler {
int64_t SamplesFromMilliseconds(int64_t ms) const;
public:
- /// @brief Constructor
AudioController(agi::Context *context);
-
- /// @brief Destructor
~AudioController();
- /// @brief Open an audio stream
- /// @param url URL of the stream to open
- void OpenAudio(agi::fs::path const& url);
-
- /// @brief Closes the current audio stream
- void CloseAudio();
-
- /// @brief Determine whether audio is currently open
- /// @return True if an audio stream is open and can be played back
- bool IsAudioOpen() const;
-
- /// @brief Get the URL for the current open audio stream
- /// @return The URL for the audio stream
- ///
- /// The returned URL can be passed into OpenAudio() later to open the same
- /// stream again.
- agi::fs::path GetAudioURL() const { return audio_url; }
-
-
/// @brief Start or restart audio playback, playing a range
/// @param range The range of audio to play back
///
@@ -233,13 +182,6 @@ public:
/// @param new_mode The new timing controller or nullptr
void SetTimingController(std::unique_ptr new_controller);
- /// @brief Save a portion of the decoded loaded audio to a wav file
- /// @param filename File to save to
- /// @param range Time range to save
- void SaveClip(agi::fs::path const& filename, TimeRange const& range) const;
-
- DEFINE_SIGNAL_ADDERS(AnnounceAudioOpen, AddAudioOpenListener)
- DEFINE_SIGNAL_ADDERS(AnnounceAudioClose, AddAudioCloseListener)
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackPosition, AddPlaybackPositionListener)
DEFINE_SIGNAL_ADDERS(AnnouncePlaybackStop, AddPlaybackStopListener)
DEFINE_SIGNAL_ADDERS(AnnounceTimingControllerChanged, AddTimingControllerListener)
diff --git a/src/audio_display.cpp b/src/audio_display.cpp
index b1d8e4707..3843e2b9a 100644
--- a/src/audio_display.cpp
+++ b/src/audio_display.cpp
@@ -48,9 +48,10 @@
#include "include/aegisub/context.h"
#include "include/aegisub/hotkey.h"
#include "options.h"
+#include "project.h"
#include "selection_controller.h"
#include "utils.h"
-#include "video_context.h"
+#include "video_controller.h"
#include
@@ -547,7 +548,7 @@ public:
AudioDisplay::AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context)
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS|wxBORDER_SIMPLE)
-, audio_open_connection(controller->AddAudioOpenListener(&AudioDisplay::OnAudioOpen, this))
+, audio_open_connection(context->project->AddAudioProviderListener(&AudioDisplay::OnAudioOpen, this))
, context(context)
, audio_renderer(agi::make_unique())
, controller(controller)
@@ -1193,16 +1194,16 @@ void AudioDisplay::OnAudioOpen(AudioProvider *provider)
{
if (connections.empty())
{
- connections.push_back(controller->AddAudioCloseListener([=] { OnAudioOpen(nullptr); }));
- connections.push_back(controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this));
- connections.push_back(controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this));
- connections.push_back(controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this));
- connections.push_back(OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this));
- connections.push_back(OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this));
- connections.push_back(OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this));
- connections.push_back(OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this));
- connections.push_back(OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this));
-
+ connections = agi::signal::make_vector({
+ controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this),
+ controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this),
+ controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this),
+ OPT_SUB("Audio/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
+ OPT_SUB("Audio/Display/Waveform Style", &AudioDisplay::ReloadRenderingSettings, this),
+ OPT_SUB("Colour/Audio Display/Spectrum", &AudioDisplay::ReloadRenderingSettings, this),
+ OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this),
+ OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this),
+ });
OnTimingController();
}
diff --git a/src/audio_display.h b/src/audio_display.h
index ca230382d..4c17d4be5 100644
--- a/src/audio_display.h
+++ b/src/audio_display.h
@@ -35,7 +35,6 @@
#include
#include
-#include
#include