Ejemplo n.º 1
0
void Steps::CalculateRadarValues( float fMusicLengthSeconds )
{
    // If we're autogen, don't calculate values.  GetRadarValues will take from our parent.
    if( parent != NULL )
        return;

    // Do write radar values, and leave it up to the reading app whether they want to trust
    // the cached values without recalculating them.
    /*
    // If we're an edit, leave the RadarValues invalid.
    if( IsAnEdit() )
    	return;
    */

    NoteData tempNoteData;
    this->GetNoteData( tempNoteData );

    FOREACH_PlayerNumber( pn )
    m_CachedRadarValues[pn].Zero();

    if( tempNoteData.IsComposite() )
    {
        vector<NoteData> vParts;

        NoteDataUtil::SplitCompositeNoteData( tempNoteData, vParts );
        for( size_t pn = 0; pn < min(vParts.size(), size_t(NUM_PLAYERS)); ++pn )
            NoteDataUtil::CalculateRadarValues( vParts[pn], fMusicLengthSeconds, m_CachedRadarValues[pn] );
    }
    else
    {
        NoteDataUtil::CalculateRadarValues( tempNoteData, fMusicLengthSeconds, m_CachedRadarValues[0] );
        fill_n( m_CachedRadarValues + 1, NUM_PLAYERS-1, m_CachedRadarValues[0] );
    }
}
Ejemplo n.º 2
0
void Steps::CreateBlank( StepsType ntTo )
{
    m_StepsType = ntTo;
    NoteData noteData;
    noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks );
    this->SetNoteData( noteData );
}
Ejemplo n.º 3
0
// -1 for iOriginalTracksToTakeFrom means no track
void NoteData::LoadTransformed( const NoteData* pOriginal, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] )
{
	// reset all notes
	Init();
	
	NoteData Original;
	Original.To4s( *pOriginal );

	Config( Original );
	m_iNumTracks = iNewNumTracks;

	// copy tracks
	for( int t=0; t<m_iNumTracks; t++ )
	{
		const int iOriginalTrack = iOriginalTrackToTakeFrom[t];
		ASSERT_M( iOriginalTrack < Original.m_iNumTracks, ssprintf("from %i >= %i (to %i)", 
			iOriginalTrack, Original.m_iNumTracks, iOriginalTrackToTakeFrom[t]));

		if( iOriginalTrack == -1 )
			continue;
		m_TapNotes[t] = Original.m_TapNotes[iOriginalTrack];
	}

	Convert4sToHoldNotes();

	m_AttackMap = Original.GetAttackMap();
}
Ejemplo n.º 4
0
void Steps::CalculateRadarValues( float fMusicLengthSeconds )
{
	// If we're autogen, don't calculate values.  GetRadarValues will take from our parent.
	if( parent != NULL )
		return;

	if( m_bAreCachedRadarValuesJustLoaded )
	{
		m_bAreCachedRadarValuesJustLoaded = false;
		return;
	}

	// Do write radar values, and leave it up to the reading app whether they want to trust
	// the cached values without recalculating them.
	/*
	// If we're an edit, leave the RadarValues invalid.
	if( IsAnEdit() )
		return;
	*/

	NoteData tempNoteData;
	this->GetNoteData( tempNoteData );

	FOREACH_PlayerNumber( pn )
		m_CachedRadarValues[pn].Zero();

	GAMESTATE->SetProcessedTimingData(this->GetTimingData());
	if( tempNoteData.IsComposite() )
	{
		vector<NoteData> vParts;

		NoteDataUtil::SplitCompositeNoteData( tempNoteData, vParts );
		for( size_t pn = 0; pn < min(vParts.size(), size_t(NUM_PLAYERS)); ++pn )
			NoteDataUtil::CalculateRadarValues( vParts[pn], fMusicLengthSeconds, m_CachedRadarValues[pn] );
	}
	else if (GAMEMAN->GetStepsTypeInfo(this->m_StepsType).m_StepsTypeCategory == StepsTypeCategory_Couple)
	{
		NoteData p1 = tempNoteData;
		// XXX: Assumption that couple will always have an even number of notes.
		const int tracks = tempNoteData.GetNumTracks() / 2;
		p1.SetNumTracks(tracks);
		NoteDataUtil::CalculateRadarValues(p1,
										   fMusicLengthSeconds,
										   m_CachedRadarValues[PLAYER_1]);
		// at this point, p2 is tempNoteData.
		NoteDataUtil::ShiftTracks(tempNoteData, tracks);
		tempNoteData.SetNumTracks(tracks);
		NoteDataUtil::CalculateRadarValues(tempNoteData,
										   fMusicLengthSeconds,
										   m_CachedRadarValues[PLAYER_2]);
	}
	else
	{
		NoteDataUtil::CalculateRadarValues( tempNoteData, fMusicLengthSeconds, m_CachedRadarValues[0] );
		fill_n( m_CachedRadarValues + 1, NUM_PLAYERS-1, m_CachedRadarValues[0] );
	}
	GAMESTATE->SetProcessedTimingData(NULL);
}
Ejemplo n.º 5
0
void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds )	// pSource does not have to be of the same StepsType
{
    m_StepsType = ntTo;
    NoteData noteData;
    pSource->GetNoteData( noteData );
    noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks );
    parent = NULL;
    this->SetNoteData( noteData );
    this->SetDescription( pSource->GetDescription() );
    this->SetDifficulty( pSource->GetDifficulty() );
    this->SetMeter( pSource->GetMeter() );
    this->CalculateRadarValues( fMusicLengthSeconds );
}
Ejemplo n.º 6
0
void Steps::GetNoteData( NoteData& noteDataOut ) const
{
	Decompress();

	if( m_bNoteDataIsFilled )
	{
		noteDataOut = *m_pNoteData;
	}
	else
	{
		noteDataOut.ClearAll();
		noteDataOut.SetNumTracks( GAMEMAN->GetStepsTypeInfo(m_StepsType).iNumTracks );
	}
}
Ejemplo n.º 7
0
void MidiOut::playPart(mid_data &part)
{
    NoteData *nd = createNoteData();

    resetPlayData();
    event = part.list;
    tick = part.tempo * part.ippqn;
    initClock();

    while(1)
    {
        if(event)
        {
            if(playEvent(part, nd))
            {
                // We managed to play the event without having to wait. Move to the next one.
                event = event->next;
            }
        }
        else
        {
            if(loop_num >= 0)
            {
                popLoop();
            }
            else if(part.repeat)
            {
                // Repeat the part.
                last_tick = 0;
                last_time = 0;
                initClock();
                event = part.list;
                // Handle note-offs?
                nd->play();
            }
            else
            {
                // We played the last event, exit.
                break;
            }
        }

        if(show_notes)
            nd->show(part.tempo);
        endLoopDelay();
    }
    delete nd;
}
Ejemplo n.º 8
0
void KSFLoader::RemoveHoles( NoteData &out, const Song &song )
{
    /* Start at the second BPM segment; the first one is already aligned. */
    for( unsigned seg = 1; seg < song.m_Timing.m_BPMSegments.size(); ++seg )
    {
//		const float FromBeat = song.m_Timing.m_BPMSegments[seg].m_fStartBeat;
        const float FromBeat = song.m_Timing.m_BPMSegments[seg].m_fStartBeat * song.m_BPMSegments[seg].m_fBPM / song.m_BPMSegments[0].m_fBPM;
        const int FromRow = (int) BeatToNoteRow(FromBeat);
        const int ToRow = (int) BeatToNoteRow(song.m_Timing.m_BPMSegments[seg].m_fStartBeat);

        LOG->Trace("from %f (%i) to (%i)", FromBeat, FromRow, ToRow);
//		const int ToRow = (int) roundf(FromRow * song.m_Timing.m_BPMSegments[0].m_fBPM / song.m_BPMSegments[seg].m_fBPM);
//		const int Rows = out.GetLastRow() - FromRow + 1;
//		int LastRow;
//		if(seg+1 < song.m_Timing.m_BPMSegments().size())
//			LastRow = (int) NoteRowToBeat( song.m_Timing.m_BPMSegments[seg+1].m_fStartBeat ) - 1;
//		else
//			LastRow = out.GetLastRow();
        NoteData tmp;
        tmp.SetNumTracks(out.GetNumTracks());
        tmp.CopyRange( &out, FromRow, out.GetLastRow() );
        out.ClearRange( FromRow, out.GetLastRow() );
        out.CopyRange( &tmp, 0, tmp.GetLastRow(), ToRow );
    }

    /*
    	out.ConvertHoldNotesTo2sAnd3s();
    	for( t = 0; t < notedata.GetNumTracks(); ++t )
    	{
    		const float CurBPM = song.GetBPMAtBeat( NoteRowToBeat(row) );
    		song.m_Timing.m_BPMSegments.size()
    		for( int row = 0; row <= notedata.GetLastRow(); ++row )
    		{
    			TapNote tn = notedata.GetTapNote(t, row);
    			if( tn == TAP_EMPTY )
    				continue;

    			const int RealRow = (int) roundf(row * OrigBPM / CurBPM);
    			if( RealRow == row )
    				continue;
    			LOG->Trace("from %i to %i", row, RealRow);
    			notedata.SetTapNote( t, RealRow, tn );
    			notedata.SetTapNote( t, row, TAP_EMPTY );
    		}
    	}
    	out.Convert2sAnd3sToHoldNotes();
    */
}
/* Return the minimum tap score of a row.  If the row isn't complete (not all
 * taps have been hit), return TNS_None or TNS_Miss. */
