1- { * UltraStar Deluxe - Karaoke Game
1+ { * UltraStar Deluxe - Karaoke Game
22 *
33 * UltraStar Deluxe is the legal property of its developers, whose names
44 * are too numerous to list here. Please refer to the COPYRIGHT
@@ -1982,7 +1982,7 @@ procedure TScreenEditSub.ToggleTextEditMode(SDL_ModState: word);
19821982 begin
19831983 if Interaction = InteractiveNoteId[NoteIndex] then
19841984 begin
1985- if (SDL_GetTicks() - LastClickTime < 250 ) and (SDL_ModState = 0 ) then
1985+ if (SDL_GetTicks() - LastClickTime < 350 ) then
19861986 begin
19871987 CopyToUndo;
19881988 GoldenRec.KillAll;
@@ -3710,15 +3710,103 @@ procedure TScreenEditSub.DivideNote(doubleclick: boolean);
37103710 NoteIndex: Integer;
37113711 CutPosition: Integer;
37123712 SpacePosition: Integer;
3713+ FirstSpacePos: Integer;
3714+ LeftWordCount: Integer;
3715+ TotalWordCount: Integer;
37133716 TempR: real;
37143717 NoteDuration: Integer;
3715- TempStr: UCS4String;
3718+ SourceText: UTF8String;
3719+ SourceTextLen: Integer;
3720+ SourceNoteText: UTF8String;
3721+ ShouldInsertContinuation: Boolean;
3722+ LeftPart: UTF8String;
3723+ RightPart: UTF8String;
3724+
3725+ function CountWords (const S: UTF8String): Integer;
3726+ var
3727+ i: Integer;
3728+ InWord: Boolean;
3729+ begin
3730+ Result := 0 ;
3731+ InWord := false;
3732+ for i := 1 to Length(S) do
3733+ begin
3734+ if S[i] <> ' ' then
3735+ begin
3736+ if not InWord then
3737+ begin
3738+ Inc(Result);
3739+ InWord := true;
3740+ end ;
3741+ end
3742+ else
3743+ InWord := false;
3744+ end ;
3745+ end ;
3746+
3747+ function IsVowel (const Ch: UTF8String): Boolean;
3748+ begin
3749+ Result :=
3750+ (Ch = ' a' ) or (Ch = ' e' ) or (Ch = ' i' ) or (Ch = ' o' ) or (Ch = ' u' ) or (Ch = ' y' ) or
3751+ (Ch = ' A' ) or (Ch = ' E' ) or (Ch = ' I' ) or (Ch = ' O' ) or (Ch = ' U' ) or (Ch = ' Y' );
3752+ end ;
3753+
3754+ function HasVowel (const S: UTF8String): Boolean;
3755+ var
3756+ Chars: UCS4String;
3757+ i: Integer;
3758+ begin
3759+ Result := false;
3760+ if S = ' ' then
3761+ Exit;
3762+
3763+ Chars := UTF8ToUCS4String(S);
3764+ for i := 0 to High(Chars) do
3765+ begin
3766+ if IsVowel(UCS4ToUTF8String(Chars[i])) then
3767+ begin
3768+ Result := true;
3769+ Exit;
3770+ end ;
3771+ end ;
3772+ end ;
3773+
3774+ // Returns the first split position at a real word boundary:
3775+ // previous char is non-space and there is at least one non-space char after.
3776+ // Result uses the same 0-based position convention expected by UTF8Copy calls below.
3777+ function FindFirstSplitSpacePos (const S: UTF8String): Integer;
3778+ var
3779+ Chars: UCS4String;
3780+ i, j: Integer;
3781+ begin
3782+ Result := -1 ;
3783+ if S = ' ' then
3784+ Exit;
3785+
3786+ Chars := UTF8ToUCS4String(S);
3787+ for i := 0 to High(Chars) do
3788+ begin
3789+ if (UCS4ToUTF8String(Chars[i]) = ' ' ) and
3790+ (i > 0 ) and
3791+ (UCS4ToUTF8String(Chars[i - 1 ]) <> ' ' ) then
3792+ begin
3793+ for j := i + 1 to High(Chars) do
3794+ begin
3795+ if UCS4ToUTF8String(Chars[j]) <> ' ' then
3796+ begin
3797+ Result := i;
3798+ Exit;
3799+ end ;
3800+ end ;
3801+ end ;
3802+ end ;
3803+ end ;
37163804begin
37173805 LineIndex := CurrentSong.Tracks[CurrentTrack].CurrentLine;
37183806 NoteDuration := CurrentSong.Tracks[CurrentTrack].Lines[LineIndex].Notes[CurrentNote[CurrentTrack]].Duration;
37193807 TempR := 720 / (CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].EndBeat - CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].Notes[0 ].StartBeat);
37203808
3721- if (doubleclick) and (InteractAt(currentX, CurrentY) > 0 ) then
3809+ if (doubleclick) and (InteractAt(currentX, CurrentY) >= 0 ) then
37223810 CutPosition := Round((currentX - button[Interactions[InteractAt(currentX, CurrentY)].Num].X) / TempR)
37233811 else
37243812 CutPosition := NoteDuration div 2 ;
@@ -3728,6 +3816,20 @@ procedure TScreenEditSub.DivideNote(doubleclick: boolean);
37283816 if CutPosition >= NoteDuration then
37293817 CutPosition := NoteDuration - 1 ;
37303818
3819+ // Auto-splitting text at spaces should also split timing proportionally by word count.
3820+ if TextPosition < 0 then
3821+ begin
3822+ SourceNoteText := CurrentSong.Tracks[CurrentTrack].Lines[LineIndex].Notes[CurrentNote[CurrentTrack]].Text;
3823+ FirstSpacePos := FindFirstSplitSpacePos(SourceNoteText);
3824+ if (FirstSpacePos >= 0 ) then
3825+ begin
3826+ LeftWordCount := CountWords(UTF8Copy(SourceNoteText, 1 , FirstSpacePos));
3827+ TotalWordCount := CountWords(SourceNoteText);
3828+ if (LeftWordCount > 0 ) and (TotalWordCount > LeftWordCount) then
3829+ CutPosition := EnsureRange(Round(NoteDuration * LeftWordCount / TotalWordCount), 1 , NoteDuration - 1 );
3830+ end ;
3831+ end ;
3832+
37313833 with CurrentSong.Tracks[CurrentTrack].Lines[LineIndex] do
37323834 begin
37333835 Inc(HighNote);
@@ -3747,38 +3849,51 @@ procedure TScreenEditSub.DivideNote(doubleclick: boolean);
37473849 Notes[CurrentNote[CurrentTrack]+1 ].StartBeat := Notes[CurrentNote[CurrentTrack]].StartBeat + Notes[CurrentNote[CurrentTrack]].Duration;
37483850 Notes[CurrentNote[CurrentTrack]+1 ].Duration := Notes[CurrentNote[CurrentTrack]+1 ].Duration - Notes[CurrentNote[CurrentTrack]].Duration;
37493851
3750- // find space in text
3751- SpacePosition := - 1 ;
3752- for NoteIndex := 0 to LengthUTF8(Notes[CurrentNote[CurrentTrack]].Text) do
3852+ // find first real word boundary (ignores leading spaces)
3853+ SpacePosition := FindFirstSplitSpacePos(Notes[CurrentNote[CurrentTrack]].Text) ;
3854+ if (TextPosition < 0 ) and (SpacePosition >= 0 ) then
37533855 begin
3754-
3755- TempStr := UTF8ToUCS4String(Notes[CurrentNote[CurrentTrack]].Text);
3756- if ((UCS4ToUTF8String(TempStr[NoteIndex]) = ' ' ) and (SpacePosition < 0 )) then
3757- SpacePosition := NoteIndex;
3758-
3759- end ;
3760- if ((TextPosition < 0 ) and (ansipos(' ' , Notes[CurrentNote[CurrentTrack]].Text) > 1 ) and (ansipos(' ' , Notes[CurrentNote[CurrentTrack]].Text) < Length(Notes[CurrentNote[CurrentTrack]].Text) )) then
3761- begin
3762- Notes[CurrentNote[CurrentTrack]+1 ].Text := UTF8Copy(Notes[CurrentNote[CurrentTrack]].Text, SpacePosition + 2 , LengthUTF8(Notes[CurrentNote[CurrentTrack]].Text));
3763- Notes[CurrentNote[CurrentTrack]].Text := UTF8Copy(Notes[CurrentNote[CurrentTrack]].Text, 1 , SpacePosition + 1 )
3856+ LeftPart := UTF8Copy(Notes[CurrentNote[CurrentTrack]].Text, 1 , SpacePosition);
3857+ RightPart := ' ' + UTF8Copy(Notes[CurrentNote[CurrentTrack]].Text, SpacePosition + 2 , LengthUTF8(Notes[CurrentNote[CurrentTrack]].Text));
3858+ ShouldInsertContinuation := not (HasVowel(LeftPart) and HasVowel(RightPart));
3859+ Notes[CurrentNote[CurrentTrack]].Text := LeftPart;
3860+ if ShouldInsertContinuation then
3861+ Notes[CurrentNote[CurrentTrack]+1 ].Text := ' ~' + RightPart
3862+ else
3863+ Notes[CurrentNote[CurrentTrack]+1 ].Text := RightPart;
37643864 end
37653865 else
3766- if (( TextPosition >= 0 ) and (TextPosition < Length (Notes[CurrentNote[CurrentTrack]].Text) )) then
3866+ if (TextPosition >= 0 ) and (TextPosition <= LengthUTF8 (Notes[CurrentNote[CurrentTrack]].Text)) then
37673867 begin
3768- Notes[CurrentNote[CurrentTrack]+1 ].Text := UTF8Copy(SelectsS[LyricSlideId].TextOpt[0 ].Text, TextPosition + 2 , LengthUTF8(SelectsS[LyricSlideId].TextOpt[0 ].Text));
3769- Notes[CurrentNote[CurrentTrack]].Text := UTF8Copy(SelectsS[LyricSlideId].TextOpt[0 ].Text, 1 , TextPosition);
3770-
3771- if (LengthUTF8(Notes[CurrentNote[CurrentTrack]].Text) > 0 ) and
3772- (UTF8Copy(Notes[CurrentNote[CurrentTrack]].Text, LengthUTF8(Notes[CurrentNote[CurrentTrack]].Text), 1 ) = ' ' ) then
3868+ SourceText := SelectsS[LyricSlideId].TextOpt[0 ].Text;
3869+ SourceTextLen := LengthUTF8(SourceText);
3870+ Notes[CurrentNote[CurrentTrack]+1 ].Text := UTF8Copy(SourceText, TextPosition + 2 , SourceTextLen);
3871+ Notes[CurrentNote[CurrentTrack]].Text := UTF8Copy(SourceText, 1 , TextPosition);
3872+ ShouldInsertContinuation := not (HasVowel(Notes[CurrentNote[CurrentTrack]].Text) and HasVowel(Notes[CurrentNote[CurrentTrack]+1 ].Text));
3873+
3874+ // If splitting at/around a space, keep a continuation marker on its own space note.
3875+ if (TextPosition < SourceTextLen) and
3876+ (UTF8Copy(SourceText, TextPosition + 1 , 1 ) = ' ' ) then
3877+ begin
3878+ if ShouldInsertContinuation then
3879+ Notes[CurrentNote[CurrentTrack]+1 ].Text := ' ~ ' + Notes[CurrentNote[CurrentTrack]+1 ].Text
3880+ else
3881+ Notes[CurrentNote[CurrentTrack]+1 ].Text := ' ' + Notes[CurrentNote[CurrentTrack]+1 ].Text;
3882+ end
3883+ else if (TextPosition > 0 ) and
3884+ (UTF8Copy(SourceText, TextPosition, 1 ) = ' ' ) then
37733885 begin
3774- UTF8Delete(Notes[CurrentNote[CurrentTrack]].Text, LengthUTF8(Notes[CurrentNote[CurrentTrack]].Text), 1 );
3775- Notes[CurrentNote[CurrentTrack]+1 ].Text := ' ~' + Notes[CurrentNote[CurrentTrack]+1 ].Text;
3886+ if (LengthUTF8(Notes[CurrentNote[CurrentTrack]].Text) > 0 ) then
3887+ UTF8Delete(Notes[CurrentNote[CurrentTrack]].Text, LengthUTF8(Notes[CurrentNote[CurrentTrack]].Text), 1 );
3888+ if ShouldInsertContinuation then
3889+ Notes[CurrentNote[CurrentTrack]+1 ].Text := ' ~ ' + UTF8Copy(SourceText, TextPosition + 1 , SourceTextLen)
3890+ else
3891+ Notes[CurrentNote[CurrentTrack]+1 ].Text := ' ' + UTF8Copy(SourceText, TextPosition + 1 , SourceTextLen);
37763892 end
3777- else if (LengthUTF8(Notes[CurrentNote[CurrentTrack]+1 ].Text) > 0 ) and
3778- (UTF8Copy(Notes[CurrentNote[CurrentTrack]+1 ].Text, 1 , 1 ) = ' ' ) then
3893+ else
37793894 begin
3780- UTF8Delete(Notes[CurrentNote[CurrentTrack]+ 1 ].Text, 1 , 1 );
3781- Notes[CurrentNote[CurrentTrack]+1 ].Text := ' ~ ' + Notes[CurrentNote[CurrentTrack]+1 ].Text;
3895+ if ShouldInsertContinuation then
3896+ Notes[CurrentNote[CurrentTrack]+1 ].Text := ' ~' + Notes[CurrentNote[CurrentTrack]+1 ].Text;
37823897 end ;
37833898
37843899 SelectsS[LyricSlideId].TextOpt[0 ].Text := Notes[CurrentNote[CurrentTrack]].Text;
@@ -4792,6 +4907,8 @@ procedure TScreenEditSub.ShowInteractiveBackground;
47924907var
47934908 TempR: real;
47944909 NoteIndex: Integer;
4910+ HitAreaY: real;
4911+ HitAreaH: real;
47954912begin
47964913
47974914 for NoteIndex := 0 to High(TransparentNoteButtonId) do
@@ -4813,12 +4930,14 @@ procedure TScreenEditSub.ShowInteractiveBackground;
48134930 InteractiveNoteId[Length(InteractiveNoteId) - 1 ] := Length(Interactions) - 1 ;
48144931 end ;
48154932 TempR := 720 / (CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].EndBeat - CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].Notes[0 ].StartBeat);
4933+ HitAreaY := Theme.EditSub.NotesBackground.Y + 7 * LineSpacing - NotesH[0 ];
4934+ HitAreaH := 2 * NotesH[0 ];
48164935 for NoteIndex := 0 to CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].HighNote do
48174936 begin
48184937 Button[TransparentNoteButtonId[NoteIndex]].SetX(Theme.EditSub.NotesBackground.X + NotesSkipX + (CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].Notes[NoteIndex].StartBeat - CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].Notes[0 ].StartBeat) * TempR + 0.5 );
4819- Button[TransparentNoteButtonId[NoteIndex]].SetY(Theme.EditSub.NotesBackground.Y + 7 * LineSpacing - (CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].Notes[NoteIndex].Tone - CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].BaseNote) * LineSpacing / 2 - NotesH[ 0 ] );
4938+ Button[TransparentNoteButtonId[NoteIndex]].SetY(HitAreaY );
48204939 Button[TransparentNoteButtonId[NoteIndex]].SetW((CurrentSong.Tracks[CurrentTrack].Lines[CurrentSong.Tracks[CurrentTrack].CurrentLine].Notes[NoteIndex].Duration) * TempR - 0.5 );
4821- Button[TransparentNoteButtonId[NoteIndex]].SetH(2 * NotesH[ 0 ] );
4940+ Button[TransparentNoteButtonId[NoteIndex]].SetH(HitAreaH );
48224941 end ;
48234942end ;
48244943
0 commit comments