Rewrite nearly everything related to karaoke

Move most karaoke parsing/serializing/editing code to AssKaraoke rather
than being scattered all over the place, and add much better support for
non-karaoke override tags and comments.

Add a karaoke timing controller.

Redesign the karaoke syllable split/join interface to have a single mode
from which both splitting and joining can be done rather than separate
split and join modes.

Only show the karaoke split/join bar when karaoke mode is enabled.

Closes #886, #987, #1190.

Originally committed to SVN as r5613.
This commit is contained in:
Thomas Goyne 2011-09-28 19:44:07 +00:00
parent cc147d1660
commit c936306593
25 changed files with 1356 additions and 1934 deletions

View file

@ -1775,6 +1775,10 @@
RelativePath="..\..\src\audio_timing_dialogue.cpp" RelativePath="..\..\src\audio_timing_dialogue.cpp"
> >
</File> </File>
<File
RelativePath="..\..\src\audio_timing_karaoke.cpp"
>
</File>
</Filter> </Filter>
<Filter <Filter
Name="Video UI" Name="Video UI"

View file

@ -261,6 +261,7 @@
<ClCompile Include="$(SrcDir)audio_renderer_spectrum.cpp" /> <ClCompile Include="$(SrcDir)audio_renderer_spectrum.cpp" />
<ClCompile Include="$(SrcDir)audio_renderer_waveform.cpp" /> <ClCompile Include="$(SrcDir)audio_renderer_waveform.cpp" />
<ClCompile Include="$(SrcDir)audio_timing_dialogue.cpp" /> <ClCompile Include="$(SrcDir)audio_timing_dialogue.cpp" />
<ClCompile Include="$(SrcDir)audio_timing_karaoke.cpp" />
<ClCompile Include="$(SrcDir)auto4_base.cpp" /> <ClCompile Include="$(SrcDir)auto4_base.cpp" />
<ClCompile Include="$(SrcDir)auto4_lua.cpp" /> <ClCompile Include="$(SrcDir)auto4_lua.cpp" />
<ClCompile Include="$(SrcDir)auto4_lua_assfile.cpp" /> <ClCompile Include="$(SrcDir)auto4_lua_assfile.cpp" />

View file

@ -728,6 +728,9 @@
<ClCompile Include="$(SrcDir)audio_timing_dialogue.cpp"> <ClCompile Include="$(SrcDir)audio_timing_dialogue.cpp">
<Filter>Audio\UI</Filter> <Filter>Audio\UI</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="$(SrcDir)audio_timing_karaoke.cpp">
<Filter>Audio\UI</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)command\app.cpp"> <ClCompile Include="$(SrcDir)command\app.cpp">
<Filter>Commands</Filter> <Filter>Commands</Filter>
</ClCompile> </ClCompile>

View file

@ -142,6 +142,7 @@ SRC += \
audio_renderer_spectrum.cpp \ audio_renderer_spectrum.cpp \
audio_renderer_waveform.cpp \ audio_renderer_waveform.cpp \
audio_timing_dialogue.cpp \ audio_timing_dialogue.cpp \
audio_timing_karaoke.cpp \
auto4_base.cpp \ auto4_base.cpp \
avisynth_wrap.cpp \ avisynth_wrap.cpp \
base_grid.cpp \ base_grid.cpp \

View file

@ -1,29 +1,16 @@
// Copyright (c) 2006-2007, Niels Martin Hansen // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// //
// * Redistributions of source code must retain the above copyright notice, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 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/ // Aegisub Project http://www.aegisub.org/
// //
@ -38,94 +25,284 @@
#include "config.h" #include "config.h"
#include "ass_karaoke.h" #include "ass_karaoke.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_override.h" #include "ass_override.h"
#include "include/aegisub/context.h"
#include "selection_controller.h"
#ifndef AGI_PRE
#include <wx/intl.h>
#endif
/// @brief DOCME wxString AssKaraoke::Syllable::GetText(bool k_tag) const {
/// wxString ret;
AssKaraokeSyllable::AssKaraokeSyllable()
{ if (k_tag)
duration = 0; ret = wxString::Format("{%s%d}", tag_type, duration / 10);
text = _T("");
unstripped_text = _T(""); size_t idx = 0;
type = _T(""); for (std::map<size_t, wxString>::const_iterator ovr = ovr_tags.begin(); ovr != ovr_tags.end(); ++ovr) {
tag = 0; ret += text.Mid(idx, ovr->first - idx);
ret += ovr->second;
idx = ovr->first;
}
ret += text.Mid(idx);
return ret;
} }
/// @brief DOCME AssKaraoke::AssKaraoke(AssDialogue *line, bool auto_split)
/// @param line : no_announce(false)
/// @param syls
///
void ParseAssKaraokeTags(const AssDialogue *line, AssKaraokeVector &syls)
{ {
// Assume line already has tags parsed if (line) SetLine(line, auto_split);
AssKaraokeSyllable syl; }
bool brackets_open = false; void AssKaraoke::SetLine(AssDialogue *line, bool auto_split) {
active_line = line;
line->ParseASSTags();
for (int i = 0; i < (int)line->Blocks.size(); i++) { syls.clear();
Syllable syl;
syl.start_time = line->Start.GetMS();
syl.duration = 0;
syl.tag_type = "\\k";
for (size_t i = 0; i < line->Blocks.size(); ++i) {
AssDialogueBlock *block = line->Blocks[i]; AssDialogueBlock *block = line->Blocks[i];
switch (block->GetType()) { if (dynamic_cast<AssDialogueBlockPlain*>(block)) {
// treat comments as overrides rather than dialogue
case BLOCK_BASE: if (block->text.size() >= 2 && block->text[0] == '{')
break; syl.ovr_tags[syl.text.size()] += block->text;
else
case BLOCK_PLAIN:
syl.text += block->text; syl.text += block->text;
syl.unstripped_text += block->text; }
break; else if (dynamic_cast<AssDialogueBlockDrawing*>(block)) {
// drawings aren't override tags but they shouldn't show up in the
case BLOCK_DRAWING: // stripped text so pretend they are
// Regard drawings as tags syl.ovr_tags[syl.text.size()] += block->text;
syl.unstripped_text += block->text; }
break; else if (AssDialogueBlockOverride *ovr = dynamic_cast<AssDialogueBlockOverride*>(block)) {
bool in_tag = false;
case BLOCK_OVERRIDE: { for (size_t j = 0; j < ovr->Tags.size(); ++j) {
AssDialogueBlockOverride *ovr = dynamic_cast<AssDialogueBlockOverride*>(block);
for (int j = 0; j < (int)ovr->Tags.size(); j++) {
AssOverrideTag *tag = ovr->Tags[j]; AssOverrideTag *tag = ovr->Tags[j];
if (tag->IsValid() && tag->Name.Mid(0,2).CmpNoCase(_T("\\k")) == 0) { if (tag->IsValid() && tag->Name.Left(2).Lower() == "\\k") {
// karaoke tag if (in_tag) {
if (brackets_open) { syl.ovr_tags[syl.text.size()] += "}";
syl.unstripped_text += _T("}"); in_tag = false;
brackets_open = false;
} }
// Store syllable // Dealing with both \K and \kf is mildly annoying so just
// convert them both to \kf
if (tag->Name == "\\K") tag->Name = "\\kf";
// Don't bother including zero duration zero length syls
if (syl.duration > 0 || !syl.text.empty()) {
syls.push_back(syl); syls.push_back(syl);
syl.text.clear();
syl.text = _T(""); syl.ovr_tags.clear();
syl.unstripped_text = _T("");
syl.tag = tag;
syl.type = tag->Name;
syl.duration = tag->Params[0]->Get<int>();
} else {
// not karaoke tag
if (!brackets_open) {
syl.unstripped_text += _T("{");
brackets_open = true;
} }
syl.unstripped_text += *tag;
syl.tag_type = tag->Name;
syl.start_time += syl.duration;
syl.duration = tag->Params[0]->Get(0) * 10;
}
else {
wxString& otext = syl.ovr_tags[syl.text.size()];
// Merge adjacent override tags
if (j == 0 && otext.size())
otext.RemoveLast();
else if (!in_tag)
otext += "{";
in_tag = true;
otext += *tag;
} }
} }
if (brackets_open) { if (in_tag)
brackets_open = false; syl.ovr_tags[syl.text.size()] += "}";
syl.unstripped_text += _T("}");
}
break;
}
} }
} }
syls.push_back(syl); syls.push_back(syl);
line->ClearBlocks();
// Normalize the syllables so that the total duration is equal to the line length
int end_time = active_line->End.GetMS();
int last_end = syl.start_time + syl.duration;
// Total duration is shorter than the line length so just extend the last
// syllable; this has no effect on rendering but is easier to work with
if (last_end < end_time)
syls.back().duration += end_time - last_end;
else if (last_end > end_time) {
// Shrink each syllable proportionately
int start_time = active_line->Start.GetMS();
double scale_factor = double(end_time - start_time) / (last_end - start_time);
for (size_t i = 0; i < size(); ++i) {
syls[i].start_time = start_time + scale_factor * (syls[i].start_time - start_time);
}
for (int i = size() - 1; i > 0; --i) {
syls[i].duration = end_time - syls[i].start_time;
end_time = syls[i].start_time;
}
}
// Add karaoke splits at each space
if (auto_split && syls.size() == 1) {
size_t pos;
no_announce = true;
while ((pos = syls.back().text.find(' ')) != wxString::npos)
AddSplit(syls.size() - 1, pos + 1);
no_announce = false;
}
AnnounceSyllablesChanged();
} }
wxString AssKaraoke::GetText() const {
wxString text;
text.reserve(size() * 10);
for (iterator it = begin(); it != end(); ++it) {
text += it->GetText(true);
}
return text;
}
wxString AssKaraoke::GetTagType() const {
return begin()->tag_type;
}
void AssKaraoke::SetTagType(wxString const& new_type) {
for (size_t i = 0; i < size(); ++i) {
syls[i].tag_type = new_type;
}
}
void AssKaraoke::AddSplit(size_t syl_idx, size_t pos) {
syls.insert(syls.begin() + syl_idx + 1, Syllable());
Syllable &syl = syls[syl_idx];
Syllable &new_syl = syls[syl_idx + 1];
// If the syl is empty or the user is adding a syllable past the last
// character then pos will be out of bounds. Doing this is a bit goofy,
// but it's sometimes required for complex karaoke scripts
if (pos < syl.text.size()) {
new_syl.text = syl.text.Mid(pos);
syl.text = syl.text.Left(pos);
}
if (new_syl.text.empty())
new_syl.duration = 0;
else {
new_syl.duration = syl.duration * new_syl.text.size() / (syl.text.size() + new_syl.text.size());
syl.duration -= new_syl.duration;
}
assert(syl.duration >= 0);
new_syl.start_time = syl.start_time + syl.duration;
new_syl.tag_type = syl.tag_type;
// Move all override tags after the split to the new syllable and fix the indices
size_t text_len = syl.text.size();
for (ovr_iterator it = syl.ovr_tags.begin(); it != syl.ovr_tags.end(); ) {
if (it->first < text_len)
++it;
else {
new_syl.ovr_tags[it->first - text_len] = it->second;
syl.ovr_tags.erase(it++);
}
}
if (!no_announce) AnnounceSyllablesChanged();
}
void AssKaraoke::RemoveSplit(size_t syl_idx) {
// Don't allow removing the first syllable
if (syl_idx == 0) return;
Syllable &syl = syls[syl_idx];
Syllable &prev = syls[syl_idx - 1];
prev.duration += syl.duration;
for (ovr_iterator it = syl.ovr_tags.begin(); it != syl.ovr_tags.end(); ++it) {
prev.ovr_tags[it->first + prev.text.size()] = it->second;
}
prev.text += syl.text;
syls.erase(syls.begin() + syl_idx);
if (!no_announce) AnnounceSyllablesChanged();
}
void AssKaraoke::SetStartTime(size_t syl_idx, int time) {
// Don't allow moving the first syllable
if (syl_idx == 0) return;
Syllable &syl = syls[syl_idx];
Syllable &prev = syls[syl_idx - 1];
assert(time >= prev.start_time);
assert(time <= syl.start_time + syl.duration);
int delta = time - syl.start_time;
syl.start_time = time;
syl.duration -= delta;
prev.duration += delta;
}
void AssKaraoke::SplitLines(std::set<AssDialogue*> const& lines, agi::Context *c) {
if (lines.empty()) return;
AssKaraoke kara;
std::set<AssDialogue*> sel = c->selectionController->GetSelectedSet();
bool did_split = false;
for (std::list<AssEntry*>::iterator it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) {
AssDialogue *diag = dynamic_cast<AssDialogue*>(*it);
if (!diag || !lines.count(diag)) continue;
kara.SetLine(diag);
// If there aren't at least two tags there's nothing to split
if (kara.size() < 2) continue;
bool in_sel = sel.count(diag) > 0;
c->ass->Line.erase(it++);
for (iterator kit = kara.begin(); kit != kara.end(); ++kit) {
AssDialogue *new_line = new AssDialogue(*diag);
new_line->Start.SetMS(kit->start_time);
new_line->End.SetMS(kit->start_time + kit->duration);
new_line->Text = kit->GetText(false);
c->ass->Line.insert(it, new_line);
if (in_sel)
sel.insert(new_line);
}
sel.erase(diag);
delete diag;
--it;
}
c->selectionController->SetSelectedSet(sel);
if (!sel.count(c->selectionController->GetActiveLine()))
c->selectionController->SetActiveLine(*sel.begin());
if (did_split)
c->ass->Commit(_("splitting"), AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL);
}

View file

