linux/sound/core/seq/seq_dummy.c
<<
>>
Prefs
   1/*
   2 * ALSA sequencer MIDI-through client
   3 * Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de>
   4 *
   5 *   This program is free software; you can redistribute it and/or modify
   6 *   it under the terms of the GNU General Public License as published by
   7 *   the Free Software Foundation; either version 2 of the License, or
   8 *   (at your option) any later version.
   9 *
  10 *   This program is distributed in the hope that it will be useful,
  11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 *   GNU General Public License for more details.
  14 *
  15 *   You should have received a copy of the GNU General Public License
  16 *   along with this program; if not, write to the Free Software
  17 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  18 *
  19 */
  20
  21#include <linux/init.h>
  22#include <linux/slab.h>
  23#include <linux/module.h>
  24#include <sound/core.h>
  25#include "seq_clientmgr.h"
  26#include <sound/initval.h>
  27#include <sound/asoundef.h>
  28
  29/*
  30
  31  Sequencer MIDI-through client
  32
  33  This gives a simple midi-through client.  All the normal input events
  34  are redirected to output port immediately.
  35  The routing can be done via aconnect program in alsa-utils.
  36
  37  Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY).
  38  If you want to auto-load this module, you may add the following alias
  39  in your /etc/conf.modules file.
  40
  41        alias snd-seq-client-62  snd-seq-dummy
  42
  43  The module is loaded on demand for client 62, or /proc/asound/seq/
  44  is accessed.  If you don't need this module to be loaded, alias
  45  snd-seq-client-62 as "off".  This will help modprobe.
  46
  47  The number of ports to be created can be specified via the module
  48  parameter "ports".  For example, to create four ports, add the
  49  following option in a configuration file under /etc/modprobe.d/:
  50
  51        option snd-seq-dummy ports=4
  52
  53  The model option "duplex=1" enables duplex operation to the port.
  54  In duplex mode, a pair of ports are created instead of single port,
  55  and events are tunneled between pair-ports.  For example, input to
  56  port A is sent to output port of another port B and vice versa.
  57  In duplex mode, each port has DUPLEX capability.
  58
  59 */
  60
  61
  62MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
  63MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
  64MODULE_LICENSE("GPL");
  65MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
  66
  67static int ports = 1;
  68static bool duplex;
  69
  70module_param(ports, int, 0444);
  71MODULE_PARM_DESC(ports, "number of ports to be created");
  72module_param(duplex, bool, 0444);
  73MODULE_PARM_DESC(duplex, "create DUPLEX ports");
  74
  75struct snd_seq_dummy_port {
  76        int client;
  77        int port;
  78        int duplex;
  79        int connect;
  80};
  81
  82static int my_client = -1;
  83
  84/*
  85 * unuse callback - send ALL_SOUNDS_OFF and RESET_CONTROLLERS events
  86 * to subscribers.
  87 * Note: this callback is called only after all subscribers are removed.
  88 */
  89static int
  90dummy_unuse(void *private_data, struct snd_seq_port_subscribe *info)
  91{
  92        struct snd_seq_dummy_port *p;
  93        int i;
  94        struct snd_seq_event ev;
  95
  96        p = private_data;
  97        memset(&ev, 0, sizeof(ev));
  98        if (p->duplex)
  99                ev.source.port = p->connect;
 100        else
 101                ev.source.port = p->port;
 102        ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
 103        ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
 104        for (i = 0; i < 16; i++) {
 105                ev.data.control.channel = i;
 106                ev.data.control.param = MIDI_CTL_ALL_SOUNDS_OFF;
 107                snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
 108                ev.data.control.param = MIDI_CTL_RESET_CONTROLLERS;
 109                snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
 110        }
 111        return 0;
 112}
 113
 114/*
 115 * event input callback - just redirect events to subscribers
 116 */
 117static int
 118dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
 119            int atomic, int hop)
 120{
 121        struct snd_seq_dummy_port *p;
 122        struct snd_seq_event tmpev;
 123
 124        p = private_data;
 125        if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
 126            ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
 127                return 0; /* ignore system messages */
 128        tmpev = *ev;
 129        if (p->duplex)
 130                tmpev.source.port = p->connect;
 131        else
 132                tmpev.source.port = p->port;
 133        tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
 134        return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
 135}
 136
 137/*
 138 * free_private callback
 139 */
 140static void
 141dummy_free(void *private_data)
 142{
 143        kfree(private_data);
 144}
 145
 146/*
 147 * create a port
 148 */
 149static struct snd_seq_dummy_port __init *
 150create_port(int idx, int type)
 151{
 152        struct snd_seq_port_info pinfo;
 153        struct snd_seq_port_callback pcb;
 154        struct snd_seq_dummy_port *rec;
 155
 156        if ((rec = kzalloc(sizeof(*rec), GFP_KERNEL)) == NULL)
 157                return NULL;
 158
 159        rec->client = my_client;
 160        rec->duplex = duplex;
 161        rec->connect = 0;
 162        memset(&pinfo, 0, sizeof(pinfo));
 163        pinfo.addr.client = my_client;
 164        if (duplex)
 165                sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
 166                        (type ? 'B' : 'A'));
 167        else
 168                sprintf(pinfo.name, "Midi Through Port-%d", idx);
 169        pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
 170        pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
 171        if (duplex)
 172                pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
 173        pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
 174                | SNDRV_SEQ_PORT_TYPE_SOFTWARE
 175                | SNDRV_SEQ_PORT_TYPE_PORT;
 176        memset(&pcb, 0, sizeof(pcb));
 177        pcb.owner = THIS_MODULE;
 178        pcb.unuse = dummy_unuse;
 179        pcb.event_input = dummy_input;
 180        pcb.private_free = dummy_free;
 181        pcb.private_data = rec;
 182        pinfo.kernel = &pcb;
 183        if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
 184                kfree(rec);
 185                return NULL;
 186        }
 187        rec->port = pinfo.addr.port;
 188        return rec;
 189}
 190
 191/*
 192 * register client and create ports
 193 */
 194static int __init
 195register_client(void)
 196{
 197        struct snd_seq_dummy_port *rec1, *rec2;
 198        int i;
 199
 200        if (ports < 1) {
 201                snd_printk(KERN_ERR "invalid number of ports %d\n", ports);
 202                return -EINVAL;
 203        }
 204
 205        /* create client */
 206        my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
 207                                                 "Midi Through");
 208        if (my_client < 0)
 209                return my_client;
 210
 211        /* create ports */
 212        for (i = 0; i < ports; i++) {
 213                rec1 = create_port(i, 0);
 214                if (rec1 == NULL) {
 215                        snd_seq_delete_kernel_client(my_client);
 216                        return -ENOMEM;
 217                }
 218                if (duplex) {
 219                        rec2 = create_port(i, 1);
 220                        if (rec2 == NULL) {
 221                                snd_seq_delete_kernel_client(my_client);
 222                                return -ENOMEM;
 223                        }
 224                        rec1->connect = rec2->port;
 225                        rec2->connect = rec1->port;
 226                }
 227        }
 228
 229        return 0;
 230}
 231
 232/*
 233 * delete client if exists
 234 */
 235static void __exit
 236delete_client(void)
 237{
 238        if (my_client >= 0)
 239                snd_seq_delete_kernel_client(my_client);
 240}
 241
 242/*
 243 *  Init part
 244 */
 245
 246static int __init alsa_seq_dummy_init(void)
 247{
 248        int err;
 249        snd_seq_autoload_lock();
 250        err = register_client();
 251        snd_seq_autoload_unlock();
 252        return err;
 253}
 254
 255static void __exit alsa_seq_dummy_exit(void)
 256{
 257        delete_client();
 258}
 259
 260module_init(alsa_seq_dummy_init)
 261module_exit(alsa_seq_dummy_exit)
 262