www.pudn.com > TMIDIPlayer_w_MidiSheet.rar > ReadMIDILyrics.pas, change:2013-02-11,size:13197b


// Unit ReadMIDILyrics 
// 
// This unit takes charge of formatting synchronized lyrics data from raw data of MIDI file. 
// 
// Author : Silhwan Hyun   (e-mail addr : hyunsh@hanafos.com) 
// 
// 
// Contibutors 
//   Emil Weiss : Test & Advice for update/debug 
// 
// 
// Ver 1.0.1                      07 Jun 2012 
//   - Modifications to read the lyrics recorded with non-standard method of some Karaoke files. 
// 
// Ver 1.0                        03 Jun 2012 
//   - Initial release 
// 
 
unit ReadMIDILyrics; 
 
interface 
 
{$DEFINE MULTI_LANGUAGE} 
 
uses Windows, Messages, Classes, SysUtils, MIDIFile2{$IFDEF MULTI_LANGUAGE}, gnugettext{$ENDIF}; 
 
// {$DEFINE USE_KOR_LANG} 
 
const 
  MaxSyllablesPerLine = 32;    // 한 라인당 최대 음절 수 
 
// 전주나 간주로 판단하는 시간 간격 
  PreInterval = 3000;  // 첫 동기가사 시작시간이 PreInterval 보다 크면 Prelude를 삽입한다. 
  MidInterval = 5000;  // 동기가사 휴지시간이 MidInterval 보다 크면 Interlude를 삽입한다. 
 
// 전주나 간주 부분에 대해서 자동으로 삽입할 문자열 
// {$IFDEF USE_KOR_LANG} 
//  Prelude = '< 전  주 >'; 
//  Interlude = '< 간  주 >'; 
// {$ELSE} 
//  Prelude = '< Prelude >'; 
//  Interlude = '< Interlude >'; 
// {$ENDIF} 
 
type 
  TMIDISyncLyric = record 
     TimePos : integer;    // position in mili second 
   //  Position: DWORD;      // position in ticks 
     Lyric: string; 
  end; 
 
  TMIDISyncLyrics = array of TMIDISyncLyric; 
 
  TSyllable = record 
      StartTime : DWORD;          // in ms 
      Duration : DWORD;           // in ms 
      WordWidth : Single; 
      CharacterStartNo : DWORD; 
      aSyllable : UnicodeString; 
   end; 
 
   TLyricsLine = record 
      StartTime : DWORD;          // in ms 
      Duration : DWORD;           // in ms 
      NumSyllable : integer; 
      LineWidth : Single; 
      Syllables : array of TSyllable; 
   end; 
 
   TSyncLyrics = record 
      NumLine : integer; 
      Lines : array of TLyricsLine; 
   end; 
 
  function ReadSyncLyrics(MidiFile: TMidiFile2; SyncLyrics: TRawLyrics; DiscardTimeZeroLyrics: boolean) : TSyncLyrics; 
 
implementation 
 
 
var 
  Prelude : string; 
  Interlude : string; 
 
// 문자열이 공백문자로만 이루어져 있을 경우 true 값을 얻는다. 
function AllSpaces(s : string): boolean; 
var 
  I : integer; 
begin 
  result := true; 
 
  for I := 1 to Length(s) do 
    if s[I] <> ' ' then 
    begin 
      result := false; 
      break; 
    end; 
 
end; 
 
// 문자열 선두의 공백문자수를 얻는다. 
function LeftSpaceLen(s: string): integer; 
var 
  I : integer; 
begin 
  result := 0; 
 
  for I := 1 to Length(s) do 
    if s[I] = ' ' then 
      result := I 
    else 
      break; 
end; 
 
// 문자열 후미의 공백문자수를 얻는다. 
function RightSpaceLen(s: string): integer; 
var 
  I : integer; 
begin 
  result := 0; 
 
  for I := Length(s) downto 1 do 
    if s[I] = ' ' then 
      inc(result) 
    else 
      break; 
 
end; 
 
// 가사 중간의 스페이스를 독립된 음절로 분리한다. 
// 이 작업을 하는 이유는 동기가사 표시창에서 시간 경과에 따라 동기가사의 색상을 바꾸어 나갈 때 
// 가사라인의 중간에 있는 스페이스는 시간적으로 소요시간이 0인 부분으로 처리하기 위해서이다. 
procedure SeperateSpace(var SyncLyrics: TSyncLyrics); 
var 
  K, N, L, I : integer; 
  StartTime : DWORD; 
  Diff : integer; 
  AllSameTime : Boolean; 
  tmpLyricsLine : TLyricsLine; 
  s : string; 
