Browse Source

HL:Invasion Add MiniAudio Music implementation

invasion^2
Roy Shapiro 2 years ago
parent
commit
f080ec41b1
  1. 4
      .gitmodules
  2. 1
      CMakeLists.txt
  3. 32
      README.md
  4. 4
      dlls/CMakeLists.txt
  5. 2
      dlls/music.cpp
  6. 495
      dlls/musicminiaudio.cpp
  7. 83
      dlls/musicminiaudio.h
  8. 1
      miniaudio

4
.gitmodules vendored

@ -1,3 +1,7 @@ @@ -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

1
CMakeLists.txt

@ -49,6 +49,7 @@ option(BUILD_CLIENT "Build client dll" ON) @@ -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

32
README.md

@ -47,7 +47,7 @@ Things fixed while transferring code: @@ -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! @@ -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}```.

4
dlls/CMakeLists.txt

@ -41,6 +41,10 @@ if (USE_GSTREAMER AND (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")) @@ -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()

2
dlls/music.cpp

@ -18,6 +18,8 @@ This code is a placeholder for systems that support neither gstreamer nor fmod. @@ -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

495
dlls/musicminiaudio.cpp

@ -0,0 +1,495 @@ @@ -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; i<total; i++ )
{
char ctitle [128];
int irepeat;
// reading the title
if ( fscanf ( myfile, "%s", ctitle ) != EOF )
{
if ( fscanf ( myfile, "%i", &irepeat ) != EOF )
OpenFile ( ctitle, irepeat );
else
break;
}
else
break;
}
}
// close text file
fclose ( myfile );
}
//---------------------------------------------------------
// end of the song
void CMusic :: songEnd ( )
{
// end of the song
g_MusicPlayer.Stop ();
// search for the first song in the list
audiofile_t *p = NULL;
p = g_MusicPlayer.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;
}
// 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 ...
*///---------------

83
dlls/musicminiaudio.h

@ -0,0 +1,83 @@ @@ -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

1
miniaudio

@ -0,0 +1 @@ @@ -0,0 +1 @@
Subproject commit 4d813cfe23c28db165cce6785419fee9d2399766
Loading…
Cancel
Save