mirror of
https://github.com/torvalds/linux.git
synced 2024-12-23 03:11:46 +00:00
d81a6d7176
These aren't modules, but they do make use of these macros, so they will need export.h to get that definition. Previously, they got it via the implicit module.h inclusion. Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>
1497 lines
38 KiB
C
1497 lines
38 KiB
C
/*
|
|
* 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>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
/*
|
|
* 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 <asm/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 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;
|
|
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 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 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, 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);
|
|
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;
|
|
}
|