class QChannel | .h |
constructor | QChannel() |
destructor | ~QChannel() |
SetVolume | void SetVolume(int v,int iterations) |
IsTransitioning | bool IsTransitioning() Returns TRUE if this channel is doing a volume transition |
Flat functions |
ProcessAudioCommand | static void ProcessAudioCommand() |
Volumize | static void Volumize(short *smp,int vol,int n) |
AudioProcess | static void AudioProcess(void *) This thread feeds the audio hardware continuously Should be run with root priority |
RunAP | void RunAP() Start up the audio process if not already started |
QSampleKillProcess | void QSampleKillProcess() |
class QSample | .h |
constructor | QSample(cstring fname,int _flags) |
destructor | ~QSample() |
IsOK | bool IsOK() Returns TRUE if the sample was succesfully loaded, or the file is open (in case it is a streaming sample) Don't use GetBuffer() for this, because that doesn't work for streaming samples. |
SampleWidth | int SampleWidth(void) Returns sample frame width in #bits (!) |
GetNoofFrames | int GetNoofFrames() Returns #frames (frames = samples * channels!) |
Load | bool Load(cstring fname) Read a sample. Returns FALSE in case of failure. |
IsPlaying | bool IsPlaying() |
Play | QChannel *Play(int vol) Play the sample; beginning at (dynamic) volume 'vol' |
Stop | void Stop() Stop (looping) sample; also for one-shot samples |
Loop | void Loop(bool yn) |
Volumize | void Volumize(int perc) Modify sample to match new volume percentage (was called SetVolume() once) Doesn't work for streaming audio Bug: assumes 16-bit stereo |
Flat functions |
QSampleSetStreamSize | void QSampleSetStreamSize(int size) Before playing any samples, you may set the stream buffer size |
QSampleSetOffset | void QSampleSetOffset(int off) |
ReadMMIO | HRESULT ReadMMIO( HMMIO hmmioIn, MMCKINFO* pckInRIFF, WAVEFORMATEX** ppwfxInfo ) |
WaveOpenFile | HRESULT WaveOpenFile( CHAR* strFileName, HMMIO* phmmioIn, WAVEFORMATEX** ppwfxInfo, MMCKINFO* pckInRIFF ) |
WaveStartDataRead | HRESULT WaveStartDataRead( HMMIO* phmmioIn, MMCKINFO* pckIn, MMCKINFO* pckInRIFF ) |
WaveReadFile | HRESULT WaveReadFile( HMMIO hmmioIn, UINT cbRead, BYTE* pbDest, MMCKINFO* pckIn, UINT* cbActualRead ) |
class CWaveSoundRead | .h |
constructor | CWaveSoundRead() |
destructor | ~CWaveSoundRead() |
Open | HRESULT Open( CHAR* strFilename ) |
Reset | HRESULT Reset() |
Read | HRESULT Read( UINT nSizeToRead, BYTE* pbData, UINT* pnSizeRead ) |
Close | HRESULT Close() |
/*
* QSample - samples wrapping
* 05-07-96: Created!
* 16-12-96: Revised completely to allow for streaming audio (disk & mem)
* Hardcoded to use 16-bit samples, 2 channels (stereo) always (!).
* 19-04-99: QChannel usage for affecting soundplay WHILE playing
* 14-09-99: Some major changes; Play() now needs start volume (needed
* for fading in loops etc). SetVolume() has been renamed to Volumize() to
* indicate better that it modifies the sample. Dynamic volume changes must
* use the QChannel interface.
* BUGS:
* - All samples are expected to be 16-bit, stereo (4 bytes/frame, 2chn)
* - Looping is done on BYTES_PER_FRAME segments; clicks may occur at loop(!)
* FUTURE:
* - Perhaps kill the audioprocess and associated ports/configs somewhere.
* - Use QThread() to start process? (prctl SETEXITSIG!)
* NOTES:
* - After opening the audioport, the ALsetchannels() may not be modified
* (setwidth() IS dynamic)
* - poll() is used to be multitasking-friendly. However, poll() returns
* immediately if the audio is not constantly kept busy. Hope it doesn't
* cost much writing zeroes all the time.
* (C) MarketGraph/RVG 1996
*/
#include <dmedia/audio.h>
#include <dmedia/audiofile.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef WIN32
#else
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <limits.h>
#include <unistd.h> // sginap()
#include <sys/prctl.h>
#include <sys/schedctl.h>
#include <signal.h>
#include <ulocks.h>
#include <bstring.h>
#endif
#include <qlib/sample.h>
#include <qlib/debug.h>
DEBUG_ENABLE
// Default stream buffer size increased from 2048 to 4096 on 19-4-99
// because of FX like volume
#define DEFAULT_STREAMSIZE 4096
//#define DEFAULT_STREAMSIZE 2048
// Default sample offset to match video output
#define DEFAULT_OFFSET 5500
#define MAX_CHANNEL 4 // Indy has 4, I believe
#define QUEUESIZE 70000 // Used audio queue (samples; /2~STREAM_BUFSIZE)
#define STREAM_BUFSIZE pSBufSize // Streaming buffer (read-ahead) in frames
#define BYTES_PER_FRAME 4 // For this class; 16-bit stereo
#define CMD_PLAY 0 // 'cmd' values for comm. with AudioProcess()
#define CMD_STOP 1
//// shared data used by the audio process and the main thread
static bool procStarted;
//static struct __APdata ad[MAX_CHANNEL];
static QChannel *ad[MAX_CHANNEL];
#ifndef WIN32
static usema_t *sendSema,*rcvSema; // Command processing
#endif
static int cmd; // Command to execute
static QSample *smp; // Sample which the command uses
static int startVolume; // Volume at which to begin play
static QChannel *playChannel; // Which channel was used to play
//// end shared data
#ifndef WIN32
static pid_t procID; // Process ID of sample process
#endif
static int apRR; // Audioport selection
static int pSBufSize=DEFAULT_STREAMSIZE; // Stream buffer size
static int smpOffset=DEFAULT_OFFSET; // Sample offset
#ifdef TEST
proftime_t t;
#endif
#ifndef WIN32
/***********
* QCHANNEL *
***********/
#undef DBG_CLASS
#define DBG_CLASS "QChannel"
QChannel::QChannel()
{
DBG_C("ctor")
streamBuf=0;
streamBufSize=0;
streamBufSizeFrames=0;
queuesize=0;
frames=0;
curFrame=0;
port=0;
volume=256; // Full
//audioConfig=0;
//audioPort=0;
port=new QAudioPort();
busy=FALSE;
smp=0;
// FX
volFrom=volTo=0;
volCur=0; volTotal=0;
}
QChannel::~QChannel()
{
DBG_C("dtor")
}
void QChannel::SetVolume(int v,int iterations)
{
DBG_C("SetVolume")
if(v<0)v=0; else if(v>256)v=256;
if(iterations==0)
{ volume=v;
} else
{ // Slowly go there
volFrom=volume;
volTo=v;
volCur=0;
volTotal=iterations; // Thread will see this
}
}
bool QChannel::IsTransitioning()
// Returns TRUE if this channel is doing a volume transition
{
if(volTotal)return TRUE;
return FALSE;
}
#undef DBG_CLASS
#define DBG_CLASS "QSample"
/************************
* SPROC'd AUDIO PROCESS *
************************/
static void ProcessAudioCommand()
{ long pvbuf[4];
int i;
static ALconfig audioconfig;
DBG_C_FLAT("ProcessAudioCommand");
//printf("processaudiocmd %d\n",cmd);
if(!audioconfig)
audioconfig=ALnewconfig();
switch(cmd)
{ case CMD_PLAY:
// Select an audioport that is not busy
for(i=0;i<MAX_CHANNEL;i++)
if(!ad[i]->busy)
{ apRR=i; goto found; }
// No-one available; overrule a used port
//printf("QSample: overrule\n");
for(i=0;i<MAX_CHANNEL;i++)
{ // Avoid streaming samples; uninterrupted background music mostly
if(ad[i]->smp->flags&QSAMPLE_STREAM)continue;
// Avoid looping samples
if(ad[i]->smp->flags&QSAMPLE_LOOP)continue;
// Kick out currently playing sample
ad[i]->smp->busy=FALSE;
apRR=i; goto found;
}
// Really no ports available; forget it
apRR=(apRR+1)%MAX_CHANNEL;
return;
found:
//printf(" (audio) play sample\n");
#ifdef TEST
profStart(&t);
#endif
playChannel=ad[apRR];
//qdbg("CMD_PLAY: playChannel=%p\n",playChannel);
ad[apRR]->busy=TRUE;
ad[apRR]->smp=smp;
ad[apRR]->curFrame=0;
ad[apRR]->SetVolume(startVolume);
ad[apRR]->frames=smp->frames;
// Set output rate
pvbuf[0]=AL_OUTPUT_RATE; pvbuf[1]=(long)ad[apRR]->smp->file_rate;
ALsetparams(AL_DEFAULT_DEVICE,pvbuf,2);
// Set config (x-bits, st/mo)
ALsetwidth(audioconfig,ad[apRR]->smp->sampwidth/8);
ALsetchannels(audioconfig,ad[apRR]->smp->samps_per_frame);
ALsetqueuesize(audioconfig,QUEUESIZE);
ALsetconfig(ad[apRR]->port->GetAudioPort(),audioconfig);
//ALfreeconfig(audioconfig);
#ifdef TEST
profReport(&t,"ProcessAudioCommand audio setup");
#endif
break;
}
}
static void Volumize(short *smp,int vol,int n)
{
//DBG_C("Volumize")
for(;n>=0;n--)
{ *smp=(*smp*vol)>>8;
smp++;
}
}
static void AudioProcess(void *)
// This thread feeds the audio hardware continuously
// Should be run with root priority
{
int i;
struct pollfd pollFD[MAX_CHANNEL+1]; // One for semaphore
DBG_C_FLAT("AudioProcess")
// Set high priority execution
//qdbg("QSample: AudioProcess() running\n");
#define LINGO
#define PRI_SAMPLE 32 // Before app itself
#ifdef LINGO
// Boost thread priority
if(setreuid(getuid(),0)==-1)
{ qdbg("QSample: can't setreuid(getuid,0) to become root\n");
}
schedctl(NDPRI,0,PRI_SAMPLE);
// Continue as user that has run the program (file permissions)
setreuid(getuid(),getuid());
#else
if(!QSetThreadPriority(PRI_MEDIUM))
{ qwarn("Couldn't set audio thread priority; run as root");
}
#endif
// Open the audio ports
for(i=0;i<MAX_CHANNEL;i++)
{ ad[i]=new QChannel();
}
for(i=0;i<MAX_CHANNEL;i++)
{
ad[i]->port->SetQueueSize(QUEUESIZE);
if(!ad[i]->port->Open())
qerr("QSample: Can't open audioport");
// Init stream buffer
ad[i]->streamBufSizeFrames=STREAM_BUFSIZE;
ad[i]->streamBufSize=ad[i]->streamBufSizeFrames*BYTES_PER_FRAME;
ad[i]->streamBuf=(char*)calloc(1,ad[i]->streamBufSize);
ad[i]->queuesize=ad[i]->port->GetQueueSize();
}
/** Prepare to poll on the channels **/
pollFD[0].fd=usopenpollsema(sendSema,0777);
pollFD[0].events=POLLIN;
for(i=0;i<MAX_CHANNEL;i++)
{
pollFD[i+1].fd=ad[i]->port->GetFD();
pollFD[i+1].events=POLLOUT;
}
/** Wait for first event to happen **/
uspsema(sendSema);
// Signal parent thread that we are active
procStarted=1;
/** Continually wait for channel draining and audio commands **/
while(1)
{
long fillPoint;
for(i=0;i<MAX_CHANNEL;i++)
{ fillPoint=ad[i]->queuesize-ad[i]->streamBufSizeFrames/4;
ad[i]->port->SetFillPoint(fillPoint);
}
poll(pollFD,MAX_CHANNEL+1,-1);
// Check for audio command
if(pollFD[0].revents&POLLIN)
{ ProcessAudioCommand();
uspsema(sendSema);
usvsema(rcvSema); // Let parent thread continue
//printf(" (audio) cmd processed\n");
}
for(i=0;i<MAX_CHANNEL;i++)
{ if(!(pollFD[i+1].revents&POLLOUT))
continue;
//qdbg("AProc: port[%d]: busy=%d\n",i,ad[i]->busy);
if(!ad[i]->busy)
{ // Write something to keep the audio busy for a while
//qdbg("> not busy; keep it doing something\n");
//ad[i]->port->WriteSamps(ad[i]->streamBuf,ad[i]->streamBufSizeFrames);
ad[i]->port->ZeroFrames(ad[i]->streamBufSizeFrames);
continue;
}
#ifdef TEST
//profReport(&t," AP pollout");
#endif
// Get sample data, be it from disk or from mem
int toPlay;
redo_loop:
toPlay=ad[i]->streamBufSizeFrames;
if(toPlay+ad[i]->curFrame>ad[i]->frames)
{ toPlay=ad[i]->frames-ad[i]->curFrame;
}
//printf("toPlay=%d\n",toPlay);
// Handle volume transitions
if(ad[i]->volTotal)
{
//qdbg("voltrans: filled=%d\n",ad[i]->port->GetFilled());
if(++ad[i]->volCur==ad[i]->volTotal)
{ ad[i]->volume=ad[i]->volTo;
ad[i]->volTotal=0;
} else
{ ad[i]->volume=ad[i]->volFrom+
ad[i]->volCur*(ad[i]->volTo-ad[i]->volFrom)/ad[i]->volTotal;
//qdbg("volume transition; from/to %d/%d, cur=%d, interpol %d/%d\n",ad[i]->volFrom,ad[i]->volTo,ad[i]->volume,ad[i]->volCur,ad[i]->volTotal);
}
}
if(toPlay>BYTES_PER_FRAME-1)
{ if(ad[i]->smp->flags&QSAMPLE_STREAM)
{ // Stream from disk
//printf("> stream from disk\n");
AFseekframe(ad[i]->smp->afh,AF_DEFAULT_TRACK,ad[i]->curFrame);
//printf("> read %d frames @%d\n",toPlay,ad[i]->curFrame);
AFreadframes(ad[i]->smp->afh,AF_DEFAULT_TRACK,ad[i]->streamBuf,
toPlay);
// Play it volumized
if(ad[i]->volume==0)
{ // Don't waste time on setting volume to 0; write silence
ad[i]->port->ZeroFrames(toPlay);
} else if(ad[i]->volume!=256)
{ // Bring sample down to right volume
Volumize((short*)ad[i]->streamBuf,ad[i]->volume,toPlay*2);
ad[i]->port->WriteSamps(ad[i]->streamBuf,toPlay*2);
} else
{ // Just output original sample
ad[i]->port->WriteSamps(ad[i]->streamBuf,toPlay*2);
}
} else
{ // Stream directly from memory
char *curMem;
//printf("> direct from mem\n");
curMem=(char*)ad[i]->smp->mem;
curMem+=ad[i]->curFrame*BYTES_PER_FRAME;
if(ad[i]->volume==0)
{ // Don't waste time on setting volume to 0; write silence
ad[i]->port->ZeroFrames(toPlay);
} else if(ad[i]->volume!=256)
{ // Bring sample down to right volume
bcopy(curMem,ad[i]->streamBuf,toPlay*2*2);
Volumize((short*)ad[i]->streamBuf,ad[i]->volume,toPlay*2);
//qdbg("volumize: filled=%d\n",ad[i]->port->GetFilled());
// Play FX-ed sample
curMem=ad[i]->streamBuf;
//qdbg("(filled=%d)\n",ad[i]->port->GetFilled());
ad[i]->port->WriteSamps(curMem,toPlay*2);
} else
{ // Just output original sample
ad[i]->port->WriteSamps(curMem,toPlay*2);
}
}
ad[i]->curFrame+=toPlay;
} else
{ // Check for looping sample
if(ad[i]->smp->flags&QSAMPLE_LOOP)
{ ad[i]->curFrame=0;
// Avoid endless loop for zero-sized samples
if(ad[i]->frames>=BYTES_PER_FRAME)
{ //printf("QSample: loop\n");
goto redo_loop;
}
}
ad[i]->busy=0;
ad[i]->smp->busy=FALSE;
}
}
}
}
void RunAP()
// Start up the audio process if not already started
{
static short int signum=9;
// Spawn audio process if not done already
if(!procStarted)
{ // Initialize audio process spawn
usptr_t *arena;
// Don't let other processes access the arena
usconfig(CONF_ARENATYPE,US_SHAREDONLY);
arena=usinit(tmpnam(0));
sendSema=usnewpollsema(arena,0);
rcvSema =usnewsema(arena,1);
prctl(PR_SETEXITSIG,(void*)signum);
//printf("QSample: sproc()\n");
procID=sproc(AudioProcess,PR_SALL);
// Must wait until audioprocess has done uspsema(sendSema)
while(!procStarted)
sginap(1);
}
}
void QSampleKillProcess()
{
if(!procStarted)return;
kill(procID,SIGKILL);
procStarted=FALSE;
}
#endif
// !Win32
/*****************
* SAMPLE LOADERS *
*****************/
#ifdef WIN32
//-----------------------------------------------------------------------------
// File: WavRead.h
//
// Desc: Support for loading and playing Wave files using DirectSound sound
// buffers.
//
// Copyright (c) 1999 Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
#include <windows.h>
#include <mmreg.h>
#include <mmsystem.h>
//-----------------------------------------------------------------------------
// Name: class CWaveSoundRead
// Desc: A class to read in sound data from a Wave file
//-----------------------------------------------------------------------------
class CWaveSoundRead
{
public:
WAVEFORMATEX* m_pwfx; // Pointer to WAVEFORMATEX structure
HMMIO m_hmmioIn; // MM I/O handle for the WAVE
MMCKINFO m_ckIn; // Multimedia RIFF chunk
MMCKINFO m_ckInRiff; // Use in opening a WAVE file
public:
CWaveSoundRead();
~CWaveSoundRead();
HRESULT Open( CHAR* strFilename );
HRESULT Reset();
HRESULT Read( UINT nSizeToRead, BYTE* pbData, UINT* pnSizeRead );
HRESULT Close();
};
#endif
/******************
* QSAMPLE MEMBERS *
******************/
QSample::QSample(cstring fname,int _flags)
{
DBG_C("ctor")
DBG_ARG_S(fname);
DBG_ARG_I(_flags);
#ifndef WIN32
// Make sure audio process is running
RunAP();
#endif
fd=0;
mem=0;
frames=0;
flags=_flags;
// Default format
file_rate=44100.0;
sampwidth=16;
samps_per_frame=2;
#ifndef WIN32
afh=0;
#endif
busy=FALSE;
Load(fname);
}
QSample::~QSample()
{
DBG_C("dtor")
if(mem)qfree(mem);
#ifndef WIN32
if(afh)
AFclosefile(afh);
#endif
}
bool QSample::IsOK()
// Returns TRUE if the sample was succesfully loaded, or the file is open
// (in case it is a streaming sample)
// Don't use GetBuffer() for this, because that doesn't work for streaming
// samples.
{
DBG_C("IsOK")
if(mem)return TRUE;
#ifndef WIN32
if(afh)return TRUE;
#endif
// Not loaded, and no file open; sample is NOT ok
return FALSE;
}
#ifndef WIN32
int QSample::SampleWidth(void)
// Returns sample frame width in #bits (!)
{
DBG_C("SampleWidth")
int sampfmt,sampwidth;
AFgetsampfmt(afh,AF_DEFAULT_TRACK,&sampfmt,&sampwidth);
return (int)sampwidth;
}
#endif
// !Win32
int QSample::GetNoofFrames()
// Returns #frames (frames = samples * channels!)
{
DBG_C("GetNoofFrames")
return frames;
}
bool QSample::Load(cstring fname)
// Read a sample.
// Returns FALSE in case of failure.
{
DBG_C("Load")
DBG_ARG_S(fname);
int offset=smpOffset;
if(!(flags&QSAMPLE_OFFSET))
offset=0;
#ifdef WIN32
// Only .wav is supported under Win32 using WAVREAD.CPP from the DirectX SDK
CWaveSoundRead *wsr=0;
HRESULT hr;
WAVEFORMATEX *fmt;
int nBytes;
wsr=new CWaveSoundRead();
if(FAILED(hr=wsr->Open((string)fname)))
{
qwarn("QSample:Load(); can't open '%s'",fname);
fail:
if(wsr)delete wsr;
return FALSE;
}
// Extract sample format
fmt=wsr->m_pwfx;
if(fmt->wFormatTag!=WAVE_FORMAT_PCM)
{ qwarn("QSample:Load(); sample now WAVE_FORMAT_PCM (formatTag=%d)",fmt->wFormatTag);
goto fail;
}
file_rate=fmt->nSamplesPerSec;
samps_per_frame=fmt->nChannels;
sampwidth=fmt->wBitsPerSample;
frames=wsr->m_ckIn.cksize/(samps_per_frame*(sampwidth/8));
// SGI vars
fd=0;
compression=0;
// Read sample data
nBytes=(sampwidth/8)*frames*samps_per_frame;
mem=qcalloc(nBytes);
if(!mem)
{ qwarn("QSample:Load(%s); no mem",fname);
goto fail;
}
UINT dummySize;
wsr->Read(nBytes,(unsigned char*)mem,&dummySize);
delete wsr;
#ifdef OBS_DEV
// Dump file (DEV)
{ FILE *fw;
fw=fopen("sample.dmp","wb");
fprintf(fw,"Sample: %s\n",fname);
fprintf(fw,"Rate: %f, samps_per_frame(channels)=%d, sampwidth=%d, frames=%d\n",
file_rate,samps_per_frame,sampwidth,frames);
fprintf(fw,"Dummsize: %d\n",dummySize);
fwrite(mem,1,frames*samps_per_frame*(sampwidth/8),fw);
fclose(fw);
}
#endif
return TRUE;
#else
// SGI AL loader; support .wav, .aiff and lots of others
//printf("QSample::Load(%s)\n",fname);
fd=open(QExpandFilename(fname),O_RDONLY);
if(fd<0)return FALSE; // Can't open file
if(AFidentifyfd(fd)<0)return FALSE; // Not AIFF-C or AIFF
afh=AFopenfd(fd,"r",AF_NULL_FILESETUP);
if(afh==AF_NULL_FILEHANDLE)return FALSE; // Failed to open
file_rate=AFgetrate(afh,AF_DEFAULT_TRACK);
compression=AFgetcompression(afh,AF_DEFAULT_TRACK);
samps_per_frame=AFgetchannels(afh,AF_DEFAULT_TRACK);
filefmt=AFgetfilefmt(afh,&vers);
// Read sample into memory
sampwidth=SampleWidth();
frames=AFgetframecnt(afh,AF_DEFAULT_TRACK);
frames+=offset;
//printf("QSample::Load: frames=%d, sampwidth=%d\n",frames,sampwidth);
if(flags&QSAMPLE_STREAM)
{ // Use streaming instead of loading up in memory
//printf("> using diskstreaming for sample\n");
mem=0;
// Keep the file open to do disk streaming
} else
{ mem=qcalloc((sampwidth/8)*frames*samps_per_frame);
if(!mem)
{ printf("QSample::Load(%s): no mem\n",fname);
return FALSE;
}
AFseekframe(afh,AF_DEFAULT_TRACK,0);
short *smp=(short*)mem;
AFreadframes(afh,AF_DEFAULT_TRACK,smp+offset*2,frames);
AFclosefile(afh);
afh=0;
}
return FALSE;
#endif
}
bool QSample::IsPlaying()
{
DBG_C("IsPlaying")
return busy;
}
#ifndef WIN32
QChannel *QSample::Play(int vol)
// Play the sample; beginning at (dynamic) volume 'vol'
{
DBG_C("Play")
//printf("QSample:Play this=%p\n",this);
// Leave message for audio process (set .mem last!)
cmd=CMD_PLAY;
smp=this;
startVolume=vol;
// Wakeup audio process
usvsema(sendSema);
// Wait for response
uspsema(rcvSema);
busy=TRUE;
while(!playChannel)
{ //printf("playChn==0\n");
QNap(1);
}
// 'playChannel' was set by AudioProcess() to indicate which channel was used
return playChannel;
}
void QSample::Stop()
// Stop (looping) sample; also for one-shot samples
{
DBG_C("Stop")
int i;
busy=FALSE;
// Find port on which this sample is playing
for(i=0;i<MAX_CHANNEL;i++)
{ //printf("QSmp:Stop: port[%d].busy=%d\n",i,ad[i].busy);
if(ad[i]->smp==this)
{ // Stop port
//printf(" qsmp:stop: stop port %d\n",i);
ad[i]->busy=FALSE;
//ad[i].smp=0;
}
}
/*for(i=0;i<MAX_CHANNEL;i++)
printf("QSmp:Stop: port[%d].busy=%d\n",i,ad[i].busy);*/
}
void QSample::Loop(bool yn)
{
DBG_C("Loop");
if(yn)flags|=QSAMPLE_LOOP;
else flags&=~QSAMPLE_LOOP;
}
void QSample::Volumize(int perc)
// Modify sample to match new volume percentage (was called SetVolume() once)
// Doesn't work for streaming audio
// Bug: assumes 16-bit stereo
{
DBG_C("Volumize")
short *p;
int i,v;
if(!mem)
{ qwarn("QSample::Volumize() called on streaming sample");
return;
}
p=(short*)mem;
if(sampwidth!=16)
{ qwarn("QSample::SetVolume for non-16-bit samples NYI");
return;
}
//qdbg("QSample::SetVol: samps_per_frame=%d\n",samps_per_frame);
for(i=0;i<frames*samps_per_frame;i++)
{ v=(int)*p;
v=v*perc/100;
//if(v>65535)v=65535;
//if(v<0)v=0;
if(v>32767)v=32767;
else if(v<-32768)v=-32768;
*p=v;
p++;
}
}
void QSampleSetStreamSize(int size)
// Before playing any samples, you may set the stream buffer size
{
DBG_C_FLAT("SetStreamSize")
QASSERT_V(size>100&&size<=32768);
if(procStarted)
{ qerr("Attempt to set sample stream buffer size after samples were played.");
return;
}
pSBufSize=size;
}
void QSampleSetOffset(int off)
{
DBG_C_FLAT("SetOffset")
smpOffset=off;
}
#endif
// !Win32
/********************
* WIN32 WAVE LOADER *
********************/
#ifdef WIN32
//-----------------------------------------------------------------------------
// File: WavRead.cpp
//
// Desc: Wave file support for loading and playing Wave files using DirectSound
// buffers.
//
// Copyright (c) 1999 Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Defines, constants, and global variables
//-----------------------------------------------------------------------------
#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } }
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
//-----------------------------------------------------------------------------
// Name: ReadMMIO()
// Desc: Support function for reading from a multimedia I/O stream
//-----------------------------------------------------------------------------
HRESULT ReadMMIO( HMMIO hmmioIn, MMCKINFO* pckInRIFF, WAVEFORMATEX** ppwfxInfo )
{
MMCKINFO ckIn; // chunk info. for general use.
PCMWAVEFORMAT pcmWaveFormat; // Temp PCM structure to load in.
*ppwfxInfo = NULL;
if( ( 0 != mmioDescend( hmmioIn, pckInRIFF, NULL, 0 ) ) )
return E_FAIL;
if( (pckInRIFF->ckid != FOURCC_RIFF) ||
(pckInRIFF->fccType != mmioFOURCC('W', 'A', 'V', 'E') ) )
return E_FAIL;
// Search the input file for for the 'fmt ' chunk.
ckIn.ckid = mmioFOURCC('f', 'm', 't', ' ');
if( 0 != mmioDescend(hmmioIn, &ckIn, pckInRIFF, MMIO_FINDCHUNK) )
return E_FAIL;
// Expect the 'fmt' chunk to be at least as large as <PCMWAVEFORMAT>;
// if there are extra parameters at the end, we'll ignore them
if( ckIn.cksize < (LONG) sizeof(PCMWAVEFORMAT) )
return E_FAIL;
// Read the 'fmt ' chunk into <pcmWaveFormat>.
if( mmioRead( hmmioIn, (HPSTR) &pcmWaveFormat,
sizeof(pcmWaveFormat)) != sizeof(pcmWaveFormat) )
return E_FAIL;
// Allocate the waveformatex, but if its not pcm format, read the next
// word, and thats how many extra bytes to allocate.
if( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_PCM )
{
if( NULL == ( *ppwfxInfo = new WAVEFORMATEX ) )
return E_FAIL;
// Copy the bytes from the pcm structure to the waveformatex structure
memcpy( *ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat) );
(*ppwfxInfo)->cbSize = 0;
}
else
{
// Read in length of extra bytes.
WORD cbExtraBytes = 0L;
if( mmioRead( hmmioIn, (CHAR*)&cbExtraBytes, sizeof(WORD)) != sizeof(WORD) )
return E_FAIL;
*ppwfxInfo = (WAVEFORMATEX*)new CHAR[ sizeof(WAVEFORMATEX) + cbExtraBytes ];
if( NULL == *ppwfxInfo )
return E_FAIL;
// Copy the bytes from the pcm structure to the waveformatex structure
memcpy( *ppwfxInfo, &pcmWaveFormat, sizeof(pcmWaveFormat) );
(*ppwfxInfo)->cbSize = cbExtraBytes;
// Now, read those extra bytes into the structure, if cbExtraAlloc != 0.
if( mmioRead( hmmioIn, (CHAR*)(((BYTE*)&((*ppwfxInfo)->cbSize))+sizeof(WORD)),
cbExtraBytes ) != cbExtraBytes )
{
delete *ppwfxInfo;
*ppwfxInfo = NULL;
return E_FAIL;
}
}
// Ascend the input file out of the 'fmt ' chunk.
if( 0 != mmioAscend( hmmioIn, &ckIn, 0 ) )
{
delete *ppwfxInfo;
*ppwfxInfo = NULL;
return E_FAIL;
}
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: WaveOpenFile()
// Desc: This function will open a wave input file and prepare it for reading,
// so the data can be easily read with WaveReadFile. Returns 0 if
// successful, the error code if not.
//-----------------------------------------------------------------------------
HRESULT WaveOpenFile( CHAR* strFileName, HMMIO* phmmioIn, WAVEFORMATEX** ppwfxInfo,
HRESULT WaveOpenFile( CHAR* strFileName, HMMIO* phmmioIn, WAVEFORMATEX** ppwfxInfo, MMCKINFO* pckInRIFF )
{
HRESULT hr;
HMMIO hmmioIn = NULL;
if( NULL == ( hmmioIn = mmioOpen( strFileName, NULL, MMIO_ALLOCBUF|MMIO_READ ) ) )
return E_FAIL;
if( FAILED( hr = ReadMMIO( hmmioIn, pckInRIFF, ppwfxInfo ) ) )
{
mmioClose( hmmioIn, 0 );
return hr;
}
*phmmioIn = hmmioIn;
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: WaveStartDataRead()
// Desc: Routine has to be called before WaveReadFile as it searches for the
// chunk to descend into for reading, that is, the 'data' chunk. For
// simplicity, this used to be in the open routine, but was taken out and
// moved to a separate routine so there was more control on the chunks
// that are before the data chunk, such as 'fact', etc...
//-----------------------------------------------------------------------------
HRESULT WaveStartDataRead( HMMIO* phmmioIn, MMCKINFO* pckIn,
HRESULT WaveStartDataRead( HMMIO* phmmioIn, MMCKINFO* pckIn, MMCKINFO* pckInRIFF )
{
// Seek to the data
if( -1 == mmioSeek( *phmmioIn, pckInRIFF->dwDataOffset + sizeof(FOURCC),
SEEK_SET ) )
return E_FAIL;
// Search the input file for for the 'data' chunk.
pckIn->ckid = mmioFOURCC('d', 'a', 't', 'a');
if( 0 != mmioDescend( *phmmioIn, pckIn, pckInRIFF, MMIO_FINDCHUNK ) )
return E_FAIL;
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: WaveReadFile()
// Desc: Reads wave data from the wave file. Make sure we're descended into
// the data chunk before calling this function.
// hmmioIn - Handle to mmio.
// cbRead - # of bytes to read.
// pbDest - Destination buffer to put bytes.
// cbActualRead - # of bytes actually read.
//-----------------------------------------------------------------------------
HRESULT WaveReadFile( HMMIO hmmioIn, UINT cbRead, BYTE* pbDest,
HRESULT WaveReadFile( HMMIO hmmioIn, UINT cbRead, BYTE* pbDest, MMCKINFO* pckIn, UINT* cbActualRead )
{
MMIOINFO mmioinfoIn; // current status of <hmmioIn>
*cbActualRead = 0;
if( 0 != mmioGetInfo( hmmioIn, &mmioinfoIn, 0 ) )
return E_FAIL;
UINT cbDataIn = cbRead;
if( cbDataIn > pckIn->cksize )
cbDataIn = pckIn->cksize;
pckIn->cksize -= cbDataIn;
for( DWORD cT = 0; cT < cbDataIn; cT++ )
{
// Copy the bytes from the io to the buffer.
if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
{
if( 0 != mmioAdvance( hmmioIn, &mmioinfoIn, MMIO_READ ) )
return E_FAIL;
if( mmioinfoIn.pchNext == mmioinfoIn.pchEndRead )
return E_FAIL;
}
// Actual copy.
*((BYTE*)pbDest+cT) = *((BYTE*)mmioinfoIn.pchNext);
mmioinfoIn.pchNext++;
}
if( 0 != mmioSetInfo( hmmioIn, &mmioinfoIn, 0 ) )
return E_FAIL;
*cbActualRead = cbDataIn;
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: CWaveSoundRead()
// Desc: Constructs the class
//-----------------------------------------------------------------------------
CWaveSoundRead::CWaveSoundRead()
{
m_pwfx = NULL;
}
//-----------------------------------------------------------------------------
// Name: ~CWaveSoundRead()
// Desc: Destructs the class
//-----------------------------------------------------------------------------
CWaveSoundRead::~CWaveSoundRead()
{
Close();
SAFE_DELETE( m_pwfx );
}
//-----------------------------------------------------------------------------
// Name: Open()
// Desc: Opens a wave file for reading
//-----------------------------------------------------------------------------
HRESULT CWaveSoundRead::Open( CHAR* strFilename )
{
SAFE_DELETE( m_pwfx );
HRESULT hr;
if( FAILED( hr = WaveOpenFile( strFilename, &m_hmmioIn, &m_pwfx, &m_ckInRiff ) ) )
return hr;
if( FAILED( hr = Reset() ) )
return hr;
return hr;
}
//-----------------------------------------------------------------------------
// Name: Reset()
// Desc: Resets the internal m_ckIn pointer so reading starts from the
// beginning of the file again
//-----------------------------------------------------------------------------
HRESULT CWaveSoundRead::Reset()
{
return WaveStartDataRead( &m_hmmioIn, &m_ckIn, &m_ckInRiff );
}
//-----------------------------------------------------------------------------
// Name: Read()
// Desc: Reads a wave file into a pointer and returns how much read
// using m_ckIn to determine where to start reading from
//-----------------------------------------------------------------------------
HRESULT CWaveSoundRead::Read( UINT nSizeToRead, BYTE* pbData, UINT* pnSizeRead )
{
return WaveReadFile( m_hmmioIn, nSizeToRead, pbData, &m_ckIn, pnSizeRead );
}
//-----------------------------------------------------------------------------
// Name: Close()
// Desc: Closes an open wave file
//-----------------------------------------------------------------------------
HRESULT CWaveSoundRead::Close()
{
mmioClose( m_hmmioIn, 0 );
return S_OK;
}
#endif
// WIN32