#include "cube.h" #import "Command.h" #import "Player.h" #import "Variable.h" #include VARP(soundvol, 0, 255, 255); VARP(musicvol, 0, 128, 255); bool nosound = false; #define MAXCHAN 32 #define SOUNDFREQ 22050 #define MAXVOL MIX_MAX_VOLUME struct soundloc { OFVector3D loc; bool inuse; } soundlocs[MAXCHAN]; static Mix_Music *mod = NULL; void stopsound() { if (nosound) return; if (mod != NULL) { Mix_HaltMusic(); Mix_FreeMusic(mod); mod = NULL; } } VAR(soundbufferlen, 128, 1024, 4096); void initsound() { memset(soundlocs, 0, sizeof(struct soundloc) * MAXCHAN); if (Mix_OpenAudio(SOUNDFREQ, MIX_DEFAULT_FORMAT, 2, soundbufferlen) < 0) { conoutf(@"sound init failed (SDL_mixer): %s", (size_t)Mix_GetError()); nosound = true; } Mix_AllocateChannels(MAXCHAN); } COMMAND(music, ARG_1STR, (^ (OFString *name) { if (nosound) return; stopsound(); if (soundvol && musicvol) { name = [name stringByReplacingOccurrencesOfString: @"\\" withString: @"/"]; OFString *path = [OFString stringWithFormat: @"packages/%@", name]; OFIRI *IRI = [Cube.sharedInstance.gameDataIRI IRIByAppendingPathComponent: path]; if ((mod = Mix_LoadMUS( IRI.fileSystemRepresentation.UTF8String)) != NULL) { Mix_PlayMusic(mod, -1); Mix_VolumeMusic((musicvol * MAXVOL) / 255); } } })) static OFMutableData *samples; static OFMutableArray *snames; COMMAND(registersound, ARG_1EST, ^int(OFString *name) { int i = 0; for (OFString *iter in snames) { if ([iter isEqual: name]) return i; i++; } if (snames == nil) snames = [[OFMutableArray alloc] init]; if (samples == nil) samples = [[OFMutableData alloc] initWithItemSize: sizeof(Mix_Chunk *)]; [snames addObject: [name stringByReplacingOccurrencesOfString: @"\\" withString: @"/"]]; Mix_Chunk *sample = NULL; [samples addItem: &sample]; return samples.count - 1; }) void cleansound() { if (nosound) return; stopsound(); Mix_CloseAudio(); } VAR(stereo, 0, 1, 1); static void updatechanvol(int chan, const OFVector3D *loc) { int vol = soundvol, pan = 255 / 2; if (loc) { OFVector3D origin = Player.player1.origin; float dist = OFDistanceOfVectors3D(origin, *loc); OFVector3D v = OFSubtractVectors3D(origin, *loc); // simple mono distance attenuation vol -= (int)(dist * 3 * soundvol / 255); if (stereo && (v.x != 0 || v.y != 0)) { // relative angle of sound along X-Y axis float yaw = -atan2(v.x, v.y) - Player.player1.yaw * (PI / 180.0f); // range is from 0 (left) to 255 (right) pan = (int)(255.9f * (0.5 * sin(yaw) + 0.5f)); } } vol = (vol * MAXVOL) / 255; Mix_Volume(chan, vol); Mix_SetPanning(chan, 255 - pan, pan); } static void newsoundloc(int chan, const OFVector3D *loc) { assert(chan >= 0 && chan < MAXCHAN); soundlocs[chan].loc = *loc; soundlocs[chan].inuse = true; } void updatevol() { if (nosound) return; for (int i = 0; i < MAXCHAN; i++) { if (soundlocs[i].inuse) { if (Mix_Playing(i)) updatechanvol(i, &soundlocs[i].loc); else soundlocs[i].inuse = false; } } } void playsoundc(int n) { addmsg(0, 2, SV_SOUND, n); playsound(n, NULL); } int soundsatonce = 0, lastsoundmillis = 0; void playsound(int n, const OFVector3D *loc) { if (nosound) return; if (!soundvol) return; if (lastmillis == lastsoundmillis) soundsatonce++; else soundsatonce = 1; lastsoundmillis = lastmillis; if (soundsatonce > 5) // avoid bursts of sounds with heavy packetloss and in sp return; if (n < 0 || n >= samples.count) { conoutf(@"unregistered sound: %d", n); return; } Mix_Chunk **sample = (Mix_Chunk **)[samples mutableItemAtIndex: n]; if (*sample == NULL) { OFString *path = [OFString stringWithFormat: @"packages/sounds/%@.wav", snames[n]]; OFIRI *IRI = [Cube.sharedInstance.gameDataIRI IRIByAppendingPathComponent: path]; *sample = Mix_LoadWAV(IRI.fileSystemRepresentation.UTF8String); if (*sample == NULL) { conoutf(@"failed to load sample: %@", IRI.string); return; } } int chan = Mix_PlayChannel(-1, *sample, 0); if (chan < 0) return; if (loc) newsoundloc(chan, loc); updatechanvol(chan, loc); } COMMAND(sound, ARG_1INT, ^ (int n) { playsound(n, NULL); })