@ -1,29 +1,16 @@
// Copyright (c) 2007, Niels Martin Hansen // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// //
// * Redistributions of source code must retain the above copyright notice, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 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/ // Aegisub Project http://www.aegisub.org/
// //
@ -36,37 +23,79 @@
#ifndef AGI_PRE #ifndef AGI_PRE
#include <map>
#include <set>
#include <vector> #include <vector>
#include <wx/string.h>
#endif #endif
#include "ass_dialogue.h" #include <libaegisub/signal.h>
namespace agi { struct Context; }
class AssDialogue;
/// DOCME /// @class AssKaraoke
struct AssKaraokeSyllable { /// @brief Karaoke parser and parsed karaoke data model
class AssKaraoke {
public:
/// Parsed syllable data
struct Syllable {
int start_time; ///< Start time relative to time zero (not line start) in milliseconds
int duration; ///< Duration in milliseconds
wxString text; ///< Stripped syllable text
wxString tag_type; ///< \k, \kf or \ko
/// Non-karaoke override tags in this syllable. Key is an index in text
/// before which the value should be inserted
std::map<size_t, wxString> ovr_tags;
/// DOCME /// Get the text of this line with override tags and optionally the karaoke tag
int duration; // centiseconds wxString GetText(bool k_tag) const;
};
private:
typedef std::map<size_t, wxString>::iterator ovr_iterator;
std::vector<Syllable> syls;
AssDialogue *active_line;
/// DOCME bool no_announce;
wxString text; // stripped text of syllable
/// DOCME agi::signal::Signal<> AnnounceSyllablesChanged;
wxString unstripped_text; // including misc. tags
/// DOCME public:
wxString type; // highlight type, \k \K \kf \ko (backslash included) /// Constructor
/// @param line Initial line
/// @param auto_split Should the line automatically be split on spaces if there are no k tags?
AssKaraoke(AssDialogue *line = 0, bool auto_split = false);
/// DOCME /// Parse a dialogue line
AssOverrideTag *tag; // parsed override tag for direct modification void SetLine(AssDialogue *line, bool auto_split = false);
AssKaraokeSyllable(); /// Add a split before character pos in syllable syl_idx
void AddSplit(size_t syl_idx, size_t pos);
/// Remove the split at the given index
void RemoveSplit(size_t syl_idx);
/// Set the start time of a syllable in ms
void SetStartTime(size_t syl_idx, int time);
typedef std::vector<Syllable>::const_iterator iterator;
iterator begin() const { return syls.begin(); }
iterator end() const { return syls.end(); }
size_t size() const { return syls.size(); }
/// Get the line's text with k tags
wxString GetText() const;
/// Get the karaoke tag type used, with leading slash
/// @returns "\k", "\kf", or "\ko"
wxString GetTagType() const;
/// Set the tag type for all karaoke tags in this line
void SetTagType(wxString const& new_type);
/// Split lines so that each syllable is its own line
/// @param lines Lines to split
/// @param c Project context
static void SplitLines(std::set<AssDialogue*> const& lines, agi::Context *c);
DEFINE_SIGNAL_ADDERS(AnnounceSyllablesChanged, AddSyllablesChangedListener)
}; };
/// DOCME
typedef std::vector<AssKaraokeSyllable> AssKaraokeVector;
void ParseAssKaraokeTags(const AssDialogue *line, AssKaraokeVector &syls);

View file

@ -61,7 +61,6 @@
#include "audio_controller.h" #include "audio_controller.h"
#include "audio_display.h" #include "audio_display.h"
#include "audio_karaoke.h" #include "audio_karaoke.h"
#include "audio_timing.h"
#include "command/command.h" #include "command/command.h"
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "main.h" #include "main.h"
@ -86,9 +85,7 @@ AudioBox::AudioBox(wxWindow *parent, agi::Context *context)
: wxPanel(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxBORDER_RAISED) : wxPanel(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxBORDER_RAISED)
, audioDisplay(new AudioDisplay(this, context->audioController, context)) , audioDisplay(new AudioDisplay(this, context->audioController, context))
, controller(context->audioController) , controller(context->audioController)
, timing_controller_dialogue(CreateDialogueTimingController(controller, context->selectionController, context->ass))
, context(context) , context(context)
, karaokeMode(false)
{ {
// Zoom // Zoom
HorizontalZoom = new wxSlider(this,Audio_Horizontal_Zoom,0,-50,30,wxDefaultPosition,wxSize(-1,20),wxSL_VERTICAL|wxSL_BOTH); HorizontalZoom = new wxSlider(this,Audio_Horizontal_Zoom,0,-50,30,wxDefaultPosition,wxSize(-1,20),wxSL_VERTICAL|wxSL_BOTH);
@ -120,51 +117,15 @@ AudioBox::AudioBox(wxWindow *parent, agi::Context *context)
TopSizer->Add(HorizontalZoom,0,wxEXPAND,0); TopSizer->Add(HorizontalZoom,0,wxEXPAND,0);
TopSizer->Add(VertVolArea,0,wxEXPAND,0); TopSizer->Add(VertVolArea,0,wxEXPAND,0);
// Buttons sizer context->karaoke = new AudioKaraoke(this, context);
wxSizer *ButtonSizer = new wxBoxSizer(wxHORIZONTAL);
KaraokeButton = new wxBitmapToggleButton(this,Audio_Button_Karaoke,GETIMAGE(kara_mode_16));
KaraokeButton->SetToolTip(_("Toggle karaoke mode"));
ButtonSizer->Add(KaraokeButton,0,wxRIGHT|wxEXPAND,0);
// Karaoke sizer
karaokeSizer = new wxBoxSizer(wxHORIZONTAL);
JoinSplitSizer = new wxBoxSizer(wxHORIZONTAL);
JoinButton = new wxBitmapButton(this,Audio_Button_Join,GETIMAGE(kara_join_16));
JoinButton->SetToolTip(_("Join selected syllables"));
SplitButton = new wxBitmapButton(this,Audio_Button_Split,GETIMAGE(kara_split_16));
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_16));
CancelButton->SetToolTip(_("Commit splits and leave split-mode"));
AcceptButton = new wxBitmapButton(this,Audio_Button_Accept,GETIMAGE(kara_split_cancel_16));
AcceptButton->SetToolTip(_("Discard all splits and leave split-mode"));
CancelAcceptSizer->Add(CancelButton,0,wxRIGHT|wxEXPAND,0);
CancelAcceptSizer->Add(AcceptButton,0,wxRIGHT|wxEXPAND,0);
karaokeSizer->Add(JoinSplitSizer,0,wxRIGHT|wxEXPAND,0);
karaokeSizer->Add(CancelAcceptSizer,0,wxRIGHT|wxEXPAND,0);
audioKaraoke = new AudioKaraoke(this);
audioKaraoke->box = this;
audioKaraoke->display = audioDisplay;
karaokeSizer->Add(audioKaraoke,1,wxEXPAND,0);
// Main sizer // Main sizer
wxBoxSizer *MainSizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
MainSizer->Add(TopSizer,1,wxEXPAND|wxALL,3); MainSizer->Add(TopSizer,1,wxEXPAND|wxALL,3);
MainSizer->Add(toolbar::GetToolbar(this, "audio", context, "Audio"),0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3); MainSizer->Add(toolbar::GetToolbar(this, "audio", context, "Audio"),0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3);
MainSizer->Add(ButtonSizer); MainSizer->Add(context->karaoke,0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3);
MainSizer->Add(karaokeSizer,0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3);
MainSizer->AddSpacer(3); MainSizer->AddSpacer(3);
SetSizer(MainSizer); SetSizer(MainSizer);
SetKaraokeButtons(); // Decide which one to show or hide.
controller->SetTimingController(timing_controller_dialogue);
} }
AudioBox::~AudioBox() { } AudioBox::~AudioBox() { }
@ -173,13 +134,6 @@ BEGIN_EVENT_TABLE(AudioBox,wxPanel)
EVT_COMMAND_SCROLL(Audio_Horizontal_Zoom, AudioBox::OnHorizontalZoom) EVT_COMMAND_SCROLL(Audio_Horizontal_Zoom, AudioBox::OnHorizontalZoom)
EVT_COMMAND_SCROLL(Audio_Vertical_Zoom, AudioBox::OnVerticalZoom) EVT_COMMAND_SCROLL(Audio_Vertical_Zoom, AudioBox::OnVerticalZoom)
EVT_COMMAND_SCROLL(Audio_Volume, AudioBox::OnVolume) EVT_COMMAND_SCROLL(Audio_Volume, AudioBox::OnVolume)
EVT_BUTTON(Audio_Button_Join,AudioBox::OnJoin)
EVT_BUTTON(Audio_Button_Split,AudioBox::OnSplit)
EVT_BUTTON(Audio_Button_Cancel,AudioBox::OnCancel)
EVT_BUTTON(Audio_Button_Accept,AudioBox::OnAccept)
EVT_TOGGLEBUTTON(Audio_Button_Karaoke, AudioBox::OnKaraoke)
END_EVENT_TABLE() END_EVENT_TABLE()
void AudioBox::OnHorizontalZoom(wxScrollEvent &event) { void AudioBox::OnHorizontalZoom(wxScrollEvent &event) {
@ -212,72 +166,3 @@ void AudioBox::OnVerticalLink(agi::OptionValue const& opt) {
} }
VolumeBar->Enable(!opt.GetBool()); VolumeBar->Enable(!opt.GetBool());
} }
void AudioBox::OnKaraoke(wxCommandEvent &) {
LOG_D("audio/box") << "OnKaraoke";
audioDisplay->SetFocus();
if (karaokeMode) {
LOG_D("audio/box") << "karaoke enabled, disabling";
if (audioKaraoke->splitting) {
audioKaraoke->EndSplit(false);
}
karaokeMode = false;
audioKaraoke->enabled = false;
/// @todo Replace this with changing timing controller
//audioDisplay->SetDialogue();
audioKaraoke->Refresh(false);
}
else {
LOG_D("audio/box") << "karaoke disabled, enabling";
karaokeMode = true;
audioKaraoke->enabled = true;
/// @todo Replace this with changing timing controller
//audioDisplay->SetDialogue();
}
SetKaraokeButtons();
LOG_D("audio/box") << "returning";
}
void AudioBox::SetKaraokeButtons() {
// What to enable
bool join,split;
join = audioKaraoke->enabled && (audioKaraoke->splitting || audioKaraoke->selectionCount>=2);
split = audioKaraoke->enabled;
// If we set focus here, the audio display will continually steal the focus
// when navigating via the grid and karaoke is enabled. So don't.
//audioDisplay->SetFocus();
JoinButton->Enable(join);
SplitButton->Enable(split);
karaokeSizer->Show(CancelAcceptSizer, audioKaraoke->splitting);
karaokeSizer->Show(JoinSplitSizer, !audioKaraoke->splitting);
}
void AudioBox::OnJoin(wxCommandEvent &) {
LOG_D("audio/box") << "join";
audioDisplay->SetFocus();
audioKaraoke->Join();
}
void AudioBox::OnSplit(wxCommandEvent &) {
LOG_D("audio/box") << "split";
audioDisplay->SetFocus();
audioKaraoke->BeginSplit();
}
void AudioBox::OnCancel(wxCommandEvent &) {
LOG_D("audio/box") << "cancel";
audioDisplay->SetFocus();
audioKaraoke->EndSplit(true);
}
void AudioBox::OnAccept(wxCommandEvent &) {
LOG_D("audio/box") << "accept";
audioDisplay->SetFocus();
audioKaraoke->EndSplit(false);
}

View file

@ -45,8 +45,6 @@ namespace agi {
class AudioController; class AudioController;
class AudioDisplay; class AudioDisplay;
class AudioKaraoke;
class AudioTimingController;
class wxBitmapToggleButton; class wxBitmapToggleButton;
class wxButton; class wxButton;
class wxCommandEvent; class wxCommandEvent;
@ -63,9 +61,6 @@ class AudioBox : public wxPanel {
/// The controller controlling this audio box /// The controller controlling this audio box
AudioController *controller; AudioController *controller;
/// The regular dialogue timing controller
AudioTimingController *timing_controller_dialogue;
/// Project context this operates on /// Project context this operates on
agi::Context *context; agi::Context *context;
@ -78,53 +73,15 @@ class AudioBox : public wxPanel {
/// DOCME /// DOCME
wxSlider *VolumeBar; wxSlider *VolumeBar;
/// Karaoke box sizer
wxSizer *karaokeSizer;
/// Karaoke mode join syllable button.
wxButton *JoinButton;
/// Karaoke mode split word button.
wxButton *SplitButton;
/// Karaoke mode split/join cancel button.
wxButton *CancelButton;
/// Karaoke mode split/join accept button.
wxButton *AcceptButton;
/// Join/Split button sizer.
wxSizer *JoinSplitSizer;
/// Cancel/Accept sizer.
wxSizer *CancelAcceptSizer;
void OnHorizontalZoom(wxScrollEvent &event); void OnHorizontalZoom(wxScrollEvent &event);
void OnVerticalZoom(wxScrollEvent &event); void OnVerticalZoom(wxScrollEvent &event);
void OnVolume(wxScrollEvent &event); void OnVolume(wxScrollEvent &event);
void OnVerticalLink(agi::OptionValue const& opt); void OnVerticalLink(agi::OptionValue const& opt);
void OnKaraoke(wxCommandEvent &);
void OnJoin(wxCommandEvent &);
void OnSplit(wxCommandEvent &);
void OnCancel(wxCommandEvent &);
void OnAccept(wxCommandEvent &);
/// DOCME
AudioKaraoke *audioKaraoke;
/// DOCME
wxBitmapToggleButton *KaraokeButton;
/// DOCME
bool karaokeMode;
public: public:
AudioBox(wxWindow *parent, agi::Context *context); AudioBox(wxWindow *parent, agi::Context *context);
~AudioBox(); ~AudioBox();
void SetKaraokeButtons();
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };

View file

@ -139,6 +139,7 @@ public:
wxString text; wxString text;
/// Range which this label applies to /// Range which this label applies to
SampleRange range; SampleRange range;
AudioLabel(wxString const& text, SampleRange const& range) : text(text), range(range) { }
}; };
/// Virtual destructor, does nothing /// Virtual destructor, does nothing

File diff suppressed because it is too large Load diff

View file

