2005-04-16 15:20:36 -07:00
/*
* synth callback routines for the emu8000 ( AWE32 / 64 )
*
* Copyright ( C ) 1999 Steve Ratcliffe
* Copyright ( C ) 1999 - 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 "emu8000_local.h"
2011-09-22 09:34:58 -04:00
# include <linux/export.h>
2005-04-16 15:20:36 -07:00
# include <sound/asoundef.h>
/*
* prototypes
*/
2005-11-17 14:34:36 +01:00
static struct snd_emux_voice * get_voice ( struct snd_emux * emu ,
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 reset_voice ( struct snd_emux * emu , int ch ) ;
static void terminate_voice ( struct snd_emux_voice * vp ) ;
static void sysex ( struct snd_emux * emu , char * buf , int len , int parsed ,
struct snd_midi_channel_set * chset ) ;
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_SND_SEQUENCER_OSS
2005-11-17 14:34:36 +01:00
static int oss_ioctl ( struct snd_emux * emu , int cmd , int p1 , int p2 ) ;
2005-04-16 15:20:36 -07:00
# endif
2005-11-17 14:34:36 +01:00
static int load_fx ( struct snd_emux * emu , int type , int mode ,
const void __user * buf , long len ) ;
static void set_pitch ( struct snd_emu8000 * hw , struct snd_emux_voice * vp ) ;
static void set_volume ( struct snd_emu8000 * hw , struct snd_emux_voice * vp ) ;
static void set_pan ( struct snd_emu8000 * hw , struct snd_emux_voice * vp ) ;
static void set_fmmod ( struct snd_emu8000 * hw , struct snd_emux_voice * vp ) ;
static void set_tremfreq ( struct snd_emu8000 * hw , struct snd_emux_voice * vp ) ;
static void set_fm2frq2 ( struct snd_emu8000 * hw , struct snd_emux_voice * vp ) ;
static void set_filterQ ( struct snd_emu8000 * hw , struct snd_emux_voice * vp ) ;
static void snd_emu8000_tweak_voice ( struct snd_emu8000 * emu , int ch ) ;
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:34:36 +01:00
static struct snd_emux_operators emu8000_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 ,
. reset = reset_voice ,
. sample_new = snd_emu8000_sample_new ,
. sample_free = snd_emu8000_sample_free ,
. sample_reset = snd_emu8000_sample_reset ,
. load_fx = load_fx ,
. sysex = sysex ,
# ifdef CONFIG_SND_SEQUENCER_OSS
. oss_ioctl = oss_ioctl ,
# endif
} ;
void
2005-11-17 14:34:36 +01:00
snd_emu8000_ops_setup ( struct snd_emu8000 * hw )
2005-04-16 15:20:36 -07:00
{
hw - > emu - > ops = emu8000_ops ;
}
/*
* Terminate a voice
*/
static void
2005-11-17 14:34:36 +01:00
release_voice ( struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
int dcysusv ;
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = vp - > hw ;
dcysusv = 0x8000 | ( unsigned char ) vp - > reg . parm . modrelease ;
EMU8000_DCYSUS_WRITE ( hw , vp - > ch , dcysusv ) ;
dcysusv = 0x8000 | ( unsigned char ) vp - > reg . parm . volrelease ;
EMU8000_DCYSUSV_WRITE ( hw , vp - > ch , dcysusv ) ;
}
/*
*/
static void
2005-11-17 14:34:36 +01:00
terminate_voice ( struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = vp - > hw ;
EMU8000_DCYSUSV_WRITE ( hw , vp - > ch , 0x807F ) ;
}
/*
*/
static void
2005-11-17 14:34:36 +01:00
update_voice ( struct snd_emux_voice * vp , int update )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = vp - > hw ;
if ( update & SNDRV_EMUX_UPDATE_VOLUME )
set_volume ( hw , vp ) ;
if ( update & SNDRV_EMUX_UPDATE_PITCH )
set_pitch ( hw , vp ) ;
if ( ( update & SNDRV_EMUX_UPDATE_PAN ) & &
vp - > port - > ctrls [ EMUX_MD_REALTIME_PAN ] )
set_pan ( hw , vp ) ;
if ( update & SNDRV_EMUX_UPDATE_FMMOD )
set_fmmod ( hw , vp ) ;
if ( update & SNDRV_EMUX_UPDATE_TREMFREQ )
set_tremfreq ( hw , vp ) ;
if ( update & SNDRV_EMUX_UPDATE_FM2FRQ2 )
set_fm2frq2 ( hw , vp ) ;
if ( update & SNDRV_EMUX_UPDATE_Q )
set_filterQ ( hw , vp ) ;
}
/*
* Find a channel ( voice ) within the EMU that is not in use or at least
* less in use than other channels . Always returns a valid pointer
* no matter what . If there is a real shortage of voices then one
* will be cut . Such is life .
*
* The channel index ( vp - > ch ) must be initialized in this routine .
* In Emu8k , it is identical with the array index .
*/
2005-11-17 14:34:36 +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
{
int i ;
2005-11-17 14:34:36 +01:00
struct snd_emux_voice * vp ;
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
/* what we are looking for, in order of preference */
enum {
OFF = 0 , RELEASED , PLAYING , END
} ;
/* Keeps track of what we are finding */
struct best {
unsigned int time ;
int voice ;
} best [ END ] ;
struct best * bp ;
hw = emu - > hw ;
for ( i = 0 ; i < END ; i + + ) {
2012-09-28 11:24:57 +02:00
best [ i ] . time = ( unsigned int ) ( - 1 ) ; /* XXX MAX_?INT really */
2005-04-16 15:20:36 -07:00
best [ i ] . voice = - 1 ;
}
/*
* Go through them all and get a best one to use .
*/
for ( i = 0 ; i < emu - > max_voices ; i + + ) {
int state , val ;
vp = & emu - > voices [ i ] ;
state = vp - > state ;
if ( state = = SNDRV_EMUX_ST_OFF )
bp = best + OFF ;
else if ( state = = SNDRV_EMUX_ST_RELEASED | |
state = = SNDRV_EMUX_ST_PENDING ) {
bp = best + RELEASED ;
val = ( EMU8000_CVCF_READ ( hw , vp - > ch ) > > 16 ) & 0xffff ;
if ( ! val )
bp = best + OFF ;
}
else if ( state & SNDRV_EMUX_ST_ON )
bp = best + PLAYING ;
else
continue ;
/* check if sample is finished playing (non-looping only) */
if ( state ! = SNDRV_EMUX_ST_OFF & &
( vp - > reg . sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT ) ) {
val = EMU8000_CCCA_READ ( hw , vp - > ch ) & 0xffffff ;
if ( val > = vp - > reg . loopstart )
bp = best + OFF ;
}
if ( vp - > time < bp - > time ) {
bp - > time = vp - > time ;
bp - > voice = i ;
}
}
for ( i = 0 ; i < END ; i + + ) {
if ( best [ i ] . voice > = 0 ) {
vp = & emu - > voices [ best [ i ] . voice ] ;
vp - > ch = best [ i ] . voice ;
return vp ;
}
}
/* not found */
return NULL ;
}
/*
*/
static int
2005-11-17 14:34:36 +01:00
start_voice ( struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
unsigned int temp ;
int ch ;
int addr ;
2005-11-17 14:34:36 +01:00
struct snd_midi_channel * chan ;
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = vp - > hw ;
ch = vp - > ch ;
chan = vp - > chan ;
/* channel to be silent and idle */
EMU8000_DCYSUSV_WRITE ( hw , ch , 0x0080 ) ;
EMU8000_VTFT_WRITE ( hw , ch , 0x0000FFFF ) ;
EMU8000_CVCF_WRITE ( hw , ch , 0x0000FFFF ) ;
EMU8000_PTRX_WRITE ( hw , ch , 0 ) ;
EMU8000_CPF_WRITE ( hw , ch , 0 ) ;
/* set pitch offset */
set_pitch ( hw , vp ) ;
/* set envelope parameters */
EMU8000_ENVVAL_WRITE ( hw , ch , vp - > reg . parm . moddelay ) ;
EMU8000_ATKHLD_WRITE ( hw , ch , vp - > reg . parm . modatkhld ) ;
EMU8000_DCYSUS_WRITE ( hw , ch , vp - > reg . parm . moddcysus ) ;
EMU8000_ENVVOL_WRITE ( hw , ch , vp - > reg . parm . voldelay ) ;
EMU8000_ATKHLDV_WRITE ( hw , ch , vp - > reg . parm . volatkhld ) ;
/* decay/sustain parameter for volume envelope is used
for triggerg the voice */
/* cutoff and volume */
set_volume ( hw , vp ) ;
/* modulation envelope heights */
EMU8000_PEFE_WRITE ( hw , ch , vp - > reg . parm . pefe ) ;
/* lfo1/2 delay */
EMU8000_LFO1VAL_WRITE ( hw , ch , vp - > reg . parm . lfo1delay ) ;
EMU8000_LFO2VAL_WRITE ( hw , ch , vp - > reg . parm . lfo2delay ) ;
/* lfo1 pitch & cutoff shift */
set_fmmod ( hw , vp ) ;
/* lfo1 volume & freq */
set_tremfreq ( hw , vp ) ;
/* lfo2 pitch & freq */
set_fm2frq2 ( hw , vp ) ;
/* pan & loop start */
set_pan ( hw , vp ) ;
/* chorus & loop end (chorus 8bit, MSB) */
addr = vp - > reg . loopend - 1 ;
temp = vp - > reg . parm . chorus ;
temp + = ( int ) chan - > control [ MIDI_CTL_E3_CHORUS_DEPTH ] * 9 / 10 ;
LIMITMAX ( temp , 255 ) ;
temp = ( temp < < 24 ) | ( unsigned int ) addr ;
EMU8000_CSL_WRITE ( hw , ch , temp ) ;
/* Q & current address (Q 4bit value, MSB) */
addr = vp - > reg . start - 1 ;
temp = vp - > reg . parm . filterQ ;
temp = ( temp < < 28 ) | ( unsigned int ) addr ;
EMU8000_CCCA_WRITE ( hw , ch , temp ) ;
/* clear unknown registers */
EMU8000_00A0_WRITE ( hw , ch , 0 ) ;
EMU8000_0080_WRITE ( hw , ch , 0 ) ;
/* reset volume */
temp = vp - > vtarget < < 16 ;
EMU8000_VTFT_WRITE ( hw , ch , temp | vp - > ftarget ) ;
EMU8000_CVCF_WRITE ( hw , ch , temp | 0xff00 ) ;
return 0 ;
}
/*
* Start envelope
*/
static void
2005-11-17 14:34:36 +01:00
trigger_voice ( struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
int ch = vp - > ch ;
unsigned int temp ;
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = vp - > hw ;
/* set reverb and pitch target */
temp = vp - > reg . parm . reverb ;
temp + = ( int ) vp - > chan - > control [ MIDI_CTL_E1_REVERB_DEPTH ] * 9 / 10 ;
LIMITMAX ( temp , 255 ) ;
temp = ( temp < < 8 ) | ( vp - > ptarget < < 16 ) | vp - > aaux ;
EMU8000_PTRX_WRITE ( hw , ch , temp ) ;
EMU8000_CPF_WRITE ( hw , ch , vp - > ptarget < < 16 ) ;
EMU8000_DCYSUSV_WRITE ( hw , ch , vp - > reg . parm . voldcysus ) ;
}
/*
* reset voice parameters
*/
static void
2005-11-17 14:34:36 +01:00
reset_voice ( struct snd_emux * emu , int ch )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = emu - > hw ;
EMU8000_DCYSUSV_WRITE ( hw , ch , 0x807F ) ;
snd_emu8000_tweak_voice ( hw , ch ) ;
}
/*
* Set the pitch of a possibly playing note .
*/
static void
2005-11-17 14:34:36 +01:00
set_pitch ( struct snd_emu8000 * hw , struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
EMU8000_IP_WRITE ( hw , vp - > ch , vp - > apitch ) ;
}
/*
* Set the volume of a possibly already playing note
*/
static void
2005-11-17 14:34:36 +01:00
set_volume ( struct snd_emu8000 * hw , struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
int ifatn ;
ifatn = ( unsigned char ) vp - > acutoff ;
ifatn = ( ifatn < < 8 ) ;
ifatn | = ( unsigned char ) vp - > avol ;
EMU8000_IFATN_WRITE ( hw , vp - > ch , ifatn ) ;
}
/*
* Set pan and loop start address .
*/
static void
2005-11-17 14:34:36 +01:00
set_pan ( struct snd_emu8000 * hw , struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
unsigned int temp ;
temp = ( ( unsigned int ) vp - > apan < < 24 ) | ( ( unsigned int ) vp - > reg . loopstart - 1 ) ;
EMU8000_PSST_WRITE ( hw , vp - > ch , temp ) ;
}
# define MOD_SENSE 18
static void
2005-11-17 14:34:36 +01:00
set_fmmod ( struct snd_emu8000 * 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 ;
EMU8000_FMMOD_WRITE ( hw , vp - > ch , fmmod ) ;
}
/* set tremolo (lfo1) volume & frequency */
static void
2005-11-17 14:34:36 +01:00
set_tremfreq ( struct snd_emu8000 * hw , struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
EMU8000_TREMFRQ_WRITE ( hw , vp - > ch , vp - > reg . parm . tremfrq ) ;
}
/* set lfo2 pitch & frequency */
static void
2005-11-17 14:34:36 +01:00
set_fm2frq2 ( struct snd_emu8000 * 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 ;
EMU8000_FM2FRQ2_WRITE ( hw , vp - > ch , fm2frq2 ) ;
}
/* set filterQ */
static void
2005-11-17 14:34:36 +01:00
set_filterQ ( struct snd_emu8000 * hw , struct snd_emux_voice * vp )
2005-04-16 15:20:36 -07:00
{
unsigned int addr ;
addr = EMU8000_CCCA_READ ( hw , vp - > ch ) & 0xffffff ;
addr | = ( vp - > reg . parm . filterQ < < 28 ) ;
EMU8000_CCCA_WRITE ( hw , vp - > ch , addr ) ;
}
/*
* set the envelope & LFO parameters to the default values
*/
static void
2005-11-17 14:34:36 +01:00
snd_emu8000_tweak_voice ( struct snd_emu8000 * emu , int i )
2005-04-16 15:20:36 -07:00
{
/* set all mod/vol envelope shape to minimum */
EMU8000_ENVVOL_WRITE ( emu , i , 0x8000 ) ;
EMU8000_ENVVAL_WRITE ( emu , i , 0x8000 ) ;
EMU8000_DCYSUS_WRITE ( emu , i , 0x7F7F ) ;
EMU8000_ATKHLDV_WRITE ( emu , i , 0x7F7F ) ;
EMU8000_ATKHLD_WRITE ( emu , i , 0x7F7F ) ;
EMU8000_PEFE_WRITE ( emu , i , 0 ) ; /* mod envelope height to zero */
EMU8000_LFO1VAL_WRITE ( emu , i , 0x8000 ) ; /* no delay for LFO1 */
EMU8000_LFO2VAL_WRITE ( emu , i , 0x8000 ) ;
EMU8000_IP_WRITE ( emu , i , 0xE000 ) ; /* no pitch shift */
EMU8000_IFATN_WRITE ( emu , i , 0xFF00 ) ; /* volume to minimum */
EMU8000_FMMOD_WRITE ( emu , i , 0 ) ;
EMU8000_TREMFRQ_WRITE ( emu , i , 0 ) ;
EMU8000_FM2FRQ2_WRITE ( emu , i , 0 ) ;
}
/*
* sysex callback
*/
static void
2005-11-17 14:34:36 +01:00
sysex ( struct snd_emux * emu , char * buf , int len , int parsed , struct snd_midi_channel_set * chset )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = emu - > hw ;
switch ( parsed ) {
case SNDRV_MIDI_SYSEX_GS_CHORUS_MODE :
hw - > chorus_mode = chset - > gs_chorus_mode ;
snd_emu8000_update_chorus_mode ( hw ) ;
break ;
case SNDRV_MIDI_SYSEX_GS_REVERB_MODE :
hw - > reverb_mode = chset - > gs_reverb_mode ;
snd_emu8000_update_reverb_mode ( hw ) ;
break ;
}
}
# ifdef CONFIG_SND_SEQUENCER_OSS
/*
* OSS ioctl callback
*/
static int
2005-11-17 14:34:36 +01:00
oss_ioctl ( struct snd_emux * emu , int cmd , int p1 , int p2 )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = emu - > hw ;
switch ( cmd ) {
case _EMUX_OSS_REVERB_MODE :
hw - > reverb_mode = p1 ;
snd_emu8000_update_reverb_mode ( hw ) ;
break ;
case _EMUX_OSS_CHORUS_MODE :
hw - > chorus_mode = p1 ;
snd_emu8000_update_chorus_mode ( hw ) ;
break ;
case _EMUX_OSS_INITIALIZE_CHIP :
/* snd_emu8000_init(hw); */ /*ignored*/
break ;
case _EMUX_OSS_EQUALIZER :
hw - > bass_level = p1 ;
hw - > treble_level = p2 ;
snd_emu8000_update_equalizer ( hw ) ;
break ;
}
return 0 ;
}
# endif
/*
* additional patch keys
*/
# define SNDRV_EMU8000_LOAD_CHORUS_FX 0x10 /* optarg=mode */
# define SNDRV_EMU8000_LOAD_REVERB_FX 0x11 /* optarg=mode */
/*
* callback routine
*/
static int
2005-11-17 14:34:36 +01:00
load_fx ( struct snd_emux * emu , int type , int mode , const void __user * buf , long len )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:34:36 +01:00
struct snd_emu8000 * hw ;
2005-04-16 15:20:36 -07:00
hw = emu - > hw ;
/* skip header */
buf + = 16 ;
len - = 16 ;
switch ( type ) {
case SNDRV_EMU8000_LOAD_CHORUS_FX :
return snd_emu8000_load_chorus_fx ( hw , mode , buf , len ) ;
case SNDRV_EMU8000_LOAD_REVERB_FX :
return snd_emu8000_load_reverb_fx ( hw , mode , buf , len ) ;
}
return - EINVAL ;
}