/*     
 **********************************************************************
 *     sblive_voice.c - Voice manager for emu10k1 driver
 *     Copyright 1999, 2000 Creative Labs, Inc. 
 * 
 ********************************************************************** 
 * 
 *     Date                 Author          Summary of changes 
 *     ----                 ------          ------------------ 
 *     October 20, 1999     Bertrand Lee    base code release 
 * 
 ********************************************************************** 
 * 
 *     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 "hwaccess.h"
#include "icardwav.h"

extern spinlock_t sblive_spinlock;

/************************************************************************
*
*   static struct emu_voice * sblive_emuVoiceAlloc(struct sblive_hw *sb_hw, u32 flags)
*
*   ENTRY
*       sb_hw -    Pointer to the HWOBJ to allocate voice from
*       flags -    flags from struct voice_allocdesc from the ICardxxx
*                  object that called sblive_voiceAlloc
*
*   RETURNS
*       SUCCESS -   Pointer to a struct emu_voice
*       FAILURE -   NULL
*
*   ABOUT
*       This is a static funciton that searches for free emuvoices
*       and tries to return the type needed according to flags
*
************************************************************************/
static struct emu_voice *sblive_emuVoiceAlloc(struct voice_mgr *voice, u32 flags)
{
	unsigned int voicenum;
	struct sblive_hw *sb_hw = voice->sb_hw;
	struct emu_voice *foundvoice = NULL;
	struct emu_voice *slavevoice = NULL;
	struct emu_voice *testvoice, *testslave;
	u32 curvol, lowestvol, mastervol, slavevol, CA, dcysusv;

	DPF("Entered sblive_emuVoiceAlloc");

