2014-06-01 17:55:23 +00:00
/*
* This file is part of the DXX - Rebirth project < http : //www.dxx-rebirth.com/>.
* It is copyright by its individual contributors , as recorded in the
* project ' s Git history . See COPYING . txt at the top level for license
* terms and a link to the Git history .
*/
2009-01-13 12:39:03 +00:00
/*
* DXX Rebirth " jukebox " code
* MD 2211 < md2211 @ users . sourceforge . net > , 2007
*/
# include <stdlib.h>
2010-09-26 13:15:20 +00:00
# include <stdio.h>
2009-01-13 12:39:03 +00:00
# include <string.h>
2010-09-02 00:07:37 +00:00
2009-01-13 12:39:03 +00:00
# include "hudmsg.h"
2012-06-10 12:02:43 +00:00
# include "songs.h"
2009-01-13 12:39:03 +00:00
# include "jukebox.h"
2012-07-07 18:35:06 +00:00
# include "dxxerror.h"
2009-01-13 12:39:03 +00:00
# include "console.h"
# include "config.h"
2013-12-26 04:18:28 +00:00
# include "strutil.h"
# include "u_mem.h"
2015-04-19 04:18:49 +00:00
# include "physfs_list.h"
2015-07-25 23:10:45 +00:00
# include "digi.h"
2009-01-13 12:39:03 +00:00
2015-02-07 04:37:37 +00:00
# include "compiler-make_unique.h"
2015-01-23 03:55:05 +00:00
# include "partial_range.h"
2015-01-23 03:55:05 +00:00
2015-12-13 18:00:49 +00:00
namespace dcx {
2015-12-13 18:00:48 +00:00
2009-01-13 12:39:03 +00:00
# define MUSIC_HUDMSG_MAXLEN 40
# define JUKEBOX_HUDMSG_PLAYING "Now playing:"
# define JUKEBOX_HUDMSG_STOPPED "Jukebox stopped"
2015-01-23 03:55:05 +00:00
namespace {
2015-01-23 03:55:05 +00:00
class list_deleter : std : : default_delete < char * [ ] > , PHYSFS_list_deleter
2015-01-23 03:55:05 +00:00
{
typedef std : : default_delete < char * [ ] > base_deleter ;
public :
2015-02-07 04:37:37 +00:00
std : : unique_ptr < char [ ] > buf ; // buffer containing song file path text
2015-01-23 03:55:05 +00:00
void operator ( ) ( char * * list )
{
if ( buf )
{
buf . reset ( ) ;
this - > base_deleter : : operator ( ) ( list ) ;
}
else
2015-01-23 03:55:05 +00:00
this - > PHYSFS_list_deleter : : operator ( ) ( list ) ;
2015-01-23 03:55:05 +00:00
}
} ;
2015-05-09 17:38:57 +00:00
class list_pointers : public PHYSFSX_uncounted_list_template < list_deleter >
2015-01-23 03:55:05 +00:00
{
2015-05-09 17:38:57 +00:00
typedef PHYSFSX_uncounted_list_template < list_deleter > base_ptr ;
2015-01-23 03:55:05 +00:00
public :
using base_ptr : : reset ;
2015-02-07 04:37:37 +00:00
void reset ( char * * list , std : : unique_ptr < char [ ] > & & buf )
2015-01-23 03:55:05 +00:00
noexcept (
noexcept ( std : : declval < base_ptr > ( ) . reset ( list ) ) & &
noexcept ( std : : declval < list_deleter > ( ) . buf = std : : move ( buf ) )
)
{
this - > base_ptr : : reset ( list ) ;
get_deleter ( ) . buf = std : : move ( buf ) ;
}
2015-05-09 17:38:57 +00:00
void reset ( PHYSFSX_uncounted_list list )
2015-01-23 03:55:05 +00:00
noexcept ( noexcept ( std : : declval < base_ptr > ( ) . reset ( list . release ( ) ) ) )
{
this - > base_ptr : : reset ( list . release ( ) ) ;
}
2015-01-23 03:55:05 +00:00
} ;
2015-01-23 03:55:05 +00:00
class jukebox_songs
2010-09-26 13:15:20 +00:00
{
2015-01-23 03:55:05 +00:00
void quick_unload ( ) ;
public :
~ jukebox_songs ( ) ;
void unload ( ) ;
2015-01-23 03:55:05 +00:00
list_pointers list ; // the actual list
2010-09-26 13:15:20 +00:00
int num_songs ; // number of jukebox songs
2015-01-23 03:55:05 +00:00
static const std : : size_t max_songs = 1024 ; // maximum number of pointers that 'list' can hold, i.e. size of list / size of one pointer
2013-12-22 22:03:07 +00:00
} ;
2010-09-26 13:15:20 +00:00
2015-01-23 03:55:05 +00:00
}
2009-01-13 12:39:03 +00:00
2015-01-23 03:55:05 +00:00
static jukebox_songs JukeboxSongs ;
jukebox_songs : : ~ jukebox_songs ( )
2009-01-13 12:39:03 +00:00
{
2015-01-23 03:55:05 +00:00
quick_unload ( ) ;
}
void jukebox_songs : : quick_unload ( )
{
2015-01-23 03:55:05 +00:00
list . reset ( ) ;
2015-01-23 03:55:05 +00:00
}
2010-10-10 10:48:24 +00:00
2015-01-23 03:55:05 +00:00
void jukebox_songs : : unload ( )
{
quick_unload ( ) ;
2015-01-23 03:55:05 +00:00
num_songs = 0 ;
2015-01-23 03:55:05 +00:00
}
void jukebox_unload ( )
{
JukeboxSongs . unload ( ) ;
2010-09-26 13:15:20 +00:00
}
2015-03-22 18:49:21 +00:00
const array < file_extension_t , 5 > jukebox_exts { {
SONG_EXT_HMP ,
SONG_EXT_MID ,
SONG_EXT_OGG ,
SONG_EXT_FLAC ,
SONG_EXT_MP3
} } ;
2009-01-13 12:39:03 +00:00
2015-12-13 18:00:48 +00:00
}
2015-12-13 18:00:49 +00:00
namespace dsx {
2015-12-13 18:00:48 +00:00
2013-10-27 22:00:14 +00:00
static int read_m3u ( void )
2010-09-26 13:15:20 +00:00
{
FILE * fp ;
2015-02-06 00:45:53 +00:00
std : : size_t length ;
2016-05-21 17:24:51 +00:00
{
2010-09-26 13:15:20 +00:00
char * buf ;
2014-12-22 04:35:47 +00:00
array < char , PATH_MAX > absbuf ;
if ( PHYSFSX_exists ( GameCfg . CMLevelMusicPath . data ( ) , 0 ) ) // it's a child of Sharepath, build full path
2015-10-11 22:21:00 +00:00
PHYSFSX_getRealPath ( GameCfg . CMLevelMusicPath . data ( ) , ( buf = absbuf . data ( ) , absbuf ) ) ;
2011-04-18 12:32:36 +00:00
else
2014-12-22 04:35:47 +00:00
buf = GameCfg . CMLevelMusicPath . data ( ) ;
fp = fopen ( buf , " rb " ) ;
2010-09-26 13:15:20 +00:00
if ( ! fp )
2011-04-18 12:32:36 +00:00
return 0 ;
2016-05-21 17:24:51 +00:00
}
2010-09-26 13:15:20 +00:00
fseek ( fp , - 1 , SEEK_END ) ;
length = ftell ( fp ) + 1 ;
2015-02-07 04:37:37 +00:00
auto list_buf = make_unique < char [ ] > ( length + 1 ) ;
2010-09-26 13:15:20 +00:00
fseek ( fp , 0 , SEEK_SET ) ;
2015-01-23 03:55:05 +00:00
if ( ! fread ( list_buf . get ( ) , length , 1 , fp ) )
2009-01-13 12:39:03 +00:00
{
2010-09-26 13:15:20 +00:00
fclose ( fp ) ;
2011-04-18 12:32:36 +00:00
return 0 ;
2009-01-13 12:39:03 +00:00
}
2010-09-26 13:15:20 +00:00
fclose ( fp ) ; // Finished with it
// The growing string list is allocated last, hopefully reducing memory fragmentation when it grows
2015-01-23 03:55:05 +00:00
list_buf [ length ] = ' \0 ' ; // make sure the last string is terminated
auto & & range = unchecked_partial_range ( list_buf . get ( ) , length ) ;
2015-01-23 03:55:05 +00:00
const auto eol = [ ] ( char c ) {
return c = = ' \n ' | | c = = ' \r ' | | ! c ;
} ;
2015-01-23 03:55:05 +00:00
JukeboxSongs . list . reset ( new char * [ JukeboxSongs . max_songs ] , std : : move ( list_buf ) ) ;
2015-01-23 03:55:05 +00:00
for ( auto buf = range . begin ( ) ; buf ! = range . end ( ) ; + + buf )
2010-09-26 13:15:20 +00:00
{
2015-01-23 03:55:05 +00:00
for ( ; buf ! = range . end ( ) & & eol ( * buf ) ; ) // find new line - support DOS, Unix and Mac line endings
2010-09-26 13:15:20 +00:00
buf + + ;
2015-01-23 03:55:05 +00:00
if ( buf = = range . end ( ) )
break ;
2010-09-26 13:15:20 +00:00
if ( * buf ! = ' # ' ) // ignore comments / extra info
{
JukeboxSongs . list [ JukeboxSongs . num_songs + + ] = buf ;
2015-01-23 03:55:05 +00:00
if ( JukeboxSongs . num_songs > = JukeboxSongs . max_songs )
break ;
2010-09-26 13:15:20 +00:00
}
2015-01-23 03:55:05 +00:00
for ( ; buf ! = range . end ( ) ; + + buf ) // find end of line
if ( eol ( * buf ) )
{
* buf = 0 ;
break ;
}
if ( buf = = range . end ( ) )
break ;
2010-09-26 13:15:20 +00:00
}
2011-04-18 12:32:36 +00:00
return 1 ;
2010-09-26 13:15:20 +00:00
}
/* Loads music file names from a given directory or M3U playlist */
2010-06-14 15:32:55 +00:00
void jukebox_load ( )
{
2010-11-21 11:55:35 +00:00
jukebox_unload ( ) ;
2009-01-13 12:39:03 +00:00
2011-04-18 12:32:36 +00:00
// Check if it's an M3U file
2014-12-22 04:35:47 +00:00
size_t musiclen = strlen ( GameCfg . CMLevelMusicPath . data ( ) ) ;
if ( musiclen > 4 & & ! d_stricmp ( & GameCfg . CMLevelMusicPath [ musiclen - 4 ] , " .m3u " ) )
2011-04-18 12:32:36 +00:00
read_m3u ( ) ;
else // a directory
2010-06-14 08:13:16 +00:00
{
2011-04-18 12:32:36 +00:00
int new_path = 0 ;
const char * sep = PHYSFS_getDirSeparator ( ) ;
2014-12-22 04:35:47 +00:00
size_t seplen = strlen ( sep ) ;
2010-06-14 08:13:16 +00:00
2011-04-18 12:32:36 +00:00
// stick a separator on the end if necessary.
2014-12-22 04:35:47 +00:00
if ( musiclen > = seplen )
2010-06-14 10:33:36 +00:00
{
2014-12-22 04:35:47 +00:00
auto p = & GameCfg . CMLevelMusicPath [ musiclen - seplen ] ;
2011-04-18 12:32:36 +00:00
if ( strcmp ( p , sep ) )
2014-12-22 04:35:47 +00:00
GameCfg . CMLevelMusicPath . copy_if ( musiclen , sep , seplen ) ;
2010-09-26 13:15:20 +00:00
}
2010-11-21 11:55:35 +00:00
// Read directory using PhysicsFS
2014-12-22 04:35:47 +00:00
if ( PHYSFS_isDirectory ( GameCfg . CMLevelMusicPath . data ( ) ) ) // find files in relative directory
2015-01-23 03:55:05 +00:00
JukeboxSongs . list . reset ( PHYSFSX_findFiles ( GameCfg . CMLevelMusicPath . data ( ) , jukebox_exts ) ) ;
2011-04-18 12:32:36 +00:00
else
{
2014-12-22 04:35:47 +00:00
new_path = PHYSFSX_isNewPath ( GameCfg . CMLevelMusicPath . data ( ) ) ;
PHYSFS_addToSearchPath ( GameCfg . CMLevelMusicPath . data ( ) , 0 ) ;
2011-04-18 12:32:36 +00:00
// as mountpoints are no option (yet), make sure only files originating from GameCfg.CMLevelMusicPath are aded to the list.
2015-01-23 03:55:05 +00:00
JukeboxSongs . list . reset ( PHYSFSX_findabsoluteFiles ( " " , GameCfg . CMLevelMusicPath . data ( ) , jukebox_exts ) ) ;
2011-04-18 12:32:36 +00:00
}
2010-11-21 11:55:35 +00:00
if ( ! JukeboxSongs . list )
2010-09-26 13:15:20 +00:00
{
2010-11-21 11:55:35 +00:00
if ( new_path )
2014-12-22 04:35:47 +00:00
PHYSFS_removeFromSearchPath ( GameCfg . CMLevelMusicPath . data ( ) ) ;
2010-11-21 11:55:35 +00:00
return ;
2009-01-13 12:39:03 +00:00
}
2015-04-26 20:15:51 +00:00
JukeboxSongs . num_songs = std : : distance ( JukeboxSongs . list . begin ( ) , JukeboxSongs . list . end ( ) ) ;
2010-11-21 11:55:35 +00:00
if ( new_path )
2014-12-22 04:35:47 +00:00
PHYSFS_removeFromSearchPath ( GameCfg . CMLevelMusicPath . data ( ) ) ;
2010-11-21 11:55:35 +00:00
}
if ( JukeboxSongs . num_songs )
{
2014-12-22 04:35:47 +00:00
con_printf ( CON_DEBUG , " Jukebox: %d music file(s) found in %s " , JukeboxSongs . num_songs , GameCfg . CMLevelMusicPath . data ( ) ) ;
2010-11-21 11:55:35 +00:00
if ( GameCfg . CMLevelMusicTrack [ 1 ] ! = JukeboxSongs . num_songs )
{
GameCfg . CMLevelMusicTrack [ 1 ] = JukeboxSongs . num_songs ;
GameCfg . CMLevelMusicTrack [ 0 ] = 0 ; // number of songs changed so start from beginning.
2010-09-26 13:15:20 +00:00
}
2009-01-13 12:39:03 +00:00
}
2010-11-21 11:55:35 +00:00
else
{
GameCfg . CMLevelMusicTrack [ 0 ] = - 1 ;
GameCfg . CMLevelMusicTrack [ 1 ] = - 1 ;
2013-12-07 00:47:27 +00:00
con_printf ( CON_DEBUG , " Jukebox music could not be found! " ) ;
2010-11-21 11:55:35 +00:00
}
2009-01-13 12:39:03 +00:00
}
2010-06-14 08:13:16 +00:00
// To proceed tru our playlist. Usually used for continous play, but can loop as well.
2013-10-27 22:00:14 +00:00
static void jukebox_hook_next ( )
2010-06-14 08:13:16 +00:00
{
2010-09-26 13:15:20 +00:00
if ( ! JukeboxSongs . list | | GameCfg . CMLevelMusicTrack [ 0 ] = = - 1 ) return ;
2009-03-03 12:55:27 +00:00
2011-01-20 11:17:30 +00:00
if ( GameCfg . CMLevelMusicPlayOrder = = MUSIC_CM_PLAYORDER_RAND )
GameCfg . CMLevelMusicTrack [ 0 ] = d_rand ( ) % GameCfg . CMLevelMusicTrack [ 1 ] ; // simply a random selection - no check if this song has already been played. But that's how I roll!
else
GameCfg . CMLevelMusicTrack [ 0 ] + + ;
2010-06-14 08:13:16 +00:00
if ( GameCfg . CMLevelMusicTrack [ 0 ] + 1 > GameCfg . CMLevelMusicTrack [ 1 ] )
GameCfg . CMLevelMusicTrack [ 0 ] = 0 ;
jukebox_play ( ) ;
}
2009-01-13 12:39:03 +00:00
2010-06-14 08:13:16 +00:00
// Play tracks from Jukebox directory. Play track specified in GameCfg.CMLevelMusicTrack[0] and loop depending on GameCfg.CMLevelMusicPlayOrder
int jukebox_play ( )
{
2013-12-03 22:20:09 +00:00
const char * music_filename ;
2014-12-22 04:35:47 +00:00
uint_fast32_t size_full_filename = 0 ;
2009-03-03 12:55:27 +00:00
2010-09-26 13:15:20 +00:00
if ( ! JukeboxSongs . list )
2010-06-14 08:13:16 +00:00
return 0 ;
2009-03-03 12:55:27 +00:00
2010-06-14 08:13:16 +00:00
if ( GameCfg . CMLevelMusicTrack [ 0 ] < 0 | | GameCfg . CMLevelMusicTrack [ 0 ] + 1 > GameCfg . CMLevelMusicTrack [ 1 ] )
return 0 ;
2009-01-13 12:39:03 +00:00
2010-09-26 13:15:20 +00:00
music_filename = JukeboxSongs . list [ GameCfg . CMLevelMusicTrack [ 0 ] ] ;
2010-02-27 13:05:34 +00:00
if ( ! music_filename )
return 0 ;
2009-01-13 12:39:03 +00:00
2013-12-03 22:12:42 +00:00
size_t size_music_filename = strlen ( music_filename ) ;
2014-12-22 04:35:47 +00:00
size_t musiclen = strlen ( GameCfg . CMLevelMusicPath . data ( ) ) ;
size_full_filename = musiclen + size_music_filename + 1 ;
2015-01-28 03:42:53 +00:00
RAIIdmem < char [ ] > full_filename ;
CALLOC ( full_filename , char [ ] , size_full_filename ) ;
2013-12-03 22:12:42 +00:00
const char * LevelMusicPath ;
2014-12-22 04:35:47 +00:00
if ( musiclen > 4 & & ! d_stricmp ( & GameCfg . CMLevelMusicPath [ musiclen - 4 ] , " .m3u " ) ) // if it's from an M3U playlist
2013-12-03 22:12:42 +00:00
LevelMusicPath = " " ;
2011-04-18 12:32:36 +00:00
else // if it's from a specified path
2014-12-22 04:35:47 +00:00
LevelMusicPath = GameCfg . CMLevelMusicPath . data ( ) ;
2015-02-17 03:52:59 +00:00
snprintf ( full_filename . get ( ) , size_full_filename , " %s%s " , LevelMusicPath , music_filename ) ;
2010-11-21 11:55:35 +00:00
2015-02-17 03:52:59 +00:00
int played = songs_play_file ( full_filename . get ( ) , ( GameCfg . CMLevelMusicPlayOrder = = MUSIC_CM_PLAYORDER_LEVEL ? 1 : 0 ) , ( GameCfg . CMLevelMusicPlayOrder = = MUSIC_CM_PLAYORDER_LEVEL ? nullptr : jukebox_hook_next ) ) ;
2015-03-28 01:16:10 +00:00
full_filename . reset ( ) ;
2013-12-03 22:12:42 +00:00
if ( ! played )
2010-11-21 11:55:35 +00:00
{
2010-04-05 11:54:23 +00:00
return 0 ; // whoops, got an error
2010-11-21 11:55:35 +00:00
}
2009-01-13 12:39:03 +00:00
// Formatting a pretty message
2013-12-03 22:20:09 +00:00
const char * prefix = " ... " ;
2013-12-03 22:12:42 +00:00
if ( size_music_filename > = MUSIC_HUDMSG_MAXLEN ) {
2013-12-03 22:20:09 +00:00
music_filename + = size_music_filename - MUSIC_HUDMSG_MAXLEN ;
2009-01-13 12:39:03 +00:00
} else {
2013-12-03 22:20:09 +00:00
prefix + = 3 ;
2009-01-13 12:39:03 +00:00
}
2013-12-03 22:20:09 +00:00
HUD_init_message ( HM_DEFAULT , " %s %s%s " , JUKEBOX_HUDMSG_PLAYING , prefix , music_filename ) ;
2009-03-03 12:55:27 +00:00
return 1 ;
2009-01-13 12:39:03 +00:00
}
2015-12-13 18:00:48 +00:00
}