//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= // // Purpose: A simple .mp3 player example // //============================================================================= #include "cbase.h" #if 0 #include "mp3player.h" #include "KeyValues.h" #include "FileSystem.h" #include "vgui_controls/MenuButton.h" #include "vgui_controls/Menu.h" #include "vgui_controls/Button.h" #include "vgui_controls/CheckButton.h" #include "vgui_controls/Slider.h" #include "vgui_controls/ListPanel.h" #include "vgui/IPanel.h" #include "vgui/IVgui.h" #include "vgui/ISurface.h" #include "vgui/IInput.h" #include "vgui/ILocalize.h" #include "vgui_controls/PHandle.h" #include "vgui_controls/PropertySheet.h" #include "vgui_controls/PropertyPage.h" #include "vgui_controls/TreeView.h" #include "vgui_controls/FileOpenDialog.h" #include "vgui_controls/DirectorySelectDialog.h" #include "checksum_crc.h" #include "engine/IEngineSound.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace vgui; // Singleton static CMP3Player *g_pPlayer = NULL; vgui::Panel *GetSDKRootPanel(); // Time between songs #define END_GAP_TIME 1.0f #define SOUND_ROOT "sound" #define MUTED_VOLUME 0.02f #define TREE_TEXT_COLOR Color( 200, 255, 200, 255 ) #define LIST_TEXT_COLOR TREE_TEXT_COLOR #define DB_FILENAME "resource/mp3player_db.txt" #define MP3_SETTINGS_FILE "resource/mp3settings.txt" #define MP3_DEFAULT_MP3DIR "c:\\my music" CMP3Player *GetMP3Player() { Assert( g_pPlayer ); return g_pPlayer; } static void mp3_f() { CMP3Player *player = GetMP3Player(); if ( player ) { player->SetVisible( !player->IsVisible() ); } } void MP3Player_Create( vgui::VPANEL parent ) { Assert( !g_pPlayer ); new CMP3Player( parent, "MP3Player" ); #if 0 mp3_f(); #endif } void MP3Player_Destroy() { if ( g_pPlayer ) { g_pPlayer->MarkForDeletion(); g_pPlayer = NULL; } } static ConCommand mp3( "mp3", mp3_f, "Show/hide mp3 player UI." ); //----------------------------------------------------------------------------- // Purpose: This assumes artist/album/file.mp3!!! // Input : *relative - // *artist - // artistlen - // *album - // albumlen - // Output : static bool //----------------------------------------------------------------------------- static bool SplitArtistAlbum( char const *relative, char *artist, size_t artistlen, char *album, size_t albumlen ) { artist[ 0 ] = 0; album[ 0 ] = 0; char str[ 512 ]; Q_strncpy( str, relative, sizeof( str ) ); char seps[] = "/\\"; char *p = strtok( str, seps ); int pos = 0; while ( p ) { switch ( pos ) { default: break; case 0: Q_strncpy( artist, p, artistlen ); break; case 1: Q_strncpy( album, p, albumlen ); break; case 2: if ( !Q_stristr( p, ".mp3" ) ) { artist[ 0 ] = 0; album[ 0 ] = 0; return false; } return true; break; } ++pos; p = strtok( NULL, seps ); } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CMP3FileListPage : public PropertyPage { DECLARE_CLASS_SIMPLE( CMP3FileListPage, PropertyPage ); public: CMP3FileListPage( Panel *parent, CMP3Player *player, char const *panelName ) : BaseClass( parent, panelName ), m_pPlayer( player ) { m_pList = new ListPanel( this, "FileList" ); m_pList->AddColumnHeader( 0, "File", "File", 200, ListPanel::COLUMN_RESIZEWITHWINDOW ); m_pList->AddColumnHeader( 1, "Artist", "Artist", 150, ListPanel::COLUMN_RESIZEWITHWINDOW ); m_pList->AddColumnHeader( 2, "Album", "Album", 150, ListPanel::COLUMN_RESIZEWITHWINDOW ); } void Reset() { m_pList->DeleteAllItems(); } virtual void ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); m_pList->SetFont( pScheme->GetFont( "DefaultVerySmall" ) ); m_pList->SetFgColor( LIST_TEXT_COLOR ); } virtual void PerformLayout() { BaseClass::PerformLayout(); int w, h; GetSize( w, h ); m_pList->SetBounds( 0, 0, w, h ); } void AddSong( int songIndex ) { Assert( m_pPlayer ); const MP3File_t* songInfo = m_pPlayer->GetSongInfo( songIndex ); if ( !songInfo ) { return; } KeyValues *kv = new KeyValues( "LI" ); kv->SetString( "File", songInfo->shortname.String() ); char fn[ 512 ]; if ( g_pFullFileSystem->String( songInfo->filename, fn, sizeof( fn ) ) ) { char artist[ 256 ]; char album[ 256 ]; if ( SplitArtistAlbum( fn, artist, sizeof( artist ), album, sizeof( album ) ) ) { kv->SetString( "Artist", artist ); kv->SetString( "Album", album ); } } kv->SetInt( "SongIndex", songIndex ); m_pList->AddItem( kv, 0, false, false ); kv->deleteThis(); } void GetSelectedSongs( CUtlVector< int >&list ) { list.RemoveAll(); int selCount = m_pList->GetSelectedItemsCount(); if ( selCount <= 0 ) { return; } for ( int i = 0; i < selCount; ++i ) { int itemId = m_pList->GetSelectedItem( 0 ); KeyValues *kv = m_pList->GetItem( itemId ); if ( !kv ) { continue; } int song = kv->GetInt( "SongIndex", -1 ); if ( song == -1 ) { continue; } list.AddToTail( song ); } } MESSAGE_FUNC_INT( OnOpenContextMenu, "OpenContextMenu", itemID ); virtual void OnCommand( char const *cmd ); MESSAGE_FUNC( OnItemSelected, "ItemSelected" ) { CUtlVector< int > songList; GetSelectedSongs( songList ); m_pPlayer->SelectedSongs( CMP3Player::SONG_FROM_FILELIST, songList ); } private: CMP3Player *m_pPlayer; ListPanel *m_pList; DHANDLE< Menu > m_hMenu; }; void CMP3FileListPage::OnOpenContextMenu( int itemID ) { if ( m_hMenu.Get() != NULL ) { delete m_hMenu.Get(); } m_hMenu = new Menu( this, "FileListContext" ); m_hMenu->AddMenuItem( "AddToPlaylist", "#PlaylistAdd", "addsong", this ); int x, y; input()->GetCursorPos( x, y ); m_hMenu->SetPos( x, y ); m_hMenu->SetVisible( true ); } void CMP3FileListPage::OnCommand( char const *cmd ) { if ( !Q_stricmp( cmd, "addsong" ) ) { // Get selected item int c = m_pList->GetSelectedItemsCount(); if ( c > 0 ) { int itemId = m_pList->GetSelectedItem( 0 ); KeyValues *kv = m_pList->GetItem( itemId ); if ( kv ) { int songIndex = kv->GetInt( "SongIndex" ); m_pPlayer->AddToPlayList( songIndex, false ); } } } else { BaseClass::OnCommand( cmd ); } } class CMP3PlayListPage : public PropertyPage { DECLARE_CLASS_SIMPLE( CMP3PlayListPage, PropertyPage ); public: CMP3PlayListPage( Panel *parent, CMP3Player *player, char const *panelName ) : BaseClass( parent, panelName ), m_pPlayer( player ) { m_pList = new ListPanel( this, "PlayList" ); m_pList->AddColumnHeader( 0, "File", "File", 400, ListPanel::COLUMN_RESIZEWITHWINDOW ); m_pList->AddColumnHeader( 1, "Artist", "Artist", 150, ListPanel::COLUMN_RESIZEWITHWINDOW ); m_pList->AddColumnHeader( 2, "Album", "Album", 150, ListPanel::COLUMN_RESIZEWITHWINDOW ); } void Reset() { m_pList->DeleteAllItems(); } virtual void ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); m_pList->SetFont( pScheme->GetFont( "DefaultVerySmall" ) ); m_pList->SetFgColor( LIST_TEXT_COLOR ); } virtual void PerformLayout() { BaseClass::PerformLayout(); int w, h; GetSize( w, h ); m_pList->SetBounds( 0, 0, w, h ); } void AddSong( int songIndex ) { Assert( m_pPlayer ); const MP3File_t* songInfo = m_pPlayer->GetSongInfo( songIndex ); if ( !songInfo ) { return; } KeyValues *kv = new KeyValues( "LI" ); kv->SetString( "File", songInfo->shortname.String() ); char fn[ 512 ]; if ( g_pFullFileSystem->String( songInfo->filename, fn, sizeof( fn ) ) ) { char artist[ 256 ]; char album[ 256 ]; if ( SplitArtistAlbum( fn, artist, sizeof( artist ), album, sizeof( album ) ) ) { kv->SetString( "Artist", artist ); kv->SetString( "Album", album ); } } kv->SetInt( "SongIndex", songIndex ); m_pList->AddItem( kv, 0, false, false ); kv->deleteThis(); } void RemoveSong( int songIndex ) { // Get selected item int c = m_pList->GetSelectedItemsCount(); if ( c > 0 ) { int itemId = m_pList->GetSelectedItem( 0 ); KeyValues *kv = m_pList->GetItem( itemId ); if ( kv && ( kv->GetInt( "SongIndex", -1 ) == songIndex ) ) { m_pList->RemoveItem( itemId ); } } } void GetSelectedSongs( CUtlVector< int >&list ) { list.RemoveAll(); int selCount = m_pList->GetSelectedItemsCount(); if ( selCount <= 0 ) { return; } for ( int i = 0; i < selCount; ++i ) { int itemId = m_pList->GetSelectedItem( 0 ); KeyValues *kv = m_pList->GetItem( itemId ); if ( !kv ) { continue; } int song = kv->GetInt( "SongIndex", -1 ); if ( song == -1 ) { continue; } list.AddToTail( song ); } } MESSAGE_FUNC_INT( OnOpenContextMenu, "OpenContextMenu", itemID ); virtual void OnCommand( char const *cmd ); MESSAGE_FUNC( OnItemSelected, "ItemSelected" ) { CUtlVector< int > songList; GetSelectedSongs( songList ); m_pPlayer->SelectedSongs( CMP3Player::SONG_FROM_PLAYLIST, songList ); } void OnItemPlaying( int listIndex ) { int itemId = m_pList->GetItemIDFromRow( listIndex ); m_pList->ClearSelectedItems(); m_pList->SetSingleSelectedItem( itemId ); } private: CMP3Player *m_pPlayer; ListPanel *m_pList; DHANDLE< Menu > m_hMenu; }; void CMP3PlayListPage::OnOpenContextMenu( int itemID ) { if ( m_hMenu.Get() != NULL ) { delete m_hMenu.Get(); } m_hMenu = new Menu( this, "PlayListContext" ); m_hMenu->AddMenuItem( "Remove", "#PlayListRemove", "removesong", this ); m_hMenu->AddMenuItem( "Clear", "#PlayListClear", "clear", this ); m_hMenu->AddMenuItem( "Load", "#PlayListLoad", "load", this ); m_hMenu->AddMenuItem( "Save", "#PlayListSave", "save", this ); int x, y; input()->GetCursorPos( x, y ); m_hMenu->SetPos( x, y ); m_hMenu->SetVisible( true ); } void CMP3PlayListPage::OnCommand( char const *cmd ) { if ( !Q_stricmp( cmd, "removesong" ) ) { // Get selected item int c = m_pList->GetSelectedItemsCount(); if ( c > 0 ) { int itemId = m_pList->GetSelectedItem( 0 ); KeyValues *kv = m_pList->GetItem( itemId ); if ( kv ) { int songIndex = kv->GetInt( "SongIndex" ); m_pPlayer->RemoveFromPlayList( songIndex ); } } } else if ( !Q_stricmp( cmd, "clear" ) ) { m_pPlayer->ClearPlayList(); } else if ( !Q_stricmp( cmd, "load" ) ) { m_pPlayer->OnLoadPlayList(); } else if ( !Q_stricmp( cmd, "save" ) ) { m_pPlayer->OnSavePlayList(); } else if ( !Q_stricmp( cmd, "saveas" ) ) { m_pPlayer->OnSavePlayListAs(); } else { BaseClass::OnCommand( cmd ); } } class CMP3FileSheet : public PropertySheet { DECLARE_CLASS_SIMPLE( CMP3FileSheet, PropertySheet ); public: CMP3FileSheet( CMP3Player *player, char const *panelName ); void ResetFileList() { if ( m_pFileList ) { m_pFileList->Reset(); } } void AddSongToFileList( int songIndex ) { if ( m_pFileList ) { m_pFileList->AddSong( songIndex ); } } void ResetPlayList() { if ( m_pPlayList ) { m_pPlayList->Reset(); } } void AddSongToPlayList( int songIndex ) { if ( m_pPlayList ) { m_pPlayList->AddSong( songIndex ); } } void RemoveSongFromPlayList( int songIndex ) { if ( m_pPlayList ) { m_pPlayList->RemoveSong( songIndex ); } } void OnPlayListItemPlaying( int listIndex ) { if ( m_pPlayList ) { m_pPlayList->OnItemPlaying( listIndex ); } } protected: CMP3Player *m_pPlayer; CMP3PlayListPage *m_pPlayList; CMP3FileListPage *m_pFileList; }; CMP3FileSheet::CMP3FileSheet( CMP3Player *player, char const *panelName ) : BaseClass( (Panel *)player, panelName ), m_pPlayer( player ) { m_pPlayList = new CMP3PlayListPage( this, player, "PlayList" ); m_pFileList = new CMP3FileListPage( this, player, "FileList" ); AddPage( m_pPlayList, "#PlayListTab" ); AddPage( m_pFileList, "#FileListTab" ); SetActivePage( m_pPlayList ); } class CMP3TreeControl : public TreeView { DECLARE_CLASS_SIMPLE( CMP3TreeControl, TreeView ); public: CMP3TreeControl( CMP3Player *player, char const *panelName ); int GetSelectedSongIndex(); MESSAGE_FUNC( OnTreeViewItemSelected, "TreeViewItemSelected" ) { CUtlVector< int > songList; int idx = GetSelectedSongIndex(); if ( idx != -1 ) { songList.AddToTail( idx ); } m_pPlayer->SelectedSongs( CMP3Player::SONG_FROM_TREE, songList ); if ( vgui::input()->IsMouseDown( MOUSE_RIGHT ) ) { OpenContextMenu(); } } virtual void OnCommand( char const *cmd ); private: void OpenContextMenu(); CMP3Player *m_pPlayer; DHANDLE< Menu > m_hMenu; }; CMP3TreeControl::CMP3TreeControl( CMP3Player *player, char const *panelName ) : BaseClass( (Panel *)player, panelName ), m_pPlayer( player ) { AddActionSignalTarget( this ); } int CMP3TreeControl::GetSelectedSongIndex() { CUtlVector< KeyValues * > kv; GetSelectedItemData( kv ); if ( !kv.Count() ) { return -1; } return kv[ 0 ]->GetInt( "SongIndex", -1 ); } void CMP3TreeControl::OpenContextMenu() { if ( m_hMenu.Get() != NULL ) { delete m_hMenu.Get(); } m_hMenu = new Menu( this, "TreeContext" ); m_hMenu->AddMenuItem( "AddToPlaylist", "#PlaylistAdd", "addsong", this ); m_hMenu->AddMenuItem( "AddToPlaylist", "#PlaySong", "playsong", this ); int x, y; input()->GetCursorPos( x, y ); m_hMenu->SetPos( x, y ); m_hMenu->SetVisible( true ); } void CMP3TreeControl::OnCommand( char const *cmd ) { if ( !Q_stricmp( cmd, "addsong" ) ) { // Get selected item int songIndex = GetSelectedSongIndex(); if ( songIndex >= 0 ) { m_pPlayer->AddToPlayList( songIndex, false ); } } else if ( !Q_stricmp( cmd, "playsong" ) ) { int songIndex = GetSelectedSongIndex(); if ( songIndex >= 0 ) { m_pPlayer->AddToPlayList( songIndex, true ); } } else { BaseClass::OnCommand( cmd ); } } class CMP3SongProgress : public Slider { DECLARE_CLASS_SIMPLE( CMP3SongProgress, Slider ); public: CMP3SongProgress( Panel *parent, char const *panelName ) : BaseClass( parent, panelName ) { SetPaintEnabled( false ); SetRange( 0, 100 ); } void SetProgress( float frac ) { SetValue( (int)( frac * 100.0f + 0.5f ), false ); } virtual void PaintBackground() { //BaseClass::PaintBackground(); int w, h; GetSize( w, h ); float frac = (float)GetValue() * 0.01f; int barend = ( int )( (float)( w - 2 ) * frac + 0.5f ); surface()->DrawSetColor( GetBgColor() ); surface()->DrawFilledRect( 0, 0, w, h ); surface()->DrawSetColor( GetFgColor() ); surface()->DrawFilledRect( 1, 1, barend, h - 1 ); } virtual void ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); SetFgColor( TREE_TEXT_COLOR ); SetBgColor( pScheme->GetColor( "BorderDark", Color( 0, 0, 0, 255 ) ) ); } }; CMP3Player::CMP3Player( VPANEL parent, char const *panelName ) : BaseClass( NULL, panelName ), m_SelectionFrom( SONG_FROM_UNKNOWN ), m_bDirty( false ), m_bSettingsDirty( false ), m_PlayListFileName( UTL_INVAL_SYMBOL ), m_bSavingFile( false ), m_bEnableAutoAdvance( true ) { g_pPlayer = this; // Get strings... g_pVGuiLocalize->AddFile( "resource/mp3player_%language%.txt" ); SetParent( parent ); SetMoveable( true ); SetSizeable( true ); SetMenuButtonVisible( false ); SetMinimizeButtonVisible( false ); SetMaximizeButtonVisible( false ); SetCloseButtonVisible( true ); m_pOptions = new MenuButton( this, "Menu", "#MP3Menu" ); Menu *options = new Menu( m_pOptions, "Options" ); options->AddMenuItem( "AddDir", "#AddDirectory", new KeyValues( "Command", "command", "adddirectory" ), this ); options->AddMenuItem( "AddGame", "#AddGameSongs", new KeyValues( "Command", "command", "addgamesongs" ), this ); options->AddMenuItem( "Refresh", "#RefreshDb", new KeyValues( "Command", "command", "refresh" ), this ); m_pOptions->SetMenu( options ); m_pTree = new CMP3TreeControl( this, "Tree" ); m_pTree->MakeReadyForUse(); // Make tree use small font IScheme *pscheme = scheme()->GetIScheme( GetScheme() ); HFont treeFont = pscheme->GetFont( "DefaultVerySmall" ); m_pTree->SetFont( treeFont ); m_pFileSheet = new CMP3FileSheet( this, "FileSheet" ); m_pPlay = new Button( this, "Play", "#Play", this, "play" ); m_pStop = new Button( this, "Stop", "#Stop", this, "stop" ); m_pNext = new Button( this, "NextTrack", "#Next", this, "nexttrack" ); m_pPrev = new Button( this, "PrevTrack", "#Prev", this, "prevtrack" ); m_pMute = new CheckButton( this, "Mute", "#Mute" ); m_pShuffle = new CheckButton( this, "Shuffle", "#Shuffle" ); m_pVolume = new Slider( this, "Volume" ); m_pVolume->SetRange( (int)( MUTED_VOLUME * 100.0f ), 100 ); m_pVolume->SetValue( 100 ); m_pCurrentSong = new Label( this, "SongName", "#NoSong" ); m_pDuration = new Label( this, "SongDuration", "" ); m_pSongProgress = new CMP3SongProgress( this, "Progress" ); m_pSongProgress->AddActionSignalTarget( this ); SetSize( 400, 450 ); SetMinimumSize( 350, 400 ); SetTitle( "#MP3PlayerTitle", true ); LoadControlSettings( "resource/MP3Player.res" ); m_pCurrentSong->SetText( "#NoSong" ); m_pDuration->SetText( "" ); m_nCurrentFile = -1; m_bFirstTime = true; m_bPlaying = false; m_SongStart = -1.0f; m_nSongGuid = 0; m_nCurrentSong = 0; m_nCurrentPlaylistSong = 0; m_flCurrentVolume = 1.0f; m_bMuted = false; vgui::ivgui()->AddTickSignal( GetVPanel(), 100 ); } CMP3Player::~CMP3Player() { if ( m_bDirty ) { SaveDb( DB_FILENAME ); } if ( m_bSettingsDirty ) { SaveSettings(); } DeleteSoundDirectories(); RemoveTempSounds(); } void CMP3Player::DeleteSoundDirectories() { int c = m_SoundDirectories.Count(); for ( int i = c - 1 ; i >= 0 ; --i ) { delete m_SoundDirectories[ i ]; } m_SoundDirectories.RemoveAll(); } void CMP3Player::RemoveTempSounds() { FileFindHandle_t fh; char path[ 512 ]; Q_strncpy( path, "sound/_mp3/*.mp3", sizeof( path ) ); char const *fn = g_pFullFileSystem->FindFirstEx( path, "MOD", &fh ); if ( fn ) { do { if ( fn[0] != '.' ) { char ext[ 10 ]; Q_ExtractFileExtension( fn, ext, sizeof( ext ) ); if ( !Q_stricmp( ext, "mp3" ) ) { char killname[ 512 ]; Q_snprintf( killname, sizeof( killname ), "sound/_mp3/%s", fn ); g_pFullFileSystem->RemoveFile( killname, "MOD" ); } } fn = g_pFullFileSystem->FindNext( fh ); } while ( fn ); g_pFullFileSystem->FindClose( fh ); } } void CMP3Player::WipeSoundDirectories() { int c = m_SoundDirectories.Count(); for ( int i = c - 1 ; i >= 0 ; --i ) { SoundDirectory_t *sd = m_SoundDirectories[ i ]; sd->m_pTree->DeleteSubdirectories(); sd->m_pTree->m_FilesInDirectory.RemoveAll(); } } void CMP3Player::AddGameSounds( bool recurse ) { SoundDirectory_t *gamesounds = NULL; int idx = FindSoundDirectory( "" ); if ( idx == m_SoundDirectories.InvalidIndex() ) { gamesounds = new SoundDirectory_t( m_SoundDirectories.Count() ); gamesounds->m_bGameSound = true; gamesounds->m_Root = ""; gamesounds->m_pTree = new MP3Dir_t(); gamesounds->m_pTree->m_DirName = "Game Sounds"; gamesounds->m_pTree->m_FullDirPath = ""; m_SoundDirectories.AddToTail( gamesounds ); } else { gamesounds = m_SoundDirectories[ idx ]; if ( recurse ) { gamesounds->m_pTree->DeleteSubdirectories(); gamesounds->m_pTree->m_FilesInDirectory.RemoveAll(); } } if ( recurse && gamesounds ) { m_nFilesAdded = 0; RecursiveFindMP3Files( gamesounds, SOUND_ROOT, "GAME" ); } } void CMP3Player::OnRefresh() { CUtlVector< CUtlSymbol > dirnames; int i, c; CUtlVector< FileNameHandle_t > m_PlayListFiles; int pcount = m_PlayList.Count(); for ( i = 0; i < pcount; ++i ) { m_PlayListFiles.AddToTail( m_Files[ m_PlayList[ i ] ].filename ); } m_Files.RemoveAll(); WipeSoundDirectories(); ClearPlayList(); c = m_SoundDirectories.Count(); for ( i = 0; i < c; ++i ) { SoundDirectory_t *sd = m_SoundDirectories[ i ]; // Now enumerate all .mp3 files in subfolders of this if ( sd->m_bGameSound ) { m_nFilesAdded = 0; RecursiveFindMP3Files( sd, SOUND_ROOT, "GAME" ); } else { // Add to search path g_pFullFileSystem->AddSearchPath( sd->m_Root.String(), "MP3" ); // Don't pollute regular searches... g_pFullFileSystem->MarkPathIDByRequestOnly( "MP3", true ); m_nFilesAdded = 0; RecursiveFindMP3Files( sd, "", "MP3" ); } } for ( i = 0; i < pcount; ++i ) { char fn[ 512 ]; if ( g_pFullFileSystem->String( m_PlayListFiles[ i ], fn, sizeof( fn ) ) ) { // Find index for song int songIndex = FindSong( fn ); if ( songIndex >= 0 ) { AddToPlayList( songIndex, false ); } } } PopulateTree(); m_bDirty = true; } void CMP3Player::SetVisible( bool state ) { BaseClass::SetVisible( state ); if ( m_bFirstTime && state ) { MoveToCenterOfScreen(); m_bFirstTime = false; LoadSettings(); if ( !RestoreDb( DB_FILENAME ) && m_Files.Count() == 0 ) { // Load the "game" stuff OnRefresh(); } PopulateTree(); } } void CMP3Player::ApplySchemeSettings(IScheme *pScheme) { BaseClass::ApplySchemeSettings(pScheme); HFont treeFont = pScheme->GetFont( "DefaultVerySmall" ); m_pTree->SetFont( treeFont ); } void CMP3Player::OnCommand( char const *cmd ) { if ( !Q_stricmp( cmd, "OnClose" ) ) { SetVisible( false ); } else if ( !Q_stricmp( cmd, "play" ) ) { OnPlay(); } else if ( !Q_stricmp( cmd, "stop" ) ) { OnStop(); } else if ( !Q_stricmp( cmd, "nexttrack" ) ) { OnNextTrack(); } else if ( !Q_stricmp( cmd, "prevtrack" ) ) { OnPrevTrack(); } else if ( !Q_stricmp( cmd, "refresh" ) ) { OnRefresh(); } else if ( !Q_stricmp( cmd, "adddirectory" ) ) { ShowDirectorySelectDialog(); } else if ( !Q_stricmp( cmd, "addgamesongs" ) ) { AddGameSounds( true ); PopulateTree(); } else { BaseClass::OnCommand( cmd ); } } void CMP3Player::SplitFile( CUtlVector< CUtlSymbol >& splitList, char const *relative ) { char work[ 512 ]; Q_strncpy( work, relative, sizeof( work ) ); char const *separators = "/\\"; char *token = strtok( work, separators ); while ( token ) { CUtlSymbol sym = token; splitList.AddToTail( sym ); token = strtok( NULL, separators ); } } MP3Dir_t *CMP3Player::FindOrAddSubdirectory( MP3Dir_t *parent, char const *dirname ) { Assert( parent ); int c = parent->m_Subdirectories.Count(); for ( int i = 0; i < c; ++i ) { MP3Dir_t *sub = parent->m_Subdirectories[ i ]; if ( !Q_stricmp( sub->m_DirName.String(), dirname ) ) { return sub; } } // Add a new subdir MP3Dir_t *sub = new MP3Dir_t(); sub->m_DirName = dirname; char fullpath[ 512 ]; if ( !parent->m_FullDirPath.String()[0] ) { Q_snprintf( fullpath, sizeof( fullpath ), "%s", dirname ); } else { Q_snprintf( fullpath, sizeof( fullpath ), "%s\\%s", parent->m_FullDirPath.String(), dirname ); } sub->m_FullDirPath = fullpath; parent->AddSubDirectory( sub ); return sub; } int CMP3Player::AddSplitFileToDirectoryTree_R( int songIndex, MP3Dir_t *parent, CUtlVector< CUtlSymbol >& splitList, int level ) { char const *current = splitList[ level ].String(); if ( !current ) { return -1; } if ( level == splitList.Count() -1 ) { // It's a filename, add if not already in list if ( songIndex != -1 && parent->m_FilesInDirectory.Find( songIndex ) == parent->m_FilesInDirectory.InvalidIndex() ) { parent->m_FilesInDirectory.AddToTail( songIndex ); } return songIndex; } // It's a directory MP3Dir_t *subdir = FindOrAddSubdirectory( parent, current ); return AddSplitFileToDirectoryTree_R( songIndex, subdir, splitList, level + 1 ); } int CMP3Player::AddFileToDirectoryTree( SoundDirectory_t *dir, char const *relative ) { // AddSong int songIndex = AddSong( relative, dir->GetIndex() ); CUtlVector< CUtlSymbol > list; SplitFile( list, relative ); return AddSplitFileToDirectoryTree_R( songIndex, dir->m_pTree, list, 0 ); } void CMP3Player::RecursiveFindMP3Files( SoundDirectory_t *root, char const *current, char const *pathID ) { FileFindHandle_t fh; #if 0 if ( m_nFilesAdded >= 200 ) return; #endif char path[ 512 ]; if ( current[ 0 ] ) { Q_snprintf( path, sizeof( path ), "%s/*.*", current ); } else { Q_snprintf( path, sizeof( path ), "*.*" ); } Q_FixSlashes( path ); char const *fn = g_pFullFileSystem->FindFirstEx( path, pathID, &fh ); if ( fn ) { do { if ( fn[0] != '.' && Q_strnicmp( fn, "_mp3", 4 ) ) { if ( g_pFullFileSystem->FindIsDirectory( fh ) ) { char nextdir[ 512 ]; if ( current[ 0 ] ) { Q_snprintf( nextdir, sizeof( nextdir ), "%s/%s", current, fn ); } else { Q_snprintf( nextdir, sizeof( nextdir ), "%s", fn ); } RecursiveFindMP3Files( root, nextdir, pathID ); } else { char ext[ 10 ]; Q_ExtractFileExtension( fn, ext, sizeof( ext ) ); if ( !Q_stricmp( ext, "mp3" ) ) { char relative[ 512 ]; if ( root->m_bGameSound ) { Q_snprintf( relative, sizeof( relative ), "%s/%s", current + Q_strlen( SOUND_ROOT"/" ), fn ); } else { if ( current[ 0 ] ) { Q_snprintf( relative, sizeof( relative ), "%s/%s", current, fn ); } else { Q_snprintf( relative, sizeof( relative ), "%s", fn ); } } Msg( "Found '%s/%s'\n", current, fn ); Q_FixSlashes( relative ); ++m_nFilesAdded; AddFileToDirectoryTree( root, relative ); } } } fn = g_pFullFileSystem->FindNext( fh ); } while ( fn ); g_pFullFileSystem->FindClose( fh ); } } int CMP3Player::FindSong( char const *relative ) { Assert( !Q_stristr( relative, "/" ) ); FileNameHandle_t handle = g_pFullFileSystem->FindOrAddFileName( relative ); int c = m_Files.Count(); for ( int i = 0 ; i < c ; ++i ) { const MP3File_t& mp3 = m_Files[ i ]; if ( mp3.filename == handle ) { return i; } } return -1; } int CMP3Player::AddSong( char const *relative, int dirnum ) { int songIndex = FindSong( relative ); if ( songIndex == -1 ) { #if 0 if ( m_Files.Count() >= 200 ) return -1; #endif MP3File_t mp3; Assert( !Q_stristr( relative, "/" ) ); mp3.filename = g_pFullFileSystem->FindOrAddFileName( relative ); char shortname[ 256 ]; Q_FileBase( relative, shortname, sizeof( shortname ) ); Q_SetExtension( shortname, ".mp3", sizeof( shortname ) ); mp3.shortname = shortname; mp3.flags = ( dirnum == 0 ) ? MP3File_t::FLAG_FROMGAME : MP3File_t::FLAG_FROMFS; mp3.dirnum = dirnum; songIndex = m_Files.AddToTail( mp3 ); m_bDirty = true; } return songIndex; } void CMP3Player::RecursiveAddToTree( MP3Dir_t *current, int parentIndex ) { if ( !current ) { return; } // Add all files at current level and then recurse through any directories int i, c; c = current->m_Subdirectories.Count(); for ( i = 0 ; i < c; ++i ) { MP3Dir_t *sub = current->m_Subdirectories[ i ]; Assert( sub ); KeyValues *kv = new KeyValues( "TVI" ); kv->SetString( "Text", sub->m_DirName.String() ); kv->SetPtr( "MP3Dir", sub ); int index = m_pTree->AddItem( kv, parentIndex ); m_pTree->SetItemFgColor( index, TREE_TEXT_COLOR ); // Recurse... RecursiveAddToTree( sub, index ); } // Add raw files c = current->m_FilesInDirectory.Count(); for ( i = 0; i < c; ++i ) { MP3File_t *song = &m_Files[ current->m_FilesInDirectory[ i ] ]; KeyValues *kv = new KeyValues( "TVI" ); kv->SetString( "Text", song->shortname.String() ); kv->SetInt( "SongIndex", current->m_FilesInDirectory[ i ] ); int index = m_pTree->AddItem( kv, parentIndex ); m_pTree->SetItemFgColor( index, TREE_TEXT_COLOR ); } } void CMP3Player::OnTreeViewItemSelected() { PopulateLists(); } void CMP3Player::PopulateTree() { m_pTree->RemoveAll(); // Now populate tree KeyValues *kv = new KeyValues( "TVI" ); kv->SetString( "Text", "Songs" ); int rootIndex = m_pTree->AddItem( kv, -1 ); m_pTree->SetItemFgColor( rootIndex, TREE_TEXT_COLOR ); int dircount = m_SoundDirectories.Count(); for ( int dirnum = 0; dirnum < dircount; ++dirnum ) { MP3Dir_t *tree = m_SoundDirectories[ dirnum ]->m_pTree; if ( !tree ) continue; char const *dirname = tree->m_DirName.String(); kv = new KeyValues( "TVI" ); kv->SetString( "Text", dirname ); int index = m_pTree->AddItem( kv, rootIndex ); RecursiveAddToTree( tree, index ); m_pTree->SetItemFgColor( index, TREE_TEXT_COLOR ); } m_pTree->ExpandItem( rootIndex, true ); PopulateLists(); } // Instead of including windows.h extern "C" { extern int __stdcall CopyFileA( char *pszSource, char *pszDest, int bFailIfExists ); }; void CMP3Player::GetLocalCopyOfSong( const MP3File_t &mp3, char *outsong, size_t outlen ) { outsong[ 0 ] = 0; char fn[ 512 ]; if ( !g_pFullFileSystem->String( mp3.filename, fn, sizeof( fn ) ) ) { return; } if ( mp3.flags == MP3File_t::FLAG_FROMGAME ) { Q_FixSlashes( fn ); Q_strncpy( outsong, fn, outlen ); return; } // Get temp filename from crc CRC32_t crc; CRC32_Init( &crc ); CRC32_ProcessBuffer( &crc, fn, Q_strlen( fn ) ); CRC32_Final( &crc ); char hexname[ 16 ]; Q_binarytohex( (const byte *)&crc, sizeof( crc ), hexname, sizeof( hexname ) ); char hexfilename[ 512 ]; Q_snprintf( hexfilename, sizeof( hexfilename ), "sound/_mp3/%s.mp3", hexname ); Q_FixSlashes( hexfilename ); if ( g_pFullFileSystem->FileExists( hexfilename, "MOD" ) ) { Q_snprintf( outsong, outlen, "_mp3/%s.mp3", hexname ); } else { // Make a local copy char mp3_temp_path[ 512 ]; Q_snprintf( mp3_temp_path, sizeof( mp3_temp_path ), "sound/_mp3" ); g_pFullFileSystem->CreateDirHierarchy( mp3_temp_path, "MOD" ); char destpath[ 512 ]; Q_snprintf( destpath, sizeof( destpath ), "%s/%s", engine->GetGameDirectory(), hexfilename ); Q_FixSlashes( destpath ); char sourcepath[ 512 ]; Assert( mp3.dirnum >= 0 && mp3.dirnum < m_SoundDirectories.Count() ); SoundDirectory_t *sdir = m_SoundDirectories[ mp3.dirnum ]; Q_snprintf( sourcepath, sizeof( sourcepath ), "%s/%s", sdir->m_Root.String(), fn ); Q_FixSlashes( sourcepath ); // !!!HACK HACK: // Total hack right now, using windows OS calls to copy file to full destination int success = ::CopyFileA( sourcepath, destpath, TRUE ); if ( success > 0 ) { Q_snprintf( outsong, outlen, "_mp3/%s.mp3", hexname ); } } Q_FixSlashes( outsong ); } void CMP3Player::PlaySong( int songIndex, float skipTime /*= 0.0f */ ) { MP3File_t& song = m_Files[ songIndex ]; float volume = 1.0f; char soundname[ 512 ]; soundname[ 0 ] = 0; if ( song.playbackfilename == (FileNameHandle_t)0 ) { GetLocalCopyOfSong( song, soundname, sizeof( soundname ) ); if ( !soundname[ 0 ] ) { return; } Assert( !Q_stristr( soundname, "/" ) ); song.playbackfilename = g_pFullFileSystem->FindOrAddFileName( soundname ); } else { if ( !g_pFullFileSystem->String( song.playbackfilename, soundname, sizeof( soundname ) ) ) { return; } } // Msg( "Playing '%s'\n", soundname ); if ( !soundname[ 0 ] ) { return; } if ( m_bPlaying ) { OnStop(); } char drymix[ 512 ]; Q_snprintf( drymix, sizeof( drymix ), "#%s", soundname ); enginesound->EmitAmbientSound( drymix, volume, PITCH_NORM, 0, skipTime == 0.0f ? 0.0f : ( gpGlobals->curtime + skipTime ) ); m_nSongGuid = enginesound->GetGuidForLastSoundEmitted(); m_nCurrentSong = songIndex; m_bPlaying = true; m_LastSong = song.playbackfilename; m_SongStart = gpGlobals->realtime; m_flSongDuration = GetMP3Duration( soundname ); m_pCurrentSong->SetText( song.shortname.String() ); m_nSongMinutes = (int)( m_flSongDuration / 60.0f ); m_nSongSeconds = (int)( m_flSongDuration - (float)( m_nSongMinutes * 60 ) ); char durationstr[ 256 ]; Q_snprintf( durationstr, sizeof( durationstr ), "0:00 / %i:%02i", m_nSongMinutes, m_nSongSeconds ); m_pDuration->SetText( durationstr ); m_pSongProgress->SetProgress( 0.0f ); } void CMP3Player::OnStop() { if ( m_bPlaying ) { m_bPlaying = false; if ( m_nSongGuid != 0 ) { enginesound->StopSoundByGuid( m_nSongGuid ); m_nSongGuid = 0; } m_LastSong = (FileNameHandle_t)0; m_pCurrentSong->SetText( "#NoSong" ); m_pSongProgress->SetProgress( 0.0f ); m_pDuration->SetText( "" ); } } float CMP3Player::GetMP3Duration( char const *songname ) { return enginesound->GetSoundDuration( songname ); } void CMP3Player::SelectedSongs( SongListSource_t from, CUtlVector< int >& songIndexList ) { m_SelectedSongs.RemoveAll(); m_SelectionFrom = from; int i, c; c = songIndexList.Count(); for ( i = 0; i < c; ++i ) { m_SelectedSongs.AddToTail( songIndexList[ i ] ); } } void CMP3Player::OnPlay() { int c = m_SelectedSongs.Count(); for ( int i = 0 ; i < c; ++i ) { int songIndex = m_SelectedSongs[ i ]; if ( songIndex < 0 || songIndex >= m_Files.Count() ) { continue; } if ( m_SelectionFrom == SONG_FROM_PLAYLIST ) { // Can only play one song at a time from playlist... PlaySong( songIndex ); break; } else { AddToPlayList( songIndex, i == 0 ); } } } void CMP3Player::OnTick() { BaseClass::OnTick(); if ( !m_bPlaying ) { return; } float newVol = (float)m_pVolume->GetValue() / 100.0f; bool volumeChanged = ( newVol != m_flCurrentVolume ); if ( volumeChanged ) { m_flCurrentVolume = newVol; } bool muteChanged = m_bMuted != m_pMute->IsSelected(); if ( muteChanged ) { m_bMuted = m_pMute->IsSelected(); } if ( m_nSongGuid == 0 ) { return; } bool playing = enginesound->IsSoundStillPlaying( m_nSongGuid ); if ( playing ) { if ( muteChanged ) { if ( m_bMuted ) { OnChangeVolume( MUTED_VOLUME ); } else { OnChangeVolume( m_flCurrentVolume ); } } if ( volumeChanged ) { // Msg( "set volume %f\n", m_flCurrentVolume ); OnChangeVolume( m_flCurrentVolume ); } if ( m_flSongDuration >= 0.001f ) { float elapsed = gpGlobals->realtime - m_SongStart; float frac = elapsed / m_flSongDuration; frac = clamp( frac, 0.0f, 1.0f ); m_pSongProgress->SetProgress( frac ); int minutes = ( int ) ( elapsed / 60.0f ); int seconds = (int)( elapsed - ( 60 * minutes ) ); char durationstr[ 256 ]; Q_snprintf( durationstr, sizeof( durationstr ), "%i:%02i / %i:%02i", minutes, seconds, m_nSongMinutes, m_nSongSeconds ); m_pDuration->SetText( durationstr ); } return; } if ( !m_bEnableAutoAdvance ) { // If we got disconnected completely, reset the flag if ( !engine->IsConnected() ) { m_bEnableAutoAdvance = true; } return; } // No song playing... m_nSongGuid = 0; OnNextTrack(); } void CMP3Player::OnChangeVolume( float newVol ) { if ( !m_bPlaying ) { return; } if ( !m_nSongGuid ) return; enginesound->SetVolumeByGuid( m_nSongGuid, newVol ); } void CMP3Player::GoToNextSong( int skip ) { bool shuffle = m_pShuffle->IsSelected(); int nextSong = 0; if ( m_PlayList.Count() > 0 ) { if ( shuffle ) { m_nCurrentPlaylistSong = random->RandomInt( 0, m_PlayList.Count() - 1 ); } else { m_nCurrentPlaylistSong = ( m_nCurrentPlaylistSong + skip ) % m_PlayList.Count(); if ( m_nCurrentPlaylistSong < 0 ) { m_nCurrentPlaylistSong = m_PlayList.Count() - 1; } } nextSong = m_PlayList[ m_nCurrentPlaylistSong ]; m_pFileSheet->OnPlayListItemPlaying( m_nCurrentPlaylistSong ); } else { if ( shuffle ) { nextSong = random->RandomInt( 0, m_Files.Count() - 1 ); } else { nextSong = ( m_nCurrentSong + skip ) % m_Files.Count(); if ( nextSong < 0 ) { nextSong = m_Files.Count() - 1; } } } PlaySong( nextSong ); } void CMP3Player::OnNextTrack() { if ( m_Files.Count() == 0 ) { return; } GoToNextSong( +1 ); } void CMP3Player::OnPrevTrack() { if ( m_Files.Count() == 0 ) { return; } GoToNextSong( -1 ); } void CMP3Player::RemoveFSSongs() { int c = m_Files.Count(); for ( int i = c - 1; i >= 0; --i ) { MP3File_t& mp3 = m_Files[ i ]; if ( mp3.flags == MP3File_t::FLAG_FROMGAME ) continue; m_Files.Remove( i ); } } int CMP3Player::FindSoundDirectory( char const *fullpath ) { CUtlSymbol sym = fullpath; int c = m_SoundDirectories.Count(); for ( int i = 0; i < c; ++i ) { if ( sym == m_SoundDirectories[ i ]->m_Root ) return i; } return m_SoundDirectories.InvalidIndex(); } SoundDirectory_t *CMP3Player::AddSoundDirectory( char const *fullpath, bool recurse ) { // RemoveFSSongs(); m_bDirty = true; m_bSettingsDirty = true; CUtlSymbol sym = fullpath; int sdi = FindSoundDirectory( fullpath ); if ( sdi == m_SoundDirectories.InvalidIndex() ) { SoundDirectory_t *sounddir = new SoundDirectory_t( m_SoundDirectories.Count() ); sounddir->m_bGameSound = false; sounddir->m_Root = sym; sounddir->m_pTree = new MP3Dir_t(); sounddir->m_pTree->m_DirName = fullpath; sounddir->m_pTree->m_FullDirPath = fullpath; sdi = m_SoundDirectories.AddToTail( sounddir ); // Add to search path g_pFullFileSystem->AddSearchPath( fullpath, "MP3" ); // Don't pollute regular searches... g_pFullFileSystem->MarkPathIDByRequestOnly( "MP3", true ); // Now enumerate all .mp3 files in subfolders of this if ( recurse ) { m_nFilesAdded = 0; RecursiveFindMP3Files( sounddir, "", "MP3" ); } } return m_SoundDirectories[ sdi ]; } void CMP3Player::PopulateLists() { CUtlVector< KeyValues * > kv; m_pTree->GetSelectedItemData( kv ); if ( !kv.Count() ) { return; } MP3Dir_t *dir = static_cast< MP3Dir_t * >( kv[ 0 ]->GetPtr( "MP3Dir", 0 ) ); if ( !dir ) { return; } int i, c; c = dir->m_FilesInDirectory.Count(); if ( !c ) { return; } m_pFileSheet->ResetFileList(); for ( i = 0; i < c ; ++i ) { m_pFileSheet->AddSongToFileList( dir->m_FilesInDirectory[ i ] ); } } MP3File_t *CMP3Player::GetSongInfo( int songIndex ) { if ( songIndex < 0 || songIndex >= m_Files.Count() ) { return NULL; } return &m_Files[ songIndex ]; } void CMP3Player::AddToPlayList( int songIndex, bool playNow ) { m_pFileSheet->AddSongToPlayList( songIndex ); m_PlayList.AddToTail( songIndex ); if ( playNow ) { PlaySong( songIndex ); SetPlayListSong( m_PlayList.Count() - 1 ); } // refresh the playlist } void CMP3Player::RemoveFromPlayList( int songIndex ) { m_pFileSheet->RemoveSongFromPlayList( songIndex ); m_PlayList.FindAndRemove( songIndex ); SetPlayListSong( m_nCurrentPlaylistSong ); } void CMP3Player::ClearPlayList() { m_pFileSheet->ResetPlayList(); m_PlayList.RemoveAll(); m_nCurrentPlaylistSong = 0; } void CMP3Player::OnLoadPlayList() { ShowFileOpenDialog( false ); } void CMP3Player::OnSavePlayList() { if ( UTL_INVAL_SYMBOL == m_PlayListFileName ) { OnSavePlayListAs(); return; } SavePlayList( m_PlayListFileName.String() ); } void CMP3Player::OnSavePlayListAs() { ShowFileOpenDialog( true ); } void CMP3Player::RestoreSongs( KeyValues *songs ) { Assert( m_Files.Count() == 0 ); for ( KeyValues *song = songs->GetFirstSubKey(); song != NULL; song = song->GetNextKey() ) { int flags = 0; int game = song->GetInt( "fromgame", 0 ); if ( game ) { flags |= MP3File_t::FLAG_FROMGAME; } int fs = song->GetInt( "fromfs", 0 ); if ( fs ) { flags |= MP3File_t::FLAG_FROMFS; } int subdir = song->GetInt( "subdirindex", 0 ); char shortname[ 512 ]; char filename[ 512 ]; Q_strncpy( shortname, song->GetString( "short", "" ), sizeof( shortname ) ); Q_strncpy( filename, song->GetString( "filename", "" ), sizeof( filename ) ); MP3File_t file; file.dirnum = subdir; file.flags = flags; file.shortname = shortname; file.filename = g_pFullFileSystem->FindOrAddFileName( filename ); m_Files.AddToTail( file ); } } void CMP3Player::RestoreDirectory( KeyValues *dir, SoundDirectory_t *sd ) { for ( KeyValues *kv = dir->GetFirstSubKey(); kv; kv = kv->GetNextKey() ) { if ( !Q_stricmp( kv->GetName(), "name" ) ) { sd->m_Root = kv->GetString(); } else if ( !Q_stricmp( kv->GetName(), "gamesounds" ) ) { sd->m_bGameSound = kv->GetBool(); } else if ( !Q_stricmp( kv->GetName(), "dirname" ) ) { sd->m_pTree->m_DirName = kv->GetString(); } else if ( !Q_stricmp( kv->GetName(), "fullpath" ) ) { sd->m_pTree->m_FullDirPath = kv->GetString(); } else if ( !Q_stricmp( kv->GetName(), "files" ) ) { for ( KeyValues *f = kv->GetFirstSubKey(); f != NULL; f = f->GetNextKey() ) { if ( !Q_stricmp( f->GetName(), "file" ) ) { int songIndex = f->GetInt(); if ( songIndex >= 0 && songIndex < m_Files.Count() ) { char fn[ 512 ]; if ( g_pFullFileSystem->String( m_Files[ songIndex ].filename, fn, sizeof( fn ) ) ) { AddFileToDirectoryTree( sd, fn ); } } } } } else { Warning( "Unknown key '%s'\n", kv->GetName() ); } } } void CMP3Player::RestoreDirectories( KeyValues *dirs ) { for ( KeyValues *dir = dirs->GetFirstSubKey(); dir != NULL; dir = dir->GetNextKey() ) { char const *dirpath = dir->GetString( "fullpath", "" ); if ( dirpath ) { int sdi = FindSoundDirectory( dirpath ); if ( sdi == m_SoundDirectories.InvalidIndex() ) { SoundDirectory_t *sd = AddSoundDirectory( dirpath, false ); sdi = sd->GetIndex(); } RestoreDirectory( dir, m_SoundDirectories[ sdi ] ); } } } bool CMP3Player::RestoreDb( char const *filename ) { KeyValues *kv = new KeyValues( "db" ); Assert( kv ); if ( !kv->LoadFromFile( g_pFullFileSystem, filename, "MOD" ) ) { Warning( "Unable to load '%s'\n", filename ); return false; } KeyValues *songs = kv; Assert( !Q_stricmp( songs->GetName(), "songs" ) ); RestoreSongs( songs ); KeyValues *dirs = songs->GetNextKey(); Assert( !Q_stricmp( dirs->GetName(), "directories" ) ); RestoreDirectories( dirs ); kv->deleteThis(); return true; } void bpr( int level, CUtlBuffer& buf, char const *fmt, ... ) { char txt[ 4096 ]; va_list argptr; va_start( argptr, fmt ); _vsnprintf( txt, sizeof( txt ) - 1, fmt, argptr ); va_end( argptr ); int indent = 2; for ( int i = 0; i < ( indent * level ); ++i ) { buf.Printf( " " ); } buf.Printf( "%s", txt ); } void CMP3Player::SaveDbFile( int level, CUtlBuffer& buf, MP3File_t *file, int filenumber ) { bpr( level, buf, "file\n" ); bpr( level, buf, "{\n" ); bpr( level + 1, buf, "filenumber %i\n", filenumber ); if ( file->flags & MP3File_t::FLAG_FROMGAME ) { bpr( level + 1, buf, "fromgame 1\n" ); } if ( file->flags & MP3File_t::FLAG_FROMFS ) { bpr( level + 1, buf, "fromfs 1\n" ); } bpr( level + 1, buf, "subdirindex %i\n", file->dirnum ); bpr( level + 1, buf, "short \"%s\"\n", file->shortname.String() ); char fn[ 512 ]; if ( g_pFullFileSystem->String( file->filename, fn, sizeof( fn ) ) ) { bpr( level + 1, buf, "filename \"%s\"\n", fn ); } else { Assert( 0 ); } bpr( level, buf, "}\n" ); } void CMP3Player::FlattenDirectoryFileList_R( MP3Dir_t *dir, CUtlVector< int >& list ) { int i, c; c = dir->m_FilesInDirectory.Count(); for ( i = 0; i < c; ++i ) { int songIndex = dir->m_FilesInDirectory[ i ]; if ( list.Find( songIndex ) == list.InvalidIndex() ) { list.AddToTail( songIndex ); } } c = dir->m_Subdirectories.Count(); for ( i = 0; i < c; ++i ) { FlattenDirectoryFileList_R( dir->m_Subdirectories[ i ], list ); } } void CMP3Player::SaveDbDirectory( int level, CUtlBuffer& buf, SoundDirectory_t *sd ) { bpr( level, buf, "directory\n" ); bpr( level, buf, "{\n" ); bpr( level + 1, buf, "gamesounds %i\n", sd->m_bGameSound ? 1 : 0 ); bpr( level + 1, buf, "name \"%s\"\n", sd->m_Root.String() ); bpr( level + 1, buf, "dirname \"%s\"\n", sd->m_pTree->m_DirName.String() ); bpr( level + 1, buf, "fullpath \"%s\"\n", sd->m_pTree->m_FullDirPath.String() ); CUtlVector< int > files; if ( sd->m_pTree ) { FlattenDirectoryFileList_R( sd->m_pTree, files ); } int i, c; c = files.Count(); if ( c > 0 ) { bpr( level + 1, buf, "files\n" ); bpr( level + 1, buf, "{\n" ); for ( i = 0; i < c; ++i ) { bpr( level + 2, buf, "file %i\n", files[ i ] ); } bpr( level + 1, buf, "}\n" ); } bpr( level, buf, "}\n" ); } void CMP3Player::SaveDb( char const *filename ) { int i, c; CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); buf.Printf( "// mp3 database, automatically generated\n" ); bpr( 0, buf, "songs\n{\n" ); c = m_Files.Count(); for ( i = 0; i < c; ++i ) { MP3File_t* file = &m_Files[ i ]; SaveDbFile( 1, buf, file, i ); } bpr( 0, buf, "}\n" ); bpr( 0, buf, "directories\n{\n" ); c = m_SoundDirectories.Count(); for ( i = 0; i < c; ++i ) { SoundDirectory_t *sd = m_SoundDirectories[ i ]; if ( sd->m_pTree ) { SaveDbDirectory( 1, buf, sd ); } } bpr( 0, buf, "}\n" ); FileHandle_t fh = g_pFullFileSystem->Open( filename, "wb" ); if ( FILESYSTEM_INVALID_HANDLE != fh ) { g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh ); g_pFullFileSystem->Close( fh ); m_bDirty = false; } else { Warning( "Unable to open '%s' for writing\n", filename ); } } void CMP3Player::OnSave() { SaveDb( DB_FILENAME ); } void CMP3Player::OnSliderMoved() { if ( !m_bPlaying ) { return; } // The engine only allows 4 seconds of skip ahead right now and you have to be connected to get it to work // until this is relaxed we can't do this this way... #if 0 float frac = (float)m_pSongProgress->GetValue() / 100.0f; float offset = frac * m_flSongDuration; PlaySong( m_nCurrentSong, -offset ); #endif } void CMP3Player::LoadPlayList( char const *filename ) { KeyValues *kv = new KeyValues( "playlist" ); Assert( kv ); if ( !kv->LoadFromFile( g_pFullFileSystem, filename, "MOD" ) ) { Warning( "Unable to load '%s'\n", MP3_SETTINGS_FILE ); return; } m_PlayListFileName = filename; for ( KeyValues *song = kv->GetFirstSubKey(); song != NULL; song = song->GetNextKey() ) { if ( !Q_stricmp( song->GetName(), "song" ) ) { char const *songname = song->GetString( "relativepath" ); if ( !songname || !songname[ 0 ] ) continue; char const *dirname = song->GetString( "directory" ); int flags = 0; int game = song->GetInt( "fromgame", 0 ); if ( game ) { flags |= MP3File_t::FLAG_FROMGAME; } int fs = song->GetInt( "fromfs", 0 ); if ( fs ) { flags |= MP3File_t::FLAG_FROMFS; } int songIndex = -1; // Find index int idx = FindSong( songname ); if ( idx == -1 ) { // See if directory exists... if ( flags & MP3File_t::FLAG_FROMGAME ) { songIndex = AddSong( songname, 0 ); } else if ( dirname ) { SoundDirectory_t *sd = NULL; int dirnum = FindSoundDirectory( dirname ); if ( dirnum == -1 ) { sd = AddSoundDirectory( dirname, false ); } else { sd = m_SoundDirectories[ dirnum ]; } if ( sd ) { songIndex = AddFileToDirectoryTree( sd, songname ); } } } else { songIndex = idx; } if ( songIndex >= 0 ) { m_PlayList.AddToTail( songIndex ); } } } kv->deleteThis(); PopulateTree(); PopulateLists(); } void CMP3Player::SavePlayList( char const *filename ) { FileHandle_t fh = g_pFullFileSystem->Open( filename, "wb" ); if ( FILESYSTEM_INVALID_HANDLE != fh ) { m_PlayListFileName = filename; CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); buf.Printf( "// mp3 playlist\n" ); bpr( 0, buf, "playlist\n{\n" ); int c = m_PlayList.Count(); for ( int i = 0; i < c; ++i ) { MP3File_t& song = m_Files[ m_PlayList[ i ] ]; char fn[ 512 ]; if ( g_pFullFileSystem->String( song.filename, fn, sizeof( fn ) ) ) { char dirname[ 512 ]; dirname[0]=0; if ( song.dirnum >= 0 ) { SoundDirectory_t *sd = m_SoundDirectories[ song.dirnum ]; Q_strncpy( dirname, sd->m_Root.String(), sizeof( dirname ) ); } bpr( 1, buf, "song\n" ); { bpr( 2, buf, "%s 1\n", song.flags == MP3File_t::FLAG_FROMFS ? "fromfs" : "fromgame" ); if ( dirname[0]) { bpr( 2, buf, "directory \"%s\"\n", dirname ); } bpr( 2, buf, "relativepath \"%s\"\n", fn ); } bpr( 1, buf, "\n" ); } } bpr( 0, buf, "}\n" ); g_pFullFileSystem->Close( fh ); SetMostRecentPlayList( filename ); } } void CMP3Player::SetMostRecentPlayList( char const *filename ) { m_PlayListFileName = filename; m_bSettingsDirty = true; } void CMP3Player::LoadSettings() { KeyValues *kv = new KeyValues( "settings" ); Assert( kv ); if ( !kv->LoadFromFile( g_pFullFileSystem, MP3_SETTINGS_FILE, "MOD" ) ) { Warning( "Unable to load '%s'\n", MP3_SETTINGS_FILE ); return; } char const *filename = kv->GetString( "mostrecentplaylist", "" ); if ( filename && filename[ 0 ] ) { LoadPlayList( filename ); } KeyValues *dirs = kv->FindKey( "directories", false ); if ( dirs ) { for ( KeyValues *sub = dirs; sub ; sub = sub->GetNextKey() ) { char const *dirname = sub->GetString( "dirname", "" ); if ( dirname && dirname[ 0 ] ) { AddSoundDirectory( dirname, false ); } else if ( dirname ) { AddGameSounds( false ); } } } kv->deleteThis(); m_bSettingsDirty = false; } void CMP3Player::ShowFileOpenDialog( bool saving ) { m_bSavingFile = saving; if ( m_hSaveLoadPlaylist.Get() ) { m_hSaveLoadPlaylist.Get()->MarkForDeletion(); } m_hSaveLoadPlaylist = new FileOpenDialog( this, "Choose Playlist", !saving ); if ( m_hSaveLoadPlaylist.Get() ) { m_hSaveLoadPlaylist->SetDeleteSelfOnClose( false ); m_hSaveLoadPlaylist->SetStartDirectory( "resource/" ); m_hSaveLoadPlaylist->AddFilter( "*.txt", "Playlists", true ); m_hSaveLoadPlaylist->DoModal( m_bSavingFile ); } } void CMP3Player::OnFileSelected( char const *fullpath ) { if ( m_bSavingFile ) { SavePlayList( fullpath ); m_PlayListFileName = fullpath; } else { m_PlayListFileName = fullpath; LoadPlayList( fullpath ); m_nCurrentPlaylistSong = 0; } if ( m_hSaveLoadPlaylist.Get() ) { m_hSaveLoadPlaylist.Get()->MarkForDeletion(); } } void CMP3Player::ShowDirectorySelectDialog() { if ( m_hDirectorySelect.Get() ) { m_hDirectorySelect.Get()->MarkForDeletion(); } m_hDirectorySelect = new DirectorySelectDialog( this, "Choose Directory" ); if ( m_hDirectorySelect.Get() ) { m_hDirectorySelect->MakeReadyForUse(); m_hDirectorySelect->SetStartDirectory( MP3_DEFAULT_MP3DIR ); m_hDirectorySelect->SetFgColor( TREE_TEXT_COLOR ); m_hDirectorySelect->DoModal(); } } void CMP3Player::OnDirectorySelected( KeyValues *params ) { if ( m_hDirectorySelect.Get() ) { m_hDirectorySelect.Get()->MarkForDeletion(); } char const *fullpath = params->GetString( "dir", "" ); if ( fullpath && fullpath[ 0 ] ) { char dir[ 512 ]; Q_strncpy( dir, fullpath, sizeof( dir ) ); Q_StripTrailingSlash( dir ); AddSoundDirectory( dir, true ); PopulateTree(); m_bDirty = true; } } void CMP3Player::SaveSettings() { if ( !m_bSettingsDirty ) { return; } m_bSettingsDirty = false; FileHandle_t fh = g_pFullFileSystem->Open( MP3_SETTINGS_FILE, "wb" ); if ( FILESYSTEM_INVALID_HANDLE != fh ) { CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); buf.Printf( "// mp3 settings, automatically generated\n" ); bpr( 0, buf, "settings\n{\n" ); // FIXME: Move to function if there are more settings to save... if ( UTL_INVAL_SYMBOL != m_PlayListFileName ) { bpr( 1, buf, "mostrecentplaylist \"%s\"\n", m_PlayListFileName.String() ); } int c; c = m_SoundDirectories.Count(); if ( c > 0 ) { bpr( 1, buf, "directories\n" ); bpr( 1, buf, "{\n" ); for ( int i = 0; i < c; ++i ) { SoundDirectory_t *sd = m_SoundDirectories[ i ]; Assert( sd ); bpr( 2, buf, "dirname \"%s\"\n", sd->m_Root.String() ); } bpr( 1, buf, "}\n" ); } bpr( 0, buf, "}\n" ); g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh ); g_pFullFileSystem->Close( fh ); } } void CMP3Player::SetPlayListSong( int listIndex ) { if ( m_PlayList.Count() > 0 ) { m_nCurrentPlaylistSong = listIndex % m_PlayList.Count(); } else { m_nCurrentPlaylistSong = 0; } } void CMP3Player::EnableAutoAdvance( bool state ) { m_bEnableAutoAdvance = state; } //----------------------------------------------------------------------------- // Purpose: The purpose of this is that when a changelevel occurs, the engine calls // StopAllSounds several times, and the OnTick handler thinks the song has finished playing // and so it moves to the next song. This causes the play list to skip ahead by > 1 song during a level // change. //----------------------------------------------------------------------------- class CMP3PlayerGameSystem : public CAutoGameSystem { public: CMP3PlayerGameSystem() { } virtual void LevelInitPreEntity() { g_pPlayer->EnableAutoAdvance( true ); } virtual void LevelShutdownPostEntity() { // If we are still connected, disable auto advance until we get into the next level if ( engine->IsConnected() ) { g_pPlayer->EnableAutoAdvance( false ); } } }; static CMP3PlayerGameSystem g_MP3Helper; #else void MP3Player_Create( vgui::VPANEL parent ) { } void MP3Player_Destroy() { } #endif