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.

356 lines
11KB

  1. #include "ScriptEngine.hpp"
  2. #include "lang/SC_LanguageClient.h"
  3. #include "LangSource/SC_LanguageConfig.hpp"
  4. #include "LangSource/SCBase.h"
  5. #include "LangSource/VMGlobals.h"
  6. #include "LangSource/PyrObject.h"
  7. #include <thread>
  8. #include <atomic>
  9. #include <unistd.h> // getcwd
  10. // SuperCollider script engine for VCV-Prototype
  11. // Original author: Brian Heim <brianlheim@gmail.com>
  12. /* DESIGN
  13. *
  14. * This is currently a work in progress. The idea is that the user writes a script
  15. * that defines a couple environment variables:
  16. *
  17. * ~vcv_frameDivider: Integer
  18. * ~vcv_bufferSize: Integer
  19. * ~vcv_process: Function (VcvPrototypeProcessBlock -> VcvPrototypeProcessBlock)
  20. *
  21. * ~vcv_process is invoked once per process block. Ideally, users should not manipulate
  22. * the block object in any way other than by writing directly to the arrays in `outputs`,
  23. * `knobs`, `lights`, and `switchLights`.
  24. */
  25. extern rack::plugin::Plugin* pluginInstance; // plugin's version of 'this'
  26. class SuperColliderEngine;
  27. class SC_VcvPrototypeClient final : public SC_LanguageClient {
  28. public:
  29. SC_VcvPrototypeClient(SuperColliderEngine* engine);
  30. ~SC_VcvPrototypeClient();
  31. // These will invoke the interpreter
  32. void interpret(const char * text) noexcept;
  33. void evaluateProcessBlock(ProcessBlock* block) noexcept;
  34. void setNumRows() noexcept {
  35. std::string&& command = "VcvPrototypeProcessBlock.numRows = " + std::to_string(NUM_ROWS);
  36. interpret(command.c_str());
  37. }
  38. int getFrameDivider() noexcept { return getResultAsInt("^~vcv_frameDivider"); }
  39. int getBufferSize() noexcept { return getResultAsInt("^~vcv_bufferSize"); }
  40. bool isOk() const noexcept { return _ok; }
  41. void postText(const char* str, size_t len) override;
  42. // No concept of flushing or stdout vs stderr
  43. void postFlush(const char* str, size_t len) override { postText(str, len); }
  44. void postError(const char* str, size_t len) override { postText(str, len); }
  45. void flush() override {}
  46. private:
  47. std::string buildScProcessBlockString(const ProcessBlock* block) const noexcept;
  48. int getResultAsInt(const char* text) noexcept;
  49. bool isVcvPrototypeProcessBlock(const PyrSlot* slot) const noexcept;
  50. // converts top of stack back to ProcessBlock data
  51. void readScProcessBlockResult(ProcessBlock* block) noexcept;
  52. void fail(const std::string& msg) noexcept {
  53. _engine->display(msg);
  54. _ok = false;
  55. }
  56. SuperColliderEngine* _engine;
  57. bool _ok = true;
  58. };
  59. class SuperColliderEngine final : public ScriptEngine {
  60. public:
  61. ~SuperColliderEngine() noexcept { _clientThread.join(); }
  62. std::string getEngineName() override { return "SuperCollider"; }
  63. int run(const std::string& path, const std::string& script) override {
  64. if (!_clientThread.joinable()) {
  65. _clientThread = std::thread([this, script]() {
  66. _client.reset(new SC_VcvPrototypeClient(this));
  67. _client->setNumRows();
  68. _client->interpret(script.c_str());
  69. setFrameDivider(_client->getFrameDivider());
  70. setBufferSize(_client->getBufferSize());
  71. finishClientLoading();
  72. });
  73. }
  74. return 0;
  75. }
  76. int process() override {
  77. if (waitingOnClient())
  78. return 0;
  79. if (clientHasError())
  80. return 1;
  81. _client->evaluateProcessBlock(getProcessBlock());
  82. return clientHasError() ? 1 : 0;
  83. }
  84. private:
  85. bool waitingOnClient() const noexcept { return !_clientRunning; }
  86. bool clientHasError() const noexcept { return !_client->isOk(); }
  87. void finishClientLoading() noexcept { _clientRunning = true; }
  88. std::unique_ptr<SC_VcvPrototypeClient> _client;
  89. std::thread _clientThread; // used only to start up client
  90. std::atomic_bool _clientRunning{false}; // set to true when client is ready to process data
  91. };
  92. SC_VcvPrototypeClient::SC_VcvPrototypeClient(SuperColliderEngine* engine)
  93. : SC_LanguageClient("SC VCV-Prototype client")
  94. , _engine(engine)
  95. {
  96. using Path = SC_LanguageConfig::Path;
  97. Path sc_lib_root = rack::asset::plugin(pluginInstance, "dep/supercollider/SCClassLibrary");
  98. Path sc_ext_root = rack::asset::plugin(pluginInstance, "dep/supercollider_extensions");
  99. Path sc_yaml_path = rack::asset::plugin(pluginInstance, "dep/supercollider/sclang_vcv_config.yml");
  100. if (!SC_LanguageConfig::defaultLibraryConfig(/* isStandalone */ true))
  101. fail("Failed setting default library config");
  102. if (!gLanguageConfig->addIncludedDirectory(sc_lib_root))
  103. fail("Failed to add main include directory");
  104. if (!gLanguageConfig->addIncludedDirectory(sc_ext_root))
  105. fail("Failed to add extensions include directory");
  106. if (!SC_LanguageConfig::writeLibraryConfigYAML(sc_yaml_path))
  107. fail("Failed to write library config YAML file");
  108. SC_LanguageConfig::setConfigPath(sc_yaml_path);
  109. // TODO allow users to add extensions somehow?
  110. initRuntime();
  111. compileLibrary(/* isStandalone */ true);
  112. if (!isLibraryCompiled())
  113. fail("Error while compiling class library");
  114. }
  115. SC_VcvPrototypeClient::~SC_VcvPrototypeClient() {
  116. shutdownLibrary();
  117. shutdownRuntime();
  118. }
  119. void SC_VcvPrototypeClient::interpret(const char* text) noexcept {
  120. setCmdLine(text);
  121. interpretCmdLine();
  122. }
  123. void SC_VcvPrototypeClient::postText(const char* str, size_t len) {
  124. // Ensure the last message logged (presumably an error) stays onscreen.
  125. if (_ok)
  126. _engine->display(std::string(str, len));
  127. }
  128. std::string SC_VcvPrototypeClient::buildScProcessBlockString(const ProcessBlock* block) const noexcept {
  129. std::ostringstream builder;
  130. // TODO so expensive
  131. builder << std::fixed; // to ensure floats aren't actually treated as Integers
  132. builder << "^~vcv_process.value(VcvPrototypeProcessBlock.new("
  133. << block->sampleRate << ','
  134. << block->sampleTime << ','
  135. << block->bufferSize << ',';
  136. auto&& appendInOutArray = [&builder](const int bufferSize, const float (&data)[NUM_ROWS][MAX_BUFFER_SIZE]) {
  137. builder << '[';
  138. for (int i = 0; i < NUM_ROWS; ++i) {
  139. builder << "FloatArray[";
  140. for (int j = 0; j < bufferSize; ++j) {
  141. builder << data[i][j];
  142. if (j != bufferSize - 1)
  143. builder << ',';
  144. }
  145. builder << ']';
  146. if (i != NUM_ROWS - 1)
  147. builder << ',';
  148. }
  149. builder << ']';
  150. };
  151. appendInOutArray(block->bufferSize, block->inputs);
  152. builder << ',';
  153. appendInOutArray(block->bufferSize, block->outputs);
  154. builder << ',';
  155. // knobs
  156. builder << "FloatArray[";
  157. for (int i = 0; i < NUM_ROWS; ++i) {
  158. builder << block->knobs[i];
  159. if (i != NUM_ROWS - 1)
  160. builder << ',';
  161. }
  162. builder << "],";
  163. // switches
  164. builder << '[' << std::boolalpha;
  165. for (int i = 0; i < NUM_ROWS; ++i) {
  166. builder << block->switches[i];
  167. if (i != NUM_ROWS - 1)
  168. builder << ',';
  169. }
  170. builder << "],";
  171. // lights, switchlights
  172. auto&& appendLightsArray = [&builder](const float (&array)[NUM_ROWS][3]) {
  173. builder << '[';
  174. for (int i = 0; i < NUM_ROWS; ++i) {
  175. builder << "FloatArray[" << array[i][0] << ',' << array[i][1] << ',' << array[i][2] << ']';
  176. if (i != NUM_ROWS - 1)
  177. builder << ',';
  178. }
  179. builder << ']';
  180. };
  181. appendLightsArray(block->lights);
  182. builder << ',';
  183. appendLightsArray(block->switchLights);
  184. builder << "));\n";
  185. return builder.str();
  186. }
  187. bool SC_VcvPrototypeClient::isVcvPrototypeProcessBlock(const PyrSlot* slot) const noexcept {
  188. // TODO
  189. return true;
  190. }
  191. void SC_VcvPrototypeClient::readScProcessBlockResult(ProcessBlock* block) noexcept {
  192. auto* resultSlot = &scGlobals()->result;
  193. if (!isVcvPrototypeProcessBlock(resultSlot)) {
  194. fail("Result of ~vcv_process must be an instance of VcvPrototypeProcessBlock");
  195. return;
  196. }
  197. // See .sc object definition
  198. constexpr unsigned outputsSlotIndex = 4;
  199. constexpr unsigned knobsSlotIndex = 5;
  200. constexpr unsigned lightsSlotIndex = 7;
  201. constexpr unsigned switchLightsSlotIndex = 8;
  202. PyrObject* object = slotRawObject(resultSlot);
  203. {
  204. // OUTPUTS
  205. PyrSlot* outputsSlot = &object->slots[outputsSlotIndex];
  206. // TODO check is array
  207. // TODO check size
  208. PyrObject* outputsObj = slotRawObject(outputsSlot);
  209. for (int i = 0; i < NUM_ROWS; ++i) {
  210. PyrSlot* floatArraySlot = &outputsObj->slots[i];
  211. // TODO check is floatarray
  212. // TODO check size
  213. PyrObject* floatArrayObj = slotRawObject(floatArraySlot);
  214. PyrFloatArray* floatArray = reinterpret_cast<PyrFloatArray*>(floatArrayObj);
  215. for (int j = 0; j < block->bufferSize; ++j) {
  216. block->outputs[i][j] = floatArray->f[j];
  217. }
  218. }
  219. }
  220. {
  221. // KNOBS
  222. PyrSlot* knobsSlot = &object->slots[knobsSlotIndex];
  223. // TODO check is floatarray
  224. // TODO check size
  225. PyrObject* knobsObj = slotRawObject(knobsSlot);
  226. PyrFloatArray* floatArray = reinterpret_cast<PyrFloatArray*>(knobsObj);
  227. for (int i = 0; i < NUM_ROWS; ++i) {
  228. block->knobs[i] = floatArray->f[i];
  229. }
  230. }
  231. {
  232. // LIGHTS
  233. PyrSlot* lightsSlot = &object->slots[lightsSlotIndex];
  234. // TODO check is array
  235. // TODO check size
  236. PyrObject* lightsObj = slotRawObject(lightsSlot);
  237. for (int i = 0; i < NUM_ROWS; ++i) {
  238. PyrSlot* floatArraySlot = &lightsObj->slots[i];
  239. // TODO check is floatarray
  240. // TODO check size
  241. PyrObject* floatArrayObj = slotRawObject(floatArraySlot);
  242. PyrFloatArray* floatArray = reinterpret_cast<PyrFloatArray*>(floatArrayObj);
  243. block->lights[i][0] = floatArray->f[0];
  244. block->lights[i][1] = floatArray->f[1];
  245. block->lights[i][2] = floatArray->f[2];
  246. }
  247. }
  248. {
  249. // SWITCH LIGHTS
  250. PyrSlot* switchLightsSlot = &object->slots[switchLightsSlotIndex];
  251. // TODO check is array
  252. // TODO check size
  253. PyrObject* switchLightsObj = slotRawObject(switchLightsSlot);
  254. for (int i = 0; i < NUM_ROWS; ++i) {
  255. PyrSlot* floatArraySlot = &switchLightsObj->slots[i];
  256. // TODO check is floatarray
  257. // TODO check size
  258. PyrObject* floatArrayObj = slotRawObject(floatArraySlot);
  259. PyrFloatArray* floatArray = reinterpret_cast<PyrFloatArray*>(floatArrayObj);
  260. block->switchLights[i][0] = floatArray->f[0];
  261. block->switchLights[i][1] = floatArray->f[1];
  262. block->switchLights[i][2] = floatArray->f[2];
  263. }
  264. }
  265. }
  266. // TODO test code
  267. static long long int gmax = 0;
  268. void SC_VcvPrototypeClient::evaluateProcessBlock(ProcessBlock* block) noexcept {
  269. // TODO timing test code
  270. auto start = std::chrono::high_resolution_clock::now();
  271. auto&& string = buildScProcessBlockString(block);
  272. interpret(string.c_str());
  273. readScProcessBlockResult(block);
  274. auto end = std::chrono::high_resolution_clock::now();
  275. auto ticks = (end - start).count();
  276. if (gmax < ticks)
  277. {
  278. gmax = ticks;
  279. printf("MAX TIME %lld\n", ticks);
  280. }
  281. }
  282. int SC_VcvPrototypeClient::getResultAsInt(const char* text) noexcept {
  283. interpret(text);
  284. auto* resultSlot = &scGlobals()->result;
  285. if (IsInt(resultSlot)) {
  286. auto intResult = slotRawInt(resultSlot);
  287. if (intResult > 0) {
  288. return intResult;
  289. } else {
  290. fail(std::string("Result of '") + text + "' should be > 0");
  291. return -1;
  292. }
  293. } else {
  294. fail(std::string("Result of '") + text + "' should be Integer");
  295. return -1;
  296. }
  297. }
  298. __attribute__((constructor(1000)))
  299. static void constructor() {
  300. addScriptEngine<SuperColliderEngine>("sc");
  301. addScriptEngine<SuperColliderEngine>("scd");
  302. }