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.
19 #include "../Misc/Util.h"
20 #include "../Misc/Config.h"
22 #include "AlsaEngine.h"
23 #include "Compressor.h"
26 extern char *instance_name
;
32 AlsaEngine::AlsaEngine(const SYNTH_T
&synth
)
35 audio
.buffer
= new short[synth
.buffersize
* 2];
45 AlsaEngine::~AlsaEngine()
48 delete[] audio
.buffer
;
51 void *AlsaEngine::_AudioThread(void *arg
)
53 return (static_cast<AlsaEngine
*>(arg
))->AudioThread();
56 void *AlsaEngine::AudioThread()
59 return processAudio();
62 bool AlsaEngine::Start()
64 return openAudio() && openMidi();
67 void AlsaEngine::Stop()
73 snd_config_update_free_global();
76 void AlsaEngine::setMidiEn(bool nval
)
84 bool AlsaEngine::getMidiEn() const
89 void AlsaEngine::setAudioEn(bool nval
)
97 bool AlsaEngine::getAudioEn() const
102 void *AlsaEngine::_MidiThread(void *arg
)
104 return static_cast<AlsaEngine
*>(arg
)->MidiThread();
108 void *AlsaEngine::MidiThread(void)
110 snd_seq_event_t
*event
;
112 struct pollfd pfd
[4 /* XXX 1 should be enough */];
119 error
= snd_seq_event_input(midi
.handle
, &event
);
121 if(error
!= -EAGAIN
&& error
!= -EINTR
)
123 error
= snd_seq_poll_descriptors(midi
.handle
, pfd
, 4, POLLIN
);
126 error
= poll(pfd
, error
, 1000 /* ms */);
128 errno
!= EAGAIN
&& errno
!= EINTR
)
140 switch(event
->type
) {
141 case SND_SEQ_EVENT_NOTEON
:
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
);
149 case SND_SEQ_EVENT_NOTEOFF
:
151 ev
.channel
= event
->data
.note
.channel
;
152 ev
.num
= event
->data
.note
.note
;
154 InMgr::getInstance().putEvent(ev
);
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
);
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
);
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
);
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
);
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
;
193 InMgr::getInstance().putEvent(ev
);
196 case SND_SEQ_EVENT_PORT_SUBSCRIBED
: // ports connected
198 cout
<< "Info, alsa midi port connected" << endl
;
201 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED
: // ports disconnected
203 cout
<< "Info, alsa midi port disconnected" << endl
;
206 case SND_SEQ_EVENT_SYSEX
: // system exclusive
207 for (unsigned int x
= 0; x
< event
->data
.ext
.len
; x
+= 3) {
209 int y
= event
->data
.ext
.len
- x
;
211 memcpy(buf
, (uint8_t *)event
->data
.ext
.ptr
+ x
, 3);
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]);
220 case SND_SEQ_EVENT_SENSING
: // midi device still there
225 cout
<< "Info, other non-handled midi event, type: "
226 << (int)event
->type
<< endl
;
229 snd_seq_free_event(event
);
234 bool AlsaEngine::openMidi()
242 if(snd_seq_open(&midi
.handle
, "default",
243 SND_SEQ_OPEN_INPUT
, SND_SEQ_NONBLOCK
) != 0)
246 string clientname
= "ZynAddSubFX";
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(
259 SND_SEQ_PORT_CAP_WRITE
260 | SND_SEQ_PORT_CAP_SUBS_WRITE
,
261 SND_SEQ_PORT_TYPE_SYNTH
);
265 midi
.exiting
= false;
268 pthread_attr_init(&attr
);
269 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_JOINABLE
);
270 pthread_create(&midi
.pThread
, &attr
, _MidiThread
, this);
274 void AlsaEngine::stopMidi()
279 snd_seq_t
*handle
= midi
.handle
;
280 if((NULL
!= midi
.handle
) && midi
.pThread
) {
282 pthread_join(midi
.pThread
, 0);
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
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()
316 /* Open PCM device for playback. */
319 const char *device
= getenv("ALSA_DEVICE");
323 rc
= snd_pcm_open(&audio
.handle
, device
,
324 SND_PCM_STREAM_PLAYBACK
, 0);
327 "unable to open pcm device: %s\n",
330 "If your device isn't '%s', use the ALSA_DEVICE\n", device
);
331 fprintf(stderr
, "environmental variable to choose another\n");
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
);
359 snd_pcm_hw_params_set_period_size_near(audio
.handle
,
360 audio
.params
, &audio
.frames
, NULL
);
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
,
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
);
382 "unable to set hw parameters: %s\n",
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);
392 pthread_attr_init(&attr
);
393 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_JOINABLE
);
394 pthread_create(&audio
.pThread
, &attr
, _AudioThread
, this);
398 void AlsaEngine::stopAudio()
403 snd_pcm_t
*handle
= audio
.handle
;
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__
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
);
419 /* EPIPE means underrun */
420 cerr
<< "underrun occurred" << endl
;
421 snd_pcm_prepare(handle
);
425 cerr
<< "AlsaEngine: Recovering connection..." << endl
;
426 rc
= snd_pcm_recover(handle
, rc
, 0);
428 throw "Could not recover ALSA connection";