@ -1,29 +1,16 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
// //
// * Redistributions of source code must retain the above copyright notice, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 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/ // Aegisub Project http://www.aegisub.org/
// //
@ -34,155 +21,127 @@
/// @ingroup audio_ui /// @ingroup audio_ui
/// ///
///////////
// Headers
#ifndef AGI_PRE #ifndef AGI_PRE
#include <set>
#include <vector> #include <vector>
#include <wx/log.h>
#include <wx/menu.h>
#include <wx/window.h> #include <wx/window.h>
#endif #endif
#include "ass_karaoke.h" #include <libaegisub/scoped_ptr.h>
#include <libaegisub/signal.h>
#include "selection_controller.h"
//////////////
// Prototypes
class AssDialogue; class AssDialogue;
class AssDialogueBlockOverride; class AssKaraoke;
class AssOverrideTag; class wxButton;
class AssOverrideParameter;
class AudioDisplay;
class AudioBox;
class AudioKaraokeTagMenu;
namespace agi { struct Context; }
/// DOCME
struct AudioKaraokeSyllable : AssKaraokeSyllable {
/// DOCME
int start_time; // centiseconds
/// DOCME
bool selected;
/// DOCME
std::vector<int> pending_splits;
/// DOCME
int display_w;
/// DOCME
int display_x;
AudioKaraokeSyllable();
AudioKaraokeSyllable(const AssKaraokeSyllable &base);
};
/// DOCME
typedef std::vector<AudioKaraokeSyllable> AudioKaraokeVector;
/// DOCME
/// @class AudioKaraoke /// @class AudioKaraoke
/// @brief DOCME /// @brief Syllable split and join UI for karaoke
/// ///
/// DOCME /// This class has two main responsibilities: the syllable split/join UI, and
class AudioKaraoke : public wxWindow { /// the karaoke mode controller. The split/join UI consists of the dialogue
friend class AudioKaraokeTagMenu; /// line with spaces and lines at each syllable split point. Clicking on a line
private: /// removes that \k tag; clicking anywhere else inserts a new \k tag with
/// interpolated duration. Added or removed splits are not autocommitted and
/// must be explicitly accepted or rejected. This is for two reasons:
/// 1. It's easy for a stray click on the split/join bar to go unnoticed,
/// making autocommitting somewhat error-prone.
/// 2. When a line with zero \k tags is activated, it's automatically split
/// at each space. This clearly should not automatically update the line
/// (changing the active selection should never directly change the file
/// itself), so there must be a notion of pending splits.
///
/// As the karaoke controller, it owns the AssKaraoke instance shared by this
/// class and the karaoke timing controller, and is responsible for switching
/// between timing controllers when entering and leaving karaoke mode. Ideally
/// the creation of the dialogue timing controller should probably be done
/// elsewhere, but there currently isn't any particularly appropriate place and
/// it's not worth caring about. The KaraokeController duties should perhaps be
/// split off into its own class, but at the moment they're insignificant
/// enough that it's not worth it.
///
/// The shared AssKaraoke instance is primarily to improve the handling of
/// pending splits. When a split is added removed, or a line is autosplit,
/// the audio display immediately reflects the changes, but the file is not
/// actually updated until the line is committed (which if auto-commit timing
/// changes is on, will happen as soon as the user adjusts the timing of the
/// new syllable).
class AudioKaraoke : public wxWindow, private SelectionListener<AssDialogue> {
agi::Context *c; ///< Project context
agi::signal::Connection file_changed; ///< File changed slot
/// DOCME /// Currently active dialogue line
AssDialogue *diag; AssDialogue *active_line;
/// Karaoke data
agi::scoped_ptr<AssKaraoke> kara;
/// DOCME /// Current line's stripped text with spaces added between each syllable
AssDialogue *workDiag; wxString spaced_text;
/// DOCME /// Indexes in spaced_text which are the beginning of syllables
int startClickSyl; std::vector<int> syl_start_points;
/// DOCME /// x coordinate in pixels of the separator lines of each syllable
bool must_rebuild; std::vector<int> syl_lines;
/// Left x coordinate of each character in spaced_text in pixels
std::vector<int> char_x;
/// DOCME int char_height; ///< Maximum character height in pixels
int split_cursor_syl; int char_width; ///< Maximum character width in pixels
int mouse_pos; ///< Last x coordinate of the mouse
/// DOCME wxFont split_font; ///< Font used in the split/join interface
int split_cursor_x;
void AutoSplit(); bool enabled; ///< Is karaoke mode enabled?
bool ParseDialogue(AssDialogue *diag);
int GetSylAtX(int x); wxButton *accept_button; ///< Accept pending splits button
int SplitSyl(unsigned int n); wxButton *cancel_button; ///< Revert pending changes
void OnPaint(wxPaintEvent &event); wxWindow *split_area; ///< The split/join window
void OnSize(wxSizeEvent &event);
/// Load syllable data from the currently active line
void LoadFromLine();
/// Cache presentational data from the loaded syllable data
void SetDisplayText();
/// Helper function for context menu creation
void AddMenuItem(wxMenu &menu, wxString const& tag, wxString const& help, wxString const& selected);
/// Set the karaoke tags for the selected syllables to the indicated one
void SetTagType(wxString new_type);
/// Refresh the area of the display around a single character
/// @param pos Index in spaced_text
void LimitedRefresh(int pos);
/// Reset all pending split information and return to normal mode
void CancelSplit();
/// Apply any pending split information to the syllable data and return to normal mode
void AcceptSplit();
void OnActiveLineChanged(AssDialogue *new_line);
void OnContextMenu(wxContextMenuEvent&);
void OnEnableButton(wxCommandEvent &evt);
void OnFileChanged(int type);
void OnMouse(wxMouseEvent &event); void OnMouse(wxMouseEvent &event);
void OnPaint(wxPaintEvent &event);
void OnSelectedSetChanged(Selection const&, Selection const&) { }
public: public:
/// Constructor
/// @param parent Parent window
/// @param c Project context
AudioKaraoke(wxWindow *parent, agi::Context *c);
/// Destructor
~AudioKaraoke();
/// DOCME /// Is karaoke mode currently enabled?
AudioDisplay *display; bool IsEnabled() const { return enabled; }
/// DOCME /// Enable or disable karaoke mode
AudioBox *box; void SetEnabled(bool enable);
/// DOCME
int curSyllable;
/// DOCME
int selectionCount;
/// DOCME
bool enabled;
/// DOCME
bool splitting;
/// DOCME
AudioKaraokeVector syllables;
AudioKaraoke(wxWindow *parent);
virtual ~AudioKaraoke();
bool LoadFromDialogue(AssDialogue *diag);
void Commit();
void SetSyllable(int n);
void SetSelection(int start,int end=-1);
bool SyllableDelta(int n,int delta,int mode);
void Join();
void BeginSplit();
void EndSplit(bool commit=true);
DECLARE_EVENT_TABLE()
};
/// DOCME
/// @class AudioKaraokeTagMenu
/// @brief DOCME
///
/// DOCME
class AudioKaraokeTagMenu : public wxMenu {
private:
/// DOCME
AudioKaraoke *kara;
void OnSelectItem(wxCommandEvent &event);
public:
AudioKaraokeTagMenu(AudioKaraoke *_kara);
virtual ~AudioKaraokeTagMenu();
DECLARE_EVENT_TABLE()
}; };

View file

@ -35,8 +35,10 @@
class AssDialogue; class AssDialogue;
class AssFile; class AssFile;
class AudioController; class AssKaraoke;
namespace agi { struct Context; }
#include "audio_controller.h"
#include "selection_controller.h" #include "selection_controller.h"
#include <libaegisub/signal.h> #include <libaegisub/signal.h>
@ -157,3 +159,8 @@ public:
/// lines being timed /// lines being timed
/// @param ass The file being timed /// @param ass The file being timed
AudioTimingController *CreateDialogueTimingController(AudioController *audio_controller, SelectionController<AssDialogue> *selection_controller, AssFile *ass); AudioTimingController *CreateDialogueTimingController(AudioController *audio_controller, SelectionController<AssDialogue> *selection_controller, AssFile *ass);
/// @brief Create a karaoke audio timing controller
/// @param c Project context
/// @param kara Karaoke model
AudioTimingController *CreateKaraokeTimingController(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed);

View file

@ -0,0 +1,326 @@
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id$
/// @file audio_timing_karaoke.cpp
/// @brief Timing mode for karaoke
/// @ingroup audio_ui
///
#include "config.h"
#include <libaegisub/signal.h>
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_karaoke.h"
#include "audio_timing.h"
#include "include/aegisub/context.h"
#include "main.h"
#include "utils.h"
#ifndef AGI_PRE
#include <deque>
#endif
/// @class KaraokeMarker
/// @brief AudioMarker implementation for AudioTimingControllerKaraoke
class KaraokeMarker : public AudioMarker {
int64_t position;
wxPen *pen;
FeetStyle style;
public:
int64_t GetPosition() const { return position; }
wxPen GetStyle() const { return *pen; }
FeetStyle GetFeet() const { return style; }
bool CanSnap() const { return false; }
void Move(int64_t new_pos) { position = new_pos; }
KaraokeMarker(int64_t position, wxPen *pen, FeetStyle style)
: position(position)
, pen(pen)
, style(style)
{
}
KaraokeMarker(int64_t position) : position(position) { }
operator int64_t() const { return position; }
};
/// @class AudioTimingControllerKaraoke
/// @brief Karaoke timing mode for timing subtitles
///
/// Displays the active line with draggable markers between each pair of
/// adjacent syllables, along with the text of each syllable.
///
/// This does not support \kt, as it inherently requires that the end time of
/// one syllable be the same as the start time of the next one.
class AudioTimingControllerKaraoke : public AudioTimingController {
std::deque<agi::signal::Connection> slots;
agi::signal::Connection& file_changed_slot;
agi::Context *c; ///< Project context
AssDialogue *active_line; ///< Currently active line
AssKaraoke *kara; ///< Parsed karaoke model provided by karaoke controller
size_t cur_syl; ///< Index of currently selected syllable in the line
/// Pen used for the mid-syllable markers
wxPen separator_pen;
/// Pen used for the start-of-line marker
wxPen start_pen;
/// Pen used for the end-of-line marker
wxPen end_pen;
/// Immobile marker for the beginning of the line
KaraokeMarker start_marker;
/// Immobile marker for the end of the line
KaraokeMarker end_marker;
/// Mobile markers between each pair of syllables
std::vector<KaraokeMarker> markers;
/// Labels containing the stripped text of each syllable
std::vector<AudioLabel> labels;
bool auto_commit; ///< Should changes be automatically commited?
bool auto_next; ///< Should user-initiated commits automatically go to the next?
int commit_id; ///< Last commit id used for an autocommit
void OnAutoCommitChange(agi::OptionValue const& opt);
void OnAutoNextChange(agi::OptionValue const& opt);
/// Reload all style options from the user preferences
void RegenStyles();
int64_t ToMS(int64_t samples) const { return c->audioController->MillisecondsFromSamples(samples); }
int64_t ToSamples(int64_t ms) const { return c->audioController->SamplesFromMilliseconds(ms); }
void DoCommit();
public:
// AudioTimingController implementation
void GetMarkers(const SampleRange &range, AudioMarkerVector &out_markers) const;
wxString GetWarningMessage() const { return ""; }
SampleRange GetIdealVisibleSampleRange() const;
SampleRange GetPrimaryPlaybackRange() const;
bool HasLabels() const { return true; }
void GetLabels(const SampleRange &range, std::vector<AudioLabel> &out_labels) const;
void Next();
void Prev();
void Commit();
void Revert();
bool IsNearbyMarker(int64_t sample, int sensitivity) const;
AudioMarker * OnLeftClick(int64_t sample, int sensitivity);
AudioMarker * OnRightClick(int64_t sample, int sensitivity) { return 0; }
void OnMarkerDrag(AudioMarker *marker, int64_t new_position);
AudioTimingControllerKaraoke(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed);
};
AudioTimingController *CreateKaraokeTimingController(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed)
{
return new AudioTimingControllerKaraoke(c, kara, file_changed);
}
AudioTimingControllerKaraoke::AudioTimingControllerKaraoke(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed)
: file_changed_slot(file_changed)
, c(c)
, active_line(c->selectionController->GetActiveLine())
, kara(kara)
, cur_syl(0)
, start_marker(ToSamples(active_line->Start.GetMS()), &start_pen, AudioMarker::Feet_Right)
, end_marker(ToSamples(active_line->End.GetMS()), &start_pen, AudioMarker::Feet_Left)
, auto_commit(OPT_GET("Audio/Auto/Commit")->GetBool())
, auto_next(OPT_GET("Audio/Next Line on Commit")->GetBool())
, commit_id(-1)
{
slots.push_back(kara->AddSyllablesChangedListener(&AudioTimingControllerKaraoke::Revert, this));
slots.push_back(OPT_SUB("Audio/Auto/Commit", &AudioTimingControllerKaraoke::OnAutoCommitChange, this));
slots.push_back(OPT_SUB("Audio/Next Line on Commit", &AudioTimingControllerKaraoke::OnAutoNextChange, this));
slots.push_back(OPT_SUB("Audio/Line Boundaries Thickness", &AudioTimingControllerKaraoke::RegenStyles, this));
slots.push_back(OPT_SUB("Colour/Audio Display/Syllable Boundaries", &AudioTimingControllerKaraoke::RegenStyles, this));
slots.push_back(OPT_SUB("Colour/Audio Display/Line boundary Start", &AudioTimingControllerKaraoke::RegenStyles, this));
slots.push_back(OPT_SUB("Colour/Audio Display/Line boundary End", &AudioTimingControllerKaraoke::RegenStyles, this));
RegenStyles();
Revert();
}
void AudioTimingControllerKaraoke::RegenStyles() {
int width = OPT_GET("Audio/Line Boundaries Thickness")->GetInt();
separator_pen = wxPen(wxColour(OPT_GET("Colour/Audio Display/Syllable Boundaries")->GetColour()), 1, wxPENSTYLE_DOT);
start_pen = wxPen(wxColour(OPT_GET("Colour/Audio Display/Line boundary Start")->GetColour()), width);
end_pen = wxPen(wxColour(OPT_GET("Colour/Audio Display/Line boundary End")->GetColour()), width);
}
void AudioTimingControllerKaraoke::OnAutoCommitChange(agi::OptionValue const& opt) {
auto_commit = opt.GetBool();
}
void AudioTimingControllerKaraoke::OnAutoNextChange(agi::OptionValue const& opt) {
auto_next = opt.GetBool();
}
void AudioTimingControllerKaraoke::Next() {
++cur_syl;
if (cur_syl > markers.size()) {
--cur_syl;
c->selectionController->NextLine();
}
else
AnnounceUpdatedPrimaryRange();
}
void AudioTimingControllerKaraoke::Prev() {
if (cur_syl == 0) {
AssDialogue *old_line = active_line;
c->selectionController->PrevLine();
if (old_line != active_line) {
cur_syl = markers.size();
AnnounceUpdatedPrimaryRange();
}
}
else {
--cur_syl;
AnnounceUpdatedPrimaryRange();
}
}
SampleRange AudioTimingControllerKaraoke::GetPrimaryPlaybackRange() const {
return SampleRange(
cur_syl > 0 ? markers[cur_syl - 1] : start_marker,
cur_syl < markers.size() ? markers[cur_syl] : end_marker);
}
SampleRange AudioTimingControllerKaraoke::GetIdealVisibleSampleRange() const {
return SampleRange(start_marker, end_marker);
}
void AudioTimingControllerKaraoke::GetMarkers(SampleRange const& range, AudioMarkerVector &out) const {
size_t i;
for (i = 0; i < markers.size() && markers[i] < range.begin(); ++i) ;
for (; i < markers.size() && markers[i] < range.end(); ++i)
out.push_back(&markers[i]);
if (range.contains(start_marker)) out.push_back(&start_marker);
if (range.contains(end_marker)) out.push_back(&end_marker);
}
void AudioTimingControllerKaraoke::DoCommit() {
active_line->Text = kara->GetText();
file_changed_slot.Block();
commit_id = c->ass->Commit(_("karaoke timing"), AssFile::COMMIT_TEXT, commit_id);
file_changed_slot.Unblock();
}
void AudioTimingControllerKaraoke::Commit() {
if (!auto_commit)
Commit();
if (auto_next)
c->selectionController->NextLine();
}
void AudioTimingControllerKaraoke::Revert() {
active_line = c->selectionController->GetActiveLine();
cur_syl = 0;
commit_id = -1;
start_marker.Move(ToSamples(active_line->Start.GetMS()));
end_marker.Move(ToSamples(active_line->End.GetMS()));
markers.clear();
labels.clear();
markers.reserve(kara->size());
labels.reserve(kara->size());
for (AssKaraoke::iterator it = kara->begin(); it != kara->end(); ++it) {
int64_t sample = ToSamples(it->start_time);
if (it != kara->begin())
markers.push_back(KaraokeMarker(sample, &separator_pen, AudioMarker::Feet_None));
labels.push_back(AudioLabel(it->text, SampleRange(sample, ToSamples(it->start_time + it->duration))));
}
AnnounceUpdatedPrimaryRange();
AnnounceMarkerMoved(0);
}
bool AudioTimingControllerKaraoke::IsNearbyMarker(int64_t sample, int sensitivity) const {
SampleRange range(sample - sensitivity, sample + sensitivity);
for (size_t i = 0; i < markers.size(); ++i)
if (range.contains(markers[i]))
return true;
return false;
}
AudioMarker *AudioTimingControllerKaraoke::OnLeftClick(int64_t sample, int sensitivity) {
SampleRange range(sample - sensitivity, sample + sensitivity);
size_t syl = distance(markers.begin(), lower_bound(markers.begin(), markers.end(), sample));
if (syl < markers.size() && range.contains(markers[syl]))
return &markers[syl];
if (syl > 0 && range.contains(markers[syl - 1]))
return &markers[syl - 1];
cur_syl = syl;
AnnounceUpdatedPrimaryRange();
return 0;
}
void AudioTimingControllerKaraoke::OnMarkerDrag(AudioMarker *m, int64_t new_position) {
KaraokeMarker *marker = static_cast<KaraokeMarker*>(m);
// No rearranging of syllables allowed
new_position = mid(
marker == &markers.front() ? start_marker.GetPosition() : (marker - 1)->GetPosition(),
new_position,
marker == &markers.back() ? end_marker.GetPosition() : (marker + 1)->GetPosition());
marker->Move(new_position);
size_t syl = marker - &markers.front() + 1;
kara->SetStartTime(syl, ToMS(new_position));
if (syl == cur_syl || syl + 1 == cur_syl)
AnnounceUpdatedPrimaryRange();
AnnounceMarkerMoved(m);
labels[syl - 1].range = SampleRange(labels[syl - 1].range.begin(), new_position);
labels[syl].range = SampleRange(new_position, labels[syl].range.end());
AnnounceLabelChanged(&labels[syl - 1]);
AnnounceLabelChanged(&labels[syl]);
if (auto_commit)
DoCommit();
else
commit_id = -1;
}
void AudioTimingControllerKaraoke::GetLabels(SampleRange const& range, std::vector<AudioLabel> &out) const {
for (size_t i = 0; i < labels.size(); ++i) {
if (range.overlaps(labels[i].range))
out.push_back(labels[i]);
}
}

