Skip to content

Instantly share code, notes, and snippets.

@zardoru
Last active December 12, 2015 00:58
Show Gist options
  • Save zardoru/4687350 to your computer and use it in GitHub Desktop.
Save zardoru/4687350 to your computer and use it in GitHub Desktop.
notes exporter to osu for stepmania. yes this is my work with help of several people for getting the osu file format right.
#include "global.h"
#include "NoteTypes.h"
#include "NoteData.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "RageFileManager.h"
#include "RageFile.h"
#include "NoteDataUtil.h"
#include "RageFile.h"
#include "Song.h"
#include "Steps.h"
#include "NotesWriterOsu.h"
static void WriteGlobalTags( RageFile &f, const Song &out, Steps* ToSave )
{
f.PutLine("osu file format v11"); // double space
f.PutLine("");
f.PutLine("[General]");
f.PutLine( ssprintf("AudioFilename: %s", out.m_sMusicFile.c_str()) );
f.PutLine("AudioLeadIn: 1500");
f.PutLine( ssprintf("PreviewTime: %.0f", out.m_fMusicSampleStartSeconds * 1000) );
f.PutLine("Countdown: 0");
f.PutLine("SampleSet: None");
f.PutLine("StackLeniency: 0.7");
f.PutLine("Mode: 3");
f.PutLine("LetterboxInBreaks: 0");
f.PutLine("");
f.PutLine("[Editor]");
f.PutLine("DistanceSnapping: 0.9");
f.PutLine("BeatDivisor: 4");
f.PutLine("GridSize: 16");
f.PutLine("CurrentTime: 0");
f.PutLine("");
f.PutLine("[Metadata]");
f.PutLine( ssprintf("Title: %s", out.m_sMainTitleTranslit.length() ? out.m_sMainTitleTranslit.c_str() : out.m_sMainTitle.c_str() ) );
f.PutLine( ssprintf("TitleUnicode: %s", out.m_sMainTitle.c_str()) );
f.PutLine( ssprintf("Artist: %s", out.m_sArtistTranslit.length() ? out.m_sArtistTranslit.c_str() : out.m_sArtist.c_str() ) );
f.PutLine( ssprintf("ArtistUnicode: %s", out.m_sArtist.c_str()) );
f.PutLine( ssprintf("Creator: %s", out.m_sCredit.length() ? out.m_sCredit.c_str() : "Stepmania 5" ) );
f.PutLine( ssprintf("Version: %s", ToSave->GetChartName().c_str()) );
f.PutLine( "Source: " );
f.PutLine( "Tags: " );
// fixme: what if we want to save the original .osu's beatmap IDs?
// does it get updated automatically on osu's end?
f.PutLine( "BeatmapID:0" );
f.PutLine( "BeatmapSetID:-1");
f.PutLine("");
f.PutLine( "[Difficulty]" );
f.PutLine( "HPDrainRate: 6" );
f.PutLine( ssprintf("CircleSize: %i", ToSave->GetNoteData().GetNumTracks()) );
f.PutLine( "OverallDifficulty: 5" );
f.PutLine( "ApproachRate: 9" );
f.PutLine( "SliderMultiplier: 1.4" );
f.PutLine( "SliderTickRate: 1" );
f.PutLine("");
RString bgp = out.GetBackgroundPath().substr(
out.GetBackgroundPath().find_last_of("/") + 1, // skip the / itself
out.GetBackgroundPath().length() - out.GetBackgroundPath().find_last_of("/") );
f.PutLine("[Events]");
f.PutLine("// Background and Video events");
f.PutLine( ssprintf("0,0,\"%s\"", bgp.c_str() ) );
f.PutLine( "// Break Periods");
f.PutLine("// Storyboard Layer 0 (Background)");
f.PutLine("// Storyboard Layer 1 (Fail)");
f.PutLine("// Storyboard Layer 2 (Pass)");
f.PutLine("// Storyboard Layer 3 (Foreground)");
f.PutLine("// Storyboard Sound Samples");
// todo: add autoplaykeysounds here
f.PutLine("// Background Colour Transformations");
f.PutLine("3,100,163,162,255");
f.PutLine("");
f.Flush();
}
static void WriteTimingData( RageFile &f, const Song &out, Steps* ToSave )
{
const TimingData &timing = ToSave->m_Timing;
f.PutLine("[TimingPoints]");
// I assume these are sorted. It wouldn't be hard to sort them myself given the need.
vector <TimingSegment*> segments = timing.GetTimingSegments(SEGMENT_BPM);
vector <TimingSegment*> stopsegments = timing.GetTimingSegments(SEGMENT_STOP);
vector <TimingSegment*> scrollsegments = timing.GetTimingSegments(SEGMENT_SCROLL);
for (int i = 0; i < segments.size(); i++)
{
float time = timing.GetElapsedTimeFromBeat(segments.at(i)->GetBeat()) * 1000;
float beatspace = 1000 / ((BPMSegment*)segments.at(i))->GetBPS();
f.PutLine( ssprintf("%.0f,%.4f,4,1,0,15,1,0\n",
time, // time for this section
beatspace)
); // 1000 / bps = ms per beat, what we need right here.
}
// it's kinda stupid to use stops if you can use speeds, assuming you're charting for osu!mania.
// i don't recommend it, so restoring speed probably won't happen soon.
for (int i = 0; i < stopsegments.size(); i++)
{
float time = timing.GetElapsedTimeFromBeat(stopsegments.at(i)->GetBeat()) * 1000;
float sectionmultiplier = -100 / 0.1; // lol maths
f.PutLine( ssprintf("%.0f,%.4f,4,1,0,15,0,0",
time,
sectionmultiplier) );
// todo: check speeds and restore that.
float stoptimefinish = time + ((StopSegment*)stopsegments.at(i))->GetPause() * 1000;
f.PutLine( ssprintf("%.0f,%.4f,4,1,0,15,0,0",
stoptimefinish,
-100) );
}
// speeds (recommended way to approach o!m charting anyway) minimum value supported by osu is 0.1
// maximum is like 10 or something.
for (int i = 0; i < scrollsegments.size(); i++)
{
float time = timing.GetElapsedTimeFromBeat(scrollsegments.at(i)->GetBeat()) * 1000;
float sectionmultiplier = -100 / ((ScrollSegment*)scrollsegments.at(i))->GetRatio(); // lol maths, again
// it's kinda stupid that osu uses -100/x instead of x/-100 but hell if i know. it doesn't really matter.
f.PutLine( ssprintf("%.0f,%.4f,4,1,0,15,0,0",
time,
sectionmultiplier) );
}
f.PutLine("");
f.Flush();
}
static int TrackToXPos(int totaltracks, int track)
{
double base = (512.0/totaltracks);
double minus = (256.0/totaltracks);
return (base * (track + 1) - minus);
}
static void WriteSteps( RageFile &f, const Song &out, Steps* ToSave )
{
vector<NoteData> parts;
float fLastBeat = -1.0f;
const TimingData &timing = ToSave->m_Timing;
float PendingTracks[16]; // For hold notes.
for (int i = 0; i < 16; i++)
{
PendingTracks[i] = 0;
}
NoteDataUtil::SplitCompositeNoteData( ToSave->GetNoteData(), parts );
FOREACH( NoteData, parts, nd )
{
NoteDataUtil::InsertHoldTails( *nd );
fLastBeat = max( fLastBeat, nd->GetLastBeat() );
}
int iLastMeasure = int( fLastBeat/4 );
f.PutLine("[HitObjects]");
FOREACH( NoteData, parts, nd )
{
for( int m = 0; m <= iLastMeasure; ++m ) // foreach measure
{
NoteType nt = NoteDataUtil::GetSmallestNoteTypeForMeasure( *nd, m );
int iRowSpacing;
if( nt == NoteType_Invalid )
iRowSpacing = 1;
else
iRowSpacing = lrintf( NoteTypeToBeat(nt) * ROWS_PER_BEAT );
const int iMeasureStartRow = m * 192;
const int iMeasureLastRow = (m+1) * 192 - 1;
for( int r=iMeasureStartRow; r<=iMeasureLastRow; r+=iRowSpacing )
{
for( int t = 0; t < nd->GetNumTracks(); ++t )
{
const TapNote &tn = nd->GetTapNote(t, r);
char c;
int xpos = TrackToXPos(nd->GetNumTracks(), t);
float ftiming = timing.GetElapsedTimeFromBeat(NoteRowToBeat(r)) * 1000;
switch( tn.type )
{
case TapNote::tap: // x, y, time, notetype, hitsound, whatever whatever whatever.
f.PutLine( ssprintf("%i,192,%.0f,1,0,0:0:0", xpos, ftiming) );
break;
case TapNote::hold_head:
PendingTracks[t] = timing.GetElapsedTimeFromBeat(NoteRowToBeat(r)) * 1000;
break;
case TapNote::hold_tail:
f.PutLine(
ssprintf("%d,0,%.0f,128,0,%.0f,1:0:0\n",
TrackToXPos(nd->GetNumTracks(), t),
PendingTracks[t],
timing.GetElapsedTimeFromBeat(NoteRowToBeat(r)) * 1000
));
} // switch (tn.type)
} // for t=0..
} // for r=0..
} // for m = 0
} // foreach..
f.Flush();
}
bool NotesWriterOSU::Write( const Song &out, const vector<Steps*>& vpStepsToSave )
{
int flags = RageFile::WRITE;
int totalSteps = vpStepsToSave.size();
// ragefiles can't be vector'd :V -az
RageFile f;
for (int i = 0; i < totalSteps; i++)
{
RString filename = out.GetSongDir() + out.GetDisplayArtist() + " - " + // directory/artist - title [difficulty].osu
out.GetDisplayMainTitle() + " [" +
vpStepsToSave.at(i)->GetChartName() + "].osu";
if( !f.Open(filename, flags ) )
{
LOG->UserLog( "Song file", filename, "couldn't be opened for writing: %s", f.GetError().c_str() );
return false;
}
WriteGlobalTags(f, out, vpStepsToSave.at(i));
WriteTimingData(f, out, vpStepsToSave.at(i));
WriteSteps(f, out, vpStepsToSave.at(i));
f.Close();
}
return true;
}
#ifndef NOTES_WRITER_OSU_H
#define NOTES_WRITER_OSU_H
class Song;
/** @brief Writes a Song to a .DWI file. */
namespace NotesWriterOSU
{
/**
* @brief Write the song out to a file.
* @param sPath the path to write the file.
* @param out the Song to be written out.
* @return its success or failure. */
bool Write( const Song &out, const vector<Steps*>& vpStepsToSave );
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment