Browse Source

Add basic SuperCollider support

tags/v1.3.0
Brian Heim 5 years ago
parent
commit
46cb271357
3 changed files with 195 additions and 0 deletions
  1. +21
    -0
      Makefile
  2. +7
    -0
      examples/gain.scd
  3. +167
    -0
      src/SuperColliderEngine.cpp

+ 21
- 0
Makefile View File

@@ -17,6 +17,7 @@ DUKTAPE ?= 0
QUICKJS ?= 1
LUAJIT ?= 1
PYTHON ?= 0
SUPERCOLLIDER ?= 1

# Entropia File System Watcher
efsw := dep/lib/libefsw-static-release.a
@@ -73,6 +74,26 @@ $(luajit):
cd dep/LuaJIT-2.0.5 && $(MAKE) BUILDMODE=static PREFIX="$(DEP_PATH)" install
endif

# SuperCollider
ifeq ($(SUPERCOLLIDER), 1)
SOURCES += src/SuperColliderEngine.cpp
FLAGS += -Idep/supercollider/include -Idep/supercollider/include/common -Idep/supercollider/lang -Idep/supercollider/common -Idep/supercollider/include/plugin_interface
supercollider := dep/supercollider/build/lang/libsclang.a
OBJECTS += $(supercollider)
DEPS += $(supercollider)
SUPERCOLLIDER_CMAKE_FLAGS += -DSUPERNOVA=OFF -DSC_EL=OFF -DSC_VIM=OFF -DSC_ED=OFF -DSC_IDE=OFF -DSC_ABLETON_LINK=OFF -DSC_QT=OFF -DCMAKE_BUILD_TYPE=Release -DSCLANG_SERVER=OFF -DBUILD_TESTING=OFF
SUPERCOLLIDER_SUBMODULES += external_libraries/hidapi external_libraries/nova-simd external_libraries/nova-tt external_libraries/portaudio_sc_org external_libraries/yaml-cpp
SUPERCOLLIDER_BRANCH := topic/vcv-prototype-support
# TODO need some better way of getting link library names!
LDFLAGS += dep/supercollider/build/lang/../external_libraries/libtlsf.a /usr/lib/libpthread.dylib dep/supercollider/build/lang/../external_libraries/hidapi/mac/libhidapi.a dep/supercollider/build/lang/../external_libraries/hidapi/hidapi_parser/libhidapi_parser.a dep/supercollider/build/lang/../external_libraries/libboost_thread_lib.a dep/supercollider/build/lang/../external_libraries/libboost_system_lib.a dep/supercollider/build/lang/../external_libraries/libboost_regex_lib.a dep/supercollider/build/lang/../external_libraries/libboost_filesystem_lib.a /usr/local/opt/readline/lib/libreadline.dylib -framework Carbon -framework CoreAudio -framework CoreMIDI -framework CoreServices -framework IOKit -framework CoreFoundation /usr/local/opt/libsndfile/lib/libsndfile.dylib dep/supercollider/build/lang/../external_libraries/libyaml.a
$(supercollider):
cd dep && git clone "https://github.com/supercollider/supercollider" --branch $(SUPERCOLLIDER_BRANCH) --depth 5
cd dep/supercollider && git submodule update --init -- $(SUPERCOLLIDER_SUBMODULES)
cd dep/supercollider && mkdir build && cd build
cd dep/supercollider/build && cmake .. -G "Unix Makefiles" $(SUPERCOLLIDER_CMAKE_FLAGS)
cd dep/supercollider/build && $(MAKE) libsclang
endif

# Python
ifeq ($(PYTHON), 1)
SOURCES += src/PythonEngine.cpp


+ 7
- 0
examples/gain.scd View File

@@ -0,0 +1,7 @@
// Simplest possible script using all variables, demonstrating buffering
// by Brian Heim

~i = 0;
a = 0;
// ~process = {|x| a = max(a, bench { 12.do { 256.do { |i| sin(i)} }; post(x); }); post(a) }
~vcv_process = {post("test")}

+ 167
- 0
src/SuperColliderEngine.cpp View File

@@ -0,0 +1,167 @@
#include "ScriptEngine.hpp"

#include "lang/SC_LanguageClient.h"
#include "LangSource/SC_LanguageConfig.hpp"
#include "LangSource/SCBase.h"
#include "LangSource/VMGlobals.h"

#include <thread>
#include <atomic>
#include <unistd.h> // getcwd

// SuperCollider script engine for VCV-Prototype
// Original author: Brian Heim <brianlheim@gmail.com>

/* DESIGN
*
* This is currently a work in progress. The idea is that the user writes a script
* that defines a couple environment variables:
*
* ~vcv_frameDivider: Integer
* ~vcv_bufferSize: Integer
* ~vcv_process: Function (VcvPrototypeProcessBlock -> VcvPrototypeProcessBlock)
*
* ~vcv_process is invoked once per process block. Ideally, users should not manipulate
* the block object in any way other than by writing directly to the arrays in `outputs`,
* `knobs`, `lights`, and `switchLights`.
*/

