/ source / audiolib / src / driver_alsa.cpp
driver_alsa.cpp
  1  /*
  2   Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
  3  
  4   This program is free software; you can redistribute it and/or
  5   modify it under the terms of the GNU General Public License
  6   as published by the Free Software Foundation; either version 2
  7   of the License, or (at your option) any later version.
  8  
  9   This program is distributed in the hope that it will be useful,
 10   but WITHOUT ANY WARRANTY; without even the implied warranty of
 11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 12  
 13   See the 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  /**
 22   * ALSA MIDI output
 23   */
 24  
 25  #include "driver_alsa.h"
 26  
 27  #include "midi.h"
 28  #include "multivoc.h"
 29  
 30  #include <alsa/asoundlib.h>
 31  #include <math.h>
 32  #include <pthread.h>
 33  #include <unistd.h>
 34  
 35  enum
 36  {
 37      ALSAErr_Warning = -2,
 38      ALSAErr_Error   = -1,
 39      ALSAErr_Ok      = 0,
 40      ALSAErr_Uninitialised,
 41      ALSAErr_SeqOpen,
 42      ALSAErr_CreateSimplePort,
 43      ALSAErr_AllocQueue,
 44      ALSAErr_ConnectTo,
 45      ALSAErr_DeviceNotFound,
 46      ALSAErr_StartQueue,
 47      ALSAErr_StopQueue,
 48      ALSAErr_PlayThread
 49  };
 50  
 51  int ALSA_ClientID = 0;
 52  int ALSA_PortID   = 0;
 53  
 54  static int ErrorCode = ALSAErr_Ok;
 55  
 56  static snd_seq_t *seq;
 57  static int        seq_port  = -1;
 58  static int        seq_queue = -1;
 59  static int        queueRunning;
 60  
 61  static pthread_t thread;
 62  static int       threadRunning;
 63  static int       threadQuit;
 64  static void (*threadService)(void);
 65  
 66  static unsigned int threadTimer;
 67  static unsigned int threadQueueTimer;
 68  static int          threadQueueTicks;
 69  #define THREAD_QUEUE_INTERVAL 20    // 1/20 sec
 70  
 71  int ALSADrv_GetError(void) { return ErrorCode; }
 72  
 73  const char *ALSADrv_ErrorString(int ErrorNumber)
 74  {
 75      const char *ErrorString;
 76  
 77      switch (ErrorNumber)
 78      {
 79          case ALSAErr_Warning:
 80          case ALSAErr_Error:
 81              ErrorString = ALSADrv_ErrorString(ErrorCode);
 82              break;
 83  
 84          case ALSAErr_Ok:
 85              ErrorString = "ALSA ok.";
 86              break;
 87  
 88          case ALSAErr_Uninitialised:
 89              ErrorString = "ALSA uninitialised.";
 90              break;
 91  
 92          case ALSAErr_SeqOpen:
 93              ErrorString = "ALSA error: failed in snd_seq_open.\n";
 94              break;
 95  
 96          case ALSAErr_CreateSimplePort:
 97              ErrorString = "ALSA error: failed in snd_seq_create_simple_port.\n";
 98              break;
 99  
100          case ALSAErr_AllocQueue:
101              ErrorString = "ALSA error: failed in snd_seq_alloc_queue.\n";
102              break;
103  
104          case ALSAErr_ConnectTo:
105              ErrorString = "ALSA error: failed in snd_seq_connect_to.\n";
106              break;
107  
108          case ALSAErr_DeviceNotFound:
109              ErrorString = "ALSA error: device not found.\n";
110              break;
111  
112          default:
113              ErrorString = "Unknown ALSA error.";
114              break;
115      }
116  
117      return ErrorString;
118  }
119  
120  static inline void sequence_event(snd_seq_event_t *ev, bool sync_output_queue)
121  {
122      int result = 0;
123  
124      snd_seq_ev_set_subs(ev);
125      snd_seq_ev_set_source(ev, seq_port);
126      snd_seq_ev_schedule_tick(ev, seq_queue, 0, threadTimer);
127      result = snd_seq_event_output(seq, ev);
128  
129      if (result < 0)
130          LOG_F(ERROR, "Unable to queue ALSA event: snd_seq_event_output error %d", result);
131      else
132      {
133          do { } while ((result = snd_seq_drain_output(seq)) > 0);
134  
135          if (result < 0)
136              LOG_F(ERROR, "Unable to drain ALSA output: snd_seq_drain_output error %d", result);
137  
138          if (sync_output_queue)
139              snd_seq_sync_output_queue(seq);
140      }
141  }
142  
143  static void Func_NoteOff(int channel, int key, int velocity)
144  {
145      snd_seq_event_t ev;
146      snd_seq_ev_clear(&ev);
147      snd_seq_ev_set_noteoff(&ev, channel, key, velocity);
148      sequence_event(&ev, true);
149  }
150  
151  static void Func_NoteOn(int channel, int key, int velocity)
152  {
153      snd_seq_event_t ev;
154      snd_seq_ev_clear(&ev);
155      snd_seq_ev_set_noteon(&ev, channel, key, velocity);
156      sequence_event(&ev, true);
157  }
158  
159  static void Func_PolyAftertouch(int channel, int key, int pressure)
160  {
161      snd_seq_event_t ev;
162      snd_seq_ev_clear(&ev);
163      snd_seq_ev_set_keypress(&ev, channel, key, pressure);
164      sequence_event(&ev, true);
165  }
166  
167  static void Func_ControlChange(int channel, int number, int value)
168  {
169      snd_seq_event_t ev;
170      snd_seq_ev_clear(&ev);
171      snd_seq_ev_set_controller(&ev, channel, number, value);
172      sequence_event(&ev, false);
173  }
174  
175  static void Func_ProgramChange(int channel, int program)
176  {
177      snd_seq_event_t ev;
178      snd_seq_ev_clear(&ev);
179      snd_seq_ev_set_pgmchange(&ev, channel, program);
180      sequence_event(&ev, true);
181  }
182  
183  static void Func_ChannelAftertouch(int channel, int pressure)
184  {
185      snd_seq_event_t ev;
186      snd_seq_ev_clear(&ev);
187      snd_seq_ev_set_chanpress(&ev, channel, pressure);
188      sequence_event(&ev, true);
189  }
190  
191  static void Func_PitchBend(int channel, int lsb, int msb)
192  {
193      snd_seq_event_t ev;
194      snd_seq_ev_clear(&ev);
195      snd_seq_ev_set_pitchbend(&ev, channel, (lsb|(msb << 7)) - 8192);
196      sequence_event(&ev, true);
197  }
198  
199  static unsigned int get_tick(void)
200  {
201      snd_seq_queue_status_t *status;
202      snd_seq_queue_status_alloca(&status);
203  
204      int result = snd_seq_get_queue_status(seq, seq_queue, status);
205      if (result < 0)
206      {
207          LOG_F(ERROR, "Unable to read ALSA queue status: snd_seq_get_queue_status error %d", result);
208          return 0;
209      }
210  
211      return (unsigned int) snd_seq_queue_status_get_tick_time(status);
212  }
213  
214  static void * threadProc(void *)
215  {
216      struct timeval tv;
217      int sleepAmount = 1000000 / THREAD_QUEUE_INTERVAL;
218      unsigned int sequenceTime;
219  
220      // prime the pump
221      threadTimer = get_tick();
222      threadQueueTimer = threadTimer + threadQueueTicks;
223  
224      while (threadTimer < threadQueueTimer)
225      {
226          if (threadService)
227              threadService();
228          threadTimer++;
229      }
230  
231      while (!threadQuit)
232      {
233          tv.tv_sec = 0;
234          tv.tv_usec = sleepAmount;
235  
236          select(0, NULL, NULL, NULL, &tv);
237  
238          sequenceTime = get_tick();
239          sleepAmount = 1000000 / THREAD_QUEUE_INTERVAL;
240  
241          if ((int)(threadTimer - sequenceTime) > threadQueueTicks)
242          {
243              // we're running ahead, so sleep for half the usual
244              // amount and try again
245              sleepAmount /= 2;
246              continue;
247          }
248  
249          threadQueueTimer = sequenceTime + threadQueueTicks;
250          while (threadTimer < threadQueueTimer)
251          {
252              if (threadService)
253                  threadService();
254              threadTimer++;
255          }
256      }
257  
258      return NULL;
259  }
260  
261  int ALSADrv_MIDI_Init(midifuncs * funcs)
262  {
263      int result;
264  
265      ALSADrv_MIDI_Shutdown();
266      memset(funcs, 0, sizeof(midifuncs));
267  
268      result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, 0);
269      if (result < 0)
270      {
271          LOG_F(ERROR, "Unable to initialize ALSA: snd_seq_open error %d", result);
272          ErrorCode = ALSAErr_SeqOpen;
273          return ALSAErr_Error;
274      }
275  
276      std::vector<alsa_mididevinfo_t> alsaDevices = ALSADrv_MIDI_ListPorts();
277      bool deviceFound = false;
278      for (const alsa_mididevinfo_t &device : alsaDevices)
279          if (device.clntid == ALSA_ClientID && device.portid == ALSA_PortID)
280          {
281              deviceFound = true;
282              break;
283          }
284      if (!deviceFound)
285      {
286          ALSADrv_MIDI_Shutdown();
287          LOG_F(ERROR, "Unable to find ALSA device at %d:%d", ALSA_ClientID, ALSA_PortID);
288          ErrorCode = ALSAErr_DeviceNotFound;
289          return ALSAErr_Error;
290      }
291  
292      seq_port = snd_seq_create_simple_port(seq, "output",
293                    SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE,
294                    SND_SEQ_PORT_TYPE_APPLICATION);
295      if (seq_port < 0)
296      {
297          ALSADrv_MIDI_Shutdown();
298          LOG_F(ERROR, "Unable to create ALSA port: snd_seq_create_simple_port error %d", seq_port);
299          ErrorCode = ALSAErr_CreateSimplePort;
300          return ALSAErr_Error;
301      }
302  
303      snd_seq_set_client_name(seq, "EDuke32");
304  
305      seq_queue = snd_seq_alloc_queue(seq);
306      if (seq_queue < 0)
307      {
308          ALSADrv_MIDI_Shutdown();
309          LOG_F(ERROR, "Unable to allocate ALSA queue: snd_seq_alloc_queue erorr %d", seq_queue);
310          ErrorCode = ALSAErr_AllocQueue;
311          return ALSAErr_Error;
312      }
313  
314      result = snd_seq_connect_to(seq, seq_port, ALSA_ClientID, ALSA_PortID);
315      if (result < 0)
316      {
317          ALSADrv_MIDI_Shutdown();
318          LOG_F(ERROR, "Unable to connect ALSA port to device at %d:%d: snd_seq_connect_to error %d", ALSA_ClientID, ALSA_PortID, result);
319          ErrorCode = ALSAErr_ConnectTo;
320          return ALSAErr_Error;
321      }
322  
323      funcs->NoteOff           = Func_NoteOff;
324      funcs->NoteOn            = Func_NoteOn;
325      funcs->PolyAftertouch    = Func_PolyAftertouch;
326      funcs->ControlChange     = Func_ControlChange;
327      funcs->ProgramChange     = Func_ProgramChange;
328      funcs->ChannelAftertouch = Func_ChannelAftertouch;
329      funcs->PitchBend         = Func_PitchBend;
330  
331      return ALSAErr_Ok;
332  }
333  
334  void ALSADrv_MIDI_Shutdown(void)
335  {
336      ALSADrv_MIDI_HaltPlayback();
337  
338      if (seq_queue >= 0)
339          snd_seq_free_queue(seq, seq_queue);
340  
341      if (seq_port >= 0)
342          snd_seq_delete_simple_port(seq, seq_port);
343  
344      if (seq)
345          snd_seq_close(seq);
346  
347      seq_queue = -1;
348      seq_port  = -1;
349      seq       = 0;
350  }
351  
352  int ALSADrv_MIDI_StartPlayback(void)
353  {
354      ALSADrv_MIDI_HaltPlayback();
355  
356      threadService = ALSADrv_MIDI_Service;
357      threadQuit = 0;
358  
359      ALSADrv_MIDI_QueueStart();
360  
361      if (pthread_create(&thread, NULL, threadProc, NULL))
362      {
363          LOG_F(ERROR, "Unable to create thread for ALSA playback.");
364          ALSADrv_MIDI_HaltPlayback();
365          return ALSAErr_PlayThread;
366      }
367  
368      threadRunning = 1;
369      return 0;
370  }
371  
372  void ALSADrv_MIDI_HaltPlayback(void)
373  {
374      if (!threadRunning)
375          return;
376  
377      threadQuit = 1;
378  
379      void *ret;
380      if (pthread_join(thread, &ret))
381          LOG_F(ERROR, "Unable to terminate ALSA playback thread.");
382  
383      ALSADrv_MIDI_QueueStop();
384  
385      threadRunning = 0;
386  }
387  
388  void ALSADrv_MIDI_SetTempo(int tempo, int division)
389  {
390      double tps;
391      snd_seq_queue_tempo_t * t;
392  
393      ALSADrv_MIDI_QueueStop();
394  
395      snd_seq_queue_tempo_alloca(&t);
396      snd_seq_queue_tempo_set_tempo(t, 60000000 / tempo);
397      snd_seq_queue_tempo_set_ppq(t, division);
398      snd_seq_set_queue_tempo(seq, seq_queue, t);
399  
400      tps = ((double)tempo * (double)division) / 60.0;
401      threadQueueTicks = (int)ceil(tps / (double)THREAD_QUEUE_INTERVAL);
402  
403      ALSADrv_MIDI_QueueStart();
404  }
405  
406  void ALSADrv_MIDI_Lock(void)    {}
407  void ALSADrv_MIDI_Unlock(void)  {}
408  void ALSADrv_MIDI_Service(void) { MIDI_ServiceRoutine(); }
409  
410  void ALSADrv_MIDI_QueueStart(void)
411  {
412      int result;
413  
414      if (!queueRunning)
415      {
416          result = snd_seq_start_queue(seq, seq_queue, NULL);
417  
418          if (result < 0)
419              LOG_F(ERROR, "Unable to start ALSA queue: snd_seq_start_queue error %d", result);
420  
421          do { } while ((result = snd_seq_drain_output(seq)) > 0);
422  
423          if (result < 0)
424              LOG_F(ERROR, "Unable to drain ALSA output: snd_seq_drain_output error %d", result);
425  
426          snd_seq_sync_output_queue(seq);
427  
428          queueRunning = 1;
429      }
430  }
431  
432  void ALSADrv_MIDI_QueueStop(void)
433  {
434      int result;
435  
436      if (queueRunning)
437      {
438          result = snd_seq_stop_queue(seq, seq_queue, NULL);
439  
440          if (result < 0)
441              LOG_F(ERROR, "Unable to stop ALSA queue: snd_seq_stop_queue error %d", result);
442  
443          do { } while ((result = snd_seq_drop_output(seq)) > 0);
444  
445          if (result < 0)
446              LOG_F(ERROR, "Unable to drop ALSA output: snd_seq_drop_output error %d", result);
447  
448          snd_seq_sync_output_queue(seq);
449  
450          queueRunning = 0;
451      }
452  }
453  
454  
455  std::vector<alsa_mididevinfo_t> const ALSADrv_MIDI_ListPorts(void)
456  {
457      int result;
458      bool requiredInit;
459      std::vector<alsa_mididevinfo_t> devices;
460  
461      if (seq == 0)
462      {
463          requiredInit = true;
464  
465          result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, 0);
466          if (result < 0)
467          {
468              LOG_F(ERROR, "Unable to initialize ALSA: snd_seq_open error %d", result);
469              return devices;
470          }
471      }
472      else
473          requiredInit = false;
474  
475      snd_seq_client_info_t *cinfo;
476      snd_seq_port_info_t *pinfo;
477  
478      snd_seq_client_info_alloca(&cinfo);
479      snd_seq_port_info_alloca(&pinfo);
480  
481      snd_seq_client_info_set_client(cinfo, -1);
482  
483      while (snd_seq_query_next_client(seq, cinfo) >= 0)
484      {
485          // Ignore 'ALSA oddities' that we don't want to use.
486          int const client = snd_seq_client_info_get_client(cinfo);
487  
488          if (client < 16)
489              continue;
490  
491          snd_seq_port_info_set_client(pinfo, client);
492          snd_seq_port_info_set_port(pinfo, -1);
493  
494          while (snd_seq_query_next_port(seq, pinfo) >= 0)
495          {
496              /* port must understand MIDI messages */
497              if (!(snd_seq_port_info_get_type(pinfo) & SND_SEQ_PORT_TYPE_MIDI_GENERIC))
498                  continue;
499  
500              /* we need both WRITE and SUBS_WRITE */
501              if ((snd_seq_port_info_get_capability(pinfo) & (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
502                  != (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
503                  continue;
504  
505              devices.push_back(alsa_mididevinfo_t(
506                                       snd_seq_port_info_get_name(pinfo),
507                                       snd_seq_port_info_get_client(pinfo),
508                                       snd_seq_port_info_get_port(pinfo)));
509          }
510      }
511  
512      if (requiredInit)
513      {
514          if (seq)
515              snd_seq_close(seq);
516  
517          seq = 0;
518      }
519  
520      return devices;
521  }