PageRenderTime 65ms CodeModel.GetById 8ms app.highlight 52ms RepoModel.GetById 2ms app.codeStats 0ms

/gme/M3u_Playlist.cpp

http://game-music-emu.googlecode.com/
C++ | 426 lines | 345 code | 58 blank | 23 comment | 98 complexity | c808dfffeb2a7a9919752dd309817527 MD5 | raw file
  1// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/
  2
  3#include "M3u_Playlist.h"
  4#include "Music_Emu.h"
  5
  6#include <string.h>
  7
  8/* Copyright (C) 2006 Shay Green. This module is free software; you
  9can redistribute it and/or modify it under the terms of the GNU Lesser
 10General Public License as published by the Free Software Foundation; either
 11version 2.1 of the License, or (at your option) any later version. This
 12module is distributed in the hope that it will be useful, but WITHOUT ANY
 13WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 14FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 15details. You should have received a copy of the GNU Lesser General Public
 16License along with this module; if not, write to the Free Software Foundation,
 17Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
 18
 19#include "blargg_source.h"
 20
 21// gme functions defined here to avoid linking in m3u code unless it's used
 22
 23blargg_err_t Gme_File::load_m3u_( blargg_err_t err )
 24{
 25	require( raw_track_count_ ); // file must be loaded first
 26	
 27	if ( !err )
 28	{
 29		if ( playlist.size() )
 30			track_count_ = playlist.size();
 31		
 32		int line = playlist.first_error();
 33		if ( line )
 34		{
 35			// avoid using bloated printf()
 36			char* out = &playlist_warning [sizeof playlist_warning];
 37			*--out = 0;
 38			do {
 39				*--out = line % 10 + '0';
 40			} while ( (line /= 10) > 0 );
 41			
 42			static const char str [] = "Problem in m3u at line ";
 43			out -= sizeof str - 1;
 44			memcpy( out, str, sizeof str - 1 );
 45			set_warning( out );
 46		}
 47	}
 48	return err;
 49}
 50
 51blargg_err_t Gme_File::load_m3u( const char* path ) { return load_m3u_( playlist.load( path ) ); }
 52
 53blargg_err_t Gme_File::load_m3u( Data_Reader& in )  { return load_m3u_( playlist.load( in ) ); }
 54
 55BLARGG_EXPORT gme_err_t gme_load_m3u( Music_Emu* me, const char* path ) { return me->load_m3u( path ); }
 56
 57BLARGG_EXPORT gme_err_t gme_load_m3u_data( Music_Emu* me, const void* data, long size )
 58{
 59	Mem_File_Reader in( data, size );
 60	return me->load_m3u( in );
 61}
 62
 63
 64
 65static char* skip_white( char* in )
 66{
 67	while ( *in == ' ' )
 68		in++;
 69	return in;
 70}
 71
 72inline unsigned from_dec( unsigned n ) { return n - '0'; }
 73
 74static char* parse_filename( char* in, M3u_Playlist::entry_t& entry )
 75{
 76	entry.file = in;
 77	entry.type = "";
 78	char* out = in;
 79	while ( 1 )
 80	{
 81		int c = *in;
 82		if ( !c ) break;
 83		in++;
 84		
 85		if ( c == ',' ) // commas in filename
 86		{
 87			char* p = skip_white( in );
 88			if ( *p == '$' || from_dec( *p ) <= 9 )
 89			{
 90				in = p;
 91				break;
 92			}
 93		}
 94		
 95		if ( c == ':' && in [0] == ':' && in [1] && in [2] != ',' ) // ::type suffix
 96		{
 97			entry.type = ++in;
 98			while ( (c = *in) != 0 && c != ',' )
 99				in++;
100			if ( c == ',' )
101			{
102				*in++ = 0; // terminate type
103				in = skip_white( in );
104			}
105			break;
106		}
107		
108		if ( c == '\\' ) // \ prefix for special characters
109		{
110			c = *in;
111			if ( !c ) break;
112			in++;
113		}
114		*out++ = (char) c;
115	}
116	*out = 0; // terminate string
117	return in;
118}
119
120static char* next_field( char* in, int* result )
121{
122	while ( 1 )
123	{
124		in = skip_white( in );
125		
126		if ( !*in )
127			break;
128		
129		if ( *in == ',' )
130		{
131			in++;
132			break;
133		}
134		
135		*result = 1;
136		in++;
137	}
138	return skip_white( in );
139}
140
141static char* parse_int_( char* in, int* out )
142{
143	int n = 0;
144	while ( 1 )
145	{
146		unsigned d = from_dec( *in );
147		if ( d > 9 )
148			break;
149		in++;
150		n = n * 10 + d;
151		*out = n;
152	}
153	return in;
154}
155
156static char* parse_int( char* in, int* out, int* result )
157{
158	return next_field( parse_int_( in, out ), result );
159}
160
161// Returns 16 or greater if not hex
162inline int from_hex_char( int h )
163{
164	h -= 0x30;
165	if ( (unsigned) h > 9 )
166		h = ((h - 0x11) & 0xDF) + 10;
167	return h;
168}
169
170static char* parse_track( char* in, M3u_Playlist::entry_t& entry, int* result )
171{
172	if ( *in == '$' )
173	{
174		in++;
175		int n = 0;
176		while ( 1 )
177		{
178			int h = from_hex_char( *in );
179			if ( h > 15 )
180				break;
181			in++;
182			n = n * 16 + h;
183			entry.track = n;
184		}
185	}
186	else
187	{
188		in = parse_int_( in, &entry.track );
189		if ( entry.track >= 0 )
190			entry.decimal_track = 1;
191	}
192	return next_field( in, result );
193}
194
195static char* parse_time_( char* in, int* out )
196{
197	*out = -1;
198	int n = -1;
199	in = parse_int_( in, &n );
200	if ( n >= 0 )
201	{
202		*out = n;
203		if ( *in == ':' )
204		{
205			n = -1;
206			in = parse_int_( in + 1, &n );
207			if ( n >= 0 )
208				*out = *out * 60 + n;
209		}
210	}
211	return in;
212}
213
214static char* parse_time( char* in, int* out, int* result )
215{
216	return next_field( parse_time_( in, out ), result );
217}
218
219static char* parse_name( char* in )
220{
221	char* out = in;
222	while ( 1 )
223	{
224		int c = *in;
225		if ( !c ) break;
226		in++;
227		
228		if ( c == ',' ) // commas in string
229		{
230			char* p = skip_white( in );
231			if ( *p == ',' || *p == '-' || from_dec( *p ) <= 9 )
232			{
233				in = p;
234				break;
235			}
236		}
237		
238		if ( c == '\\' ) // \ prefix for special characters
239		{
240			c = *in;
241			if ( !c ) break;
242			in++;
243		}
244		*out++ = (char) c;
245	}
246	*out = 0; // terminate string
247	return in;
248}
249
250static int parse_line( char* in, M3u_Playlist::entry_t& entry )
251{
252	int result = 0;
253	
254	// file
255	entry.file = in;
256	entry.type = "";
257	in = parse_filename( in, entry );
258	
259	// track
260	entry.track = -1;
261	entry.decimal_track = 0;
262	in = parse_track( in, entry, &result );
263	
264	// name
265	entry.name = in;
266	in = parse_name( in );
267	
268	// time
269	entry.length = -1;
270	in = parse_time( in, &entry.length, &result );
271	
272	// loop
273	entry.intro = -1;
274	entry.loop  = -1;
275	if ( *in == '-' )
276	{
277		entry.loop = entry.length;
278		in++;
279	}
280	else
281	{
282		in = parse_time_( in, &entry.loop );
283		if ( entry.loop >= 0 )
284		{
285			entry.intro = 0;
286			if ( *in == '-' ) // trailing '-' means that intro length was specified 
287			{
288				in++;
289				entry.intro = entry.loop;
290				entry.loop  = entry.length - entry.intro;
291			}
292		}
293	}
294	in = next_field( in, &result );
295	
296	// fade
297	entry.fade = -1;
298	in = parse_time( in, &entry.fade, &result );
299	
300	// repeat
301	entry.repeat = -1;
302	in = parse_int( in, &entry.repeat, &result );
303	
304	return result;
305}
306
307static void parse_comment( char* in, M3u_Playlist::info_t& info, bool first )
308{
309	in = skip_white( in + 1 );
310	const char* field = in;
311	while ( *in && *in != ':' )
312		in++;
313	
314	if ( *in == ':' )
315	{
316		const char* text = skip_white( in + 1 );
317		if ( *text )
318		{
319			*in = 0;
320			     if ( !strcmp( "Composer", field ) ) info.composer = text;
321			else if ( !strcmp( "Engineer", field ) ) info.engineer = text;
322			else if ( !strcmp( "Ripping" , field ) ) info.ripping  = text;
323			else if ( !strcmp( "Tagging" , field ) ) info.tagging  = text;
324			else
325				text = 0;
326			if ( text )
327				return;
328			*in = ':';
329		}
330	}
331	
332	if ( first )
333		info.title = field;
334}
335
336blargg_err_t M3u_Playlist::parse_()
337{
338	info_.title    = "";
339	info_.composer = "";
340	info_.engineer = "";
341	info_.ripping  = "";
342	info_.tagging  = "";
343	
344	int const CR = 13;
345	int const LF = 10;
346	
347	data.end() [-1] = LF; // terminate input
348	
349	first_error_ = 0;
350	bool first_comment = true;
351	int line  = 0;
352	int count = 0;
353	char* in  = data.begin();
354	while ( in < data.end() )
355	{
356		// find end of line and terminate it
357		line++;
358		char* begin = in;
359		while ( *in != CR && *in != LF )
360		{
361			if ( !*in )
362				return "Not an m3u playlist";
363			in++;
364		}
365		if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line
366			*in++ = 0;
367		*in++ = 0;
368		
369		// parse line
370		if ( *begin == '#' )
371		{
372			parse_comment( begin, info_, first_comment );
373			first_comment = false;
374		}
375		else if ( *begin )
376		{
377			if ( (int) entries.size() <= count )
378				RETURN_ERR( entries.resize( count * 2 + 64 ) );
379			
380			if ( !parse_line( begin, entries [count] ) )
381				count++;
382			else if ( !first_error_ )
383				first_error_ = line;
384			first_comment = false;
385		}
386	}
387	if ( count <= 0 )
388		return "Not an m3u playlist";
389	
390	if ( !(info_.composer [0] | info_.engineer [0] | info_.ripping [0] | info_.tagging [0]) )
391		info_.title = "";
392	
393	return entries.resize( count );
394}
395
396blargg_err_t M3u_Playlist::parse()
397{
398	blargg_err_t err = parse_();
399	if ( err )
400	{
401		entries.clear();
402		data.clear();
403	}
404	return err;
405}
406
407blargg_err_t M3u_Playlist::load( Data_Reader& in )
408{
409	RETURN_ERR( data.resize( in.remain() + 1 ) );
410	RETURN_ERR( in.read( data.begin(), data.size() - 1 ) );
411	return parse();
412}
413
414blargg_err_t M3u_Playlist::load( const char* path )
415{
416	GME_FILE_READER in;
417	RETURN_ERR( in.open( path ) );
418	return load( in );
419}
420
421blargg_err_t M3u_Playlist::load( void const* in, long size )
422{
423	RETURN_ERR( data.resize( size + 1 ) );
424	memcpy( data.begin(), in, size );
425	return parse();
426}