/*
===========================================================================
ARX FATALIS GPL Source Code
Copyright (C) 1999-2010 Arkane Studios SA, a ZeniMax Media company.
This file is part of the Arx Fatalis GPL Source Code ('Arx Fatalis Source Code').
Arx Fatalis Source Code 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.
Arx Fatalis Source Code 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 Arx Fatalis Source Code. If not, see
.
In addition, the Arx Fatalis Source Code is also subject to certain additional terms. You should have received a copy of these
additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Arx
Fatalis Source Code. If not, please request a copy in writing from Arkane Studios at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing Arkane Studios, c/o
ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include
#include
#include "Athena_Global.h"
#include "Athena_Instance.h"
#include "Athena_Stream.h"
#define _CRTDBG_MAP_ALLOC
#include
namespace ATHENA
{
// Status flags //
static enum ATHENAInstance
{
IS_IDLED = 0x00000001,
IS_PAUSED = 0x00000002,
IS_TOOFAR = 0x00000004
};
static aalVoid InstanceDebugLog(Instance * instance, const char * _text)
{
char text[256];
aalULong _time(BytesToUnits(instance->time, instance->sample->format, AAL_UNIT_MS));
sprintf(text, "[%03u - %03u][%02u\" %02u' %03u][%02u][%s]\n",
GetSampleID(instance->id), GetInstanceID(instance->id),
_time / 60000, _time % 60000 / 1000, _time % 1000,
instance->loop, _text);
DebugLog(text);
}
///////////////////////////////////////////////////////////////////////////////
// //
// Constructor and destructor //
// //
///////////////////////////////////////////////////////////////////////////////
Instance::Instance() :
sample(NULL),
status(0),
loop(0), time(0),
stream(NULL), size(0), read(0), write(0),
lpdsb(NULL), lpds3db(NULL), lpeax(NULL)
{
}
extern long NBREVERB;
extern char szT[1024];
extern bool bLog;
Instance::~Instance()
{
Clean();
}
///////////////////////////////////////////////////////////////////////////////
// //
// Setup //
// //
///////////////////////////////////////////////////////////////////////////////
aalError Instance::Init(Sample * __sample, const aalChannel & _channel)
{
DSBUFFERDESC _desc;
WAVEFORMATEX _format;
aalUBool streaming(AAL_UFALSE);
Clean();
sample = __sample;
sample->Catch();
channel = _channel;
memset(&_desc, 0, sizeof(DSBUFFERDESC));
_desc.dwSize = sizeof(DSBUFFERDESC);
_desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
if (channel.flags & AAL_FLAG_VOLUME) _desc.dwFlags |= DSBCAPS_CTRLVOLUME;
else if (_mixer[channel.mixer]->flags & AAL_FLAG_VOLUME)
{
channel.flags |= AAL_FLAG_VOLUME;
channel.volume = 1.0F;
}
if (channel.flags & AAL_FLAG_PITCH) _desc.dwFlags |= DSBCAPS_CTRLFREQUENCY;
else if (_mixer[channel.mixer]->flags & AAL_FLAG_PITCH)
{
channel.flags |= AAL_FLAG_PITCH;
channel.pitch = 1.0F;
}
if (channel.flags & AAL_FLAG_PAN) _desc.dwFlags |= DSBCAPS_CTRLPAN;
else if (_mixer[channel.mixer]->flags & AAL_FLAG_PAN)
{
channel.flags |= AAL_FLAG_PAN;
channel.pan = 0.0F;
}
if (channel.flags & FLAG_ANY_3D_FX)
{
_desc.dwFlags |= DSBCAPS_CTRL3D;
_desc.dwFlags &= ~DSBCAPS_CTRLPAN;
channel.flags &= ~AAL_FLAG_PAN;
}
if (channel.flags & AAL_FLAG_BACKGROUND) _desc.dwFlags |= DSBCAPS_GLOBALFOCUS;
_desc.lpwfxFormat = &_format;
_format.nSamplesPerSec = sample->format.frequency;
_format.wBitsPerSample = (aalUWord)sample->format.quality;
_format.nChannels = (aalUWord)sample->format.channels;
_format.wFormatTag = WAVE_FORMAT_PCM;
_format.nBlockAlign = (aalUWord)(sample->format.channels * (sample->format.quality >> 3));
_format.nAvgBytesPerSec = _format.nBlockAlign * sample->format.frequency;
_format.cbSize = 0;
// Get buffer size and determine if streaming must be enable
if (sample->length > stream_limit_bytes)
size = stream_limit_bytes, streaming = AAL_UTRUE;
else
size = sample->length;
_desc.dwBufferBytes = size;
if (device->CreateSoundBuffer(&_desc, &lpdsb, NULL)) return AAL_ERROR_SYSTEM;
SetVolume(channel.volume);
SetPitch(channel.pitch);
// Create 3D interface if required
if (channel.flags & FLAG_ANY_3D_FX)
{
if (lpdsb->QueryInterface(IID_IDirectSound3DBuffer, (aalVoid **)&lpds3db))
return AAL_ERROR_SYSTEM;
if (channel.flags & AAL_FLAG_RELATIVE &&
lpds3db->SetMode(DS3DMODE_HEADRELATIVE, DS3D_DEFERRED))
return AAL_ERROR_SYSTEM;
SetPosition(channel.position);
SetVelocity(channel.velocity);
SetDirection(channel.direction);
SetCone(channel.cone);
SetFalloff(channel.falloff);
if (is_reverb_present)
{
lpds3db->QueryInterface(IID_IKsPropertySet, (aalVoid **)&lpeax);
aalSLong value(0);
lpeax->Set(DSPROPSETID_EAX_BufferProperties,
DSPROPERTY_EAXBUFFER_FLAGS | DSPROPERTY_EAXBUFFER_DEFERRED,
NULL, 0, &value, sizeof(aalSLong));
if (!environment || !(channel.flags & AAL_FLAG_REVERBERATION))
{
value = -10000;
lpeax->Set(DSPROPSETID_EAX_BufferProperties,
DSPROPERTY_EAXBUFFER_ROOM | DSPROPERTY_EAXBUFFER_DEFERRED,
NULL, 0, &value, sizeof(aalSLong));
lpeax->Set(DSPROPSETID_EAX_BufferProperties,
DSPROPERTY_EAXBUFFER_ROOMHF | DSPROPERTY_EAXBUFFER_DEFERRED,
NULL, 0, &value, sizeof(aalSLong));
}
}
}
else SetPan(channel.pan);
stream = CreateStream(sample->name);
if (!stream) return AAL_ERROR_FILEIO;
//Load sample data if not streamed
if (!streaming)
{
aalULong cur0, cur1;
aalVoid * ptr0, *ptr1;
if (stream->SetPosition(0)) return AAL_ERROR_SYSTEM;
if (lpdsb->Lock(0, 0, &ptr0, &cur0, &ptr1, &cur1, DSBLOCK_ENTIREBUFFER)) return AAL_ERROR_SYSTEM;
stream->Read(ptr0, size, write);
if (lpdsb->Unlock(ptr0, cur0, ptr1, cur1)) return AAL_ERROR_SYSTEM;
if (write != size)
return AAL_ERROR_SYSTEM;
DeleteStream(stream);
}
return AAL_OK;
}
aalError Instance::Init(Instance * instance, const aalChannel & _channel)
{
if (instance->stream || _channel.flags ^ instance->channel.flags)
return Init(instance->sample, _channel);
Clean();
sample = instance->sample;
sample->Catch();
channel = _channel;
size = instance->size;
if (device->DuplicateSoundBuffer(instance->lpdsb, &lpdsb))
return AAL_ERROR_SYSTEM;
SetVolume(channel.volume);
SetPitch(channel.pitch);
//Create 3D interface if required
if (channel.flags & FLAG_ANY_3D_FX)
{
if (lpdsb->QueryInterface(IID_IDirectSound3DBuffer, (aalVoid **)&lpds3db))
return AAL_ERROR_SYSTEM;
if (channel.flags & AAL_FLAG_RELATIVE &&
lpds3db->SetMode(DS3DMODE_HEADRELATIVE, DS3D_DEFERRED))
return AAL_ERROR_SYSTEM;
SetPosition(channel.position);
SetVelocity(channel.velocity);
SetDirection(channel.direction);
SetCone(channel.cone);
SetFalloff(channel.falloff);
if (is_reverb_present)
{
lpds3db->QueryInterface(IID_IKsPropertySet, (aalVoid **)&lpeax);
aalSLong value(0);
lpeax->Set(DSPROPSETID_EAX_BufferProperties,
DSPROPERTY_EAXBUFFER_FLAGS | DSPROPERTY_EAXBUFFER_DEFERRED,
NULL, 0, &value, sizeof(aalSLong));
if (!environment || !(channel.flags & AAL_FLAG_REVERBERATION))
{
value = -10000;
lpeax->Set(DSPROPSETID_EAX_BufferProperties,
DSPROPERTY_EAXBUFFER_ROOM | DSPROPERTY_EAXBUFFER_DEFERRED,
NULL, 0, &value, sizeof(aalSLong));
lpeax->Set(DSPROPSETID_EAX_BufferProperties,
DSPROPERTY_EAXBUFFER_ROOMHF | DSPROPERTY_EAXBUFFER_DEFERRED,
NULL, 0, &value, sizeof(aalSLong));
}
}
}
else SetPan(channel.pan);
return AAL_OK;
}
aalError Instance::Clean()
{
if (lpeax) lpeax->Release(), lpeax = NULL;
if (lpds3db) lpds3db->Release(), lpds3db = NULL;
if (lpdsb)
{
if (IsPlaying()) lpdsb->Stop();
lpdsb->Release(), lpdsb = NULL;
}
if (stream) DeleteStream(stream);
if (sample) sample->Release(), sample = NULL;
status = 0;
return AAL_OK;
}
aalError Instance::SetVolume(const aalFloat & v)
{
if (!(channel.flags & AAL_FLAG_VOLUME)) return AAL_ERROR_INIT;
aalFloat volume(1.0F);
const Mixer * mixer = _mixer[channel.mixer];
channel.volume = v > 1.0F ? 1.0F : v < 0.0F ? 0.0F : v;
while (mixer) volume *= mixer->volume, mixer = mixer->parent;
if (volume) volume = LinearToLogVolume(volume) * channel.volume;
aalSLong value(aalSLong((volume - 1.0F) * 10000.0F));
if (lpdsb->SetVolume(value)) return AAL_ERROR_SYSTEM;
if (lpeax)
{
if (lpeax->Set(DSPROPSETID_EAX_SourceProperties, DSPROPERTY_EAXBUFFER_ROOM | DSPROPERTY_EAXBUFFER_DEFERRED,
NULL, 0, &value, sizeof(aalSLong)))
return AAL_ERROR_SYSTEM;
}
return AAL_OK;
}
aalError Instance::SetPitch(const aalFloat & p)
{
if (!(channel.flags & AAL_FLAG_PITCH)) return AAL_ERROR_INIT;
float pitch = 1.f;
const Mixer * mixer = _mixer[channel.mixer];
channel.pitch = p;
if (channel.pitch > 2.0F) channel.pitch = 2.0F;
else if (channel.pitch < 0.1F) channel.pitch = 0.1F;
while (mixer) pitch *= mixer->pitch, mixer = mixer->parent;
if (pitch > 2.0F) pitch = 2.0F;
else if (pitch < 0.1F) pitch = 0.1F;
if (lpdsb->SetFrequency(aalULong(channel.pitch * pitch * sample->format.frequency)))
return AAL_ERROR_SYSTEM;
return AAL_OK;
}
aalError Instance::SetPan(const aalFloat & p)
{
if (!(channel.flags & AAL_FLAG_PAN)) return AAL_ERROR_INIT;
channel.pan = p > 1.0F ? 1.0F : p < -1.0F ? -1.0F : p;
if (lpdsb->SetPan(aalSLong(channel.pan * 10000.0F))) return AAL_ERROR_SYSTEM;
return AAL_OK;
}
aalError Instance::SetPosition(const aalVector & position)
{
if (!lpds3db || !(channel.flags & AAL_FLAG_POSITION)) return AAL_ERROR_INIT;
channel.position = position;
if (lpds3db->SetPosition(position.x, position.y, position.z, DS3D_DEFERRED))
return AAL_ERROR_SYSTEM;
return AAL_OK;
}
aalError Instance::SetVelocity(const aalVector & velocity)
{
if (!lpds3db || !(channel.flags & AAL_FLAG_VELOCITY)) return AAL_ERROR_INIT;
channel.velocity = velocity;
if (lpds3db->SetVelocity(velocity.x, velocity.y, velocity.z, DS3D_DEFERRED))
return AAL_ERROR_SYSTEM;
return AAL_OK;
}
aalError Instance::SetDirection(const aalVector & direction)
{
if (!lpds3db || !(channel.flags & AAL_FLAG_DIRECTION)) return AAL_ERROR_INIT;
channel.direction = direction;
if (lpds3db->SetConeOrientation(direction.x, direction.y, direction.z, DS3D_DEFERRED))
return AAL_ERROR_INIT;
return AAL_OK;
}
aalError Instance::SetCone(const aalCone & cone)
{
if (!lpds3db || !(channel.flags & AAL_FLAG_CONE)) return AAL_ERROR_INIT;
channel.cone.inner_angle = cone.inner_angle;
channel.cone.outer_angle = cone.outer_angle;
channel.cone.outer_volume = cone.outer_volume > 1.0F ? 1.0F : cone.outer_volume < 0.0F ? 0.0F : cone.outer_volume;
if (lpds3db->SetConeAngles(aalULong(channel.cone.inner_angle), aalULong(channel.cone.outer_angle), DS3D_DEFERRED))
return AAL_ERROR_SYSTEM;
if (lpds3db->SetConeOutsideVolume(aalSLong((channel.cone.outer_volume - 1.0F) * 10000.0F), DS3D_DEFERRED))
return AAL_ERROR_SYSTEM;
return AAL_OK;
}
aalError Instance::SetFalloff(const aalFalloff & falloff)
{
if (!lpds3db || !(channel.flags & AAL_FLAG_FALLOFF)) return AAL_ERROR_INIT;
channel.falloff = falloff;
if (lpds3db->SetMinDistance(falloff.start, DS3D_DEFERRED) ||
lpds3db->SetMaxDistance(falloff.end, DS3D_DEFERRED))
return AAL_ERROR_SYSTEM;
return AAL_OK;
}
///////////////////////////////////////////////////////////////////////////////
// //
// Status //
// //
///////////////////////////////////////////////////////////////////////////////
aalError Instance::GetStatistics(aalFloat & av_vol, aalFloat & av_dev) const
{
aalULong pos, length(0);
aalULong cur0, cur1;
aalVoid * ptr0, *ptr1;
av_vol = av_dev = 0.0F;
if (lpdsb->GetCurrentPosition(&pos, NULL)) return AAL_ERROR_SYSTEM;
if (pos > 150) pos -= 150;
if (stream) length = write < pos ? write + size : write;
else length = sample->length;
length -= pos;
aalULong sec(256);
if (length > sec) length = sec;
if (lpdsb->Lock(pos, length, &ptr0, &cur0, &ptr1, &cur1, 0)) return AAL_ERROR_SYSTEM;
length >>= 1;
if (ptr0)
{
aalUWord * ptr = (aalUWord *)ptr0 + (cur0 >> 1);
while (ptr > ptr0) av_vol += *(--ptr);
}
if (ptr1)
{
aalUWord * ptr = (aalUWord *)ptr1 + (cur1 >> 1);
while (ptr > ptr1) av_vol += *(--ptr);
}
av_vol /= length;
av_vol /= 65535.0F;
if (ptr0)
{
aalFloat dev;
aalUWord * ptr = (aalUWord *)ptr0 + (cur0 >> 1);
while (ptr > ptr0)
{
dev = aalFloat(*(--ptr)) / 65535.0F - av_vol;
dev *= dev;
av_dev += dev;
}
}
if (ptr1)
{
aalFloat dev;
aalUWord * ptr = (aalUWord *)ptr1 + (cur1 >> 1);
while (ptr > ptr1)
{
dev = aalFloat(*(--ptr)) / 65535.0F - av_vol;
dev *= dev;
av_dev += dev;
}
}
av_dev /= length;
av_dev = (aalFloat)sqrt(av_dev);
lpdsb->Unlock(ptr0, cur0, ptr1, cur1);
if (debug_log) fprintf(debug_log, "AVV[%f] - AVD[%f]\n", av_vol, av_dev);
return AAL_OK;
}
aalError Instance::GetPosition(aalVector & position) const
{
if (!lpds3db || !(channel.flags & AAL_FLAG_POSITION)) return AAL_ERROR_INIT;
if (lpds3db->GetPosition((D3DVECTOR *)&position)) return AAL_ERROR_SYSTEM;
return AAL_OK;
}
aalError Instance::GetFalloff(aalFalloff & falloff) const
{
falloff = channel.falloff;
return AAL_OK;
}
aalUBool Instance::IsPlaying()
{
aalULong value;
if (lpdsb->GetStatus(&value)) value = 0;
return value & DSBSTATUS_PLAYING ? AAL_UTRUE : AAL_UFALSE;
}
aalUBool Instance::IsIdled()
{
return status & IS_IDLED ? AAL_UTRUE : AAL_UFALSE;
}
aalULong Instance::Time(const aalUnit & unit)
{
return BytesToUnits(time, sample->format, unit);
}
///////////////////////////////////////////////////////////////////////////////
// //
// Control //
// //
///////////////////////////////////////////////////////////////////////////////
aalError Instance::Play(const aalULong & play_count)
{
//Enqueue _loop count if instance is already playing
if (IsPlaying())
{
if (play_count) loop += play_count;
else loop = 0xffffffff;
lpdsb->Play(0, 0, loop || stream ? DSBPLAY_LOOPING : 0);
if (debug_log) InstanceDebugLog(this, "QUEUED");
return AAL_OK;
}
//Streaming : preload first segment
if (stream)
{
aalULong cur0, cur1;
aalVoid * ptr0, *ptr1;
if (stream->SetPosition(0)) return AAL_ERROR;
if (lpdsb->Lock(0, size, &ptr0, &cur0, &ptr1, &cur1, DSBLOCK_ENTIREBUFFER)) return AAL_ERROR_SYSTEM;
stream->Read(ptr0, size, write);
if (lpdsb->Unlock(ptr0, cur0, ptr1, cur1)) return AAL_ERROR_SYSTEM;
if (write != size) return AAL_ERROR;
}
status &= ~IS_PAUSED;
time = read = write = 0;
loop = play_count - 1;
callb_i = channel.flags & AAL_FLAG_CALLBACK ? 0 : 0xffffffff;
if (lpdsb->SetCurrentPosition(0)) return AAL_ERROR_SYSTEM;
if (lpdsb->Play(0, 0, loop || stream ? DSBPLAY_LOOPING : 0))
return AAL_ERROR_SYSTEM;
if (debug_log) InstanceDebugLog(this, "STARTED");
return AAL_OK;
}
aalError Instance::Stop()
{
if (status & IS_IDLED) return AAL_OK;
if (debug_log) InstanceDebugLog(this, "STOPPED");
if (lpdsb->Stop() || lpdsb->SetCurrentPosition(0)) return AAL_ERROR_SYSTEM;
status &= ~IS_PAUSED;
status |= IS_IDLED;
return AAL_OK;
}
aalError Instance::Pause()
{
if (status & IS_IDLED) return AAL_OK;
if (debug_log) InstanceDebugLog(this, "PAUSED");
lpdsb->Stop();
status |= IS_PAUSED;
return AAL_OK;
}
aalError Instance::Resume()
{
if (status & IS_IDLED) return AAL_OK;
if (debug_log) InstanceDebugLog(this, "RESUMED");
status &= ~IS_PAUSED;
if (listener && channel.flags & AAL_FLAG_POSITION && lpds3db && IsTooFar())
return AAL_OK;
if (lpdsb->Play(0, 0, loop || stream ? DSBPLAY_LOOPING : 0))
return AAL_ERROR_SYSTEM;
return AAL_OK;
}
static inline aalFloat Distance(const aalVector & from, const aalVector & to)
{
aalFloat x, y, z;
x = from.x - to.x;
y = from.y - to.y;
z = from.z - to.z;
return aalFloat(sqrt(x * x + y * y + z * z));
}
aalUBool Instance::IsTooFar()
{
aalVector listener_pos;
aalFloat dist, max;
lpds3db->GetMaxDistance(&max);
if (channel.flags & AAL_FLAG_RELATIVE)
listener_pos.x = listener_pos.y = listener_pos.z = 0.0F;
else
listener->GetPosition((D3DVECTOR *)&listener_pos);
dist = Distance(listener_pos, channel.position);
if (status & IS_TOOFAR)
{
if (dist > max) return AAL_UTRUE;
status &= ~IS_TOOFAR;
lpdsb->Play(0, 0, loop || stream ? DSBPLAY_LOOPING : 0);
return AAL_UFALSE;
}
else
{
if (dist <= max) return AAL_UFALSE;
status |= IS_TOOFAR;
lpdsb->Stop();
}
return AAL_UTRUE;
}
aalVoid Instance::UpdateStreaming()
{
aalVoid * ptr0, *ptr1;
aalULong cur0, cur1;
aalULong to_fill, count;
if (debug_log) InstanceDebugLog(this, "STREAMED");
to_fill = write >= read ? read + size - write : read - write;
if (!lpdsb->Lock(write, to_fill, &ptr0, &cur0, &ptr1, &cur1, 0))
{
if (ptr0)
{
stream->Read(ptr0, cur0, count);
if (count < cur0)
{
if (loop)
{
stream->SetPosition(0);
stream->Read((aalUByte *)ptr0 + count, cur0 - count, count);
}
else
{
memset((aalUByte *)ptr0 + count, 0, cur0 - count);
}
}
}
if (ptr1)
{
stream->Read(ptr1, cur1, count);
if (count < cur1)
{
if (loop)
{
stream->SetPosition(0);
stream->Read((aalUByte *)ptr1 + count, cur1 - count, count);
}
else
{
memset((aalUByte *)ptr1 + count, 0, cur1 - count);
lpdsb->Play(0, 0, 0);
}
}
}
lpdsb->Unlock(ptr0, cur0, ptr1, cur1);
}
write += to_fill;
if (write >= size) write -= size;
}
aalError Instance::Update()
{
aalULong last;
if (status & (IS_IDLED | IS_PAUSED)) return AAL_OK;
if (listener && channel.flags & AAL_FLAG_POSITION && lpds3db && IsTooFar())
{
if (! this->loop)
{
this->Stop();
}
else
{
return AAL_OK;
}
}
last = read;
lpdsb->GetCurrentPosition(&read, NULL);
if (read == last)
{
if (!IsPlaying())
{
Stop();
}
return AAL_OK;
}
time += read < last ? read + size - last : read - last;
//Check if's time to launch a callback
if (callb_i < sample->callb_c && sample->callb[callb_i].time <= time)
{
sample->callb[callb_i].func(this, id, sample->callb[callb_i].data);
callb_i++;
}
if (time >= sample->length)
{
if (loop)
{
if (debug_log) InstanceDebugLog(this, "LOOPED");
if (!--loop && !stream) lpdsb->Play(0, 0, 0);
}
else
{
if (debug_log) InstanceDebugLog(this, "IDLED");
status |= IS_IDLED;
return AAL_OK;
}
if (channel.flags & AAL_FLAG_CALLBACK) callb_i = 0;
time -= sample->length;
}
if (stream) UpdateStreaming();
return AAL_OK;
}
}//ATHENA::