game-music-emu: Merged in m3u-more-fields (pull request #34)

From f965bb340bf573e5538b3613c8b957d5b1d03b0e Mon Sep 17 00:00:00 2001
From: Wohlstand <[EMAIL REDACTED]>
Date: Wed, 8 Jun 2022 20:58:41 +0300
Subject: [PATCH] By @kode54: Add support for more track info fields

---
 gme/Gme_File.cpp     | 20 ++++++++++--
 gme/Gme_File.h       | 59 +++++++++++++++++++++--------------
 gme/M3u_Playlist.cpp | 73 ++++++++++++++++++++++++++++++++++++--------
 gme/M3u_Playlist.h   | 16 ++++++----
 4 files changed, 124 insertions(+), 44 deletions(-)

diff --git a/gme/Gme_File.cpp b/gme/Gme_File.cpp
index 22b4133..24bcada 100644
--- a/gme/Gme_File.cpp
+++ b/gme/Gme_File.cpp
@@ -183,14 +183,24 @@ blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
 	out->loop_length   = -1;
 	out->intro_length  = -1;
 	out->fade_length   = -1;
+	out->play_length   = -1;
+	out->repeat_count  = -1;
 	out->song [0]      = 0;
 	
 	out->game [0]      = 0;
 	out->author [0]    = 0;
+	out->composer [0]  = 0;
+	out->engineer [0]  = 0;
+	out->sequencer [0] = 0;
+	out->tagger [0]    = 0;
 	out->copyright [0] = 0;
+	out->date [0]      = 0;
 	out->comment [0]   = 0;
 	out->dumper [0]    = 0;
 	out->system [0]    = 0;
+	out->disc [0]      = 0;
+	out->track [0]     = 0;
+	out->ost [0]       = 0;
 	
 	copy_field_( out->system, type()->system );
 	
@@ -203,9 +213,14 @@ blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
 	{
 		M3u_Playlist::info_t const& i = playlist.info();
 		copy_field_( out->game  , i.title );
-		copy_field_( out->author, i.engineer );
-		copy_field_( out->author, i.composer );
+		copy_field_( out->author, i.artist );
+		copy_field_( out->engineer, i.engineer );
+		copy_field_( out->composer, i.composer );
+		copy_field_( out->sequencer, i.sequencer );
+		copy_field_( out->copyright, i.copyright );
 		copy_field_( out->dumper, i.ripping );
+		copy_field_( out->tagger, i.tagging );
+		copy_field_( out->date, i.date );
 		
 		M3u_Playlist::entry_t const& e = playlist [track];
 		copy_field_( out->song, e.name );
@@ -213,6 +228,7 @@ blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
 		if ( e.intro  >= 0 ) out->intro_length = e.intro;
 		if ( e.loop   >= 0 ) out->loop_length  = e.loop;
 		if ( e.fade   >= 0 ) out->fade_length  = e.fade;
+		if ( e.repeat >= 0 ) out->repeat_count = e.repeat;
 	}
 	return 0;
 }
diff --git a/gme/Gme_File.h b/gme/Gme_File.h
index 6b886e7..5bb220c 100644
--- a/gme/Gme_File.h
+++ b/gme/Gme_File.h
@@ -18,7 +18,7 @@ struct gme_type_t_
 	int track_count;            /* non-zero for formats with a fixed number of tracks */
 	Music_Emu* (*new_emu)();    /* Create new emulator for this type (useful in C++ only) */
 	Music_Emu* (*new_info)();   /* Create new info reader for this type */
-	
+
 	/* internal */
 	const char* extension_;
 	int flags_;
