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