Creating VGUI Soundscape Maker


This tutorial will show you how to make a vgui soundscape maker panel that allows you to modify, create, and play soundscapes in game.
Requirements
- A mod running on Source SDK 2013
- Knowledge of C++
The main vgui panel
Before you start the tutorial you need to copy and paste both these files into your src/game/client/ directory

Once that is done you are almost all done. The next thing you are going to want to do is go over to client/vgui_int.cpp and at the top of the file and under
#include <vgui_controls/Controls.h>
add
#include "vgui_soundscape_maker.h"
Then in the same file find:
void VGui_CreateGlobalPanels( void )
{
VPANEL gameToolParent = enginevgui->GetPanel( PANEL_CLIENTDLL_TOOLS );
VPANEL toolParent = enginevgui->GetPanel( PANEL_TOOLS );
and under that add
g_SoundscapeMaker->Create(toolParent);
Now find
void VGui_Shutdown()
{
And under it add:
g_SoundscapeMaker->Destroy();
Now go to public\tier1\KeyValues.h and find:
private:
KeyValues( KeyValues& ); // prevent copy constructor being used
(should be around line 258) and make that be:
public:
KeyValues( KeyValues& ); // prevent copy constructor being used
Now go to public\vgui_controls\TextEntry.h and find:
virtual void GetTextRange( char *buf, int from, int numchars ); // copy a portion of the text to the buffer and add zero-termination
private:
CUtlVector<wchar_t> m_TextStream; // the text in the text window is stored in this buffer
(should be around line 318) and make that be:
virtual void GetTextRange( char *buf, int from, int numchars ); // copy a portion of the text to the buffer and add zero-termination
public:
CUtlVector<wchar_t> m_TextStream; // the text in the text window is stored in this buffer
Now go to client/c_soundscape.cpp and copy:
#include "c_soundscape.h"
extern bool g_IsPlayingSoundscape;
extern bool g_bSSMHack;
//debug stuff
void SoundscapePrint(Color color, const char* msg, ...);
void SoundscapeAddLine(Color color, float speed, float width, bool accending);
int SoundscapeGetLineNum();
to be under
#include "tier0/icommandline.h"
Then. find the function: void Soundscape_Update( audioparams_t &audio ) and at the very top of the function and before g_SoundscapeSystem.UpdateAudioParams( audio ); add:
if (g_IsPlayingSoundscape)
return;
now find the function: void C_SoundscapeSystem::StartNewSoundscape( KeyValues *pSoundscape ) and at the very top of it and before int i; add:
if (g_IsPlayingSoundscape && !g_bSSMHack)
return;
Now find StartSubSoundscape( pSoundscapeKeys, subParams ); inside void C_SoundscapeSystem::ProcessPlaySoundscape( KeyValues *pPlaySoundscape, subsoundscapeparams_t ¶msIn ) and under it add
if (g_IsPlayingSoundscape)
SoundscapePrint(Color(100, 255, 0, 255), "Playing Sub Soundscape: \"%s\"\n\n", pSoundscapeName);
Now find if ( soundSlot < 0 ) in the function int C_SoundscapeSystem::AddLoopingSound( const char *pSoundName, bool isAmbient, float volume, soundlevel_t soundlevel, int pitch, const Vector &position ) and above it add:
if (g_IsPlayingSoundscape)
{
//get index
int index = Clamp<int>(SoundscapeGetLineNum(), 0, 6);
//color for looping sounds
static Color LoopingSoundColors[] = {
Color(255, 100, 0, 255),
Color(255, 255, 0, 255),
Color(255, 0, 100, 255),
Color(0, 255, 0, 255),
Color(0, 255, 255, 255),
Color(255, 0, 0, 255),
Color(0, 100, 255, 255),
};
SoundscapePrint(LoopingSoundColors[index], "Fading looping sound \"%s\" in. Volume = %f, Pitch = %d\nPosition = {%.2f %.2f %.2f}\n\n", pSoundName, volume, pitch, position.x, position.y, position.z);
//sound widths
static float LoopingSoundsIn[] = {
0.4f,
0.5f,
0.6f,
0.7f,
0.8f,
0.9f,
1.0f,
};
//add to graph
SoundscapeAddLine(LoopingSoundColors[index], 3 / soundscape_fadetime.GetFloat(), LoopingSoundsIn[index], true);
}
Now find m_randomSounds[i].nextPlayTime = gameTime + RandomInterval( m_randomSounds[i].time ); inside C_SoundscapeSystem::UpdateRandomSounds( float gameTime ) and under it add:
if (g_IsPlayingSoundscape)
SoundscapePrint(Color(100, 255, 255, 255), "Playing next random sound for RandomSound index \"%d\" In \"%f\" Seconds\n\n", i, m_randomSounds[i].nextPlayTime - gameTime);
Now find m_randomSounds[index].nextPlayTime = gpGlobals->curtime + 0.5 * RandomInterval( sound.time ); inside int C_SoundscapeSystem::AddRandomSound( const randomsound_t &sound ) and under it add:
if (g_IsPlayingSoundscape)
SoundscapePrint(Color(100, 255, 255, 255), "Adding random sounds to soundscape system.\nNumber of sounds = \"%d\" For index \"%d\"\n\n", sound.waveCount, index);
Now you need to find:
if ( !pWaveName )
return;
inside void C_SoundscapeSystem::PlayRandomSound and under it add:
int pitch = (int)RandomInterval(sound.pitch);
float volume = sound.masterVolume * RandomInterval(sound.volume);
if (sound.isRandom)
{
sound.position = GenerateRandomSoundPosition();
}
if (g_IsPlayingSoundscape)
SoundscapePrint(Color(100, 255, 255, 255), "Playing Random sound \"%s\", Volume = %f, Pitch = %d\nPosition = {%.2f, %.2f, %.2f}\n", pWaveName, volume, pitch, sound.position.x, sound.position.y, sound.position.z);
Then, in that same function find:
ep.m_flVolume = sound.masterVolume * RandomInterval( sound.volume );
ep.m_SoundLevel = (soundlevel_t)(int)RandomInterval( sound.soundlevel );
ep.m_nPitch = (int)RandomInterval( sound.pitch );
if ( sound.isRandom )
{
sound.position = GenerateRandomSoundPosition();
}
ep.m_pOrigin = &sound.position;
And replace that with
ep.m_flVolume = volume;
ep.m_SoundLevel = (soundlevel_t)(int)RandomInterval( sound.soundlevel );
ep.m_nPitch = pitch;
ep.m_pOrigin = &sound.position;
Then in the same file move
struct loopingsound_t
{
Vector position; // position (if !isAmbient)
const char *pWaveName; // name of the wave file
float volumeTarget; // target volume level (fading towards this)
float volumeCurrent; // current volume level
soundlevel_t soundlevel; // sound level (if !isAmbient)
int pitch; // pitch shift
int id; // Used to fade out sounds that don't belong to the most current setting
bool isAmbient; // Ambient sounds have no spatialization - they play from everywhere
};
to be under
#ifdef _WIN32
#pragma once
from c_soundscapes.cpp into the file client/c_soundscapes.h
and finally move:
struct randomsound_t
{
Vector position;
float nextPlayTime; // time to play a sound from the set
interval_t time;
interval_t volume;
interval_t pitch;
interval_t soundlevel;
float masterVolume;
int waveCount;
bool isAmbient;
bool isRandom;
KeyValues *pWaves;
void Init()
{
memset( this, 0, sizeof(*this) );
}
};
struct subsoundscapeparams_t
{
int recurseLevel; // test for infinite loops in the script / circular refs
float masterVolume;
int startingPosition;
int positionOverride; // forces all sounds to this position
int ambientPositionOverride; // forces all ambient sounds to this position
bool allowDSP;
bool wroteSoundMixer;
bool wroteDSPVolume;
};
class C_SoundscapeSystem : public CBaseGameSystemPerFrame
{
public:
virtual char const *Name() { return "C_SoundScapeSystem"; }
C_SoundscapeSystem()
{
m_nRestoreFrame = -1;
}
~C_SoundscapeSystem() {}
void OnStopAllSounds()
{
m_params.ent.Set( NULL );
m_params.soundscapeIndex = -1;
m_loopingSounds.Purge();
m_randomSounds.Purge();
}
// IClientSystem hooks, not needed
virtual void LevelInitPreEntity()
{
Shutdown();
Init();
TouchSoundFiles();
}
virtual void LevelInitPostEntity()
{
if ( !m_pSoundMixerVar )
{
m_pSoundMixerVar = (ConVar *)cvar->FindVar( "snd_soundmixer" );
}
if ( !m_pDSPVolumeVar )
{
m_pDSPVolumeVar = (ConVar *)cvar->FindVar( "dsp_volume" );
}
}
// The level is shutdown in two parts
virtual void LevelShutdownPreEntity() {}
// Entities are deleted / released here...
virtual void LevelShutdownPostEntity()
{
OnStopAllSounds();
}
virtual void OnSave() {}
virtual void OnRestore()
{
m_nRestoreFrame = gpGlobals->framecount;
}
virtual void SafeRemoveIfDesired() {}
// Called before rendering
virtual void PreRender() { }
// Called after rendering
virtual void PostRender() { }
// IClientSystem hooks used
virtual bool Init();
virtual void Shutdown();
// Gets called each frame
virtual void Update( float frametime );
void PrintDebugInfo()
{
Msg( "\n------- CLIENT SOUNDSCAPES -------\n" );
for ( int i=0; i < m_soundscapes.Count(); i++ )
{
Msg( "- %d: %s\n", i, m_soundscapes[i]->GetName() );
}
if ( m_forcedSoundscapeIndex )
{
Msg( "- PLAYING DEBUG SOUNDSCAPE: %d [%s]\n", m_forcedSoundscapeIndex, SoundscapeNameByIndex(m_forcedSoundscapeIndex) );
}
Msg( "- CURRENT SOUNDSCAPE: %d [%s]\n", m_params.soundscapeIndex.Get(), SoundscapeNameByIndex(m_params.soundscapeIndex) );
Msg( "----------------------------------\n\n" );
}
// local functions
void UpdateAudioParams( audioparams_t &audio );
void GetAudioParams( audioparams_t &out ) const { out = m_params; }
int GetCurrentSoundscape()
{
if ( m_forcedSoundscapeIndex >= 0 )
return m_forcedSoundscapeIndex;
return m_params.soundscapeIndex;
}
void DevReportSoundscapeName( int index );
void UpdateLoopingSounds( float frametime );
int AddLoopingAmbient( const char *pSoundName, float volume, int pitch );
void UpdateLoopingSound( loopingsound_t &loopSound );
void StopLoopingSound( loopingsound_t &loopSound );
int AddLoopingSound( const char *pSoundName, bool isAmbient, float volume,
soundlevel_t soundLevel, int pitch, const Vector &position );
int AddRandomSound( const randomsound_t &sound );
void PlayRandomSound( randomsound_t &sound );
void UpdateRandomSounds( float gameClock );
Vector GenerateRandomSoundPosition();
void ForceSoundscape( const char *pSoundscapeName, float radius );
int FindSoundscapeByName( const char *pSoundscapeName );
const char *SoundscapeNameByIndex( int index );
KeyValues *SoundscapeByIndex( int index );
// main-level soundscape processing, called on new soundscape
void StartNewSoundscape( KeyValues *pSoundscape );
void StartSubSoundscape( KeyValues *pSoundscape, subsoundscapeparams_t ¶ms );
// root level soundscape keys
// add a process for each new command here
// "dsp"
void ProcessDSP( KeyValues *pDSP );
// "dsp_player"
void ProcessDSPPlayer( KeyValues *pDSPPlayer );
// "playlooping"
void ProcessPlayLooping( KeyValues *pPlayLooping, const subsoundscapeparams_t ¶ms );
// "playrandom"
void ProcessPlayRandom( KeyValues *pPlayRandom, const subsoundscapeparams_t ¶ms );
// "playsoundscape"
void ProcessPlaySoundscape( KeyValues *pPlaySoundscape, subsoundscapeparams_t ¶ms );
// "soundmixer"
void ProcessSoundMixer( KeyValues *pSoundMixer, subsoundscapeparams_t ¶ms );
// "dsp_volume"
void ProcessDSPVolume( KeyValues *pKey, subsoundscapeparams_t ¶ms );
bool IsBeingRestored() const
{
return gpGlobals->framecount == m_nRestoreFrame ? true : false;
}
void AddSoundScapeFile( const char *filename );
void TouchPlayLooping( KeyValues *pAmbient );
void TouchPlayRandom( KeyValues *pPlayRandom );
void TouchWaveFiles( KeyValues *pSoundScape );
void TouchSoundFile( char const *wavefile );
void TouchSoundFiles();
int m_nRestoreFrame;
CUtlVector< KeyValues * > m_SoundscapeScripts; // The whole script file in memory
CUtlVector<KeyValues *> m_soundscapes; // Lookup by index of each root section
audioparams_t m_params; // current player audio params
CUtlVector<loopingsound_t> m_loopingSounds; // list of currently playing sounds
CUtlVector<randomsound_t> m_randomSounds; // list of random sound commands
float m_nextRandomTime; // next time to play a random sound
int m_loopingSoundId; // marks when the sound was issued
int m_forcedSoundscapeIndex;// >= 0 if this a "forced" soundscape? i.e. debug mode?
float m_forcedSoundscapeRadius;// distance to spatialized sounds
static ConVar *m_pDSPVolumeVar;
static ConVar *m_pSoundMixerVar;
};
from c_soundscape.cpp to under the code you added in the c_soundscape.h file. before anything else you need to look for
void ProcessDSPVolume(KeyValues* pKey, subsoundscapeparams_t& params);
private:
bool IsBeingRestored() const
in the .h file you just added the class to. and if you find it then make the private be public.
Then under the class definition add:
extern C_SoundscapeSystem g_SoundscapeSystem;
Now go to shared/hl2/hl2_usermessages.cpp and inside the file find:
usermessages->Register( "AchievementEvent", -1 );
usermessages->Register( "UpdateJalopyRadar", -1 );
(should be around line 47) and under it add
usermessages->Register("SoundscapeMaker_Recieve", -1);
Now for the final part. you need to go to server/player.cpp and just above:
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBasePlayer::PostThink()
(should be around line 4590) add:
bool g_bInSoundscapePositionEditor = false;
int g_SoundscapePositionNumber = 0;
//color based on g_SoundscapePositionNumber
Color g_SoundscapeColors[] = {
Color(255, 0, 0, 50),
Color(200, 200, 0, 50),
Color(200, 0, 255, 50),
Color(0, 0, 255, 50),
Color(0, 255, 0, 50),
Color(200, 100, 0, 50),
Color(0, 0, 0, 50),
Color(0, 100, 250, 50),
};
//vector positions
Vector g_SoundscapePositions[] = {
vec3_origin,
vec3_origin,
vec3_origin,
vec3_origin,
vec3_origin,
vec3_origin,
vec3_origin,
vec3_origin,
};
bool g_bUsingVector[] = {
false,
false,
false,
false,
false,
false,
false,
false,
};
#define MAX_SOUNDSCAPES 8
//hidden commands
CON_COMMAND_F(__ss_maker_start, "", FCVAR_HIDDEN)
{
g_bInSoundscapePositionEditor = true;
g_SoundscapePositionNumber = atoi(args.Arg(1));
}
CON_COMMAND_F(__ss_maker_set, "", FCVAR_HIDDEN)
{
int index = Clamp<int>(atoi(args.Arg(1)), 0, MAX_SOUNDSCAPES - 1);
g_SoundscapePositions[index] = Vector(atof(args.Arg(2)), atof(args.Arg(3)), atof(args.Arg(4)));
g_bUsingVector[index] = atoi(args.Arg(5)) == 0 ? false : true;
}
ConVar __ss_draw("__ss_draw", "0", FCVAR_HIDDEN);
CON_COMMAND_F(__ss_maker_stop, "", FCVAR_HIDDEN)
{
g_bInSoundscapePositionEditor = false;
if (!__ss_draw.GetBool())
g_bUsingVector[g_SoundscapePositionNumber] = false;
}
Then finally at the top of the CBasePlayer::PostThink function add:
if (g_bInSoundscapePositionEditor)
{
do {
//get endpos for player's eye position + forward 150
//eye position
Vector EyePos = EyePosition();
EyePos.z = EyePos.z - 10;
//forward direction
Vector ForwardDir;
AngleVectors(EyeAngles(), &ForwardDir);
//get endpos
Vector EndPos = EyePos + (ForwardDir * 75);
int index = Clamp<int>(g_SoundscapePositionNumber, 0, MAX_SOUNDSCAPES - 1);
//draw overlay line
Color& c = g_SoundscapeColors[index];
NDebugOverlay::Line(EyePos, EndPos, c.r(), c.b(), c.g(), false, NDEBUG_PERSIST_TILL_NEXT_SERVER);
//set vector at position
g_SoundscapePositions[index] = EndPos;
g_bUsingVector[index] = true;
//check for primary attack
if (m_afButtonPressed & IN_USE)
{
g_bInSoundscapePositionEditor = false;
//write message for client
CReliableBroadcastRecipientFilter filter;
UserMessageBegin(filter, "SoundscapeMaker_Recieve");
WRITE_BYTE(g_SoundscapePositionNumber);
WRITE_VEC3COORD(g_SoundscapePositions[g_SoundscapePositionNumber]);
MessageEnd();
break;
}
if (m_afButtonPressed & IN_RELOAD)
{
g_bInSoundscapePositionEditor = false;
g_bUsingVector[index] = false;
//write that we canceled the thing
CReliableBroadcastRecipientFilter filter;
UserMessageBegin(filter, "SoundscapeMaker_Recieve");
WRITE_BYTE(255);
WRITE_VEC3COORD(vec3_invalid);
MessageEnd();
break;
}
} while (false);
}
//draw all the positions
if (g_bInSoundscapePositionEditor || __ss_draw.GetBool())
{
//now draw all the positions
for (int i = 0; i < MAX_SOUNDSCAPES; i++)
{
//check for invalid vector
if (g_bUsingVector[i] == false || vec == vec3_origin)
continue;
//get box stuff
Vector vec = g_SoundscapePositions[i];
Color& c = g_SoundscapeColors[i];
//draw all the items
NDebugOverlay::Box(vec, Vector(-10, -10, -10), Vector(10, 10, 10), c.r(), c.b(), c.g(), c.a(), NDEBUG_PERSIST_TILL_NEXT_SERVER);
//move vec up 3
vec.z = vec.z + 3;
NDebugOverlay::Text(vec, CFmtStr("Origin: %f %f %f", vec.x, vec.y, vec.z), false, NDEBUG_PERSIST_TILL_NEXT_SERVER);
//move vec down 6
vec.z = vec.z - 6;
NDebugOverlay::Text(vec, CFmtStr("Position: %d", i), false, NDEBUG_PERSIST_TILL_NEXT_SERVER);
}
}
Now the panel should be all done. to open the panel in game use the modbase_soundscape_panel command and the panel should show.
For information on how to use the vgui panel head over to: