summaryrefslogtreecommitdiffstats
path: root/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp')
-rw-r--r--src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp362
1 files changed, 362 insertions, 0 deletions
diff --git a/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp b/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp
new file mode 100644
index 0000000..cf9d15a
--- /dev/null
+++ b/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp
@@ -0,0 +1,362 @@
+// see http://stackoverflow.com/questions/6563810/m-pi-works-with-math-h-but-not-with-cmath-in-visual-studio
+#define _USE_MATH_DEFINES
+#include <cmath>
+
+#include "OpenALInvoker.h"
+#include <uscxml/config.h>
+#include <glog/logging.h>
+#include <limits.h>
+
+#ifdef BUILD_AS_PLUGINS
+#include <Pluma/Connector.hpp>
+#endif
+
+namespace uscxml {
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_CONNECTOR
+bool connect(pluma::Host& host) {
+ host.add( new OpenALInvokerProvider() );
+ return true;
+}
+#endif
+
+// see http://stackoverflow.com/questions/1904635/warning-c4003-and-errors-c2589-and-c2059-on-x-stdnumeric-limitsintmax
+#undef max
+
+OpenALInvoker::OpenALInvoker() {
+ _isStarted = false;
+ _isRunning = false;
+ _alContext = NULL;
+ _alDevice = NULL;
+ _thread = NULL;
+}
+
+OpenALInvoker::~OpenALInvoker() {
+ if (_thread) {
+ _isRunning = false;
+ _thread->join();
+ delete(_thread);
+ }
+ if (_alContext) {
+ alcCloseDevice(alcGetContextsDevice(_alContext));
+ alcDestroyContext(_alContext);
+ }
+};
+
+boost::shared_ptr<InvokerImpl> OpenALInvoker::create(InterpreterImpl* interpreter) {
+ boost::shared_ptr<OpenALInvoker> invoker = boost::shared_ptr<OpenALInvoker>(new OpenALInvoker());
+ invoker->_interpreter = interpreter;
+ return invoker;
+}
+
+Data OpenALInvoker::getDataModelVariables() {
+ Data data;
+ return data;
+}
+
+void OpenALInvoker::send(const SendRequest& req) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+
+ if (!_isStarted)
+ start();
+
+ if (boost::iequals(req.name, "play")) {
+ if (req.params.find("src") == req.params.end()) {
+ LOG(ERROR) << "Sent event play with no src URL";
+ }
+
+ URL srcURL = req.params.find("src")->second;
+ if (!srcURL.toAbsolute(_interpreter->getBaseURI())) {
+ LOG(ERROR) << "src URL " << req.params.find("src")->second << " is relative with no base URI set for interpreter";
+ return;
+ }
+
+ _sources[req.sendid] = new OpenALSource();
+ _sources[req.sendid]->loop = req.params.find("loop") != req.params.end() && boost::iequals(req.params.find("loop")->second, "true");
+ _sources[req.sendid]->file = srcURL;
+#ifdef LIBSNDFILE_FOUND
+ _sources[req.sendid]->transform = new LibSoundFile(srcURL.asLocalFile(".audio"));
+#else
+# ifdef AUDIOTOOLBOX_FOUND
+ _sources[req.sendid]->transform = new AudioToolbox(srcURL.asLocalFile(".audio"));
+# endif
+#endif
+ if (_sources[req.sendid]->transform == NULL) {
+ LOG(ERROR) << "No transcoder for input file known - install libsndfile or AudioToolbox";
+ _sources.erase(req.sendid);
+ return;
+ }
+
+ // force mono format to ensure actual spatial audio
+ PCMFormat format = _sources[req.sendid]->transform->getInFormat();
+ format.alFormat = AL_FORMAT_MONO16;
+ _sources[req.sendid]->transform->setOutFormat(format);
+
+ try {
+ _sources[req.sendid]->player = new OpenALPlayer(_alContext, NULL, format.alFormat, format.sampleRate);
+ } catch (std::exception ex) {
+ returnErrorExecution(ex.what());
+ return;
+ }
+
+ getPosFromParams(req.params, _sources[req.sendid]->pos);
+
+ try {
+ _sources[req.sendid]->player->setPosition(_sources[req.sendid]->pos);
+ } catch (std::exception ex) {
+ returnErrorExecution(ex.what());
+ }
+
+
+ _sourcesAvailable.notify_all();
+ }
+
+ if (boost::iequals(req.name, "move.source")) {
+ std::string sourceId;
+ if (req.params.find("source") == req.params.end()) {
+ LOG(WARNING) << "Cannot move source with no source given in parameters";
+ return;
+ }
+ sourceId = req.params.find("source")->second;
+
+ if (_sources.find(sourceId) == _sources.end()) {
+ LOG(WARNING) << "Given source '" << sourceId << "' not active or not existing";
+ return;
+ }
+
+ getPosFromParams(req.params, _sources[sourceId]->pos);
+ try {
+ _sources[sourceId]->player->setPosition(_sources[sourceId]->pos);
+ } catch (std::exception ex) {
+ returnErrorExecution(ex.what());
+ }
+ }
+}
+
+void OpenALInvoker::start() {
+ _isStarted = true;
+ _thread = new tthread::thread(&OpenALInvoker::fillBuffers, this);
+}
+
+void OpenALInvoker::fillBuffers(void* userdata) {
+ OpenALInvoker* INST = (OpenALInvoker*)userdata;
+ while(INST->_isStarted) {
+ // do nothing until we have at least one source
+ int waitMs = std::numeric_limits<int>::max();
+ INST->_mutex.lock();
+ while (INST->_sources.size() == 0) {
+ INST->_sourcesAvailable.wait(INST->_mutex);
+ }
+ // here we are with at least one source and a locked mutex
+ assert(INST->_sources.size() > 0);
+
+ std::map<std::string, OpenALSource*>::iterator srcIter = INST->_sources.begin();
+ while(srcIter != INST->_sources.end()) {
+ OpenALSource* src = srcIter->second;
+ int wait = std::numeric_limits<int>::max();
+
+ if (src->finished) {
+ // source has already finished playing, feed no more samples to it
+ try {
+ wait = src->player->isPlaying();
+ if (wait == 0) {
+ // source stopped playing, delete it
+ INST->notifyOfEnd(src);
+ delete src;
+ INST->_sources.erase(srcIter++);
+ continue;
+ } else {
+ // source returned time when to repoll
+ assert(wait > 0);
+ }
+ } catch (std::exception ex) {
+ INST->returnErrorExecution(ex.what());
+ delete src;
+ INST->_sources.erase(srcIter++);
+ continue;
+ }
+ } else {
+ // source still needs more samples or play existing buffer
+ if (src->written == src->read) {
+ // all read samples have been written, read some more
+ src->written = 0;
+ src->read = src->transform->read(src->buffer, ALPLAY_AUDIO_BUFFER_SIZE);
+ if (src->read < ALPLAY_AUDIO_BUFFER_SIZE) {
+ if (src->loop) {
+ INST->notifyOfLoop(src);
+ while (src->read < ALPLAY_AUDIO_BUFFER_SIZE) {
+ src->transform->seek(0);
+ src->read += src->transform->read(src->buffer + src->read, ALPLAY_AUDIO_BUFFER_SIZE - src->read);
+ }
+ } else {
+ src->finished = true;
+ memset(src->buffer + src->read, 0, ALPLAY_AUDIO_BUFFER_SIZE - src->read);
+ }
+ }
+ }
+
+ // there are unwritten samples in the buffer
+ if (src->read != src->written) {
+ try {
+ int written = src->player->write(src->buffer, ALPLAY_AUDIO_BUFFER_SIZE, &wait);
+ if (written >=0 ) {
+ src->written += written;
+ }
+ } catch (std::exception ex) {
+ INST->returnErrorExecution(ex.what());
+ src->finished = true;
+ }
+ } else {
+ assert(src->finished);
+ }
+ }
+
+ waitMs = (wait < waitMs ? wait : waitMs);
+ srcIter++;
+ }
+
+// std::cout << "W" << waitMs << ".";
+
+ INST->_mutex.unlock();
+ tthread::this_thread::sleep_for(tthread::chrono::milliseconds(waitMs));
+ }
+}
+
+void OpenALInvoker::cancel(const std::string sendId) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_mutex);
+
+}
+
+void OpenALInvoker::invoke(const InvokeRequest& req) {
+ _alDevice = alcOpenDevice(NULL);
+ if (_alDevice == NULL) {
+ throw std::string("__FILE__ __LINE__ openal error opening device");
+ }
+
+ // create new context with device
+ _alContext = alcCreateContext (_alDevice, NULL);
+ if (_alContext == NULL) {
+ alcCloseDevice (_alDevice);
+ throw std::string("openal error create context");
+ }
+
+// alcMakeContextCurrent(_alContext);
+// float listener[3] = {0,0,0};
+// alListenerfv(AL_POSITION, listener);
+//
+// float orientation[6] = {
+// 0.0, 0.0, -1.0, // direction
+// 0.0, 1.0, 0.0 }; //up
+// alListenerfv(AL_ORIENTATION, orientation);
+//
+// float velocity[3] = { 0.0, 0.0, 0.0}; //up
+// alListenerfv(AL_VELOCITY, velocity);
+//
+// alListenerf(AL_GAIN, 0.5);
+
+ start();
+}
+
+void OpenALInvoker::notifyOfEnd(OpenALSource* src) {
+ Event ev;
+ ev.name = "audio.end";
+ ev.data.compound["file"] = src->file;
+ returnEvent(ev);
+}
+
+void OpenALInvoker::notifyOfLoop(OpenALSource* src) {
+ Event ev;
+ ev.name = "audio.loop";
+ ev.data.compound["file"] = src->file;
+ returnEvent(ev);
+}
+
+void OpenALInvoker::getPosFromParams(const std::multimap<std::string, std::string>& params, float* position) {
+ // vector explicitly given
+ try {
+ if (params.find("x") != params.end())
+ position[0] = boost::lexical_cast<float>(params.find("x")->second);
+ if (params.find("y") != params.end())
+ position[1] = boost::lexical_cast<float>(params.find("y")->second);
+ if (params.find("z") != params.end())
+ position[2] = boost::lexical_cast<float>(params.find("z")->second);
+ } catch (boost::bad_lexical_cast& e) {
+ LOG(ERROR) << "Cannot interpret x, y or z as float value in params: " << e.what();
+ }
+
+ try {
+ // right is an alias for x
+ if (params.find("right") != params.end())
+ position[0] = boost::lexical_cast<float>(params.find("right")->second);
+ // height is an alias for y
+ if (params.find("height") != params.end())
+ position[1] = boost::lexical_cast<float>(params.find("height")->second);
+ // front is an alias for z
+ if (params.find("front") != params.end())
+ position[2] = boost::lexical_cast<float>(params.find("front")->second);
+ } catch (boost::bad_lexical_cast& e) {
+ LOG(ERROR) << "Cannot interpret right, height or front as float value in params: " << e.what();
+ }
+
+ // do we have a position on a circle?
+ try {
+ if (params.find("circle") != params.end()) {
+ float rad = posToRadian(params.find("circle")->second);
+ position[0] = cosf(rad);
+ position[2] = -1 * sinf(rad); // z axis increases to front
+ position[0] *= 150;
+ position[2] *= 150;
+
+ }
+ } catch (boost::bad_lexical_cast& e) {
+ LOG(ERROR) << "Cannot interpret circle as float value in params: " << e.what();
+ }
+
+// position[0] = position[0] / _maxPos[0];
+// position[1] = position[1] / _maxPos[1];
+// position[2] = position[2] / _maxPos[2];
+ // std::cout << _pos[0] << ":" << _pos[1] << ":" << _pos[2] << std::endl;
+
+}
+
+float OpenALInvoker::posToRadian(const std::string& pos) {
+
+ std::string trimmedPos = boost::trim_copy(pos);
+ float rad = 0;
+
+ if (trimmedPos.size() > 3 && boost::iequals("deg", trimmedPos.substr(trimmedPos.length() - 3, 3))) {
+ rad = boost::lexical_cast<float>(trimmedPos.substr(0, trimmedPos.size() - 3));
+ rad = fmodf(rad, 360); // into range [0-360]
+ rad /= 180; // into range [0-2]
+ rad *= M_PI; // into range [0-2PI]
+ rad -= M_PI_2; // 0 to top;
+ rad *= -1; // make clockwise
+ rad += 2 * M_PI; // make positive
+ } else if (trimmedPos.size() > 3 && boost::iequals("rad", trimmedPos.substr(trimmedPos.length() - 3, 3))) {
+ rad = boost::lexical_cast<float>(trimmedPos.substr(0, trimmedPos.size() - 3));
+ rad = fmodf(rad, M_PI * 2); // into range [0-2*PI]
+ } else {
+ LOG(ERROR) << "Cannot make sense of position value " << trimmedPos << ": does not end in 'deg', 'rad'";
+ }
+ return rad;
+}
+
+OpenALSource::OpenALSource() {
+ pos[0] = pos[1] = pos[2] = 0;
+ player = NULL;
+ loop = false;
+ finished = false;
+ transform = NULL;
+ read = written = 0;
+ memset(buffer, 0, ALPLAY_AUDIO_BUFFER_SIZE);
+}
+
+OpenALSource::~OpenALSource() {
+ if (player)
+ delete player;
+ if (transform)
+ delete transform;
+}
+
+} \ No newline at end of file