Redesign project file handling

Add a new Project class which is responsible for everything related to
opening and closing audio, video, subtitles, timecodes and keyframes.
This pulls almost everything not directly related to playing audio/video
out of the audio and video controllers, pulls more crap out of
FrameMain, and happens to make things a little simpler in the process.
This commit is contained in:
Thomas Goyne 2014-05-21 16:23:28 -07:00
parent a345b8c4d5
commit 19e8f19e52
80 changed files with 1493 additions and 1717 deletions

View File

@ -205,6 +205,7 @@
<ClInclude Include="$(SrcDir)placeholder_ctrl.h" />
<ClInclude Include="$(SrcDir)preferences.h" />
<ClInclude Include="$(SrcDir)preferences_base.h" />
<ClInclude Include="$(SrcDir)project.h" />
<ClInclude Include="$(SrcDir)resolution_resampler.h" />
<ClInclude Include="$(SrcDir)scintilla_text_ctrl.h" />
<ClInclude Include="$(SrcDir)search_replace_engine.h" />
@ -234,7 +235,7 @@
<ClInclude Include="$(SrcDir)text_file_writer.h" />
<ClInclude Include="$(SrcDir)text_selection_controller.h" />
<ClInclude Include="$(SrcDir)thesaurus.h" />
<ClInclude Include="$(SrcDir)threaded_frame_source.h" />
<ClInclude Include="$(SrcDir)async_video_provider.h" />
<ClInclude Include="$(SrcDir)time_range.h" />
<ClInclude Include="$(SrcDir)timeedit_ctrl.h" />
<ClInclude Include="$(SrcDir)toggle_bitmap.h" />
@ -244,7 +245,7 @@
<ClInclude Include="$(SrcDir)vector2d.h" />
<ClInclude Include="$(SrcDir)version.h" />
<ClInclude Include="$(SrcDir)video_box.h" />
<ClInclude Include="$(SrcDir)video_context.h" />
<ClInclude Include="$(SrcDir)video_controller.h" />
<ClInclude Include="$(SrcDir)video_display.h" />
<ClInclude Include="$(SrcDir)video_frame.h" />
<ClInclude Include="$(SrcDir)video_out_gl.h" />
@ -282,6 +283,7 @@
<ClCompile Include="$(SrcDir)ass_style.cpp" />
<ClCompile Include="$(SrcDir)ass_style_storage.cpp" />
<ClCompile Include="$(SrcDir)ass_time.cpp" />
<ClCompile Include="$(SrcDir)async_video_provider.cpp" />
<ClCompile Include="$(SrcDir)audio_box.cpp" />
<ClCompile Include="$(SrcDir)audio_colorscheme.cpp" />
<ClCompile Include="$(SrcDir)audio_controller.cpp" />
@ -395,6 +397,7 @@
<ClCompile Include="$(SrcDir)persist_location.cpp" />
<ClCompile Include="$(SrcDir)preferences.cpp" />
<ClCompile Include="$(SrcDir)preferences_base.cpp" />
<ClCompile Include="$(SrcDir)project.cpp" />
<ClCompile Include="$(SrcDir)resolution_resampler.cpp" />
<ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp" />
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
@ -426,7 +429,6 @@
<ClCompile Include="$(SrcDir)text_file_writer.cpp" />
<ClCompile Include="$(SrcDir)text_selection_controller.cpp" />
<ClCompile Include="$(SrcDir)thesaurus.cpp" />
<ClCompile Include="$(SrcDir)threaded_frame_source.cpp" />
<ClCompile Include="$(SrcDir)timeedit_ctrl.cpp" />
<ClCompile Include="$(SrcDir)toggle_bitmap.cpp" />
<ClCompile Include="$(SrcDir)toolbar.cpp" />
@ -436,7 +438,7 @@
<ClCompile Include="$(SrcDir)vector2d.cpp" />
<ClCompile Include="$(SrcDir)version.cpp" />
<ClCompile Include="$(SrcDir)video_box.cpp" />
<ClCompile Include="$(SrcDir)video_context.cpp" />
<ClCompile Include="$(SrcDir)video_controller.cpp" />
<ClCompile Include="$(SrcDir)video_display.cpp" />
<ClCompile Include="$(SrcDir)video_frame.cpp" />
<ClCompile Include="$(SrcDir)video_out_gl.cpp" />

