/*
* Patch transfer callback for Emu10k1
*
* Copyright (C) 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
*/
/*
* All the code for loading in a patch. There is very little that is
* chip specific here. Just the actual writing to the board.
*/
#include "emu10k1_synth_local.h"
/*
*/
#define BLANK_LOOP_START 4
#define BLANK_LOOP_END 8
#define BLANK_LOOP_SIZE 12
#define BLANK_HEAD_SIZE 32
/*
* allocate a sample block and copy data from userspace
*/
int
snd_emu10k1_sample_new(struct snd_emux *rec, struct snd_sf_sample *sp,
struct snd_util_memhdr *hdr,
const void __user *data, long count)
{
int offset;
int truesize, size, loopsize, blocksize;
int loopend, sampleend;
unsigned int start_addr;
struct snd_emu10k1 *emu;
emu = rec->hw;
if (snd_BUG_ON(!sp || !hdr))
return -EINVAL;
if (sp->v.size == 0) {
snd_printd("emu: rom font for sample %d\n", sp->v.sample);
return 0;
}
/* recalculate address offset */
sp->v.end -= sp->v.start;
sp->v.loopstart -= sp->v.start;
sp->v.loopend -= sp->v.start;
sp->v.start = 0;
/* some samples have invalid data. the addresses are corrected in voice info */
sampleend = sp->v.end;
if (sampleend > sp->v.size)
sampleend = sp->v.size;
loopend = sp->v.loopend;
if (loopend > sampleend)
loopend = sampleend;
/* be sure loop points start < end */
if (sp->v.loopstart >= sp->v.loopend) {
int tmp = sp->v.loopstart;
sp->v.loopstart = sp->v.loopend;
sp->v.loopend = tmp;
}
/* compute true data size to be loaded */
truesize = sp->v.size + BLANK_HEAD_SIZE;
loopsize = 0;
#if 0 /* not supported */
if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))
loopsize = sp->v.loopend - sp->v.loopstart;
truesize += loopsize;
#endif
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK)
truesize += BLANK_LOOP_SIZE;
/* try to allocate a memory block */
blocksize = truesize;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
blocksize *= 2;
sp->block = snd_emu10k1_synth_alloc(emu, blocksize);
if (sp->block == NULL) {
snd_printd("emu10k1: synth malloc failed (size=%d)\n", blocksize);
/* not ENOMEM (for compatibility with OSS) */
return -ENOSPC;
}
/* set the total size */
sp->v.truesize = blocksize;
/* write blank samples at head */
offset = 0;
size = BLANK_HEAD_SIZE;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
size *= 2;
if (offset + size > blocksize)
return -EINVAL;
snd_emu10k1_synth_bzero(emu, sp->block, offset, size);
offset += size;
/* copy start->loopend */
size = loopend;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
size *= 2;
if (offset + size > blocksize)
return -EINVAL;
if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
snd_emu10k1_synth_free(emu, sp->block);
sp->block = NULL;
return -EFAULT;
}
offset += size;
data += size;
#if 0 /* not suppported yet */
/* handle reverse (or bidirectional) loop */
if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) {
/* copy loop in reverse */
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
int woffset;
unsigned short *wblock = (unsigned short*)block;
woffset = offset / 2;
if (offset + loopsize * 2 > blocksize)
return -EINVAL;
for (i = 0; i < loopsize; i++)
wblock[woffset + i] = wblock[woffset - i -1];
offset += loopsize * 2;
} else {
if (offset + loopsize > blocksize)
return -EINVAL;
for (i = 0; i < loopsize; i++)
block[offset + i] = block[offset - i -1];
offset += loopsize;
}
/* modify loop pointers */
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) {
sp->v.loopend += loopsize;
} else {
sp->v.loopstart += loopsize;
sp->v.loopend += loopsize;
}
/* add sample pointer */
sp->v.end += loopsize;
}
#endif
/* loopend -> sample end */
size = sp->v.size - loopend;
if (size < 0)
return -EINVAL;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
size *= 2;
if (snd_emu10k1_synth_copy_from_user(emu, sp->block, offset, data, size)) {
snd_emu10k1_synth_free(emu, sp->block);
sp->block = NULL;
return -EFAULT;
}
offset += size;
/* clear rest of samples (if any) */
if (offset < blocksize)
snd_emu10k1_synth_bzero(emu, sp->block, offset, blocksize - offset);
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) {
/* if no blank loop is attached in the sample, add it */
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) {
sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
sp->v.loopend = sp->v.end + BLANK_LOOP_END;
}
}
#if 0 /* not supported yet */
if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_UNSIGNED) {
/* unsigned -> signed */
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS)) {
unsigned short *wblock = (unsigned short*)block;
for (i = 0; i < truesize; i++)
wblock[i] ^= 0x8000;
} else {
for (i = 0; i < truesize; i++)
block[i] ^= 0x80;
}
}
#endif
/* recalculate offset */
start_addr = BLANK_HEAD_SIZE * 2;
if (! (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS))
start_addr >>= 1;
sp->v.start += start_addr;
sp->v.end += start_addr;
sp->v.loopstart += start_addr;
sp->v.loopend += start_addr;
return 0;
}
/*
* free a sample block
*/
int
snd_emu10k1_sample_free(struct snd_emux *rec, struct snd_sf_sample *sp,
struct snd_util_memhdr *hdr)
{
struct snd_emu10k1 *emu;
emu = rec->hw;
if (snd_BUG_ON(!sp || !hdr))
return -EINVAL;
if (sp->block) {
snd_emu10k1_synth_free(emu, sp->block);
sp->block = NULL;
}
return 0;
}