// Copyright (c) 2005, Rodrigo Braz Monteiro // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Aegisub Group nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // ----------------------------------------------------------------------------- // // AEGISUB // // Website: http://aegisub.cellosoft.com // Contact: mailto:zeratul@cellosoft.com // /////////// // Headers #include #include "audio_karaoke.h" #include "audio_display.h" #include "audio_box.h" #include "ass_dialogue.h" #include "ass_override.h" //////////////////////// // Syllable constructor KaraokeSyllable::KaraokeSyllable() { length = 0; position = 0; display_w = 0; display_x = 0; selected = false; } /////////////// // Constructor AudioKaraoke::AudioKaraoke(wxWindow *parent) : wxWindow (parent,-1,wxDefaultPosition,wxSize(10,5),wxTAB_TRAVERSAL|wxBORDER_SUNKEN) { enabled = false; curSyllable = 0; diag = NULL; } ////////////////////// // Load from dialogue bool AudioKaraoke::LoadFromDialogue(AssDialogue *_diag) { // Set dialogue diag = _diag; if (!diag) { Refresh(false); return false; } // Split bool hasKar = ParseDialogue(diag); // No karaoke, autosplit if (!hasKar) { AutoSplit(); } // Done SetSelection(curSyllable); Refresh(false); return !hasKar; } /////////////////////////////// // Calculate length of karaoke int AudioKaraoke::GetKaraokeLength(AssDialogueBlockOverride *block) { AssOverrideTag *tag; size_t n = block->Tags.size(); int len = -1; for (size_t i=0;iTags.at(i); if (tag->Name == _T("\\k") || tag->Name == _T("\\K") || tag->Name == _T("\\kf") || tag->Name == _T("\\ko")) { len = tag->Params.at(0)->AsInt(); } } return len; } //////////////////////////// // Gets tag of nth syllable wxString AudioKaraoke::GetSyllableTag(AssDialogueBlockOverride *block,int n) { return block->Tags.at(n)->Name; } //////////////////// // Writes line back void AudioKaraoke::Commit() { wxString finalText = _T(""); KaraokeSyllable *syl; size_t n = syllables.size(); for (size_t i=0;ilength) + syl->contents; } diag->Text = finalText; diag->ParseASSTags(); } ////////////////// // Autosplit line void AudioKaraoke::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); while (tkz.HasMoreTokens()) { wxString token = tkz.GetNextToken(); curlen = (token.Length() * timelen + round) / letterlen; acumLen += curlen; if (acumLen > timelen) { curlen -= acumLen - timelen; acumLen = timelen; } newText += wxString::Format(_T("{\\k%i}"),curlen) + token; } // Load AssDialogue newDiag(diag->data); newDiag.Text = newText; newDiag.ParseASSTags(); ParseDialogue(&newDiag); } ////////////////////////////////// // Parses text to extract karaoke bool AudioKaraoke::ParseDialogue(AssDialogue *curDiag) { // Wipe syllables.clear(); // Prepare syllable data AssDialogueBlock *block; AssDialogueBlockOverride *override; AssDialogueBlockPlain *plain; KaraokeSyllable temp; temp.contents = _T(""); int pos = 0; temp.length = 0; temp.position = 0; size_t n = curDiag->Blocks.size(); bool foundOne = false; bool foundBlock = false; // Load syllable data for (size_t i=0;iBlocks.at(i); override = AssDialogueBlock::GetAsOverride(block); if (override) { int len = GetKaraokeLength(override); if (len != -1) { if (foundOne) syllables.push_back(temp); foundOne = true; foundBlock = true; pos += temp.length; temp.length = len; temp.position = pos; temp.contents = _T(""); } } else { plain = AssDialogueBlock::GetAsPlain(block); temp.contents += plain->text; if (plain->text != _T("")) foundOne = true; } } // Empty? if (curDiag->Text.IsEmpty()) { temp.length = (curDiag->End.GetMS() - curDiag->Start.GetMS())/10; temp.contents = curDiag->Text; temp.position = 0; foundBlock = true; } // Last syllable if (foundBlock) syllables.push_back(temp); return foundBlock; } //////////////// // Set syllable void AudioKaraoke::SetSyllable(int n) { curSyllable = n; startClickSyl = n; SetSelection(n); Refresh(false); } /////////////// // Event table BEGIN_EVENT_TABLE(AudioKaraoke,wxWindow) EVT_PAINT(AudioKaraoke::OnPaint) EVT_SIZE(AudioKaraoke::OnSize) EVT_MOUSE_EVENTS(AudioKaraoke::OnMouse) END_EVENT_TABLE() /////////////// // Paint event void AudioKaraoke::OnPaint(wxPaintEvent &event) { // Get dimensions int w,h; GetClientSize(&w,&h); // Start Paint wxPaintDC dc(this); dc.BeginDrawing(); // Draw background dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE))); dc.SetPen(*wxTRANSPARENT_PEN); dc.DrawRectangle(0,0,w,h); // Set syllable font wxFont curFont(9,wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_NORMAL,false,_T("Verdana"),wxFONTENCODING_SYSTEM); dc.SetFont(curFont); dc.SetPen(wxPen(wxColour(0,0,0))); // 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;iUpdate(); } } } } ////////////////////////////// // Get Syllable at position X 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; } ///////////////// // Set selection void AudioKaraoke::SetSelection(int start,int 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; } // 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++; } // Set box buttons box->SetKaraokeButtons(sels > 1,sels > 0); } ////////////////// // Join syllables void AudioKaraoke::Join() { // Variables bool gotOne = false; size_t syls = syllables.size(); KaraokeSyllable *curSyl; int first = 0; // Loop for (size_t i=0;iselected) { if (!gotOne) { gotOne = true; first = i; } else { syllables.at(i-1).length += curSyl->length; syllables.at(i-1).contents += curSyl->contents; syllables.erase(syllables.begin()+i); i--; syls--; } } } // Set selection curSyllable = first; // Update display->NeedCommit = true; display->Update(); Refresh(false); } /////////////////// // Split syllables void AudioKaraoke::Split() { // Variables bool hasSplit = false; // Loop size_t syls = syllables.size(); for (size_t i=0;i 0) { syls += split; i += split; hasSplit = true; } if (split == -1) break; } } // Update if (hasSplit) { display->NeedCommit = true; display->Update(); Refresh(false); } } //////////////////// // Split a syllable int AudioKaraoke::SplitSyl (int n) { // Get split text KaraokeSyllable *curSyl = &syllables.at(n); wxString result = wxGetTextFromUser(_("Enter pipes (\"|\") to split:"), _("Split syllable"), curSyl->contents); if (result.IsEmpty()) return -1; // Prepare parsing const int splits = result.Freq(_T('|')); const int totalCharLen = curSyl->contents.Length() + splits + 1; const int charRound = totalCharLen / 2; const int totalTimeLen = curSyl->length; int curpos = curSyl->position; int curlen = 0; wxStringTokenizer tkn(result,_T("|"),wxTOKEN_RET_EMPTY_ALL); bool isFirst = true; // Parse for (int curn=n;tkn.HasMoreTokens();curn++) { // Prepare syllable if (!isFirst) { KaraokeSyllable temp; syllables.insert(syllables.begin()+curn,temp); temp.selected = true; } curSyl = &syllables.at(curn); // Set text wxString token = tkn.GetNextToken(); curSyl->contents = token; // Set position int len = (totalTimeLen * (token.Length() + 1) + charRound) / totalCharLen; curlen += len; if (curlen > totalTimeLen) { len -= totalTimeLen - curlen; curlen = totalTimeLen; } curSyl->length = len; curSyl->position = curpos; curpos += len; // Done isFirst = false; } // Return splits return splits; } ////////////////////////////////// // Apply delta length to syllable bool AudioKaraoke::SyllableDelta(int n,int delta,int mode) { // Get syllable and next KaraokeSyllable *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->length; // Cap delta int minLen = 0; if (len + delta < minLen) delta = minLen-len; if (mode == 0 && nextSyl && (nextSyl->length - delta) < minLen) delta = nextSyl->length - minLen; // Apply if (delta != 0) { curSyl->length += delta; // Normal mode if (mode == 0 && nextSyl) { nextSyl->length -= delta; nextSyl->position += delta; } // Shift mode if (mode == 1) { for (int i=n+1;i