proteaAudio/proAudioSdl.cpp
2012-05-27 18:34:36 -04:00

290 lines
10 KiB
C++

#include "proAudioSdl.h"
extern "C" {
#include <SDL.h>
};
#include <map>
#include <cmath>
#include <climits>
using namespace std;
//--- class DeviceAudioSdl ------------------------------------------
/// internal class to store sample data
class _AudioTrack {
public:
/// default constructor
_AudioTrack(Uint8 * pData=0, unsigned int length=0) : data(pData), dlen(length) {
dpos=0; disparity=0.0f; volL=1.0f; volR=1.0f; pitch=1.0f; isLoop=false; isPlaying=false; };
/// pointer to raw sample data
Uint8 *data;
/// position in playback
Uint32 dpos;
/// length of sample in bytes
Uint32 dlen;
/// disparity in seconds between left and right, normally 0.0f
float disparity;
/// left volume
float volL;
/// right volume
float volR;
/// pitch factor, normally 1.0f
float pitch;
/// stores whether sample has to be looped
bool isLoop;
/// stores whether sample is currently playing
bool isPlaying;
};
DeviceAudio* DeviceAudioSdl::create(unsigned int nTracks, unsigned int frequency, unsigned int chunkSize) {
if(!s_instance) {
DeviceAudioSdl* pAudio = new DeviceAudioSdl(nTracks,frequency,chunkSize);
if(!pAudio->m_freqOut) delete pAudio;
else s_instance = pAudio;
}
return s_instance;
}
DeviceAudioSdl::DeviceAudioSdl(unsigned int nTracks, unsigned int frequency, unsigned int chunkSize) : DeviceAudio(), m_sampleCounter(0) {
// initialize SDL sound system:
if(!SDL_WasInit(SDL_INIT_AUDIO)&&(SDL_InitSubSystem(SDL_INIT_AUDIO)<0)) {
fprintf(stderr, "DeviceAudioSdl ERROR: cannot initialize SDL audio subsystem.\n");
return;
}
// set the audio format:
SDL_AudioSpec desired;
desired.freq = frequency;
desired.format = AUDIO_S16;
desired.channels = 2; // 1 = mono, 2 = stereo
desired.samples = chunkSize; // good low-latency value for callback
desired.callback = cbOutput;
desired.userdata = NULL;
// open the audio device
if ( SDL_OpenAudio(&desired, &m_spec) < 0 ) {
fprintf(stderr, "DeviceAudioSdl ERROR: Couldn't open audio.\n");
return;
}
if((m_spec.format!=AUDIO_S16)||(m_spec.channels!=2)) {
fprintf(stderr, "DeviceAudioSdl WARNING: Could not get desired signed 16 bit stereo. Expect low quality sound.\n");
m_isDesiredFormat=false;
}
else {
m_isDesiredFormat=true;
}
// initialize tracks:
m_nSound=nTracks;
ma_sound=new _AudioTrack[m_nSound];
SDL_PauseAudio(0); // start sound
m_freqOut = frequency;
}
DeviceAudioSdl::~DeviceAudioSdl() {
SDL_PauseAudio(1);
SDL_CloseAudio();
delete [] ma_sound;
}
void DeviceAudioSdl::cbOutput(void * userData, Uint8 *stream, int len) {
if(dynamic_cast<DeviceAudioSdl*>(s_instance)->m_isDesiredFormat)
dynamic_cast<DeviceAudioSdl*>(s_instance)->mixOutputFloat((signed short *)stream, len/2);
else dynamic_cast<DeviceAudioSdl*>(s_instance)->mixOutputSInt(stream, len);
}
void DeviceAudioSdl::mixOutputFloat(signed short *outputBuffer, unsigned int nFrames) {
for(unsigned int j=0; j<nFrames; j+=2) {
float left=0.0f;
float right=0.0f;
for (unsigned int i=0; i<m_nSound; ++i ) if(ma_sound[i].isPlaying) {
if((ma_sound[i].pitch==1.0f)&&!ma_sound[i].disparity) { // use optimized default mixing:
unsigned int currPos=ma_sound[i].dpos+j;
if(ma_sound[i].isLoop) currPos%=ma_sound[i].dlen;
else if(currPos >= ma_sound[i].dlen) continue;
left +=float(*((Sint16 *)(&ma_sound[i].data[currPos])))
*m_volL*ma_sound[i].volL;
right+=float(*((Sint16 *)(&ma_sound[i].data[currPos])))
*m_volR*ma_sound[i].volR;
}
else { // use linear interpolation and disparity:
double fract=ma_sound[i].dpos+j*ma_sound[i].pitch;
int currPos=int(fract*0.5f)*2;
fract=(fract-currPos)*0.5f;
int currPosL= (ma_sound[i].disparity<0.0f)
? currPos-2*int(m_spec.freq*-ma_sound[i].disparity)
: currPos;
int currPosR= (ma_sound[i].disparity>0.0f)
? currPos-2*int(m_spec.freq*ma_sound[i].disparity)
: currPos;
if(ma_sound[i].isLoop) {
currPosL=(currPosL+10*ma_sound[i].dlen-2)%(ma_sound[i].dlen-2);
currPosR=(currPosR+10*ma_sound[i].dlen-2)%(ma_sound[i].dlen-2);
}
if((currPosL < int(ma_sound[i].dlen)-2)&&(currPosL>=0)) {
float currWavL=(1.0f-fract)*float(*((Sint16 *)(&ma_sound[i].data[currPosL])))
+fract*float(*((Sint16 *)(&ma_sound[i].data[currPosL+2])));
left +=currWavL*m_volL*ma_sound[i].volL;
}
if((currPosR < int(ma_sound[i].dlen)-2)&&(currPosR>=0)) {
float currWavR=(1.0f-fract)*float(*((Sint16 *)(&ma_sound[i].data[currPosR])))
+fract*float(*((Sint16 *)(&ma_sound[i].data[currPosR+2])));
right+=currWavR*m_volR*ma_sound[i].volR;
}
}
}
// clamp:
if(left>(float)SHRT_MAX) outputBuffer[j]=SHRT_MAX;
else if(left<(float)SHRT_MIN) outputBuffer[j]=SHRT_MIN;
else outputBuffer[j]=(Sint16)left;
if(right>(float)SHRT_MAX) outputBuffer[j+1]=SHRT_MAX;
else if(right<(float)SHRT_MIN) outputBuffer[j+1]=SHRT_MIN;
else outputBuffer[j+1]=(Sint16)right;
}
for (unsigned int i=0; i<m_nSound; ++i ) {
if(ma_sound[i].pitch==1.0f) ma_sound[i].dpos += nFrames;
else ma_sound[i].dpos += (unsigned int)(nFrames*ma_sound[i].pitch);
if(ma_sound[i].isLoop) ma_sound[i].dpos%=ma_sound[i].dlen;
else if(ma_sound[i].dpos>ma_sound[i].dlen+2*abs(int(m_spec.freq*-ma_sound[i].disparity)))
ma_sound[i].isPlaying=false;
}
}
void DeviceAudioSdl::mixOutputSInt(Uint8 *stream, int len) {
for (unsigned int i=0; i<m_nSound; ++i ) {
unsigned int amount = ma_sound[i].dlen-ma_sound[i].dpos;
if (amount > (unsigned int)len) amount = (unsigned int)len;
SDL_MixAudio(stream, &ma_sound[i].data[ma_sound[i].dpos], amount, SDL_MIX_MAXVOLUME);
ma_sound[i].dpos += amount;
}
}
static void adjustVolume(signed short* data, size_t len, float volume) {
for(size_t i=0; i<len; ++i) {
signed short & shortValue=data[i];
float value=shortValue*volume;
if(value>(float)SHRT_MAX) value=(float)SHRT_MAX; // clamp
else if(value<(float)SHRT_MIN) value=(float)SHRT_MIN;
shortValue=(signed short)value;
}
}
unsigned int DeviceAudioSdl::sampleFromMemory(const AudioSample & sample, float volume) {
Uint8 destChannels= m_isDesiredFormat ? 1 : m_spec.channels; // convert to mono
Uint16 format = sample.bytesPerSample()==2 ? AUDIO_S16 : sample.bytesPerSample()==1 ? AUDIO_S8 : 0;
if(!format) {
fprintf(stderr, "DeviceAudioSdl WARNING: %i bit samples not supported.\n", sample.bitsPerSample());
return 0;
}
SDL_AudioCVT cvt;
SDL_BuildAudioCVT(&cvt, format, sample.channels(), sample.sampleRate(),
m_spec.format, destChannels, m_spec.freq);
cvt.buf = new Uint8[sample.size()*cvt.len_mult];
memcpy(cvt.buf, sample.data(), sample.size());
cvt.len = sample.size();
SDL_ConvertAudio(&cvt);
_AudioTrack track;
track.data = cvt.buf;
track.dlen = cvt.len_cvt;
track.dpos = 0;
if (!track.dlen) {
fprintf(stderr, "DeviceAudioSdl WARNING: Sample has zero length.\n");
return 0;
}
// adjust volume:
if((volume!=1.0f)&&m_isDesiredFormat)
adjustVolume((signed short *)track.data, track.dlen/2, volume);
mm_sample.insert(make_pair(++m_sampleCounter,track));
return m_sampleCounter;
}
bool DeviceAudioSdl::sampleDestroy(unsigned int sample) {
// look for sample:
map<unsigned int,_AudioTrack>::iterator iter=mm_sample.find(sample);
if( iter == mm_sample.end() ) return false;
// stop currently playing sounds referring to this sample:
SDL_LockAudio();
for (unsigned int i=0; i<m_nSound; ++i ) if(ma_sound[i].data == iter->second.data)
ma_sound[i].isPlaying=false;
SDL_UnlockAudio();
// cleanup:
delete iter->second.data;
if(iter->first==m_sampleCounter) --m_sampleCounter;
mm_sample.erase(iter);
return true;
}
unsigned int DeviceAudioSdl::soundPlay(unsigned int sample, float volumeL, float volumeR, float disparity, float pitch) {
// look for sample:
map<unsigned int,_AudioTrack>::iterator iter=mm_sample.find(sample);
if(iter==mm_sample.end()) return 0; // no sample found
// look for an empty (or finished) sound track
unsigned int i;
for ( i=0; i<m_nSound; ++i )
if (!ma_sound[i].isPlaying) break;
if ( i == m_nSound ) return 0; // no empty slot found
// put the sample data in the slot and play it
SDL_LockAudio();
ma_sound[i].data = iter->second.data;
ma_sound[i].dlen = iter->second.dlen;
ma_sound[i].dpos = 0;
ma_sound[i].volL=volumeL;
ma_sound[i].volR=volumeR;
ma_sound[i].disparity=disparity;
ma_sound[i].pitch=fabs(pitch);
ma_sound[i].isLoop=false;
ma_sound[i].isPlaying=true;
SDL_UnlockAudio();
return i+1;
}
unsigned int DeviceAudioSdl::soundLoop(unsigned int sample, float volumeL, float volumeR, float disparity, float pitch) {
unsigned int ret=soundPlay(sample,volumeL,volumeR,disparity, pitch);
if(ret) {
SDL_LockAudio();
ma_sound[ret-1].isLoop=true;
SDL_UnlockAudio();
}
return ret;
}
bool DeviceAudioSdl::soundUpdate(unsigned int sound, float volumeL, float volumeR, float disparity, float pitch ) {
if(!sound || (sound>m_nSound) || !ma_sound[sound-1].isPlaying) return false;
SDL_LockAudio();
ma_sound[--sound].volL=volumeL;
ma_sound[sound].volR=volumeR;
ma_sound[sound].disparity=disparity;
ma_sound[sound].pitch=fabs(pitch);
SDL_UnlockAudio();
return true;
}
bool DeviceAudioSdl::soundStop(unsigned int sound) {
if(!sound||(sound>m_nSound)||!ma_sound[sound-1].isPlaying) return false;
SDL_LockAudio();
ma_sound[sound-1].isPlaying=false;
SDL_UnlockAudio();
return true;
}
void DeviceAudioSdl::soundStop() {
SDL_LockAudio();
for (unsigned int i=0; i<m_nSound; ++i )
ma_sound[i].isPlaying=false;
SDL_UnlockAudio();
}
unsigned int DeviceAudioSdl::soundActive() const {
unsigned int ret = 0, i;
for ( i=0; i<m_nSound; ++i )
if (ma_sound[i].isPlaying) ++ret;
return ret;
}