aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--unicode/src/screens/UScreenEditConvert.pas454
1 files changed, 289 insertions, 165 deletions
diff --git a/unicode/src/screens/UScreenEditConvert.pas b/unicode/src/screens/UScreenEditConvert.pas
index 2705a5b7..b4417cc7 100644
--- a/unicode/src/screens/UScreenEditConvert.pas
+++ b/unicode/src/screens/UScreenEditConvert.pas
@@ -33,6 +33,19 @@ unit UScreenEditConvert;
* http://www.midi.org/techspecs/rp26.php
* MIDI File Format
* http://www.sonicspot.com/guide/midifiles.html
+ * KMIDI File Format
+ * http://gnese.free.fr/Projects/KaraokeTime/Fichiers/karfaq.html
+ * http://journals.rpungin.fotki.com/karaoke/category/midi
+ *
+ * There are two widely spread karaoke formats:
+ * - KMIDI (.kar), an inofficial midi extension by Tune 1000
+ * - Standard Midi files with lyric meta-tags (SMF with lyrics, .mid).
+ *
+ * KMIDI uses two tracks, the first just contains a header (mostly track 2) and
+ * the second the lyrics (track 3). It uses text meta tags for the lyrics.
+ * SMF uses just one track (normally track 1) and uses lyric meta tags for storage.
+ *
+ * Most files are in the KMIDI format. Some Midi files contain both lyric types.
*}
interface
@@ -67,14 +80,16 @@ type
Len: real;
Data1: integer;
Data2: integer;
- Str: AnsiString;
+ Str: UTF8String; // normally ASCII
end;
+ TLyricType = (ltKMIDI, ltSMFLyric);
+
TTrack = record
Note: array of TMidiNote;
- Name: AnsiString;
- Status: set of (tsNotes, tsLyrics);
- LyricType: set of (ltKMIDI, ltSMFLyric);
+ Name: UTF8String; // normally ASCII
+ Status: set of (tsNotes, tsLyrics); //< track contains notes, lyrics or both
+ LyricType: set of TLyricType;
NoteType: (ntNone, ntAvail);
end;
@@ -82,7 +97,7 @@ type
Start: integer;
Len: integer;
Tone: integer;
- Lyric: AnsiString;
+ Lyric: UTF8String;
NewSentence: boolean;
end;
@@ -96,23 +111,19 @@ type
ColB: array[0..100] of real;
Len: real;
SelTrack: integer; // index of selected track
- //FileName: IPath;
+ fFileName: IPath;
{$IFDEF UseMIDIPort}
MidiFile: TMidiFile;
- MidiTrack: TMidiTrack;
- MidiEvent: PMidiEvent;
MidiOut: TMidiOutput;
{$ENDIF}
- Song: TSong;
- Lines: TLines;
BPM: real;
Ticks: real;
Note: array of TNote;
- procedure AddLyric(Start: integer; Text: AnsiString);
- procedure Extract;
+ procedure AddLyric(Start: integer; LyricType: TLyricType; Text: UTF8String);
+ procedure Extract(out Song: TSong; out Lines: TLines);
{$IFDEF UseMIDIPort}
procedure MidiFile1MidiEvent(event: PMidiEvent);
@@ -128,9 +139,6 @@ type
procedure OnHide; override;
end;
-var
- ConversionFileName: IPath;
-
implementation
uses
@@ -144,6 +152,7 @@ uses
UMain,
UPathUtils,
USkins,
+ ULanguage,
UTextEncoding,
UUnicodeUtils;
@@ -162,9 +171,14 @@ const
MIDI_META_LYRICS = $5;
function TScreenEditConvert.ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown: boolean): boolean;
+{$IFDEF UseMIDIPort}
var
SResult: TSaveSongResult;
Playing: boolean;
+ MidiTrack: TMidiTrack;
+ Song: TSong;
+ Lines: TLines;
+{$ENDIF}
begin
Result := true;
if (PressedDown) then
@@ -184,7 +198,8 @@ begin
SDLK_BACKSPACE :
begin
{$IFDEF UseMIDIPort}
- MidiFile.StopPlaying;
+ if (MidiFile <> nil) then
+ MidiFile.StopPlaying;
{$ENDIF}
AudioPlayback.PlaySound(SoundLib.Back);
FadeTo(@ScreenEdit);
@@ -195,82 +210,96 @@ begin
if Interaction = 0 then
begin
AudioPlayback.PlaySound(SoundLib.Start);
+ ScreenOpen.Filename := GamePath.Append('file.mid');
ScreenOpen.BackScreen := @ScreenEditConvert;
FadeTo(@ScreenOpen);
- end;
-
- if Interaction = 1 then
+ end
+ else if Interaction = 1 then
begin
{$IFDEF UseMIDIPort}
- MidiFile.OnMidiEvent := MidiFile1MidiEvent;
- //MidiFile.GoToTime(MidiFile.GetTrackLength div 2);
- MidiFile.StartPlaying;
+ if (MidiFile <> nil) then
+ begin
+ MidiFile.OnMidiEvent := MidiFile1MidiEvent;
+ //MidiFile.GoToTime(MidiFile.GetTrackLength div 2);
+ MidiFile.StartPlaying;
+ end;
{$ENDIF}
- end;
-
- if Interaction = 2 then
+ end
+ else if Interaction = 2 then
begin
{$IFDEF UseMIDIPort}
- MidiFile.OnMidiEvent := nil;
- MidiFile.StartPlaying;
+ if (MidiFile <> nil) then
+ begin
+ MidiFile.OnMidiEvent := nil;
+ MidiFile.StartPlaying;
+ end;
{$ENDIF}
- end;
-
- if Interaction = 3 then
+ end
+ else if Interaction = 3 then
begin
+ {$IFDEF UseMIDIPort}
if CountSelectedTracks > 0 then
begin
- Extract;
- SResult := SaveSong(Song, Lines, ConversionFileName.SetExtension('.txt'),
+ Extract(Song, Lines);
+ SResult := SaveSong(Song, Lines, fFileName.SetExtension('.txt'),
false);
- if (SResult <> ssrOK) then
- begin
- ScreenPopupError.ShowPopup('Could not save file');
- end;
+ FreeAndNil(Song);
+ if (SResult = ssrOK) then
+ ScreenPopupInfo.ShowPopup(Language.Translate('INFO_FILE_SAVED'))
+ else
+ ScreenPopupError.ShowPopup(Language.Translate('ERROR_SAVE_FILE_FAILED'));
+ end
+ else
+ begin
+ ScreenPopupError.ShowPopup(Language.Translate('ERROR_EDITOR_NO_TRACK_SELECTED'));
end;
+ {$ENDIF}
end;
end;
SDLK_SPACE:
begin
- if (Tracks[SelTrack].NoteType = ntAvail) and
- (Tracks[SelTrack].LyricType <> []) then
- begin
- if (Tracks[SelTrack].Status = []) then
- Tracks[SelTrack].Status := [tsNotes]
- else if (Tracks[SelTrack].Status = [tsNotes]) then
- Tracks[SelTrack].Status := [tsLyrics]
- else if (Tracks[SelTrack].Status = [tsLyrics]) then
- Tracks[SelTrack].Status := [tsNotes, tsLyrics]
- else if (Tracks[SelTrack].Status = [tsNotes, tsLyrics]) then
- Tracks[SelTrack].Status := [];
- end
- else if (Tracks[SelTrack].NoteType = ntAvail) then
- begin
- if (Tracks[SelTrack].Status = []) then
- Tracks[SelTrack].Status := [tsNotes]
- else
- Tracks[SelTrack].Status := [];
- end
- else if (Tracks[SelTrack].LyricType <> []) then
+ {$IFDEF UseMIDIPort}
+ if (MidiFile <> nil) then
begin
- if (Tracks[SelTrack].Status = []) then
- Tracks[SelTrack].Status := [tsLyrics]
+ if (Tracks[SelTrack].NoteType = ntAvail) and
+ (Tracks[SelTrack].LyricType <> []) then
+ begin
+ if (Tracks[SelTrack].Status = []) then
+ Tracks[SelTrack].Status := [tsNotes]
+ else if (Tracks[SelTrack].Status = [tsNotes]) then
+ Tracks[SelTrack].Status := [tsLyrics]
+ else if (Tracks[SelTrack].Status = [tsLyrics]) then
+ Tracks[SelTrack].Status := [tsNotes, tsLyrics]
+ else if (Tracks[SelTrack].Status = [tsNotes, tsLyrics]) then
+ Tracks[SelTrack].Status := [];
+ end
+ else if (Tracks[SelTrack].NoteType = ntAvail) then
+ begin
+ if (Tracks[SelTrack].Status = []) then
+ Tracks[SelTrack].Status := [tsNotes]
+ else
+ Tracks[SelTrack].Status := [];
+ end
+ else if (Tracks[SelTrack].LyricType <> []) then
+ begin
+ if (Tracks[SelTrack].Status = []) then
+ Tracks[SelTrack].Status := [tsLyrics]
+ else
+ Tracks[SelTrack].Status := [];
+ end;
+
+ Playing := (MidiFile.GetCurrentTime > 0);
+ MidiFile.StopPlaying();
+ MidiTrack := MidiFile.GetTrack(SelTrack);
+ if tsNotes in Tracks[SelTrack].Status then
+ MidiTrack.OnMidiEvent := MidiFile1MidiEvent
else
- Tracks[SelTrack].Status := [];
+ MidiTrack.OnMidiEvent := nil;
+ if (Playing) then
+ MidiFile.ContinuePlaying();
end;
-
- {$IFDEF UseMIDIPort}
- Playing := (MidiFile.GetCurrentTime > 0);
- MidiFile.StopPlaying();
- MidiTrack := MidiFile.GetTrack(SelTrack);
- if tsNotes in Tracks[SelTrack].Status then
- MidiTrack.OnMidiEvent := MidiFile1MidiEvent
- else
- MidiTrack.OnMidiEvent := nil;
- if (Playing) then
- MidiFile.ContinuePlaying();
{$ENDIF}
end;
@@ -300,38 +329,62 @@ begin
end;
end;
-procedure TScreenEditConvert.AddLyric(Start: integer; Text: AnsiString);
+procedure TScreenEditConvert.AddLyric(Start: integer; LyricType: TLyricType; Text: UTF8String);
var
N: integer;
begin
- for N := 0 to High(Note) do
+ // find corresponding note
+ N := 0;
+ while (N <= High(Note)) do
begin
if Note[N].Start = Start then
- begin
- // Line Feed -> end of paragraph
- if Copy(Text, 1, 1) = #$0A then
- Delete(Text, 1, 1);
- // Carriage Return -> end of line
- if Copy(Text, 1, 1) = #$0D then
- begin
- Delete(Text, 1, 1);
- Note[N].NewSentence := true;
- end;
-
- // overwrite lyric or append
- if Note[N].Lyric = '-' then
- Note[N].Lyric := Text
- else
- Note[N].Lyric := Note[N].Lyric + Text;
+ Break;
+ Inc(N);
+ end;
- Exit;
+ // check if note was found
+ if (N > High(Note)) then
+ Exit;
+
+ // set text
+ if (LyricType = ltKMIDI) then
+ begin
+ // end of paragraph
+ if Copy(Text, 1, 1) = '\' then
+ begin
+ Delete(Text, 1, 1);
+ end
+ // end of line
+ else if Copy(Text, 1, 1) = '/' then
+ begin
+ Delete(Text, 1, 1);
+ Note[N].NewSentence := true;
+ end;
+ end
+ else // SMFLyric
+ begin
+ // Line Feed -> end of paragraph
+ if Copy(Text, 1, 1) = #$0A then
+ begin
+ Delete(Text, 1, 1);
+ end
+ // Carriage Return -> end of line
+ else if Copy(Text, 1, 1) = #$0D then
+ begin
+ Delete(Text, 1, 1);
+ Note[N].NewSentence := true;
end;
end;
- DebugWriteln('Missing: ' + Text);
+ // overwrite lyric or append
+ if Note[N].Lyric = '-' then
+ Note[N].Lyric := Text
+ else
+ Note[N].Lyric := Note[N].Lyric + Text;
end;
-procedure TScreenEditConvert.Extract;
+procedure TScreenEditConvert.Extract(out Song: TSong; out Lines: TLines);
+
var
T: integer;
C: integer;
@@ -340,6 +393,8 @@ var
NoteTemp: TNote;
Move: integer;
Max, Min: integer;
+ LyricType: TLyricType;
+ Text: UTF8String;
begin
// song info
Song := TSong.Create();
@@ -347,7 +402,6 @@ begin
Song.Resolution := 4;
SetLength(Song.BPM, 1);
Song.BPM[0].BPM := BPM*4;
-
SetLength(Note, 0);
// extract notes
@@ -371,18 +425,51 @@ begin
end;
end;
- // extract lyrics
+ // extract lyrics (and artist + title info)
for T := 0 to High(Tracks) do
begin
- if tsLyrics in Tracks[T].Status then
+ if not (tsLyrics in Tracks[T].Status) then
+ Continue;
+
+ for N := 0 to High(Tracks[T].Note) do
begin
- for N := 0 to High(Tracks[T].Note) do
+ if (Tracks[T].Note[N].Event = MIDI_EVENT_META) then
begin
- if (Tracks[T].Note[N].Event = MIDI_EVENT_META) and
- (Tracks[T].Note[N].Data1 = MIDI_META_LYRICS) then
+ // determine and validate lyric meta tag
+ if (ltKMIDI in Tracks[T].LyricType) and
+ (Tracks[T].Note[N].Data1 = MIDI_META_TEXT) then
begin
- AddLyric(Round(Tracks[T].Note[N].Start / Ticks), Tracks[T].Note[N].Str);
+ Text := Tracks[T].Note[N].Str;
+
+ // check for meta info
+ if (Length(Text) > 2) and (Text[1] = '@') then
+ begin
+ case Text[2] of
+ 'L': Song.Language := Copy(Text, 3, Length(Text)); // language
+ 'T': begin // title info
+ if (Song.Artist = '') then
+ Song.Artist := Copy(Text, 3, Length(Text))
+ else if (Song.Title = '') then
+ Song.Title := Copy(Text, 3, Length(Text));
+ end;
+ end;
+ Continue;
+ end;
+
+ LyricType := ltKMIDI;
+ end
+ else if (ltSMFLyric in Tracks[T].LyricType) and
+ (Tracks[T].Note[N].Data1 = MIDI_META_LYRICS) then
+ begin
+ LyricType := ltSMFLyric;
+ end
+ else
+ begin
+ // unknown meta event
+ Continue;
end;
+
+ AddLyric(Round(Tracks[T].Note[N].Start / Ticks), LyricType, Tracks[T].Note[N].Str);
end;
end;
end;
@@ -404,8 +491,12 @@ begin
// copy notes
SetLength(Lines.Line, 1);
- Lines.Number := 1;
- Lines.High := 0;
+ Lines.Number := 1;
+ Lines.High := 0;
+ Lines.Current := 0;
+ Lines.Resolution := 0;
+ Lines.NotesGAP := 0;
+ Lines.ScoreValue := 0;
C := 0;
N := 0;
@@ -454,7 +545,6 @@ begin
Lines.Line[C].Note[N].Length := Note[Nu].Len;
Lines.Line[C].Note[N].Tone := Note[Nu].Tone;
Lines.Line[C].Note[N].Text := DecodeStringUTF8(Note[Nu].Lyric, DEFAULT_ENCODING);
- //All Notes are Freestyle when Converted Fix:
Lines.Line[C].Note[N].NoteType := ntNormal;
Inc(N);
end;
@@ -500,7 +590,7 @@ begin
AddButton(500, 20, 100, 40, Skin.GetTextureFileName('ButtonF'));
AddButtonText(20, 5, 0, 0, 0, 'Save');
- ConversionFileName := GamePath.Append('file.mid');
+ fFileName := PATH_NONE;
for P := 0 to 100 do
begin
@@ -515,84 +605,120 @@ procedure TScreenEditConvert.OnShow;
var
T: integer; // track
N: integer; // note
+ {$IFDEF UseMIDIPort}
+ MidiTrack: TMidiTrack;
+ MidiEvent: PMidiEvent;
+ {$ENDIF}
+ FileOpened: boolean;
+ KMIDITrackIndex, SMFTrackIndex: integer;
begin
inherited;
+ Interaction := 0;
+
{$IFDEF UseMIDIPort}
MidiOut := TMidiOutput.Create(nil);
- //if Ini.Debug = 1 then
- // MidiOut.ProductName := 'Microsoft GS Wavetable SW Synth'; // for my kxproject without midi table
Log.LogInfo(MidiOut.ProductName, 'MIDI');
MidiOut.Open;
MidiFile := nil;
+ SetLength(Tracks, 0);
+
+ // Filename is only <> PATH_NONE if we called the OpenScreen before
+ fFilename := ScreenOpen.Filename;
+ if (fFilename = PATH_NONE) then
+ Exit;
+ ScreenOpen.Filename := PATH_NONE;
- if ConversionFileName.Exists then
+ FileOpened := false;
+ if fFileName.Exists then
begin
MidiFile := TMidiFile.Create(nil);
- MidiFile.Filename := ConversionFileName;
- MidiFile.ReadFile;
+ MidiFile.Filename := fFileName;
+ try
+ MidiFile.ReadFile;
+ FileOpened := true;
+ except
+ MidiFile.Free;
+ end;
+ end;
+
+ if (not FileOpened) then
+ begin
+ ScreenPopupError.ShowPopup(Language.Translate('ERROR_FILE_NOT_FOUND'));
+ Exit;
+ end;
+
+ Len := 0;
+ SelTrack := 0;
+ BPM := MidiFile.Bpm;
+ Ticks := MidiFile.TicksPerQuarter / 4;
- Len := 0;
- SelTrack := 0;
- BPM := MidiFile.Bpm;
- Ticks := MidiFile.TicksPerQuarter / 4;
+ KMIDITrackIndex := -1;
+ SMFTrackIndex := -1;
- SetLength(Tracks, MidiFile.NumberOfTracks);
- for T := 0 to MidiFile.NumberOfTracks-1 do
+ SetLength(Tracks, MidiFile.NumberOfTracks);
+ for T := 0 to MidiFile.NumberOfTracks-1 do
+ Tracks[T].LyricType := [];
+
+ for T := 0 to MidiFile.NumberOfTracks-1 do
+ begin
+ MidiTrack := MidiFile.GetTrack(T);
+ MidiTrack.OnMidiEvent := nil;
+ Tracks[T].Name := DecodeStringUTF8(MidiTrack.getName, DEFAULT_ENCODING);
+ Tracks[T].NoteType := ntNone;
+ Tracks[T].Status := [];
+
+ SetLength(Tracks[T].Note, MidiTrack.getEventCount());
+ for N := 0 to MidiTrack.getEventCount-1 do
begin
- MidiTrack := MidiFile.GetTrack(T);
- MidiTrack.OnMidiEvent := nil;
- Tracks[T].Name := MidiTrack.getName;
- Tracks[T].NoteType := ntNone;
- Tracks[T].LyricType := [];
- Tracks[T].Status := [];
-
- SetLength(Tracks[T].Note, MidiTrack.getEventCount());
- for N := 0 to MidiTrack.getEventCount-1 do
+ MidiEvent := MidiTrack.GetEvent(N);
+
+ Tracks[T].Note[N].Start := MidiEvent.time;
+ Tracks[T].Note[N].Len := MidiEvent.len;
+ Tracks[T].Note[N].Event := MidiEvent.event;
+ Tracks[T].Note[N].EventType := MidiEvent.event shr 4;
+ Tracks[T].Note[N].Channel := MidiEvent.event and $0F;
+ Tracks[T].Note[N].Data1 := MidiEvent.data1;
+ Tracks[T].Note[N].Data2 := MidiEvent.data2;
+ Tracks[T].Note[N].Str := DecodeStringUTF8(MidiEvent.str, DEFAULT_ENCODING);
+
+ if (Tracks[T].Note[N].Event = MIDI_EVENT_META) then
begin
- MidiEvent := MidiTrack.GetEvent(N);
-
- Tracks[T].Note[N].Start := MidiEvent.time;
- Tracks[T].Note[N].Len := MidiEvent.len;
- Tracks[T].Note[N].Event := MidiEvent.event;
- Tracks[T].Note[N].EventType := MidiEvent.event shr 4;
- Tracks[T].Note[N].Channel := MidiEvent.event and $0F;
- Tracks[T].Note[N].Data1 := MidiEvent.data1;
- Tracks[T].Note[N].Data2 := MidiEvent.data2;
- Tracks[T].Note[N].Str := MidiEvent.str;
-
- if (Tracks[T].Note[N].Event = MIDI_EVENT_META) then
- begin
- case (Tracks[T].Note[N].Data1) of
- MIDI_META_TEXT: begin
- if (Copy(Tracks[T].Note[N].Str, 1, 6) = '@KMIDI') then
- begin
- Tracks[T].LyricType := Tracks[T].LyricType + [ltKMIDI];
- //Tracks[T].Status := [tsLyrics];
- //DebugWriteln('Text: ' + Tracks[T].Note[N].Str);
- end;
- end;
- MIDI_META_LYRICS: begin
- // lyrics in Standard Midi File format found
- Tracks[T].LyricType := Tracks[T].LyricType + [ltSMFLyric];
- //Tracks[T].Status := [tsLyrics];
+ case (Tracks[T].Note[N].Data1) of
+ MIDI_META_TEXT: begin
+ // KMIDI lyrics (uses MIDI_META_TEXT events)
+ if (StrLComp(PAnsiChar(Tracks[T].Note[N].Str), '@KMIDI KARAOKE FILE', 19) = 0) and
+ (High(Tracks) >= T+1) then
+ begin
+ // The '@KMIDI ...' mark is in the first track (mostly named 'Soft Karaoke')
+ // but the lyrics are in the second track (named 'Words')
+ Tracks[T+1].LyricType := Tracks[T+1].LyricType + [ltKMIDI];
+ KMIDITrackIndex := T+1;
end;
end;
- end
- else if (Tracks[T].Note[N].EventType = MIDI_EVENTTYPE_NOTEON) then
- begin
- // notes available
- Tracks[T].NoteType := ntAvail;
+ MIDI_META_LYRICS: begin
+ // lyrics in Standard Midi File format found (uses MIDI_META_LYRICS events)
+ Tracks[T].LyricType := Tracks[T].LyricType + [ltSMFLyric];
+ SMFTrackIndex := T;
+ end;
end;
-
- if Tracks[T].Note[N].Start + Tracks[T].Note[N].Len > Len then
- Len := Tracks[T].Note[N].Start + Tracks[T].Note[N].Len;
+ end
+ else if (Tracks[T].Note[N].EventType = MIDI_EVENTTYPE_NOTEON) then
+ begin
+ // notes available
+ Tracks[T].NoteType := ntAvail;
end;
- end;
+ if Tracks[T].Note[N].Start + Tracks[T].Note[N].Len > Len then
+ Len := Tracks[T].Note[N].Start + Tracks[T].Note[N].Len;
+ end;
end;
- Interaction := 0;
+ // set default lyric track. Prefer KMIDI.
+ if (KMIDITrackIndex > -1) then
+ Tracks[KMIDITrackIndex].Status := Tracks[KMIDITrackIndex].Status + [tsLyrics]
+ else if (SMFTrackIndex > -1) then
+ Tracks[SMFTrackIndex].Status := Tracks[SMFTrackIndex].Status + [tsLyrics];
{$ENDIF}
end;
@@ -658,12 +784,9 @@ begin
for Count := 0 to High(Tracks) do
begin
- // track names should be ASCII only, but who knows
- TrackName := DecodeStringUTF8(Tracks[Count].Name, DEFAULT_ENCODING);
-
SetFontPos(65, Y + Count*YSkip);
SetFontSize(15);
- glPrint(TrackName);
+ glPrint(Tracks[Count].Name);
end;
for Count := 0 to High(Tracks) do
@@ -684,7 +807,8 @@ begin
// playing line
{$IFDEF UseMIDIPort}
- X := 60 + MidiFile.GetCurrentTime/MidiFile.GetTrackLength*730;
+ if (MidiFile <> nil) then
+ X := 60 + MidiFile.GetCurrentTime/MidiFile.GetTrackLength*730;
{$ENDIF}
DrawLine(X, Y, X, Bottom, 0.3, 0.3, 0.3);
@@ -694,9 +818,9 @@ end;
procedure TScreenEditConvert.OnHide;
begin
{$IFDEF UseMIDIPort}
- MidiFile.Free;
+ FreeAndNil(MidiFile);
MidiOut.Close;
- MidiOut.Free;
+ FreeAndNil(MidiOut);
{$ENDIF}
end;