experimental time signature support. Background changes and beat bars are currently the only things that are aware of time signature. Note coloring isn't aware of time signature yet.

This commit is contained in:
Chris Danford
2007-04-30 05:12:48 +00:00
parent e5dd10fbef
commit d2f6f8e03f
18 changed files with 314 additions and 69 deletions
+2 -2
View File
@@ -30,8 +30,8 @@ void Attack::GetRealtimeAttackBeats( const Song *pSong, const PlayerState* pPlay
ASSERT( pPlayerState ); ASSERT( pPlayerState );
ASSERT( pSong ); ASSERT( pSong );
/* If reasonable, push the attack forward so 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+BEATS_PER_MEASURE*2, pPlayerState->m_fLastDrawnBeat ); fStartBeat = min( GAMESTATE->m_fSongBeat+8, pPlayerState->m_fLastDrawnBeat );
fStartBeat = truncf(fStartBeat)+1; fStartBeat = truncf(fStartBeat)+1;
const float fStartSecond = pSong->GetElapsedTimeFromBeat( fStartBeat ); const float fStartSecond = pSong->GetElapsedTimeFromBeat( fStartBeat );
+24 -6
View File
@@ -433,10 +433,19 @@ BackgroundDef BackgroundImpl::Layer::CreateRandomBGA( const Song *pSong, const R
void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change ) void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change )
{ {
int iStartRow = BeatToNoteRow(fFirstBeat);
int iEndRow = BeatToNoteRow(fEndBeat);
const TimingData &timing = m_pSong->m_Timing; const TimingData &timing = m_pSong->m_Timing;
// change BG every 4 bars // change BG every time signature change or 4 measures
for( float f=fFirstBeat; f<fEndBeat; f+=BEATS_PER_MEASURE*4 ) FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
{
vector<TimeSignatureSegment>::const_iterator next = iter;
next++;
int iSegmentEndRow = (next == timing.m_vTimeSignatureSegments.end()) ? iEndRow : next->m_iStartRow;
for( int i=max(iter->m_iStartRow,iStartRow); i<min(iEndRow,iSegmentEndRow); i+=iter->GetNoteRowsPerMeasure() )
{ {
// Don't fade. It causes frame rate dip, especially on slower machines. // Don't fade. It causes frame rate dip, especially on slower machines.
BackgroundDef bd = m_Layer[0].CreateRandomBGA( m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this ); BackgroundDef bd = m_Layer[0].CreateRandomBGA( m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this );
@@ -444,19 +453,28 @@ void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const Bac
{ {
BackgroundChange c = change; BackgroundChange c = change;
c.m_def = bd; c.m_def = bd;
c.m_fStartBeat = f; c.m_fStartBeat = NoteRowToBeat(i);
m_Layer[0].m_aBGChanges.push_back( c ); m_Layer[0].m_aBGChanges.push_back( c );
} }
} }
}
// change BG every BPM change that is at the beginning of a measure // change BG every BPM change that is at the beginning of a measure
int iStartRow = BeatToNoteRow(fFirstBeat);
int iEndRow = BeatToNoteRow(fEndBeat);
for( unsigned i=0; i<timing.m_BPMSegments.size(); i++ ) for( unsigned i=0; i<timing.m_BPMSegments.size(); i++ )
{ {
const BPMSegment& bpmseg = timing.m_BPMSegments[i]; const BPMSegment& bpmseg = timing.m_BPMSegments[i];
if( bpmseg.m_iStartRow % BeatToNoteRow((float) BEATS_PER_MEASURE) != 0 ) bool bAtBeginningOfMeasure = false;
FOREACH_CONST( TimeSignatureSegment, timing.m_vTimeSignatureSegments, iter )
{
if( (bpmseg.m_iStartRow - iter->m_iStartRow) % iter->GetNoteRowsPerMeasure() == 0 )
{
bAtBeginningOfMeasure = true;
break;
}
}
if( !bAtBeginningOfMeasure )
continue; // skip continue; // skip
// start so that we don't create a BGChange right on top of fEndBeat // start so that we don't create a BGChange right on top of fEndBeat
+16 -8
View File
@@ -27,7 +27,8 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
* 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_fMusicSeconds;
fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f; fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f;
const float fSongBeat = GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds ); const TimingData &timing = GAMESTATE->m_pCurSong->m_Timing;
const float fSongBeat = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );
const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) ); const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) );
static int iRowLastCrossed = -1; static int iRowLastCrossed = -1;
@@ -45,7 +46,7 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
if( iClapRow != -1 ) if( iClapRow != -1 )
{ {
const float fTickBeat = NoteRowToBeat( iClapRow ); const float fTickBeat = NoteRowToBeat( iClapRow );
const float fTickSecond = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeatNoOffset( fTickBeat ); const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds; float fSecondsUntil = fTickSecond - GAMESTATE->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 */
@@ -59,22 +60,29 @@ void GameplayAssist::PlayTicks( const NoteData &nd )
{ {
//iRowLastCrossed+1, iSongRow+1 //iRowLastCrossed+1, iSongRow+1
int iLastCrossedBeat = iRowLastCrossed / ROWS_PER_BEAT; int iLastCrossedMeasureIndex;
int iCurrentBeat = iSongRow / ROWS_PER_BEAT; int iLastCrossedBeatIndex;
int iLastCrossedRowsRemainder;
timing.NoteRowToMeasureAndBeat( iRowLastCrossed, iLastCrossedMeasureIndex, iLastCrossedBeatIndex, iLastCrossedRowsRemainder );
int iCurrentMeasureIndex;
int iCurrentBeatIndex;
int iCurrentRowsRemainder;
timing.NoteRowToMeasureAndBeat( iSongRow, iCurrentMeasureIndex, iCurrentBeatIndex, iCurrentRowsRemainder );
int iMetronomeRow = -1; int iMetronomeRow = -1;
bool bIsMeasure = false; bool bIsMeasure = false;
if( iLastCrossedBeat != iCurrentBeat ) if( iLastCrossedMeasureIndex != iCurrentMeasureIndex || iLastCrossedBeatIndex != iCurrentBeatIndex )
{ {
iMetronomeRow = iCurrentBeat * ROWS_PER_BEAT; iMetronomeRow = iSongRow - iCurrentRowsRemainder;
bIsMeasure = (iCurrentBeat % BEATS_PER_MEASURE) == 0; bIsMeasure = iCurrentBeatIndex == 0 && iCurrentRowsRemainder == 0;
} }
if( iMetronomeRow != -1 ) if( iMetronomeRow != -1 )
{ {
const float fTickBeat = NoteRowToBeat( iMetronomeRow ); const float fTickBeat = NoteRowToBeat( iMetronomeRow );
const float fTickSecond = GAMESTATE->m_pCurSong->m_Timing.GetElapsedTimeFromBeatNoOffset( fTickBeat ); const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
float fSecondsUntil = fTickSecond - GAMESTATE->m_fMusicSeconds; float fSecondsUntil = fTickSecond - GAMESTATE->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 */
+12 -3
View File
@@ -10,11 +10,20 @@
#include "Foreach.h" #include "Foreach.h"
#include <utility> #include <utility>
NoteType NoteDataUtil::GetSmallestNoteTypeForMeasure( const NoteData &n, int iMeasureIndex ) // TODO: Remove these constants that aren't time signature-aware
static const int BEATS_PER_MEASURE = 4;
static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE;
NoteType NoteDataUtil::GetSmallestNoteTypeForMeasure( const NoteData &nd, int iMeasureIndex )
{ {
const int iMeasureStartIndex = iMeasureIndex * ROWS_PER_MEASURE; const int iMeasureStartIndex = iMeasureIndex * ROWS_PER_MEASURE;
const int iMeasureLastIndex = (iMeasureIndex+1) * ROWS_PER_MEASURE - 1; const int iMeasureEndIndex = (iMeasureIndex+1) * ROWS_PER_MEASURE;
return NoteDataUtil::GetSmallestNoteTypeInRange( nd, iMeasureStartIndex, iMeasureEndIndex );
}
NoteType NoteDataUtil::GetSmallestNoteTypeInRange( const NoteData &n, int iStartIndex, int iEndIndex )
{
// probe to find the smallest note type // probe to find the smallest note type
NoteType nt; NoteType nt;
for( nt=(NoteType)0; nt<NUM_NoteType; nt=NoteType(nt+1) ) // for each NoteType, largest to largest for( nt=(NoteType)0; nt<NUM_NoteType; nt=NoteType(nt+1) ) // for each NoteType, largest to largest
@@ -24,7 +33,7 @@ NoteType NoteDataUtil::GetSmallestNoteTypeForMeasure( const NoteData &n, int iMe
bool bFoundSmallerNote = false; bool bFoundSmallerNote = false;
// for each index in this measure // for each index in this measure
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( n, i, iMeasureStartIndex, iMeasureLastIndex ) FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( n, i, iStartIndex, iEndIndex )
{ {
if( i % iRowSpacing == 0 ) if( i % iRowSpacing == 0 )
continue; // skip continue; // skip
+2 -1
View File
@@ -18,7 +18,8 @@ struct AttackArray;
* the future. */ * the future. */
namespace NoteDataUtil namespace NoteDataUtil
{ {
NoteType GetSmallestNoteTypeForMeasure( const NoteData &n, int iMeasureIndex ); NoteType GetSmallestNoteTypeForMeasure( const NoteData &nd, int iMeasureIndex );
NoteType GetSmallestNoteTypeInRange( const NoteData &nd, int iStartIndex, int iEndIndex );
void LoadFromSMNoteDataString( NoteData &out, const RString &sSMNoteData, bool bComposite ); void LoadFromSMNoteDataString( NoteData &out, const RString &sSMNoteData, bool bComposite );
void GetSMNoteDataString( const NoteData &in, RString &notes_out ); void GetSMNoteDataString( const NoteData &in, RString &notes_out );
void SplitCompositeNoteData( const NoteData &in, vector<NoteData> &out ); void SplitCompositeNoteData( const NoteData &in, vector<NoteData> &out );
+96 -36
View File
@@ -243,13 +243,9 @@ float NoteField::GetWidth() const
return (fMaxX - fMinX + ARROW_SIZE) * fYZoom; return (fMaxX - fMinX + ARROW_SIZE) * fYZoom;
} }
void NoteField::DrawBeatBar( const float fBeat ) void NoteField::DrawBeatBar( const float fBeat, BeatBarType type, int iMeasureIndex )
{ {
bool bIsMeasure = fmodf( fBeat, (float)BEATS_PER_MEASURE ) == 0; bool bIsMeasure = type == measure;
int iMeasureIndex = (int)fBeat / BEATS_PER_MEASURE;
int iMeasureNoDisplay = iMeasureIndex+1;
NoteType nt = BeatToNoteType( fBeat );
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat );
const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels );
@@ -267,12 +263,13 @@ void NoteField::DrawBeatBar( const float fBeat )
float fScrollSpeed = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed; float fScrollSpeed = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed;
if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing > 0 ) if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing > 0 )
fScrollSpeed = 4; fScrollSpeed = 4;
switch( nt ) switch( type )
{ {
default: ASSERT(0); DEFAULT_FAIL( type );
case NOTE_TYPE_4TH: fAlpha = BAR_4TH_ALPHA; iState = 1; break; case measure:
case NOTE_TYPE_8TH: fAlpha = SCALE(fScrollSpeed,1.0f,2.0f,0.0f,BAR_8TH_ALPHA); iState = 2; break; case beat: fAlpha = BAR_4TH_ALPHA; iState = 1; break;
case NOTE_TYPE_16TH: fAlpha = SCALE(fScrollSpeed,2.0f,4.0f,0.0f,BAR_16TH_ALPHA); iState = 3; break; case half_beat: fAlpha = SCALE(fScrollSpeed,1.0f,2.0f,0.0f,BAR_8TH_ALPHA); iState = 2; break;
case quarter_beat: fAlpha = SCALE(fScrollSpeed,2.0f,4.0f,0.0f,BAR_16TH_ALPHA); iState = 3; break;
} }
CLAMP( fAlpha, 0, 1 ); CLAMP( fAlpha, 0, 1 );
} }
@@ -291,6 +288,8 @@ void NoteField::DrawBeatBar( const float fBeat )
if( GAMESTATE->IsEditing() && bIsMeasure ) if( GAMESTATE->IsEditing() && bIsMeasure )
{ {
int iMeasureNoDisplay = iMeasureIndex+1;
m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) ); m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) ); m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) );
m_textMeasureNumber.SetHorizAlign( align_right ); m_textMeasureNumber.SetHorizAlign( align_right );
@@ -392,6 +391,21 @@ void NoteField::DrawFreezeText( const float fBeat, const float fSecs )
m_textMeasureNumber.Draw(); m_textMeasureNumber.Draw();
} }
void NoteField::DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator )
{
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 );
m_textMeasureNumber.SetZoom( fZoom );
m_textMeasureNumber.SetHorizAlign( align_right );
m_textMeasureNumber.SetDiffuse( RageColor(0.8f,0.8f,0,1) );
m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) );
m_textMeasureNumber.SetText( ssprintf("%d\n--\n%d", iNumerator, iDenominator) );
m_textMeasureNumber.SetXY( -GetWidth()/2.f - 30*fZoom, 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 );
@@ -495,7 +509,7 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB
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
// [iFirstIndexToDraw,iLastIndexToDraw] aren't necessarily visible. // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
// Test to see if this beat is visible before drawing. // Test to see if this beat is visible before drawing.
float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat ); float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat );
if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen
@@ -545,11 +559,11 @@ void NoteField::DrawPrimitives()
m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw; m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw;
const int iFirstIndexToDraw = BeatToNoteRow(fFirstBeatToDraw); const int iFirstRowToDraw = BeatToNoteRow(fFirstBeatToDraw);
const int iLastIndexToDraw = BeatToNoteRow(fLastBeatToDraw); const int iLastRowToDraw = BeatToNoteRow(fLastBeatToDraw);
// LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat ); // LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat );
// LOG->Trace( "Drawing elements %d through %d", iFirstIndexToDraw, iLastIndexToDraw ); // LOG->Trace( "Drawing elements %d through %d", iFirstRowToDraw, iLastRowToDraw );
#define IS_ON_SCREEN( fBeat ) IsOnScreen( fBeat, 0, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) #define IS_ON_SCREEN( fBeat ) IsOnScreen( fBeat, 0, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels )
@@ -573,11 +587,42 @@ void NoteField::DrawPrimitives()
// //
if( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) if( GAMESTATE->IsEditing() || SHOW_BEAT_BARS )
{ {
float fStartDrawingMeasureBars = max( 0, Quantize(fFirstBeatToDraw-0.25f,0.25f) ); const vector<TimeSignatureSegment> &vTimeSignatureSegments = GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments;
for( float f=fStartDrawingMeasureBars; f<fLastBeatToDraw; f+=0.25f ) int iMeasureIndex = 0;
FOREACH_CONST( TimeSignatureSegment, vTimeSignatureSegments, iter )
{ {
if( IS_ON_SCREEN(f) ) vector<TimeSignatureSegment>::const_iterator next = iter;
DrawBeatBar( f ); next++;
int iSegmentEndRow = (next == vTimeSignatureSegments.end()) ? iLastRowToDraw : next->m_iStartRow;
// beat bars every 16th note
int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)iter->m_iDenominator) / 4 ) / 4;
// In 4/4, every 16th beat bar is a measure
int iMeasureBarFrequency = iter->m_iNumerator * 4;
int iBeatBarsDrawn = 0;
for( int i=iter->m_iStartRow; i < iSegmentEndRow; i += iDrawBeatBarsEveryRows )
{
bool bMeasureBar = iBeatBarsDrawn % iMeasureBarFrequency == 0;
BeatBarType type = quarter_beat;
if( bMeasureBar )
type = measure;
else if( iBeatBarsDrawn % 4 == 0 )
type = beat;
else if( iBeatBarsDrawn % 2 == 0 )
type = half_beat;
float fBeat = NoteRowToBeat(i);
if( IS_ON_SCREEN(fBeat) )
{
DrawBeatBar( fBeat, type, iMeasureIndex );
}
iBeatBarsDrawn++;
if( bMeasureBar )
iMeasureIndex++;
}
} }
} }
@@ -591,8 +636,8 @@ void NoteField::DrawPrimitives()
const vector<BPMSegment> &aBPMSegments = GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments; const vector<BPMSegment> &aBPMSegments = GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments;
for( unsigned i=0; i<aBPMSegments.size(); i++ ) for( unsigned i=0; i<aBPMSegments.size(); i++ )
{ {
if( aBPMSegments[i].m_iStartRow >= iFirstIndexToDraw && if( aBPMSegments[i].m_iStartRow >= iFirstRowToDraw &&
aBPMSegments[i].m_iStartRow <= iLastIndexToDraw) aBPMSegments[i].m_iStartRow <= iLastRowToDraw)
{ {
float fBeat = NoteRowToBeat(aBPMSegments[i].m_iStartRow); float fBeat = NoteRowToBeat(aBPMSegments[i].m_iStartRow);
if( IS_ON_SCREEN(fBeat) ) if( IS_ON_SCREEN(fBeat) )
@@ -606,8 +651,8 @@ void NoteField::DrawPrimitives()
const vector<StopSegment> &aStopSegments = GAMESTATE->m_pCurSong->m_Timing.m_StopSegments; const vector<StopSegment> &aStopSegments = GAMESTATE->m_pCurSong->m_Timing.m_StopSegments;
for( unsigned i=0; i<aStopSegments.size(); i++ ) for( unsigned i=0; i<aStopSegments.size(); i++ )
{ {
if( aStopSegments[i].m_iStartRow >= iFirstIndexToDraw && if( aStopSegments[i].m_iStartRow >= iFirstRowToDraw &&
aStopSegments[i].m_iStartRow <= iLastIndexToDraw) aStopSegments[i].m_iStartRow <= iLastRowToDraw)
{ {
float fBeat = NoteRowToBeat(aStopSegments[i].m_iStartRow); float fBeat = NoteRowToBeat(aStopSegments[i].m_iStartRow);
if( IS_ON_SCREEN(fBeat) ) if( IS_ON_SCREEN(fBeat) )
@@ -615,6 +660,21 @@ void NoteField::DrawPrimitives()
} }
} }
//
// Time Signature text
//
const vector<TimeSignatureSegment> &vTimeSignatureSegments = GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments;
for( unsigned i=0; i<vTimeSignatureSegments.size(); i++ )
{
if( vTimeSignatureSegments[i].m_iStartRow >= iFirstRowToDraw &&
vTimeSignatureSegments[i].m_iStartRow <= iLastRowToDraw)
{
float fBeat = NoteRowToBeat(vTimeSignatureSegments[i].m_iStartRow);
if( IS_ON_SCREEN(fBeat) )
DrawTimeSignatureText( fBeat, vTimeSignatureSegments[i].m_iNumerator, vTimeSignatureSegments[i].m_iDenominator );
}
}
// //
// Course mods text // Course mods text
// //
@@ -629,8 +689,8 @@ void NoteField::DrawPrimitives()
float fSecond = a->fStartSecond; float fSecond = a->fStartSecond;
float fBeat = GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( fSecond ); float fBeat = GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( fSecond );
if( BeatToNoteRow(fBeat) >= iFirstIndexToDraw && if( BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
BeatToNoteRow(fBeat) <= iLastIndexToDraw) BeatToNoteRow(fBeat) <= iLastRowToDraw)
{ {
if( IS_ON_SCREEN(fBeat) ) if( IS_ON_SCREEN(fBeat) )
DrawAttackText( fBeat, *a ); DrawAttackText( fBeat, *a );
@@ -713,20 +773,20 @@ void NoteField::DrawPrimitives()
{ {
int iBegin = m_iBeginMarker; int iBegin = m_iBeginMarker;
int iEnd = m_iEndMarker; int iEnd = m_iEndMarker;
CLAMP( iBegin, iFirstIndexToDraw, iLastIndexToDraw ); CLAMP( iBegin, iFirstRowToDraw, iLastRowToDraw );
CLAMP( iEnd, iFirstIndexToDraw, iLastIndexToDraw ); CLAMP( iEnd, iFirstRowToDraw, iLastRowToDraw );
DrawAreaHighlight( iBegin, iEnd ); DrawAreaHighlight( iBegin, iEnd );
} }
else if( m_iBeginMarker != -1 ) else if( m_iBeginMarker != -1 )
{ {
if( m_iBeginMarker >= iFirstIndexToDraw && if( m_iBeginMarker >= iFirstRowToDraw &&
m_iBeginMarker <= iLastIndexToDraw ) m_iBeginMarker <= iLastRowToDraw )
DrawMarkerBar( m_iBeginMarker ); DrawMarkerBar( m_iBeginMarker );
} }
else if( m_iEndMarker != -1 ) else if( m_iEndMarker != -1 )
{ {
if( m_iEndMarker >= iFirstIndexToDraw && if( m_iEndMarker >= iFirstRowToDraw &&
m_iEndMarker <= iLastIndexToDraw ) m_iEndMarker <= iLastRowToDraw )
DrawMarkerBar( m_iEndMarker ); DrawMarkerBar( m_iEndMarker );
} }
} }
@@ -752,7 +812,7 @@ void NoteField::DrawPrimitives()
// //
{ {
NoteData::TrackMap::const_iterator begin, end; NoteData::TrackMap::const_iterator begin, end;
m_pNoteData->GetTapNoteRangeInclusive( c, iFirstIndexToDraw, iLastIndexToDraw+1, begin, end ); m_pNoteData->GetTapNoteRangeInclusive( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end );
for( ; begin != end; ++begin ) for( ; begin != end; ++begin )
{ {
@@ -768,7 +828,7 @@ void NoteField::DrawPrimitives()
int iEndRow = iStartRow + tn.iDuration; int iEndRow = iStartRow + tn.iDuration;
// TRICKY: If boomerang is on, then all notes in the range // TRICKY: If boomerang is on, then all notes in the range
// [iFirstIndexToDraw,iLastIndexToDraw] aren't necessarily visible. // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
// Test every note to make sure it's on screen before drawing // Test every note to make sure it's on screen before drawing
float fThrowAway; float fThrowAway;
bool bStartIsPastPeak = false; bool bStartIsPastPeak = false;
@@ -815,7 +875,7 @@ void NoteField::DrawPrimitives()
// draw notes from furthest to closest // draw notes from furthest to closest
NoteData::TrackMap::const_iterator begin, end; NoteData::TrackMap::const_iterator begin, end;
m_pNoteData->GetTapNoteRange( c, iFirstIndexToDraw, iLastIndexToDraw+1, begin, end ); m_pNoteData->GetTapNoteRange( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end );
for( ; begin != end; ++begin ) for( ; begin != end; ++begin )
{ {
int i = begin->first; int i = begin->first;
@@ -833,12 +893,12 @@ void NoteField::DrawPrimitives()
// TRICKY: If boomerang is on, then all notes in the range // TRICKY: If boomerang is on, then all notes in the range
// [iFirstIndexToDraw,iLastIndexToDraw] aren't necessarily visible. // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
// Test every note to make sure it's on screen before drawing // Test every note to make sure it's on screen before drawing
if( !IsOnScreen( NoteRowToBeat(i), c, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) ) if( !IsOnScreen( NoteRowToBeat(i), c, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) )
continue; // skip continue; // skip
ASSERT_M( NoteRowToBeat(i) > -2000, ssprintf("%i %i %i, %f %f", i, iLastIndexToDraw, iFirstIndexToDraw, GAMESTATE->m_fSongBeat, GAMESTATE->m_fMusicSeconds) ); ASSERT_M( NoteRowToBeat(i) > -2000, ssprintf("%i %i %i, %f %f", i, iLastRowToDraw, iFirstRowToDraw, GAMESTATE->m_fSongBeat, GAMESTATE->m_fMusicSeconds) );
// See if there is a hold step that begins on this index. Only do this // See if there is a hold step that begins on this index. Only do this
// if the note skin cares. // if the note skin cares.
+4 -1
View File
@@ -52,11 +52,14 @@ protected:
bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const; bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const;
void DrawBoard( int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ); void DrawBoard( int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels );
void DrawBeatBar( const float fBeat );
enum BeatBarType { measure, beat, half_beat, quarter_beat };
void DrawBeatBar( const float fBeat, BeatBarType type, int iMeasureIndex );
void DrawMarkerBar( int fBeat ); void DrawMarkerBar( int fBeat );
void DrawAreaHighlight( int iStartBeat, int iEndBeat ); void DrawAreaHighlight( int iStartBeat, int iEndBeat );
void DrawBPMText( const float fBeat, const float fBPM ); void DrawBPMText( const float fBeat, const float fBPM );
void DrawFreezeText( const float fBeat, const float fBPM ); void DrawFreezeText( const float fBeat, const float fBPM );
void DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator );
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;
+4
View File
@@ -49,6 +49,10 @@ float NoteTypeToBeat( NoteType nt )
} }
} }
// FIXME: Remove hard-coded beat values and instead look at the time signature in the song.
static const int BEATS_PER_MEASURE = 4;
static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE;
NoteType GetNoteType( int row ) NoteType GetNoteType( int row )
{ {
if( row % (ROWS_PER_MEASURE/4) == 0) return NOTE_TYPE_4TH; if( row % (ROWS_PER_MEASURE/4) == 0) return NOTE_TYPE_4TH;
+1 -2
View File
@@ -164,8 +164,7 @@ const int ROWS_PER_BEAT = 48;
/* In the editor, enforce a reasonable limit on the number of notes. */ /* In the editor, enforce a reasonable limit on the number of notes. */
const int MAX_NOTES_PER_MEASURE = 50; const int MAX_NOTES_PER_MEASURE = 50;
const int BEATS_PER_MEASURE = 4;
const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE;
const int MAX_NOTE_ROW = (1<<30); const int MAX_NOTE_ROW = (1<<30);
enum NoteType enum NoteType
+3
View File
@@ -422,6 +422,9 @@ static void ReadTimeSigs( const NameToData_t &mapNameToData, MeasureToTimeSig_t
} }
} }
static const int BEATS_PER_MEASURE = 4;
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 LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out,
const MeasureToTimeSig_t &sigAdjustments, const map<RString,int> &idToKeySoundIndex ) const MeasureToTimeSig_t &sigAdjustments, const map<RString,int> &idToKeySoundIndex )
{ {
+2
View File
@@ -113,6 +113,8 @@ static bool Is192( const RString &sStepData, size_t pos )
return false; return false;
} }
const int BEATS_PER_MEASURE = 4;
static bool LoadFromDWITokens( static bool LoadFromDWITokens(
RString sMode, RString sMode,
RString sDescription, RString sDescription,
+46
View File
@@ -151,6 +151,52 @@ void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid BPM change at beat %f, BPM %f.", fBeat, fNewBPM ); LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid BPM change at beat %f, BPM %f.", fBeat, fNewBPM );
} }
} }
else if( sValueName=="TIMESIGNATURES" )
{
vector<RString> vs1;
split( sParams[1], ",", vs1 );
FOREACH_CONST( RString, vs1, s1 )
{
vector<RString> vs2;
split( *s1, "=", vs2 );
if( vs2.size() < 3 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with %i values.", (int)vs2.size() );
continue;
}
const float fBeat = StringToFloat( vs2[0] );
TimeSignatureSegment seg;
seg.m_iStartRow = BeatToNoteRow(fBeat);
seg.m_iNumerator = atoi( vs2[1] );
seg.m_iDenominator = atoi( vs2[2] );
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;
}
if( seg.m_iDenominator < 1 )
{
LOG->UserLog( "Song file", "(UNKNOWN)", "has an invalid time signature change with beat %f, iDenominator %i.", fBeat, seg.m_iDenominator );
continue;
}
out.AddTimeSignatureSegment( seg );
}
}
} }
} }
+2
View File
@@ -191,6 +191,8 @@ RString OptimizeDWIString( RString holds, RString taps )
return ssprintf( "<%s>", ret.c_str() ); return ssprintf( "<%s>", ret.c_str() );
} }
static const int BEATS_PER_MEASURE = 4;
static void WriteDWINotesField( RageFile &f, const Steps &out, int start ) static void WriteDWINotesField( RageFile &f, const Steps &out, int start )
{ {
NoteData notedata; NoteData notedata;
+12
View File
@@ -106,6 +106,18 @@ static void WriteGlobalTags( RageFile &f, const Song &out )
} }
f.PutLine( ";" ); 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( ";" );
FOREACH_BackgroundLayer( b ) FOREACH_BackgroundLayer( b )
{ {
if( b==0 ) if( b==0 )
+4
View File
@@ -58,6 +58,10 @@ const float RECORD_HOLD_SECONDS = 0.3f;
#define PLAY_RECORD_HELP_TEXT THEME->GetString(m_sName,"PlayRecordHelpText") #define PLAY_RECORD_HELP_TEXT THEME->GetString(m_sName,"PlayRecordHelpText")
#define EDIT_HELP_TEXT THEME->GetString(m_sName,"EditHelpText") #define EDIT_HELP_TEXT THEME->GetString(m_sName,"EditHelpText")
// FIXME: Remove hard-coded beat values and instead look at the time signature in the song.
static const int BEATS_PER_MEASURE = 4;
static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE;
AutoScreenMessage( SM_UpdateTextInfo ) AutoScreenMessage( SM_UpdateTextInfo )
AutoScreenMessage( SM_BackFromMainMenu ) AutoScreenMessage( SM_BackFromMainMenu )
AutoScreenMessage( SM_BackFromAreaMenu ) AutoScreenMessage( SM_BackFromAreaMenu )
+11 -1
View File
@@ -33,7 +33,7 @@
#include <set> #include <set>
#include <float.h> #include <float.h>
const int FILE_CACHE_VERSION = 154; // increment this to invalidate cache const int FILE_CACHE_VERSION = 156; // increment this to invalidate cache
const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f; const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f;
@@ -689,6 +689,16 @@ void Song::TidyUpData()
} }
} }
} }
/* If no time signature specified, assume 4/4 time for the whole song. */
if( m_Timing.m_vTimeSignatureSegments.empty() )
{
TimeSignatureSegment seg;
seg.m_iStartRow = 0;
seg.m_iNumerator = 4;
seg.m_iDenominator = 4;
m_Timing.m_vTimeSignatureSegments.push_back( seg );
}
} }
void Song::TranslateTitles() void Song::TranslateTitles()
+41
View File
@@ -7,6 +7,7 @@
#include "Foreach.h" #include "Foreach.h"
#include <float.h> #include <float.h>
TimingData::TimingData() TimingData::TimingData()
{ {
m_fBeat0OffsetInSeconds = 0; m_fBeat0OffsetInSeconds = 0;
@@ -35,6 +36,11 @@ void TimingData::AddStopSegment( const StopSegment &seg )
m_StopSegments.insert( upper_bound(m_StopSegments.begin(), m_StopSegments.end(), seg), seg ); m_StopSegments.insert( upper_bound(m_StopSegments.begin(), m_StopSegments.end(), seg), seg );
} }
void TimingData::AddTimeSignatureSegment( const TimeSignatureSegment &seg )
{
m_vTimeSignatureSegments.insert( upper_bound(m_vTimeSignatureSegments.begin(), m_vTimeSignatureSegments.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 )
{ {
@@ -392,6 +398,41 @@ bool TimingData::HasStops() const
return m_StopSegments.size()>0; return m_StopSegments.size()>0;
} }
void TimingData::NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const
{
iMeasureIndexOut = 0;
FOREACH_CONST( TimeSignatureSegment, m_vTimeSignatureSegments, iter )
{
vector<TimeSignatureSegment>::const_iterator next = iter;
next++;
int iSegmentEndRow = (next == m_vTimeSignatureSegments.end()) ? INT_MAX : next->m_iStartRow;
int iRowsPerMeasureThisSegment = iter->GetNoteRowsPerMeasure();
if( iNoteRow >= iter->m_iStartRow )
{
// iNoteRow lands in this segment
int iNumRowsThisSegment = iNoteRow - iter->m_iStartRow;
int iNumMeasuresThisSegment = (iNumRowsThisSegment) / iRowsPerMeasureThisSegment; // don't round up
iMeasureIndexOut += iNumMeasuresThisSegment;
iBeatIndexOut = iNumRowsThisSegment / iRowsPerMeasureThisSegment;
iRowsRemainder = iNumRowsThisSegment % iRowsPerMeasureThisSegment;
return;
}
else
{
// iNoteRow lands after this segment
int iNumRowsThisSegment = iSegmentEndRow - iter->m_iStartRow;
int iNumMeasuresThisSegment = (iNumRowsThisSegment + iRowsPerMeasureThisSegment - 1) / iRowsPerMeasureThisSegment; // round up
iMeasureIndexOut += iNumMeasuresThisSegment;
}
}
ASSERT(0);
return;
}
/* /*
* (c) 2001-2004 Chris Danford, Glenn Maynard * (c) 2001-2004 Chris Danford, Glenn Maynard
* All rights reserved. * All rights reserved.
+23
View File
@@ -44,6 +44,26 @@ struct StopSegment
bool operator<( const StopSegment &other ) const { return m_iStartRow < other.m_iStartRow; } bool operator<( const StopSegment &other ) const { return m_iStartRow < other.m_iStartRow; }
}; };
struct TimeSignatureSegment
{
TimeSignatureSegment() : m_iStartRow(-1), m_iNumerator(4), m_iDenominator(4) { }
int m_iStartRow;
int m_iNumerator;
int m_iDenominator;
int GetNoteRowsPerMeasure() const { return (BeatToNoteRow(1) * 4) / m_iDenominator; }
bool operator==( const TimeSignatureSegment &other ) const
{
COMPARE( m_iStartRow );
COMPARE( m_iNumerator );
COMPARE( m_iDenominator );
return true;
}
bool operator!=( const TimeSignatureSegment &other ) const { return !operator==(other); }
bool operator<( const TimeSignatureSegment &other ) const { return m_iStartRow < other.m_iStartRow; }
};
class TimingData class TimingData
{ {
public: public:
@@ -59,8 +79,10 @@ public:
void MultiplyBPMInBeatRange( int iStartIndex, int iEndIndex, float fFactor ); void MultiplyBPMInBeatRange( int iStartIndex, int iEndIndex, float fFactor );
void AddBPMSegment( const BPMSegment &seg ); void AddBPMSegment( const BPMSegment &seg );
void AddStopSegment( const StopSegment &seg ); void AddStopSegment( const StopSegment &seg );
void AddTimeSignatureSegment( const TimeSignatureSegment &seg );
int GetBPMSegmentIndexAtBeat( float fBeat ); int GetBPMSegmentIndexAtBeat( float fBeat );
BPMSegment& GetBPMSegmentAtBeat( float fBeat ); BPMSegment& GetBPMSegmentAtBeat( float fBeat );
void NoteRowToMeasureAndBeat( int iNoteRow, int &iMeasureIndexOut, int &iBeatIndexOut, int &iRowsRemainder ) const;
void GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut ) const; void GetBeatAndBPSFromElapsedTime( float fElapsedTime, float &fBeatOut, float &fBPSOut, bool &bFreezeOut ) const;
float GetBeatFromElapsedTime( float fElapsedTime ) const // shortcut for places that care only about the beat float GetBeatFromElapsedTime( float fElapsedTime ) const // shortcut for places that care only about the beat
@@ -105,6 +127,7 @@ public:
RString m_sFile; // informational only RString m_sFile; // informational only
vector<BPMSegment> m_BPMSegments; // this must be sorted before gameplay vector<BPMSegment> m_BPMSegments; // this must be sorted before gameplay
vector<StopSegment> m_StopSegments; // this must be sorted before gameplay vector<StopSegment> m_StopSegments; // this must be sorted before gameplay
vector<TimeSignatureSegment> m_vTimeSignatureSegments; // this must be sorted before gameplay
float m_fBeat0OffsetInSeconds; float m_fBeat0OffsetInSeconds;
}; };