2 /* faustxml.pure: Parse a Faust XML file. */
4 /* Copyright (c) 2009 by Albert Graef.
6 This is free software; you can redistribute it and/or modify it under the
7 terms of the GNU General Public License as published by the Free Software
8 Foundation; either version 3, or (at your option) any later version.
10 This software is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 You should have received a copy of the GNU General Public License along
16 with this program. If not, see <http://www.gnu.org/licenses/>. */
18 using dict, regex, system, xml;
21 /* .. default-domain:: pure
23 .. namespace:: faustxml
28 The faustxml module is provided along with faust2pd to retrieve the
29 description of a Faust DSP from its XML file as a data structure which
30 is ready to be processed by Pure programs. It may also be useful in other
31 Pure applications which need to inspect description of Faust DSPs.
33 The main entry point is the :func:`info` function which takes the name of a
34 Faust-generated XML file as argument and returns a tuple ``(name, descr,
35 version, in, out, controls)`` with the name, description, version, number
36 of inputs and outputs and the toplevel group with the descriptions of the
37 controls of the dsp. A couple of other convenience functions are provided
38 to deal with the control descriptions.
43 Use the following declaration to import this module in your programs::
47 For convenience, you can also use the following to get access to the
50 using namespace faustxml;
55 The following constructors are used to represent the UI controls of Faust
58 .. constructor:: button label
61 A button or checkbox with the given label.
63 .. constructor:: nentry (label,init,min,max,step)
64 vslider (label,init,min,max,step)
65 hslider (label,init,min,max,step)
67 A numeric input control with the given label, initial value, range and
70 .. constructor:: vbargraph (label,min,max)
71 hbargraph (label,min,max)
73 A numeric output control with the given label and range.
75 .. constructor:: vgroup (label,controls)
76 hgroup (label,controls)
77 tgroup (label,controls)
79 A group with the given label and list of controls in the group. */
81 nonfix button checkbox nentry vslider hslider vbargraph hbargraph
91 .. function:: controlp x
93 Check for control description values. */
97 button _ | checkbox _ | nentry _ | vslider _ | hslider _ |
98 vbargraph _ | hbargraph _ | vgroup _ | hgroup _ | tgroup _ = true;
102 /* .. function:: control_type x
106 Access functions for the various components of a control description. */
108 public control_type control_label control_args;
110 control_type x@(f@_ _) = f if controlp x;
112 control_label x@(_ label::string) |
113 control_label x@(_ (label,_)) = label if controlp x;
115 control_args x@(_ _::string) = () if controlp x;
116 control_args x@(_ (_,args)) = args if controlp x;
118 /* .. function:: controls x
120 This function returns a flat representation of a control group ``x`` as
121 a list of basic control descriptions, which provides a quick way to
122 access all the control values of a Faust DSP. The grouping controls
123 themselves are omitted. You can pass the last component of the return
124 value of the :func:`info` function to this function. */
130 _,ctrls = catmap controls ctrls if listp ctrls;
134 /* .. function:: pcontrols x
136 Works like the :func:`controls` function above, but also replaces the label of
137 each basic control with a fully qualified path consisting of all control
138 labels leading up to the given control. Thus, e.g., the label of a
139 slider ``"gain"`` inside a group ``"voice#0"`` inside the main
140 ``"faust"`` group will be denoted by the label
141 ``"faust/voice#0/gain"``. */
145 pcontrols x = controls "" x with
146 controls path (f@_ (label::string,args))
147 = catmap (controls (join path label)) args
149 = [f (join path label,args)];
150 controls path (f@_ label::string)
151 = [f (join path label)];
154 join s t = s+"/"+t otherwise;
157 /* .. function:: info fname
159 Extract the description of a Faust DSP from its XML file. This is the
160 main entry point. Returns a tuple with the name, description and version
161 of the DSP, as well as the number of inputs and outputs and the toplevel
162 group with all the control descriptions. Raises an exception if the XML
163 file doesn't exist or contains invalid contents.
168 > let name,descr,version,in,out,group =
169 > faustxml::info "examples/basic/freeverb.dsp.xml";
170 > name,descr,version,in,out;
171 "freeverb","freeverb -- a Schroeder reverb","1.0",2,2
173 > do (puts.str) $ faustxml::pcontrols group;
174 faustxml::hslider ("freeverb/damp",0.5,0.0,1.0,0.025)
175 faustxml::hslider ("freeverb/roomsize",0.5,0.0,1.0,0.025)
176 faustxml::hslider ("freeverb/wet",0.3333,0.0,1.0,0.025)
182 private basename trim str_val tree node;
183 private parse parse_doc parse_node parse_prop parse_type
184 parse_control make_control parse_group make_group;
187 = case xml::load_file fname 0 of
188 doc = name,descr,info when
189 name = basename fname; descr,info = parse doc;
190 descr = if null descr then name else descr;
191 end if xml::docp doc;
192 _ = throw "could not open XML file" otherwise;
195 /* Private operations. *******************************************************/
197 /* Determine the basename of a file (strip off path and extension). */
200 = s when s::string = last $ split "/" s; s::string = head $ split "." s; end;
202 /* Remove leading and trailing whitespace. */
204 trim s::string = regex "^[ \t\n]*((.|\n)*[^ \t\n])[ \t\n]*$" REG_EXTENDED
207 /* Parse a string value. */
210 = case eval (sprintf "quote (%s)" s) of
215 /* Generate a tree representation of an entire XML document, or the subtree of
216 an XML document rooted at a given node. */
218 tree doc::pointer = tree (xml::root doc) if xml::docp doc;
219 tree n::pointer = node (xml::node_info n)
220 [tree m | m = xml::children n; ~xml::is_blank_node m]
223 /* Helper functions to parse the contents of a Faust XML file. */
226 = case map (map tree . xml::select doc)
227 ["/faust/name","/faust/version",
228 "/faust/inputs","/faust/outputs",
229 "/faust/ui/activewidgets/widget",
230 "/faust/ui/passivewidgets/widget",
231 "/faust/ui/layout/group"] of
232 [[name],[version],[in],[out],active,passive,layout] =
233 parse_doc (name,version,in,out,active+passive,layout);
234 _ = throw "invalid XML data" otherwise;
237 private extern int atoi(char*);
238 private extern double atof(char*);
240 parse_doc (node (xml::element "name" _ _) name,
241 node (xml::element "version" _ _) version,
242 node (xml::element "inputs" _ _) in,
243 node (xml::element "outputs" _ _) out,
245 = case map (parse_group (dict controls)) layout of
246 [controls] = (name,version,in,out,controls);
247 _ = throw "invalid XML data" otherwise;
249 [name,version,in,out] = map parse_node [name,version,in,out];
250 [name,version] = map (parse_prop.trim) [name,version];
251 [in,out] = map atoi [in,out];
252 controls = map parse_control controls;
254 parse_doc _ = throw "invalid XML data" otherwise;
256 parse_node [node (xml::text s::string) _] = s;
258 parse_node _ = throw "invalid XML data" otherwise;
262 "Unknow" = ""; // sic! (old Faust versions)
264 _::string = str_val s;
268 parse_type s::string = eval $ "faustxml::"+s;
270 parse_control (node (xml::element "widget" _ attrs) params)
271 = case attrs!!["type","id"]+params!!["label"] of
273 make_control (atoi id) ty (str_val label) params;
274 _ = throw "invalid XML data" otherwise;
276 attrs = dict attrs; params = dict $ map param params with
277 param (node (xml::element name::string _ _) val)
278 = name=>val if stringp val when val = parse_node val end;
279 param _ = throw "invalid XML data" otherwise;
282 parse_control _ = throw "invalid XML data" otherwise;
284 make_control id ty label params
285 = id => parse_type ty label if any ((==)ty) ["button","checkbox"];
286 = case params!!["init","min","max","step"] of
287 res@[init,min,max,step] =
288 id => parse_type ty (label,tuple (map atof res));
289 _ = throw "invalid XML data" otherwise;
290 end if any ((==)ty) ["vslider","hslider","nentry"];
291 = case params!!["min","max"] of
293 id => parse_type ty (label,tuple (map atof res));
294 _ = throw "invalid XML data" otherwise;
295 end if any ((==)ty) ["vbargraph","hbargraph"];
296 make_control _ _ _ _ = throw "invalid XML data" otherwise;
298 parse_group cdict (node (xml::element "group" _ attrs) params)
299 = case attrs!!["type"] of
300 [ty] = make_group cdict ty params;
301 _ = throw "invalid XML data" otherwise;
302 end when attrs = dict attrs end;
303 parse_group cdict (node (xml::element "widgetref" _ ["id"=>id::string]) [])
304 = case cdict!![atoi id] of [c] = c; _ = throw "invalid XML data"; end;
305 parse_group _ _ = throw "invalid XML data" otherwise;
307 make_group cdict ty (node (xml::element "label" _ _) label:params)
308 = case parse_type ty (str_val label,map (parse_group cdict) params) of
309 c = c if stringp label && controlp c;
310 _ = throw "invalid XML data" otherwise;
311 end when label = parse_node label end;
312 make_group _ _ _ = throw "invalid XML data" otherwise;