unit UScreenEditConvert;
interface
uses UMenu, SDL, MidiFile, MidiOut, ULog, USongs, UMusic, UThemes;
type
TNote = record
Event: integer;
EventType: integer;
Channel: integer;
Start: real;
Len: real;
Data1: integer;
Data2: integer;
Str: string;
end;
TTrack = record
Note: array of TNote;
Name: string;
Hear: boolean;
Status: byte; // 0 - none, 1 - notes, 2 - lyrics, 3 - notes + lyrics
end;
TNuta = record
Start: integer;
Len: integer;
Tone: integer;
Lyric: string;
NewSentence: boolean;
end;
TArrayTrack = array of TTrack;
TScreenEditConvert = class(TMenu)
public
ATrack: TArrayTrack; // actual track
// Track: TArrayTrack;
Channel: TArrayTrack;
ColR: array[0..100] of real;
ColG: array[0..100] of real;
ColB: array[0..100] of real;
Len: real;
Sel: integer;
Selected: boolean;
// FileName: string;
MidiFile: TMidiFile;
MidiTrack: TMidiTrack;
MidiEvent: pMidiEvent;
MidiOut: TMidiOutput;
Song: TSong;
Czesc: TCzesci;
BPM: real;
Ticks: real;
Nuta: array of TNuta;
procedure AddLyric(Start: integer; Tekst: string);
procedure Extract;
procedure MidiFile1MidiEvent(event: PMidiEvent);
function SelectedNumber: integer;
constructor Create; override;
procedure onShow; override;
function ParseInput(PressedKey: Cardinal; ScanCode: byte; PressedDown: Boolean): Boolean; override;
function Draw: boolean; override;
procedure onHide; override;
end;
implementation
uses UGraphic, SysUtils, UDrawTexture, TextGL, UFiles, UMain, UIni, OpenGL, USkins;
function TScreenEditConvert.ParseInput(PressedKey: Cardinal; ScanCode: byte; PressedDown: Boolean): Boolean;
var
T: integer;
begin
Result := true;
If (PressedDown) Then
begin // Key Down
case PressedKey of
SDLK_Q:
begin
Result := false;
end;
SDLK_ESCAPE,
SDLK_BACKSPACE:
begin
MidiFile.StopPlaying;
Music.PlayBack;
FadeTo(@ScreenEdit);
end;
SDLK_RETURN:
begin
if Interaction = 0 then begin
Music.PlayStart;
ScreenOpen.BackScreen := @ScreenEditConvert;
FadeTo(@ScreenOpen);
end;
if Interaction = 1 then begin
Selected := false;
MidiFile.OnMidiEvent := MidiFile1MidiEvent;
// MidiFile.GoToTime(MidiFile.GetTrackLength div 2);
MidiFile.StartPlaying;
end;
if Interaction = 2 then begin
Selected := true;
MidiFile.OnMidiEvent := nil;
{for T := 0 to High(ATrack) do begin
if ATrack[T].Hear then begin
MidiTrack := MidiFile.GetTrack(T);
MidiTrack.OnMidiEvent := MidiFile1MidiEvent;
end;
end;
MidiFile.StartPlaying;//}
end;
if Interaction = 3 then begin
if SelectedNumber > 0 then begin
Extract;
SaveSong(Song, Czesc, ChangeFileExt(FileName, '.txt'), false);
end;
end;
end;
SDLK_SPACE:
begin
// ATrack[Sel].Hear := not ATrack[Sel].Hear;
ATrack[Sel].Status := (ATrack[Sel].Status + 1) mod 4;
{ if Selected then begin
MidiTrack := MidiFile.GetTrack(Sel);
if Track[Sel].Hear then
MidiTrack.OnMidiEvent := MidiFile1MidiEvent
else
MidiTrack.OnMidiEvent := nil;
end;}
end;
SDLK_RIGHT:
begin
InteractNext;
end;
SDLK_LEFT:
begin
InteractPrev;
end;
SDLK_DOWN:
begin
Inc(Sel);
if Sel > High(ATrack) then Sel := 0;
end;
SDLK_UP:
begin
Dec(Sel);
if Sel < 0 then Sel := High(ATrack);
end;
end;
end;
end;
procedure TScreenEditConvert.AddLyric(Start: integer; Tekst: string);
var
N: integer;
begin
for N := 0 to High(Nuta) do begin
if Nuta[N].Start = Start then begin
// check for new sentece
if Copy(Tekst, 1, 1) = '\' then Delete(Tekst, 1, 1);
if Copy(Tekst, 1, 1) = '/' then begin
Delete(Tekst, 1, 1);
Nuta[N].NewSentence := true;
end;
// overwrite lyric od append
if Nuta[N].Lyric = '-' then
Nuta[N].Lyric := Tekst
else
Nuta[N].Lyric := Nuta[N].Lyric + Tekst;
end;
end;
end;
procedure TScreenEditConvert.Extract;
var
T: integer;
C: integer;
N: integer;
Nu: integer;
NutaTemp: TNuta;
Move: integer;
Max, Min: integer;
begin
// song info
Song.Title := '';
Song.Artist := '';
Song.Mp3 := '';
Song.Resolution := 4;
SetLength(Song.BPM, 1);
Song.BPM[0].BPM := BPM*4;
SetLength(Nuta, 0);
// extract notes
for T := 0 to High(ATrack) do begin
// if ATrack[T].Hear then begin
if ((ATrack[T].Status div 1) and 1) = 1 then begin
for N := 0 to High(ATrack[T].Note) do begin
if (ATrack[T].Note[N].EventType = 9) and (ATrack[T].Note[N].Data2 > 0) then begin
Nu := Length(Nuta);
SetLength(Nuta, Nu + 1);
Nuta[Nu].Start := Round(ATrack[T].Note[N].Start / Ticks);
Nuta[Nu].Len := Round(ATrack[T].Note[N].Len / Ticks);
Nuta[Nu].Tone := ATrack[T].Note[N].Data1 - 12*5;
Nuta[Nu].Lyric := '-';
end;
end;
end;
end;
// extract lyrics
for T := 0 to High(ATrack) do begin
// if ATrack[T].Hear then begin
if ((ATrack[T].Status div 2) and 1) = 1 then begin
for N := 0 to High(ATrack[T].Note) do begin
if (ATrack[T].Note[N].EventType = 15) then begin
// Log.LogStatus('<' + Track[T].Note[N].Str + '>', 'MIDI');
AddLyric(Round(ATrack[T].Note[N].Start / Ticks), ATrack[T].Note[N].Str);
end;
end;
end;
end;
// sort notes
for N := 0 to High(Nuta) do
for Nu := 0 to High(Nuta)-1 do
if Nuta[Nu].Start > Nuta[Nu+1].Start then begin
NutaTemp := Nuta[Nu];
Nuta[Nu] := Nuta[Nu+1];
Nuta[Nu+1] := NutaTemp;
end;
// move to 0 at beginning
Move := Nuta[0].Start;
for N := 0 to High(Nuta) do
Nuta[N].Start := Nuta[N].Start - Move;
// copy notes
SetLength(Czesc.Czesc, 1);
Czesc.Ilosc := 1;
Czesc.High := 0;
C := 0;
N := 0;
Czesc.Czesc[C].IlNut := 0;
Czesc.Czesc[C].HighNut := -1;
for Nu := 0 to High(Nuta) do begin
if Nuta[Nu].NewSentence then begin // nowa linijka
SetLength(Czesc.Czesc, Length(Czesc.Czesc)+1);
Czesc.Ilosc := Czesc.Ilosc + 1;
Czesc.High := Czesc.High + 1;
C := C + 1;
N := 0;
SetLength(Czesc.Czesc[C].Nuta, 0);
Czesc.Czesc[C].IlNut := 0;
Czesc.Czesc[C].HighNut := -1;
//Calculate Start of the Last Sentence
if (C > 0) and (Nu > 0) then
begin
Max := Nuta[Nu].Start;
Min := Nuta[Nu-1].Start + Nuta[Nu-1].Len;
case (Max - Min) of
0: Czesc.Czesc[C].Start := Max;
1: Czesc.Czesc[C].Start := Max;
2: Czesc.Czesc[C].Start := Max - 1;
3: Czesc.Czesc[C].Start := Max - 2;
else
if ((Max - Min) > 4) then
Czesc.Czesc[C].Start := Min + 2
else
Czesc.Czesc[C].Start := Max;
end; // case
end;
end;
// tworzy miejsce na nowa nute
SetLength(Czesc.Czesc[C].Nuta, Length(Czesc.Czesc[C].Nuta)+1);
Czesc.Czesc[C].IlNut := Czesc.Czesc[C].IlNut + 1;
Czesc.Czesc[C].HighNut := Czesc.Czesc[C].HighNut + 1;
// dopisuje
Czesc.Czesc[C].Nuta[N].Start := Nuta[Nu].Start;
Czesc.Czesc[C].Nuta[N].Dlugosc := Nuta[Nu].Len;
Czesc.Czesc[C].Nuta[N].Ton := Nuta[Nu].Tone;
Czesc.Czesc[C].Nuta[N].Tekst := Nuta[Nu].Lyric;
//All Notes are Freestyle when Converted Fix:
Czesc.Czesc[C].Nuta[N].Wartosc := 1;
Inc(N);
end;
end;
function TScreenEditConvert.SelectedNumber: integer;
var
T: integer; // track
begin
Result := 0;
for T := 0 to High(ATrack) do
// if ATrack[T].Hear then Inc(Result);
if ((ATrack[T].Status div 1) and 1) = 1 then Inc(Result);
end;
procedure TScreenEditConvert.MidiFile1MidiEvent(event: PMidiEvent);
begin
// Log.LogStatus(IntToStr(event.event), 'MIDI');
MidiOut.PutShort(event.event, event.data1, event.data2);
end;
constructor TScreenEditConvert.Create;
var
P: integer;
begin
inherited Create;
AddButton(40, 20, 100, 40, Skin.GetTextureFileName('ButtonF'));
AddButtonText(15, 5, 0, 0, 0, 'Open');
// Button[High(Button)].Text[0].Size := 11;
AddButton(160, 20, 100, 40, Skin.GetTextureFileName('ButtonF'));
AddButtonText(25, 5, 0, 0, 0, 'Play');
AddButton(280, 20, 200, 40, Skin.GetTextureFileName('ButtonF'));
AddButtonText(25, 5, 0, 0, 0, 'Play Selected');
AddButton(500, 20, 100, 40, Skin.GetTextureFileName('ButtonF'));
AddButtonText(20, 5, 0, 0, 0, 'Save');
{ MidiOut := TMidiOutput.Create(nil);
// MidiOut.Close;
// MidiOut.DeviceID := 0;
if Ini.Debug = 1 then
MidiOut.ProductName := 'Microsoft GS Wavetable SW Synth'; // for my kxproject without midi table
Log.LogStatus(MidiOut.ProductName, 'MIDI');
MidiOut.Open;
// MidiOut.SetVolume(100, 100); // temporary}
FileName := GamePath + 'file.mid';
MidiFile := TMidiFile.Create(nil);
for P := 0 to 100 do begin
ColR[P] := Random(10)/10;
ColG[P] := Random(10)/10;
ColB[P] := Random(10)/10;
end;
end;
procedure TScreenEditConvert.onShow;
var
T: integer; // track
N: integer; // note
C: integer; // channel
CN: integer; // channel note
begin
MidiOut := TMidiOutput.Create(nil);
if Ini.Debug = 1 then
MidiOut.ProductName := 'Microsoft GS Wavetable SW Synth'; // for my kxproject without midi table
Log.LogStatus(MidiOut.ProductName, 'MIDI');
MidiOut.Open;
if FileExists(FileName) then begin
MidiFile.Filename := FileName;
MidiFile.ReadFile;
Len := 0;
Sel := 0;
BPM := MidiFile.Bpm;
Ticks := MidiFile.TicksPerQuarter / 4;
{ for T := 0 to MidiFile.NumberOfTracks-1 do begin
SetLength(Track, Length(Track)+1);
MidiTrack := MidiFile.GetTrack(T);
MidiTrack.OnMidiEvent := MidiFile1MidiEvent;
Track[T].Name := MidiTrack.getName;
for N := 0 to MidiTrack.getEventCount-1 do begin
SetLength(Track[T].Note, Length(Track[T].Note)+1);
MidiEvent := MidiTrack.GetEvent(N);
Track[T].Note[N].Start := MidiEvent.time;
Track[T].Note[N].Len := MidiEvent.len;
Track[T].Note[N].Event := MidiEvent.event;
Track[T].Note[N].EventType := MidiEvent.event div 16;
Track[T].Note[N].Channel := MidiEvent.event and 15;
Track[T].Note[N].Data1 := MidiEvent.data1;
Track[T].Note[N].Data2 := MidiEvent.data2;
Track[T].Note[N].Str := MidiEvent.str;
if Track[T].Note[N].Start + Track[T].Note[N].Len > Len then
Len := Track[T].Note[N].Start + Track[T].Note[N].Len;
end;
end;}
SetLength(Channel, 16);
for T := 0 to 15 do
begin
Channel[T].Name := IntToStr(T+1);
SetLength(Channel[T].Note, 0);
Channel[T].Status := 0;
end;
for T := 0 to MidiFile.NumberOfTracks-1 do begin
MidiTrack := MidiFile.GetTrack(T);
MidiTrack.OnMidiEvent := MidiFile1MidiEvent;
for N := 0 to MidiTrack.getEventCount-1 do begin
MidiEvent := MidiTrack.GetEvent(N);
C := MidiEvent.event and 15;
CN := Length(Channel[C].Note);
SetLength(Channel[C].Note, CN+1);
Channel[C].Note[CN].Start := MidiEvent.time;
Channel[C].Note[CN].Len := MidiEvent.len;
Channel[C].Note[CN].Event := MidiEvent.event;
Channel[C].Note[CN].EventType := MidiEvent.event div 16;
Channel[C].Note[CN].Channel := MidiEvent.event and 15;
Channel[C].Note[CN].Data1 := MidiEvent.data1;
Channel[C].Note[CN].Data2 := MidiEvent.data2;
Channel[C].Note[CN].Str := MidiEvent.str;
if Channel[C].Note[CN].Start + Channel[C].Note[CN].Len > Len then
Len := Channel[C].Note[CN].Start + Channel[C].Note[CN].Len;
end;
end;
ATrack := Channel;
end;
Interaction := 0;
end;
function TScreenEditConvert.Draw: boolean;
var
Pet: integer;
Pet2: integer;
Bottom: real;
X: real;
Y: real;
H: real;
YSkip: real;
begin
// draw static menu
inherited Draw;
Y := 100;
H := Length(ATrack)*40;
if H > 480 then H := 480;
Bottom := Y + H;
YSkip := H / Length(ATrack);
// select
DrawQuad(10, Y+Sel*YSkip, 780, YSkip, 0.8, 0.8, 0.8);
// selected - now me use Status System
for Pet := 0 to High(ATrack) do
if ATrack[Pet].Hear then
DrawQuad(10, Y+Pet*YSkip, 50, YSkip, 0.8, 0.3, 0.3);
glColor3f(0, 0, 0);
for Pet := 0 to High(ATrack) do begin
if ((ATrack[Pet].Status div 1) and 1) = 1 then begin
SetFontPos(25, Y + Pet*YSkip + 10);
SetFontSize(5);
glPrint('N');
end;
if ((ATrack[Pet].Status div 2) and 1) = 1 then begin
SetFontPos(40, Y + Pet*YSkip + 10);
SetFontSize(5);
glPrint('L');
end;
end;
DrawLine(10, Y, 10, Bottom, 0, 0, 0);
DrawLine(60, Y, 60, Bottom, 0, 0, 0);
DrawLine(790, Y, 790, Bottom, 0, 0, 0);
for Pet := 0 to Length(ATrack) do
DrawLine(10, Y+Pet*YSkip, 790, Y+Pet*YSkip, 0, 0, 0);
for Pet := 0 to High(ATrack) do begin
SetFontPos(11, Y + 10 + Pet*YSkip);
SetFontSize(5);
glPrint(pchar(ATrack[Pet].Name));
end;
for Pet := 0 to High(ATrack) do
for Pet2 := 0 to High(ATrack[Pet].Note) do begin
if ATrack[Pet].Note[Pet2].EventType = 9 then
DrawQuad(60 + ATrack[Pet].Note[Pet2].Start/Len * 725, Y + (Pet+1)*YSkip - ATrack[Pet].Note[Pet2].Data1*35/127, 3, 3, ColR[Pet], ColG[Pet], ColB[Pet]);
if ATrack[Pet].Note[Pet2].EventType = 15 then
DrawLine(60 + ATrack[Pet].Note[Pet2].Start/Len * 725, Y + 0.75 * YSkip + Pet*YSkip, 60 + ATrack[Pet].Note[Pet2].Start/Len * 725, Y + YSkip + Pet*YSkip, ColR[Pet], ColG[Pet], ColB[Pet]);
end;
// playing line
X := 60+MidiFile.GetCurrentTime/MidiFile.GetTrackLength*730;
DrawLine(X, Y, X, Bottom, 0.3, 0.3, 0.3);
end;
procedure TScreenEditConvert.onHide;
begin
MidiOut.Close;
MidiOut.Free;
end;
end.