linux/sound/synth/emux/emux_synth.c
Oswald Buddenhagen e68235c8aa ALSA: emu10k1: fix synthesizer pitch for E-MU cards at 44.1 kHz
This is only a very partial fix - the frequency-dependent envelope & LFO
register values aren't adjusted.

But I'm not sure they were even correct at 48 kHz to start with, as most
of them are precalculated by common code which assumes an EMU8K-specific
44.1 kHz word clock, and it seems somewhat unlikely that the hardware's
register interpretation was adjusted to compensate for the different
word clock.

In any case I'm not going to spend time on fixing that, as this code is
unlikely to be actually used by anyone today.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Link: https://lore.kernel.org/r/20230612191325.1315854-6-oswald.buddenhagen@gmx.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2023-06-13 07:42:08 +02:00

970 lines
25 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Midi synth routines for the Emu8k/Emu10k1
*
* Copyright (C) 1999 Steve Ratcliffe
* Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de>
*
* Contains code based on awe_wave.c by Takashi Iwai
*/
#include <linux/export.h>
#include "emux_voice.h"
#include <sound/asoundef.h>
/*
* Prototypes
*/
/*
* 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)
static int get_zone(struct snd_emux *emu, struct snd_emux_port *port,
int *notep, int vel, struct snd_midi_channel *chan,
struct snd_sf_zone **table);
static int get_bank(struct snd_emux_port *port, struct snd_midi_channel *chan);
static void terminate_note1(struct snd_emux *emu, int note,
struct snd_midi_channel *chan, int free);
static void exclusive_note_off(struct snd_emux *emu, struct snd_emux_port *port,
int exclass);
static void terminate_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int free);
static void update_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int update);
static void setup_voice(struct snd_emux_voice *vp);
static int calc_pan(struct snd_emux_voice *vp);
static int calc_volume(struct snd_emux_voice *vp);
static int calc_pitch(struct snd_emux_voice *vp);
/*
* Start a note.
*/
void
snd_emux_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
{
struct snd_emux *emu;
int i, key, nvoices;
struct snd_emux_voice *vp;
struct snd_sf_zone *table[SNDRV_EMUX_MAX_MULTI_VOICES];
unsigned long flags;
struct snd_emux_port *port;
port = p;
if (snd_BUG_ON(!port || !chan))
return;
emu = port->emu;
if (snd_BUG_ON(!emu || !emu->ops.get_voice || !emu->ops.trigger))
return;
key = note; /* remember the original note */
nvoices = get_zone(emu, port, &note, vel, chan, table);
if (! nvoices)
return;
/* exclusive note off */
for (i = 0; i < nvoices; i++) {
struct snd_sf_zone *zp = table[i];
if (zp && zp->v.exclusiveClass)
exclusive_note_off(emu, port, zp->v.exclusiveClass);
}
#if 0 // seems not necessary
/* Turn off the same note on the same channel. */
terminate_note1(emu, key, chan, 0);
#endif
spin_lock_irqsave(&emu->voice_lock, flags);
for (i = 0; i < nvoices; i++) {
/* set up each voice parameter */
/* at this stage, we don't trigger the voice yet. */
if (table[i] == NULL)
continue;
vp = emu->ops.get_voice(emu, port);
if (vp == NULL || vp->ch < 0)
continue;
if (STATE_IS_PLAYING(vp->state))
emu->ops.terminate(vp);
vp->time = emu->use_time++;
vp->chan = chan;
vp->port = port;
vp->key = key;
vp->note = note;
vp->velocity = vel;
vp->zone = table[i];
if (vp->zone->sample)
vp->block = vp->zone->sample->block;
else
vp->block = NULL;
setup_voice(vp);
vp->state = SNDRV_EMUX_ST_STANDBY;
if (emu->ops.prepare) {
vp->state = SNDRV_EMUX_ST_OFF;
if (emu->ops.prepare(vp) >= 0)
vp->state = SNDRV_EMUX_ST_STANDBY;
}
}
/* start envelope now */
for (i = 0; i < emu->max_voices; i++) {
vp = &emu->voices[i];
if (vp->state == SNDRV_EMUX_ST_STANDBY &&
vp->chan == chan) {
emu->ops.trigger(vp);
vp->state = SNDRV_EMUX_ST_ON;
vp->ontime = jiffies; /* remember the trigger timing */
}
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
#ifdef SNDRV_EMUX_USE_RAW_EFFECT
if (port->port_mode == SNDRV_EMUX_PORT_MODE_OSS_SYNTH) {
/* clear voice position for the next note on this channel */
struct snd_emux_effect_table *fx = chan->private;
if (fx) {
fx->flag[EMUX_FX_SAMPLE_START] = 0;
fx->flag[EMUX_FX_COARSE_SAMPLE_START] = 0;
}
}
#endif
}
/*
* Release a note in response to a midi note off.
*/
void
snd_emux_note_off(void *p, int note, int vel, struct snd_midi_channel *chan)
{
int ch;
struct snd_emux *emu;
struct snd_emux_voice *vp;
unsigned long flags;
struct snd_emux_port *port;
port = p;
if (snd_BUG_ON(!port || !chan))
return;
emu = port->emu;
if (snd_BUG_ON(!emu || !emu->ops.release))
return;
spin_lock_irqsave(&emu->voice_lock, flags);
for (ch = 0; ch < emu->max_voices; ch++) {
vp = &emu->voices[ch];
if (STATE_IS_PLAYING(vp->state) &&
vp->chan == chan && vp->key == note) {
vp->state = SNDRV_EMUX_ST_RELEASED;
if (vp->ontime == jiffies) {
/* if note-off is sent too shortly after
* note-on, emuX engine cannot produce the sound
* correctly. so we'll release this note
* a bit later via timer callback.
*/
vp->state = SNDRV_EMUX_ST_PENDING;
if (! emu->timer_active) {
mod_timer(&emu->tlist, jiffies + 1);
emu->timer_active = 1;
}
} else
/* ok now release the note */
emu->ops.release(vp);
}
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
* timer callback
*
* release the pending note-offs
*/
void snd_emux_timer_callback(struct timer_list *t)
{
struct snd_emux *emu = from_timer(emu, t, tlist);
struct snd_emux_voice *vp;
unsigned long flags;
int ch, do_again = 0;
spin_lock_irqsave(&emu->voice_lock, flags);
for (ch = 0; ch < emu->max_voices; ch++) {
vp = &emu->voices[ch];
if (vp->state == SNDRV_EMUX_ST_PENDING) {
if (vp->ontime == jiffies)
do_again++; /* release this at the next interrupt */
else {
emu->ops.release(vp);
vp->state = SNDRV_EMUX_ST_RELEASED;
}
}
}
if (do_again) {
mod_timer(&emu->tlist, jiffies + 1);
emu->timer_active = 1;
} else
emu->timer_active = 0;
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
* key pressure change
*/
void
snd_emux_key_press(void *p, int note, int vel, struct snd_midi_channel *chan)
{
int ch;
struct snd_emux *emu;
struct snd_emux_voice *vp;
unsigned long flags;
struct snd_emux_port *port;
port = p;
if (snd_BUG_ON(!port || !chan))
return;
emu = port->emu;
if (snd_BUG_ON(!emu || !emu->ops.update))
return;
spin_lock_irqsave(&emu->voice_lock, flags);
for (ch = 0; ch < emu->max_voices; ch++) {
vp = &emu->voices[ch];
if (vp->state == SNDRV_EMUX_ST_ON &&
vp->chan == chan && vp->key == note) {
vp->velocity = vel;
update_voice(emu, vp, SNDRV_EMUX_UPDATE_VOLUME);
}
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
* Modulate the voices which belong to the channel
*/
void
snd_emux_update_channel(struct snd_emux_port *port, struct snd_midi_channel *chan, int update)
{
struct snd_emux *emu;
struct snd_emux_voice *vp;
int i;
unsigned long flags;
if (! update)
return;
emu = port->emu;
if (snd_BUG_ON(!emu || !emu->ops.update))
return;
spin_lock_irqsave(&emu->voice_lock, flags);
for (i = 0; i < emu->max_voices; i++) {
vp = &emu->voices[i];
if (vp->chan == chan)
update_voice(emu, vp, update);
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
* Modulate all the voices which belong to the port.
*/
void
snd_emux_update_port(struct snd_emux_port *port, int update)
{
struct snd_emux *emu;
struct snd_emux_voice *vp;
int i;
unsigned long flags;
if (! update)
return;
emu = port->emu;
if (snd_BUG_ON(!emu || !emu->ops.update))
return;
spin_lock_irqsave(&emu->voice_lock, flags);
for (i = 0; i < emu->max_voices; i++) {
vp = &emu->voices[i];
if (vp->port == port)
update_voice(emu, vp, update);
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
* Deal with a controller type event. This includes all types of
* control events, not just the midi controllers
*/
void
snd_emux_control(void *p, int type, struct snd_midi_channel *chan)
{
struct snd_emux_port *port;
port = p;
if (snd_BUG_ON(!port || !chan))
return;
switch (type) {
case MIDI_CTL_MSB_MAIN_VOLUME:
case MIDI_CTL_MSB_EXPRESSION:
snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_VOLUME);
break;
case MIDI_CTL_MSB_PAN:
snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN);
break;
case MIDI_CTL_SOFT_PEDAL:
#ifdef SNDRV_EMUX_USE_RAW_EFFECT
/* FIXME: this is an emulation */
if (chan->control[type] >= 64)
snd_emux_send_effect(port, chan, EMUX_FX_CUTOFF, -160,
EMUX_FX_FLAG_ADD);
else
snd_emux_send_effect(port, chan, EMUX_FX_CUTOFF, 0,
EMUX_FX_FLAG_OFF);
#endif
break;
case MIDI_CTL_PITCHBEND:
snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PITCH);
break;
case MIDI_CTL_MSB_MODWHEEL:
case MIDI_CTL_CHAN_PRESSURE:
snd_emux_update_channel(port, chan,
SNDRV_EMUX_UPDATE_FMMOD |
SNDRV_EMUX_UPDATE_FM2FRQ2);
break;
}
if (port->chset.midi_mode == SNDRV_MIDI_MODE_XG) {
snd_emux_xg_control(port, chan, type);
}
}
/*
* terminate note - if free flag is true, free the terminated voice
*/
static void
terminate_note1(struct snd_emux *emu, int note, struct snd_midi_channel *chan, int free)
{
int i;
struct snd_emux_voice *vp;
unsigned long flags;
spin_lock_irqsave(&emu->voice_lock, flags);
for (i = 0; i < emu->max_voices; i++) {
vp = &emu->voices[i];
if (STATE_IS_PLAYING(vp->state) && vp->chan == chan &&
vp->key == note)
terminate_voice(emu, vp, free);
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
* terminate note - exported for midi emulation
*/
void
snd_emux_terminate_note(void *p, int note, struct snd_midi_channel *chan)
{
struct snd_emux *emu;
struct snd_emux_port *port;
port = p;
if (snd_BUG_ON(!port || !chan))
return;
emu = port->emu;
if (snd_BUG_ON(!emu || !emu->ops.terminate))
return;
terminate_note1(emu, note, chan, 1);
}
/*
* Terminate all the notes
*/
void
snd_emux_terminate_all(struct snd_emux *emu)
{
int i;
struct snd_emux_voice *vp;
unsigned long flags;
spin_lock_irqsave(&emu->voice_lock, flags);
for (i = 0; i < emu->max_voices; i++) {
vp = &emu->voices[i];
if (STATE_IS_PLAYING(vp->state))
terminate_voice(emu, vp, 0);
if (vp->state == SNDRV_EMUX_ST_OFF) {
if (emu->ops.free_voice)
emu->ops.free_voice(vp);
if (emu->ops.reset)
emu->ops.reset(emu, i);
}
vp->time = 0;
}
/* initialize allocation time */
emu->use_time = 0;
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
EXPORT_SYMBOL(snd_emux_terminate_all);
/*
* Terminate all voices associated with the given port
*/
void
snd_emux_sounds_off_all(struct snd_emux_port *port)
{
int i;
struct snd_emux *emu;
struct snd_emux_voice *vp;
unsigned long flags;
if (snd_BUG_ON(!port))
return;
emu = port->emu;
if (snd_BUG_ON(!emu || !emu->ops.terminate))
return;
spin_lock_irqsave(&emu->voice_lock, flags);
for (i = 0; i < emu->max_voices; i++) {
vp = &emu->voices[i];
if (STATE_IS_PLAYING(vp->state) &&
vp->port == port)
terminate_voice(emu, vp, 0);
if (vp->state == SNDRV_EMUX_ST_OFF) {
if (emu->ops.free_voice)
emu->ops.free_voice(vp);
if (emu->ops.reset)
emu->ops.reset(emu, i);
}
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
* Terminate all voices that have the same exclusive class. This
* is mainly for drums.
*/
static void
exclusive_note_off(struct snd_emux *emu, struct snd_emux_port *port, int exclass)
{
struct snd_emux_voice *vp;
int i;
unsigned long flags;
spin_lock_irqsave(&emu->voice_lock, flags);
for (i = 0; i < emu->max_voices; i++) {
vp = &emu->voices[i];
if (STATE_IS_PLAYING(vp->state) && vp->port == port &&
vp->reg.exclusiveClass == exclass) {
terminate_voice(emu, vp, 0);
}
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
* terminate a voice
* if free flag is true, call free_voice after termination
*/
static void
terminate_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int free)
{
emu->ops.terminate(vp);
vp->time = emu->use_time++;
vp->chan = NULL;
vp->port = NULL;
vp->zone = NULL;
vp->block = NULL;
vp->state = SNDRV_EMUX_ST_OFF;
if (free && emu->ops.free_voice)
emu->ops.free_voice(vp);
}
/*
* Modulate the voice
*/
static void
update_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int update)
{
if (!STATE_IS_PLAYING(vp->state))
return;
if (vp->chan == NULL || vp->port == NULL)
return;
if (update & SNDRV_EMUX_UPDATE_VOLUME)
calc_volume(vp);
if (update & SNDRV_EMUX_UPDATE_PITCH)
calc_pitch(vp);
if (update & SNDRV_EMUX_UPDATE_PAN) {
if (! calc_pan(vp) && (update == SNDRV_EMUX_UPDATE_PAN))
return;
}
emu->ops.update(vp, update);
}
#if 0 // not used
/* table for volume target calculation */
static const unsigned short voltarget[16] = {
0xEAC0, 0xE0C8, 0xD740, 0xCE20, 0xC560, 0xBD08, 0xB500, 0xAD58,
0xA5F8, 0x9EF0, 0x9830, 0x91C0, 0x8B90, 0x85A8, 0x8000, 0x7A90
};
#endif
#define LO_BYTE(v) ((v) & 0xff)
#define HI_BYTE(v) (((v) >> 8) & 0xff)
/*
* Sets up the voice structure by calculating some values that
* will be needed later.
*/
static void
setup_voice(struct snd_emux_voice *vp)
{
struct soundfont_voice_parm *parm;
int pitch;
/* copy the original register values */
vp->reg = vp->zone->v;
#ifdef SNDRV_EMUX_USE_RAW_EFFECT
snd_emux_setup_effect(vp);
#endif
/* reset status */
vp->apan = -1;
vp->avol = -1;
vp->apitch = -1;
calc_volume(vp);
calc_pitch(vp);
calc_pan(vp);
parm = &vp->reg.parm;
/* compute filter target and correct modulation parameters */
if (LO_BYTE(parm->modatkhld) >= 0x80 && parm->moddelay >= 0x8000) {
parm->moddelay = 0xbfff;
pitch = (HI_BYTE(parm->pefe) << 4) + vp->apitch;
if (pitch > 0xffff)
pitch = 0xffff;
/* calculate filter target */
vp->ftarget = parm->cutoff + LO_BYTE(parm->pefe);
LIMITVALUE(vp->ftarget, 0, 255);
vp->ftarget <<= 8;
} else {
vp->ftarget = parm->cutoff;
vp->ftarget <<= 8;
pitch = vp->apitch;
}
/* compute pitch target */
if (pitch != 0xffff) {
vp->ptarget = 1 << (pitch >> 12);
if (pitch & 0x800) vp->ptarget += (vp->ptarget*0x102e)/0x2710;
if (pitch & 0x400) vp->ptarget += (vp->ptarget*0x764)/0x2710;
if (pitch & 0x200) vp->ptarget += (vp->ptarget*0x389)/0x2710;
vp->ptarget += (vp->ptarget >> 1);
if (vp->ptarget > 0xffff) vp->ptarget = 0xffff;
} else
vp->ptarget = 0xffff;
if (LO_BYTE(parm->modatkhld) >= 0x80) {
parm->modatkhld &= ~0xff;
parm->modatkhld |= 0x7f;
}
/* compute volume target and correct volume parameters */
vp->vtarget = 0;
#if 0 /* FIXME: this leads to some clicks.. */
if (LO_BYTE(parm->volatkhld) >= 0x80 && parm->voldelay >= 0x8000) {
parm->voldelay = 0xbfff;
vp->vtarget = voltarget[vp->avol % 0x10] >> (vp->avol >> 4);
}
#endif
if (LO_BYTE(parm->volatkhld) >= 0x80) {
parm->volatkhld &= ~0xff;
parm->volatkhld |= 0x7f;
}
}
/*
* calculate pitch parameter
*/
static const unsigned char pan_volumes[256] = {
0x00,0x03,0x06,0x09,0x0c,0x0f,0x12,0x14,0x17,0x1a,0x1d,0x20,0x22,0x25,0x28,0x2a,
0x2d,0x30,0x32,0x35,0x37,0x3a,0x3c,0x3f,0x41,0x44,0x46,0x49,0x4b,0x4d,0x50,0x52,
0x54,0x57,0x59,0x5b,0x5d,0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6f,0x71,0x73,0x75,
0x77,0x79,0x7b,0x7c,0x7e,0x80,0x82,0x84,0x86,0x88,0x89,0x8b,0x8d,0x8f,0x90,0x92,
0x94,0x96,0x97,0x99,0x9a,0x9c,0x9e,0x9f,0xa1,0xa2,0xa4,0xa5,0xa7,0xa8,0xaa,0xab,
0xad,0xae,0xaf,0xb1,0xb2,0xb3,0xb5,0xb6,0xb7,0xb9,0xba,0xbb,0xbc,0xbe,0xbf,0xc0,
0xc1,0xc2,0xc3,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,0xd0,0xd1,
0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdc,0xdd,0xde,0xdf,
0xdf,0xe0,0xe1,0xe2,0xe2,0xe3,0xe4,0xe4,0xe5,0xe6,0xe6,0xe7,0xe8,0xe8,0xe9,0xe9,
0xea,0xeb,0xeb,0xec,0xec,0xed,0xed,0xee,0xee,0xef,0xef,0xf0,0xf0,0xf1,0xf1,0xf1,
0xf2,0xf2,0xf3,0xf3,0xf3,0xf4,0xf4,0xf5,0xf5,0xf5,0xf6,0xf6,0xf6,0xf7,0xf7,0xf7,
0xf7,0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xfa,0xfa,0xfa,0xfa,0xfb,0xfb,0xfb,
0xfb,0xfb,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,
0xfd,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
};
static int
calc_pan(struct snd_emux_voice *vp)
{
struct snd_midi_channel *chan = vp->chan;
int pan;
/* pan & loop start (pan 8bit, MSB, 0:right, 0xff:left) */
if (vp->reg.fixpan > 0) /* 0-127 */
pan = 255 - (int)vp->reg.fixpan * 2;
else {
pan = chan->control[MIDI_CTL_MSB_PAN] - 64;
if (vp->reg.pan >= 0) /* 0-127 */
pan += vp->reg.pan - 64;
pan = 127 - (int)pan * 2;
}
LIMITVALUE(pan, 0, 255);
if (vp->emu->linear_panning) {
/* assuming linear volume */
if (pan != vp->apan) {
vp->apan = pan;
if (pan == 0)
vp->aaux = 0xff;
else
vp->aaux = (-pan) & 0xff;
return 1;
} else
return 0;
} else {
/* using volume table */
if (vp->apan != (int)pan_volumes[pan]) {
vp->apan = pan_volumes[pan];
vp->aaux = pan_volumes[255 - pan];
return 1;
}
return 0;
}
}
/*
* calculate volume attenuation
*
* Voice volume is controlled by volume attenuation parameter.
* So volume becomes maximum when avol is 0 (no attenuation), and
* minimum when 255 (-96dB or silence).
*/
/* tables for volume->attenuation calculation */
static const unsigned char voltab1[128] = {
0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
0x63, 0x2b, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22,
0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a,
0x19, 0x19, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14,
0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10,
0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d,
0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b,
0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09,
0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06,
0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04,
0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char voltab2[128] = {
0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x2a,
0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x24, 0x23, 0x22, 0x21,
0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1c, 0x1b, 0x1a,
0x1a, 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15,
0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x10,
0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d,
0x0d, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a,
0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08,
0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01,
0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char expressiontab[128] = {
0x7f, 0x6c, 0x62, 0x5a, 0x54, 0x50, 0x4b, 0x48, 0x45, 0x42,
0x40, 0x3d, 0x3b, 0x39, 0x38, 0x36, 0x34, 0x33, 0x31, 0x30,
0x2f, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25,
0x24, 0x24, 0x23, 0x22, 0x21, 0x21, 0x20, 0x1f, 0x1e, 0x1e,
0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18,
0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x15, 0x14, 0x14, 0x13,
0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, 0x10, 0x0f, 0x0f,
0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09,
0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06,
0x06, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03,
0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/*
* Magic to calculate the volume (actually attenuation) from all the
* voice and channels parameters.
*/
static int
calc_volume(struct snd_emux_voice *vp)
{
int vol;
int main_vol, expression_vol, master_vol;
struct snd_midi_channel *chan = vp->chan;
struct snd_emux_port *port = vp->port;
expression_vol = chan->control[MIDI_CTL_MSB_EXPRESSION];
LIMITMAX(vp->velocity, 127);
LIMITVALUE(expression_vol, 0, 127);
if (port->port_mode == SNDRV_EMUX_PORT_MODE_OSS_SYNTH) {
/* 0 - 127 */
main_vol = chan->control[MIDI_CTL_MSB_MAIN_VOLUME];
vol = (vp->velocity * main_vol * expression_vol) / (127*127);
vol = vol * vp->reg.amplitude / 127;
LIMITVALUE(vol, 0, 127);
/* calc to attenuation */
vol = snd_sf_vol_table[vol];
} else {
main_vol = chan->control[MIDI_CTL_MSB_MAIN_VOLUME] * vp->reg.amplitude / 127;
LIMITVALUE(main_vol, 0, 127);
vol = voltab1[main_vol] + voltab2[vp->velocity];
vol = (vol * 8) / 3;
vol += vp->reg.attenuation;
vol += ((0x100 - vol) * expressiontab[expression_vol])/128;
}
master_vol = port->chset.gs_master_volume;
LIMITVALUE(master_vol, 0, 127);
vol += snd_sf_vol_table[master_vol];
vol += port->volume_atten;
#ifdef SNDRV_EMUX_USE_RAW_EFFECT
if (chan->private) {
struct snd_emux_effect_table *fx = chan->private;
vol += fx->val[EMUX_FX_ATTEN];
}
#endif
LIMITVALUE(vol, 0, 255);
if (vp->avol == vol)
return 0; /* value unchanged */
vp->avol = vol;
if (!SF_IS_DRUM_BANK(get_bank(port, chan))
&& LO_BYTE(vp->reg.parm.volatkhld) < 0x7d) {
int atten;
if (vp->velocity < 70)
atten = 70;
else
atten = vp->velocity;
vp->acutoff = (atten * vp->reg.parm.cutoff + 0xa0) >> 7;
} else {
vp->acutoff = vp->reg.parm.cutoff;
}
return 1; /* value changed */
}
/*
* calculate pitch offset
*
* 0xE000 is no pitch offset at 44100Hz sample.
* Every 4096 is one octave.
*/
static int
calc_pitch(struct snd_emux_voice *vp)
{
struct snd_midi_channel *chan = vp->chan;
int offset;
/* calculate offset */
if (vp->reg.fixkey >= 0) {
offset = (vp->reg.fixkey - vp->reg.root) * 4096 / 12;
} else {
offset = (vp->note - vp->reg.root) * 4096 / 12;
}
offset = (offset * vp->reg.scaleTuning) / 100;
offset += vp->reg.tune * 4096 / 1200;
if (chan->midi_pitchbend != 0) {
/* (128 * 8192: 1 semitone) ==> (4096: 12 semitones) */
offset += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 3072;
}
/* tuning via RPN:
* coarse = -8192 to 8192 (100 cent per 128)
* fine = -8192 to 8192 (max=100cent)
*/
/* 4096 = 1200 cents in emu8000 parameter */
offset += chan->gm_rpn_coarse_tuning * 4096 / (12 * 128);
offset += chan->gm_rpn_fine_tuning / 24;
#ifdef SNDRV_EMUX_USE_RAW_EFFECT
/* add initial pitch correction */
if (chan->private) {
struct snd_emux_effect_table *fx = chan->private;
if (fx->flag[EMUX_FX_INIT_PITCH])
offset += fx->val[EMUX_FX_INIT_PITCH];
}
#endif
/* 0xe000: root pitch */
offset += 0xe000 + vp->reg.rate_offset;
if (vp->emu->ops.get_pitch_shift)
offset += vp->emu->ops.get_pitch_shift(vp->emu);
LIMITVALUE(offset, 0, 0xffff);
if (offset == vp->apitch)
return 0; /* unchanged */
vp->apitch = offset;
return 1; /* value changed */
}
/*
* Get the bank number assigned to the channel
*/
static int
get_bank(struct snd_emux_port *port, struct snd_midi_channel *chan)
{
int val;
switch (port->chset.midi_mode) {
case SNDRV_MIDI_MODE_XG:
val = chan->control[MIDI_CTL_MSB_BANK];
if (val == 127)
return 128; /* return drum bank */
return chan->control[MIDI_CTL_LSB_BANK];
case SNDRV_MIDI_MODE_GS:
if (chan->drum_channel)
return 128;
/* ignore LSB (bank map) */
return chan->control[MIDI_CTL_MSB_BANK];
default:
if (chan->drum_channel)
return 128;
return chan->control[MIDI_CTL_MSB_BANK];
}
}
/* Look for the zones matching with the given note and velocity.
* The resultant zones are stored on table.
*/
static int
get_zone(struct snd_emux *emu, struct snd_emux_port *port,
int *notep, int vel, struct snd_midi_channel *chan,
struct snd_sf_zone **table)
{
int preset, bank, def_preset, def_bank;
bank = get_bank(port, chan);
preset = chan->midi_program;
if (SF_IS_DRUM_BANK(bank)) {
def_preset = port->ctrls[EMUX_MD_DEF_DRUM];
def_bank = bank;
} else {
def_preset = preset;
def_bank = port->ctrls[EMUX_MD_DEF_BANK];
}
return snd_soundfont_search_zone(emu->sflist, notep, vel, preset, bank,
def_preset, def_bank,
table, SNDRV_EMUX_MAX_MULTI_VOICES);
}
/*
*/
void
snd_emux_init_voices(struct snd_emux *emu)
{
struct snd_emux_voice *vp;
int i;
unsigned long flags;
spin_lock_irqsave(&emu->voice_lock, flags);
for (i = 0; i < emu->max_voices; i++) {
vp = &emu->voices[i];
vp->ch = -1; /* not used */
vp->state = SNDRV_EMUX_ST_OFF;
vp->chan = NULL;
vp->port = NULL;
vp->time = 0;
vp->emu = emu;
vp->hw = emu->hw;
}
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
/*
*/
void snd_emux_lock_voice(struct snd_emux *emu, int voice)
{
unsigned long flags;
spin_lock_irqsave(&emu->voice_lock, flags);
if (emu->voices[voice].state == SNDRV_EMUX_ST_OFF)
emu->voices[voice].state = SNDRV_EMUX_ST_LOCKED;
else
snd_printk(KERN_WARNING
"invalid voice for lock %d (state = %x)\n",
voice, emu->voices[voice].state);
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
EXPORT_SYMBOL(snd_emux_lock_voice);
/*
*/
void snd_emux_unlock_voice(struct snd_emux *emu, int voice)
{
unsigned long flags;
spin_lock_irqsave(&emu->voice_lock, flags);
if (emu->voices[voice].state == SNDRV_EMUX_ST_LOCKED)
emu->voices[voice].state = SNDRV_EMUX_ST_OFF;
else
snd_printk(KERN_WARNING
"invalid voice for unlock %d (state = %x)\n",
voice, emu->voices[voice].state);
spin_unlock_irqrestore(&emu->voice_lock, flags);
}
EXPORT_SYMBOL(snd_emux_unlock_voice);