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:
parent
cc147d1660
commit
c936306593
25 changed files with 1356 additions and 1934 deletions
|
@ -1775,6 +1775,10 @@
|
|||
RelativePath="..\..\src\audio_timing_dialogue.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\src\audio_timing_karaoke.cpp"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="Video UI"
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
326
aegisub/src/audio_timing_karaoke.cpp
Normal file
326
aegisub/src/audio_timing_karaoke.cpp
Normal 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]);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
};
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
///
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue