From ff0e0fd9ea6ce001e9c2c6f9e18ecda6aef15d10 Mon Sep 17 00:00:00 2001 From: havoc Date: Thu, 15 Sep 2011 19:12:19 +0000 Subject: [PATCH] sound mixer now operates on floating point, has on-the-fly resampling (allows features like pitch shifting, and potentially doppler), and the ogg and modplug streaming code has been largely rewritten, much cleaner now, this saves about 4MB of ram when playing quake content at default settings (by not resampling the wav data to 48khz anymore), snd_speed changes will now apply to next vid_restart (tested at 48, 96, 192khz) git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@11352 d7cf8633-e32d-0410-b094-e92efae38249 --- cd_shared.c | 2 +- gl_textures.c | 2 +- snd_main.c | 178 +++++++------- snd_main.h | 47 ++-- snd_mem.c | 2 - snd_mix.c | 633 ++++++++++++++++++++------------------------------ snd_modplug.c | 224 +++++++----------- snd_null.c | 2 +- snd_ogg.c | 331 +++++++++----------------- snd_wav.c | 90 ++++--- sound.h | 3 +- 11 files changed, 613 insertions(+), 901 deletions(-) diff --git a/cd_shared.c b/cd_shared.c index 453aba62..cd12235c 100644 --- a/cd_shared.c +++ b/cd_shared.c @@ -324,7 +324,7 @@ void CDAudio_Play_byName (const char *trackname, qboolean looping, qboolean tryr } if (FS_FileExists(filename) && (sfx = S_PrecacheSound (filename, false, false))) { - faketrack = S_StartSound_StartPosition_Flags (-1, 0, sfx, vec3_origin, cdvolume, 0, startposition, (looping ? CHANNELFLAG_FORCELOOP : 0) | CHANNELFLAG_FULLVOLUME | CHANNELFLAG_LOCALSOUND); + faketrack = S_StartSound_StartPosition_Flags (-1, 0, sfx, vec3_origin, cdvolume, 0, startposition, (looping ? CHANNELFLAG_FORCELOOP : 0) | CHANNELFLAG_FULLVOLUME | CHANNELFLAG_LOCALSOUND, 1.0f); if (faketrack != -1) { if(track >= 1) diff --git a/gl_textures.c b/gl_textures.c index 2302eef8..e7d44437 100644 --- a/gl_textures.c +++ b/gl_textures.c @@ -2208,7 +2208,7 @@ rtexture_t *R_LoadTextureDDSFile(rtexturepool_t *rtexturepool, const char *filen bytesperblock = 8; ddssize -= 128; ddssize /= 2; - for (i = 0;i < ddssize;i += bytesperblock) + for (i = 0;i < (int)ddssize;i += bytesperblock) memcpy(&ddspixels[i], &ddspixels[(i<<1)+8], 8); ddssize += 128; } diff --git a/snd_main.c b/snd_main.c index 3ca5f494..34727e61 100644 --- a/snd_main.c +++ b/snd_main.c @@ -29,7 +29,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define SND_MIN_SPEED 8000 -#define SND_MAX_SPEED 96000 +#define SND_MAX_SPEED 192000 #define SND_MIN_WIDTH 1 #define SND_MAX_WIDTH 2 #define SND_MIN_CHANNELS 1 @@ -178,7 +178,7 @@ cvar_t snd_spatialization_occlusion = {CVAR_SAVE, "snd_spatialization_occlusion" // Cvars declared in snd_main.h (shared with other snd_*.c files) cvar_t _snd_mixahead = {CVAR_SAVE, "_snd_mixahead", "0.15", "how much sound to mix ahead of time"}; cvar_t snd_streaming = { CVAR_SAVE, "snd_streaming", "1", "enables keeping compressed ogg sound files compressed, decompressing them only as needed, otherwise they will be decompressed completely at load (may use a lot of memory); when set to 2, streaming is performed even if this would waste memory"}; -cvar_t snd_streaming_length = { CVAR_SAVE, "snd_streaming_length", "0", "When set, sound files are only streamed if longer than the given length in seconds"}; +cvar_t snd_streaming_length = { CVAR_SAVE, "snd_streaming_length", "1", "decompress sounds completely if they are less than this play time when snd_streaming is 1"}; cvar_t snd_swapstereo = {CVAR_SAVE, "snd_swapstereo", "0", "swaps left/right speakers for old ISA soundblaster cards"}; extern cvar_t v_flipped; cvar_t snd_channellayout = {0, "snd_channellayout", "0", "channel layout. Can be 0 (auto - snd_restart needed), 1 (standard layout), or 2 (ALSA layout)"}; @@ -315,16 +315,15 @@ static void S_SoundList_f (void) if (sfx->fetcher != NULL) { unsigned int size; - const snd_format_t* format; size = sfx->memsize; - format = sfx->fetcher->getfmt(sfx); - Con_Printf ("%c%c%c(%2db, %6s) %8i : %s\n", + Con_Printf ("%c%c%c(%5iHz %2db %6s) %8i : %s\n", (sfx->loopstart < sfx->total_length) ? 'L' : ' ', (sfx->flags & SFXFLAG_STREAMED) ? 'S' : ' ', (sfx->flags & SFXFLAG_MENUSOUND) ? 'P' : ' ', - format->width * 8, - (format->channels == 1) ? "mono" : "stereo", + sfx->format.speed, + sfx->format.width * 8, + (sfx->format.channels == 1) ? "mono" : "stereo", size, sfx->name); total += size; @@ -615,6 +614,8 @@ void S_Startup (void) fixed_width = true; } +#if 0 + // LordHavoc: now you can with the resampler... // You can't change sound speed after start time (not yet supported) if (prev_render_format.speed != 0) { @@ -626,6 +627,7 @@ void S_Startup (void) chosen_fmt.speed = prev_render_format.speed; } } +#endif // Sanity checks if (chosen_fmt.speed < SND_MIN_SPEED) @@ -1047,8 +1049,8 @@ void S_FreeSfx (sfx_t *sfx, qboolean force) } // Free it - if (sfx->fetcher != NULL && sfx->fetcher->free != NULL) - sfx->fetcher->free (sfx->fetcher_data); + if (sfx->fetcher != NULL && sfx->fetcher->freesfx != NULL) + sfx->fetcher->freesfx(sfx); Mem_Free (sfx); } @@ -1075,7 +1077,8 @@ void S_ClearUsed (void) channels[i].sfx = ambient_sfxs[i]; channels[i].sfx->flags |= SFXFLAG_MENUSOUND; channels[i].flags |= CHANNELFLAG_FORCELOOP; - channels[i].master_vol = 0; + channels[i].basevolume = 0.0f; + channels[i].basespeed = channels[i].mixspeed = 1.0f; } } @@ -1247,7 +1250,7 @@ channel_t *SND_PickChannel(int entnum, int entchannel) // don't override looped sounds if ((ch->flags & CHANNELFLAG_FORCELOOP) || sfx->loopstart < sfx->total_length) continue; - life_left = sfx->total_length - ch->pos; + life_left = (int)((double)sfx->total_length - ch->position); if (life_left < first_life_left) { @@ -1277,8 +1280,8 @@ void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) { int i; double f; - float angle_side, angle_front, angle_factor; - vec_t dist, mastervol, intensity, vol; + float angle_side, angle_front, angle_factor, mixspeed; + vec_t dist, mastervol, intensity; vec3_t source_vec; // update sound origin if we know about the entity @@ -1304,7 +1307,10 @@ void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) } } - mastervol = ch->master_vol; + mastervol = ch->basevolume; + mixspeed = ch->basespeed; + + // TODO: implement doppler based on origin change relative to viewer and time of recent origin changes // Adjust volume of static sounds if (isstatic) @@ -1392,7 +1398,7 @@ void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) mastervol *= volume.value; // clamp HERE to allow to go at most 10dB past mastervolume (before clamping), when mastervolume < -10dB (so relative volumes don't get too messy) - mastervol = bound(0, mastervol, 655360); + mastervol = bound(0.0f, mastervol, 10.0f); // always apply "master" mastervol *= mastervolume.value; @@ -1404,35 +1410,32 @@ void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) // Replaygain support // Con_DPrintf("Setting volume on ReplayGain-enabled track... %f -> ", fvol); mastervol *= sfx->volume_mult; - if(mastervol * sfx->volume_peak > 65536) - mastervol = 65536 / sfx->volume_peak; + if(mastervol * sfx->volume_peak > 1.0f) + mastervol = 1.0f / sfx->volume_peak; // Con_DPrintf("%f\n", fvol); } // clamp HERE to keep relative volumes of the channels correct - mastervol = bound(0, mastervol, 65536); + mastervol = bound(0.0f, mastervol, 1.0f); + + ch->mixspeed = mixspeed; // anything coming from the view entity will always be full volume // LordHavoc: make sounds with ATTN_NONE have no spatialization - if (ch->entnum == cl.viewentity || ch->dist_mult == 0) + if (ch->entnum == cl.viewentity || ch->distfade == 0) { ch->prologic_invert = 1; if (snd_spatialization_prologic.integer != 0) { - vol = mastervol * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5); - ch->listener_volume[0] = (int)bound(0, vol, 65536); - vol = mastervol * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5); - ch->listener_volume[1] = (int)bound(0, vol, 65536); + ch->volume[0] = mastervol * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5); + ch->volume[1] = mastervol * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5); for (i = 2;i < SND_LISTENERS;i++) - ch->listener_volume[i] = 0; + ch->volume[i] = 0; } else { for (i = 0;i < SND_LISTENERS;i++) - { - vol = mastervol * snd_speakerlayout.listeners[i].ambientvolume; - ch->listener_volume[i] = (int)bound(0, vol, 65536); - } + ch->volume[i] = mastervol * snd_speakerlayout.listeners[i].ambientvolume; } } else @@ -1440,7 +1443,7 @@ void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) // calculate stereo seperation and distance attenuation VectorSubtract(listener_origin, ch->origin, source_vec); dist = VectorLength(source_vec); - intensity = mastervol * (1.0 - dist * ch->dist_mult); + intensity = mastervol * (1.0f - dist * ch->distfade); if (intensity > 0) { qboolean occluded = false; @@ -1460,13 +1463,13 @@ void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) occluded = true; } if(occluded) - intensity *= 0.5; + intensity *= 0.5f; ch->prologic_invert = 1; if (snd_spatialization_prologic.integer != 0) { if (dist == 0) - angle_factor = 0.5; + angle_factor = 0.5f; else { Matrix4x4_Transform(&listener_basematrix, ch->origin, source_vec); @@ -1515,12 +1518,10 @@ void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) //angle_factor is between 0 and 1 and represents the angle range from the front left to the center to the front right speaker } - vol = intensity * sqrt(angle_factor); - ch->listener_volume[0] = (int)bound(0, vol, 65536); - vol = intensity * sqrt(1 - angle_factor); - ch->listener_volume[1] = (int)bound(0, vol, 65536); + ch->volume[0] = intensity * sqrt(angle_factor); + ch->volume[1] = intensity * sqrt(1 - angle_factor); for (i = 2;i < SND_LISTENERS;i++) - ch->listener_volume[i] = 0; + ch->volume[i] = 0; } else { @@ -1552,15 +1553,13 @@ void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) break; } - vol = intensity * max(0, source_vec[0] * snd_speakerlayout.listeners[i].dotscale + snd_speakerlayout.listeners[i].dotbias); - - ch->listener_volume[i] = (int)bound(0, vol, 65536); + ch->volume[i] = intensity * max(0, source_vec[0] * snd_speakerlayout.listeners[i].dotscale + snd_speakerlayout.listeners[i].dotbias); } } } else for (i = 0;i < SND_LISTENERS;i++) - ch->listener_volume[i] = 0; + ch->volume[i] = 0; } } void SND_Spatialize(channel_t *ch, qboolean isstatic) @@ -1574,7 +1573,7 @@ void SND_Spatialize(channel_t *ch, qboolean isstatic) // Start a sound effect // ======================================================================= -void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, vec3_t origin, float fvol, float attenuation, qboolean isstatic, int entnum, int entchannel, int startpos) +void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, vec3_t origin, float fvol, float attenuation, qboolean isstatic, int entnum, int entchannel, int startpos, float fspeed) { if (!sfx) { @@ -1606,7 +1605,7 @@ void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, memset (target_chan, 0, sizeof (*target_chan)); VectorCopy (origin, target_chan->origin); target_chan->flags = flags; - target_chan->pos = startpos; // start of the sound + target_chan->position = startpos; // start of the sound target_chan->entnum = entnum; target_chan->entchannel = entchannel; @@ -1615,13 +1614,14 @@ void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, { if (sfx->loopstart >= sfx->total_length && (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEWORLD)) Con_DPrintf("Quake compatibility warning: Static sound \"%s\" is not looped\n", sfx->name); - target_chan->dist_mult = attenuation / (64.0f * snd_soundradius.value); + target_chan->distfade = attenuation / (64.0f * snd_soundradius.value); } else - target_chan->dist_mult = attenuation / snd_soundradius.value; + target_chan->distfade = attenuation / snd_soundradius.value; // set the listener volumes S_SetChannelVolume(target_chan - channels, fvol); + S_SetChannelSpeed(target_chan - channels, fspeed); SND_Spatialize_WithSfx (target_chan, isstatic, sfx); // finally, set the sfx pointer, so the channel becomes valid for playback @@ -1630,7 +1630,7 @@ void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, } -int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags) +int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags, float fspeed) { channel_t *target_chan, *check, *ch; int ch_idx, startpos; @@ -1648,7 +1648,8 @@ int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, ve if (ch->entnum == entnum && ch->entchannel == entchannel) { S_SetChannelVolume(ch_idx, fvol); - ch->dist_mult = attenuation / snd_soundradius.value; + S_SetChannelSpeed(ch_idx, fspeed); + ch->distfade = attenuation / snd_soundradius.value; SND_Spatialize(ch, false); return ch_idx; } @@ -1667,14 +1668,14 @@ int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, ve // if an identical sound has also been started this frame, offset the pos // a bit to keep it from just making the first one louder check = &channels[NUM_AMBIENTS]; - startpos = (int)(startposition * S_GetSoundRate()); + startpos = (int)(startposition * sfx->format.speed); if (startpos == 0) { for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++, check++) { if (check == target_chan) continue; - if (check->sfx == sfx && check->pos == 0) + if (check->sfx == sfx && check->position == 0) { // use negative pos offset to delay this sound effect startpos = (int)lhrandom(0, -0.1 * snd_renderbuffer->format.speed); @@ -1683,14 +1684,14 @@ int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, ve } } - S_PlaySfxOnChannel (sfx, target_chan, flags, origin, fvol, attenuation, false, entnum, entchannel, startpos); + S_PlaySfxOnChannel (sfx, target_chan, flags, origin, fvol, attenuation, false, entnum, entchannel, startpos, fspeed); return (target_chan - channels); } int S_StartSound_StartPosition (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition) { - return S_StartSound_StartPosition_Flags(entnum, entchannel, sfx, origin, fvol, attenuation, startposition, CHANNELFLAG_NONE); + return S_StartSound_StartPosition_Flags(entnum, entchannel, sfx, origin, fvol, attenuation, startposition, CHANNELFLAG_NONE, 1.0f); } int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation) @@ -1717,13 +1718,8 @@ void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean frees sfx = ch->sfx; if (ch->sfx != NULL) { - if (sfx->fetcher != NULL) - { - snd_fetcher_endsb_t fetcher_endsb = sfx->fetcher->endsb; - if (fetcher_endsb != NULL) - fetcher_endsb (ch->fetcher_data); - } - + if (sfx->fetcher != NULL && sfx->fetcher->stopchannel != NULL) + sfx->fetcher->stopchannel(ch); ch->fetcher_data = NULL; ch->sfx = NULL; } @@ -1815,24 +1811,29 @@ void S_PauseGameSounds (qboolean toggle) void S_SetChannelVolume(unsigned int ch_ind, float fvol) { - channels[ch_ind].master_vol = (int)(fvol * 65536.0f); + channels[ch_ind].basevolume = fvol; +} + +void S_SetChannelSpeed(unsigned int ch_ind, float fspeed) +{ + channels[ch_ind].basespeed = fspeed; } float S_GetChannelPosition (unsigned int ch_ind) { // note: this is NOT accurate yet - int s; + double s; channel_t *ch = &channels[ch_ind]; sfx_t *sfx = ch->sfx; if (!sfx) return -1; - s = ch->pos; + s = ch->position / sfx->format.speed; /* if(!snd_usethreadedmixing) - s += _snd_mixahead.value * S_GetSoundRate(); + s += _snd_mixahead.value; */ - return (s % sfx->total_length) / (float) S_GetSoundRate(); + return (float)s; } float S_GetEntChannelPosition(int entnum, int entchannel) @@ -1873,7 +1874,7 @@ void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) } target_chan = &channels[total_channels++]; - S_PlaySfxOnChannel (sfx, target_chan, CHANNELFLAG_FORCELOOP, origin, fvol, attenuation, true, 0, 0, 0); + S_PlaySfxOnChannel (sfx, target_chan, CHANNELFLAG_FORCELOOP, origin, fvol, attenuation, true, 0, 0, 0, 1.0f); } @@ -1885,7 +1886,8 @@ S_UpdateAmbientSounds void S_UpdateAmbientSounds (void) { int i; - int vol; + float vol; + float fade = (float)max(0.0, cl.time - cl.oldtime) * ambient_fade.value / 256.0f; int ambient_channel; channel_t *chan; unsigned char ambientlevels[NUM_AMBIENTS]; @@ -1903,40 +1905,36 @@ void S_UpdateAmbientSounds (void) if (sfx == NULL || sfx->fetcher == NULL) continue; - vol = (int)ambientlevels[ambient_channel]; - if (vol < 8) - vol = 0; - vol *= 256; + i = ambientlevels[ambient_channel]; + if (i < 8) + i = 0; + vol = i * (1.0f / 256.0f); // Don't adjust volume too fast - // FIXME: this rounds off to an int each frame, meaning there is little to no fade at extremely high framerates! - if (cl.time > cl.oldtime) + if (chan->basevolume < vol) { - if (chan->master_vol < vol) - { - chan->master_vol += (int)((cl.time - cl.oldtime) * 256.0 * ambient_fade.value); - if (chan->master_vol > vol) - chan->master_vol = vol; - } - else if (chan->master_vol > vol) - { - chan->master_vol -= (int)((cl.time - cl.oldtime) * 256.0 * ambient_fade.value); - if (chan->master_vol < vol) - chan->master_vol = vol; - } + chan->basevolume += fade; + if (chan->basevolume > vol) + chan->basevolume = vol; + } + else if (chan->basevolume > vol) + { + chan->basevolume -= fade; + if (chan->basevolume < vol) + chan->basevolume = vol; } if (snd_spatialization_prologic.integer != 0) { - chan->listener_volume[0] = (int)bound(0, chan->master_vol * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5), 65536); - chan->listener_volume[1] = (int)bound(0, chan->master_vol * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5), 65536); + chan->volume[0] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5); + chan->volume[1] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5); for (i = 2;i < SND_LISTENERS;i++) - chan->listener_volume[i] = 0; + chan->volume[i] = 0.0f; } else { for (i = 0;i < SND_LISTENERS;i++) - chan->listener_volume[i] = (int)bound(0, chan->master_vol * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[i].ambientvolume, 65536); + chan->volume[i] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[i].ambientvolume; } } } @@ -2211,7 +2209,7 @@ void S_Update(const matrix4x4_t *listenermatrix) { // no need to merge silent channels for (j = 0;j < SND_LISTENERS;j++) - if (ch->listener_volume[j]) + if (ch->volume[j]) break; if (j == SND_LISTENERS) continue; @@ -2233,13 +2231,13 @@ void S_Update(const matrix4x4_t *listenermatrix) { for (j = 0;j < SND_LISTENERS;j++) { - combine->listener_volume[j] = bound(0, combine->listener_volume[j] + ch->listener_volume[j], 65536); - ch->listener_volume[j] = 0; + combine->volume[j] += ch->volume[j]; + ch->volume[j] = 0; } } } for (k = 0;k < SND_LISTENERS;k++) - if (ch->listener_volume[k]) + if (ch->volume[k]) break; if (k < SND_LISTENERS) cls.soundstats.mixedsounds++; diff --git a/snd_main.h b/snd_main.h index 59838ec6..527ce49f 100644 --- a/snd_main.h +++ b/snd_main.h @@ -64,6 +64,7 @@ struct sfx_s sfx_t *next; size_t memsize; // total memory used (including sfx_t and fetcher data) + snd_format_t format; // format describing the audio data that fetcher->getsamplesfloat will return unsigned int flags; // cf SFXFLAG_* defines unsigned int loopstart; // in sample frames. equals total_length if not looped unsigned int total_length; // in sample frames @@ -79,31 +80,39 @@ struct sfx_s typedef struct channel_s { - int listener_volume [SND_LISTENERS]; // 0-65536 volume per speaker - int master_vol; // 0-65536 master volume + // provided sound information sfx_t *sfx; // pointer to sound sample being used + float basevolume; // 0-1 master volume unsigned int flags; // cf CHANNELFLAG_* defines - int pos; // sample position in sfx, negative values delay the start of the sound playback - int entnum; // to allow overriding a specific sound - int entchannel; + int entnum; // makes sound follow entity origin (allows replacing interrupting existing sound on same id) + int entchannel; // which channel id on the entity vec3_t origin; // origin of sound effect - vec_t dist_mult; // distance multiplier (attenuation/clipK) + vec_t distfade; // distance multiplier (attenuation/clipK) void *fetcher_data; // Per-channel data for the sound fetching function int prologic_invert;// whether a sound is played on the surround channels in prologic + float basespeed; // playback rate multiplier for pitch variation + + // these are often updated while mixer is running, glitching should be minimized (mismatched channel volumes from spatialization is okay) + // spatialized playback speed (speed * doppler ratio) + float mixspeed; + // spatialized volume per speaker (mastervol * distanceattenuation * channelvolume cvars) + float volume[SND_LISTENERS]; + + // updated ONLY by mixer + // position in sfx, starts at 0, loops or stops at sfx->total_length + double position; } channel_t; // Sound fetching functions // "start" is both an input and output parameter: it returns the actual start time of the sound buffer -typedef const snd_buffer_t* (*snd_fetcher_getsb_t) (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes); -typedef void (*snd_fetcher_endsb_t) (void *chfetcherdata); -typedef void (*snd_fetcher_free_t) (void *sfxfetcherdata); -typedef const snd_format_t* (*snd_fetcher_getfmt_t) (sfx_t* sfx); +typedef void (*snd_fetcher_getsamplesfloat_t) (channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat); +typedef void (*snd_fetcher_stopchannel_t) (channel_t *ch); +typedef void (*snd_fetcher_freesfx_t) (sfx_t *sfx); struct snd_fetcher_s { - snd_fetcher_getsb_t getsb; - snd_fetcher_endsb_t endsb; - snd_fetcher_free_t free; - snd_fetcher_getfmt_t getfmt; + snd_fetcher_getsamplesfloat_t getsamplesfloat; + snd_fetcher_stopchannel_t stopchannel; + snd_fetcher_freesfx_t freesfx; }; extern unsigned int total_channels; @@ -133,13 +142,7 @@ extern mempool_t *snd_mempool; extern qboolean simsound; -#define STREAM_BUFFER_DURATION 0.3f // in seconds -#define STREAM_BUFFER_FILL 0.2f // in seconds -#define STREAM_BUFFER_SIZE(format_ptr) ((int)ceil (STREAM_BUFFER_DURATION * (format_ptr)->speed) * (format_ptr)->width * (format_ptr)->channels) - -// We work with 1 sec sequences, so this buffer must be able to contain -// 1 sec of sound of the highest quality (48 KHz, 16 bit samples, stereo) -extern unsigned char resampling_buffer [48000 * 2 * 2]; +#define STREAM_BUFFERSIZE 16384 // in sampleframes // ==================================================================== @@ -187,7 +190,7 @@ void SndSys_SendKeyEvents(void); // exported for capturevideo so ogg can see all channels typedef struct portable_samplepair_s { - int sample[SND_LISTENERS]; + float sample[SND_LISTENERS]; } portable_sampleframe_t; typedef struct listener_s diff --git a/snd_mem.c b/snd_mem.c index 11b0a9d9..635bdd3f 100644 --- a/snd_mem.c +++ b/snd_mem.c @@ -26,8 +26,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "snd_wav.h" #include "snd_modplug.h" -unsigned char resampling_buffer [48000 * 2 * 2]; - /* ==================== diff --git a/snd_mix.c b/snd_mix.c index 6bec880e..e01ca0ee 100644 --- a/snd_mix.c +++ b/snd_mix.c @@ -28,7 +28,7 @@ static portable_sampleframe_t paintbuffer_unswapped[PAINTBUFFER_SIZE]; extern speakerlayout_t snd_speakerlayout; // for querying the listeners extern void SCR_CaptureVideo_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length); -static void S_CaptureAVISound(size_t length) +static void S_CaptureAVISound(const portable_sampleframe_t *paintbuffer, size_t length) { size_t i; unsigned int j; @@ -50,6 +50,8 @@ static void S_CaptureAVISound(size_t length) static void S_ConvertPaintBuffer(const portable_sampleframe_t *painted_ptr, void *rb_ptr, int nbframes, int width, int channels) { int i, val; + // FIXME: add 24bit and 32bit float formats + // FIXME: optimize with SSE intrinsics? if (width == 2) // 16bit { short *snd_out = (short*)rb_ptr; @@ -57,52 +59,51 @@ static void S_ConvertPaintBuffer(const portable_sampleframe_t *painted_ptr, void { for (i = 0;i < nbframes;i++, painted_ptr++) { - *snd_out++ = bound(-32768, painted_ptr->sample[0], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[1], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[2], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[3], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[4], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[5], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[6], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[7], 32767); + val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[4] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[5] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[6] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[7] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); } } else if (channels == 6) // 5.1 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { - *snd_out++ = bound(-32768, painted_ptr->sample[0], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[1], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[2], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[3], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[4], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[5], 32767); + val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[4] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[5] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); } } else if (channels == 4) // 4.0 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { - *snd_out++ = bound(-32768, painted_ptr->sample[0], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[1], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[2], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[3], 32767); + val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); } } else if (channels == 2) // 2.0 stereo { for (i = 0; i < nbframes; i++, painted_ptr++) { - *snd_out++ = bound(-32768, painted_ptr->sample[0], 32767); - *snd_out++ = bound(-32768, painted_ptr->sample[1], 32767); + val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); } } else if (channels == 1) // 1.0 mono { for (i = 0; i < nbframes; i++, painted_ptr++) { - val = (painted_ptr->sample[0] + painted_ptr->sample[1]) >> 1; - *snd_out++ = bound(-32768, val, 32767); + val = (int)((painted_ptr->sample[0] + painted_ptr->sample[1]) * 16384.0f);*snd_out++ = bound(-32768, val, 32767); } } @@ -117,52 +118,51 @@ static void S_ConvertPaintBuffer(const portable_sampleframe_t *painted_ptr, void { for (i = 0; i < nbframes; i++, painted_ptr++) { - val = (painted_ptr->sample[0] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[1] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[2] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[3] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[4] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[5] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[6] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[7] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[4] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[5] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[6] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[7] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); } } else if (channels == 6) // 5.1 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { - val = (painted_ptr->sample[0] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[1] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[2] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[3] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[4] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[5] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[4] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[5] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); } } else if (channels == 4) // 4.0 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { - val = (painted_ptr->sample[0] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[1] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[2] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[3] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); } } else if (channels == 2) // 2.0 stereo { for (i = 0; i < nbframes; i++, painted_ptr++) { - val = (painted_ptr->sample[0] >> 8) + 128; *snd_out++ = bound(0, val, 255); - val = (painted_ptr->sample[1] >> 8) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); } } else if (channels == 1) // 1.0 mono { for (i = 0;i < nbframes;i++, painted_ptr++) { - val = ((painted_ptr->sample[0] + painted_ptr->sample[1]) >> 9) + 128; - *snd_out++ = bound(0, val, 255); + val = (int)((painted_ptr->sample[0] + painted_ptr->sample[1]) * 64.0f) + 128; *snd_out++ = bound(0, val, 255); } } @@ -181,326 +181,52 @@ CHANNEL MIXING =============================================================================== */ -static qboolean SND_PaintChannel (channel_t *ch, portable_sampleframe_t *paint, unsigned int count) -{ - int vol[SND_LISTENERS]; - const snd_buffer_t *sb; - unsigned int i, sb_offset; - sfx_t *sfx; - - sfx = ch->sfx; // fetch the volatile variable - if (!sfx) // given that this is called by the mixer thread, this never happens, but... - return false; - - // move to the stack (do we need to?) - for (i = 0;i < SND_LISTENERS;i++) - vol[i] = ch->listener_volume[i]; - - // if volumes are all zero, just return - for (i = 0;i < SND_LISTENERS;i++) - if (vol[i]) - break; - if (i == SND_LISTENERS) - return false; - - sb_offset = ch->pos; - sb = sfx->fetcher->getsb (sfx->fetcher_data, &ch->fetcher_data, &sb_offset, count); - if (sb == NULL) - { - Con_DPrintf("SND_PaintChannel: ERROR: can't get sound buffer from sfx \"%s\"\n", - sfx->name); // , count); // or add this? FIXME - return false; - } - else - { -#if SND_LISTENERS != 8 -# error the following code only supports up to 8 channels, update it -#endif - if (sb->format.width == 1) - { - const signed char *samples = (signed char*)sb->samples + (ch->pos - sb_offset) * sb->format.channels; - - // Stereo sound support - if (sb->format.channels == 2) - { - if (vol[6] + vol[7] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] += (samples[1] * vol[1]) >> 8; - paint[i].sample[2] += (samples[0] * vol[2]) >> 8; - paint[i].sample[3] += (samples[1] * vol[3]) >> 8; - paint[i].sample[4] += ((samples[0] + samples[1]) * vol[4]) >> 9; - paint[i].sample[5] += ((samples[0] + samples[1]) * vol[5]) >> 9; - paint[i].sample[6] += (samples[0] * vol[6]) >> 8; - paint[i].sample[7] += (samples[1] * vol[7]) >> 8; - samples += 2; - } - } - else if (vol[4] + vol[5] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] += (samples[1] * vol[1]) >> 8; - paint[i].sample[2] += (samples[0] * vol[2]) >> 8; - paint[i].sample[3] += (samples[1] * vol[3]) >> 8; - paint[i].sample[4] += ((samples[0] + samples[1]) * vol[4]) >> 9; - paint[i].sample[5] += ((samples[0] + samples[1]) * vol[5]) >> 9; - samples += 2; - } - } - else if (vol[2] + vol[3] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] += (samples[1] * vol[1]) >> 8; - paint[i].sample[2] += (samples[0] * vol[2]) >> 8; - paint[i].sample[3] += (samples[1] * vol[3]) >> 8; - samples += 2; - } - } - else if (vol[0] + vol[1] > 0 && ch->prologic_invert == -1) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] -= (samples[1] * vol[1]) >> 8; - samples += 2; - } - } - else if (vol[0] + vol[1] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] += (samples[1] * vol[1]) >> 8; - samples += 2; - } - } - } - else if (sb->format.channels == 1) - { - if (vol[6] + vol[7] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] += (samples[0] * vol[1]) >> 8; - paint[i].sample[2] += (samples[0] * vol[2]) >> 8; - paint[i].sample[3] += (samples[0] * vol[3]) >> 8; - paint[i].sample[4] += (samples[0] * vol[4]) >> 8; - paint[i].sample[5] += (samples[0] * vol[5]) >> 8; - paint[i].sample[6] += (samples[0] * vol[6]) >> 8; - paint[i].sample[7] += (samples[0] * vol[7]) >> 8; - samples += 1; - } - } - else if (vol[4] + vol[5] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] += (samples[0] * vol[1]) >> 8; - paint[i].sample[2] += (samples[0] * vol[2]) >> 8; - paint[i].sample[3] += (samples[0] * vol[3]) >> 8; - paint[i].sample[4] += (samples[0] * vol[4]) >> 8; - paint[i].sample[5] += (samples[0] * vol[5]) >> 8; - samples += 1; - } - } - else if (vol[2] + vol[3] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] += (samples[0] * vol[1]) >> 8; - paint[i].sample[2] += (samples[0] * vol[2]) >> 8; - paint[i].sample[3] += (samples[0] * vol[3]) >> 8; - samples += 1; - } - } - else if (vol[0] + vol[1] > 0 && ch->prologic_invert == -1) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] -= (samples[0] * vol[1]) >> 8; - samples += 1; - } - } - else if (vol[0] + vol[1] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 8; - paint[i].sample[1] += (samples[0] * vol[1]) >> 8; - samples += 1; - } - } - } - else - return false; // unsupported number of channels in sound - } - else if (sb->format.width == 2) - { - const signed short *samples = (signed short*)sb->samples + (ch->pos - sb_offset) * sb->format.channels; - - // Stereo sound support - if (sb->format.channels == 2) - { - if (vol[6] + vol[7] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] += (samples[1] * vol[1]) >> 16; - paint[i].sample[2] += (samples[0] * vol[2]) >> 16; - paint[i].sample[3] += (samples[1] * vol[3]) >> 16; - paint[i].sample[4] += ((samples[0] + samples[1]) * vol[4]) >> 17; - paint[i].sample[5] += ((samples[0] + samples[1]) * vol[5]) >> 17; - paint[i].sample[6] += (samples[0] * vol[6]) >> 16; - paint[i].sample[7] += (samples[1] * vol[7]) >> 16; - samples += 2; - } - } - else if (vol[4] + vol[5] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] += (samples[1] * vol[1]) >> 16; - paint[i].sample[2] += (samples[0] * vol[2]) >> 16; - paint[i].sample[3] += (samples[1] * vol[3]) >> 16; - paint[i].sample[4] += ((samples[0] + samples[1]) * vol[4]) >> 17; - paint[i].sample[5] += ((samples[0] + samples[1]) * vol[5]) >> 17; - samples += 2; - } - } - else if (vol[2] + vol[3] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] += (samples[1] * vol[1]) >> 16; - paint[i].sample[2] += (samples[0] * vol[2]) >> 16; - paint[i].sample[3] += (samples[1] * vol[3]) >> 16; - samples += 2; - } - } - else if (vol[0] + vol[1] > 0 && ch->prologic_invert == -1) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] -= (samples[1] * vol[1]) >> 16; - samples += 2; - } - } - else if (vol[0] + vol[1] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] += (samples[1] * vol[1]) >> 16; - samples += 2; - } - } - } - else if (sb->format.channels == 1) - { - if (vol[6] + vol[7] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] += (samples[0] * vol[1]) >> 16; - paint[i].sample[2] += (samples[0] * vol[2]) >> 16; - paint[i].sample[3] += (samples[0] * vol[3]) >> 16; - paint[i].sample[4] += (samples[0] * vol[4]) >> 16; - paint[i].sample[5] += (samples[0] * vol[5]) >> 16; - paint[i].sample[6] += (samples[0] * vol[6]) >> 16; - paint[i].sample[7] += (samples[0] * vol[7]) >> 16; - samples += 1; - } - } - else if (vol[4] + vol[5] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] += (samples[0] * vol[1]) >> 16; - paint[i].sample[2] += (samples[0] * vol[2]) >> 16; - paint[i].sample[3] += (samples[0] * vol[3]) >> 16; - paint[i].sample[4] += (samples[0] * vol[4]) >> 16; - paint[i].sample[5] += (samples[0] * vol[5]) >> 16; - samples += 1; - } - } - else if (vol[2] + vol[3] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] += (samples[0] * vol[1]) >> 16; - paint[i].sample[2] += (samples[0] * vol[2]) >> 16; - paint[i].sample[3] += (samples[0] * vol[3]) >> 16; - samples += 1; - } - } - else if (vol[0] + vol[1] > 0 && ch->prologic_invert == -1) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] -= (samples[0] * vol[1]) >> 16; - samples += 1; - } - } - else if (vol[0] + vol[1] > 0) - { - for (i = 0;i < count;i++) - { - paint[i].sample[0] += (samples[0] * vol[0]) >> 16; - paint[i].sample[1] += (samples[0] * vol[1]) >> 16; - samples += 1; - } - } - } - else - return false; // unsupported number of channels in sound - } - } - return true; -} - void S_MixToBuffer(void *stream, unsigned int bufferframes) { - unsigned int i; + int channelindex; channel_t *ch; - unsigned int frames; + int totalmixframes; unsigned char *outbytes = (unsigned char *) stream; + sfx_t *sfx; + portable_sampleframe_t *paint; + int wantframes; + int i; + int count; + int fetched; + int fetch; + int istartframe; + int iendframe; + int ilengthframes; + int totallength; + int loopstart; + int indexfrac; + int indexfracstep; + const int fetchsampleframesmax = 1024; + float fetchsampleframes[1024*2]; + const float *fetchsampleframe; + float vol[SND_LISTENERS]; + float lerp[2]; + float sample[3]; + double posd; + double speedd; + float sum; + qboolean looping; + qboolean silent; // mix as many times as needed to fill the requested buffer while (bufferframes) { // limit to the size of the paint buffer - frames = min(bufferframes, PAINTBUFFER_SIZE); + totalmixframes = min(bufferframes, PAINTBUFFER_SIZE); // clear the paint buffer - memset (paintbuffer, 0, frames * sizeof (paintbuffer[0])); + memset(paintbuffer, 0, totalmixframes * sizeof(paintbuffer[0])); // paint in the channels. // channels with zero volumes still advance in time but don't paint. ch = channels; - for (i = 0; i < total_channels ; i++, ch++) + for (channelindex = 0;channelindex < (int)total_channels;channelindex++, ch++) { - sfx_t *sfx; - int ltime; - int count; - sfx = ch->sfx; if (sfx == NULL) continue; @@ -510,53 +236,200 @@ void S_MixToBuffer(void *stream, unsigned int bufferframes) continue; if (!sfx->total_length) continue; - if (sfx->total_length > 1<<30) - Sys_Error("S_MixToBuffer: sfx corrupt\n"); - ltime = 0; - if (ch->pos < 0) + // copy the channel information to the stack for reference, otherwise the + // values might change during a mix if the spatializer is updating them + // (note: this still may get some old and some new values!) + posd = ch->position; + speedd = ch->mixspeed * sfx->format.speed / snd_renderbuffer->format.speed; + for (i = 0;i < SND_LISTENERS;i++) + vol[i] = ch->volume[i]; + + // check total volume level, because we can skip some code on silent sounds but other code must still run (position updates mainly) + for (i = 0;i < SND_LISTENERS;i++) + sum += vol[i]*vol[i]; + silent = sum < 0.001f; + + // when doing prologic mixing, some channels invert one side + if (ch->prologic_invert == -1) + vol[1] *= -1.0f; + + // get some sfx info in a consistent form + totallength = sfx->total_length; + loopstart = (int)sfx->loopstart < totallength ? (int)sfx->loopstart : ((ch->flags & CHANNELFLAG_FORCELOOP) ? 0 : totallength); + looping = loopstart < totallength; + + // do the actual paint now (may skip work if silent) + paint = paintbuffer; + wantframes = totalmixframes; + while (wantframes > 0) { - count = -ch->pos; - count = min(count, (int)frames - ltime); - ch->pos += count; - ltime += count; - } + // mix full output length (if possible) + count = wantframes; + if (posd < 0) + { + // for a delayed sound we have to eat into the delay first + count = (int)-posd; + if (count > wantframes) + count = wantframes; + posd += count; + wantframes -= count; + continue; + } - while (ltime < (int)frames) - { - // paint up to end of buffer or of input, whichever is lower - count = sfx->total_length - ch->pos; - count = bound(0, count, (int)frames - ltime); - // mix the remaining samples - if (count) + // get fetch size + istartframe = (int)floor(posd); + iendframe = (int)floor(posd + count * speedd); + ilengthframes = iendframe + 2 - istartframe; + // don't overflow fetch buffer + while (ilengthframes > fetchsampleframesmax) { - SND_PaintChannel (ch, paintbuffer + ltime, count); - ch->pos += count; - ltime += count; + count /= 2; + iendframe = (int)floor(posd + count * speedd); + ilengthframes = iendframe + 2 - istartframe; + if (count < 2) + ilengthframes = 2; } - // if at end of sfx, loop or stop the channel - else + + // zero whole fetch buffer for safety + // (floating point noise from uninitialized memory = HORRIBLE) + // otherwise we would only need to clear the excess + if (!silent) + memset(fetchsampleframes, 0, ilengthframes*sfx->format.channels*sizeof(fetchsampleframes[0])); + + // if looping, do multiple fetches + fetched = 0; + for (;;) { - if (sfx->loopstart < sfx->total_length) - ch->pos = sfx->loopstart; - else if (ch->flags & CHANNELFLAG_FORCELOOP) - ch->pos = 0; - else + fetch = min(ilengthframes, totallength - istartframe); + if (fetch > 0) + { + if (!silent) + sfx->fetcher->getsamplesfloat(ch, sfx, istartframe, fetch, fetchsampleframes + fetched*sfx->format.channels); + istartframe += fetch; + fetched += fetch; + } + if (istartframe == totallength && looping && fetched < ilengthframes) { - S_StopChannel (ch - channels, false, false); + // loop and fetch some more + posd += loopstart - totallength; + istartframe = loopstart; + } + else break; + } + + // set up our fixedpoint resampling variables (float to int conversions are expensive so do not do one per sampleframe) + fetchsampleframe = fetchsampleframes; + indexfrac = (int)floor((posd - floor(posd)) * 65536.0); + indexfracstep = (int)floor(speedd * 65536.0); + if (!silent) + { + if (sfx->format.channels == 2) + { + // music is stereo +#if SND_LISTENERS != 8 +#error the following code only supports up to 8 channels, update it +#endif + if (snd_speakerlayout.channels > 2) + { + // surround mixing + for (i = 0;i < count;i++, paint++) + { + lerp[1] = indexfrac * (1.0f / 65536.0f); + lerp[0] = 1.0f - lerp[1]; + sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[2] * lerp[1]; + sample[1] = fetchsampleframe[1] * lerp[0] + fetchsampleframe[3] * lerp[1]; + sample[2] = (sample[0] + sample[1]) * 0.5f; + paint->sample[0] += sample[0] * vol[0]; + paint->sample[1] += sample[1] * vol[1]; + paint->sample[2] += sample[0] * vol[2]; + paint->sample[3] += sample[1] * vol[3]; + paint->sample[4] += sample[2] * vol[4]; + paint->sample[5] += sample[2] * vol[5]; + paint->sample[6] += sample[0] * vol[6]; + paint->sample[7] += sample[1] * vol[7]; + indexfrac += indexfracstep; + fetchsampleframe += 2 * (indexfrac >> 16); + indexfrac &= 0xFFFF; + } + } + else + { + // stereo mixing + for (i = 0;i < count;i++, paint++) + { + lerp[1] = indexfrac * (1.0f / 65536.0f); + lerp[0] = 1.0f - lerp[1]; + sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[2] * lerp[1]; + sample[1] = fetchsampleframe[1] * lerp[0] + fetchsampleframe[3] * lerp[1]; + paint->sample[0] += sample[0] * vol[0]; + paint->sample[1] += sample[1] * vol[1]; + indexfrac += indexfracstep; + fetchsampleframe += 2 * (indexfrac >> 16); + indexfrac &= 0xFFFF; + } + } + } + else if (sfx->format.channels == 1) + { + // most sounds are mono +#if SND_LISTENERS != 8 +#error the following code only supports up to 8 channels, update it +#endif + if (snd_speakerlayout.channels > 2) + { + // surround mixing + for (i = 0;i < count;i++, paint++) + { + lerp[1] = indexfrac * (1.0f / 65536.0f); + lerp[0] = 1.0f - lerp[1]; + sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[1] * lerp[1]; + paint->sample[0] += sample[0] * vol[0]; + paint->sample[1] += sample[0] * vol[1]; + paint->sample[2] += sample[0] * vol[2]; + paint->sample[3] += sample[0] * vol[3]; + paint->sample[4] += sample[0] * vol[4]; + paint->sample[5] += sample[0] * vol[5]; + paint->sample[6] += sample[0] * vol[6]; + paint->sample[7] += sample[0] * vol[7]; + indexfrac += indexfracstep; + fetchsampleframe += (indexfrac >> 16); + indexfrac &= 0xFFFF; + } + } + else + { + // stereo mixing + for (i = 0;i < count;i++, paint++) + { + lerp[1] = indexfrac * (1.0f / 65536.0f); + lerp[0] = 1.0f - lerp[1]; + sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[1] * lerp[1]; + paint->sample[0] += sample[0] * vol[0]; + paint->sample[1] += sample[0] * vol[1]; + indexfrac += indexfracstep; + fetchsampleframe += (indexfrac >> 16); + indexfrac &= 0xFFFF; + } + } } } + posd += count * speedd; + wantframes -= count; } + ch->position = posd; + if (!looping && istartframe == totallength) + S_StopChannel(ch - channels, false, false); } if (!snd_usethreadedmixing) - S_CaptureAVISound(frames); + S_CaptureAVISound(paintbuffer, totalmixframes); - S_ConvertPaintBuffer(paintbuffer, outbytes, frames, snd_renderbuffer->format.width, snd_renderbuffer->format.channels); + S_ConvertPaintBuffer(paintbuffer, outbytes, totalmixframes, snd_renderbuffer->format.width, snd_renderbuffer->format.channels); // advance the output pointer - outbytes += frames * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; - bufferframes -= frames; + outbytes += totalmixframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + bufferframes -= totalmixframes; } } diff --git a/snd_modplug.c b/snd_modplug.c index a8fad86b..6dc3fce2 100644 --- a/snd_modplug.c +++ b/snd_modplug.c @@ -205,57 +205,46 @@ typedef struct { unsigned char *file; size_t filesize; - snd_format_t format; - unsigned int total_length; - char name[128]; - sfx_t *sfx; } modplug_stream_persfx_t; // Per-channel data structure typedef struct { ModPlugFile *mf; - unsigned int sb_offset; int bs; - snd_buffer_t sb; // must be at the end due to its dynamically allocated size + int buffer_firstframe; + int buffer_numframes; + unsigned char buffer[STREAM_BUFFERSIZE*4]; } modplug_stream_perchannel_t; /* ==================== -ModPlug_FetchSound +ModPlug_GetSamplesFloat ==================== */ -static const snd_buffer_t* ModPlug_FetchSound (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes) +static void ModPlug_GetSamplesFloat(channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) { - modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)*chfetcherpointer; - modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfxfetcher; - snd_buffer_t* sb; + modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)ch->fetcher_data; + modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data; int newlength, done, ret; - unsigned int real_start; - unsigned int factor; + int f = sfx->format.width * sfx->format.channels; // bytes per frame + short *buf; + int i, len; // If there's no fetcher structure attached to the channel yet if (per_ch == NULL) { - size_t buff_len, memsize; - snd_format_t sb_format; - - sb_format.speed = snd_renderbuffer->format.speed; - sb_format.width = per_sfx->format.width; - sb_format.channels = per_sfx->format.channels; - - buff_len = STREAM_BUFFER_SIZE(&sb_format); - memsize = sizeof (*per_ch) - sizeof (per_ch->sb.samples) + buff_len; - per_ch = (modplug_stream_perchannel_t *)Mem_Alloc (snd_mempool, memsize); + per_ch = (modplug_stream_perchannel_t *)Mem_Alloc(snd_mempool, sizeof(*per_ch)); // Open it with the modplugFile API per_ch->mf = qModPlug_Load(per_sfx->file, per_sfx->filesize); if (!per_ch->mf) { - Con_Printf("error while reading ModPlug stream \"%s\"\n", per_sfx->name); - Mem_Free (per_ch); - return NULL; + // we can't call Con_Printf here, not thread safe +// Con_Printf("error while reading ModPlug stream \"%s\"\n", per_sfx->name); + Mem_Free(per_ch); + return; } #ifndef SND_MODPLUG_STATIC @@ -265,130 +254,88 @@ static const snd_buffer_t* ModPlug_FetchSound (void *sfxfetcher, void **chfetche per_ch->bs = 0; - per_ch->sb_offset = 0; - per_ch->sb.format = sb_format; - per_ch->sb.nbframes = 0; - per_ch->sb.maxframes = buff_len / (per_ch->sb.format.channels * per_ch->sb.format.width); - - *chfetcherpointer = per_ch; + per_ch->buffer_firstframe = 0; + per_ch->buffer_numframes = 0; + ch->fetcher_data = per_ch; } - real_start = *start; - - sb = &per_ch->sb; - factor = per_sfx->format.width * per_sfx->format.channels; - - // If the stream buffer can't contain that much samples anyway - if (nbsampleframes > sb->maxframes) + // if the request is too large for our buffer, loop... + while (numsampleframes * f > (int)sizeof(per_ch->buffer)) { - Con_Printf ("ModPlug_FetchSound: stream buffer too small (%u sample frames required)\n", nbsampleframes); - return NULL; + done = sizeof(per_ch->buffer) / f; + ModPlug_GetSamplesFloat(ch, sfx, firstsampleframe, done, outsamplesfloat); + firstsampleframe += done; + numsampleframes -= done; + outsamplesfloat += done * sfx->format.channels; } - // If the data we need has already been decompressed in the sfxbuffer, just return it - if (per_ch->sb_offset <= real_start && per_ch->sb_offset + sb->nbframes >= real_start + nbsampleframes) + // seek if the request is before the current buffer (loop back) + // seek if the request starts beyond the current buffer by at least one frame (channel was zero volume for a while) + // do not seek if the request overlaps the buffer end at all (expected behavior) + if (per_ch->buffer_firstframe > firstsampleframe || per_ch->buffer_firstframe + per_ch->buffer_numframes < firstsampleframe) { - *start = per_ch->sb_offset; - return sb; + // we expect to decode forward from here so this will be our new buffer start + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes = 0; + // we don't actually seek - we don't care much about timing on silent mod music streams and looping never happens + //qModPlug_Seek(per_ch->mf, firstsampleframe * 1000.0 / sfx->format.speed); } - newlength = (int)(per_ch->sb_offset + sb->nbframes) - real_start; - - // If we need to skip some data before decompressing the rest, or if the stream has looped - if (newlength < 0 || per_ch->sb_offset > real_start) + // decompress the file as needed + if (firstsampleframe + numsampleframes > per_ch->buffer_firstframe + per_ch->buffer_numframes) { - unsigned int time_start; - unsigned int modplug_start; - - /* - MODs loop on their own, so any position is valid! - if (real_start > (unsigned int)per_sfx->total_length) - { - Con_Printf ("ModPlug_FetchSound: asked for a start position after the end of the sfx! (%u > %u)\n", - real_start, per_sfx->total_length); - return NULL; - } - */ - - // We work with 200ms (1/5 sec) steps to avoid rounding errors - time_start = real_start * 5 / snd_renderbuffer->format.speed; - modplug_start = time_start * (1000 / 5); - - Con_DPrintf("warning: mod file needed to seek (to %d)\n", modplug_start); - - qModPlug_Seek(per_ch->mf, modplug_start); - sb->nbframes = 0; - - real_start = (unsigned int) ((float)modplug_start / 1000 * snd_renderbuffer->format.speed); - if (*start - real_start + nbsampleframes > sb->maxframes) + // first slide the buffer back, discarding any data preceding the range we care about + int offset = firstsampleframe - per_ch->buffer_firstframe; + int keeplength = per_ch->buffer_numframes - offset; + if (keeplength > 0) + memmove(per_ch->buffer, per_ch->buffer + offset * sfx->format.width * sfx->format.channels, keeplength * sfx->format.width * sfx->format.channels); + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes -= offset; + // decompress as much as we can fit in the buffer + newlength = sizeof(per_ch->buffer) - per_ch->buffer_numframes * f; + done = 0; + while (newlength > done && (ret = qModPlug_Read(per_ch->mf, (void *)((unsigned char *)per_ch->buffer + done), (int)(newlength - done))) > 0) + done += ret; + // clear the missing space if any + if (done < newlength) { - Con_Printf ("ModPlug_FetchSound: stream buffer too small after seek (%u sample frames required)\n", - *start - real_start + nbsampleframes); - per_ch->sb_offset = real_start; - return NULL; + memset(per_ch->buffer + done, 0, newlength - done); + // Argh. We didn't get as many samples as we wanted. Probably + // libmodplug forgot what mLoopCount==-1 means... basically, this means + // we can't loop like this. Try to let DP fix it later... + sfx->total_length = firstsampleframe + done / f; + sfx->loopstart = 0; + // can't Con_Printf from this thread + //if (newlength != done) + // Con_DPrintf("ModPlug_Fetch: wanted: %d, got: %d\n", newlength, done); } + // we now have more data in the buffer + per_ch->buffer_numframes += done / f; } - // Else, move forward the samples we need to keep in the sound buffer - else - { - memmove (sb->samples, sb->samples + (real_start - per_ch->sb_offset) * factor, newlength * factor); - sb->nbframes = newlength; - } - - per_ch->sb_offset = real_start; - // We add more than one frame of sound to the buffer: - // 1- to ensure we won't lose many samples during the resampling process - // 2- to reduce calls to ModPlug_FetchSound to regulate workload - newlength = (int)(per_sfx->format.speed*STREAM_BUFFER_FILL); - if ((size_t) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed) + sb->nbframes > sb->maxframes) - { - Con_Printf ("ModPlug_FetchSound: stream buffer overflow (%u + %u = %u sample frames / %u)\n", - (unsigned int) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed), sb->nbframes, (unsigned int) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed) + sb->nbframes, sb->maxframes); - return NULL; - } - newlength *= factor; // convert from sample frames to bytes - if(newlength > (int)sizeof(resampling_buffer)) - newlength = sizeof(resampling_buffer); - - // Decompress in the resampling_buffer - done = 0; - while ((ret = qModPlug_Read (per_ch->mf, (char *)&resampling_buffer[done], (int)(newlength - done))) > 0) - done += ret; - if(done < newlength) - { - // Argh. We didn't get as many samples as we wanted. Probably - // libmodplug forgot what mLoopCount==-1 means... basically, this means - // we can't loop like this. Try to let DP fix it later... - per_sfx->sfx->total_length = (real_start + ((size_t)done / (size_t)factor)); - per_sfx->sfx->loopstart = 0; - - if(newlength != done) - Con_DPrintf("ModPlug_Fetch: wanted: %d, got: %d\n", newlength, done); - } - - Snd_AppendToSndBuffer (sb, resampling_buffer, (size_t)done / (size_t)factor, &per_sfx->format); - - *start = per_ch->sb_offset; - return sb; + // convert the sample format for the caller + buf = (short *)(per_ch->buffer + (firstsampleframe - per_ch->buffer_firstframe) * f); + len = numsampleframes * sfx->format.channels; + for (i = 0;i < len;i++) + outsamplesfloat[i] = buf[i] * (1.0f / 32768.0f); } /* ==================== -ModPlug_FetchEnd +ModPlug_StopChannel ==================== */ -static void ModPlug_FetchEnd (void *chfetcherdata) +static void ModPlug_StopChannel(channel_t *ch) { - modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)chfetcherdata; + modplug_stream_perchannel_t *per_ch = (modplug_stream_perchannel_t *)ch->fetcher_data; if (per_ch != NULL) { // Free the modplug decoder - qModPlug_Unload (per_ch->mf); + qModPlug_Unload(per_ch->mf); - Mem_Free (per_ch); + Mem_Free(per_ch); } } @@ -398,9 +345,9 @@ static void ModPlug_FetchEnd (void *chfetcherdata) ModPlug_FreeSfx ==================== */ -static void ModPlug_FreeSfx (void *sfxfetcherdata) +static void ModPlug_FreeSfx (sfx_t *sfx) { - modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfxfetcherdata; + modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data; // Free the modplug file Mem_Free(per_sfx->file); @@ -410,18 +357,7 @@ static void ModPlug_FreeSfx (void *sfxfetcherdata) } -/* -==================== -ModPlug_GetFormat -==================== -*/ -static const snd_format_t* qModPlug_GetFormat (sfx_t* sfx) -{ - modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data; - return &per_sfx->format; -} - -static const snd_fetcher_t modplug_fetcher = { ModPlug_FetchSound, ModPlug_FetchEnd, ModPlug_FreeSfx, qModPlug_GetFormat }; +static const snd_fetcher_t modplug_fetcher = { ModPlug_GetSamplesFloat, ModPlug_StopChannel, ModPlug_FreeSfx }; /* @@ -479,17 +415,13 @@ qboolean ModPlug_LoadModPlugFile (const char *filename, sfx_t *sfx) if (developer_loading.integer >= 2) Con_Printf ("\"%s\" will be streamed\n", filename); per_sfx = (modplug_stream_persfx_t *)Mem_Alloc (snd_mempool, sizeof (*per_sfx)); - strlcpy(per_sfx->name, sfx->name, sizeof(per_sfx->name)); - sfx->memsize += sizeof (*per_sfx); per_sfx->file = data; per_sfx->filesize = filesize; + sfx->memsize += sizeof(*per_sfx); sfx->memsize += filesize; - - per_sfx->format.speed = 44100; // modplug always works at that rate - per_sfx->format.width = 2; // We always work with 16 bits samples - per_sfx->format.channels = 2; // stereo rulez ;) (MAYBE default to mono because Amiga MODs sound better then?) - per_sfx->sfx = sfx; - + sfx->format.speed = 44100; // modplug always works at that rate + sfx->format.width = 2; // We always work with 16 bits samples + sfx->format.channels = 2; // stereo rulez ;) (MAYBE default to mono because Amiga MODs sound better then?) sfx->fetcher_data = per_sfx; sfx->fetcher = &modplug_fetcher; sfx->flags |= SFXFLAG_STREAMED; diff --git a/snd_null.c b/snd_null.c index 83859644..a30bfd79 100755 --- a/snd_null.c +++ b/snd_null.c @@ -74,7 +74,7 @@ int S_StartSound_StartPosition (int entnum, int entchannel, sfx_t *sfx, vec3_t o return -1; } -int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags) +int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags, float fspeed) { return -1; } diff --git a/snd_ogg.c b/snd_ogg.c index e1bf0959..b1f3e2a1 100644 --- a/snd_ogg.c +++ b/snd_ogg.c @@ -399,9 +399,6 @@ typedef struct { unsigned char *file; size_t filesize; - snd_format_t format; - unsigned int total_length; - char name[128]; } ogg_stream_persfx_t; // Per-channel data structure @@ -409,9 +406,10 @@ typedef struct { OggVorbis_File vf; ov_decode_t ov_decode; - unsigned int sb_offset; int bs; - snd_buffer_t sb; // must be at the end due to its dynamically allocated size + int buffer_firstframe; + int buffer_numframes; + unsigned char buffer[STREAM_BUFFERSIZE*4]; } ogg_stream_perchannel_t; @@ -419,159 +417,110 @@ static const ov_callbacks callbacks = {ovcb_read, ovcb_seek, ovcb_close, ovcb_te /* ==================== -OGG_FetchSound +OGG_GetSamplesFloat ==================== */ -static const snd_buffer_t* OGG_FetchSound (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes) +static void OGG_GetSamplesFloat (channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) { - ogg_stream_perchannel_t* per_ch = (ogg_stream_perchannel_t *)*chfetcherpointer; - ogg_stream_persfx_t* per_sfx = (ogg_stream_persfx_t *)sfxfetcher; - snd_buffer_t* sb; + ogg_stream_perchannel_t *per_ch = (ogg_stream_perchannel_t *)ch->fetcher_data; + ogg_stream_persfx_t *per_sfx = (ogg_stream_persfx_t *)sfx->fetcher_data; + int f = sfx->format.width * sfx->format.channels; // bytes per frame in the buffer + short *buf; + int i, len; int newlength, done, ret; - unsigned int real_start; - unsigned int factor; - // If there's no fetcher structure attached to the channel yet + // if this channel does not yet have a channel fetcher, make one if (per_ch == NULL) { - size_t buff_len, memsize; - snd_format_t sb_format; - - sb_format.speed = snd_renderbuffer->format.speed; - sb_format.width = per_sfx->format.width; - sb_format.channels = per_sfx->format.channels; - - buff_len = STREAM_BUFFER_SIZE(&sb_format); - memsize = sizeof (*per_ch) - sizeof (per_ch->sb.samples) + buff_len; - per_ch = (ogg_stream_perchannel_t *)Mem_Alloc (snd_mempool, memsize); - - // Open it with the VorbisFile API + // allocate a struct to keep track of our file position and buffer + per_ch = (ogg_stream_perchannel_t *)Mem_Alloc(snd_mempool, sizeof(*per_ch)); + // begin decoding the file per_ch->ov_decode.buffer = per_sfx->file; per_ch->ov_decode.ind = 0; per_ch->ov_decode.buffsize = per_sfx->filesize; - if (qov_open_callbacks (&per_ch->ov_decode, &per_ch->vf, NULL, 0, callbacks) < 0) + if (qov_open_callbacks(&per_ch->ov_decode, &per_ch->vf, NULL, 0, callbacks) < 0) { - Con_Printf("error while reading Ogg Vorbis stream \"%s\"\n", per_sfx->name); - Mem_Free (per_ch); - return NULL; + // this never happens - this function succeeded earlier on the same data + Mem_Free(per_ch); + return; } per_ch->bs = 0; - - per_ch->sb_offset = 0; - per_ch->sb.format = sb_format; - per_ch->sb.nbframes = 0; - per_ch->sb.maxframes = buff_len / (per_ch->sb.format.channels * per_ch->sb.format.width); - - *chfetcherpointer = per_ch; - } - - real_start = *start; - - sb = &per_ch->sb; - factor = per_sfx->format.width * per_sfx->format.channels; - - // If the stream buffer can't contain that much samples anyway - if (nbsampleframes > sb->maxframes) - { - Con_Printf ("OGG_FetchSound: stream buffer too small (%u sample frames required)\n", nbsampleframes); - return NULL; + per_ch->buffer_firstframe = 0; + per_ch->buffer_numframes = 0; + // attach the struct to our channel + ch->fetcher_data = (void *)per_ch; } - // If the data we need has already been decompressed in the sfxbuffer, just return it - if (per_ch->sb_offset <= real_start && per_ch->sb_offset + sb->nbframes >= real_start + nbsampleframes) + // if the request is too large for our buffer, loop... + while (numsampleframes * f > (int)sizeof(per_ch->buffer)) { - *start = per_ch->sb_offset; - return sb; + done = sizeof(per_ch->buffer) / f; + OGG_GetSamplesFloat(ch, sfx, firstsampleframe, done, outsamplesfloat); + firstsampleframe += done; + numsampleframes -= done; + outsamplesfloat += done * sfx->format.channels; } - newlength = (int)(per_ch->sb_offset + sb->nbframes) - real_start; - - // If we need to skip some data before decompressing the rest, or if the stream has looped - if (newlength < 0 || per_ch->sb_offset > real_start) + // seek if the request is before the current buffer (loop back) + // seek if the request starts beyond the current buffer by at least one frame (channel was zero volume for a while) + // do not seek if the request overlaps the buffer end at all (expected behavior) + if (per_ch->buffer_firstframe > firstsampleframe || per_ch->buffer_firstframe + per_ch->buffer_numframes < firstsampleframe) { - unsigned int time_start; - ogg_int64_t ogg_start; - int err; - - if (real_start > (unsigned int)per_sfx->total_length) + // we expect to decode forward from here so this will be our new buffer start + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes = 0; + ret = qov_pcm_seek(&per_ch->vf, (ogg_int64_t)firstsampleframe); + if (ret != 0) { - Con_Printf ("OGG_FetchSound: asked for a start position after the end of the sfx! (%u > %u)\n", - real_start, per_sfx->total_length); - return NULL; + // LordHavoc: we can't Con_Printf here, not thread safe... + //Con_Printf("OGG_FetchSound: qov_pcm_seek(..., %d) returned %d\n", firstsampleframe, ret); + return; } - - // We work with 200ms (1/5 sec) steps to avoid rounding errors - time_start = real_start * 5 / snd_renderbuffer->format.speed; - ogg_start = time_start * (per_sfx->format.speed / 5); - err = qov_pcm_seek (&per_ch->vf, ogg_start); - if (err != 0) - { - Con_Printf ("OGG_FetchSound: qov_pcm_seek(..., %d) returned %d\n", - real_start, err); - return NULL; - } - sb->nbframes = 0; - - real_start = (unsigned int) ((float)ogg_start / per_sfx->format.speed * snd_renderbuffer->format.speed); - if (*start - real_start + nbsampleframes > sb->maxframes) - { - Con_Printf ("OGG_FetchSound: stream buffer too small after seek (%u sample frames required)\n", - *start - real_start + nbsampleframes); - per_ch->sb_offset = real_start; - return NULL; - } - } - // Else, move forward the samples we need to keep in the sound buffer - else - { - memmove (sb->samples, sb->samples + (real_start - per_ch->sb_offset) * factor, newlength * factor); - sb->nbframes = newlength; } - per_ch->sb_offset = real_start; - - // We add more than one frame of sound to the buffer: - // 1- to ensure we won't lose many samples during the resampling process - // 2- to reduce calls to OGG_FetchSound to regulate workload - newlength = (int)(per_sfx->format.speed*STREAM_BUFFER_FILL); - // this is how much we FETCH... - if ((size_t) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed) + sb->nbframes > sb->maxframes) + // decompress the file as needed + if (firstsampleframe + numsampleframes > per_ch->buffer_firstframe + per_ch->buffer_numframes) { - Con_Printf ("OGG_FetchSound: stream buffer overflow (%u + %u = %u sample frames / %u)\n", - (unsigned int) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed), sb->nbframes, (unsigned int) ((double) newlength * (double)sb->format.speed / (double)per_sfx->format.speed) + sb->nbframes, sb->maxframes); - return NULL; + // first slide the buffer back, discarding any data preceding the range we care about + int offset = firstsampleframe - per_ch->buffer_firstframe; + int keeplength = per_ch->buffer_numframes - offset; + if (keeplength > 0) + memmove(per_ch->buffer, per_ch->buffer + offset * sfx->format.width * sfx->format.channels, keeplength * sfx->format.width * sfx->format.channels); + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes -= offset; + // decompress as much as we can fit in the buffer + newlength = sizeof(per_ch->buffer) - per_ch->buffer_numframes * f; + done = 0; + while (newlength > done && (ret = qov_read(&per_ch->vf, (char *)per_ch->buffer + per_ch->buffer_numframes * f + done, (int)(newlength - done), mem_bigendian, 2, 1, &per_ch->bs)) > 0) + done += ret; + // clear the missing space if any + if (done < newlength) + memset(per_ch->buffer + done, 0, newlength - done); + // we now have more data in the buffer + per_ch->buffer_numframes += done / f; } - newlength *= factor; // convert from sample frames to bytes - if(newlength > (int)sizeof(resampling_buffer)) - newlength = sizeof(resampling_buffer); - // Decompress in the resampling_buffer - done = 0; - while ((ret = qov_read (&per_ch->vf, (char *)&resampling_buffer[done], (int)(newlength - done), mem_bigendian, 2, 1, &per_ch->bs)) > 0) - done += ret; - - Snd_AppendToSndBuffer (sb, resampling_buffer, (size_t)done / (size_t)factor, &per_sfx->format); - - *start = per_ch->sb_offset; - return sb; + // convert the sample format for the caller + buf = (short *)((char *)per_ch->buffer + (firstsampleframe - per_ch->buffer_firstframe) * f); + len = numsampleframes * sfx->format.channels; + for (i = 0;i < len;i++) + outsamplesfloat[i] = buf[i] * (1.0f / 32768.0f); } /* ==================== -OGG_FetchEnd +OGG_StopChannel ==================== */ -static void OGG_FetchEnd (void *chfetcherdata) +static void OGG_StopChannel(channel_t *ch) { - ogg_stream_perchannel_t* per_ch = (ogg_stream_perchannel_t *)chfetcherdata; - + ogg_stream_perchannel_t *per_ch = (ogg_stream_perchannel_t *)ch->fetcher_data; if (per_ch != NULL) { - // Free the ogg vorbis decoder - qov_clear (&per_ch->vf); - - Mem_Free (per_ch); + // release the vorbis decompressor + qov_clear(&per_ch->vf); + Mem_Free(per_ch); } } @@ -581,32 +530,19 @@ static void OGG_FetchEnd (void *chfetcherdata) OGG_FreeSfx ==================== */ -static void OGG_FreeSfx (void *sfxfetcherdata) +static void OGG_FreeSfx(sfx_t *sfx) { - ogg_stream_persfx_t* per_sfx = (ogg_stream_persfx_t *)sfxfetcherdata; - - // Free the Ogg Vorbis file + ogg_stream_persfx_t *per_sfx = (ogg_stream_persfx_t *)sfx->fetcher_data; + // free the complete file we were keeping around Mem_Free(per_sfx->file); - - // Free the stream structure + // free the file information structure Mem_Free(per_sfx); } -/* -==================== -OGG_GetFormat -==================== -*/ -static const snd_format_t* OGG_GetFormat (sfx_t* sfx) -{ - ogg_stream_persfx_t* per_sfx = (ogg_stream_persfx_t *)sfx->fetcher_data; - return &per_sfx->format; -} - -static const snd_fetcher_t ogg_fetcher = { OGG_FetchSound, OGG_FetchEnd, OGG_FreeSfx, OGG_GetFormat }; +static const snd_fetcher_t ogg_fetcher = {OGG_GetSamplesFloat, OGG_StopChannel, OGG_FreeSfx}; -static void OGG_DecodeTags(vorbis_comment *vc, unsigned int *start, unsigned int *length, double samplesfactor, unsigned int numsamples, double *peak, double *gaindb) +static void OGG_DecodeTags(vorbis_comment *vc, unsigned int *start, unsigned int *length, unsigned int numsamples, double *peak, double *gaindb) { const char *startcomment = NULL, *lengthcomment = NULL, *endcomment = NULL, *thiscomment = NULL; @@ -649,11 +585,11 @@ static void OGG_DecodeTags(vorbis_comment *vc, unsigned int *start, unsigned int if(startcomment) { - *start = (unsigned int) bound(0, atof(startcomment) * samplesfactor, numsamples); + *start = (unsigned int) bound(0, atof(startcomment), numsamples); if(endcomment) - *length = (unsigned int) bound(0, atof(endcomment) * samplesfactor, numsamples); + *length = (unsigned int) bound(0, atof(endcomment), numsamples); else if(lengthcomment) - *length = (unsigned int) bound(0, *start + atof(lengthcomment) * samplesfactor, numsamples); + *length = (unsigned int) bound(0, *start + atof(lengthcomment), numsamples); } } @@ -664,7 +600,7 @@ OGG_LoadVorbisFile Load an Ogg Vorbis file into memory ==================== */ -qboolean OGG_LoadVorbisFile (const char *filename, sfx_t *sfx) +qboolean OGG_LoadVorbisFile(const char *filename, sfx_t *sfx) { unsigned char *data; fs_offset_t filesize; @@ -672,40 +608,38 @@ qboolean OGG_LoadVorbisFile (const char *filename, sfx_t *sfx) OggVorbis_File vf; vorbis_info *vi; vorbis_comment *vc; - ogg_int64_t len, buff_len; double peak, gaindb; - qboolean want_stream; #ifndef LINK_TO_LIBVORBIS if (!vf_dll) return false; #endif - // Already loaded? + // Return if already loaded if (sfx->fetcher != NULL) return true; - // Load the file - data = FS_LoadFile (filename, snd_mempool, false, &filesize); + // Load the file completely + data = FS_LoadFile(filename, snd_mempool, false, &filesize); if (data == NULL) return false; if (developer_loading.integer >= 2) - Con_Printf ("Loading Ogg Vorbis file \"%s\"\n", filename); + Con_Printf("Loading Ogg Vorbis file \"%s\"\n", filename); // Open it with the VorbisFile API ov_decode.buffer = data; ov_decode.ind = 0; ov_decode.buffsize = filesize; - if (qov_open_callbacks (&ov_decode, &vf, NULL, 0, callbacks) < 0) + if (qov_open_callbacks(&ov_decode, &vf, NULL, 0, callbacks) < 0) { - Con_Printf ("error while opening Ogg Vorbis file \"%s\"\n", filename); + Con_Printf("error while opening Ogg Vorbis file \"%s\"\n", filename); Mem_Free(data); return false; } // Get the stream information - vi = qov_info (&vf, -1); + vi = qov_info(&vf, -1); if (vi->channels < 1 || vi->channels > 2) { Con_Printf("%s has an unsupported number of channels (%i)\n", @@ -715,99 +649,54 @@ qboolean OGG_LoadVorbisFile (const char *filename, sfx_t *sfx) return false; } - len = qov_pcm_total (&vf, -1) * vi->channels * 2; // 16 bits => "* 2" - - // Decide if we go for a stream or a simple PCM cache - buff_len = (int)ceil (STREAM_BUFFER_DURATION * snd_renderbuffer->format.speed) * 2 * vi->channels; + sfx->format.speed = vi->rate; + sfx->format.channels = vi->channels; + sfx->format.width = 2; // We always work with 16 bits samples - if(snd_streaming.integer) - { - want_stream = true; - - // don't stream if we would need more RAM when streaming - if(snd_streaming.integer < 2) - if(len <= (ogg_int64_t)filesize + 3 * buff_len) - want_stream = false; + sfx->total_length = qov_pcm_total(&vf, -1); - // if streaming length is set, do NOT stream if below the length - if(snd_streaming_length.value > 0) - if(len <= snd_streaming_length.value * vi->channels * 2) - want_stream = false; - } - else - want_stream = false; - - if (want_stream) + if (snd_streaming.integer && (snd_streaming.integer >= 2 || sfx->total_length > max(sizeof(ogg_stream_perchannel_t), snd_streaming_length.value * sfx->format.speed))) { + // large sounds use the OGG fetcher to decode the file on demand (but the entire file is held in memory) ogg_stream_persfx_t* per_sfx; - if (developer_loading.integer >= 2) - Con_Printf ("Ogg sound file \"%s\" will be streamed\n", filename); - per_sfx = (ogg_stream_persfx_t *)Mem_Alloc (snd_mempool, sizeof (*per_sfx)); - strlcpy(per_sfx->name, sfx->name, sizeof(per_sfx->name)); + Con_Printf("Ogg sound file \"%s\" will be streamed\n", filename); + per_sfx = (ogg_stream_persfx_t *)Mem_Alloc(snd_mempool, sizeof(*per_sfx)); sfx->memsize += sizeof (*per_sfx); per_sfx->file = data; per_sfx->filesize = filesize; sfx->memsize += filesize; - - per_sfx->format.speed = vi->rate; - per_sfx->format.width = 2; // We always work with 16 bits samples - per_sfx->format.channels = vi->channels; - sfx->fetcher_data = per_sfx; sfx->fetcher = &ogg_fetcher; sfx->flags |= SFXFLAG_STREAMED; - sfx->total_length = (int)((size_t)len / (per_sfx->format.channels * 2) * ((double)snd_renderbuffer->format.speed / per_sfx->format.speed)); vc = qov_comment(&vf, -1); - OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, (double)snd_renderbuffer->format.speed / (double)per_sfx->format.speed, sfx->total_length, &peak, &gaindb); - per_sfx->total_length = sfx->total_length; - qov_clear (&vf); + OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, sfx->total_length, &peak, &gaindb); + qov_clear(&vf); } else { + // small sounds are entirely loaded and use the PCM fetcher char *buff; + ogg_int64_t len; ogg_int64_t done; int bs; long ret; - snd_buffer_t *sb; - snd_format_t ogg_format; - if (developer_loading.integer >= 2) Con_Printf ("Ogg sound file \"%s\" will be cached\n", filename); - - // Decode it - buff = (char *)Mem_Alloc (snd_mempool, (int)len); + len = sfx->total_length * sfx->format.channels * sfx->format.width; + sfx->flags &= ~SFXFLAG_STREAMED; + sfx->memsize += len; + sfx->fetcher = &wav_fetcher; + sfx->fetcher_data = Mem_Alloc(snd_mempool, (size_t)len); + buff = (char *)sfx->fetcher_data; done = 0; bs = 0; - while ((ret = qov_read (&vf, &buff[done], (int)(len - done), mem_bigendian, 2, 1, &bs)) > 0) + while ((ret = qov_read(&vf, &buff[done], (int)(len - done), mem_bigendian, 2, 1, &bs)) > 0) done += ret; - - // Build the sound buffer - ogg_format.speed = vi->rate; - ogg_format.channels = vi->channels; - ogg_format.width = 2; // We always work with 16 bits samples - sb = Snd_CreateSndBuffer ((unsigned char *)buff, (size_t)done / (vi->channels * 2), &ogg_format, snd_renderbuffer->format.speed); - if (sb == NULL) - { - qov_clear (&vf); - Mem_Free (data); - Mem_Free (buff); - return false; - } - - sfx->fetcher = &wav_fetcher; - sfx->fetcher_data = sb; - - sfx->total_length = sb->nbframes; - sfx->memsize += sb->maxframes * sb->format.channels * sb->format.width + sizeof (*sb) - sizeof (sb->samples); - - sfx->flags &= ~SFXFLAG_STREAMED; vc = qov_comment(&vf, -1); - OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, (double)snd_renderbuffer->format.speed / (double)sb->format.speed, sfx->total_length, &peak, &gaindb); - sb->nbframes = sfx->total_length; - qov_clear (&vf); - Mem_Free (data); - Mem_Free (buff); + OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, sfx->total_length, &peak, &gaindb); + qov_clear(&vf); + Mem_Free(data); } if(peak) diff --git a/snd_wav.c b/snd_wav.c index b55c3665..258b501c 100644 --- a/snd_wav.c +++ b/snd_wav.c @@ -226,13 +226,24 @@ static wavinfo_t GetWavinfo (char *name, unsigned char *wav, int wavlength) /* ==================== -WAV_FetchSound +WAV_GetSamplesFloat ==================== */ -static const snd_buffer_t* WAV_FetchSound (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes) +static void WAV_GetSamplesFloat(channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) { - *start = 0; - return (snd_buffer_t *)sfxfetcher; + int i, len = numsampleframes * sfx->format.channels; + if (sfx->format.width == 2) + { + const short *bufs = (const short *)sfx->fetcher_data + firstsampleframe * sfx->format.channels; + for (i = 0;i < len;i++) + outsamplesfloat[i] = bufs[i] * (1.0f / 32768.0f); + } + else + { + const signed char *bufb = (const signed char *)sfx->fetcher_data + firstsampleframe * sfx->format.channels; + for (i = 0;i < len;i++) + outsamplesfloat[i] = bufb[i] * (1.0f / 128.0f); + } } /* @@ -240,25 +251,13 @@ static const snd_buffer_t* WAV_FetchSound (void *sfxfetcher, void **chfetcherpoi WAV_FreeSfx ==================== */ -static void WAV_FreeSfx (void *sfxfetcherdata) +static void WAV_FreeSfx(sfx_t *sfx) { - snd_buffer_t* sb = (snd_buffer_t *)sfxfetcherdata; - // Free the sound buffer - Mem_Free(sb); + // free the loaded sound data + Mem_Free(sfx->fetcher_data); } -/* -==================== -WAV_GetFormat -==================== -*/ -static const snd_format_t* WAV_GetFormat (sfx_t* sfx) -{ - snd_buffer_t* sb = (snd_buffer_t *)sfx->fetcher_data; - return &sb->format; -} - -const snd_fetcher_t wav_fetcher = { WAV_FetchSound, NULL, WAV_FreeSfx, WAV_GetFormat }; +const snd_fetcher_t wav_fetcher = { WAV_GetSamplesFloat, NULL, WAV_FreeSfx }; /* @@ -271,8 +270,9 @@ qboolean S_LoadWavFile (const char *filename, sfx_t *sfx) fs_offset_t filesize; unsigned char *data; wavinfo_t info; - snd_format_t wav_format; - snd_buffer_t* sb; + int i, len; + const unsigned char *inb; + unsigned char *outb; // Already loaded? if (sfx->fetcher != NULL) @@ -316,28 +316,46 @@ qboolean S_LoadWavFile (const char *filename, sfx_t *sfx) ptr[i] = LittleShort (ptr[i]); } - wav_format.speed = info.rate; - wav_format.width = info.width; - wav_format.channels = info.channels; - sb = Snd_CreateSndBuffer (data + info.dataofs, info.samples, &wav_format, snd_renderbuffer->format.speed); - if (sb == NULL) + sfx->format.speed = info.rate; + sfx->format.width = info.width; + sfx->format.channels = info.channels; + sfx->fetcher = &wav_fetcher; + sfx->fetcher_data = Mem_Alloc(snd_mempool, info.samples * sfx->format.width * sfx->format.channels); + sfx->total_length = info.samples; + sfx->memsize += filesize; + len = info.samples * sfx->format.channels * sfx->format.width; + inb = data + info.dataofs; + outb = (unsigned char *)sfx->fetcher_data; + if (info.width == 2) { - Mem_Free(data); - return false; + if (mem_bigendian) + { + // we have to byteswap the data at load (better than doing it while mixing) + for (i = 0;i < len;i += 2) + { + outb[i] = inb[i+1]; + outb[i+1] = inb[i]; + } + } + else + { + // we can just copy it straight + memcpy(outb, inb, len); + } + } + else + { + // convert unsigned byte sound data to signed bytes for quicker mixing + for (i = 0;i < len;i++) + outb[i] = inb[i] - 0x80; } - sfx->fetcher = &wav_fetcher; - sfx->fetcher_data = sb; - - sfx->total_length = sb->nbframes; - sfx->memsize += sb->maxframes * sb->format.channels * sb->format.width + sizeof (*sb) - sizeof (sb->samples); if (info.loopstart < 0) sfx->loopstart = sfx->total_length; else - sfx->loopstart = (unsigned int) ((double)info.loopstart * (double)sb->format.speed / (double)info.rate); + sfx->loopstart = info.loopstart; sfx->loopstart = min(sfx->loopstart, sfx->total_length); sfx->flags &= ~SFXFLAG_STREAMED; - Mem_Free (data); return true; } diff --git a/sound.h b/sound.h index ff1e9187..86839d17 100644 --- a/sound.h +++ b/sound.h @@ -98,7 +98,7 @@ qboolean S_IsSoundPrecached (const sfx_t *sfx); // S_StartSound returns the channel index, or -1 if an error occurred int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation); int S_StartSound_StartPosition (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition); -int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags); +int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags, float fspeed); qboolean S_LocalSound (const char *s); void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation); @@ -109,6 +109,7 @@ void S_PauseGameSounds (qboolean toggle); void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx); qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value); void S_SetChannelVolume (unsigned int ch_ind, float fvol); +void S_SetChannelSpeed (unsigned int ch_ind, float fspeed); float S_GetChannelPosition (unsigned int ch_ind); float S_GetEntChannelPosition(int entnum, int entchannel); -- 2.39.2