view src/libmlk-core/core/sys.c @ 379:67c1c46af2c8

core: replace SDL2_mixer with OpenAL, closes #2528 @3h
author David Demelier <markand@malikania.fr>
date Wed, 05 Jan 2022 12:45:17 +0100
parents 460c78706989
children 31e2f6d35c34
line wrap: on
line source

/*
 * sys.c -- system routines
 *
 * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "config.h"

#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#if defined(_WIN32)
#       include <shlwapi.h>
#       include <windows.h>
#else
#       include <errno.h>
#       include <string.h>
#endif

#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>

#include <sndfile.h>

#include <port/port.h>

#include "alloc.h"
#include "error.h"
#include "panic.h"
#include "sound.h"
#include "sys.h"
#include "sys_p.h"

ALCdevice *audio_dev = NULL;
ALCcontext *audio_ctx = NULL;

static struct {
	char organization[128];
	char name[128];
} info = {
	.organization = "fr.malikania",
	.name = "molko"
};

static struct {
	char bindir[PATH_MAX];
	char datadir[PATH_MAX];
	char localedir[PATH_MAX];
} paths;

static inline char *
normalize(char *str)
{
	for (char *p = str; *p; ++p)
		if (*p == '\\')
			*p = '/';

	return str;
}

static inline int
absolute(const char *path)
{
#if defined(_WIN32)
	return !PathIsRelativeA(path);
#else
	/* Assuming UNIX like. */
	if (path[0] == '/')
		return 1;

	return 0;
#endif
}

static const char *
system_directory(const char *whichdir)
{
	static char path[PATH_MAX];
	static char ret[PATH_MAX];
	char *base, *binsect;

	/*
	 * Some system does not provide support (shame on you OpenBSD)
	 * to the executable path. In that case we use PREFIX+<dir>
	 * instead unless <dir> is already absolute.
	 */

	/*
	 * If requested directory is absolute return immediately.
	 *
	 * e.g. whichdir == /usr/share  -> return immediately
	 *      whichdir == bin         -> will be computed
	 */
	if (absolute(whichdir))
		return whichdir;

	/*
	 * If MLK_BINDIR is absolute then we're unable to compute whichdir which
	 * now is mandatory relative. In that case return its whole path to the
	 * prefix.
	 */
	if (absolute(MLK_BINDIR) || !(base = SDL_GetBasePath()))
		snprintf(ret, sizeof (ret), "%s/%s", MLK_PREFIX, whichdir);
	else {
		/*
		 * Decompose the path to the given special directory by
		 * computing relative directory to it from where the
		 * binary is located.
		 *
		 * Example:
		 *
		 *   PREFIX/bin/<executable>
		 *   PREFIX/share/mysupergame
		 *
		 * The path to the data is ../share/mysupergame starting from
		 * the binary.
		 *
		 * Put the base path into the path and remove the value
		 * of MLK_BINDIR.
		 *
		 * Example:
		 *   from: /usr/local/bin
		 *   to:   /usr/local
		 */
		port_strlcpy(path, base, sizeof (path));
		SDL_free(base);

		/* TODO: remove using negative offset. */
		if ((binsect = strstr(path, MLK_BINDIR)))
			*binsect = '\0';

		snprintf(ret, sizeof (ret), "%s%s", path, whichdir);
	}

	return normalize(ret);
}

static const char *
user_directory(enum sys_dir kind)
{
	/* Kept for future use. */
	(void)kind;

	static char path[PATH_MAX];
	char *pref;

	if ((pref = SDL_GetPrefPath(info.organization, info.name))) {
		port_strlcpy(path, pref, sizeof (path));
		SDL_free(pref);
	} else
		port_strlcpy(path, "./", sizeof (path));

	return path;
}

