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.

336 lines
8.5KB

  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("Multiple simultaneous libpd (Pure Data) instances not yet supported.");
  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 = system::getFilename(path);
  71. std::string dir = system::getDirectory(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 multiplier, 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] == "toRack:") {
  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. bool utility_is_valid = true;
  196. int utility_idx = -1;
  197. try {
  198. utility_idx = _utility_map.at(atoms[0]); // map::at throws an out-of-range
  199. }
  200. catch (const std::out_of_range& oor) {
  201. WARN("Prototype libpd: %s", s);
  202. utility_is_valid = false;
  203. //display("Warning:"+atoms[1]+" not found!");
  204. // print out on command line
  205. }
  206. if (utility_is_valid) {
  207. switch (utility_idx) {
  208. case 1:
  209. WARN("Prototype libpd: %s", s);
  210. break;
  211. default:
  212. break;
  213. }
  214. }
  215. }
  216. }
  217. bool LibPDEngine::knobChanged(const float* knobs, int i) {
  218. bool knob_changed = false;
  219. if (_old_knobs[i] != knobs[i]) {
  220. knob_changed = true;
  221. _old_knobs[i] = knobs[i];
  222. }
  223. return knob_changed;
  224. }
  225. bool LibPDEngine::switchChanged(const bool* switches, int i) {
  226. bool switch_changed = false;
  227. if (_old_switches[i] != switches[i]) {
  228. switch_changed = true;
  229. _old_switches[i] = switches[i];
  230. }
  231. return switch_changed;
  232. }
  233. const std::map<std::string, int> LibPDEngine::_light_map{
  234. { "L1", 0 },
  235. { "L2", 1 },
  236. { "L3", 2 },
  237. { "L4", 3 },
  238. { "L5", 4 },
  239. { "L6", 5 }
  240. };
  241. const std::map<std::string, int> LibPDEngine::_switchLight_map{
  242. { "S1", 0 },
  243. { "S2", 1 },
  244. { "S3", 2 },
  245. { "S4", 3 },
  246. { "S5", 4 },
  247. { "S6", 5 }
  248. };
  249. const std::map<std::string, int> LibPDEngine::_utility_map{
  250. { "display", 0 },
  251. { "error:", 1 }
  252. };
  253. void LibPDEngine::sendKnob(const int idx, const float value) {
  254. std::string knob = "K" + std::to_string(idx + 1);
  255. libpd_start_message(1);
  256. libpd_add_float(value);
  257. libpd_finish_message("fromRack", knob.c_str());
  258. }
  259. void LibPDEngine::sendSwitch(const int idx, const bool value) {
  260. std::string sw = "S" + std::to_string(idx + 1);
  261. libpd_start_message(1);
  262. libpd_add_float(value);
  263. libpd_finish_message("fromRack", sw.c_str());
  264. }
  265. void LibPDEngine::sendInitialStates(const ProcessBlock* block) {
  266. // knobs
  267. for (int i = 0; i < NUM_ROWS; i++) {
  268. sendKnob(i, block->knobs[i]);
  269. sendSwitch(i, block->knobs[i]);
  270. }
  271. for (int i = 0; i < NUM_ROWS; i++) {
  272. g_lights[i][0] = 0;
  273. g_lights[i][1] = 0;
  274. g_lights[i][2] = 0;
  275. g_switchLights[i][0] = 0;
  276. g_switchLights[i][1] = 0;
  277. g_switchLights[i][2] = 0;
  278. }
  279. //g_utility[0] = "";
  280. //g_utility[1] = "";
  281. //g_display_is_valid = false;
  282. }
  283. __attribute__((constructor(1000)))
  284. static void constructor() {
  285. addScriptEngine<LibPDEngine>(".pd");
  286. }