1
2 /*
3 # Sfront, a SAOL to C translator
4 # This file: ALSA Sequencer Control driver for Sfront.
5 #
6 # This driver was originaly based on the alsamidi driver, but has
7 # been almost completely rewritten.
8 #
9 # Copyright (C) 2001 Enrique Robledo Arnuncio
10 #
11 # Redistribution and use in source and binary forms, with or without
12 # modification, are permitted provided that the following conditions are
13 # met:
14 #
15 # Redistributions of source code must retain the above copyright
16 # notice, this list of conditions and the following disclaimer.
17 #
18 # Redistributions in binary form must reproduce the above copyright
19 # notice, this list of conditions and the following disclaimer in the
20 # documentation and/or other materials provided with the distribution.
21 #
22 # Neither the name of the University of California, Berkeley nor the
23 # names of its contributors may be used to endorse or promote products
24 # derived from this software without specific prior written permission.
25 #
26 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 #
38 # Maintainer: John Lazzaro, lazzaro@cs.berkeley.edu
39 */
40
41
42 #include <sys/asoundlib.h>
43
44 /****************************************************************/
45 /****************************************************************/
46 /* Linux ALSA /dev/snd/seq control driver for sfront */
47 /* See the alsaseq README file included in the sfman for usage */
48 /* tips and some configuration issues. */
49 /* Specific command line options: */
50 /* */
51 /* -csys_alsaseq_subscribe client:port [client:port ...] */
52 /* */
53 /****************************************************************/
54
55 #undef CSYSI_DEBUG
56
57 /* Configuration for multiple connections */
58 #define CSYSI_MAX_DEVS 32
59 #define CSYSI_MAP2_BITS 6
60 #define CSYSI_MASK1 0xFFC0
61
62 /* Driver info */
63 snd_seq_t *csysi_handle;
64 int csysi_port;
65 unsigned short csysi_self_addr;
66
67 /* ALSA address <-> channel device number associative tables interface */
68 void csysi_init_maps();
69 int csysi_new_ext_channel_device(unsigned short alsa_addr);
70 int csysi_read_ext_channel_device(unsigned short alsa_addr);
71 void csysi_remove_ext_channel_device(unsigned short alsa_addr);
72
73
74 /****************************************************************/
75 /* initialization routine for control */
76 /****************************************************************/
77
csysi_parse_address(const char * str)78 int csysi_parse_address(const char* str)
79 {
80 const char *port = NULL;
81 int c,p;
82 const char* pt;
83
84 if (str==NULL)
85 return -1;
86 for (pt=str; *pt!=0; pt++) {
87 if (*pt == ':' || *pt == '.')
88 if (port==NULL) port = pt+1;
89 else return -1;
90 else
91 if (!isdigit(*pt))
92 return -1;
93 }
94 if (port == NULL)
95 return -1;
96 c = atoi(str);
97 p = atoi(port);
98 if (c<0 || c>255 || p<0 || p>255)
99 return -1;
100 return c<<8|p;
101 }
102
csys_setup(void)103 int csys_setup(void)
104 {
105 int res,arg,addr;
106
107 /* Driver connection */
108 if ((res = snd_seq_open(&csysi_handle, SND_SEQ_OPEN_IN)) < 0) {
109 fprintf(stderr, "ALSASEQ: Open failed: %s\n", snd_strerror(res));
110 return CSYS_ERROR;
111 }
112 if ((res = snd_seq_set_client_name(csysi_handle,
113 "Sfront SA Synthesizer")) < 0) {
114 fprintf(stderr, "ALSASEQ: Could not set name: %s\n",
115 snd_strerror(res));
116 snd_seq_close(csysi_handle);
117 return CSYS_ERROR;
118 }
119 if ((res = snd_seq_create_simple_port(csysi_handle,
120 "MIDI input",
121 SND_SEQ_PORT_CAP_WRITE |
122 SND_SEQ_PORT_CAP_SUBS_WRITE,
123 SND_SEQ_PORT_TYPE_SYNTH)) < 0) {
124 fprintf(stderr, "ALSASEQ: Port creation failed: %s\n",
125 snd_strerror(res));
126 snd_seq_close(csysi_handle);
127 return CSYS_ERROR;
128 }
129 csysi_port = res;
130 csysi_self_addr = snd_seq_client_id(csysi_handle) << 8 + res;
131
132 /* Extended channel mapping tables initialization */
133 csysi_init_maps();
134
135 /* Command line processing */
136 for (arg=1; arg<EV(csys_argc); arg++) {
137 if (strcmp(EV(csys_argv[arg]),"-csys_alsaseq_subscribe"))
138 continue;
139 while ((arg+1)<EV(csys_argc) && EV(csys_argv[arg+1])[0] != '-') {
140 arg++;
141 if ((addr = csysi_parse_address(EV(csys_argv[arg]))) < 0) {
142 fprintf(stderr,"ALSASEQ: Warning: Invalid ALSA address: %s\n\n",
143 EV(csys_argv[arg]));
144 continue;
145 }
146 if (csysi_new_ext_channel_device(addr) < 0) {
147 fprintf(stderr,"ALSASEQ: Warning: Ignored subscription to %s:\n"
148 " Too many extended channel devices in use.\n\n",
149 EV(csys_argv[arg]));
150 continue;
151 }
152 if ((res = snd_seq_connect_from(csysi_handle,
153 csysi_port,
154 (addr&0xFF00)>>8,
155 addr&0x00FF)) < 0) {
156 csysi_remove_ext_channel_device(addr);
157 fprintf(stderr,
158 "ALSASEQ: Warning: Could not connect"
159 "to the given address: %s\n\n",
160 EV(csys_argv[arg]));
161 }
162 }
163 }
164 return CSYS_DONE;
165 }
166
167 /****************************************************************/
168 /* polling routine for new data */
169 /****************************************************************/
170
csys_newdata(void)171 int csys_newdata(void)
172
173 {
174 int len;
175
176 len = snd_seq_event_input_pending(csysi_handle,1);
177 if (len <= 0)
178 return CSYS_NONE;
179 return CSYS_MIDIEVENTS;
180 }
181
182
183 /****************************************************************/
184 /* processes a MIDI event */
185 /****************************************************************/
186
csys_midievent(unsigned char * cmd,unsigned char * ndata,unsigned char * vdata,unsigned short * extchan,float * fval)187 int csys_midievent(unsigned char * cmd,
188 unsigned char * ndata,
189 unsigned char * vdata,
190 unsigned short * extchan,
191 float * fval)
192
193 {
194 snd_seq_event_t *event;
195 int len,res,device;
196 unsigned short channel;
197 unsigned short alsa_address;
198
199 res = snd_seq_event_input(csysi_handle,&event);
200 if (res<0) {
201 *cmd = CSYS_MIDI_NOOP;
202 return CSYS_DONE;
203 }
204
205 /* First, we check where does the event come from */
206 alsa_address = event->source.client << 8 + event->source.port;
207
208 if (alsa_address == csysi_self_addr) {
209 switch (event->type) {
210 case SND_SEQ_EVENT_PORT_USED:
211 alsa_address = event->data.addr.client << 8 +
212 event->data.addr.port;
213 device = csysi_new_ext_channel_device(alsa_address);
214 #ifdef CSYSI_DEBUG
215 if (device < 0)
216 fprintf(stderr,"ALSASEQ: Could not map %d:%d:"
217 " no extended devices left\n",
218 event->data.addr.client,
219 event->data.addr.port);
220 else
221 fprintf(stderr,"ALSASEQ: %d:%d mapped to extended device %d\n",
222 event->data.addr.client,
223 event->data.addr.port,
224 device);
225 #endif
226 break;
227 }
228 *cmd = CSYS_MIDI_NOOP;
229 if ( snd_seq_event_input_pending(csysi_handle,1) <= 0)
230 return CSYS_NONE;
231 return CSYS_EVENTS;
232 }
233
234 device = csysi_read_ext_channel_device(alsa_address);
235 if (device<0) { /* Unregistered source. Should not happen. */
236 *cmd = CSYS_MIDI_NOOP;
237 if ( snd_seq_event_input_pending(csysi_handle,1) <= 0)
238 return CSYS_NONE;
239 return CSYS_EVENTS;
240 }
241
242 switch (event->type) {
243 case SND_SEQ_EVENT_NOTEON:
244 *cmd = CSYS_MIDI_NOTEON;
245 *ndata = event->data.note.note;
246 *vdata = event->data.note.velocity;
247 channel = event->data.note.channel;
248 break;
249 case SND_SEQ_EVENT_NOTEOFF:
250 *cmd = CSYS_MIDI_NOTEOFF;
251 *ndata = event->data.note.note;
252 channel = event->data.note.channel;
253 break;
254 case SND_SEQ_EVENT_KEYPRESS: /* Not tested */
255 *cmd = CSYS_MIDI_PTOUCH;
256 *ndata = event->data.note.note;
257 *vdata = event->data.note.velocity;
258 channel = event->data.note.channel;
259 break;
260 case SND_SEQ_EVENT_CONTROLLER:
261 *cmd = CSYS_MIDI_CC;
262 *ndata = event->data.control.param;
263 *vdata = event->data.control.value;
264 channel = event->data.control.channel;
265 break;
266 case SND_SEQ_EVENT_PGMCHANGE:
267 *cmd = CSYS_MIDI_PROGRAM;
268 *ndata = event->data.control.value;
269 channel = event->data.control.channel;
270 break;
271 case SND_SEQ_EVENT_CHANPRESS:
272 *cmd = CSYS_MIDI_CTOUCH;
273 *ndata = event->data.control.value;
274 channel = event->data.control.channel;
275 break;
276 case SND_SEQ_EVENT_PITCHBEND:
277 *cmd = CSYS_MIDI_WHEEL;
278 *ndata = event->data.control.value & 0x007F;
279 *vdata = (event->data.control.value & (0x007F << 7) ) >> 7;
280 channel = event->data.control.channel;
281 break;
282 case SND_SEQ_EVENT_TEMPO: /* ??? Not tested. */
283 *cmd = CSYS_MIDI_NEWTEMPO;
284 *fval = (float)event->data.queue.param.value;
285 break;
286 case SND_SEQ_EVENT_PORT_UNUSED:
287 csysi_remove_ext_channel_device(alsa_address);
288 #ifdef CSYSI_DEBUG
289 fprintf(stderr,"ALSASEQ: %d:%d disconected from device %d\n",
290 event->data.addr.client,
291 event->data.addr.port,
292 device);
293 #endif
294 break;
295 /* No MIDI_ENDTIME event in alsa? */
296 default:
297 *cmd = CSYS_MIDI_NOOP;
298 }
299
300 *extchan = device * 16 + channel;
301
302 len = snd_seq_event_input_pending(csysi_handle,1);
303 if (len <= 0)
304 return CSYS_NONE;
305 return CSYS_EVENTS;
306 }
307
308 /****************************************************************/
309 /* closing routine for control */
310 /****************************************************************/
311
csys_shutdown(void)312 void csys_shutdown(void)
313 {
314 snd_seq_close(csysi_handle);
315 }
316
317
318 /****************************************************************/
319 /* ALSA address to extended channel device mapping */
320 /****************************************************************/
321
322
323 /* New alsa subscriptions to our port are asigned an "extended device
324 * number" (dev). Extended channels will be calculated as:
325 * extchan = dev*16 + MIDI_channel
326 * The structures and functions below do the mapping between alsa
327 * addresses and extended device number, using an asociative table
328 * mechanism. A single level of indirection would require a 64Kb
329 * table, so we use two levels of indirection.
330 */
331
332 #define CSYSI_MASK2 ~CSYSI_MASK1
333 #define CSYSI_SIZE1 ( 1 << (16-CSYSI_MAP2_BITS) )
334 #define CSYSI_SIZE2 ( 1 << CSYSI_MAP2_BITS )
335
336
337 unsigned char csysi_is_used_dev[CSYSI_MAX_DEVS];
338 unsigned char csysi_available_dev;
339 unsigned short csysi_used_devs;
340 unsigned char csysi_map1[CSYSI_SIZE1];
341 unsigned char csysi_map2[CSYSI_MAX_DEVS][CSYSI_SIZE2];
342 unsigned char csysi_map2_counts[CSYSI_MAX_DEVS];
343 unsigned char csysi_available_page;
344
345
346 /* Initialize Address mapping structures */
csysi_init_maps()347 void csysi_init_maps()
348 {
349 int i,j;
350
351 for (i=0; i<CSYSI_SIZE1; i++)
352 csysi_map1[i]=0xFF;
353 for (i=0; i<CSYSI_MAX_DEVS; i++) {
354 csysi_is_used_dev[i]=0;
355 csysi_map2_counts[i]=0;
356 for (j=0; j<CSYSI_SIZE2; j++)
357 csysi_map2[i][j]=0xFF;
358 }
359 csysi_used_devs = 0;
360 csysi_available_dev = 0;
361 csysi_available_page = 0;
362 }
363
364 /* Get extended channel device number for an alsa address.
365 * Returns the extended channel number, or -1 if the address was not
366 * mapped. */
csysi_read_ext_channel_device(unsigned short addr)367 int csysi_read_ext_channel_device(unsigned short addr)
368 {
369 unsigned short idx1,idx2_1,idx2_2;
370
371 idx1 = ( addr & CSYSI_MASK1 ) >> CSYSI_MAP2_BITS;
372 idx2_1 = csysi_map1[idx1];
373 idx2_2 = ( addr & CSYSI_MASK2 );
374 if ( idx2_1 > CSYSI_MAX_DEVS-1 ||
375 csysi_map2[idx2_1][idx2_2] > CSYSI_MAX_DEVS-1 )
376 return -1;
377 return csysi_map2[idx2_1][idx2_2];
378 }
379
380 /* Add an ALSA address to the asociative tables. Returns <0 if there
381 * was no space left for the new address. Otherwhise, the address is
382 * mapped to the first available device number, which is returned.
383 * NOTE: In the worst case, the loops used to update the "available
384 * page" and the "available device" pointers can have a length of
385 * almost CSYSI_MAX_DEVS. This would happen in a system with most of
386 * the available pages/devices used, where a page/device near the
387 * begginig is freed, and then a new page/device is requested.
388 * Hopefuly this can be asumed to be infrecuent enough, so we can
389 * avoid using a more complicated mechanism to track free
390 * pages/devices */
csysi_new_ext_channel_device(unsigned short addr)391 int csysi_new_ext_channel_device(unsigned short addr)
392 {
393 unsigned short idx1,idx2_1,idx2_2,dev;
394
395 if (csysi_used_devs > CSYSI_MAX_DEVS-1)
396 return -1;
397
398 idx1 = ( addr & CSYSI_MASK1 ) >> CSYSI_MAP2_BITS;
399 idx2_1 = csysi_map1[idx1];
400 idx2_2 = ( addr & CSYSI_MASK2 );
401 if ( idx2_1 < CSYSI_MAX_DEVS &&
402 csysi_map2[idx2_1][idx2_2] < CSYSI_MAX_DEVS )
403 return csysi_map2[idx2_1][idx2_2];
404
405 if (idx2_1 > CSYSI_MAX_DEVS-1) {
406 idx2_1 = csysi_map1[idx1] = csysi_available_page;
407 while (csysi_map2_counts[++csysi_available_page])
408 if (csysi_available_page==CSYSI_MAX_DEVS)
409 break;
410 }
411
412 dev = csysi_map2[idx2_1][idx2_2] = csysi_available_dev;
413 csysi_used_devs++;
414 csysi_map2_counts[idx2_1]++;
415 csysi_is_used_dev[csysi_available_dev] = 1;
416 while (csysi_is_used_dev[++csysi_available_dev])
417 if (csysi_available_dev==CSYSI_MAX_DEVS)
418 break;
419
420 return dev;
421 }
422
423 /* Removes an ALSA address to the asociative tables. */
csysi_remove_ext_channel_device(unsigned short addr)424 void csysi_remove_ext_channel_device(unsigned short addr)
425 {
426 unsigned short idx1,idx2_1,idx2_2,dev;
427
428 idx1 = ( addr & CSYSI_MASK1 ) >> CSYSI_MAP2_BITS;
429 idx2_1 = csysi_map1[idx1];
430 idx2_2 = ( addr & CSYSI_MASK2 );
431 if ( idx2_1 > CSYSI_MAX_DEVS-1 ||
432 csysi_map2[idx2_1][idx2_2] > CSYSI_MAX_DEVS-1 ||
433 !csysi_map2_counts[idx2_1] ||
434 !csysi_used_devs )
435 return;
436
437 dev = csysi_map2[idx2_1][idx2_2];
438 csysi_map2[idx2_1][idx2_2] = 0xFF;
439 csysi_is_used_dev[dev]=0;
440 csysi_used_devs--;
441 if (dev < csysi_available_dev)
442 csysi_available_dev = dev;
443
444 if (--csysi_map2_counts[idx2_1]==0) {
445 csysi_map1[idx1] = 0xFF;
446 if (idx2_1 < csysi_available_page)
447 csysi_available_page = idx2_1;
448 }
449 }
450