TapNoteScore NoteDataWithScoring::MinTapNoteScore( const NoteData &in, unsigned row, PlayerNumber plnum )
{
	//LOG->Trace("Hey I'm NoteDataWithScoring::MinTapNoteScore");
	TapNoteScore score = TNS_W1;
	for( int t=0; t<in.GetNumTracks(); t++ )
	{
		// Ignore mines (and fake arrows), or the score will always be TNS_None.
		const TapNote &tn = in.GetTapNote( t, row );
		if (tn.type == TapNoteType_Empty ||
			tn.type == TapNoteType_Mine ||
			tn.type == TapNoteType_Fake ||
			tn.type == TapNoteType_AutoKeysound ||
			( plnum != PlayerNumber_Invalid && tn.pn != plnum ) )
			continue;
		score = min( score, tn.result.tns );
	}

	//LOG->Trace( ssprintf("OMG score is?? %s",TapNoteScoreToString(score).c_str()) );
	return score;
}
Ejemplo n.º 10
0
void ScoreKeeperNormal::GetRowCounts( const NoteData &nd, int iRow,
					  int &iNumHitContinueCombo, int &iNumHitMaintainCombo,
					  int &iNumBreakCombo )
{
	iNumHitContinueCombo = iNumHitMaintainCombo = iNumBreakCombo = 0;
	for( int track = 0; track < nd.GetNumTracks(); ++track )
	{
		const TapNote &tn = nd.GetTapNote( track, iRow );

		if( tn.type != TapNoteType_Tap && tn.type != TapNoteType_HoldHead && tn.type != TapNoteType_Lift )
			continue;
		TapNoteScore tns = tn.result.tns;
		if( tns >= m_MinScoreToContinueCombo )
			++iNumHitContinueCombo;
		else if( tns >= m_MinScoreToMaintainCombo )
			++iNumHitMaintainCombo;
		else
			++iNumBreakCombo;
	}
}
Ejemplo n.º 11
0
void Steps::SetNoteData( const NoteData& noteDataNew )
{
	ASSERT( noteDataNew.GetNumTracks() == GAMEMAN->GetStepsTypeInfo(m_StepsType).iNumTracks );

	DeAutogen( false );

	*m_pNoteData = noteDataNew;
	m_bNoteDataIsFilled = true;
	
	m_sNoteDataCompressed = RString();
	m_iHash = 0;
}
Ejemplo n.º 12
0
void Steps::SetNoteData( const NoteData& noteDataNew )
{
    ASSERT( noteDataNew.GetNumTracks() == GAMEMAN->GetStepsTypeInfo(m_StepsType).iNumTracks );

    DeAutogen( false );

    *m_pNoteData = noteDataNew;
    m_bNoteDataIsFilled = true;

    m_sNoteDataCompressed = RString();
    m_iHash = 0;
    m_sFilename = RString(); // We can no longer read from the file because it has changed in memory.
}
Ejemplo n.º 13
0
static StepsType DetermineStepsType( int iPlayer, const NoteData &nd, const RString &sPath )
{
	ASSERT( NUM_PMS_TRACKS == nd.GetNumTracks() );

	bool bTrackHasNote[NUM_NON_AUTO_KEYSOUND_TRACKS];
	ZERO( bTrackHasNote );

	int iLastRow = nd.GetLastRow();
	for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ )
	{
		for( int r=0; r<=iLastRow; r++ )
		{
			if( nd.GetTapNote(t, r).type != TapNote::empty )
			{
				bTrackHasNote[t] = true;
				break;				
			}
		}
	}

	int iNumNonEmptyTracks = 0;
	for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ )
		if( bTrackHasNote[t] )
			iNumNonEmptyTracks++;

	switch( iPlayer )
	{
	case 1:
		switch( iNumNonEmptyTracks ) 
		{
			case 5:		return StepsType_popn_five;
			case 9:		return StepsType_popn_nine;
			default:	return StepsType_Invalid;
		}
	default:
		LOG->UserLog( "Song file", sPath, "has an invalid #PLAYER value %d.", iPlayer );
		return StepsType_Invalid;
	}
}
Ejemplo n.º 14
0
void Style::GetTransformedNoteDataForStyle( PlayerNumber pn, const NoteData& original, NoteData& noteDataOut ) const
{
	ASSERT( pn >=0 && pn <= NUM_PLAYERS );

	int iNewToOriginalTrack[MAX_COLS_PER_PLAYER];
	for( int col=0; col<m_iColsPerPlayer; col++ )
	{
		ColumnInfo colInfo = m_ColumnInfo[pn][col];
		iNewToOriginalTrack[col] = colInfo.track;
	}

	noteDataOut.LoadTransformed( original, m_iColsPerPlayer, iNewToOriginalTrack );
}
Ejemplo n.º 15
0
static void Serialize( const NoteData &o, Json::Value &root )
{
	root = Json::Value(Json::arrayValue);
	for(int t=0; t < o.GetNumTracks(); t++ )
	{
		NoteData::TrackMap::const_iterator begin, end;
		o.GetTapNoteRange( t, 0, MAX_NOTE_ROW, begin, end );
		//NoteData::TrackMap tm = o.GetTrack(t);
		//FOREACHM_CONST( int, TapNote, tm, iter )
		for( ; begin != end; ++begin )
		{
			int iRow = begin->first;
			TapNote tn = begin->second;
			root.resize( root.size()+1 );
			Json::Value &root2 = root[ root.size()-1 ];
			root2 = Json::Value(Json::arrayValue);
			root2.resize(3);
			root2[(unsigned)0] = NoteRowToBeat(iRow);
			root2[1] = t;
			Serialize( tn, root2[2] );
		}
	}
}
Ejemplo n.º 16
0
void GameplayAssist::PlayTicks( const NoteData &nd, const PlayerState *ps )
{
	using std::max;
	bool bClap = GAMESTATE->m_SongOptions.GetCurrent().m_bAssistClap;
	bool bMetronome = GAMESTATE->m_SongOptions.GetCurrent().m_bAssistMetronome;
	if( !bClap  &&  !bMetronome )
		return;

	// don't play sounds for dead players
	if( ps->m_HealthState == HealthState_Dead )
		return;

	/* Sound cards have a latency between when a sample is Play()ed and when the sound
	 * will start coming out the speaker.  Compensate for this by boosting fPositionSeconds
	 * ahead.  This is just to make sure that we request the sound early enough for it to
	 * come out on time; the actual precise timing is handled by SetStartTime. */
	SongPosition &position = GAMESTATE->m_pPlayerState[ps->m_PlayerNumber]->m_Position;
	float fPositionSeconds = position.m_fMusicSeconds;

	//float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds;
	fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f;
	const TimingData &timing = *GAMESTATE->m_pCurSteps[ps->m_PlayerNumber]->GetTimingData();
	const float fSongBeat = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds );

	const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) );
	static int iRowLastCrossed = -1;
	if( iSongRow < iRowLastCrossed )
		iRowLastCrossed = iSongRow;

	if( bClap )
	{
		int iClapRow = -1;
		// for each index we crossed since the last update:
		FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( nd, r, iRowLastCrossed+1, iSongRow+1 )
			if( nd.IsThereATapOrHoldHeadAtRow( r ) )
				iClapRow = r;

		if( iClapRow != -1 && timing.IsJudgableAtRow(iClapRow))
		{
			const float fTickBeat = NoteRowToBeat( iClapRow );
			const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
			float fSecondsUntil = fTickSecond - position.m_fMusicSeconds;
			fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */

			RageSoundParams p;
			p.m_StartTime = position.m_LastBeatUpdate  + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
			m_soundAssistClap.Play(false, &p);
		}
	}

	if( bMetronome && iRowLastCrossed != -1 )
	{
		//iRowLastCrossed+1, iSongRow+1

		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( iLastCrossedMeasureIndex != iCurrentMeasureIndex  ||  iLastCrossedBeatIndex != iCurrentBeatIndex )
		{
			iMetronomeRow = iSongRow - iCurrentRowsRemainder;
			bIsMeasure = iCurrentBeatIndex == 0  &&  iCurrentRowsRemainder == 0;
		}

		if( iMetronomeRow != -1 )
		{
			const float fTickBeat = NoteRowToBeat( iMetronomeRow );
			const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat );
			float fSecondsUntil = fTickSecond - position.m_fMusicSeconds;
			fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */

			RageSoundParams p;
			p.m_StartTime = position.m_LastBeatUpdate  + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS);
			if( bIsMeasure )
				m_soundAssistMetronomeMeasure.Play(false, &p);
			else
				m_soundAssistMetronomeBeat.Play(false, &p);
		}
	}

	iRowLastCrossed = iSongRow;
}
Ejemplo n.º 17
0
static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out,
			     const MeasureToTimeSig_t &sigAdjustments, const map<RString,int> &idToKeySoundIndex )
{
	LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() );

	out.m_StepsType = StepsType_Invalid;

	// BMS player code.  Fill in below and use to determine StepsType.
	int iPlayer = -1;
	RString sData;
	if( GetTagFromMap( mapNameToData, "#player", sData ) )
		iPlayer = atoi(sData);
	if( GetTagFromMap( mapNameToData, "#playlevel", sData ) )
		out.SetMeter( atoi(sData) );

	NoteData ndNotes;
	ndNotes.SetNumTracks( NUM_BMS_TRACKS );

	/* Read time signatures.  Note that these can differ across files in the same
	 * song. */
	MeasureToTimeSig_t mapMeasureToTimeSig;
	ReadTimeSigs( mapNameToData, mapMeasureToTimeSig );

	int iHoldStarts[NUM_BMS_TRACKS];
	int iHoldPrevs[NUM_BMS_TRACKS];
	
	for( int i = 0; i < NUM_BMS_TRACKS; ++i )
	{
		iHoldStarts[i] = -1;
		iHoldPrevs[i] = -1;
	}
	
	NameToData_t::const_iterator it;
	for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it )
	{
		const RString &sName = it->first;
		if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1, 5) ) )
			 continue;

		// this is step or offset data.  Looks like "#00705"
		int iMeasureNo = atoi( sName.substr(1,3).c_str() );
		int iRawTrackNum = atoi( sName.substr(4,2).c_str() );
		int iRowNo = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
		float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustments );
		const RString &sNoteData = it->second;

		vector<TapNote> vTapNotes;
		for( size_t i=0; i+1<sNoteData.size(); i+=2 )
		{
			RString sNoteId = sNoteData.substr( i, 2 );
			if( sNoteId != "00" )
			{
				vTapNotes.push_back( TAP_ORIGINAL_TAP );
				map<RString,int>::const_iterator it = idToKeySoundIndex.find( sNoteId );
				if( it != idToKeySoundIndex.end() )
					vTapNotes.back().iKeysoundIndex = it->second;
			}
			else
			{
				vTapNotes.push_back( TAP_EMPTY );
			}
		}

		const unsigned iNumNotesInThisMeasure = vTapNotes.size();
		for( unsigned j=0; j<iNumNotesInThisMeasure; j++ )
		{
			float fPercentThroughMeasure = (float)j/(float)iNumNotesInThisMeasure;
			
			int row = iRowNo + lrintf( fPercentThroughMeasure * fBeatsPerMeasure * ROWS_PER_BEAT );
			
			// some BMS files seem to have funky alignment, causing us to write gigantic cache files.
			// Try to correct for this by quantizing.
			row = Quantize( row, ROWS_PER_MEASURE/64 );
			
			BmsTrack bmsTrack;
			bool bIsHold;
			if( ConvertRawTrackToTapNote(iRawTrackNum, bmsTrack, bIsHold) )
			{
				TapNote &tn = vTapNotes[j];
				if( tn.type != TapNote::empty )
				{
					if( bmsTrack == BMS_AUTO_KEYSOUND_1 )
					{
						// shift the auto keysound as far right as possible
						int iLastEmptyTrack = -1;
						if( ndNotes.GetTapLastEmptyTrack(row, iLastEmptyTrack)  &&
						    iLastEmptyTrack >= BMS_AUTO_KEYSOUND_1 )
						{
							tn.type = TapNote::autoKeysound;
							bmsTrack = (BmsTrack)iLastEmptyTrack;
						}
						else
						{
							// no room for this note.  Drop it.
							continue;
						}
					}
					else if( bIsHold )
					{
						if( iHoldStarts[bmsTrack] == -1 )
						{
							// Start of a hold.
							iHoldStarts[bmsTrack] = row;
							iHoldPrevs[bmsTrack] = row;
						}
						else
						{
							// We're continuing a hold.
							iHoldPrevs[bmsTrack] = row;
						}
						continue;
					}
				}
				if( iHoldStarts[bmsTrack] != -1 )
				{
					// This is ending a hold.
					const int iBegin = iHoldStarts[bmsTrack];
					const int iEnd = iHoldPrevs[bmsTrack];
					
					if( iBegin < iEnd )
						ndNotes.AddHoldNote( bmsTrack, iBegin, iEnd, TAP_ORIGINAL_HOLD_HEAD );
					else
						ndNotes.SetTapNote( bmsTrack, iBegin, TAP_ORIGINAL_TAP );
					iHoldStarts[bmsTrack] = -1;
					iHoldPrevs[bmsTrack] = -1;
				}
				// Don't bother inserting empty taps.
				if( tn.type != TapNote::empty )
					ndNotes.SetTapNote( bmsTrack, row, tn );
			}
		}
	}

	// We're done reading in all of the BMS values. Time to check for any unfinished holds.
	for( int iTrack = 0; iTrack < NUM_BMS_TRACKS; ++iTrack )
	{
		const int iBegin = iHoldStarts[iTrack];
		const int iEnd = iHoldPrevs[iTrack];
				
		if( iBegin == -1 )
			continue;
		if( iBegin < iEnd )
			ndNotes.AddHoldNote( iTrack, iBegin, iEnd, TAP_ORIGINAL_HOLD_HEAD );
		else
			ndNotes.SetTapNote( iTrack, iBegin, TAP_ORIGINAL_TAP );
	}		

	out.m_StepsType = DetermineStepsType( iPlayer, ndNotes, sPath );
	if( out.m_StepsType == StepsType_beat_single5 && GetTagFromMap( mapNameToData, "#title", sData ) )
	{
		/* Hack: guess at 6-panel. */
		
		// extract the Steps description (looks like 'Music <BASIC>')
		const size_t iOpenBracket = sData.find_first_of( "<(" );
		const size_t iCloseBracket = sData.find_first_of( ">)", iOpenBracket );
		
		// if there's a 6 in the description, it's probably part of "6panel" or "6-panel"		
		if( sData.find('6', iOpenBracket) < iCloseBracket )
			out.m_StepsType = StepsType_dance_solo;
	}
	
	if( out.m_StepsType == StepsType_Invalid )
	{
		LOG->UserLog( "Song file", sPath, "has an unknown steps type" );
		return false;
	}


	// shift all of the autokeysound tracks onto the main tracks
	for( int t=BMS_AUTO_KEYSOUND_1; t<BMS_AUTO_KEYSOUND_1+NUM_AUTO_KEYSOUND_TRACKS; t++ )
	{
		FOREACH_NONEMPTY_ROW_IN_TRACK( ndNotes, t, row )
		{
			TapNote tn = ndNotes.GetTapNote( t, row );
			int iEmptyTrack;
			if( ndNotes.GetTapFirstEmptyTrack(row, iEmptyTrack) )
			{
				ndNotes.SetTapNote( iEmptyTrack, row, tn );
				ndNotes.SetTapNote( t, row, TAP_EMPTY );
			}
			else
			{
				LOG->UserLog( "Song file", sPath, "has no room to shift the autokeysound tracks." );
			}
		}
	}
