aboutsummaryrefslogblamecommitdiff
path: root/smelt/w32/sfx_w32.cpp
blob: c49162a96f8b77e9d87b5b9504f8c2defb9da882 (plain) (tree)



































































































































































































































































































































































                                                                                                                              
// -*- C++ -*-
/*
 * Simple MultimEdia LiTerator(SMELT)
 * by Chris Xiong 2015
 * GFX implementation based on OpenAL
 *
 * WARNING: This library is in development and interfaces would be very
 * unstable.
 *
 */
#include "smelt_internal.hpp"

static const char* SFX_SDL_SRCFN="smelt/sdl/sfx_sdl.cpp";
struct oggdata{const BYTE *data;DWORD size,pos;};
static void* readVorbis(const BYTE *data,const DWORD size, ALsizei *decomp_size,ALenum *fmt,ALsizei *freq);
static void* readRiffWv(const BYTE *data,const DWORD size, ALsizei *decomp_size,ALenum *fmt,ALsizei *freq);
SMSFX SMELT_IMPL::smSFXLoad(const char *path)
{
	FILE *pFile;DWORD size,rsize; char *buff;
	pFile=fopen(path,"rb");
	if(!pFile)return 0;
	fseek(pFile,0,SEEK_END);size=ftell(pFile);rewind(pFile);
	buff=(char*)malloc(sizeof(char)*size);
	if(!buff)return 0;
	rsize=fread(buff,1,size,pFile);
	if(rsize!=size)return 0;
	SMSFX ret=smSFXLoadFromMemory(buff,size);
	free(buff);
	fclose(pFile);
	return ret;
}
SMSFX SMELT_IMPL::smSFXLoadFromMemory(const char *ptr,DWORD size)
{
	if(pOpenALDevice&&!mute)
	{
		bool isOgg=size>4&&ptr[0]=='O'&&ptr[1]=='g'&&ptr[2]=='g'&&ptr[3]=='S';
		void *decompdata=NULL,*decomp=NULL;
		ALsizei decompsize=0,freq=0;
		ALenum fmt=AL_FORMAT_STEREO16;
		decompdata=readRiffWv((const BYTE*)ptr,size,&decompsize,&fmt,&freq);
		if(!decompdata)
		{
			if(!isOgg)return 0;
			else decompdata=readVorbis((const BYTE*)ptr,size,&decompsize,&fmt,&freq);
		}
		if(!decompdata)return 0;
		decomp=decompdata;
		ALuint buff=0;alGenBuffers(1,&buff);
		alBufferData(buff,fmt,decomp,decompsize,freq);
		free(decompdata);return(SMSFX)buff;
	}
	return 0;
}
SMCHN SMELT_IMPL::smSFXPlay(SMSFX fx,int vol,int pan,float pitch,bool loop)
{
	if(pOpenALDevice)
	{
		ALuint src=getSource();
		if(src)
		{
			if(vol<0)vol=0;if(vol>100)vol=100;
			if(pan<-100)pan=-100;if(pan>100)pan=100;
			alSourceStop(src);
			alSourcei(src,AL_BUFFER,(ALint)fx);
			alSourcef(src,AL_GAIN,((ALfloat)vol)/100.);
			alSourcef(src,AL_PITCH,pitch);
			alSource3f(src,AL_POSITION,((ALfloat)pan)/100.,.0,.0);
			alSourcei(src,AL_LOOPING,loop?AL_TRUE:AL_FALSE);
			alSourcePlay(src);
		}
		return src;
	}
	return 0;
}
float SMELT_IMPL::smSFXGetLengthf(SMSFX fx)
{
	ALuint buff=(ALuint)fx;
	ALint freq;
	DWORD lend=smSFXGetLengthd(fx);
	alGetBufferi(buff,AL_FREQUENCY,&freq);
	float ret=lend/(float)freq;
	return ret;
}
DWORD SMELT_IMPL::smSFXGetLengthd(SMSFX fx)
{
	if(pOpenALDevice)
	{
		ALint size_b,chnc,bit;
		ALuint buff=(ALuint)fx;
		alGetBufferi(buff,AL_SIZE,&size_b);
		alGetBufferi(buff,AL_CHANNELS,&chnc);
		alGetBufferi(buff,AL_BITS,&bit);
		DWORD ret=size_b*8/(chnc*bit);
		return ret;
	}
	return -1;
}
void SMELT_IMPL::smSFXSetLoopPoint(SMSFX fx,DWORD l,DWORD r)
{
	if(pOpenALDevice)
	{
		ALint pt[2];pt[0]=l;pt[1]=r;
		alBufferiv((ALuint)fx,AL_LOOP_POINTS_SOFT,pt);
	}
}
void SMELT_IMPL::smSFXFree(SMSFX fx)
{
	if(pOpenALDevice)
	{
		ALuint buff=(ALuint)fx;
		alDeleteBuffers(1,&buff);
	}
}
void SMELT_IMPL::smChannelVol(SMCHN chn,int vol)
{
	if(pOpenALDevice)
	{
		if(vol<0)vol=0;if(vol>100)vol=100;
		alSourcef((ALuint)chn,AL_GAIN,((ALfloat)vol)/100.);
	}
}
void SMELT_IMPL::smChannelPan(SMCHN chn,int pan)
{
	if(pOpenALDevice)
	{
		if(pan<-100)pan=-100;if(pan>100)pan=100;
		alSource3f((ALuint)chn,AL_POSITION,((ALfloat)pan)/100.,.0,.0);
	}
}
void SMELT_IMPL::smChannelPitch(SMCHN chn,float pitch)
{
	if(pOpenALDevice)alSourcef((ALuint)chn,AL_PITCH,pitch);
}
void SMELT_IMPL::smChannelPause(SMCHN chn)
{
	if(pOpenALDevice)alSourcePause((ALuint)chn);
}
void SMELT_IMPL::smChannelResume(SMCHN chn)
{
	if(pOpenALDevice)alSourcePlay((ALuint)chn);
}
void SMELT_IMPL::smChannelStop(SMCHN chn)
{
	if(pOpenALDevice)alSourceStop((ALuint)chn);
}
void SMELT_IMPL::smChannelPauseAll()
{
	if(pOpenALDevice)
	{
		ALCcontext *ctx=alcGetCurrentContext();
		alcSuspendContext(ctx);
	}
}
void SMELT_IMPL::smChannelResumeAll()
{
	if(pOpenALDevice)
	{
		ALCcontext *ctx=alcGetCurrentContext();
		alcProcessContext(ctx);
	}
}
void SMELT_IMPL::smChannelStopAll()
{
	if(pOpenALDevice)
	{
		for(int i=0;i<scnt;++i)alSourceStop(src[i]);
	}
}
bool SMELT_IMPL::smChannelIsPlaying(SMCHN chn)
{
	if(pOpenALDevice)
	{
		ALint state=AL_STOPPED;
		alGetSourceiv((ALuint)chn,AL_SOURCE_STATE,&state);
		return state==AL_PLAYING;
	}
	return false;
}
float SMELT_IMPL::smChannelGetPosf(SMCHN chn)
{
	if(pOpenALDevice)
	{
		ALfloat ret;
		alGetSourcef((ALuint)chn,AL_SEC_OFFSET,&ret);
		return (float)ret;
	}
	return -1.;
}
void SMELT_IMPL::smChannelSetPosf(SMCHN chn,float pos)
{
	if(pOpenALDevice)alSourcef((ALuint)chn,AL_SEC_OFFSET,(ALfloat)pos);
}
int SMELT_IMPL::smChannelGetPosd(SMCHN chn)
{
	if(pOpenALDevice)
	{
		ALint ret;
		alGetSourcei((ALuint)chn,AL_SAMPLE_OFFSET,&ret);
		return (int)ret;
	}
	return -1;
}
void SMELT_IMPL::smChannelSetPosd(SMCHN chn,int pos)
{
	if(pOpenALDevice)alSourcei((ALuint)chn,AL_SAMPLE_OFFSET,(ALint)pos);
}