View file

@ -46,6 +46,7 @@
#include "../ass_dialogue.h" #include "../ass_dialogue.h"
#include "../audio_controller.h" #include "../audio_controller.h"
#include "../audio_karaoke.h"
#include "../audio_timing.h" #include "../audio_timing.h"
#include "../compat.h" #include "../compat.h"
#include "../include/aegisub/context.h" #include "../include/aegisub/context.h"
@ -423,6 +424,22 @@ struct audio_vertical_link : public Command {
} }
}; };
/// Toggle karaoke mode
struct audio_karaoke : public Command {
CMD_NAME("audio/karaoke")
STR_MENU("Toggle karaoke mode")
STR_DISP("Toggle karaoke mode")
STR_HELP("Toggle karaoke mode")
CMD_TYPE(COMMAND_TOGGLE)
bool IsActive(const agi::Context *c) {
return c->karaoke->IsEnabled();
}
void operator()(agi::Context *c) {
c->karaoke->SetEnabled(!c->karaoke->IsEnabled());
}
};
/// @} /// @}
} }
@ -453,5 +470,6 @@ namespace cmd {
reg(new audio_vertical_link); reg(new audio_vertical_link);
reg(new audio_view_spectrum); reg(new audio_view_spectrum);
reg(new audio_view_waveform); reg(new audio_view_waveform);
reg(new audio_karaoke);
} }
} }

View file

@ -48,6 +48,7 @@
#include "../ass_dialogue.h" #include "../ass_dialogue.h"
#include "../ass_file.h" #include "../ass_file.h"
#include "../ass_karaoke.h"
#include "../dialog_search_replace.h" #include "../dialog_search_replace.h"
#include "../include/aegisub/context.h" #include "../include/aegisub/context.h"
#include "../subs_edit_ctrl.h" #include "../subs_edit_ctrl.h"
@ -303,16 +304,7 @@ struct edit_line_split_by_karaoke : public validate_sel_nonempty {
STR_HELP("Uses karaoke timing to split line into multiple smaller lines.") STR_HELP("Uses karaoke timing to split line into multiple smaller lines.")
void operator()(agi::Context *c) { void operator()(agi::Context *c) {
c->subsGrid->BeginBatch(); AssKaraoke::SplitLines(c->selectionController->GetSelectedSet(), c);
wxArrayInt sels = c->subsGrid->GetSelection();
bool didSplit = false;
for (int i = sels.size() - 1; i >= 0; --i) {
didSplit |= c->subsGrid->SplitLineByKaraoke(sels[i]);
}
if (didSplit) {
c->ass->Commit(_("splitting"), AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL);
}
c->subsGrid->EndBatch();
} }
}; };

View file

@ -94,6 +94,7 @@ INSERT_ICON("app/updates", blank_button)
INSERT_ICON("audio/close", close_audio_menu) INSERT_ICON("audio/close", close_audio_menu)
INSERT_ICON("audio/commit", button_audio_commit) INSERT_ICON("audio/commit", button_audio_commit)
INSERT_ICON("audio/go_to", button_audio_goto) INSERT_ICON("audio/go_to", button_audio_goto)
INSERT_ICON("audio/karaoke", kara_mode)
INSERT_ICON("audio/open", open_audio_menu) INSERT_ICON("audio/open", open_audio_menu)
INSERT_ICON("audio/open/video", open_audio_from_video_menu) INSERT_ICON("audio/open/video", open_audio_from_video_menu)
INSERT_ICON("audio/opt/autocommit", toggle_audio_autocommit) INSERT_ICON("audio/opt/autocommit", toggle_audio_autocommit)

View file

