Files
stk/STK/RTSoundIO.cpp
Gary Scavone 4b6500d3de Version 3.1
2013-09-29 22:42:17 +02:00

966 lines
32 KiB
C++

/******************************************/
/* RTSoundIO.cpp */
/* Realtime Sound I/O Object for STK, */
/* by Gary P. Scavone, 1998-1999. */
/* */
/* The sound output sections of this */
/* object were based in part on code */
/* by Doug Scott (SGI), Tim Stilson */
/* (Linux), Bill Putnam (Win Wav), and */
/* R. Marsanyi (DirectSound). */
/* */
/* This object provides a standard API */
/* across all platforms for STK realtime */
/* audio input/output. The sound output */
/* code is fairly robust. Audio input, */
/* however, is more dependent on the */
/* capabilities of the particular OS and */
/* the soundcard being used. For the */
/* moment, I'll try to provide 1 and 2 */
/* channel support. */
/* */
/* 16-bit integer audio input/output */
/* data is being assumed. */
/******************************************/
#include "RTSoundIO.h"
#if (defined(__STK_REALTIME_) && defined(__OS_IRIX_))
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
RTSoundIO :: RTSoundIO(MY_FLOAT srate, int channels, char *mode)
{
ALconfig audio_port_config;
int lookaheadbuffers = 8; // number of lookahead buffers
long pvbuf[4];
int nbuf, totalBufSize;
/* Check the number of channels */
if (channels > 2) {
fprintf(stderr,"RTSoundIO: Unsupported # of audio i/o channels: %d\n", channels);
exit(0);
}
/* Create ALconfig structure */
audio_port_config = ALnewconfig();
if (!audio_port_config) {
fprintf(stderr,"Couldn't create ALconfig:%s\n", alGetErrorString(oserror()));
exit(0);
}
/* Configure channels */
if(ALsetchannels(audio_port_config, channels) < 0) {
fprintf(stderr,"Cannot configure channels: %s\n", alGetErrorString(oserror()));
exit(0);
}
/* Size the output queue */
nbuf = (channels == 2) ? lookaheadbuffers : lookaheadbuffers/2;
totalBufSize = RT_BUFFER_SIZE * nbuf;
if(ALsetqueuesize(audio_port_config, totalBufSize) < 0) {
fprintf(stderr,"Cannot configure output queue size: %s\n", alGetErrorString(oserror()));
exit(0);
}
if (!strcmp(mode,"play")) { // playback only
/* Open the output audio port */
audio_port_out = ALopenport("STK output port", "w", audio_port_config);
if(!audio_port_out) {
fprintf(stderr,"Cannot initialize output audio port: %s\n", alGetErrorString(oserror()));
exit(0);
}
/* Set sample rate parameters */
pvbuf[0] = AL_OUTPUT_RATE;
pvbuf[1] = (long) srate;
ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 2); /* set output SR */
/* Tell port to accept refill at buffers - 1 */
ALsetfillpoint(audio_port_out,RT_BUFFER_SIZE * (lookaheadbuffers - 1));
audio_port_in = 0;
}
else if (!strcmp(mode,"record")) { // record only
/* Open the input audio port */
audio_port_in = ALopenport("STK input port", "r", audio_port_config);
if(!audio_port_in) {
fprintf(stderr,"Cannot initialize input audio port: %s\n", alGetErrorString(oserror()));
exit(0);
}
/* Set sample rate parameters */
pvbuf[0] = AL_OUTPUT_RATE;
pvbuf[1] = (long) srate;
ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 2); /* set input SR */
/* tell port to accept refill at buffers - 1 */
ALsetfillpoint(audio_port_in,RT_BUFFER_SIZE * (lookaheadbuffers - 1));
audio_port_out = 0;
}
else if (!strcmp(mode,"duplex")) { // duplex mode
/* Open the output audio port */
audio_port_out = ALopenport("STK output port", "w", audio_port_config);
if(!audio_port_out) {
fprintf(stderr,"Cannot initialize output audio port: %s\n", alGetErrorString(oserror()));
exit(0);
}
/* Open the input audio port */
audio_port_in = ALopenport("STK input port", "r", audio_port_config);
if(!audio_port_in) {
fprintf(stderr,"Cannot initialize input audio port: %s\n", alGetErrorString(oserror()));
exit(0);
}
/* Set sample rate parameters */
pvbuf[0] = AL_OUTPUT_RATE;
pvbuf[1] = (long) srate;
pvbuf[2] = AL_INPUT_RATE;
pvbuf[3] = (long) srate;
ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 4); /* set output SR */
/* tell port to accept refill at buffers - 1 */
ALsetfillpoint(audio_port_out,RT_BUFFER_SIZE * (lookaheadbuffers - 1));
ALsetfillpoint(audio_port_in,RT_BUFFER_SIZE * (lookaheadbuffers - 1));
}
else {
fprintf(stderr,"Unsupported RTSoundIO mode: %s\n",mode);
exit(0);
}
ALfreeconfig(audio_port_config);
audio_port_config = 0;
}
RTSoundIO :: ~RTSoundIO()
{
if(audio_port_out) ALcloseport(audio_port_out);
audio_port_out=0;
if(audio_port_in) ALcloseport(audio_port_in);
audio_port_in=0;
}
int RTSoundIO :: playBuffer(short *buf, int bufsize)
{
ALwritesamps(audio_port_out, buf, bufsize);
return 0;
}
int RTSoundIO :: recordBuffer(short *buf, int bufsize)
{
ALreadsamps(audio_port_in, buf, bufsize);
return 0;
}
/* Linux OSS Sound API here */
#elif (defined(__STK_REALTIME_) && defined(__OSS_API_))
#define ABS(x) ((x < 0) ? (-x) : (x))
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>
RTSoundIO :: RTSoundIO(MY_FLOAT srate, int channels, char *mode)
{
int lookaheadbuffers = 8; // number of lookahead buffers
int nbuf;
char DEVICE_NAME[100];
int format;
int stereo; /* 0=mono, 1=stereo */
int stereoset;
int speed;
int BUFFER_SIZE_LOG;
int fragsize;
BUFFER_SIZE_LOG = (int)(log10((double)RT_BUFFER_SIZE)/log10(2.0));
/* Check the number of channels */
if (channels > 2) {
fprintf(stderr,"RTSoundIO: Unsupported # of audio i/o channels: %d\n", channels);
exit(0);
}
if (channels == 2) stereo = 1;
else stereo = 0;
strcpy(DEVICE_NAME,"/dev/dsp");
if (!strcmp(mode,"play")) { // playback only
if ((audio_fd = open(DEVICE_NAME, O_WRONLY, 0)) == -1)
{ /* Opening device failed */
fprintf(stderr,"Cannot open audio device: %s\n",DEVICE_NAME);
exit(0);
}
}
else if (!strcmp(mode,"record")) { // record only
if ((audio_fd = open(DEVICE_NAME, O_RDONLY, 0)) == -1)
{ /* Opening device failed */
fprintf(stderr,"Cannot open audio device: %s\n",DEVICE_NAME);
exit(0);
}
}
else if (!strcmp(mode,"duplex")) { // duplex mode
if ((audio_fd = open(DEVICE_NAME, O_RDWR, 0)) == -1)
{ /* Opening device failed */
fprintf(stderr,"Cannot open audio device: %s\n",DEVICE_NAME);
exit(0);
}
int caps;
if (ioctl(audio_fd, SNDCTL_DSP_GETCAPS, &caps))
{
close(audio_fd);
fprintf(stderr,"Error getting device capabilities: %s\n",DEVICE_NAME);
exit(0);
}
if (!(caps & DSP_CAP_DUPLEX))
{
close(audio_fd);
fprintf(stderr,"Audio device does not support duplex mode: %s\n",DEVICE_NAME);
exit(0);
}
if (ioctl(audio_fd, SNDCTL_DSP_SETDUPLEX, 0))
{
close(audio_fd);
fprintf(stderr,"Error setting duplex mode: %s\n",DEVICE_NAME);
exit(0);
}
int cursrc, srcbit = SOUND_MASK_MIC;
ioctl(audio_fd, MIXER_READ(SOUND_MIXER_RECSRC),&cursrc);
srcbit = (srcbit & cursrc);
ioctl(audio_fd,MIXER_WRITE(SOUND_MIXER_RECSRC),&srcbit);
// The following opens a direct analog line from the mic to the output
//srcbit = 99;
//ioctl(audio_fd,MIXER_WRITE(SOUND_MIXER_IMIX),&srcbit);
}
else {
fprintf(stderr,"Unsupported RTSoundIO mode: %s\n",mode);
exit(0);
}
/* Size the output queue */
nbuf = (channels == 2) ? lookaheadbuffers : lookaheadbuffers/2;
fragsize = (nbuf << 16) + BUFFER_SIZE_LOG;
if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &fragsize))
{
close(audio_fd);
fprintf(stderr,"Error setting audio buffer size!\n");
exit(0);
}
format = AFMT_S16_LE;
if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format)==-1)
{ /* Fatal error */
close(audio_fd);
fprintf(stderr,"SNDCTL_DSP_SETFMT error\n");
exit(0);
}
if (format != AFMT_S16_LE)
{
close(audio_fd);
fprintf(stderr,"Audio device doesn't support 16-bit signed LE format\n");
exit(0);
}
stereoset = stereo;
if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &stereoset)==-1)
{ /* Fatal error */
close(audio_fd);
fprintf(stderr,"SNDCTL_DSP_STEREO\n");
exit(0);
}
if (stereoset != stereo)
{
close(audio_fd);
fprintf(stderr,"The audio device did not set correct stereo mode: %s\n",DEVICE_NAME);
exit(0);
}
speed = (int)srate;
if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed)==-1)
{ /* Fatal error */
close(audio_fd);
fprintf(stderr,"SNDCTL_DSP_SPEED\n");
exit(0);
}
if (ABS(speed - srate)>100)
{
close(audio_fd);
fprintf(stderr,"The device doesn't support the requested speed.\n");
exit(0);
}
}
RTSoundIO :: ~RTSoundIO()
{
if(audio_fd) close(audio_fd);
audio_fd=0;
}
int RTSoundIO :: playBuffer(short *buf, int bufsize)
{
/* The OSS write() routine takes the buffer size in bytes, thus the
multiplication by two.
*/
int len;
if ((len = write(audio_fd, buf, 2*bufsize)) == -1)
{
fprintf(stderr,"Audio write error!\n");
return -1;
}
return 0;
}
int RTSoundIO :: recordBuffer(short *buf, int bufsize)
{
/* The OSS read() routine takes the buffer size in bytes, thus the
multiplication by two.
*/
int len;
if ((len = read(audio_fd, buf, 2*bufsize)) == -1)
{
fprintf(stderr,"Audio read error!\n");
return -1;
}
return 0;
}
#elif (defined(__STK_REALTIME_) && defined(__WINDS_API_) )
/*
* AUDIO OUTPUT:
*
* There are two ways (or more) to go about handling sound output
* under DirectSound. For minimum latency, one should write new
* buffers in front of the read pointer (Method 1). The other
* method is to always write new buffers of data behind the read
* pointer (Method 2). Method 2 is very safe but inherently
* produces a delay equivalent to the entire sound buffer (plus
* any other delays that Microsloth provides). Even using
* Method 1, however, Microsloth requires that you leave about
* 15 ms of buffer between the read and write pointers. I've tried
* both methods and neither results in performance that can compare
* to either Linux or SGI sound output. In order to minimize
* latency, however, I'll go with Method 1 and leave Method 2
* commented out below.
*
* If the primary sound buffer exists in hardware, I will write
* directly to it. If the primary sound buffer is emulated in
* software, this is not possible and we must use a secondary buffer.
*
* AUDIO INPUT:
*
* I didn't spend a lot of time doing the audio input code. I
* basically got it working, heard that it had noticeable delay,
* but didn't try screwing around to minimize that delay. I provide
* a single variable (at the beginning of the Capture initialization)
* which controls the size of the DirectSound Capture buffer, which
* in turn controls the latency.
*
* The DirectSoundCapture API is only available with DirectX versions
* 5.0 and higher. If you don't have such capabilities (ex. WinNT),
* then use the WinMM API code (uncomment the __WINMM_API_ define
* statement in Object.h and comment out the __WINDS_API_ flag). This
* will allow you to still compile STK, but without the audio input
* capabilities.
*/
#define DS_WRITE_METHOD 1
#include <stdio.h>
RTSoundIO :: RTSoundIO(MY_FLOAT srate, int channels, char *mode)
{
HRESULT result;
WAVEFORMATEX wfFormat;
BYTE *pAudioPtr;
DWORD dwDataLen;
// Initialize the DirectSound object and buffer pointers to NULL
lpDirectSound = NULL;
lpDSBuffer = NULL;
lpDSCapture = NULL;
lpDSCBuffer = NULL;
// Define the wave format structure (16-bit PCM, srate, channels)
ZeroMemory(&wfFormat, sizeof(WAVEFORMATEX));
wfFormat.wFormatTag = WAVE_FORMAT_PCM;
wfFormat.nChannels = channels;
wfFormat.nSamplesPerSec = (unsigned long) srate;
wfFormat.wBitsPerSample = 8 * sizeof(short);
wfFormat.nBlockAlign = wfFormat.nChannels * wfFormat.wBitsPerSample / 8;
wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign;
if ( (!strcmp(mode,"play")) || (!strcmp(mode,"duplex")) ) {
DSBUFFERDESC dsbdesc;
HWND hWnd;
// If using Method 1:
// Define a maximum distance that the write pointer is
// allowed to lead safePos. The size of this zone is
// fairly critical to the behavior of this scheme. The
// value below is set for a 15 millisecond region.
zoneSize = (DWORD) (0.015 * srate * sizeof(short)); // bytes
// Create the DS object
if ((result = DirectSoundCreate(NULL, &lpDirectSound, NULL)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Cannot open default sound output device!!\n");
exit(0);
}
// Get DS device capabilites
DSCAPS dscaps;
ZeroMemory(&dscaps, sizeof(DSCAPS));
dscaps.dwSize = sizeof(DSCAPS);
if ((result = lpDirectSound->GetCaps(&dscaps)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Cannot get DS device capabilities!!\n");
exit(0);
}
// Determine whether primary buffer exists in hardware or software
if (dscaps.dwFlags & DSCAPS_EMULDRIVER) { // Write to secondary buffer
// Number of buffers of size RT_BUFFER_SIZE to make secondary
// DS buffer. A larger secondary buffer does NOT produce more
// output delay when the write pointer is kept in front of the
// read pointer (Method 1). However, if you use Method 2, this
// variable will be more critical and you'll probably want to
// make it smaller.
int nBufs = 12;
// Set coooperative level
hWnd = GetForegroundWindow();
if ((result = lpDirectSound->SetCooperativeLevel(hWnd, DSSCL_EXCLUSIVE)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't set EXCLUSIVE cooperative level!\n");
exit(0);
}
// Even though we will write to the secondary buffer, we need
// to access the primary buffer to set the correct output format.
// The default is 8-bit, 22 kHz!
LPDIRECTSOUNDBUFFER lpdsbPrimary;
// Setup the DS primary buffer description.
ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
// Obtain the primary buffer
if ((result = lpDirectSound->CreateSoundBuffer(&dsbdesc,
&lpdsbPrimary, NULL)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Cannot get the primary DS buffer address!\n");
exit(0);
}
// Set the primary DS buffer sound format.
if ((result = lpdsbPrimary->SetFormat(&wfFormat)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Cannot set the primary DS buffer to proper sound format!\n");
exit(0);
}
// Setup the secondary DS buffer description.
dwDSBufSize = RT_BUFFER_SIZE * sizeof(short) * nBufs;
ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2;
dsbdesc.dwBufferBytes = dwDSBufSize;
dsbdesc.lpwfxFormat = &wfFormat;
// Try to create the secondary DS buffer.
if ((result = lpDirectSound->CreateSoundBuffer(&dsbdesc, &lpDSBuffer, NULL)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't create the DS sound buffer!\n");
exit(0);
}
}
else { // Write to primary buffer
// Setup the DS primary buffer description.
ZeroMemory(&dsbdesc, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_STICKYFOCUS;
// Buffer size is determined by sound hardware
dsbdesc.dwBufferBytes = 0;
dsbdesc.lpwfxFormat = NULL; // Must be NULL for primary buffer.
// Obtain write-primary coooperative level.
hWnd = GetForegroundWindow();
if ((result = lpDirectSound->SetCooperativeLevel(hWnd, DSSCL_WRITEPRIMARY)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't set WRITEPRIMARY cooperative level!\n");
exit(0);
}
// Try to create the primary DS buffer.
if ((result = lpDirectSound->CreateSoundBuffer(&dsbdesc, &lpDSBuffer, NULL)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't create the DS sound buffer!\n");
exit(0);
}
// Set primary DS buffer to desired format.
if ((result = lpDSBuffer->SetFormat(&wfFormat)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't set correct format!\n");
exit(0);
}
}
// Get the buffer size
DSBCAPS dsbcaps;
dsbcaps.dwSize = sizeof(DSBCAPS);
lpDSBuffer->GetCaps(&dsbcaps);
dwDSBufSize = dsbcaps.dwBufferBytes;
// Lock the DS buffer
if ((result = lpDSBuffer->Lock(0, dwDSBufSize, (LPLPVOID) &pAudioPtr, &dwDataLen, NULL, NULL, 0)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't lock DS sound buffer!\n");
exit(0);
}
// Zero the DS buffer
ZeroMemory(pAudioPtr, dwDataLen);
// Unlock the DS buffer
if ((result = lpDSBuffer->Unlock(pAudioPtr, dwDataLen, NULL, 0)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't unlock DS sound buffer!\n");
exit(0);
}
} // end of play/duplex initialization
if ( (!strcmp(mode,"record")) || (!strcmp(mode,"duplex")) ) {
// DirectSound Capture capabilities require DirectX 5.0 or higher
// The following variable controls the size of the DS Capture
// buffer, which in turns controls the latency in the capture
// process. When dscbufscale = 1, the buffer is equivalent to
// one second of audio. A dscbufscale = 0.5 halves this value.
// Likewise, a dscbufscale = 2 doubles this value. It seems to
// work OK with dscbufscale = 0.5, but there is a periodic
// "clicking" which sometimes occurs. You can go lower, but
// the "clicking" gets worse. Good luck! Yet another reason
// why not to use Windoze.
float dscbufscale = 0.5;
// Create the DS Capture object
if ((result = DirectSoundCaptureCreate(NULL, &lpDSCapture, NULL)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't open DirectSoundCapture Object!\n");
fprintf(stderr,"RTSoundIO: Requires DirectX 5 or later.\n");
exit(0);
}
// Setup the DS Capture buffer description
DSCBUFFERDESC dscbdesc;
ZeroMemory(&dscbdesc, sizeof(DSCBUFFERDESC));
dscbdesc.dwSize = sizeof(DSCBUFFERDESC);
dscbdesc.dwFlags = 0;
// Control size of DS Capture buffer here
dwDSCBufSize = (DWORD)(wfFormat.nAvgBytesPerSec*dscbufscale);
dscbdesc.dwBufferBytes = dwDSCBufSize;
dscbdesc.dwReserved = 0;
dscbdesc.lpwfxFormat = &wfFormat;
// Create the DS Capture buffer
if ((result = lpDSCapture->CreateCaptureBuffer(&dscbdesc, &lpDSCBuffer, NULL)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't create DirectSoundCapture buffer!\n");
if (result == DSERR_BADFORMAT) {
fprintf(stderr,"RTSoundIO: The input device could not support the desired\n");
fprintf(stderr,"sample rate (%f), bits per sample (16),\n", srate);
fprintf(stderr,"and/or number of channels (%d).\n", channels);
}
exit(0);
}
// Lock the DS Capture buffer
if ((result = lpDSCBuffer->Lock(0, dwDSCBufSize, (LPLPVOID) &pAudioPtr, &dwDataLen, NULL, NULL, 0)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't lock DS Capture sound buffer!\n");
exit(0);
}
// Zero the DS Capture buffer
ZeroMemory(pAudioPtr, dwDataLen);
// Unlock the DS Capture buffer
if ((result = lpDSCBuffer->Unlock(pAudioPtr, dwDataLen, NULL, 0)) != DS_OK) {
fprintf(stderr,"RTSoundIO: Couldn't unlock DS Capture sound buffer!\n");
exit(0);
}
// Start the DS Capture buffer input
if ((result = lpDSCBuffer->Start(DSCBSTART_LOOPING) != DS_OK)) {
fprintf(stderr,"RTSoundIO: Couldn't start DS Capture sound input!\n");
exit(0);
}
} // end of record/duplex initialization
if ( (!strcmp(mode,"play")) || (!strcmp(mode,"duplex")) ) {
// Start the DS buffer playback
if ((result = lpDSBuffer->Play(0, 0, DSBPLAY_LOOPING ) != DS_OK)) {
fprintf(stderr,"RTSoundIO: Couldn't play DS sound buffer!\n");
exit(0);
}
}
}
RTSoundIO :: ~RTSoundIO()
{
// Cleanup the DS buffers
if (lpDSBuffer) {
lpDSBuffer->Stop();
lpDSBuffer->Release();
lpDSBuffer = NULL;
}
if (lpDSCBuffer) {
lpDSCBuffer->Stop();
lpDSCBuffer->Release();
lpDSCBuffer = NULL;
}
// Cleanup the DS objects
if (lpDirectSound) {
lpDirectSound->Release();
lpDirectSound = NULL;
}
if (lpDSCapture) {
lpDSCapture->Release();
lpDSCapture = NULL;
}
}
int RTSoundIO :: playBuffer(short *buf, int bufsize)
{
HRESULT hr;
LPVOID lpbuf1 = NULL;
LPVOID lpbuf2 = NULL;
DWORD dwsize1 = 0;
DWORD dwsize2 = 0;
DWORD playPos, safePos;
static UINT nextWritePos = 0;
// Find out where the read and "safe write" pointers are.
hr = lpDSBuffer->GetCurrentPosition(&playPos, &safePos);
if (hr != DS_OK) return -1;
// METHOD 1: Keep write pointer in front of read pointer.
//
// Microsloth says that the safePos is about 15 ms ahead of
// playPos. I think this figure is somewhat hardware related,
// especially if you are writing to the primary buffer. With
// my shit-blaster 16, I found the safePos to be about 10 ms
// ahead of playPos. If you really need to reduce delay, you
// can try moving your "safePos" closer to the play pointer.
// You'll be treading on dangerous ground, but then again,
// you're obviously using Windoze so you're already familiar
// with such uncertainty! I've been able to lop off 2-5 ms
// in some circumstances.
//static DWORD backup = (DWORD) (0.005 * SRATE * sizeof(short));
//safePos = (safePos + dwDSBufSize - backup) % dwDSBufSize;
// Assume that the next write position is always in front
// of safePos. If not, the write pointer must have wrapped.
// NOTE: If safePos somehow gets ahead of the write pointer,
// then an underrun has occurred and there's not much we can
// do anyway.
DWORD deltaPos;
if( safePos > nextWritePos )
deltaPos = nextWritePos + dwDSBufSize - safePos;
else
deltaPos = nextWritePos - safePos;
// Check whether the write pointer is in the allowed region.
while ( deltaPos > zoneSize ) {
// If we are here, then we must wait until the write pointer
// is in the allowed region. For this, we can either
// continuously check the pointer positions until they are
// OK or we can use the Sleep() function to pause operations
// for a certain amount of time. Use of the Sleep() function
// would seem to be the better choice, however, there are
// reports that Sleep() often "sleeps" for much longer than
// requested. I'll let you choose which method to use.
static int sleep = 1; // 1 = sleep, 0 = don't sleep
if (sleep) {
// Sleep until safePos catches up. Calculate number of
// milliseconds to wait as:
// time = distance * (milliseconds/second) * fudgefactor /
// ((bytes/sample) * (samples/second))
// A "fudgefactor" less than 1 is used because it was found
// that sleeping too long was MUCH worse than sleeping for
// several shorter periods.
DWORD millis = (DWORD) ((deltaPos * 200.0) / ( sizeof(short) * SRATE));
// Sleep for that long
Sleep( millis );
}
// Wake up, find out where we are now
hr = lpDSBuffer->GetCurrentPosition( &playPos, &safePos );
if( hr != DS_OK ) return -1;
// Backup safePos? (See above)
//safePos = (safePos + dwDSBufSize - backup) % dwDSBufSize;
if( safePos > nextWritePos )
deltaPos = nextWritePos + dwDSBufSize - safePos;
else
deltaPos = nextWritePos - safePos;
}
// End of Method 1
/*
// METHOD 2: Keep write region behind of play pointer.
if( playPos < nextWritePos ) playPos += dwDSBufSize; // unwrap offset
DWORD endWrite = nextWritePos + bufsize * sizeof(short);
// Check whether the write region is behind the play pointer.
while ( playPos < endWrite ) {
// If we are here, then we must wait until the play pointer
// gets beyond the write region. For this, we can either
// continuously check the pointer positions until they are
// OK or we can use the Sleep() function to pause operations
// for a certain amount of time. Use of the Sleep() function
// would seem to be the better choice, however, there are
// reports that Sleep() often "sleeps" for much longer than
// requested. I'll let you choose which method to use.
static int sleep = 1; // 1 = sleep, 0 = don't sleep
if (sleep) {
// Sleep until safePos catches up. Calculate number of
// milliseconds to wait as:
// time = distance * (milliseconds/second) * fudgefactor /
// ((bytes/sample) * (samples/second))
// A "fudgefactor" less than 1 is used because it was found
// that sleeping too long was MUCH worse than sleeping for
// several shorter periods.
DWORD millis = (DWORD) (((endWrite - playPos) * 200.0) / ( sizeof(short) * SRATE));
// Sleep for that long
Sleep( millis );
}
// Wake up, find out where we are now
hr = lpDSBuffer->GetCurrentPosition( &playPos, &safePos );
if( hr != DS_OK ) return -1;
if( playPos < nextWritePos ) playPos += dwDSBufSize; // unwrap offset
}
// End of Method 2.
*/
// Lock free space in the DS
hr = lpDSBuffer->Lock (nextWritePos, bufsize * sizeof(short), &lpbuf1, &dwsize1, &lpbuf2, &dwsize2, 0);
if (hr == DS_OK) {
// Copy the buffer into the DS
CopyMemory(lpbuf1, buf, dwsize1);
if(NULL != lpbuf2) CopyMemory(lpbuf2, buf+dwsize1, dwsize2);
// Update our buffer offset and unlock sound buffer
nextWritePos = (nextWritePos + dwsize1 + dwsize2) % dwDSBufSize;
lpDSBuffer->Unlock (lpbuf1, dwsize1, lpbuf2, dwsize2);
return 0;
}
else return -1;
}
int RTSoundIO :: recordBuffer(short *buf, int bufsize)
{
HRESULT hr;
LPVOID lpbuf1 = NULL;
LPVOID lpbuf2 = NULL;
DWORD dwsize1 = 0;
DWORD dwsize2 = 0;
DWORD capPos, safePos;
static UINT nextReadPos = 0;
// Find out where the write and "safe read" pointers are.
hr = lpDSCBuffer->GetCurrentPosition(&capPos, &safePos);
if (hr != DS_OK) return -1;
//printf("capPos = %d, safePos = %d\n", capPos, safePos);
if( safePos < nextReadPos ) safePos += dwDSCBufSize; // unwrap offset
DWORD endRead = nextReadPos + bufsize * sizeof(short);
//printf("endRead = %d\n", endRead);
// Check whether the read region is behind the capture pointer.
while ( safePos < endRead ) {
// If we are here, then we must wait until the read pointer
// gets beyond the write region. For this, we can either
// continuously check the pointer positions until they are
// OK or we can use the Sleep() function to pause operations
// for a certain amount of time. Use of the Sleep() function
// would seem to be the better choice, however, there are
// reports that Sleep() often "sleeps" for much longer than
// requested. I'll let you choose which method to use.
static int sleep = 1; // 1 = sleep, 0 = don't sleep
if (sleep) {
// Sleep until safePos catches up. Calculate number of
// milliseconds to wait as:
// time = distance * (milliseconds/second) * fudgefactor /
// ((bytes/sample) * (samples/second))
// A "fudgefactor" less than 1 is used because it was found
// that sleeping too long was MUCH worse than sleeping for
// several shorter periods.
DWORD millis = (DWORD) (((endRead - safePos) * 200.0) / ( sizeof(short) * SRATE));
// Sleep for that long
Sleep( millis );
}
// Wake up, find out where we are now
hr = lpDSCBuffer->GetCurrentPosition( &capPos, &safePos );
if( hr != DS_OK ) return -1;
//printf("capPos = %d, safePos = %d\n", capPos, safePos);
if( safePos < nextReadPos ) safePos += dwDSCBufSize; // unwrap offset
}
//printf("how about here?\n");
// Lock free space in the DS Capture buffer
hr = lpDSCBuffer->Lock(nextReadPos, bufsize * sizeof(short), &lpbuf1, &dwsize1, &lpbuf2, &dwsize2, 0);
if (hr == DS_OK) {
// Copy the DS Capture data to the buffer
CopyMemory(buf, lpbuf1, dwsize1);
if(NULL != lpbuf2) CopyMemory(buf+dwsize1, lpbuf2, dwsize2);
// Update our buffer offset and unlock sound buffer
nextReadPos = (nextReadPos + dwsize1 + dwsize2) % dwDSCBufSize;
lpDSCBuffer->Unlock (lpbuf1, dwsize1, lpbuf2, dwsize2);
return 0;
}
else return -1;
}
#elif (defined(__STK_REALTIME_) && defined(__WINMM_API_) )
#include <stdio.h>
#define FRAMETIME (long) (1000.0 * RT_BUFFER_SIZE / SRATE)
RTSoundIO :: RTSoundIO(MY_FLOAT srate, int channels, char *mode)
{
MMRESULT result;
WAVEFORMATEX wfx;
int bufferSize = RT_BUFFER_SIZE;
audioPort = NULL;
if ( (!strcmp(mode,"record")) || (!strcmp(mode,"duplex")) ) {
fprintf(stderr,"Sorry ... no audio input support under WinMM API!\n");
exit(0);
}
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = channels;
wfx.nSamplesPerSec = (unsigned long) srate;
wfx.nBlockAlign = sizeof(short) * channels;
wfx.nAvgBytesPerSec = (unsigned long) srate * wfx.nBlockAlign;
wfx.wBitsPerSample = 8 * sizeof(short);
wfx.cbSize = 0;
/* Open a Wave Out device using the wave mapper to guide us */
result = waveOutOpen(&audioPort,WAVE_MAPPER,&wfx,(DWORD)NULL,(DWORD)NULL,CALLBACK_NULL);
if (result != MMSYSERR_NOERROR) {
fprintf(stderr,"RTSoundIO: Cannot open audio port (WinMM API)!\n");
exit(0);
}
for( outBufNum = 0; outBufNum < (UINT)NUM_OUT_BUFFERS; outBufNum++ )
{
/* set up a couple of wave headers */
whOut[outBufNum].lpData = (LPSTR)calloc(channels*bufferSize, sizeof(short));
if (whOut[outBufNum].lpData == NULL){
waveOutClose( audioPort );
fprintf(stderr,"RTSoundIO: Error initializing audio buffers (WinMM API)!\n");
exit(0);
}
whOut[outBufNum].dwBufferLength = channels*bufferSize*sizeof(short);
whOut[outBufNum].dwBytesRecorded = 0;
whOut[outBufNum].dwUser = 1;
//whOut[outBufNum].dwFlags = 0;
whOut[outBufNum].dwFlags = WHDR_DONE;
whOut[outBufNum].dwLoops = 0;
whOut[outBufNum].lpNext = NULL;
whOut[outBufNum].reserved = 0;
}
/* Write the first buffer out to get things going */
outBufNum = 0;
result = waveOutPrepareHeader(audioPort, &whOut[outBufNum],sizeof(WAVEHDR));
result = waveOutWrite(audioPort, &whOut[outBufNum], sizeof(WAVEHDR));
/* Keep track of time so that we know how long we can sleep */
lastWriteTime = timeGetTime();
}
RTSoundIO :: ~RTSoundIO()
{
MMRESULT result;
long timeToGo;
/* Close Audio Port */
if (audioPort != NULL) {
result = waveOutReset(audioPort);
for( outBufNum = 0; outBufNum < (UINT)NUM_OUT_BUFFERS; outBufNum++ )
{
/* Loop until the next waveheader indicates that we are done */
while( !(whOut[outBufNum].dwFlags & WHDR_DONE) )
{
//printf(".");
timeToGo = (long) (FRAMETIME - (timeGetTime()-lastWriteTime));
if( timeToGo > 0 ) Sleep( (long) timeToGo );
}
/* Unprepare the header */
result = waveOutUnprepareHeader(audioPort, &whOut[outBufNum],sizeof(WAVEHDR));
if (whOut[outBufNum].lpData != NULL) {
free(whOut[outBufNum].lpData);
whOut[outBufNum].lpData = NULL;
}
}
result = waveOutClose(audioPort);
}
}
int RTSoundIO :: playBuffer(short *buf, int bufsize)
{
MMRESULT result;
long timeToGo;
outBufNum++;
if( outBufNum >= (UINT)NUM_OUT_BUFFERS ) outBufNum = 0;
/* Loop until the next waveheader indicates that we are done */
while( !(whOut[outBufNum].dwFlags & WHDR_DONE) )
{
//printf(".");
timeToGo = (long) (FRAMETIME - (timeGetTime()-lastWriteTime));
//timeToGo = (long) (FRAMETIME * 0.5);
if( timeToGo > 0 ) Sleep( (long) timeToGo );
}
result = waveOutUnprepareHeader(audioPort, &whOut[outBufNum], sizeof(WAVEHDR));
memcpy( whOut[outBufNum].lpData, buf, bufsize*sizeof(short));
result = waveOutPrepareHeader(audioPort, &whOut[outBufNum], sizeof(WAVEHDR));
result = waveOutWrite(audioPort, &whOut[outBufNum], sizeof(WAVEHDR));
lastWriteTime = timeGetTime();
return 0;
}
int RTSoundIO :: recordBuffer(short *buf, int bufsize)
{
// There is no current support for audio input under the WinMM API ... sorry!
return -1;
}
#endif