Created
December 18, 2015 23:13
-
-
Save yne/1955fa8b7403b4ed73a6 to your computer and use it in GitHub Desktop.
Easily hackable SDL based BMS player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
http://bm98.yaneu.com/bm98/bmsformat.html | |
http://fileformats.wikia.com/wiki/Be-Music_Script | |
http://mrqqn.net/dotclear/public/beatmaker/bms-specification.txt | |
http://hitkey.nekokan.dyndns.info/cmds.htm | |
*/ | |
#include <malloc.h> | |
#include <string.h> | |
#include <fcntl.h> | |
#include <SDL/SDL.h> | |
#include "SDL/SDL_audio.h" | |
char*chan2names[]={ | |
"0","BGS","TS","BPM","BGA","??","POR","LAY","bpm","STP", | |
"10?","S","D","F","-","J","SCR","08","K","L", | |
"20?","s","d","f","_","j","scr","??","k","l" | |
}; | |
char col2lane[]={ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
0, 1, 2, 3, 4, 5, 8, 0, 6, 7, | |
0, 9,10,11,12,13,16, 0,14,15}; | |
#define _ 9 | |
char lane4k []={0,_,1,2,8,_,4,_,_,_,_,_,_,_,_,_,_};//_th lane is unused so i dumyze with it | |
char lane4ks[]={0,_,1,2,8,3,4,_,_,_,_,_,_,_,_,_,_};//sc redirected to center key | |
char lane5k []={0,_,1,2,3,4,5,7,8,_,_,_,_,_,_,_,_};//5k are centered | |
char lane5ks[]={0,_,1,2,3,4,5,7,8,_,_,_,_,_,_,_,_};//5k are centered sc keeped | |
char lane6k []={0,1,2,3,_,4,5,6,8,_,_,_,_,_,_,_,_};//separate 123 end 456 | |
char lane6ks[]={0,1,2,3,8,4,5,6,_,_,_,_,_,_,_,_,_};//center key unused as sc | |
char lanedef[]={0,1,2,3,9,4,5,6,8,9,10,11,12,13,14,15,16};//do not change lane layout | |
#undef _ | |
int lane2color[]={0, | |
0xFFFFFF,0x00FF00,0xFFFFFF,0x0000FF,0xFFFFFF,0x00FF00,0xFFFFFF,0xFF0000, | |
0xFF0000,0xFFFFFF,0x00FF00,0xFFFFFF,0x0000FF,0xFFFFFF,0x00FF00,0xFFFFFF}; | |
int lane2key[]={0, | |
SDLK_s,SDLK_d,SDLK_f,SDLK_SPACE,SDLK_j,SDLK_k,SDLK_l,SDLK_m, | |
SDLK_p,SDLK_z,SDLK_e,SDLK_r ,SDLK_n,SDLK_u,SDLK_i,SDLK_o}; | |
typedef struct{ | |
float time; | |
int sound; | |
// short status;//past,current,to come | |
}Key; | |
typedef struct{ | |
Key*keys; | |
short length; | |
short used; | |
}Lane; | |
typedef struct{ | |
SDL_AudioCVT*cvt; | |
}Sound; | |
typedef struct{ | |
int player; | |
int level; | |
int rank; | |
float bpm; | |
char genre[64]; | |
char title[64]; | |
char artist[64]; | |
char splash[64]; | |
Sound wavs[1300]; | |
Lane lanes[17]; //TODO get the file.bms size/2 | |
//context | |
int fd; | |
char tmp[512]; | |
float barlen; | |
}BMS; | |
int die(char*err){ | |
puts(err); | |
exit(1); | |
} | |
/*AUDIO*/ | |
#define NUM_SOUNDS 32 | |
struct sample { | |
unsigned char *data; | |
unsigned dpos; | |
unsigned dlen; | |
} sounds[NUM_SOUNDS]; | |
void mixaudio(void *unused, unsigned char *stream, int len){ | |
int i; | |
for(i=0; i<NUM_SOUNDS; ++i){ | |
unsigned amount = (sounds[i].dlen-sounds[i].dpos); | |
if(amount > len)amount = len; | |
SDL_MixAudio(stream, &sounds[i].data[sounds[i].dpos], amount, SDL_MIX_MAXVOLUME); | |
sounds[i].dpos += amount; | |
} | |
} | |
SDL_AudioCVT* LoadSound(char *file){ | |
SDL_AudioSpec wave; | |
unsigned char *data; | |
unsigned dlen=0; | |
if(!SDL_LoadWAV(file, &wave, &data, &dlen))return NULL; | |
SDL_AudioCVT*cvt=malloc(sizeof(SDL_AudioCVT)); | |
SDL_BuildAudioCVT(cvt,wave.format,wave.channels,wave.freq,AUDIO_S16,2,44100); | |
cvt->buf = malloc(dlen*cvt->len_mult); | |
memcpy(cvt->buf, data, dlen); | |
cvt->len = dlen; | |
SDL_ConvertAudio(cvt); | |
SDL_FreeWAV(data); | |
return cvt; | |
} | |
void PlaySound(SDL_AudioCVT *cvt){ | |
if(!cvt)return; | |
int index; | |
for(index=0;index<NUM_SOUNDS;++index) | |
if(sounds[index].dpos==sounds[index].dlen)break;//search an empty slot | |
if(index == NUM_SOUNDS)return;//full | |
//if(sounds[index].data)free(sounds[index].data);//do not free, it can be used later !! | |
SDL_LockAudio(); | |
sounds[index].data = cvt->buf; | |
sounds[index].dlen = cvt->len_cvt; | |
sounds[index].dpos = 0; | |
SDL_UnlockAudio(); | |
} | |
/*CONTROL*/ | |
int keyState[17]; | |
void keyHandler(BMS*bms,int lane){ | |
if(!bms->lanes[lane].keys)return;//unused lane key pressed | |
int curr=bms->lanes[lane].used; | |
Key*key=&bms->lanes[lane].keys[curr]; | |
int coef=2; | |
int off=(SDL_GetTicks()/10)*coef;//ms | |
PlaySound(bms->wavs[key->sound].cvt); | |
if((key->time*coef*-100+off)<200)return; | |
bms->lanes[lane].used++; | |
} | |
void keyPool(BMS*bms,unsigned char*state){ | |
SDL_PumpEvents(); | |
int k;for(k=1;k<17;++k){ | |
if(state[lane2key[k]]&&!keyState[k]){ | |
keyState[k]=1; | |
keyHandler(bms,k); | |
}else if(!state[lane2key[k]]) | |
keyState[k]=0; | |
} | |
} | |
/*BMS CORE*/ | |
int getkey(char*in){ | |
int out=0; | |
if(in[0]>='0'&&in[0]<='9')out =(in[0]-'0')*36; | |
if(in[0]>='A'&&in[0]<='Z')out =(in[0]-'A'+10)*36; | |
if(in[1]>='0'&&in[1]<='9')out+=(in[1]-'0'); | |
if(in[1]>='A'&&in[1]<='Z')out+=(in[1]-'A'+10); | |
return out; | |
} | |
int getLine(BMS*bms){ | |
int i; | |
char*line=bms->tmp; | |
for(i=0;i<sizeof(bms->tmp);i++){ | |
if(read(bms->fd,bms->tmp+i,1)<1){return -1;} | |
if(bms->tmp[i]=='\n'||bms->tmp[i]=='\r'){ | |
bms->tmp[i]=0; | |
return i; | |
} | |
} | |
} | |
void parseLine(BMS*bms){ | |
char*line=bms->tmp; | |
char*id=line+1; | |
switch(line[0]){ | |
case 0:return 0; | |
case '*':return 0; | |
case ';':return 0; | |
case '%':return 0; | |
case '#': | |
if(!strncmp("WAV" ,id,3))return bms->wavs[getkey(id+3)].cvt=LoadSound(id+6); | |
if(!strncmp("PLAYER" ,id,6))return bms->player=atoi(line+ 8); | |
if(!strncmp("PLAYLEVEL",id,9))return bms->level =atoi(line+11); | |
if(!strncmp("RANK" ,id,4))return bms->level =atoi(line+ 6); | |
if(!strncmp("BPM" ,id,3))return bms->barlen=240/(float)(bms->bpm=atof(line+ 5)); | |
if(!strncmp("TITLE" ,id,5))return strncpy(bms->title ,line+ 7,sizeof(bms->title )); | |
if(!strncmp("GENRE" ,id,5))return strncpy(bms->genre ,line+ 7,sizeof(bms->genre )); | |
if(!strncmp("ARTIST" ,id,6))return strncpy(bms->artist,line+ 8,sizeof(bms->artist)); | |
if(!strncmp("STAGEFILE",id,9))return strncpy(bms->splash,line+11,sizeof(bms->splash)); | |
if(!strncmp("TOTAL" ,id,4))return 0;//TODO:implement | |
if(!strncmp("STOP" ,id,4))return 0;//TODO:implement | |
if(!strncmp("BMP" ,id,3))return 0;//TODO:implement | |
if(!strncmp("VOLWAV" ,id,6))return 0;//TODO:implement | |
if(line[1]>='0'&&line[1]<='9'){ | |
int i,len=strlen(id+6); | |
char tmp[4]={0,0,0,0}; | |
int bar=atoi(strncpy(tmp,id,3)); | |
memset(tmp,0,4); | |
int col=atoi(strncpy(tmp,id+3,2))%30; | |
//printf("\ntab:%i key:%i (%s)",bar,col,chan2names[col<30?col:0]); | |
int lane=col2lane[col]; | |
for(i=0;i<len;i+=2){ | |
int ok=getkey(id+6+i); | |
if(!ok)continue; | |
int llen=++bms->lanes[lane].length; | |
bms->lanes[lane].keys=realloc(bms->lanes[lane].keys,llen*sizeof(Key)); | |
float time=((float)i/len)*bms->barlen+bar*bms->barlen; | |
//faire uniquement pour les bgs ?? | |
Key*curr=&bms->lanes[lane].keys[llen-1]; | |
int e;for(e=0;e<bms->lanes[lane].length;e++){//pour chaque entry | |
if(bms->lanes[lane].keys[e].time>time){//l'entry est superieur a moi? | |
int n;for(n=llen-1;n>e;--n)//shifter les entry apres moi | |
bms->lanes[lane].keys[n]=bms->lanes[lane].keys[n-1]; | |
curr=&bms->lanes[lane].keys[e];//m'ajouter | |
break; | |
} | |
} | |
curr->time=time; | |
curr->sound=ok; | |
// if(ok)printf("<%f>%i\n",curr->time,curr->sound); | |
} | |
} | |
return; | |
default:printf("unk char:%c line:%i (%s)\n",line[0],0,line);//die("unknow line type"); | |
} | |
} | |
void reorderLane(Lane*lanes){ | |
Lane tmp[17]; | |
memcpy(tmp,lanes,sizeof(tmp)); | |
int i,type=0; | |
for(i=0;i<16;i++)type|=(!!lanes[i+1].length)<<i; | |
char*layout=lanedef; | |
switch(type){ | |
case 0x000F:printf("04k+0s\n");layout=lane4k;break; | |
case 0x001F:printf("05k+0s\n");layout=lane5k;break; | |
case 0x003f:printf("06k+0s\n");layout=lane6k;break; | |
case 0x007f:printf("07k+0s\n");layout=lanedef;break; | |
case 0x008F:printf("04k+1s\n");layout=lane4ks;break; | |
case 0x009F:printf("05k+1s\n");layout=lane5ks;break; | |
case 0x00BF:printf("06k+1s\n");layout=lane6ks;break; | |
case 0x00FF:printf("07k+1s\n");layout=lanedef;break; | |
case 0xFFFF:printf("14k+2s\n");layout=lanedef;break; | |
default:printf("unk layout:%08X using 14k+2s mode\n",type); | |
} | |
for(i=0;i<16;i++){ | |
lanes[i]=tmp[layout[i]]; | |
} | |
} | |
BMS*newBMS(char*path){ | |
BMS*bms=(BMS*)malloc(sizeof(BMS)); | |
memset(bms,0,sizeof(BMS)); | |
bms->fd=open(path,O_RDONLY,0777); | |
while(getLine(bms)>=0) | |
parseLine(bms); | |
close(bms->fd); | |
reorderLane(bms->lanes); | |
return bms; | |
} | |
/*VIDEO*/ | |
SDL_Surface *image,*screen;int isFullScreen=0; | |
void draw(int n,BMS*bms){ | |
SDL_Rect src={0,0,image->w,image->h},dest={0,0,screen->w,screen->h}; | |
SDL_BlitSurface(image, &src, screen, &dest); | |
int coef=2; | |
int off=(SDL_GetTicks()/10)*coef;//ms | |
int curSprite=0; | |
int j; | |
for(j=16;j>=0;j--){//for each lane | |
int from=bms->lanes[j].used; | |
int to =bms->lanes[j].length; | |
int i; | |
for(i=from;i<to;i++){ | |
Key*key=&bms->lanes[j].keys[i]; | |
SDL_Rect rect = {10*j+100,key->time*coef*-100+off,10,2}; | |
if(rect.y<0)break;//all the other keys are hidden, so stop now. | |
if(j?rect.y>screen->h+16:rect.y>screen->h-16){ | |
PlaySound(bms->wavs[key->sound].cvt); | |
bms->lanes[j].used++; | |
}//do not draw it next time | |
SDL_FillRect(screen, &rect, lane2color[j]); | |
} | |
if(keyState[j]){ | |
SDL_Rect rect = {10*j+104,236,3,20}; | |
SDL_FillRect(screen, &rect,lane2color[j]); | |
} | |
} | |
} | |
void sdl_job(BMS*bms){ | |
SDL_AudioSpec fmt={44100,AUDIO_S16,2,0,512,0,0,mixaudio,NULL}; | |
if(SDL_OpenAudio(&fmt,NULL)<0)printf("Unable to open audio\n"); | |
SDL_PauseAudio(0); | |
if(SDL_Init(SDL_INIT_VIDEO)<0)die("Unable to init video\n"); | |
screen = SDL_SetVideoMode(480, 272, 32, /*SDL_FULLSCREEN|*/SDL_RESIZABLE|SDL_HWSURFACE); | |
SDL_ShowCursor(SDL_DISABLE); | |
SDL_Surface *temp = SDL_LoadBMP("bg.bmp"); | |
image = SDL_DisplayFormat(temp); | |
SDL_FreeSurface(temp); | |
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 0); | |
SDL_Event event; | |
unsigned char*state=SDL_GetKeyState(NULL); | |
do{ | |
keyPool(bms,state); | |
SDL_PollEvent(&event); | |
if(event.type==SDL_VIDEORESIZE)screen=SDL_SetVideoMode(event.resize.w<100?100:event.resize.w,event.resize.h<100?100:event.resize.h,32,isFullScreen|SDL_RESIZABLE | SDL_HWSURFACE); | |
if(state[SDLK_ESCAPE])event.type=SDL_QUIT; | |
if(state[SDLK_F11]){ | |
isFullScreen=isFullScreen?0:SDL_FULLSCREEN; | |
screen=SDL_SetVideoMode(screen->w,screen->h,32,isFullScreen|SDL_RESIZABLE | SDL_HWSURFACE); | |
} | |
draw(0,bms); | |
SDL_Flip(screen); | |
}while(event.type!=SDL_QUIT); | |
SDL_FreeSurface(image); | |
SDL_Quit(); | |
SDL_CloseAudio(); | |
} | |
int main(int argc,char**argv){ | |
BMS*bms=newBMS(argc>1?argv[1]:"test.bms"); | |
sdl_job(bms); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment