From 17edfc4c20cfd855d68e5b0ef044da2e7509f3f3 Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Mon, 30 Apr 2018 23:30:10 +0200 Subject: On going effort, now load and support LUA+C plugin --- conf.d/project/etc/4a-softmixer-config.json | 23 +++- conf.d/project/etc/CMakeLists.txt | 2 +- conf.d/project/htdocs/AFB-websock.js | 177 +++++++++++++++++++++++++ conf.d/project/htdocs/AudioBinding.css | 7 + conf.d/project/htdocs/AudioBinding.js | 197 ++++++++++++++++++++++++++++ conf.d/project/htdocs/CMakeLists.txt | 34 +++++ conf.d/project/htdocs/README.md | 7 + conf.d/project/htdocs/index.html | 43 ++++++ conf.d/project/lua.d/CMakeLists.txt | 32 +++++ conf.d/project/lua.d/softmixer-00.lua | 86 ++++++++++++ conf.d/project/lua.d/softmixer-01.lua | 46 +++++++ 11 files changed, 649 insertions(+), 5 deletions(-) create mode 100644 conf.d/project/htdocs/AFB-websock.js create mode 100644 conf.d/project/htdocs/AudioBinding.css create mode 100644 conf.d/project/htdocs/AudioBinding.js create mode 100644 conf.d/project/htdocs/CMakeLists.txt create mode 100644 conf.d/project/htdocs/README.md create mode 100644 conf.d/project/htdocs/index.html create mode 100644 conf.d/project/lua.d/CMakeLists.txt create mode 100644 conf.d/project/lua.d/softmixer-00.lua create mode 100644 conf.d/project/lua.d/softmixer-01.lua (limited to 'conf.d/project') diff --git a/conf.d/project/etc/4a-softmixer-config.json b/conf.d/project/etc/4a-softmixer-config.json index 9d89f07..db13705 100644 --- a/conf.d/project/etc/4a-softmixer-config.json +++ b/conf.d/project/etc/4a-softmixer-config.json @@ -3,18 +3,27 @@ "metadata": { "uid": "Soft Mixer", "version": "1.0", - "api": "soft-mixer", + "api": "softmixer", "info": "Soft Mixer emulating hardware mixer", - "require": ["alsa-core"] + "require": ["alsacore"] }, "plugins": [ { "uid": "alsa-router", "ldpath": "package/lib/plugins", + "lua2c": ["AlsaDmix", "AlsaRouter"], "info": "Map alsa-loop subdevices to 4A HAL streams" } ], + "onload": [ + { + "uid": "init-soft-mixer", + "info": "Initialise Audio Router", + "lua": "_init_softmixer_" + } + ], + "sndcards": [ { "uid": "Focusrite_Scarlett_18i8", @@ -72,11 +81,17 @@ "controls": [ { "uid": "stream", - "function": "plugin://alsa-router/stream_ctl" + "callback": { + "plugin": "alsa-router", + "function": "stream_ctl" + } }, { "uid": "zone", - "function": "plugin://alsa-router/zone_ctl" + "callback": { + "plugin": "alsa-router", + "function": "zone_ctl" + } } ] } diff --git a/conf.d/project/etc/CMakeLists.txt b/conf.d/project/etc/CMakeLists.txt index 378effc..2e7daa7 100644 --- a/conf.d/project/etc/CMakeLists.txt +++ b/conf.d/project/etc/CMakeLists.txt @@ -19,7 +19,7 @@ ################################################## # Control Policy Config file ################################################## -PROJECT_TARGET_ADD(soft-mixer-config) +PROJECT_TARGET_ADD(softmixer-config) file(GLOB CONF_FILES "*.json") diff --git a/conf.d/project/htdocs/AFB-websock.js b/conf.d/project/htdocs/AFB-websock.js new file mode 100644 index 0000000..99ab3b8 --- /dev/null +++ b/conf.d/project/htdocs/AFB-websock.js @@ -0,0 +1,177 @@ +var urlws; +var urlhttp; + +AFB = function(base, initialtoken){ + +urlws = "ws://"+window.location.host+"/"+base; +urlhttp = "http://"+window.location.host+"/"+base; + +/*********************************************/ +/**** ****/ +/**** AFB_context ****/ +/**** ****/ +/*********************************************/ +var AFB_context; +{ + var UUID = undefined; + var TOKEN = initialtoken; + + var context = function(token, uuid) { + this.token = token; + this.uuid = uuid; + } + + context.prototype = { + get token() {return TOKEN;}, + set token(tok) {if(tok) TOKEN=tok;}, + get uuid() {return UUID;}, + set uuid(id) {if(id) UUID=id;} + }; + + AFB_context = new context(); +} +/*********************************************/ +/**** ****/ +/**** AFB_websocket ****/ +/**** ****/ +/*********************************************/ +var AFB_websocket; +{ + var CALL = 2; + var RETOK = 3; + var RETERR = 4; + var EVENT = 5; + + var PROTO1 = "x-afb-ws-json1"; + + AFB_websocket = function(onopen, onabort) { + var u = urlws; + if (AFB_context.token) { + u = u + '?x-afb-token=' + AFB_context.token; + if (AFB_context.uuid) + u = u + '&x-afb-uuid=' + AFB_context.uuid; + } + this.ws = new WebSocket(u, [ PROTO1 ]); + this.pendings = {}; + this.awaitens = {}; + this.counter = 0; + this.ws.onopen = onopen.bind(this); + this.ws.onerror = onerror.bind(this); + this.ws.onclose = onclose.bind(this); + this.ws.onmessage = onmessage.bind(this); + this.onopen = onopen; + this.onabort = onabort; + this.onclose = onabort; + } + + function onerror(event) { + var f = this.onabort; + if (f) { + delete this.onopen; + delete this.onabort; + f && f(this); + } + this.onerror && this.onerror(this); + } + + function onopen(event) { + var f = this.onopen; + delete this.onopen; + delete this.onabort; + f && f(this); + } + + function onclose(event) { + for (var id in this.pendings) { + var ferr = this.pendings[id].onerror; + ferr && ferr(null, this); + } + this.pendings = {}; + this.onclose && this.onclose(); + } + + function fire(awaitens, name, data) { + var a = awaitens[name]; + if (a) + a.forEach(function(handler){handler(data);}); + var i = name.indexOf("/"); + if (i >= 0) { + a = awaitens[name.substring(0,i)]; + if (a) + a.forEach(function(handler){handler(data);}); + } + a = awaitens["*"]; + if (a) + a.forEach(function(handler){handler(data);}); + } + + function reply(pendings, id, ans, offset) { + if (id in pendings) { + var p = pendings[id]; + delete pendings[id]; + var f = p[offset]; + f(ans); + } + } + + function onmessage(event) { + var obj = JSON.parse(event.data); + var code = obj[0]; + var id = obj[1]; + var ans = obj[2]; + AFB_context.token = obj[3]; + switch (code) { + case RETOK: + reply(this.pendings, id, ans, 0); + break; + case RETERR: + reply(this.pendings, id, ans, 1); + break; + case EVENT: + default: + fire(this.awaitens, id, ans); + break; + } + } + + function close() { + this.ws.close(); + this.onabort(); + } + + function call(method, request) { + return new Promise((function(resolve, reject){ + var id, arr; + do { + id = String(this.counter = 4095 & (this.counter + 1)); + } while (id in this.pendings); + this.pendings[id] = [ resolve, reject ]; + arr = [CALL, id, method, request ]; + if (AFB_context.token) arr.push(AFB_context.token); + this.ws.send(JSON.stringify(arr)); + }).bind(this)); + } + + function onevent(name, handler) { + var id = name; + var list = this.awaitens[id] || (this.awaitens[id] = []); + list.push(handler); + } + + AFB_websocket.prototype = { + close: close, + call: call, + onevent: onevent + }; +} +/*********************************************/ +/**** ****/ +/**** ****/ +/**** ****/ +/*********************************************/ +return { + context: AFB_context, + ws: AFB_websocket +}; +}; + diff --git a/conf.d/project/htdocs/AudioBinding.css b/conf.d/project/htdocs/AudioBinding.css new file mode 100644 index 0000000..1052aa7 --- /dev/null +++ b/conf.d/project/htdocs/AudioBinding.css @@ -0,0 +1,7 @@ +pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; } +.string { color: green; } +.number { color: darkorange; } +.boolean { color: blue; } +.null { color: magenta; } +.key { color: red; } + diff --git a/conf.d/project/htdocs/AudioBinding.js b/conf.d/project/htdocs/AudioBinding.js new file mode 100644 index 0000000..0f5caf9 --- /dev/null +++ b/conf.d/project/htdocs/AudioBinding.js @@ -0,0 +1,197 @@ + var afb = new AFB("api", "mysecret"); + var ws; + var sndcard="HALNotSelected"; + var evtidx=0; + var numid=0; + + function syntaxHighlight(json) { + if (typeof json !== 'string') { + json = JSON.stringify(json, undefined, 2); + } + json = json.replace(/&/g, '&').replace(//g, '>'); + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + var cls = 'number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'key'; + } else { + cls = 'string'; + } + } else if (/true|false/.test(match)) { + cls = 'boolean'; + } else if (/null/.test(match)) { + cls = 'null'; + } + return '' + match + ''; + }); + } + + function getParameterByName(name, url) { + if (!url) { + url = window.location.href; + } + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); + } + + // default soundcard is "PCH" + var devid=getParameterByName("devid"); + if (!devid) devid="hw:1"; + + var haldev=getParameterByName("haldev"); + if (!haldev) haldev="scarlett-usb"; + + var sndname=getParameterByName("sndname"); + if (!sndname) sndname="PCH"; + + var mode=getParameterByName("mode"); + if (!mode) mode="0"; + + + + + function replyok(obj) { + console.log("replyok:" + JSON.stringify(obj)); + document.getElementById("output").innerHTML = "OK: "+ syntaxHighlight(obj); + } + + function replyerr(obj) { + console.log("replyerr:" + JSON.stringify(obj)); + document.getElementById("output").innerHTML = "ERROR: "+ syntaxHighlight(obj); + } + + function gotevent(obj) { + console.log("gotevent:" + JSON.stringify(obj)); + document.getElementById("outevt").innerHTML = (evtidx++) +": "+JSON.stringify(obj); + } + + function send(message) { + var api = document.getElementById("api").value; + var verb = document.getElementById("verb").value; + document.getElementById("question").innerHTML = "subscribe: "+api+"/"+verb + " (" + JSON.stringify(message) +")"; + ws.call(api+"/"+verb, {data:message}).then(replyok, replyerr); + } + + + // On button click from HTML page + function callbinder(api, verb, query) { + console.log ("subscribe api="+api+" verb="+verb+" query=" +query); + var question = urlws +"/" +api +"/" +verb + "?query=" + JSON.stringify(query); + document.getElementById("question").innerHTML = syntaxHighlight(question); + ws.call(api+"/"+verb, query).then(replyok, replyerr); + } + + + // Retreive Select value and Text from the binder + // Note: selection of value/text for a given context is huggly!!! + function querySelectList (elemid, api, verb, query) { + + console.log("querySelectList elemid=%s api=%s verb=%s query=%s", elemid, api, verb, query); + + var selectobj = document.getElementById(elemid); + if (!selectobj) { + return; + } + + // onlick update selected HAL api + selectobj.onclick=function(){ + sndcard= this.value; + console.log ("Default Selection=" + sndcard); + }; + + function gotit (result) { + + // display response as for normal onclick action + replyok(result); + var response=result.response; + + // fulfill select with avaliable active HAL + for (idx=0; idx 0) return; + + // onlick update selected HAL api + self.onclick=function(){ + numid = parseInt(self.value); + console.log ("Default numid=%d", numid); + }; + + function gotit (result) { + + // display response as for normal onclick action + replyok(result); + var response=result.response; + + + + // fulfill select with avaliable active HAL + for (idx=0; idx + + Simple COntroller Test + + + + + + + +

Simple Control Test

+ + +

+ +

V2 API CALL

+
    +
  1. +
  2. +
    +
  3. +
+ +

V3 API CALL

+
    +
  1. +
  2. +
    +
  3. +
+ +

Signal/Timeout

+
    +
  1. +
+ + diff --git a/conf.d/project/lua.d/CMakeLists.txt b/conf.d/project/lua.d/CMakeLists.txt new file mode 100644 index 0000000..5171349 --- /dev/null +++ b/conf.d/project/lua.d/CMakeLists.txt @@ -0,0 +1,32 @@ +########################################################################### +# Copyright 2017 IoT.bzh +# +# author: Fulup Ar Foll +# +# 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. +########################################################################### + + +################################################## +# Soft Mixer Lua Scripts +################################################## +PROJECT_TARGET_ADD(softmixer-lua) + + file(GLOB LUA_FILES "*.lua") + + add_input_files("${LUA_FILES}") + + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + LABELS "DATA" + OUTPUT_NAME ${TARGET_NAME} + ) diff --git a/conf.d/project/lua.d/softmixer-00.lua b/conf.d/project/lua.d/softmixer-00.lua new file mode 100644 index 0000000..29d2c70 --- /dev/null +++ b/conf.d/project/lua.d/softmixer-00.lua @@ -0,0 +1,86 @@ +--[[ + Copyright (C) 2016 "IoT.bzh" + Author Fulup Ar Foll + + 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. + + Note: this file should be called before any other to assert declare function + is loaded before anything else. + + References: + http://lua-users.org/wiki/DetectingUndefinedVariables + +--]] + + +--=================================================== +--= Niklas Frykholm +-- basically if user tries to create global variable +-- the system will not let them!! +-- call GLOBAL_lock(_G) +-- +--=================================================== +function GLOBAL_lock(t) + local mt = getmetatable(t) or {} + mt.__newindex = lock_new_index + setmetatable(t, mt) +end + +--=================================================== +-- call GLOBAL_unlock(_G) +-- to change things back to normal. +--=================================================== +function GLOBAL_unlock(t) + local mt = getmetatable(t) or {} + mt.__newindex = unlock_new_index + setmetatable(t, mt) +end + +function lock_new_index(t, k, v) + if (string.sub(k,1,1) ~= "_") then + GLOBAL_unlock(_G) + error("GLOBALS are locked -- " .. k .. + " must be declared local or prefix with '_' for globals.", 2) + else + rawset(t, k, v) + end +end + +function unlock_new_index(t, k, v) + rawset(t, k, v) +end + +-- return serialised version of printable table +function Dump_Table(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. Dump_Table(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end +end + + +-- simulate C prinf function +printf = function(s,...) + io.write(s:format(...)) + io.write("\n") + return +end + +-- lock global variable +GLOBAL_lock(_G) diff --git a/conf.d/project/lua.d/softmixer-01.lua b/conf.d/project/lua.d/softmixer-01.lua new file mode 100644 index 0000000..d4ae580 --- /dev/null +++ b/conf.d/project/lua.d/softmixer-01.lua @@ -0,0 +1,46 @@ +--[[ + Copyright (C) 2016 "IoT.bzh" + Author Fulup Ar Foll + + 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. + + + NOTE: strict mode: every global variables should be prefixed by '_' +--]] + +-- Static variables should be prefixed with _ +_EventHandle={} + +-- Call when AlsaCore return HAL active list +function _AlsaPingCB_ (source, result, context) + + AFB:notice (source, "--InLua-- PingCB: result='%s'", Dump_Table(result)) + +end + +-- Display receive arguments and echo them to caller +function _init_softmixer_ (source, args) + + -- create event to push change audio roles to potential listeners + _EventHandle=AFB:evtmake(source, "control") + + -- get list of supported HAL devices + AFB:service(source, "alsacore","ping", {}, "_AlsaPingCB_", {}) + + -- test Lua2C plugin + L2C:alsadmix(source, {}) + + AFB:notice (source, "--InLua-- _init_softmixer_ done") + + return 0 -- happy end +end -- cgit 1.2.3-korg