1 /*
2     JackEngine.cpp
3 
4     Copyright 2009, Alan Calvert
5 
6     This file is part of yoshimi, which is free software: you can
7     redistribute it and/or modify it under the terms of the GNU General
8     Public License as published by the Free Software Foundation, either
9     version 2 of the License, or (at your option) any later version.
10 
11     yoshimi is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with yoshimi.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include <iostream>
21 
22 #include <jack/midiport.h>
23 #include <fcntl.h>
24 #include <sys/stat.h>
25 #include <cassert>
26 #include <cstring>
27 
28 #include "Nio.h"
29 #include "InMgr.h"
30 
31 #include "JackEngine.h"
32 
33 using namespace std;
34 
35 extern char *instance_name;
36 
JackEngine()37 JackEngine::JackEngine()
38     :AudioOut(), jackClient(NULL)
39 {
40     name = "JACK";
41     audio.jackSamplerate = 0;
42     audio.jackNframes    = 0;
43     for(int i = 0; i < 2; ++i) {
44         audio.ports[i]     = NULL;
45         audio.portBuffs[i] = NULL;
46     }
47     midi.inport = NULL;
48     midi.jack_sync = false;
49 }
50 
connectServer(string server)51 bool JackEngine::connectServer(string server)
52 {
53     bool autostart_jack = true;
54     if(jackClient)
55         return true;
56 
57     string clientname = "zynaddsubfx";
58     string postfix    = Nio::getPostfix();
59     if(!postfix.empty())
60         clientname += "_" + postfix;
61     jack_status_t jackstatus;
62     bool use_server_name = server.size() && server.compare("default") != 0;
63     jack_options_t jopts = (jack_options_t)
64                            (((!instance_name
65                               && use_server_name) ? JackServerName :
66                              JackNullOption)
67                             | ((autostart_jack) ? JackNullOption :
68                                JackNoStartServer));
69 
70     if(instance_name)
71         jackClient = jack_client_open(instance_name, jopts, &jackstatus);
72     else {
73         if(use_server_name)
74             jackClient = jack_client_open(
75                 clientname.c_str(), jopts, &jackstatus,
76                 server.c_str());
77         else
78             jackClient = jack_client_open(
79                 clientname.c_str(), jopts, &jackstatus);
80     }
81 
82 
83     if(NULL != jackClient)
84         return true;
85     else
86         cerr << "Error, failed to open jack client on server: " << server
87              << " status " << jackstatus << endl;
88     return false;
89 }
90 
connectJack()91 bool JackEngine::connectJack()
92 {
93     connectServer("");
94     if(NULL != jackClient) {
95         setBufferSize(jack_get_buffer_size(jackClient));
96         int chk;
97         jack_set_error_function(_errorCallback);
98         jack_set_info_function(_infoCallback);
99         if(jack_set_buffer_size_callback(jackClient, _bufferSizeCallback, this))
100             cerr << "Error setting the bufferSize callback" << endl;
101         if((chk = jack_set_xrun_callback(jackClient, _xrunCallback, this)))
102             cerr << "Error setting jack xrun callback" << endl;
103         if(jack_set_process_callback(jackClient, _processCallback, this)) {
104             cerr << "Error, JackEngine failed to set process callback" << endl;
105             return false;
106         }
107         if(jack_activate(jackClient)) {
108             cerr << "Error, failed to activate jack client" << endl;
109             return false;
110         }
111 
112         return true;
113     }
114     else
115         cerr << "Error, NULL jackClient through Start()" << endl;
116     return false;
117 }
118 
disconnectJack()119 void JackEngine::disconnectJack()
120 {
121     if(jackClient) {
122         cout << "Deactivating and closing JACK client" << endl;
123 
124         jack_deactivate(jackClient);
125         jack_client_close(jackClient);
126         jackClient = NULL;
127     }
128 }
129 
Start()130 bool JackEngine::Start()
131 {
132     return openMidi() && openAudio();
133 }
134 
Stop()135 void JackEngine::Stop()
136 {
137     stopMidi();
138     stopAudio();
139 }
140 
setMidiEn(bool nval)141 void JackEngine::setMidiEn(bool nval)
142 {
143     if(nval)
144         openMidi();
145     else
146         stopMidi();
147 }
148 
getMidiEn() const149 bool JackEngine::getMidiEn() const
150 {
151     return midi.inport;
152 }
153 
setAudioEn(bool nval)154 void JackEngine::setAudioEn(bool nval)
155 {
156     if(nval)
157         openAudio();
158     else
159         stopAudio();
160 }
161 
getAudioEn() const162 bool JackEngine::getAudioEn() const
163 {
164     return audio.ports[0];
165 }
166 
openAudio()167 bool JackEngine::openAudio()
168 {
169     if(getAudioEn())
170         return true;
171 
172     if(!getMidiEn())
173         if(!connectJack())
174             return false;
175 
176 
177     const char *portnames[] = { "out_1", "out_2" };
178     for(int port = 0; port < 2; ++port)
179         audio.ports[port] = jack_port_register(
180             jackClient,
181             portnames[port],
182             JACK_DEFAULT_AUDIO_TYPE,
183             JackPortIsOutput
184             | JackPortIsTerminal,
185             0);
186     if((NULL != audio.ports[0]) && (NULL != audio.ports[1])) {
187         audio.jackSamplerate = jack_get_sample_rate(jackClient);
188         audio.jackNframes    = jack_get_buffer_size(jackClient);
189         samplerate = audio.jackSamplerate;
190         bufferSize = audio.jackNframes;
191 
192 
193         //Attempt to autoConnect when specified
194         if(Nio::autoConnect) {
195             const char **outPorts = jack_get_ports(
196                 jackClient,
197                 NULL,
198                 NULL,
199                 JackPortIsPhysical
200                 | JackPortIsInput);
201             if(outPorts != NULL) {
202                 //Verify that stereo is available
203                 assert(outPorts[0]);
204                 assert(outPorts[1]);
205 
206                 //Connect to physical outputs
207                 jack_connect(jackClient, jack_port_name(
208                                  audio.ports[0]), outPorts[0]);
209                 jack_connect(jackClient, jack_port_name(
210                                  audio.ports[1]), outPorts[1]);
211             }
212             else
213                 cerr << "Warning, No outputs to autoconnect to" << endl;
214         }
215         midi.jack_sync = true;
216         return true;
217     }
218     else
219         cerr << "Error, failed to register jack audio ports" << endl;
220     midi.jack_sync = false;
221     return false;
222 }
223 
stopAudio()224 void JackEngine::stopAudio()
225 {
226     for(int i = 0; i < 2; ++i) {
227         jack_port_t *port = audio.ports[i];
228         audio.ports[i] = NULL;
229         if(NULL != port)
230             jack_port_unregister(jackClient, port);
231     }
232     midi.jack_sync = false;
233     if(!getMidiEn())
234         disconnectJack();
235 }
236 
openMidi()237 bool JackEngine::openMidi()
238 {
239     if(getMidiEn())
240         return true;
241     if(!getAudioEn())
242         if(!connectJack())
243             return false;
244 
245     midi.inport = jack_port_register(jackClient, "midi_input",
246                                      JACK_DEFAULT_MIDI_TYPE,
247                                      JackPortIsInput | JackPortIsTerminal, 0);
248     return midi.inport;
249 }
250 
stopMidi()251 void JackEngine::stopMidi()
252 {
253     jack_port_t *port = midi.inport;
254     midi.inport = NULL;
255     if(port)
256         jack_port_unregister(jackClient, port);
257 
258     if(!getAudioEn())
259         disconnectJack();
260 }
261 
clientId()262 int JackEngine::clientId()
263 {
264     if(NULL != jackClient)
265         return (long)jack_client_thread_id(jackClient);
266     else
267         return -1;
268 }
269 
clientName()270 string JackEngine::clientName()
271 {
272     if(NULL != jackClient)
273         return string(jack_get_client_name(jackClient));
274     else
275         cerr << "Error, clientName() with null jackClient" << endl;
276     return string("Oh, yoshimi :-(");
277 }
278 
_processCallback(jack_nframes_t nframes,void * arg)279 int JackEngine::_processCallback(jack_nframes_t nframes, void *arg)
280 {
281     return static_cast<JackEngine *>(arg)->processCallback(nframes);
282 }
283 
processCallback(jack_nframes_t nframes)284 int JackEngine::processCallback(jack_nframes_t nframes)
285 {
286     bool okaudio = true;
287 
288     handleMidi(nframes);
289     if((NULL != audio.ports[0]) && (NULL != audio.ports[1]))
290         okaudio = processAudio(nframes);
291     return okaudio ? 0 : -1;
292 }
293 
processAudio(jack_nframes_t nframes)294 bool JackEngine::processAudio(jack_nframes_t nframes)
295 {
296     for(int port = 0; port < 2; ++port) {
297         audio.portBuffs[port] =
298             (jsample_t *)jack_port_get_buffer(audio.ports[port], nframes);
299         if(NULL == audio.portBuffs[port]) {
300             cerr << "Error, failed to get jack audio port buffer: "
301                  << port << endl;
302             return false;
303         }
304     }
305 
306     Stereo<float *> smp = getNext();
307 
308     //Assumes size of smp.l == nframes
309     memcpy(audio.portBuffs[0], smp.l, bufferSize * sizeof(float));
310     memcpy(audio.portBuffs[1], smp.r, bufferSize * sizeof(float));
311     return true;
312 }
313 
_xrunCallback(void *)314 int JackEngine::_xrunCallback(void *)
315 {
316     cerr << "Jack reports xrun" << endl;
317     return 0;
318 }
319 
_errorCallback(const char * msg)320 void JackEngine::_errorCallback(const char *msg)
321 {
322     cerr << "Jack reports error: " << msg << endl;
323 }
324 
_infoCallback(const char * msg)325 void JackEngine::_infoCallback(const char *msg)
326 {
327     cerr << "Jack info message: " << msg << endl;
328 }
329 
_bufferSizeCallback(jack_nframes_t nframes,void * arg)330 int JackEngine::_bufferSizeCallback(jack_nframes_t nframes, void *arg)
331 {
332     return static_cast<JackEngine *>(arg)->bufferSizeCallback(nframes);
333 }
334 
bufferSizeCallback(jack_nframes_t nframes)335 int JackEngine::bufferSizeCallback(jack_nframes_t nframes)
336 {
337     cerr << "Jack buffer resized" << endl;
338     setBufferSize(nframes);
339     return 0;
340 }
341 
handleMidi(unsigned long frames)342 void JackEngine::handleMidi(unsigned long frames)
343 {
344     if(!midi.inport)
345         return;
346     void *midi_buf = jack_port_get_buffer(midi.inport, frames);
347     jack_midi_event_t jack_midi_event;
348     jack_nframes_t    event_index = 0;
349     unsigned char    *midi_data;
350     unsigned char     type;
351 
352     while(jack_midi_event_get(&jack_midi_event, midi_buf,
353                               event_index++) == 0) {
354         MidiEvent ev;
355         midi_data  = jack_midi_event.buffer;
356         type       = midi_data[0] & 0xF0;
357         ev.channel = midi_data[0] & 0x0F;
358         ev.time    = midi.jack_sync ? jack_midi_event.time : 0;
359 
360         switch(type) {
361             case 0x80: /* note-off */
362                 ev.type  = M_NOTE;
363                 ev.num   = midi_data[1];
364                 ev.value = 0;
365                 InMgr::getInstance().putEvent(ev);
366                 break;
367 
368             case 0x90: /* note-on */
369                 ev.type  = M_NOTE;
370                 ev.num   = midi_data[1];
371                 ev.value = midi_data[2];
372                 InMgr::getInstance().putEvent(ev);
373                 break;
374 
375             case 0xA0: /* pressure, aftertouch */
376                 ev.type  = M_PRESSURE;
377                 ev.num   = midi_data[1];
378                 ev.value = midi_data[2];
379                 InMgr::getInstance().putEvent(ev);
380                 break;
381 
382             case 0xB0: /* controller */
383                 ev.type  = M_CONTROLLER;
384                 ev.num   = midi_data[1];
385                 ev.value = midi_data[2];
386                 InMgr::getInstance().putEvent(ev);
387                 break;
388 
389             case 0xC0: /* program change */
390                 ev.type  = M_PGMCHANGE;
391                 ev.num   = midi_data[1];
392                 ev.value = 0;
393                 InMgr::getInstance().putEvent(ev);
394                 break;
395 
396             case 0xE0: /* pitch bend */
397                 ev.type  = M_CONTROLLER;
398                 ev.num   = C_pitchwheel;
399                 ev.value = ((midi_data[2] << 7) | midi_data[1]) - 8192;
400                 InMgr::getInstance().putEvent(ev);
401                 break;
402 
403                 /* XXX TODO: handle MSB/LSB controllers and RPNs and NRPNs */
404         }
405     }
406 }
407