Guest User

NotesLoaderCHART.cpp

a guest
Mar 23rd, 2016
197
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 17.75 KB | None | 0 0
  1. #include "global.h"
  2. #include "NotesLoaderCHART.h"
  3. #include "RageUtil_CharConversions.h"
  4. #include "RageFile.h"
  5. #include "TimingData.h"
  6. #include "RageUtil.h"
  7. #include "RageLog.h"
  8. #include "BackgroundUtil.h"
  9. #include "NoteData.h"
  10. #include "Song.h"
  11. #include "Steps.h"
  12. #include "GameManager.h"
  13. #include "Difficulty.h"
  14. #include <string>
  15. #include <sstream>
  16.  
  17. /**
  18.  Example .chart file layout:
  19.  [Song]
  20.  {
  21.     Name = "SONGNAME"
  22.     Artist = "ARTIST"
  23.     Charter = "CHARTERNAME"
  24.     Offset = 0 (float, starting point in seconds)
  25.     Resolution = 192 (ticks per beat)
  26.     Player2 = bass (unused)
  27.     Difficulty = 0 (?)
  28.     PreviewStart = 0.00 (start of preview in seconds)
  29.     PreviewEnd = 0.00   (end   "  "       "  "      )
  30.     Genre = "GENRE"
  31.     MediaType = "IMPORT MEDIA, DEFAULT CD?" (unused ?)
  32.     MusicStream = "AUDIOFILE NAME WITH EXTENSION"
  33.  }
  34.  [SyncTrack]
  35.  {
  36.     0 = B 240000
  37.     0 = TS 4
  38.    iTicks = <B, TS> BPM (to 3 decimal places as an int (or TS is X/4))
  39.  }
  40.  [Events]
  41.  {
  42.    iTicks = E "section SECTION_NAME_WITH_UNDERSCORES" (unused?)
  43.  }
  44.  [ExpertSingle] (always look for "Single" for now, first part is the difficulty)
  45.                 (Expert = Challenge, Hard = Hard, Medium = Medium, Easy = Easy)
  46.  {
  47.    iTicks = N iTrack iDuration
  48.    (some lines may have S instead of N, that's for star power. Ignore those lines)
  49.  }
  50.  (other difficulties/modes go here)
  51.  
  52.  This is completely different layout than the already made file parsers, so I needed to make my own in here
  53.  */
  54.  
  55. // I'm not even going to pretend that the following wasn't copy/pasted from MsdFile.cpp, I needed to parse this somehow
  56. // Well it was originally, now it's just a mess
  57. void ReadBuf( const char *buf, int len, Song *outSong, Steps *outSteps )
  58. {
  59.    bool parseSongInfo = outSong != NULL;
  60.    bool bDontReadThis = false;
  61.    int iMode = 0;
  62.    int iPrevNoteMark = 0;
  63.    int iPrevNoteTrack = 0;
  64.    int iPrevNoteLength = 0;
  65.    bool bPrevNoteIsHOPO = false;
  66.    // 192 is default resolution per beat in GH
  67.    int resolution = 192;
  68.    bool readingNotes = false;
  69.    vector<string> headerInfo(3);
  70.    // NoteData to store stuff that's being parsed
  71.    NoteData notedata;
  72.    // TimingData to store other stuff being parsed
  73.    TimingData timing;
  74.    // Pointer to Steps that may or may not need to be added, depending on if we're parsing for a Song or not
  75.    Steps* pNewNotes = NULL;
  76.    // Dunno if this will actually do anything, but it seems to be defaults put into SSC files for some reason?
  77.    timing.AddSegment(ComboSegment(0.0,1,1));
  78.    timing.AddSegment(ScrollSegment(0.0,1.0));
  79.    // Create timing data inside song if none exists
  80.    //if( parseSongInfo && outSong->m_SongTiming == NULL ) outSong->m_SongTiming = TimingData();
  81.    // Create timing data for the steps if none exists
  82.    //if( !parseSongInfo && outSteps->m_Timing == NULL ) outSteps->m_Timing = TimingData();
  83.    // Set notedata tracks
  84.    notedata.SetNumTracks(6);
  85.    // Screw efficiency, just gonna parse this word by word like I was gonna do with Python
  86.    std::string bufStr(buf);
  87.    std::istringstream iss(bufStr);
  88.    std::string line;
  89.    // Parse line-by-line
  90.    while( std::getline(iss,line) )
  91.    {
  92.       // Remove tabs
  93.       line.erase(std::remove(line.begin(), line.end(), '\t'), line.end());
  94.       vector<string> vsWords;
  95.       std::istringstream lineIss(line);
  96.       // Separate by spaces
  97.       copy( istream_iterator<string>(lineIss), istream_iterator<string>(), back_inserter(vsWords) );
  98.       int i = 0;
  99.       int numWords = vsWords.size();
  100.       // Don't need to parse through every word, since each of these if blocks parse the entire line
  101.      
  102.       /**
  103.        Change the mode once you hit a section tag. Until the actual notes, the tags should always be in the order:
  104.        Song, SyncTrack, Events, then comes the notes which are tagged by difficulty and game mode
  105.        */
  106.       if( vsWords[i].at(0) == '[' ) {
  107.          if( iMode < 4 ) ++iMode;
  108.          // Labels actually have a use! how 'bout that!
  109.          //if( iMode == 3 ) bDontReadThis = true;
  110.          bDontReadThis = false;
  111.          if( iMode == 4 ) {
  112.             // If it's not single mode, we don't care about it
  113.             if( vsWords[i].find("Single") != std::string::npos ) {
  114.                // If we're parsing a song, add the previous notes parsed, then get space for new steps
  115.                if( parseSongInfo ) {
  116.                   if( readingNotes ) {
  117.                      pNewNotes->m_Timing = timing;
  118.                      pNewNotes->SetNoteData( notedata );
  119.                      pNewNotes->TidyUpData();
  120.                      outSong->AddSteps( pNewNotes );
  121.                      // Reset notedata for new set of steps
  122.                      notedata = NoteData();
  123.                      notedata.SetNumTracks(6);
  124.                   } else {
  125.                      outSong->m_SongTiming = timing;
  126.                   }
  127.                   pNewNotes = outSong->CreateSteps();
  128.                   pNewNotes->SetChartStyle("Guitar");
  129.                   pNewNotes->SetCredit( headerInfo[1] );
  130.                   pNewNotes->SetDescription( headerInfo[1] );
  131.                   pNewNotes->SetMusicFile( headerInfo[2] );
  132.                   // Charts only load for guitar mode... for now.
  133.                   pNewNotes->m_StepsType = StepsType_guitar_solo;
  134.                }
  135.                if( !readingNotes ) readingNotes = true;
  136.                if( vsWords[i].find("Expert") != std::string::npos ) {
  137.                   // Expert == Challenge
  138.                   if( !parseSongInfo && outSteps->GetDifficulty() != Difficulty_Challenge ) {
  139.                      // This is the incorrect difficulty for the desired steps, continue to the next noteinfo tag
  140.                      bDontReadThis = true;
  141.                      continue;
  142.                   }
  143.                   if( parseSongInfo ) {
  144.                      pNewNotes->SetDifficulty( Difficulty_Challenge );
  145.                      pNewNotes->SetChartName( headerInfo[0]+" - Expert" );
  146.                      continue;
  147.                   }
  148.                } else if( vsWords[i].find("Hard") != std::string::npos ) {
  149.                   if( !parseSongInfo && outSteps->GetDifficulty() != Difficulty_Hard ) {
  150.                      bDontReadThis = true;
  151.                      continue;
  152.                   }
  153.                   if( parseSongInfo ) {
  154.                      pNewNotes->SetDifficulty( Difficulty_Hard );
  155.                      pNewNotes->SetChartName( headerInfo[0]+" - Hard" );
  156.                      continue;
  157.                   }
  158.                } else if( vsWords[i].find("Medium") != std::string::npos ) {
  159.                   if( !parseSongInfo && outSteps->GetDifficulty() != Difficulty_Medium ) {
  160.                      bDontReadThis = true;
  161.                      continue;
  162.                   }
  163.                   if( parseSongInfo ) {
  164.                      pNewNotes->SetDifficulty( Difficulty_Medium );
  165.                      pNewNotes->SetChartName( headerInfo[0]+" - Medium" );
  166.                      continue;
  167.                   }
  168.                } else if( vsWords[i].find("Easy") != std::string::npos ) {
  169.                   if( !parseSongInfo && outSteps->GetDifficulty() != Difficulty_Easy ) {
  170.                      bDontReadThis = true;
  171.                      continue;
  172.                   }
  173.                   if( parseSongInfo ) {
  174.                      pNewNotes->SetDifficulty( Difficulty_Easy );
  175.                      pNewNotes->SetChartName( headerInfo[0]+" - Easy" );
  176.                      continue;
  177.                   }
  178.                }
  179.                // Custom Guitar Hero charts have no beginner difficulty, this was added starting in GH World Tour
  180.             } else {
  181.                bDontReadThis = true;
  182.                continue;
  183.             }
  184.          } else {
  185.             continue;
  186.          }
  187.       }
  188.      
  189.       /* Curly braces are just filler */
  190.       if( !bDontReadThis && vsWords[i].at(0) != '{' && vsWords[i].at(0) != '}' )
  191.       {
  192.          if( iMode == 1 ) {
  193.             // Parse song information
  194.             // Separation by words also means that titles, artists and other info is likely broken up, stitch it back together
  195.             int j = 2;
  196.             string wholeString = "";
  197.             while(j < numWords) {
  198.                if(j>2) wholeString += " ";
  199.                wholeString += vsWords[j];
  200.                j++;
  201.             }
  202.             if( !vsWords[i].compare("Name") )
  203.             {
  204.                string mainTitle = wholeString.substr(1, wholeString.size()-2);
  205.                headerInfo[0] = mainTitle;
  206.                if( parseSongInfo ) outSong->m_sMainTitle = mainTitle;
  207.             } else if( !vsWords[i].compare("Artist") )
  208.             {
  209.                if( parseSongInfo ) outSong->m_sArtist = wholeString.substr(1, wholeString.size()-2);
  210.             } else if( !vsWords[i].compare("Charter") )
  211.             {
  212.                headerInfo[1] = wholeString.substr(1, wholeString.size()-2);
  213.             } else if( !vsWords[i].compare("Offset") )
  214.             {
  215.                timing.m_fBeat0OffsetInSeconds = atof(vsWords[i+2].c_str());
  216.             } else if( !vsWords[i].compare("Resolution") )
  217.             {
  218.                /* Here's the thing, Guitar Hero has a default resolution of 192 'ticks' per beat. Comparatively, Stepmania uses
  219.                 * 192 ticks per MEASURE as a maximum. This causes a lot of problems. If the resolution is >48, just make it 48
  220.                 * But only for setting the tickcount segment, still need the original resolution for note and timing parsing
  221.                 */
  222.                resolution = atoi(vsWords[i+2].c_str());
  223.                /* testing
  224.                if( parseSongInfo ) {
  225.                   if( resolution <= 48 ) outSong->m_SongTiming.AddSegment(TickcountSegment(0, resolution));
  226.                   else outSong->m_SongTiming.AddSegment(TickcountSegment(0, 48));
  227.                } */
  228.             } else if( !vsWords[i].compare("Difficulty") )
  229.             {
  230.                /* Uh, skip this for now, causes Stepmania to crash...
  231.                if( parseSongInfo ) pNewNotes->SetMeter(atoi(vsWords[i+2].c_str()) + 1);
  232.                else outSteps->SetMeter(atoi(vsWords[i+2].c_str()) + 1);
  233.                 */
  234.             } else if( !vsWords[i].compare("PreviewStart") )
  235.             {
  236.                if( parseSongInfo ) outSong->m_fMusicSampleStartSeconds = atof(vsWords[i+2].c_str());
  237.             } else if( !vsWords[i].compare("PreviewEnd") )
  238.             {
  239.                if( parseSongInfo )
  240.                {
  241.                   // Default to 12 second length, just seems like the default in StepMania
  242.                   float estSampleSec = atof(vsWords[i+2].c_str()) - outSong->m_fMusicSampleStartSeconds;
  243.                   if( estSampleSec <= 12 ) outSong->m_fMusicSampleStartSeconds = 12;
  244.                   else outSong->m_fMusicSampleLengthSeconds = estSampleSec;
  245.                }
  246.             } else if( !vsWords[i].compare("MusicStream") )
  247.             {
  248.                string songFile = wholeString.substr(1, wholeString.size()-2);
  249.                if( parseSongInfo ) outSong->m_sMusicFile = songFile;
  250.                else outSteps->SetMusicFile( songFile );
  251.                headerInfo[2] = songFile;
  252.             }
  253.          } else if( iMode == 2 ) {
  254.             // Parse BPM and time signature changes
  255.             // Steps and songs can have different timing data. Why?
  256.             int startMark = atoi(vsWords[i].c_str());
  257.             float newBPM = atof(vsWords[i+3].c_str()) / 1000;
  258.             if( !vsWords[i+2].compare( "B" ) )
  259.             {
  260.                if( startMark == 0 ) timing.AddSegment( BPMSegment(0, newBPM ));
  261.                else timing.SetBPMAtBeat((float)startMark/resolution, newBPM);
  262.             } else if( !vsWords[i+2].compare( "TS" ) )
  263.             {
  264.                // Fun fact, Guitar Hero songs are always in a time signature of x/4
  265.                timing.AddSegment( TimeSignatureSegment(BeatToNoteRow(atof(vsWords[i].c_str()) / resolution), atoi(vsWords[i+3].c_str()), 4) );
  266.             }
  267.          } else if( iMode == 3 ) {
  268.             // Parse section labels
  269.             int end = vsWords.size()-1;
  270.             string sectionTitle = vsWords[end].substr(0, vsWords[end].size()-1);
  271.             timing.AddSegment(LabelSegment(BeatToNoteRow(atof(vsWords[i].c_str())/resolution),sectionTitle));
  272.          } else if( iMode == 4 ) {
  273.             // Parse the notedata
  274.             // Some other markers for star power and such are thrown in here, ignore them
  275.             if( !vsWords[i+2].compare( "N" ) )
  276.             {
  277.                /* A note on sustained notes, Guitar Hero likes to have holds end exactly on beats, unfortunately this means that
  278.                 * sometimes a hold note can overlap into the next hold if they're in the same track. Need to check for this and
  279.                 * correct it by shortening the first hold slightly (1/4 resolution)
  280.                 */
  281.                int iNoteMark = atoi(vsWords[i].c_str());
  282.                int iNoteTrack = atoi(vsWords[i+3].c_str());
  283.                int iNoteLength = atoi(vsWords[i+4].c_str());
  284.                
  285.                if( iNoteTrack == iPrevNoteTrack && iPrevNoteLength + iPrevNoteMark >= iNoteMark ) {
  286.                   notedata.SetTapNote(iPrevNoteTrack, BeatToNoteRow((float)iPrevNoteMark/resolution), TAP_EMPTY);
  287.                   iPrevNoteLength = iNoteMark - iPrevNoteMark - (resolution / 4);
  288.                   notedata.AddHoldNote(iPrevNoteTrack, BeatToNoteRow((float)iPrevNoteMark/resolution),
  289.                                        BeatToNoteRow((float)(iPrevNoteMark+iPrevNoteLength)/resolution), TAP_ORIGINAL_GEM_HOLD);
  290.                }
  291.                
  292.                // If this note and the previous note are on the same beat and the previous note was a HOPO,
  293.                // Change it to a normal gem
  294.                if( bPrevNoteIsHOPO && (iNoteMark == iPrevNoteMark) )
  295.                {
  296.                   if( iPrevNoteLength ) // Previous note was a hold
  297.                   {
  298.                      notedata.AddHoldNote(iPrevNoteTrack, BeatToNoteRow((float)iPrevNoteMark/resolution),
  299.                               BeatToNoteRow((float)(iPrevNoteMark+iPrevNoteLength)/resolution), TAP_ORIGINAL_GEM_HOLD);
  300.                   } else
  301.                   {
  302.                      notedata.SetTapNote(iPrevNoteTrack, BeatToNoteRow((float)iPrevNoteMark/resolution), TAP_ORIGINAL_GEM);
  303.                   }
  304.                }
  305.                
  306.                // If the difference between this note and the last is <= 1/16th note (and > 0), and they're
  307.                // on different tracks, make this a HOPO
  308.                if( (iNoteMark - iPrevNoteMark <= resolution / 16) && (iNoteMark != iPrevNoteMark) && (iNoteTrack != iPrevNoteTrack) )
  309.                {
  310.                   if( iNoteLength )
  311.                   {
  312.                      notedata.AddHoldNote(iNoteTrack, BeatToNoteRow((float)iNoteMark/resolution),
  313.                                  BeatToNoteRow((float)(iNoteMark+iNoteLength)/resolution), TAP_ORIGINAL_HOPO_HOLD);
  314.                   } else
  315.                   {
  316.                      notedata.SetTapNote(iNoteTrack, BeatToNoteRow((float)iNoteMark/resolution), TAP_ORIGINAL_HOPO);
  317.                   }
  318.                   bPrevNoteIsHOPO = true;
  319.                }
  320.                
  321.                // Otherwise, plop a Gem in there
  322.                else
  323.                {
  324.                   if( iNoteLength )
  325.                   {
  326.                      notedata.AddHoldNote(iNoteTrack, BeatToNoteRow((float)iNoteMark/resolution),
  327.                                  BeatToNoteRow((float)(iNoteMark+iNoteLength)/resolution), TAP_ORIGINAL_GEM_HOLD);
  328.                   } else
  329.                   {
  330.                      notedata.SetTapNote(iNoteTrack, BeatToNoteRow((float)iNoteMark/resolution),TAP_ORIGINAL_GEM);
  331.                   }
  332.                   bPrevNoteIsHOPO = false;
  333.                }
  334.                
  335.                iPrevNoteMark = iNoteMark;
  336.                iPrevNoteTrack = iNoteTrack;
  337.                iPrevNoteLength = iNoteLength;
  338.             }
  339.          }
  340.       }
  341.    }
  342.    // If we reached the end of the file while still reading notes, add them to the song, if we're parsing that
  343.    if( parseSongInfo && readingNotes ) {
  344.       pNewNotes->m_Timing = timing;
  345.       pNewNotes->SetNoteData( notedata );
  346.       pNewNotes->TidyUpData();
  347.       outSong->AddSteps(pNewNotes);
  348.    }
  349.    if( !parseSongInfo )
  350.    {
  351.       outSteps->m_Timing = timing;
  352.       outSteps->SetNoteData( notedata );
  353.       outSteps->TidyUpData();
  354.    }
  355. }
  356.  
  357. // Returns true if successful, false otherwise
  358. // Pass Steps and Song by pointer, so they can be null incase they aren't used
  359. bool ReadFile( RString sNewPath, Song *outSong, Steps *outSteps )
  360. {
  361.    RageFile f;
  362.    /* Open a file. */
  363.    if( !f.Open( sNewPath ) )return false;
  364.    
  365.    // allocate a string to hold the file
  366.    RString FileString;
  367.    FileString.reserve( f.GetFileSize() );
  368.    
  369.    int iBytesRead = f.Read( FileString );
  370.    if( iBytesRead == -1 )return false;
  371.    
  372.    ReadBuf( FileString.c_str(), iBytesRead, outSong, outSteps );
  373.    
  374.    return true;
  375. }
  376.  
  377. void CHARTLoader::GetApplicableFiles( const RString &sPath, vector<RString> &out )
  378. {
  379.    GetDirListing( sPath + RString("*.chart"), out );
  380. }
  381.  
  382. bool CHARTLoader::LoadFromDir( const RString &sDir, Song &out ) {
  383.    LOG->Trace( "CHARTLoader::LoadFromDir(%s)", sDir.c_str() );
  384.    
  385.    vector<RString> arrayCHARTFileNames;
  386.    GetDirListing( sDir + RString("*.chart"), arrayCHARTFileNames );
  387.    
  388.    // We shouldn't have been called to begin with if there were no CHARTs.
  389.    ASSERT( arrayCHARTFileNames.size() != 0 );
  390.    
  391.    RString dir = out.GetSongDir();
  392.    
  393.    // Only need to use the first file, since there should only be 1
  394.    return ReadFile( dir+arrayCHARTFileNames[0], &out, NULL );
  395. }
  396.  
  397. bool CHARTLoader::LoadNoteDataFromSimfile( const RString & cachePath, Steps &out ) {
  398.    // This is simple since the path is already given to us
  399.    return ReadFile(cachePath, NULL, &out);
  400. }
Advertisement
Add Comment
Please, Sign In to add comment