	if (flags & VOICEMGR_FLAGS_MONO) 
	{
		DPF("sblive_emuVoiceAlloc MONO PLAYBACK voice");

		/* Check for free voices first */
		if (voice->free_voices) 
		{
			voice->free_voices->voicestate = VOICEMGR_STATE_TRANSITION;
			foundvoice = voice->free_voices;
			foundvoice->flags = 0;
			DPD("  Voice found %d\n", foundvoice->voicenum);
			return foundvoice;
		}
		
		/* We couldn't find any free voices so we need to free up
		 * some voices. We only rip off MIDI voices at the moment. */
		testvoice = dlFirstNode(&voice->midi_voices);

		lowestvol = 0xffffffff;
		
		while (testvoice) 
		{
			/* Voice stealing algorithm goes here */
			voicenum = testvoice->voicenum;
			
			if (!(testvoice->voicestate & VOICEMGR_STATE_TRANSITION)) 
			{
				/* Check if voice is looping silence */
				CA = sblive_readptr(sb_hw, CCCA_CURRADDR, voicenum);
				if (CA > testvoice->voice_params.end) 
				{
					testvoice->voicestate |= VOICEMGR_STATE_TRANSITION;
					if (foundvoice)
					  foundvoice->voicestate &= ~VOICEMGR_STATE_TRANSITION;
					lowestvol = 0;
					foundvoice = testvoice;
					break;
				}
				
				curvol = sblive_readptr(sb_hw, CVCF_CURRENTVOL, voicenum);
				dcysusv = sblive_readptr(sb_hw, DCYSUSV, voicenum);
                                dcysusv = (dcysusv & DCYSUSV_PHASE1_MASK) ? 0 : 0x1000; /* 0x1000 means what? */

				if ((curvol < 0x200) && !dcysusv && (testvoice->voicestate & VOICEMGR_STATE_RELEASE)) 
				{
					/* The volume is less than the threshold and
					   the volume envelope is in the decaying state.
					   Stop the pitch to stop the busmaster activities.
					 */
					sblive_writeptr(sb_hw, IP, voicenum, 0);
					sblive_writeptr(sb_hw, VTFT, voicenum, VTFT_FILTERTARGET_MASK);
					sblive_writeptr(sb_hw, PTRX_PITCHTARGET, voicenum, 0);
				} else 
				{
					if (testvoice->voicestate & VOICEMGR_STATE_SUSTAIN)
					  curvol += 0x500 + dcysusv;
					else if (testvoice->voicestate & VOICEMGR_STATE_ATTACK)
					  curvol += 0x200 + dcysusv;
				}

				if (curvol < lowestvol) 
				{
					testvoice->voicestate |= VOICEMGR_STATE_TRANSITION;
					
					if (foundvoice)
					  foundvoice->voicestate &= ~VOICEMGR_STATE_TRANSITION;
					foundvoice = testvoice;
					lowestvol = curvol;
				}
			}
			testvoice = dlNextNode(testvoice);
		}

		if (foundvoice) 
		{
			if (foundvoice->callback) 
			{
				/* Note that this function can change the voice suggested to another voice */
				if (foundvoice->callback(VOICECALLBACK_EVENT_FREEVOICE, foundvoice->callback_data, (u32)&foundvoice) 
				    != CTSTATUS_SUCCESS) 
				{
					DPF("Found voide callback failed\n");
					foundvoice = NULL;
				}
			}
			
			foundvoice->flags = 0;
			foundvoice->master_voice = NULL;
			foundvoice->linked_voice = NULL;
		} else 
		{
			DPF("Voice not foind!\n");
		}

		return foundvoice;
	} else 
	{
		DPF("sblive_emuVoiceAlloc STEREO PLAYBACK voice");

		voicenum = 8;
		lowestvol = 0xffffffff;
		
		do 
		{
			/* Since stereo voices must be side-by-side,
			   we have to check 2 voices at once. */
			if (((voice->voices[voicenum].usage == VOICEMGR_USAGE_FREE)
			     || (voice->voices[voicenum].usage == VOICEMGR_USAGE_MIDI))
			    && ((voice->voices[voicenum + 1].usage == VOICEMGR_USAGE_FREE)
				|| (voice->voices[voicenum + 1].usage == VOICEMGR_USAGE_MIDI))) 
			{
				mastervol = 0xffffffff;
				slavevol = 0xffffffff;
				testvoice = &voice->voices[voicenum];
				testslave = &voice->voices[voicenum + 1];

				if ((!(testvoice->voicestate & VOICEMGR_STATE_TRANSITION))
				    && (!(testslave->voicestate & VOICEMGR_STATE_TRANSITION))) 
				{
					/* Voice stealing algorithm goes here */

					/* Check master voice */
					if (testvoice->usage == VOICEMGR_USAGE_FREE) 
					  mastervol = 0;
					else 
					{
						/* Check if voice is looping silence */
						CA = sblive_readptr(sb_hw, CCCA_CURRADDR, voicenum);
						
						if (CA > testvoice->voice_params.end)
						  mastervol = 0;
						else 
						{
							mastervol = sblive_readptr(sb_hw, CVCF_CURRENTVOL, voicenum);
							if (!(testvoice->voicestate & VOICEMGR_STATE_RELEASE))
							  mastervol += 0x1300; /* Why??? */
						}
					}

					/* Check slave voice */
					if (testslave->usage == VOICEMGR_USAGE_FREE)
					  slavevol = 0;
					else 
					{
						/* Check if voice is looping silence */
						CA = sblive_readptr(sb_hw, CCCA_CURRADDR, voicenum + 1);
					
						if (CA > testslave->voice_params.end)
							slavevol = 0;
						else 
						{
							slavevol = sblive_readptr(sb_hw, CVCF_CURRENTVOL, (voicenum + 1));
							if (!(testslave->voicestate & VOICEMGR_STATE_RELEASE))
								slavevol += 0x1300; /* Why??? */
						}
					}

					if ((mastervol + slavevol) < lowestvol) 
					{
						testvoice->voicestate |= VOICEMGR_STATE_TRANSITION;
						testslave->voicestate |= VOICEMGR_STATE_TRANSITION;
						
						if (foundvoice) 
						{
							foundvoice->voicestate &= ~VOICEMGR_STATE_TRANSITION;
							slavevoice->voicestate &= ~VOICEMGR_STATE_TRANSITION;
						}
						
						foundvoice = testvoice;
						slavevoice = testslave;
						lowestvol = mastervol + slavevol;
					}
				}
			}
			
			voicenum = (voicenum + 2) % NUM_G;

		} while (voicenum != 8);

		if (foundvoice) 
		{
			if (foundvoice->callback) 
			{
				/* Note that this function can change the voice suggested to another voice */
				if (foundvoice->callback(VOICECALLBACK_EVENT_FREEVOICE, foundvoice->callback_data, (u32)&foundvoice)
				    != CTSTATUS_SUCCESS) 
				{
					DPF("Voice callback failed\n");
				}
			}
			
			if (slavevoice->callback) 
			{
				/* Note that this function can change the voice suggested to another voice */
				testvoice = slavevoice;
				if (testvoice->callback(VOICECALLBACK_EVENT_FREEVOICE, testvoice->callback_data, (u32)&testvoice)
				    != CTSTATUS_SUCCESS) 
				{
					DPF("Voice callback failed\n");
				}
				
				if (testvoice != slavevoice) 
				{
					DPF("testvoice != slavevoice\n");
					return NULL;
				}
			}
			
			DPD("Voice found %x\n", (u32)voicenum);

			/* Link next voice in stereo pair */
			foundvoice->master_voice = NULL;
			foundvoice->linked_voice = slavevoice;
			slavevoice->master_voice = foundvoice;
			slavevoice->linked_voice = NULL;

			/* Mark second voice as stereo slave */
			foundvoice->flags = VOICEMGR_FLAGS_VOICEMASTER;
			slavevoice->flags = VOICEMGR_FLAGS_STEREOSLAVE;

			return foundvoice;
		}
	}

