Cube  sound.m at [5b7b7d2fc5]

File src/sound.m artifact 432ff899cb part of check-in 5b7b7d2fc5


#include "cube.h"

#import "Command.h"
#import "Player.h"

#include <SDL_mixer.h>

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<OFString *> *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);
})