//============================================================================= // 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 #include "al/al.h" #include "al/xml.h" #include "al/tempo.h" #include "muse.h" #include "synth.h" #include "midi.h" #include "synti/libsynti/mess.h" #include "song.h" #include "audio.h" #include "event.h" #include "midievent.h" #include "audio.h" #include "midictrl.h" #include "instruments/minstrument.h" #include "audiodev.h" std::vector synthis; // array of available synthis extern void connectNodes(AudioTrack*, AudioTrack*); //--------------------------------------------------------- // description //--------------------------------------------------------- const char* MessSynth::description() const { return descr ? descr->description : ""; } //--------------------------------------------------------- // version //--------------------------------------------------------- const char* MessSynth::version() const { return descr ? descr->version : ""; } bool MessSynthIF::guiVisible() const { return _mess ? _mess->guiVisible() : false; } void MessSynthIF::showGui(bool v) { if (v == guiVisible()) return; if (_mess) _mess->showGui(v); } bool MessSynthIF::hasGui() const { if (_mess) return _mess->hasGui(); return false; } MidiEvent MessSynthIF::receiveEvent() { if (_mess) return _mess->receiveEvent(); return MidiEvent(); } int MessSynthIF::eventsPending() const { if (_mess) return _mess->eventsPending(); return 0; } void MessSynthIF::getGeometry(int* x, int* y, int* w, int* h) const { if (_mess) _mess->getGeometry(x, y, w, h); } void MessSynthIF::setGeometry(int x, int y, int w, int h) { if (_mess) _mess->setGeometry(x, y, w, h); } //--------------------------------------------------------- // findSynth // search for synthesizer base class //--------------------------------------------------------- Synth* findSynth(const QString& sclass) { for (std::vector::iterator i = synthis.begin(); i != synthis.end(); ++i) { if ((*i)->name() == sclass) return *i; } printf("synthi class <%s> not found\n", sclass.toLatin1().data()); return 0; } //--------------------------------------------------------- // Synth //--------------------------------------------------------- Synth::Synth(const QFileInfo* fi, QString s) : info(*fi), _name(s) { _instances = 0; } //--------------------------------------------------------- // instantiate //--------------------------------------------------------- void* MessSynth::instantiate(const QString& instanceName) { ++_instances; const char* path = strdup(info.filePath().toAscii().data()); // load Synti dll if (debugMsg) printf(" load synti <%s>\n", path); void* handle = dlopen(path, RTLD_NOW); // void* handle = dlopen(path, RTLD_LAZY); if (handle == 0) { fprintf(stderr, "Synth::instantiate: dlopen(%s) failed: %s\n", path, dlerror()); delete path; return 0; } typedef const MESS* (*MESS_Function)(); MESS_Function msynth = (MESS_Function)dlsym(handle, "mess_descriptor"); if (!msynth) { const char *txt = dlerror(); if (txt) { fprintf(stderr, "Unable to find msynth_descriptor() function in plugin " "library file \"%s\": %s.\n" "Are you sure this is a MESS plugin file?\n", path, txt); delete path; return 0; } } delete path; descr = msynth(); if (descr == 0) { fprintf(stderr, "Synth::instantiate: no MESS descr found\n"); return 0; } Mess* mess = descr->instantiate(AL::sampleRate, instanceName.toLatin1().data()); return mess; } //--------------------------------------------------------- // SynthI //--------------------------------------------------------- SynthI::SynthI() : AudioTrack() { track = this; synthesizer = 0; _sif = 0; // setVolume(1.0); // setPan(0.0); setReadonly(true); // midi instrument cannot be edited } //--------------------------------------------------------- // ~SynthI //--------------------------------------------------------- SynthI::~SynthI() { deactivate2(); deactivate3(); } //--------------------------------------------------------- // setName //--------------------------------------------------------- void SynthI::setName(const QString& s) { Track::setName(s); } //--------------------------------------------------------- // init //--------------------------------------------------------- bool MessSynthIF::init(Synth* s, SynthI* si) { _mess = (Mess*)((MessSynth*)s)->instantiate(si->name()); return (_mess == 0); } //--------------------------------------------------------- // channels //--------------------------------------------------------- int MessSynthIF::channels() const { return _mess->channels(); } //--------------------------------------------------------- // createSIF //--------------------------------------------------------- SynthIF* MessSynth::createSIF(SynthI* si) { MessSynthIF* sif = new MessSynthIF(si); sif->init(this, si); return sif; } //--------------------------------------------------------- // initInstance // returns false on success //--------------------------------------------------------- bool SynthI::initInstance(Synth* s) { synthesizer = s; _sif = s->createSIF(this); setIName(name()); // set instrument name int n = _sif->channels(); AudioTrack::setChannels(n); //--------------------------------------------------- // read available controller from synti //--------------------------------------------------- int id = 0; MidiControllerList* cl = MidiInstrument::controller(); for (;;) { const char* name; int ctrl; int min; int max; id = _sif->getControllerInfo(id, &name, &ctrl, &min, &max); if (id == 0) break; MidiController* c = new MidiController(QString(name), ctrl, min, max, 0); cl->push_back(c); } EventList* iel = midiState(); if (!iel->empty()) { for (iEvent i = iel->begin(); i != iel->end(); ++i) { Event ev = i->second; MidiEvent pev(0, 0, ev); if (_sif->putEvent(pev)) putFifo.put(pev); // save for later retry } iel->clear(); } int idx = 0; for (std::vector::iterator i = initParams.begin(); i != initParams.end(); ++i, ++idx) _sif->setParameter(idx, *i); return false; } //--------------------------------------------------------- // getControllerInfo //--------------------------------------------------------- int MessSynthIF::getControllerInfo(int id, const char** name, int* ctrl, int* min, int* max) { return _mess->getControllerInfo(id, name, ctrl, min, max); } //--------------------------------------------------------- // SynthI::deactivate //--------------------------------------------------------- void SynthI::deactivate2() { removeMidiInstrument(this); } //--------------------------------------------------------- // deactivate3 //--------------------------------------------------------- void SynthI::deactivate3() { delete _sif; _sif = 0; synthesizer->incInstances(-1); } void MessSynthIF::deactivate3() { if (_mess) { delete _mess; _mess = 0; } } MessSynthIF::~MessSynthIF() { deactivate3(); } //--------------------------------------------------------- // initMidiSynth // search for software synthis and advertise //--------------------------------------------------------- void initMidiSynth() { QString s = museGlobalLib + "/synthi"; #ifdef __APPLE__ QDir pluginDir(s, QString("*.dylib"), 0, QDir::Files); #else QDir pluginDir(s, QString("*.so"), 0, QDir::Files); #endif if (debugMsg) printf("searching for software synthesizer in <%s>\n", s.toLatin1().data()); if (pluginDir.exists()) { QFileInfoList list = pluginDir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fi = list.at(i); synthis.push_back(new MessSynth(&fi)); } if (debugMsg) printf("%zd soft synth found\n", synthis.size()); } } //--------------------------------------------------------- // write //--------------------------------------------------------- void SynthI::write(Xml& xml) const { xml.stag("SynthI"); AudioTrack::writeProperties(xml); xml.tag("class", synth()->name()); //--------------------------------------------- // if soft synth is attached to a midi port, // write out port number //--------------------------------------------- if (hasGui()) { xml.tag("guiVisible", guiVisible()); int x, y, w, h; w = 0; h = 0; getGeometry(&x, &y, &w, &h); if (h || w) xml.tag("geometry", QRect(x, y, w, h)); } _sif->write(xml); xml.etag("SynthI"); } void MessSynthIF::write(Xml& xml) const { //--------------------------------------------- // dump current state of synth //--------------------------------------------- int len = 0; const unsigned char* p; _mess->getInitData(&len, &p); if (len) { xml.stag("midistate"); xml.stag(QString("event type=\"%1\" datalen=\"%2\"").arg(Sysex).arg(len)); xml.dump(len, p); xml.etag("event"); xml.etag("midistate"); } } //--------------------------------------------------------- // SynthI::read //--------------------------------------------------------- void SynthI::read(QDomNode node) { QString sclass; int port = -1; bool startGui = false; QRect r; for (; !node.isNull(); node = node.nextSibling()) { QDomElement e = node.toElement(); QString tag(e.tagName()); if (tag == "class") sclass = e.text(); else if (tag == "port") port = e.text().toInt(); else if (tag == "guiVisible") startGui = e.text().toInt(); else if (tag == "midistate") readMidiState(node.firstChild()); else if (tag == "param") { float val = e.text().toFloat(); initParams.push_back(val); } else if (tag == "geometry") r = AL::readGeometry(node); else if (AudioTrack::readProperties(node)) { printf("MusE:SynthI: unknown tag %s\n", e.tagName().toLatin1().data()); } } Synth* s = findSynth(sclass); if (s == 0) return; if (initInstance(s)) return; song->insertTrack0(this, -1); setGeometry(r.x(), r.y(), r.width(), r.height()); showGui(startGui); } //--------------------------------------------------------- // getPatchName //--------------------------------------------------------- QString MessSynthIF::getPatchName(int channel, int prog) { if (_mess) return _mess->getPatchName(channel, prog, 0); return ""; } //--------------------------------------------------------- // populatePatchPopup //--------------------------------------------------------- void MessSynthIF::populatePatchPopup(QMenu* menu, int ch) { menu->clear(); const MidiPatch* mp = _mess->getPatchInfo(ch, 0); QMenu *hm = NULL, *lm = NULL; while (mp) { switch(mp->typ) { case MP_TYPE_HBANK : hm = menu->addMenu(QString(mp->name)); break; case MP_TYPE_LBANK : if(hm) lm = hm->addMenu(QString(mp->name)); else lm = menu->addMenu(QString(mp->name)); break; default : int id = ((mp->hbank & 0xff) << 16) + ((mp->lbank & 0xff) << 8) + mp->prog; QAction* a; if(lm) a = lm->addAction(QString(mp->name)); else a = menu->addAction(QString(mp->name)); a->setData(id); break; } mp = _mess->getPatchInfo(ch, mp); } } //--------------------------------------------------------- // getData //--------------------------------------------------------- void MessSynthIF::getData(MidiEventList* el, unsigned pos, int ports, unsigned n, float** buffer) { // Reset buffers first for (int port = 0; port < ports; ++port) memset(buffer[port], 0, n * sizeof(float)); int curPos = pos; int endPos = pos + n; while (!synti->putFifo.isEmpty()) { if (putEvent(synti->putFifo.peek())) break; synti->putFifo.remove(); } // Echo events from Synti back while (_mess->eventsPending()) // _mess->processEvent(_mess->receiveEvent()); _mess->receiveEvent(); // throw away event if (ports >= channels()) { iMidiEvent i = el->begin(); for (; i != el->end(); ++i) { int frame = i->time(); if (frame >= endPos) break; if (frame > curPos) { // Several following notes during same segmentsize? _mess->process(buffer, curPos-pos, frame - curPos); curPos = frame; // don't process this piece again } if (putEvent(*i)) synti->putFifo.put(*i); } if (endPos - curPos > 0) _mess->process(buffer, curPos-pos, endPos - curPos); el->erase(el->begin(), i); } else { // this happens if the synth has stereo and we switch the // channel to mono printf("MessSynthIF::getData - ports %d < channels %d\n", ports, channels()); } } //--------------------------------------------------------- // putEvent // return true on error (busy), event will later be // resend //--------------------------------------------------------- bool MessSynthIF::putEvent(const MidiEvent& ev) { bool rv = true; if (_mess) { rv = _mess->processEvent(ev); if (midiOutputTrace && !rv) { printf("MidiOut<%s>", synti->name().toLatin1().data()); ev.dump(); } } return rv; } //--------------------------------------------------------- // collectInputData //--------------------------------------------------------- void SynthI::collectInputData() { bufferEmpty = false; _sif->getData(&_schedEvents, audioDriver->frameTime(), channels(), segmentSize, buffer); } //------------------------------------------------------------------- // process // Collect all midi events for the current process cycle and put // into _schedEvents queue. For note on events create the proper // note off events. The note off events maybe played after the // current process cycle. //------------------------------------------------------------------- void SynthI::processMidi(SeqTime* t) { if (mute()) return; MidiOut::processMidi(_schedEvents, t); }