@@ -27,81 +27,94 @@ struct gme_type_t_
 struct track_info_t
 {
 	long track_count;
-	
+
 	/* times in milliseconds; -1 if unknown */
 	long length;
 	long intro_length;
 	long loop_length;
 	long fade_length;
-	
+	long repeat_count;
+
+	/* Length if available, otherwise intro_length+loop_length*2 if available,
+	 * otherwise a default of 150000 (2.5 minutes) */
+	long play_length;
+
 	/* empty string if not available */
 	char system    [256];
 	char game      [256];
 	char song      [256];
 	char author    [256];
+	char composer  [256];
+	char engineer  [256];
+	char sequencer [256];
+	char tagger    [256];
 	char copyright [256];
+	char date      [256];
 	char comment   [256];
 	char dumper    [256];
+	char disc      [256];
+	char track     [256];
+	char ost       [256];
 };
 enum { gme_max_field = 255 };
 
 struct Gme_File {
 public:
 // File loading
-	
+
 	// Each loads game music data from a file and returns an error if
 	// file is wrong type or is seriously corrupt. They also set warning
 	// string for minor problems.
-	
+
 	// Load from file on disk
 	blargg_err_t load_file( const char* path );
-	
+
 	// Load from custom data source (see Data_Reader.h)
 	blargg_err_t load( Data_Reader& );
-	
+
 	// Load from file already read into memory. Keeps pointer to data, so you
 	// must not free it until you're done with the file.
 	blargg_err_t load_mem( void const* data, long size );
-	
+
 	// Load an m3u playlist. Must be done after loading main music file.
 	blargg_err_t load_m3u( const char* path );
 	blargg_err_t load_m3u( Data_Reader& in );
-	
+
 	// Clears any loaded m3u playlist and any internal playlist that the music
 	// format supports (NSFE for example).
 	void clear_playlist();
-	
+
 // Informational
-	
+
 	// Type of emulator. For example if this returns gme_nsfe_type, this object
 	// is an NSFE emulator, and you can cast to an Nsfe_Emu* if necessary.
 	gme_type_t type() const;
-	
+
 	// Most recent warning string, or NULL if none. Clears current warning after
 	// returning.
 	const char* warning();
-	
+
 	// Number of tracks or 0 if no file has been loaded
 	int track_count() const;
-	
+
 	// Get information for a track (length, name, author, etc.)
 	// See gme.h for definition of struct track_info_t.
 	blargg_err_t track_info( track_info_t* out, int track ) const;
-	
+
 // User data/cleanup
-	
+
 	// Set/get pointer to data you want to associate with this emulator.
 	// You can use this for whatever you want.
 	void set_user_data( void* p )       { user_data_ = p; }
 	void* user_data() const             { return user_data_; }
-	
+
 	// Register cleanup function to be called when deleting emulator, or NULL to
 	// clear it. Passes user_data to cleanup function.
 	void set_user_cleanup( gme_user_cleanup_t func ) { user_cleanup_ = func; }
-	
+
 	bool is_archive = false;
 	virtual blargg_err_t load_archive( const char* ) { return gme_wrong_file_type; }
-	
+
 public:
 	// deprecated
 	int error_count() const; // use warning()
@@ -116,7 +129,7 @@ struct Gme_File {
 	void set_warning( const char* s )   { warning_ = s; }
 	void set_type( gme_type_t t )       { type_ = t; }
 	blargg_err_t load_remaining_( void const* header, long header_size, Data_Reader& remaining );
-	
+
 	// Overridable
 	virtual void unload();  // called before loading file and if loading fails
 	virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_()
@@ -125,14 +138,14 @@ struct Gme_File {
 	virtual void pre_load();
 	virtual void post_load_();
 	virtual void clear_playlist_() { }
-	
+
 public:
 	blargg_err_t remap_track_( int* track_io ) const; // need by Music_Emu
 private:
 	// noncopyable
 	Gme_File( const Gme_File& );
 	Gme_File& operator = ( const Gme_File& );
-	
+
 	gme_type_t type_;
 	int track_count_;
 	int raw_track_count_;
@@ -142,7 +155,7 @@ struct Gme_File {
 	M3u_Playlist playlist;
 	char playlist_warning [64];
 	blargg_vector<byte> file_data; // only if loaded into memory using default load
-	
+
 	blargg_err_t load_m3u_( blargg_err_t );
 	blargg_err_t post_load( blargg_err_t err );
 public:
diff --git a/gme/M3u_Playlist.cpp b/gme/M3u_Playlist.cpp
index abff866..dab65b9 100644
--- a/gme/M3u_Playlist.cpp
+++ b/gme/M3u_Playlist.cpp
@@ -330,12 +330,13 @@ static int parse_line( char* in, M3u_Playlist::entry_t& entry )
 	return result;
 }
 
-static void parse_comment( char* in, M3u_Playlist::info_t& info, bool first )
+static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_comment_value, bool first )
 {
 	in = skip_white( in + 1 );
 	const char* field = in;
-	while ( *in && *in != ':' )
-		in++;
+	if ( *field != '@' )
+		while ( *in && *in != ':' )
+			in++;
 	
 	if ( *in == ':' )
 	{
@@ -343,10 +344,13 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, bool first )
 		if ( *text )
 		{
 			*in = 0;
-			     if ( !strcmp( "Composer", field ) ) info.composer = text;
-			else if ( !strcmp( "Engineer", field ) ) info.engineer = text;
-			else if ( !strcmp( "Ripping" , field ) ) info.ripping  = text;
-			else if ( !strcmp( "Tagging" , field ) ) info.tagging  = text;
+				 if ( !strcmp( "Composer" , field ) ) info.composer  = text;
+			else if ( !strcmp( "Engineer" , field ) ) info.engineer  = text;
+			else if ( !strcmp( "Ripping"  , field ) ) info.ripping   = text;
+			else if ( !strcmp( "Tagging"  , field ) ) info.tagging   = text;
+			else if ( !strcmp( "Game"     , field ) ) info.title     = text;
+			else if ( !strcmp( "Artist"   , field ) ) info.artist    = text;
+			else if ( !strcmp( "Copyright", field ) ) info.copyright = text;
 			else
 				text = 0;
 			if ( text )
@@ -354,6 +358,43 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, bool first )
 			*in = ':';
 		}
 	}
+	else if ( *field == '@' )
+	{
+		++field;
+		in = (char*)field;
+		while ( *in && *in > ' ' )
+			in++;
+		const char* text = skip_white( in );
+		if ( *text )
+		{
+			*in = 0;
+				 if ( !strcmp( "TITLE"    , field ) ) info.title     = text;
+			else if ( !strcmp( "ARTIST"   , field ) ) info.artist    = text;
+			else if ( !strcmp( "DATE"     , field ) ) info.date      = text;
+			else if ( !strcmp( "COMPOSER" , field ) ) info.composer  = text;
+			else if ( !strcmp( "SEQUENCER", field ) ) info.sequencer = text;
+			else if ( !strcmp( "ENGINEER" , field ) ) info.engineer  = text;
+			else if ( !strcmp( "RIPPER"   , field ) ) info.ripping   = text;
+			else if ( !strcmp( "TAGGER"   , field ) ) info.tagging   = text;
+			else
+				text = 0;
+			if ( text )
+			{
+				last_comment_value = (char*)text;
+				return;
+			}
+		}
+	}
+	else if ( last_comment_value )
+	{
+		size_t len = strlen( last_comment_value );
+		last_comment_value[ len ] = ',';
+		last_comment_value[ len + 1 ] = ' ';
+		size_t field_len = strlen( field );
+		memmove( last_comment_value + len + 2, field, field_len );
+		last_comment_value[ len + 2 + field_len ] = 0;
+		return;
+	}
 	
 	if ( first )
 		info.title = field;
@@ -361,11 +402,15 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, bool first )
 
 blargg_err_t M3u_Playlist::parse_()
 {
-	info_.title    = "";
-	info_.composer = "";
-	info_.engineer = "";
-	info_.ripping  = "";
-	info_.tagging  = "";
+	info_.title     = "";
+	info_.artist    = "";
+	info_.date      = "";
+	info_.composer  = "";
+	info_.sequencer = "";
+	info_.engineer  = "";
+	info_.ripping   = "";
+	info_.tagging   = "";
+	info_.copyright = "";
 	
 	int const CR = 13;
 	int const LF = 10;
@@ -377,6 +422,7 @@ blargg_err_t M3u_Playlist::parse_()
 	int line  = 0;
 	int count = 0;
 	char* in  = data.begin();
+	char* last_comment_value = 0;
 	while ( in < data.end() )
 	{
 		// find end of line and terminate it
@@ -395,7 +441,7 @@ blargg_err_t M3u_Playlist::parse_()
 		// parse line
 		if ( *begin == '#' )
 		{
-			parse_comment( begin, info_, first_comment );
+			parse_comment( begin, info_, last_comment_value, first_comment );
 			first_comment = false;
 		}
 		else if ( *begin )
@@ -409,6 +455,7 @@ blargg_err_t M3u_Playlist::parse_()
 				first_error_ = line;
 			first_comment = false;
 		}
+		else last_comment_value = 0;
 	}
 	if ( count <= 0 )
 		return "Not an m3u playlist";
