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 }