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