Ejemplo n.º 1
0
float Profile::GetSongsPossible( StepsType st, Difficulty dc ) const
{
	int iTotalSteps = 0;

	// add steps high scores
	const vector<Song*> vSongs = SONGMAN->GetAllSongs();
	for( unsigned i=0; i<vSongs.size(); i++ )
	{
		Song* pSong = vSongs[i];
		
		if( pSong->m_SelectionDisplay == Song::SHOW_NEVER )
			continue;	// skip

		vector<Steps*> vSteps = pSong->GetAllSteps();
		for( unsigned j=0; j<vSteps.size(); j++ )
		{
			Steps* pSteps = vSteps[j];
			
			if( pSteps->m_StepsType != st )
				continue;	// skip

			if( pSteps->GetDifficulty() != dc )
				continue;	// skip

			iTotalSteps++;
		}
	}

	return (float) iTotalSteps;
}
Ejemplo n.º 2
0
Steps* SongUtil::GetClosestNotes( const Song *pSong, StepsType st, Difficulty dc, bool bIgnoreLocked )
{
    ASSERT( dc != Difficulty_Invalid );

    const vector<Steps*>& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st);
    Steps *pClosest = NULL;
    int iClosestDistance = 999;
    for( unsigned i=0; i<vpSteps.size(); i++ )	// for each of the Song's Steps
    {
        Steps* pSteps = vpSteps[i];

        if( pSteps->GetDifficulty() == Difficulty_Edit && dc != Difficulty_Edit )
            continue;
        if( bIgnoreLocked && UNLOCKMAN->StepsIsLocked(pSong,pSteps) )
            continue;

        int iDistance = abs(dc - pSteps->GetDifficulty());
        if( iDistance < iClosestDistance )
        {
            pClosest = pSteps;
            iClosestDistance = iDistance;
        }
    }

    return pClosest;
}
Ejemplo n.º 3
0
static bool WriteDWINotesTag( RageFile &f, const Steps &out )
{
	if( out.GetDifficulty() == Difficulty_Edit )
		return false;	// not supported by DWI

	LOG->Trace( "Steps::WriteDWINotesTag" );

	switch( out.m_StepsType )
	{
	case StepsType_dance_single:	f.Write( "#SINGLE:" );	break;
	case StepsType_dance_couple:	f.Write( "#COUPLE:" );	break;
	case StepsType_dance_double:	f.Write( "#DOUBLE:" );	break;
	case StepsType_dance_solo:	f.Write( "#SOLO:" );	break;
	default:	return false;	// not a type supported by DWI
	}

	switch( out.GetDifficulty() )
	{
	case Difficulty_Beginner:	f.Write( "BEGINNER:" ); break;
	case Difficulty_Easy:		f.Write( "BASIC:" );	break;
	case Difficulty_Medium:		f.Write( "ANOTHER:" );	break;
	case Difficulty_Hard:		f.Write( "MANIAC:" );	break;
	case Difficulty_Challenge:	f.Write( "SMANIAC:" );	break;
	default:	ASSERT(0);	return false;
	}

	f.PutLine( ssprintf("%d:", out.GetMeter()) );
	return true;
}
Ejemplo n.º 4
0
const TimingData &PlayerState::GetDisplayedTiming() const
{
	Steps *steps = GAMESTATE->m_pCurSteps[m_PlayerNumber];
	if( steps == NULL )
		return GAMESTATE->m_pCurSong->m_SongTiming;
	return *steps->GetTimingData();
}
Ejemplo n.º 5
0
void ScreenEditMenu::HandleScreenMessage( const ScreenMessage SM )
{
	if( SM == SM_RefreshSelector )
	{
		m_Selector.RefreshAll();
		RefreshNumStepsLoadedFromProfile();
	}
	else if( SM == SM_Success && m_Selector.GetSelectedAction() == EditMenuAction_Delete )
	{
		LOG->Trace( "Delete successful; deleting steps from memory" );

		Song* pSong = GAMESTATE->m_pCurSong;
		Steps* pStepsToDelete = GAMESTATE->m_pCurSteps[PLAYER_1];
		FOREACH_PlayerNumber(pn)
		{
			GAMESTATE->m_pCurSteps[pn].Set(NULL);
		}
		bool bSaveSong = !pStepsToDelete->WasLoadedFromProfile();
		pSong->DeleteSteps( pStepsToDelete );
		SONGMAN->Invalidate( pSong );

		/* Only save to the main .SM file if the steps we're deleting
		 * were loaded from it. */
		if( bSaveSong )
		{
			pSong->Save();
			SCREENMAN->ZeroNextUpdate();
		}
		SCREENMAN->SendMessageToTopScreen( SM_RefreshSelector );
	}
Ejemplo n.º 6
0
void StepsUtil::SortStepsPointerArrayByNumPlays( vector<Steps*> &vStepsPointers, const Profile* pProfile, bool bDecending )
{
	// ugly...
	vector<Song*> vpSongs = SONGMAN->GetAllSongs();
	vector<Steps*> vpAllSteps;
	map<Steps*,Song*> mapStepsToSong;
	{
		for( unsigned i=0; i<vpSongs.size(); i++ )
		{
			Song* pSong = vpSongs[i];
			vector<Steps*> vpSteps = pSong->GetAllSteps();
			for( unsigned j=0; j<vpSteps.size(); j++ )
			{
				Steps* pSteps = vpSteps[j];
				if( pSteps->IsAutogen() )
					continue;	// skip
				vpAllSteps.push_back( pSteps );
				mapStepsToSong[pSteps] = pSong;
			}
		}
	}

	ASSERT( pProfile != NULL );
	for(unsigned i = 0; i < vStepsPointers.size(); ++i)
	{
		Steps* pSteps = vStepsPointers[i];
		Song* pSong = mapStepsToSong[pSteps];
		steps_sort_val[vStepsPointers[i]] = ssprintf("%9i", pProfile->GetStepsNumTimesPlayed(pSong,pSteps));
	}
	stable_sort( vStepsPointers.begin(), vStepsPointers.end(), bDecending ? CompareStepsPointersBySortValueDescending : CompareStepsPointersBySortValueAscending );
	steps_sort_val.clear();
}
Ejemplo n.º 7
0
int main( int argc, char** argv ) {
    Steps* s;
    s = new Steps( argv[1], argv[2], argv[3], 1000 );
    WorkUnit* wu;
    while ( (wu = s->newWorkUnit()) != NULL ) {
	wu->print( stdout );
	delete wu;
    }
}
Ejemplo n.º 8
0
void Calibration::SignalPath::add_step (Scalar* function, double step)
{
  Steps* steps = dynamic_cast<Steps*> (function);
  if (steps)
  {
#ifdef _DEBUG
    cerr << "Calibration::SignalPath::add_step step=" << step << endl;
#endif
    steps->add_step (step);
  }
}
Ejemplo n.º 9
0
void SongUtil::SortSongPointerArrayByMeter( vector<Song*> &arraySongPointers, Difficulty dc )
{
	song_sort_val.clear();
	for(unsigned i = 0; i < arraySongPointers.size(); ++i)
	{
		Steps* pSteps = arraySongPointers[i]->GetStepsByDifficulty( GAMESTATE->GetCurrentStyle()->m_StepsType, dc );
		CString &s = song_sort_val[arraySongPointers[i]];
		s = ssprintf("%03d", pSteps ? pSteps->GetMeter() : 0);
		if( PREFSMAN->m_bSubSortByNumSteps )
			s += ssprintf("%06.0f",pSteps ? pSteps->GetRadarValues()[RADAR_NUM_TAPS_AND_HOLDS] : 0);
	}
	stable_sort( arraySongPointers.begin(), arraySongPointers.end(), CompareSongPointersBySortValueAscending );
}
Ejemplo n.º 10
0
float Profile::GetSongsActual( StepsType st, Difficulty dc ) const
{
	float fTotalPercents = 0;
	
	// add steps high scores
	for( std::map<SongID,HighScoresForASong>::const_iterator i = m_SongHighScores.begin();
		i != m_SongHighScores.end();
		++i )
	{
		const SongID &id = i->first;
		Song* pSong = id.ToSong();
		
		// If the Song isn't loaded on the current machine, then we can't 
		// get radar values to compute dance points.
		if( pSong == NULL )
			continue;

		if( pSong->m_SelectionDisplay == Song::SHOW_NEVER )
			continue;	// skip

		const HighScoresForASong &hsfas = i->second;

		for( std::map<StepsID,HighScoresForASteps>::const_iterator j = hsfas.m_StepsHighScores.begin();
			j != hsfas.m_StepsHighScores.end();
			++j )
		{
			const StepsID &id = j->first;
			Steps* pSteps = id.ToSteps( pSong, true );
			
			// If the Steps isn't loaded on the current machine, then we can't 
			// get radar values to compute dance points.
			if( pSteps == NULL )
				continue;

			if( pSteps->m_StepsType != st )
				continue;

			if( pSteps->GetDifficulty() != dc )
				continue;	// skip

			const HighScoresForASteps& h = j->second;
			const HighScoreList& hs = h.hs;

			fTotalPercents += hs.GetTopScore().fPercentDP;
		}
	}

	return fTotalPercents;
}
Ejemplo n.º 11
0
bool KSFLoader::LoadNoteDataFromSimfile( const std::string & cachePath, Steps &out )
{
	bool KIUCompliant = false;
	Song dummy;
	if (!LoadGlobalData(cachePath, dummy, KIUCompliant))
		return false;
	Steps *notes = dummy.CreateSteps();
	if (LoadFromKSFFile(cachePath, *notes, dummy, KIUCompliant))
	{
		KIUCompliant = true; // yeah, reusing a variable.
		out.SetNoteData(notes->GetNoteData());
	}
	delete notes;
	return KIUCompliant;
}
Ejemplo n.º 12
0
void Calibration::SignalPath::add_step (Scalar* function, const MJD& mjd)
{
  Steps* steps = dynamic_cast<Steps*> (function);
  if (!steps)
    throw Error (InvalidState, "Calibration::SignalPath::add_step",
		 "function is not a Steps");

  MJD zero;
  if (convert.get_reference_epoch() == zero)
    convert.set_reference_epoch ( mjd );

  time.set_value (mjd);
  double step = convert.get_value();

  steps->add_step (step);
}
Ejemplo n.º 13
0
bool KSFLoader::LoadFromDir( const std::string &sDir, Song &out )
{
	LOG->Trace( "KSFLoader::LoadFromDir(%s)", sDir.c_str() );

	vector<std::string> arrayKSFFileNames;
	GetDirListing( sDir + "*.ksf", arrayKSFFileNames );

	// We shouldn't have been called to begin with if there were no KSFs.
	ASSERT( arrayKSFFileNames.size() != 0 );

	bool bKIUCompliant = false;
	/* With Split Timing, there has to be a backup Song Timing in case
	 * anything goes wrong. As these files are kept in alphabetical
	 * order (hopefully), it is best to use the LAST file for timing
	 * purposes, for that is the "normal", or easiest difficulty.
	 * Usually. */
	// Nevermind, kiu compilancy is screwing things up:
	// IE, I have two simfiles, oh wich each have four ksf files, the first one has
	// the first ksf with directmove timing changes, and the rest are not, everything
	// goes fine. In the other hand I have my second simfile with the first ksf file
	// without directmove timing changes and the rest have changes, changes are not
	// loaded due to kiucompilancy in the first ksf file.
	// About the "normal" thing, my simfiles' ksfs uses non-standard naming so
	// the last chart is usually nightmare or normal, I use easy and normal
	// indistinctly for SM so it shouldn't matter, I use piu fiesta/ex naming
	// for directmove though, and we're just gathering basic info anyway, and
	// most of the time all the KSF files have the same info in the #TITLE:; section
	unsigned files = arrayKSFFileNames.size();
	std::string dir = out.GetSongDir();
	if( !LoadGlobalData(dir + arrayKSFFileNames[files - 1], out, bKIUCompliant) )
		return false;

	out.m_sSongFileName = dir + arrayKSFFileNames[files - 1];
	// load the Steps from the rest of the KSF files
	for( unsigned i=0; i<files; i++ )
	{
		Steps* pNewNotes = out.CreateSteps();
		if( !LoadFromKSFFile(dir + arrayKSFFileNames[i], *pNewNotes, out, bKIUCompliant) )
		{
			delete pNewNotes;
			continue;
		}
		pNewNotes->SetFilename(dir + arrayKSFFileNames[i]);
		out.AddSteps( pNewNotes );
	}
	return true;
}
Ejemplo n.º 14
0
Steps* SongUtil::GetStepsByDifficulty( const Song *pSong, StepsType st, Difficulty dc, bool bIncludeAutoGen )
{
    const vector<Steps*>& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st);
    for( unsigned i=0; i<vpSteps.size(); i++ )	// for each of the Song's Steps
    {
        Steps* pSteps = vpSteps[i];

        if( dc != Difficulty_Invalid && dc != pSteps->GetDifficulty() )
            continue;
        if( !bIncludeAutoGen && pSteps->IsAutogen() )
            continue;

        return pSteps;
    }

    return NULL;
}
Ejemplo n.º 15
0
Steps* SongUtil::GetStepsByMeter( const Song *pSong, StepsType st, int iMeterLow, int iMeterHigh )
{
    const vector<Steps*>& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st);
    for( unsigned i=0; i<vpSteps.size(); i++ )	// for each of the Song's Steps
    {
        Steps* pSteps = vpSteps[i];

        if( iMeterLow > pSteps->GetMeter() )
            continue;
        if( iMeterHigh < pSteps->GetMeter() )
            continue;

        return pSteps;
    }

    return NULL;
}
Ejemplo n.º 16
0
		FOREACH_ENUM( Difficulty, dc )
		{
			if( dc == Difficulty_Edit )
				continue;

			vector<Steps*> vSteps;
			SongUtil::GetSteps( &p, vSteps, st, dc );

			StepsUtil::SortNotesArrayByDifficulty( vSteps );
			for( unsigned k=1; k<vSteps.size(); k++ )
			{
				Steps* pSteps = vSteps[k];

				Difficulty dc2 = min( (Difficulty)(dc+1), Difficulty_Challenge );
				pSteps->SetDifficulty( dc2 );
			}
		}
