You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
6.8 KiB
D
276 lines
6.8 KiB
D
/*
|
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
|
Copyright (C) 2008 Nicolay Korslund
|
|
Email: < korslund@gmail.com >
|
|
WWW: http://openmw.snaptoad.com/
|
|
|
|
This file (sfx.d) is part of the OpenMW package.
|
|
|
|
OpenMW is distributed as free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License
|
|
version 3, as published by the Free Software Foundation.
|
|
|
|
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
|
|
version 3 along with this program. If not, see
|
|
http://www.gnu.org/licenses/ .
|
|
|
|
*/
|
|
|
|
module sound.sfx;
|
|
|
|
import sound.avcodec;
|
|
import sound.audio;
|
|
import sound.al;
|
|
|
|
import core.config;
|
|
import core.resource;
|
|
|
|
import std.string;
|
|
import std.stdio;
|
|
|
|
// Handle for a sound resource. This struct represents one sound
|
|
// effect file (not a music file, those are handled differently
|
|
// because of their size.) From this handle we may get instances,
|
|
// which may be played and managed independently of each other. TODO:
|
|
// Let the resource manager worry about only opening one resource per
|
|
// file, when to kill resources, etc.
|
|
struct SoundFile
|
|
{
|
|
ALuint bID;
|
|
char[] name;
|
|
bool loaded;
|
|
|
|
private int refs;
|
|
|
|
private void fail(char[] msg)
|
|
{
|
|
throw new SoundException(format("SoundFile '%s'", name), msg);
|
|
}
|
|
|
|
// Load a sound resource.
|
|
void load(char[] file)
|
|
{
|
|
name = file;
|
|
|
|
loaded = false;
|
|
refs = 0;
|
|
bID = 0;
|
|
|
|
ubyte[] outData;
|
|
AVFile fileHandle = avc_openAVFile(toStringz(file));
|
|
AVAudio audioHandle = avc_getAVAudioStream(fileHandle, 0);
|
|
|
|
if(!fileHandle)
|
|
{
|
|
writefln("Unable to open %s", file);
|
|
goto errclose;
|
|
}
|
|
if(!audioHandle)
|
|
{
|
|
writefln("Unable to load sound %s", file);
|
|
goto errclose;
|
|
}
|
|
|
|
int ch, bits, rate;
|
|
if(avc_getAVAudioInfo(audioHandle, &rate, &ch, &bits) != 0)
|
|
{
|
|
writefln("Unable to get info for sound %s", file);
|
|
goto errclose;
|
|
}
|
|
|
|
int fmt = 0;
|
|
if(bits == 8)
|
|
{
|
|
if(ch == 1) fmt = AL_FORMAT_MONO8;
|
|
if(ch == 2) fmt = AL_FORMAT_STEREO8;
|
|
if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD8");
|
|
}
|
|
if(bits == 16)
|
|
{
|
|
if(ch == 1) fmt = AL_FORMAT_MONO16;
|
|
if(ch == 2) fmt = AL_FORMAT_STEREO16;
|
|
if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16");
|
|
}
|
|
|
|
if(fmt == 0)
|
|
{
|
|
writefln("Unhandled format (%d channels, %d bits) for sound %s", ch, bits, file);
|
|
goto errclose;
|
|
}
|
|
|
|
int total = 0;
|
|
do
|
|
{
|
|
// Grow by an arbitrary amount. Should be big enough to get
|
|
// the whole sound in one or two iterations, but not allocate
|
|
// too much memory in case it's short
|
|
outData.length = outData.length+8192;
|
|
int length = avc_getAVAudioData(audioHandle, outData.ptr+total, outData.length-total);
|
|
total += length;
|
|
}
|
|
while(total == outData.length);
|
|
|
|
if(total)
|
|
{
|
|
alGenBuffers(1, &bID);
|
|
alBufferData(bID, fmt, outData.ptr, total, rate);
|
|
if(!noALError())
|
|
{
|
|
writefln("Unable to load sound %s", file);
|
|
alDeleteBuffers(1, &bID);
|
|
bID = 0;
|
|
}
|
|
else loaded = true;
|
|
}
|
|
|
|
errclose:
|
|
if(fileHandle) avc_closeAVFile(fileHandle);
|
|
fileHandle = null;
|
|
audioHandle = null;
|
|
}
|
|
|
|
// Get an instance of this resource.
|
|
SoundInstance getInstance()
|
|
{
|
|
SoundInstance si;
|
|
si.owner = this;
|
|
alGenSources(1, &si.inst);
|
|
if(!noALError() || !si.inst)
|
|
fail("Failed to instantiate sound resource");
|
|
|
|
alSourcei(si.inst, AL_BUFFER, cast(ALint)bID);
|
|
if(!noALError())
|
|
{
|
|
alDeleteSources(1, &si.inst);
|
|
fail("Failed to load sound resource");
|
|
}
|
|
refs++;
|
|
|
|
return si;
|
|
}
|
|
|
|
// Return the sound instance when you're done with it
|
|
private void returnInstance(ALuint sid)
|
|
{
|
|
refs--;
|
|
alSourceStop(sid);
|
|
alDeleteSources(1, &sid);
|
|
if(refs == 0) unload();
|
|
}
|
|
|
|
// Unload the resource.
|
|
void unload()
|
|
{
|
|
loaded = false;
|
|
alDeleteBuffers(1, &bID);
|
|
}
|
|
}
|
|
|
|
struct SoundInstance
|
|
{
|
|
ALuint inst;
|
|
ALfloat volume;
|
|
SoundFile *owner;
|
|
|
|
// Return this instance to the owner
|
|
void kill()
|
|
{
|
|
owner.returnInstance(inst);
|
|
owner = null;
|
|
}
|
|
|
|
// Start playing a sound.
|
|
void play()
|
|
{
|
|
alSourcePlay(inst);
|
|
checkALError("playing sound instance");
|
|
}
|
|
|
|
// Go buy a cookie
|
|
void stop()
|
|
{
|
|
alSourceStop(inst);
|
|
checkALError("stopping sound instance");
|
|
}
|
|
|
|
// Set parameters such as max volume and range
|
|
void setParams(float volume, float minRange, float maxRange, bool repeat=false)
|
|
in
|
|
{
|
|
assert(volume >= 0 && volume <= 1.0, "Volume out of range");
|
|
}
|
|
body
|
|
{
|
|
this.volume = volume;
|
|
alSourcef(inst, AL_GAIN, 0);
|
|
alSourcef(inst, AL_REFERENCE_DISTANCE, minRange);
|
|
alSourcef(inst, AL_MAX_DISTANCE, maxRange);
|
|
alSourcei(inst, AL_LOOPING, repeat ? AL_TRUE : AL_FALSE);
|
|
alSourcePlay(inst);
|
|
checkALError("setting sound parameters");
|
|
}
|
|
|
|
// Set 3D position of sounds. Need to convert from app's world coords to
|
|
// standard left-hand coords
|
|
void setPos(float x, float y, float z)
|
|
{
|
|
alSource3f(inst, AL_POSITION, x, z, -y);
|
|
checkALError("setting sound position");
|
|
}
|
|
|
|
// TODO: This should probably be merged in setPos. All active sounds
|
|
// should respecify their position after the listener does, which would
|
|
// be ideal for keeping moving sounds moving while handling distance
|
|
// culling for moving and stationary sounds
|
|
void updateSound()
|
|
{
|
|
if(owner is null) return;
|
|
|
|
ALfloat lp[3];
|
|
ALfloat p[3];
|
|
ALfloat dist;
|
|
alGetSourcef(inst, AL_MAX_DISTANCE, &dist);
|
|
alGetSourcefv(inst, AL_POSITION, p.ptr);
|
|
alGetListenerfv(AL_POSITION, lp.ptr);
|
|
if(noALError())
|
|
{
|
|
p[0] -= lp[0];
|
|
p[1] -= lp[1];
|
|
p[2] -= lp[2];
|
|
|
|
// If the sound is out of range, mute it
|
|
ALfloat d2 = p[0]*p[0] + p[1]*p[1] + p[2]*p[2];
|
|
if(d2 > dist*dist)
|
|
{
|
|
alSourcef(inst, AL_GAIN, 0);
|
|
|
|
// If the sound is really out of range, we should reclaim
|
|
// it. This is not implemented yet.
|
|
}
|
|
else
|
|
alSourcef(inst, AL_GAIN, volume);
|
|
}
|
|
}
|
|
|
|
static void setPlayerPos(float x, float y, float z,
|
|
float frontx, float fronty, float frontz,
|
|
float upx, float upy, float upz)
|
|
{
|
|
ALfloat orient[6];
|
|
orient[0] = frontx;
|
|
orient[1] = frontz;
|
|
orient[2] =-fronty;
|
|
orient[3] = upx;
|
|
orient[4] = upz;
|
|
orient[5] =-upy;
|
|
alListener3f(AL_POSITION, x, z, -y);
|
|
alListenerfv(AL_ORIENTATION, orient.ptr);
|
|
checkALError("setting player position");
|
|
}
|
|
}
|