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.

317 lines
8.1KB

  1. #include "ScriptEngine.hpp"
  2. #include "z_libpd.h"
  3. #include "util/z_print_util.h"
  4. using namespace rack;
  5. static const int BUFFERSIZE = MAX_BUFFER_SIZE * NUM_ROWS;
  6. // there is no multi-instance support for receiving messages from libpd
  7. // for now, received values for the prototype gui will be stored in global variables
  8. static float g_lights[NUM_ROWS][3] = {};
  9. static float g_switchLights[NUM_ROWS][3] = {};
  10. static std::string g_utility[2] = {};
  11. static bool g_display_is_valid = false;
  12. static std::vector<std::string> split(const std::string& s, char delim) {
  13. std::vector<std::string> result;
  14. std::stringstream ss(s);
  15. std::string item;
  16. while (getline(ss, item, delim)) {
  17. result.push_back(item);
  18. }
  19. return result;
  20. }
  21. struct LibPDEngine : ScriptEngine {
  22. t_pdinstance* _lpd = NULL;
  23. int _pd_block_size = 64;
  24. int _sampleRate = 0;
  25. int _ticks = 0;
  26. bool _init = true;
  27. float _old_knobs[NUM_ROWS] = {};
  28. bool _old_switches[NUM_ROWS] = {};
  29. float _output[BUFFERSIZE] = {};
  30. float _input[BUFFERSIZE] = {};// = (float*)malloc(1024*2*sizeof(float));
  31. const static std::map<std::string, int> _light_map;
  32. const static std::map<std::string, int> _switchLight_map;
  33. const static std::map<std::string, int> _utility_map;
  34. ~LibPDEngine() {
  35. if (_lpd)
  36. libpd_free_instance(_lpd);
  37. }
  38. void sendInitialStates(const ProcessBlock* block);
  39. static void receiveLights(const char* s);
  40. bool knobChanged(const float* knobs, int idx);
  41. bool switchChanged(const bool* knobs, int idx);
  42. void sendKnob(const int idx, const float value);
  43. void sendSwitch(const int idx, const bool value);
  44. std::string getEngineName() override {
  45. return "Pure Data";
  46. }
  47. int run(const std::string& path, const std::string& script) override {
  48. ProcessBlock* block = getProcessBlock();
  49. _sampleRate = block->sampleRate;
  50. setBufferSize(_pd_block_size);
  51. setFrameDivider(1);
  52. libpd_init();
  53. _lpd = libpd_new_instance();
  54. libpd_set_printhook((t_libpd_printhook)libpd_print_concatenator);
  55. libpd_set_concatenated_printhook(receiveLights);
  56. if (libpd_num_instances() > 2) {
  57. display("Sorry, multi instance support in libpd is under development!");
  58. return -1;
  59. }
  60. //display(std::to_string(libpd_num_instances()));
  61. libpd_init_audio(NUM_ROWS, NUM_ROWS, _sampleRate);
  62. // compute audio [; pd dsp 1(
  63. libpd_start_message(1); // one enstry in list
  64. libpd_add_float(1.0f);
  65. libpd_finish_message("pd", "dsp");
  66. std::string version = "pd " + std::to_string(PD_MAJOR_VERSION) + "." +
  67. std::to_string(PD_MINOR_VERSION) + "." +
  68. std::to_string(PD_BUGFIX_VERSION);
  69. display(version);
  70. std::string name = string::filename(path);
  71. std::string dir = string::directory(path);
  72. libpd_openfile(name.c_str(), dir.c_str());
  73. sendInitialStates(block);
  74. return 0;
  75. }
  76. int process() override {
  77. // block
  78. ProcessBlock* block = getProcessBlock();
  79. // get samples prototype
  80. int rows = NUM_ROWS;
  81. for (int s = 0; s < _pd_block_size; s++) {
  82. for (int r = 0; r < rows; r++) {
  83. _input[s * rows + r] = block->inputs[r][s];
  84. }
  85. }
  86. libpd_set_instance(_lpd);
  87. // knobs
  88. for (int i = 0; i < NUM_ROWS; i++) {
  89. if (knobChanged(block->knobs, i)) {
  90. sendKnob(i, block->knobs[i]);
  91. }
  92. }
  93. // lights
  94. for (int i = 0; i < NUM_ROWS; i++) {
  95. block->lights[i][0] = g_lights[i][0];
  96. block->lights[i][1] = g_lights[i][1];
  97. block->lights[i][2] = g_lights[i][2];
  98. }
  99. // switch lights
  100. for (int i = 0; i < NUM_ROWS; i++) {
  101. block->switchLights[i][0] = g_switchLights[i][0];
  102. block->switchLights[i][1] = g_switchLights[i][1];
  103. block->switchLights[i][2] = g_switchLights[i][2];
  104. }
  105. // switches
  106. for (int i = 0; i < NUM_ROWS; i++) {
  107. if (switchChanged(block->switches, i)) {
  108. sendSwitch(i, block->switches[i]);
  109. }
  110. }
  111. // display
  112. if (g_display_is_valid) {
  113. display(g_utility[1]);
  114. g_display_is_valid = false;
  115. }
  116. // process samples in libpd
  117. _ticks = 1;
  118. libpd_process_float(_ticks, _input, _output);
  119. //return samples to prototype
  120. for (int s = 0; s < _pd_block_size; s++) {
  121. for (int r = 0; r < rows; r++) {
  122. block->outputs[r][s] = _output[s * rows + r]; // scale up again to +-5V signal
  123. // there is a correction multilpier, because libpd's output is too quiet(?)
  124. }
  125. }
  126. return 0;
  127. }
  128. };
  129. void LibPDEngine::receiveLights(const char* s) {
  130. std::string str = std::string(s);
  131. std::vector<std::string> atoms = split(str, ' ');
  132. if (atoms[0] == "toVCV:") {
  133. // parse lights list
  134. bool light_is_valid = true;
  135. int light_idx = -1;
  136. try {
  137. light_idx = _light_map.at(atoms[1]); // map::at throws an out-of-range
  138. }
  139. catch (const std::out_of_range& oor) {
  140. light_is_valid = false;
  141. //display("Warning:"+atoms[1]+" not found!");
  142. }
  143. //std::cout << v[1] << ", " << g_led_map[v[1]] << std::endl;
  144. if (light_is_valid && atoms.size() == 5) {
  145. g_lights[light_idx][0] = stof(atoms[2]); // red
  146. g_lights[light_idx][1] = stof(atoms[3]); // green
  147. g_lights[light_idx][2] = stof(atoms[4]); // blue
  148. }
  149. else {
  150. // error
  151. }
  152. // parse switch lights list
  153. bool switchLight_is_valid = true;
  154. int switchLight_idx = -1;
  155. try {
  156. switchLight_idx = _switchLight_map.at(atoms[1]); // map::at throws an out-of-range
  157. }
  158. catch (const std::out_of_range& oor) {
  159. switchLight_is_valid = false;
  160. //display("Warning:"+atoms[1]+" not found!");
  161. }
  162. //std::cout << v[1] << ", " << g_led_map[v[1]] << std::endl;
  163. if (switchLight_is_valid && atoms.size() == 5) {
  164. g_switchLights[switchLight_idx][0] = stof(atoms[2]); // red
  165. g_switchLights[switchLight_idx][1] = stof(atoms[3]); // green
  166. g_switchLights[switchLight_idx][2] = stof(atoms[4]); // blue
  167. }
  168. else {
  169. // error
  170. }
  171. // parse switch lights list
  172. bool utility_is_valid = true;
  173. try {
  174. _utility_map.at(atoms[1]); // map::at throws an out-of-range
  175. }
  176. catch (const std::out_of_range& oor) {
  177. utility_is_valid = false;
  178. //g_display_is_valid = true;
  179. //display("Warning:"+atoms[1]+" not found!");
  180. }
  181. //std::cout << v[1] << ", " << g_led_map[v[1]] << std::endl;
  182. if (utility_is_valid && atoms.size() >= 3) {
  183. g_utility[0] = atoms[1]; // display
  184. g_utility[1] = {""};
  185. for (unsigned i = 0; i < atoms.size() - 2; i++) {
  186. g_utility[1] += " " + atoms[i + 2]; // concatenate message
  187. }
  188. g_display_is_valid = true;
  189. }
  190. else {
  191. // error
  192. }
  193. }
  194. else {
  195. // print out on command line
  196. std::cout << "libpd prototype unrecognizes message: " << std::string(s) << std::endl;
  197. }
  198. }
  199. bool LibPDEngine::knobChanged(const float* knobs, int i) {
  200. bool knob_changed = false;
  201. if (_old_knobs[i] != knobs[i]) {
  202. knob_changed = true;
  203. _old_knobs[i] = knobs[i];
  204. }
  205. return knob_changed;
  206. }
  207. bool LibPDEngine::switchChanged(const bool* switches, int i) {
  208. bool switch_changed = false;
  209. if (_old_switches[i] != switches[i]) {
  210. switch_changed = true;
  211. _old_switches[i] = switches[i];
  212. }
  213. return switch_changed;
  214. }
  215. const std::map<std::string, int> LibPDEngine::_light_map{
  216. { "L1", 0 },
  217. { "L2", 1 },
  218. { "L3", 2 },
  219. { "L4", 3 },
  220. { "L5", 4 },
  221. { "L6", 5 }
  222. };
  223. const std::map<std::string, int> LibPDEngine::_switchLight_map{
  224. { "S1", 0 },
  225. { "S2", 1 },
  226. { "S3", 2 },
  227. { "S4", 3 },
  228. { "S5", 4 },
  229. { "S6", 5 }
  230. };
  231. const std::map<std::string, int> LibPDEngine::_utility_map{
  232. { "display", 0 }
  233. };
  234. void LibPDEngine::sendKnob(const int idx, const float value) {
  235. std::string knob = "K" + std::to_string(idx + 1);
  236. libpd_start_message(1);
  237. libpd_add_float(value);
  238. libpd_finish_message("fromVCV", knob.c_str());
  239. }
  240. void LibPDEngine::sendSwitch(const int idx, const bool value) {
  241. std::string sw = "S" + std::to_string(idx + 1);
  242. libpd_start_message(1);
  243. libpd_add_float(value);
  244. libpd_finish_message("fromVCV", sw.c_str());
  245. }
  246. void LibPDEngine::sendInitialStates(const ProcessBlock* block) {
  247. // knobs
  248. for (int i = 0; i < NUM_ROWS; i++) {
  249. sendKnob(i, block->knobs[i]);
  250. sendSwitch(i, block->knobs[i]);
  251. }
  252. for (int i = 0; i < NUM_ROWS; i++) {
  253. g_lights[i][0] = 0;
  254. g_lights[i][1] = 0;
  255. g_lights[i][2] = 0;
  256. g_switchLights[i][0] = 0;
  257. g_switchLights[i][1] = 0;
  258. g_switchLights[i][2] = 0;
  259. }
  260. //g_utility[0] = "";
  261. //g_utility[1] = "";
  262. //g_display_is_valid = false;
  263. }
  264. __attribute__((constructor(1000)))
  265. static void constructor() {
  266. addScriptEngine<LibPDEngine>("pd");
  267. }