diff options
Diffstat (limited to 'sound/drivers/opl3/opl3_seq.c')
-rw-r--r-- | sound/drivers/opl3/opl3_seq.c | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c new file mode 100644 index 000000000000..136964b844de --- /dev/null +++ b/sound/drivers/opl3/opl3_seq.c @@ -0,0 +1,314 @@ +/* + * Copyright (c) by Uros Bizjak <uros@kss-loka.si> + * + * Midi Sequencer interface routines for OPL2/OPL3/OPL4 FM + * + * OPL2/3 FM instrument loader: + * alsa-tools/seq/sbiload/ + * + * 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 + * + */ + +#include "opl3_voice.h" +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALSA driver for OPL3 FM synth"); + +int use_internal_drums = 0; +module_param(use_internal_drums, bool, 0444); +MODULE_PARM_DESC(use_internal_drums, "Enable internal OPL2/3 drums."); + +int snd_opl3_synth_use_inc(opl3_t * opl3) +{ + if (!try_module_get(opl3->card->module)) + return -EFAULT; + return 0; + +} + +void snd_opl3_synth_use_dec(opl3_t * opl3) +{ + module_put(opl3->card->module); +} + +int snd_opl3_synth_setup(opl3_t * opl3) +{ + int idx; + + down(&opl3->access_mutex); + if (opl3->used) { + up(&opl3->access_mutex); + return -EBUSY; + } + opl3->used++; + up(&opl3->access_mutex); + + snd_opl3_reset(opl3); + + for (idx = 0; idx < MAX_OPL3_VOICES; idx++) { + opl3->voices[idx].state = SNDRV_OPL3_ST_OFF; + opl3->voices[idx].time = 0; + opl3->voices[idx].keyon_reg = 0x00; + } + opl3->use_time = 0; + opl3->connection_reg = 0x00; + if (opl3->hardware >= OPL3_HW_OPL3) { + /* Clear 4-op connections */ + opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, + opl3->connection_reg); + opl3->max_voices = MAX_OPL3_VOICES; + } + return 0; +} + +void snd_opl3_synth_cleanup(opl3_t * opl3) +{ + unsigned long flags; + + /* Stop system timer */ + spin_lock_irqsave(&opl3->sys_timer_lock, flags); + if (opl3->sys_timer_status) { + del_timer(&opl3->tlist); + opl3->sys_timer_status = 0; + } + spin_unlock_irqrestore(&opl3->sys_timer_lock, flags); + + snd_opl3_reset(opl3); + down(&opl3->access_mutex); + opl3->used--; + up(&opl3->access_mutex); +} + +static int snd_opl3_synth_use(void *private_data, snd_seq_port_subscribe_t * info) +{ + opl3_t *opl3 = private_data; + int err; + + if ((err = snd_opl3_synth_setup(opl3)) < 0) + return err; + + if (use_internal_drums) { + /* Percussion mode */ + opl3->voices[6].state = opl3->voices[7].state = + opl3->voices[8].state = SNDRV_OPL3_ST_NOT_AVAIL; + snd_opl3_load_drums(opl3); + opl3->drum_reg = OPL3_PERCUSSION_ENABLE; + opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, opl3->drum_reg); + } else { + opl3->drum_reg = 0x00; + } + + if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) { + if ((err = snd_opl3_synth_use_inc(opl3)) < 0) + return err; + } + opl3->synth_mode = SNDRV_OPL3_MODE_SEQ; + return 0; +} + +static int snd_opl3_synth_unuse(void *private_data, snd_seq_port_subscribe_t * info) +{ + opl3_t *opl3 = private_data; + + snd_opl3_synth_cleanup(opl3); + + if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) + snd_opl3_synth_use_dec(opl3); + return 0; +} + +/* + * MIDI emulation operators + */ +snd_midi_op_t opl3_ops = { + .note_on = snd_opl3_note_on, + .note_off = snd_opl3_note_off, + .key_press = snd_opl3_key_press, + .note_terminate = snd_opl3_terminate_note, + .control = snd_opl3_control, + .nrpn = snd_opl3_nrpn, + .sysex = snd_opl3_sysex, +}; + +static int snd_opl3_synth_event_input(snd_seq_event_t * ev, int direct, + void *private_data, int atomic, int hop) +{ + opl3_t *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); + } + return 0; +} + +/* ------------------------------ */ + +static void snd_opl3_synth_free_port(void *private_data) +{ + opl3_t *opl3 = private_data; + + snd_midi_channel_free_set(opl3->chset); +} + +static int snd_opl3_synth_create_port(opl3_t * opl3) +{ + snd_seq_port_callback_t callbacks; + char name[32]; + int voices, opl_ver; + + voices = (opl3->hardware < OPL3_HW_OPL3) ? + MAX_OPL2_VOICES : MAX_OPL3_VOICES; + opl3->chset = snd_midi_channel_alloc_set(16); + if (opl3->chset == NULL) + return -ENOMEM; + opl3->chset->private_data = opl3; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.owner = THIS_MODULE; + callbacks.use = snd_opl3_synth_use; + callbacks.unuse = snd_opl3_synth_unuse; + callbacks.event_input = snd_opl3_synth_event_input; + callbacks.private_free = snd_opl3_synth_free_port; + callbacks.private_data = opl3; + + opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8; + sprintf(name, "OPL%i FM Port", opl_ver); + + opl3->chset->client = opl3->seq_client; + opl3->chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks, + SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE, + SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_MIDI_GM | + SNDRV_SEQ_PORT_TYPE_SYNTH, + 16, voices, + name); + if (opl3->chset->port < 0) { + snd_midi_channel_free_set(opl3->chset); + return opl3->chset->port; + } + return 0; +} + +/* ------------------------------ */ + +static int snd_opl3_seq_new_device(snd_seq_device_t *dev) +{ + opl3_t *opl3; + int client; + snd_seq_client_callback_t callbacks; + snd_seq_client_info_t cinfo; + int opl_ver; + + opl3 = *(opl3_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (opl3 == NULL) + return -EINVAL; + + spin_lock_init(&opl3->voice_lock); + + opl3->seq_client = -1; + + /* allocate new client */ + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.private_data = opl3; + callbacks.allow_output = callbacks.allow_input = 1; + client = opl3->seq_client = + snd_seq_create_kernel_client(opl3->card, opl3->seq_dev_num, &callbacks); + if (client < 0) + return client; + + /* change name of client */ + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.client = client; + cinfo.type = KERNEL_CLIENT; + opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8; + sprintf(cinfo.name, "OPL%i FM synth", opl_ver); + snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo); + + snd_opl3_synth_create_port(opl3); + + /* 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; + opl3->tlist.data = (unsigned long) opl3; + spin_lock_init(&opl3->sys_timer_lock); + opl3->sys_timer_status = 0; + +#ifdef CONFIG_SND_SEQUENCER_OSS + snd_opl3_init_seq_oss(opl3, cinfo.name); +#endif + return 0; +} + +static int snd_opl3_seq_delete_device(snd_seq_device_t *dev) +{ + opl3_t *opl3; + + opl3 = *(opl3_t **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (opl3 == NULL) + return -EINVAL; + +#ifdef CONFIG_SND_SEQUENCER_OSS + snd_opl3_free_seq_oss(opl3); +#endif + if (opl3->seq_client >= 0) { + snd_seq_delete_kernel_client(opl3->seq_client); + opl3->seq_client = -1; + } + if (opl3->ilist) + snd_seq_instr_list_free(&opl3->ilist); + return 0; +} + +static int __init alsa_opl3_seq_init(void) +{ + static snd_seq_dev_ops_t ops = + { + snd_opl3_seq_new_device, + snd_opl3_seq_delete_device + }; + + return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL3, &ops, + sizeof(opl3_t*)); +} + +static void __exit alsa_opl3_seq_exit(void) +{ + snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL3); +} + +module_init(alsa_opl3_seq_init) +module_exit(alsa_opl3_seq_exit) |