begin 
  for K := 0 to (SyncLyrics.NumLine - 1) do 
  begin 
    if SyncLyrics.Lines[K].NumSyllable = 1 then  // NumSyllable이 1인 경우는 줄단위 처리 
      Continue; 
 
    StartTime := SyncLyrics.Lines[K].Syllables[0].StartTime; 
    AllSameTime := true; 
    for I := 1 to (SyncLyrics.Lines[K].NumSyllable - 1) do 
      if SyncLyrics.Lines[K].Syllables[I].StartTime <> StartTime then 
      begin 
        AllSameTime := false; 
        break; 
      end; 
    if AllSameTime then 
      Continue; 
 
    tmpLyricsLine := SyncLyrics.Lines[K]; 
    for N := 0 to (SyncLyrics.Lines[K].NumSyllable - 1) do 
    begin 
      s := SyncLyrics.Lines[K].Syllables[N].aSyllable; 
      if AllSpaces(s) then 
        Continue; 
      if (s[1] = ' ') then 
      begin 
        L := LeftSpaceLen(s); 
        SetLength(tmpLyricsLine.Syllables, High(tmpLyricsLine.Syllables) + 2); 
        Diff := High(tmpLyricsLine.Syllables) + 1 - SyncLyrics.Lines[K].NumSyllable; 
        for I := High(tmpLyricsLine.Syllables) downto (N + Diff + 1) do 
          tmpLyricsLine.Syllables[I] := tmpLyricsLine.Syllables[I-1]; 
        tmpLyricsLine.Syllables[N+Diff-1].aSyllable := copy(s, 1, L); 
        s := copy(s, L+1, Length(s) - L); 
        tmpLyricsLine.Syllables[N+Diff].aSyllable := s; 
        tmpLyricsLine.Syllables[N+Diff].StartTime := tmpLyricsLine.Syllables[N+Diff-1].StartTime; 
      end; 
      if (s[Length(s)] = ' ') then 
      begin 
        L := RightSpaceLen(s); 
        SetLength(tmpLyricsLine.Syllables, High(tmpLyricsLine.Syllables) + 2); 
        Diff := High(tmpLyricsLine.Syllables) + 1 - SyncLyrics.Lines[K].NumSyllable; 
        for I := High(tmpLyricsLine.Syllables) downto (N + Diff + 1) do 
          tmpLyricsLine.Syllables[I] := tmpLyricsLine.Syllables[I-1]; 
        tmpLyricsLine.Syllables[N+Diff-1].aSyllable := copy(s, 1, Length(s) - L); 
        tmpLyricsLine.Syllables[N+Diff].aSyllable := copy(s, Length(s) - L + 1, L); 
        tmpLyricsLine.Syllables[N+Diff].StartTime := tmpLyricsLine.Syllables[N+Diff-1].StartTime; 
      end; 
 
    end; 
 
    tmpLyricsLine.NumSyllable := High(tmpLyricsLine.Syllables) + 1; 
  // 공백문자(열)의 시작시간은 항상 앞 음절의 시작시간으로 맞춘다. 
    for N := 1 to (tmpLyricsLine.NumSyllable - 2) do 
      if AllSpaces(tmpLyricsLine.Syllables[N].aSyllable) then 
        tmpLyricsLine.Syllables[N].StartTime := tmpLyricsLine.Syllables[N-1].StartTime; 
    SyncLyrics.Lines[K] := tmpLyricsLine; 
  end; 
end; 
 
// 전주나 간주 부분에 대해서 자동으로 표시할 내용을 삽입해 준다. 
procedure InsertComment(var SyncLyrics: TSyncLyrics); 
var 
  I, J : integer; 
  M, N, K : integer; 
  T1 : DWORD; 
