Skip to content

Instantly share code, notes, and snippets.

@yne
Created December 18, 2015 23:13
Show Gist options
  • Save yne/1955fa8b7403b4ed73a6 to your computer and use it in GitHub Desktop.
Save yne/1955fa8b7403b4ed73a6 to your computer and use it in GitHub Desktop.
Easily hackable SDL based BMS player
/*
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