diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
index 8a613a2a1..2270786ff 100644
--- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
+++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj
@@ -47,6 +47,7 @@
MinimalRebuild="true"
UsePrecompiledHeader="2"
PrecompiledHeaderThrough="agi_pre.h"
+ DebugInformationFormat="3"
DisableSpecificWarnings="4267"
ForcedIncludeFiles="agi_pre.h"
/>
@@ -113,6 +114,7 @@
MinimalRebuild="true"
UsePrecompiledHeader="2"
PrecompiledHeaderThrough="agi_pre.h"
+ DebugInformationFormat="3"
DisableSpecificWarnings="4267"
ForcedIncludeFiles="agi_pre.h"
/>
@@ -715,6 +717,14 @@
RelativePath="..\..\src\aegisublocale.h"
>
+
+
+
+
@@ -1699,6 +1709,22 @@
RelativePath="..\..\src\audio_box.h"
>
+
+
+
+
+
+
+
+
@@ -1731,10 +1757,34 @@
RelativePath="..\..\src\audio_renderer_spectrum.h"
>
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1799,6 +1849,18 @@
+
+
+
+
+
+
diff --git a/aegisub/build/aegisub_vs2008/compiler_options.vsprops b/aegisub/build/aegisub_vs2008/compiler_options.vsprops
index 99bf7cc81..6c49214a3 100644
--- a/aegisub/build/aegisub_vs2008/compiler_options.vsprops
+++ b/aegisub/build/aegisub_vs2008/compiler_options.vsprops
@@ -6,6 +6,7 @@
>
+
#include
#include
+#include
#include
#include
#include
@@ -199,6 +200,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/aegisub/src/ass_exporter.cpp b/aegisub/src/ass_exporter.cpp
index b9087db35..0589ba0df 100644
--- a/aegisub/src/ass_exporter.cpp
+++ b/aegisub/src/ass_exporter.cpp
@@ -39,6 +39,7 @@
#include "ass_export_filter.h"
#include "ass_exporter.h"
#include "ass_file.h"
+#include "audio_controller.h"
#include "frame_main.h"
/// @brief Constructor
diff --git a/aegisub/src/audio_box.cpp b/aegisub/src/audio_box.cpp
index a632eb82a..38d1029b1 100644
--- a/aegisub/src/audio_box.cpp
+++ b/aegisub/src/audio_box.cpp
@@ -47,9 +47,13 @@
#include
+#include "include/aegisub/audio_player.h"
+#include "selection_controller.h"
+#include "audio_controller.h"
#include "audio_box.h"
#include "audio_display.h"
#include "audio_karaoke.h"
+#include "audio_timing.h"
#include "frame_main.h"
#include "hotkeys.h"
#include "include/aegisub/audio_player.h"
@@ -58,60 +62,79 @@
#include "toggle_bitmap.h"
#include "tooltip_manager.h"
+// Stuff defines "min" and "max" as macros and breaks std::min and std::max in the process
+#undef min
+#undef max
+
+
+enum AudioBoxControlIDs {
+ Audio_Scrollbar = 1600,
+ Audio_Horizontal_Zoom,
+ Audio_Vertical_Zoom,
+ Audio_Volume,
+ Audio_Sash,
+ Audio_Vertical_Link,
+ Audio_Button_Play,
+ Audio_Button_Stop,
+ Audio_Button_Prev,
+ Audio_Button_Next,
+ Audio_Button_Play_500ms_Before,
+ Audio_Button_Play_500ms_After,
+ Audio_Button_Play_500ms_First,
+ Audio_Button_Play_500ms_Last,
+ Audio_Button_Play_Row,
+ Audio_Button_Play_To_End,
+ Audio_Button_Commit,
+ Audio_Button_Karaoke,
+ Audio_Button_Goto,
+
+ Audio_Button_Join, /// Karaoke -> Enter join mode.
+ Audio_Button_Split, /// Karaoke -> Enter split mode.
+ Audio_Button_Accept, /// Karaoke -> Split/Join mode -> Accept.
+ Audio_Button_Cancel, /// KAraoke -> Split/Join mode -> Cancel.
+
+ Audio_Button_Leadin,
+ Audio_Button_Leadout,
+
+ Audio_Check_AutoCommit,
+ Audio_Check_NextCommit,
+ Audio_Check_AutoGoto,
+ Audio_Check_Medusa,
+ Audio_Check_Spectrum
+};
+
+
/// @brief Constructor
/// @param parent
///
-AudioBox::AudioBox(wxWindow *parent, SubtitlesGrid *grid) :
-wxPanel(parent,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL|wxBORDER_RAISED)
+AudioBox::AudioBox(wxWindow *parent, AudioController *_controller, SelectionController *selection_controller)
+: wxPanel(parent,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL|wxBORDER_RAISED)
+, selection_controller(selection_controller)
+, controller(_controller)
{
// Setup
- loaded = false;
karaokeMode = false;
// Sash and Display
- audioScroll = new wxScrollBar(this,Audio_Scrollbar);
- audioScroll->PushEventHandler(new FocusEvent());
- audioScroll->SetToolTip(_("Seek bar"));
- Sash = new wxSashWindow(this,Audio_Sash,wxDefaultPosition,wxDefaultSize,wxCLIP_CHILDREN | wxSW_3DBORDER);
- sashSizer = new wxBoxSizer(wxVERTICAL);
- audioDisplay = new AudioDisplay(Sash, grid);
- sashSizer->Add(audioDisplay,1,wxEXPAND,0);
- Sash->SetSizer(sashSizer);
- Sash->SetSashVisible(wxSASH_BOTTOM,true);
- //Sash->SetSashBorder(wxSASH_BOTTOM,true);
- Sash->SetMinimumSizeY(50);
- audioDisplay->ScrollBar = audioScroll;
- audioDisplay->box = this;
- int _w,_h;
- audioDisplay->GetSize(&_w,&_h);
- audioDisplay->SetSizeHints(-1,_h,-1,_h);
+ audioDisplay = new AudioDisplay(this, controller);
// Zoom
- HorizontalZoom = new wxSlider(this,Audio_Horizontal_Zoom,50,0,100,wxDefaultPosition,wxSize(-1,20),wxSL_VERTICAL|wxSL_BOTH);
- HorizontalZoom->PushEventHandler(new FocusEvent());
+ HorizontalZoom = new wxSlider(this,Audio_Horizontal_Zoom,0,-50,30,wxDefaultPosition,wxSize(-1,20),wxSL_VERTICAL|wxSL_BOTH);
HorizontalZoom->SetToolTip(_("Horizontal zoom"));
VerticalZoom = new wxSlider(this,Audio_Vertical_Zoom,50,0,100,wxDefaultPosition,wxSize(-1,20),wxSL_VERTICAL|wxSL_BOTH|wxSL_INVERSE);
- VerticalZoom->PushEventHandler(new FocusEvent());
VerticalZoom->SetToolTip(_("Vertical zoom"));
VolumeBar = new wxSlider(this,Audio_Volume,50,0,100,wxDefaultPosition,wxSize(-1,20),wxSL_VERTICAL|wxSL_BOTH|wxSL_INVERSE);
- VolumeBar->PushEventHandler(new FocusEvent());
VolumeBar->SetToolTip(_("Audio Volume"));
bool link = OPT_GET("Audio/Link")->GetBool();
if (link) {
VolumeBar->SetValue(VerticalZoom->GetValue());
VolumeBar->Enable(false);
}
- VerticalLink = new ToggleBitmap(this,Audio_Vertical_Link,GETIMAGE(toggle_audio_link_24));
+ VerticalLink = new ToggleBitmap(this,Audio_Vertical_Link,GETIMAGE(toggle_audio_link_16));
VerticalLink->SetToolTip(_("Link vertical zoom and volume sliders"));
VerticalLink->SetValue(link);
- // Display sizer
- DisplaySizer = new wxBoxSizer(wxVERTICAL);
- //DisplaySizer->Add(audioDisplay,1,wxEXPAND,0);
- DisplaySizer->Add(Sash,0,wxEXPAND,0);
- DisplaySizer->Add(audioScroll,0,wxEXPAND,0);
-
// VertVol sider
wxSizer *VertVol = new wxBoxSizer(wxHORIZONTAL);
VertVol->Add(VerticalZoom,1,wxEXPAND,0);
@@ -122,99 +145,93 @@ wxPanel(parent,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL|wxBORDER_RAISE
// Top sizer
TopSizer = new wxBoxSizer(wxHORIZONTAL);
- TopSizer->Add(DisplaySizer,1,wxEXPAND,0);
+ TopSizer->Add(audioDisplay,1,wxEXPAND,0);
TopSizer->Add(HorizontalZoom,0,wxEXPAND,0);
TopSizer->Add(VertVolArea,0,wxEXPAND,0);
// Buttons sizer
wxSizer *ButtonSizer = new wxBoxSizer(wxHORIZONTAL);
wxButton *temp;
- temp = new wxBitmapButton(this,Audio_Button_Prev,GETIMAGE(button_prev_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Prev,GETIMAGE(button_prev_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Previous line or syllable (%KEY%/%KEY%)"),_T("Audio Prev Line"),_T("Audio Prev Line Alt"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Next,GETIMAGE(button_next_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Next,GETIMAGE(button_next_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Next line/syllable (%KEY%/%KEY%)"),_T("Audio Next Line"),_T("Audio Next Line Alt"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Play,GETIMAGE(button_playsel_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Play,GETIMAGE(button_playsel_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Play selection (%KEY%/%KEY%)"),_T("Audio Play"),_T("Audio Play Alt"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Play_Row,GETIMAGE(button_playline_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Play_Row,GETIMAGE(button_playline_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Play current line (%KEY%)"),_T("Audio Play Original Line"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Stop,GETIMAGE(button_stop_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Stop,GETIMAGE(button_stop_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Stop (%KEY%)"),_T("Audio Stop"));
ButtonSizer->Add(temp,0,wxRIGHT,10);
- temp = new wxBitmapButton(this,Audio_Button_Play_500ms_Before,GETIMAGE(button_playfivehbefore_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Play_500ms_Before,GETIMAGE(button_playfivehbefore_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Play 500 ms before selection (%KEY%)"),_T("Audio Play 500ms Before"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Play_500ms_After,GETIMAGE(button_playfivehafter_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Play_500ms_After,GETIMAGE(button_playfivehafter_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Play 500 ms after selection (%KEY%)"),_T("Audio Play 500ms after"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Play_500ms_First,GETIMAGE(button_playfirstfiveh_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Play_500ms_First,GETIMAGE(button_playfirstfiveh_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Play first 500ms of selection (%KEY%)"),_T("Audio Play First 500ms"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Play_500ms_Last,GETIMAGE(button_playlastfiveh_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Play_500ms_Last,GETIMAGE(button_playlastfiveh_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Play last 500ms of selection (%KEY%)"),_T("Audio Play Last 500ms"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Play_To_End,GETIMAGE(button_playtoend_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Play_To_End,GETIMAGE(button_playtoend_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Play from selection start to end of file (%KEY%)"),_T("Audio Play To End"));
ButtonSizer->Add(temp,0,wxRIGHT,10);
- temp = new wxBitmapButton(this,Audio_Button_Leadin,GETIMAGE(button_leadin_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Leadin,GETIMAGE(button_leadin_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Add lead in (%KEY%)"),_T("Audio Add Lead In"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Leadout,GETIMAGE(button_leadout_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Leadout,GETIMAGE(button_leadout_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Add lead out (%KEY%)"),_T("Audio Add Lead Out"));
ButtonSizer->Add(temp,0,wxRIGHT,10);
- temp = new wxBitmapButton(this,Audio_Button_Commit,GETIMAGE(button_audio_commit_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Commit,GETIMAGE(button_audio_commit_16),wxDefaultPosition,wxDefaultSize);
ToolTipManager::Bind(temp,_("Commit changes (%KEY%/%KEY%)"),_T("Audio Commit (Stay)"),_T("Audio Commit Alt"));
ButtonSizer->Add(temp,0,wxRIGHT,0);
- temp = new wxBitmapButton(this,Audio_Button_Goto,GETIMAGE(button_audio_goto_24),wxDefaultPosition,wxSize(30,-1));
+ temp = new wxBitmapButton(this,Audio_Button_Goto,GETIMAGE(button_audio_goto_16),wxDefaultPosition,wxDefaultSize);
temp->SetToolTip(_("Go to selection"));
ButtonSizer->Add(temp,0,wxRIGHT,10);
- AutoCommit = new ToggleBitmap(this,Audio_Check_AutoCommit,GETIMAGE(toggle_audio_autocommit_24),wxSize(30,-1));
+ AutoCommit = new ToggleBitmap(this,Audio_Check_AutoCommit,GETIMAGE(toggle_audio_autocommit_16), wxSize(20, -1));
AutoCommit->SetToolTip(_("Automatically commit all changes"));
AutoCommit->SetValue(OPT_GET("Audio/Auto/Commit")->GetBool());
ButtonSizer->Add(AutoCommit,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
- NextCommit = new ToggleBitmap(this,Audio_Check_NextCommit,GETIMAGE(toggle_audio_nextcommit_24),wxSize(30,-1));
+ NextCommit = new ToggleBitmap(this,Audio_Check_NextCommit,GETIMAGE(toggle_audio_nextcommit_16), wxSize(20, -1));
NextCommit->SetToolTip(_("Auto goes to next line on commit"));
NextCommit->SetValue(OPT_GET("Audio/Next Line on Commit")->GetBool());
ButtonSizer->Add(NextCommit,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
- AutoScroll = new ToggleBitmap(this,Audio_Check_AutoGoto,GETIMAGE(toggle_audio_autoscroll_24),wxSize(30,-1));
+ AutoScroll = new ToggleBitmap(this,Audio_Check_AutoGoto,GETIMAGE(toggle_audio_autoscroll_16), wxSize(20, -1));
AutoScroll->SetToolTip(_("Auto scrolls audio display to selected line"));
AutoScroll->SetValue(OPT_GET("Audio/Auto/Scroll")->GetBool());
- ButtonSizer->Add(AutoScroll,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
- SpectrumMode = new ToggleBitmap(this,Audio_Check_Spectrum,GETIMAGE(toggle_audio_spectrum_24),wxSize(30,-1));
- SpectrumMode->SetToolTip(_("Spectrum analyzer mode"));
- SpectrumMode->SetValue(OPT_GET("Audio/Spectrum")->GetBool());
- ButtonSizer->Add(SpectrumMode,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
- MedusaMode = new ToggleBitmap(this,Audio_Check_Medusa,GETIMAGE(toggle_audio_medusa_24),wxSize(30,-1));
- MedusaMode->SetToolTip(_("Enable Medusa-Style Timing Shortcuts"));
- MedusaMode->SetValue(OPT_GET("Audio/Medusa Timing Hotkeys")->GetBool());
- ButtonSizer->Add(MedusaMode,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,0);
+ ButtonSizer->Add(AutoScroll,0,wxRIGHT | wxALIGN_CENTER | wxEXPAND,10);
+
ButtonSizer->AddStretchSpacer(1);
+ KaraokeButton = new wxBitmapToggleButton(this,Audio_Button_Karaoke,GETIMAGE(kara_mode_16),wxDefaultPosition,wxDefaultSize);
+ KaraokeButton->SetToolTip(_("Toggle karaoke mode"));
+ ButtonSizer->Add(KaraokeButton,0,wxRIGHT|wxEXPAND,0);
+
// Karaoke sizer
karaokeSizer = new wxBoxSizer(wxHORIZONTAL);
- KaraokeButton = new wxBitmapToggleButton(this,Audio_Button_Karaoke,GETIMAGE(kara_mode_24),wxDefaultPosition,wxSize(33,30));
- KaraokeButton->SetToolTip(_("Toggle karaoke mode"));
- karaokeSizer->Add(KaraokeButton,0,wxRIGHT|wxEXPAND,0);
JoinSplitSizer = new wxBoxSizer(wxHORIZONTAL);
- JoinButton = new wxBitmapButton(this,Audio_Button_Join,GETIMAGE(kara_join_24),wxDefaultPosition,wxSize(33,30));
+ JoinButton = new wxBitmapButton(this,Audio_Button_Join,GETIMAGE(kara_join_16),wxDefaultPosition,wxDefaultSize);
JoinButton->SetToolTip(_("Join selected syllables"));
- SplitButton = new wxBitmapButton(this,Audio_Button_Split,GETIMAGE(kara_split_24),wxDefaultPosition,wxSize(33,30));
+ SplitButton = new wxBitmapButton(this,Audio_Button_Split,GETIMAGE(kara_split_16),wxDefaultPosition,wxDefaultSize);
SplitButton->SetToolTip(_("Enter split-mode"));
JoinSplitSizer->Add(JoinButton,0,wxRIGHT|wxEXPAND,0);
JoinSplitSizer->Add(SplitButton,0,wxRIGHT|wxEXPAND,0);
CancelAcceptSizer = new wxBoxSizer(wxHORIZONTAL);
- CancelButton = new wxBitmapButton(this,Audio_Button_Cancel,GETIMAGE(kara_split_accept_24),wxDefaultPosition,wxSize(33,30));
+ CancelButton = new wxBitmapButton(this,Audio_Button_Cancel,GETIMAGE(kara_split_accept_16),wxDefaultPosition,wxDefaultSize);
CancelButton->SetToolTip(_("Commit splits and leave split-mode"));
- AcceptButton = new wxBitmapButton(this,Audio_Button_Accept,GETIMAGE(kara_split_cancel_24),wxDefaultPosition,wxSize(33,30));
+ AcceptButton = new wxBitmapButton(this,Audio_Button_Accept,GETIMAGE(kara_split_cancel_16),wxDefaultPosition,wxDefaultSize);
AcceptButton->SetToolTip(_("Discard all splits and leave split-mode"));
CancelAcceptSizer->Add(CancelButton,0,wxRIGHT|wxEXPAND,0);
CancelAcceptSizer->Add(AcceptButton,0,wxRIGHT|wxEXPAND,0);
@@ -225,70 +242,40 @@ wxPanel(parent,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL|wxBORDER_RAISE
audioKaraoke = new AudioKaraoke(this);
audioKaraoke->box = this;
audioKaraoke->display = audioDisplay;
- audioDisplay->karaoke = audioKaraoke;
karaokeSizer->Add(audioKaraoke,1,wxEXPAND,0);
- SetKaraokeButtons(); // Decide which one to show or hide.
-
// Main sizer
MainSizer = new wxBoxSizer(wxVERTICAL);
- MainSizer->Add(TopSizer,0,wxEXPAND,0);
- MainSizer->Add(ButtonSizer,0,wxEXPAND,0);
- MainSizer->Add(new wxStaticLine(this),0,wxEXPAND|wxTOP|wxBOTTOM,2);
- MainSizer->Add(karaokeSizer,0,wxEXPAND,0);
+ MainSizer->Add(TopSizer,1,wxEXPAND|wxALL,3);
+ MainSizer->Add(ButtonSizer,0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3);
+ //MainSizer->Add(new wxStaticLine(this),0,wxEXPAND|wxTOP|wxBOTTOM,2);
+ MainSizer->Add(karaokeSizer,0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3);
+ MainSizer->AddSpacer(3);
//MainSizer->SetSizeHints(this);
SetSizer(MainSizer);
+ SetKaraokeButtons(); // Decide which one to show or hide.
+
+ timing_controller_dialogue = CreateDialogueTimingController(controller, selection_controller);
+ controller->SetTimingController(timing_controller_dialogue);
}
/// @brief Destructor
///
-AudioBox::~AudioBox() {
- audioScroll->PopEventHandler(true);
- HorizontalZoom->PopEventHandler(true);
- VerticalZoom->PopEventHandler(true);
- VolumeBar->PopEventHandler(true);
+AudioBox::~AudioBox()
+{
}
-/// @brief Set file
-/// @param file
-/// @param FromVideo
-/// @return
-///
-void AudioBox::SetFile(wxString file,bool FromVideo) {
- LOG_D("audio/box") << "file=" << file << " FromVideo: " << FromVideo;
- loaded = false;
-
- if (FromVideo) {
- audioDisplay->SetFromVideo();
- loaded = audioDisplay->loaded;
- audioName = _T("?video");
- }
-
- else {
- audioDisplay->SetFile(file);
- if (file != _T("")) loaded = audioDisplay->loaded;
- audioName = file;
- }
-
- LOG_D("audio/box") << "setting up acceleraters in frameMain";
- frameMain->SetAccelerators();
- LOG_D("audio/box") << "finished setting up accelerators in frameMain";
-}
-
-
///////////////
// Event table
BEGIN_EVENT_TABLE(AudioBox,wxPanel)
- EVT_COMMAND_SCROLL(Audio_Scrollbar, AudioBox::OnScrollbar)
EVT_COMMAND_SCROLL(Audio_Horizontal_Zoom, AudioBox::OnHorizontalZoom)
EVT_COMMAND_SCROLL(Audio_Vertical_Zoom, AudioBox::OnVerticalZoom)
EVT_COMMAND_SCROLL(Audio_Volume, AudioBox::OnVolume)
- EVT_SASH_DRAGGED(Audio_Sash,AudioBox::OnSash)
EVT_BUTTON(Audio_Button_Play, AudioBox::OnPlaySelection)
EVT_BUTTON(Audio_Button_Play_Row, AudioBox::OnPlayDialogue)
@@ -312,28 +299,19 @@ BEGIN_EVENT_TABLE(AudioBox,wxPanel)
EVT_TOGGLEBUTTON(Audio_Vertical_Link, AudioBox::OnVerticalLink)
EVT_TOGGLEBUTTON(Audio_Button_Karaoke, AudioBox::OnKaraoke)
EVT_TOGGLEBUTTON(Audio_Check_AutoGoto,AudioBox::OnAutoGoto)
- EVT_TOGGLEBUTTON(Audio_Check_Medusa,AudioBox::OnMedusaMode)
- EVT_TOGGLEBUTTON(Audio_Check_Spectrum,AudioBox::OnSpectrumMode)
EVT_TOGGLEBUTTON(Audio_Check_AutoCommit,AudioBox::OnAutoCommit)
EVT_TOGGLEBUTTON(Audio_Check_NextCommit,AudioBox::OnNextLineCommit)
END_EVENT_TABLE()
-/// @brief Scrollbar changed
-/// @param event
-///
-void AudioBox::OnScrollbar(wxScrollEvent &event) {
- audioDisplay->SetPosition(event.GetPosition()*12);
-}
-
-
-
/// @brief Horizontal zoom bar changed
/// @param event
///
void AudioBox::OnHorizontalZoom(wxScrollEvent &event) {
- audioDisplay->SetSamplesPercent(event.GetPosition());
+ // Negate the value, we want zoom out to be on bottom and zoom in on top,
+ // but the control doesn't want negative on bottom and positive on top.
+ audioDisplay->SetZoomLevel(-event.GetPosition());
}
@@ -346,9 +324,9 @@ void AudioBox::OnVerticalZoom(wxScrollEvent &event) {
if (pos < 1) pos = 1;
if (pos > 100) pos = 100;
float value = pow(float(pos)/50.0f,3);
- audioDisplay->SetScale(value);
+ audioDisplay->SetAmplitudeScale(value);
if (VerticalLink->GetValue()) {
- audioDisplay->player->SetVolume(value);
+ controller->SetVolume(value);
VolumeBar->SetValue(pos);
}
}
@@ -363,7 +341,7 @@ void AudioBox::OnVolume(wxScrollEvent &event) {
int pos = event.GetPosition();
if (pos < 1) pos = 1;
if (pos > 100) pos = 100;
- audioDisplay->player->SetVolume(pow(float(pos)/50.0f,3));
+ controller->SetVolume(pow(float(pos)/50.0f,3));
}
}
@@ -378,7 +356,7 @@ void AudioBox::OnVerticalLink(wxCommandEvent &event) {
if (pos > 100) pos = 100;
float value = pow(float(pos)/50.0f,3);
if (VerticalLink->GetValue()) {
- audioDisplay->player->SetVolume(value);
+ controller->SetVolume(value);
VolumeBar->SetValue(pos);
}
VolumeBar->Enable(!VerticalLink->GetValue());
@@ -388,63 +366,11 @@ void AudioBox::OnVerticalLink(wxCommandEvent &event) {
-/// @brief Sash
-/// @param event
-/// @return
-///
-void AudioBox::OnSash(wxSashEvent& event) {
- // OK?
- if (event.GetDragStatus() == wxSASH_STATUS_OUT_OF_RANGE) return;
-
- // Recursion guard
- static wxRecursionGuardFlag inside;
- wxRecursionGuard guard(inside);
- if (guard.IsInside()) {
- return;
- }
-
- // Get size
- wxRect newSize = event.GetDragRect();
- int w = newSize.GetWidth();
- int h = newSize.GetHeight();
- if (h < 50) h = 50;
- int oldh = audioDisplay->GetSize().GetHeight();
- if (oldh == h) return;
-
- // Resize
- audioDisplay->SetSizeHints(w,h,-1,h);
- audioDisplay->SetSize(w,h);
- sashSizer->Layout();
- Sash->GetParent()->Layout();
-
- // Store new size
- OPT_SET("Audio/Display Height")->SetInt(h);
-
- // Fix layout
- frameMain->Freeze();
- DisplaySizer->Layout();
- //TopSizer->Layout();
- //MainSizer->Layout();
- Layout();
- frameMain->ToolSizer->Layout();
- frameMain->MainSizer->Layout();
- frameMain->Layout();
- frameMain->Refresh();
- frameMain->Thaw();
-
- //event.Skip();
-}
-
-
-
/// @brief Play selection
/// @param event
///
void AudioBox::OnPlaySelection(wxCommandEvent &event) {
- int start=0,end=0;
- audioDisplay->SetFocus();
- audioDisplay->GetTimesSelection(start,end);
- audioDisplay->Play(start,end);
+ controller->PlayPrimaryRange();
}
@@ -453,11 +379,9 @@ void AudioBox::OnPlaySelection(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnPlayDialogue(wxCommandEvent &event) {
- int start=0,end=0;
- audioDisplay->SetFocus();
- audioDisplay->GetTimesDialogue(start,end);
- audioDisplay->SetSelection(start, end);
- audioDisplay->Play(start,end);
+ if (controller->GetTimingController())
+ controller->GetTimingController()->Revert();
+ controller->PlayPrimaryRange();
}
@@ -466,8 +390,7 @@ void AudioBox::OnPlayDialogue(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnStop(wxCommandEvent &event) {
- audioDisplay->SetFocus();
- audioDisplay->Stop();
+ controller->Stop();
}
@@ -476,9 +399,11 @@ void AudioBox::OnStop(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnNext(wxCommandEvent &event) {
- audioDisplay->SetFocus();
- audioDisplay->Stop();
- audioDisplay->Next();
+ //audioDisplay->SetFocus();
+ controller->Stop();
+ if (controller->GetTimingController())
+ controller->GetTimingController()->Next();
+ controller->PlayPrimaryRange();
}
@@ -487,9 +412,11 @@ void AudioBox::OnNext(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnPrev(wxCommandEvent &event) {
- audioDisplay->SetFocus();
- audioDisplay->Stop();
- audioDisplay->Prev();
+ //audioDisplay->SetFocus();
+ controller->Stop();
+ if (controller->GetTimingController())
+ controller->GetTimingController()->Prev();
+ controller->PlayPrimaryRange();
}
@@ -498,10 +425,10 @@ void AudioBox::OnPrev(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnPlay500Before(wxCommandEvent &event) {
- int start=0,end=0;
- audioDisplay->SetFocus();
- audioDisplay->GetTimesSelection(start,end);
- audioDisplay->Play(start-500,start);
+ AudioController::SampleRange times(controller->GetPrimaryPlaybackRange());
+ controller->PlayRange(AudioController::SampleRange(
+ times.begin() - controller->SamplesFromMilliseconds(500),
+ times.begin()));
}
@@ -510,10 +437,10 @@ void AudioBox::OnPlay500Before(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnPlay500After(wxCommandEvent &event) {
- int start=0,end=0;
- audioDisplay->SetFocus();
- audioDisplay->GetTimesSelection(start,end);
- audioDisplay->Play(end,end+500);
+ AudioController::SampleRange times(controller->GetPrimaryPlaybackRange());
+ controller->PlayRange(AudioController::SampleRange(
+ times.end(),
+ times.end() + controller->SamplesFromMilliseconds(500)));
}
@@ -522,12 +449,12 @@ void AudioBox::OnPlay500After(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnPlay500First(wxCommandEvent &event) {
- int start=0,end=0;
- audioDisplay->SetFocus();
- audioDisplay->GetTimesSelection(start,end);
- int endp = start+500;
- if (endp > end) endp = end;
- audioDisplay->Play(start,endp);
+ AudioController::SampleRange times(controller->GetPrimaryPlaybackRange());
+ controller->PlayRange(AudioController::SampleRange(
+ times.begin(),
+ times.begin() + std::min(
+ controller->SamplesFromMilliseconds(500),
+ times.length())));
}
@@ -536,12 +463,12 @@ void AudioBox::OnPlay500First(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnPlay500Last(wxCommandEvent &event) {
- int start=0,end=0;
- audioDisplay->SetFocus();
- audioDisplay->GetTimesSelection(start,end);
- int startp = end-500;
- if (startp < start) startp = start;
- audioDisplay->Play(startp,end);
+ AudioController::SampleRange times(controller->GetPrimaryPlaybackRange());
+ controller->PlayRange(AudioController::SampleRange(
+ times.end() - std::min(
+ controller->SamplesFromMilliseconds(500),
+ times.length()),
+ times.end()));
}
@@ -550,10 +477,7 @@ void AudioBox::OnPlay500Last(wxCommandEvent &event) {
/// @param event
///
void AudioBox::OnPlayToEnd(wxCommandEvent &event) {
- int start=0,end=0;
- audioDisplay->SetFocus();
- audioDisplay->GetTimesSelection(start,end);
- audioDisplay->Play(start,-1);
+ controller->PlayToEnd(controller->GetPrimaryPlaybackRange().begin());
}
@@ -566,7 +490,8 @@ void AudioBox::OnCommit(wxCommandEvent &event) {
LOG_D("audio/box") << "OnCommit";
audioDisplay->SetFocus();
LOG_D("audio/box") << "has set focus, now committing changes";
- audioDisplay->CommitChanges(true);
+ /// @todo Commit changes and go to next line if appropriate
+ //audioDisplay->CommitChanges(true);
LOG_D("audio/box") << "returning";
}
@@ -586,7 +511,8 @@ void AudioBox::OnKaraoke(wxCommandEvent &event) {
}
karaokeMode = false;
audioKaraoke->enabled = false;
- audioDisplay->SetDialogue();
+ /// @todo Replace this with changing timing controller
+ //audioDisplay->SetDialogue();
audioKaraoke->Refresh(false);
}
@@ -594,7 +520,8 @@ void AudioBox::OnKaraoke(wxCommandEvent &event) {
LOG_D("audio/box") << "karaoke disabled, enabling";
karaokeMode = true;
audioKaraoke->enabled = true;
- audioDisplay->SetDialogue();
+ /// @todo Replace this with changing timing controller
+ //audioDisplay->SetDialogue();
}
SetKaraokeButtons();
@@ -618,15 +545,9 @@ void AudioBox::SetKaraokeButtons() {
JoinButton->Enable(join);
SplitButton->Enable(split);
- if (audioKaraoke->splitting) {
- karaokeSizer->Show(CancelAcceptSizer);
- karaokeSizer->Hide(JoinSplitSizer);
- karaokeSizer->Layout();
- } else {
- karaokeSizer->Hide(CancelAcceptSizer);
- karaokeSizer->Show(JoinSplitSizer);
- karaokeSizer->Layout();
- }
+
+ karaokeSizer->Show(CancelAcceptSizer, audioKaraoke->splitting);
+ karaokeSizer->Show(JoinSplitSizer, !audioKaraoke->splitting);
}
/// @brief Join button in karaoke mode
@@ -671,7 +592,8 @@ void AudioBox::OnAccept(wxCommandEvent &event) {
///
void AudioBox::OnGoto(wxCommandEvent &event) {
audioDisplay->SetFocus();
- audioDisplay->MakeDialogueVisible(true);
+ if (controller->GetTimingController())
+ audioDisplay->ScrollSampleRangeInView(controller->GetTimingController()->GetIdealVisibleSampleRange());
}
@@ -706,26 +628,14 @@ void AudioBox::OnNextLineCommit(wxCommandEvent &event) {
-/// @brief Medusa Mode
-/// @param event
-///
+/// @todo Put global audio hotkeys toggling into the menu bar
+/*
void AudioBox::OnMedusaMode(wxCommandEvent &event) {
audioDisplay->SetFocus();
OPT_SET("Audio/Medusa Timing Hotkeys")->SetBool(MedusaMode->GetValue());
frameMain->SetAccelerators();
}
-
-
-
-/// @brief Spectrum Analyzer Mode
-/// @param event
-///
-void AudioBox::OnSpectrumMode(wxCommandEvent &event) {
- OPT_SET("Audio/Spectrum")->SetBool(SpectrumMode->GetValue());
- audioDisplay->UpdateImage(false);
- audioDisplay->SetFocus();
- audioDisplay->Refresh(false);
-}
+*/
@@ -734,7 +644,7 @@ void AudioBox::OnSpectrumMode(wxCommandEvent &event) {
///
void AudioBox::OnLeadIn(wxCommandEvent &event) {
audioDisplay->SetFocus();
- audioDisplay->AddLead(true,false);
+ //audioDisplay->AddLead(true,false);
}
@@ -743,21 +653,6 @@ void AudioBox::OnLeadIn(wxCommandEvent &event) {
///
void AudioBox::OnLeadOut(wxCommandEvent &event) {
audioDisplay->SetFocus();
- audioDisplay->AddLead(false,true);
+ //audioDisplay->AddLead(false,true);
}
-
-//////////////////////////////////////////
-// Focus event handling for the scrollbar
-BEGIN_EVENT_TABLE(FocusEvent,wxEvtHandler)
- EVT_SET_FOCUS(FocusEvent::OnSetFocus)
-END_EVENT_TABLE()
-
-
-/// @brief DOCME
-/// @param event
-///
-void FocusEvent::OnSetFocus(wxFocusEvent &event) {
- wxWindow *previous = event.GetWindow();
- if (previous) previous->SetFocus();
-}
diff --git a/aegisub/src/audio_box.h b/aegisub/src/audio_box.h
index d847890ab..15d068bb3 100644
--- a/aegisub/src/audio_box.h
+++ b/aegisub/src/audio_box.h
@@ -54,9 +54,14 @@
#include
#endif
+#ifndef AGI_AUDIO_CONTROLLER_INCLUDED
+#error You must include "audio_controller.h" before "audio_box.h"
+#endif
+
//////////////
// Prototypes
+class AssDialogue;
class AudioDisplay;
class AudioKaraoke;
class FrameMain;
@@ -66,18 +71,21 @@ class ToggleBitmap;
-/// DOCME
/// @class AudioBox
-/// @brief DOCME
-///
-/// DOCME
+/// @brief Panel with audio playback and timing controls, also containing an AudioDisplay
class AudioBox : public wxPanel {
- friend class AudioDisplay;
+ /// @todo Get rid of this ASAP, currently required for FrameMain to be able to notify
+ /// audio display about renderer having changed.
+ friend class FrameMain;
-private:
-
- /// DOCME
- wxScrollBar *audioScroll;
+ /// The audio display in the box
+ AudioDisplay *audioDisplay;
+
+ /// Selection controller used for timing controllers
+ SelectionController *selection_controller;
+
+ /// The regular dalogue timing controller
+ AudioTimingController *timing_controller_dialogue;
/// DOCME
wxSlider *HorizontalZoom;
@@ -100,9 +108,6 @@ private:
/// DOCME
wxSizer *DisplaySizer;
- /// DOCME
- wxSashWindow *Sash;
-
/// DOCME
ToggleBitmap *VerticalLink;
@@ -133,21 +138,13 @@ private:
/// DOCME
ToggleBitmap *NextCommit;
- /// DOCME
- ToggleBitmap *MedusaMode;
-
/// DOCME
ToggleBitmap *AutoCommit;
- /// DOCME
- ToggleBitmap *SpectrumMode;
-
- void OnScrollbar(wxScrollEvent &event);
void OnHorizontalZoom(wxScrollEvent &event);
void OnVerticalZoom(wxScrollEvent &event);
void OnVolume(wxScrollEvent &event);
void OnVerticalLink(wxCommandEvent &event);
- void OnSash(wxSashEvent &event);
void OnPlaySelection(wxCommandEvent &event);
void OnPlayDialogue(wxCommandEvent &event);
@@ -171,14 +168,13 @@ private:
void OnAutoGoto(wxCommandEvent &event);
void OnAutoCommit(wxCommandEvent &event);
- void OnMedusaMode(wxCommandEvent &event);
- void OnSpectrumMode(wxCommandEvent &event);
void OnNextLineCommit(wxCommandEvent &event);
+
public:
- /// DOCME
- AudioDisplay *audioDisplay;
+ /// The controller controlling this audio box
+ AudioController *controller;
/// DOCME
AudioKaraoke *audioKaraoke;
@@ -189,124 +185,15 @@ public:
/// DOCME
FrameMain *frameMain;
- /// DOCME
- wxString audioName;
-
- /// DOCME
- bool loaded;
-
/// DOCME
bool karaokeMode;
- AudioBox(wxWindow *parent, SubtitlesGrid *grid);
+ AudioBox(wxWindow *parent, AudioController *controller, SelectionController *selection_controller);
~AudioBox();
- void SetFile(wxString file,bool FromVideo);
void SetKaraokeButtons();
DECLARE_EVENT_TABLE()
};
-
-/// DOCME
-/// @class FocusEvent
-/// @brief DOCME
-///
-/// DOCME
-class FocusEvent : public wxEvtHandler {
-
-private:
- void OnSetFocus(wxFocusEvent &event);
- DECLARE_EVENT_TABLE()
-};
-
-
-///////
-// IDs
-enum {
-
- /// DOCME
- Audio_Scrollbar = 1600,
-
- /// DOCME
- Audio_Horizontal_Zoom,
-
- /// DOCME
- Audio_Vertical_Zoom,
-
- /// DOCME
- Audio_Volume,
-
- /// DOCME
- Audio_Sash,
-
- /// DOCME
- Audio_Vertical_Link,
-
- /// DOCME
- Audio_Button_Play,
-
- /// DOCME
- Audio_Button_Stop,
-
- /// DOCME
- Audio_Button_Prev,
-
- /// DOCME
- Audio_Button_Next,
-
- /// DOCME
- Audio_Button_Play_500ms_Before,
-
- /// DOCME
- Audio_Button_Play_500ms_After,
-
- /// DOCME
- Audio_Button_Play_500ms_First,
-
- /// DOCME
- Audio_Button_Play_500ms_Last,
-
- /// DOCME
- Audio_Button_Play_Row,
-
- /// DOCME
- Audio_Button_Play_To_End,
-
- /// DOCME
- Audio_Button_Commit,
-
- /// DOCME
- Audio_Button_Karaoke,
-
- /// DOCME
- Audio_Button_Goto,
-
- Audio_Button_Join, /// Karaoke -> Enter join mode.
- Audio_Button_Split, /// Karaoke -> Enter split mode.
- Audio_Button_Accept, /// Karaoke -> Split/Join mode -> Accept.
- Audio_Button_Cancel, /// KAraoke -> Split/Join mode -> Cancel.
-
- /// DOCME
- Audio_Button_Leadin,
-
- /// DOCME
- Audio_Button_Leadout,
-
-
- /// DOCME
- Audio_Check_AutoCommit,
-
- /// DOCME
- Audio_Check_NextCommit,
-
- /// DOCME
- Audio_Check_AutoGoto,
-
- /// DOCME
- Audio_Check_Medusa,
-
- /// DOCME
- Audio_Check_Spectrum
-};
diff --git a/aegisub/src/audio_colorscheme.cpp b/aegisub/src/audio_colorscheme.cpp
new file mode 100644
index 000000000..6bd720053
--- /dev/null
+++ b/aegisub/src/audio_colorscheme.cpp
@@ -0,0 +1,79 @@
+// Copyright (c) 2009-2010, Niels Martin Hansen
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+//
+// $Id$
+
+/// @file audio_colorscheme.cpp
+/// @ingroup audio_ui
+///
+/// Manage colour schemes for the audio display
+
+#include "config.h"
+
+#ifndef AGI_PRE
+#include
+#endif
+
+#include "audio_colorscheme.h"
+#include "colorspace.h"
+
+// Something is defining "min" and "max" macros, and they interfere with using std::min and std::max
+#undef min
+#undef max
+
+
+
+void AudioColorScheme::InitIcyBlue_Normal()
+{
+ unsigned char *palptr = palette;
+ for (size_t i = 0; i <= factor; ++i)
+ {
+ float t = (float)i / factor;
+ int H = (int)(255 * (1.5 - t) / 2);
+ int S = (int)(255 * (0.5 + t/2));
+ int L = std::min(255, (int)(128 * 2 * t));
+ hsl_to_rgb(H, S, L, palptr + 0, palptr + 1, palptr + 2);
+ palptr += 4;
+ }
+}
+
+
+void AudioColorScheme::InitIcyBlue_Selected()
+{
+ unsigned char *palptr = palette;
+ for (size_t i = 0; i <= factor; ++i)
+ {
+ float t = (float)i / factor;
+ int H = (int)(255 * (1.5 - t) / 2);
+ int S = (int)(255 * (0.5 + t/2));
+ int L = std::min(255, (int)(128 * (3 * t/2 + 0.5)));
+ hsl_to_rgb(H, S, L, palptr + 0, palptr + 1, palptr + 2);
+ palptr += 4;
+ }
+}
diff --git a/aegisub/src/audio_colorscheme.h b/aegisub/src/audio_colorscheme.h
new file mode 100644
index 000000000..1e3d2c7f5
--- /dev/null
+++ b/aegisub/src/audio_colorscheme.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2009-2010, Niels Martin Hansen
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+//
+// $Id$
+
+/// @file audio_colorscheme.h
+/// @see audio_colorscheme.cpp
+/// @ingroup audio_ui
+///
+/// Manage colour schemes for the audio display
+
+
+#ifndef AGI_PRE
+#include
+#endif
+
+
+/// @class AudioSpectrumColorMap
+/// @brief Provides colour maps for audio display rendering
+///
+/// Maps values from floats in range 0..1 into RGB colour values.
+///
+/// First create an instance of this class, then call an initialisation function
+/// in it to fill the palette with a colour map.
+///
+/// @todo Let consumers of this class specify their own palette generation function.
+class AudioColorScheme {
+ /// The palette data for the map
+ unsigned char *palette;
+
+ /// Factor to multiply 0..1 values by to map them into the palette range
+ size_t factor;
+
+public:
+ /// @brief Constructor
+ /// @param prec Bit precision to create the colour map with
+ ///
+ /// Allocates the palette array to 2^prec entries
+ AudioColorScheme(int prec)
+ : palette(new unsigned char[(4< 1.0) val = 1.0;
+ // Find the colour in the palette
+ unsigned char *color = palette + ((int)(val*factor) * 4);
+ // Copy to the destination.
+ // Has to be done one byte at a time since we're writing RGB and not RGBX or RGBA
+ // data, and we otherwise write past the end of the pixel we're writing, possibly
+ // hitting adjacent memory blocks or just overwriting the start of the following
+ // scanline in the image.
+ // As the image is 24 bpp, 3 of every 4 uint32_t writes would be unaligned anyway.
+ pixel[0] = color[0];
+ pixel[1] = color[1];
+ pixel[2] = color[2];
+ }
+
+ /// @brief Get a floating point value's colour as a wxColour
+ /// @param val The value to map from
+ /// @return The corresponding wxColour
+ wxColour get(float val)
+ {
+ if (val < 0.0) val = 0.0;
+ if (val > 1.0) val = 1.0;
+ unsigned char *color = palette + ((int)(val*factor) * 4);
+ return wxColour(color[0], color[1], color[2]);
+ }
+};
+
diff --git a/aegisub/src/audio_controller.cpp b/aegisub/src/audio_controller.cpp
new file mode 100644
index 000000000..acfbbab98
--- /dev/null
+++ b/aegisub/src/audio_controller.cpp
@@ -0,0 +1,568 @@
+// Copyright (c) 2009-2010, Niels Martin Hansen
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+//
+// $Id$
+
+/// @file audio_controller.cpp
+/// @brief Manage open audio and abstract state away from display
+/// @ingroup audio_ui
+///
+
+
+#include "config.h"
+
+#ifndef AGI_PRE
+#include
+
+#include
+#endif
+
+#include "selection_controller.h"
+#include "audio_controller.h"
+#include "include/aegisub/audio_provider.h"
+#include "include/aegisub/audio_player.h"
+#include "audio_provider_dummy.h"
+#include "audio_timing.h"
+#include "compat.h"
+#include "video_context.h"
+
+class AudioMarkerKeyframe : public AudioMarker {
+ int64_t position;
+ static wxPen style;
+public:
+ AudioMarkerKeyframe(int64_t position) : position(position) { }
+ int64_t GetPosition() const { return position; }
+ FeetStyle GetFeet() const { return Feet_None; }
+ bool CanSnap() const { return true; }
+ wxPen GetStyle() const
+ {
+ if (!style.IsOk())
+ /// @todo Make this colour configurable
+ style = wxPen(wxColour(255,0,255), 1);
+ return style;
+ }
+ bool operator < (const AudioMarkerKeyframe &other) const { return position < other.position; }
+ operator int64_t() const { return position; }
+};
+bool operator < (int64_t a, const AudioMarkerKeyframe &b) { return a < b.GetPosition(); }
+bool operator < (const AudioMarkerKeyframe &a, int64_t b) { return a.GetPosition() < b; }
+wxPen AudioMarkerKeyframe::style;
+
+class AudioMarkerProviderKeyframes : public AudioMarkerProvider, private AudioControllerAudioEventListener {
+ // GetMarkers needs to be const but still needs to modify this state, which is really
+ // just a cache... use the mutable "hack".
+ mutable int last_keyframes_revision;
+ mutable std::vector keyframe_samples;
+ AudioController *controller;
+ int64_t samplerate;
+
+ void ReloadKeyframes() const
+ {
+ keyframe_samples.clear();
+
+ VideoContext *vc = VideoContext::Get();
+ if (!vc) return;
+
+ last_keyframes_revision = vc->GetKeyframesRevision();
+ const std::vector &raw_keyframes = vc->GetKeyFrames();
+ keyframe_samples.reserve(raw_keyframes.size());
+ for (size_t i = 0; i < raw_keyframes.size(); ++i)
+ {
+ keyframe_samples.push_back(AudioMarkerKeyframe(
+ vc->TimeAtFrame(raw_keyframes[i]) * samplerate / 1000));
+ }
+ std::sort(keyframe_samples.begin(), keyframe_samples.end());
+ }
+
+private:
+ // AudioControllerAudioEventListener implementation
+ virtual void OnAudioOpen(AudioProvider *provider)
+ {
+ samplerate = provider->GetSampleRate();
+ ReloadKeyframes();
+ }
+ virtual void OnAudioClose() { }
+ virtual void OnPlaybackPosition(int64_t sample_position) { }
+ virtual void OnPlaybackStop() { }
+
+public:
+ AudioMarkerProviderKeyframes(AudioController *controller)
+ : controller(controller)
+ {
+ // Assume that a video context with keyframes revision 0 never has keyframes loaded
+ last_keyframes_revision = 0;
+ samplerate = 44100;
+ controller->AddAudioListener(this);
+ }
+
+ virtual ~AudioMarkerProviderKeyframes()
+ {
+ controller->RemoveAudioListener(this);
+ }
+
+ void GetMarkers(const AudioController::SampleRange &range, AudioMarkerVector &out) const
+ {
+ VideoContext *vc = VideoContext::Get();
+ if (!vc) return;
+
+ // Re-read keyframe data if the revision number changed, the keyframe data probably did too
+ if (vc->GetKeyframesRevision() != last_keyframes_revision)
+ ReloadKeyframes();
+
+ // Find first and last keyframes inside the range
+ std::vector::iterator a = std::lower_bound(
+ keyframe_samples.begin(), keyframe_samples.end(), range.begin());
+ std::vector::iterator b = std::upper_bound(
+ keyframe_samples.begin(), keyframe_samples.end(), range.end());
+
+ // Place pointers to the markers in the output vector
+ for (; a != b; ++a)
+ out.push_back(&*a);
+ }
+};
+
+
+/// Type of the audio event listener container in AudioController
+typedef std::set AudioEventListenerSet;
+/// Type of the timing event listener container in AudioController
+typedef std::set TimingEventListenerSet;
+
+/// Macro to iterate audio event listeners in AudioController implementation
+#define AUDIO_LISTENERS(listener) for (AudioEventListenerSet::iterator listener = audio_event_listeners.begin(); listener != audio_event_listeners.end(); ++listener)
+/// Macro to iterate audio event listeners in AudioController implementation
+#define TIMING_LISTENERS(listener) for (TimingEventListenerSet::iterator listener = timing_event_listeners.begin(); listener != timing_event_listeners.end(); ++listener)
+
+
+AudioController::AudioController()
+: player(0)
+, provider(0)
+, timing_controller(0)
+, keyframes_marker_provider(new AudioMarkerProviderKeyframes(this))
+, playback_mode(PM_NotPlaying)
+, playback_timer(this)
+{
+ Connect(playback_timer.GetId(), wxEVT_TIMER, (wxObjectEventFunction)&AudioController::OnPlaybackTimer);
+
+#ifdef wxHAS_POWER_EVENTS
+ Connect(wxEVT_POWER_SUSPENDED, (wxObjectEventFunction)&AudioController::OnComputerSuspending);
+ Connect(wxEVT_POWER_RESUME, (wxObjectEventFunction)&AudioController::OnComputerResuming);
+#endif
+}
+
+
+AudioController::~AudioController()
+{
+ CloseAudio();
+}
+
+
+void AudioController::OnPlaybackTimer(wxTimerEvent &event)
+{
+ int64_t pos = player->GetCurrentPosition();
+
+ if (!player->IsPlaying() ||
+ (playback_mode != PM_ToEnd && pos >= player->GetEndPosition()+200))
+ {
+ // The +200 is to allow the player to end the sound output cleanly, otherwise a popping
+ // artifact can sometimes be heard.
+ Stop();
+ }
+ else
+ {
+ AUDIO_LISTENERS(l)
+ {
+ (*l)->OnPlaybackPosition(pos);
+ }
+ }
+}
+
+
+#ifdef wxHAS_POWER_EVENTS
+void AudioController::OnComputerSuspending(wxPowerEvent &event)
+{
+ Stop();
+ player->CloseStream();
+}
+
+
+void AudioController::OnComputerResuming(wxPowerEvent &event)
+{
+ if (provider)
+ player->OpenStream();
+}
+#endif
+
+
+void AudioController::OpenAudio(const wxString &url)
+{
+ CloseAudio();
+
+ if (!url)
+ throw agi::InternalError("AudioController::OpenAudio() was passed an empty string. This must not happen.", 0);
+
+ wxString path_part;
+
+ if (url.StartsWith(_T("dummy-audio:"), &path_part))
+ {
+ /*
+ * scheme ::= "dummy-audio" ":" signal-specifier "?" signal-parameters
+ * signal-specifier ::= "silence" | "noise" | "sine" "/" frequency
+ * frequency ::= integer
+ * signal-parameters ::= signal-parameter [ "&" signal-parameters ]
+ * signal-parameter ::= signal-parameter-name "=" integer
+ * signal-parameter-name ::= "sr" | "bd" | "ch" | "ln"
+ *
+ * Signal types:
+ * "silence", a silent signal is generated.
+ * "noise", a white noise signal is generated.
+ * "sine", a sine wave is generated at the specified frequency.
+ *
+ * Signal parameters:
+ * "sr", sample rate to generate signal at.
+ * "bd", bit depth to generate signal at (usually 16).
+ * "ch", number of channels to generate, usually 1 or 2. The same signal is generated
+ * in every channel even if one would be LFE.
+ * "ln", length of signal in samples. ln/sr gives signal length in seconds.
+ */
+ provider = new DummyAudioProvider(5*30*60*1000, true);
+ }
+ else if (url.StartsWith(_T("video-audio:"), &path_part))
+ {
+ /*
+ * scheme ::= "video-audio" ":" stream-type
+ * stream-type ::= "stream" | "cache"
+ *
+ * Stream types:
+ *
+ * "stream", the audio is streamed as required directly from the video provider,
+ * and cannot be used to drive an audio display. Seeking is unreliable.
+ *
+ * "cache", the entire audio is cached to memory or disk. Audio displays can be
+ * driven and seeking is reliable. Opening takes longer because the entire audio
+ * stream has to be decoded and stored.
+ */
+ }
+ else if (url.StartsWith(_T("file:"), &path_part))
+ {
+ /*
+ * scheme ::= "file" ":" "//" file-system-path
+ *
+ * On Unix-like systems, the file system path is regular. On Windows-systems, the
+ * path uses forward slashes instead of back-slashes and the drive letter is
+ * preceded by a slash.
+ *
+ * URL-encoding??
+ */
+ }
+ else
+ {
+ /*
+ * Assume it's not a URI but instead a filename in the platform's native format.
+ */
+ wxFileName fn(url);
+ if (!fn.FileExists())
+ {
+ agi::FileNotFoundError fnf(STD_STR(url));
+ throw agi::AudioOpenError(
+ "Failed opening audio file (parsing as plain filename)",
+ &fnf);
+ }
+ provider = AudioProviderFactory::GetProvider(url);
+ }
+
+ try
+ {
+ player = AudioPlayerFactory::GetAudioPlayer();
+ player->SetProvider(provider);
+ player->OpenStream();
+ }
+ catch (...)
+ {
+ delete player;
+ delete provider;
+ player = 0;
+ provider = 0;
+ throw;
+ }
+
+ // Tell listeners about this.
+ AUDIO_LISTENERS(l)
+ {
+ (*l)->OnAudioOpen(provider);
+ }
+}
+
+
+void AudioController::CloseAudio()
+{
+ Stop();
+
+ delete player;
+ delete provider;
+ player = 0;
+ provider = 0;
+
+ AUDIO_LISTENERS(l)
+ {
+ (*l)->OnAudioClose();
+ }
+}
+
+
+bool AudioController::IsAudioOpen() const
+{
+ return player && provider;
+}
+
+
+wxString AudioController::GetAudioURL() const
+{
+ /// @todo figure out how to get the url
+ return _T("");
+}
+
+
+
+void AudioController::AddAudioListener(AudioControllerAudioEventListener *listener)
+{
+ audio_event_listeners.insert(listener);
+}
+
+
+void AudioController::RemoveAudioListener(AudioControllerAudioEventListener *listener)
+{
+ audio_event_listeners.erase(listener);
+}
+
+
+void AudioController::AddTimingListener(AudioControllerTimingEventListener *listener)
+{
+ timing_event_listeners.insert(listener);
+}
+
+
+void AudioController::RemoveTimingListener(AudioControllerTimingEventListener *listener)
+{
+ timing_event_listeners.erase(listener);
+}
+
+
+
+void AudioController::SetTimingController(AudioTimingController *new_controller)
+{
+ delete timing_controller;
+ timing_controller = new_controller;
+
+ TIMING_LISTENERS(l)
+ {
+ (*l)->OnTimingControllerChanged();
+ }
+}
+
+
+
+void AudioController::OnTimingControllerUpdatedPrimaryRange(AudioTimingController *sending_controller)
+{
+ assert(sending_controller != 0);
+ if (sending_controller != timing_controller)
+ return;
+
+ if (playback_mode == PM_PrimaryRange)
+ {
+ player->SetEndPosition(timing_controller->GetPrimaryPlaybackRange().end());
+ }
+
+ TIMING_LISTENERS(l)
+ {
+ (*l)->OnSelectionChanged();
+ }
+}
+
+
+void AudioController::OnTimingControllerUpdatedStyleRanges(AudioTimingController *sending_controller)
+{
+ assert(sending_controller != 0);
+ if (sending_controller != timing_controller)
+ return;
+
+ /// @todo redraw and stuff, probably
+}
+
+
+void AudioController::OnTimingControllerMarkerMoved(AudioTimingController *sending_controller, AudioMarker *marker)
+{
+ assert(sending_controller != 0);
+ if (sending_controller != timing_controller)
+ return;
+
+ /// @todo shouldn't this be more detailed?
+ TIMING_LISTENERS(l)
+ {
+ (*l)->OnMarkersMoved();
+ }
+}
+
+
+
+void AudioController::PlayRange(const AudioController::SampleRange &range)
+{
+ if (!IsAudioOpen()) return;
+
+ player->Play(range.begin(), range.length());
+ playback_mode = PM_Range;
+ playback_timer.Start(20);
+
+ AUDIO_LISTENERS(l)
+ {
+ (*l)->OnPlaybackPosition(range.begin());
+ }
+}
+
+
+void AudioController::PlayPrimaryRange()
+{
+ PlayRange(GetPrimaryPlaybackRange());
+ if (playback_mode == PM_Range)
+ playback_mode = PM_PrimaryRange;
+}
+
+
+void AudioController::PlayToEnd(int64_t start_sample)
+{
+ if (!IsAudioOpen()) return;
+
+ player->Play(start_sample, provider->GetNumSamples()-start_sample);
+ playback_mode = PM_ToEnd;
+ playback_timer.Start(20);
+
+ AUDIO_LISTENERS(l)
+ {
+ (*l)->OnPlaybackPosition(start_sample);
+ }
+}
+
+
+void AudioController::Stop()
+{
+ if (!IsAudioOpen()) return;
+
+ player->Stop();
+ playback_mode = PM_NotPlaying;
+ playback_timer.Stop();
+
+ AUDIO_LISTENERS(l)
+ {
+ (*l)->OnPlaybackStop();
+ }
+}
+
+
+bool AudioController::IsPlaying()
+{
+ return IsAudioOpen() && playback_mode != PM_NotPlaying;
+}
+
+
+int64_t AudioController::GetPlaybackPosition()
+{
+ if (!IsPlaying()) return 0;
+
+ return player->GetCurrentPosition();
+}
+
+
+void AudioController::ResyncPlaybackPosition(int64_t new_position)
+{
+ if (!IsPlaying()) return;
+
+ player->SetCurrentPosition(new_position);
+}
+
+
+AudioController::SampleRange AudioController::GetPrimaryPlaybackRange() const
+{
+ if (timing_controller != 0)
+ {
+ return timing_controller->GetPrimaryPlaybackRange();
+ }
+ else
+ {
+ return SampleRange(0, 0);
+ }
+}
+
+
+void AudioController::GetMarkers(const SampleRange &range, AudioMarkerVector &markers) const
+{
+ /// @todo Find all sources of markers
+ keyframes_marker_provider->GetMarkers(range, markers);
+}
+
+
+double AudioController::GetVolume() const
+{
+ if (!IsAudioOpen()) return 1.0;
+ return player->GetVolume();
+}
+
+
+void AudioController::SetVolume(double volume)
+{
+ if (!IsAudioOpen()) 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;
+}
+
+
+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;
+}
+
diff --git a/aegisub/src/audio_controller.h b/aegisub/src/audio_controller.h
new file mode 100644
index 000000000..90db3015e
--- /dev/null
+++ b/aegisub/src/audio_controller.h
@@ -0,0 +1,439 @@
+// Copyright (c) 2009-2010, Niels Martin Hansen
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+//
+// $Id$
+
+/// @file audio_controller.h
+/// @see audio_controller.cpp
+/// @ingroup audio_ui
+
+
+#ifndef AGI_PRE
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+#include
+
+#define AGI_AUDIO_CONTROLLER_INCLUDED 1
+
+
+class AudioPlayer;
+class AudioProvider;
+
+// Declared below
+class AudioControllerAudioEventListener;
+class AudioControllerTimingEventListener;
+class AudioTimingController;
+class AudioMarker;
+class AudioMarkerProvider;
+
+
+typedef std::vector AudioMarkerVector;
+
+
+/// @class AudioController
+/// @brief Manage an open audio stream and UI state for it
+///
+/// Keeps track of the UI interaction state of the open audio for a project, ie. what the current
+/// selection is, what moveable markers are on the audio, and any secondary non-moveable markers
+/// that are present.
+///
+/// Changes in interaction are broadcast to all managed audio displays so they can redraw, and
+/// the audio displays report all interactions back to the controller. There is a one to many
+/// relationship between controller and audio displays. There is at most one audio controller
+/// for an open subtitling project.
+///
+/// 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.
+class AudioController : public wxEvtHandler {
+public:
+
+ /// @class SampleRange
+ /// @brief Represents an immutable range of audio samples
+ class SampleRange {
+ int64_t _begin;
+ int64_t _end;
+
+ public:
+ /// @brief Constructor
+ /// @param begin Index of the first sample to include in the range
+ /// @param end Index of one past the last sample to include in the range
+ SampleRange(int64_t begin, int64_t end)
+ : _begin(begin)
+ , _end(end)
+ {
+ assert(end >= begin);
+ }
+
+ /// @brief Copy constructor, optionally adjusting the range
+ /// @param src The range to duplicate
+ /// @param begin_adjust Number of samples to add to the start of the range
+ /// @param end_adjust Number of samples to add to the end of the range
+ SampleRange(const SampleRange &src, int64_t begin_adjust = 0, int64_t end_adjust = 0)
+ {
+ _begin = src._begin + begin_adjust;
+ _end = src._end + end_adjust;
+ assert(_end >= _begin);
+ }
+
+ /// Get the number of samples in the range
+ int64_t length() const { return _end - _begin; }
+ /// Get the index of the first sample in the range
+ int64_t begin() const { return _begin; }
+ /// Get the index of one past the last sample in the range
+ int64_t end() const { return _end; }
+
+ /// Determine whether the range contains a given sample index
+ bool contains(int64_t sample) const { return sample >= begin() && sample < end(); }
+
+ /// Determine whether there is an overlap between two ranges
+ bool overlaps(const SampleRange &other) const
+ {
+ return other.contains(_begin)
+ || other.contains(_end)
+ || contains(other._begin)
+ || contains(other._end);
+ }
+ };
+
+
+private:
+
+ /// Listeners for audio-related events
+ std::set audio_event_listeners;
+
+ /// Listeners for timing-related events
+ std::set timing_event_listeners;
+
+ /// The audio output object
+ AudioPlayer *player;
+
+ /// The audio provider
+ AudioProvider *provider;
+
+ /// The current timing mode, if any; owned by the audio controller
+ AudioTimingController *timing_controller;
+
+ /// Provide keyframe data for audio displays
+ std::auto_ptr keyframes_marker_provider;
+
+
+ enum PlaybackMode {
+ PM_NotPlaying,
+ PM_Range,
+ PM_PrimaryRange,
+ PM_ToEnd
+ };
+ /// The current playback mode
+ PlaybackMode playback_mode;
+
+
+ /// Timer used for playback position updates
+ wxTimer playback_timer;
+
+ /// Event handler for the playback timer
+ void OnPlaybackTimer(wxTimerEvent &event);
+
+
+#ifdef wxHAS_POWER_EVENTS
+ /// Handle computer going into suspend mode by stopping audio and closing device
+ void OnComputerSuspending(wxPowerEvent &event);
+ /// Handle computer resuming from suspend by re-opening the audio device
+ void OnComputerResuming(wxPowerEvent &event);
+#endif
+
+
+public:
+
+ /// @brief Constructor
+ AudioController();
+
+ /// @brief Destructor
+ ~AudioController();
+
+
+ /// @brief Open an audio stream
+ /// @param url URL of the stream to open
+ ///
+ /// The URL can either be a plain filename (with no qualifiers) or one
+ /// recognised by various providers.
+ void OpenAudio(const wxString &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.
+ wxString GetAudioURL() const;
+
+
+ /// @brief Add an audio event listener
+ /// @param listener The listener to add
+ void AddAudioListener(AudioControllerAudioEventListener *listener);
+
+ /// @brief Remove an audio event listener
+ /// @param listener The listener to remove
+ void RemoveAudioListener(AudioControllerAudioEventListener *listener);
+
+ /// @brief Add a timing event listener
+ /// @param listener The listener to add
+ void AddTimingListener(AudioControllerTimingEventListener *listener);
+
+ /// @brief Remove a timing event listener
+ /// @param listener The listener to remove
+ void RemoveTimingListener(AudioControllerTimingEventListener *listener);
+
+
+ /// @brief Start or restart audio playback, playing a range
+ /// @param range The range of audio to play back
+ ///
+ /// The end of the played back range may be requested changed, but is not changed
+ /// automatically from any other operations.
+ void PlayRange(const SampleRange &range);
+
+ /// @brief Start or restart audio playback, playing the primary playback range
+ ///
+ /// If the primary playback range is updated during playback, the end of the
+ /// active playback range will be updated to match the new selection. The playback
+ /// end can not be changed in any other way.
+ void PlayPrimaryRange();
+
+ /// @brief Start or restart audio playback, playing from a point to the end of stream
+ /// @param start_sample Index of the sample to start playback at
+ ///
+ /// Playback to end cannot be converted to a range playback like range playback can,
+ /// it will continue until the end is reached, it is stopped, or restarted.
+ void PlayToEnd(int64_t start_sample);
+
+ /// @brief Stop all audio playback
+ void Stop();
+
+ /// @brief Determine whether playback is ongoing
+ /// @return True if audio is being played back
+ bool IsPlaying();
+
+ /// @brief Get the current playback position
+ /// @return Approximate current sample index being heard by the user
+ ///
+ /// Returns 0 if playback is stopped. The return value is only approximate.
+ int64_t GetPlaybackPosition();
+
+ /// @brief If playing, restart playback from the specified position
+ /// @param new_position Sample index to restart playback from
+ ///
+ /// This function can be used to re-synchronise audio playback to another source that
+ /// might not be able to keep up with the full speed, such as video playback in high
+ /// resolution or with complex subtitles.
+ ///
+ /// This function only does something if audio is already playing.
+ void ResyncPlaybackPosition(int64_t new_position);
+
+
+ /// @brief Get the primary playback range
+ /// @return An immutable SampleRange object
+ SampleRange GetPrimaryPlaybackRange() const;
+
+ /// @brief Get all static markers inside a range
+ /// @param range The sample range to retrieve markers for
+ /// @param markers Vector to fill found markers into
+ ///
+ /// The markers retrieved are static markers the user can't interact with.
+ /// Markers for user interaction are obtained through the timing controller.
+ void GetMarkers(const SampleRange &range, AudioMarkerVector &markers) const;
+
+
+ /// @brief Get the playback audio volume
+ /// @return The amplification factor for the audio
+ double GetVolume() const;
+
+ /// @brief Set the playback audio volume
+ /// @param volume The new amplification factor for the audio
+ void SetVolume(double volume);
+
+
+ /// @brief Return the current audio provider
+ /// @return A const pointer to the current audio provider
+ const AudioProvider * GetAudioProvider() const { return provider; }
+
+
+ /// @brief Return the current timing controller
+ /// @return The current timing controller or 0
+ AudioTimingController * GetTimingController() const { return timing_controller; }
+
+ /// @brief Change the current timing controller
+ /// @param new_mode The new timing controller or 0. This may be the same object as
+ /// the current timing controller, to signal that the timing controller has changed
+ /// the object being timed, eg. changed to a new dialogue line.
+ void SetTimingController(AudioTimingController *new_controller);
+
+
+ /// @brief Timing controller signals primary playback range changed
+ /// @param timing_controller The timing controller sending this notification
+ ///
+ /// Only timing controllers should call this function. This function must be called
+ /// when the primary playback range is changed in the timing controller, usually
+ /// as a result of user interaction.
+ void OnTimingControllerUpdatedPrimaryRange(AudioTimingController *timing_controller);
+
+ /// @brief Timing controller signals that the rendering style ranges have changed
+ /// @param timing_controller The timing controller sending this notification
+ ///
+ /// Only timing controllers should call this function. This function must be called
+ /// when one or more rendering style ranges have changed in the timing controller.
+ void OnTimingControllerUpdatedStyleRanges(AudioTimingController *timing_controller);
+
+ /// @brief Timing controller signals that an audio marker has moved
+ /// @param timing_controller The timing controller sending this notification
+ /// @param marker The marker that was moved
+ ///
+ /// Only timing controllers should call this function. This function must be called
+ /// when a marker owned by the timing controller has been updated in some way.
+ void OnTimingControllerMarkerMoved(AudioTimingController *timing_controller, AudioMarker *marker);
+
+
+ /// @brief Convert a count of audio samples to a time in milliseconds
+ /// @param samples Sample count to convert
+ /// @return The number of milliseconds equivalent to the sample-count, rounded down
+ int64_t MillisecondsFromSamples(int64_t samples) const;
+
+ /// @brief Convert a time in milliseconds to a count of audio samples
+ /// @param ms Time in milliseconds to convert
+ /// @return The index of the first sample that is wholly inside the millisecond
+ int64_t SamplesFromMilliseconds(int64_t ms) const;
+};
+
+
+
+/// @class AudioControllerAudioEventListener
+/// @brief Abstract interface for objects that want audio events
+class AudioControllerAudioEventListener {
+public:
+ /// A new audio stream was opened (and any previously open was closed)
+ virtual void OnAudioOpen(AudioProvider *) = 0;
+
+ /// The current audio stream was closed
+ virtual void OnAudioClose() = 0;
+
+ /// Playback is in progress and ths current position was updated
+ virtual void OnPlaybackPosition(int64_t sample_position) = 0;
+
+ /// Playback has stopped
+ virtual void OnPlaybackStop() = 0;
+};
+
+
+/// @class AudioControllerTimingEventListener
+/// @brief Abstract interface for objects that want audio timing events
+class AudioControllerTimingEventListener {
+public:
+ /// One or more moveable markers were moved
+ virtual void OnMarkersMoved() = 0;
+
+ /// The selection was changed
+ virtual void OnSelectionChanged() = 0;
+
+ /// The timing controller was replaced
+ virtual void OnTimingControllerChanged() = 0;
+};
+
+
+
+/// @class AudioMarkerProvider
+/// @brief Abstract interface for audio marker providers
+class AudioMarkerProvider {
+public:
+ /// Virtual destructor, does nothing
+ virtual ~AudioMarkerProvider() { }
+
+ /// @brief Return markers in a sample range
+ virtual void GetMarkers(const AudioController::SampleRange &range, AudioMarkerVector &out) const = 0;
+};
+
+
+
+/// @class AudioMarker
+/// @brief A marker on the audio display
+class AudioMarker {
+public:
+
+ /// Describe which directions a marker has feet in
+ enum FeetStyle {
+ Feet_None = 0,
+ Feet_Left,
+ Feet_Right,
+ Feet_Both // Conveniently Feet_Left|Feet_Right
+ };
+
+ /// @brief Get the marker's position
+ /// @return The marker's position in samples
+ virtual int64_t GetPosition() const = 0;
+
+ /// @brief Get the marker's drawing style
+ /// @return A pen object describing the marker's drawing style
+ virtual wxPen GetStyle() const = 0;
+
+ /// @brief Get the marker's feet style
+ /// @return The marker's feet style
+ virtual FeetStyle GetFeet() const = 0;
+
+ /// @brief Retrieve whether this marker participates in snapping
+ /// @return True if this marker may snap to other snappable markers
+ ///
+ /// If a marker being dragged returns true from this method, and another marker which also
+ /// returns true from this method is within range, the marker being dragged will be positioned
+ /// at the position of the other marker if it is released while it is inside snapping range.
+ virtual bool CanSnap() const = 0;
+};
+
+
+
+namespace agi {
+ DEFINE_BASE_EXCEPTION(AudioControllerError, Exception);
+ DEFINE_SIMPLE_EXCEPTION(AudioOpenError, AudioControllerError, "audio_controller/open_failed");
+};
diff --git a/aegisub/src/audio_display.cpp b/aegisub/src/audio_display.cpp
index 5a8c72995..47f7fdf21 100644
--- a/aegisub/src/audio_display.cpp
+++ b/aegisub/src/audio_display.cpp
@@ -1,4 +1,5 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro
+// Copyright (c) 2009-2010, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -32,2181 +33,1237 @@
/// @file audio_display.cpp
/// @brief Display audio in the main UI
/// @ingroup audio_ui
+///
+
///////////
// Headers
#include "config.h"
#ifndef AGI_PRE
-#include
+#include
-#include
-
-#include
-#include
+#include
+#include
+#include
#endif
-#include "ass_file.h"
-#include "audio_box.h"
+#include "ass_time.h"
+#include "audio_colorscheme.h"
+#include "audio_controller.h"
#include "audio_display.h"
-#include "audio_karaoke.h"
-#ifdef _DEBUG
-#include "audio_provider_dummy.h"
-#endif
-#include "colorspace.h"
-#include "compat.h"
-#include "fft.h"
-#include "hotkeys.h"
+#include "block_cache.h"
+#include "audio_renderer.h"
+#include "audio_renderer_spectrum.h"
+#include "audio_renderer_waveform.h"
+#include "selection_controller.h"
+#include "audio_timing.h"
+#include "include/aegisub/audio_provider.h"
#include "include/aegisub/audio_player.h"
#include "main.h"
-#include "standard_paths.h"
-#include "subs_edit_box.h"
-#include "subs_edit_ctrl.h"
-#include "subs_grid.h"
-#include "timeedit_ctrl.h"
#include "utils.h"
-#include "video_context.h"
-#ifdef __WXMAC__
-/// DOCME
-# define AudioDisplayWindowStyle wxWANTS_CHARS
-#else
+#undef min
+#undef max
-/// DOCME
-# define AudioDisplayWindowStyle wxSUNKEN_BORDER | wxWANTS_CHARS
-#endif
-/// @brief Constructor
-/// @param parent
-AudioDisplay::AudioDisplay(wxWindow *parent, SubtitlesGrid *grid)
-: wxWindow (parent, -1, wxDefaultPosition, wxSize(200,OPT_GET("Audio/Display Height")->GetInt()), AudioDisplayWindowStyle , _T("Audio Display"))
-, grid(grid)
-{
- // Set variables
- origImage = NULL;
- spectrumDisplay = NULL;
- spectrumDisplaySelected = NULL;
- spectrumRenderer = NULL;
- ScrollBar = NULL;
- dialogue = NULL;
- karaoke = NULL;
- peak = NULL;
- min = NULL;
- hasSel = false;
- diagUpdated = false;
- NeedCommit = false;
- loaded = false;
- temporary = false;
- blockUpdate = false;
- holding = false;
- draggingScale = false;
- Position = 0;
- PositionSample = 0;
- oldCurPos = 0;
- scale = 1.0f;
- provider = NULL;
- player = NULL;
- hold = 0;
- samples = 0;
- samplesPercent = 100;
- hasFocus = (wxWindow::FindFocus() == this);
- needImageUpdate = false;
- needImageUpdateWeak = true;
- playingToEnd = false;
+class AudioDisplayScrollbar : public AudioDisplayInteractionObject {
+ static const int height = 10;
- // Init
- UpdateTimer.SetOwner(this,Audio_Update_Timer);
- GetClientSize(&w,&h);
- h -= OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
- SetSamplesPercent(50,false);
+ wxRect bounds;
+ wxRect thumb;
- VideoContext *vc = VideoContext::Get();
- vc->AddKeyframesOpenListener(&AudioDisplay::Update, this);
- if (OPT_GET("Audio/Display/Draw/Video Position")->GetBool())
- vc->AddSeekListener(&AudioDisplay::UpdateImage, this, false);
+ bool dragging; // user is dragging with the primary mouse button
- grid->AddSelectionListener(this);
- commitListener = grid->ass->AddCommitListener(&AudioDisplay::OnCommit, this);
+ int data_length; // total amount of data in control
+ int page_length; // amount of data in one page
+ int position; // first item displayed
- // Set cursor
- //wxCursor cursor(wxCURSOR_BLANK);
- //SetCursor(cursor);
+ int sel_start; // first data item in selection
+ int sel_length; // number of data items in selection
- //wxLog::SetActiveTarget(new wxLogWindow(NULL,_T("Log"),true,false));
-}
+ AudioDisplay *display;
-/// @brief Destructor
-AudioDisplay::~AudioDisplay() {
- if (player) player->CloseStream();
- delete provider;
- delete player;
- delete origImage;
- delete spectrumRenderer;
- delete spectrumDisplay;
- delete spectrumDisplaySelected;
- delete[] peak;
- delete[] min;
- provider = NULL;
- player = NULL;
- origImage = NULL;
- spectrumRenderer = NULL;
- spectrumDisplay = NULL;
- spectrumDisplaySelected = NULL;
- peak = NULL;
- min = NULL;
-}
-
-/// @brief Reset
-void AudioDisplay::Reset() {
- hasSel = false;
- diagUpdated = false;
- NeedCommit = false;
- karaoke->enabled = false;
- karaoke->syllables.clear();
- box->karaokeMode = false;
- box->KaraokeButton->SetValue(false);
- dialogue = NULL;
-}
-
-/// @brief Update image
-/// @param weak
-void AudioDisplay::UpdateImage(bool weak) {
- // Update samples
- UpdateSamples();
-
- // Set image as needing to be redrawn
- needImageUpdate = true;
- if (weak == false && needImageUpdateWeak == true) {
- needImageUpdateWeak = false;
- }
- Refresh(false);
-}
-
-/// @brief Actually update the image on the display
-/// This is where most actual drawing of the audio display happens, or other functions
-/// to draw specific parts are called from.
-void AudioDisplay::DoUpdateImage() {
- // Loaded?
- if (!loaded || !provider) return;
-
- // Needs updating?
- if (!needImageUpdate) return;
- bool weak = needImageUpdateWeak;
-
- // Prepare bitmap
- int timelineHeight = OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
- int displayH = h+timelineHeight;
- if (origImage) {
- if (origImage->GetWidth() != w || origImage->GetHeight() != displayH) {
- delete origImage;
- origImage = NULL;
- }
+ // Recalculate thumb bounds from position and length data
+ void RecalculateThumb()
+ {
+ thumb.width = std::max((height+1)/2, bounds.width * page_length / data_length);
+ thumb.height = height;
+ thumb.x = bounds.width * position / data_length;
+ thumb.y = bounds.y;
}
- // Options
- bool draw_boundary_lines = OPT_GET("Audio/Display/Draw/Secondary Lines")->GetBool();
- bool draw_selection_background = OPT_GET("Audio/Display/Draw/Selection Background")->GetBool();
- bool drawKeyframes = OPT_GET("Audio/Display/Draw/Keyframes")->GetBool();
+public:
- // Invalid dimensions
- if (w == 0 || displayH == 0) return;
-
- // New bitmap
- if (!origImage) origImage = new wxBitmap(w,displayH,-1);
-
- // Is spectrum?
- bool spectrum = false;
- if (provider && OPT_GET("Audio/Spectrum")->GetBool()) {
- spectrum = true;
+ AudioDisplayScrollbar(AudioDisplay *_display)
+ : dragging(false)
+ , data_length(1)
+ , page_length(1)
+ , position(0)
+ , sel_start(-1)
+ , sel_length(0)
+ , display(_display)
+ {
}
- // Draw image to be displayed
- wxMemoryDC dc;
- dc.SelectObject(*origImage);
-
- // Black background
- dc.SetPen(*wxTRANSPARENT_PEN);
- dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Background/Background")->GetColour())));
- dc.DrawRectangle(0,0,w,h);
-
- // Selection position
- hasSel = false;
- hasKaraoke = karaoke->enabled;
- selStart = 0;
- selEnd = 0;
- lineStart = 0;
- lineEnd = 0;
- selStartCap = 0;
- selEndCap = 0;
- int64_t drawSelStart = 0;
- int64_t drawSelEnd = 0;
- if (dialogue) {
- GetDialoguePos(lineStart,lineEnd,false);
- hasSel = true;
- if (hasKaraoke) {
- GetKaraokePos(selStartCap,selEndCap,true);
- GetKaraokePos(drawSelStart,drawSelEnd,false);
- selStart = lineStart;
- selEnd = lineEnd;
- }
- else {
- GetDialoguePos(selStartCap,selEndCap,true);
- selStart = lineStart;
- selEnd = lineEnd;
- drawSelStart = lineStart;
- drawSelEnd = lineEnd;
- }
+ virtual ~AudioDisplayScrollbar()
+ {
}
- // Draw selection bg
- if (hasSel && drawSelStart < drawSelEnd && draw_selection_background) {
- if (NeedCommit && !karaoke->enabled) dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Background/Selection Modified")->GetColour())));
- else dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Background/Background")->GetColour())));
- dc.DrawRectangle(drawSelStart,0,drawSelEnd-drawSelStart,h);
+ // The audio display has changed size
+ void SetDisplaySize(const wxSize &display_size)
+ {
+ bounds.x = 0;
+ bounds.y = display_size.y - height;
+ bounds.width = display_size.x;
+ bounds.height = height;
+ page_length = display_size.x;
+
+ RecalculateThumb();
}
- // Draw spectrum
- if (spectrum) {
- DrawSpectrum(dc,weak);
+
+ const wxRect & GetBounds() const
+ {
+ return bounds;
}
- // Waveform
- else if (provider) {
- DrawWaveform(dc,weak);
+ int GetPosition() const
+ {
+ return position;
}
- // Nothing
- else {
- dc.DrawLine(0,h/2,w,h/2);
- }
+ int SetPosition(int new_position)
+ {
+ // These two conditionals can't be swapped, otherwise the position can become
+ // negative if the entire data is shorter than one page.
+ if (new_position + page_length >= data_length)
+ new_position = data_length - page_length - 1;
+ if (new_position < 0)
+ new_position = 0;
- // Draw seconds boundaries
- if (draw_boundary_lines) {
- int64_t start = Position*samples;
- int rate = provider->GetSampleRate();
- int pixBounds = rate / samples;
- dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Seconds Boundaries")->GetColour()),1,wxDOT));
- if (pixBounds >= 8) {
- for (int x=0;xGetBool()) {
- if (VideoContext::Get()->IsLoaded()) {
- dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Play Cursor")->GetColour())));
- int x = GetXAtMS(VideoContext::Get()->TimeAtFrame(VideoContext::Get()->GetFrameN()));
- dc.DrawLine(x,0,x,h);
- }
- }
-
- // Draw keyframes
- if (drawKeyframes && VideoContext::Get()->KeyFramesLoaded()) {
- DrawKeyframes(dc);
- }
-
- // Draw previous line
- DrawInactiveLines(dc);
-
- if (hasSel) {
- // Draw boundaries
- if (true) {
- // Draw start boundary
- int selWidth = OPT_GET("Audio/Line Boundaries Thickness")->GetInt();
- dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Line boundary Start")->GetColour())));
- dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Line boundary Start")->GetColour())));
- dc.DrawRectangle(lineStart-selWidth/2+1,0,selWidth,h);
- wxPoint points1[3] = { wxPoint(lineStart,0), wxPoint(lineStart+10,0), wxPoint(lineStart,10) };
- wxPoint points2[3] = { wxPoint(lineStart,h-1), wxPoint(lineStart+10,h-1), wxPoint(lineStart,h-11) };
- dc.DrawPolygon(3,points1);
- dc.DrawPolygon(3,points2);
-
- // Draw end boundary
- dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Line boundary End")->GetColour())));
- dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Line boundary End")->GetColour())));
- dc.DrawRectangle(lineEnd-selWidth/2+1,0,selWidth,h);
- wxPoint points3[3] = { wxPoint(lineEnd,0), wxPoint(lineEnd-10,0), wxPoint(lineEnd,10) };
- wxPoint points4[3] = { wxPoint(lineEnd,h-1), wxPoint(lineEnd-10,h-1), wxPoint(lineEnd,h-11) };
- dc.DrawPolygon(3,points3);
- dc.DrawPolygon(3,points4);
+ // This check is required to avoid mutual recursion with the display
+ if (new_position != position)
+ {
+ position = new_position;
+ RecalculateThumb();
+ display->ScrollPixelToLeft(position);
}
- // Draw karaoke
- if (hasKaraoke) {
- try {
- // Prepare
- wxPen curPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Syllable Boundaries")->GetColour()),1,wxDOT);
- dc.SetPen(curPen);
- wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"),wxFONTENCODING_SYSTEM);
- dc.SetFont(curFont);
- if (!spectrum) dc.SetTextForeground(lagi_wxColour(OPT_GET("Colour/Audio Display/Syllable Text")->GetColour()));
- else dc.SetTextForeground(wxColour(255,255,255));
- size_t karn = karaoke->syllables.size();
- int64_t pos1,pos2;
- int len,curpos;
- wxCoord tw=0,th=0;
- AudioKaraokeSyllable *curSyl;
- wxString temptext;
-
- // Draw syllables
- for (size_t i=0;isyllables.at(i);
- len = curSyl->duration*10;
- curpos = curSyl->start_time*10;
- if (len != -1) {
- pos1 = GetXAtMS(curStartMS+curpos);
- pos2 = GetXAtMS(curStartMS+len+curpos);
- dc.DrawLine(pos2,0,pos2,h);
- temptext = curSyl->text;
- temptext.Trim(true);
- temptext.Trim(false);
- GetTextExtent(temptext,&tw,&th,NULL,NULL,&curFont);
- dc.DrawText(temptext,(pos1+pos2-tw)/2,4);
- }
- }
- }
- catch (...) {
- // FIXME?
- }
- }
+ return position;
}
- // Modified text
- if (NeedCommit) {
- dc.SetFont(wxFont(9,wxDEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"))); // FIXME: hardcoded font name
- dc.SetTextForeground(wxColour(255,0,0));
- if (selStart <= selEnd) {
- dc.DrawText(_T("Modified"),4,4);
- }
- else {
- dc.DrawText(_T("Negative time"),4,4);
- }
+ void SetSelection(int new_start, int new_length)
+ {
+ sel_start = new_start;
+ sel_length = new_length;
}
- // Draw timescale
- if (timelineHeight) {
- DrawTimescale(dc);
+ void ChangeLengths(int new_data_length, int new_page_length)
+ {
+ data_length = new_data_length;
+ page_length = new_page_length;
+
+ RecalculateThumb();
}
- // Draw selection border
- if (hasFocus) {
- dc.SetPen(*wxGREEN_PEN);
+ bool OnMouseEvent(wxMouseEvent &event)
+ {
+ if (event.LeftIsDown())
+ {
+ const int thumb_left = event.GetPosition().x - thumb.width/2;
+ const int data_length_less_page = data_length - page_length;
+ const int shaft_length_less_thumb = bounds.width - thumb.width;
+
+ SetPosition(data_length_less_page * thumb_left / shaft_length_less_thumb);
+
+ dragging = true;
+ }
+ else if (event.LeftUp())
+ {
+ dragging = false;
+ }
+
+ return dragging;
+ }
+
+ void Paint(wxDC &dc, bool has_focus)
+ {
+ wxColour light(89, 145, 220);
+ wxColour dark(8, 4, 13);
+ wxColour sel(65, 34, 103);
+
+ if (has_focus)
+ {
+ light.Set(205, 240, 226);
+ sel.Set(82, 107, 213);
+ }
+
+ dc.SetPen(wxPen(light));
+ dc.SetBrush(wxBrush(dark));
+ dc.DrawRectangle(bounds);
+
+ if (sel_length > 0 && sel_start >= 0)
+ {
+ wxRect r;
+ r.x = sel_start * bounds.width / data_length;
+ r.y = bounds.y;
+ r.width = sel_length * bounds.width / data_length;
+ r.height = bounds.height;
+
+ dc.SetPen(wxPen(sel));
+ dc.SetBrush(wxBrush(sel));
+ dc.DrawRectangle(r);
+ }
+
+ dc.SetPen(wxPen(light));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
- dc.DrawRectangle(0,0,w,h);
+ dc.DrawRectangle(bounds);
+
+ dc.SetPen(wxPen(light));
+ dc.SetBrush(wxBrush(light));
+ dc.DrawRectangle(thumb);
+ }
+};
+
+
+
+class AudioDisplayTimeline : public AudioDisplayInteractionObject {
+ int64_t num_samples;
+ int samplerate;
+ int samples_per_pixel;
+ int pixel_left;
+
+ wxRect bounds;
+
+ wxPoint drag_lastpos;
+ bool dragging;
+
+ enum Scale {
+ Sc_Millisecond,
+ Sc_Centisecond,
+ Sc_Decisecond,
+ Sc_Second,
+ Sc_Decasecond,
+ Sc_Minute,
+ Sc_Decaminute,
+ Sc_Hour,
+ Sc_Decahour, // If anyone needs this they should reconsider their project
+ Sc_MAX = Sc_Decahour
+ };
+ Scale scale_minor;
+ int scale_major_modulo; // If minor_scale_mark_index % scale_major_modulo == 0 the mark is a major mark
+ double scale_minor_divisor; // Absolute scale-mark index multiplied by this number gives sample index for scale mark
+
+ AudioDisplay *display;
+
+public:
+
+ AudioDisplayTimeline(AudioDisplay *_display)
+ : num_samples(0)
+ , samplerate(44100)
+ , samples_per_pixel(1)
+ , pixel_left(0)
+ , dragging(false)
+ , display(_display)
+ {
}
- // Done
- needImageUpdate = false;
- needImageUpdateWeak = true;
-}
-
-/// @brief Draw other lines than the current active
-/// @param dc The DC to draw to.
-/// Draws markers for inactive lines, eg. the previous line, per configuration.
-void AudioDisplay::DrawInactiveLines(wxDC &dc) {
- // Check if there is anything to do
- int shadeType = OPT_GET("Audio/Inactive Lines Display Mode")->GetInt();
- if (shadeType == 0) return;
-
- // Spectrum?
- bool spectrum = false;
- if (provider && OPT_GET("Audio/Spectrum")->GetBool()) {
- spectrum = true;
+ virtual ~AudioDisplayTimeline()
+ {
}
- // Set options
- dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Audio Display/Line Boundary Inactive Line")->GetColour())));
- int selWidth = OPT_GET("Audio/Line Boundaries Thickness")->GetInt();
- AssDialogue *shade;
- int shadeX1,shadeX2;
- int shadeFrom,shadeTo;
-
- // Only previous
- if (shadeType == 1) {
- shadeFrom = this->line_n-1;
- shadeTo = shadeFrom+1;
+ int GetHeight() const
+ {
+ int width, height;
+ display->GetTextExtent(_T("0123456789:."), &width, &height);
+ return height + 4;
}
- // All
- else {
- shadeFrom = 0;
- shadeTo = grid->GetRows();
- }
-
- for (int j=shadeFrom;jGetDialogue(j);
-
- if (shade) {
- // Get coordinates
- shadeX1 = GetXAtMS(shade->Start.GetMS());
- shadeX2 = GetXAtMS(shade->End.GetMS());
- if (shadeX2 < 0 || shadeX1 > w) continue;
-
- // Draw over waveform
- if (!spectrum) {
- // Selection
- int selX1 = MAX(0,GetXAtMS(curStartMS));
- int selX2 = MIN(w,GetXAtMS(curEndMS));
-
- // Get ranges (x1->x2, x3->x4).
- int x1 = MAX(0,shadeX1);
- int x2 = MIN(w,shadeX2);
- int x3 = MAX(x1,selX2);
- int x4 = MAX(x2,selX2);
-
- // Clip first range
- x1 = MIN(x1,selX1);
- x2 = MIN(x2,selX1);
-
- // Set pen and draw
- dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform Inactive")->GetColour())));
- for (int i=x1;iGetColour())));
- dc.DrawRectangle(shadeX1-selWidth/2+1,0,selWidth,h);
- dc.DrawRectangle(shadeX2-selWidth/2+1,0,selWidth,h);
- }
- }
-}
-
-/// @brief Draw keyframe markers
-/// @param dc The DC to draw to.
-void AudioDisplay::DrawKeyframes(wxDC &dc) {
- std::vector KeyFrames = VideoContext::Get()->GetKeyFrames();
- int nKeys = (int)KeyFrames.size();
- dc.SetPen(wxPen(wxColour(255,0,255),1));
-
- // Get min and max frames to care about
- int minFrame = VideoContext::Get()->FrameAtTime(GetMSAtX(0),agi::vfr::START);
- int maxFrame = VideoContext::Get()->FrameAtTime(GetMSAtX(w),agi::vfr::END);
-
- // Scan list
- for (int i=0;i= minFrame && cur <= maxFrame) {
- int x = GetXAtMS(VideoContext::Get()->TimeAtFrame(cur,agi::vfr::START));
- dc.DrawLine(x,0,x,h);
- }
- else if (cur > maxFrame) break;
- }
-}
-
-/// @brief Draw timescale at bottom of audio display
-/// @param dc The DC to draw to.
-void AudioDisplay::DrawTimescale(wxDC &dc) {
- // Set size
- int timelineHeight = OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
-
- // Set colours
- dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
- dc.SetPen(*wxTRANSPARENT_PEN);
- dc.DrawRectangle(0,h,w,timelineHeight);
- dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT));
- dc.DrawLine(0,h,w,h);
- dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DHIGHLIGHT));
- dc.DrawLine(0,h+1,w,h+1);
- dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
- dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT));
- wxFont scaleFont;
- scaleFont.SetFaceName(_T("Tahoma")); // FIXME: hardcoded font name
- scaleFont.SetPointSize(8);
- dc.SetFont(scaleFont);
-
- // Timescale ticks
- int64_t start = Position*samples;
- int rate = provider->GetSampleRate();
- for (int i=1;i<32;i*=2) {
- int pixBounds = rate / (samples * 4 / i);
- if (pixBounds >= 8) {
- for (int x=0;x= 3600) {
- s -= 3600;
- hr++;
- }
- while (s >= 60) {
- s -= 60;
- m++;
- }
- wxString text;
- if (hr) text = wxString::Format(_T("%i:%02i:%02i"),hr,m,s);
- else if (m) text = wxString::Format(_T("%i:%02i"),m,s);
- else text = wxString::Format(_T("%i"),s);
- dc.GetTextExtent(text,&textW,&textH,NULL,NULL,&scaleFont);
- dc.DrawText(text,MAX(0,x-textW/2)+1,h+8);
- }
-
- // Other
- else if (pos % (rate / 4 * i) < samples) {
- dc.DrawLine(x,h+2,x,h+5);
- }
- }
- break;
- }
- }
-}
-
-/// @brief Draw audio waveform
-/// @param dc The DC to draw to.
-/// @param weak False if the visible portion of the display has changed.
-void AudioDisplay::DrawWaveform(wxDC &dc,bool weak) {
- // Prepare Waveform
- if (!weak || peak == NULL || min == NULL) {
- if (peak) delete[] peak;
- if (min) delete[] min;
- peak = new int[w];
- min = new int[w];
+ void SetDisplaySize(const wxSize &display_size)
+ {
+ // The size is without anything that goes below the timeline (like scrollbar)
+ bounds.width = display_size.x;
+ bounds.height = GetHeight();
+ bounds.x = 0;
+ bounds.y = 0;
}
- // Get waveform
- if (!weak) {
- provider->GetWaveForm(min,peak,Position*samples,w,h,samples,scale);
+ const wxRect & GetBounds() const
+ {
+ return bounds;
}
- // Draw pre-selection
- if (!hasSel) selStartCap = w;
- dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform")->GetColour())));
- for (int64_t i=0;iGetBool()) {
- if (NeedCommit && !karaoke->enabled) dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform Modified")->GetColour())));
- else dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Waveform Selected")->GetColour())));
- }
- for (int64_t i=selStartCap;iGetColour())));
- for (int64_t i=selEndCap;iGetWidth() != w || spectrumDisplay->GetHeight() != h) {
- if (spectrumDisplay) {
- delete spectrumDisplay;
- delete spectrumDisplaySelected;
- spectrumDisplay = 0;
- spectrumDisplaySelected = 0;
- }
- weak = false;
- }
-
- if (!weak) {
- if (!spectrumRenderer)
- spectrumRenderer = new AudioSpectrum(provider);
-
- spectrumRenderer->SetScaling(scale);
-
- unsigned char *img = (unsigned char *)malloc(h*w*3); // wxImage requires using malloc
-
- // Use a slightly slower, but simple way
- // Always draw the spectrum for the entire width
- // Hack: without those divs by 2 the display is horizontally compressed
- spectrumRenderer->RenderRange(Position*samples, (Position+w)*samples, false, img, 0, w, w, h);
-
- // The spectrum bitmap will have been deleted above already, so just make a new one
- wxImage imgobj(w, h, img, false);
- spectrumDisplay = new wxBitmap(imgobj);
- }
-
- if (hasSel && selStartCap < selEndCap && !spectrumDisplaySelected) {
- // There is a visible selection and we don't have a rendered one
- // This should be done regardless whether we're "weak" or not
- // Assume a few things were already set up when things were first rendered though
- unsigned char *img = (unsigned char *)malloc(h*w*3);
- spectrumRenderer->RenderRange(Position*samples, (Position+w)*samples, true, img, 0, w, w, h);
- wxImage imgobj(w, h, img, false);
- spectrumDisplaySelected = new wxBitmap(imgobj);
- }
-
- // Draw
- wxMemoryDC dc;
- dc.SelectObject(*spectrumDisplay);
- finaldc.Blit(0,0,w,h,&dc,0,0);
-
- if (hasSel && spectrumDisplaySelected && selStartCap < selEndCap) {
- dc.SelectObject(*spectrumDisplaySelected);
- finaldc.Blit(selStartCap, 0, selEndCap-selStartCap, h, &dc, selStartCap, 0);
- }
-}
-
-/// @brief Get selection position
-/// @param selStart
-/// @param selEnd
-/// @param cap
-void AudioDisplay::GetDialoguePos(int64_t &selStart,int64_t &selEnd, bool cap) {
- selStart = GetXAtMS(curStartMS);
- selEnd = GetXAtMS(curEndMS);
-
- if (cap) {
- if (selStart < 0) selStart = 0;
- if (selEnd < 0) selEnd = 0;
- if (selStart >= w) selStart = w-1;
- if (selEnd >= w) selEnd = w-1;
- }
-}
-
-/// @brief Get karaoke position
-/// @param karStart
-/// @param karEnd
-/// @param cap
-void AudioDisplay::GetKaraokePos(int64_t &karStart,int64_t &karEnd, bool cap) {
- try {
- // Wrap around
- int nsyls = (int)karaoke->syllables.size();
- if (karaoke->curSyllable == -1) {
- karaoke->SetSyllable(nsyls-1);
- }
- if (karaoke->curSyllable >= nsyls) karaoke->curSyllable = nsyls-1;
-
- // Get positions
- int pos = karaoke->syllables.at(karaoke->curSyllable).start_time;
- int len = karaoke->syllables.at(karaoke->curSyllable).duration;
- karStart = GetXAtMS(curStartMS+pos*10);
- karEnd = GetXAtMS(curStartMS+pos*10+len*10);
-
- // Cap
- if (cap) {
- if (karStart < 0) karStart = 0;
- if (karEnd < 0) karEnd = 0;
- if (karStart >= w) karStart = w-1;
- if (karEnd >= w) karEnd = w-1;
- }
- }
- catch (...) {
- }
-}
-
-/// @brief Update
-/// @return
-void AudioDisplay::Update() {
- if (blockUpdate) return;
- if (loaded) {
- if (OPT_GET("Audio/Auto/Scroll")->GetBool())
- MakeDialogueVisible();
- else
- UpdateImage(true);
- }
-}
-
-/// @brief Recreate the image
-void AudioDisplay::RecreateImage() {
- GetClientSize(&w,&h);
- h -= OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
- delete origImage;
- origImage = NULL;
- UpdateImage(false);
-}
-
-/// @brief Make dialogue visible
-/// @param force
-void AudioDisplay::MakeDialogueVisible(bool force) {
- // Variables
- int startShow=0, endShow=0;
- if (karaoke->enabled) {
- // In karaoke mode the syllable and as much as possible towards the end of the line should be shown
- int dummy = 0;
- GetTimesSelection(startShow, dummy);
- GetTimesDialogue(dummy, endShow);
- } else {
- GetTimesSelection(startShow,endShow);
- }
- int startPos = GetSampleAtMS(startShow);
- int endPos = GetSampleAtMS(endShow);
- int startX = GetXAtMS(startShow);
- int endX = GetXAtMS(endShow);
-
- if (force || startX < 50 || endX > w-50) {
- if (startX < 50 || endX - startX >= w) {
- // Make sure the left edge of the selection is at least 50 pixels from the edge of the display
- UpdatePosition(startPos - 50*samples, true);
+ if (px_sec > 3000) {
+ scale_minor = Sc_Millisecond;
+ scale_minor_divisor = (double)samplerate / 1000;
+ scale_major_modulo = 10;
+ } else if (px_sec > 300) {
+ scale_minor = Sc_Centisecond;
+ scale_minor_divisor = (double)samplerate / 100;
+ scale_major_modulo = 10;
+ } else if (px_sec > 30) {
+ scale_minor = Sc_Decisecond;
+ scale_minor_divisor = (double)samplerate / 10;
+ scale_major_modulo = 10;
+ } else if (px_sec > 3) {
+ scale_minor = Sc_Second;
+ scale_minor_divisor = (double)samplerate;
+ scale_major_modulo = 10;
+ } else if (px_sec > 1.0/3.0) {
+ scale_minor = Sc_Decasecond;
+ scale_minor_divisor = (double)samplerate * 10;
+ scale_major_modulo = 6;
+ } else if (px_sec > 1.0/9.0) {
+ scale_minor = Sc_Minute;
+ scale_minor_divisor = (double)samplerate * 60;
+ scale_major_modulo = 10;
+ } else if (px_sec > 1.0/90.0) {
+ scale_minor = Sc_Decaminute;
+ scale_minor_divisor = (double)samplerate * 600;
+ scale_major_modulo = 6;
} else {
- // Otherwise center the selection in display
- UpdatePosition((startPos+endPos-w*samples)/2,true);
+ scale_minor = Sc_Hour;
+ scale_minor_divisor = (double)samplerate * 3600;
+ scale_major_modulo = 10;
}
}
- // Update
- UpdateImage();
-}
+ void SetPosition(int new_pixel_left)
+ {
+ if (new_pixel_left < 0)
+ new_pixel_left = 0;
-/// @brief Set position
-/// @param pos
-void AudioDisplay::SetPosition(int pos) {
- Position = pos;
- PositionSample = pos * samples;
- UpdateImage();
-}
-
-/// @brief Update position
-/// @param pos
-/// @param IsSample
-/// @return
-void AudioDisplay::UpdatePosition (int pos,bool IsSample) {
- // Safeguards
- if (!provider) return;
- if (IsSample) pos /= samples;
- int len = provider->GetNumSamples() / samples;
- if (pos < 0) pos = 0;
- if (pos >= len) pos = len-1;
-
- // Set
- Position = pos;
- PositionSample = pos*samples;
- UpdateScrollbar();
-}
-
-/// @brief Note: aka Horizontal Zoom Set samples in percentage
-/// @param percent
-/// @param update
-/// @param pivot
-/// @return
-void AudioDisplay::SetSamplesPercent(int percent,bool update,float pivot) {
- // Calculate
- if (percent < 1) percent = 1;
- if (percent > 100) percent = 100;
- if (samplesPercent == percent) return;
- samplesPercent = percent;
-
- // Update
- if (update) {
- // Center scroll
- int oldSamples = samples;
- UpdateSamples();
- PositionSample += int64_t((oldSamples-samples)*w*pivot);
- if (PositionSample < 0) PositionSample = 0;
-
- // Update
- UpdateSamples();
- UpdateScrollbar();
- UpdateImage();
- Refresh(false);
- }
-}
-
-/// @brief Update samples
-/// @return
-void AudioDisplay::UpdateSamples() {
- // Set samples
- if (!provider) return;
- if (w) {
- int64_t totalSamples = provider->GetNumSamples();
- int total = totalSamples / w;
- int max = 5760000 / w; // 2 minutes at 48 kHz maximum
- if (total > max) total = max;
- int min = 8;
- if (total < min) total = min;
- int range = total-min;
- samples = int(range*pow(samplesPercent/100.0,3)+min);
-
- // Set position
- int length = w * samples;
- if (PositionSample + length > totalSamples) {
- PositionSample = totalSamples - length;
- if (PositionSample < 0) PositionSample = 0;
- if (samples) Position = PositionSample / samples;
- }
- }
-}
-
-/// @brief Set scale
-/// @param _scale
-/// @return
-void AudioDisplay::SetScale(float _scale) {
- if (scale == _scale) return;
- scale = _scale;
- UpdateImage();
-}
-
-/// @brief Load from file
-/// @param file
-/// @return
-void AudioDisplay::SetFile(wxString file) {
- // Unload
- if (file.IsEmpty()) try {
- try {
- if (player) player->CloseStream();
- }
- catch (const wxChar *e) {
- wxLogError(e);
- }
- delete provider;
- delete player;
- delete spectrumRenderer;
- provider = NULL;
- player = NULL;
- spectrumRenderer = NULL;
- try {
- Reset();
- }
- catch (const wxChar *e) {
- wxLogError(e);
+ if (new_pixel_left != pixel_left)
+ {
+ pixel_left = new_pixel_left;
+ display->ScrollPixelToLeft(pixel_left);
}
- loaded = false;
- temporary = false;
- StandardPaths::SetPathValue(_T("?audio"),_T(""));
- }
- catch (wxString e) {
- wxLogError(e);
- }
- catch (const wxChar *e) {
- wxLogError(e);
- }
- catch (...) {
- wxLogError(_T("Unknown error unloading audio"));
}
- // Load
- else {
- SetFile(_T(""));
- try {
- // Get provider
- bool is_dummy = false;
-#ifdef _DEBUG
- if (file == _T("?dummy")) {
- is_dummy = true;
- provider = new DummyAudioProvider(150*60*1000, false); // 150 minutes non-noise
- } else if (file == _T("?noise")) {
- is_dummy = true;
- provider = new DummyAudioProvider(150*60*1000, true); // 150 minutes noise
- } else {
- provider = AudioProviderFactory::GetProvider(file);
+ bool OnMouseEvent(wxMouseEvent &event)
+ {
+ if (event.LeftDown())
+ {
+ drag_lastpos = event.GetPosition();
+ dragging = true;
+ }
+ else if (event.LeftIsDown())
+ {
+ SetPosition(pixel_left - event.GetPosition().x + drag_lastpos.x);
+
+ drag_lastpos = event.GetPosition();
+ dragging = true;
+ }
+ else if (event.LeftUp())
+ {
+ dragging = false;
+ }
+
+ return dragging;
+ }
+
+ void Paint(wxDC &dc)
+ {
+ wxColour light(89, 145, 220);
+ wxColour dark(8, 4, 13);
+
+ int bottom = bounds.y + bounds.height;
+
+ // Background
+ dc.SetPen(wxPen(dark));
+ dc.SetBrush(wxBrush(dark));
+ dc.DrawRectangle(bounds);
+
+ // Top line
+ dc.SetPen(wxPen(light));
+ dc.DrawLine(bounds.x, bottom-1, bounds.x+bounds.width, bottom-1);
+
+ // Prepare for writing text
+ dc.SetTextBackground(dark);
+ dc.SetTextForeground(light);
+
+ // Figure out the first scale mark to show
+ int64_t sample_left = pixel_left * samples_per_pixel;
+ int next_scale_mark = (int)(sample_left / scale_minor_divisor);
+ if (next_scale_mark * scale_minor_divisor < sample_left)
+ next_scale_mark += 1;
+ assert(next_scale_mark * scale_minor_divisor >= sample_left);
+
+ // Draw scale marks
+ int next_scale_mark_pos;
+ int last_text_right = -1;
+ int last_hour = -1, last_minute = -1;
+ if (num_samples / samplerate < 3600) last_hour = 0; // Trick to only show hours if audio is longer than 1 hour
+ do {
+ next_scale_mark_pos = (int)(next_scale_mark * scale_minor_divisor / samples_per_pixel) - pixel_left;
+ bool mark_is_major = next_scale_mark % scale_major_modulo == 0;
+
+ if (mark_is_major)
+ dc.DrawLine(next_scale_mark_pos, bottom-6, next_scale_mark_pos, bottom-1);
+ else
+ dc.DrawLine(next_scale_mark_pos, bottom-4, next_scale_mark_pos, bottom-1);
+
+ // Print time labels on major scale marks
+ if (mark_is_major && next_scale_mark_pos > last_text_right)
+ {
+ double mark_time = next_scale_mark * scale_minor_divisor / samplerate;
+ int mark_hour = (int)(mark_time / 3600);
+ int mark_minute = (int)(mark_time / 60) % 60;
+ double mark_second = mark_time - mark_hour*3600 - mark_minute*60;
+
+ wxString time_string;
+ bool changed_hour = mark_hour != last_hour;
+ bool changed_minute = mark_minute != last_minute;
+
+ if (changed_hour)
+ {
+ time_string = wxString::Format(_T("%d:%02d:"), mark_hour, mark_minute);
+ last_hour = mark_hour;
+ last_minute = mark_minute;
+ }
+ else if (changed_minute)
+ {
+ time_string = wxString::Format(_T("%d:"), mark_minute);
+ last_minute = mark_minute;
+ }
+ if (scale_minor >= Sc_Decisecond)
+ time_string += wxString::Format(_T("%02d"), (int)mark_second);
+ else if (scale_minor == Sc_Centisecond)
+ time_string += wxString::Format(_T("%02.1f"), mark_second);
+ else
+ time_string += wxString::Format(_T("%02.2f"), mark_second);
+
+ int tw, th;
+ dc.GetTextExtent(time_string, &tw, &th);
+ last_text_right = next_scale_mark_pos + tw;
+
+ dc.DrawText(time_string, next_scale_mark_pos, 0);
}
-#else
- provider = AudioProviderFactory::GetProvider(file);
+
+ next_scale_mark += 1;
+
+ } while (next_scale_mark_pos < bounds.width);
+ }
+};
+
+
+
+class AudioMarkerInteractionObject : public AudioDisplayInteractionObject {
+ // Object-pair being intracted with
+ AudioMarker *marker;
+ AudioTimingController *timing_controller;
+ // Audio display drag is happening on
+ AudioDisplay *display;
+ // Audio controller managing it all
+ AudioController *controller;
+ // Mouse button used to initiate the drag
+ wxMouseButton button_used;
+ // Default to snapping to snappable markers
+ bool default_snap;
+ // Range in pixels to snap at
+ int snap_range;
+
+public:
+ AudioMarkerInteractionObject(AudioMarker *marker, AudioTimingController *timing_controller, AudioDisplay *display, AudioController *controller, wxMouseButton button_used)
+ : marker(marker)
+ , timing_controller(timing_controller)
+ , display(display)
+ , controller(controller)
+ , button_used(button_used)
+ {
+ /// @todo Make these configurable
+ snap_range = 5;
+ default_snap = false;
+ }
+
+ virtual ~AudioMarkerInteractionObject()
+ {
+ }
+
+ virtual bool OnMouseEvent(wxMouseEvent &event)
+ {
+ if (event.Dragging())
+ {
+ int64_t sample_pos = display->SamplesFromRelativeX(event.GetPosition().x);
+
+ if (marker->CanSnap() && (default_snap != event.ShiftDown()))
+ {
+ AudioController::SampleRange snap_sample_range(
+ display->SamplesFromRelativeX(event.GetPosition().x - snap_range),
+ display->SamplesFromRelativeX(event.GetPosition().x + snap_range));
+ const AudioMarker *snap_marker = 0;
+ AudioMarkerVector potential_snaps;
+ controller->GetMarkers(snap_sample_range, potential_snaps);
+ for (AudioMarkerVector::iterator mi = potential_snaps.begin(); mi != potential_snaps.end(); ++mi)
+ {
+ if ((*mi)->CanSnap())
+ {
+ if (!snap_marker)
+ snap_marker = *mi;
+ else if (tabs((*mi)->GetPosition() - sample_pos) < tabs(snap_marker->GetPosition() - sample_pos))
+ snap_marker = *mi;
+ }
+ }
+
+ if (snap_marker)
+ sample_pos = snap_marker->GetPosition();
+ }
+
+ timing_controller->OnMarkerDrag(marker, sample_pos);
+ }
+
+ // We lose the marker drag if the button used to initiate it goes up
+ return !event.ButtonUp(button_used);
+ }
+};
+
+
+
+
+AudioDisplay::AudioDisplay(wxWindow *parent, AudioController *_controller)
+: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS|wxBORDER_SIMPLE)
+, provider(0)
+, controller(_controller)
+, dragged_object(0)
+, old_selection(0, 0)
+{
+ scrollbar = new AudioDisplayScrollbar(this);
+ timeline = new AudioDisplayTimeline(this);
+
+ audio_renderer = new AudioRenderer;
+ audio_spectrum_renderer = new AudioSpectrumRenderer;
+ audio_waveform_renderer = new AudioWaveformRenderer;
+
+ scroll_left = 0;
+ pixel_audio_width = 0;
+ scale_amplitude = 1.0;
+
+ track_cursor_pos = -1;
+
+ controller->AddAudioListener(this);
+ controller->AddTimingListener(this);
+
+ audio_renderer->SetAmplitudeScale(scale_amplitude);
+ SetZoomLevel(0);
+
+ ReloadRenderingSettings();
+
+ SetMinClientSize(wxSize(-1, 70));
+ SetBackgroundStyle(wxBG_STYLE_PAINT);
+ SetThemeEnabled(false);
+}
+
+
+AudioDisplay::~AudioDisplay()
+{
+ delete audio_renderer;
+ delete audio_spectrum_renderer;
+ delete audio_waveform_renderer;
+
+ delete timeline;
+ delete scrollbar;
+
+ controller->RemoveAudioListener(this);
+ controller->RemoveTimingListener(this);
+}
+
+
+void AudioDisplay::ScrollBy(int pixel_amount)
+{
+ ScrollPixelToLeft(scroll_left + pixel_amount);
+}
+
+
+void AudioDisplay::ScrollPixelToLeft(int pixel_position)
+{
+ const int client_width = GetClientRect().GetWidth();
+
+ if (pixel_position + client_width >= pixel_audio_width)
+ pixel_position = pixel_audio_width - client_width;
+ if (pixel_position < 0)
+ pixel_position = 0;
+
+ // This check is required to avoid needless redraws, but more importantly to
+ // avoid mutual recursion with the scrollbar and timeline.
+ if (pixel_position != scroll_left)
+ {
+ scroll_left = pixel_position;
+ scrollbar->SetPosition(scroll_left);
+ timeline->SetPosition(scroll_left);
+ Refresh();
+ }
+}
+
+
+void AudioDisplay::ScrollPixelToCenter(int pixel_position)
+{
+ ScrollPixelToLeft(pixel_position - GetClientRect().GetWidth()/2);
+}
+
+
+void AudioDisplay::ScrollSampleToLeft(int64_t sample_position)
+{
+ ScrollPixelToLeft(AbsoluteXFromSamples(sample_position));
+}
+
+
+void AudioDisplay::ScrollSampleToCenter(int64_t sample_position)
+{
+ ScrollPixelToCenter(AbsoluteXFromSamples(sample_position));
+}
+
+
+void AudioDisplay::ScrollSampleRangeInView(const AudioController::SampleRange &range)
+{
+ int client_width = GetClientRect().GetWidth();
+ int range_begin = AbsoluteXFromSamples(range.begin());
+ int range_end = AbsoluteXFromSamples(range.end());
+ int range_len = range_end - range_begin;
+
+ // Is everything already in view?
+ if (range_begin >= scroll_left && range_end <= scroll_left+client_width)
+ return;
+
+ // For the rest of the calculation, remove 5 % from each side of the client area.
+ // The leftadjust is the amount to subtract from the final scroll_left value.
+ int leftadjust = client_width / 20;
+ client_width = client_width * 9 / 10;
+
+ // The entire range can fit inside the view, center it
+ if (range_len < client_width)
+ {
+ ScrollPixelToLeft(range_begin - (client_width-range_len)/2 - leftadjust);
+ }
+
+ // Range doesn't fit in view and we're viewing a middle part of it, just leave it alone
+ else if (range_begin < scroll_left+leftadjust && range_end > scroll_left+leftadjust+client_width)
+ {
+ // nothing
+ }
+
+ // Right edge is in view, scroll it as far to the right as possible
+ else if (range_end >= scroll_left+leftadjust && range_end < scroll_left+leftadjust+client_width)
+ {
+ ScrollPixelToLeft(range_end - client_width - leftadjust);
+ }
+
+ // Nothing is in view or the left edge is in view, scroll left edge as far to the left as possible
+ else
+ {
+ ScrollPixelToLeft(range_begin - leftadjust);
+ }
+}
+
+void AudioDisplay::SetZoomLevel(int new_zoom_level)
+{
+ zoom_level = new_zoom_level;
+
+ if (!provider)
+ {
+ pixel_samples = 1;
+ return;
+ }
+
+ const int samples_per_second = provider ? provider->GetSampleRate() : 48000;
+ const int base_pixels_per_second = 50; /// @todo Make this customisable
+ const int base_samples_per_pixel = samples_per_second / base_pixels_per_second;
+
+ const int factor = GetZoomLevelFactor(zoom_level);
+
+ const int new_samples_per_pixel = std::max(1, 100 * base_samples_per_pixel / factor);
+
+ if (pixel_samples != new_samples_per_pixel)
+ {
+ pixel_samples = new_samples_per_pixel;
+ audio_renderer->SetSamplesPerPixel(pixel_samples);
+
+ if (provider)
+ pixel_audio_width = provider->GetNumSamples() / pixel_samples + 1;
+ else
+ pixel_audio_width = 1;
+
+ scrollbar->ChangeLengths(pixel_audio_width, GetClientSize().GetWidth());
+ timeline->ChangeZoom(pixel_samples);
+
+ Refresh();
+ }
+}
+
+
+int AudioDisplay::GetZoomLevel() const
+{
+ return zoom_level;
+}
+
+
+wxString AudioDisplay::GetZoomLevelDescription(int level) const
+{
+ const int factor = GetZoomLevelFactor(level);
+ const int base_pixels_per_second = 50; /// @todo Make this customisable along with the above
+ const int second_pixels = 100 * base_pixels_per_second / factor;
+
+ return wxString::Format(_("%d%%, %d pixel/second"), factor, second_pixels);
+}
+
+
+int AudioDisplay::GetZoomLevelFactor(int level)
+{
+ int factor = 100;
+
+ if (level > 0)
+ {
+ factor += 25 * level;
+ }
+ else if (level < 0)
+ {
+ if (level >= -5)
+ factor += 10 * level;
+ else if (level >= -11)
+ factor = 50 + (level+5) * 5;
+ else
+ factor = 20 + level + 11;
+ if (factor <= 0)
+ factor = 1;
+ }
+
+ return factor;
+}
+
+
+void AudioDisplay::SetAmplitudeScale(float scale)
+{
+ audio_renderer->SetAmplitudeScale(scale);
+ Refresh();
+}
+
+
+float AudioDisplay::GetAmplitudeScale() const
+{
+ return audio_renderer->GetAmplitudeScale();
+}
+
+
+void AudioDisplay::ReloadRenderingSettings()
+{
+ int64_t spectrum_quality = OPT_GET("Audio/Renderer/Spectrum/Quality")->GetInt();
+#ifdef WITH_FFTW
+ // FFTW is so fast we can afford to upgrade quality by two levels
+ spectrum_quality += 2;
#endif
+ if (spectrum_quality < 0) spectrum_quality = 0;
+ if (spectrum_quality > 5) spectrum_quality = 5;
- // Get player
- player = AudioPlayerFactory::GetAudioPlayer();
- player->SetDisplayTimer(&UpdateTimer);
- player->SetProvider(provider);
- player->OpenStream();
- loaded = true;
+ // Quality indexes: 0 1 2 3 4 5
+ int spectrum_width[] = {8, 9, 9, 9, 10, 11};
+ int spectrum_distance[] = {8, 8, 7, 6, 6, 5};
- // Add to recent
- if (!is_dummy) {
- config::mru->Add("Audio", STD_STR(file));
- wxFileName fn(file);
- StandardPaths::SetPathValue(_T("?audio"),fn.GetPath());
- }
+ audio_spectrum_renderer->SetResolution(
+ spectrum_width[spectrum_quality],
+ spectrum_distance[spectrum_quality]);
- // Update
- UpdateImage();
- }
- catch (agi::UserCancelException const&) {
- return;
- }
- catch (agi::FileNotFoundError const& e) {
- config::mru->Remove("Audio", STD_STR(file));
- wxMessageBox(lagi_wxString(e.GetMessage()), L"Error loading audio",wxICON_ERROR | wxOK);
- }
- catch (AudioOpenError const& e) {
- wxMessageBox(lagi_wxString(e.GetMessage()), L"Error loading audio",wxICON_ERROR | wxOK);
- }
- catch (const wxChar *e) {
- if (player) { delete player; player = 0; }
- if (provider) { delete provider; provider = 0; }
- wxLogError(e);
- }
- catch (wxString &err) {
- if (player) { delete player; player = 0; }
- if (provider) { delete provider; provider = 0; }
- wxMessageBox(err,_T("Error loading audio"),wxICON_ERROR | wxOK);
- }
- catch (...) {
- if (player) { delete player; player = 0; }
- if (provider) { delete provider; provider = 0; }
- wxLogError(_T("Unknown error loading audio"));
- }
- }
-
- if (!loaded) return;
+ if (OPT_GET("Audio/Spectrum")->GetBool())
+ audio_renderer->SetRenderer(audio_spectrum_renderer);
+ else
+ audio_renderer->SetRenderer(audio_waveform_renderer);
- assert(loaded == (provider != NULL));
+ audio_renderer->Invalidate();
- // Set default selection
- AssDialogue *dlg = grid->GetActiveLine();
- SetDialogue(grid,dlg,grid->GetDialogueIndex(dlg));
+ Refresh();
}
-/// @brief Load from video
-void AudioDisplay::SetFromVideo() {
- if (VideoContext::Get()->IsLoaded()) {
- wxString extension = VideoContext::Get()->videoName.Right(4);
- extension.LowerCase();
- if (extension != _T(".d2v")) SetFile(VideoContext::Get()->videoName);
- }
-}
-/// @brief Reload audio
-void AudioDisplay::Reload() {
- if (provider) SetFile(provider->GetFilename());
-}
-
-/// @brief Update scrollbar
-/// @return
-void AudioDisplay::UpdateScrollbar() {
- if (!provider) return;
- int page = w/12;
- int len = provider->GetNumSamples() / samples / 12;
- Position = PositionSample / samples;
- ScrollBar->SetScrollbar(Position/12,page,len,int(page*0.7),true);
-}
-
-/// @brief Gets the sample number at the x coordinate
-/// @param x
-/// @return
-int64_t AudioDisplay::GetSampleAtX(int x) {
- return (x+Position)*samples;
-}
-
-/// @brief Gets the x coordinate corresponding to sample
-/// @param n
-/// @return
-int AudioDisplay::GetXAtSample(int64_t n) {
- return samples ? (n/samples)-Position : 0;
-}
-
-/// @brief Get MS from X
-/// @param x
-/// @return
-int AudioDisplay::GetMSAtX(int64_t x) {
- return (PositionSample+(x*samples)) * 1000 / provider->GetSampleRate();
-}
-
-/// @brief Get X from MS
-/// @param ms
-/// @return
-int AudioDisplay::GetXAtMS(int64_t ms) {
- return ((ms * provider->GetSampleRate() / 1000)-PositionSample)/samples;
-}
-
-/// @brief Get MS At sample
-/// @param x
-/// @return
-int AudioDisplay::GetMSAtSample(int64_t x) {
- return x * 1000 / provider->GetSampleRate();
-}
-
-/// @brief Get Sample at MS
-/// @param ms
-/// @return
-int64_t AudioDisplay::GetSampleAtMS(int64_t ms) {
- return ms * provider->GetSampleRate() / 1000;
-}
-
-/// @brief Play
-/// @param start
-/// @param end
-/// @return
-void AudioDisplay::Play(int start,int end) {
- Stop();
-
- // Check provider
- if (!provider) {
- return;
- }
-
- // Set defaults
- playingToEnd = end < 0;
- int64_t num_samples = provider->GetNumSamples();
- start = GetSampleAtMS(start);
- if (end != -1) end = GetSampleAtMS(end);
- else end = num_samples-1;
-
- // Sanity checking
- if (start < 0) start = 0;
- if (start >= num_samples) start = num_samples-1;
- if (end >= num_samples) end = num_samples-1;
- if (end < start) end = start;
-
- // Redraw the image to avoid any junk left over from mouse movements etc
- // See issue #598
- UpdateImage(true);
-
- // Call play
- player->Play(start,end-start);
-}
-
-/// @brief Stop
-void AudioDisplay::Stop() {
- if (VideoContext::Get()->IsPlaying()) VideoContext::Get()->Stop();
- if (player) player->Stop();
-}
-
-/// @brief Get samples of dialogue
-/// @param start
-/// @param end
-/// @return
-void AudioDisplay::GetTimesDialogue(int &start,int &end) {
- if (!dialogue) {
- start = 0;
- end = 0;
- return;
- }
-
- start = dialogue->Start.GetMS();
- end = dialogue->End.GetMS();
-}
-
-/// @brief Get samples of selection
-/// @param start
-/// @param end
-/// @return
-void AudioDisplay::GetTimesSelection(int &start,int &end) {
- start = 0;
- end = 0;
- if (!dialogue) return;
-
- try {
- if (karaoke->enabled) {
- int pos = karaoke->syllables.at(karaoke->curSyllable).start_time;
- int len = karaoke->syllables.at(karaoke->curSyllable).duration;
- start = curStartMS+pos*10;
- end = curStartMS+pos*10+len*10;
- }
- else {
- start = curStartMS;
- end = curEndMS;
- }
- }
- catch (...) {}
-}
-
-/// @brief Set the current selection
-/// @param start
-/// @param end
-void AudioDisplay::SetSelection(int start, int end) {
- curStartMS = start;
- curEndMS = end;
- Update();
-}
-
-/// @brief Set dialogue
-/// @param _grid
-/// @param diag
-/// @param n
-/// @return
-void AudioDisplay::SetDialogue(SubtitlesGrid *,AssDialogue *diag,int n) {
- // Set variables
- line_n = n;
- dialogue = diag;
-
- // Set flags
- diagUpdated = false;
- NeedCommit = false;
-
- // Set times
- if (dialogue && OPT_GET("Audio/Grab Times on Select")->GetBool()) {
- int s = dialogue->Start.GetMS();
- int e = dialogue->End.GetMS();
-
- // Never do it for 0:00:00.00->0:00:00.00 lines
- if (s != 0 || e != 0) {
- curStartMS = s;
- curEndMS = e;
- }
- }
-
- // Read karaoke data
- if (dialogue && karaoke->enabled) {
- NeedCommit = karaoke->LoadFromDialogue(dialogue);
-
- // Reset karaoke pos
- if (karaoke->curSyllable == -1) karaoke->SetSyllable((int)karaoke->syllables.size()-1);
- else karaoke->SetSyllable(0);
- }
-
- // Update
- Update();
-}
-
-/// @brief Commit changes
-/// @param nextLine
-/// @return
-void AudioDisplay::CommitChanges (bool nextLine) {
- // Loaded?
- if (!loaded) return;
- commitListener.Block();
-
- // Check validity
- bool timeNeedsCommit = grid->GetDialogue(line_n)->Start.GetMS() != curStartMS || grid->GetDialogue(line_n)->End.GetMS() != curEndMS;
- NeedCommit = timeNeedsCommit;
- bool wasKaraSplitting = false;
- bool validCommit = true;
- if (!karaoke->enabled && !karaoke->splitting) {
- if (!NeedCommit || curEndMS < curStartMS) validCommit = false;
- }
-
- // Update karaoke
- int karaSelStart = 0, karaSelEnd = -1;
- if (karaoke->enabled) {
- wasKaraSplitting = karaoke->splitting;
- karaoke->Commit();
- // Get karaoke selection
- karaSelStart = karaoke->syllables.size();
- for (size_t k = 0; k < karaoke->syllables.size(); ++k) {
- if (karaoke->syllables[k].selected) {
- if ((signed)k < karaSelStart) karaSelStart = k;
- if ((signed)k > karaSelEnd) karaSelEnd = k;
- }
- }
- }
-
- // Get selected rows
- wxArrayInt sel = grid->GetSelection();
-
- // Commit ok?
- if (validCommit) {
- // Reset flags
- diagUpdated = false;
- NeedCommit = false;
-
- // Update dialogues
- blockUpdate = true;
- AssDialogue *curDiag;
- for (size_t i=0;iIsInSelection(line_n)) curDiag = grid->GetDialogue(sel[i]);
- else curDiag = grid->GetDialogue(line_n);
- if (timeNeedsCommit) {
- curDiag->Start.SetMS(curStartMS);
- curDiag->End.SetMS(curEndMS);
- }
- if (!karaoke->enabled) {
- // If user was editing karaoke stuff, that should take precedence of manual changes in the editbox,
- // so only update from editbox when not in kara mode
- curDiag->Text = grid->editBox->TextEdit->GetText();
- }
- if (!grid->IsInSelection(line_n)) break;
- }
-
- grid->ass->Commit(_T(""), karaoke->enabled ? AssFile::COMMIT_TEXT : AssFile::COMMIT_TIMES);
- karaoke->SetSelection(karaSelStart, karaSelEnd);
- blockUpdate = false;
- }
-
- // Next line (ugh what a condition, can this be simplified?)
- if (nextLine && !karaoke->enabled && OPT_GET("Audio/Next Line on Commit")->GetBool() && !wasKaraSplitting) {
- // Insert a line if it doesn't exist
- int nrows = grid->GetRows();
- if (nrows == line_n + 1) {
- AssDialogue *def = new AssDialogue;
- def->Start = grid->GetDialogue(line_n)->End;
- def->End = grid->GetDialogue(line_n)->End;
- def->End.SetMS(def->End.GetMS()+OPT_GET("Timing/Default Duration")->GetInt());
- def->Style = grid->GetDialogue(line_n)->Style;
- grid->InsertLine(def,line_n,true);
- }
- int endMs = curEndMS;
-
- grid->NextLine();
- curStartMS = grid->GetActiveLine()->Start.GetMS();
- curEndMS = grid->GetActiveLine()->End.GetMS();
- if (curStartMS == 0 && curEndMS == 0) {
- curStartMS = endMs;
- curEndMS = endMs + OPT_GET("Timing/Default Duration")->GetInt();
- }
- }
-
- commitListener.Unblock();
- Update();
-}
-
-/// @brief Add lead
-/// @param in
-/// @param out
-void AudioDisplay::AddLead(bool in,bool out) {
- // Lead in
- if (in) {
- curStartMS -= OPT_GET("Audio/Lead/IN")->GetInt();
- if (curStartMS < 0) curStartMS = 0;
- }
-
- // Lead out
- if (out) {
- curEndMS += OPT_GET("Audio/Lead/OUT")->GetInt();
- }
-
- // Set changes
- NeedCommit = true;
- if (OPT_GET("Audio/Auto/Commit")->GetBool()) CommitChanges();
- Update();
-}
-
-///////////////
-// Event table
BEGIN_EVENT_TABLE(AudioDisplay, wxWindow)
EVT_MOUSE_EVENTS(AudioDisplay::OnMouseEvent)
EVT_PAINT(AudioDisplay::OnPaint)
EVT_SIZE(AudioDisplay::OnSize)
- EVT_TIMER(Audio_Update_Timer,AudioDisplay::OnUpdateTimer)
- EVT_KEY_DOWN(AudioDisplay::OnKeyDown)
- EVT_SET_FOCUS(AudioDisplay::OnGetFocus)
- EVT_KILL_FOCUS(AudioDisplay::OnLoseFocus)
+ EVT_SET_FOCUS(AudioDisplay::OnFocus)
+ EVT_KILL_FOCUS(AudioDisplay::OnFocus)
END_EVENT_TABLE()
-/// @brief Paint
-/// @param event
-/// @return
-void AudioDisplay::OnPaint(wxPaintEvent& event) {
- if (w == 0 || h == 0) return;
- DoUpdateImage();
- wxPaintDC dc(this);
- if (origImage) dc.DrawBitmap(*origImage,0,0);
+struct RedrawSubregion {
+ int x1, x2;
+ bool selected;
+ RedrawSubregion(int x1, int x2, bool selected) : x1(x1), x2(x2), selected(selected) { }
+};
+
+void AudioDisplay::OnPaint(wxPaintEvent& event)
+{
+ wxAutoBufferedPaintDC dc(this);
+
+ if (!provider)
+ {
+ dc.SetBackground(*wxBLACK_BRUSH);
+ dc.Clear();
+ return;
+ }
+
+ int client_width, client_height;
+ GetClientSize(&client_width, &client_height);
+
+ wxRect audio_bounds(0, audio_top, client_width, audio_height);
+ const wxRect &scrollbar_bounds = scrollbar->GetBounds();
+ const wxRect &timeline_bounds = timeline->GetBounds();
+ bool redraw_scrollbar = false;
+ bool redraw_timeline = false;
+
+ /// @todo Get rendering style ranges from timing controller instead
+ AudioController::SampleRange sel_samples(controller->GetPrimaryPlaybackRange());
+ int selection_start = AbsoluteXFromSamples(sel_samples.begin());
+ int selection_end = AbsoluteXFromSamples(sel_samples.end());
+
+ wxRegionIterator region(GetUpdateRegion());
+ wxPoint client_org = GetClientAreaOrigin();
+ while (region)
+ {
+ wxRect updrect = region.GetRect();
+ // Work around wxMac issue, client border offsets update rectangles but does
+ // not affect drawing coordinates.
+ updrect.x += client_org.x; updrect.y += client_org.y;
+
+ redraw_scrollbar |= scrollbar_bounds.Intersects(updrect);
+ redraw_timeline |= timeline_bounds.Intersects(updrect);
+
+ if (audio_bounds.Intersects(updrect))
+ {
+ int p1, p2, p3, p4;
+ // p1 -> p2 = before selection
+ // p2 -> p3 = in selection
+ // p3 -> p4 = after selection
+ p1 = scroll_left + updrect.x;
+ p2 = selection_start;
+ p3 = selection_end;
+ p4 = p1 + updrect.width;
+
+ std::vector subregions;
+
+ if (p1 < p2)
+ subregions.push_back(RedrawSubregion(p1, std::min(p2, p4), false));
+ if (p4 > p2 && p1 < p3)
+ subregions.push_back(RedrawSubregion(std::max(p1, p2), std::min(p3, p4), true));
+ if (p4 > p3)
+ subregions.push_back(RedrawSubregion(std::max(p1, p3), p4, false));
+
+ int x = updrect.x;
+ for (std::vector::iterator sr = subregions.begin(); sr != subregions.end(); ++sr)
+ {
+ audio_renderer->Render(dc, wxPoint(x, audio_top), sr->x1, sr->x2 - sr->x1, sr->selected);
+ x += sr->x2 - sr->x1;
+ }
+
+ // Draw markers on top of it all
+ AudioMarkerVector markers;
+ const int foot_size = 6;
+ AudioController::SampleRange updrectsamples(
+ SamplesFromRelativeX(updrect.x - foot_size),
+ SamplesFromRelativeX(updrect.x + updrect.width + foot_size));
+ controller->GetMarkers(updrectsamples, markers);
+ if (controller->GetTimingController())
+ controller->GetTimingController()->GetMarkers(updrectsamples, markers);
+ wxDCPenChanger pen_retainer(dc, wxPen());
+ wxDCBrushChanger brush_retainer(dc, wxBrush());
+ for (AudioMarkerVector::iterator marker_i = markers.begin(); marker_i != markers.end(); ++marker_i)
+ {
+ const AudioMarker *marker = *marker_i;
+ dc.SetPen(marker->GetStyle());
+ int marker_x = RelativeXFromSamples(marker->GetPosition());
+ dc.DrawLine(marker_x, audio_top, marker_x, audio_top+audio_height);
+ dc.SetBrush(wxBrush(marker->GetStyle().GetColour()));
+ dc.SetPen(*wxTRANSPARENT_PEN);
+ if (marker->GetFeet() & AudioMarker::Feet_Left)
+ {
+ wxPoint foot_top[3] = { wxPoint(-foot_size, 0), wxPoint(0, 0), wxPoint(0, foot_size) };
+ wxPoint foot_bot[3] = { wxPoint(-foot_size, 0), wxPoint(0, -foot_size), wxPoint(0, 0) };
+ dc.DrawPolygon(3, foot_top, marker_x, audio_top);
+ dc.DrawPolygon(3, foot_bot, marker_x, audio_top+audio_height);
+ }
+ if (marker->GetFeet() & AudioMarker::Feet_Right)
+ {
+ wxPoint foot_top[3] = { wxPoint(foot_size, 0), wxPoint(0, 0), wxPoint(0, foot_size) };
+ wxPoint foot_bot[3] = { wxPoint(foot_size, 0), wxPoint(0, -foot_size), wxPoint(0, 0) };
+ dc.DrawPolygon(3, foot_top, marker_x, audio_top);
+ dc.DrawPolygon(3, foot_bot, marker_x, audio_top+audio_height);
+ }
+ }
+ }
+
+ region++;
+ }
+
+ if (track_cursor_pos >= 0)
+ {
+ wxDCPenChanger penchanger(dc, wxPen(*wxWHITE));
+ dc.DrawLine(track_cursor_pos-scroll_left, audio_top, track_cursor_pos-scroll_left, audio_top+audio_height);
+
+ if (!track_cursor_label.IsEmpty())
+ {
+ wxDCFontChanger fc(dc);
+ wxFont font = dc.GetFont();
+ font.SetWeight(wxFONTWEIGHT_BOLD);
+ dc.SetFont(font);
+
+ wxSize label_size(dc.GetTextExtent(track_cursor_label));
+ wxPoint label_pos(track_cursor_pos - scroll_left - label_size.x/2, audio_top + 2);
+ if (label_pos.x < 2) label_pos.x = 2;
+ if (label_pos.x + label_size.x >= client_width - 2) label_pos.x = client_width - label_size.x - 2;
+
+ int old_bg_mode = dc.GetBackgroundMode();
+ dc.SetBackgroundMode(wxTRANSPARENT);
+ dc.SetTextForeground(wxColour(64, 64, 64));
+ dc.DrawText(track_cursor_label, label_pos.x+1, label_pos.y+1);
+ dc.DrawText(track_cursor_label, label_pos.x+1, label_pos.y-1);
+ dc.DrawText(track_cursor_label, label_pos.x-1, label_pos.y+1);
+ dc.DrawText(track_cursor_label, label_pos.x-1, label_pos.y-1);
+ dc.SetTextForeground(*wxWHITE);
+ dc.DrawText(track_cursor_label, label_pos.x, label_pos.y);
+ dc.SetBackgroundMode(old_bg_mode);
+
+ label_pos.x -= 2; label_pos.y -= 2;
+ label_size.IncBy(4, 4);
+ // If the rendered text changes size we have to draw it an extra time to make sure the entire thing was drawn
+ bool need_extra_redraw = track_cursor_label_rect.GetSize() != label_size;
+ track_cursor_label_rect.SetPosition(label_pos);
+ track_cursor_label_rect.SetSize(label_size);
+ if (need_extra_redraw)
+ RefreshRect(track_cursor_label_rect);
+ }
+ }
+
+ if (redraw_scrollbar)
+ scrollbar->Paint(dc, HasFocus());
+ if (redraw_timeline)
+ timeline->Paint(dc);
}
-/// @brief Mouse event
-/// @param event
-/// @return
-void AudioDisplay::OnMouseEvent(wxMouseEvent& event) {
- // Get x,y
- int64_t x = event.GetX();
- int64_t y = event.GetY();
- bool karMode = karaoke->enabled;
- bool shiftDown = event.m_shiftDown;
- int timelineHeight = OPT_GET("Audio/Display/Draw/Timeline")->GetBool() ? 20 : 0;
- // Leaving event
- if (event.Leaving()) {
- event.Skip();
- return;
- }
+void AudioDisplay::SetDraggedObject(AudioDisplayInteractionObject *new_obj)
+{
+ // Special case for audio markers being dragged: they use a temporary wrapper object
+ // which must be deleted when it is no longer used.
+ AudioMarkerInteractionObject *dragged_marker = dynamic_cast(dragged_object);
+ if (dragged_marker)
+ delete dragged_marker;
- if (!player || !provider) {
- event.Skip();
- return;
- }
+ dragged_object = new_obj;
- // Is inside?
- bool inside = false;
- bool onScale = false;
- if (x >= 0 && y >= 0 && x < w) {
- if (y < h) {
- inside = true;
-
- // Get focus
- if (wxWindow::FindFocus() != this && OPT_GET("Audio/Auto/Focus")->GetBool()) SetFocus();
- }
- else if (y < h+timelineHeight) onScale = true;
- }
-
- // Buttons
- bool leftIsDown = event.ButtonIsDown(wxMOUSE_BTN_LEFT);
- bool rightIsDown = event.ButtonIsDown(wxMOUSE_BTN_RIGHT);
- bool buttonIsDown = leftIsDown || rightIsDown;
- bool leftClick = event.ButtonDown(wxMOUSE_BTN_LEFT);
- bool rightClick = event.ButtonDown(wxMOUSE_BTN_RIGHT);
- bool middleClick = event.Button(wxMOUSE_BTN_MIDDLE);
- bool buttonClick = leftClick || rightClick;
- bool defCursor = true;
-
- // Click type
- if (buttonClick && !holding) {
- holding = true;
+ if (dragged_object && !HasCapture())
CaptureMouse();
- }
- if (!buttonIsDown && holding) {
- holding = false;
- if (HasCapture()) ReleaseMouse();
- }
+ else if (!dragged_object && HasCapture())
+ ReleaseMouse();
+}
- // Mouse wheel
- if (event.GetWheelRotation() != 0) {
- // Zoom or scroll?
- bool zoom = shiftDown;
+
+void AudioDisplay::SetTrackCursor(int new_pos, bool show_time)
+{
+ if (new_pos != track_cursor_pos)
+ {
+ int old_pos = track_cursor_pos;
+ track_cursor_pos = new_pos;
+
+ RefreshRect(wxRect(old_pos - scroll_left - 0, audio_top, 1, audio_height));
+ RefreshRect(wxRect(new_pos - scroll_left - 0, audio_top, 1, audio_height));
+
+ // Make sure the old label gets cleared away
+ RefreshRect(track_cursor_label_rect);
+
+ if (show_time)
+ {
+ AssTime new_label_time;
+ new_label_time.SetMS(controller->MillisecondsFromSamples(SamplesFromAbsoluteX(track_cursor_pos)));
+ track_cursor_label = new_label_time.GetASSFormated();
+ track_cursor_label_rect.x += new_pos - old_pos;
+ RefreshRect(track_cursor_label_rect);
+ }
+ else
+ {
+ track_cursor_label_rect.SetSize(wxSize(0,0));
+ track_cursor_label.Clear();
+ }
+ }
+}
+
+
+void AudioDisplay::RemoveTrackCursor()
+{
+ SetTrackCursor(-1, false);
+}
+
+
+void AudioDisplay::OnMouseEvent(wxMouseEvent& event)
+{
+ // Check for mouse wheel scrolling
+ if (event.GetWheelRotation() != 0)
+ {
+ // First check if the cursor is inside or outside the display.
+ // If it's outside, we want to send the event to the control it's over instead.
+ /// @todo Factor this into a reusable function
+ {
+ wxWindow *targetwindow = wxFindWindowAtPoint(event.GetPosition());
+ if (targetwindow && targetwindow != this)
+ {
+ targetwindow->GetEventHandler()->ProcessEvent(event);
+ event.Skip(false);
+ return;
+ }
+ }
+
+ bool zoom = event.CmdDown();
if (OPT_GET("Audio/Wheel Default to Zoom")->GetBool()) zoom = !zoom;
- // Zoom
- if (zoom) {
-#ifdef __APPLE__
- // Reverse scroll directions on Apple... ugly hack
- // Otherwise left=right and right=left on systems that support four-way scrolling.
- int step = -event.GetWheelRotation() / event.GetWheelDelta();
-#else
- int step = event.GetWheelRotation() / event.GetWheelDelta();
-#endif
- int value = box->HorizontalZoom->GetValue()+step;
- box->HorizontalZoom->SetValue(value);
- SetSamplesPercent(value,true,float(x)/float(w));
+ if (!zoom)
+ {
+ int amount = -event.GetWheelRotation();
+ // If the user did a horizontal scroll the amount should be inverted
+ // for it to be natural.
+ if (event.GetWheelAxis() == 1) amount = -amount;
+
+ // Reset any accumulated zoom
+ mouse_zoom_accum = 0;
+
+ ScrollBy(amount);
+ }
+ else if (event.GetWheelAxis() == 0)
+ {
+ mouse_zoom_accum += event.GetWheelRotation();
+ int zoom_delta = mouse_zoom_accum / event.GetWheelDelta();
+ mouse_zoom_accum %= event.GetWheelDelta();
+ SetZoomLevel(GetZoomLevel() + zoom_delta);
+ /// @todo This has to update the trackbar in the audio box... maybe move handling mouse zoom to
+ /// the audio box instead to avoid messing with friend classes?
}
- // Scroll
- else {
- int step = -event.GetWheelRotation() * w / 360;
- UpdatePosition(Position+step,false);
- UpdateImage();
- }
+ // Scroll event processed
+ return;
+ }
+
+ // If we have focus, we get mouse move events on Mac even when the mouse is
+ // outside our client rectangle, we don't want those.
+ if (event.Moving() && !GetClientRect().Contains(event.GetPosition()))
+ {
+ event.Skip();
+ return;
}
- // Cursor drawing
- if (player && !player->IsPlaying() && origImage) {
- // Draw bg
- wxClientDC dc(this);
- if (origImage) dc.DrawBitmap(*origImage,0,0);
+ if (event.IsButton())
+ SetFocus();
- if (inside) {
- // Draw cursor
- dc.SetLogicalFunction(wxINVERT);
- dc.DrawLine(x,0,x,h);
-
- // Time
- if (OPT_GET("Audio/Display/Draw/Cursor Time")->GetBool()) {
- // Time string
- AssTime time;
- time.SetMS(GetMSAtX(x));
- wxString text = time.GetASSFormated();
-
- // Calculate metrics
- // FIXME: Hardcoded font name
- wxFont font(10,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD,false,_T("Verdana"));
- dc.SetFont(font);
- int tw,th;
- GetTextExtent(text,&tw,&th,NULL,NULL,&font);
-
- // Set inversion
- bool left = true;
- if (x > w/2) left = false;
-
- // Text coordinates
- int dx;
- dx = x - tw/2;
- if (dx < 4) dx = 4;
- int max = w - tw - 4;
- if (dx > max) dx = max;
- int dy = 4;
- if (karMode) dy += th;
-
- // Draw text
- dc.SetTextForeground(wxColour(64,64,64));
- dc.DrawText(text,dx+1,dy-1);
- dc.DrawText(text,dx+1,dy+1);
- dc.DrawText(text,dx-1,dy-1);
- dc.DrawText(text,dx-1,dy+1);
- dc.SetTextForeground(wxColour(255,255,255));
- dc.DrawText(text,dx,dy);
- }
- }
- }
-
- // Scale dragging
- if ((hold == 0 && onScale) || draggingScale) {
- if (event.ButtonDown(wxMOUSE_BTN_LEFT)) {
- lastDragX = x;
- draggingScale = true;
- }
- else if (holding) {
- int delta = lastDragX - x;
- lastDragX = x;
- UpdatePosition(Position + delta);
- UpdateImage();
- Refresh(false);
+ // Handle any ongoing drag
+ if (dragged_object && HasCapture())
+ {
+ if (!dragged_object->OnMouseEvent(event))
+ {
+ SetDraggedObject(0);
SetCursor(wxNullCursor);
+ }
+ return;
+ }
+ else
+ {
+ // Something is wrong, we might have lost capture somehow.
+ // Fix state and pretend it didn't happen.
+ SetDraggedObject(0);
+ SetCursor(wxNullCursor);
+ }
+
+ wxPoint mousepos = event.GetPosition();
+
+ // Check for scrollbar action
+ if (scrollbar->GetBounds().Contains(mousepos))
+ {
+ if (!controller->IsPlaying())
+ RemoveTrackCursor();
+ if (scrollbar->OnMouseEvent(event))
+ SetDraggedObject(scrollbar);
+ return;
+ }
+
+ // Check for timeline action
+ if (timeline->GetBounds().Contains(mousepos))
+ {
+ SetCursor(wxCursor(wxCURSOR_SIZEWE));
+ if (!controller->IsPlaying())
+ RemoveTrackCursor();
+ if (timeline->OnMouseEvent(event))
+ SetDraggedObject(timeline);
+ return;
+ }
+
+ AudioTimingController *timing = controller->GetTimingController();
+ int drag_sensitivity = pixel_samples*3; /// @todo Make this depend on configuration
+
+ // Not scrollbar, not timeline, no button action
+ if (event.Moving())
+ {
+ if (timing)
+ {
+ int64_t samplepos = SamplesFromRelativeX(mousepos.x);
+
+ if (timing->IsNearbyMarker(samplepos, drag_sensitivity))
+ SetCursor(wxCursor(wxCURSOR_SIZEWE));
+ else
+ SetCursor(wxNullCursor);
+ }
+
+ if (!controller->IsPlaying())
+ SetTrackCursor(scroll_left + mousepos.x, true);
+ }
+
+ if (event.Leaving() && !controller->IsPlaying())
+ {
+ RemoveTrackCursor();
+ }
+
+ if (event.LeftDown() && timing)
+ {
+ int64_t samplepos = SamplesFromRelativeX(mousepos.x);
+ AudioMarker *marker = timing->OnLeftClick(samplepos, drag_sensitivity);
+
+ if (marker)
+ {
+ RemoveTrackCursor();
+ SetDraggedObject(new AudioMarkerInteractionObject(marker, timing, this, controller, wxMOUSE_BTN_LEFT));
return;
}
- else draggingScale = false;
}
- // Outside
- if (!inside && hold == 0) return;
+ if (event.RightDown() && timing)
+ {
+ int64_t samplepos = SamplesFromRelativeX(mousepos.x);
+ AudioMarker *marker = timing->OnRightClick(samplepos, drag_sensitivity);
- // Left click
- if (leftClick) {
- SetFocus();
- }
-
- // Right click
- if (rightClick) {
- SetFocus();
- if (karaoke->enabled) {
- int syl = GetSyllableAtX(x);
- if (syl != -1) {
- int start = karaoke->syllables.at(syl).start_time * 10 + dialogue->Start.GetMS();
- int count = karaoke->syllables.at(syl).duration * 10;
- Play(start, start+count);
- }
+ if (marker)
+ {
+ RemoveTrackCursor();
+ SetDraggedObject(new AudioMarkerInteractionObject(marker, timing, this, controller, wxMOUSE_BTN_RIGHT));
+ return;
}
}
- // Middle click
- if (middleClick) {
- SetFocus();
- if (VideoContext::Get()->IsLoaded()) {
- VideoContext::Get()->JumpToTime(GetMSAtX(x));
- }
- }
-
- // Timing
- if (hasSel) {
- bool updated = false;
-
- // Grab start/end
- if (hold == 0) {
- bool gotGrab = false;
- bool karTime = karMode && !
-#ifdef __APPLE__
- event.CmdDown();
-#else
- event.ControlDown();
-#endif
-
- // Line timing mode
- if (!karTime) {
- // Grab start
- if (abs64 (x - selStart) < 6 && OPT_GET("Audio/Display/Dragging Times")->GetBool()==false) {
- wxCursor cursor(wxCURSOR_SIZEWE);
- SetCursor(cursor);
- defCursor = false;
- if (buttonClick) {
- hold = 1;
- gotGrab = true;
- }
- }
-
- // Grab end
- else if (abs64 (x - selEnd) < 6 && OPT_GET("Audio/Display/Dragging Times")->GetBool()==false) {
- wxCursor cursor(wxCURSOR_SIZEWE);
- SetCursor(cursor);
- defCursor = false;
- if (buttonClick) {
- hold = 2;
- gotGrab = true;
- }
- }
-
- // Dragging nothing, time from scratch
- else {
- if (buttonClick) {
- if (leftClick) hold = 3;
- else hold = 2;
- lastX = x;
- gotGrab = true;
- }
- }
- }
-
- // Karaoke mode
- else {
- // Look for a syllable
- int64_t pos,len,curpos;
- AudioKaraokeSyllable *curSyl;
- size_t karn = karaoke->syllables.size();
- for (size_t i=0;isyllables.at(i);
- len = curSyl->duration*10;
- curpos = curSyl->start_time*10;
- if (len != -1) {
- pos = GetXAtMS(curStartMS+len+curpos);
-
- // Grabbing syllable boundary
- if (abs64 (x - pos) < 7) {
- wxCursor cursor(wxCURSOR_SIZEWE);
- SetCursor(cursor);
- defCursor = false;
- if (event.LeftIsDown()) {
- hold = 4;
- holdSyl = (int)i;
- gotGrab = true;
- }
- break;
- }
- }
- }
-
- // No syllable found, select if possible
- if (hold == 0 && leftClick) {
- int syl = GetSyllableAtX(x);
- if (syl != -1) {
- karaoke->SetSyllable(syl);
- updated = true;
- }
- }
- }
- }
-
- // Drag start/end
- if (hold != 0) {
- // Dragging
- if (buttonIsDown) {
- // Drag from nothing or straight timing
- if (hold == 3 && buttonIsDown) {
- if (!karMode) {
- if (leftIsDown) curStartMS = GetMSAtX(x);
- else curEndMS = GetMSAtX(x);
- updated = true;
- diagUpdated = true;
-
- if (leftIsDown && abs((long)(x-lastX)) > OPT_GET("Audio/Start Drag Sensitivity")->GetInt()) {
- selStart = lastX;
- selEnd = x;
- curStartMS = GetBoundarySnap(GetMSAtX(lastX),10,event.ShiftDown(),true);
- curEndMS = GetMSAtX(x);
- hold = 2;
- }
- }
- }
-
- // Drag start
- if (hold == 1 && buttonIsDown) {
- // Set new value
- if (x != selStart) {
- int snapped = GetBoundarySnap(GetMSAtX(x),10,event.ShiftDown(),true);
- selStart = GetXAtMS(snapped);
- if (selStart > selEnd) {
- int temp = selStart;
- selStart = selEnd;
- selEnd = temp;
- hold = 2;
- curEndMS = snapped;
- snapped = GetMSAtX(selStart);
- }
- curStartMS = snapped;
- updated = true;
- diagUpdated = true;
- }
- }
-
- // Drag end
- if (hold == 2 && buttonIsDown) {
- // Set new value
- if (x != selEnd) {
- int snapped = GetBoundarySnap(GetMSAtX(x),10,event.ShiftDown(),false);
- selEnd = GetXAtMS(snapped);
- //selEnd = GetBoundarySnap(x,event.ShiftDown()?0:10,false);
- if (selStart > selEnd) {
- int temp = selStart;
- selStart = selEnd;
- selEnd = temp;
- hold = 1;
- curStartMS = snapped;
- snapped = GetMSAtX(selEnd);
- }
- curEndMS = snapped;
- updated = true;
- diagUpdated = true;
- }
- }
-
- // Drag karaoke
- if (hold == 4 && leftIsDown) {
- // Set new value
- int curpos,len,pos,nkar;
- AudioKaraokeSyllable *curSyl=NULL,*nextSyl=NULL;
- curSyl = &karaoke->syllables.at(holdSyl);
- nkar = (int)karaoke->syllables.size();
- if (holdSyl < nkar-1) {
- nextSyl = &karaoke->syllables.at(holdSyl+1);
- }
- curpos = curSyl->start_time;
- len = curSyl->duration;
- pos = GetXAtMS(curStartMS+(len+curpos)*10);
- if (x != pos) {
- // Calculate delta in centiseconds
- int delta = ((int64_t)(x-pos)*samples*100)/provider->GetSampleRate();
-
- // Apply delta
- int deltaMode = 0;
- if (shiftDown) deltaMode = 1;
- // else if (ctrlDown) deltaMode = 2;
- bool result = karaoke->SyllableDelta(holdSyl,delta,deltaMode);
- if (result) {
- updated = true;
- diagUpdated = true;
- }
- }
- }
- }
-
- // Release
- else {
- // Commit changes
- if (diagUpdated) {
- diagUpdated = false;
- NeedCommit = true;
- if (curStartMS <= curEndMS) {
- if (OPT_GET("Audio/Auto/Commit")->GetBool()) CommitChanges();
- }
-
- else UpdateImage(true);
- }
-
- // Update stuff
- SetCursor(wxNullCursor);
- hold = 0;
- }
- }
-
- // Update stuff
- if (updated) {
- if (diagUpdated) NeedCommit = true;
- if (karaoke->enabled && !playingToEnd) {
- AudioKaraokeSyllable &syl = karaoke->syllables[karaoke->curSyllable];
- player->SetEndPosition(GetSampleAtMS(curStartMS + (syl.start_time+syl.duration)*10));
- } else if (!playingToEnd) {
- player->SetEndPosition(GetSampleAtX(selEnd));
- }
- if (hold != 0) {
- wxCursor cursor(wxCURSOR_SIZEWE);
- SetCursor(cursor);
- }
- UpdateImage(true);
- }
- }
-
- // Not holding
- else {
- hold = 0;
- }
-
- // Restore cursor
- if (defCursor) SetCursor(wxNullCursor);
+ /// @todo Handle middle click to seek video
}
-/// @brief Get snap to boundary
-/// @param ms
-/// @param rangeX
-/// @param shiftHeld
-/// @param start
-/// @return
-int AudioDisplay::GetBoundarySnap(int ms,int rangeX,bool shiftHeld,bool start) {
- // Range?
- if (rangeX <= 0) return ms;
- // Convert range into miliseconds
- int rangeMS = rangeX*samples*1000 / provider->GetSampleRate();
+void AudioDisplay::OnSize(wxSizeEvent &event)
+{
+ // We changed size, update the sub-controls' internal data and redraw
+ wxSize size = GetClientSize();
- // Keyframe boundaries
- wxArrayInt boundaries;
- bool snapKey = OPT_GET("Audio/Display/Snap/Keyframes")->GetBool();
- if (shiftHeld) snapKey = !snapKey;
- if (snapKey && VideoContext::Get()->KeyFramesLoaded() && OPT_GET("Audio/Display/Draw/Keyframes")->GetBool()) {
- int64_t keyMS;
- std::vector keyFrames = VideoContext::Get()->GetKeyFrames();
- int frame;
- for (unsigned int i=0;iTimeAtFrame(frame,start ? agi::vfr::START : agi::vfr::END);
- //if (start) keyX++;
- if (GetXAtMS(keyMS) >= 0 && GetXAtMS(keyMS) < w) boundaries.Add(keyMS);
- }
- }
+ scrollbar->SetDisplaySize(size);
+ timeline->SetDisplaySize(wxSize(size.x, scrollbar->GetBounds().y));
- // Other subtitles' boundaries
- int inactiveType = OPT_GET("Audio/Inactive Lines Display Mode")->GetInt();
- bool snapLines = OPT_GET("Audio/Display/Snap/Other Lines")->GetBool();
- if (shiftHeld) snapLines = !snapLines;
- if (snapLines && (inactiveType == 1 || inactiveType == 2)) {
- AssDialogue *shade;
- int shadeX1,shadeX2;
- int shadeFrom,shadeTo;
+ audio_height = size.GetHeight();
+ audio_height -= scrollbar->GetBounds().GetHeight();
+ audio_height -= timeline->GetHeight();
+ audio_renderer->SetHeight(audio_height);
- // Get range
- if (inactiveType == 1) {
- shadeFrom = this->line_n-1;
- shadeTo = shadeFrom+1;
- }
- else {
- shadeFrom = 0;
- shadeTo = grid->GetRows();
- }
+ audio_top = timeline->GetHeight();
- for (int j=shadeFrom;jGetDialogue(j);
-
- if (shade) {
- // Get coordinates
- shadeX1 = GetXAtMS(shade->Start.GetMS());
- shadeX2 = GetXAtMS(shade->End.GetMS());
- if (shadeX1 >= 0 && shadeX1 < w) boundaries.Add(shade->Start.GetMS());
- if (shadeX2 >= 0 && shadeX2 < w) boundaries.Add(shade->End.GetMS());
- }
- }
- }
-
- // See if ms falls within range of any of them
- int minDist = rangeMS+1;
- int bestMS = ms;
- for (unsigned int i=0;iGetBool() ? 20 : 0;
- // Update image
- UpdateSamples();
- if (samples) {
- UpdatePosition(PositionSample / samples);
- }
- UpdateImage();
-
- // Update scrollbar
- UpdateScrollbar();
+void AudioDisplay::OnFocus(wxFocusEvent &event)
+{
+ // The scrollbar indicates focus so repaint that
+ RefreshRect(scrollbar->GetBounds());
}
-/// @brief Timer event
-/// @param event
-/// @return
-void AudioDisplay::OnUpdateTimer(wxTimerEvent &event) {
- if (!origImage)
- return;
- // Get lock and check if it's OK
- if (player->GetMutex()) {
- wxMutexLocker locker(*player->GetMutex());
- if (!locker.IsOk()) return;
- }
-
- if (!player->IsPlaying()) return;
+void AudioDisplay::OnAudioOpen(AudioProvider *_provider)
+{
+ provider = _provider;
- // Get DCs
- //wxMutexGuiEnter();
- wxClientDC dc(this);
+ audio_renderer->SetAudioProvider(provider);
+ audio_renderer->SetCacheMaxSize(OPT_GET("Audio/Renderer/Spectrum/Memory Max")->GetInt() * 1024 * 1024);
- // Draw cursor
- int curpos = -1;
- if (player->IsPlaying()) {
- int64_t curPos = player->GetCurrentPosition();
- if (curPos > player->GetStartPosition() && curPos < player->GetEndPosition()) {
- // Scroll if needed
- int posX = GetXAtSample(curPos);
- bool fullDraw = false;
- bool centerLock = false;
- bool scrollToCursor = OPT_GET("Audio/Lock Scroll on Cursor")->GetBool();
- if (centerLock) {
- int goTo = MAX(0,curPos - w*samples/2);
- if (goTo >= 0) {
- UpdatePosition(goTo,true);
- UpdateImage();
- fullDraw = true;
- }
- }
- else {
- if (scrollToCursor) {
- if (posX < 80 || posX > w-80) {
- int goTo = MAX(0,curPos - 80*samples);
- if (goTo >= 0) {
- UpdatePosition(goTo,true);
- UpdateImage();
- fullDraw = true;
- }
- }
- }
- }
+ if (provider)
+ timeline->ChangeAudio(provider->GetNumSamples(), provider->GetSampleRate());
- // Draw cursor
- wxMemoryDC src;
- curpos = GetXAtSample(curPos);
- if (curpos >= 0 && curpos < GetClientSize().GetWidth()) {
- dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Audio Display/Play Cursor")->GetColour())));
- src.SelectObject(*origImage);
- if (fullDraw) {
- //dc.Blit(0,0,w,h,&src,0,0);
- dc.DrawLine(curpos,0,curpos,h);
- //dc.Blit(0,0,curpos-10,h,&src,0,0);
- //dc.Blit(curpos+10,0,w-curpos-10,h,&src,curpos+10,0);
- }
- else {
- dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
- dc.DrawLine(curpos,0,curpos,h);
- }
- }
+ SetZoomLevel(zoom_level);
+
+ Refresh();
+}
+
+
+void AudioDisplay::OnAudioClose()
+{
+ OnAudioOpen(0);
+}
+
+
+void AudioDisplay::OnPlaybackPosition(int64_t sample_position)
+{
+ SetTrackCursor(AbsoluteXFromSamples(sample_position), false);
+}
+
+
+void AudioDisplay::OnPlaybackStop()
+{
+ RemoveTrackCursor();
+}
+
+
+void AudioDisplay::OnMarkersMoved()
+{
+ Refresh();
+}
+
+
+void AudioDisplay::OnSelectionChanged()
+{
+ /// @todo Handle rendering style ranges from timing controller instead
+ AudioController::SampleRange sel(controller->GetPrimaryPlaybackRange());
+ scrollbar->SetSelection(AbsoluteXFromSamples(sel.begin()), AbsoluteXFromSamples(sel.length()));
+
+ if (sel.overlaps(old_selection))
+ {
+ // Only redraw the parts of the selection that changed, to avoid flicker
+ int s1 = RelativeXFromSamples(sel.begin());
+ int e1 = RelativeXFromSamples(sel.end());
+ int s2 = RelativeXFromSamples(old_selection.begin());
+ int e2 = RelativeXFromSamples(old_selection.end());
+ if (s1 != s2)
+ {
+ wxRect r(std::min(s1, s2)-10, audio_top, abs(s1-s2)+20, audio_height);
+ RefreshRect(r);
}
- else {
- if (curPos > player->GetEndPosition() + 8192) {
- player->Stop();
- }
- wxMemoryDC src;
- src.SelectObject(*origImage);
- dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
+ if (e1 != e2)
+ {
+ wxRect r(std::min(e1, e2)-10, audio_top, abs(e1-e2)+20, audio_height);
+ RefreshRect(r);
}
}
-
- // Restore background
- else {
- wxMemoryDC src;
- src.SelectObject(*origImage);
- dc.Blit(oldCurPos,0,1,h,&src,oldCurPos,0);
+ else
+ {
+ RefreshRect(wxRect(0, audio_top, GetClientSize().GetX(), audio_height));
}
- oldCurPos = curpos;
+
+ RefreshRect(scrollbar->GetBounds());
+
+ old_selection = sel;
}
-/// @brief Key down
-/// @param event
-void AudioDisplay::OnKeyDown(wxKeyEvent &event) {
- int key = event.GetKeyCode();
-#ifdef __APPLE__
- Hotkeys.SetPressed(key,event.m_metaDown,event.m_altDown,event.m_shiftDown);
-#else
- Hotkeys.SetPressed(key,event.m_controlDown,event.m_altDown,event.m_shiftDown);
-#endif
- // Accept
- if (Hotkeys.IsPressed(_T("Audio Commit"))) {
- CommitChanges(true);
- //ChangeLine(1);
- }
-
- // Accept (SSA's "Grab times")
- if (Hotkeys.IsPressed(_T("Audio Commit Alt"))) {
- CommitChanges(true);
- }
-
- // Accept (stay)
- if (Hotkeys.IsPressed(_T("Audio Commit (Stay)"))) {
- CommitChanges();
- }
-
- // Previous
- if (Hotkeys.IsPressed(_T("Audio Prev Line")) || Hotkeys.IsPressed(_T("Audio Prev Line Alt"))) {
- Prev();
- }
-
- // Next
- if (Hotkeys.IsPressed(_T("Audio Next Line")) || Hotkeys.IsPressed(_T("Audio Next Line Alt"))) {
- Next();
- }
-
- // Play
- if (Hotkeys.IsPressed(_T("Audio Play")) || Hotkeys.IsPressed(_T("Audio Play Alt"))) {
- int start=0,end=0;
- GetTimesSelection(start,end);
- Play(start,end);
- }
-
- // Play/Stop
- if (Hotkeys.IsPressed(_T("Audio Play or Stop"))) {
- if (player->IsPlaying()) Stop();
- else {
- int start=0,end=0;
- GetTimesSelection(start,end);
- Play(start,end);
- }
- }
-
- // Stop
- if (Hotkeys.IsPressed(_T("Audio Stop"))) {
- Stop();
- }
-
- // Increase length
- if (Hotkeys.IsPressed(_T("Audio Karaoke Increase Len"))) {
- if (karaoke->enabled) {
- bool result = karaoke->SyllableDelta(karaoke->curSyllable,1,0);
- if (result) diagUpdated = true;
- }
- }
-
- // Increase length (shift)
- if (Hotkeys.IsPressed(_T("Audio Karaoke Increase Len Shift"))) {
- if (karaoke->enabled) {
- bool result = karaoke->SyllableDelta(karaoke->curSyllable,1,1);
- if (result) diagUpdated = true;
- }
- }
-
- // Decrease length
- if (Hotkeys.IsPressed(_T("Audio Karaoke Decrease Len"))) {
- if (karaoke->enabled) {
- bool result = karaoke->SyllableDelta(karaoke->curSyllable,-1,0);
- if (result) diagUpdated = true;
- }
- }
-
- // Decrease length (shift)
- if (Hotkeys.IsPressed(_T("Audio Karaoke Decrease Len Shift"))) {
- if (karaoke->enabled) {
- bool result = karaoke->SyllableDelta(karaoke->curSyllable,-1,1);
- if (result) diagUpdated = true;
- }
- }
-
- // Move backwards
- if (Hotkeys.IsPressed(_T("Audio Scroll Left"))) {
- UpdatePosition(Position-128,false);
- UpdateImage();
- }
-
- // Move forward
- if (Hotkeys.IsPressed(_T("Audio Scroll Right"))) {
- UpdatePosition(Position+128,false);
- UpdateImage();
- }
-
- // Play first 500 ms
- if (Hotkeys.IsPressed(_T("Audio Play First 500ms"))) {
- int start=0,end=0;
- GetTimesSelection(start,end);
- int e = start+500;
- if (e > end) e = end;
- Play(start,e);
- }
-
- // Play last 500 ms
- if (Hotkeys.IsPressed(_T("Audio Play Last 500ms"))) {
- int start=0,end=0;
- GetTimesSelection(start,end);
- int s = end-500;
- if (s < start) s = start;
- Play(s,end);
- }
-
- // Play 500 ms before
- if (Hotkeys.IsPressed(_T("Audio Play 500ms Before"))) {
- int start=0,end=0;
- GetTimesSelection(start,end);
- Play(start-500,start);
- }
-
- // Play 500 ms after
- if (Hotkeys.IsPressed(_T("Audio Play 500ms After"))) {
- int start=0,end=0;
- GetTimesSelection(start,end);
- Play(end,end+500);
- }
-
- // Play to end of file
- if (Hotkeys.IsPressed(_T("Audio Play To End"))) {
- int start=0,end=0;
- GetTimesSelection(start,end);
- Play(start,-1);
- }
-
- // Play original line
- if (Hotkeys.IsPressed(_T("Audio Play Original Line"))) {
- int start=0,end=0;
- GetTimesDialogue(start,end);
- SetSelection(start, end);
- Play(start,end);
- }
-
- // Lead in
- if (Hotkeys.IsPressed(_T("Audio Add Lead In"))) {
- AddLead(true,false);
- }
-
- // Lead out
- if (Hotkeys.IsPressed(_T("Audio Add Lead Out"))) {
- AddLead(false,true);
- }
-
- // Update
- if (diagUpdated) {
- diagUpdated = false;
- NeedCommit = true;
- if (OPT_GET("Audio/Auto/Commit")->GetBool() && curStartMS <= curEndMS) CommitChanges();
- else UpdateImage(true);
- }
+void AudioDisplay::OnTimingControllerChanged()
+{
+ Refresh();
+ /// @todo Do something more about the new timing controller?
}
-/// @brief Change line
-/// @param delta
-/// @param block
-/// @return
-void AudioDisplay::ChangeLine(int delta, bool block) {
- if (dialogue) {
- // Get next line number and make sure it's within bounds
- int next;
- if (block && grid->IsInSelection(line_n)) next = grid->GetLastSelRow()+delta;
- else next = line_n+delta;
-
- if (next == -1) next = 0;
- if (next == grid->GetRows()) next = grid->GetRows() - 1;
-
- // Set stuff
- NeedCommit = false;
- dialogue = NULL;
- grid->SetActiveLine(grid->GetDialogue(next));
- grid->SelectRow(next);
- grid->MakeCellVisible(next,0,true);
- if (!dialogue) UpdateImage(true);
- else UpdateImage(false);
- line_n = next;
- }
-}
-
-/// @brief Next
-/// @param play
-/// @return
-void AudioDisplay::Next(bool play) {
- // Karaoke
- if (karaoke->enabled) {
- int nextSyl = karaoke->curSyllable+1;
- bool needsUpdate = true;
-
- // Last syllable; jump to next
- if (nextSyl >= (signed int)karaoke->syllables.size()) {
- // Already last?
- if (line_n == grid->GetRows()-1) return;
-
- if (NeedCommit) {
- int result = wxMessageBox(_("Do you want to commit your changes? If you choose No, they will be discarded."),_("Commit?"),wxYES_NO | wxCANCEL | wxICON_QUESTION);
- //int result = wxNO;
- if (result == wxYES) {
- CommitChanges();
- }
- else if (result == wxCANCEL) {
- karaoke->curSyllable = (int)karaoke->syllables.size()-1;
- return;
- }
- }
- nextSyl = 0;
- karaoke->curSyllable = 0;
- ChangeLine(1);
- needsUpdate = false;
- }
-
- // Set syllable
- karaoke->SetSyllable(nextSyl);
- if (needsUpdate) Update();
- int start=0,end=0;
- GetTimesSelection(start,end);
- if (play) Play(start,end);
- }
-
- // Plain mode
- else {
- ChangeLine(1);
- }
-
-}
-
-/// @brief Previous
-/// @param play
-/// @return
-void AudioDisplay::Prev(bool play) {
- // Karaoke
- if (karaoke->enabled) {
- int nextSyl = karaoke->curSyllable-1;
- bool needsUpdate = true;
-
- // First syllable; jump line
- if (nextSyl < 0) {
- // Already first?
- if (line_n == 0) return;
-
- if (NeedCommit) {
- int result = wxMessageBox(_("Do you want to commit your changes? If you choose No, they will be discarded."),_("Commit?"),wxYES_NO | wxCANCEL);
- if (result == wxYES) {
- CommitChanges();
- }
- else if (result == wxCANCEL) {
- karaoke->curSyllable = 0;
- return;
- }
- }
- karaoke->curSyllable = -1;
- ChangeLine(-1);
- needsUpdate = false;
- }
-
- // Set syllable
- karaoke->SetSyllable(nextSyl);
- if (needsUpdate) Update();
- int start=0,end=0;
- GetTimesSelection(start,end);
- if (play) Play(start,end);
- }
-
- // Plain mode
- else {
- ChangeLine(-1);
- }
-
-}
-
-/// @brief Gets syllable at x position
-/// @param x
-/// @return
-int AudioDisplay::GetSyllableAtX(int x) {
- if (!karaoke->enabled) return -1;
- int ms = GetMSAtX(x);
- size_t syllables = karaoke->syllables.size();;
- int sylstart,sylend;
-
- // Find a matching syllable
- for (size_t i=0;isyllables.at(i).start_time*10 + curStartMS;
- sylend = karaoke->syllables.at(i).duration*10 + sylstart;
- if (ms >= sylstart && ms < sylend) {
- return (int)i;
- }
- }
- return -1;
-}
-
-/// @brief Focus events
-/// @param event
-void AudioDisplay::OnGetFocus(wxFocusEvent &event) {
- if (!hasFocus) {
- hasFocus = true;
- UpdateImage(true);
- }
-}
-
-/// @brief DOCME
-/// @param event
-void AudioDisplay::OnLoseFocus(wxFocusEvent &event) {
- if (hasFocus && loaded) {
- hasFocus = false;
- UpdateImage(true);
- Refresh(false);
- }
-}
-void AudioDisplay::OnActiveLineChanged(AssDialogue *new_line) {
- SetDialogue(grid,new_line,grid->GetDialogueIndex(new_line));
-}
-void AudioDisplay::OnSelectedSetChanged(const Selection &lines_added, const Selection &lines_removed) {
-}
-void AudioDisplay::OnCommit(int type) {
- AssDialogue *line = grid->GetActiveLine();
- SetDialogue(grid,line,grid->GetDialogueIndex(line));
-}
diff --git a/aegisub/src/audio_display.h b/aegisub/src/audio_display.h
index 6758f74b8..ef9769cce 100644
--- a/aegisub/src/audio_display.h
+++ b/aegisub/src/audio_display.h
@@ -1,4 +1,5 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro
+// Copyright (c) 2009-2010, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -45,247 +46,285 @@
#include
#endif
-#include
-
-#include "audio_renderer_spectrum.h"
#include "selection_controller.h"
-class AudioBox;
+#ifndef AGI_AUDIO_CONTROLLER_INCLUDED
+#error You must include "audio_controller.h" before "audio_display.h"
+#endif
+
+
+class AudioRenderer;
+class AudioSpectrumRenderer;
+class AudioWaveformRenderer;
class AudioKaraoke;
-class AudioPlayer;
class AudioProvider;
-class AssDialogue;
-class FrameMain;
+class AudioPlayer;
class SubtitlesGrid;
class VideoProvider;
-/// DOCME
+class AudioBox;
+class SubtitlesGrid;
+class AssDialogue;
+class wxScrollBar;
+
+// Helper classes used in implementation of the audio display
+class AudioDisplayScrollbar;
+class AudioDisplayTimeline;
+class AudioDisplaySelection;
+
+
+
+/// @class AudioDisplayInteractionObject
+/// @brief Interface for objects on the audio display that can respond to mouse events
+class AudioDisplayInteractionObject {
+public:
+ /// @brief The user is interacting with the object using the mouse
+ /// @param event Mouse event data
+ /// @return True to take mouse capture, false to release mouse capture
+ ///
+ /// Assuming no object has the mouse capture, the audio display uses other methods
+ /// in the object implementing this interface to deterine whether a mouse event
+ /// should go to the object. If the mouse event goes to the object, this method
+ /// is called.
+ ///
+ /// If this method returns true, the audio display takes the mouse capture and
+ /// stores a pointer to the AudioDisplayInteractionObject interface for the object
+ /// and redirects the next mouse event to that object.
+ ///
+ /// If the object that has the mouse capture returns false from this method, the
+ /// capture is released and regular processing is done for the next event.
+ ///
+ /// If the object does not have mouse capture and returns false from this method,
+ /// no capture is taken or released and regular processing is done for the next
+ /// mouse event.
+ virtual bool OnMouseEvent(wxMouseEvent &event) = 0;
+
+ /// @brief Destructor
+ ///
+ /// Empty virtual destructor for the cases that need it.
+ virtual ~AudioDisplayInteractionObject() { }
+};
+
+
/// @class AudioDisplay
-/// @brief DOCME
+/// @brief Primary view/UI for interaction with audio timing
///
-/// DOCME
-class AudioDisplay: public wxWindow, private SelectionListener {
- friend class FrameMain;
+/// The audio display is the common view that allows the user to interact with the active
+/// timing controller. The audio display also renders audio according to the audio controller
+/// and the timing controller, using an audio renderer instance.
+class AudioDisplay: public wxWindow, private AudioControllerAudioEventListener, private AudioControllerTimingEventListener {
private:
- /// DOCME
- SubtitlesGrid *grid;
+ /// The audio renderer manager
+ AudioRenderer *audio_renderer;
- /// DOCME
- int line_n;
+ /// The renderer for audio spectrums
+ AudioSpectrumRenderer *audio_spectrum_renderer;
- /// DOCME
- AssDialogue *dialogue;
+ /// The renderer for audio waveforms
+ AudioWaveformRenderer *audio_waveform_renderer;
- /// DOCME
- AudioSpectrum *spectrumRenderer;
+ /// Our current audio provider
+ AudioProvider *provider;
- /// DOCME
- wxBitmap *origImage;
+ /// The controller managing us
+ AudioController *controller;
- /// DOCME
- wxBitmap *spectrumDisplay;
- /// DOCME
- wxBitmap *spectrumDisplaySelected;
+ /// Scrollbar helper object
+ AudioDisplayScrollbar *scrollbar;
- /// DOCME
- int64_t PositionSample;
+ /// Timeline helper object
+ AudioDisplayTimeline *timeline;
- /// DOCME
- float scale;
- /// DOCME
- int samples;
+ /// Current object on display being dragged, if any
+ AudioDisplayInteractionObject *dragged_object;
+ /// Change the dragged object and update mouse capture
+ void SetDraggedObject(AudioDisplayInteractionObject *new_obj);
- /// DOCME
- int64_t Position;
- /// DOCME
- int samplesPercent;
+ /// Leftmost pixel in the vitual audio image being displayed
+ int scroll_left;
- /// DOCME
- int oldCurPos;
+ /// Total width of the audio in pixels
+ int pixel_audio_width;
- /// DOCME
- bool hasFocus;
+ /// Horizontal zoom measured in audio samples per pixel
+ int pixel_samples;
- /// DOCME
- bool blockUpdate;
+ /// Amplitude scaling ("vertical zoom") as a factor, 1.0 is neutral
+ float scale_amplitude;
- /// DOCME
- bool dontReadTimes;
+ /// Top of the main audio area in pixels
+ int audio_top;
- /// DOCME
- bool playingToEnd;
+ /// Height of main audio area in pixels
+ int audio_height;
- /// DOCME
- bool needImageUpdate;
- /// DOCME
- bool needImageUpdateWeak;
+ /// Zoom level given as a number, see SetZoomLevel for details
+ int zoom_level;
+ // Mouse wheel zoom accumulator
+ int mouse_zoom_accum;
- /// DOCME
- bool hasSel;
- /// DOCME
- bool hasKaraoke;
+ /// Absolute pixel position of the tracking cursor (mouse or playback)
+ int track_cursor_pos;
+ /// Label to show by track cursor
+ wxString track_cursor_label;
+ /// Bounding rectangle last drawn track cursor label
+ wxRect track_cursor_label_rect;
+ /// @brief Move the tracking cursor
+ /// @param new_pos New absolute pixel position of the tracking cursor
+ /// @param show_time Display timestamp by the tracking cursor?
+ void SetTrackCursor(int new_pos, bool show_time);
+ /// @brief Remove the tracking cursor from the display
+ void RemoveTrackCursor();
- /// DOCME
- bool diagUpdated;
- /// DOCME
- bool holding;
+ /// Previous audio selection for optimising redraw when selection changes
+ AudioController::SampleRange old_selection;
- /// DOCME
- bool draggingScale;
-
- /// DOCME
- int64_t selStart;
-
- /// DOCME
- int64_t selEnd;
-
- /// DOCME
- int64_t lineStart;
-
- /// DOCME
- int64_t lineEnd;
-
- /// DOCME
- int64_t selStartCap;
-
- /// DOCME
- int64_t selEndCap;
-
- /// DOCME
- int hold;
-
- /// DOCME
- int lastX;
-
- /// DOCME
- int lastDragX;
-
- /// DOCME
- int curStartMS;
-
- /// DOCME
- int curEndMS;
-
- /// DOCME
- int holdSyl;
-
- /// DOCME
- int *peak;
-
- /// DOCME
- int *min;
+ /// wxWidgets paint event
void OnPaint(wxPaintEvent &event);
+ /// wxWidgets mouse input event
void OnMouseEvent(wxMouseEvent &event);
+ /// wxWidgets control size changed event
void OnSize(wxSizeEvent &event);
- void OnUpdateTimer(wxTimerEvent &event);
- void OnKeyDown(wxKeyEvent &event);
- void OnGetFocus(wxFocusEvent &event);
- void OnLoseFocus(wxFocusEvent &event);
+ /// wxWidgets input focus changed event
+ void OnFocus(wxFocusEvent &event);
- void UpdateSamples();
- void Reset();
- void DrawTimescale(wxDC &dc);
- void DrawKeyframes(wxDC &dc);
- void DrawInactiveLines(wxDC &dc);
- void DrawWaveform(wxDC &dc,bool weak);
- void DrawSpectrum(wxDC &dc,bool weak);
- void GetDialoguePos(int64_t &start,int64_t &end,bool cap);
- void GetKaraokePos(int64_t &start,int64_t &end,bool cap);
- void UpdatePosition(int pos,bool IsSample=false);
- int GetBoundarySnap(int x,int range,bool shiftHeld,bool start=true);
- void DoUpdateImage();
+private:
+ // AudioControllerAudioEventListener implementation
+ virtual void OnAudioOpen(AudioProvider *provider);
+ virtual void OnAudioClose();
+ virtual void OnPlaybackPosition(int64_t sample_position);
+ virtual void OnPlaybackStop();
+
+ // AudioControllerTimingEventListener implementation
+ virtual void OnMarkersMoved();
+ virtual void OnSelectionChanged();
+ virtual void OnTimingControllerChanged();
- void OnActiveLineChanged(AssDialogue *new_line);
- void OnSelectedSetChanged(const Selection &lines_added, const Selection &lines_removed);
- void OnCommit(int);
- agi::signal::Connection commitListener;
public:
- /// DOCME
- AudioProvider *provider;
-
- /// DOCME
- AudioPlayer *player;
-
- /// DOCME
- bool NeedCommit;
-
- /// DOCME
- bool loaded;
-
- /// DOCME
- bool temporary;
-
- /// DOCME
-
- /// DOCME
- int w,h;
-
- /// DOCME
- AudioBox *box;
-
- /// DOCME
- AudioKaraoke *karaoke;
-
- /// DOCME
- wxScrollBar *ScrollBar;
-
- /// DOCME
- wxTimer UpdateTimer;
-
- AudioDisplay(wxWindow *parent, SubtitlesGrid *grid);
+ AudioDisplay(wxWindow *parent, AudioController *controller);
~AudioDisplay();
- void UpdateImage(bool weak=false);
- void Update();
- void RecreateImage();
- void SetPosition(int pos);
- void SetSamplesPercent(int percent,bool update=true,float pivot=0.5);
- void SetScale(float scale);
- void UpdateScrollbar();
- void SetDialogue(SubtitlesGrid *_grid=NULL,AssDialogue *diag=NULL,int n=-1);
- void MakeDialogueVisible(bool force=false);
- void ChangeLine(int delta, bool block=false);
- void Next(bool play=true);
- void Prev(bool play=true);
- void CommitChanges(bool nextLine=false);
- void AddLead(bool in,bool out);
+ /// @brief Scroll the audio display
+ /// @param pixel_amount Number of pixels to scroll the view
+ ///
+ /// A positive amount moves the display to the right, making later parts of the audio visible.
+ void ScrollBy(int pixel_amount);
- void SetFile(wxString file);
- void SetFromVideo();
- void Reload();
+ /// @brief Scroll the audio display
+ /// @param pixel_position Absolute pixel to put at left edge of the audio display
+ ///
+ /// This is the principal scrolling function. All other scrolling functions eventually
+ /// call this function to perform the actual scrolling.
+ void ScrollPixelToLeft(int pixel_position);
- void Play(int start,int end);
- void Stop();
+ /// @brief Scroll the audio display
+ /// @param pixel_position Absolute pixel to put in center of the audio display
+ void ScrollPixelToCenter(int pixel_position);
- int64_t GetSampleAtX(int x);
- int GetXAtSample(int64_t n);
- int GetMSAtX(int64_t x);
- int GetXAtMS(int64_t ms);
- int GetMSAtSample(int64_t x);
- int64_t GetSampleAtMS(int64_t ms);
- int GetSyllableAtX(int x);
+ /// @brief Scroll the audio display
+ /// @param sample_position Audio sample to put at left edge of the audio display
+ void ScrollSampleToLeft(int64_t sample_position);
+
+ /// @brief Scroll the audio display
+ /// @param sample_position Audio sample to put in center of the audio display
+ void ScrollSampleToCenter(int64_t sample_position);
+
+ /// @brief Scroll the audio display
+ /// @param range Range of audio samples to ensure is in view
+ ///
+ /// If the entire range is already visible inside the display, nothing is scrolled. If
+ /// just one of the two endpoints is visible, the display is scrolled such that the
+ /// visible endpoint stays in view but more of the rest of the range becomes visible.
+ ///
+ /// If the entire range fits inside the display, the display is centered over the range.
+ /// For this calculation, the display is considered smaller by some margins, see below.
+ ///
+ /// If the range does not fit within the display with margins subtracted, the start of
+ /// the range is ensured visible and as much of the rest of the range is brought into
+ /// view.
+ ///
+ /// For the purpose of this function, a 5 percent margin is assumed at each end of the
+ /// audio display such that a range endpoint that is ensured to be in view never gets
+ /// closer to the edge of the display than the margin. The edge that is not ensured to
+ /// be in view might be outside of view or might be closer to the display edge than the
+ /// margin.
+ void ScrollSampleRangeInView(const AudioController::SampleRange &range);
+
+
+ /// @brief Change the zoom level
+ /// @param new_zoom_level The new zoom level to use
+ ///
+ /// A zoom level of 0 is the default zoom level, all other levels are based on this.
+ /// Negative zoom levels zoom out, positive zoom in.
+ ///
+ /// The zoom levels generally go from +30 to -30. It is possible to zoom in more than
+ /// +30
+ void SetZoomLevel(int new_zoom_level);
+
+ /// @brief Get the zoom level
+ /// @return The zoom level
+ ///
+ /// See SetZoomLevel for a description of zoom levels.
+ int GetZoomLevel() const;
+
+ /// @brief Get a textual description of a zoom level
+ /// @param level The zoom level to describe
+ /// @return A translated string describing a zoom level
+ ///
+ /// The zoom level description can tell the user details about how much audio is
+ /// actually displayed.
+ wxString GetZoomLevelDescription(int level) const;
+
+ /// @brief Get the zoom factor in percent for a zoom level
+ /// @param level The zoom level to get the factor of
+ /// @return The zoom factor in percent
+ ///
+ /// Positive: 125, 150, 175, 200, 225, ...
+ ///
+ /// Negative: 90, 80, 70, 60, 50, 45, 40, 35, 30, 25, 20, 19, 18, 17, ..., 1
+ ///
+ /// Too negative numbers get clamped.
+ static int GetZoomLevelFactor(int level);
+
+
+ /// @brief Set amplitude scale factor
+ /// @param scale New amplitude scale factor, 1.0 is no scaling
+ void SetAmplitudeScale(float scale);
+
+ /// @brief Get amplitude scale factor
+ /// @return The amplitude scaling factor
+ float GetAmplitudeScale() const;
+
+
+ /// @brief Reload all rendering settings from Options and reset caches
+ ///
+ /// This can be called if some rendering quality settings have been changed in Options
+ /// and need to be reloaded to take effect.
+ void ReloadRenderingSettings();
+
+
+ /// @brief Get a sample index from an X coordinate relative to current scroll
+ int64_t SamplesFromRelativeX(int x) const { return (scroll_left + x) * pixel_samples; }
+ /// @brief Get a sample index from an absolute X coordinate
+ int64_t SamplesFromAbsoluteX(int x) const { return x * pixel_samples; }
+ /// @brief Get an X coordinate relative to the current scroll from a sample index
+ int RelativeXFromSamples(int64_t samples) const { return samples/pixel_samples - scroll_left; }
+ /// @brief Get an absolute X coordinate from a sample index
+ int AbsoluteXFromSamples(int64_t samples) const { return samples/pixel_samples; }
- void GetTimesDialogue(int &start,int &end);
- void GetTimesSelection(int &start,int &end);
- void SetSelection(int start, int end);
DECLARE_EVENT_TABLE()
};
-///////
-// IDs
-enum {
- Audio_Update_Timer = 1700
-};
diff --git a/aegisub/src/audio_karaoke.cpp b/aegisub/src/audio_karaoke.cpp
index 1d8ca4c6d..84be566da 100644
--- a/aegisub/src/audio_karaoke.cpp
+++ b/aegisub/src/audio_karaoke.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2005, 2006, 2007, Rodrigo Braz Monteiro, Niels Martin Hansen
+// Copyright (c) 2005-2009, Rodrigo Braz Monteiro, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -48,6 +48,8 @@
#include
#include "ass_override.h"
+#include "selection_controller.h"
+#include "audio_controller.h"
#include "audio_box.h"
#include "audio_display.h"
#include "audio_karaoke.h"
@@ -634,7 +636,7 @@ void AudioKaraoke::Join() {
// Update
must_rebuild = true;
- display->NeedCommit = true;
+ //display->NeedCommit = true;
display->Update();
Refresh(false);
@@ -678,7 +680,7 @@ void AudioKaraoke::EndSplit(bool commit) {
if (hasSplit) {
LOG_D("karaoke/audio") << "hassplit";
must_rebuild = true;
- display->NeedCommit = true;
+ //display->NeedCommit = true;
SetSelection(first_sel);
display->Update();
}
@@ -879,8 +881,9 @@ void AudioKaraokeTagMenu::OnSelectItem(wxCommandEvent &event) {
// Update display
kara->must_rebuild = true;
//kara->Commit();
- kara->display->NeedCommit = true;
- kara->display->CommitChanges();
+ //kara->display->NeedCommit = true;
+ /// @todo Commit changes and stay on current line
+ //kara->display->CommitChanges();
//kara->display->Update();
kara->SetSelection(firstsel, lastsel);
}
diff --git a/aegisub/src/audio_player_alsa.cpp b/aegisub/src/audio_player_alsa.cpp
index ae5ab6c9d..4b7ddcd7f 100644
--- a/aegisub/src/audio_player_alsa.cpp
+++ b/aegisub/src/audio_player_alsa.cpp
@@ -40,6 +40,7 @@
#include
+#include "audio_controller.h"
#include "audio_player_alsa.h"
#include "main.h"
#include "compat.h"
diff --git a/aegisub/src/audio_player_dsound.cpp b/aegisub/src/audio_player_dsound.cpp
index 2fe021c1b..14ad47965 100644
--- a/aegisub/src/audio_player_dsound.cpp
+++ b/aegisub/src/audio_player_dsound.cpp
@@ -40,6 +40,7 @@
#include
+#include "audio_controller.h"
#include "audio_player_dsound.h"
#include "frame_main.h"
#include "main.h"
diff --git a/aegisub/src/audio_player_dsound2.cpp b/aegisub/src/audio_player_dsound2.cpp
index 2d3969223..5ed938d78 100644
--- a/aegisub/src/audio_player_dsound2.cpp
+++ b/aegisub/src/audio_player_dsound2.cpp
@@ -49,9 +49,10 @@
#include
+#include "audio_controller.h"
#include "audio_player_dsound2.h"
-#include "frame_main.h"
#include "include/aegisub/audio_provider.h"
+#include "frame_main.h"
#include "main.h"
#include "utils.h"
diff --git a/aegisub/src/audio_player_openal.cpp b/aegisub/src/audio_player_openal.cpp
index f87c11cd7..443e7fb96 100644
--- a/aegisub/src/audio_player_openal.cpp
+++ b/aegisub/src/audio_player_openal.cpp
@@ -40,6 +40,7 @@
#include
+#include "audio_controller.h"
#include "audio_player_openal.h"
#include "frame_main.h"
#include "utils.h"
diff --git a/aegisub/src/audio_player_oss.cpp b/aegisub/src/audio_player_oss.cpp
index c9cfec9cb..c9187371b 100644
--- a/aegisub/src/audio_player_oss.cpp
+++ b/aegisub/src/audio_player_oss.cpp
@@ -38,6 +38,7 @@
#include
+#include "audio_controller.h"
#include "audio_player_oss.h"
#include "frame_main.h"
#include "compat.h"
diff --git a/aegisub/src/audio_provider.cpp b/aegisub/src/audio_provider.cpp
index d37307679..4c230293c 100644
--- a/aegisub/src/audio_provider.cpp
+++ b/aegisub/src/audio_provider.cpp
@@ -65,77 +65,6 @@ AudioProvider::~AudioProvider() {
delete[] raw;
}
-/// @brief Get waveform
-/// @param min
-/// @param peak
-/// @param start
-/// @param w
-/// @param h
-/// @param samples
-/// @param scale
-///
-void AudioProvider::GetWaveForm(int *min,int *peak,int64_t start,int w,int h,int samples,float scale) {
- // Setup
- int channels = GetChannels();
- int n = w * samples;
- for (int i=0;i h) curvalue = h;
- if (curvalue < 0) curvalue = 0;
- if (curvalue < min[cur]) min[cur] = curvalue;
- if (curvalue > peak[cur]) peak[cur] = curvalue;
- }
- }
-
- if (bytes_per_sample == 2) {
- // Read raw samples
- short *raw_short = (short*) raw;
- GetAudio(raw,start,n);
- int half_h = h/2;
- int half_amplitude = int(half_h * scale);
-
- // Calculate waveform
- for (int i=0;i h) curvalue = h;
- if (curvalue < 0) curvalue = 0;
- if (curvalue < min[cur]) min[cur] = curvalue;
- if (curvalue > peak[cur]) peak[cur] = curvalue;
- }
- }
-}
-
/// @brief Get audio with volume
/// @param buf
/// @param start
@@ -143,7 +72,7 @@ void AudioProvider::GetWaveForm(int *min,int *peak,int64_t start,int w,int h,int
/// @param volume
/// @return
///
-void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) {
+void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const {
try {
GetAudio(buf,start,count);
}
diff --git a/aegisub/src/audio_provider_avs.cpp b/aegisub/src/audio_provider_avs.cpp
index e342da69c..82ca5f35f 100644
--- a/aegisub/src/audio_provider_avs.cpp
+++ b/aegisub/src/audio_provider_avs.cpp
@@ -141,7 +141,7 @@ void AvisynthAudioProvider::LoadFromClip(AVSValue _clip) {
/// @param start
/// @param count
///
-void AvisynthAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) {
+void AvisynthAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
// Requested beyond the length of audio
if (start+count > num_samples) {
int64_t oldcount = count;
diff --git a/aegisub/src/audio_provider_avs.h b/aegisub/src/audio_provider_avs.h
index 2f573ec6b..31836ec87 100644
--- a/aegisub/src/audio_provider_avs.h
+++ b/aegisub/src/audio_provider_avs.h
@@ -57,15 +57,12 @@ class AvisynthAudioProvider : public AudioProvider, public AviSynthWrapper {
public:
AvisynthAudioProvider(wxString _filename);
- wxString GetFilename() { return filename; }
+ wxString GetFilename() const { return filename; }
- /// @brief Only exists for x86 Windows, always delivers machine (little) endian
- /// @return
- ///
bool AreSamplesNativeEndian() const { return true; }
bool NeedsCache() const { return true; }
- void GetAudio(void *buf, int64_t start, int64_t count);
+ void GetAudio(void *buf, int64_t start, int64_t count) const;
void GetWaveForm(int *min,int *peak,int64_t start,int w,int h,int samples,float scale);
};
#endif
diff --git a/aegisub/src/audio_provider_convert.cpp b/aegisub/src/audio_provider_convert.cpp
index f7475f946..e08df165d 100644
--- a/aegisub/src/audio_provider_convert.cpp
+++ b/aegisub/src/audio_provider_convert.cpp
@@ -62,7 +62,7 @@ ConvertAudioProvider::ConvertAudioProvider(AudioProvider *src) : source(src) {
/// @param dst
/// @param count
///
-void ConvertAudioProvider::Make16Bit(const char *src, short *dst, int64_t count) {
+void ConvertAudioProvider::Make16Bit(const char *src, short *dst, int64_t count) const {
for (int64_t i=0;i
/// @param count
/// @param converter
///
-void ConvertAudioProvider::ChangeSampleRate(const short *src, short *dst, int64_t count, const SampleConverter &converter) {
+void ConvertAudioProvider::ChangeSampleRate(const short *src, short *dst, int64_t count, const SampleConverter &converter) const {
// Upsample by 2
if (sampleMult == 2) {
int64_t size = count/2;
@@ -139,7 +139,7 @@ struct EndianSwapSampleConverter {
/// @param start
/// @param count
///
-void ConvertAudioProvider::GetAudio(void *destination, int64_t start, int64_t count) {
+void ConvertAudioProvider::GetAudio(void *destination, int64_t start, int64_t count) const {
// Bits per sample
int srcBps = source->GetBytesPerSample();
diff --git a/aegisub/src/audio_provider_convert.h b/aegisub/src/audio_provider_convert.h
index 397995117..79b3e2ef8 100644
--- a/aegisub/src/audio_provider_convert.h
+++ b/aegisub/src/audio_provider_convert.h
@@ -51,9 +51,9 @@ class ConvertAudioProvider : public AudioProvider {
/// DOCME
std::tr1::shared_ptr source;
- void Make16Bit(const char *src, short *dst, int64_t count);
+ void Make16Bit(const char *src, short *dst, int64_t count) const;
template
- void ChangeSampleRate(const short *src, short *dst, int64_t count, const SampleConverter &converter);
+ void ChangeSampleRate(const short *src, short *dst, int64_t count, const SampleConverter &converter) const;
public:
ConvertAudioProvider(AudioProvider *source);
@@ -62,9 +62,9 @@ public:
/// That's one of the points of it!
bool AreSamplesNativeEndian() const { return true; }
- void GetAudio(void *buf, int64_t start, int64_t count);
+ void GetAudio(void *buf, int64_t start, int64_t count) const;
- wxString GetFilename() { return source->GetFilename(); }
+ wxString GetFilename() const { return source->GetFilename(); }
};
AudioProvider *CreateConvertAudioProvider(AudioProvider *source_provider);
diff --git a/aegisub/src/audio_provider_downmix.cpp b/aegisub/src/audio_provider_downmix.cpp
index 1d1ab2492..a8c277e95 100644
--- a/aegisub/src/audio_provider_downmix.cpp
+++ b/aegisub/src/audio_provider_downmix.cpp
@@ -64,7 +64,7 @@ DownmixingAudioProvider::DownmixingAudioProvider(AudioProvider *source) : provid
/// @param start
/// @param count
///
-void DownmixingAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) {
+void DownmixingAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
if (count == 0) return;
// We can do this ourselves
diff --git a/aegisub/src/audio_provider_downmix.h b/aegisub/src/audio_provider_downmix.h
index 91cceaa23..af79d316d 100644
--- a/aegisub/src/audio_provider_downmix.h
+++ b/aegisub/src/audio_provider_downmix.h
@@ -57,5 +57,5 @@ public:
///
bool AreSamplesNativeEndian() const { return true; }
- void GetAudio(void *buf, int64_t start, int64_t count);
+ void GetAudio(void *buf, int64_t start, int64_t count) const;
};
diff --git a/aegisub/src/audio_provider_dummy.cpp b/aegisub/src/audio_provider_dummy.cpp
index ae7f3da77..08b1c5ce7 100644
--- a/aegisub/src/audio_provider_dummy.cpp
+++ b/aegisub/src/audio_provider_dummy.cpp
@@ -62,7 +62,7 @@ DummyAudioProvider::~DummyAudioProvider() {
/// @param start
/// @param count
///
-void DummyAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) {
+void DummyAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
short *workbuf = (short*)buf;
if (noise) {
diff --git a/aegisub/src/audio_provider_dummy.h b/aegisub/src/audio_provider_dummy.h
index 508c15ab3..6448412eb 100644
--- a/aegisub/src/audio_provider_dummy.h
+++ b/aegisub/src/audio_provider_dummy.h
@@ -50,5 +50,5 @@ public:
~DummyAudioProvider();
bool AreSamplesNativeEndian() const { return true; }
- void GetAudio(void *buf, int64_t start, int64_t count);
+ void GetAudio(void *buf, int64_t start, int64_t count) const;
};
diff --git a/aegisub/src/audio_provider_ffmpegsource.cpp b/aegisub/src/audio_provider_ffmpegsource.cpp
index 051009789..4f8f3afa0 100644
--- a/aegisub/src/audio_provider_ffmpegsource.cpp
+++ b/aegisub/src/audio_provider_ffmpegsource.cpp
@@ -228,7 +228,7 @@ void FFmpegSourceAudioProvider::Close() {
/// @param Start
/// @param Count
///
-void FFmpegSourceAudioProvider::GetAudio(void *Buf, int64_t Start, int64_t Count) {
+void FFmpegSourceAudioProvider::GetAudio(void *Buf, int64_t Start, int64_t Count) const {
uint8_t *Buf2 = static_cast(Buf);
Start -= delay;
if (Start < 0) {
diff --git a/aegisub/src/audio_provider_ffmpegsource.h b/aegisub/src/audio_provider_ffmpegsource.h
index 6e9314f3e..12062805c 100644
--- a/aegisub/src/audio_provider_ffmpegsource.h
+++ b/aegisub/src/audio_provider_ffmpegsource.h
@@ -46,8 +46,8 @@ private:
FFMS_AudioSource *AudioSource; ///< audio source object
bool COMInited; ///< COM initialization state
- char FFMSErrMsg[1024]; ///< FFMS error message
- FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages
+ mutable char FFMSErrMsg[1024]; ///< FFMS error message
+ mutable FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages
void Close();
void LoadAudio(wxString filename);
@@ -65,6 +65,6 @@ public:
bool AreSamplesNativeEndian() const { return true; }
bool NeedsCache() const { return true; }
- virtual void GetAudio(void *buf, int64_t start, int64_t count);
+ virtual void GetAudio(void *buf, int64_t start, int64_t count) const;
};
#endif
diff --git a/aegisub/src/audio_provider_hd.cpp b/aegisub/src/audio_provider_hd.cpp
index 93a67a4e7..db41eb909 100644
--- a/aegisub/src/audio_provider_hd.cpp
+++ b/aegisub/src/audio_provider_hd.cpp
@@ -41,6 +41,7 @@
#include
#endif
+#include "audio_controller.h"
#include "audio_provider_hd.h"
#include "compat.h"
#include "dialog_progress.h"
@@ -114,7 +115,7 @@ HDAudioProvider::~HDAudioProvider() {
/// @param start
/// @param count
///
-void HDAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) {
+void HDAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
// Requested beyond the length of audio
if (start+count > num_samples) {
int64_t oldcount = count;
diff --git a/aegisub/src/audio_provider_hd.h b/aegisub/src/audio_provider_hd.h
index fd78d4587..f62af2396 100644
--- a/aegisub/src/audio_provider_hd.h
+++ b/aegisub/src/audio_provider_hd.h
@@ -48,10 +48,10 @@
/// DOCME
class HDAudioProvider : public AudioProvider {
/// DOCME
- wxMutex diskmutex;
+ mutable wxMutex diskmutex;
/// DOCME
- wxFile file_cache;
+ mutable wxFile file_cache;
/// DOCME
wxString diskCacheFilename;
@@ -71,5 +71,5 @@ public:
bool AreSamplesNativeEndian() const { return samples_native_endian; }
- void GetAudio(void *buf, int64_t start, int64_t count);
+ void GetAudio(void *buf, int64_t start, int64_t count) const;
};
diff --git a/aegisub/src/audio_provider_pcm.cpp b/aegisub/src/audio_provider_pcm.cpp
index c4cf0bae0..22f3e51eb 100644
--- a/aegisub/src/audio_provider_pcm.cpp
+++ b/aegisub/src/audio_provider_pcm.cpp
@@ -144,7 +144,7 @@ PCMAudioProvider::~PCMAudioProvider()
/// @param range_length
/// @return
///
-char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t range_length)
+char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t range_length) const
{
if (range_start + range_length > file_size) {
throw AudioDecodeError("Attempted to map beyond end of file");
@@ -217,13 +217,13 @@ char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t rang
/// @param start
/// @param count
///
-void PCMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count)
+void PCMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const
{
// Read blocks from the file
size_t index = 0;
while (count > 0 && index < index_points.size()) {
// Check if this index contains the samples we're looking for
- IndexPoint &ip = index_points[index];
+ const IndexPoint &ip = index_points[index];
if (ip.start_sample <= start && ip.start_sample+ip.num_samples > start) {
// How many samples we can maximum take from this block
diff --git a/aegisub/src/audio_provider_pcm.h b/aegisub/src/audio_provider_pcm.h
index 46425d6a7..2c036cea1 100644
--- a/aegisub/src/audio_provider_pcm.h
+++ b/aegisub/src/audio_provider_pcm.h
@@ -64,24 +64,24 @@ private:
HANDLE file_mapping;
/// DOCME
- void *current_mapping;
+ mutable void *current_mapping;
/// DOCME
- int64_t mapping_start;
+ mutable int64_t mapping_start;
/// DOCME
- size_t mapping_length;
+ mutable size_t mapping_length;
#else
int file_handle;
- void *current_mapping;
- off_t mapping_start;
- size_t mapping_length;
+ mutable void *current_mapping;
+ mutable off_t mapping_start;
+ mutable size_t mapping_length;
#endif
protected:
PCMAudioProvider(const wxString &filename); // Create base object and open the file mapping
virtual ~PCMAudioProvider(); // Closes the file mapping
- char * EnsureRangeAccessible(int64_t range_start, int64_t range_length); // Ensure that the given range of bytes are accessible in the file mapping and return a pointer to the first byte of the requested range
+ char * EnsureRangeAccessible(int64_t range_start, int64_t range_length) const; // Ensure that the given range of bytes are accessible in the file mapping and return a pointer to the first byte of the requested range
/// DOCME
@@ -108,7 +108,7 @@ protected:
IndexVector index_points;
public:
- virtual void GetAudio(void *buf, int64_t start, int64_t count);
+ virtual void GetAudio(void *buf, int64_t start, int64_t count) const;
};
// Construct the right PCM audio provider (if any) for the file
diff --git a/aegisub/src/audio_provider_ram.cpp b/aegisub/src/audio_provider_ram.cpp
index ca468e301..927c26f30 100644
--- a/aegisub/src/audio_provider_ram.cpp
+++ b/aegisub/src/audio_provider_ram.cpp
@@ -36,6 +36,7 @@
#include "config.h"
+#include "audio_controller.h"
#include "audio_provider_ram.h"
#include "dialog_progress.h"
#include "frame_main.h"
@@ -131,7 +132,7 @@ void RAMAudioProvider::Clear() {
/// @param start
/// @param count
///
-void RAMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) {
+void RAMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
// Requested beyond the length of audio
if (start+count > num_samples) {
int64_t oldcount = count;
diff --git a/aegisub/src/audio_provider_ram.h b/aegisub/src/audio_provider_ram.h
index 1c5cb9a62..efba843ae 100644
--- a/aegisub/src/audio_provider_ram.h
+++ b/aegisub/src/audio_provider_ram.h
@@ -58,5 +58,5 @@ public:
~RAMAudioProvider();
bool AreSamplesNativeEndian() const { return samples_native_endian; }
- void GetAudio(void *buf, int64_t start, int64_t count);
+ void GetAudio(void *buf, int64_t start, int64_t count) const;
};
diff --git a/aegisub/src/audio_renderer.cpp b/aegisub/src/audio_renderer.cpp
index b7d8c66cd..2767e7c84 100644
--- a/aegisub/src/audio_renderer.cpp
+++ b/aegisub/src/audio_renderer.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2009, Niels Martin Hansen
+// Copyright (c) 2009-2010, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -42,9 +42,13 @@
#include
#endif
+#include "block_cache.h"
#include "audio_renderer.h"
#include "include/aegisub/audio_provider.h"
+#undef min
+#undef max
+
AudioRendererBitmapCacheBitmapFactory::AudioRendererBitmapCacheBitmapFactory(AudioRenderer *_renderer)
{
@@ -56,7 +60,7 @@ AudioRendererBitmapCacheBitmapFactory::AudioRendererBitmapCacheBitmapFactory(Aud
wxBitmap *AudioRendererBitmapCacheBitmapFactory::ProduceBlock(int i)
{
(void)i;
- return new wxBitmap(renderer->cache_bitmap_width, renderer->pixel_height, 32);
+ return new wxBitmap(renderer->cache_bitmap_width, renderer->pixel_height, 24);
}
@@ -68,7 +72,7 @@ void AudioRendererBitmapCacheBitmapFactory::DisposeBlock(wxBitmap *bmp)
size_t AudioRendererBitmapCacheBitmapFactory::GetBlockSize() const
{
- return sizeof(wxBitmap) + renderer->cache_bitmap_width * renderer->pixel_height * 4;
+ return sizeof(wxBitmap) + renderer->cache_bitmap_width * renderer->pixel_height * 3;
}
@@ -78,7 +82,8 @@ AudioRenderer::AudioRenderer()
: cache_bitmap_width(32) // arbitrary value for now
, bitmaps_normal(256, AudioRendererBitmapCacheBitmapFactory(this))
, bitmaps_selected(256, AudioRendererBitmapCacheBitmapFactory(this))
-, cache_maxsize(0)
+, cache_bitmap_maxsize(0)
+, cache_renderer_maxsize(0)
, renderer(0)
, provider(0)
{
@@ -166,7 +171,12 @@ void AudioRenderer::SetAudioProvider(AudioProvider *_provider)
void AudioRenderer::SetCacheMaxSize(size_t max_size)
{
- cache_maxsize = max_size;
+ // Limit the bitmap cache sizes to 16 MB hard, to avoid the risk of exhausting
+ // system bitmap object resources and similar. Experimenting shows that 16 MB
+ // bitmap cache should be plenty even if working with a one hour audio clip.
+ cache_bitmap_maxsize = std::min(max_size/8, (size_t)0x1000000);
+ // The renderer gets whatever is left.
+ cache_renderer_maxsize = max_size - 2*cache_bitmap_maxsize;
}
@@ -174,9 +184,10 @@ void AudioRenderer::ResetBlockCount()
{
if (provider)
{
- size_t num_bitmaps = (size_t)((provider->GetNumSamples() + pixel_samples - 1) / pixel_samples);
- bitmaps_normal.SetBlockCount(num_bitmaps);
- bitmaps_selected.SetBlockCount(num_bitmaps);
+ size_t rendered_width = (size_t)((provider->GetNumSamples() + pixel_samples - 1) / pixel_samples);
+ cache_numblocks = rendered_width / cache_bitmap_width;
+ bitmaps_normal.SetBlockCount(cache_numblocks);
+ bitmaps_selected.SetBlockCount(cache_numblocks);
}
}
@@ -204,16 +215,16 @@ wxBitmap AudioRenderer::GetCachedBitmap(int i, bool selected)
void AudioRenderer::Render(wxDC &dc, wxPoint origin, int start, int length, bool selected)
{
- assert(start >= 0);
- assert(length >= 0);
-
assert(start >= 0);
if (!provider) return;
if (!renderer) return;
+ if (length <= 0) return;
- // Last absolute pixel strip to render
- int end = start + length - 1;
+ // One past last absolute pixel strip to render
+ int end = start + length;
+ // One past last X coordinate to render on
+ int lastx = origin.x + length;
// Figure out which range of bitmaps are required
int firstbitmap = start / cache_bitmap_width;
// And the offset in it to start its use at
@@ -223,20 +234,36 @@ void AudioRenderer::Render(wxDC &dc, wxPoint origin, int start, int length, bool
// How many columns of the last bitmap to use
int lastbitmapoffset = end % cache_bitmap_width;
- // Two basic cases now: Either firstbitmap is the same as lastbitmap, or they're different.
+ // Check if we need to render any blank audio past the last bitmap from cache,
+ // this happens if we're asked to render more audio than the provider has.
+ if (lastbitmap >= (int)cache_numblocks)
+ {
+ lastbitmap = cache_numblocks - 1;
+ lastbitmapoffset = cache_bitmap_width;
+
+ if (firstbitmap > lastbitmap)
+ firstbitmap = lastbitmap;
+ }
+
+ // Three basic cases now:
+ // * Either we're just rendering blank audio,
+ // * Or there is exactly one bitmap to render,
+ // * Or there is more than one bitmap to render.
// origin is passed by value because we'll be using it as a local var to keep track
// of rendering progress!
- if (firstbitmap == lastbitmap)
+ if (start / cache_bitmap_width >= (int)cache_numblocks)
{
- // These better be the same: The first to the last column of the single bitmap
- // to use should equal the length of the area to render.
- assert(lastbitmapoffset - firstbitmapoffset == length);
-
+ // Do nothing, the blank audio rendering will happen later
+ }
+ else if (firstbitmap == lastbitmap)
+ {
+ const int renderwidth = lastbitmapoffset - firstbitmapoffset;
wxBitmap bmp = GetCachedBitmap(firstbitmap, selected);
wxMemoryDC bmpdc(bmp);
- dc.Blit(origin, wxSize(length, pixel_height), &bmpdc, wxPoint(firstbitmapoffset, 0));
+ dc.Blit(origin, wxSize(renderwidth, pixel_height), &bmpdc, wxPoint(firstbitmapoffset, 0));
+ origin.x += renderwidth;
}
else
{
@@ -244,32 +271,40 @@ void AudioRenderer::Render(wxDC &dc, wxPoint origin, int start, int length, bool
{
bmp = GetCachedBitmap(firstbitmap, selected);
+ // Can't use dc.DrawBitmap here because we need to clip the bitmap
wxMemoryDC bmpdc(bmp);
dc.Blit(origin, wxSize(cache_bitmap_width-firstbitmapoffset, pixel_height),
&bmpdc, wxPoint(firstbitmapoffset, 0));
origin.x += cache_bitmap_width-firstbitmapoffset;
}
- for (int i = 1; i < lastbitmap; ++i)
+ for (int i = firstbitmap+1; i < lastbitmap; ++i)
{
bmp = GetCachedBitmap(i, selected);
- wxMemoryDC bmpdc(bmp);
- dc.Blit(origin, wxSize(cache_bitmap_width, pixel_height), &bmpdc, wxPoint(0, 0));
+ dc.DrawBitmap(bmp, origin);
origin.x += cache_bitmap_width;
}
{
bmp = GetCachedBitmap(lastbitmap, selected);
+ // We also need clipping here
wxMemoryDC bmpdc(bmp);
- dc.Blit(origin, wxSize(lastbitmapoffset, pixel_height), &bmpdc, wxPoint(0, 0));
+ dc.Blit(origin, wxSize(lastbitmapoffset+1, pixel_height), &bmpdc, wxPoint(0, 0));
+ origin.x += lastbitmapoffset+1;
}
}
+ // Now render blank audio from origin to end
+ if (origin.x < lastx)
+ {
+ renderer->RenderBlank(dc, wxRect(origin.x-1, origin.y, lastx-origin.x+1, pixel_height), selected);
+ }
+
if (selected)
- bitmaps_selected.Age(cache_maxsize / 8);
+ bitmaps_selected.Age(cache_bitmap_maxsize);
else
- bitmaps_normal.Age(cache_maxsize / 8);
- renderer->AgeCache(3 * cache_maxsize / 4);
+ bitmaps_normal.Age(cache_bitmap_maxsize);
+ renderer->AgeCache(cache_renderer_maxsize);
}
@@ -297,7 +332,7 @@ void AudioRendererBitmapProvider::SetSamplesPerPixel(int _pixel_samples)
if (pixel_samples == _pixel_samples) return;
pixel_samples = _pixel_samples;
-
+
OnSetSamplesPerPixel();
}
diff --git a/aegisub/src/audio_renderer.h b/aegisub/src/audio_renderer.h
index 668e9eb8b..9d9daac2a 100644
--- a/aegisub/src/audio_renderer.h
+++ b/aegisub/src/audio_renderer.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009, Niels Martin Hansen
+// Copyright (c) 2009-2010, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -43,8 +43,6 @@
#include
#endif
-#include "block_cache.h"
-
// Some forward declarations for outside stuff
class AudioProvider;
@@ -54,6 +52,11 @@ class AudioRendererBitmapProvider;
class AudioRenderer;
+#ifndef AGI_BLOCK_CACHE_INCLUDED
+#error You much include "block_cache.h" before "audio_renderer.h"
+#endif
+
+
/// @class AudioRendererBitmapCacheBitmapFactory
/// @brief Produces wxBitmap objects for DataBlockCache storage for the audio renderer
@@ -111,8 +114,12 @@ class AudioRenderer {
AudioRendererBitmapCache bitmaps_normal;
/// Cached bitmaps for marked (selected) audio ranges
AudioRendererBitmapCache bitmaps_selected;
- /// The maximum allowed size of the cache, in bytes
- size_t cache_maxsize;
+ /// Number of blocks in the bitmap caches
+ size_t cache_numblocks;
+ /// The maximum allowed size of each bitmap cache, in bytes
+ size_t cache_bitmap_maxsize;
+ /// The maximum allowed size of the renderer's cache, in bytes
+ size_t cache_renderer_maxsize;
/// Actual renderer for bitmaps
AudioRendererBitmapProvider *renderer;
@@ -284,7 +291,7 @@ public:
AudioRendererBitmapProvider() : provider(0), pixel_samples(0) { };
/// @brief Destructor
- ~AudioRendererBitmapProvider() { }
+ virtual ~AudioRendererBitmapProvider() { }
/// @brief Rendering function
/// @param bmp Bitmap to render to
@@ -295,6 +302,15 @@ public:
/// the width and height to render.
virtual void Render(wxBitmap &bmp, int start, bool selected) = 0;
+ /// @brief Blank audio rendering function
+ /// @param dc The device context to render to
+ /// @param rect The rectangle to fill with the image of blank audio
+ /// @param selected Whether to render as being selected or not
+ ///
+ /// Deriving classes must implement this method. The rectangle has the height
+ /// of the entire canvas the audio is being rendered in.
+ virtual void RenderBlank(wxDC &dc, const wxRect &rect, bool selected) = 0;
+
/// @brief Change audio provider
/// @param provider Audio provider to change to
void SetProvider(AudioProvider *provider);
diff --git a/aegisub/src/audio_renderer_spectrum.cpp b/aegisub/src/audio_renderer_spectrum.cpp
index ab96542d8..a75d98187 100644
--- a/aegisub/src/audio_renderer_spectrum.cpp
+++ b/aegisub/src/audio_renderer_spectrum.cpp
@@ -1,5 +1,5 @@
-// Copyright (c) 2005, 2006, Rodrigo Braz Monteiro
-// Copyright (c) 2006, 2007, Niels Martin Hansen
+// Copyright (c) 2005-2006, Rodrigo Braz Monteiro
+// Copyright (c) 2006-2010, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -33,770 +33,331 @@
/// @file audio_renderer_spectrum.cpp
/// @brief Caching frequency-power spectrum renderer for audio display
/// @ingroup audio_ui
-///
#include "config.h"
#ifndef AGI_PRE
-#include
-#ifdef _OPENMP
-#include
-#endif
-
#include
-#include
-#include
-#include
-#include
+#include
+#include
+#include
#endif
#include
+#include "block_cache.h"
#include "include/aegisub/audio_provider.h"
+#include "audio_colorscheme.h"
+#include "audio_renderer.h"
#include "audio_renderer_spectrum.h"
-#include "colorspace.h"
+
+#ifdef WITH_FFTW
+#include
+#else
#include "fft.h"
+#endif
#include "main.h"
#include "utils.h"
-
-/// @class AudioSpectrumCache
-/// @brief Base class for the frequency-power cache tree.
-///
-/// The cached frequency-power are kept in a shallow tree-structure composed of
-/// intermediate branches and final leaves, both accessed through a common
-/// interface, which is this class.
-///
-/// The term "cache line" here means the frequency-power data derived from
-/// some range of audio samples, calculated by a single FFT.
-class AudioSpectrumCache {
-public:
-
- /// The type of frequency-power data at one point in time.
- typedef std::vector CacheLine;
-
-
- /// The type of timestamps for last access.
- typedef unsigned int CacheAccessTime;
-
- /// Holds last-access data for a range of range of cache lines.
- /// Ranges can overlap, in case overlapping FFT's are used to increase precision.
- struct CacheAgeData {
- /// Last time this range of cache lines were accessed.
- CacheAccessTime access_time;
- /// First line in the range.
- unsigned long first_line;
- /// Number of lines in the range.
- unsigned long num_lines;
-
- /// @brief Comparison operator for sorting age data by last access time.
- /// @param second The age data structure to compare against.
- /// @return Returns true if last access time of this range is less than that of the other.
- bool operator< (const CacheAgeData& second) const { return access_time < second.access_time; }
-
- /// @brief Constructor.
- /// @param t Initial access time to set.
- /// @param first First line in the range.
- /// @param num Number of lines in the range.
- ///
- CacheAgeData(CacheAccessTime t, unsigned long first, unsigned long num) : access_time(t), first_line(first), num_lines(num) { }
- };
-
- /// Type of a list of cache age data.
- typedef std::vector CacheAgeList;
-
- /// @brief Retrieve frequency-power data.
- /// @param i Index of the block to get the line from.
- /// @param overlap Index of the overlap in the block to get the line for.
- /// @param created [out] Set to true if the data had to be calculated, false if the data
- /// was found in cache.
- /// @param access_time Timestamp to mark the cache data as accessed at.
- /// @return Returns a reference to the frequency-power data requested.
- ///
- /// The data are fetched from the cache if they are cached, otherwise the required
- /// audio data are retrieved, the FFT derived, and power data calculated.
- virtual CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time) = 0;
-
- /// @brief Get the size of the cache subtree.
- /// @return Number of lines stored in all nodes below this one in the tree.
- virtual size_t GetManagedLineCount() = 0;
-
- /// @brief Retrieve cache access times.
- /// @param ages [in,out] List to append age data of managed lines to.
- ///
- /// Existing contents of the list is kept, new entries are added to the end.
- virtual void GetLineAccessTimes(CacheAgeList &ages) = 0;
-
- /// @brief Remove data from the cache.
- /// @param line_id Index of the block the cache node to remove starts at.
- /// @return Returns true if the object the method was called on no longer manages
- // any cache lines and can safely be deleted.
- virtual bool KillLine(unsigned long line_id) = 0;
-
-
- /// @brief Set the FFT size used globally.
- /// @param new_length Number of audio samples to use in calculation.
- static void SetLineLength(unsigned long new_length)
- {
- line_length = new_length;
- null_line.resize(new_length, 0);
- }
-
-
- /// @brief Destructor, does nothing in base class.
- virtual ~AudioSpectrumCache() {};
-
-protected:
-
- /// Global template for cache lines.
- static CacheLine null_line;
- /// Number of audio samples used for power calculation, determining the
- /// frequency resolution of the frequency-power data.
- static unsigned long line_length;
-};
-
-// Actual variables allocating memory for the static class members
-AudioSpectrumCache::CacheLine AudioSpectrumCache::null_line;
-unsigned long AudioSpectrumCache::line_length;
-
-
-
-/// @class FinalSpectrumCache
-/// @brief Leaf node in frequency-power cache tree, holds actual data.
-///
-/// This class stores frequency-power data and is responsible for calculating it as well.
-class FinalSpectrumCache : public AudioSpectrumCache {
-private:
-
- /// The stored data.
- std::vector data;
-
- unsigned long start, ///< Start of block range
- length; ///< Number of blocks
- unsigned int overlaps; ///< How many lines per block
-
- /// Last access time for cache management.
- CacheAccessTime last_access;
-
-public:
-
- /// @brief Returns stored frequency-power data.
- /// @param i Index of the block to get the line from.
- /// @param overlap Index of the overlap in the block to get the line for.
- /// @param created [out] Set to true if the data had to be calculated, false if the data
- /// was found in cache.
- /// @param access_time Timestamp to mark the cache data as accessed at.
- /// @return Returns a reference to the frequency-power data requested.
- CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time)
- {
- last_access = access_time;
-
- // This check ought to be redundant
- if (i >= start && i-start < length)
- return data[i - start + overlap*length];
- else
- return null_line;
- }
-
-
- /// @brief Get number of lines in cache.
- /// @return Number of lines stored at this leaf.
- size_t GetManagedLineCount()
- {
- return data.size();
- }
-
-
- /// @brief Add own cache age data to list of age data.
- /// @param ages [in,out] List to add cache age data to.
- ///
- /// Produces a single cache age data object, representing the entire node,
- /// and adds it to the list.
- void GetLineAccessTimes(CacheAgeList &ages)
- {
- ages.push_back(CacheAgeData(last_access, start, data.size()));
- }
-
-
- /// @brief Return true if this is the line to remove.
- /// @param line_id Index of the block the cache node to remove starts at.
- /// @return Returns true if this is the cache block to remove.
- ///
- /// This function won't actually delete anything, instead it is the responsibility
- /// of the caller to delete the cache node if this function returns true.
- bool KillLine(unsigned long line_id)
- {
- return start == line_id;
- }
-
-
- /// @brief Constructor, derives FFT and calculates frequency-power data.
- /// @param provider Audio provider to get audio from.
- /// @param _start Index of first block to calculate data for.
- /// @param _length Number of blocks to calculate data for.
- /// @param _overlaps Number of lines to calculate per block.
- FinalSpectrumCache(AudioProvider *provider, unsigned long _start, unsigned long _length, unsigned int _overlaps)
- {
- start = _start;
- length = _length;
- overlaps = _overlaps;
-
- if (overlaps < 1) overlaps = 1;
- // Add an upper limit to number of overlaps or trust user to do sane things?
- // Any limit should probably be a function of length
-
- assert(length > 2);
-
- // First fill the data vector with blanks
- // Both start and end are included in the range stored, so we have end-start+1 elements
- data.resize(length*overlaps, null_line);
-
- unsigned int overlap_offset = line_length / overlaps * 2; // FIXME: the result seems weird/wrong without this factor 2, but why?
-
- FFT fft; // Use FFTW instead? A wavelet?
-
- for (unsigned int overlap = 0; overlap < overlaps; ++overlap) {
- // Start sample number of the next line calculated
- // line_length is half of the number of samples used to calculate a line, since half of the output from
- // a Fourier transform of real data is redundant, and not interesting for the purpose of creating
- // a frequency/power spectrum.
- int64_t sample = start * line_length*2 + overlap*overlap_offset;
-
- long len = length;
-#ifdef _OPENMP
-#pragma omp parallel shared(overlap,len)
-#endif
- {
- short *raw_sample_data = new short[line_length*2];
- float *sample_data = new float[line_length*2];
- float *out_r = new float[line_length*2];
- float *out_i = new float[line_length*2];
-
-#ifdef _OPENMP
-#pragma omp for
-#endif
- for (long i = 0; i < len; ++i) {
- // Initialize
- sample = start * line_length*2 + overlap*overlap_offset + i*line_length*2;
-
- provider->GetAudio(raw_sample_data, sample, line_length*2);
- for (size_t j = 0; j < line_length; ++j) {
- sample_data[j*2] = (float)raw_sample_data[j*2];
- sample_data[j*2+1] = (float)raw_sample_data[j*2+1];
- }
-
- fft.Transform(line_length*2, sample_data, out_r, out_i);
-
- CacheLine &line = data[i + length*overlap];
- for (size_t j = 0; j < line_length; ++j) {
- line[j] = sqrt(out_r[j]*out_r[j] + out_i[j]*out_i[j]);
- }
-
- //sample += line_length*2;
- }
-
- delete[] raw_sample_data;
- delete[] sample_data;
- delete[] out_r;
- delete[] out_i;
- }
- }
- }
-
-
- /// @brief Destructor, does nothing.
- ///
- /// All data is managed by C++ types and gets deleted when those types'
- /// destructors are implicitly run.
- virtual ~FinalSpectrumCache()
- {
- }
-
-};
-
-
-/// @class IntermediateSpectrumCache
-/// @brief Intermediate node in the spectrum cache tree.
-///
-/// References further nodes in the spectrum cache tree and delegates operations to them.
-class IntermediateSpectrumCache : public AudioSpectrumCache {
-private:
-
- /// The child-nodes in the cache tree.
- std::vector sub_caches;
-
- unsigned long start, ///< DOCME
- length, ///< DOCME
- subcache_length; ///< DOCME
- unsigned int overlaps; ///< Number of overlaps used.
- bool subcaches_are_final; ///< Are the children leaf nodes?
- int depth; ///< How deep is this in the tree.
-
- /// Audio provider to pass on to child nodes.
- AudioProvider *provider;
-
-public:
-
- /// @brief Delegate line retrieval to a child node.
- /// @param i Index of the block to get the line from.
- /// @param overlap Index of the overlap in the block to get the line for.
- /// @param created [out] Set to true if the data had to be calculated, false if the data
- /// was found in cache.
- /// @param access_time Timestamp to mark the cache data as accessed at.
- /// @return Returns a reference to the frequency-power data requested.
- ///
- /// Will create the required child node if it doesn't exist yet.
- CacheLine &GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time)
- {
- if (i >= start && i-start <= length) {
- // Determine which sub-cache this line resides in
- size_t subcache = (i-start) / subcache_length;
- assert(subcache < sub_caches.size());
-
- if (!sub_caches[subcache]) {
- created = true;
- if (subcaches_are_final) {
- sub_caches[subcache] = new FinalSpectrumCache(provider, start+subcache*subcache_length, subcache_length, overlaps);
- } else {
- sub_caches[subcache] = new IntermediateSpectrumCache(provider, start+subcache*subcache_length, subcache_length, overlaps, depth+1);
- }
- }
-
- return sub_caches[subcache]->GetLine(i, overlap, created, access_time);
- } else {
- return null_line;
- }
- }
-
-
- /// @brief Iterate all direct children and return the sum of their managed line count.
- /// @return Returns the sum of the managed line count of all children.
- size_t GetManagedLineCount()
- {
- size_t res = 0;
- for (size_t i = 0; i < sub_caches.size(); ++i) {
- if (sub_caches[i])
- res += sub_caches[i]->GetManagedLineCount();
- }
- return res;
- }
-
-
- /// @brief Get access time data for all child nodes in cache tree.
- /// @param ages [in,out] List for child nodes to add their data to.
- void GetLineAccessTimes(CacheAgeList &ages)
- {
- for (size_t i = 0; i < sub_caches.size(); ++i) {
- if (sub_caches[i])
- sub_caches[i]->GetLineAccessTimes(ages);
- }
- }
-
-
- /// @brief Remove block with given index from cache.
- /// @param line_id Index of the block the cache node to remove starts at.
- /// @return Returns true if this node has no more live childs, false if
- /// there is a least one line child.
- ///
- /// Iterates the child nodes, calls the method recursively on all live
- /// nodes, deletes any node returning true, and counts number of nodes
- /// still alive, returning true if any are alive.
- bool KillLine(unsigned long line_id)
- {
- int sub_caches_left = 0;
- for (size_t i = 0; i < sub_caches.size(); ++i) {
- if (sub_caches[i]) {
- if (sub_caches[i]->KillLine(line_id)) {
- delete sub_caches[i];
- sub_caches[i] = 0;
- } else {
- sub_caches_left++;
- }
- }
- }
- return sub_caches_left == 0;
- }
-
-
- /// @brief Constructor.
- /// @param _provider Audio provider to pass to child nodes.
- /// @param _start Index of first block to manage.
- /// @param _length Number of blocks to manage.
- /// @param _overlaps Number of lines per block.
- /// @param _depth Number of levels in the tree above this node.
- ///
- /// Determine how many sub-caches are required, how big they
- /// should be and allocates memory to store their pointers.
- IntermediateSpectrumCache(AudioProvider *_provider, unsigned long _start, unsigned long _length, unsigned int _overlaps, int _depth)
- {
- provider = _provider;
- start = _start;
- length = _length;
- overlaps = _overlaps;
- depth = _depth;
-
- // FIXME: this calculation probably needs tweaking
- int num_subcaches = 1;
- unsigned long tmp = length;
- while (tmp > 0) {
- tmp /= 16;
- num_subcaches *= 2;
- }
- subcache_length = length / (num_subcaches-1);
-
- subcaches_are_final = num_subcaches <= 4;
-
- sub_caches.resize(num_subcaches, 0);
- }
-
-
- /// @brief Destructor, deletes all still-live sub caches.
- virtual ~IntermediateSpectrumCache()
- {
- for (size_t i = 0; i < sub_caches.size(); ++i)
- if (sub_caches[i])
- delete sub_caches[i];
- }
-
-};
-
-
-
-
-/// @class AudioSpectrumCacheManager
-/// @brief Manages a frequency-power cache tree.
-///
-/// The primary task of this class is to manage the amount of memory consumed by
-/// the cache and delete items when it grows too large.
-class AudioSpectrumCacheManager {
-private:
-
- /// Root node of the cache tree.
- IntermediateSpectrumCache *cache_root;
-
- unsigned long cache_hits, ///< Number of times the cache was used to retrieve data
- cache_misses; ///< Number of times data had to be calculated
-
- /// Current time, used for cache aging purposes.
- AudioSpectrumCache::CacheAccessTime cur_time;
-
- /// Maximum number of lines to keep in cache.
- unsigned long max_lines_cached;
-
-public:
-
- /// @brief Wrapper around cache tree, to get frequency-power data
- /// @param i Block to get data from.
- /// @param overlap Line in block to get data from.
- /// @return Returns a reference to the requested line.
- ///
- AudioSpectrumCache::CacheLine &GetLine(unsigned long i, unsigned int overlap)
- {
- bool created = false;
- AudioSpectrumCache::CacheLine &res = cache_root->GetLine(i, overlap, created, cur_time++);
- if (created)
- cache_misses++;
- else
- cache_hits++;
- return res;
- }
-
-
- /// @brief Remove old data from the cache.
- ///
- /// Ages the cache by finding the least recently accessed data and removing cache data
- /// until the total number of lines stored in the tree is less than the maximum.
- void Age()
- {
- LOG_D("audio/renderer/spectrum/cache") << "stats: hits=" << cache_hits << " misses=" << cache_misses << " misses%=" << cache_misses/float(cache_hits+cache_misses)*100 << " managed lines=" << cache_root->GetManagedLineCount() << "(max=" << max_lines_cached << ")";
-
- // 0 means no limit
- if (max_lines_cached == 0)
- return;
- // No reason to proceed with complicated stuff if the count is too small
- // (FIXME: does this really pay off?)
- if (cache_root->GetManagedLineCount() < max_lines_cached)
- return;
-
- // Get and sort ages
- AudioSpectrumCache::CacheAgeList ages;
- cache_root->GetLineAccessTimes(ages);
- std::sort(ages.begin(), ages.end());
-
- // Number of lines we have found used so far
- // When this exceeds max_lines_caches go into kill-mode
- unsigned long cumulative_lines = 0;
- // Run backwards through the line age list (the most recently accessed items are at end)
- AudioSpectrumCache::CacheAgeList::reverse_iterator it = ages.rbegin();
-
- // Find the point where we have too many lines cached
- while (cumulative_lines < max_lines_cached) {
- if (it == ages.rend()) {
- LOG_D("audio/renderer/spectrum/") << "done aging did not exeed max_lines_cached";
- return;
- }
- cumulative_lines += it->num_lines;
- ++it;
- }
-
- // By here, we have exceeded max_lines_cached so backtrack one
- --it;
-
- // And now start cleaning up
- for (; it != ages.rend(); ++it) {
- cache_root->KillLine(it->first_line);
- }
-
- LOG_D("audio/renderer/spectrum/") << "done aging, managed lines now=" << cache_root->GetManagedLineCount() << " (max=" << max_lines_cached << ")";
- assert(cache_root->GetManagedLineCount() < max_lines_cached);
- }
-
+/// Allocates blocks of derived data for the audio spectrum
+struct AudioSpectrumCacheBlockFactory {
+ /// Pointer back to the owning spectrum renderer
+ AudioSpectrumRenderer *spectrum;
/// @brief Constructor
- /// @param provider Audio provider to pass to cache tree nodes.
- /// @param line_length Number of audio samples to use per block.
- /// @param num_lines Number of blocks to produce in total from the audio.
- /// @param num_overlaps Number of overlaps per block.
- ///
- /// Initialises the cache tree root and calculates the maximum number of cache lines
- /// to keep based on the Audio Spectrum Memory Max configuration setting.
- AudioSpectrumCacheManager(AudioProvider *provider, unsigned long line_length, unsigned long num_lines, unsigned int num_overlaps)
- {
- cache_hits = cache_misses = 0;
- cur_time = 0;
- cache_root = new IntermediateSpectrumCache(provider, 0, num_lines, num_overlaps, 0);
+ /// @param s The owning spectrum renderer
+ AudioSpectrumCacheBlockFactory(AudioSpectrumRenderer *s) : spectrum(s) { }
- // option is stored in megabytes, but we want number of bytes
- unsigned long max_cache_size = OPT_GET("Audio/Renderer/Spectrum/Memory Max")->GetInt();
- // It can't go too low
- if (max_cache_size < 5) max_cache_size = 128;
- max_cache_size *= 1024 * 1024;
- unsigned long line_size = sizeof(AudioSpectrumCache::CacheLine::value_type) * line_length;
- max_lines_cached = max_cache_size / line_size;
+ /// @brief Allocate and fill a data block
+ /// @param i Index of the block to produce data for
+ /// @return Newly allocated and filled block
+ ///
+ /// The filling is delegated to the spectrum renderer
+ float *ProduceBlock(size_t i)
+ {
+ float *res = new float[((size_t)1)<derivation_size];
+ spectrum->FillBlock(i, res);
+ return res;
}
-
- /// @brief Destructor, deletes the cache tree root node.
- ~AudioSpectrumCacheManager()
+ /// @brief De-allocate a cache block
+ /// @param block The block to dispose of
+ void DisposeBlock(float *block)
{
- delete cache_root;
+ delete[] block;
+ }
+
+ /// @brief Calculate the in-memory size of a spec
+ /// @return The size in bytes of a spectrum cache block
+ size_t GetBlockSize() const
+ {
+ return sizeof(float) << spectrum->derivation_size;
}
};
-// AudioSpectrum, documented in .h file
+/// @brief Cache for audio spectrum frequency-power data
+class AudioSpectrumCache
+ : public DataBlockCache {
+public:
+ AudioSpectrumCache(size_t block_count, AudioSpectrumRenderer *renderer)
+ : DataBlockCache(
+ block_count, AudioSpectrumCacheBlockFactory(renderer))
+ {
+ }
+};
-AudioSpectrum::AudioSpectrum(AudioProvider *_provider)
+
+
+AudioSpectrumRenderer::AudioSpectrumRenderer()
+: AudioRendererBitmapProvider()
+, cache(0)
+, colors_normal(12)
+, colors_selected(12)
+, derivation_size(8)
+, derivation_dist(8)
+, audio_scratch(0)
+#ifdef WITH_FFTW
+, dft_plan(0)
+, dft_input(0)
+, dft_output(0)
+#else
+, fft_scratch(0)
+#endif
{
- provider = _provider;
-
- // Determine the quality of the spectrum rendering based on an index
- int quality_index = OPT_GET("Audio/Renderer/Spectrum/Quality")->GetInt();
- if (quality_index < 0) quality_index = 0;
- if (quality_index > 5) quality_index = 5; // no need to go freaking insane
-
- // Line length determines the balance between resolution in the time and frequency domains.
- // Larger line length gives better resolution in frequency domain,
- // smaller gives better resolution in time domain.
- // Any values uses the same amount of memory, but larger values takes (slightly) more CPU.
- // Line lengths must be powers of 2 due to the FFT algorithm.
- // 2^8 is a good compromise between time and frequency domain resolution, any smaller
- // gives an unreasonably low resolution in the frequency domain.
-
- // Increasing the number of overlaps gives better resolution in the time domain.
- // Doubling the number of overlaps doubles memory and CPU use, and also
- // doubles resolution in the time domain.
-
- switch (quality_index) {
- case 0:
- // No overlaps, good comprimise between time/frequency resolution.
- // 4 bytes used per sample.
- line_length = 1<<8;
- fft_overlaps = 1;
- break;
- case 1:
- // Double frequency resolution, the resulting half time resolution
- // is countered with an overlap.
- // 8 bytes per sample.
- line_length = 1<<9;
- fft_overlaps = 2;
- break;
- case 2:
- // Resulting double resolution in both domains.
- // 16 bytes per sample.
- line_length = 1<<9;
- fft_overlaps = 4;
- break;
- case 3:
- // Double frequency and quadrouble time resolution.
- // 32 bytes per sample.
- line_length = 1<<9;
- fft_overlaps = 8;
- break;
- case 4:
- // Quadrouble resolution in both domains.
- // 64 bytes per sample.
- line_length = 1<<10;
- fft_overlaps = 16;
- break;
- case 5:
- // Eight-double resolution in both domains.
- // 256 bytes per sample.
- line_length = 1<<11;
- fft_overlaps = 64;
- break;
- default:
- throw _T("Internal error in AudioSpectrum class - impossible quality index");
- }
-
- int64_t _num_lines = provider->GetNumSamples() / line_length / 2;
- num_lines = (unsigned long)_num_lines;
-
- AudioSpectrumCache::SetLineLength(line_length);
- cache = new AudioSpectrumCacheManager(provider, line_length, num_lines, fft_overlaps);
-
- power_scale = 1;
- minband = OPT_GET("Audio/Renderer/Spectrum/Cutoff")->GetInt();
- maxband = line_length - minband * 2/3; // TODO: make this customisable?
-
- // Generate colour maps
- unsigned char *palptr = colours_normal;
- for (int i = 0; i < 256; i++) {
- //hsl_to_rgb(170 + i * 2/3, 128 + i/2, i, palptr+0, palptr+1, palptr+2); // Previous
- hsl_to_rgb((255+128-i)/2, 128 + i/2, MIN(255,2*i), palptr+0, palptr+1, palptr+2); // Icy blue
- palptr += 3;
- }
- palptr = colours_selected;
- for (int i = 0; i < 256; i++) {
- //hsl_to_rgb(170 + i * 2/3, 128 + i/2, i*3/4+64, palptr+0, palptr+1, palptr+2);
- hsl_to_rgb((255+128-i)/2, 128 + i/2, MIN(255,3*i/2+64), palptr+0, palptr+1, palptr+2); // Icy blue
- palptr += 3;
- }
+ colors_normal.InitIcyBlue_Normal();
+ colors_selected.InitIcyBlue_Selected();
}
-AudioSpectrum::~AudioSpectrum()
+AudioSpectrumRenderer::~AudioSpectrumRenderer()
+{
+ // This sequence will clean up
+ provider = 0;
+ RecreateCache();
+}
+
+
+void AudioSpectrumRenderer::RecreateCache()
{
delete cache;
+ delete[] audio_scratch;
+ cache = 0;
+ audio_scratch = 0;
+
+#ifdef WITH_FFTW
+ if (dft_plan)
+ {
+ fftw_destroy_plan(dft_plan);
+ fftw_free(dft_input);
+ fftw_free(dft_output);
+ dft_plan = 0;
+ dft_input = 0;
+ dft_output = 0;
+ }
+#else
+ delete[] fft_scratch;
+ fft_scratch = 0;
+#endif
+
+ if (provider)
+ {
+ size_t block_count = (size_t)((provider->GetNumSamples() + (size_t)(1<> derivation_dist);
+ cache = new AudioSpectrumCache(block_count, this);
+
+#ifdef WITH_FFTW
+ dft_input = (double*)fftw_malloc(sizeof(double) * (2<GetLine(baseline, overlap);
- ++overlap;
- if (overlap >= fft_overlaps) {
- overlap = 0;
- ++baseline;
- }
-
- // Apply a "compressed" scaling to the signal power
- for (unsigned int j = 0; j < line_length; j++) {
- // First do a simple linear scale power calculation -- 8 gives a reasonable default scaling
- power[j] = line[j] * upscale;
- if (power[j] > maxpower * 2/3) {
- double p = power[j] - twothirdmaxpower;
- p = log(p) * onethirdmaxpower / logoverscale;
- power[j] = p + twothirdmaxpower;
- }
- }
-
-/// @internal Macro that stores pixel data, depends on local variables in AudioSpectrum::RenderRange
-#define WRITE_PIXEL \
- if (intensity < 0) intensity = 0; \
- if (intensity > 255) intensity = 255; \
- img[((imgheight-y-1)*imgpitch+x)*3 + 0] = palette[intensity*3+0]; \
- img[((imgheight-y-1)*imgpitch+x)*3 + 1] = palette[intensity*3+1]; \
- img[((imgheight-y-1)*imgpitch+x)*3 + 2] = palette[intensity*3+2];
-
- // Handle horizontal expansion
- int next_line_imgcol = imgleft + imgwidth * (i - first_line + 1) / (last_line - first_line + 1);
- if (next_line_imgcol >= imgpitch)
- next_line_imgcol = imgpitch-1;
-
- for (int x = imgcol; x <= next_line_imgcol; ++x) {
-
- // Decide which rendering algo to use
- if (maxband - minband > imgheight) {
- // more than one frequency sample per pixel (vertically compress data)
- // pick the largest value per pixel for display
-
- // Iterate over pixels, picking a range of samples for each
- for (int y = 0; y < imgheight; ++y) {
- int sample1 = MAX(0,maxband * y/imgheight + minband);
- int sample2 = MIN(signed(line_length-1),maxband * (y+1)/imgheight + minband);
- float maxval = 0;
- for (int samp = sample1; samp <= sample2; samp++) {
- if (power[samp] > maxval) maxval = power[samp];
- }
- int intensity = int(256 * maxval / maxpower);
- WRITE_PIXEL
- }
- }
- else {
- // less than one frequency sample per pixel (vertically expand data)
- // interpolate between pixels
- // can also happen with exactly one sample per pixel, but how often is that?
-
- // Iterate over pixels, picking the nearest power values
- for (int y = 0; y < imgheight; ++y) {
- float ideal = (float)(y+1.)/imgheight * maxband;
- float sample1 = power[(int)floor(ideal)+minband];
- float sample2 = power[(int)ceil(ideal)+minband];
- float frac = ideal - floor(ideal);
- int intensity = int(((1-frac)*sample1 + frac*sample2) / maxpower * 256);
- WRITE_PIXEL
- }
- }
- }
-
-/// @internal The WRITE_PIXEL macro is only defined inside AudioSpectrum::RenderRange
-#undef WRITE_PIXEL
+void AudioSpectrumRenderer::SetResolution(size_t _derivation_size, size_t _derivation_dist)
+{
+ if (derivation_dist != _derivation_dist)
+ {
+ derivation_dist = _derivation_dist;
+ if (cache)
+ cache->Age(0);
}
- delete[] power;
-
- cache->Age();
+ if (derivation_size != _derivation_size)
+ {
+ derivation_size = _derivation_size;
+ RecreateCache();
+ }
}
-void AudioSpectrum::SetScaling(float _power_scale)
+void AudioSpectrumRenderer::FillBlock(size_t block_index, float *block)
{
- power_scale = _power_scale;
+ assert(cache);
+ assert(block);
+
+ int64_t first_sample = ((int64_t)block_index) << derivation_dist;
+ provider->GetAudio(audio_scratch, first_sample, 2 << derivation_size);
+
+#ifdef WITH_FFTW
+ // Convert audio data to float range [-1;+1)
+ for (size_t si = 0; si < (size_t)(2< 0; --si)
+ {
+ *block++ = log10( sqrt(o[0][0] * o[0][0] + o[0][1] * o[0][1]) * scale_factor + 1 );
+ o++;
+ }
+#else
+ float *fft_input = fft_scratch;
+ float *fft_real = fft_scratch + (2 << derivation_size);
+ float *fft_imag = fft_scratch + (4 << derivation_size);
+
+ // Convert audio data to float range [-1;+1)
+ for (size_t si = 0; si < (size_t)(2< 0; --si)
+ {
+ // With x in range [0;1], log10(x*9+1) will also be in range [0;1],
+ // although the FFT output can apparently get greater magnitudes than 1
+ // despite the input being limited to [-1;+1).
+ *block++ = log10( sqrt(*fft_real * *fft_real + *fft_imag * *fft_imag) * scale_factor + 1 );
+ fft_real++; fft_imag++;
+ }
+#endif
}
+void AudioSpectrumRenderer::Render(wxBitmap &bmp, int start, bool selected)
+{
+ if (!cache)
+ return;
+
+ assert(bmp.IsOk());
+ assert(bmp.GetDepth() == 24);
+
+ int end = start + bmp.GetWidth();
+
+ assert(start >= 0);
+ assert(end >= 0);
+ assert(end >= start);
+
+ // Prepare an image buffer to write
+ wxImage img(bmp.GetSize());
+ unsigned char *imgdata = img.GetData();
+ ptrdiff_t stride = img.GetWidth()*3;
+ int imgheight = img.GetHeight();
+
+ AudioColorScheme *pal = selected ? &colors_selected : &colors_normal;
+
+ /// @todo Make minband and maxband configurable
+ int minband = 0;
+ int maxband = 1 << derivation_size;
+
+ // ax = absolute x, absolute to the virtual spectrum bitmap
+ for (int ax = start; ax < end; ++ax)
+ {
+ // Derived audio data
+ size_t block_index = (size_t)(ax * pixel_samples) >> derivation_dist;
+ float *power = cache->Get(block_index);
+
+ // Prepare bitmap writing
+ unsigned char *px = imgdata + (imgheight-1) * stride + (ax - start) * 3;
+
+ // Scale up or down vertically?
+ if (imgheight > 1<= imgdata);
+ assert(px < imgdata + imgheight*stride);
+ float ideal = (float)(y+1.)/imgheight * (maxband-minband) + minband;
+ float sample1 = power[(int)floor(ideal)+minband];
+ float sample2 = power[(int)ceil(ideal)+minband];
+ float frac = ideal - floor(ideal);
+ float val = (1-frac)*sample1 + frac*sample2;
+ pal->map(val*amplitude_scale, px);
+ px -= stride;
+ }
+ }
+ else
+ {
+ // Pick greatest
+ for (int y = 0; y < imgheight; ++y)
+ {
+ assert(px >= imgdata);
+ assert(px < imgdata + imgheight*stride);
+ int sample1 = std::max(0, maxband * y/imgheight + minband);
+ int sample2 = std::min((1< maxval) maxval = power[samp];
+ pal->map(maxval*amplitude_scale, px);
+ px -= stride;
+ }
+ }
+ }
+
+ wxBitmap tmpbmp(img);
+ wxMemoryDC targetdc(bmp);
+ targetdc.DrawBitmap(tmpbmp, 0, 0);
+}
+void AudioSpectrumRenderer::RenderBlank(wxDC &dc, const wxRect &rect, bool selected)
+{
+ // Get the colour of silence
+ AudioColorScheme *pal = selected ? &colors_selected : &colors_normal;
+ unsigned char color_raw[4];
+ pal->map(0.0, color_raw);
+ wxColour col(color_raw[0], color_raw[1], color_raw[2]);
+
+ dc.SetBrush(wxBrush(col));
+ dc.SetPen(wxPen(col));
+ dc.DrawRectangle(rect);
+}
+
+
+void AudioSpectrumRenderer::AgeCache(size_t max_size)
+{
+ if (cache)
+ cache->Age(max_size);
+}
+
diff --git a/aegisub/src/audio_renderer_spectrum.h b/aegisub/src/audio_renderer_spectrum.h
index e89cedc96..c9e9843f0 100644
--- a/aegisub/src/audio_renderer_spectrum.h
+++ b/aegisub/src/audio_renderer_spectrum.h
@@ -1,5 +1,4 @@
-// Copyright (c) 2005, 2006, Rodrigo Braz Monteiro
-// Copyright (c) 2006, 2007, Niels Martin Hansen
+// Copyright (c) 2009, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -40,65 +39,102 @@
#include
#endif
-class AudioProvider;
-
-// Specified and implemented in cpp file, interface is private to spectrum code
-class AudioSpectrumCacheManager;
+#ifdef WITH_FFTW
+#include
+#endif
-/// @class AudioSpectrum
+
+// Specified and implemented in cpp file, to avoid pulling in too much
+// complex template code in this header.
+class AudioSpectrumCache;
+struct AudioSpectrumCacheBlockFactory;
+
+
+
+/// @class AudioSpectrumRenderer
/// @brief Render frequency-power spectrum graphs for audio data.
///
-/// Renders frequency-power spectrum graphs of PCM audio data using a fast fourier transform
-/// to derive the data. The frequency-power data are cached to avoid re-computing them
-/// frequently, and the cache size is limited by a configuration setting.
-///
-/// The spectrum image is rendered to a 32 bit RGB bitmap. Power data is scaled linearly
-/// and not logarithmically, since the rendering is done with limited precision, but
-/// an amplification factor can be specified to see different ranges.
-class AudioSpectrum {
-private:
+/// Renders frequency-power spectrum graphs of PCM audio data using a derivation function
+/// such as the fast fourier transform.
+class AudioSpectrumRenderer : public AudioRendererBitmapProvider {
+ friend struct AudioSpectrumCacheBlockFactory;
/// Internal cache management for the spectrum
- AudioSpectrumCacheManager *cache;
+ AudioSpectrumCache *cache;
/// Colour table used for regular rendering
- unsigned char colours_normal[256*3];
+ AudioColorScheme colors_normal;
/// Colour table used for rendering the audio selection
- unsigned char colours_selected[256*3];
+ AudioColorScheme colors_selected;
- /// The audio provider to use as source
- AudioProvider *provider;
+ /// Binary logarithm of number of samples to use in deriving frequency-power data
+ size_t derivation_size;
- unsigned long line_length; ///< Number of frequency components per line (half of number of samples)
- unsigned long num_lines; ///< Number of lines needed for the audio
- unsigned int fft_overlaps; ///< Number of overlaps used in FFT
- float power_scale; ///< Amplification of displayed power
- int minband; ///< Smallest frequency band displayed
- int maxband; ///< Largest frequency band displayed
+ /// Binary logarithm of number of samples between the start of derivations
+ size_t derivation_dist;
+
+ /// @brief Reset in response to changing audio provider
+ ///
+ /// Overrides the OnSetProvider event handler in the base class, to reset things
+ /// when the audio provider is changed.
+ void OnSetProvider();
+
+ /// @brief Recreates the cache
+ ///
+ /// To be called when the number of blocks in cache might have changed,
+ // eg. new audio provider or new resolution.
+ void RecreateCache();
+
+ /// @brief Fill a block with frequency-power data for a time range
+ /// @param block_index Index of the block to fill data for
+ /// @param[out] block Address to write the data to
+ void FillBlock(size_t block_index, float *block);
+
+#ifdef WITH_FFTW
+ /// FFTW plan data
+ fftw_plan dft_plan;
+ /// Pre-allocated input array for FFTW
+ double *dft_input;
+ /// Pre-allocated output array for FFTW
+ fftw_complex *dft_output;
+#else
+ /// Pre-allocated scratch area for doing FFT derivations
+ float *fft_scratch;
+#endif
+
+ /// Pre-allocated scratch area for storing raw audio data
+ int16_t *audio_scratch;
public:
/// @brief Constructor
- /// @param _provider Audio provider to render spectrum data for.
- ///
- /// Reads configuration data for the spectrum display and initialises itself following that.
- AudioSpectrum(AudioProvider *_provider);
+ AudioSpectrumRenderer();
+
/// @brief Destructor
- ~AudioSpectrum();
+ virtual ~AudioSpectrumRenderer();
- /// @brief Render a range of audio spectrum to a bitmap buffer.
- /// @param range_start First audio sample in the range to render.
- /// @param range_end Last audio sample in the range to render.
- /// @param selected Use the alternate colour palette?
- /// @param img Pointer to 32 bit RGBX data
- /// @param imgleft Offset from left edge of bitmap to render to, in pixels
- /// @param imgwidth Width of bitmap to render, in pixels
- /// @param imgpitch Offset from one scanline to the next in the bitmap, in bytes
- /// @param imgheight Number of lines in the bitmap
- void RenderRange(int64_t range_start, int64_t range_end, bool selected, unsigned char *img, int imgleft, int imgwidth, int imgpitch, int imgheight);
+ /// @brief Render a range of audio spectrum
+ /// @param bmp [in,out] Bitmap to render into, also carries lenght information
+ /// @param start First column of pixel data in display to render
+ /// @param selected Whether to use the alternate colour scheme
+ void Render(wxBitmap &bmp, int start, bool selected);
- /// @brief Set the amplification to use when rendering.
- /// @param _power_scale Amplification factor to use.
- void SetScaling(float _power_scale);
+ /// @brief Render blank area
+ void RenderBlank(wxDC &dc, const wxRect &rect, bool selected);
+
+ /// @brief Set the derivation resolution
+ /// @param derivation_size Binary logarithm of number of samples to use in deriving frequency-power data
+ /// @param derivation_dist Binary logarithm of number of samples between the start of derivations
+ ///
+ /// The derivations done will each use 2^derivation_size audio samples and at a distance
+ /// of 2^derivation_dist samples.
+ ///
+ /// The derivation distance must be smaller than or equal to the size. If the distance
+ /// is specified too large, it will be clamped to the size.
+ void SetResolution(size_t derivation_size, size_t derivation_dist);
+
+ /// @brief Cleans up the cache
+ /// @param max_size Maximum size in bytes for the cache
+ void AgeCache(size_t max_size);
};
diff --git a/aegisub/src/audio_renderer_waveform.cpp b/aegisub/src/audio_renderer_waveform.cpp
new file mode 100644
index 000000000..4e77e043a
--- /dev/null
+++ b/aegisub/src/audio_renderer_waveform.cpp
@@ -0,0 +1,178 @@
+// Copyright (c) 2010, Niels Martin Hansen
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+//
+// $Id$
+
+/// @file audio_renderer_waveform.cpp
+/// @ingroup audio_ui
+///
+/// Render a waveform display of PCM audio data
+
+#include "config.h"
+
+#ifndef AGI_PRE
+#include
+
+#include
+#endif
+
+#include "block_cache.h"
+#include "include/aegisub/audio_provider.h"
+#include "audio_colorscheme.h"
+#include "audio_renderer.h"
+#include "audio_renderer_waveform.h"
+#include "colorspace.h"
+
+#undef min
+#undef max
+
+
+
+AudioWaveformRenderer::AudioWaveformRenderer()
+: AudioRendererBitmapProvider()
+, colors_normal(6)
+, colors_selected(6)
+, audio_buffer(0)
+{
+ colors_normal.InitIcyBlue_Normal();
+ colors_selected.InitIcyBlue_Selected();
+}
+
+
+AudioWaveformRenderer::~AudioWaveformRenderer()
+{
+ delete[] audio_buffer;
+}
+
+
+void AudioWaveformRenderer::Render(wxBitmap &bmp, int start, bool selected)
+{
+ wxMemoryDC dc(bmp);
+ wxRect rect(wxPoint(0, 0), bmp.GetSize());
+ int midpoint = rect.height / 2;
+
+ AudioColorScheme *pal = selected ? &colors_selected : &colors_normal;
+
+ // Fill the background
+ dc.SetBrush(wxBrush(pal->get(0.0f)));
+ dc.SetPen(*wxTRANSPARENT_PEN);
+ dc.DrawRectangle(rect);
+
+ // Make sure we've got a buffer to fill with audio data
+ if (!audio_buffer)
+ {
+ // Buffer for one pixel strip of audio
+ size_t buffer_needed = pixel_samples * provider->GetChannels() * provider->GetSampleRate() * provider->GetBytesPerSample();
+ audio_buffer = new char[buffer_needed];
+ }
+
+ int64_t cur_sample = start * pixel_samples;
+
+ assert(provider->GetBytesPerSample() == 2);
+ assert(provider->GetChannels() == 1);
+
+ wxPen pen_peaks(wxPen(pal->get(0.4f)));
+ wxPen pen_avgs(wxPen(pal->get(0.7f)));
+
+ for (int x = 0; x < rect.width; ++x)
+ {
+ provider->GetAudio(audio_buffer, cur_sample, pixel_samples);
+ cur_sample += pixel_samples;
+
+ int peak_min = 0, peak_max = 0;
+ int64_t avg_min_accum = 0, avg_max_accum = 0;
+ const int16_t *aud = (const int16_t *)audio_buffer;
+ for (int si = pixel_samples; si > 0; --si, ++aud)
+ {
+ if (*aud > 0)
+ {
+ peak_max = std::max(peak_max, (int)*aud);
+ avg_max_accum += *aud;
+ }
+ else
+ {
+ peak_min = std::min(peak_min, (int)*aud);
+ avg_min_accum += *aud;
+ }
+ }
+
+ // midpoint is half height
+ peak_min = std::max((int)(peak_min * amplitude_scale * midpoint) / 0x8000, -midpoint);
+ peak_max = std::min((int)(peak_max * amplitude_scale * midpoint) / 0x8000, midpoint);
+ int avg_min = std::max((int)(avg_min_accum * amplitude_scale * midpoint / pixel_samples) / 0x8000, -midpoint);
+ int avg_max = std::min((int)(avg_max_accum * amplitude_scale * midpoint / pixel_samples) / 0x8000, midpoint);
+
+ dc.SetPen(pen_peaks);
+ dc.DrawLine(x, midpoint - peak_max, x, midpoint - peak_min);
+ dc.SetPen(pen_avgs);
+ dc.DrawLine(x, midpoint - avg_max, x, midpoint - avg_min);
+ }
+
+ // Horizontal zero-point line
+ dc.SetPen(wxPen(pal->get(1.0f)));
+ dc.DrawLine(0, midpoint, rect.width, midpoint);
+}
+
+
+void AudioWaveformRenderer::RenderBlank(wxDC &dc, const wxRect &rect, bool selected)
+{
+ AudioColorScheme *pal = selected ? &colors_selected : &colors_normal;
+
+ wxColor line(pal->get(1.0));
+ wxColor bg(pal->get(0.0));
+
+ // Draw the line as background above and below, and line in the middle, to avoid
+ // overdraw flicker (the common theme in all of audio display direct drawing).
+ int halfheight = rect.height / 2;
+
+ dc.SetBrush(wxBrush(bg));
+ dc.SetPen(*wxTRANSPARENT_PEN);
+ dc.DrawRectangle(rect.x, rect.y, rect.width, halfheight);
+ dc.DrawRectangle(rect.x, rect.y + halfheight + 1, rect.width, rect.height - halfheight - 1);
+
+ dc.SetPen(wxPen(line));
+ dc.DrawLine(rect.x, rect.y+halfheight, rect.x+rect.width, rect.y+halfheight);
+}
+
+
+
+void AudioWaveformRenderer::OnSetProvider()
+{
+ delete[] audio_buffer;
+ audio_buffer = 0;
+}
+
+
+void AudioWaveformRenderer::OnSetSamplesPerPixel()
+{
+ delete[] audio_buffer;
+ audio_buffer = 0;
+}
+
+
diff --git a/aegisub/src/audio_renderer_waveform.h b/aegisub/src/audio_renderer_waveform.h
new file mode 100644
index 000000000..5abf20b20
--- /dev/null
+++ b/aegisub/src/audio_renderer_waveform.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2010, Niels Martin Hansen
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+//
+// $Id$
+
+/// @file audio_renderer_waveform.h
+/// @see audio_renderer_waveform.cpp
+/// @ingroup audio_ui
+///
+/// Render a waveform display of PCM audio data
+
+#include
+
+
+
+class AudioWaveformRenderer : public AudioRendererBitmapProvider {
+ /// Colour table used for regular rendering
+ AudioColorScheme colors_normal;
+
+ /// Colour table used for rendering the audio selection
+ AudioColorScheme colors_selected;
+
+ /// Pre-allocated buffer for audio fetched from provider
+ char *audio_buffer;
+
+protected:
+ virtual void OnSetProvider();
+ virtual void OnSetSamplesPerPixel();
+
+public:
+ /// @brief Constructor
+ AudioWaveformRenderer();
+
+ /// @brief Destructor
+ virtual ~AudioWaveformRenderer();
+
+ /// @brief Render a range of audio waveform
+ /// @param bmp [in,out] Bitmap to render into, also carries lenght information
+ /// @param start First column of pixel data in display to render
+ /// @param selected Whether to use the alternate colour scheme
+ void Render(wxBitmap &bmp, int start, bool selected);
+
+ /// @brief Render blank area
+ void RenderBlank(wxDC &dc, const wxRect &rect, bool selected);
+
+ /// @brief Cleans up the cache
+ /// @param max_size Maximum size in bytes for the cache
+ ///
+ /// Does nothing for waveform renderer, since it does not have a backend cache
+ void AgeCache(size_t max_size) { }
+};
diff --git a/aegisub/src/audio_timing.h b/aegisub/src/audio_timing.h
new file mode 100644
index 000000000..a1d0dc39e
--- /dev/null
+++ b/aegisub/src/audio_timing.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2010, Niels Martin Hansen
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+//
+// $Id$
+
+/// @file audio_timing.h
+/// @brief Construction-functions for timing controller objects
+/// @ingroup audio_ui
+
+
+
+class AssDialogue;
+class AudioController;
+
+
+/// @class AudioTimingController
+/// @brief Base class for objects controlling audio timing
+///
+/// There is just one active audio timing controller at a time per audio controller.
+/// The timing controller manages the timing mode and supplies markers that can be
+/// manupulated to the audio display, as well as the current selection.
+///
+/// The timing controller must then be sent the marker drag events as well as clicks
+/// in empty areas of the audio display.
+class AudioTimingController : public AudioMarkerProvider {
+public:
+ /// @brief Get any warning message to show in the audio display
+ /// @return The warning message to show, may be empty if there is none
+ virtual wxString GetWarningMessage() const = 0;
+
+ /// @brief Get the sample range the user is most likely to want to see for the current state
+ /// @return A sample range
+ ///
+ /// This is used for "bring working area into view" operations.
+ virtual AudioController::SampleRange GetIdealVisibleSampleRange() const = 0;
+
+ /// @brief Get the primary playback range
+ /// @return A sample range
+ ///
+ /// Get the sample range the user is most likely to want to play back currently.
+ virtual AudioController::SampleRange GetPrimaryPlaybackRange() const = 0;
+
+ /// @brief Does this timing mode have labels on the audio display?
+ /// @return True if this timing mode needs labels on the audio display.
+ ///
+ /// This is labels for things such as karaoke syllables. When labels are required, some vertical
+ /// space is set off for them in the drawing of the audio display.
+ virtual bool HasLabels() const = 0;
+
+ /// @brief Go to next timing unit
+ ///
+ /// Advances the timing controller cursor to the next timing unit, for example the next dialogue
+ /// line or the next karaoke syllable.
+ virtual void Next() = 0;
+
+ /// @brief Go to the previous timing unit
+ ///
+ /// Rewinds the timing controller to the previous timing unit.
+ virtual void Prev() = 0;
+
+ /// @brief Commit all changes
+ ///
+ /// Stores all changes permanently.
+ virtual void Commit() = 0;
+
+ /// @brief Revert all changes
+ ///
+ /// Revert all changes to the last committed state.
+ virtual void Revert() = 0;
+
+ /// @brief Determine if a position is close to a draggable marker
+ /// @param sample The audio sample index to test
+ /// @param sensitivity Distance in samples to consider markers as nearby
+ /// @return True if a marker is close by the given sample, as defined by sensitivity
+ ///
+ /// This is solely for hit-testing against draggable markers, for controlling the mouse cursor.
+ virtual bool IsNearbyMarker(int64_t sample, int sensitivity) const = 0;
+
+ /// @brief The user pressed the left button down at an empty place in the audio
+ /// @param sample The audio sample index the user clicked
+ /// @param sensitivity Distance in samples to consider existing markers
+ /// @return An audio marker or 0. If a marker is returned and the user starts dragging
+ /// the mouse after pressing down the button, the returned marker is being dragged.
+ virtual AudioMarker * OnLeftClick(int64_t sample, int sensitivity) = 0;
+
+ /// @brief The user pressed the right button down at an empty place in the audio
+ /// @param sample The audio sample index the user clicked
+ /// @param sensitivity Distance in samples to consider existing markers
+ /// @return An audio marker or 0. If a marker is returned and the user starts dragging
+ /// the mouse after pressing down the button, the returned marker is being dragged.
+ virtual AudioMarker * OnRightClick(int64_t sample, int sensitivity) = 0;
+
+ /// @brief The user dragged a timing marker
+ /// @param marker The marker being dragged
+ /// @param new_position Sample position the marker was dragged to
+ virtual void OnMarkerDrag(AudioMarker *marker, int64_t new_position) = 0;
+
+ /// @brief Destructor
+ ///
+ /// Does nothing in the base class, only present for virtual destruction.
+ virtual ~AudioTimingController() { }
+};
+
+
+
+/// @brief Create a standard dialogue audio timing controller
+/// @param audio_controller The audio controller to own the timing controller
+/// @param selection_controller The selection controller to manage the set of lines being timed
+AudioTimingController *CreateDialogueTimingController(AudioController *audio_controller, SelectionController *selection_controller);
+
diff --git a/aegisub/src/audio_timing_dialogue.cpp b/aegisub/src/audio_timing_dialogue.cpp
new file mode 100644
index 000000000..a7687073e
--- /dev/null
+++ b/aegisub/src/audio_timing_dialogue.cpp
@@ -0,0 +1,478 @@
+// Copyright (c) 2010, Niels Martin Hansen
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+//
+// $Id$
+
+/// @file audio_timing_dialogue.cpp
+/// @brief Default timing mode for dialogue subtitles
+/// @ingroup audio_ui
+
+
+#ifndef AGI_PRE
+#include
+#include
+#endif
+
+#include "ass_time.h"
+#include "ass_dialogue.h"
+#include "selection_controller.h"
+#include "audio_controller.h"
+#include "audio_timing.h"
+#include "utils.h"
+
+
+
+/// @class AudioMarkerDialogueTiming
+/// @brief AudioMarker implementation for AudioTimingControllerDialogue
+///
+/// Audio marker intended to live in pairs of two, taking styles depending
+/// on which marker in the pair is to the left and which is to the right.
+class AudioMarkerDialogueTiming : public AudioMarker {
+ /// The other marker for the dialogue line's pair
+ AudioMarkerDialogueTiming *other;
+
+ /// Current sample position of this marker
+ int64_t position;
+
+ /// Draw style for the marker
+ wxPen style;
+ /// Foot style for the marker
+ FeetStyle feet;
+
+public:
+ // AudioMarker interface
+ virtual int64_t GetPosition() const { return position; }
+ virtual wxPen GetStyle() const { return style; }
+ virtual FeetStyle GetFeet() const { return feet; }
+ virtual bool CanSnap() const { return true; }
+
+public:
+ // Specific interface
+
+ /// @brief Move the marker to a new position
+ /// @param new_position The position to move the marker to, in audio samples
+ ///
+ /// If the marker moves to the opposite side of the ohter marker in the pair,
+ /// the styles of the two markers will be changed to match the new start/end
+ /// relationship of them.
+ void SetPosition(int64_t new_position);
+
+
+ /// @brief Constructor
+ ///
+ /// Initialises the fields to default values.
+ AudioMarkerDialogueTiming();
+
+
+ /// @brief Initialise a pair of dialogue markers to be a pair
+ /// @param marker1 The first marker in the pair to make
+ /// @param marker2 The second marker in the pair to make
+ ///
+ /// This checks that the markers aren't already part of a pair, and then sets their
+ /// "other" field. Positions and styles aren't affected.
+ static void InitPair(AudioMarkerDialogueTiming *marker1, AudioMarkerDialogueTiming *marker2);
+};
+
+
+
+/// @class AudioTimingControllerDialogue
+/// @brief Default timing mode for dialogue subtitles
+///
+/// Displays a start and end marker for an active subtitle line, and allows
+/// for those markers to be dragged. Dragging the start/end markers changes
+/// the audio selection.
+///
+/// When the audio rendering code is expanded to support it, inactive lines
+/// will also be shown as shaded lines that cannot be changed.
+///
+/// Another later expansion will be to affect the timing of multiple selected
+/// lines at the same time, if they e.g. have end1==start2.
+class AudioTimingControllerDialogue : public AudioTimingController, private SelectionListener {
+ /// Start and end markers for the active line
+ AudioMarkerDialogueTiming markers[2];
+
+ /// Has the timing been modified by the user?
+ bool timing_modified;
+
+ /// Get the leftmost of the markers
+ AudioMarkerDialogueTiming *GetLeftMarker();
+ const AudioMarkerDialogueTiming *GetLeftMarker() const;
+ /// Get the rightmost of the markers
+ AudioMarkerDialogueTiming *GetRightMarker();
+ const AudioMarkerDialogueTiming *GetRightMarker() const;
+
+ /// The owning audio controller
+ AudioController *audio_controller;
+
+ /// Update the audio controller's selection
+ void UpdateSelection();
+
+ /// Selection controller managing the set of lines currently being timed
+ SelectionController *selection_controller;
+
+private:
+ // SubtitleSelectionListener interface
+ virtual void OnActiveLineChanged(AssDialogue *new_line);
+ virtual void OnSelectedSetChanged(const Selection &lines_added, const Selection &lines_removed);
+
+public:
+ // AudioMarkerProvider interface
+ virtual void GetMarkers(const AudioController::SampleRange &range, AudioMarkerVector &out_markers) const;
+
+ // AudioTimingController interface
+ virtual wxString GetWarningMessage() const;
+ virtual AudioController::SampleRange GetIdealVisibleSampleRange() const;
+ virtual AudioController::SampleRange GetPrimaryPlaybackRange() const;
+ virtual bool HasLabels() const;
+ virtual void Next();
+ virtual void Prev();
+ virtual void Commit();
+ virtual void Revert();
+ virtual bool IsNearbyMarker(int64_t sample, int sensitivity) const;
+ virtual AudioMarker * OnLeftClick(int64_t sample, int sensitivity);
+ virtual AudioMarker * OnRightClick(int64_t sample, int sensitivity);
+ virtual void OnMarkerDrag(AudioMarker *marker, int64_t new_position);
+
+public:
+ // Specific interface
+
+ /// @brief Constructor
+ AudioTimingControllerDialogue(AudioController *audio_controller, SelectionController *selection_controller);
+ virtual ~AudioTimingControllerDialogue();
+};
+
+
+
+AudioTimingController *CreateDialogueTimingController(AudioController *audio_controller, SelectionController *selection_controller)
+{
+ return new AudioTimingControllerDialogue(audio_controller, selection_controller);
+}
+
+
+
+// AudioMarkerDialogueTiming
+
+void AudioMarkerDialogueTiming::SetPosition(int64_t new_position)
+{
+ position = new_position;
+
+ if (other)
+ {
+ /// @todo Make this depend on configuration
+ wxPen style_left = wxPen(*wxRED, 2);
+ wxPen style_right = wxPen(*wxBLUE, 2);
+ if (position < other->position)
+ {
+ feet = Feet_Right;
+ other->feet = Feet_Left;
+ style = style_left;
+ other->style = style_right;
+ }
+ else if (position > other->position)
+ {
+ feet = Feet_Left;
+ other->feet = Feet_Right;
+ style = style_right;
+ other->style = style_left;
+ }
+ }
+}
+
+
+AudioMarkerDialogueTiming::AudioMarkerDialogueTiming()
+: other(0)
+, position(0)
+, style(*wxTRANSPARENT_PEN)
+, feet(Feet_None)
+{
+ // Nothing more to do
+}
+
+
+void AudioMarkerDialogueTiming::InitPair(AudioMarkerDialogueTiming *marker1, AudioMarkerDialogueTiming *marker2)
+{
+ assert(marker1->other == 0);
+ assert(marker2->other == 0);
+
+ marker1->other = marker2;
+ marker2->other = marker1;
+}
+
+
+
+// AudioTimingControllerDialogue
+
+AudioTimingControllerDialogue::AudioTimingControllerDialogue(AudioController *audio_controller, SelectionController *selection_controller)
+: timing_modified(false)
+, audio_controller(audio_controller)
+, selection_controller(selection_controller)
+{
+ assert(audio_controller != 0);
+
+ AudioMarkerDialogueTiming::InitPair(&markers[0], &markers[1]);
+
+ selection_controller->AddSelectionListener(this);
+}
+
+
+AudioTimingControllerDialogue::~AudioTimingControllerDialogue()
+{
+ selection_controller->RemoveSelectionListener(this);
+}
+
+
+
+AudioMarkerDialogueTiming *AudioTimingControllerDialogue::GetLeftMarker()
+{
+ return markers[0].GetPosition() < markers[1].GetPosition() ? &markers[0] : &markers[1];
+}
+
+const AudioMarkerDialogueTiming *AudioTimingControllerDialogue::GetLeftMarker() const
+{
+ return markers[0].GetPosition() < markers[1].GetPosition() ? &markers[0] : &markers[1];
+}
+
+AudioMarkerDialogueTiming *AudioTimingControllerDialogue::GetRightMarker()
+{
+ return markers[0].GetPosition() < markers[1].GetPosition() ? &markers[1] : &markers[0];
+}
+
+const AudioMarkerDialogueTiming *AudioTimingControllerDialogue::GetRightMarker() const
+{
+ return markers[0].GetPosition() < markers[1].GetPosition() ? &markers[1] : &markers[0];
+}
+
+
+
+void AudioTimingControllerDialogue::GetMarkers(const AudioController::SampleRange &range, AudioMarkerVector &out_markers) const
+{
+ if (range.contains(markers[0].GetPosition()))
+ out_markers.push_back(&markers[0]);
+ if (range.contains(markers[1].GetPosition()))
+ out_markers.push_back(&markers[1]);
+}
+
+
+
+void AudioTimingControllerDialogue::OnActiveLineChanged(AssDialogue *new_line)
+{
+ /// @todo Need to change policy to default commit at some point
+ Revert(); // revert will read and reset the selection/markers
+}
+
+
+
+void AudioTimingControllerDialogue::OnSelectedSetChanged(const Selection &lines_added, const Selection &lines_removed)
+{
+ /// @todo Create new passive markers, perhaps
+}
+
+
+
+wxString AudioTimingControllerDialogue::GetWarningMessage() const
+{
+ // We have no warning messages currently, maybe add the old "Modified" message back later?
+ return wxString();
+}
+
+
+
+AudioController::SampleRange AudioTimingControllerDialogue::GetIdealVisibleSampleRange() const
+{
+ return GetPrimaryPlaybackRange();
+}
+
+
+
+AudioController::SampleRange AudioTimingControllerDialogue::GetPrimaryPlaybackRange() const
+{
+ return AudioController::SampleRange(
+ GetLeftMarker()->GetPosition(),
+ GetRightMarker()->GetPosition());
+}
+
+
+
+bool AudioTimingControllerDialogue::HasLabels() const
+{
+ return false;
+}
+
+
+
+void AudioTimingControllerDialogue::Next()
+{
+ selection_controller->NextLine();
+}
+
+
+
+void AudioTimingControllerDialogue::Prev()
+{
+ selection_controller->PrevLine();
+}
+
+
+
+void AudioTimingControllerDialogue::Commit()
+{
+ /// @todo Make these depend on actual configuration
+ const bool next_line_on_commit = true;
+ const int default_duration = 5000; // milliseconds
+
+ int new_start_ms = audio_controller->MillisecondsFromSamples(GetLeftMarker()->GetPosition());
+ int new_end_ms = audio_controller->MillisecondsFromSamples(GetRightMarker()->GetPosition());
+
+ // Store back new times
+ if (timing_modified)
+ {
+ Selection sel;
+ selection_controller->GetSelectedSet(sel);
+ for (Selection::iterator sub = sel.begin(); sub != sel.end(); ++sub)
+ {
+ (*sub)->Start.SetMS(new_start_ms);
+ (*sub)->End.SetMS(new_end_ms);
+ }
+ /// @todo Set an undo point
+ timing_modified = false;
+ }
+
+ // Assume that the next line might be zero-timed and should thus get a default timing
+ if (next_line_on_commit)
+ {
+ markers[0].SetPosition(audio_controller->SamplesFromMilliseconds(new_end_ms));
+ markers[1].SetPosition(audio_controller->SamplesFromMilliseconds(new_end_ms + default_duration));
+ UpdateSelection();
+ }
+}
+
+
+
+void AudioTimingControllerDialogue::Revert()
+{
+ AssDialogue *line = selection_controller->GetActiveLine();
+ if (line)
+ {
+ AssTime new_start = line->Start;
+ AssTime new_end = line->End;
+
+ if (new_start.GetMS() != 0 || new_end.GetMS() != 0)
+ {
+ markers[0].SetPosition(audio_controller->SamplesFromMilliseconds(new_start.GetMS()));
+ markers[1].SetPosition(audio_controller->SamplesFromMilliseconds(new_end.GetMS()));
+ timing_modified = false;
+ UpdateSelection();
+ }
+ }
+}
+
+
+
+bool AudioTimingControllerDialogue::IsNearbyMarker(int64_t sample, int sensitivity) const
+{
+ AudioController::SampleRange range(sample-sensitivity, sample+sensitivity);
+
+ return range.contains(markers[0].GetPosition()) || range.contains(markers[1].GetPosition());
+}
+
+
+AudioMarker * AudioTimingControllerDialogue::OnLeftClick(int64_t sample, int sensitivity)
+{
+ assert(sensitivity >= 0);
+
+ int64_t dist_l, dist_r;
+
+ AudioMarkerDialogueTiming *left = GetLeftMarker();
+ AudioMarkerDialogueTiming *right = GetRightMarker();
+
+ dist_l = tabs(left->GetPosition() - sample);
+ dist_r = tabs(right->GetPosition() - sample);
+
+ if (dist_l < dist_r && dist_l <= sensitivity)
+ {
+ // Clicked near the left marker:
+ // Insta-move it and start dragging it
+ left->SetPosition(sample);
+ audio_controller->OnTimingControllerMarkerMoved(this, left);
+ timing_modified = true;
+ UpdateSelection();
+ return left;
+ }
+
+ if (dist_r < dist_l && dist_r <= sensitivity)
+ {
+ // Clicked near the right marker:
+ // Only drag it. For insta-move, the user must right-click.
+ return right;
+ }
+
+ // Clicked far from either marker:
+ // Insta-set the left marker to the clicked position and return the right as the dragged one,
+ // such that if the user does start dragging, he will create a new selection from scratch
+ left->SetPosition(sample);
+ audio_controller->OnTimingControllerMarkerMoved(this, left);
+ timing_modified = true;
+ UpdateSelection();
+ return right;
+}
+
+
+
+AudioMarker * AudioTimingControllerDialogue::OnRightClick(int64_t sample, int sensitivity)
+{
+ AudioMarkerDialogueTiming *right = GetRightMarker();
+
+ right->SetPosition(sample);
+ audio_controller->OnTimingControllerMarkerMoved(this, right);
+ timing_modified = true;
+ UpdateSelection();
+ return right;
+}
+
+
+
+void AudioTimingControllerDialogue::OnMarkerDrag(AudioMarker *marker, int64_t new_position)
+{
+ assert(marker == &markers[0] || marker == &markers[1]);
+
+ static_cast(marker)->SetPosition(new_position);
+ audio_controller->OnTimingControllerMarkerMoved(this, marker);
+ timing_modified = true;
+
+ UpdateSelection();
+}
+
+
+
+void AudioTimingControllerDialogue::UpdateSelection()
+{
+ audio_controller->OnTimingControllerUpdatedPrimaryRange(this);
+}
+
+
diff --git a/aegisub/src/base_grid.cpp b/aegisub/src/base_grid.cpp
index a2e50f259..588ed91e2 100644
--- a/aegisub/src/base_grid.cpp
+++ b/aegisub/src/base_grid.cpp
@@ -50,7 +50,8 @@
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_style.h"
-#include "audio_display.h"
+#include "audio_controller.h"
+#include "selection_controller.h"
#include "compat.h"
#include "frame_main.h"
#include "main.h"
@@ -1251,9 +1252,12 @@ void BaseGrid::OnKeyPress(wxKeyEvent &event) {
}
// Other events, send to audio display
+ /// @todo Reinstate this, or make a better solution, when audio is getting stabler again
+ /*
if (context->audio->loaded) {
context->audio->GetEventHandler()->ProcessEvent(event);
}
+ */
else event.Skip();
}
diff --git a/aegisub/src/base_grid.h b/aegisub/src/base_grid.h
index 27b8c79d4..2bde01568 100644
--- a/aegisub/src/base_grid.h
+++ b/aegisub/src/base_grid.h
@@ -149,7 +149,6 @@ public:
/// DOCME
SubsEditBox *editBox;
-
/// DOCME
bool byFrame;
diff --git a/aegisub/src/block_cache.h b/aegisub/src/block_cache.h
index b63d98b6a..da815935e 100644
--- a/aegisub/src/block_cache.h
+++ b/aegisub/src/block_cache.h
@@ -41,6 +41,8 @@
#include
#endif
+#define AGI_BLOCK_CACHE_INCLUDED 1
+
/// @class BasicDataBlockFactory
/// @brief Simple factory for allocating blocks for DataBlockCache
@@ -143,7 +145,7 @@ class DataBlockCache {
for (size_t bi = 0; bi < mb.blocks.size(); ++bi)
{
BlockT *b = mb.blocks[bi];
- if (!b)
+ if (b)
factory.DisposeBlock(b);
}
diff --git a/aegisub/src/config/config_windows0.h b/aegisub/src/config/config_windows0.h
index d0e31b26a..be8f75b2b 100644
--- a/aegisub/src/config/config_windows0.h
+++ b/aegisub/src/config/config_windows0.h
@@ -107,6 +107,12 @@
//#define FINAL_RELEASE
+// Use FFTW instead of shipped FFT code
+// FFTW is a very fast library for computing the discrete fourier transform, but is a bit
+// tricky to get working on Windows, and has the additional problem of being GPL licensed.
+// Enable this option to use FFTW to get faster rendering of the audio spectrogram
+//#define WITH_FFTW
+//#pragma comment(lib,libfftw.lib)
// Specify tags the update checker accepts
// See for details on tags.
// Depending on who will be using your build, you may or may not want to have the
diff --git a/aegisub/src/dialog_automation.h b/aegisub/src/dialog_automation.h
index 37e6a18d7..e7a2505c0 100644
--- a/aegisub/src/dialog_automation.h
+++ b/aegisub/src/dialog_automation.h
@@ -46,8 +46,11 @@
#endif
-/// DOCME
-namespace Automation4 { class ScriptManager; class Script; class AutoloadScriptManager; };
+namespace Automation4 {
+ class ScriptManager;
+ class AutoloadScriptManager;
+ class Script;
+};
/// DOCME
diff --git a/aegisub/src/dialog_detached_video.cpp b/aegisub/src/dialog_detached_video.cpp
index 138711f1d..028c8107c 100644
--- a/aegisub/src/dialog_detached_video.cpp
+++ b/aegisub/src/dialog_detached_video.cpp
@@ -42,6 +42,7 @@
#include /// Must be included last.
#endif
+#include "audio_controller.h"
#include "dialog_detached_video.h"
#include "frame_main.h"
#include "main.h"
diff --git a/aegisub/src/dialog_fonts_collector.cpp b/aegisub/src/dialog_fonts_collector.cpp
index f9210ca63..5e2a31de4 100644
--- a/aegisub/src/dialog_fonts_collector.cpp
+++ b/aegisub/src/dialog_fonts_collector.cpp
@@ -51,6 +51,7 @@
#include "ass_file.h"
#include "ass_override.h"
#include "ass_style.h"
+#include "audio_controller.h"
#include "compat.h"
#include "dialog_fonts_collector.h"
#include "font_file_lister.h"
@@ -58,6 +59,7 @@
#include "libresrc/libresrc.h"
#include "main.h"
#include "scintilla_text_ctrl.h"
+#include "selection_controller.h"
#include "subs_grid.h"
#include "utils.h"
diff --git a/aegisub/src/dialog_kara_timing_copy.cpp b/aegisub/src/dialog_kara_timing_copy.cpp
index c99699d85..e6e7a2d07 100644
--- a/aegisub/src/dialog_kara_timing_copy.cpp
+++ b/aegisub/src/dialog_kara_timing_copy.cpp
@@ -55,6 +55,7 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "main.h"
+#include "selection_controller.h"
#include "subs_grid.h"
#include "utils.h"
#include "validators.h"
diff --git a/aegisub/src/dialog_search_replace.cpp b/aegisub/src/dialog_search_replace.cpp
index f2bd3de33..d1be828a0 100644
--- a/aegisub/src/dialog_search_replace.cpp
+++ b/aegisub/src/dialog_search_replace.cpp
@@ -47,9 +47,11 @@
#include "compat.h"
#include "ass_dialogue.h"
#include "ass_file.h"
+#include "audio_controller.h"
#include "dialog_search_replace.h"
#include "frame_main.h"
#include "main.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
#include "subs_grid.h"
diff --git a/aegisub/src/dialog_selection.cpp b/aegisub/src/dialog_selection.cpp
index 030023e99..115eed689 100644
--- a/aegisub/src/dialog_selection.cpp
+++ b/aegisub/src/dialog_selection.cpp
@@ -49,6 +49,7 @@
#include "dialog_selection.h"
#include "help_button.h"
#include "main.h"
+#include "selection_controller.h"
#include "subs_grid.h"
#include "subs_edit_box.h"
diff --git a/aegisub/src/dialog_spellchecker.cpp b/aegisub/src/dialog_spellchecker.cpp
index b1d63077a..d4c48d4af 100644
--- a/aegisub/src/dialog_spellchecker.cpp
+++ b/aegisub/src/dialog_spellchecker.cpp
@@ -44,6 +44,7 @@
#include "ass_dialogue.h"
#include "ass_file.h"
+#include "audio_controller.h"
#include "compat.h"
#include "dialog_spellchecker.h"
#include "frame_main.h"
@@ -51,6 +52,7 @@
#include "libresrc/libresrc.h"
#include "main.h"
#include "include/aegisub/spellchecker.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
#include "subs_grid.h"
diff --git a/aegisub/src/dialog_style_editor.cpp b/aegisub/src/dialog_style_editor.cpp
index 1f3f05154..8a8ee81e5 100644
--- a/aegisub/src/dialog_style_editor.cpp
+++ b/aegisub/src/dialog_style_editor.cpp
@@ -55,6 +55,7 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "main.h"
+#include "selection_controller.h"
#include "subs_grid.h"
#include "subs_preview.h"
#include "include/aegisub/subtitles_provider.h"
diff --git a/aegisub/src/dialog_style_manager.cpp b/aegisub/src/dialog_style_manager.cpp
index ae92c6ab8..f7fb895c1 100644
--- a/aegisub/src/dialog_style_manager.cpp
+++ b/aegisub/src/dialog_style_manager.cpp
@@ -54,6 +54,7 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "main.h"
+#include "selection_controller.h"
#include "standard_paths.h"
#include "subs_grid.h"
#include "utils.h"
diff --git a/aegisub/src/dialog_styling_assistant.cpp b/aegisub/src/dialog_styling_assistant.cpp
index 9f9634c99..d65cdd258 100644
--- a/aegisub/src/dialog_styling_assistant.cpp
+++ b/aegisub/src/dialog_styling_assistant.cpp
@@ -46,8 +46,9 @@
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_style.h"
+#include "selection_controller.h"
+#include "audio_controller.h"
#include "audio_box.h"
-#include "audio_display.h"
#include "dialog_styling_assistant.h"
#include "frame_main.h"
#include "help_button.h"
@@ -72,8 +73,8 @@ wxDialog (parent, -1, _("Styling assistant"), wxDefaultPosition, wxDefaultSize,
// Variables
grid = _grid;
- audio = VideoContext::Get()->audio->box->audioDisplay;
- video = video->Get();
+ audio = VideoContext::Get()->audio;
+ video = VideoContext::Get();
needCommit = false;
linen = -1;
@@ -269,7 +270,8 @@ void DialogStyling::OnActivate(wxActivateEvent &event) {
}
// Enable/disable play video/audio buttons
PlayVideoButton->Enable(video->IsLoaded());
- PlayAudioButton->Enable(audio->loaded);
+ /// @todo Reinstate this when the audio controller is made reachable from here
+ //PlayAudioButton->Enable(audio->loaded);
// Fix style list
Styles->Set(grid->ass->GetStyles());
// Fix line selection
@@ -377,7 +379,9 @@ void DialogStyling::OnPlayVideoButton(wxCommandEvent &event) {
/// @param event
///
void DialogStyling::OnPlayAudioButton(wxCommandEvent &event) {
- audio->Play(line->Start.GetMS(),line->End.GetMS());
+ audio->PlayRange(AudioController::SampleRange(
+ audio->SamplesFromMilliseconds(line->Start.GetMS()),
+ audio->SamplesFromMilliseconds(line->End.GetMS())));
TypeBox->SetFocus();
}
@@ -446,9 +450,12 @@ void StyleEditBox::OnKeyDown(wxKeyEvent &event) {
// Play audio
if (Hotkeys.IsPressed(_T("Styling Assistant Play Audio"))) {
+ /// @todo Reinstate this when the audio controller is made reachable from here
+ /*
if (diag->audio->loaded) {
diag->audio->Play(diag->line->Start.GetMS(),diag->line->End.GetMS());
}
+ */
return;
}
diff --git a/aegisub/src/dialog_styling_assistant.h b/aegisub/src/dialog_styling_assistant.h
index a1973299e..de21ccac2 100644
--- a/aegisub/src/dialog_styling_assistant.h
+++ b/aegisub/src/dialog_styling_assistant.h
@@ -56,6 +56,7 @@ class SubtitlesGrid;
class DialogStyling;
class AudioDisplay;
class VideoContext;
+class AudioController;
@@ -142,7 +143,7 @@ public:
AssDialogue *line;
/// DOCME
- AudioDisplay *audio;
+ AudioController *audio;
/// DOCME
VideoContext *video;
diff --git a/aegisub/src/dialog_timing_processor.cpp b/aegisub/src/dialog_timing_processor.cpp
index 6d3aca2dd..11f0ebb6c 100644
--- a/aegisub/src/dialog_timing_processor.cpp
+++ b/aegisub/src/dialog_timing_processor.cpp
@@ -43,6 +43,7 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "main.h"
+#include "selection_controller.h"
#include "subs_grid.h"
#include "utils.h"
#include "validators.h"
diff --git a/aegisub/src/dialog_translation.cpp b/aegisub/src/dialog_translation.cpp
index 6d5d5e6fa..a5a6e3797 100644
--- a/aegisub/src/dialog_translation.cpp
+++ b/aegisub/src/dialog_translation.cpp
@@ -45,12 +45,13 @@
#include "ass_dialogue.h"
#include "ass_file.h"
-#include "audio_display.h"
+#include "audio_controller.h"
#include "dialog_translation.h"
#include "frame_main.h"
#include "help_button.h"
#include "hotkeys.h"
#include "libresrc/libresrc.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
#include "subs_grid.h"
@@ -78,7 +79,7 @@ DialogTranslation::DialogTranslation (wxWindow *parent,AssFile *_subs,SubtitlesG
subs = _subs;
grid = _grid;
audio = VideoContext::Get()->audio;
- video = video->Get();
+ video = VideoContext::Get();
// Translation controls
OrigText = new ScintillaTextCtrl(this,TEXT_ORIGINAL,_T(""),wxDefaultPosition,wxSize(320,80));
@@ -132,7 +133,8 @@ DialogTranslation::DialogTranslation (wxWindow *parent,AssFile *_subs,SubtitlesG
wxButton *PlayVideoButton = new wxButton(this,BUTTON_TRANS_PLAY_VIDEO,_("Play Video"));
wxButton *PlayAudioButton = new wxButton(this,BUTTON_TRANS_PLAY_AUDIO,_("Play Audio"));
PlayVideoButton->Enable(video->IsLoaded());
- PlayAudioButton->Enable(audio->loaded);
+ /// @todo Reinstate this when the audio context is made reachable from here
+ //PlayAudioButton->Enable(audio->loaded);
ToolSizer->Add(PlayAudioButton,0,wxALL,5);
ToolSizer->Add(PlayVideoButton,0,wxLEFT | wxRIGHT | wxBOTTOM,5);
@@ -406,10 +408,13 @@ void DialogTranslation::OnTransBoxKey(wxKeyEvent &event) {
// Play audio
if (Hotkeys.IsPressed(_T("Translation Assistant Play Audio"))) {
+ /// @todo Reinstate this when the audio controller is made reachable from here
+ /*
if (audio->loaded) {
audio->Play(current->Start.GetMS(),current->End.GetMS());
TransText->SetFocus();
}
+ */
return;
}
@@ -439,7 +444,9 @@ void DialogTranslation::OnPlayVideoButton(wxCommandEvent &event) {
/// @param event
///
void DialogTranslation::OnPlayAudioButton(wxCommandEvent &event) {
- audio->Play(current->Start.GetMS(),current->End.GetMS());
+ audio->PlayRange(AudioController::SampleRange(
+ audio->SamplesFromMilliseconds(current->Start.GetMS()),
+ audio->SamplesFromMilliseconds(current->End.GetMS())));
TransText->SetFocus();
}
diff --git a/aegisub/src/dialog_translation.h b/aegisub/src/dialog_translation.h
index 75b7b569f..9c43c3dbf 100644
--- a/aegisub/src/dialog_translation.h
+++ b/aegisub/src/dialog_translation.h
@@ -45,6 +45,7 @@ class AudioDisplay;
class ScintillaTextCtrl;
class SubtitlesGrid;
class VideoContext;
+class AudioController;
/// DOCME
@@ -56,7 +57,7 @@ class DialogTranslation : public wxDialog {
private:
/// DOCME
- AudioDisplay *audio;
+ AudioController *audio;
/// DOCME
VideoContext *video;
diff --git a/aegisub/src/drop.cpp b/aegisub/src/drop.cpp
index a7cebb7c4..90c7a4fc1 100644
--- a/aegisub/src/drop.cpp
+++ b/aegisub/src/drop.cpp
@@ -43,6 +43,7 @@
#include
#endif
+#include "audio_controller.h"
#include "drop.h"
#include "frame_main.h"
diff --git a/aegisub/src/ffmpegsource_common.cpp b/aegisub/src/ffmpegsource_common.cpp
index e4b474c69..678a71aa9 100644
--- a/aegisub/src/ffmpegsource_common.cpp
+++ b/aegisub/src/ffmpegsource_common.cpp
@@ -48,6 +48,7 @@
#include
+#include "audio_controller.h"
#include "compat.h"
#include "ffmpegsource_common.h"
#include "frame_main.h"
diff --git a/aegisub/src/frame_main.cpp b/aegisub/src/frame_main.cpp
index 7381e2fe2..ee20306aa 100644
--- a/aegisub/src/frame_main.cpp
+++ b/aegisub/src/frame_main.cpp
@@ -46,8 +46,9 @@
#include "ass_file.h"
+#include "selection_controller.h"
+#include "audio_controller.h"
#include "audio_box.h"
-#include "audio_display.h"
#ifdef WITH_AUTOMATION
#include "auto4_base.h"
#endif
@@ -114,8 +115,7 @@ FrameMain::FrameMain (wxArrayString args)
// Initialize flags
HasSelection = false;
menuCreated = false;
- blockAudioLoad = false;
- blockAudioLoad = false;
+ blockVideoLoad = false;
StartupLog(_T("Install PNG handler"));
// Create PNG handler
@@ -130,6 +130,10 @@ FrameMain::FrameMain (wxArrayString args)
local_scripts = new Automation4::ScriptManager();
#endif
+ // Contexts and controllers
+ audioController = new AudioController;
+ audioController->AddAudioListener(this);
+
// Create menu and tool bars
StartupLog(_T("Apply saved Maximized state"));
if (OPT_GET("App/Maximized")->GetBool()) Maximize(true);
@@ -206,7 +210,10 @@ FrameMain::FrameMain (wxArrayString args)
/// @brief FrameMain destructor
FrameMain::~FrameMain () {
+ VideoContext::Get()->SetVideo(_T(""));
+ audioController->CloseAudio();
DeInitContents();
+ delete audioController;
#ifdef WITH_AUTOMATION
delete local_scripts;
#endif
@@ -492,6 +499,9 @@ void FrameMain::InitMenu() {
AppendBitmapMenuItem(audioMenu, Menu_Audio_Close, _("&Close Audio"), _("Closes the currently open audio file"), GETIMAGE(close_audio_menu_16));
wxMenuItem *RecentAudParent = new wxMenuItem(audioMenu, Menu_File_Recent_Auds_Parent, _("Recent"), _T(""), wxITEM_NORMAL, RecentAuds);
audioMenu->Append(RecentAudParent);
+ audioMenu->AppendSeparator();
+ audioMenu->Append(Menu_Audio_Spectrum, _("Spectrum display"), _("Display audio as a frequency-power spectrogrph"), wxITEM_RADIO);
+ audioMenu->Append(Menu_Audio_Waveform, _("Waveform display"), _("Display audio as a linear amplitude graph"), wxITEM_RADIO);
#ifdef _DEBUG
audioMenu->AppendSeparator();
audioMenu->Append(Menu_Audio_Open_Dummy, _T("Open 2h30 Blank Audio"), _T("Open a 150 minutes blank audio clip, for debugging"));
@@ -557,45 +567,52 @@ void FrameMain::InitContents() {
StartupLog(_T("Create background panel"));
Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN);
- // Initialize sizers
- StartupLog(_T("Create main sizers"));
- MainSizer = new wxBoxSizer(wxVERTICAL);
- TopSizer = new wxBoxSizer(wxHORIZONTAL);
- BottomSizer = new wxBoxSizer(wxHORIZONTAL);
-
// Video area;
StartupLog(_T("Create video box"));
videoBox = new VideoBox(Panel, false, ZoomBox, ass);
- TopSizer->Add(videoBox,0,wxEXPAND,0);
+ VideoContext::Get()->audio = audioController;
+ wxBoxSizer *videoSizer = new wxBoxSizer(wxVERTICAL);
+ videoSizer->Add(videoBox, 0, wxEXPAND);
+ videoSizer->AddStretchSpacer(1);
// Subtitles area
StartupLog(_T("Create subtitles grid"));
SubsGrid = new SubtitlesGrid(this,Panel,-1,ass,wxDefaultPosition,wxSize(600,100),wxWANTS_CHARS | wxSUNKEN_BORDER,_T("Subs grid"));
- BottomSizer->Add(SubsGrid,1,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,0);
videoBox->videoSlider->grid = SubsGrid;
VideoContext::Get()->grid = SubsGrid;
Search.grid = SubsGrid;
+ // Tools area
+ StartupLog(_T("Create tool area splitter window"));
+ audioSash = new wxSashWindow(Panel, Main_AudioSash, wxDefaultPosition, wxDefaultSize, wxSW_3D|wxCLIP_CHILDREN);
+ wxBoxSizer *audioSashSizer = new wxBoxSizer(wxHORIZONTAL);
+ audioSash->SetSashVisible(wxSASH_BOTTOM, true);
+
// Audio area
StartupLog(_T("Create audio box"));
- audioBox = new AudioBox(Panel, SubsGrid);
+ audioBox = new AudioBox(audioSash, audioController, SubsGrid);
audioBox->frameMain = this;
- VideoContext::Get()->audio = audioBox->audioDisplay;
+ audioSashSizer->Add(audioBox, 1, wxEXPAND);
+ audioSash->SetSizer(audioSashSizer);
+ audioBox->Fit();
+ audioSash->SetMinimumSizeY(audioBox->GetSize().GetHeight());
- // Top sizer
+ // Editing area
StartupLog(_T("Create subtitle editing box"));
EditBox = new SubsEditBox(Panel,SubsGrid);
- StartupLog(_T("Arrange controls in sizers"));
- ToolSizer = new wxBoxSizer(wxVERTICAL);
- ToolSizer->Add(audioBox,0,wxEXPAND | wxBOTTOM,5);
- ToolSizer->Add(EditBox,1,wxEXPAND,5);
- TopSizer->Add(ToolSizer,1,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,5);
// Set sizers/hints
StartupLog(_T("Arrange main sizers"));
+ ToolsSizer = new wxBoxSizer(wxVERTICAL);
+ ToolsSizer->Add(audioSash, 0, wxEXPAND);
+ ToolsSizer->Add(EditBox, 1, wxEXPAND);
+ TopSizer = new wxBoxSizer(wxHORIZONTAL);
+ TopSizer->Add(videoSizer, 0, wxEXPAND, 0);
+ TopSizer->Add(ToolsSizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
+ MainSizer = new wxBoxSizer(wxVERTICAL);
MainSizer->Add(new wxStaticLine(Panel),0,wxEXPAND | wxALL,0);
MainSizer->Add(TopSizer,0,wxEXPAND | wxALL,0);
- MainSizer->Add(BottomSizer,1,wxEXPAND | wxALL,0);
+ MainSizer->Add(SubsGrid,1,wxEXPAND | wxALL,0);
Panel->SetSizer(MainSizer);
//MainSizer->SetSizeHints(Panel);
//SetSizer(MainSizer);
@@ -828,7 +845,7 @@ void FrameMain::SetDisplayMode(int video, int audio) {
else if (video) sv = VideoContext::Get()->IsLoaded() && !detachedVideo;
if (audio == -1) sa = showAudio;
- else if (audio) sa = audioBox->loaded;
+ else if (audio) sa = audioController->IsAudioOpen();
// See if anything changed
if (sv == showVideo && sa == showAudio) return;
@@ -842,8 +859,8 @@ void FrameMain::SetDisplayMode(int video, int audio) {
VideoContext::Get()->Stop();
// Set display
- TopSizer->Show(videoBox,showVideo,true);
- ToolSizer->Show(audioBox,showAudio,true);
+ TopSizer->Show(videoBox, showVideo, true);
+ ToolsSizer->Show(audioSash, showAudio, true);
// Update
UpdateToolbar();
@@ -881,7 +898,7 @@ void FrameMain::UpdateTitle() {
else newTitle << _("untitled");
#endif
-#ifdef __WXMAC__
+#if defined(__WXMAC__) && !defined(__LP64__)
// On Mac, set the mark in the close button
OSXSetModified(subsMod);
#endif
@@ -914,19 +931,19 @@ void FrameMain::SynchronizeProject(bool fromSubs) {
// Get new state info
ass->GetScriptInfo(_T("Video Position")).ToLong(&videoPos);
ass->GetScriptInfo(_T("Video Zoom Percent")).ToDouble(&videoZoom);
- wxString curassVideo = DecodeRelativePath(ass->GetScriptInfo(_T("Video File")),ass->filename);
- wxString curassVFR = DecodeRelativePath(ass->GetScriptInfo(_T("VFR File")),ass->filename);
- wxString curassKeyframes = DecodeRelativePath(ass->GetScriptInfo(_T("Keyframes File")),ass->filename);
- wxString curassAudio = DecodeRelativePath(ass->GetScriptInfo(_T("Audio File")),ass->filename);
+ wxString curSubsVideo = DecodeRelativePath(ass->GetScriptInfo(_T("Video File")),ass->filename);
+ wxString curSubsVFR = DecodeRelativePath(ass->GetScriptInfo(_T("VFR File")),ass->filename);
+ wxString curSubsKeyframes = DecodeRelativePath(ass->GetScriptInfo(_T("Keyframes File")),ass->filename);
+ wxString curSubsAudio = DecodeRelativePath(ass->GetScriptInfo(_T("Audio URI")),ass->filename);
wxString AutoScriptString = ass->GetScriptInfo(_T("Automation Scripts"));
// Check if there is anything to change
int autoLoadMode = OPT_GET("App/Auto/Load Linked Files")->GetInt();
bool hasToLoad = false;
- if (curassAudio != audioBox->audioName ||
- curassVFR != VideoContext::Get()->GetTimecodesName() ||
- curassVideo != VideoContext::Get()->videoName ||
- curassKeyframes != VideoContext::Get()->GetKeyFramesName()
+ if (curSubsAudio !=audioController->GetAudioURL() ||
+ curSubsVFR != VideoContext::Get()->GetTimecodesName() ||
+ curSubsVideo != VideoContext::Get()->videoName ||
+ curSubsKeyframes != VideoContext::Get()->GetKeyFramesName()
#ifdef WITH_AUTOMATION
|| !AutoScriptString.IsEmpty() || local_scripts->GetScripts().size() > 0
#endif
@@ -946,8 +963,8 @@ void FrameMain::SynchronizeProject(bool fromSubs) {
if (doLoad) {
// Video
- if (curassVideo != VideoContext::Get()->videoName) {
- LoadVideo(curassVideo);
+ if (curSubsVideo != VideoContext::Get()->videoName) {
+ LoadVideo(curSubsVideo);
if (VideoContext::Get()->IsLoaded()) {
VideoContext::Get()->SetAspectRatio(videoAr,videoArValue);
videoBox->videoDisplay->SetZoom(videoZoom);
@@ -955,13 +972,12 @@ void FrameMain::SynchronizeProject(bool fromSubs) {
}
}
- VideoContext::Get()->LoadTimecodes(curassVFR);
- VideoContext::Get()->LoadKeyframes(curassKeyframes);
+ VideoContext::Get()->LoadTimecodes(curSubsVFR);
+ VideoContext::Get()->LoadKeyframes(curSubsKeyframes);
// Audio
- if (curassAudio != audioBox->audioName) {
- if (curassAudio == _T("?video")) LoadAudio(_T(""),true);
- else LoadAudio(curassAudio);
+ if (curSubsAudio != audioController->GetAudioURL()) {
+ audioController->OpenAudio(curSubsAudio);
}
// Automation scripts
@@ -1019,7 +1035,7 @@ void FrameMain::SynchronizeProject(bool fromSubs) {
}
// Store audio data
- ass->SetScriptInfo(_T("Audio File"),MakeRelativePath(audioBox->audioName,ass->filename));
+ ass->SetScriptInfo(_T("Audio URI"),MakeRelativePath(audioController->GetAudioURL(),ass->filename));
// Store video data
ass->SetScriptInfo(_T("Video File"),MakeRelativePath(VideoContext::Get()->videoName,ass->filename));
@@ -1124,31 +1140,6 @@ void FrameMain::LoadVideo(wxString file,bool autoload) {
Thaw();
}
-/// @brief Loads audio
-/// @param filename
-/// @param FromVideo
-void FrameMain::LoadAudio(wxString filename,bool FromVideo) {
- if (blockAudioLoad) return;
- VideoContext::Get()->Stop();
- try {
- audioBox->SetFile(filename,FromVideo);
- SetDisplayMode(-1,1);
- }
- catch (const wchar_t *error) {
- wxString err(error);
- wxMessageBox(err, _T("Error opening audio file"), wxOK | wxICON_ERROR, this);
- }
- #ifdef WITH_AVISYNTH
- catch (AvisynthError err) {
- wxMessageBox (wxString(_T("AviSynth error: ")) + wxString(err.msg,wxConvUTF8), _T("Error loading audio"), wxOK | wxICON_ERROR);
- return;
- }
- #endif
- catch (...) {
- wxMessageBox(_T("Unknown error"), _T("Error opening audio file"), wxOK | wxICON_ERROR, this);
- }
-}
-
void FrameMain::LoadVFR(wxString filename) {
if (filename.empty()) {
VideoContext::Get()->CloseTimecodes();
@@ -1208,7 +1199,7 @@ void FrameMain::SetAccelerators() {
// Medusa
bool medusaPlay = OPT_GET("Audio/Medusa Timing Hotkeys")->GetBool();
- if (medusaPlay && audioBox->audioDisplay->loaded) {
+ if (medusaPlay && audioController->IsAudioOpen()) {
entry.push_back(Hotkeys.GetAccelerator(_T("Audio Medusa Play"),Medusa_Play));
entry.push_back(Hotkeys.GetAccelerator(_T("Audio Medusa Stop"),Medusa_Stop));
entry.push_back(Hotkeys.GetAccelerator(_T("Audio Medusa Play Before"),Medusa_Play_Before));
@@ -1294,7 +1285,6 @@ bool FrameMain::LoadList(wxArrayString list) {
}
// Set blocking
- blockAudioLoad = (audio != _T(""));
blockVideoLoad = (video != _T(""));
// Load files
@@ -1305,10 +1295,8 @@ bool FrameMain::LoadList(wxArrayString list) {
blockVideoLoad = false;
LoadVideo(video);
}
- if (blockAudioLoad) {
- blockAudioLoad = false;
- LoadAudio(audio);
- }
+ if (!audio.IsEmpty())
+ audioController->OpenAudio(audio);
// Result
return ((subs != _T("")) || (audio != _T("")) || (video != _T("")));
diff --git a/aegisub/src/frame_main.h b/aegisub/src/frame_main.h
index 31a252e93..960a0c663 100644
--- a/aegisub/src/frame_main.h
+++ b/aegisub/src/frame_main.h
@@ -43,9 +43,14 @@
#include
#include
#include
+#include
#include
#endif
+#ifndef AGI_AUDIO_CONTROLLER_INCLUDED
+#error You must include "audio_controller.h" before "frame_main.h"
+#endif
+
class AssFile;
class VideoDisplay;
class VideoSlider;
@@ -57,6 +62,7 @@ class VideoBox;
class DialogDetachedVideo;
class DialogStyling;
class AegisubFileDropTarget;
+class AudioController;
namespace Automation4 { class FeatureMacro; class ScriptManager; }
@@ -67,7 +73,7 @@ namespace Automation4 { class FeatureMacro; class ScriptManager; }
/// @brief DOCME
///
/// DOCME
-class FrameMain: public wxFrame {
+class FrameMain: public wxFrame, private AudioControllerAudioEventListener {
friend class AegisubFileDropTarget;
friend class AegisubApp;
friend class SubtitlesGrid;
@@ -93,9 +99,6 @@ private:
wxTimer StatusClear;
- /// DOCME
- bool blockAudioLoad;
-
/// DOCME
bool blockVideoLoad;
@@ -184,6 +187,8 @@ private:
void OnVideoPlay(wxCommandEvent &event);
+ void OnAudioBoxResize(wxSashEvent &event);
+
void OnOpenRecentSubs (wxCommandEvent &event);
void OnOpenRecentVideo (wxCommandEvent &event);
void OnOpenRecentAudio (wxCommandEvent &event);
@@ -239,6 +244,7 @@ private:
void OnOpenAudio (wxCommandEvent &event);
void OnOpenAudioFromVideo (wxCommandEvent &event);
void OnCloseAudio (wxCommandEvent &event);
+ void OnAudioDisplayMode (wxCommandEvent &event);
#ifdef _DEBUG
void OnOpenDummyAudio(wxCommandEvent &event);
void OnOpenDummyNoiseAudio(wxCommandEvent &event);
@@ -313,7 +319,6 @@ private:
void OnMedusaPrev(wxCommandEvent &event);
void LoadVideo(wxString filename,bool autoload=false);
- void LoadAudio(wxString filename,bool FromVideo=false);
void LoadVFR(wxString filename);
void LoadSubtitles(wxString filename,wxString charset=_T(""));
bool SaveSubtitles(bool saveas=false,bool withCharset=false);
@@ -322,21 +327,33 @@ private:
void RebuildRecentList(wxString listName,wxMenu *menu,int startID);
void SynchronizeProject(bool FromSubs=false);
+
+private:
+ // AudioControllerAudioEventListener implementation
+ virtual void OnAudioOpen(AudioProvider *provider);
+ virtual void OnAudioClose();
+ virtual void OnPlaybackPosition(int64_t sample_position);
+ virtual void OnPlaybackStop();
+
+
void OnSubtitlesFileChanged();
public:
- /// DOCME
+ /// The subtitle editing area
SubtitlesGrid *SubsGrid;
- /// DOCME
+ /// The subtitle editing textbox
SubsEditBox *EditBox;
- /// DOCME
+ /// Sash for resizing the audio area
+ wxSashWindow *audioSash;
+
+ /// The audio area
AudioBox *audioBox;
- /// DOCME
+ /// The video area
VideoBox *videoBox;
/// DOCME
@@ -345,18 +362,19 @@ public:
/// DOCME
DialogStyling *stylingAssistant;
+ /// The audio controller for the open project
+ AudioController *audioController;
- /// DOCME
+
+ /// Arranges things from top to bottom in the window
wxBoxSizer *MainSizer;
- /// DOCME
+ /// Arranges video box and tool box from left to right
wxBoxSizer *TopSizer;
- /// DOCME
- wxBoxSizer *BottomSizer;
+ /// Arranges audio and editing areas top to bottom
+ wxBoxSizer *ToolsSizer;
- /// DOCME
- wxBoxSizer *ToolSizer;
FrameMain (wxArrayString args);
~FrameMain ();
@@ -435,6 +453,9 @@ enum {
Menu_Audio_Open_File,
Menu_Audio_Open_From_Video,
Menu_Audio_Close,
+
+ Menu_Audio_Spectrum,
+ Menu_Audio_Waveform,
#ifdef _DEBUG
Menu_Audio_Open_Dummy,
Menu_Audio_Open_Dummy_Noise,
@@ -503,6 +524,12 @@ enum {
AutoSave_Timer,
StatusClear_Timer,
+
+ /// Id for the audio box resizing sash
+ Main_AudioSash,
+
+
+ /// DOCME
Video_Next_Frame,
Video_Prev_Frame,
Video_Focus_Seek,
diff --git a/aegisub/src/frame_main_events.cpp b/aegisub/src/frame_main_events.cpp
index be9de9e42..64d119706 100644
--- a/aegisub/src/frame_main_events.cpp
+++ b/aegisub/src/frame_main_events.cpp
@@ -48,6 +48,8 @@
#include "ass_dialogue.h"
#include "ass_file.h"
+#include "selection_controller.h"
+#include "audio_controller.h"
#include "audio_box.h"
#include "audio_display.h"
#ifdef WITH_AUTOMATION
@@ -84,6 +86,7 @@
#include "main.h"
#include "preferences.h"
#include "standard_paths.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
#include "subs_grid.h"
@@ -104,6 +107,8 @@ BEGIN_EVENT_TABLE(FrameMain, wxFrame)
EVT_CLOSE(FrameMain::OnCloseWindow)
+ EVT_SASH_DRAGGED(Main_AudioSash, FrameMain::OnAudioBoxResize)
+
EVT_MENU_OPEN(FrameMain::OnMenuOpen)
EVT_MENU_RANGE(Menu_File_Recent,Menu_File_Recent+99, FrameMain::OnOpenRecentSubs)
EVT_MENU_RANGE(Menu_Video_Recent,Menu_Video_Recent+99, FrameMain::OnOpenRecentVideo)
@@ -155,6 +160,8 @@ BEGIN_EVENT_TABLE(FrameMain, wxFrame)
EVT_MENU(Menu_Audio_Open_File, FrameMain::OnOpenAudio)
EVT_MENU(Menu_Audio_Open_From_Video, FrameMain::OnOpenAudioFromVideo)
EVT_MENU(Menu_Audio_Close, FrameMain::OnCloseAudio)
+ EVT_MENU(Menu_Audio_Spectrum, FrameMain::OnAudioDisplayMode)
+ EVT_MENU(Menu_Audio_Waveform, FrameMain::OnAudioDisplayMode)
#ifdef _DEBUG
EVT_MENU(Menu_Audio_Open_Dummy, FrameMain::OnOpenDummyAudio)
EVT_MENU(Menu_Audio_Open_Dummy_Noise, FrameMain::OnOpenDummyNoiseAudio)
@@ -294,7 +301,7 @@ void FrameMain::OnMenuOpen (wxMenuEvent &event) {
// View menu
else if (curMenu == viewMenu) {
// Flags
- bool aud = audioBox->audioDisplay->loaded;
+ bool aud = audioController->IsAudioOpen();
bool vid = VideoContext::Get()->IsLoaded() && !detachedVideo;
// Set states
@@ -365,12 +372,16 @@ void FrameMain::OnMenuOpen (wxMenuEvent &event) {
// Audio menu
else if (curMenu == audioMenu) {
- bool state = audioBox->loaded;
+ bool state = audioController->IsAudioOpen();
bool vidstate = VideoContext::Get()->IsLoaded();
MenuBar->Enable(Menu_Audio_Open_From_Video,vidstate);
MenuBar->Enable(Menu_Audio_Close,state);
+ bool spectrum_enabled = OPT_GET("Audio/Spectrum")->GetBool();
+ MenuBar->Check(Menu_Audio_Spectrum, spectrum_enabled);
+ MenuBar->Check(Menu_Audio_Waveform, !spectrum_enabled);
+
// Rebuild recent
RebuildRecentList(_T("Audio"),RecentAuds,Menu_Audio_Recent);
}
@@ -541,7 +552,7 @@ void FrameMain::OnOpenRecentKeyframes(wxCommandEvent &event) {
/// @brief Open recent audio menu entry
/// @param event
void FrameMain::OnOpenRecentAudio(wxCommandEvent &event) {
- LoadAudio(lagi_wxString(config::mru->GetEntry("Audio", event.GetId()-Menu_Audio_Recent)));
+ audioController->OpenAudio(lagi_wxString(config::mru->GetEntry("Audio", event.GetId()-Menu_Audio_Recent)));
}
/// @brief Open new Window
@@ -650,31 +661,40 @@ void FrameMain::OnOpenAudio (wxCommandEvent&) {
+ _("All files") + _T(" (*.*)|*.*");
wxString filename = wxFileSelector(_("Open audio file"),path,_T(""),_T(""),str,wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (!filename.empty()) {
- LoadAudio(filename);
+ audioController->OpenAudio(filename);
OPT_SET("Path/Last/Audio")->SetString(STD_STR(filename));
}
}
/// @brief DOCME
void FrameMain::OnOpenAudioFromVideo (wxCommandEvent&) {
- LoadAudio(_T(""),true);
+ audioController->OpenAudio(_T("audio-video:cache"));
}
/// @brief DOCME
void FrameMain::OnCloseAudio (wxCommandEvent&) {
- LoadAudio(_T(""));
+ audioController->CloseAudio();
+}
+
+
+/// @brief Event handler for audio display renderer selection menu options
+/// @param event wxWidgets event object
+void FrameMain::OnAudioDisplayMode (wxCommandEvent &event) {
+ OPT_SET("Audio/Spectrum")->SetBool(event.GetId() == Menu_Audio_Spectrum);
+ /// @todo Remove this reload call when the audio display starts listening for option changes
+ audioBox->audioDisplay->ReloadRenderingSettings();
}
#ifdef _DEBUG
/// @brief DOCME
void FrameMain::OnOpenDummyAudio (wxCommandEvent&) {
- LoadAudio(_T("?dummy"));
+ audioController->OpenAudio(_T("dummy-audio:silence?sr=44100&bd=16&ch=1&ln=396900000"));
}
/// @brief DOCME
void FrameMain::OnOpenDummyNoiseAudio (wxCommandEvent&) {
- LoadAudio(_T("?noise"));
+ audioController->OpenAudio(_T("dummy-audio:noise?sr=44100&bd=16&ch=1&ln=396900000"));
}
#endif
@@ -1245,7 +1265,7 @@ void FrameMain::OnSetARCustom (wxCommandEvent &) {
void FrameMain::OnCloseWindow (wxCloseEvent &event) {
// Stop audio and video
VideoContext::Get()->Stop();
- audioBox->audioDisplay->Stop();
+ audioController->Stop();
// Ask user if he wants to save first
bool canVeto = event.CanVeto();
@@ -1465,6 +1485,7 @@ void FrameMain::OnChooseLanguage (wxCommandEvent &) {
/// @brief View standard
void FrameMain::OnViewStandard (wxCommandEvent &) {
+ if (!audioController->IsAudioOpen() || !VideoContext::Get()->IsLoaded()) return;
SetDisplayMode(1,1);
}
@@ -1475,6 +1496,7 @@ void FrameMain::OnViewVideo (wxCommandEvent &) {
/// @brief View audio
void FrameMain::OnViewAudio (wxCommandEvent &) {
+ if (!audioController->IsAudioOpen()) return;
SetDisplayMode(0,1);
}
@@ -1485,82 +1507,132 @@ void FrameMain::OnViewSubs (wxCommandEvent &) {
/// @brief Medusa shortcuts
void FrameMain::OnMedusaPlay(wxCommandEvent &) {
- int start=0,end=0;
- audioBox->audioDisplay->GetTimesSelection(start,end);
- audioBox->audioDisplay->Play(start,end);
+ audioController->PlayPrimaryRange();
}
/// @brief DOCME
void FrameMain::OnMedusaStop(wxCommandEvent &) {
// Playing, stop
- if (audioBox->audioDisplay->player->IsPlaying()) {
- audioBox->audioDisplay->Stop();
- audioBox->audioDisplay->Refresh();
+ if (audioController->IsPlaying()) {
+ audioController->Stop();
}
// Otherwise, play the last 500 ms
else {
- int start=0,end=0;
- audioBox->audioDisplay->GetTimesSelection(start,end);
- audioBox->audioDisplay->Play(end-500,end);
+ AudioController::SampleRange sel(audioController->GetPrimaryPlaybackRange());
+ audioController->PlayRange(AudioController::SampleRange(
+ sel.end() - audioController->SamplesFromMilliseconds(500),
+ sel.end()));;
}
}
/// @brief DOCME
void FrameMain::OnMedusaShiftStartForward(wxCommandEvent &) {
- audioBox->audioDisplay->curStartMS += 10;
- audioBox->audioDisplay->Update();
- audioBox->audioDisplay->wxWindow::Update();
+ AudioController::SampleRange newsel(
+ audioController->GetPrimaryPlaybackRange(),
+ audioController->SamplesFromMilliseconds(10),
+ 0);
+ /// @todo Make this use the timing controller instead
+ //audioController->SetSelection(newsel);
}
/// @brief DOCME
void FrameMain::OnMedusaShiftStartBack(wxCommandEvent &) {
- audioBox->audioDisplay->curStartMS -= 10;
- audioBox->audioDisplay->Update();
- audioBox->audioDisplay->wxWindow::Update();
+ AudioController::SampleRange newsel(
+ audioController->GetPrimaryPlaybackRange(),
+ -audioController->SamplesFromMilliseconds(10),
+ 0);
+ /// @todo Make this use the timing controller instead
+ //audioController->SetSelection(newsel);
}
/// @brief DOCME
void FrameMain::OnMedusaShiftEndForward(wxCommandEvent &) {
- audioBox->audioDisplay->curEndMS += 10;
- audioBox->audioDisplay->Update();
- audioBox->audioDisplay->wxWindow::Update();
+ AudioController::SampleRange newsel(
+ audioController->GetPrimaryPlaybackRange(),
+ 0,
+ audioController->SamplesFromMilliseconds(10));
+ /// @todo Make this use the timing controller instead
+ //audioController->SetSelection(newsel);
}
/// @brief DOCME
void FrameMain::OnMedusaShiftEndBack(wxCommandEvent &) {
- audioBox->audioDisplay->curEndMS -= 10;
- audioBox->audioDisplay->Update();
- audioBox->audioDisplay->wxWindow::Update();
+ AudioController::SampleRange newsel(
+ audioController->GetPrimaryPlaybackRange(),
+ 0,
+ -audioController->SamplesFromMilliseconds(10));
+ /// @todo Make this use the timing controller instead
+ //audioController->SetSelection(newsel);
}
/// @brief DOCME
void FrameMain::OnMedusaPlayBefore(wxCommandEvent &) {
- int start=0,end=0;
- audioBox->audioDisplay->GetTimesSelection(start,end);
- audioBox->audioDisplay->Play(start-500,start);
+ AudioController::SampleRange sel(audioController->GetPrimaryPlaybackRange());
+ audioController->PlayRange(AudioController::SampleRange(
+ sel.begin() - audioController->SamplesFromMilliseconds(500),
+ sel.begin()));;
}
/// @brief DOCME
void FrameMain::OnMedusaPlayAfter(wxCommandEvent &) {
- int start=0,end=0;
- audioBox->audioDisplay->GetTimesSelection(start,end);
- audioBox->audioDisplay->Play(end,end+500);
+ AudioController::SampleRange sel(audioController->GetPrimaryPlaybackRange());
+ audioController->PlayRange(AudioController::SampleRange(
+ sel.end(),
+ sel.end() + audioController->SamplesFromMilliseconds(500)));;
}
/// @brief DOCME
void FrameMain::OnMedusaNext(wxCommandEvent &) {
- audioBox->audioDisplay->Next(false);
+ /// @todo Figure out how to handle this in the audio controller
+ //audioBox->audioDisplay->Next(false);
}
/// @brief DOCME
void FrameMain::OnMedusaPrev(wxCommandEvent &) {
- audioBox->audioDisplay->Prev(false);
+ /// @todo Figure out how to handle this in the audio controller
+ //audioBox->audioDisplay->Prev(false);
}
/// @brief DOCME
void FrameMain::OnMedusaEnter(wxCommandEvent &) {
- audioBox->audioDisplay->CommitChanges(true);
+ /// @todo Figure out how to handle this in the audio controller
+ //audioBox->audioDisplay->CommitChanges(true);
+}
+
+void FrameMain::OnAudioBoxResize(wxSashEvent &event)
+{
+ if (event.GetDragStatus() == wxSASH_STATUS_OUT_OF_RANGE)
+ return;
+
+ wxRect rect = event.GetDragRect();
+
+ if (rect.GetHeight() < audioSash->GetMinimumSizeY())
+ rect.SetHeight(audioSash->GetMinimumSizeY());
+
+ audioBox->SetMinSize(wxSize(-1, rect.GetHeight()));
+ Panel->Layout();
+ Refresh();
+}
+
+void FrameMain::OnAudioOpen(AudioProvider *provider)
+{
+ SetDisplayMode(-1, 1);
+}
+
+void FrameMain::OnAudioClose()
+{
+ SetDisplayMode(-1, 0);
+}
+
+void FrameMain::OnPlaybackPosition(int64_t sample_position)
+{
+ // do nothing
+}
+
+void FrameMain::OnPlaybackStop()
+{
+ // do nothing
}
void FrameMain::OnSubtitlesFileChanged() {
@@ -1570,3 +1642,4 @@ void FrameMain::OnSubtitlesFileChanged() {
UpdateTitle();
}
+
diff --git a/aegisub/src/include/aegisub/audio_provider.h b/aegisub/src/include/aegisub/audio_provider.h
index b52d369e9..ca1b07f05 100644
--- a/aegisub/src/include/aegisub/audio_provider.h
+++ b/aegisub/src/include/aegisub/audio_provider.h
@@ -79,17 +79,15 @@ public:
virtual ~AudioProvider();
virtual wxString GetFilename() const { return filename; };
- virtual void GetAudio(void *buf, int64_t start, int64_t count)=0;
- void GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume);
+ virtual void GetAudio(void *buf, int64_t start, int64_t count) const = 0;
+ void GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const;
int64_t GetNumSamples() const { return num_samples; }
- int GetSampleRate() const { return sample_rate; }
+ int GetSampleRate() const { return sample_rate; }
int GetBytesPerSample() const { return bytes_per_sample; }
- int GetChannels() const { return channels; }
+ int GetChannels() const { return channels; }
virtual bool AreSamplesNativeEndian() const = 0;
- void GetWaveForm(int *min,int *peak,int64_t start,int w,int h,int samples,float scale);
-
/// @brief Does this provider benefit from external caching?
virtual bool NeedsCache() const { return false; }
};
diff --git a/aegisub/src/main.cpp b/aegisub/src/main.cpp
index acf6d05f5..427465bc7 100644
--- a/aegisub/src/main.cpp
+++ b/aegisub/src/main.cpp
@@ -54,8 +54,9 @@
#include "ass_export_filter.h"
#include "ass_file.h"
#include "ass_time.h"
+#include "selection_controller.h"
+#include "audio_controller.h"
#include "audio_box.h"
-#include "audio_display.h"
#ifdef WITH_AUTOMATION
#include "auto4_base.h"
#endif
@@ -68,7 +69,6 @@
#include "libresrc/libresrc.h"
#include "plugin_manager.h"
#include "standard_paths.h"
-#include "subs_grid.h"
#include "subtitle_format.h"
#include "version.h"
#include "video_context.h"
@@ -551,13 +551,14 @@ END_EVENT_TABLE()
/// @param event
///
void AegisubApp::OnMouseWheel(wxMouseEvent &event) {
+ if (event.WasProcessed()) return;
wxPoint pt;
wxWindow *target = wxFindWindowAtPointer(pt);
- if (frame && (target == frame->audioBox->audioDisplay || target == frame->SubsGrid)) {
+ /*if (frame && (target == frame->audioBox->audioDisplay || target == frame->SubsGrid)) {
if (target->IsShownOnScreen()) target->GetEventHandler()->ProcessEvent(event);
else event.Skip();
}
- else event.Skip();
+ else event.Skip();*/
}
diff --git a/aegisub/src/setup.cpp b/aegisub/src/setup.cpp
index f994d69cf..10f92b42a 100644
--- a/aegisub/src/setup.cpp
+++ b/aegisub/src/setup.cpp
@@ -140,6 +140,4 @@
#pragma comment(lib, "libass.lib")
#endif
-
#endif // VisualC
-
diff --git a/aegisub/src/subs_edit_box.cpp b/aegisub/src/subs_edit_box.cpp
index fd3fb7227..6adb7a142 100644
--- a/aegisub/src/subs_edit_box.cpp
+++ b/aegisub/src/subs_edit_box.cpp
@@ -58,12 +58,13 @@
#include "ass_file.h"
#include "ass_override.h"
#include "ass_style.h"
-#include "audio_display.h"
+#include "audio_controller.h"
#include "dialog_colorpicker.h"
#include "dialog_search_replace.h"
#include "frame_main.h"
#include "libresrc/libresrc.h"
#include "main.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
#include "subs_grid.h"
diff --git a/aegisub/src/subs_edit_box.h b/aegisub/src/subs_edit_box.h
index 5b7e49276..a4729fa5e 100644
--- a/aegisub/src/subs_edit_box.h
+++ b/aegisub/src/subs_edit_box.h
@@ -55,6 +55,7 @@ class wxSpinCtrl;
class wxStyledTextCtrl;
class wxStyledTextEvent;
class wxTextCtrl;
+class AudioController;
/// DOCME
/// @class SubsEditBox
diff --git a/aegisub/src/subs_edit_ctrl.cpp b/aegisub/src/subs_edit_ctrl.cpp
index 829157092..62f0efcd6 100644
--- a/aegisub/src/subs_edit_ctrl.cpp
+++ b/aegisub/src/subs_edit_ctrl.cpp
@@ -49,6 +49,7 @@
#include "compat.h"
#include "main.h"
#include "include/aegisub/spellchecker.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
#include "subs_grid.h"
diff --git a/aegisub/src/subs_grid.cpp b/aegisub/src/subs_grid.cpp
index e9e34e7b6..ab78e77ec 100644
--- a/aegisub/src/subs_grid.cpp
+++ b/aegisub/src/subs_grid.cpp
@@ -51,8 +51,10 @@
#include "ass_karaoke.h"
#include "ass_override.h"
#include "ass_style.h"
+#include "include/aegisub/audio_provider.h"
+#include "selection_controller.h"
+#include "audio_controller.h"
#include "audio_box.h"
-#include "audio_display.h"
#include "charset_conv.h"
#include "dialog_paste_over.h"
#include "frame_main.h"
@@ -204,7 +206,7 @@ void SubtitlesGrid::OnPopupMenu(bool alternate) {
menu.AppendSeparator();
//Make audio clip
- state = parentFrame->audioBox->audioDisplay->loaded==true;
+ state = parentFrame->audioController->IsAudioOpen()==true;
menu.Append(MENU_AUDIOCLIP,_("Create audio clip"),_("Create an audio clip of the selected line"))->Enable(state);
menu.AppendSeparator();
@@ -646,8 +648,8 @@ void SubtitlesGrid::OnRecombine(wxCommandEvent &) {
/// @brief Export audio clip of line
void SubtitlesGrid::OnAudioClip(wxCommandEvent &) {
int64_t num_samples,start=0,end=0,temp;
- AudioDisplay *audioDisplay = parentFrame->audioBox->audioDisplay;
- AudioProvider *provider = audioDisplay->provider;
+ AudioController *audioController = parentFrame->audioController;
+ const AudioProvider *provider = audioController->GetAudioProvider();
AssDialogue *cur;
wxArrayInt sel = GetSelection();
@@ -656,9 +658,9 @@ void SubtitlesGrid::OnAudioClip(wxCommandEvent &) {
for(unsigned int i=0;i!=sel.GetCount();i++) {
cur = GetDialogue(sel[i]);
- temp = audioDisplay->GetSampleAtMS(cur->Start.GetMS());
+ temp = audioController->SamplesFromMilliseconds(cur->Start.GetMS());
start = (i==0||tempGetSampleAtMS(cur->End.GetMS());
+ temp = audioController->SamplesFromMilliseconds(cur->End.GetMS());
end = (i==0||temp>end)?temp:end;
}
diff --git a/aegisub/src/subtitles_provider_libass.cpp b/aegisub/src/subtitles_provider_libass.cpp
index 5e32a0ced..abdc658ef 100644
--- a/aegisub/src/subtitles_provider_libass.cpp
+++ b/aegisub/src/subtitles_provider_libass.cpp
@@ -50,6 +50,7 @@
#include
+#include "audio_controller.h"
#include "ass_file.h"
#include "dialog_progress.h"
#include "frame_main.h"
diff --git a/aegisub/src/utils.h b/aegisub/src/utils.h
index 897705800..b53094933 100644
--- a/aegisub/src/utils.h
+++ b/aegisub/src/utils.h
@@ -70,6 +70,10 @@ int AegiStringToFix(const wxString &str,size_t decimalPlaces,int start=0,int end
wxIcon BitmapToIcon(wxBitmap bmp);
void RestartAegisub();
+
+/// @brief Templated abs() function
+template T tabs(T x) { return x < 0 ? -x : x; }
+
#ifndef MIN
#define MIN(a,b) ((a)<(b))?(a):(b)
#endif
diff --git a/aegisub/src/video_box.cpp b/aegisub/src/video_box.cpp
index 58f373bea..a58eac78c 100644
--- a/aegisub/src/video_box.cpp
+++ b/aegisub/src/video_box.cpp
@@ -45,10 +45,12 @@
#include "ass_dialogue.h"
#include "ass_file.h"
+#include "audio_controller.h"
#include "frame_main.h"
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "main.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_grid.h"
#include "toggle_bitmap.h"
diff --git a/aegisub/src/video_context.cpp b/aegisub/src/video_context.cpp
index d379085b1..58de11457 100644
--- a/aegisub/src/video_context.cpp
+++ b/aegisub/src/video_context.cpp
@@ -53,11 +53,13 @@
#include
#endif
+#include "include/aegisub/audio_player.h"
+#include "include/aegisub/audio_provider.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_style.h"
#include "ass_time.h"
-#include "audio_display.h"
+#include "audio_controller.h"
#include "compat.h"
#include "include/aegisub/audio_player.h"
#include "include/aegisub/audio_provider.h"
@@ -67,6 +69,7 @@
#include "main.h"
#include "mkv_wrap.h"
#include "standard_paths.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_grid.h"
#include "threaded_frame_source.h"
@@ -119,10 +122,6 @@ VideoContext::VideoContext()
}
VideoContext::~VideoContext () {
- if (audio && audio->temporary) {
- delete audio->provider;
- delete audio->player;
- }
}
VideoContext *VideoContext::Get() {
@@ -135,15 +134,7 @@ void VideoContext::Reset() {
keyFrames.clear();
videoFPS = agi::vfr::Framerate();
-
- // Remove temporary audio provider
- if (audio && audio->temporary) {
- delete audio->provider;
- audio->provider = NULL;
- delete audio->player;
- audio->player = NULL;
- audio->temporary = false;
- }
+ keyframesRevision++;
// Remove video data
frame_n = 0;
@@ -316,8 +307,11 @@ void VideoContext::PlayNextFrame() {
int thisFrame = frame_n;
JumpToFrame(frame_n + 1);
// Start playing audio
- if (playAudioOnStep->GetBool())
- audio->Play(TimeAtFrame(thisFrame),TimeAtFrame(thisFrame + 1));
+ if (playAudioOnStep->GetBool()) {
+ audio->PlayRange(AudioController::SampleRange(
+ audio->SamplesFromMilliseconds(TimeAtFrame(thisFrame)),
+ audio->SamplesFromMilliseconds(TimeAtFrame(thisFrame + 1))));
+ }
}
void VideoContext::PlayPrevFrame() {
@@ -327,8 +321,11 @@ void VideoContext::PlayPrevFrame() {
int thisFrame = frame_n;
JumpToFrame(frame_n -1);
// Start playing audio
- if (playAudioOnStep->GetBool())
- audio->Play(TimeAtFrame(thisFrame - 1),TimeAtFrame(thisFrame));
+ if (playAudioOnStep->GetBool()) {
+ audio->PlayRange(AudioController::SampleRange(
+ audio->SamplesFromMilliseconds(TimeAtFrame(thisFrame - 1)),
+ audio->SamplesFromMilliseconds(TimeAtFrame(thisFrame))));
+ }
}
void VideoContext::Play() {
@@ -342,7 +339,7 @@ void VideoContext::Play() {
endFrame = -1;
// Start playing audio
- audio->Play(TimeAtFrame(startFrame),-1);
+ audio->PlayToEnd(audio->SamplesFromMilliseconds(TimeAtFrame(startFrame)));
//audio->Play will override this if we put it before, so put it after.
isPlaying = true;
@@ -358,7 +355,9 @@ void VideoContext::PlayLine() {
if (!curline) return;
// Start playing audio
- audio->Play(curline->Start.GetMS(),curline->End.GetMS());
+ audio->PlayRange(AudioController::SampleRange(
+ audio->SamplesFromMilliseconds(curline->Start.GetMS()),
+ audio->SamplesFromMilliseconds(curline->End.GetMS())));
// Set variables
isPlaying = true;
@@ -417,7 +416,9 @@ void VideoContext::OnPlayTimer(wxTimerEvent &event) {
if (nextFrame == frame_n) return;
// Next frame is before or over 2 frames ahead, so force audio resync
- if (audio->player && keepAudioSync && (nextFrame < frame_n || nextFrame > frame_n + 2)) audio->player->SetCurrentPosition(audio->GetSampleAtMS(TimeAtFrame(nextFrame)));
+ if (audio->IsPlaying() && keepAudioSync && (nextFrame < frame_n || nextFrame > frame_n + 2)) {
+ audio->ResyncPlaybackPosition(audio->SamplesFromMilliseconds(TimeAtFrame(nextFrame)));
+ }
// Jump to next frame
playNextFrame = nextFrame;
@@ -425,13 +426,13 @@ void VideoContext::OnPlayTimer(wxTimerEvent &event) {
JumpToFrame(nextFrame);
// Sync audio
- if (keepAudioSync && nextFrame % 10 == 0 && audio && audio->provider && audio->player) {
- int64_t audPos = audio->GetSampleAtMS(TimeAtFrame(nextFrame));
- int64_t curPos = audio->player->GetCurrentPosition();
+ if (keepAudioSync && nextFrame % 10 == 0 && audio->IsPlaying()) {
+ int64_t audPos = audio->SamplesFromMilliseconds(TimeAtFrame(nextFrame));
+ int64_t curPos = audio->GetPlaybackPosition();
int delta = int(audPos-curPos);
if (delta < 0) delta = -delta;
- int maxDelta = audio->provider->GetSampleRate();
- if (delta > maxDelta) audio->player->SetCurrentPosition(audPos);
+ int maxDelta = audio->SamplesFromMilliseconds(1000);
+ if (delta > maxDelta) audio->ResyncPlaybackPosition(audPos);
}
}
@@ -468,10 +469,12 @@ void VideoContext::LoadKeyframes(wxString filename) {
catch (...) {
wxMessageBox(_T("Unknown error"), _T("Error opening keyframes file"), wxOK | wxICON_ERROR, NULL);
}
+ keyframesRevision++;
}
void VideoContext::SaveKeyframes(wxString filename) {
KeyFrameFile::Save(filename, GetKeyFrames());
+ keyframesRevision++;
}
void VideoContext::CloseKeyframes() {
@@ -483,6 +486,7 @@ void VideoContext::CloseKeyframes() {
keyFrames.clear();
}
KeyframesOpen(keyFrames);
+ keyframesRevision++;
}
void VideoContext::LoadTimecodes(wxString filename) {
diff --git a/aegisub/src/video_context.h b/aegisub/src/video_context.h
index f5fb22d81..abff82a22 100644
--- a/aegisub/src/video_context.h
+++ b/aegisub/src/video_context.h
@@ -62,6 +62,7 @@ class SubtitlesProviderErrorEvent;
class ThreadedFrameSource;
class VideoProvider;
class VideoProviderErrorEvent;
+class AudioController;
namespace agi {
class OptionValue;
@@ -101,6 +102,10 @@ private:
/// DOCME
wxString keyFramesFilename;
+ /// Revision counter for keyframes, when the set of keyframes is changed this number changes
+ int keyframesRevision;
+
+
/// DOCME
wxMutex playMutex;
@@ -161,8 +166,8 @@ public:
/// File name of currently open video, if any
wxString videoName;
- /// DOCME
- AudioDisplay *audio;
+ /// The audio controller for this video context
+ AudioController *audio;
const agi::vfr::Framerate &VFR_Input;
const agi::vfr::Framerate &VFR_Output;
@@ -265,6 +270,7 @@ public:
void CloseKeyframes();
bool OverKeyFramesLoaded() const { return !keyFramesFilename.empty(); }
bool KeyFramesLoaded() const { return !keyFrames.empty(); }
+ int GetKeyframesRevision() const { return keyframesRevision; }
wxString GetTimecodesName() const { return ovrTimecodeFile; }
void LoadTimecodes(wxString filename);
diff --git a/aegisub/src/video_display.cpp b/aegisub/src/video_display.cpp
index d32ba3b51..2afa3c558 100644
--- a/aegisub/src/video_display.cpp
+++ b/aegisub/src/video_display.cpp
@@ -57,6 +57,7 @@
#endif
#include "video_display.h"
+#include "selection_controller.h"
#include "ass_dialogue.h"
#include "ass_file.h"
diff --git a/aegisub/src/video_slider.cpp b/aegisub/src/video_slider.cpp
index a1bc94004..6e25f88a9 100644
--- a/aegisub/src/video_slider.cpp
+++ b/aegisub/src/video_slider.cpp
@@ -43,6 +43,7 @@
#include "ass_dialogue.h"
#include "main.h"
#include "subs_edit_box.h"
+#include "selection_controller.h"
#include "subs_grid.h"
#include "utils.h"
#include "video_context.h"
diff --git a/aegisub/src/visual_tool.cpp b/aegisub/src/visual_tool.cpp
index f89825cf8..c3e66deb0 100644
--- a/aegisub/src/visual_tool.cpp
+++ b/aegisub/src/visual_tool.cpp
@@ -46,6 +46,7 @@
#include "ass_style.h"
#include "ass_time.h"
#include "main.h"
+#include "selection_controller.h"
#include "subs_edit_box.h"
#include "subs_grid.h"
#include "utils.h"
diff --git a/aegisub/src/visual_tool_vector_clip.cpp b/aegisub/src/visual_tool_vector_clip.cpp
index 53bd86543..a2c17c612 100644
--- a/aegisub/src/visual_tool_vector_clip.cpp
+++ b/aegisub/src/visual_tool_vector_clip.cpp
@@ -53,6 +53,7 @@
#endif
#include "config.h"
+#include "selection_controller.h"
#include "ass_dialogue.h"
#include "libresrc/libresrc.h"