Browse Source

Added ring buffers, made Rack a class, added stringf helper function, added VIPMutex/VIPLock, added sample rate and block size options to AudioInterface

tags/v0.3.0
Andrew Belt 7 years ago
parent
commit
a18e4a4d6e
40 changed files with 555 additions and 338 deletions
  1. +71
    -24
      include/dsp.hpp
  2. +35
    -21
      include/rack.hpp
  3. +8
    -0
      include/util.hpp
  4. +216
    -0
      src/Rack.cpp
  5. +156
    -39
      src/core/AudioInterface.cpp
  6. +1
    -3
      src/core/MidiInterface.cpp
  7. +6
    -4
      src/core/core.hpp
  8. +1
    -1
      src/gui.cpp
  9. +8
    -7
      src/main.cpp
  10. +1
    -1
      src/plugin.cpp
  11. +0
    -202
      src/rack.cpp
  12. +17
    -0
      src/util.cpp
  13. +1
    -1
      src/widgets/Button.cpp
  14. +1
    -1
      src/widgets/ChoiceButton.cpp
  15. +1
    -1
      src/widgets/InputPort.cpp
  16. +1
    -1
      src/widgets/Knob.cpp
  17. +1
    -1
      src/widgets/Label.cpp
  18. +1
    -1
      src/widgets/Light.cpp
  19. +1
    -1
      src/widgets/Menu.cpp
  20. +1
    -1
      src/widgets/MenuEntry.cpp
  21. +1
    -1
      src/widgets/MenuItem.cpp
  22. +1
    -1
      src/widgets/MenuLabel.cpp
  23. +1
    -1
      src/widgets/MenuOverlay.cpp
  24. +1
    -1
      src/widgets/ModulePanel.cpp
  25. +5
    -6
      src/widgets/ModuleWidget.cpp
  26. +1
    -1
      src/widgets/OutputPort.cpp
  27. +2
    -2
      src/widgets/ParamWidget.cpp
  28. +1
    -1
      src/widgets/Port.cpp
  29. +1
    -1
      src/widgets/QuantityWidget.cpp
  30. +1
    -1
      src/widgets/RackWidget.cpp
  31. +1
    -1
      src/widgets/Scene.cpp
  32. +1
    -1
      src/widgets/Screw.cpp
  33. +1
    -1
      src/widgets/ScrollBar.cpp
  34. +1
    -1
      src/widgets/ScrollWidget.cpp
  35. +1
    -1
      src/widgets/Slider.cpp
  36. +1
    -1
      src/widgets/SpriteWidget.cpp
  37. +1
    -1
      src/widgets/Toolbar.cpp
  38. +1
    -1
      src/widgets/Tooltip.cpp
  39. +1
    -1
      src/widgets/Widget.cpp
  40. +3
    -3
      src/widgets/WireWidget.cpp

+ 71
- 24
include/dsp.hpp View File

