Linux-2.6.12-rc2
[linux-flexiantxendom0-natty.git] / sound / core / seq / seq_dummy.c
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 <sound/driver.h>
22 #include <linux/init.h>
23 #include <linux/slab.h>
24 #include <linux/moduleparam.h>
25 #include <sound/core.h>
26 #include "seq_clientmgr.h"
27 #include <sound/initval.h>
28 #include <sound/asoundef.h>
29
30 /*
31
32   Sequencer MIDI-through client
33
34   This gives a simple midi-through client.  All the normal input events
35   are redirected to output port immediately.
36   The routing can be done via aconnect program in alsa-utils.
37
38   Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY).
39   If you want to auto-load this module, you may add the following alias
40   in your /etc/conf.modules file.
41
42         alias snd-seq-client-62  snd-seq-dummy
43
44   The module is loaded on demand for client 62, or /proc/asound/seq/
45   is accessed.  If you don't need this module to be loaded, alias
46   snd-seq-client-62 as "off".  This will help modprobe.
47
48   The number of ports to be created can be specified via the module
49   parameter "ports".  For example, to create four ports, add the
50   following option in /etc/modprobe.conf:
51
52         option snd-seq-dummy ports=4
53
54   The modle option "duplex=1" enables duplex operation to the port.
55   In duplex mode, a pair of ports are created instead of single port,
56   and events are tunneled between pair-ports.  For example, input to
57   port A is sent to output port of another port B and vice versa.
58   In duplex mode, each port has DUPLEX capability.
59
60  */
61
62
63 MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
64 MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
65 MODULE_LICENSE("GPL");
66 MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
67
68 static int ports = 1;
69 static int duplex = 0;
70
71 module_param(ports, int, 0444);
72 MODULE_PARM_DESC(ports, "number of ports to be created");
73 module_param(duplex, bool, 0444);
74 MODULE_PARM_DESC(duplex, "create DUPLEX ports");
75
76 typedef struct snd_seq_dummy_port {
77         int client;
78         int port;
79         int duplex;
80         int connect;
81 } snd_seq_dummy_port_t;
82
83 static int my_client = -1;
84
85 /*
86  * unuse callback - send ALL_SOUNDS_OFF and RESET_CONTROLLERS events
87  * to subscribers.
88  * Note: this callback is called only after all subscribers are removed.
89  */
90 static int
91 dummy_unuse(void *private_data, snd_seq_port_subscribe_t *info)
92 {
93         snd_seq_dummy_port_t *p;
94         int i;
95         snd_seq_event_t ev;
96
97         p = private_data;
98         memset(&ev, 0, sizeof(ev));
99         if (p->duplex)
100                 ev.source.port = p->connect;
101         else
102                 ev.source.port = p->port;
103         ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
104         ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
105         for (i = 0; i < 16; i++) {
106                 ev.data.control.channel = i;
107                 ev.data.control.param = MIDI_CTL_ALL_SOUNDS_OFF;
108                 snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
109                 ev.data.control.param = MIDI_CTL_RESET_CONTROLLERS;
110                 snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
111         }
112         return 0;
113 }
114
115 /*
116  * event input callback - just redirect events to subscribers
117  */
118 static int
119 dummy_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop)
120 {
121         snd_seq_dummy_port_t *p;
122         snd_seq_event_t 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  */
140 static void
141 dummy_free(void *private_data)
142 {
143         snd_seq_dummy_port_t *p;
144
145         p = private_data;
146         kfree(p);
147 }
148
149 /*
150  * create a port
151  */
152 static snd_seq_dummy_port_t __init *
153 create_port(int idx, int type)
154 {
155         snd_seq_port_info_t pinfo;
156         snd_seq_port_callback_t pcb;
157         snd_seq_dummy_port_t *rec;
158
159         if ((rec = kcalloc(1, sizeof(*rec), GFP_KERNEL)) == NULL)
160                 return NULL;
161
162         rec->client = my_client;
163         rec->duplex = duplex;
164         rec->connect = 0;
165         memset(&pinfo, 0, sizeof(pinfo));
166         pinfo.addr.client = my_client;
167         if (duplex)
168                 sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
169                         (type ? 'B' : 'A'));
170         else
171                 sprintf(pinfo.name, "Midi Through Port-%d", idx);
172         pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
173         pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
174         if (duplex)
175                 pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
176         pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
177         memset(&pcb, 0, sizeof(pcb));
178         pcb.owner = THIS_MODULE;
179         pcb.unuse = dummy_unuse;
180         pcb.event_input = dummy_input;
181         pcb.private_free = dummy_free;
182         pcb.private_data = rec;
183         pinfo.kernel = &pcb;
184         if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
185                 kfree(rec);
186                 return NULL;
187         }
188         rec->port = pinfo.addr.port;
189         return rec;
190 }
191
192 /*
193  * register client and create ports
194  */
195 static int __init
196 register_client(void)
197 {
198         snd_seq_client_callback_t cb;
199         snd_seq_client_info_t cinfo;
200         snd_seq_dummy_port_t *rec1, *rec2;
201         int i;
202
203         if (ports < 1) {
204                 snd_printk(KERN_ERR "invalid number of ports %d\n", ports);
205                 return -EINVAL;
206         }
207
208         /* create client */
209         memset(&cb, 0, sizeof(cb));
210         cb.allow_input = 1;
211         cb.allow_output = 1;
212         my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, &cb);
213         if (my_client < 0)
214                 return my_client;
215
216         /* set client name */
217         memset(&cinfo, 0, sizeof(cinfo));
218         cinfo.client = my_client;
219         cinfo.type = KERNEL_CLIENT;
220         strcpy(cinfo.name, "Midi Through");
221         snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
222
223         /* create ports */
224         for (i = 0; i < ports; i++) {
225                 rec1 = create_port(i, 0);
226                 if (rec1 == NULL) {
227                         snd_seq_delete_kernel_client(my_client);
228                         return -ENOMEM;
229                 }
230                 if (duplex) {
231                         rec2 = create_port(i, 1);
232                         if (rec2 == NULL) {
233                                 snd_seq_delete_kernel_client(my_client);
234                                 return -ENOMEM;
235                         }
236                         rec1->connect = rec2->port;
237                         rec2->connect = rec1->port;
238                 }
239         }
240
241         return 0;
242 }
243
244 /*
245  * delete client if exists
246  */
247 static void __exit
248 delete_client(void)
249 {
250         if (my_client >= 0)
251                 snd_seq_delete_kernel_client(my_client);
252 }
253
254 /*
255  *  Init part
256  */
257
258 static int __init alsa_seq_dummy_init(void)
259 {
260         int err;
261         snd_seq_autoload_lock();
262         err = register_client();
263         snd_seq_autoload_unlock();
264         return err;
265 }
266
267 static void __exit alsa_seq_dummy_exit(void)
268 {
269         delete_client();
270 }
271
272 module_init(alsa_seq_dummy_init)
273 module_exit(alsa_seq_dummy_exit)