/* ** Haaf's Game Engine 1.8 ** Copyright (C) 2003-2007, Relish Games ** hge.relishgames.com ** ** Core functions implementation: audio routines */ // This is just enough to get Hammerfight working without using libBA$$. // If you want a full HGE audio implementation, you should either use the // code in sound_libbass.cpp (and maybe pay for a BA$$ licen$e), or improve // this code. // Well, this code is now improved by Chris Xiong, adding several new interfaces. // (Such as seeking in sample...) // Channel functions are fully supported now. However music and streaming are // still not supported. Some APIs changed for OpenAL is different from BA$$. #include "hge_impl.h" #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "ogg/ogg.h" #include "vorbis/vorbisfile.h" static const char* SOUND_SRC_FN="hge/sound.cpp"; struct oggcbdata { const BYTE *data; DWORD size; DWORD pos; }; static size_t oggcb_read(void *ptr, size_t size, size_t nmemb, void *datasource) { oggcbdata *data = (oggcbdata *) datasource; const DWORD avail = data->size - data->pos; size_t want = nmemb * size; if (want > avail) want = avail - (avail % size); if (want > 0) { memcpy(ptr, data->data + data->pos, want); data->pos += want; } return want / size; } static int oggcb_seek(void *datasource, ogg_int64_t offset, int whence) { oggcbdata *data = (oggcbdata *) datasource; ogg_int64_t pos = 0; switch (whence) { case SEEK_SET: pos = offset; break; case SEEK_CUR: pos = ((ogg_int64_t) data->pos) + offset; break; case SEEK_END: pos = ((ogg_int64_t) data->size) + offset; break; default: return -1; } if ( (pos < 0) || (pos > ((ogg_int64_t) data->size)) ) return -1; data->pos = (DWORD) pos; return 0; } static int oggcb_close(void *datasource) { return 0; } static long oggcb_tell(void *datasource) { oggcbdata *data = (oggcbdata *) datasource; return (long) data->pos; } static ov_callbacks oggcb = { oggcb_read, oggcb_seek, oggcb_close, oggcb_tell }; static void *decompress_vorbis(const BYTE *data, const DWORD size, ALsizei *decompressed_size, ALenum *fmt, ALsizei *freq) { #ifdef __POWERPC__ const int bigendian = 1; #else const int bigendian = 0; #endif oggcbdata cbdata = { data, size, 0 }; OggVorbis_File vf; memset(&vf, '\0', sizeof (vf)); if (ov_open_callbacks(&cbdata, &vf, NULL, 0, oggcb) == 0) { int bitstream = 0; vorbis_info *info = ov_info(&vf, -1); *decompressed_size = 0; *fmt = (info->channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; *freq = info->rate; if ((info->channels != 1) && (info->channels != 2)) { ov_clear(&vf); return NULL; } char buf[1024 * 16]; long rc = 0; size_t allocated = 64 * 1024; BYTE *retval = (ALubyte *) malloc(allocated); while ( (rc = ov_read(&vf, buf, sizeof (buf), bigendian, 2, 1, &bitstream)) != 0 ) { if (rc > 0) { *decompressed_size += rc; if ((unsigned)*decompressed_size >= allocated) { allocated *= 2; ALubyte *tmp = (ALubyte *) realloc(retval, allocated); if (tmp == NULL) { free(retval); retval = NULL; break; } retval = tmp; } memcpy(retval + (*decompressed_size - rc), buf, rc); } } ov_clear(&vf); return retval; } return NULL; } #define MAX_SIDS 128 static int sidcount = 0; static ALuint sids[MAX_SIDS]; static ALuint get_source() { for (int i = 0; i < sidcount; i++) { ALint state = AL_PLAYING; alGetSourceiv(sids[i], AL_SOURCE_STATE, &state); if ((state != AL_PLAYING) && (state != AL_PAUSED)) return sids[i]; } if (sidcount >= MAX_SIDS) return 0; ALuint sid = 0; alGenSources(1, &sid); if (sid == 0) return 0; sids[sidcount++] = sid; return sid; } HEFFECT CALL HGE_Impl::Effect_Load(const char *filename, DWORD size) { DWORD _size; void *data; if(hOpenAL) { if(bSilent) return 1; if(size) { data=(void *)filename; _size=size; } else { data=Resource_Load(filename, &_size); if(!data) return 0; } const BYTE *magic = (const BYTE *) data; const bool isOgg = ( (_size > 4) && (magic[0] == 'O') && (magic[1] == 'g') && (magic[2] == 'g') && (magic[3] == 'S') ); if (!isOgg) { if(!size) Resource_Free(data); return 0; } void *allocation_decompressed = NULL; void *decompressed = NULL; ALsizei decompressed_size = 0; ALsizei freq = 0; ALenum fmt = AL_FORMAT_STEREO16; if (isOgg) { /*if (alIsExtensionPresent((const ALchar *) "AL_EXT_vorbis"))//useless { fmt = alGetEnumValue((const ALchar *) "AL_FORMAT_VORBIS_EXT"); decompressed = data; decompressed_size = _size; } else {*/ allocation_decompressed = decompress_vorbis((const BYTE *) data, _size, &decompressed_size, &fmt, &freq); decompressed = allocation_decompressed; //} } ALuint bid = 0; alGenBuffers(1, &bid); alBufferData(bid, fmt, decompressed, decompressed_size, freq); free(allocation_decompressed); // not delete[] ! if(!size) Resource_Free(data); return (HEFFECT) bid; } else return 0; } HCHANNEL CALL HGE_Impl::Effect_Play(HEFFECT eff) { return Effect_PlayEx(eff, 1.0f, 0, 1.0f, false); } HCHANNEL CALL HGE_Impl::Effect_PlayEx(HEFFECT eff, float volume, float pan, float pitch, bool loop) { if(hOpenAL) { const ALuint sid = get_source(); // find an unused sid, or generate a new one. if (sid != 0) { if (volume < 0) volume = 0; else if (volume > 1.0) volume = 1.0; if (pan < -1.0) pan = -1.0; else if (pan > 1.0) pan = 1.0; alSourceStop(sid); alSourcei(sid, AL_BUFFER, (ALint) eff); alSourcef(sid, AL_GAIN, volume); alSourcef(sid, AL_PITCH, pitch); alSource3f(sid, AL_POSITION, pan, 0.0f, 0.0f); alSourcei(sid, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); alSourcePlay(sid); } return sid; } else return 0; } void CALL HGE_Impl::Effect_Free(HEFFECT eff) { if(hOpenAL) { ALuint bid = (ALuint) eff; alDeleteBuffers(1, &bid); } } //Castrate!! HMUSIC CALL HGE_Impl::Music_Load(const char *filename, DWORD size){return 0;} HCHANNEL CALL HGE_Impl::Music_Play(HMUSIC mus, bool loop, int volume, int order, int row){return 0;} void CALL HGE_Impl::Music_Free(HMUSIC mus){} void CALL HGE_Impl::Music_SetAmplification(HMUSIC music, int ampl){} int CALL HGE_Impl::Music_GetAmplification(HMUSIC music){return -1;} int CALL HGE_Impl::Music_GetLength(HMUSIC music){return -1;} void CALL HGE_Impl::Music_SetPos(HMUSIC music, int order, int row){} bool CALL HGE_Impl::Music_GetPos(HMUSIC music, int *order, int *row){return false;} void CALL HGE_Impl::Music_SetInstrVolume(HMUSIC music, int instr, int volume){} int CALL HGE_Impl::Music_GetInstrVolume(HMUSIC music, int instr){return -1;} void CALL HGE_Impl::Music_SetChannelVolume(HMUSIC music, int channel, int volume){} int CALL HGE_Impl::Music_GetChannelVolume(HMUSIC music, int channel){return -1;} HSTREAM CALL HGE_Impl::Stream_Load(const char *filename, DWORD size){return 0;} void CALL HGE_Impl::Stream_Free(HSTREAM stream){} HCHANNEL CALL HGE_Impl::Stream_Play(HSTREAM stream, bool loop, int volume){return 0;} void CALL HGE_Impl::Channel_SetPanning(HCHANNEL chn, float pan) { if(pan>1.0)pan=1.0; if(pan<-1.0)pan=-1.0; if(hOpenAL) { alSource3f((ALuint) chn, AL_POSITION, pan, 0.0f, 0.0f); } } void CALL HGE_Impl::Channel_SetVolume(HCHANNEL chn, float volume) { if(hOpenAL) { if (volume < 0) volume = 0; else if (volume > 1.0f) volume = 1.0f; alSourcef((ALuint) chn, AL_GAIN, volume); } } void CALL HGE_Impl::Channel_SetPitch(HCHANNEL chn, float pitch) { if(hOpenAL) { alSourcef((ALuint) chn, AL_PITCH, pitch); } } void CALL HGE_Impl::Channel_Pause(HCHANNEL chn) { if(hOpenAL) { alSourcePause((ALuint) chn); } } void CALL HGE_Impl::Channel_Resume(HCHANNEL chn) { if(hOpenAL) { alSourcePlay((ALuint) chn); } } void CALL HGE_Impl::Channel_Stop(HCHANNEL chn) { if(hOpenAL) { alSourceStop((ALuint) chn); } } void CALL HGE_Impl::Channel_PauseAll() { if(hOpenAL) { ALCcontext *ctx = alcGetCurrentContext(); alcSuspendContext(ctx); } } void CALL HGE_Impl::Channel_ResumeAll() { if(hOpenAL) { ALCcontext *ctx = alcGetCurrentContext(); alcProcessContext(ctx); } } void CALL HGE_Impl::Channel_StopAll() { if(hOpenAL) { for (int i = 0; i < sidcount; i++) alSourceStop(sids[i]); } } bool CALL HGE_Impl::Channel_IsPlaying(HCHANNEL chn) { if(hOpenAL) { ALint state = AL_STOPPED; alGetSourceiv((ALuint) chn, AL_SOURCE_STATE, &state); return state == AL_PLAYING; } else return false; } //The following features are ported to OpenAL by Chris float CALL HGE_Impl::Channel_GetLength(HCHANNEL chn) //WARNING!!:In OpenAL We pass HEFFECT insteat HCHANNEL in! //This should be fixed. { //Well, you developers should know this "by heart". if (hOpenAL) { ALint sizeInBytes; ALint channels; ALint bits; ALuint bufferID=chn; alGetBufferi(bufferID, AL_SIZE, &sizeInBytes); alGetBufferi(bufferID, AL_CHANNELS, &channels); alGetBufferi(bufferID, AL_BITS, &bits); int lengthInSamples = sizeInBytes * 8 / (channels * bits); ALint frequency; alGetBufferi(bufferID, AL_FREQUENCY, &frequency); float durationInSeconds = (float)lengthInSamples / (float)frequency; return durationInSeconds; } return -1; } float CALL HGE_Impl::Channel_GetPos(HCHANNEL chn) { if (hOpenAL) { ALfloat res; alGetSourcef((ALuint)chn,AL_SEC_OFFSET,&res); return (float)res; } else return -1.0f; } void CALL HGE_Impl::Channel_SetPos(HCHANNEL chn, float fSeconds) { if (hOpenAL) { alSourcef((ALuint)chn,AL_SEC_OFFSET,(ALfloat)fSeconds); } } int CALL HGE_Impl::Channel_GetPos_BySample(HCHANNEL chn) { if (hOpenAL) { ALint res; alGetSourcei((ALuint)chn,AL_SAMPLE_OFFSET,&res); return (int)res; } else return -1; } void CALL HGE_Impl::Channel_SetPos_BySample(HCHANNEL chn, int iSample) { if (hOpenAL) { alSourcei((ALuint)chn,AL_SAMPLE_OFFSET,(ALint)iSample); } } void CALL HGE_Impl::Channel_SlideTo(HCHANNEL channel, float time, int volume, int pan, float pitch){} bool CALL HGE_Impl::Channel_IsSliding(HCHANNEL channel){return false;} //////// Implementation //////// bool HGE_Impl::_SoundInit() { if(!bUseSound || hOpenAL) return true; bSilent=false; sidcount = 0; memset(sids, '\0', sizeof (sids)); System_Log("%s: Starting OpenAL init",SOUND_SRC_FN); ALCdevice *dev = alcOpenDevice(NULL); if (!dev) { System_Log("%s: alcOpenDevice(NULL) failed, using no sound",SOUND_SRC_FN); bSilent=true; return true; } ALint caps[] = { ALC_FREQUENCY, nSampleRate, 0 }; ALCcontext *ctx = alcCreateContext(dev, caps); if (!ctx) { alcCloseDevice(dev); System_Log("%s: alcCreateContext(NULL) failed, using no sound",SOUND_SRC_FN); bSilent=true; return true; } alcMakeContextCurrent(ctx); alcProcessContext(ctx); System_Log("%s: OpenAL initialized successfully.",SOUND_SRC_FN); System_Log("%s: AL_VENDOR: %s",SOUND_SRC_FN, (char *) alGetString(AL_VENDOR)); System_Log("%s: AL_RENDERER: %s",SOUND_SRC_FN, (char *) alGetString(AL_RENDERER)); System_Log("%s: AL_VERSION: %s",SOUND_SRC_FN,(char *) alGetString(AL_VERSION)); System_Log("%s: AL_EXTENSIONS: %s",SOUND_SRC_FN,(char *) alGetString(AL_EXTENSIONS)); hOpenAL = (void *) 0x1; // something non-NULL (!!! FIXME: this should eventually be a library handle). _SetFXVolume(nFXVolume); //_SetMusVolume(nMusVolume); //_SetStreamVolume(nStreamVolume); return true; } void HGE_Impl::_SoundDone() { CStreamList *stmItem=streams, *stmNext; if(hOpenAL) { for (int i = 0; i < sidcount; i++) alSourceStop(sids[i]); alDeleteSources(sidcount, sids); sidcount = 0; memset(sids, '\0', sizeof (sids)); ALCcontext *ctx = alcGetCurrentContext(); ALCdevice *dev = alcGetContextsDevice(ctx); alcMakeContextCurrent(NULL); alcSuspendContext(ctx); alcDestroyContext(ctx); alcCloseDevice(dev); hOpenAL=0; while(stmItem) { stmNext=stmItem->next; Resource_Free(stmItem->data); delete stmItem; stmItem=stmNext; } streams=0; } } void HGE_Impl::_SetMusVolume(int vol){} void HGE_Impl::_SetStreamVolume(int vol){} void HGE_Impl::_SetFXVolume(int vol) { if(hOpenAL) { alListenerf(AL_GAIN, ((ALfloat) vol) / 100.0f); } }