diff --git a/.gitmodules b/.gitmodules index 9510c702..3ace6396 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "vgui_support"] path = vgui_support url = https://github.com/FWGS/vgui_support + +[submodule "miniaudio"] + path = miniaudio + url = https://github.com/mackron/miniaudio diff --git a/CMakeLists.txt b/CMakeLists.txt index 36fc55a8..589bd644 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ option(BUILD_CLIENT "Build client dll" ON) option(BUILD_SERVER "Build server dll" ON) option(GOLDSOURCE_SUPPORT "Build goldsource compatible client library" OFF) option(USE_GSTREAMER "Enable GStreamer." OFF) +option(USE_MINIAUDIO "Enable Miniaudio." OFF) option(USE_FMOD "Enable FMOD." OFF) if (CMAKE_SIZEOF_VOID_P EQUAL 4 OR diff --git a/README.md b/README.md index 58bac40f..01d478be 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Things fixed while transferring code: - Sniper rifle zoom states didn't work, m_pPlayer->pev->fov needed to be equalized to m_pPlayer->m_iFOV. - VGUI used to hard-crash, partially fixed: CImageLables in vgui_OrdiControl that caused the crash were replaced with CommandButtons, skipTime added to keypad, OrdiMenu and OrdiControl in order to fix double mouse key presses, +1 added to radiomsg.cpp to fix the butts of the text messages, HUD health display didn't work due to Health Msg system receieving Battery-related data, fixed by manually re-coping a part of old code and paritioning it properly. - Also fixed .txt files and fonts not loading due to backslashes, VGUI folder name being written in capitals and such. -- Music not playing fixed by creating a GStreamer implementation. +- Music not playing fixed by creating Miniaudio (thanks @nekonomicon) and GStreamer implementations. - l2m3 has a green exploding tank, which has func_door's named tremble_1 and tremble_2, when activated they cause SegFault on any non GoldSource-compatible build. Don't know why yet. Fixed by replacing a multi-manager target with garbage names if the map name and targetname match, see triggers.cpp. - Tank used to have a weird sound (glock event playback) when primary attack is activated, fixed with m_iPlayerInTankExternal see hud_tank.cpp and hl_weapons.cpp. - IR gun didn't actually Infra-Red anything, fixed by transferring changes to StudioModelRenderer.cpp. @@ -78,25 +78,33 @@ systems - you're more than welcome to do so! mkdir build cd build - cmake ../ -DUSE_VGUI=1 -DUSE_GSTREAMER=1 + cmake ../ -DUSE_VGUI=1 -DUSE_MINIAUDIO=1 make -On Debian, the following is necessary to: +Must be built with -DUSE_VGUI=1 to work properly, as this mod makes heavy use of true-VGUI, thus update the modules when clonning. + +To be able to hear music during gameplay, on the following three is necessary: +-DUSE_MINIAUDIO=1 (this is the smallest and most compatible implementation as of now), +-DUSE_GSTREAMER=1 (and have appropriate architechture (i386) gstreamer-1.0 and dev packages installed), or, +-DUSE_FMOD=1 (untested, on Windows and will require fmod headers v 3.6.1 if you can still get them somewhere). + +If using MiniAudio, make sure the submodules are updated. +As mentioned above, you have to do it anyway to get the VGUI headers. + +If GStreamer is preferred, on Debian, the following is necessary to: sudo apt install libgstreamer1.0:i386 #to play with music (also install plugins) - sudo apt install libgstreamer1.0-dev:i386 #to compile with music (-DUSE_GSTREAMER=1) + sudo apt install libgstreamer1.0-dev:i386 #to compile with music -Also added -DUSE_FMOD option for cmake and tried my best to prevent it being used in Linux (uses windows.h include). +So far GStreamer should only compile on Linux, as I haven't tested it (or linking it) on any other system. +Also, dlls/CMakeLists.txt must have a system-appropriate path to i386 version of GStreamer headers. +See set for ```ENV{PKG_CONFIG_PATH}```. + +The FMod option can be used on Windows instead (uses windows.h include), +but requires and old and outdated FMod implementation v 3.6.1. Not recommended. The following will likely be necessary to compile a gold-source compatible (old xash, e.t.c) binaries: sudo apt install libsdl2-dev:i386 (but this wants to install a ton of i386 dev packages) - -Must be built with -DUSE_VGUI=1 at least to work properly (thus, update the modules when clonning). -Must be built with either -DUSE_FMOD=1 (untested, on Windows and will require fmod headers v 3.6.1 if you can still get them somewhere), -or -DUSE_GSTREAMER=1 (and have appropriate architechture (i386) gstreamer-1.0 and dev packages installed) to be able to play with music. -So far GStreamer should only compile on Linux, as I haven't tested it (or linking it) on any other system. -Also, dlls/CMakeLists.txt must have a system-appropriate path to i386 version of GStreamer headers. -See set for ```ENV{PKG_CONFIG_PATH}```. \ No newline at end of file diff --git a/dlls/CMakeLists.txt b/dlls/CMakeLists.txt index 2e4f8e2e..84f15a72 100644 --- a/dlls/CMakeLists.txt +++ b/dlls/CMakeLists.txt @@ -41,6 +41,10 @@ if (USE_GSTREAMER AND (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")) add_definitions(-DUSE_GSTREAMER) endif() +if (USE_MINIAUDIO) + add_definitions(-DUSE_MINIAUDIO) +endif() + if (USE_FMOD AND (WIN32 OR NOT (${CMAKE_SYSTEM_NAME} STREQUAL "Linux"))) add_definitions(-DUSE_FMOD) endif() diff --git a/dlls/music.cpp b/dlls/music.cpp index 7ca84ff9..12868736 100644 --- a/dlls/music.cpp +++ b/dlls/music.cpp @@ -18,6 +18,8 @@ This code is a placeholder for systems that support neither gstreamer nor fmod. #ifdef USE_GSTREAMER #include "musicgstreamer.cpp" +#elif defined(USE_MINIAUDIO) +#include "musicminiaudio.cpp" #elif defined(USE_FMOD) #include "musicfmod.cpp" #else diff --git a/dlls/musicminiaudio.cpp b/dlls/musicminiaudio.cpp new file mode 100644 index 00000000..5194d5c6 --- /dev/null +++ b/dlls/musicminiaudio.cpp @@ -0,0 +1,495 @@ +//------------------------------------------------------------- +//------------------------------------------------------------- +//- +//- musicminiaudio.cpp +//- +//------------------------------------------------------------- +//------------------------------------------------------------- +//- by Roy at suggestion by nekonomicon, based on code by JujU +//------------------------------------------------------------- +//- mp3 player code for HL mod +//------------------------------------------------------------- +//- +//- compatible with version 0.11.9 of Miniaudio +//- https://github.com/mackron/miniaudio +//- +//------------------------------------------------------------- + +/* +Don't forget to update the miniaudio submodule. + +Miniaudio 0.11.9 or better required. + +Tested on Debian. + +For playlist format see the bottom of the file. +*/ + + + +//--------------------------------------------------------- +// inclusions + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + +#include "musicminiaudio.h" + +//These are just initial ones. If the actual track has different ones, they will be re-applied during Play(). +#define SAMPLE_FORMAT ma_format_f32 +#define CHANNEL_COUNT 2 +#define SAMPLE_RATE 48000 + +CMusic g_MusicPlayer; +void CMusic_DecoderCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + +//--------------------------------------------------------- +// initialisation + +void CMusic :: Init ( void ) +{ + + if( m_bInit == TRUE ){ + return; //Do not re-init. + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = SAMPLE_FORMAT; + deviceConfig.playback.channels = CHANNEL_COUNT; + deviceConfig.sampleRate = SAMPLE_RATE; + deviceConfig.dataCallback = CMusic_DecoderCallback; // this contains the callback that monitors the end of the song + deviceConfig.pUserData = NULL; + + if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) { + ALERT ( at_console, "MUSICPLAYER : unable to initialize\n" ); + return; + } + + m_bInit = TRUE; +} + + + + +//--------------------------------------------------------- +// Callback being called during playback + +void CMusic_DecoderCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + if(g_MusicPlayer.m_IsPlaying == FALSE){ + return; //We are paused or stopped, let's exit now. + } + + ma_decoder* pDecoder = (ma_decoder*)&g_MusicPlayer.decoder; + if (pDecoder == NULL) { + return; + } + + if(frameCount<=0) return; + + ma_uint64 framesRead; + + ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount, &framesRead); + if(framesRead < frameCount) //This happens when the song ends. + g_MusicPlayer.songEnd(); + + (void)pInput; +} + +//--------------------------------------------------------- +// playing an audio file + + +void CMusic :: OpenFile ( const char *filename, int repeat ) +{ + audiofile_t *p = NULL; + p = new audiofile_t; + + sprintf ( p->name, filename ); + p->repeat = repeat; + p->next = m_pTrack; + + m_pTrack = p; +} + + + +//--------------------------------------------------------- +// play a list of audio files + + +void CMusic :: OpenList ( const char *filename ) +{ + + // open text file + + FILE *myfile = fopen ( filename, "r" ); + + if ( myfile == NULL ) + { + ALERT ( at_console, "MUSICPLAYER : impossible to load %s\n", filename ); + return; + } + + // saving songs to the list + + int total = 0; + + if ( fscanf ( myfile, "%i", &total ) != EOF ) + { + for ( int i=0; inext == NULL ) + break; + else + p = p->next; + } + + if ( p == NULL ) + { + ALERT ( at_console, "MUSICPLAYER : no song in the list\n" ); + return; + } + + // decrease repeat count + + p->repeat --; + + // removal of songs whose repeats ran off + + if ( p->repeat < 1 ) + { + if ( g_MusicPlayer.m_pTrack == p ) + { + delete g_MusicPlayer.m_pTrack; + g_MusicPlayer.m_pTrack = NULL; + } + else + { + audiofile_t *q = NULL; + q = g_MusicPlayer.m_pTrack; + + while ( q->next != p ) + q = q->next; + + delete q->next; + q->next = NULL; + } + } + + // close player if list is empty + + if ( g_MusicPlayer.m_pTrack == NULL ) + { + g_MusicPlayer.Reset(); + } + + // next track start + + else + { + g_MusicPlayer.Play(); + } + + return; +} + + +//--------------------------------------------------------- +// initiate playback + + +void CMusic :: Play ( void ) +{ + if ( m_IsPlaying == TRUE ) + return; + + if ( m_bInit == FALSE ) + { + Init (); + + if ( m_bInit == FALSE ) + { + ALERT ( at_console, "MUSICPLAYER : unable to initialize\n" ); + return; + } + } + + // search for the first song in the list + + audiofile_t *p = NULL; + p = m_pTrack; + + while ( p != NULL ) + { + if ( p->next == NULL ) + break; + else + p = p->next; + } + + if ( p == NULL ) + { + ALERT ( at_console, "MUSICPLAYER : no song in the list\n" ); + return; + } + + //Stop playback + m_IsPlaying = FALSE; //Pause playback. + ma_decoder_seek_to_pcm_frame(&decoder, 0); //Reset the file to start. + + // loading file + char payload [512]; + sprintf(payload, "%s", p->name); + + ALERT ( at_console, "MUSICPLAYER : Opening file %s.\n", payload ); + + result = ma_decoder_init_file(payload, NULL, &decoder); + if (result != MA_SUCCESS) { + ALERT ( at_console, "MUSICPLAYER : %s : can not load file\n", p->name ); + return; + } + + //If the new track has different properties to the previous one. + if( + deviceConfig.playback.format != decoder.outputFormat || + deviceConfig.playback.channels != decoder.outputChannels || + deviceConfig.sampleRate != decoder.outputSampleRate + ){ + deviceConfig.playback.format = decoder.outputFormat; //Change device settings + deviceConfig.playback.channels = decoder.outputChannels; + deviceConfig.sampleRate = decoder.outputSampleRate; + + ALERT ( at_console, "MUSICPLAYER : Changing format to %d, channels to %d and sample rate to %d.\n", deviceConfig.playback.format, deviceConfig.playback.channels, deviceConfig.sampleRate); + + //Now we need to recreate the device to apply. + ma_device_uninit(&device); //This is crucial, failing to do this results in segFault. + if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) { //Apply new config. + ALERT ( at_console, "MUSICPLAYER : Failed to change playback device configuration.\n" ); + g_MusicPlayer.m_bInit = FALSE; //We have been deinitialized. This is NOT ideal. + return; + }else + ALERT ( at_console, "MUSICPLAYER : New configuration applied successfully.\n"); + } + + // playback + if (ma_device_start(&device) != MA_SUCCESS) { + ALERT ( at_console, "MUSICPLAYER : Failed to start playback device.\n" ); + m_IsPlaying = FALSE; //Pause playback. + ma_decoder_seek_to_pcm_frame(&decoder, 0); //Reset the file to start. + return; + }else{ + m_IsPlaying = TRUE; + } + + return; +} + + +void CMusic :: Stop ( void ) +{ + if ( m_IsPlaying == TRUE ) + { + m_IsPlaying = FALSE; //Pause playback. + ma_decoder_seek_to_pcm_frame(&decoder, 0); //Reset the file to start. + } +} + + +void CMusic :: Reset ( void ) //Should instead be called "Next Track", but we keep Julien's naming. +{ + //Reset the player. + if ( m_bInit == TRUE ) + ALERT ( at_console, "MUSICPLAYER : Player reset.\n" ); + + Stop(); + + audiofile_t *p = NULL; + + while ( m_pTrack != NULL ) + { + p = m_pTrack; + m_pTrack = p->next; + delete p; + } +} + +void CMusic :: Terminate ( void ) //Cleanup and dereference +{ + ALERT ( at_console, "MUSICPLAYER : Terminating and unloading.\n" ); + ma_device_uninit(&device); + ma_decoder_uninit(&decoder); + g_MusicPlayer.m_bInit = FALSE; +} + + +//--------------------------------------------------------- +// entity class + + + +class CTriggerMusic : public CPointEntity +{ +public: + + void Spawn ( void ); + + void KeyValue ( KeyValueData *pkvd ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + + virtual int Save ( CSave &save ); + virtual int Restore ( CRestore &restore ); + + + static TYPEDESCRIPTION m_SaveData[]; + + + string_t m_iFileName; // file path + int m_iFileType; // text file (list) or audio file + +}; + +LINK_ENTITY_TO_CLASS( trigger_music, CTriggerMusic ); + + + +TYPEDESCRIPTION CTriggerMusic::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerMusic, m_iFileType, FIELD_INTEGER ), + DEFINE_FIELD( CTriggerMusic, m_iFileName, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CTriggerMusic, CPointEntity ); + + + +void CTriggerMusic :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + +} + +void CTriggerMusic :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "filetype")) + { + m_iFileType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "filename")) + { + m_iFileName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTriggerMusic :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( g_MusicPlayer.m_IsPlaying == TRUE ) + return; + + if ( m_iFileType == MUSIC_AUDIO_FILE ) + { + g_MusicPlayer.OpenFile ( STRING(m_iFileName), 1 ); + } + else + { + g_MusicPlayer.OpenList ( STRING(m_iFileName) ); + } + + g_MusicPlayer.Play(); +} + + + + + + + +/* +code + + +@PointClass base( Targetname ) = trigger_music : "Trigger Music" +[ + filetype(choices) : "File type" : 0 = + [ + 0: "File list (*.txt)" + 1: "File wav mp2 mp3 ogg raw" + ] + filename(string) : "Name (mod/folder/file.extension)" +] + +*/ + + +/*//--------------- +Playlist contents + +example: music01.txt file: + +// + +3 + +monmod/sound/mp3/music01_debut.mp3 1 +monmod/sound/mp3/music01_boucle.mp3 3 +monmod/sound/mp3/music01_fin.mp3 1 + + +// + +composition : + - total number of tracks + - path of the first music file + - times to repeat that file + - path of the second + - etc ... + +*///--------------- diff --git a/dlls/musicminiaudio.h b/dlls/musicminiaudio.h new file mode 100644 index 00000000..7bf49431 --- /dev/null +++ b/dlls/musicminiaudio.h @@ -0,0 +1,83 @@ +//------------------------------------------------------------- +//------------------------------------------------------------- +//- +//- musicminiaudio.h +//- +//------------------------------------------------------------- +//------------------------------------------------------------- +//- by Roy at suggestion by nekonomicon, based on code by JujU +//------------------------------------------------------------- +//- mp3 player code for HL mod +//------------------------------------------------------------- +//- +//- compatible with version 0.11.9 of Miniaudio +//- https://github.com/mackron/miniaudio +//- +//------------------------------------------------------------- + +#ifndef MUSIC_H +#define MUSIC_H + +#define MINIAUDIO_IMPLEMENTATION +#include "../miniaudio/miniaudio.h" + +//--------------------------------------------------------- +// defines + +#define MUSIC_AUDIO_FILE 1 +#define MUSIC_LIST_FILE 0 + +//--------------------------------------------------------- +// audio file structure + +struct audiofile_t +{ + char name [128]; + int repeat; + audiofile_t *next; +}; + +//--------------------------------------------------------- +// reader class + + +class CMusic +{ +public: + + // reading functions + + void OpenFile ( const char *filename, int repeat ); // open a single file + void OpenList ( const char *filename ); // opening a text file containing the files + + void Init ( void ); // initialization + + void Play ( void ); // playback + void Stop ( void ); // stop + void Reset ( void ); // pause and switch to next track + void Terminate ( void ); // clean-up during termination + + // variables + + BOOL m_IsPlaying; // monitors whether the music is played, used to pause the music + BOOL m_bInit; // checks if the player is initialized + + audiofile_t *m_pTrack; // playlist items + + // constructor / destructor + + CMusic () { m_bInit = FALSE; m_IsPlaying = FALSE; m_pTrack = NULL; Reset(); }; + ~CMusic () { Terminate(); }; + + // object instances + + ma_result result; + ma_decoder decoder; + ma_device_config deviceConfig; + ma_device device; + + void songEnd(); +}; + +extern CMusic g_MusicPlayer; +#endif // MUSIC_H diff --git a/miniaudio b/miniaudio new file mode 160000 index 00000000..4d813cfe --- /dev/null +++ b/miniaudio @@ -0,0 +1 @@ +Subproject commit 4d813cfe23c28db165cce6785419fee9d2399766