summaryrefslogtreecommitdiffstats
path: root/sound/synth
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /sound/synth
parentInitial commit. (diff)
downloadlinux-upstream.tar.xz
linux-upstream.zip
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/synth')
-rw-r--r--sound/synth/Kconfig3
-rw-r--r--sound/synth/Makefile13
-rw-r--r--sound/synth/emux/Makefile14
-rw-r--r--sound/synth/emux/emux.c147
-rw-r--r--sound/synth/emux/emux_effect.c296
-rw-r--r--sound/synth/emux/emux_hwdep.c147
-rw-r--r--sound/synth/emux/emux_nrpn.c386
-rw-r--r--sound/synth/emux/emux_oss.c490
-rw-r--r--sound/synth/emux/emux_proc.c111
-rw-r--r--sound/synth/emux/emux_seq.c403
-rw-r--r--sound/synth/emux/emux_synth.c968
-rw-r--r--sound/synth/emux/emux_voice.h87
-rw-r--r--sound/synth/emux/soundfont.c1486
-rw-r--r--sound/synth/util_mem.c182
14 files changed, 4733 insertions, 0 deletions
diff --git a/sound/synth/Kconfig b/sound/synth/Kconfig
new file mode 100644
index 000000000..70235269b
--- /dev/null
+++ b/sound/synth/Kconfig
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_SYNTH_EMUX
+ tristate
diff --git a/sound/synth/Makefile b/sound/synth/Makefile
new file mode 100644
index 000000000..b9f71d5db
--- /dev/null
+++ b/sound/synth/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+snd-util-mem-objs := util_mem.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_EMU10K1) += snd-util-mem.o
+obj-$(CONFIG_SND_TRIDENT) += snd-util-mem.o
+obj-$(CONFIG_SND_SBAWE_SEQ) += snd-util-mem.o
+obj-$(CONFIG_SND_SEQUENCER) += emux/
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, &note, vel, chan, table);
+ if (! nvoices)
+ return;
+
+ /* exclusive note off */
+ for (i = 0; i < nvoices; i++) {
+ struct snd_sf_zone *zp = table[i];
+ if (zp && zp->v.exclusiveClass)
+ exclusive_note_off(emu, port, zp->v.exclusiveClass);
+ }
+
+#if 0 // seems not necessary
+ /* Turn off the same note on the same channel. */
+ terminate_note1(emu, key, chan, 0);
+#endif
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (i = 0; i < nvoices; i++) {
+
+ /* set up each voice parameter */
+ /* at this stage, we don't trigger the voice yet. */
+
+ if (table[i] == NULL)
+ continue;
+
+ vp = emu->ops.get_voice(emu, port);
+ if (vp == NULL || vp->ch < 0)
+ continue;
+ if (STATE_IS_PLAYING(vp->state))
+ emu->ops.terminate(vp);
+
+ vp->time = emu->use_time++;
+ vp->chan = chan;
+ vp->port = port;
+ vp->key = key;
+ vp->note = note;
+ vp->velocity = vel;
+ vp->zone = table[i];
+ if (vp->zone->sample)
+ vp->block = vp->zone->sample->block;
+ else
+ vp->block = NULL;
+
+ setup_voice(vp);
+
+ vp->state = SNDRV_EMUX_ST_STANDBY;
+ if (emu->ops.prepare) {
+ vp->state = SNDRV_EMUX_ST_OFF;
+ if (emu->ops.prepare(vp) >= 0)
+ vp->state = SNDRV_EMUX_ST_STANDBY;
+ }
+ }
+
+ /* start envelope now */
+ for (i = 0; i < emu->max_voices; i++) {
+ vp = &emu->voices[i];
+ if (vp->state == SNDRV_EMUX_ST_STANDBY &&
+ vp->chan == chan) {
+ emu->ops.trigger(vp);
+ vp->state = SNDRV_EMUX_ST_ON;
+ vp->ontime = jiffies; /* remember the trigger timing */
+ }
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+ if (port->port_mode == SNDRV_EMUX_PORT_MODE_OSS_SYNTH) {
+ /* clear voice position for the next note on this channel */
+ struct snd_emux_effect_table *fx = chan->private;
+ if (fx) {
+ fx->flag[EMUX_FX_SAMPLE_START] = 0;
+ fx->flag[EMUX_FX_COARSE_SAMPLE_START] = 0;
+ }
+ }
+#endif
+}
+
+/*
+ * Release a note in response to a midi note off.
+ */
+void
+snd_emux_note_off(void *p, int note, int vel, struct snd_midi_channel *chan)
+{
+ int ch;
+ struct snd_emux *emu;
+ struct snd_emux_voice *vp;
+ unsigned long flags;
+ struct snd_emux_port *port;
+
+ port = p;
+ if (snd_BUG_ON(!port || !chan))
+ return;
+
+ emu = port->emu;
+ if (snd_BUG_ON(!emu || !emu->ops.release))
+ return;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (ch = 0; ch < emu->max_voices; ch++) {
+ vp = &emu->voices[ch];
+ if (STATE_IS_PLAYING(vp->state) &&
+ vp->chan == chan && vp->key == note) {
+ vp->state = SNDRV_EMUX_ST_RELEASED;
+ if (vp->ontime == jiffies) {
+ /* if note-off is sent too shortly after
+ * note-on, emuX engine cannot produce the sound
+ * correctly. so we'll release this note
+ * a bit later via timer callback.
+ */
+ vp->state = SNDRV_EMUX_ST_PENDING;
+ if (! emu->timer_active) {
+ mod_timer(&emu->tlist, jiffies + 1);
+ emu->timer_active = 1;
+ }
+ } else
+ /* ok now release the note */
+ emu->ops.release(vp);
+ }
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ * timer callback
+ *
+ * release the pending note-offs
+ */
+void snd_emux_timer_callback(struct timer_list *t)
+{
+ struct snd_emux *emu = from_timer(emu, t, tlist);
+ struct snd_emux_voice *vp;
+ unsigned long flags;
+ int ch, do_again = 0;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (ch = 0; ch < emu->max_voices; ch++) {
+ vp = &emu->voices[ch];
+ if (vp->state == SNDRV_EMUX_ST_PENDING) {
+ if (vp->ontime == jiffies)
+ do_again++; /* release this at the next interrupt */
+ else {
+ emu->ops.release(vp);
+ vp->state = SNDRV_EMUX_ST_RELEASED;
+ }
+ }
+ }
+ if (do_again) {
+ mod_timer(&emu->tlist, jiffies + 1);
+ emu->timer_active = 1;
+ } else
+ emu->timer_active = 0;
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ * key pressure change
+ */
+void
+snd_emux_key_press(void *p, int note, int vel, struct snd_midi_channel *chan)
+{
+ int ch;
+ struct snd_emux *emu;
+ struct snd_emux_voice *vp;
+ unsigned long flags;
+ struct snd_emux_port *port;
+
+ port = p;
+ if (snd_BUG_ON(!port || !chan))
+ return;
+
+ emu = port->emu;
+ if (snd_BUG_ON(!emu || !emu->ops.update))
+ return;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (ch = 0; ch < emu->max_voices; ch++) {
+ vp = &emu->voices[ch];
+ if (vp->state == SNDRV_EMUX_ST_ON &&
+ vp->chan == chan && vp->key == note) {
+ vp->velocity = vel;
+ update_voice(emu, vp, SNDRV_EMUX_UPDATE_VOLUME);
+ }
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+
+/*
+ * Modulate the voices which belong to the channel
+ */
+void
+snd_emux_update_channel(struct snd_emux_port *port, struct snd_midi_channel *chan, int update)
+{
+ struct snd_emux *emu;
+ struct snd_emux_voice *vp;
+ int i;
+ unsigned long flags;
+
+ if (! update)
+ return;
+
+ emu = port->emu;
+ if (snd_BUG_ON(!emu || !emu->ops.update))
+ return;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (i = 0; i < emu->max_voices; i++) {
+ vp = &emu->voices[i];
+ if (vp->chan == chan)
+ update_voice(emu, vp, update);
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ * Modulate all the voices which belong to the port.
+ */
+void
+snd_emux_update_port(struct snd_emux_port *port, int update)
+{
+ struct snd_emux *emu;
+ struct snd_emux_voice *vp;
+ int i;
+ unsigned long flags;
+
+ if (! update)
+ return;
+
+ emu = port->emu;
+ if (snd_BUG_ON(!emu || !emu->ops.update))
+ return;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (i = 0; i < emu->max_voices; i++) {
+ vp = &emu->voices[i];
+ if (vp->port == port)
+ update_voice(emu, vp, update);
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+
+/*
+ * Deal with a controller type event. This includes all types of
+ * control events, not just the midi controllers
+ */
+void
+snd_emux_control(void *p, int type, struct snd_midi_channel *chan)
+{
+ struct snd_emux_port *port;
+
+ port = p;
+ if (snd_BUG_ON(!port || !chan))
+ return;
+
+ switch (type) {
+ case MIDI_CTL_MSB_MAIN_VOLUME:
+ case MIDI_CTL_MSB_EXPRESSION:
+ snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_VOLUME);
+ break;
+
+ case MIDI_CTL_MSB_PAN:
+ snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN);
+ break;
+
+ case MIDI_CTL_SOFT_PEDAL:
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+ /* FIXME: this is an emulation */
+ if (chan->control[type] >= 64)
+ snd_emux_send_effect(port, chan, EMUX_FX_CUTOFF, -160,
+ EMUX_FX_FLAG_ADD);
+ else
+ snd_emux_send_effect(port, chan, EMUX_FX_CUTOFF, 0,
+ EMUX_FX_FLAG_OFF);
+#endif
+ break;
+
+ case MIDI_CTL_PITCHBEND:
+ snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PITCH);
+ break;
+
+ case MIDI_CTL_MSB_MODWHEEL:
+ case MIDI_CTL_CHAN_PRESSURE:
+ snd_emux_update_channel(port, chan,
+ SNDRV_EMUX_UPDATE_FMMOD |
+ SNDRV_EMUX_UPDATE_FM2FRQ2);
+ break;
+
+ }
+
+ if (port->chset.midi_mode == SNDRV_MIDI_MODE_XG) {
+ snd_emux_xg_control(port, chan, type);
+ }
+}
+
+
+/*
+ * terminate note - if free flag is true, free the terminated voice
+ */
+static void
+terminate_note1(struct snd_emux *emu, int note, struct snd_midi_channel *chan, int free)
+{
+ int i;
+ struct snd_emux_voice *vp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (i = 0; i < emu->max_voices; i++) {
+ vp = &emu->voices[i];
+ if (STATE_IS_PLAYING(vp->state) && vp->chan == chan &&
+ vp->key == note)
+ terminate_voice(emu, vp, free);
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+
+/*
+ * terminate note - exported for midi emulation
+ */
+void
+snd_emux_terminate_note(void *p, int note, struct snd_midi_channel *chan)
+{
+ struct snd_emux *emu;
+ struct snd_emux_port *port;
+
+ port = p;
+ if (snd_BUG_ON(!port || !chan))
+ return;
+
+ emu = port->emu;
+ if (snd_BUG_ON(!emu || !emu->ops.terminate))
+ return;
+
+ terminate_note1(emu, note, chan, 1);
+}
+
+
+/*
+ * Terminate all the notes
+ */
+void
+snd_emux_terminate_all(struct snd_emux *emu)
+{
+ int i;
+ struct snd_emux_voice *vp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (i = 0; i < emu->max_voices; i++) {
+ vp = &emu->voices[i];
+ if (STATE_IS_PLAYING(vp->state))
+ terminate_voice(emu, vp, 0);
+ if (vp->state == SNDRV_EMUX_ST_OFF) {
+ if (emu->ops.free_voice)
+ emu->ops.free_voice(vp);
+ if (emu->ops.reset)
+ emu->ops.reset(emu, i);
+ }
+ vp->time = 0;
+ }
+ /* initialize allocation time */
+ emu->use_time = 0;
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+EXPORT_SYMBOL(snd_emux_terminate_all);
+
+/*
+ * Terminate all voices associated with the given port
+ */
+void
+snd_emux_sounds_off_all(struct snd_emux_port *port)
+{
+ int i;
+ struct snd_emux *emu;
+ struct snd_emux_voice *vp;
+ unsigned long flags;
+
+ if (snd_BUG_ON(!port))
+ return;
+ emu = port->emu;
+ if (snd_BUG_ON(!emu || !emu->ops.terminate))
+ return;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (i = 0; i < emu->max_voices; i++) {
+ vp = &emu->voices[i];
+ if (STATE_IS_PLAYING(vp->state) &&
+ vp->port == port)
+ terminate_voice(emu, vp, 0);
+ if (vp->state == SNDRV_EMUX_ST_OFF) {
+ if (emu->ops.free_voice)
+ emu->ops.free_voice(vp);
+ if (emu->ops.reset)
+ emu->ops.reset(emu, i);
+ }
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+
+/*
+ * Terminate all voices that have the same exclusive class. This
+ * is mainly for drums.
+ */
+static void
+exclusive_note_off(struct snd_emux *emu, struct snd_emux_port *port, int exclass)
+{
+ struct snd_emux_voice *vp;
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&emu->voice_lock, flags);
+ for (i = 0; i < emu->max_voices; i++) {
+ vp = &emu->voices[i];
+ if (STATE_IS_PLAYING(vp->state) && vp->port == port &&
+ vp->reg.exclusiveClass == exclass) {
+ terminate_voice(emu, vp, 0);
+ }
+ }
+ spin_unlock_irqrestore(&emu->voice_lock, flags);
+}
+
+/*
+ * terminate a voice
+ * if free flag is true, call free_voice after termination
+ */
+static void
+terminate_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int free)
+{
+ emu->ops.terminate(vp);
+ vp->time = emu->use_time++;
+ vp->chan = NULL;
+ vp->port = NULL;
+ vp->zone = NULL;
+ vp->block = NULL;
+ vp->state = SNDRV_EMUX_ST_OFF;
+ if (free && emu->ops.free_voice)
+ emu->ops.free_voice(vp);
+}
+
+
+/*
+ * Modulate the voice
+ */
+static void
+update_voice(struct snd_emux *emu, struct snd_emux_voice *vp, int update)
+{
+ if (!STATE_IS_PLAYING(vp->state))
+ return;
+
+ if (vp->chan == NULL || vp->port == NULL)
+ return;
+ if (update & SNDRV_EMUX_UPDATE_VOLUME)
+ calc_volume(vp);
+ if (update & SNDRV_EMUX_UPDATE_PITCH)
+ calc_pitch(vp);
+ if (update & SNDRV_EMUX_UPDATE_PAN) {
+ if (! calc_pan(vp) && (update == SNDRV_EMUX_UPDATE_PAN))
+ return;
+ }
+ emu->ops.update(vp, update);
+}
+
+
+#if 0 // not used
+/* table for volume target calculation */
+static const unsigned short voltarget[16] = {
+ 0xEAC0, 0xE0C8, 0xD740, 0xCE20, 0xC560, 0xBD08, 0xB500, 0xAD58,
+ 0xA5F8, 0x9EF0, 0x9830, 0x91C0, 0x8B90, 0x85A8, 0x8000, 0x7A90
+};
+#endif
+
+#define LO_BYTE(v) ((v) & 0xff)
+#define HI_BYTE(v) (((v) >> 8) & 0xff)
+
+/*
+ * Sets up the voice structure by calculating some values that
+ * will be needed later.
+ */
+static void
+setup_voice(struct snd_emux_voice *vp)
+{
+ struct soundfont_voice_parm *parm;
+ int pitch;
+
+ /* copy the original register values */
+ vp->reg = vp->zone->v;
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+ snd_emux_setup_effect(vp);
+#endif
+
+ /* reset status */
+ vp->apan = -1;
+ vp->avol = -1;
+ vp->apitch = -1;
+
+ calc_volume(vp);
+ calc_pitch(vp);
+ calc_pan(vp);
+
+ parm = &vp->reg.parm;
+
+ /* compute filter target and correct modulation parameters */
+ if (LO_BYTE(parm->modatkhld) >= 0x80 && parm->moddelay >= 0x8000) {
+ parm->moddelay = 0xbfff;
+ pitch = (HI_BYTE(parm->pefe) << 4) + vp->apitch;
+ if (pitch > 0xffff)
+ pitch = 0xffff;
+ /* calculate filter target */
+ vp->ftarget = parm->cutoff + LO_BYTE(parm->pefe);
+ LIMITVALUE(vp->ftarget, 0, 255);
+ vp->ftarget <<= 8;
+ } else {
+ vp->ftarget = parm->cutoff;
+ vp->ftarget <<= 8;
+ pitch = vp->apitch;
+ }
+
+ /* compute pitch target */
+ if (pitch != 0xffff) {
+ vp->ptarget = 1 << (pitch >> 12);
+ if (pitch & 0x800) vp->ptarget += (vp->ptarget*0x102e)/0x2710;
+ if (pitch & 0x400) vp->ptarget += (vp->ptarget*0x764)/0x2710;
+ if (pitch & 0x200) vp->ptarget += (vp->ptarget*0x389)/0x2710;
+ vp->ptarget += (vp->ptarget >> 1);
+ if (vp->ptarget > 0xffff) vp->ptarget = 0xffff;
+ } else
+ vp->ptarget = 0xffff;
+
+ if (LO_BYTE(parm->modatkhld) >= 0x80) {
+ parm->modatkhld &= ~0xff;
+ parm->modatkhld |= 0x7f;
+ }
+
+ /* compute volume target and correct volume parameters */
+ vp->vtarget = 0;
+#if 0 /* FIXME: this leads to some clicks.. */
+ if (LO_BYTE(parm->volatkhld) >= 0x80 && parm->voldelay >= 0x8000) {
+ parm->voldelay = 0xbfff;
+ vp->vtarget = voltarget[vp->avol % 0x10] >> (vp->avol >> 4);
+ }
+#endif
+
+ if (LO_BYTE(parm->volatkhld) >= 0x80) {
+ parm->volatkhld &= ~0xff;
+ parm->volatkhld |= 0x7f;
+ }
+}
+
+/*
+ * calculate pitch parameter
+ */
+static const unsigned char pan_volumes[256] = {
+0x00,0x03,0x06,0x09,0x0c,0x0f,0x12,0x14,0x17,0x1a,0x1d,0x20,0x22,0x25,0x28,0x2a,
+0x2d,0x30,0x32,0x35,0x37,0x3a,0x3c,0x3f,0x41,0x44,0x46,0x49,0x4b,0x4d,0x50,0x52,
+0x54,0x57,0x59,0x5b,0x5d,0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6f,0x71,0x73,0x75,
+0x77,0x79,0x7b,0x7c,0x7e,0x80,0x82,0x84,0x86,0x88,0x89,0x8b,0x8d,0x8f,0x90,0x92,
+0x94,0x96,0x97,0x99,0x9a,0x9c,0x9e,0x9f,0xa1,0xa2,0xa4,0xa5,0xa7,0xa8,0xaa,0xab,
+0xad,0xae,0xaf,0xb1,0xb2,0xb3,0xb5,0xb6,0xb7,0xb9,0xba,0xbb,0xbc,0xbe,0xbf,0xc0,
+0xc1,0xc2,0xc3,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,0xd0,0xd1,
+0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdc,0xdd,0xde,0xdf,
+0xdf,0xe0,0xe1,0xe2,0xe2,0xe3,0xe4,0xe4,0xe5,0xe6,0xe6,0xe7,0xe8,0xe8,0xe9,0xe9,
+0xea,0xeb,0xeb,0xec,0xec,0xed,0xed,0xee,0xee,0xef,0xef,0xf0,0xf0,0xf1,0xf1,0xf1,
+0xf2,0xf2,0xf3,0xf3,0xf3,0xf4,0xf4,0xf5,0xf5,0xf5,0xf6,0xf6,0xf6,0xf7,0xf7,0xf7,
+0xf7,0xf8,0xf8,0xf8,0xf9,0xf9,0xf9,0xf9,0xf9,0xfa,0xfa,0xfa,0xfa,0xfb,0xfb,0xfb,
+0xfb,0xfb,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,0xfd,
+0xfd,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+};
+
+static int
+calc_pan(struct snd_emux_voice *vp)
+{
+ struct snd_midi_channel *chan = vp->chan;
+ int pan;
+
+ /* pan & loop start (pan 8bit, MSB, 0:right, 0xff:left) */
+ if (vp->reg.fixpan > 0) /* 0-127 */
+ pan = 255 - (int)vp->reg.fixpan * 2;
+ else {
+ pan = chan->control[MIDI_CTL_MSB_PAN] - 64;
+ if (vp->reg.pan >= 0) /* 0-127 */
+ pan += vp->reg.pan - 64;
+ pan = 127 - (int)pan * 2;
+ }
+ LIMITVALUE(pan, 0, 255);
+
+ if (vp->emu->linear_panning) {
+ /* assuming linear volume */
+ if (pan != vp->apan) {
+ vp->apan = pan;
+ if (pan == 0)
+ vp->aaux = 0xff;
+ else
+ vp->aaux = (-pan) & 0xff;
+ return 1;
+ } else
+ return 0;
+ } else {
+ /* using volume table */
+ if (vp->apan != (int)pan_volumes[pan]) {
+ vp->apan = pan_volumes[pan];
+ vp->aaux = pan_volumes[255 - pan];
+ return 1;
+ }
+ return 0;
+ }
+}
+
+
+/*
+ * calculate volume attenuation
+ *
+ * Voice volume is controlled by volume attenuation parameter.
+ * So volume becomes maximum when avol is 0 (no attenuation), and
+ * minimum when 255 (-96dB or silence).
+ */
+
+/* tables for volume->attenuation calculation */
+static const unsigned char voltab1[128] = {
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x2b, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22,
+ 0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a,
+ 0x19, 0x19, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14,
+ 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10,
+ 0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0e, 0x0d,
+ 0x0d, 0x0d, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0a, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const unsigned char voltab2[128] = {
+ 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x2a,
+ 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x24, 0x23, 0x22, 0x21,
+ 0x21, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1c, 0x1c, 0x1b, 0x1a,
+ 0x1a, 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15,
+ 0x14, 0x14, 0x13, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x10,
+ 0x10, 0x10, 0x0f, 0x0f, 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d,
+ 0x0d, 0x0c, 0x0c, 0x0c, 0x0b, 0x0b, 0x0b, 0x0b, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const unsigned char expressiontab[128] = {
+ 0x7f, 0x6c, 0x62, 0x5a, 0x54, 0x50, 0x4b, 0x48, 0x45, 0x42,
+ 0x40, 0x3d, 0x3b, 0x39, 0x38, 0x36, 0x34, 0x33, 0x31, 0x30,
+ 0x2f, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25,
+ 0x24, 0x24, 0x23, 0x22, 0x21, 0x21, 0x20, 0x1f, 0x1e, 0x1e,
+ 0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, 0x1a, 0x19, 0x18, 0x18,
+ 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x15, 0x14, 0x14, 0x13,
+ 0x13, 0x12, 0x12, 0x11, 0x11, 0x11, 0x10, 0x10, 0x0f, 0x0f,
+ 0x0f, 0x0e, 0x0e, 0x0e, 0x0d, 0x0d, 0x0d, 0x0c, 0x0c, 0x0c,
+ 0x0b, 0x0b, 0x0b, 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09,
+ 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06,
+ 0x06, 0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03,
+ 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/*
+ * Magic to calculate the volume (actually attenuation) from all the
+ * voice and channels parameters.
+ */
+static int
+calc_volume(struct snd_emux_voice *vp)
+{
+ int vol;
+ int main_vol, expression_vol, master_vol;
+ struct snd_midi_channel *chan = vp->chan;
+ struct snd_emux_port *port = vp->port;
+
+ expression_vol = chan->control[MIDI_CTL_MSB_EXPRESSION];
+ LIMITMAX(vp->velocity, 127);
+ LIMITVALUE(expression_vol, 0, 127);
+ if (port->port_mode == SNDRV_EMUX_PORT_MODE_OSS_SYNTH) {
+ /* 0 - 127 */
+ main_vol = chan->control[MIDI_CTL_MSB_MAIN_VOLUME];
+ vol = (vp->velocity * main_vol * expression_vol) / (127*127);
+ vol = vol * vp->reg.amplitude / 127;
+
+ LIMITVALUE(vol, 0, 127);
+
+ /* calc to attenuation */
+ vol = snd_sf_vol_table[vol];
+
+ } else {
+ main_vol = chan->control[MIDI_CTL_MSB_MAIN_VOLUME] * vp->reg.amplitude / 127;
+ LIMITVALUE(main_vol, 0, 127);
+
+ vol = voltab1[main_vol] + voltab2[vp->velocity];
+ vol = (vol * 8) / 3;
+ vol += vp->reg.attenuation;
+ vol += ((0x100 - vol) * expressiontab[expression_vol])/128;
+ }
+
+ master_vol = port->chset.gs_master_volume;
+ LIMITVALUE(master_vol, 0, 127);
+ vol += snd_sf_vol_table[master_vol];
+ vol += port->volume_atten;
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+ if (chan->private) {
+ struct snd_emux_effect_table *fx = chan->private;
+ vol += fx->val[EMUX_FX_ATTEN];
+ }
+#endif
+
+ LIMITVALUE(vol, 0, 255);
+ if (vp->avol == vol)
+ return 0; /* value unchanged */
+
+ vp->avol = vol;
+ if (!SF_IS_DRUM_BANK(get_bank(port, chan))
+ && LO_BYTE(vp->reg.parm.volatkhld) < 0x7d) {
+ int atten;
+ if (vp->velocity < 70)
+ atten = 70;
+ else
+ atten = vp->velocity;
+ vp->acutoff = (atten * vp->reg.parm.cutoff + 0xa0) >> 7;
+ } else {
+ vp->acutoff = vp->reg.parm.cutoff;
+ }
+
+ return 1; /* value changed */
+}
+
+/*
+ * calculate pitch offset
+ *
+ * 0xE000 is no pitch offset at 44100Hz sample.
+ * Every 4096 is one octave.
+ */
+
+static int
+calc_pitch(struct snd_emux_voice *vp)
+{
+ struct snd_midi_channel *chan = vp->chan;
+ int offset;
+
+ /* calculate offset */
+ if (vp->reg.fixkey >= 0) {
+ offset = (vp->reg.fixkey - vp->reg.root) * 4096 / 12;
+ } else {
+ offset = (vp->note - vp->reg.root) * 4096 / 12;
+ }
+ offset = (offset * vp->reg.scaleTuning) / 100;
+ offset += vp->reg.tune * 4096 / 1200;
+ if (chan->midi_pitchbend != 0) {
+ /* (128 * 8192: 1 semitone) ==> (4096: 12 semitones) */
+ offset += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 3072;
+ }
+
+ /* tuning via RPN:
+ * coarse = -8192 to 8192 (100 cent per 128)
+ * fine = -8192 to 8192 (max=100cent)
+ */
+ /* 4096 = 1200 cents in emu8000 parameter */
+ offset += chan->gm_rpn_coarse_tuning * 4096 / (12 * 128);
+ offset += chan->gm_rpn_fine_tuning / 24;
+
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+ /* add initial pitch correction */
+ if (chan->private) {
+ struct snd_emux_effect_table *fx = chan->private;
+ if (fx->flag[EMUX_FX_INIT_PITCH])
+ offset += fx->val[EMUX_FX_INIT_PITCH];
+ }
+#endif
+
+ /* 0xe000: root pitch */
+ offset += 0xe000 + vp->reg.rate_offset;
+ 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;
+}
diff --git a/sound/synth/util_mem.c b/sound/synth/util_mem.c
new file mode 100644
index 000000000..304a8f174
--- /dev/null
+++ b/sound/synth/util_mem.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2000 Takashi Iwai <tiwai@suse.de>
+ *
+ * Generic memory management routines for soundcard memory allocation
+ */
+
+#include <linux/mutex.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/util_mem.h>
+
+MODULE_AUTHOR("Takashi Iwai");
+MODULE_DESCRIPTION("Generic memory management routines for soundcard memory allocation");
+MODULE_LICENSE("GPL");
+
+#define get_memblk(p) list_entry(p, struct snd_util_memblk, list)
+
+/*
+ * create a new memory manager
+ */
+struct snd_util_memhdr *
+snd_util_memhdr_new(int memsize)
+{
+ struct snd_util_memhdr *hdr;
+
+ hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
+ if (hdr == NULL)
+ return NULL;
+ hdr->size = memsize;
+ mutex_init(&hdr->block_mutex);
+ INIT_LIST_HEAD(&hdr->block);
+
+ return hdr;
+}
+
+/*
+ * free a memory manager
+ */
+void snd_util_memhdr_free(struct snd_util_memhdr *hdr)
+{
+ struct list_head *p;
+
+ if (!hdr)
+ return;
+ /* release all blocks */
+ while ((p = hdr->block.next) != &hdr->block) {
+ list_del(p);
+ kfree(get_memblk(p));
+ }
+ kfree(hdr);
+}
+
+/*
+ * allocate a memory block (without mutex)
+ */
+struct snd_util_memblk *
+__snd_util_mem_alloc(struct snd_util_memhdr *hdr, int size)
+{
+ struct snd_util_memblk *blk;
+ unsigned int units, prev_offset;
+ struct list_head *p;
+
+ if (snd_BUG_ON(!hdr || size <= 0))
+ return NULL;
+
+ /* word alignment */
+ units = size;
+ if (units & 1)
+ units++;
+ if (units > hdr->size)
+ return NULL;
+
+ /* look for empty block */
+ prev_offset = 0;
+ list_for_each(p, &hdr->block) {
+ blk = get_memblk(p);
+ if (blk->offset - prev_offset >= units)
+ goto __found;
+ prev_offset = blk->offset + blk->size;
+ }
+ if (hdr->size - prev_offset < units)
+ return NULL;
+
+__found:
+ return __snd_util_memblk_new(hdr, units, p->prev);
+}
+
+
+/*
+ * create a new memory block with the given size
+ * the block is linked next to prev
+ */
+struct snd_util_memblk *
+__snd_util_memblk_new(struct snd_util_memhdr *hdr, unsigned int units,
+ struct list_head *prev)
+{
+ struct snd_util_memblk *blk;
+
+ blk = kmalloc(sizeof(struct snd_util_memblk) + hdr->block_extra_size,
+ GFP_KERNEL);
+ if (blk == NULL)
+ return NULL;
+
+ if (prev == &hdr->block)
+ blk->offset = 0;
+ else {
+ struct snd_util_memblk *p = get_memblk(prev);
+ blk->offset = p->offset + p->size;
+ }
+ blk->size = units;
+ list_add(&blk->list, prev);
+ hdr->nblocks++;
+ hdr->used += units;
+ return blk;
+}
+
+
+/*
+ * allocate a memory block (with mutex)
+ */
+struct snd_util_memblk *
+snd_util_mem_alloc(struct snd_util_memhdr *hdr, int size)
+{
+ struct snd_util_memblk *blk;
+ mutex_lock(&hdr->block_mutex);
+ blk = __snd_util_mem_alloc(hdr, size);
+ mutex_unlock(&hdr->block_mutex);
+ return blk;
+}
+
+
+/*
+ * remove the block from linked-list and free resource
+ * (without mutex)
+ */
+void
+__snd_util_mem_free(struct snd_util_memhdr *hdr, struct snd_util_memblk *blk)
+{
+ list_del(&blk->list);
+ hdr->nblocks--;
+ hdr->used -= blk->size;
+ kfree(blk);
+}
+
+/*
+ * free a memory block (with mutex)
+ */
+int snd_util_mem_free(struct snd_util_memhdr *hdr, struct snd_util_memblk *blk)
+{
+ if (snd_BUG_ON(!hdr || !blk))
+ return -EINVAL;
+
+ mutex_lock(&hdr->block_mutex);
+ __snd_util_mem_free(hdr, blk);
+ mutex_unlock(&hdr->block_mutex);
+ return 0;
+}
+
+/*
+ * return available memory size
+ */
+int snd_util_mem_avail(struct snd_util_memhdr *hdr)
+{
+ unsigned int size;
+ mutex_lock(&hdr->block_mutex);
+ size = hdr->size - hdr->used;
+ mutex_unlock(&hdr->block_mutex);
+ return size;
+}
+
+
+EXPORT_SYMBOL(snd_util_memhdr_new);
+EXPORT_SYMBOL(snd_util_memhdr_free);
+EXPORT_SYMBOL(snd_util_mem_alloc);
+EXPORT_SYMBOL(snd_util_mem_free);
+EXPORT_SYMBOL(snd_util_mem_avail);
+EXPORT_SYMBOL(__snd_util_mem_alloc);
+EXPORT_SYMBOL(__snd_util_mem_free);
+EXPORT_SYMBOL(__snd_util_memblk_new);