diff options
| author | Tim Redfern <tim@herge.(none)> | 2013-02-27 16:47:24 +0000 |
|---|---|---|
| committer | Tim Redfern <tim@herge.(none)> | 2013-02-27 16:47:24 +0000 |
| commit | 2f3b709e63e9aba347f6513565d4283b24b351d3 (patch) | |
| tree | 7ce73bea02bf1717e141f620aa1ade7e10ec9f1a /rotord | |
| parent | 06c11d8aa29e3a097c3ceb076b7d78f1c28a974f (diff) | |
audio plugin worker
Diffstat (limited to 'rotord')
| -rwxr-xr-x | rotord/rotor.h | 76 | ||||
| -rwxr-xr-x | rotord/rotord.cpp | 31 | ||||
| -rw-r--r-- | rotord/vampHost.cpp | 226 | ||||
| -rw-r--r-- | rotord/vampHost.h | 16 |
4 files changed, 299 insertions, 50 deletions
diff --git a/rotord/rotor.h b/rotord/rotor.h index 594465d..8d4b712 100755 --- a/rotord/rotor.h +++ b/rotord/rotor.h @@ -85,17 +85,24 @@ get progress #include "Poco/Mutex.h" #include "Poco/Random.h" #include "Poco/AutoPtr.h" +#include "Poco/File.h" #include <iostream> using Poco::UUID; using Poco::UUIDGenerator; -#define ROTOR_READY 0 -#define ROTOR_ANALYSING_AUDIO 1 -#define ROTOR_CREATING_PREVIEW 2 -#define ROTOR_RENDERING 3 - namespace Rotor { +#define IDLE 0 +#define ANALYSING_AUDIO 1 +#define AUDIO_READY 2 +#define CREATING_PREVIEW 3 +#define PREVIEW_READY 4 +#define RENDERING 5 +#define RENDER_READY 6 + +#define ANALYSE_AUDIO 0 +#define PREVIEW 1 +#define RENDER 2 //forward declaration class Node; @@ -110,41 +117,66 @@ namespace Rotor { int num_performances; int num_clips; }; + class Command_response{ + public: + Command_response() { status=HTTPResponse::HTTP_OK; } + string description; + HTTPResponse::HTTPStatus status; + }; class Render_context: public Poco::Task { //Poco task object //manages a 'patchbay' //high level interfaces for the wizard //and low level interface onto the graph public: - Render_context(const std::string& name): Task(name) { + Render_context(const std::string& name): Task(name) { + state=IDLE; }; void runTask() { while (!isCancelled()) { mutex.lock(); if (work_queue.size()){ - std::string out=name()+": "+work_queue[0]+"\n"; - printf(out.c_str()); + int cmd=work_queue[0]; work_queue.pop_front(); + if (cmd==ANALYSE_AUDIO) { + + } } mutex.unlock(); sleep(100); } printf("Rotor: stopping thread\n"); } - void add_queue(std::string item) { + void add_queue(int item) { mutex.lock(); work_queue.push_back(item); mutex.unlock(); } - std::string session_command(const std::vector<std::string>& command){ - string response; + Command_response session_command(const std::vector<std::string>& command){ + //method,id,command1,{command2,}{body} + //here we allow the controlling server to communicate with running tasks + Command_response response; mutex.lock(); - if (command[1]=="audio") { - if (command[0]=="PUT") { - //get audio file location and initiate analysis - if (command.size()>1) { - //pass message to task - work_queue.push_back(command[2]); - response="<status>1</status>\n"; + if (command[2]=="audio") { + if (command[0]=="PUT") { //get audio file location and initiate analysis + if (command.size()>2) { + if (state==IDLE) { + //check file exists + Poco::File f=Poco::File(command[3]); + if (f.exists()) { + //pass to worker thread ??if engine is ready?? ??what if engine has finished but results aren't read?? + audio_filename=command[3]; //for now, store session variables in memory + work_queue.push_back(ANALYSE_AUDIO); + response.description="<status>Starting audio analysis: "+command[3]+"</status>\n"; + } + else { + response.status=HTTPResponse::HTTP_NOT_FOUND; + response.description="<status>File "+command[3]+" not found</status>\n"; + } + } + else { + response.status=HTTPResponse::HTTP_BAD_REQUEST; + response.description="<status>Rotor: session busy</status>\n"; + } } } } @@ -161,11 +193,11 @@ namespace Rotor { int load_video(int num,string filename); //can be performance or clip private: - int status; + int state; float progress; //for a locking process: audio analysis or rendering - std::deque<std::string> work_queue; - - Poco::Mutex mutex; //access to notification queue + std::deque<int> work_queue; + Poco::Mutex mutex; //lock for access from parent thread + std::string audio_filename; }; class Input{ public: diff --git a/rotord/rotord.cpp b/rotord/rotord.cpp index c4103e3..21ce639 100755 --- a/rotord/rotord.cpp +++ b/rotord/rotord.cpp @@ -69,7 +69,7 @@ void AudioAnalyserHandler::handleRequest(HTTPServerRequest& request,HTTPServerRe ostr << "</head>"; ostr << "<body><p style=\"text-align: center; " "font-size: 48px;\">"; - vampHost::runPlugin("",settings.soname,settings.filtername, "",0, settings.inputFile, ostr,true); + vampHost::runPlugin("",settings.soname,settings.filtername, "",0, settings.inputFile, ostr,true); ostr << "</p></body></html>"; } @@ -187,36 +187,27 @@ HTTPRequestHandler* RotorRequestHandlerFactory::createRequestHandler(const HTTPS content="<status>Rotor: render context invoked with no command</status>\n"; } } - else { - //in a way the rest of this stuff happens inside the thread - //its not elegant to decipher the message just to assemble another message to decipher elsewhere! - //pass a message object with the command type and contents selected? - //every action will have a seperate work function in the render_context - //some of these functions have to return data immediately- maybe a queue isn't the ideal model - //what's the best way to seperate these? - //could have a seperate mutex for the graph - //all the get methods want to get something from the render context and express it in xml. - //what is the best way... - //should the processing be done inside the render context - I think so - - //++++ processing inside object - //---- processing occurring in different places - - //the idea of command queueing won't work - + else { //session modifier command- to be passed to render context + //some commands need to return error codes + //ie where the audio file isn't found + //on the other hand, some commands need to know state of the renderer? std::string s; std::ostringstream os; os<<request.stream().rdbuf(); s=os.str(); - vector<string> sc; + vector<string> sc; //method,id,command1,{command2,}{body} sc.push_back(request.getMethod()); for (auto& i: command){ sc.push_back(i); } sc.push_back(s); - content=((Poco::AutoPtr<Rotor::Render_context>)task)->session_command(command); + Rotor::Command_response response=((Poco::AutoPtr<Rotor::Render_context>)task)->session_command(sc); + + content=response.description; + status=response.status; + } } } diff --git a/rotord/vampHost.cpp b/rotord/vampHost.cpp index 54ce77b..c7e5691 100644 --- a/rotord/vampHost.cpp +++ b/rotord/vampHost.cpp @@ -7,7 +7,7 @@ int vampHost::runPlugin(string myname, string soname, string id, string output, PluginLoader *loader = PluginLoader::getInstance(); PluginLoader::PluginKey key = loader->composePluginKey(soname, id); - + SNDFILE *sndfile; SF_INFO sfinfo; memset(&sfinfo, 0, sizeof(SF_INFO)); @@ -118,7 +118,7 @@ int vampHost::runPlugin(string myname, string soname, string id, string output, if (int(outputs.size()) <= outputNo) { cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl; goto done; - } + } } od = outputs[outputNo]; @@ -139,7 +139,7 @@ int vampHost::runPlugin(string myname, string soname, string id, string output, wrapper->getWrapper<PluginInputDomainAdapter>(); if (ida) adjustment = ida->getTimestampAdjustment(); } - + // Here we iterate over the frames, avoiding asking the numframes in case it's streaming input. do { @@ -259,5 +259,221 @@ void vampHost::printFeatures(int frame, int sr, int output, out << endl; } - -}
\ No newline at end of file + +} + +int vampHost::QMBeats::process(const string inputFile){ + //vampHost::runPlugin("",settings.soname,settings.filtername, "",0, settings.inputFile, ostr,true); + //would run the plugin, outputting progress to cerr and the data to ostr + // + //we want to run a specific plugin, outputting progress to a mutex-protected passed variable + //and ultimately passing the data back as a string + //get the progress as a float + //how to handle errors? + string soname="qm-vamp-plugins"; + string filtername="qm-tempotracker"; + + PluginLoader *loader = PluginLoader::getInstance(); + + PluginLoader::PluginKey key = loader->composePluginKey(soname, id); + + SNDFILE *sndfile; + SF_INFO sfinfo; + memset(&sfinfo, 0, sizeof(SF_INFO)); + + sndfile = sf_open(inputFile.c_str(), SFM_READ, &sfinfo); + if (!sndfile) { + cerr << myname << ": ERROR: Failed to open input file \"" + << inputFile << "\": " << sf_strerror(sndfile) << endl; + return 1; + } + + Plugin *plugin = loader->loadPlugin(key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE); + + if (!plugin) { + cerr << myname << ": ERROR: Failed to load plugin \"" << id + << "\" from library \"" << soname << "\"" << endl; + sf_close(sndfile); + return 1; + } + + cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl; + + // Note that the following would be much simpler if we used a + // PluginBufferingAdapter as well -- i.e. if we had passed + // PluginLoader::ADAPT_ALL to loader->loadPlugin() above, instead + // of ADAPT_ALL_SAFE. Then we could simply specify our own block + // size, keep the step size equal to the block size, and ignore + // the plugin's bleatings. However, there are some issues with + // using a PluginBufferingAdapter that make the results sometimes + // technically different from (if effectively the same as) the + // un-adapted plugin, so we aren't doing that here. See the + // PluginBufferingAdapter documentation for details. + + int blockSize = plugin->getPreferredBlockSize(); + int stepSize = plugin->getPreferredStepSize(); + + if (blockSize == 0) { + blockSize = 1024; + } + if (stepSize == 0) { + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + stepSize = blockSize/2; + } else { + stepSize = blockSize; + } + } else if (stepSize > blockSize) { + cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to "; + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + blockSize = stepSize * 2; + } else { + blockSize = stepSize; + } + cerr << blockSize << endl; + } + int overlapSize = blockSize - stepSize; + sf_count_t currentStep = 0; + int finalStepsRemaining = max(1, (blockSize / stepSize) - 1); // at end of file, this many part-silent frames needed after we hit EOF + + int channels = sfinfo.channels; + + float *filebuf = new float[blockSize * channels]; + float **plugbuf = new float*[channels]; + for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2]; + + cerr << "Using block size = " << blockSize << ", step size = " << stepSize << endl; + + // The channel queries here are for informational purposes only -- + // a PluginChannelAdapter is being used automatically behind the + // scenes, and it will take case of any channel mismatch + + int minch = plugin->getMinChannelCount(); + int maxch = plugin->getMaxChannelCount(); + cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl; + cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl; + + Plugin::OutputList outputs = plugin->getOutputDescriptors(); + Plugin::OutputDescriptor od; + + int returnValue = 1; + int _progress = 0; + + RealTime rt; + PluginWrapper *wrapper = 0; + RealTime adjustment = RealTime::zeroTime; + + if (outputs.empty()) { + cerr << "ERROR: Plugin has no outputs!" << endl; + goto done; + } + + if (outputNo < 0) { + + for (size_t oi = 0; oi < outputs.size(); ++oi) { + if (outputs[oi].identifier == output) { + outputNo = oi; + break; + } + } + + if (outputNo < 0) { + cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl; + goto done; + } + + } else { + + if (int(outputs.size()) <= outputNo) { + cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl; + goto done; + } + } + + od = outputs[outputNo]; + cerr << "Output is: \"" << od.identifier << "\"" << endl; + + if (!plugin->initialise(channels, stepSize, blockSize)) { + cerr << "ERROR: Plugin initialise (channels = " << channels + << ", stepSize = " << stepSize << ", blockSize = " + << blockSize << ") failed." << endl; + goto done; + } + + wrapper = dynamic_cast<PluginWrapper *>(plugin); + if (wrapper) { + // See documentation for + // PluginInputDomainAdapter::getTimestampAdjustment + PluginInputDomainAdapter *ida = + wrapper->getWrapper<PluginInputDomainAdapter>(); + if (ida) adjustment = ida->getTimestampAdjustment(); + } + + // Here we iterate over the frames, avoiding asking the numframes in case it's streaming input. + do { + + int count; + + if ((blockSize==stepSize) || (currentStep==0)) { + // read a full fresh block + if ((count = sf_readf_float(sndfile, filebuf, blockSize)) < 0) { + cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; + break; + } + if (count != blockSize) --finalStepsRemaining; + } else { + // otherwise shunt the existing data down and read the remainder. + memmove(filebuf, filebuf + (stepSize * channels), overlapSize * channels * sizeof(float)); + if ((count = sf_readf_float(sndfile, filebuf + (overlapSize * channels), stepSize)) < 0) { + cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; + break; + } + if (count != stepSize) --finalStepsRemaining; + count += overlapSize; + } + + for (int c = 0; c < channels; ++c) { + int j = 0; + while (j < count) { + plugbuf[c][j] = filebuf[j * sfinfo.channels + c]; + ++j; + } + while (j < blockSize) { + plugbuf[c][j] = 0.0f; + ++j; + } + } + + rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate); + + vampHost::printFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate),sfinfo.samplerate, outputNo, plugin->process(plugbuf, rt),out, useFrames); + + if (sfinfo.frames > 0){ + int pp = _progress; + _progress = lrintf((float(currentStep * stepSize) / sfinfo.frames) * 100.f); + if (_progress != pp) { + cerr << "\r" << _progress << "%"; + } + mutex.lock(); + progress = (float(currentStep * stepSize) / sfinfo.frames) * 100.f; + mutex.unlock(); + } + + ++currentStep; + + } while (finalStepsRemaining > 0); + + cerr << "\rDone" << endl; + + rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate); + + vampHost::printFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate),sfinfo.samplerate, outputNo,plugin->getRemainingFeatures(), out, useFrames); + + returnValue = 0; + +done: + delete plugin; + sf_close(sndfile); + return returnValue; + + return 0; +} diff --git a/rotord/vampHost.h b/rotord/vampHost.h index 03a375e..d4ae1ac 100644 --- a/rotord/vampHost.h +++ b/rotord/vampHost.h @@ -2,6 +2,8 @@ #include <vamp-hostsdk/PluginInputDomainAdapter.h> #include <vamp-hostsdk/PluginLoader.h> +#include "Poco/Mutex.h" + #include <iostream> #include <fstream> #include <set> @@ -26,7 +28,7 @@ using Vamp::HostExt::PluginInputDomainAdapter; #define HOST_VERSION "1.5" namespace vampHost { - + class Settings{ public: Settings(string _so="",string _filter="",string _input="") { @@ -38,9 +40,17 @@ namespace vampHost { string filtername; string inputFile; }; + class QMBeats{ + public: + int process(const string soundfile); + float getProgress(); + private: + float progress; + Poco::Mutex mutex; //lock for progress data + }; - + string getQMBeats(const string soundfile); void printFeatures(int, int, int, Plugin::FeatureSet, ostream &, bool frames); int runPlugin(string myname, string soname, string id, string output,int outputNo, string inputFile, ostream& out, bool frames); -}
\ No newline at end of file +} |
