From d2f6f8e03ff63d5b5467a76649649887f91773eb Mon Sep 17 00:00:00 2001 From: Chris Danford Date: Mon, 30 Apr 2007 05:12:48 +0000 Subject: [PATCH] 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. --- stepmania/src/Attack.cpp | 4 +- stepmania/src/Background.cpp | 42 +++++++--- stepmania/src/GameplayAssist.cpp | 24 ++++-- stepmania/src/NoteDataUtil.cpp | 15 +++- stepmania/src/NoteDataUtil.h | 3 +- stepmania/src/NoteField.cpp | 132 ++++++++++++++++++++++--------- stepmania/src/NoteField.h | 5 +- stepmania/src/NoteTypes.cpp | 4 + stepmania/src/NoteTypes.h | 3 +- stepmania/src/NotesLoaderBMS.cpp | 3 + stepmania/src/NotesLoaderDWI.cpp | 2 + stepmania/src/NotesLoaderSM.cpp | 46 +++++++++++ stepmania/src/NotesWriterDWI.cpp | 2 + stepmania/src/NotesWriterSM.cpp | 12 +++ stepmania/src/ScreenEdit.cpp | 4 + stepmania/src/Song.cpp | 12 ++- stepmania/src/TimingData.cpp | 41 ++++++++++ stepmania/src/TimingData.h | 29 ++++++- 18 files changed, 314 insertions(+), 69 deletions(-) diff --git a/stepmania/src/Attack.cpp b/stepmania/src/Attack.cpp index d3b91fb082..7f6bae9bb7 100644 --- a/stepmania/src/Attack.cpp +++ b/stepmania/src/Attack.cpp @@ -30,8 +30,8 @@ void Attack::GetRealtimeAttackBeats( const Song *pSong, const PlayerState* pPlay ASSERT( pPlayerState ); ASSERT( pSong ); - /* If reasonable, push the attack forward so notes on screen don't change suddenly. */ - fStartBeat = min( GAMESTATE->m_fSongBeat+BEATS_PER_MEASURE*2, pPlayerState->m_fLastDrawnBeat ); + /* 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 = truncf(fStartBeat)+1; const float fStartSecond = pSong->GetElapsedTimeFromBeat( fStartBeat ); diff --git a/stepmania/src/Background.cpp b/stepmania/src/Background.cpp index d850d2cdc1..b22a9331eb 100644 --- a/stepmania/src/Background.cpp +++ b/stepmania/src/Background.cpp @@ -433,30 +433,48 @@ BackgroundDef BackgroundImpl::Layer::CreateRandomBGA( const Song *pSong, const R 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; - // change BG every 4 bars - for( float f=fFirstBeat; f::const_iterator next = iter; + next++; + int iSegmentEndRow = (next == timing.m_vTimeSignatureSegments.end()) ? iEndRow : next->m_iStartRow; + + for( int i=max(iter->m_iStartRow,iStartRow); iGetNoteRowsPerMeasure() ) { - BackgroundChange c = change; - c.m_def = bd; - c.m_fStartBeat = f; - m_Layer[0].m_aBGChanges.push_back( c ); + // 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 ); + if( !bd.IsEmpty() ) + { + BackgroundChange c = change; + c.m_def = bd; + c.m_fStartBeat = NoteRowToBeat(i); + m_Layer[0].m_aBGChanges.push_back( c ); + } } } // 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; im_iStartRow) % iter->GetNoteRowsPerMeasure() == 0 ) + { + bAtBeginningOfMeasure = true; + break; + } + } + + if( !bAtBeginningOfMeasure ) continue; // skip // start so that we don't create a BGChange right on top of fEndBeat diff --git a/stepmania/src/GameplayAssist.cpp b/stepmania/src/GameplayAssist.cpp index 9da3e356b9..9686c4b3a3 100644 --- a/stepmania/src/GameplayAssist.cpp +++ b/stepmania/src/GameplayAssist.cpp @@ -27,7 +27,8 @@ void GameplayAssist::PlayTicks( const NoteData &nd ) * come out on time; the actual precise timing is handled by SetStartTime. */ float fPositionSeconds = GAMESTATE->m_fMusicSeconds; 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 ) ); static int iRowLastCrossed = -1; @@ -45,7 +46,7 @@ void GameplayAssist::PlayTicks( const NoteData &nd ) if( iClapRow != -1 ) { 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; 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 - int iLastCrossedBeat = iRowLastCrossed / ROWS_PER_BEAT; - int iCurrentBeat = iSongRow / ROWS_PER_BEAT; + int iLastCrossedMeasureIndex; + 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; bool bIsMeasure = false; - if( iLastCrossedBeat != iCurrentBeat ) + if( iLastCrossedMeasureIndex != iCurrentMeasureIndex || iLastCrossedBeatIndex != iCurrentBeatIndex ) { - iMetronomeRow = iCurrentBeat * ROWS_PER_BEAT; - bIsMeasure = (iCurrentBeat % BEATS_PER_MEASURE) == 0; + iMetronomeRow = iSongRow - iCurrentRowsRemainder; + bIsMeasure = iCurrentBeatIndex == 0 && iCurrentRowsRemainder == 0; } if( iMetronomeRow != -1 ) { 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; fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */ diff --git a/stepmania/src/NoteDataUtil.cpp b/stepmania/src/NoteDataUtil.cpp index 836cea7d90..fce12bb2d7 100644 --- a/stepmania/src/NoteDataUtil.cpp +++ b/stepmania/src/NoteDataUtil.cpp @@ -10,11 +10,20 @@ #include "Foreach.h" #include -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 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 NoteType nt; for( nt=(NoteType)0; nt &out ); diff --git a/stepmania/src/NoteField.cpp b/stepmania/src/NoteField.cpp index 0a1f2c04cd..b6149cebf8 100644 --- a/stepmania/src/NoteField.cpp +++ b/stepmania/src/NoteField.cpp @@ -243,13 +243,9 @@ float NoteField::GetWidth() const 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; - int iMeasureIndex = (int)fBeat / BEATS_PER_MEASURE; - int iMeasureNoDisplay = iMeasureIndex+1; - - NoteType nt = BeatToNoteType( fBeat ); + bool bIsMeasure = type == measure; const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); 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; if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing > 0 ) fScrollSpeed = 4; - switch( nt ) + switch( type ) { - default: ASSERT(0); - case NOTE_TYPE_4TH: fAlpha = BAR_4TH_ALPHA; iState = 1; break; - case NOTE_TYPE_8TH: fAlpha = SCALE(fScrollSpeed,1.0f,2.0f,0.0f,BAR_8TH_ALPHA); iState = 2; break; - case NOTE_TYPE_16TH: fAlpha = SCALE(fScrollSpeed,2.0f,4.0f,0.0f,BAR_16TH_ALPHA); iState = 3; break; + DEFAULT_FAIL( type ); + case measure: + case beat: fAlpha = BAR_4TH_ALPHA; iState = 1; 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 ); } @@ -291,6 +288,8 @@ void NoteField::DrawBeatBar( const float fBeat ) if( GAMESTATE->IsEditing() && bIsMeasure ) { + int iMeasureNoDisplay = iMeasureIndex+1; + m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) ); m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) ); m_textMeasureNumber.SetHorizAlign( align_right ); @@ -392,6 +391,21 @@ void NoteField::DrawFreezeText( const float fBeat, const float fSecs ) 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 ) { 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 { // 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. float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat ); if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen @@ -545,11 +559,11 @@ void NoteField::DrawPrimitives() m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw; - const int iFirstIndexToDraw = BeatToNoteRow(fFirstBeatToDraw); - const int iLastIndexToDraw = BeatToNoteRow(fLastBeatToDraw); + const int iFirstRowToDraw = BeatToNoteRow(fFirstBeatToDraw); + const int iLastRowToDraw = BeatToNoteRow(fLastBeatToDraw); // 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 ) @@ -573,11 +587,42 @@ void NoteField::DrawPrimitives() // if( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) { - float fStartDrawingMeasureBars = max( 0, Quantize(fFirstBeatToDraw-0.25f,0.25f) ); - for( float f=fStartDrawingMeasureBars; f &vTimeSignatureSegments = GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments; + int iMeasureIndex = 0; + FOREACH_CONST( TimeSignatureSegment, vTimeSignatureSegments, iter ) { - if( IS_ON_SCREEN(f) ) - DrawBeatBar( f ); + vector::const_iterator next = iter; + 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 &aBPMSegments = GAMESTATE->m_pCurSong->m_Timing.m_BPMSegments; for( unsigned i=0; i= iFirstIndexToDraw && - aBPMSegments[i].m_iStartRow <= iLastIndexToDraw) + if( aBPMSegments[i].m_iStartRow >= iFirstRowToDraw && + aBPMSegments[i].m_iStartRow <= iLastRowToDraw) { float fBeat = NoteRowToBeat(aBPMSegments[i].m_iStartRow); if( IS_ON_SCREEN(fBeat) ) @@ -606,8 +651,8 @@ void NoteField::DrawPrimitives() const vector &aStopSegments = GAMESTATE->m_pCurSong->m_Timing.m_StopSegments; for( unsigned i=0; i= iFirstIndexToDraw && - aStopSegments[i].m_iStartRow <= iLastIndexToDraw) + if( aStopSegments[i].m_iStartRow >= iFirstRowToDraw && + aStopSegments[i].m_iStartRow <= iLastRowToDraw) { float fBeat = NoteRowToBeat(aStopSegments[i].m_iStartRow); if( IS_ON_SCREEN(fBeat) ) @@ -615,6 +660,21 @@ void NoteField::DrawPrimitives() } } + // + // Time Signature text + // + const vector &vTimeSignatureSegments = GAMESTATE->m_pCurSong->m_Timing.m_vTimeSignatureSegments; + for( unsigned i=0; i= 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 // @@ -629,8 +689,8 @@ void NoteField::DrawPrimitives() float fSecond = a->fStartSecond; float fBeat = GAMESTATE->m_pCurSong->m_Timing.GetBeatFromElapsedTime( fSecond ); - if( BeatToNoteRow(fBeat) >= iFirstIndexToDraw && - BeatToNoteRow(fBeat) <= iLastIndexToDraw) + if( BeatToNoteRow(fBeat) >= iFirstRowToDraw && + BeatToNoteRow(fBeat) <= iLastRowToDraw) { if( IS_ON_SCREEN(fBeat) ) DrawAttackText( fBeat, *a ); @@ -713,20 +773,20 @@ void NoteField::DrawPrimitives() { int iBegin = m_iBeginMarker; int iEnd = m_iEndMarker; - CLAMP( iBegin, iFirstIndexToDraw, iLastIndexToDraw ); - CLAMP( iEnd, iFirstIndexToDraw, iLastIndexToDraw ); + CLAMP( iBegin, iFirstRowToDraw, iLastRowToDraw ); + CLAMP( iEnd, iFirstRowToDraw, iLastRowToDraw ); DrawAreaHighlight( iBegin, iEnd ); } else if( m_iBeginMarker != -1 ) { - if( m_iBeginMarker >= iFirstIndexToDraw && - m_iBeginMarker <= iLastIndexToDraw ) + if( m_iBeginMarker >= iFirstRowToDraw && + m_iBeginMarker <= iLastRowToDraw ) DrawMarkerBar( m_iBeginMarker ); } else if( m_iEndMarker != -1 ) { - if( m_iEndMarker >= iFirstIndexToDraw && - m_iEndMarker <= iLastIndexToDraw ) + if( m_iEndMarker >= iFirstRowToDraw && + m_iEndMarker <= iLastRowToDraw ) DrawMarkerBar( m_iEndMarker ); } } @@ -752,7 +812,7 @@ void NoteField::DrawPrimitives() // { 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 ) { @@ -768,7 +828,7 @@ void NoteField::DrawPrimitives() int iEndRow = iStartRow + tn.iDuration; // 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 float fThrowAway; bool bStartIsPastPeak = false; @@ -815,7 +875,7 @@ void NoteField::DrawPrimitives() // draw notes from furthest to closest 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 ) { int i = begin->first; @@ -833,12 +893,12 @@ void NoteField::DrawPrimitives() // 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 if( !IsOnScreen( NoteRowToBeat(i), c, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) ) 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 // if the note skin cares. diff --git a/stepmania/src/NoteField.h b/stepmania/src/NoteField.h index ada29b275d..c6a3622f65 100644 --- a/stepmania/src/NoteField.h +++ b/stepmania/src/NoteField.h @@ -52,11 +52,14 @@ protected: bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const; 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 DrawAreaHighlight( int iStartBeat, int iEndBeat ); void DrawBPMText( 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 DrawBGChangeText( const float fBeat, const RString sNewBGName ); float GetWidth() const; diff --git a/stepmania/src/NoteTypes.cpp b/stepmania/src/NoteTypes.cpp index 33c7c6bd07..5253ce8025 100644 --- a/stepmania/src/NoteTypes.cpp +++ b/stepmania/src/NoteTypes.cpp @@ -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 ) { if( row % (ROWS_PER_MEASURE/4) == 0) return NOTE_TYPE_4TH; diff --git a/stepmania/src/NoteTypes.h b/stepmania/src/NoteTypes.h index 9d3eddc5ac..d13b26716a 100644 --- a/stepmania/src/NoteTypes.h +++ b/stepmania/src/NoteTypes.h @@ -164,8 +164,7 @@ const int ROWS_PER_BEAT = 48; /* In the editor, enforce a reasonable limit on the number of notes. */ 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); enum NoteType diff --git a/stepmania/src/NotesLoaderBMS.cpp b/stepmania/src/NotesLoaderBMS.cpp index dd631975da..f54b211e7b 100644 --- a/stepmania/src/NotesLoaderBMS.cpp +++ b/stepmania/src/NotesLoaderBMS.cpp @@ -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, const MeasureToTimeSig_t &sigAdjustments, const map &idToKeySoundIndex ) { diff --git a/stepmania/src/NotesLoaderDWI.cpp b/stepmania/src/NotesLoaderDWI.cpp index 51163449e3..83a4a33637 100644 --- a/stepmania/src/NotesLoaderDWI.cpp +++ b/stepmania/src/NotesLoaderDWI.cpp @@ -113,6 +113,8 @@ static bool Is192( const RString &sStepData, size_t pos ) return false; } +const int BEATS_PER_MEASURE = 4; + static bool LoadFromDWITokens( RString sMode, RString sDescription, diff --git a/stepmania/src/NotesLoaderSM.cpp b/stepmania/src/NotesLoaderSM.cpp index 004b4d1382..384d4c5d73 100644 --- a/stepmania/src/NotesLoaderSM.cpp +++ b/stepmania/src/NotesLoaderSM.cpp @@ -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 ); } } + + else if( sValueName=="TIMESIGNATURES" ) + { + vector vs1; + split( sParams[1], ",", vs1 ); + + FOREACH_CONST( RString, vs1, s1 ) + { + vector 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 ); + } + } + } } diff --git a/stepmania/src/NotesWriterDWI.cpp b/stepmania/src/NotesWriterDWI.cpp index cea35df065..7d1eaf8fef 100644 --- a/stepmania/src/NotesWriterDWI.cpp +++ b/stepmania/src/NotesWriterDWI.cpp @@ -191,6 +191,8 @@ RString OptimizeDWIString( RString holds, RString taps ) return ssprintf( "<%s>", ret.c_str() ); } +static const int BEATS_PER_MEASURE = 4; + static void WriteDWINotesField( RageFile &f, const Steps &out, int start ) { NoteData notedata; diff --git a/stepmania/src/NotesWriterSM.cpp b/stepmania/src/NotesWriterSM.cpp index b1e50a372a..cd885e2e3e 100644 --- a/stepmania/src/NotesWriterSM.cpp +++ b/stepmania/src/NotesWriterSM.cpp @@ -106,6 +106,18 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) } 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::const_iterator iter2 = iter; + iter2++; + if( iter2 != out.m_Timing.m_vTimeSignatureSegments.end() ) + f.Write( "," ); + } + f.PutLine( ";" ); + FOREACH_BackgroundLayer( b ) { if( b==0 ) diff --git a/stepmania/src/ScreenEdit.cpp b/stepmania/src/ScreenEdit.cpp index 5242b99939..b22f2f0eef 100644 --- a/stepmania/src/ScreenEdit.cpp +++ b/stepmania/src/ScreenEdit.cpp @@ -58,6 +58,10 @@ const float RECORD_HOLD_SECONDS = 0.3f; #define PLAY_RECORD_HELP_TEXT THEME->GetString(m_sName,"PlayRecordHelpText") #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_BackFromMainMenu ) AutoScreenMessage( SM_BackFromAreaMenu ) diff --git a/stepmania/src/Song.cpp b/stepmania/src/Song.cpp index 3f53b93b21..7dbc2797a4 100644 --- a/stepmania/src/Song.cpp +++ b/stepmania/src/Song.cpp @@ -33,7 +33,7 @@ #include #include -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; @@ -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() diff --git a/stepmania/src/TimingData.cpp b/stepmania/src/TimingData.cpp index 1c2c3cf2f6..46720f78fd 100644 --- a/stepmania/src/TimingData.cpp +++ b/stepmania/src/TimingData.cpp @@ -7,6 +7,7 @@ #include "Foreach.h" #include + TimingData::TimingData() { 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 ); } +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. */ void TimingData::SetBPMAtRow( int iNoteRow, float fBPM ) { @@ -392,6 +398,41 @@ bool TimingData::HasStops() const 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::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 * All rights reserved. diff --git a/stepmania/src/TimingData.h b/stepmania/src/TimingData.h index fd2bd8d474..0c94400974 100644 --- a/stepmania/src/TimingData.h +++ b/stepmania/src/TimingData.h @@ -44,6 +44,26 @@ struct StopSegment 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 { public: @@ -59,8 +79,10 @@ public: void MultiplyBPMInBeatRange( int iStartIndex, int iEndIndex, float fFactor ); void AddBPMSegment( const BPMSegment &seg ); void AddStopSegment( const StopSegment &seg ); + void AddTimeSignatureSegment( const TimeSignatureSegment &seg ); int GetBPMSegmentIndexAtBeat( 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; float GetBeatFromElapsedTime( float fElapsedTime ) const // shortcut for places that care only about the beat @@ -102,9 +124,10 @@ public: void InsertRows( int iStartRow, int iRowsToAdd ); void DeleteRows( int iStartRow, int iRowsToDelete ); - RString m_sFile; // informational only - vector m_BPMSegments; // this must be sorted before gameplay - vector m_StopSegments; // this must be sorted before gameplay + RString m_sFile; // informational only + vector m_BPMSegments; // this must be sorted before gameplay + vector m_StopSegments; // this must be sorted before gameplay + vector m_vTimeSignatureSegments; // this must be sorted before gameplay float m_fBeat0OffsetInSeconds; };