raze/polymer/eduke32/source/jaudiolib/src/multivoc.c

1852 lines
44 KiB
C
Raw Normal View History

/*
Copyright (C) 1994-1995 Apogee Software, Ltd.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**********************************************************************
module: MULTIVOC.C
author: James R. Dose
date: December 20, 1993
Routines to provide multichannel digitized sound playback for
Sound Blaster compatible sound cards.
(c) Copyright 1993 James R. Dose. All Rights Reserved.
**********************************************************************/
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include "linklist.h"
#include "sndcards.h"
#include "drivers.h"
#include "pitch.h"
#include "multivoc.h"
#include "_multivc.h"
static void MV_Mix( VoiceNode *voice, int32_t buffer );
static void MV_StopVoice( VoiceNode *voice );
static void MV_ServiceVoc( void );
static VoiceNode *MV_GetVoice( int32_t handle );
static const int16_t *MV_GetVolumeTable( int32_t vol );
static void MV_CalcVolume( int32_t MaxLevel );
static void MV_CalcPanTable( void );
/*
#define RoundFixed( fixedval, bits ) \
( \
( \
(fixedval) + ( 1 << ( (bits) - 1 ) )\
) >> (bits) \
)
*/
#define IS_QUIET( ptr ) ( ( void * )( ptr ) == ( void * )&MV_VolumeTable[ 0 ] )
static int32_t MV_ReverbLevel;
static int32_t MV_ReverbDelay;
static VOLUME16 *MV_ReverbTable = NULL;
static int16_t MV_VolumeTable[ MV_MAXVOLUME + 1 ][ 256 ];
Pan MV_PanTable[ MV_NUMPANPOSITIONS ][ MV_MAXVOLUME + 1 ];
int32_t MV_Installed = FALSE;
static int32_t MV_TotalVolume = MV_MAXTOTALVOLUME;
static int32_t MV_MaxVoices = 1;
static int32_t MV_BufferSize = MV_MIXBUFFERSIZE;
static int32_t MV_BufferLength;
static int32_t MV_NumberOfBuffers = MV_NUMBEROFBUFFERS;
static int32_t MV_MixMode = MONO_8BIT;
static int32_t MV_Channels = 1;
static int32_t MV_Bits = 8;
static int32_t MV_Silence = SILENCE_8BIT;
static int32_t MV_SwapLeftRight = FALSE;
static int32_t MV_RequestedMixRate;
int32_t MV_MixRate;
//static int32_t MV_BuffShift;
static int32_t MV_BufferEmpty[ MV_NUMBEROFBUFFERS ];
char *MV_MixBuffer[ MV_NUMBEROFBUFFERS + 1 ];
static VoiceNode *MV_Voices = NULL;
static volatile VoiceNode VoiceList;
static volatile VoiceNode VoicePool;
static int32_t MV_MixPage = 0;
void (*MV_Printf)(const char *fmt, ...) = NULL;
static void (*MV_CallBackFunc)(uint32_t) = NULL;
static void (*MV_MixFunction)(VoiceNode *voice, int32_t buffer);
char *MV_HarshClipTable;
char *MV_MixDestination;
const int16_t *MV_LeftVolume;
const int16_t *MV_RightVolume;
int32_t MV_SampleSize = 1;
int32_t MV_RightChannelOffset;
uint32_t MV_MixPosition;
int32_t MV_ErrorCode = MV_Ok;
static int32_t lockdepth = 0;
static inline void DisableInterrupts(void)
{
if (lockdepth++ > 0)
return;
SoundDriver_Lock();
return;
}
static inline void RestoreInterrupts(void)
{
if (--lockdepth > 0)
return;
SoundDriver_Unlock();
}
/*---------------------------------------------------------------------
Function: MV_ErrorString
Returns a pointer to the error message associated with an error
number. A -1 returns a pointer the current error.
---------------------------------------------------------------------*/
const char *MV_ErrorString(int32_t ErrorNumber)
{
switch (ErrorNumber)
{
case MV_Warning :
case MV_Error :
return MV_ErrorString(MV_ErrorCode);
case MV_Ok :
return "Multivoc ok.";
case MV_UnsupportedCard :
return "Selected sound card is not supported by Multivoc.";
case MV_NotInstalled :
return "Multivoc not installed.";
case MV_DriverError :
return SoundDriver_ErrorString(SoundDriver_GetError());
case MV_NoVoices :
return "No free voices available to Multivoc.";
case MV_NoMem :
return "Out of memory in Multivoc.";
case MV_VoiceNotFound :
return "No voice with matching handle found.";
case MV_InvalidVOCFile :
return "Invalid VOC file passed in to Multivoc.";
case MV_InvalidWAVFile :
return "Invalid WAV file passed in to Multivoc.";
case MV_InvalidVorbisFile :
return "Invalid OggVorbis file passed in to Multivoc.";
case MV_InvalidFLACFile :
return "Invalid FLAC file passed in to Multivoc.";
case MV_InvalidXAFile :
return "Invalid XA file passed in to Multivoc.";
case MV_InvalidMixMode :
return "Invalid mix mode request in Multivoc.";
default :
return "Unknown Multivoc error code.";
}
}
/*---------------------------------------------------------------------
Function: MV_Mix
Mixes the sound into the buffer.
---------------------------------------------------------------------*/
static void MV_Mix(VoiceNode *voice,int32_t buffer)
{
const char *start;
int32_t length;
int32_t voclength;
uint32_t position;
uint32_t rate;
uint32_t FixedPointBufferSize;
/* cheap fix for a crash under 64-bit linux */
/* v v v v */
if (voice->length == 0 && (!voice->GetSound || voice->GetSound(voice) != KeepPlaying))
return;
length = MV_MIXBUFFERSIZE;
FixedPointBufferSize = voice->FixedPointBufferSize;
MV_MixDestination = MV_MixBuffer[ buffer ];
MV_LeftVolume = voice->LeftVolume;
MV_RightVolume = voice->RightVolume;
if ((MV_Channels == 2) && (IS_QUIET(MV_LeftVolume)))
{
MV_LeftVolume = MV_RightVolume;
MV_MixDestination += MV_RightChannelOffset;
}
// Add this voice to the mix
while (length > 0)
{
start = voice->sound;
rate = voice->RateScale;
position = voice->position;
// Check if the last sample in this buffer would be
// beyond the length of the sample block
if ((position + FixedPointBufferSize) >= voice->length)
{
if (position < voice->length)
{
voclength = (voice->length - position + rate - voice->channels) / rate;
}
else
{
voice->GetSound(voice);
return;
}
}
else
voclength = length;
if (voice->mix)
voice->mix(position, rate, start, voclength);
voice->position = MV_MixPosition;
length -= voclength;
if (voice->position >= voice->length)
{
// Get the next block of sound
if (voice->GetSound(voice) != KeepPlaying)
return;
if (length > (voice->channels - 1))
{
// Get the position of the last sample in the buffer
FixedPointBufferSize = voice->RateScale * (length - voice->channels);
}
}
}
}
/*---------------------------------------------------------------------
Function: MV_PlayVoice
Adds a voice to the play list.
---------------------------------------------------------------------*/
void MV_PlayVoice(VoiceNode *voice)
{
DisableInterrupts();
LL_SortedInsertion(&VoiceList, voice, prev, next, VoiceNode, priority);
RestoreInterrupts();
}
/*---------------------------------------------------------------------
Function: MV_StopVoice
Removes the voice from the play list and adds it to the free list.
---------------------------------------------------------------------*/
static void MV_StopVoice(VoiceNode *voice)
{
DisableInterrupts();
// move the voice from the play list to the free list
LL_Remove(voice, next, prev);
LL_Add((VoiceNode*) &VoicePool, voice, next, prev);
RestoreInterrupts();
#ifdef HAVE_VORBIS
if (voice->wavetype == Vorbis)
MV_ReleaseVorbisVoice(voice);
#endif
#ifdef HAVE_FLAC
if (voice->wavetype == FLAC)
MV_ReleaseFLACVoice(voice);
#endif
if (voice->wavetype == XA)
MV_ReleaseXAVoice(voice);
voice->handle = 0;
}
/*---------------------------------------------------------------------
Function: MV_ServiceVoc
Starts playback of the waiting buffer and mixes the next one.
JBF: no synchronisation happens inside MV_ServiceVoc nor the
supporting functions it calls. This would cause a deadlock
between the mixer thread in the driver vs the nested
locking in the user-space functions of MultiVoc. The call
to MV_ServiceVoc is synchronised in the driver.
---------------------------------------------------------------------*/
static void MV_ServiceVoc(void)
{
VoiceNode *voice;
VoiceNode *next;
//int32_t flags;
int32_t iter;
// Toggle which buffer we'll mix next
if (++MV_MixPage >= MV_NumberOfBuffers)
MV_MixPage -= MV_NumberOfBuffers;
if (MV_ReverbLevel == 0)
{
// Initialize buffer
//Commented out so that the buffer is always cleared.
//This is so the guys at Echo Speech can mix into the
//buffer even when no sounds are playing.
if (!MV_BufferEmpty[MV_MixPage])
{
ClearBuffer_DW(MV_MixBuffer[ MV_MixPage ], MV_Silence, MV_BufferSize >> 2);
MV_BufferEmpty[ MV_MixPage ] = TRUE;
}
}
else
{
char *end;
char *source;
char *dest;
int32_t count;
int32_t length;
end = MV_MixBuffer[ 0 ] + MV_BufferLength;
dest = MV_MixBuffer[ MV_MixPage ];
source = MV_MixBuffer[ MV_MixPage ] - MV_ReverbDelay;
if (source < MV_MixBuffer[ 0 ])
source += MV_BufferLength;
length = MV_BufferSize;
while (length > 0)
{
count = length;
if (source + count > end)
count = end - source;
if (MV_Bits == 16)
{
if (MV_ReverbTable != NULL)
MV_16BitReverb(source, dest, MV_ReverbTable, count / 2);
else
MV_16BitReverbFast(source, dest, count / 2, MV_ReverbLevel);
}
else // if (MV_Bits == 8)
{
if (MV_ReverbTable != NULL)
MV_8BitReverb((int8_t *) source, (int8_t *) dest, MV_ReverbTable, count);
else
MV_8BitReverbFast((int8_t *) source, (int8_t *) dest, count, MV_ReverbLevel);
}
// if we go through the loop again, it means that we've wrapped around the buffer
source = MV_MixBuffer[ 0 ];
dest += count;
length -= count;
}
}
// Play any waiting voices
//DisableInterrupts();
if (!VoiceList.next || (voice = VoiceList.next) == &VoiceList)
return;
iter = 0;
do
{
next = voice->next;
iter++;
if (iter > MV_MaxVoices && MV_Printf)
MV_Printf("more iterations than voices! iter: %d\n",iter);
if (voice->Paused)
continue;
MV_BufferEmpty[ MV_MixPage ] = FALSE;
MV_MixFunction(voice, MV_MixPage);
// Is this voice done?
if (!voice->Playing)
{
//JBF: prevent a deadlock caused by MV_StopVoice grabbing the mutex again
//MV_StopVoice( voice );
LL_Remove(voice, next, prev);
LL_Add((VoiceNode*) &VoicePool, voice, next, prev);
#ifdef HAVE_VORBIS
if (voice->wavetype == Vorbis)
MV_ReleaseVorbisVoice(voice);
#endif
#ifdef HAVE_FLAC
if (voice->wavetype == FLAC)
MV_ReleaseFLACVoice(voice);
#endif
voice->handle = 0;
if (MV_CallBackFunc)
MV_CallBackFunc(voice->callbackval);
}
}
while ((voice = next) != &VoiceList);
//RestoreInterrupts();
}
/*---------------------------------------------------------------------
Function: MV_GetVoice
Locates the voice with the specified handle.
---------------------------------------------------------------------*/
static VoiceNode *MV_GetVoice(int32_t handle)
{
VoiceNode *voice;
if (handle < MV_MINVOICEHANDLE || handle > MV_MaxVoices)
{
if (MV_Printf)
MV_Printf("MV_GetVoice(): bad handle (%d)!\n", handle);
return NULL;
}
DisableInterrupts();
for (voice = VoiceList.next; voice != &VoiceList; voice = voice->next)
{
if (handle == voice->handle)
{
RestoreInterrupts();
return voice;
}
}
RestoreInterrupts();
MV_SetErrorCode(MV_VoiceNotFound);
return NULL;
}
/*---------------------------------------------------------------------
Function: MV_VoicePlaying
Checks if the voice associated with the specified handle is
playing.
---------------------------------------------------------------------*/
int32_t MV_VoicePlaying(int32_t handle)
{
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return FALSE;
}
return MV_GetVoice(handle) ? TRUE : FALSE;
}
/*---------------------------------------------------------------------
Function: MV_KillAllVoices
Stops output of all currently active voices.
---------------------------------------------------------------------*/
int32_t MV_KillAllVoices(void)
{
VoiceNode * voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
DisableInterrupts();
if (&VoiceList == VoiceList.next)
{
RestoreInterrupts();
return MV_Ok;
}
voice = VoiceList.prev;
// Remove all the voices from the list
while (voice != &VoiceList)
{
if (voice->priority == MV_MUSIC_PRIORITY)
{
voice = voice->prev;
continue;
}
MV_Kill(voice->handle);
voice = VoiceList.prev;
}
RestoreInterrupts();
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_Kill
Stops output of the voice associated with the specified handle.
---------------------------------------------------------------------*/
int32_t MV_Kill(int32_t handle)
{
VoiceNode *voice;
uint32_t callbackval;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
DisableInterrupts();
if ((voice = MV_GetVoice(handle)) == NULL)
{
RestoreInterrupts();
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Error;
}
callbackval = voice->callbackval;
MV_StopVoice(voice);
RestoreInterrupts();
if (MV_CallBackFunc)
MV_CallBackFunc(callbackval);
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_VoicesPlaying
Determines the number of currently active voices.
---------------------------------------------------------------------*/
int32_t MV_VoicesPlaying(void)
{
VoiceNode *voice;
int32_t NumVoices = 0;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return 0;
}
DisableInterrupts();
for (voice = VoiceList.next; voice != &VoiceList; voice = voice->next)
NumVoices++;
RestoreInterrupts();
return NumVoices;
}
/*---------------------------------------------------------------------
Function: MV_AllocVoice
Retrieve an inactive or lower priority voice for output.
---------------------------------------------------------------------*/
VoiceNode *MV_AllocVoice(int32_t priority)
{
VoiceNode *voice;
VoiceNode *node;
DisableInterrupts();
// Check if we have any free voices
if (LL_Empty(&VoicePool, next, prev))
{
// check if we have a higher priority than a voice that is playing.
for (voice = node = VoiceList.next; node != &VoiceList; node = node->next)
{
if (node->priority < voice->priority)
voice = node;
}
if (priority >= voice->priority && voice != &VoiceList && voice->handle >= MV_MINVOICEHANDLE)
MV_Kill(voice->handle);
if (LL_Empty(&VoicePool, next, prev))
{
// No free voices
RestoreInterrupts();
return NULL;
}
}
voice = VoicePool.next;
LL_Remove(voice, next, prev);
RestoreInterrupts();
{
int32_t vhan = MV_MINVOICEHANDLE;
// Find a free voice handle
do
{
if (++vhan < MV_MINVOICEHANDLE || vhan > MV_MaxVoices)
vhan = MV_MINVOICEHANDLE;
}
while (MV_VoicePlaying(vhan));
voice->handle = vhan;
}
return voice;
}
/*---------------------------------------------------------------------
Function: MV_VoiceAvailable
Checks if a voice can be played at the specified priority.
---------------------------------------------------------------------*/
int32_t MV_VoiceAvailable(int32_t priority)
{
VoiceNode *voice;
VoiceNode *node;
// Check if we have any free voices
if (!LL_Empty(&VoicePool, next, prev))
return TRUE;
DisableInterrupts();
// check if we have a higher priority than a voice that is playing.
for (voice = node = VoiceList.next; node != &VoiceList; node = node->next)
{
if (node->priority < voice->priority)
voice = node;
}
if ((voice == &VoiceList) || (priority < voice->priority))
{
RestoreInterrupts();
return FALSE;
}
RestoreInterrupts();
return TRUE;
}
/*---------------------------------------------------------------------
Function: MV_SetVoicePitch
Sets the pitch for the specified voice.
---------------------------------------------------------------------*/
void MV_SetVoicePitch
(
VoiceNode *voice,
uint32_t rate,
int32_t pitchoffset
)
{
voice->SamplingRate = rate;
voice->PitchScale = PITCH_GetScale(pitchoffset);
voice->RateScale = (rate * voice->PitchScale) / MV_MixRate;
// Multiply by MV_MIXBUFFERSIZE - 1
voice->FixedPointBufferSize = (voice->RateScale * MV_MIXBUFFERSIZE) -
voice->RateScale;
}
/*---------------------------------------------------------------------
Function: MV_SetPitch
Sets the pitch for the voice associated with the specified handle.
---------------------------------------------------------------------*/
int32_t MV_SetPitch
(
int32_t handle,
int32_t pitchoffset
)
{
VoiceNode *voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
voice = MV_GetVoice(handle);
if (voice == NULL)
{
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Error;
}
MV_SetVoicePitch(voice, voice->SamplingRate, pitchoffset);
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_SetFrequency
Sets the frequency for the voice associated with the specified handle.
---------------------------------------------------------------------*/
int32_t MV_SetFrequency
(
int32_t handle,
int32_t frequency
)
{
VoiceNode *voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
voice = MV_GetVoice(handle);
if (voice == NULL)
{
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Error;
}
MV_SetVoicePitch(voice, frequency, 0);
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_GetVolumeTable
Returns a pointer to the volume table associated with the specified
volume.
---------------------------------------------------------------------*/
static const int16_t *MV_GetVolumeTable(int32_t vol)
{
return MV_VolumeTable[MIX_VOLUME(vol)];
}
/*---------------------------------------------------------------------
Function: MV_SetVoiceMixMode
Selects which method should be used to mix the voice.
8Bit 16Bit 8Bit 16Bit | 8Bit 16Bit 8Bit 16Bit |
Mono Mono Ster Ster | Mono Mono Ster Ster | Mixer
Out Out Out Out | In In In In |
--------------------------+---------------------------+-------------
X | X | Mix8BitMono16
X | X | Mix8BitMono
X | X | Mix8BitStereo16
X | X | Mix8BitStereo
X | X | Mix16BitMono16
X | X | Mix16BitMono
X | X | Mix16BitStereo16
X | X | Mix16BitStereo
--------------------------+---------------------------+-------------
X | X | Mix16BitStereo16Stereo
X | X | Mix16BitStereo8Stereo
X | X | Mix8BitStereo16Stereo
X | X | Mix8BitStereo8Stereo
X | X | Mix16BitMono16Stereo
X | X | Mix16BitMono8Stereo
X | X | Mix8BitMono16Stereo
X | X | Mix8BitMono8Stereo
---------------------------------------------------------------------*/
void MV_SetVoiceMixMode(VoiceNode *voice)
{
//int32_t flags;
int32_t test;
//DisableInterrupts();
test = T_DEFAULT;
if (MV_Bits == 8)
{
test |= T_8BITS;
}
if (MV_Channels == 1)
{
test |= T_MONO;
}
else
{
if (IS_QUIET(voice->RightVolume))
{
test |= T_RIGHTQUIET;
}
else if (IS_QUIET(voice->LeftVolume))
{
test |= T_LEFTQUIET;
}
}
if (voice->bits == 16)
{
test |= T_16BITSOURCE;
}
if (voice->channels == 2)
{
test |= T_STEREOSOURCE;
test &= ~(T_RIGHTQUIET | T_LEFTQUIET);
}
switch (test)
{
case T_8BITS | T_MONO | T_16BITSOURCE :
voice->mix = MV_Mix8BitMono16;
break;
case T_8BITS | T_MONO :
voice->mix = MV_Mix8BitMono;
break;
case T_8BITS | T_16BITSOURCE | T_LEFTQUIET :
MV_LeftVolume = MV_RightVolume;
voice->mix = MV_Mix8BitMono16;
break;
case T_8BITS | T_LEFTQUIET :
MV_LeftVolume = MV_RightVolume;
voice->mix = MV_Mix8BitMono;
break;
case T_8BITS | T_16BITSOURCE | T_RIGHTQUIET :
voice->mix = MV_Mix8BitMono16;
break;
case T_8BITS | T_RIGHTQUIET :
voice->mix = MV_Mix8BitMono;
break;
case T_8BITS | T_16BITSOURCE :
voice->mix = MV_Mix8BitStereo16;
break;
case T_8BITS :
voice->mix = MV_Mix8BitStereo;
break;
case T_MONO | T_16BITSOURCE :
voice->mix = MV_Mix16BitMono16;
break;
case T_MONO :
voice->mix = MV_Mix16BitMono;
break;
case T_16BITSOURCE | T_LEFTQUIET :
MV_LeftVolume = MV_RightVolume;
voice->mix = MV_Mix16BitMono16;
break;
case T_LEFTQUIET :
MV_LeftVolume = MV_RightVolume;
voice->mix = MV_Mix16BitMono;
break;
case T_16BITSOURCE | T_RIGHTQUIET :
voice->mix = MV_Mix16BitMono16;
break;
case T_RIGHTQUIET :
voice->mix = MV_Mix16BitMono;
break;
case T_16BITSOURCE :
voice->mix = MV_Mix16BitStereo16;
break;
case T_SIXTEENBIT_STEREO :
voice->mix = MV_Mix16BitStereo;
break;
case T_16BITSOURCE | T_STEREOSOURCE:
voice->mix = MV_Mix16BitStereo16Stereo;
break;
case T_16BITSOURCE | T_STEREOSOURCE | T_8BITS:
voice->mix = MV_Mix8BitStereo16Stereo;
break;
case T_16BITSOURCE | T_STEREOSOURCE | T_MONO:
voice->mix = MV_Mix16BitMono16Stereo;
break;
case T_16BITSOURCE | T_STEREOSOURCE | T_8BITS | T_MONO:
voice->mix = MV_Mix8BitMono16Stereo;
break;
case T_STEREOSOURCE:
voice->mix = MV_Mix16BitStereo8Stereo;
break;
case T_STEREOSOURCE | T_8BITS:
voice->mix = MV_Mix8BitStereo8Stereo;
break;
case T_STEREOSOURCE | T_MONO:
voice->mix = MV_Mix16BitMono8Stereo;
break;
case T_STEREOSOURCE | T_8BITS | T_MONO:
voice->mix = MV_Mix8BitMono8Stereo;
break;
default :
voice->mix = 0;
}
//RestoreInterrupts( flags );
}
/*---------------------------------------------------------------------
Function: MV_SetVoiceVolume
Sets the stereo and mono volume level of the voice associated
with the specified handle.
---------------------------------------------------------------------*/
void MV_SetVoiceVolume
(
VoiceNode *voice,
int32_t vol,
int32_t left,
int32_t right
)
{
if (MV_Channels == 1)
{
left = vol;
right = vol;
}
if (MV_SwapLeftRight)
{
// SBPro uses reversed panning
voice->LeftVolume = MV_GetVolumeTable(right);
voice->RightVolume = MV_GetVolumeTable(left);
}
else
{
voice->LeftVolume = MV_GetVolumeTable(left);
voice->RightVolume = MV_GetVolumeTable(right);
}
MV_SetVoiceMixMode(voice);
}
/*---------------------------------------------------------------------
Function: MV_PauseVoice
Pauses the voice associated with the specified handle
without stoping the sound.
---------------------------------------------------------------------*/
int32_t MV_PauseVoice
(
int32_t handle,
int32_t pause
)
{
VoiceNode *voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
DisableInterrupts();
voice = MV_GetVoice(handle);
if (voice == NULL)
{
RestoreInterrupts();
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Warning;
}
voice->Paused = pause;
RestoreInterrupts();
return MV_Ok;
}
int32_t MV_GetPosition(int32_t handle, int32_t *position)
{
VoiceNode *voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
DisableInterrupts();
voice = MV_GetVoice(handle);
if (voice == NULL)
{
RestoreInterrupts();
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Warning;
}
#ifdef HAVE_VORBIS
if (voice->wavetype == Vorbis)
*position = MV_GetVorbisPosition(voice);
#endif
#ifdef HAVE_FLAC
if (voice->wavetype == FLAC)
*position = MV_GetFLACPosition(voice);
#endif
if (voice->wavetype == XA)
*position = MV_GetXAPosition(voice);
RestoreInterrupts();
return MV_Ok;
}
int32_t MV_SetPosition(int32_t handle, int32_t position)
{
VoiceNode *voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
DisableInterrupts();
voice = MV_GetVoice(handle);
if (voice == NULL)
{
RestoreInterrupts();
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Warning;
}
#ifdef HAVE_VORBIS
if (voice->wavetype == Vorbis)
MV_SetVorbisPosition(voice, position);
#endif
#ifdef HAVE_FLAC
if (voice->wavetype == FLAC)
MV_SetFLACPosition(voice, position);
#endif
if (voice->wavetype == XA)
MV_SetXAPosition(voice, position);
RestoreInterrupts();
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_EndLooping
Stops the voice associated with the specified handle from looping
without stoping the sound.
---------------------------------------------------------------------*/
int32_t MV_EndLooping(int32_t handle)
{
VoiceNode *voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
DisableInterrupts();
voice = MV_GetVoice(handle);
if (voice == NULL)
{
RestoreInterrupts();
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Warning;
}
voice->LoopCount = 0;
voice->LoopStart = NULL;
voice->LoopEnd = NULL;
RestoreInterrupts();
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_SetPan
Sets the stereo and mono volume level of the voice associated
with the specified handle.
---------------------------------------------------------------------*/
int32_t MV_SetPan
(
int32_t handle,
int32_t vol,
int32_t left,
int32_t right
)
{
VoiceNode *voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
voice = MV_GetVoice(handle);
if (voice == NULL)
{
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Warning;
}
MV_SetVoiceVolume(voice, vol, left, right);
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_Pan3D
Set the angle and distance from the listener of the voice associated
with the specified handle.
---------------------------------------------------------------------*/
int32_t MV_Pan3D
(
int32_t handle,
int32_t angle,
int32_t distance
)
{
int32_t left;
int32_t right;
int32_t mid;
int32_t volume;
int32_t status;
if (distance < 0)
{
distance = -distance;
angle += MV_NUMPANPOSITIONS / 2;
}
volume = MIX_VOLUME(distance);
// Ensure angle is within 0 - 127
angle &= MV_MAXPANPOSITION;
left = MV_PanTable[ angle ][ volume ].left;
right = MV_PanTable[ angle ][ volume ].right;
mid = max(0, 255 - distance);
status = MV_SetPan(handle, mid, left, right);
return status;
}
/*---------------------------------------------------------------------
Function: MV_SetReverb
Sets the level of reverb to add to mix.
---------------------------------------------------------------------*/
void MV_SetReverb(int32_t reverb)
{
MV_ReverbLevel = MIX_VOLUME(reverb);
MV_ReverbTable = &MV_VolumeTable[ MV_ReverbLevel ];
}
/*---------------------------------------------------------------------
Function: MV_SetFastReverb
Sets the level of reverb to add to mix.
---------------------------------------------------------------------*/
void MV_SetFastReverb(int32_t reverb)
{
MV_ReverbLevel = max(0, min(16, reverb));
MV_ReverbTable = NULL;
}
/*---------------------------------------------------------------------
Function: MV_GetMaxReverbDelay
Returns the maximum delay time for reverb.
---------------------------------------------------------------------*/
int32_t MV_GetMaxReverbDelay(void)
{
int32_t maxdelay;
maxdelay = MV_MIXBUFFERSIZE * MV_NumberOfBuffers;
return maxdelay;
}
/*---------------------------------------------------------------------
Function: MV_GetReverbDelay
Returns the current delay time for reverb.
---------------------------------------------------------------------*/
int32_t MV_GetReverbDelay(void)
{
return MV_ReverbDelay / MV_SampleSize;
}
/*---------------------------------------------------------------------
Function: MV_SetReverbDelay
Sets the delay level of reverb to add to mix.
---------------------------------------------------------------------*/
void MV_SetReverbDelay(int32_t delay)
{
int32_t maxdelay;
maxdelay = MV_GetMaxReverbDelay();
MV_ReverbDelay = max(MV_MIXBUFFERSIZE, min(delay, maxdelay));
MV_ReverbDelay *= MV_SampleSize;
}
/*---------------------------------------------------------------------
Function: MV_SetMixMode
Prepares Multivoc to play stereo of mono digitized sounds.
---------------------------------------------------------------------*/
static int32_t MV_SetMixMode
(
int32_t numchannels,
int32_t samplebits
)
{
int32_t mode;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
mode = 0;
if (numchannels == 2)
{
mode |= STEREO;
}
if (samplebits == 16)
{
mode |= SIXTEEN_BIT;
}
MV_MixMode = mode;
MV_Channels = 1;
if (MV_MixMode & STEREO)
{
MV_Channels = 2;
}
MV_Bits = 8;
if (MV_MixMode & SIXTEEN_BIT)
{
MV_Bits = 16;
}
// MV_BuffShift = 7 + MV_Channels;
MV_SampleSize = sizeof(MONO8) * MV_Channels;
if (MV_Bits == 8)
{
MV_Silence = SILENCE_8BIT;
}
else
{
MV_Silence = SILENCE_16BIT;
// MV_BuffShift += 1;
MV_SampleSize *= 2;
}
MV_BufferSize = MV_MIXBUFFERSIZE * MV_SampleSize;
MV_NumberOfBuffers = MV_TOTALBUFFERSIZE / MV_BufferSize;
MV_BufferLength = MV_TOTALBUFFERSIZE;
MV_RightChannelOffset = MV_SampleSize / 2;
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_StartPlayback
Starts the sound playback engine.
---------------------------------------------------------------------*/
static int32_t MV_StartPlayback(void)
{
int32_t status;
int32_t buffer;
// Initialize the buffers
ClearBuffer_DW(MV_MixBuffer[ 0 ], MV_Silence, MV_TOTALBUFFERSIZE >> 2);
for (buffer = 0; buffer < MV_NumberOfBuffers; buffer++)
{
MV_BufferEmpty[ buffer ] = TRUE;
}
// Set the mix buffer variables
MV_MixPage = 1;
MV_MixFunction = MV_Mix;
//JIM
// MV_MixRate = MV_RequestedMixRate;
// return MV_Ok;
// Start playback
status = SoundDriver_BeginPlayback(MV_MixBuffer[0], MV_BufferSize,
MV_NumberOfBuffers, MV_ServiceVoc);
if (status != MV_Ok)
{
MV_SetErrorCode(MV_DriverError);
return MV_Error;
}
MV_MixRate = MV_RequestedMixRate;
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_StopPlayback
Stops the sound playback engine.
---------------------------------------------------------------------*/
static void MV_StopPlayback(void)
{
VoiceNode *voice;
VoiceNode *next;
// Stop sound playback
SoundDriver_StopPlayback();
// Make sure all callbacks are done.
DisableInterrupts();
for (voice = VoiceList.next; voice != &VoiceList; voice = next)
{
next = voice->next;
MV_StopVoice(voice);
if (MV_CallBackFunc)
MV_CallBackFunc(voice->callbackval);
}
RestoreInterrupts();
}
/*---------------------------------------------------------------------
Function: MV_CreateVolumeTable
Create the table used to convert sound data to a specific volume
level.
---------------------------------------------------------------------*/
static void MV_CreateVolumeTable
(
int32_t index,
int32_t volume,
int32_t MaxVolume
)
{
int32_t val;
int32_t level;
int32_t i;
level = (volume * MaxVolume) / MV_MAXTOTALVOLUME;
if (MV_Bits == 16)
{
for (i = 0; i < 65536; i += 256)
{
val = i - 0x8000;
val *= level;
val /= MV_MAXVOLUME;
MV_VolumeTable[ index ][ i / 256 ] = val;
}
}
else
{
for (i = 0; i < 256; i++)
{
val = i - 0x80;
val *= level;
val /= MV_MAXVOLUME;
MV_VolumeTable[ volume ][ i ] = val;
}
}
}
/*---------------------------------------------------------------------
Function: MV_CalcVolume
Create the table used to convert sound data to a specific volume
level.
---------------------------------------------------------------------*/
static void MV_CalcVolume(int32_t MaxVolume)
{
int32_t volume;
for (volume = 0; volume < 128; volume++)
{
MV_HarshClipTable[ volume ] = 0;
MV_HarshClipTable[ volume + 384 ] = 255;
}
for (volume = 0; volume < 256; volume++)
{
MV_HarshClipTable[ volume + 128 ] = volume;
}
// For each volume level, create a translation table with the
// appropriate volume calculated.
for (volume = 0; volume <= MV_MAXVOLUME; volume++)
{
MV_CreateVolumeTable(volume, volume, MaxVolume);
}
}
/*---------------------------------------------------------------------
Function: MV_CalcPanTable
Create the table used to determine the stereo volume level of
a sound located at a specific angle and distance from the listener.
---------------------------------------------------------------------*/
static void MV_CalcPanTable(void)
{
int32_t angle, distance;
const int32_t HalfAngle = MV_NUMPANPOSITIONS / 2;
const int32_t QuarterAngle = HalfAngle / 2;
for (distance = 0; distance <= MV_MAXVOLUME; distance++)
{
const int32_t level = (255 * (MV_MAXVOLUME - distance)) / MV_MAXVOLUME;
for (angle = 0; angle <= QuarterAngle; angle++)
{
const int32_t ramp = level - (level * angle) / QuarterAngle;
MV_PanTable[ angle ][ distance ].left = ramp;
MV_PanTable[ HalfAngle - angle ][ distance ].left = ramp;
MV_PanTable[ HalfAngle + angle ][ distance ].left = level;
MV_PanTable[ MV_MAXPANPOSITION - angle ][ distance ].left = level;
MV_PanTable[ angle ][ distance ].right = level;
MV_PanTable[ HalfAngle - angle ][ distance ].right = level;
MV_PanTable[ HalfAngle + angle ][ distance ].right = ramp;
MV_PanTable[ MV_MAXPANPOSITION - angle ][ distance ].right = ramp;
}
}
}
/*---------------------------------------------------------------------
Function: MV_SetVolume
Sets the volume of digitized sound playback.
---------------------------------------------------------------------*/
void MV_SetVolume(int32_t volume)
{
volume = max(0, volume);
volume = min(volume, MV_MAXTOTALVOLUME);
MV_TotalVolume = volume;
// Calculate volume table
MV_CalcVolume(volume);
}
/*---------------------------------------------------------------------
Function: MV_GetVolume
Returns the volume of digitized sound playback.
---------------------------------------------------------------------*/
int32_t MV_GetVolume(void)
{
return MV_TotalVolume;
}
/*---------------------------------------------------------------------
Function: MV_SetCallBack
Set the function to call when a voice stops.
---------------------------------------------------------------------*/
void MV_SetCallBack(void (*function)(uint32_t))
{
MV_CallBackFunc = function;
}
/*---------------------------------------------------------------------
Function: MV_SetReverseStereo
Set the orientation of the left and right channels.
---------------------------------------------------------------------*/
void MV_SetReverseStereo(int32_t setting)
{
MV_SwapLeftRight = setting;
}
/*---------------------------------------------------------------------
Function: MV_GetReverseStereo
Returns the orientation of the left and right channels.
---------------------------------------------------------------------*/
int32_t MV_GetReverseStereo(void)
{
return MV_SwapLeftRight;
}
/*---------------------------------------------------------------------
Function: MV_Init
Perform the initialization of variables and memory used by
Multivoc.
---------------------------------------------------------------------*/
int32_t MV_Init
(
int32_t soundcard,
int32_t MixRate,
int32_t Voices,
int32_t numchannels,
int32_t samplebits,
void * initdata
)
{
char *ptr;
int32_t status;
int32_t buffer;
int32_t index;
int32_t totalmem;
if (MV_Installed)
{
MV_Shutdown();
}
MV_SetErrorCode(MV_Ok);
// MV_TotalMemory + 2: FIXME, see valgrind_errors.log
totalmem = Voices * sizeof(VoiceNode) + sizeof(HARSH_CLIP_TABLE_8) + MV_TOTALBUFFERSIZE + 2;
ptr = (char *) calloc(1, totalmem);
if (!ptr)
{
MV_SetErrorCode(MV_NoMem);
return MV_Error;
}
MV_Voices = (VoiceNode *)ptr;
ptr += Voices * sizeof(VoiceNode);
MV_HarshClipTable = ptr;
ptr += sizeof(HARSH_CLIP_TABLE_8);
// Set number of voices before calculating volume table
MV_MaxVoices = Voices;
LL_Reset((VoiceNode*) &VoiceList, next, prev);
LL_Reset((VoiceNode*) &VoicePool, next, prev);
for (index = 0; index < Voices; index++)
{
LL_Add((VoiceNode*) &VoicePool, &MV_Voices[ index ], next, prev);
}
MV_SetReverseStereo(FALSE);
ASS_SoundDriver = soundcard;
// Initialize the sound card
status = SoundDriver_Init(&MixRate, &numchannels, &samplebits, initdata);
if (status != MV_Ok)
{
MV_SetErrorCode(MV_DriverError);
}
if (MV_ErrorCode != MV_Ok)
{
status = MV_ErrorCode;
free(MV_Voices);
MV_Voices = NULL;
MV_HarshClipTable = NULL;
MV_SetErrorCode(status);
return MV_Error;
}
MV_Installed = TRUE;
MV_CallBackFunc = NULL;
MV_ReverbLevel = 0;
MV_ReverbTable = NULL;
// Set the sampling rate
MV_RequestedMixRate = MixRate;
// Set Mixer to play stereo digitized sound
MV_SetMixMode(numchannels, samplebits);
MV_ReverbDelay = MV_BufferSize * 3;
// Make sure we don't cross a physical page
MV_MixBuffer[ MV_NumberOfBuffers ] = ptr;
for (buffer = 0; buffer < MV_NumberOfBuffers; buffer++)
{
MV_MixBuffer[ buffer ] = ptr;
ptr += MV_BufferSize;
}
// Calculate pan table
MV_CalcPanTable();
MV_SetVolume(MV_MAXTOTALVOLUME);
// Start the playback engine
status = MV_StartPlayback();
if (status != MV_Ok)
{
// Preserve error code while we shutdown.
status = MV_ErrorCode;
MV_Shutdown();
MV_SetErrorCode(status);
return MV_Error;
}
return MV_Ok;
}
/*---------------------------------------------------------------------
Function: MV_Shutdown
Restore any resources allocated by Multivoc back to the system.
---------------------------------------------------------------------*/
int32_t MV_Shutdown(void)
{
int32_t buffer;
if (!MV_Installed)
{
return MV_Ok;
}
MV_KillAllVoices();
MV_Installed = FALSE;
// Stop the sound playback engine
MV_StopPlayback();
// Shutdown the sound card
SoundDriver_Shutdown();
// Free any voices we allocated
free(MV_Voices);
MV_Voices = NULL;
LL_Reset((VoiceNode*) &VoiceList, next, prev);
LL_Reset((VoiceNode*) &VoicePool, next, prev);
MV_MaxVoices = 1;
// Release the descriptor from our mix buffer
for (buffer = 0; buffer < MV_NUMBEROFBUFFERS; buffer++)
{
MV_MixBuffer[ buffer ] = NULL;
}
return MV_Ok;
}
int32_t MV_SetVoiceCallback(int32_t handle, uint32_t callbackval)
{
VoiceNode *voice;
if (!MV_Installed)
{
MV_SetErrorCode(MV_NotInstalled);
return MV_Error;
}
DisableInterrupts();
if ((voice = MV_GetVoice(handle)) == NULL)
{
RestoreInterrupts();
MV_SetErrorCode(MV_VoiceNotFound);
return MV_Error;
}
voice->callbackval = callbackval;
RestoreInterrupts();
return MV_Ok;
}
void MV_SetPrintf(void (*function)(const char *, ...))
{
MV_Printf = function;
}
const char *loopStartTags[loopStartTagCount] = {
"LOOP_START",
"LOOPSTART"
};
const char *loopEndTags[loopEndTagCount] = {
"LOOP_END",
"LOOPEND"
};
const char *loopLengthTags[loopLengthTagCount] = {
"LOOP_LENGTH",
"LOOPLENGTH"
};
// vim:ts=3:expandtab: