forked from oerkki/voxelands
677 lines
15 KiB
C
677 lines
15 KiB
C
/************************************************************************
|
|
* sound.c
|
|
* voxelands - 3d voxel world sandbox game
|
|
* Copyright (C) Lisa 'darkrose' Milne 2017 <lisa@ltmnet.com>
|
|
*
|
|
* 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, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>
|
|
************************************************************************/
|
|
|
|
#include "common.h"
|
|
#include "list.h"
|
|
#include "file.h"
|
|
#define _VL_SOUND_EXPOSE_INTERNAL
|
|
#include "sound.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static struct {
|
|
int init;
|
|
float volume;
|
|
struct {
|
|
float volume;
|
|
float fade;
|
|
float fadev;
|
|
sound_t *sounds;
|
|
sound_instance_t *playing;
|
|
} effects;
|
|
struct {
|
|
float volume;
|
|
sound_t *sounds;
|
|
sound_instance_t *playing;
|
|
} music;
|
|
ALCdevice *device;
|
|
ALCcontext *context;
|
|
} sound_data = {
|
|
0,
|
|
0.5,
|
|
{
|
|
0.5,
|
|
-1.0,
|
|
1.0,
|
|
NULL,
|
|
NULL
|
|
},
|
|
{
|
|
0.5,
|
|
NULL,
|
|
NULL
|
|
},
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
/* initialise sound */
|
|
int sound_init()
|
|
{
|
|
/* initialise audio */
|
|
sound_data.device = alcOpenDevice(NULL);
|
|
if (!sound_data.device)
|
|
return 1;
|
|
|
|
sound_data.context = alcCreateContext(sound_data.device, NULL);
|
|
|
|
if (!sound_data.context) {
|
|
alcCloseDevice(sound_data.device);
|
|
return 1;
|
|
}
|
|
|
|
if (!alcMakeContextCurrent(sound_data.context) || (alcGetError(sound_data.device) != ALC_NO_ERROR)) {
|
|
alcDestroyContext(sound_data.context);
|
|
alcCloseDevice(sound_data.device);
|
|
return 1;
|
|
}
|
|
|
|
alDistanceModel(AL_EXPONENT_DISTANCE);
|
|
alListenerf(AL_GAIN, 1.0);
|
|
|
|
#if USE_MUMBLE == 1
|
|
if (config_get_bool("client.sound.mumble"))
|
|
sound_mumble_init();
|
|
#endif
|
|
|
|
sound_data.init = 1;
|
|
|
|
/* sounds must be mono sounds, stereo will not work right!
|
|
* exceptions: background music */
|
|
|
|
/* walking */
|
|
/* CMT_DIRT */
|
|
sound_load_effect("step_dirt.1.ogg","dirt-step-left");
|
|
sound_load_effect("step_dirt.2.ogg","dirt-step-right");
|
|
/* CMT_STONE */
|
|
sound_load_effect("step_stone.1.ogg","stone-step-left");
|
|
sound_load_effect("step_stone.2.ogg","stone-step-right");
|
|
sound_load_effect("step_stone.3.ogg","stone-step-left");
|
|
sound_load_effect("step_stone.4.ogg","stone-step-right");
|
|
/* CMT_PLANT */
|
|
sound_load_effect("step_plant.1.ogg","plant-step-left");
|
|
sound_load_effect("step_plant.2.ogg","plant-step-right");
|
|
/* CMT_LIQUID */
|
|
sound_load_effect("step_liquid.1.ogg","liquid-step-left");
|
|
sound_load_effect("step_liquid.2.ogg","liquid-step-right");
|
|
/* CMT_WOOD */
|
|
sound_load_effect("step_wood.1.ogg","wood-step-left");
|
|
sound_load_effect("step_wood.2.ogg","wood-step-right");
|
|
sound_load_effect("step_wood.3.ogg","wood-step-left");
|
|
sound_load_effect("step_wood.4.ogg","wood-step-right");
|
|
/* CMT_GLASS */
|
|
sound_load_effect("step_glass.1.ogg","glass-step-left");
|
|
sound_load_effect("step_glass.1.ogg","glass-step-right");
|
|
/* special for grass */
|
|
sound_load_effect("step_grass.1.ogg","grass-step-left");
|
|
sound_load_effect("step_grass.2.ogg","grass-step-right");
|
|
|
|
/* digging */
|
|
/* CMT_DIRT */
|
|
sound_load_effect("dig_dirt.1.ogg","dirt-dig");
|
|
/* CMT_STONE */
|
|
sound_load_effect("dig_stone.1.ogg","stone-dig");
|
|
/* CMT_PLANT */
|
|
sound_load_effect("dig_plant.1.ogg","plant-dig");
|
|
/* CMT_LIQUID */
|
|
sound_load_effect("dig_liquid.1.ogg","liquid-dig");
|
|
/* CMT_WOOD */
|
|
sound_load_effect("dig_wood.1.ogg","wood-dig");
|
|
/* CMT_GLASS */
|
|
sound_load_effect("dig_glass.1.ogg","glass-dig");
|
|
/* mobs */
|
|
sound_load_effect("dig_mob.ogg","mob-dig");
|
|
/* miss */
|
|
sound_load_effect("dig_miss.ogg","miss-dig");
|
|
|
|
/* placing */
|
|
sound_load_effect("place_node.1.ogg","place");
|
|
sound_load_effect("place_node.2.ogg","place");
|
|
sound_load_effect("place_node.3.ogg","place");
|
|
/* CMT_DIRT */
|
|
/* CMT_STONE */
|
|
/* CMT_PLANT */
|
|
/* CMT_LIQUID */
|
|
sound_load_effect("place_liquid.1.ogg","liquid-place");
|
|
/* CMT_WOOD */
|
|
/* CMT_GLASS */
|
|
|
|
/* open formspec */
|
|
sound_load_effect("open_menu.ogg","open-menu");
|
|
sound_load_effect("open_book.ogg","open-book");
|
|
sound_load_effect("open_chest.ogg","open-chest");
|
|
|
|
/* environment and node sounds */
|
|
sound_load_effect("env_piston.ogg","env-piston");
|
|
sound_load_effect("env_dooropen.ogg","env-dooropen");
|
|
sound_load_effect("env_doorclose.ogg","env-doorclose");
|
|
sound_load_effect("env_fire.ogg","env-fire");
|
|
sound_load_effect("env_lava.ogg","env-lava");
|
|
sound_load_effect("env_water.ogg","env-water");
|
|
sound_load_effect("env_steam.ogg","env-steam");
|
|
sound_load_effect("env_tnt.ogg","env-tnt");
|
|
sound_load_effect("env_teleport.ogg","env-teleport");
|
|
|
|
/* mobs */
|
|
sound_load_effect("mob_oerkki_spawn.ogg","mob-oerkki-spawn");
|
|
sound_load_effect("mob_wolf_hit.ogg","mob-wolf-hit");
|
|
sound_load_effect("mob_wolf_spawn.ogg","mob-wolf-spawn");
|
|
sound_load_effect("mob_sheep_env.ogg","mob-sheep-env");
|
|
sound_load_effect("mob_ducksheep_env.ogg","mob-ducksheep-env");
|
|
sound_load_effect("mob_deer_env.ogg","mob-deer-env");
|
|
|
|
/* special */
|
|
sound_load_effect("wield_item.ogg","wield");
|
|
sound_load_effect("low_energy_F.ogg","low-energy-F");
|
|
sound_load_effect("low_energy_M.ogg","low-energy-M");
|
|
sound_load_effect("player_hurt_F.ogg","player-hurt-F");
|
|
sound_load_effect("player_hurt_M.ogg","player-hurt-M");
|
|
|
|
/* use */
|
|
sound_load_effect("use_eat.ogg","use-eat");
|
|
sound_load_effect("use_drink.ogg","use-drink");
|
|
|
|
/* menu backgrounds */
|
|
sound_load_music("bg_mainmenu.ogg","bg-mainmenu");
|
|
sound_load_music("bg_charcreator.ogg","bg-charcreator");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* exit sound */
|
|
void sound_exit()
|
|
{
|
|
if (!sound_data.init)
|
|
return;
|
|
|
|
sound_stop(0);
|
|
alcMakeContextCurrent(NULL);
|
|
alcDestroyContext(sound_data.context);
|
|
alcCloseDevice(sound_data.device);
|
|
|
|
sound_data.init = 0;
|
|
|
|
/* TODO: unload sounds */
|
|
}
|
|
|
|
/* do stuff */
|
|
void sound_process(float dtime)
|
|
{
|
|
ALint state;
|
|
float v;
|
|
sound_instance_t *d;
|
|
sound_instance_t *i;
|
|
|
|
/* factor = dtime / fade */
|
|
|
|
if (sound_data.effects.fade > -0.5) {
|
|
v = dtime/sound_data.effects.fade;
|
|
sound_data.effects.fadev -= v*sound_data.effects.volume*sound_data.volume;
|
|
if (sound_data.effects.fadev < 0.01)
|
|
sound_stop_effects(0);
|
|
}
|
|
i = sound_data.effects.playing;
|
|
while (i) {
|
|
alGetSourcei(i->id, AL_SOURCE_STATE, &state);
|
|
if (state != AL_PLAYING) {
|
|
d = i;
|
|
i = i->next;
|
|
sound_data.effects.playing = list_remove(&sound_data.effects.playing,d);
|
|
alDeleteSources(1, &d->id);
|
|
free(d);
|
|
continue;
|
|
}
|
|
if (i->fade > -0.5) {
|
|
if (i->fade > 0.01 && i->volume > 0.01) {
|
|
v = dtime/i->fade;
|
|
i->volume -= v*sound_data.music.volume*sound_data.volume;
|
|
if (i->volume > 0.01)
|
|
alSourcef(i->id, AL_GAIN, i->volume);
|
|
}else{
|
|
d = i;
|
|
i = i->next;
|
|
sound_data.effects.playing = list_remove(&sound_data.effects.playing,d);
|
|
alDeleteSources(1, &d->id);
|
|
free(d);
|
|
continue;
|
|
}
|
|
}
|
|
if (sound_data.effects.fade > -0.5)
|
|
alSourcef(i->id, AL_GAIN, sound_data.effects.fadev);
|
|
i = i->next;
|
|
}
|
|
}
|
|
|
|
/* */
|
|
void sound_step(float dtime, v3_t *pos, v3_t *at, v3_t *up)
|
|
{
|
|
float orientation[6] = {0.0,0.0,0.0,0.0,0.0,0.0};
|
|
|
|
sound_process(dtime);
|
|
|
|
if (pos) {
|
|
alListener3f(AL_POSITION, pos->x,pos->y,pos->z);
|
|
}else{
|
|
alListener3f(AL_POSITION, 0.0,0.0,0.0);
|
|
}
|
|
|
|
alListener3f(AL_VELOCITY, 0.0,0.0,0.0);
|
|
|
|
if (at) {
|
|
orientation[0] = at->x;
|
|
orientation[1] = at->y;
|
|
orientation[2] = at->z;
|
|
}
|
|
|
|
if (up) {
|
|
orientation[3] = -up->x;
|
|
orientation[4] = -up->y;
|
|
orientation[5] = -up->z;
|
|
}
|
|
|
|
alListenerfv(AL_ORIENTATION, orientation);
|
|
|
|
#if USE_MUMBLE == 1
|
|
if (pos && at && up)
|
|
sound_mumble_step(dtime,pos,at,up);
|
|
#endif
|
|
}
|
|
|
|
/* get/set sound effect volume */
|
|
float sound_volume_effects(float v)
|
|
{
|
|
if (v > -0.5) {
|
|
if (v > 1.0)
|
|
v = 1.0;
|
|
sound_data.effects.volume = v;
|
|
|
|
if (sound_data.init) {
|
|
float ev;
|
|
sound_instance_t *i = sound_data.effects.playing;
|
|
|
|
ev = v*sound_data.volume;
|
|
|
|
while (i) {
|
|
alSourcef(i->id, AL_GAIN, ev);
|
|
i = i->next;
|
|
}
|
|
}
|
|
}
|
|
return sound_data.effects.volume;
|
|
}
|
|
|
|
/* get/set sound music volume */
|
|
float sound_volume_music(float v)
|
|
{
|
|
if (v > -0.5) {
|
|
if (v > 1.0)
|
|
v = 1.0;
|
|
sound_data.music.volume = v;
|
|
|
|
if (sound_data.init && sound_data.music.playing) {
|
|
float ev;
|
|
|
|
ev = v*sound_data.volume;
|
|
|
|
alSourcef(sound_data.music.playing->id, AL_GAIN, ev);
|
|
|
|
}
|
|
}
|
|
return sound_data.music.volume;
|
|
}
|
|
|
|
/* get/set sound master volume */
|
|
float sound_volume_master(float v)
|
|
{
|
|
if (v > -0.5) {
|
|
if (v > 1.0)
|
|
v = 1.0;
|
|
sound_data.volume = v;
|
|
sound_volume_effects(sound_data.effects.volume);
|
|
sound_volume_music(sound_data.music.volume);
|
|
}
|
|
return sound_data.volume;
|
|
}
|
|
|
|
/* load a sound identified by token from a file */
|
|
static sound_t *sound_load(char* file, char* token)
|
|
{
|
|
file_t *f;
|
|
sound_t *e = malloc(sizeof(sound_t));
|
|
e->file = strdup(file);
|
|
e->token = strdup(token);
|
|
e->data = NULL;
|
|
e->d_len = 0;
|
|
|
|
f = file_load("sound",file);
|
|
|
|
if (sound_is_ogg(f)) {
|
|
if (sound_load_ogg(f,e))
|
|
goto sound_load_fail;
|
|
}else if (sound_is_wav(f)) {
|
|
if (sound_load_wav(f,e))
|
|
goto sound_load_fail;
|
|
}else{
|
|
goto sound_load_fail;
|
|
}
|
|
|
|
file_free(f);
|
|
|
|
return e;
|
|
|
|
sound_load_fail:
|
|
file_free(f);
|
|
free(e->file);
|
|
free(e->token);
|
|
if (e->data)
|
|
free(e->data);
|
|
free(e);
|
|
|
|
vlprintf(CN_ERROR, "failed to load sound data for file '%s'",file);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* load a sound effect identifed by token */
|
|
int sound_load_effect(char* file, char* token)
|
|
{
|
|
sound_t *e = sound_load(file,token);
|
|
if (!e)
|
|
return 1;
|
|
|
|
sound_data.effects.sounds = list_push(&sound_data.effects.sounds,e);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* load music identifed by token */
|
|
int sound_load_music(char* file, char* token)
|
|
{
|
|
sound_t *e = sound_load(file,token);
|
|
if (!e)
|
|
return 1;
|
|
|
|
sound_data.music.sounds = list_push(&sound_data.music.sounds,e);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* unload a sound effect */
|
|
void sound_free_effect(char* token)
|
|
{
|
|
sound_t *e = sound_data.effects.sounds;
|
|
|
|
if (!e)
|
|
return;
|
|
|
|
while (e) {
|
|
if (!strcmp(e->token,token))
|
|
break;
|
|
e = e->next;
|
|
}
|
|
|
|
if (!e)
|
|
return;
|
|
|
|
sound_data.effects.sounds = list_remove(&sound_data.effects.sounds,e);
|
|
|
|
alDeleteBuffers(1,&e->id);
|
|
|
|
free(e->data);
|
|
free(e->file);
|
|
free(e->token);
|
|
free(e);
|
|
}
|
|
|
|
/* unload a music */
|
|
void sound_free_music(char* token)
|
|
{
|
|
sound_t *e = sound_data.music.sounds;
|
|
if (!e)
|
|
return;
|
|
|
|
while (e) {
|
|
if (!strcmp(e->token,token))
|
|
break;
|
|
e = e->next;
|
|
}
|
|
|
|
if (!e)
|
|
return;
|
|
|
|
sound_data.music.sounds = list_remove(&sound_data.music.sounds,e);
|
|
|
|
alDeleteBuffers(1,&e->id);
|
|
|
|
free(e->data);
|
|
free(e->file);
|
|
free(e->token);
|
|
free(e);
|
|
}
|
|
|
|
/* play sound effect */
|
|
uint32_t sound_play_effect(char* token, float volume, uint8_t loop, v3_t *pos)
|
|
{
|
|
sound_instance_t *i;
|
|
sound_t *e = sound_data.effects.sounds;
|
|
if (!sound_data.init || !e)
|
|
return 0;
|
|
|
|
while (e) {
|
|
if (!strcmp(e->token,token))
|
|
break;
|
|
e = e->next;
|
|
}
|
|
|
|
if (!e)
|
|
return 0;
|
|
|
|
i = malloc(sizeof(sound_instance_t));
|
|
if (!i)
|
|
return 0;
|
|
|
|
alGenSources(1, &i->id);
|
|
alSourcei(i->id, AL_BUFFER, e->id);
|
|
|
|
i->volume = volume;
|
|
i->fade = -1.0;
|
|
|
|
if (pos) {
|
|
alSourcei(i->id, AL_SOURCE_RELATIVE, AL_FALSE);
|
|
alSource3f(i->id, AL_POSITION, pos->x, pos->y, pos->z);
|
|
alSourcef(i->id, AL_REFERENCE_DISTANCE, 30.0);
|
|
}else{
|
|
alSourcei(i->id, AL_SOURCE_RELATIVE, AL_TRUE);
|
|
alSource3f(i->id, AL_POSITION, 0, 0, 0);
|
|
}
|
|
|
|
alSource3f(i->id, AL_VELOCITY, 0, 0, 0);
|
|
if (loop) {
|
|
alSourcei(i->id, AL_LOOPING, AL_TRUE);
|
|
}else{
|
|
alSourcei(i->id, AL_LOOPING, AL_FALSE);
|
|
}
|
|
alSourcef(i->id, AL_GAIN, sound_data.effects.volume*sound_data.volume*volume);
|
|
alSourcePlay(i->id);
|
|
|
|
if (alGetError() != AL_NO_ERROR) {
|
|
alDeleteSources(1, &i->id);
|
|
free(i);
|
|
return 0;
|
|
}
|
|
|
|
sound_data.effects.playing = list_push(&sound_data.effects.playing,i);
|
|
|
|
return i->id;
|
|
}
|
|
|
|
/* play music */
|
|
uint32_t sound_play_music(char* token, float volume, uint8_t loop)
|
|
{
|
|
sound_t *e = sound_data.music.sounds;
|
|
if (!sound_data.init || !e)
|
|
return 0;
|
|
|
|
while (e) {
|
|
if (!strcmp(e->token,token))
|
|
break;
|
|
e = e->next;
|
|
}
|
|
|
|
if (!e)
|
|
return 0;
|
|
|
|
if (sound_data.music.playing) {
|
|
sound_data.effects.playing->fade = 1.0;
|
|
sound_data.effects.playing = list_push(&sound_data.effects.playing,sound_data.music.playing);
|
|
}
|
|
|
|
sound_data.music.playing = malloc(sizeof(sound_instance_t));
|
|
if (!sound_data.music.playing)
|
|
return 0;
|
|
|
|
alGenSources(1, &sound_data.music.playing->id);
|
|
alSourcei(sound_data.music.playing->id, AL_BUFFER, e->id);
|
|
|
|
sound_data.music.playing->volume = volume;
|
|
sound_data.music.playing->fade = -1.0;
|
|
|
|
alSourcei(sound_data.music.playing->id, AL_SOURCE_RELATIVE, AL_TRUE);
|
|
alSource3f(sound_data.music.playing->id, AL_POSITION, 0, 0, 0);
|
|
alSource3f(sound_data.music.playing->id, AL_VELOCITY, 0, 0, 0);
|
|
if (loop) {
|
|
alSourcei(sound_data.music.playing->id, AL_LOOPING, AL_TRUE);
|
|
}else{
|
|
alSourcei(sound_data.music.playing->id, AL_LOOPING, AL_FALSE);
|
|
}
|
|
alSourcef(sound_data.music.playing->id, AL_GAIN, sound_data.music.volume*sound_data.volume*volume);
|
|
alSourcePlay(sound_data.music.playing->id);
|
|
|
|
if (alGetError() != AL_NO_ERROR) {
|
|
alDeleteSources(1, &sound_data.music.playing->id);
|
|
free(sound_data.music.playing);
|
|
return 0;
|
|
}
|
|
|
|
return sound_data.music.playing->id;
|
|
}
|
|
|
|
/* stop sound effects, optionally fading out in fade seconds */
|
|
void sound_stop_effects(int fade)
|
|
{
|
|
if (fade) {
|
|
sound_data.effects.fade = fade;
|
|
sound_data.effects.fadev = sound_data.effects.volume;
|
|
}else{
|
|
sound_instance_t *i = sound_data.effects.playing;
|
|
while (i) {
|
|
alSourceStop(i->id);
|
|
i = i->next;
|
|
}
|
|
sound_data.effects.fade = -1.0;
|
|
}
|
|
}
|
|
|
|
/* stop music, optionally fading out in fade seconds */
|
|
void sound_stop_music(int fade)
|
|
{
|
|
if (!sound_data.music.playing)
|
|
return;
|
|
|
|
if (!fade)
|
|
alSourceStop(sound_data.music.playing->id);
|
|
|
|
sound_data.music.playing->fade = fade;
|
|
sound_data.music.playing->volume = sound_data.music.volume;
|
|
sound_data.effects.playing = list_push(&sound_data.effects.playing,sound_data.music.playing);
|
|
sound_data.music.playing = NULL;
|
|
|
|
}
|
|
|
|
void sound_stop_single(uint32_t id)
|
|
{
|
|
sound_instance_t *i = sound_data.effects.playing;
|
|
while (i) {
|
|
if (i->id == id) {
|
|
alSourceStop(i->id);
|
|
break;
|
|
}
|
|
i = i->next;
|
|
}
|
|
}
|
|
|
|
int sound_exists(uint32_t id)
|
|
{
|
|
sound_instance_t *i = sound_data.effects.playing;
|
|
while (i) {
|
|
if (i->id == id)
|
|
return 1;
|
|
i = i->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* stop all sounds, optionally fading out in fade seconds */
|
|
void sound_stop(int fade)
|
|
{
|
|
sound_stop_music(fade);
|
|
sound_stop_effects(fade);
|
|
}
|
|
|
|
/* command setter for sound effects volume */
|
|
int sound_effects_setter(char* value)
|
|
{
|
|
float vf;
|
|
int v = strtol(value,NULL,10);
|
|
vf = (float)v/100.0;
|
|
sound_volume_effects(vf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* command setter for music volume */
|
|
int sound_music_setter(char* value)
|
|
{
|
|
float vf;
|
|
int v = strtol(value,NULL,10);
|
|
vf = (float)v/100.0;
|
|
sound_volume_music(vf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* command setter for master volume */
|
|
int sound_master_setter(char* value)
|
|
{
|
|
int v = strtol(value,NULL,10);
|
|
if (v < 0)
|
|
v = 0;
|
|
if (v > 100)
|
|
v = 100;
|
|
|
|
sound_data.volume = (float)v/100.0;
|
|
|
|
sound_volume_effects(sound_data.effects.volume);
|
|
sound_volume_music(sound_data.music.volume);
|
|
|
|
return 0;
|
|
}
|