Merge /spare/repo/linux-2.6/
[pandora-kernel.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         kfree(private_data);
144 }
145
146 /*
147  * create a port
148  */
149 static snd_seq_dummy_port_t __init *
150 create_port(int idx, int type)
151 {
152         snd_seq_port_info_t pinfo;
153         snd_seq_port_callback_t pcb;
154         snd_seq_dummy_port_t *rec;
155
156         if ((rec = kcalloc(1, 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         memset(&pcb, 0, sizeof(pcb));
175         pcb.owner = THIS_MODULE;
176         pcb.unuse = dummy_unuse;
177         pcb.event_input = dummy_input;
178         pcb.private_free = dummy_free;
179         pcb.private_data = rec;
180         pinfo.kernel = &pcb;
181         if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
182                 kfree(rec);
183                 return NULL;
184         }
185         rec->port = pinfo.addr.port;
186         return rec;
187 }
188
189 /*
190  * register client and create ports
191  */
192 static int __init
193 register_client(void)
194 {
195         snd_seq_client_callback_t cb;
196         snd_seq_client_info_t cinfo;
197         snd_seq_dummy_port_t *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         memset(&cb, 0, sizeof(cb));
207         cb.allow_input = 1;
208         cb.allow_output = 1;
209         my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, &cb);
210         if (my_client < 0)
211                 return my_client;
212
213         /* set client name */
214         memset(&cinfo, 0, sizeof(cinfo));
215         cinfo.client = my_client;
216         cinfo.type = KERNEL_CLIENT;
217         strcpy(cinfo.name, "Midi Through");
218         snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
219
220         /* create ports */
221         for (i = 0; i < ports; i++) {
222                 rec1 = create_port(i, 0);
223                 if (rec1 == NULL) {
224                         snd_seq_delete_kernel_client(my_client);
225                         return -ENOMEM;
226                 }
227                 if (duplex) {
228                         rec2 = create_port(i, 1);
229                         if (rec2 == NULL) {
230                                 snd_seq_delete_kernel_client(my_client);
231                                 return -ENOMEM;
232                         }
233                         rec1->connect = rec2->port;
234                         rec2->connect = rec1->port;
235                 }
236         }
237
238         return 0;
239 }
240
241 /*
242  * delete client if exists
243  */
244 static void __exit
245 delete_client(void)
246 {
247         if (my_client >= 0)
248                 snd_seq_delete_kernel_client(my_client);
249 }
250
251 /*
252  *  Init part
253  */
254
255 static int __init alsa_seq_dummy_init(void)
256 {
257         int err;
258         snd_seq_autoload_lock();
259         err = register_client();
260         snd_seq_autoload_unlock();
261         return err;
262 }
263
264 static void __exit alsa_seq_dummy_exit(void)
265 {
266         delete_client();
267 }
268
269 module_init(alsa_seq_dummy_init)
270 module_exit(alsa_seq_dummy_exit)