extern rack::plugin::Plugin* pluginInstance; // plugin's version of 'this'
class SuperColliderEngine;

class SC_VcvPrototypeClient final : public SC_LanguageClient {
public:
SC_VcvPrototypeClient(SuperColliderEngine* engine);
~SC_VcvPrototypeClient();

// These will invoke the interpreter
void interpret(const std::string& text) noexcept;
void evaluateProcessBlock(ProcessBlock* block) noexcept;
int getFrameDivider() noexcept { return 1; } // getResultAsInt("^~vcv_frameDivider"); }
int getBufferSize() noexcept { return 256; } // getResultAsInt("^~vcv_bufferSize"); }

void postText(const char* str, size_t len) override;

// No concept of flushing or stdout vs stderr
void postFlush(const char* str, size_t len) override { postText(str, len); }
void postError(const char* str, size_t len) override { postText(str, len); }
void flush() override {}

private:
// TODO
int getResultAsInt(const char* text) noexcept;

SuperColliderEngine* _engine;
};

class SuperColliderEngine final : public ScriptEngine {
public:
~SuperColliderEngine() noexcept { _clientThread.join(); }

std::string getEngineName() override { return "SuperCollider"; }

int run(const std::string& path, const std::string& script) override {
if (!_clientThread.joinable()) {
_clientThread = std::thread([this, script]() {
_client.reset(new SC_VcvPrototypeClient(this));
_client->interpret(script);
setFrameDivider(_client->getFrameDivider());
setBufferSize(_client->getBufferSize());
finishClientLoading();
});
}

return 0;
}

int process() override {
if (waitingOnClient())
return 0;

_client->evaluateProcessBlock(getProcessBlock());
return 0;
}

private:
bool waitingOnClient() const noexcept { return !_clientRunning; }

// TODO handle failure conditions
void finishClientLoading() noexcept { _clientRunning = true; }

std::unique_ptr<SC_VcvPrototypeClient> _client;
std::thread _clientThread; // used only to start up client
std::atomic_bool _clientRunning{false}; // set to true when client is ready to process data
};

// TODO
#define FAIL(_msg_) _engine->display(_msg_)

SC_VcvPrototypeClient::SC_VcvPrototypeClient(SuperColliderEngine* engine)
: SC_LanguageClient("SC VCV-Prototype client")
, _engine(engine)
{
using Path = SC_LanguageConfig::Path;
Path sc_lib_root = rack::asset::plugin(pluginInstance, "dep/supercollider/SCClassLibrary");
Path sc_ext_root = rack::asset::plugin(pluginInstance, "dep/supercollider_extensions");
Path sc_yaml_path = rack::asset::plugin(pluginInstance, "dep/supercollider/sclang_vcv_config.yml");

if (!SC_LanguageConfig::defaultLibraryConfig(/* isStandalone */ true))
FAIL("Failed setting default library config");
if (!gLanguageConfig->addIncludedDirectory(sc_lib_root))
FAIL("Failed to add main include directory");
if (!gLanguageConfig->addIncludedDirectory(sc_ext_root))
FAIL("Failed to add extensions include directory");
if (!SC_LanguageConfig::writeLibraryConfigYAML(sc_yaml_path))
FAIL("Failed to write library config YAML file");

SC_LanguageConfig::setConfigPath(sc_yaml_path);

// TODO allow users to add extensions somehow?

initRuntime();
compileLibrary(/* isStandalone */ true);
// TODO better logging here?
if (!isLibraryCompiled())
FAIL("Error while compiling class library");
}

SC_VcvPrototypeClient::~SC_VcvPrototypeClient() {
shutdownLibrary();
shutdownRuntime();
}

void SC_VcvPrototypeClient::interpret(const std::string& text) noexcept {
setCmdLine(text.c_str());
interpretCmdLine();
}

void SC_VcvPrototypeClient::postText(const char* str, size_t len) {
_engine->display(std::string(str, len));
}

// TODO test code
static long long int gmax = 0;

void SC_VcvPrototypeClient::evaluateProcessBlock(ProcessBlock* block) noexcept {
std::ostringstream builder;

builder << "~vcv_process.value(" << block->knobs[0] << ");\n";

// TIMING TODO test code
auto start = std::chrono::high_resolution_clock::now();
interpret(builder.str());
auto end = std::chrono::high_resolution_clock::now();
auto ticks = (end - start).count();
if (gmax < ticks)
{
gmax = ticks;
printf("MAX TIME %lld\n", ticks);
}
// END TIMING
}

__attribute__((constructor(1000)))
static void constructor() {
addScriptEngine<SuperColliderEngine>("sc");
addScriptEngine<SuperColliderEngine>("scd");
}

Loading…
Cancel
Save