patch-2_6_7-vs1_9_1_12
[linux-2.6.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_CLASSES("{sound}");
67 MODULE_SUPPORTED_DEVICE("sound");
68
69 static int ports = 1;
70 static int duplex = 0;
71
72 module_param(ports, int, 0444);
73 MODULE_PARM_DESC(ports, "number of ports to be created");
74 module_param(duplex, bool, 0444);
75 MODULE_PARM_DESC(duplex, "create DUPLEX ports");
76
77 typedef struct snd_seq_dummy_port {
78         int client;
79         int port;
80         int duplex;
81         int connect;
82 } snd_seq_dummy_port_t;
83
84 static int my_client = -1;
85
86 /*
87  * unuse callback - send ALL_SOUNDS_OFF and RESET_CONTROLLERS events
88  * to subscribers.
89  * Note: this callback is called only after all subscribers are removed.
90  */
91 static int
92 dummy_unuse(void *private_data, snd_seq_port_subscribe_t *info)
93 {
94         snd_seq_dummy_port_t *p;
95         int i;
96         snd_seq_event_t ev;
97
98         p = snd_magic_cast(snd_seq_dummy_port_t, private_data, return -EINVAL);
99         memset(&ev, 0, sizeof(ev));
100         if (p->duplex)
101                 ev.source.port = p->connect;
102         else
103                 ev.source.port = p->port;
104         ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
105         ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
106         for (i = 0; i < 16; i++) {
107                 ev.data.control.channel = i;
108                 ev.data.control.param = MIDI_CTL_ALL_SOUNDS_OFF;
109                 snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
110                 ev.data.control.param = MIDI_CTL_RESET_CONTROLLERS;
111                 snd_seq_kernel_client_dispatch(p->client, &ev, 0, 0);
112         }
113         return 0;
114 }
115
116 /*
117  * event input callback - just redirect events to subscribers
118  */
119 static int
120 dummy_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop)
121 {
122         snd_seq_dummy_port_t *p;
123         snd_seq_event_t tmpev;
124
125         p = snd_magic_cast(snd_seq_dummy_port_t, private_data, return -EINVAL);
126         if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
127             ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
128                 return 0; /* ignore system messages */
129         /* save the original sender */
130         tmpev.type = SNDRV_SEQ_EVENT_KERNEL_QUOTE;
131         tmpev.flags = (ev->flags & ~SNDRV_SEQ_EVENT_LENGTH_MASK)
132                 | SNDRV_SEQ_EVENT_LENGTH_FIXED;
133         tmpev.tag = ev->tag;
134         tmpev.time = ev->time;
135         tmpev.data.quote.origin = ev->source;
136         tmpev.data.quote.event = ev;
137         if (p->duplex)
138                 tmpev.source.port = p->connect;
139         else
140                 tmpev.source.port = p->port;
141         tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
142         return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
143 }
144
145 /*
146  * free_private callback
147  */
148 static void
149 dummy_free(void *private_data)
150 {
151         snd_seq_dummy_port_t *p;
152
153         p = snd_magic_cast(snd_seq_dummy_port_t, private_data, return);
154         snd_magic_kfree(p);
155 }
156
157 /*
158  * create a port
159  */
160 static snd_seq_dummy_port_t __init *
161 create_port(int idx, int type)
162 {
163         snd_seq_port_info_t pinfo;
164         snd_seq_port_callback_t pcb;
165         snd_seq_dummy_port_t *rec;
166
167         if ((rec = snd_magic_kcalloc(snd_seq_dummy_port_t, 0, GFP_KERNEL)) == NULL)
168                 return NULL;
169
170         rec->client = my_client;
171         rec->duplex = duplex;
172         rec->connect = 0;
173         memset(&pinfo, 0, sizeof(pinfo));
174         pinfo.addr.client = my_client;
175         if (duplex)
176                 sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
177                         (type ? 'B' : 'A'));
178         else
179                 sprintf(pinfo.name, "Midi Through Port-%d", idx);
180         pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
181         pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
182         if (duplex)
183                 pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
184         pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
185         memset(&pcb, 0, sizeof(pcb));
186         pcb.owner = THIS_MODULE;
187         pcb.unuse = dummy_unuse;
188         pcb.event_input = dummy_input;
189         pcb.private_free = dummy_free;
190         pcb.private_data = rec;
191         pinfo.kernel = &pcb;
192         if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
193                 snd_magic_kfree(rec);
194                 return NULL;
195         }
196         rec->port = pinfo.addr.port;
197         return rec;
198 }
199
200 /*
201  * register client and create ports
202  */
203 static int __init
204 register_client(void)
205 {
206         snd_seq_client_callback_t cb;
207         snd_seq_client_info_t cinfo;
208         snd_seq_dummy_port_t *rec1, *rec2;
209         int i;
210
211         if (ports < 1) {
212                 snd_printk(KERN_ERR "invalid number of ports %d\n", ports);
213                 return -EINVAL;
214         }
215
216         /* create client */
217         memset(&cb, 0, sizeof(cb));
218         cb.allow_input = 1;
219         cb.allow_output = 1;
220         my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, &cb);
221         if (my_client < 0)
222                 return my_client;
223
224         /* set client name */
225         memset(&cinfo, 0, sizeof(cinfo));
226         cinfo.client = my_client;
227         cinfo.type = KERNEL_CLIENT;
228         strcpy(cinfo.name, "Midi Through");
229         snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
230
231         /* create ports */
232         for (i = 0; i < ports; i++) {
233                 rec1 = create_port(i, 0);
234                 if (rec1 == NULL) {
235                         snd_seq_delete_kernel_client(my_client);
236                         return -ENOMEM;
237                 }
238                 if (duplex) {
239                         rec2 = create_port(i, 1);
240                         if (rec2 == NULL) {
241                                 snd_seq_delete_kernel_client(my_client);
242                                 return -ENOMEM;
243                         }
244                         rec1->connect = rec2->port;
245                         rec2->connect = rec1->port;
246                 }
247         }
248
249         return 0;
250 }
251
252 /*
253  * delete client if exists
254  */
255 static void __exit
256 delete_client(void)
257 {
258         if (my_client >= 0)
259                 snd_seq_delete_kernel_client(my_client);
260 }
261
262 /*
263  *  Init part
264  */
265
266 static int __init alsa_seq_dummy_init(void)
267 {
268         return register_client();
269 }
270
271 static void __exit alsa_seq_dummy_exit(void)
272 {
273         delete_client();
274 }
275
276 module_init(alsa_seq_dummy_init)
277 module_exit(alsa_seq_dummy_exit)