linux-IllusionX/sound/synth/emux/soundfont.c
Takashi Iwai 561b220a4d [ALSA] Replace with kzalloc() - others
Documentation,SA11xx UDA1341 driver,Generic drivers,MPU401 UART,OPL3
OPL4,Digigram VX core,I2C cs8427,I2C lib core,I2C tea6330t,L3 drivers
AK4114 receiver,AK4117 receiver,PDAudioCF driver,PPC PMAC driver
SPARC AMD7930 driver,SPARC cs4231 driver,Synth,Common EMU synth
USB generic driver,USB USX2Y
Replace kcalloc(1,..) with kzalloc().

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2005-09-12 10:48:22 +02:00

1462 lines
36 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 <sound/driver.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/soundfont.h>
#include <sound/seq_oss_legacy.h>
/* Prototypes for static functions */
static int open_patch(snd_sf_list_t *sflist, const char __user *data, int count, int client);
static snd_soundfont_t *newsf(snd_sf_list_t *sflist, int type, char *name);
static int is_identical_font(snd_soundfont_t *sf, int type, unsigned char *name);
static int close_patch(snd_sf_list_t *sflist);
static int probe_data(snd_sf_list_t *sflist, int sample_id);
static void set_zone_counter(snd_sf_list_t *sflist, snd_soundfont_t *sf, snd_sf_zone_t *zp);
static snd_sf_zone_t *sf_zone_new(snd_sf_list_t *sflist, snd_soundfont_t *sf);
static void set_sample_counter(snd_sf_list_t *sflist, snd_soundfont_t *sf, snd_sf_sample_t *sp);
static snd_sf_sample_t *sf_sample_new(snd_sf_list_t *sflist, snd_soundfont_t *sf);
static void sf_sample_delete(snd_sf_list_t *sflist, snd_soundfont_t *sf, snd_sf_sample_t *sp);
static int load_map(snd_sf_list_t *sflist, const void __user *data, int count);
static int load_info(snd_sf_list_t *sflist, const void __user *data, long count);
static int remove_info(snd_sf_list_t *sflist, snd_soundfont_t *sf, int bank, int instr);
static void init_voice_info(soundfont_voice_info_t *avp);
static void init_voice_parm(soundfont_voice_parm_t *pp);
static snd_sf_sample_t *set_sample(snd_soundfont_t *sf, soundfont_voice_info_t *avp);
static snd_sf_sample_t *find_sample(snd_soundfont_t *sf, int sample_id);
static int load_data(snd_sf_list_t *sflist, const void __user *data, long count);
static void rebuild_presets(snd_sf_list_t *sflist);
static void add_preset(snd_sf_list_t *sflist, snd_sf_zone_t *cur);
static void delete_preset(snd_sf_list_t *sflist, snd_sf_zone_t *zp);
static snd_sf_zone_t *search_first_zone(snd_sf_list_t *sflist, int bank, int preset, int key);
static int search_zones(snd_sf_list_t *sflist, int *notep, int vel, int preset, int bank, snd_sf_zone_t **table, int max_layers, int level);
static int get_index(int bank, int instr, int key);
static void snd_sf_init(snd_sf_list_t *sflist);
static void snd_sf_clear(snd_sf_list_t *sflist);
/*
* lock access to sflist
*/
static void
lock_preset(snd_sf_list_t *sflist)
{
unsigned long flags;
down(&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(snd_sf_list_t *sflist)
{
unsigned long flags;
spin_lock_irqsave(&sflist->lock, flags);
sflist->presets_locked = 0;
spin_unlock_irqrestore(&sflist->lock, flags);
up(&sflist->presets_mutex);
}
/*
* close the patch if the patch was opened by this client.
*/
int
snd_soundfont_close_check(snd_sf_list_t *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(snd_sf_list_t *sflist, const void __user *data, long count, int client)
{
soundfont_patch_info_t patch;
unsigned long flags;
int rc;
if (count < (long)sizeof(patch)) {
snd_printk("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("'The wrong kind of patch' %x\n", patch.key);
return -EINVAL;
}
if (count < patch.len) {
snd_printk("Patch too short %ld, need %d\n", count, patch.len);
return -EINVAL;
}
if (patch.len < 0) {
snd_printk("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("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(snd_sf_list_t *sflist, const char __user *data, int count, int client)
{
soundfont_open_parm_t parm;
snd_soundfont_t *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 snd_soundfont_t *
newsf(snd_sf_list_t *sflist, int type, char *name)
{
snd_soundfont_t *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(snd_soundfont_t *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(snd_sf_list_t *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(snd_sf_list_t *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(snd_sf_list_t *sflist, snd_soundfont_t *sf, snd_sf_zone_t *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 snd_sf_zone_t *
sf_zone_new(snd_sf_list_t *sflist, snd_soundfont_t *sf)
{
snd_sf_zone_t *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 couter
*/
static void
set_sample_counter(snd_sf_list_t *sflist, snd_soundfont_t *sf, snd_sf_sample_t *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 snd_sf_sample_t *
sf_sample_new(snd_sf_list_t *sflist, snd_soundfont_t *sf)
{
snd_sf_sample_t *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(snd_sf_list_t *sflist, snd_soundfont_t *sf, snd_sf_sample_t *sp)
{
/* only last sample is accepted */
if (sp == sf->samples) {
sf->samples = sp->next;
kfree(sp);
}
}
/* load voice map */
static int
load_map(snd_sf_list_t *sflist, const void __user *data, int count)
{
snd_sf_zone_t *zp, *prevp;
snd_soundfont_t *sf;
soundfont_voice_map_t 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(snd_sf_list_t *sflist, snd_soundfont_t *sf, int bank, int instr)
{
snd_sf_zone_t *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(snd_sf_list_t *sflist, const void __user *data, long count)
{
snd_soundfont_t *sf;
snd_sf_zone_t *zone;
soundfont_voice_rec_hdr_t 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("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("Soundfont error: Illegal voice number %d\n", hdr.nvoices);
return -EINVAL;
}
if (count < (long)sizeof(soundfont_voice_info_t)*hdr.nvoices) {
printk("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++) {
snd_sf_zone_t 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(soundfont_voice_info_t *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(soundfont_voice_parm_t *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 snd_sf_sample_t *
set_sample(snd_soundfont_t *sf, soundfont_voice_info_t *avp)
{
snd_sf_sample_t *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 snd_sf_sample_t *
find_sample(snd_soundfont_t *sf, int sample_id)
{
snd_sf_sample_t *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(snd_sf_list_t *sflist, const void __user *data, long count)
{
snd_soundfont_t *sf;
soundfont_sample_info_t sample_info;
snd_sf_sample_t *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;
}
#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(snd_sf_list_t *sflist, const char __user *data, long count, int client)
{
struct patch_info patch;
snd_soundfont_t *sf;
snd_sf_zone_t *zone;
snd_sf_sample_t *smp;
int note, sample_id;
int rc;
if (count < (long)sizeof(patch)) {
snd_printk("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("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("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(snd_sf_list_t *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(snd_sf_list_t *sflist)
{
snd_soundfont_t *sf;
snd_sf_zone_t *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(snd_sf_list_t *sflist, snd_sf_zone_t *cur)
{
snd_sf_zone_t *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 */
snd_sf_zone_t *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(snd_sf_list_t *sflist, snd_sf_zone_t *zp)
{
int index;
snd_sf_zone_t *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(snd_sf_list_t *sflist, int *notep, int vel,
int preset, int bank,
int def_preset, int def_bank,
snd_sf_zone_t **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 snd_sf_zone_t *
search_first_zone(snd_sf_list_t *sflist, int bank, int preset, int key)
{
int index;
snd_sf_zone_t *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(snd_sf_list_t *sflist, int *notep, int vel, int preset, int bank, snd_sf_zone_t **table, int max_layers, int level)
{
snd_sf_zone_t *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(snd_sf_list_t *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(snd_sf_list_t *sflist)
{
snd_soundfont_t *sf, *nextsf;
snd_sf_zone_t *zp, *nextzp;
snd_sf_sample_t *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
*/
snd_sf_list_t *
snd_sf_new(snd_sf_callback_t *callback, snd_util_memhdr_t *hdr)
{
snd_sf_list_t *sflist;
if ((sflist = kzalloc(sizeof(*sflist), GFP_KERNEL)) == NULL)
return NULL;
init_MUTEX(&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(snd_sf_list_t *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(snd_sf_list_t *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(snd_sf_list_t *sflist)
{
snd_soundfont_t *sf;
snd_sf_zone_t *zp, *nextzp;
snd_sf_sample_t *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;
}