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"
>
</File>
<File
RelativePath="..\..\src\audio_timing_karaoke.cpp"
>
</File>
</Filter>
<Filter
Name="Video UI"

View File

@ -261,6 +261,7 @@
<ClCompile Include="$(SrcDir)audio_renderer_spectrum.cpp" />
<ClCompile Include="$(SrcDir)audio_renderer_waveform.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_lua.cpp" />
<ClCompile Include="$(SrcDir)auto4_lua_assfile.cpp" />

View File

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

View File

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

View File

@ -1,29 +1,16 @@
// Copyright (c) 2006-2007, Niels Martin Hansen
// All rights reserved.
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 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.
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// 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/
//
@ -38,94 +25,284 @@
#include "config.h"
#include "ass_karaoke.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_override.h"
#include "include/aegisub/context.h"
#include "selection_controller.h"
#ifndef AGI_PRE
#include <wx/intl.h>
#endif
/// @brief DOCME
///
AssKaraokeSyllable::AssKaraokeSyllable()
{
duration = 0;
text = _T("");
unstripped_text = _T("");
type = _T("");
tag = 0;
wxString AssKaraoke::Syllable::GetText(bool k_tag) const {
wxString ret;
if (k_tag)
ret = wxString::Format("{%s%d}", tag_type, duration / 10);
size_t idx = 0;
for (std::map<size_t, wxString>::const_iterator ovr = ovr_tags.begin(); ovr != ovr_tags.end(); ++ovr) {
ret += text.Mid(idx, ovr->first - idx);
ret += ovr->second;
idx = ovr->first;
}
ret += text.Mid(idx);
return ret;
}
/// @brief DOCME
/// @param line
/// @param syls
///
void ParseAssKaraokeTags(const AssDialogue *line, AssKaraokeVector &syls)
AssKaraoke::AssKaraoke(AssDialogue *line, bool auto_split)
: no_announce(false)
{
// Assume line already has tags parsed
AssKaraokeSyllable syl;
if (line) SetLine(line, auto_split);
}
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];
switch (block->GetType()) {
case BLOCK_BASE:
break;
case BLOCK_PLAIN:
if (dynamic_cast<AssDialogueBlockPlain*>(block)) {
// treat comments as overrides rather than dialogue
if (block->text.size() >= 2 && block->text[0] == '{')
syl.ovr_tags[syl.text.size()] += block->text;
else
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
// stripped text so pretend they are
syl.ovr_tags[syl.text.size()] += block->text;
}
else if (AssDialogueBlockOverride *ovr = dynamic_cast<AssDialogueBlockOverride*>(block)) {
bool in_tag = false;
for (size_t j = 0; j < ovr->Tags.size(); ++j) {
AssOverrideTag *tag = ovr->Tags[j];
case BLOCK_DRAWING:
// Regard drawings as tags
syl.unstripped_text += block->text;
break;
case BLOCK_OVERRIDE: {
AssDialogueBlockOverride *ovr = dynamic_cast<AssDialogueBlockOverride*>(block);
for (int j = 0; j < (int)ovr->Tags.size(); j++) {
AssOverrideTag *tag = ovr->Tags[j];
if (tag->IsValid() && tag->Name.Mid(0,2).CmpNoCase(_T("\\k")) == 0) {
// karaoke tag
if (brackets_open) {
syl.unstripped_text += _T("}");
brackets_open = false;
}
// Store syllable
syls.push_back(syl);
syl.text = _T("");
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;
if (tag->IsValid() && tag->Name.Left(2).Lower() == "\\k") {
if (in_tag) {
syl.ovr_tags[syl.text.size()] += "}";
in_tag = false;
}
}
if (brackets_open) {
brackets_open = false;
syl.unstripped_text += _T("}");
}
// Dealing with both \K and \kf is mildly annoying so just
// convert them both to \kf
if (tag->Name == "\\K") tag->Name = "\\kf";
break;
// Don't bother including zero duration zero length syls
if (syl.duration > 0 || !syl.text.empty()) {
syls.push_back(syl);
syl.text.clear();
syl.ovr_tags.clear();
}
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 (in_tag)
syl.ovr_tags[syl.text.size()] += "}";
}
}
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
// All rights reserved.
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 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.
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// 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/
//
@ -36,37 +23,79 @@
#ifndef AGI_PRE
#include <map>
#include <set>
#include <vector>
#include <wx/string.h>
#endif
#include "ass_dialogue.h"
#include <libaegisub/signal.h>
namespace agi { struct Context; }
class AssDialogue;
/// DOCME
struct AssKaraokeSyllable {
/// @class AssKaraoke
/// @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
int duration; // centiseconds
/// Get the text of this line with override tags and optionally the karaoke tag
wxString GetText(bool k_tag) const;
};
private:
typedef std::map<size_t, wxString>::iterator ovr_iterator;
std::vector<Syllable> syls;
AssDialogue *active_line;
/// DOCME
wxString text; // stripped text of syllable
bool no_announce;
/// DOCME
wxString unstripped_text; // including misc. tags
agi::signal::Signal<> AnnounceSyllablesChanged;
/// DOCME
wxString type; // highlight type, \k \K \kf \ko (backslash included)
public:
/// 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
AssOverrideTag *tag; // parsed override tag for direct modification
/// Parse a dialogue line
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_display.h"
#include "audio_karaoke.h"
#include "audio_timing.h"
#include "command/command.h"
#include "libresrc/libresrc.h"
#include "main.h"
@ -86,9 +85,7 @@ AudioBox::AudioBox(wxWindow *parent, agi::Context *context)
: wxPanel(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxBORDER_RAISED)
, audioDisplay(new AudioDisplay(this, context->audioController, context))
, controller(context->audioController)
, timing_controller_dialogue(CreateDialogueTimingController(controller, context->selectionController, context->ass))
, context(context)
, karaokeMode(false)
{
// Zoom
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(VertVolArea,0,wxEXPAND,0);
// Buttons sizer
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);
context->karaoke = new AudioKaraoke(this, context);
// Main sizer
wxBoxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
MainSizer->Add(TopSizer,1,wxEXPAND|wxALL,3);
MainSizer->Add(toolbar::GetToolbar(this, "audio", context, "Audio"),0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3);
MainSizer->Add(ButtonSizer);
MainSizer->Add(karaokeSizer,0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3);
MainSizer->Add(context->karaoke,0,wxEXPAND|wxBOTTOM|wxLEFT|wxRIGHT,3);
MainSizer->AddSpacer(3);
SetSizer(MainSizer);
SetKaraokeButtons(); // Decide which one to show or hide.
controller->SetTimingController(timing_controller_dialogue);
}
AudioBox::~AudioBox() { }
@ -173,13 +134,6 @@ BEGIN_EVENT_TABLE(AudioBox,wxPanel)
EVT_COMMAND_SCROLL(Audio_Horizontal_Zoom, AudioBox::OnHorizontalZoom)
EVT_COMMAND_SCROLL(Audio_Vertical_Zoom, AudioBox::OnVerticalZoom)
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()
void AudioBox::OnHorizontalZoom(wxScrollEvent &event) {
@ -212,72 +166,3 @@ void AudioBox::OnVerticalLink(agi::OptionValue const& opt) {
}
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 AudioDisplay;
class AudioKaraoke;
class AudioTimingController;
class wxBitmapToggleButton;
class wxButton;
class wxCommandEvent;
@ -63,9 +61,6 @@ class AudioBox : public wxPanel {
/// The controller controlling this audio box
AudioController *controller;
/// The regular dialogue timing controller
AudioTimingController *timing_controller_dialogue;
/// Project context this operates on
agi::Context *context;
@ -78,53 +73,15 @@ class AudioBox : public wxPanel {
/// DOCME
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 OnVerticalZoom(wxScrollEvent &event);
void OnVolume(wxScrollEvent &event);
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:
AudioBox(wxWindow *parent, agi::Context *context);
~AudioBox();
void SetKaraokeButtons();
DECLARE_EVENT_TABLE()
};

View File

@ -139,6 +139,7 @@ public:
wxString text;
/// Range which this label applies to
SampleRange range;
AudioLabel(wxString const& text, SampleRange const& range) : text(text), range(range) { }
};
/// 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
// All rights reserved.
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 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.
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// 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/
//
@ -34,155 +21,127 @@
/// @ingroup audio_ui
///
///////////
// Headers
#ifndef AGI_PRE
#include <set>
#include <vector>
#include <wx/log.h>
#include <wx/menu.h>
#include <wx/window.h>
#endif
#include "ass_karaoke.h"
#include <libaegisub/scoped_ptr.h>
#include <libaegisub/signal.h>
#include "selection_controller.h"
//////////////
// Prototypes
class AssDialogue;
class AssDialogueBlockOverride;
class AssOverrideTag;
class AssOverrideParameter;
class AudioDisplay;
class AudioBox;
class AudioKaraokeTagMenu;
class AssKaraoke;
class wxButton;
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
/// @brief DOCME
/// @brief Syllable split and join UI for karaoke
///
/// DOCME
class AudioKaraoke : public wxWindow {
friend class AudioKaraokeTagMenu;
private:
/// This class has two main responsibilities: the syllable split/join UI, and
/// the karaoke mode controller. The split/join UI consists of the dialogue
/// line with spaces and lines at each syllable split point. Clicking on a line
/// 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
AssDialogue *diag;
/// Currently active dialogue line
AssDialogue *active_line;
/// Karaoke data
agi::scoped_ptr<AssKaraoke> kara;
/// DOCME
AssDialogue *workDiag;
/// Current line's stripped text with spaces added between each syllable
wxString spaced_text;
/// DOCME
int startClickSyl;
/// Indexes in spaced_text which are the beginning of syllables
std::vector<int> syl_start_points;
/// DOCME
bool must_rebuild;
/// x coordinate in pixels of the separator lines of each syllable
std::vector<int> syl_lines;
/// Left x coordinate of each character in spaced_text in pixels
std::vector<int> char_x;
/// DOCME
int split_cursor_syl;
int char_height; ///< Maximum character height in pixels
int char_width; ///< Maximum character width in pixels
int mouse_pos; ///< Last x coordinate of the mouse
/// DOCME
int split_cursor_x;
wxFont split_font; ///< Font used in the split/join interface
void AutoSplit();
bool ParseDialogue(AssDialogue *diag);
bool enabled; ///< Is karaoke mode enabled?
int GetSylAtX(int x);
int SplitSyl(unsigned int n);
wxButton *accept_button; ///< Accept pending splits button
wxButton *cancel_button; ///< Revert pending changes
void OnPaint(wxPaintEvent &event);
void OnSize(wxSizeEvent &event);
wxWindow *split_area; ///< The split/join window
/// 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 OnPaint(wxPaintEvent &event);
void OnSelectedSetChanged(Selection const&, Selection const&) { }
public:
/// Constructor
/// @param parent Parent window
/// @param c Project context
AudioKaraoke(wxWindow *parent, agi::Context *c);
/// Destructor
~AudioKaraoke();
/// DOCME
AudioDisplay *display;
/// Is karaoke mode currently enabled?
bool IsEnabled() const { return enabled; }
/// DOCME
AudioBox *box;
/// 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()
/// Enable or disable karaoke mode
void SetEnabled(bool enable);
};

View File

@ -35,8 +35,10 @@
class AssDialogue;
class AssFile;
class AudioController;
class AssKaraoke;
namespace agi { struct Context; }
#include "audio_controller.h"
#include "selection_controller.h"
#include <libaegisub/signal.h>
@ -154,6 +156,11 @@ public:
/// @brief Create a standard dialogue audio timing controller
/// @param audio_controller The audio controller to own the timing controller
/// @param selection_controller The selection controller to manage the set of
/// lines being timed
/// lines being timed
/// @param ass The file being timed
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 "../audio_controller.h"
#include "../audio_karaoke.h"
#include "../audio_timing.h"
#include "../compat.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_view_spectrum);
reg(new audio_view_waveform);
reg(new audio_karaoke);
}
}

View File

@ -48,6 +48,7 @@
#include "../ass_dialogue.h"
#include "../ass_file.h"
#include "../ass_karaoke.h"
#include "../dialog_search_replace.h"
#include "../include/aegisub/context.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.")
void operator()(agi::Context *c) {
c->subsGrid->BeginBatch();
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();
AssKaraoke::SplitLines(c->selectionController->GetSelectedSet(), c);
}
};

View File

@ -94,6 +94,7 @@ INSERT_ICON("app/updates", blank_button)
INSERT_ICON("audio/close", close_audio_menu)
INSERT_ICON("audio/commit", button_audio_commit)
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/video", open_audio_from_video_menu)
INSERT_ICON("audio/opt/autocommit", toggle_audio_autocommit)

View File

@ -37,37 +37,35 @@
#include "config.h"
#include "dialog_kara_timing_copy.h"
#ifndef AGI_PRE
#include <deque>
#include <vector>
#include <wx/checkbox.h>
#include <wx/combobox.h>
#include <wx/dcclient.h>
#include <wx/listctrl.h>
#include <wx/msgdlg.h>
#include <wx/regex.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/string.h>
#endif
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_karaoke.h"
#include "ass_override.h"
#include "ass_style.h"
#include "dialog_kara_timing_copy.h"
#include "help_button.h"
#include "include/aegisub/context.h"
#include "kana_table.h"
#include "libresrc/libresrc.h"
#include "main.h"
#include "selection_controller.h"
#include "utils.h"
#include "validators.h"
#include "video_context.h"
/// DOCME
#define TEXT_LABEL_SOURCE _("Source: ")
/// DOCME
#define TEXT_LABEL_DEST _("Dest: ")
// IDs
@ -89,126 +87,58 @@ enum {
///
/// DOCME
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 {
/// DOCME
std::vector<MatchSyllable> src;
/// DOCME
typedef std::vector<MatchSyllable>::iterator SrcIterator;
/// DOCME
wxString dst;
/// DOCME
size_t duration;
/// DOCME
int last_render_width;
/// @brief DOCME
///
MatchGroup() : duration(0), last_render_width(0) { }
MatchGroup() : last_render_width(0) { }
};
/// DOCME
std::vector<MatchGroup> matched_groups;
/// DOCME
typedef std::vector<MatchGroup>::iterator MatchedGroupIterator;
/// DOCME
std::deque<MatchSyllable> unmatched_source;
/// DOCME
typedef std::deque<MatchSyllable>::iterator UnmatchedSourceIterator;
/// DOCME
wxString unmatched_destination;
/// DOCME
int last_total_matchgroup_render_width;
/// DOCME
size_t source_sel_length;
/// DOCME
size_t destination_sel_length;
/// DOCME
bool has_source, has_destination;
void OnPaint(wxPaintEvent &event);
void OnKeyboard(wxKeyEvent &event);
void OnFocusEvent(wxFocusEvent &event);
/// DOCME
/// DOCME
const wxString label_source, label_destination;
public:
// Start processing a new line pair
void SetInputData(const AssDialogue *src, const AssDialogue *dst);
// Build and return the output line from the matched syllables
wxString GetOutputLine();
/// Start processing a new line pair
void SetInputData(AssDialogue *src, AssDialogue *dst);
/// Build and return the output line from the matched syllables
wxString GetOutputLine() const;
// Number of syllables not yet matched from source
size_t GetRemainingSource();
// Number of characters not yet matched from destination
size_t GetRemainingDestination();
/// Number of syllables not yet matched from source
size_t GetRemainingSource() const { return unmatched_source.size(); }
/// Number of characters not yet matched from destination
size_t GetRemainingDestination() const { return unmatched_destination.size(); }
// Adjust source and destination match lengths
void IncreaseSourceMatch();
void DecreaseSourceMatch();
void IncreseDestinationMatch();
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();
// Accept current selection and save match
/// Accept current selection and save match
bool AcceptMatch();
// Undo last match, adding it back to the unmatched input
/// Undo last match, adding it back to the unmatched input
bool UndoMatch();
// Constructor and destructor
KaraokeLineMatchDisplay(DialogKanjiTimer *parent);
~KaraokeLineMatchDisplay();
KaraokeLineMatchDisplay(wxWindow *parent);
wxSize GetBestSize();
DECLARE_EVENT_TABLE()
wxSize GetBestSize() const;
};
/// @brief DOCME
/// @param parent
///
KaraokeLineMatchDisplay::KaraokeLineMatchDisplay(DialogKanjiTimer *parent)
KaraokeLineMatchDisplay::KaraokeLineMatchDisplay(wxWindow *parent)
: wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE|wxWANTS_CHARS)
, label_source(TEXT_LABEL_SOURCE)
, label_destination(TEXT_LABEL_DEST)
@ -219,27 +149,19 @@ KaraokeLineMatchDisplay::KaraokeLineMatchDisplay(DialogKanjiTimer *parent)
wxSize best_size = GetBestSize();
SetMaxSize(wxSize(-1, best_size.GetHeight()));
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);
}
/// @brief DOCME
///
KaraokeLineMatchDisplay::~KaraokeLineMatchDisplay()
{
// Nothing to do
}
/// @brief DOCME
/// @return
///
wxSize KaraokeLineMatchDisplay::GetBestSize()
wxSize KaraokeLineMatchDisplay::GetBestSize() const
{
int w_src, h_src, w_dst, h_dst;
GetTextExtent(label_source, &w_src, &h_src);
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:
// 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);
}
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 tw, th;
// Assume the pen, brush and font properties have already been set in the DC.
// Return the advance width, including box margins, borders etc
if (txt == "")
if (txt.empty())
{
// Empty string gets special handling:
// 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)
{
wxPaintDC dc(this);
@ -422,50 +323,27 @@ void KaraokeLineMatchDisplay::OnPaint(wxPaintEvent &event)
}
// Remaining destination
dc.SetTextBackground(sel_back);
dc.SetTextForeground(sel_text);
dc.SetBrush(wxBrush(sel_back));
wxString txt = unmatched_destination.Left(destination_sel_length);
if (txt != "")
if (!txt.empty())
{
dc.SetTextBackground(sel_back);
dc.SetTextForeground(sel_text);
dc.SetBrush(wxBrush(sel_back));
next_x += DrawBoxedText(dc, txt, next_x, y_line2);
}
dc.SetTextBackground(inner_back);
dc.SetTextForeground(inner_text);
dc.SetBrush(wxBrush(inner_back));
txt = unmatched_destination.Mid(destination_sel_length);
if (txt != "")
if (!txt.empty())
{
dc.SetTextBackground(inner_back);
dc.SetTextForeground(inner_text);
dc.SetBrush(wxBrush(inner_back));
DrawBoxedText(dc, txt, next_x, y_line2);
}
}
/// @brief DOCME
/// @param event
///
void KaraokeLineMatchDisplay::OnKeyboard(wxKeyEvent &event)
void KaraokeLineMatchDisplay::SetInputData(AssDialogue *src, AssDialogue *dst)
{
((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;
matched_groups.clear();
@ -474,16 +352,9 @@ void KaraokeLineMatchDisplay::SetInputData(const AssDialogue *src, const AssDial
source_sel_length = 0;
if (src)
{
AssDialogue *varsrc = dynamic_cast<AssDialogue*>(src->Clone());
varsrc->ParseASSTags();
AssKaraokeVector kara;
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;
AssKaraoke kara(src);
copy(kara.begin(), kara.end(), back_inserter(unmatched_source));
source_sel_length = 1;
}
unmatched_destination.clear();
@ -491,121 +362,73 @@ void KaraokeLineMatchDisplay::SetInputData(const AssDialogue *src, const AssDial
if (dst)
{
unmatched_destination = dst->GetStrippedText();
if (!unmatched_destination.empty())
{
destination_sel_length = 1;
}
}
IncreaseSourceMatch();
IncreseDestinationMatch();
Refresh(true);
}
/// @brief DOCME
/// @return
///
wxString KaraokeLineMatchDisplay::GetOutputLine()
wxString KaraokeLineMatchDisplay::GetOutputLine() const
{
wxString res;
for (size_t i = 0; i < matched_groups.size(); ++i)
{
MatchGroup &match = matched_groups[i];
res = wxString::Format("%s{\\k%d}%s", res, match.duration, match.dst);
const MatchGroup &match = matched_groups[i];
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;
}
/// @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()
{
source_sel_length += 1;
if (source_sel_length > GetRemainingSource())
source_sel_length = GetRemainingSource();
source_sel_length = std::min(source_sel_length + 1, GetRemainingSource());
Refresh(true);
}
/// @brief DOCME
///
void KaraokeLineMatchDisplay::DecreaseSourceMatch()
{
if (source_sel_length > 0)
source_sel_length -= 1;
source_sel_length = std::max(source_sel_length, 1u) - 1;
Refresh(true);
}
/// @brief DOCME
///
void KaraokeLineMatchDisplay::IncreseDestinationMatch()
{
destination_sel_length += 1;
if (destination_sel_length > GetRemainingDestination())
destination_sel_length = GetRemainingDestination();
destination_sel_length = std::min(destination_sel_length + 1, GetRemainingDestination());
Refresh(true);
}
/// @brief DOCME
///
void KaraokeLineMatchDisplay::DecreaseDestinationMatch()
{
if (destination_sel_length > 0)
destination_sel_length -= 1;
destination_sel_length = std::max(destination_sel_length, 1u) - 1;
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()
{
if (unmatched_source.size() < 1) return;
// Quick escape: If there's no destination left, take all remaining source.
// (Usually this means the user made a mistake.)
if (unmatched_destination.size() == 0)
if (unmatched_destination.empty())
{
source_sel_length = unmatched_source.size();
destination_sel_length = 0;
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
wxString src(unmatched_source[0].text.Lower());
wxString dst(unmatched_destination);
@ -613,32 +436,35 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
destination_sel_length = 0;
// Quick escape: If the source syllable is empty, return with first source syllable and empty destination
if (src.size() == 0)
if (src.empty())
{
return;
}
// Try to match the next source syllable against the destination.
// Do it "inverted", try all kana from the table and prefix-match them against the destination,
// then if it matches a prefix, try to match the hepburn 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.
// Try to match the next source syllable against the destination. Do it
// "inverted": try all kana from the table and prefix-match them against
// the destination, then if it matches a prefix, try to match the hepburn
// 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)
{
wxString dst_hira_rest, dst_kata_rest, src_rest;
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();
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 (src.StartsWith(ke->hepburn, &src_rest))
{
matched = true;
src = src_rest;
dst = hira_matches ? dst_hira_rest : dst_kata_rest;
destination_sel_length += (hira_matches?ke->hiragana:ke->katakana).size();
break;
bool hira_matches = dst.StartsWith(ke->hiragana, &dst_hira_rest) && *ke->hiragana;
bool kata_matches = dst.StartsWith(ke->katakana, &dst_kata_rest);
if (hira_matches || kata_matches)
{
matched = true;
src = src_rest;
dst = hira_matches ? dst_hira_rest : dst_kata_rest;
destination_sel_length += wcslen(hira_matches ? ke->hiragana : ke->katakana);
break;
}
}
}
if (!matched) break;
@ -646,7 +472,7 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
// The source might be empty now: That's good!
// 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
// and source_sel_length was alredy 1
@ -657,11 +483,8 @@ void KaraokeLineMatchDisplay::AutoMatchJapanese()
// Eat all whitespace at the start of the destination.
if (StringEmptyOrWhitespace(src))
{
trycatchingmorespaces:
// ASCII space
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; }
while (IsWhitespace(unmatched_destination[destination_sel_length]))
++destination_sel_length;
// Now we've eaten all spaces in the destination as well
// so the selection lengths should be good
return;
@ -679,15 +502,17 @@ trycatchingmorespaces:
}
#ifdef KANA_SEARCH_DISTANCE
// Try to look up to KANA_SEARCH_DISTANCE characters ahead in destination, see if any of those
// are recognised kana. If there are any within the range, see if it matches a following syllable,
// at most 5 source syllables per character in source we're ahead.
// The number 5 comes from the kanji with the longest readings: 'uketamawa.ru' and 'kokorozashi'
// which each has a reading consisting of five kana.
// Only match the found kana in destination against the beginning of source 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.
// Try to look up to KANA_SEARCH_DISTANCE characters ahead in destination,
// see if any of those are recognised kana. If there are any within the
// range, see if it matches a following syllable, at most 5 source
// syllables per character in source we're ahead.
// The number 5 comes from the kanji with the longest readings:
// 'uketamawa.ru' and 'kokorozashi' which each have a reading consisting of
// five kana.
// Only match the found kana in destination against the beginning of source
// 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;
for (size_t lookahead = 0; lookahead < KANA_SEARCH_DISTANCE; ++lookahead)
{
@ -696,15 +521,15 @@ trycatchingmorespaces:
// Find a position where hiragana or katakana matches
wxString matched_roma;
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_kana = ke->hiragana;
break;
}
if (!!ke->katakana && dst.StartsWith(ke->katakana))
if (*ke->katakana && dst.StartsWith(ke->katakana))
{
matched_roma = ke->hepburn;
matched_kana = ke->katakana;
@ -718,7 +543,7 @@ trycatchingmorespaces:
// For the magic number 5, see big comment block above
int src_lookahead_max = (lookahead+1)*5;
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
if (src_lookahead_pos++ >= src_lookahead_max) break;
@ -748,11 +573,6 @@ trycatchingmorespaces:
}
#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.
// Just fail...
// We know from earlier that we do have both some source and some destination.
@ -761,11 +581,6 @@ trycatchingmorespaces:
return;
}
/// @brief DOCME
/// @return
///
bool KaraokeLineMatchDisplay::AcceptMatch()
{
MatchGroup match;
@ -777,13 +592,8 @@ bool KaraokeLineMatchDisplay::AcceptMatch()
}
assert(source_sel_length <= unmatched_source.size());
for (; source_sel_length > 0; source_sel_length--)
{
match.src.push_back(unmatched_source.front());
match.duration += unmatched_source.front().dur;
unmatched_source.pop_front();
}
assert(source_sel_length == 0);
copy(unmatched_source.begin(), unmatched_source.begin() + source_sel_length, back_inserter(match.src));
source_sel_length = 0;
assert(destination_sel_length <= unmatched_destination.size());
match.dst = unmatched_destination.Left(destination_sel_length);
@ -799,10 +609,6 @@ bool KaraokeLineMatchDisplay::AcceptMatch()
return true;
}
/// @brief DOCME
/// @return
///
bool KaraokeLineMatchDisplay::UndoMatch()
{
if (matched_groups.empty())
@ -813,11 +619,8 @@ bool KaraokeLineMatchDisplay::UndoMatch()
source_sel_length = group.src.size();
destination_sel_length = group.dst.size();
while (group.src.size() > 0)
{
unmatched_source.push_front(group.src.back());
group.src.pop_back();
}
copy(group.src.rbegin(), group.src.rend(), front_inserter(unmatched_source));
group.src.clear();
unmatched_destination = group.dst + unmatched_destination;
@ -828,15 +631,11 @@ bool KaraokeLineMatchDisplay::UndoMatch()
return true;
}
/// @brief Constructor
/// @param parent
DialogKanjiTimer::DialogKanjiTimer(agi::Context *c)
: wxDialog(c->parent,-1,_("Kanji timing"),wxDefaultPosition)
{
// Set icon
SetIcon(BitmapToIcon(GETIMAGE(kara_timing_copier_24)));
// Variables
subs = c->ass;
currentSourceLine = 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->SetValue(OPT_GET("Tool/Kanji Timer/Interpolation")->GetBool());
SourceStyle=new wxComboBox(this,-1,"",wxDefaultPosition,wxSize(160,-1),
subs->GetStyles(),wxCB_READONLY,wxDefaultValidator,_("Source Style"));
DestStyle = new wxComboBox(this,-1,"",wxDefaultPosition,wxSize(160,-1),
subs->GetStyles(),wxCB_READONLY,wxDefaultValidator,_("Dest Style"));
wxArrayString styles = subs->GetStyles();
SourceStyle = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(160, -1),
styles, wxCB_READONLY, wxDefaultValidator, _("Source 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"));
@ -913,11 +713,10 @@ DialogKanjiTimer::DialogKanjiTimer(agi::Context *c)
SetSizerAndFit(MainStackSizer);
CenterOnParent();
display->Bind(wxEVT_KEY_DOWN, &DialogKanjiTimer::OnKeyDown, this);
}
///////////////
// Event table
BEGIN_EVENT_TABLE(DialogKanjiTimer,wxDialog)
EVT_BUTTON(wxID_CLOSE,DialogKanjiTimer::OnClose)
EVT_BUTTON(BUTTON_KTSTART,DialogKanjiTimer::OnStart)
@ -932,33 +731,22 @@ BEGIN_EVENT_TABLE(DialogKanjiTimer,wxDialog)
EVT_TEXT_ENTER(TEXT_DEST,DialogKanjiTimer::OnKeyEnter)
END_EVENT_TABLE()
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnClose(wxCommandEvent &event) {
OPT_SET("Tool/Kanji Timer/Interpolation")->SetBool(Interpolate->IsChecked());
bool modified = !LinesToChange.empty();
while(LinesToChange.empty()==false) {
std::pair<entryIter,wxString> p = LinesToChange.back();
LinesToChange.pop_back();
AssDialogue *line = dynamic_cast<AssDialogue*>(*p.first);
line->Text = p.second;
for (size_t i = 0; i < LinesToChange.size(); ++i) {
LinesToChange[i].first->Text = LinesToChange[i].second;
}
if (modified) {
if (LinesToChange.size()) {
subs->Commit(_("kanji timing"), AssFile::COMMIT_DIAG_TEXT);
LinesToChange.clear();
}
Close();
}
/// @brief DOCME
/// @param 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);
else if (SourceStyle->GetValue() == DestStyle->GetValue())
wxMessageBox(_("The source and destination styles must be different."),_("Error"),wxICON_EXCLAMATION | wxOK);
@ -970,10 +758,6 @@ void DialogKanjiTimer::OnStart(wxCommandEvent &event) {
LinesToChange.clear();
}
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnLink(wxCommandEvent &event) {
if (display->AcceptMatch())
TryAutoMatch();
@ -981,40 +765,24 @@ void DialogKanjiTimer::OnLink(wxCommandEvent &event) {
wxBell();
}
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnUnlink(wxCommandEvent &event) {
if (!display->UndoMatch())
wxBell();
// Don't auto-match here, undoing sets the selection to the undone match
}
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnSkipSource(wxCommandEvent &event) {
currentSourceLine = FindNextStyleMatch(currentSourceLine, SourceStyle->GetValue());
ResetForNewLine();
}
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnSkipDest(wxCommandEvent &event) {
currentDestinationLine = FindNextStyleMatch(currentDestinationLine, DestStyle->GetValue());
ResetForNewLine();
}
/// @brief DOCME
/// @param 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.
currentSourceLine = FindPrevStyleMatch(currentSourceLine, SourceStyle->GetValue());
@ -1022,17 +790,11 @@ void DialogKanjiTimer::OnGoBack(wxCommandEvent &event) {
ResetForNewLine();
}
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnAccept(wxCommandEvent &event) {
if (display->GetRemainingSource() > 0)
wxMessageBox(_("Group all of the source text."),_("Error"),wxICON_EXCLAMATION | wxOK);
else {
wxString OutputText = display->GetOutputLine();
std::pair<entryIter,wxString> ins(currentDestinationLine, OutputText);
LinesToChange.push_back(ins);
LinesToChange.push_back(std::make_pair(dynamic_cast<AssDialogue*>(*currentDestinationLine), display->GetOutputLine()));
currentSourceLine = FindNextStyleMatch(currentSourceLine, SourceStyle->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) {
wxCommandEvent evt;
switch(event.GetKeyCode()) {
@ -1074,10 +831,6 @@ void DialogKanjiTimer::OnKeyDown(wxKeyEvent &event) {
}
}
/// @brief DOCME
/// @param event
///
void DialogKanjiTimer::OnKeyEnter(wxCommandEvent &event) {
wxCommandEvent evt;
@ -1091,10 +844,6 @@ void DialogKanjiTimer::OnKeyEnter(wxCommandEvent &event) {
}
}
/// @brief DOCME
///
void DialogKanjiTimer::ResetForNewLine()
{
AssDialogue *src = 0;
@ -1118,21 +867,12 @@ void DialogKanjiTimer::ResetForNewLine()
display->SetFocus();
}
/// @brief DOCME
///
void DialogKanjiTimer::TryAutoMatch()
{
if (Interpolate->GetValue())
display->AutoMatchJapanese();
}
/// @brief DOCME
/// @param search_from
/// @param search_style
/// @return
///
entryIter DialogKanjiTimer::FindNextStyleMatch(entryIter search_from, const wxString &search_style)
{
if (search_from == subs->Line.end()) return search_from;
@ -1147,11 +887,6 @@ entryIter DialogKanjiTimer::FindNextStyleMatch(entryIter search_from, const wxSt
return search_from;
}
/// @brief DOCME
/// @param search_from
/// @param search_style
///
entryIter DialogKanjiTimer::FindPrevStyleMatch(entryIter search_from, const wxString &search_style)
{
if (search_from == subs->Line.begin()) return search_from;
@ -1165,5 +900,3 @@ entryIter DialogKanjiTimer::FindPrevStyleMatch(entryIter search_from, const wxSt
return search_from;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -51,8 +51,8 @@
#include "include/aegisub/audio_provider.h"
#include "include/aegisub/menu.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_karaoke.h"
#include "ass_override.h"
#include "ass_style.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);
}
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)
/// @return
///

View File

@ -86,12 +86,6 @@ public:
/// @param pos Position in line
/// @param estimateTimes Adjust the times based on the lengths of the halves
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
/// @param n1 First frame to duplicate
/// @param n2 Last frame to duplicate