Files
itgmania212121/stepmania/src/NotesWriterDWI.cpp
T

426 lines
12 KiB
C++
Raw Normal View History

2003-02-16 04:01:45 +00:00
#include "global.h"
#include "NotesWriterDWI.h"
#include "NoteTypes.h"
#include "NoteData.h"
#include "RageUtil.h"
#include "RageLog.h"
2003-07-22 07:47:27 +00:00
#include "RageFile.h"
2003-08-07 06:36:34 +00:00
#include "NoteDataUtil.h"
/* Output is an angle bracket expression without angle brackets, eg. "468". */
CString NotesWriterDWI::NotesToDWIString( const TapNote cNoteCols[6] )
{
const char dirs[] = { '4', 'C', '2', '8', 'D', '6' };
CString taps, holds, ret;
for( int col = 0; col < 6; ++col )
{
2003-11-12 08:13:02 +00:00
switch( cNoteCols[col] )
{
case TAP_EMPTY:
case TAP_MINE:
continue;
2003-11-12 08:13:02 +00:00
}
if( cNoteCols[col] == TAP_HOLD_HEAD )
holds += dirs[col];
else
taps += dirs[col];
}
if( holds.size() + taps.size() == 0 )
return "0";
// CString combine = taps;
// for( unsigned i = 0; i < holds.size(); ++i )
// combine += ssprintf("%c!%c", holds[i], holds[i]);
// if( holds.size() + taps.size() > 1 )
// combine = ssprintf("<%s>", combine.c_str() );
// return combine;
/* More than one. */
return OptimizeDWIString( holds, taps );
/* struct DWICharLookup {
char c;
bool bCol[6];
} const lookup[] = {
2002-11-16 09:12:55 +00:00
{ '0', { 0, 0, 0, 0, 0, 0 } },
{ '1', { 1, 0, 1, 0, 0, 0 } },
{ '2', { 0, 0, 1, 0, 0, 0 } },
{ '3', { 0, 0, 1, 0, 0, 1 } },
{ '4', { 1, 0, 0, 0, 0, 0 } },
{ '6', { 0, 0, 0, 0, 0, 1 } },
{ '7', { 1, 0, 0, 1, 0, 0 } },
{ '8', { 0, 0, 0, 1, 0, 0 } },
{ '9', { 0, 0, 0, 1, 0, 1 } },
{ 'A', { 0, 0, 1, 1, 0, 0 } },
{ 'B', { 1, 0, 0, 0, 0, 1 } },
{ 'C', { 0, 1, 0, 0, 0, 0 } },
{ 'D', { 0, 0, 0, 0, 1, 0 } },
{ 'E', { 1, 1, 0, 0, 0, 0 } },
{ 'F', { 0, 1, 1, 0, 0, 0 } },
{ 'G', { 0, 1, 0, 1, 0, 0 } },
{ 'H', { 0, 1, 0, 0, 0, 1 } },
{ 'I', { 1, 0, 0, 0, 1, 0 } },
{ 'J', { 0, 0, 1, 0, 1, 0 } },
{ 'K', { 0, 0, 0, 1, 1, 0 } },
{ 'L', { 0, 0, 0, 0, 1, 1 } },
{ 'M', { 0, 1, 0, 0, 1, 0 } },
};
const int iNumLookups = sizeof(lookup) / sizeof(*lookup);
for( int i=0; i<iNumLookups; i++ )
{
const DWICharLookup& l = lookup[i];
if( l.bCol[0]==bCol1 && l.bCol[1]==bCol2 && l.bCol[2]==bCol3 && l.bCol[3]==bCol4 && l.bCol[4]==bCol5 && l.bCol[5]==bCol6 )
return l.c;
}
LOG->Warn( "Failed to find the DWI character for the row %d %d %d %d %d %d", bCol1, bCol2, bCol3, bCol4, bCol5, bCol6 );
return '0';*/
}
2002-11-03 05:02:48 +00:00
CString NotesWriterDWI::NotesToDWIString( TapNote cNoteCol1, TapNote cNoteCol2, TapNote cNoteCol3, TapNote cNoteCol4, TapNote cNoteCol5, TapNote cNoteCol6 )
{
const TapNote cNoteCols[6] = {
cNoteCol1, cNoteCol2, cNoteCol3, cNoteCol4, cNoteCol5, cNoteCol6
};
return NotesToDWIString( cNoteCols );
}
2002-11-03 05:02:48 +00:00
CString NotesWriterDWI::NotesToDWIString( TapNote cNoteCol1, TapNote cNoteCol2, TapNote cNoteCol3, TapNote cNoteCol4 )
{
2002-11-03 05:02:48 +00:00
return NotesToDWIString( cNoteCol1, TAP_EMPTY, cNoteCol2, cNoteCol3, TAP_EMPTY, cNoteCol4 );
}
char NotesWriterDWI::OptimizeDWIPair( char c1, char c2 )
{
typedef pair<char,char> cpair;
static map< cpair, char > joins;
static bool Initialized = false;
if(!Initialized)
{
Initialized = true;
/* The first character in the pair is always the lowest. */
joins[ cpair('2', '4') ] = '1';
joins[ cpair('2', '6') ] = '3';
joins[ cpair('4', '8') ] = '7';
joins[ cpair('6', '8') ] = '9';
joins[ cpair('2', '8') ] = 'A';
joins[ cpair('4', '6') ] = 'B';
joins[ cpair('C', 'D') ] = 'M';
joins[ cpair('4', 'C') ] = 'E';
joins[ cpair('2', 'C') ] = 'F';
joins[ cpair('8', 'C') ] = 'G';
joins[ cpair('6', 'C') ] = 'H';
joins[ cpair('4', 'D') ] = 'I';
joins[ cpair('2', 'D') ] = 'J';
joins[ cpair('8', 'D') ] = 'K';
joins[ cpair('6', 'D') ] = 'L';
}
if( c1 > c2 )
swap( c1, c2 );
map< cpair, char >::const_iterator it = joins.find( cpair(c1, c2) );
ASSERT( it != joins.end() );
return it->second;
}
CString NotesWriterDWI::OptimizeDWIString( CString holds, CString taps )
{
/* First, sort the holds and taps in ASCII order. This puts 2468 first.
* This way 1379 combinations will always be found first, so we'll always
* do eg. 1D, not 2I. */
sort( holds.begin(), holds.end() );
sort( taps.begin(), taps.end() );
/* Combine characters as much as possible. */
CString comb_taps, comb_holds;
/* 24 -> 1 */
while( taps.size() > 1 )
{
comb_taps += OptimizeDWIPair( taps[0], taps[1] );
taps.erase(0, 2);
}
/* 2!24!4 -> 1!1 */
while( holds.size() > 1 )
{
const char to = OptimizeDWIPair( holds[0], holds[1] );
holds.erase(0, 2);
comb_holds += ssprintf( "%c!%c", to, to );
}
ASSERT( taps.size() <= 1 );
ASSERT( holds.size() <= 1 );
/* 24!4 -> 1!4 */
while( holds.size() == 1 && taps.size() == 1 )
{
const char to = OptimizeDWIPair( taps[0], holds[0] );
comb_holds += ssprintf( "%c!%c", to, holds[0] );
taps.erase(0, 1);
holds.erase(0, 1);
}
/* Now we have at most one single tap and one hold remaining, and any
* number of taps and holds in comb_taps and comb_holds. */
CString ret;
ret += taps;
ret += comb_taps;
if( holds.size() == 1 )
ret += ssprintf( "%c!%c", holds[0], holds[0] );
ret += comb_holds;
if( ret.size() == 1 || (ret.size() == 3 && ret[1] == '!') )
return ret;
else
return ssprintf( "<%s>", ret.c_str() );
}
2003-08-03 00:13:55 +00:00
void NotesWriterDWI::WriteDWINotesField( FILE* fp, const Steps &out, int start )
{
NoteData notedata;
out.GetNoteData( &notedata );
notedata.ConvertHoldNotesTo2sAnd3s();
const int iLastMeasure = int( notedata.GetLastBeat()/BEATS_PER_MEASURE );
2002-09-09 02:23:03 +00:00
for( int m=0; m<=iLastMeasure; m++ ) // foreach measure
{
2002-12-13 22:07:59 +00:00
NoteType nt = NoteDataUtil::GetSmallestNoteTypeForMeasure( notedata, m );
2002-09-09 02:23:03 +00:00
double fCurrentIncrementer;
switch( nt )
{
2002-09-09 02:23:03 +00:00
case NOTE_TYPE_4TH:
case NOTE_TYPE_8TH:
2003-07-07 00:16:52 +00:00
fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE;
2002-09-09 02:23:03 +00:00
break;
case NOTE_TYPE_12TH:
2002-09-11 04:49:07 +00:00
case NOTE_TYPE_24TH:
2002-09-09 02:23:03 +00:00
fprintf( fp, "[" );
fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE;
break;
case NOTE_TYPE_16TH:
fprintf( fp, "(" );
fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE;
break;
2002-09-11 04:49:07 +00:00
case NOTE_TYPE_32ND:
case NOTE_TYPE_64TH:
2002-09-11 04:49:07 +00:00
fprintf( fp, "{" );
fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE;
2002-09-09 02:23:03 +00:00
break;
2003-11-04 18:28:06 +00:00
default:
ASSERT(0);
// fall though
case NOTE_TYPE_48TH:
2003-11-13 07:40:49 +00:00
case NOTE_TYPE_192ND:
2003-07-06 23:37:16 +00:00
case NOTE_TYPE_INVALID:
// since, for whatever reason, the only way to do
// 48ths is through a block of 192nds...
2003-07-06 23:37:16 +00:00
fprintf( fp, "`" );
fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE;
break;
2002-09-09 02:23:03 +00:00
}
2002-09-09 02:23:03 +00:00
double fFirstBeatInMeasure = m * BEATS_PER_MEASURE;
double fLastBeatInMeasure = (m+1) * BEATS_PER_MEASURE;
2002-09-11 04:49:07 +00:00
for( double b=fFirstBeatInMeasure; b<=fLastBeatInMeasure-1/64.0f; b+=fCurrentIncrementer ) // need the -0.0001 to account for rounding errors
2002-09-09 02:23:03 +00:00
{
int row = BeatToNoteRow( (float)b );
2003-07-06 23:37:16 +00:00
CString str;
2003-08-07 06:36:34 +00:00
switch( out.m_StepsType )
{
2003-08-07 06:16:17 +00:00
case STEPS_TYPE_DANCE_SINGLE:
case STEPS_TYPE_DANCE_COUPLE:
case STEPS_TYPE_DANCE_DOUBLE:
2003-07-06 23:37:16 +00:00
str = NotesToDWIString(
2002-10-25 04:37:00 +00:00
notedata.GetTapNote(start+0, row),
notedata.GetTapNote(start+1, row),
notedata.GetTapNote(start+2, row),
2003-07-06 23:37:16 +00:00
notedata.GetTapNote(start+3, row) );
2002-09-11 04:49:07 +00:00
// Blank out the notes so we don't write them again if the incrementer is small
2002-10-25 04:37:00 +00:00
notedata.SetTapNote(start+0, row, TAP_EMPTY);
notedata.SetTapNote(start+1, row, TAP_EMPTY);
notedata.SetTapNote(start+2, row, TAP_EMPTY);
notedata.SetTapNote(start+3, row, TAP_EMPTY);
break;
2003-08-07 06:16:17 +00:00
case STEPS_TYPE_DANCE_SOLO:
2003-07-06 23:37:16 +00:00
str = NotesToDWIString(
2002-10-25 04:37:00 +00:00
notedata.GetTapNote(0, row),
notedata.GetTapNote(1, row),
notedata.GetTapNote(2, row),
notedata.GetTapNote(3, row),
notedata.GetTapNote(4, row),
2003-07-06 23:37:16 +00:00
notedata.GetTapNote(5, row) );
2002-09-11 04:49:07 +00:00
// Blank out the notes so we don't write them again if the incrementer is small
2002-10-25 04:37:00 +00:00
notedata.SetTapNote(start+0, row, TAP_EMPTY);
notedata.SetTapNote(start+1, row, TAP_EMPTY);
notedata.SetTapNote(start+2, row, TAP_EMPTY);
notedata.SetTapNote(start+3, row, TAP_EMPTY);
notedata.SetTapNote(start+4, row, TAP_EMPTY);
notedata.SetTapNote(start+5, row, TAP_EMPTY);
break;
2002-09-11 04:49:07 +00:00
default:
ASSERT(0); // not a type supported by DWI. We shouldn't have called in here if that's the case
}
2003-07-06 23:37:16 +00:00
fprintf( fp, "%s", str.c_str() );
}
2002-09-09 02:23:03 +00:00
switch( nt )
{
case NOTE_TYPE_4TH:
case NOTE_TYPE_8TH:
break;
case NOTE_TYPE_12TH:
2002-09-11 04:49:07 +00:00
case NOTE_TYPE_24TH:
2002-09-09 02:23:03 +00:00
fprintf( fp, "]" );
break;
case NOTE_TYPE_16TH:
fprintf( fp, ")" );
break;
2003-07-06 23:37:16 +00:00
case NOTE_TYPE_32ND:
case NOTE_TYPE_64TH:
2003-07-06 23:37:16 +00:00
fprintf( fp, "}" );
break;
case NOTE_TYPE_48TH:
2003-11-13 07:40:49 +00:00
case NOTE_TYPE_192ND:
2002-09-09 02:23:03 +00:00
case NOTE_TYPE_INVALID:
2003-07-06 23:37:16 +00:00
fprintf( fp, "'" );
2002-09-09 02:23:03 +00:00
break;
default:
ASSERT(0);
// fall though
2002-09-09 02:23:03 +00:00
}
fprintf( fp, "\n" );
}
}
2003-08-03 00:13:55 +00:00
bool NotesWriterDWI::WriteDWINotesTag( FILE* fp, const Steps &out )
2002-09-09 02:23:03 +00:00
{
/* Flush dir cache when writing steps, so the old size isn't cached. */
FlushDirCache();
2003-08-03 00:13:55 +00:00
LOG->Trace( "Steps::WriteDWINotesTag" );
2002-09-09 02:23:03 +00:00
2003-08-07 06:36:34 +00:00
switch( out.m_StepsType )
2002-09-09 02:23:03 +00:00
{
2003-08-07 06:16:17 +00:00
case STEPS_TYPE_DANCE_SINGLE: fprintf( fp, "#SINGLE:" ); break;
case STEPS_TYPE_DANCE_COUPLE: fprintf( fp, "#COUPLE:" ); break;
case STEPS_TYPE_DANCE_DOUBLE: fprintf( fp, "#DOUBLE:" ); break;
case STEPS_TYPE_DANCE_SOLO: fprintf( fp, "#SOLO:" ); break;
2002-09-09 02:23:03 +00:00
default: return false; // not a type supported by DWI
}
switch( out.GetDifficulty() )
2002-09-09 02:23:03 +00:00
{
case DIFFICULTY_BEGINNER: fprintf( fp, "BEGINNER:" ); break;
case DIFFICULTY_EASY: fprintf( fp, "BASIC:" ); break;
case DIFFICULTY_MEDIUM: fprintf( fp, "ANOTHER:" ); break;
case DIFFICULTY_HARD: fprintf( fp, "MANIAC:" ); break;
case DIFFICULTY_CHALLENGE: fprintf( fp, "SMANIAC:" ); break;
default: ASSERT(0); return false;
2002-09-09 02:23:03 +00:00
}
2003-01-02 22:10:51 +00:00
fprintf( fp, "%d:\n", out.GetMeter() );
2002-09-09 02:23:03 +00:00
return true;
}
bool NotesWriterDWI::Write( CString sPath, const Song &out )
{
FILE* fp = fopen( sPath, "w" );
if( fp == NULL )
2003-04-25 00:01:35 +00:00
RageException::Throw( "Error opening song file '%s' for writing.", sPath.c_str() );
2003-02-11 23:52:18 +00:00
/* Write transliterations, if we have them, since DWI doesn't support UTF-8. */
fprintf( fp, "#TITLE:%s;\n", out.GetFullTranslitTitle().c_str() );
2003-04-25 00:01:35 +00:00
fprintf( fp, "#ARTIST:%s;\n", out.GetTranslitArtist().c_str() );
ASSERT( out.m_BPMSegments[0].m_fStartBeat == 0 );
2003-10-06 04:31:44 +00:00
fprintf( fp, "#FILE:%s;\n", out.m_sMusicFile.c_str() );
fprintf( fp, "#BPM:%.3f;\n", out.m_BPMSegments[0].m_fBPM );
2002-09-09 02:59:48 +00:00
fprintf( fp, "#GAP:%d;\n", int(-roundf( out.m_fBeat0OffsetInSeconds*1000 )) );
fprintf( fp, "#SAMPLESTART:%.3f;\n", out.m_fMusicSampleStartSeconds );
fprintf( fp, "#SAMPLELENGTH:%.3f;\n", out.m_fMusicSampleLengthSeconds );
2003-02-28 08:55:56 +00:00
if( out.m_sCDTitleFile.size() )
2003-04-25 00:01:35 +00:00
fprintf( fp, "#CDTITLE:%s;\n", out.m_sCDTitleFile.c_str() );
switch( out.m_DisplayBPMType )
{
case Song::DISPLAY_ACTUAL:
// write nothing
break;
case Song::DISPLAY_SPECIFIED:
2003-06-16 17:28:58 +00:00
if( out.m_fSpecifiedBPMMin == out.m_fSpecifiedBPMMax )
fprintf( fp, "#DISPLAYBPM:%i;\n", (int) out.m_fSpecifiedBPMMin );
else
2003-06-16 17:28:58 +00:00
fprintf( fp, "#DISPLAYBPM:%i..%i;\n", (int) out.m_fSpecifiedBPMMin, (int) out.m_fSpecifiedBPMMax );
break;
case Song::DISPLAY_RANDOM:
fprintf( fp, "#DISPLAYBPM:*" );
break;
}
if( !out.m_StopSegments.empty() )
{
2002-09-09 02:33:57 +00:00
fprintf( fp, "#FREEZE:" );
for( unsigned i=0; i<out.m_StopSegments.size(); i++ )
2002-09-09 02:33:57 +00:00
{
const StopSegment &fs = out.m_StopSegments[i];
fprintf( fp, "%.3f=%.3f", BeatToNoteRow( fs.m_fStartBeat ) * 4.0f / ROWS_PER_BEAT,
2002-09-09 02:59:48 +00:00
roundf(fs.m_fStopSeconds*1000) );
if( i != out.m_StopSegments.size()-1 )
2002-09-09 02:33:57 +00:00
fprintf( fp, "," );
}
fprintf( fp, ";\n" );
}
if( out.m_BPMSegments.size() > 1)
{
2002-09-09 02:33:57 +00:00
fprintf( fp, "#CHANGEBPM:" );
for( unsigned i=1; i<out.m_BPMSegments.size(); i++ )
2002-09-09 02:33:57 +00:00
{
const BPMSegment &bs = out.m_BPMSegments[i];
fprintf( fp, "%.3f=%.3f", BeatToNoteRow( bs.m_fStartBeat ) * 4.0f / ROWS_PER_BEAT, bs.m_fBPM );
if( i != out.m_BPMSegments.size()-1 )
2002-09-09 02:33:57 +00:00
fprintf( fp, "," );
}
fprintf( fp, ";\n" );
}
2002-09-09 02:33:57 +00:00
for( unsigned i=0; i<out.m_apNotes.size(); i++ )
2002-09-09 02:23:03 +00:00
{
2003-02-04 20:01:26 +00:00
if( out.m_apNotes[i]->IsAutogen() )
2002-10-08 08:22:04 +00:00
continue; // don't save autogen notes
2002-09-09 02:23:03 +00:00
if(!WriteDWINotesTag( fp, *out.m_apNotes[i] ))
continue;
WriteDWINotesField( fp, *out.m_apNotes[i], 0 );
2003-08-07 06:36:34 +00:00
if(out.m_apNotes[i]->m_StepsType==STEPS_TYPE_DANCE_DOUBLE ||
out.m_apNotes[i]->m_StepsType==STEPS_TYPE_DANCE_COUPLE)
2002-09-09 02:23:03 +00:00
{
fprintf( fp, ":\n" );
WriteDWINotesField( fp, *out.m_apNotes[i], 4 );
}
fprintf( fp, ";\n" );
}
fclose( fp );
return true;
}
/*
* Copyright (c) 2003 by the person(s) listed below. All rights reserved.
* Chris Danford
* Glenn Maynard
*/