Esempio n. 1
0
void SongPosition::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer &timestamp )
{

	if( !timestamp.IsZero() )
		m_LastBeatUpdate = timestamp;
	else
		m_LastBeatUpdate.Touch();

	TimingData::GetBeatArgs beat_info;
	beat_info.elapsed_time= fPositionSeconds;
	timing.GetBeatAndBPSFromElapsedTime(beat_info);
	m_fSongBeat= beat_info.beat;
	m_fCurBPS= beat_info.bps_out;
	m_bFreeze= beat_info.freeze_out;
	m_bDelay= beat_info.delay_out;
	m_iWarpBeginRow= beat_info.warp_begin_out;
	m_fWarpDestination= beat_info.warp_dest_out;
	
	// "Crash reason : -243478.890625 -48695.773438"
	// The question is why is -2000 used as the limit? -aj
	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 );
	
	m_fMusicSecondsVisible = fPositionSeconds - g_fVisualDelaySeconds.Get();
	beat_info.elapsed_time= m_fMusicSecondsVisible;
	timing.GetBeatAndBPSFromElapsedTime(beat_info);
	m_fSongBeatVisible= beat_info.beat;
}
Esempio n. 2
0
	MusicPlaying( RageSound *Music )
	{
		m_Timing.AddBPMSegment( BPMSegment(0,120) );
		m_NewTiming.AddBPMSegment( BPMSegment(0,120) );
		m_HasTiming = false;
		m_TimingDelayed = false;
		m_Music = Music;
	}
Esempio n. 3
0
void BPStoSPB(TimingData &BPS)
{
    auto BPSCopy = BPS;
    for (auto i = BPS.begin(); i != BPS.end(); ++i)
    {
        double valueBPS = i->Value;
        i->Value = 1 / valueBPS;
        i->Time = IntegrateToTime(BPSCopy, i->Time); // Find time in beats based off beats in time
    }
}
Esempio n. 4
0
void SSCLoader::ProcessStops( TimingData &out, const RString sParam )
{
	vector<RString> arrayStopExpressions;
	split( sParam, ",", arrayStopExpressions );
	
	for( unsigned b=0; b<arrayStopExpressions.size(); b++ )
	{
		vector<RString> arrayStopValues;
		split( arrayStopExpressions[b], "=", arrayStopValues );
		if( arrayStopValues.size() != 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid #STOPS value \"%s\" (must have exactly one '='), ignored.",
				     arrayStopExpressions[b].c_str() );
			continue;
		}
		
		const float fBeat = StringToFloat( arrayStopValues[0] );
		const float fNewStop = StringToFloat( arrayStopValues[1] );
		if( fBeat >= 0 && fNewStop > 0 )
			out.AddSegment( StopSegment(BeatToNoteRow(fBeat), fNewStop) );
		else
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid Stop at beat %f, length %f.",
				     fBeat, fNewStop );
		}
	}
}
Esempio n. 5
0
void SSCLoader::ProcessLabels( TimingData &out, const RString sParam )
{
	vector<RString> arrayLabelExpressions;
	split( sParam, ",", arrayLabelExpressions );
	
	for( unsigned b=0; b<arrayLabelExpressions.size(); b++ )
	{
		vector<RString> arrayLabelValues;
		split( arrayLabelExpressions[b], "=", arrayLabelValues );
		if( arrayLabelValues.size() != 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid #LABELS value \"%s\" (must have exactly one '='), ignored.",
				     arrayLabelExpressions[b].c_str() );
			continue;
		}
		
		const float fBeat = StringToFloat( arrayLabelValues[0] );
		RString sLabel = arrayLabelValues[1];
		TrimRight(sLabel);
		if( fBeat >= 0.0f )
			out.AddSegment( LabelSegment(BeatToNoteRow(fBeat), sLabel) );
		else 
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid Label at beat %f called %s.",
				     fBeat, sLabel.c_str() );
		}
		
	}
}
Esempio n. 6
0
void SSCLoader::ProcessScrolls( 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",
				     this->GetSongTitle(),
				     "has an scroll change with %i values.",
				     static_cast<int>(vs2.size()) );
			continue;
		}

		const float fBeat = StringToFloat( vs2[0] );
		const float fRatio = StringToFloat( vs2[1] );

		if( fBeat < 0 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an scroll change with beat %f.",
				     fBeat );
			continue;
		}

		out.AddSegment( ScrollSegment(BeatToNoteRow(fBeat), fRatio) );
	}
