diff --git a/Windows/zvuk_win.cpp b/Windows/zvuk_win.cpp index ceacd50..2050cf3 100644 --- a/Windows/zvuk_win.cpp +++ b/Windows/zvuk_win.cpp @@ -1,5 +1,5 @@ #include -#include +#include extern "C" { diff --git a/libs/zvuk.c b/ZVUK/zvuk.c similarity index 100% rename from libs/zvuk.c rename to ZVUK/zvuk.c diff --git a/libs/zvuk.h b/ZVUK/zvuk.h similarity index 100% rename from libs/zvuk.h rename to ZVUK/zvuk.h diff --git a/libs/zvuk_dx.cpp b/ZVUK/zvuk_dx.cpp similarity index 100% rename from libs/zvuk_dx.cpp rename to ZVUK/zvuk_dx.cpp diff --git a/libs/zvuka.asm b/ZVUK/zvuka.asm similarity index 100% rename from libs/zvuka.asm rename to ZVUK/zvuka.asm diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 2d613ea..8283f0b 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -42,4 +42,5 @@ target_link_libraries(skeldal skeldal_libs skeldal_platform skeldal_sdl + skeldal_libs ${SDL2_LIBRARIES} pthread) diff --git a/game/automap.c b/game/automap.c index f892037..d14a325 100644 --- a/game/automap.c +++ b/game/automap.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "engine1.h" #include diff --git a/game/builder.c b/game/builder.c index 2a484d3..e927bf0 100644 --- a/game/builder.c +++ b/game/builder.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "engine1.h" #include diff --git a/game/chargen.c b/game/chargen.c index 757da6b..85f6050 100644 --- a/game/chargen.c +++ b/game/chargen.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include "engine1.h" diff --git a/game/chargen2.c b/game/chargen2.c index 069bd93..aab09e4 100644 --- a/game/chargen2.c +++ b/game/chargen2.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include "engine1.h" diff --git a/game/dialogy.c b/game/dialogy.c index 98a5792..6a698eb 100644 --- a/game/dialogy.c +++ b/game/dialogy.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include "engine1.h" diff --git a/game/enemy.c b/game/enemy.c index 597976f..02dbb7c 100644 --- a/game/enemy.c +++ b/game/enemy.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include "engine1.h" #include "globals.h" #include "specproc.h" diff --git a/game/engine1.c b/game/engine1.c index 8ba1fd0..c59a670 100644 --- a/game/engine1.c +++ b/game/engine1.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "math.h" #include "globals.h" #include "engine1.h" diff --git a/game/gamesave.c b/game/gamesave.c index f8cab09..fb28d59 100644 --- a/game/gamesave.c +++ b/game/gamesave.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include "globals.h" #include "temp_storage.h" diff --git a/game/globals.h b/game/globals.h index c655cd3..43489ea 100644 --- a/game/globals.h +++ b/game/globals.h @@ -1428,12 +1428,12 @@ extern char **sound_table; void init_tracks(void); void recalc_volumes(int sector,int side); -void play_effekt(int x,int y,int xd,int yd,int side,int sided,const TMA_SOUND *p); +void play_effekt(int x,int y,int xd,int yd,int sector, int side,const TMA_SOUND *p); void create_playlist(char *playlist); const char *get_next_music_from_playlist(void); const char * end_of_song_callback(void *ctx); void purge_playlist(void); -void play_sample_at_sector(int sample,int sector1,int sector2,int track, char loop); +void play_sample_at_sector(int sample,int listener,int source,int track, char loop); void play_sample_at_channel(int sample,int channel,int vol); void stop_track(int track); char test_playing(int track); diff --git a/game/globmap.c b/game/globmap.c index 23a283d..67d79e5 100644 --- a/game/globmap.c +++ b/game/globmap.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include "engine1.h" #include diff --git a/game/interfac.c b/game/interfac.c index 60351ea..75370e4 100644 --- a/game/interfac.c +++ b/game/interfac.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/game/inv.c b/game/inv.c index 73e59ee..8f62bdd 100644 --- a/game/inv.c +++ b/game/inv.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include "engine1.h" diff --git a/game/macros.c b/game/macros.c index 84e7e7a..93de2a0 100644 --- a/game/macros.c +++ b/game/macros.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "engine1.h" #include @@ -185,7 +185,7 @@ void macro_sound(const TMA_SOUND *p,int psect,int pdir,int sect,int dir) if (sound_side_flags & SD_PRIM_FORV) up=2; if (~(p->bit16) & up) { if (psect) { - play_effekt(map_coord[sect].x,map_coord[sect].y,map_coord[psect].x,map_coord[psect].y,dir,pdir,p); + play_effekt(map_coord[sect].x,map_coord[sect].y,map_coord[psect].x,map_coord[psect].y,psect,pdir,p); } else { play_effekt(0,0,0,0,-1,-1,p); } diff --git a/game/menu.c b/game/menu.c index 9515e38..7ecfdb6 100644 --- a/game/menu.c +++ b/game/menu.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include "engine1.h" diff --git a/game/realgame.c b/game/realgame.c index c00ee72..e38d11c 100644 --- a/game/realgame.c +++ b/game/realgame.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include "engine1.h" #include @@ -185,7 +185,6 @@ int load_map(char *filename) mob_template=NULL; mob_size=0; if (f==NULL) return -1; - if (snd_devnum==DEV_DAC) stop_mixing(); do { r=load_section(f,&temp,§,&size); @@ -329,7 +328,6 @@ int load_map(char *filename) exit(0); } doNotLoadMapState=0; - if (snd_devnum==DEV_DAC) start_mixing(); return suc; } @@ -1605,45 +1603,49 @@ void step_zoom(char smer) if (cur_mode==MD_GAME) recalc_volumes(viewsector,viewdir); } -void turn_zoom(int smer) - { - if (running_anm) return; - if (pass_zavora) return;else pass_zavora=1; - if (!GlobEvent(MAGLOB_ONTURN,viewsector,viewdir)) return; - if (set_halucination) do_halucinace(); - norefresh=1; - hold_timer(TM_BACK_MUSIC,1); - viewdir=(viewdir+smer)&3; - render_scene(viewsector,viewdir); - hide_ms_at(387); - OutBuffer2nd(); - other_draw(); - bott_draw(0); - if (smer==1) - { - anim_sipky(H_SIPKY_SV,1); - anim_sipky(0,255); - turn_left(); - } - else - { - anim_sipky(H_SIPKY_SZ,1); - anim_sipky(0,255); - turn_right(); - } - chod_s_postavama(0); - if (battle || (game_extras & EX_ALWAYS_MINIMAP)) draw_medium_map(); - update_mysky(); - ukaz_mysku(); - showview(0,0,0,0); - recalc_volumes(viewsector,viewdir); - if (!battle) calc_game(); - norefresh=0; - cancel_render=1; - hold_timer(TM_BACK_MUSIC,0); - mix_back_sound(0); - pass_zavora=0; - } +void turn_zoom(int smer) { + if (running_anm) + return; + if (pass_zavora) + return; + else + pass_zavora = 1; + if (!GlobEvent(MAGLOB_ONTURN, viewsector, viewdir)) + return; + if (set_halucination) + do_halucinace(); + norefresh = 1; + hold_timer(TM_BACK_MUSIC, 1); + viewdir = (viewdir + smer) & 3; + recalc_volumes(viewsector, viewdir); + render_scene(viewsector, viewdir); + hide_ms_at(387); + OutBuffer2nd(); + other_draw(); + bott_draw(0); + if (smer == 1) { + anim_sipky(H_SIPKY_SV, 1); + anim_sipky(0, 255); + turn_left(); + } else { + anim_sipky(H_SIPKY_SZ, 1); + anim_sipky(0, 255); + turn_right(); + } + chod_s_postavama(0); + if (battle || (game_extras & EX_ALWAYS_MINIMAP)) + draw_medium_map(); + update_mysky(); + ukaz_mysku(); + showview(0, 0, 0, 0); + if (!battle) + calc_game(); + norefresh = 0; + cancel_render = 1; + hold_timer(TM_BACK_MUSIC, 0); + mix_back_sound(0); + pass_zavora = 0; +} int check_path(word **path,word tosect) { diff --git a/game/setup.c b/game/setup.c index 5a6658e..cc99a40 100644 --- a/game/setup.c +++ b/game/setup.c @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "engine1.h" #include diff --git a/game/skeldal.c b/game/skeldal.c index cb6d3d4..967b70b 100644 --- a/game/skeldal.c +++ b/game/skeldal.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -87,8 +87,6 @@ THUMAN postavy[POCET_POSTAV],postavy_save[POCET_POSTAV]; void (*unwire_proc)(void); void (*wire_proc)(void); char cur_mode,battle_mode; -static int init_music_vol=127; -static int init_gfx_vol=255; static char titles_on=0; const void *pcx_fade_decomp(const void *p, int32_t *s); @@ -498,25 +496,6 @@ void set_font(int font,int c1,...) memcpy(f_default,charcolors,sizeof(charcolors)); } -void music_init(void) - { -// char *path; -/* if (sound_detection) - { - - if (sound_detect(&snd_devnum,&snd_parm1,&snd_parm2,&snd_parm3)) snd_devnum=DEV_NOSOUND; - }*/ - SEND_LOG("(SOUND) SOUND_SET Setting Sound: Device '%s' Port: %3X",device_name(snd_devnum),snd_parm1); - SEND_LOG("(SOUND) SOUND_SET Setting Sound: IRQ: %X DMA: %X",snd_parm2,snd_parm3); - set_mixing_device(snd_devnum,snd_mixing,snd_parm1,snd_parm2,snd_parm3); - - start_mixing(); - set_snd_effect(SND_GFX,init_gfx_vol); - set_snd_effect(SND_MUSIC,init_music_vol); -// path=plugins_path; - - - } void clrscr(void) { @@ -984,7 +963,8 @@ void init_skeldal(const INI_CONFIG *cfg) add_game_window(); - music_init(); + game_sound_init_device(ini_section_open(cfg, "audio")); + start_mixing(); if ((verr=init_mysky())!=0) { @@ -1267,7 +1247,7 @@ void play_anim(int anim_num) TSTR_LIST titl=NULL; const char *s = build_pathname(2,gpathtable[SR_VIDEO], texty[anim_num]); s = local_strdup(s); - if (snd_devnum==DEV_NOSOUND || titles_on) + if (titles_on) { concat(t,s," "); z=strrchr(t,'.'); @@ -1344,8 +1324,8 @@ static void game_big_circle(char enforced) {viewdir=i;break;} } } - for(r=0;r #include -#include +#include #include #include #include "globals.h" @@ -29,13 +29,27 @@ typedef struct snd_info { const TMA_SOUND *data; //4 short xpos,ypos,side; //10 - word volume,block; //14 + word volume,sample_block; //14 + word sector; }SND_INFO; static short chan_state[CHANNELS]; static short track_state[TRACKS]; short sample_volume=255; +typedef struct sound_side_map_dir_t { + uint32_t distance; + int32_t visit_counter; +} TSOUND_SIDE_MAP_DIR; + +typedef struct sound_side_map_t { + TSOUND_SIDE_MAP_DIR parts[4]; +} TSOUND_SIDE_MAP; + +static TSOUND_SIDE_MAP *current_sound_sector_map = NULL; +static size_t current_sound_sector_map_size = 0; +static int32_t current_sound_sector_map_counter = 0; + //static struct t_wave wav_last_head; //static int wav_last_size; static int mute_task=-1; @@ -140,7 +154,7 @@ void release_channel(int channel) if (i==-1) return; mute_channel(channel); { - aunlock(playings[channel].block); + aunlock(playings[channel].sample_block); chan_state[channel]=-1; track_state[i]=-1; } @@ -158,7 +172,62 @@ int calc_volume(int *x,int *y,int side) return ds; } -int calcul_volume(int chan,int x,int y,int side,int volume) +int set_channel_volume_from_sector(int channel, + int sound_source_sector, + int sound_source_side, + int listener_sector, + int listener_direction, + int volume) { + const int maxvolume = 32768; + volume = 255*volume/100; + int left = maxvolume; + int right = maxvolume; + if (sound_source_sector > mapsize) return 0; + if (sound_source_sector <0 || sound_source_sector == listener_sector) { + if (sound_source_side >= 0) { + int ddf = (sound_source_side + 4 - listener_direction) & 3; + switch (ddf) { + default: + case 0: + case 2: left = right = maxvolume;break; + case 1: left = maxvolume/4; right = maxvolume;break; + case 3: left = maxvolume; right = maxvolume/4;break; + } + } + } else { + + const TSOUND_SIDE_MAP *p = current_sound_sector_map+sound_source_sector; + left = 0; + right = 0; + for (int i = 0; i < 4; ++i) { + if (p->parts[i].visit_counter == current_sound_sector_map_counter) { + int dist = p->parts[i].distance; + if (!dist) return 1; //error + int inc = maxvolume/(dist*dist); + switch(i) { + default: + case 2: + case 0: left += inc; right += inc; break; + case 1: right += inc;break; + case 3: left += inc;break; + + } + } + } + + } + left = (left * volume) >> 8; + right = (right * volume) >> 8; + if (left > maxvolume) left = maxvolume; + if (right > maxvolume) right = maxvolume; + if (left == 0 && right == 0) return 0; + + set_channel_volume(channel,left,right); + return 1; + +} + +/*int calcul_volume(int chan,int x,int y,int side,int volume) { int lv,rv; int ds,bal,i; @@ -195,7 +264,7 @@ int calcul_volume(int chan,int x,int y,int side,int volume) set_channel_volume(chan,lv,rv); return 0; } - +*/ const void *wav_load(const void *p, int32_t *s) { const char *sr; @@ -220,7 +289,7 @@ const void *wav_load(const void *p, int32_t *s) return tgr; } -void play_effekt(int x,int y,int xd,int yd,int side,int sided,const TMA_SOUND *p) +void play_effekt(int x,int y,int xd,int yd,int sector,int side,const TMA_SOUND *p) { int chan; int blockid; @@ -228,14 +297,14 @@ void play_effekt(int x,int y,int xd,int yd,int side,int sided,const TMA_SOUND *p const char *s; if (!sound_enabled) return; - side; chan=find_free_channel(p->soundid); release_channel(chan); track=&tracks[p->soundid]; track->data=p; track->xpos=xd; track->ypos=yd; - track->side=sided; + track->side=side; + track->sector = sector; track_state[p->soundid]=-1; if (p->bit16 & 0x8) { @@ -244,7 +313,8 @@ void play_effekt(int x,int y,int xd,int yd,int side,int sided,const TMA_SOUND *p else set_channel_volume(chan,vol,rnd(vol)); } else { - if (calcul_volume(chan,x-xd,y-yd,/*side-*/sided,p->volume)) return; + if (!set_channel_volume_from_sector(chan, sector, side, viewsector, viewdir, p->volume)) + return; } blockid = find_handle(p->filename, wav_load); @@ -260,12 +330,75 @@ void play_effekt(int x,int y,int xd,int yd,int side,int sided,const TMA_SOUND *p playings[chan].data=p; playings[chan].xpos=xd; playings[chan].ypos=yd; - playings[chan].side=sided; + playings[chan].sector = sector; + playings[chan].side=side; playings[chan].volume=p->volume; - playings[chan].block=blockid; + playings[chan].sample_block=blockid; chan_state[chan]=p->soundid; track_state[p->soundid]=chan; } + +typedef struct _sound_map_queue_t { + int from_sector; + int to_sector; +} TSOUND_MAP_QUEUE; + +static void build_dungeon_sound_map_in_dir(int sector, int side, int position, int32_t counter) { + if (map_sectors[sector].step_next[side] == 0 + || !(map_sides[sector * 4 +side].flags & SD_TRANSPARENT)) return; + + TSOUND_MAP_QUEUE queue[128]; + int qbeg = 0; + int qend = 0; + queue[qend].from_sector = sector; + queue[qend].to_sector = map_sectors[sector].step_next[side]; + ++qend; + while (qbeg != qend) { + const TSOUND_MAP_QUEUE *item = queue+(qbeg % countof(queue)); + int cursect = item->to_sector; + int fromsect = item->from_sector; + ++qbeg; + if (current_sound_sector_map[cursect].parts[position].visit_counter != counter) { + current_sound_sector_map[cursect].parts[position].visit_counter = counter; + int d = current_sound_sector_map[cursect].parts[position].distance = + current_sound_sector_map[fromsect].parts[position].distance + 1; + if (d<32) { + for (int i = 0; i < 4; ++i) { + int nx = map_sectors[cursect].step_next[i]; + if (nx && (map_sides[cursect * 4 +i].flags & SD_TRANSPARENT)) { + if (current_sound_sector_map[nx].parts[position].visit_counter != counter) { + TSOUND_MAP_QUEUE *t = queue+(qend % countof(queue)); + ++qend; + t->from_sector = cursect; + t->to_sector = nx; + } + } + } + } + } + } + +} + +static void build_dungeon_sound_map(int sector, int side) { + if (mapsize != (int)current_sound_sector_map_size) { + free(current_sound_sector_map); + current_sound_sector_map = NewArr(TSOUND_SIDE_MAP, mapsize); + current_sound_sector_map_size = mapsize; + } + int32_t counter = ++current_sound_sector_map_counter; + if (sector > mapsize) return; + for (int i = 0; i < 4; ++i) { + current_sound_sector_map[sector].parts[i].distance = 0; + current_sound_sector_map[sector].parts[i].visit_counter = counter; + } + build_dungeon_sound_map_in_dir(sector, side, 0, counter); + build_dungeon_sound_map_in_dir(sector, (side+1)&3, 1, counter); + build_dungeon_sound_map_in_dir(sector, (side+2)&3, 2, counter); + build_dungeon_sound_map_in_dir(sector, (side+3)&3, 3, counter); +} + + void recalc_volumes(int sector,int side) { int i; @@ -275,16 +408,29 @@ void recalc_volumes(int sector,int side) side; SEND_LOG("(SOUND) %s","Recalculating volumes"); + build_dungeon_sound_map(sector, side); newx=map_coord[sector].x; newy=map_coord[sector].y; // layer=map_coord[sector].layer; - for(i=0;i=0 && playings[i].side>=0) { - calcul_volume(i,newx-playings[i].xpos,newy-playings[i].ypos,/*side-*/playings[i].side,playings[i].volume); + set_channel_volume_from_sector( + i, + playings[i].sector, + playings[i].side, + sector, + side, + volume); + //calcul_volume(i,newx-playings[i].xpos,newy-playings[i].ypos,/*side-*/playings[i].side,playings[i].volume); if (!get_channel_state(i)) release_channel(i); } - else calcul_volume(i,0,0,-1,playings[i].volume); + else { + int v = SND_EFF_MAXVOL*volume / 100; + set_channel_volume(i, v, v); + } + } for(i=1;i0) - if (have_loop(tracks[i].data))play_effekt(newx,newy,tracks[i].xpos,tracks[i].ypos,side,tracks[i].side,tracks[i].data); + if (have_loop(tracks[i].data)) + play_effekt(newx,newy,tracks[i].xpos,tracks[i].ypos,tracks[i].sector,tracks[i].side,tracks[i].data); } } mute_task=-1; @@ -360,7 +507,7 @@ const char *get_next_music_from_playlist() if (!remain_play) for(i=0;cur_playlist[i]!=NULL;remain_play++,i++) cur_playlist[i][0]=32; if (play_list_mode==PL_RANDOM) - step=rand()*(playlist_size-1)/32768+1; + step=rnd(playlist_size)+1; else step=1; i=playing_track; @@ -387,24 +534,22 @@ void purge_playlist() cur_playlist=NULL; } -void play_sample_at_sector(int sample,int sector1,int sector2,int track, char loop) +void play_sample_at_sector(int sample,int listener,int source,int track, char loop) { - int x,y,xd,yd,chan; + int xd,yd,chan; const char *s; struct t_wave *p; int siz; int oldtrack; if (!sound_enabled) return; - if (map_coord[sector1].layer!=map_coord[sector2].layer) return; - x=map_coord[sector1].x; - y=map_coord[sector1].y; - xd=map_coord[sector2].x; - yd=map_coord[sector2].y; + if (map_coord[listener].layer!=map_coord[source].layer) return; + xd=map_coord[source].x; + yd=map_coord[source].y; chan=find_free_channel(track); oldtrack=track_state[track]; if (!track || oldtrack==-1) release_channel(chan); - if (calcul_volume(chan,x-xd,y-yd,viewdir,100)) return; + if (!set_channel_volume_from_sector(chan, source, -1, listener, -1, 100)) return; if (!track || oldtrack==-1) { alock(sample); @@ -419,7 +564,8 @@ void play_sample_at_sector(int sample,int sector1,int sector2,int track, char lo playings[chan].ypos=yd; playings[chan].side=viewdir; playings[chan].volume=100; - playings[chan].block=sample; + playings[chan].sample_block=sample; + playings[chan].sector=source; chan_state[chan]=track; track_state[track]=chan; } @@ -519,7 +665,8 @@ char test_playing(int track) return track_state[track]!=-1; } -static int flute_canal=30; +static int flute_channel=30; +static int flute_channel_offset = 0; void start_play_flute(char note) { @@ -534,8 +681,9 @@ void start_play_flute(char note) q=ablock(H_FLETNA); w=q;w+=sizeof(struct t_wave)+4; vol*=SND_EFF_MAXVOL/100; - set_channel_volume(flute_canal,vol,vol); - play_sample(flute_canal,w,0x1665,0xADE,(int)(realfrq+0.5),1); + int ch = flute_channel+flute_channel_offset; + set_channel_volume(ch,vol,vol); + play_sample(ch,w,0x1665,0xADE,(int)(realfrq+0.5),1); } else { @@ -552,8 +700,9 @@ void stop_play_flute() { q=ablock(H_FLETNA); w=q;w+=sizeof(struct t_wave); - chan_break_ext(flute_canal,w+4,*(int *)w); - flute_canal^=1; + int ch = flute_channel+flute_channel_offset; + chan_break_ext(ch,w+4,*(int *)w); + flute_channel_offset = (flute_channel_offset+1) & 7; } else { diff --git a/game/souboje.c b/game/souboje.c index bf31f3a..e7936bc 100644 --- a/game/souboje.c +++ b/game/souboje.c @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include diff --git a/game/wizard.c b/game/wizard.c index b126279..a2ba00c 100644 --- a/game/wizard.c +++ b/game/wizard.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index e939471..903e7af 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -13,7 +13,8 @@ SET(files basicobj.c pcx.c strlite.c wav_mem.c - strlists.c + strlists.c + music.cpp swaper.c ) add_library(skeldal_libs ${files}) diff --git a/libs/mgfplay.c b/libs/mgfplay.c index e8d6107..81b66ea 100644 --- a/libs/mgfplay.c +++ b/libs/mgfplay.c @@ -69,7 +69,7 @@ #include #include -#include +#include //#include //#include diff --git a/libs/mgifmapmem.c b/libs/mgifmapmem.c index bba6080..fdc67ca 100644 --- a/libs/mgifmapmem.c +++ b/libs/mgifmapmem.c @@ -5,7 +5,7 @@ #include "memman.h" #include "mem.h" #include "mgifmem.h" -#include +#include static MGIF_HEADER_T *mgif_header; diff --git a/libs/music.cpp b/libs/music.cpp new file mode 100644 index 0000000..dc6024d --- /dev/null +++ b/libs/music.cpp @@ -0,0 +1,161 @@ +#include "music.h" +#include + +#include +#include +#include +#include +#include +#include +#include + + + +class MUSStreamParser: public IMusicStream { +public: + + MUSStreamParser(const char *name) { + data = map_file_to_memory(file_icase_find(name), &sz); + if (data) { + _loaded = true; + iter = reinterpret_cast(data); + chans = read_short(); + freq = read_int(); + iter += 4; + blocks = read_int(); + iter += 8; + std::memcpy(btable,iter,sizeof(btable)); + iter += sizeof(btable); + btable[0] = -31767; + } + } + + ~MUSStreamParser() { + if (data) unmap_file(data, sz); + } + MUSStreamParser(const MUSStreamParser &) = delete; + MUSStreamParser& operator=(const MUSStreamParser &) = delete; + + bool is_loaded() const {return _loaded;} + + + virtual TMUSIC_STREAM_INFO get_info() const override { + TMUSIC_STREAM_INFO r; + r.format = 2; + r.channels = chans; + r.freq = freq; + return r; + } + + virtual std::string_view read() override { + if (unprocessed_buffer.empty()) { + if (!process_next()) return {}; + } + return std::exchange(unprocessed_buffer, {}); + } + virtual void put_back(std::string_view data) override { + unprocessed_buffer = data; + } + +protected: + bool _loaded = false; + void *data; + std::size_t sz; + + std::int16_t chans; + std::int32_t freq; + std::int32_t blocks; + short btable[256]; + const uint8_t *iter; + std::vector outbuff; + std::string_view unprocessed_buffer; + + int32_t read_int() { + int32_t r = iter[0] | (iter[1] << 8) | (iter[2]<<16) | (iter[3] << 24); + iter+=4; + return r; + } + int16_t read_short() { + int16_t r = iter[0] | (iter[1] << 8); + iter+=2; + return r; + } + + bool process_next() { + if (blocks == 0) { + return false; + } + --blocks; + std::int32_t p = read_int(); + read_int(); + outbuff.clear(); + + short accum[2]={0,0}; + int c = 0; + for (int i = 0; i < p; ++i) { + uint8_t p = *iter++; + short val=accum[c]+btable[p]; + accum[c]=val; + /* + if (p==0) //pridano jako provizorni reseni pro korekci chyby komprimacniho programu + { + val-=31767; + } + */ + c = (c + 1) % chans; + outbuff.push_back(val); + } + unprocessed_buffer = {reinterpret_cast(outbuff.data()), outbuff.size()*2}; + return true; + } + + +}; + + +void music_close(TMUSIC_STREAM *stream) { + if (stream) { + IMusicStream *s = static_cast(stream); + delete s; + } +} + +TMUSIC_STREAM *music_open(const char *filename) { + MUSStreamParser *player = new MUSStreamParser(filename); + if (player->is_loaded()) return player; + delete player; + return NULL; +} + +TMUSIC_STREAM_CHUNK music_read(TMUSIC_STREAM *stream) { + TMUSIC_STREAM_CHUNK r = {NULL,0}; + if (stream) { + IMusicStream *s = static_cast(stream); + auto data = s->read(); + r.ptr = data.data(); + r.sz = data.size(); + } + return r; +} + +char music_is_eof(const TMUSIC_STREAM_CHUNK *chunk) { + return chunk->sz == 0; +} + +void music_put_back_chunk(TMUSIC_STREAM *stream, const TMUSIC_STREAM_CHUNK *chunk){ + if (stream) { + IMusicStream *s = static_cast(stream); + s->put_back({reinterpret_cast(chunk->ptr), chunk->sz}); + } +} + +TMUSIC_STREAM_INFO music_get_info(const TMUSIC_STREAM *stream) { + if (stream) { + const IMusicStream *s = static_cast(stream); + return s->get_info(); + } else { + TMUSIC_STREAM_INFO r = {}; + return r; + } +} + diff --git a/libs/music.h b/libs/music.h new file mode 100644 index 0000000..56f62de --- /dev/null +++ b/libs/music.h @@ -0,0 +1,51 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct music_stream_t {} TMUSIC_STREAM; + +typedef struct music_stream_info_t { + int freq; + int channels; + int format; //1 - uint8_t. 2 - int16_t +} TMUSIC_STREAM_INFO; + + +typedef struct music_stream_chunk_t { + const void *ptr; + size_t sz; +} TMUSIC_STREAM_CHUNK; + + + +///open music stream +TMUSIC_STREAM *music_open(const char *filename); +///retrieve information +TMUSIC_STREAM_INFO music_get_info(const TMUSIC_STREAM *stream); + +TMUSIC_STREAM_CHUNK music_read(TMUSIC_STREAM *stream); + +char music_is_eof(const TMUSIC_STREAM_CHUNK *chunk); + +void music_put_back_chunk(TMUSIC_STREAM *stream, const TMUSIC_STREAM_CHUNK *chunk); + +///close music stream +void music_close(TMUSIC_STREAM *stream); + + +#ifdef __cplusplus +} + +#include +class IMusicStream: public TMUSIC_STREAM { +public: + virtual ~IMusicStream() = default; + virtual TMUSIC_STREAM_INFO get_info() const = 0; + virtual std::string_view read() = 0; + virtual void put_back(std::string_view data) = 0; +}; + +#endif diff --git a/platform/platform.h b/platform/platform.h index c3f1975..96aa404 100644 --- a/platform/platform.h +++ b/platform/platform.h @@ -75,6 +75,7 @@ int stricmp(const char *a, const char *b); void strupr(char *c); const char * int2ascii(int i, char *c, int radix); +int get_timer_value(void); uint32_t get_game_tick_count(void); void sleep_ms(uint32_t); diff --git a/platform/sdl/BGraph2.cpp b/platform/sdl/BGraph2.cpp index db43325..3d101ab 100644 --- a/platform/sdl/BGraph2.cpp +++ b/platform/sdl/BGraph2.cpp @@ -12,7 +12,7 @@ static uint16_t screen_pitch = 640; char game_display_init(const INI_CONFIG_SECTION *display_section, const char *title) { - SDLContext::Config cfg = {}; + SDLContext::VideoConfig cfg = {}; const char *aspect_str; aspect_str = ini_get_string(display_section, "aspect_ratio", "4:3"); diff --git a/platform/sdl/CMakeLists.txt b/platform/sdl/CMakeLists.txt index 82b28fd..da07b3a 100644 --- a/platform/sdl/CMakeLists.txt +++ b/platform/sdl/CMakeLists.txt @@ -1,4 +1,8 @@ add_subdirectory(tests) -add_library(skeldal_sdl sdl_context.cpp BGraph2.cpp input.cpp sound.cpp) +add_library(skeldal_sdl + sdl_context.cpp + BGraph2.cpp + input.cpp + sound.cpp) set_property(TARGET skeldal_sdl PROPERTY CXX_STANDARD 20) diff --git a/platform/sdl/sdl_context.cpp b/platform/sdl/sdl_context.cpp index 196b76e..0888089 100644 --- a/platform/sdl/sdl_context.cpp +++ b/platform/sdl/sdl_context.cpp @@ -24,6 +24,11 @@ void SDLContext::SDL_Deleter::operator ()(SDL_Texture* texture) { SDL_DestroyTexture(texture); } +void SDLContext::SDL_Audio_Deleter::operator()(void *x) { + SDL_AudioDeviceID id = reinterpret_cast(x); + SDL_CloseAudioDevice(id); +} + struct SDL_INIT_Context { SDL_INIT_Context() { @@ -80,7 +85,7 @@ void SDLContext::generateCRTTexture(SDL_Renderer* renderer, SDL_Texture** textur -void SDLContext::init_video(const Config &config, const char *title) { +void SDLContext::init_video(const VideoConfig &config, const char *title) { char buff[256]; static Uint32 update_request_event = SDL_RegisterEvents(1); _update_request_event = update_request_event; @@ -502,3 +507,32 @@ SDL_Rect SDLContext::to_window_rect(const SDL_Rect &winrc, const SDL_Rect &sourc void SDLContext::set_quit_callback(std::function fn) { _quit_callback = std::move(fn); } + +SDLContext::AudioInfo SDLContext::init_audio(const AudioConfig &config, SDL_AudioCallback cb, void *cb_ctx) { + char buff[256]; + _audio.reset(); + SDL_AudioSpec aspec = {}; + aspec.callback = cb; + aspec.userdata = cb_ctx; + aspec.channels = 2; + aspec.format = AUDIO_F32; + aspec.freq = 44100; + aspec.samples = 1024; + SDL_AudioSpec obtaied = {}; + auto id = SDL_OpenAudioDevice(config.audioDevice, 0, &aspec, &obtaied, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + if (id < 1) { + snprintf(buff, sizeof(buff), "SDL Error create audio: %s\n", SDL_GetError()); + throw std::runtime_error(buff); + } + _audio.reset(reinterpret_cast(id)); + + return {obtaied.freq}; +} +void SDLContext::pause_audio(bool pause) { + SDL_AudioDeviceID id = reinterpret_cast(_audio.get()); + SDL_PauseAudioDevice(id, pause?1:0); +} + +void SDLContext::close_audio() { + _audio.reset(); +} diff --git a/platform/sdl/sdl_context.h b/platform/sdl/sdl_context.h index 8a433da..70fd5d2 100644 --- a/platform/sdl/sdl_context.h +++ b/platform/sdl/sdl_context.h @@ -15,7 +15,7 @@ public: SDLContext(); - struct Config { + struct VideoConfig { int window_width; int window_height; bool crt_filter; @@ -26,11 +26,22 @@ public: int aspect_y; }; + struct AudioConfig { + const char *audioDevice; + }; - void init_video(const Config &config, const char *title); + struct AudioInfo { + int freq; + }; + + void init_video(const VideoConfig &config, const char *title); void close_video(); + AudioInfo init_audio(const AudioConfig &config, SDL_AudioCallback cb, void *cb_ctx); + void pause_audio(bool pause); + void close_audio(); + void present_rect(uint16_t *pixels, unsigned int pitch, unsigned int x, unsigned int y, unsigned int xs,unsigned ys); void swap_render_buffers(); @@ -99,6 +110,12 @@ protected: slide_transition }; + struct SDL_Audio_Deleter { + void operator()(void *x); + }; + + + MS_EVENT ms_event; mutable std::mutex _mx; @@ -112,6 +129,7 @@ protected: std::unique_ptr _texture; std::unique_ptr _texture2; std::unique_ptr _crt_effect; + std::unique_ptr _audio; SDL_Texture *_visible_texture; SDL_Texture *_hidden_texture; @@ -125,6 +143,7 @@ protected: std::atomic _quit_requested = false; + std::vector _display_update_queue; using QueueIter = const char *; std::queue _keyboard_queue; diff --git a/platform/sdl/sound.cpp b/platform/sdl/sound.cpp index 8667ffa..9b65ba8 100644 --- a/platform/sdl/sound.cpp +++ b/platform/sdl/sound.cpp @@ -1,36 +1,250 @@ #include "../platform.h" -#include +#include "../sound.h" +#include +#include "global_context.h" + +#include "sound_mixer.h" + +#include +#include #include -void set_mixing_device(int mix_dev,int mix_freq,...) { + +#define countof(array) (sizeof(array)/sizeof(array[0])) +static void SDLCALL mixing_callback(void *userdata, Uint8 * stream, int len); +static SoundMixer<2> sound_mixer; + +static float master_volume = 1.0; +static float sound_effect_volume = 1.0; +static float music_volume = 1.0; +static float base_freq; +bool swap_channels = false; +static void empty_deleter(const void *) {} + +constexpr int BACK_BUFF_SIZE = 0x40000; +static uint8_t *music_buffer = {}; +std::shared_ptr music_source; +static std::unique_ptr current_music = NULL; + +struct VolumePreset { + float left = 1.0; + float right = 1.0; +}; + +static std::unordered_map channel_volume_presset; + +void game_sound_init_device(const INI_CONFIG_SECTION *audio_section) { + + const char *dev = ini_get_string(audio_section, "device", ""); + if (!dev[0]) dev = nullptr; + + SDLContext::AudioConfig cfg; + cfg.audioDevice = dev; + + auto r = get_sdl_global_context().init_audio(cfg, mixing_callback, nullptr); + base_freq = r.freq; + + } + +static void SDLCALL mixing_callback(void *userdata, Uint8 * stream, int len) { + float *s = reinterpret_cast(stream); + int samples = len/8; + std::fill(s, s+2*samples, 0.0f); + sound_mixer.mix_to_buffer(s, samples); +} + char start_mixing() { - return 0; + get_sdl_global_context().pause_audio(false); + return 0; } void stop_mixing() { + get_sdl_global_context().pause_audio(true); } + + +static std::array make_channel_volume(float left, float right) { + if (swap_channels) std::swap(left, right); + return { + left * master_volume * sound_effect_volume, + right * master_volume * sound_effect_volume, + }; +} + void play_sample(int channel,const void *sample,int32_t size,int32_t lstart,int32_t sfreq,int type) { + float speed = sfreq/base_freq; + int step = type==1?1:2; + auto samples = size/step; + auto start = lstart/step; + WaveSource::Format fmt = step==1?WaveSource::Format::uint8:WaveSource::Format::int16; + auto src = std::make_shared(WaveSource::WavePtr(sample,&empty_deleter),fmt,speed,samples,start); + + VolumePreset &vp = channel_volume_presset[channel]; + + + + + sound_mixer.set_track(channel, WaveMixer<2>(src, make_channel_volume(vp.left, vp.right), 1.0)); } void set_channel_volume(int channel,int left,int right) { - + float l = left / 32768.0; + float r = right / 32768.0; + VolumePreset &vp = channel_volume_presset[channel]; + vp.left = l; + vp.right = r; + sound_mixer.visit_track(channel, [&](WaveMixer<2> &mx){ + mx.set_channel_volumes_fade(make_channel_volume(vp.left, vp.right), {2e-4,2e-4}); + }); } -void set_end_of_song_callback(const char * (*cb)(void *), void *ctx) { +static size_t music_buffer_write_pos = 0; + + +size_t copy_to_music_buffer(const void *data, size_t data_size) { + size_t rdpos = music_source->get_output_position_bytes(); + if (static_cast >(rdpos) < 0) return 0; + size_t l = music_source->length_bytes(); + size_t space = rdpos < music_buffer_write_pos?l - music_buffer_write_pos: rdpos - music_buffer_write_pos; + if (space == 0) return 0; + data_size = std::min(space,data_size); + std::memcpy(music_buffer+music_buffer_write_pos, data, data_size); + music_buffer_write_pos += data_size; + if (music_buffer_write_pos >= l) { + music_buffer_write_pos = 0; + } + return data_size; +} + + +static const char * (*end_of_song_callback)(void *ctx) = NULL; +static void *end_of_song_callback_ctx = NULL; + +void set_end_of_song_callback(const char * (*cb)(void *), void *ctx) { + end_of_song_callback = cb; + end_of_song_callback_ctx = ctx; } void fade_music() { + std::lock_guard _(music_source->get_lock()); + size_t rdpos = music_source->get_output_position_samples(); + WaveSource::StereoInt16 *rdptr = reinterpret_cast(music_buffer); + size_t wrsamples = music_buffer_write_pos / sizeof(WaveSource::StereoInt16); + size_t l =music_source->length(); + float_t size = (rdpos < wrsamples?0:l) + wrsamples - rdpos; + for (size_t i = 0; i < size; ++i) { + float fact = (size - i)/size; + rdptr[rdpos].left = rdptr[rdpos].left * fact; + rdptr[rdpos].right = rdptr[rdpos].right * fact; + ++rdpos; + if (rdpos >=l) rdpos = 0; + } +} + +constexpr int music_track_id_base = 0x1000; + +static void music_buffer_deleter(const void *ptr) { + uint8_t *p = reinterpret_cast(const_cast(ptr)); + delete [] p; +} + +static void stop_music() { + if (music_source) { + std::lock_guard _(music_source->get_lock()); + size_t l =music_source->length_bytes(); + size_t rdpos = music_source->get_output_position_bytes(); + size_t size = (rdpos < music_buffer_write_pos?0:l) + music_buffer_write_pos - rdpos; + music_source->stop_after_bytes(size); + } + music_source.reset(); +} + +void create_new_music_track(int freq) { + stop_music(); + static int track_id = music_track_id_base; + double basef = base_freq; + double rel_speed = freq/basef; + music_buffer = new uint8_t[BACK_BUFF_SIZE]; + std::memset(music_buffer, 0, BACK_BUFF_SIZE); + music_source = std::make_shared( + WaveSource::WavePtr(music_buffer,music_buffer_deleter), + WaveSource::Format::stereo_int16, rel_speed, + BACK_BUFF_SIZE/sizeof(WaveSource::StereoInt16),0); + + sound_mixer.set_track(track_id, WaveMixer<2>(music_source,{music_volume,music_volume},1.0)); + track_id = track_id ^ 1; + music_buffer_write_pos = BACK_BUFF_SIZE/2; +} + +static std::unique_ptr load_music_ex(const char *name) { + if (name) { + TMUSIC_STREAM *stream = music_open(name); + if (stream) { + TMUSIC_STREAM_INFO nfo = music_get_info(stream); + if (nfo.channels != 2) { + SEND_LOG("(MUSIC) Reject music - only stereo is supported"); + } else if (nfo.format != 2){ + SEND_LOG("(MUSIC) Reject music - only 16bit is supported"); + } else { + return std::unique_ptr(static_cast(stream)); + } + } + } + return {}; } -int mix_back_sound(int synchro) { - return 0; + +static void handle_end_of_song() { + current_music.reset(); + if (end_of_song_callback != NULL) { + const char *new_music = end_of_song_callback(end_of_song_callback_ctx); + current_music = load_music_ex(new_music); + if (current_music) { + auto nfo = current_music->get_info(); + create_new_music_track(nfo.freq); + mix_back_sound(0); + } + } +} + +int mix_back_sound(int _) { + + if (current_music==NULL) { + stop_music(); + } else { + auto data = current_music->read(); + while (!data.empty()) { + auto sz = copy_to_music_buffer(data.data(), data.size()); + if (sz == 0) { + current_music->put_back(data); + break; + } + data = data.substr(sz); + if (data.empty()) data = current_music->read(); + } + if (data.empty()) { + handle_end_of_song(); + return mix_back_sound(_); + } + } + return 1; + } //int open_backsound(char *filename); void change_music(const char *filename) { - + if (music_source) { + fade_music(); + stop_music(); + } + current_music = load_music_ex(filename); + if (current_music) { + auto nfo = current_music->get_info(); + create_new_music_track(nfo.freq); + mix_back_sound(0); + } } //char *device_name(int device); @@ -39,29 +253,67 @@ void change_music(const char *filename) { //void set_backsnd_freq(int freq); char get_channel_state(int channel) { - return 0; + char c = 0; + sound_mixer.visit_track(channel, [&](auto){c = 1;}); + return c; } void get_channel_volume(int channel,int *left,int *right) { - + VolumePreset &vp = channel_volume_presset[channel]; + *left = static_cast(vp.left*32768.0); + *right = static_cast(vp.right*32768.0); } void mute_channel(int channel) { - + sound_mixer.visit_track(channel, [&](WaveMixer<2> &mx){ + mx.get_source()->stop(); + }); } void chan_break_loop(int channel) { + sound_mixer.visit_track(channel, [&](WaveMixer<2> &mx){ + mx.get_source()->break_loop(); + }); } void chan_break_ext(int channel,const void *org_sample,int32_t size_sample) { + sound_mixer.visit_track(channel, [&](WaveMixer<2> &mx){ + auto src = mx.get_source(); + src->break_loop(src->length_in_samples(src->get_format(), size_sample)); + }); } -char set_snd_effect(int funct,int data) { + +static void update_music_volume(){ + float v = music_volume * master_volume; + for (int i = 0; i <2; i++) + sound_mixer.visit_track(music_track_id_base+i,[&](WaveMixer<2> &m){ + m.set_channel_volume(v, v); + }); +} + +char set_snd_effect(AUDIO_PROPERTY funct,int data) { + switch (funct) { + case SND_PING: break; + case SND_GFX: sound_effect_volume = data/256.0;break; + case SND_MUSIC: music_volume = data/128.0;update_music_volume();break; + case SND_GVOLUME: master_volume = data/256.0;update_music_volume();break; + case SND_SWAP: swap_channels = !!data;break; + default: return 0; + } + return 1; +} +char check_snd_effect(AUDIO_PROPERTY funct) { + if (funct == SND_PING|| funct == SND_GFX || funct == SND_MUSIC || funct == SND_GVOLUME) return 1; return 0; } -char check_snd_effect(int funct) { - return 0; -} -int get_snd_effect(int funct) { - return 0; +int get_snd_effect(AUDIO_PROPERTY funct) { + switch (funct) { + case SND_PING: return 1; + case SND_GFX: return static_cast(sound_effect_volume * 256.0);break; + case SND_MUSIC: return static_cast(music_volume * 128.0);break; + case SND_GVOLUME: return static_cast(master_volume * 256.0);break; + case SND_SWAP:return swap_channels?1:0; + default: return 0; + } } void *PrepareVideoSound(int mixfreq, int buffsize) { diff --git a/platform/sdl/sound.h b/platform/sdl/sound.h deleted file mode 100644 index b1f0614..0000000 --- a/platform/sdl/sound.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * sound.h - * - * Created on: 26. 1. 2025 - * Author: ondra - */ - -#ifndef PLATFORM_SDL_SOUND_H_ -#define PLATFORM_SDL_SOUND_H_ - - - - - -#endif /* PLATFORM_SDL_SOUND_H_ */ diff --git a/platform/sdl/sound_mixer.h b/platform/sdl/sound_mixer.h new file mode 100644 index 0000000..8245f6c --- /dev/null +++ b/platform/sdl/sound_mixer.h @@ -0,0 +1,104 @@ +#pragma once +#include "wave_mixer.h" + +#include +#include +#include + +template +class SoundMixer { +public: + + + void mix_to_buffer(float *buffer, std::size_t samples_len) { + std::lock_guard _(_mx); + for (std::size_t i = 0; i < _tracks.size(); ++i) { + Track &t = _tracks[i]; + bool playing = t.wave.output_mix(buffer, samples_len); + if (!playing) { + Track &b = _tracks.back(); + if (&b != &t) std::swap(b,t); + _tracks.pop_back(); + --i; + } + } + } + + template &> Fn> + bool visit_track(int trackID, Fn &&fn) { + std::lock_guard _(_mx); + Track *t = find_track_by_id(trackID); + if (t) { + fn(t->wave); + return true; + } else { + return false; + } + + } + template &> Fn> + bool visit_track(int trackID, Fn &&fn) const { + std::lock_guard _(_mx); + const Track *t = find_track_by_id(trackID); + if (t) { + fn(t->wave); + return true; + } else { + return false; + } + + } + + void set_track(int trackID, WaveMixer wave) { + std::lock_guard _(_mx); + Track *t = find_track_by_id(trackID); + if (t) t->wave = std::move(wave); + else _tracks.push_back({trackID, std::move(wave)}); + } + + struct Track { + int trackID; + WaveMixer wave; + }; + + + void set_tracks(Track *tracks, int count) { + std::lock_guard _(_mx); + for (int i = 0; i < count; ++i) { + Track *t = find_track_by_id(tracks[i].trackID); + if (t) t->wave = std::move(tracks[i].wave); + else _tracks.push_back(std::move(tracks[i])); + } + } + + +protected: + + Track *find_track_by_id(int track) { + auto iter = std::find_if(_tracks.begin(), _tracks.end(), [&](const Track &t){ + return t.trackID == track; + }); + if (iter == _tracks.end()) { + return nullptr; + } else { + return &(*iter); + } + } + + const Track *find_track_by_id(int track) const { + auto iter = std::find_if(_tracks.begin(), _tracks.end(), [&](const Track &t){ + return t.trackID == track; + }); + if (iter == _tracks.end()) { + return nullptr; + } else { + return &(*iter); + } + } + + + std::mutex _mx; + std::vector _tracks; + + +}; diff --git a/platform/sdl/wave_mixer.h b/platform/sdl/wave_mixer.h new file mode 100644 index 0000000..191b6dd --- /dev/null +++ b/platform/sdl/wave_mixer.h @@ -0,0 +1,145 @@ +#pragma once +#include "wave_source.h" + +#include + + +template +class NoCopyObject: public X { +public: + using X::X; + + NoCopyObject(const NoCopyObject &other):X() {} + NoCopyObject &operator=(const NoCopyObject &other) { + return *this; + } +}; + +template +class WaveMixer { +public: + + enum State { + playing, + done + }; + + WaveMixer(std::shared_ptr source) + :_src(std::move(source)) {} + WaveMixer(std::shared_ptr source, const std::array &volumes, float speed = 1.0) + :_src(std::move(source)) + ,_volume{volumes} + ,_speed(speed) {} + + + + static_assert(channels > 0); + + + ///output to buffer by adding values (mixing) + /** + * @param buffer sound buffer - add values here + * @param samples_count count of samples in buffer (so samples * channels for count of floats) + * @retval true still playing + * @retval false done playing + */ + bool output_mix(float *buffer, std::size_t samples_count) { + std::array vol; + float speed; + { + std::lock_guard _(_mx); + if (_state == done) { + return 0; + } + vol = _volume; + speed = _speed; + } + + bool ch = false; + + auto do_volume_fade = [this,&ch](int c){ + float d = _target_volume[c] - _volume[c]; + if (d > 0) { + d = std::min(d, _volume_change[c]); + ch = true; + } + else if (d < 0) { + d = std::max(d, -_volume_change[c]); + ch = true; + } + _volume[c] += d; + }; + + auto iter = buffer; + bool playing = _src->output([&](const auto &value){ + using T = std::decay_t; + if constexpr (std::is_same_v) { + for (int c = 0; c < channels; ++c) { + if (_volume_fade) do_volume_fade(c); + float v = value * vol[c]; + v = v + (*iter); + (*iter) = v; + ++iter; + } + } else { + //TODO: handle 1 channel audio + static_assert(std::is_same_v); + if (_volume_fade) for (int c =0; c< channels; ++c) do_volume_fade(c); + if constexpr(channels == 1) { + float v = (value.left + value.right) * 0.5; + (*iter) += v * vol[0]; + } else { + (*iter) += value.left * vol[0]; + ++iter; + (*iter) += value.right * vol[0]; + ++iter; + } + } + + }, samples_count, speed); + _volume_fade = ch; + return playing; + } + + void set_channel_volumes(const std::array &volumes) { + std::lock_guard _(_mx); + _volume = volumes; + } + + void set_channel_volumes_fade(const std::array &volumes, const std::array &change_speed) { + std::lock_guard _(_mx); + _volume_fade = true; + _target_volume = volumes; + _volume_change = change_speed; + } + + void set_channel_volume(int channel, float volume) { + std::lock_guard _(_mx); + if (channel >= 0 && channel < channels) { + _volume[channel] = volume; + } + } + + void set_speed(float speed) { + std::lock_guard _(_mx); + _speed = speed; + } + + std::shared_ptr get_source() const {return _src;} + +protected: + + + + std::shared_ptr _src; + mutable NoCopyObject _mx; + std::array _volume = {}; + std::array _target_volume = {}; + std::array _volume_change = {}; + bool _volume_fade = false; + float _speed = 1.0; + State _state = playing; + + + +}; diff --git a/platform/sdl/wave_source.h b/platform/sdl/wave_source.h new file mode 100644 index 0000000..92c1bde --- /dev/null +++ b/platform/sdl/wave_source.h @@ -0,0 +1,248 @@ +#pragma once +#include +#include +#include +#include +#include + + +class WaveSource { +public: + using WaveDeleter = void (*)(const void *); + using WavePtr = std::unique_ptr; + + struct StereoInt16 { + std::int16_t left; + std::int16_t right; + }; + + struct StereoFloat { + float left; + float right; + }; + + enum class Format { + uint8, + int8, + uint16, + int16, + stereo_int16 + }; + + constexpr static std::size_t length_in_samples(Format f, std::size_t bytes) { + if (f == Format::stereo_int16) return bytes>>2; + if (f == Format::uint16 || f == Format::int16) return bytes>>1; + else return bytes; + } + constexpr static std::size_t length_in_bytes(Format f, std::size_t samples) { + if (f == Format::stereo_int16) return samples<<2; + if (f == Format::uint16 || f == Format::int16) return samples<<1; + else return samples; + + } + Format get_format() const {return _format;} + + ///retrieve overall play time in samples of source wave + /** + * @return play time in samples - this value can be non-integer - when pointer + * can point between two samples + * + */ + double get_play_time() const { + return _position; + } + ///retrieves output position in samples. + /** useful for cycle buffer returns sample index in wave buffer which has been + * recently sent to the wave device, so it is save to overwrite it. As the + * @return output position in samples + */ + std::size_t get_output_position_samples() const { + std::lock_guard _(_mx); + return static_cast(std::round(wrap_pos(_position - 1.0))); + } + + ///retrieves output position in samples. + /** useful for cycle buffer returns byte offset in wave buffer which has been + * recently sent to the wave device, so it is save to overwrite it. As the + * @return output position in bytes + */ + std::size_t get_output_position_bytes() const { + return length_in_bytes(_format,get_output_position_samples()); + } + + ///break loop - so wave finishes current cycle and ends + void break_loop() { + std::lock_guard _(_mx); + break_loop_finish_at(_length); + } + + ///break loop by extending play beyond the loop - must be already defined in wave buffer + void break_loop(std::size_t final_len) { + std::lock_guard _(_mx); + break_loop_finish_at(final_len); + } + + ///stop playing now + void stop() { + std::lock_guard _(_mx); + _wrap_offset = _position - _length; + _loop_pos = _length; + } + + ///stops playing after given samples played + void stop_after_samples(std::size_t pos) { + std::lock_guard _(_mx); + _stop_at = _position + pos; + } + ///stops playing after given bytes played + void stop_after_bytes(std::size_t pos) { + stop_after_samples(length_in_samples(_format, pos)); + } + std::size_t length() const {return _length;} + std::size_t length_bytes() const {return length_in_bytes(_format, _length);} + + + ///create wave source + /** + * @param data pointer to data, can be allocated or static, depend on deleter + * @param format format - one of enum + * @param speed frequency in ratio to base frequency. For instance if base frequency is 44100 + * and wave frequency is 11025, then speed is 0.25 + * @param len_samples count of samples + * @param loop_pos + */ + WaveSource(WavePtr data, Format format, double speed, std::size_t len_samples, std::size_t loop_pos) + :_wave(std::move(data)) + ,_format(format) + ,_speed(speed) + ,_length(len_samples) + ,_loop_pos(std::min(loop_pos, len_samples)) + { + } + + + + + template Fn> + bool output(Fn &&fn, std::size_t samples, double speed = 1.0) { + std::lock_guard _(_mx); + switch (_format) { + case Format::int8: return do_output(std::forward(fn), + reinterpret_cast(_wave.get()), + samples, speed) > 0; + case Format::int16: return do_output(std::forward(fn), + reinterpret_cast(_wave.get()), + samples, speed) > 0; + case Format::uint8: return do_output(std::forward(fn), + reinterpret_cast(_wave.get()), + samples, speed) > 0; + case Format::uint16: return do_output(std::forward(fn), + reinterpret_cast(_wave.get()), + samples, speed) > 0; + case Format::stereo_int16: return do_output(std::forward(fn), + reinterpret_cast(_wave.get()), + samples, speed) > 0; + } + return false; + + } + + + + + + + + + + + ///for locking buffer - but probably not needed if you use memory barriers + std::recursive_mutex &get_lock() {return _mx;} + +protected: + mutable std::recursive_mutex _mx; + WavePtr _wave; + Format _format; + double _speed; + double _length; + double _loop_pos; + double _position = 0; + double _wrap_offset = 0; + double _interpolation_step = 1.0; + double _stop_at = std::numeric_limits::max(); + + + + constexpr float interpolate(float s1, float s2, float frac) { + return s1 + (s2 - s1) * frac; + } + + constexpr float interpolate(uint8_t s1, uint8_t s2, float frac) { + float sampl1 = static_cast(s1)/static_cast(128)- 1.0f; + float sampl2 = static_cast(s2)/static_cast(128)- 1.0f; + return interpolate(sampl1, sampl2, frac); + } + constexpr float interpolate(uint16_t s1, uint16_t s2, float frac) { + float sampl1 = static_cast(s1)/static_cast(32768) - 1.0f; + float sampl2 = static_cast(s2)/static_cast(32768) - 1.0f; + return interpolate(sampl1, sampl2, frac); + } + + constexpr float interpolate(int16_t s1, int16_t s2, float frac) { + float sampl1 = static_cast(s1)/static_cast(32768); + float sampl2 = static_cast(s2)/static_cast(32768); + return interpolate(sampl1, sampl2, frac); + } + constexpr StereoFloat interpolate(const StereoInt16 &s1, const StereoInt16 &s2, float frac) { + return { + interpolate(s1.left, s1.left, frac), + interpolate(s2.right, s2.right, frac) + }; + } + constexpr float interpolate(int8_t s1, int8_t s2, float frac) { + float sampl1 = static_cast(s1)/static_cast(128); + float sampl2 = static_cast(s2)/static_cast(128); + return interpolate(sampl1, sampl2, frac); + } + + + template + std::size_t do_output(Fn &&fn, const T *wave, std::size_t samples, double speed) { + float spd = speed * _speed; + for (std::size_t i = 0; i < samples; ++i) { + if (_position>= _stop_at) { + return i; + } + double p = wrap_pos(_position - _wrap_offset); + double sp; + double fp = std::modf(p,&sp); + std::size_t s1 = static_cast(wrap_pos(sp)); + std::size_t s2 = static_cast(wrap_pos(sp+1)); + if (s1 == s2) return i; + fn(interpolate(wave[s1], wave[s2], fp)); + _position = _position + spd; + } + return samples; + } + + + double wrap_pos(double position) const { + if (position >= _length) { + if (_loop_pos == _length) { + position = _length; + } else { + position = std::fmod((position - _length),(_length - _loop_pos)) + _loop_pos; + } + } + return position; + } + + + void break_loop_finish_at(double position) { + double adj = wrap_pos(_position); + _wrap_offset = _position - adj; + _loop_pos = _length = position; + } + +}; + diff --git a/platform/sound.h b/platform/sound.h new file mode 100644 index 0000000..42f4531 --- /dev/null +++ b/platform/sound.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../libs/music.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + +SND_PING, +SND_GVOLUME, +SND_BASS, +SND_TREBL, +SND_SWAP, +SND_LSWAP, +SND_SURROUND, +SND_OUTFILTER, +SND_GFX, +SND_MUSIC, +SND_XBASS, + +SND_MAXFUNCT} AUDIO_PROPERTY; + +void game_sound_init_device(const INI_CONFIG_SECTION *audio_section); + +char start_mixing(); +void stop_mixing(); +void play_sample(int channel,const void *sample,int32_t size,int32_t lstart,int32_t sfreq,int type); +void set_channel_volume(int channel,int left,int right); +void set_end_of_song_callback(const char * (*cb)(void *), void *ctx); +void fade_music(); +int mix_back_sound(int synchro); +void change_music(const char *filename); +char get_channel_state(int channel); +void get_channel_volume(int channel,int *left,int *right); +void mute_channel(int channel); +void chan_break_loop(int channel); +void chan_break_ext(int channel,const void *org_sample,int32_t size_sample); +char set_snd_effect(AUDIO_PROPERTY funct,int data); +char check_snd_effect(AUDIO_PROPERTY funct); +int get_snd_effect(AUDIO_PROPERTY funct); +void *PrepareVideoSound(int mixfreq, int buffsize) ; +char LoadNextVideoFrame(void *buffer, const char *data, int size, const short *xlat, short *accnums, int32_t *writepos); +void DoneVideoSound(void *buffer); +const char *device_name(int ); + + +#ifdef __cplusplus +} +#endif