Files
fs2open.github.com/code/network/multi_voice.cpp
T
asarium 0ff8a2c159 Add support for multiple sounds in one game sound entry
This adds support for specifying multiple sounds for a single game sound
entry. The engine then selects which sound will be played based on the
strategy provided by the game data. This required a few changes to some
of the game code since previously every game sound had exactly one sound
file attached to it. The most notable change is that it is no longer
possible to determine the exact duration a sound can have so instead the
new code computes the maximum duration a sound can have and uses that
where appropriate.

This also adds support for randomizing the pitch and volume of the sound
to provide a more varied experience even if only a single sound is used.

One possible issue that exists is that the "sequential" strategy only
works globally at the moment so if two sources use the same game sound
concurrently then the two sources will share the sequence of sounds
since there is no sound instance data at the moment. That can be added
in a future change but should not present a huge issue for most
use-cases.

Visual Studio on Appveyor somehow causes deprecation error on _our_
definition of vsprintf. I have no idea why these changes introduced this
warning but I won't debug Visual Studio compiler bugs that I can't
reproduce with my Visual Studio version so I just disabled those
warnings.
2017-10-17 10:57:22 +02:00

2134 lines
67 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "network/multi_voice.h"
#include "io/timer.h"
#include "io/key.h"
#include "gamesequence/gamesequence.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "network/multi_pmsg.h"
#include "gamesnd/gamesnd.h"
#include "sound/rtvoice.h"
#include "menuui/optionsmenumulti.h"
#include "network/multi.h"
#include "playerman/player.h"
#include "debugconsole/console.h"
// --------------------------------------------------------------------------------------------------
// MULTI VOICE DEFINES/VARS
//
// #define MULTI_VOICE_POST_DECOMPRESS // when we're _not_ using streaming
#define MULTI_VOICE_PRE_DECOMPRESS // when we _are_ using streaming
#define MULTI_VOICE_VERBOSE // keep this defined for verbose debug output
#define MULTI_VOICE_LOCAL_ECHO // keep this defined for local echo of recorded network voice
// flag indicating the status of the multi voice system
int Multi_voice_inited = 0;
int Multi_voice_can_record = 0;
int Multi_voice_can_play = 0;
int Multi_voice_send_mode = MULTI_MSG_NONE; // gotten from the multi_msg system when we start recording
// packet code defines
#define MV_CODE_GIVE_TOKEN 0 // received player side - he now has the token to speak
#define MV_CODE_DENY_TOKEN 1 // received player side - server has denied this request
#define MV_CODE_TAKE_TOKEN 2 // received player side - the server is forcibly taking his token
#define MV_CODE_RELEASE_TOKEN 3 // received server side - player is relinquishing token
#define MV_CODE_REQUEST_TOKEN 4 // received server side - player is requesting token
#define MV_CODE_PLAYER_PREFS 5 // received server side - player bitflags for who he'll receive from
#define MV_CODE_DATA 6 // sound data
#define MV_CODE_DATA_DUMMY 7 // in place of a packet which has been deemed too large, so that receivers don't time out early
// default quality of sound
#define MV_DEFAULT_QOS 10 // default quality of sound
int Multi_voice_qos; // default quality of sound
// sounds added to the front and end of a playing voice stream (set to -1 if none are wanted)
#define MULTI_VOICE_PRE_SOUND SND_CUE_VOICE
#define MULTI_VOICE_POST_SOUND SND_END_VOICE
int Multi_voice_pre_sound_size = 0;
// sound data
// NOTE : the following 2 defines should be used for reference only. they represent the worst case situation,
// sending voice to a specific target under IPX. you should use multi_voice_max_chunk_size(...) when
// determining if a given chunk will fit into an individual freespace packet
// max size of a data packet header (note, this changes as the code itself changes - should probably never use this except for reference)
#define MULTI_VOICE_MAX_HEADER_SIZE 22
// size of an individual chunk (CHUNK == block of data stuck into a packet), in the worst case of header size (see above)
#define MULTI_VOICE_MAX_CHUNK_SIZE 488
// total max size of an incoming or an outgoing uncompressed buffer (note this is probably too big, but we won't worry about that for now)
#define MULTI_VOICE_MAX_BUFFER_SIZE ((1<<16)+(1<<14)) // 80k
// overall size of an total accum buffer for a stream
#define MULTI_VOICE_ACCUM_BUFFER_SIZE (1<<14) // 16k
// how many accum buffers need to be in a total accum buffer
// NOTE : we reference MULTI_VOICE_MAX_CHUNK_SIZE here because it is worst case. ie, we'll always have enough
// accum buffers in anything better than the worst case if we use MULTI_VOICE_MAX_CHUNK_SIZE
#define MULTI_VOICE_ACCUM_BUFFER_COUNT (MULTI_VOICE_ACCUM_BUFFER_SIZE / MULTI_VOICE_MAX_CHUNK_SIZE)
int Multi_voice_max_time; // current maximum recording time
char *Multi_voice_record_buffer = NULL; // buffer for recording back voice
char *Multi_voice_playback_buffer = NULL; // buffer for processing the accum buffer and playing the result
// DEBUG CODE
#ifdef MULTI_VOICE_POST_DECOMPRESS
char Multi_voice_unpack_buffer[MULTI_VOICE_MAX_BUFFER_SIZE];
#endif
// the max amount of tokens we want to be floating about (max sound streams)
#define MULTI_VOICE_MAX_STREAMS 1
// voice algorithm stuff
// it would probably be good to base the timeout time on some multiple of our average ping to the server
#define MV_ALG_TIMEOUT 500 // if start get new data for a window then a pause this long, play the window
int Multi_voice_stamps[MULTI_VOICE_MAX_STREAMS];
// NOTE : this should be > then MULTI_VOICE_MAX_TIME + the time for the data to come over a network connection!!
#define MULTI_VOICE_TOKEN_TIMEOUT 7000 // timeout - server will take the token back if he does not hear from the guy in this amount of time
#define MULTI_VOICE_TOKEN_RELEASE_WAIT (1.0f) // wait 1 second
// the token index of a voice stream is set to one of these values, or the index of the player who has the token
#define MULTI_VOICE_TOKEN_INDEX_FREE -1 // the token (and the stream are free)
#define MULTI_VOICE_TOKEN_INDEX_RELEASED 0xBEAD // the token has been released but the stream is still active
typedef struct voice_stream {
int token_status; // status of the token (player index if a player has it) or one of the above defines
int token_stamp; // timestamp for the MULTI_VOICE_TOKEN_TIMEOUT
short stream_from; // id of the player the stream is coming from
ubyte *accum_buffer[MULTI_VOICE_ACCUM_BUFFER_COUNT]; // accum buffer
ubyte accum_buffer_flags[MULTI_VOICE_ACCUM_BUFFER_COUNT]; // flag indicating the existence of a given accum (sub)buffer
ushort accum_buffer_usize[MULTI_VOICE_ACCUM_BUFFER_COUNT]; // uncompressed size of the corresponding (sub)buffer
ushort accum_buffer_csize[MULTI_VOICE_ACCUM_BUFFER_COUNT]; // compressed size of the corresponding (sub)buffer
double accum_buffer_gain[MULTI_VOICE_ACCUM_BUFFER_COUNT]; // gain of the corresponding (sub)buffer
ubyte stream_id; // stream id #
fix stream_last_heard; // last time we heard from this stream
fix stream_start_time; // time the stream started playing
int stream_snd_handle; // sound playing instance handle
int stream_rtvoice_handle; // rtvoice buffer handle
} voice_stream;
voice_stream Multi_voice_stream[MULTI_VOICE_MAX_STREAMS]; // voice streams themselves
// player-side data
#define MULTI_VOICE_KEY KEY_LAPOSTRO // key used for realtime voice
int Multi_voice_keydown = 0; // is the record key currently being pressed
int Multi_voice_recording = 0; // flag indicating if we're currently recording or not
int Multi_voice_token = 0; // if we currently have a token or not
int Multi_voice_recording_stamp = -1; // how long we've been recording
ubyte Multi_voice_stream_id = 0; // stream id for the stream we're currently sending
int Multi_voice_current_stream_index = 0; // packet index of the currently recodring stream
int Multi_voice_current_stream_sent = -1; // index of packet we've sent up to
// server-side data
ubyte Multi_voice_next_stream_id = 0; // kept on the server - given to the next valid token requester
int Multi_voice_player_prefs[MAX_PLAYERS]; // player bitflag preferences
// voice status data - used for determing the result of multi_voice_status
#define MULTI_VOICE_DENIED_TIME 1000 // how long to display the "denied" status
int Multi_voice_denied_stamp = -1; // timestamp for when we got denied a token
// local muting preferences
int Multi_voice_local_prefs = 0xffffffff;
// --------------------------------------------------------------------------------------------------
// MULTI VOICE FORWARD DECLARATIONS
//
// process voice details as the server
void multi_voice_server_process();
// process voice details as a player (may also be the server)
void multi_voice_player_process();
// determine if the voice key is down this frame
int multi_voice_keydown();
// find the voice stream index by token player index
int multi_voice_find_token(int player_index);
// <server> gives the token to a given player
void multi_voice_give_token(int stream_index,int player_index);
// <server> takes the token from a given stream entry
void multi_voice_take_token(int stream_index);
// <server> tells the client he's been denied on this request
void multi_voice_deny_token(int player_index);
// <player> releases the token back to the server
void multi_voice_release_token();
// <player> requests the token from the server
void multi_voice_request_token();
// <server> process a request for the token
void multi_voice_process_token_request(int player_index);
// free up any memory which may have been malloced
void multi_voice_free_all();
// <player> send the currently recorded sound
void multi_voice_player_send_stream();
// process incoming sound data, return bytes processed
int multi_voice_process_data(ubyte *data, int player_index,int msg_mode,net_player *target);
// <server> increment the current stream id#
void multi_voice_inc_stream_id();
// flush any old sound stream data because we've started to receive data for a new stream
void multi_voice_flush_old_stream(int stream_index);
// route sound data through the server to all appropriate players
void multi_voice_route_data(ubyte *data, int packet_size,int player_index,int mode,net_player *target);
// find the stream to apply incoming sound data to, freeing up old ones as necessary
int multi_voice_get_stream(int stream_id);
// NOTE : these 4 functions can be arbitrarily written to perform in any way necessary. This way the algorithm is
// completely seperate from the transport and token layers
// initialize the smart algorithm
void multi_voice_alg_init();
// process incoming sound data in whatever way necessary (this function should take care of playing data when necessary)
void multi_voice_alg_process_data(int stream_index);
// process existing streams
void multi_voice_alg_process_streams();
// we are going to flush the current stream because we have started to receive data for a new one. do something first
void multi_voice_alg_flush_old_stream(int stream_index);
// is the given sound stream playing (compares uncompressed sound size with current playback position)
int multi_voice_stream_playing(int stream_index);
// tack on a post voice sound (pass -1 for none)
// return final buffer size
int multi_voice_mix(int post_sound,char *data,int cur_size,int max_size);
// send a dummy packet in the place of a too-large data packet
void multi_voice_send_dummy_packet();
// process a dummy data packet
int multi_voice_process_data_dummy(ubyte *data);
// max size of a sound chunk which we can fit into a packet
int multi_voice_max_chunk_size(int msg_mode);
// process a player preferences packet, return bytes processed
int multi_voice_process_player_prefs(ubyte *data,int player_index);
// process and play the current window of sound stream data we have. reset the window for the next incoming data as well
void multi_voice_alg_play_window(int stream_index);
// send all pending voice packets
void multi_voice_client_send_pending();
// --------------------------------------------------------------------------------------------------
// MULTI VOICE FUNCTIONS
//
// initialize the multiplayer voice system
void multi_voice_init()
{
int idx,s_idx,pre_size,pre_sound;
// if the voice system is already initialized, just reset some stuff
if(Multi_voice_inited){
multi_voice_reset();
return;
}
// set the default quality of sound
Multi_voice_qos = MV_DEFAULT_QOS;
// if we're the standalone server, we can't record _or_ playback, but we can still route data and manage tokens
if(Game_mode & GM_STANDALONE_SERVER){
Multi_voice_can_record = 0;
Multi_voice_can_play = 0;
} else {
// initialize the realtime voice module
if(rtvoice_init_recording(Multi_voice_qos)){
nprintf(("Network","MULTI VOICE : Error initializing rtvoice - recording will not be possible\n"));
Multi_voice_can_record = 0;
} else {
Multi_voice_can_record = 1;
}
if(rtvoice_init_playback()){
nprintf(("Network","MULTI VOICE : Error initializing rtvoice - playback will not be possible\n"));
Multi_voice_can_play = 0;
} else {
Multi_voice_can_play = 1;
}
// _always_ set the quality of server
multi_voice_set_vars(MV_DEFAULT_QOS,MULTI_VOICE_MAX_TIME);
}
// initialize player-side data
Multi_voice_token = 0;
Multi_voice_keydown = 0;
Multi_voice_recording = 0;
Multi_voice_stream_id = 0;
Multi_voice_recording_stamp = -1;
Multi_voice_current_stream_index = 0;
Multi_voice_current_stream_sent = -1;
// initialize server-side data
memset(Multi_voice_player_prefs,0xff,sizeof(int)*MAX_PLAYERS);
Multi_voice_next_stream_id = 0;
Multi_voice_local_prefs = 0xffffffff;
// initialize the sound buffers
Multi_voice_record_buffer = NULL;
Multi_voice_playback_buffer = NULL;
Multi_voice_pre_sound_size = 0;
if(Multi_voice_can_play){
// attempt to allocate the buffer
Multi_voice_playback_buffer = (char*)vm_malloc(MULTI_VOICE_MAX_BUFFER_SIZE);
if(Multi_voice_playback_buffer == NULL){
nprintf(("Network","MULTI VOICE : Error allocating playback buffer - playback will not be possible\n"));
Multi_voice_can_play = 0;
}
// attempt to copy in the "pre" voice sound
auto gs = gamesnd_get_game_sound(MULTI_VOICE_PRE_SOUND);
pre_sound = snd_load(gamesnd_choose_entry(gs), gs->flags, 0);
if(pre_sound != -1){
// get the pre-sound size
if((snd_size(pre_sound,&pre_size) != -1) && (pre_size < MULTI_VOICE_MAX_BUFFER_SIZE)){
snd_get_data(pre_sound,Multi_voice_playback_buffer);
Multi_voice_pre_sound_size = pre_size;
} else {
Multi_voice_pre_sound_size = 0;
}
} else {
Multi_voice_pre_sound_size = 0;
}
}
// initialize the streams
memset(Multi_voice_stream,0,sizeof(voice_stream) * MULTI_VOICE_MAX_STREAMS);
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
Multi_voice_stream[idx].token_status = MULTI_VOICE_TOKEN_INDEX_FREE;
Multi_voice_stream[idx].token_stamp = -1;
Multi_voice_stream[idx].stream_snd_handle = -1;
// get a playback buffer handle
if(Multi_voice_can_play){
Multi_voice_stream[idx].stream_rtvoice_handle = -1;
Multi_voice_stream[idx].stream_rtvoice_handle = rtvoice_create_playback_buffer();
if(Multi_voice_stream[idx].stream_rtvoice_handle == -1){
nprintf(("Network","MULTI VOICE : Error getting rtvoice buffer handle - playback will not be possible!\n"));
multi_voice_free_all();
Multi_voice_can_play = 0;
}
// allocate the accum buffer
for(s_idx=0;s_idx<MULTI_VOICE_ACCUM_BUFFER_COUNT;s_idx++){
Multi_voice_stream[idx].accum_buffer[s_idx] = NULL;
Multi_voice_stream[idx].accum_buffer[s_idx] = (ubyte*)vm_malloc(MULTI_VOICE_ACCUM_BUFFER_SIZE);
if(Multi_voice_stream[idx].accum_buffer[s_idx] == NULL){
nprintf(("Network","MULTI VOICE : Error allocating accum buffer - playback will not be possible\n"));
multi_voice_free_all();
Multi_voice_can_play = 0;
}
}
}
}
// initialize the default max time
Multi_voice_max_time = MULTI_VOICE_MAX_TIME;
// initialize voice status data
Multi_voice_denied_stamp = -1;
// initialize the smart algorithm
multi_voice_alg_init();
Multi_voice_inited = 1;
}
// shutdown the multiplayer voice system
void multi_voice_close()
{
int idx;
// if the voice system isn't already initialized, don't do anything
if(!Multi_voice_inited){
return;
}
// free up buffers
multi_voice_free_all();
// release all the rtvoice buffers
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
if(Multi_voice_stream[idx].stream_rtvoice_handle != -1){
rtvoice_free_playback_buffer(Multi_voice_stream[idx].stream_rtvoice_handle);
Multi_voice_stream[idx].stream_rtvoice_handle = -1;
Multi_voice_stream[idx].stream_snd_handle = -1;
}
}
// close the realtime voice module
rtvoice_close_recording();
rtvoice_close_playback();
Multi_voice_inited = 0;
}
// reset between levels
void multi_voice_reset()
{
int idx;
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : Resetting\n"));
#endif
Assert(Multi_voice_inited);
// if we're the standalone server, we can't record _or_ playback, but we can still route data and manage tokens
if(Game_mode & GM_STANDALONE_SERVER){
Multi_voice_can_record = 0;
Multi_voice_can_play = 0;
}
// initialize player-side data
Multi_voice_token = 0;
Multi_voice_keydown = 0;
Multi_voice_recording = 0;
Multi_voice_stream_id = 0;
Multi_voice_recording_stamp = -1;
// initialize server-side data
memset(Multi_voice_player_prefs,0xff,sizeof(int)*MAX_PLAYERS);
Multi_voice_local_prefs = 0xffffffff;
Multi_voice_next_stream_id = 0;
// initialize the sound buffers
Multi_voice_record_buffer = NULL;
// initialize the streams
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
Multi_voice_stream[idx].token_status = MULTI_VOICE_TOKEN_INDEX_FREE;
Multi_voice_stream[idx].token_stamp = -1;
}
// initialize the smart algorithm
multi_voice_alg_init();
}
// process all voice details
void multi_voice_process()
{
int idx;
// don't do anything if the voice module is not initialized
if((!Multi_voice_inited) || !(Net_player->flags & NETINFO_FLAG_CONNECTED)){
return;
}
// send all pending voice packets
multi_voice_client_send_pending();
// find any playing sound streams which have finished and unmark them
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
if((Multi_voice_stream[idx].stream_snd_handle != -1) && !multi_voice_stream_playing(idx)){
Multi_voice_stream[idx].stream_snd_handle = -1;
}
}
// process seperately as player or server
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_voice_server_process();
}
// all "players" do this, except the standalone who isn't a real player by definition
if(!(Game_mode & GM_STANDALONE_SERVER)){
multi_voice_player_process();
}
// everyont calls the general algorithm process function
multi_voice_alg_process_streams();
}
// voice settings debug console function
void multi_voice_dcf()
{
SCP_string arg;
int value;
dc_stuff_string_white(arg);
// set the quality of sound
if (arg == NOX("qos")) {
dc_stuff_int(&value);
if((value >= 1) && (value <= 10) && (Net_player->flags & NETINFO_FLAG_AM_MASTER)){
multi_voice_set_vars(value,-1);
dc_printf("Quality of sound : %d\n", value);
}
}
}
// the status of the voice system - use this to determine what bitmaps to display, etc see above MULTI_VOICE_STATUS_* defines
int multi_voice_status()
{
int idx;
int earliest;
fix earliest_time;
// if the "denied" timestamp is set, return that as the status
if(Multi_voice_denied_stamp != -1){
return MULTI_VOICE_STATUS_DENIED;
}
// if we're currently recording (has precedence over playing back a sound from somebody)
if(Multi_voice_recording){
return MULTI_VOICE_STATUS_RECORDING;
}
// find the stream which started playing the farthest back (if any)
earliest = -1;
earliest_time = -1;
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
// if we found a playing stream
if(Multi_voice_stream[idx].stream_snd_handle != -1){
if((earliest == -1) || (Multi_voice_stream[idx].stream_start_time < earliest_time)){
earliest = idx;
earliest_time = Multi_voice_stream[idx].stream_start_time;
}
}
}
// if we found a stream
if(earliest != -1){
return MULTI_VOICE_STATUS_PLAYING;
}
// system is idle
return MULTI_VOICE_STATUS_IDLE;
}
// update the qos if the current setting is different from the passed in value
void multi_voice_maybe_update_vars(int new_qos,int new_duration)
{
// if the current qos is different from the passed qos, set it
if((new_qos != Multi_voice_qos) || (new_duration != Multi_voice_max_time)){
multi_voice_set_vars(new_qos,new_duration);
}
}
// --------------------------------------------------------------------------------------------------
// MULTI VOICE FORWARD DECLARATIONS
//
// process voice details as the server
void multi_voice_server_process()
{
int idx;
// process all the tokens for all the available streams
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
switch(Multi_voice_stream[idx].token_status){
// if the token is free, so is the stream - don't do anything
case MULTI_VOICE_TOKEN_INDEX_FREE:
break;
// if the token has been released - check to see if the stream is "done" (ie, can be marked as FREE once again)
case MULTI_VOICE_TOKEN_INDEX_RELEASED:
// if the stream_last_heard var is -1, it means we never got sound from this guy so free the token up immediately
if(Multi_voice_stream[idx].stream_last_heard == -1){
Multi_voice_stream[idx].token_status = MULTI_VOICE_TOKEN_INDEX_FREE;
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : freeing released token (no packets)\n"));
#endif
}
// if a sufficiently long amount of time has elapsed since he released the token, free it up
else {
float t1,t2;
t1 = f2fl(Multi_voice_stream[idx].stream_last_heard);
t2 = f2fl(timer_get_fixed_seconds());
if((t2 - t1) >= MULTI_VOICE_TOKEN_RELEASE_WAIT){
Multi_voice_stream[idx].token_status = MULTI_VOICE_TOKEN_INDEX_FREE;
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : freeing released token (time elapsed)\n"));
#endif
}
}
break;
// if the token is still being held by a player
default :
// if the token timestamp has elapsed, take the token back
if((Multi_voice_stream[idx].token_stamp != -1) && timestamp_elapsed(Multi_voice_stream[idx].token_stamp)){
Assert(Multi_voice_stream[idx].token_status != MULTI_VOICE_TOKEN_INDEX_FREE);
multi_voice_take_token(idx);
}
break;
}
}
// for each netplayer, if his token wait timestamp is running, see if it has popped yet
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (Net_players[idx].s_info.voice_token_timestamp != -1) && timestamp_elapsed(Net_players[idx].s_info.voice_token_timestamp)){
// unset it so that he can have the token again
Net_players[idx].s_info.voice_token_timestamp = -1;
}
}
}
// process voice details as a player (may also be the server)
void multi_voice_player_process()
{
// if the voice key is down for the first time this frame, send a request for the token
if(!Multi_voice_keydown && multi_voice_keydown() && Multi_voice_can_record && !(Netgame.options.flags & MSO_FLAG_NO_VOICE)){
// mark the key as being down
Multi_voice_keydown = 1;
// send a request for a token
multi_voice_request_token();
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : Request\n"));
#endif
}
// if the key is still being pressed
if(Multi_voice_keydown && multi_voice_keydown() && Multi_voice_can_record){
// if we have the token
if(Multi_voice_token){
// if we're not already recording, start recording
if(!Multi_voice_recording){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : RECORD %d\n",(int)Multi_voice_stream_id));
#endif
// flush the old stream
multi_voice_flush_old_stream(0);
// start the recording process with the appropriate callback function
if(rtvoice_start_recording(multi_voice_process_next_chunk, 175)){
nprintf(("Network","MULTI VOICE : Error initializing recording!\n"));
return;
}
// set myself to be recording
Multi_voice_recording = 1;
// set the time when I started recording
Multi_voice_recording_stamp = timestamp(Multi_voice_max_time);
// set the current packet/chunk index to 0
Multi_voice_current_stream_index = 0;
Multi_voice_current_stream_sent = 0;
// get the proper messaging mode
if(Game_mode & GM_IN_MISSION){
// in mission, paused
if(gameseq_get_state() == GS_STATE_MULTI_PAUSED){
Multi_voice_send_mode = MULTI_MSG_ALL;
}
// in mission, unpaused
else {
Multi_voice_send_mode = multi_msg_mode();
}
} else {
Multi_voice_send_mode = MULTI_MSG_ALL;
}
}
// if we've recorded the max time allowed, send the data
if((Multi_voice_recording_stamp != -1) && timestamp_elapsed(Multi_voice_recording_stamp)){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : timestamp popped"));
#endif
// mark me as no longer recording
Multi_voice_recording = 0;
Multi_voice_current_stream_sent = -1;
// stop the recording process
rtvoice_stop_recording();
#ifdef MULTI_VOICE_POST_DECOMPRESS
multi_voice_player_send_stream();
#endif
// play my sound locally as well
#ifdef MULTI_VOICE_LOCAL_ECHO
multi_voice_alg_play_window(0);
#endif
// release the token back to the server
multi_voice_release_token();
}
}
}
// if the key has been released
else if(Multi_voice_keydown && !multi_voice_keydown() && Multi_voice_can_record){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : Release\n"));
#endif
// mark the kay as not being down
Multi_voice_keydown = 0;
// if we were recording, send the data
if(Multi_voice_recording){
// mark me as no longer recording
Multi_voice_recording = 0;
Multi_voice_current_stream_sent = -1;
// stop the recording process
rtvoice_stop_recording();
#ifdef MULTI_VOICE_POST_DECOMPRESS
multi_voice_player_send_stream();
#endif
// play my sound locally as well
#ifdef MULTI_VOICE_LOCAL_ECHO
multi_voice_alg_play_window(0);
#endif
// release the token back to the server
multi_voice_release_token();
}
}
// if the "denied" timestamp is set, but has elapsed or the user has let up on the key, set it to -1
if((Multi_voice_denied_stamp != -1) && (timestamp_elapsed(Multi_voice_denied_stamp) || !multi_voice_keydown())){
Multi_voice_denied_stamp = -1;
}
}
// determine if the voice key is down this frame
int multi_voice_keydown()
{
// if we're in the options screen, we should never allow the button to be pressed
if(gameseq_get_state() == GS_STATE_OPTIONS_MENU){
return 0;
}
// if we're pre-game, we should just be checking the keyboard bitflags
if(!(Game_mode & GM_IN_MISSION)){
return (keyd_pressed[MULTI_VOICE_KEY] && !(keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT])) ? 1 : 0;
}
// in-mission, paused - treat just like any other "chattable" screen.
if(gameseq_get_state() == GS_STATE_MULTI_PAUSED){
return (keyd_pressed[MULTI_VOICE_KEY] && !(keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT])) ? 1 : 0;
}
// ingame, unpaused, rely on the multi-messaging system (ingame)
return multi_msg_voice_record();
}
// find the voice stream index by token player index
int multi_voice_find_token(int player_index)
{
int idx;
// look through all the existing streams
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
if(Multi_voice_stream[idx].token_status == player_index){
return idx;
}
}
// couldn't find it
return -1;
}
// <server> gives the token to a given player
void multi_voice_give_token(int stream_index,int player_index)
{
ubyte data[10],code;
int packet_size = 0;
// only the server should ever be here
Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER);
// set this player as having the token
Multi_voice_stream[stream_index].token_status = player_index;
// set the token timeout
Multi_voice_stream[stream_index].token_stamp = timestamp(MULTI_VOICE_TOKEN_TIMEOUT);
// set the stream id and increment the count
Multi_voice_stream[stream_index].stream_id = Multi_voice_next_stream_id;
multi_voice_inc_stream_id();
// set the last heard from time to -1 to indicate we've heard no sound from this guy
Multi_voice_stream[stream_index].stream_last_heard = -1;
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : GIVE TOKEN %d\n",(int)Multi_voice_next_stream_id));
#endif
// if we're giving to ourself, don't send any data
if(Net_player == &Net_players[player_index]){
Multi_voice_token = 1;
Multi_voice_stream_id = Multi_voice_stream[stream_index].stream_id;
} else {
// send the "give" packet to the guy
BUILD_HEADER(VOICE_PACKET);
code = MV_CODE_GIVE_TOKEN;
ADD_DATA(code);
// add the current stream id#
ADD_DATA(Multi_voice_stream[stream_index].stream_id);
// send reliably
multi_io_send_reliable(&Net_players[player_index], data, packet_size);
}
}
// <server> takes the token from a given player
void multi_voice_take_token(int stream_index)
{
ubyte data[10],code;
int packet_size = 0;
// only the server should ever be here
Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER);
// if the index is -1, the token has probably been released to us "officially" already
if((Multi_voice_stream[stream_index].token_status == (int)MULTI_VOICE_TOKEN_INDEX_FREE) || (Multi_voice_stream[stream_index].token_status == (int)MULTI_VOICE_TOKEN_INDEX_RELEASED)){
Multi_voice_stream[stream_index].token_stamp = -1;
return;
}
// if i'm taking from myself, don't send any data
if(Net_player == &Net_players[Multi_voice_stream[stream_index].token_status]){
Multi_voice_token = 0;
// timestamp this guy so that he can't get the token back immediately
Net_player->s_info.voice_token_timestamp = timestamp(Netgame.options.voice_token_wait);
} else {
// send the "take" packet to the guy
BUILD_HEADER(VOICE_PACKET);
code = MV_CODE_TAKE_TOKEN;
ADD_DATA(code);
// send reliably
multi_io_send_reliable(&Net_players[Multi_voice_stream[stream_index].token_status], data, packet_size);
// timestamp this guy so that he can't get the token back immediately
Net_players[Multi_voice_stream[stream_index].token_status].s_info.voice_token_timestamp = timestamp(Netgame.options.voice_token_wait);
}
// take the token back from the dude
Multi_voice_stream[stream_index].token_status = MULTI_VOICE_TOKEN_INDEX_RELEASED;
Multi_voice_stream[stream_index].token_stamp = -1;
}
// <server> tells the client he's been denied on this request
void multi_voice_deny_token(int player_index)
{
ubyte data[10],code;
int packet_size = 0;
// only the server should ever be here
Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER);
// if i'm denying myself, set the denied timestamp
if(Net_player == &Net_players[player_index]){
Multi_voice_denied_stamp = timestamp(MULTI_VOICE_DENIED_TIME);
} else {
// send the "deny" packet to the guy
BUILD_HEADER(VOICE_PACKET);
code = MV_CODE_DENY_TOKEN;
ADD_DATA(code);
// send reliably
multi_io_send_reliable(&Net_players[player_index], data, packet_size);
}
}
// <player> releases the token back to the server
void multi_voice_release_token()
{
ubyte data[10],code;
int packet_size = 0;
// I don't have the token anymore
Multi_voice_token = 0;
// if i'm the server, don't send any data
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
// mark the token as being released
int stream_index = multi_voice_find_token(MY_NET_PLAYER_NUM);
if (stream_index != -1)
Multi_voice_stream[stream_index].token_status = MULTI_VOICE_TOKEN_INDEX_RELEASED;
// timestamp this guy so that he can't get the token back immediately
Net_player->s_info.voice_token_timestamp = timestamp(Netgame.options.voice_token_wait);
} else {
// send the "release" packet to the server
BUILD_HEADER(VOICE_PACKET);
code = MV_CODE_RELEASE_TOKEN;
ADD_DATA(code);
// send reliably
multi_io_send_reliable(Net_player, data, packet_size);
}
}
// <player> requests the token from the server
void multi_voice_request_token()
{
ubyte data[10],code;
int packet_size = 0;
// if i'm the server, process the request right now
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_voice_process_token_request(MY_NET_PLAYER_NUM);
} else {
// send the "request" packet to the server
BUILD_HEADER(VOICE_PACKET);
code = MV_CODE_REQUEST_TOKEN;
ADD_DATA(code);
// send reliably
multi_io_send_reliable(Net_player, data, packet_size);
}
}
// <player> sends hit bitflag settings (who he'll receive sound from, etc)
void multi_voice_set_prefs(int pref_flags)
{
ubyte data[MAX_PACKET_SIZE],code;
int idx;
int packet_size = 0;
// set the local flags
Multi_voice_local_prefs = pref_flags;
// if i'm the server, set the sound prefs right now
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
Multi_voice_player_prefs[MY_NET_PLAYER_NUM] = pref_flags;
} else {
// send the prefs to the server
BUILD_HEADER(VOICE_PACKET);
code = MV_CODE_PLAYER_PREFS;
ADD_DATA(code);
// add the address of all players being ignored
for(idx=0;idx<MAX_PLAYERS;idx++){
if(!(pref_flags & (1<<idx))){
code = 0x0;
ADD_DATA(code);
// add the player's id
ADD_SHORT(Net_players[idx].player_id);
}
}
// add final stop byte
code = 0xff;
ADD_DATA(code);
// send reliably
multi_io_send_reliable(Net_player, data, packet_size);
}
}
// set the default voice quality and duration (if server passes -1, he just broadcasts the qos to all clients)
void multi_voice_set_vars(int qos,int duration)
{
int need_update = 0;
// make sure its in the right range
if((qos > 0) && (qos <= 10)){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : SETTING QOS %d\n",qos));
#endif
// set the default value
Multi_voice_qos = qos;
// set the value in the rtvoice module
rtvoice_set_qos(Multi_voice_qos);
// update the netgame settings
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
Netgame.options.voice_qos = (ubyte)Multi_voice_qos;
need_update = 1;
}
}
// set the maximum duration
if((duration > 0) && (duration <= MULTI_VOICE_MAX_TIME)){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : SETTING MAX RECORD TIME %d\n",duration));
#endif
// set the default value
Multi_voice_max_time = duration;
// update the netgame settings
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
Netgame.options.voice_record_time = duration;
need_update = 1;
}
}
// send an options update if necessary
if(need_update && !(Game_mode & GM_STANDALONE_SERVER)){
multi_options_update_netgame();
}
}
// <server> process a request for the token
void multi_voice_process_token_request(int player_index)
{
int idx;
// if we're not doing voice on this server, return now
if(Netgame.options.flags & MSO_FLAG_NO_VOICE){
return;
}
// if the player's token timestamp is not -1, can't give him the token
if(Net_players[player_index].s_info.voice_token_timestamp != -1){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : Not giving token because player %s's timestamp hasn't elapsed yet!\n",Net_players[player_index].m_player->callsign));
nprintf(("Network","MULTI VOICE : token status %d\n",Multi_voice_stream[0].token_status));
#endif
// deny the guy
multi_voice_deny_token(player_index);
return;
}
// attempt to find a free token token
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
if(Multi_voice_stream[idx].token_status == MULTI_VOICE_TOKEN_INDEX_FREE){
multi_voice_give_token(idx,player_index);
return;
}
}
}
// free up any memory which may have been malloced
void multi_voice_free_all()
{
int idx,s_idx;
// free up the playback buffer
if(Multi_voice_playback_buffer != NULL){
vm_free(Multi_voice_playback_buffer);
Multi_voice_playback_buffer = NULL;
}
// free up the accum buffers
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
for(s_idx=0;s_idx<MULTI_VOICE_ACCUM_BUFFER_COUNT;s_idx++){
if(Multi_voice_stream[idx].accum_buffer[s_idx] != NULL){
vm_free(Multi_voice_stream[idx].accum_buffer[s_idx]);
Multi_voice_stream[idx].accum_buffer[s_idx] = NULL;
}
}
}
}
// <player> send the currently recorded sound
void multi_voice_player_send_stream()
{
ubyte data[MAX_PACKET_SIZE],code,*rbuf,msg_mode,chunk_index;
ushort chunk_size,uc_size;
int packet_size = 0;
int sound_size,size_sent,target_index,max_chunk_size;
float gain;
double d_gain;
// we'd better not ever get here as we can't record voice
Assert(Multi_voice_can_record);
// get the data
rtvoice_get_data((unsigned char**)&Multi_voice_record_buffer, &sound_size, &d_gain);
gain = (float)d_gain;
msg_mode = (ubyte)Multi_voice_send_mode;
// get the specific target if we're in MSG_TARGET mode
target_index = -1;
if(msg_mode == MULTI_MSG_TARGET){
if(Player_ai->target_objnum != -1){
target_index = multi_find_player_by_object(&Objects[Player_ai->target_objnum]);
if(target_index == -1){
return;
}
} else {
return;
}
}
// get the max chunk size
max_chunk_size = multi_voice_max_chunk_size(Multi_voice_send_mode);
// go through the data and send all of it
code = MV_CODE_DATA;
chunk_index = 0;
size_sent = 0;
rbuf = (unsigned char*)Multi_voice_record_buffer;
while(size_sent < sound_size){
// build the header and add the opcode
BUILD_HEADER(VOICE_PACKET);
// add the packet code type
ADD_DATA(code);
// add the routing data and any necessary targeting information
ADD_DATA(msg_mode);
if(msg_mode == MULTI_MSG_TARGET){
ADD_DATA(Objects[Net_players[target_index].m_player->objnum].net_signature);
}
// add my id#
ADD_SHORT(Net_player->player_id);
// add the current stream id#
ADD_DATA(Multi_voice_stream_id);
Assert(sound_size < MULTI_VOICE_MAX_BUFFER_SIZE);
uc_size = (ushort)sound_size;
ADD_USHORT(uc_size);
// add the chunk index
ADD_DATA(chunk_index);
// determine how much we are going to send in this packet
if((sound_size - size_sent) >= max_chunk_size){
chunk_size = (ushort)max_chunk_size;
} else {
chunk_size = (ushort)(sound_size - size_sent);
}
ADD_USHORT(chunk_size);
// add the gain
ADD_FLOAT(gain);
// add the chunk of data
memcpy(data+packet_size, rbuf,chunk_size);
packet_size += chunk_size;
// send to the server or rebroadcast if I _am_ the server
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_voice_route_data(data,packet_size,MY_NET_PLAYER_NUM,(int)msg_mode,(target_index == -1) ? NULL : &Net_players[target_index]);
} else {
multi_io_send(Net_player, data, packet_size);
}
// increment the chunk_index
chunk_index++;
// increment bytes sent and the buffer
size_sent += (int)chunk_size;
rbuf += chunk_size;
}
}
// process incoming sound data, return bytes processed
int multi_voice_process_data(ubyte *data, int player_index,int msg_mode,net_player *target)
{
ubyte stream_id,chunk_index;
ushort chunk_size,uc_size;
short who_from;
int stream_index;
float gain;
int offset = 0;
// read in all packet data except for the sound chunk itself
GET_SHORT(who_from);
GET_DATA(stream_id);
GET_USHORT(uc_size);
GET_DATA(chunk_index);
GET_USHORT(chunk_size);
GET_FLOAT(gain);
// if our netgame options are currently set for no voice, ignore the packet
if((Netgame.options.flags & MSO_FLAG_NO_VOICE) || !Multi_options_g.std_voice){
offset += chunk_size;
return offset;
}
// get a handle to a valid stream to be using, freeing old streams as necessary
stream_index = multi_voice_get_stream((int)stream_id);
// if this index is too high, flush the stream
if(chunk_index >= MULTI_VOICE_ACCUM_BUFFER_COUNT){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : flushing stream because packet index is too high!!\n"));
#endif
// flush the stream
multi_voice_flush_old_stream(stream_index);
// return bytes processed
offset += chunk_size;
return offset;
}
// if we found a stream to work with
if(stream_index != -1){
// set the id of where it came from
Multi_voice_stream[stream_index].stream_from = who_from;
// set the stream id#
Multi_voice_stream[stream_index].stream_id = stream_id;
// set the gain
Multi_voice_stream[stream_index].accum_buffer_gain[chunk_index] = (double)gain;
// set the stream uncompressed size size
Multi_voice_stream[stream_index].accum_buffer_usize[chunk_index] = uc_size;
// set the token timestamp
Multi_voice_stream[stream_index].token_stamp = timestamp(MULTI_VOICE_TOKEN_TIMEOUT);
// set the last heard time
Multi_voice_stream[stream_index].stream_last_heard = timer_get_fixed_seconds();
// copy the data and setup any other accum buffer data necessary
// ignore data if we can't play sounds
if(Multi_voice_can_play){
memcpy(Multi_voice_stream[stream_index].accum_buffer[chunk_index],data+offset,(int)chunk_size);
}
Multi_voice_stream[stream_index].accum_buffer_flags[chunk_index] = 1;
Multi_voice_stream[stream_index].accum_buffer_csize[chunk_index] = chunk_size;
// pass the data into the smart voice algorithm
if(player_index != -1){
multi_voice_alg_process_data(stream_index);
}
}
// increment the offset
offset += (int)chunk_size;
return offset;
}
// <server> increment the current stream id#
void multi_voice_inc_stream_id()
{
Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER);
if(Multi_voice_next_stream_id == 0xff){
Multi_voice_next_stream_id = 0;
} else {
Multi_voice_next_stream_id++;
}
}
// flush any old sound stream data because we've started to receive data for a new stream
void multi_voice_flush_old_stream(int stream_index)
{
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : old stream flush\n"));
#endif
// call the smart algorithm for flushing streams
multi_voice_alg_flush_old_stream(stream_index);
// clear all the accum buffer flags
memset(Multi_voice_stream[stream_index].accum_buffer_flags,0,MULTI_VOICE_ACCUM_BUFFER_COUNT);
// clear the token
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_voice_take_token(stream_index);
}
Multi_voice_stream[stream_index].token_stamp = -1;
Multi_voice_stream[stream_index].token_status = MULTI_VOICE_TOKEN_INDEX_FREE;
// timestamp the player
Net_player->s_info.voice_token_timestamp = timestamp(Netgame.options.voice_token_wait);
}
// route sound data through the server to all appropriate players
void multi_voice_route_data(ubyte *data, int packet_size,int player_index,int mode,net_player *target)
{
int idx;
// route the data to all other players
switch(mode){
case MULTI_MSG_ALL:
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED( Net_players[idx] ) && // player is connected
( &Net_players[idx] != &Net_players[player_index] ) && // not the sending player
( Net_player != &Net_players[idx] ) && // not me
( Multi_voice_player_prefs[idx] & (1 << player_index) ) && // is accepting sound from this player
!( Net_players[idx].p_info.options.flags & MLO_FLAG_NO_VOICE ) ){ // is accepting sound periods
multi_io_send(&Net_players[idx], data, packet_size);
}
}
break;
case MULTI_MSG_FRIENDLY:
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED( Net_players[idx] ) && // player is connected
( &Net_players[idx] != &Net_players[player_index] ) && // not the sending player
( Net_player != &Net_players[idx] ) && // not me
( Net_players[idx].p_info.team == Net_players[player_index].p_info.team ) &&// on the same team
( Multi_voice_player_prefs[idx] & (1 << player_index) ) && // is accepting sound from the sender
!( Net_players[idx].p_info.options.flags & MLO_FLAG_NO_VOICE) ){ // is accepting sound periods
multi_io_send(&Net_players[idx], data, packet_size);
}
}
break;
case MULTI_MSG_HOSTILE:
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED( Net_players[idx] ) && // player is connected
( &Net_players[idx] != &Net_players[player_index] ) && // not the sending player
( Net_player != &Net_players[idx] ) && // not me
( Net_players[idx].p_info.team != Net_players[player_index].p_info.team ) &&// on the opposite team
( Multi_voice_player_prefs[idx] & (1 << player_index) ) && // is accepting sound from the sender
!( Net_players[idx].p_info.options.flags & MLO_FLAG_NO_VOICE ) ){ // is accepting sound periods
multi_io_send(&Net_players[idx], data, packet_size);
}
}
break;
case MULTI_MSG_TARGET:
Assert(target != NULL);
if(!(target->p_info.options.flags & MLO_FLAG_NO_VOICE)){
multi_io_send(target, data, packet_size);
}
break;
}
}
// find the stream to apply incoming sound data to, freeing up old ones as necessary
int multi_voice_get_stream(int stream_id)
{
int idx,max_diff_index;
fix cur_time,max_diff;
// first check to see if this stream exists
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
if(Multi_voice_stream[idx].stream_id == (ubyte)stream_id){
return idx;
}
}
// if we got to this point, we didn't find the matching stream, so we should try and find an empty stream
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
if(Multi_voice_stream[idx].token_stamp == -1){
return idx;
}
}
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : going to blast old voice stream while looking for a free one - beware!!\n"));
#endif
// if we got to this point, we should free up the oldest stream we have
cur_time = timer_get_fixed_seconds();
max_diff_index = -1;
max_diff = -1;
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
if(((max_diff_index == -1) || ((cur_time - Multi_voice_stream[idx].stream_last_heard) > max_diff)) && (Multi_voice_stream[idx].token_stamp != -1)){
max_diff_index = idx;
max_diff = cur_time - Multi_voice_stream[idx].stream_last_heard;
}
}
// if we found the oldest
if(max_diff_index != -1){
// flush the old stream
multi_voice_flush_old_stream(max_diff_index);
return max_diff_index;
}
// some other fail condition
return -1;
}
// is the given sound stream playing (compares uncompressed sound size with current playback position)
int multi_voice_stream_playing(int stream_index)
{
// if the handle is invalid, it can't be playing
/*
if(Multi_voice_stream[stream_index].stream_snd_handle < 0){
return 0;
}
// if the sound is playing and the buffer is past the uncompressed size, its effectively done
if(ds_get_play_position(ds_get_channel(Multi_voice_stream[stream_index].stream_snd_handle)) >= (DWORD)Multi_voice_stream[stream_index].stream_uc_size){
return 1;
}
*/
// not done yet
return 0;
}
// tack on pre and post sounds to a sound stream (pass -1 for either if no sound is wanted)
// return final buffer size
int multi_voice_mix(int post_sound,char *data,int cur_size,int max_size)
{
int post_size;
// if the user passed -1 for both pre and post sounds, don't do a thing
if(post_sound == -1){
return cur_size;
}
// get the sizes of the additional sounds
// post sound
if(post_sound >= 0){
auto gs = gamesnd_get_game_sound(post_sound);
post_sound = snd_load(gamesnd_choose_entry(gs), gs->flags, 0);
if(post_sound >= 0){
if(snd_size(post_sound,&post_size) == -1){
post_size = 0;
}
} else {
post_size = 0;
}
} else {
post_size = 0;
}
// if we have a "post" sound to add
if(post_size > 0){
if((max_size - cur_size) > post_size){
// copy in the sound
snd_get_data(post_sound,data + cur_size);
// increment the cur_size
cur_size += post_size;
}
}
// return the size of the new buffer
return cur_size;
}
// max size of a sound chunk which we can fit into a packet
int multi_voice_max_chunk_size(int msg_mode)
{
int header_size;
// all headers contain the following data
header_size = 1 + // messaging mode
1 + // stream id #
2 + // packet uncompressed size
2 + // compressed size
4; // gain
// if we're targeting a specific player
if(msg_mode == MULTI_MSG_TARGET){
header_size += 2; // targeted player's object net_signature
}
// allocate header space for my address
header_size += 4; // my address (4 bytes in TCP)
// calculate max chunk size
return (MAX_PACKET_SIZE - // max freespace packet size
1 - // packet type
1 - // voice packet code subtype
header_size); // calculated header size
}
// --------------------------------------------------------------------------------------------------
// MULTI VOICE / RTVOICE INTERFACE
//
// process the "next" chunk of standalone valid sound data from the rtvoice system
void multi_voice_process_next_chunk()
{
int sound_size;
double d_gain;
voice_stream *str;
// we'd better not ever get here is we can't record voice
Assert(Multi_voice_can_record);
// get the data
rtvoice_get_data((unsigned char**)&Multi_voice_record_buffer, &sound_size, &d_gain);
// if we've reached the max # of packets for this stream, bail
if(Multi_voice_current_stream_index >= (MULTI_VOICE_ACCUM_BUFFER_COUNT - 1)){
nprintf(("Network","MULTI VOICE : Forcing stream to stop on the record size!!!\n"));
// mark me as no longer recording
Multi_voice_recording = 0;
Multi_voice_current_stream_sent = -1;
// stop the recording process
rtvoice_stop_recording();
#ifdef MULTI_VOICE_POST_DECOMPRESS
multi_voice_player_send_stream();
#endif
// play my sound locally as well
#ifdef MULTI_VOICE_LOCAL_ECHO
multi_voice_alg_play_window(0);
#endif
// release the token back to the server
multi_voice_release_token();
// unset the timestamp so we don't still think we're still recording
Multi_voice_recording_stamp = -1;
return;
}
// pack the data locally as well (so I can hear myself)
str = &Multi_voice_stream[0];
memcpy(str->accum_buffer[Multi_voice_current_stream_index],Multi_voice_record_buffer,sound_size);
str->stream_from = Net_player->player_id;
str->accum_buffer_flags[Multi_voice_current_stream_index] = 1;
str->accum_buffer_usize[Multi_voice_current_stream_index] = (ushort)sound_size;
str->accum_buffer_csize[Multi_voice_current_stream_index] = (ushort)sound_size;
str->accum_buffer_gain[Multi_voice_current_stream_index] = d_gain;
// increment the stream index
Multi_voice_current_stream_index++;
}
// --------------------------------------------------------------------------------------------------
// MULTI VOICE PACKET HANDLERS
//
// send a dummy packet in the place of a too-large data packet
void multi_voice_send_dummy_packet()
{
ubyte data[10],code,msg_mode;
int packet_size,target_index;
// build the header and add the opcode
BUILD_HEADER(VOICE_PACKET);
code = (ubyte)MV_CODE_DATA_DUMMY;
ADD_DATA(code);
msg_mode = (ubyte)Multi_voice_send_mode;
// get the specific target if we're in MSG_TARGET mode
target_index = -1;
if(msg_mode == MULTI_MSG_TARGET){
if(Player_ai->target_objnum != -1){
target_index = multi_find_player_by_object(&Objects[Player_ai->target_objnum]);
if(target_index == -1){
return;
}
} else {
return;
}
}
ADD_DATA(msg_mode);
if(msg_mode == MULTI_MSG_TARGET){
ADD_USHORT(Objects[Net_players[target_index].m_player->objnum].net_signature);
}
// add the voice stream id
ADD_DATA(Multi_voice_stream_id);
// send to the server or rebroadcast if I _am_ the server
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_voice_route_data(data,packet_size,MY_NET_PLAYER_NUM,(int)msg_mode,(target_index == -1) ? NULL : &Net_players[target_index]);
} else {
multi_io_send(Net_player, data, packet_size);
}
}
// process a dummy data packet
int multi_voice_process_data_dummy(ubyte *data)
{
int offset = 0;
int stream_index;
ubyte stream_id;
// get the stream id
GET_DATA(stream_id);
// get the proper stream index
if ( (stream_index = multi_voice_get_stream((int)stream_id) ) != -1 ) {
// set the token timestamp
Multi_voice_stream[stream_index].token_stamp = timestamp(MULTI_VOICE_TOKEN_TIMEOUT);
// set the last heard time
Multi_voice_stream[stream_index].stream_last_heard = timer_get_fixed_seconds();
// set the timeout timestamp
Multi_voice_stamps[stream_index] = timestamp(MV_ALG_TIMEOUT);
}
// return bytes processed
return offset;
}
// process a player preferences packet, return bytes processed
int multi_voice_process_player_prefs(ubyte *data,int player_index)
{
ubyte val;
int mute_index;
short mute_id;
int offset = 0;
// set all channels active
Multi_voice_player_prefs[player_index] = 0xffffffff;
// get all muted players
GET_DATA(val);
while(val != 0xff){
GET_SHORT(mute_id);
// get the player to mute
mute_index = find_player_id(mute_id);
if(mute_index != -1){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","Player %s muting player %s\n",Net_players[player_index].m_player->callsign,Net_players[mute_index].m_player->callsign));
#endif
// mute the guy
Multi_voice_player_prefs[player_index] &= ~(1<<mute_index);
}
// get the next stop value
GET_DATA(val);
}
// return bytes processed
return offset;
}
// process an incoming voice packet of some kind or another
void multi_voice_process_packet(ubyte *data, header *hinfo)
{
ubyte code,msg_mode;
ushort target_sig;
int player_index,stream_index,target_index;
int offset = HEADER_LENGTH;
// find out who is sending this data
player_index = find_player_id(hinfo->id);
// get the opcode
GET_DATA(code);
// process the packet
switch(code){
// I don't have the token anymore
case MV_CODE_TAKE_TOKEN:
// we should never have the token if we cannot record
if(!Multi_voice_can_record){
Int3();
}
Multi_voice_token = 0;
break;
// I have been denied the token
case MV_CODE_DENY_TOKEN:
// set the "denied" timestamp
Multi_voice_denied_stamp = timestamp(MULTI_VOICE_DENIED_TIME);
break;
// I now have the token
case MV_CODE_GIVE_TOKEN:
GET_DATA(Multi_voice_stream_id);
// we should never get the token if we cannot record
if(!Multi_voice_can_record){
Int3();
}
// if we no longer have the keydown, automatically release the token
if(!Multi_voice_keydown){
multi_voice_release_token();
} else {
Multi_voice_token = 1;
}
break;
// a request for the token from a player
case MV_CODE_REQUEST_TOKEN:
if(player_index >= 0){
multi_voice_process_token_request(player_index);
}
break;
// a player gave up the token
case MV_CODE_RELEASE_TOKEN:
if(player_index >= 0){
stream_index = multi_voice_find_token(player_index);
} else {
break;
}
if(stream_index >= 0){
// set the token as having been released
Multi_voice_stream[stream_index].token_status = MULTI_VOICE_TOKEN_INDEX_RELEASED;
// timestamp this guy so that he can't get the token back immediately
Net_players[player_index].s_info.voice_token_timestamp = timestamp(Netgame.options.voice_token_wait);
}
break;
// a player has set prefs for himself
case MV_CODE_PLAYER_PREFS:
Assert(player_index != -1);
offset += multi_voice_process_player_prefs(data+offset,player_index);
break;
// a data packet
case MV_CODE_DATA:
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","VOICE : PROC DATA\n"));
#endif
// get routing information
target_index = -1;
GET_DATA(msg_mode);
if(msg_mode == MULTI_MSG_TARGET){
GET_USHORT(target_sig);
target_index = multi_find_player_by_net_signature(target_sig);
Assert(target_index != -1);
}
offset += multi_voice_process_data(data+offset,player_index,msg_mode,(target_index == -1) ? NULL : &Net_players[target_index]);
// if we're the server of the game, we should also route this data to all other players
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_voice_route_data(data,offset,player_index,msg_mode,(target_index == -1) ? NULL : &Net_players[target_index]);
}
break;
// a data dummy packet
case MV_CODE_DATA_DUMMY:
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","VOICE : PROC DATA DUMMY\n"));
#endif
// get routing information
target_index = -1;
GET_DATA(msg_mode);
if(msg_mode == MULTI_MSG_TARGET){
GET_USHORT(target_sig);
target_index = multi_find_player_by_net_signature(target_sig);
Assert(target_index != -1);
}
offset += multi_voice_process_data_dummy(data+offset);
// if we're the server of the game, we should also route this data to all other players
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_voice_route_data(data,offset,player_index,msg_mode,(target_index == -1) ? NULL : &Net_players[target_index]);
}
break;
}
PACKET_SET_SIZE();
}
// send all pending voice packets
void multi_voice_client_send_pending()
{
ubyte data[MAX_PACKET_SIZE],code;
ubyte msg_mode,chunk_index;
ushort uc_size,chunk_size;
int max_chunk_size,sent,target_index;
int packet_size;
float gain;
voice_stream *str;
// if we're not recording
if(!Multi_voice_recording || (Multi_voice_current_stream_sent < 0) || (Multi_voice_current_stream_sent > Multi_voice_current_stream_index)){
return;
}
// stream all buffered up packets
str = &Multi_voice_stream[0];
while(Multi_voice_current_stream_sent < Multi_voice_current_stream_index){
sent = Multi_voice_current_stream_sent++;
// get the current messaging mode
msg_mode = (ubyte)Multi_voice_send_mode;
// if the size of this voice chunk will fit in the packet
max_chunk_size = multi_voice_max_chunk_size(Multi_voice_send_mode);
if(str->accum_buffer_csize[sent] > max_chunk_size){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : streamed packet size too large!!\n"));
#endif
Multi_voice_current_stream_sent++;
// send a dummy data packet instead
multi_voice_send_dummy_packet();
continue;
}
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : PACKET %d %d\n",(int)str->accum_buffer_csize[sent],(int)str->accum_buffer_usize[sent]));
#endif
// get the specific target if we're in MSG_TARGET mode
target_index = -1;
if(msg_mode == MULTI_MSG_TARGET){
if(Player_ai->target_objnum != -1){
target_index = multi_find_player_by_object(&Objects[Player_ai->target_objnum]);
if(target_index == -1){
return;
}
} else {
return;
}
}
// go through the data and send all of it
code = MV_CODE_DATA;
chunk_index = 0;
// if this packet is small enough to fit within a psnet data packet
BUILD_HEADER(VOICE_PACKET);
// add the packet code type
ADD_DATA(code);
// add the routing data and any necessary targeting information
ADD_DATA(msg_mode);
if(msg_mode == MULTI_MSG_TARGET){
Assert(Game_mode & GM_IN_MISSION);
ADD_USHORT(Objects[Net_players[target_index].m_player->objnum].net_signature);
}
// add my address
ADD_SHORT(Net_player->player_id);
// add the current stream id#
ADD_DATA(Multi_voice_stream_id);
uc_size = (ushort)str->accum_buffer_usize[sent];
ADD_USHORT(uc_size);
// add the chunk index
chunk_index = (ubyte)sent;
ADD_DATA(chunk_index);
// size of the sound data
chunk_size = (ushort)str->accum_buffer_csize[sent];
ADD_USHORT(chunk_size);
// add the gain
gain = (float)str->accum_buffer_gain[sent];
ADD_FLOAT(gain);
// add the chunk of data
memcpy(data+packet_size, str->accum_buffer[sent],chunk_size);
packet_size += chunk_size;
// send to the server or rebroadcast if I _am_ the server
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_voice_route_data(data,packet_size,MY_NET_PLAYER_NUM,(int)msg_mode,(target_index == -1) ? NULL : &Net_players[target_index]);
} else {
multi_io_send(Net_player, data, packet_size);
}
}
}
// --------------------------------------------------------------------------------------------------
// MULTI VOICE ALGORITHM stuff
//
// process and play the current window of sound stream data we have. reset the window for the next incoming data as well
void multi_voice_alg_play_window(int stream_index)
{
int idx,buffer_offset;
voice_stream *st;
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : PLAYING STREAM %d\n",stream_index));
#endif
// get a pointer to the stream
st = &Multi_voice_stream[stream_index];
// don't play anything back if we can't hear sound
if(Multi_voice_can_play){
// first, pack all the accum buffers into the playback buffer
#ifdef MULTI_VOICE_PRE_DECOMPRESS
buffer_offset = Multi_voice_pre_sound_size;
nprintf(("Network","VOICE : pre sound size %d\n",Multi_voice_pre_sound_size));
for(idx=0;idx<MULTI_VOICE_ACCUM_BUFFER_COUNT;idx++){
// if the flag is set, uncompress the data into the playback buffer
if(st->accum_buffer_flags[idx]){
// first, uncompress the data
rtvoice_uncompress(st->accum_buffer[idx],(int)st->accum_buffer_csize[idx],st->accum_buffer_gain[idx],(ubyte*)Multi_voice_playback_buffer+buffer_offset,st->accum_buffer_usize[idx]);
// increment the buffer offset
buffer_offset += st->accum_buffer_usize[idx];
}
}
#endif
#ifdef MULTI_VOICE_POST_DECOMPRESS
buffer_offset = 0;
for(idx=0;idx<MULTI_VOICE_ACCUM_BUFFER_COUNT;idx++){
// if the flag is set, copy the data
if(st->accum_buffer_flags[idx]){
memcpy(Multi_voice_unpack_buffer+buffer_offset,st->accum_buffer[idx],st->accum_buffer_csize[idx]);
buffer_offset += st->accum_buffer_csize[idx];
}
}
// decompress the whole shebang
rtvoice_uncompress((ubyte*)Multi_voice_unpack_buffer,buffer_offset,st->accum_buffer_gain[0],(ubyte*)Multi_voice_playback_buffer,st->accum_buffer_usize[0]);
buffer_offset = st->accum_buffer_usize[0];
#endif
// mix in the SND_CUE_VOICE and the SND_END_VOICE game sounds
buffer_offset = multi_voice_mix(MULTI_VOICE_POST_SOUND,Multi_voice_playback_buffer,buffer_offset,MULTI_VOICE_MAX_BUFFER_SIZE);
Assert(Multi_voice_stream[stream_index].stream_rtvoice_handle != -1);
// kill any previously playing sounds
rtvoice_stop_playback(Multi_voice_stream[stream_index].stream_rtvoice_handle);
Multi_voice_stream[stream_index].stream_snd_handle = -1;
// if we can play sound and we know who this is from, display it
if(Multi_voice_can_play){
char voice_msg[256];
int player_index = find_player_id(Multi_voice_stream[stream_index].stream_from);
if(player_index != -1){
memset(voice_msg,0,256);
sprintf(voice_msg,XSTR("<%s is speaking>",712),Net_players[player_index].m_player->callsign);
// display a chat message (write to the correct spot - hud, standalone gui, chatbox, etc)
multi_display_chat_msg(voice_msg,player_index,0);
}
}
// now call the rtvoice playback functions
Multi_voice_stream[stream_index].stream_snd_handle = rtvoice_play(Multi_voice_stream[stream_index].stream_rtvoice_handle,(unsigned char*)Multi_voice_playback_buffer,buffer_offset);
Multi_voice_stream[stream_index].stream_start_time = timer_get_fixed_seconds();
}
// unset the stamp so that its not "free"
Multi_voice_stamps[stream_index] = -1;
// flush the stream (will also grab the token back, if the server)
multi_voice_flush_old_stream(stream_index);
}
// decision function which decides if we should play the current block of sound we have
int multi_voice_alg_should_play(int stream_index)
{
// if the timestamp has expired, play the sound
if((Multi_voice_stamps[stream_index] != -1) && timestamp_elapsed(Multi_voice_stamps[stream_index])){
#ifdef MULTI_VOICE_VERBOSE
nprintf(("Network","MULTI VOICE : DECIDE, TIMEOUT\n"));
#endif
return 1;
}
return 0;
}
// process incoming sound data in whatever way necessary (this function should take care of playing data when necessary)
void multi_voice_alg_process_data(int stream_index)
{
// update the timestamp for this window
Multi_voice_stamps[stream_index] = timestamp(MV_ALG_TIMEOUT);
}
// process existing streams
void multi_voice_alg_process_streams()
{
int idx;
int player_index;
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
// determine if we should play this window of data
if((Multi_voice_stamps[idx] != -1) && multi_voice_alg_should_play(idx)){
// determine who this stream came from
player_index = find_player_id(Multi_voice_stream[idx].stream_from);
// server should check his own settings here
if((Net_player->flags & NETINFO_FLAG_AM_MASTER) && ((Net_player->p_info.options.flags & MLO_FLAG_NO_VOICE) || (player_index == -1) || !(Multi_voice_player_prefs[MY_NET_PLAYER_NUM] & (1<<player_index))) ){
// unset the stamp so that its not "free"
Multi_voice_stamps[idx] = -1;
// flush the stream (will also grab the token back, if the server)
multi_voice_flush_old_stream(idx);
nprintf(("Network","Server not playing sound because of set options!\n"));
}
// play the current sound
else {
multi_voice_alg_play_window(idx);
}
}
}
}
// we are going to flush the current stream because we have started to receive data for a new one. do something first
void multi_voice_alg_flush_old_stream(int stream_index)
{
// just unset the heard from timestamp for now
Multi_voice_stamps[stream_index] = -1;
}
// initialize the smart algorithm
void multi_voice_alg_init()
{
int idx;
for(idx=0;idx<MULTI_VOICE_MAX_STREAMS;idx++){
Multi_voice_stamps[idx] = -1;
}
}
// --------------------------------------------------------------------------------------------------
// MULTI VOICE TESTING FUNCTIONS
//
#define MV_TEST_RECORD_TIME 3000 // recording time in ms for testing voice
int Multi_voice_test_record_stamp = -1;
int Multi_voice_test_packet_tossed = 0;
// process the next chunk of voice data
void multi_voice_test_process_next_chunk()
{
unsigned char *outbuf;
int size;
double gain;
// if the test recording stamp is -1, we should stop
if(Multi_voice_test_record_stamp == -1){
rtvoice_stop_recording();
return;
}
// if the recording timestamp has elapsed, stop the whole thing
if(timestamp_elapsed(Multi_voice_test_record_stamp)){
nprintf(("Network","Stopping voice test recording\n"));
rtvoice_stop_recording();
Multi_voice_test_record_stamp = -1;
Multi_voice_test_packet_tossed = 0;
return;
}
// otherwise get the compressed and uncompressed data and do something interesting with it
rtvoice_get_data(&outbuf, &size, &gain);
// determine whether the packet would have been dropped
if (size > multi_voice_max_chunk_size(MULTI_MSG_ALL)) {
Multi_voice_test_packet_tossed = 1;
} else {
Multi_voice_test_packet_tossed = 0;
}
// send the raw output buffer to the voice options screen
options_multi_set_voice_data(outbuf, size, gain);
}
// start recording voice locally for playback testing
void multi_voice_test_record_start()
{
// if there is test recording going on already, don't do anything
if(Multi_voice_test_record_stamp != -1){
return;
}
// stop any playback which may be occuring
rtvoice_stop_playback_all();
// stop any recording which may be occuring
rtvoice_stop_recording();
// set the timestamp
Multi_voice_test_record_stamp = timestamp(MV_TEST_RECORD_TIME);
// start the recording of voice
rtvoice_start_recording(multi_voice_test_process_next_chunk, 175);
}
// force stop any recording voice test
void multi_voice_test_record_stop()
{
Multi_voice_test_record_stamp = -1;
Multi_voice_test_packet_tossed = 0;
rtvoice_stop_recording();
}
// return if the test recording is going on
int multi_voice_test_recording()
{
return (Multi_voice_test_record_stamp == -1) ? 0 : 1;
}
// call this function if multi_voice_test_recording() is true to process various odds and ends of the test recording
void multi_voice_test_process()
{
// if we're not recording, do nothing
if(Multi_voice_test_record_stamp == -1){
return;
}
// check to see if the timestamp has elapsed
if(timestamp_elapsed(Multi_voice_test_record_stamp)){
Multi_voice_test_record_stamp = -1;
Multi_voice_test_packet_tossed = 0;
}
}
// get a playback buffer handle (return -1 if none exist - bad)
int multi_voice_test_get_playback_buffer()
{
// return voice stream 0
Assert(Multi_voice_stream[0].stream_snd_handle == -1);
Assert(Multi_voice_stream[0].stream_rtvoice_handle != -1);
return Multi_voice_stream[0].stream_rtvoice_handle;
}
// return whether the last sampled chunk would have been too large to fit in a packet
int multi_voice_test_packet_tossed()
{
return Multi_voice_test_packet_tossed;
}