Audacity  2.2.2
Lyrics.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  Lyrics.cpp
6 
7  Dominic Mazzoni
8  Vaughan Johnson
9 
10 **********************************************************************/
11 
12 #include <math.h>
13 
14 #include <wx/defs.h>
15 #include <wx/dcmemory.h>
16 #include <wx/mimetype.h>
17 
18 #include "Lyrics.h"
19 #include "Internat.h"
20 #include "Project.h" // for GetActiveProject
21 #include "LabelTrack.h"
22 
23 
24 BEGIN_EVENT_TABLE(HighlightTextCtrl, wxTextCtrl)
25  EVT_MOUSE_EVENTS(HighlightTextCtrl::OnMouseEvent)
27 
29  wxWindowID id,
30  const wxString& value /*= ""*/,
31  const wxPoint& pos /*= wxDefaultPosition*/,
32  const wxSize& size /*= wxDefaultSize*/)
33 : wxTextCtrl(parent, id, // wxWindow* parent, wxWindowID id,
34  value, // const wxString& value = "",
35  pos, // const wxPoint& pos = wxDefaultPosition,
36  size, // const wxSize& size = wxDefaultSize,
37  wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH | wxTE_RICH2 | wxTE_AUTO_URL | wxTE_NOHIDESEL), //v | wxHSCROLL)
38  mLyricsPanel(parent)
39 {
40 }
41 
42 void HighlightTextCtrl::OnMouseEvent(wxMouseEvent& event)
43 {
44  if (event.ButtonUp())
45  {
46  long from, to;
47  this->GetSelection(&from, &to);
48 
49  int nCurSyl = mLyricsPanel->GetCurrentSyllableIndex();
50  int nNewSyl = mLyricsPanel->FindSyllable(from);
51  if (nNewSyl != nCurSyl)
52  {
53  Syllable* pCurSyl = mLyricsPanel->GetSyllable(nNewSyl);
55  pProj->SetSel0(pCurSyl->t);
56 
57  //v Should probably select to end as in AudacityProject::OnSelectCursorEnd,
58  // but better to generalize that in AudacityProject methods.
59  pProj->mViewInfo.selectedRegion.setT1(pCurSyl->t);
60  }
61  }
62 
63  event.Skip();
64 }
65 
66 
67 //v static const kHighlightTextCtrlID = 7654;
68 
69 BEGIN_EVENT_TABLE(LyricsPanel, wxPanelWrapper)
70  EVT_KEY_DOWN(LyricsPanel::OnKeyEvent)
71  EVT_PAINT(LyricsPanel::OnPaint)
72  EVT_SIZE(LyricsPanel::OnSize)
73 
74  //v Doesn't seem to be a way to capture a selection event in a read-only wxTextCtrl.
75  // EVT_COMMAND_LEFT_CLICK(kHighlightTextCtrlID, LyricsPanel::OnHighlightTextCtrl)
77 
79 
80 LyricsPanel::LyricsPanel(wxWindow* parent, wxWindowID id,
81  const wxPoint& pos /*= wxDefaultPosition*/,
82  const wxSize& size /*= wxDefaultSize*/):
83  wxPanelWrapper(parent, id, pos, size, wxWANTS_CHARS),
84  mWidth(size.x), mHeight(size.y)
85 {
86  mKaraokeHeight = mHeight;
87  mLyricsStyle = kBouncingBallLyrics; // default
88  mKaraokeFontSize = this->GetDefaultFontSize(); // Call only after mLyricsPanelStyle is set.
89 
90  this->SetBackgroundColour(*wxWHITE);
91 
92  mHighlightTextCtrl =
93  safenew HighlightTextCtrl(this, -1, // wxWindow* parent, wxWindowID id,
94  wxT(""), // const wxString& value = wxT(""),
95  wxPoint(0, 0), // const wxPoint& pos = wxDefaultPosition,
96  size); // const wxSize& size = wxDefaultSize
97  this->SetHighlightFont();
98  mHighlightTextCtrl->Show(mLyricsStyle == kHighlightLyrics); // test, in case we conditionalize the default, above
99 
100 
101  mT = 0.0;
102 
103  Clear();
104  Finish(0.0);
105 
106  #ifdef __WXMAC__
107  wxSizeEvent dummyEvent;
108  OnSize(dummyEvent);
109  #endif
110 }
111 
113 {
114 }
115 
116 #define I_FIRST_REAL_SYLLABLE 2
117 
119 {
120  mSyllables.clear();
121  mText = wxT("");
122 
123  // Add two dummy syllables at the beginning
124  mSyllables.push_back(Syllable());
125  mSyllables[0].t = -2.0;
126  mSyllables.push_back(Syllable());
127  mSyllables[1].t = -1.0;
128 
129  mHighlightTextCtrl->Clear();
130 }
131 
133 {
134  const size_t numLabels = pLT->GetNumLabels();
135  wxString highlightText;
136  for (size_t ii = 0; ii < numLabels; ++ii) {
137  const LabelStruct *const pLabel = pLT->GetLabel(ii);
138  Add(pLabel->getT0(), pLabel->title, highlightText);
139  }
140  mHighlightTextCtrl->AppendText(highlightText);
141 }
142 
143 void LyricsPanel::Add(double t, const wxString &syllable, wxString &highlightText)
144 {
145  int i = mSyllables.size();
146 
147  {
148  Syllable &prevSyllable = mSyllables[i - 1];
149 
150  if (prevSyllable.t == t) {
151  // We can't have two syllables with the same time, so append
152  // this to the end of the previous one if they're at the
153  // same time.
154  prevSyllable.text += syllable;
155  prevSyllable.textWithSpace += syllable;
156  prevSyllable.char1 += syllable.Length();
157  return;
158  }
159  }
160 
161  mSyllables.push_back(Syllable());
162  Syllable &thisSyllable = mSyllables[i];
163  thisSyllable.t = t;
164  thisSyllable.text = syllable;
165 
166  thisSyllable.char0 = mText.Length();
167 
168  // Put a space between syllables unless the previous one
169  // ended in a hyphen
170  if (i > 0 &&
171  // mSyllables[i-1].text.Length() > 0 &&
172  mSyllables[i - 1].text.Right(1) != wxT("-"))
173  thisSyllable.textWithSpace = wxT(" ") + syllable;
174  else
175  thisSyllable.textWithSpace = syllable;
176 
177  mText += thisSyllable.textWithSpace;
178  thisSyllable.char1 = mText.Length();
179 
180  int nTextLen = thisSyllable.textWithSpace.Length();
181  if ((nTextLen > 0) && (thisSyllable.textWithSpace.Right(1) == wxT("_")))
182  highlightText += (thisSyllable.textWithSpace.Left(nTextLen - 1) + wxT("\n"));
183  else
184  highlightText += thisSyllable.textWithSpace;
185 }
186 
187 void LyricsPanel::Finish(double finalT)
188 {
189  // Add 3 dummy syllables at the end
190  int i = mSyllables.size();
191  mSyllables.push_back(Syllable());
192  mSyllables[i].t = finalT + 1.0;
193  mSyllables.push_back(Syllable());
194  mSyllables[i+1].t = finalT + 2.0;
195  mSyllables.push_back(Syllable());
196  mSyllables[i+2].t = finalT + 3.0;
197 
198  // Mark measurements as invalid
199  mMeasurementsDone = false; // only for drawn text
200  mCurrentSyllable = 0;
201  mHighlightTextCtrl->ShowPosition(0);
202 }
203 
204 // Binary-search for the syllable syllable whose char0 <= startChar <= char1.
205 int LyricsPanel::FindSyllable(long startChar)
206 {
207  int i1, i2;
208 
209  i1 = 0;
210  i2 = mSyllables.size();
211  while (i2 > i1+1) {
212  int pmid = (i1+i2)/2;
213  if (mSyllables[pmid].char0 > startChar)
214  i2 = pmid;
215  else
216  i1 = pmid;
217  }
218 
219  if (i1 < 2)
220  i1 = 2;
221  if (i1 > (int)(mSyllables.size()) - 3)
222  i1 = mSyllables.size() - 3;
223 
224  return i1;
225 }
226 
227 void LyricsPanel::SetLyricsStyle(const LyricsStyle newLyricsStyle)
228 {
229  if (mLyricsStyle == newLyricsStyle)
230  return;
231 
232  mLyricsStyle = newLyricsStyle;
234 
235  wxSizeEvent ignore;
236  this->OnSize(ignore);
237 }
238 
239 
241 {
242  return (mLyricsStyle == kBouncingBallLyrics) ? 48 : 10;
243 }
244 
246 {
247  dc->SetFont(wxFont(mKaraokeFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
248 }
249 
250 void LyricsPanel::SetHighlightFont() // for kHighlightLyrics
251 {
252  wxFont newFont(mKaraokeFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
253  mHighlightTextCtrl->SetDefaultStyle(wxTextAttr(wxNullColour, wxNullColour, newFont));
254  mHighlightTextCtrl->SetStyle(0, mHighlightTextCtrl->GetLastPosition(),
255  wxTextAttr(wxNullColour, wxNullColour, newFont));
256 }
257 
258 void LyricsPanel::Measure(wxDC *dc) // only for drawn text
259 {
260  this->SetDrawnFont(dc);
261  int width = 0, height = 0;
262 
263  const int kIndent = 4;
264  int x = 2*kIndent;
265 
266  unsigned int i;
267  for(i = 0; i < mSyllables.size(); i++) {
268  if ((i < I_FIRST_REAL_SYLLABLE) || // Clear() starts the list with I_FIRST_REAL_SYLLABLE dummies.
269  (i >= mSyllables.size() - 3)) // Finish() ends with 3 dummies.
270  {
271  dc->GetTextExtent(wxT("DUMMY"), &width, &height); // Get the correct height even if we're at i=0.
272  width = 0;
273  }
274  else {
275  dc->GetTextExtent(mSyllables[i].textWithSpace, &width, &height);
276  }
277 
278  // Add some space between words; the space is normally small but
279  // when there's a long pause relative to the previous word, insert
280  // extra space.
281  int extraWidth;
282  if (i >= I_FIRST_REAL_SYLLABLE && i < mSyllables.size() - 2)
283  {
284  double deltaThis = mSyllables[i+1].t - mSyllables[i].t;
285  double deltaPrev = mSyllables[i].t - mSyllables[i-1].t;
286 
287  double ratio;
288  if (deltaPrev > 0.0)
289  ratio = deltaThis / deltaPrev;
290  else
291  ratio = deltaThis;
292 
293  if (ratio > 2.0)
294  extraWidth = 15 + (int)(15.0 * ratio);
295  else
296  extraWidth = 15;
297  }
298  else
299  extraWidth = 20;
300 
301  mSyllables[i].width = width + extraWidth;
302  mSyllables[i].leftX = x;
303  mSyllables[i].x = x + width/2;
304  x += mSyllables[i].width;
305  }
306 
307  mTextHeight = height;
308 
309  mMeasurementsDone = true;
310 }
311 
312 // Binary-search for the syllable with the largest time not greater than t
314 {
315  int i1, i2;
316 
317  i1 = 0;
318  i2 = mSyllables.size();
319  while (i2 > i1+1) {
320  int pmid = (i1+i2)/2;
321  if (mSyllables[pmid].t > t)
322  i2 = pmid;
323  else
324  i1 = pmid;
325  }
326 
327  if (i1 < 2)
328  i1 = 2;
329  if (i1 > (int)(mSyllables.size()) - 3)
330  i1 = mSyllables.size() - 3;
331 
332  return i1;
333 }
334 
335 // Bouncing Ball:
336 // Given the current time t, returns the x/y position of the scrolling
337 // karaoke display. For some syllable i, when t==mSyllables[i].t,
338 // it will return mSyllables[i].x for outX and 0 for outY.
339 // In-between words, outX is interpolated using smooth acceleration
340 // between the two neighboring words, and y is a positive number indicating
341 // the bouncing ball height
343  int *outX, double *outY)
344 {
345  *outX = 0;
346  *outY = 0;
347 
348  if (t < mSyllables[I_FIRST_REAL_SYLLABLE].t || t > mSyllables[mSyllables.size() - 3].t)
349  return;
350 
351  int i0, i1, i2, i3;
352  int x0, x1, x2, x3;
353  double t0, t1, t2, t3;
354  i1 = FindSyllable(t);
355  i2 = i1 + 1;
356 
357  // Because we've padded the syllables with two dummies at the beginning
358  // and end, we know that i0...i3 will always exist. Also, we've made
359  // sure that we don't ever have two of the same time, so t2>t1 strictly.
360  //
361  // t
362  // \/
363  // time: t0 t1 t2 t3
364  // pos: x0 x1 x2 x3
365  // index: i0 i1 i2 i3
366  // vel: vel1 vel2
367 
368  i0 = i1 - 1;
369  i3 = i2 + 1;
370 
371  x0 = mSyllables[i0].x;
372  x1 = mSyllables[i1].x;
373  x2 = mSyllables[i2].x;
374  x3 = mSyllables[i3].x;
375 
376  t0 = mSyllables[i0].t;
377  t1 = mSyllables[i1].t;
378  t2 = mSyllables[i2].t;
379  t3 = mSyllables[i3].t;
380 
381  double linear_vel0 = (x1 - x0) / (t1 - t0);
382  double linear_vel1 = (x2 - x1) / (t2 - t1);
383  double linear_vel2 = (x3 - x2) / (t3 - t2);
384 
385  // average velocities
386  double v1 = (linear_vel0 + linear_vel1) / 2;
387  double v2 = (linear_vel1 + linear_vel2) / 2;
388 
389  // Solve a cubic equation f(t) = at^3 + bt^2 + ct + d
390  // which gives the position x as a function of
391  // (t - t1), by constraining f(0), f'(0), f(t2-t1), f'(t2-t1)
392  double delta_t = t2 - t1;
393  double delta_x = x2 - x1;
394  v1 *= delta_t;
395  v2 *= delta_t;
396  double a = v1 + v2 - 2*delta_x;
397  double b = 3*delta_x - 2*v1 - v2;
398  double c = v1;
399  double d = x1;
400 
401  t = (t - t1) / (t2 - t1);
402  double xx = a*t*t*t + b*t*t + c*t + d;
403 
404  // Unfortunately sometimes our cubic goes backwards. This is a quick
405  // hack to stop that from happening.
406  if (xx < x1)
407  xx = x1;
408 
409  *outX = (int)xx;
410 
411  // The y position is a simple cosine curve; the max height is a
412  // function of the time.
413  double height = t2 - t1 > 4.0? 1.0: sqrt((t2-t1)/4.0);
414  *outY = height * sin(M_PI * t);
415 }
416 
417 void LyricsPanel::Update(double t)
418 {
419  if (t < 0.0)
420  {
421  // TrackPanel::OnTimer passes gAudioIO->GetStreamTime(), which is -DBL_MAX if !IsStreamActive().
422  // In that case, use the selection start time.
424  mT = pProj->GetSel0();
425  }
426  else
427  mT = t;
428 
430  {
431  wxRect karaokeRect(0, 0, mWidth, mKaraokeHeight);
432  this->Refresh(false, &karaokeRect);
433  }
434 
435  int i = FindSyllable(mT);
436  if (i == mCurrentSyllable)
437  return;
438 
439  mCurrentSyllable = i;
440 
442  {
443  mHighlightTextCtrl->SetSelection(mSyllables[i].char0, mSyllables[i].char1);
444 
445  //v No trail for now.
447  //if (i == I_FIRST_REAL_SYLLABLE)
448  // // Reset the trail to zero.
449  // mHighlightTextCtrl->SetStyle(0, mHighlightTextCtrl->GetLastPosition(), wxTextAttr(wxNullColour, *wxWHITE));
451  //mHighlightTextCtrl->SetStyle(mSyllables[i].char0, mSyllables[i].char1, wxTextAttr(wxNullColour, *wxLIGHT_GREY));
452 
453  //v Too much flicker: mHighlightTextCtrl->ShowPosition(mSyllables[i].char0);
454  }
455 }
456 
457 void LyricsPanel::OnKeyEvent(wxKeyEvent & event)
458 {
459  AudacityProject *project = GetActiveProject();
460  project->GetCommandManager()->FilterKeyEvent(project, event, true);
461  event.Skip();
462 }
463 
464 void LyricsPanel::OnPaint(wxPaintEvent & WXUNUSED(event))
465 {
466  wxPaintDC dc(this);
467  DoPaint(dc);
468 }
469 
470 void LyricsPanel::DoPaint(wxDC &dc)
471 {
472  if (!this->GetParent()->IsShown())
473  return;
474 
476  {
477  if (!mMeasurementsDone)
478  Measure(&dc);
479 
480  #ifdef __WXMAC__
481  // Mac OS X automatically double-buffers the screen for you,
482  // so our bitmap is unneccessary
483  HandlePaint(dc);
484  #else
485  wxBitmap bitmap(mWidth, mKaraokeHeight);
486  wxMemoryDC memDC;
487  memDC.SelectObject(bitmap);
488  HandlePaint(memDC);
489  dc.Blit(0, 0, mWidth, mKaraokeHeight, &memDC, 0, 0, wxCOPY, FALSE);
490  #endif
491  }
492  else // (mLyricsStyle == kHighlightLyrics)
493  {
494  //v causes flicker in ported version
495  // this->SetHighlightFont();
496  }
497 }
498 
499 void LyricsPanel::OnSize(wxSizeEvent & WXUNUSED(event))
500 {
501  GetClientSize(&mWidth, &mHeight);
502 
504 
506  (int)((float)(this->GetDefaultFontSize() * mHeight) / (float)LYRICS_DEFAULT_HEIGHT);
507  // Usually don't get the size window we want, usually less than
508  // LYRICS_DEFAULT_HEIGHT, so bump it a little.
509  mKaraokeFontSize += 2;
510 
512  {
513  mMeasurementsDone = false;
514  wxClientDC dc(this);
515  this->DoPaint(dc);
516  }
517  else // (mLyricsStyle == kHighlightLyrics)
518  {
520  this->SetHighlightFont();
521  }
522 
523  this->Refresh(false);
524 }
525 
526 //v Doesn't seem to be a way to capture a selection event in a read-only wxTextCtrl.
527 //void LyricsPanel::OnHighlightTextCtrl(wxCommandEvent & event)
528 //{
529 // long from, to;
530 //
531 // mHighlightTextCtrl->GetSelection(&from, &to);
532 // // TODO: Find the start time of the corresponding syllable and set playback to start there.
533 //}
534 
536 {
537  wxASSERT(mLyricsStyle == kBouncingBallLyrics);
538  dc.SetBrush(*wxWHITE_BRUSH);
539  dc.DrawRectangle(0, 0, mWidth, mKaraokeHeight);
540 
541  this->HandlePaint_BouncingBall(dc);
542 }
543 
545 {
546  int ctr = mWidth / 2;
547  int x;
548  double y;
549  GetKaraokePosition(mT, &x, &y);
550 
551  dc.SetTextForeground(wxColour(238, 0, 102));
552  bool changedColor = false;
553 
554  SetDrawnFont(&dc);
555  unsigned int i;
556  wxCoord yTextTop = mKaraokeHeight - mTextHeight - 4;
557  for(i = 0; i < mSyllables.size(); i++) {
558  if (mSyllables[i].x + mSyllables[i].width < (x - ctr))
559  continue;
560  if (mSyllables[i].x > x + ctr)
561  continue;
562 
563  if (!changedColor && mSyllables[i].x >= x) {
564  dc.SetTextForeground(*wxBLACK);
565  changedColor = true;
566  }
567 
568  wxString text = mSyllables[i].text;
569  if (text.Length() > 0 && text.Right(1) == wxT("_")) {
570  text = text.Left(text.Length() - 1);
571  }
572 
573  dc.DrawText(text,
574  mSyllables[i].leftX + ctr - x,
575  yTextTop);
576  }
577 
578  int ballRadius = (int)(mTextHeight / 8.0);
579  int bounceTop = ballRadius * 2;
580  int bounceHeight = yTextTop - bounceTop;
581  int yi = (int)(yTextTop - 4 - (y * bounceHeight));
582 
583  if (mT >= 0.0) {
584  wxRect ball(ctr - ballRadius, yi - ballRadius, 2 * ballRadius, 2 * ballRadius);
585  dc.SetBrush(wxBrush(wxColour(238, 0, 102), wxSOLID));
586  dc.DrawEllipse(ball);
587  }
588 }
589 
std::vector< Syllable > mSyllables
Definition: Lyrics.h:143
int char0
Definition: Lyrics.h:37
void SetLyricsStyle(const LyricsStyle newLyricsStyle)
Definition: Lyrics.cpp:227
int mCurrentSyllable
Definition: Lyrics.h:142
Syllable * GetSyllable(int nSyl)
Definition: Lyrics.h:93
void HandlePaint_BouncingBall(wxDC &dc)
Definition: Lyrics.cpp:544
void Add(double t, const wxString &syllable, wxString &highlightText)
Definition: Lyrics.cpp:143
void OnMouseEvent(wxMouseEvent &evt)
Definition: Lyrics.cpp:42
double GetSel0() const
Definition: Project.h:186
SelectedRegion selectedRegion
Definition: ViewInfo.h:160
bool FilterKeyEvent(AudacityProject *project, const wxKeyEvent &evt, bool permit=false)
void SetSel0(double)
Definition: Project.cpp:1373
void OnPaint(wxPaintEvent &evt)
Definition: Lyrics.cpp:464
virtual ~LyricsPanel()
Definition: Lyrics.cpp:112
int char1
Definition: Lyrics.h:38
void HandlePaint(wxDC &dc)
Definition: Lyrics.cpp:535
#define I_FIRST_REAL_SYLLABLE
Definition: Lyrics.cpp:116
wxString title
Definition: LabelTrack.h:92
int mHeight
Definition: Lyrics.h:132
LyricsPanel * mLyricsPanel
Definition: Lyrics.h:60
int GetCurrentSyllableIndex()
Definition: Lyrics.h:92
void SetHighlightFont()
Definition: Lyrics.cpp:250
double t
Definition: Lyrics.h:34
CommandManager * GetCommandManager()
Definition: Project.h:328
int GetNumLabels() const
double mT
Definition: Lyrics.h:140
void Finish(double finalT)
Definition: Lyrics.cpp:187
#define safenew
Definition: Audacity.h:223
int FindSyllable(long startChar)
Definition: Lyrics.cpp:205
A LabelTrack is a Track that holds labels (LabelStruct).
Definition: LabelTrack.h:113
unsigned int GetDefaultFontSize() const
Definition: Lyrics.cpp:240
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:158
A LabelStruct holds information for ONE label in a LabelTrack.
Definition: LabelTrack.h:44
int mKaraokeHeight
Definition: Lyrics.h:134
void OnSize(wxSizeEvent &evt)
Definition: Lyrics.cpp:499
void SetDrawnFont(wxDC *dc)
Definition: Lyrics.cpp:245
void DoPaint(wxDC &dc)
Definition: Lyrics.cpp:470
bool setT1(double t, bool maySwap=true)
const LabelStruct * GetLabel(int index) const
int mTextHeight
Definition: Lyrics.h:146
void Measure(wxDC *dc)
Definition: Lyrics.cpp:258
ViewInfo mViewInfo
Definition: Project.h:542
wxString text
Definition: Lyrics.h:35
int mWidth
Definition: Lyrics.h:131
LyricsPanel is a panel that paints the bouncing ball and the lyrics text.
Definition: Lyrics.h:71
IMPLEMENT_CLASS(ControlToolBar, ToolBar)
#define LYRICS_DEFAULT_HEIGHT
Definition: Lyrics.h:25
void Update(double t)
Definition: Lyrics.cpp:417
bool mMeasurementsDone
Definition: Lyrics.h:147
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:300
#define M_PI
Definition: Distortion.cpp:28
wxString textWithSpace
Definition: Lyrics.h:36
unsigned int mKaraokeFontSize
Definition: Lyrics.h:135
wxString mText
Definition: Lyrics.h:144
END_EVENT_TABLE()
void OnKeyEvent(wxKeyEvent &event)
Definition: Lyrics.cpp:457
double getT0() const
Definition: LabelTrack.h:61
LyricsStyle mLyricsStyle
Definition: Lyrics.h:137
void Clear()
Definition: Lyrics.cpp:118
void GetKaraokePosition(double t, int *outX, double *outY)
Definition: Lyrics.cpp:342
void AddLabels(const LabelTrack *pLT)
Definition: Lyrics.cpp:132
HighlightTextCtrl * mHighlightTextCtrl
Definition: Lyrics.h:138