/* ZynAddSubFX - a software synthesizer DSSIaudiooutput.cpp - Audio functions for DSSI Copyright (C) 2002 Nasca Octavian Paul Author: Nasca Octavian Paul This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. 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 (version 2 or later) for more details. You should have received a copy of the GNU General Public License (version 2) along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Inital working DSSI output code contributed by Stephen G. Parry */ //this file contains code used from trivial_synth.c from //the DSSI (published by Steve Harris under public domain) as a template. #include "DSSIaudiooutput.h" #include "../Misc/Config.h" #include "../Misc/Bank.h" #include "../Misc/Util.h" #include #include using std::string; using std::vector; //Dummy variables and functions for linking purposes const char *instance_name = 0; class WavFile; namespace Nio { bool start(void){return 1;}; void stop(void){}; void waveNew(WavFile *){} void waveStart(void){} void waveStop(void){} void waveEnd(void){} } // // Static stubs for LADSPA member functions // // LADSPA is essentially a C handle based API; This plug-in implementation is // a C++ OO one so we need stub functions to map from C API calls to C++ object // method calls. void DSSIaudiooutput::stub_connectPort(LADSPA_Handle instance, unsigned long port, LADSPA_Data *data) { getInstance(instance)->connectPort(port, data); } void DSSIaudiooutput::stub_activate(LADSPA_Handle instance) { getInstance(instance)->activate(); } void DSSIaudiooutput::stub_run(LADSPA_Handle instance, unsigned long sample_count) { getInstance(instance)->run(sample_count); } void DSSIaudiooutput::stub_deactivate(LADSPA_Handle instance) { getInstance(instance)->deactivate(); } void DSSIaudiooutput::stub_cleanup(LADSPA_Handle instance) { DSSIaudiooutput *plugin_instance = getInstance(instance); plugin_instance->cleanup(); delete plugin_instance; } const LADSPA_Descriptor *ladspa_descriptor(unsigned long index) { return DSSIaudiooutput::getLadspaDescriptor(index); } // // Static stubs for DSSI member functions // // DSSI is essentially a C handle based API; This plug-in implementation is // a C++ OO one so we need stub functions to map from C API calls to C++ object // method calls. const DSSI_Program_Descriptor *DSSIaudiooutput::stub_getProgram( LADSPA_Handle instance, unsigned long index) { return getInstance(instance)->getProgram(index); } void DSSIaudiooutput::stub_selectProgram(LADSPA_Handle instance, unsigned long bank, unsigned long program) { getInstance(instance)->selectProgram(bank, program); } int DSSIaudiooutput::stub_getMidiControllerForPort(LADSPA_Handle instance, unsigned long port) { return getInstance(instance)->getMidiControllerForPort(port); } void DSSIaudiooutput::stub_runSynth(LADSPA_Handle instance, unsigned long sample_count, snd_seq_event_t *events, unsigned long event_count) { getInstance(instance)->runSynth(sample_count, events, event_count); } const DSSI_Descriptor *dssi_descriptor(unsigned long index) { return DSSIaudiooutput::getDssiDescriptor(index); } // // LADSPA member functions // /** * Instantiates a plug-in. * * This LADSPA member function instantiates a plug-in. * Note that instance initialisation should generally occur in * activate() rather than here. * * Zyn Implementation * ------------------ * This implementation creates a C++ class object and hides its pointer * in the handle by type casting. * * @param descriptor [in] the descriptor for this plug-in * @param s_rate [in] the sample rate * @return the plug-in instance handle if successful else NULL */ LADSPA_Handle DSSIaudiooutput::instantiate(const LADSPA_Descriptor *descriptor, unsigned long s_rate) { if(descriptor->UniqueID == dssiDescriptor->LADSPA_Plugin->UniqueID) return (LADSPA_Handle)(new DSSIaudiooutput(s_rate)); else return NULL; } /** * Connects a port on an instantiated plug-in. * * This LADSPA member function connects a port on an instantiated plug-in to a * memory location at which a block of data for the port will be read/written. * The data location is expected to be an array of LADSPA_Data for audio ports * or a single LADSPA_Data value for control ports. Memory issues will be * managed by the host. The plug-in must read/write the data at these locations * every time run() or run_adding() is called and the data present at the time * of this connection call should not be considered meaningful. * * Zyn Implementation * ------------------ * The buffer pointers are stored as member variables * * @param port [in] the port to be connected * @param data [in] the data buffer to write to / read from */ void DSSIaudiooutput::connectPort(unsigned long port, LADSPA_Data *data) { switch(port) { case 0: outl = data; break; case 1: outr = data; break; } } /** * Initialises a plug-in instance and activates it for use. * * This LADSPA member function initialises a plug-in instance and activates it * for use. This is separated from instantiate() to aid real-time support and * so that hosts can reinitialise a plug-in instance by calling deactivate() and * then activate(). In this case the plug-in instance must reset all state * information dependent on the history of the plug-in instance except for any * data locations provided by connect_port() and any gain set by * set_run_adding_gain(). * * Zyn Implementation * ------------------ * Currently this does nothing; Care must be taken as to code placed here as * too much code here seems to cause time-out problems in jack-dssi-host. */ void DSSIaudiooutput::activate() {} /** * Runs an instance of a plug-in for a block. * * This LADSPA member function runs an instance of a plug-in for a block. * Note that if an activate() function exists then it must be called before * run() or run_adding(). If deactivate() is called for a plug-in instance then * the plug-in instance may not be reused until activate() has been called again. * * Zyn Implementation * ------------------ * This is a LADSPA function that does not process any MIDI events; it is hence * implemented by simply calling runSynth() with an empty event list. * * @param sample_count [in] the block size (in samples) for which the plug-in instance may run */ void DSSIaudiooutput::run(unsigned long sample_count) { runSynth(sample_count, NULL, (unsigned long)0); } /** * Counterpart to activate(). * * This LADSPA member function is the counterpart to activate() (see above). * Deactivation is not similar to pausing as the plug-in instance will be * reinitialised when activate() is called to reuse it. * * Zyn Implementation * ------------------ * Currently this function does nothing. */ void DSSIaudiooutput::deactivate() {} /** * Deletes a plug-in instance that is no longer required. * * LADSPA member function; once an instance of a plug-in has been finished with * it can be deleted using this function. The instance handle ceases to be * valid after this call. * * If activate() was called for a plug-in instance then a corresponding call to * deactivate() must be made before cleanup() is called. * * Zyn Implementation * ------------------ * Currently cleanup is deferred to the destructor that is invoked after cleanup() */ void DSSIaudiooutput::cleanup() {} /** * Initial entry point for the LADSPA plug-in library. * * This LADSPA function is the initial entry point for the plug-in library. * The LADSPA host looks for this entry point in each shared library object it * finds and then calls the function to enumerate the plug-ins within the * library. * * Zyn Implementation * ------------------ * As the Zyn plug-in is a DSSI plug-in, the LADSPA descriptor is embedded inside * the DSSI descriptor, which is created by DSSIaudiooutput::initDssiDescriptor() * statically when the library is loaded. This function then merely returns a pointer * to that embedded descriptor. * * @param index [in] the index number of the plug-in within the library. * @return if index is in range, a pointer to the plug-in descriptor is returned, else NULL */ const LADSPA_Descriptor *DSSIaudiooutput::getLadspaDescriptor( unsigned long index) { if((index > 0) || (dssiDescriptor == NULL)) return NULL; else return dssiDescriptor->LADSPA_Plugin; } // // DSSI member functions // /** * Provides a description of a program available on this synth. * * This DSSI member function pointer provides a description of a program (named * preset sound) available on this synth. * * Zyn Implementation * ------------------ * The instruments in all Zyn's bank directories, as shown by the `instrument * -> show instrument bank` command, are enumerated to the host by this * function, allowing access to all those instruments. * The first time an instrument is requested, the bank it is in and any * unmapped ones preceding that are mapped; all the instruments names and * filenames from those banks are stored in the programMap member variable for * later use. This is done on demand in this way, rather than up front in one * go because loading all the instrument names in one go can lead to timeouts * and zombies. * * @param index [in] index into the plug-in's list of * programs, not a program number as represented by the Program * field of the DSSI_Program_Descriptor. (This distinction is * needed to support synths that use non-contiguous program or * bank numbers.) * @return a DSSI_Program_Descriptor pointer that is * guaranteed to be valid only until the next call to get_program, * deactivate, or configure, on the same plug-in instance, or NULL if index is out of range. */ const DSSI_Program_Descriptor *DSSIaudiooutput::getProgram(unsigned long index) { static DSSI_Program_Descriptor retVal; /* Make sure we have the list of banks loaded */ initBanks(); /* Make sure that the bank containing the instrument has been mapped */ while(index >= programMap.size() && mapNextBank()) /* DO NOTHING MORE */; if(index >= programMap.size()) /* No more instruments */ return NULL; else { /* OK, return the instrument */ retVal.Name = programMap[index].name.c_str(); retVal.Program = programMap[index].program; retVal.Bank = programMap[index].bank; return &retVal; } } /** * Selects a new program for this synth. * * This DSSI member function selects a new program for this synth. The program * change will take effect immediately at the start of the next run_synth() * call. An invalid bank / instrument combination is ignored. * * Zyn Implementation * ------------------ * the banks and instruments are as shown in the `instrument -> show instrument * bank` command in Zyn. The bank no is a 1-based index into the list of banks * Zyn loads and shows in the drop down and the program number is the * instrument within that bank. * * @param bank [in] the bank number to select * @param program [in] the program number within the bank to select */ void DSSIaudiooutput::selectProgram(unsigned long bank, unsigned long program) { initBanks(); // cerr << "selectProgram(" << (bank & 0x7F) << ':' << ((bank >> 7) & 0x7F) << "," << program << ")" << '\n'; if((bank < master->bank.banks.size()) && (program < BANK_SIZE)) { const std::string bankdir = master->bank.banks[bank].dir; if(!bankdir.empty()) { pthread_mutex_lock(&master->mutex); /* We have to turn off the CheckPADsynth functionality, else * the program change takes way too long and we get timeouts * and hence zombies (!) */ int save = config.cfg.CheckPADsynth; config.cfg.CheckPADsynth = 0; /* Load the bank... */ master->bank.loadbank(bankdir); /* restore the CheckPADsynth flag */ config.cfg.CheckPADsynth = save; /* Now load the instrument... */ master->bank.loadfromslot((unsigned int)program, master->part[0]); pthread_mutex_unlock(&master->mutex); } } } /** * Returns the MIDI controller number or NRPN for a input control port * * This DSSI member function returns the MIDI controller number or NRPN that * should be mapped to the given input control port. If the given port should * not have any MIDI controller mapped to it, the function will return DSSI_NONE. * The behaviour of this function is undefined if the given port * number does not correspond to an input control port. * * Zyn Implementation * ------------------ * Currently Zyn does not define any controller ports, but may do in the future. * * @param port [in] the input controller port * @return the CC and NRPN values shifted and ORed together. */ int DSSIaudiooutput::getMidiControllerForPort(unsigned long port) { return DSSI_NONE; } /** * Runs the synth for a block. * * This DSSI member function runs the synth for a block. This is identical in * function to the LADSPA run() function, except that it also supplies events * to the synth. * * Zyn Implementation * ------------------ * Zyn implements synthesis in Master::GetAudioOutSamples; runSynth calls this * function in chunks delimited by the sample_count and the frame indexes in * the events block, calling the appropriate NoteOn, NoteOff and SetController * members of Master to process the events supplied between each chunk. * * @param sample_count [in] the block size (in samples) for which the synth * instance may run. * @param events [in] The Events pointer points to a block of ALSA * sequencer events, used to communicate MIDI and related events to the synth. * Each event must be timestamped relative to the start of the block, * (mis)using the ALSA "tick time" field as a frame count. The host is * responsible for ensuring that events with differing timestamps are already * ordered by time. Must not include NOTE (only NOTE_ON / NOTE_OFF), LSB or MSB * events. * @param event_count [in] the number of entries in the `events` block */ void DSSIaudiooutput::runSynth(unsigned long sample_count, snd_seq_event_t *events, unsigned long event_count) { unsigned long from_frame = 0; unsigned long event_index = 0; unsigned long next_event_frame = 0; unsigned long to_frame = 0; pthread_mutex_lock(&master->mutex); do { /* Find the time of the next event, if any */ if((events == NULL) || (event_index >= event_count)) next_event_frame = ULONG_MAX; else next_event_frame = events[event_index].time.tick; /* find the end of the sub-sample to be processed this time round... */ /* if the next event falls within the desired sample interval... */ if((next_event_frame < sample_count) && (next_event_frame >= to_frame)) /* set the end to be at that event */ to_frame = next_event_frame; else /* ...else go for the whole remaining sample */ to_frame = sample_count; if(from_frame < to_frame) { // call master to fill from `from_frame` to `to_frame`: master->GetAudioOutSamples(to_frame - from_frame, (int)sampleRate, &(outl[from_frame]), &(outr[from_frame])); // next sub-sample please... from_frame = to_frame; } // Now process any event(s) at the current timing point while(events != NULL && event_index < event_count && events[event_index].time.tick == to_frame) { if(events[event_index].type == SND_SEQ_EVENT_NOTEON) master->noteOn(events[event_index].data.note.channel, events[event_index].data.note.note, events[event_index].data.note.velocity); else if(events[event_index].type == SND_SEQ_EVENT_NOTEOFF) master->noteOff(events[event_index].data.note.channel, events[event_index].data.note.note); else if(events[event_index].type == SND_SEQ_EVENT_CONTROLLER) master->setController(events[event_index].data.control.channel, events[event_index].data.control.param, events[event_index].data.control.value); else {} event_index++; } // Keep going until we have the desired total length of sample... } while(to_frame < sample_count); pthread_mutex_unlock(&master->mutex); } /** * Initial entry point for the DSSI plug-in library. * * This DSSI function is the initial entry point for the plug-in library. * The DSSI host looks for this entry point in each shared library object it * finds and then calls the function to enumerate the plug-ins within the * library. * * Zyn Implementation * ------------------ * The descriptor is created statically by DSSIaudiooutput::initDssiDescriptor() * when the plug-in library is loaded. This function merely returns a pointer to * that descriptor. * * @param index [in] the index number of the plug-in within the library. * @return if index is in range, a pointer to the plug-in descriptor is returned, else NULL */ const DSSI_Descriptor *DSSIaudiooutput::getDssiDescriptor(unsigned long index) { if((index > 0) || (dssiDescriptor == NULL)) return NULL; else return dssiDescriptor; } // // Internal member functions // // Initialise the DSSI descriptor, statically: DSSI_Descriptor *DSSIaudiooutput::dssiDescriptor = DSSIaudiooutput::initDssiDescriptor(); /** * Initializes the DSSI (and LADSPA) descriptor, returning it is an object. */ DSSI_Descriptor *DSSIaudiooutput::initDssiDescriptor() { DSSI_Descriptor *newDssiDescriptor = new DSSI_Descriptor; LADSPA_PortDescriptor *newPortDescriptors; const char **newPortNames; LADSPA_PortRangeHint *newPortRangeHints; if(newDssiDescriptor) { LADSPA_Descriptor *newLadspaDescriptor = new LADSPA_Descriptor; if(newLadspaDescriptor) { newLadspaDescriptor->UniqueID = 100; newLadspaDescriptor->Label = "ZASF"; newLadspaDescriptor->Properties = 0; newLadspaDescriptor->Name = "ZynAddSubFX"; newLadspaDescriptor->Maker = "Nasca Octavian Paul "; newLadspaDescriptor->Copyright = "GNU General Public License v.2"; newLadspaDescriptor->PortCount = 2; newPortNames = new const char *[newLadspaDescriptor->PortCount]; newPortNames[0] = "Output L"; newPortNames[1] = "Output R"; newLadspaDescriptor->PortNames = newPortNames; newPortDescriptors = new LADSPA_PortDescriptor[newLadspaDescriptor->PortCount]; newPortDescriptors[0] = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO; newPortDescriptors[1] = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO; newLadspaDescriptor->PortDescriptors = newPortDescriptors; newPortRangeHints = new LADSPA_PortRangeHint[newLadspaDescriptor->PortCount]; newPortRangeHints[0].HintDescriptor = 0; newPortRangeHints[1].HintDescriptor = 0; newLadspaDescriptor->PortRangeHints = newPortRangeHints; newLadspaDescriptor->activate = stub_activate; newLadspaDescriptor->cleanup = stub_cleanup; newLadspaDescriptor->connect_port = stub_connectPort; newLadspaDescriptor->deactivate = stub_deactivate; newLadspaDescriptor->instantiate = instantiate; newLadspaDescriptor->run = stub_run; newLadspaDescriptor->run_adding = NULL; newLadspaDescriptor->set_run_adding_gain = NULL; } newDssiDescriptor->LADSPA_Plugin = newLadspaDescriptor; newDssiDescriptor->DSSI_API_Version = 1; newDssiDescriptor->configure = NULL; newDssiDescriptor->get_program = stub_getProgram; newDssiDescriptor->get_midi_controller_for_port = stub_getMidiControllerForPort; newDssiDescriptor->select_program = stub_selectProgram; newDssiDescriptor->run_synth = stub_runSynth; newDssiDescriptor->run_synth_adding = NULL; newDssiDescriptor->run_multiple_synths = NULL; newDssiDescriptor->run_multiple_synths_adding = NULL; } dssiDescriptor = newDssiDescriptor; return dssiDescriptor; } /** * Converts a LADSPA / DSSI handle into a DSSIaudiooutput instance. * * @param instance [in] * @return the instance */ DSSIaudiooutput *DSSIaudiooutput::getInstance(LADSPA_Handle instance) { return (DSSIaudiooutput *)(instance); } SYNTH_T *synth; /** * The private sole constructor for the DSSIaudiooutput class. * * Only ever called via instantiate(). * @param sampleRate [in] the sample rate to be used by the synth. * @return */ DSSIaudiooutput::DSSIaudiooutput(unsigned long sampleRate) { synth = new SYNTH_T; synth->samplerate = sampleRate; this->sampleRate = sampleRate; this->banksInited = false; config.init(); sprng(time(NULL)); denormalkillbuf = new float [synth->buffersize]; for(int i = 0; i < synth->buffersize; i++) denormalkillbuf[i] = (RND - 0.5f) * 1e-16; synth->alias(); this->master = new Master(); } /** * The destructor for the DSSIaudiooutput class * @return */ DSSIaudiooutput::~DSSIaudiooutput() {} /** * Ensures the list of bank (directories) has been initialised. */ void DSSIaudiooutput::initBanks(void) { if(!banksInited) { pthread_mutex_lock(&master->mutex); master->bank.rescanforbanks(); banksInited = true; pthread_mutex_unlock(&master->mutex); } } /** * constructor for the internally used ProgramDescriptor class * * @param _bank [in] bank number * @param _program [in] program number * @param _name [in] instrument / sample name * @return */ DSSIaudiooutput::ProgramDescriptor::ProgramDescriptor(unsigned long _bank, unsigned long _program, char *_name) :bank(_bank), program(_program), name(_name) {} /** * The map of programs available; held as a single shared statically allocated object. */ vector DSSIaudiooutput::programMap = vector(); /** * Index controlling the map of banks */ long DSSIaudiooutput::bankNoToMap = 1; /** * Queries and maps the next available bank of instruments. * * If the program index requested to getProgram() lies beyond the banks mapped to date, * this member function is called to map the next one. * @return true if a new bank has been found and mapped, else false. */ bool DSSIaudiooutput::mapNextBank() { pthread_mutex_lock(&master->mutex); Bank &bank = master->bank; bool retval; if((bankNoToMap >= (int)bank.banks.size()) || bank.banks[bankNoToMap].dir.empty()) retval = false; else { bank.loadbank(bank.banks[bankNoToMap].dir); for(unsigned long instrument = 0; instrument < BANK_SIZE; ++instrument) { string insName = bank.getname(instrument); if(!insName.empty() && (insName[0] != '\0') && (insName[0] != ' ')) programMap.push_back(ProgramDescriptor(bankNoToMap, instrument, const_cast( insName.c_str()))); } bankNoToMap++; retval = true; } pthread_mutex_unlock(&master->mutex); return retval; }