View File

@ -348,10 +348,7 @@
<ClInclude Include="$(SrcDir)dialog_version_check.h">
<Filter>Features\Update checker</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)threaded_frame_source.h">
<Filter>Video\Providers</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)video_context.h">
<ClInclude Include="$(SrcDir)video_controller.h">
<Filter>Video</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)video_frame.h">
@ -624,6 +621,12 @@
<ClInclude Include="$(SrcDir)dialog_video_properties.h">
<Filter>Features\Resolution resampler</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)async_video_provider.h">
<Filter>Video\Providers</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)project.h">
<Filter>Main UI</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
@ -950,10 +953,7 @@
<ClCompile Include="$(SrcDir)dialog_version_check.cpp">
<Filter>Features\Update checker</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)threaded_frame_source.cpp">
<Filter>Video\Providers</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)video_context.cpp">
<ClCompile Include="$(SrcDir)video_controller.cpp">
<Filter>Video</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)video_slider.cpp">
@ -1181,9 +1181,15 @@
<ClCompile Include="$(SrcDir)dialog_video_properties.cpp">
<Filter>Features\Resolution resampler</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)async_video_provider.cpp">
<Filter>Video\Providers</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)project.cpp">
<Filter>Main UI</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="$(SrcDir)res\res.rc" />
<ResourceCompile Include="$(SrcDir)res\strings.rc" />
</ItemGroup>
</Project>
</Project>

View File

@ -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 <boost/container/map.hpp>
@ -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<detail::ConnectionToken> 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<Connection>, which can't be copied.
inline std::vector<Connection> make_vector(std::initializer_list<UnscopedConnection> connections) {
return std::vector<Connection>(std::begin(connections), std::end(connections));
}
} }
/// @brief Define functions which forward their arguments to the connect method

View File

@ -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 \

View File