	return foundvoice;
}

/************************************************************************
*
*   static int sblive_voiceIrqCallback(unsigned long event,
*                           unsigned long refdata,unsigned long param)
*
*   ENTRY
*       event   -   event that cause the callback
*       refdata -   reference data for this callback
*       param   -   parameter used for this callback
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Voice IRQ callback function.
*
************************************************************************/
static int sblive_voiceIrqCallback(unsigned long event, unsigned long refdata, unsigned long param)
{
	struct emu_voice *pvoices;
	struct sblive_hw *sb_hw = (struct sblive_hw *) refdata;

	pvoices = &sb_hw->voice_manager.voices[param & IPR_CHANNELNUMBERMASK];
	if (pvoices->voicestate & VOICEMGR_STATE_IRQON) 
	{
		if (pvoices->callback) 
		  pvoices->callback(VOICECALLBACK_EVENT_CALLBACKSIZEREACHED, pvoices->callback_data, 0);
		
		return CTSTATUS_SUCCESS;
	}
	
	return CTSTATUS_ERROR;
}

/************************************************************************
*
*   int sblive_voiceInit(struct sblive_hw *sb_hw)
*
*   ENTRY
*       card  -    Pointer to the card obj to init voice manager
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Inits data
*
************************************************************************/
int sblive_voiceInit(struct sblive_hw *sb_hw)
{
	int i;
	struct voice_mgr *voice;
	struct emu_voice *emu_voice;
	unsigned long flags;

	voice = &sb_hw->voice_manager;
	voice->free_voices = NULL;

	emu_voice = voice->voices;
	for (i = 0; i < NUM_G; i++) 
	{
		emu_voice->sb_hw = sb_hw;
		emu_voice->usage = VOICEMGR_USAGE_FREE;
		emu_voice->voicestate = VOICEMGR_STATE_IDLE;
		emu_voice->voicenum = i;
		emu_voice->linked_voice = NULL;
		
		spin_lock_irqsave(&sblive_spinlock, flags);
		
		dlAddNode(&voice->free_voices, emu_voice);
		
		spin_unlock_irqrestore(&sblive_spinlock, flags);
		
		++emu_voice;
	}

	voice->sb_hw = sb_hw;
	voice->playback_voices = NULL;
	voice->record_voices = NULL;
	voice->midi_voices = NULL;

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int sblive_voiceExit(struct sblive_hw *sb_hw)
*
*   ENTRY
*       sb_hw  -    Pointer to the HWOBJ to init voice manager
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Exits
*
************************************************************************/
int sblive_voiceExit(struct sblive_hw *sb_hw)
{
	int i;
	struct emu_voice *emu_voice;

	emu_voice = sb_hw->voice_manager.voices;
	for (i = 0; i < NUM_G; i++) 
	{
		/* TODO: CALL ALL RELEASE CALLBACKS FOR BUSY VOICES */

		emu_voice->usage = VOICEMGR_USAGE_FREE;
		emu_voice->voicestate = VOICEMGR_STATE_IDLE;
		emu_voice->voicenum = i;
		emu_voice->linked_voice = NULL;
		++emu_voice;
	}

	sb_hw->voice_manager.sb_hw = NULL;
	sb_hw->voice_manager.free_voices = NULL;
	sb_hw->voice_manager.playback_voices = NULL;
	sb_hw->voice_manager.record_voices = NULL;
	sb_hw->voice_manager.midi_voices = NULL;

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int sblive_voiceAlloc(struct voice_allocdesc *voiceallocdesc,
*                         struct emu_voice * *voices)
*
*   ENTRY
*       voiceallocdesc   -
*           Pointer to a struct voice_allocdesc that describes the
*           voices needed.
*
*       voices        -
*           Address to return allocated struct emu_voices
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_INUSE (not enough available voices)
*                   CTSTATUS_ERROR
*
*   ABOUT
*       Allocates voices according to the types requested.
*
************************************************************************/
int sblive_voiceAlloc(struct voice_allocdesc *voiceallocdesc, struct emu_voice **voices)
{
	u32 i;
	struct emu_voice **ppOwnerList = NULL;	/* Linux: added initialization */
	struct emu_voice *allocvoicelist;
	struct emu_voice *lastvoice = NULL;	/* Linux: added initialization */
	struct emu_voice *linkvoice;
	int status = CTSTATUS_SUCCESS;
	struct sblive_hw *sb_hw = voiceallocdesc->sb_hw;
	unsigned long flags;

	if (!voiceallocdesc->numvoicereqs) 
	{
		/* No voices requested??? */
		DPF("No voices requested!\n");
		return CTSTATUS_INVALIDPARAM;
	}
	
	/* Determine 'busy' list to attach voices to */
	if (voiceallocdesc->ownertype == VOICEMGR_USAGE_PLAYBACK)
	  ppOwnerList = &sb_hw->voice_manager.playback_voices;
	else if (voiceallocdesc->ownertype == VOICEMGR_USAGE_MIDI)
	  ppOwnerList = &sb_hw->voice_manager.midi_voices;

	allocvoicelist = NULL;

	/* Satisfy voice requests one by one */
	for (i = 0; i < voiceallocdesc->numvoicereqs; i++) 
	{
		/* Allocate voices */

		linkvoice = sblive_emuVoiceAlloc(&sb_hw->voice_manager, voiceallocdesc->flags[i]);

		if (linkvoice) 
		{
			/* Suitable free voice found */
			if (allocvoicelist == NULL) 
			{
				allocvoicelist = linkvoice;
				linkvoice->master_voice = NULL;
			} else 
			{
				lastvoice->linked_voice = linkvoice;
				linkvoice->master_voice = lastvoice;
			}
			
			linkvoice->flags = VOICEMGR_FLAGS_VOICEMASTER;

			/* Repeat for all linked voices */
			while (linkvoice) 
			{
				/* Shut off the voice */
				sblive_writeptr(sb_hw, IFATN, linkvoice->voicenum, 0xffff);
				sblive_writeptr(sb_hw, DCYSUSV , linkvoice->voicenum, ENV_OFF);
				sblive_writeptr(sb_hw, VTFT, linkvoice->voicenum, 0xffff);
				sblive_writeptr(sb_hw, PTRX, linkvoice->voicenum, 0);

				spin_lock_irqsave(&sblive_spinlock, flags);

				dlDelNode(&sb_hw->voice_manager.free_voices, linkvoice); /* Remove voice from free list */

				linkvoice->sb_hw = sb_hw;
				linkvoice->ownertype = voiceallocdesc->ownertype;
				linkvoice->callback = voiceallocdesc->callback;
				linkvoice->callback_data = voiceallocdesc->callback_data;

				/* We need a transition state to make a voice
				   as belonging to a owner but not yet assigned
				   to that owner yet */
				linkvoice->voicestate |= VOICEMGR_STATE_TRANSITION;
				linkvoice->usage = voiceallocdesc->ownertype;
				linkvoice->flags |= voiceallocdesc->flags[i];

				dlAddNode(ppOwnerList, linkvoice); /* Attach voice to busy list */

				spin_unlock_irqrestore(&sblive_spinlock, flags);

				lastvoice = linkvoice; /* Remember last of linked voices */

				linkvoice = linkvoice->linked_voice;
			}
		} else 
		{
			/* No free suitable voices found - steal from busy voices */
			status = CTSTATUS_INUSE;

			/* TODO : IMPLEMENT VOICE STEALING ALGORITHM */

			DPF("Steal a voice: not yet implemented\n");
			
			spin_lock_irqsave(&sblive_spinlock, flags);
			
			while (allocvoicelist) 
			{
				linkvoice = allocvoicelist;
				allocvoicelist = allocvoicelist->linked_voice;
				dlDelNode(ppOwnerList, linkvoice);

				linkvoice->linked_voice = NULL;
				linkvoice->master_voice = NULL;
				linkvoice->flags = 0;
				linkvoice->callback = 0;
				linkvoice->callback_data = 0;
				linkvoice->voicestate = VOICEMGR_STATE_IDLE;
				linkvoice->usage = VOICEMGR_USAGE_FREE;

				dlAddNode(&sb_hw->voice_manager.free_voices, linkvoice);
			}
			
			spin_unlock_irqrestore(&sblive_spinlock, flags);

			break;
		}
	}

	if (voiceallocdesc->ownertype != VOICEMGR_USAGE_MIDI) 
	{
		linkvoice = allocvoicelist;
		
		while (linkvoice) 
		{
			linkvoice->voicestate &= ~VOICEMGR_STATE_TRANSITION;
			linkvoice = linkvoice->linked_voice;
		}
	}
	
	*voices = allocvoicelist;

	return status;
}


/************************************************************************
*
*   int sblive_voiceFree(struct emu_voice * voices)
*
*   ENTRY
*       voices -
*           pointer to a struct emu_voice returned
*           by a call to sblive_voiceAlloc()
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Frees a previously allocated voice. Multiple voices can
*       be allocated at once but must be freed individually.
*
************************************************************************/
int sblive_voiceFree(struct emu_voice *voices)
{
	struct emu_voice *voicetemp;
	struct emu_voice **ppOwnerList;
	struct sblive_hw *sb_hw;
	unsigned long flags;

	if (voices == NULL) 
	  return CTSTATUS_INVALIDPARAM;
	
	sb_hw = voices->sb_hw;

	if (voices->usage == VOICEMGR_USAGE_PLAYBACK)
	  ppOwnerList = &sb_hw->voice_manager.playback_voices;
	else if (voices->usage == VOICEMGR_USAGE_MIDI)
	  ppOwnerList = &sb_hw->voice_manager.midi_voices;
	else 
	{
		DPF("Unknown VOICEMGR_USAGE\n");
		return CTSTATUS_ERROR;
	}

	if (!(voices->flags & VOICEMGR_FLAGS_VOICEMASTER)) 
	{
		/* Not first voice in list */
		DPF("Not first voice in list!\n");
		return CTSTATUS_ERROR;
	}
	
	while (voices) 
	{
		unsigned dcysusv, voicenum;

		voicenum = voices->voicenum;
		
		sblive_writeptr(sb_hw, IFATN, voicenum, IFATN_FILTERCUTOFF_MASK | IFATN_ATTENUATION_MASK);
		sblive_writeptr(sb_hw, IP, voicenum, 0);

		dcysusv = sblive_readptr(sb_hw, DCYSUSV, voicenum) & (DCYSUSV_PHASE1_MASK | DCYSUSV_SUSTAINLEVEL_MASK | DCYSUSV_DECAYTIME_MASK);
                sblive_writeptr(sb_hw, DCYSUSV, voicenum, dcysusv | ENV_OFF);

		sblive_writeptr(sb_hw, VTFT, voicenum, VTFT_FILTERTARGET_MASK);
		sblive_writeptr(sb_hw, PTRX_PITCHTARGET, voicenum, 0);
		sblive_writeptr(sb_hw, CVCF,  voicenum, CVCF_CURRENTFILTER_MASK);
		sblive_writeptr(sb_hw, CPF, voicenum, 0);

		{
			u32 cra;
			u32 sample;
			int i;

			sample = (voices->flags & VOICEMGR_FLAGS_16BIT) ? 0 : 0x80808080;
			cra = sblive_readptr(sb_hw, CCR, voicenum) & CCR_READADDRESS_MASK;
			sblive_writeptr(sb_hw, CCR, voicenum, cra);
			cra = (cra >> 18) & 0xf;
			sblive_writeptr(sb_hw, CD0 + cra, voicenum, sample);
			cra = (cra + 0x1) & 0xf;
			sblive_writeptr(sb_hw, CD0 + cra, voicenum, sample);

			for (i = 0; i < NUM_FXSENDS; i++)
			  if (voices->sendhandle[i]) 
			    voices->sendhandle[i] = 0;
		}

		spin_lock_irqsave(&sblive_spinlock, flags);

		/* Reattach to free list */
		voicetemp = voices;
		voices = voices->linked_voice;

		dlDelNode(ppOwnerList, voicetemp);	/* Detach first */
		voicetemp->linked_voice = NULL;
		voicetemp->master_voice = NULL;
		voicetemp->flags = 0;
		voicetemp->callback = 0;
		voicetemp->callback_data = 0;
		voicetemp->voicestate = VOICEMGR_STATE_IDLE;
		voicetemp->usage = VOICEMGR_USAGE_FREE;
		dlAddNode(&sb_hw->voice_manager.free_voices, voicetemp);

		spin_unlock_irqrestore(&sblive_spinlock, flags);

	}

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int sblive_voicePlaybackSetup(struct emu_voice * voices)
*
*   ENTRY
*       voices -
*           pointer to a struct emu_voice returned
*           by a call to sblive_voiceAlloc()
*       pVoiceSetting -
*           pointer to an array of setting params
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Sets up a voices for Wave Playback
*
************************************************************************/
int sblive_voicePlaybackSetup(struct emu_voice *voices)
{
	int voicenum;
	struct sblive_hw *sb_hw;
	u32 cra = 0;
	u32 start = 0;
	struct voice_param *pvoice_params;
	struct emu_voice *currvoice;

	currvoice = voices;
	while (currvoice) 
	{
		/* Or here? (voices can be on different cards) */
		sb_hw = currvoice->sb_hw;
		voicenum = currvoice->voicenum;
		pvoice_params = &currvoice->voice_params;

		sblive_writeptr(sb_hw, DCYSUSV, voicenum, ENV_OFF);
		sblive_writeptr(sb_hw, VTFT, voicenum, VTFT_FILTERTARGET_MASK);
		sblive_writeptr(sb_hw, CVCF, voicenum, CVCF_CURRENTFILTER_MASK);

		/* Stop CA */
		/* Assumption that PT is already 0 so no harm overwriting */
		sblive_writeptr(sb_hw, PTRX, voicenum,
				(pvoice_params->unSends.tSends.reverb_send << 8) | pvoice_params->unSends.tSends.aux_send);

		if (currvoice->flags & VOICEMGR_FLAGS_VOICEMASTER) 
		{
			u32 sample;

			if (currvoice->linked_voice != NULL) 
			{
				/* Set stereo bit */
				cra = 64;
				sblive_writeptr(sb_hw, CPF, voicenum, CPF_STEREO_MASK);
				sblive_writeptr(sb_hw, CPF, voicenum + 1, CPF_STEREO_MASK);
			} else 
			{
				cra = 32;
				sblive_writeptr(sb_hw, CPF, voicenum, 0);
			}

			if (currvoice->flags & VOICEMGR_FLAGS_16BIT) 
			  sample = 0;
			else 
			{
				cra = cra * 2;
				sample = 0x80808080;
			}
			cra -= 4;

			if (currvoice->linked_voice != NULL) 
			{
				sblive_writeptr(sb_hw, CCR, voicenum, 0x3c << 16);
				sblive_writeptr(sb_hw, CCR, voicenum + 1, cra << 16);
				sblive_writeptr(sb_hw, CDE, voicenum + 1, sample);
				sblive_writeptr(sb_hw, CDF, voicenum + 1, sample);
				start = pvoice_params->start + cra / 2;
			} else 
			{
				sblive_writeptr(sb_hw, CCR, voicenum, 0x1c << 16); /* FIXME: Is 0x1c correct? */
				sblive_writeptr(sb_hw, CDE, voicenum, sample);
				sblive_writeptr(sb_hw, CDF, voicenum, sample);
				start = pvoice_params->start + cra;
			}

			if (start > pvoice_params->endloop) 
			{
				start -= pvoice_params->endloop;
				
				if (currvoice->linked_voice != NULL)
				  cra = (cra << 25) | 0x1bc0000 | ((cra - start) << 9);
				else
				  cra = (cra << 25) | 0x11c0000 | ((cra - start) << 9);
				
				start += pvoice_params->startloop;
				
				if (start >= pvoice_params->endloop)
				  start = pvoice_params->endloop - 1;
			} else 
			  if (currvoice->linked_voice != NULL)
			    cra = (cra << 25) | (0x3c << 16);
			else
			  cra = (cra << 25) | (0x1c << 16);

			start |= CCCA_INTERPROM_0;
		}
		
		/* CSL, ST, CA */
		sblive_writeptr(sb_hw, DSL, voicenum,
				pvoice_params->endloop | ((u32) pvoice_params->unSends.tSends.chorus_send << 24));

		sblive_writeptr(sb_hw, PSST, voicenum,
				pvoice_params->startloop | ((u32) pvoice_params->unSends.tSends.pan_send << 24));

		if (currvoice->flags & VOICEMGR_FLAGS_16BIT)
		  sblive_writeptr(sb_hw, CCCA, voicenum, start);
		else
		  sblive_writeptr(sb_hw, CCCA, voicenum, start | CCCA_8BITSELECT);

		/* Clear filter delay memory */
		sblive_writeptr(sb_hw, Z1, voicenum, 0);
		sblive_writeptr(sb_hw, Z2, voicenum, 0);

		/* Invalidate maps */
		sblive_writeptr(sb_hw, MAPA, voicenum, MAP_PTI_MASK | (sb_hw->silentpagephysaddx * 2));
		sblive_writeptr(sb_hw, MAPB, voicenum, MAP_PTI_MASK | (sb_hw->silentpagephysaddx * 2));

		/* Fill cache */
		if (currvoice->flags & VOICEMGR_FLAGS_VOICEMASTER)
		  sblive_writeptr(sb_hw, CCR, voicenum, cra);

		sblive_writeptr(sb_hw, ATKHLDV, voicenum, ATKHLDV_HOLDTIME_MASK | ATKHLDV_ATTACKTIME_MASK);
		sblive_writeptr(sb_hw, LFOVAL1, voicenum, 0x8000);
		sblive_writeptr(sb_hw, ATKHLDM, voicenum, 0);
		sblive_writeptr(sb_hw, DCYSUSM, voicenum, DCYSUSM_DECAYTIME_MASK);
		sblive_writeptr(sb_hw, LFOVAL2, voicenum, 0x8000);
		sblive_writeptr(sb_hw, IP,  voicenum, pvoice_params->initial_pitch);
		sblive_writeptr(sb_hw, PEFE, voicenum, 0x7f);
		sblive_writeptr(sb_hw, FMMOD, voicenum, 0);
		sblive_writeptr(sb_hw, TREMFRQ, voicenum, 0);
		sblive_writeptr(sb_hw, FM2FRQ2, voicenum, 0);
		sblive_writeptr(sb_hw, ENVVAL, voicenum, 0xbfff);
		sblive_writeptr(sb_hw, ENVVOL, voicenum, 0xbfff);
		sblive_writeptr(sb_hw, IFATN, voicenum, IFATN_FILTERCUTOFF_MASK | pvoice_params->initial_attn);
		
		pvoice_params->FC_target = 0xffff;
		pvoice_params->pitch_target = (u16)(IP_TO_CP(pvoice_params->initial_pitch) >> 16);

		currvoice = currvoice->linked_voice;
	}

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int sblive_voiceEnableIrq(struct emu_voice * voices)
*
*   ENTRY
*       voices -
*           pointer to a struct emu_voice returned
*           by a call to sblive_voiceAlloc()
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Enable irqs for 1 voice
*
************************************************************************/
int sblive_voiceEnableIrq(struct emu_voice *voices)
{
	if (sblive_irqmgrEnableVoiceIrq(voices->sb_hw, voices->voicenum) == CTSTATUS_SUCCESS) 
	{
		voices->voicestate |= VOICEMGR_STATE_IRQON;
		return CTSTATUS_SUCCESS;
	}

	return CTSTATUS_ERROR;
}


/************************************************************************
*
*   int sblive_voiceDisableIrq(struct emu_voice * voices)
*
*   ENTRY
*       voices -
*           pointer to a struct emu_voice returned
*           by a call to sblive_voiceAlloc()
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Disable irqs for 1 voice
*
************************************************************************/
int sblive_voiceDisableIrq(struct emu_voice *voices)
{
	if (sblive_irqmgrDisableVoiceIrq(voices->sb_hw, voices->voicenum) == CTSTATUS_SUCCESS) 
	{
		voices->voicestate &= ~VOICEMGR_STATE_IRQON;
		return CTSTATUS_SUCCESS;
	}

	return CTSTATUS_ERROR;
}


/************************************************************************
*
*   int sblive_voiceStart(struct emu_voice * voices)
*
*   ENTRY
*       voices -
*           pointer to a struct emu_voice returned
*           by a call to sblive_voiceAlloc()
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Starts voices
*
************************************************************************/
int sblive_voiceStart(struct emu_voice *voices)
{
	int voicenum = 0;	/* Linux: Added initialization */
	struct sblive_hw *sb_hw;
	struct emu_voice *tempvoice;
	struct voice_param *pvoice_params;

	tempvoice = voices;
	sb_hw = tempvoice->sb_hw;

	/* Actual start */
	while (tempvoice) 
	{
		if (tempvoice->flags & VOICEMGR_FLAGS_PLAYBACK) 
		{
			voicenum = tempvoice->voicenum;
			pvoice_params = &tempvoice->voice_params;

			/* Playback */
			sblive_writeptr(sb_hw, PTRX_PITCHTARGET, voicenum, pvoice_params->pitch_target);

			if (!(tempvoice->flags & VOICEMGR_FLAGS_STEREOSLAVE))
			  sblive_writeptr(sb_hw, CPF_CURRENTPITCH, voicenum, pvoice_params->pitch_target);
			
			sblive_writeptr(sb_hw, VTFT, voicenum,
					  ((u32) pvoice_params->volume_target << 16)
					  | pvoice_params->FC_target);
			sblive_writeptr(sb_hw, CVCF, voicenum,
					  ((u32) pvoice_params->volume_target << 16)
					  | pvoice_params->FC_target);
			sblive_writeptr(sb_hw, DCYSUSV , voicenum,
					  (pvoice_params->byampl_env_sustain << 8)
					  | ENV_ON | pvoice_params->byampl_env_decay);
			
			/* Using StopOnLoop for MIDI stops the playback
			   too early, which may cause a DC level to be played
			   until the note is released. */
			
			if (tempvoice->usage == VOICEMGR_USAGE_MIDI)
			  halClearStopOnLoop(sb_hw, voicenum);
			else
			{
				if (pvoice_params->startloop > pvoice_params->end)
				  halSetStopOnLoop(sb_hw, voicenum);
				else
				  halClearStopOnLoop(sb_hw, voicenum);
			}
		} else 
		  /* Recording */
		  sblive_writeptr(sb_hw, DCYSUSV, voicenum, DCYSUSV_SUSTAINLEVEL_MASK |  DCYSUSV_DECAYTIME_MASK | ENV_ON);

		tempvoice->voicestate |= VOICEMGR_STATE_ATTACK;
		tempvoice = tempvoice->linked_voice;
	}

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int sblive_voiceStop(struct emu_voice * voices)
*
*   ENTRY
*       voices -
*           pointer to a struct emu_voice returned
*           by a call to sblive_voiceAlloc()
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Stops voices
*
************************************************************************/
int sblive_voiceStop(struct emu_voice *voices)
{
	int voicenum;
	struct sblive_hw *sb_hw = voices->sb_hw;
	struct emu_voice *tempvoice = voices;
	unsigned long flags;

	spin_lock_irqsave(&sblive_spinlock, flags);
	
	while (tempvoice) 
	{
		voicenum = tempvoice->voicenum;
		
		if (voices->flags & VOICEMGR_FLAGS_PLAYBACK) 
		{
			sblive_writeptr(sb_hw, IFATN, voicenum, 0xffff);
			sblive_writeptr(sb_hw, IP, voicenum, 0);
			sblive_writeptr(sb_hw, VTFT, voicenum, 0xffff);
			sblive_writeptr(sb_hw, PTRX_PITCHTARGET, voicenum, 0);
		}

		tempvoice->voicestate = VOICEMGR_STATE_IDLE;
		tempvoice = tempvoice->linked_voice;
	}

	tempvoice = voices;
	
	spin_unlock_irqrestore(&sblive_spinlock, flags);

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int sblive_voiceSetControl(struct emu_voice *voices,
*                              struct voice_cntlset *pSetting,
*                              u32 numparam)
*
*   ENTRY
*       voices -
*           pointer to a struct emu_voice returned
*           by a call to sblive_voiceAlloc()
*
*       pSetting    -
*           An array of CNTLSET structures
*
*       numparam  -
*           Number of struct voice_cntlset structures in pSetting
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Sets a voice control
*
************************************************************************/
int sblive_voiceSetControl(struct emu_voice *voices, struct voice_cntlset *setting, u32 numparam)
{
	struct sblive_hw *sb_hw = voices->sb_hw;
	struct emu_voice *currvoice = voices;
	u32 count;

	for (count = 0; count < numparam; count++) 
	  sblive_writeptr(sb_hw, setting[count].paramID, currvoice->voicenum, setting[count].value);

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int sblive_voiceGetControl(struct emu_voice * voices,
*                              u32 controlid, u32 *value)
*
*   ENTRY
*       voices -
*           pointer to a struct emu_voice returned
*           by a call to sblive_voiceAlloc()
*
*       controlid -
*           control to get
*
*       pvalue    -
*           address to return control value
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Gets a voice control
*
************************************************************************/
int sblive_voiceGetControl(struct emu_voice *voices, u32 controlid, u32 *value)
{
	struct sblive_hw *sb_hw = voices->sb_hw;

	*value = sblive_readptr(sb_hw, controlid, voices->voicenum);

	return CTSTATUS_SUCCESS;
}
