diff options
author | Fulup Ar Foll <fulup@iot.bzh> | 2017-03-08 14:23:35 +0100 |
---|---|---|
committer | Fulup Ar Foll <fulup@iot.bzh> | 2017-03-08 14:23:35 +0100 |
commit | 1393b5d318e22dbd1625692847a51c27932fd442 (patch) | |
tree | 19fd954a04456be8e1c7ebde5b3aaefbe08dbcad | |
parent | 0964e7da8576b8761e8d3d16b50cc842406c7e67 (diff) |
Events Partially Working
-rw-r--r-- | Alsa/core-binding/AlsaAfbBinding.c | 4 | ||||
-rw-r--r-- | Alsa/core-binding/AlsaLibMapping.c | 32 | ||||
-rw-r--r-- | Alsa/core-binding/AlsaLibMapping.h | 3 | ||||
-rw-r--r-- | Alsa/core-binding/README.md | 2 | ||||
-rw-r--r-- | AudioLogic/AudioLogicBinding.c (renamed from BusinessLogic/AudioLogicBinding.c) | 26 | ||||
-rw-r--r-- | AudioLogic/AudioLogicLib.c (renamed from BusinessLogic/AudioLogicMapping.c) | 13 | ||||
-rw-r--r-- | AudioLogic/AudioLogicLib.h (renamed from BusinessLogic/AudioLogicMapping.h) | 4 | ||||
-rw-r--r-- | AudioLogic/CMakeLists.txt (renamed from BusinessLogic/CMakeLists.txt) | 2 | ||||
-rw-r--r-- | AudioLogic/README.md (renamed from BusinessLogic/README.md) | 4 | ||||
-rw-r--r-- | CMakeLists.txt | 5 | ||||
-rw-r--r-- | README.md | 31 | ||||
-rw-r--r-- | htdocs/CMakeLists.txt | 25 | ||||
-rw-r--r-- | htdocs/alsa-core.html | 3 | ||||
-rw-r--r-- | htdocs/audio-logic.html | 40 | ||||
-rw-r--r-- | htdocs/index.html | 1 |
15 files changed, 142 insertions, 53 deletions
diff --git a/Alsa/core-binding/AlsaAfbBinding.c b/Alsa/core-binding/AlsaAfbBinding.c index 012b6c8..95d5e8d 100644 --- a/Alsa/core-binding/AlsaAfbBinding.c +++ b/Alsa/core-binding/AlsaAfbBinding.c @@ -30,7 +30,7 @@ #include "AlsaLibMapping.h" #include <afb/afb-service-itf.h> -PUBLIC const struct afb_binding_interface *binderIface; +PUBLIC const struct afb_binding_interface *afbIface; static void localping(struct afb_req request) { json_object *query = afb_req_json(request); @@ -71,7 +71,7 @@ extern int afbBindingV1ServiceInit(struct afb_service service) { * activation function for registering the binding called by afb-daemon */ const struct afb_binding *afbBindingV1Register(const struct afb_binding_interface *itf) { - binderIface= itf; + afbIface= itf; return &binding_description; /* returns the description of the binding */ } diff --git a/Alsa/core-binding/AlsaLibMapping.c b/Alsa/core-binding/AlsaLibMapping.c index 797f9ba..59ce8ff 100644 --- a/Alsa/core-binding/AlsaLibMapping.c +++ b/Alsa/core-binding/AlsaLibMapping.c @@ -270,13 +270,13 @@ STATIC json_object* alsaCardProbe (const char *rqtSndId) { snd_ctl_card_info_alloca(&cardinfo); if ((err = snd_ctl_open(&handle, rqtSndId, 0)) < 0) { - INFO (binderIface, "SndCard [%s] Not Found", rqtSndId); + INFO (afbIface, "SndCard [%s] Not Found", rqtSndId); return NULL; } if ((err = snd_ctl_card_info(handle, cardinfo)) < 0) { snd_ctl_close(handle); - WARNING (binderIface, "SndCard [%s] info error: %s", rqtSndId, snd_strerror(err)); + WARNING (afbIface, "SndCard [%s] info error: %s", rqtSndId, snd_strerror(err)); return NULL; } @@ -288,13 +288,13 @@ STATIC json_object* alsaCardProbe (const char *rqtSndId) { name = snd_ctl_card_info_get_name(cardinfo); json_object_object_add (sndcard, "name", json_object_new_string (name)); - if (binderIface->verbosity > 1) { + if (afbIface->verbosity > 1) { json_object_object_add (sndcard, "devid", json_object_new_string(rqtSndId)); driver= snd_ctl_card_info_get_driver(cardinfo); json_object_object_add (sndcard, "driver" , json_object_new_string(driver)); info = strdup(snd_ctl_card_info_get_longname (cardinfo)); json_object_object_add (sndcard, "info" , json_object_new_string (info)); - INFO (binderIface, "AJG: Soundcard Devid=%-5s Cardid=%-7s Name=%s\n", rqtSndId, devid, info); + INFO (afbIface, "AJG: Soundcard Devid=%-5s Cardid=%-7s Name=%s\n", rqtSndId, devid, info); } // free card handle and return info @@ -565,6 +565,7 @@ PUBLIC void alsaGetCtl(struct afb_req request) { return; } +// This routine is called when ALSA event are fired STATIC int sndCtlEventCB (sd_event_source* src, int fd, uint32_t revents, void* userData) { int err; evtHandleT *evtHandle = (evtHandleT*)userData; @@ -579,7 +580,7 @@ STATIC int sndCtlEventCB (sd_event_source* src, int fd, uint32_t revents, void* int index; if ((revents & EPOLLHUP) != 0) { - NOTICE (binderIface, "SndCtl hanghup [car disconnected]"); + NOTICE (afbIface, "SndCtl hanghup [car disconnected]"); goto ExitOnSucess; } @@ -604,7 +605,7 @@ STATIC int sndCtlEventCB (sd_event_source* src, int fd, uint32_t revents, void* devname= snd_ctl_event_elem_get_name(ctlEvent); index = snd_ctl_event_elem_get_index(ctlEvent); - fprintf(stdout, "*** Debug (%i,%i,%i,%i,%s,%i)", numid, iface, device, subdev, devname, index); + DEBUG(afbIface, "sndCtlEventCB: (%i,%i,%i,%i,%s,%i)", numid, iface, device, subdev, devname, index); // proxy ctlevent as a binder event ctlEventJson = json_object_new_object(); @@ -617,13 +618,12 @@ STATIC int sndCtlEventCB (sd_event_source* src, int fd, uint32_t revents, void* afb_event_push(evtHandle->afbevt, ctlEventJson); } - - ExitOnError: - WARNING (binderIface, "sndCtlEventCB: ignored unsupported event type"); - return (0); - ExitOnSucess: return 0; + + ExitOnError: + WARNING (afbIface, "sndCtlEventCB: ignored unsupported event type"); + return (0); } // Loop on every potential Sound card and register active one @@ -688,7 +688,7 @@ PUBLIC void alsaSubCtl (struct afb_req request) { snd_ctl_poll_descriptors(evtHandle->ctl, &evtHandle->pfds, 1); // register sound event to binder main loop - err = sd_event_add_io(afb_daemon_get_event_loop(binderIface->daemon), &evtHandle->src, evtHandle->pfds.fd, EPOLLIN, sndCtlEventCB, evtHandle); + err = sd_event_add_io(afb_daemon_get_event_loop(afbIface->daemon), &evtHandle->src, evtHandle->pfds.fd, EPOLLIN, sndCtlEventCB, evtHandle); if (err < 0) { afb_req_fail_f (request, "register-mainloop", "Cannot hook events to mainloop devid=%s err=%d", devid, err); snd_ctl_close(ctlHandle); @@ -696,13 +696,13 @@ PUBLIC void alsaSubCtl (struct afb_req request) { } // create binder event attached to devid name - evtHandle->afbevt = afb_daemon_make_event (binderIface->daemon, devid); + evtHandle->afbevt = afb_daemon_make_event (afbIface->daemon, devid); if (!afb_event_is_valid (evtHandle->afbevt)) { afb_req_fail_f (request, "register-event", "Cannot register new binder event name=%s", devid); snd_ctl_close(ctlHandle); goto ExitOnError; } - + // everything looks OK let's move forward idx=idxFree; } @@ -714,6 +714,10 @@ PUBLIC void alsaSubCtl (struct afb_req request) { goto ExitOnError; } + json_object *ctlEventJson = json_object_new_object(); + json_object_object_add(ctlEventJson, "test",json_object_new_string ("done")); + afb_event_push(evtHandle->afbevt, ctlEventJson ); + // increase usage count and return success sndHandles[idx].ucount ++; afb_req_success(request, NULL, NULL); diff --git a/Alsa/core-binding/AlsaLibMapping.h b/Alsa/core-binding/AlsaLibMapping.h index 9e23051..5fc1b98 100644 --- a/Alsa/core-binding/AlsaLibMapping.h +++ b/Alsa/core-binding/AlsaLibMapping.h @@ -37,8 +37,7 @@ typedef int BOOL; // import from AlsaAfbBinding -extern const struct afb_binding_interface *binderIface; -extern struct sd_event *afb_common_get_event_loop(); +extern const struct afb_binding_interface *afbIface; // import from AlsaAfbMapping PUBLIC void alsaGetInfo (struct afb_req request); diff --git a/Alsa/core-binding/README.md b/Alsa/core-binding/README.md index 4cce341..5f0c562 100644 --- a/Alsa/core-binding/README.md +++ b/Alsa/core-binding/README.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------ Testing: (from project directory bindings) - * start binder: ~/opt/bin/afb-daemon --ldpaths=./Alsa/src/low-level-binding:./BusinessLogic/audiologic-afb.so --roothttp=htdocs + * start binder: ~/opt/bin/afb-daemon --ldpaths=./build --roothttp=htdocs * connect browser on http://localhost:1234 # List Avaliable Sound cards diff --git a/BusinessLogic/AudioLogicBinding.c b/AudioLogic/AudioLogicBinding.c index 4d7dc35..65943c0 100644 --- a/BusinessLogic/AudioLogicBinding.c +++ b/AudioLogic/AudioLogicBinding.c @@ -27,11 +27,11 @@ #include <sys/types.h> #define _GNU_SOURCE // needed for vasprintf -#include "AudioLogicMapping.h" +#include "AudioLogicLib.h" -PUBLIC const struct afb_binding_interface *binderIface; +PUBLIC const struct afb_binding_interface *afbIface; -static void localping(struct afb_req request) { +STATIC void localping(struct afb_req request) { json_object *query = afb_req_json(request); afb_req_success(request, query, NULL); } @@ -39,19 +39,19 @@ static void localping(struct afb_req request) { /* * array of the verbs exported to afb-daemon */ -static const struct afb_verb_desc_v1 binding_verbs[] = { +STATIC const struct afb_verb_desc_v1 binding_verbs[] = { /* VERB'S NAME SESSION MANAGEMENT FUNCTION TO CALL SHORT DESCRIPTION */ { .name= "ping" , .session= AFB_SESSION_NONE, .callback= localping, .info= "Ping Binding" }, { .name= "setvolume", .session= AFB_SESSION_NONE, .callback= audioLogicSetVol, .info= "Set Volume" }, { .name= "getvolume", .session= AFB_SESSION_NONE, .callback= audioLogicGetVol, .info= "Get Volume" }, - { .name= "subscribe", .session= AFB_SESSION_NONE, .callback= audioLogicSubscribe, .info= "Get Volume" }, + { .name= "monitor", .session= AFB_SESSION_NONE, .callback= audioLogicMonitor, .info= "Subscribe Volume Events" }, { .name= NULL } /* marker for end of the array */ }; /* * description of the binding for afb-daemon */ -static const struct afb_binding binding_description = { +STATIC const struct afb_binding binding_description = { /* description conforms to VERSION 1 */ .type= AFB_BINDING_VERSION_1, .v1= { @@ -61,8 +61,14 @@ static const struct afb_binding binding_description = { } }; -extern int afbBindingV1ServiceInit(struct afb_service service) { - // this is call when after all bindings are loaded +// This receive all event this binding subscribe to +PUBLIC void afbBindingV1ServiceEvent(const char *evtname, struct json_object *object) { + + NOTICE (afbIface, "afbBindingV1ServiceEvent evtname=%s [msg=%s]", evtname, json_object_to_json_string(object)); +} + +// this is call when after all bindings are loaded +PUBLIC int afbBindingV1ServiceInit(struct afb_service service) { return (audioLogicInit(service)); }; @@ -70,8 +76,8 @@ extern int afbBindingV1ServiceInit(struct afb_service service) { /* * activation function for registering the binding called by afb-daemon */ -const struct afb_binding *afbBindingV1Register(const struct afb_binding_interface *itf) { - binderIface= itf; +PUBLIC const struct afb_binding *afbBindingV1Register(const struct afb_binding_interface *itf) { + afbIface= itf; return &binding_description; /* returns the description of the binding */ } diff --git a/BusinessLogic/AudioLogicMapping.c b/AudioLogic/AudioLogicLib.c index 8e0b773..ebe241c 100644 --- a/BusinessLogic/AudioLogicMapping.c +++ b/AudioLogic/AudioLogicLib.c @@ -26,7 +26,7 @@ #include <sys/time.h> #include <sys/types.h> -#include "AudioLogicMapping.h" +#include "AudioLogicLib.h" static struct afb_service srvitf; #define _GNU_SOURCE // needed for vasprintf @@ -37,13 +37,14 @@ PUBLIC int audioLogicInit (struct afb_service service) { return 0; } +// This callback is fired when afb_service_call for api/alsacore/subctl returns STATIC void alsaSubcribeCB (void *handle, int iserror, struct json_object *result) { struct afb_req request = afb_req_unstore(handle); struct json_object *x, *resp = NULL; const char *info = NULL; if (result) { - fprintf (stdout, "result=[%s]\n", json_object_to_json_string (result)); + INFO (afbIface, "result=[%s]\n", json_object_to_json_string (result)); if (json_object_object_get_ex(result, "request", &x) && json_object_object_get_ex(x, "info", &x)) info = json_object_get_string(x); if (!json_object_object_get_ex(result, "response", &resp)) resp = NULL; @@ -57,8 +58,8 @@ STATIC void alsaSubcribeCB (void *handle, int iserror, struct json_object *resul afb_req_unref(request); } - -PUBLIC void audioLogicSubscribe(struct afb_req request) { +// Create and subscribe to alsacore ctl events +PUBLIC void audioLogicMonitor(struct afb_req request) { // save request in session as it might be used after return by callback struct afb_req *handle = afb_req_store(request); @@ -67,12 +68,12 @@ PUBLIC void audioLogicSubscribe(struct afb_req request) { if (!handle) afb_req_fail(request, "error", "out of memory"); else afb_service_call(srvitf, "alsacore", "subctl", json_object_get(afb_req_json(request)), alsaSubcribeCB, handle); - // success/failure message will be position from callback + // success/failure messages return from callback } +// Call when all bindings are loaded and ready to accept request PUBLIC void audioLogicGetVol(struct afb_req request) { - afb_req_success (request, NULL, NULL); return; diff --git a/BusinessLogic/AudioLogicMapping.h b/AudioLogic/AudioLogicLib.h index e1033f7..21513fd 100644 --- a/BusinessLogic/AudioLogicMapping.h +++ b/AudioLogic/AudioLogicLib.h @@ -38,12 +38,12 @@ typedef int BOOL; // import from AlsaAfbBinding -extern const struct afb_binding_interface *binderIface; +extern const struct afb_binding_interface *afbIface; // import from AlsaAfbMapping PUBLIC void audioLogicSetVol (struct afb_req request); PUBLIC void audioLogicGetVol(struct afb_req request); -PUBLIC void audioLogicSubscribe(struct afb_req request); +PUBLIC void audioLogicMonitor(struct afb_req request); PUBLIC int audioLogicInit (struct afb_service service); #endif /* AUDIOLIBMAPPING_H */ diff --git a/BusinessLogic/CMakeLists.txt b/AudioLogic/CMakeLists.txt index 7ae760b..0b72b5c 100644 --- a/BusinessLogic/CMakeLists.txt +++ b/AudioLogic/CMakeLists.txt @@ -22,7 +22,7 @@ INCLUDE_DIRECTORIES(${include_dirs}) ################################################## # AudioLogicBinding ################################################## -ADD_LIBRARY(audiologic-afb MODULE AudioLogicBinding.c AudioLogicMapping.c) +ADD_LIBRARY(audiologic-afb MODULE AudioLogicBinding.c AudioLogicLib.c) SET_TARGET_PROPERTIES(audiologic-afb PROPERTIES PREFIX "" diff --git a/BusinessLogic/README.md b/AudioLogic/README.md index a07f752..399f905 100644 --- a/BusinessLogic/README.md +++ b/AudioLogic/README.md @@ -3,11 +3,11 @@ ------------------------------------------------------------------------ Testing: (from project directory bindings) - * start binder: ~/opt/bin/afb-daemon --ldpaths=./Alsa/src/low-level-binding:./BusinessLogic/audiologic-afb.so --roothttp=htdocs + * start binder: ~/opt/bin/afb-daemon --ldpaths=./build --roothttp=htdocs * connect browser on http://localhost:1234 # Subscribe event for a given board - http://localhost:1234/api/audio/subscribe&devid=hw:0 + http://localhost:1234/api/audio/subscribe?devid=hw:0 # Increase Volume http://localhost:1234/api/audio/setvol?devid=hw:0&pcm=master&vol=50% diff --git a/CMakeLists.txt b/CMakeLists.txt index a5fe3d2..3f120e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ INCLUDE(CheckLibraryExists) INCLUDE(GNUInstallDirs) -SET(binding_install_dir ${CMAKE_INSTALL_FULL_LIBDIR}/afb) +SET(binding_install_dir ${CMAKE_INSTALL_FULL_LIBDIR}/audio) # Generic useful macro ########################################################### @@ -111,4 +111,5 @@ SET(link_libraries # Bindings to compile # -------------------- add_subdirectory(Alsa) -add_subdirectory(BusinessLogic) +add_subdirectory(AudioLogic) +add_subdirectory(htdocs) @@ -8,21 +8,34 @@ AFB_daemon dependency on Standard Linux Distributions # handle dependencies > (OpenSuse-42.2, Fedora-25, Ubuntu 16.04.2LTS) gcc > 4.8 libsystemd-dev>=222 - libmicrohttpd-dev>=0.9.48 openssl-dev uuid-dev + libmicrohttpd with AGL patches http://iot.bzh/download/public/2016/appfw/libmicrohttpd-0.9.49-agl.tgz + afb-daemon from AGL Gerrit git clone https://gerrit.automotivelinux.org/gerrit/src/app-framework-binder + ``` # Might want to add following variables into ~/.bashrc - export CC=gcc-5; export CXX=g++-5 # if using gcc5 - export DEST=$HOME/opt - export LD_LIBRARY_PATH=$DEST/lib64 - export LIBRARY_PATH=$DEST/lib64 - export PKG_CONFIG_PATH=$DEST/lib64/pkgconfig - export PATH=$DEST/bin:$PATH + # export CC=gcc-5; export CXX=g++-5 # if using gcc5 + echo "export DEST=$HOME/opt" >>~/.bashrc + echo "export LD_LIBRARY_PATH=$DEST/lib64" >>~/.bashrc + echo "export LIBRARY_PATH=$DEST/lib64" >>~/.bashrc + echo "export PKG_CONFIG_PATH=$DEST/lib64/pkgconfig" >>~/.bashrc + echo "export PATH=$DEST/bin:$PATH" >>~/.bashrc + source ~/.bashrc + + # install AGL pached version of LibMicroHttpd + wget http://iot.bzh/download/public/2016/appfw/libmicrohttpd-0.9.49-agl.tgz + tar -xzf libmicrohttpd-0.9.49-agl.tgz + cd libmicrohttpd-0.9.49-agl + ./configure --prefix=$DEST + make + make install-strip + + # retreive last AFB_daemon from AGL + git clone https://gerrit.automotivelinux.org/gerrit/src/app-framework-binder # Warning: previous GCC options should be set before initial cmake (clean Build/*) - source ~/.bashrc cd app-framework-binder; mkdir build; cd build cmake -DCMAKE_INSTALL_PREFIX=$DEST .. make @@ -48,7 +61,7 @@ make install ls # Start the binder -afb-daemon --token=x --ldpaths=$INSTALL_DIR/lib --port=1234 --roothttp=$INSTALL_DIR/htdocs/audio-bindings --verbose +afb-daemon --token=x --ldpaths=$INSTALL_DIR/lib/audio --port=1234 --roothttp=$INSTALL_DIR/htdocs/audio-bindings --verbose ``` diff --git a/htdocs/CMakeLists.txt b/htdocs/CMakeLists.txt new file mode 100644 index 0000000..22a4d63 --- /dev/null +++ b/htdocs/CMakeLists.txt @@ -0,0 +1,25 @@ +########################################################################### +# Copyright 2015, 2016, 2017 IoT.bzh +# +# author: Fulup Ar Foll <fulup@iot.bzh> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################### + + + + +################################################## +# HTML Testing Files +################################################## +install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} DESTINATION . FILES_MATCHING PATTERN "*.html" PATTERN "*.js" PATTERN "*.jpg") diff --git a/htdocs/alsa-core.html b/htdocs/alsa-core.html index e408a01..0969b6f 100644 --- a/htdocs/alsa-core.html +++ b/htdocs/alsa-core.html @@ -15,7 +15,7 @@ document.getElementById("connected").innerHTML = "Connected Closed"; } function init() { - ws = new AfbWsItf("api", onopen, onabort, new AfbCtxItf("hello")); + ws = new AfbWsItf("api", onopen, onabort, new AfbCtxItf("mysecret")); } function replyok(obj) { document.getElementById("output").innerHTML = "OK: "+JSON.stringify(obj); @@ -38,6 +38,5 @@ <li><a href="api/alsacore/getctl?devid=hw:0&numid=1&quiet=0">return control numid=1 for hw:0</a> <li><button id="connected" onclick="subscribe('hw:0')">Click to Connected</button></li> <div id="main" style="visibility:hidden"> - <p>Event Output</p> Server says... <div id="output"></div> </div> diff --git a/htdocs/audio-logic.html b/htdocs/audio-logic.html new file mode 100644 index 0000000..ff806ad --- /dev/null +++ b/htdocs/audio-logic.html @@ -0,0 +1,40 @@ +<html> +<head> + <title>Hello world test</title> + + <script type="text/javascript" src="websock.js"></script> + <script type="text/javascript"> + var ws; + + function onopen() { + document.getElementById("main").style.visibility = "visible"; + document.getElementById("connected").innerHTML = "WebSocket Open"; + } + function onabort() { + document.getElementById("main").style.visibility = "hidden"; + document.getElementById("connected").innerHTML = "Connected Closed"; + } + function init() { + ws = new AfbWsItf("api", onopen, onabort, new AfbCtxItf("mysecret")); + } + function replyok(obj) { + document.getElementById("output").innerHTML = "OK: "+JSON.stringify(obj); + } + function replyerr(obj) { + document.getElementById("output").innerHTML = "ERROR: "+JSON.stringify(obj); + } + function subscribe(devid) { + ws.call("alsacore", "subctl", {devid:devid}, replyok, replyerr); + } + </script> + +<body onload="init();"> + <h1>Hello world test</h1> + <ol> + <li><a href="api/audio/setvol?devid=hw:0&pcm=master&vol=50%">Set Master PCM volume to 50%</a> + <li><a href="api/audio/getvol?devid=hw:0&pcm=master">Get Master PCM volume</a> + <li><a href="api/alsacore/monitor?devid=hw:0">Activate devid=hw:0 monitoring</a> + <li><button id="connected" onclick="subscribe('hw:0')">Click to Subscribe</button></li> + <div id="main" style="visibility:hidden"> + Server says... <div id="output"></div> + </div> diff --git a/htdocs/index.html b/htdocs/index.html index dfb3c71..330ef4d 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -5,3 +5,4 @@ <h1>audio-bindings test</h1> <ol> <li><a href="alsa-core.html">AlsaCore Low Level Binding</a> + <li><a href="audio-logic.html">AudioLogic High level business API</a> |