Ejemplo n.º 18
0
static StepsType DetermineStepsType( int iPlayer, const NoteData &nd, const RString &sPath )
{
	ASSERT( NUM_BMS_TRACKS == nd.GetNumTracks() );

	bool bTrackHasNote[NUM_NON_AUTO_KEYSOUND_TRACKS];
	ZERO( bTrackHasNote );

	int iLastRow = nd.GetLastRow();
	for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ )
	{
		for( int r=0; r<=iLastRow; r++ )
		{
			if( nd.GetTapNote(t, r).type != TapNote::empty )
			{
				bTrackHasNote[t] = true;
				break;				
			}
		}
	}

	int iNumNonEmptyTracks = 0;
	for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ )
		if( bTrackHasNote[t] )
			iNumNonEmptyTracks++;

	switch( iPlayer )
	{
	case 1:	// "1 player"		
		/* Track counts:
		 * 4 - dance 4-panel
		 * 5 - pop 5-key
		 * 6 - dance 6-panel, beat 5-key
		 * 8 - beat 7-key
		 * 9 - popn 9-key */
		switch( iNumNonEmptyTracks ) 
		{
		case 4:		return StepsType_dance_single;
		case 5:		return StepsType_popn_five;
		case 6:
			// FIXME: There's no way to distinguish between these types.
			// They use the same tracks.  Assume it's a Beat type since they
			// are more common.
			//return StepsType_dance_solo;
			return StepsType_beat_single5;
		case 8:		return StepsType_beat_single7;
		case 9:		return StepsType_popn_nine;
		default:	return StepsType_Invalid;
		}
	case 2:	// couple/battle
		return StepsType_dance_couple;
	case 3:	// double
		/* Track counts:
		 * 8 - dance Double
		 * 12 - beat Double 5-key
		 * 16 - beat Double 7-key */
		switch( iNumNonEmptyTracks ) 
		{
		case 8:		return StepsType_beat_single7;
		case 12:	return StepsType_beat_double5;
		case 16:	return StepsType_beat_double7;
		default:	return StepsType_Invalid;
		}
	default:
		LOG->UserLog( "Song file", sPath, "has an invalid #PLAYER value %d.", iPlayer );
		return StepsType_Invalid;
	}
}
Ejemplo n.º 19
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;
}
Ejemplo n.º 20
0
void getNoteList(vector<char260>& song, vector<NoteData>& songdata, double mindur,
		int tonic) {
	songdata.resize(0);
	NoteData tempnote;
	int melstart = -1;
	int melstop  = -1;
	int i, j;
	int octave      = 0;
	int degree      = 0;
	int accidental  = 0;
	double duration = mindur;
	int bar    = 0;
	// int tuplet = 0;
	int major[8] = {-1, 0, 6, 12, 17, 23, 29, 35};
	// int oldstate  = -1;
	int state     = -1;
	int nextstate = -1;
	int phend = 0;
	int phnum = 0;
	int phstart = 0;
	int slend = 0;
	int slstart = 0;
	int tie = 0;

	getLineRange(song, "MEL", melstart, melstop);

	for (i=melstart; i<=melstop; i++) {
		if (song[i].c[0] == '\0' || song[i].c[1] == '\0' ||
			 song[i].c[2] == '\0' || song[i].c[3] == '\0') {
			cout << "Error: invalid line in MEL[]: " << song[i].c << endl;
			exit(1);
		}
		j = 4;
		phstart = 1;
		phend = 0;
		// Note Format: (+|-)*[0..7]_*\.*(  )?
		// ONADB
		// Order of data: Octave, Note, Accidental, Duration, Barline

		#define STATE_SLSTART -1
		#define STATE_OCTAVE   0
		#define STATE_NOTE     1
		#define STATE_ACC      2
		#define STATE_DUR      3
		#define STATE_BAR      4
		#define STATE_SLEND    5

		while (j < 200 && song[i].c[j] != '\0') {
			// oldstate = state;
			switch (song[i].c[j]) {
				// Octave information:
				case '-': octave--; state = STATE_OCTAVE; break;
				case '+': octave++; state = STATE_OCTAVE; break;

				// Duration information:
				case '_': duration *= 2.0; state = STATE_DUR; break;
				case '.': duration *= 1.5; state = STATE_DUR; break;

				// Accidental information:
				case 'b': accidental--; state = STATE_ACC;  break;
				case '#': accidental++; state = STATE_ACC;  break;

				// Note information:
				case '0': case '1': case '2': case '3': case '4':
				case '5': case '6': case '7':
					degree =  major[song[i].c[j] - '0'];
					state = STATE_NOTE;
					break;
				case 'O':
					degree =  major[0];
					state = STATE_NOTE;
					break;

				// Barline information:
				case ' ':
					state = STATE_BAR;
					if (song[i].c[j+1] == ' ') {
						bar = 1;
					}
					break;

				// Other information:
				case '{': slstart = 1;  state = STATE_SLSTART;  break;
				case '}': slend   = 1;  state = STATE_SLEND;    break;
				// case '(': tuplet  = 1;        break;
				// case ')': tuplet  = 0;        break;
				case '/':                     break;
				case ']':                     break;
//            case '>':                     break;   // unknown marker
//            case '<':                     break;   //
				case '^': tie = 1; state = STATE_NOTE; break;
				default : cout << "Error: unknown character " << song[i].c[j]
							      << " on the line: " << song[i].c << endl;
							 exit(1);
			}
			j++;
			switch (song[i].c[j]) {
				case '-': case '+': nextstate = STATE_OCTAVE; break;
				case 'O':
				case '0': case '1': case '2': case '3': case '4':
				case '5': case '6': case '7': nextstate = STATE_NOTE; break;
				case 'b': case '#': nextstate = STATE_ACC;    break;
				case '_': case '.': nextstate = STATE_DUR; break;
				case '{': nextstate = STATE_SLSTART; break;
				case '}': nextstate = STATE_SLEND; break;
				case '^': nextstate = STATE_NOTE; break;
				case ' ':
					 if (song[i].c[j+1] == ' ') nextstate = STATE_BAR;
					 else if (song[i].c[j+1] == '/') nextstate = -2;
					 break;
				case '\0':
					phend = 1;
				default: nextstate = -1;
			}

			if (nextstate < state ||
					((nextstate == STATE_NOTE) && (state == nextstate))) {
				 tempnote.clear();
				 if (degree < 0) { // rest
					 tempnote.pitch = -999;
				 } else {
					 tempnote.pitch = degree + 40*(octave + 4) + accidental + tonic;
				 }
				 if (tie) {
					 tempnote.pitch = songdata[(int)songdata.size()-1].pitch;
					 if (songdata[(int)songdata.size()-1].tieend) {
						 songdata[(int)songdata.size()-1].tiecont = 1;
						 songdata[(int)songdata.size()-1].tieend = 0;
					 } else {
						 songdata[(int)songdata.size()-1].tiestart = 1;
					 }
					 tempnote.tieend = 1;
				 }
				 tempnote.duration = duration;
				 tempnote.phend = phend;
				 tempnote.bar = bar;
				 tempnote.phstart = phstart;
				 tempnote.slstart = slstart;
				 tempnote.slend = slend;
				 if (nextstate == -2) {
					 tempnote.bar = 2;
					 tempnote.phend = 1;
				 }
				 tempnote.phnum = phnum;

				 songdata.push_back(tempnote);
				 duration = mindur;
				 degree = 0;
				 bar = 0;
				 tie = 0;
				 phend = 0;
				 phstart = 0;
				 slend = 0;
				 slstart = 0;
				 octave = 0;
				 accidental = 0;
				 if (nextstate == -2) {
					 return;
				 }
			}
		}
		phnum++;
	}

}
Ejemplo n.º 21
0
void NoteDataWithScoring::GetActualRadarValues(const NoteData &in,
        const PlayerStageStats &pss, float song_seconds, RadarValues& out)
{
    using std::max;
    // Anybody editing this function should also examine
    // NoteDataUtil::CalculateRadarValues to make sure it handles things the
    // same way.
    // Some of this logic is similar or identical to
    // NoteDataUtil::CalculateRadarValues because I couldn't figure out a good
    // way to combine them into one. -Kyz
    PlayerNumber pn= pss.m_player_number;
    garv_state state;

    NoteData::all_tracks_const_iterator curr_note=
        in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW);
    TimingData* timing= GAMESTATE->GetProcessedTimingData();
    // first_hittable_row and last_hittable_row exist so that
    // GetActualVoltageRadarValue can be passed the correct song length.
    // GetActualVoltageRadarValue scores based on the max combo, a full combo
    // is a full voltage score.  The song length is used instead of trying to
    // figure out the max combo for the song because rolls mean there isn't a
    // limit to the max combo. -Kyz
    int first_hittable_row= -1;
    int last_hittable_row= -1;
    bool tick_holds= GAMESTATE->GetCurrentGame()->m_bTickHolds;

    while(!curr_note.IsAtEnd())
    {
        if(curr_note.Row() != state.curr_row)
        {
            DoRowEndRadarActualCalc(state);
            state.curr_row= curr_note.Row();
            state.num_notes_on_curr_row= 0;
            state.num_holds_on_curr_row= 0;
            state.judgable= timing->IsJudgableAtRow(state.curr_row);
            for(size_t n= 0; n < state.hold_ends.size(); ++n)
            {
                if(state.hold_ends[n].end_row < state.curr_row)
                {
                    state.hold_ends.erase(state.hold_ends.begin() + n);
                    --n;
                }
            }
            state.last_tns_on_row= TapNoteScore_Invalid;
            state.last_time_on_row= -9999;
            state.worst_tns_on_row= TapNoteScore_Invalid;
        }
        bool for_this_player= curr_note->pn == pn || pn == PLAYER_INVALID ||
                              curr_note->pn == PLAYER_INVALID;
        if(state.judgable && for_this_player)
        {
            switch(curr_note->type)
            {
            case TapNoteType_HoldTail:
                // If there are tick holds, then the hold tail needs to be counted
                // in last_hittable_row because that's where the combo will end.
                // -Kyz
                if(tick_holds)
                {
                    UpdateHittable(state.curr_row, first_hittable_row, last_hittable_row);
                }
                break;
            case TapNoteType_Tap:
            case TapNoteType_HoldHead:
            // HoldTails and Attacks are counted by IsTap.  But it doesn't
            // make sense to count HoldTails as hittable notes. -Kyz
            case TapNoteType_Attack:
            case TapNoteType_Lift:
                UpdateHittable(state.curr_row, first_hittable_row, last_hittable_row);
                ++state.num_notes_on_curr_row;
                state.notes_hit_for_stream+= (curr_note->result.tns >= state.stream_tns);
                state.notes_hit+= (curr_note->result.tns >= state.taps_tns);
                if(curr_note->result.tns < state.worst_tns_on_row)
                {
                    state.worst_tns_on_row= curr_note->result.tns;
                }
                if(curr_note->result.fTapNoteOffset > state.last_time_on_row)
                {
                    state.last_time_on_row= curr_note->result.fTapNoteOffset;
                    state.last_tns_on_row= curr_note->result.tns;
                }
                if(curr_note->type == TapNoteType_HoldHead)
                {
                    if(curr_note->subType == TapNoteSubType_Hold)
                    {
                        state.holds_held+= (curr_note->HoldResult.hns == HNS_Held);
                    }
                    else if(curr_note->subType == TapNoteSubType_Roll)
                    {
                        state.rolls_held+= (curr_note->HoldResult.hns == HNS_Held);
                    }
                    state.hold_ends.push_back(
                        hold_status(state.curr_row + curr_note->iDuration,
                                    curr_note->HoldResult.iLastHeldRow));
                    ++state.num_holds_on_curr_row;
                }
                else if(curr_note->type == TapNoteType_Lift)
                {
                    state.lifts_hit+= (curr_note->result.tns >= state.lifts_tns);
                }
                break;
            case TapNoteType_Mine:
                state.mines_avoided+= (curr_note->result.tns == TNS_AvoidMine);
                break;
            case TapNoteType_Fake:
            default:
                break;
            }
        }
        ++curr_note;
    }
    DoRowEndRadarActualCalc(state);

    // ScreenGameplay passes in the RadarValues that were calculated by
    // NoteDataUtil::CalculateRadarValues, so those are reused here. -Kyz
    int note_count = static_cast<int>(out[RadarCategory_Notes]);
    int jump_count = static_cast<int>(out[RadarCategory_Jumps]);
    int hold_count = static_cast<int>(out[RadarCategory_Holds]);
    float hittable_steps_length = max(0.f,
                                      timing->GetElapsedTimeFromBeat(NoteRowToBeat(last_hittable_row)) -
                                      timing->GetElapsedTimeFromBeat(NoteRowToBeat(first_hittable_row)));
    // The for loop and the assert are used to ensure that all fields of
    // RadarValue get set in here.
    FOREACH_ENUM(RadarCategory, rc)
    {
        switch(rc)
        {
        case RadarCategory_Stream:
            out[rc]= Rage::clamp(float(state.notes_hit_for_stream) / note_count, 0.0f, 1.0f);
            break;
        case RadarCategory_Voltage:
            out[rc]= GetActualVoltageRadarValue(in, hittable_steps_length, pss);
            break;
        case RadarCategory_Air:
            out[rc]= Rage::clamp(float(state.jumps_hit_for_air) / jump_count, 0.0f, 1.0f);
            break;
        case RadarCategory_Freeze:
            out[rc]= Rage::clamp(float(state.holds_held) / hold_count, 0.0f, 1.0f);
            break;
        case RadarCategory_Chaos:
            out[rc]= GetActualChaosRadarValue(in, song_seconds, pss);
            break;
        case RadarCategory_TapsAndHolds:
            out[rc] = static_cast<float>(state.taps_hit);
            break;
        case RadarCategory_Jumps:
            out[rc] = static_cast<float>(state.jumps_hit);
            break;
        case RadarCategory_Holds:
            out[rc] = static_cast<float>(state.holds_held);
            break;
        case RadarCategory_Mines:
            out[rc] = static_cast<float>(state.mines_avoided);
            break;
        case RadarCategory_Hands:
            out[rc] = static_cast<float>(state.hands_hit);
            break;
        case RadarCategory_Rolls:
            out[rc] = static_cast<float>(state.rolls_held);
            break;
        case RadarCategory_Lifts:
            out[rc] = static_cast<float>(state.lifts_hit);
            break;
        case RadarCategory_Fakes:
            out[rc] = out[rc];
            break;
        case RadarCategory_Notes:
            out[rc] = static_cast<float>(state.notes_hit);
            break;
            DEFAULT_FAIL(rc);
        }
    }
}
Ejemplo n.º 22
0
static void WriteDWINotesField( RageFile &f, const Steps &out, int start )
{
	NoteData notedata;
	out.GetNoteData( notedata );
	NoteDataUtil::InsertHoldTails( notedata );

	const int iLastMeasure = int( notedata.GetLastBeat()/BEATS_PER_MEASURE );
	for( int m=0; m<=iLastMeasure; m++ )	// foreach measure
	{
		NoteType nt = NoteDataUtil::GetSmallestNoteTypeForMeasure( notedata, m );

		double fCurrentIncrementer = 0;
		switch( nt )
		{
		case NOTE_TYPE_4TH:
		case NOTE_TYPE_8TH:	
			fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
			break;
		case NOTE_TYPE_12TH:
		case NOTE_TYPE_24TH:
			f.Write( "[" );
			fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE;
			break;
		case NOTE_TYPE_16TH:
			f.Write( "(" );
			fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE;
			break;
		case NOTE_TYPE_32ND:
		case NOTE_TYPE_64TH:
			f.Write( "{" );
			fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE;
			break;
		case NOTE_TYPE_48TH:
		case NOTE_TYPE_192ND:
		case NoteType_Invalid:
			// since, for whatever reason, the only way to do
			// 48ths is through a block of 192nds...
			f.Write(  "`" );
			fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
			break;
		default:
			ASSERT_M(0, ssprintf("nt = %d",nt) );
			break;
		}

		double fFirstBeatInMeasure = m * BEATS_PER_MEASURE;
		double fLastBeatInMeasure = (m+1) * BEATS_PER_MEASURE;

		for( double b=fFirstBeatInMeasure; b<=fLastBeatInMeasure-1/64.0f; b+=fCurrentIncrementer )	// need the -0.0001 to account for rounding errors
		{
			int row = BeatToNoteRow( (float)b );

			RString str;
			switch( out.m_StepsType )
			{
			case StepsType_dance_single:
			case StepsType_dance_couple:
			case StepsType_dance_double:
				str = NotesToDWIString( 
					notedata.GetTapNote(start+0, row), 
					notedata.GetTapNote(start+1, row),
					notedata.GetTapNote(start+2, row),
					notedata.GetTapNote(start+3, row) );

				// Blank out the notes so we don't write them again if the incrementer is small
				notedata.SetTapNote(start+0, row, TAP_EMPTY);
				notedata.SetTapNote(start+1, row, TAP_EMPTY);
				notedata.SetTapNote(start+2, row, TAP_EMPTY);
				notedata.SetTapNote(start+3, row, TAP_EMPTY);
				break;
			case StepsType_dance_solo:
				str = NotesToDWIString( 
					notedata.GetTapNote(0, row),
					notedata.GetTapNote(1, row),
					notedata.GetTapNote(2, row),
					notedata.GetTapNote(3, row),
					notedata.GetTapNote(4, row),
					notedata.GetTapNote(5, row) );

				// Blank out the notes so we don't write them again if the incrementer is small
				notedata.SetTapNote(start+0, row, TAP_EMPTY);
				notedata.SetTapNote(start+1, row, TAP_EMPTY);
				notedata.SetTapNote(start+2, row, TAP_EMPTY);
				notedata.SetTapNote(start+3, row, TAP_EMPTY);
				notedata.SetTapNote(start+4, row, TAP_EMPTY);
				notedata.SetTapNote(start+5, row, TAP_EMPTY);
				break;
			default:
				ASSERT(0);	// not a type supported by DWI.  We shouldn't have called in here if that's the case
			}
			f.Write( str );
		}

		switch( nt )
		{
		case NOTE_TYPE_4TH:
		case NOTE_TYPE_8TH:	
			break;
		case NOTE_TYPE_12TH:
		case NOTE_TYPE_24TH:
			f.Write( "]" );
			break;
		case NOTE_TYPE_16TH:
			f.Write( ")" );
			break;
		case NOTE_TYPE_32ND:
		case NOTE_TYPE_64TH:
			f.Write( "}" );
			break;
		case NOTE_TYPE_48TH:
		case NOTE_TYPE_192ND:
		case NoteType_Invalid:
			f.Write( "'" );
			break;
		default:
			ASSERT(0);
			// fall though
		}
		f.PutLine( "" );
	}
}
Ejemplo n.º 23
0
static NoteData ParseNoteData(RString &step1, RString &step2,
			      Steps &out, const RString &path)
{
	g_mapDanceNoteToNoteDataColumn.clear();
	switch( out.m_StepsType )
	{
		case StepsType_dance_single:
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3;
			break;
		case StepsType_dance_double:
		case StepsType_dance_couple:
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7;
			break;
		case StepsType_dance_solo:
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4;
			g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5;
			break;
			DEFAULT_FAIL( out.m_StepsType );
	}
	
	NoteData newNoteData;
	newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() );
	
	for( int pad=0; pad<2; pad++ )		// foreach pad
	{
		RString sStepData;
		switch( pad )
		{
			case 0:
				sStepData = step1;
				break;
			case 1:
				if( step2 == "" )	// no data
					continue;	// skip
				sStepData = step2;
				break;
				DEFAULT_FAIL( pad );
		}
		
		sStepData.Replace("\n", "");
		sStepData.Replace("\r", "");
		sStepData.Replace("\t", "");
		sStepData.Replace(" ", "");
		
		double fCurrentBeat = 0;
		double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
		
		for( size_t i=0; i<sStepData.size(); )
		{
			char c = sStepData[i++];
			switch( c )
			{
					// begins a series
				case '(':
					fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE;
					break;
				case '[':
					fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE;
					break;
				case '{':
					fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE;
					break;
				case '`':
					fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
					break;
					
					// ends a series
				case ')':
				case ']':
				case '}':
				case '\'':
				case '>':
					fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
					break;
					
				default:	// this is a note character
				{
					if( c == '!' )
					{
						LOG->UserLog(
							     "Song file",
							     path,
							     "has an unexpected character: '!'." );
						continue;
					}
					
					bool jump = false;
					if( c == '<' )
					{
						/* Arr.  Is this a jump or a 1/192 marker? */
						if( Is192( sStepData, i ) )
						{
							fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
							break;
						}
						
						/* It's a jump.
						 * We need to keep reading notes until we hit a >. */
						jump = true;
						i++;
					}
					
					const int iIndex = BeatToNoteRow( (float)fCurrentBeat );
					i--;
					do {
						c = sStepData[i++];
						
						if( jump && c == '>' )
							break;
						
						int iCol1, iCol2;
						DWIcharToNoteCol(
								 c,
								 (GameController)pad,
								 iCol1,
								 iCol2,
								 path );
						
						if( iCol1 != -1 )
							newNoteData.SetTapNote(iCol1,
									       iIndex,
									       TAP_ORIGINAL_TAP);
						if( iCol2 != -1 )
							newNoteData.SetTapNote(iCol2,
									       iIndex,
									       TAP_ORIGINAL_TAP);
						
						if(i>=sStepData.length())
						{
							break;
							//we ran out of data
							//while looking for the ending > mark
						}
						
						if( sStepData[i] == '!' )
						{
							i++;
							const char holdChar = sStepData[i++];
							
							DWIcharToNoteCol(holdChar,
									 (GameController)pad,
									 iCol1,
									 iCol2,
									 path );
							
							if( iCol1 != -1 )
								newNoteData.SetTapNote(iCol1,
										       iIndex,
										       TAP_ORIGINAL_HOLD_HEAD);
							if( iCol2 != -1 )
								newNoteData.SetTapNote(iCol2,
										       iIndex,
										       TAP_ORIGINAL_HOLD_HEAD);
						}
					}
					while( jump );
					fCurrentBeat += fCurrentIncrementer;
				}
					break;
			}
		}
	}
	
	/* Fill in iDuration. */
	for( int t=0; t<newNoteData.GetNumTracks(); ++t )
	{
		FOREACH_NONEMPTY_ROW_IN_TRACK( newNoteData, t, iHeadRow )
		{
			TapNote tn = newNoteData.GetTapNote( t, iHeadRow  );
			if( tn.type != TapNote::hold_head )
				continue;
			
			int iTailRow = iHeadRow;
			bool bFound = false;
			while( !bFound && newNoteData.GetNextTapNoteRowForTrack(t, iTailRow) )
			{
				const TapNote &TailTap = newNoteData.GetTapNote( t, iTailRow );
				if( TailTap.type == TapNote::empty )
					continue;
				
				newNoteData.SetTapNote( t, iTailRow, TAP_EMPTY );
				tn.iDuration = iTailRow - iHeadRow;
				newNoteData.SetTapNote( t, iHeadRow, tn );
				bFound = true;
			}
			
			if( !bFound )
			{
				/* The hold was never closed.  */
				LOG->UserLog("Song file",
					     path,
					     "failed to close a hold note in \"%s\" on track %i", 
					     DifficultyToString(out.GetDifficulty()).c_str(),
					     t);
				
				newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY );
			}
		}
	}
