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