/* ZynAddSubFX - a software synthesizer JackEngine.cpp - Jack Driver Copyright (C) 2009 Alan Calvert Copyright (C) 2014 Mark McCurry This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include #include #ifdef JACK_HAS_METADATA_API # include #endif // JACK_HAS_METADATA_API #include "jack_osc.h" #include #include #include #include #include // access() #include // std::istream #include "Nio.h" #include "OutMgr.h" #include "InMgr.h" #include "Misc/Util.h" #include "JackEngine.h" extern char *instance_name; namespace zyncarla { using namespace std; JackEngine::JackEngine(const SYNTH_T &synth) :AudioOut(synth), jackClient(NULL) { name = "JACK"; audio.jackSamplerate = 0; audio.jackNframes = 0; for(int i = 0; i < 2; ++i) { audio.ports[i] = NULL; audio.portBuffs[i] = NULL; } midi.inport = NULL; midi.jack_sync = false; osc.oscport = NULL; } bool JackEngine::connectServer(string server) { bool autostart_jack = true; if(jackClient) return true; string clientname = "zynaddsubfx"; string postfix = Nio::getPostfix(); if(!postfix.empty()) clientname += "_" + postfix; if(Nio::pidInClientName) clientname += "_" + os_pid_as_padded_string(); jack_status_t jackstatus; bool use_server_name = server.size() && server.compare("default") != 0; jack_options_t jopts = (jack_options_t) (((!instance_name && use_server_name) ? JackServerName : JackNullOption) | ((autostart_jack) ? JackNullOption : JackNoStartServer)); if(instance_name) jackClient = jack_client_open(instance_name, jopts, &jackstatus); else { if(use_server_name) jackClient = jack_client_open( clientname.c_str(), jopts, &jackstatus, server.c_str()); else jackClient = jack_client_open( clientname.c_str(), jopts, &jackstatus); } if(NULL != jackClient) return true; else cerr << "Error, failed to open jack client on server: " << server << " status " << jackstatus << endl; return false; } bool JackEngine::connectJack() { connectServer(""); if(NULL != jackClient) { setBufferSize(jack_get_buffer_size(jackClient)); jack_set_error_function(_errorCallback); jack_set_info_function(_infoCallback); if(jack_set_buffer_size_callback(jackClient, _bufferSizeCallback, this)) cerr << "Error setting the bufferSize callback" << endl; if((jack_set_xrun_callback(jackClient, _xrunCallback, this))) cerr << "Error setting jack xrun callback" << endl; if(jack_set_process_callback(jackClient, _processCallback, this)) { cerr << "Error, JackEngine failed to set process callback" << endl; return false; } if(jack_activate(jackClient)) { cerr << "Error, failed to activate jack client" << endl; return false; } return true; } else cerr << "Error, NULL jackClient through Start()" << endl; return false; } void JackEngine::disconnectJack() { if(jackClient) { cout << "Deactivating and closing JACK client" << endl; jack_deactivate(jackClient); jack_client_close(jackClient); jackClient = NULL; } } bool JackEngine::Start() { return openMidi() && openAudio(); } void JackEngine::Stop() { stopMidi(); stopAudio(); } void JackEngine::setMidiEn(bool nval) { if(nval) openMidi(); else stopMidi(); } bool JackEngine::getMidiEn() const { return midi.inport; } void JackEngine::setAudioEn(bool nval) { if(nval) openAudio(); else stopAudio(); } bool JackEngine::getAudioEn() const { return audio.ports[0]; } bool JackEngine::openAudio() { if(getAudioEn()) return true; if(!getMidiEn()) if(!connectJack()) return false; const char *portnames[] = { "out_1", "out_2" }; for(int port = 0; port < 2; ++port) audio.ports[port] = jack_port_register( jackClient, portnames[port], JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); if((NULL != audio.ports[0]) && (NULL != audio.ports[1])) { audio.jackSamplerate = jack_get_sample_rate(jackClient); audio.jackNframes = jack_get_buffer_size(jackClient); samplerate = audio.jackSamplerate; bufferSize = audio.jackNframes; //Attempt to autoConnect when specified if(Nio::autoConnect) { const char **outPorts = jack_get_ports( jackClient, NULL, NULL, JackPortIsPhysical | JackPortIsInput); if(outPorts != NULL) { //Verify that stereo is available assert(outPorts[0]); assert(outPorts[1]); //Connect to physical outputs jack_connect(jackClient, jack_port_name( audio.ports[0]), outPorts[0]); jack_connect(jackClient, jack_port_name( audio.ports[1]), outPorts[1]); } else cerr << "Warning, No outputs to autoconnect to" << endl; } midi.jack_sync = true; osc.oscport = jack_port_register(jackClient, "osc", JACK_DEFAULT_OSC_TYPE, JackPortIsInput, 0); #ifdef JACK_HAS_METADATA_API jack_uuid_t uuid = jack_port_uuid(osc.oscport); jack_set_property(jackClient, uuid, "http://jackaudio.org/metadata/event-types", JACK_EVENT_TYPE__OSC, "text/plain"); #endif // JACK_HAS_METADATA_API return true; } else cerr << "Error, failed to register jack audio ports" << endl; midi.jack_sync = false; return false; } void JackEngine::stopAudio() { for(int i = 0; i < 2; ++i) { jack_port_t *port = audio.ports[i]; audio.ports[i] = NULL; if(jackClient != NULL && NULL != port) jack_port_unregister(jackClient, port); } midi.jack_sync = false; if(osc.oscport) { if (jackClient != NULL) { jack_port_unregister(jackClient, osc.oscport); #ifdef JACK_HAS_METADATA_API jack_uuid_t uuid = jack_port_uuid(osc.oscport); jack_remove_property(jackClient, uuid, "http://jackaudio.org/metadata/event-types"); #endif // JACK_HAS_METADATA_API } } if(!getMidiEn()) disconnectJack(); } bool JackEngine::openMidi() { if(getMidiEn()) return true; if(!getAudioEn()) if(!connectJack()) return false; midi.inport = jack_port_register(jackClient, "midi_input", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput | JackPortIsTerminal, 0); return midi.inport; } void JackEngine::stopMidi() { jack_port_t *port = midi.inport; midi.inport = NULL; if(port) jack_port_unregister(jackClient, port); if(!getAudioEn()) disconnectJack(); } int JackEngine::clientId() { if(NULL != jackClient) return (long)jack_client_thread_id(jackClient); else return -1; } string JackEngine::clientName() { if(NULL != jackClient) return string(jack_get_client_name(jackClient)); else cerr << "Error, clientName() with null jackClient" << endl; return string("Oh, yoshimi :-("); } int JackEngine::_processCallback(jack_nframes_t nframes, void *arg) { return static_cast(arg)->processCallback(nframes); } int JackEngine::processCallback(jack_nframes_t nframes) { bool okaudio = true; handleMidi(nframes); if((NULL != audio.ports[0]) && (NULL != audio.ports[1])) okaudio = processAudio(nframes); return okaudio ? 0 : -1; } bool JackEngine::processAudio(jack_nframes_t nframes) { //handle rt osc events first void *oscport = jack_port_get_buffer(osc.oscport, nframes); size_t osc_packets = jack_osc_get_event_count(oscport); for(size_t i = 0; i < osc_packets; ++i) { jack_osc_event_t event; if(jack_osc_event_get(&event, oscport, i)) continue; if(*event.buffer!='/') //Bundles are unhandled continue; //TODO validate message length OutMgr::getInstance().applyOscEventRt((char*)event.buffer); } for(int port = 0; port < 2; ++port) { audio.portBuffs[port] = (jsample_t *)jack_port_get_buffer(audio.ports[port], nframes); if(NULL == audio.portBuffs[port]) { cerr << "Error, failed to get jack audio port buffer: " << port << endl; return false; } } Stereo smp = getNext(); //Assumes size of smp.l == nframes memcpy(audio.portBuffs[0], smp.l, bufferSize * sizeof(float)); memcpy(audio.portBuffs[1], smp.r, bufferSize * sizeof(float)); return true; } int JackEngine::_xrunCallback(void *) { cerr << "Jack reports xrun" << endl; return 0; } void JackEngine::_errorCallback(const char *msg) { cerr << "Jack reports error: " << msg << endl; } void JackEngine::_infoCallback(const char *msg) { cerr << "Jack info message: " << msg << endl; } int JackEngine::_bufferSizeCallback(jack_nframes_t nframes, void *arg) { return static_cast(arg)->bufferSizeCallback(nframes); } int JackEngine::bufferSizeCallback(jack_nframes_t nframes) { cerr << "Jack buffer resized" << endl; setBufferSize(nframes); return 0; } void JackEngine::handleMidi(unsigned long frames) { if(!midi.inport) return; void *midi_buf = jack_port_get_buffer(midi.inport, frames); jack_midi_event_t jack_midi_event; jack_nframes_t event_index = 0; unsigned char *midi_data; unsigned char type; while(jack_midi_event_get(&jack_midi_event, midi_buf, event_index++) == 0) { MidiEvent ev; midi_data = jack_midi_event.buffer; type = midi_data[0] & 0xF0; ev.channel = midi_data[0] & 0x0F; ev.time = midi.jack_sync ? jack_midi_event.time : 0; switch(type) { case 0x80: /* note-off */ ev.type = M_NOTE; ev.num = midi_data[1]; ev.value = 0; InMgr::getInstance().putEvent(ev); break; case 0x90: /* note-on */ ev.type = M_NOTE; ev.num = midi_data[1]; ev.value = midi_data[2]; InMgr::getInstance().putEvent(ev); break; case 0xA0: /* pressure, aftertouch */ ev.type = M_PRESSURE; ev.num = midi_data[1]; ev.value = midi_data[2]; InMgr::getInstance().putEvent(ev); break; case 0xB0: /* controller */ ev.type = M_CONTROLLER; ev.num = midi_data[1]; ev.value = midi_data[2]; InMgr::getInstance().putEvent(ev); break; case 0xC0: /* program change */ ev.type = M_PGMCHANGE; ev.num = midi_data[1]; ev.value = 0; InMgr::getInstance().putEvent(ev); break; case 0xE0: /* pitch bend */ ev.type = M_CONTROLLER; ev.num = C_pitchwheel; ev.value = ((midi_data[2] << 7) | midi_data[1]) - 8192; InMgr::getInstance().putEvent(ev); break; /* XXX TODO: handle MSB/LSB controllers and RPNs and NRPNs */ } } } }