Descent3/mac/MACSTREAMAUDIO.CPP
Thomas Otto b58d3585bd Remove unused osMutex code
It was never Acquire'd, only Created and Destroyed; and it was
a stub on Linux and macOS.
2024-04-17 01:20:51 +02:00

1366 lines
38 KiB
C++

/*
* $Logfile: /DescentIII/Main/mac/MACSTREAMAUDIO.CPP $
* $Revision: 1.1.1.1 $
* $Date: 2003/08/26 03:58:15 $
* $Author: kevinb $
*
* Interface for streaming audio, compressed or not
*
* $Log: MACSTREAMAUDIO.CPP,v $
* Revision 1.1.1.1 2003/08/26 03:58:15 kevinb
* initial 1.5 import
*
*
* 2 4/12/00 7:08p Matt
* From Duane for 1.4
*
* 1 10/21/99 1:55p Kevin
* Mac!
*
* 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 <sound.h>
#include "streamaudio.h"
#include "pserror.h"
#include "CFILE.H"
#include "mem.h"
#include "Macros.h"
#include "ddio.h"
#include <stdlib.h>
#include <string.h>
#ifdef MACINTOSH
// #define __MACBUILD__
#include "adecode.h"
#endif
// #include "samirlog.h"
#define LOGFILE(_s)
#if defined(__LINUX__)
typedef struct {
bool empty;
} AudioDecoder;
typedef void *(*ad_malloc)(unsigned size);
typedef void (*ad_free)(void *p);
#endif
#if defined(__LINUX__)
typedef unsigned ReadFunction(void *data, void *buf, unsigned qty);
AudioDecoder *Create_AudioDecoder(ReadFunction *reader, void *data, unsigned *pChannels, unsigned *pSampleRate,
long *pSampleCount);
unsigned AudioDecoder_Read(AudioDecoder *ad, void *buf, unsigned qty);
void AudioDecoder_Close(AudioDecoder *ad);
void AudioDecoder_MallocFree(ad_malloc *fn_malloc, ad_free *fn_free);
#endif
void *MyDoubleBackProc(SndChannelPtr channel, SndDoubleBufferPtr dBuffer);
// 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
SndDoubleBackUPP gSndDoubleBackUPP = NULL;
// sets the low-level sound object
void AudioStream::InitSystem(llsSystem *sndsys) {
int i;
OSErr err;
if (AudioStream::m_ll_sndsys)
return;
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;
}
if (gSndDoubleBackUPP == NULL)
gSndDoubleBackUPP = NewSndDoubleBackProc(MyDoubleBackProc);
}
// 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((1, "STRMAUD: AudioStream queue filled!\n"));
return false;
}
void AudioStream::DeactivateStream(AudioStream *stream) {
if (stream->m_streamindex > -1) {
mprintf((1, "Deactivateing: STRM[%d]\n", stream->m_streamindex));
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();
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) {
mprintf((1, "frame:STRM[%d]: NEW m_state=%d\n", strm->m_curid, strm->m_state));
}
if (strm->m_state == STRM_PLAYING) {
// mprintf((1, "frame:STRM[%d]: Playing\n", strm->m_curid));
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;
strm->UpdateData();
}
// else if (strm->m_state == STRM_STOPPED) {
// if (CHECK_FLAG(strm->m_flags,STRM_OPNF_ONETIME))
// strm->Close();
// }
else if (strm->m_state == STRM_STOPPING) {
if (CHECK_FLAG((strm->doubleHeader.dbhBufferPtr[0])->dbFlags, dbLastBuffer) && !strm->IsPlaying()) {
mprintf((1, "frame STRM[%d]: chan 0x%X StopING! Last Buffer read!!\n", strm->m_curid, strm->strm_channel));
strm->Stop();
if (CHECK_FLAG(strm->m_flags, STRM_OPNF_ONETIME)) {
mprintf((1, "Frame: STRM[%d] stoped in onetime so closing\n", strm->m_curid));
strm->Close();
}
strm->m_readahead = false;
}
} else if (strm->m_state == STRM_INVALID) {
mprintf((1, "STRM[%d]: INVALID!!\n", strm->m_curid));
strm->DeactivateStream(strm);
} else if (strm->m_start_on_frame) {
mprintf((1, "frame: STRM[%d]: start on frame playstream\n", strm->m_curid));
strm->m_llshandle = strm->PlayStream(&strm->m_playinfo);
if (strm->m_llshandle > -1) {
mprintf((1, "frame: STRM[%d]:handle %d\n", strm->m_curid, strm->m_llshandle));
// 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;
mprintf((1, "frame: ONLY ONE BUFFERS WORTH nbufs == 1 & !looped\n"));
} else {
strm->m_state = STRM_PLAYING;
mprintf((1, "frame: STRM[%d]: playing\n", strm->m_curid));
}
}
strm->m_start_on_frame = false;
strm->m_start_on_frame_looped = false;
} else {
// mprintf((1, "frame: STRM[%d]: NOT DOING ANYTHING WHOOPS!!!\n", strm->m_curid));
}
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;
strm_channel = NULL;
OSErr err;
err = SndNewChannel(&strm_channel, sampledSynth, initStereo + initNoInterp, NULL);
if (err)
mprintf((1, "unable to open stream channel\n"));
}
AudioStream::~AudioStream() {
AudioStream::Close();
if (strm_channel == 0)
return;
SndDisposeChannel(strm_channel, true);
// mprintf((0, "distroying: STRM[%d]:\n", this->m_curid));
}
// 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;
mprintf((1, "SetLoopCount: %d\n", m_loopcount));
}
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;
SndCommand sndCommand;
OSErr err;
if (m_ll_sndsys) {
if (strm_channel == 0)
return;
short one_side = vol * 0x0080;
long both_side = (one_side << 16) | one_side;
sndCommand.cmd = volumeCmd;
sndCommand.param1 = 0;
sndCommand.param2 = both_side;
err = SndDoImmediate(strm_channel, &sndCommand);
if (err)
mprintf((0, "stream channel is corrupt: volume\n"));
}
}
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;
mprintf((0, "Open new stream %s flags %d sof %d\n", filename, open_flags, m_start_on_frame));
// 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.
}
SetVolume(m_volume);
// 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;
mprintf((1, "opening: STRM[%d]: STOPPED sof %d\n", this->m_curid, m_start_on_frame));
m_state = STRM_STOPPED;
// m_state = STRM_PENDING;
return true;
}
return false;
}
// deallocates all stream buffers and decoder.
void AudioStream::Close() {
int i;
if (m_archive.Opened()) {
mprintf((0, "Close: STRM[%d]: \n", this->m_curid));
// 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) {
AudioDecoder_Close(m_decoder);
m_decoder = NULL;
}
}
if (doubleHeader.dbhBufferPtr[0]) {
mem_free(doubleHeader.dbhBufferPtr[0]);
doubleHeader.dbhBufferPtr[0] = NULL;
}
if (doubleHeader.dbhBufferPtr[1]) {
mem_free(doubleHeader.dbhBufferPtr[1]);
doubleHeader.dbhBufferPtr[1] = 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() {
return true;
if (m_state == STRM_STOPPED) {
if (!m_readahead || m_fbufidx == (STRM_BUFCOUNT - 1)) {
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
bool AudioStream::ReopenDigitalStream(ubyte fbufidx, int nbufs) {
const tOSFDigiHdr *digihdr = (const tOSFDigiHdr *)m_archive.StreamHeader();
int bytesize, granularity;
long sample_count;
unsigned channels;
m_bytesleft = m_archive.StreamLength();
m_readahead = false;
mprintf((1, "reopen: %d total length %d\n", m_curid, m_bytesleft));
// free current decoder.
if (m_decoder) {
AudioDecoder_Close(m_decoder);
m_decoder = NULL;
}
// instatiate decompression facility or use raw source data
if (m_archive.StreamComp() == OSF_DIGIACM_STRM) {
unsigned sample_rate;
m_decoder = Create_AudioDecoder(ADecodeFileRead, this, &channels, &sample_rate, &sample_count);
if (!m_decoder) {
AudioDecoder_Close(m_decoder);
m_decoder = NULL;
return false;
}
// m_total_size = sample_count;
} else {
return false;
}
// convert bufsize to true bufsize (bufsize = samples per measure for now.)
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));
return false;
}
long bytes_per_buf = (SAMPLES_PER_STREAM_BUF * granularity);
long 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));
return false;
}
}
mprintf((1, "Reopen: nbuffers %d of %d each\n", nbuffers, bytes_per_buf));
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((1, "reopen: %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 = (ubyte *)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((1, "reopen: STRM[%d] m_buffer[%d] 0x%X bytes readin 0x%x left %X\n", this->m_curid, m_fbufidx, m_bufsize,
m_playbytesleft, m_buffer[m_fbufidx].flags));
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"));
m_buffer[m_fbufidx].flags |= STRM_BUFF_TERMINAL;
m_readahead = false;
m_readahead_finished_loop = true;
}
m_nbufs++;
m_fbufidx = (m_fbufidx + 1) % STRM_BUFCOUNT;
// T2 m_fbufidx = (m_fbufidx)?0:1;
}
// 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;
mprintf((1, "Play: STRM[%d]: called w sof %d\n", this->m_curid, start_on_frame));
if (m_state == STRM_INVALID) {
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) {
mprintf((1, "Play: STRM[%d]: Not Opened stop???\n", this->m_curid));
AudioStream::Stop();
}
if (m_playcount > 0) {
mprintf((1, "Play: STRM[%d]: m_playcount > 0 resetting!!\n", this->m_curid));
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;
}
}
}
}
// invoke low level sound system.
memset(&m_playinfo, 0, sizeof(m_playinfo));
#if 0
m_playinfo.samples_per_22khz_sample = 1.0;
m_playinfo.left_volume = m_playinfo.right_volume = m_volume;
#endif
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.priority = SND_PRIORITY_CRITICAL; // this stream must play.
m_start_on_frame = start_on_frame;
m_start_on_frame_looped = looped;
mprintf((1, "Play: STRM[%d]: PlayStream sof %d\n", this->m_curid, m_start_on_frame));
if (!m_start_on_frame) {
mprintf((1, "play: size %d bufsize %d\n", m_playinfo.m_stream_size, m_playinfo.m_stream_bufsize));
m_llshandle = AudioStream::PlayStream(&m_playinfo);
mprintf((1, "Play: STRM[%d]: PlayStream handle %d\n", this->m_curid, m_llshandle));
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;
mprintf((1, " not looped so stoping IMEDIATLY\n"));
} else {
m_state = STRM_PLAYING;
mprintf((1, "looped so start playing\n"));
}
return true;
}
}
return true;
}
bool AudioStream::IsPlaying() {
SCStatus channel_status;
SndChannelStatus(strm_channel, sizeof(SCStatus), &channel_status);
return (channel_status.scChannelBusy);
}
// pauses a stream
void AudioStream::Pause() {
if (m_state != STRM_PLAYING) {
return;
}
m_state = STRM_PAUSED;
SndCommand sndCommand;
OSErr err;
sndCommand.cmd = pauseCmd;
sndCommand.param1 = 0;
sndCommand.param2 = 0L;
err = SndDoImmediate(strm_channel, &sndCommand);
if (err)
mprintf((1, "stream channelis corrupt: pause\n"));
mprintf((0, "Paused stream\n"));
}
// pauses a stream
void AudioStream::Resume() {
if (m_state != STRM_PAUSED) {
return;
}
SndCommand sndCommand;
OSErr err;
sndCommand.cmd = resumeCmd;
sndCommand.param1 = 0;
sndCommand.param2 = 0L;
err = SndDoImmediate(strm_channel, &sndCommand);
if (err)
mprintf((1, "stream channelis corrupt: resume\n"));
int sflag = SIF_STREAMING_16_M;
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;
}
memset(&m_playinfo, 0, sizeof(m_playinfo));
#if 0
m_playinfo.samples_per_22khz_sample = 1.0;
m_playinfo.left_volume = m_playinfo.right_volume = m_volume;
#endif
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.priority = SND_PRIORITY_CRITICAL; // this stream must play.
mprintf((1, "play: size %d bufsize %d\n", m_playinfo.m_stream_size, m_playinfo.m_stream_bufsize));
for (int i = 0; i <= 1; ++i) {
doubleHeader.dbhBufferPtr[i]->dbUserInfo[0] = (long)&m_playinfo;
AudioStream::MyDoubleBackStart(strm_channel, doubleHeader.dbhBufferPtr[i]); // initialize the buffers
}
err = SndPlayDoubleBuffer(strm_channel, &doubleHeader);
m_state = STRM_PLAYING;
mprintf((1, "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) {
SndCommand sndCommand;
OSErr err;
m_start_on_frame = false;
if (strm_channel == 0)
return;
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) {
sndCommand.cmd = flushCmd;
sndCommand.param1 = 0;
sndCommand.param2 = 0L;
err = SndDoImmediate(strm_channel, &sndCommand);
sndCommand.cmd = quietCmd;
sndCommand.param1 = 0;
sndCommand.param2 = 0L;
err = SndDoImmediate(strm_channel, &sndCommand);
m_state = STRM_STOPPED;
mprintf((1, "Stop: STRM[%d]\n", m_curid));
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) {
unsigned channels;
unsigned sample_rate;
long sample_count;
AudioDecoder_Close(m_decoder);
m_decoder = Create_AudioDecoder(ADecodeFileRead, this, &channels, &sample_rate, &sample_count);
}
}
//////////////////////////////////////////////////////////////////////////////
// invoked by AudioStreamCB.
void *AudioStream::StreamCallback(int *size) {
void *data = NULL;
ubyte nextbuffer = (m_sbufidx + 1) % STRM_BUFCOUNT;
// mprintf((0, "callback: m_state %d flag %d %d\n", m_state,m_buffer[0].flags, m_buffer[1].flags));
if (m_state == STRM_PAUSED) {
// mprintf((0, "callback: %d paused\n", m_curid));
*size = 1;
m_buffer[0].flags = STRM_BUFF_USED;
m_buffer[1].flags = STRM_BUFF_USED;
m_buffer[0].nbytes = m_bufsize;
m_buffer[1].nbytes = m_bufsize;
return NULL;
}
// we're not done yet.
// adjust sound buffer to the next buffer
if (m_state == STRM_STOPPED || m_state == STRM_STOPPING) {
// 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, "**> stream %d (nextbuffer %d) (flags %x) unused stoping!! \n", m_sbufidx, nextbuffer,
// m_buffer[nextbuffer].flags)); mprintf((0, "STRM[%d]: Playing onetime buffer?\n",m_curid));
m_state = STRM_STOPPED;
*size = 0;
return NULL;
}
// mark played buffer as unused.
m_buffer[m_sbufidx].flags = 0;
m_buffer[m_sbufidx].nbytes = 0;
m_sbufidx = nextbuffer;
// 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;
// mprintf((0, "cb size %x\n", m_buffer[m_sbufidx].nbytes));
// mprintf((0, "callback: m_buffer[%d] nbytes 0x%x data 0x%X\n", m_sbufidx, m_buffer[m_sbufidx].nbytes,
// m_buffer[m_sbufidx].data));
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;
// mprintf((0, "callback: flags == STRM_BUFF_TERMINAL stoping NOT loopend\n"));
}
if (m_stopnextmeasure) {
m_state = STRM_STOPPING;
// mprintf((0, "callback: flags == STRM_BUFF_TERMINAL stoping stopnexmesure\n"));
}
}
if ((*size) == 0) {
// mprintf((0, "STRM[%d]: Used buffer has 0 bytes!\n", m_curid));
m_state = STRM_STOPPING;
data = NULL;
}
return data;
}
// 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 AudioDecoder_Read(m_decoder, m_buffer[buf].data, len);
} else {
// 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);
}
}
// reads in decompressed raw data.
int AudioStream::ReadFileDirect(char *buf, int len) {
if (!m_archive.Opened())
return 0;
if (m_decoder) {
// We have a compressed stream
return AudioDecoder_Read(m_decoder, buf, len);
} else {
// We have a non-compressed stream
if (len > m_bytesleft) {
len = m_bytesleft;
m_bytesleft = 0;
} else
m_bytesleft -= len;
return m_archive.Read((unsigned char *)buf, len);
}
}
// TURN OFF OPTIMIZATIONS HERE. Placement of instructions is VERY IMPORTANT here. synched with StreamCallback,
// which also has optimizations off.
// updates file buffers
void AudioStream::UpdateData() {
int nextbuffer = ((m_fbufidx + 1) % STRM_BUFCOUNT);
// mprintf((1, "update: nextbuf %d flags %d\n", nextbuffer, m_buffer[nextbuffer].flags));
// mprintf((0, "UD proc_sw %d m_state %d flags %d\n", proc_sw, m_state, m_buffer[nextbuffer].flags));
// 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 = (ubyte *)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((1, "update: %d m_buffer[%d] 0x%X bytes readin 0x%x left flag %d\n", m_curid, m_fbufidx, m_bufsize,
m_playbytesleft, m_buffer[m_fbufidx].flags));
if (m_playbytesleft <= (m_bufsize / 4)) {
mprintf((1, "update: %d last buffer has %d left\n", m_curid, m_playbytesleft));
if (m_buffer[m_fbufidx].nbytes == 0) {
memset(m_buffer[m_fbufidx].data, 0, 4);
m_buffer[m_fbufidx].nbytes = 4;
mprintf((1, "update: %d PAD last buffer with %d\n", m_curid, m_buffer[m_fbufidx].nbytes));
}
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)) {
mprintf((1, "update: loopcount %d\n", m_loopcount));
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;
}
}
}
int AudioStream::PlayStream(play_information *play_info) {
ASSERT(play_info != NULL);
SndDoubleBufferPtr doubleBuffer;
OSStatus err;
switch (play_info->m_stream_format) {
case SIF_STREAMING_8_M:
doubleHeader.dbhSampleSize = 8;
doubleHeader.dbhNumChannels = 1;
break;
case SIF_STREAMING_8_S:
doubleHeader.dbhSampleSize = 8;
doubleHeader.dbhNumChannels = 2;
break;
case SIF_STREAMING_16_M:
doubleHeader.dbhSampleSize = 16;
doubleHeader.dbhNumChannels = 1;
break;
case SIF_STREAMING_16_S:
doubleHeader.dbhSampleSize = 16;
doubleHeader.dbhNumChannels = 2;
break;
}
doubleHeader.dbhSampleRate = rate22050hz;
doubleHeader.dbhCompressionID = notCompressed;
doubleHeader.dbhPacketSize = 0;
doubleHeader.dbhDoubleBack = gSndDoubleBackUPP;
for (int i = 0; i <= 1; ++i) {
doubleBuffer = (SndDoubleBufferPtr)mem_malloc(sizeof(SndDoubleBuffer) + play_info->m_stream_bufsize);
if (doubleBuffer == nil)
return 0;
// Debugger();
doubleBuffer->dbNumFrames = 0;
doubleBuffer->dbFlags = 0;
doubleBuffer->dbUserInfo[0] = (long)play_info;
mprintf((1, "PlayStream bufsize %d\n", play_info->m_stream_bufsize));
AudioStream::MyDoubleBackStart(strm_channel, doubleBuffer); // initialize the buffers
doubleHeader.dbhBufferPtr[i] = doubleBuffer;
}
err = SndPlayDoubleBuffer(strm_channel, &doubleHeader);
mprintf((1, "PlayStream:STRM[%d]: channel 0x%X SndPlayDoubleBuffer 0x%X & 0x%X\n", this->m_curid, strm_channel,
doubleHeader.dbhBufferPtr[0], doubleHeader.dbhBufferPtr[1]));
strm_channel->userInfo = play_info->m_stream_handle;
return (play_info->m_stream_handle);
}
void *MyDoubleBackProc(SndChannelPtr channel, SndDoubleBufferPtr dBuffer) {
play_information *play_info;
int bytesFromBuffer;
Ptr dataPtr;
play_info = (play_information *)dBuffer->dbUserInfo[0];
if ((dBuffer->dbFlags) & dbLastBuffer)
return NULL;
dataPtr = (Ptr)play_info->m_stream_cback(play_info->user_data, play_info->m_stream_handle, &bytesFromBuffer);
dBuffer->dbFlags = (dBuffer->dbFlags) | dbBufferReady;
if (dataPtr) {
memcpy(&dBuffer->dbSoundData[0], dataPtr, bytesFromBuffer);
// BlockMove (dataPtr, &dBuffer->dbSoundData[0], bytesFromBuffer);
dBuffer->dbNumFrames = bytesFromBuffer;
if (play_info->m_stream_format >= SIF_STREAMING_16_M)
dBuffer->dbNumFrames = dBuffer->dbNumFrames >> 1;
if (play_info->m_stream_format == SIF_STREAMING_16_S)
dBuffer->dbNumFrames = dBuffer->dbNumFrames >> 1;
} else if (bytesFromBuffer) {
dBuffer->dbFlags = 0;
dBuffer->dbNumFrames = 0;
} else {
dBuffer->dbFlags = (dBuffer->dbFlags) | dbLastBuffer;
dBuffer->dbNumFrames = 0;
}
return dataPtr;
}
void *AudioStream::MyDoubleBackStart(SndChannelPtr channel, SndDoubleBufferPtr dBuffer) {
int bytesToCopy;
play_information *play_info;
play_info = (play_information *)dBuffer->dbUserInfo[0];
mprintf((1, "MyDoubleBackStart: bufsize %d\n", play_info->m_stream_bufsize));
bytesToCopy = play_info->m_stream_bufsize / 2; // split the buffer in half
memcpy(&dBuffer->dbSoundData[0], play_info->m_stream_data, bytesToCopy);
// BlockMove (play_info->m_stream_data, &dBuffer->dbSoundData[ 0], bytesToCopy);
play_info->m_stream_data = (void *)((int)(play_info->m_stream_data) + bytesToCopy);
dBuffer->dbNumFrames = bytesToCopy;
if (play_info->m_stream_format >= SIF_STREAMING_16_M)
dBuffer->dbNumFrames = dBuffer->dbNumFrames / 2;
if (play_info->m_stream_format == SIF_STREAMING_16_S)
dBuffer->dbNumFrames = dBuffer->dbNumFrames / 2;
dBuffer->dbFlags = (dBuffer->dbFlags) | dbBufferReady;
return NULL;
}
// 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);
// return stream->NewStreamCallback(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);
}
///////////////////////////////////////////////////////////////////////////////
// decoder
unsigned 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((ubyte *)buf, iqty);
}
int voice_stream = -1;
// 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;
voice_stream = retval;
} catch (...) {
return -1;
}
return retval;
}
void StreamStop(int handle) {
if (handle == 0 && voice_stream > -1) {
if (AudioStream::m_streams[voice_stream])
AudioStream::m_streams[voice_stream]->Stop();
} else 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;
}