openal-alsa-creative/src/al_play.c

643 lines
11 KiB
C

/*
* Copyright (C) 2004 Christopher John Purnell
* cjp@lost.org.uk
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <sys/time.h>
#include <math.h>
#include <alloca.h>
#include <string.h>
#include "al_source.h"
#include "al_error.h"
#include "al_vector.h"
#include "alc_device.h"
#include "alc_context.h"
static ALfloat _alCalculateGainAndPitch(AL_source *src)
{
AL_context *ctx = src->context;
ALCdevice *dev = ctx->device;
ALfloat position[3];
position[0] = src->position[0];
position[1] = src->position[1];
position[2] = src->position[2];
if (!src->relative)
{
position[0] -= ctx->listener.position[0];
position[1] -= ctx->listener.position[1];
position[2] -= ctx->listener.position[2];
}
/* Master Gain */
{
ALint volume;
ALfloat gain = ctx->listener.gain;
ALfloat dist = _alVectorMagnitude(position);
gain *= ctx->distance_func(src, dist);
if (dist)
{
position[0] /= dist;
position[1] /= dist;
position[2] /= dist;
}
if (src->conic)
{
ALfloat a;
a = _alVectorDotProduct(position, src->direction);
a = acos(-a) * 360.0 / M_PI;
if (a > src->cone_inner_angle)
{
if (a >= src->cone_outer_angle)
{
gain *= src->cone_outer_gain;
}
else
{
a -= src->cone_inner_angle;
a *= (src->cone_outer_gain - 1.0f);
a /= (src->cone_outer_angle -
src->cone_inner_angle);
gain *= (1.0f + a);
}
}
}
if (gain > src->max_gain)
{
gain = src->max_gain;
}
else if (gain < src->min_gain)
{
gain = src->min_gain;
}
volume = (ALint)(65535.0f * gain);
snd_ctl_elem_value_set_integer(src->vol_ctl, 1, volume);
snd_ctl_elem_value_set_integer(src->vol_ctl, 2, volume);
}
/* Speaker Gains */
{
ALuint i;
ALint volume[_ALC_NUM_SPEAKERS];
for (i = 0; i < _ALC_NUM_SPEAKERS; i++)
{
AL_speaker *speaker = &ctx->listener.speakers[i];
volume[i] = speaker->gain ?
(ALint)((_alVectorDotProduct
(position, speaker->position) + 1.0f)
* speaker->gain) : 0;
}
if (dev->send_count == 24)
{
snd_ctl_elem_value_set_integer(src->send_ctl, 8,
volume[0]);
snd_ctl_elem_value_set_integer(src->send_ctl, 10,
volume[2]);
snd_ctl_elem_value_set_integer(src->send_ctl, 12,
(volume[4] + 1) >> 1);
snd_ctl_elem_value_set_integer(src->send_ctl, 13,
(volume[5] + 1) >> 1);
snd_ctl_elem_value_set_integer(src->send_ctl, 14,
volume[6]);
snd_ctl_elem_value_set_integer(src->send_ctl, 17,
volume[1]);
snd_ctl_elem_value_set_integer(src->send_ctl, 19,
volume[3]);
snd_ctl_elem_value_set_integer(src->send_ctl, 20,
volume[4] >> 1);
snd_ctl_elem_value_set_integer(src->send_ctl, 21,
volume[5] >> 1);
snd_ctl_elem_value_set_integer(src->send_ctl, 23,
volume[7]);
}
else
{
snd_ctl_elem_value_set_integer(src->send_ctl, 4,
volume[0]);
snd_ctl_elem_value_set_integer(src->send_ctl, 5,
volume[2]);
snd_ctl_elem_value_set_integer(src->send_ctl, 6,
(volume[4] + 1) >> 1);
snd_ctl_elem_value_set_integer(src->send_ctl, 7,
(volume[5] + 1) >> 1);
snd_ctl_elem_value_set_integer(src->send_ctl, 8,
volume[1]);
snd_ctl_elem_value_set_integer(src->send_ctl, 9,
volume[3]);
snd_ctl_elem_value_set_integer(src->send_ctl, 10,
volume[4] >> 1);
snd_ctl_elem_value_set_integer(src->send_ctl, 11,
volume[5] >> 1);
}
}
/* Pitch */
{
ALfloat vl, vs;
ALfloat pitch = src->pitch;
if (ctx->doppler_factor)
{
vl = _alVectorDotProduct(ctx->listener.velocity,
position);
vs = _alVectorDotProduct(src->velocity, position);
vl *= ctx->doppler_factor;
vs *= ctx->doppler_factor;
vl += ctx->doppler_velocity;
vs += ctx->doppler_velocity;
pitch *= vl / vs;
}
if (pitch < 0.0f)
{
pitch = 0.0f;
}
return pitch;
}
}
static snd_pcm_uframes_t _alWriteData(AL_source *src, ALfloat pitch,
int32_t *dest, snd_pcm_uframes_t frames)
{
AL_queue *que;
AL_buffer *buf;
ALfloat f;
ALuint inc, acc;
ALuint i;
if (!src->playing)
{
return 0;
}
if ((que = src->current_q) ||
((que = src->first_q) && (que->state == AL_PENDING)))
{
buf = que->buffer;
}
else if ((buf = src->buffer))
{
que = 0;
}
else
{
src->playing = AL_FALSE;
return 0;
}
f = pitch * (ALfloat)buf->freq / (ALfloat)src->freq;
inc = (f >= 65535.0) ? 0xFFFF0000 : (ALuint)(f * 65536.0);
acc = 0;
i = 0;
while (i < frames)
{
ALuint j = src->index;
if (j >= buf->size)
{
src->index = 0;
if (que)
{
que->state = AL_PROCESSED;
src->current_q = que->next;
}
else if(!src->looping)
{
src->playing = AL_FALSE;
}
break;
}
dest[i++] = buf->data[j];
acc += inc;
src->index = j + (acc >> 16);
acc &= 0xFFFF;
}
return i;
}
ALvoid _alProcessSource(AL_source *src)
{
const snd_pcm_channel_area_t *area;
snd_pcm_sframes_t avail;
ALfloat pitch;
int state;
if (src->state != AL_PLAYING)
{
return;
}
snd_pcm_hwsync(src->handle);
state = snd_pcm_state(src->handle);
if (state != SND_PCM_STATE_RUNNING)
{
if (src->playing)
{
snd_pcm_prepare(src->handle);
}
else
{
src->state = AL_STOPPED;
return;
}
}
pitch = _alCalculateGainAndPitch(src);
avail = snd_pcm_avail_update(src->handle);
while (avail > 0)
{
snd_pcm_uframes_t offset;
snd_pcm_uframes_t frames = avail;
snd_pcm_uframes_t written = 0;
int32_t *map;
if (snd_pcm_mmap_begin(src->handle, &area, &offset, &frames))
{
return;
}
avail -= frames;
map = area->addr + ((area->first + area->step * offset) >> 3);
while (frames)
{
snd_pcm_uframes_t f;
if (!(f = _alWriteData(src, pitch, map, frames)))
{
bzero(map, frames << 2);
avail = 0;
break;
}
map += f;
written += f;
frames -= f;
}
snd_pcm_mmap_commit(src->handle, offset, written);
}
{
AL_context *ctx = src->context;
ALCdevice *dev = ctx->device;
snd_ctl_elem_write(dev->ctl, src->vol_ctl);
snd_ctl_elem_write(dev->ctl, src->send_ctl);
}
if (state != SND_PCM_STATE_RUNNING)
{
if (!snd_pcm_delay(src->handle, &avail))
{
if (avail)
{
snd_pcm_start(src->handle);
}
}
}
if (!src->playing)
{
snd_pcm_drain(src->handle);
}
}
static ALvoid _alSourcePlay(AL_source *src)
{
switch(src->state)
{
case AL_PAUSED:
snd_pcm_pause(src->handle, 0);
break;
case AL_PLAYING:
src->index = 0;
break;
}
src->state = AL_PLAYING;
src->playing = AL_TRUE;
}
static ALvoid _alSourceStop(AL_source *src)
{
switch (src->state)
{
case AL_PAUSED:
snd_pcm_pause(src->handle, 0);
break;
case AL_PLAYING:
snd_pcm_drop(src->handle);
break;
}
src->state = AL_STOPPED;
src->index = 0;
}
static ALvoid _alSourcePause(AL_source *src)
{
if (src->state == AL_PLAYING)
{
snd_pcm_pause(src->handle, 1);
src->state = AL_PAUSED;
}
}
static ALvoid _alSourceRewind(AL_source *src)
{
switch (src->state)
{
case AL_PAUSED:
snd_pcm_pause(src->handle, 0);
break;
case AL_PLAYING:
snd_pcm_drop(src->handle);
break;
}
src->state = AL_INITIAL;
src->index = 0;
}
ALvoid alSourcePlay(ALuint sid)
{
AL_context *ctx;
AL_source *src;
if (!(ctx = _alcCurrentContext))
{
_alSetError(AL_INVALID_OPERATION);
return;
}
if (!(src = _alFindSource(ctx, sid)))
{
_alSetError(AL_INVALID_NAME);
}
else
{
_alSourcePlay(src);
}
_alcUnlockContext(ctx);
}
ALvoid alSourceStop(ALuint sid)
{
AL_context *ctx;
AL_source *src;
if (!(ctx = _alcCurrentContext))
{
_alSetError(AL_INVALID_OPERATION);
return;
}
_alcLockContext(ctx);
if (!(src = _alFindSource(ctx, sid)))
{
_alSetError(AL_INVALID_NAME);
}
else
{
_alSourceStop(src);
}
_alcUnlockContext(ctx);
}
ALvoid alSourcePause(ALuint sid)
{
AL_context *ctx;
AL_source *src;
if (!(ctx = _alcCurrentContext))
{
_alSetError(AL_INVALID_OPERATION);
return;
}
_alcLockContext(ctx);
if (!(src = _alFindSource(ctx, sid)))
{
_alSetError(AL_INVALID_NAME);
}
else
{
_alSourcePause(src);
}
_alcUnlockContext(ctx);
}
ALvoid alSourceRewind(ALuint sid)
{
AL_context *ctx;
AL_source *src;
if (!(ctx = _alcCurrentContext))
{
_alSetError(AL_INVALID_OPERATION);
return;
}
_alcLockContext(ctx);
if (!(src = _alFindSource(ctx, sid)))
{
_alSetError(AL_INVALID_NAME);
}
else
{
_alSourceRewind(src);
}
_alcUnlockContext(ctx);
}
ALvoid alSourcePlayv(ALsizei ns, ALuint *ids)
{
AL_context *ctx;
AL_source **src;
ALsizei i;
if (!(ctx = _alcCurrentContext))
{
_alSetError(AL_INVALID_OPERATION);
return;
}
_alcLockContext(ctx);
src = alloca(ns * sizeof(AL_source *));
for (i = 0; i < ns; i++)
{
if (!(src[i] = _alFindSource(ctx, ids[i])))
{
_alSetError(AL_INVALID_NAME);
goto unlock;
}
}
for (i = 0; i < ns; i++)
{
_alSourcePlay(src[i]);
}
unlock:
_alcUnlockContext(ctx);
}
ALvoid alSourceStopv(ALsizei ns, ALuint *ids)
{
AL_context *ctx;
AL_source **src;
ALsizei i;
if (!(ctx = _alcCurrentContext))
{
_alSetError(AL_INVALID_OPERATION);
return;
}
_alcLockContext(ctx);
src = alloca(ns * sizeof(AL_source *));
for (i = 0; i < ns; i++)
{
if (!(src[i] = _alFindSource(ctx, ids[i])))
{
_alSetError(AL_INVALID_NAME);
goto unlock;
}
}
for (i = 0; i < ns; i++)
{
_alSourceStop(src[i]);
}
unlock:
_alcUnlockContext(ctx);
}
ALvoid alSourcePausev(ALsizei ns, ALuint *ids)
{
AL_context *ctx;
AL_source **src;
ALsizei i;
if (!(ctx = _alcCurrentContext))
{
_alSetError(AL_INVALID_OPERATION);
return;
}
_alcLockContext(ctx);
src = alloca(ns * sizeof(AL_source *));
for (i = 0; i < ns; i++)
{
if (!(src[i] = _alFindSource(ctx, ids[i])))
{
_alSetError(AL_INVALID_NAME);
goto unlock;
}
}
for (i = 0; i < ns; i++)
{
_alSourcePause(src[i]);
}
unlock:
_alcUnlockContext(ctx);
}
ALvoid alSourceRewindv(ALsizei ns, ALuint *ids)
{
AL_context *ctx;
AL_source **src;
ALsizei i;
if (!(ctx = _alcCurrentContext))
{
_alSetError(AL_INVALID_OPERATION);
return;
}
_alcLockContext(ctx);
src = alloca(ns * sizeof(AL_source *));
for (i = 0; i < ns; i++)
{
if (!(src[i] = _alFindSource(ctx, ids[i])))
{
_alSetError(AL_INVALID_NAME);
goto unlock;
}
}
for (i = 0; i < ns; i++)
{
_alSourceRewind(src[i]);
}
unlock:
_alcUnlockContext(ctx);
}