diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj index 26ebc056c..30b63baab 100644 --- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj +++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj @@ -1775,6 +1775,10 @@ RelativePath="..\..\src\audio_timing_dialogue.cpp" > + + + diff --git a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters index 819186bdb..00002e03a 100644 --- a/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters +++ b/aegisub/build/msbuild/Aegisub/Aegisub.vcxproj.filters @@ -728,6 +728,9 @@ Audio\UI + + Audio\UI + Commands diff --git a/aegisub/src/Makefile b/aegisub/src/Makefile index 07e3f635a..7293750c8 100644 --- a/aegisub/src/Makefile +++ b/aegisub/src/Makefile @@ -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 \ diff --git a/aegisub/src/ass_karaoke.cpp b/aegisub/src/ass_karaoke.cpp index d04a735e2..b56fa8a1f 100644 --- a/aegisub/src/ass_karaoke.cpp +++ b/aegisub/src/ass_karaoke.cpp @@ -1,29 +1,16 @@ -// Copyright (c) 2006-2007, Niels Martin Hansen -// All rights reserved. +// Copyright (c) 2011, Thomas Goyne // -// 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 +#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::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(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(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(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(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(); - - } 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 const& lines, agi::Context *c) { + if (lines.empty()) return; + + AssKaraoke kara; + + std::set sel = c->selectionController->GetSelectedSet(); + + bool did_split = false; + for (std::list::iterator it = c->ass->Line.begin(); it != c->ass->Line.end(); ++it) { + AssDialogue *diag = dynamic_cast(*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); +} diff --git a/aegisub/src/ass_karaoke.h b/aegisub/src/ass_karaoke.h index 128990993..b91d02c35 100644 --- a/aegisub/src/ass_karaoke.h +++ b/aegisub/src/ass_karaoke.h @@ -1,29 +1,16 @@ -// Copyright (c) 2007, Niels Martin Hansen -// All rights reserved. +// Copyright (c) 2011, Thomas Goyne // -// 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 +#include #include + +#include #endif -#include "ass_dialogue.h" +#include +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 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::iterator ovr_iterator; + std::vector 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::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 const& lines, agi::Context *c); + + DEFINE_SIGNAL_ADDERS(AnnounceSyllablesChanged, AddSyllablesChangedListener) }; - - -/// DOCME -typedef std::vector AssKaraokeVector; - -void ParseAssKaraokeTags(const AssDialogue *line, AssKaraokeVector &syls); - - diff --git a/aegisub/src/audio_box.cpp b/aegisub/src/audio_box.cpp index 3cfc50236..67e3762ee 100644 --- a/aegisub/src/audio_box.cpp +++ b/aegisub/src/audio_box.cpp @@ -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); -} diff --git a/aegisub/src/audio_box.h b/aegisub/src/audio_box.h index c22979d3a..1a8e87e5e 100644 --- a/aegisub/src/audio_box.h +++ b/aegisub/src/audio_box.h @@ -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() }; diff --git a/aegisub/src/audio_controller.h b/aegisub/src/audio_controller.h index c31e2e93b..ffade17cb 100644 --- a/aegisub/src/audio_controller.h +++ b/aegisub/src/audio_controller.h @@ -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 diff --git a/aegisub/src/audio_karaoke.cpp b/aegisub/src/audio_karaoke.cpp index a01db2bfa..60a73d90a 100644 --- a/aegisub/src/audio_karaoke.cpp +++ b/aegisub/src/audio_karaoke.cpp @@ -1,29 +1,16 @@ -// Copyright (c) 2005-2009, Rodrigo Braz Monteiro, Niels Martin Hansen -// All rights reserved. +// Copyright (c) 2011, Thomas Goyne // -// 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,855 +23,285 @@ #include "config.h" +#include "audio_karaoke.h" + #ifndef AGI_PRE #include +#include +#include +#include #include +#include #include +#include #include +#include #include #endif -#include +#include "include/aegisub/context.h" +#include "audio_timing.h" +#include "ass_dialogue.h" +#include "ass_file.h" +#include "ass_karaoke.h" #include "ass_override.h" -#include "audio_box.h" -#include "audio_controller.h" -#include "audio_display.h" -#include "audio_karaoke.h" +#include "libresrc/libresrc.h" +#include "main.h" #include "selection_controller.h" -/// @brief Empty constructor -/// -AudioKaraokeSyllable::AudioKaraokeSyllable() -: AssKaraokeSyllable() -, start_time(0), selected(false) -, display_w(0), display_x(0) +template +static inline size_t last_lt_or_eq(Container const& c, Value const& v) { + typename Container::const_iterator it = lower_bound(c.begin(), c.end(), v); + // lower_bound gives first >= + if (it == c.end() || *it > v) + --it; + return distance(c.begin(), it); +}; + +AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c) +: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_SUNKEN) +, c(c) +, file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this)) +, active_line(0) +, kara(new AssKaraoke) +, enabled(false) { + using std::tr1::bind; + + wxSizer *main_sizer = new wxBoxSizer(wxHORIZONTAL); + + cancel_button = new wxBitmapButton(this, -1, GETIMAGE(kara_split_cancel_16)); + cancel_button->SetToolTip(_("Discard all uncommitted splits")); + cancel_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, bind(&AudioKaraoke::CancelSplit, this)); + main_sizer->Add(cancel_button); + + accept_button = new wxBitmapButton(this, -1, GETIMAGE(kara_split_accept_16)); + accept_button->SetToolTip(_("Commit splits")); + accept_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, bind(&AudioKaraoke::AcceptSplit, this)); + main_sizer->Add(accept_button); + + split_area = new wxPanel(this); + main_sizer->Add(split_area, wxSizerFlags(1).Expand()); + + SetSizerAndFit(main_sizer); + + /// @todo subscribe + split_font.SetFaceName(OPT_GET("Audio/Karaoke/Font Face")->GetString()); + split_font.SetPointSize(OPT_GET("Audio/Karaoke/Font Size")->GetInt()); + + Bind(wxEVT_SIZE, bind(&AudioKaraoke::Refresh, this, false, (const wxRect*)0)); + split_area->Bind(wxEVT_PAINT, &AudioKaraoke::OnPaint, this); + split_area->Bind(wxEVT_LEFT_DOWN, &AudioKaraoke::OnMouse, this); + split_area->Bind(wxEVT_LEFT_UP, &AudioKaraoke::OnMouse, this); + split_area->Bind(wxEVT_MOTION, &AudioKaraoke::OnMouse, this); + split_area->Bind(wxEVT_LEAVE_WINDOW, &AudioKaraoke::OnMouse, this); + split_area->Bind(wxEVT_CONTEXT_MENU, &AudioKaraoke::OnContextMenu, this); + + c->selectionController->AddSelectionListener(this); + + SetEnabled(false); } -/// @brief Copy-from-base constructor -/// @param base -/// -AudioKaraokeSyllable::AudioKaraokeSyllable(const AssKaraokeSyllable &base) -: AssKaraokeSyllable(base) -, start_time(0), selected(false) -, display_w(0), display_x(0) -{ -} - -/// @brief Constructor -/// @param parent -/// -AudioKaraoke::AudioKaraoke(wxWindow *parent) -: wxWindow (parent,-1,wxDefaultPosition,wxSize(10,5),wxTAB_TRAVERSAL|wxBORDER_SUNKEN) -{ - LOG_D("karaoke/audio") << "Constructor"; - enabled = false; - splitting = false; - split_cursor_syl = -1; - curSyllable = 0; - diag = 0; - workDiag = 0; -} - -/// @brief Destructor -/// AudioKaraoke::~AudioKaraoke() { - delete workDiag; + c->selectionController->RemoveSelectionListener(this); } -/// @brief Load from dialogue -/// @param _diag -/// @return -/// -bool AudioKaraoke::LoadFromDialogue(AssDialogue *_diag) { - LOG_D("karaoke/audio") << "diag=" << _diag; - // Make sure we're not in splitting-mode - if (splitting) { - LOG_D("karaoke/audio") << "is splitting, discarding splits"; - // Discard any splits and leave split-mode - EndSplit(false); +void AudioKaraoke::OnActiveLineChanged(AssDialogue *new_line) { + active_line = new_line; + if (enabled) { + LoadFromLine(); + split_area->Refresh(false); + } +} + +void AudioKaraoke::OnFileChanged(int type) { + if (enabled && (type & AssFile::COMMIT_DIAG_FULL)) { + LoadFromLine(); + split_area->Refresh(false); + } +} + +void AudioKaraoke::SetEnabled(bool en) { + wxSize new_size; + if (en) { + LoadFromLine(); + enabled = true; + c->audioController->SetTimingController(CreateKaraokeTimingController(c, kara.get(), file_changed)); + new_size = wxSize(-1, accept_button->GetSize().GetHeight() + 4); + } + else { + accept_button->Enable(false); + cancel_button->Enable(false); + enabled = false; + c->audioController->SetTimingController(CreateDialogueTimingController(c->audioController, c->selectionController, c->ass)); + new_size = wxSize(-1, 0); } - // Set dialogue - delete workDiag; - diag = _diag; - if (!diag) { - LOG_D("karaoke/audio") << "no diag, refreshing and returning flase"; + SetMinSize(new_size); + SetMaxSize(new_size); + SetSize(new_size); + GetParent()->Layout(); + split_area->SetSize(GetSize().GetWidth(), -1); + + if (en) Refresh(false); - return false; - } - - // Split - LOG_D("karaoke/audio") << "split"; - workDiag = new AssDialogue(diag->GetEntryData(), false); - workDiag->ParseASSTags(); - must_rebuild = false; - bool hasKar = ParseDialogue(workDiag); - - // No karaoke, autosplit - if (!hasKar) { - LOG_D("karaoke/audio") << "no existing karaoke, auto-splitting"; - AutoSplit(); - } - - // Done - //if (curSyllable < 0) curSyllable = syllables.size()-1; - //if (curSyllable >= (signed) syllables.size()) curSyllable = 0; - //SetSelection(curSyllable); - //Refresh(false); - LOG_D("karaoke/audio") << "returning " << hasKar; - return !hasKar; } -/// @brief Writes line back -/// @return -/// -void AudioKaraoke::Commit() { - LOG_D("karaoke/audio") << "commit"; - if (splitting) { - LOG_D("karaoke/audio") << "splitting, ending split"; - EndSplit(true); - } - wxString finalText = _T(""); - AudioKaraokeSyllable *syl; - size_t n = syllables.size(); - LOG_D("karaoke/audio") << "syllabels.size() = " << n; - if (must_rebuild) { - LOG_D("karaoke/audio") << "must_rebuild"; - workDiag->ClearBlocks(); - for (size_t i=0;itype.c_str(), syl->duration) + syl->text; - } - workDiag->Text = finalText; - workDiag->ParseASSTags(); - diag->Text = finalText; - } else { - LOG_D("karaoke/audio") << "updating karaoke without rebuild"; - for (size_t i = 0; i < n; i++) { - LOG_D("karaoke/audio") << "updating syllabel: " << i; - syl = &syllables.at(i); - LOG_D("karaoke/audio") << "syllabel pointer: " << syl << "; tagdata pointer: " << syl->tag << "; length: " << syl->duration; - // Some weird people have text before the first karaoke tag on a line. - // Check that a karaoke tag actually exists for the (non-)syllable to avoid a crash. - if (syl->tag && syl->tag->Params.size()>0) - syl->tag->Params[0]->Set(syl->duration); - // Of course, if the user changed the duration of such a non-syllable, its timing can't be updated and will stay zero. - // There is no way to check for that right now, and I can't bother to fix it. - } - LOG_D("karaoke/audio") << "done updating syllabels"; - workDiag->UpdateText(); - workDiag->ClearBlocks(); - workDiag->ParseASSTags(); - diag->Text = workDiag->Text; - } - LOG_D("karaoke/audio") << "returning"; -} +void AudioKaraoke::OnPaint(wxPaintEvent &evt) { + int w, h; + split_area->GetClientSize(&w, &h); -/// @brief Autosplit line -/// @return -/// -void AudioKaraoke::AutoSplit() { - LOG_D("karaoke/audio") << "autosplit"; - - // Get lengths - int timeLen = (diag->End.GetMS() - diag->Start.GetMS())/10; - int letterlen = diag->Text.Length(); - int round = letterlen / 2; - int curlen; - int acumLen = 0; - wxString newText; - - // Parse words - wxStringTokenizer tkz(diag->Text,_T(" "),wxTOKEN_RET_DELIMS); - wxArrayString words; - while (tkz.HasMoreTokens()) { - words.Add(tkz.GetNextToken()); - } - - // Process words - for (size_t i=0;i timeLen) { - curlen -= acumLen - timeLen; - acumLen = timeLen; - } - - // Ensure that it accumulates all of it - if (i == words.Count()-1 && acumLen < timeLen) { - curlen += timeLen - acumLen; - acumLen = timeLen; - } - - newText += wxString::Format(_T("{\\k%i}"),curlen) + words[i]; - } - - // Workaround for bug #503 - // Make the line one blank syllable if it's completely blank - if (newText == _T("")) newText = wxString::Format(_T("{\\k%d}"), timeLen); - - // Load - must_rebuild = true; - AssDialogue newDiag(diag->GetEntryData()); - newDiag.Text = newText; - newDiag.ParseASSTags(); - ParseDialogue(&newDiag); - - LOG_D("karaoke/audio") << "returning"; -} - -/// @brief Parses text to extract karaoke -/// @param curDiag -/// @return -/// -bool AudioKaraoke::ParseDialogue(AssDialogue *curDiag) { - // parse the tagdata - AssKaraokeVector tempsyls; - ParseAssKaraokeTags(curDiag, tempsyls); - - bool found_kara = tempsyls.size() > 1; - - // copy base syllables to real - syllables.clear(); - syllables.reserve(tempsyls.size()); - int cur_time = 0; - for (AssKaraokeVector::iterator base = tempsyls.begin(); base != tempsyls.end(); ++base) { - AudioKaraokeSyllable fullsyl(*base); - fullsyl.start_time = cur_time; - cur_time += fullsyl.duration; - syllables.push_back(fullsyl); - } - - // if first syllable is empty, remove it - if (!syllables[0].unstripped_text) { - syllables.erase(syllables.begin()); - found_kara = syllables.size() > 0; - } - - // if there's more than one syllable in the list, at least one karaoke tag was found - return found_kara; -} - -/// @brief Set syllable -/// @param n -/// @return -/// -void AudioKaraoke::SetSyllable(int n) { - LOG_D("karaoke/audio") << "n=" << n; - if (n == -1) n = syllables.size()-1; - if (n >= (signed) syllables.size()) n = 0; - curSyllable = n; - startClickSyl = n; - SetSelection(n); - Refresh(false); - LOG_D("karaoke/audio") << "returning"; -} - -/////////////// -// Event table -BEGIN_EVENT_TABLE(AudioKaraoke,wxWindow) - EVT_PAINT(AudioKaraoke::OnPaint) - EVT_SIZE(AudioKaraoke::OnSize) - EVT_MOUSE_EVENTS(AudioKaraoke::OnMouse) -END_EVENT_TABLE() - -/// @brief Paint event -/// @param event -/// -void AudioKaraoke::OnPaint(wxPaintEvent &event) { - // Get dimensions - int w,h; - GetClientSize(&w,&h); - - // Start Paint - wxPaintDC dc(this); + wxPaintDC dc(split_area); // Draw background - dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE))); + dc.SetBrush(wxBrush(wxSystemSettings::GetColour(enabled ? wxSYS_COLOUR_WINDOW : wxSYS_COLOUR_FRAMEBK))); dc.SetPen(*wxTRANSPARENT_PEN); - dc.DrawRectangle(0,0,w,h); + dc.DrawRectangle(0, 0, w, h); - // Set syllable font - wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_NORMAL,false,_T("Verdana"),wxFONTENCODING_SYSTEM); // FIXME, hardcoded font - dc.SetFont(curFont); + if (!enabled) return; - // Draw syllables - if (enabled) { - wxString temptext; - size_t syln = syllables.size(); - int dx = 0; - int tw,th; - int delta; - int dlen; - for (size_t i=0;i 0) { - wxArrayInt widths; - if (dc.GetPartialTextExtents(temptext, widths)) { - for (unsigned int j = 0; j < syl.pending_splits.size(); j++) { - dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); - int splitxpos = dx + 4; - // Handle splitters placed before first char in syllable; these are represented as -1 - if (syl.pending_splits[j] >= 0) { - splitxpos += widths[syl.pending_splits[j]]; - } else { - splitxpos += 0; - } - dc.DrawLine(splitxpos, 0, splitxpos, h); - } - } else { - wxLogError(_T("Karaoke syllable display: Failed to GetPartialTextExtents. This should never happen, except on severely overloaded systems.")); - } - } - - if (splitting && split_cursor_syl == (signed)i /*&& split_cursor_x > 0*/) { - dc.SetPen(*wxRED); - dc.DrawLine(dx+4+split_cursor_x, 0, dx+4+split_cursor_x, h); - dc.SetPen(wxPen(wxColour(0,0,0))); - } - - // Set syllable data - syl.display_x = dx; - syl.display_w = dlen; - - // Increment dx - dx += dlen; - } + // Draw each character in the line + int y = (h - char_height) / 2; + for (size_t i = 0; i < spaced_text.size(); ++i) { + dc.DrawText(spaced_text[i], char_x[i], y); } - event.Skip(); + // Draw the split line under the mouse + dc.SetPen(*wxRED); + dc.DrawLine(mouse_pos, 0, mouse_pos, h); + + // Draw the lines between each syllable + dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); + for (size_t i = 0; i < syl_lines.size(); ++i) { + dc.DrawLine(syl_lines[i], 0, syl_lines[i], h); + } } -/// @brief Size event -/// @param event -/// -void AudioKaraoke::OnSize(wxSizeEvent &event) { - Refresh(false); +void AudioKaraoke::AddMenuItem(wxMenu &menu, wxString const& tag, wxString const& help, wxString const& selected) { + wxMenuItem *item = menu.AppendCheckItem(-1, tag, help); + menu.Bind(wxEVT_COMMAND_MENU_SELECTED, std::tr1::bind(&AudioKaraoke::SetTagType, this, tag), item->GetId()); + item->Check(tag == selected); +} + +void AudioKaraoke::OnContextMenu(wxContextMenuEvent&) { + if (!enabled) return; + + wxMenu context_menu(_("Karaoke tag")); + wxString type = kara->GetTagType(); + + AddMenuItem(context_menu, "\\k", _("Change karaoke tag to \\k"), type); + AddMenuItem(context_menu, "\\kf", _("Change karaoke tag to \\kf"), type); + AddMenuItem(context_menu, "\\ko", _("Change karaoke tag to \\ko"), type); + + PopupMenu(&context_menu); } -/// @brief Mouse event -/// @param event -/// @return -/// void AudioKaraoke::OnMouse(wxMouseEvent &event) { - // Get coordinates - int x = event.GetX(); - //int y = event.GetY(); - bool shift = event.m_shiftDown; + if (!enabled) return; - // Syllable selection mode - if (!splitting) { - int syl = GetSylAtX(x); + int old_mouse_pos = mouse_pos; + mouse_pos = event.GetX(); - // Button pressed - if (event.LeftDown() || event.RightDown()) { - if (syl != -1) { - if (shift) { - SetSelection(syl,startClickSyl); - Refresh(false); - } + if (event.Leaving()) + mouse_pos = -1; - else { - SetSelection(syl); - startClickSyl = syl; - curSyllable = syl; - Refresh(false); - display->Update(); - CaptureMouse(); - } - } - } - // Dragging to make a selection - else if (event.Dragging() && (event.LeftIsDown() || event.RightIsDown())) { - if (syl < 0) syl = 0; - SetSelection(syl, startClickSyl); - Refresh(false); - } - // Released left button - else if (event.LeftUp() && HasCapture()) { - ReleaseMouse(); - } - // Released right button; make a menu for selecting \k type - else if (event.RightUp()) { - if (HasCapture()) - ReleaseMouse(); - - AudioKaraokeTagMenu menu(this); - PopupMenu(&menu); - } + if (!event.LeftDown()) { + // Erase the old line and draw the new one + wxRect r1(mouse_pos - 1, 0, mouse_pos + 1, 100); + wxRect r2(old_mouse_pos - 1, 0, old_mouse_pos + 1, 100); + split_area->Refresh(false, &r1); + split_area->Refresh(false, &r2); + return; + } + + // Character to insert the new split point before + int split_pos = std::min((mouse_pos - char_width / 2) / char_width, spaced_text.size()); + + // Syllable this character is in + int syl = last_lt_or_eq(syl_start_points, split_pos); + + // If the click is sufficiently close to a line of a syllable split, + // remove that split rather than adding a new one + if ((syl > 0 && mouse_pos <= syl_lines[syl - 1] + 2) || (syl < (int)syl_lines.size() && mouse_pos >= syl_lines[syl] - 2)) { + kara->RemoveSplit(syl); } - - // Karaoke syllable splitting mode else { - int syli = GetSylAtX(x); - - // Valid syllable - if (syli != -1) { - AudioKaraokeSyllable &syl = syllables.at(syli); - - // Get the widths after each character in the text - wxClientDC dc(this); - wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_NORMAL,false,_T("Verdana"),wxFONTENCODING_SYSTEM); - dc.SetFont(curFont); - wxArrayInt widths; - dc.GetPartialTextExtents(syl.text, widths); - - // Find the character closest to the mouse - int rx = x - syl.display_x - 4; - int split_cursor_char = -2; - split_cursor_syl = -1; - split_cursor_x = -1; - if (syl.text.Len() > 0) { - int lastx = 0; - split_cursor_syl = syli; - for (unsigned int i = 0; i < widths.size(); i++) { - //wx Log Debug(_T("rx=%d, lastx=%d, widths[i]=%d, i=%d, widths.size()=%d, syli=%d"), rx, lastx, widths[i], i, widths.size(), syli); - if (lastx - rx < widths[i] - rx) { - if (rx - lastx < widths[i] - rx) { - //wx Log Debug(_T("Found at PREV!")); - split_cursor_x = lastx; - split_cursor_char = i - 1; - break; - } else if (rx < widths[i]) { - //wx Log Debug(_T("Found at CURRENT!")); - split_cursor_x = widths[i]; - split_cursor_char = i; - break; - } - } - lastx = widths[i]; - } - // If no split-point was caught by the for-loop, it must be over the last character, - // ie. split at next-to-last position - if (split_cursor_x < 0) { - //wx Log Debug(_T("Emergency picking LAST!")); - split_cursor_x = widths[widths.size()-1]; - split_cursor_char = widths.size() - 1; - } - } - - // Do something if there was a click and we're at a valid position - if (event.LeftDown() && split_cursor_char >= -1) { - //wx Log Debug(_T("A click!")); - int num_removed = 0; - std::vector::iterator i = syl.pending_splits.begin(); - while (i != syl.pending_splits.end()) { - if (split_cursor_char == *i) { - //wx Log Debug(_T("Erasing entry")); - num_removed++; - syl.pending_splits.erase(i); - break; - } else { - i++; - } - } - if (num_removed == 0) { - syl.pending_splits.push_back(split_cursor_char); - } - } - - } - - // Invalid syllable (int syli = GetSylAtX(x); returned -1) - else { - split_cursor_syl = -1; - split_cursor_x = -1; - } - - if (split_cursor_syl >= 0) { - Refresh(false); - } + kara->AddSplit(syl, split_pos - syl_start_points[syl]); } + + SetDisplayText(); + accept_button->Enable(true); + cancel_button->Enable(true); + split_area->Refresh(false); } -/// @brief Get Syllable at position X -/// @param x -/// @return -/// -int AudioKaraoke::GetSylAtX(int x) { - int dx,dw; - size_t syln = syllables.size(); - for (size_t i=0;i= dx && x < dx+dw) { - return i; - } - } - return -1; +void AudioKaraoke::LoadFromLine() { + kara->SetLine(active_line, true); + SetDisplayText(); + accept_button->Enable(false); + cancel_button->Enable(false); } -/// @brief Set selection -/// @param start -/// @param end -/// -void AudioKaraoke::SetSelection(int start,int end) { - LOG_D("karaoke/audio") << "start=" << start << " end=" << end; - // Default end - if (end == -1) end = start; - - // Get min/max range - size_t min = start; - size_t max = end; - if (max < min) { - size_t temp = max; - max = min; - min = temp; +void AudioKaraoke::SetDisplayText() { + // Insert spaces between each syllable to avoid crowding + spaced_text.clear(); + syl_start_points.clear(); + syl_start_points.reserve(kara->size()); + for (AssKaraoke::iterator it = kara->begin(); it != kara->end(); ++it) { + syl_start_points.push_back(spaced_text.size()); + spaced_text += " " + it->text; } - LOG_D("karaoke/audio") << "min=" << min << ", max=" << max; - // Set values - bool state; - size_t syls = syllables.size(); - int sels = 0; - for (size_t i=0;i= min && i <= max); - syllables.at(i).selected = state; - if (state) sels++; + // Get the x-coordinates of the right edge of each character + wxMemoryDC dc; + dc.SetFont(split_font); + wxArrayInt p_char_x; + dc.GetPartialTextExtents(spaced_text, p_char_x); + + // Convert the partial sub to the the width of each character + adjacent_difference(p_char_x.begin(), p_char_x.end(), p_char_x.begin()); + + // Get the maximum character width + char_width = *max_element(p_char_x.begin(), p_char_x.end()); + + // Center each character within the space available to it + char_x.resize(p_char_x.size()); + for (size_t i = 0; i < p_char_x.size(); ++i) { + char_x[i] = i * char_width + (char_width - p_char_x[i]) / 2; } - curSyllable = min; - selectionCount = max-min+1; - LOG_D("karaoke/audio") << "new curSyllabel=" << curSyllable << ", selectionCount=" << selectionCount; - // Set box buttons - box->SetKaraokeButtons(); + // Calculate the positions of the syllable divider lines + syl_lines.resize(syl_start_points.size() - 1); + for (size_t i = 1; i < syl_start_points.size(); ++i) { + syl_lines[i - 1] = syl_start_points[i] * char_width + char_width / 2; + } + + // Get line height + wxSize extents = dc.GetTextExtent(spaced_text); + char_height = extents.GetHeight(); } -/// @brief Join syllables -/// @return -/// -void AudioKaraoke::Join() { - LOG_D("karaoke/audio") << "join"; - // Variables - bool gotOne = false; - size_t syls = syllables.size(); - AudioKaraokeSyllable *curSyl; - int first = 0; - - // Loop - for (size_t i=0;iRefresh(false); } -/// @brief Enter splitting-mode -/// -void AudioKaraoke::BeginSplit() { - LOG_D("karaoke/audio") << "beginsplit"; - splitting = true; - split_cursor_syl = -1; - split_cursor_x = -1; - box->SetKaraokeButtons(); - Refresh(false); +void AudioKaraoke::AcceptSplit() { + active_line->Text = kara->GetText(); + file_changed.Block(); + c->ass->Commit(_("karaoke split"), AssFile::COMMIT_DIAG_TEXT); + file_changed.Unblock(); + + accept_button->Enable(false); + cancel_button->Enable(false); } -/// @brief Leave splitting-mode, committing changes -/// @param commit -/// @return -/// -void AudioKaraoke::EndSplit(bool commit) { - LOG_D("karaoke/audio") << "endsplit, commit=" << commit; - splitting = false; - bool hasSplit = false; - size_t first_sel = ~0U; - for (size_t i = 0; i < syllables.size(); i ++) { - if (syllables[i].pending_splits.size() > 0) { - if (commit) { - if (syllables[i].selected && i < first_sel) - first_sel = i; - SplitSyl(i); - hasSplit = true; - } else { - syllables[i].pending_splits.clear(); - } - } - } - - // Update - if (hasSplit) { - LOG_D("karaoke/audio") << "hassplit"; - must_rebuild = true; - //display->NeedCommit = true; - SetSelection(first_sel); - display->Update(); - } - // Always redraw, since the display is different in splitting mode - box->SetKaraokeButtons(); - Refresh(false); - - LOG_D("karaoke/audio") << "returning"; +void AudioKaraoke::SetTagType(wxString new_tag) { + kara->SetTagType(new_tag); + AcceptSplit(); } - -/// @brief Split a syllable using the pending_slits data -/// @param n -/// @return -/// -int AudioKaraoke::SplitSyl (unsigned int n) { - LOG_D("karaoke/audio") << "splitsyl, n=" << n; - - // Avoid multiple vector resizes (this must be first) - syllables.reserve(syllables.size() + syllables[n].pending_splits.size()); - - // The syllable we're splitting - AudioKaraokeSyllable &basesyl = syllables[n]; - LOG_D("karaoke/audio") << "basesyl, contents='" << basesyl.unstripped_text.c_str() << "', selected=" << basesyl.selected; - - // Start by sorting the split points - std::sort(basesyl.pending_splits.begin(), basesyl.pending_splits.end()); - - wxString originalText = basesyl.text; - int originalDuration = basesyl.duration; - - // Fixup the first syllable - basesyl.text = originalText.Mid(0, basesyl.pending_splits[0] + 1); - basesyl.unstripped_text = basesyl.text; - basesyl.selected = false; - basesyl.duration = originalDuration * basesyl.text.Length() / originalText.Length(); - int curpos = basesyl.start_time + basesyl.duration; - - // For each split, make a new syllable - for (unsigned int i = 0; i < basesyl.pending_splits.size(); i++) { - AudioKaraokeSyllable newsyl; - if (i < basesyl.pending_splits.size()-1) { - // in the middle - newsyl.text = originalText.Mid(basesyl.pending_splits[i]+1, basesyl.pending_splits[i+1] - basesyl.pending_splits[i]); - } else { - // the last one (take the rest) - newsyl.text = originalText.Mid(basesyl.pending_splits[i]+1); - } - newsyl.unstripped_text = newsyl.text; - newsyl.duration = originalDuration * newsyl.text.Length() / originalText.Length(); - newsyl.start_time = curpos; - newsyl.type = basesyl.type; - newsyl.selected = false;//basesyl.selected; - LOG_D("karaoke/audio") << "splitsyl: newsyl, contents='" << newsyl.text.c_str() << "', selected=" << newsyl.selected; - curpos += newsyl.duration; - syllables.insert(syllables.begin()+n+i+1, newsyl); - } - - // The total duration of the new syllables will be equal to or less than the original duration - // Fix this, so they'll always add up - // Use an unfair method, just adding 1 to each syllable one after another, until it's correct - int newDuration = 0; - for (unsigned int j = n; j < basesyl.pending_splits.size()+n+1; j++) { - newDuration += syllables[j].duration; - } - unsigned int k = n; - while (newDuration < originalDuration) { - syllables[k].duration++; - k++; - if (k >= syllables.size()) { - k = n; - } - newDuration++; - } - - // Prepare for return and clear pending splits - int numsplits = basesyl.pending_splits.size(); - basesyl.pending_splits.clear(); - return numsplits; -} - -/// @brief Apply delta length to syllable -/// @param n -/// @param delta -/// @param mode -/// @return -/// -bool AudioKaraoke::SyllableDelta(int n,int delta,int mode) { - LOG_D("karaoke/audio") << "n=" << n << ", delta=" << delta << ", mode=" << mode; - // Get syllable and next - AudioKaraokeSyllable *curSyl=NULL,*nextSyl=NULL; - curSyl = &syllables.at(n); - int nkar = syllables.size(); - if (n < nkar-1) { - nextSyl = &syllables.at(n+1); - } - - // Get variables - int len = curSyl->duration; - - // Cap delta - int minLen = 0; - if (len + delta < minLen) delta = minLen-len; - if (mode == 0 && nextSyl && (nextSyl->duration - delta) < minLen) delta = nextSyl->duration - minLen; - - LOG_D("karaoke/audio") << "nkar=" << nkar << ", len=" << len << ", minLen=" << minLen << ", delta=" << delta; - - // Apply - if (delta != 0) { - LOG_D("karaoke/audio") << "delta != 0"; - curSyl->duration += delta; - - // Normal mode - if (mode == 0 && nextSyl) { - LOG_D("karaoke/audio") << "normal mode"; - nextSyl->duration -= delta; - nextSyl->start_time += delta; - } - - // Shift mode - if (mode == 1) { - LOG_D("karaoke/audio") << "shift mode"; - for (int i=n+1;isyllables.size(); i++) { - AudioKaraokeSyllable &syl = kara->syllables[i]; - if (syl.selected) { - if (syl.type == _T("\\k")) { - Check(10001, true); - } else if (syl.type == _T("\\kf") || syl.type == _T("\\K")) { - Check(10002, true); - } else if (syl.type == _T("\\ko")) { - Check(10003, true); - } - } - } -} - -/// @brief Karaoke tag menu destructor -/// -AudioKaraokeTagMenu::~AudioKaraokeTagMenu() { -} - -/////////////// -// Event table -BEGIN_EVENT_TABLE(AudioKaraokeTagMenu,wxMenu) - EVT_MENU_RANGE(10001, 10003, AudioKaraokeTagMenu::OnSelectItem) -END_EVENT_TABLE() - -/// @brief Karaoke tag menu event handler -/// @param event -/// -void AudioKaraokeTagMenu::OnSelectItem(wxCommandEvent &event) { - // Select the new tag for the syllables - wxString newtag; - switch (event.GetId()) { - case 10001: newtag = _T("\\k"); break; - case 10002: newtag = _T("\\kf"); break; - case 10003: newtag = _T("\\ko"); break; - default: return; - } - - // Apply it - size_t firstsel = kara->syllables.size(); - int lastsel = -1; - for (size_t i = 0; i < kara->syllables.size(); i++) { - AudioKaraokeSyllable &syl = kara->syllables[i]; - if (syl.selected) { - if (firstsel > i) firstsel = i; - lastsel = i; - syl.type = newtag; - } - } - - // Update display - kara->must_rebuild = true; - //kara->Commit(); - //kara->display->NeedCommit = true; - /// @todo Commit changes and stay on current line - //kara->display->CommitChanges(); - //kara->display->Update(); - kara->SetSelection(firstsel, lastsel); -} - diff --git a/aegisub/src/audio_karaoke.h b/aegisub/src/audio_karaoke.h index cc4d054c8..8399f5c83 100644 --- a/aegisub/src/audio_karaoke.h +++ b/aegisub/src/audio_karaoke.h @@ -1,29 +1,16 @@ -// Copyright (c) 2005, Rodrigo Braz Monteiro -// All rights reserved. +// Copyright (c) 2011, Thomas Goyne // -// 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 #include -#include -#include #include #endif -#include "ass_karaoke.h" +#include +#include +#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 pending_splits; - - /// DOCME - int display_w; - - /// DOCME - int display_x; - - AudioKaraokeSyllable(); - AudioKaraokeSyllable(const AssKaraokeSyllable &base); -}; - -/// DOCME -typedef std::vector 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 { + 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 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 syl_start_points; - /// DOCME - bool must_rebuild; + /// x coordinate in pixels of the separator lines of each syllable + std::vector syl_lines; + /// Left x coordinate of each character in spaced_text in pixels + std::vector 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); }; diff --git a/aegisub/src/audio_timing.h b/aegisub/src/audio_timing.h index 4dad031c9..dcd49f0a4 100644 --- a/aegisub/src/audio_timing.h +++ b/aegisub/src/audio_timing.h @@ -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 @@ -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 *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); diff --git a/aegisub/src/audio_timing_karaoke.cpp b/aegisub/src/audio_timing_karaoke.cpp new file mode 100644 index 000000000..389b41de9 --- /dev/null +++ b/aegisub/src/audio_timing_karaoke.cpp @@ -0,0 +1,326 @@ +// Copyright (c) 2011, Thomas Goyne +// +// 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 + +#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 +#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 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 markers; + + /// Labels containing the stripped text of each syllable + std::vector 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 &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(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 &out) const { + for (size_t i = 0; i < labels.size(); ++i) { + if (range.overlaps(labels[i].range)) + out.push_back(labels[i]); + } +} diff --git a/aegisub/src/command/audio.cpp b/aegisub/src/command/audio.cpp index 06fbf94f4..5fb607499 100644 --- a/aegisub/src/command/audio.cpp +++ b/aegisub/src/command/audio.cpp @@ -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); } } diff --git a/aegisub/src/command/edit.cpp b/aegisub/src/command/edit.cpp index 0b2bfa837..9108cdef1 100644 --- a/aegisub/src/command/edit.cpp +++ b/aegisub/src/command/edit.cpp @@ -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); } }; diff --git a/aegisub/src/command/icon.cpp b/aegisub/src/command/icon.cpp index c488df6f5..860f6aef3 100644 --- a/aegisub/src/command/icon.cpp +++ b/aegisub/src/command/icon.cpp @@ -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) diff --git a/aegisub/src/dialog_kara_timing_copy.cpp b/aegisub/src/dialog_kara_timing_copy.cpp index a8f936271..452045047 100644 --- a/aegisub/src/dialog_kara_timing_copy.cpp +++ b/aegisub/src/dialog_kara_timing_copy.cpp @@ -37,37 +37,35 @@ #include "config.h" +#include "dialog_kara_timing_copy.h" + #ifndef AGI_PRE #include -#include +#include +#include #include +#include #include +#include #include #include #include #include #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 src; - - /// DOCME - typedef std::vector::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 matched_groups; - - /// DOCME - typedef std::vector::iterator MatchedGroupIterator; - - /// DOCME std::deque unmatched_source; - - /// DOCME - typedef std::deque::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(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::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::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 p = LinesToChange.back(); - LinesToChange.pop_back(); - AssDialogue *line = dynamic_cast(*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 ins(currentDestinationLine, OutputText); - LinesToChange.push_back(ins); + LinesToChange.push_back(std::make_pair(dynamic_cast(*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; } - - diff --git a/aegisub/src/dialog_kara_timing_copy.h b/aegisub/src/dialog_kara_timing_copy.h index 45fa320c2..e8fa602bb 100644 --- a/aegisub/src/dialog_kara_timing_copy.h +++ b/aegisub/src/dialog_kara_timing_copy.h @@ -35,22 +35,19 @@ /// #ifndef AGI_PRE +#include #include -#include -#include #include -#include -#include #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::iterator entryIter; + /// DOCME AssFile *subs; @@ -75,7 +74,7 @@ class DialogKanjiTimer : public wxDialog { /// DOCME - std::vector > LinesToChange; + std::vector > 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() }; diff --git a/aegisub/src/include/aegisub/context.h b/aegisub/src/include/aegisub/context.h index 817c5845d..e37e7cd18 100644 --- a/aegisub/src/include/aegisub/context.h +++ b/aegisub/src/include/aegisub/context.h @@ -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; diff --git a/aegisub/src/kana_table.cpp b/aegisub/src/kana_table.cpp index 5cfc55419..33b3e96e7 100644 --- a/aegisub/src/kana_table.cpp +++ b/aegisub/src/kana_table.cpp @@ -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 } +}; diff --git a/aegisub/src/kana_table.h b/aegisub/src/kana_table.h index c0a06cef2..7a5121bd6 100644 --- a/aegisub/src/kana_table.h +++ b/aegisub/src/kana_table.h @@ -34,54 +34,24 @@ /// @ingroup kara_timing_copy /// - -/////////// -// Headers #ifndef AGI_PRE #include #include #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 entries; - KanaTable(); - ~KanaTable(); -}; +/// Table of Hiragana, Katakana and Hepburn romaji tuples. +extern KanaEntry KanaTable[]; diff --git a/aegisub/src/libresrc/default_config.json b/aegisub/src/libresrc/default_config.json index ece1c3a06..c3f5395f7 100644 --- a/aegisub/src/libresrc/default_config.json +++ b/aegisub/src/libresrc/default_config.json @@ -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 diff --git a/aegisub/src/libresrc/default_toolbar.json b/aegisub/src/libresrc/default_toolbar.json index b20d41c02..b8888431d 100644 --- a/aegisub/src/libresrc/default_toolbar.json +++ b/aegisub/src/libresrc/default_toolbar.json @@ -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", diff --git a/aegisub/src/subs_grid.cpp b/aegisub/src/subs_grid.cpp index e5f0e9655..09c48d23f 100644 --- a/aegisub/src/subs_grid.cpp +++ b/aegisub/src/subs_grid.cpp @@ -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 /// diff --git a/aegisub/src/subs_grid.h b/aegisub/src/subs_grid.h index 883bdd3bf..834cf712c 100644 --- a/aegisub/src/subs_grid.h +++ b/aegisub/src/subs_grid.h @@ -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