Change ALSA device default to 'default' from hw:0
[zynaddsubfx-code.git] / src / Nio / AlsaEngine.cpp
blobedec8e59aaaebd98b9aa2a03dd222b52322bccf3
1 /*
2 ZynAddSubFX - a software synthesizer
4 AlsaEngine.cpp - ALSA Driver
5 Copyright (C) 2009 Alan Calvert
6 Copyright (C) 2014 Mark McCurry
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.
14 #include <stdlib.h>
15 #include <iostream>
16 #include <cmath>
17 #include <poll.h>
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"
26 extern char *instance_name;
28 using namespace std;
30 namespace zyn {
32 AlsaEngine::AlsaEngine(const SYNTH_T &synth)
33 :AudioOut(synth)
35 audio.buffer = new short[synth.buffersize * 2];
36 name = "ALSA";
37 audio.handle = NULL;
38 audio.peaks[0] = 0;
40 midi.handle = NULL;
41 midi.alsaId = -1;
42 midi.pThread = 0;
45 AlsaEngine::~AlsaEngine()
47 Stop();
48 delete[] audio.buffer;
51 void *AlsaEngine::_AudioThread(void *arg)
53 return (static_cast<AlsaEngine *>(arg))->AudioThread();
56 void *AlsaEngine::AudioThread()
58 set_realtime();
59 return processAudio();
62 bool AlsaEngine::Start()
64 return openAudio() && openMidi();
67 void AlsaEngine::Stop()
69 if(getMidiEn())
70 setMidiEn(false);
71 if(getAudioEn())
72 setAudioEn(false);
73 snd_config_update_free_global();
76 void AlsaEngine::setMidiEn(bool nval)
78 if(nval)
79 openMidi();
80 else
81 stopMidi();
84 bool AlsaEngine::getMidiEn() const
86 return midi.handle;
89 void AlsaEngine::setAudioEn(bool nval)
91 if(nval)
92 openAudio();
93 else
94 stopAudio();
97 bool AlsaEngine::getAudioEn() const
99 return audio.handle;
102 void *AlsaEngine::_MidiThread(void *arg)
104 return static_cast<AlsaEngine *>(arg)->MidiThread();
108 void *AlsaEngine::MidiThread(void)
110 snd_seq_event_t *event;
111 MidiEvent ev = {};
112 struct pollfd pfd[4 /* XXX 1 should be enough */];
113 int error;
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;
132 //ensure ev is empty
133 ev.channel = 0;
134 ev.num = 0;
135 ev.value = 0;
136 ev.type = 0;
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;
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;
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;
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;
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;
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;
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;
196 case SND_SEQ_EVENT_PORT_SUBSCRIBED: // ports connected
197 if(true)
198 cout << "Info, alsa midi port connected" << endl;
199 break;
201 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: // ports disconnected
202 if(true)
203 cout << "Info, alsa midi port disconnected" << endl;
204 break;
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);
216 midiProcess(buf[0], buf[1], buf[2]);
218 break;
220 case SND_SEQ_EVENT_SENSING: // midi device still there
221 break;
223 default:
224 if(true)
225 cout << "Info, other non-handled midi event, type: "
226 << (int)event->type << endl;
227 break;
229 snd_seq_free_event(event);
231 return NULL;
234 bool AlsaEngine::openMidi()
236 if(getMidiEn())
237 return true;
239 int alsaport;
240 midi.handle = NULL;
242 if(snd_seq_open(&midi.handle, "default",
243 SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK) != 0)
244 return false;
246 string clientname = "ZynAddSubFX";
247 if(instance_name)
248 clientname = (string) instance_name;
249 string postfix = Nio::getPostfix();
250 if (!postfix.empty())
251 clientname += "_" + postfix;
252 if(Nio::pidInClientName)
253 clientname += "_" + os_pid_as_padded_string();
254 snd_seq_set_client_name(midi.handle, clientname.c_str());
256 alsaport = snd_seq_create_simple_port(
257 midi.handle,
258 "ZynAddSubFX",
259 SND_SEQ_PORT_CAP_WRITE
260 | SND_SEQ_PORT_CAP_SUBS_WRITE,
261 SND_SEQ_PORT_TYPE_SYNTH);
262 if(alsaport < 0)
263 return false;
265 midi.exiting = false;
266 pthread_attr_t attr;
268 pthread_attr_init(&attr);
269 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
270 pthread_create(&midi.pThread, &attr, _MidiThread, this);
271 return true;
274 void AlsaEngine::stopMidi()
276 if(!getMidiEn())
277 return;
279 snd_seq_t *handle = midi.handle;
280 if((NULL != midi.handle) && midi.pThread) {
281 midi.exiting = true;
282 pthread_join(midi.pThread, 0);
284 midi.handle = NULL;
285 if(handle)
286 snd_seq_close(handle);
289 short *AlsaEngine::interleave(const Stereo<float *> &smps)
291 /**\todo TODO fix repeated allocation*/
292 short *shortInterleaved = audio.buffer;
293 memset(shortInterleaved, 0, bufferSize * 2 * sizeof(short));
294 int idx = 0; //possible off by one error here
295 double scaled;
296 for(int frame = 0; frame < bufferSize; ++frame) { // with a nod to libsamplerate ...
297 float l = smps.l[frame];
298 float r = smps.r[frame];
299 if(isOutputCompressionEnabled)
300 stereoCompressor(synth.samplerate, audio.peaks[0], l, r);
302 scaled = l * (8.0f * 0x10000000);
303 shortInterleaved[idx++] = (short int)(lrint(scaled) >> 16);
304 scaled = r * (8.0f * 0x10000000);
305 shortInterleaved[idx++] = (short int)(lrint(scaled) >> 16);
307 return shortInterleaved;
310 bool AlsaEngine::openAudio()
312 if(getAudioEn())
313 return true;
315 int rc = 0;
316 /* Open PCM device for playback. */
317 audio.handle = NULL;
319 const char *device = getenv("ALSA_DEVICE");
320 if(device == 0)
321 device = "default";
323 rc = snd_pcm_open(&audio.handle, device,
324 SND_PCM_STREAM_PLAYBACK, 0);
325 if(rc < 0) {
326 fprintf(stderr,
327 "unable to open pcm device: %s\n",
328 snd_strerror(rc));
329 fprintf(stderr,
330 "If your device isn't '%s', use the ALSA_DEVICE\n", device);
331 fprintf(stderr, "environmental variable to choose another\n");
332 return false;
335 /* Allocate a hardware parameters object. */
336 snd_pcm_hw_params_alloca(&audio.params);
338 /* Fill it in with default values. */
339 snd_pcm_hw_params_any(audio.handle, audio.params);
341 /* Set the desired hardware parameters. */
343 /* Interleaved mode */
344 snd_pcm_hw_params_set_access(audio.handle, audio.params,
345 SND_PCM_ACCESS_RW_INTERLEAVED);
347 /* Signed 16-bit little-endian format */
348 snd_pcm_hw_params_set_format(audio.handle, audio.params,
349 SND_PCM_FORMAT_S16_LE);
351 /* Two channels (stereo) */
352 snd_pcm_hw_params_set_channels(audio.handle, audio.params, 2);
354 audio.sampleRate = synth.samplerate;
355 snd_pcm_hw_params_set_rate_near(audio.handle, audio.params,
356 &audio.sampleRate, NULL);
358 audio.frames = 512;
359 snd_pcm_hw_params_set_period_size_near(audio.handle,
360 audio.params, &audio.frames, NULL);
362 audio.periods = 4;
363 snd_pcm_hw_params_set_periods_near(audio.handle,
364 audio.params, &audio.periods, NULL);
366 /* Set buffer size (in frames). The resulting latency is given by */
367 /* latency = periodsize * periods / (rate * bytes_per_frame) */
368 snd_pcm_uframes_t alsa_buffersize = synth.buffersize;
369 rc = snd_pcm_hw_params_set_buffer_size_near(audio.handle,
370 audio.params,
371 &alsa_buffersize);
373 /* At this place, ALSA's and zyn's buffer sizes may differ. */
374 /* This should not be a problem. */
375 if((int)alsa_buffersize != synth.buffersize)
376 cerr << "ALSA buffer size: " << alsa_buffersize << endl;
378 /* Write the parameters to the driver */
379 rc = snd_pcm_hw_params(audio.handle, audio.params);
380 if(rc < 0) {
381 fprintf(stderr,
382 "unable to set hw parameters: %s\n",
383 snd_strerror(rc));
384 return false;
387 //snd_pcm_hw_params_get_period_size(audio.params, &audio.frames, NULL);
388 //snd_pcm_hw_params_get_period_time(audio.params, &val, NULL);
391 pthread_attr_t attr;
392 pthread_attr_init(&attr);
393 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
394 pthread_create(&audio.pThread, &attr, _AudioThread, this);
395 return true;
398 void AlsaEngine::stopAudio()
400 if(!getAudioEn())
401 return;
403 snd_pcm_t *handle = audio.handle;
404 audio.handle = NULL;
405 pthread_join(audio.pThread, NULL);
406 snd_pcm_drain(handle);
407 if(snd_pcm_close(handle))
408 cout << "Error: in snd_pcm_close " << __LINE__ << ' ' << __FILE__
409 << endl;
412 void *AlsaEngine::processAudio()
414 while(audio.handle) {
415 audio.buffer = interleave(getNext());
416 snd_pcm_t *handle = audio.handle;
417 int rc = snd_pcm_writei(handle, audio.buffer, synth.buffersize);
418 if(rc == -EPIPE) {
419 /* EPIPE means underrun */
420 cerr << "underrun occurred" << endl;
421 snd_pcm_prepare(handle);
423 else
424 if(rc < 0) {
425 cerr << "AlsaEngine: Recovering connection..." << endl;
426 rc = snd_pcm_recover(handle, rc, 0);
427 if(rc < 0)
428 throw "Could not recover ALSA connection";
431 return NULL;