begin 
// 시작시점에 "< 전 주 >" 추가 
  if SyncLyrics.NumLine >= 2 then 
    if SyncLyrics.Lines[0].StartTime > PreInterval then  // 가사 시작시점이 PreInterval 이후일 경우 
    begin 
      SetLength(SyncLyrics.Lines, SyncLyrics.NumLine + 1); 
      inc(SyncLyrics.NumLine, 1); 
      for I := (SyncLyrics.NumLine - 1) downto 1 do 
        SyncLyrics.Lines[i] := SyncLyrics.Lines[i-1]; 
      SyncLyrics.Lines[0].StartTime := 0; 
      SyncLyrics.Lines[0].NumSyllable := 1; 
      SetLength(SyncLyrics.Lines[0].Syllables, 1); 
      SyncLyrics.Lines[0].Syllables[0].StartTime := 0; 
      SyncLyrics.Lines[0].Syllables[0].aSyllable := Prelude; 
    end; 
 
  if SyncLyrics.NumLine < 5 then 
    exit; 
 
 // 중간 휴지 부분에 "<간 주 >" 삽입 
  K := 3;    // 간주는 최소한 2 줄 연주 이후에  나타나는 것으로 본다. 
  repeat 
    M := (SyncLyrics.NumLine - 1); 
    for I := K to M do 
    begin 
      N := SyncLyrics.Lines[I-1].NumSyllable - 1; 
      if (SyncLyrics.Lines[I].StartTime - SyncLyrics.Lines[I-1].Syllables[N].StartTime) > 
         MidInterval then     // MidInterval 이상 가사가 없는 상태이면 간주 부분으로 판단한다. 
      begin 
        SetLength(SyncLyrics.Lines, SyncLyrics.NumLine + 1); 
        inc(SyncLyrics.NumLine, 1); 
        for J := (SyncLyrics.NumLine - 1) downto (I+1) do 
          SyncLyrics.Lines[J] := SyncLyrics.Lines[J-1]; 
        T1 := SyncLyrics.Lines[I-1].StartTime 
                      + (SyncLyrics.Lines[I-1].StartTime - SyncLyrics.Lines[I-2].StartTime); 
        if T1 > SyncLyrics.Lines[I-1].Syllables[N].StartTime then 
          SyncLyrics.Lines[I].StartTime := T1 
        else 
          SyncLyrics.Lines[I].StartTime := SyncLyrics.Lines[I-1].Syllables[N].StartTime + 1000; 
        SyncLyrics.Lines[I].NumSyllable := 1; 
        SetLength(SyncLyrics.Lines[I].Syllables, 1); 
        SyncLyrics.Lines[I].Syllables[0].StartTime := SyncLyrics.Lines[I].StartTime; 
        SyncLyrics.Lines[I].Syllables[0].aSyllable := Interlude; 
        break; 
      end; 
    end; 
 
    K := I + 2; 
  until I >= M; 
end; 
 
function ReadSyncLyrics(MidiFile: TMidiFile2; SyncLyrics: TRawLyrics; DiscardTimeZeroLyrics: boolean) : TSyncLyrics; 
var 
  N, J, K: integer; 
  SyllableLen: integer; 
  MIDISyncLyrics: TMIDISyncLyrics; 
  SubNum: integer; 
 
begin 
  Result.NumLine := 0; 
  SubNum := 0; 
 
