Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

829 lines
23KB

  1. #include "ports.h"
  2. #include <ostream>
  3. #include <cassert>
  4. #include <climits>
  5. #include <cstring>
  6. #include <string>
  7. using namespace rtosc;
  8. static inline void scat(char *dest, const char *src)
  9. {
  10. while(*dest) dest++;
  11. if(*dest) dest++;
  12. while(*src && *src!=':') *dest++ = *src++;
  13. *dest = 0;
  14. }
  15. RtData::RtData(void)
  16. :loc(NULL), loc_size(0), obj(NULL), matches(0)
  17. {}
  18. void RtData::reply(const char *path, const char *args, ...)
  19. {
  20. va_list va;
  21. va_start(va,args);
  22. char buffer[1024];
  23. rtosc_vmessage(buffer,1024,path,args,va);
  24. reply(buffer);
  25. va_end(va);
  26. };
  27. void RtData::reply(const char *msg)
  28. {(void)msg;};
  29. void RtData::broadcast(const char *path, const char *args, ...)
  30. {
  31. va_list va;
  32. va_start(va,args);
  33. char buffer[1024];
  34. rtosc_vmessage(buffer,1024,path,args,va);
  35. broadcast(buffer);
  36. va_end(va);
  37. }
  38. void RtData::broadcast(const char *msg)
  39. {reply(msg);};
  40. void metaiterator_advance(const char *&title, const char *&value)
  41. {
  42. if(!title || !*title) {
  43. value = NULL;
  44. return;
  45. }
  46. //Try to find "\0=" after title string
  47. value = title;
  48. while(*value)
  49. ++value;
  50. if(*++value != '=')
  51. value = NULL;
  52. else
  53. value++;
  54. }
  55. Port::MetaIterator::MetaIterator(const char *str)
  56. :title(str), value(NULL)
  57. {
  58. metaiterator_advance(title, value);
  59. }
  60. Port::MetaIterator& Port::MetaIterator::operator++(void)
  61. {
  62. if(!title || !*title) {
  63. title = NULL;
  64. return *this;
  65. }
  66. //search for next parameter start
  67. //aka "\0:" unless "\0\0" is seen
  68. char prev = 0;
  69. while(prev || (*title && *title != ':'))
  70. prev = *title++;
  71. if(!*title)
  72. title = NULL;
  73. else
  74. ++title;
  75. metaiterator_advance(title, value);
  76. return *this;
  77. }
  78. Port::MetaContainer::MetaContainer(const char *str_)
  79. :str_ptr(str_)
  80. {}
  81. Port::MetaIterator Port::MetaContainer::begin(void) const
  82. {
  83. if(str_ptr && *str_ptr == ':')
  84. return Port::MetaIterator(str_ptr+1);
  85. else
  86. return Port::MetaIterator(str_ptr);
  87. }
  88. Port::MetaIterator Port::MetaContainer::end(void) const
  89. {
  90. return MetaIterator(NULL);
  91. }
  92. Port::MetaIterator Port::MetaContainer::find(const char *str) const
  93. {
  94. for(const auto x : *this)
  95. if(!strcmp(x.title, str))
  96. return x;
  97. return NULL;
  98. }
  99. size_t Port::MetaContainer::length(void) const
  100. {
  101. if(!str_ptr || !*str_ptr)
  102. return 0;
  103. char prev = 0;
  104. const char *itr = str_ptr;
  105. while(prev || *itr)
  106. prev = *itr++;
  107. return 2+(itr-str_ptr);
  108. }
  109. const char *Port::MetaContainer::operator[](const char *str) const
  110. {
  111. for(const auto x : *this)
  112. if(!strcmp(x.title, str))
  113. return x.value;
  114. return NULL;
  115. }
  116. //Match the arg string or fail
  117. inline bool arg_matcher(const char *pattern, const char *args)
  118. {
  119. //match anything if now arg restriction is present (ie the ':')
  120. if(*pattern++ != ':')
  121. return true;
  122. const char *arg_str = args;
  123. bool arg_match = *pattern || *pattern == *arg_str;
  124. while(*pattern && *pattern != ':')
  125. arg_match &= (*pattern++==*arg_str++);
  126. if(*pattern==':') {
  127. if(arg_match && !*arg_str)
  128. return true;
  129. else
  130. return arg_matcher(pattern, args); //retry
  131. }
  132. return arg_match;
  133. }
  134. inline bool scmp(const char *a, const char *b)
  135. {
  136. while(*a && *a == *b) a++, b++;
  137. return a[0] == b[0];
  138. }
  139. typedef std::vector<std::string> words_t;
  140. typedef std::vector<std::string> svec_t;
  141. typedef std::vector<const char *> cvec_t;
  142. typedef std::vector<int> ivec_t;
  143. typedef std::vector<int> tuple_t;
  144. typedef std::vector<tuple_t> tvec_t;
  145. namespace rtosc{
  146. class Port_Matcher
  147. {
  148. public:
  149. bool *enump;
  150. svec_t fixed;
  151. cvec_t arg_spec;
  152. ivec_t pos;
  153. ivec_t assoc;
  154. ivec_t remap;
  155. bool rtosc_match_args(const char *pattern, const char *msg)
  156. {
  157. //match anything if now arg restriction is present
  158. //(ie the ':')
  159. if(*pattern++ != ':')
  160. return true;
  161. const char *arg_str = rtosc_argument_string(msg);
  162. bool arg_match = *pattern || *pattern == *arg_str;
  163. while(*pattern && *pattern != ':')
  164. arg_match &= (*pattern++==*arg_str++);
  165. if(*pattern==':') {
  166. if(arg_match && !*arg_str)
  167. return true;
  168. else
  169. return rtosc_match_args(pattern, msg); //retry
  170. }
  171. return arg_match;
  172. }
  173. bool hard_match(int i, const char *msg)
  174. {
  175. if(strncmp(msg, fixed[i].c_str(), fixed[i].length()))
  176. return false;
  177. if(arg_spec[i])
  178. return rtosc_match_args(arg_spec[i], msg);
  179. else
  180. return true;
  181. }
  182. };
  183. }
  184. tvec_t do_hash(const words_t &strs, const ivec_t &pos)
  185. {
  186. tvec_t tvec;
  187. for(auto &s:strs) {
  188. tuple_t tuple;
  189. tuple.push_back(s.length());
  190. for(const auto &p:pos)
  191. if(p < (int)s.size())
  192. tuple.push_back(s[p]);
  193. tvec.push_back(std::move(tuple));
  194. }
  195. return tvec;
  196. }
  197. template<class T>
  198. int count_dups(std::vector<T> &t)
  199. {
  200. int dups = 0;
  201. int N = t.size();
  202. bool mark[t.size()];
  203. memset(mark, 0, N);
  204. for(int i=0; i<N; ++i) {
  205. if(mark[i])
  206. continue;
  207. for(int j=i+1; j<N; ++j) {
  208. if(t[i] == t[j]) {
  209. dups++;
  210. mark[j] = true;
  211. }
  212. }
  213. }
  214. return dups;
  215. }
  216. template<class T, class Z>
  217. bool has(T &t, Z&z)
  218. {
  219. for(auto tt:t)
  220. if(tt==z)
  221. return true;
  222. return false;
  223. }
  224. int rtosc_max(int a, int b) { return a<b?b:a;}
  225. ivec_t find_pos(words_t &strs)
  226. {
  227. ivec_t pos;
  228. int current_dups = strs.size();
  229. int N = 0;
  230. for(auto w:strs)
  231. N = rtosc_max(N,w.length());
  232. int pos_best = -1;
  233. int pos_best_val = INT_MAX;
  234. while(true)
  235. {
  236. for(int i=0; i<N; ++i) {
  237. ivec_t npos = pos;
  238. if(has(pos, i))
  239. continue;
  240. npos.push_back(i);
  241. auto hashed = do_hash(strs, npos);
  242. int d = count_dups(hashed);
  243. if(d < pos_best_val) {
  244. pos_best_val = d;
  245. pos_best = i;
  246. }
  247. }
  248. if(pos_best_val >= current_dups)
  249. break;
  250. current_dups = pos_best_val;
  251. pos.push_back(pos_best);
  252. }
  253. auto hashed = do_hash(strs, pos);
  254. int d = count_dups(hashed);
  255. //printf("Total Dups: %d\n", d);
  256. if(d != 0)
  257. pos.clear();
  258. return pos;
  259. }
  260. ivec_t do_hash(const words_t &strs, const ivec_t &pos, const ivec_t &assoc)
  261. {
  262. ivec_t ivec;
  263. ivec.reserve(strs.size());
  264. for(auto &s:strs) {
  265. int t = s.length();
  266. for(auto p:pos)
  267. if(p < (int)s.size())
  268. t += assoc[s[p]];
  269. ivec.push_back(t);
  270. }
  271. return ivec;
  272. }
  273. ivec_t find_assoc(const words_t &strs, const ivec_t &pos)
  274. {
  275. ivec_t assoc;
  276. int current_dups = strs.size();
  277. int N = 127;
  278. std::vector<char> useful_chars;
  279. for(auto w:strs)
  280. for(auto c:w)
  281. if(!has(useful_chars, c))
  282. useful_chars.push_back(c);
  283. for(int i=0; i<N; ++i)
  284. assoc.push_back(0);
  285. int assoc_best = -1;
  286. int assoc_best_val = INT_MAX;
  287. for(int k=0; k<4; ++k)
  288. {
  289. for(int i:useful_chars) {
  290. assoc_best_val = INT_MAX;
  291. for(int j=0; j<100; ++j) {
  292. //printf(".");
  293. assoc[i] = j;
  294. auto hashed = do_hash(strs, pos, assoc);
  295. //for(int i=0; i<hashed.size(); ++i)
  296. // printf("%d ", hashed[i]);
  297. //printf("\n");
  298. int d = count_dups(hashed);
  299. //printf("dup %d\n",d);
  300. if(d < assoc_best_val) {
  301. assoc_best_val = d;
  302. assoc_best = j;
  303. }
  304. }
  305. assoc[i] = assoc_best;
  306. }
  307. if(assoc_best_val >= current_dups)
  308. break;
  309. current_dups = assoc_best_val;
  310. }
  311. auto hashed = do_hash(strs, pos, assoc);
  312. //int d = count_dups(hashed);
  313. //printf("Total Dups Assoc: %d\n", d);
  314. return assoc;
  315. }
  316. ivec_t find_remap(words_t &strs, ivec_t &pos, ivec_t &assoc)
  317. {
  318. ivec_t remap;
  319. auto hashed = do_hash(strs, pos, assoc);
  320. //for(int i=0; i<strs.size(); ++i)
  321. // printf("%d) '%s'\n", hashed[i], strs[i].c_str());
  322. int N = 0;
  323. for(auto h:hashed)
  324. N = rtosc_max(N,h+1);
  325. for(int i=0; i<N; ++i)
  326. remap.push_back(0);
  327. for(int i=0; i<(int)hashed.size(); ++i)
  328. remap[hashed[i]] = i;
  329. return remap;
  330. }
  331. void generate_minimal_hash(std::vector<std::string> str, Port_Matcher &pm)
  332. {
  333. pm.pos = find_pos(str);
  334. if(pm.pos.empty()) {
  335. fprintf(stderr, "rtosc: Failed to generate minimal hash\n");
  336. return;
  337. }
  338. pm.assoc = find_assoc(str, pm.pos);
  339. pm.remap = find_remap(str, pm.pos, pm.assoc);
  340. }
  341. void generate_minimal_hash(Ports &p, Port_Matcher &pm)
  342. {
  343. svec_t keys;
  344. cvec_t args;
  345. bool enump = false;
  346. for(unsigned i=0; i<p.ports.size(); ++i)
  347. if(strchr(p.ports[i].name, '#'))
  348. enump = true;
  349. if(enump)
  350. return;
  351. for(unsigned i=0; i<p.ports.size(); ++i)
  352. {
  353. std::string tmp = p.ports[i].name;
  354. const char *arg = NULL;
  355. int idx = tmp.find(':');
  356. if(idx > 0) {
  357. arg = p.ports[i].name+idx;
  358. tmp = tmp.substr(0,idx);
  359. }
  360. keys.push_back(tmp);
  361. args.push_back(arg);
  362. }
  363. pm.fixed = keys;
  364. pm.arg_spec = args;
  365. generate_minimal_hash(keys, pm);
  366. }
  367. Ports::Ports(std::initializer_list<Port> l)
  368. :ports(l), impl(new Port_Matcher)
  369. {
  370. generate_minimal_hash(*this, *impl);
  371. impl->enump = new bool[ports.size()];
  372. for(int i=0; i<(int)ports.size(); ++i)
  373. impl->enump[i] = strchr(ports[i].name, '#');
  374. elms = ports.size();
  375. }
  376. Ports::~Ports()
  377. {
  378. delete []impl->enump;
  379. delete impl;
  380. }
  381. #if !defined(__GNUC__)
  382. #define __builtin_expect(a,b) a
  383. #endif
  384. void Ports::dispatch(const char *m, rtosc::RtData &d) const
  385. {
  386. void *obj = d.obj;
  387. //simple case
  388. if(!d.loc || !d.loc_size) {
  389. for(const Port &port: ports) {
  390. if(rtosc_match(port.name,m))
  391. d.port = &port, port.cb(m,d), d.obj = obj;
  392. }
  393. } else {
  394. //TODO this function is certainly buggy at the moment, some tests
  395. //are needed to make it clean
  396. //XXX buffer_size is not properly handled yet
  397. if(__builtin_expect(d.loc[0] == 0, 0)) {
  398. memset(d.loc, 0, d.loc_size);
  399. d.loc[0] = '/';
  400. }
  401. char *old_end = d.loc;
  402. while(*old_end) ++old_end;
  403. if(impl->pos.empty()) { //No perfect minimal hash function
  404. for(unsigned i=0; i<elms; ++i) {
  405. const Port &port = ports[i];
  406. if(!rtosc_match(port.name, m))
  407. continue;
  408. if(!port.ports)
  409. d.matches++;
  410. //Append the path
  411. if(strchr(port.name,'#')) {
  412. const char *msg = m;
  413. char *pos = old_end;
  414. while(*msg && *msg != '/')
  415. *pos++ = *msg++;
  416. if(strchr(port.name, '/'))
  417. *pos++ = '/';
  418. *pos = '\0';
  419. } else
  420. scat(d.loc, port.name);
  421. d.port = &port;
  422. //Apply callback
  423. port.cb(m,d), d.obj = obj;
  424. //Remove the rest of the path
  425. char *tmp = old_end;
  426. while(*tmp) *tmp++=0;
  427. }
  428. } else {
  429. //Define string to be hashed
  430. unsigned len=0;
  431. const char *tmp = m;
  432. while(*tmp && *tmp != '/')
  433. tmp++;
  434. if(*tmp == '/')
  435. tmp++;
  436. len = tmp-m;
  437. //Compute the hash
  438. int t = len;
  439. for(auto p:impl->pos)
  440. if(p < (int)len)
  441. t += impl->assoc[m[p]];
  442. if(t >= (int)impl->remap.size())
  443. return;
  444. int port_num = impl->remap[t];
  445. //Verify the chosen port is correct
  446. if(__builtin_expect(impl->hard_match(port_num, m), 1)) {
  447. const Port &port = ports[impl->remap[t]];
  448. if(!port.ports)
  449. d.matches++;
  450. //Append the path
  451. if(impl->enump[port_num]) {
  452. const char *msg = m;
  453. char *pos = old_end;
  454. while(*msg && *msg != '/')
  455. *pos++ = *msg++;
  456. if(strchr(port.name, '/'))
  457. *pos++ = '/';
  458. *pos = '\0';
  459. } else
  460. memcpy(old_end, impl->fixed[port_num].c_str(),
  461. impl->fixed[port_num].length()+1);
  462. d.port = &port;
  463. //Apply callback
  464. port.cb(m,d), d.obj = obj;
  465. //Remove the rest of the path
  466. old_end[0] = '\0';
  467. }
  468. }
  469. }
  470. }
  471. const Port *Ports::operator[](const char *name) const
  472. {
  473. for(const Port &port:ports) {
  474. const char *_needle = name,
  475. *_haystack = port.name;
  476. while(*_needle && *_needle==*_haystack)_needle++,_haystack++;
  477. if(*_needle == 0 && (*_haystack == ':' || *_haystack == '\0')) {
  478. return &port;
  479. }
  480. }
  481. return NULL;
  482. }
  483. static msg_t snip(msg_t m)
  484. {
  485. while(*m && *m != '/') ++m;
  486. return m+1;
  487. }
  488. const Port *Ports::apropos(const char *path) const
  489. {
  490. if(path && path[0] == '/')
  491. ++path;
  492. for(const Port &port: ports)
  493. if(strchr(port.name,'/') && rtosc_match_path(port.name,path))
  494. return (strchr(path,'/')[1]==0) ? &port :
  495. port.ports->apropos(snip(path));
  496. //This is the lowest level, now find the best port
  497. for(const Port &port: ports)
  498. if(*path && strstr(port.name, path)==port.name)
  499. return &port;
  500. return NULL;
  501. }
  502. void rtosc::walk_ports(const Ports *base,
  503. char *name_buffer,
  504. size_t buffer_size,
  505. void *data,
  506. port_walker_t walker)
  507. {
  508. assert(name_buffer);
  509. //XXX buffer_size is not properly handled yet
  510. if(name_buffer[0] == 0)
  511. name_buffer[0] = '/';
  512. char *old_end = name_buffer;
  513. while(*old_end) ++old_end;
  514. for(const Port &p: *base) {
  515. if(strchr(p.name, '/')) {//it is another tree
  516. if(strchr(p.name,'#')) {
  517. const char *name = p.name;
  518. char *pos = old_end;
  519. while(*name != '#') *pos++ = *name++;
  520. const unsigned max = atoi(name+1);
  521. for(unsigned i=0; i<max; ++i)
  522. {
  523. sprintf(pos,"%d",i);
  524. //Ensure the result is a path
  525. if(strrchr(name_buffer, '/')[1] != '/')
  526. strcat(name_buffer, "/");
  527. //Recurse
  528. rtosc::walk_ports(p.ports, name_buffer, buffer_size,
  529. data, walker);
  530. }
  531. } else {
  532. //Append the path
  533. scat(name_buffer, p.name);
  534. //Recurse
  535. rtosc::walk_ports(p.ports, name_buffer, buffer_size,
  536. data, walker);
  537. }
  538. } else {
  539. if(strchr(p.name,'#')) {
  540. const char *name = p.name;
  541. char *pos = old_end;
  542. while(*name != '#') *pos++ = *name++;
  543. const unsigned max = atoi(name+1);
  544. for(unsigned i=0; i<max; ++i)
  545. {
  546. sprintf(pos,"%d",i);
  547. //Apply walker function
  548. walker(&p, name_buffer, data);
  549. }
  550. } else {
  551. //Append the path
  552. scat(name_buffer, p.name);
  553. //Apply walker function
  554. walker(&p, name_buffer, data);
  555. }
  556. }
  557. //Remove the rest of the path
  558. char *tmp = old_end;
  559. while(*tmp) *tmp++=0;
  560. }
  561. }
  562. void walk_ports2(const rtosc::Ports *base,
  563. char *name_buffer,
  564. size_t buffer_size,
  565. void *data,
  566. rtosc::port_walker_t walker)
  567. {
  568. assert(name_buffer);
  569. //XXX buffer_size is not properly handled yet
  570. if(name_buffer[0] == 0)
  571. name_buffer[0] = '/';
  572. char *old_end = name_buffer;
  573. while(*old_end) ++old_end;
  574. for(const rtosc::Port &p: *base) {
  575. if(strchr(p.name, '/')) {//it is another tree
  576. if(strchr(p.name,'#')) {
  577. const char *name = p.name;
  578. char *pos = old_end;
  579. while(*name != '#') *pos++ = *name++;
  580. const unsigned max = atoi(name+1);
  581. //for(unsigned i=0; i<max; ++i)
  582. {
  583. sprintf(pos,"[0,%d]",max);
  584. //Ensure the result is a path
  585. if(strrchr(name_buffer, '/')[1] != '/')
  586. strcat(name_buffer, "/");
  587. //Recurse
  588. walk_ports2(p.ports, name_buffer, buffer_size,
  589. data, walker);
  590. }
  591. } else {
  592. //Append the path
  593. scat(name_buffer, p.name);
  594. //Recurse
  595. walk_ports2(p.ports, name_buffer, buffer_size,
  596. data, walker);
  597. }
  598. } else {
  599. if(strchr(p.name,'#')) {
  600. const char *name = p.name;
  601. char *pos = old_end;
  602. while(*name != '#') *pos++ = *name++;
  603. const unsigned max = atoi(name+1);
  604. //for(unsigned i=0; i<max; ++i)
  605. {
  606. sprintf(pos,"[0,%d]",max);
  607. //Apply walker function
  608. walker(&p, name_buffer, data);
  609. }
  610. } else {
  611. //Append the path
  612. scat(name_buffer, p.name);
  613. //Apply walker function
  614. walker(&p, name_buffer, data);
  615. }
  616. }
  617. //Remove the rest of the path
  618. char *tmp = old_end;
  619. while(*tmp) *tmp++=0;
  620. }
  621. }
  622. static void units(std::ostream &o, const char *u)
  623. {
  624. if(!u)
  625. return;
  626. o << " units=\"" << u << "\"";
  627. }
  628. void dump_ports_cb(const rtosc::Port *p, const char *name, void *v)
  629. {
  630. std::ostream &o = *(std::ostream*)v;
  631. auto meta = p->meta();
  632. if(meta.find("parameter") != p->meta().end()) {
  633. char type = 0;
  634. const char *foo = strchr(p->name, ':');
  635. if(strchr(foo, 'f'))
  636. type = 'f';
  637. else if(strchr(foo, 'i'))
  638. type = 'i';
  639. else if(strchr(foo, 'c'))
  640. type = 'c';
  641. else if(strchr(foo, 'T'))
  642. type = 't';
  643. if(!type) {
  644. fprintf(stderr, "rtosc port dumper: Cannot handle '%s'\n", p->name);
  645. return;
  646. }
  647. if(type == 't')
  648. {
  649. o << " <message_in pattern=\"" << name << "\" typetag=\"T\">\n";
  650. o << " <desc>Enable " << p->meta()["documentation"] << "</desc>\n";
  651. o << " <param_T symbol=\"x\"/>\n";
  652. o << " </message_in>\n";
  653. o << " <message_in pattern=\"" << name << "\" typetag=\"F\">\n";
  654. o << " <desc>Disable " << p->meta()["documentation"] << "</desc>\n";
  655. o << " <param_F symbol=\"x\"/>\n";
  656. o << " </message_in>\n";
  657. o << " <message_in pattern=\"" << name << "\" typetag=\"\">\n";
  658. o << " <desc>Get state of " << p->meta()["documentation"] << "</desc>\n";
  659. o << " </message_in>\n";
  660. o << " <message_out pattern=\"" << name << "\" typetag=\"T\">\n";
  661. o << " <desc>Value of " << p->meta()["documentation"] << "</desc>\n";
  662. o << " <param_T symbol=\"x\"/>";
  663. o << " </message_out>\n";
  664. o << " <message_out pattern=\"" << name << "\" typetag=\"F\">\n";
  665. o << " <desc>Value of %s</desc>\n", p->meta()["documentation"];
  666. o << " <param_F symbol=\"x\"/>";
  667. o << " </message_out>\n";
  668. return;
  669. }
  670. o << " <message_in pattern=\"" << name << "\" typetag=\"" << type << "\">\n";
  671. o << " <desc>Set Value of " << p->meta()["documentation"] << "</desc>\n";
  672. if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c')
  673. {
  674. o << " <param_" << type << " symbol=\"x\"";
  675. units(o, meta["unit"]);
  676. o << ">\n";
  677. o << " <range_min_max " << (type == 'f' ? "lmin=\"[\" lmax=\"]\"" : "");
  678. o << " min=\"" << meta["min"] << "\" max=\"" << meta["max"] << "\"/>\n";
  679. o << " </param_" << type << ">";
  680. } else {
  681. o << " <param_" << type << " symbol=\"x\"";
  682. units(o, meta["unit"]);
  683. o << "/>\n";
  684. }
  685. o << " </message_in>\n";
  686. o << " <message_in pattern=\"" << name << "\" typetag=\"\">\n";
  687. o << " <desc>Get Value of " << p->meta()["documentation"] << "</desc>\n";
  688. o << " </message_in>\n";
  689. o << " <message_out pattern=\"" << name << "\" typetag=\"" << type << "\">\n";
  690. o << " <desc>Value of " << p->meta()["documentation"] << "</desc>\n";
  691. if(meta.find("min") != meta.end() && meta.find("max") != meta.end() && type != 'c')
  692. {
  693. o << " <param_" << type << " symbol=\"x\"";
  694. units(o, meta["unit"]);
  695. o << ">\n";
  696. o << " <range_min_max " << (type == 'f' ? "lmin=\"[\" lmax=\"]\"" : "");
  697. o << " min=\"" << meta["min"] << "\" max=\"" << meta["max"] << "\"/>\n";
  698. o << " </param_" << type << ">\n";
  699. } else {
  700. o << " <param_" << type << " symbol=\"x\"";
  701. units(o, meta["unit"]);
  702. o << "/>\n";
  703. }
  704. o << " </message_out>\n";
  705. }// else if(meta.find("documentation") != meta.end())
  706. // fprintf(stderr, "Skipping \"%s\"\n", name);
  707. //else
  708. // fprintf(stderr, "Skipping [UNDOCUMENTED] \"%s\"\n", name);
  709. }
  710. std::ostream &rtosc::operator<<(std::ostream &o, rtosc::OscDocFormatter &formatter)
  711. {
  712. o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  713. o << "<osc_unit format_version=\"1.0\">\n";
  714. o << " <meta>\n";
  715. o << " <name>" << formatter.prog_name << "</name>\n";
  716. o << " <uri>" << formatter.uri << "</uri>\n";
  717. o << " <doc_origin>" << formatter.doc_origin << "</doc_origin>\n";
  718. o << " <author><firstname>" << formatter.author_first;
  719. o << "</firstname><lastname>" << formatter.author_last << "</lastname></author>\n";
  720. o << " </meta>\n";
  721. char buffer[1024];
  722. memset(buffer, 0, sizeof(buffer));
  723. walk_ports2(formatter.p, buffer, 1024, &o, dump_ports_cb);
  724. o << "</osc_unit>\n";
  725. return o;
  726. }