/* Effects.c by Michael Thorpe 2022-12-12 */ /* You can define SLOWOUT for "turning off power to phonograph" effect */ /* You can define CRACKLE for "static on the radio" effect */ /* You can define PAUSE to make spacebar pause/unpause audio */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* FIXME: This is just for ntohs, which isn't what we should really be using */ #include /* Set this to 1 to have stdin flipped to raw mode when it's a terminal */ #define RAW_STDIN 1 #define MAX_FADEOUT 32768 //#define FADEOUT_SPEED 5 /* approximately a 1.5sec fadeout time with MAX_FADEOUT=32768 and BUF_SIZE=256 and RATE=44100 */ #define FADEOUT_SPEED 6 /* approximately a 2.9sec fadeout time with MAX_FADEOUT=32768 and BUF_SIZE=256 and RATE=44100 */ int fadeout_speed=FADEOUT_SPEED; #ifdef SLOWOUT //#define SLOWOUT_TIME 172 /* approximately a 1.0sec speedout time with BUF_SIZE=256 and RATE=44100 */ //#define SLOWOUT_TIME 344 /* approximately a 2.0sec speedout time with BUF_SIZE=256 and RATE=44100 */ #define SLOWOUT_TIME 258 /* approximately a 1.5sec speedout time with BUF_SIZE=256 and RATE=44100 */ #endif #define BUFSIZE 256 /* in samples */ #define RATE 44100 struct sfx { char *name; signed short *samples; unsigned int len; /* # of samples */ char autoplay; }; struct playback { struct playback *next; struct sfx *sfx; unsigned int offset; signed int fadeout; /* if !0, multiply samples by fadeout/MAX_FADEOUT */ #ifdef SLOWOUT int slowout_acc,slowout_num,slowout_den; #endif }; static snd_pcm_t *pcm; static struct playback *going=0; static unsigned int quickmode=0; static FILE *logfile=0; static struct sfx *effects[128]; static char *cachefile=0; /* Has dir+slash+char+'\0' */ static char *cachefilecp; /* Points to the char in cachefile */ #ifdef CRACKLE static int crackle=0; /* 0=no crackle, 9=completely gone */ #endif #ifdef PAUSE static int pauseaudio=0; #endif #if RAW_STDIN #include #include struct termios orig_tp; int in_raw_mode=0; static void unrawinput() { if(tcsetattr(0,TCSANOW,&orig_tp)) { perror("tcsetattr"); exit(-1); } in_raw_mode=0; } static void unrawinput2(int unused) { unrawinput(); exit(1); } static int rawinput() { struct termios tp; if(tcgetattr(0,&tp)) { perror("tcgetattr"); return(1); } memcpy(&orig_tp,&tp,sizeof(tp)); if(atexit(unrawinput)) { perror("atexit"); return(1); } signal(SIGINT,unrawinput2); signal(SIGTERM,unrawinput2); tp.c_lflag &= ~(ECHO|ICANON); if(tcsetattr(0,TCSANOW,&tp)) { perror("tcsetattr"); return(1); } in_raw_mode=1; return(0); } #endif #define READ_SIZE 4096 static int read_sfx(FILE *f,struct sfx *sfx) { size_t cursize,l,pos; signed short *s,*s2; cursize=READ_SIZE; s=(signed short *)malloc(cursize*sizeof(signed short)); if(!s) { perror("malloc"); return(1); } pos=0; while(1) { if(cursize-possamples=s; sfx->len=pos; return(0); } static const char avconvcmd1[]="avconv -loglevel error -i "; static const char avconvcmd2[]=" -ac 1 -ar 44100 -f s16le -"; static int convert_sound(struct sfx *sfx,char trigger,const char *filename) { FILE *f; char *s; struct stat sfile,scache; struct utimbuf times; if(cachefile && isgraph(trigger) && '.' != trigger) { *cachefilecp=trigger; f=fopen(cachefile,"rb"); if(f) { if(fstat(fileno(f),&scache)) { perror("stat"); return(1); } if(stat(filename,&sfile)) { perror("stat"); return(2); } if(scache.st_mtime != sfile.st_mtime) { fclose(f); goto REDO_CACHE; } if(read_sfx(f,sfx)) return(3); if(fclose(f)) { perror("fclose"); return(4); } return(0); } } REDO_CACHE: s=(char *)malloc(sizeof(avconvcmd1)-1+strlen(filename)+sizeof(avconvcmd2)); if(!s) { perror("malloc"); return(5); } memcpy(s,avconvcmd1,sizeof(avconvcmd1)-1); strcpy(s+sizeof(avconvcmd1)-1,filename); memcpy(strrchr(s,'\0'),avconvcmd2,sizeof(avconvcmd2)); f=popen(s,"r"); free(s); if(!f) { perror("popen"); return(6); } if(read_sfx(f,sfx)) return(7); if(pclose(f)) { perror("pclose"); return(8); } if(cachefile && isgraph(trigger) && '.' != trigger) { f=fopen(cachefile,"wb"); if(f) { /* FIXME: This should handle short writes properly: */ if(sfx->len != fwrite(sfx->samples,2,sfx->len,f)) { perror("write"); return(9); } if(fclose(f)) { perror("fclose"); return(10); } times.actime=sfile.st_atime; times.modtime=sfile.st_mtime; if(utime(cachefile,×)) { perror("utime"); return(11); } } } return(0); } static struct sfx *load_sound(char trigger,const char *filename) { FILE *f; struct stat st; struct sfx *sfx; char *suffix; int i; sfx=(struct sfx *)malloc(sizeof(struct sfx)); if(!sfx) { perror("malloc"); return(0); } sfx->name=strdup(filename); if(!sfx->name) { perror("malloc"); return(0); } suffix=strrchr(filename,'.'); if(suffix) suffix++; else suffix=""; if(!strcmp("aiff",suffix) || !strcmp("m4a",suffix) || !strcmp("mp3",suffix) || !strcmp("wav",suffix)) { if('-'==filename[0]) { fputs("can't use converted filename starting with dash",stderr); return(0); } if(strchr(filename,' ')) { fputs("can't use converted filename that has a space",stderr); return(0); } if(convert_sound(sfx,trigger,filename)) return(0); fputc('.',stderr); } else { f=fopen(filename,"rb"); if(!f) { perror("open"); return(0); } if(fstat(fileno(f),&st)) { perror("stat"); return(0); } sfx->len=st.st_size>>1; /* assume raw 16-bit little-endian mono samples */ sfx->samples=(signed short *)malloc(sfx->len*sizeof(signed short)); if(!sfx->samples) { perror("malloc"); return(0); } if(1 != fread(sfx->samples,sfx->len*sizeof(signed short),1,f)) { perror("read"); return(0); } if(fclose(f)) { perror("close"); return(0); } if(!strcmp("be",suffix)) { /* mono big-endian samples */ for(i=0;ilen;i++) sfx->samples[i]=ntohs(sfx->samples[i]); } if(!strcmp("cd",suffix)) { /* stereo big-endian samples for CD */ sfx->len/=2; for(i=0;ilen;i++) sfx->samples[i]=ntohs(sfx->samples[2*i]); sfx->samples=(signed short *)realloc(sfx->samples,sfx->len*sizeof(signed short)); if(!sfx->samples) { perror("realloc"); return(0); } } } /* quickmode only plays the first and last <#> seconds of each SFX */ if(quickmode && quickmode*2len) { memmove(&sfx->samples[quickmode],&sfx->samples[sfx->len-quickmode],quickmode*sizeof(signed short)); sfx->len=quickmode*2; sfx->samples=(signed short *)realloc(sfx->samples,quickmode*2*sizeof(signed short)); if(!sfx->samples) { perror("realloc"); return(0); } } return(sfx); } /* example devname: "plughw:1,0" */ static snd_pcm_t *start_alsa(const char *devname) { snd_pcm_t *pcm_handle; snd_pcm_stream_t stream=SND_PCM_STREAM_PLAYBACK; snd_pcm_hw_params_t *hwparams; unsigned int exact_rate; snd_pcm_hw_params_alloca(&hwparams); if(!devname) devname="plughw:0,0"; if(snd_pcm_open(&pcm_handle,devname,stream,0)<0) { fprintf(stderr,"Error opening PCM device\n"); return(0); } if(snd_pcm_hw_params_any(pcm_handle,hwparams)<0) { fprintf(stderr,"snd_pcm_hw_params_any\n"); return(0); } if(snd_pcm_hw_params_set_access(pcm_handle,hwparams,SND_PCM_ACCESS_RW_INTERLEAVED)<0) { fprintf(stderr,"snd_pcm_hw_params_set_access\n"); return(0); } if(snd_pcm_hw_params_set_format(pcm_handle,hwparams,SND_PCM_FORMAT_S16_LE)<0) { fprintf(stderr,"snd_pcm_hw_params_set_format\n"); return(0); } exact_rate=RATE; if(snd_pcm_hw_params_set_rate_near(pcm_handle,hwparams,&exact_rate,0)<0) { fprintf(stderr,"snd_pcm_hw_params_set_rate_near\n"); return(0); } if(RATE != exact_rate) { fprintf(stderr,"WARNING: using %d Hz\n",exact_rate); } if(snd_pcm_hw_params_set_channels(pcm_handle,hwparams,1)<0) { fprintf(stderr,"snd_pcm_hw_params_set_channels\n"); return(0); } if(snd_pcm_hw_params_set_periods(pcm_handle,hwparams,4,0)<0) { fprintf(stderr,"snd_pcm_hw_params_set_periods\n"); return(0); } if(snd_pcm_hw_params_set_buffer_size(pcm_handle,hwparams,BUFSIZE)<0) { fprintf(stderr,"snd_pcm_hw_params_set_buffer_size\n"); return(0); } if(snd_pcm_hw_params(pcm_handle,hwparams)<0) { fprintf(stderr,"snd_pcm_hw_params\n"); return(0); } return(pcm_handle); } static void pcm_write(snd_pcm_t *pcm,signed short *buf,snd_pcm_sframes_t len) { snd_pcm_sframes_t l; while(len) { while((l=snd_pcm_writei(pcm,buf,len))<0) { fprintf(stderr,"Buffer underrun\n"); snd_pcm_prepare(pcm); } buf+=l; if(lnext) { if(pb->fadeout) { cur=pb->fadeout>>fadeout_speed; if(!cur) cur=1; pb->fadeout-=cur; if(!pb->fadeout) pb->offset=pb->sfx->len; } #ifdef SLOWOUT if(pb->slowout_den) { pb->slowout_num--; if(!pb->slowout_num) pb->offset=pb->sfx->len; } #endif } for(i=0;inext) { if(pb->offsetsfx->len) { if(pb->fadeout) cur+=pb->sfx->samples[pb->offset]*pb->fadeout/MAX_FADEOUT; #ifdef SLOWOUT else if(pb->slowout_den) cur+=pb->sfx->samples[pb->offset]*pb->slowout_num/pb->slowout_den; #endif else cur+=pb->sfx->samples[pb->offset]; #ifdef SLOWOUT if(pb->slowout_den) { pb->slowout_acc+=pb->slowout_num; if(pb->slowout_acc>=pb->slowout_den) { pb->slowout_acc-=pb->slowout_den; pb->offset++; } } else #endif pb->offset++; if(pb->offset==pb->sfx->len && pb->sfx->autoplay && effects[pb->sfx->autoplay]) { pb->offset=0; if(pb->sfx != effects[pb->sfx->autoplay]) { pb->sfx=effects[pb->sfx->autoplay]; fprintf(logfile?logfile:stdout,"Auto-playing %s\n",pb->sfx->name); } } } } if(curSHRT_MAX) cur=SHRT_MAX; buf[i]=cur; } #ifdef CRACKLE if(crackle) { #if 0 i=rand()/97%9; i*=rand()/97%9; #else i=rand()/97%72; #endif if(isfx->len<=going->offset) { pb=going; going=going->next; free(pb); } if(!sfx) return; pb=(struct playback *)malloc(sizeof(struct playback)); if(!pb) { perror("malloc"); return; } pb->next=going; pb->sfx=sfx; pb->offset=0; pb->fadeout=0; #ifdef SLOWOUT pb->slowout_acc=0; pb->slowout_num=0; pb->slowout_den=0; #endif going=pb; fprintf(logfile?logfile:stdout,"Playing %s\n",sfx->name); } static int load_effects(const char *mapname) { int i; FILE *f; char line[259]; unsigned char autoplay; double gain; char *tmp; for(i=0;i<128;i++) effects[i]=0; f=fopen(mapname,"r"); if(!f) { perror("open"); return(1); } while(fgets(line,sizeof(line),f)) { /* Ignore lines starting with # unless it's followed by \t or by >{nontab} */ if('#'==line[0] && !('\t'==line[1]) && !('>'==line[1] && '\t' != line[2])) continue; i=strlen(line); if(i==0 || '\n' != line[i-1]) goto BADLINE; line[i-1]='\0'; if(!line[0] || 128<=line[0]) { BADLINE: fprintf(stderr,"Invalid line in .map file: %s",line); return(2); } i=1; if('>'==line[i] && line[i+1] && line[i+1]<128) { autoplay=line[i+1]; i+=2; } else { autoplay=0; } gain=1.0; if('*'==line[i] && ('.'==line[i+1] || isdigit(line[i+1]))) { gain=strtod(&line[i+1],&tmp); i=tmp-line; } if('\t' != line[i++]) goto BADLINE; if(effects[line[0]]) { fprintf(stderr,"\nDuplicate entry for %c:\n\tWAS: %s\n\tNOW: %s\n",line[0],effects[line[0]]->name,line+i); return(3); } effects[line[0]]=load_sound(line[0],line+i); if(!effects[line[0]]) { fprintf(stderr,"unable to load %s\n",line+i); return(4); } effects[line[0]]->autoplay=autoplay; if(1.0 != gain) for(i=0;ilen;i++) effects[line[0]]->samples[i]=effects[line[0]]->samples[i]*gain+.5; } if(ferror(f)) { perror("read"); return(5); } if(fclose(f)) { perror("close"); return(6); } return(0); } static int open_logfile(const char *name) { logfile=fopen(name,"a"); if(!logfile) { perror("open"); return(-1); } setbuf(logfile,0); return(0); } static void silence() { struct playback *pb; unsigned int cnt=0; for(pb=going;pb;pb=pb->next) { if(pb->offsetsfx->len) { pb->offset=pb->sfx->len; cnt++; } } fprintf(logfile?logfile:stdout,"Silenced %u samples\n",cnt); } static void fadeout() { struct playback *pb; unsigned int cnt=0; for(pb=going;pb;pb=pb->next) { if(!pb->fadeout) { pb->fadeout=MAX_FADEOUT; cnt++; } } fprintf(logfile?logfile:stdout,"Fading out %u samples\n",cnt); } #ifdef SLOWOUT static void slowout() { struct playback *pb; unsigned int cnt=0; for(pb=going;pb;pb=pb->next) { if(!pb->slowout_den) { pb->slowout_acc=0; pb->slowout_num=SLOWOUT_TIME; pb->slowout_den=SLOWOUT_TIME; cnt++; } } fprintf(logfile?logfile:stdout,"Slowing out %u samples\n",cnt); } #endif static void handle_char(int c) { if(c<128 && effects[c]) start_playback(effects[c]); else if('\x08'==c || '='==c || '\x7F'==c) silence(); else if('\n'==c) ;/* otherwise ignore it since we're not in raw mode */ #ifdef PAUSE else if(' '==c) pauseaudio=!pauseaudio; #endif #ifdef CRACKLE else if('0'<=c && c<='9') { crackle=c-'0'; fprintf(logfile?logfile:stdout,"Setting crackle to %u\n",crackle); } #endif else if('+'==c) fadeout(); else if('Q'==c) exit(0); else if('\\'==c) #ifdef SLOWOUT slowout(); #else silence(); #endif else fprintf(stderr,"Unknown sample: %c (%2.2X)\n",c,(unsigned int)c); } static void select_loop() { unsigned char c; int i; fd_set r; ssize_t s; struct timeval tv; FD_ZERO(&r); while(1) { tv.tv_sec=10; tv.tv_usec=0; FD_SET(0,&r); i=select(1,&r,0,0,&tv); if(-1==i) return; if(0==i) start_playback(0); if(FD_ISSET(0,&r)) { s=read(0,&c,1); if(-1==s) { perror("stdin"); return; } if(0==s) return; /* probably lost our input */ if(1==s) handle_char(c); } } } int main(int argc,char **argv) { pthread_t thread; int argi=1; if(2] [-q] [ []]\n",argv[0]); return(2); } if(getenv("FADEOUT_SPEED")) { fadeout_speed=atoi(getenv("FADEOUT_SPEED")); if(fadeout_speed<1 || 15