Descent3/stream_audio/streamaudio.cpp
Jan Engelhardt de0af3b10d mem_malloc type triviality checks (6/8)
```
git grep -l mem_malloc | xargs perl -i -lpe 's{\((uint8_t) \*\)mem_malloc\((\S+)\)}{mem_rmalloc<$1>($2)}'
```
2024-09-16 22:12:13 +02:00

954 lines
28 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/Main/streamaudio/streamaudio.cpp $
* $Revision: 1.1.1.1 $
* $Date: 2000/04/18 00:00:49 $
* $Author: icculus $
*
* Interface for streaming audio, compressed or not
*
* $Log: streamaudio.cpp,v $
* Revision 1.1.1.1 2000/04/18 00:00:49 icculus
* initial checkin
*
*
* 44 9/29/99 5:13p Jeff
* Linux now has real stream audio
*
* 43 7/28/99 2:04p Kevin
* Macintosh Changes
*
* 41 5/17/99 1:56p Ardussi
* changed to compile on Mac
*
* 40 5/10/99 9:22p Samir
* added code to allow for certain streams to be specified to start on
* next sound frame.
*
* 39 4/29/99 3:03p Samir
* took out mprintfs and if we're opening an opened stream, close it and
* continue.
*
* 38 4/17/99 5:58a Jeff
* Changes for linux
*
* 37 4/14/99 1:48a Jeff
* fixed case mismatched #includes
*
* 36 4/10/99 5:09p Samir
* beginning to add prioritization of sounds code. currently non
* functional, but allows me to change all calls to Play3dSound so that I
* can test later.
*
* 35 3/22/99 5:12p Samir
* on shutdown, CLOSE any open streams.
*
* 34 3/17/99 4:22p Samir
* made pause and resume for streams work.
*
* 33 3/09/99 6:40p Jeff
* put try/catch around StreamPlay
*
* 32 3/04/99 6:38p Samir
* Saved stream state per frame.
*
* 31 3/03/99 5:08p Samir
* added slew of debug code: hopefully the library will be revamped after
* OEM.
*
* 30 3/01/99 8:11p Samir
* fixed bug when audio stream was playing while sound system shutdown.
*
* 29 2/28/99 6:35p Samir
* fixed streaming bugs for very small samples.
*
* 28 2/28/99 5:11p Nate
* added playcount.
*
* 27 2/27/99 6:52p Samir
* added function to get current loop count
*
* 26 2/26/99 8:34p Samir
* may have fixed problem with really small streams.
*
* 25 2/26/99 5:31p Samir
*
* 24 2/25/99 9:20p Kevin
* Semi fix for music in DS
*
* 23 2/17/99 8:35p Samir
* use m_streamdone to determine whether play should reset the stream or
* not.
*
* 22 12/13/98 6:50p Samir
* fixed problems with looping and next.switching sync.
*
* 21 12/11/98 5:25p Samir
* took out debug messages.
*
* 20 12/11/98 5:20p Samir
* perhaps prevent unused buffers from playing.
*
* 19 12/11/98 3:27p Samir
* add debug code.
*
* 18 12/10/98 7:12p Samir
* fixed some bugs in file/playback buffer sequencing with new system.
*
* 17 12/10/98 10:11a Samir
* newer streaming audio library.
*
* 11 11/20/98 5:20p Samir
* correcting a lot of errors in AudioStream::Next
*
* 10 11/13/98 4:03p Nate
* took out volume stuff from high level sound lib
*
* 9 11/13/98 2:25p Samir
* added 'next' stream processing.
*
* 9 11/13/98 10:48a Samir
* added 'next' stream capability.
*
* 8 10/23/98 7:04p Samir
*
* 7 10/23/98 6:45p Matt
* Scale the streaming audio volume by the master volume.
*
* 6 8/10/98 5:54p Samir
* added looping streams and soft measure stopping.
*
* 5 7/30/98 4:35p Dan
* more error checking.
*
* 4 7/30/98 10:57a Sean
* if no sound, then don't do anything.
*
* 3 7/24/98 5:18p Samir
* use OSFArchive for stream file operations now.
*
* 2 7/09/98 8:36p Samir
* fully implemented redo of Stream interface.
*
* 1 7/09/98 6:48p Samir
*
* MOVED FROM MAIN PROJECT
*
* 10 6/29/98 4:15p Chris
* Streaming audio works
*
* 9 6/24/98 5:00p Jeff
* set filehandle to null on close, short circuit read data if file handle
* is NULL
*
* 8 6/24/98 4:41p Jeff
* will kill the stream is the sound is killed
*
*
* $NoKeywords: $
*/
#include <cstdlib>
#include <cstring>
#include "adecode.h"
#include "cfile.h"
#include "ddio.h"
#include "log.h"
#include "Macros.h"
#include "mem.h"
#include "pserror.h"
#include "streamaudio.h"
// this stream is for everyone (used by the StreamPlay interface)
static AudioStream User_audio_stream;
llsSystem *AudioStream::m_ll_sndsys = NULL;
AudioStream *AudioStream::m_streams[STRM_LIMIT];
int AudioStream::m_thisid = -1;
#define SAMPLES_PER_STREAM_SHORT_BUF 22050
#define SAMPLES_PER_STREAM_BUF 44100
// sets the low-level sound object
void AudioStream::InitSystem(llsSystem *sndsys) {
int i;
AudioStream::m_ll_sndsys = sndsys;
// reserve sound slots for streams, they should never get replaced, and always should be played.
for (i = 0; i < STRM_LIMIT; i++) {
AudioStream::m_streams[i] = NULL;
}
}
// shutdsown
void AudioStream::Shutdown() {
if (AudioStream::m_ll_sndsys) {
int i;
// stop all streams.
for (i = 0; i < STRM_LIMIT; i++) {
if (AudioStream::m_streams[i]) {
AudioStream::m_streams[i]->Close();
AudioStream::m_streams[i] = NULL;
}
}
AudioStream::m_ll_sndsys = NULL;
}
}
// allocates a stream slot for a stream
bool AudioStream::ActivateStream(AudioStream *stream) {
int i;
for (i = 0; i < STRM_LIMIT; i++) {
if (AudioStream::m_streams[i] == NULL) {
AudioStream::m_streams[i] = stream;
stream->m_streamindex = i;
return true;
}
}
LOG_DEBUG << "AudioStream queue filled!";
return false;
}
void AudioStream::DeactivateStream(AudioStream *stream) {
if (stream->m_streamindex > -1) {
AudioStream::m_streams[stream->m_streamindex] = NULL;
stream->m_streamindex = -1;
}
}
// called by application to allow stream playback
void AudioStream::Frame() {
int t;
float time = timer_GetTime();
if (!m_ll_sndsys)
return;
for (t = 0; t < STRM_LIMIT; t++) {
if (AudioStream::m_streams[t]) {
AudioStream *strm = AudioStream::m_streams[t];
if (strm->m_laststate != strm->m_state) {
LOG_DEBUG.printf("STRM[%d]:frame_state=%d", strm->m_curid, strm->m_state);
}
if (strm->m_state == STRM_PLAYING) {
// see if the sound is still playing if it isn't then kill it (also if no more loops)
strm->m_measure_timer += (time - strm->m_last_frametime);
if (strm->m_measure_timer >= strm->m_measure_time) {
strm->m_curmeasure++;
}
strm->m_last_frametime = time;
if (strm->m_state == STRM_PLAYING)
strm->UpdateData();
} else if (strm->m_state == STRM_STOPPING) {
if (!AudioStream::m_ll_sndsys->IsSoundInstancePlaying(strm->m_llshandle)) {
strm->Stop();
if (CHECK_FLAG(strm->m_flags, STRM_OPNF_ONETIME)) {
strm->Close();
}
strm->m_readahead = false;
}
} else if (strm->m_state == STRM_INVALID) {
strm->DeactivateStream(strm);
} else if (strm->m_start_on_frame) {
strm->m_llshandle = m_ll_sndsys->PlayStream(&strm->m_playinfo);
if (strm->m_llshandle > -1) {
// for one buffer samples, prepare to stop now so that the one and only one buffer gets called.
// looped one buffer samples dont do this though...
if (strm->m_nbufs == 1 && !strm->m_start_on_frame_looped) {
strm->m_state = STRM_STOPPING;
} else {
strm->m_state = STRM_PLAYING;
}
}
strm->m_start_on_frame = false;
strm->m_start_on_frame_looped = false;
}
strm->m_laststate = strm->m_state;
}
}
}
// called to pause all streams.
void AudioStream::PauseAll() {
int t;
for (t = 0; t < STRM_LIMIT; t++) {
if (AudioStream::m_streams[t]) {
AudioStream::m_streams[t]->Pause();
}
}
}
void AudioStream::ResumeAll() {
int t;
for (t = 0; t < STRM_LIMIT; t++) {
if (AudioStream::m_streams[t]) {
AudioStream::m_streams[t]->Resume();
}
}
}
// AudioStream object Interface
// allows dynamic playing of streams
AudioStream::AudioStream() {
m_decoder = NULL; // audio decomp object
m_llshandle = -1; // snd system handle
m_sbufidx = 0; // stream position markers for synching.
m_fbufidx = 0;
m_flags = 0;
m_state = STRM_INVALID;
m_streamindex = -1;
m_curmeasure = -1;
m_stopnextmeasure = false;
m_stopflag = NULL;
m_bytesleft = 0;
m_readahead_finished_loop = false;
m_playcount = 0;
m_curid = -1;
m_nbufs = 0;
m_start_on_frame = false;
}
AudioStream::~AudioStream() { AudioStream::Close(); }
// sets flags for playback (STRM_OPNF_XXX)
void AudioStream::SetFlags(int flags) { m_flags = flags; }
void AudioStream::SetLoopCount(int loop_count) {
// if loop_count = 0, then m_loopcount= -1, infinite;
// if loop_count = 1, then m_loopcount=0, no looping, etc.
m_loopcount = loop_count - 1;
}
int AudioStream::GetLoopCount() const {
// if loop_count = 0, then m_loopcount= -1, infinite;
// if loop_count = 1, then m_loopcount=0, no looping, etc.
return m_loopcount + 1;
}
void AudioStream::SetVolume(float vol) {
m_volume = vol;
if (m_ll_sndsys) {
if (m_llshandle < 0)
return;
m_ll_sndsys->AdjustSound(m_llshandle, vol, 0.0f, m_playinfo.samples_per_22khz_sample * 22050);
}
}
float AudioStream::GetVolume() { return m_volume; }
// flags specify what type of stream it is.
bool AudioStream::Open(const char *filename, int open_flags) {
// don't open a stream that's already open, or bogus filename
if (m_state != STRM_INVALID) {
AudioStream::Close();
// Int3();
// return false;
}
if (m_archive.Opened() || !filename)
return false;
if (!m_ll_sndsys)
return false;
m_state = STRM_INVALID;
m_streamindex = -1;
m_flags = open_flags;
m_curmeasure = 0;
m_volume = 1.0f;
m_bufsize = SAMPLES_PER_STREAM_BUF * 4;
m_measure_timer = 0.0f;
m_measure_time = 0.0f;
m_last_frametime = 0.0f;
m_readahead_finished_loop = false;
m_curid = -1;
m_nbufs = 0;
m_start_on_frame = m_start_on_frame_looped = false;
// open up stream file
if (m_archive.Open(filename)) {
int i;
int nbufs;
ASSERT(m_archive.StreamType() == OSF_DIGITAL_STRM);
m_thisid++;
m_curid = m_thisid;
for (i = 0; i < STRM_BUFCOUNT; i++) {
m_buffer[i].flags = 0;
m_buffer[i].nbytes = 0;
m_buffer[i].data = NULL;
m_buffer[i].id = -1; // marks stream buffer to be allocated.
}
// gradual streams don't readahead yet.
nbufs = CHECK_FLAG(m_flags, STRM_OPNF_GRADUAL) ? 1 : STRM_BUFCOUNT;
if (!AudioStream::ReopenDigitalStream(0, nbufs)) {
return false;
}
AudioStream::SetLoopCount(1);
m_laststate = m_state;
m_state = STRM_STOPPED;
return true;
}
return false;
}
// deallocates all stream buffers and decoder.
void AudioStream::Close() {
int i;
if (m_archive.Opened()) {
// stop the stream, close the archive, close the decoder.
AudioStream::Stop();
m_archive.Close();
m_curid = -1;
// free streaming buffers and decoder if we need to.
for (i = 0; i < STRM_BUFCOUNT; i++) {
if (m_buffer[i].data) {
mem_free(m_buffer[i].data);
m_buffer[i].data = NULL;
m_buffer[i].nbytes = 0;
m_buffer[i].flags = 0;
m_buffer[i].id = -1;
}
}
if (m_decoder) {
delete m_decoder;
m_decoder = NULL;
}
}
m_playcount = 0;
m_nbufs = 0;
m_state = STRM_INVALID;
}
// has stream finished with its readahead of current loop?
bool AudioStream::ReadAheadFinishedLoop() {
if (m_readahead_finished_loop) {
m_readahead_finished_loop = false;
return true;
}
return false;
}
// are we still reading from disk?
bool AudioStream::ReadAhead() { return m_readahead; }
bool AudioStream::IsReady() {
if (m_state == STRM_STOPPED) {
if (!m_readahead || m_fbufidx == (STRM_BUFCOUNT - 1)) {
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
bool AudioStream::ReopenDigitalStream(uint8_t fbufidx, int nbufs) {
const tOSFDigiHdr *digihdr = (const tOSFDigiHdr *)m_archive.StreamHeader();
m_bytesleft = m_archive.StreamLength();
m_readahead = false;
// free current decoder.
if (m_decoder) {
delete m_decoder;
m_decoder = NULL;
}
// instatiate decompression facility or use raw source data
uint32_t sample_count = 0;
uint32_t channels = 0;
if (m_archive.StreamComp() == OSF_DIGIACM_STRM) {
uint32_t sample_rate;
m_decoder = AudioDecoder::CreateDecoder(ADecodeFileRead, this, channels, sample_rate, sample_count);
if (!m_decoder) {
delete m_decoder;
m_decoder = NULL;
return false;
}
} else {
return false;
}
// convert bufsize to true bufsize (bufsize = samples per measure for now.)
int bytesize, granularity;
switch (m_archive.StreamFormat()) {
case SAF_8BIT_M:
granularity = 1;
break;
case SAF_8BIT_S:
granularity = 2;
bytesize = digihdr->measure << 1;
break;
case SAF_16BIT_M:
granularity = 2;
bytesize = digihdr->measure << 1;
break;
case SAF_16BIT_S:
granularity = 4;
bytesize = digihdr->measure << 2;
break;
}
// bufsize is bytes per measure. now scale according to memory requirements.
// clear out buffer list
if (channels == 0 || channels > 2) {
// weird, faulty osf
LOG_WARNING.printf("STRM[%d]: Illegal OSF (no channels?): %d.", m_curid, channels);
return false;
}
int32_t bytes_per_buf = (SAMPLES_PER_STREAM_BUF * granularity);
int32_t filelen = (sample_count / channels) * granularity;
int nbuffers = filelen / bytes_per_buf;
if (nbuffers >= 0 && nbuffers <= 1) {
if (filelen > 0) {
bytes_per_buf = (SAMPLES_PER_STREAM_SHORT_BUF * granularity);
nbuffers = filelen / bytes_per_buf;
if (nbuffers == 0)
nbuffers = 1;
} else {
LOG_WARNING.printf("STRM[%d]: Bad stream length %d", m_curid, filelen);
return false;
}
}
m_playbytestotal = filelen;
m_playbytesleft = filelen;
m_fbufidx = fbufidx;
m_sbufidx = 0;
// Get the file size, and figure out an ideal buffer size
// so our looping sounds don't have an odd silence in the middle
m_measure_time = (digihdr->measure / 22050.0f);
m_bufsize = (filelen / nbuffers);
// figure out if we still need to be 4-byte aligned.
granularity = granularity >> 1;
if (granularity) {
if (m_bufsize % 4) {
m_bufsize = m_bufsize + (4 - (m_bufsize % 4));
if (m_bufsize % 4) {
Int3();
}
}
}
// mark stream as not done.
m_readahead = true;
m_readahead_finished_loop = false;
if (!nbufs) {
// dont set m_readahead, because readahead only told this function whether it was going to do
// the read of buffers now, or let UpdateData do it later.
return true;
}
// fill buffers.
nbufs--;
while (!CHECK_FLAG(m_buffer[m_fbufidx].flags, STRM_BUFF_USED) && nbufs >= 0 && m_readahead) {
// if our stream's current id does not match the streaming buffer's id, then we need to reallocate
// the stream buffer with the new memory size
if (m_buffer[m_fbufidx].id != (int)m_curid) {
if (m_buffer[m_fbufidx].data) {
mem_free(m_buffer[m_fbufidx].data);
}
m_buffer[m_fbufidx].data = mem_rmalloc<uint8_t>(m_bufsize);
}
m_buffer[m_fbufidx].nbytes = AudioStream::ReadFileData(m_fbufidx, m_bufsize);
m_buffer[m_fbufidx].flags = 0;
m_buffer[m_fbufidx].flags |= STRM_BUFF_USED;
m_buffer[m_fbufidx].id = m_thisid;
m_playbytesleft -= m_buffer[m_fbufidx].nbytes;
if (m_playbytesleft <= (m_bufsize / 4)) {
if (m_buffer[m_fbufidx].nbytes == 0) {
memset(m_buffer[m_fbufidx].data, 0, 4);
m_buffer[m_fbufidx].nbytes = 4;
}
m_buffer[m_fbufidx].flags |= STRM_BUFF_TERMINAL;
m_readahead = false;
m_readahead_finished_loop = true;
}
m_nbufs++;
//@@ if (m_buffer[m_fbufidx].nbytes < m_bufsize || m_bytesleft == 0) {
//@@ if (m_buffer[m_fbufidx].nbytes == 0) {
//@@ memset(m_buffer[m_fbufidx].data, 0, 4);
//@@ m_buffer[m_fbufidx].nbytes = 4;
//@@ }
//@@ m_buffer[m_fbufidx].flags |= STRM_BUFF_TERMINAL;
//@@ m_readahead = false;
//@@ m_readahead_finished_loop = true;
//@@ }
m_fbufidx = (m_fbufidx + 1) % STRM_BUFCOUNT;
}
// readjust file buffer index down so that it matches the CURRENT file index, not the next one.
if (m_fbufidx == 0)
m_fbufidx = STRM_BUFCOUNT - 1;
else
m_fbufidx--;
return true;
}
///////////////////////////////////////////////////////////////////////////////
// plays a stream
bool AudioStream::Play(bool start_on_frame) {
// call low level stream manager. - samir
int sflag = SIF_STREAMING_16_M, i;
bool looped = false;
if (m_state == STRM_INVALID) {
#ifdef WIN32
Int3();
#endif
return false;
}
// play stream if there's room to.
if (!ActivateStream(this))
return false;
switch (m_archive.StreamFormat()) {
case SAF_8BIT_M:
sflag = SIF_STREAMING_8_M;
break;
case SAF_8BIT_S:
sflag = SIF_STREAMING_8_S;
break;
case SAF_16BIT_M:
sflag = SIF_STREAMING_16_M;
break;
case SAF_16BIT_S:
sflag = SIF_STREAMING_16_S;
break;
default:
Int3();
return false;
}
// reset soft stopping.
m_stopnextmeasure = false;
m_curmeasure = 0;
m_stopflag = NULL;
if (m_state != STRM_STOPPED) {
AudioStream::Stop();
}
if (m_playcount > 0) {
AudioStream::Reset();
}
m_playcount++;
m_measure_timer = 0.0f;
m_last_frametime = timer_GetTime();
// check for terminal and if loopcount != 0 then specify terminal as looping
if (m_loopcount != 0) {
for (i = 0; i < STRM_BUFCOUNT; i++) {
if (CHECK_FLAG(m_buffer[i].flags, STRM_BUFF_USED)) {
if (CHECK_FLAG(m_buffer[i].flags, STRM_BUFF_TERMINAL)) {
if (m_loopcount == -1) {
m_buffer[m_fbufidx].flags |= STRM_BUFF_LOOPEND;
looped = true;
AudioStream::Reset();
} else if (m_loopcount > 0) {
m_buffer[m_fbufidx].flags |= STRM_BUFF_LOOPEND;
looped = true;
AudioStream::Reset();
m_loopcount--;
}
m_readahead_finished_loop = true;
}
}
}
}
// if (m_curid == 3) Int3();
// invoke low level sound system.
memset(&m_playinfo, 0, sizeof(m_playinfo));
m_playinfo.samples_per_22khz_sample = 1.0;
m_playinfo.sample_skip_interval = 0;
m_playinfo.m_stream_cback = AudioStreamCB;
m_playinfo.m_stream_data = m_buffer[m_sbufidx].data;
m_playinfo.m_stream_format = sflag;
m_playinfo.m_stream_size = m_buffer[m_sbufidx].nbytes;
m_playinfo.m_stream_handle = m_streamindex;
m_playinfo.m_stream_bufsize = m_bufsize;
m_playinfo.user_data = this;
m_playinfo.left_volume = m_playinfo.right_volume = m_volume;
m_playinfo.priority = SND_PRIORITY_CRITICAL; // this stream must play.
m_start_on_frame = start_on_frame;
m_start_on_frame_looped = looped;
if (!m_start_on_frame) {
m_llshandle = m_ll_sndsys->PlayStream(&m_playinfo);
if (m_llshandle > -1) {
// for one buffer samples, prepare to stop now so that the one and only one buffer gets called.
// looped one buffer samples dont do this though...
if (m_nbufs == 1 && !looped) {
m_state = STRM_STOPPING;
} else {
m_state = STRM_PLAYING;
}
return true;
} else {
LOG_ERROR << "STRMAUD:Eh? this should be reserved??";
Int3();
}
}
return true;
}
// pauses a stream
void AudioStream::Pause() {
if (m_state != STRM_PLAYING) {
return;
}
m_state = STRM_PAUSED;
m_ll_sndsys->PauseSound(m_llshandle);
// mprintf(0, "Paused stream (%d)\n", m_llshandle);
}
// pauses a stream
void AudioStream::Resume() {
if (m_state != STRM_PAUSED) {
return;
}
m_ll_sndsys->ResumeSound(m_llshandle);
m_state = STRM_PLAYING;
// mprintf(0, "Resumed stream (%d)\n", m_llshandle);
}
// causes a rewind to start of stream, if on_measure is true, stop occurs when measure ends
void AudioStream::Stop(bool on_measure, int *stop_flag) {
if (!m_ll_sndsys) {
return;
}
m_start_on_frame = false;
if (m_state == STRM_INVALID) {
if (stop_flag) {
*stop_flag = true;
}
return;
}
if (m_state != STRM_STOPPED) {
if (!on_measure) {
if (m_state == STRM_PLAYING || m_state == STRM_PAUSED || m_state == STRM_STOPPING) {
m_ll_sndsys->StopSound(m_llshandle, SKT_STOP_IMMEDIATELY);
m_state = STRM_STOPPED;
m_readahead = m_readahead_finished_loop = true;
DeactivateStream(this);
m_llshandle = -1;
m_curmeasure = 0;
}
} else {
// handle soft stopping.
m_stopflag = stop_flag;
if (m_stopflag) {
*m_stopflag = false;
}
m_stopnextmeasure = on_measure;
}
} else {
DeactivateStream(this);
m_llshandle = -1;
m_curmeasure = 0;
if (m_stopflag) {
*m_stopflag = true;
}
}
}
// performs a clean stop and play when switching to next stream
void AudioStream::Reset() {
m_archive.Rewind();
m_bytesleft = m_archive.StreamLength();
m_curmeasure = 0;
m_readahead = true;
m_playbytesleft = m_playbytestotal;
if (m_decoder) {
delete m_decoder;
uint32_t channels;
uint32_t sample_rate;
uint32_t sample_count;
m_decoder = AudioDecoder::CreateDecoder(ADecodeFileRead, this, channels, sample_rate, sample_count);
}
}
//////////////////////////////////////////////////////////////////////////////
// invoked by AudioStreamCB.
#pragma optimize("", off)
void *AudioStream::StreamCallback(int *size) {
uint8_t nextbuffer = (m_sbufidx + 1) % STRM_BUFCOUNT;
void *data = NULL;
// we're not done yet.
// adjust sound buffer to the next buffer
if (m_state == STRM_STOPPING || m_state == STRM_STOPPED) {
if (m_state == STRM_STOPPED) {
LOG_DEBUG.printf("STRM[%d]: Callback entered while stopping! Force stop.", m_curid);
} else {
LOG_DEBUG.printf("STRM[%d]: Stopping softly", m_curid);
}
// mark played buffer as unused.
m_buffer[m_sbufidx].flags = 0;
m_buffer[m_sbufidx].nbytes = 0;
*size = 0;
return NULL;
}
if (!CHECK_FLAG(m_buffer[nextbuffer].flags, STRM_BUFF_USED)) {
LOG_DEBUG.printf("STRM[%d]: Playing onetime buffer?", m_curid);
m_state = STRM_STOPPED;
*size = 0;
return NULL;
//@@ mprintf(0, "STRMAUD: Repeating last buffer.\n");
//@@ data = (void *)m_buffer[m_sbufidx].data;
//@@ *size = m_buffer[m_sbufidx].nbytes;
//@@ return data;
}
// mark played buffer as unused.
m_buffer[m_sbufidx].flags = 0;
m_buffer[m_sbufidx].nbytes = 0;
m_sbufidx = nextbuffer;
// ASSERT(CHECK_FLAG(m_buffer[m_sbufidx].flags, STRM_BUFF_USED));
// mprintf(0,"%c",m_sbufidx+'A');
// increment measure count if we've entered a new measure.
// if stop measure has been flagged, don't return new valid data.
// make sure and lock mutex, because we're modifying these contents when the caller could
// do the same.
// if we reached the last buffer in an opened stream, and we're not switching prepare to stop
// if we are switching, release the switch lock and continue
// otherwise just continue.
data = (void *)m_buffer[m_sbufidx].data;
*size = m_buffer[m_sbufidx].nbytes;
// ASSERT((*size) > 0);
if (CHECK_FLAG(m_buffer[m_sbufidx].flags, STRM_BUFF_TERMINAL)) {
// mprintf(0, "STRMAUD: reached end of stream (%d bytes).\n", (*size));
if (!CHECK_FLAG(m_buffer[m_sbufidx].flags, STRM_BUFF_LOOPEND)) {
m_state = STRM_STOPPING;
}
if (m_stopnextmeasure) {
m_state = STRM_STOPPING;
}
LOG_DEBUG.printf("STRM[%d]: Start stopping softly", m_curid);
// *done = true;
}
if ((*size) == 0) {
// *done = true;
LOG_DEBUG.printf("STRM[%d]: Used buffer has 0 bytes!", m_curid);
m_state = STRM_STOPPING;
data = NULL;
}
return data;
}
#pragma optimize("", on)
// reads in decompressed raw data.
int AudioStream::ReadFileData(int buf, int len) {
if (!m_archive.Opened())
return 0;
if (m_decoder) {
// We have a compressed stream
return m_decoder->Read(m_buffer[buf].data, len);
}
// We have a non-compressed stream
if (len > m_bytesleft) {
len = m_bytesleft;
m_bytesleft = 0;
} else {
m_bytesleft -= len;
}
return m_archive.Read(m_buffer[buf].data, len);
}
// TURN OFF OPTIMIZATIONS HERE. Placement of instructions is VERY IMPORTANT here. synched with StreamCallback,
// which also has optimizations off.
#pragma optimize("", off)
// updates file buffers
void AudioStream::UpdateData() {
int nextbuffer = ((m_fbufidx + 1) % STRM_BUFCOUNT);
// check if are on a measure boundary for current stream. if so, then check if we have a next request pending
if (CHECK_FLAG(m_buffer[nextbuffer].flags, STRM_BUFF_USED)) {
return;
}
// quit out if we can.
if (nextbuffer == m_sbufidx) {
return;
}
// do read!
// READ DATA INTO BUFFER. UPDATE BYTES LEFT PER MEASURE, ETC.
if (m_readahead) {
// ok update the next buffer with data
m_fbufidx = nextbuffer;
// mprintf(0,"%c",m_fbufidx+'a');
// if our stream's current id does not match the streaming buffer's id, then we need to reallocate
// the stream buffer with the new memory size
if (m_buffer[m_fbufidx].id != (int)m_curid) {
if (m_buffer[m_fbufidx].data) {
mem_free(m_buffer[m_fbufidx].data);
}
m_buffer[m_fbufidx].data = mem_rmalloc<uint8_t>(m_bufsize);
}
m_buffer[m_fbufidx].nbytes = AudioStream::ReadFileData(m_fbufidx, m_bufsize);
m_buffer[m_fbufidx].flags = 0;
m_buffer[m_fbufidx].flags |= STRM_BUFF_USED;
m_buffer[m_fbufidx].id = (int)m_curid;
m_playbytesleft -= m_buffer[m_fbufidx].nbytes;
if (m_playbytesleft <= (m_bufsize / 4)) {
if (m_buffer[m_fbufidx].nbytes == 0) {
memset(m_buffer[m_fbufidx].data, 0, 4);
m_buffer[m_fbufidx].nbytes = 4;
}
m_buffer[m_fbufidx].flags |= STRM_BUFF_TERMINAL;
m_readahead = false;
m_readahead_finished_loop = true;
}
// looping?
if (CHECK_FLAG(m_buffer[m_fbufidx].flags, STRM_BUFF_TERMINAL)) {
if (m_loopcount == -1) {
m_buffer[m_fbufidx].flags |= STRM_BUFF_LOOPEND;
AudioStream::Reset();
} else if (m_loopcount > 0) {
m_buffer[m_fbufidx].flags |= STRM_BUFF_LOOPEND;
AudioStream::Reset();
m_loopcount--;
}
m_readahead_finished_loop = true;
}
}
}
#pragma optimize("", on)
///////////////////////////////////////////////////////////////////////////////
// decoder
int ADecodeFileRead(void *data, void *buf, uint32_t qty) {
AudioStream *stream = (AudioStream *)data;
int iqty = (int)qty;
if (iqty > stream->m_bytesleft) {
iqty = stream->m_bytesleft;
stream->m_bytesleft = 0;
} else {
stream->m_bytesleft -= iqty;
}
return stream->m_archive.Read((uint8_t *)buf, iqty);
}
// Router for stream callbacks.
// Invoked by sound system
void *AudioStreamCB(void *user_data, int handle, int *size) {
AudioStream *stream = (AudioStream *)user_data;
ASSERT(stream);
ASSERT(stream->m_streamindex == handle);
return stream->StreamCallback(size);
}
static float Stream_volume = 1.0f;
static float Stream_master_volume = 1.0f;
void StreamVolume(float master_volume) {
Stream_master_volume = master_volume;
User_audio_stream.SetVolume(Stream_volume * master_volume);
}
// these functions are the 'simplified' stream interface from Jeff (most of this code is from Jeff)
int StreamPlay(const char *filename, float volume, int flags) {
int retval = -1;
try {
flags = 0;
User_audio_stream.Close();
User_audio_stream.Open(filename, STRM_OPNF_ONETIME);
Stream_volume = volume;
User_audio_stream.SetVolume(Stream_volume * Stream_master_volume);
User_audio_stream.Play(true); // start this stream on the next frame.
retval = User_audio_stream.m_streamindex;
} catch (...) {
return -1;
}
return retval;
}
void StreamStop(int handle) {
if (handle > -1) {
if (AudioStream::m_streams[handle])
AudioStream::m_streams[handle]->Stop();
}
User_audio_stream.Close();
}
int StreamGetSoundHandle(int handle) {
if (handle > -1) {
if (AudioStream::m_streams[handle])
return AudioStream::m_streams[handle]->GetSoundHandle();
else
return -1;
}
return -1;
}