Esempio n. 7
0
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.AddSegment( BPMSegment(BeatToNoteRow(beat), fCurBPM) );
}
Esempio n. 8
0
static void ProcessTickcounts( const std::string & value, int & ticks, TimingData & timing )
{
	/* TICKCOUNT will be used below if there are DM compliant BPM changes
	 * and stops. It will be called again in LoadFromKSFFile for the
	 * actual steps. */
	ticks = StringToInt( value );
	ticks = Rage::clamp( ticks, 0, ROWS_PER_BEAT );

	if( ticks == 0 )
		ticks = TickcountSegment::DEFAULT_TICK_COUNT;

	timing.AddSegment( TickcountSegment(0, ticks) );
}
Esempio n. 9
0
void Difficulty::ProcessVSpeeds(TimingData& BPS, TimingData& VerticalSpeeds, double SpeedConstant)
{
    VerticalSpeeds.clear();

    if (SpeedConstant) // We're using a CMod, so further processing is pointless
    {
        TimingSegment VSpeed;
        VSpeed.Time = 0;
        VSpeed.Value = SpeedConstant;
        VerticalSpeeds.push_back(VSpeed);
        return;
    }

    // Calculate velocity at time based on BPM at time
    for (auto Time = BPS.begin();
    Time != BPS.end();
        ++Time)
    {
        float VerticalSpeed;
        TimingSegment VSpeed;

        if (Time->Value)
        {
            float spb = 1 / Time->Value;
            VerticalSpeed = MeasureBaseSpacing / (spb * 4);
        }
        else
            VerticalSpeed = 0;

        VSpeed.Value = VerticalSpeed;
        VSpeed.Time = Time->Time; // We blindly take the BPS time that had offset and drift applied.

        VerticalSpeeds.push_back(VSpeed);
    }

    // Let first speed be not-null.
    if (VerticalSpeeds.size() && VerticalSpeeds[0].Value == 0)
    {
        for (auto i = VerticalSpeeds.begin();
        i != VerticalSpeeds.end();
            ++i)
        {
            if (i->Value != 0)
                VerticalSpeeds[0].Value = i->Value;
        }
    }
}
Esempio n. 10
0
void SSCLoader::ProcessWarps( TimingData &out, const RString sParam, const float fVersion )
{
	vector<RString> arrayWarpExpressions;
	split( sParam, ",", arrayWarpExpressions );
	
	for( unsigned b=0; b<arrayWarpExpressions.size(); b++ )
	{
		vector<RString> arrayWarpValues;
		split( arrayWarpExpressions[b], "=", arrayWarpValues );
		if( arrayWarpValues.size() != 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid #WARPS value \"%s\" (must have exactly one '='), ignored.",
				     arrayWarpExpressions[b].c_str() );
			continue;
		}
		
		const float fBeat = StringToFloat( arrayWarpValues[0] );
		const float fNewBeat = StringToFloat( arrayWarpValues[1] );
		// Early versions were absolute in beats. They should be relative.
		if( ( fVersion < VERSION_SPLIT_TIMING && fNewBeat > fBeat ) )
		{
			out.AddSegment( WarpSegment(BeatToNoteRow(fBeat), fNewBeat - fBeat) );
		}
		else if( fNewBeat > 0 )
			out.AddSegment( WarpSegment(BeatToNoteRow(fBeat), fNewBeat) );
		else
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid Warp at beat %f, BPM %f.",
				     fBeat, fNewBeat );
		}
	}
}
Esempio n. 11
0
void SSCLoader::ProcessCombos( TimingData &out, const RString line, const int rowsPerBeat )
{
	vector<RString> arrayComboExpressions;
	split( line, ",", arrayComboExpressions );
	
	for( unsigned f=0; f<arrayComboExpressions.size(); f++ )
	{
		vector<RString> arrayComboValues;
		split( arrayComboExpressions[f], "=", arrayComboValues );
		unsigned size = arrayComboValues.size();
		if( size < 2 )
		{
			LOG->UserLog("Song file",
				     this->GetSongTitle(),
				     "has an invalid #COMBOS value \"%s\" (must have at least one '='), ignored.",
				     arrayComboExpressions[f].c_str() );
			continue;
		}
		const float fComboBeat = StringToFloat( arrayComboValues[0] );
		const int iCombos = StringToInt( arrayComboValues[1] );
		const int iMisses = (size == 2 ? iCombos : StringToInt(arrayComboValues[2]));
		out.AddSegment( ComboSegment( BeatToNoteRow(fComboBeat), iCombos, iMisses ) );
	}
}
Esempio n. 12
0
void run()
{
#define CHECK(call, exp) \
{ \
	float ret = call; \
	if( call != exp ) { \
		LOG->Warn( "Line %i: Got %f, expected %f", __LINE__, ret, exp); \
		return; \
	} \
}

	TimingData test;
	test.AddBPMSegment( BPMSegment(0, 60) );
	
	/* First, trivial sanity checks. */
	CHECK( test.GetBeatFromElapsedTime(60), 60.0f );
	CHECK( test.GetElapsedTimeFromBeat(60), 60.0f );

	/* The first BPM segment extends backwards in time. */
	CHECK( test.GetBeatFromElapsedTime(-60), -60.0f );
	CHECK( test.GetElapsedTimeFromBeat(-60), -60.0f );
	
	CHECK( test.GetBeatFromElapsedTime(100000), 100000.0f );
	CHECK( test.GetElapsedTimeFromBeat(100000), 100000.0f );
	CHECK( test.GetBeatFromElapsedTime(-100000), -100000.0f );
	CHECK( test.GetElapsedTimeFromBeat(-100000), -100000.0f );
	
	CHECK( test.GetBPMAtBeat(0), 60.0f );
	CHECK( test.GetBPMAtBeat(100000), 60.0f );
	CHECK( test.GetBPMAtBeat(-100000), 60.0f );

	/* 120BPM at beat 10: */
	test.AddBPMSegment( BPMSegment(10, 120) );
	CHECK( test.GetBPMAtBeat(9.99), 60.0f );
	CHECK( test.GetBPMAtBeat(10), 120.0f );

	CHECK( test.GetBeatFromElapsedTime(9), 9.0f );
	CHECK( test.GetBeatFromElapsedTime(10), 10.0f );
	CHECK( test.GetBeatFromElapsedTime(10.5), 11.0f );

	CHECK( test.GetElapsedTimeFromBeat(9), 9.0f );
	CHECK( test.GetElapsedTimeFromBeat(10), 10.0f );
	CHECK( test.GetElapsedTimeFromBeat(11), 10.5f );

	/* Add a 5-second stop at beat 10. */
	test.AddStopSegment( StopSegment(10, 5) );

	/* The stop shouldn't affect GetBPMAtBeat at all. */
	CHECK( test.GetBPMAtBeat(9.99), 60.0f );
	CHECK( test.GetBPMAtBeat(10), 120.0f );

	CHECK( test.GetBeatFromElapsedTime(9), 9.0f );
	CHECK( test.GetBeatFromElapsedTime(10), 10.0f );
	CHECK( test.GetBeatFromElapsedTime(12), 10.0f );
	CHECK( test.GetBeatFromElapsedTime(14), 10.0f );
	CHECK( test.GetBeatFromElapsedTime(15), 10.0f );
	CHECK( test.GetBeatFromElapsedTime(15.5), 11.0f );
	
	CHECK( test.GetElapsedTimeFromBeat(9), 9.0f );
	CHECK( test.GetElapsedTimeFromBeat(10), 10.0f );
	CHECK( test.GetElapsedTimeFromBeat(11), 15.5f );

	/* Add a 2-second stop at beat 5 and a 5-second stop at beat 15. */
	test.m_StopSegments.clear();
	test.AddStopSegment( StopSegment(5, 2) );
	test.AddStopSegment( StopSegment(15, 5) );
	CHECK( test.GetBPMAtBeat(9.99), 60.0f );
	CHECK( test.GetBPMAtBeat(10), 120.0f );

	CHECK( test.GetBeatFromElapsedTime(1), 1.0f );
	CHECK( test.GetBeatFromElapsedTime(2), 2.0f );
	CHECK( test.GetBeatFromElapsedTime(5), 5.0f ); // stopped
	CHECK( test.GetBeatFromElapsedTime(6), 5.0f ); // stopped
	CHECK( test.GetBeatFromElapsedTime(7), 5.0f ); // stop finished
	CHECK( test.GetBeatFromElapsedTime(8), 6.0f );
	CHECK( test.GetBeatFromElapsedTime(12), 10.0f ); // bpm changes to 120
	CHECK( test.GetBeatFromElapsedTime(13), 12.0f );
	CHECK( test.GetBeatFromElapsedTime(14), 14.0f );
	CHECK( test.GetBeatFromElapsedTime(14.5f), 15.0f ); // stopped
	CHECK( test.GetBeatFromElapsedTime(15), 15.0f ); // stopped
	CHECK( test.GetBeatFromElapsedTime(17), 15.0f ); // stopped
	CHECK( test.GetBeatFromElapsedTime(19.5f), 15.0f ); // stop finished
	CHECK( test.GetBeatFromElapsedTime(20), 16.0f );

	CHECK( test.GetElapsedTimeFromBeat(1), 1.0f );
	CHECK( test.GetElapsedTimeFromBeat(2), 2.0f );
	CHECK( test.GetElapsedTimeFromBeat(5), 5.0f ); // stopped
	CHECK( test.GetElapsedTimeFromBeat(6), 8.0f );
	CHECK( test.GetElapsedTimeFromBeat(10), 12.0f ); // bpm changes to 120
	CHECK( test.GetElapsedTimeFromBeat(12), 13.0f );
	CHECK( test.GetElapsedTimeFromBeat(14), 14.0f );
	CHECK( test.GetElapsedTimeFromBeat(15.0f), 14.5f ); // stopped
	CHECK( test.GetElapsedTimeFromBeat(16), 20.0f );

RageTimer foobar;
	/* We can look up the time of any given beat, then look up the beat of that
	 * time and get the original value.  (We can't do this in reverse; the beat
	 * doesn't move during stop segments.) */
int q = 0;
	for( float f = -10; f < 250; f += 0.002 )
	{
		++q;
//		const float t = test.GetElapsedTimeFromBeat( f );
		const float b = test.GetBeatFromElapsedTime( f );

		/* b == f */
	
//		if( fabsf(b-f) > 0.001 )
//		{
//			LOG->Warn( "%f != %f", b, f );
//			return;
//		}
	}
LOG->Trace("... %i in %f", q, foobar.GetDeltaTime());

	TimingData test2;
	test2.AddBPMSegment( BPMSegment(0, 60) );
	test2.AddStopSegment( StopSegment(0, 1) );
	CHECK( test2.GetBeatFromElapsedTime(-1), -1.0f );
	CHECK( test2.GetBeatFromElapsedTime(0), 0.0f );
	CHECK( test2.GetBeatFromElapsedTime(1), 0.0f );
	CHECK( test2.GetBeatFromElapsedTime(2), 1.0f );
	CHECK( test2.GetElapsedTimeFromBeat(-1), -1.0f );
	CHECK( test2.GetElapsedTimeFromBeat(0), 0.0f );
	CHECK( test2.GetElapsedTimeFromBeat(1), 2.0f );
	CHECK( test2.GetElapsedTimeFromBeat(2), 3.0f );
}
Esempio n. 13
0
static void Serialize(const TimingData &td, Json::Value &root)
{
	JsonUtil::SerializeVectorPointers( td.GetTimingSegments(SEGMENT_BPM), Serialize, root["BpmSegments"] );
	JsonUtil::SerializeVectorPointers( td.GetTimingSegments(SEGMENT_STOP), Serialize, root["StopSegments"] );
}
Esempio n. 14
0
void Difficulty::GetPlayableData(VectorTN NotesOut,
    TimingData& BPS,
    TimingData& VerticalSpeeds,
    TimingData& Warps,
    float Drift, double SpeedConstant)
{
    /*
        We'd like to build the notes' position from 0 to infinity,
        however the real "zero" position would be the judgment line
        in other words since "up" is negative relative to 0
        and 0 is the judgment line
        position would actually be
        judgeline - positiveposition
        and positiveposition would just be
        measure * measuresize + fraction * fractionsize

        In practice, since we use a ms-based model rather than a beat one,
        we just do regular integration of
        position = sum(speed_i * duration_i) + speed_current * (time_current - speed_start_time)
    */

    assert(Data != nullptr);

    ProcessBPS(BPS, Drift);
    ProcessVSpeeds(BPS, VerticalSpeeds, SpeedConstant);

    if (!SpeedConstant) // If there is a speed constant having speed changes is not what we want
        ProcessSpeedVariations(BPS, VerticalSpeeds, Drift);

    if (SpeedConstant) Warps.clear();
    else Warps = Data->Warps;

    // From here on, we'll just copy the notes out. Otherwise, just leave the processed data.
    if (!NotesOut)
        return;

    for (int KeyIndex = 0; KeyIndex < Channels; KeyIndex++)
        NotesOut[KeyIndex].clear();

    /* For all channels of this difficulty */
    for (int KeyIndex = 0; KeyIndex < Channels; KeyIndex++)
    {
        int MIdx = 0;

        /* For each measure of this channel */
        for (auto Msr = Data->Measures.begin();
        Msr != Data->Measures.end();
            ++Msr)
        {
            /* For each note in the measure... */
            ptrdiff_t total_notes = Msr->Notes[KeyIndex].size();

            for (auto Note = 0; Note < total_notes; Note++)
            {
                /*
                    Calculate position. (Change this to TrackNote instead of processing?)
                    issue is not having the speed change data there.
                */
                NoteData &CurrentNote = (*Msr).Notes[KeyIndex][Note];
                TrackNote NewNote;

                NewNote.AssignNotedata(CurrentNote);

                NewNote.AddTime(Drift);

                float VerticalPosition = IntegrateToTime(VerticalSpeeds, NewNote.GetStartTime());
                float HoldEndPosition = IntegrateToTime(VerticalSpeeds, NewNote.GetTimeFinal());

                // if upscroll change minus for plus as well as matrix at screengameplay7k
                if (!CurrentNote.EndTime)
                    NewNote.AssignPosition(VerticalPosition);
                else
                    NewNote.AssignPosition(VerticalPosition, HoldEndPosition);

                // Okay, now we want to know what fraction of a beat we're dealing with
                // this way we can display colored (a la Stepmania) notes.
                // We should do this before changing time by drift.
                double cBeat = IntegrateToTime(BPS, NewNote.GetStartTime());
                double iBeat = floor(cBeat);
                double dBeat = (cBeat - iBeat);

                NewNote.AssignFraction(dBeat);

                double Wamt = -GetWarpAmountAtTime(CurrentNote.StartTime);
                NewNote.AddTime(Wamt);

                if (!SpeedConstant || (NewNote.IsJudgable() && !IsWarpingAt(CurrentNote.StartTime)))
                    NotesOut[KeyIndex].push_back(NewNote);
            }

            MIdx++;
        }

        // done with the channel - sort it
        std::stable_sort(NotesOut[KeyIndex].begin(), NotesOut[KeyIndex].end(),
            [](const TrackNote &A, const TrackNote &B) -> bool
        {
            return A.GetVertical() < B.GetVertical();
        });
    }
}
Esempio n. 15
0
void Difficulty::ProcessSpeedVariations(TimingData& BPS, TimingData& VerticalSpeeds, double Drift)
{
    assert(Data != NULL);

    TimingData tVSpeeds = VerticalSpeeds; // We need this to store what values to change
    TimingData &Scrolls = Data->Scrolls;

    std::sort(Scrolls.begin(), Scrolls.end());

    for (TimingData::const_iterator Change = Scrolls.begin();
    Change != Scrolls.end();
        ++Change)
    {
        TimingData::const_iterator NextChange = (Change + 1);
        double ChangeTime = Change->Time + Drift + Offset;

        /*
            Find all VSpeeds
            if there exists a speed change which is virtually happening at the same time as this VSpeed
            modify it to be this value * factor
        */

        bool MoveOn = false;
        for (auto Time = VerticalSpeeds.begin();
        Time != VerticalSpeeds.end();
            ++Time)
        {
            if (abs(ChangeTime - Time->Time) < 0.00001)
            {
                Time->Value *= Change->Value;
                MoveOn = true;
            }
        }

        if (MoveOn) continue;

        /*
            There are no collisions- insert a new speed at this time
        */

        if (ChangeTime < 0)
            continue;

        float SpeedValue;
        SpeedValue = SectionValue(tVSpeeds, ChangeTime) * Change->Value;

        TimingSegment VSpeed;

        VSpeed.Time = ChangeTime;
        VSpeed.Value = SpeedValue;

        VerticalSpeeds.push_back(VSpeed);

        /*
            Theorically, if there were a VSpeed change after this one (such as a BPM change) we've got to modify them
            if they're between this and the next speed change.

            Apparently, this behaviour is a "bug" since osu!mania resets SV changes
            after a BPM change.
        */

        if (BPMType == VSRG::Difficulty::BT_BEATSPACE) // Okay, we're an osu!mania chart, leave the resetting.
            continue;

        // We're not an osu!mania chart, so it's time to do what should be done.
        for (auto Time = VerticalSpeeds.begin();
        Time != VerticalSpeeds.end();
            ++Time)
        {
            if (Time->Time > ChangeTime)
            {
                // Two options, between two speed changes, or the last one. Second case, NextChange == Scrolls.end().
                // Otherwise, just move on
                // Last speed change
                if (NextChange == Scrolls.end())
                {
                    Time->Value = Change->Value * SectionValue(tVSpeeds, Time->Time);
                }
                else
                {
                    if (Time->Time < NextChange->Time) // Between speed changes
                        Time->Value = Change->Value * SectionValue(tVSpeeds, Time->Time);
                }
            }
        }
    }

    std::sort(VerticalSpeeds.begin(), VerticalSpeeds.end());
}
Esempio n. 16
0
void Difficulty::ProcessBPS(TimingData& BPS, double Drift)
{
    /*
        Calculate BPS. The algorithm is basically the same as VSpeeds.
        BPS time is calculated applying the offset and drift.
    */
    assert(Data != NULL);

    TimingData &StopsTiming = Data->Stops;

    BPS.clear();

    for (auto Time = Timing.begin();
    Time != Timing.end();
        ++Time)
    {
        TimingSegment Seg;

        Seg.Time = TimeFromTimingKind(Timing, StopsTiming, *Time, BPMType, Offset, Drift);
        Seg.Value = BPSFromTimingKind(Time->Value, BPMType);

        BPS.push_back(Seg);
    }

    /* Sort for justice */
    sort(BPS.begin(), BPS.end());

    if (!StopsTiming.size() || BPMType != VSRG::Difficulty::BT_BEAT) // Stops only supported in Beat mode.
        return;

    /* Here on, just working with stops. */
    for (auto Time = StopsTiming.begin();
    Time != StopsTiming.end();
        ++Time)
    {
        TimingSegment Seg;
        double TValue = TimeAtBeat(Timing, Offset + Drift, Time->Time) + StopTimeAtBeat(StopsTiming, Time->Time);
        double TValueN = TimeAtBeat(Timing, Offset + Drift, Time->Time) + StopTimeAtBeat(StopsTiming, Time->Time) + Time->Value;

        /* Initial Stop */
        Seg.Time = TValue;
        Seg.Value = 0;

        /* First, eliminate collisions. */
        for (auto k = BPS.begin(); k != BPS.end();)
        {
            if (k->Time == TValue) /* Equal? Remove the collision, leaving only the 0 in front. */
            {
                k = BPS.erase(k);

                if (k == BPS.end())
                    break;
                else continue;
            }

            ++k;
        }

        // Okay, the collision is out. Let's push our 0-speeder.
        BPS.push_back(Seg);

        // Now we find what bps to restore to.
        float bpsRestore = bps(SectionValue(Timing, Time->Time));

        for (auto k = BPS.begin(); k != BPS.end(); )
        {
            if (k->Time > TValue && k->Time <= TValueN) // So wait, there's BPM changes in between? Holy shit.
            {
                bpsRestore = k->Value; /* This is the last speed change in the interval that the stop lasts. We'll use it. */

                /* Eliminate this since we're not going to use it. */
                k = BPS.erase(k);

                if (k == BPS.end())
                    break;
                continue;
            }

            ++k;
        }

        /* Restored speed after stop */
        Seg.Time = TValueN;
        Seg.Value = bpsRestore;
        BPS.push_back(Seg);
    }

    std::sort(BPS.begin(), BPS.end());
}
Esempio n. 17
0
static bool LoadFromKSFFile( const std::string &sPath, Steps &out, Song &song, bool bKIUCompliant )
{
	using std::max;
	LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );

	MsdFile msd;
	if( !msd.ReadFile( sPath, false ) )  // don't unescape
	{
		LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() );
		return false;
	}

	// this is the value we read for TICKCOUNT
	int iTickCount = -1;
	// used to adapt weird tickcounts
	//float fScrollRatio = 1.0f; -- uncomment when ready to use.
	vector<std::string> vNoteRows;

	// According to Aldo_MX, there is a default BPM and it's 60. -aj
	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++ )
	{
		const MsdFile::value_t &sParams = msd.GetValue( i );
		std::string sValueName = Rage::make_upper(sParams[0]);

		/* handle the data...well, not this data: not related to steps.
		 * Skips INTRO, MUSICINTRO, TITLEFILE, DISCFILE, SONGFILE. */
		if (sValueName=="TITLE" || Rage::ends_with(sValueName, "INTRO")
			|| Rage::ends_with(sValueName, "FILE") )
		{

		}
		else if( sValueName=="BPM" )
		{
			BPM1 = StringToFloat(sParams[1]);
			stepsTiming.AddSegment( 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.set_offset(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] );
			if( iTickCount <= 0 )
			{
				LOG->UserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount );
				return false;
			}
			stepsTiming.AddSegment( TickcountSegment(0, iTickCount));
		}

		else if( sValueName=="DIFFICULTY" )
		{
			out.SetMeter( max(StringToInt(sParams[1]), 1) );
		}
		// new cases from Aldo_MX's fork:
		else if( sValueName=="PLAYER" )
		{
			std::string sPlayer = Rage::make_lower(sParams[1]);
			if( sPlayer.find( "double" ) != string::npos )
			{
				bDoublesChart = true;
			}
		}
		// This should always be last.
		else if( sValueName=="STEP" )
		{
			std::string theSteps = Rage::trim_left(sParams[1]);
			auto toDump = Rage::split(theSteps, "\n", Rage::EmptyEntries::skip);
			vNoteRows.insert(vNoteRows.end(), std::make_move_iterator(toDump.begin()), std::make_move_iterator(toDump.end()));
		}
	}

	if( iTickCount == -1 )
	{
		iTickCount = 4;
		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

	{
		std::string sDir, sFName, sExt;
		splitpath( sPath, sDir, sFName, sExt );
		sFName = Rage::make_lower(sFName);

		out.SetDescription(sFName);
		// Check another before anything else... is this okay? -DaisuMaster
		if( sFName.find("another") != string::npos )
		{
			out.SetDifficulty( Difficulty_Edit );
			if( !out.GetMeter() ) out.SetMeter( 25 );
		}
		else if(sFName.find("wild") != string::npos ||
			sFName.find("wd") != string::npos ||
			sFName.find("crazy+") != string::npos ||
			sFName.find("cz+") != string::npos ||
			sFName.find("hardcore") != string::npos )
		{
			out.SetDifficulty( Difficulty_Challenge );
			if( !out.GetMeter() ) out.SetMeter( 20 );
		}
		else if(sFName.find("crazy") != string::npos ||
			sFName.find("cz") != string::npos ||
			sFName.find("nightmare") != string::npos ||
			sFName.find("nm") != string::npos ||
			sFName.find("crazydouble") != string::npos )
		{
			out.SetDifficulty( Difficulty_Hard );
			if( !out.GetMeter() ) out.SetMeter( 14 ); // Set the meters to the Pump scale, not DDR.
		}
		else if(sFName.find("hard") != string::npos ||
			sFName.find("hd") != string::npos ||
			sFName.find("freestyle") != string::npos ||
			sFName.find("fs") != string::npos ||
			sFName.find("double") != string::npos )
		{
			out.SetDifficulty( Difficulty_Medium );
			if( !out.GetMeter() ) out.SetMeter( 8 );
		}
		else if(sFName.find("easy") != string::npos ||
			sFName.find("ez") != string::npos ||
			sFName.find("normal") != string::npos )
		{
			// I wonder if I should leave easy fall into the Beginner difficulty... -DaisuMaster
			out.SetDifficulty( Difficulty_Easy );
			if( !out.GetMeter() ) out.SetMeter( 4 );
		}
		else if(sFName.find("beginner") != string::npos ||
			sFName.find("practice") != string::npos || sFName.find("pr") != string::npos  )
		{
			out.SetDifficulty( Difficulty_Beginner );
			if( !out.GetMeter() ) out.SetMeter( 4 );
		}
		else
		{
			out.SetDifficulty( Difficulty_Hard );
			if( !out.GetMeter() ) out.SetMeter( 10 );
		}

		out.m_StepsType = StepsType_pump_single;

		// Check for "halfdouble" before "double".
		if(sFName.find("halfdouble") != string::npos ||
		   sFName.find("half-double") != string::npos ||
		   sFName.find("h_double") != string::npos ||
		   sFName.find("hdb") != string::npos )
			out.m_StepsType = StepsType_pump_halfdouble;
		// Handle bDoublesChart from above as well. -aj
		else if(sFName.find("double") != string::npos ||
			sFName.find("nightmare") != string::npos ||
			sFName.find("freestyle") != string::npos ||
			sFName.find("db") != string::npos ||
			sFName.find("nm") != string::npos ||
			sFName.find("fs") != string::npos || bDoublesChart )
			out.m_StepsType = StepsType_pump_double;
		else if( sFName.find("_1") != string::npos )
			out.m_StepsType = StepsType_pump_single;
		else if( sFName.find("_2") != string::npos )
			out.m_StepsType = StepsType_pump_couple;
	}

	switch( out.m_StepsType )
	{
	case StepsType_pump_single: notedata.SetNumTracks( 5 ); break;
	case StepsType_pump_couple: 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;
	default: FAIL_M( fmt::sprintf("%i", out.m_StepsType) );
	}

	int t = 0;
	int iHoldStartRow[13];
	for( t=0; t<13; t++ )
		iHoldStartRow[t] = -1;

	bool bTickChangeNeeded = false;
	int newTick = -1;
	float fCurBeat = 0.0f;
	float prevBeat = 0.0f; // Used for hold tails.

	for (auto &sRowString: vNoteRows)
	{
		sRowString = Rage::trim_right(sRowString, "\r\n");

		if( sRowString == "" )
		{
			continue;	// skip
		}
		// All 2s indicates the end of the song.
		if( sRowString == "2222222222222" )
		{
			// Finish any holds that didn't get...well, finished.
			for( t=0; t < notedata.GetNumTracks(); t++ )
			{
				if( iHoldStartRow[t] != -1 )	// this ends the hold
				{
					if( iHoldStartRow[t] == BeatToNoteRow(prevBeat) )
					{
						notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP );
					}
					else
					{
						notedata.AddHoldNote(t,
								     iHoldStartRow[t],
								     BeatToNoteRow(prevBeat),
								     TAP_ORIGINAL_HOLD_HEAD );
					}
				}
			}
			/* have this row be the last moment in the song, unless
			 * a future step ends later. */
			//float curTime = stepsTiming.GetElapsedTimeFromBeat(fCurBeat);
			//if (curTime > song.GetSpecifiedLastSecond())
			//{
			//	song.SetSpecifiedLastSecond(curTime);
			//}

			song.SetSpecifiedLastSecond( song.GetSpecifiedLastSecond() + 4 );

			break;
		}

		else if( Rage::starts_with(sRowString, "|") )
		{
			/*
			if (bKIUCompliant)
			{
				// Log an error, ignore the line.
				continue;
			}
			*/
			// gotta do something tricky here: if the bpm is below one then a couple of calculations
			// for scrollsegments will be made, example, bpm 0.2, tick 4000, the scrollsegment will
			// be 0. if the tickcount is non a stepmania standard then it will be adapted, a scroll
			// segment will then be added based on approximations. -DaisuMaster
			// eh better do it considering the tickcount (high tickcounts)

			// I'm making some experiments, please spare me...
			//continue;

			std::string temp = sRowString.substr(2,sRowString.size()-3);
			float numTemp = StringToFloat(temp);
			if (Rage::starts_with(sRowString, "|T"))
			{
				// duh
				iTickCount = static_cast<int>(numTemp);
				// I have been owned by the man -DaisuMaster
				stepsTiming.SetTickcountAtBeat( fCurBeat, Rage::clamp(iTickCount, 0, ROWS_PER_BEAT) );
			}
			else if (Rage::starts_with(sRowString, "|B"))
			{
				// BPM
				stepsTiming.SetBPMAtBeat( fCurBeat, numTemp );
			}
			else if (Rage::starts_with(sRowString, "|E"))
			{
				// DelayBeat
				float fCurDelay = 60 / stepsTiming.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount;
				fCurDelay += stepsTiming.GetDelayAtRow(BeatToNoteRow(fCurBeat) );
				stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay );
			}
			else if (Rage::starts_with(sRowString, "|D"))
			{
				// Delays
				float fCurDelay = stepsTiming.GetStopAtRow(BeatToNoteRow(fCurBeat) );
				fCurDelay += numTemp / 1000;
				stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay );
			}
			else if (Rage::starts_with(sRowString, "|M") || Rage::starts_with(sRowString, "|C"))
			{
				// multipliers/combo
				ComboSegment seg( BeatToNoteRow(fCurBeat), int(numTemp) );
				stepsTiming.AddSegment( seg );
			}
			else if (Rage::starts_with(sRowString, "|S"))
			{
				// speed segments
			}
			else if (Rage::starts_with(sRowString, "|F"))
			{
				// fakes
			}
			else if (Rage::starts_with(sRowString, "|X"))
			{
				// scroll segments
				ScrollSegment seg = ScrollSegment( BeatToNoteRow(fCurBeat), numTemp );
				stepsTiming.AddSegment( seg );
				//return true;
			}

			continue;
		}

		// Half-doubles is offset; "0011111100000".
		if( out.m_StepsType == StepsType_pump_halfdouble )
			sRowString.erase( 0, 2 );

		// Update TICKCOUNT for Direct Move files.
		if( bTickChangeNeeded )
		{
			iTickCount = newTick;
			bTickChangeNeeded = false;
		}

		for( t=0; t < notedata.GetNumTracks(); t++ )
		{
			if( sRowString[t] == '4' )
			{
				/* Remember when each hold starts; ignore the middle. */
				if( iHoldStartRow[t] == -1 )
					iHoldStartRow[t] = BeatToNoteRow(fCurBeat);
				continue;
			}

			if( iHoldStartRow[t] != -1 )	// this ends the hold
			{
				int iEndRow = BeatToNoteRow(prevBeat);
				if( iHoldStartRow[t] == iEndRow )
					notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP );
				else
				{
					//notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_PUMP_HEAD );
					notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_HOLD_HEAD );
				}
				iHoldStartRow[t] = -1;
			}

			TapNote tap;
			switch( sRowString[t] )
			{
			case '0':	tap = TAP_EMPTY;		break;
			case '1':	tap = TAP_ORIGINAL_TAP;		break;
				//allow setting more notetypes on ksf files, this may come in handy (it should) -DaisuMaster
			case 'M':
			case 'm':
						tap = TAP_ORIGINAL_MINE;
						break;
			case 'F':
			case 'f':
						tap = TAP_ORIGINAL_FAKE;
						break;
			case 'L':
			case 'l':
						tap = TAP_ORIGINAL_LIFT;
						break;
			default:
				LOG->UserLog( "Song file", sPath, "has an invalid row \"%s\"; corrupt notes ignored.",
					      sRowString.c_str() );
				//return false;
				tap = TAP_EMPTY;
				break;
			}

			notedata.SetTapNote(t, BeatToNoteRow(fCurBeat), tap);
		}
		prevBeat = fCurBeat;
		fCurBeat = prevBeat + 1.0f / iTickCount;
	}

	out.SetNoteData( notedata );
	out.m_Timing = stepsTiming;

	out.TidyUpData();

	out.SetSavedToDisk( true );	// we're loading from disk, so this is by definintion already saved

	return true;
}
Esempio n. 18
0
void SMLoader::LoadTimingFromSMFile( const MsdFile &msd, TimingData &out )
{
	out.m_fBeat0OffsetInSeconds = 0;
	out.m_BPMSegments.clear();
	out.m_StopSegments.clear();

	for( unsigned i=0; i<msd.GetNumValues(); i++ )
	{
		const MsdFile::value_t &sParams = msd.GetValue(i);
		const CString sValueName = sParams[0];

		if( 0==stricmp(sValueName,"OFFSET") )
			out.m_fBeat0OffsetInSeconds = strtof( sParams[1], NULL );
		else if( 0==stricmp(sValueName,"STOPS") || 0==stricmp(sValueName,"FREEZES") )
		{
			CStringArray arrayFreezeExpressions;
			split( sParams[1], ",", arrayFreezeExpressions );

			for( unsigned f=0; f<arrayFreezeExpressions.size(); f++ )
			{
				CStringArray arrayFreezeValues;
				split( arrayFreezeExpressions[f], "=", arrayFreezeValues );
				/* XXX: Once we have a way to display warnings that the user actually
				 * cares about (unlike most warnings), this should be one of them. */
				if( arrayFreezeValues.size() != 2 )
				{
					LOG->Warn( "Invalid #%s value \"%s\" (must have exactly one '='), ignored",
						sValueName.c_str(), arrayFreezeExpressions[f].c_str() );
					continue;
				}

				const float fFreezeBeat = strtof( arrayFreezeValues[0], NULL );
				const float fFreezeSeconds = strtof( arrayFreezeValues[1], NULL );
				
				StopSegment new_seg;
				new_seg.m_fStartBeat = fFreezeBeat;
				new_seg.m_fStopSeconds = fFreezeSeconds;

//				LOG->Trace( "Adding a freeze segment: beat: %f, seconds = %f", new_seg.m_fStartBeat, new_seg.m_fStopSeconds );

				out.AddStopSegment( new_seg );
			}
		}

		else if( 0==stricmp(sValueName,"BPMS") )
		{
			CStringArray arrayBPMChangeExpressions;
			split( sParams[1], ",", arrayBPMChangeExpressions );

			for( unsigned b=0; b<arrayBPMChangeExpressions.size(); b++ )
			{
				CStringArray arrayBPMChangeValues;
				split( arrayBPMChangeExpressions[b], "=", arrayBPMChangeValues );
				/* XXX: Once we have a way to display warnings that the user actually
				 * cares about (unlike most warnings), this should be one of them. */
				if(arrayBPMChangeValues.size() != 2)
				{
					LOG->Warn( "Invalid #%s value \"%s\" (must have exactly one '='), ignored",
						sValueName.c_str(), arrayBPMChangeExpressions[b].c_str() );
					continue;
				}

				const float fBeat = strtof( arrayBPMChangeValues[0], NULL );
				const float fNewBPM = strtof( arrayBPMChangeValues[1], NULL );
				
				BPMSegment new_seg;
				new_seg.m_fStartBeat = fBeat;
				new_seg.m_fBPM = fNewBPM;
				
				out.AddBPMSegment( new_seg );
			}
		}
	}
}