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.

268 lines
6.8KB

  1. #include <math.h>
  2. #include "miditable.h"
  3. using namespace rtosc;
  4. #define RTOSC_INVALID_MIDI 255
  5. class rtosc::MidiTable_Impl
  6. {
  7. public:
  8. MidiTable_Impl(unsigned len, unsigned elms)
  9. :len(len), elms(elms)
  10. {
  11. table = new MidiAddr[elms];
  12. for(unsigned i=0; i<elms; ++i) {
  13. table[i].ch = RTOSC_INVALID_MIDI;
  14. table[i].ctl = RTOSC_INVALID_MIDI;
  15. table[i].path = new char[len];
  16. table[i].conversion = NULL;
  17. }
  18. //TODO initialize all elms
  19. }
  20. ~MidiTable_Impl()
  21. {
  22. for(unsigned i=0; i<elms; ++i) {
  23. delete [] table[i].path;
  24. }
  25. delete [] table;
  26. }
  27. MidiAddr *begin(void) {return table;}
  28. MidiAddr *end(void) {return table + elms;}
  29. unsigned len;
  30. unsigned elms;
  31. MidiAddr *table;
  32. };
  33. //MidiAddr::MidiAddr(void)
  34. // :ch(RTOSC_INVALID_MIDI),ctl(RTOSC_INVALID_MIDI)
  35. //{}
  36. static void black_hole3 (const char *, const char *, const char *, int, int)
  37. {}
  38. static void black_hole2(const char *a, const char *b)
  39. {printf("'%s' and '%s'\n", a,b);}
  40. static void black_hole1(const char *a)
  41. {printf("'%s'\n", a);}
  42. #define MAX_UNHANDLED_PATH 128
  43. MidiTable::MidiTable(const Ports &_dispatch_root)
  44. :dispatch_root(_dispatch_root), unhandled_ch(RTOSC_INVALID_MIDI), unhandled_ctl(RTOSC_INVALID_MIDI),
  45. error_cb(black_hole2), event_cb(black_hole1), modify_cb(black_hole3)
  46. {
  47. impl = new MidiTable_Impl(128,128);
  48. unhandled_path = new char[MAX_UNHANDLED_PATH];
  49. memset(unhandled_path, 0, MAX_UNHANDLED_PATH);
  50. }
  51. MidiTable::~MidiTable()
  52. {
  53. delete impl;
  54. delete [] unhandled_path;
  55. }
  56. bool MidiTable::has(uint8_t ch, uint8_t ctl) const
  57. {
  58. for(auto e: *impl) {
  59. if(e.ch == ch && e.ctl == ctl)
  60. return true;
  61. }
  62. return false;
  63. }
  64. MidiAddr *MidiTable::get(uint8_t ch, uint8_t ctl)
  65. {
  66. for(auto &e: *impl)
  67. if(e.ch==ch && e.ctl == ctl)
  68. return &e;
  69. return NULL;
  70. }
  71. const MidiAddr *MidiTable::get(uint8_t ch, uint8_t ctl) const
  72. {
  73. for(auto &e:*impl)
  74. if(e.ch==ch && e.ctl == ctl)
  75. return &e;
  76. return NULL;
  77. }
  78. bool MidiTable::mash_port(MidiAddr &e, const Port &port)
  79. {
  80. const char *args = strchr(port.name, ':');
  81. if(!args)
  82. return false;
  83. //Consider a path to be typed based upon the argument restrictors
  84. if(strchr(args, 'f')) {
  85. e.type = 'f';
  86. e.conversion = port.metadata;
  87. } else if(strchr(args, 'i'))
  88. e.type = 'i';
  89. else if(strchr(args, 'T'))
  90. e.type = 'T';
  91. else if(strchr(args, 'c'))
  92. e.type = 'c';
  93. else
  94. return false;
  95. return true;
  96. }
  97. void MidiTable::addElm(uint8_t ch, uint8_t ctl, const char *path)
  98. {
  99. const Port *port = dispatch_root.apropos(path);
  100. if(!port || port->ports) {//missing or directory node
  101. error_cb("Bad path", path);
  102. return;
  103. }
  104. if(MidiAddr *e = this->get(ch,ctl)) {
  105. strncpy(e->path,path,impl->len);
  106. if(!mash_port(*e, *port)) {
  107. e->ch = RTOSC_INVALID_MIDI;
  108. e->ctl = RTOSC_INVALID_MIDI;
  109. error_cb("Failed to read metadata", path);
  110. }
  111. modify_cb("REPLACE", path, e->conversion, (int) ch, (int) ctl);
  112. return;
  113. }
  114. for(MidiAddr &e:*impl) {
  115. if(e.ch == RTOSC_INVALID_MIDI) {//free spot
  116. e.ch = ch;
  117. e.ctl = ctl;
  118. strncpy(e.path,path,impl->len);
  119. if(!mash_port(e, *port)) {
  120. e.ch = RTOSC_INVALID_MIDI;
  121. e.ctl = RTOSC_INVALID_MIDI;
  122. error_cb("Failed to read metadata", path);
  123. }
  124. modify_cb("ADD", path, e.conversion, (int) ch, (int) ctl);
  125. return;
  126. }
  127. }
  128. }
  129. void MidiTable::check_learn(void)
  130. {
  131. if(unhandled_ctl == RTOSC_INVALID_MIDI || unhandled_path[0] == '\0')
  132. return;
  133. addElm(unhandled_ch, unhandled_ctl, unhandled_path);
  134. unhandled_ch = unhandled_ctl = RTOSC_INVALID_MIDI;
  135. memset(unhandled_path, 0, MAX_UNHANDLED_PATH);
  136. }
  137. void MidiTable::learn(const char *s)
  138. {
  139. if(strlen(s) > impl->len) {
  140. error_cb("String too long", s);
  141. return;
  142. }
  143. clear_entry(s);
  144. strncpy(unhandled_path, s, MAX_UNHANDLED_PATH);
  145. unhandled_path[MAX_UNHANDLED_PATH-1] = '\0';
  146. check_learn();
  147. }
  148. void MidiTable::clear_entry(const char *s)
  149. {
  150. for(unsigned i=0; i<impl->elms; ++i) {
  151. if(!strcmp(impl->table[i].path, s)) {
  152. //Invalidate
  153. impl->table[i].ch = RTOSC_INVALID_MIDI;
  154. impl->table[i].ctl = RTOSC_INVALID_MIDI;
  155. modify_cb("DEL", s, "", -1, -1);
  156. break;
  157. }
  158. }
  159. }
  160. void MidiTable::process(uint8_t ch, uint8_t ctl, uint8_t val)
  161. {
  162. const MidiAddr *addr = get(ch,ctl);
  163. if(!addr) {
  164. unhandled_ctl = ctl;
  165. unhandled_ch = ch;
  166. check_learn();
  167. return;
  168. }
  169. char buffer[1024];
  170. switch(addr->type)
  171. {
  172. case 'f':
  173. rtosc_message(buffer, 1024, addr->path,
  174. "f", translate(val,addr->conversion));
  175. break;
  176. case 'i':
  177. rtosc_message(buffer, 1024, addr->path,
  178. "i", val);
  179. break;
  180. case 'T':
  181. rtosc_message(buffer, 1024, addr->path,
  182. (val<64 ? "F" : "T"));
  183. break;
  184. case 'c':
  185. rtosc_message(buffer, 1024, addr->path,
  186. "c", val);
  187. }
  188. event_cb(buffer);
  189. }
  190. Port MidiTable::learnPort(void)
  191. {
  192. return Port{"learn:s", "", 0, [this](msg_t m, RtData&){
  193. this->learn(rtosc_argument(m,0).s);
  194. }};
  195. }
  196. Port MidiTable::unlearnPort(void)
  197. {
  198. return Port{"unlearn:s", "", 0, [this](msg_t m, RtData&){
  199. this->clear_entry(rtosc_argument(m,0).s);
  200. }};
  201. }
  202. Port MidiTable::registerPort(void)
  203. {
  204. return Port{"register:iis","", 0, [this](msg_t m,RtData&){
  205. const char *pos = rtosc_argument(m,2).s;
  206. while(*pos) putchar(*pos++);
  207. this->addElm(rtosc_argument(m,0).i,rtosc_argument(m,1).i,rtosc_argument(m,2).s);}};
  208. }
  209. //TODO generalize to an addScalingFunction() system
  210. float MidiTable::translate(uint8_t val, const char *meta_)
  211. {
  212. //Allow for middle value to be set
  213. //TODO consider the centered trait for this op
  214. float x = val!=64.0 ? val/127.0 : 0.5;
  215. Port::MetaContainer meta(meta_);
  216. if(!meta["min"] || !meta["max"] || !meta["scale"]) {
  217. fprintf(stderr, "failed to get properties\n");
  218. return 0.0f;
  219. }
  220. const float min = atof(meta["min"]);
  221. const float max = atof(meta["max"]);
  222. const char *scale = meta["scale"];
  223. if(!strcmp(scale,"linear"))
  224. return x*(max-min)+min;
  225. else if(!strcmp(scale,"logarithmic")) {
  226. const float b = log(min);
  227. const float a = log(max)-b;
  228. return expf(a*x+b);
  229. }
  230. return 0.0f;
  231. }