static inline int
mkpath(const char *path)
{
#ifdef _WIN32
	/* TODO: add error using the convenient FormatMessage function. */
	if (!CreateDirectoryA(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
		return errorf("unable to create directory: %s", path);
#else
	if (mkdir(path, 0755) < 0 && errno != EEXIST)
		return errorf("%s", strerror(errno));
#endif

	return 0;
}

int
sys_init(const char *organization, const char *name)
{
#if defined(__MINGW64__)
	/* On MinGW buffering leads to painful debugging. */
	setbuf(stderr, NULL);
	setbuf(stdout, NULL);
#endif
	port_strlcpy(paths.bindir, system_directory(MLK_BINDIR), sizeof (paths.bindir));
	port_strlcpy(paths.datadir, system_directory(MLK_DATADIR), sizeof (paths.datadir));
	port_strlcpy(paths.localedir, system_directory(MLK_LOCALEDIR), sizeof (paths.localedir));

	port_strlcpy(info.organization, organization, sizeof (info.organization));
	port_strlcpy(info.name, name, sizeof (info.name));

	/* SDL2. */
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
		return errorf("%s", SDL_GetError());
	if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG)
		return errorf("%s", SDL_GetError());
	if (TTF_Init() < 0)
		return errorf("%s", SDL_GetError());

	/* OpenAL. */
	if (!(audio_dev = alcOpenDevice(NULL)))
		return errorf("unable to create audio device");
	if (!(audio_ctx = alcCreateContext(audio_dev, NULL)))
		return errorf("unable to create audio context");

	alcMakeContextCurrent(audio_ctx);

	return 0;
}

const char *
sys_dir(enum sys_dir kind)
{
	switch (kind) {
	case SYS_DIR_BIN:
		return paths.bindir;
	case SYS_DIR_DATA:
		return paths.datadir;
	case SYS_DIR_LOCALE:
		return paths.localedir;
	default:
		return user_directory(kind);
	}
}

int
sys_mkdir(const char *directory)
{
	char path[PATH_MAX], *p;

	/* Copy the directory to normalize and iterate over '/'. */
	port_strlcpy(path, directory, sizeof (path));
	normalize(path);

#if defined(_WIN32)
	/* Remove drive letter that we don't need. */
	if ((p = strchr(path, ':')))
		++p;
	else
		p = path;
#else
	p = path;
#endif

	for (p = p + 1; *p; ++p) {
		if (*p == '/') {
			*p = 0;

			if (mkpath(path) < 0)
				return -1;

			*p = '/';
		}
	}

	return mkpath(path);
}

void
sys_finish(void)
{
	TTF_Quit();
	IMG_Quit();
	SDL_Quit();

	alcMakeContextCurrent(NULL);

	if (audio_ctx) {
		alcDestroyContext(audio_ctx);
		audio_ctx = NULL;
	}
	if (audio_dev) {
		alcCloseDevice(audio_dev);
		audio_dev = NULL;
	}
}

struct audiostream *
audiostream_create(SNDFILE *file, const SF_INFO *info)
{
	struct audiostream *stream;

	stream = alloc_new(sizeof (*stream));
	stream->samplerate = info->samplerate;
	stream->samplesz = info->frames * info->channels;
	stream->samples = alloc_array(stream->samplesz, sizeof (*stream->samples));
	stream->format = info->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;

	if (sf_read_short(file, stream->samples, stream->samplesz) != stream->samplesz) {
		free(stream->samples);
		free(stream);
		stream = NULL;
	}

	alGenBuffers(1, &stream->buffer);
	alBufferData(stream->buffer, stream->format, stream->samples,
	    stream->samplesz * sizeof (ALushort), stream->samplerate);
	alGenSources(1, &stream->source);
	alSourcei(stream->source, AL_BUFFER, stream->buffer);
	
	sf_close(file);

	return stream;
}

struct audiostream *
audiostream_open(const char *path)
{
	assert(path);

	SF_INFO info;
	SNDFILE *file;

	if (!(file = sf_open(path, SFM_READ, &info)))
		return NULL;

	return audiostream_create(file, &info);
}

struct viodata {
	const unsigned char *data;
	const size_t datasz;
	sf_count_t offset;
};

static sf_count_t
vio_get_filelen(void *data)
{
	const struct viodata *vio = data;

	return (sf_count_t)vio->datasz;
}

static sf_count_t
vio_seek(sf_count_t offset, int whence, void *data)
{
	struct viodata *vio = data;

	switch (whence) {
	case SEEK_SET:
		vio->offset = offset;
		break;
	case SEEK_CUR:
		vio->offset += offset;
		break;
	case SEEK_END:
		vio->offset = vio->datasz - offset;
		break;
	default:
		break;
	}

	return vio->offset;
}

static sf_count_t
vio_read(void *ptr, sf_count_t count, void *data)
{
	struct viodata *vio = data;

	memcpy(ptr, vio->data + vio->offset, count);
	vio->offset += count;

	return count;
}

static sf_count_t
vio_tell(void *data)
{
	const struct viodata *vio = data;

	return vio->offset;
}

struct audiostream *
audiostream_openmem(const void *data, size_t datasz)
{
	assert(data);

	SF_VIRTUAL_IO io = {
		.get_filelen = vio_get_filelen,
		.seek = vio_seek,
		.read = vio_read,
		.tell = vio_tell
	};
	SF_INFO info;
	SNDFILE *file;
	struct viodata viodata = {
		.data = data,
		.datasz = datasz,
	};
	
	if (!(file = sf_open_virtual(&io, SFM_READ, &info, &viodata)))
		return NULL;

	return audiostream_create(file, &info);
}

void
audiostream_finish(struct audiostream *s)
{
	assert(s);

	alDeleteBuffers(1, &s->buffer);
	alSourcei(s->source, AL_BUFFER, 0);
	alDeleteSources(1, &s->source);
}