| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  *  synth callback routines for Emu10k1 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  Copyright (C) 2000 Takashi Iwai <tiwai@suse.de> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "emu10k1_synth_local.h"
 | 
					
						
							|  |  |  | #include <sound/asoundef.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* voice status */ | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  | 	V_FREE=0, V_OFF, V_RELEASED, V_PLAYING, V_END | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Keeps track of what we are finding */ | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | struct best_voice { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	unsigned int time; | 
					
						
							|  |  |  | 	int voice; | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * prototypes | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2007-11-10 17:55:14 +00:00
										 |  |  | static void lookup_voices(struct snd_emux *emux, struct snd_emu10k1 *hw, | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 			  struct best_voice *best, int active_only); | 
					
						
							| 
									
										
										
										
											2007-11-10 17:55:14 +00:00
										 |  |  | static struct snd_emux_voice *get_voice(struct snd_emux *emux, | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 					struct snd_emux_port *port); | 
					
						
							|  |  |  | static int start_voice(struct snd_emux_voice *vp); | 
					
						
							|  |  |  | static void trigger_voice(struct snd_emux_voice *vp); | 
					
						
							|  |  |  | static void release_voice(struct snd_emux_voice *vp); | 
					
						
							|  |  |  | static void update_voice(struct snd_emux_voice *vp, int update); | 
					
						
							|  |  |  | static void terminate_voice(struct snd_emux_voice *vp); | 
					
						
							|  |  |  | static void free_voice(struct snd_emux_voice *vp); | 
					
						
							|  |  |  | static void set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); | 
					
						
							|  |  |  | static void set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); | 
					
						
							|  |  |  | static void set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Ensure a value is between two points | 
					
						
							|  |  |  |  * macro evaluates its args more than once, so changed to upper-case. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0)
 | 
					
						
							|  |  |  | #define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * set up operators | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | static struct snd_emux_operators emu10k1_ops = { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	.owner =	THIS_MODULE, | 
					
						
							|  |  |  | 	.get_voice =	get_voice, | 
					
						
							|  |  |  | 	.prepare =	start_voice, | 
					
						
							|  |  |  | 	.trigger =	trigger_voice, | 
					
						
							|  |  |  | 	.release =	release_voice, | 
					
						
							|  |  |  | 	.update =	update_voice, | 
					
						
							|  |  |  | 	.terminate =	terminate_voice, | 
					
						
							|  |  |  | 	.free_voice =	free_voice, | 
					
						
							|  |  |  | 	.sample_new =	snd_emu10k1_sample_new, | 
					
						
							|  |  |  | 	.sample_free =	snd_emu10k1_sample_free, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void | 
					
						
							| 
									
										
										
										
											2007-11-10 17:55:14 +00:00
										 |  |  | snd_emu10k1_ops_setup(struct snd_emux *emux) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2007-11-10 17:55:14 +00:00
										 |  |  | 	emux->ops = emu10k1_ops; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * get more voice for pcm | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * terminate most inactive voice and give it as a pcm voice. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | int | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_emux *emu; | 
					
						
							|  |  |  | 	struct snd_emux_voice *vp; | 
					
						
							|  |  |  | 	struct best_voice best[V_END]; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	unsigned long flags; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	emu = hw->synth; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	spin_lock_irqsave(&emu->voice_lock, flags); | 
					
						
							|  |  |  | 	lookup_voices(emu, hw, best, 1); /* no OFF voices */ | 
					
						
							|  |  |  | 	for (i = 0; i < V_END; i++) { | 
					
						
							|  |  |  | 		if (best[i].voice >= 0) { | 
					
						
							|  |  |  | 			int ch; | 
					
						
							|  |  |  | 			vp = &emu->voices[best[i].voice]; | 
					
						
							|  |  |  | 			if ((ch = vp->ch) < 0) { | 
					
						
							| 
									
										
										
										
											2009-02-05 16:08:14 +01:00
										 |  |  | 				/*
 | 
					
						
							|  |  |  | 				printk(KERN_WARNING | 
					
						
							|  |  |  | 				       "synth_get_voice: ch < 0 (%d) ??", i); | 
					
						
							|  |  |  | 				*/ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				continue; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			vp->emu->num_voices--; | 
					
						
							|  |  |  | 			vp->ch = -1; | 
					
						
							|  |  |  | 			vp->state = SNDRV_EMUX_ST_OFF; | 
					
						
							|  |  |  | 			spin_unlock_irqrestore(&emu->voice_lock, flags); | 
					
						
							|  |  |  | 			return ch; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	spin_unlock_irqrestore(&emu->voice_lock, flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* not found */ | 
					
						
							|  |  |  | 	return -ENOMEM; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * turn off the voice (not terminated) | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | release_voice(struct snd_emux_voice *vp) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	int dcysusv; | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_emu10k1 *hw; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	hw = vp->hw; | 
					
						
							|  |  |  | 	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, DCYSUSM, vp->ch, dcysusv); | 
					
						
							|  |  |  | 	dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease | DCYSUSV_CHANNELENABLE_MASK; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, dcysusv); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * terminate the voice | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | terminate_voice(struct snd_emux_voice *vp) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_emu10k1 *hw; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	 | 
					
						
							| 
									
										
										
										
											2008-08-08 17:12:14 +02:00
										 |  |  | 	if (snd_BUG_ON(!vp)) | 
					
						
							|  |  |  | 		return; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	hw = vp->hw; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK); | 
					
						
							|  |  |  | 	if (vp->block) { | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 		struct snd_emu10k1_memblk *emem; | 
					
						
							|  |  |  | 		emem = (struct snd_emu10k1_memblk *)vp->block; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (emem->map_locked > 0) | 
					
						
							|  |  |  | 			emem->map_locked--; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * release the voice to system | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | free_voice(struct snd_emux_voice *vp) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_emu10k1 *hw; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	hw = vp->hw; | 
					
						
							| 
									
										
										
										
											2007-11-10 17:55:14 +00:00
										 |  |  | 	/* FIXME: emu10k1_synth is broken. */ | 
					
						
							|  |  |  | 	/* This can get called with hw == 0 */ | 
					
						
							|  |  |  | 	/* Problem apparent on plug, unplug then plug */ | 
					
						
							|  |  |  | 	/* on the Audigy 2 ZS Notebook. */ | 
					
						
							|  |  |  | 	if (hw && (vp->ch >= 0)) { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		snd_emu10k1_ptr_write(hw, IFATN, vp->ch, 0xff00); | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK); | 
					
						
							|  |  |  | 		// snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0);
 | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, VTFT, vp->ch, 0xffff); | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, CVCF, vp->ch, 0xffff); | 
					
						
							|  |  |  | 		snd_emu10k1_voice_free(hw, &hw->voices[vp->ch]); | 
					
						
							|  |  |  | 		vp->emu->num_voices--; | 
					
						
							|  |  |  | 		vp->ch = -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * update registers | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | update_voice(struct snd_emux_voice *vp, int update) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_emu10k1 *hw; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	hw = vp->hw; | 
					
						
							|  |  |  | 	if (update & SNDRV_EMUX_UPDATE_VOLUME) | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, IFATN_ATTENUATION, vp->ch, vp->avol); | 
					
						
							|  |  |  | 	if (update & SNDRV_EMUX_UPDATE_PITCH) | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch); | 
					
						
							|  |  |  | 	if (update & SNDRV_EMUX_UPDATE_PAN) { | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_A, vp->ch, vp->apan); | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_B, vp->ch, vp->aaux); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (update & SNDRV_EMUX_UPDATE_FMMOD) | 
					
						
							|  |  |  | 		set_fmmod(hw, vp); | 
					
						
							|  |  |  | 	if (update & SNDRV_EMUX_UPDATE_TREMFREQ) | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq); | 
					
						
							|  |  |  | 	if (update & SNDRV_EMUX_UPDATE_FM2FRQ2) | 
					
						
							|  |  |  | 		set_fm2frq2(hw, vp); | 
					
						
							|  |  |  | 	if (update & SNDRV_EMUX_UPDATE_Q) | 
					
						
							|  |  |  | 		set_filterQ(hw, vp); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * look up voice table - get the best voice in order of preference | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | /* spinlock held! */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | lookup_voices(struct snd_emux *emu, struct snd_emu10k1 *hw, | 
					
						
							|  |  |  | 	      struct best_voice *best, int active_only) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_emux_voice *vp; | 
					
						
							|  |  |  | 	struct best_voice *bp; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int  i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < V_END; i++) { | 
					
						
							|  |  |  | 		best[i].time = (unsigned int)-1; /* XXX MAX_?INT really */; | 
					
						
							|  |  |  | 		best[i].voice = -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Go through them all and get a best one to use. | 
					
						
							|  |  |  | 	 * NOTE: could also look at volume and pick the quietest one. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	for (i = 0; i < emu->max_voices; i++) { | 
					
						
							|  |  |  | 		int state, val; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		vp = &emu->voices[i]; | 
					
						
							|  |  |  | 		state = vp->state; | 
					
						
							|  |  |  | 		if (state == SNDRV_EMUX_ST_OFF) { | 
					
						
							|  |  |  | 			if (vp->ch < 0) { | 
					
						
							|  |  |  | 				if (active_only) | 
					
						
							|  |  |  | 					continue; | 
					
						
							|  |  |  | 				bp = best + V_FREE; | 
					
						
							|  |  |  | 			} else | 
					
						
							|  |  |  | 				bp = best + V_OFF; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		else if (state == SNDRV_EMUX_ST_RELEASED || | 
					
						
							|  |  |  | 			 state == SNDRV_EMUX_ST_PENDING) { | 
					
						
							|  |  |  | 			bp = best + V_RELEASED; | 
					
						
							| 
									
										
										
										
											2005-10-25 11:10:55 +02:00
										 |  |  | #if 1
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			val = snd_emu10k1_ptr_read(hw, CVCF_CURRENTVOL, vp->ch); | 
					
						
							|  |  |  | 			if (! val) | 
					
						
							|  |  |  | 				bp = best + V_OFF; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		else if (state == SNDRV_EMUX_ST_STANDBY) | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 		else if (state & SNDRV_EMUX_ST_ON) | 
					
						
							|  |  |  | 			bp = best + V_PLAYING; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* check if sample is finished playing (non-looping only) */ | 
					
						
							|  |  |  | 		if (bp != best + V_OFF && bp != best + V_FREE && | 
					
						
							|  |  |  | 		    (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) { | 
					
						
							|  |  |  | 			val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch); | 
					
						
							|  |  |  | 			if (val >= vp->reg.loopstart) | 
					
						
							|  |  |  | 				bp = best + V_OFF; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (vp->time < bp->time) { | 
					
						
							|  |  |  | 			bp->time = vp->time; | 
					
						
							|  |  |  | 			bp->voice = i; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * get an empty voice | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * emu->voice_lock is already held. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | static struct snd_emux_voice * | 
					
						
							|  |  |  | get_voice(struct snd_emux *emu, struct snd_emux_port *port) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_emu10k1 *hw; | 
					
						
							|  |  |  | 	struct snd_emux_voice *vp; | 
					
						
							|  |  |  | 	struct best_voice best[V_END]; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hw = emu->hw; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lookup_voices(emu, hw, best, 0); | 
					
						
							|  |  |  | 	for (i = 0; i < V_END; i++) { | 
					
						
							|  |  |  | 		if (best[i].voice >= 0) { | 
					
						
							|  |  |  | 			vp = &emu->voices[best[i].voice]; | 
					
						
							|  |  |  | 			if (vp->ch < 0) { | 
					
						
							|  |  |  | 				/* allocate a voice */ | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 				struct snd_emu10k1_voice *hwvoice; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, &hwvoice) < 0 || hwvoice == NULL) | 
					
						
							|  |  |  | 					continue; | 
					
						
							|  |  |  | 				vp->ch = hwvoice->number; | 
					
						
							|  |  |  | 				emu->num_voices++; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return vp; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* not found */ | 
					
						
							|  |  |  | 	return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * prepare envelopes and LFOs | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | start_voice(struct snd_emux_voice *vp) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned int temp; | 
					
						
							|  |  |  | 	int ch; | 
					
						
							|  |  |  | 	unsigned int addr, mapped_offset; | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_midi_channel *chan; | 
					
						
							|  |  |  | 	struct snd_emu10k1 *hw; | 
					
						
							|  |  |  | 	struct snd_emu10k1_memblk *emem; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	hw = vp->hw; | 
					
						
							|  |  |  | 	ch = vp->ch; | 
					
						
							| 
									
										
										
										
											2008-08-08 17:12:14 +02:00
										 |  |  | 	if (snd_BUG_ON(ch < 0)) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	chan = vp->chan; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	emem = (struct snd_emu10k1_memblk *)vp->block; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (emem == NULL) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	emem->map_locked++; | 
					
						
							|  |  |  | 	if (snd_emu10k1_memblk_map(hw, emem) < 0) { | 
					
						
							| 
									
										
										
										
											2009-02-05 16:08:14 +01:00
										 |  |  | 		/* printk(KERN_ERR "emu: cannot map!\n"); */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mapped_offset = snd_emu10k1_memblk_offset(emem) >> 1; | 
					
						
							|  |  |  | 	vp->reg.start += mapped_offset; | 
					
						
							|  |  |  | 	vp->reg.end += mapped_offset; | 
					
						
							|  |  |  | 	vp->reg.loopstart += mapped_offset; | 
					
						
							|  |  |  | 	vp->reg.loopend += mapped_offset; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* set channel routing */ | 
					
						
							|  |  |  | 	/* A = left(0), B = right(1), C = reverb(c), D = chorus(d) */ | 
					
						
							|  |  |  | 	if (hw->audigy) { | 
					
						
							|  |  |  | 		temp = FXBUS_MIDI_LEFT | (FXBUS_MIDI_RIGHT << 8) |  | 
					
						
							|  |  |  | 			(FXBUS_MIDI_REVERB << 16) | (FXBUS_MIDI_CHORUS << 24); | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, A_FXRT1, ch, temp); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		temp = (FXBUS_MIDI_LEFT << 16) | (FXBUS_MIDI_RIGHT << 20) |  | 
					
						
							|  |  |  | 			(FXBUS_MIDI_REVERB << 24) | (FXBUS_MIDI_CHORUS << 28); | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, FXRT, ch, temp); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* channel to be silent and idle */ | 
					
						
							| 
									
										
										
										
											2005-10-25 11:10:55 +02:00
										 |  |  | 	snd_emu10k1_ptr_write(hw, DCYSUSV, ch, 0x0000); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	snd_emu10k1_ptr_write(hw, VTFT, ch, 0x0000FFFF); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, CVCF, ch, 0x0000FFFF); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, PTRX, ch, 0); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, CPF, ch, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* set pitch offset */ | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* set envelope parameters */ | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, ENVVAL, ch, vp->reg.parm.moddelay); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, ATKHLDM, ch, vp->reg.parm.modatkhld); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, DCYSUSM, ch, vp->reg.parm.moddcysus); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, ENVVOL, ch, vp->reg.parm.voldelay); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, ATKHLDV, ch, vp->reg.parm.volatkhld); | 
					
						
							|  |  |  | 	/* decay/sustain parameter for volume envelope is used
 | 
					
						
							|  |  |  | 	   for triggerg the voice */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* cutoff and volume */ | 
					
						
							|  |  |  | 	temp = (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, IFATN, vp->ch, temp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* modulation envelope heights */ | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, PEFE, ch, vp->reg.parm.pefe); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* lfo1/2 delay */ | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, LFOVAL1, ch, vp->reg.parm.lfo1delay); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, LFOVAL2, ch, vp->reg.parm.lfo2delay); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* lfo1 pitch & cutoff shift */ | 
					
						
							|  |  |  | 	set_fmmod(hw, vp); | 
					
						
							|  |  |  | 	/* lfo1 volume & freq */ | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq); | 
					
						
							|  |  |  | 	/* lfo2 pitch & freq */ | 
					
						
							|  |  |  | 	set_fm2frq2(hw, vp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* reverb and loop start (reverb 8bit, MSB) */ | 
					
						
							|  |  |  | 	temp = vp->reg.parm.reverb; | 
					
						
							|  |  |  | 	temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10; | 
					
						
							|  |  |  | 	LIMITMAX(temp, 255); | 
					
						
							|  |  |  | 	addr = vp->reg.loopstart; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, PSST, vp->ch, (temp << 24) | addr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* chorus & loop end (chorus 8bit, MSB) */ | 
					
						
							|  |  |  | 	addr = vp->reg.loopend; | 
					
						
							|  |  |  | 	temp = vp->reg.parm.chorus; | 
					
						
							|  |  |  | 	temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10; | 
					
						
							|  |  |  | 	LIMITMAX(temp, 255); | 
					
						
							|  |  |  | 	temp = (temp <<24) | addr; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, DSL, ch, temp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* clear filter delay memory */ | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, Z1, ch, 0); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, Z2, ch, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* invalidate maps */ | 
					
						
							|  |  |  | 	temp = (hw->silent_page.addr << 1) | MAP_PTI_MASK; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, MAPA, ch, temp); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, MAPB, ch, temp); | 
					
						
							|  |  |  | #if 0
 | 
					
						
							|  |  |  | 	/* cache */ | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		unsigned int val, sample; | 
					
						
							|  |  |  | 		val = 32; | 
					
						
							|  |  |  | 		if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS) | 
					
						
							|  |  |  | 			sample = 0x80808080; | 
					
						
							|  |  |  | 		else { | 
					
						
							|  |  |  | 			sample = 0; | 
					
						
							|  |  |  | 			val *= 2; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* cache */ | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, CCR, ch, 0x1c << 16); | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, CDE, ch, sample); | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, CDF, ch, sample); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* invalidate maps */ | 
					
						
							|  |  |  | 		temp = ((unsigned int)hw->silent_page.addr << 1) | MAP_PTI_MASK; | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, MAPA, ch, temp); | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, MAPB, ch, temp); | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		/* fill cache */ | 
					
						
							|  |  |  | 		val -= 4; | 
					
						
							|  |  |  | 		val <<= 25; | 
					
						
							|  |  |  | 		val |= 0x1c << 16; | 
					
						
							|  |  |  | 		snd_emu10k1_ptr_write(hw, CCR, ch, val); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Q & current address (Q 4bit value, MSB) */ | 
					
						
							|  |  |  | 	addr = vp->reg.start; | 
					
						
							|  |  |  | 	temp = vp->reg.parm.filterQ; | 
					
						
							|  |  |  | 	temp = (temp<<28) | addr; | 
					
						
							|  |  |  | 	if (vp->apitch < 0xe400) | 
					
						
							|  |  |  | 		temp |= CCCA_INTERPROM_0; | 
					
						
							|  |  |  | 	else { | 
					
						
							|  |  |  | 		unsigned int shift = (vp->apitch - 0xe000) >> 10; | 
					
						
							|  |  |  | 		temp |= shift << 25; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS) | 
					
						
							|  |  |  | 		temp |= CCCA_8BITSELECT; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, CCCA, ch, temp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* reset volume */ | 
					
						
							|  |  |  | 	temp = (unsigned int)vp->vtarget << 16; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, VTFT, ch, temp | vp->ftarget); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, CVCF, ch, temp | 0xff00); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Start envelope | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | trigger_voice(struct snd_emux_voice *vp) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned int temp, ptarget; | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	struct snd_emu10k1 *hw; | 
					
						
							|  |  |  | 	struct snd_emu10k1_memblk *emem; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	hw = vp->hw; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | 	emem = (struct snd_emu10k1_memblk *)vp->block; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (! emem || emem->mapped_page < 0) | 
					
						
							|  |  |  | 		return; /* not mapped */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #if 0
 | 
					
						
							|  |  |  | 	ptarget = (unsigned int)vp->ptarget << 16; | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | 	ptarget = IP_TO_CP(vp->apitch); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 	/* set pitch target and pan (volume) */ | 
					
						
							|  |  |  | 	temp = ptarget | (vp->apan << 8) | vp->aaux; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, PTRX, vp->ch, temp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* pitch target */ | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, CPF, vp->ch, ptarget); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* trigger voice */ | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, vp->reg.parm.voldcysus|DCYSUSV_CHANNELENABLE_MASK); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define MOD_SENSE 18
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* set lfo1 modulation height and cutoff */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned short fmmod; | 
					
						
							|  |  |  | 	short pitch; | 
					
						
							|  |  |  | 	unsigned char cutoff; | 
					
						
							|  |  |  | 	int modulation; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pitch = (char)(vp->reg.parm.fmmod>>8); | 
					
						
							|  |  |  | 	cutoff = (vp->reg.parm.fmmod & 0xff); | 
					
						
							|  |  |  | 	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; | 
					
						
							|  |  |  | 	pitch += (MOD_SENSE * modulation) / 1200; | 
					
						
							|  |  |  | 	LIMITVALUE(pitch, -128, 127); | 
					
						
							|  |  |  | 	fmmod = ((unsigned char)pitch<<8) | cutoff; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, fmmod); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* set lfo2 pitch & frequency */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned short fm2frq2; | 
					
						
							|  |  |  | 	short pitch; | 
					
						
							|  |  |  | 	unsigned char freq; | 
					
						
							|  |  |  | 	int modulation; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pitch = (char)(vp->reg.parm.fm2frq2>>8); | 
					
						
							|  |  |  | 	freq = vp->reg.parm.fm2frq2 & 0xff; | 
					
						
							|  |  |  | 	modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; | 
					
						
							|  |  |  | 	pitch += (MOD_SENSE * modulation) / 1200; | 
					
						
							|  |  |  | 	LIMITVALUE(pitch, -128, 127); | 
					
						
							|  |  |  | 	fm2frq2 = ((unsigned char)pitch<<8) | freq; | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, fm2frq2); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* set filterQ */ | 
					
						
							|  |  |  | static void | 
					
						
							| 
									
										
										
										
											2005-11-17 14:50:13 +01:00
										 |  |  | set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned int val; | 
					
						
							|  |  |  | 	val = snd_emu10k1_ptr_read(hw, CCCA, vp->ch) & ~CCCA_RESONANCE; | 
					
						
							|  |  |  | 	val |= (vp->reg.parm.filterQ << 28); | 
					
						
							|  |  |  | 	snd_emu10k1_ptr_write(hw, CCCA, vp->ch, val); | 
					
						
							|  |  |  | } |