Ejemplo n.º 24
0
void VibratoTimeAxis::doUpdate()
{
  Channel *active = gdata->getActiveChannel();

  int myStartChunk = -1;
  int myCurrentChunk = -1;
  int myEndChunk = -1;
  double myNoteLength = 0.0;
  int myWindowOffset = -999999;

  if (active) {
    AnalysisData *data = active->dataAtCurrentChunk();
    if(data && active->isVisibleNote(data->getNoteIndex()) && active->isLabelNote(data->getNoteIndex())) {
      NoteData *note = new NoteData();
      note = &active->noteData[data->getNoteIndex()];

      myStartChunk = note->startChunk();
      myCurrentChunk = active->chunkAtCurrentTime();
      myEndChunk = note->endChunk();
      myNoteLength = note->noteLength();

      // Calculate windowoffset
      if ((myEndChunk - myStartChunk) * zoomFactorX > width() - 2 * noteLabelOffset) {
        // The vibrato-polyline doesn't fit in the window
        if ((myCurrentChunk - myStartChunk) * zoomFactorX < (width() - 2 * noteLabelOffset)/2) {
          // We're at the left side of the vibrato-polyline
          myWindowOffset = 0 - noteLabelOffset;
        } else if ((myEndChunk - myCurrentChunk) * zoomFactorX < (width() - 2 * noteLabelOffset)/2) {
          // We're at the right side of the vibrato-polyline
          myWindowOffset = toInt((myEndChunk - myStartChunk) * zoomFactorX - width() + noteLabelOffset + 1);
        } else {
          // We're somewhere in the middle of the vibrato-polyline
          myWindowOffset = toInt((myCurrentChunk - myStartChunk) * zoomFactorX - width()/2);
        }
      } else {
        // The vibrato-polyline does fit in the window
        myWindowOffset = 0 - noteLabelOffset;
      }
    }
  }

  if (myCurrentChunk == -1) {
    // No note
    if (prevCurrentChunk == myCurrentChunk) {
      // Still no timeaxis needed, no update needed

    } else {
      // Timeaxis should be erased, update widget

      prevCurrentChunk = -1;
      prevWindowOffset = -999999;

      currentChunkToUse = -1;
    }
  } else {
    // Note
    if (prevWindowOffset == myWindowOffset) {
      // No movement, don't redraw timeaxis

    } else {
      // Position in note to draw has changed, so draw the timeaxis

      prevCurrentChunk = myCurrentChunk;
      prevWindowOffset = myWindowOffset;

      startChunkToUse = myStartChunk;
      currentChunkToUse = myCurrentChunk;
      endChunkToUse = myEndChunk;
      noteLengthToUse = myNoteLength;
      windowOffsetToUse = myWindowOffset;
    }
  }
}
Ejemplo n.º 25
0
bool KSFLoader::LoadFromKSFFile( const CString &sPath, Steps &out, const Song &song )
{
    LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() );

    MsdFile msd;
    if( !msd.ReadFile( sPath ) )
        RageException::Throw( "Error opening file '%s'.", sPath.c_str() );

    int iTickCount = -1;	// this is the value we read for TICKCOUNT
    CStringArray asRows;

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

        // handle the data
        if( 0==stricmp(sValueName,"TICKCOUNT") )
            iTickCount = atoi(sParams[1]);

        else if( 0==stricmp(sValueName,"STEP") )
        {
            CString step = sParams[1];
            TrimLeft(step);
            split( step, "\n", asRows, true );
        }
        else if( 0==stricmp(sValueName,"DIFFICULTY") )
            out.SetMeter(atoi(sParams[1]));
    }

    if( iTickCount == -1 )
    {
        iTickCount = 2;
        LOG->Warn( "\"%s\": TICKCOUNT not found; defaulting to %i", sPath.c_str(), iTickCount );
    }

    NoteData notedata;	// read it into here

    {
        CString sDir, sFName, sExt;
        splitpath( sPath, sDir, sFName, sExt );
        sFName.MakeLower();

        out.SetDescription(sFName);
        if( sFName.Find("crazy")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_HARD);
            if(!out.GetMeter()) out.SetMeter(8);
        }
        else if( sFName.Find("hard")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_MEDIUM);
            if(!out.GetMeter()) out.SetMeter(5);
        }
        else if( sFName.Find("easy")!=-1 )
        {
            out.SetDifficulty(DIFFICULTY_EASY);
            if(!out.GetMeter()) out.SetMeter(2);
        }
        else
        {
            out.SetDifficulty(DIFFICULTY_MEDIUM);
            if(!out.GetMeter()) out.SetMeter(5);
        }

        notedata.SetNumTracks( 5 );
        out.m_StepsType = STEPS_TYPE_PUMP_SINGLE;

        /* Check for "halfdouble" before "double". */
        if( sFName.Find("halfdouble") != -1 || sFName.Find("h_double") != -1 )
        {
            notedata.SetNumTracks( 6 );
            out.m_StepsType = STEPS_TYPE_PUMP_HALFDOUBLE;
        }
        else if( sFName.Find("double") != -1 )
        {
            notedata.SetNumTracks( 10 );
            out.m_StepsType = STEPS_TYPE_PUMP_DOUBLE;
        }
        else if( sFName.Find("_2") != -1 )
        {
            notedata.SetNumTracks( 10 );
            out.m_StepsType = STEPS_TYPE_PUMP_COUPLE;
        }
    }

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

    for( unsigned r=0; r<asRows.size(); r++ )
    {
        CString& sRowString = asRows[r];
        StripCrnl( sRowString );

        if( sRowString == "" )
            continue;	// skip

        /* All 2s indicates the end of the song. */
        if( sRowString == "2222222222222" )
            break;

        if(sRowString.size() != 13)
        {
            LOG->Warn("File %s had a RowString with an improper length (\"%s\"); corrupt notes ignored",
                      sPath.c_str(), sRowString.c_str());
            return false;
        }

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

        // the length of a note in a row depends on TICKCOUNT
        float fBeatThisRow = r/(float)iTickCount;
        int row = BeatToNoteRow(fBeatThisRow);
        for( int t=0; t < notedata.GetNumTracks(); t++ )
        {
            if( sRowString[t] == '4' )
            {
                /* Remember when each hold starts; ignore the middle. */
                if( iHoldStartRow[t] == -1 )
                    iHoldStartRow[t] = r;

                continue;
            }

            if( iHoldStartRow[t] != -1 )	// this ends the hold
            {
                HoldNote hn (
                    t, /* button */
                    BeatToNoteRow(iHoldStartRow[t]/(float)iTickCount), /* start */
                    BeatToNoteRow((r-1)/(float)iTickCount) /* end */
                );
                notedata.AddHoldNote( hn );
                iHoldStartRow[t] = -1;
            }

            TapNote tap;
            switch(sRowString[t])
            {
            case '0':
                tap = TAP_EMPTY;
                break;
            case '1':
                tap = TAP_ORIGINAL_TAP;
                break;
            default:
                ASSERT(0);
                tap = TAP_EMPTY;
                break;
            }

            notedata.SetTapNote(t, row, tap);
        }
    }

    /* We need to remove holes where the BPM increases. */
//	if( song.m_Timing.m_BPMSegments.size() > 1 )
//		RemoveHoles( notedata, song );

    out.SetNoteData(&notedata);

    out.TidyUpData();

    return true;
}