AlsaEngine.cpp (12880B)
1 /* 2 ZynAddSubFX - a software synthesizer 3 4 AlsaEngine.cpp - ALSA Driver 5 Copyright (C) 2009 Alan Calvert 6 Copyright (C) 2014 Mark McCurry 7 8 This program is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License 10 as published by the Free Software Foundation; either version 2 11 of the License, or (at your option) any later version. 12 */ 13 14 #include <stdlib.h> 15 #include <iostream> 16 #include <cmath> 17 #include <poll.h> 18 19 #include "../Misc/Util.h" 20 #include "../Misc/Config.h" 21 #include "InMgr.h" 22 #include "AlsaEngine.h" 23 #include "Compressor.h" 24 #include "Nio.h" 25 26 extern char *instance_name; 27 28 using namespace std; 29 30 namespace zyn { 31 32 AlsaEngine::AlsaEngine(const SYNTH_T &synth) 33 :AudioOut(synth) 34 { 35 audio.buffer = new short[synth.buffersize * 2]; 36 name = "ALSA"; 37 audio.handle = NULL; 38 audio.peaks[0] = 0; 39 40 midi.handle = NULL; 41 midi.alsaId = -1; 42 midi.pThread = 0; 43 } 44 45 AlsaEngine::~AlsaEngine() 46 { 47 Stop(); 48 delete[] audio.buffer; 49 } 50 51 void *AlsaEngine::_AudioThread(void *arg) 52 { 53 return (static_cast<AlsaEngine *>(arg))->AudioThread(); 54 } 55 56 void *AlsaEngine::AudioThread() 57 { 58 set_realtime(); 59 return processAudio(); 60 } 61 62 bool AlsaEngine::Start() 63 { 64 return openAudio() && openMidi(); 65 } 66 67 void AlsaEngine::Stop() 68 { 69 if(getMidiEn()) 70 setMidiEn(false); 71 if(getAudioEn()) 72 setAudioEn(false); 73 snd_config_update_free_global(); 74 } 75 76 void AlsaEngine::setMidiEn(bool nval) 77 { 78 if(nval) 79 openMidi(); 80 else 81 stopMidi(); 82 } 83 84 bool AlsaEngine::getMidiEn() const 85 { 86 return midi.handle; 87 } 88 89 void AlsaEngine::setAudioEn(bool nval) 90 { 91 if(nval) 92 openAudio(); 93 else 94 stopAudio(); 95 } 96 97 bool AlsaEngine::getAudioEn() const 98 { 99 return audio.handle; 100 } 101 102 void *AlsaEngine::_MidiThread(void *arg) 103 { 104 return static_cast<AlsaEngine *>(arg)->MidiThread(); 105 } 106 107 108 void *AlsaEngine::MidiThread(void) 109 { 110 snd_seq_event_t *event; 111 MidiEvent ev = {}; 112 struct pollfd pfd[4 /* XXX 1 should be enough */]; 113 int error; 114 115 set_realtime(); 116 while(1) { 117 if(midi.exiting) 118 break; 119 error = snd_seq_event_input(midi.handle, &event); 120 if (error < 0) { 121 if(error != -EAGAIN && error != -EINTR) 122 break; 123 error = snd_seq_poll_descriptors(midi.handle, pfd, 4, POLLIN); 124 if(error <= 0) 125 break; 126 error = poll(pfd, error, 1000 /* ms */); 127 if(error < 0 && 128 errno != EAGAIN && errno != EINTR) 129 break; 130 continue; 131 } 132 //ensure ev is empty 133 ev.channel = 0; 134 ev.num = 0; 135 ev.value = 0; 136 ev.type = 0; 137 138 if(!event) 139 continue; 140 switch(event->type) { 141 case SND_SEQ_EVENT_NOTEON: 142 ev.type = M_NOTE; 143 ev.channel = event->data.note.channel; 144 ev.num = event->data.note.note; 145 ev.value = event->data.note.velocity; 146 InMgr::getInstance().putEvent(ev); 147 break; 148 149 case SND_SEQ_EVENT_NOTEOFF: 150 ev.type = M_NOTE; 151 ev.channel = event->data.note.channel; 152 ev.num = event->data.note.note; 153 ev.value = 0; 154 InMgr::getInstance().putEvent(ev); 155 break; 156 157 case SND_SEQ_EVENT_KEYPRESS: 158 ev.type = M_PRESSURE; 159 ev.channel = event->data.note.channel; 160 ev.num = event->data.note.note; 161 ev.value = event->data.note.velocity; 162 InMgr::getInstance().putEvent(ev); 163 break; 164 165 case SND_SEQ_EVENT_PITCHBEND: 166 ev.type = M_CONTROLLER; 167 ev.channel = event->data.control.channel; 168 ev.num = C_pitchwheel; 169 ev.value = event->data.control.value; 170 InMgr::getInstance().putEvent(ev); 171 break; 172 173 case SND_SEQ_EVENT_CONTROLLER: 174 ev.type = M_CONTROLLER; 175 ev.channel = event->data.control.channel; 176 ev.num = event->data.control.param; 177 ev.value = event->data.control.value; 178 InMgr::getInstance().putEvent(ev); 179 break; 180 181 case SND_SEQ_EVENT_PGMCHANGE: 182 ev.type = M_PGMCHANGE; 183 ev.channel = event->data.control.channel; 184 ev.num = event->data.control.value; 185 InMgr::getInstance().putEvent(ev); 186 break; 187 188 case SND_SEQ_EVENT_RESET: // reset to power-on state 189 ev.type = M_CONTROLLER; 190 ev.channel = event->data.control.channel; 191 ev.num = C_resetallcontrollers; 192 ev.value = 0; 193 InMgr::getInstance().putEvent(ev); 194 break; 195 196 case SND_SEQ_EVENT_PORT_SUBSCRIBED: // ports connected 197 if(true) 198 cout << "Info, alsa midi port connected" << endl; 199 break; 200 201 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: // ports disconnected 202 if(true) 203 cout << "Info, alsa midi port disconnected" << endl; 204 break; 205 206 case SND_SEQ_EVENT_SYSEX: // system exclusive 207 for (unsigned int x = 0; x < event->data.ext.len; x += 3) { 208 uint8_t buf[3]; 209 int y = event->data.ext.len - x; 210 if (y >= 3) { 211 memcpy(buf, (uint8_t *)event->data.ext.ptr + x, 3); 212 } else { 213 memset(buf, 0, sizeof(buf)); 214 memcpy(buf, (uint8_t *)event->data.ext.ptr + x, y); 215 } 216 midiProcess(buf[0], buf[1], buf[2]); 217 } 218 break; 219 220 case SND_SEQ_EVENT_SENSING: // midi device still there 221 break; 222 223 default: 224 for (unsigned int x = 0; x < event->data.ext.len; x += 3) { 225 uint8_t buf[3]; 226 int y = event->data.ext.len - x; 227 if (y >= 3) { 228 memcpy(buf, (uint8_t *)event->data.ext.ptr + x, 3); 229 } else { 230 memset(buf, 0, sizeof(buf)); 231 memcpy(buf, (uint8_t *)event->data.ext.ptr + x, y); 232 } 233 midiProcess(buf[0], buf[1], buf[2]); 234 } 235 break; 236 } 237 snd_seq_free_event(event); 238 } 239 return NULL; 240 } 241 242 bool AlsaEngine::openMidi() 243 { 244 if(getMidiEn()) 245 return true; 246 247 int alsaport; 248 midi.handle = NULL; 249 250 if(snd_seq_open(&midi.handle, "default", 251 SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK) != 0) 252 return false; 253 254 string clientname = "ZynAddSubFX"; 255 if(instance_name) 256 clientname = (string) instance_name; 257 string postfix = Nio::getPostfix(); 258 if (!postfix.empty()) 259 clientname += "_" + postfix; 260 if(Nio::pidInClientName) 261 clientname += "_" + os_pid_as_padded_string(); 262 snd_seq_set_client_name(midi.handle, clientname.c_str()); 263 264 alsaport = snd_seq_create_simple_port( 265 midi.handle, 266 "ZynAddSubFX", 267 SND_SEQ_PORT_CAP_WRITE 268 | SND_SEQ_PORT_CAP_SUBS_WRITE, 269 SND_SEQ_PORT_TYPE_SYNTH); 270 if(alsaport < 0) 271 return false; 272 273 midi.exiting = false; 274 pthread_attr_t attr; 275 276 pthread_attr_init(&attr); 277 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 278 pthread_create(&midi.pThread, &attr, _MidiThread, this); 279 return true; 280 } 281 282 void AlsaEngine::stopMidi() 283 { 284 if(!getMidiEn()) 285 return; 286 287 snd_seq_t *handle = midi.handle; 288 if((NULL != midi.handle) && midi.pThread) { 289 midi.exiting = true; 290 pthread_join(midi.pThread, 0); 291 } 292 midi.handle = NULL; 293 if(handle) 294 snd_seq_close(handle); 295 } 296 297 short *AlsaEngine::interleave(const Stereo<float *> &smps) 298 { 299 /**\todo TODO fix repeated allocation*/ 300 short *shortInterleaved = audio.buffer; 301 memset(shortInterleaved, 0, bufferSize * 2 * sizeof(short)); 302 int idx = 0; //possible off by one error here 303 double scaled; 304 for(int frame = 0; frame < bufferSize; ++frame) { // with a nod to libsamplerate ... 305 float l = smps.l[frame]; 306 float r = smps.r[frame]; 307 if(isOutputCompressionEnabled) 308 stereoCompressor(synth.samplerate, audio.peaks[0], l, r); 309 310 scaled = l * (8.0f * 0x10000000); 311 shortInterleaved[idx++] = (short int)(lrint(scaled) >> 16); 312 scaled = r * (8.0f * 0x10000000); 313 shortInterleaved[idx++] = (short int)(lrint(scaled) >> 16); 314 } 315 return shortInterleaved; 316 } 317 318 bool AlsaEngine::openAudio() 319 { 320 if(getAudioEn()) 321 return true; 322 323 int rc = 0; 324 /* Open PCM device for playback. */ 325 audio.handle = NULL; 326 327 const char *device = getenv("ALSA_DEVICE"); 328 if(device == 0) 329 device = "default"; 330 331 rc = snd_pcm_open(&audio.handle, device, 332 SND_PCM_STREAM_PLAYBACK, 0); 333 if(rc < 0) { 334 fprintf(stderr, 335 "unable to open pcm device: %s\n", 336 snd_strerror(rc)); 337 fprintf(stderr, 338 "If your device isn't '%s', use the ALSA_DEVICE\n", device); 339 fprintf(stderr, "environmental variable to choose another\n"); 340 return false; 341 } 342 343 /* Allocate a hardware parameters object. */ 344 snd_pcm_hw_params_alloca(&audio.params); 345 346 /* Fill it in with default values. */ 347 snd_pcm_hw_params_any(audio.handle, audio.params); 348 349 /* Set the desired hardware parameters. */ 350 351 /* Interleaved mode */ 352 snd_pcm_hw_params_set_access(audio.handle, audio.params, 353 SND_PCM_ACCESS_RW_INTERLEAVED); 354 355 /* Signed 16-bit little-endian format */ 356 snd_pcm_hw_params_set_format(audio.handle, audio.params, 357 SND_PCM_FORMAT_S16_LE); 358 359 /* Two channels (stereo) */ 360 snd_pcm_hw_params_set_channels(audio.handle, audio.params, 2); 361 362 audio.sampleRate = synth.samplerate; 363 snd_pcm_hw_params_set_rate_near(audio.handle, audio.params, 364 &audio.sampleRate, NULL); 365 366 audio.frames = 512; 367 snd_pcm_hw_params_set_period_size_near(audio.handle, 368 audio.params, &audio.frames, NULL); 369 370 audio.periods = 4; 371 snd_pcm_hw_params_set_periods_near(audio.handle, 372 audio.params, &audio.periods, NULL); 373 374 /* Set buffer size (in frames). The resulting latency is given by */ 375 /* latency = periodsize * periods / (rate * bytes_per_frame) */ 376 snd_pcm_uframes_t alsa_buffersize = synth.buffersize; 377 rc = snd_pcm_hw_params_set_buffer_size_near(audio.handle, 378 audio.params, 379 &alsa_buffersize); 380 381 /* At this place, ALSA's and zyn's buffer sizes may differ. */ 382 /* This should not be a problem. */ 383 if((int)alsa_buffersize != synth.buffersize) 384 cerr << "ALSA buffer size: " << alsa_buffersize << endl; 385 386 /* Write the parameters to the driver */ 387 rc = snd_pcm_hw_params(audio.handle, audio.params); 388 if(rc < 0) { 389 fprintf(stderr, 390 "unable to set hw parameters: %s\n", 391 snd_strerror(rc)); 392 return false; 393 } 394 395 //snd_pcm_hw_params_get_period_size(audio.params, &audio.frames, NULL); 396 //snd_pcm_hw_params_get_period_time(audio.params, &val, NULL); 397 398 399 pthread_attr_t attr; 400 pthread_attr_init(&attr); 401 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 402 pthread_create(&audio.pThread, &attr, _AudioThread, this); 403 return true; 404 } 405 406 void AlsaEngine::stopAudio() 407 { 408 if(!getAudioEn()) 409 return; 410 411 snd_pcm_t *handle = audio.handle; 412 audio.handle = NULL; 413 pthread_join(audio.pThread, NULL); 414 snd_pcm_drain(handle); 415 if(snd_pcm_close(handle)) 416 cout << "Error: in snd_pcm_close " << __LINE__ << ' ' << __FILE__ 417 << endl; 418 } 419 420 void *AlsaEngine::processAudio() 421 { 422 while(audio.handle) { 423 audio.buffer = interleave(getNext()); 424 snd_pcm_t *handle = audio.handle; 425 int rc = snd_pcm_writei(handle, audio.buffer, synth.buffersize); 426 if(rc == -EPIPE) { 427 /* EPIPE means underrun */ 428 cerr << "underrun occurred" << endl; 429 snd_pcm_prepare(handle); 430 } 431 else 432 if(rc < 0) { 433 cerr << "AlsaEngine: Recovering connection..." << endl; 434 rc = snd_pcm_recover(handle, rc, 0); 435 if(rc < 0) 436 throw "Could not recover ALSA connection"; 437 } 438 } 439 return NULL; 440 } 441 442 }