+++ /dev/null
-
-/* 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 <http://www.gnu.org/licenses/>. */
-
-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;