Ejemplo n.º 17
0
SongCreditDisplay::SongCreditDisplay()
{
	if( GAMESTATE->IsCourseMode() )
		return;

	this->LoadFromFont( THEME->GetPathF("SongCreditDisplay","text") );
	Song* pSong = GAMESTATE->m_pCurSong;
	ASSERT( pSong );

	CString s = pSong->GetFullDisplayTitle() + "\n" + pSong->GetDisplayArtist() + "\n";
	if( !pSong->m_sCredit.empty() )
		s += pSong->m_sCredit + "\n";

	// use a vector and not a set so that ordering is maintained
	vector<Steps*> vpStepsToShow;
	FOREACH_PlayerNumber( p )
	{
		if( !GAMESTATE->IsHumanPlayer(p) )
			continue;	// skip
		
		Steps* pSteps = GAMESTATE->m_pCurSteps[p];
		bool bAlreadyAdded = find( vpStepsToShow.begin(), vpStepsToShow.end(), pSteps ) != vpStepsToShow.end();
		if( !bAlreadyAdded )
			vpStepsToShow.push_back( pSteps );
	}
	for( unsigned i=0; i<vpStepsToShow.size(); i++ )
	{
		Steps* pSteps = vpStepsToShow[i];
		CString sDifficulty = DifficultyToThemedString( pSteps->GetDifficulty() );
		
		// HACK: reset capitalization
		sDifficulty.MakeLower();
		sDifficulty = Capitalize( sDifficulty );
		
		s += sDifficulty + " steps: " + pSteps->GetDescription() + "\n";
	}

	// erase the last newline
	s.erase( s.end()-1 );

	this->SetText( s );
}
Ejemplo n.º 18
0
void Calibration::SignalPath::fix_last_step (Scalar* function)
{
  Steps* steps = dynamic_cast<Steps*> (function);
  if (steps && step_after_cal) 
  {
    int nstep = steps->get_nstep();
    if (nstep > 0)
    {
      time.set_value ( max_epoch );

      if (convert.get_value() < steps->get_step(nstep-1))
      {
#ifdef _DEBUG
        cerr << "fix_last_step: removing step " << nstep-1 << endl;
#endif
        steps->remove_step(nstep-1);
      }
    }
  }
}
Ejemplo n.º 19
0
void Calibration::SignalPath::offset_steps (Scalar* function, double offset)
{
  Steps* steps = dynamic_cast<Steps*> (function);
  if (!steps)
    return;

#ifdef _DEBUG
  cerr << "Calibration::SignalPath::offset_steps offset=" << offset << " ";
#endif
  for (unsigned i=0; i < steps->get_nstep(); i++)
  {
    steps->set_step( i, steps->get_step(i) + offset );
#ifdef _DEBUG
    cerr << i << "=" << steps->get_step(i) << " ";
#endif
  }
#ifdef _DEBUG
  cerr << endl;
#endif
}
Ejemplo n.º 20
0
void Calibration::SignalPath::set_min_step (Scalar* function, double step)
{
  Steps* steps = dynamic_cast<Steps*> (function);
  if (!steps)
    return;

  if (!steps->get_nstep())
  {
#ifdef _DEBUG
    cerr << "Calibration::SignalPath::set_min_step add step[0]="
         << step << endl;
#endif
    steps->add_step( step );
  }
 
  else if (step < steps->get_step(0))
  {
#ifdef _DEBUG
    cerr << "Calibration::SignalPath::set_min_step set step[0]="
         << step << endl;
#endif
    if (step_after_cal)
      steps->add_step( step);
    else
      steps->set_step( 0, step );
  }
}
Ejemplo n.º 21
0
/* This data is added to each player profile, and to the machine profile per-player. */
void AddPlayerStatsToProfile( Profile *pProfile, const StageStats &ss, PlayerNumber pn )
{
	ss.AssertValid( pn );
	CHECKPOINT;

	StyleID sID;
	sID.FromStyle( ss.m_pStyle );

	ASSERT( (int) ss.m_vpPlayedSongs.size() == ss.m_player[pn].m_iStepsPlayed );
	for( int i=0; i<ss.m_player[pn].m_iStepsPlayed; i++ )
	{
		Steps *pSteps = ss.m_player[pn].m_vpPossibleSteps[i];

		pProfile->m_iNumSongsPlayedByPlayMode[ss.m_playMode]++;
		pProfile->m_iNumSongsPlayedByStyle[sID] ++;
		pProfile->m_iNumSongsPlayedByDifficulty[pSteps->GetDifficulty()] ++;

		int iMeter = clamp( pSteps->GetMeter(), 0, MAX_METER );
		pProfile->m_iNumSongsPlayedByMeter[iMeter] ++;
	}
	
	pProfile->m_iTotalDancePoints += ss.m_player[pn].m_iActualDancePoints;

	if( ss.m_Stage == Stage_Extra1 || ss.m_Stage == Stage_Extra2 )
	{
		if( ss.m_player[pn].m_bFailed )
			++pProfile->m_iNumExtraStagesFailed;
		else
			++pProfile->m_iNumExtraStagesPassed;
	}

	// If you fail in a course, you passed all but the final song.
	// FIXME: Not true.  If playing with 2 players, one player could have failed earlier.
	if( !ss.m_player[pn].m_bFailed )
	{
		pProfile->m_iNumStagesPassedByPlayMode[ss.m_playMode] ++;
		pProfile->m_iNumStagesPassedByGrade[ss.m_player[pn].GetGrade()] ++;
	}
}
Ejemplo n.º 22
0
void SMLoader::LoadFromSMTokens( 
	CString sStepsType, 
	CString sDescription,
	CString sDifficulty,
	CString sMeter,
	CString sRadarValues,
	CString sNoteData,
	CString sAttackData,
	Steps &out
)
{
	TrimLeft(sStepsType); 
	TrimRight(sStepsType); 
	TrimLeft(sDescription); 
	TrimRight(sDescription); 
	TrimLeft(sDifficulty); 
	TrimRight(sDifficulty); 


//	LOG->Trace( "Steps::LoadFromSMTokens()" );

	out.m_StepsType = GameManager::StringToStepsType(sStepsType);
	out.SetDescription(sDescription);
	out.SetDifficulty(StringToDifficulty( sDifficulty ));

	// HACK:  We used to store SMANIAC as DIFFICULTY_HARD with special description.
	// Now, it has its own DIFFICULTY_CHALLENGE
	if( sDescription.CompareNoCase("smaniac") == 0 ) 
		out.SetDifficulty( DIFFICULTY_CHALLENGE );
	// HACK:  We used to store CHALLENGE as DIFFICULTY_HARD with special description.
	// Now, it has its own DIFFICULTY_CHALLENGE
	if( sDescription.CompareNoCase("challenge") == 0 ) 
		out.SetDifficulty( DIFFICULTY_CHALLENGE );

	out.SetMeter(atoi(sMeter));
	CStringArray saValues;
	split( sRadarValues, ",", saValues, true );
	if( saValues.size() == NUM_RADAR_CATEGORIES )
	{
		RadarValues v;
		FOREACH_RadarCategory(rc)
			v[rc] = strtof( saValues[rc], NULL );
		out.SetRadarValues( v ); 
	}
    
	out.SetSMNoteData(sNoteData, sAttackData);

	out.TidyUpData();
}
Ejemplo n.º 23
0
int main(int argc, char **argv)
{
  const char *filename = nullptr;
  unsigned long max_stages = 1;
  for (int i = 1; i < argc; ++i) {
    std::string arg(argv[i]);
    if (arg == "--max-stages") {
      if (++i == argc) {
        usage();
        std::exit(1);
      }
      char *endp;
      max_stages = strtoul(argv[i], &endp, 10);
      if (*endp != '\0' || max_stages > std::numeric_limits<unsigned>::max()) {
        usage();
        std::exit(1);
      }
    } else if (arg == "--help") {
      usage();
      std::exit(0);
    } else {
      if (filename) {
        usage();
        std::exit(1);
      }
      filename = argv[i];
    }
  }
  const auto map = filename ? read_map(filename) : read_map(std::cin);
  Steps steps = map_generator::generate(map, max_stages);
  steps.print(std::cout);
  CodeCost cost = steps.compute_cost();
  std::cerr << "Number of bytes: " << cost.bytes << '\n';
  std::cerr << "Number of cycles: " << cost.cycles << '\n';
  return 0;
}
Ejemplo n.º 24
0
/* Add a list of difficulties/edits to the given row/handler. */
void ScreenOptionsMaster::SetStep( OptionRowData &row, OptionRowHandler &hand )
{
	hand.type = ROW_STEP;
	row.bOneChoiceForAllPlayers = false;

	// fill in difficulty names
	if( GAMESTATE->m_bEditing )
	{
		row.choices.push_back( "" );
		hand.ListEntries.push_back( ModeChoice() );
	}
	else if( GAMESTATE->IsCourseMode() )   // playing a course
	{
		row.bOneChoiceForAllPlayers = PREFSMAN->m_bLockCourseDifficulties;

		vector<Trail*> vTrails;
		GAMESTATE->m_pCurCourse->GetTrails( vTrails, GAMESTATE->GetCurrentStyle()->m_StepsType );
		ModeChoice mc;
		for( unsigned i=0; i<vTrails.size(); i++ )
		{
			row.choices.push_back( CourseDifficultyToThemedString(vTrails[i]->m_CourseDifficulty) );
			mc.m_pTrail = vTrails[i];
			hand.ListEntries.push_back( mc );
		}
	}
	else // !GAMESTATE->IsCourseMode(), playing a song
	{
		vector<Steps*> vSteps;
		GAMESTATE->m_pCurSong->GetSteps( vSteps, GAMESTATE->GetCurrentStyle()->m_StepsType );
		StepsUtil::SortNotesArrayByDifficulty( vSteps );
		ModeChoice mc;
		for( unsigned i=0; i<vSteps.size(); i++ )
		{
			Steps* pSteps = vSteps[i];

			CString s;
			if( pSteps->GetDifficulty() == DIFFICULTY_EDIT )
				s = pSteps->GetDescription();
			else
				s = DifficultyToThemedString( pSteps->GetDifficulty() );
			s += ssprintf( " (%d)", pSteps->GetMeter() );

			row.choices.push_back( s );
			mc.m_pSteps = pSteps;
			mc.m_dc = pSteps->GetDifficulty();
			hand.ListEntries.push_back( mc );
		}
	}
}
Ejemplo n.º 25
0
void SongUtil::GetSteps(
    const Song *pSong,
    vector<Steps*>& arrayAddTo,
    StepsType st,
    Difficulty dc,
    int iMeterLow,
    int iMeterHigh,
    const RString &sDescription,
    bool bIncludeAutoGen,
    unsigned uHash,
    int iMaxToGet
)
{
    if( !iMaxToGet )
        return;

    const vector<Steps*> &vpSteps = st == StepsType_Invalid ? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st);
    for( unsigned i=0; i<vpSteps.size(); i++ )	// for each of the Song's Steps
    {
        Steps* pSteps = vpSteps[i];

        if( dc != Difficulty_Invalid && dc != pSteps->GetDifficulty() )
            continue;
        if( iMeterLow != -1 && iMeterLow > pSteps->GetMeter() )
            continue;
        if( iMeterHigh != -1 && iMeterHigh < pSteps->GetMeter() )
            continue;
        if( sDescription.size() && sDescription != pSteps->GetDescription() )
            continue;
        if( uHash != 0 && uHash != pSteps->GetHash() )
            continue;
        if( !bIncludeAutoGen && pSteps->IsAutogen() )
            continue;

        arrayAddTo.push_back( pSteps );

        if( iMaxToGet != -1 )
        {
            --iMaxToGet;
            if( !iMaxToGet )
                break;
        }
    }
}
Ejemplo n.º 26
0
void NotesWriterSM::WriteSMNotesTag( const Steps &in, RageFile &f, bool bSavingCache )
{
	f.PutLine( "" );
	f.PutLine( ssprintf( "//---------------%s - %s----------------",
		GameManager::StepsTypeToString(in.m_StepsType).c_str(), in.GetDescription().c_str() ) );
	f.PutLine( "#NOTES:" );
	f.PutLine( ssprintf( "     %s:", GameManager::StepsTypeToString(in.m_StepsType).c_str() ) );
	f.PutLine( ssprintf( "     %s:", in.GetDescription().c_str() ) );
	f.PutLine( ssprintf( "     %s:", DifficultyToString(in.GetDifficulty()).c_str() ) );
	f.PutLine( ssprintf( "     %d:", in.GetMeter() ) );
	
	int MaxRadar = bSavingCache? NUM_RADAR_CATEGORIES:5;
	CStringArray asRadarValues;
	for( int r=0; r < MaxRadar; r++ )
		asRadarValues.push_back( ssprintf("%.3f", in.GetRadarValues()[r]) );
	/* Don't append a newline here; it's added in NoteDataUtil::GetSMNoteDataString.
	 * If we add it here, then every time we write unmodified data we'll add an extra
	 * newline and they'll accumulate. */
	f.Write( ssprintf( "     %s:", join(",",asRadarValues).c_str() ) );

	CString sNoteData;
	CString sAttackData;
	in.GetSMNoteData( sNoteData, sAttackData );

	vector<CString> lines;

	split( sNoteData, "\n", lines, false );
	WriteLineList( f, lines, true, true );

	if( sAttackData.empty() )
		f.PutLine( ";" );
	else
	{
		f.PutLine( ":" );

		lines.clear();
		split( sAttackData, "\n", lines, false );
		WriteLineList( f, lines, true, true );

		f.PutLine( ";" );
	}
}
Ejemplo n.º 27
0
void SMLoader::LoadFromTokens( 
			     RString sStepsType, 
			     RString sDescription,
			     RString sDifficulty,
			     RString sMeter,
			     RString sRadarValues,
			     RString sNoteData,
			     Steps &out
			     )
{
	// we're loading from disk, so this is by definition already saved:
	out.SetSavedToDisk( true );

	Trim( sStepsType );
	Trim( sDescription );
	Trim( sDifficulty );
	Trim( sNoteData );

	// LOG->Trace( "Steps::LoadFromTokens(), %s", sStepsType.c_str() );

	// backwards compatibility hacks:
	// HACK: We eliminated "ez2-single-hard", but we should still handle it.
	if( sStepsType == "ez2-single-hard" )
		sStepsType = "ez2-single";

	// HACK: "para-single" used to be called just "para"
	if( sStepsType == "para" )
		sStepsType = "para-single";

	out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType );
	out.m_StepsTypeStr = sStepsType;
	out.SetDescription( sDescription );
	out.SetCredit( sDescription ); // this is often used for both.
	out.SetChartName(sDescription); // yeah, one more for good measure.
	out.SetDifficulty( OldStyleStringToDifficulty(sDifficulty) );

	// Handle hacks that originated back when StepMania didn't have
	// Difficulty_Challenge. (At least v1.64, possibly v3.0 final...)
	if( out.GetDifficulty() == Difficulty_Hard )
	{
		// HACK: SMANIAC used to be Difficulty_Hard with a special description.
		if( sDescription.CompareNoCase("smaniac") == 0 ) 
			out.SetDifficulty( Difficulty_Challenge );

		// HACK: CHALLENGE used to be Difficulty_Hard with a special description.
		if( sDescription.CompareNoCase("challenge") == 0 ) 
			out.SetDifficulty( Difficulty_Challenge );
	}

	if( sMeter.empty() )
	{
		// some simfiles (e.g. X-SPECIALs from Zenius-I-Vanisher) don't
		// have a meter on certain steps. Make the meter 1 in these instances.
		sMeter = "1";
	}
	out.SetMeter( StringToInt(sMeter) );

	out.SetSMNoteData( sNoteData );

	out.TidyUpData();
}
Ejemplo n.º 28
0
/* For visibility testing: if bAbsolute is false, random modifiers must return
 * the minimum possible scroll speed. */
