diff --git a/stepmania/Docs/Licenses.txt b/stepmania/Docs/Licenses.txt index 89d9cfaf51..c79f2921f3 100644 --- a/stepmania/Docs/Licenses.txt +++ b/stepmania/Docs/Licenses.txt @@ -124,3 +124,14 @@ The MAD library (included with most Windows binary distributions), and interface code in RageSoundReader_MP3, is under the GPL, available in Copying.MAD. Support for this library can be disabled at compile-time. +****************************************************************************** + +NotesLoaderMidi contains code from STK under the following license: + +Copyright 2003 Gary P. Scavone + +LEGAL AND ETHICAL: + +This software was designed and created to be made publicly available for +free, primarily for academic purposes, so if you use it, pass it on with +this documentation, and for free. diff --git a/stepmania/src/NotesLoader.cpp b/stepmania/src/NotesLoader.cpp index 8adba8870e..4c4ebf0e57 100644 --- a/stepmania/src/NotesLoader.cpp +++ b/stepmania/src/NotesLoader.cpp @@ -4,6 +4,7 @@ #include "NotesLoaderDWI.h" #include "NotesLoaderBMS.h" #include "NotesLoaderKSF.h" +#include "NotesLoaderMidi.h" void NotesLoader::GetMainAndSubTitlesFromFullTitle( const RString &sFullTitle, RString &sMainTitleOut, RString &sSubTitleOut ) { @@ -44,6 +45,9 @@ bool NotesLoader::LoadFromDir( const RString &sPath, Song &out, set &Bl KSFLoader::GetApplicableFiles( sPath, list ); if( !list.empty() ) return KSFLoader::LoadFromDir( sPath, out ); + MidiLoader::GetApplicableFiles( sPath, list ); + if( !list.empty() ) + return MidiLoader::LoadFromDir( sPath, out ); return false; } diff --git a/stepmania/src/NotesLoaderMidi.cpp b/stepmania/src/NotesLoaderMidi.cpp new file mode 100644 index 0000000000..b4240e03af --- /dev/null +++ b/stepmania/src/NotesLoaderMidi.cpp @@ -0,0 +1,810 @@ +/* + * Header/track parsing adapted from STK's FileInMidi: http://ccrma.stanford.edu/software/stk/ + * STK license documentation is below near the MidiFileIn class. ("If you make compositions with it, put us in the program notes") + + * MIDI event info is explained here: http://www.sonicspot.com/guide/midifiles.html + + * Explanation of MIDI "running status" here: http://www.borg.com/~jglatt/tech/midispec/run.htm + + * Guitar track mappings are explained here: http://www.scorehero.com/forum/viewtopic.php?t=1179 + + STK license documentation: + DISCLAIMER: + You probably already guessed this, but just to be sure, we don't guarantee anything works. :-) It's free ... what do you expect? If you find a bug, please let us know and we'll try to correct it. You can also make suggestions, but again, no guarantees. Send email to the mail list. + + LEGAL AND ETHICAL: + This software was designed and created to be made publicly available for free, primarily for academic purposes, so if you use it, pass it on with this documentation, and for free. + If you make a million dollars with it, give us some. If you make compositions with it, put us in the program notes. + Some of the concepts are covered by various patents, some known to us and likely others which are unknown. Many of the ones known to us are administered by the Stanford Office of Technology and Licensing. + The good news is that large hunks of the techniques used here are public domain. To avoid subtle legal issues, we'll not state what's freely useable here, but we'll try to note within the various classes where certain things are likely to be protected by patents. + */ + +#include "global.h" +#include "NotesLoaderMidi.h" +#include "Song.h" +#include "Steps.h" +#include "RageLog.h" +#include "NoteData.h" +#include "IniFile.h" +#include "RageFile.h" + +/**********************************************************************/ +/*! \class MidiFileIn +\brief A standard MIDI file reading/parsing class. + +This class can be used to read events from a standard MIDI file. +Event bytes are copied to a C++ vector and must be subsequently +interpreted by the user. The function getNextMidiEvent() skips +meta and sysex events, returning only MIDI channel messages. +Event delta-times are returned in the form of "ticks" and a +function is provided to determine the current "seconds per tick". +Tempo changes are internally tracked by the class and reflected in +the values returned by the function getTickSeconds(). + +by Gary P. Scavone, 2003. +*/ +/**********************************************************************/ +class MidiFileIn +{ +public: + //! Default constructor. + /*! + If an error occurs while opening or parsing the file header, an + StkError exception will be thrown. + */ + MidiFileIn() {} + void Load( RString fileName ); + + //! Class destructor. + ~MidiFileIn(); + + MidiFileIn( const MidiFileIn &other ) {} + + //! Return the MIDI file format (0, 1, or 2). + int getFileFormat() const; + + //! Return the number of tracks in the MIDI file. + unsigned int getNumberOfTracks() const; + + //! Return the MIDI file division value from the file header. + /*! + Note that this value must be "parsed" in accordance with the + MIDI File Specification. In particular, if the MSB is set, the + file uses time-code representations for delta-time values. + */ + int getDivision() const; + + //! Move the specified track event reader to the beginning of its track. + /*! + The relevant track tempo value is reset as well. If an invalid + track number is specified, an StkError exception will be thrown. + */ + void rewindTrack( unsigned int track = 0 ); + + //! Get the current value, in seconds, of delta-time ticks for the specified track. + /*! + This value can change as events are read (via "Set Tempo" + Meta-Events). Therefore, one should call this function after + every call to getNextEvent() or getNextMidiEvent(). If an + invalid track number is specified, an StkError exception will be + thrown. + */ + double getTickSeconds( unsigned int track = 0 ); + + //! Fill the user-provided vector with the next event in the specified track and return the event delta-time in ticks. + /*! + MIDI File events consist of a delta time and a sequence of event + bytes. This function returns the delta-time value and writes + the subsequent event bytes directly to the event vector. The + user must parse the event bytes in accordance with the MIDI File + Specification. All returned MIDI channel events are complete + ... a status byte is provided even when running status is used + in the file. If the track has reached its end, no bytes will be + written and the event vector size will be zero. If an invalid + track number is specified or an error occurs while reading the + file, an StkError exception will be thrown. + */ + unsigned long getNextEvent( std::vector *event, unsigned int track = 0 ); + + //! Fill the user-provided vector with the next MIDI channel event in the specified track and return the event delta time in ticks. + /*! + All returned MIDI events are complete ... a status byte is + provided even when running status is used in the file. Meta and + sysex events in the track are skipped though "Set Tempo" events + are properly parsed for use by the getTickSeconds() function. + If the track has reached its end, no bytes will be written and + the event vector size will be zero. If an invalid track number + is specified or an error occurs while reading the file, an + StkError exception will be thrown. + */ + unsigned long getNextMidiEvent( std::vector *midiEvent, unsigned int track = 0 ); + + + // This structure and the following variables are used to save and + // keep track of a format 1 tempo map (and the initial tickSeconds + // parameter for formats 0 and 2). + struct TempoChange { + unsigned long count; + double tickSeconds; + }; + std::vector tempoEvents_; + std::vector trackCounters_; + std::vector trackTempoIndex_; + +protected: + + // This protected class function is used for reading variable-length + // MIDI file values. It is assumed that this function is called with + // the file read pointer positioned at the start of a + // variable-length value. The function returns true if the value is + // successfully parsed. Otherwise, it returns false. + bool readVariableLength( unsigned long *value ); + + RageFile file_; + unsigned int nTracks_; + int format_; + int division_; + bool usingTimeCode_; + std::vector tickSeconds_; + std::vector trackPointers_; + std::vector trackOffsets_; + std::vector trackLengths_; + std::vector trackStatus_; + +}; + + + +void MidiFileIn :: Load( RString fileName ) +{ + // Attempt to open the file. + if( !file_.Open(fileName) ) + { + FAIL_M( "MidiFileIn: error opening or finding file (" + fileName + ")." ); + } + + // Parse header info. + char chunkType[4]; + int32_t length; + if( !file_.Read( chunkType, 4 ) ) + goto error; + if( !file_.Read( &length, 4 ) ) + goto error; + length = Swap32BE( length ); + if( strncmp( chunkType, "MThd", 4 ) || ( length != 6 ) ) + { + FAIL_M( "MidiFileIn: file (" + fileName + ") does not appear to be a MIDI file!" ); + } + + // Read the MIDI file format. + int16_t data; + if( !file_.Read( &data, 2 ) ) + goto error; + data = Swap16BE( data ); + if( data < 0 || data > 2 ) + { + FAIL_M( "MidiFileIn: the file (" + fileName + ") format is invalid!" ); + } + format_ = data; + + // Read the number of tracks. + if( !file_.Read( &data, 2 ) ) + goto error; + data = Swap16BE( data ); + if( format_ == 0 && data != 1 ) + { + FAIL_M( "MidiFileIn: invalid number of tracks (>1) for a file format = 0!" ); + } + nTracks_ = data; + + // Read the beat division. + if( !file_.Read( &data, 2 ) ) + goto error; + data = Swap16BE( data ); + division_ = (int)data; + double tickrate; + usingTimeCode_ = false; + if( data & 0x8000 ) + { + // Determine ticks per second from time-code formats. + tickrate = (double) -(data & 0x7F00); + // If frames per second value is 29, it really should be 29.97. + if( tickrate == 29.0 ) + tickrate = 29.97; + tickrate *= (data & 0x00FF); + usingTimeCode_ = true; + } + else + { + tickrate = (double) (data & 0x7FFF); // ticks per quarter note + } + + // Now locate the track offsets and lengths. If not using time + // code, we can initialize the "tick time" using a default tempo of + // 120 beats per minute. We will then check for tempo meta-events + // afterward. + for( unsigned int i=0; i event; + unsigned long count = getNextEvent( &event, 0 ); + while( event.size() ) + { + if( ( event.size() == 6 ) && ( event[0] == 0xff ) && + ( event[1] == 0x51 ) && ( event[2] == 0x03 ) ) + { + tempoEvent.count = count; + unsigned long value = ( event[3] << 16 ) + ( event[4] << 8 ) + event[5]; + tempoEvent.tickSeconds = (double) (0.000001 * value / tickrate); + if( count > tempoEvents_.back().count ) + tempoEvents_.push_back( tempoEvent ); + else + tempoEvents_.back() = tempoEvent; + } + count += getNextEvent( &event, 0 ); + } + rewindTrack( 0 ); + for( unsigned int i=0; i= nTracks_ ) + { + FAIL_M( ssprintf("MidiFileIn::getNextEvent: invalid track argument (%u).", track) ); + } + + trackPointers_[track] = trackOffsets_[track]; + trackStatus_[track] = 0; + tickSeconds_[track] = tempoEvents_[0].tickSeconds; +} + +double MidiFileIn :: getTickSeconds( unsigned int track ) +{ + // Return the current tick value in seconds for the given track. + if( track >= nTracks_ ) + { + FAIL_M( ssprintf("MidiFileIn::getTickSeconds: invalid track argument (%u).", track) ); + } + + return tickSeconds_[track]; +} + +unsigned long MidiFileIn :: getNextEvent( std::vector *event, unsigned int track ) +{ + // Fill the user-provided vector with the next event in the + // specified track (default = 0) and return the event delta time in + // ticks. This function assumes that the stored track pointer is + // positioned at the start of a track event. If the track has + // reached its end, the event vector size will be zero. + // + // If we have a format 0 or 2 file and we're not using timecode, we + // should check every meta-event for tempo changes and make + // appropriate updates to the tickSeconds_ parameter if so. + // + // If we have a format 1 file and we're not using timecode, keep a + // running sum of ticks for each track and update the tickSeconds_ + // parameter as needed based on the stored tempo map. + + if( track >= nTracks_ ) + { + FAIL_M( ssprintf("MidiFileIn::getNextEvent: invalid track argument (%u).", track) ); + } + + event->clear(); + // Check for the end of the track. + if( (trackPointers_[track] - trackOffsets_[track]) >= trackLengths_[track] ) + return 0; + + unsigned long ticks = 0; + unsigned long bytes = 0; + bool isTempoEvent = false; + + // Read the event delta time. + file_.Seek( trackPointers_[track] ); + if( !readVariableLength( &ticks ) ) + goto error; + + // Parse the event stream to determine the event length. + unsigned char c; + if( !file_.Read( (char *)&c, 1 ) ) + goto error; + switch( c ) + { + case 0xFF: // A Meta-Event + unsigned long position; + trackStatus_[track] = 0; + event->push_back( c ); + if( !file_.Read( (char *)&c, 1 ) ) + goto error; + event->push_back( c ); + if( format_ != 1 && ( c == 0x51 ) ) + isTempoEvent = true; + position = file_.Tell(); + if( !readVariableLength( &bytes ) ) + goto error; + bytes += ( (unsigned long)file_.Tell() - position ); + file_.Seek( position ); + break; + + case 0xF0: + case 0xF7: // The start or continuation of a Sysex event + trackStatus_[track] = 0; + event->push_back( c ); + position = file_.Tell(); + if( !readVariableLength( &bytes ) ) + goto error; + bytes += ( (unsigned long)file_.Tell() - position ); + file_.Seek( position ); + break; + + default: // Should be a MIDI channel event + if( c & 0x80 ) // MIDI status byte + { + if( c > 0xF0 ) + goto error; + trackStatus_[track] = c; + event->push_back( c ); + c &= 0xF0; + if( (c == 0xC0) || (c == 0xD0) ) + bytes = 1; + else + bytes = 2; + } + else if( trackStatus_[track] & 0x80 ) // Running status + { + event->push_back( trackStatus_[track] ); + event->push_back( c ); + c = trackStatus_[track] & 0xF0; + if( (c != 0xC0) && (c != 0xD0) ) + bytes = 1; + } + else + { + goto error; + } + break; + + } + + // Read the rest of the event into the event vector. + for( unsigned long i=0; ipush_back( c ); + } + + if( !usingTimeCode_ ) + { + if( isTempoEvent ) + { + // Parse the tempo event and update tickSeconds_[track]. + double tickrate = (double) (division_ & 0x7FFF); + unsigned long value = ( event->at(3) << 16 ) + ( event->at(4) << 8 ) + event->at(5); + tickSeconds_[track] = (double) (0.000001 * value / tickrate); + } + + if( format_ == 1 ) + { + // Update track counter and check the tempo map. + trackCounters_[track] += ticks; + TempoChange tempoEvent = tempoEvents_[ trackTempoIndex_[track] ]; + if( trackCounters_[track] >= tempoEvent.count ) + { + trackTempoIndex_[track]++; + tickSeconds_[track] = tempoEvent.tickSeconds; + } + } + } + + // Save the current track pointer value. + trackPointers_[track] = file_.Tell(); + + return ticks; + +error: + FAIL_M( "MidiFileIn::getNextEvent: file read error!" ); + return 0; +} + +unsigned long MidiFileIn :: getNextMidiEvent( std::vector *midiEvent, unsigned int track ) +{ + // Fill the user-provided vector with the next MIDI event in the + // specified track (default = 0) and return the event delta time in + // ticks. Meta-Events preceeding this event are skipped and ignored. + if( track >= nTracks_ ) + { + FAIL_M( ssprintf("MidiFileIn::getNextMidiEvent: invalid track argument ().", track) ); + } + + unsigned long ticks = getNextEvent( midiEvent, track ); + while( midiEvent->size() && ( midiEvent->at(0) >= 0xF0 ) ) + { + //for( unsigned int i=0; isize(); i++ ) + //std::cout << "event byte = " << i << ", value = " << (int)midiEvent->at(i) << std::endl; + ticks = getNextEvent( midiEvent, track ); + } + + //for( unsigned int i=0; isize(); i++ ) + //std::cout << "event byte = " << i << ", value = " << (int)midiEvent->at(i) << std::endl; + + return ticks; +} + +bool MidiFileIn :: readVariableLength( unsigned long *value ) +{ + // It is assumed that this function is called with the file read + // pointer positioned at the start of a variable-length value. The + // function returns "true" if the value is successfully parsed and + // "false" otherwise. + *value = 0; + char c; + + if( !file_.Read( &c, 1 ) ) + return false; + *value = (unsigned long) c; + if( *value & 0x80 ) + { + *value &= 0x7f; + do + { + if( !file_.Read( &c, 1 ) ) + return false; + *value = ( *value << 7 ) + ( c & 0x7f ); + } while( c & 0x80 ); + } + + return true; +} + +namespace Guitar +{ + enum GuitarDifficulty { easy, medium, hard, expert, NUM_GuitarDifficulty }; + enum NoteNumberType { + green, + red, + yellow, + blue, + orange, + star_power, + player1_section, + player2_section, + NUM_NoteNumberType + }; + const int NUM_FRETS = 5; + enum MidiEventType + { + note_off = 0x8, + note_on = 0x9, + note_aftertouch = 0xA, + controller = 0xB, + program_change = 0xC, + channel_aftertouch = 0xD, + pitch_bend = 0xE, + }; +}; + +using namespace Guitar; + +/* +60: guitar note GREEN, easy (C) +61: guitar note RED, easy (C#) +62: guitar note YELLOW, easy (D) +63: guitar note BLUE, easy (D#) +64: guitar note ORANGE, easy (E) +67: star power group, easy (G) +69: player 1 section, easy (A) +70: player 2 section, easy (A#) +72: guitar note GREEN, medium (C) +73: guitar note RED, medium (C#) +74: guitar note YELLOW, medium (D) +75: guitar note BLUE, medium (D#) +76: guitar note ORANGE, medium (E) +79: star power group, medium (G) +81: player 1 section, medium (A) +82: player 2 section, medium (A#) +84: guitar note GREEN, hard (C) +85: guitar note RED, hard (C#) +86: guitar note YELLOW, hard (D) +87: guitar note BLUE, hard (D#) +88: guitar note ORANGE, hard (E) +91: star power group, hard (G) +93: player 1 section, hard (A) +94: player 2 section, hard (A#) +96: guitar note GREEN, expert (C) +97: guitar note RED, expert (C#) +98: guitar note YELLOW, expert (D) +99: guitar note BLUE, expert (D#) +100: guitar note ORANGE, expert (E) +103: star power group, expert (G) +105: player 1 section, expert (A) +106: player 2 section, expert (A#) +108: vocal track (C) +*/ +static bool NoteNumberToDifficultyAndNoteNumberType( uint8_t uNoteNumber, GuitarDifficulty &gdOut, NoteNumberType &nntOut ) +{ + gdOut = (GuitarDifficulty)((uNoteNumber-60) / 12); + nntOut = (NoteNumberType)((uNoteNumber-60) % 12); + + return gdOut >= 0 && gdOut < NUM_GuitarDifficulty && + nntOut >= 0 && nntOut < NUM_NoteNumberType; +} + +// TODO: Generalize these. These values are specific to guitar midi files. +static long GUITAR_MIDI_COUNTS_PER_EIGHTH_NOTE = 240; +static long GUITAR_MIDI_COUNTS_PER_BEAT = GUITAR_MIDI_COUNTS_PER_EIGHTH_NOTE * 2; + +static int MidiCountToNoteRow( long iMidiCount ) +{ + return (iMidiCount * ROWS_PER_BEAT) / GUITAR_MIDI_COUNTS_PER_BEAT; +} + +static bool LoadFromMidi( const RString &sPath, Song &songOut ) +{ + MidiFileIn midi; + midi.Load( sPath ); + + FOREACH_CONST( MidiFileIn::TempoChange, midi.tempoEvents_, iter ) + { + BPMSegment bpmSeg; + bpmSeg.m_iStartIndex = MidiCountToNoteRow( iter->count ); + double fSecondsPerBeat = (iter->tickSeconds * GUITAR_MIDI_COUNTS_PER_BEAT); + bpmSeg.m_fBPS = 1 / fSecondsPerBeat; + + songOut.AddBPMSegment( bpmSeg ); + } + + + /* Read the MIDI events into per-NoteNumber maps. The guitar processing rules for + * MIDI events often need to look at the next or previous event with the same + * NoteNumber. This is difficult using MidiFileIn, so this intermediate processing + * step is helpful. */ + map mapCountToMidiEventType[NUM_GuitarDifficulty][NUM_NoteNumberType]; + + { + std::vector event; + int iTrack = 1; // this track should be named "T1 GEMS" + unsigned long count = midi.getNextEvent( &event, iTrack ); + double fSeconds = midi.getTickSeconds( iTrack ); + while( event.size() ) + { + MidiEventType midiEventType = (MidiEventType)(event[0] >> 4); + //uint8_t uMidiChannel = event[0] & 0xF; // currently unused + uint8_t uParam1 = event[1]; // meaning is MidiEventType-specific + //uint8_t uParam2 = event[2]; // currently unused, meaning is MidiEventType-specific + + switch( midiEventType ) + { + case note_off: + case note_on: + { + const uint8_t &uNoteNumber = uParam1; + //const uint8_t &uVelocity = uParam2; // currently unused + + GuitarDifficulty gd; + NoteNumberType nnt; + if( NoteNumberToDifficultyAndNoteNumberType( uNoteNumber, gd, nnt ) ) + mapCountToMidiEventType[gd][nnt] [count] = midiEventType; + } + break; + default: + LOG->Trace( "Unhandled MIDI event type %X", midiEventType ); + break; + } + + count += midi.getNextEvent( &event, iTrack ); + fSeconds = midi.getTickSeconds( iTrack ); + } + } + + + FOREACH_ENUM( GuitarDifficulty, gd ) + { + NoteData noteData; + noteData.SetNumTracks( NUM_FRETS ); + + FOREACH_ENUM( NoteNumberType, nnt ) + { + if( nnt > NUM_FRETS ) + continue; // data other than the frets is not handled yet + + bool bTrackIsOn = false; + long countOfLastNote = 0; + FOREACHM( long, MidiEventType, mapCountToMidiEventType[gd][nnt], iter ) + { + /* + 1) Note-on events indicate the start of a guitar note + 2) The end of a note is indicated by a corresponding note-off event or by another note-on event, + both occuring at a non-zero number of pulses after the start of the first note-on event + 3) The pulse duration of a note is determined by subtracting the timestamp of the note-on event + from the corresponding endpoint event. + 4) Notes with a pulse duration shorter than 240 pulses are considered to be non-sustained notes + + Not yet implemented: + 5) Valid non-sustained notes must have a corresponding note-off event. If a note endpoint is a + second note-on event and the duration of the note is less than 161 pulses, the game considers + the note to be an invalid note and it is ignored for all purposes (as exhibited by Cheat on + the Church) + 5) If a player section note-off event occurs more than 15 (30?) pulses prior to the endpoint of + a sustained note, the sustained note is ignored by the game for all purposes, even in single + player mode (as exhibited in the solo of You Got Another Thing Comin') + 6) At any given time stamp, note-off events will be placed before note-on events. Duplicate + note-on and note-off events within the same timestamp are ignored for purposes of determining + note endpoints. In this case, the term "duplicate" means a situation where a note-on or + note-off event specifies the same note-number as a prior event occuring at the same timestamp. + */ + MidiEventType midiEventType = iter->second; + bool bNoteHandled = false; + if( bTrackIsOn ) + { + if( midiEventType == note_off || midiEventType == note_on ) + { + // end this track + long length = iter->first - countOfLastNote; + + if( length >= 240 ) + { + TapNote tn = TAP_ORIGINAL_HOLD_HEAD; + tn.iDuration = MidiCountToNoteRow( length ); + noteData.SetTapNote( nnt, MidiCountToNoteRow(countOfLastNote), tn ); + } + + bTrackIsOn = false; + countOfLastNote = 0; + bNoteHandled = true; + } + } + else + { + if( midiEventType == note_on ) + { + // start this track + TapNote tn = TAP_ORIGINAL_TAP; + noteData.SetTapNote( nnt, MidiCountToNoteRow(iter->first), tn ); + + bTrackIsOn = true; + countOfLastNote = iter->first; + bNoteHandled = true; + } + } + + if( !bNoteHandled ) + { + LOG->Trace( "Unexpected MIDI event type %X at count %u", midiEventType, iter->first ); + } + } + } + + Steps *pSteps = new Steps; + pSteps->m_StepsType = STEPS_TYPE_GUITAR_FIVE; + pSteps->SetDifficulty( (Difficulty)(gd+1) ); + pSteps->SetNoteData( noteData ); + songOut.AddSteps( pSteps ); + } + + return true; +} + +void MidiLoader::GetApplicableFiles( const RString &sPath, vector &out ) +{ + GetDirListing( sPath + RString("*.mid"), out ); +} + +bool MidiLoader::LoadFromDir( const RString &sDir, Song &out ) +{ + LOG->Trace( "MidiLoader::LoadFromDir(%s)", sDir.c_str() ); + + // Discover song title + { + out.m_sMainTitle = Basename( sDir ); + IniFile ini; + if( ini.ReadFile(sDir+"song.ini") ) + { + ini.GetValue("song","artist",out.m_sArtist); + ini.GetValue("song","name",out.m_sMainTitle); + } + } + + vector vsFiles; + GetDirListing( sDir + RString("*.mid"), vsFiles ); + + /* We shouldn't have been called to begin with if there were no KSFs. */ + ASSERT( vsFiles.size() ); + + if( !LoadFromMidi(sDir+vsFiles[0], out) ) + return false; + + return true; +} + +/* + * (c) 2007 Chris Danford + * 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. + */ \ No newline at end of file diff --git a/stepmania/src/NotesLoaderMidi.h b/stepmania/src/NotesLoaderMidi.h new file mode 100644 index 0000000000..c298225f94 --- /dev/null +++ b/stepmania/src/NotesLoaderMidi.h @@ -0,0 +1,39 @@ +/* MidiLoader - Reads a Song from a .mid file. */ + +#ifndef NotesLoaderMidi_H +#define NotesLoaderMidi_H + +class Song; + +namespace MidiLoader +{ + void GetApplicableFiles( const RString &sPath, vector &out ); + bool LoadFromDir( const RString &sDir, Song &out ); +} + +#endif + +/* + * (c) 2001-2004 Chris Danford, Glenn Maynard + * 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. + */