mirror of
				https://github.com/zenorogue/hyperrogue.git
				synced 2025-10-31 05:52:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			309 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			309 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Hyperbolic Rogue -- sound effects and music
 | |
| // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
 | |
| 
 | |
| /** \file sound.cpp
 | |
|  *  \brief sound effects and music
 | |
|  */
 | |
| 
 | |
| #include "hyper.h"
 | |
| namespace hr {
 | |
| 
 | |
| #if HDR
 | |
| void playSound(cell *c, const string& fname, int vol = 100);
 | |
| void resetmusic();
 | |
| #endif
 | |
| 
 | |
| #if HDR
 | |
| /** RogueViz may be used for situations where music does not correspond to lands, so we allow extra IDs */
 | |
| static constexpr int MUSIC_MAX = 500;
 | |
| #endif
 | |
| 
 | |
| EX const char *musicfile = "";
 | |
| EX bool audio;
 | |
| EX string musiclicense;
 | |
| EX string musfname[MUSIC_MAX];
 | |
| EX int musicvolume = 60;
 | |
| EX int effvolume = 60;
 | |
| EX bool music_available;
 | |
| EX int musiclength[MUSIC_MAX];
 | |
| 
 | |
| EX eLand getCurrentLandForMusic() {
 | |
|   eLand id = ((anims::center_music()) && centerover) ? centerover->land : cwt.at->land;
 | |
|   if(isHaunted(id)) id = laHaunted;
 | |
|   if(id == laWarpSea) id = laWarpCoast;
 | |
|   if(id == laMercuryRiver) id = laTerracotta;
 | |
|   return id;
 | |
|   }
 | |
| 
 | |
| EX void playSeenSound(cell *c) {
 | |
|   if(!c->monst) return;
 | |
|   bool nearme = c->cpdist <= 7;
 | |
|   forCellEx(c2, c) if(c2->cpdist <= 7) nearme = true; 
 | |
|   if(!nearme) return;
 | |
|   if(among(c->monst, moEagle, moWindCrow, moAcidBird))
 | |
|     playSound(c, "seen-eagle");
 | |
|   else if(c->monst == moEarthElemental) 
 | |
|     playSound(c, "seen-earth");
 | |
|   else if(c->monst == moAirElemental) 
 | |
|     playSound(c, "seen-air");
 | |
|   else if(c->monst == moPhaser) 
 | |
|     playSound(c, "seen-frog1");
 | |
|   else if(c->monst == moFrog) 
 | |
|     playSound(c, "seen-frog2");
 | |
|   else if(c->monst == moVaulter) 
 | |
|     playSound(c, "seen-frog3");
 | |
|   else if(c->monst == moWaterElemental)
 | |
|     playSound(c, "seen-water");
 | |
|   else if(c->monst == moFireElemental)
 | |
|     playSound(c, "fire");
 | |
|   else if(c->monst == moDragonHead)
 | |
|     playSound(c, "seen-dragon");
 | |
|   else if(c->monst == moWorm)
 | |
|     playSound(c, "seen-sandworm");
 | |
|   else if(c->monst == moSkeleton && c->land != laDungeon)
 | |
|     playSound(c, "seen-skeleton");
 | |
|   else if(c->monst == moHexSnake)
 | |
|     playSound(c, "seen-snake");
 | |
|   else if(c->monst == moWolf || c->monst == moRedFox)
 | |
|     playSound(c, "seen-woof");
 | |
|   else if(isTroll(c->monst))
 | |
|     playSound(c, "seen-troll");
 | |
|   else if(c->monst == moNecromancer)
 | |
|     playSound(c, "seen-necromancer");
 | |
|   else if(c->monst == moGhost)
 | |
|     playSound(c, "seen-ghost");
 | |
|   else if(c->monst == moRoseBeauty) 
 | |
|     playSound(c, princessgender() ? "seen-rosebeauty" : "seen-gardener");
 | |
|   else if(c->monst == moVizier) 
 | |
|     playSound(c, "seen-vizier");
 | |
|   else if(c->monst == moFireFairy)
 | |
|     playSound(c, "seen-fairy");
 | |
|   else if(c->monst == moCultist) 
 | |
|     playSound(c, "seen-cultist");
 | |
|   else if(c->monst == moPyroCultist) 
 | |
|     playSound(c, "seen-cultistfire");
 | |
|   else if(c->monst == moCultistLeader)
 | |
|     playSound(c, "seen-cultistleader");
 | |
|   }
 | |
| 
 | |
| #if CAP_SDLAUDIO
 | |
| 
 | |
| bool loaded[MUSIC_MAX];
 | |
| Mix_Music* music[MUSIC_MAX];
 | |
| EX int musicpos[MUSIC_MAX];
 | |
| EX int musstart;
 | |
| int musfadeval = 2000;
 | |
| 
 | |
| eLand cid = laNone;
 | |
| 
 | |
| EX hookset<bool(eLand&)> hooks_music;
 | |
| EX hookset<void(eLand&)> hooks_sync_music;
 | |
| 
 | |
| EX bool music_out_of_focus = false;
 | |
| 
 | |
| EX void handlemusic() {
 | |
|   DEBBI(DF_GRAPH, ("handle music"));
 | |
|   if(audio && musicvolume) {
 | |
|     eLand id = getCurrentLandForMusic();
 | |
|     if(callhandlers(false, hooks_music, id)) return;
 | |
|     if(outoffocus && !music_out_of_focus) id = eLand(0);
 | |
|     if(musfname[id] == "LAST") id = cid;
 | |
|     if(!loaded[id] && !memory_issues()) {
 | |
|       loaded[id] = true;
 | |
|       // printf("loading (%d)> %s\n", id, musfname[id].c_str());      
 | |
|       // reuse music
 | |
|       if(musfname[id] != "") {
 | |
|         for(int i=0; i<landtypes; i++)
 | |
|           if(music[i] && musfname[i] == musfname[id])
 | |
|             music[id] = music[i];
 | |
|         }
 | |
|       if(!music[id]) {
 | |
|         memory_for_lib();
 | |
|         music[id] = Mix_LoadMUS(musfname[id].c_str());
 | |
|         if(!music[id]) {
 | |
|            printf("Mix_LoadMUS: %s\n", Mix_GetError());
 | |
|            }
 | |
|         }
 | |
|       }
 | |
|     if(cid != id && !musfadeval) {
 | |
|       musicpos[cid] = SDL_GetTicks() - musstart;
 | |
|       musfadeval = musicpos[id] ? 500 : 2000;
 | |
|       Mix_FadeOutMusic(musfadeval);
 | |
|       // printf("fadeout %d, pos %d\n", musfadeval, musicpos[cid]);
 | |
|       }
 | |
|     if(music[id] && !Mix_PlayingMusic()) {
 | |
|       callhooks(hooks_sync_music, id);
 | |
|       cid = id;
 | |
|       Mix_VolumeMusic(musicvolume);
 | |
|       Mix_FadeInMusicPos(music[id], -1, musfadeval, musicpos[id] / 1000.0);
 | |
|       // printf("fadein %d, pos %d\n", musfadeval, musicpos[cid]);
 | |
|       musstart = SDL_GetTicks() - musicpos[id];
 | |
|       musicpos[id] = 0;
 | |
|       musfadeval = 0;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| hookset<bool(eLand&)> hooks_resetmusic;
 | |
| 
 | |
| EX void resetmusic() {
 | |
|   if(audio && musicvolume) {
 | |
|     Mix_FadeOutMusic(3000);
 | |
|     cid = laNone;
 | |
|     for(int i=0; i<landtypes; i++) musicpos[i] = 0;
 | |
|     musfadeval = 2000;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| #if HDR
 | |
| constexpr eLand mfcode(const char* buf) { return eLand((buf[0] - '0') * 10 + buf[1] - '0'); }
 | |
| #endif
 | |
| 
 | |
| EX bool loadMusicInfo(string dir) {
 | |
|   DEBBI(DF_INIT, ("load music info"));
 | |
|   if(dir == "") return false;
 | |
|   FILE *f = fopen(dir.c_str(), "rt");
 | |
|   if(f) {
 | |
|     string dir2;
 | |
|     for(int i=0; i<isize(dir); i++) if(dir[i] == '/' || dir[i] == '\\')
 | |
|       dir2 = dir.substr(0, i+1);
 | |
|     char buf[1000];
 | |
|     while(fgets(buf, 800, f) != NULL) {
 | |
|       for(int i=0; buf[i]; i++) if(buf[i] == 10 || buf[i] == 13) buf[i] = 0;
 | |
|       if(buf[0] == '[' && buf[3] == ']') {
 | |
|         int id = (buf[1] - '0') * 10 + buf[2] - '0';
 | |
|         if(id >= 0 && id < MUSIC_MAX) {
 | |
|           if(buf[5] == 'L' && buf[6] == '=') musiclength[id] = atoi(buf+7);
 | |
|           else if(buf[5] == '*' && buf[6] == '/') musfname[id] = dir2 + (buf+7);
 | |
|           else musfname[id] = buf+5;
 | |
|           music_available = true;
 | |
|           }
 | |
|         else {
 | |
|           fprintf(stderr, "warning: bad soundtrack id, use the following format:\n");
 | |
|           fprintf(stderr, "[##] */filename\n");
 | |
|           fprintf(stderr, "where ## are two digits, and */ is optional and replaced by path to the music\n");
 | |
|           fprintf(stderr, "alternatively LAST = reuse the last track instead of having a special one");
 | |
|           }
 | |
|         // printf("[%2d] %s\n", id, buf);
 | |
|         }
 | |
|       else if(buf[0] == '#') {
 | |
|         }
 | |
|       else {
 | |
|         musiclicense += buf;
 | |
|         musiclicense += "\n";
 | |
|         }
 | |
|       }
 | |
|     fclose(f);
 | |
|     return true;
 | |
|     }
 | |
|   return false;
 | |
|   }
 | |
| 
 | |
| EX bool loadMusicInfo() {
 | |
|   return
 | |
|     loadMusicInfo(find_file(musicfile))
 | |
|     || loadMusicInfo(find_file("hyperrogue-music.txt") )
 | |
|     || loadMusicInfo(find_file("music/hyperrogue-music.txt") )
 | |
| #ifdef FHS
 | |
|     || (getenv("HOME") && loadMusicInfo(s0 + getenv("HOME") + "/.hyperrogue-music.txt"))
 | |
| #endif
 | |
|     ;
 | |
|   }
 | |
| 
 | |
| EX void initAudio() {
 | |
|   audio = loadMusicInfo();
 | |
| 
 | |
|   if(audio) {
 | |
|     if(SDL23(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 2048) != 0, !Mix_OpenAudio(0, nullptr))) {
 | |
|       fprintf(stderr, "Unable to initialize audio: %s\n", Mix_GetError());
 | |
|       audio = false;
 | |
|       }
 | |
|     else {
 | |
|       audio = true;
 | |
|       Mix_AllocateChannels(16);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| map<string, Mix_Chunk*> chunks;
 | |
| 
 | |
| hookset<bool(const string& s, int vol)> hooks_sound;
 | |
| 
 | |
| EX string wheresounds = "sounds/";
 | |
| 
 | |
| EX void playSound(cell *c, const string& fname, int vol) {
 | |
|   LATE( hr::playSound(c, fname, vol); )
 | |
|   if(effvolume == 0) return;
 | |
|   if(callhandlers(false, hooks_sound, fname, vol)) return;
 | |
|   // printf("Play sound: %s\n", fname.c_str());
 | |
|   if(!chunks.count(fname)) {
 | |
|     string s = find_file(wheresounds + fname + ".ogg");
 | |
|     if(memory_issues()) return;
 | |
|     memory_for_lib();
 | |
|     chunks[fname] = Mix_LoadWAV(s.c_str());
 | |
|     // printf("Loading, as %p\n", chunks[fname]);
 | |
|     }
 | |
|   Mix_Chunk *chunk = chunks[fname];
 | |
|   if(chunk) {
 | |
|     Mix_VolumeChunk(chunk, effvolume * vol / 100);
 | |
|     Mix_PlayChannel(-1, chunk, 0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| EX void reuse_music_memory() {
 | |
|   for(int i=0; i<landtypes; i++)
 | |
|     if(music[i] && music[i] != music[cid]) {
 | |
|       Mix_Music *which = music[i];
 | |
|       println(hlog, "freeing music for ", dnameof(eLand(i)));
 | |
|       Mix_FreeMusic(which);
 | |
|       for(int j=0; j<landtypes; j++) if(music[j] == which) {
 | |
|         println(hlog, "... which equals ", dnameof(eLand(j)));
 | |
|         music[j] = NULL;
 | |
|         }
 | |
|       }
 | |
|   set<Mix_Chunk*> currently_played;
 | |
|   for(int ch=0; ch<16; ch++) currently_played.insert(Mix_GetChunk(ch));
 | |
|   set<string> to_free;
 | |
|   for(auto& p: chunks) 
 | |
|     if(p.second) {
 | |
|       if(currently_played.count(p.second)) {
 | |
|         println(hlog, p.first, ": currently played");
 | |
|         }
 | |
|       else {
 | |
|         Mix_FreeChunk(p.second);
 | |
|         to_free.insert(p.first); 
 | |
|         println(hlog, p.first, ": freed");
 | |
|         }
 | |
|       }
 | |
|   for(auto& s: to_free) chunks.erase(s);
 | |
|   }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #if CAP_COMMANDLINE
 | |
| int read_sound_args() {
 | |
|   using namespace arg;
 | |
|   if(argis("-m")) { PHASE(1); shift(); musicfile = argcs(); }
 | |
| #if CAP_SDLAUDIO
 | |
|   else if(argis("-se")) { PHASE(1); shift(); wheresounds = args(); }
 | |
|   else if(argis("-musicfocus")) { music_out_of_focus = true; }
 | |
| #endif
 | |
|   else if(argis("-svol")) { PHASEFROM(2); shift(); effvolume = argi(); }
 | |
|   else if(argis("-mvol")) { PHASEFROM(2); shift(); musicvolume = argi(); }
 | |
|   else return 1;
 | |
|   return 0;
 | |
|   }
 | |
| 
 | |
| #if CAP_SDLAUDIO
 | |
| auto ah_sound = addHook(hooks_args, 0, read_sound_args) + addHook(hooks_clear_cache, 0, reuse_music_memory);
 | |
| #endif
 | |
| 
 | |
| #endif
 | |
| 
 | |
| #if !CAP_AUDIO
 | |
| EX void playSound(cell *c, const string& fname, int vol) { }
 | |
| EX void resetmusic() { }
 | |
| #endif
 | |
| 
 | |
| }
 | 
