mirror of
https://github.com/kevinbentley/Descent3.git
synced 2025-01-22 19:55:23 +00:00
220108f650
Update libacm module, minor cleanups.
977 lines
30 KiB
C++
977 lines
30 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 "Macros.h"
|
|
#include "mem.h"
|
|
#include "pserror.h"
|
|
#include "streamaudio.h"
|
|
|
|
#define LOGFILE(_s)
|
|
// 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;
|
|
}
|
|
}
|
|
mprintf(0, "STRMAUD: AudioStream queue filled!\n");
|
|
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) {
|
|
LOGFILE((_logfp, "STRM[%d]:frame_state=%d\n", 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();
|
|
// mprintf(0, "STRM[%d]: Stopped!\n", strm->m_curid);
|
|
LOGFILE((_logfp, "STRM[%d]: Stopped!\n", strm->m_curid));
|
|
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
|
|
mprintf(0, "STRM[%d]: Illegal OSF (no channels?): %d.\n", m_curid, channels);
|
|
LOGFILE((_logfp, "STRM[%d]: Illegal OSF (no channels?): %d.\n", 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 {
|
|
mprintf(0, "STRM[%d]: Bad stream length %d\n", m_curid, filelen);
|
|
LOGFILE((_logfp, "STRM[%d]: Bad stream length %d\n", 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();
|
|
}
|
|
}
|
|
}
|
|
// mprintf(0,"STRM[%d]: Using buffer size of %d\n",m_curid, m_bufsize);
|
|
LOGFILE((_logfp, "STRM[%d]: Using buffer size of %d\n", m_curid, m_bufsize));
|
|
// 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 = (uint8_t *)mem_malloc(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;
|
|
// mprintf(0, "[%d]:pbytesleft=%d\n", m_curid, m_playbytesleft);
|
|
LOGFILE((_logfp, "[%d]:pbytesleft=%d\n", m_curid, m_playbytesleft));
|
|
if (m_playbytesleft <= (m_bufsize / 4)) {
|
|
// mprintf(0, "STRM[%d]: ", m_curid);
|
|
if (m_buffer[m_fbufidx].nbytes == 0) {
|
|
memset(m_buffer[m_fbufidx].data, 0, 4);
|
|
m_buffer[m_fbufidx].nbytes = 4;
|
|
mprintf(0, "making empty buffer and");
|
|
}
|
|
// mprintf(0, "TERMINAL buffer.\n");
|
|
LOGFILE((_logfp, "STRM[%d] TERMINAL buffer. ", m_curid));
|
|
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 {
|
|
mprintf(0, "STRMAUD:Eh? this should be reserved??\n");
|
|
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) {
|
|
// mprintf(0, "STRM[%d]: Callback entered while stopping! Force stop.\n", m_curid);
|
|
LOGFILE((_logfp, "STRM[%d]: Callback entered while stopping! Force stop.\n", m_curid));
|
|
} else {
|
|
// mprintf(0, "STRM[%d]: Stopping softly\n", m_curid);
|
|
LOGFILE((_logfp, "STRM[%d]: Stopping softly\n", m_curid));
|
|
// if (m_curid == 3) Int3();
|
|
}
|
|
// 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)) {
|
|
// mprintf(0, "STRM[%d]: Playing onetime buffer?\n",m_curid);
|
|
LOGFILE((_logfp, "STRM[%d]: Playing onetime buffer?\n", 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;
|
|
}
|
|
// mprintf(0, "STRM[%d]: Start stopping softly\n", m_curid);
|
|
LOGFILE((_logfp, "STRM[%d]: Start stopping softly\n", m_curid));
|
|
// *done = true;
|
|
}
|
|
if ((*size) == 0) {
|
|
// *done = true;
|
|
// mprintf(0, "STRM[%d]: Used buffer has 0 bytes!\n", m_curid);
|
|
LOGFILE((_logfp, "STRM[%d]: Used buffer has 0 bytes!\n", 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 = (uint8_t *)mem_malloc(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;
|
|
// mprintf(0, "[%d]:pbytesleft=%d\n", m_curid, m_playbytesleft);
|
|
LOGFILE((_logfp, "[%d]:pbytesleft=%d\n", m_curid, m_playbytesleft));
|
|
if (m_playbytesleft <= (m_bufsize / 4)) {
|
|
// mprintf(0, "STRM[%d]: ", m_curid);
|
|
if (m_buffer[m_fbufidx].nbytes == 0) {
|
|
memset(m_buffer[m_fbufidx].data, 0, 4);
|
|
m_buffer[m_fbufidx].nbytes = 4;
|
|
mprintf(0, "making empty buffer and");
|
|
}
|
|
// mprintf(0, "TERMINAL buffer.\n");
|
|
LOGFILE((_logfp, "STRM[%d]: TERMINAL buffer.\n", m_curid));
|
|
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, unsigned 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;
|
|
}
|