From e76366bdba5dd273d1c706f1b67d8fe46a9c0dde Mon Sep 17 00:00:00 2001 From: MinaciousGrace Date: Mon, 5 Jun 2017 18:46:26 -0400 Subject: [PATCH] experiment with new notedata string format and tackle the hilarity that is song load --- src/CMakeData-data.cmake | 4 + src/NoteDataUtil.cpp | 1 - src/NotesLoaderETT.cpp | 0 src/NotesLoaderETT.h | 5 + src/NotesLoaderSM.cpp | 10 +- src/NotesWriterETT.cpp | 486 +++++++++++++++++++++++++++++++++++++++ src/NotesWriterETT.h | 48 ++++ src/NotesWriterSSC.cpp | 54 ++--- src/Song.cpp | 82 ++++++- src/Song.h | 1 + src/Steps.cpp | 29 ++- src/Steps.h | 1 + 12 files changed, 678 insertions(+), 43 deletions(-) create mode 100644 src/NotesLoaderETT.cpp create mode 100644 src/NotesLoaderETT.h create mode 100644 src/NotesWriterETT.cpp create mode 100644 src/NotesWriterETT.h diff --git a/src/CMakeData-data.cmake b/src/CMakeData-data.cmake index 3b2682d4a5..b9a1a4c967 100644 --- a/src/CMakeData-data.cmake +++ b/src/CMakeData-data.cmake @@ -49,6 +49,7 @@ list(APPEND SM_DATA_NOTELOAD_SRC "NotesLoaderSM.cpp" "NotesLoaderSMA.cpp" "NotesLoaderSSC.cpp" + "NotesLoaderETT.cpp" ) list(APPEND SM_DATA_NOTELOAD_HPP @@ -60,6 +61,7 @@ list(APPEND SM_DATA_NOTELOAD_HPP "NotesLoaderSM.h" "NotesLoaderSMA.h" "NotesLoaderSSC.h" + "NotesLoaderETT.h" ) source_group("Data Structures\\\\Notes Loaders" FILES ${SM_DATA_NOTELOAD_SRC} ${SM_DATA_NOTELOAD_HPP}) @@ -69,6 +71,7 @@ list(APPEND SM_DATA_NOTEWRITE_SRC "NotesWriterJson.cpp" "NotesWriterSM.cpp" "NotesWriterSSC.cpp" + "NotesWriterETT.cpp" ) list(APPEND SM_DATA_NOTEWRITE_HPP @@ -76,6 +79,7 @@ list(APPEND SM_DATA_NOTEWRITE_HPP "NotesWriterJson.h" "NotesWriterSM.h" "NotesWriterSSC.h" + "NotesWriterETT.h" ) source_group("Data Structures\\\\Notes Writers" FILES ${SM_DATA_NOTEWRITE_SRC} ${SM_DATA_NOTEWRITE_HPP}) diff --git a/src/NoteDataUtil.cpp b/src/NoteDataUtil.cpp index 1164b8851f..6dfea2f1ff 100644 --- a/src/NoteDataUtil.cpp +++ b/src/NoteDataUtil.cpp @@ -519,7 +519,6 @@ void NoteDataUtil::GetETTNoteDataString(const NoteData &in, RString &sRet) { // Get note data vector parts; float fLastBeat = -1.f; - LOG->Warn("sdasdfas23dff"); SplitCompositeNoteData(in, parts); FOREACH(NoteData, parts, nd) { diff --git a/src/NotesLoaderETT.cpp b/src/NotesLoaderETT.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/NotesLoaderETT.h b/src/NotesLoaderETT.h new file mode 100644 index 0000000000..b65912e4c6 --- /dev/null +++ b/src/NotesLoaderETT.h @@ -0,0 +1,5 @@ +#ifndef NotesLoaderETT_H +#define NotesLoaderETT_H + + +#endif \ No newline at end of file diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index f9d78fcdee..559bc551f4 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -404,7 +404,7 @@ void SMLoader::ParseBPMs( vector< pair > &out, const RString &line continue; } - out.push_back( make_pair(fBeat, fNewBPM) ); + out.emplace_back( make_pair(fBeat, fNewBPM) ); } } @@ -434,7 +434,7 @@ void SMLoader::ParseStops( vector< pair > &out, const RString line continue; } - out.push_back( make_pair(fFreezeBeat, fFreezeSeconds) ); + out.emplace_back( make_pair(fFreezeBeat, fFreezeSeconds) ); } } @@ -797,12 +797,12 @@ void SMLoader::ProcessSpeeds( TimingData &out, const RString &line, const int ro if( vs2[0] == 0 && vs2.size() == 2 ) // First one always seems to have 2. { - vs2.push_back("0"); + vs2.emplace_back("0"); } if( vs2.size() == 3 ) // use beats by default. { - vs2.push_back("0"); + vs2.emplace_back("0"); } if( vs2.size() < 4 ) @@ -1286,7 +1286,7 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache ) if( !IsAFile( song.GetBackgroundPath() ) ) break; - bg.push_back( BackgroundChange(lastBeat,song.m_sBackgroundFile) ); + bg.emplace_back( BackgroundChange(lastBeat,song.m_sBackgroundFile) ); } while(0); } if (bFromCache) diff --git a/src/NotesWriterETT.cpp b/src/NotesWriterETT.cpp new file mode 100644 index 0000000000..796a72f013 --- /dev/null +++ b/src/NotesWriterETT.cpp @@ -0,0 +1,486 @@ +#include "global.h" +#include +#include +#include "NotesWriterETT.h" +#include "BackgroundUtil.h" +#include "Foreach.h" +#include "GameManager.h" +#include "LocalizedString.h" +#include "NoteTypes.h" +#include "Profile.h" +#include "ProfileManager.h" +#include "RageFile.h" +#include "RageFileManager.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "Song.h" +#include "Steps.h" + +/** + * @brief Turn a vector of lines into a single line joined by newline characters. + * @param lines the list of lines to join. + * @return the joined lines. */ +static RString JoinLineList( vector &lines ) +{ + for( unsigned i = 0; i < lines.size(); ++i ) + TrimRight( lines[i] ); + + // Skip leading blanks. + unsigned j = 0; + while( j < lines.size() && lines.size() == 0 ) + ++j; + + return join( "\r\n", lines.begin()+j, lines.end() ); +} + +RString MSDToString2(MinaSD x) { + RString o = ""; + for (size_t i = 0; i < x.size(); i++) { + for (size_t ii = 0; ii < x[i].size(); ii++) { + o.append(to_string(x[i][ii]).substr(0, 5)); + if (ii != x[i].size() - 1) + o.append(","); + } + if (i != x.size() - 1) + o.append(":"); + } + return o; +} + +// A utility class to write timing tags more easily! +struct TimingTagWriter { + + vector *m_pvsLines; + RString m_sNext; + + TimingTagWriter( vector *pvsLines ): m_pvsLines (pvsLines) { } + + void Write( const int row, const char *value ) + { + m_pvsLines->emplace_back( m_sNext + ssprintf( "%.6f=%s", NoteRowToBeat(row), value ) ); + m_sNext = ","; + } + + void Write( const int row, const float value ) { Write( row, ssprintf( "%.6f", value ) ); } + void Write( const int row, const int value ) { Write( row, ssprintf( "%d", value ) ); } + void Write( const int row, const int a, const int b ) { Write( row, ssprintf( "%d=%d", a, b ) ); } + void Write( const int row, const float a, const float b ) { Write( row, ssprintf( "%.6f=%.6f", a, b) ); } + void Write( const int row, const float a, const float b, const unsigned short c ) + { Write( row, ssprintf( "%.6f=%.6f=%hd", a, b, c) ); } + + void Init( const RString sTag ) { m_sNext = "#" + sTag + ":"; } + void Finish( ) { m_pvsLines->emplace_back( ( m_sNext != "," ? m_sNext : "" ) + ";" ); } + +}; + +static void GetTimingTags( vector &lines, const TimingData &timing, bool bIsSong = false ) +{ + TimingTagWriter writer ( &lines ); + + // timing.TidyUpData(); // UGLY: done via const_cast. do we really -need- this here? +#define WRITE_SEG_LOOP_OPEN(enum_type, seg_type, seg_name, to_func) \ + { \ + vector const& segs= timing.GetTimingSegments(enum_type); \ + if(!segs.empty()) \ + { \ + writer.Init(seg_name); \ + for(auto&& seg : segs) \ + { \ + const seg_type* segment= to_func(seg); + +#define WRITE_SEG_LOOP_CLOSE } writer.Finish(); } } + + WRITE_SEG_LOOP_OPEN(SEGMENT_BPM, BPMSegment, "BPMS", ToBPM); + writer.Write(segment->GetRow(), segment->GetBPM()); + WRITE_SEG_LOOP_CLOSE; + + WRITE_SEG_LOOP_OPEN(SEGMENT_STOP, StopSegment, "STOPS", ToStop); + writer.Write(segment->GetRow(), segment->GetPause()); + WRITE_SEG_LOOP_CLOSE; + + WRITE_SEG_LOOP_OPEN(SEGMENT_DELAY, DelaySegment, "DELAYS", ToDelay); + writer.Write(segment->GetRow(), segment->GetPause()); + WRITE_SEG_LOOP_CLOSE; + + WRITE_SEG_LOOP_OPEN(SEGMENT_WARP, WarpSegment, "WARPS", ToWarp); + writer.Write(segment->GetRow(), segment->GetLength()); + WRITE_SEG_LOOP_CLOSE; + + WRITE_SEG_LOOP_OPEN(SEGMENT_TIME_SIG, TimeSignatureSegment, "TIMESIGNATURESEGMENT", ToTimeSignature); + writer.Write(segment->GetRow(), segment->GetNum(), segment->GetDen()); + WRITE_SEG_LOOP_CLOSE; + + WRITE_SEG_LOOP_OPEN(SEGMENT_TICKCOUNT, TickcountSegment, "TICKCOUNTS", ToTickcount); + writer.Write(segment->GetRow(), segment->GetTicks()); + WRITE_SEG_LOOP_CLOSE; + + WRITE_SEG_LOOP_OPEN(SEGMENT_COMBO, ComboSegment, "COMBOS", ToCombo); + if (segment->GetCombo() == segment->GetMissCombo()) + { + writer.Write(segment->GetRow(), segment->GetCombo()); + } + else + { + writer.Write(segment->GetRow(), segment->GetCombo(), segment->GetMissCombo()); + } + WRITE_SEG_LOOP_CLOSE; + + WRITE_SEG_LOOP_OPEN(SEGMENT_SPEED, SpeedSegment, "SPEEDS", ToSpeed); + writer.Write(segment->GetRow(), segment->GetRatio(), segment->GetDelay(), segment->GetUnit()); + WRITE_SEG_LOOP_CLOSE; + + WRITE_SEG_LOOP_OPEN(SEGMENT_SCROLL, ScrollSegment, "SCROLLS", ToScroll); + writer.Write(segment->GetRow(), segment->GetRatio()); + WRITE_SEG_LOOP_CLOSE; + + // TODO: Investigate why someone wrote a condition for leaving fakes out of + // the song timing tags, and put it in a function that is only used when + // writing the steps timing tags. -Kyz + if (!bIsSong) + { + WRITE_SEG_LOOP_OPEN(SEGMENT_FAKE, FakeSegment, "FAKES", ToFake); + writer.Write(segment->GetRow(), segment->GetLength()); + WRITE_SEG_LOOP_CLOSE; + } + + WRITE_SEG_LOOP_OPEN(SEGMENT_LABEL, LabelSegment, "LABELS", ToLabel); + if (!segment->GetLabel().empty()) + { + writer.Write(segment->GetRow(), segment->GetLabel()); + } + WRITE_SEG_LOOP_CLOSE; + +#undef WRITE_SEG_LOOP_OPEN +#undef WRITE_SEG_LOOP_CLOSE +} + +static void write_tag(RageFile& f, RString const& format, + std::string const& value) +{ + if (!value.empty()) + { + f.PutLine(ssprintf(format, SmEscape(value).c_str())); + } +} + +static void WriteTimingTags( RageFile &f, const TimingData &timing, bool bIsSong = false ) +{ + write_tag(f, "#BPMS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_BPM, 3))); + write_tag(f, "#STOPS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_STOP, 3))); + write_tag(f, "#DELAYS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_DELAY, 3))); + write_tag(f, "#WARPS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_WARP, 3))); + write_tag(f, "#TIMESIGNATURES:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_TIME_SIG, 3))); + write_tag(f, "#TICKCOUNTS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_TICKCOUNT, 3))); + write_tag(f, "#COMBOS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_COMBO, 3))); + write_tag(f, "#SPEEDS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_SPEED, 3))); + write_tag(f, "#SCROLLS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_SCROLL, 3))); + write_tag(f, "#FAKES:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_FAKE, 3))); + write_tag(f, "#LABELS:%s;", + join(",\r\n", timing.ToVectorString(SEGMENT_LABEL, 3))); + +} + +/** + * @brief Write out the common tags for .SSC files. + * @param f the file in question. + * @param out the Song in question. */ +static void WriteGlobalTags( RageFile &f, const Song &out ) +{ + f.PutLine(ssprintf("#VERSION:%.2f;", STEPFILE_VERSION_NUMBER)); + write_tag(f, "#TITLE:%s;", out.m_sMainTitle); + write_tag(f, "#SUBTITLE:%s;", out.m_sSubTitle); + write_tag(f, "#ARTIST:%s;", out.m_sArtist); + write_tag(f, "#TITLETRANSLIT:%s;", out.m_sMainTitleTranslit); + write_tag(f, "#SUBTITLETRANSLIT:%s;", out.m_sSubTitleTranslit); + write_tag(f, "#ARTISTTRANSLIT:%s;", out.m_sArtistTranslit); + write_tag(f, "#GENRE:%s;", out.m_sGenre); + write_tag(f, "#ORIGIN:%s;", out.m_sOrigin); + write_tag(f, "#CREDIT:%s;", out.m_sCredit); + write_tag(f, "#BANNER:%s;", out.m_sBannerFile); + write_tag(f, "#BACKGROUND:%s;", out.m_sBackgroundFile); + write_tag(f, "#PREVIEWVID:%s;", out.m_sPreviewVidFile); + write_tag(f, "#JACKET:%s;", out.m_sJacketFile); + write_tag(f, "#CDIMAGE:%s;", out.m_sCDFile); + write_tag(f, "#DISCIMAGE:%s;", out.m_sDiscFile); + write_tag(f, "#LYRICSPATH:%s;", out.m_sLyricsFile); + write_tag(f, "#CDTITLE:%s;", out.m_sCDTitleFile); + write_tag(f, "#MUSIC:%s;", out.m_sMusicFile); + write_tag(f, "#PREVIEW:%s;", out.m_PreviewFile); + { + auto vs = out.GetInstrumentTracksToVectorString(); + if (!vs.empty()) + { + std::string s = join(",", vs); + f.PutLine("#INSTRUMENTTRACK:" + s + ";\n"); + } + } + f.PutLine(ssprintf("#OFFSET:%.6f;", out.m_SongTiming.m_fBeat0OffsetInSeconds)); + f.PutLine(ssprintf("#SAMPLESTART:%.6f;", out.m_fMusicSampleStartSeconds)); + f.PutLine(ssprintf("#SAMPLELENGTH:%.6f;", out.m_fMusicSampleLengthSeconds)); + + f.Write("#SELECTABLE:"); + switch (out.m_SelectionDisplay) + { + default: ASSERT_M(0, "An invalid selectable value was found for this song!"); // fall through + case Song::SHOW_ALWAYS: f.Write("YES"); break; + //case Song::SHOW_NONSTOP: f.Write( "NONSTOP" ); break; + case Song::SHOW_NEVER: f.Write("NO"); break; + } + f.PutLine(";"); + + switch (out.m_DisplayBPMType) + { + case DISPLAY_BPM_ACTUAL: + // write nothing + break; + case DISPLAY_BPM_SPECIFIED: + if (out.m_fSpecifiedBPMMin == out.m_fSpecifiedBPMMax) + { + f.PutLine(ssprintf("#DISPLAYBPM:%.6f;", out.m_fSpecifiedBPMMin)); + } + else + { + f.PutLine(ssprintf("#DISPLAYBPM:%.6f:%.6f;", out.m_fSpecifiedBPMMin, out.m_fSpecifiedBPMMax)); + } + break; + case DISPLAY_BPM_RANDOM: + f.PutLine(ssprintf("#DISPLAYBPM:*;")); + break; + default: + break; + } + + WriteTimingTags(f, out.m_SongTiming); + + if (out.GetSpecifiedLastSecond() > 0) + { + f.PutLine(ssprintf("#LASTSECONDHINT:%.6f;", out.GetSpecifiedLastSecond())); + } + FOREACH_BackgroundLayer(b) + { + if (out.GetBackgroundChanges(b).empty()) + { + continue; // skip + } + if (b == 0) + { + f.Write("#BGCHANGES:"); + } + else + { + f.Write(ssprintf("#BGCHANGES%d:", b + 1)); + } + for (auto &bgc : out.GetBackgroundChanges(b)) + { + f.PutLine(bgc.ToString() + ","); + } + + /* If there's an animation plan at all, add a dummy "-nosongbg-" tag to + * indicate that this file doesn't want a song BG entry added at the end. + * See SSCLoader::TidyUpData. This tag will be removed on load. Add it + * at a very high beat, so it won't cause problems if loaded in older versions. */ + if (b == 0 && !out.GetBackgroundChanges(b).empty()) + { + f.PutLine("99999=-nosongbg-=1.000=0=0=0 // don't automatically add -songbackground-"); + } + f.PutLine(";"); + } + + if (out.GetForegroundChanges().size()) + { + f.Write("#FGCHANGES:"); + for (auto const &bgc : out.GetForegroundChanges()) + { + f.PutLine(bgc.ToString() + ","); + } + f.PutLine(";"); + } + + if (!out.m_vsKeysoundFile.empty()) + { + f.Write("#KEYSOUNDS:"); + for (unsigned i = 0; i < out.m_vsKeysoundFile.size(); i++) + { + // some keysound files has the first sound that starts with #, + // which makes MsdFile fail parsing the whole declaration. + // in this case, add a backslash at the front + // (#KEYSOUNDS:\#bgm.wav,01.wav,02.wav,..) and handle that on load. + if (i == 0 && out.m_vsKeysoundFile[i].size() > 0 && out.m_vsKeysoundFile[i][0] == '#') + { + f.Write("\\"); + } + f.Write(out.m_vsKeysoundFile[i]); + if (i != out.m_vsKeysoundFile.size() - 1) + { + f.Write(","); + } + } + f.PutLine(";"); + } + + f.PutLine(""); +} + +static void emplace_back_tag(vector& lines, + RString const& format, RString const& value) +{ + if (!value.empty()) + { + lines.emplace_back(ssprintf(format, SmEscape(value).c_str())); + } +} + +/** + * @brief Retrieve the individual batches of NoteData. + * @param song the Song in question. + * @param in the Steps in question. + * @param bSavingCache a flag to see if we're saving certain cache data. + * @return the NoteData in RString form. */ +static RString GetETTNoteData( const Song &song, Steps &in) { + vector lines; + + lines.emplace_back(""); + // Escape to prevent some clown from making a comment of "\r\n;" + lines.emplace_back(ssprintf("//---------------%s - %s----------------", + in.m_StepsTypeStr.c_str(), SmEscape(in.GetDescription()).c_str())); + lines.emplace_back("#NOTEDATA:;"); // our new separator. + emplace_back_tag(lines, "#CHARTNAME:%s;", in.GetChartName()); + emplace_back_tag(lines, "#STEPSTYPE:%s;", in.m_StepsTypeStr); + emplace_back_tag(lines, "#DESCRIPTION:%s;", in.GetDescription()); + emplace_back_tag(lines, "#CHARTSTYLE:%s;", in.GetChartStyle()); + emplace_back_tag(lines, "#DIFFICULTY:%s;", DifficultyToString(in.GetDifficulty())); + lines.emplace_back(ssprintf("#METER:%d;", in.GetMeter())); + lines.emplace_back(ssprintf("#MSDVALUES:%s;", MSDToString2(in.GetAllMSD()).c_str())); + lines.emplace_back(ssprintf("#CHARTKEY:%s;", SmEscape(in.GetChartKey()).c_str())); + + emplace_back_tag(lines, "#MUSIC:%s;", in.GetMusicFile()); + + vector asRadarValues; + const RadarValues &rv = in.GetRadarValues(); + FOREACH_ENUM(RadarCategory, rc) + asRadarValues.emplace_back(ssprintf("%i", rv[rc])); + lines.emplace_back(ssprintf("#RADARVALUES:%s;", join(",", asRadarValues).c_str())); + + emplace_back_tag(lines, "#CREDIT:%s;", in.GetCredit()); + + // If the Steps TimingData is not empty, then they have their own + // timing. Write out the corresponding tags. + if (!in.m_Timing.empty()) + { + lines.emplace_back(ssprintf("#OFFSET:%.6f;", in.m_Timing.m_fBeat0OffsetInSeconds)); + GetTimingTags(lines, in.m_Timing); + } + + switch (in.GetDisplayBPM()) + { + case DISPLAY_BPM_ACTUAL: + // write nothing + break; + case DISPLAY_BPM_SPECIFIED: + { + float small = in.GetMinBPM(); + float big = in.GetMaxBPM(); + if (small == big) + lines.emplace_back(ssprintf("#DISPLAYBPM:%.6f;", small)); + else + lines.emplace_back(ssprintf("#DISPLAYBPM:%.6f:%.6f;", small, big)); + break; + } + case DISPLAY_BPM_RANDOM: + lines.emplace_back(ssprintf("#DISPLAYBPM:*;")); + break; + default: + break; + } + + RString sNoteData; + in.GetETTNoteData(sNoteData); + lines.emplace_back(song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:"); + + TrimLeft(sNoteData); + lines.emplace_back(sNoteData); + lines.emplace_back(";"); + return JoinLineList(lines); +} + +bool NotesWriterETT::Write( RString &sPath, const Song &out, const vector& vpStepsToSave ) { + int flags = RageFile::WRITE; + + /* If we're not saving cache, we're saving real data, so enable SLOW_FLUSH + * to prevent data loss. If we're saving cache, this will slow things down + * too much. */ + + RageFile f; + if( !f.Open( sPath, flags ) ) + { + LOG->UserLog( "Song file", sPath, "couldn't be opened for writing: %s", f.GetError().c_str() ); + return false; + } + + WriteGlobalTags( f, out ); + + f.PutLine(ssprintf("// cache tags:")); + f.PutLine(ssprintf("#FIRSTSECOND:%.6f;", out.GetFirstSecond())); + f.PutLine(ssprintf("#LASTSECOND:%.6f;", out.GetLastSecond())); + f.PutLine(ssprintf("#SONGFILENAME:%s;", out.m_sSongFileName.c_str())); + f.PutLine(ssprintf("#HASMUSIC:%i;", out.m_bHasMusic)); + f.PutLine(ssprintf("#HASBANNER:%i;", out.m_bHasBanner)); + f.PutLine(ssprintf("#MUSICLENGTH:%.6f;", out.m_fMusicLengthSeconds)); + f.PutLine(ssprintf("// end cache tags")); + + // Save specified Steps to this file + FOREACH_CONST(Steps*, vpStepsToSave, s) + { + Steps* pSteps = *s; + if (pSteps->GetChartKey() != "") { // Avoid writing cache tags for invalid chartkey files(empty steps) -Mina + RString sTag = GetETTNoteData(out, *pSteps); + f.PutLine(sTag); + } + else + { + LOG->Info("Not caching empty difficulty in file %s", sPath.c_str()); + } + } + if( f.Flush() == -1 ) + return false; + + return true; +} + +static LocalizedString DESTINATION_ALREADY_EXISTS ("NotesWriterSSC", "Error renaming file. Destination file '%s' already exists."); +static LocalizedString ERROR_WRITING_FILE ("NotesWriterSSC", "Error writing file '%s'."); + +/* + * (c) 2011 Jason Felds + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/src/NotesWriterETT.h b/src/NotesWriterETT.h new file mode 100644 index 0000000000..20dc4adfdf --- /dev/null +++ b/src/NotesWriterETT.h @@ -0,0 +1,48 @@ +#ifndef NOTES_WRITER_ETT_H +#define NOTES_WRITER_ETT_H + +#include "global.h" + +class Song; +class Steps; +/** @brief Writes a Song to a .ETT file. */ +namespace NotesWriterETT +{ + /** + * @brief Write the song out to a file. + * @param sPath the path to write the file. + * @param out the Song to be written out. + * @param vpStepsToSave the Steps to save. + * @param bSavingCache a flag to see if we're saving certain cache data. + * @return its success or failure. */ + bool Write( RString &sPath, const Song &out, const vector& vpStepsToSave ); +} + +#endif + +/** + * @file + * @author Jason Felds (c) 2011 + * @section LICENSE + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 7617400610..5710a89cf7 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -57,7 +57,7 @@ struct TimingTagWriter { void Write( const int row, const char *value ) { - m_pvsLines->push_back( m_sNext + ssprintf( "%.6f=%s", NoteRowToBeat(row), value ) ); + m_pvsLines->emplace_back( m_sNext + ssprintf( "%.6f=%s", NoteRowToBeat(row), value ) ); m_sNext = ","; } @@ -69,7 +69,7 @@ struct TimingTagWriter { { Write( row, ssprintf( "%.6f=%.6f=%hd", a, b, c) ); } void Init( const RString sTag ) { m_sNext = "#" + sTag + ":"; } - void Finish( ) { m_pvsLines->push_back( ( m_sNext != "," ? m_sNext : "" ) + ";" ); } + void Finish( ) { m_pvsLines->emplace_back( ( m_sNext != "," ? m_sNext : "" ) + ";" ); } }; @@ -331,12 +331,12 @@ static void WriteGlobalTags( RageFile &f, const Song &out ) f.PutLine(""); } -static void push_back_tag(vector& lines, +static void emplace_back_tag(vector& lines, RString const& format, RString const& value) { if (!value.empty()) { - lines.push_back(ssprintf(format, SmEscape(value).c_str())); + lines.emplace_back(ssprintf(format, SmEscape(value).c_str())); } } @@ -350,35 +350,35 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa { vector lines; - lines.push_back(""); + lines.emplace_back(""); // Escape to prevent some clown from making a comment of "\r\n;" - lines.push_back(ssprintf("//---------------%s - %s----------------", + lines.emplace_back(ssprintf("//---------------%s - %s----------------", in.m_StepsTypeStr.c_str(), SmEscape(in.GetDescription()).c_str())); - lines.push_back("#NOTEDATA:;"); // our new separator. - push_back_tag(lines, "#CHARTNAME:%s;", in.GetChartName()); - push_back_tag(lines, "#STEPSTYPE:%s;", in.m_StepsTypeStr); - push_back_tag(lines, "#DESCRIPTION:%s;", in.GetDescription()); - push_back_tag(lines, "#CHARTSTYLE:%s;", in.GetChartStyle()); - push_back_tag(lines, "#DIFFICULTY:%s;", DifficultyToString(in.GetDifficulty())); - lines.push_back(ssprintf("#METER:%d;", in.GetMeter())); - lines.push_back(ssprintf("#MSDVALUES:%s;", MSDToString(in.GetAllMSD()).c_str())); - lines.push_back(ssprintf("#CHARTKEY:%s;", SmEscape(in.GetChartKey()).c_str())); - - push_back_tag(lines, "#MUSIC:%s;", in.GetMusicFile()); + lines.emplace_back("#NOTEDATA:;"); // our new separator. + emplace_back_tag(lines, "#CHARTNAME:%s;", in.GetChartName()); + emplace_back_tag(lines, "#STEPSTYPE:%s;", in.m_StepsTypeStr); + emplace_back_tag(lines, "#DESCRIPTION:%s;", in.GetDescription()); + emplace_back_tag(lines, "#CHARTSTYLE:%s;", in.GetChartStyle()); + emplace_back_tag(lines, "#DIFFICULTY:%s;", DifficultyToString(in.GetDifficulty())); + lines.emplace_back(ssprintf("#METER:%d;", in.GetMeter())); + lines.emplace_back(ssprintf("#MSDVALUES:%s;", MSDToString(in.GetAllMSD()).c_str())); + lines.emplace_back(ssprintf("#CHARTKEY:%s;", SmEscape(in.GetChartKey()).c_str())); + + emplace_back_tag(lines, "#MUSIC:%s;", in.GetMusicFile()); vector asRadarValues; const RadarValues &rv = in.GetRadarValues(); FOREACH_ENUM(RadarCategory, rc) - asRadarValues.push_back(ssprintf("%i", rv[rc])); - lines.push_back(ssprintf("#RADARVALUES:%s;", join(",", asRadarValues).c_str())); + asRadarValues.emplace_back(ssprintf("%i", rv[rc])); + lines.emplace_back(ssprintf("#RADARVALUES:%s;", join(",", asRadarValues).c_str())); - push_back_tag(lines, "#CREDIT:%s;", in.GetCredit()); + emplace_back_tag(lines, "#CREDIT:%s;", in.GetCredit()); // If the Steps TimingData is not empty, then they have their own // timing. Write out the corresponding tags. if (!in.m_Timing.empty()) { - lines.push_back(ssprintf("#OFFSET:%.6f;", in.m_Timing.m_fBeat0OffsetInSeconds)); + lines.emplace_back(ssprintf("#OFFSET:%.6f;", in.m_Timing.m_fBeat0OffsetInSeconds)); GetTimingTags(lines, in.m_Timing); } @@ -392,33 +392,33 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa float small = in.GetMinBPM(); float big = in.GetMaxBPM(); if (small == big) - lines.push_back(ssprintf("#DISPLAYBPM:%.6f;", small)); + lines.emplace_back(ssprintf("#DISPLAYBPM:%.6f;", small)); else - lines.push_back(ssprintf("#DISPLAYBPM:%.6f:%.6f;", small, big)); + lines.emplace_back(ssprintf("#DISPLAYBPM:%.6f:%.6f;", small, big)); break; } case DISPLAY_BPM_RANDOM: - lines.push_back(ssprintf("#DISPLAYBPM:*;")); + lines.emplace_back(ssprintf("#DISPLAYBPM:*;")); break; default: break; } if (bSavingCache) { - lines.push_back(ssprintf("#STEPFILENAME:%s;", in.GetFilename().c_str())); + lines.emplace_back(ssprintf("#STEPFILENAME:%s;", in.GetFilename().c_str())); } else { RString sNoteData; in.GetSMNoteData(sNoteData); - lines.push_back(song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:"); + lines.emplace_back(song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:"); TrimLeft(sNoteData); vector splitData; split(sNoteData, "\n", splitData); lines.insert(lines.end(), std::make_move_iterator(splitData.begin()), std::make_move_iterator(splitData.end())); - lines.push_back(";"); + lines.emplace_back(";"); } return JoinLineList(lines); } diff --git a/src/Song.cpp b/src/Song.cpp index 8a31e7efb3..f217a0f142 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -32,6 +32,7 @@ #include "NotesWriterJson.h" #include "NotesWriterSM.h" #include "NotesWriterSSC.h" +#include "NotesWriterETT.h" #include "LyricsLoader.h" #include "ActorUtil.h" @@ -1118,15 +1119,11 @@ void Song::ReCalculateRadarValuesAndLastSecond(bool fromCache, bool duringCache) } } - // Wipe NoteData + // Cache etterna stuff and 'radar values' if (duringCache) { pSteps->CalculateRadarValues(m_fMusicLengthSeconds); pSteps->CalcEtternaMetadata(); - - NoteData dummy; - dummy.SetNumTracks(tempNoteData.GetNumTracks()); - pSteps->SetNoteData(dummy); } } @@ -1299,6 +1296,79 @@ bool Song::SaveToSSCFile( const RString &sPath, bool bSavingCache, bool autosave return true; } + +bool Song::SaveToETTFile(const RString &sPath, bool bSavingCache, bool autosave) +{ + RString path = sPath; + if (!bSavingCache) + path = SetExtension(sPath, "ett"); + if (autosave) + { + path = SetExtension(sPath, "ats"); + } + + LOG->Trace("Song::SaveToETTFile('%s')", path.c_str()); + + // If the file exists, make a backup. + if (!bSavingCache && !autosave && IsAFile(path)) + FileCopy(path, path + ".old"); + + vector vpStepsToSave; + FOREACH_CONST(Steps*, m_vpSteps, s) + { + Steps *pSteps = *s; + + // Only save steps that weren't loaded from a profile. + if (pSteps->WasLoadedFromProfile()) + continue; + + if (!bSavingCache) + pSteps->SetFilename(path); + vpStepsToSave.push_back(pSteps); + } + FOREACH_CONST(Steps*, m_UnknownStyleSteps, s) + { + vpStepsToSave.push_back(*s); + } + + if (bSavingCache || autosave) + { + return NotesWriterETT::Write(path, *this, vpStepsToSave); + } + + if (!NotesWriterETT::Write(path, *this, vpStepsToSave)) + return false; + + RemoveAutosave(); + + if (g_BackUpAllSongSaves.Get()) + { + RString sExt = GetExtension(path); + RString sBackupFile = SetExtension(path, ""); + + time_t cur_time; + time(&cur_time); + struct tm now; + localtime_r(&cur_time, &now); + + sBackupFile += ssprintf("-%04i-%02i-%02i--%02i-%02i-%02i", + 1900 + now.tm_year, now.tm_mon + 1, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec); + sBackupFile = SetExtension(sBackupFile, sExt); + sBackupFile += ssprintf(".old"); + + if (FileCopy(path, sBackupFile)) + LOG->Trace("Backed up %s to %s", path.c_str(), sBackupFile.c_str()); + else + LOG->Trace("Failed to back up %s to %s", path.c_str(), sBackupFile.c_str()); + } + + // Mark these steps saved to disk. + FOREACH(Steps*, vpStepsToSave, s) + (*s)->SetSavedToDisk(true); + + return true; +} + bool Song::SaveToJsonFile( const RString &sPath ) { LOG->Trace( "Song::SaveToJsonFile('%s')", sPath.c_str() ); @@ -1313,7 +1383,7 @@ bool Song::SaveToCacheFile() } SONGINDEX->AddCacheIndex(m_sSongDir, GetHashForDirectory(m_sSongDir)); const RString sPath = GetCacheFilePath(); - return SaveToSSCFile(sPath, true); + return SaveToETTFile(sPath, true); } bool Song::SaveToDWIFile() diff --git a/src/Song.h b/src/Song.h index e24dd0dd46..525b095503 100644 --- a/src/Song.h +++ b/src/Song.h @@ -121,6 +121,7 @@ class Song * @param bSavingCache a flag to determine if we're saving cache data. */ bool SaveToSSCFile(const RString &sPath, bool bSavingCache, bool autosave= false); + bool SaveToETTFile(const RString &sPath, bool bSavingCache, bool autosave = false); /** @brief Save to the SSC and SM files no matter what. */ void Save(bool autosave= false); /** diff --git a/src/Steps.cpp b/src/Steps.cpp index 066903c6ce..f2118ef304 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -31,6 +31,7 @@ #include #include "MinaCalc.h" #include +#include "NotesWriterETT.h" /* register DisplayBPM with StringConversion */ #include "EnumHelper.h" @@ -225,6 +226,21 @@ void Steps::GetSMNoteData( RString ¬es_comp_out ) const notes_comp_out = m_sNoteDataCompressed; } +/* XXX: this function should pull data from m_sFilename, like Decompress() */ +void Steps::GetETTNoteData(RString ¬es_comp_out) const +{ + if (m_sNoteDataCompressed.empty()) + { + if (!m_bNoteDataIsFilled) + { + /* no data is no data */ + notes_comp_out = ""; + return; + } + } + notes_comp_out = m_sNoteDataCompressed; +} + void Steps::TidyUpData() { // Don't set the StepsType to dance single if it's invalid. That just @@ -301,6 +317,7 @@ void Steps::CalculateRadarValues( float fMusicLengthSeconds ) NoteDataUtil::CalculateRadarValues( tempNoteData, fMusicLengthSeconds, m_CachedRadarValues ); } + tempNoteData.ClearAll(); GAMESTATE->SetProcessedTimingData(NULL); } @@ -384,15 +401,19 @@ void Steps::CalcEtternaMetadata() { const vector& etaner = GetTimingData()->BuildAndGetEtaner(nerv); stuffnthings = MinaSDCalc(GetNoteData().SerializeNoteData(etaner), GetNoteData().GetNumTracks(), 0.93f, 1.f, GetTimingData()->HasWarps()); - vector hoop = GetNoteData().SerializeNoteData2(etaner); - if (GetNoteData().GetNumTracks() == 4 && GetTimingData()->HasWarps() == false) - MinaCalc2(stuffnthings, hoop, 1.f, 0.93f); + //if (GetNoteData().GetNumTracks() == 4 && GetTimingData()->HasWarps() == false) + //MinaCalc2(stuffnthings, GetNoteData().SerializeNoteData2(etaner), 1.f, 0.93f); ChartKey = GenerateChartKey(*m_pNoteData, GetTimingData()); for (int i = 0; i < 8; ++i) SONGMAN->keyconversionmap.emplace(GenerateBustedChartKey(*m_pNoteData, GetTimingData(), i), ChartKey); + if (GetNoteData().GetNumTracks() == 4) { + NoteDataUtil::GetETTNoteDataString(*m_pNoteData, m_sNoteDataCompressed); + m_bNoteDataIsFilled = true; + } + m_pNoteData->UnsetNerv(); m_pNoteData->UnsetSerializedNoteData(); m_pNoteData->UnsetSerializedNoteData2(); @@ -498,7 +519,7 @@ void Steps::Compress() const return; } - if( !m_sFilename.empty() && m_LoadedFromProfile == ProfileSlot_Invalid ) + if( !m_sFilename.empty()) { /* We have a file on disk; clear all data in memory. * Data on profiles can't be accessed normally (need to mount and time-out diff --git a/src/Steps.h b/src/Steps.h index 17e2ec80bb..29361afd82 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -146,6 +146,7 @@ class Steps * @return true if our notedata is empty, false otherwise. */ bool IsNoteDataEmpty() const; + void GetETTNoteData(RString & notes_comp_out) const; void TidyUpData(); void CalculateRadarValues(float fMusicLengthSeconds);