/* Copyright (C) 2020 SovietPony
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License ONLY.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "sound.h"
#include "music.h"
#include "files.h" // F_findres F_getreslen
#include "memory.h" // M_lock M_unlock
#include "error.h" // logo
#include "common/endianness.h"
#ifdef __APPLE__
# include
# include
#else
# include
# include
#endif
#include
#include // malloc
#include // memcpy
#define TAG_OAL1 0x4F414C31
#define MAX_CHANNELS 8
#define OAL_FORMAT AL_FORMAT_MONO8
#pragma pack(1)
typedef struct dmi {
word len;
word rate;
word lstart;
word llen;
byte data[];
} dmi;
#pragma pack()
typedef struct openal_snd {
snd_t base;
ALuint buffer;
} openal_snd;
typedef struct openal_channel {
ALuint source;
} openal_channel;
static short snd_vol;
static ALCdevice *device;
static ALCcontext *context;
static ALuint sources[MAX_CHANNELS];
/* Music */
const cfg_t *MUS_args (void) {
return NULL;
}
const cfg_t *MUS_conf (void) {
return NULL;
}
const menu_t *MUS_menu (void) {
return NULL;
}
void MUS_init (void) {
}
void MUS_done (void) {
}
void MUS_start (int time) {
}
void MUS_stop (void) {
}
void MUS_volume (int v) {
}
void MUS_load (char n[8]) {
}
void MUS_free (void) {
}
void MUS_update (void) {
}
/* Sound */
static int sound_menu_handler (menu_msg_t *msg, const menu_t *m, int i) {
static int cur;
enum { VOLUME, __NUM__ };
static const simple_menu_t sm = {
GM_BIG, "Sound", NULL,
{
{ "Volume", NULL },
}
};
if (i == VOLUME) {
switch (msg->type) {
case GM_GETENTRY: return GM_init_int0(msg, GM_SCROLLER, 0, 0, 0);
case GM_GETINT: return GM_init_int(msg, snd_vol, 0, 128, 8);
case GM_SETINT: S_volume(msg->integer.i); return 1;
}
}
return simple_menu_handler(msg, i, __NUM__, &sm, &cur);
}
const menu_t *S_menu (void) {
static const menu_t m = { &sound_menu_handler };
return &m;
}
const cfg_t *S_args (void) {
static const cfg_t args[] = {
{ "sndvol", &snd_vol, Y_WORD },
{ NULL, NULL, 0 }
};
return args;
}
const cfg_t *S_conf (void) {
static const cfg_t conf[] = {
{ "sound_volume", &snd_vol, Y_WORD },
{ NULL, NULL, 0 }
};
return conf;
}
// Convert mono signed/unsigned 8bit sound to openal format
// AL_FORMAT_MONO8 -> mono unsigned 8bit
// AL_FORMAT_STEREO8 -> stereo unsigned 8bit
// AL_FORMAT_MONO16 -> mono signed 16bit
// AL_FORMAT_STEREO16 -> stereo signed 16bit
static void cvt_this (int sign, ALint format, const void *data, size_t len, void **outdata, size_t *outlen) {
*outdata = NULL;
*outlen = 0;
switch (format) {
case AL_FORMAT_MONO8:
{
int8_t *y = malloc(len);
if (y != NULL) {
memcpy(y, data, len);
if (sign) {
for (size_t i = 0; i < len; i++) {
y[i] ^= 0x80;
}
}
*outdata = y;
*outlen = len;
}
}
break;
case AL_FORMAT_MONO16:
{
int16_t *y = malloc(2 * len);
if (y != NULL) {
for (size_t i = 0; i < len; ++i) {
int32_t c = ((int8_t*)data)[i] ^ (sign ? 0x00 : 0x80);
y[i] = c >= 0 ? c * 32767 / 127 : c * -32767 / -128;
}
}
*outdata = y;
*outlen = 2 * len;
}
break;
case AL_FORMAT_STEREO8:
{
int8_t *y = malloc(2 * len);
if (y != NULL) {
int8_t *yy = y;
for (size_t i = 0; i < len; i++) {
int8_t xx = ((int8_t*)data)[i] ^ (sign ? 0x80 : 0x00);
*yy = xx; yy++;
*yy = xx; yy++;
}
*outdata = y;
*outlen = 2 * len;
}
}
break;
case AL_FORMAT_STEREO16:
{
int16_t *y = malloc(2 * 2 * len);
if (y != NULL) {
int16_t *yy = y;
for (size_t i = 0; i < len; ++i) {
int32_t x = ((int8_t*)data)[i] ^ (sign ? 0x00 : 0x80);
int16_t xx = x >= 0 ? x * 32767 / 127 : x * -32767 / -128;
*yy = xx; yy++;
*yy = xx; yy++;
}
}
*outdata = y;
*outlen = 2 * 2* len;
}
break;
}
}
static openal_snd *new_openal_snd (const void *data, dword len, dword rate, dword lstart, dword llen, int sign) {
assert(data);
ALuint buffer = 0;
openal_snd *snd = NULL;
void *newdata = NULL;
size_t newlen = 0;
cvt_this(sign, OAL_FORMAT, data, len, &newdata, &newlen);
if (newdata != NULL) {
alGenBuffers(1, &buffer);
if (alGetError() == AL_NO_ERROR) {
alBufferData(buffer, OAL_FORMAT, newdata, newlen, rate);
if (alGetError() == AL_NO_ERROR) {
snd = malloc(sizeof(openal_snd));
if (snd != NULL) {
snd->base.tag = TAG_OAL1;
snd->buffer = buffer;
// TODO loops
} else {
alDeleteBuffers(1, &buffer);
}
} else {
alDeleteBuffers(1, &buffer);
}
}
free(newdata);
}
return snd;
}
snd_t *S_get (int id) {
void *handle;
openal_snd *snd = NULL;
if (context != NULL) {
handle = M_lock(id);
if (handle != NULL) {
void *data = handle;
word len = F_getreslen(id);
word rate = 11025;
word lstart = 0;
word llen = 0;
int sign = 0;
if (len > 8) {
dmi *hdr = handle;
word hdr_len = short2host(hdr->len);
word hdr_rate = short2host(hdr->rate);
word hdr_lstart = short2host(hdr->lstart);
word hdr_llen = short2host(hdr->llen);
if (hdr_len <= len - 4 && hdr_lstart + hdr_llen <= len - 8) {
data = hdr->data;
len = hdr_len;
rate = hdr_rate;
lstart = hdr_lstart;
llen = hdr_llen;
sign = 1;
}
}
snd = new_openal_snd(data, len, rate, lstart, llen, sign);
M_unlock(handle);
}
}
return (snd_t*)snd;
}
snd_t *S_load (const char name[8]) {
return S_get(F_findres(name));
}
void S_free (snd_t *s) {
int i;
ALint h;
openal_snd *snd = (openal_snd*)s;
if (snd != NULL) {
assert(snd->base.tag == TAG_OAL1);
if (context != NULL) {
for (i = 0; i < MAX_CHANNELS; i++) {
alGetSourcei(sources[i], AL_BUFFER, &h);
if (h == snd->buffer) {
alSourceStop(sources[i]);
alSourcei(sources[i], AL_BUFFER, 0);
}
}
alDeleteBuffers(1, &snd->buffer);
assert(alGetError() == AL_NO_ERROR);
}
snd->base.tag = 0;
free(s);
}
}
void S_init (void) {
assert(device == NULL && context == NULL);
const ALCint attrs[] = {ALC_MONO_SOURCES, MAX_CHANNELS, 0};
device = alcOpenDevice(NULL);
if (device != NULL) {
context = alcCreateContext(device, attrs);
if (context != NULL) {
if (alcMakeContextCurrent(context)) {
alGenSources(MAX_CHANNELS, sources);
if (alGetError() == AL_NO_ERROR) {
alListenerf(AL_GAIN, 1);
alListener3f(AL_POSITION, 0, 0, 0);
alListener3f(AL_VELOCITY, 0, 0, 0);
} else {
logo("S_init: unable to create OpenAL sources\n");
alcDestroyContext(context);
alcCloseDevice(device);
context = NULL;
device = NULL;
}
} else {
logo("S_init: unable to make OpenAL context current\n");
alcDestroyContext(context);
alcCloseDevice(device);
context = NULL;
device = NULL;
}
} else {
logo("S_init: unable to create OpenAL context\n");
alcCloseDevice(device);
device = NULL;
}
} else {
logo("S_init: unable to open OpenAL device\n");
}
}
void S_done (void) {
if (context != NULL) {
alcMakeContextCurrent(NULL);
alcDestroyContext(context);
alcCloseDevice(device);
}
context = NULL;
device = NULL;
}
short S_play (snd_t *s, short c, short v) {
assert(c >= 0 && c <= MAX_CHANNELS);
assert(v >= 0 && v < 256);
ALuint source;
ALint state;
int channel;
if (context != NULL && s != NULL) {
openal_snd *snd = (openal_snd*)s;
assert(snd->base.tag == TAG_OAL1);
if (c == 0) {
for (channel = 0; channel < MAX_CHANNELS; channel++) {
state = AL_PLAYING;
alGetSourcei(sources[channel], AL_SOURCE_STATE, &state);
if (state == AL_STOPPED || state == AL_INITIAL) {
break; // !!!
}
}
} else {
channel = c - 1;
}
if (channel < MAX_CHANNELS) {
source = sources[channel];
alSourceStop(source);
alSourcei(source, AL_BUFFER, snd->buffer);
alSourcef(source, AL_PITCH, 1);
alSourcef(source, AL_GAIN, v / 255.0);
alSource3f(source, AL_POSITION, 0, 0, 0);
alSource3f(source, AL_VELOCITY, 0, 0, 0);
alSourcei(source, AL_LOOPING, AL_FALSE);
alSourcePlay(source);
}
}
return 0;
}
void S_stop (short c) {
assert(c >= 0 && c <= MAX_CHANNELS);
if (context != NULL) {
if (c != 0) {
alSourceStop(sources[c - 1]);
}
}
}
void S_volume (int v) {
assert(v >= 0 && v <= 128);
snd_vol = v;
if (context != NULL) {
alListenerf(AL_GAIN, v / 128.0);
}
}
void S_wait (void) {
int i;
ALint state;
if (context != NULL) {
for (i = 0; i < MAX_CHANNELS; i++) {
do {
state = AL_STOPPED;
alGetSourcei(sources[i], AL_SOURCE_STATE, &state);
} while (state == AL_PLAYING);
}
}
}