mirror of
https://github.com/torvalds/linux.git
synced 2024-12-17 00:21:32 +00:00
e68235c8aa
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>
970 lines
25 KiB
C
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, ¬e, 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);
|