#if 0 gcc ${CFLAGS:--s -O2} -c -Wno-unused-result sound.c `sdl-config --cflags` exit #endif #include "SDL.h" #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "sqlite3.h" #include "smallxrm.h" #include "quarks.h" #include "heromesh.h" typedef struct { Uint8*data; Uint32 len; // length in bytes } WaveSound; static Uint8 sound_on; static Sint16 mmlvolume=32767; static SDL_AudioSpec spec; static WaveSound*standardsounds; static Uint16 nstandardsounds; static WaveSound*usersounds; static Uint16 nusersounds; static Uint8**user_sound_names; static FILE*l_fp; static long l_offset,l_size; static float wavevolume=1.0; static Uint8 needs_amplify=0; static Uint8*volatile wavesound; static volatile Uint32 wavelen; static void audio_callback(void*userdata,Uint8*stream,int len) { if(wavesound) { if(wavelen<=len) { memcpy(stream,wavesound,wavelen); memset(stream+wavelen,0,len-wavelen); wavesound=0; wavelen=0; } else { memcpy(stream,wavesound,len); wavesound+=len; len-=len; } } else { memset(stream,0,len); } //TODO: MML sounds } static int my_seek(SDL_RWops*cxt,int o,int w) { switch(w) { case RW_SEEK_SET: fseek(l_fp,l_offset+o,SEEK_SET); break; case RW_SEEK_CUR: fseek(l_fp,o,SEEK_CUR); break; case RW_SEEK_END: fseek(l_fp,l_offset+l_size+o,SEEK_SET); break; } return ftell(l_fp)-l_offset; } static int my_read(SDL_RWops*cxt,void*ptr,int size,int maxnum) { if(size*maxnum+l_offset>ftell(l_fp)+l_size) maxnum=(ftell(l_fp)-l_offset)/size; return fread(ptr,size,maxnum,l_fp); } static int my_close(SDL_RWops*cxt) { return 0; } static SDL_RWops my_rwops={ .seek=my_seek, .read=my_read, .close=my_close, }; static void amplify_wave_sound(const WaveSound*ws) { Uint32 n; Sint16*b=(Sint16*)ws->data; Uint32 m=ws->len/sizeof(Sint16); if(!needs_amplify || !b) return; for(n=0;n<m;n++) b[n]*=wavevolume; } static void load_sound(FILE*fp,long offset,long size,WaveSound*ws) { SDL_AudioSpec src; SDL_AudioCVT cvt; Uint32 len=0; Uint8*buf=0; ws->data=0; ws->len=0; l_fp=fp; l_offset=offset; l_size=size; if(!SDL_LoadWAV_RW(&my_rwops,0,&src,&buf,&len)) { if(main_options['v']) fprintf(stderr,"[Cannot load wave audio at %ld (%ld bytes): %s]\n",offset,size,SDL_GetError()); return; } memset(&cvt,0,sizeof(SDL_AudioCVT)); if(SDL_BuildAudioCVT(&cvt,src.format,src.channels,src.freq,spec.format,spec.channels,spec.freq)<0) goto fail; cvt.buf=malloc(len*cvt.len_mult); cvt.len=len; if(!cvt.buf) goto fail; memcpy(cvt.buf,buf,len); if(SDL_ConvertAudio(&cvt)) goto fail; SDL_FreeWAV(buf); ws->data=cvt.buf; ws->len=cvt.len; amplify_wave_sound(ws); return; fail: if(main_options['v']) fprintf(stderr,"[Failed to convert wave audio at %ld (%ld bytes)]\n",offset,size); SDL_FreeWAV(buf); } static void load_sound_set(int is_user) { const char*v; char*nam; FILE*fp; Uint32 i,j; if(is_user) { if(main_options['z']) { fp=composite_slice(".xclass",0); if(!fp) return; } else { nam=sqlite3_mprintf("%s.xclass",basefilename); if(!nam) return; fp=fopen(nam,"r"); sqlite3_free(nam); if(!fp) return; } } else { optionquery[2]=Q_standardSounds; v=xrm_get_resource(resourcedb,optionquery,optionquery,3); if(!v) return; fp=fopen(v,"r"); if(!fp) fatal("Cannot open standard sounds file (%m)\n"); nstandardsounds=50; standardsounds=malloc(nstandardsounds*sizeof(WaveSoud)); if(!standardsounds) fatal("Allocation failed\n"); for(i=0;i<nstandardsounds;i++) standardsounds[i].data=0,standardsounds[i].len=0; } nam=malloc(256); for(;;) { for(i=0;;) { if(i==255) goto done; nam[i++]=j=fgetc(fp); if(j==EOF) goto done; if(!j) break; } i--; j=fgetc(fp)<<16; j|=fgetc(fp)<<24; j|=fgetc(fp)<<0; j|=fgetc(fp)<<8; l_offset=ftell(fp); l_size=j; if(i>4 && nam[i-4]=='.' && nam[i-3]=='W' && nam[i-1]=='V' && (nam[i-2]=='A' || nam[i-2]=='Z')) { nam[i-4]=0; if(is_user) { } else { } } fseek(fp,l_offset+l_size,SEEK_CUR); } done: fclose(fp); free(nam); } void init_sound(void) { const char*v; optionquery[1]=Q_audio; optionquery[2]=Q_rate; v=xrm_get_resource(resourcedb,optionquery,optionquery,3); if(!v) return; spec.freq=strtol(v,0,10); optionquery[2]=Q_buffer; v=xrm_get_resource(resourcedb,optionquery,optionquery,3); if(!v) return; spec.samples=strtol(v,0,10); if(!spec.freq || !spec.samples) return; fprintf(stderr,"Initializing audio...\n"); spec.channels=1; spec.format=AUDIO_S16SYS; spec.callback=audio_callback; if(SDL_InitSubSystem(SDL_INIT_AUDIO)) { fprintf(stderr,"Cannot initalize audio subsystem.\n"); return; } if(SDL_OpenAudio(&spec,0)) { fprintf(stderr,"Cannot open audio device.\n"); return; } optionquery[2]=Q_waveVolume; if(v=xrm_get_resource(resourcedb,optionquery,optionquery,3)) { needs_amplify=1; wavevolume=strtod(v,0); } optionquery[2]=Q_mmlVolume; if(v=xrm_get_resource(resourcedb,optionquery,optionquery,3)) mmlvolume=fmin(strtod(v,0)*32767.0,32767.0); if(wavevolume>0.00001) { load_sound_set(0); // Standard sounds load_sound_set(1); // User sounds } fprintf(stderr,"Done.\n"); wavesound=0; SDL_PauseAudio(0); sound_on=1; } void set_sound_effect(Value v1,Value v2) { if(!sound_on) return; if(!v2.t && !v2.u && wavesound) return; SDL_LockAudio(); wavesound=0; switch(v1.t) { case TY_SOUND: if(v1.u<nstandardsounds) { wavesound=standardsounds[v1.u].data; wavelen=standardsounds[v1.u].len; } break; case TY_USOUND: if(v1.u<nusersounds) { wavesound=usersounds[v1.u].data; wavelen=usersounds[v1.u].len; } break; case TY_STRING: case TY_LEVELSTRING: if(!mmlvolume) break; break; } SDL_UnlockAudio(); } Uint16 find_user_sound(const char*name) { int i; for(i=0;i<nusersounds;i++) if(!strcmp(name,user_sound_names[i])) return i+0x0400; return 0x03FF; }