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:
parent
a345b8c4d5
commit
19e8f19e52
80 changed files with 1493 additions and 1717 deletions
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||