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