float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute )
{
	// Default values that are returned if boomerang is off.
	fPeakYOffsetOut = FLT_MAX;
	bIsPastPeakOut = true;

	float fYOffset = 0;
	const SongPosition &position = pPlayerState->GetDisplayedPosition();
	
	float fSongBeat = position.m_fSongBeatVisible;
	
	Steps *pCurSteps = GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber];

	/* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or
	 * entirely time spacing (respectively). Occasionally, we tween between them. */
	if( curr_options->m_fTimeSpacing != 1.0f )
	{
		if( GAMESTATE->m_bInStepEditor ) {
			// Use constant spacing in step editor
			fYOffset = fNoteBeat - fSongBeat;
		} else {
			fYOffset = GetDisplayedBeat(pPlayerState, fNoteBeat) - GetDisplayedBeat(pPlayerState, fSongBeat);
			fYOffset *= pCurSteps->GetTimingData()->GetDisplayedSpeedPercent(
								     position.m_fSongBeatVisible,
								     position.m_fMusicSecondsVisible );
		}
		fYOffset *= 1 - curr_options->m_fTimeSpacing;
	}

	if( curr_options->m_fTimeSpacing != 0.0f )
	{
		float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible;
		float fNoteSeconds = pCurSteps->GetTimingData()->GetElapsedTimeFromBeat(fNoteBeat);
		float fSecondsUntilStep = fNoteSeconds - fSongSeconds;
		float fBPM = curr_options->m_fScrollBPM;
		float fBPS = fBPM/60.f / GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate;
		float fYOffsetTimeSpacing = fSecondsUntilStep * fBPS;
		fYOffset += fYOffsetTimeSpacing * curr_options->m_fTimeSpacing;
	}

	// TODO: If we allow noteskins to have metricable row spacing
	// (per issue 24), edit this to reflect that. -aj
	fYOffset *= ARROW_SPACING;

	// Factor in scroll speed
	float fScrollSpeed = curr_options->m_fScrollSpeed;
	if(curr_options->m_fMaxScrollBPM != 0)
	{
		fScrollSpeed= curr_options->m_fMaxScrollBPM /
			(pPlayerState->m_fReadBPM * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate);
	}
	
	// don't mess with the arrows after they've crossed 0
	if( fYOffset < 0 )
	{
		return fYOffset * fScrollSpeed;
	}

	const float* fAccels = curr_options->m_fAccels;
	//const float* fEffects = curr_options->m_fEffects;

	float fYAdjust = 0;	// fill this in depending on PlayerOptions

	if( fAccels[PlayerOptions::ACCEL_BOOST] != 0 )
	{
		float fEffectHeight = GetNoteFieldHeight();
		float fNewYOffset = fYOffset * 1.5f / ((fYOffset+fEffectHeight/1.2f)/fEffectHeight); 
		float fAccelYAdjust =	fAccels[PlayerOptions::ACCEL_BOOST] * (fNewYOffset - fYOffset);
		// TRICKY: Clamp this value, or else BOOST+BOOMERANG will draw a ton of arrows on the screen.
		CLAMP( fAccelYAdjust, BOOST_MOD_MIN_CLAMP, BOOST_MOD_MAX_CLAMP );
		fYAdjust += fAccelYAdjust;
	}
	if( fAccels[PlayerOptions::ACCEL_BRAKE] != 0 )
	{
		float fEffectHeight = GetNoteFieldHeight();
		float fScale = SCALE( fYOffset, 0.f, fEffectHeight, 0, 1.f );
		float fNewYOffset = fYOffset * fScale; 
		float fBrakeYAdjust = fAccels[PlayerOptions::ACCEL_BRAKE] * (fNewYOffset - fYOffset);
		// TRICKY: Clamp this value the same way as BOOST so that in BOOST+BRAKE, BRAKE doesn't overpower BOOST
		CLAMP( fBrakeYAdjust, BRAKE_MOD_MIN_CLAMP, BRAKE_MOD_MAX_CLAMP );
		fYAdjust += fBrakeYAdjust;
	}
	if( fAccels[PlayerOptions::ACCEL_WAVE] != 0 )
		fYAdjust +=	fAccels[PlayerOptions::ACCEL_WAVE] * WAVE_MOD_MAGNITUDE *RageFastSin( fYOffset/WAVE_MOD_HEIGHT );

	fYOffset += fYAdjust;

	// Factor in boomerang
	if( fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0 )
	{
		float fPeakAtYOffset = SCREEN_HEIGHT * BOOMERANG_PEAK_PERCENTAGE;	// zero point of boomerang function
		fPeakYOffsetOut = (-1*fPeakAtYOffset*fPeakAtYOffset/SCREEN_HEIGHT) + 1.5f*fPeakAtYOffset;
		bIsPastPeakOut = fYOffset < fPeakAtYOffset;

		fYOffset = (-1*fYOffset*fYOffset/SCREEN_HEIGHT) + 1.5f*fYOffset;
	}

	if( curr_options->m_fRandomSpeed > 0 && !bAbsolute )
	{
		// Generate a deterministically "random" speed for each arrow.
		unsigned seed = GAMESTATE->m_iStageSeed + ( BeatToNoteRow( fNoteBeat ) << 8 ) + (iCol * 100);

		for( int i = 0; i < 3; ++i )
			seed = ((seed * 1664525u) + 1013904223u) & 0xFFFFFFFF;
		float fRandom = seed / 4294967296.0f;

		/* Random speed always increases speed: a random speed of 10 indicates
		 * [1,11]. This keeps it consistent with other mods: 0 means no effect. */
		fScrollSpeed *=
				SCALE( fRandom,
						0.0f, 1.0f,
						1.0f, curr_options->m_fRandomSpeed + 1.0f );
	}

	if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 )
	{
		// TODO: Don't index by PlayerNumber.
		PerPlayerData &data = g_EffectData[pPlayerState->m_PlayerNumber];
	
		float fExpandMultiplier = SCALE( RageFastCos(data.m_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY),
						EXPAND_MULTIPLIER_SCALE_FROM_LOW, EXPAND_MULTIPLIER_SCALE_FROM_HIGH,
						EXPAND_MULTIPLIER_SCALE_TO_LOW, EXPAND_MULTIPLIER_SCALE_TO_HIGH );
		fScrollSpeed *=	SCALE( fAccels[PlayerOptions::ACCEL_EXPAND], 
				      EXPAND_SPEED_SCALE_FROM_LOW, EXPAND_SPEED_SCALE_FROM_HIGH,
				      EXPAND_SPEED_SCALE_TO_LOW, fExpandMultiplier );
	}

	fYOffset *= fScrollSpeed;
	fPeakYOffsetOut *= fScrollSpeed;

	return fYOffset;
}
Ejemplo n.º 29
0
 Steps(const Steps& s) : std::vector < Step* >(s)
 {
     for(const_iterator it = s.begin(); it != s.end(); ++it)
         push_back(new Step(**it));
 }
Ejemplo n.º 30
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;
}