[ALSA] opl3 - Use hwdep for patch loading
Use the hwdep device for loading OPL2/3 patch data instead of the messy sequencer instrument layer. Due to this change, the sbiload program should be updated, too. Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
parent
ceac4bf34e
commit
224a033252
7 changed files with 292 additions and 159 deletions
|
@ -104,6 +104,8 @@ struct snd_dm_fm_params {
|
||||||
#define SNDRV_DM_FM_IOCTL_SET_MODE _IOW('H', 0x25, int)
|
#define SNDRV_DM_FM_IOCTL_SET_MODE _IOW('H', 0x25, int)
|
||||||
/* for OPL3 only */
|
/* for OPL3 only */
|
||||||
#define SNDRV_DM_FM_IOCTL_SET_CONNECTION _IOW('H', 0x26, int)
|
#define SNDRV_DM_FM_IOCTL_SET_CONNECTION _IOW('H', 0x26, int)
|
||||||
|
/* SBI patch management */
|
||||||
|
#define SNDRV_DM_FM_IOCTL_CLEAR_PATCHES _IO ('H', 0x40)
|
||||||
|
|
||||||
#define SNDRV_DM_FM_OSS_IOCTL_RESET 0x20
|
#define SNDRV_DM_FM_OSS_IOCTL_RESET 0x20
|
||||||
#define SNDRV_DM_FM_OSS_IOCTL_PLAY_NOTE 0x21
|
#define SNDRV_DM_FM_OSS_IOCTL_PLAY_NOTE 0x21
|
||||||
|
@ -112,4 +114,21 @@ struct snd_dm_fm_params {
|
||||||
#define SNDRV_DM_FM_OSS_IOCTL_SET_MODE 0x24
|
#define SNDRV_DM_FM_OSS_IOCTL_SET_MODE 0x24
|
||||||
#define SNDRV_DM_FM_OSS_IOCTL_SET_OPL 0x25
|
#define SNDRV_DM_FM_OSS_IOCTL_SET_OPL 0x25
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Patch Record - fixed size for write
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define FM_KEY_SBI "SBI\032"
|
||||||
|
#define FM_KEY_2OP "2OP\032"
|
||||||
|
#define FM_KEY_4OP "4OP\032"
|
||||||
|
|
||||||
|
struct sbi_patch {
|
||||||
|
unsigned char prog;
|
||||||
|
unsigned char bank;
|
||||||
|
char key[4];
|
||||||
|
char name[25];
|
||||||
|
char extension[7];
|
||||||
|
unsigned char data[32];
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* __SOUND_ASOUND_FM_H */
|
#endif /* __SOUND_ASOUND_FM_H */
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
#include "seq_oss_legacy.h"
|
#include "seq_oss_legacy.h"
|
||||||
#endif
|
#endif
|
||||||
#include "seq_device.h"
|
#include "seq_device.h"
|
||||||
#include "ainstr_fm.h"
|
#include "asound_fm.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register numbers for the global registers
|
* Register numbers for the global registers
|
||||||
|
@ -239,6 +239,47 @@
|
||||||
|
|
||||||
struct snd_opl3;
|
struct snd_opl3;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Instrument record, aka "Patch"
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* FM operator */
|
||||||
|
struct fm_operator {
|
||||||
|
unsigned char am_vib;
|
||||||
|
unsigned char ksl_level;
|
||||||
|
unsigned char attack_decay;
|
||||||
|
unsigned char sustain_release;
|
||||||
|
unsigned char wave_select;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/* Instrument data */
|
||||||
|
struct fm_instrument {
|
||||||
|
struct fm_operator op[4];
|
||||||
|
unsigned char feedback_connection[2];
|
||||||
|
unsigned char echo_delay;
|
||||||
|
unsigned char echo_atten;
|
||||||
|
unsigned char chorus_spread;
|
||||||
|
unsigned char trnsps;
|
||||||
|
unsigned char fix_dur;
|
||||||
|
unsigned char modes;
|
||||||
|
unsigned char fix_key;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* type */
|
||||||
|
#define FM_PATCH_OPL2 0x01 /* OPL2 2 operators FM instrument */
|
||||||
|
#define FM_PATCH_OPL3 0x02 /* OPL3 4 operators FM instrument */
|
||||||
|
|
||||||
|
/* Instrument record */
|
||||||
|
struct fm_patch {
|
||||||
|
unsigned char prog;
|
||||||
|
unsigned char bank;
|
||||||
|
unsigned char type;
|
||||||
|
struct fm_instrument inst;
|
||||||
|
char name[24];
|
||||||
|
struct fm_patch *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A structure to keep track of each hardware voice
|
* A structure to keep track of each hardware voice
|
||||||
*/
|
*/
|
||||||
|
@ -297,8 +338,8 @@ struct snd_opl3 {
|
||||||
struct snd_midi_channel_set * oss_chset;
|
struct snd_midi_channel_set * oss_chset;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct snd_seq_kinstr_ops fm_ops;
|
#define OPL3_PATCH_HASH_SIZE 32
|
||||||
struct snd_seq_kinstr_list *ilist;
|
struct fm_patch *patch_table[OPL3_PATCH_HASH_SIZE];
|
||||||
|
|
||||||
struct snd_opl3_voice voices[MAX_OPL3_VOICES]; /* Voices (OPL3 'channel') */
|
struct snd_opl3_voice voices[MAX_OPL3_VOICES]; /* Voices (OPL3 'channel') */
|
||||||
int use_time; /* allocation counter */
|
int use_time; /* allocation counter */
|
||||||
|
@ -333,8 +374,19 @@ int snd_opl3_hwdep_new(struct snd_opl3 * opl3, int device, int seq_device,
|
||||||
int snd_opl3_open(struct snd_hwdep * hw, struct file *file);
|
int snd_opl3_open(struct snd_hwdep * hw, struct file *file);
|
||||||
int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file,
|
int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file,
|
||||||
unsigned int cmd, unsigned long arg);
|
unsigned int cmd, unsigned long arg);
|
||||||
|
long snd_opl3_write(struct snd_hwdep *hw, const char __user *buf, long count,
|
||||||
|
loff_t *offset);
|
||||||
int snd_opl3_release(struct snd_hwdep * hw, struct file *file);
|
int snd_opl3_release(struct snd_hwdep * hw, struct file *file);
|
||||||
|
|
||||||
void snd_opl3_reset(struct snd_opl3 * opl3);
|
void snd_opl3_reset(struct snd_opl3 * opl3);
|
||||||
|
|
||||||
|
int snd_opl3_load_patch(struct snd_opl3 *opl3,
|
||||||
|
int prog, int bank, int type,
|
||||||
|
const char *name,
|
||||||
|
const unsigned char *ext,
|
||||||
|
const unsigned char *data);
|
||||||
|
struct fm_patch *snd_opl3_find_patch(struct snd_opl3 *opl3, int prog, int bank,
|
||||||
|
int create_patch);
|
||||||
|
void snd_opl3_clear_patches(struct snd_opl3 *opl3);
|
||||||
|
|
||||||
#endif /* __SOUND_OPL3_H */
|
#endif /* __SOUND_OPL3_H */
|
||||||
|
|
|
@ -327,6 +327,7 @@ static int snd_opl3_free(struct snd_opl3 *opl3)
|
||||||
snd_assert(opl3 != NULL, return -ENXIO);
|
snd_assert(opl3 != NULL, return -ENXIO);
|
||||||
if (opl3->private_free)
|
if (opl3->private_free)
|
||||||
opl3->private_free(opl3);
|
opl3->private_free(opl3);
|
||||||
|
snd_opl3_clear_patches(opl3);
|
||||||
release_and_free_resource(opl3->res_l_port);
|
release_and_free_resource(opl3->res_l_port);
|
||||||
release_and_free_resource(opl3->res_r_port);
|
release_and_free_resource(opl3->res_r_port);
|
||||||
kfree(opl3);
|
kfree(opl3);
|
||||||
|
@ -521,6 +522,7 @@ int snd_opl3_hwdep_new(struct snd_opl3 * opl3,
|
||||||
/* operators - only ioctl */
|
/* operators - only ioctl */
|
||||||
hw->ops.open = snd_opl3_open;
|
hw->ops.open = snd_opl3_open;
|
||||||
hw->ops.ioctl = snd_opl3_ioctl;
|
hw->ops.ioctl = snd_opl3_ioctl;
|
||||||
|
hw->ops.write = snd_opl3_write;
|
||||||
hw->ops.release = snd_opl3_release;
|
hw->ops.release = snd_opl3_release;
|
||||||
|
|
||||||
opl3->seq_dev_num = seq_device;
|
opl3->seq_dev_num = seq_device;
|
||||||
|
|
|
@ -289,8 +289,6 @@ static int snd_opl3_oss_map[MAX_OPL3_VOICES] = {
|
||||||
void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||||
{
|
{
|
||||||
struct snd_opl3 *opl3;
|
struct snd_opl3 *opl3;
|
||||||
struct snd_seq_instr wanted;
|
|
||||||
struct snd_seq_kinstr *kinstr;
|
|
||||||
int instr_4op;
|
int instr_4op;
|
||||||
|
|
||||||
int voice;
|
int voice;
|
||||||
|
@ -306,11 +304,13 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||||
unsigned char voice_offset;
|
unsigned char voice_offset;
|
||||||
unsigned short opl3_reg;
|
unsigned short opl3_reg;
|
||||||
unsigned char reg_val;
|
unsigned char reg_val;
|
||||||
|
unsigned char prg, bank;
|
||||||
|
|
||||||
int key = note;
|
int key = note;
|
||||||
unsigned char fnum, blocknum;
|
unsigned char fnum, blocknum;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
struct fm_patch *patch;
|
||||||
struct fm_instrument *fm;
|
struct fm_instrument *fm;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
|
@ -320,19 +320,17 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||||
snd_printk("Note on, ch %i, inst %i, note %i, vel %i\n",
|
snd_printk("Note on, ch %i, inst %i, note %i, vel %i\n",
|
||||||
chan->number, chan->midi_program, note, vel);
|
chan->number, chan->midi_program, note, vel);
|
||||||
#endif
|
#endif
|
||||||
wanted.cluster = 0;
|
|
||||||
wanted.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
|
|
||||||
|
|
||||||
/* in SYNTH mode, application takes care of voices */
|
/* in SYNTH mode, application takes care of voices */
|
||||||
/* in SEQ mode, drum voice numbers are notes on drum channel */
|
/* in SEQ mode, drum voice numbers are notes on drum channel */
|
||||||
if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
|
if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
|
||||||
if (chan->drum_channel) {
|
if (chan->drum_channel) {
|
||||||
/* percussion instruments are located in bank 128 */
|
/* percussion instruments are located in bank 128 */
|
||||||
wanted.bank = 128;
|
bank = 128;
|
||||||
wanted.prg = note;
|
prg = note;
|
||||||
} else {
|
} else {
|
||||||
wanted.bank = chan->gm_bank_select;
|
bank = chan->gm_bank_select;
|
||||||
wanted.prg = chan->midi_program;
|
prg = chan->midi_program;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Prepare for OSS mode */
|
/* Prepare for OSS mode */
|
||||||
|
@ -340,8 +338,8 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* OSS instruments are located in bank 127 */
|
/* OSS instruments are located in bank 127 */
|
||||||
wanted.bank = 127;
|
bank = 127;
|
||||||
wanted.prg = chan->midi_program;
|
prg = chan->midi_program;
|
||||||
}
|
}
|
||||||
|
|
||||||
spin_lock_irqsave(&opl3->voice_lock, flags);
|
spin_lock_irqsave(&opl3->voice_lock, flags);
|
||||||
|
@ -353,15 +351,14 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||||
}
|
}
|
||||||
|
|
||||||
__extra_prg:
|
__extra_prg:
|
||||||
kinstr = snd_seq_instr_find(opl3->ilist, &wanted, 1, 0);
|
patch = snd_opl3_find_patch(opl3, prg, bank, 0);
|
||||||
if (kinstr == NULL) {
|
if (!patch) {
|
||||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fm = KINSTR_DATA(kinstr);
|
fm = &patch->inst;
|
||||||
|
switch (patch->type) {
|
||||||
switch (fm->type) {
|
|
||||||
case FM_PATCH_OPL2:
|
case FM_PATCH_OPL2:
|
||||||
instr_4op = 0;
|
instr_4op = 0;
|
||||||
break;
|
break;
|
||||||
|
@ -371,14 +368,12 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
snd_seq_instr_free_use(opl3->ilist, kinstr);
|
|
||||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_MIDI
|
#ifdef DEBUG_MIDI
|
||||||
snd_printk(" --> OPL%i instrument: %s\n",
|
snd_printk(" --> OPL%i instrument: %s\n",
|
||||||
instr_4op ? 3 : 2, kinstr->name);
|
instr_4op ? 3 : 2, patch->name);
|
||||||
#endif
|
#endif
|
||||||
/* in SYNTH mode, application takes care of voices */
|
/* in SYNTH mode, application takes care of voices */
|
||||||
/* in SEQ mode, allocate voice on free OPL3 channel */
|
/* in SEQ mode, allocate voice on free OPL3 channel */
|
||||||
|
@ -569,8 +564,6 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||||
/* get extra pgm, but avoid possible loops */
|
/* get extra pgm, but avoid possible loops */
|
||||||
extra_prg = (extra_prg) ? 0 : fm->modes;
|
extra_prg = (extra_prg) ? 0 : fm->modes;
|
||||||
|
|
||||||
snd_seq_instr_free_use(opl3->ilist, kinstr);
|
|
||||||
|
|
||||||
/* do the bookkeeping */
|
/* do the bookkeeping */
|
||||||
vp->time = opl3->use_time++;
|
vp->time = opl3->use_time++;
|
||||||
vp->note = key;
|
vp->note = key;
|
||||||
|
@ -601,12 +594,12 @@ void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||||
/* allocate extra program if specified in patch library */
|
/* allocate extra program if specified in patch library */
|
||||||
if (extra_prg) {
|
if (extra_prg) {
|
||||||
if (extra_prg > 128) {
|
if (extra_prg > 128) {
|
||||||
wanted.bank = 128;
|
bank = 128;
|
||||||
/* percussions start at 35 */
|
/* percussions start at 35 */
|
||||||
wanted.prg = extra_prg - 128 + 35 - 1;
|
prg = extra_prg - 128 + 35 - 1;
|
||||||
} else {
|
} else {
|
||||||
wanted.bank = 0;
|
bank = 0;
|
||||||
wanted.prg = extra_prg - 1;
|
prg = extra_prg - 1;
|
||||||
}
|
}
|
||||||
#ifdef DEBUG_MIDI
|
#ifdef DEBUG_MIDI
|
||||||
snd_printk(" *** allocating extra program\n");
|
snd_printk(" *** allocating extra program\n");
|
||||||
|
|
|
@ -195,17 +195,6 @@ static int snd_opl3_close_seq_oss(struct snd_seq_oss_arg *arg)
|
||||||
|
|
||||||
/* load patch */
|
/* load patch */
|
||||||
|
|
||||||
/* offsets for SBI params */
|
|
||||||
#define AM_VIB 0
|
|
||||||
#define KSL_LEVEL 2
|
|
||||||
#define ATTACK_DECAY 4
|
|
||||||
#define SUSTAIN_RELEASE 6
|
|
||||||
#define WAVE_SELECT 8
|
|
||||||
|
|
||||||
/* offset for SBI instrument */
|
|
||||||
#define CONNECTION 10
|
|
||||||
#define OFFSET_4OP 11
|
|
||||||
|
|
||||||
/* from sound_config.h */
|
/* from sound_config.h */
|
||||||
#define SBFM_MAXINSTR 256
|
#define SBFM_MAXINSTR 256
|
||||||
|
|
||||||
|
@ -213,112 +202,42 @@ static int snd_opl3_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
|
||||||
const char __user *buf, int offs, int count)
|
const char __user *buf, int offs, int count)
|
||||||
{
|
{
|
||||||
struct snd_opl3 *opl3;
|
struct snd_opl3 *opl3;
|
||||||
int err = -EINVAL;
|
struct sbi_instrument sbi;
|
||||||
|
char name[32];
|
||||||
|
int err, type;
|
||||||
|
|
||||||
snd_assert(arg != NULL, return -ENXIO);
|
snd_assert(arg != NULL, return -ENXIO);
|
||||||
opl3 = arg->private_data;
|
opl3 = arg->private_data;
|
||||||
|
|
||||||
if ((format == FM_PATCH) || (format == OPL3_PATCH)) {
|
if (format == FM_PATCH)
|
||||||
struct sbi_instrument sbi;
|
type = FM_PATCH_OPL2;
|
||||||
|
else if (format == OPL3_PATCH)
|
||||||
|
type = FM_PATCH_OPL3;
|
||||||
|
else
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
size_t size;
|
if (count < (int)sizeof(sbi)) {
|
||||||
struct snd_seq_instr_header *put;
|
snd_printk("FM Error: Patch record too short\n");
|
||||||
struct snd_seq_instr_data *data;
|
return -EINVAL;
|
||||||
struct fm_xinstrument *xinstr;
|
|
||||||
|
|
||||||
struct snd_seq_event ev;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
mm_segment_t fs;
|
|
||||||
|
|
||||||
if (count < (int)sizeof(sbi)) {
|
|
||||||
snd_printk("FM Error: Patch record too short\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
if (copy_from_user(&sbi, buf, sizeof(sbi)))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
|
|
||||||
snd_printk("FM Error: Invalid instrument number %d\n", sbi.channel);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
size = sizeof(*put) + sizeof(struct fm_xinstrument);
|
|
||||||
put = kzalloc(size, GFP_KERNEL);
|
|
||||||
if (put == NULL)
|
|
||||||
return -ENOMEM;
|
|
||||||
/* build header */
|
|
||||||
data = &put->data;
|
|
||||||
data->type = SNDRV_SEQ_INSTR_ATYPE_DATA;
|
|
||||||
strcpy(data->data.format, SNDRV_SEQ_INSTR_ID_OPL2_3);
|
|
||||||
/* build data section */
|
|
||||||
xinstr = (struct fm_xinstrument *)(data + 1);
|
|
||||||
xinstr->stype = FM_STRU_INSTR;
|
|
||||||
|
|
||||||
for (i = 0; i < 2; i++) {
|
|
||||||
xinstr->op[i].am_vib = sbi.operators[AM_VIB + i];
|
|
||||||
xinstr->op[i].ksl_level = sbi.operators[KSL_LEVEL + i];
|
|
||||||
xinstr->op[i].attack_decay = sbi.operators[ATTACK_DECAY + i];
|
|
||||||
xinstr->op[i].sustain_release = sbi.operators[SUSTAIN_RELEASE + i];
|
|
||||||
xinstr->op[i].wave_select = sbi.operators[WAVE_SELECT + i];
|
|
||||||
}
|
|
||||||
xinstr->feedback_connection[0] = sbi.operators[CONNECTION];
|
|
||||||
|
|
||||||
if (format == OPL3_PATCH) {
|
|
||||||
xinstr->type = FM_PATCH_OPL3;
|
|
||||||
for (i = 0; i < 2; i++) {
|
|
||||||
xinstr->op[i+2].am_vib = sbi.operators[OFFSET_4OP + AM_VIB + i];
|
|
||||||
xinstr->op[i+2].ksl_level = sbi.operators[OFFSET_4OP + KSL_LEVEL + i];
|
|
||||||
xinstr->op[i+2].attack_decay = sbi.operators[OFFSET_4OP + ATTACK_DECAY + i];
|
|
||||||
xinstr->op[i+2].sustain_release = sbi.operators[OFFSET_4OP + SUSTAIN_RELEASE + i];
|
|
||||||
xinstr->op[i+2].wave_select = sbi.operators[OFFSET_4OP + WAVE_SELECT + i];
|
|
||||||
}
|
|
||||||
xinstr->feedback_connection[1] = sbi.operators[OFFSET_4OP + CONNECTION];
|
|
||||||
} else {
|
|
||||||
xinstr->type = FM_PATCH_OPL2;
|
|
||||||
}
|
|
||||||
|
|
||||||
put->id.instr.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
|
|
||||||
put->id.instr.bank = 127;
|
|
||||||
put->id.instr.prg = sbi.channel;
|
|
||||||
put->cmd = SNDRV_SEQ_INSTR_PUT_CMD_CREATE;
|
|
||||||
|
|
||||||
memset (&ev, 0, sizeof(ev));
|
|
||||||
ev.source.client = SNDRV_SEQ_CLIENT_OSS;
|
|
||||||
ev.dest = arg->addr;
|
|
||||||
|
|
||||||
ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARUSR;
|
|
||||||
ev.queue = SNDRV_SEQ_QUEUE_DIRECT;
|
|
||||||
|
|
||||||
fs = snd_enter_user();
|
|
||||||
__again:
|
|
||||||
ev.type = SNDRV_SEQ_EVENT_INSTR_PUT;
|
|
||||||
ev.data.ext.len = size;
|
|
||||||
ev.data.ext.ptr = put;
|
|
||||||
|
|
||||||
err = snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, &ev,
|
|
||||||
opl3->seq_client, 0, 0);
|
|
||||||
if (err == -EBUSY) {
|
|
||||||
struct snd_seq_instr_header remove;
|
|
||||||
|
|
||||||
memset (&remove, 0, sizeof(remove));
|
|
||||||
remove.cmd = SNDRV_SEQ_INSTR_FREE_CMD_SINGLE;
|
|
||||||
remove.id.instr = put->id.instr;
|
|
||||||
|
|
||||||
/* remove instrument */
|
|
||||||
ev.type = SNDRV_SEQ_EVENT_INSTR_FREE;
|
|
||||||
ev.data.ext.len = sizeof(remove);
|
|
||||||
ev.data.ext.ptr = &remove;
|
|
||||||
|
|
||||||
snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, &ev,
|
|
||||||
opl3->seq_client, 0, 0);
|
|
||||||
goto __again;
|
|
||||||
}
|
|
||||||
snd_leave_user(fs);
|
|
||||||
|
|
||||||
kfree(put);
|
|
||||||
}
|
}
|
||||||
return err;
|
if (copy_from_user(&sbi, buf, sizeof(sbi)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
|
||||||
|
snd_printk("FM Error: Invalid instrument number %d\n",
|
||||||
|
sbi.channel);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(name, 0, sizeof(name));
|
||||||
|
sprintf(name, "Chan%d", sbi.channel);
|
||||||
|
|
||||||
|
err = snd_opl3_load_patch(opl3, sbi.channel, 127, type, name, NULL,
|
||||||
|
sbi.operators);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sizeof(sbi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ioctl */
|
/* ioctl */
|
||||||
|
|
|
@ -152,15 +152,7 @@ static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct,
|
||||||
{
|
{
|
||||||
struct snd_opl3 *opl3 = private_data;
|
struct snd_opl3 *opl3 = private_data;
|
||||||
|
|
||||||
if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN &&
|
snd_midi_process_event(&opl3_ops, ev, opl3->chset);
|
||||||
ev->type <= SNDRV_SEQ_EVENT_INSTR_CHANGE) {
|
|
||||||
if (direct) {
|
|
||||||
snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, ev,
|
|
||||||
opl3->seq_client, atomic, hop);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
snd_midi_process_event(&opl3_ops, ev, opl3->chset);
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,16 +241,6 @@ static int snd_opl3_seq_new_device(struct snd_seq_device *dev)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* initialize instrument list */
|
|
||||||
opl3->ilist = snd_seq_instr_list_new();
|
|
||||||
if (opl3->ilist == NULL) {
|
|
||||||
snd_seq_delete_kernel_client(client);
|
|
||||||
opl3->seq_client = -1;
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
opl3->ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT;
|
|
||||||
snd_seq_fm_init(&opl3->fm_ops, NULL);
|
|
||||||
|
|
||||||
/* setup system timer */
|
/* setup system timer */
|
||||||
init_timer(&opl3->tlist);
|
init_timer(&opl3->tlist);
|
||||||
opl3->tlist.function = snd_opl3_timer_func;
|
opl3->tlist.function = snd_opl3_timer_func;
|
||||||
|
@ -287,8 +269,6 @@ static int snd_opl3_seq_delete_device(struct snd_seq_device *dev)
|
||||||
snd_seq_delete_kernel_client(opl3->seq_client);
|
snd_seq_delete_kernel_client(opl3->seq_client);
|
||||||
opl3->seq_client = -1;
|
opl3->seq_client = -1;
|
||||||
}
|
}
|
||||||
if (opl3->ilist)
|
|
||||||
snd_seq_instr_list_free(&opl3->ilist);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,6 +165,10 @@ int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file,
|
||||||
#endif
|
#endif
|
||||||
return snd_opl3_set_connection(opl3, (int) arg);
|
return snd_opl3_set_connection(opl3, (int) arg);
|
||||||
|
|
||||||
|
case SNDRV_DM_FM_IOCTL_CLEAR_PATCHES:
|
||||||
|
snd_opl3_clear_patches(opl3);
|
||||||
|
return 0;
|
||||||
|
|
||||||
#ifdef CONFIG_SND_DEBUG
|
#ifdef CONFIG_SND_DEBUG
|
||||||
default:
|
default:
|
||||||
snd_printk("unknown IOCTL: 0x%x\n", cmd);
|
snd_printk("unknown IOCTL: 0x%x\n", cmd);
|
||||||
|
@ -188,6 +192,170 @@ int snd_opl3_release(struct snd_hwdep * hw, struct file *file)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* write the device - load patches
|
||||||
|
*/
|
||||||
|
long snd_opl3_write(struct snd_hwdep *hw, const char __user *buf, long count,
|
||||||
|
loff_t *offset)
|
||||||
|
{
|
||||||
|
struct snd_opl3 *opl3 = hw->private_data;
|
||||||
|
long result = 0;
|
||||||
|
int err = 0;
|
||||||
|
struct sbi_patch inst;
|
||||||
|
|
||||||
|
while (count >= sizeof(inst)) {
|
||||||
|
unsigned char type;
|
||||||
|
if (copy_from_user(&inst, buf, sizeof(inst)))
|
||||||
|
return -EFAULT;
|
||||||
|
if (!memcmp(inst.key, FM_KEY_SBI, 4) ||
|
||||||
|
!memcmp(inst.key, FM_KEY_2OP, 4))
|
||||||
|
type = FM_PATCH_OPL2;
|
||||||
|
else if (!memcmp(inst.key, FM_KEY_4OP, 4))
|
||||||
|
type = FM_PATCH_OPL3;
|
||||||
|
else /* invalid type */
|
||||||
|
break;
|
||||||
|
err = snd_opl3_load_patch(opl3, inst.prog, inst.bank, type,
|
||||||
|
inst.name, inst.extension,
|
||||||
|
inst.data);
|
||||||
|
if (err < 0)
|
||||||
|
break;
|
||||||
|
result += sizeof(inst);
|
||||||
|
count -= sizeof(inst);
|
||||||
|
}
|
||||||
|
return result > 0 ? result : err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Patch management
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* offsets for SBI params */
|
||||||
|
#define AM_VIB 0
|
||||||
|
#define KSL_LEVEL 2
|
||||||
|
#define ATTACK_DECAY 4
|
||||||
|
#define SUSTAIN_RELEASE 6
|
||||||
|
#define WAVE_SELECT 8
|
||||||
|
|
||||||
|
/* offset for SBI instrument */
|
||||||
|
#define CONNECTION 10
|
||||||
|
#define OFFSET_4OP 11
|
||||||
|
|
||||||
|
/*
|
||||||
|
* load a patch, obviously.
|
||||||
|
*
|
||||||
|
* loaded on the given program and bank numbers with the given type
|
||||||
|
* (FM_PATCH_OPLx).
|
||||||
|
* data is the pointer of SBI record _without_ header (key and name).
|
||||||
|
* name is the name string of the patch.
|
||||||
|
* ext is the extension data of 7 bytes long (stored in name of SBI
|
||||||
|
* data up to offset 25), or NULL to skip.
|
||||||
|
* return 0 if successful or a negative error code.
|
||||||
|
*/
|
||||||
|
int snd_opl3_load_patch(struct snd_opl3 *opl3,
|
||||||
|
int prog, int bank, int type,
|
||||||
|
const char *name,
|
||||||
|
const unsigned char *ext,
|
||||||
|
const unsigned char *data)
|
||||||
|
{
|
||||||
|
struct fm_patch *patch;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
patch = snd_opl3_find_patch(opl3, prog, bank, 1);
|
||||||
|
if (!patch)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
patch->type = type;
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
patch->inst.op[i].am_vib = data[AM_VIB + i];
|
||||||
|
patch->inst.op[i].ksl_level = data[KSL_LEVEL + i];
|
||||||
|
patch->inst.op[i].attack_decay = data[ATTACK_DECAY + i];
|
||||||
|
patch->inst.op[i].sustain_release = data[SUSTAIN_RELEASE + i];
|
||||||
|
patch->inst.op[i].wave_select = data[WAVE_SELECT + i];
|
||||||
|
}
|
||||||
|
patch->inst.feedback_connection[0] = data[CONNECTION];
|
||||||
|
|
||||||
|
if (type == FM_PATCH_OPL3) {
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
patch->inst.op[i+2].am_vib =
|
||||||
|
data[OFFSET_4OP + AM_VIB + i];
|
||||||
|
patch->inst.op[i+2].ksl_level =
|
||||||
|
data[OFFSET_4OP + KSL_LEVEL + i];
|
||||||
|
patch->inst.op[i+2].attack_decay =
|
||||||
|
data[OFFSET_4OP + ATTACK_DECAY + i];
|
||||||
|
patch->inst.op[i+2].sustain_release =
|
||||||
|
data[OFFSET_4OP + SUSTAIN_RELEASE + i];
|
||||||
|
patch->inst.op[i+2].wave_select =
|
||||||
|
data[OFFSET_4OP + WAVE_SELECT + i];
|
||||||
|
}
|
||||||
|
patch->inst.feedback_connection[1] =
|
||||||
|
data[OFFSET_4OP + CONNECTION];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ext) {
|
||||||
|
patch->inst.echo_delay = ext[0];
|
||||||
|
patch->inst.echo_atten = ext[1];
|
||||||
|
patch->inst.chorus_spread = ext[2];
|
||||||
|
patch->inst.trnsps = ext[3];
|
||||||
|
patch->inst.fix_dur = ext[4];
|
||||||
|
patch->inst.modes = ext[5];
|
||||||
|
patch->inst.fix_key = ext[6];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name)
|
||||||
|
strlcpy(patch->name, name, sizeof(patch->name));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(snd_opl3_load_patch);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* find a patch with the given program and bank numbers, returns its pointer
|
||||||
|
* if no matching patch is found and create_patch is set, it creates a
|
||||||
|
* new patch object.
|
||||||
|
*/
|
||||||
|
struct fm_patch *snd_opl3_find_patch(struct snd_opl3 *opl3, int prog, int bank,
|
||||||
|
int create_patch)
|
||||||
|
{
|
||||||
|
/* pretty dumb hash key */
|
||||||
|
unsigned int key = (prog + bank) % OPL3_PATCH_HASH_SIZE;
|
||||||
|
struct fm_patch *patch;
|
||||||
|
|
||||||
|
for (patch = opl3->patch_table[key]; patch; patch = patch->next) {
|
||||||
|
if (patch->prog == prog && patch->bank == bank)
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
if (!create_patch)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
patch = kzalloc(sizeof(*patch), GFP_KERNEL);
|
||||||
|
if (!patch)
|
||||||
|
return NULL;
|
||||||
|
patch->prog = prog;
|
||||||
|
patch->bank = bank;
|
||||||
|
patch->next = opl3->patch_table[key];
|
||||||
|
opl3->patch_table[key] = patch;
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(snd_opl3_find_patch);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clear all patches of the given OPL3 instance
|
||||||
|
*/
|
||||||
|
void snd_opl3_clear_patches(struct snd_opl3 *opl3)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < OPL3_PATCH_HASH_SIZE; i++) {
|
||||||
|
struct fm_patch *patch, *next;
|
||||||
|
for (patch = opl3->patch_table[i]; patch; patch = next) {
|
||||||
|
next = patch->next;
|
||||||
|
kfree(patch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memset(opl3->patch_table, 0, sizeof(opl3->patch_table));
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------ */
|
/* ------------------------------ */
|
||||||
|
|
||||||
void snd_opl3_reset(struct snd_opl3 * opl3)
|
void snd_opl3_reset(struct snd_opl3 * opl3)
|
||||||
|
|
Loading…
Reference in a new issue