[splittiming -> default] Bring it in.

Any bugs left in here will be fixed as a team.

Now let's kick some StepMania 5 booty!
This commit is contained in:
Jason Felds
2011-05-19 00:01:23 -04:00
81 changed files with 4158 additions and 2539 deletions
+1
View File
@@ -64,6 +64,7 @@ lib*_link.a
Data/UserPrefs Data/UserPrefs
Data/uPrefs Data/uPrefs
Data/GamePrefs/*.cfg Data/GamePrefs/*.cfg
Data/Static.ini
PBProject/Hardware.plist PBProject/Hardware.plist
Themes/pump Themes/pump
Themes/optical-release Themes/optical-release
+6
View File
@@ -9,6 +9,12 @@ change to JSON, but it is unsure if this will be done.
Implement .ssc at your own risk. Implement .ssc at your own risk.
________________________________________________________________________________ ________________________________________________________________________________
[v0.7] - theDtTvB, Wolfman2000
* Split Timing officially implemented.
* #SPEEDS tag for modified scroll speeds.
* #FAKES tag for ignoring judgments within a range.
* #WARPS modified so that second parameter is now relative to the start.
[v0.59] - Wolfman2000 [v0.59] - Wolfman2000
* Typo fix: #RADARVALUES needed a semicolon at the end, not a colon. * Typo fix: #RADARVALUES needed a semicolon at the end, not a colon.
+14
View File
@@ -8,16 +8,30 @@ ________________________________________________________________________________
StepMania 5.0 $NEXT | 20110xyy StepMania 5.0 $NEXT | 20110xyy
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
2011/05/17
----------
* [NotesWriterSM] Write out OpenITG compatible SM files from here on out.
This includes turning Warps into Negative Stops. For those that wanted
negative BPMs specifically, my apologies, but stops are easier on math.
[Wolfman2000]
2011/05/16 2011/05/16
---------- ----------
* [ProfileManager] Added ProfileWasLoadedFromMemoryCard(PlayerNumber) * [ProfileManager] Added ProfileWasLoadedFromMemoryCard(PlayerNumber)
Lua binding. [AJ] Lua binding. [AJ]
* Added #FAKES to the SSC format. Similar to the fake arrows, these blocks
prevent all judgments within the range. [Wolfman2000]
* Changed the syntax of #WARPS slightly. Now the second value is the relative
distance that is skipped, not the absolute value when you start hitting
arrows again. [Wolfman2000]
2011/05/15 2011/05/15
---------- ----------
* [ScreenGameplay] Removed hardcoded commands for Debug (give up text) and * [ScreenGameplay] Removed hardcoded commands for Debug (give up text) and
replace them with DebugStartOnCommand, DebugBackOnCommand, and replace them with DebugStartOnCommand, DebugBackOnCommand, and
DebugTweenOffCommand. [AJ] DebugTweenOffCommand. [AJ]
* Added #SPEEDS to the SSC format. This multiplies your scrolling speed during
gameplay, not replaces it (see mod attacks for replacing). [Wolfman2000]
2011/05/14 2011/05/14
---------- ----------
+3
View File
@@ -1349,6 +1349,7 @@
<Function name='GetMeter'/> <Function name='GetMeter'/>
<Function name='GetRadarValues'/> <Function name='GetRadarValues'/>
<Function name='GetStepsType'/> <Function name='GetStepsType'/>
<Function name='HasSignificantTimingChanges'/>
<Function name='IsAPlayerEdit'/> <Function name='IsAPlayerEdit'/>
<Function name='IsAnEdit'/> <Function name='IsAnEdit'/>
<Function name='IsAutogen'/> <Function name='IsAutogen'/>
@@ -1405,6 +1406,8 @@
<Function name='HasNegativeBPMs'/> <Function name='HasNegativeBPMs'/>
<Function name='HasStops'/> <Function name='HasStops'/>
<Function name='HasWarps'/> <Function name='HasWarps'/>
<Function name='HasFakes'/>
<Function name='HasSpeedChanges'/>
</Class> </Class>
<Class name='Trail'> <Class name='Trail'>
<Function name='GetArtists'/> <Function name='GetArtists'/>
+9
View File
@@ -3367,6 +3367,9 @@
<Function name='GetMeter' return='int' arguments=''> <Function name='GetMeter' return='int' arguments=''>
Returns the numerical difficulty of the Steps. Returns the numerical difficulty of the Steps.
</Function> </Function>
<Function name='HasSignificantTimingChanges' sm-ssc='true' return='bool' arguments=''>
Returns <code>true</code> if the song has significant timing changes.
</Function>
<Function name='GetRadarValues' return='RadarValues' arguments='PlayerNumber pn'> <Function name='GetRadarValues' return='RadarValues' arguments='PlayerNumber pn'>
Returns the complete list of RadarValues for player <code>pn</code>. Use <Link class='RadarValues' function='GetValue' /> to grab a specific value. Returns the complete list of RadarValues for player <code>pn</code>. Use <Link class='RadarValues' function='GetValue' /> to grab a specific value.
</Function> </Function>
@@ -3515,6 +3518,12 @@
<Function name='HasWarps' sm-ssc='true' return='bool' arguments=''> <Function name='HasWarps' sm-ssc='true' return='bool' arguments=''>
Returns <code>true</code> if the TimingData contains warps. Returns <code>true</code> if the TimingData contains warps.
</Function> </Function>
<Function name='HasFakes' sm-ssc='true' return='bool' arguments=''>
Returns <code>true</code> if the TimingData contains fake segments.
</Function>
<Function name='HasSpeedChanges' sm-ssc='true' return='bool' arguments=''>
Returns <code>true</code> if the TimingData contains speed scrolling changes.
</Function>
</Class> </Class>
<Class name='Trail'> <Class name='Trail'>
<Function name='GetArtists' return='{string}' arguments=''> <Function name='GetArtists' return='{string}' arguments=''>
+11 -3
View File
@@ -16,9 +16,10 @@
#INSTRUMENTTRACK:; #INSTRUMENTTRACK:;
#MUSICLENGTH:; #MUSICLENGTH:;
#OFFSET:; #OFFSET:;
#BPMS:;
#STOPS:; #STOPS:;
#DELAYS:; #DELAYS:;
#BPMS:; #LABELS:;
#TIMESIGNATURES:; #TIMESIGNATURES:;
#LASTBEATHINT:; #LASTBEATHINT:;
#SAMPLESTART:; #SAMPLESTART:;
@@ -43,17 +44,24 @@
// information from #NOTES moved here // information from #NOTES moved here
#STEPSTYPE:; #STEPSTYPE:;
#DESCRIPTION:; #DESCRIPTION:;
#CHARTSTYLE:;
#DIFFICULTY:; #DIFFICULTY:;
#METER:; #METER:;
#RADARVALUES:; #RADARVALUES:;
#CREDIT:; #CREDIT:;
// steps-based timingdata // steps-based timingdata
#BPMS:;
#STOPS:; #STOPS:;
#DELAYS:; #DELAYS:;
#BPMS:;
#TIMESIGNATURES:; #TIMESIGNATURES:;
#LASTBEATHINT:; #TICKCOUNTS:;
#COMBOS:;
#SPEEDS:;
#FAKES:;
#LABELS:;
#ATTACKS:; // Not yet in use.
#OFFSET:;
// actual step data // actual step data
#NOTES:; #NOTES:;
+1 -1
View File
@@ -6,7 +6,7 @@ local function Beat(self)
local part = beat%1 local part = beat%1
part = clamp(part,0,0.5) part = clamp(part,0,0.5)
local eff = scale(part,0,0.5,1,0) local eff = scale(part,0,0.5,1,0)
if (GAMESTATE:GetSongDelay() or false) and part == 0 then eff = 0 end if (GAMESTATE:GetSongPosition:GetDelay() or false) and part == 0 then eff = 0 end
if beat < 0 then if beat < 0 then
eff = 0 eff = 0
end end
+28 -14
View File
@@ -558,6 +558,7 @@ Baked Random=Baked Random
Bar=Bar Bar=Bar
Battery=Battery Battery=Battery
Beat=Beat Beat=Beat
Beats=Beats
Big=Big Big=Big
Blind=Blind Blind=Blind
Blink=Blink Blink=Blink
@@ -694,6 +695,7 @@ Right=Right
Roll=Roll Roll=Roll
Romanization=Transliterate Romanization=Transliterate
SHOW=Show SHOW=Show
Seconds=Seconds
Select=Select Select=Select
Servers=Servers Servers=Servers
Shift Left=Shift Left Shift Left=Shift Left
@@ -845,15 +847,18 @@ Exit Edit Mode=Exit Edit Mode
Edit stop=Edit stop Edit stop=Edit stop
Edit delay=Edit delay Edit delay=Edit delay
Edit Timing Data=Edit Timing Data Edit Timing Data=Edit Timing Data
Edit time signature=Edit time signature (not working yet) Edit time signature=Edit time signature
Edit time signature (top)=Edit time signature (beats per measure)
Edit time signature (bottom)=Edit time signature (single beat note value)
Edit tickcount=Edit tickcount Edit tickcount=Edit tickcount
Edit combo=Edit combo Edit combo=Edit combo
Edit label=Edit label Edit label=Edit label
Edit warp=Edit warp Edit warp=Edit warp
Edit speed (percent)=Edit speed (percent)
Edit speed (wait)=Edit speed (length)
Edit speed (mode)=Edit speed (mode)
Edit fake=Edit fake segment
Editor options=Options Editor options=Options
EditorShowBGChangesPlay=Show Backgrounds EditorShowBGChangesPlay=Show Backgrounds
Erase step timing=Erase step timing
Effect=Effect Effect=Effect
EnableAttackSounds=Attack Sounds EnableAttackSounds=Attack Sounds
EnableMineHitSound=Mine Sounds EnableMineHitSound=Mine Sounds
@@ -1217,15 +1222,19 @@ Can't undo - no undo data.=Can't undo - no undo data.
Do you want to revert from disk?=Do you want to revert from disk? Do you want to revert from disk?=Do you want to revert from disk?
Do you want to revert to your last save?=Do you want to revert to your last save? Do you want to revert to your last save?=Do you want to revert to your last save?
Do you want to save changes before exiting?=Do you want to save changes before exiting? Do you want to save changes before exiting?=Do you want to save changes before exiting?
Enter a new BPM value.=Enter a new BPM value. Enter a new BPM value.=Enter a new BPM value.\n\nEnter the previous value to remove.
Enter a new Stop value.=Enter a new Stop value. Enter a new Stop value.=Enter a new Stop value.\n\nType "0" to remove.
Enter a new Delay value.=Enter a new Delay value. Enter a new Delay value.=Enter a new Delay value.\n\nType "0" to remove.
Enter a new Time Signature numerator value.=Enter the number of beats per measure. Enter a new Time Signature.=Enter a new time signature.\nUse the format "x/y".\nEnter the previous value to remove.
Enter a new Time Signature denominator value.=Enter the note value that represents one beat. Enter a new Tickcount value.=Enter a new Tickcount value.\n\nEnter the previous value to remove.
Enter a new Tickcount value.=Enter a new Tickcount value. Enter a new Combo value.=Enter a new Combo value.\n\nEnter the previous value to remove.
Enter a new Combo value.=Enter a new Combo value. Enter a new Label value.=Enter a name for this section of the chart.\n\nEnter a previously used label to remove.
Enter a new Label value.=Enter a name for this section of the chart. Enter a new Warp value.=Enter the number of beats you will warp over.\n\nType "0" to remove this segment.
Enter a new Warp value.=Enter the beat you will warp to when you reach this point. Enter a new Speed percent value.=Enter the ratio for speed scrolling.\nEnter the previous value to remove.\n1 is the default scroll.
Enter a new Speed wait value.=Enter how long in seconds/beats it takes to change.\n\n0 is instant.
Enter a new Speed mode value.=Enter how this segment is handled.\n\nType "0" for beats, "1" for seconds.
Enter a new Fake value.=Enter the number of beats that won't be judged.\n\nType "0" to remove this.
Are you sure you want to erase this chart's timing data?=Are you sure you want to erase this chart's timing data?
Enter a new artist transliteration.=Enter a new artist transliteration. Enter a new artist transliteration.=Enter a new artist transliteration.
Enter a new artist.=Enter a new artist. Enter a new artist.=Enter a new artist.
Enter a new genre.=Enter a new genre. Enter a new genre.=Enter a new genre.
@@ -1246,7 +1255,7 @@ Enter a new min BPM.=Enter the minimum displayed BPM.
Enter a new max BPM.=Enter the maximum displayed BPM. Enter a new max BPM.=Enter the maximum displayed BPM.
More than %d notes per measure is not allowed. This change has been reverted.=More than %d notes per measure is not allowed. This change has been reverted. More than %d notes per measure is not allowed. This change has been reverted.=More than %d notes per measure is not allowed. This change has been reverted.
No backgrounds available=No backgrounds available No backgrounds available=No backgrounds available
EditHelpText=Up/Down:\n change beat\nLeft/Right:\n change snap\nNumber keys:\n add/remove\n tap note\nCreate hold note:\n Hold a number\n while moving\n Up or Down\nCreate roll note:\n Hold Shift,\n then create a\n hold note.\nSpace bar:\n Set area\n marker\nEnter:\n Area Menu\nEscape:\n Main Menu\nF1:\n Show help\n EditHelpText=Up/Down:\n change beat\nLeft/Right:\n change snap\nNumber keys:\n add/remove\n tap note\nN and M keys:\n swap tap notes\nCreate hold note:\n Hold a number\n while moving\n Up or Down\nCreate roll note:\n Hold Shift,\n then create a\n hold note.\nSpace bar:\n Set area\n marker\nEnter:\n Area Menu\nEscape:\n Main Menu\nF4:\n Timing Menu\nF1:\n Show help\n
PlayRecordHelpText=Press START to end PlayRecordHelpText=Press START to end
Save successful.=Save successful. Save successful.=Save successful.
Saved as SM and DWI.=Saved as SM and DWI. Saved as SM and DWI.=Saved as SM and DWI.
@@ -1257,6 +1266,7 @@ This change creates more than %d notes in a measure.=This change creates more th
This change creates notes past the end of the music and is not allowed.=This change creates notes past the end of the music and is not allowed. This change creates notes past the end of the music and is not allowed.=This change creates notes past the end of the music and is not allowed.
This will destroy all unsaved changes.=This will destroy all unsaved changes. This will destroy all unsaved changes.=This will destroy all unsaved changes.
Undo=Undo Undo=Undo
You must be in Song Timing Mode to edit BG Changes.=You must be in Song Timing Mode to edit BG Changes.
Current beat=Current beat Current beat=Current beat
Current second=Current second Current second=Current second
@@ -1276,6 +1286,9 @@ Mines=Mines
Rolls=Rolls Rolls=Rolls
Lifts=Lifts Lifts=Lifts
Fakes=Fakes Fakes=Fakes
Timing Mode=Timing Mode
Step Timing=Step Timing
Song Timing=Song Timing
Beat 0 offset=Beat 0 offset Beat 0 offset=Beat 0 offset
Preview Start=Preview Start Preview Start=Preview Start
Preview Length=Preview Length Preview Length=Preview Length
@@ -1944,7 +1957,7 @@ Decrease/increase music offset=Decrease/increase music offset
Decrease/increase sample music start=Decrease/increase sample music start Decrease/increase sample music start=Decrease/increase sample music start
Decrease/increase sample music length=Decrease/increase sample music length Decrease/increase sample music length=Decrease/increase sample music length
Play sample music=Play sample music Play sample music=Play sample music
Add/Edit Background Change=Add/Edit Background Change Add/Edit Background Change=Add/Edit Background Change (Song Timing Only)
Insert beat and shift down=Insert beat and shift down Insert beat and shift down=Insert beat and shift down
Shift BPM changes and stops down one beat=Shift BPM changes and stops down one beat Shift BPM changes and stops down one beat=Shift BPM changes and stops down one beat
Delete beat and shift up=Delete beat and shift up Delete beat and shift up=Delete beat and shift up
@@ -1953,6 +1966,7 @@ Cycle between tap notes=Cycle between tap notes
Lay mine=Lay mine Lay mine=Lay mine
Lay lift=Lay lift Lay lift=Lay lift
Add to/remove from right half=Add to/remove from right half Add to/remove from right half=Add to/remove from right half
Switch Timing=Switch Timing
Switch player (Routine only)=Switch player (Routine only) Switch player (Routine only)=Switch player (Routine only)
[Grade] [Grade]
+10
View File
@@ -64,3 +64,13 @@ end
if ScreenMetric then if ScreenMetric then
ScreenMetric = Screen.Metric ScreenMetric = Screen.Metric
end end
--[[ GameState ]]
--Aliases for old GAMESTATE timing functions.
--These have been converted to SongPosition, but most themes still use these old functions.
function GameState:GetSongBeat() return self:GetSongPosition():GetSongBeat() end
function GameState:GetSongBeatNoOffset() return self:GetSongPosition():GetSongBeatNoOffset() end
function GameState:GetSongBPS() return self:GetSongPosition():GetCurBPS() end
function GameState:GetSongDelay() return self:GetSongPosition:GetDelay() end
function GameState:GetSongFreeze() return self:GetSongPosition:GetFreeze() end
@@ -0,0 +1 @@
MusicWheel sort
@@ -0,0 +1 @@
MusicWheel sort
+12 -5
View File
@@ -989,6 +989,8 @@ TimeSignatureColor=color("1,0.55,0,1")
TickcountColor=color("0,1,0,1") TickcountColor=color("0,1,0,1")
ComboColor=color("0.55,1,0,1") ComboColor=color("0.55,1,0,1")
LabelColor=color("1,0,0,1") LabelColor=color("1,0,0,1")
SpeedColor=color("0.5,1,1,1")
FakeColor=color("1,1,0.5,1")
# #
BPMIsLeftSide=true BPMIsLeftSide=true
StopIsLeftSide=true StopIsLeftSide=true
@@ -998,15 +1000,19 @@ TimeSignatureIsLeftSide=true
TickcountIsLeftSide=false TickcountIsLeftSide=false
ComboIsLeftSide=false ComboIsLeftSide=false
LabelIsLeftSide=false LabelIsLeftSide=false
SpeedIsLeftSide=false
FakeIsLeftSide=true
# #
BPMOffsetX=60 BPMOffsetX=60
StopOffsetX=50 StopOffsetX=50
DelayOffsetX=120 DelayOffsetX=120
WarpOffsetX=60 WarpOffsetX=90
TimeSignatureOffsetX=30 TimeSignatureOffsetX=30
TickcountOffsetX=10 TickcountOffsetX=70
ComboOffsetX=30 ComboOffsetX=50
LabelOffsetX=80 LabelOffsetX=130
SpeedOffsetX=30
FakeOffsetX=90
[PlayerStageStats] [PlayerStageStats]
# Original CVS Grading # Original CVS Grading
@@ -3607,7 +3613,7 @@ TimerSeconds=-1
EditModifiers="no reverse" EditModifiers="no reverse"
EditHelpX=SCREEN_LEFT+4 EditHelpX=SCREEN_LEFT+4
EditHelpY=SCREEN_TOP+16 EditHelpY=SCREEN_TOP+16
EditHelpOnCommand=halign,0;valign,0;zoom,0.675;shadowlength,1 EditHelpOnCommand=halign,0;valign,0;zoom,0.5;shadowlength,1
InfoX=SCREEN_RIGHT-128 InfoX=SCREEN_RIGHT-128
InfoY=SCREEN_TOP+16 InfoY=SCREEN_TOP+16
InfoOnCommand=halign,0;valign,0;zoom,0.5;shadowlength,1 InfoOnCommand=halign,0;valign,0;zoom,0.5;shadowlength,1
@@ -3636,6 +3642,7 @@ NumHandsFormat="%s: %d\n"
NumRollsFormat="%s: %d\n" NumRollsFormat="%s: %d\n"
NumLiftsFormat="%s: %d\n" NumLiftsFormat="%s: %d\n"
NumFakesFormat="%s: %d\n" NumFakesFormat="%s: %d\n"
TimingModeFormat="%s:\n %s\n"
Beat0OffsetFormat="%s:\n %.5f secs\n" Beat0OffsetFormat="%s:\n %.5f secs\n"
PreviewStartFormat="%s:\n %.5f secs\n" PreviewStartFormat="%s:\n %.5f secs\n"
PreviewLengthFormat="%s:\n %.5f secs\n" PreviewLengthFormat="%s:\n %.5f secs\n"
@@ -1657,6 +1657,8 @@
AAFF41A40F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = AAFF41A20F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.h */; }; AAFF41A40F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = AAFF41A20F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.h */; };
AAFF41A50F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAFF41A10F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.cpp */; }; AAFF41A50F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAFF41A10F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.cpp */; };
AAFF41A60F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = AAFF41A20F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.h */; }; AAFF41A60F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = AAFF41A20F5BD4AD00FFDB67 /* ScreenOptionsExportPackage.h */; };
B75CE3B5137963350076A82D /* SongPosition.h in Headers */ = {isa = PBXBuildFile; fileRef = B75CE3B3137963350076A82D /* SongPosition.h */; };
B75CE3B6137963350076A82D /* SongPosition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B75CE3B4137963350076A82D /* SongPosition.cpp */; };
EE2153A910A75BEE0074B01B /* NotesLoaderPMS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EE2153A710A75BEE0074B01B /* NotesLoaderPMS.cpp */; }; EE2153A910A75BEE0074B01B /* NotesLoaderPMS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EE2153A710A75BEE0074B01B /* NotesLoaderPMS.cpp */; };
EE2153AA10A75BEE0074B01B /* NotesLoaderPMS.h in Headers */ = {isa = PBXBuildFile; fileRef = EE2153A810A75BEE0074B01B /* NotesLoaderPMS.h */; }; EE2153AA10A75BEE0074B01B /* NotesLoaderPMS.h in Headers */ = {isa = PBXBuildFile; fileRef = EE2153A810A75BEE0074B01B /* NotesLoaderPMS.h */; };
EE879B60132DEE720078FDDC /* RageDisplay_Legacy_Helpers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EE879B5C132DEE720078FDDC /* RageDisplay_Legacy_Helpers.cpp */; }; EE879B60132DEE720078FDDC /* RageDisplay_Legacy_Helpers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EE879B5C132DEE720078FDDC /* RageDisplay_Legacy_Helpers.cpp */; };
@@ -3025,6 +3027,8 @@
AAFF55220549F43A00D02EE9 /* ComboGraph.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; name = ComboGraph.h; path = ../src/ComboGraph.h; sourceTree = "<group>"; }; AAFF55220549F43A00D02EE9 /* ComboGraph.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; name = ComboGraph.h; path = ../src/ComboGraph.h; sourceTree = "<group>"; };
AAFF5541054E736D00D02EE9 /* Attack.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; name = Attack.cpp; path = ../src/Attack.cpp; sourceTree = "<group>"; }; AAFF5541054E736D00D02EE9 /* Attack.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; name = Attack.cpp; path = ../src/Attack.cpp; sourceTree = "<group>"; };
AAFF5542054E736D00D02EE9 /* Attack.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; name = Attack.h; path = ../src/Attack.h; sourceTree = "<group>"; }; AAFF5542054E736D00D02EE9 /* Attack.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; name = Attack.h; path = ../src/Attack.h; sourceTree = "<group>"; };
B75CE3B3137963350076A82D /* SongPosition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SongPosition.h; path = ../src/SongPosition.h; sourceTree = SOURCE_ROOT; };
B75CE3B4137963350076A82D /* SongPosition.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SongPosition.cpp; path = ../src/SongPosition.cpp; sourceTree = SOURCE_ROOT; };
EE2153A710A75BEE0074B01B /* NotesLoaderPMS.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = NotesLoaderPMS.cpp; path = ../src/NotesLoaderPMS.cpp; sourceTree = SOURCE_ROOT; }; EE2153A710A75BEE0074B01B /* NotesLoaderPMS.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = NotesLoaderPMS.cpp; path = ../src/NotesLoaderPMS.cpp; sourceTree = SOURCE_ROOT; };
EE2153A810A75BEE0074B01B /* NotesLoaderPMS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NotesLoaderPMS.h; path = ../src/NotesLoaderPMS.h; sourceTree = SOURCE_ROOT; }; EE2153A810A75BEE0074B01B /* NotesLoaderPMS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NotesLoaderPMS.h; path = ../src/NotesLoaderPMS.h; sourceTree = SOURCE_ROOT; };
EE879B5C132DEE720078FDDC /* RageDisplay_Legacy_Helpers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RageDisplay_Legacy_Helpers.cpp; path = ../src/RageDisplay_Legacy_Helpers.cpp; sourceTree = SOURCE_ROOT; }; EE879B5C132DEE720078FDDC /* RageDisplay_Legacy_Helpers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RageDisplay_Legacy_Helpers.cpp; path = ../src/RageDisplay_Legacy_Helpers.cpp; sourceTree = SOURCE_ROOT; };
@@ -3938,6 +3942,8 @@
AA7CDD930D8395F90001DAFC /* Song.h */, AA7CDD930D8395F90001DAFC /* Song.h */,
AA6EFE0704770C79005F316C /* SongOptions.cpp */, AA6EFE0704770C79005F316C /* SongOptions.cpp */,
AA6EFE0804770C79005F316C /* SongOptions.h */, AA6EFE0804770C79005F316C /* SongOptions.h */,
B75CE3B4137963350076A82D /* SongPosition.cpp */,
B75CE3B3137963350076A82D /* SongPosition.h */,
AA5C73F90637A5F000AFB891 /* SongUtil.cpp */, AA5C73F90637A5F000AFB891 /* SongUtil.cpp */,
AA5C73FA0637A5F000AFB891 /* SongUtil.h */, AA5C73FA0637A5F000AFB891 /* SongUtil.h */,
AAEBF57C0B5C83F4005F3C7C /* SoundEffectControl.cpp */, AAEBF57C0B5C83F4005F3C7C /* SoundEffectControl.cpp */,
@@ -5056,6 +5062,7 @@
2BC6132D13773C8E00CC8BBD /* pngpriv.h in Headers */, 2BC6132D13773C8E00CC8BBD /* pngpriv.h in Headers */,
2BC6133313773C8E00CC8BBD /* pngstruct.h in Headers */, 2BC6133313773C8E00CC8BBD /* pngstruct.h in Headers */,
2BC6133E13773D2C00CC8BBD /* pnglibconf.h in Headers */, 2BC6133E13773D2C00CC8BBD /* pnglibconf.h in Headers */,
B75CE3B5137963350076A82D /* SongPosition.h in Headers */,
2B9C2E8E137A138B004C92F3 /* NotesLoaderJson.h in Headers */, 2B9C2E8E137A138B004C92F3 /* NotesLoaderJson.h in Headers */,
2B9C2E90137A138B004C92F3 /* NotesWriterJson.h in Headers */, 2B9C2E90137A138B004C92F3 /* NotesWriterJson.h in Headers */,
); );
@@ -6317,6 +6324,7 @@
2BC6133813773C8E00CC8BBD /* pngwrite.c in Sources */, 2BC6133813773C8E00CC8BBD /* pngwrite.c in Sources */,
2BC6133913773C8E00CC8BBD /* pngwtran.c in Sources */, 2BC6133913773C8E00CC8BBD /* pngwtran.c in Sources */,
2BC6133A13773C8E00CC8BBD /* pngwutil.c in Sources */, 2BC6133A13773C8E00CC8BBD /* pngwutil.c in Sources */,
B75CE3B6137963350076A82D /* SongPosition.cpp in Sources */,
2B9C2E8D137A138B004C92F3 /* NotesLoaderJson.cpp in Sources */, 2B9C2E8D137A138B004C92F3 /* NotesLoaderJson.cpp in Sources */,
2B9C2E8F137A138B004C92F3 /* NotesWriterJson.cpp in Sources */, 2B9C2E8F137A138B004C92F3 /* NotesWriterJson.cpp in Sources */,
); );
+18 -1
View File
@@ -34,6 +34,8 @@ REGISTER_ACTOR_CLASS_WITH_NAME( HiddenActor, Actor );
float Actor::g_fCurrentBGMTime = 0, Actor::g_fCurrentBGMBeat; float Actor::g_fCurrentBGMTime = 0, Actor::g_fCurrentBGMBeat;
float Actor::g_fCurrentBGMTimeNoOffset = 0, Actor::g_fCurrentBGMBeatNoOffset = 0; float Actor::g_fCurrentBGMTimeNoOffset = 0, Actor::g_fCurrentBGMBeatNoOffset = 0;
vector<float> Actor::g_vfCurrentBGMBeatPlayer(NUM_PlayerNumber, 0);
vector<float> Actor::g_vfCurrentBGMBeatPlayerNoOffset(NUM_PlayerNumber, 0);
Actor *Actor::Copy() const { return new Actor(*this); } Actor *Actor::Copy() const { return new Actor(*this); }
@@ -68,6 +70,12 @@ void Actor::SetBGMTime( float fTime, float fBeat, float fTimeNoOffset, float fBe
g_fCurrentBGMBeatNoOffset = fBeatNoOffset; g_fCurrentBGMBeatNoOffset = fBeatNoOffset;
} }
void Actor::SetPlayerBGMBeat( PlayerNumber pn, float fBeat, float fBeatNoOffset )
{
g_vfCurrentBGMBeatPlayer[pn] = fBeat;
g_vfCurrentBGMBeatPlayerNoOffset[pn] = fBeatNoOffset;
}
void Actor::SetBGMLight( int iLightNumber, float fCabinetLights ) void Actor::SetBGMLight( int iLightNumber, float fCabinetLights )
{ {
ASSERT( iLightNumber < NUM_CabinetLight ); ASSERT( iLightNumber < NUM_CabinetLight );
@@ -151,7 +159,6 @@ Actor::Actor()
lua_pop( L, 1 ); lua_pop( L, 1 );
LUA->Release( L ); LUA->Release( L );
m_size = RageVector2( 1, 1 ); m_size = RageVector2( 1, 1 );
InitState(); InitState();
m_pParent = NULL; m_pParent = NULL;
@@ -676,6 +683,16 @@ void Actor::UpdateInternal( float fDeltaTime )
m_fSecsIntoEffect = g_fCurrentBGMBeat; m_fSecsIntoEffect = g_fCurrentBGMBeat;
break; break;
case CLOCK_BGM_BEAT_PLAYER1:
m_fEffectDelta = g_vfCurrentBGMBeatPlayer[PLAYER_1] - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_vfCurrentBGMBeatPlayerNoOffset[PLAYER_1];
break;
case CLOCK_BGM_BEAT_PLAYER2:
m_fEffectDelta = g_vfCurrentBGMBeatPlayer[PLAYER_2] - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_vfCurrentBGMBeatPlayerNoOffset[PLAYER_2];
break;
case CLOCK_BGM_TIME: case CLOCK_BGM_TIME:
m_fEffectDelta = g_fCurrentBGMTime - m_fSecsIntoEffect; m_fEffectDelta = g_fCurrentBGMTime - m_fSecsIntoEffect;
m_fSecsIntoEffect = g_fCurrentBGMTime; m_fSecsIntoEffect = g_fCurrentBGMTime;
+6
View File
@@ -1,6 +1,7 @@
#ifndef ACTOR_H #ifndef ACTOR_H
#define ACTOR_H #define ACTOR_H
#include "PlayerNumber.h"
#include "RageTypes.h" #include "RageTypes.h"
#include "RageUtil_AutoPtr.h" #include "RageUtil_AutoPtr.h"
#include "LuaReference.h" #include "LuaReference.h"
@@ -109,6 +110,7 @@ public:
virtual void LoadFromNode( const XNode* pNode ); virtual void LoadFromNode( const XNode* pNode );
static void SetBGMTime( float fTime, float fBeat, float fTimeNoOffset, float fBeatNoOffset ); static void SetBGMTime( float fTime, float fBeat, float fTimeNoOffset, float fBeatNoOffset );
static void SetPlayerBGMBeat( PlayerNumber pn, float fBeat, float fBeatNoOffset );
static void SetBGMLight( int iLightNumber, float fCabinetLights ); static void SetBGMLight( int iLightNumber, float fCabinetLights );
/** /**
@@ -131,6 +133,8 @@ public:
CLOCK_BGM_BEAT, CLOCK_BGM_BEAT,
CLOCK_BGM_TIME_NO_OFFSET, CLOCK_BGM_TIME_NO_OFFSET,
CLOCK_BGM_BEAT_NO_OFFSET, CLOCK_BGM_BEAT_NO_OFFSET,
CLOCK_BGM_BEAT_PLAYER1,
CLOCK_BGM_BEAT_PLAYER2,
CLOCK_LIGHT_1 = 1000, CLOCK_LIGHT_1 = 1000,
CLOCK_LIGHT_LAST = 1100, CLOCK_LIGHT_LAST = 1100,
NUM_CLOCKS NUM_CLOCKS
@@ -713,6 +717,8 @@ protected:
// global state // global state
static float g_fCurrentBGMTime, g_fCurrentBGMBeat; static float g_fCurrentBGMTime, g_fCurrentBGMBeat;
static float g_fCurrentBGMTimeNoOffset, g_fCurrentBGMBeatNoOffset; static float g_fCurrentBGMTimeNoOffset, g_fCurrentBGMBeatNoOffset;
static vector<float> g_vfCurrentBGMBeatPlayer;
static vector<float> g_vfCurrentBGMBeatPlayerNoOffset;
private: private:
// commands // commands
+12 -12
View File
@@ -58,7 +58,7 @@ void AdjustSync::ResetOriginalSyncData()
s_pTimingDataOriginal = new TimingData; s_pTimingDataOriginal = new TimingData;
if( GAMESTATE->m_pCurSong ) if( GAMESTATE->m_pCurSong )
*s_pTimingDataOriginal = GAMESTATE->m_pCurSong->m_Timing; *s_pTimingDataOriginal = GAMESTATE->m_pCurSong->m_SongTiming;
else else
*s_pTimingDataOriginal = TimingData(); *s_pTimingDataOriginal = TimingData();
s_fGlobalOffsetSecondsOriginal = PREFSMAN->m_fGlobalOffsetSeconds; s_fGlobalOffsetSecondsOriginal = PREFSMAN->m_fGlobalOffsetSeconds;
@@ -87,7 +87,7 @@ void AdjustSync::SaveSyncChanges()
{ {
if( GAMESTATE->IsCourseMode() ) if( GAMESTATE->IsCourseMode() )
return; return;
if( GAMESTATE->m_pCurSong && *s_pTimingDataOriginal != GAMESTATE->m_pCurSong->m_Timing ) if( GAMESTATE->m_pCurSong && *s_pTimingDataOriginal != GAMESTATE->m_pCurSong->m_SongTiming )
{ {
if( GAMESTATE->IsEditing() ) if( GAMESTATE->IsEditing() )
{ {
@@ -110,7 +110,7 @@ void AdjustSync::RevertSyncChanges()
if( GAMESTATE->IsCourseMode() ) if( GAMESTATE->IsCourseMode() )
return; return;
PREFSMAN->m_fGlobalOffsetSeconds.Set( s_fGlobalOffsetSecondsOriginal ); PREFSMAN->m_fGlobalOffsetSeconds.Set( s_fGlobalOffsetSecondsOriginal );
GAMESTATE->m_pCurSong->m_Timing = *s_pTimingDataOriginal; GAMESTATE->m_pCurSong->m_SongTiming = *s_pTimingDataOriginal;
ResetOriginalSyncData(); ResetOriginalSyncData();
s_fStandardDeviation = 0.0f; s_fStandardDeviation = 0.0f;
s_fAverageError = 0.0f; s_fAverageError = 0.0f;
@@ -186,7 +186,7 @@ void AdjustSync::AutosyncOffset()
switch( GAMESTATE->m_SongOptions.GetCurrent().m_AutosyncType ) switch( GAMESTATE->m_SongOptions.GetCurrent().m_AutosyncType )
{ {
case SongOptions::AUTOSYNC_SONG: case SongOptions::AUTOSYNC_SONG:
GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += mean; GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += mean;
break; break;
case SongOptions::AUTOSYNC_MACHINE: case SongOptions::AUTOSYNC_MACHINE:
PREFSMAN->m_fGlobalOffsetSeconds.Set( PREFSMAN->m_fGlobalOffsetSeconds + mean ); PREFSMAN->m_fGlobalOffsetSeconds.Set( PREFSMAN->m_fGlobalOffsetSeconds + mean );
@@ -232,15 +232,15 @@ void AdjustSync::AutosyncTempo()
if( !CalcLeastSquares( s_vAutosyncTempoData, fSlope, fIntercept, fFilteredError ) ) if( !CalcLeastSquares( s_vAutosyncTempoData, fSlope, fIntercept, fFilteredError ) )
return; return;
GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += fIntercept; GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += fIntercept;
const float fScaleBPM = 1.0f/(1.0f - fSlope); const float fScaleBPM = 1.0f/(1.0f - fSlope);
FOREACH( BPMSegment, GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments, i ) FOREACH( BPMSegment, GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments, i )
i->SetBPM( i->GetBPM() * fScaleBPM ); i->SetBPM( i->GetBPM() * fScaleBPM );
// We assume that the stops were measured as a number of beats. // We assume that the stops were measured as a number of beats.
// Therefore, if we change the bpms, we need to make a similar // Therefore, if we change the bpms, we need to make a similar
// change to the stops. // change to the stops.
FOREACH( StopSegment, GAMESTATE->m_pCurSong->m_Timing.m_StopSegments, i ) FOREACH( StopSegment, GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments, i )
i->m_fStopSeconds *= 1.0f - fSlope; i->m_fStopSeconds *= 1.0f - fSlope;
SCREENMAN->SystemMessage( AUTOSYNC_CORRECTION_APPLIED.GetValue() ); SCREENMAN->SystemMessage( AUTOSYNC_CORRECTION_APPLIED.GetValue() );
@@ -296,7 +296,7 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
{ {
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_fBeat0OffsetInSeconds, 0.001f ); float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_fBeat0OffsetInSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds, 0.001f ); float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds, 0.001f );
float fDelta = fNew - fOld; float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f ) if( fabsf(fDelta) > 0.0001f )
@@ -309,10 +309,10 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
} }
} }
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments.size(); i++ ) for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments.size(); i++ )
{ {
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_BPMSegments[i].GetBPM(), 0.001f ); float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_BPMSegments[i].GetBPM(), 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments[i].GetBPM(), 0.001f ); float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_BPMSegments[i].GetBPM(), 0.001f );
float fDelta = fNew - fOld; float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f ) if( fabsf(fDelta) > 0.0001f )
@@ -330,10 +330,10 @@ void AdjustSync::GetSyncChangeTextSong( vector<RString> &vsAddTo )
} }
} }
for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_Timing.m_StopSegments.size(); i++ ) for( unsigned i=0; i<GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments.size(); i++ )
{ {
float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_StopSegments[i].m_fStopSeconds, 0.001f ); float fOld = Quantize( AdjustSync::s_pTimingDataOriginal->m_StopSegments[i].m_fStopSeconds, 0.001f );
float fNew = Quantize( GAMESTATE->m_pCurSong->m_Timing.m_StopSegments[i].m_fStopSeconds, 0.001f ); float fNew = Quantize( GAMESTATE->m_pCurSong->m_SongTiming.m_StopSegments[i].m_fStopSeconds, 0.001f );
float fDelta = fNew - fOld; float fDelta = fNew - fOld;
if( fabsf(fDelta) > 0.0001f ) if( fabsf(fDelta) > 0.0001f )
+75 -18
View File
@@ -67,13 +67,13 @@ static float GetNoteFieldHeight( const PlayerState* pPlayerState )
namespace namespace
{ {
float g_fExpandSeconds = 0;
struct PerPlayerData struct PerPlayerData
{ {
float m_fMinTornadoX[MAX_COLS_PER_PLAYER]; float m_fMinTornadoX[MAX_COLS_PER_PLAYER];
float m_fMaxTornadoX[MAX_COLS_PER_PLAYER]; float m_fMaxTornadoX[MAX_COLS_PER_PLAYER];
float m_fInvertDistance[MAX_COLS_PER_PLAYER]; float m_fInvertDistance[MAX_COLS_PER_PLAYER];
float m_fBeatFactor; float m_fBeatFactor;
float m_fExpandSeconds;
}; };
PerPlayerData g_EffectData[NUM_PLAYERS]; PerPlayerData g_EffectData[NUM_PLAYERS];
}; };
@@ -82,22 +82,25 @@ void ArrowEffects::Update()
{ {
const Style* pStyle = GAMESTATE->GetCurrentStyle(); const Style* pStyle = GAMESTATE->GetCurrentStyle();
FOREACH_PlayerNumber( pn )
{
const Style::ColumnInfo* pCols = pStyle->m_ColumnInfo[pn];
const SongPosition &position = GAMESTATE->m_bIsUsingStepTiming
? GAMESTATE->m_pPlayerState[pn]->m_Position : GAMESTATE->m_Position;
PerPlayerData &data = g_EffectData[pn];
{ {
static float fLastTime = 0; static float fLastTime = 0;
float fTime = RageTimer::GetTimeSinceStartFast(); float fTime = RageTimer::GetTimeSinceStartFast();
if( !GAMESTATE->m_bFreeze || !GAMESTATE->m_bDelay ) if( !position.m_bFreeze || !position.m_bDelay )
{ {
g_fExpandSeconds += fTime - fLastTime; data.m_fExpandSeconds += fTime - fLastTime;
g_fExpandSeconds = fmodf( g_fExpandSeconds, PI*2 ); data.m_fExpandSeconds = fmodf( data.m_fExpandSeconds, PI*2 );
} }
fLastTime = fTime; fLastTime = fTime;
} }
FOREACH_PlayerNumber( pn )
{
const Style::ColumnInfo* pCols = pStyle->m_ColumnInfo[pn];
PerPlayerData &data = g_EffectData[pn];
// Update Tornado // Update Tornado
for( int iColNum = 0; iColNum < MAX_COLS_PER_PLAYER; ++iColNum ) for( int iColNum = 0; iColNum < MAX_COLS_PER_PLAYER; ++iColNum )
{ {
@@ -176,7 +179,7 @@ void ArrowEffects::Update()
// Update Beat // Update Beat
do { do {
float fAccelTime = 0.2f, fTotalTime = 0.5f; float fAccelTime = 0.2f, fTotalTime = 0.5f;
float fBeat = GAMESTATE->m_fSongBeatVisible + fAccelTime; float fBeat = position.m_fSongBeatVisible + fAccelTime;
const bool bEvenBeat = ( int(fBeat) % 2 ) != 0; const bool bEvenBeat = ( int(fBeat) % 2 ) != 0;
@@ -208,6 +211,50 @@ void ArrowEffects::Update()
} }
} }
float GetSpeedMultiplier( float fSongBeat, float fMusicSeconds, const TimingData &tim )
{
if( tim.m_SpeedSegments.size() == 0 )
return 1.0;
const int index = tim.GetSpeedSegmentIndexAtBeat( fSongBeat );
const SpeedSegment &seg = tim.m_SpeedSegments[index];
float fStartBeat = NoteRowToBeat(seg.m_iStartRow);
float fStartTime = tim.GetElapsedTimeFromBeat( fStartBeat ) - tim.GetDelayAtBeat( fStartBeat );
float fEndTime;
float fCurTime = fMusicSeconds;
if( seg.m_usMode == 1 ) // seconds
{
fEndTime = fStartTime + seg.m_fWait;
}
else
{
fEndTime = tim.GetElapsedTimeFromBeat( fStartBeat + seg.m_fWait ) - tim.GetDelayAtBeat( fStartBeat + seg.m_fWait );
}
if( ( index == 0 && tim.m_SpeedSegments[0].m_fWait > 0.0 ) && fCurTime < fStartTime )
{
return 1.0;
}
else if( fEndTime >= fCurTime && ( index > 0 || tim.m_SpeedSegments[0].m_fWait > 0.0 ) )
{
const float fPriorSpeed = ( index == 0 ? 1 : tim.m_SpeedSegments[index - 1].m_fPercent );
float fTimeUsed = fCurTime - fStartTime;
float fDuration = fEndTime - fStartTime;
float fRatioUsed = fDuration == 0.0 ? 1 : fTimeUsed / fDuration;
float fDistance = fPriorSpeed - seg.m_fPercent;
float fRatioNeed = fRatioUsed * -fDistance;
return (fPriorSpeed + fRatioNeed);
}
else
{
return seg.m_fPercent;
}
}
/* For visibility testing: if bAbsolute is false, random modifiers must return /* For visibility testing: if bAbsolute is false, random modifiers must return
* the minimum possible scroll speed. */ * the minimum possible scroll speed. */
float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute ) float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute )
@@ -217,21 +264,27 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
bIsPastPeakOut = true; bIsPastPeakOut = true;
float fYOffset = 0; float fYOffset = 0;
const SongPosition &position = GAMESTATE->m_bIsUsingStepTiming
? pPlayerState->m_Position : GAMESTATE->m_Position;
float fSongBeat = position.m_fSongBeatVisible;
Steps *pCurSteps = GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber];
/* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or /* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or
* entirely time spacing (respectively). Occasionally, we tween between them. */ * entirely time spacing (respectively). Occasionally, we tween between them. */
if( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 1.0f ) if( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 1.0f )
{ {
float fSongBeat = GAMESTATE->m_fSongBeatVisible;
float fBeatsUntilStep = fNoteBeat - fSongBeat; float fBeatsUntilStep = fNoteBeat - fSongBeat;
float fYOffsetBeatSpacing = fBeatsUntilStep; float fYOffsetBeatSpacing = fBeatsUntilStep;
fYOffset += fYOffsetBeatSpacing * (1-pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing); float fSpeedMultiplier = ( GAMESTATE->m_bInStepEditor || !GAMESTATE->m_bIsUsingStepTiming ) ? 1.0 : GetSpeedMultiplier( position.m_fSongBeatVisible, position.m_fMusicSecondsVisible, pCurSteps->m_Timing );
fYOffset += fSpeedMultiplier * fYOffsetBeatSpacing * (1-pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing);
} }
if( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 0.0f ) if( pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing != 0.0f )
{ {
float fSongSeconds = GAMESTATE->m_fMusicSecondsVisible; float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible;
float fNoteSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fNoteBeat); float fNoteSeconds = pCurSteps->m_Timing.GetElapsedTimeFromBeat(fNoteBeat);
float fSecondsUntilStep = fNoteSeconds - fSongSeconds; float fSecondsUntilStep = fNoteSeconds - fSongSeconds;
float fBPM = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollBPM; float fBPM = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollBPM;
float fBPS = fBPM/60.f; float fBPS = fBPM/60.f;
@@ -288,6 +341,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
// Factor in scroll speed // Factor in scroll speed
float fScrollSpeed = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed; float fScrollSpeed = pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed;
if( pPlayerState->m_PlayerOptions.GetCurrent().m_fRandomSpeed > 0 && !bAbsolute ) if( pPlayerState->m_PlayerOptions.GetCurrent().m_fRandomSpeed > 0 && !bAbsolute )
{ {
// Generate a deterministically "random" speed for each arrow. // Generate a deterministically "random" speed for each arrow.
@@ -307,7 +361,10 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float
if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 ) if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 )
{ {
float fExpandMultiplier = SCALE( RageFastCos(g_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY), // TODO: Don't index by PlayerNumber.
PerPlayerData &data = g_EffectData[pPlayerState->m_PlayerNumber];
float fExpandMultiplier = SCALE( RageFastCos(data.m_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY),
EXPAND_MULTIPLIER_SCALE_FROM_LOW, EXPAND_MULTIPLIER_SCALE_FROM_HIGH, EXPAND_MULTIPLIER_SCALE_FROM_LOW, EXPAND_MULTIPLIER_SCALE_FROM_HIGH,
EXPAND_MULTIPLIER_SCALE_TO_LOW, EXPAND_MULTIPLIER_SCALE_TO_HIGH ); EXPAND_MULTIPLIER_SCALE_TO_LOW, EXPAND_MULTIPLIER_SCALE_TO_HIGH );
fScrollSpeed *= SCALE( fAccels[PlayerOptions::ACCEL_EXPAND], fScrollSpeed *= SCALE( fAccels[PlayerOptions::ACCEL_EXPAND],
@@ -511,7 +568,7 @@ float ArrowEffects::GetRotationZ( const PlayerState* pPlayerState, float fNoteBe
// As usual, enable dizzy hold heads at your own risk. -Wolfman2000 // As usual, enable dizzy hold heads at your own risk. -Wolfman2000
if( fEffects[PlayerOptions::EFFECT_DIZZY] != 0 && ( DIZZY_HOLD_HEADS || !bIsHoldHead ) ) if( fEffects[PlayerOptions::EFFECT_DIZZY] != 0 && ( DIZZY_HOLD_HEADS || !bIsHoldHead ) )
{ {
const float fSongBeat = GAMESTATE->m_fSongBeatVisible; const float fSongBeat = pPlayerState->m_Position.m_fSongBeatVisible;
float fDizzyRotation = fNoteBeat - fSongBeat; float fDizzyRotation = fNoteBeat - fSongBeat;
fDizzyRotation *= fEffects[PlayerOptions::EFFECT_DIZZY]; fDizzyRotation *= fEffects[PlayerOptions::EFFECT_DIZZY];
fDizzyRotation = fmodf( fDizzyRotation, 2*PI ); fDizzyRotation = fmodf( fDizzyRotation, 2*PI );
@@ -528,7 +585,7 @@ float ArrowEffects::ReceptorGetRotationZ( const PlayerState* pPlayerState )
if( fEffects[PlayerOptions::EFFECT_CONFUSION] != 0 ) if( fEffects[PlayerOptions::EFFECT_CONFUSION] != 0 )
{ {
float fConfRotation = GAMESTATE->m_fSongBeatVisible; float fConfRotation = pPlayerState->m_Position.m_fSongBeatVisible;
fConfRotation *= fEffects[PlayerOptions::EFFECT_CONFUSION]; fConfRotation *= fEffects[PlayerOptions::EFFECT_CONFUSION];
fConfRotation = fmodf( fConfRotation, 2*PI ); fConfRotation = fmodf( fConfRotation, 2*PI );
fConfRotation *= -180/PI; fConfRotation *= -180/PI;
@@ -680,7 +737,7 @@ float ArrowEffects::GetBrightness( const PlayerState* pPlayerState, float fNoteB
if( GAMESTATE->IsEditing() ) if( GAMESTATE->IsEditing() )
return 1; return 1;
float fSongBeat = GAMESTATE->m_fSongBeatVisible; float fSongBeat = pPlayerState->m_Position.m_fSongBeatVisible;
float fBeatsUntilStep = fNoteBeat - fSongBeat; float fBeatsUntilStep = fNoteBeat - fSongBeat;
float fBrightness = SCALE( fBeatsUntilStep, 0, -1, 1.f, 0.f ); float fBrightness = SCALE( fBeatsUntilStep, 0, -1, 1.f, 0.f );
+7 -5
View File
@@ -12,8 +12,9 @@ void Attack::GetAttackBeats( const Song *pSong, float &fStartBeat, float &fEndBe
ASSERT( pSong ); ASSERT( pSong );
ASSERT_M( fStartSecond >= 0, ssprintf("StartSecond: %f",fStartSecond) ); ASSERT_M( fStartSecond >= 0, ssprintf("StartSecond: %f",fStartSecond) );
fStartBeat = pSong->GetBeatFromElapsedTime( fStartSecond ); const TimingData &timing = pSong->m_SongTiming;
fEndBeat = pSong->GetBeatFromElapsedTime( fStartSecond+fSecsRemaining ); fStartBeat = timing.GetBeatFromElapsedTime( fStartSecond );
fEndBeat = timing.GetBeatFromElapsedTime( fStartSecond+fSecsRemaining );
} }
/* Get the range for an attack that's being applied in realtime, eg. during battle /* Get the range for an attack that's being applied in realtime, eg. during battle
@@ -31,12 +32,13 @@ void Attack::GetRealtimeAttackBeats( const Song *pSong, const PlayerState* pPlay
ASSERT( pSong ); ASSERT( pSong );
/* If reasonable, push the attack forward 8 beats so that notes on screen don't change suddenly. */ /* If reasonable, push the attack forward 8 beats so that notes on screen don't change suddenly. */
fStartBeat = min( GAMESTATE->m_fSongBeat+8, pPlayerState->m_fLastDrawnBeat ); fStartBeat = min( GAMESTATE->m_Position.m_fSongBeat+8, pPlayerState->m_fLastDrawnBeat );
fStartBeat = truncf(fStartBeat)+1; fStartBeat = truncf(fStartBeat)+1;
const float lStartSecond = pSong->GetElapsedTimeFromBeat( fStartBeat ); const TimingData &timing = pSong->m_SongTiming;
const float lStartSecond = timing.GetElapsedTimeFromBeat( fStartBeat );
const float fEndSecond = lStartSecond + fSecsRemaining; const float fEndSecond = lStartSecond + fSecsRemaining;
fEndBeat = pSong->GetBeatFromElapsedTime( fEndSecond ); fEndBeat = timing.GetBeatFromElapsedTime( fEndSecond );
fEndBeat = truncf(fEndBeat)+1; fEndBeat = truncf(fEndBeat)+1;
// loading the course should have caught this. // loading the course should have caught this.
+1 -1
View File
@@ -99,7 +99,7 @@ void AutoKeysounds::LoadAutoplaySoundsInto( RageSoundReader_Chain *pChain )
if( tn[pn].iKeysoundIndex >= 0 ) if( tn[pn].iKeysoundIndex >= 0 )
{ {
RString sKeysoundFilePath = sSongDir + pSong->m_vsKeysoundFile[tn[pn].iKeysoundIndex]; RString sKeysoundFilePath = sSongDir + pSong->m_vsKeysoundFile[tn[pn].iKeysoundIndex];
float fSeconds = pSong->m_Timing.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency(); float fSeconds = GAMESTATE->m_pCurSteps[pn]->m_Timing.GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency();
float fPan = 0; float fPan = 0;
if( !bSoundIsGlobal ) if( !bSoundIsGlobal )
+1 -1
View File
@@ -277,7 +277,7 @@ SongBPMDisplay::SongBPMDisplay()
void SongBPMDisplay::Update( float fDeltaTime ) void SongBPMDisplay::Update( float fDeltaTime )
{ {
float fGameStateBPM = GAMESTATE->m_fCurBPS * 60.0f; float fGameStateBPM = GAMESTATE->m_Position.m_fCurBPS * 60.0f;
if( m_fLastGameStateBPM != fGameStateBPM ) if( m_fLastGameStateBPM != fGameStateBPM )
{ {
m_fLastGameStateBPM = fGameStateBPM; m_fLastGameStateBPM = fGameStateBPM;
+5 -5
View File
@@ -419,7 +419,7 @@ void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const Bac
int iStartRow = BeatToNoteRow(fFirstBeat); int iStartRow = BeatToNoteRow(fFirstBeat);
int iEndRow = BeatToNoteRow(fEndBeat); int iEndRow = BeatToNoteRow(fEndBeat);
const TimingData &timing = m_pSong->m_Timing; const TimingData &timing = m_pSong->m_SongTiming;
// change BG every time signature change or 4 measures // change BG every time signature change or 4 measures
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter ) FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
@@ -697,7 +697,7 @@ void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMus
float fBeat, fBPS, fThrowAway; float fBeat, fBPS, fThrowAway;
bool bFreeze; bool bFreeze;
int iThrowAway; int iThrowAway;
pSong->m_Timing.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway ); pSong->m_SongTiming.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway );
// Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it. // Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it.
const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
@@ -762,7 +762,7 @@ void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMus
m_pCurrentBGA->PlayCommand( "GainFocus" ); m_pCurrentBGA->PlayCommand( "GainFocus" );
/* How much time of this BGA have we skipped? (This happens with SetSeconds.) */ /* How much time of this BGA have we skipped? (This happens with SetSeconds.) */
const float fStartSecond = pSong->m_Timing.GetElapsedTimeFromBeat( change.m_fStartBeat ); const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( change.m_fStartBeat );
/* This is affected by the music rate. */ /* This is affected by the music rate. */
fDeltaTime = fCurrentTime - fStartSecond; fDeltaTime = fCurrentTime - fStartSecond;
@@ -800,9 +800,9 @@ void BackgroundImpl::Update( float fDeltaTime )
FOREACH_BackgroundLayer( i ) FOREACH_BackgroundLayer( i )
{ {
Layer &layer = m_Layer[i]; Layer &layer = m_Layer[i];
layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_fMusicSeconds, m_mapNameToTransition ); layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_Position.m_fMusicSeconds, m_mapNameToTransition );
} }
m_fLastMusicSeconds = GAMESTATE->m_fMusicSeconds; m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
} }
void BackgroundImpl::DrawPrimitives() void BackgroundImpl::DrawPrimitives()
+8 -8
View File
@@ -191,7 +191,7 @@ void BeginnerHelper::ShowStepCircle( PlayerNumber pn, int CSTEP )
m_sStepCircle[pn][isc].StopEffect(); m_sStepCircle[pn][isc].StopEffect();
m_sStepCircle[pn][isc].SetZoom( 2 ); m_sStepCircle[pn][isc].SetZoom( 2 );
m_sStepCircle[pn][isc].StopTweening(); m_sStepCircle[pn][isc].StopTweening();
m_sStepCircle[pn][isc].BeginTweening( GAMESTATE->m_fCurBPS/3, TWEEN_LINEAR ); m_sStepCircle[pn][isc].BeginTweening( GAMESTATE->m_Position.m_fCurBPS/3, TWEEN_LINEAR );
m_sStepCircle[pn][isc].SetZoom( 0 ); m_sStepCircle[pn][isc].SetZoom( 0 );
} }
@@ -318,19 +318,19 @@ void BeginnerHelper::Step( PlayerNumber pn, int CSTEP )
ShowStepCircle( pn, ST_DOWN ); ShowStepCircle( pn, ST_DOWN );
m_pDancer[pn]->StopTweening(); m_pDancer[pn]->StopTweening();
m_pDancer[pn]->PlayAnimation( "Step-JUMPLR", 1.5f ); m_pDancer[pn]->PlayAnimation( "Step-JUMPLR", 1.5f );
m_pDancer[pn]->BeginTweening( GAMESTATE->m_fCurBPS/8, TWEEN_LINEAR ); m_pDancer[pn]->BeginTweening( GAMESTATE->m_Position.m_fCurBPS/8, TWEEN_LINEAR );
m_pDancer[pn]->SetRotationY( 90 ); m_pDancer[pn]->SetRotationY( 90 );
m_pDancer[pn]->BeginTweening( 1/(GAMESTATE->m_fCurBPS * 2) ); //sleep between jump-frames m_pDancer[pn]->BeginTweening( 1/(GAMESTATE->m_Position.m_fCurBPS * 2) ); //sleep between jump-frames
m_pDancer[pn]->BeginTweening( GAMESTATE->m_fCurBPS /6, TWEEN_LINEAR ); m_pDancer[pn]->BeginTweening( GAMESTATE->m_Position.m_fCurBPS /6, TWEEN_LINEAR );
m_pDancer[pn]->SetRotationY( 0 ); m_pDancer[pn]->SetRotationY( 0 );
break; break;
} }
m_sFlash.StopEffect(); m_sFlash.StopEffect();
m_sFlash.StopTweening(); m_sFlash.StopTweening();
m_sFlash.Sleep( GAMESTATE->m_fCurBPS/16 ); m_sFlash.Sleep( GAMESTATE->m_Position.m_fCurBPS/16 );
m_sFlash.SetDiffuseAlpha( 1 ); m_sFlash.SetDiffuseAlpha( 1 );
m_sFlash.BeginTweening( 1/GAMESTATE->m_fCurBPS * 0.5f ); m_sFlash.BeginTweening( 1/GAMESTATE->m_Position.m_fCurBPS * 0.5f );
m_sFlash.SetDiffuseAlpha( 0 ); m_sFlash.SetDiffuseAlpha( 0 );
} }
@@ -340,7 +340,7 @@ void BeginnerHelper::Update( float fDeltaTime )
return; return;
// the row we want to check on this update // the row we want to check on this update
int iCurRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat + 0.4f ); int iCurRow = BeatToNoteRowNotRounded( GAMESTATE->m_Position.m_fSongBeat + 0.4f );
FOREACH_EnabledPlayer( pn ) FOREACH_EnabledPlayer( pn )
{ {
for( int iRow=m_iLastRowChecked; iRow<iCurRow; iRow++ ) for( int iRow=m_iLastRowChecked; iRow<iCurRow; iRow++ )
@@ -369,7 +369,7 @@ void BeginnerHelper::Update( float fDeltaTime )
m_pDancePad->Update( fDeltaTime ); m_pDancePad->Update( fDeltaTime );
m_sFlash.Update( fDeltaTime ); m_sFlash.Update( fDeltaTime );
float beat = fDeltaTime*GAMESTATE->m_fCurBPS; float beat = fDeltaTime*GAMESTATE->m_Position.m_fCurBPS;
// If this is not a human player, the dancer is not shown // If this is not a human player, the dancer is not shown
FOREACH_HumanPlayer( pu ) FOREACH_HumanPlayer( pu )
{ {
+7 -7
View File
@@ -175,7 +175,7 @@ int Neg1OrPos1() { return RandomInt( 2 ) ? -1 : +1; }
void DancingCharacters::Update( float fDelta ) void DancingCharacters::Update( float fDelta )
{ {
if( GAMESTATE->m_bFreeze || GAMESTATE->m_bDelay ) if( GAMESTATE->m_Position.m_bFreeze || GAMESTATE->m_Position.m_bDelay )
{ {
// spin the camera Matrix-style // spin the camera Matrix-style
m_CameraPanYStart += fDelta*40; m_CameraPanYStart += fDelta*40;
@@ -184,7 +184,7 @@ void DancingCharacters::Update( float fDelta )
else else
{ {
// make the characters move // make the characters move
float fBPM = GAMESTATE->m_fCurBPS*60; float fBPM = GAMESTATE->m_Position.m_fCurBPS*60;
float fUpdateScale = SCALE( fBPM, 60.f, 300.f, 0.75f, 1.5f ); float fUpdateScale = SCALE( fBPM, 60.f, 300.f, 0.75f, 1.5f );
CLAMP( fUpdateScale, 0.75f, 1.5f ); CLAMP( fUpdateScale, 0.75f, 1.5f );
@@ -209,8 +209,8 @@ void DancingCharacters::Update( float fDelta )
} }
bWasGameplayStarting = bGameplayStarting; bWasGameplayStarting = bGameplayStarting;
static float fLastBeat = GAMESTATE->m_fSongBeat; static float fLastBeat = GAMESTATE->m_Position.m_fSongBeat;
float fThisBeat = GAMESTATE->m_fSongBeat; float fThisBeat = GAMESTATE->m_Position.m_fSongBeat;
if( fLastBeat < GAMESTATE->m_pCurSong->m_fFirstBeat && if( fLastBeat < GAMESTATE->m_pCurSong->m_fFirstBeat &&
fThisBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat ) fThisBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat )
{ {
@@ -220,7 +220,7 @@ void DancingCharacters::Update( float fDelta )
fLastBeat = fThisBeat; fLastBeat = fThisBeat;
// time for a new sweep? // time for a new sweep?
if( GAMESTATE->m_fSongBeat > m_fThisCameraEndBeat ) if( GAMESTATE->m_Position.m_fSongBeat > m_fThisCameraEndBeat )
{ {
if( RandomInt(6) >= 4 ) if( RandomInt(6) >= 4 )
{ {
@@ -248,7 +248,7 @@ void DancingCharacters::Update( float fDelta )
m_fLookAtHeight = CAMERA_STILL_LOOK_AT_HEIGHT; m_fLookAtHeight = CAMERA_STILL_LOOK_AT_HEIGHT;
} }
int iCurBeat = (int)GAMESTATE->m_fSongBeat; int iCurBeat = (int)GAMESTATE->m_Position.m_fSongBeat;
iCurBeat -= iCurBeat%8; iCurBeat -= iCurBeat%8;
m_fThisCameraStartBeat = (float) iCurBeat; m_fThisCameraStartBeat = (float) iCurBeat;
@@ -313,7 +313,7 @@ void DancingCharacters::DrawPrimitives()
if(m_fThisCameraStartBeat == m_fThisCameraEndBeat) if(m_fThisCameraStartBeat == m_fThisCameraEndBeat)
fPercentIntoSweep = 0; fPercentIntoSweep = 0;
else else
fPercentIntoSweep = SCALE(GAMESTATE->m_fSongBeat, m_fThisCameraStartBeat, m_fThisCameraEndBeat, 0.f, 1.f ); fPercentIntoSweep = SCALE(GAMESTATE->m_Position.m_fSongBeat, m_fThisCameraStartBeat, m_fThisCameraEndBeat, 0.f, 1.f );
float fCameraPanY = SCALE( fPercentIntoSweep, 0.f, 1.f, m_CameraPanYStart, m_CameraPanYEnd ); float fCameraPanY = SCALE( fPercentIntoSweep, 0.f, 1.f, m_CameraPanYStart, m_CameraPanYEnd );
float fCameraHeight = SCALE( fPercentIntoSweep, 0.f, 1.f, m_fCameraHeightStart, m_fCameraHeightEnd ); float fCameraHeight = SCALE( fPercentIntoSweep, 0.f, 1.f, m_fCameraHeightStart, m_fCameraHeightEnd );
+8 -8
View File
@@ -41,9 +41,9 @@ void Foreground::LoadFromSong( const Song *pSong )
bga.m_fStartBeat = change.m_fStartBeat; bga.m_fStartBeat = change.m_fStartBeat;
bga.m_bFinished = false; bga.m_bFinished = false;
const float fStartSecond = pSong->m_Timing.GetElapsedTimeFromBeat( bga.m_fStartBeat ); const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( bga.m_fStartBeat );
const float fStopSecond = fStartSecond + bga.m_bga->GetTweenTimeLeft(); const float fStopSecond = fStartSecond + bga.m_bga->GetTweenTimeLeft();
bga.m_fStopBeat = pSong->m_Timing.GetBeatFromElapsedTime( fStopSecond ); bga.m_fStopBeat = pSong->m_SongTiming.GetBeatFromElapsedTime( fStopSecond );
bga.m_bga->SetVisible( false ); bga.m_bga->SetVisible( false );
@@ -65,7 +65,7 @@ void Foreground::Update( float fDeltaTime )
{ {
LoadedBGA &bga = m_BGAnimations[i]; LoadedBGA &bga = m_BGAnimations[i];
if( GAMESTATE->m_fSongBeat < bga.m_fStartBeat ) if( GAMESTATE->m_Position.m_fSongBeat < bga.m_fStartBeat )
{ {
// The animation hasn't started yet. // The animation hasn't started yet.
continue; continue;
@@ -82,12 +82,12 @@ void Foreground::Update( float fDeltaTime )
bga.m_bga->SetVisible( true ); bga.m_bga->SetVisible( true );
bga.m_bga->PlayCommand( "On" ); bga.m_bga->PlayCommand( "On" );
const float fStartSecond = m_pSong->m_Timing.GetElapsedTimeFromBeat( bga.m_fStartBeat ); const float fStartSecond = m_pSong->m_SongTiming.GetElapsedTimeFromBeat( bga.m_fStartBeat );
lDeltaTime = GAMESTATE->m_fMusicSeconds - fStartSecond; lDeltaTime = GAMESTATE->m_Position.m_fMusicSeconds - fStartSecond;
} }
else else
{ {
lDeltaTime = GAMESTATE->m_fMusicSeconds - m_fLastMusicSeconds; lDeltaTime = GAMESTATE->m_Position.m_fMusicSeconds - m_fLastMusicSeconds;
} }
// This shouldn't go down, but be safe: // This shouldn't go down, but be safe:
@@ -95,7 +95,7 @@ void Foreground::Update( float fDeltaTime )
bga.m_bga->Update( lDeltaTime / fRate ); bga.m_bga->Update( lDeltaTime / fRate );
if( GAMESTATE->m_fSongBeat > bga.m_fStopBeat ) if( GAMESTATE->m_Position.m_fSongBeat > bga.m_fStopBeat )
{ {
// Finished. // Finished.
bga.m_bga->SetVisible( false ); bga.m_bga->SetVisible( false );
@@ -104,7 +104,7 @@ void Foreground::Update( float fDeltaTime )
} }
} }
m_fLastMusicSeconds = GAMESTATE->m_fMusicSeconds; m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
} }
/* /*
+14 -14
View File
@@ -146,7 +146,7 @@ static void StartMusic( MusicToPlay &ToPlay )
SSCLoader::LoadFromSSCFile(ToPlay.m_sTimingFile, song) ) SSCLoader::LoadFromSSCFile(ToPlay.m_sTimingFile, song) )
{ {
ToPlay.HasTiming = true; ToPlay.HasTiming = true;
ToPlay.m_TimingData = song.m_Timing; ToPlay.m_TimingData = song.m_SongTiming;
// get cabinet lights if any // get cabinet lights if any
Steps *pStepsCabinetLights = SongUtil::GetOneSteps( &song, StepsType_lights_cabinet ); Steps *pStepsCabinetLights = SongUtil::GetOneSteps( &song, StepsType_lights_cabinet );
if( pStepsCabinetLights ) if( pStepsCabinetLights )
@@ -156,7 +156,7 @@ static void StartMusic( MusicToPlay &ToPlay )
SMLoader::LoadFromSMFile(ToPlay.m_sTimingFile, song) ) SMLoader::LoadFromSMFile(ToPlay.m_sTimingFile, song) )
{ {
ToPlay.HasTiming = true; ToPlay.HasTiming = true;
ToPlay.m_TimingData = song.m_Timing; ToPlay.m_TimingData = song.m_SongTiming;
// get cabinet lights if any // get cabinet lights if any
Steps *pStepsCabinetLights = SongUtil::GetOneSteps( &song, StepsType_lights_cabinet ); Steps *pStepsCabinetLights = SongUtil::GetOneSteps( &song, StepsType_lights_cabinet );
if( pStepsCabinetLights ) if( pStepsCabinetLights )
@@ -201,7 +201,7 @@ static void StartMusic( MusicToPlay &ToPlay )
{ {
/* This song has no real timing data. The offset is arbitrary. Change it so /* This song has no real timing data. The offset is arbitrary. Change it so
* the beat will line up to where we are now, so we don't have to delay. */ * the beat will line up to where we are now, so we don't have to delay. */
float fDestBeat = fmodfp( GAMESTATE->m_fSongBeatNoOffset, 1 ); float fDestBeat = fmodfp( GAMESTATE->m_Position.m_fSongBeatNoOffset, 1 );
float fTime = NewMusic->m_NewTiming.GetElapsedTimeFromBeatNoOffset( fDestBeat ); float fTime = NewMusic->m_NewTiming.GetElapsedTimeFromBeatNoOffset( fDestBeat );
NewMusic->m_NewTiming.m_fBeat0OffsetInSeconds = fTime; NewMusic->m_NewTiming.m_fBeat0OffsetInSeconds = fTime;
@@ -223,7 +223,7 @@ static void StartMusic( MusicToPlay &ToPlay )
* common when starting a precached sound, but our sound isn't, so it'll * common when starting a precached sound, but our sound isn't, so it'll
* probably take a little longer. Nudge the latency up. */ * probably take a little longer. Nudge the latency up. */
const float fPresumedLatency = SOUNDMAN->GetPlayLatency() + 0.040f; const float fPresumedLatency = SOUNDMAN->GetPlayLatency() + 0.040f;
const float fCurSecond = GAMESTATE->m_fMusicSeconds + fPresumedLatency; const float fCurSecond = GAMESTATE->m_Position.m_fMusicSeconds + fPresumedLatency;
const float fCurBeat = g_Playing->m_Timing.GetBeatFromElapsedTimeNoOffset( fCurSecond ); const float fCurBeat = g_Playing->m_Timing.GetBeatFromElapsedTimeNoOffset( fCurSecond );
/* The beat that the new sound will start on. */ /* The beat that the new sound will start on. */
@@ -236,9 +236,9 @@ static void StartMusic( MusicToPlay &ToPlay )
const float fSecondToStartOn = g_Playing->m_Timing.GetElapsedTimeFromBeatNoOffset( fCurBeatToStartOn ); const float fSecondToStartOn = g_Playing->m_Timing.GetElapsedTimeFromBeatNoOffset( fCurBeatToStartOn );
const float fMaximumDistance = 2; const float fMaximumDistance = 2;
const float fDistance = min( fSecondToStartOn - GAMESTATE->m_fMusicSeconds, fMaximumDistance ); const float fDistance = min( fSecondToStartOn - GAMESTATE->m_Position.m_fMusicSeconds, fMaximumDistance );
when = GAMESTATE->m_LastBeatUpdate + fDistance; when = GAMESTATE->m_Position.m_LastBeatUpdate + fDistance;
} }
/* Important: don't hold the mutex while we load and seek the actual sound. */ /* Important: don't hold the mutex while we load and seek the actual sound. */
@@ -552,8 +552,8 @@ void GameSoundManager::Update( float fDeltaTime )
if( !g_Playing->m_Music->IsPlaying() ) if( !g_Playing->m_Music->IsPlaying() )
{ {
/* There's no song playing. Fake it. */ /* There's no song playing. Fake it. */
CHECKPOINT_M( ssprintf("%f, delta %f", GAMESTATE->m_fMusicSeconds, fDeltaTime) ); CHECKPOINT_M( ssprintf("%f, delta %f", GAMESTATE->m_Position.m_fMusicSeconds, fDeltaTime) );
GAMESTATE->UpdateSongPosition( GAMESTATE->m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing ); GAMESTATE->UpdateSongPosition( GAMESTATE->m_Position.m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
return; return;
} }
@@ -570,8 +570,8 @@ void GameSoundManager::Update( float fDeltaTime )
// //
if( PREFSMAN->m_bLogSkips && !g_Playing->m_bTimingDelayed ) if( PREFSMAN->m_bLogSkips && !g_Playing->m_bTimingDelayed )
{ {
const float fExpectedTimePassed = (tm - GAMESTATE->m_LastBeatUpdate) * g_Playing->m_Music->GetPlaybackRate(); const float fExpectedTimePassed = (tm - GAMESTATE->m_Position.m_LastBeatUpdate) * g_Playing->m_Music->GetPlaybackRate();
const float fSoundTimePassed = fSeconds - GAMESTATE->m_fMusicSeconds; const float fSoundTimePassed = fSeconds - GAMESTATE->m_Position.m_fMusicSeconds;
const float fDiff = fExpectedTimePassed - fSoundTimePassed; const float fDiff = fExpectedTimePassed - fSoundTimePassed;
static RString sLastFile = ""; static RString sLastFile = "";
@@ -580,7 +580,7 @@ void GameSoundManager::Update( float fDeltaTime )
/* If fSoundTimePassed < 0, the sound has probably looped. */ /* If fSoundTimePassed < 0, the sound has probably looped. */
if( sLastFile == ThisFile && fSoundTimePassed >= 0 && fabsf(fDiff) > 0.003f ) if( sLastFile == ThisFile && fSoundTimePassed >= 0 && fabsf(fDiff) > 0.003f )
LOG->Trace("Song position skip in %s: expected %.3f, got %.3f (cur %f, prev %f) (%.3f difference)", LOG->Trace("Song position skip in %s: expected %.3f, got %.3f (cur %f, prev %f) (%.3f difference)",
Basename(ThisFile).c_str(), fExpectedTimePassed, fSoundTimePassed, fSeconds, GAMESTATE->m_fMusicSeconds, fDiff ); Basename(ThisFile).c_str(), fExpectedTimePassed, fSoundTimePassed, fSeconds, GAMESTATE->m_Position.m_fMusicSeconds, fDiff );
sLastFile = ThisFile; sLastFile = ThisFile;
} }
@@ -599,7 +599,7 @@ void GameSoundManager::Update( float fDeltaTime )
{ {
/* We're still waiting for the new sound to start playing, so keep using the /* We're still waiting for the new sound to start playing, so keep using the
* old timing data and fake the time. */ * old timing data and fake the time. */
GAMESTATE->UpdateSongPosition( GAMESTATE->m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing ); GAMESTATE->UpdateSongPosition( GAMESTATE->m_Position.m_fMusicSeconds + fDeltaTime, g_Playing->m_Timing );
} }
else else
{ {
@@ -614,7 +614,7 @@ void GameSoundManager::Update( float fDeltaTime )
{ {
static int iBeatLastCrossed = 0; static int iBeatLastCrossed = 0;
float fSongBeat = GAMESTATE->m_fSongBeat; float fSongBeat = GAMESTATE->m_Position.m_fSongBeat;
int iRowNow = BeatToNoteRowNotRounded( fSongBeat ); int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
iRowNow = max( 0, iRowNow ); iRowNow = max( 0, iRowNow );
@@ -638,7 +638,7 @@ void GameSoundManager::Update( float fDeltaTime )
NoteData &lights = g_Playing->m_Lights; NoteData &lights = g_Playing->m_Lights;
if( lights.GetNumTracks() > 0 ) // lights data was loaded if( lights.GetNumTracks() > 0 ) // lights data was loaded
{ {
const float fSongBeat = GAMESTATE->m_fLightSongBeat; const float fSongBeat = GAMESTATE->m_Position.m_fLightSongBeat;
const int iSongRow = BeatToNoteRowNotRounded( fSongBeat ); const int iSongRow = BeatToNoteRowNotRounded( fSongBeat );
static int iRowLastCrossed = 0; static int iRowLastCrossed = 0;
+31 -64
View File
@@ -127,7 +127,9 @@ GameState::GameState() :
m_pEditSourceSteps( Message_EditSourceStepsChanged ), m_pEditSourceSteps( Message_EditSourceStepsChanged ),
m_stEditSource( Message_EditSourceStepsTypeChanged ), m_stEditSource( Message_EditSourceStepsTypeChanged ),
m_iEditCourseEntryIndex( Message_EditCourseEntryIndexChanged ), m_iEditCourseEntryIndex( Message_EditCourseEntryIndexChanged ),
m_sEditLocalProfileID( Message_EditLocalProfileIDChanged ) m_sEditLocalProfileID( Message_EditLocalProfileIDChanged ),
m_bIsUsingStepTiming( true ),
m_bInStepEditor( false )
{ {
g_pImpl = new GameStateImpl; g_pImpl = new GameStateImpl;
@@ -893,22 +895,15 @@ const float GameState::MUSIC_SECONDS_INVALID = -5000.0f;
void GameState::ResetMusicStatistics() void GameState::ResetMusicStatistics()
{ {
m_fMusicSeconds = 0; // MUSIC_SECONDS_INVALID; m_Position.Reset();
// todo: move me to FOREACH_EnabledPlayer( p ) after [NUM_PLAYERS]ing
m_fSongBeat = 0;
m_fSongBeatNoOffset = 0;
m_fCurBPS = 10;
//m_bStop = false;
m_bFreeze = false;
m_bDelay = false;
m_iWarpBeginRow = -1; // Set to -1 because some song may want to warp to row 0. -aj
m_fWarpDestination = -1; // Set when a warp is encountered. also see above. -aj
m_fMusicSecondsVisible = 0;
m_fSongBeatVisible = 0;
Actor::SetBGMTime( 0, 0, 0, 0 ); Actor::SetBGMTime( 0, 0, 0, 0 );
FOREACH_PlayerNumber( p ) FOREACH_PlayerNumber( p )
{
m_pPlayerState[p]->ClearHopoState(); m_pPlayerState[p]->ClearHopoState();
m_pPlayerState[p]->m_Position.Reset();
}
} }
void GameState::ResetStageStatistics() void GameState::ResetStageStatistics()
@@ -947,57 +942,23 @@ void GameState::ResetStageStatistics()
m_iStageSeed = rand(); m_iStageSeed = rand();
} }
static Preference<float> g_fVisualDelaySeconds( "VisualDelaySeconds", 0.0f ); void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp, bool bUpdatePlayers )
void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp )
{ {
if( !timestamp.IsZero() )
m_LastBeatUpdate = timestamp;
else
m_LastBeatUpdate.Touch();
// xxx testing: only do this on monotune survivor m_Position.UpdateSongPosition( fPositionSeconds, timing, timestamp );
/*
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
LOG->Trace( ssprintf("[GameState::UpdateSongPosition] cur BPS = %f, fPositionSeconds = %f",m_fCurBPS,fPositionSeconds) );
*/
timing.GetBeatAndBPSFromElapsedTime( fPositionSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze, m_bDelay, m_iWarpBeginRow, m_fWarpDestination ); if( bUpdatePlayers )
// "Crash reason : -243478.890625 -48695.773438"
ASSERT_M( m_fSongBeat > -2000, ssprintf("Song beat %f at %f seconds", m_fSongBeat, fPositionSeconds) );
m_fMusicSeconds = fPositionSeconds;
m_fLightSongBeat = timing.GetBeatFromElapsedTime( fPositionSeconds + g_fLightsAheadSeconds );
m_fSongBeatNoOffset = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get();
float fThrowAway, fThrowAway2;
bool bThrowAway;
int iThrowAway;
timing.GetBeatAndBPSFromElapsedTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fThrowAway, bThrowAway, bThrowAway, iThrowAway, fThrowAway2 );
/*
// xxx testing: only do this on monotune survivor
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
{ {
// and only do it in the known negative bpm region. HACKITY HACK FOREACH_EnabledPlayer( pn )
if(m_fSongBeat >= 445.490f && m_fSongBeat <= 453.72f)
{ {
LOG->Trace( ssprintf("fPositionSeconds = %f",fPositionSeconds) ); if( m_pCurSteps[pn] )
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
//LOG->Trace( ssprintf("Music seconds visible %f = fPositionSeconds %f - g_fVisualDelaySeconds %f", m_fMusicSecondsVisible,fPositionSeconds,g_fVisualDelaySeconds.Get()) );
}
else if(m_fSongBeat == 445.500f)
{ {
LOG->Trace( ssprintf("[beat 445.500] fPositionSeconds = %f",fPositionSeconds) ); m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, m_pCurSteps[pn]->m_Timing, timestamp );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) ); Actor::SetPlayerBGMBeat( pn, m_pPlayerState[pn]->m_Position.m_fSongBeatVisible, m_pPlayerState[pn]->m_Position.m_fSongBeatNoOffset );
} }
} }
*/ }
Actor::SetBGMTime( GAMESTATE->m_Position.m_fMusicSecondsVisible, GAMESTATE->m_Position.m_fSongBeatVisible, fPositionSeconds, GAMESTATE->m_Position.m_fSongBeatNoOffset );
Actor::SetBGMTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fPositionSeconds, m_fSongBeatNoOffset );
// LOG->Trace( "m_fMusicSeconds = %f, m_fSongBeat = %f, m_fCurBPS = %f, m_bFreeze = %f", m_fMusicSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze ); // LOG->Trace( "m_fMusicSeconds = %f, m_fSongBeat = %f, m_fCurBPS = %f, m_bFreeze = %f", m_fMusicSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze );
} }
@@ -2291,11 +2252,16 @@ public:
DEFINE_METHOD( GetHardestStepsDifficulty, GetHardestStepsDifficulty() ) DEFINE_METHOD( GetHardestStepsDifficulty, GetHardestStepsDifficulty() )
DEFINE_METHOD( IsEventMode, IsEventMode() ) DEFINE_METHOD( IsEventMode, IsEventMode() )
DEFINE_METHOD( GetNumPlayersEnabled, GetNumPlayersEnabled() ) DEFINE_METHOD( GetNumPlayersEnabled, GetNumPlayersEnabled() )
DEFINE_METHOD( GetSongBeat, m_fSongBeat ) /*DEFINE_METHOD( GetSongBeat, m_Position.m_fSongBeat )
DEFINE_METHOD( GetSongBeatVisible, m_fSongBeatVisible ) DEFINE_METHOD( GetSongBeatVisible, m_Position.m_fSongBeatVisible )
DEFINE_METHOD( GetSongBPS, m_fCurBPS ) DEFINE_METHOD( GetSongBPS, m_Position.m_fCurBPS )
DEFINE_METHOD( GetSongFreeze, m_bFreeze ) DEFINE_METHOD( GetSongFreeze, m_Position.m_bFreeze )
DEFINE_METHOD( GetSongDelay, m_bDelay ) DEFINE_METHOD( GetSongDelay, m_Position.m_bDelay )*/
static int GetSongPosition( T* p, lua_State *L )
{
p->m_Position.PushSelf(L);
return 1;
}
DEFINE_METHOD( GetGameplayLeadIn, m_bGameplayLeadIn ) DEFINE_METHOD( GetGameplayLeadIn, m_bGameplayLeadIn )
DEFINE_METHOD( GetCoins, m_iCoins ) DEFINE_METHOD( GetCoins, m_iCoins )
DEFINE_METHOD( IsSideJoined, m_bSideIsJoined[Enum::Check<PlayerNumber>(L, 1)] ) DEFINE_METHOD( IsSideJoined, m_bSideIsJoined[Enum::Check<PlayerNumber>(L, 1)] )
@@ -2434,7 +2400,7 @@ public:
static int JoinPlayer( T* p, lua_State *L ) { p->JoinPlayer(Enum::Check<PlayerNumber>(L, 1)); return 0; } static int JoinPlayer( T* p, lua_State *L ) { p->JoinPlayer(Enum::Check<PlayerNumber>(L, 1)); return 0; }
static int UnjoinPlayer( T* p, lua_State *L ) { p->UnjoinPlayer(Enum::Check<PlayerNumber>(L, 1)); return 0; } static int UnjoinPlayer( T* p, lua_State *L ) { p->UnjoinPlayer(Enum::Check<PlayerNumber>(L, 1)); return 0; }
static int GetSongPercent( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongPercent(FArg(1))); return 1; } static int GetSongPercent( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongPercent(FArg(1))); return 1; }
DEFINE_METHOD( GetCurMusicSeconds, m_fMusicSeconds ) DEFINE_METHOD( GetCurMusicSeconds, m_Position.m_fMusicSeconds )
DEFINE_METHOD( GetWorkoutGoalComplete, m_bWorkoutGoalComplete ) DEFINE_METHOD( GetWorkoutGoalComplete, m_bWorkoutGoalComplete )
static int GetCharacter( T* p, lua_State *L ) { p->m_pCurCharacters[Enum::Check<PlayerNumber>(L, 1)]->PushSelf(L); return 1; } static int GetCharacter( T* p, lua_State *L ) { p->m_pCurCharacters[Enum::Check<PlayerNumber>(L, 1)]->PushSelf(L); return 1; }
@@ -2500,11 +2466,12 @@ public:
ADD_METHOD( GetHardestStepsDifficulty ); ADD_METHOD( GetHardestStepsDifficulty );
ADD_METHOD( IsEventMode ); ADD_METHOD( IsEventMode );
ADD_METHOD( GetNumPlayersEnabled ); ADD_METHOD( GetNumPlayersEnabled );
ADD_METHOD( GetSongBeat ); /*ADD_METHOD( GetSongBeat );
ADD_METHOD( GetSongBeatVisible ); ADD_METHOD( GetSongBeatVisible );
ADD_METHOD( GetSongBPS ); ADD_METHOD( GetSongBPS );
ADD_METHOD( GetSongFreeze ); ADD_METHOD( GetSongFreeze );
ADD_METHOD( GetSongDelay ); ADD_METHOD( GetSongDelay );*/
ADD_METHOD( GetSongPosition );
ADD_METHOD( GetGameplayLeadIn ); ADD_METHOD( GetGameplayLeadIn );
ADD_METHOD( GetCoins ); ADD_METHOD( GetCoins );
ADD_METHOD( IsSideJoined ); ADD_METHOD( IsSideJoined );
+44 -29
View File
@@ -10,6 +10,7 @@
#include "RageTimer.h" #include "RageTimer.h"
#include "PlayerOptions.h" #include "PlayerOptions.h"
#include "SongOptions.h" #include "SongOptions.h"
#include "SongPosition.h"
#include "Preference.h" #include "Preference.h"
#include <map> #include <map>
@@ -43,7 +44,8 @@ public:
void ResetPlayer( PlayerNumber pn ); void ResetPlayer( PlayerNumber pn );
void ApplyCmdline(); // called by Reset void ApplyCmdline(); // called by Reset
void ApplyGameCommand( const RString &sCommand, PlayerNumber pn=PLAYER_INVALID ); void ApplyGameCommand( const RString &sCommand, PlayerNumber pn=PLAYER_INVALID );
void BeginGame(); // called when first player joins /** @brief Start the game when the first player joins in. */
void BeginGame();
void JoinPlayer( PlayerNumber pn ); void JoinPlayer( PlayerNumber pn );
void UnjoinPlayer( PlayerNumber pn ); void UnjoinPlayer( PlayerNumber pn );
bool JoinInput( PlayerNumber pn ); bool JoinInput( PlayerNumber pn );
@@ -55,13 +57,25 @@ public:
bool HaveProfileToSave(); bool HaveProfileToSave();
void SaveLocalData(); void SaveLocalData();
void LoadCurrentSettingsFromProfile( PlayerNumber pn ); void LoadCurrentSettingsFromProfile( PlayerNumber pn );
void SaveCurrentSettingsToProfile( PlayerNumber pn ); // called at the beginning of each stage /**
* @brief Save the specified player's settings to his/her profile.
*
* This is called at the beginning of each stage.
* @param pn the PlayerNumber to save the stats to. */
void SaveCurrentSettingsToProfile( PlayerNumber pn );
Song* GetDefaultSong() const; Song* GetDefaultSong() const;
void Update( float fDelta ); void Update( float fDelta );
// Main state info // Main state info
void SetCurGame( const Game *pGame ); // Call this instead of m_pCurGame.Set to make sure PREFSMAN->m_sCurrentGame stays in sync
/**
* @brief State what the current game is.
*
* Call this instead of m_pCurGame.Set to make sure that
* PREFSMAN->m_sCurrentGame stays in sync.
* @param pGame the game to start using. */
void SetCurGame( const Game *pGame );
BroadcastOnChangePtr<const Game> m_pCurGame; BroadcastOnChangePtr<const Game> m_pCurGame;
BroadcastOnChangePtr<const Style> m_pCurStyle; BroadcastOnChangePtr<const Style> m_pCurStyle;
/** @brief Determine which side is joined. /** @brief Determine which side is joined.
@@ -129,6 +143,9 @@ public:
bool IsCourseMode() const; bool IsCourseMode() const;
bool IsBattleMode() const; // not Rave bool IsBattleMode() const; // not Rave
/**
* @brief Do we show the W1 timing judgment?
* @return true if we do, or false otherwise. */
bool ShowW1() const; bool ShowW1() const;
BroadcastOnChange<RString> m_sPreferredSongGroup; // GROUP_ALL denotes no preferred group BroadcastOnChange<RString> m_sPreferredSongGroup; // GROUP_ALL denotes no preferred group
@@ -141,7 +158,11 @@ public:
SortOrder m_PreferredSortOrder; // used by MusicWheel SortOrder m_PreferredSortOrder; // used by MusicWheel
EditMode m_EditMode; EditMode m_EditMode;
bool IsEditing() const { return m_EditMode != EditMode_Invalid; } bool IsEditing() const { return m_EditMode != EditMode_Invalid; }
bool m_bDemonstrationOrJukebox; // ScreenGameplay does special stuff when this is true /**
* @brief Are we in the demonstration or jukebox mode?
*
* ScreenGameplay often does special things when this is set to true. */
bool m_bDemonstrationOrJukebox;
bool m_bJukeboxUsesModifiers; bool m_bJukeboxUsesModifiers;
int m_iNumStagesOfThisSong; int m_iNumStagesOfThisSong;
/** /**
@@ -194,29 +215,9 @@ public:
bool m_bBackedOutOfFinalStage; bool m_bBackedOutOfFinalStage;
// Music statistics: // Music statistics:
// Arcade - the current stage (one song). SongPosition m_Position;
// Oni/Endless - a single song in a course.
// Let a lot of classes access this info here so they don't have to keep their own copies.
// todo: [NUM_PLAYERS] this for split bpm lolol -aj
float m_fMusicSeconds; // time into the current song, not scaled by music rate
float m_fSongBeat;
float m_fSongBeatNoOffset;
float m_fCurBPS;
float m_fLightSongBeat; // g_fLightsFalloffSeconds ahead
//bool m_bStop; // in the middle of a stop (freeze or delay)
/** @brief A flag to determine if we're in the middle of a freeze/stop. */
bool m_bFreeze;
/** @brief A flag to determine if we're in the middle of a delay (Pump style stop). */
bool m_bDelay;
/** @brief The row used to start a warp. */
int m_iWarpBeginRow;
/** @brief The beat to warp to afterwards. */
float m_fWarpDestination;
RageTimer m_LastBeatUpdate; // time of last m_fSongBeat, etc. update
BroadcastOnChange<bool> m_bGameplayLeadIn;
float m_fMusicSecondsVisible; BroadcastOnChange<bool> m_bGameplayLeadIn;
float m_fSongBeatVisible;
// if re-adding noteskin changes in courses, add functions and such here -aj // if re-adding noteskin changes in courses, add functions and such here -aj
void GetAllUsedNoteSkins( vector<RString> &out ) const; void GetAllUsedNoteSkins( vector<RString> &out ) const;
@@ -224,7 +225,7 @@ public:
static const float MUSIC_SECONDS_INVALID; static const float MUSIC_SECONDS_INVALID;
void ResetMusicStatistics(); // Call this when it's time to play a new song. Clears the values above. void ResetMusicStatistics(); // Call this when it's time to play a new song. Clears the values above.
void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer ); void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer, bool bUpdatePlayers = false );
float GetSongPercent( float beat ) const; float GetSongPercent( float beat ) const;
bool AllAreInDangerOrWorse() const; bool AllAreInDangerOrWorse() const;
@@ -259,8 +260,11 @@ public:
// Options stuff // Options stuff
ModsGroup<SongOptions> m_SongOptions; ModsGroup<SongOptions> m_SongOptions;
// True if the current mode has changed the default NoteSkin, such as Edit/Sync Songs does. /**
// Note: any mode that wants to use it must set it * @brief Did the current game mode change the default Noteskin?
*
* This is true if it has: see Edit/Sync Songs for a common example.
* Note: any mode that wants to use this must set it explicitly. */
bool m_bDidModeChangeNoteSkin; bool m_bDidModeChangeNoteSkin;
void GetDefaultPlayerOptions( PlayerOptions &po ); void GetDefaultPlayerOptions( PlayerOptions &po );
@@ -327,6 +331,17 @@ public:
Premium GetPremium() const; Premium GetPremium() const;
// Edit stuff // Edit stuff
/**
* @brief Is the game right now using Song timing or Steps timing?
*
* Different options are available depending on this setting. */
bool m_bIsUsingStepTiming;
/**
* @brief Are we presently in the Step Editor, where some rules apply differently?
*
* TODO: Find a better way to implement this. */
bool m_bInStepEditor;
BroadcastOnChange<StepsType> m_stEdit; BroadcastOnChange<StepsType> m_stEdit;
BroadcastOnChange<CourseDifficulty> m_cdEdit; BroadcastOnChange<CourseDifficulty> m_cdEdit;
BroadcastOnChangePtr<Steps> m_pEditSourceSteps; BroadcastOnChangePtr<Steps> m_pEditSourceSteps;
+6 -6
View File
@@ -25,9 +25,9 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
* will start coming out the speaker. Compensate for this by boosting fPositionSeconds * will start coming out the speaker. Compensate for this by boosting fPositionSeconds
* ahead. This is just to make sure that we request the sound early enough for it to * ahead. This is just to make sure that we request the sound early enough for it to
* come out on time; the actual precise timing is handled by SetStartTime. */ * come out on time; the actual precise timing is handled by SetStartTime. */
float fPositionSeconds = GAMESTATE->m_fMusicSeconds; float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f; fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f;
const TimingData &timing = GAMESTATE->m_pCurSong->m_Timing; const TimingData &timing = GAMESTATE->m_pCurSong->m_SongTiming;
const float fSongBeat = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds ); const float fSongBeat = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) ); const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) );
@@ -47,11 +47,11 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
{ {
const float fTickBeat = NoteRowToBeat( iClapRow ); const float fTickBeat = NoteRowToBeat( iClapRow );
const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat ); const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds; float fSecondsUntil = fTickSecond - GAMESTATE->m_Position.m_fMusicSeconds;
fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */ fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */
RageSoundParams p; RageSoundParams p;
p.m_StartTime = GAMESTATE->m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS); p.m_StartTime = GAMESTATE->m_Position.m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
m_soundAssistClap.Play( &p ); m_soundAssistClap.Play( &p );
} }
} }
@@ -83,11 +83,11 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
{ {
const float fTickBeat = NoteRowToBeat( iMetronomeRow ); const float fTickBeat = NoteRowToBeat( iMetronomeRow );
const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat ); const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds; float fSecondsUntil = fTickSecond - GAMESTATE->m_Position.m_fMusicSeconds;
fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */ fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */
RageSoundParams p; RageSoundParams p;
p.m_StartTime = GAMESTATE->m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS); p.m_StartTime = GAMESTATE->m_Position.m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
if( bIsMeasure ) if( bIsMeasure )
m_soundAssistMetronomeMeasure.Play( &p ); m_soundAssistMetronomeMeasure.Play( &p );
else else
+1 -1
View File
@@ -121,7 +121,7 @@ void Inventory::Update( float fDelta )
// use items if this player is CPU-controlled // use items if this player is CPU-controlled
if( m_pPlayerState->m_PlayerController != PC_HUMAN && if( m_pPlayerState->m_PlayerController != PC_HUMAN &&
GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat ) GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
{ {
// every 1 seconds, try to use an item // every 1 seconds, try to use an item
int iLastSecond = (int)(RageTimer::GetTimeSinceStartFast() - fDelta); int iLastSecond = (int)(RageTimer::GetTimeSinceStartFast() - fDelta);
+3 -3
View File
@@ -230,12 +230,12 @@ void LightsManager::Update( float fDeltaTime )
static float fLastBeat; static float fLastBeat;
static int iLight; static int iLight;
if( fracf(GAMESTATE->m_fLightSongBeat) < fracf(fLastBeat) ) if( fracf(GAMESTATE->m_Position.m_fLightSongBeat) < fracf(fLastBeat) )
{ {
++iLight; ++iLight;
wrap( iLight, 4 ); wrap( iLight, 4 );
} }
fLastBeat = GAMESTATE->m_fLightSongBeat; fLastBeat = GAMESTATE->m_Position.m_fLightSongBeat;
switch( iLight ) switch( iLight )
{ {
case 0: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_UP_LEFT] = true; break; case 0: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_UP_LEFT] = true; break;
@@ -405,7 +405,7 @@ void LightsManager::Update( float fDeltaTime )
// If not joined, has enough credits, and not too late to join, then // If not joined, has enough credits, and not too late to join, then
// blink the menu buttons rapidly so they'll press Start // blink the menu buttons rapidly so they'll press Start
{ {
int iBeat = (int)(GAMESTATE->m_fLightSongBeat*4); int iBeat = (int)(GAMESTATE->m_Position.m_fLightSongBeat*4);
bool bBlinkOn = (iBeat%2)==0; bool bBlinkOn = (iBeat%2)==0;
FOREACH_PlayerNumber( pn ) FOREACH_PlayerNumber( pn )
{ {
+4 -4
View File
@@ -41,9 +41,9 @@ void LyricDisplay::Update( float fDeltaTime )
return; return;
// If the song has changed (in a course), reset. // If the song has changed (in a course), reset.
if( GAMESTATE->m_fMusicSeconds < m_fLastSecond ) if( GAMESTATE->m_Position.m_fMusicSeconds < m_fLastSecond )
Init(); Init();
m_fLastSecond = GAMESTATE->m_fMusicSeconds; m_fLastSecond = GAMESTATE->m_Position.m_fMusicSeconds;
if( m_iCurLyricNumber >= GAMESTATE->m_pCurSong->m_LyricSegments.size() ) if( m_iCurLyricNumber >= GAMESTATE->m_pCurSong->m_LyricSegments.size() )
return; return;
@@ -51,7 +51,7 @@ void LyricDisplay::Update( float fDeltaTime )
const Song *pSong = GAMESTATE->m_pCurSong; const Song *pSong = GAMESTATE->m_pCurSong;
const float fStartTime = (pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime) - IN_LENGTH.GetValue(); const float fStartTime = (pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime) - IN_LENGTH.GetValue();
if( GAMESTATE->m_fMusicSeconds < fStartTime ) if( GAMESTATE->m_Position.m_fMusicSeconds < fStartTime )
return; return;
// Clamp this lyric to the beginning of the next or the end of the music. // Clamp this lyric to the beginning of the next or the end of the music.
@@ -59,7 +59,7 @@ void LyricDisplay::Update( float fDeltaTime )
if( m_iCurLyricNumber+1 < GAMESTATE->m_pCurSong->m_LyricSegments.size() ) if( m_iCurLyricNumber+1 < GAMESTATE->m_pCurSong->m_LyricSegments.size() )
fEndTime = pSong->m_LyricSegments[m_iCurLyricNumber+1].m_fStartTime; fEndTime = pSong->m_LyricSegments[m_iCurLyricNumber+1].m_fStartTime;
else else
fEndTime = pSong->GetElapsedTimeFromBeat( pSong->m_fLastBeat ); fEndTime = pSong->m_SongTiming.GetElapsedTimeFromBeat( pSong->m_fLastBeat );
const float fDistance = fEndTime - pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime; const float fDistance = fEndTime - pSong->m_LyricSegments[m_iCurLyricNumber].m_fStartTime;
const float fTweenBufferTime = IN_LENGTH.GetValue() + OUT_LENGTH.GetValue(); const float fTweenBufferTime = IN_LENGTH.GetValue() + OUT_LENGTH.GetValue();
+2 -1
View File
@@ -373,7 +373,8 @@ ScoreDisplayBattle.cpp ScoreDisplayBattle.h \
ScoreDisplayCalories.cpp ScoreDisplayCalories.h \ ScoreDisplayCalories.cpp ScoreDisplayCalories.h \
ScoreDisplayLifeTime.cpp ScoreDisplayLifeTime.h \ ScoreDisplayLifeTime.cpp ScoreDisplayLifeTime.h \
ScoreDisplayNormal.cpp ScoreDisplayNormal.h ScoreDisplayOni.cpp ScoreDisplayOni.h \ ScoreDisplayNormal.cpp ScoreDisplayNormal.h ScoreDisplayOni.cpp ScoreDisplayOni.h \
ScoreDisplayPercentage.cpp ScoreDisplayPercentage.h ScoreDisplayRave.cpp ScoreDisplayRave.h ScoreDisplayPercentage.cpp ScoreDisplayPercentage.h ScoreDisplayRave.cpp ScoreDisplayRave.h \
SongPosition.cpp SongPosition.h
PCRE = ../extern/pcre/get.c ../extern/pcre/internal.h ../extern/pcre/maketables.c ../extern/pcre/pcre.c ../extern/pcre/pcre.h ../extern/pcre/study.c PCRE = ../extern/pcre/get.c ../extern/pcre/internal.h ../extern/pcre/maketables.c ../extern/pcre/pcre.c ../extern/pcre/pcre.h ../extern/pcre/study.c
EXTRA_DIST += ../extern/pcre/chartables.c EXTRA_DIST += ../extern/pcre/chartables.c
+3 -3
View File
@@ -73,9 +73,9 @@ void SongMeterDisplay::Update( float fDeltaTime )
{ {
if( GAMESTATE->m_pCurSong ) if( GAMESTATE->m_pCurSong )
{ {
float fSongStartSeconds = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fFirstBeat ); float fSongStartSeconds = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fFirstBeat );
float fSongEndSeconds = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat ); float fSongEndSeconds = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
float fPercentPositionSong = SCALE( GAMESTATE->m_fMusicSeconds, fSongStartSeconds, fSongEndSeconds, 0.0f, 1.0f ); float fPercentPositionSong = SCALE( GAMESTATE->m_Position.m_fMusicSeconds, fSongStartSeconds, fSongEndSeconds, 0.0f, 1.0f );
CLAMP( fPercentPositionSong, 0, 1 ); CLAMP( fPercentPositionSong, 0, 1 );
SetPercent( fPercentPositionSong ); SetPercent( fPercentPositionSong );
+2 -2
View File
@@ -2215,7 +2215,7 @@ void NoteDataUtil::AddTapAttacks( NoteData &nd, Song* pSong )
for( float sec=15; sec<pSong->m_fMusicLengthSeconds; sec+=30 ) for( float sec=15; sec<pSong->m_fMusicLengthSeconds; sec+=30 )
{ {
float fBeat = pSong->GetBeatFromElapsedTime( sec ); float fBeat = pSong->m_SongTiming.GetBeatFromElapsedTime( sec );
int iBeat = (int)fBeat; int iBeat = (int)fBeat;
int iTrack = iBeat % nd.GetNumTracks(); // deterministically calculates track int iTrack = iBeat % nd.GetNumTracks(); // deterministically calculates track
TapNote tn( TapNote tn(
@@ -2448,7 +2448,7 @@ void NoteDataUtil::SetHopoPossibleFlags( const Song *pSong, NoteData& ndInOut )
FOREACH_NONEMPTY_ROW_ALL_TRACKS( ndInOut, r ) FOREACH_NONEMPTY_ROW_ALL_TRACKS( ndInOut, r )
{ {
float fBeat = NoteRowToBeat( r ); float fBeat = NoteRowToBeat( r );
float fSeconds = pSong->GetElapsedTimeFromBeat( fBeat ); float fSeconds = pSong->m_SongTiming.GetElapsedTimeFromBeat( fBeat );
int iLastTapTrack = ndInOut.GetLastTrackWithTapOrHoldHead( r ); int iLastTapTrack = ndInOut.GetLastTrackWithTapOrHoldHead( r );
if( iLastTapTrack != -1 && fSeconds <= fLastRowMusicSeconds + g_fTimingWindowHopo ) if( iLastTapTrack != -1 && fSeconds <= fLastRowMusicSeconds + g_fTimingWindowHopo )
+1 -1
View File
@@ -286,7 +286,7 @@ void NoteDisplay::Update( float fDeltaTime )
void NoteDisplay::SetActiveFrame( float fNoteBeat, Actor &actorToSet, float fAnimationLength, bool bVivid ) void NoteDisplay::SetActiveFrame( float fNoteBeat, Actor &actorToSet, float fAnimationLength, bool bVivid )
{ {
/* -inf ... inf */ /* -inf ... inf */
float fBeatOrSecond = cache->m_bAnimationBasedOnBeats ? GAMESTATE->m_fSongBeat : GAMESTATE->m_fMusicSeconds; float fBeatOrSecond = cache->m_bAnimationBasedOnBeats ? m_pPlayerState->m_Position.m_fSongBeat : m_pPlayerState->m_Position.m_fMusicSeconds;
/* -len ... +len */ /* -len ... +len */
float fPercentIntoAnimation = fmodf( fBeatOrSecond, fAnimationLength ); float fPercentIntoAnimation = fmodf( fBeatOrSecond, fAnimationLength );
/* -1 ... 1 */ /* -1 ... 1 */
+146 -22
View File
@@ -32,6 +32,33 @@ static ThemeMetric<float> FADE_FAIL_TIME( "NoteField", "FadeFailTime" );
static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); } static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); }
static ThemeMetric1D<RString> ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS ); static ThemeMetric1D<RString> ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS );
inline const TimingData *GetRealTiming(const PlayerState *pPlayerState)
{
if( GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber] != NULL )
return &GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber]->m_Timing;
return NULL;
}
inline const TimingData *GetDisplayedTiming(const PlayerState *pPlayerState)
{
if( !GAMESTATE->m_bIsUsingStepTiming )
return &GAMESTATE->m_pCurSong->m_SongTiming;
return GetRealTiming(pPlayerState);
}
inline const SongPosition *GetRealPosition(const PlayerState *pPlayerState)
{
return &pPlayerState->m_Position;
}
inline const SongPosition *GetDisplayedPosition(const PlayerState *pPlayerState)
{
if( !GAMESTATE->m_bIsUsingStepTiming )
return &GAMESTATE->m_Position;
return GetRealPosition(pPlayerState);
}
NoteField::NoteField() NoteField::NoteField()
{ {
m_pNoteData = NULL; m_pNoteData = NULL;
@@ -240,7 +267,7 @@ void NoteField::Update( float fDeltaTime )
ActorFrame::Update( fDeltaTime ); ActorFrame::Update( fDeltaTime );
// update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate // update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate
const float fCurrentBeat = GAMESTATE->m_fSongBeat; const float fCurrentBeat = GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK
if( !bTweeningOn && m_fCurrentBeatLastUpdate != -1 ) if( !bTweeningOn && m_fCurrentBeatLastUpdate != -1 )
{ {
@@ -427,6 +454,8 @@ static ThemeMetric<RageColor> TIME_SIGNATURE_COLOR ( "NoteField", "TimeSignature
static ThemeMetric<RageColor> TICKCOUNT_COLOR ( "NoteField", "TickcountColor" ); static ThemeMetric<RageColor> TICKCOUNT_COLOR ( "NoteField", "TickcountColor" );
static ThemeMetric<RageColor> COMBO_COLOR ( "NoteField", "ComboColor" ); static ThemeMetric<RageColor> COMBO_COLOR ( "NoteField", "ComboColor" );
static ThemeMetric<RageColor> LABEL_COLOR ( "NoteField", "LabelColor" ); static ThemeMetric<RageColor> LABEL_COLOR ( "NoteField", "LabelColor" );
static ThemeMetric<RageColor> SPEED_COLOR ( "NoteField", "SpeedColor" );
static ThemeMetric<RageColor> FAKE_COLOR ("NoteField", "FakeColor" );
static ThemeMetric<bool> BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" ); static ThemeMetric<bool> BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" );
static ThemeMetric<bool> STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" ); static ThemeMetric<bool> STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" );
static ThemeMetric<bool> DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" ); static ThemeMetric<bool> DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" );
@@ -435,6 +464,8 @@ static ThemeMetric<bool> TIME_SIGNATURE_IS_LEFT_SIDE ( "NoteField", "TimeSignatu
static ThemeMetric<bool> TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" ); static ThemeMetric<bool> TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" );
static ThemeMetric<bool> COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" ); static ThemeMetric<bool> COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" );
static ThemeMetric<bool> LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" ); static ThemeMetric<bool> LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" );
static ThemeMetric<bool> SPEED_IS_LEFT_SIDE ( "NoteField", "SpeedIsLeftSide" );
static ThemeMetric<bool> FAKE_IS_LEFT_SIDE ( "NoteField", "FakeIsLeftSide" );
static ThemeMetric<float> BPM_OFFSETX ( "NoteField", "BPMOffsetX" ); static ThemeMetric<float> BPM_OFFSETX ( "NoteField", "BPMOffsetX" );
static ThemeMetric<float> STOP_OFFSETX ( "NoteField", "StopOffsetX" ); static ThemeMetric<float> STOP_OFFSETX ( "NoteField", "StopOffsetX" );
static ThemeMetric<float> DELAY_OFFSETX ( "NoteField", "DelayOffsetX" ); static ThemeMetric<float> DELAY_OFFSETX ( "NoteField", "DelayOffsetX" );
@@ -443,6 +474,8 @@ static ThemeMetric<float> TIME_SIGNATURE_OFFSETX ( "NoteField", "TimeSignatureOf
static ThemeMetric<float> TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" ); static ThemeMetric<float> TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" );
static ThemeMetric<float> COMBO_OFFSETX ( "NoteField", "ComboOffsetX" ); static ThemeMetric<float> COMBO_OFFSETX ( "NoteField", "ComboOffsetX" );
static ThemeMetric<float> LABEL_OFFSETX ( "NoteField", "LabelOffsetX" ); static ThemeMetric<float> LABEL_OFFSETX ( "NoteField", "LabelOffsetX" );
static ThemeMetric<float> SPEED_OFFSETX ( "NoteField", "SpeedOffsetX" );
static ThemeMetric<float> FAKE_OFFSETX ( "NoteField", "FakeOffsetX" );
void NoteField::DrawBPMText( const float fBeat, const float fBPM ) void NoteField::DrawBPMText( const float fBeat, const float fBPM )
{ {
@@ -572,6 +605,40 @@ void NoteField::DrawLabelText( const float fBeat, RString sLabel )
m_textMeasureNumber.Draw(); m_textMeasureNumber.Draw();
} }
void NoteField::DrawSpeedText( const float fBeat, float fPercent, float fWait, unsigned short usMode )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
const float xBase = GetWidth()/2.f;
const float xOffset = SPEED_OFFSETX * fZoom;
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( SPEED_IS_LEFT_SIDE ? align_right : align_left );
m_textMeasureNumber.SetDiffuse( SPEED_COLOR );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%.3f\n%s\n%.3f", fPercent, (usMode == 1 ? "S" : "B"), fWait) );
m_textMeasureNumber.SetXY( (SPEED_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
m_textMeasureNumber.Draw();
}
void NoteField::DrawFakeText( const float fBeat, const float fNewBeat )
{
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
const float fZoom = ArrowEffects::GetZoom( m_pPlayerState );
const float xBase = GetWidth()/2.f;
const float xOffset = FAKE_OFFSETX * fZoom;
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( FAKE_IS_LEFT_SIDE ? align_right : align_left );
m_textMeasureNumber.SetDiffuse( FAKE_COLOR );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%.3f", fNewBeat) );
m_textMeasureNumber.SetXY( (FAKE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos );
m_textMeasureNumber.Draw();
}
void NoteField::DrawAttackText( const float fBeat, const Attack &attack ) void NoteField::DrawAttackText( const float fBeat, const Attack &attack )
{ {
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
@@ -606,7 +673,7 @@ void NoteField::DrawBGChangeText( const float fBeat, const RString sNewBGName )
// change this probing to binary search // change this probing to binary search
float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels ) float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels )
{ {
float fFirstBeatToDraw = GAMESTATE->m_fSongBeat-4; // Adjust to balance off performance and showing enough notes. float fFirstBeatToDraw = GetDisplayedPosition(pPlayerState)->m_fSongBeat-4; // Adjust to balance off performance and showing enough notes.
/* In Boomerang, we'll usually have two sections of notes: before and after /* In Boomerang, we'll usually have two sections of notes: before and after
* the peak. We always start drawing before the peak, and end after it, or * the peak. We always start drawing before the peak, and end after it, or
@@ -618,7 +685,7 @@ float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistance
bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0); bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0);
} }
while( fFirstBeatToDraw < GAMESTATE->m_fSongBeat ) while( fFirstBeatToDraw < GetDisplayedPosition(pPlayerState)->m_fSongBeat )
{ {
bool bIsPastPeakYOffset; bool bIsPastPeakYOffset;
float fPeakYOffset; float fPeakYOffset;
@@ -640,7 +707,7 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB
// Probe for last note to draw. Worst case is 0.25x + boost. // Probe for last note to draw. Worst case is 0.25x + boost.
// Adjust search distance so that notes don't pop onto the screen. // Adjust search distance so that notes don't pop onto the screen.
float fSearchDistance = 10; float fSearchDistance = 10;
float fLastBeatToDraw = GAMESTATE->m_fSongBeat+fSearchDistance; float fLastBeatToDraw = GetDisplayedPosition(pPlayerState)->m_fSongBeat+fSearchDistance;
const int NUM_ITERATIONS = 20; const int NUM_ITERATIONS = 20;
@@ -669,6 +736,19 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB
return fLastBeatToDraw; return fLastBeatToDraw;
} }
inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow )
{
/*
if( GAMESTATE->m_bIsUsingStepTiming )
{
*/
return NoteRowToBeat(iRow);
/*
}
return GetDisplayedTiming(pPlayerState)->GetBeatFromElapsedTime(GetRealTiming(pPlayerState)->GetElapsedTimeFromBeat(NoteRowToBeat(iRow)));
*/
}
bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const
{ {
// TRICKY: If boomerang is on, then ones in the range // TRICKY: If boomerang is on, then ones in the range
@@ -739,10 +819,13 @@ void NoteField::DrawPrimitives()
cur->m_ReceptorArrowRow.Draw(); cur->m_ReceptorArrowRow.Draw();
} }
const TimingData *pTiming = GetDisplayedTiming(m_pPlayerState);
// Draw beat bars // Draw beat bars
if( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) if( ( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) && pTiming != NULL )
{ {
const vector<TimeSignatureSegment> &vTimeSignatureSegments = GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments; const TimingData &timing = *pTiming;
const vector<TimeSignatureSegment> &vTimeSignatureSegments = timing.m_vTimeSignatureSegments;
int iMeasureIndex = 0; int iMeasureIndex = 0;
FOREACH_CONST( TimeSignatureSegment, vTimeSignatureSegments, iter ) FOREACH_CONST( TimeSignatureSegment, vTimeSignatureSegments, iter )
{ {
@@ -781,12 +864,14 @@ void NoteField::DrawPrimitives()
} }
} }
if( GAMESTATE->IsEditing() ) if( GAMESTATE->IsEditing() && pTiming != NULL )
{ {
ASSERT(GAMESTATE->m_pCurSong); ASSERT(GAMESTATE->m_pCurSong);
const TimingData &timing = *pTiming;
// BPM text // BPM text
FOREACH_CONST( BPMSegment, GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments, seg ) FOREACH_CONST( BPMSegment, timing.m_BPMSegments, seg )
{ {
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw ) if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{ {
@@ -797,7 +882,7 @@ void NoteField::DrawPrimitives()
} }
// Freeze text // Freeze text
FOREACH_CONST( StopSegment, GAMESTATE->m_pCurSong->m_Timing.m_StopSegments, seg ) FOREACH_CONST( StopSegment, timing.m_StopSegments, seg )
{ {
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw ) if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{ {
@@ -808,18 +893,21 @@ void NoteField::DrawPrimitives()
} }
// Warp text // Warp text
FOREACH_CONST( WarpSegment, GAMESTATE->m_pCurSong->m_Timing.m_WarpSegments, seg ) if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, seg )
{ {
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw ) if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{ {
float fBeat = NoteRowToBeat(seg->m_iStartRow); float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) ) if( IS_ON_SCREEN(fBeat) )
DrawWarpText( fBeat, seg->m_fEndBeat ); DrawWarpText( fBeat, seg->m_fLengthBeats );
}
} }
} }
// Time Signature text // Time Signature text
FOREACH_CONST( TimeSignatureSegment, GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments, seg ) FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, seg )
{ {
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw ) if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{ {
@@ -829,8 +917,10 @@ void NoteField::DrawPrimitives()
} }
} }
if( GAMESTATE->m_bIsUsingStepTiming )
{
// Tickcount text // Tickcount text
FOREACH_CONST( TickcountSegment, GAMESTATE->m_pCurSong->m_Timing.m_TickcountSegments, seg ) FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, seg )
{ {
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw ) if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{ {
@@ -839,9 +929,12 @@ void NoteField::DrawPrimitives()
DrawTickcountText( fBeat, seg->m_iTicks ); DrawTickcountText( fBeat, seg->m_iTicks );
} }
} }
}
if( GAMESTATE->m_bIsUsingStepTiming )
{
// Combo text // Combo text
FOREACH_CONST( ComboSegment, GAMESTATE->m_pCurSong->m_Timing.m_ComboSegments, seg ) FOREACH_CONST( ComboSegment, timing.m_ComboSegments, seg )
{ {
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw ) if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{ {
@@ -850,9 +943,10 @@ void NoteField::DrawPrimitives()
DrawComboText( fBeat, seg->m_iCombo ); DrawComboText( fBeat, seg->m_iCombo );
} }
} }
}
// Label text // Label text
FOREACH_CONST( LabelSegment, GAMESTATE->m_pCurSong->m_Timing.m_LabelSegments, seg ) FOREACH_CONST( LabelSegment, timing.m_LabelSegments, seg )
{ {
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw ) if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{ {
@@ -862,6 +956,33 @@ void NoteField::DrawPrimitives()
} }
} }
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( SpeedSegment, timing.m_SpeedSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawSpeedText( fBeat, seg->m_fPercent, seg->m_fWait, seg->m_usMode );
}
}
}
// Speed text
if( GAMESTATE->m_bIsUsingStepTiming )
{
FOREACH_CONST( FakeSegment, timing.m_FakeSegments, seg )
{
if( seg->m_iStartRow >= iFirstRowToDraw && seg->m_iStartRow <= iLastRowToDraw )
{
float fBeat = NoteRowToBeat(seg->m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawFakeText( fBeat, seg->m_fLengthBeats );
}
}
}
// Course mods text // Course mods text
const Course *pCourse = GAMESTATE->m_pCurCourse; const Course *pCourse = GAMESTATE->m_pCurCourse;
if( pCourse ) if( pCourse )
@@ -872,7 +993,7 @@ void NoteField::DrawPrimitives()
FOREACH_CONST( Attack, ce.attacks, a ) FOREACH_CONST( Attack, ce.attacks, a )
{ {
float fSecond = a->fStartSecond; float fSecond = a->fStartSecond;
float fBeat = GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( fSecond ); float fBeat = timing.GetBeatFromElapsedTime( fSecond );
if( BeatToNoteRow(fBeat) >= iFirstRowToDraw && if( BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
BeatToNoteRow(fBeat) <= iLastRowToDraw) BeatToNoteRow(fBeat) <= iLastRowToDraw)
@@ -883,6 +1004,8 @@ void NoteField::DrawPrimitives()
} }
} }
if( !GAMESTATE->m_bIsUsingStepTiming )
{
// BGChange text // BGChange text
switch( GAMESTATE->m_EditMode ) switch( GAMESTATE->m_EditMode )
{ {
@@ -948,6 +1071,7 @@ void NoteField::DrawPrimitives()
default: default:
ASSERT(0); ASSERT(0);
} }
}
// Draw marker bars // Draw marker bars
if( m_iBeginMarker != -1 && m_iEndMarker != -1 ) if( m_iBeginMarker != -1 && m_iEndMarker != -1 )
@@ -1012,8 +1136,8 @@ void NoteField::DrawPrimitives()
float fThrowAway; float fThrowAway;
bool bStartIsPastPeak = false; bool bStartIsPastPeak = false;
bool bEndIsPastPeak = false; bool bEndIsPastPeak = false;
float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iStartRow), fThrowAway, bStartIsPastPeak ); float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iStartRow), fThrowAway, bStartIsPastPeak );
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iEndRow), fThrowAway, bEndIsPastPeak ); float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iEndRow), fThrowAway, bEndIsPastPeak );
bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels; bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels;
bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels; bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels;
@@ -1043,7 +1167,7 @@ void NoteField::DrawPrimitives()
displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT ); m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT );
bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > GAMESTATE->m_fSongBeat; bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bAnyUpcomingInThisCol |= bNoteIsUpcoming; bAnyUpcomingInThisCol |= bNoteIsUpcoming;
} }
} }
@@ -1085,7 +1209,7 @@ void NoteField::DrawPrimitives()
continue; // skip continue; // skip
ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw, ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw,
iFirstRowToDraw, GAMESTATE->m_fSongBeat, GAMESTATE->m_fMusicSeconds) ); iFirstRowToDraw, GetDisplayedPosition(m_pPlayerState)->m_fSongBeat, GetDisplayedPosition(m_pPlayerState)->m_fMusicSeconds) );
// See if there is a hold step that begins on this index. // See if there is a hold step that begins on this index.
// Only do this if the noteskin cares. // Only do this if the noteskin cares.
@@ -1110,12 +1234,12 @@ void NoteField::DrawPrimitives()
bool bIsHopoPossible = (tn.bHopoPossible); bool bIsHopoPossible = (tn.bHopoPossible);
bool bUseAdditionColoring = bIsAddition || bIsHopoPossible; bool bUseAdditionColoring = bIsAddition || bIsHopoPossible;
NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn]; NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn];
displayCols->display[c].DrawTap( tn, c, NoteRowToBeat(q), bHoldNoteBeginsOnThisBeat, displayCols->display[c].DrawTap( tn, c, NoteRowToVisibleBeat(m_pPlayerState, q), bHoldNoteBeginsOnThisBeat,
bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels, m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels,
FADE_BEFORE_TARGETS_PERCENT ); FADE_BEFORE_TARGETS_PERCENT );
bool bNoteIsUpcoming = NoteRowToBeat(q) > GAMESTATE->m_fSongBeat; bool bNoteIsUpcoming = NoteRowToBeat(q) > GetDisplayedPosition(m_pPlayerState)->m_fSongBeat;
bAnyUpcomingInThisCol |= bNoteIsUpcoming; bAnyUpcomingInThisCol |= bNoteIsUpcoming;
} }
+4
View File
@@ -1,6 +1,8 @@
#ifndef NOTE_FIELD_H #ifndef NOTE_FIELD_H
#define NOTE_FIELD_H #define NOTE_FIELD_H
#include "TimingData.h"
#include "SongPosition.h"
#include "Sprite.h" #include "Sprite.h"
#include "ActorFrame.h" #include "ActorFrame.h"
#include "BitmapText.h" #include "BitmapText.h"
@@ -62,6 +64,8 @@ protected:
void DrawTickcountText( const float fBeat, int iTicks ); void DrawTickcountText( const float fBeat, int iTicks );
void DrawComboText( const float fBeat, int iCombo ); void DrawComboText( const float fBeat, int iCombo );
void DrawLabelText( const float fBeat, RString sLabel ); void DrawLabelText( const float fBeat, RString sLabel );
void DrawSpeedText( const float fBeat, float fPercent, float fWait, unsigned short usMode );
void DrawFakeText( const float fBeat, const float fNewBeat );
void DrawAttackText( const float fBeat, const Attack &attack ); void DrawAttackText( const float fBeat, const Attack &attack );
void DrawBGChangeText( const float fBeat, const RString sNewBGName ); void DrawBGChangeText( const float fBeat, const RString sNewBGName );
float GetWidth() const; float GetWidth() const;
-2
View File
@@ -43,11 +43,9 @@ bool NotesLoader::LoadFromDir( const RString &sPath, Song &out, set<RString> &Bl
SMLoader::GetApplicableFiles( sPath, list ); SMLoader::GetApplicableFiles( sPath, list );
if (!list.empty() ) if (!list.empty() )
return SMLoader::LoadFromDir( sPath, out ); return SMLoader::LoadFromDir( sPath, out );
#if defined(_MSC_VER) || defined(MACOSX)
SMALoader::GetApplicableFiles( sPath, list ); SMALoader::GetApplicableFiles( sPath, list );
if (!list.empty() ) if (!list.empty() )
return SMALoader::LoadFromDir( sPath, out ); return SMALoader::LoadFromDir( sPath, out );
#endif
DWILoader::GetApplicableFiles( sPath, list ); DWILoader::GetApplicableFiles( sPath, list );
if( !list.empty() ) if( !list.empty() )
return DWILoader::LoadFromDir( sPath, out, BlacklistedImages ); return DWILoader::LoadFromDir( sPath, out, BlacklistedImages );
+232 -178
View File
@@ -435,9 +435,121 @@ static void ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t
static const int BEATS_PER_MEASURE = 4; static const int BEATS_PER_MEASURE = 4;
static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE; static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE;
static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out, static bool SearchForKeysound( const RString &sPath, RString nDataOriginal, map<RString, int> &mapFilenameToKeysoundIndex, Song &out, int &outKeysoundIndex )
const MeasureToTimeSig_t &sigAdjustments, const map<RString,int> &idToKeySoundIndex )
{ {
// Search for memoized file names:
{
RString nDataToSearchFor = nDataOriginal;
nDataToSearchFor.MakeLower();
map<RString, int>::iterator it = mapFilenameToKeysoundIndex.find(nDataToSearchFor);
if (it != mapFilenameToKeysoundIndex.end()) {
outKeysoundIndex = it->second;
return true;
}
}
// FIXME: garbled song names seem to crash the app.
// this might not be the best place to put this code.
if( !utf8_is_valid(nDataOriginal) )
return false;
/* Due to bugs in some programs, many BMS files have a "WAV" extension
* on files in the BMS for files that actually have some other extension.
* Do a search. Don't do a wildcard search; if sData is "song.wav",
* we might also have "song.png", which we shouldn't match. */
RString nData = nDataOriginal;
if( !IsAFile(out.GetSongDir()+nData) )
{
const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
for( unsigned i = 0; exts[i] != NULL; ++i )
{
RString fn = SetExtension( nData, exts[i] );
if( IsAFile(out.GetSongDir()+fn) )
{
nData = fn;
break;
}
}
}
if( !IsAFile(out.GetSongDir()+nData) )
{
LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() );
return false;
}
// Let's again search for memoized file names (we got the normalized one!):
{
RString nDataToSearchFor = nData;
nDataToSearchFor.MakeLower();
map<RString, int>::iterator it = mapFilenameToKeysoundIndex.find(nDataToSearchFor);
if (it != mapFilenameToKeysoundIndex.end()) {
outKeysoundIndex = it->second;
{
RString nDataToAdd = nDataOriginal;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
return true;
}
}
// Now this is a new sample.
out.m_vsKeysoundFile.push_back( nData );
outKeysoundIndex = out.m_vsKeysoundFile.size() - 1;
{
RString nDataToAdd = nDataOriginal;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
{
RString nDataToAdd = nData;
nDataToAdd.MakeLower();
mapFilenameToKeysoundIndex[nDataToAdd] = outKeysoundIndex;
}
return true;
}
static bool SearchForKeysound( const RString &sPath, RString sNoteId, const NameToData_t &mapNameToData, map<RString, int> &mapIdToKeysoundIndex, map<RString, int> &mapFilenameToKeysoundIndex, Song &out, int &outKeysoundIndex )
{
sNoteId.MakeLower();
{
map<RString, int>::iterator it = mapIdToKeysoundIndex.find(sNoteId);
if (it != mapIdToKeysoundIndex.end())
{
outKeysoundIndex = it->second;
return outKeysoundIndex >= 0;
}
}
RString sTagToLookFor = ssprintf( "#wav%s", sNoteId.c_str() );
RString nDataOriginal;
if( !GetTagFromMap( mapNameToData, sTagToLookFor, nDataOriginal ) )
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
return false;
}
bool retval = SearchForKeysound(sPath, nDataOriginal, mapFilenameToKeysoundIndex, out, outKeysoundIndex);
mapIdToKeysoundIndex[sNoteId] = outKeysoundIndex;
return retval;
}
static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out, Song &outSong, map<RString, int> &mapFilenameToKeysoundIndex )
{
map<RString, int> mapIdToKeysoundIndex;
MeasureToTimeSig_t sigAdjustments;
LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() ); LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );
out.m_StepsType = StepsType_Invalid; out.m_StepsType = StepsType_Invalid;
@@ -453,11 +565,121 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
NoteData ndNotes; NoteData ndNotes;
ndNotes.SetNumTracks( NUM_BMS_TRACKS ); ndNotes.SetNumTracks( NUM_BMS_TRACKS );
// Read BPM
if( GetTagFromMap(mapNameToData, "#bpm", sData) )
{
const float fBPM = StringToFloat( sData );
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.m_Timing.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
}
}
/* Read time signatures. Note that these can differ across files in the same /* Read time signatures. Note that these can differ across files in the same
* song. */ * song. */
MeasureToTimeSig_t mapMeasureToTimeSig; MeasureToTimeSig_t mapMeasureToTimeSig;
ReadTimeSigs( mapNameToData, mapMeasureToTimeSig ); ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );
for( NameToData_t::const_iterator it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iBMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
RString nData = it->second;
int totalPairs = nData.size() / 2;
for( int i = 0; i < totalPairs; ++i )
{
RString sPair = nData.substr( i*2, 2 );
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
float fBeat = NoteRowToBeat( iRow );
int iVal = 0;
sscanf( sPair, "%x", &iVal );
if (sPair == "00")
{
continue;
}
switch( iBMSTrackNo )
{
case BMS_TRACK_BPM:
if( iVal > 0 )
{
out.m_Timing.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has an invalid BPM change at beat %f, BPM %d.",
fBeat, iVal );
}
break;
case BMS_TRACK_BPM_REF:
{
RString sTagToLookFor = ssprintf( "#bpm%s", sPair.c_str() );
RString sBPM;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
{
float fBPM = StringToFloat( sBPM );
out.m_Timing.SetBPMAtBeat( fBeat, fBPM );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
case BMS_TRACK_STOP:
{
if( iVal == 0 )
{
break;
}
RString sTagToLookFor = ssprintf( "#stop%02x", iVal );
RString sBeats;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{
// find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.m_Timing.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
}
else
{
LOG->UserLog( "Song file", sPath.c_str(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
}
}
}
// Now that we're done reading BPMs, factor out weird time signatures.
SetTimeSigAdjustments( mapMeasureToTimeSig, outSong, sigAdjustments );
int iHoldStarts[NUM_BMS_TRACKS]; int iHoldStarts[NUM_BMS_TRACKS];
TapNote iHoldHeads[NUM_BMS_TRACKS]; TapNote iHoldHeads[NUM_BMS_TRACKS];
@@ -487,10 +709,9 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
RString sNoteId = sNoteData.substr( i, 2 ); RString sNoteId = sNoteData.substr( i, 2 );
if( sNoteId != "00" ) if( sNoteId != "00" )
{ {
vTapNotes.push_back( TAP_ORIGINAL_TAP ); TapNote tn = TAP_ORIGINAL_TAP;
map<RString,int>::const_iterator rInt = idToKeySoundIndex.find( sNoteId ); SearchForKeysound( sPath, sNoteId, mapNameToData, mapIdToKeysoundIndex, mapFilenameToKeysoundIndex, outSong, tn.iKeysoundIndex );
if( rInt != idToKeySoundIndex.end() ) vTapNotes.push_back( tn );
vTapNotes.back().iKeysoundIndex = rInt->second;
} }
else else
{ {
@@ -781,7 +1002,7 @@ static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameTo
return true; return true;
} }
static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, MeasureToTimeSig_t &sigAdjustmentsOut, map<RString,int> &idToKeySoundIndexOut ) static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out )
{ {
RString sData; RString sData;
if( GetTagFromMap(mapNameToData, "#title", sData) ) if( GetTagFromMap(mapNameToData, "#title", sData) )
@@ -792,172 +1013,6 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile ); GetTagFromMap( mapNameToData, "#backbmp", out.m_sBackgroundFile );
GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile ); GetTagFromMap( mapNameToData, "#wav", out.m_sMusicFile );
if( GetTagFromMap(mapNameToData, "#bpm", sData) )
{
const float fBPM = StringToFloat( sData );
if( PREFSMAN->m_bQuirksMode )
{
BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg );
if( fBPM > 0.0f )
LOG->Trace( "Inserting new positive BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
else
LOG->Trace( "Inserting new negative BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
if( fBPM > 0.0f )
{
BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM );
}
}
}
NameToData_t::const_iterator it;
for( it = mapNameToData.lower_bound("#wav"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName.Left(4) != "#wav" )
continue;
// this is keysound file name. Looks like "#WAV1A"
RString nData = it->second;
RString sWavID = sName.Right(2);
// FIXME: garbled song names seem to crash the app.
// this might not be the best place to put this code.
if( !utf8_is_valid(nData) )
continue;
/* Due to bugs in some programs, many BMS files have a "WAV" extension
* on files in the BMS for files that actually have some other extension.
* Do a search. Don't do a wildcard search; if sData is "song.wav",
* we might also have "song.png", which we shouldn't match. */
if( !IsAFile(out.GetSongDir()+nData) )
{
const char *exts[] = { "oga", "ogg", "wav", "mp3", NULL }; // XXX: stop duplicating these everywhere
for( unsigned i = 0; exts[i] != NULL; ++i )
{
RString fn = SetExtension( nData, exts[i] );
if( IsAFile(out.GetSongDir()+fn) )
{
nData = fn;
break;
}
}
}
if( !IsAFile(out.GetSongDir()+nData) )
LOG->UserLog( "Song file", out.GetSongDir(), "references key \"%s\" that can't be found", nData.c_str() );
sWavID.MakeUpper(); // HACK: undo the MakeLower()
out.m_vsKeysoundFile.push_back( nData );
idToKeySoundIndexOut[ sWavID ] = out.m_vsKeysoundFile.size()-1;
LOG->Trace( "Inserting keysound index %u '%s'", unsigned(out.m_vsKeysoundFile.size()-1), sWavID.c_str() );
}
// Time signature tags affect all other global timing tags, so read them first.
MeasureToTimeSig_t mapMeasureToTimeSig;
ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );
for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
{
const RString &sName = it->first;
if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1,5) ) )
continue;
// this is step or offset data. Looks like "#00705"
int iMeasureNo = atoi( sName.substr(1, 3).c_str() );
int iBMSTrackNo = atoi( sName.substr(4, 2).c_str() );
int iStepIndex = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut );
float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustmentsOut );
int iRowsPerMeasure = BeatToNoteRow( fBeatsPerMeasure );
RString nData = it->second;
int totalPairs = nData.size() / 2;
for( int i = 0; i < totalPairs; ++i )
{
RString sPair = nData.substr( i*2, 2 );
int iRow = iStepIndex + (i * iRowsPerMeasure) / totalPairs;
float fBeat = NoteRowToBeat( iRow );
int iVal = 0;
sscanf( sPair, "%x", &iVal );
if (sPair == "00")
{
continue;
}
switch( iBMSTrackNo )
{
case BMS_TRACK_BPM:
if( iVal > 0 )
{
out.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has an invalid BPM change at beat %f, BPM %d.",
fBeat, iVal );
}
break;
case BMS_TRACK_BPM_REF:
{
RString sTagToLookFor = ssprintf( "#bpm%s", sPair.c_str() );
RString sBPM;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBPM ) )
{
float fBPM = StringToFloat( sBPM );
out.SetBPMAtBeat( fBeat, fBPM );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
case BMS_TRACK_STOP:
{
if( iVal == 0 )
{
break;
}
RString sTagToLookFor = ssprintf( "#stop%02x", iVal );
RString sBeats;
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{
// find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
}
else
{
LOG->UserLog( "Song file", out.GetSongDir(), "has tag \"%s\" which cannot be found.", sTagToLookFor.c_str() );
}
break;
}
}
}
}
// Now that we're done reading BPMs, factor out weird time signatures.
SetTimeSigAdjustments( mapMeasureToTimeSig, out, sigAdjustmentsOut );
} }
static void SlideDuplicateDifficulties( Song &p ) static void SlideDuplicateDifficulties( Song &p )
@@ -1031,7 +1086,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
// Create a Steps for each. // Create a Steps for each.
vector<Steps*> apSteps; vector<Steps*> apSteps;
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ ) for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
apSteps.push_back( new Steps ); apSteps.push_back( out.CreateSteps() );
// Now, with our fancy little substring, trim the titles and // Now, with our fancy little substring, trim the titles and
// figure out where each goes. // figure out where each goes.
@@ -1100,9 +1155,7 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
if( apSteps[i]->GetDifficulty() == Difficulty_Medium ) if( apSteps[i]->GetDifficulty() == Difficulty_Medium )
iMainDataIndex = i; iMainDataIndex = i;
MeasureToTimeSig_t sigAdjustments; ReadGlobalTags( aBMSData[iMainDataIndex], out );
map<RString,int> idToKeysoundIndex;
ReadGlobalTags( aBMSData[iMainDataIndex], out, sigAdjustments, idToKeysoundIndex );
// The brackets before the difficulty are in common substring, so remove them if it's found. // The brackets before the difficulty are in common substring, so remove them if it's found.
if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' ) if( commonSubstring.size() > 2 && commonSubstring[commonSubstring.size() - 2] == ' ' )
@@ -1124,10 +1177,11 @@ bool BMSLoader::LoadFromDir( const RString &sDir, Song &out )
// Now that we've parsed the keysound data, load the Steps from the rest // Now that we've parsed the keysound data, load the Steps from the rest
// of the .bms files. // of the .bms files.
map<RString, int> mapFilenameToKeysoundIndex;
for( unsigned i=0; i<arrayBMSFileNames.size(); i++ ) for( unsigned i=0; i<arrayBMSFileNames.size(); i++ )
{ {
Steps* pNewNotes = apSteps[i]; Steps* pNewNotes = apSteps[i];
const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes, sigAdjustments, idToKeysoundIndex ); const bool ok = LoadFromBMSFile( out.GetSongDir() + arrayBMSFileNames[i], aBMSData[i], *pNewNotes, out, mapFilenameToKeysoundIndex );
if( ok ) if( ok )
out.AddSteps( pNewNotes ); out.AddSteps( pNewNotes );
else else
+7 -7
View File
@@ -509,11 +509,11 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
if( PREFSMAN->m_bQuirksMode ) if( PREFSMAN->m_bQuirksMode )
{ {
out.AddBPMSegment( BPMSegment(0, fBPM) ); out.m_SongTiming.AddBPMSegment( BPMSegment(0, fBPM) );
} }
else{ else{
if( fBPM > 0.0f ) if( fBPM > 0.0f )
out.AddBPMSegment( BPMSegment(0, fBPM) ); out.m_SongTiming.AddBPMSegment( BPMSegment(0, fBPM) );
else else
LOG->UserLog( "Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.", LOG->UserLog( "Song file", sPath, "has an invalid BPM change at beat %f, BPM %f.",
NoteRowToBeat(0), fBPM ); NoteRowToBeat(0), fBPM );
@@ -545,7 +545,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
else if( sValueName.EqualsNoCase("GAP") ) else if( sValueName.EqualsNoCase("GAP") )
// the units of GAP is 1/1000 second // the units of GAP is 1/1000 second
out.m_Timing.m_fBeat0OffsetInSeconds = -StringToInt( sParams[1] ) / 1000.0f; out.m_SongTiming.m_fBeat0OffsetInSeconds = -StringToInt( sParams[1] ) / 1000.0f;
else if( sValueName.EqualsNoCase("SAMPLESTART") ) else if( sValueName.EqualsNoCase("SAMPLESTART") )
out.m_fMusicSampleStartSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]); out.m_fMusicSampleStartSeconds = ParseBrokenDWITimestamp(sParams[1], sParams[2], sParams[3]);
@@ -570,7 +570,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
int iFreezeRow = BeatToNoteRow( StringToFloat(arrayFreezeValues[0]) / 4.0f ); int iFreezeRow = BeatToNoteRow( StringToFloat(arrayFreezeValues[0]) / 4.0f );
float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ) / 1000.0f; float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] ) / 1000.0f;
out.AddStopSegment( StopSegment(iFreezeRow, fFreezeSeconds) ); out.m_SongTiming.AddStopSegment( StopSegment(iFreezeRow, fFreezeSeconds) );
// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", fFreezeBeat, fFreezeSeconds ); // LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", fFreezeBeat, fFreezeSeconds );
} }
} }
@@ -595,7 +595,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
if( fBPM > 0.0f ) if( fBPM > 0.0f )
{ {
BPMSegment bs( iStartIndex, fBPM ); BPMSegment bs( iStartIndex, fBPM );
out.AddBPMSegment( bs ); out.m_SongTiming.AddBPMSegment( bs );
} }
else else
{ {
@@ -610,7 +610,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
sValueName.EqualsNoCase("COUPLE") || sValueName.EqualsNoCase("COUPLE") ||
sValueName.EqualsNoCase("SOLO") ) sValueName.EqualsNoCase("SOLO") )
{ {
Steps* pNewNotes = new Steps; Steps* pNewNotes = out.CreateSteps();
LoadFromDWITokens( LoadFromDWITokens(
sParams[0], sParams[0],
sParams[1], sParams[1],
@@ -656,7 +656,7 @@ bool DWILoader::LoadFromDir( const RString &sPath_, Song &out, set<RString> &Bla
// do nothing. We don't care about this value name // do nothing. We don't care about this value name
} }
} }
out.TidyUpData();
return true; return true;
} }
+2 -2
View File
@@ -139,7 +139,7 @@ static void Deserialize( Song &out, const Json::Value &root )
out.m_sLyricsFile = root["LyricsFile"].asString(); out.m_sLyricsFile = root["LyricsFile"].asString();
out.m_sCDTitleFile = root["CDTitle"].asString(); out.m_sCDTitleFile = root["CDTitle"].asString();
out.m_sMusicFile = root["Music"].asString(); out.m_sMusicFile = root["Music"].asString();
out.m_Timing.m_fBeat0OffsetInSeconds = (float)root["Offset"].asDouble(); out.m_SongTiming.m_fBeat0OffsetInSeconds = (float)root["Offset"].asDouble();
out.m_fMusicSampleStartSeconds = (float)root["SampleStart"].asDouble(); out.m_fMusicSampleStartSeconds = (float)root["SampleStart"].asDouble();
out.m_fMusicSampleLengthSeconds = (float)root["SampleLength"].asDouble(); out.m_fMusicSampleLengthSeconds = (float)root["SampleLength"].asDouble();
RString sSelectable = root["Selectable"].asString(); RString sSelectable = root["Selectable"].asString();
@@ -167,7 +167,7 @@ static void Deserialize( Song &out, const Json::Value &root )
out.m_fSpecifiedBPMMax = (float)root["SpecifiedBpmMax"].asDouble(); out.m_fSpecifiedBPMMax = (float)root["SpecifiedBpmMax"].asDouble();
} }
Deserialize( out.m_Timing, root["TimingData"] ); Deserialize( out.m_SongTiming, root["TimingData"] );
JsonUtil::DeserializeVectorObjects( out.m_LyricSegments, Deserialize, root["LyricSegments"] ); JsonUtil::DeserializeVectorObjects( out.m_LyricSegments, Deserialize, root["LyricSegments"] );
{ {
+183 -78
View File
@@ -9,6 +9,51 @@
#include "Song.h" #include "Song.h"
#include "Steps.h" #include "Steps.h"
static void HandleBunki( TimingData &timing, const float fEarlyBPM,
const float fCurBPM, const float fGap,
const float fPos )
{
const float BeatsPerSecond = fEarlyBPM / 60.0f;
const float beat = (fPos + fGap) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos %f, beat %f",
fEarlyBPM, BeatsPerSecond, fPos, beat );
timing.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), fCurBPM) );
}
static bool HandlePipeChars( TimingData &timing, const RString sNoteRow,
const float fCurBeat, int &iTickCount )
{
RString temp = sNoteRow.substr(2,sNoteRow.size()-3);
float numTemp = StringToFloat(temp);
if (BeginsWith(sNoteRow, "|T"))
{
iTickCount = static_cast<int>(numTemp);
timing.SetTickcountAtBeat( fCurBeat, clamp(iTickCount, 0, ROWS_PER_BEAT) );
return true;
}
else if (BeginsWith(sNoteRow, "|B"))
{
timing.SetBPMAtBeat( fCurBeat, numTemp );
return true;
}
else if (BeginsWith(sNoteRow, "|E"))
{
// Finally! the |E| tag is working as it should. I can die happy now -DaisuMaster
float fCurDelay = 60 / timing.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount;
fCurDelay += timing.GetDelayAtRow(BeatToNoteRow(fCurBeat) );
timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
return true;
}
else if (BeginsWith(sNoteRow, "|D"))
{
float fCurDelay = timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
fCurDelay += numTemp / 1000;
timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
return true;
}
return false;
}
static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song, bool bKIUCompliant ) static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song, bool bKIUCompliant )
{ {
LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() ); LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );
@@ -26,14 +71,96 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
// According to Aldo_MX, there is a default BPM and it's 60. -aj // According to Aldo_MX, there is a default BPM and it's 60. -aj
bool bDoublesChart = false; bool bDoublesChart = false;
TimingData stepsTiming;
float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1;
for( unsigned i=0; i<msd.GetNumValues(); i++ ) for( unsigned i=0; i<msd.GetNumValues(); i++ )
{ {
const MsdFile::value_t &sParams = msd.GetValue( i ); const MsdFile::value_t &sParams = msd.GetValue( i );
RString sValueName = sParams[0]; RString sValueName = sParams[0];
sValueName.MakeUpper(); sValueName.MakeUpper();
// handle the data /* handle the data...well, not this data: not related to steps.
if( sValueName=="TICKCOUNT" ) * Skips INTRO, MUSICINTRO, TITLEFILE, DISCFILE, SONGFILE. */
if (sValueName=="TITLE" || EndsWith(sValueName, "INTRO")
|| EndsWith(sValueName, "FILE") )
{
;
}
else if( sValueName=="BPM" )
{
BPM1 = StringToFloat(sParams[1]);
stepsTiming.AddBPMSegment( BPMSegment(0, BPM1) );
}
else if( sValueName=="BPM2" )
{
if (bKIUCompliant)
{
BPM2 = StringToFloat( sParams[1] );
}
else
{
// LOG an error.
}
}
else if( sValueName=="BPM3" )
{
if (bKIUCompliant)
{
BPM3 = StringToFloat( sParams[1] );
}
else
{
// LOG an error.
}
}
else if( sValueName=="BUNKI" )
{
if (bKIUCompliant)
{
BPMPos2 = StringToFloat( sParams[1] ) / 100.0f;
}
else
{
// LOG an error.
}
}
else if( sValueName=="BUNKI2" )
{
if (bKIUCompliant)
{
BPMPos3 = StringToFloat( sParams[1] ) / 100.0f;
}
else
{
// LOG an error.
}
}
else if( sValueName=="STARTTIME" )
{
SMGap1 = -StringToFloat( sParams[1] )/100;
stepsTiming.m_fBeat0OffsetInSeconds = SMGap1;
}
// This is currently required for more accurate KIU BPM changes.
else if( sValueName=="STARTTIME2" )
{
if (bKIUCompliant)
{
SMGap2 = -StringToFloat( sParams[1] )/100;
}
else
{
// LOG an error.
}
}
else if ( sValueName=="STARTTIME3" )
{
// STARTTIME3 only ensures this is a KIU compliant simfile.
bKIUCompliant = true;
}
else if( sValueName=="TICKCOUNT" )
{ {
iTickCount = StringToInt( sParams[1] ); iTickCount = StringToInt( sParams[1] );
if( iTickCount <= 0 ) if( iTickCount <= 0 )
@@ -41,13 +168,9 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
LOG->UserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount ); LOG->UserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount );
return false; return false;
} }
stepsTiming.AddTickcountSegment(TickcountSegment(0, iTickCount));
} }
else if( sValueName=="STEP" )
{
RString theSteps = sParams[1];
TrimLeft( theSteps );
split( theSteps, "\n", vNoteRows, true );
}
else if( sValueName=="DIFFICULTY" ) else if( sValueName=="DIFFICULTY" )
{ {
out.SetMeter( max(StringToInt(sParams[1]), 0) ); out.SetMeter( max(StringToInt(sParams[1]), 0) );
@@ -60,14 +183,35 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
if( sPlayer.find( "double" ) != string::npos ) if( sPlayer.find( "double" ) != string::npos )
bDoublesChart = true; bDoublesChart = true;
} }
// This should always be last.
else if( sValueName=="STEP" )
{
RString theSteps = sParams[1];
TrimLeft( theSteps );
split( theSteps, "\n", vNoteRows, true );
}
} }
if( iTickCount == -1 ) if( iTickCount == -1 )
{ {
iTickCount = 2; // Direct Move 0.5 has a default value of 4... -aj iTickCount = 4;
LOG->UserLog( "Song file", sPath, "doesn't have a TICKCOUNT. Defaulting to %i.", iTickCount ); LOG->UserLog( "Song file", sPath, "doesn't have a TICKCOUNT. Defaulting to %i.", iTickCount );
} }
// Prepare BPM stuff already if the file uses KSF syntax.
if( bKIUCompliant )
{
if( BPM2 > 0 && BPMPos2 > 0 )
{
HandleBunki( stepsTiming, BPM1, BPM2, SMGap1, BPMPos2 );
}
if( BPM3 > 0 && BPMPos3 > 0 )
{
HandleBunki( stepsTiming, BPM2, BPM3, SMGap2, BPMPos3 );
}
}
NoteData notedata; // read it into here NoteData notedata; // read it into here
{ {
@@ -133,6 +277,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
case StepsType_pump_single: notedata.SetNumTracks( 5 ); break; case StepsType_pump_single: notedata.SetNumTracks( 5 ); break;
case StepsType_pump_couple: notedata.SetNumTracks( 10 ); break; case StepsType_pump_couple: notedata.SetNumTracks( 10 ); break;
case StepsType_pump_double: notedata.SetNumTracks( 10 ); break; case StepsType_pump_double: notedata.SetNumTracks( 10 ); break;
case StepsType_pump_routine: notedata.SetNumTracks( 10 ); break; // future files may have this?
case StepsType_pump_halfdouble: notedata.SetNumTracks( 6 ); break; case StepsType_pump_halfdouble: notedata.SetNumTracks( 6 ); break;
default: FAIL_M( ssprintf("%i", out.m_StepsType) ); default: FAIL_M( ssprintf("%i", out.m_StepsType) );
} }
@@ -146,6 +291,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
int newTick = -1; int newTick = -1;
float fCurBeat = 0.0f; float fCurBeat = 0.0f;
float prevBeat = 0.0f; // Used for hold tails. float prevBeat = 0.0f; // Used for hold tails.
for( unsigned r=0; r<vNoteRows.size(); r++ ) for( unsigned r=0; r<vNoteRows.size(); r++ )
{ {
RString& sRowString = vNoteRows[r]; RString& sRowString = vNoteRows[r];
@@ -155,7 +301,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
continue; // skip continue; // skip
// All 2s indicates the end of the song. // All 2s indicates the end of the song.
if( sRowString == "2222222222222" ) else if( sRowString == "2222222222222" )
{ {
// Finish any holds that didn't get...well, finished. // Finish any holds that didn't get...well, finished.
for( t=0; t < notedata.GetNumTracks(); t++ ) for( t=0; t < notedata.GetNumTracks(); t++ )
@@ -171,29 +317,21 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
break; break;
} }
// Why do this? Rows made with precise DM05 tags can go up to 13 too -DaisuMaster else if( BeginsWith(sRowString, "|") )
//if( sRowString.size() != 13 )
//this is wrong in many ways...
/*if( bKIUCompliant )
{ {
LOG->UserLog( "Song file", sPath, "has illegal syntax \"%s\" which can't be in KIU complient files.", if (bKIUCompliant)
sRowString.c_str() );
return false;
//In other words: you can't mix ksf's with DM05 tags and ksf's without any DM05 tags
//Either one set or another will be read...
}*/
if( BeginsWith(sRowString, "|B") || BeginsWith(sRowString, "|D") || BeginsWith(sRowString, "|E") )
{ {
// These don't have to be worried about here: the changes and stops were already added. // Log an error, ignore the line.
continue; continue;
} }
else if ( BeginsWith(sRowString, "|T") ) if ( !HandlePipeChars( stepsTiming, sRowString, fCurBeat, iTickCount ) )
{ {
RString temp = sRowString.substr(2,sRowString.size()-3); // LOG it first.
newTick = StringToInt(temp);
bTickChangeNeeded = true;
continue;
} }
continue;
}
else else
{ {
// Is this why improper ksf or some kiucompilant ksf mixed with dm05 ksf are ignored?? -DaisuMaster // Is this why improper ksf or some kiucompilant ksf mixed with dm05 ksf are ignored?? -DaisuMaster
@@ -270,6 +408,7 @@ static bool LoadFromKSFFile( const RString &sPath, Steps &out, const Song &song,
} }
out.SetNoteData( notedata ); out.SetNoteData( notedata );
out.m_Timing = stepsTiming;
out.TidyUpData(); out.TidyUpData();
@@ -358,7 +497,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
else if( sValueName=="BPM" ) else if( sValueName=="BPM" )
{ {
BPM1 = StringToFloat(sParams[1]); BPM1 = StringToFloat(sParams[1]);
out.AddBPMSegment( BPMSegment(0, BPM1) ); out.m_SongTiming.AddBPMSegment( BPMSegment(0, BPM1) );
} }
else if( sValueName=="BPM2" ) else if( sValueName=="BPM2" )
{ {
@@ -383,7 +522,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
else if( sValueName=="STARTTIME" ) else if( sValueName=="STARTTIME" )
{ {
SMGap1 = -StringToFloat( sParams[1] )/100; SMGap1 = -StringToFloat( sParams[1] )/100;
out.m_Timing.m_fBeat0OffsetInSeconds = SMGap1; out.m_SongTiming.m_fBeat0OffsetInSeconds = SMGap1;
} }
// This is currently required for more accurate KIU BPM changes. // This is currently required for more accurate KIU BPM changes.
else if( sValueName=="STARTTIME2" ) else if( sValueName=="STARTTIME2" )
@@ -409,7 +548,7 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
TickcountSegment tcs; TickcountSegment tcs;
tcs.m_iStartRow = BeatToNoteRow(0.0f); tcs.m_iStartRow = BeatToNoteRow(0.0f);
tcs.m_iTicks = iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount; tcs.m_iTicks = iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount;
out.m_Timing.AddTickcountSegment( tcs ); out.m_SongTiming.AddTickcountSegment( tcs );
} }
else if ( sValueName=="STEP" ) else if ( sValueName=="STEP" )
{ {
@@ -465,21 +604,12 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
{ {
if( BPM2 > 0 && BPMPos2 > 0 ) if( BPM2 > 0 && BPMPos2 > 0 )
{ {
const float BeatsPerSecond = BPM1 / 60.0f; HandleBunki( out.m_SongTiming, BPM1, BPM2, SMGap1, BPMPos2 );
const float beat = (BPMPos2 + SMGap1) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos2 %f, beat %f",
BPM1, BeatsPerSecond, BPMPos2, beat );
out.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), BPM2) );
} }
if( BPM3 > 0 && BPMPos3 > 0 ) if( BPM3 > 0 && BPMPos3 > 0 )
{ {
const float BeatsPerSecond = BPM2 / 60.0f; HandleBunki( out.m_SongTiming, BPM2, BPM3, SMGap2, BPMPos3 );
//The line below isn't perfect, but works better than previous versions.
const float beat = (BPMPos3 + SMGap2) * BeatsPerSecond;
LOG->Trace( "BPM %f, BPS %f, BPMPos3 %f, beat %f",
BPM2, BeatsPerSecond, BPMPos3, beat );
out.AddBPMSegment( BPMSegment(BeatToNoteRow(beat), BPM3) );
} }
} }
else else
@@ -507,41 +637,12 @@ static bool LoadGlobalData( const RString &sPath, Song &out, bool &bKIUCompliant
BeginsWith(NoteRowString, "|D") || BeginsWith(NoteRowString, "|E") ) BeginsWith(NoteRowString, "|D") || BeginsWith(NoteRowString, "|E") )
{ {
bDMRequired = true; bDMRequired = true;
RString temp = NoteRowString.substr(2,NoteRowString.size()-3); if ( !HandlePipeChars( out.m_SongTiming, NoteRowString, fCurBeat, iTickCount ) )
float numTemp = StringToFloat(temp);
if (BeginsWith(NoteRowString, "|T"))
{ {
iTickCount = (int)numTemp; // LOG it first.
TickcountSegment tcs;
tcs.m_iStartRow = BeatToNoteRow(fCurBeat);
tcs.m_iTicks = iTickCount > ROWS_PER_BEAT ? ROWS_PER_BEAT : iTickCount;
out.m_Timing.AddTickcountSegment( tcs );
continue;
} }
else if (BeginsWith(NoteRowString, "|B"))
{
float fCurBpm = (float)numTemp;
//out.m_Timing.AddBPMSegment( BPMSegment( BeatToNoteRow(fCurBeat), (float)numTemp ) );
out.m_Timing.SetBPMAtBeat( fCurBeat, fCurBpm );
continue; continue;
} }
else if (BeginsWith(NoteRowString, "|E"))
{
// Finally! the |E| tag is working as it should. I can die happy now -DaisuMaster
float fCurDelay = 60 / out.m_Timing.GetBPMAtBeat(fCurBeat) * (float)numTemp / iTickCount;
fCurDelay += out.m_Timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
out.m_Timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
continue;
}
else if (BeginsWith(NoteRowString, "|D"))
{
float fCurDelay = out.m_Timing.GetStopAtRow(BeatToNoteRow(fCurBeat) );
fCurDelay += (float)numTemp / 1000;
out.m_Timing.SetStopAtBeat( fCurBeat, fCurDelay, true );
continue;
}
}
else else
{ {
// ignore whatever else... // ignore whatever else...
@@ -580,16 +681,19 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out )
ASSERT( arrayKSFFileNames.size() ); ASSERT( arrayKSFFileNames.size() );
bool bKIUCompliant = false; bool bKIUCompliant = false;
/* If only the first file is read, it will cause problems for other simfiles with /* With Split Timing, there has to be a backup Song Timing in case
* different BPM changes and tickcounts. This command will probably have to be * anything goes wrong. As these files are kept in alphabetical
* changed in the future. */ * order (hopefully), it is best to use the LAST file for timing
if( !LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[0], out, bKIUCompliant) ) * purposes, for that is the "normal", or easiest difficulty.
* Usually. */
unsigned files = arrayKSFFileNames.size();
if( !LoadGlobalData(out.GetSongDir() + arrayKSFFileNames[files - 1], out, bKIUCompliant) )
return false; return false;
// load the Steps from the rest of the KSF files // load the Steps from the rest of the KSF files
for( unsigned i=0; i<arrayKSFFileNames.size(); i++ ) for( unsigned i=0; i<files; i++ )
{ {
Steps* pNewNotes = new Steps; Steps* pNewNotes = out.CreateSteps();
if( !LoadFromKSFFile(out.GetSongDir() + arrayKSFFileNames[i], *pNewNotes, out, bKIUCompliant) ) if( !LoadFromKSFFile(out.GetSongDir() + arrayKSFFileNames[i], *pNewNotes, out, bKIUCompliant) )
{ {
delete pNewNotes; delete pNewNotes;
@@ -598,6 +702,7 @@ bool KSFLoader::LoadFromDir( const RString &sDir, Song &out )
out.AddSteps( pNewNotes ); out.AddSteps( pNewNotes );
} }
out.TidyUpData();
return true; return true;
} }
+4 -3
View File
@@ -682,7 +682,7 @@ static bool LoadFromMidi( const RString &sPath, Song &songOut )
double fSecondsPerBeat = (iter->tickSeconds * GUITAR_MIDI_COUNTS_PER_BEAT); double fSecondsPerBeat = (iter->tickSeconds * GUITAR_MIDI_COUNTS_PER_BEAT);
bpmSeg.m_fBPS = float( 1. / fSecondsPerBeat ); bpmSeg.m_fBPS = float( 1. / fSecondsPerBeat );
songOut.m_Timing.AddBPMSegment( bpmSeg ); songOut.m_SongTiming.AddBPMSegment( bpmSeg );
} }
FOREACH_CONST( MidiFileIn::TimeSignatureChange, midi.timeSignatureEvents_, iter ) FOREACH_CONST( MidiFileIn::TimeSignatureChange, midi.timeSignatureEvents_, iter )
@@ -692,7 +692,7 @@ static bool LoadFromMidi( const RString &sPath, Song &songOut )
seg.m_iNumerator = iter->numerator; seg.m_iNumerator = iter->numerator;
seg.m_iDenominator = iter->denominator; seg.m_iDenominator = iter->denominator;
songOut.m_Timing.AddTimeSignatureSegment( seg ); songOut.m_SongTiming.AddTimeSignatureSegment( seg );
} }
@@ -877,7 +877,7 @@ skip_track:
} }
} }
Steps *pSteps = new Steps; Steps *pSteps = songOut.CreateSteps();
pSteps->m_StepsType = StepsType_guitar_five; pSteps->m_StepsType = StepsType_guitar_five;
pSteps->SetDifficulty( (Difficulty)(gd+1) ); pSteps->SetDifficulty( (Difficulty)(gd+1) );
pSteps->SetNoteData( noteData ); pSteps->SetNoteData( noteData );
@@ -956,6 +956,7 @@ bool MidiLoader::LoadFromDir( const RString &sDir, Song &out )
if( !LoadFromMidi(sDir+vsFiles[0], out) ) if( !LoadFromMidi(sDir+vsFiles[0], out) )
return false; return false;
out.TidyUpData();
return true; return true;
} }
+8 -8
View File
@@ -591,7 +591,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f ) if( fBPM > 0.0f )
{ {
BPMSegment newSeg( 0, fBPM ); BPMSegment newSeg( 0, fBPM );
out.AddBPMSegment( newSeg ); out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM ); LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(0), fBPM );
} }
else else
@@ -673,7 +673,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
case PMS_TRACK_BPM: case PMS_TRACK_BPM:
if( iVal > 0 ) if( iVal > 0 )
{ {
out.SetBPMAtBeat( fBeat, (float) iVal ); out.m_SongTiming.SetBPMAtBeat( fBeat, (float) iVal );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal ); LOG->Trace( "Inserting new BPM change at beat %f, BPM %i", fBeat, iVal );
} }
else else
@@ -694,7 +694,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f ) if( fBPM > 0.0f )
{ {
BPMSegment newSeg( BeatToNoteRow(fBeat), fBPM ); BPMSegment newSeg( BeatToNoteRow(fBeat), fBPM );
out.AddBPMSegment( newSeg ); out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", fBeat, newSeg.GetBPM() ); LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", fBeat, newSeg.GetBPM() );
} }
else else
@@ -716,12 +716,12 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) ) if( GetTagFromMap( mapNameToData, sTagToLookFor, sBeats ) )
{ {
// find the BPM at the time of this freeze // find the BPM at the time of this freeze
float fBPS = out.m_Timing.GetBPMAtBeat(fBeat) / 60.0f; float fBPS = out.m_SongTiming.GetBPMAtBeat(fBeat) / 60.0f;
float fBeats = StringToFloat( sBeats ) / 48.0f; float fBeats = StringToFloat( sBeats ) / 48.0f;
float fFreezeSecs = fBeats / fBPS; float fFreezeSecs = fBeats / fBPS;
StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs ); StopSegment newSeg( BeatToNoteRow(fBeat), fFreezeSecs );
out.AddStopSegment( newSeg ); out.m_SongTiming.AddStopSegment( newSeg );
LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds ); LOG->Trace( "Inserting new Freeze at beat %f, secs %f", fBeat, newSeg.m_fStopSeconds );
} }
else else
@@ -750,7 +750,7 @@ static void ReadGlobalTags( const NameToData_t &mapNameToData, Song &out, Measur
if( fBPM > 0.0f ) if( fBPM > 0.0f )
{ {
BPMSegment newSeg( iStepIndex, fBPM ); BPMSegment newSeg( iStepIndex, fBPM );
out.AddBPMSegment( newSeg ); out.m_SongTiming.AddBPMSegment( newSeg );
LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(newSeg.m_iStartRow), newSeg.GetBPM() ); LOG->Trace( "Inserting new BPM change at beat %f, BPM %f", NoteRowToBeat(newSeg.m_iStartRow), newSeg.GetBPM() );
} }
@@ -843,7 +843,7 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
/* Create a Steps for each. */ /* Create a Steps for each. */
vector<Steps*> apSteps; vector<Steps*> apSteps;
for( unsigned i=0; i<arrayPMSFileNames.size(); i++ ) for( unsigned i=0; i<arrayPMSFileNames.size(); i++ )
apSteps.push_back( new Steps ); apSteps.push_back( out.CreateSteps() );
// Now, with our fancy little substring, trim the titles and // Now, with our fancy little substring, trim the titles and
// figure out where each goes. // figure out where each goes.
@@ -940,7 +940,7 @@ bool PMSLoader::LoadFromDir( const RString &sDir, Song &out )
ConvertString( out.m_sArtist, "utf-8,japanese" ); ConvertString( out.m_sArtist, "utf-8,japanese" );
ConvertString( out.m_sGenre, "utf-8,japanese" ); ConvertString( out.m_sGenre, "utf-8,japanese" );
out.TidyUpData();
return true; return true;
} }
+158 -137
View File
@@ -15,11 +15,6 @@
/** @brief The maximum file size for edits. */ /** @brief The maximum file size for edits. */
const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60KB const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60KB
/**
* @brief The highest allowable speed before Warps come in.
*
* This was brought in from StepMania 4's recent betas. */
const float FAST_BPM_WARP = 9999999.f;
void SMLoader::LoadFromSMTokens( void SMLoader::LoadFromSMTokens(
RString sStepsType, RString sStepsType,
@@ -103,33 +98,103 @@ bool SMLoader::LoadTimingFromFile( const RString &fn, TimingData &out )
return true; return true;
} }
void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out ) void SMLoader::ProcessBGChanges( Song &out, const RString &sValueName, const RString &sPath, const RString &sParam )
{ {
out.m_fBeat0OffsetInSeconds = 0; BackgroundLayer iLayer = BACKGROUND_LAYER_1;
out.m_BPMSegments.clear(); if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
out.m_StopSegments.clear(); enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
out.m_WarpSegments.clear();
out.m_vTimeSignatureSegments.clear();
for( unsigned i=0; i<msd.GetNumValues(); i++ ) bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{ {
const MsdFile::value_t &sParams = msd.GetValue(i); LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
RString sValueName = sParams[0];
sValueName.MakeUpper();
if( sValueName=="OFFSET" )
{
out.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
} }
else if( sValueName=="BPMS" ) else
{
vector<RString> aBGChangeExpressions;
split( sParam, ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
}
void SMLoader::ProcessAttacks( Song &out, MsdFile::value_t sParams )
{
// Build the RString vector here so we can write it to file again later
for( unsigned s=1; s < sParams.params.size(); ++s )
out.m_sAttackString.push_back( sParams[s] );
Attack attack;
float end = -9999;
for( unsigned j=1; j < sParams.params.size(); ++j )
{
vector<RString> sBits;
split( sParams[j], "=", sBits, false );
// Need an identifer and a value for this to work
if( sBits.size() < 2 )
continue;
TrimLeft( sBits[0] );
TrimRight( sBits[0] );
if( !sBits[0].CompareNoCase("TIME") )
attack.fStartSecond = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("LEN") )
attack.fSecsRemaining = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("END") )
end = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("MODS") )
{
attack.sModifiers = sBits[1];
if( end != -9999 )
{
attack.fSecsRemaining = end - attack.fStartSecond;
end = -9999;
}
if( attack.fSecsRemaining < 0.0f )
attack.fSecsRemaining = 0.0f;
out.m_Attacks.push_back( attack );
}
}
}
void SMLoader::ProcessInstrumentTracks( Song &out, const RString &sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
}
bool SMLoader::ProcessBPMs( TimingData &out, const RString sParam )
{ {
vector<RString> arrayBPMChangeExpressions; vector<RString> arrayBPMChangeExpressions;
split( sParams[1], ",", arrayBPMChangeExpressions ); split( sParam, ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps. // prepare storage variables for negative BPMs -> Warps.
float negBeat = -1; float negBeat = -1;
float negBPM = 1; float negBPM = 1;
float highspeedBeat = -1; float highspeedBeat = -1;
bool bNotEmpty = false;
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ ) for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{ {
@@ -138,11 +203,13 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
// XXX: Hard to tell which file caused this. // XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 ) if( arrayBPMChangeValues.size() != 2 )
{ {
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #BPMs value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() ); arrayBPMChangeExpressions[b].c_str() );
continue; continue;
} }
bNotEmpty = true;
const float fBeat = StringToFloat( arrayBPMChangeValues[0] ); const float fBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] ); const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
@@ -179,20 +246,20 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
highspeedBeat = -1; highspeedBeat = -1;
} }
{ {
BPMSegment new_seg; BPMSegment new_seg( BeatToNoteRow( fBeat ), fNewBPM );
new_seg.m_iStartRow = BeatToNoteRow(fBeat);
new_seg.SetBPM( fNewBPM );
out.AddBPMSegment( new_seg ); out.AddBPMSegment( new_seg );
} }
} }
} }
} }
return bNotEmpty;
} }
else if( sValueName=="STOPS" || sValueName=="FREEZES" ) void SMLoader::ProcessStops( TimingData &out, const RString sParam )
{ {
vector<RString> arrayFreezeExpressions; vector<RString> arrayFreezeExpressions;
split( sParams[1], ",", arrayFreezeExpressions ); split( sParam, ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion. // Prepare variables for negative stop conversion.
float negBeat = -1; float negBeat = -1;
@@ -205,8 +272,8 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
if( arrayFreezeValues.size() != 2 ) if( arrayFreezeValues.size() != 2 )
{ {
// XXX: Hard to tell which file caused this. // XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayFreezeExpressions[f].c_str() ); arrayFreezeExpressions[f].c_str() );
continue; continue;
} }
@@ -223,7 +290,7 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
if( negBeat + fSkipBeats > fFreezeBeat ) if( negBeat + fSkipBeats > fFreezeBeat )
fSkipBeats = fFreezeBeat - negBeat; fSkipBeats = fFreezeBeat - negBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats); WarpSegment ws( negBeat, fSkipBeats);
out.AddWarpSegment( ws ); out.AddWarpSegment( ws );
negBeat = -1; negBeat = -1;
@@ -255,10 +322,10 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
} }
} }
else if( sValueName=="DELAYS" ) void SMLoader::ProcessDelays( TimingData &out, const RString sParam )
{ {
vector<RString> arrayDelayExpressions; vector<RString> arrayDelayExpressions;
split( sParams[1], ",", arrayDelayExpressions ); split( sParam, ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ ) for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{ {
@@ -267,8 +334,8 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
if( arrayDelayValues.size() != 2 ) if( arrayDelayValues.size() != 2 )
{ {
// XXX: Hard to tell which file caused this. // XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #DELAYS value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayDelayExpressions[f].c_str() ); arrayDelayExpressions[f].c_str() );
continue; continue;
} }
@@ -289,10 +356,10 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
} }
} }
else if( sValueName=="TIMESIGNATURES" ) void SMLoader::ProcessTimeSignatures( TimingData &out, const RString sParam )
{ {
vector<RString> vs1; vector<RString> vs1;
split( sParams[1], ",", vs1 ); split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 ) FOREACH_CONST( RString, vs1, s1 )
{ {
@@ -307,10 +374,7 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
const float fBeat = StringToFloat( vs2[0] ); const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg; TimeSignatureSegment seg( BeatToNoteRow( fBeat ), StringToInt( vs2[1] ), StringToInt( vs2[2] ));
seg.m_iStartRow = BeatToNoteRow(fBeat);
seg.m_iNumerator = StringToInt( vs2[1] );
seg.m_iDenominator = StringToInt( vs2[2] );
if( fBeat < 0 ) if( fBeat < 0 )
{ {
@@ -334,10 +398,10 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
} }
} }
else if( sValueName=="TICKCOUNTS" ) void SMLoader::ProcessTickcounts( TimingData &out, const RString sParam )
{ {
vector<RString> arrayTickcountExpressions; vector<RString> arrayTickcountExpressions;
split( sParams[1], ",", arrayTickcountExpressions ); split( sParam, ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ ) for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{ {
@@ -346,32 +410,60 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
if( arrayTickcountValues.size() != 2 ) if( arrayTickcountValues.size() != 2 )
{ {
// XXX: Hard to tell which file caused this. // XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.", LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #TICKCOUNTS value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayTickcountExpressions[f].c_str() ); arrayTickcountExpressions[f].c_str() );
continue; continue;
} }
const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] ); const float fTickcountBeat = StringToFloat( arrayTickcountValues[0] );
int iTicks = StringToInt( arrayTickcountValues[1] ); int iTicks = clamp(atoi( arrayTickcountValues[1] ), 0, ROWS_PER_BEAT);
// you're lazy, let SM do the work for you... -DaisuMaster
if( iTicks < 1) iTicks = 1;
if( iTicks > ROWS_PER_BEAT ) iTicks = ROWS_PER_BEAT;
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks ); TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg ); out.AddTickcountSegment( new_seg );
}
}
if(iTicks >= 1 && iTicks <= ROWS_PER_BEAT ) // Constants void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
{ {
// LOG->Trace( "Adding a tickcount segment: beat: %f, ticks = %d", fTickcountBeat, iTicks ); out.m_fBeat0OffsetInSeconds = 0;
//out.AddTickcountSegment( new_seg ); out.m_BPMSegments.clear();
} out.m_StopSegments.clear();
else out.m_WarpSegments.clear();
out.m_vTimeSignatureSegments.clear();
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{ {
//LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid tickcount at beat %f, ticks %d.", fTickcountBeat, iTicks ); const MsdFile::value_t &sParams = msd.GetValue(i);
//LOG->UserLog( "Song file", "(UNKNOWN)", "Clamping tickcount value to %d at beat %f.", iTicks, fTickcountBeat); RString sValueName = sParams[0];
//etc sValueName.MakeUpper();
if( sValueName=="OFFSET" )
{
out.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
} }
else if( sValueName=="BPMS" )
{
ProcessBPMs(out, sParams[1]);
} }
else if( sValueName=="STOPS" || sValueName=="FREEZES" )
{
ProcessStops(out, sParams[1]);
}
else if( sValueName=="DELAYS" )
{
ProcessDelays(out, sParams[1]);
}
else if( sValueName=="TIMESIGNATURES" )
{
ProcessTimeSignatures(out, sParams[1]);
}
else if( sValueName=="TICKCOUNTS" )
{
ProcessTickcounts(out, sParams[1]);
} }
// Ensure all of the warps are handled right. // Ensure all of the warps are handled right.
sort(out.m_WarpSegments.begin(), out.m_WarpSegments.end()); sort(out.m_WarpSegments.begin(), out.m_WarpSegments.end());
@@ -457,8 +549,8 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
return false; return false;
} }
out.m_Timing.m_sFile = sPath; out.m_SongTiming.m_sFile = sPath;
LoadTimingFromSMFile( msd, out.m_Timing ); LoadTimingFromSMFile( msd, out.m_SongTiming );
for( unsigned i=0; i<msd.GetNumValues(); i++ ) for( unsigned i=0; i<msd.GetNumValues(); i++ )
{ {
@@ -512,19 +604,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else if( sValueName=="INSTRUMENTTRACK" ) else if( sValueName=="INSTRUMENTTRACK" )
{ {
vector<RString> vs1; ProcessInstrumentTracks( out, sParams[1] );
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
} }
else if( sValueName=="MUSICLENGTH" ) else if( sValueName=="MUSICLENGTH" )
@@ -617,27 +697,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" ) else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" )
{ {
BackgroundLayer iLayer = BACKGROUND_LAYER_1; ProcessBGChanges( out, sValueName, sPath, sParams[1]);
if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{
LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
}
else
{
vector<RString> aBGChangeExpressions;
split( sParams[1], ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
} }
else if( sValueName=="FGCHANGES" ) else if( sValueName=="FGCHANGES" )
@@ -661,47 +721,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
// Attacks loaded from file // Attacks loaded from file
else if( sValueName=="ATTACKS" ) else if( sValueName=="ATTACKS" )
{ {
// Build the RString vector here so we can write it to file again later ProcessAttacks( out, sParams );
for( unsigned s=1; s < sParams.params.size(); ++s )
out.m_sAttackString.push_back( sParams[s] );
Attack attack;
float end = -9999;
for( unsigned j=1; j < sParams.params.size(); ++j )
{
vector<RString> sBits;
split( sParams[j], "=", sBits, false );
// Need an identifer and a value for this to work
if( sBits.size() < 2 )
continue;
TrimLeft( sBits[0] );
TrimRight( sBits[0] );
if( !sBits[0].CompareNoCase("TIME") )
attack.fStartSecond = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("LEN") )
attack.fSecsRemaining = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("END") )
end = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("MODS") )
{
attack.sModifiers = sBits[1];
if( end != -9999 )
{
attack.fSecsRemaining = end - attack.fStartSecond;
end = -9999;
}
if( attack.fSecsRemaining < 0.0f )
attack.fSecsRemaining = 0.0f;
out.m_Attacks.push_back( attack );
}
}
} }
else if( sValueName=="NOTES" || sValueName=="NOTES2" ) else if( sValueName=="NOTES" || sValueName=="NOTES2" )
@@ -712,7 +732,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
continue; continue;
} }
Steps* pNewNotes = new Steps; Steps* pNewNotes = out.CreateSteps();
LoadFromSMTokens( LoadFromSMTokens(
sParams[1], sParams[1],
sParams[2], sParams[2],
@@ -733,7 +753,7 @@ bool SMLoader::LoadFromSMFile( const RString &sPath, Song &out, bool bFromCache
else else
LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() ); LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() );
} }
TidyUpData( out, bFromCache );
return true; return true;
} }
@@ -846,7 +866,7 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath
if( !bAddStepsToSong ) if( !bAddStepsToSong )
return true; return true;
Steps* pNewNotes = new Steps; Steps* pNewNotes = pSong->CreateSteps();
LoadFromSMTokens( LoadFromSMTokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes); *pNewNotes);
@@ -926,6 +946,7 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache )
bg.push_back( BackgroundChange(song.m_fLastBeat,song.m_sBackgroundFile) ); bg.push_back( BackgroundChange(song.m_fLastBeat,song.m_sBackgroundFile) );
} while(0); } while(0);
} }
song.TidyUpData();
} }
/* /*
+19 -1
View File
@@ -3,11 +3,18 @@
#include "GameConstantsAndTypes.h" #include "GameConstantsAndTypes.h"
#include "BackgroundUtil.h" #include "BackgroundUtil.h"
#include "MsdFile.h" // we require the struct from here.
class MsdFile;
class Song; class Song;
class Steps; class Steps;
class TimingData; class TimingData;
/**
* @brief The highest allowable speed before Warps come in.
*
* This was brought in from StepMania 4's recent betas. */
const float FAST_BPM_WARP = 9999999.f;
/** @brief Reads a Song from an .SM file. */ /** @brief Reads a Song from an .SM file. */
namespace SMLoader namespace SMLoader
{ {
@@ -25,6 +32,17 @@ namespace SMLoader
bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot ); bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot );
bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ); bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression ); bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression );
bool ProcessBPMs( TimingData &, const RString );
void ProcessStops( TimingData &, const RString );
void ProcessDelays( TimingData &, const RString );
void ProcessTimeSignatures( TimingData &, const RString );
void ProcessTickcounts( TimingData &, const RString );
void ProcessBGChanges( Song &out, const RString &sValueName,
const RString &sPath, const RString &sParam );
void ProcessAttacks( Song &out, MsdFile::value_t sParams );
void ProcessInstrumentTracks( Song &out, const RString &sParam );
} }
#endif #endif
+484 -389
View File
@@ -12,12 +12,379 @@
#include "Song.h" #include "Song.h"
#include "SongManager.h" #include "SongManager.h"
#include "Steps.h" #include "Steps.h"
#include "Attack.h"
/** /**
* @brief A custom .edit file can only be so big before we have to reject it. * @brief A custom .edit file can only be so big before we have to reject it.
*/ */
const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60 KB const int MAX_EDIT_STEPS_SIZE_BYTES = 60*1024; // 60 KB
bool SMALoader::LoadFromBGChangesString( BackgroundChange &change,
const RString &sBGChangeExpression )
{
return SMLoader::LoadFromBGChangesString(change, sBGChangeExpression);
}
bool SMALoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath, "has more than one SMA file. Only one SMA file is allowed per song." );
return false;
}
ASSERT( aFileNames.size() == 1 );
return LoadFromSMAFile( sPath + aFileNames[0], out );
}
float SMALoader::RowToBeat( RString sLine, const int iRowsPerBeat )
{
if( sLine.find("R") || sLine.find("r") )
{
sLine = sLine.Left(sLine.size()-1);
return StringToFloat( sLine ) / iRowsPerBeat;
}
else
{
return StringToFloat( sLine );
}
}
bool SMALoader::ProcessBPMs( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayBPMChangeExpressions;
split( sParam, ",", arrayBPMChangeExpressions );
// prepare storage variables for negative BPMs -> Warps.
float negBeat = -1;
float negBPM = 1;
float highspeedBeat = -1;
bool bNotEmpty = false;
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #BPMs value \"%s\" (must have exactly one '='), ignored.",
arrayBPMChangeExpressions[b].c_str() );
continue;
}
bNotEmpty = true;
const float fBeat = RowToBeat( arrayBPMChangeValues[0], iRowsPerBeat );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
if( fNewBPM < 0.0f )
{
out.m_bHasNegativeBpms = true;
negBeat = fBeat;
negBPM = fNewBPM;
}
else if( fNewBPM > 0.0f )
{
// add in a warp.
if( negBPM < 0 )
{
float endBeat = fBeat + (fNewBPM / -negBPM) * (fBeat - negBeat);
WarpSegment new_seg(negBeat, endBeat);
out.AddWarpSegment( new_seg );
negBeat = -1;
negBPM = 1;
}
// too fast. make it a warp.
if( fNewBPM > FAST_BPM_WARP )
{
highspeedBeat = fBeat;
}
else
{
// add in a warp.
if( highspeedBeat > 0 )
{
WarpSegment new_seg(highspeedBeat, fBeat);
out.AddWarpSegment( new_seg );
highspeedBeat = -1;
}
{
BPMSegment new_seg( BeatToNoteRow( fBeat ), fNewBPM );
out.AddBPMSegment( new_seg );
}
}
}
}
return bNotEmpty;
}
void SMALoader::ProcessStops( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayFreezeExpressions;
split( sParam, ",", arrayFreezeExpressions );
// Prepare variables for negative stop conversion.
float negBeat = -1;
float negPause = 0;
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
arrayFreezeExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = RowToBeat( arrayFreezeValues[0], iRowsPerBeat );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
// Process the prior stop.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
if( negBeat + fSkipBeats > fFreezeBeat )
fSkipBeats = fFreezeBeat - negBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
negBeat = -1;
negPause = 0;
}
if( fFreezeSeconds < 0.0f )
{
negBeat = fFreezeBeat;
negPause = -fFreezeSeconds;
}
else if( fFreezeSeconds > 0.0f )
{
StopSegment ss( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
out.AddStopSegment( ss );
}
}
// Process the prior stop if there was one.
if( negPause > 0 )
{
BPMSegment oldBPM = out.GetBPMSegmentAtRow(BeatToNoteRow(negBeat));
float fSecondsPerBeat = 60 / oldBPM.GetBPM();
float fSkipBeats = negPause / fSecondsPerBeat;
WarpSegment ws( negBeat, negBeat + fSkipBeats);
out.AddWarpSegment( ws );
}
}
void SMALoader::ProcessDelays( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayDelayExpressions;
split( sParam, ",", arrayDelayExpressions );
for( unsigned f=0; f<arrayDelayExpressions.size(); f++ )
{
vector<RString> arrayDelayValues;
split( arrayDelayExpressions[f], "=", arrayDelayValues );
if( arrayDelayValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #DELAYS value \"%s\" (must have exactly one '='), ignored.",
arrayDelayExpressions[f].c_str() );
continue;
}
const float fFreezeBeat = RowToBeat( arrayDelayValues[0], iRowsPerBeat );
const float fFreezeSeconds = StringToFloat( arrayDelayValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds, true );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
// LOG->Trace( "Adding a delay segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
if(fFreezeSeconds > 0.0f)
out.AddStopSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid delay at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
}
void SMALoader::ProcessTickcounts( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayTickcountExpressions;
split( sParam, ",", arrayTickcountExpressions );
for( unsigned f=0; f<arrayTickcountExpressions.size(); f++ )
{
vector<RString> arrayTickcountValues;
split( arrayTickcountExpressions[f], "=", arrayTickcountValues );
if( arrayTickcountValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #TICKCOUNTS value \"%s\" (must have exactly one '='), ignored.",
arrayTickcountExpressions[f].c_str() );
continue;
}
const float fTickcountBeat = RowToBeat( arrayTickcountValues[0], iRowsPerBeat );
int iTicks = clamp(atoi( arrayTickcountValues[1] ), 0, ROWS_PER_BEAT);
TickcountSegment new_seg( BeatToNoteRow(fTickcountBeat), iTicks );
out.AddTickcountSegment( new_seg );
}
}
void SMALoader::ProcessMultipliers( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayMultiplierExpressions;
split( sParam, ",", arrayMultiplierExpressions );
for( unsigned f=0; f<arrayMultiplierExpressions.size(); f++ )
{
vector<RString> arrayMultiplierValues;
split( arrayMultiplierExpressions[f], "=", arrayMultiplierValues );
if( arrayMultiplierValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #MULTIPLIER value \"%s\" (must have exactly one '='), ignored.",
arrayMultiplierExpressions[f].c_str() );
continue;
}
const float fComboBeat = RowToBeat( arrayMultiplierValues[0], iRowsPerBeat );
const int iCombos = StringToInt( arrayMultiplierValues[1] );
ComboSegment new_seg( BeatToNoteRow( fComboBeat ), iCombos );
out.AddComboSegment( new_seg );
}
}
void SMALoader::ProcessBeatsPerMeasure( TimingData &out, const RString sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2.size() < 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid beats per measure change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg( BeatToNoteRow( fBeat ), StringToInt( vs2[1] ), 4 );
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f.", fBeat );
continue;
}
if( seg.m_iNumerator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iNumerator %i.", fBeat, seg.m_iNumerator );
continue;
}
out.AddTimeSignatureSegment( seg );
}
}
float BeatToSeconds(float fromBeat, RString toSomething)
{
return 0;
}
void SMALoader::ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> vs1;
split( sParam, ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( RowToBeat(vs2[0], iRowsPerBeat) == 0 && vs2.size() == 2 ) // First one always seems to have 2.
{
vs2.push_back("0");
}
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = RowToBeat( vs2[0], iRowsPerBeat );
unsigned short tmp = ( (vs2[2].find("s") || vs2[2].find("S") )
? 1 : 0);
SpeedSegment seg( fBeat, StringToFloat( vs2[1] ), StringToFloat( vs2[2] ), tmp);
if( fBeat < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f.", fBeat );
continue;
}
if( seg.m_fWait < 0 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an speed change with beat %f, fWait %f.", fBeat, seg.m_fWait );
continue;
}
out.AddSpeedSegment( seg );
}
}
void SMALoader::ProcessFakes( TimingData &out, const int iRowsPerBeat, const RString sParam )
{
vector<RString> arrayFakeExpressions;
split( sParam, ",", arrayFakeExpressions );
for( unsigned b=0; b<arrayFakeExpressions.size(); b++ )
{
vector<RString> arrayFakeValues;
split( arrayFakeExpressions[b], "=", arrayFakeValues );
// XXX: Hard to tell which file caused this.
if( arrayFakeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #FAKES value \"%s\" (must have exactly one '='), ignored.",
arrayFakeExpressions[b].c_str() );
continue;
}
const float fBeat = RowToBeat( arrayFakeValues[0], iRowsPerBeat );
const float fNewBeat = StringToFloat( arrayFakeValues[1] );
if(fNewBeat > 0)
out.AddFakeSegment( FakeSegment(fBeat, fNewBeat) );
else
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid Fake at beat %f, BPM %f.", fBeat, fNewBeat );
}
}
}
void SMALoader::LoadFromSMATokens( void SMALoader::LoadFromSMATokens(
RString sStepsType, RString sStepsType,
RString sDescription, RString sDescription,
@@ -28,73 +395,9 @@ void SMALoader::LoadFromSMATokens(
Steps &out Steps &out
) )
{ {
// we're loading from disk, so this is by definition already saved: SMLoader::LoadFromSMTokens( sStepsType, sDescription,
out.SetSavedToDisk( true ); sDifficulty, sMeter, sRadarValues,
sNoteData, out );
Trim( sStepsType );
Trim( sDescription );
Trim( sDifficulty );
Trim( sNoteData );
// LOG->Trace( "Steps::LoadFromSMTokens()" );
// insert stepstype hacks from GameManager.cpp here? -aj
out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType );
out.SetDescription( sDescription );
out.SetCredit( sDescription ); // this is often used for both.
out.SetDifficulty( StringToDifficulty(sDifficulty) );
sDescription.MakeLower();
// Handle hacks that originated back when StepMania didn't have
// Difficulty_Challenge. (At least v1.64, possibly v3.0 final...)
if( out.GetDifficulty() == Difficulty_Hard )
{
// HACK: SMANIAC used to be Difficulty_Hard with a special description.
if( sDescription == "smaniac" )
out.SetDifficulty( Difficulty_Challenge );
// HACK: CHALLENGE used to be Difficulty_Hard with a special description.
if( sDescription == "challenge" )
out.SetDifficulty( Difficulty_Challenge );
}
out.SetMeter( StringToInt(sMeter) );
vector<RString> saValues;
split( sRadarValues, ",", saValues, true );
int categories = NUM_RadarCategory - 1; // Fakes aren't counted in the radar values.
if( saValues.size() == (unsigned)categories * NUM_PLAYERS )
{
RadarValues v[NUM_PLAYERS];
FOREACH_PlayerNumber( pn )
{
// Can't use the foreach anymore due to flexible radar lines.
for( RadarCategory rc = (RadarCategory)0; rc < categories;
enum_add<RadarCategory>( rc, 1 ) )
{
v[pn][rc] = StringToFloat( saValues[pn*categories + rc] );
}
}
out.SetCachedRadarValues( v );
}
out.SetSMNoteData( sNoteData );
out.TidyUpData();
}
bool SMALoader::LoadFromDir( const RString &sPath, Song &out )
{
vector<RString> aFileNames;
GetApplicableFiles( sPath, aFileNames );
if( aFileNames.size() > 1 )
{
LOG->UserLog( "Song", sPath, "has more than one SMA file. There can be only one!" );
return false;
}
ASSERT( aFileNames.size() == 1 );
return LoadFromSMAFile( sPath + aFileNames[0], out );
} }
void SMALoader::TidyUpData( Song &song, bool bFromCache ) void SMALoader::TidyUpData( Song &song, bool bFromCache )
@@ -113,8 +416,12 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
return false; return false;
} }
out.m_Timing.m_sFile = sPath; out.m_SongTiming.m_sFile = sPath; // songs still have their fallback timing.
LoadTimingFromSMAFile( msd, out.m_Timing );
int state = SMA_GETTING_SONG_INFO;
Steps* pNewNotes = NULL;
TimingData stepsTiming;
int iRowsPerBeat = -1; // Start with an invalid value: needed for checking.
for( unsigned i=0; i<msd.GetNumValues(); i++ ) for( unsigned i=0; i<msd.GetNumValues(); i++ )
{ {
@@ -168,19 +475,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
else if( sValueName=="INSTRUMENTTRACK" ) else if( sValueName=="INSTRUMENTTRACK" )
{ {
vector<RString> vs1; SMLoader::ProcessInstrumentTracks( out, sParams[1] );
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s )
{
vector<RString> vs2;
split( *s, "=", vs2 );
if( vs2.size() >= 2 )
{
InstrumentTrack it = StringToInstrumentTrack( vs2[0] );
if( it != InstrumentTrack_Invalid )
out.m_sInstrumentTrackFile[it] = vs2[1];
}
}
} }
else if( sValueName=="MUSICLENGTH" ) else if( sValueName=="MUSICLENGTH" )
@@ -202,7 +497,8 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
} }
else if( sValueName=="LASTBEAT" ) else if( sValueName=="LASTBEAT" )
{ {
; } ;
}
else if( sValueName=="SONGFILENAME" ) else if( sValueName=="SONGFILENAME" )
{ {
; ;
@@ -226,6 +522,11 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
//else if( sValueName=="SAMPLEPATH" ) //else if( sValueName=="SAMPLEPATH" )
//out.m_sMusicSamplePath = sParams[1]; //out.m_sMusicSamplePath = sParams[1];
else if( sValueName=="LISTSORT" )
{
;
}
else if( sValueName=="DISPLAYBPM" ) else if( sValueName=="DISPLAYBPM" )
{ {
// #DISPLAYBPM:[xxx][xxx:xxx]|[*]; // #DISPLAYBPM:[xxx][xxx:xxx]|[*];
@@ -242,6 +543,41 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
} }
} }
else if( sValueName=="SMAVERSION" )
{
; // ignore it.
}
else if( sValueName=="ROWSPERBEAT" )
{
/* This value is used to help translate the timings
* the SMA format uses. Starting with the second
* appearance, it delimits NoteData. Right now, this
* value doesn't seem to be editable in SMA. When it
* becomes so, make adjustments to this code. */
if( iRowsPerBeat < 0 )
{
vector<RString> arrayBeatChangeExpressions;
split( sParams[1], ",", arrayBeatChangeExpressions );
vector<RString> arrayBeatChangeValues;
split( arrayBeatChangeExpressions[0], "=", arrayBeatChangeValues );
iRowsPerBeat = StringToInt(arrayBeatChangeValues[1]);
}
else
{
state = SMA_GETTING_STEP_INFO;
pNewNotes = new Steps;
}
}
else if( sValueName=="BEATSPERMEASURE" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessBeatsPerMeasure( timing, sParams[1] );
}
else if( sValueName=="SELECTABLE" ) else if( sValueName=="SELECTABLE" )
{ {
if(sParams[1].EqualsNoCase("YES")) if(sParams[1].EqualsNoCase("YES"))
@@ -265,27 +601,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" ) else if( sValueName.Left(strlen("BGCHANGES"))=="BGCHANGES" || sValueName=="ANIMATIONS" )
{ {
BackgroundLayer iLayer = BACKGROUND_LAYER_1; SMLoader::ProcessBGChanges( out, sValueName, sPath, sParams[1]);
if( sscanf(sValueName, "BGCHANGES%d", &*ConvertValue<int>(&iLayer)) == 1 )
enum_add(iLayer, -1); // #BGCHANGES2 = BACKGROUND_LAYER_2
bool bValid = iLayer>=0 && iLayer<NUM_BackgroundLayer;
if( !bValid )
{
LOG->UserLog( "Song file", sPath, "has a #BGCHANGES tag \"%s\" that is out of range.", sValueName.c_str() );
}
else
{
vector<RString> aBGChangeExpressions;
split( sParams[1], ",", aBGChangeExpressions );
for( unsigned b=0; b<aBGChangeExpressions.size(); b++ )
{
BackgroundChange change;
if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) )
out.AddBackgroundChange( iLayer, change );
}
}
} }
else if( sValueName=="FGCHANGES" ) else if( sValueName=="FGCHANGES" )
@@ -301,6 +617,65 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
} }
} }
else if( sValueName=="OFFSET" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
timing.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
}
else if( sValueName=="BPMS" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessBPMs( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="STOPS" || sValueName=="FREEZES" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessStops( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="DELAYS" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessDelays( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="TICKCOUNT" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessTickcounts( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="SPEED" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessSpeeds( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="MULTIPLIER" )
{
ProcessMultipliers( pNewNotes->m_Timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="FAKES" )
{
TimingData &timing = (state == SMA_GETTING_STEP_INFO
? pNewNotes->m_Timing : out.m_SongTiming);
ProcessFakes( timing, iRowsPerBeat, sParams[1] );
}
else if( sValueName=="METERTYPE" )
{
; // We don't use this...yet.
}
else if( sValueName=="KEYSOUNDS" ) else if( sValueName=="KEYSOUNDS" )
{ {
split( sParams[1], ",", out.m_vsKeysoundFile ); split( sParams[1], ",", out.m_vsKeysoundFile );
@@ -309,47 +684,7 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
// Attacks loaded from file // Attacks loaded from file
else if( sValueName=="ATTACKS" ) else if( sValueName=="ATTACKS" )
{ {
// Build the RString vector here so we can write it to file again later SMLoader::ProcessAttacks( out, sParams );
for( unsigned s=1; s < sParams.params.size(); ++s )
out.m_sAttackString.push_back( sParams[s] );
Attack attack;
float end = -9999;
for( unsigned j=1; j < sParams.params.size(); ++j )
{
vector<RString> sBits;
split( sParams[j], "=", sBits, false );
// Need an identifer and a value for this to work
if( sBits.size() < 2 )
continue;
TrimLeft( sBits[0] );
TrimRight( sBits[0] );
if( !sBits[0].CompareNoCase("TIME") )
attack.fStartSecond = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("LEN") )
attack.fSecsRemaining = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("END") )
end = strtof( sBits[1], NULL );
else if( !sBits[0].CompareNoCase("MODS") )
{
attack.sModifiers = sBits[1];
if( end != -9999 )
{
attack.fSecsRemaining = end - attack.fStartSecond;
end = -9999;
}
if( attack.fSecsRemaining < 0.0f )
attack.fSecsRemaining = 0.0f;
out.m_Attacks.push_back( attack );
}
}
} }
else if( sValueName=="NOTES" || sValueName=="NOTES2" ) else if( sValueName=="NOTES" || sValueName=="NOTES2" )
@@ -360,7 +695,6 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
continue; continue;
} }
Steps* pNewNotes = new Steps;
LoadFromSMATokens( LoadFromSMATokens(
sParams[1], sParams[1],
sParams[2], sParams[2],
@@ -372,16 +706,13 @@ bool SMALoader::LoadFromSMAFile( const RString &sPath, Song &out )
out.AddSteps( pNewNotes ); out.AddSteps( pNewNotes );
} }
/* else if( sValueName=="TIMESIGNATURES" || sValueName=="LEADTRACK" )
* We used to check for timing data in this section. That has
* since been moved to a dedicated function.
*/
else if( sValueName=="OFFSET" || sValueName=="BPMS" || sValueName=="STOPS" || sValueName=="FREEZES" || sValueName=="DELAYS" || sValueName=="TIMESIGNATURES" || sValueName=="LEADTRACK" || sValueName=="TICKCOUNTS" )
; ;
else else
LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() ); LOG->UserLog( "Song file", sPath, "has an unexpected value named \"%s\".", sValueName.c_str() );
} }
TidyUpData(out, false);
out.TidyUpData();
return true; return true;
} }
@@ -390,236 +721,6 @@ void SMALoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
GetDirListing( sPath + RString("*.sma"), out ); GetDirListing( sPath + RString("*.sma"), out );
} }
bool SMALoader::LoadTimingFromFile( const RString &fn, TimingData &out )
{
MsdFile msd;
if( !msd.ReadFile( fn, true ) ) // unescape
{
LOG->UserLog( "Song file", fn, "couldn't be loaded: %s", msd.GetError().c_str() );
return false;
}
out.m_sFile = fn;
LoadTimingFromSMAFile( msd, out );
return true;
}
void SMALoader::LoadTimingFromSMAFile( const MsdFile &msd, TimingData &out )
{
out.m_fBeat0OffsetInSeconds = 0;
out.m_BPMSegments.clear();
out.m_StopSegments.clear();
out.m_WarpSegments.clear();
out.m_vTimeSignatureSegments.clear();
vector<WarpSegment> arrayWarpsFromNegativeBPMs;
//vector<WarpSegment> arrayWarpsFromNegativeStops;
int rowsPerMeasure = 0;
bool encountered = false;
for( unsigned i=0; i<msd.GetNumValues(); i++ )
{
const MsdFile::value_t &sParams = msd.GetValue(i);
RString sValueName = sParams[0];
sValueName.MakeUpper();
if( sValueName=="ROWSPERBEAT")
{
if( encountered )
{
break;
}
encountered = true;
rowsPerMeasure = StringToInt( sParams[1] );
}
else if( sValueName=="BEATSPERMEASURE" )
{
TimeSignatureSegment new_seg;
new_seg.m_iStartRow = 0;
new_seg.m_iNumerator = StringToInt( sParams[1] );
new_seg.m_iDenominator = 4;
out.AddTimeSignatureSegment( new_seg );
}
else if( sValueName=="OFFSET" )
{
out.m_fBeat0OffsetInSeconds = StringToFloat( sParams[1] );
}
else if( sValueName=="STOPS" )
{
vector<RString> arrayFreezeExpressions;
split( sParams[1], ",", arrayFreezeExpressions );
for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
{
vector<RString> arrayFreezeValues;
split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
if( arrayFreezeValues.size() != 2 )
{
// XXX: Hard to tell which file caused this.
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayFreezeExpressions[f].c_str() );
continue;
}
float fFreezeBeat = 0;
RString beat = arrayFreezeValues[0];
if( beat.Right(0).MakeUpper() == "R" )
{
beat = beat.Left(beat.size()-1);
fFreezeBeat = StringToFloat( beat ) / rowsPerMeasure;
}
else
{
fFreezeBeat = StringToFloat(beat);
}
//float fFreezeBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fFreezeSeconds = StringToFloat( arrayFreezeValues[1] );
StopSegment new_seg( BeatToNoteRow(fFreezeBeat), fFreezeSeconds );
// XXX: Remove Negatives Bug?
new_seg.m_iStartRow = BeatToNoteRow(fFreezeBeat);
new_seg.m_fStopSeconds = fFreezeSeconds;
if(fFreezeSeconds > 0.0f)
{
// LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
out.AddStopSegment( new_seg );
}
else
{
// negative stops (hi JS!) -aj
if( PREFSMAN->m_bQuirksMode )
{
// LOG->Trace( "Adding a negative freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );
out.AddStopSegment( new_seg );
}
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid stop at beat %f, length %f.", fFreezeBeat, fFreezeSeconds );
}
}
}
else if( sValueName=="BPMS" )
{
vector<RString> arrayBPMChangeExpressions;
split( sParams[1], ",", arrayBPMChangeExpressions );
for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
{
vector<RString> arrayBPMChangeValues;
split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
// XXX: Hard to tell which file caused this.
if( arrayBPMChangeValues.size() != 2 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid #%s value \"%s\" (must have exactly one '='), ignored.",
sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() );
continue;
}
float fBeat = 0;
RString beat = arrayBPMChangeValues[0];
if( beat.Right(0).MakeUpper() == "R" )
{
beat = beat.Left(beat.size()-1);
fBeat = StringToFloat( beat ) / rowsPerMeasure;
}
else
{
fBeat = StringToFloat(beat);
}
//float fBeat = StringToFloat( arrayBPMChangeValues[0] );
const float fNewBPM = StringToFloat( arrayBPMChangeValues[1] );
// XXX: Remove Negatives Bug?
BPMSegment new_seg;
new_seg.m_iStartRow = BeatToNoteRow(fBeat);
new_seg.SetBPM( fNewBPM );
// convert negative BPMs into Warp segments
if( fNewBPM < 0.0f )
{
vector<RString> arrayNextBPMChangeValues;
// get next bpm in sequence
if((b+1) < arrayBPMChangeExpressions.size())
{
split( arrayBPMChangeExpressions[b+1], "=", arrayNextBPMChangeValues );
const float fNextPositiveBeat = StringToFloat( arrayNextBPMChangeValues[0] );
const float fNextPositiveBPM = StringToFloat( arrayNextBPMChangeValues[1] );
// tJumpPos = (tPosBPS-abs(negBPS)) + (gPosBPMPosition - fNegPosition)
float fDeltaBeat = ((fNextPositiveBPM/60.0f)-abs(fNewBPM/60.0f)) + (fNextPositiveBeat-fBeat);
//float fWarpLengthBeats = fNextPositiveBeat + fDeltaBeat;
WarpSegment wsTemp(BeatToNoteRow(fBeat),fDeltaBeat);
arrayWarpsFromNegativeBPMs.push_back(wsTemp);
/*
LOG->Trace( ssprintf("==NotesLoSM negbpm==\nfnextposbeat = %f, fnextposbpm = %f,\nfdelta = %f, fwarpto = %f",
fNextPositiveBeat,
fNextPositiveBPM,
fDeltaBeat,
fWarpToBeat
) );
*/
/*
LOG->Trace( ssprintf("==Negative/Subtractive BPM in NotesLoader==\nNegBPM has noterow = %i, BPM = %f\nNextBPM @ noterow %i\nDelta value = %i noterows\nThis warp will have us end up at noterow %i",
BeatToNoteRow(fBeat), fNewBPM,
BeatToNoteRow(fNextPositiveBeat),
BeatToNoteRow(fDeltaBeat),
BeatToNoteRow(fWarpToBeat))
);
*/
//float fDeltaBeat = ((fNextPositiveBPM/60.0f)-abs(fNewBPM/60.0f)) + (fNextPositiveBeat-fBeat);
/*
LOG->Trace( ssprintf("==NotesLoader Delta as NoteRows==\nfDeltaBeat = %f (beat)\nfDeltaBeat = (NextBPMSeg %f - abs(fBPS %f)) + (nextStartRow %i - thisRow %i)",
fDeltaBeat,(fNextPositiveBPM/60.0f),abs(fNewBPM/60.0f),BeatToNoteRow(fNextPositiveBeat),BeatToNoteRow(fBeat))
);
*/
out.AddBPMSegment( new_seg );
continue;
}
else
{
// last BPM is a negative one? ugh. -aj (MAX_NOTE_ROW exists btw)
out.AddBPMSegment( new_seg );
}
}
if(fNewBPM > 0.0f)
out.AddBPMSegment( new_seg );
else
{
out.m_bHasNegativeBpms = true;
// only add Negative BPMs in quirks mode -aj
if( PREFSMAN->m_bQuirksMode )
out.AddBPMSegment( new_seg );
else
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid BPM change at beat %f, BPM %f.", fBeat, fNewBPM );
}
}
}
// Note: Even though it is possible to have Negative BPMs and Stops in
// a song along with Warps, we should not support files that contain
// both styles of warp tricks (Negatives vs. #WARPS).
// If Warps have been populated from Negative BPMs, then go through that
// instead of using the data in the Warps tag. This should be above,
// but it breaks compiling so...
if(arrayWarpsFromNegativeBPMs.size() > 0)
{
// zomg we already have some warps...
for( unsigned j=0; j<arrayWarpsFromNegativeBPMs.size(); j++ )
{
out.AddWarpSegment( arrayWarpsFromNegativeBPMs[j] );
}
}
// warp sorting will need to take place.
//sort(out.m_WarpSegments.begin(), out.m_WarpSegments.end());
}
}
bool SMALoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ) bool SMALoader::LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong )
{ {
LOG->Trace( "SMALoader::LoadEditFromFile(%s)", sEditFilePath.c_str() ); LOG->Trace( "SMALoader::LoadEditFromFile(%s)", sEditFilePath.c_str() );
@@ -702,7 +803,7 @@ bool SMALoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
if( !bAddStepsToSong ) if( !bAddStepsToSong )
return true; return true;
Steps* pNewNotes = new Steps; Steps* pNewNotes = pSong->CreateSteps();
LoadFromSMATokens( LoadFromSMATokens(
sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6], sParams[1], sParams[2], sParams[3], sParams[4], sParams[5], sParams[6],
*pNewNotes); *pNewNotes);
@@ -730,12 +831,6 @@ bool SMALoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePat
return true; return true;
} }
bool SMALoader::LoadFromBGChangesString( BackgroundChange &change,
const RString &sBGChangeExpression )
{
return SMLoader::LoadFromBGChangesString(change, sBGChangeExpression);
}
/** /**
* @file * @file
* @author Aldo Fregoso, Jason Felds (c) 2009-2011 * @author Aldo Fregoso, Jason Felds (c) 2009-2011
+22 -2
View File
@@ -9,6 +9,16 @@ class Song;
class Steps; class Steps;
class TimingData; class TimingData;
/**
* @brief The various states while parsing a .sma file.
*/
enum SMALoadingStates
{
SMA_GETTING_SONG_INFO, /**< Retrieving song information. */
SMA_GETTING_STEP_INFO, /**< Retrieving step information. */
NUM_SMALoadingStates /**< The number of states used. */
};
/** @brief Reads a Song from a .SMA file. */ /** @brief Reads a Song from a .SMA file. */
namespace SMALoader namespace SMALoader
{ {
@@ -25,12 +35,22 @@ namespace SMALoader
bool LoadFromSMAFile( const RString &sPath, Song &out ); bool LoadFromSMAFile( const RString &sPath, Song &out );
void GetApplicableFiles( const RString &sPath, vector<RString> &out ); void GetApplicableFiles( const RString &sPath, vector<RString> &out );
bool LoadTimingFromFile( const RString &fn, TimingData &out );
void LoadTimingFromSMAFile( const MsdFile &msd, TimingData &out );
bool LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ); bool LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot ); bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot );
bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong ); bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong );
bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression ); bool LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression );
void ProcessBeatsPerMeasure( TimingData &out, const RString sParam );
bool ProcessBPMs( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessStops( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessDelays( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessTickcounts( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessMultipliers( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessSpeeds( TimingData &out, const int iRowsPerBeat, const RString sParam );
void ProcessFakes( TimingData &out, const int iRowsPerBeat, const RString sParam );
float RowToBeat( RString sLine, const int iRowsPerBeat );
}; };
#endif #endif
+287 -551
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -16,8 +16,6 @@ enum SSCLoadingStates
{ {
GETTING_SONG_INFO, /**< Retrieving song information. */ GETTING_SONG_INFO, /**< Retrieving song information. */
GETTING_STEP_INFO, /**< Retrieving step information. */ GETTING_STEP_INFO, /**< Retrieving step information. */
GETTING_STEP_TIMING_INFO, /**< Retrieving a step's individual timing information. */
GETTING_NOTE_INFO, /**< Retrieving the specific notes. This state may be deprecated. */
NUM_SSCLoadingStates /**< The number of states used. */ NUM_SSCLoadingStates /**< The number of states used. */
}; };
@@ -25,6 +23,8 @@ enum SSCLoadingStates
const float VERSION_RADAR_FAKE = 0.53f; const float VERSION_RADAR_FAKE = 0.53f;
/** @brief The version where WarpSegments started to be utilized. */ /** @brief The version where WarpSegments started to be utilized. */
const float VERSION_WARP_SEGMENT = 0.56f; const float VERSION_WARP_SEGMENT = 0.56f;
/** @brief The version that formally introduced Split Timing. */
const float VERSION_SPLIT_TIMING = 0.7f;
/** /**
* @brief The SSCLoader handles all of the parsing needed for .ssc files. * @brief The SSCLoader handles all of the parsing needed for .ssc files.
@@ -75,6 +75,13 @@ namespace SSCLoader
* @param bFromCache a flag to determine if this song is loaded from a cache file. * @param bFromCache a flag to determine if this song is loaded from a cache file.
*/ */
void TidyUpData( Song &song, bool bFromCache ); void TidyUpData( Song &song, bool bFromCache );
void ProcessWarps( TimingData &, const RString, const float );
void ProcessLabels( TimingData &, const RString );
void ProcessCombos( TimingData &, const RString );
void ProcessSpeeds( TimingData &, const RString );
void ProcessFakes( TimingData &, const RString );
} }
#endif #endif
/** /**
+11 -11
View File
@@ -350,10 +350,10 @@ bool NotesWriterDWI::Write( RString sPath, const Song &out )
/* Write transliterations, if we have them, since DWI doesn't support UTF-8. */ /* Write transliterations, if we have them, since DWI doesn't support UTF-8. */
f.PutLine( ssprintf("#TITLE:%s;", DwiEscape(out.GetTranslitFullTitle()).c_str()) ); f.PutLine( ssprintf("#TITLE:%s;", DwiEscape(out.GetTranslitFullTitle()).c_str()) );
f.PutLine( ssprintf("#ARTIST:%s;", DwiEscape(out.GetTranslitArtist()).c_str()) ); f.PutLine( ssprintf("#ARTIST:%s;", DwiEscape(out.GetTranslitArtist()).c_str()) );
ASSERT( out.m_Timing.m_BPMSegments[0].m_iStartRow == 0 ); ASSERT( out.m_SongTiming.m_BPMSegments[0].m_iStartRow == 0 );
f.PutLine( ssprintf("#FILE:%s;", DwiEscape(out.m_sMusicFile).c_str()) ); f.PutLine( ssprintf("#FILE:%s;", DwiEscape(out.m_sMusicFile).c_str()) );
f.PutLine( ssprintf("#BPM:%.3f;", out.m_Timing.m_BPMSegments[0].GetBPM()) ); f.PutLine( ssprintf("#BPM:%.3f;", out.m_SongTiming.m_BPMSegments[0].GetBPM()) );
f.PutLine( ssprintf("#GAP:%ld;", -lrintf( out.m_Timing.m_fBeat0OffsetInSeconds*1000 )) ); f.PutLine( ssprintf("#GAP:%ld;", -lrintf( out.m_SongTiming.m_fBeat0OffsetInSeconds*1000 )) );
f.PutLine( ssprintf("#SAMPLESTART:%.3f;", out.m_fMusicSampleStartSeconds) ); f.PutLine( ssprintf("#SAMPLESTART:%.3f;", out.m_fMusicSampleStartSeconds) );
f.PutLine( ssprintf("#SAMPLELENGTH:%.3f;", out.m_fMusicSampleLengthSeconds) ); f.PutLine( ssprintf("#SAMPLELENGTH:%.3f;", out.m_fMusicSampleLengthSeconds) );
if( out.m_sCDTitleFile.size() ) if( out.m_sCDTitleFile.size() )
@@ -374,29 +374,29 @@ bool NotesWriterDWI::Write( RString sPath, const Song &out )
break; break;
} }
if( !out.m_Timing.m_StopSegments.empty() ) if( !out.m_SongTiming.m_StopSegments.empty() )
{ {
f.Write( "#FREEZE:" ); f.Write( "#FREEZE:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ ) for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
{ {
const StopSegment &fs = out.m_Timing.m_StopSegments[i]; const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
f.Write( ssprintf("%.3f=%.3f", fs.m_iStartRow * 4.0f / ROWS_PER_BEAT, f.Write( ssprintf("%.3f=%.3f", fs.m_iStartRow * 4.0f / ROWS_PER_BEAT,
roundf(fs.m_fStopSeconds*1000)) ); roundf(fs.m_fStopSeconds*1000)) );
if( i != out.m_Timing.m_StopSegments.size()-1 ) if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.Write( "," ); f.Write( "," );
} }
f.PutLine( ";" ); f.PutLine( ";" );
} }
if( out.m_Timing.m_BPMSegments.size() > 1) if( out.m_SongTiming.m_BPMSegments.size() > 1)
{ {
f.Write( "#CHANGEBPM:" ); f.Write( "#CHANGEBPM:" );
for( unsigned i=1; i<out.m_Timing.m_BPMSegments.size(); i++ ) for( unsigned i=1; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
{ {
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i]; const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
f.Write( ssprintf("%.3f=%.3f", bs.m_iStartRow * 4.0f / ROWS_PER_BEAT, bs.GetBPM() ) ); f.Write( ssprintf("%.3f=%.3f", bs.m_iStartRow * 4.0f / ROWS_PER_BEAT, bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 ) if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
f.Write( "," ); f.Write( "," );
} }
f.PutLine( ";" ); f.PutLine( ";" );
+2 -2
View File
@@ -137,7 +137,7 @@ bool NotesWriterJson::WriteSong( const RString &sFile, const Song &out, bool bWr
root["LyricsFile"] = out.m_sLyricsFile; root["LyricsFile"] = out.m_sLyricsFile;
root["CDTitle"] = out.m_sCDTitleFile; root["CDTitle"] = out.m_sCDTitleFile;
root["Music"] = out.m_sMusicFile; root["Music"] = out.m_sMusicFile;
root["Offset"] = out.m_Timing.m_fBeat0OffsetInSeconds; root["Offset"] = out.m_SongTiming.m_fBeat0OffsetInSeconds;
root["SampleStart"] = out.m_fMusicSampleStartSeconds; root["SampleStart"] = out.m_fMusicSampleStartSeconds;
root["SampleLength"] = out.m_fMusicSampleLengthSeconds; root["SampleLength"] = out.m_fMusicSampleLengthSeconds;
if( out.m_SelectionDisplay == Song::SHOW_ALWAYS ) if( out.m_SelectionDisplay == Song::SHOW_ALWAYS )
@@ -161,7 +161,7 @@ bool NotesWriterJson::WriteSong( const RString &sFile, const Song &out, bool bWr
root["SpecifiedBpmMax"] = out.m_fSpecifiedBPMMax; root["SpecifiedBpmMax"] = out.m_fSpecifiedBPMMax;
} }
Serialize( out.m_Timing, root["TimingData"] ); Serialize( out.m_SongTiming, root["TimingData"] );
JsonUtil::SerializeVectorObjects( out.m_LyricSegments, Serialize, root["LyricSegments"] ); JsonUtil::SerializeVectorObjects( out.m_LyricSegments, Serialize, root["LyricSegments"] );
{ {
+36 -76
View File
@@ -48,7 +48,7 @@ static RString BackgroundChangeToString( const BackgroundChange &bgc )
* @brief Write out the common tags for .SM files. * @brief Write out the common tags for .SM files.
* @param f the file in question. * @param f the file in question.
* @param out the Song in question. */ * @param out the Song in question. */
static void WriteGlobalTags( RageFile &f, const Song &out ) static void WriteGlobalTags( RageFile &f, Song &out )
{ {
f.PutLine( ssprintf( "#TITLE:%s;", SmEscape(out.m_sMainTitle).c_str() ) ); f.PutLine( ssprintf( "#TITLE:%s;", SmEscape(out.m_sMainTitle).c_str() ) );
f.PutLine( ssprintf( "#SUBTITLE:%s;", SmEscape(out.m_sSubTitle).c_str() ) ); f.PutLine( ssprintf( "#SUBTITLE:%s;", SmEscape(out.m_sSubTitle).c_str() ) );
@@ -63,20 +63,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( ssprintf( "#LYRICSPATH:%s;", SmEscape(out.m_sLyricsFile).c_str() ) ); f.PutLine( ssprintf( "#LYRICSPATH:%s;", SmEscape(out.m_sLyricsFile).c_str() ) );
f.PutLine( ssprintf( "#CDTITLE:%s;", SmEscape(out.m_sCDTitleFile).c_str() ) ); f.PutLine( ssprintf( "#CDTITLE:%s;", SmEscape(out.m_sCDTitleFile).c_str() ) );
f.PutLine( ssprintf( "#MUSIC:%s;", SmEscape(out.m_sMusicFile).c_str() ) ); f.PutLine( ssprintf( "#MUSIC:%s;", SmEscape(out.m_sMusicFile).c_str() ) );
f.PutLine( ssprintf( "#OFFSET:%.3f;", out.m_SongTiming.m_fBeat0OffsetInSeconds ) );
{
vector<RString> vs;
FOREACH_ENUM( InstrumentTrack, it )
if( out.HasInstrumentTrack(it) )
vs.push_back( InstrumentTrackToString(it) +
"=" + out.m_sInstrumentTrackFile[it] );
if( !vs.empty() )
{
RString s = join( ",", vs );
f.PutLine( "#INSTRUMENTTRACK:" + s + ";\n" );
}
}
f.PutLine( ssprintf( "#OFFSET:%.3f;", out.m_Timing.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#SAMPLESTART:%.3f;", out.m_fMusicSampleStartSeconds ) ); f.PutLine( ssprintf( "#SAMPLESTART:%.3f;", out.m_fMusicSampleStartSeconds ) );
f.PutLine( ssprintf( "#SAMPLELENGTH:%.3f;", out.m_fMusicSampleLengthSeconds ) ); f.PutLine( ssprintf( "#SAMPLELENGTH:%.3f;", out.m_fMusicSampleLengthSeconds ) );
if( out.m_fSpecifiedLastBeat > 0 ) if( out.m_fSpecifiedLastBeat > 0 )
@@ -111,79 +98,52 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.Write( "#BPMS:" ); f.Write( "#BPMS:" );
for( unsigned i=0; i<out.m_Timing.m_BPMSegments.size(); i++ ) for( unsigned i=0; i<out.m_SongTiming.m_BPMSegments.size(); i++ )
{ {
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i]; const BPMSegment &bs = out.m_SongTiming.m_BPMSegments[i];
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(bs.m_iStartRow), bs.GetBPM() ) ); f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(bs.m_iStartRow), bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 ) if( i != out.m_SongTiming.m_BPMSegments.size()-1 )
f.Write( "," ); f.Write( "," );
} }
f.PutLine( ";" ); f.PutLine( ";" );
f.Write( "#STOPS:" ); unsigned wSize = out.m_SongTiming.m_WarpSegments.size();
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ ) if( wSize > 0 )
{ {
const StopSegment &fs = out.m_Timing.m_StopSegments[i]; for( unsigned i=0; i < wSize; i++ )
{
int iRow = out.m_SongTiming.m_WarpSegments[i].m_iStartRow;
float fBPS = 60 / out.m_SongTiming.GetBPMAtRow(iRow);
float fSkip = fBPS * out.m_SongTiming.m_WarpSegments[i].m_fLengthBeats;
StopSegment ss;
ss.m_iStartRow = iRow;
ss.m_fStopSeconds = -fSkip;
ss.m_bDelay = false; // Best to be sure.
out.m_SongTiming.AddStopSegment( ss );
}
}
f.Write( "#STOPS:" );
for( unsigned i=0; i<out.m_SongTiming.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_SongTiming.m_StopSegments[i];
int iRow = fs.m_iStartRow;
float fBeat = NoteRowToBeat(!fs.m_bDelay ? iRow : iRow - 1);
if(!fs.m_bDelay) if(!fs.m_bDelay)
{ {
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) ); f.PutLine( ssprintf( "%.3f=%.3f", fBeat, fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 ) if( i != out.m_SongTiming.m_StopSegments.size()-1 )
f.Write( "," ); f.Write( "," );
} if( fs.m_fStopSeconds < 0 )
}
f.PutLine( ";" );
f.Write( "#ATTACKS:" );
for( unsigned j = 0; j < out.m_Attacks.size(); j++ )
{ {
const Attack &a = out.m_Attacks[j]; out.m_SongTiming.m_StopSegments.erase(
f.Write( ssprintf( "TIME=%.2f:LEN=%.2f:MODS=%s", out.m_SongTiming.m_StopSegments.begin()+i,
a.fStartSecond, a.fSecsRemaining, a.sModifiers.c_str() ) ); out.m_SongTiming.m_StopSegments.begin()+i+1 );
i--;
if( j+1 < out.m_Attacks.size() )
f.Write( ":" );
f.PutLine( "" );
}
f.PutLine( ";" );
f.Write( "#DELAYS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if( fs.m_bDelay )
{
f.PutLine( ssprintf( "%.3f=%.3f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
} }
} }
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_vTimeSignatureSegments.empty() );
f.Write( "#TIMESIGNATURES:" );
FOREACH_CONST( TimeSignatureSegment, out.m_Timing.m_vTimeSignatureSegments, iter )
{
f.PutLine( ssprintf( "%.3f=%d=%d", NoteRowToBeat(iter->m_iStartRow),
iter->m_iNumerator, iter->m_iDenominator ) );
vector<TimeSignatureSegment>::const_iterator iter2 = iter;
iter2++;
if( iter2 != out.m_Timing.m_vTimeSignatureSegments.end() )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_TickcountSegments.empty() );
f.Write( "#TICKCOUNTS:" );
for( unsigned i=0; i<out.m_Timing.m_TickcountSegments.size(); i++ )
{
const TickcountSegment &ts = out.m_Timing.m_TickcountSegments[i];
f.PutLine( ssprintf( "%.3f=%d", NoteRowToBeat(ts.m_iStartRow), ts.m_iTicks ) );
if( i != out.m_Timing.m_TickcountSegments.size()-1 )
f.Write( "," );
} }
f.PutLine( ";" ); f.PutLine( ";" );
@@ -277,8 +237,8 @@ static RString GetSMNotesTag( const Song &song, const Steps &in )
lines.push_back( ssprintf( " %d:", in.GetMeter() ) ); lines.push_back( ssprintf( " %d:", in.GetMeter() ) );
vector<RString> asRadarValues; vector<RString> asRadarValues;
// SM files don't use fakes for radar data. Keep it that way. // OpenITG simfiles use 11 radar categories.
int categories = NUM_RadarCategory - 1; int categories = 11;
FOREACH_PlayerNumber( pn ) FOREACH_PlayerNumber( pn )
{ {
const RadarValues &rv = in.GetRadarValues( pn ); const RadarValues &rv = in.GetRadarValues( pn );
@@ -300,7 +260,7 @@ static RString GetSMNotesTag( const Song &song, const Steps &in )
return JoinLineList( lines ); return JoinLineList( lines );
} }
bool NotesWriterSM::Write( RString sPath, const Song &out, const vector<Steps*>& vpStepsToSave ) bool NotesWriterSM::Write( RString sPath, Song &out, const vector<Steps*>& vpStepsToSave )
{ {
int flags = RageFile::WRITE; int flags = RageFile::WRITE;
+1 -1
View File
@@ -11,7 +11,7 @@ namespace NotesWriterSM
* @param sPath the path to write the file. * @param sPath the path to write the file.
* @param out the Song to be written out. * @param out the Song to be written out.
* @return its success or failure. */ * @return its success or failure. */
bool Write( RString sPath, const Song &out, const vector<Steps*>& vpStepsToSave ); bool Write( RString sPath, Song &out, const vector<Steps*>& vpStepsToSave );
/** /**
* @brief Get some contents about the edit file first. * @brief Get some contents about the edit file first.
* @param pSong the Song in question. * @param pSong the Song in question.
+129 -129
View File
@@ -42,7 +42,128 @@ static RString BackgroundChangeToString( const BackgroundChange &bgc )
} }
/** /**
* @brief Write out the common tags for .SM files. * @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
* @return the joined lines. */
static RString JoinLineList( vector<RString> &lines )
{
for( unsigned i = 0; i < lines.size(); ++i )
TrimRight( lines[i] );
// Skip leading blanks.
unsigned j = 0;
while( j < lines.size() && lines.size() == 0 )
++j;
return join( "\r\n", lines.begin()+j, lines.end() );
}
// A utility class to write timing tags more easily!
struct TimingTagWriter {
vector<RString> *m_pvsLines;
RString m_sNext;
TimingTagWriter( vector<RString> *pvsLines ): m_pvsLines (pvsLines) { }
void Write( const int row, const char *value )
{
m_pvsLines->push_back( m_sNext + ssprintf( "%.6f=%s", NoteRowToBeat(row), value ) );
m_sNext = ",";
}
void Write( const int row, const float value ) { Write( row, ssprintf( "%.6f", value ) ); }
void Write( const int row, const int value ) { Write( row, ssprintf( "%d", value ) ); }
void Write( const int row, const int a, const int b ) { Write( row, ssprintf( "%d=%d", a, b ) ); }
void Write( const int row, const float a, const float b ) { Write( row, ssprintf( "%.6f=%.6f", a, b) ); }
void Write( const int row, const float a, const float b, const unsigned short c )
{ Write( row, ssprintf( "%.6f=%.6f=%hd", a, b, c) ); }
void Init( const RString sTag ) { m_sNext = "#" + sTag + ":"; }
void Finish( ) { m_pvsLines->push_back( ( m_sNext != "," ? m_sNext : "" ) + ";" ); }
};
static void GetTimingTags( vector<RString> &lines, TimingData timing, bool bIsSong = false )
{
TimingTagWriter w ( &lines );
timing.TidyUpData();
w.Init( "BPMS" );
FOREACH_CONST( BPMSegment, timing.m_BPMSegments, bs )
w.Write( bs->m_iStartRow, bs->GetBPM() );
w.Finish();
w.Init( "STOPS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
if( !ss->m_bDelay )
w.Write( ss->m_iStartRow, ss->m_fStopSeconds );
w.Finish();
w.Init( "DELAYS" );
FOREACH_CONST( StopSegment, timing.m_StopSegments, ss )
if( ss->m_bDelay )
w.Write( ss->m_iStartRow, ss->m_fStopSeconds );
w.Finish();
w.Init( "WARPS" );
FOREACH_CONST( WarpSegment, timing.m_WarpSegments, ws )
w.Write( ws->m_iStartRow, ws->m_fLengthBeats );
w.Finish();
ASSERT( !timing.m_vTimeSignatureSegments.empty() );
w.Init( "TIMESIGNATURES" );
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
w.Write( iter->m_iStartRow, iter->m_iNumerator, iter->m_iDenominator );
w.Finish();
ASSERT( !timing.m_TickcountSegments.empty() );
w.Init( "TICKCOUNTS" );
FOREACH_CONST( TickcountSegment, timing.m_TickcountSegments, ts )
w.Write( ts->m_iStartRow, ts->m_iTicks );
w.Finish();
ASSERT( !timing.m_ComboSegments.empty() );
w.Init( "COMBOS" );
FOREACH_CONST( ComboSegment, timing.m_ComboSegments, cs )
w.Write( cs->m_iStartRow, cs->m_iCombo );
w.Finish();
// Song Timing should only have the initial value.
w.Init( "SPEEDS" );
FOREACH_CONST( SpeedSegment, timing.m_SpeedSegments, ss )
w.Write( ss->m_iStartRow, ss->m_fPercent, ss->m_fWait, ss->m_usMode );
w.Finish();
if( !bIsSong )
{
w.Init( "FAKES" );
FOREACH_CONST( FakeSegment, timing.m_FakeSegments, fs )
w.Write( fs->m_iStartRow, fs->m_fLengthBeats );
w.Finish();
}
w.Init( "LABELS" );
FOREACH_CONST( LabelSegment, timing.m_LabelSegments, ls )
w.Write( ls->m_iStartRow, ls->m_sLabel.c_str() );
w.Finish();
}
static void WriteTimingTags( RageFile &f, const TimingData &timing, bool bIsSong = false )
{
vector<RString> lines;
GetTimingTags( lines, timing, bIsSong );
f.PutLine( JoinLineList( lines ) );
}
/**
* @brief Write out the common tags for .SSC files.
* @param f the file in question. * @param f the file in question.
* @param out the Song in question. */ * @param out the Song in question. */
static void WriteGlobalTags( RageFile &f, const Song &out ) static void WriteGlobalTags( RageFile &f, const Song &out )
@@ -74,7 +195,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( "#INSTRUMENTTRACK:" + s + ";\n" ); f.PutLine( "#INSTRUMENTTRACK:" + s + ";\n" );
} }
} }
f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_Timing.m_fBeat0OffsetInSeconds ) ); f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_SongTiming.m_fBeat0OffsetInSeconds ) );
f.PutLine( ssprintf( "#SAMPLESTART:%.6f;", out.m_fMusicSampleStartSeconds ) ); f.PutLine( ssprintf( "#SAMPLESTART:%.6f;", out.m_fMusicSampleStartSeconds ) );
f.PutLine( ssprintf( "#SAMPLELENGTH:%.6f;", out.m_fMusicSampleLengthSeconds ) ); f.PutLine( ssprintf( "#SAMPLELENGTH:%.6f;", out.m_fMusicSampleLengthSeconds ) );
if( out.m_fSpecifiedLastBeat > 0 ) if( out.m_fSpecifiedLastBeat > 0 )
@@ -106,105 +227,7 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
break; break;
} }
f.Write( "#BPMS:" ); WriteTimingTags( f, out.m_SongTiming, true );
for( unsigned i=0; i<out.m_Timing.m_BPMSegments.size(); i++ )
{
const BPMSegment &bs = out.m_Timing.m_BPMSegments[i];
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(bs.m_iStartRow), bs.GetBPM() ) );
if( i != out.m_Timing.m_BPMSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
f.Write( "#STOPS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if(!fs.m_bDelay)
{
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
f.Write( "#DELAYS:" );
for( unsigned i=0; i<out.m_Timing.m_StopSegments.size(); i++ )
{
const StopSegment &fs = out.m_Timing.m_StopSegments[i];
if( fs.m_bDelay )
{
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(fs.m_iStartRow), fs.m_fStopSeconds ) );
if( i != out.m_Timing.m_StopSegments.size()-1 )
f.Write( "," );
}
}
f.PutLine( ";" );
f.Write( "#WARPS:" );
for( unsigned i=0; i<out.m_Timing.m_WarpSegments.size(); i++ )
{
const WarpSegment &ws = out.m_Timing.m_WarpSegments[i];
f.PutLine( ssprintf( "%.6f=%.6f", NoteRowToBeat(ws.m_iStartRow), ws.m_fEndBeat ) );
if( i != out.m_Timing.m_WarpSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_vTimeSignatureSegments.empty() );
f.Write( "#TIMESIGNATURES:" );
FOREACH_CONST( TimeSignatureSegment, out.m_Timing.m_vTimeSignatureSegments, iter )
{
f.PutLine( ssprintf( "%.6f=%d=%d", NoteRowToBeat(iter->m_iStartRow), iter->m_iNumerator, iter->m_iDenominator ) );
vector<TimeSignatureSegment>::const_iterator iter2 = iter;
iter2++;
if( iter2 != out.m_Timing.m_vTimeSignatureSegments.end() )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_TickcountSegments.empty() );
f.Write( "#TICKCOUNTS:" );
for( unsigned i=0; i<out.m_Timing.m_TickcountSegments.size(); i++ )
{
const TickcountSegment &ts = out.m_Timing.m_TickcountSegments[i];
f.PutLine( ssprintf( "%.6f=%d", NoteRowToBeat(ts.m_iStartRow), ts.m_iTicks ) );
if( i != out.m_Timing.m_TickcountSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
ASSERT( !out.m_Timing.m_ComboSegments.empty() );
f.Write( "#COMBOS:" );
for( unsigned i=0; i<out.m_Timing.m_ComboSegments.size(); i++ )
{
const ComboSegment &cs = out.m_Timing.m_ComboSegments[i];
f.PutLine( ssprintf( "%.6f=%d", NoteRowToBeat(cs.m_iStartRow), cs.m_iCombo ) );
if( i != out.m_Timing.m_ComboSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
f.Write( "#LABELS:" );
for( unsigned i=0; i<out.m_Timing.m_LabelSegments.size(); i++ )
{
const LabelSegment &ls = out.m_Timing.m_LabelSegments[i];
f.PutLine( ssprintf( "%.6f=%s", NoteRowToBeat(ls.m_iStartRow), ls.m_sLabel.c_str() ) );
if( i != out.m_Timing.m_LabelSegments.size()-1 )
f.Write( "," );
}
f.PutLine( ";" );
FOREACH_BackgroundLayer( b ) FOREACH_BackgroundLayer( b )
{ {
@@ -258,23 +281,6 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
f.PutLine( ";" ); f.PutLine( ";" );
} }
/**
* @brief Turn a vector of lines into a single line joined by newline characters.
* @param lines the list of lines to join.
* @return the joined lines. */
static RString JoinLineList( vector<RString> &lines )
{
for( unsigned i = 0; i < lines.size(); ++i )
TrimRight( lines[i] );
// Skip leading blanks.
unsigned j = 0;
while( j < lines.size() && lines.size() == 0 )
++j;
return join( "\r\n", lines.begin()+j, lines.end() );
}
/** /**
* @brief Retrieve the individual batches of NoteData. * @brief Retrieve the individual batches of NoteData.
* @param song the Song in question. * @param song the Song in question.
@@ -307,18 +313,11 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa
lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) ); lines.push_back( ssprintf( "#CREDIT:%s;", SmEscape(in.GetCredit()).c_str() ) );
/* GetTimingTags( lines, in.m_Timing );
* TODO: Remove this block, transplant above code
* below for Split Timing. -Wolfman2000 */ // For now, attacks are NOT in use for the step.
lines.push_back( "#BPMS:;" );
lines.push_back( "#STOPS:;" );
lines.push_back( "#DELAYS:;" );
lines.push_back( "#WARPS:;" );
lines.push_back( "#LABELS:;" );
lines.push_back( "#TIMESIGNATURES:;" );
lines.push_back( "#TICKCOUNTS:;" );
lines.push_back( "#ATTACKS:;" ); lines.push_back( "#ATTACKS:;" );
lines.push_back( "#COMBOS:;" ); lines.push_back( ssprintf( "#OFFSET:%.6f;", in.m_Timing.m_fBeat0OffsetInSeconds ) );
RString sNoteData; RString sNoteData;
in.GetSMNoteData( sNoteData ); in.GetSMNoteData( sNoteData );
@@ -350,6 +349,7 @@ bool NotesWriterSSC::Write( RString sPath, const Song &out, const vector<Steps*>
} }
WriteGlobalTags( f, out ); WriteGlobalTags( f, out );
if( bSavingCache ) if( bSavingCache )
{ {
f.PutLine( ssprintf( "// cache tags:" ) ); f.PutLine( ssprintf( "// cache tags:" ) );
+44 -33
View File
@@ -506,7 +506,7 @@ void Player::Load()
m_LastTapNoteScore = TNS_None; m_LastTapNoteScore = TNS_None;
// The editor can start playing in the middle of the song. // The editor can start playing in the middle of the song.
const int iNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat ); const int iNoteRow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat );
m_iFirstUncrossedRow = iNoteRow - 1; m_iFirstUncrossedRow = iNoteRow - 1;
m_pJudgedRows->Reset( iNoteRow ); m_pJudgedRows->Reset( iNoteRow );
@@ -541,6 +541,8 @@ void Player::Load()
if( GAMESTATE->m_pCurGame->m_bAllowHopos ) if( GAMESTATE->m_pCurGame->m_bAllowHopos )
NoteDataUtil::SetHopoPossibleFlags( pSong, m_NoteData ); NoteDataUtil::SetHopoPossibleFlags( pSong, m_NoteData );
m_Timing = &GAMESTATE->m_pCurSteps[pn]->m_Timing;
switch( GAMESTATE->m_PlayMode ) switch( GAMESTATE->m_PlayMode )
{ {
case PLAY_MODE_RAVE: case PLAY_MODE_RAVE:
@@ -693,7 +695,7 @@ void Player::Update( float fDeltaTime )
return; return;
} }
const float fSongBeat = GAMESTATE->m_fSongBeat; const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat ); const int iSongRow = BeatToNoteRow( fSongBeat );
// Optimization: Don't spend time processing the things below that won't show // Optimization: Don't spend time processing the things below that won't show
@@ -795,7 +797,7 @@ void Player::Update( float fDeltaTime )
// Check for a strum miss // Check for a strum miss
if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 && if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 &&
m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < GAMESTATE->m_fMusicSeconds ) m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < m_pPlayerState->m_Position.m_fMusicSeconds )
{ {
DoStrumMiss(); DoStrumMiss();
} }
@@ -917,12 +919,12 @@ void Player::Update( float fDeltaTime )
/* We want to send the crossed row message exactly when we cross the row--not /* We want to send the crossed row message exactly when we cross the row--not
* .5 before the row. Use a very slow song (around 2 BPM) as a test case: without * .5 before the row. Use a very slow song (around 2 BPM) as a test case: without
* rounding, autoplay steps early. -glenn */ * rounding, autoplay steps early. -glenn */
const int iRowNow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat ); const int iRowNow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat );
if( iRowNow >= 0 ) if( iRowNow >= 0 )
{ {
if( GAMESTATE->IsPlayerEnabled(m_pPlayerState) ) if( GAMESTATE->IsPlayerEnabled(m_pPlayerState) )
{ {
if(GAMESTATE->m_bDelay) if(m_pPlayerState->m_Position.m_bDelay)
{ {
if( !m_bDelay ) if( !m_bDelay )
m_bDelay = true; m_bDelay = true;
@@ -1528,7 +1530,7 @@ int Player::GetClosestNoteDirectional( int col, int iStartRow, int iEndRow, bool
// Is this the row we want? // Is this the row we want?
do { do {
const TapNote &tn = begin->second; const TapNote &tn = begin->second;
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( begin->first ) ) if( m_Timing->IsWarpAtRow( begin->first ) || m_Timing->IsFakeAtRow( begin->first ) )
break; break;
if( tn.type == TapNote::empty ) if( tn.type == TapNote::empty )
break; break;
@@ -1579,7 +1581,7 @@ int Player::GetClosestNonEmptyRowDirectional( int iStartRow, int iEndRow, bool b
++iter; ++iter;
continue; continue;
} }
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iter.Row() ) ) if( m_Timing->IsWarpAtRow( iter.Row() ) || m_Timing->IsFakeAtRow( iter.Row() ) )
{ {
++iter; ++iter;
continue; continue;
@@ -1650,7 +1652,7 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
} }
// Handle hammer-ons and pull-offs // Handle hammer-ons and pull-offs
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago(); const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago();
int iHopoCol = -1; int iHopoCol = -1;
bool bDoHopo = bool bDoHopo =
m_pPlayerState->m_fLastHopoNoteMusicSeconds != -1 && m_pPlayerState->m_fLastHopoNoteMusicSeconds != -1 &&
@@ -1703,7 +1705,7 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
// Check if this fret breaks all active holds. // Check if this fret breaks all active holds.
if( !bRelease ) if( !bRelease )
{ {
const float fSongBeat = GAMESTATE->m_fSongBeat; const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat ); const int iSongRow = BeatToNoteRow( fSongBeat );
int iMaxHoldCol = -1; int iMaxHoldCol = -1;
@@ -1745,7 +1747,7 @@ void Player::Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRel
DoStrumMiss(); DoStrumMiss();
} }
m_pPlayerState->m_fLastStrumMusicSeconds = GAMESTATE->m_fMusicSeconds; m_pPlayerState->m_fLastStrumMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds;
StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged ); StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged );
} }
@@ -1794,7 +1796,7 @@ void Player::ScoreAllActiveHoldsLetGo()
{ {
if( PENALIZE_TAP_SCORE_NONE ) if( PENALIZE_TAP_SCORE_NONE )
{ {
const float fSongBeat = GAMESTATE->m_fSongBeat; const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
const int iSongRow = BeatToNoteRow( fSongBeat ); const int iSongRow = BeatToNoteRow( fSongBeat );
// Score all active holds to NotHeld // Score all active holds to NotHeld
@@ -1857,8 +1859,8 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
// Do everything that depends on a RageTimer here; // Do everything that depends on a RageTimer here;
// set your breakpoints somewhere after this block. // set your breakpoints somewhere after this block.
const float fLastBeatUpdate = GAMESTATE->m_LastBeatUpdate.Ago(); const float fLastBeatUpdate = m_pPlayerState->m_Position.m_LastBeatUpdate.Ago();
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago(); const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago();
const float fTimeSinceStep = tm.Ago(); const float fTimeSinceStep = tm.Ago();
switch( pbt ) switch( pbt )
@@ -1873,7 +1875,16 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
break; break;
} }
const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : GAMESTATE->m_fSongBeat; float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat;
if( GAMESTATE->m_pCurSong )
{
fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds );
if( GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber] )
fSongBeat = m_Timing->GetBeatFromElapsedTime( fPositionSeconds );
}
const int iSongRow = row == -1 ? BeatToNoteRow( fSongBeat ) : row; const int iSongRow = row == -1 ? BeatToNoteRow( fSongBeat ) : row;
if( col != -1 && !bRelease ) if( col != -1 && !bRelease )
@@ -2011,8 +2022,8 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
* "jack hammers." Hmm. * "jack hammers." Hmm.
*/ */
const int iStepSearchRows = max( const int iStepSearchRows = max(
BeatToNoteRow( GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( GAMESTATE->m_fMusicSeconds + StepSearchDistance ) ) - iSongRow, BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds + StepSearchDistance ) ) - iSongRow,
iSongRow - BeatToNoteRow( GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( GAMESTATE->m_fMusicSeconds - StepSearchDistance ) ) iSongRow - BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds - StepSearchDistance ) )
) + ROWS_PER_BEAT; ) + ROWS_PER_BEAT;
int iRowOfOverlappingNoteOrRow = row; int iRowOfOverlappingNoteOrRow = row;
if( row == -1 ) if( row == -1 )
@@ -2039,7 +2050,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
float fNoteOffset = 0.0f; float fNoteOffset = 0.0f;
// we need this later if we are autosyncing // we need this later if we are autosyncing
const float fStepBeat = NoteRowToBeat( iRowOfOverlappingNoteOrRow ); const float fStepBeat = NoteRowToBeat( iRowOfOverlappingNoteOrRow );
const float fStepSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fStepBeat); const float fStepSeconds = m_Timing->GetElapsedTimeFromBeat(fStepBeat);
if( row == -1 ) if( row == -1 )
{ {
@@ -2048,7 +2059,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
/* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure /* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure
* out what the music time is as of now. */ * out what the music time is as of now. */
const float fCurrentMusicSeconds = GAMESTATE->m_fMusicSeconds + (fLastBeatUpdate*GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate); const float fCurrentMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds + (fLastBeatUpdate*GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate);
// ... which means it happened at this point in the music: // ... which means it happened at this point in the music:
const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceStep * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceStep * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
@@ -2089,7 +2100,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
// Stepped too close to mine? // Stepped too close to mine?
if( !bRelease && ( REQUIRE_STEP_ON_MINES == !bHeld ) && if( !bRelease && ( REQUIRE_STEP_ON_MINES == !bHeld ) &&
fSecondsFromExact <= GetWindowSeconds(TW_Mine) && fSecondsFromExact <= GetWindowSeconds(TW_Mine) &&
!GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow(iSongRow) ) !m_Timing->IsWarpAtRow(iSongRow) && !m_Timing->IsFakeAtRow(iSongRow))
score = TNS_HitMine; score = TNS_HitMine;
break; break;
@@ -2557,14 +2568,14 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
{ {
//LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat ); //LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat );
int iMissIfOlderThanThisRow; int iMissIfOlderThanThisRow;
const float fEarliestTime = GAMESTATE->m_fMusicSeconds - fMissIfOlderThanSeconds; const float fEarliestTime = m_pPlayerState->m_Position.m_fMusicSeconds - fMissIfOlderThanSeconds;
{ {
bool bFreeze, bDelay; bool bFreeze, bDelay;
float fMissIfOlderThanThisBeat; float fMissIfOlderThanThisBeat;
float fThrowAway; float fThrowAway;
int iWarpBeginRow; int iWarpBeginRow;
float fWarpLength; float fWarpLength;
GAMESTATE->m_pCurSong->m_Timing.GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength ); m_Timing->GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength );
iMissIfOlderThanThisRow = BeatToNoteRow( fMissIfOlderThanThisBeat ); iMissIfOlderThanThisRow = BeatToNoteRow( fMissIfOlderThanThisBeat );
if( bFreeze || bDelay ) if( bFreeze || bDelay )
@@ -2586,8 +2597,8 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
if( !NeedsTapJudging(tn) ) if( !NeedsTapJudging(tn) )
continue; continue;
// Ignore all notes that are skipped via WARPS. // Ignore all notes in WarpSegments or FakeSegments.
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iter.Row() ) ) if( m_Timing->IsWarpAtRow( iter.Row() ) || m_Timing->IsFakeAtRow( iter.Row() ) )
continue; continue;
if( tn.type == TapNote::mine ) if( tn.type == TapNote::mine )
@@ -2610,7 +2621,7 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds )
void Player::UpdateJudgedRows() void Player::UpdateJudgedRows()
{ {
const int iEndRow = BeatToNoteRow( GAMESTATE->m_fSongBeat ); const int iEndRow = BeatToNoteRow( m_pPlayerState->m_Position.m_fSongBeat );
bool bAllJudged = true; bool bAllJudged = true;
const bool bSeparately = GAMESTATE->GetCurrentGame()->m_bCountNotesSeparately; const bool bSeparately = GAMESTATE->GetCurrentGame()->m_bCountNotesSeparately;
@@ -2621,8 +2632,8 @@ void Player::UpdateJudgedRows()
{ {
int iRow = iter.Row(); int iRow = iter.Row();
// If row is within a warp section, ignore it. -aj // Do not judge arrows in WarpSegments or FakeSegments
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow(iRow) ) if( m_Timing->IsWarpAtRow(iRow) || m_Timing->IsFakeAtRow(iRow) )
continue; continue;
if( iLastSeenRow != iRow ) if( iLastSeenRow != iRow )
@@ -2843,13 +2854,13 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now )
int iCheckpointFrequencyRows = ROWS_PER_BEAT/2; int iCheckpointFrequencyRows = ROWS_PER_BEAT/2;
if( CHECKPOINTS_USE_TICKCOUNTS ) if( CHECKPOINTS_USE_TICKCOUNTS )
{ {
int tickCurrent = GAMESTATE->m_pCurSong->m_Timing.GetTickcountAtRow( iLastRowCrossed ); int tickCurrent = m_Timing->GetTickcountAtRow( iLastRowCrossed );
// There are some charts that don't want tickcounts involved at all. // There are some charts that don't want tickcounts involved at all.
iCheckpointFrequencyRows = (tickCurrent > 0 ? ROWS_PER_BEAT / tickCurrent : 0); iCheckpointFrequencyRows = (tickCurrent > 0 ? ROWS_PER_BEAT / tickCurrent : 0);
} }
else if( CHECKPOINTS_USE_TIME_SIGNATURES ) else if( CHECKPOINTS_USE_TIME_SIGNATURES )
{ {
TimeSignatureSegment tSignature = GAMESTATE->m_pCurSong->m_Timing.GetTimeSignatureSegmentAtBeat( NoteRowToBeat( iLastRowCrossed ) ); TimeSignatureSegment tSignature = m_Timing->GetTimeSignatureSegmentAtBeat( NoteRowToBeat( iLastRowCrossed ) );
// Most songs are in 4/4 time. The frequency for checking tick counts should reflect that. // Most songs are in 4/4 time. The frequency for checking tick counts should reflect that.
iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature.m_iDenominator / (tSignature.m_iNumerator * 4); iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature.m_iDenominator / (tSignature.m_iNumerator * 4);
@@ -2956,8 +2967,8 @@ void Player::HandleTapRowScore( unsigned row )
bNoCheating = false; bNoCheating = false;
#endif #endif
// Warp hackery. -aj // Do not score rows in WarpSegments or FakeSegments
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( row ) ) if( m_Timing->IsWarpAtRow( row ) || m_Timing->IsFakeAtRow( row ) )
return; return;
if( GAMESTATE->m_bDemonstrationOrJukebox ) if( GAMESTATE->m_bDemonstrationOrJukebox )
@@ -3060,8 +3071,8 @@ void Player::HandleHoldCheckpoint( int iRow, int iNumHoldsHeldThisRow, int iNumH
bNoCheating = false; bNoCheating = false;
#endif #endif
// More warp hackery. -aj // WarpSegments and FakeSegments aren't judged in any way.
if( GAMESTATE->m_pCurSong->m_Timing.IsWarpAtRow( iRow ) ) if( m_Timing->IsWarpAtRow( iRow ) || m_Timing->IsFakeAtRow( iRow ) )
return; return;
// don't accumulate combo if AutoPlay is on. // don't accumulate combo if AutoPlay is on.
@@ -3257,7 +3268,7 @@ void Player::SetCombo( int iCombo, int iMisses )
} }
else else
{ {
bPastBeginning = GAMESTATE->m_fMusicSeconds > GAMESTATE->m_pCurSong->m_fMusicLengthSeconds * PERCENT_UNTIL_COLOR_COMBO; bPastBeginning = m_pPlayerState->m_Position.m_fMusicSeconds > GAMESTATE->m_pCurSong->m_fMusicLengthSeconds * PERCENT_UNTIL_COLOR_COMBO;
} }
if( m_bSendJudgmentAndComboMessages ) if( m_bSendJudgmentAndComboMessages )
+2
View File
@@ -10,6 +10,7 @@
#include "ScreenMessage.h" #include "ScreenMessage.h"
#include "ThemeMetric.h" #include "ThemeMetric.h"
#include "InputEventPlus.h" #include "InputEventPlus.h"
#include "TimingData.h"
class ScoreDisplay; class ScoreDisplay;
class LifeMeter; class LifeMeter;
@@ -147,6 +148,7 @@ protected:
PlayerState *m_pPlayerState; PlayerState *m_pPlayerState;
/** @brief The player's present stage stats. */ /** @brief The player's present stage stats. */
PlayerStageStats *m_pPlayerStageStats; PlayerStageStats *m_pPlayerStageStats;
TimingData *m_Timing;
float m_fNoteFieldHeight; float m_fNoteFieldHeight;
bool m_bPaused; bool m_bPaused;
+1 -1
View File
@@ -680,7 +680,7 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const
bool PlayerOptions::IsEasierForSongAndSteps( Song* pSong, Steps* pSteps, PlayerNumber pn ) const bool PlayerOptions::IsEasierForSongAndSteps( Song* pSong, Steps* pSteps, PlayerNumber pn ) const
{ {
if( m_fTimeSpacing && pSong->HasSignificantBpmChangesOrStops() ) if( m_fTimeSpacing && pSteps->HasSignificantTimingChanges() )
return true; return true;
const RadarValues &rv = pSteps->GetRadarValues( pn ); const RadarValues &rv = pSteps->GetRadarValues( pn );
if( m_bTransforms[TRANSFORM_NOHOLDS] && rv[RadarCategory_Holds]>0 ) if( m_bTransforms[TRANSFORM_NOHOLDS] && rv[RadarCategory_Holds]>0 )
+2 -1
View File
@@ -35,8 +35,9 @@ public:
m_fPassmark(0), m_SpeedfPassmark(1.0f), m_fPassmark(0), m_SpeedfPassmark(1.0f),
m_fRandomSpeed(0), m_SpeedfRandomSpeed(1.0f), m_fRandomSpeed(0), m_SpeedfRandomSpeed(1.0f),
m_bMuteOnError(false), m_FailType(FAIL_IMMEDIATE), m_bMuteOnError(false), m_FailType(FAIL_IMMEDIATE),
m_ScoreDisplay(SCORING_ADD), m_sNoteSkin("") m_ScoreDisplay(SCORING_ADD)
{ {
m_sNoteSkin = "";
ZERO( m_fAccels ); ONE( m_SpeedfAccels ); ZERO( m_fAccels ); ONE( m_SpeedfAccels );
ZERO( m_fEffects ); ONE( m_SpeedfEffects ); ZERO( m_fEffects ); ONE( m_SpeedfEffects );
ZERO( m_fAppearances ); ONE( m_SpeedfAppearances ); ZERO( m_fAppearances ); ONE( m_SpeedfAppearances );
+9 -3
View File
@@ -69,8 +69,8 @@ void PlayerState::Update( float fDelta )
bool bCurrentlyEnabled = bool bCurrentlyEnabled =
attack.bGlobal || attack.bGlobal ||
( attack.fStartSecond < GAMESTATE->m_fMusicSeconds && ( attack.fStartSecond < m_Position.m_fMusicSeconds &&
GAMESTATE->m_fMusicSeconds < attack.fStartSecond+attack.fSecsRemaining ); m_Position.m_fMusicSeconds < attack.fStartSecond+attack.fSecsRemaining );
if( m_ActiveAttacks[s].bOn == bCurrentlyEnabled ) if( m_ActiveAttacks[s].bOn == bCurrentlyEnabled )
continue; // OK continue; // OK
@@ -116,7 +116,7 @@ void PlayerState::LaunchAttack( const Attack& a )
* so Player::Update knows to apply attack transforms correctly. (yuck) */ * so Player::Update knows to apply attack transforms correctly. (yuck) */
m_ModsToApply.push_back( attack ); m_ModsToApply.push_back( attack );
if( attack.fStartSecond == -1 ) if( attack.fStartSecond == -1 )
attack.fStartSecond = GAMESTATE->m_fMusicSeconds; attack.fStartSecond = m_Position.m_fMusicSeconds;
m_ActiveAttacks.push_back( attack ); m_ActiveAttacks.push_back( attack );
RebuildPlayerOptionsFromActiveAttacks(); RebuildPlayerOptionsFromActiveAttacks();
@@ -197,6 +197,11 @@ class LunaPlayerState: public Luna<PlayerState>
{ {
public: public:
DEFINE_METHOD( GetPlayerNumber, m_PlayerNumber ); DEFINE_METHOD( GetPlayerNumber, m_PlayerNumber );
static int GetSongPosition( T* p, lua_State *L )
{
p->m_Position.PushSelf(L);
return 1;
}
DEFINE_METHOD( GetMultiPlayerNumber, m_mp ); DEFINE_METHOD( GetMultiPlayerNumber, m_mp );
DEFINE_METHOD( GetPlayerController, m_PlayerController ); DEFINE_METHOD( GetPlayerController, m_PlayerController );
static int SetPlayerOptions( T* p, lua_State *L ) static int SetPlayerOptions( T* p, lua_State *L )
@@ -246,6 +251,7 @@ public:
ADD_METHOD( GetPlayerOptionsArray ); ADD_METHOD( GetPlayerOptionsArray );
ADD_METHOD( GetPlayerOptionsString ); ADD_METHOD( GetPlayerOptionsString );
ADD_METHOD( GetCurrentPlayerOptions ); ADD_METHOD( GetCurrentPlayerOptions );
ADD_METHOD( GetSongPosition );
ADD_METHOD( GetHealthState ); ADD_METHOD( GetHealthState );
} }
}; };
+4
View File
@@ -3,6 +3,7 @@
#ifndef PlayerState_H #ifndef PlayerState_H
#define PlayerState_H #define PlayerState_H
#include "SongPosition.h"
#include "Attack.h" #include "Attack.h"
#include "ModsGroup.h" #include "ModsGroup.h"
#include "PlayerNumber.h" #include "PlayerNumber.h"
@@ -38,6 +39,9 @@ public:
*/ */
MultiPlayer m_mp; MultiPlayer m_mp;
// Music statistics:
SongPosition m_Position;
/** /**
* @brief Change the PlayerOptions to their default. * @brief Change the PlayerOptions to their default.
* @param l the level of mods to reset. * @param l the level of mods to reset.
+2 -2
View File
@@ -443,7 +443,7 @@ void ScoreKeeperNormal::HandleComboInternal( int iNumHitContinueCombo, int iNumH
if( iNumBreakCombo == 0 ) if( iNumBreakCombo == 0 )
{ {
TimingData td = GAMESTATE->m_pCurSong->m_Timing; TimingData td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing;
int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow ).m_iCombo ); int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow ).m_iCombo );
m_pPlayerStageStats->m_iCurCombo += iNumHitContinueCombo * multiplier; m_pPlayerStageStats->m_iCurCombo += iNumHitContinueCombo * multiplier;
} }
@@ -463,7 +463,7 @@ void ScoreKeeperNormal::HandleRowComboInternal( TapNoteScore tns, int iNumTapsIn
if ( tns >= m_MinScoreToContinueCombo ) if ( tns >= m_MinScoreToContinueCombo )
{ {
m_pPlayerStageStats->m_iCurMissCombo = 0; m_pPlayerStageStats->m_iCurMissCombo = 0;
TimingData td = GAMESTATE->m_pCurSong->m_Timing; TimingData td = GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber]->m_Timing;
int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow ).m_iCombo ); int multiplier = ( iRow == -1 ? 1 : td.GetComboSegmentAtRow( iRow ).m_iCombo );
m_pPlayerStageStats->m_iCurCombo += iNumTapsInRow * multiplier; m_pPlayerStageStats->m_iCurCombo += iNumTapsInRow * multiplier;
} }
+414 -196
View File
File diff suppressed because it is too large Load Diff
+43 -5
View File
@@ -86,6 +86,7 @@ enum EditButton
EDIT_BUTTON_SNAP_PREV, EDIT_BUTTON_SNAP_PREV,
EDIT_BUTTON_OPEN_EDIT_MENU, EDIT_BUTTON_OPEN_EDIT_MENU,
EDIT_BUTTON_OPEN_TIMING_MENU,
EDIT_BUTTON_OPEN_AREA_MENU, EDIT_BUTTON_OPEN_AREA_MENU,
EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU, EDIT_BUTTON_OPEN_BGCHANGE_LAYER1_MENU,
EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU, EDIT_BUTTON_OPEN_BGCHANGE_LAYER2_MENU,
@@ -138,6 +139,8 @@ enum EditButton
EDIT_BUTTON_SWITCH_PLAYERS, /**< Allow entering notes for a different Player. */ EDIT_BUTTON_SWITCH_PLAYERS, /**< Allow entering notes for a different Player. */
EDIT_BUTTON_SWITCH_TIMINGS, /**< Allow switching between Song and Step TimingData. */
NUM_EditButton, // leave this at the end NUM_EditButton, // leave this at the end
EditButton_Invalid EditButton_Invalid
}; };
@@ -232,6 +235,9 @@ protected:
void DoHelp(); void DoHelp();
/** @brief Display the TimingData menu for editing song and step timing. */
void DisplayTimingMenu();
EditState m_EditState; EditState m_EditState;
Song* m_pSong; Song* m_pSong;
@@ -275,14 +281,22 @@ protected:
/** @brief Has the NoteData been changed such that a user should be prompted to save? */ /** @brief Has the NoteData been changed such that a user should be prompted to save? */
bool m_bDirty; bool m_bDirty;
/** @brief The sound that is played when a note is added. */
RageSound m_soundAddNote; RageSound m_soundAddNote;
/** @brief The sound that is played when a note is removed. */
RageSound m_soundRemoveNote; RageSound m_soundRemoveNote;
RageSound m_soundChangeLine; RageSound m_soundChangeLine;
RageSound m_soundChangeSnap; RageSound m_soundChangeSnap;
RageSound m_soundMarker; RageSound m_soundMarker;
RageSound m_soundValueIncrease; RageSound m_soundValueIncrease;
RageSound m_soundValueDecrease; RageSound m_soundValueDecrease;
/** @brief The sound that is played when switching players for Routine. */
RageSound m_soundSwitchPlayer;
/** @brief The sound that is played when switching song/step timing. */
RageSound m_soundSwitchTiming;
/** @brief The sound that is played when switching to a different chart. */
RageSound m_soundSwitchSteps; RageSound m_soundSwitchSteps;
/** @brief The sound that is played when the chart is saved. */
RageSound m_soundSave; RageSound m_soundSave;
// used for reverting // used for reverting
@@ -459,7 +473,6 @@ public:
main_title_transliteration, main_title_transliteration,
sub_title_transliteration, sub_title_transliteration,
artist_transliteration, artist_transliteration,
beat_0_offset,
last_beat_hint, last_beat_hint,
preview_start, preview_start,
preview_length, preview_length,
@@ -472,16 +485,21 @@ public:
enum TimingDataInformationChoice enum TimingDataInformationChoice
{ {
beat_0_offset,
bpm, bpm,
stop, stop,
delay, delay,
// time_signature, time_signature,
time_signature_numerator, label,
time_signature_denominator,
tickcount, tickcount,
combo, combo,
label,
warp, warp,
// speed,
speed_percent,
speed_wait,
speed_mode,
fake,
erase_step_timing,
NUM_TIMING_DATA_INFORMATION_CHOICES NUM_TIMING_DATA_INFORMATION_CHOICES
}; };
@@ -515,6 +533,18 @@ public:
NUM_BGCHANGE_CHOICES NUM_BGCHANGE_CHOICES
}; };
enum SpeedSegmentModes
{
SSMODE_Beats,
SSMODE_Seconds
};
/**
* @brief Take care of any background changes that the user wants.
*
* It is important that this is only called in Song Timing mode.
* @param c the Background Change style requested.
* @param iAnswers the other settings involving the change. */
void HandleBGChangeChoice( BGChangeChoice c, const vector<int> &iAnswers ); void HandleBGChangeChoice( BGChangeChoice c, const vector<int> &iAnswers );
enum CourseAttackChoice enum CourseAttackChoice
@@ -544,6 +574,14 @@ public:
void MakeFilteredMenuDef( const MenuDef* pDef, MenuDef &menu ); void MakeFilteredMenuDef( const MenuDef* pDef, MenuDef &menu );
void EditMiniMenu( const MenuDef* pDef, ScreenMessage SM_SendOnOK = SM_None, ScreenMessage SM_SendOnCancel = SM_None ); void EditMiniMenu( const MenuDef* pDef, ScreenMessage SM_SendOnOK = SM_None, ScreenMessage SM_SendOnCancel = SM_None );
private:
/**
* @brief Retrieve the appropriate TimingData based on GAMESTATE.
* @return the proper TimingData. */
TimingData & GetAppropriateTiming() const;
void SetBeat(float fBeat);
float GetBeat();
}; };
#endif #endif
+1 -1
View File
@@ -236,7 +236,7 @@ void ScreenEditMenu::MenuStart( const InputEventPlus &input )
case EditMenuAction_Create: case EditMenuAction_Create:
ASSERT( !pSteps ); ASSERT( !pSteps );
{ {
pSteps = new Steps; pSteps = pSong->CreateSteps();
switch( m_Selector.EDIT_MODE ) switch( m_Selector.EDIT_MODE )
{ {
+17 -17
View File
@@ -1325,7 +1325,7 @@ void ScreenGameplay::StartPlayingSong( float fMinTimeToNotes, float fMinTimeToMu
{ {
const float fFirstBeat = GAMESTATE->m_pCurSong->m_fFirstBeat; const float fFirstBeat = GAMESTATE->m_pCurSong->m_fFirstBeat;
const float fFirstSecond = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( fFirstBeat ); const float fFirstSecond = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( fFirstBeat );
float fStartDelay = fMinTimeToNotes - fFirstSecond; float fStartDelay = fMinTimeToNotes - fFirstSecond;
fStartDelay = max( fStartDelay, fMinTimeToMusic ); fStartDelay = max( fStartDelay, fMinTimeToMusic );
p.m_StartSecond = -fStartDelay; p.m_StartSecond = -fStartDelay;
@@ -1347,10 +1347,10 @@ void ScreenGameplay::StartPlayingSong( float fMinTimeToNotes, float fMinTimeToMu
m_pSoundMusic->Pause( true ); m_pSoundMusic->Pause( true );
/* Make sure GAMESTATE->m_fMusicSeconds is set up. */ /* Make sure GAMESTATE->m_fMusicSeconds is set up. */
GAMESTATE->m_fMusicSeconds = -5000; GAMESTATE->m_Position.m_fMusicSeconds = -5000;
UpdateSongPosition(0); UpdateSongPosition(0);
ASSERT( GAMESTATE->m_fMusicSeconds > -4000 ); /* make sure the "fake timer" code doesn't trigger */ ASSERT( GAMESTATE->m_Position.m_fMusicSeconds > -4000 ); /* make sure the "fake timer" code doesn't trigger */
} }
@@ -1405,7 +1405,7 @@ void ScreenGameplay::PlayAnnouncer( RString type, float fSeconds )
if( m_DancingState != STATE_DANCING ) if( m_DancingState != STATE_DANCING )
return; return;
if( GAMESTATE->m_pCurSong == NULL || // this will be true on ScreenDemonstration sometimes if( GAMESTATE->m_pCurSong == NULL || // this will be true on ScreenDemonstration sometimes
GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat ) GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat )
return; return;
if( m_fTimeSinceLastDancingComment < fSeconds ) if( m_fTimeSinceLastDancingComment < fSeconds )
@@ -1423,7 +1423,7 @@ void ScreenGameplay::UpdateSongPosition( float fDeltaTime )
RageTimer tm; RageTimer tm;
const float fSeconds = m_pSoundMusic->GetPositionSeconds( NULL, &tm ); const float fSeconds = m_pSoundMusic->GetPositionSeconds( NULL, &tm );
const float fAdjust = SOUND->GetFrameTimingAdjustment( fDeltaTime ); const float fAdjust = SOUND->GetFrameTimingAdjustment( fDeltaTime );
GAMESTATE->UpdateSongPosition( fSeconds+fAdjust, GAMESTATE->m_pCurSong->m_Timing, tm+fAdjust ); GAMESTATE->UpdateSongPosition( fSeconds+fAdjust, GAMESTATE->m_pCurSong->m_SongTiming, tm+fAdjust, true );
} }
void ScreenGameplay::BeginScreen() void ScreenGameplay::BeginScreen()
@@ -1480,7 +1480,7 @@ bool ScreenGameplay::AllAreFailing()
void ScreenGameplay::GetMusicEndTiming( float &fSecondsToStartFadingOutMusic, float &fSecondsToStartTransitioningOut ) void ScreenGameplay::GetMusicEndTiming( float &fSecondsToStartFadingOutMusic, float &fSecondsToStartTransitioningOut )
{ {
float fLastStepSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat ); float fLastStepSeconds = GAMESTATE->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat( GAMESTATE->m_pCurSong->m_fLastBeat );
fLastStepSeconds += Player::GetMaxStepDistanceSeconds(); fLastStepSeconds += Player::GetMaxStepDistanceSeconds();
float fTransitionLength; float fTransitionLength;
@@ -1687,7 +1687,7 @@ void ScreenGameplay::Update( float fDeltaTime )
// update fGameplaySeconds // update fGameplaySeconds
STATSMAN->m_CurStageStats.m_fGameplaySeconds += fUnscaledDeltaTime; STATSMAN->m_CurStageStats.m_fGameplaySeconds += fUnscaledDeltaTime;
if( GAMESTATE->m_fSongBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat && GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat ) if( GAMESTATE->m_Position.m_fSongBeat >= GAMESTATE->m_pCurSong->m_fFirstBeat && GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fLastBeat )
{ {
STATSMAN->m_CurStageStats.m_fStepsSeconds += fUnscaledDeltaTime; STATSMAN->m_CurStageStats.m_fStepsSeconds += fUnscaledDeltaTime;
@@ -1707,7 +1707,7 @@ void ScreenGameplay::Update( float fDeltaTime )
if( bAllReallyFailed ) if( bAllReallyFailed )
fSecondsToStartTransitioningOut += BEGIN_FAILED_DELAY; fSecondsToStartTransitioningOut += BEGIN_FAILED_DELAY;
if( GAMESTATE->m_fMusicSeconds >= fSecondsToStartTransitioningOut && !m_NextSong.IsTransitioning() ) if( GAMESTATE->m_Position.m_fMusicSeconds >= fSecondsToStartTransitioningOut && !m_NextSong.IsTransitioning() )
this->PostScreenMessage( SM_NotesEnded, 0 ); this->PostScreenMessage( SM_NotesEnded, 0 );
} }
@@ -1843,8 +1843,8 @@ void ScreenGameplay::Update( float fDeltaTime )
float ScreenGameplay::GetHasteRate() float ScreenGameplay::GetHasteRate()
{ {
if( GAMESTATE->m_fMusicSeconds < GAMESTATE->m_fLastHasteUpdateMusicSeconds || // new song if( GAMESTATE->m_Position.m_fMusicSeconds < GAMESTATE->m_fLastHasteUpdateMusicSeconds || // new song
GAMESTATE->m_fMusicSeconds > GAMESTATE->m_fLastHasteUpdateMusicSeconds + 4 ) GAMESTATE->m_Position.m_fMusicSeconds > GAMESTATE->m_fLastHasteUpdateMusicSeconds + 4 )
{ {
bool bAnyPlayerHitAllNotes = false; bool bAnyPlayerHitAllNotes = false;
FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi ) FOREACH_EnabledPlayerInfo( m_vPlayerInfo, pi )
@@ -1865,7 +1865,7 @@ float ScreenGameplay::GetHasteRate()
GAMESTATE->m_fHasteRate += 0.1f; GAMESTATE->m_fHasteRate += 0.1f;
CLAMP( GAMESTATE->m_fHasteRate, -1.0f, +1.0f ); CLAMP( GAMESTATE->m_fHasteRate, -1.0f, +1.0f );
GAMESTATE->m_fLastHasteUpdateMusicSeconds = GAMESTATE->m_fMusicSeconds; GAMESTATE->m_fLastHasteUpdateMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
} }
/* If the life meter is less than half full, push the haste rate down to let /* If the life meter is less than half full, push the haste rate down to let
@@ -1914,7 +1914,7 @@ void ScreenGameplay::UpdateLights()
ZERO( bBlinkGameButton ); ZERO( bBlinkGameButton );
bool bCrossedABeat = false; bool bCrossedABeat = false;
{ {
const float fSongBeat = GAMESTATE->m_fLightSongBeat; const float fSongBeat = GAMESTATE->m_Position.m_fLightSongBeat;
const int iSongRow = BeatToNoteRowNotRounded( fSongBeat ); const int iSongRow = BeatToNoteRowNotRounded( fSongBeat );
static int iRowLastCrossed = 0; static int iRowLastCrossed = 0;
@@ -1968,7 +1968,7 @@ void ScreenGameplay::UpdateLights()
} }
// Before the first beat of the song, all cabinet lights solid on (except for menu buttons). // Before the first beat of the song, all cabinet lights solid on (except for menu buttons).
bool bOverrideCabinetBlink = (GAMESTATE->m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat); bool bOverrideCabinetBlink = (GAMESTATE->m_Position.m_fSongBeat < GAMESTATE->m_pCurSong->m_fFirstBeat);
FOREACH_CabinetLight( cl ) FOREACH_CabinetLight( cl )
bBlinkCabinetLight[cl] |= bOverrideCabinetBlink; bBlinkCabinetLight[cl] |= bOverrideCabinetBlink;
@@ -1994,8 +1994,8 @@ void ScreenGameplay::SendCrossedMessages()
{ {
static int iRowLastCrossed = 0; static int iRowLastCrossed = 0;
float fPositionSeconds = GAMESTATE->m_fMusicSeconds; float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
float fSongBeat = GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ); float fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds );
int iRowNow = BeatToNoteRowNotRounded( fSongBeat ); int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
iRowNow = max( 0, iRowNow ); iRowNow = max( 0, iRowNow );
@@ -2032,8 +2032,8 @@ void ScreenGameplay::SendCrossedMessages()
{ {
float fNoteWillCrossInSeconds = MESSAGE_SPACING_SECONDS * i; float fNoteWillCrossInSeconds = MESSAGE_SPACING_SECONDS * i;
float fPositionSeconds = GAMESTATE->m_fMusicSeconds + fNoteWillCrossInSeconds; float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds + fNoteWillCrossInSeconds;
float fSongBeat = GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ); float fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds );
int iRowNow = BeatToNoteRowNotRounded( fSongBeat ); int iRowNow = BeatToNoteRowNotRounded( fSongBeat );
iRowNow = max( 0, iRowNow ); iRowNow = max( 0, iRowNow );
+6 -6
View File
@@ -208,7 +208,7 @@ void ScreenHowToPlay::Step()
#define ST_JUMPUD (ST_UP | ST_DOWN) #define ST_JUMPUD (ST_UP | ST_DOWN)
int iStep = 0; int iStep = 0;
const int iNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat + 0.6f ); const int iNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_Position.m_fSongBeat + 0.6f );
// if we want to miss from here on out, don't process steps. // if we want to miss from here on out, don't process steps.
if( m_iW2s < m_iNumW2s && m_NoteData.IsThereATapAtRow( iNoteRow ) ) if( m_iW2s < m_iNumW2s && m_NoteData.IsThereATapAtRow( iNoteRow ) )
{ {
@@ -229,10 +229,10 @@ void ScreenHowToPlay::Step()
m_pmCharacter->PlayAnimation( "Step-JUMPLR", 1.8f ); m_pmCharacter->PlayAnimation( "Step-JUMPLR", 1.8f );
m_pmCharacter->StopTweening(); m_pmCharacter->StopTweening();
m_pmCharacter->BeginTweening( GAMESTATE->m_fCurBPS /8, TWEEN_LINEAR ); m_pmCharacter->BeginTweening( GAMESTATE->m_Position.m_fCurBPS /8, TWEEN_LINEAR );
m_pmCharacter->SetRotationY( 90 ); m_pmCharacter->SetRotationY( 90 );
m_pmCharacter->BeginTweening( (1/(GAMESTATE->m_fCurBPS * 2) ) ); //sleep between jump-frames m_pmCharacter->BeginTweening( (1/(GAMESTATE->m_Position.m_fCurBPS * 2) ) ); //sleep between jump-frames
m_pmCharacter->BeginTweening( GAMESTATE->m_fCurBPS /6, TWEEN_LINEAR ); m_pmCharacter->BeginTweening( GAMESTATE->m_Position.m_fCurBPS /6, TWEEN_LINEAR );
m_pmCharacter->SetRotationY( 0 ); m_pmCharacter->SetRotationY( 0 );
break; break;
} }
@@ -243,11 +243,11 @@ void ScreenHowToPlay::Update( float fDelta )
{ {
if( GAMESTATE->m_pCurSong != NULL ) if( GAMESTATE->m_pCurSong != NULL )
{ {
GAMESTATE->UpdateSongPosition( m_fFakeSecondsIntoSong, GAMESTATE->m_pCurSong->m_Timing ); GAMESTATE->UpdateSongPosition( m_fFakeSecondsIntoSong, GAMESTATE->m_pCurSong->m_SongTiming );
m_fFakeSecondsIntoSong += fDelta; m_fFakeSecondsIntoSong += fDelta;
static int iLastNoteRowCounted = 0; static int iLastNoteRowCounted = 0;
int iCurNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_fSongBeat ); int iCurNoteRow = BeatToNoteRowNotRounded( GAMESTATE->m_Position.m_fSongBeat );
if( iCurNoteRow != iLastNoteRowCounted &&m_NoteData.IsThereATapAtRow(iCurNoteRow) ) if( iCurNoteRow != iLastNoteRowCounted &&m_NoteData.IsThereATapAtRow(iCurNoteRow) )
{ {
+2 -2
View File
@@ -61,7 +61,7 @@ void ScreenNameEntry::ScrollingText::Init( const RString &sName, const vector<fl
void ScreenNameEntry::ScrollingText::DrawPrimitives() void ScreenNameEntry::ScrollingText::DrawPrimitives()
{ {
const float fFakeBeat = GAMESTATE->m_fSongBeat; const float fFakeBeat = GAMESTATE->m_Position.m_fSongBeat;
const size_t iClosestIndex = lrintf( fFakeBeat ) % CHARS_CHOICES.size(); const size_t iClosestIndex = lrintf( fFakeBeat ) % CHARS_CHOICES.size();
const float fClosestYOffset = GetClosestCharYOffset( fFakeBeat ); const float fClosestYOffset = GetClosestCharYOffset( fFakeBeat );
@@ -325,7 +325,7 @@ void ScreenNameEntry::Update( float fDelta )
SOUND->PlayOnceFromDir( ANNOUNCER->GetPathTo("name entry") ); SOUND->PlayOnceFromDir( ANNOUNCER->GetPathTo("name entry") );
m_fFakeBeat += fDelta * FAKE_BEATS_PER_SEC; m_fFakeBeat += fDelta * FAKE_BEATS_PER_SEC;
GAMESTATE->m_fSongBeat = m_fFakeBeat; GAMESTATE->m_Position.m_fSongBeat = m_fFakeBeat;
ScreenWithMenuElements::Update(fDelta); ScreenWithMenuElements::Update(fDelta);
} }
+1 -1
View File
@@ -1764,7 +1764,7 @@ void ScreenSelectMusic::AfterMusicChange()
case SampleMusicPreviewMode_LastSong: // fall through case SampleMusicPreviewMode_LastSong: // fall through
// play the sample music // play the sample music
m_sSampleMusicToPlay = pSong->GetMusicPath(); m_sSampleMusicToPlay = pSong->GetMusicPath();
m_pSampleMusicTimingData = &pSong->m_Timing; m_pSampleMusicTimingData = &pSong->m_SongTiming;
m_fSampleStartSeconds = pSong->m_fMusicSampleStartSeconds; m_fSampleStartSeconds = pSong->m_fMusicSampleStartSeconds;
m_fSampleLengthSeconds = pSong->m_fMusicSampleLengthSeconds; m_fSampleLengthSeconds = pSong->m_fMusicSampleLengthSeconds;
break; break;
+2 -2
View File
@@ -221,7 +221,7 @@ bool ScreenSyncOverlay::OverlayInput( const InputEventPlus &input )
} }
if( GAMESTATE->m_pCurSong != NULL ) if( GAMESTATE->m_pCurSong != NULL )
{ {
BPMSegment& seg = GAMESTATE->m_pCurSong->GetBPMSegmentAtBeat( GAMESTATE->m_fSongBeat ); BPMSegment& seg = GAMESTATE->m_pCurSong->m_SongTiming.GetBPMSegmentAtBeat( GAMESTATE->m_Position.m_fSongBeat );
seg.m_fBPS += fDelta; seg.m_fBPS += fDelta;
} }
} }
@@ -253,7 +253,7 @@ bool ScreenSyncOverlay::OverlayInput( const InputEventPlus &input )
case ChangeSongOffset: case ChangeSongOffset:
if( GAMESTATE->m_pCurSong != NULL ) if( GAMESTATE->m_pCurSong != NULL )
GAMESTATE->m_pCurSong->m_Timing.m_fBeat0OffsetInSeconds += fDelta; GAMESTATE->m_pCurSong->m_SongTiming.m_fBeat0OffsetInSeconds += fDelta;
break; break;
} }
} }
+37 -54
View File
@@ -41,7 +41,7 @@
* @brief The internal version of the cache for StepMania. * @brief The internal version of the cache for StepMania.
* *
* Increment this value to invalidate the current cache. */ * Increment this value to invalidate the current cache. */
const int FILE_CACHE_VERSION = 171; const int FILE_CACHE_VERSION = 174;
/** @brief How long does a song sample last by default? */ /** @brief How long does a song sample last by default? */
const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f; const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f;
@@ -145,6 +145,18 @@ void Song::AddLyricSegment( LyricSegment seg )
m_LyricSegments.push_back( seg ); m_LyricSegments.push_back( seg );
} }
Steps *Song::CreateSteps()
{
Steps *pSteps = new Steps;
InitSteps( pSteps );
return pSteps;
}
void Song::InitSteps(Steps *pSteps)
{
pSteps->m_Timing = m_SongTiming;
}
void Song::GetDisplayBpms( DisplayBpms &AddTo ) const void Song::GetDisplayBpms( DisplayBpms &AddTo ) const
{ {
if( m_DisplayBPMType == DISPLAY_BPM_SPECIFIED ) if( m_DisplayBPMType == DISPLAY_BPM_SPECIFIED )
@@ -155,7 +167,7 @@ void Song::GetDisplayBpms( DisplayBpms &AddTo ) const
else else
{ {
float fMinBPM, fMaxBPM; float fMinBPM, fMaxBPM;
m_Timing.GetActualBPM( fMinBPM, fMaxBPM ); m_SongTiming.GetActualBPM( fMinBPM, fMaxBPM );
AddTo.Add( fMinBPM ); AddTo.Add( fMinBPM );
AddTo.Add( fMaxBPM ); AddTo.Add( fMaxBPM );
} }
@@ -460,6 +472,13 @@ void Song::TidyUpData()
m_fMusicLengthSeconds = 0; m_fMusicLengthSeconds = 0;
} }
m_SongTiming.TidyUpData();
FOREACH( Steps *, m_vpSteps, s )
{
(*s)->m_Timing.TidyUpData();
}
/* Generate these before we autogen notes, so the new notes can inherit /* Generate these before we autogen notes, so the new notes can inherit
* their source's values. */ * their source's values. */
ReCalculateRadarValuesAndLastBeat(); ReCalculateRadarValuesAndLastBeat();
@@ -476,29 +495,18 @@ void Song::TidyUpData()
m_sArtist = "Unknown artist"; m_sArtist = "Unknown artist";
TranslateTitles(); TranslateTitles();
if( m_Timing.m_BPMSegments.empty() )
{
LOG->UserLog( "Song file", m_sSongDir + m_sSongFileName, "has no BPM segments, default provided." );
m_Timing.AddBPMSegment( BPMSegment(0, 60) );
}
// Make sure the first BPM segment starts at beat 0.
if( m_Timing.m_BPMSegments[0].m_iStartRow != 0 )
m_Timing.m_BPMSegments[0].m_iStartRow = 0;
if( m_fMusicSampleStartSeconds == -1 || if( m_fMusicSampleStartSeconds == -1 ||
m_fMusicSampleStartSeconds == 0 || m_fMusicSampleStartSeconds == 0 ||
m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds ) m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
{ {
m_fMusicSampleStartSeconds = this->GetElapsedTimeFromBeat( 100 ); const TimingData &timing = this->m_SongTiming;
m_fMusicSampleStartSeconds = timing.GetElapsedTimeFromBeat( 100 );
if( m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds ) if( m_fMusicSampleStartSeconds+m_fMusicSampleLengthSeconds > this->m_fMusicLengthSeconds )
{ {
int iBeat = lrintf( m_fLastBeat/2 ); int iBeat = lrintf( m_fLastBeat/2 );
iBeat -= iBeat%4; iBeat -= iBeat%4;
m_fMusicSampleStartSeconds = this->GetElapsedTimeFromBeat( (float)iBeat ); m_fMusicSampleStartSeconds = timing.GetElapsedTimeFromBeat( (float)iBeat );
} }
} }
@@ -777,38 +785,6 @@ void Song::TidyUpData()
m_sSongFileName += ".ssc"; m_sSongFileName += ".ssc";
} while(0); } while(0);
} }
// If no time signature specified, assume 4/4 time for the whole song.
if( m_Timing.m_vTimeSignatureSegments.empty() )
{
TimeSignatureSegment seg(0, 4, 4);
m_Timing.m_vTimeSignatureSegments.push_back( seg );
}
/*
* Likewise, if no tickcount signature is specified, assume 2 ticks
* per beat for the entire song. The default of 2 is chosen more
* for compatibility with the Pump Pro series than anything else.
*/
if( m_Timing.m_TickcountSegments.empty() )
{
TickcountSegment seg(0, 2);
m_Timing.m_TickcountSegments.push_back( seg );
}
// Have a default combo segment of one just in case.
if( m_Timing.m_ComboSegments.empty() )
{
ComboSegment seg(0, 1);
m_Timing.m_ComboSegments.push_back( seg );
}
// Have a default label segment just in case.
if( m_Timing.m_LabelSegments.empty() )
{
LabelSegment seg(0, "Song Start");
m_Timing.m_LabelSegments.push_back( seg );
}
} }
void Song::TranslateTitles() void Song::TranslateTitles()
@@ -857,8 +833,8 @@ void Song::ReCalculateRadarValuesAndLastBeat()
if( tempNoteData.GetLastRow() == 0 ) if( tempNoteData.GetLastRow() == 0 )
continue; continue;
fFirstBeat = min( fFirstBeat, tempNoteData.GetFirstBeat() ); fFirstBeat = min( fFirstBeat, m_SongTiming.GetBeatFromElapsedTime(pSteps->m_Timing.GetElapsedTimeFromBeat(tempNoteData.GetFirstBeat())) );
fLastBeat = max( fLastBeat, tempNoteData.GetLastBeat() ); fLastBeat = max( fLastBeat, m_SongTiming.GetBeatFromElapsedTime(pSteps->m_Timing.GetElapsedTimeFromBeat(tempNoteData.GetLastBeat())) );
} }
m_fFirstBeat = fFirstBeat; m_fFirstBeat = fFirstBeat;
@@ -888,6 +864,12 @@ void Song::Save()
ReCalculateRadarValuesAndLastBeat(); ReCalculateRadarValuesAndLastBeat();
TranslateTitles(); TranslateTitles();
// TODO: Figure out a better way to save to Song's timing data.
if( m_vpSteps.size() == 1 )
{
m_SongTiming = m_vpSteps[0]->m_Timing;
}
// Save the new files. These calls make backups on their own. // Save the new files. These calls make backups on their own.
if( !SaveToSSCFile(GetSongFilePath(), false) ) if( !SaveToSSCFile(GetSongFilePath(), false) )
return; return;
@@ -1453,7 +1435,7 @@ bool Song::IsEditAlreadyLoaded( Steps* pSteps ) const
bool Song::HasSignificantBpmChangesOrStops() const bool Song::HasSignificantBpmChangesOrStops() const
{ {
if( m_Timing.HasStops() ) if( m_SongTiming.HasStops() )
return true; return true;
// Don't consider BPM changes that only are only for maintaining sync as // Don't consider BPM changes that only are only for maintaining sync as
@@ -1463,7 +1445,7 @@ bool Song::HasSignificantBpmChangesOrStops() const
if( m_fSpecifiedBPMMin != m_fSpecifiedBPMMax ) if( m_fSpecifiedBPMMin != m_fSpecifiedBPMMax )
return true; return true;
} }
else if( m_Timing.HasBpmChanges() ) else if( m_SongTiming.HasBpmChanges() )
{ {
return true; return true;
} }
@@ -1473,7 +1455,8 @@ bool Song::HasSignificantBpmChangesOrStops() const
float Song::GetStepsSeconds() const float Song::GetStepsSeconds() const
{ {
return GetElapsedTimeFromBeat( m_fLastBeat ) - GetElapsedTimeFromBeat( m_fFirstBeat ); const TimingData &timing = this->m_SongTiming;
return timing.GetElapsedTimeFromBeat( m_fLastBeat ) - timing.GetElapsedTimeFromBeat( m_fFirstBeat );
} }
bool Song::IsLong() const bool Song::IsLong() const
@@ -1556,7 +1539,7 @@ public:
} }
static int GetTimingData( T* p, lua_State *L ) static int GetTimingData( T* p, lua_State *L )
{ {
p->m_Timing.PushSelf(L); p->m_SongTiming.PushSelf(L);
return 1; return 1;
} }
// has functions // has functions
+37 -3
View File
@@ -8,16 +8,16 @@
#include "RageUtil_AutoPtr.h" #include "RageUtil_AutoPtr.h"
#include "RageUtil_CachedObject.h" #include "RageUtil_CachedObject.h"
#include "RageTypes.h" #include "RageTypes.h"
#include "Steps.h"
#include <set> #include <set>
class Steps;
class Style; class Style;
class StepsID; class StepsID;
struct lua_State; struct lua_State;
struct BackgroundChange; struct BackgroundChange;
/** @brief The version of the .ssc file format. */ /** @brief The version of the .ssc file format. */
const static float STEPFILE_VERSION_NUMBER = 0.59f; const static float STEPFILE_VERSION_NUMBER = 0.7f;
/** @brief How many edits for this song can each profile have? */ /** @brief How many edits for this song can each profile have? */
const int MAX_EDITS_PER_SONG_PER_PROFILE = 5; const int MAX_EDITS_PER_SONG_PER_PROFILE = 5;
@@ -285,7 +285,10 @@ public:
bool Matches(RString sGroup, RString sSong) const; bool Matches(RString sGroup, RString sSong) const;
/** @brief The Song's TimingData. */ /** @brief The Song's TimingData. */
TimingData m_Timing; TimingData m_SongTiming;
/** @brief The initial offset of a song. */
float m_fBeat0OffsetInSeconds;
typedef vector<BackgroundChange> VBackgroundChange; typedef vector<BackgroundChange> VBackgroundChange;
private: private:
@@ -317,9 +320,11 @@ public:
* This must be sorted before gameplay. */ * This must be sorted before gameplay. */
vector<LyricSegment> m_LyricSegments; vector<LyricSegment> m_LyricSegments;
/* [splittiming]
void AddBPMSegment( const BPMSegment &seg ) { m_Timing.AddBPMSegment( seg ); } void AddBPMSegment( const BPMSegment &seg ) { m_Timing.AddBPMSegment( seg ); }
void AddStopSegment( const StopSegment &seg ) { m_Timing.AddStopSegment( seg ); } void AddStopSegment( const StopSegment &seg ) { m_Timing.AddStopSegment( seg ); }
void AddWarpSegment( const WarpSegment &seg ) { m_Timing.AddWarpSegment( seg ); } void AddWarpSegment( const WarpSegment &seg ) { m_Timing.AddWarpSegment( seg ); }
*/
void AddBackgroundChange( BackgroundLayer blLayer, BackgroundChange seg ); void AddBackgroundChange( BackgroundLayer blLayer, BackgroundChange seg );
void AddForegroundChange( BackgroundChange seg ); void AddForegroundChange( BackgroundChange seg );
void AddLyricSegment( LyricSegment seg ); void AddLyricSegment( LyricSegment seg );
@@ -327,18 +332,47 @@ public:
void GetDisplayBpms( DisplayBpms &AddTo ) const; void GetDisplayBpms( DisplayBpms &AddTo ) const;
const BackgroundChange &GetBackgroundAtBeat( BackgroundLayer iLayer, float fBeat ) const; const BackgroundChange &GetBackgroundAtBeat( BackgroundLayer iLayer, float fBeat ) const;
/* [splittiming]
float GetBPMAtBeat( float fBeat ) const { return m_Timing.GetBPMAtBeat( fBeat ); } float GetBPMAtBeat( float fBeat ) const { return m_Timing.GetBPMAtBeat( fBeat ); }
void SetBPMAtBeat( float fBeat, float fBPM ) { m_Timing.SetBPMAtBeat( fBeat, fBPM ); } void SetBPMAtBeat( float fBeat, float fBPM ) { m_Timing.SetBPMAtBeat( fBeat, fBPM ); }
BPMSegment& GetBPMSegmentAtBeat( float fBeat ) { return m_Timing.GetBPMSegmentAtBeat( fBeat ); } BPMSegment& GetBPMSegmentAtBeat( float fBeat ) { return m_Timing.GetBPMSegmentAtBeat( fBeat ); }
*/
Steps *CreateSteps();
void InitSteps(Steps *pSteps);
/** /**
* @brief Retrieve the beat based on the specified time. * @brief Retrieve the beat based on the specified time.
* @param fElapsedTime the amount of time since the Song started. * @param fElapsedTime the amount of time since the Song started.
* @return the appropriate beat. */ * @return the appropriate beat. */
/* [splittiming]
float SongGetBeatFromElapsedTime( float fElapsedTime ) const
{
return m_SongTiming.GetBeatFromElapsedTime( fElapsedTime );
}
float StepsGetBeatFromElapsedTime( float fElapsedTime, const Steps &steps ) const
{
return steps.m_Timing.GetBeatFromElapsedTime( fElapsedTime );
}
float SongGetElapsedTimeFromBeat( float fBeat ) const
{
return m_SongTiming.GetElapsedTimeFromBeat( fBeat );
}
float StepsGetElapsedTimeFromBeat( float fBeat, const Steps &steps ) const
{
return steps.m_Timing.GetElapsedTimeFromBeat( fBeat );
}
*/
/* [splittiming]
float GetBeatFromElapsedTime( float fElapsedTime ) const float GetBeatFromElapsedTime( float fElapsedTime ) const
{ {
return m_Timing.GetBeatFromElapsedTime( fElapsedTime ); return m_Timing.GetBeatFromElapsedTime( fElapsedTime );
} }
float GetElapsedTimeFromBeat( float fBeat ) const { return m_Timing.GetElapsedTimeFromBeat( fBeat ); } float GetElapsedTimeFromBeat( float fBeat ) const { return m_Timing.GetElapsedTimeFromBeat( fBeat ); }
*/
bool HasSignificantBpmChangesOrStops() const; bool HasSignificantBpmChangesOrStops() const;
float GetStepsSeconds() const; float GetStepsSeconds() const;
bool IsLong() const; bool IsLong() const;
+135
View File
@@ -0,0 +1,135 @@
#include "global.h"
#include "SongPosition.h"
static Preference<float> g_fVisualDelaySeconds( "VisualDelaySeconds", 0.0f );
void SongPosition::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp )
{
if( !timestamp.IsZero() )
m_LastBeatUpdate = timestamp;
else
m_LastBeatUpdate.Touch();
// xxx testing: only do this on monotune survivor
/*
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
LOG->Trace( ssprintf("[GameState::UpdateSongPosition] cur BPS = %f, fPositionSeconds = %f",m_fCurBPS,fPositionSeconds) );
*/
timing.GetBeatAndBPSFromElapsedTime( fPositionSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze, m_bDelay, m_iWarpBeginRow, m_fWarpDestination );
// "Crash reason : -243478.890625 -48695.773438"
ASSERT_M( m_fSongBeat > -2000, ssprintf("Song beat %f at %f seconds is less than -2000!", m_fSongBeat, fPositionSeconds) );
m_fMusicSeconds = fPositionSeconds;
m_fLightSongBeat = timing.GetBeatFromElapsedTime( fPositionSeconds + g_fLightsAheadSeconds );
m_fSongBeatNoOffset = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
/*
// xxx testing: only do this on monotune survivor
if( m_pCurSong && m_pCurSong->GetDisplayFullTitle() == "monotune survivor" )
{
// and only do it in the known negative bpm region. HACKITY HACK
if(m_fSongBeat >= 445.490f && m_fSongBeat <= 453.72f)
{
LOG->Trace( ssprintf("fPositionSeconds = %f",fPositionSeconds) );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
//LOG->Trace( ssprintf("Music seconds visible %f = fPositionSeconds %f - g_fVisualDelaySeconds %f", m_fMusicSecondsVisible,fPositionSeconds,g_fVisualDelaySeconds.Get()) );
}
else if(m_fSongBeat == 445.500f)
{
LOG->Trace( ssprintf("[beat 445.500] fPositionSeconds = %f",fPositionSeconds) );
LOG->Trace( ssprintf("Song beat: %f (%f seconds), BPS = %f (%f BPM)",m_fSongBeat,m_fMusicSecondsVisible,m_fCurBPS,m_fCurBPS*60.0f) );
}
}
*/
m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get();
float fThrowAway, fThrowAway2;
bool bThrowAway;
int iThrowAway;
timing.GetBeatAndBPSFromElapsedTime( m_fMusicSecondsVisible, m_fSongBeatVisible, fThrowAway, bThrowAway, bThrowAway, iThrowAway, fThrowAway2 );
}
void SongPosition::Reset()
{
m_fMusicSecondsVisible = 0;
m_fSongBeatVisible = 0;
m_fMusicSeconds = 0; // MUSIC_SECONDS_INVALID;
// todo: move me to FOREACH_EnabledPlayer( p ) after [NUM_PLAYERS]ing
m_fSongBeat = 0;
m_fSongBeatNoOffset = 0;
m_fCurBPS = 10;
//m_bStop = false;
m_bFreeze = false;
m_bDelay = false;
m_iWarpBeginRow = -1; // Set to -1 because some song may want to warp to row 0. -aj
m_fWarpDestination = -1; // Set when a warp is encountered. also see above. -aj
}
//lua start
#include "LuaBinding.h"
class LunaSongPosition: public Luna<SongPosition>
{
public:
DEFINE_METHOD( GetMusicSecondsVisible, m_fMusicSecondsVisible );
DEFINE_METHOD( GetSongBeatVisible, m_fSongBeatVisible );
DEFINE_METHOD( GetMusicSeconds, m_fMusicSeconds );
DEFINE_METHOD( GetSongBeat, m_fSongBeat );
DEFINE_METHOD( GetSongBeatNoOffset, m_fSongBeatNoOffset );
DEFINE_METHOD( GetCurBPS, m_fCurBPS );
DEFINE_METHOD( GetFreeze, m_bFreeze );
DEFINE_METHOD( GetDelay, m_bDelay );
DEFINE_METHOD( GetWarpBeginRow, m_iWarpBeginRow );
DEFINE_METHOD( GetWarpDestination, m_fWarpDestination );
LunaSongPosition()
{
ADD_METHOD( GetMusicSecondsVisible );
ADD_METHOD( GetSongBeatVisible );
ADD_METHOD( GetMusicSeconds );
ADD_METHOD( GetSongBeat );
ADD_METHOD( GetSongBeatNoOffset );
ADD_METHOD( GetCurBPS );
ADD_METHOD( GetFreeze );
ADD_METHOD( GetDelay );
ADD_METHOD( GetWarpBeginRow );
ADD_METHOD( GetWarpDestination );
}
};
LUA_REGISTER_CLASS( SongPosition );
/**
* @file
* @author Thai Pangsakulyanont (c) 2011
* @section LICENSE
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
+72
View File
@@ -0,0 +1,72 @@
#ifndef SONGPOSITION_H
#define SONGPOSITION_H
#include "GameConstantsAndTypes.h"
#include "RageTimer.h"
#include "LightsManager.h"
#include "MessageManager.h"
#include "TimingData.h"
//XXX: where does this come from? might need another include
struct lua_State;
class SongPosition
{
public:
// Arcade - the current stage (one song).
// Oni/Endless - a single song in a course.
// Let a lot of classes access this info here so they don't have to keep their own copies.
float m_fMusicSeconds; // time into the current song, not scaled by music rate
float m_fSongBeat;
float m_fSongBeatNoOffset;
float m_fCurBPS;
float m_fLightSongBeat; // g_fLightsFalloffSeconds ahead
//bool m_bStop; // in the middle of a stop (freeze or delay)
/** @brief A flag to determine if we're in the middle of a freeze/stop. */
bool m_bFreeze;
/** @brief A flag to determine if we're in the middle of a delay (Pump style stop). */
bool m_bDelay;
/** @brief The row used to start a warp. */
int m_iWarpBeginRow;
/** @brief The beat to warp to afterwards. */
float m_fWarpDestination;
RageTimer m_LastBeatUpdate; // time of last m_fSongBeat, etc. update
float m_fMusicSecondsVisible;
float m_fSongBeatVisible;
void Reset();
void UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp = RageZeroTimer );
// Lua
void PushSelf( lua_State *L );
};
#endif
/**
* @file
* @author Thai Pangsakulyanont (c) 2011
* @section LICENSE
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
+1 -1
View File
@@ -44,7 +44,7 @@ void SoundEffectControl::Update( float fDeltaTime )
if( LOCK_TO_HOLD ) if( LOCK_TO_HOLD )
{ {
int iRow = BeatToNoteRow( GAMESTATE->m_fSongBeat ); int iRow = BeatToNoteRow( GAMESTATE->m_Position.m_fSongBeat );
int iHoldsHeld, iHoldsLetGo; int iHoldsHeld, iHoldsLetGo;
HoldsBeingHeld( iRow, iHoldsHeld, iHoldsLetGo ); HoldsBeingHeld( iRow, iHoldsHeld, iHoldsLetGo );
+8
View File
@@ -1444,6 +1444,14 @@
RelativePath="SongOptions.h" RelativePath="SongOptions.h"
> >
</File> </File>
<File
RelativePath=".\SongPosition.cpp"
>
</File>
<File
RelativePath=".\SongPosition.h"
>
</File>
<File <File
RelativePath="SongUtil.cpp" RelativePath="SongUtil.cpp"
> >
+28
View File
@@ -339,6 +339,7 @@ void Steps::AutogenFrom( const Steps *parent_, StepsType ntTo )
{ {
parent = parent_; parent = parent_;
m_StepsType = ntTo; m_StepsType = ntTo;
m_Timing = parent->m_Timing;
} }
void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds ) // pSource does not have to be of the same StepsType void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds ) // pSource does not have to be of the same StepsType
@@ -348,6 +349,7 @@ void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds
pSource->GetNoteData( noteData ); pSource->GetNoteData( noteData );
noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks ); noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks );
parent = NULL; parent = NULL;
m_Timing = pSource->m_Timing;
this->SetNoteData( noteData ); this->SetNoteData( noteData );
this->SetDescription( pSource->GetDescription() ); this->SetDescription( pSource->GetDescription() );
this->SetDifficulty( pSource->GetDifficulty() ); this->SetDifficulty( pSource->GetDifficulty() );
@@ -400,6 +402,21 @@ void Steps::SetMeter( int meter )
m_iMeter = meter; m_iMeter = meter;
} }
bool Steps::HasSignificantTimingChanges() const
{
if( m_Timing.HasStops() )
return true;
/* TODO: Deal with DisplayBPM here...if possible?
* Song's version may still be useful. */
else if( m_Timing.HasBpmChanges() || m_Timing.HasWarps() || m_Timing.HasSpeedChanges() )
{
return true;
}
return false;
}
void Steps::SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] ) void Steps::SetCachedRadarValues( const RadarValues v[NUM_PLAYERS] )
{ {
DeAutogen(); DeAutogen();
@@ -424,6 +441,8 @@ public:
DEFINE_METHOD( IsAnEdit, IsAnEdit() ) DEFINE_METHOD( IsAnEdit, IsAnEdit() )
DEFINE_METHOD( IsAPlayerEdit, IsAPlayerEdit() ) DEFINE_METHOD( IsAPlayerEdit, IsAPlayerEdit() )
static int HasSignificantTimingChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasSignificantTimingChanges()); return 1; }
static int GetRadarValues( T* p, lua_State *L ) static int GetRadarValues( T* p, lua_State *L )
{ {
PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1); PlayerNumber pn = Enum::Check<PlayerNumber>(L, 1);
@@ -432,6 +451,12 @@ public:
return 1; return 1;
} }
static int GetTimingData( T* p, lua_State *L )
{
p->m_Timing.PushSelf(L);
return 1;
}
static int GetHash( T* p, lua_State *L ) { lua_pushnumber( L, p->GetHash() ); return 1; } static int GetHash( T* p, lua_State *L ) { lua_pushnumber( L, p->GetHash() ); return 1; }
// untested // untested
@@ -443,6 +468,7 @@ public:
return 1; return 1;
} }
LunaSteps() LunaSteps()
{ {
ADD_METHOD( GetAuthorCredit ); ADD_METHOD( GetAuthorCredit );
@@ -452,7 +478,9 @@ public:
ADD_METHOD( GetFilename ); ADD_METHOD( GetFilename );
ADD_METHOD( GetHash ); ADD_METHOD( GetHash );
ADD_METHOD( GetMeter ); ADD_METHOD( GetMeter );
ADD_METHOD( HasSignificantTimingChanges );
ADD_METHOD( GetRadarValues ); ADD_METHOD( GetRadarValues );
ADD_METHOD( GetTimingData );
//ADD_METHOD( GetSMNoteData ); //ADD_METHOD( GetSMNoteData );
ADD_METHOD( GetStepsType ); ADD_METHOD( GetStepsType );
ADD_METHOD( IsAnEdit ); ADD_METHOD( IsAnEdit );
+10
View File
@@ -8,6 +8,8 @@
#include "Difficulty.h" #include "Difficulty.h"
#include "RageUtil_AutoPtr.h" #include "RageUtil_AutoPtr.h"
#include "RageUtil_CachedObject.h" #include "RageUtil_CachedObject.h"
#include "TimingData.h"
class Profile; class Profile;
class NoteData; class NoteData;
struct lua_State; struct lua_State;
@@ -116,6 +118,14 @@ public:
void TidyUpData(); void TidyUpData();
void CalculateRadarValues( float fMusicLengthSeconds ); void CalculateRadarValues( float fMusicLengthSeconds );
/** @brief Timing data */
TimingData m_Timing;
/**
* @brief Determine if the Steps have any major timing changes during gameplay.
* @return true if it does, or false otherwise. */
bool HasSignificantTimingChanges() const;
// Lua // Lua
void PushSelf( lua_State *L ); void PushSelf( lua_State *L );
+348 -21
View File
@@ -8,10 +8,16 @@
#include <float.h> #include <float.h>
TimingData::TimingData() TimingData::TimingData() :
m_fBeat0OffsetInSeconds(0),
m_bHasNegativeBpms(false)
{
}
TimingData::TimingData(float fOffset) :
m_fBeat0OffsetInSeconds(fOffset),
m_bHasNegativeBpms(false)
{ {
m_fBeat0OffsetInSeconds = 0;
m_bHasNegativeBpms = false;
} }
void TimingData::GetActualBPM( float &fMinBPMOut, float &fMaxBPMOut ) const void TimingData::GetActualBPM( float &fMinBPMOut, float &fMaxBPMOut ) const
@@ -62,6 +68,16 @@ void TimingData::AddLabelSegment( const LabelSegment &seg )
m_LabelSegments.insert( upper_bound(m_LabelSegments.begin(), m_LabelSegments.end(), seg), seg ); m_LabelSegments.insert( upper_bound(m_LabelSegments.begin(), m_LabelSegments.end(), seg), seg );
} }
void TimingData::AddSpeedSegment( const SpeedSegment &seg )
{
m_SpeedSegments.insert( upper_bound(m_SpeedSegments.begin(), m_SpeedSegments.end(), seg), seg );
}
void TimingData::AddFakeSegment( const FakeSegment &seg )
{
m_FakeSegments.insert( upper_bound(m_FakeSegments.begin(), m_FakeSegments.end(), seg), seg );
}
/* Change an existing BPM segment, merge identical segments together or insert a new one. */ /* Change an existing BPM segment, merge identical segments together or insert a new one. */
void TimingData::SetBPMAtRow( int iNoteRow, float fBPM ) void TimingData::SetBPMAtRow( int iNoteRow, float fBPM )
{ {
@@ -160,7 +176,7 @@ void TimingData::SetWarpAtRow( int iRow, float fNew )
for( i=0; i<m_WarpSegments.size(); i++ ) for( i=0; i<m_WarpSegments.size(); i++ )
if( m_WarpSegments[i].m_iStartRow == iRow ) if( m_WarpSegments[i].m_iStartRow == iRow )
break; break;
bool valid = iRow > 0 && NoteRowToBeat(iRow) < fNew; bool valid = iRow > 0 && fNew > 0;
if( i == m_WarpSegments.size() ) if( i == m_WarpSegments.size() )
{ {
if( valid ) if( valid )
@@ -172,7 +188,7 @@ void TimingData::SetWarpAtRow( int iRow, float fNew )
{ {
if( valid ) if( valid )
{ {
m_WarpSegments[i].m_fEndBeat = fNew; m_WarpSegments[i].m_fLengthBeats = fNew;
} }
else else
m_WarpSegments.erase( m_WarpSegments.begin()+i, m_WarpSegments.begin()+i+1 ); m_WarpSegments.erase( m_WarpSegments.begin()+i, m_WarpSegments.begin()+i+1 );
@@ -244,6 +260,86 @@ void TimingData::SetLabelAtRow( int iRow, const RString sLabel )
} }
} }
void TimingData::SetSpeedAtRow( int iRow, float fPercent, float fWait, unsigned short usMode )
{
unsigned i;
for( i = 0; i < m_SpeedSegments.size(); i++ )
{
if( m_SpeedSegments[i].m_iStartRow >= iRow)
break;
}
if ( i == m_SpeedSegments.size() || m_SpeedSegments[i].m_iStartRow != iRow )
{
// the core mod itself matters the most for comparisons.
if( i == 0 || m_SpeedSegments[i-1].m_fPercent != fPercent )
AddSpeedSegment( SpeedSegment(iRow, fPercent, fWait, usMode) );
}
else
{
// The others aren't compared: only the mod itself matters.
if( i > 0 && m_SpeedSegments[i-1].m_fPercent == fPercent )
m_SpeedSegments.erase( m_SpeedSegments.begin()+i,
m_SpeedSegments.begin()+i+1 );
else
{
m_SpeedSegments[i].m_fPercent = fPercent;
m_SpeedSegments[i].m_fWait = fWait;
m_SpeedSegments[i].m_usMode = usMode;
}
}
}
void TimingData::SetFakeAtRow( int iRow, float fNew )
{
unsigned i;
for( i=0; i<m_FakeSegments.size(); i++ )
if( m_FakeSegments[i].m_iStartRow == iRow )
break;
bool valid = iRow > 0 && fNew > 0;
if( i == m_FakeSegments.size() )
{
if( valid )
{
AddFakeSegment( FakeSegment(iRow, fNew) );
}
}
else
{
if( valid )
{
m_FakeSegments[i].m_fLengthBeats = fNew;
}
else
m_FakeSegments.erase( m_FakeSegments.begin()+i, m_FakeSegments.begin()+i+1 );
}
}
void TimingData::SetSpeedPercentAtRow( int iRow, float fPercent )
{
SetSpeedAtRow( iRow,
fPercent,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_fWait,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_usMode);
}
void TimingData::SetSpeedWaitAtRow( int iRow, float fWait )
{
SetSpeedAtRow( iRow,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_fPercent,
fWait,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_usMode);
}
void TimingData::SetSpeedModeAtRow( int iRow, unsigned short usMode )
{
SetSpeedAtRow( iRow,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_fPercent,
GetSpeedSegmentAtBeat( NoteRowToBeat( iRow ) ).m_fWait,
usMode );
}
float TimingData::GetStopAtRow( int iNoteRow, bool bDelay ) const float TimingData::GetStopAtRow( int iNoteRow, bool bDelay ) const
{ {
for( unsigned i=0; i<m_StopSegments.size(); i++ ) for( unsigned i=0; i<m_StopSegments.size(); i++ )
@@ -283,7 +379,34 @@ float TimingData::GetWarpAtRow( int iWarpRow ) const
{ {
if( m_WarpSegments[i].m_iStartRow == iWarpRow ) if( m_WarpSegments[i].m_iStartRow == iWarpRow )
{ {
return m_WarpSegments[i].m_fEndBeat; return m_WarpSegments[i].m_fLengthBeats;
}
}
return 0;
}
float TimingData::GetSpeedPercentAtRow( int iRow )
{
return GetSpeedSegmentAtRow( iRow ).m_fPercent;
}
float TimingData::GetSpeedWaitAtRow( int iRow )
{
return GetSpeedSegmentAtRow( iRow ).m_fWait;
}
unsigned short TimingData::GetSpeedModeAtRow( int iRow )
{
return GetSpeedSegmentAtRow( iRow ).m_usMode;
}
float TimingData::GetFakeAtRow( int iFakeRow ) const
{
for( unsigned i=0; i<m_FakeSegments.size(); i++ )
{
if( m_FakeSegments[i].m_iStartRow == iFakeRow )
{
return m_FakeSegments[i].m_fLengthBeats;
} }
} }
return 0; return 0;
@@ -371,6 +494,18 @@ int TimingData::GetWarpSegmentIndexAtRow( int iNoteRow ) const
return static_cast<int>(i); return static_cast<int>(i);
} }
int TimingData::GetFakeSegmentIndexAtRow( int iNoteRow ) const
{
unsigned i;
for( i=0; i<m_FakeSegments.size()-1; i++ )
{
const FakeSegment& s = m_FakeSegments[i+1];
if( s.m_iStartRow > iNoteRow )
break;
}
return static_cast<int>(i);
}
bool TimingData::IsWarpAtRow( int iNoteRow ) const bool TimingData::IsWarpAtRow( int iNoteRow ) const
{ {
if( m_WarpSegments.empty() ) if( m_WarpSegments.empty() )
@@ -378,7 +513,7 @@ bool TimingData::IsWarpAtRow( int iNoteRow ) const
int i = GetWarpSegmentIndexAtRow( iNoteRow ); int i = GetWarpSegmentIndexAtRow( iNoteRow );
const WarpSegment& s = m_WarpSegments[i]; const WarpSegment& s = m_WarpSegments[i];
if( s.m_iStartRow <= iNoteRow && iNoteRow < BeatToNoteRow(s.m_fEndBeat) ) if( s.m_iStartRow <= iNoteRow && iNoteRow < (s.m_iStartRow + BeatToNoteRow(s.m_fLengthBeats) ) )
{ {
if( m_StopSegments.empty() ) if( m_StopSegments.empty() )
{ {
@@ -393,6 +528,20 @@ bool TimingData::IsWarpAtRow( int iNoteRow ) const
return false; return false;
} }
bool TimingData::IsFakeAtRow( int iNoteRow ) const
{
if( m_FakeSegments.empty() )
return false;
int i = GetFakeSegmentIndexAtRow( iNoteRow );
const FakeSegment& s = m_FakeSegments[i];
if( s.m_iStartRow <= iNoteRow && iNoteRow < ( s.m_iStartRow + BeatToNoteRow(s.m_fLengthBeats) ) )
{
return true;
}
return false;
}
int TimingData::GetTimeSignatureSegmentIndexAtRow( int iRow ) const int TimingData::GetTimeSignatureSegmentIndexAtRow( int iRow ) const
{ {
unsigned i; unsigned i;
@@ -426,6 +575,15 @@ int TimingData::GetLabelSegmentIndexAtRow( int iRow ) const
return static_cast<int>(i); return static_cast<int>(i);
} }
int TimingData::GetSpeedSegmentIndexAtRow( int iRow ) const
{
unsigned i;
for (i=0; i < m_SpeedSegments.size() - 1; i++ )
if( m_SpeedSegments[i+1].m_iStartRow > iRow )
break;
return static_cast<int>(i);
}
BPMSegment& TimingData::GetBPMSegmentAtRow( int iNoteRow ) BPMSegment& TimingData::GetBPMSegmentAtRow( int iNoteRow )
{ {
static BPMSegment empty; static BPMSegment empty;
@@ -445,6 +603,15 @@ TimeSignatureSegment& TimingData::GetTimeSignatureSegmentAtRow( int iRow )
return m_vTimeSignatureSegments[i]; return m_vTimeSignatureSegments[i];
} }
SpeedSegment& TimingData::GetSpeedSegmentAtRow( int iRow )
{
unsigned i;
for( i=0; i<m_SpeedSegments.size()-1; i++ )
if( m_SpeedSegments[i+1].m_iStartRow > iRow )
break;
return m_SpeedSegments[i];
}
int TimingData::GetTimeSignatureNumeratorAtRow( int iRow ) int TimingData::GetTimeSignatureNumeratorAtRow( int iRow )
{ {
return GetTimeSignatureSegmentAtRow( iRow ).m_iNumerator; return GetTimeSignatureSegmentAtRow( iRow ).m_iNumerator;
@@ -493,6 +660,16 @@ WarpSegment& TimingData::GetWarpSegmentAtRow( int iRow )
return m_WarpSegments[i]; return m_WarpSegments[i];
} }
FakeSegment& TimingData::GetFakeSegmentAtRow( int iRow )
{
static FakeSegment empty;
if( m_FakeSegments.empty() )
return empty;
int i = GetFakeSegmentIndexAtRow( iRow );
return m_FakeSegments[i];
}
int TimingData::GetTickcountSegmentIndexAtRow( int iRow ) const int TimingData::GetTickcountSegmentIndexAtRow( int iRow ) const
{ {
unsigned i; unsigned i;
@@ -652,16 +829,19 @@ void TimingData::GetBeatAndBPSFromElapsedTimeNoOffset( float fElapsedTime, float
} }
break; break;
case FOUND_WARP: case FOUND_WARP:
bIsWarping = true;
if( itWS->m_fEndBeat > fWarpDestination )
{ {
fWarpDestination = itWS->m_fEndBeat; bIsWarping = true;
float fWarpSum = itWS->m_fLengthBeats + NoteRowToBeat( itWS->m_iStartRow );
if( fWarpSum > fWarpDestination )
{
fWarpDestination = fWarpSum;
} }
iWarpBeginOut = iEventRow; iWarpBeginOut = iEventRow;
fWarpDestinationOut = fWarpDestination; fWarpDestinationOut = fWarpDestination;
itWS ++; itWS ++;
break; break;
} }
}
iLastRow = iEventRow; iLastRow = iEventRow;
} }
@@ -744,14 +924,17 @@ float TimingData::GetElapsedTimeFromBeatNoOffset( float fBeat ) const
case FOUND_MARKER: case FOUND_MARKER:
return fLastTime; return fLastTime;
case FOUND_WARP: case FOUND_WARP:
bIsWarping = true;
if( itWS->m_fEndBeat > fWarpDestination )
{ {
fWarpDestination = itWS->m_fEndBeat; bIsWarping = true;
float fWarpSum = itWS->m_fLengthBeats + NoteRowToBeat( itWS->m_iStartRow );
if( fWarpSum > fWarpDestination )
{
fWarpDestination = fWarpSum;
} }
itWS ++; itWS ++;
break; break;
} }
}
iLastRow = iEventRow; iLastRow = iEventRow;
} }
@@ -790,13 +973,13 @@ void TimingData::ScaleRegion( float fScale, int iStartIndex, int iEndIndex, bool
for( unsigned i = 0; i < m_WarpSegments.size(); i++ ) for( unsigned i = 0; i < m_WarpSegments.size(); i++ )
{ {
const int iSegStartRow = m_WarpSegments[i].m_iStartRow; const int iSegStartRow = m_WarpSegments[i].m_iStartRow;
const int iSegEndRow = BeatToNoteRow( m_WarpSegments[i].m_fEndBeat ); const int iSegEndRow = iSegStartRow + BeatToNoteRow( m_WarpSegments[i].m_fLengthBeats );
if( iSegEndRow >= iStartIndex ) if( iSegEndRow >= iStartIndex )
{ {
if( iSegEndRow > iEndIndex ) if( iSegEndRow > iEndIndex )
m_WarpSegments[i].m_fEndBeat += NoteRowToBeat(lrintf((iEndIndex - iStartIndex) * (fScale - 1))); m_WarpSegments[i].m_fLengthBeats += NoteRowToBeat(lrintf((iEndIndex - iStartIndex) * (fScale - 1)));
else else
m_WarpSegments[i].m_fEndBeat = NoteRowToBeat(lrintf((iSegEndRow - iStartIndex) * fScale) + iStartIndex); m_WarpSegments[i].m_fLengthBeats = NoteRowToBeat(lrintf((iSegEndRow - iStartIndex) * fScale));
} }
if( iSegStartRow < iStartIndex ) if( iSegStartRow < iStartIndex )
continue; continue;
@@ -839,6 +1022,36 @@ void TimingData::ScaleRegion( float fScale, int iStartIndex, int iEndIndex, bool
m_LabelSegments[i].m_iStartRow = lrintf( (iSegStart - iStartIndex) * fScale ) + iStartIndex; m_LabelSegments[i].m_iStartRow = lrintf( (iSegStart - iStartIndex) * fScale ) + iStartIndex;
} }
for ( unsigned i = 0; i < m_SpeedSegments.size(); i++ )
{
const int iSegStart = m_SpeedSegments[i].m_iStartRow;
if( iSegStart < iStartIndex )
continue;
else if( iSegStart > iEndIndex )
m_SpeedSegments[i].m_iStartRow += lrintf( (iEndIndex - iStartIndex) * (fScale - 1) );
else
m_SpeedSegments[i].m_iStartRow = lrintf( (iSegStart - iStartIndex) * fScale ) + iStartIndex;
}
for( unsigned i = 0; i < m_FakeSegments.size(); i++ )
{
const int iSegStartRow = m_FakeSegments[i].m_iStartRow;
const int iSegEndRow = iSegStartRow + BeatToNoteRow( m_FakeSegments[i].m_fLengthBeats );
if( iSegEndRow >= iStartIndex )
{
if( iSegEndRow > iEndIndex )
m_FakeSegments[i].m_fLengthBeats += NoteRowToBeat(lrintf((iEndIndex - iStartIndex) * (fScale - 1)));
else
m_FakeSegments[i].m_fLengthBeats = NoteRowToBeat(lrintf((iSegEndRow - iStartIndex) * fScale));
}
if( iSegStartRow < iStartIndex )
continue;
else if( iSegStartRow > iEndIndex )
m_FakeSegments[i].m_iStartRow += lrintf((iEndIndex - iStartIndex) * (fScale - 1));
else
m_FakeSegments[i].m_iStartRow = lrintf((iSegStartRow - iStartIndex) * fScale) + iStartIndex;
}
// adjust BPM changes to preserve timing // adjust BPM changes to preserve timing
if( bAdjustBPM ) if( bAdjustBPM )
{ {
@@ -886,8 +1099,6 @@ void TimingData::InsertRows( int iStartRow, int iRowsToAdd )
for( unsigned i = 0; i < m_WarpSegments.size(); i++ ) for( unsigned i = 0; i < m_WarpSegments.size(); i++ )
{ {
WarpSegment &warp = m_WarpSegments[i]; WarpSegment &warp = m_WarpSegments[i];
if( BeatToNoteRow(warp.m_fEndBeat) >= iStartRow )
warp.m_fEndBeat += NoteRowToBeat(iRowsToAdd);
if( warp.m_iStartRow < iStartRow ) if( warp.m_iStartRow < iStartRow )
continue; continue;
warp.m_iStartRow += iRowsToAdd; warp.m_iStartRow += iRowsToAdd;
@@ -924,6 +1135,22 @@ void TimingData::InsertRows( int iStartRow, int iRowsToAdd )
labl.m_iStartRow += iRowsToAdd; labl.m_iStartRow += iRowsToAdd;
} }
for( unsigned i = 0; i < m_SpeedSegments.size(); i++ )
{
SpeedSegment &sped = m_SpeedSegments[i];
if( sped.m_iStartRow < iStartRow )
continue;
sped.m_iStartRow += iRowsToAdd;
}
for( unsigned i = 0; i < m_FakeSegments.size(); i++ )
{
FakeSegment &fake = m_FakeSegments[i];
if( fake.m_iStartRow < iStartRow )
continue;
fake.m_iStartRow += iRowsToAdd;
}
if( iStartRow == 0 ) if( iStartRow == 0 )
{ {
/* If we're shifting up at the beginning, we just shifted up the first /* If we're shifting up at the beginning, we just shifted up the first
@@ -985,9 +1212,6 @@ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete )
{ {
WarpSegment &warp = m_WarpSegments[i]; WarpSegment &warp = m_WarpSegments[i];
if( BeatToNoteRow(warp.m_fEndBeat) >= iStartRow )
warp.m_fEndBeat = max( NoteRowToBeat(iStartRow), warp.m_fEndBeat - NoteRowToBeat(iRowsToDelete) );
if( warp.m_iStartRow < iStartRow ) if( warp.m_iStartRow < iStartRow )
continue; continue;
@@ -1081,9 +1305,98 @@ void TimingData::DeleteRows( int iStartRow, int iRowsToDelete )
labl.m_iStartRow -= iRowsToDelete; labl.m_iStartRow -= iRowsToDelete;
} }
for( unsigned i = 0; i < m_SpeedSegments.size(); i++ )
{
SpeedSegment &sped = m_SpeedSegments[i];
if( sped.m_iStartRow < iStartRow )
continue;
if( sped.m_iStartRow < iStartRow+iRowsToDelete )
{
m_SpeedSegments.erase( m_SpeedSegments.begin()+i, m_SpeedSegments.begin()+i+1 );
--i;
continue;
}
sped.m_iStartRow -= iRowsToDelete;
}
for( unsigned i = 0; i < m_FakeSegments.size(); i++ )
{
FakeSegment &fake = m_FakeSegments[i];
if( fake.m_iStartRow < iStartRow )
continue;
if( fake.m_iStartRow < iStartRow+iRowsToDelete )
{
m_FakeSegments.erase( m_FakeSegments.begin()+i, m_FakeSegments.begin()+i+1 );
--i;
continue;
}
fake.m_iStartRow -= iRowsToDelete;
}
this->SetBPMAtRow( iStartRow, fNewBPM ); this->SetBPMAtRow( iStartRow, fNewBPM );
} }
void TimingData::TidyUpData()
{
// If there are no BPM segments, provide a default.
if( m_BPMSegments.empty() )
{
LOG->UserLog( "Song file", m_sFile, "has no BPM segments, default provided." );
AddBPMSegment( BPMSegment(0, 60) );
}
// Make sure the first BPM segment starts at beat 0.
if( m_BPMSegments[0].m_iStartRow != 0 )
m_BPMSegments[0].m_iStartRow = 0;
// If no time signature specified, assume 4/4 time for the whole song.
if( m_vTimeSignatureSegments.empty() )
{
TimeSignatureSegment seg(0, 4, 4);
m_vTimeSignatureSegments.push_back( seg );
}
// Likewise, if no tickcount signature is specified, assume 2 ticks
//per beat for the entire song. The default of 2 is chosen more
//for compatibility with the Pump Pro series than anything else.
if( m_TickcountSegments.empty() )
{
TickcountSegment seg(0, 2);
m_TickcountSegments.push_back( seg );
}
// Have a default combo segment of one just in case.
if( m_ComboSegments.empty() )
{
ComboSegment seg(0, 1);
m_ComboSegments.push_back( seg );
}
// Have a default label segment just in case.
if( m_LabelSegments.empty() )
{
LabelSegment seg(0, "Song Start");
m_LabelSegments.push_back( seg );
}
// Always be sure there is a starting speed.
if( m_SpeedSegments.empty() )
{
SpeedSegment seg(0, 1, 0);
m_SpeedSegments.push_back( seg );
}
}
bool TimingData::HasBpmChanges() const bool TimingData::HasBpmChanges() const
{ {
return m_BPMSegments.size()>1; return m_BPMSegments.size()>1;
@@ -1099,6 +1412,16 @@ bool TimingData::HasWarps() const
return m_WarpSegments.size()>0; return m_WarpSegments.size()>0;
} }
bool TimingData::HasFakes() const
{
return m_FakeSegments.size()>0;
}
bool TimingData::HasSpeedChanges() const
{
return m_SpeedSegments.size()>1;
}
void TimingData::NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const void TimingData::NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const
{ {
iMeasureIndexOut = 0; iMeasureIndexOut = 0;
@@ -1145,6 +1468,8 @@ public:
static int HasStops( T* p, lua_State *L ) { lua_pushboolean(L, p->HasStops()); return 1; } static int HasStops( T* p, lua_State *L ) { lua_pushboolean(L, p->HasStops()); return 1; }
static int HasBPMChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasBpmChanges()); return 1; } static int HasBPMChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasBpmChanges()); return 1; }
static int HasWarps( T* p, lua_State *L ) { lua_pushboolean(L, p->HasWarps()); return 1; } static int HasWarps( T* p, lua_State *L ) { lua_pushboolean(L, p->HasWarps()); return 1; }
static int HasFakes( T* p, lua_State *L ) { lua_pushboolean(L, p->HasFakes()); return 1; }
static int HasSpeedChanges( T* p, lua_State *L ) { lua_pushboolean(L, p->HasSpeedChanges()); return 1; }
static int GetStops( T* p, lua_State *L ) static int GetStops( T* p, lua_State *L )
{ {
vector<RString> vStops; vector<RString> vStops;
@@ -1232,6 +1557,8 @@ public:
ADD_METHOD( HasStops ); ADD_METHOD( HasStops );
ADD_METHOD( HasBPMChanges ); ADD_METHOD( HasBPMChanges );
ADD_METHOD( HasWarps ); ADD_METHOD( HasWarps );
ADD_METHOD( HasFakes );
ADD_METHOD( HasSpeedChanges );
ADD_METHOD( GetStops ); ADD_METHOD( GetStops );
ADD_METHOD( GetDelays ); ADD_METHOD( GetDelays );
ADD_METHOD( GetBPMs ); ADD_METHOD( GetBPMs );
+470 -18
View File
@@ -313,8 +313,8 @@ struct TimeSignatureSegment
* *
* A warp segment is used to replicate the effects of Negative BPMs without * A warp segment is used to replicate the effects of Negative BPMs without
* abusing negative BPMs. Negative BPMs should be converted to warp segments. * abusing negative BPMs. Negative BPMs should be converted to warp segments.
* WarpAt=WarpTo is the format, where both are in beats. (Technically they're * WarpAt=WarpToRelative is the format, where both are in beats.
* both rows though.) */ * (Technically they're both rows though.) */
struct WarpSegment struct WarpSegment
{ {
/** /**
@@ -322,45 +322,45 @@ struct WarpSegment
* *
* It is best to override the values as soon as possible. * It is best to override the values as soon as possible.
*/ */
WarpSegment() : m_iStartRow(-1), m_fEndBeat(-1) { } WarpSegment() : m_iStartRow(-1), m_fLengthBeats(-1) { }
/** /**
* @brief Create a Warp Segment with the specified starting row and row to warp to. * @brief Create a Warp Segment with the specified starting row and row to warp to.
* @param s the starting row of this segment. * @param s the starting row of this segment.
* @param r the row to warp to. * @param r the number of rows to jump ahead.
*/ */
WarpSegment( int s, int r ): m_iStartRow(max(0, (s < r ? s : r))), WarpSegment( int s, int r ): m_iStartRow(s),
m_fEndBeat(max(0, NoteRowToBeat((r > s ? r : s)))) {} m_fLengthBeats(NoteRowToBeat(r)) {}
/** /**
* @brief Creates a Warp Segment with the specified starting row and beat to warp to. * @brief Creates a Warp Segment with the specified starting row and beat to warp to.
* @param s the starting row of this segment. * @param s the starting row of this segment.
* @param b the beat to warp to. * @param b the number of beats to jump ahead.
*/ */
WarpSegment( int s, float b ): m_iStartRow(max(0, s)), WarpSegment( int s, float b ): m_iStartRow(max(0, s)),
m_fEndBeat(max(0, b)) {} m_fLengthBeats(max(0, b)) {}
/** /**
* @brief Create a Warp Segment with the specified starting beat and row to warp to. * @brief Create a Warp Segment with the specified starting beat and row to warp to.
* @param s the starting beat in this segment. * @param s the starting beat in this segment.
* @param r the row to warp to. * @param r the number of rows to jump ahead.
*/ */
WarpSegment( float s, int r ): WarpSegment( float s, int r ):
m_iStartRow(max(0, BeatToNoteRow(s))), m_iStartRow(max(0, BeatToNoteRow(s))),
m_fEndBeat(max(0, NoteRowToBeat(r))) {} m_fLengthBeats(max(0, NoteRowToBeat(r))) {}
/** /**
* @brief Creates a Warp Segment with the specified starting beat and beat to warp to. * @brief Creates a Warp Segment with the specified starting beat and beat to warp to.
* @param s the starting beat of this segment. * @param s the starting beat of this segment.
* @param b the beat to warp to. * @param b the number of beats to jump ahead.
*/ */
WarpSegment( float s, float b ): WarpSegment( float s, float b ):
m_iStartRow(max(0, BeatToNoteRow((s < b ? s : b)))), m_iStartRow(BeatToNoteRow(s)),
m_fEndBeat(max(0, (b > s ? b : s))) {} m_fLengthBeats(b) {}
/** /**
* @brief The row in which the WarpSegment activates. * @brief The row in which the WarpSegment activates.
*/ */
int m_iStartRow; int m_iStartRow;
/** /**
* @brief The beat to warp to. * @brief The number of beats to warp ahead by.
*/ */
float m_fEndBeat; float m_fLengthBeats;
/** /**
* @brief Compares two WarpSegments to see if they are equal to each other. * @brief Compares two WarpSegments to see if they are equal to each other.
* @param other the other WarpSegment to compare to. * @param other the other WarpSegment to compare to.
@@ -369,7 +369,7 @@ struct WarpSegment
bool operator==( const WarpSegment &other ) const bool operator==( const WarpSegment &other ) const
{ {
COMPARE( m_iStartRow ); COMPARE( m_iStartRow );
COMPARE( m_fEndBeat ); COMPARE( m_fLengthBeats );
return true; return true;
} }
/** /**
@@ -386,7 +386,7 @@ struct WarpSegment
bool operator<( const WarpSegment &other ) const bool operator<( const WarpSegment &other ) const
{ {
return m_iStartRow < other.m_iStartRow || return m_iStartRow < other.m_iStartRow ||
( m_iStartRow == other.m_iStartRow && m_fEndBeat < other.m_fEndBeat ); ( m_iStartRow == other.m_iStartRow && m_fLengthBeats < other.m_fLengthBeats );
} }
/** /**
* @brief Compares two WarpSegments to see if one is less than or equal to the other. * @brief Compares two WarpSegments to see if one is less than or equal to the other.
@@ -508,7 +508,7 @@ struct ComboSegment
* @param t the amount the combo increases on a succesful hit. * @param t the amount the combo increases on a succesful hit.
*/ */
ComboSegment( int s, int t ): m_iStartRow(max(0, s)), ComboSegment( int s, int t ): m_iStartRow(max(0, s)),
m_iCombo(max(1,t)) {} m_iCombo(max(0,t)) {}
/** /**
* @brief The row in which the ComboSegment activates. * @brief The row in which the ComboSegment activates.
*/ */
@@ -647,6 +647,243 @@ struct LabelSegment
bool operator>=( const LabelSegment &other ) const { return !operator<(other); } bool operator>=( const LabelSegment &other ) const { return !operator<(other); }
}; };
/**
* @brief Identifies when the arrow scroll changes.
*
* SpeedSegments take a Player's scrolling BPM (Step's BPM * speed mod),
* and then multiplies it with the percentage value. No matter the player's
* speed mod, the ratio will be the same. Unlike forced attacks, these
* cannot be turned off at a set time: reset it by setting the precentage
* back to 1.
*
* These were inspired by the Pump It Up series. */
struct SpeedSegment
{
/** @brief Sets up the SpeedSegment with default values. */
SpeedSegment(): m_iStartRow(0),
m_fPercent(1), m_fWait(0), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param i The row this activates.
* @param p The percentage to use. */
SpeedSegment(int i, float p): m_iStartRow(0),
m_fPercent(p), m_fWait(0), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param r The beat this activates.
* @param p The percentage to use. */
SpeedSegment(float r, float p): m_iStartRow(BeatToNoteRow(r)),
m_fPercent(p), m_fWait(0), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param i The row this activates.
* @param p The percentage to use.
* @param w The number of beats to wait. */
SpeedSegment(int i, float p, float w): m_iStartRow(i),
m_fPercent(p), m_fWait(w), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param r The beat this activates.
* @param p The percentage to use.
* @param w The number of beats to wait. */
SpeedSegment(float r, float p, float w): m_iStartRow(BeatToNoteRow(r)),
m_fPercent(p), m_fWait(w), m_usMode(0) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param i The row this activates.
* @param p The percentage to use.
* @param w The number of beats/seconds to wait.
* @param k The mode used for the wait variable. */
SpeedSegment(int i, float p, float w, unsigned short k): m_iStartRow(i),
m_fPercent(p), m_fWait(w), m_usMode(k) {}
/**
* @brief Sets up the SpeedSegment with specified values.
* @param r The beat this activates.
* @param p The percentage to use.
* @param w The number of beats/seconds to wait.
* @param k The mode used for the wait variable.*/
SpeedSegment(float r, float p, float w, unsigned short k): m_iStartRow(BeatToNoteRow(r)),
m_fPercent(p), m_fWait(w), m_usMode(k) {}
/** @brief The row in which the ComboSegment activates. */
int m_iStartRow;
/** @brief The percentage to use when multiplying the Player's BPM. */
float m_fPercent;
/**
* @brief The number of beats or seconds to wait for the change to take place.
*
* A value of 0 means this is immediate. */
float m_fWait;
/**
* @brief The mode that this segment uses for the math.
*
* 0: beats
* 1: seconds
* other
*/
unsigned short m_usMode;
/**
* @brief Compares two SpeedSegments to see if they are equal to each other.
* @param other the other SpeedSegment to compare to.
* @return the equality of the two segments.
*/
bool operator==( const SpeedSegment &other ) const
{
COMPARE( m_iStartRow );
COMPARE( m_fPercent );
COMPARE( m_usMode );
COMPARE( m_fWait );
return true;
}
/**
* @brief Compares two SpeedSegments to see if they are not equal to each other.
* @param other the other SpeedSegment to compare to.
* @return the inequality of the two segments.
*/
bool operator!=( const SpeedSegment &other ) const { return !operator==(other); }
/**
* @brief Compares two SpeedSegments to see if one is less than the other.
* @param other the other SpeedSegment to compare to.
* @return the truth/falsehood of if the first is less than the second.
*/
bool operator<( const SpeedSegment &other ) const { return m_iStartRow < other.m_iStartRow; }
/**
* @brief Compares two SpeedSegments to see if one is less than or equal to the other.
* @param other the other SpeedSegment to compare to.
* @return the truth/falsehood of if the first is less or equal to than the second.
*/
bool operator<=( const SpeedSegment &other ) const
{
return ( operator<(other) || operator==(other) );
}
/**
* @brief Compares two SpeedSegments to see if one is greater than the other.
* @param other the other SpeedSegment to compare to.
* @return the truth/falsehood of if the first is greater than the second.
*/
bool operator>( const SpeedSegment &other ) const { return !operator<=(other); }
/**
* @brief Compares two SpeedSegments to see if one is greater than or equal to the other.
* @param other the other SpeedSegment to compare to.
* @return the truth/falsehood of if the first is greater than or equal to the second.
*/
bool operator>=( const SpeedSegment &other ) const { return !operator<(other); }
};
/**
* @brief Identifies when a whole region of arrows is to be ignored.
*
* FakeSegments are similar to the Fake Tap Notes in that the contents
* inside are neither for nor against the player. They can be useful for
* mission modes, in conjunction with WarpSegments, or perhaps other
* uses not thought up at the time of this comment. Unlike the Warp
* Segments, these are not magically jumped over: instead, these are
* drawn normally.
*
* These were inspired by the Pump It Up series. */
struct FakeSegment
{
/**
* @brief Create a simple Fake Segment with default values.
*
* It is best to override the values as soon as possible.
*/
FakeSegment() : m_iStartRow(-1), m_fLengthBeats(-1) { }
/**
* @brief Create a Fake Segment with the specified values.
* @param s the starting row of this segment.
* @param r the number of rows this segment lasts.
*/
FakeSegment( int s, int r ): m_iStartRow(s),
m_fLengthBeats(NoteRowToBeat(r)) {}
/**
* @brief Creates a Fake Segment with the specified values.
* @param s the starting row of this segment.
* @param b the number of beats this segment lasts.
*/
FakeSegment( int s, float b ): m_iStartRow(max(0, s)),
m_fLengthBeats(max(0, b)) {}
/**
* @brief Create a Fake Segment with the specified values.
* @param s the starting beat in this segment.
* @param r the number of rows this segment lasts.
*/
FakeSegment( float s, int r ):
m_iStartRow(max(0, BeatToNoteRow(s))),
m_fLengthBeats(max(0, NoteRowToBeat(r))) {}
/**
* @brief Creates a Fake Segment with the specified values.
* @param s the starting beat of this segment.
* @param b the number of beats this segment lasts.
*/
FakeSegment( float s, float b ):
m_iStartRow(BeatToNoteRow(s)),
m_fLengthBeats(b) {}
/**
* @brief The row in which the FakeSegment activates.
*/
int m_iStartRow;
/**
* @brief The number of beats the FakeSegment is alive for.
*/
float m_fLengthBeats;
/**
* @brief Compares two FakeSegments to see if they are equal to each other.
* @param other the other FakeSegment to compare to.
* @return the equality of the two segments.
*/
bool operator==( const FakeSegment &other ) const
{
COMPARE( m_iStartRow );
COMPARE( m_fLengthBeats );
return true;
}
/**
* @brief Compares two FakeSegments to see if they are not equal to each other.
* @param other the other FakeSegment to compare to.
* @return the inequality of the two segments.
*/
bool operator!=( const FakeSegment &other ) const { return !operator==(other); }
/**
* @brief Compares two FakeSegments to see if one is less than the other.
* @param other the other FakeSegment to compare to.
* @return the truth/falsehood of if the first is less than the second.
*/
bool operator<( const FakeSegment &other ) const
{
return m_iStartRow < other.m_iStartRow ||
( m_iStartRow == other.m_iStartRow && m_fLengthBeats < other.m_fLengthBeats );
}
/**
* @brief Compares two FakeSegments to see if one is less than or equal to the other.
* @param other the other FakeSegment to compare to.
* @return the truth/falsehood of if the first is less or equal to than the second.
*/
bool operator<=( const FakeSegment &other ) const
{
return ( operator<(other) || operator==(other) );
}
/**
* @brief Compares two FakeSegments to see if one is greater than the other.
* @param other the other FakeSegment to compare to.
* @return the truth/falsehood of if the first is greater than the second.
*/
bool operator>( const FakeSegment &other ) const { return !operator<=(other); }
/**
* @brief Compares two FakeSegments to see if one is greater than or equal to the other.
* @param other the other FakeSegment to compare to.
* @return the truth/falsehood of if the first is greater than or equal to the second.
*/
bool operator>=( const FakeSegment &other ) const { return !operator<(other); }
};
/** /**
* @brief Holds data for translating beats<->seconds. * @brief Holds data for translating beats<->seconds.
@@ -658,6 +895,10 @@ public:
* @brief Sets up initial timing data. * @brief Sets up initial timing data.
*/ */
TimingData(); TimingData();
/**
* @brief Sets up initial timing data with a defined offset.
* @param fOffset the offset from the 0th beat. */
TimingData(float fOffset);
/** /**
* @brief Gets the actual BPM of the song. * @brief Gets the actual BPM of the song.
* @param fMinBPMOut the minimium specified BPM. * @param fMinBPMOut the minimium specified BPM.
@@ -1234,6 +1475,193 @@ public:
*/ */
float GetNextLabelSegmentBeatAtBeat( float fBeat ) const { return GetNextLabelSegmentBeatAtRow( BeatToNoteRow(fBeat) ); } float GetNextLabelSegmentBeatAtBeat( float fBeat ) const { return GetNextLabelSegmentBeatAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Retrieve the Speed's percent at the given row.
* @param iNoteRow the row in question.
* @return the percent.
*/
float GetSpeedPercentAtRow( int iNoteRow );
/**
* @brief Retrieve the Speed's percent at the given beat.
* @param fBeat the beat in question.
* @return the percent.
*/
float GetSpeedPercentAtBeat( float fBeat ) { return GetSpeedPercentAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Retrieve the Speed's wait at the given row.
* @param iNoteRow the row in question.
* @return the wait.
*/
float GetSpeedWaitAtRow( int iNoteRow );
/**
* @brief Retrieve the Speed's wait at the given beat.
* @param fBeat the beat in question.
* @return the wait.
*/
float GetSpeedWaitAtBeat( float fBeat ) { return GetSpeedWaitAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Retrieve the Speed's mode at the given row.
* @param iNoteRow the row in question.
* @return the mode.
*/
unsigned short GetSpeedModeAtRow( int iNoteRow );
/**
* @brief Retrieve the Speed's mode at the given beat.
* @param fBeat the beat in question.
* @return the mode.
*/
unsigned short GetSpeedModeAtBeat( float fBeat ) { return GetSpeedModeAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Set the row to have the new Speed.
* @param iNoteRow the row to have the new Speed.
* @param fPercent the percent.
* @param fWait the wait.
* @param usMode the mode.
*/
void SetSpeedAtRow( int iNoteRow, float fPercent, float fWait, unsigned short usMode );
/**
* @brief Set the beat to have the new Speed.
* @param fBeat the beat to have the new Speed.
* @param fPercent the percent.
* @param fWait the wait.
* @param usMode the mode.
*/
void SetSpeedAtBeat( float fBeat, float fPercent, float fWait, unsigned short usMode ) { SetSpeedAtRow( BeatToNoteRow(fBeat), fPercent, fWait, usMode ); }
/**
* @brief Set the row to have the new Speed percent.
* @param iNoteRow the row to have the new Speed percent.
* @param fPercent the percent.
*/
void SetSpeedPercentAtRow( int iNoteRow, float fPercent );
/**
* @brief Set the beat to have the new Speed percent.
* @param fBeat the beat to have the new Speed percent.
* @param fPercent the percent.
*/
void SetSpeedPercentAtBeat( float fBeat, float fPercent ) { SetSpeedPercentAtRow( BeatToNoteRow(fBeat), fPercent); }
/**
* @brief Set the row to have the new Speed wait.
* @param iNoteRow the row to have the new Speed wait.
* @param fWait the wait.
*/
void SetSpeedWaitAtRow( int iNoteRow, float fWait );
/**
* @brief Set the beat to have the new Speed wait.
* @param fBeat the beat to have the new Speed wait.
* @param fWait the wait.
*/
void SetSpeedWaitAtBeat( float fBeat, float fWait ) { SetSpeedWaitAtRow( BeatToNoteRow(fBeat), fWait); }
/**
* @brief Set the row to have the new Speed mode.
* @param iNoteRow the row to have the new Speed mode.
* @param usMode the mode.
*/
void SetSpeedModeAtRow( int iNoteRow, unsigned short usMode );
/**
* @brief Set the beat to have the new Speed mode.
* @param fBeat the beat to have the new Speed mode.
* @param usMode the mode.
*/
void SetSpeedModeAtBeat( float fBeat, unsigned short usMode ) { SetSpeedModeAtRow( BeatToNoteRow(fBeat), usMode); }
/**
* @brief Retrieve the SpeedSegment at the specified row.
* @param iNoteRow the row that has a SpeedSegment.
* @return the SpeedSegment in question.
*/
SpeedSegment& GetSpeedSegmentAtRow( int iNoteRow );
/**
* @brief Retrieve the SpeedSegment at the specified beat.
* @param fBeat the beat that has a SpeedSegment.
* @return the SpeedSegment in question.
*/
SpeedSegment& GetSpeedSegmentAtBeat( float fBeat ) { return GetSpeedSegmentAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Retrieve the index of the SpeedSegments at the specified row.
* @param iNoteRow the row that has a SpeedSegment.
* @return the SpeedSegment's index in question.
*/
int GetSpeedSegmentIndexAtRow( int iNoteRow ) const;
/**
* @brief Retrieve the index of the SpeedSegments at the specified beat.
* @param fBeat the beat that has a SpeedSegment.
* @return the SpeedSegment's index in question.
*/
int GetSpeedSegmentIndexAtBeat( float fBeat ) const { return GetSpeedSegmentIndexAtRow( BeatToNoteRow(fBeat) ); }
/**
* @brief Add the SpeedSegment to the TimingData.
* @param seg the new SpeedSegment.
*/
void AddSpeedSegment( const SpeedSegment &seg );
/**
* @brief Determine when the fakes end.
* @param iRow The row you start on.
* @return the time when the fakes end.
*/
float GetFakeAtRow( int iRow ) const;
/**
* @brief Determine when the fakes end.
* @param fBeat The beat you start on.
* @return the time when the fakes end.
*/
float GetFakeAtBeat( float fBeat ) const { return GetFakeAtRow( BeatToNoteRow( fBeat ) ); }
/**
* @brief Set the beat to indicate when the FakeSegment ends.
* @param iRow The row to start on.
* @param fNew The destination beat.
*/
void SetFakeAtRow( int iRow, float fNew );
/**
* @brief Set the beat to indicate when the FakeSegment ends.
* @param fBeat The beat to start on.
* @param fNew The destination beat.
*/
void SetFakeAtBeat( float fBeat, float fNew ) { SetFakeAtRow( BeatToNoteRow( fBeat ), fNew ); }
/**
* @brief Retrieve the FakeSegment at the specified row.
* @param iRow the row to focus on.
* @return the FakeSegment in question.
*/
FakeSegment& GetFakeSegmentAtRow( int iRow );
/**
* @brief Retrieve the FakeSegment at the specified beat.
* @param fBeat the beat to focus on.
* @return the FakeSegment in question.
*/
FakeSegment& GetFakeSegmentAtBeat( float fBeat ) { return GetFakeSegmentAtRow( BeatToNoteRow( fBeat ) ); }
/**
* @brief Retrieve the index of the FakeSegment at the specified row.
* @param iRow the row to focus on.
* @return the index in question.
*/
int GetFakeSegmentIndexAtRow( int iRow ) const;
/**
* @brief Retrieve the index of the FakeSegment at the specified beat.
* @param fBeat the beat to focus on.
* @return the index in question.
*/
int GetFakeSegmentIndexAtBeat( float fBeat ) const { return GetFakeSegmentIndexAtRow( BeatToNoteRow( fBeat ) ); }
/**
* @brief Checks if the row is inside a fake.
* @param iRow the row to focus on.
* @return true if the row is inside a fake, false otherwise.
*/
bool IsFakeAtRow( int iRow ) const;
/**
* @brief Checks if the beat is inside a fake.
* @param fBeat the beat to focus on.
* @return true if the row is inside a fake, false otherwise.
*/
bool IsFakeAtBeat( float fBeat ) const { return IsFakeAtRow( BeatToNoteRow( fBeat ) ); }
/**
* @brief Add the FakeSegment to the TimingData.
* @param seg the new FakeSegment.
*/
void AddFakeSegment( const FakeSegment &seg );
void MultiplyBPMInBeatRange( int iStartIndex, int iEndIndex, float fFactor ); void MultiplyBPMInBeatRange( int iStartIndex, int iEndIndex, float fFactor );
void NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const; void NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const;
@@ -1276,6 +1704,14 @@ public:
* @return true if there is at least one warp, false otherwise. * @return true if there is at least one warp, false otherwise.
*/ */
bool HasWarps() const; bool HasWarps() const;
/**
* @brief View the TimingData to see if there is at least one fake segment involved.
* @return true if there is at least one fake segment, false otherwise. */
bool HasFakes() const;
/**
* @brief View the TimingData to see if a song changes its speed scrolling at any point.
* @return true if there is at least one change, false otherwise. */
bool HasSpeedChanges() const;
/** /**
* @brief Compare two sets of timing data to see if they are equal. * @brief Compare two sets of timing data to see if they are equal.
* @param other the other TimingData. * @param other the other TimingData.
@@ -1301,8 +1737,15 @@ public:
COMPARE( m_ComboSegments.size() ); COMPARE( m_ComboSegments.size() );
for( unsigned i=0; i<m_ComboSegments.size(); i++ ) for( unsigned i=0; i<m_ComboSegments.size(); i++ )
COMPARE( m_ComboSegments[i] ); COMPARE( m_ComboSegments[i] );
COMPARE( m_LabelSegments.size() );
for( unsigned i=0; i<m_LabelSegments.size(); i++ ) for( unsigned i=0; i<m_LabelSegments.size(); i++ )
COMPARE( m_LabelSegments[i] ); COMPARE( m_LabelSegments[i] );
COMPARE( m_SpeedSegments.size() );
for( unsigned i=0; i<m_SpeedSegments.size(); i++ )
COMPARE( m_SpeedSegments[i] );
COMPARE( m_FakeSegments.size() );
for( unsigned i=0; i<m_FakeSegments.size(); i++ )
COMPARE( m_FakeSegments[i] );
COMPARE( m_fBeat0OffsetInSeconds ); COMPARE( m_fBeat0OffsetInSeconds );
return true; return true;
} }
@@ -1317,6 +1760,11 @@ public:
void InsertRows( int iStartRow, int iRowsToAdd ); void InsertRows( int iStartRow, int iRowsToAdd );
void DeleteRows( int iStartRow, int iRowsToDelete ); void DeleteRows( int iStartRow, int iRowsToDelete );
/**
* @brief Tidy up the timing data, e.g. provide default BPMs, labels, tickcounts.
*/
void TidyUpData();
// Lua // Lua
void PushSelf( lua_State *L ); void PushSelf( lua_State *L );
/** /**
@@ -1354,6 +1802,10 @@ public:
* @brief The collection of LabelSegments. * @brief The collection of LabelSegments.
*/ */
vector<LabelSegment> m_LabelSegments; vector<LabelSegment> m_LabelSegments;
/** @brief The collection of SpeedSegments. */
vector<SpeedSegment> m_SpeedSegments;
/** @brief The collection of FakeSegments. */
vector<FakeSegment> m_FakeSegments;
/** /**
* @brief The initial offset of a song. * @brief The initial offset of a song.
*/ */