/* * 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 "cmdline/cmdline.h" #include "debugconsole/console.h" #include "gamesnd/gamesnd.h" #include "globalincs/linklist.h" #include "io/joy_ff.h" #include "io/timer.h" #include "object/object.h" #include "object/objectsnd.h" #include "render/3d.h" #include "ship/ship.h" #include "sound/ds.h" #include "sound/ds3d.h" #include "species_defs/species_defs.h" // // --mharris port hack-- // int ds_using_ds3d(); // int ds_get_channel(int); // int ds3d_update_buffer(int, float, float, vec3d *, vec3d *); // --end hack-- // Persistent sounds for objects (pointer to obj_snd is in object struct) typedef struct _obj_snd { _obj_snd *next, *prev; int objnum; // object index of object that contains this sound gamesnd_id id; // Index into Snds[] array sound_handle instance; // handle of currently playing sound (a ds3d handle if USES_DS3D flag set) int next_update; // timestamp that marks next allowed vol/pan change float vol; // volume of sound (range: 0.0 -> 1.0) float pan; // pan of sound (range: -1.0 -> 1.0) int freq; // valid range: 100 -> 100000 Hz int flags; vec3d offset; // offset from the center of the object where the sound lives const ship_subsys *ss; //Associated subsystem } obj_snd; #define SPEED_SOUND 600.0f // speed of sound in FreeSpace static int MAX_OBJ_SOUNDS_PLAYING = -1; // initialized in obj_snd_level_init() static int Num_obj_sounds_playing; static obj_snd obj_snd_list; // head of linked list of object sound structs static int Doppler_enabled = TRUE; #define MAX_OBJ_SNDS 256 obj_snd Objsnds[MAX_OBJ_SNDS]; int Obj_snd_enabled = TRUE; UI_TIMESTAMP Obj_snd_last_update; // timer used to run object sound updates at fixed time intervals int Obj_snd_level_inited=0; // ship flyby data constexpr int FLYBY_MIN_DISTANCE = 90; constexpr int FLYBY_MIN_RELATIVE_SPEED = 100; constexpr int FLYBY_MIN_NEXT_TIME = 1 * MILLISECONDS_PER_SECOND; // in ms constexpr int FLYBY_MIN_REPEAT_TIME = 4 * MILLISECONDS_PER_SECOND; // in ms static TIMESTAMP Flyby_next_sound; static TIMESTAMP Flyby_next_repeat; object *Flyby_last_objp; // return the world pos of the sound source on a ship. void obj_snd_source_pos(vec3d *sound_pos, obj_snd *osp) { vec3d offset_world; object *objp = &Objects[osp->objnum]; // get sound pos in world coords vm_vec_unrotate(&offset_world, &osp->offset, &objp->orient); vm_vec_add(sound_pos, &objp->pos, &offset_world); } // determine what index in this guy the sound is int obj_snd_find(object *objp, obj_snd *osp) { int idx = 0; for (auto iter = objp->objsnd_num.begin(); iter != objp->objsnd_num.end(); ++iter, ++idx) { if (*iter == (osp - Objsnds)) { return idx; } } return -1; } // --------------------------------------------------------------------------------------- // dcf_objsnd() // // Debug console function for object linked persistent sounds // //XSTR:OFF DCF(objsnd, "Persistent sound stuff" ) { char buf1[4]; char buf2[MAX_NAME_LEN]; obj_snd *osp; SCP_string arg; if (dc_optional_string_either("help", "--help")) { dc_printf ("Usage: objsnd [-list]\n"); dc_printf ("[-list] -- displays status of all objects with linked sounds\n"); dc_printf ("with no parameters, object sounds are toggled on/off\n"); return; } if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { dc_printf( "Object sounds are: %s\n", (Obj_snd_enabled?"ON":"OFF") ); } if (dc_optional_string("-list")) { for ( osp = GET_FIRST(&obj_snd_list); osp !=END_OF_LIST(&obj_snd_list); osp = GET_NEXT(osp) ) { vec3d source_pos; float distance; Assert(osp != NULL); if (!osp->instance.isValid()) { continue; //sprintf(buf1,"OFF"); } else { sprintf(buf1,"ON"); } if ( Objects[osp->objnum].type == OBJ_SHIP ) { strcpy_s(buf2, Ships[Objects[osp->objnum].instance].ship_name); } else if ( Objects[osp->objnum].type == OBJ_DEBRIS ) { sprintf(buf2, "Debris"); } else { sprintf(buf2, "Unknown"); } obj_snd_source_pos(&source_pos, osp); distance = vm_vec_dist_quick( &source_pos, &View_position ); dc_printf("Object %d => name: %s vol: %.2f pan: %.2f dist: %.2f status: %s\n", osp->objnum, buf2, osp->vol, osp->pan, distance, buf1); } // end for dc_printf("Number object-linked sounds playing: %d\n", Num_obj_sounds_playing); return; } if (!dc_maybe_stuff_string_white(arg)) { // No arguments, toggle snd on/off if ( Obj_snd_enabled == TRUE ) { obj_snd_stop_all(); Obj_snd_enabled = FALSE; } else { Obj_snd_enabled = TRUE; } } else { dc_printf("Unknown argument '%s'\n", arg.c_str()); } } //XSTR:ON // --------------------------------------------------------------------------------------- // Debug console function for toggling doppler effection on/off // DCF_BOOL( doppler, Doppler_enabled ) // --------------------------------------------------------------------------------------- // obj_snd_get_slot() // // Get a free slot in the Objsnds[] array // // returns -1 if no slot is available int obj_snd_get_slot() { int i; for ( i = 0; i < MAX_OBJ_SNDS; i++ ) { if ( !(Objsnds[i].flags & OS_IN_USE) ) return i; } return -1; } // --------------------------------------------------------------------------------------- // obj_snd_init() // // Called once at level start to initialize the persistent object sound system // void obj_snd_level_init() { int i; if (MAX_OBJ_SOUNDS_PLAYING < 0) { MAX_OBJ_SOUNDS_PLAYING = Cmdline_no_enhanced_sound ? 5 : 12; } list_init(&obj_snd_list); for ( i = 0; i < MAX_OBJ_SNDS; i++ ) { Objsnds[i].flags = 0; } Num_obj_sounds_playing = 0; Flyby_next_sound = TIMESTAMP::immediate(); Flyby_next_repeat = TIMESTAMP::immediate(); Flyby_last_objp = NULL; Obj_snd_last_update = ui_timestamp(); if ( !snd_is_inited() ) { Obj_snd_enabled = FALSE; return; } Obj_snd_level_inited=1; } // --------------------------------------------------------------------------------------- // obj_snd_stop() // // Stop a persistent sound from playing. // // parameters: objp => pointer to object that sound is to be stopped for // // void obj_snd_stop(object *objp, int index) { obj_snd *osp; // sanity if(index >= (int) objp->objsnd_num.size()){ Error(LOCATION, "Object sound index %d is bigger than the actual size %d!", index, (int) objp->objsnd_num.size()); return; } // if index is -1, kill all sounds for this guy if(index == -1){ // kill all sounds for this guy for(SCP_vector::iterator iter = objp->objsnd_num.begin(); iter != objp->objsnd_num.end(); ++iter){ if ( *iter == -1 ){ continue; } osp = &Objsnds[*iter]; if (osp->instance.isValid()) { snd_stop(osp->instance); osp->instance = sound_handle::invalid(); switch(objp->type) { case OBJ_SHIP: case OBJ_GHOST: case OBJ_DEBRIS: case OBJ_ASTEROID: case OBJ_WEAPON: Num_obj_sounds_playing--; Assert(Num_obj_sounds_playing >= 0); break; default: Int3(); // get Alan break; } } } } else { if ( objp->objsnd_num[index] == -1 ){ return; } osp = &Objsnds[objp->objsnd_num[index]]; if (osp->instance.isValid()) { snd_stop(osp->instance); osp->instance = sound_handle::invalid(); switch(objp->type) { case OBJ_SHIP: case OBJ_GHOST: case OBJ_DEBRIS: case OBJ_ASTEROID: case OBJ_WEAPON: Num_obj_sounds_playing--; Assert(Num_obj_sounds_playing >= 0); break; default: Int3(); // get Alan break; } } } } // --------------------------------------------------------------------------------------- // obj_snd_stop_all() // // Stop all object-linked persistent sounds from playing // // void obj_snd_stop_all() { object* A; for ( A = GET_FIRST(&obj_used_list); A !=END_OF_LIST(&obj_used_list); A = GET_NEXT(A) ) { // we probably don't want to skip should-be-dead objects here obj_snd_stop(A, -1); } } /** * Stop a playing object sound, if it is quieter than sound at new_distance * * @param new_vol Volume of requested sound to play * * @return true A sound was stopped * @return false A sound was not stopped */ bool obj_snd_stop_lowest_vol(float new_vol) { obj_snd *osp; object *objp = NULL; obj_snd *lowest_vol_osp = NULL; float lowest_vol; int obj_snd_index = -1; lowest_vol = 1000.0f; for ( osp = GET_FIRST(&obj_snd_list); osp !=END_OF_LIST(&obj_snd_list); osp = GET_NEXT(osp) ) { Assert(osp->objnum != -1); objp = &Objects[osp->objnum]; if ((osp->instance.isValid()) && (osp->vol < lowest_vol)) { lowest_vol = osp->vol; lowest_vol_osp = osp; } } if (lowest_vol_osp != NULL) objp = &Objects[lowest_vol_osp->objnum]; if ( (lowest_vol < new_vol) && (objp != NULL) ) { obj_snd_index = obj_snd_find(objp, lowest_vol_osp); if((obj_snd_index == -1) || (obj_snd_index >= (int) objp->objsnd_num.size())){ Int3(); // get dave } else { obj_snd_stop(objp, obj_snd_index); } return true; } return false; } //int Debug_1 = 0, Debug_2 = 0; // --------------------------------------------------------------------------------------- // maybe_play_flyby_snd() // // Based on how close the object is to the camera (and relative speed), maybe // play a flyby sound. Only play flyby sound for OBJ_SHIP objects. // // NOTE: global data Flyby_last_objp, Flyby_next_sound, Flyby_next_repeat are // used. // void maybe_play_flyby_snd(float closest_dist, object *closest_objp, object *listener_objp) { if ( closest_objp == NULL || closest_objp->type != OBJ_SHIP || closest_objp == listener_objp ) { return; } if ( closest_dist < FLYBY_MIN_DISTANCE ) { float relative_speed; vec3d diff; vm_vec_sub(&diff, &listener_objp->phys_info.vel, &closest_objp->phys_info.vel); relative_speed = vm_vec_mag_quick(&diff); if ( relative_speed > FLYBY_MIN_RELATIVE_SPEED ) { if ( timestamp_elapsed(Flyby_next_sound) ) { if ( closest_objp == Flyby_last_objp ) { if ( timestamp_elapsed(Flyby_next_repeat) ) { Flyby_next_repeat = _timestamp(FLYBY_MIN_REPEAT_TIME); } else return; } Assert(closest_objp->type == OBJ_SHIP); if(closest_objp->type != OBJ_SHIP){ return; } // check to see if a flyby sound is specified in ship class // if not, then pick a random species-based sound ship_info* sip = &Ship_info[Ships[closest_objp->instance].ship_info_index]; game_snd *snd; if (sip->flyby_snd.isValid()) { snd = gamesnd_get_game_sound(sip->flyby_snd); } else { if (sip->flags[Ship::Info_Flags::Bomber]) snd = &Species_info[sip->species].snd_flyby_bomber; else snd = &Species_info[sip->species].snd_flyby_fighter; } if (snd->sound_entries.empty()) return; // This class or species does not define any relevant flyby sounds // play da sound snd_play_3d(snd, &closest_objp->pos, &View_position); //float dist = vm_vec_dist(&closest_objp->pos, &View_position); //nprintf(("AI", "Frame %i: Playing flyby sound, species = %i, size = %i, dist = %7.3f\n", Framecount, species, ship_size, dist)); // nprintf(("AI", "Frame %i: Playing flyby sound, species = %i, size = %i, dist = %7.3f\n", Framecount, Debug_1, Debug_2, dist)); //Debug_1 = (Debug_1+1)%3; //Debug_2 = (Debug_2+1)%2; joy_ff_fly_by(100 - (int) (100.0f * closest_dist / FLYBY_MIN_DISTANCE)); Flyby_next_sound = _timestamp(FLYBY_MIN_NEXT_TIME); Flyby_last_objp = closest_objp; } } } } // --------------------------------------------------------------------------------------- // obj_snd_do_frame() // // Called once per frame to process the persistent sound objects // void obj_snd_do_frame() { float closest_dist, distance, speed_vol_multiplier, rot_vol_mult, percent_max, alive_vol_mult; obj_snd *osp; object *objp, *closest_objp; game_snd *gs; int channel; vec3d source_pos; float add_distance; if ( Obj_snd_enabled == FALSE ) return; if ( ui_timestamp_since(Obj_snd_last_update) > 20 ) { Obj_snd_last_update = ui_timestamp(); } else { return; } closest_dist = 1000000.0f; closest_objp = nullptr; object *observer_obj = nullptr; if (Viewer_obj != nullptr) { observer_obj = Viewer_obj; } else if (Viewer_mode & VM_OTHER_SHIP && Player_ai->target_objnum != -1) { // apparently Viewer_obj is still null when Viewing Other Ship externally observer_obj = &Objects[Player_ai->target_objnum]; } else { observer_obj = Player_obj; } for ( osp = GET_FIRST(&obj_snd_list); osp !=END_OF_LIST(&obj_snd_list); osp = GET_NEXT(osp) ) { Assert(osp != nullptr); objp = &Objects[osp->objnum]; if ((Player_obj == objp) && (observer_obj == Player_obj) && !(osp->flags & OS_PLAY_ON_PLAYER)) { // we don't play sounds if the view is from the player // unless OS_PLAY_ON_PLAYER was set manually continue; } gs = gamesnd_get_game_sound(osp->id); if (gs->flags & GAME_SND_NOT_VALID) { continue; } bool obj_is_ship = (objp->type == OBJ_SHIP); obj_snd_source_pos(&source_pos, osp); distance = vm_vec_dist_quick( &source_pos, &View_position ); // how much extra distance do we add before attentuation? add_distance = 0.0f; if(osp->flags & OS_MAIN){ add_distance = objp->radius; } distance -= add_distance; if ( distance < 0.0f ) { distance = 0.0f; } // save closest distance (used for flyby sound) if this is a small ship (and not the observer) if ( (obj_is_ship) && (distance < closest_dist) && (objp != observer_obj) ) { if ( Ship_info[Ships[objp->instance].ship_info_index].is_small_ship() ) { closest_dist = distance; closest_objp = objp; } } speed_vol_multiplier = 1.0f; rot_vol_mult = 1.0f; alive_vol_mult = 1.0f; if ( obj_is_ship ) { ship_info *sip = &Ship_info[Ships[objp->instance].ship_info_index]; // we don't want to start the engine sound unless the ship is // moving (unless flag SIF_BIG_SHIP is set) if ( (osp->flags & OS_ENGINE) && (!(sip->is_big_or_huge()) || Unify_minimum_engine_sound) ) { if ( objp->phys_info.max_vel.xyz.z <= 0.0f ) { percent_max = 0.0f; } else percent_max = objp->phys_info.fspeed / objp->phys_info.max_vel.xyz.z; if ( sip->min_engine_vol == -1.0f) { // Retail behavior: volume ramps from 0.5 (when stationary) to 1.0 (when at half speed) if ( percent_max >= 0.5f ) { speed_vol_multiplier = 1.0f; } else { speed_vol_multiplier = 0.5f + (percent_max); // linear interp: 0.5->1.0 when 0.0->0.5 } } else { // Volume ramps from min_engine_vol (when stationary) to 1.0 (when at full speed) speed_vol_multiplier = sip->min_engine_vol + ((1.0f - sip->min_engine_vol) * percent_max); } } // check conditions for subsystem sounds if (osp->ss != nullptr) { if (osp->flags & OS_TURRET_BASE_ROTATION) { if (osp->ss->base_rotation_rate_pct > 0.0f) rot_vol_mult = ((0.25f + (0.75f * osp->ss->base_rotation_rate_pct)) * osp->ss->system_info->turret_base_rotation_snd_mult); else rot_vol_mult = 0.0f; } if (osp->flags & OS_TURRET_GUN_ROTATION) { if (osp->ss->gun_rotation_rate_pct > 0.0f) rot_vol_mult = ((0.25f + (0.75f * osp->ss->gun_rotation_rate_pct)) * osp->ss->system_info->turret_gun_rotation_snd_mult); else rot_vol_mult = 0.0f; } if (osp->flags & OS_SUBSYS_ROTATION ) { if (osp->ss->flags[Ship::Subsystem_Flags::Rotates]) { rot_vol_mult = 1.0f; } else { rot_vol_mult = 0.0f; } } if (osp->flags & OS_SUBSYS_ALIVE) { if (osp->ss->current_hits > 0.0f) { alive_vol_mult = 1.0f; } else { alive_vol_mult = 0.0f; } } if (osp->flags & OS_SUBSYS_DEAD) { if (osp->ss->current_hits <= 0.0f) { alive_vol_mult = 1.0f; } else { alive_vol_mult = 0.0f; } } if (osp->flags & OS_SUBSYS_DAMAGED) { alive_vol_mult = osp->ss->current_hits / osp->ss->max_hits; CLAMP(alive_vol_mult, 0.0f, 1.0f); } } } if ( !osp->instance.isValid() ) { bool go_ahead_flag = true; float new_vol; // if this is a 3D sound, check distance // (since non-3D sounds have gs->max set to 0, they will never be played) if ( distance < gs->max ) { float max_vol = gs->volume_range.max(); if ( distance <= gs->min ) { new_vol = max_vol; } else { new_vol = max_vol - (distance - gs->min) * max_vol / (gs->max - gs->min); } if ( new_vol < 0.1f ) { continue; } switch( objp->type ) { case OBJ_SHIP: case OBJ_DEBRIS: case OBJ_ASTEROID: case OBJ_WEAPON: if ( Num_obj_sounds_playing >= MAX_OBJ_SOUNDS_PLAYING ) { go_ahead_flag = obj_snd_stop_lowest_vol(new_vol); } break; default: UNREACHABLE("Unhandled object type %d for persistent sound; get a coder!", objp->type); break; } // end switch if ( go_ahead_flag ) { int is_looping = (osp->flags & OS_LOOPING_DISABLED) ? 0 : 1; osp->instance = snd_play_3d(gs, &source_pos, &View_position, add_distance, &objp->phys_info.vel, is_looping, 1.0f, SND_PRIORITY_TRIPLE_INSTANCE, nullptr, 1.0f, 0, true); if (osp->instance.isValid()) { Num_obj_sounds_playing++; } } Assert(Num_obj_sounds_playing <= MAX_OBJ_SOUNDS_PLAYING); } } else { // sound has finished playing and won't be played again if ((osp->flags & OS_LOOPING_DISABLED) && !snd_is_playing(osp->instance)) { auto osp_prev = GET_PREV(osp); // non-looping sounds that have already played once need to be removed from the object sound list int sound_index = obj_snd_find(objp, osp); obj_snd_delete(objp, sound_index); // don't corrupt the iterating loop (next iteration will move to the deleted osp's next sibling) osp = osp_prev; continue; } // currently playing sound has gone past maximum if ( distance > gamesnd_get_game_sound(osp->id)->max ) { int sound_index = obj_snd_find(objp, osp); Assert(sound_index != -1); obj_snd_stop(objp, sound_index); } } if (!osp->instance.isValid()) continue; bool sound_allowed = true; ship* sp = nullptr; if (obj_is_ship) { sp = &Ships[objp->instance]; if (osp->flags & OS_ENGINE) { bool disabled_and_silent = Disabled_or_disrupted_engines_silent && (sp->flags[Ship::Ship_Flags::Disabled] || ship_subsys_disrupted(sp, SUBSYSTEM_ENGINE)); sound_allowed = (sp->flags[Ship::Ship_Flags::Engine_sound_on]) && !disabled_and_silent; } } channel = ds_get_channel(osp->instance); // for DirectSound3D sounds, re-establish the maximum speed based on the // speed_vol_multiplier if ( sound_allowed ) { snd_set_volume( osp->instance, gs->volume_range.next() *speed_vol_multiplier*rot_vol_mult*alive_vol_mult ); } else { // engine sound is disabled snd_set_volume( osp->instance, 0.0f ); } vec3d vel = objp->phys_info.vel; // Don't play doppler effect for cruisers or capitals if (obj_is_ship) { if ( Ship_info[sp->ship_info_index].is_big_or_huge() ) { vel = vmd_zero_vector; } } ds3d_update_buffer(channel, i2fl(gs->min), i2fl(gs->max), &source_pos, &vel); snd_get_3d_vol_and_pan(gs, &source_pos, &osp->vol, &osp->pan, add_distance); } // end for // see if we want to play a flyby sound maybe_play_flyby_snd(closest_dist, closest_objp, observer_obj); } // --------------------------------------------------------------------------------------- // obj_snd_assign() // // Assign a persistent sound to an object. // // parameters: objnum => index of object that sound is being assigned to // i => Index into Snds[] array // fname => filename of sound to play ( so DS3D can load the sound ) // // returns: -1 => sound could not be assigned (possible, since only MAX_OBJECT_SOUNDS persistent // sound can be assigned per object). // >= 0 => sound was successfully assigned // int obj_snd_assign(int objnum, gamesnd_id sndnum, const vec3d *pos, int flags, const ship_subsys *associated_sub) { Assertion(pos != nullptr, "Sound position must not be null!"); if(objnum < 0 || objnum >= MAX_OBJECTS) return -1; if(!sndnum.isValid()) return -1; if ( Obj_snd_enabled == FALSE ) return -1; //Initialize object sound list, if not initialized if (!Obj_snd_level_inited) obj_snd_level_init(); obj_snd *snd = NULL; object *objp = &Objects[objnum]; int sound_index; int idx = 0; // try and find a valid objsound index sound_index = -1; for(SCP_vector::iterator iter = objp->objsnd_num.begin(); iter != objp->objsnd_num.end(); ++iter, ++idx){ if(*iter == -1){ sound_index = idx; break; } } // no sound slot free, make a new one! if ( sound_index == -1 ){ sound_index = (int) objp->objsnd_num.size(); objp->objsnd_num.push_back(-1); } objp->objsnd_num[sound_index] = obj_snd_get_slot(); if ( objp->objsnd_num[sound_index] == -1 ) { nprintf(("Sound", "SOUND ==> No free object-linked sounds left\n")); return -1; } snd = &Objsnds[objp->objsnd_num[sound_index]]; snd->flags = OS_IN_USE; if(flags > 0){ snd->flags |= flags; } snd->id = sndnum; snd->instance = sound_handle::invalid(); snd->vol = 0.0f; snd->objnum = OBJ_INDEX(objp); snd->next_update = 1; snd->offset = *pos; snd->ss = associated_sub; // vm_vec_sub(&snd->offset, pos, &objp->pos); // add objp to the obj_snd_list list_append( &obj_snd_list, snd ); return sound_index; } // --------------------------------------------------------------------------------------- // obj_snd_delete() // // Remove a persistent sound that has been assigned to an object. // // parameters: objnum => index of object that sound is being removed from. // index => index of sound in objsnd_num // void obj_snd_delete(object *objp, int index) { Assert(index > -1 && index < (int) objp->objsnd_num.size()); obj_snd *osp = &Objsnds[objp->objsnd_num[index]]; //Stop the sound obj_snd_stop(objp, index); // remove objp from the obj_snd_list list_remove( &obj_snd_list, osp ); osp->objnum = -1; osp->flags = 0; osp = NULL; objp->objsnd_num[index] = -1; } // --------------------------------------------------------------------------------------- // obj_snd_delete_type() // // Remove every similar persistent sound that has been assigned to an object. // // parameters: objnum => index of object that sound is being removed from. // sndnum => index of sound that we're trying to completely get rid of // -1 to delete all persistent sounds on ship. // // void obj_snd_delete_type(int objnum, gamesnd_id sndnum, ship_subsys *ss) { object *objp; obj_snd *osp; if(objnum < 0 || objnum >= MAX_OBJECTS) return; objp = &Objects[objnum]; int idx = 0; //Go through the list and get sounds that match criteria for(SCP_vector::iterator iter = objp->objsnd_num.begin(); iter != objp->objsnd_num.end(); ++iter, ++idx){ // no sound if ( *iter == -1 ){ continue; } osp = &Objsnds[*iter]; // if we're just deleting a specific sound type // and this is not one of them. skip it. //Also check if this is assigned to the right subsystem, if one has been given. if(((sndnum.isValid()) && (osp->id != sndnum)) || ((ss != NULL) && (osp->ss != ss))){ continue; } obj_snd_delete(objp, idx); } } // --------------------------------------------------------------------------------------- // obj_snd_delete_all() // // Remove all persistent sounds // void obj_snd_delete_all() { int idx; for(idx=0; idx