/* faustxml.pure: Parse a Faust XML file. */ /* Copyright (c) 2009 by Albert Graef. This is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This software 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 for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using dict, regex, system, xml; namespace faustxml; /* .. default-domain:: pure .. module:: faustxml .. namespace:: faustxml Appendix: faustxml ================== The faustxml module is provided along with faust2pd to retrieve the description of a Faust DSP from its XML file as a data structure which is ready to be processed by Pure programs. It may also be useful in other Pure applications which need to inspect description of Faust DSPs. The main entry point is the :func:`info` function which takes the name of a Faust-generated XML file as argument and returns a tuple ``(name, descr, version, in, out, controls)`` with the name, description, version, number of inputs and outputs and the toplevel group with the descriptions of the controls of the dsp. A couple of other convenience functions are provided to deal with the control descriptions. Usage ----- Use the following declaration to import this module in your programs:: using faustxml; For convenience, you can also use the following to get access to the module's namespace:: using namespace faustxml; Data Structure -------------- The following constructors are used to represent the UI controls of Faust DSPs: .. constructor:: button label checkbox label A button or checkbox with the given label. .. constructor:: nentry (label,init,min,max,step) vslider (label,init,min,max,step) hslider (label,init,min,max,step) A numeric input control with the given label, initial value, range and stepwidth. .. constructor:: vbargraph (label,min,max) hbargraph (label,min,max) A numeric output control with the given label and range. .. constructor:: vgroup (label,controls) hgroup (label,controls) tgroup (label,controls) A group with the given label and list of controls in the group. */ nonfix button checkbox nentry vslider hslider vbargraph hbargraph vgroup hgroup tgroup; public controlp; /* .. Operations ---------- .. function:: controlp x Check for control description values. */ controlp x = case x of button _ | checkbox _ | nentry _ | vslider _ | hslider _ | vbargraph _ | hbargraph _ | vgroup _ | hgroup _ | tgroup _ = true; _ = false; end; /* .. function:: control_type x control_label x control_args x Access functions for the various components of a control description. */ public control_type control_label control_args; control_type x@(f@_ _) = f if controlp x; control_label x@(_ label::string) | control_label x@(_ (label,_)) = label if controlp x; control_args x@(_ _::string) = () if controlp x; control_args x@(_ (_,args)) = args if controlp x; /* .. function:: controls x This function returns a flat representation of a control group ``x`` as a list of basic control descriptions, which provides a quick way to access all the control values of a Faust DSP. The grouping controls themselves are omitted. You can pass the last component of the return value of the :func:`info` function to this function. */ public controls; controls x@(_ args) = case args of _,ctrls = catmap controls ctrls if listp ctrls; _ = [x] otherwise; end if controlp x; /* .. function:: pcontrols x Works like the :func:`controls` function above, but also replaces the label of each basic control with a fully qualified path consisting of all control labels leading up to the given control. Thus, e.g., the label of a slider ``"gain"`` inside a group ``"voice#0"`` inside the main ``"faust"`` group will be denoted by the label ``"faust/voice#0/gain"``. */ public pcontrols; pcontrols x = controls "" x with controls path (f@_ (label::string,args)) = catmap (controls (join path label)) args if listp args; = [f (join path label,args)]; controls path (f@_ label::string) = [f (join path label)]; join "" s | join s "" = s; join s t = s+"/"+t otherwise; end if controlp x; /* .. function:: info fname Extract the description of a Faust DSP from its XML file. This is the main entry point. Returns a tuple with the name, description and version of the DSP, as well as the number of inputs and outputs and the toplevel group with all the control descriptions. Raises an exception if the XML file doesn't exist or contains invalid contents. Example:: > using faustxml; > let name,descr,version,in,out,group = > faustxml::info "examples/basic/freeverb.dsp.xml"; > name,descr,version,in,out; "freeverb","freeverb -- a Schroeder reverb","1.0",2,2 > using system; > do (puts.str) $ faustxml::pcontrols group; faustxml::hslider ("freeverb/damp",0.5,0.0,1.0,0.025) faustxml::hslider ("freeverb/roomsize",0.5,0.0,1.0,0.025) faustxml::hslider ("freeverb/wet",0.3333,0.0,1.0,0.025) */ public info; private basename trim str_val tree node; private parse parse_doc parse_node parse_prop parse_type parse_control make_control parse_group make_group; info fname::string = case xml::load_file fname 0 of doc = name,descr,info when name = basename fname; descr,info = parse doc; descr = if null descr then name else descr; end if xml::docp doc; _ = throw "could not open XML file" otherwise; end; /* Private operations. *******************************************************/ /* Determine the basename of a file (strip off path and extension). */ basename s::string = s when s::string = last $ split "/" s; s::string = head $ split "." s; end; /* Remove leading and trailing whitespace. */ trim s::string = regex "^[ \t\n]*((.|\n)*[^ \t\n])[ \t\n]*$" REG_EXTENDED s 0!4; /* Parse a string value. */ str_val s::string = case eval (sprintf "quote (%s)" s) of s::string = s; _ = s otherwise; end; /* Generate a tree representation of an entire XML document, or the subtree of an XML document rooted at a given node. */ tree doc::pointer = tree (xml::root doc) if xml::docp doc; tree n::pointer = node (xml::node_info n) [tree m | m = xml::children n; ~xml::is_blank_node m] if xml::nodep n; /* Helper functions to parse the contents of a Faust XML file. */ parse doc = case map (map tree . xml::select doc) ["/faust/name","/faust/version", "/faust/inputs","/faust/outputs", "/faust/ui/activewidgets/widget", "/faust/ui/passivewidgets/widget", "/faust/ui/layout/group"] of [[name],[version],[in],[out],active,passive,layout] = parse_doc (name,version,in,out,active+passive,layout); _ = throw "invalid XML data" otherwise; end; private extern int atoi(char*); private extern double atof(char*); parse_doc (node (xml::element "name" _ _) name, node (xml::element "version" _ _) version, node (xml::element "inputs" _ _) in, node (xml::element "outputs" _ _) out, controls,layout) = case map (parse_group (dict controls)) layout of [controls] = (name,version,in,out,controls); _ = throw "invalid XML data" otherwise; end when [name,version,in,out] = map parse_node [name,version,in,out]; [name,version] = map (parse_prop.trim) [name,version]; [in,out] = map atoi [in,out]; controls = map parse_control controls; end; parse_doc _ = throw "invalid XML data" otherwise; parse_node [node (xml::text s::string) _] = s; parse_node [] = ""; parse_node _ = throw "invalid XML data" otherwise; parse_prop s = case s of "Unknow" = ""; // sic! (old Faust versions) "Unknown" = ""; _::string = str_val s; _ = "" otherwise; end; parse_type s::string = eval $ "faustxml::"+s; parse_control (node (xml::element "widget" _ attrs) params) = case attrs!!["type","id"]+params!!["label"] of [ty,id,label] = make_control (atoi id) ty (str_val label) params; _ = throw "invalid XML data" otherwise; end when attrs = dict attrs; params = dict $ map param params with param (node (xml::element name::string _ _) val) = name=>val if stringp val when val = parse_node val end; param _ = throw "invalid XML data" otherwise; end; end; parse_control _ = throw "invalid XML data" otherwise; make_control id ty label params = id => parse_type ty label if any ((==)ty) ["button","checkbox"]; = case params!!["init","min","max","step"] of res@[init,min,max,step] = id => parse_type ty (label,tuple (map atof res)); _ = throw "invalid XML data" otherwise; end if any ((==)ty) ["vslider","hslider","nentry"]; = case params!!["min","max"] of res@[min,max] = id => parse_type ty (label,tuple (map atof res)); _ = throw "invalid XML data" otherwise; end if any ((==)ty) ["vbargraph","hbargraph"]; make_control _ _ _ _ = throw "invalid XML data" otherwise; parse_group cdict (node (xml::element "group" _ attrs) params) = case attrs!!["type"] of [ty] = make_group cdict ty params; _ = throw "invalid XML data" otherwise; end when attrs = dict attrs end; parse_group cdict (node (xml::element "widgetref" _ ["id"=>id::string]) []) = case cdict!![atoi id] of [c] = c; _ = throw "invalid XML data"; end; parse_group _ _ = throw "invalid XML data" otherwise; make_group cdict ty (node (xml::element "label" _ _) label:params) = case parse_type ty (str_val label,map (parse_group cdict) params) of c = c if stringp label && controlp c; _ = throw "invalid XML data" otherwise; end when label = parse_node label end; make_group _ _ _ = throw "invalid XML data" otherwise;