@@ -8,14 +8,17 @@
namespace rack {


/** S must be a power of 2 */
/** A simple cyclic buffer.
S must be a power of 2.
push() is constant time O(1)
*/
template <typename T, size_t S>
struct RingBuffer {
T data[S] = {};
T data[S];
size_t start = 0;
size_t end = 0;

size_t mask(size_t i) {
size_t mask(size_t i) const {
return i & (S - 1);
}
void push(T t) {
@@ -25,26 +28,29 @@ struct RingBuffer {
T shift() {
return data[mask(start++)];
}
bool empty() {
bool empty() const {
return start >= end;
}
bool full() {
bool full() const {
return end - start >= S;
}
size_t size() {
size_t size() const {
return end - start;
}
};


/** S must be a power of 2 */
/** A cyclic buffer which maintains a valid linear array of size S by keeping a copy of the buffer in adjacent memory.
S must be a power of 2.
push() is constant time O(2) relative to RingBuffer
*/
template <typename T, size_t S>
struct DoubleRingBuffer {
T data[S*2] = {};
T data[S*2];
size_t start = 0;
size_t end = 0;

size_t mask(size_t i) {
size_t mask(size_t i) const {
return i & (S - 1);
}
void push(T t) {
@@ -55,21 +61,51 @@ struct DoubleRingBuffer {
T shift() {
return data[mask(start++)];
}
bool empty() {
bool empty() const {
return start >= end;
}
bool full() {
bool full() const {
return end - start >= S;
}
size_t size() {
size_t size() const {
return end - start;
}
/** Returns a pointer to S consecutive elements for appending.
If any data is appended, you must call endIncr afterwards.
Pointer is invalidated when any other method is called.
*/
T *endData() {
return &data[mask(end)];
}
void endIncr(size_t n) {
size_t mend = mask(end) + n;
if (mend > S) {
// Copy data backward from the doubled block to the main block
memcpy(data, &data[S], sizeof(T) * (mend - S));
// Don't bother copying forward
}
end += n;
}
/** Returns a pointer to S consecutive elements for consumption
If any data is consumed, call startIncr afterwards.
*/
const T *startData() const {
return &data[mask(start)];
}
void startIncr(size_t n) {
start += n;
}
};


/** A cyclic buffer which maintains a valid linear array of size S by sliding along a larger block of size N.
The linear array of S elements are moved back to the start of the block once it outgrows past the end.
This happens every N - S pushes, so the push() time is O(1 + S / (N - S)).
For example, a float buffer of size 64 in a block of size 1024 is nearly as efficient as RingBuffer.
*/
template <typename T, size_t S, size_t N>
struct AppleRingBuffer {
T data[N] = {};
T data[N];
size_t start = 0;
size_t end = 0;

@@ -85,19 +121,34 @@ struct AppleRingBuffer {
T shift() {
return data[start++];
}
bool empty() {
bool empty() const {
return start >= end;
}
bool full() {
bool full() const {
return end - start >= S;
}
size_t size() {
size_t size() const {
return end - start;
}
/** Returns a pointer to S consecutive elements for appending, requesting to append n elements.
*/
T *endData(size_t n) {
// TODO
return &data[end];
}
/** Returns a pointer to S consecutive elements for consumption
If any data is consumed, call startIncr afterwards.
*/
const T *startData() const {
return &data[start];
}
void startIncr(size_t n) {
// This is valid as long as n < S
start += n;
}
};


template <size_t S>
struct SampleRateConverter {
SRC_STATE *state;
SRC_DATA data;
@@ -116,17 +167,13 @@ struct SampleRateConverter {
void setRatio(float r) {
data.src_ratio = r;
}
void push(const float *in, int length) {
float out[S];
void process(float *in, int inLength, float *out, int outLength) {
data.data_in = in;
data.input_frames = length;
data.input_frames = inLength;
data.data_out = out;
data.output_frames = S;
data.output_frames = outLength;
src_process(state, &data);
}
void push(float in) {
push(&in, 1);
}
};




include/Rack.hpp → include/rack.hpp View File

@@ -4,17 +4,14 @@
#include <list>
#include <vector>
#include <memory>
#include <set>
#include <thread>
#include <mutex>
#include "widgets.hpp"


namespace rack {

extern std::string gApplicationName;
extern std::string gApplicationVersion;

extern Scene *gScene;
extern RackWidget *gRackWidget;

////////////////////
// Plugin manager
////////////////////
@@ -75,10 +72,6 @@ void drawImage(NVGcontext *vg, Vec pos, int imageId);
// rack.cpp
////////////////////

// TODO Find a clean way to make this a variable
#define SAMPLE_RATE 44100


struct Wire;

struct Module {
@@ -106,17 +99,27 @@ struct Wire {
float value = 0.0;
};

void rackInit();
void rackDestroy();
void rackStart();
void rackStop();
// Does not transfer ownership
void rackAddModule(Module *module);
void rackRemoveModule(Module *module);
// Does not transfer ownership
void rackConnectWire(Wire *wire);
void rackDisconnectWire(Wire *wire);
void rackSetParamSmooth(Module *module, int paramId, float value);
struct Rack {
Rack();
~Rack();
/** Launches rack thread */
void start();
void stop();
void run();
void step();
/** Does not transfer pointer ownership */
void addModule(Module *module);
void removeModule(Module *module);
/** Does not transfer pointer ownership */
void addWire(Wire *wire);
void removeWire(Wire *wire);
void setParamSmooth(Module *module, int paramId, float value);

float sampleRate;

struct Impl;
Impl *impl;
};

////////////////////
// Optional helpers for plugins
@@ -186,6 +189,17 @@ Screw *createScrew(Vec pos) {
return screw;
}

////////////////////
// Globals
////////////////////

extern std::string gApplicationName;
extern std::string gApplicationVersion;

extern Scene *gScene;
extern RackWidget *gRackWidget;

extern Rack *gRack;

} // namespace rack


+ 8
- 0
include/util.hpp View File

@@ -3,6 +3,7 @@
#include <stdint.h>
#include <math.h>
#include <random>
#include <string>


namespace rack {
@@ -183,5 +184,12 @@ struct Rect {
}
};

////////////////////
// Helper functions
////////////////////

/** Converts a printf format string and optional arguments into a std::string */
std::string stringf(const char *format, ...);


} // namespace rack

+ 216
- 0
src/Rack.cpp View File

@@ -0,0 +1,216 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <chrono>
#include <condition_variable>

#include "rack.hpp"


namespace rack {

/** Threads which obtain a VIPLock will cause wait() to block for other less important threads.
This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread.
*/
struct VIPMutex {
int count = 0;
std::condition_variable cv;
std::mutex countMutex;

/** Blocks until there are no remaining VIPLocks */
void wait() {
std::unique_lock<std::mutex> lock(countMutex);
while (count > 0)
cv.wait(lock);
}
};

struct VIPLock {
VIPMutex &m;
VIPLock(VIPMutex &m) : m(m) {
std::unique_lock<std::mutex> lock(m.countMutex);
m.count++;
}
~VIPLock() {
std::unique_lock<std::mutex> lock(m.countMutex);
m.count--;
lock.unlock();
m.cv.notify_all();
}
};


struct Rack::Impl {
bool running = false;

std::mutex mutex;
std::thread audioThread;
VIPMutex vipMutex;

std::set<Module*> modules;
// Merely used for keeping track of which module inputs point to which module outputs, to prevent pointer mistakes and make the rack API more rigorous
std::set<Wire*> wires;

// Parameter interpolation
Module *smoothModule = NULL;
int smoothParamId;
float smoothValue;
};


Rack::Rack() {
impl = new Rack::Impl();
sampleRate = 44100.0;
}

Rack::~Rack() {
// Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself before the GUI was destroyed.
assert(impl->wires.empty());
assert(impl->modules.empty());
delete impl;
}

void Rack::start() {
impl->running = true;
impl->audioThread = std::thread(&Rack::run, this);
}

void Rack::stop() {
impl->running = false;
impl->audioThread.join();
}

void Rack::run() {
// Every time the rack waits and locks a mutex, it steps this many frames
const int stepSize = 32;

while (impl->running) {
impl->vipMutex.wait();

auto start = std::chrono::high_resolution_clock::now();
{
std::lock_guard<std::mutex> lock(impl->mutex);
for (int i = 0; i < stepSize; i++) {
step();
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::nanoseconds((long) (0.9 * 1e9 * stepSize / sampleRate)) - (end - start);
// Avoid pegging the CPU at 100% when there are no "blocking" modules like AudioInterface, but still step audio at a reasonable rate
std::this_thread::sleep_for(duration);
}
}

void Rack::step() {
// Param interpolation
if (impl->smoothModule) {
float value = impl->smoothModule->params[impl->smoothParamId];
const float lambda = 60.0; // decay rate is 1 graphics frame
const float snap = 0.0001;
float delta = impl->smoothValue - value;
if (fabsf(delta) < snap) {
impl->smoothModule->params[impl->smoothParamId] = impl->smoothValue;
impl->smoothModule = NULL;
}
else {
value += delta * lambda / sampleRate;
impl->smoothModule->params[impl->smoothParamId] = value;
}
}
// Step all modules
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
for (Module *module : impl->modules) {
// Start clock for CPU usage
start = std::chrono::high_resolution_clock::now();
// Step module by one frame
module->step();
// Stop clock and smooth step time value
end = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> diff = end - start;
float elapsed = diff.count() * sampleRate;
const float lambda = 1.0;
module->cpuTime += (elapsed - module->cpuTime) * lambda / sampleRate;
}
}

void Rack::addModule(Module *module) {
assert(module);
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
// Check that the module is not already added
assert(impl->modules.find(module) == impl->modules.end());
impl->modules.insert(module);
}

void Rack::removeModule(Module *module) {
assert(module);
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
// Remove parameter interpolation which point to this module
if (module == impl->smoothModule) {
impl->smoothModule = NULL;
}
// Check that all wires are disconnected
for (Wire *wire : impl->wires) {
assert(wire->outputModule != module);
assert(wire->inputModule != module);
}
auto it = impl->modules.find(module);
if (it != impl->modules.end()) {
impl->modules.erase(it);
}
}

void Rack::addWire(Wire *wire) {
assert(wire);
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
// It would probably be good to reset the wire voltage
wire->value = 0.0;
// Check that the wire is not already added
assert(impl->wires.find(wire) == impl->wires.end());
assert(wire->outputModule);
assert(wire->inputModule);
// Check that the inputs/outputs are not already used by another cable
for (Wire *wire2 : impl->wires) {
assert(wire2 != wire);
assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId));
assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId));
}
// Connect the wire to inputModule
impl->wires.insert(wire);
wire->inputModule->inputs[wire->inputId] = &wire->value;
wire->outputModule->outputs[wire->outputId] = &wire->value;
}

void Rack::removeWire(Wire *wire) {
assert(wire);
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
// Disconnect wire from inputModule
wire->inputModule->inputs[wire->inputId] = NULL;
wire->outputModule->outputs[wire->outputId] = NULL;

auto it = impl->wires.find(wire);
assert(it != impl->wires.end());
impl->wires.erase(it);
}

void Rack::setParamSmooth(Module *module, int paramId, float value) {
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
// Check existing parameter interpolation
if (impl->smoothModule) {
if (!(impl->smoothModule == module && impl->smoothParamId == paramId)) {
// Jump param value to smooth value
impl->smoothModule->params[impl->smoothParamId] = impl->smoothValue;
}
}
impl->smoothModule = module;
impl->smoothParamId = paramId;
impl->smoothValue = value;
}


} // namespace rack

+ 156
- 39
src/core/AudioInterface.cpp View File

@@ -37,21 +37,25 @@ struct AudioInterface : Module {
PaStream *stream = NULL;
int numOutputs;
int numInputs;
int numFrames;
int bufferFrame;
float outputBuffer[1<<14] = {};
float inputBuffer[1<<14] = {};
int bufferFrame;
// Used because the GUI thread and Rack thread can both interact with this class
std::mutex mutex;

// Set these and call openDevice()
int deviceId = -1;
float sampleRate = 44100.0;
int blockSize = 256;

AudioInterface();
~AudioInterface();
void step();

int getDeviceCount();
std::string getDeviceName(int deviceId);
// Use -1 as the deviceId to close the current device
void openDevice(int deviceId);
void openDevice();
void closeDevice();
};


@@ -59,10 +63,11 @@ AudioInterface::AudioInterface() {
params.resize(NUM_PARAMS);
inputs.resize(NUM_INPUTS);
outputs.resize(NUM_OUTPUTS);
closeDevice();
}

AudioInterface::~AudioInterface() {
openDevice(-1);
closeDevice();
}

void AudioInterface::step() {
@@ -85,14 +90,14 @@ void AudioInterface::step() {
setf(outputs[i], inputBuffer[numOutputs * bufferFrame + i] * 5.0);
}

if (++bufferFrame >= numFrames) {
if (++bufferFrame >= blockSize) {
bufferFrame = 0;
PaError err;

// Input
// (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.)
if (numInputs > 0) {
err = Pa_ReadStream(stream, inputBuffer, numFrames);
err = Pa_ReadStream(stream, inputBuffer, blockSize);
if (err) {
// Ignore buffer underflows
if (err != paInputOverflowed) {
@@ -103,7 +108,7 @@ void AudioInterface::step() {

// Output
if (numOutputs > 0) {
err = Pa_WriteStream(stream, outputBuffer, numFrames);
err = Pa_WriteStream(stream, outputBuffer, blockSize);
if (err) {
// Ignore buffer underflows
if (err != paOutputUnderflowed) {
@@ -123,29 +128,13 @@ std::string AudioInterface::getDeviceName(int deviceId) {
if (!info)
return "";
const PaHostApiInfo *apiInfo = Pa_GetHostApiInfo(info->hostApi);
char name[1024];
snprintf(name, sizeof(name), "%s: %s (%d in, %d out)", apiInfo->name, info->name, info->maxInputChannels, info->maxOutputChannels);
return name;
return stringf("%s: %s (%d in, %d out)", apiInfo->name, info->name, info->maxInputChannels, info->maxOutputChannels);
}

void AudioInterface::openDevice(int deviceId) {
void AudioInterface::openDevice() {
closeDevice();
std::lock_guard<std::mutex> lock(mutex);

// Close existing device
if (stream) {
PaError err;
err = Pa_CloseStream(stream);
if (err) {
// This shouldn't happen:
printf("Failed to close audio stream: %s\n", Pa_GetErrorText(err));
}
stream = NULL;
}
numOutputs = 0;
numInputs = 0;

numFrames = 256;
bufferFrame = 0;
// Open new device
if (deviceId >= 0) {
PaError err;
@@ -173,10 +162,10 @@ void AudioInterface::openDevice(int deviceId) {
inputParameters.hostApiSpecificStreamInfo = NULL;

// Don't use stream parameters if 0 input or output channels
PaStreamParameters *outputP = numOutputs == 0 ? NULL : &outputParameters;
PaStreamParameters *inputP = numInputs == 0 ? NULL : &inputParameters;
err = Pa_OpenStream(&stream, inputP, outputP, SAMPLE_RATE, numFrames, paNoFlag, NULL, NULL);
err = Pa_OpenStream(&stream,
numInputs == 0 ? NULL : &outputParameters,
numOutputs == 0 ? NULL : &inputParameters,
sampleRate, blockSize, paNoFlag, NULL, NULL);
if (err) {
printf("Failed to open audio stream: %s\n", Pa_GetErrorText(err));
return;
@@ -190,12 +179,30 @@ void AudioInterface::openDevice(int deviceId) {
}
}

void AudioInterface::closeDevice() {
std::lock_guard<std::mutex> lock(mutex);

if (stream) {
PaError err;
err = Pa_CloseStream(stream);
if (err) {
// This shouldn't happen:
printf("Failed to close audio stream: %s\n", Pa_GetErrorText(err));
}
stream = NULL;
}
numOutputs = 0;
numInputs = 0;
bufferFrame = 0;
}


struct AudioItem : MenuItem {
AudioInterface *audioInterface;
int deviceId;
void onAction() {
audioInterface->openDevice(deviceId);
audioInterface->deviceId = deviceId;
audioInterface->openDevice();
}
};

@@ -224,6 +231,81 @@ struct AudioChoice : ChoiceButton {
overlay->addChild(menu);
gScene->setOverlay(overlay);
}
void step() {
std::string name = audioInterface->getDeviceName(audioInterface->deviceId);
if (name.empty())
text = "(no device)";
else
text = name;
}
};


struct SampleRateItem : MenuItem {
AudioInterface *audioInterface;
float sampleRate;
void onAction() {
audioInterface->sampleRate = sampleRate;
audioInterface->openDevice();
}
};

struct SampleRateChoice : ChoiceButton {
AudioInterface *audioInterface;
void onAction() {
MenuOverlay *overlay = new MenuOverlay();
Menu *menu = new Menu();
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y));

const float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000};
for (int i = 0; i < 6; i++) {
SampleRateItem *item = new SampleRateItem();
item->audioInterface = audioInterface;
item->sampleRate = sampleRates[i];
item->text = stringf("%.0f", sampleRates[i]);
menu->pushChild(item);
}

overlay->addChild(menu);
gScene->setOverlay(overlay);
}
void step() {
this->text = stringf("%.0f", audioInterface->sampleRate);
}
};


struct BlockSizeItem : MenuItem {
AudioInterface *audioInterface;
int blockSize;
void onAction() {
audioInterface->blockSize = blockSize;
audioInterface->openDevice();
}
};

struct BlockSizeChoice : ChoiceButton {
AudioInterface *audioInterface;
void onAction() {
MenuOverlay *overlay = new MenuOverlay();
Menu *menu = new Menu();
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y));

const int blockSizes[6] = {128, 256, 512, 1024, 2048, 4096};
for (int i = 0; i < 6; i++) {
BlockSizeItem *item = new BlockSizeItem();
item->audioInterface = audioInterface;
item->blockSize = blockSizes[i];
item->text = stringf("%d", blockSizes[i]);
menu->pushChild(item);
}

overlay->addChild(menu);
gScene->setOverlay(overlay);
}
void step() {
this->text = stringf("%d", audioInterface->blockSize);
}
};


@@ -242,13 +324,48 @@ AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()
}

{
AudioChoice *audioChoice = new AudioChoice();
audioChoice->audioInterface = dynamic_cast<AudioInterface*>(module);
audioChoice->text = "Audio device";
audioChoice->box.pos = Vec(margin, yPos);
audioChoice->box.size.x = box.size.x - 10;
addChild(audioChoice);
yPos += audioChoice->box.size.y + 2*margin;
Label *label = new Label();
label->box.pos = Vec(margin, yPos);
label->text = "Audio device";
addChild(label);
yPos += label->box.size.y + margin;

AudioChoice *choice = new AudioChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 10;
addChild(choice);
yPos += choice->box.size.y + 2*margin;
}

{
Label *label = new Label();
label->box.pos = Vec(margin, yPos);
label->text = "Sample rate";
addChild(label);
yPos += label->box.size.y + margin;

SampleRateChoice *choice = new SampleRateChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 10;
addChild(choice);
yPos += choice->box.size.y + 2*margin;
}

{
Label *label = new Label();
label->box.pos = Vec(margin, yPos);
label->text = "Block size";
addChild(label);
yPos += label->box.size.y + margin;

BlockSizeChoice *choice = new BlockSizeChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 10;
addChild(choice);
yPos += choice->box.size.y + 2*margin;
}

{


+ 1
- 3
src/core/MidiInterface.cpp View File

@@ -92,9 +92,7 @@ std::string MidiInterface::getPortName(int portId) {
const PmDeviceInfo *info = Pm_GetDeviceInfo(portId);
if (!info)
return "";
char name[1024];
snprintf(name, sizeof(name), "%s: %s (%s)", info->interf, info->name, info->input ? "input" : "output");
return name;
return stringf("%s: %s (%s)", info->interf, info->name, info->input ? "input" : "output");
}

void MidiInterface::openPort(int portId) {


+ 6
- 4
src/core/core.hpp View File

@@ -1,7 +1,9 @@
#include "Rack.hpp"
#include "rack.hpp"


rack::Plugin *coreInit();
using namespace rack;

Plugin *coreInit();

void audioInit();
void midiInit();
@@ -10,12 +12,12 @@ void midiInit();
// module widgets
////////////////////

struct AudioInterfaceWidget : rack::ModuleWidget {
struct AudioInterfaceWidget : ModuleWidget {
AudioInterfaceWidget();
void draw(NVGcontext *vg);
};

struct MidiInterfaceWidget : rack::ModuleWidget {
struct MidiInterfaceWidget : ModuleWidget {
MidiInterfaceWidget();
void draw(NVGcontext *vg);
};

+ 1
- 1
src/gui.cpp View File

@@ -1,5 +1,5 @@
#include <unistd.h>
#include "Rack.hpp"
#include "rack.hpp"

#include <GL/glew.h>
#include <GLFW/glfw3.h>


+ 8
- 7
src/main.cpp View File

@@ -4,7 +4,7 @@
#include <libgen.h>
#endif

#include "Rack.hpp"
#include "rack.hpp"


namespace rack {
@@ -12,14 +12,16 @@ namespace rack {
std::string gApplicationName = "Virtuoso Rack";
std::string gApplicationVersion = "v0.0.0 alpha";

Rack *gRack;

} // namespace rack


using namespace rack;

int main() {
// Set working directory
#if defined(APPLE)
// macOS workaround for setting the working directory to the location of the .app
{
CFBundleRef bundle = CFBundleGetMainBundle();
CFURLRef bundleURL = CFBundleCopyBundleURL(bundle);
@@ -32,19 +34,18 @@ int main() {
}
#endif


rackInit();
gRack = new Rack();
guiInit();
pluginInit();
gRackWidget->loadPatch("autosave.json");

rackStart();
gRack->start();
guiRun();
rackStop();
gRack->stop();

gRackWidget->savePatch("autosave.json");
guiDestroy();
rackDestroy();
delete gRack;
pluginDestroy();
return 0;
}


+ 1
- 1
src/plugin.cpp View File

@@ -7,7 +7,7 @@
#include <glob.h>
#endif

#include "Rack.hpp"
#include "rack.hpp"
#include "core/core.hpp"




+ 0
- 202
src/rack.cpp View File

@@ -1,202 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <set>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>

#include "Rack.hpp"


namespace rack {

static std::thread thread;
static std::mutex mutex;
static bool running;

static std::set<Module*> modules;
// Merely used for keeping track of which module inputs point to which module outputs, to prevent pointer mistakes and make the rack API rigorous
static std::set<Wire*> wires;

// Parameter interpolation
static Module *smoothModule = NULL;
static int smoothParamId;
static float smoothValue;


// HACK
static std::mutex requestMutex;
static std::condition_variable requestCv;
static bool request = false;

struct RackRequest {
RackRequest() {
std::unique_lock<std::mutex> lock(requestMutex);
request = true;
}
~RackRequest() {
std::unique_lock<std::mutex> lock(requestMutex);
request = false;
lock.unlock();
requestCv.notify_all();
}
};
// END HACK


void rackInit() {
}

void rackDestroy() {
// Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself before the GUI was destroyed.
assert(wires.empty());
assert(modules.empty());
}

void rackStep() {
// Param interpolation
if (smoothModule) {
float value = smoothModule->params[smoothParamId];
const float lambda = 60.0; // decay rate is 1 graphics frame
const float snap = 0.0001;
float delta = smoothValue - value;
if (fabsf(delta) < snap) {
smoothModule->params[smoothParamId] = smoothValue;
smoothModule = NULL;
}
else {
value += delta * lambda / SAMPLE_RATE;
smoothModule->params[smoothParamId] = value;
}
}
// Step all modules
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
for (Module *module : modules) {
// Start clock for CPU usage
start = std::chrono::high_resolution_clock::now();
// Step module by one frame
module->step();
// Stop clock and smooth step time value
end = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> diff = end - start;
float elapsed = diff.count() * SAMPLE_RATE;
const float lambda = 1.0;
module->cpuTime += (elapsed - module->cpuTime) * lambda / SAMPLE_RATE;
}
}

static void rackRun() {
// Every time the rack waits and locks a mutex, it steps this many frames
const int stepSize = 32;

while (running) {
auto start = std::chrono::high_resolution_clock::now();
// This lock is to make sure the GUI gets higher priority than this thread
{
std::unique_lock<std::mutex> lock(requestMutex);
while (request)
requestCv.wait(lock);
}
{
std::lock_guard<std::mutex> lock(mutex);
for (int i = 0; i < stepSize; i++) {
rackStep();
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::nanoseconds((long) (0.9 * 1e9 * stepSize / SAMPLE_RATE)) - (end - start);
std::this_thread::sleep_for(duration);
}
}

void rackStart() {
running = true;
thread = std::thread(rackRun);
}

void rackStop() {
running = false;
thread.join();
}

void rackAddModule(Module *module) {
assert(module);
RackRequest rm;
std::lock_guard<std::mutex> lock(mutex);
// Check that the module is not already added
assert(modules.find(module) == modules.end());
modules.insert(module);
}

void rackRemoveModule(Module *module) {
assert(module);
RackRequest rm;
std::lock_guard<std::mutex> lock(mutex);
// Remove parameter interpolation which point to this module
if (module == smoothModule) {
smoothModule = NULL;
}
// Check that all wires are disconnected
for (Wire *wire : wires) {
assert(wire->outputModule != module);
assert(wire->inputModule != module);
}
auto it = modules.find(module);
if (it != modules.end()) {
modules.erase(it);
}
}

void rackConnectWire(Wire *wire) {
assert(wire);
RackRequest rm;
std::lock_guard<std::mutex> lock(mutex);
// It would probably be good to reset the wire voltage
wire->value = 0.0;
// Check that the wire is not already added
assert(wires.find(wire) == wires.end());
assert(wire->outputModule);
assert(wire->inputModule);
// Check that the inputs/outputs are not already used by another cable
for (Wire *wire2 : wires) {
assert(wire2 != wire);
assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId));
assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId));
}
// Connect the wire to inputModule
wires.insert(wire);
wire->inputModule->inputs[wire->inputId] = &wire->value;
wire->outputModule->outputs[wire->outputId] = &wire->value;
}

void rackDisconnectWire(Wire *wire) {
assert(wire);
RackRequest rm;
std::lock_guard<std::mutex> lock(mutex);
// Disconnect wire from inputModule
wire->inputModule->inputs[wire->inputId] = NULL;
wire->outputModule->outputs[wire->outputId] = NULL;

auto it = wires.find(wire);
assert(it != wires.end());
wires.erase(it);
}

void rackSetParamSmooth(Module *module, int paramId, float value) {
// Check existing parameter interpolation
if (smoothModule) {
if (!(smoothModule == module && smoothParamId == paramId)) {
// Jump param value to smooth value
smoothModule->params[smoothParamId] = smoothValue;
}
}
smoothModule = module;
smoothParamId = paramId;
smoothValue = value;
}


} // namespace rack

+ 17
- 0
src/util.cpp View File

@@ -1,4 +1,6 @@
#include "util.hpp"
#include <stdio.h>
#include <stdarg.h>


namespace rack {
@@ -23,5 +25,20 @@ float randomNormal(){
return normalDist(rng);
}

std::string stringf(const char *format, ...) {
va_list ap;
va_start(ap, format);
size_t size = vsnprintf(NULL, 0, format, ap);
if (size < 0)
return "";
size++;
char *buf = new char[size];
vsnprintf(buf, size, format, ap);
va_end(ap);
std::string s = buf;
delete[] buf;
return s;
}


} // namespace rack

+ 1
- 1
src/widgets/Button.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/ChoiceButton.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/InputPort.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Knob.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Label.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Light.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Menu.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/MenuEntry.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/MenuItem.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/MenuLabel.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/MenuOverlay.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/ModulePanel.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 5
- 6
src/widgets/ModuleWidget.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {
@@ -6,7 +6,7 @@ namespace rack {
ModuleWidget::ModuleWidget(Module *module) {
this->module = module;
if (module) {
rackAddModule(module);
gRack->addModule(module);
}
}

@@ -14,7 +14,7 @@ ModuleWidget::~ModuleWidget() {
// Make sure WireWidget destructors are called *before* removing `module` from the rack.
disconnectPorts();
if (module) {
rackRemoveModule(module);
gRack->removeModule(module);
delete module;
}
}
@@ -101,10 +101,9 @@ void ModuleWidget::draw(NVGcontext *vg) {

// CPU usage text
if (module) {
char text[32];
snprintf(text, sizeof(text), "%.2f%%", module->cpuTime * 10);
std::string text = stringf("%.1f%%", module->cpuTime * 100.0);
nvgSave(vg);
bndSlider(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, module->cpuTime, text, NULL);
bndSlider(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, module->cpuTime, text.c_str(), NULL);
nvgRestore(vg);
}
}


+ 1
- 1
src/widgets/OutputPort.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 2
- 2
src/widgets/ParamWidget.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {
@@ -23,7 +23,7 @@ void ParamWidget::onChange() {
return;

// moduleWidget->module->params[paramId] = value;
rackSetParamSmooth(module, paramId, value);
gRack->setParamSmooth(module, paramId, value);
}




+ 1
- 1
src/widgets/Port.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/QuantityWidget.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/RackWidget.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"
#include <algorithm>




+ 1
- 1
src/widgets/Scene.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Screw.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/ScrollBar.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/ScrollWidget.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Slider.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/SpriteWidget.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Toolbar.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Tooltip.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {


+ 1
- 1
src/widgets/Widget.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"
#include <algorithm>




+ 3
- 3
src/widgets/WireWidget.cpp View File

@@ -1,4 +1,4 @@
#include "Rack.hpp"
#include "rack.hpp"


namespace rack {
@@ -70,7 +70,7 @@ WireWidget::~WireWidget() {

void WireWidget::updateWire() {
if (wire) {
rackDisconnectWire(wire);
gRack->removeWire(wire);
delete wire;
wire = NULL;
}
@@ -80,7 +80,7 @@ void WireWidget::updateWire() {
wire->outputId = outputPort->outputId;
wire->inputModule = inputPort->module;
wire->inputId = inputPort->inputId;
rackConnectWire(wire);
gRack->addWire(wire);
}
}



Loading…
Cancel
Save