@ -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 <memory>
#include <wx/sizer.h>
@ -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) {

View File

@ -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<VideoFrame> ThreadedFrameSource::ProcFrame(int frame_number, double time, bool raw) {
std::shared_ptr<VideoFrame> AsyncVideoProvider::ProcFrame(int frame_number, double time, bool raw) {
std::shared_ptr<VideoFrame> 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<SubtitlesProvider> 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 AssDialogue*> const& changes) throw() {
void AsyncVideoProvider::UpdateSubtitles(const AssFile *new_subs, std::set<const AssDialogue*> 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::set<cons
});
}
void ThreadedFrameSource::RequestFrame(int new_frame, double new_time) throw() {
void AsyncVideoProvider::RequestFrame(int new_frame, double new_time) throw() {
uint_fast32_t req_version = ++version;
worker->Async([=]{
@ -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<VideoFrame> ThreadedFrameSource::GetFrame(int frame, double time, bool raw) {
std::shared_ptr<VideoFrame> AsyncVideoProvider::GetFrame(int frame, double time, bool raw) {
std::shared_ptr<VideoFrame> 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);

View File

@ -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 <libaegisub/exception.h>
#include <libaegisub/fs_fwd.h>
#include <atomic>
#include <deque>
#include <memory>
#include <set>
#include <wx/event.h>
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<agi::dispatch::Queue> worker;
/// Subtitles provider
std::unique_ptr<SubtitlesProvider> subs_provider;
/// Video provider
std::unique_ptr<VideoProvider> video_provider;
std::unique_ptr<VideoProvider> 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<VideoFrame> 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<int> 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

View File

@ -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))

View File

@ -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 <libaegisub/io.h>
#include <libaegisub/path.h>
#include <algorithm>
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<AudioProvider> 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<AudioTimingController> 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<char> buf(bufsize);
for(int64_t i = start_sample; i < end_sample; i += spr) {
size_t len = std::min<size_t>(spr, end_sample - i);
provider->GetAudio(&buf[0], i, len);
out.write(&buf[0], len * bytes_per_sample);
}
return samples * 1000 / provider->GetSampleRate();
}

View File

@ -27,24 +27,15 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_controller.h
/// @see audio_controller.cpp
/// @ingroup audio_ui
#pragma once
#include <boost/filesystem/path.hpp>
#include <cstdint>
#include <memory>
#include <wx/event.h>
#include <wx/timer.h>
#include <wx/power.h>
#include <libaegisub/exception.h>
#include <libaegisub/fs_fwd.h>
#include <libaegisub/signal.h>
#include <cstdint>
#include <wx/event.h>
#include <wx/power.h>
#include <wx/timer.h>
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<AudioProvider*> AnnounceAudioOpen;
/// The current audio stream was closed
agi::signal::Signal<> AnnounceAudioClose;
/// Playback is in progress and the current position was updated
agi::signal::Signal<int> AnnouncePlaybackPosition;
@ -88,16 +66,9 @@ class AudioController final : public wxEvtHandler {
/// The audio output object
std::unique_ptr<AudioPlayer> player;
/// The audio provider
std::unique_ptr<AudioProvider> provider;
/// The current timing mode, if any; owned by the audio controller
std::unique_ptr<AudioTimingController> 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<AudioTimingController> 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)

View File

@ -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 <libaegisub/make_unique.h>
@ -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<AudioRenderer>())
, 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();
}

View File

@ -35,7 +35,6 @@
#include <chrono>
#include <cstdint>
#include <deque>
#include <map>
#include <memory>
@ -103,7 +102,7 @@ public:
class AudioDisplay: public wxWindow {
agi::signal::Connection audio_open_connection;
std::deque<agi::signal::Connection> connections;
std::vector<agi::signal::Connection> connections;
agi::Context *context;
/// The audio renderer manager

View File

@ -32,6 +32,7 @@
#include "compat.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "utils.h"
@ -40,7 +41,6 @@
#include <algorithm>
#include <boost/locale/boundary.hpp>
#include <numeric>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/dcclient.h>
@ -63,8 +63,7 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c)
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_SUNKEN)
, c(c)
, file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this))
, audio_opened(c->audioController->AddAudioOpenListener(&AudioKaraoke::OnAudioOpened, this))
, audio_closed(c->audioController->AddAudioCloseListener(&AudioKaraoke::OnAudioClosed, this))
, audio_opened(c->project->AddAudioProviderListener(&AudioKaraoke::OnAudioOpened, this))
, active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this))
, kara(agi::make_unique<AssKaraoke>())
{
@ -122,12 +121,11 @@ void AudioKaraoke::OnFileChanged(int type, std::set<const AssDialogue *> const&
}
}
void AudioKaraoke::OnAudioOpened() {
SetEnabled(enabled);
}
void AudioKaraoke::OnAudioClosed() {
c->audioController->SetTimingController(nullptr);
void AudioKaraoke::OnAudioOpened(AudioProvider *provider) {
if (provider)
SetEnabled(enabled);
else
c->audioController->SetTimingController(nullptr);
}
void AudioKaraoke::SetEnabled(bool en) {

View File

@ -14,26 +14,20 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_karaoke.h
/// @see audio_karaoke.cpp
/// @ingroup audio_ui
///
#include <libaegisub/signal.h>
#include <memory>
#include <set>
#include <unordered_map>
#include <vector>
#include <wx/bitmap.h>
#include <wx/timer.h>
#include <wx/window.h>
class AssDialogue;
class AssKaraoke;
class AudioProvider;
class wxButton;
namespace agi { struct Context; }
/// @class AudioKaraoke
@ -147,8 +141,7 @@ class AudioKaraoke final : public wxWindow {
void OnMouse(wxMouseEvent &event);
void OnPaint(wxPaintEvent &event);
void OnSize(wxSizeEvent &event);
void OnAudioOpened();
void OnAudioClosed();
void OnAudioOpened(AudioProvider *provider);
void OnScrollTimer(wxTimerEvent &event);
public:

View File

@ -24,7 +24,8 @@
#include "include/aegisub/context.h"
#include "options.h"
#include "pen.h"
#include "video_context.h"
#include "project.h"
#include "video_controller.h"
#include <libaegisub/make_unique.h>
@ -42,9 +43,9 @@ public:
};
AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, const char *opt_name)
: vc(c->videoController.get())
, keyframe_slot(vc->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
, timecode_slot(vc->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
: p(c->project.get())
, keyframe_slot(p->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
, timecode_slot(p->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
, enabled_slot(OPT_SUB(opt_name, &AudioMarkerProviderKeyframes::Update, this))
, enabled_opt(OPT_GET(opt_name))
, style(agi::make_unique<Pen>("Colour/Audio Display/Keyframe"))
@ -55,8 +56,8 @@ AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, cons
AudioMarkerProviderKeyframes::~AudioMarkerProviderKeyframes() { }
void AudioMarkerProviderKeyframes::Update() {
std::vector<int> const& keyframes = vc->GetKeyFrames();
agi::vfr::Framerate const& timecodes = vc->FPS();
auto const& keyframes = p->Keyframes();
auto const& timecodes = p->Timecodes();
if (keyframes.empty() || !timecodes.IsLoaded() || !enabled_opt->GetBool()) {
if (!markers.empty()) {

View File

@ -14,11 +14,6 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file audio_marker.h
/// @see audio_marker.cpp
/// @ingroup audio_ui
///
#pragma once
#include <libaegisub/signal.h>
@ -30,7 +25,8 @@
class AudioMarkerKeyframe;
class Pen;
class VideoContext;
class Project;
class VideoController;
class TimeRange;
class VideoPositionMarker;
class wxPen;
@ -113,8 +109,8 @@ public:
/// Marker provider for video keyframes
class AudioMarkerProviderKeyframes final : public AudioMarkerProvider {
/// Video controller to get keyframes from
VideoContext *vc;
/// Project to get keyframes from
Project *p;
agi::signal::Connection keyframe_slot;
agi::signal::Connection timecode_slot;
@ -146,7 +142,7 @@ public:
/// Marker provider for the current video playback position
class VideoPositionMarkerProvider final : public AudioMarkerProvider {
VideoContext *vc;
VideoController *vc;
std::unique_ptr<VideoPositionMarker> marker;

View File

@ -64,8 +64,6 @@ public:
};
AvisynthAudioProvider::AvisynthAudioProvider(agi::fs::path const& filename) {
this->filename = filename;
agi::acs::CheckFileRead(filename);
std::lock_guard<std::mutex> lock(avs_wrapper.GetMutex());

View File

@ -158,8 +158,6 @@ public:
RiffWavPCMAudioProvider(agi::fs::path const& filename)
: PCMAudioProvider(filename)
{
this->filename = filename;
// Read header
auto const& header = Read<RIFFChunk>(0);
@ -292,8 +290,6 @@ public:
Wave64AudioProvider(agi::fs::path const& filename)
: PCMAudioProvider(filename)
{
this->filename = filename;
size_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk);
if (file->size() < smallest_possible_file)

View File

@ -39,7 +39,6 @@
#include <boost/range/algorithm/copy.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/sliced.hpp>
#include <deque>
/// @class KaraokeMarker
/// @brief AudioMarker implementation for AudioTimingControllerKaraoke
@ -55,6 +54,8 @@ public:
void Move(int new_pos) { position = new_pos; }
KaraokeMarker(int position) : position(position) { }
KaraokeMarker(int position, Pen *pen, FeetStyle style)
: position(position)
, pen(pen)
@ -62,11 +63,6 @@ public:
{
}
KaraokeMarker(int position)
: position(position)
{
}
operator int() const { return position; }
};
@ -79,7 +75,7 @@ public:
/// This does not support \kt, as it inherently requires that the end time of
/// one syllable be the same as the start time of the next one.
class AudioTimingControllerKaraoke final : public AudioTimingController {
std::deque<agi::signal::Connection> slots;
std::vector<agi::signal::Connection> connections;
agi::signal::Connection& file_changed_slot;
agi::Context *c; ///< Project context
@ -161,8 +157,8 @@ AudioTimingControllerKaraoke::AudioTimingControllerKaraoke(agi::Context *c, AssK
, keyframes_provider(c, "Audio/Display/Draw/Keyframes in Karaoke Mode")
,