// 미디파일의 동기가사는 Carriage return code(#$0D)가 행(라인) 구분자이다. 
// Carriage return code(#$0D)는  Line feed code(#$0A)로 변환하고, Carriage return code만 
// 있는 Syllable이면서 가사 표시시점이 이전 Syllable과 동일할 경우는 이전 Syllable과 합친다. 
  if High(SyncLyrics) <> - 1 then 
  begin 
    SetLength(MIDISyncLyrics, High(SyncLyrics) + 1); 
    for N := 0 to High(SyncLyrics) do 
    begin 
 
    // ** Added at 2012-06-07 
      if SyncLyrics[N].Position = 0 then 
        if DiscardTimeZeroLyrics then 
        begin 
          inc(SubNum); 
          Continue; 
        end; 
 
      MIDISyncLyrics[N-SubNum].TimePos := MIDIFile.Tick2TimePos(SyncLyrics[N].Position); 
    //  MIDISyncLyrics[N-SubNum].Position := SyncLyrics[N].Position; 
 
    // Carriage return code(#$0D)는 Line feed code(#$0A)로 바꾸어 준다. 
      if Length(SyncLyrics[N].Lyric) = 0 then 
        MIDISyncLyrics[N-SubNum].Lyric := ' ' 
      else if (SyncLyrics[N].Lyric[Length(SyncLyrics[N].Lyric)] = chr(13)) then 
      begin 
        if Length(SyncLyrics[N].Lyric) = 1 then 
        begin 
           if N > 0 then 
             if MIDISyncLyrics[N-SubNum].TimePos = MIDISyncLyrics[N-SubNum-1].TimePos then 
             begin 
           // 가사 표시시점이 이전 Syllable과 동일할 경우는 이전 Syllable과 합친다. 
           // 이 경우 MIDISyncLyrics의 유효한 배열 갯수는 하나 줄어들므로 줄어든 갯수를 
           // 나타내는 변수 SubNum를 +1 증가시킨다. 
               MIDISyncLyrics[N-SubNum-1].Lyric := MIDISyncLyrics[N-SubNum-1].Lyric + chr(10); 
               inc(SubNum); 
             end else 
               MIDISyncLyrics[N-SubNum].Lyric := ' ' + chr(10) 
           else 
             MIDISyncLyrics[N-SubNum].Lyric := ' ' + chr(10); 
        end else 
          MIDISyncLyrics[N-SubNum].Lyric := copy(SyncLyrics[N].Lyric, 1, Length(SyncLyrics[N].Lyric) - 1) + chr(10) 
      end 
    // ** Soft Karaoke files use '\' for clear screen, '/' for new line, which are pre-converted 
    // **  to chr(13) in the MIDIFile2.pas unit 
      else if (SyncLyrics[N].Lyric[1] = chr(13)) {or (SyncLyrics[N].Lyric[1] = '\') or (SyncLyrics[N].Lyric[1] = '/')} then 
      begin 
        if Length(SyncLyrics[N].Lyric) = 1 then 
        begin 
           if N > 0 then 
             if MIDISyncLyrics[N-SubNum].TimePos = MIDISyncLyrics[N-SubNum-1].TimePos then 
             begin 
           // 가사 표시시점이 이전 Syllable과 동일할 경우는 이전 Syllable과 합친다. 
           // 이 경우 MIDISyncLyrics의 유효한 배열 갯수는 하나 줄어들므로 줄어든 갯수를 
           // 나타내는 변수 SubNum를 +1 증가시킨다. 
               MIDISyncLyrics[N-SubNum-1].Lyric := MIDISyncLyrics[N-SubNum-1].Lyric + chr(10); 
               inc(SubNum); 
             end else 
               MIDISyncLyrics[N-SubNum].Lyric := ' ' + chr(10) 
           else 
             MIDISyncLyrics[N-SubNum].Lyric := ' ' + chr(10); 
        end else 
          if (N - SubNum) > 0 then    // ** Changed at 2012-06-07 
          begin 
            MIDISyncLyrics[N-SubNum - 1].Lyric := MIDISyncLyrics[N-SubNum - 1].Lyric + chr(10); 
            MIDISyncLyrics[N-SubNum].Lyric := copy(SyncLyrics[N].Lyric, 2, Length(SyncLyrics[N].Lyric) - 1); 
          end else 
            MIDISyncLyrics[N-SubNum].Lyric := copy(SyncLyrics[N].Lyric, 2, Length(SyncLyrics[N].Lyric) - 1); 
      end else 
        MIDISyncLyrics[N-SubNum].Lyric := SyncLyrics[N].Lyric; 
    end; 
 
  end else 
    exit; 
 
  if SubNum > 0 then 
    SetLength(MIDISyncLyrics, High(SyncLyrics) + 1 - SubNum); 
  J := 0;  // Syllable counter 
  K := 0;  // Line Counter 
  SetLength(Result.Lines, 1); 
  for N := 0 to High(MIDISyncLyrics) do 
  begin 
    SetLength(Result.Lines[k].Syllables, J+1); 
    if J = 0 then  // Is the first syllable ? 
      Result.Lines[k].StartTime := MIDISyncLyrics[N].TimePos; 
 
    Result.Lines[k].Syllables[J].StartTime := MIDISyncLyrics[N].TimePos; 
    Result.Lines[k].Syllables[J].aSyllable := MIDISyncLyrics[N].Lyric; 
    inc(J); 
 
    SyllableLen := length(MIDISyncLyrics[N].Lyric); 
    if (MIDISyncLyrics[N].Lyric[SyllableLen] = chr(10)) or (J = MaxSyllablesPerLine) then 
    begin 
      Result.Lines[k].NumSyllable := J; 
      if N <> High(MIDISyncLyrics) then   // not last data ? 
      begin 
        inc(K); 
        SetLength(Result.Lines, K+1); 
        J := 0; 
      end; 
    end; 
 
  // 마지막 Sync Lyricsa 음절인 경우에 대한 처리 
    if N = High(MIDISyncLyrics) then 
      if (MIDISyncLyrics[N].Lyric[SyllableLen] <> chr(10)) then 
        Result.Lines[k].NumSyllable := J; 
  end; 
 
 Result.NumLine := K + 1; 
 
 SeperateSpace(Result); 
 InsertComment(Result); 
end; 
 
initialization 
{$IFDEF MULTI_LANGUAGE} 
  Prelude := _('< Prelude >'); 
  Interlude := _('< Interlude >'); 
{$ELSE} 
  Prelude := '< Prelude >'; 
  Interlude := '< Interlude >'; 
{$ENDIF} 
 
end.