diff options
Diffstat (limited to 'sound/synth/emux')
-rw-r--r-- | sound/synth/emux/Makefile | 14 | ||||
-rw-r--r-- | sound/synth/emux/emux.c | 147 | ||||
-rw-r--r-- | sound/synth/emux/emux_effect.c | 296 | ||||
-rw-r--r-- | sound/synth/emux/emux_hwdep.c | 147 | ||||
-rw-r--r-- | sound/synth/emux/emux_nrpn.c | 386 | ||||
-rw-r--r-- | sound/synth/emux/emux_oss.c | 490 | ||||
-rw-r--r-- | sound/synth/emux/emux_proc.c | 111 | ||||
-rw-r--r-- | sound/synth/emux/emux_seq.c | 403 | ||||
-rw-r--r-- | sound/synth/emux/emux_synth.c | 968 | ||||
-rw-r--r-- | sound/synth/emux/emux_voice.h | 87 | ||||
-rw-r--r-- | sound/synth/emux/soundfont.c | 1486 |
11 files changed, 4535 insertions, 0 deletions
diff --git a/sound/synth/emux/Makefile b/sound/synth/emux/Makefile new file mode 100644 index 000000000..ed28c81ac --- /dev/null +++ b/sound/synth/emux/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-emux-synth-objs := emux.o emux_synth.o emux_seq.o emux_nrpn.o \ + emux_effect.o emux_hwdep.o soundfont.o +snd-emux-synth-$(CONFIG_SND_PROC_FS) += emux_proc.o +ifneq ($(CONFIG_SND_SEQUENCER_OSS),) +snd-emux-synth-y += emux_oss.o +endif + +obj-$(CONFIG_SND_SYNTH_EMUX) += snd-emux-synth.o diff --git a/sound/synth/emux/emux.c b/sound/synth/emux/emux.c new file mode 100644 index 000000000..c60ff8139 --- /dev/null +++ b/sound/synth/emux/emux.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2000 Takashi Iwai <tiwai@suse.de> + * + * Routines for control of EMU WaveTable chip + */ + +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <sound/core.h> +#include <sound/emux_synth.h> +#include <linux/init.h> +#include <linux/module.h> +#include "emux_voice.h" + +MODULE_AUTHOR("Takashi Iwai"); +MODULE_DESCRIPTION("Routines for control of EMU WaveTable chip"); +MODULE_LICENSE("GPL"); + +/* + * create a new hardware dependent device for Emu8000/Emu10k1 + */ +int snd_emux_new(struct snd_emux **remu) +{ + struct snd_emux *emu; + + *remu = NULL; + emu = kzalloc(sizeof(*emu), GFP_KERNEL); + if (emu == NULL) + return -ENOMEM; + + spin_lock_init(&emu->voice_lock); + mutex_init(&emu->register_mutex); + + emu->client = -1; +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + emu->oss_synth = NULL; +#endif + emu->max_voices = 0; + emu->use_time = 0; + + timer_setup(&emu->tlist, snd_emux_timer_callback, 0); + emu->timer_active = 0; + + *remu = emu; + return 0; +} + +EXPORT_SYMBOL(snd_emux_new); + +/* + */ +static int sf_sample_new(void *private_data, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr, + const void __user *buf, long count) +{ + struct snd_emux *emu = private_data; + return emu->ops.sample_new(emu, sp, hdr, buf, count); + +} + +static int sf_sample_free(void *private_data, struct snd_sf_sample *sp, + struct snd_util_memhdr *hdr) +{ + struct snd_emux *emu = private_data; + return emu->ops.sample_free(emu, sp, hdr); + +} + +static void sf_sample_reset(void *private_data) +{ + struct snd_emux *emu = private_data; + emu->ops.sample_reset(emu); +} + +int snd_emux_register(struct snd_emux *emu, struct snd_card *card, int index, char *name) +{ + int err; + struct snd_sf_callback sf_cb; + + if (snd_BUG_ON(!emu->hw || emu->max_voices <= 0)) + return -EINVAL; + if (snd_BUG_ON(!card || !name)) + return -EINVAL; + + emu->card = card; + emu->name = kstrdup(name, GFP_KERNEL); + emu->voices = kcalloc(emu->max_voices, sizeof(struct snd_emux_voice), + GFP_KERNEL); + if (emu->name == NULL || emu->voices == NULL) + return -ENOMEM; + + /* create soundfont list */ + memset(&sf_cb, 0, sizeof(sf_cb)); + sf_cb.private_data = emu; + if (emu->ops.sample_new) + sf_cb.sample_new = sf_sample_new; + if (emu->ops.sample_free) + sf_cb.sample_free = sf_sample_free; + if (emu->ops.sample_reset) + sf_cb.sample_reset = sf_sample_reset; + emu->sflist = snd_sf_new(&sf_cb, emu->memhdr); + if (emu->sflist == NULL) + return -ENOMEM; + + if ((err = snd_emux_init_hwdep(emu)) < 0) + return err; + + snd_emux_init_voices(emu); + + snd_emux_init_seq(emu, card, index); +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + snd_emux_init_seq_oss(emu); +#endif + snd_emux_init_virmidi(emu, card); + + snd_emux_proc_init(emu, card, index); + return 0; +} + +EXPORT_SYMBOL(snd_emux_register); + +/* + */ +int snd_emux_free(struct snd_emux *emu) +{ + if (! emu) + return -EINVAL; + + del_timer_sync(&emu->tlist); + + snd_emux_proc_free(emu); + snd_emux_delete_virmidi(emu); +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + snd_emux_detach_seq_oss(emu); +#endif + snd_emux_detach_seq(emu); + snd_emux_delete_hwdep(emu); + snd_sf_free(emu->sflist); + kfree(emu->voices); + kfree(emu->name); + kfree(emu); + return 0; +} + +EXPORT_SYMBOL(snd_emux_free); diff --git a/sound/synth/emux/emux_effect.c b/sound/synth/emux/emux_effect.c new file mode 100644 index 000000000..afd119b11 --- /dev/null +++ b/sound/synth/emux/emux_effect.c @@ -0,0 +1,296 @@ +// 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 "emux_voice.h" +#include <linux/slab.h> + +#ifdef SNDRV_EMUX_USE_RAW_EFFECT +/* + * effects table + */ + +#define xoffsetof(type,tag) ((long)(&((type)NULL)->tag) - (long)(NULL)) + +#define parm_offset(tag) xoffsetof(struct soundfont_voice_parm *, tag) + +#define PARM_IS_BYTE (1 << 0) +#define PARM_IS_WORD (1 << 1) +#define PARM_IS_ALIGNED (3 << 2) +#define PARM_IS_ALIGN_HI (1 << 2) +#define PARM_IS_ALIGN_LO (2 << 2) +#define PARM_IS_SIGNED (1 << 4) + +#define PARM_WORD (PARM_IS_WORD) +#define PARM_BYTE_LO (PARM_IS_BYTE|PARM_IS_ALIGN_LO) +#define PARM_BYTE_HI (PARM_IS_BYTE|PARM_IS_ALIGN_HI) +#define PARM_BYTE (PARM_IS_BYTE) +#define PARM_SIGN_LO (PARM_IS_BYTE|PARM_IS_ALIGN_LO|PARM_IS_SIGNED) +#define PARM_SIGN_HI (PARM_IS_BYTE|PARM_IS_ALIGN_HI|PARM_IS_SIGNED) + +static struct emux_parm_defs { + int type; /* byte or word */ + int low, high; /* value range */ + long offset; /* offset in parameter record (-1 = not written) */ + int update; /* flgas for real-time update */ +} parm_defs[EMUX_NUM_EFFECTS] = { + {PARM_WORD, 0, 0x8000, parm_offset(moddelay), 0}, /* env1 delay */ + {PARM_BYTE_LO, 1, 0x80, parm_offset(modatkhld), 0}, /* env1 attack */ + {PARM_BYTE_HI, 0, 0x7e, parm_offset(modatkhld), 0}, /* env1 hold */ + {PARM_BYTE_LO, 1, 0x7f, parm_offset(moddcysus), 0}, /* env1 decay */ + {PARM_BYTE_LO, 1, 0x7f, parm_offset(modrelease), 0}, /* env1 release */ + {PARM_BYTE_HI, 0, 0x7f, parm_offset(moddcysus), 0}, /* env1 sustain */ + {PARM_BYTE_HI, 0, 0xff, parm_offset(pefe), 0}, /* env1 pitch */ + {PARM_BYTE_LO, 0, 0xff, parm_offset(pefe), 0}, /* env1 fc */ + + {PARM_WORD, 0, 0x8000, parm_offset(voldelay), 0}, /* env2 delay */ + {PARM_BYTE_LO, 1, 0x80, parm_offset(volatkhld), 0}, /* env2 attack */ + {PARM_BYTE_HI, 0, 0x7e, parm_offset(volatkhld), 0}, /* env2 hold */ + {PARM_BYTE_LO, 1, 0x7f, parm_offset(voldcysus), 0}, /* env2 decay */ + {PARM_BYTE_LO, 1, 0x7f, parm_offset(volrelease), 0}, /* env2 release */ + {PARM_BYTE_HI, 0, 0x7f, parm_offset(voldcysus), 0}, /* env2 sustain */ + + {PARM_WORD, 0, 0x8000, parm_offset(lfo1delay), 0}, /* lfo1 delay */ + {PARM_BYTE_LO, 0, 0xff, parm_offset(tremfrq), SNDRV_EMUX_UPDATE_TREMFREQ}, /* lfo1 freq */ + {PARM_SIGN_HI, -128, 127, parm_offset(tremfrq), SNDRV_EMUX_UPDATE_TREMFREQ}, /* lfo1 vol */ + {PARM_SIGN_HI, -128, 127, parm_offset(fmmod), SNDRV_EMUX_UPDATE_FMMOD}, /* lfo1 pitch */ + {PARM_BYTE_LO, 0, 0xff, parm_offset(fmmod), SNDRV_EMUX_UPDATE_FMMOD}, /* lfo1 cutoff */ + + {PARM_WORD, 0, 0x8000, parm_offset(lfo2delay), 0}, /* lfo2 delay */ + {PARM_BYTE_LO, 0, 0xff, parm_offset(fm2frq2), SNDRV_EMUX_UPDATE_FM2FRQ2}, /* lfo2 freq */ + {PARM_SIGN_HI, -128, 127, parm_offset(fm2frq2), SNDRV_EMUX_UPDATE_FM2FRQ2}, /* lfo2 pitch */ + + {PARM_WORD, 0, 0xffff, -1, SNDRV_EMUX_UPDATE_PITCH}, /* initial pitch */ + {PARM_BYTE, 0, 0xff, parm_offset(chorus), 0}, /* chorus */ + {PARM_BYTE, 0, 0xff, parm_offset(reverb), 0}, /* reverb */ + {PARM_BYTE, 0, 0xff, parm_offset(cutoff), SNDRV_EMUX_UPDATE_VOLUME}, /* cutoff */ + {PARM_BYTE, 0, 15, parm_offset(filterQ), SNDRV_EMUX_UPDATE_Q}, /* resonance */ + + {PARM_WORD, 0, 0xffff, -1, 0}, /* sample start */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* loop start */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* loop end */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* coarse sample start */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* coarse loop start */ + {PARM_WORD, 0, 0xffff, -1, 0}, /* coarse loop end */ + {PARM_BYTE, 0, 0xff, -1, SNDRV_EMUX_UPDATE_VOLUME}, /* initial attenuation */ +}; + +/* set byte effect value */ +static void +effect_set_byte(unsigned char *valp, struct snd_midi_channel *chan, int type) +{ + short effect; + struct snd_emux_effect_table *fx = chan->private; + + effect = fx->val[type]; + if (fx->flag[type] == EMUX_FX_FLAG_ADD) { + if (parm_defs[type].type & PARM_IS_SIGNED) + effect += *(char*)valp; + else + effect += *valp; + } + if (effect < parm_defs[type].low) + effect = parm_defs[type].low; + else if (effect > parm_defs[type].high) + effect = parm_defs[type].high; + *valp = (unsigned char)effect; +} + +/* set word effect value */ +static void +effect_set_word(unsigned short *valp, struct snd_midi_channel *chan, int type) +{ + int effect; + struct snd_emux_effect_table *fx = chan->private; + + effect = *(unsigned short*)&fx->val[type]; + if (fx->flag[type] == EMUX_FX_FLAG_ADD) + effect += *valp; + if (effect < parm_defs[type].low) + effect = parm_defs[type].low; + else if (effect > parm_defs[type].high) + effect = parm_defs[type].high; + *valp = (unsigned short)effect; +} + +/* address offset */ +static int +effect_get_offset(struct snd_midi_channel *chan, int lo, int hi, int mode) +{ + int addr = 0; + struct snd_emux_effect_table *fx = chan->private; + + if (fx->flag[hi]) + addr = (short)fx->val[hi]; + addr = addr << 15; + if (fx->flag[lo]) + addr += (short)fx->val[lo]; + if (!(mode & SNDRV_SFNT_SAMPLE_8BITS)) + addr /= 2; + return addr; +} + +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) +/* change effects - for OSS sequencer compatibility */ +void +snd_emux_send_effect_oss(struct snd_emux_port *port, + struct snd_midi_channel *chan, int type, int val) +{ + int mode; + + if (type & 0x40) + mode = EMUX_FX_FLAG_OFF; + else if (type & 0x80) + mode = EMUX_FX_FLAG_ADD; + else + mode = EMUX_FX_FLAG_SET; + type &= 0x3f; + + snd_emux_send_effect(port, chan, type, val, mode); +} +#endif + +/* Modify the effect value. + * if update is necessary, call emu8000_control + */ +void +snd_emux_send_effect(struct snd_emux_port *port, struct snd_midi_channel *chan, + int type, int val, int mode) +{ + int i; + int offset; + unsigned char *srcp, *origp; + struct snd_emux *emu; + struct snd_emux_effect_table *fx; + unsigned long flags; + + emu = port->emu; + fx = chan->private; + if (emu == NULL || fx == NULL) + return; + if (type < 0 || type >= EMUX_NUM_EFFECTS) + return; + + fx->val[type] = val; + fx->flag[type] = mode; + + /* do we need to modify the register in realtime ? */ + if (! parm_defs[type].update || (offset = parm_defs[type].offset) < 0) + return; + +#ifdef SNDRV_LITTLE_ENDIAN + if (parm_defs[type].type & PARM_IS_ALIGN_HI) + offset++; +#else + if (parm_defs[type].type & PARM_IS_ALIGN_LO) + offset++; +#endif + /* modify the register values */ + spin_lock_irqsave(&emu->voice_lock, flags); + for (i = 0; i < emu->max_voices; i++) { + struct snd_emux_voice *vp = &emu->voices[i]; + if (!STATE_IS_PLAYING(vp->state) || vp->chan != chan) + continue; + srcp = (unsigned char*)&vp->reg.parm + offset; + origp = (unsigned char*)&vp->zone->v.parm + offset; + if (parm_defs[i].type & PARM_IS_BYTE) { + *srcp = *origp; + effect_set_byte(srcp, chan, type); + } else { + *(unsigned short*)srcp = *(unsigned short*)origp; + effect_set_word((unsigned short*)srcp, chan, type); + } + } + spin_unlock_irqrestore(&emu->voice_lock, flags); + + /* activate them */ + snd_emux_update_channel(port, chan, parm_defs[type].update); +} + + +/* copy wavetable registers to voice table */ +void +snd_emux_setup_effect(struct snd_emux_voice *vp) +{ + struct snd_midi_channel *chan = vp->chan; + struct snd_emux_effect_table *fx; + unsigned char *srcp; + int i; + + if (! (fx = chan->private)) + return; + + /* modify the register values via effect table */ + for (i = 0; i < EMUX_FX_END; i++) { + int offset; + if (! fx->flag[i] || (offset = parm_defs[i].offset) < 0) + continue; +#ifdef SNDRV_LITTLE_ENDIAN + if (parm_defs[i].type & PARM_IS_ALIGN_HI) + offset++; +#else + if (parm_defs[i].type & PARM_IS_ALIGN_LO) + offset++; +#endif + srcp = (unsigned char*)&vp->reg.parm + offset; + if (parm_defs[i].type & PARM_IS_BYTE) + effect_set_byte(srcp, chan, i); + else + effect_set_word((unsigned short*)srcp, chan, i); + } + + /* correct sample and loop points */ + vp->reg.start += effect_get_offset(chan, EMUX_FX_SAMPLE_START, + EMUX_FX_COARSE_SAMPLE_START, + vp->reg.sample_mode); + + vp->reg.loopstart += effect_get_offset(chan, EMUX_FX_LOOP_START, + EMUX_FX_COARSE_LOOP_START, + vp->reg.sample_mode); + + vp->reg.loopend += effect_get_offset(chan, EMUX_FX_LOOP_END, + EMUX_FX_COARSE_LOOP_END, + vp->reg.sample_mode); +} + +/* + * effect table + */ +void +snd_emux_create_effect(struct snd_emux_port *p) +{ + int i; + p->effect = kcalloc(p->chset.max_channels, + sizeof(struct snd_emux_effect_table), GFP_KERNEL); + if (p->effect) { + for (i = 0; i < p->chset.max_channels; i++) + p->chset.channels[i].private = p->effect + i; + } else { + for (i = 0; i < p->chset.max_channels; i++) + p->chset.channels[i].private = NULL; + } +} + +void +snd_emux_delete_effect(struct snd_emux_port *p) +{ + kfree(p->effect); + p->effect = NULL; +} + +void +snd_emux_clear_effect(struct snd_emux_port *p) +{ + if (p->effect) { + memset(p->effect, 0, sizeof(struct snd_emux_effect_table) * + p->chset.max_channels); + } +} + +#endif /* SNDRV_EMUX_USE_RAW_EFFECT */ diff --git a/sound/synth/emux/emux_hwdep.c b/sound/synth/emux/emux_hwdep.c new file mode 100644 index 000000000..8a965e2f1 --- /dev/null +++ b/sound/synth/emux/emux_hwdep.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Interface for hwdep device + * + * Copyright (C) 2004 Takashi Iwai <tiwai@suse.de> + */ + +#include <sound/core.h> +#include <sound/hwdep.h> +#include <linux/uaccess.h> +#include <linux/nospec.h> +#include "emux_voice.h" + +#define TMP_CLIENT_ID 0x1001 + +/* + * load patch + */ +static int +snd_emux_hwdep_load_patch(struct snd_emux *emu, void __user *arg) +{ + int err; + struct soundfont_patch_info patch; + + if (copy_from_user(&patch, arg, sizeof(patch))) + return -EFAULT; + + if (patch.key == GUS_PATCH) + return snd_soundfont_load_guspatch(emu->sflist, arg, + patch.len + sizeof(patch), + TMP_CLIENT_ID); + + if (patch.type >= SNDRV_SFNT_LOAD_INFO && + patch.type <= SNDRV_SFNT_PROBE_DATA) { + err = snd_soundfont_load(emu->sflist, arg, patch.len + sizeof(patch), TMP_CLIENT_ID); + if (err < 0) + return err; + } else { + if (emu->ops.load_fx) + return emu->ops.load_fx(emu, patch.type, patch.optarg, arg, patch.len + sizeof(patch)); + else + return -EINVAL; + } + return 0; +} + +/* + * set misc mode + */ +static int +snd_emux_hwdep_misc_mode(struct snd_emux *emu, void __user *arg) +{ + struct snd_emux_misc_mode info; + int i; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + if (info.mode < 0 || info.mode >= EMUX_MD_END) + return -EINVAL; + info.mode = array_index_nospec(info.mode, EMUX_MD_END); + + if (info.port < 0) { + for (i = 0; i < emu->num_ports; i++) + emu->portptrs[i]->ctrls[info.mode] = info.value; + } else { + if (info.port < emu->num_ports) { + info.port = array_index_nospec(info.port, emu->num_ports); + emu->portptrs[info.port]->ctrls[info.mode] = info.value; + } + } + return 0; +} + + +/* + * ioctl + */ +static int +snd_emux_hwdep_ioctl(struct snd_hwdep * hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_emux *emu = hw->private_data; + + switch (cmd) { + case SNDRV_EMUX_IOCTL_VERSION: + return put_user(SNDRV_EMUX_VERSION, (unsigned int __user *)arg); + case SNDRV_EMUX_IOCTL_LOAD_PATCH: + return snd_emux_hwdep_load_patch(emu, (void __user *)arg); + case SNDRV_EMUX_IOCTL_RESET_SAMPLES: + snd_soundfont_remove_samples(emu->sflist); + break; + case SNDRV_EMUX_IOCTL_REMOVE_LAST_SAMPLES: + snd_soundfont_remove_unlocked(emu->sflist); + break; + case SNDRV_EMUX_IOCTL_MEM_AVAIL: + if (emu->memhdr) { + int size = snd_util_mem_avail(emu->memhdr); + return put_user(size, (unsigned int __user *)arg); + } + break; + case SNDRV_EMUX_IOCTL_MISC_MODE: + return snd_emux_hwdep_misc_mode(emu, (void __user *)arg); + } + + return 0; +} + + +/* + * register hwdep device + */ + +int +snd_emux_init_hwdep(struct snd_emux *emu) +{ + struct snd_hwdep *hw; + int err; + + if ((err = snd_hwdep_new(emu->card, SNDRV_EMUX_HWDEP_NAME, emu->hwdep_idx, &hw)) < 0) + return err; + emu->hwdep = hw; + strcpy(hw->name, SNDRV_EMUX_HWDEP_NAME); + hw->iface = SNDRV_HWDEP_IFACE_EMUX_WAVETABLE; + hw->ops.ioctl = snd_emux_hwdep_ioctl; + /* The ioctl parameter types are compatible between 32- and + * 64-bit architectures, so use the same function. */ + hw->ops.ioctl_compat = snd_emux_hwdep_ioctl; + hw->exclusive = 1; + hw->private_data = emu; + if ((err = snd_card_register(emu->card)) < 0) + return err; + + return 0; +} + + +/* + * unregister + */ +void +snd_emux_delete_hwdep(struct snd_emux *emu) +{ + if (emu->hwdep) { + snd_device_free(emu->card, emu->hwdep); + emu->hwdep = NULL; + } +} diff --git a/sound/synth/emux/emux_nrpn.c b/sound/synth/emux/emux_nrpn.c new file mode 100644 index 000000000..a7d83182f --- /dev/null +++ b/sound/synth/emux/emux_nrpn.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NRPN / SYSEX callbacks for Emu8k/Emu10k1 + * + * Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de> + */ + +#include "emux_voice.h" +#include <sound/asoundef.h> + +/* + * conversion from NRPN/control parameters to Emu8000 raw parameters + */ + +/* NRPN / CC -> Emu8000 parameter converter */ +struct nrpn_conv_table { + int control; + int effect; + int (*convert)(int val); +}; + +/* effect sensitivity */ + +#define FX_CUTOFF 0 +#define FX_RESONANCE 1 +#define FX_ATTACK 2 +#define FX_RELEASE 3 +#define FX_VIBRATE 4 +#define FX_VIBDEPTH 5 +#define FX_VIBDELAY 6 +#define FX_NUMS 7 + +/* + * convert NRPN/control values + */ + +static int send_converted_effect(const struct nrpn_conv_table *table, + int num_tables, + struct snd_emux_port *port, + struct snd_midi_channel *chan, + int type, int val, int mode) +{ + int i, cval; + for (i = 0; i < num_tables; i++) { + if (table[i].control == type) { + cval = table[i].convert(val); + snd_emux_send_effect(port, chan, table[i].effect, + cval, mode); + return 1; + } + } + return 0; +} + +#define DEF_FX_CUTOFF 170 +#define DEF_FX_RESONANCE 6 +#define DEF_FX_ATTACK 50 +#define DEF_FX_RELEASE 50 +#define DEF_FX_VIBRATE 30 +#define DEF_FX_VIBDEPTH 4 +#define DEF_FX_VIBDELAY 1500 + +/* effect sensitivities for GS NRPN: + * adjusted for chaos 8MB soundfonts + */ +static const int gs_sense[] = +{ + DEF_FX_CUTOFF, DEF_FX_RESONANCE, DEF_FX_ATTACK, DEF_FX_RELEASE, + DEF_FX_VIBRATE, DEF_FX_VIBDEPTH, DEF_FX_VIBDELAY +}; + +/* effect sensitivies for XG controls: + * adjusted for chaos 8MB soundfonts + */ +static const int xg_sense[] = +{ + DEF_FX_CUTOFF, DEF_FX_RESONANCE, DEF_FX_ATTACK, DEF_FX_RELEASE, + DEF_FX_VIBRATE, DEF_FX_VIBDEPTH, DEF_FX_VIBDELAY +}; + + +/* + * AWE32 NRPN effects + */ + +static int fx_delay(int val); +static int fx_attack(int val); +static int fx_hold(int val); +static int fx_decay(int val); +static int fx_the_value(int val); +static int fx_twice_value(int val); +static int fx_conv_pitch(int val); +static int fx_conv_Q(int val); + +/* function for each NRPN */ /* [range] units */ +#define fx_env1_delay fx_delay /* [0,5900] 4msec */ +#define fx_env1_attack fx_attack /* [0,5940] 1msec */ +#define fx_env1_hold fx_hold /* [0,8191] 1msec */ +#define fx_env1_decay fx_decay /* [0,5940] 4msec */ +#define fx_env1_release fx_decay /* [0,5940] 4msec */ +#define fx_env1_sustain fx_the_value /* [0,127] 0.75dB */ +#define fx_env1_pitch fx_the_value /* [-127,127] 9.375cents */ +#define fx_env1_cutoff fx_the_value /* [-127,127] 56.25cents */ + +#define fx_env2_delay fx_delay /* [0,5900] 4msec */ +#define fx_env2_attack fx_attack /* [0,5940] 1msec */ +#define fx_env2_hold fx_hold /* [0,8191] 1msec */ +#define fx_env2_decay fx_decay /* [0,5940] 4msec */ +#define fx_env2_release fx_decay /* [0,5940] 4msec */ +#define fx_env2_sustain fx_the_value /* [0,127] 0.75dB */ + +#define fx_lfo1_delay fx_delay /* [0,5900] 4msec */ +#define fx_lfo1_freq fx_twice_value /* [0,127] 84mHz */ +#define fx_lfo1_volume fx_twice_value /* [0,127] 0.1875dB */ +#define fx_lfo1_pitch fx_the_value /* [-127,127] 9.375cents */ +#define fx_lfo1_cutoff fx_twice_value /* [-64,63] 56.25cents */ + +#define fx_lfo2_delay fx_delay /* [0,5900] 4msec */ +#define fx_lfo2_freq fx_twice_value /* [0,127] 84mHz */ +#define fx_lfo2_pitch fx_the_value /* [-127,127] 9.375cents */ + +#define fx_init_pitch fx_conv_pitch /* [-8192,8192] cents */ +#define fx_chorus fx_the_value /* [0,255] -- */ +#define fx_reverb fx_the_value /* [0,255] -- */ +#define fx_cutoff fx_twice_value /* [0,127] 62Hz */ +#define fx_filterQ fx_conv_Q /* [0,127] -- */ + +static int fx_delay(int val) +{ + return (unsigned short)snd_sf_calc_parm_delay(val); +} + +static int fx_attack(int val) +{ + return (unsigned short)snd_sf_calc_parm_attack(val); +} + +static int fx_hold(int val) +{ + return (unsigned short)snd_sf_calc_parm_hold(val); +} + +static int fx_decay(int val) +{ + return (unsigned short)snd_sf_calc_parm_decay(val); +} + +static int fx_the_value(int val) +{ + return (unsigned short)(val & 0xff); +} + +static int fx_twice_value(int val) +{ + return (unsigned short)((val * 2) & 0xff); +} + +static int fx_conv_pitch(int val) +{ + return (short)(val * 4096 / 1200); +} + +static int fx_conv_Q(int val) +{ + return (unsigned short)((val / 8) & 0xff); +} + + +static const struct nrpn_conv_table awe_effects[] = +{ + { 0, EMUX_FX_LFO1_DELAY, fx_lfo1_delay}, + { 1, EMUX_FX_LFO1_FREQ, fx_lfo1_freq}, + { 2, EMUX_FX_LFO2_DELAY, fx_lfo2_delay}, + { 3, EMUX_FX_LFO2_FREQ, fx_lfo2_freq}, + + { 4, EMUX_FX_ENV1_DELAY, fx_env1_delay}, + { 5, EMUX_FX_ENV1_ATTACK,fx_env1_attack}, + { 6, EMUX_FX_ENV1_HOLD, fx_env1_hold}, + { 7, EMUX_FX_ENV1_DECAY, fx_env1_decay}, + { 8, EMUX_FX_ENV1_SUSTAIN, fx_env1_sustain}, + { 9, EMUX_FX_ENV1_RELEASE, fx_env1_release}, + + {10, EMUX_FX_ENV2_DELAY, fx_env2_delay}, + {11, EMUX_FX_ENV2_ATTACK, fx_env2_attack}, + {12, EMUX_FX_ENV2_HOLD, fx_env2_hold}, + {13, EMUX_FX_ENV2_DECAY, fx_env2_decay}, + {14, EMUX_FX_ENV2_SUSTAIN, fx_env2_sustain}, + {15, EMUX_FX_ENV2_RELEASE, fx_env2_release}, + + {16, EMUX_FX_INIT_PITCH, fx_init_pitch}, + {17, EMUX_FX_LFO1_PITCH, fx_lfo1_pitch}, + {18, EMUX_FX_LFO2_PITCH, fx_lfo2_pitch}, + {19, EMUX_FX_ENV1_PITCH, fx_env1_pitch}, + {20, EMUX_FX_LFO1_VOLUME, fx_lfo1_volume}, + {21, EMUX_FX_CUTOFF, fx_cutoff}, + {22, EMUX_FX_FILTERQ, fx_filterQ}, + {23, EMUX_FX_LFO1_CUTOFF, fx_lfo1_cutoff}, + {24, EMUX_FX_ENV1_CUTOFF, fx_env1_cutoff}, + {25, EMUX_FX_CHORUS, fx_chorus}, + {26, EMUX_FX_REVERB, fx_reverb}, +}; + + +/* + * GS(SC88) NRPN effects; still experimental + */ + +/* cutoff: quarter semitone step, max=255 */ +static int gs_cutoff(int val) +{ + return (val - 64) * gs_sense[FX_CUTOFF] / 50; +} + +/* resonance: 0 to 15(max) */ +static int gs_filterQ(int val) +{ + return (val - 64) * gs_sense[FX_RESONANCE] / 50; +} + +/* attack: */ +static int gs_attack(int val) +{ + return -(val - 64) * gs_sense[FX_ATTACK] / 50; +} + +/* decay: */ +static int gs_decay(int val) +{ + return -(val - 64) * gs_sense[FX_RELEASE] / 50; +} + +/* release: */ +static int gs_release(int val) +{ + return -(val - 64) * gs_sense[FX_RELEASE] / 50; +} + +/* vibrato freq: 0.042Hz step, max=255 */ +static int gs_vib_rate(int val) +{ + return (val - 64) * gs_sense[FX_VIBRATE] / 50; +} + +/* vibrato depth: max=127, 1 octave */ +static int gs_vib_depth(int val) +{ + return (val - 64) * gs_sense[FX_VIBDEPTH] / 50; +} + +/* vibrato delay: -0.725msec step */ +static int gs_vib_delay(int val) +{ + return -(val - 64) * gs_sense[FX_VIBDELAY] / 50; +} + +static const struct nrpn_conv_table gs_effects[] = +{ + {32, EMUX_FX_CUTOFF, gs_cutoff}, + {33, EMUX_FX_FILTERQ, gs_filterQ}, + {99, EMUX_FX_ENV2_ATTACK, gs_attack}, + {100, EMUX_FX_ENV2_DECAY, gs_decay}, + {102, EMUX_FX_ENV2_RELEASE, gs_release}, + {8, EMUX_FX_LFO1_FREQ, gs_vib_rate}, + {9, EMUX_FX_LFO1_VOLUME, gs_vib_depth}, + {10, EMUX_FX_LFO1_DELAY, gs_vib_delay}, +}; + + +/* + * NRPN events + */ +void +snd_emux_nrpn(void *p, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ + struct snd_emux_port *port; + + port = p; + if (snd_BUG_ON(!port || !chan)) + return; + + if (chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] == 127 && + chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB] <= 26) { + int val; + /* Win/DOS AWE32 specific NRPNs */ + /* both MSB/LSB necessary */ + val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) | + chan->control[MIDI_CTL_LSB_DATA_ENTRY]; + val -= 8192; + send_converted_effect + (awe_effects, ARRAY_SIZE(awe_effects), + port, chan, chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB], + val, EMUX_FX_FLAG_SET); + return; + } + + if (port->chset.midi_mode == SNDRV_MIDI_MODE_GS && + chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] == 1) { + int val; + /* GS specific NRPNs */ + /* only MSB is valid */ + val = chan->control[MIDI_CTL_MSB_DATA_ENTRY]; + send_converted_effect + (gs_effects, ARRAY_SIZE(gs_effects), + port, chan, chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB], + val, EMUX_FX_FLAG_ADD); + return; + } +} + + +/* + * XG control effects; still experimental + */ + +/* cutoff: quarter semitone step, max=255 */ +static int xg_cutoff(int val) +{ + return (val - 64) * xg_sense[FX_CUTOFF] / 64; +} + +/* resonance: 0(open) to 15(most nasal) */ +static int xg_filterQ(int val) +{ + return (val - 64) * xg_sense[FX_RESONANCE] / 64; +} + +/* attack: */ +static int xg_attack(int val) +{ + return -(val - 64) * xg_sense[FX_ATTACK] / 64; +} + +/* release: */ +static int xg_release(int val) +{ + return -(val - 64) * xg_sense[FX_RELEASE] / 64; +} + +static const struct nrpn_conv_table xg_effects[] = +{ + {71, EMUX_FX_CUTOFF, xg_cutoff}, + {74, EMUX_FX_FILTERQ, xg_filterQ}, + {72, EMUX_FX_ENV2_RELEASE, xg_release}, + {73, EMUX_FX_ENV2_ATTACK, xg_attack}, +}; + +int +snd_emux_xg_control(struct snd_emux_port *port, struct snd_midi_channel *chan, + int param) +{ + if (param >= ARRAY_SIZE(chan->control)) + return -EINVAL; + + return send_converted_effect(xg_effects, ARRAY_SIZE(xg_effects), + port, chan, param, + chan->control[param], + EMUX_FX_FLAG_ADD); +} + +/* + * receive sysex + */ +void +snd_emux_sysex(void *p, unsigned char *buf, int len, int parsed, + struct snd_midi_channel_set *chset) +{ + struct snd_emux_port *port; + struct snd_emux *emu; + + port = p; + if (snd_BUG_ON(!port || !chset)) + return; + emu = port->emu; + + switch (parsed) { + case SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME: + snd_emux_update_port(port, SNDRV_EMUX_UPDATE_VOLUME); + break; + default: + if (emu->ops.sysex) + emu->ops.sysex(emu, buf, len, parsed, chset); + break; + } +} + diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c new file mode 100644 index 000000000..d8d32671f --- /dev/null +++ b/sound/synth/emux/emux_oss.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Interface for OSS sequencer emulation + * + * Copyright (C) 1999 Takashi Iwai <tiwai@suse.de> + * + * Changes + * 19990227 Steve Ratcliffe Made separate file and merged in latest + * midi emulation. + */ + + +#include <linux/export.h> +#include <linux/uaccess.h> +#include <sound/core.h> +#include "emux_voice.h" +#include <sound/asoundef.h> + +static int snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure); +static int snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg); +static int snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, + unsigned long ioarg); +static int snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, + const char __user *buf, int offs, int count); +static int snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg); +static int snd_emux_event_oss_input(struct snd_seq_event *ev, int direct, + void *private, int atomic, int hop); +static void reset_port_mode(struct snd_emux_port *port, int midi_mode); +static void emuspec_control(struct snd_emux *emu, struct snd_emux_port *port, + int cmd, unsigned char *event, int atomic, int hop); +static void gusspec_control(struct snd_emux *emu, struct snd_emux_port *port, + int cmd, unsigned char *event, int atomic, int hop); +static void fake_event(struct snd_emux *emu, struct snd_emux_port *port, + int ch, int param, int val, int atomic, int hop); + +/* operators */ +static const struct snd_seq_oss_callback oss_callback = { + .owner = THIS_MODULE, + .open = snd_emux_open_seq_oss, + .close = snd_emux_close_seq_oss, + .ioctl = snd_emux_ioctl_seq_oss, + .load_patch = snd_emux_load_patch_seq_oss, + .reset = snd_emux_reset_seq_oss, +}; + + +/* + * register OSS synth + */ + +void +snd_emux_init_seq_oss(struct snd_emux *emu) +{ + struct snd_seq_oss_reg *arg; + struct snd_seq_device *dev; + + /* using device#1 here for avoiding conflicts with OPL3 */ + if (snd_seq_device_new(emu->card, 1, SNDRV_SEQ_DEV_ID_OSS, + sizeof(struct snd_seq_oss_reg), &dev) < 0) + return; + + emu->oss_synth = dev; + strcpy(dev->name, emu->name); + arg = SNDRV_SEQ_DEVICE_ARGPTR(dev); + arg->type = SYNTH_TYPE_SAMPLE; + arg->subtype = SAMPLE_TYPE_AWE32; + arg->nvoices = emu->max_voices; + arg->oper = oss_callback; + arg->private_data = emu; + + /* register to OSS synth table */ + snd_device_register(emu->card, dev); +} + + +/* + * unregister + */ +void +snd_emux_detach_seq_oss(struct snd_emux *emu) +{ + if (emu->oss_synth) { + snd_device_free(emu->card, emu->oss_synth); + emu->oss_synth = NULL; + } +} + + +/* use port number as a unique soundfont client number */ +#define SF_CLIENT_NO(p) ((p) + 0x1000) + +/* + * open port for OSS sequencer + */ +static int +snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) +{ + struct snd_emux *emu; + struct snd_emux_port *p; + struct snd_seq_port_callback callback; + char tmpname[64]; + + emu = closure; + if (snd_BUG_ON(!arg || !emu)) + return -ENXIO; + + if (!snd_emux_inc_count(emu)) + return -EFAULT; + + memset(&callback, 0, sizeof(callback)); + callback.owner = THIS_MODULE; + callback.event_input = snd_emux_event_oss_input; + + sprintf(tmpname, "%s OSS Port", emu->name); + p = snd_emux_create_port(emu, tmpname, 32, + 1, &callback); + if (p == NULL) { + snd_printk(KERN_ERR "can't create port\n"); + snd_emux_dec_count(emu); + return -ENOMEM; + } + + /* fill the argument data */ + arg->private_data = p; + arg->addr.client = p->chset.client; + arg->addr.port = p->chset.port; + p->oss_arg = arg; + + reset_port_mode(p, arg->seq_mode); + + snd_emux_reset_port(p); + return 0; +} + + +#define DEFAULT_DRUM_FLAGS ((1<<9) | (1<<25)) + +/* + * reset port mode + */ +static void +reset_port_mode(struct snd_emux_port *port, int midi_mode) +{ + if (midi_mode) { + port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI; + port->drum_flags = DEFAULT_DRUM_FLAGS; + port->volume_atten = 0; + port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS; + } else { + port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH; + port->drum_flags = 0; + port->volume_atten = 32; + port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + } +} + + +/* + * close port + */ +static int +snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg) +{ + struct snd_emux *emu; + struct snd_emux_port *p; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + p = arg->private_data; + if (snd_BUG_ON(!p)) + return -ENXIO; + + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + + snd_emux_sounds_off_all(p); + snd_soundfont_close_check(emu->sflist, SF_CLIENT_NO(p->chset.port)); + snd_seq_event_port_detach(p->chset.client, p->chset.port); + snd_emux_dec_count(emu); + + return 0; +} + + +/* + * load patch + */ +static int +snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, + const char __user *buf, int offs, int count) +{ + struct snd_emux *emu; + struct snd_emux_port *p; + int rc; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + p = arg->private_data; + if (snd_BUG_ON(!p)) + return -ENXIO; + + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + + if (format == GUS_PATCH) + rc = snd_soundfont_load_guspatch(emu->sflist, buf, count, + SF_CLIENT_NO(p->chset.port)); + else if (format == SNDRV_OSS_SOUNDFONT_PATCH) { + struct soundfont_patch_info patch; + if (count < (int)sizeof(patch)) + return -EINVAL; + if (copy_from_user(&patch, buf, sizeof(patch))) + return -EFAULT; + if (patch.type >= SNDRV_SFNT_LOAD_INFO && + patch.type <= SNDRV_SFNT_PROBE_DATA) + rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port)); + else { + if (emu->ops.load_fx) + rc = emu->ops.load_fx(emu, patch.type, patch.optarg, buf, count); + else + rc = -EINVAL; + } + } else + rc = 0; + return rc; +} + + +/* + * ioctl + */ +static int +snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, unsigned long ioarg) +{ + struct snd_emux_port *p; + struct snd_emux *emu; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + p = arg->private_data; + if (snd_BUG_ON(!p)) + return -ENXIO; + + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -ENXIO; + + switch (cmd) { + case SNDCTL_SEQ_RESETSAMPLES: + snd_soundfont_remove_samples(emu->sflist); + return 0; + + case SNDCTL_SYNTH_MEMAVL: + if (emu->memhdr) + return snd_util_mem_avail(emu->memhdr); + return 0; + } + + return 0; +} + + +/* + * reset device + */ +static int +snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg) +{ + struct snd_emux_port *p; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + p = arg->private_data; + if (snd_BUG_ON(!p)) + return -ENXIO; + snd_emux_reset_port(p); + return 0; +} + + +/* + * receive raw events: only SEQ_PRIVATE is accepted. + */ +static int +snd_emux_event_oss_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct snd_emux *emu; + struct snd_emux_port *p; + unsigned char cmd, *data; + + p = private_data; + if (snd_BUG_ON(!p)) + return -EINVAL; + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -EINVAL; + if (ev->type != SNDRV_SEQ_EVENT_OSS) + return snd_emux_event_input(ev, direct, private_data, atomic, hop); + + data = ev->data.raw8.d; + /* only SEQ_PRIVATE is accepted */ + if (data[0] != 0xfe) + return 0; + cmd = data[2] & _EMUX_OSS_MODE_VALUE_MASK; + if (data[2] & _EMUX_OSS_MODE_FLAG) + emuspec_control(emu, p, cmd, data, atomic, hop); + else + gusspec_control(emu, p, cmd, data, atomic, hop); + return 0; +} + + +/* + * OSS/AWE driver specific h/w controls + */ +static void +emuspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd, + unsigned char *event, int atomic, int hop) +{ + int voice; + unsigned short p1; + short p2; + int i; + struct snd_midi_channel *chan; + + voice = event[3]; + if (voice < 0 || voice >= port->chset.max_channels) + chan = NULL; + else + chan = &port->chset.channels[voice]; + + p1 = *(unsigned short *) &event[4]; + p2 = *(short *) &event[6]; + + switch (cmd) { +#if 0 /* don't do this atomically */ + case _EMUX_OSS_REMOVE_LAST_SAMPLES: + snd_soundfont_remove_unlocked(emu->sflist); + break; +#endif + case _EMUX_OSS_SEND_EFFECT: + if (chan) + snd_emux_send_effect_oss(port, chan, p1, p2); + break; + + case _EMUX_OSS_TERMINATE_ALL: + snd_emux_terminate_all(emu); + break; + + case _EMUX_OSS_TERMINATE_CHANNEL: + /*snd_emux_mute_channel(emu, chan);*/ + break; + case _EMUX_OSS_RESET_CHANNEL: + /*snd_emux_channel_init(chset, chan);*/ + break; + + case _EMUX_OSS_RELEASE_ALL: + fake_event(emu, port, voice, MIDI_CTL_ALL_NOTES_OFF, 0, atomic, hop); + break; + case _EMUX_OSS_NOTEOFF_ALL: + fake_event(emu, port, voice, MIDI_CTL_ALL_SOUNDS_OFF, 0, atomic, hop); + break; + + case _EMUX_OSS_INITIAL_VOLUME: + if (p2) { + port->volume_atten = (short)p1; + snd_emux_update_port(port, SNDRV_EMUX_UPDATE_VOLUME); + } + break; + + case _EMUX_OSS_CHN_PRESSURE: + if (chan) { + chan->midi_pressure = p1; + snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_FMMOD|SNDRV_EMUX_UPDATE_FM2FRQ2); + } + break; + + case _EMUX_OSS_CHANNEL_MODE: + reset_port_mode(port, p1); + snd_emux_reset_port(port); + break; + + case _EMUX_OSS_DRUM_CHANNELS: + port->drum_flags = *(unsigned int*)&event[4]; + for (i = 0; i < port->chset.max_channels; i++) { + chan = &port->chset.channels[i]; + chan->drum_channel = ((port->drum_flags >> i) & 1) ? 1 : 0; + } + break; + + case _EMUX_OSS_MISC_MODE: + if (p1 < EMUX_MD_END) + port->ctrls[p1] = p2; + break; + case _EMUX_OSS_DEBUG_MODE: + break; + + default: + if (emu->ops.oss_ioctl) + emu->ops.oss_ioctl(emu, cmd, p1, p2); + break; + } +} + +/* + * GUS specific h/w controls + */ + +#include <linux/ultrasound.h> + +static void +gusspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd, + unsigned char *event, int atomic, int hop) +{ + int voice; + unsigned short p1; + int plong; + struct snd_midi_channel *chan; + + if (port->port_mode != SNDRV_EMUX_PORT_MODE_OSS_SYNTH) + return; + if (cmd == _GUS_NUMVOICES) + return; + voice = event[3]; + if (voice < 0 || voice >= port->chset.max_channels) + return; + + chan = &port->chset.channels[voice]; + + p1 = *(unsigned short *) &event[4]; + plong = *(int*) &event[4]; + + switch (cmd) { + case _GUS_VOICESAMPLE: + chan->midi_program = p1; + return; + + case _GUS_VOICEBALA: + /* 0 to 15 --> 0 to 127 */ + chan->control[MIDI_CTL_MSB_PAN] = (int)p1 << 3; + snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN); + return; + + case _GUS_VOICEVOL: + case _GUS_VOICEVOL2: + /* not supported yet */ + return; + + case _GUS_RAMPRANGE: + case _GUS_RAMPRATE: + case _GUS_RAMPMODE: + case _GUS_RAMPON: + case _GUS_RAMPOFF: + /* volume ramping not supported */ + return; + + case _GUS_VOLUME_SCALE: + return; + + case _GUS_VOICE_POS: +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_send_effect(port, chan, EMUX_FX_SAMPLE_START, + (short)(plong & 0x7fff), + EMUX_FX_FLAG_SET); + snd_emux_send_effect(port, chan, EMUX_FX_COARSE_SAMPLE_START, + (plong >> 15) & 0xffff, + EMUX_FX_FLAG_SET); +#endif + return; + } +} + + +/* + * send an event to midi emulation + */ +static void +fake_event(struct snd_emux *emu, struct snd_emux_port *port, int ch, int param, int val, int atomic, int hop) +{ + struct snd_seq_event ev; + memset(&ev, 0, sizeof(ev)); + ev.type = SNDRV_SEQ_EVENT_CONTROLLER; + ev.data.control.channel = ch; + ev.data.control.param = param; + ev.data.control.value = val; + snd_emux_event_input(&ev, 0, port, atomic, hop); +} diff --git a/sound/synth/emux/emux_proc.c b/sound/synth/emux/emux_proc.c new file mode 100644 index 000000000..7993e6a01 --- /dev/null +++ b/sound/synth/emux/emux_proc.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2000 Takashi Iwai <tiwai@suse.de> + * + * Proc interface for Emu8k/Emu10k1 WaveTable synth + */ + +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/emux_synth.h> +#include <sound/info.h> +#include "emux_voice.h" + +static void +snd_emux_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buf) +{ + struct snd_emux *emu; + int i; + + emu = entry->private_data; + mutex_lock(&emu->register_mutex); + if (emu->name) + snd_iprintf(buf, "Device: %s\n", emu->name); + snd_iprintf(buf, "Ports: %d\n", emu->num_ports); + snd_iprintf(buf, "Addresses:"); + for (i = 0; i < emu->num_ports; i++) + snd_iprintf(buf, " %d:%d", emu->client, emu->ports[i]); + snd_iprintf(buf, "\n"); + snd_iprintf(buf, "Use Counter: %d\n", emu->used); + snd_iprintf(buf, "Max Voices: %d\n", emu->max_voices); + snd_iprintf(buf, "Allocated Voices: %d\n", emu->num_voices); + if (emu->memhdr) { + snd_iprintf(buf, "Memory Size: %d\n", emu->memhdr->size); + snd_iprintf(buf, "Memory Available: %d\n", snd_util_mem_avail(emu->memhdr)); + snd_iprintf(buf, "Allocated Blocks: %d\n", emu->memhdr->nblocks); + } else { + snd_iprintf(buf, "Memory Size: 0\n"); + } + if (emu->sflist) { + mutex_lock(&emu->sflist->presets_mutex); + snd_iprintf(buf, "SoundFonts: %d\n", emu->sflist->fonts_size); + snd_iprintf(buf, "Instruments: %d\n", emu->sflist->zone_counter); + snd_iprintf(buf, "Samples: %d\n", emu->sflist->sample_counter); + snd_iprintf(buf, "Locked Instruments: %d\n", emu->sflist->zone_locked); + snd_iprintf(buf, "Locked Samples: %d\n", emu->sflist->sample_locked); + mutex_unlock(&emu->sflist->presets_mutex); + } +#if 0 /* debug */ + if (emu->voices[0].state != SNDRV_EMUX_ST_OFF && emu->voices[0].ch >= 0) { + struct snd_emux_voice *vp = &emu->voices[0]; + snd_iprintf(buf, "voice 0: on\n"); + snd_iprintf(buf, "mod delay=%x, atkhld=%x, dcysus=%x, rel=%x\n", + vp->reg.parm.moddelay, + vp->reg.parm.modatkhld, + vp->reg.parm.moddcysus, + vp->reg.parm.modrelease); + snd_iprintf(buf, "vol delay=%x, atkhld=%x, dcysus=%x, rel=%x\n", + vp->reg.parm.voldelay, + vp->reg.parm.volatkhld, + vp->reg.parm.voldcysus, + vp->reg.parm.volrelease); + snd_iprintf(buf, "lfo1 delay=%x, lfo2 delay=%x, pefe=%x\n", + vp->reg.parm.lfo1delay, + vp->reg.parm.lfo2delay, + vp->reg.parm.pefe); + snd_iprintf(buf, "fmmod=%x, tremfrq=%x, fm2frq2=%x\n", + vp->reg.parm.fmmod, + vp->reg.parm.tremfrq, + vp->reg.parm.fm2frq2); + snd_iprintf(buf, "cutoff=%x, filterQ=%x, chorus=%x, reverb=%x\n", + vp->reg.parm.cutoff, + vp->reg.parm.filterQ, + vp->reg.parm.chorus, + vp->reg.parm.reverb); + snd_iprintf(buf, "avol=%x, acutoff=%x, apitch=%x\n", + vp->avol, vp->acutoff, vp->apitch); + snd_iprintf(buf, "apan=%x, aaux=%x, ptarget=%x, vtarget=%x, ftarget=%x\n", + vp->apan, vp->aaux, + vp->ptarget, + vp->vtarget, + vp->ftarget); + snd_iprintf(buf, "start=%x, end=%x, loopstart=%x, loopend=%x\n", + vp->reg.start, vp->reg.end, vp->reg.loopstart, vp->reg.loopend); + snd_iprintf(buf, "sample_mode=%x, rate=%x\n", vp->reg.sample_mode, vp->reg.rate_offset); + } +#endif + mutex_unlock(&emu->register_mutex); +} + + +void snd_emux_proc_init(struct snd_emux *emu, struct snd_card *card, int device) +{ + struct snd_info_entry *entry; + char name[64]; + + sprintf(name, "wavetableD%d", device); + entry = snd_info_create_card_entry(card, name, card->proc_root); + if (entry == NULL) + return; + + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = emu; + entry->c.text.read = snd_emux_proc_info_read; +} + +void snd_emux_proc_free(struct snd_emux *emu) +{ + snd_info_free_entry(emu->proc); + emu->proc = NULL; +} diff --git a/sound/synth/emux/emux_seq.c b/sound/synth/emux/emux_seq.c new file mode 100644 index 000000000..b227c7e0b --- /dev/null +++ b/sound/synth/emux/emux_seq.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Midi Sequencer interface routines. + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de> + */ + +#include "emux_voice.h" +#include <linux/slab.h> +#include <linux/module.h> + +/* Prototypes for static functions */ +static void free_port(void *private); +static void snd_emux_init_port(struct snd_emux_port *p); +static int snd_emux_use(void *private_data, struct snd_seq_port_subscribe *info); +static int snd_emux_unuse(void *private_data, struct snd_seq_port_subscribe *info); + +/* + * MIDI emulation operators + */ +static const struct snd_midi_op emux_ops = { + .note_on = snd_emux_note_on, + .note_off = snd_emux_note_off, + .key_press = snd_emux_key_press, + .note_terminate = snd_emux_terminate_note, + .control = snd_emux_control, + .nrpn = snd_emux_nrpn, + .sysex = snd_emux_sysex, +}; + + +/* + * number of MIDI channels + */ +#define MIDI_CHANNELS 16 + +/* + * type flags for MIDI sequencer port + */ +#define DEFAULT_MIDI_TYPE (SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |\ + SNDRV_SEQ_PORT_TYPE_MIDI_GM |\ + SNDRV_SEQ_PORT_TYPE_MIDI_GS |\ + SNDRV_SEQ_PORT_TYPE_MIDI_XG |\ + SNDRV_SEQ_PORT_TYPE_HARDWARE |\ + SNDRV_SEQ_PORT_TYPE_SYNTHESIZER) + +/* + * Initialise the EMUX Synth by creating a client and registering + * a series of ports. + * Each of the ports will contain the 16 midi channels. Applications + * can connect to these ports to play midi data. + */ +int +snd_emux_init_seq(struct snd_emux *emu, struct snd_card *card, int index) +{ + int i; + struct snd_seq_port_callback pinfo; + char tmpname[64]; + + emu->client = snd_seq_create_kernel_client(card, index, + "%s WaveTable", emu->name); + if (emu->client < 0) { + snd_printk(KERN_ERR "can't create client\n"); + return -ENODEV; + } + + if (emu->num_ports < 0) { + snd_printk(KERN_WARNING "seqports must be greater than zero\n"); + emu->num_ports = 1; + } else if (emu->num_ports >= SNDRV_EMUX_MAX_PORTS) { + snd_printk(KERN_WARNING "too many ports." + "limited max. ports %d\n", SNDRV_EMUX_MAX_PORTS); + emu->num_ports = SNDRV_EMUX_MAX_PORTS; + } + + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.owner = THIS_MODULE; + pinfo.use = snd_emux_use; + pinfo.unuse = snd_emux_unuse; + pinfo.event_input = snd_emux_event_input; + + for (i = 0; i < emu->num_ports; i++) { + struct snd_emux_port *p; + + sprintf(tmpname, "%s Port %d", emu->name, i); + p = snd_emux_create_port(emu, tmpname, MIDI_CHANNELS, + 0, &pinfo); + if (!p) { + snd_printk(KERN_ERR "can't create port\n"); + return -ENOMEM; + } + + p->port_mode = SNDRV_EMUX_PORT_MODE_MIDI; + snd_emux_init_port(p); + emu->ports[i] = p->chset.port; + emu->portptrs[i] = p; + } + + return 0; +} + + +/* + * Detach from the ports that were set up for this synthesizer and + * destroy the kernel client. + */ +void +snd_emux_detach_seq(struct snd_emux *emu) +{ + if (emu->voices) + snd_emux_terminate_all(emu); + + if (emu->client >= 0) { + snd_seq_delete_kernel_client(emu->client); + emu->client = -1; + } +} + + +/* + * create a sequencer port and channel_set + */ + +struct snd_emux_port * +snd_emux_create_port(struct snd_emux *emu, char *name, + int max_channels, int oss_port, + struct snd_seq_port_callback *callback) +{ + struct snd_emux_port *p; + int i, type, cap; + + /* Allocate structures for this channel */ + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return NULL; + + p->chset.channels = kcalloc(max_channels, sizeof(*p->chset.channels), + GFP_KERNEL); + if (!p->chset.channels) { + kfree(p); + return NULL; + } + for (i = 0; i < max_channels; i++) + p->chset.channels[i].number = i; + p->chset.private_data = p; + p->chset.max_channels = max_channels; + p->emu = emu; + p->chset.client = emu->client; +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_create_effect(p); +#endif + callback->private_free = free_port; + callback->private_data = p; + + cap = SNDRV_SEQ_PORT_CAP_WRITE; + if (oss_port) { + type = SNDRV_SEQ_PORT_TYPE_SPECIFIC; + } else { + type = DEFAULT_MIDI_TYPE; + cap |= SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + } + + p->chset.port = snd_seq_event_port_attach(emu->client, callback, + cap, type, max_channels, + emu->max_voices, name); + + return p; +} + + +/* + * release memory block for port + */ +static void +free_port(void *private_data) +{ + struct snd_emux_port *p; + + p = private_data; + if (p) { +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_delete_effect(p); +#endif + kfree(p->chset.channels); + kfree(p); + } +} + + +#define DEFAULT_DRUM_FLAGS (1<<9) + +/* + * initialize the port specific parameters + */ +static void +snd_emux_init_port(struct snd_emux_port *p) +{ + p->drum_flags = DEFAULT_DRUM_FLAGS; + p->volume_atten = 0; + + snd_emux_reset_port(p); +} + + +/* + * reset port + */ +void +snd_emux_reset_port(struct snd_emux_port *port) +{ + int i; + + /* stop all sounds */ + snd_emux_sounds_off_all(port); + + snd_midi_channel_set_clear(&port->chset); + +#ifdef SNDRV_EMUX_USE_RAW_EFFECT + snd_emux_clear_effect(port); +#endif + + /* set port specific control parameters */ + port->ctrls[EMUX_MD_DEF_BANK] = 0; + port->ctrls[EMUX_MD_DEF_DRUM] = 0; + port->ctrls[EMUX_MD_REALTIME_PAN] = 1; + + for (i = 0; i < port->chset.max_channels; i++) { + struct snd_midi_channel *chan = port->chset.channels + i; + chan->drum_channel = ((port->drum_flags >> i) & 1) ? 1 : 0; + } +} + + +/* + * input sequencer event + */ +int +snd_emux_event_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct snd_emux_port *port; + + port = private_data; + if (snd_BUG_ON(!port || !ev)) + return -EINVAL; + + snd_midi_process_event(&emux_ops, ev, &port->chset); + + return 0; +} + + +/* + * increment usage count + */ +static int +__snd_emux_inc_count(struct snd_emux *emu) +{ + emu->used++; + if (!try_module_get(emu->ops.owner)) + goto __error; + if (!try_module_get(emu->card->module)) { + module_put(emu->ops.owner); + __error: + emu->used--; + return 0; + } + return 1; +} + +int snd_emux_inc_count(struct snd_emux *emu) +{ + int ret; + + mutex_lock(&emu->register_mutex); + ret = __snd_emux_inc_count(emu); + mutex_unlock(&emu->register_mutex); + return ret; +} + +/* + * decrease usage count + */ +static void +__snd_emux_dec_count(struct snd_emux *emu) +{ + module_put(emu->card->module); + emu->used--; + if (emu->used <= 0) + snd_emux_terminate_all(emu); + module_put(emu->ops.owner); +} + +void snd_emux_dec_count(struct snd_emux *emu) +{ + mutex_lock(&emu->register_mutex); + __snd_emux_dec_count(emu); + mutex_unlock(&emu->register_mutex); +} + +/* + * Routine that is called upon a first use of a particular port + */ +static int +snd_emux_use(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct snd_emux_port *p; + struct snd_emux *emu; + + p = private_data; + if (snd_BUG_ON(!p)) + return -EINVAL; + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -EINVAL; + + mutex_lock(&emu->register_mutex); + snd_emux_init_port(p); + __snd_emux_inc_count(emu); + mutex_unlock(&emu->register_mutex); + return 0; +} + +/* + * Routine that is called upon the last unuse() of a particular port. + */ +static int +snd_emux_unuse(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct snd_emux_port *p; + struct snd_emux *emu; + + p = private_data; + if (snd_BUG_ON(!p)) + return -EINVAL; + emu = p->emu; + if (snd_BUG_ON(!emu)) + return -EINVAL; + + mutex_lock(&emu->register_mutex); + snd_emux_sounds_off_all(p); + __snd_emux_dec_count(emu); + mutex_unlock(&emu->register_mutex); + return 0; +} + + +/* + * attach virtual rawmidi devices + */ +int snd_emux_init_virmidi(struct snd_emux *emu, struct snd_card *card) +{ + int i; + + emu->vmidi = NULL; + if (emu->midi_ports <= 0) + return 0; + + emu->vmidi = kcalloc(emu->midi_ports, sizeof(*emu->vmidi), GFP_KERNEL); + if (!emu->vmidi) + return -ENOMEM; + + for (i = 0; i < emu->midi_ports; i++) { + struct snd_rawmidi *rmidi; + struct snd_virmidi_dev *rdev; + if (snd_virmidi_new(card, emu->midi_devidx + i, &rmidi) < 0) + goto __error; + rdev = rmidi->private_data; + sprintf(rmidi->name, "%s Synth MIDI", emu->name); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_ATTACH; + rdev->client = emu->client; + rdev->port = emu->ports[i]; + if (snd_device_register(card, rmidi) < 0) { + snd_device_free(card, rmidi); + goto __error; + } + emu->vmidi[i] = rmidi; + /* snd_printk(KERN_DEBUG "virmidi %d ok\n", i); */ + } + return 0; + +__error: + /* snd_printk(KERN_DEBUG "error init..\n"); */ + snd_emux_delete_virmidi(emu); + return -ENOMEM; +} + +int snd_emux_delete_virmidi(struct snd_emux *emu) +{ + int i; + + if (!emu->vmidi) + return 0; + + for (i = 0; i < emu->midi_ports; i++) { + if (emu->vmidi[i]) + snd_device_free(emu->card, emu->vmidi[i]); + } + kfree(emu->vmidi); + emu->vmidi = NULL; + return 0; +} diff --git a/sound/synth/emux/emux_synth.c b/sound/synth/emux/emux_synth.c new file mode 100644 index 000000000..a5385efce --- /dev/null +++ b/sound/synth/emux/emux_synth.c @@ -0,0 +1,968 @@ +// 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; + offset += vp->emu->pitch_shift; + 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); diff --git a/sound/synth/emux/emux_voice.h b/sound/synth/emux/emux_voice.h new file mode 100644 index 000000000..cb061034a --- /dev/null +++ b/sound/synth/emux/emux_voice.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __EMUX_VOICE_H +#define __EMUX_VOICE_H + +/* + * A structure to keep track of each hardware voice + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/wait.h> +#include <linux/sched.h> +#include <sound/core.h> +#include <sound/emux_synth.h> + +/* Prototypes for emux_seq.c */ +int snd_emux_init_seq(struct snd_emux *emu, struct snd_card *card, int index); +void snd_emux_detach_seq(struct snd_emux *emu); +struct snd_emux_port *snd_emux_create_port(struct snd_emux *emu, char *name, + int max_channels, int type, + struct snd_seq_port_callback *callback); +void snd_emux_reset_port(struct snd_emux_port *port); +int snd_emux_event_input(struct snd_seq_event *ev, int direct, void *private, + int atomic, int hop); +int snd_emux_inc_count(struct snd_emux *emu); +void snd_emux_dec_count(struct snd_emux *emu); +int snd_emux_init_virmidi(struct snd_emux *emu, struct snd_card *card); +int snd_emux_delete_virmidi(struct snd_emux *emu); + +/* Prototypes for emux_synth.c */ +void snd_emux_init_voices(struct snd_emux *emu); + +void snd_emux_note_on(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_emux_note_off(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_emux_key_press(void *p, int note, int vel, struct snd_midi_channel *chan); +void snd_emux_terminate_note(void *p, int note, struct snd_midi_channel *chan); +void snd_emux_control(void *p, int type, struct snd_midi_channel *chan); + +void snd_emux_sounds_off_all(struct snd_emux_port *port); +void snd_emux_update_channel(struct snd_emux_port *port, + struct snd_midi_channel *chan, int update); +void snd_emux_update_port(struct snd_emux_port *port, int update); + +void snd_emux_timer_callback(struct timer_list *t); + +/* emux_effect.c */ +#ifdef SNDRV_EMUX_USE_RAW_EFFECT +void snd_emux_create_effect(struct snd_emux_port *p); +void snd_emux_delete_effect(struct snd_emux_port *p); +void snd_emux_clear_effect(struct snd_emux_port *p); +void snd_emux_setup_effect(struct snd_emux_voice *vp); +void snd_emux_send_effect_oss(struct snd_emux_port *port, + struct snd_midi_channel *chan, int type, int val); +void snd_emux_send_effect(struct snd_emux_port *port, + struct snd_midi_channel *chan, int type, int val, int mode); +#endif + +/* emux_nrpn.c */ +void snd_emux_sysex(void *private_data, unsigned char *buf, int len, + int parsed, struct snd_midi_channel_set *chset); +int snd_emux_xg_control(struct snd_emux_port *port, + struct snd_midi_channel *chan, int param); +void snd_emux_nrpn(void *private_data, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset); + +/* emux_oss.c */ +void snd_emux_init_seq_oss(struct snd_emux *emu); +void snd_emux_detach_seq_oss(struct snd_emux *emu); + +/* emux_proc.c */ +#ifdef CONFIG_SND_PROC_FS +void snd_emux_proc_init(struct snd_emux *emu, struct snd_card *card, int device); +void snd_emux_proc_free(struct snd_emux *emu); +#else +static inline void snd_emux_proc_init(struct snd_emux *emu, + struct snd_card *card, int device) {} +static inline void snd_emux_proc_free(struct snd_emux *emu) {} +#endif + +#define STATE_IS_PLAYING(s) ((s) & SNDRV_EMUX_ST_ON) + +/* emux_hwdep.c */ +int snd_emux_init_hwdep(struct snd_emux *emu); +void snd_emux_delete_hwdep(struct snd_emux *emu); + +#endif diff --git a/sound/synth/emux/soundfont.c b/sound/synth/emux/soundfont.c new file mode 100644 index 000000000..9ebc711af --- /dev/null +++ b/sound/synth/emux/soundfont.c @@ -0,0 +1,1486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Soundfont generic routines. + * It is intended that these should be used by any driver that is willing + * to accept soundfont patches. + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de> + */ +/* + * Deal with reading in of a soundfont. Code follows the OSS way + * of doing things so that the old sfxload utility can be used. + * Everything may change when there is an alsa way of doing things. + */ +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/soundfont.h> +#include <sound/seq_oss_legacy.h> + +/* Prototypes for static functions */ + +static int open_patch(struct snd_sf_list *sflist, const char __user *data, + int count, int client); +static struct snd_soundfont *newsf(struct snd_sf_list *sflist, int type, char *name); +static int is_identical_font(struct snd_soundfont *sf, int type, unsigned char *name); +static int close_patch(struct snd_sf_list *sflist); +static int probe_data(struct snd_sf_list *sflist, int sample_id); +static void set_zone_counter(struct snd_sf_list *sflist, + struct snd_soundfont *sf, struct snd_sf_zone *zp); +static struct snd_sf_zone *sf_zone_new(struct snd_sf_list *sflist, + struct snd_soundfont *sf); +static void set_sample_counter(struct snd_sf_list *sflist, + struct snd_soundfont *sf, struct snd_sf_sample *sp); +static struct snd_sf_sample *sf_sample_new(struct snd_sf_list *sflist, + struct snd_soundfont *sf); +static void sf_sample_delete(struct snd_sf_list *sflist, + struct snd_soundfont *sf, struct snd_sf_sample *sp); +static int load_map(struct snd_sf_list *sflist, const void __user *data, int count); +static int load_info(struct snd_sf_list *sflist, const void __user *data, long count); +static int remove_info(struct snd_sf_list *sflist, struct snd_soundfont *sf, + int bank, int instr); +static void init_voice_info(struct soundfont_voice_info *avp); +static void init_voice_parm(struct soundfont_voice_parm *pp); +static struct snd_sf_sample *set_sample(struct snd_soundfont *sf, + struct soundfont_voice_info *avp); +static struct snd_sf_sample *find_sample(struct snd_soundfont *sf, int sample_id); +static int load_data(struct snd_sf_list *sflist, const void __user *data, long count); +static void rebuild_presets(struct snd_sf_list *sflist); +static void add_preset(struct snd_sf_list *sflist, struct snd_sf_zone *cur); +static void delete_preset(struct snd_sf_list *sflist, struct snd_sf_zone *zp); +static struct snd_sf_zone *search_first_zone(struct snd_sf_list *sflist, + int bank, int preset, int key); +static int search_zones(struct snd_sf_list *sflist, int *notep, int vel, + int preset, int bank, struct snd_sf_zone **table, + int max_layers, int level); +static int get_index(int bank, int instr, int key); +static void snd_sf_init(struct snd_sf_list *sflist); +static void snd_sf_clear(struct snd_sf_list *sflist); + +/* + * lock access to sflist + */ +static void +lock_preset(struct snd_sf_list *sflist) +{ + unsigned long flags; + mutex_lock(&sflist->presets_mutex); + spin_lock_irqsave(&sflist->lock, flags); + sflist->presets_locked = 1; + spin_unlock_irqrestore(&sflist->lock, flags); +} + + +/* + * remove lock + */ +static void +unlock_preset(struct snd_sf_list *sflist) +{ + unsigned long flags; + spin_lock_irqsave(&sflist->lock, flags); + sflist->presets_locked = 0; + spin_unlock_irqrestore(&sflist->lock, flags); + mutex_unlock(&sflist->presets_mutex); +} + + +/* + * close the patch if the patch was opened by this client. + */ +int +snd_soundfont_close_check(struct snd_sf_list *sflist, int client) +{ + unsigned long flags; + spin_lock_irqsave(&sflist->lock, flags); + if (sflist->open_client == client) { + spin_unlock_irqrestore(&sflist->lock, flags); + return close_patch(sflist); + } + spin_unlock_irqrestore(&sflist->lock, flags); + return 0; +} + + +/* + * Deal with a soundfont patch. Any driver could use these routines + * although it was designed for the AWE64. + * + * The sample_write and callargs pararameters allow a callback into + * the actual driver to write sample data to the board or whatever + * it wants to do with it. + */ +int +snd_soundfont_load(struct snd_sf_list *sflist, const void __user *data, + long count, int client) +{ + struct soundfont_patch_info patch; + unsigned long flags; + int rc; + + if (count < (long)sizeof(patch)) { + snd_printk(KERN_ERR "patch record too small %ld\n", count); + return -EINVAL; + } + if (copy_from_user(&patch, data, sizeof(patch))) + return -EFAULT; + + count -= sizeof(patch); + data += sizeof(patch); + + if (patch.key != SNDRV_OSS_SOUNDFONT_PATCH) { + snd_printk(KERN_ERR "The wrong kind of patch %x\n", patch.key); + return -EINVAL; + } + if (count < patch.len) { + snd_printk(KERN_ERR "Patch too short %ld, need %d\n", + count, patch.len); + return -EINVAL; + } + if (patch.len < 0) { + snd_printk(KERN_ERR "poor length %d\n", patch.len); + return -EINVAL; + } + + if (patch.type == SNDRV_SFNT_OPEN_PATCH) { + /* grab sflist to open */ + lock_preset(sflist); + rc = open_patch(sflist, data, count, client); + unlock_preset(sflist); + return rc; + } + + /* check if other client already opened patch */ + spin_lock_irqsave(&sflist->lock, flags); + if (sflist->open_client != client) { + spin_unlock_irqrestore(&sflist->lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&sflist->lock, flags); + + lock_preset(sflist); + rc = -EINVAL; + switch (patch.type) { + case SNDRV_SFNT_LOAD_INFO: + rc = load_info(sflist, data, count); + break; + case SNDRV_SFNT_LOAD_DATA: + rc = load_data(sflist, data, count); + break; + case SNDRV_SFNT_CLOSE_PATCH: + rc = close_patch(sflist); + break; + case SNDRV_SFNT_REPLACE_DATA: + /*rc = replace_data(&patch, data, count);*/ + break; + case SNDRV_SFNT_MAP_PRESET: + rc = load_map(sflist, data, count); + break; + case SNDRV_SFNT_PROBE_DATA: + rc = probe_data(sflist, patch.optarg); + break; + case SNDRV_SFNT_REMOVE_INFO: + /* patch must be opened */ + if (!sflist->currsf) { + snd_printk(KERN_ERR "soundfont: remove_info: " + "patch not opened\n"); + rc = -EINVAL; + } else { + int bank, instr; + bank = ((unsigned short)patch.optarg >> 8) & 0xff; + instr = (unsigned short)patch.optarg & 0xff; + if (! remove_info(sflist, sflist->currsf, bank, instr)) + rc = -EINVAL; + else + rc = 0; + } + break; + } + unlock_preset(sflist); + + return rc; +} + + +/* check if specified type is special font (GUS or preset-alias) */ +static inline int +is_special_type(int type) +{ + type &= 0x0f; + return (type == SNDRV_SFNT_PAT_TYPE_GUS || + type == SNDRV_SFNT_PAT_TYPE_MAP); +} + + +/* open patch; create sf list */ +static int +open_patch(struct snd_sf_list *sflist, const char __user *data, + int count, int client) +{ + struct soundfont_open_parm parm; + struct snd_soundfont *sf; + unsigned long flags; + + spin_lock_irqsave(&sflist->lock, flags); + if (sflist->open_client >= 0 || sflist->currsf) { + spin_unlock_irqrestore(&sflist->lock, flags); + return -EBUSY; + } + spin_unlock_irqrestore(&sflist->lock, flags); + + if (copy_from_user(&parm, data, sizeof(parm))) + return -EFAULT; + + if (is_special_type(parm.type)) { + parm.type |= SNDRV_SFNT_PAT_SHARED; + sf = newsf(sflist, parm.type, NULL); + } else + sf = newsf(sflist, parm.type, parm.name); + if (sf == NULL) { + return -ENOMEM; + } + + spin_lock_irqsave(&sflist->lock, flags); + sflist->open_client = client; + sflist->currsf = sf; + spin_unlock_irqrestore(&sflist->lock, flags); + + return 0; +} + +/* + * Allocate a new soundfont structure. + */ +static struct snd_soundfont * +newsf(struct snd_sf_list *sflist, int type, char *name) +{ + struct snd_soundfont *sf; + + /* check the shared fonts */ + if (type & SNDRV_SFNT_PAT_SHARED) { + for (sf = sflist->fonts; sf; sf = sf->next) { + if (is_identical_font(sf, type, name)) { + return sf; + } + } + } + + /* not found -- create a new one */ + sf = kzalloc(sizeof(*sf), GFP_KERNEL); + if (sf == NULL) + return NULL; + sf->id = sflist->fonts_size; + sflist->fonts_size++; + + /* prepend this record */ + sf->next = sflist->fonts; + sflist->fonts = sf; + + sf->type = type; + sf->zones = NULL; + sf->samples = NULL; + if (name) + memcpy(sf->name, name, SNDRV_SFNT_PATCH_NAME_LEN); + + return sf; +} + +/* check if the given name matches to the existing list */ +static int +is_identical_font(struct snd_soundfont *sf, int type, unsigned char *name) +{ + return ((sf->type & SNDRV_SFNT_PAT_SHARED) && + (sf->type & 0x0f) == (type & 0x0f) && + (name == NULL || + memcmp(sf->name, name, SNDRV_SFNT_PATCH_NAME_LEN) == 0)); +} + +/* + * Close the current patch. + */ +static int +close_patch(struct snd_sf_list *sflist) +{ + unsigned long flags; + + spin_lock_irqsave(&sflist->lock, flags); + sflist->currsf = NULL; + sflist->open_client = -1; + spin_unlock_irqrestore(&sflist->lock, flags); + + rebuild_presets(sflist); + + return 0; + +} + +/* probe sample in the current list -- nothing to be loaded */ +static int +probe_data(struct snd_sf_list *sflist, int sample_id) +{ + /* patch must be opened */ + if (sflist->currsf) { + /* search the specified sample by optarg */ + if (find_sample(sflist->currsf, sample_id)) + return 0; + } + return -EINVAL; +} + +/* + * increment zone counter + */ +static void +set_zone_counter(struct snd_sf_list *sflist, struct snd_soundfont *sf, + struct snd_sf_zone *zp) +{ + zp->counter = sflist->zone_counter++; + if (sf->type & SNDRV_SFNT_PAT_LOCKED) + sflist->zone_locked = sflist->zone_counter; +} + +/* + * allocate a new zone record + */ +static struct snd_sf_zone * +sf_zone_new(struct snd_sf_list *sflist, struct snd_soundfont *sf) +{ + struct snd_sf_zone *zp; + + if ((zp = kzalloc(sizeof(*zp), GFP_KERNEL)) == NULL) + return NULL; + zp->next = sf->zones; + sf->zones = zp; + + init_voice_info(&zp->v); + + set_zone_counter(sflist, sf, zp); + return zp; +} + + +/* + * increment sample counter + */ +static void +set_sample_counter(struct snd_sf_list *sflist, struct snd_soundfont *sf, + struct snd_sf_sample *sp) +{ + sp->counter = sflist->sample_counter++; + if (sf->type & SNDRV_SFNT_PAT_LOCKED) + sflist->sample_locked = sflist->sample_counter; +} + +/* + * allocate a new sample list record + */ +static struct snd_sf_sample * +sf_sample_new(struct snd_sf_list *sflist, struct snd_soundfont *sf) +{ + struct snd_sf_sample *sp; + + if ((sp = kzalloc(sizeof(*sp), GFP_KERNEL)) == NULL) + return NULL; + + sp->next = sf->samples; + sf->samples = sp; + + set_sample_counter(sflist, sf, sp); + return sp; +} + +/* + * delete sample list -- this is an exceptional job. + * only the last allocated sample can be deleted. + */ +static void +sf_sample_delete(struct snd_sf_list *sflist, struct snd_soundfont *sf, + struct snd_sf_sample *sp) +{ + /* only last sample is accepted */ + if (sp == sf->samples) { + sf->samples = sp->next; + kfree(sp); + } +} + + +/* load voice map */ +static int +load_map(struct snd_sf_list *sflist, const void __user *data, int count) +{ + struct snd_sf_zone *zp, *prevp; + struct snd_soundfont *sf; + struct soundfont_voice_map map; + + /* get the link info */ + if (count < (int)sizeof(map)) + return -EINVAL; + if (copy_from_user(&map, data, sizeof(map))) + return -EFAULT; + + if (map.map_instr < 0 || map.map_instr >= SF_MAX_INSTRUMENTS) + return -EINVAL; + + sf = newsf(sflist, SNDRV_SFNT_PAT_TYPE_MAP|SNDRV_SFNT_PAT_SHARED, NULL); + if (sf == NULL) + return -ENOMEM; + + prevp = NULL; + for (zp = sf->zones; zp; prevp = zp, zp = zp->next) { + if (zp->mapped && + zp->instr == map.map_instr && + zp->bank == map.map_bank && + zp->v.low == map.map_key && + zp->v.start == map.src_instr && + zp->v.end == map.src_bank && + zp->v.fixkey == map.src_key) { + /* the same mapping is already present */ + /* relink this record to the link head */ + if (prevp) { + prevp->next = zp->next; + zp->next = sf->zones; + sf->zones = zp; + } + /* update the counter */ + set_zone_counter(sflist, sf, zp); + return 0; + } + } + + /* create a new zone */ + if ((zp = sf_zone_new(sflist, sf)) == NULL) + return -ENOMEM; + + zp->bank = map.map_bank; + zp->instr = map.map_instr; + zp->mapped = 1; + if (map.map_key >= 0) { + zp->v.low = map.map_key; + zp->v.high = map.map_key; + } + zp->v.start = map.src_instr; + zp->v.end = map.src_bank; + zp->v.fixkey = map.src_key; + zp->v.sf_id = sf->id; + + add_preset(sflist, zp); + + return 0; +} + + +/* remove the present instrument layers */ +static int +remove_info(struct snd_sf_list *sflist, struct snd_soundfont *sf, + int bank, int instr) +{ + struct snd_sf_zone *prev, *next, *p; + int removed = 0; + + prev = NULL; + for (p = sf->zones; p; p = next) { + next = p->next; + if (! p->mapped && + p->bank == bank && p->instr == instr) { + /* remove this layer */ + if (prev) + prev->next = next; + else + sf->zones = next; + removed++; + kfree(p); + } else + prev = p; + } + if (removed) + rebuild_presets(sflist); + return removed; +} + + +/* + * Read an info record from the user buffer and save it on the current + * open soundfont. + */ +static int +load_info(struct snd_sf_list *sflist, const void __user *data, long count) +{ + struct snd_soundfont *sf; + struct snd_sf_zone *zone; + struct soundfont_voice_rec_hdr hdr; + int i; + + /* patch must be opened */ + if ((sf = sflist->currsf) == NULL) + return -EINVAL; + + if (is_special_type(sf->type)) + return -EINVAL; + + if (count < (long)sizeof(hdr)) { + printk(KERN_ERR "Soundfont error: invalid patch zone length\n"); + return -EINVAL; + } + if (copy_from_user((char*)&hdr, data, sizeof(hdr))) + return -EFAULT; + + data += sizeof(hdr); + count -= sizeof(hdr); + + if (hdr.nvoices <= 0 || hdr.nvoices >= 100) { + printk(KERN_ERR "Soundfont error: Illegal voice number %d\n", + hdr.nvoices); + return -EINVAL; + } + + if (count < (long)sizeof(struct soundfont_voice_info) * hdr.nvoices) { + printk(KERN_ERR "Soundfont Error: " + "patch length(%ld) is smaller than nvoices(%d)\n", + count, hdr.nvoices); + return -EINVAL; + } + + switch (hdr.write_mode) { + case SNDRV_SFNT_WR_EXCLUSIVE: + /* exclusive mode - if the instrument already exists, + return error */ + for (zone = sf->zones; zone; zone = zone->next) { + if (!zone->mapped && + zone->bank == hdr.bank && + zone->instr == hdr.instr) + return -EINVAL; + } + break; + case SNDRV_SFNT_WR_REPLACE: + /* replace mode - remove the instrument if it already exists */ + remove_info(sflist, sf, hdr.bank, hdr.instr); + break; + } + + for (i = 0; i < hdr.nvoices; i++) { + struct snd_sf_zone tmpzone; + + /* copy awe_voice_info parameters */ + if (copy_from_user(&tmpzone.v, data, sizeof(tmpzone.v))) { + return -EFAULT; + } + + data += sizeof(tmpzone.v); + count -= sizeof(tmpzone.v); + + tmpzone.bank = hdr.bank; + tmpzone.instr = hdr.instr; + tmpzone.mapped = 0; + tmpzone.v.sf_id = sf->id; + if (tmpzone.v.mode & SNDRV_SFNT_MODE_INIT_PARM) + init_voice_parm(&tmpzone.v.parm); + + /* create a new zone */ + if ((zone = sf_zone_new(sflist, sf)) == NULL) { + return -ENOMEM; + } + + /* copy the temporary data */ + zone->bank = tmpzone.bank; + zone->instr = tmpzone.instr; + zone->v = tmpzone.v; + + /* look up the sample */ + zone->sample = set_sample(sf, &zone->v); + } + + return 0; +} + + +/* initialize voice_info record */ +static void +init_voice_info(struct soundfont_voice_info *avp) +{ + memset(avp, 0, sizeof(*avp)); + + avp->root = 60; + avp->high = 127; + avp->velhigh = 127; + avp->fixkey = -1; + avp->fixvel = -1; + avp->fixpan = -1; + avp->pan = -1; + avp->amplitude = 127; + avp->scaleTuning = 100; + + init_voice_parm(&avp->parm); +} + +/* initialize voice_parm record: + * Env1/2: delay=0, attack=0, hold=0, sustain=0, decay=0, release=0. + * Vibrato and Tremolo effects are zero. + * Cutoff is maximum. + * Chorus and Reverb effects are zero. + */ +static void +init_voice_parm(struct soundfont_voice_parm *pp) +{ + memset(pp, 0, sizeof(*pp)); + + pp->moddelay = 0x8000; + pp->modatkhld = 0x7f7f; + pp->moddcysus = 0x7f7f; + pp->modrelease = 0x807f; + + pp->voldelay = 0x8000; + pp->volatkhld = 0x7f7f; + pp->voldcysus = 0x7f7f; + pp->volrelease = 0x807f; + + pp->lfo1delay = 0x8000; + pp->lfo2delay = 0x8000; + + pp->cutoff = 0xff; +} + +/* search the specified sample */ +static struct snd_sf_sample * +set_sample(struct snd_soundfont *sf, struct soundfont_voice_info *avp) +{ + struct snd_sf_sample *sample; + + sample = find_sample(sf, avp->sample); + if (sample == NULL) + return NULL; + + /* add in the actual sample offsets: + * The voice_info addresses define only the relative offset + * from sample pointers. Here we calculate the actual DRAM + * offset from sample pointers. + */ + avp->start += sample->v.start; + avp->end += sample->v.end; + avp->loopstart += sample->v.loopstart; + avp->loopend += sample->v.loopend; + + /* copy mode flags */ + avp->sample_mode = sample->v.mode_flags; + + return sample; +} + +/* find the sample pointer with the given id in the soundfont */ +static struct snd_sf_sample * +find_sample(struct snd_soundfont *sf, int sample_id) +{ + struct snd_sf_sample *p; + + if (sf == NULL) + return NULL; + + for (p = sf->samples; p; p = p->next) { + if (p->v.sample == sample_id) + return p; + } + return NULL; +} + + +/* + * Load sample information, this can include data to be loaded onto + * the soundcard. It can also just be a pointer into soundcard ROM. + * If there is data it will be written to the soundcard via the callback + * routine. + */ +static int +load_data(struct snd_sf_list *sflist, const void __user *data, long count) +{ + struct snd_soundfont *sf; + struct soundfont_sample_info sample_info; + struct snd_sf_sample *sp; + long off; + + /* patch must be opened */ + if ((sf = sflist->currsf) == NULL) + return -EINVAL; + + if (is_special_type(sf->type)) + return -EINVAL; + + if (copy_from_user(&sample_info, data, sizeof(sample_info))) + return -EFAULT; + + off = sizeof(sample_info); + + if (sample_info.size != (count-off)/2) + return -EINVAL; + + /* Check for dup */ + if (find_sample(sf, sample_info.sample)) { + /* if shared sample, skip this data */ + if (sf->type & SNDRV_SFNT_PAT_SHARED) + return 0; + return -EINVAL; + } + + /* Allocate a new sample structure */ + if ((sp = sf_sample_new(sflist, sf)) == NULL) + return -ENOMEM; + + sp->v = sample_info; + sp->v.sf_id = sf->id; + sp->v.dummy = 0; + sp->v.truesize = sp->v.size; + + /* + * If there is wave data then load it. + */ + if (sp->v.size > 0) { + int rc; + rc = sflist->callback.sample_new + (sflist->callback.private_data, sp, sflist->memhdr, + data + off, count - off); + if (rc < 0) { + sf_sample_delete(sflist, sf, sp); + return rc; + } + sflist->mem_used += sp->v.truesize; + } + + return count; +} + + +/* log2_tbl[i] = log2(i+128) * 0x10000 */ +static const int log_tbl[129] = { + 0x70000, 0x702df, 0x705b9, 0x7088e, 0x70b5d, 0x70e26, 0x710eb, 0x713aa, + 0x71663, 0x71918, 0x71bc8, 0x71e72, 0x72118, 0x723b9, 0x72655, 0x728ed, + 0x72b80, 0x72e0e, 0x73098, 0x7331d, 0x7359e, 0x7381b, 0x73a93, 0x73d08, + 0x73f78, 0x741e4, 0x7444c, 0x746b0, 0x74910, 0x74b6c, 0x74dc4, 0x75019, + 0x75269, 0x754b6, 0x75700, 0x75946, 0x75b88, 0x75dc7, 0x76002, 0x7623a, + 0x7646e, 0x766a0, 0x768cd, 0x76af8, 0x76d1f, 0x76f43, 0x77164, 0x77382, + 0x7759d, 0x777b4, 0x779c9, 0x77bdb, 0x77dea, 0x77ff5, 0x781fe, 0x78404, + 0x78608, 0x78808, 0x78a06, 0x78c01, 0x78df9, 0x78fef, 0x791e2, 0x793d2, + 0x795c0, 0x797ab, 0x79993, 0x79b79, 0x79d5d, 0x79f3e, 0x7a11d, 0x7a2f9, + 0x7a4d3, 0x7a6ab, 0x7a880, 0x7aa53, 0x7ac24, 0x7adf2, 0x7afbe, 0x7b188, + 0x7b350, 0x7b515, 0x7b6d8, 0x7b899, 0x7ba58, 0x7bc15, 0x7bdd0, 0x7bf89, + 0x7c140, 0x7c2f5, 0x7c4a7, 0x7c658, 0x7c807, 0x7c9b3, 0x7cb5e, 0x7cd07, + 0x7ceae, 0x7d053, 0x7d1f7, 0x7d398, 0x7d538, 0x7d6d6, 0x7d872, 0x7da0c, + 0x7dba4, 0x7dd3b, 0x7ded0, 0x7e063, 0x7e1f4, 0x7e384, 0x7e512, 0x7e69f, + 0x7e829, 0x7e9b3, 0x7eb3a, 0x7ecc0, 0x7ee44, 0x7efc7, 0x7f148, 0x7f2c8, + 0x7f446, 0x7f5c2, 0x7f73d, 0x7f8b7, 0x7fa2f, 0x7fba5, 0x7fd1a, 0x7fe8d, + 0x80000, +}; + +/* convert from linear to log value + * + * conversion: value = log2(amount / base) * ratio + * + * argument: + * amount = linear value (unsigned, 32bit max) + * offset = base offset (:= log2(base) * 0x10000) + * ratio = division ratio + * + */ +int +snd_sf_linear_to_log(unsigned int amount, int offset, int ratio) +{ + int v; + int s, low, bit; + + if (amount < 2) + return 0; + for (bit = 0; ! (amount & 0x80000000L); bit++) + amount <<= 1; + s = (amount >> 24) & 0x7f; + low = (amount >> 16) & 0xff; + /* linear approxmimation by lower 8 bit */ + v = (log_tbl[s + 1] * low + log_tbl[s] * (0x100 - low)) >> 8; + v -= offset; + v = (v * ratio) >> 16; + v += (24 - bit) * ratio; + return v; +} + +EXPORT_SYMBOL(snd_sf_linear_to_log); + + +#define OFFSET_MSEC 653117 /* base = 1000 */ +#define OFFSET_ABSCENT 851781 /* base = 8176 */ +#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */ + +#define ABSCENT_RATIO 1200 +#define TIMECENT_RATIO 1200 +#define SAMPLERATE_RATIO 4096 + +/* + * mHz to abscent + * conversion: abscent = log2(MHz / 8176) * 1200 + */ +static int +freq_to_note(int mhz) +{ + return snd_sf_linear_to_log(mhz, OFFSET_ABSCENT, ABSCENT_RATIO); +} + +/* convert Hz to AWE32 rate offset: + * sample pitch offset for the specified sample rate + * rate=44100 is no offset, each 4096 is 1 octave (twice). + * eg, when rate is 22050, this offset becomes -4096. + * + * conversion: offset = log2(Hz / 44100) * 4096 + */ +static int +calc_rate_offset(int hz) +{ + return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO); +} + + +/* calculate GUS envelope time */ +static int +calc_gus_envelope_time(int rate, int start, int end) +{ + int r, p, t; + r = (3 - ((rate >> 6) & 3)) * 3; + p = rate & 0x3f; + if (!p) + p = 1; + t = end - start; + if (t < 0) t = -t; + if (13 > r) + t = t << (13 - r); + else + t = t >> (r - 13); + return (t * 10) / (p * 441); +} + +/* convert envelope time parameter to soundfont parameters */ + +/* attack & decay/release time table (msec) */ +static const short attack_time_tbl[128] = { +32767, 32767, 5989, 4235, 2994, 2518, 2117, 1780, 1497, 1373, 1259, 1154, 1058, 970, 890, 816, +707, 691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377, +361, 345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188, +180, 172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94, +90, 86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47, +45, 43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, +22, 21, 20, 19, 19, 18, 17, 16, 16, 15, 15, 14, 13, 13, 12, 12, +11, 11, 10, 10, 10, 9, 9, 8, 8, 8, 8, 7, 7, 7, 6, 0, +}; + +static const short decay_time_tbl[128] = { +32767, 32767, 22614, 15990, 11307, 9508, 7995, 6723, 5653, 5184, 4754, 4359, 3997, 3665, 3361, 3082, +2828, 2765, 2648, 2535, 2428, 2325, 2226, 2132, 2042, 1955, 1872, 1793, 1717, 1644, 1574, 1507, +1443, 1382, 1324, 1267, 1214, 1162, 1113, 1066, 978, 936, 897, 859, 822, 787, 754, 722, +691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377, 361, +345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188, 180, +172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94, 90, +86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47, 45, +43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, 22, +}; + +/* delay time = 0x8000 - msec/92 */ +int +snd_sf_calc_parm_hold(int msec) +{ + int val = (0x7f * 92 - msec) / 92; + if (val < 1) val = 1; + if (val >= 126) val = 126; + return val; +} + +/* search an index for specified time from given time table */ +static int +calc_parm_search(int msec, const short *table) +{ + int left = 1, right = 127, mid; + while (left < right) { + mid = (left + right) / 2; + if (msec < (int)table[mid]) + left = mid + 1; + else + right = mid; + } + return left; +} + +/* attack time: search from time table */ +int +snd_sf_calc_parm_attack(int msec) +{ + return calc_parm_search(msec, attack_time_tbl); +} + +/* decay/release time: search from time table */ +int +snd_sf_calc_parm_decay(int msec) +{ + return calc_parm_search(msec, decay_time_tbl); +} + +int snd_sf_vol_table[128] = { + 255,111,95,86,79,74,70,66,63,61,58,56,54,52,50,49, + 47,46,45,43,42,41,40,39,38,37,36,35,34,34,33,32, + 31,31,30,29,29,28,27,27,26,26,25,24,24,23,23,22, + 22,21,21,21,20,20,19,19,18,18,18,17,17,16,16,16, + 15,15,15,14,14,14,13,13,13,12,12,12,11,11,11,10, + 10,10,10,9,9,9,8,8,8,8,7,7,7,7,6,6, + 6,6,5,5,5,5,5,4,4,4,4,3,3,3,3,3, + 2,2,2,2,2,1,1,1,1,1,0,0,0,0,0,0, +}; + + +#define calc_gus_sustain(val) (0x7f - snd_sf_vol_table[(val)/2]) +#define calc_gus_attenuation(val) snd_sf_vol_table[(val)/2] + +/* load GUS patch */ +static int +load_guspatch(struct snd_sf_list *sflist, const char __user *data, + long count, int client) +{ + struct patch_info patch; + struct snd_soundfont *sf; + struct snd_sf_zone *zone; + struct snd_sf_sample *smp; + int note, sample_id; + int rc; + + if (count < (long)sizeof(patch)) { + snd_printk(KERN_ERR "patch record too small %ld\n", count); + return -EINVAL; + } + if (copy_from_user(&patch, data, sizeof(patch))) + return -EFAULT; + + count -= sizeof(patch); + data += sizeof(patch); + + sf = newsf(sflist, SNDRV_SFNT_PAT_TYPE_GUS|SNDRV_SFNT_PAT_SHARED, NULL); + if (sf == NULL) + return -ENOMEM; + if ((smp = sf_sample_new(sflist, sf)) == NULL) + return -ENOMEM; + sample_id = sflist->sample_counter; + smp->v.sample = sample_id; + smp->v.start = 0; + smp->v.end = patch.len; + smp->v.loopstart = patch.loop_start; + smp->v.loopend = patch.loop_end; + smp->v.size = patch.len; + + /* set up mode flags */ + smp->v.mode_flags = 0; + if (!(patch.mode & WAVE_16_BITS)) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_8BITS; + if (patch.mode & WAVE_UNSIGNED) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_UNSIGNED; + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_NO_BLANK; + if (!(patch.mode & (WAVE_LOOPING|WAVE_BIDIR_LOOP|WAVE_LOOP_BACK))) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_SINGLESHOT; + if (patch.mode & WAVE_BIDIR_LOOP) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_BIDIR_LOOP; + if (patch.mode & WAVE_LOOP_BACK) + smp->v.mode_flags |= SNDRV_SFNT_SAMPLE_REVERSE_LOOP; + + if (patch.mode & WAVE_16_BITS) { + /* convert to word offsets */ + smp->v.size /= 2; + smp->v.end /= 2; + smp->v.loopstart /= 2; + smp->v.loopend /= 2; + } + /*smp->v.loopend++;*/ + + smp->v.dummy = 0; + smp->v.truesize = 0; + smp->v.sf_id = sf->id; + + /* set up voice info */ + if ((zone = sf_zone_new(sflist, sf)) == NULL) { + sf_sample_delete(sflist, sf, smp); + return -ENOMEM; + } + + /* + * load wave data + */ + if (sflist->callback.sample_new) { + rc = sflist->callback.sample_new + (sflist->callback.private_data, smp, sflist->memhdr, + data, count); + if (rc < 0) { + sf_sample_delete(sflist, sf, smp); + kfree(zone); + return rc; + } + /* memory offset is updated after */ + } + + /* update the memory offset here */ + sflist->mem_used += smp->v.truesize; + + zone->v.sample = sample_id; /* the last sample */ + zone->v.rate_offset = calc_rate_offset(patch.base_freq); + note = freq_to_note(patch.base_note); + zone->v.root = note / 100; + zone->v.tune = -(note % 100); + zone->v.low = (freq_to_note(patch.low_note) + 99) / 100; + zone->v.high = freq_to_note(patch.high_note) / 100; + /* panning position; -128 - 127 => 0-127 */ + zone->v.pan = (patch.panning + 128) / 2; +#if 0 + snd_printk(KERN_DEBUG + "gus: basefrq=%d (ofs=%d) root=%d,tune=%d, range:%d-%d\n", + (int)patch.base_freq, zone->v.rate_offset, + zone->v.root, zone->v.tune, zone->v.low, zone->v.high); +#endif + + /* detuning is ignored */ + /* 6points volume envelope */ + if (patch.mode & WAVE_ENVELOPES) { + int attack, hold, decay, release; + attack = calc_gus_envelope_time + (patch.env_rate[0], 0, patch.env_offset[0]); + hold = calc_gus_envelope_time + (patch.env_rate[1], patch.env_offset[0], + patch.env_offset[1]); + decay = calc_gus_envelope_time + (patch.env_rate[2], patch.env_offset[1], + patch.env_offset[2]); + release = calc_gus_envelope_time + (patch.env_rate[3], patch.env_offset[1], + patch.env_offset[4]); + release += calc_gus_envelope_time + (patch.env_rate[4], patch.env_offset[3], + patch.env_offset[4]); + release += calc_gus_envelope_time + (patch.env_rate[5], patch.env_offset[4], + patch.env_offset[5]); + zone->v.parm.volatkhld = + (snd_sf_calc_parm_hold(hold) << 8) | + snd_sf_calc_parm_attack(attack); + zone->v.parm.voldcysus = (calc_gus_sustain(patch.env_offset[2]) << 8) | + snd_sf_calc_parm_decay(decay); + zone->v.parm.volrelease = 0x8000 | snd_sf_calc_parm_decay(release); + zone->v.attenuation = calc_gus_attenuation(patch.env_offset[0]); +#if 0 + snd_printk(KERN_DEBUG + "gus: atkhld=%x, dcysus=%x, volrel=%x, att=%d\n", + zone->v.parm.volatkhld, + zone->v.parm.voldcysus, + zone->v.parm.volrelease, + zone->v.attenuation); +#endif + } + + /* fast release */ + if (patch.mode & WAVE_FAST_RELEASE) { + zone->v.parm.volrelease = 0x807f; + } + + /* tremolo effect */ + if (patch.mode & WAVE_TREMOLO) { + int rate = (patch.tremolo_rate * 1000 / 38) / 42; + zone->v.parm.tremfrq = ((patch.tremolo_depth / 2) << 8) | rate; + } + /* vibrato effect */ + if (patch.mode & WAVE_VIBRATO) { + int rate = (patch.vibrato_rate * 1000 / 38) / 42; + zone->v.parm.fm2frq2 = ((patch.vibrato_depth / 6) << 8) | rate; + } + + /* scale_freq, scale_factor, volume, and fractions not implemented */ + + if (!(smp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT)) + zone->v.mode = SNDRV_SFNT_MODE_LOOPING; + else + zone->v.mode = 0; + + /* append to the tail of the list */ + /*zone->bank = ctrls[AWE_MD_GUS_BANK];*/ + zone->bank = 0; + zone->instr = patch.instr_no; + zone->mapped = 0; + zone->v.sf_id = sf->id; + + zone->sample = set_sample(sf, &zone->v); + + /* rebuild preset now */ + add_preset(sflist, zone); + + return 0; +} + +/* load GUS patch */ +int +snd_soundfont_load_guspatch(struct snd_sf_list *sflist, const char __user *data, + long count, int client) +{ + int rc; + lock_preset(sflist); + rc = load_guspatch(sflist, data, count, client); + unlock_preset(sflist); + return rc; +} + + +/* + * Rebuild the preset table. This is like a hash table in that it allows + * quick access to the zone information. For each preset there are zone + * structures linked by next_instr and by next_zone. Former is the whole + * link for this preset, and latter is the link for zone (i.e. instrument/ + * bank/key combination). + */ +static void +rebuild_presets(struct snd_sf_list *sflist) +{ + struct snd_soundfont *sf; + struct snd_sf_zone *cur; + + /* clear preset table */ + memset(sflist->presets, 0, sizeof(sflist->presets)); + + /* search all fonts and insert each font */ + for (sf = sflist->fonts; sf; sf = sf->next) { + for (cur = sf->zones; cur; cur = cur->next) { + if (! cur->mapped && cur->sample == NULL) { + /* try again to search the corresponding sample */ + cur->sample = set_sample(sf, &cur->v); + if (cur->sample == NULL) + continue; + } + + add_preset(sflist, cur); + } + } +} + + +/* + * add the given zone to preset table + */ +static void +add_preset(struct snd_sf_list *sflist, struct snd_sf_zone *cur) +{ + struct snd_sf_zone *zone; + int index; + + zone = search_first_zone(sflist, cur->bank, cur->instr, cur->v.low); + if (zone && zone->v.sf_id != cur->v.sf_id) { + /* different instrument was already defined */ + struct snd_sf_zone *p; + /* compare the allocated time */ + for (p = zone; p; p = p->next_zone) { + if (p->counter > cur->counter) + /* the current is older.. skipped */ + return; + } + /* remove old zones */ + delete_preset(sflist, zone); + zone = NULL; /* do not forget to clear this! */ + } + + /* prepend this zone */ + if ((index = get_index(cur->bank, cur->instr, cur->v.low)) < 0) + return; + cur->next_zone = zone; /* zone link */ + cur->next_instr = sflist->presets[index]; /* preset table link */ + sflist->presets[index] = cur; +} + +/* + * delete the given zones from preset_table + */ +static void +delete_preset(struct snd_sf_list *sflist, struct snd_sf_zone *zp) +{ + int index; + struct snd_sf_zone *p; + + if ((index = get_index(zp->bank, zp->instr, zp->v.low)) < 0) + return; + for (p = sflist->presets[index]; p; p = p->next_instr) { + while (p->next_instr == zp) { + p->next_instr = zp->next_instr; + zp = zp->next_zone; + if (zp == NULL) + return; + } + } +} + + +/* + * Search matching zones from preset table. + * The note can be rewritten by preset mapping (alias). + * The found zones are stored on 'table' array. max_layers defines + * the maximum number of elements in this array. + * This function returns the number of found zones. 0 if not found. + */ +int +snd_soundfont_search_zone(struct snd_sf_list *sflist, int *notep, int vel, + int preset, int bank, + int def_preset, int def_bank, + struct snd_sf_zone **table, int max_layers) +{ + int nvoices; + unsigned long flags; + + /* this function is supposed to be called atomically, + * so we check the lock. if it's busy, just returns 0 to + * tell the caller the busy state + */ + spin_lock_irqsave(&sflist->lock, flags); + if (sflist->presets_locked) { + spin_unlock_irqrestore(&sflist->lock, flags); + return 0; + } + nvoices = search_zones(sflist, notep, vel, preset, bank, + table, max_layers, 0); + if (! nvoices) { + if (preset != def_preset || bank != def_bank) + nvoices = search_zones(sflist, notep, vel, + def_preset, def_bank, + table, max_layers, 0); + } + spin_unlock_irqrestore(&sflist->lock, flags); + return nvoices; +} + + +/* + * search the first matching zone + */ +static struct snd_sf_zone * +search_first_zone(struct snd_sf_list *sflist, int bank, int preset, int key) +{ + int index; + struct snd_sf_zone *zp; + + if ((index = get_index(bank, preset, key)) < 0) + return NULL; + for (zp = sflist->presets[index]; zp; zp = zp->next_instr) { + if (zp->instr == preset && zp->bank == bank) + return zp; + } + return NULL; +} + + +/* + * search matching zones from sflist. can be called recursively. + */ +static int +search_zones(struct snd_sf_list *sflist, int *notep, int vel, + int preset, int bank, struct snd_sf_zone **table, + int max_layers, int level) +{ + struct snd_sf_zone *zp; + int nvoices; + + zp = search_first_zone(sflist, bank, preset, *notep); + nvoices = 0; + for (; zp; zp = zp->next_zone) { + if (*notep >= zp->v.low && *notep <= zp->v.high && + vel >= zp->v.vellow && vel <= zp->v.velhigh) { + if (zp->mapped) { + /* search preset mapping (aliasing) */ + int key = zp->v.fixkey; + preset = zp->v.start; + bank = zp->v.end; + + if (level > 5) /* too deep alias level */ + return 0; + if (key < 0) + key = *notep; + nvoices = search_zones(sflist, &key, vel, + preset, bank, table, + max_layers, level + 1); + if (nvoices > 0) + *notep = key; + break; + } + table[nvoices++] = zp; + if (nvoices >= max_layers) + break; + } + } + + return nvoices; +} + + +/* calculate the index of preset table: + * drums are mapped from 128 to 255 according to its note key. + * other instruments are mapped from 0 to 127. + * if the index is out of range, return -1. + */ +static int +get_index(int bank, int instr, int key) +{ + int index; + if (SF_IS_DRUM_BANK(bank)) + index = key + SF_MAX_INSTRUMENTS; + else + index = instr; + index = index % SF_MAX_PRESETS; + if (index < 0) + return -1; + return index; +} + +/* + * Initialise the sflist structure. + */ +static void +snd_sf_init(struct snd_sf_list *sflist) +{ + memset(sflist->presets, 0, sizeof(sflist->presets)); + + sflist->mem_used = 0; + sflist->currsf = NULL; + sflist->open_client = -1; + sflist->fonts = NULL; + sflist->fonts_size = 0; + sflist->zone_counter = 0; + sflist->sample_counter = 0; + sflist->zone_locked = 0; + sflist->sample_locked = 0; +} + +/* + * Release all list records + */ +static void +snd_sf_clear(struct snd_sf_list *sflist) +{ + struct snd_soundfont *sf, *nextsf; + struct snd_sf_zone *zp, *nextzp; + struct snd_sf_sample *sp, *nextsp; + + for (sf = sflist->fonts; sf; sf = nextsf) { + nextsf = sf->next; + for (zp = sf->zones; zp; zp = nextzp) { + nextzp = zp->next; + kfree(zp); + } + for (sp = sf->samples; sp; sp = nextsp) { + nextsp = sp->next; + if (sflist->callback.sample_free) + sflist->callback.sample_free(sflist->callback.private_data, + sp, sflist->memhdr); + kfree(sp); + } + kfree(sf); + } + + snd_sf_init(sflist); +} + + +/* + * Create a new sflist structure + */ +struct snd_sf_list * +snd_sf_new(struct snd_sf_callback *callback, struct snd_util_memhdr *hdr) +{ + struct snd_sf_list *sflist; + + if ((sflist = kzalloc(sizeof(*sflist), GFP_KERNEL)) == NULL) + return NULL; + + mutex_init(&sflist->presets_mutex); + spin_lock_init(&sflist->lock); + sflist->memhdr = hdr; + + if (callback) + sflist->callback = *callback; + + snd_sf_init(sflist); + return sflist; +} + + +/* + * Free everything allocated off the sflist structure. + */ +void +snd_sf_free(struct snd_sf_list *sflist) +{ + if (sflist == NULL) + return; + + lock_preset(sflist); + if (sflist->callback.sample_reset) + sflist->callback.sample_reset(sflist->callback.private_data); + snd_sf_clear(sflist); + unlock_preset(sflist); + + kfree(sflist); +} + +/* + * Remove all samples + * The soundcard should be silet before calling this function. + */ +int +snd_soundfont_remove_samples(struct snd_sf_list *sflist) +{ + lock_preset(sflist); + if (sflist->callback.sample_reset) + sflist->callback.sample_reset(sflist->callback.private_data); + snd_sf_clear(sflist); + unlock_preset(sflist); + + return 0; +} + +/* + * Remove unlocked samples. + * The soundcard should be silent before calling this function. + */ +int +snd_soundfont_remove_unlocked(struct snd_sf_list *sflist) +{ + struct snd_soundfont *sf; + struct snd_sf_zone *zp, *nextzp; + struct snd_sf_sample *sp, *nextsp; + + lock_preset(sflist); + + if (sflist->callback.sample_reset) + sflist->callback.sample_reset(sflist->callback.private_data); + + /* to be sure */ + memset(sflist->presets, 0, sizeof(sflist->presets)); + + for (sf = sflist->fonts; sf; sf = sf->next) { + for (zp = sf->zones; zp; zp = nextzp) { + if (zp->counter < sflist->zone_locked) + break; + nextzp = zp->next; + sf->zones = nextzp; + kfree(zp); + } + + for (sp = sf->samples; sp; sp = nextsp) { + if (sp->counter < sflist->sample_locked) + break; + nextsp = sp->next; + sf->samples = nextsp; + sflist->mem_used -= sp->v.truesize; + if (sflist->callback.sample_free) + sflist->callback.sample_free(sflist->callback.private_data, + sp, sflist->memhdr); + kfree(sp); + } + } + + sflist->zone_counter = sflist->zone_locked; + sflist->sample_counter = sflist->sample_locked; + + rebuild_presets(sflist); + + unlock_preset(sflist); + return 0; +} |