diff --git a/gme/M3u_Playlist.h b/gme/M3u_Playlist.h
index 1b32c53..2af7480 100644
--- a/gme/M3u_Playlist.h
+++ b/gme/M3u_Playlist.h
@@ -13,21 +13,25 @@ class M3u_Playlist {
 	blargg_err_t load( const char* path );
 	blargg_err_t load( Data_Reader& in );
 	blargg_err_t load( void const* data, long size );
-	
+
 	// Line number of first parse error, 0 if no error. Any lines with parse
 	// errors are ignored.
 	int first_error() const { return first_error_; }
-	
+
 	struct info_t
 	{
 		const char* title;
+		const char* artist;
+		const char* date;
 		const char* composer;
+		const char* sequencer;
 		const char* engineer;
 		const char* ripping;
 		const char* tagging;
+		const char* copyright;
 	};
 	info_t const& info() const { return info_; }
-	
+
 	struct entry_t
 	{
 		const char* file; // filename without stupid ::TYPE suffix
@@ -44,15 +48,15 @@ class M3u_Playlist {
 	};
 	entry_t const& operator [] ( int i ) const { return entries [i]; }
 	int size() const { return entries.size(); }
-	
+
 	void clear();
-	
+
 private:
 	blargg_vector<entry_t> entries;
 	blargg_vector<char> data;
 	int first_error_;
 	info_t info_;
-	
+
 	blargg_err_t parse();
 	blargg_err_t parse_();
 };