@ -37,37 +37,35 @@
#include "config.h" #include "config.h"
#include "dialog_kara_timing_copy.h"
#ifndef AGI_PRE #ifndef AGI_PRE
#include <deque> #include <deque>
#include <vector>
#include <wx/checkbox.h>
#include <wx/combobox.h>
#include <wx/dcclient.h> #include <wx/dcclient.h>
#include <wx/listctrl.h>
#include <wx/msgdlg.h> #include <wx/msgdlg.h>
#include <wx/regex.h>
#include <wx/settings.h> #include <wx/settings.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/stattext.h> #include <wx/stattext.h>
#include <wx/string.h> #include <wx/string.h>
#endif #endif
#include "ass_dialogue.h"
#include "ass_file.h" #include "ass_file.h"
#include "ass_karaoke.h" #include "ass_karaoke.h"
#include "ass_override.h"
#include "ass_style.h"
#include "dialog_kara_timing_copy.h"
#include "help_button.h" #include "help_button.h"
#include "include/aegisub/context.h" #include "include/aegisub/context.h"
#include "kana_table.h"
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "main.h" #include "main.h"
#include "selection_controller.h" #include "selection_controller.h"
#include "utils.h" #include "utils.h"
#include "validators.h"
#include "video_context.h"
/// DOCME
#define TEXT_LABEL_SOURCE _("Source: ") #define TEXT_LABEL_SOURCE _("Source: ")
/// DOCME
#define TEXT_LABEL_DEST _("Dest: ") #define TEXT_LABEL_DEST _("Dest: ")
// IDs // IDs
@ -89,126 +87,58 @@ enum {
/// ///
/// DOCME /// DOCME
class KaraokeLineMatchDisplay : public wxControl { class KaraokeLineMatchDisplay : public wxControl {
typedef AssKaraoke::Syllable MatchSyllable;
/// DOCME
struct MatchSyllable {
/// DOCME
size_t dur;
/// DOCME
wxString text;
/// @brief DOCME
/// @param _dur
/// @param _text
///
MatchSyllable(int _dur, const wxString &_text) : dur(_dur), text(_text) { }
};
/// DOCME
struct MatchGroup { struct MatchGroup {
/// DOCME
std::vector<MatchSyllable> src; std::vector<MatchSyllable> src;
/// DOCME
typedef std::vector<MatchSyllable>::iterator SrcIterator;
/// DOCME
wxString dst; wxString dst;
/// DOCME
size_t duration;
/// DOCME
int last_render_width; int last_render_width;
MatchGroup() : last_render_width(0) { }
/// @brief DOCME
///
MatchGroup() : duration(0), last_render_width(0) { }
}; };
/// DOCME
std::vector<MatchGroup> matched_groups; std::vector<MatchGroup> matched_groups;
/// DOCME
typedef std::vector<MatchGroup>::iterator MatchedGroupIterator;
/// DOCME
std::deque<MatchSyllable> unmatched_source; std::deque<MatchSyllable> unmatched_source;
/// DOCME
typedef std::deque<MatchSyllable>::iterator UnmatchedSourceIterator;
/// DOCME
wxString unmatched_destination; wxString unmatched_destination;
/// DOCME
int last_total_matchgroup_render_width; int last_total_matchgroup_render_width;
/// DOCME
size_t source_sel_length; size_t source_sel_length;
/// DOCME
size_t destination_sel_length; size_t destination_sel_length;
/// DOCME
bool has_source, has_destination;
void OnPaint(wxPaintEvent &event); void OnPaint(wxPaintEvent &event);
void OnKeyboard(wxKeyEvent &event);
void OnFocusEvent(wxFocusEvent &event);
/// DOCME
/// DOCME
const wxString label_source, label_destination; const wxString label_source, label_destination;
public: public:
// Start processing a new line pair /// Start processing a new line pair
void SetInputData(const AssDialogue *src, const AssDialogue *dst); void SetInputData(AssDialogue *src, AssDialogue *dst);
// Build and return the output line from the matched syllables /// Build and return the output line from the matched syllables
wxString GetOutputLine(); wxString GetOutputLine() const;
// Number of syllables not yet matched from source /// Number of syllables not yet matched from source
size_t GetRemainingSource(); size_t GetRemainingSource() const { return unmatched_source.size(); }
// Number of characters not yet matched from destination /// Number of characters not yet matched from destination
size_t GetRemainingDestination(); size_t GetRemainingDestination() const { return unmatched_destination.size(); }
// Adjust source and destination match lengths // Adjust source and destination match lengths
void IncreaseSourceMatch(); void IncreaseSourceMatch();
void DecreaseSourceMatch(); void DecreaseSourceMatch();
void IncreseDestinationMatch(); void IncreseDestinationMatch();
void DecreaseDestinationMatch(); void DecreaseDestinationMatch();
// Attempt to treat source as Japanese romaji, destination as Japanese kana+kanji, and make an automatic match /// Attempt to treat source as Japanese romaji, destination as Japanese kana+kanji, and make an automatic match
void AutoMatchJapanese(); void AutoMatchJapanese();
// Accept current selection and save match /// Accept current selection and save match
bool AcceptMatch(); bool AcceptMatch();
// Undo last match, adding it back to the unmatched input /// Undo last match, adding it back to the unmatched input
bool UndoMatch(); bool UndoMatch();
// Constructor and destructor KaraokeLineMatchDisplay(wxWindow *parent);
KaraokeLineMatchDisplay(DialogKanjiTimer *parent);
~KaraokeLineMatchDisplay();
wxSize GetBestSize(); wxSize GetBestSize() const;
DECLARE_EVENT_TABLE()
}; };
KaraokeLineMatchDisplay::KaraokeLineMatchDisplay(wxWindow *parent)
/// @brief DOCME
/// @param parent
///
KaraokeLineMatchDisplay::KaraokeLineMatchDisplay(DialogKanjiTimer *parent)
: wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE|wxWANTS_CHARS) : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE|wxWANTS_CHARS)
, label_source(TEXT_LABEL_SOURCE) , label_source(TEXT_LABEL_SOURCE)
, label_destination(TEXT_LABEL_DEST) , label_destination(TEXT_LABEL_DEST)
@ -219,27 +149,19 @@ KaraokeLineMatchDisplay::KaraokeLineMatchDisplay(DialogKanjiTimer *parent)
wxSize best_size = GetBestSize(); wxSize best_size = GetBestSize();
SetMaxSize(wxSize(-1, best_size.GetHeight())); SetMaxSize(wxSize(-1, best_size.GetHeight()));
SetMinSize(best_size); SetMinSize(best_size);
Bind(wxEVT_SET_FOCUS, std::tr1::bind(&wxControl::Refresh, this, true, (const wxRect*)0));
Bind(wxEVT_KILL_FOCUS, std::tr1::bind(&wxControl::Refresh, this, true, (const wxRect*)0));
Bind(wxEVT_PAINT, &KaraokeLineMatchDisplay::OnPaint, this);
} }
wxSize KaraokeLineMatchDisplay::GetBestSize() const
/// @brief DOCME
///
KaraokeLineMatchDisplay::~KaraokeLineMatchDisplay()
{
// Nothing to do
}
/// @brief DOCME
/// @return
///
wxSize KaraokeLineMatchDisplay::GetBestSize()
{ {
int w_src, h_src, w_dst, h_dst; int w_src, h_src, w_dst, h_dst;
GetTextExtent(label_source, &w_src, &h_src); GetTextExtent(label_source, &w_src, &h_src);
GetTextExtent(label_destination, &w_dst, &h_dst); GetTextExtent(label_destination, &w_dst, &h_dst);
int min_width = (w_dst > w_src) ? w_dst : w_src; int min_width = std::max(w_dst, w_src);
// Magic number 7: // Magic number 7:
// 1 pixel for top black border, 1 pixel white top border in first row, 1 pixel white bottom padding in first row // 1 pixel for top black border, 1 pixel white top border in first row, 1 pixel white bottom padding in first row
@ -247,30 +169,13 @@ wxSize KaraokeLineMatchDisplay::GetBestSize()
return wxSize(min_width * 2, h_src + h_dst + 7); return wxSize(min_width * 2, h_src + h_dst + 7);
} }
BEGIN_EVENT_TABLE(KaraokeLineMatchDisplay,wxControl)
EVT_PAINT(KaraokeLineMatchDisplay::OnPaint)
EVT_KEY_DOWN(KaraokeLineMatchDisplay::OnKeyboard)
EVT_SET_FOCUS(KaraokeLineMatchDisplay::OnFocusEvent)
EVT_KILL_FOCUS(KaraokeLineMatchDisplay::OnFocusEvent)
END_EVENT_TABLE()
/// @brief DOCME
/// @param dc
/// @param txt
/// @param x
/// @param y
/// @return
///
int DrawBoxedText(wxDC &dc, const wxString &txt, int x, int y) int DrawBoxedText(wxDC &dc, const wxString &txt, int x, int y)
{ {
int tw, th; int tw, th;
// Assume the pen, brush and font properties have already been set in the DC. // Assume the pen, brush and font properties have already been set in the DC.
// Return the advance width, including box margins, borders etc // Return the advance width, including box margins, borders etc
if (txt == "") if (txt.empty())
{ {
// Empty string gets special handling: // Empty string gets special handling:
// The box is drawn in shorter width, to emphasize it's empty // The box is drawn in shorter width, to emphasize it's empty
@ -288,10 +193,6 @@ int DrawBoxedText(wxDC &dc, const wxString &txt, int x, int y)
} }
} }
/// @brief DOCME
/// @param event
///
void KaraokeLineMatchDisplay::OnPaint(wxPaintEvent &event) void KaraokeLineMatchDisplay::OnPaint(wxPaintEvent &event)
{ {
wxPaintDC dc(this); wxPaintDC dc(this);
@ -422,50 +323,27 @@ void KaraokeLineMatchDisplay::OnPaint(wxPaintEvent &event)
} }
// Remaining destination // Remaining destination
wxString txt = unmatched_destination.Left(destination_sel_length);
if (!txt.empty())
{
dc.SetTextBackground(sel_back); dc.SetTextBackground(sel_back);
dc.SetTextForeground(sel_text); dc.SetTextForeground(sel_text);
dc.SetBrush(wxBrush(sel_back)); dc.SetBrush(wxBrush(sel_back));
wxString txt = unmatched_destination.Left(destination_sel_length);
if (txt != "")
next_x += DrawBoxedText(dc, txt, next_x, y_line2); next_x += DrawBoxedText(dc, txt, next_x, y_line2);
}
txt = unmatched_destination.Mid(destination_sel_length);
if (!txt.empty())
{
dc.SetTextBackground(inner_back); dc.SetTextBackground(inner_back);
dc.SetTextForeground(inner_text); dc.SetTextForeground(inner_text);
dc.SetBrush(wxBrush(inner_back)); dc.SetBrush(wxBrush(inner_back));
txt = unmatched_destination.Mid(destination_sel_length);
if (txt != "")
DrawBoxedText(dc, txt, next_x, y_line2); DrawBoxedText(dc, txt, next_x, y_line2);
}
} }
void KaraokeLineMatchDisplay::SetInputData(AssDialogue *src, AssDialogue *dst)
/// @brief DOCME
/// @param event
///
void KaraokeLineMatchDisplay::OnKeyboard(wxKeyEvent &event)
{ {
((DialogKanjiTimer*)GetParent())->OnKeyDown(event);
}
/// @brief DOCME
/// @param event
///
void KaraokeLineMatchDisplay::OnFocusEvent(wxFocusEvent &event)
{
Refresh(true);
}
/// @brief DOCME
/// @param src
/// @param dst
///
void KaraokeLineMatchDisplay::SetInputData(const AssDialogue *src, const AssDialogue *dst)
{
has_source = src != 0;
has_destination = dst != 0;
last_total_matchgroup_render_width = 0; last_total_matchgroup_render_width = 0;
matched_groups.clear(); matched_groups.clear();
@ -474,16 +352,9 @@ void KaraokeLineMatchDisplay::SetInputData(const AssDialogue *src, const AssDial
source_sel_length = 0; source_sel_length = 0;
if (src) if (src)
{ {
AssDialogue *varsrc = dynamic_cast<AssDialogue*>(src->Clone()); AssKaraoke kara(src);
varsrc->ParseASSTags(); copy(kara.begin(), kara.end(), back_inserter(unmatched_source));
AssKaraokeVector kara; source_sel_length = 1;
ParseAssKaraokeTags(varsrc, kara);
// Start from 1 instead of 0: The first syllable is actually everything before the first
for (size_t i = 1; i < kara.size(); ++i)
{
unmatched_source.push_back(MatchSyllable(kara[i].duration, kara[i].text));
}
delete varsrc;
} }
unmatched_destination.clear(); unmatched_destination.clear();
@ -491,121 +362,73 @@ void KaraokeLineMatchDisplay::SetInputData(const AssDialogue *src, const AssDial
if (dst) if (dst)
{ {
unmatched_destination = dst->GetStrippedText(); unmatched_destination = dst->GetStrippedText();
if (!unmatched_destination.empty())
{
destination_sel_length = 1;
}
} }
IncreaseSourceMatch();
IncreseDestinationMatch();
Refresh(true); Refresh(true);
} }
wxString KaraokeLineMatchDisplay::GetOutputLine() const
/// @brief DOCME
/// @return
///
wxString KaraokeLineMatchDisplay::GetOutputLine()
{ {
wxString res; wxString res;
for (size_t i = 0; i < matched_groups.size(); ++i) for (size_t i = 0; i < matched_groups.size(); ++i)
{ {
MatchGroup &match = matched_groups[i]; const MatchGroup &match = matched_groups[i];
res = wxString::Format("%s{\\k%d}%s", res, match.duration, match.dst); int duration = 0;
for (size_t j = 0; j < match.src.size(); ++j)
{
duration += match.src[j].duration;
}
res = wxString::Format("%s{\\k%d}%s", res, duration, match.dst);
} }
return res; return res;
} }
/// @brief DOCME
/// @return
///
size_t KaraokeLineMatchDisplay::GetRemainingSource()
{
return unmatched_source.size();
}
/// @brief DOCME
/// @return
///
size_t KaraokeLineMatchDisplay::GetRemainingDestination()
{
return unmatched_destination.size();
}
/// @brief DOCME
///
void KaraokeLineMatchDisplay::IncreaseSourceMatch() void KaraokeLineMatchDisplay::IncreaseSourceMatch()
{ {
source_sel_length += 1; source_sel_length = std::min(source_sel_length + 1, GetRemainingSource());
if (source_sel_length > GetRemainingSource())
source_sel_length = GetRemainingSource();
Refresh(true); Refresh(true);
} }
/// @brief DOCME
///
void KaraokeLineMatchDisplay::DecreaseSourceMatch() void KaraokeLineMatchDisplay::DecreaseSourceMatch()
{ {
if (source_sel_length > 0) source_sel_length = std::max(source_sel_length, 1u) - 1;
source_sel_length -= 1;
Refresh(true); Refresh(true);
} }
/// @brief DOCME
///
void KaraokeLineMatchDisplay::IncreseDestinationMatch() void KaraokeLineMatchDisplay::IncreseDestinationMatch()
{ {
destination_sel_length += 1; destination_sel_length = std::min(destination_sel_length + 1, GetRemainingDestination());
if (destination_sel_length > GetRemainingDestination())
destination_sel_length = GetRemainingDestination();
Refresh(true); Refresh(true);
} }
/// @brief DOCME
///
void KaraokeLineMatchDisplay::DecreaseDestinationMatch() void KaraokeLineMatchDisplay::DecreaseDestinationMatch()
{ {
if (destination_sel_length > 0) destination_sel_length = std::max(destination_sel_length, 1u) - 1;
destination_sel_length -= 1;
Refresh(true); Refresh(true);
} }
/// Kana interpolation, in characters, unset to disable
#define KANA_SEARCH_DISTANCE 3
/// DOCME
#define KANA_SEARCH_DISTANCE 3 //Kana interpolation, in characters, unset to disable
/// DOCME
#define ROMAJI_SEARCH_DISTANCE 4 //Romaji interpolation, in karaoke groups, unset to disable
/// @brief DOCME
/// @return
///
void KaraokeLineMatchDisplay::AutoMatchJapanese() void KaraokeLineMatchDisplay::AutoMatchJapanese()
{ {
if (unmatched_source.size() < 1) return; if (unmatched_source.size() < 1) return;
// Quick escape: If there's no destination left, take all remaining source. // Quick escape: If there's no destination left, take all remaining source.
// (Usually this means the user made a mistake.) // (Usually this means the user made a mistake.)
if (unmatched_destination.size() == 0) if (unmatched_destination.empty())
{ {
source_sel_length = unmatched_source.size(); source_sel_length = unmatched_source.size();
destination_sel_length = 0; destination_sel_length = 0;
return; return;
} }
KanaTable kt;
typedef std::list<KanaEntry>::const_iterator KanaIterator;
// We'll first see if we can do something with the first unmatched source syllable // We'll first see if we can do something with the first unmatched source syllable
wxString src(unmatched_source[0].text.Lower()); wxString src(unmatched_source[0].text.Lower());
wxString dst(unmatched_destination); wxString dst(unmatched_destination);
@ -613,40 +436,43 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
destination_sel_length = 0; destination_sel_length = 0;
// Quick escape: If the source syllable is empty, return with first source syllable and empty destination // Quick escape: If the source syllable is empty, return with first source syllable and empty destination
if (src.size() == 0) if (src.empty())
{ {
return; return;
} }
// Try to match the next source syllable against the destination. // Try to match the next source syllable against the destination. Do it
// Do it "inverted", try all kana from the table and prefix-match them against the destination, // "inverted": try all kana from the table and prefix-match them against
// then if it matches a prefix, try to match the hepburn for it agast the source; eat if it matches. // the destination, then if it matches a prefix, try to match the hepburn
// Keep trying to match as long as there's text left in the source syllable or matching fails. // for it agast the source; eat if it matches. Keep trying to match as
// long as there's text left in the source syllable or matching fails.
while (src.size() > 0) while (src.size() > 0)
{ {
wxString dst_hira_rest, dst_kata_rest, src_rest; wxString dst_hira_rest, dst_kata_rest, src_rest;
bool matched = false; bool matched = false;
for (KanaIterator ke = kt.entries.begin(); ke != kt.entries.end(); ++ke) for (KanaEntry *ke = KanaTable; ke->hiragana; ++ke)
{ {
bool hira_matches = dst.StartsWith(ke->hiragana, &dst_hira_rest) && !ke->hiragana.IsEmpty(); if (src.StartsWith(ke->hepburn, &src_rest))
{
bool hira_matches = dst.StartsWith(ke->hiragana, &dst_hira_rest) && *ke->hiragana;
bool kata_matches = dst.StartsWith(ke->katakana, &dst_kata_rest); bool kata_matches = dst.StartsWith(ke->katakana, &dst_kata_rest);
bool roma_matches = src.StartsWith(ke->hepburn, &src_rest);
if ((hira_matches || kata_matches) && roma_matches) if (hira_matches || kata_matches)
{ {
matched = true; matched = true;
src = src_rest; src = src_rest;
dst = hira_matches ? dst_hira_rest : dst_kata_rest; dst = hira_matches ? dst_hira_rest : dst_kata_rest;
destination_sel_length += (hira_matches?ke->hiragana:ke->katakana).size(); destination_sel_length += wcslen(hira_matches ? ke->hiragana : ke->katakana);
break; break;
} }
} }
}
if (!matched) break; if (!matched) break;
} }
// The source might be empty now: That's good! // The source might be empty now: That's good!
// That means we managed to match it all against destination text // That means we managed to match it all against destination text
if (src.size() == 0) if (src.empty())
{ {
// destination_sel_length already has the appropriate value // destination_sel_length already has the appropriate value
// and source_sel_length was alredy 1 // and source_sel_length was alredy 1
@ -657,11 +483,8 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
// Eat all whitespace at the start of the destination. // Eat all whitespace at the start of the destination.
if (StringEmptyOrWhitespace(src)) if (StringEmptyOrWhitespace(src))
{ {
trycatchingmorespaces: while (IsWhitespace(unmatched_destination[destination_sel_length]))
// ASCII space ++destination_sel_length;
if (unmatched_destination[destination_sel_length] == L'\x20') { ++destination_sel_length; goto trycatchingmorespaces; }
// Japanese fullwidth ('ideographic') space
if (unmatched_destination[destination_sel_length] == L'\u3000') { ++destination_sel_length; goto trycatchingmorespaces; }
// Now we've eaten all spaces in the destination as well // Now we've eaten all spaces in the destination as well
// so the selection lengths should be good // so the selection lengths should be good
return; return;
@ -679,15 +502,17 @@ trycatchingmorespaces:
} }
#ifdef KANA_SEARCH_DISTANCE #ifdef KANA_SEARCH_DISTANCE
// Try to look up to KANA_SEARCH_DISTANCE characters ahead in destination, see if any of those // Try to look up to KANA_SEARCH_DISTANCE characters ahead in destination,
// are recognised kana. If there are any within the range, see if it matches a following syllable, // see if any of those are recognised kana. If there are any within the
// at most 5 source syllables per character in source we're ahead. // range, see if it matches a following syllable, at most 5 source
// The number 5 comes from the kanji with the longest readings: 'uketamawa.ru' and 'kokorozashi' // syllables per character in source we're ahead.
// which each has a reading consisting of five kana. // The number 5 comes from the kanji with the longest readings:
// Only match the found kana in destination against the beginning of source syllables, not the // 'uketamawa.ru' and 'kokorozashi' which each have a reading consisting of
// middle of them. // five kana.
// If a match is found, make a guess at how much source and destination should be selected based // Only match the found kana in destination against the beginning of source
// on the distances it was found at. // syllables, not the middle of them.
// If a match is found, make a guess at how much source and destination
// should be selected based on the distances it was found at.
dst = unmatched_destination; dst = unmatched_destination;
for (size_t lookahead = 0; lookahead < KANA_SEARCH_DISTANCE; ++lookahead) for (size_t lookahead = 0; lookahead < KANA_SEARCH_DISTANCE; ++lookahead)
{ {
@ -696,15 +521,15 @@ trycatchingmorespaces:
// Find a position where hiragana or katakana matches // Find a position where hiragana or katakana matches
wxString matched_roma; wxString matched_roma;
wxString matched_kana; wxString matched_kana;
for (KanaIterator ke = kt.entries.begin(); ke != kt.entries.end(); ++ke) for (KanaEntry *ke = KanaTable; ke->hiragana; ++ke)
{ {
if (!!ke->hiragana && dst.StartsWith(ke->hiragana)) if (*ke->hiragana && dst.StartsWith(ke->hiragana))
{ {
matched_roma = ke->hepburn; matched_roma = ke->hepburn;
matched_kana = ke->hiragana; matched_kana = ke->hiragana;
break; break;
} }
if (!!ke->katakana && dst.StartsWith(ke->katakana)) if (*ke->katakana && dst.StartsWith(ke->katakana))
{ {
matched_roma = ke->hepburn; matched_roma = ke->hepburn;
matched_kana = ke->katakana; matched_kana = ke->katakana;
@ -718,7 +543,7 @@ trycatchingmorespaces:
// For the magic number 5, see big comment block above // For the magic number 5, see big comment block above
int src_lookahead_max = (lookahead+1)*5; int src_lookahead_max = (lookahead+1)*5;
int src_lookahead_pos = 0; int src_lookahead_pos = 0;
for (UnmatchedSourceIterator ss = unmatched_source.begin(); ss != unmatched_source.end(); ++ss) for (std::deque<MatchSyllable>::iterator ss = unmatched_source.begin(); ss != unmatched_source.end(); ++ss)
{ {
// Check if we've gone too far ahead in the source // Check if we've gone too far ahead in the source
if (src_lookahead_pos++ >= src_lookahead_max) break; if (src_lookahead_pos++ >= src_lookahead_max) break;
@ -748,11 +573,6 @@ trycatchingmorespaces:
} }
#endif #endif
#ifdef ROMAJI_SEARCH_DISTANCE
// Not re-implemented (yet?)
// So far testing shows that the kana-based interpolation works just fine by itself.
#endif
// Okay so we didn't match anything. Aww. // Okay so we didn't match anything. Aww.
// Just fail... // Just fail...
// We know from earlier that we do have both some source and some destination. // We know from earlier that we do have both some source and some destination.
@ -761,11 +581,6 @@ trycatchingmorespaces:
return; return;
} }
/// @brief DOCME
/// @return
///
bool KaraokeLineMatchDisplay::AcceptMatch() bool KaraokeLineMatchDisplay::AcceptMatch()
{ {
MatchGroup match; MatchGroup match;
@ -777,13 +592,8 @@ bool KaraokeLineMatchDisplay::AcceptMatch()
} }
assert(source_sel_length <= unmatched_source.size()); assert(source_sel_length <= unmatched_source.size());
for (; source_sel_length > 0; source_sel_length--) copy(unmatched_source.begin(), unmatched_source.begin() + source_sel_length, back_inserter(match.src));
{ source_sel_length = 0;
match.src.push_back(unmatched_source.front());
match.duration += unmatched_source.front().dur;
unmatched_source.pop_front();
}
assert(source_sel_length == 0);
assert(destination_sel_length <= unmatched_destination.size()); assert(destination_sel_length <= unmatched_destination.size());
match.dst = unmatched_destination.Left(destination_sel_length); match.dst = unmatched_destination.Left(destination_sel_length);
@ -799,10 +609,6 @@ bool KaraokeLineMatchDisplay::AcceptMatch()
return true; return true;
} }
/// @brief DOCME
/// @return
///
bool KaraokeLineMatchDisplay::UndoMatch() bool KaraokeLineMatchDisplay::UndoMatch()
{ {
if (matched_groups.empty()) if (matched_groups.empty())
@ -813,11 +619,8 @@ bool KaraokeLineMatchDisplay::UndoMatch()
source_sel_length = group.src.size(); source_sel_length = group.src.size();
destination_sel_length = group.dst.size(); destination_sel_length = group.dst.size();
while (group.src.size() > 0) copy(group.src.rbegin(), group.src.rend(), front_inserter(unmatched_source));
{ group.src.clear();
unmatched_source.push_front(group.src.back());
group.src.pop_back();
}
unmatched_destination = group.dst + unmatched_destination; unmatched_destination = group.dst + unmatched_destination;
@ -828,15 +631,11 @@ bool KaraokeLineMatchDisplay::UndoMatch()
return true; return true;
} }
/// @brief Constructor
/// @param parent
DialogKanjiTimer::DialogKanjiTimer(agi::Context *c) DialogKanjiTimer::DialogKanjiTimer(agi::Context *c)
: wxDialog(c->parent,-1,_("Kanji timing"),wxDefaultPosition) : wxDialog(c->parent,-1,_("Kanji timing"),wxDefaultPosition)
{ {
// Set icon
SetIcon(BitmapToIcon(GETIMAGE(kara_timing_copier_24))); SetIcon(BitmapToIcon(GETIMAGE(kara_timing_copier_24)));
// Variables
subs = c->ass; subs = c->ass;
currentSourceLine = subs->Line.begin(); currentSourceLine = subs->Line.begin();
currentDestinationLine = subs->Line.begin(); currentDestinationLine = subs->Line.begin();
@ -857,10 +656,11 @@ DialogKanjiTimer::DialogKanjiTimer(agi::Context *c)
Interpolate = new wxCheckBox(this,-1,_("Attempt to interpolate kanji."),wxDefaultPosition,wxDefaultSize,wxALIGN_LEFT); Interpolate = new wxCheckBox(this,-1,_("Attempt to interpolate kanji."),wxDefaultPosition,wxDefaultSize,wxALIGN_LEFT);
Interpolate->SetValue(OPT_GET("Tool/Kanji Timer/Interpolation")->GetBool()); Interpolate->SetValue(OPT_GET("Tool/Kanji Timer/Interpolation")->GetBool());
SourceStyle=new wxComboBox(this,-1,"",wxDefaultPosition,wxSize(160,-1), wxArrayString styles = subs->GetStyles();
subs->GetStyles(),wxCB_READONLY,wxDefaultValidator,_("Source Style")); SourceStyle = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(160, -1),
DestStyle = new wxComboBox(this,-1,"",wxDefaultPosition,wxSize(160,-1), styles, wxCB_READONLY, wxDefaultValidator, _("Source Style"));
subs->GetStyles(),wxCB_READONLY,wxDefaultValidator,_("Dest Style")); DestStyle = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(160, -1),
styles, wxCB_READONLY, wxDefaultValidator, _("Dest Style"));
wxStaticText *ShortcutKeys = new wxStaticText(this,-1,_("When the destination textbox has focus, use the following keys:\n\nRight Arrow: Increase dest. selection length\nLeft Arrow: Decrease dest. selection length\nUp Arrow: Increase source selection length\nDown Arrow: Decrease source selection length\nEnter: Link, accept line when done\nBackspace: Unlink last")); wxStaticText *ShortcutKeys = new wxStaticText(this,-1,_("When the destination textbox has focus, use the following keys:\n\nRight Arrow: Increase dest. selection length\nLeft Arrow: Decrease dest. selection length\nUp Arrow: Increase source selection length\nDown Arrow: Decrease source selection length\nEnter: Link, accept line when done\nBackspace: Unlink last"));
@ -913,11 +713,10 @@ DialogKanjiTimer::DialogKanjiTimer(agi::Context *c)
SetSizerAndFit(MainStackSizer); SetSizerAndFit(MainStackSizer);
CenterOnParent(); CenterOnParent();
display->Bind(wxEVT_KEY_DOWN, &DialogKanjiTimer::OnKeyDown, this);
} }
///////////////
// Event table
BEGIN_EVENT_TABLE(DialogKanjiTimer,wxDialog) BEGIN_EVENT_TABLE(DialogKanjiTimer,wxDialog)
EVT_BUTTON(wxID_CLOSE,DialogKanjiTimer::OnClose) EVT_BUTTON(wxID_CLOSE,DialogKanjiTimer::OnClose)
EVT_BUTTON(BUTTON_KTSTART,DialogKanjiTimer::OnStart) EVT_BUTTON(BUTTON_KTSTART,DialogKanjiTimer::OnStart)
@ -932,33 +731,22 @@ BEGIN_EVENT_TABLE(DialogKanjiTimer,wxDialog)
EVT_TEXT_ENTER(TEXT_DEST,DialogKanjiTimer::OnKeyEnter) EVT_TEXT_ENTER(TEXT_DEST,DialogKanjiTimer::OnKeyEnter)
END_EVENT_TABLE() END_EVENT_TABLE()
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnClose(wxCommandEvent &event) { void DialogKanjiTimer::OnClose(wxCommandEvent &event) {
OPT_SET("Tool/Kanji Timer/Interpolation")->SetBool(Interpolate->IsChecked()); OPT_SET("Tool/Kanji Timer/Interpolation")->SetBool(Interpolate->IsChecked());
bool modified = !LinesToChange.empty();
while(LinesToChange.empty()==false) { for (size_t i = 0; i < LinesToChange.size(); ++i) {
std::pair<entryIter,wxString> p = LinesToChange.back(); LinesToChange[i].first->Text = LinesToChange[i].second;
LinesToChange.pop_back();
AssDialogue *line = dynamic_cast<AssDialogue*>(*p.first);
line->Text = p.second;
} }
if (modified) {
if (LinesToChange.size()) {
subs->Commit(_("kanji timing"), AssFile::COMMIT_DIAG_TEXT); subs->Commit(_("kanji timing"), AssFile::COMMIT_DIAG_TEXT);
LinesToChange.clear(); LinesToChange.clear();
} }
Close(); Close();
} }
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnStart(wxCommandEvent &event) { void DialogKanjiTimer::OnStart(wxCommandEvent &event) {
if (SourceStyle->GetValue().Len() == 0 || DestStyle->GetValue().Len() == 0) if (SourceStyle->GetValue().empty() || DestStyle->GetValue().empty())
wxMessageBox(_("Select source and destination styles first."),_("Error"),wxICON_EXCLAMATION | wxOK); wxMessageBox(_("Select source and destination styles first."),_("Error"),wxICON_EXCLAMATION | wxOK);
else if (SourceStyle->GetValue() == DestStyle->GetValue()) else if (SourceStyle->GetValue() == DestStyle->GetValue())
wxMessageBox(_("The source and destination styles must be different."),_("Error"),wxICON_EXCLAMATION | wxOK); wxMessageBox(_("The source and destination styles must be different."),_("Error"),wxICON_EXCLAMATION | wxOK);
@ -970,10 +758,6 @@ void DialogKanjiTimer::OnStart(wxCommandEvent &event) {
LinesToChange.clear(); LinesToChange.clear();
} }
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnLink(wxCommandEvent &event) { void DialogKanjiTimer::OnLink(wxCommandEvent &event) {
if (display->AcceptMatch()) if (display->AcceptMatch())
TryAutoMatch(); TryAutoMatch();
@ -981,40 +765,24 @@ void DialogKanjiTimer::OnLink(wxCommandEvent &event) {
wxBell(); wxBell();
} }
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnUnlink(wxCommandEvent &event) { void DialogKanjiTimer::OnUnlink(wxCommandEvent &event) {
if (!display->UndoMatch()) if (!display->UndoMatch())
wxBell(); wxBell();
// Don't auto-match here, undoing sets the selection to the undone match // Don't auto-match here, undoing sets the selection to the undone match
} }
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnSkipSource(wxCommandEvent &event) { void DialogKanjiTimer::OnSkipSource(wxCommandEvent &event) {
currentSourceLine = FindNextStyleMatch(currentSourceLine, SourceStyle->GetValue()); currentSourceLine = FindNextStyleMatch(currentSourceLine, SourceStyle->GetValue());
ResetForNewLine(); ResetForNewLine();
} }
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnSkipDest(wxCommandEvent &event) { void DialogKanjiTimer::OnSkipDest(wxCommandEvent &event) {
currentDestinationLine = FindNextStyleMatch(currentDestinationLine, DestStyle->GetValue()); currentDestinationLine = FindNextStyleMatch(currentDestinationLine, DestStyle->GetValue());
ResetForNewLine(); ResetForNewLine();
} }
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnGoBack(wxCommandEvent &event) { void DialogKanjiTimer::OnGoBack(wxCommandEvent &event) {
if (LinesToChange.empty()==false) if (LinesToChange.size())
LinesToChange.pop_back(); //If we go back, then take out the modified line we saved. LinesToChange.pop_back(); //If we go back, then take out the modified line we saved.
currentSourceLine = FindPrevStyleMatch(currentSourceLine, SourceStyle->GetValue()); currentSourceLine = FindPrevStyleMatch(currentSourceLine, SourceStyle->GetValue());
@ -1022,17 +790,11 @@ void DialogKanjiTimer::OnGoBack(wxCommandEvent &event) {
ResetForNewLine(); ResetForNewLine();
} }
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnAccept(wxCommandEvent &event) { void DialogKanjiTimer::OnAccept(wxCommandEvent &event) {
if (display->GetRemainingSource() > 0) if (display->GetRemainingSource() > 0)
wxMessageBox(_("Group all of the source text."),_("Error"),wxICON_EXCLAMATION | wxOK); wxMessageBox(_("Group all of the source text."),_("Error"),wxICON_EXCLAMATION | wxOK);
else { else {
wxString OutputText = display->GetOutputLine(); LinesToChange.push_back(std::make_pair(dynamic_cast<AssDialogue*>(*currentDestinationLine), display->GetOutputLine()));
std::pair<entryIter,wxString> ins(currentDestinationLine, OutputText);
LinesToChange.push_back(ins);
currentSourceLine = FindNextStyleMatch(currentSourceLine, SourceStyle->GetValue()); currentSourceLine = FindNextStyleMatch(currentSourceLine, SourceStyle->GetValue());
currentDestinationLine = FindNextStyleMatch(currentDestinationLine, DestStyle->GetValue()); currentDestinationLine = FindNextStyleMatch(currentDestinationLine, DestStyle->GetValue());
@ -1040,11 +802,6 @@ void DialogKanjiTimer::OnAccept(wxCommandEvent &event) {
} }
} }
/// @brief DOCME
/// @param event
/// @return
///
void DialogKanjiTimer::OnKeyDown(wxKeyEvent &event) { void DialogKanjiTimer::OnKeyDown(wxKeyEvent &event) {
wxCommandEvent evt; wxCommandEvent evt;
switch(event.GetKeyCode()) { switch(event.GetKeyCode()) {
@ -1074,10 +831,6 @@ void DialogKanjiTimer::OnKeyDown(wxKeyEvent &event) {
} }
} }
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnKeyEnter(wxCommandEvent &event) { void DialogKanjiTimer::OnKeyEnter(wxCommandEvent &event) {
wxCommandEvent evt; wxCommandEvent evt;
@ -1091,10 +844,6 @@ void DialogKanjiTimer::OnKeyEnter(wxCommandEvent &event) {
} }
} }
/// @brief DOCME
///
void DialogKanjiTimer::ResetForNewLine() void DialogKanjiTimer::ResetForNewLine()
{ {
AssDialogue *src = 0; AssDialogue *src = 0;
@ -1118,21 +867,12 @@ void DialogKanjiTimer::ResetForNewLine()
display->SetFocus(); display->SetFocus();
} }
/// @brief DOCME
///
void DialogKanjiTimer::TryAutoMatch() void DialogKanjiTimer::TryAutoMatch()
{ {
if (Interpolate->GetValue()) if (Interpolate->GetValue())
display->AutoMatchJapanese(); display->AutoMatchJapanese();
} }
/// @brief DOCME
/// @param search_from
/// @param search_style
/// @return
///
entryIter DialogKanjiTimer::FindNextStyleMatch(entryIter search_from, const wxString &search_style) entryIter DialogKanjiTimer::FindNextStyleMatch(entryIter search_from, const wxString &search_style)
{ {
if (search_from == subs->Line.end()) return search_from; if (search_from == subs->Line.end()) return search_from;
@ -1147,11 +887,6 @@ entryIter DialogKanjiTimer::FindNextStyleMatch(entryIter search_from, const wxSt
return search_from; return search_from;
} }
/// @brief DOCME
/// @param search_from
/// @param search_style
///
entryIter DialogKanjiTimer::FindPrevStyleMatch(entryIter search_from, const wxString &search_style) entryIter DialogKanjiTimer::FindPrevStyleMatch(entryIter search_from, const wxString &search_style)
{ {
if (search_from == subs->Line.begin()) return search_from; if (search_from == subs->Line.begin()) return search_from;
@ -1165,5 +900,3 @@ entryIter DialogKanjiTimer::FindPrevStyleMatch(entryIter search_from, const wxSt
return search_from; return search_from;
} }

View file

@ -35,22 +35,19 @@
/// ///
#ifndef AGI_PRE #ifndef AGI_PRE
#include <list>
#include <vector> #include <vector>
#include <wx/checkbox.h>
#include <wx/combobox.h>
#include <wx/dialog.h> #include <wx/dialog.h>
#include <wx/listctrl.h>
#include <wx/regex.h>
#endif #endif
#include "ass_entry.h"
#include "ass_file.h"
#include "kana_table.h"
namespace agi { struct Context; } namespace agi { struct Context; }
class AssOverrideParameter; class AssDialogue;
class AssEntry;
class AssFile;
class KaraokeLineMatchDisplay; class KaraokeLineMatchDisplay;
class wxComboBox;
class wxCheckBox;
/// DOCME /// DOCME
/// @class DialogKanjiTimer /// @class DialogKanjiTimer
@ -58,6 +55,8 @@ class KaraokeLineMatchDisplay;
/// ///
/// DOCME /// DOCME
class DialogKanjiTimer : public wxDialog { class DialogKanjiTimer : public wxDialog {
typedef std::list<AssEntry*>::iterator entryIter;
/// DOCME /// DOCME
AssFile *subs; AssFile *subs;
@ -75,7 +74,7 @@ class DialogKanjiTimer : public wxDialog {
/// DOCME /// DOCME
std::vector<std::pair<entryIter,wxString> > LinesToChange; std::vector<std::pair<AssDialogue*, wxString> > LinesToChange;
/// DOCME /// DOCME
entryIter currentSourceLine; entryIter currentSourceLine;
@ -92,6 +91,7 @@ class DialogKanjiTimer : public wxDialog {
void OnGoBack(wxCommandEvent &event); void OnGoBack(wxCommandEvent &event);
void OnAccept(wxCommandEvent &event); void OnAccept(wxCommandEvent &event);
inline void OnKeyEnter(wxCommandEvent &event); inline void OnKeyEnter(wxCommandEvent &event);
void OnKeyDown(wxKeyEvent &event);
void ResetForNewLine(); void ResetForNewLine();
void TryAutoMatch(); void TryAutoMatch();
@ -101,6 +101,5 @@ class DialogKanjiTimer : public wxDialog {
public: public:
DialogKanjiTimer(agi::Context *context); DialogKanjiTimer(agi::Context *context);
void OnKeyDown(wxKeyEvent &event);
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };

View file

@ -2,6 +2,7 @@ class AssFile;
class AudioBox; class AudioBox;
class AudioController; class AudioController;
class AssDialogue; class AssDialogue;
class AudioKaraoke;
class DialogDetachedVideo; class DialogDetachedVideo;
class DialogStyling; class DialogStyling;
class DialogTranslation; class DialogTranslation;
@ -31,6 +32,7 @@ struct Context {
// Views (i.e. things that should eventually not be here at all) // Views (i.e. things that should eventually not be here at all)
AudioBox *audioBox; AudioBox *audioBox;
AudioKaraoke *karaoke;
DialogDetachedVideo *detachedVideo; DialogDetachedVideo *detachedVideo;
DialogStyling *stylingAssistant; DialogStyling *stylingAssistant;
DialogTranslation *translationAssistant; DialogTranslation *translationAssistant;

View file

@ -39,247 +39,226 @@
#include "kana_table.h" #include "kana_table.h"
KanaEntry KanaTable[] =
/// @brief Constructor
KanaTable::KanaTable()
{ {
// Regular kana usage and combinations // Regular kana usage and combinations
Insert(L"\u3042",L"\u30a2",L"a"); { L"\u3042", L"\u30a2", L"a" },
Insert(L"\u3044",L"\u30a4",L"i"); { L"\u3044", L"\u30a4", L"i" },
Insert(L"\u3046",L"\u30a6",L"u"); { L"\u3046", L"\u30a6", L"u" },
Insert(L"\u3048",L"\u30a8",L"e"); { L"\u3048", L"\u30a8", L"e" },
Insert(L"\u304a",L"\u30aa",L"o"); { L"\u304a", L"\u30aa", L"o" },
Insert(L"\u304b",L"\u30ab",L"ka"); { L"\u304b", L"\u30ab", L"ka" },
Insert(L"\u304d",L"\u30ad",L"ki"); { L"\u304d", L"\u30ad", L"ki" },
Insert(L"\u304f",L"\u30af",L"ku"); { L"\u304f", L"\u30af", L"ku" },
Insert(L"\u3051",L"\u30b1",L"ke"); { L"\u3051", L"\u30b1", L"ke" },
Insert(L"\u3053",L"\u30b3",L"ko"); { L"\u3053", L"\u30b3", L"ko" },
Insert(L"\u3055",L"\u30b5",L"sa"); { L"\u3055", L"\u30b5", L"sa" },
Insert(L"\u3057",L"\u30b7",L"shi"); { L"\u3057", L"\u30b7", L"shi" },
Insert(L"\u3059",L"\u30b9",L"su"); { L"\u3059", L"\u30b9", L"su" },
Insert(L"\u305b",L"\u30bb",L"se"); { L"\u305b", L"\u30bb", L"se" },
Insert(L"\u305d",L"\u30bd",L"so"); { L"\u305d", L"\u30bd", L"so" },
Insert(L"\u305f",L"\u30bf",L"ta"); { L"\u305f", L"\u30bf", L"ta" },
Insert(L"\u3061",L"\u30c1",L"chi"); { L"\u3061", L"\u30c1", L"chi" },
Insert(L"\u3064",L"\u30c4",L"tsu"); { L"\u3064", L"\u30c4", L"tsu" },
Insert(L"\u3066",L"\u30c6",L"te"); { L"\u3066", L"\u30c6", L"te" },
Insert(L"\u3068",L"\u30c8",L"to"); { L"\u3068", L"\u30c8", L"to" },
Insert(L"\u306a",L"\u30ca",L"na"); { L"\u306a", L"\u30ca", L"na" },
Insert(L"\u306b",L"\u30cb",L"ni"); { L"\u306b", L"\u30cb", L"ni" },
Insert(L"\u306c",L"\u30cc",L"nu"); { L"\u306c", L"\u30cc", L"nu" },
Insert(L"\u306d",L"\u30cd",L"ne"); { L"\u306d", L"\u30cd", L"ne" },
Insert(L"\u306e",L"\u30ce",L"no"); { L"\u306e", L"\u30ce", L"no" },
Insert(L"\u306f",L"\u30cf",L"ha"); { L"\u306f", L"\u30cf", L"ha" },
Insert(L"\u3072",L"\u30d2",L"hi"); { L"\u3072", L"\u30d2", L"hi" },
Insert(L"\u3075",L"\u30d5",L"fu"); { L"\u3075", L"\u30d5", L"fu" },
Insert(L"\u3078",L"\u30d8",L"he"); { L"\u3078", L"\u30d8", L"he" },
Insert(L"\u307b",L"\u30db",L"ho"); { L"\u307b", L"\u30db", L"ho" },
Insert(L"\u307e",L"\u30de",L"ma"); { L"\u307e", L"\u30de", L"ma" },
Insert(L"\u307f",L"\u30df",L"mi"); { L"\u307f", L"\u30df", L"mi" },
Insert(L"\u3080",L"\u30e0",L"mu"); { L"\u3080", L"\u30e0", L"mu" },
Insert(L"\u3081",L"\u30e1",L"me"); { L"\u3081", L"\u30e1", L"me" },
Insert(L"\u3082",L"\u30e2",L"mo"); { L"\u3082", L"\u30e2", L"mo" },
Insert(L"\u3084",L"\u30e4",L"ya"); { L"\u3084", L"\u30e4", L"ya" },
Insert(L"\u3086",L"\u30e6",L"yu"); { L"\u3086", L"\u30e6", L"yu" },
Insert(L"\u3088",L"\u30e8",L"yo"); { L"\u3088", L"\u30e8", L"yo" },
Insert(L"\u3089",L"\u30e9",L"ra"); { L"\u3089", L"\u30e9", L"ra" },
Insert(L"\u308a",L"\u30ea",L"ri"); { L"\u308a", L"\u30ea", L"ri" },
Insert(L"\u308b",L"\u30eb",L"ru"); { L"\u308b", L"\u30eb", L"ru" },
Insert(L"\u308c",L"\u30ec",L"re"); { L"\u308c", L"\u30ec", L"re" },
Insert(L"\u308d",L"\u30ed",L"ro"); { L"\u308d", L"\u30ed", L"ro" },
Insert(L"\u308f",L"\u30ef",L"wa"); { L"\u308f", L"\u30ef", L"wa" },
Insert(L"\u3090",L"\u30f0",L"wi"); { L"\u3090", L"\u30f0", L"wi" },
Insert(L"\u3091",L"\u30f1",L"we"); { L"\u3091", L"\u30f1", L"we" },
Insert(L"\u3092",L"\u30f2",L"wo"); { L"\u3092", L"\u30f2", L"wo" },
Insert(L"\u304c",L"\u30ac",L"ga"); { L"\u304c", L"\u30ac", L"ga" },
Insert(L"\u304e",L"\u30ae",L"gi"); { L"\u304e", L"\u30ae", L"gi" },
Insert(L"\u3050",L"\u30b0",L"gu"); { L"\u3050", L"\u30b0", L"gu" },
Insert(L"\u3052",L"\u30b2",L"ge"); { L"\u3052", L"\u30b2", L"ge" },
Insert(L"\u3054",L"\u30b4",L"go"); { L"\u3054", L"\u30b4", L"go" },
Insert(L"\u3056",L"\u30b6",L"za"); { L"\u3056", L"\u30b6", L"za" },
Insert(L"\u3058",L"\u30b8",L"ji"); { L"\u3058", L"\u30b8", L"ji" },
Insert(L"\u305a",L"\u30ba",L"zu"); { L"\u305a", L"\u30ba", L"zu" },
Insert(L"\u305c",L"\u30bc",L"ze"); { L"\u305c", L"\u30bc", L"ze" },
Insert(L"\u305e",L"\u30be",L"zo"); { L"\u305e", L"\u30be", L"zo" },
Insert(L"\u3060",L"\u30c0",L"da"); { L"\u3060", L"\u30c0", L"da" },
Insert(L"\u3062",L"\u30c2",L"ji"); { L"\u3062", L"\u30c2", L"ji" },
Insert(L"\u3065",L"\u30c5",L"zu"); { L"\u3065", L"\u30c5", L"zu" },
Insert(L"\u3067",L"\u30c7",L"de"); { L"\u3067", L"\u30c7", L"de" },
Insert(L"\u3069",L"\u30c9",L"do"); { L"\u3069", L"\u30c9", L"do" },
Insert(L"\u3070",L"\u30d0",L"ba"); { L"\u3070", L"\u30d0", L"ba" },
Insert(L"\u3073",L"\u30d3",L"bi"); { L"\u3073", L"\u30d3", L"bi" },
Insert(L"\u3076",L"\u30d6",L"bu"); { L"\u3076", L"\u30d6", L"bu" },
Insert(L"\u3079",L"\u30d9",L"be"); { L"\u3079", L"\u30d9", L"be" },
Insert(L"\u307c",L"\u30dc",L"bo"); { L"\u307c", L"\u30dc", L"bo" },
Insert(L"\u3071",L"\u30d1",L"pa"); { L"\u3071", L"\u30d1", L"pa" },
Insert(L"\u3074",L"\u30d4",L"pi"); { L"\u3074", L"\u30d4", L"pi" },
Insert(L"\u3077",L"\u30d7",L"pu"); { L"\u3077", L"\u30d7", L"pu" },
Insert(L"\u307a",L"\u30da",L"pe"); { L"\u307a", L"\u30da", L"pe" },
Insert(L"\u307d",L"\u30dd",L"po"); { L"\u307d", L"\u30dd", L"po" },
Insert(L"\u304d\u3083",L"\u30ad\u30e3",L"kya"); { L"\u304d\u3083", L"\u30ad\u30e3", L"kya" },
Insert(L"\u304d\u3085",L"\u30ad\u30e5",L"kyu"); { L"\u304d\u3085", L"\u30ad\u30e5", L"kyu" },
Insert(L"\u304d\u3087",L"\u30ad\u30e7",L"kyo"); { L"\u304d\u3087", L"\u30ad\u30e7", L"kyo" },
Insert(L"\u3057\u3083",L"\u30b7\u30e3",L"sha"); { L"\u3057\u3083", L"\u30b7\u30e3", L"sha" },
Insert(L"\u3057\u3085",L"\u30b7\u30e5",L"shu"); { L"\u3057\u3085", L"\u30b7\u30e5", L"shu" },
Insert(L"\u3057\u3087",L"\u30b7\u30e7",L"sho"); { L"\u3057\u3087", L"\u30b7\u30e7", L"sho" },
Insert(L"\u3061\u3083",L"\u30c1\u30e3",L"cha"); { L"\u3061\u3083", L"\u30c1\u30e3", L"cha" },
Insert(L"\u3061\u3085",L"\u30c1\u30e5",L"chu"); { L"\u3061\u3085", L"\u30c1\u30e5", L"chu" },
Insert(L"\u3061\u3087",L"\u30c1\u30e7",L"cho"); { L"\u3061\u3087", L"\u30c1\u30e7", L"cho" },
Insert(L"\u306b\u3083",L"\u30cb\u30e3",L"nya"); { L"\u306b\u3083", L"\u30cb\u30e3", L"nya" },
Insert(L"\u306b\u3085",L"\u30cb\u30e5",L"nyu"); { L"\u306b\u3085", L"\u30cb\u30e5", L"nyu" },
Insert(L"\u306b\u3087",L"\u30cb\u30e7",L"nyo"); { L"\u306b\u3087", L"\u30cb\u30e7", L"nyo" },
Insert(L"\u3072\u3083",L"\u30d2\u30e3",L"hya"); { L"\u3072\u3083", L"\u30d2\u30e3", L"hya" },
Insert(L"\u3072\u3085",L"\u30d2\u30e5",L"hyu"); { L"\u3072\u3085", L"\u30d2\u30e5", L"hyu" },
Insert(L"\u3072\u3087",L"\u30d2\u30e7",L"hyo"); { L"\u3072\u3087", L"\u30d2\u30e7", L"hyo" },
Insert(L"\u307f\u3083",L"\u30df\u30e3",L"mya"); { L"\u307f\u3083", L"\u30df\u30e3", L"mya" },
Insert(L"\u307f\u3085",L"\u30df\u30e5",L"myu"); { L"\u307f\u3085", L"\u30df\u30e5", L"myu" },
Insert(L"\u307f\u3087",L"\u30df\u30e7",L"myo"); { L"\u307f\u3087", L"\u30df\u30e7", L"myo" },
Insert(L"\u308a\u3083",L"\u30ea\u30e3",L"rya"); { L"\u308a\u3083", L"\u30ea\u30e3", L"rya" },
Insert(L"\u308a\u3085",L"\u30ea\u30e5",L"ryu"); { L"\u308a\u3085", L"\u30ea\u30e5", L"ryu" },
Insert(L"\u308a\u3087",L"\u30ea\u30e7",L"ryo"); { L"\u308a\u3087", L"\u30ea\u30e7", L"ryo" },
Insert(L"\u304e\u3083",L"\u30ae\u30e3",L"gya"); { L"\u304e\u3083", L"\u30ae\u30e3", L"gya" },
Insert(L"\u304e\u3085",L"\u30ae\u30e5",L"gyu"); { L"\u304e\u3085", L"\u30ae\u30e5", L"gyu" },
Insert(L"\u304e\u3087",L"\u30ae\u30e7",L"gyo"); { L"\u304e\u3087", L"\u30ae\u30e7", L"gyo" },
Insert(L"\u3058\u3083",L"\u30b8\u30e3",L"ja"); { L"\u3058\u3083", L"\u30b8\u30e3", L"ja" },
Insert(L"\u3058\u3085",L"\u30b8\u30e5",L"ju"); { L"\u3058\u3085", L"\u30b8\u30e5", L"ju" },
Insert(L"\u3058\u3087",L"\u30b8\u30e7",L"jo"); { L"\u3058\u3087", L"\u30b8\u30e7", L"jo" },
Insert(L"\u3062\u3083",L"\u30c2\u30e3",L"ja"); { L"\u3062\u3083", L"\u30c2\u30e3", L"ja" },
Insert(L"\u3062\u3085",L"\u30c2\u30e5",L"ju"); { L"\u3062\u3085", L"\u30c2\u30e5", L"ju" },
Insert(L"\u3062\u3087",L"\u30c2\u30e7",L"jo"); { L"\u3062\u3087", L"\u30c2\u30e7", L"jo" },
Insert(L"\u3073\u3083",L"\u30d3\u30e3",L"bya"); { L"\u3073\u3083", L"\u30d3\u30e3", L"bya" },
Insert(L"\u3073\u3085",L"\u30d3\u30e5",L"byu"); { L"\u3073\u3085", L"\u30d3\u30e5", L"byu" },
Insert(L"\u3073\u3087",L"\u30d3\u30e7",L"byo"); { L"\u3073\u3087", L"\u30d3\u30e7", L"byo" },
Insert(L"\u3074\u3083",L"\u30d4\u30e3",L"pya"); { L"\u3074\u3083", L"\u30d4\u30e3", L"pya" },
Insert(L"\u3074\u3085",L"\u30d4\u30e5",L"pyu"); { L"\u3074\u3085", L"\u30d4\u30e5", L"pyu" },
Insert(L"\u3074\u3087",L"\u30d4\u30e7",L"pyo"); { L"\u3074\u3087", L"\u30d4\u30e7", L"pyo" },
// Specialty katakana usage for loan words // Specialty katakana usage for loan words
// Katakana fu + small vowel // Katakana fu + small vowel
Insert(L"",L"\u30d5\u30a1",L"fa"); { L"", L"\u30d5\u30a1", L"fa" },
Insert(L"",L"\u30d5\u30a3",L"fi"); { L"", L"\u30d5\u30a3", L"fi" },
Insert(L"",L"\u30d5\u30a7",L"fe"); { L"", L"\u30d5\u30a7", L"fe" },
Insert(L"",L"\u30d5\u30a9",L"fo"); { L"", L"\u30d5\u30a9", L"fo" },
// Katakana vu + small vowel // Katakana vu + small vowel
Insert(L"",L"\u30f4\u30a1",L"va"); { L"", L"\u30f4\u30a1", L"va" },
Insert(L"",L"\u30f4\u30a3",L"vi"); { L"", L"\u30f4\u30a3", L"vi" },
Insert(L"",L"\u30f4",L"vu"); { L"", L"\u30f4", L"vu" },
Insert(L"",L"\u30f4\u30a7",L"ve"); { L"", L"\u30f4\u30a7", L"ve" },
Insert(L"",L"\u30f4\u30a9",L"vo"); { L"", L"\u30f4\u30a9", L"vo" },
// Katakana fu + small yu // Katakana fu + small yu
Insert(L"",L"\u30d5\u30e5",L"fyu"); { L"", L"\u30d5\u30e5", L"fyu" },
// Katakana i + little e // Katakana i + little e
Insert(L"",L"\u30a4\u30a7",L"ye"); { L"", L"\u30a4\u30a7", L"ye" },
// Katakana u + little vowels // Katakana u + little vowels
Insert(L"",L"\u30a6\u30a3",L"wi"); { L"", L"\u30a6\u30a3", L"wi" },
Insert(L"",L"\u30a6\u30a7",L"we"); { L"", L"\u30a6\u30a7", L"we" },
Insert(L"",L"\u30a6\u30a9",L"wo"); { L"", L"\u30a6\u30a9", L"wo" },
// Katakana vu + small ya-yu-yo // Katakana vu + small ya-yu-yo
Insert(L"",L"\u30f4\u30e3",L"vya"); { L"", L"\u30f4\u30e3", L"vya" },
Insert(L"",L"\u30f4\u30e5",L"vyu"); { L"", L"\u30f4\u30e5", L"vyu" },
Insert(L"",L"\u30f4\u30e7",L"vyo"); { L"", L"\u30f4\u30e7", L"vyo" },
// Katakana shi-ji-chi + small e // Katakana shi-ji-chi + small e
Insert(L"",L"\u30b7\u30a7",L"she"); { L"", L"\u30b7\u30a7", L"she" },
Insert(L"",L"\u30b8\u30a7",L"je"); { L"", L"\u30b8\u30a7", L"je" },
Insert(L"",L"\u30c1\u30a7",L"che"); { L"", L"\u30c1\u30a7", L"che" },
// Katakana de + small i-u-yu // Katakana de + small i-u-yu
Insert(L"",L"\u30c6\u30a3",L"ti"); { L"", L"\u30c6\u30a3", L"ti" },
Insert(L"",L"\u30c6\u30a5",L"tu"); { L"", L"\u30c6\u30a5", L"tu" },
Insert(L"",L"\u30c6\u30e5",L"tyu"); { L"", L"\u30c6\u30e5", L"tyu" },
// Katakana de + small i-u-yu // Katakana de + small i-u-yu
Insert(L"",L"\u30c7\u30a3",L"di"); { L"", L"\u30c7\u30a3", L"di" },
Insert(L"",L"\u30c7\u30a5",L"du"); { L"", L"\u30c7\u30a5", L"du" },
Insert(L"",L"\u30c7\u30a5",L"dyu"); { L"", L"\u30c7\u30a5", L"dyu" },
// Katakana tsu + small vowels // Katakana tsu + small vowels
Insert(L"",L"\u30c4\u30a1",L"tsa"); { L"", L"\u30c4\u30a1", L"tsa" },
Insert(L"",L"\u30c4\u30a3",L"tsi"); { L"", L"\u30c4\u30a3", L"tsi" },
Insert(L"",L"\u30c4\u30a7",L"tse"); { L"", L"\u30c4\u30a7", L"tse" },
Insert(L"",L"\u30c4\u30a9",L"tso"); { L"", L"\u30c4\u30a9", L"tso" },
// Syllablic consonants // Syllablic consonants
// Small tsu // Small tsu
Insert(L"\u3063",L"\u30c3",L"t"); { L"\u3063", L"\u30c3", L"t" },
Insert(L"\u3063",L"\u30c3",L"c"); { L"\u3063", L"\u30c3", L"c" },
Insert(L"\u3063",L"\u30c3",L"s"); { L"\u3063", L"\u30c3", L"s" },
Insert(L"\u3063",L"\u30c3",L"k"); { L"\u3063", L"\u30c3", L"k" },
Insert(L"\u3063",L"\u30c3",L"p"); { L"\u3063", L"\u30c3", L"p" },
// Syllabic n // Syllabic n
Insert(L"\u3093",L"\u30f3",L"n"); { L"\u3093", L"\u30f3", L"n" },
Insert(L"\u3093",L"\u30f3",L"m"); { L"\u3093", L"\u30f3", L"m" },
// Other special usage // Other special usage
// Small vowels // Small vowels
Insert(L"\u3041",L"\u30a1",L"a"); { L"\u3041", L"\u30a1", L"a" },
Insert(L"\u3043",L"\u30a3",L"i"); { L"\u3043", L"\u30a3", L"i" },
Insert(L"\u3045",L"\u30a5",L"u"); { L"\u3045", L"\u30a5", L"u" },
Insert(L"\u3047",L"\u30a7",L"e"); { L"\u3047", L"\u30a7", L"e" },
Insert(L"\u3049",L"\u30a9",L"o"); { L"\u3049", L"\u30a9", L"o" },
// Long vowel mark (dash) // Long vowel mark (dash)
Insert(L"",L"\u30fc",L"a"); { L"", L"\u30fc", L"a" },
Insert(L"",L"\u30fc",L"i"); { L"", L"\u30fc", L"i" },
Insert(L"",L"\u30fc",L"u"); { L"", L"\u30fc", L"u" },
Insert(L"",L"\u30fc",L"e"); { L"", L"\u30fc", L"e" },
Insert(L"",L"\u30fc",L"o"); { L"", L"\u30fc", L"o" },
} { 0, 0, 0 }
};
/// @brief Destructor
KanaTable::~KanaTable()
{
// Do nothing
}
/// @brief Add Hiragana, Katakana and hepburn romaji tuple.
/// @param hira Hiragana to add.
/// @param kata Katakana to add.
/// @param hep Hepburn romaji to add.
///
void KanaTable::Insert(const wchar_t *hira, const wchar_t *kata, const wchar_t *hep)
{
entries.push_back(KanaEntry(hira,kata,hep));
}

View file

@ -34,54 +34,24 @@
/// @ingroup kara_timing_copy /// @ingroup kara_timing_copy
/// ///
///////////
// Headers
#ifndef AGI_PRE #ifndef AGI_PRE
#include <list> #include <list>
#include <wx/string.h> #include <wx/string.h>
#endif #endif
/// @class KanaEntry /// @class KanaEntry
/// @brief Base class for Kana + Romaji tuples. /// @brief Base class for Kana + Romaji tuples.
class KanaEntry { struct KanaEntry {
public:
/// Hiragana /// Hiragana
wxString hiragana; const wchar_t *hiragana;
/// Katakana /// Katakana
wxString katakana; const wchar_t *katakana;
/// Hepburn romaji. /// Hepburn romaji.
wxString hepburn; const wchar_t *hepburn;
/// @brief Constructor
KanaEntry() {}
KanaEntry(const wxString &hira, const wxString &kata, const wxString &hep) {
hiragana = hira;
katakana = kata;
hepburn = hep;
}
}; };
/// Table of Hiragana, Katakana and Hepburn romaji tuples.
extern KanaEntry KanaTable[];
/// @class KanaTable
/// @brief Table of Hiragana, Katakana and Hepburn romaji tuples.
///
class KanaTable {
private:
void Insert(const wchar_t *hira, const wchar_t *kata, const wchar_t *hep);
public:
/// Memory list.
std::list<KanaEntry> entries;
KanaTable();
~KanaTable();
};

View file

@ -51,6 +51,10 @@
"Downmixer" : "ConvertToMono", "Downmixer" : "ConvertToMono",
"Grab Times on Select" : true, "Grab Times on Select" : true,
"Inactive Lines Display Mode" : 1, "Inactive Lines Display Mode" : 1,
"Karaoke" : {
"Font Face" : "Verdana",
"Font Size" : 9
},
"Lead" : { "Lead" : {
"IN" : 200, "IN" : 200,
"OUT" : 300 "OUT" : 300

View file

@ -21,7 +21,9 @@
"audio/opt/autonext", "audio/opt/autonext",
"audio/opt/autoscroll", "audio/opt/autoscroll",
"audio/opt/spectrum", "audio/opt/spectrum",
"app/toggle/global_hotkeys" "app/toggle/global_hotkeys",
"",
"audio/karaoke"
], ],
"main" : [ "main" : [
"subtitle/new", "subtitle/new",

View file

@ -51,8 +51,8 @@
#include "include/aegisub/audio_provider.h" #include "include/aegisub/audio_provider.h"
#include "include/aegisub/menu.h" #include "include/aegisub/menu.h"
#include "ass_dialogue.h"
#include "ass_file.h" #include "ass_file.h"
#include "ass_karaoke.h"
#include "ass_override.h" #include "ass_override.h"
#include "ass_style.h" #include "ass_style.h"
#include "audio_controller.h" #include "audio_controller.h"
@ -514,45 +514,6 @@ void SubtitlesGrid::SplitLine(AssDialogue *n1,int pos,bool estimateTimes) {
context->ass->Commit(_("split"), AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL); context->ass->Commit(_("split"), AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL);
} }
bool SubtitlesGrid::SplitLineByKaraoke(int lineNumber) {
AssDialogue *line = GetDialogue(lineNumber);
line->ParseASSTags();
AssKaraokeVector syls;
ParseAssKaraokeTags(line, syls);
line->ClearBlocks();
// If there's only 1 or 0 syllables, splitting would be counter-productive.
// 1 syllable means there's no karaoke tags in the line at all and that is
// the case that triggers bug #929.
if (syls.size() < 2) return false;
// Insert a new line for each syllable
int start_ms = line->Start.GetMS();
int nextpos = lineNumber;
for (AssKaraokeVector::iterator syl = syls.begin(); syl != syls.end(); ++syl)
{
// Skip blank lines
if (syl->unstripped_text.IsEmpty()) continue;
AssDialogue *nl = new AssDialogue(line->GetEntryData());
nl->Start.SetMS(start_ms);
start_ms += syl->duration * 10;
nl->End.SetMS(start_ms);
nl->Text = syl->unstripped_text;
InsertLine(nl, nextpos++, true, false);
}
// Remove the source line
{
wxArrayInt oia;
oia.Add(lineNumber);
DeleteLines(oia, false);
}
return true;
}
/// @brief Retrieve a list of selected lines in the actual ASS file (ie. not as displayed in the grid but as represented in the file) /// @brief Retrieve a list of selected lines in the actual ASS file (ie. not as displayed in the grid but as represented in the file)
/// @return /// @return
/// ///

View file

@ -86,12 +86,6 @@ public:
/// @param pos Position in line /// @param pos Position in line
/// @param estimateTimes Adjust the times based on the lengths of the halves /// @param estimateTimes Adjust the times based on the lengths of the halves
void SplitLine(AssDialogue *line,int splitPosition,bool estimateTimes); void SplitLine(AssDialogue *line,int splitPosition,bool estimateTimes);
/// @brief Split a line into as many new lines as there are karaoke syllables, timed as the syllables
/// @param lineNumber Line to split
/// @return Were changes made?
///
/// DOES NOT FLAG AS MODIFIED OR COMMIT CHANGES
bool SplitLineByKaraoke(int lineNumber);
/// @brief Duplicate lines /// @brief Duplicate lines
/// @param n1 First frame to duplicate /// @param n1 First frame to duplicate
/// @param n2 Last frame to duplicate /// @param n2 Last frame to duplicate