//============================================================================= // MusE // Linux Music Editor // $Id:$ // // Copyright (C) 2002-2006 by Werner Schweer and others // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //============================================================================= #include "config.h" #include "al/al.h" #include "al/tempo.h" #include "audio.h" #include "globals.h" #include "song.h" #include "jackaudio.h" #include "track.h" #include "midiinport.h" #ifdef VST_SUPPORT #include #endif JackAudio* jackAudio; //--------------------------------------------------------- // init //--------------------------------------------------------- bool JackAudio::init() { return true; } //--------------------------------------------------------- // jack_thread_init //--------------------------------------------------------- static void jack_thread_init (void* /*data*/) { #ifdef VST_SUPPORT if (loadVST) fst_adopt_thread(); #endif } //--------------------------------------------------------- // timebase_callback //--------------------------------------------------------- static void timebase_callback(jack_transport_state_t /* state */, jack_nframes_t /* nframes */, jack_position_t* pos, int /* new_pos */, void*) { AL::Pos p(pos->frame, AL::FRAMES); pos->valid = JackPositionBBT; p.mbt(&pos->bar, &pos->beat, &pos->tick); pos->bar++; pos->beat++; pos->bar_start_tick = Pos(pos->bar, 0, 0).tick(); // // dummy: // pos->beats_per_bar = 4; pos->beat_type = 4; pos->ticks_per_beat = 384; int tempo = AL::tempomap.tempo(p.tick()); pos->beats_per_minute = (60000000.0 / tempo) * AL::tempomap.globalTempo()/100.0; } //--------------------------------------------------------- // processAudio // JACK callback //--------------------------------------------------------- int JackAudio::processAudio(jack_nframes_t frames, void*) { jackAudio->_frameCounter += frames; int jackState = jackAudio->getTransportState(); segmentSize = frames; if (audioState == AUDIO_RUNNING) audio->process((unsigned long)frames, jackState); else if (audioState == AUDIO_STOP) { if (debugMsg) puts("jack calling when audio is stopped!\n"); } else if (audioState == AUDIO_START1) audioState = AUDIO_START2; return 0; } //--------------------------------------------------------- // getTransportState //--------------------------------------------------------- int JackAudio::getTransportState() { int jackState; transportState = jack_transport_query(_client, &pos); switch (jackAudio->transportState) { case JackTransportStopped: jackState = Audio::STOP; break; case JackTransportLooping: case JackTransportRolling: jackState = Audio::PLAY; break; case JackTransportStarting: jackState = Audio::START_PLAY; break; default: jackState = Audio::STOP; break; } return jackState; } //--------------------------------------------------------- // processSync // return TRUE (non-zero) when ready to roll. //--------------------------------------------------------- static int processSync(jack_transport_state_t state, jack_position_t* pos, void*) { int audioState = Audio::STOP; switch (state) { case JackTransportStopped: audioState = Audio::STOP; break; case JackTransportLooping: case JackTransportRolling: audioState = Audio::PLAY; break; case JackTransportStarting: audioState = Audio::START_PLAY; break; } unsigned frame = pos->frame; return audio->sync(audioState, frame); } //--------------------------------------------------------- // processShutdown //--------------------------------------------------------- static void processShutdown(void*) { audio->shutdown(); for (int i = 0; i < 10; ++i) { if (audioState == AUDIO_STOP) break; sleep(1); } if (audioState == AUDIO_RUNNING) fprintf(stderr, "MusE: sequencer still running, something is very wrong.\n"); jackAudio->zeroClientPtr(); // jack disconnect client no longer valid } //--------------------------------------------------------- // jackError //--------------------------------------------------------- static void jackError(const char* s) { fprintf(stderr, "JACK ERROR: %s\n", s); } //--------------------------------------------------------- // noJackError //--------------------------------------------------------- static void noJackError(const char* /* s */) { } //--------------------------------------------------------- // JackAudio //--------------------------------------------------------- JackAudio::JackAudio(jack_client_t* cl, char* name) : AudioDriver() { strcpy(jackRegisteredName, name); _client = cl; _frameCounter = 0; } //--------------------------------------------------------- // ~JackAudio //--------------------------------------------------------- JackAudio::~JackAudio() { if (_client) { if (jack_client_close(_client)) { fprintf(stderr, "jack_client_close() failed: %s\n", strerror(errno)); } } _client = 0; } //--------------------------------------------------------- // getJackName() //--------------------------------------------------------- char* JackAudio::getJackName() { return jackRegisteredName; } //--------------------------------------------------------- // restart //--------------------------------------------------------- bool JackAudio::restart() { printf("JackAudio::restart\n"); _client = jack_client_new(jackRegisteredName); if (!_client) return true; registerClient(); return false; } //--------------------------------------------------------- // bufsize_callback //--------------------------------------------------------- static int bufsize_callback(jack_nframes_t n, void*) { printf("JACK: buffersize changed %d\n", n); return 0; } //--------------------------------------------------------- // freewheel_callback //--------------------------------------------------------- static void freewheel_callback(int starting, void*) { audio->setFreewheel(starting); } //--------------------------------------------------------- // srate_callback //--------------------------------------------------------- static int srate_callback(jack_nframes_t n, void*) { if (debugMsg) printf("JACK: sample rate changed: %d\n", n); return 0; } //--------------------------------------------------------- // registration_callback //--------------------------------------------------------- static void registration_callback(jack_port_id_t, int, void*) { if (debugMsg) printf("JACK: registration changed\n"); } //--------------------------------------------------------- // graph_callback // this is called from jack when the connections // changed //--------------------------------------------------------- static int graph_callback(void*) { // we cannot call JackAudio::graphChanged() from this // context, so we send a message to the gui thread which in turn // calls graphChanged() audio->sendMsgToGui(MSG_GRAPH_CHANGED); if (debugMsg) printf("JACK: graph changed!\n"); return 0; } //--------------------------------------------------------- // JackAudio::graphChanged // this is called from song in gui context triggered // by graph_callback() //--------------------------------------------------------- void JackAudio::graphChanged() { RouteList rr, ra; InputList* il = song->inputs(); for (iAudioInput ii = il->begin(); ii != il->end(); ++ii) { AudioInput* it = *ii; int channels = it->channels(); RouteList* irl = it->inRoutes(); for (int channel = 0; channel < channels; ++channel) { jack_port_t* port = it->jackPort(channel).jackPort(); if (port == 0) continue; const char** ports = jack_port_get_all_connections(_client, port); //--------------------------------------- // check for disconnects //--------------------------------------- foreach (const Route& r, *irl) { if (r.dst.channel != channel) continue; const char* name = jack_port_name(r.src.port.jackPort()); bool found = false; for (const char** pn = ports; pn && *pn; ++pn) { if (strcmp(*pn, name) == 0) { found = true; break; } } if (!found) rr.append(r); } //--------------------------------------- // check for connects //--------------------------------------- if (ports) { for (const char** pn = ports; *pn; ++pn) { bool found = false; foreach(const Route& r, *irl) { if (r.dst.channel != channel) continue; const char* name = jack_port_name(r.src.port.jackPort()); if (strcmp(*pn, name) == 0) { found = true; break; } } if (!found) { Route a; Port port(jack_port_by_name(_client, *pn)); a.src = RouteNode(port, -1, RouteNode::AUDIOPORT); a.dst = RouteNode(it, channel); ra.append(a); } } free(ports); } } } // printf(" input: remove %d add %d routes\n", rr.size(), ra.size()); foreach(Route r, rr) audio->msgRemoveRoute1(r); foreach(Route r, ra) audio->msgAddRoute1(r); rr.clear(); ra.clear(); OutputList* ol = song->outputs(); for (iAudioOutput ii = ol->begin(); ii != ol->end(); ++ii) { AudioOutput* it = *ii; int channels = it->channels(); for (int channel = 0; channel < channels; ++channel) { jack_port_t* port = it->jackPort(channel).jackPort(); if (port == 0) continue; const char** ports = jack_port_get_all_connections(_client, port); RouteList* rl = it->outRoutes(); //--------------------------------------- // check for disconnects //--------------------------------------- foreach(const Route& r, *rl) { if (r.src.channel != channel) continue; const char* name = jack_port_name(r.dst.port.jackPort()); bool found = false; const char** pn = ports; while (pn && *pn) { if (strcmp(*pn, name) == 0) { found = true; break; } ++pn; } if (!found) rr.append(r); } //--------------------------------------- // check for connects //--------------------------------------- if (ports) { const char** pn = ports; while (*pn) { bool found = false; foreach (const Route& r, *rl) { if (r.src.channel != channel) continue; const char* name = jack_port_name(r.dst.port.jackPort()); if (strcmp(*pn, name) == 0) { found = true; break; } } if (!found) { Route a; Port port(jack_port_by_name(_client, *pn)); a.src = RouteNode(it, channel, RouteNode::TRACK); a.dst = RouteNode(port, -1, RouteNode::AUDIOPORT); ra.append(a); } ++pn; } free(ports); } } } // printf(" output: remove %d add %d routes\n", rr.size(), ra.size()); foreach(Route r, rr) audio->msgRemoveRoute1(r); foreach(Route r, ra) audio->msgAddRoute1(r); } //--------------------------------------------------------- // register //--------------------------------------------------------- void JackAudio::registerClient() { jack_set_process_callback(_client, processAudio, 0); jack_set_sync_callback(_client, processSync, 0); jack_on_shutdown(_client, processShutdown, 0); jack_set_buffer_size_callback(_client, bufsize_callback, 0); jack_set_sample_rate_callback(_client, srate_callback, 0); jack_set_port_registration_callback(_client, registration_callback, 0); jack_set_graph_order_callback(_client, graph_callback, 0); jack_set_freewheel_callback (_client, freewheel_callback, 0); jack_set_thread_init_callback(_client, (JackThreadInitCallback) jack_thread_init, 0); jack_set_timebase_callback(_client, 0, timebase_callback, 0); } //--------------------------------------------------------- // registerInPort //--------------------------------------------------------- Port JackAudio::registerInPort(const QString& name, bool midi) { const char* type = midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE; Port p(jack_port_register(_client, name.toLatin1().data(), type, JackPortIsInput, 0)); // printf("JACK: registerInPort<%s>: <%s> %p\n", type, name.toLatin1().data(), p.jackPort()); if (!p.jackPort()) p.setZero(); return p; } //--------------------------------------------------------- // registerOutPort //--------------------------------------------------------- Port JackAudio::registerOutPort(const QString& name, bool midi) { const char* type = midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE; Port p(jack_port_register(_client, name.toLatin1().data(), type, JackPortIsOutput, 0)); // printf("JACK: registerOutPort<%s>: <%s> %p\n", type, name.toLatin1().data(), p.jackPort()); if (!p.jackPort()) p.setZero(); return p; } //--------------------------------------------------------- // exitJackAudio //--------------------------------------------------------- void exitJackAudio() { if (jackAudio) delete jackAudio; } //--------------------------------------------------------- // connect // return false on error //--------------------------------------------------------- bool JackAudio::connect(Port src, Port dst) { if (src.isZero() || dst.isZero()) { fprintf(stderr, "JackAudio::connect(1): unknown jack ports (%d-%d)\n", src.isZero(), dst.isZero()); return false; } const char* sn = jack_port_name(src.jackPort()); const char* dn = jack_port_name(dst.jackPort()); if (debugMsg) printf("jack connect <%s>%p - <%s>%p\n", sn, src.jackPort(), dn, dst.jackPort()); if (sn == 0 || dn == 0) { fprintf(stderr, "JackAudio::connect(2): unknown jack ports\n"); return false; } int rv = jack_connect(_client, sn, dn); if (rv) { fprintf(stderr, "%d: jack connect <%s> - <%s> failed\n", rv, sn, dn); if (rv == EEXIST) fprintf(stderr, " connection already made\n"); else { int pf = jack_port_flags(src.jackPort()); if (!(pf & JackPortIsOutput)) fprintf(stderr, " src is not an output port\n"); pf = jack_port_flags(dst.jackPort()); if (!(pf & JackPortIsInput)) fprintf(stderr, " dst is not an input port\n"); } return false; } return true; } //--------------------------------------------------------- // disconnect //--------------------------------------------------------- bool JackAudio::disconnect(Port src, Port dst) { const char* sn = jack_port_name(src.jackPort()); const char* dn = jack_port_name(dst.jackPort()); if (debugMsg) printf("jack disconnect <%s>%p - <%s>%p\n", sn, src.jackPort(), dn, dst.jackPort()); if (sn == 0 || dn == 0) { fprintf(stderr, "JackAudio::disconnect: unknown jack ports\n"); return false; } if (jack_disconnect(_client, sn, dn)) { fprintf(stderr, "jack disconnect <%s> - <%s> failed\n", sn, dn); return false; } return true; } //--------------------------------------------------------- // start //--------------------------------------------------------- void JackAudio::start(int) { if (jack_activate(_client)) { fprintf (stderr, "JACK: cannot activate client\n"); exit(-1); } } //--------------------------------------------------------- // stop //--------------------------------------------------------- void JackAudio::stop() { if (_client == 0) return; if (jack_deactivate(_client)) fprintf (stderr, "JACK: cannot deactivate client\n"); } //--------------------------------------------------------- // outputPorts //--------------------------------------------------------- QList JackAudio::outputPorts(bool midi) { const char* type = midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE; const char** ports = jack_get_ports(_client, 0, type, JackPortIsOutput); QList clientList; for (const char** p = ports; p && *p; ++p) { jack_port_t* port = jack_port_by_name(_client, *p); char buffer[128]; strncpy(buffer, *p, 128); if (strncmp(buffer, "MusE", 4) == 0) continue; PortName pn; pn.name = QString(buffer); pn.port = Port(port); clientList.append(pn); } return clientList; } //--------------------------------------------------------- // inputPorts //--------------------------------------------------------- QList JackAudio::inputPorts(bool midi) { const char* type = midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE; const char** ports = jack_get_ports(_client, 0, type, JackPortIsInput); QList clientList; for (const char** p = ports; p && *p; ++p) { jack_port_t* port = jack_port_by_name(_client, *p); char buffer[128]; strncpy(buffer, *p, 128); if (strncmp(buffer, "MusE", 4) == 0) continue; PortName pn; pn.name = QString(buffer); pn.port = Port(port); clientList.append(pn); } return clientList; } //--------------------------------------------------------- // portName //--------------------------------------------------------- QString JackAudio::portName(Port port) { const char* nameStrPtr = jack_port_name(port.jackPort()); QString s(nameStrPtr); return s; } //--------------------------------------------------------- // unregisterPort //--------------------------------------------------------- void JackAudio::unregisterPort(Port p) { if (_client) { // printf("JACK: unregister Port %p\n", p); if (jack_port_unregister(_client, p.jackPort())) fprintf(stderr, "jack unregister port %p failed\n", p.jackPort()); } } //--------------------------------------------------------- // setFreewheel //--------------------------------------------------------- void JackAudio::setFreewheel(bool f) { // printf("JACK: setFreewheel %d\n", f); jack_set_freewheel(_client, f); } //--------------------------------------------------------- // startTransport //--------------------------------------------------------- void JackAudio::startTransport() { jack_transport_start(_client); } //--------------------------------------------------------- // stopTransport //--------------------------------------------------------- void JackAudio::stopTransport() { if (_client) jack_transport_stop(_client); } //--------------------------------------------------------- // seekTransport //--------------------------------------------------------- void JackAudio::seekTransport(unsigned frame) { jack_transport_locate(_client, frame); } //--------------------------------------------------------- // findPort //--------------------------------------------------------- Port JackAudio::findPort(const QString& name) { if (_client == 0) { printf("JackAudio(%p)::findPort(%s): _client==0\n", this, qPrintable(name)); return Port(); } jack_port_t* port = jack_port_by_name(_client, name.toLatin1().data()); return (port == 0) ? Port() : Port(port); } //--------------------------------------------------------- // realtimePriority // return zero if not running realtime // can only be called if JACK client thread is already // running //--------------------------------------------------------- int JackAudio::realtimePriority() const { pthread_t t = jack_client_thread_id(_client); int policy; struct sched_param param; memset(¶m, 0, sizeof(param)); int rv = pthread_getschedparam(t, &policy, ¶m); if (rv) { perror("MusE: get jack schedule parameter"); return 0; } if (policy != SCHED_FIFO) { printf("JACK is not running realtime\n"); return 0; } return param.sched_priority; } //--------------------------------------------------------- // initJackAudio // return true if JACK not found //--------------------------------------------------------- bool initJackAudio() { if (debugMsg) { fprintf(stderr, "init Jack Audio\n"); jack_set_error_function(jackError); } else jack_set_error_function(noJackError); jack_client_t* client = 0; jack_status_t status; jack_options_t options = JackNullOption; client = jack_client_open("MusE", options, &status); if (!client) { if (status & JackServerStarted) printf("jack server started...\n"); if (status & JackServerFailed) printf("cannot connect to jack server\n"); if (status & JackServerError) printf("communication with jack server failed\n"); if (status & JackShmFailure) printf("jack cannot access shared memory\n"); if (status & JackVersionError) printf("jack server has wrong version\n"); printf("cannot create jack client\n"); return true; } if (debugMsg) fprintf(stderr, "init Jack Audio: register device\n"); jack_set_error_function(jackError); if (debugMsg) fprintf(stderr, "init Jack Audio: register device\n"); jackAudio = new JackAudio(client, jack_get_client_name(client)); if (debugMsg) fprintf(stderr, "init Jack Audio: register client\n"); jackAudio->registerClient(); AL::sampleRate = jack_get_sample_rate(client); segmentSize = jack_get_buffer_size(client); audioDriver = jackAudio; return false; } //--------------------------------------------------------- // putEvent //--------------------------------------------------------- void JackAudio::putEvent(Port port, const MidiEvent& e) { if (midiOutputTrace) { printf("MidiOut<%s>: jackMidi: ", portName(port).toLatin1().data()); e.dump(); } void* pb = jack_port_get_buffer(port.jackPort(), segmentSize); int ft = e.time() - _frameCounter; if (ft < 0) ft = 0; if (ft >= (int)segmentSize) { printf("JackAudio::putEvent: time out of range %d(seg=%d)\n", ft, segmentSize); if (ft > (int)segmentSize) ft = segmentSize - 1; } switch(e.type()) { case ME_NOTEON: case ME_NOTEOFF: case ME_POLYAFTER: case ME_CONTROLLER: case ME_PITCHBEND: { unsigned char* p = jack_midi_event_reserve(pb, ft, 3); if (p == 0) { fprintf(stderr, "JackMidi: buffer overflow, event lost\n"); return; } p[0] = e.type() | e.channel(); p[1] = e.dataA(); p[2] = e.dataB(); } break; case ME_PROGRAM: case ME_AFTERTOUCH: { unsigned char* p = jack_midi_event_reserve(pb, ft, 2); if (p == 0) { fprintf(stderr, "JackMidi: buffer overflow, event lost\n"); return; } p[0] = e.type() | e.channel(); p[1] = e.dataA(); } break; case ME_SYSEX: { const unsigned char* data = e.data(); int len = e.len(); unsigned char* p = jack_midi_event_reserve(pb, ft, len+2); if (p == 0) { fprintf(stderr, "JackMidi: buffer overflow, event lost\n"); return; } p[0] = 0xf0; p[len+1] = 0xf7; memcpy(p+1, data, len); } break; case ME_SONGPOS: case ME_CLOCK: case ME_START: case ME_CONTINUE: case ME_STOP: printf("JackMidi: event type %x not supported\n", e.type()); break; } } //--------------------------------------------------------- // startMidiCycle //--------------------------------------------------------- void JackAudio::startMidiCycle(Port port) { void* port_buf = jack_port_get_buffer(port.jackPort(), segmentSize); jack_midi_clear_buffer(port_buf); } //--------------------------------------------------------- // collectMidiEvents //--------------------------------------------------------- void JackAudio::collectMidiEvents(MidiInPort* track, Port port) { void* port_buf = jack_port_get_buffer(port.jackPort(), segmentSize); jack_midi_event_t event; jack_nframes_t eventCount = jack_midi_get_event_count(port_buf); for (jack_nframes_t i = 0; i < eventCount; ++i) { jack_midi_event_get(&event, port_buf, i); track->eventReceived(&event); } }