#define SNDRV_DM_FM_IOCTL_SET_MODE _IOW('H', 0x25, int)
/* for OPL3 only */
#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_PLAY_NOTE 0x21
#define SNDRV_DM_FM_OSS_IOCTL_SET_MODE 0x24
#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 */
#include "seq_oss_legacy.h"
#endif
#include "seq_device.h"
-#include "ainstr_fm.h"
+#include "asound_fm.h"
/*
* Register numbers for the global registers
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
*/
struct snd_opl3_voice {
struct snd_midi_channel_set * oss_chset;
#endif
- struct snd_seq_kinstr_ops fm_ops;
- struct snd_seq_kinstr_list *ilist;
+#define OPL3_PATCH_HASH_SIZE 32
+ struct fm_patch *patch_table[OPL3_PATCH_HASH_SIZE];
struct snd_opl3_voice voices[MAX_OPL3_VOICES]; /* Voices (OPL3 'channel') */
int use_time; /* allocation counter */
int snd_opl3_open(struct snd_hwdep * hw, struct file *file);
int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file,
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);
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 */
snd_assert(opl3 != NULL, return -ENXIO);
if (opl3->private_free)
opl3->private_free(opl3);
+ snd_opl3_clear_patches(opl3);
release_and_free_resource(opl3->res_l_port);
release_and_free_resource(opl3->res_r_port);
kfree(opl3);
/* operators - only ioctl */
hw->ops.open = snd_opl3_open;
hw->ops.ioctl = snd_opl3_ioctl;
+ hw->ops.write = snd_opl3_write;
hw->ops.release = snd_opl3_release;
opl3->seq_dev_num = seq_device;
void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
{
struct snd_opl3 *opl3;
- struct snd_seq_instr wanted;
- struct snd_seq_kinstr *kinstr;
int instr_4op;
int voice;
unsigned char voice_offset;
unsigned short opl3_reg;
unsigned char reg_val;
+ unsigned char prg, bank;
int key = note;
unsigned char fnum, blocknum;
int i;
+ struct fm_patch *patch;
struct fm_instrument *fm;
unsigned long flags;
snd_printk("Note on, ch %i, inst %i, note %i, vel %i\n",
chan->number, chan->midi_program, note, vel);
#endif
- wanted.cluster = 0;
- wanted.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
/* in SYNTH mode, application takes care of voices */
/* in SEQ mode, drum voice numbers are notes on drum channel */
if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
if (chan->drum_channel) {
/* percussion instruments are located in bank 128 */
- wanted.bank = 128;
- wanted.prg = note;
+ bank = 128;
+ prg = note;
} else {
- wanted.bank = chan->gm_bank_select;
- wanted.prg = chan->midi_program;
+ bank = chan->gm_bank_select;
+ prg = chan->midi_program;
}
} else {
/* Prepare for OSS mode */
return;
/* OSS instruments are located in bank 127 */
- wanted.bank = 127;
- wanted.prg = chan->midi_program;
+ bank = 127;
+ prg = chan->midi_program;
}
spin_lock_irqsave(&opl3->voice_lock, flags);
}
__extra_prg:
- kinstr = snd_seq_instr_find(opl3->ilist, &wanted, 1, 0);
- if (kinstr == NULL) {
+ patch = snd_opl3_find_patch(opl3, prg, bank, 0);
+ if (!patch) {
spin_unlock_irqrestore(&opl3->voice_lock, flags);
return;
}
- fm = KINSTR_DATA(kinstr);
-
- switch (fm->type) {
+ fm = &patch->inst;
+ switch (patch->type) {
case FM_PATCH_OPL2:
instr_4op = 0;
break;
break;
}
default:
- snd_seq_instr_free_use(opl3->ilist, kinstr);
spin_unlock_irqrestore(&opl3->voice_lock, flags);
return;
}
-
#ifdef DEBUG_MIDI
snd_printk(" --> OPL%i instrument: %s\n",
- instr_4op ? 3 : 2, kinstr->name);
+ instr_4op ? 3 : 2, patch->name);
#endif
/* in SYNTH mode, application takes care of voices */
/* in SEQ mode, allocate voice on free OPL3 channel */
/* get extra pgm, but avoid possible loops */
extra_prg = (extra_prg) ? 0 : fm->modes;
- snd_seq_instr_free_use(opl3->ilist, kinstr);
-
/* do the bookkeeping */
vp->time = opl3->use_time++;
vp->note = key;
/* allocate extra program if specified in patch library */
if (extra_prg) {
if (extra_prg > 128) {
- wanted.bank = 128;
+ bank = 128;
/* percussions start at 35 */
- wanted.prg = extra_prg - 128 + 35 - 1;
+ prg = extra_prg - 128 + 35 - 1;
} else {
- wanted.bank = 0;
- wanted.prg = extra_prg - 1;
+ bank = 0;
+ prg = extra_prg - 1;
}
#ifdef DEBUG_MIDI
snd_printk(" *** allocating extra program\n");
/* 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 */
#define SBFM_MAXINSTR 256
const char __user *buf, int offs, int count)
{
struct snd_opl3 *opl3;
- int err = -EINVAL;
+ struct sbi_instrument sbi;
+ char name[32];
+ int err, type;
snd_assert(arg != NULL, return -ENXIO);
opl3 = arg->private_data;
- if ((format == FM_PATCH) || (format == OPL3_PATCH)) {
- struct sbi_instrument sbi;
+ if (format == FM_PATCH)
+ type = FM_PATCH_OPL2;
+ else if (format == OPL3_PATCH)
+ type = FM_PATCH_OPL3;
+ else
+ return -EINVAL;
- size_t size;
- struct snd_seq_instr_header *put;
- struct snd_seq_instr_data *data;
- struct fm_xinstrument *xinstr;
+ 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;
- struct snd_seq_event ev;
- int i;
+ if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
+ snd_printk("FM Error: Invalid instrument number %d\n",
+ sbi.channel);
+ return -EINVAL;
+ }
- mm_segment_t fs;
+ memset(name, 0, sizeof(name));
+ sprintf(name, "Chan%d", sbi.channel);
- 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;
+ err = snd_opl3_load_patch(opl3, sbi.channel, 127, type, name, NULL,
+ sbi.operators);
+ if (err < 0)
+ return err;
- 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;
+ return sizeof(sbi);
}
/* ioctl */
{
struct snd_opl3 *opl3 = private_data;
- if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN &&
- 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);
- }
+ snd_midi_process_event(&opl3_ops, ev, opl3->chset);
return 0;
}
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 */
init_timer(&opl3->tlist);
opl3->tlist.function = snd_opl3_timer_func;
snd_seq_delete_kernel_client(opl3->seq_client);
opl3->seq_client = -1;
}
- if (opl3->ilist)
- snd_seq_instr_list_free(&opl3->ilist);
return 0;
}
#endif
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
default:
snd_printk("unknown IOCTL: 0x%x\n", cmd);
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)