Loading...
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * ALSA sequencer MIDI-through client
4 * Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de>
5 */
6
7#include <linux/init.h>
8#include <linux/slab.h>
9#include <linux/module.h>
10#include <sound/core.h>
11#include "seq_clientmgr.h"
12#include <sound/initval.h>
13#include <sound/asoundef.h>
14
15/*
16
17 Sequencer MIDI-through client
18
19 This gives a simple midi-through client. All the normal input events
20 are redirected to output port immediately.
21 The routing can be done via aconnect program in alsa-utils.
22
23 Each client has a static client number 62 (= SNDRV_SEQ_CLIENT_DUMMY).
24 If you want to auto-load this module, you may add the following alias
25 in your /etc/conf.modules file.
26
27 alias snd-seq-client-62 snd-seq-dummy
28
29 The module is loaded on demand for client 62, or /proc/asound/seq/
30 is accessed. If you don't need this module to be loaded, alias
31 snd-seq-client-62 as "off". This will help modprobe.
32
33 The number of ports to be created can be specified via the module
34 parameter "ports". For example, to create four ports, add the
35 following option in a configuration file under /etc/modprobe.d/:
36
37 option snd-seq-dummy ports=4
38
39 The model option "duplex=1" enables duplex operation to the port.
40 In duplex mode, a pair of ports are created instead of single port,
41 and events are tunneled between pair-ports. For example, input to
42 port A is sent to output port of another port B and vice versa.
43 In duplex mode, each port has DUPLEX capability.
44
45 */
46
47
48MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
49MODULE_DESCRIPTION("ALSA sequencer MIDI-through client");
50MODULE_LICENSE("GPL");
51MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY));
52
53static int ports = 1;
54static bool duplex;
55
56module_param(ports, int, 0444);
57MODULE_PARM_DESC(ports, "number of ports to be created");
58module_param(duplex, bool, 0444);
59MODULE_PARM_DESC(duplex, "create DUPLEX ports");
60
61struct snd_seq_dummy_port {
62 int client;
63 int port;
64 int duplex;
65 int connect;
66};
67
68static int my_client = -1;
69
70/*
71 * event input callback - just redirect events to subscribers
72 */
73static int
74dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
75 int atomic, int hop)
76{
77 struct snd_seq_dummy_port *p;
78 struct snd_seq_event tmpev;
79
80 p = private_data;
81 if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
82 ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
83 return 0; /* ignore system messages */
84 tmpev = *ev;
85 if (p->duplex)
86 tmpev.source.port = p->connect;
87 else
88 tmpev.source.port = p->port;
89 tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
90 return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
91}
92
93/*
94 * free_private callback
95 */
96static void
97dummy_free(void *private_data)
98{
99 kfree(private_data);
100}
101
102/*
103 * create a port
104 */
105static struct snd_seq_dummy_port __init *
106create_port(int idx, int type)
107{
108 struct snd_seq_port_info pinfo;
109 struct snd_seq_port_callback pcb;
110 struct snd_seq_dummy_port *rec;
111
112 if ((rec = kzalloc(sizeof(*rec), GFP_KERNEL)) == NULL)
113 return NULL;
114
115 rec->client = my_client;
116 rec->duplex = duplex;
117 rec->connect = 0;
118 memset(&pinfo, 0, sizeof(pinfo));
119 pinfo.addr.client = my_client;
120 if (duplex)
121 sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
122 (type ? 'B' : 'A'));
123 else
124 sprintf(pinfo.name, "Midi Through Port-%d", idx);
125 pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
126 pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
127 if (duplex)
128 pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
129 pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
130 | SNDRV_SEQ_PORT_TYPE_SOFTWARE
131 | SNDRV_SEQ_PORT_TYPE_PORT;
132 memset(&pcb, 0, sizeof(pcb));
133 pcb.owner = THIS_MODULE;
134 pcb.event_input = dummy_input;
135 pcb.private_free = dummy_free;
136 pcb.private_data = rec;
137 pinfo.kernel = &pcb;
138 if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
139 kfree(rec);
140 return NULL;
141 }
142 rec->port = pinfo.addr.port;
143 return rec;
144}
145
146/*
147 * register client and create ports
148 */
149static int __init
150register_client(void)
151{
152 struct snd_seq_dummy_port *rec1, *rec2;
153 int i;
154
155 if (ports < 1) {
156 pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports);
157 return -EINVAL;
158 }
159
160 /* create client */
161 my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
162 "Midi Through");
163 if (my_client < 0)
164 return my_client;
165
166 /* create ports */
167 for (i = 0; i < ports; i++) {
168 rec1 = create_port(i, 0);
169 if (rec1 == NULL) {
170 snd_seq_delete_kernel_client(my_client);
171 return -ENOMEM;
172 }
173 if (duplex) {
174 rec2 = create_port(i, 1);
175 if (rec2 == NULL) {
176 snd_seq_delete_kernel_client(my_client);
177 return -ENOMEM;
178 }
179 rec1->connect = rec2->port;
180 rec2->connect = rec1->port;
181 }
182 }
183
184 return 0;
185}
186
187/*
188 * delete client if exists
189 */
190static void __exit
191delete_client(void)
192{
193 if (my_client >= 0)
194 snd_seq_delete_kernel_client(my_client);
195}
196
197/*
198 * Init part
199 */
200
201static int __init alsa_seq_dummy_init(void)
202{
203 return register_client();
204}
205
206static void __exit alsa_seq_dummy_exit(void)
207{
208 delete_client();
209}
210
211module_init(alsa_seq_dummy_init)
212module_exit(alsa_seq_dummy_exit)
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 * event input callback - just redirect events to subscribers
86 */
87static int
88dummy_input(struct snd_seq_event *ev, int direct, void *private_data,
89 int atomic, int hop)
90{
91 struct snd_seq_dummy_port *p;
92 struct snd_seq_event tmpev;
93
94 p = private_data;
95 if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM ||
96 ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR)
97 return 0; /* ignore system messages */
98 tmpev = *ev;
99 if (p->duplex)
100 tmpev.source.port = p->connect;
101 else
102 tmpev.source.port = p->port;
103 tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
104 return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop);
105}
106
107/*
108 * free_private callback
109 */
110static void
111dummy_free(void *private_data)
112{
113 kfree(private_data);
114}
115
116/*
117 * create a port
118 */
119static struct snd_seq_dummy_port __init *
120create_port(int idx, int type)
121{
122 struct snd_seq_port_info pinfo;
123 struct snd_seq_port_callback pcb;
124 struct snd_seq_dummy_port *rec;
125
126 if ((rec = kzalloc(sizeof(*rec), GFP_KERNEL)) == NULL)
127 return NULL;
128
129 rec->client = my_client;
130 rec->duplex = duplex;
131 rec->connect = 0;
132 memset(&pinfo, 0, sizeof(pinfo));
133 pinfo.addr.client = my_client;
134 if (duplex)
135 sprintf(pinfo.name, "Midi Through Port-%d:%c", idx,
136 (type ? 'B' : 'A'));
137 else
138 sprintf(pinfo.name, "Midi Through Port-%d", idx);
139 pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ;
140 pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
141 if (duplex)
142 pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX;
143 pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC
144 | SNDRV_SEQ_PORT_TYPE_SOFTWARE
145 | SNDRV_SEQ_PORT_TYPE_PORT;
146 memset(&pcb, 0, sizeof(pcb));
147 pcb.owner = THIS_MODULE;
148 pcb.event_input = dummy_input;
149 pcb.private_free = dummy_free;
150 pcb.private_data = rec;
151 pinfo.kernel = &pcb;
152 if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) {
153 kfree(rec);
154 return NULL;
155 }
156 rec->port = pinfo.addr.port;
157 return rec;
158}
159
160/*
161 * register client and create ports
162 */
163static int __init
164register_client(void)
165{
166 struct snd_seq_dummy_port *rec1, *rec2;
167 int i;
168
169 if (ports < 1) {
170 pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports);
171 return -EINVAL;
172 }
173
174 /* create client */
175 my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY,
176 "Midi Through");
177 if (my_client < 0)
178 return my_client;
179
180 /* create ports */
181 for (i = 0; i < ports; i++) {
182 rec1 = create_port(i, 0);
183 if (rec1 == NULL) {
184 snd_seq_delete_kernel_client(my_client);
185 return -ENOMEM;
186 }
187 if (duplex) {
188 rec2 = create_port(i, 1);
189 if (rec2 == NULL) {
190 snd_seq_delete_kernel_client(my_client);
191 return -ENOMEM;
192 }
193 rec1->connect = rec2->port;
194 rec2->connect = rec1->port;
195 }
196 }
197
198 return 0;
199}
200
201/*
202 * delete client if exists
203 */
204static void __exit
205delete_client(void)
206{
207 if (my_client >= 0)
208 snd_seq_delete_kernel_client(my_client);
209}
210
211/*
212 * Init part
213 */
214
215static int __init alsa_seq_dummy_init(void)
216{
217 return register_client();
218}
219
220static void __exit alsa_seq_dummy_exit(void)
221{
222 delete_client();
223}
224
225module_init(alsa_seq_dummy_init)
226module_exit(alsa_seq_dummy_exit)