1 /*
2 ZynAddSubFX - a software synthesizer
3
4 SndioEngine.cpp - SNDIO Driver
5 Copyright (C) 2020 Kinichiro Inoguchi
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
11 */
12
13 #include <cmath>
14 #include <iostream>
15 #include <poll.h>
16 #include <stdlib.h>
17
18 #include "Compressor.h"
19 #include "InMgr.h"
20 #include "Nio.h"
21 #include "SndioEngine.h"
22 #include "../Misc/Config.h"
23 #include "../Misc/Util.h"
24
25 using namespace std;
26
27 namespace zyn {
28
SndioEngine(const SYNTH_T & synth)29 SndioEngine::SndioEngine(const SYNTH_T &synth)
30 :AudioOut(synth)
31 {
32 name = "SNDIO";
33 audio.handle = NULL;
34 audio.buffer = new short[synth.buffersize * 2];
35 audio.buffer_size = synth.buffersize * 2 * sizeof(short);
36 audio.peaks[0] = 0;
37 audio.pThread = 0;
38
39 midi.handle = NULL;
40 midi.pThread = 0;
41 }
42
~SndioEngine()43 SndioEngine::~SndioEngine()
44 {
45 Stop();
46 delete[] audio.buffer;
47 }
48
Start()49 bool SndioEngine::Start()
50 {
51 return openAudio() && openMidi();
52 }
53
Stop()54 void SndioEngine::Stop()
55 {
56 if(getMidiEn())
57 setMidiEn(false);
58 if(getAudioEn())
59 setAudioEn(false);
60 }
61
setAudioEn(bool nval)62 void SndioEngine::setAudioEn(bool nval)
63 {
64 if(nval)
65 openAudio();
66 else
67 stopAudio();
68 }
69
getAudioEn() const70 bool SndioEngine::getAudioEn() const
71 {
72 return audio.handle;
73 }
74
setMidiEn(bool nval)75 void SndioEngine::setMidiEn(bool nval)
76 {
77 if(nval)
78 openMidi();
79 else
80 stopMidi();
81 }
82
getMidiEn() const83 bool SndioEngine::getMidiEn() const
84 {
85 return midi.handle;
86 }
87
AudioThread()88 void *SndioEngine::AudioThread()
89 {
90 set_realtime();
91 return processAudio();
92 }
93
_AudioThread(void * arg)94 void *SndioEngine::_AudioThread(void *arg)
95 {
96 return (static_cast<SndioEngine *>(arg))->AudioThread();
97 }
98
MidiThread(void)99 void *SndioEngine::MidiThread(void)
100 {
101 set_realtime();
102 return processMidi();
103 }
104
_MidiThread(void * arg)105 void *SndioEngine::_MidiThread(void *arg)
106 {
107 return static_cast<SndioEngine *>(arg)->MidiThread();
108 }
109
openAudio()110 bool SndioEngine::openAudio()
111 {
112 int rc;
113
114 if(getAudioEn())
115 return true;
116
117 audio.handle = NULL;
118
119 if((audio.handle = sio_open(SIO_DEVANY, SIO_PLAY, 0)) == NULL) {
120 fprintf(stderr, "unable to open sndio audio device\n");
121 return false;
122 }
123
124 sio_initpar(&audio.params);
125 audio.params.rate = synth.samplerate;
126 audio.params.appbufsz = audio.params.rate * 0.05;
127 audio.params.xrun = SIO_SYNC;
128
129 rc = sio_setpar(audio.handle, &audio.params);
130 if(rc != 1) {
131 fprintf(stderr, "unable to set sndio audio parameters");
132 return false;
133 }
134
135 showAudioInfo(audio.handle);
136
137 rc = sio_start(audio.handle);
138 if(rc != 1) {
139 fprintf(stderr, "unable to start sndio audio");
140 return false;
141 }
142
143 pthread_attr_t attr;
144 pthread_attr_init(&attr);
145 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
146 pthread_create(&audio.pThread, &attr, _AudioThread, this);
147 return true;
148 }
149
stopAudio()150 void SndioEngine::stopAudio()
151 {
152 struct sio_hdl *handle = audio.handle;
153 int rc;
154
155 if(!getAudioEn())
156 return;
157
158 audio.handle = NULL;
159
160 pthread_join(audio.pThread, NULL);
161
162 rc = sio_stop(handle);
163 if(rc != 1)
164 fprintf(stderr, "unable to stop sndio audio");
165
166 sio_close(handle);
167 }
168
openMidi()169 bool SndioEngine::openMidi()
170 {
171 if(getMidiEn())
172 return true;
173
174 midi.handle = NULL;
175
176 if((midi.handle = mio_open(MIO_PORTANY, MIO_IN, 1)) == NULL) {
177 fprintf(stderr, "unable to open sndio midi device\n");
178 return false;
179 }
180
181 midi.exiting = false;
182 pthread_attr_t attr;
183
184 pthread_attr_init(&attr);
185 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
186 pthread_create(&midi.pThread, &attr, _MidiThread, this);
187 return true;
188 }
189
stopMidi()190 void SndioEngine::stopMidi()
191 {
192 struct mio_hdl *handle = midi.handle;
193
194 if(!getMidiEn())
195 return;
196
197 if((midi.handle != NULL) && midi.pThread) {
198 midi.exiting = true;
199 pthread_join(midi.pThread, 0);
200 }
201
202 midi.handle = NULL;
203
204 if(handle)
205 mio_close(handle);
206 }
207
processAudio()208 void *SndioEngine::processAudio()
209 {
210 size_t len;
211 struct sio_hdl *handle;
212
213 while(audio.handle) {
214 audio.buffer = interleave(getNext());
215 handle = audio.handle;
216 len = sio_write(handle, audio.buffer, audio.buffer_size);
217 if(len == 0) // write error according to sndio examples
218 cerr << "sio_write error" << endl;
219 }
220 return NULL;
221 }
222
processMidi()223 void *SndioEngine::processMidi()
224 {
225 int n;
226 int nfds;
227 struct pollfd *pfd;
228 int rc;
229 int revents;
230 size_t len;
231 unsigned char buf[3];
232
233 n = mio_nfds(midi.handle);
234 if(n <= 0) {
235 cerr << "mio_nfds error" << endl;
236 return NULL;
237 }
238
239 pfd = (struct pollfd *) calloc(n, sizeof(struct pollfd));
240 if(pfd == NULL) {
241 cerr << "calloc error" << endl;
242 return NULL;
243 }
244
245 while(1) {
246 if(midi.exiting)
247 break;
248
249 nfds = mio_pollfd(midi.handle, pfd, POLLIN);
250
251 rc = poll(pfd, nfds, 1000);
252 if(rc < 0 && rc != EAGAIN && rc != EINTR) {
253 cerr << "poll error" << endl;
254 break;
255 }
256
257 revents = mio_revents(midi.handle, pfd);
258 if(revents & POLLHUP) {
259 cerr << "mio_revents catches POLLHUP" << endl;
260 continue;
261 }
262 if(!(revents & POLLIN))
263 continue;
264
265 memset(buf, 0, sizeof(buf));
266 len = mio_read(midi.handle, buf, sizeof(buf));
267 if(len == 0) {
268 // since mio_read is non-blocking, this must indicate an error
269 // so stop processing all MIDI
270 break;
271 } else if(len > sizeof(buf)) {
272 fprintf(stderr, "mio_read invalid len = %zu\n", len);
273 continue;
274 }
275
276 midiProcess(buf[0], buf[1], buf[2]);
277 }
278 free(pfd);
279 return NULL;
280 }
281
interleave(const Stereo<float * > & smps)282 short *SndioEngine::interleave(const Stereo<float *> &smps)
283 {
284 short *shortInterleaved;
285 int frame, idx;
286 float l, r;
287 double scaled;
288
289 shortInterleaved = audio.buffer;
290 memset(shortInterleaved, 0, audio.buffer_size);
291
292 for(frame = idx = 0; frame < synth.buffersize; ++frame) {
293 l = smps.l[frame];
294 r = smps.r[frame];
295 stereoCompressor(synth.samplerate, audio.peaks[0], l, r);
296
297 scaled = l * (8.0f * 0x10000000);
298 shortInterleaved[idx++] = (short int)(lrint(scaled) >> 16);
299 scaled = r * (8.0f * 0x10000000);
300 shortInterleaved[idx++] = (short int)(lrint(scaled) >> 16);
301 }
302 return shortInterleaved;
303 }
304
showAudioInfo(struct sio_hdl * handle)305 void SndioEngine::showAudioInfo(struct sio_hdl *handle)
306 {
307 int rc;
308 struct sio_par par;
309 struct sio_cap cap;
310 unsigned int i;
311
312 rc = sio_getpar(handle, &par);
313 if(rc != 1) {
314 fprintf(stderr, "unable to get sndio audio parameters");
315 return;
316 }
317
318 fprintf(stderr, "sndio audio parameters:\n");
319 fprintf(stderr,
320 " bits = %u bps = %u sig = %u le = %u msb = %u rchan = %u pchan = %u\n"
321 " rate = %u appbufsz = %u bufsz = %u round = %u xrun = %u\n",
322 par.bits, par.bps, par.sig, par.le, par.msb, par.rchan, par.pchan,
323 par.rate, par.appbufsz, par.bufsz, par.round, par.xrun);
324
325 rc = sio_getcap(handle, &cap);
326 if(rc != 1) {
327 fprintf(stderr, "unable to get sndio audio capabilities");
328 return;
329 }
330
331 fprintf(stderr, "sndio audio capabilities:\n");
332 fprintf(stderr, " supported encodings:\n");
333 for(i = 0; i < SIO_NENC; ++i)
334 fprintf(stderr,
335 " [%d] bits = %u bps = %u sig = %u le = %u msb = %u\n",
336 i, cap.enc[i].bits, cap.enc[i].bps, cap.enc[i].sig,
337 cap.enc[i].le, cap.enc[i].msb);
338
339 fprintf(stderr, " supported channel numbers of recording:\n");
340 for(i = 0; i < SIO_NCHAN; ++i)
341 fprintf(stderr, " [%d] rchan = %u\n", i, cap.rchan[i]);
342
343 fprintf(stderr, " supported channel numbers of playback:\n");
344 for(i = 0; i < SIO_NCHAN; ++i)
345 fprintf(stderr, " [%d] pchan = %u\n", i, cap.pchan[i]);
346
347 fprintf(stderr, " supported sample rates:\n");
348 for(i = 0; i < SIO_NRATE; ++i)
349 fprintf(stderr, " [%d] rate = %u\n", i, cap.rate[i]);
350
351 fprintf(stderr, " available configurations:\n");
352 for(i = 0; i < cap.nconf; ++i)
353 fprintf(stderr,
354 " [%d] enc = %x rchan = %x pchan = %x rate = %x\n",
355 i, cap.confs[i].enc, cap.confs[i].rchan, cap.confs[i].pchan,
356 cap.confs[i].rate);
357 }
358
359 }
360