/* * 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 #include #include #include #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); }