/* 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 "glob.h"
#include "sound.h"
#include "music.h"
#include "memory.h" // M_lock M_unlock
#include "files.h" // F_findres
#include "error.h"
#include "common/endianness.h"
#include "SDL.h"
#include "SDL_mixer.h"
#include
#include
#define TAG_MIX1 0x4d495831
#pragma pack(1)
typedef struct dmi {
word len; // length [bytes]
word rate; // freq [Hz]
word lstart; // loop start offset [bytes]
word llen; // loop length [bytes]
byte data[]; // sound data
} dmi;
#pragma pack()
typedef struct sdlmixer_snd {
snd_t base;
Mix_Chunk *c;
} sdlmixer_snd;
static short snd_vol;
static int devfreq = MIX_DEFAULT_FREQUENCY;
static Uint32 devformat = AUDIO_S16SYS; // MIX_DEFAULT_FORMAT
static int devchannels = 1; // MIX_DEFAULT_CHANNELS;
static int devchunksize = 1024;
static int devchunkchannels = 8;
static int devinit;
/* 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;
}
void S_init (void) {
assert(devinit == 0);
logo("S_init: initialize sound\n");
if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) {
if (Mix_OpenAudio(devfreq, devformat, devchannels, devchunksize) == 0) {
Mix_AllocateChannels(devchunkchannels);
devinit = 1;
} else {
logo("S_init: Mix_OpenAudio: %s\n", Mix_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
} else {
logo("S_init: SDL_InitSubSytem: %s\n", SDL_GetError());
}
}
static Mix_Chunk *convert_this (int rate, int sign, const Uint8 *buf, int len) {
SDL_AudioCVT cvt;
Mix_Chunk *c = NULL;
if (SDL_BuildAudioCVT(&cvt, sign ? AUDIO_S8 : AUDIO_U8, 1, rate, devformat, devchannels, devfreq) != -1) {
int maxlen = len * cvt.len_mult;
Uint8 *maxbuf = malloc(maxlen);
if (maxbuf != NULL) {
memcpy(maxbuf, buf, len);
cvt.buf = maxbuf;
cvt.len = len;
if (SDL_ConvertAudio(&cvt) == 0) {
c = malloc(sizeof(Mix_Chunk));
if (c != NULL) {
c->allocated = 0;
c->abuf = maxbuf;
c->alen = len * cvt.len_ratio;
c->volume = MIX_MAX_VOLUME;
} else {
free(maxbuf);
}
} else {
free(maxbuf);
}
}
}
return c;
}
static sdlmixer_snd *new_sdlmixer_snd (const void *data, dword len, dword rate, dword lstart, dword llen, int sign) {
Mix_Chunk *c = NULL;
sdlmixer_snd *snd = NULL;
c = convert_this(rate, sign, data, len);
if (c != NULL) {
snd = malloc(sizeof(sdlmixer_snd));
if (snd != NULL) {
snd->base.tag = TAG_MIX1;
snd->c = c;
} else {
free(c->abuf);
free(c);
}
}
return snd;
}
snd_t *S_get (int id) {
void *handle;
sdlmixer_snd *snd = NULL;
if (devinit) {
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_sdlmixer_snd(data, len, rate, lstart, llen, sign);
M_unlock(handle);
}
}
return (snd_t*)snd;
}
snd_t *S_load (const char name[8]) {
int id = F_findres(name);
return S_get(id);
}
void S_free (snd_t *s) {
int i;
sdlmixer_snd *snd = (sdlmixer_snd*)s;
if (snd != NULL) {
assert(snd->base.tag == TAG_MIX1);
if (devinit) {
for (i = 0; i < devchunkchannels; i++) {
if (Mix_GetChunk(i) == snd->c) {
Mix_HaltChannel(i);
}
}
}
free(snd->c->abuf);
free(snd->c);
free(snd);
}
}
short S_play (snd_t *s, short c, short v) {
short channel = 0;
sdlmixer_snd *snd = (sdlmixer_snd*)s;
assert(c >= 0 && c <= 8);
assert(v >= 0 && v <= 255);
if (devinit && snd != NULL) {
assert(snd->base.tag == TAG_MIX1);
// TODO care about global volume level
snd->c->volume = v * MIX_MAX_VOLUME / 255;
channel = Mix_PlayChannel(c <= 0 ? -1 : c - 1, snd->c, 0);
channel = channel == -1 ? 0 : channel + 1;
}
return channel;
}
void S_stop (short c) {
assert(c >= 0 && c <= 8);
if (devinit && c > 0) {
Mix_HaltChannel(c - 1);
}
}
void S_volume (int v) {
snd_vol = min(max(v, 0), 128);
if (devinit) {
// TODO change relativelly for every channel
Mix_Volume(-1, v * MIX_MAX_VOLUME / 128);
}
}
void S_wait (void) {
if (devinit) {
while (Mix_Playing(-1) > 0) {
SDL_Delay(10);
}
}
}
void S_done (void) {
if (devinit) {
// TODO free memory
Mix_AllocateChannels(0);
Mix_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
}