static size_t oggRead(void *ptr,size_t size,size_t nmemb,void *ds)
{
	oggdata *data=(oggdata*)ds;
	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 oggSeek(void *ds,ogg_int64_t offset,int whence)
{
	oggdata *data=(oggdata*)ds;
	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 long oggLocate(void *ds)
{
	oggdata* data=(oggdata*)ds;
	return (long)data->pos;
}
static int oggClose(void *ds){return 0;}

static ov_callbacks oggrt={oggRead,oggSeek,oggClose,oggLocate};
static void* readVorbis(const BYTE *data,const DWORD size, ALsizei *decomp_size,ALenum *fmt,ALsizei *freq)
{
	oggdata adata={data,size,0};
	OggVorbis_File vf;
	memset(&vf,0,sizeof(vf));
	if(ov_open_callbacks(&adata,&vf,NULL,0,oggrt)==0)
	{
		int bs=0;
		vorbis_info *info=ov_info(&vf,-1);
		*decomp_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=1024*16;
		BYTE *ret=(ALubyte*)malloc(allocated);
		while((rc=ov_read(&vf,buf,sizeof(buf),0,2,1,&bs))!=0)
		{
			if(rc>0)
			{
				*decomp_size+=rc;
				if((unsigned)*decomp_size>=allocated)
				{
					allocated<<=1;
					ALubyte *tmp=(ALubyte*)realloc(ret,allocated);
					if(!tmp){free(ret);ret=NULL;break;}
					ret=tmp;
				}
				memcpy(ret+(*decomp_size-rc),buf,rc);
			}
		}
		ov_clear(&vf);
		return ret;
	}
	return NULL;
}
static void* readRiffWv(const BYTE *data,const DWORD size, ALsizei *decomp_size,ALenum *fmt,ALsizei *freq)
{
	if(data[0x0]!='R'||data[0x1]!='I'||data[0x2]!='F'||data[0x3]!='F')return NULL;
	if(data[0x8]!='W'||data[0x9]!='A'||data[0xA]!='V'||data[0xB]!='E')return NULL;
	if(data[0xC]!='f'||data[0xD]!='m'||data[0xE]!='t'||data[0xF]!=' ')return NULL;
	if(data[0x16]==2)
	{
		if(data[0x22]==16)*fmt=AL_FORMAT_STEREO16;
		else if(data[0x22]==8)*fmt=AL_FORMAT_STEREO8;
		else return NULL;
	}
	else if(data[0x16]==1)
	{
		if(data[0x22]==16)*fmt=AL_FORMAT_MONO16;
		else if(data[0x22]==8)*fmt=AL_FORMAT_MONO8;
		else return NULL;
	}else return NULL;
	*decomp_size=(ALsizei)(data[0x28]|(data[0x29]<<8L)|(data[0x2A]<<16L)|(data[0x2B]<<24L));
	*freq=(ALsizei)(data[0x18]|(data[0x19]<<8L)|(data[0x1A]<<16L)|(data[0x1B]<<24L));
	ALubyte *ret=(ALubyte*)malloc(*decomp_size);
	memcpy(ret,data+44,*decomp_size);
	return ret;
}
ALuint SMELT_IMPL::getSource()
{
	for(int i=0;i<scnt;++i)
	{
		ALint state=AL_PLAYING;
		alGetSourceiv(src[i],AL_SOURCE_STATE,&state);
		if(!(state==AL_PLAYING||state==AL_PAUSED))return src[i];
	}
	if(scnt>=SRC_MAX)return 0;
	ALuint ret=0;
	alGenSources(1,&ret);if(!ret)return 0;
	src[scnt++]=ret;return ret;
}
bool SMELT_IMPL::initOAL()
{
	if(pOpenALDevice)return true;
	scnt=0;memset(src,0,sizeof(src));
	smLog("%s:" SLINE ": Initializing OpenAL...\n",SFX_SDL_SRCFN);
	ALCdevice *dev=alcOpenDevice(NULL);
	if(!dev)
	{
		smLog("%s:" SLINE ": alcOpenDevice() failed.\n",SFX_SDL_SRCFN);
		return mute=true;
	}
	ALint caps[]={ALC_FREQUENCY,44100,0};
	ALCcontext *ctx=alcCreateContext(dev,caps);
	if(!ctx)
	{
		smLog("%s:" SLINE ": alcCreateContext() failed.\n",SFX_SDL_SRCFN);
		return mute=true;
	}
	alcMakeContextCurrent(ctx);alcProcessContext(ctx);
	smLog("%s:" SLINE ": Done OpenAL initialization\n",SFX_SDL_SRCFN);
	smLog("%s:" SLINE ": AL_VENDOR: %s\n",SFX_SDL_SRCFN,(char*)alGetString(AL_VENDOR));
	smLog("%s:" SLINE ": AL_RENDERER: %s\n",SFX_SDL_SRCFN,(char*)alGetString(AL_RENDERER));
	smLog("%s:" SLINE ": AL_VERSION: %s\n",SFX_SDL_SRCFN,(char*)alGetString(AL_VERSION));
	const char* ext=(const char*)alGetString(AL_EXTENSIONS);
	lpp=strstr(ext,"AL_SOFT_loop_points")!=NULL;
	if(!lpp)smLog("%s:" SLINE ": Warning: loop points not supported. Please recompile with OpenAL Soft.\n",SFX_SDL_SRCFN);
	pOpenALDevice=(void*)dev;
	return true;
}
void SMELT_IMPL::finiOAL()
{
	if(pOpenALDevice)
	{
		for(int i=0;i<scnt;++i)alSourceStop(src[i]);
		alDeleteSources(scnt,src);scnt=0;memset(src,0,sizeof(src));
		ALCcontext *ctx=alcGetCurrentContext();
		ALCdevice *dev=alcGetContextsDevice(ctx);
		alcMakeContextCurrent(NULL);
		alcSuspendContext(ctx);alcDestroyContext(ctx);
		alcCloseDevice(dev);pOpenALDevice=NULL;
	}
}