@@ -8,14 +8,17 @@ | |||||
namespace rack { | 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> | template <typename T, size_t S> | ||||
struct RingBuffer { | struct RingBuffer { | ||||
T data[S] = {}; | |||||
T data[S]; | |||||
size_t start = 0; | size_t start = 0; | ||||
size_t end = 0; | size_t end = 0; | ||||
size_t mask(size_t i) { | |||||
size_t mask(size_t i) const { | |||||
return i & (S - 1); | return i & (S - 1); | ||||
} | } | ||||
void push(T t) { | void push(T t) { | ||||
@@ -25,26 +28,29 @@ struct RingBuffer { | |||||
T shift() { | T shift() { | ||||
return data[mask(start++)]; | return data[mask(start++)]; | ||||
} | } | ||||
bool empty() { | |||||
bool empty() const { | |||||
return start >= end; | return start >= end; | ||||
} | } | ||||
bool full() { | |||||
bool full() const { | |||||
return end - start >= S; | return end - start >= S; | ||||
} | } | ||||
size_t size() { | |||||
size_t size() const { | |||||
return end - start; | 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> | template <typename T, size_t S> | ||||
struct DoubleRingBuffer { | struct DoubleRingBuffer { | ||||
T data[S*2] = {}; | |||||
T data[S*2]; | |||||
size_t start = 0; | size_t start = 0; | ||||
size_t end = 0; | size_t end = 0; | ||||
size_t mask(size_t i) { | |||||
size_t mask(size_t i) const { | |||||
return i & (S - 1); | return i & (S - 1); | ||||
} | } | ||||
void push(T t) { | void push(T t) { | ||||
@@ -55,21 +61,51 @@ struct DoubleRingBuffer { | |||||
T shift() { | T shift() { | ||||
return data[mask(start++)]; | return data[mask(start++)]; | ||||
} | } | ||||
bool empty() { | |||||
bool empty() const { | |||||
return start >= end; | return start >= end; | ||||
} | } | ||||
bool full() { | |||||
bool full() const { | |||||
return end - start >= S; | return end - start >= S; | ||||
} | } | ||||
size_t size() { | |||||
size_t size() const { | |||||
return end - start; | 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> | template <typename T, size_t S, size_t N> | ||||
struct AppleRingBuffer { | struct AppleRingBuffer { | ||||
T data[N] = {}; | |||||
T data[N]; | |||||
size_t start = 0; | size_t start = 0; | ||||
size_t end = 0; | size_t end = 0; | ||||
@@ -85,19 +121,34 @@ struct AppleRingBuffer { | |||||
T shift() { | T shift() { | ||||
return data[start++]; | return data[start++]; | ||||
} | } | ||||
bool empty() { | |||||
bool empty() const { | |||||
return start >= end; | return start >= end; | ||||
} | } | ||||
bool full() { | |||||
bool full() const { | |||||
return end - start >= S; | return end - start >= S; | ||||
} | } | ||||
size_t size() { | |||||
size_t size() const { | |||||
return end - start; | 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 { | struct SampleRateConverter { | ||||
SRC_STATE *state; | SRC_STATE *state; | ||||
SRC_DATA data; | SRC_DATA data; | ||||
@@ -116,17 +167,13 @@ struct SampleRateConverter { | |||||
void setRatio(float r) { | void setRatio(float r) { | ||||
data.src_ratio = 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.data_in = in; | ||||
data.input_frames = length; | |||||
data.input_frames = inLength; | |||||
data.data_out = out; | data.data_out = out; | ||||
data.output_frames = S; | |||||
data.output_frames = outLength; | |||||
src_process(state, &data); | src_process(state, &data); | ||||
} | } | ||||
void push(float in) { | |||||
push(&in, 1); | |||||
} | |||||
}; | }; | ||||
@@ -4,17 +4,14 @@ | |||||
#include <list> | #include <list> | ||||
#include <vector> | #include <vector> | ||||
#include <memory> | #include <memory> | ||||
#include <set> | |||||
#include <thread> | |||||
#include <mutex> | |||||
#include "widgets.hpp" | #include "widgets.hpp" | ||||
namespace rack { | namespace rack { | ||||
extern std::string gApplicationName; | |||||
extern std::string gApplicationVersion; | |||||
extern Scene *gScene; | |||||
extern RackWidget *gRackWidget; | |||||
//////////////////// | //////////////////// | ||||
// Plugin manager | // Plugin manager | ||||
//////////////////// | //////////////////// | ||||
@@ -75,10 +72,6 @@ void drawImage(NVGcontext *vg, Vec pos, int imageId); | |||||
// rack.cpp | // rack.cpp | ||||
//////////////////// | //////////////////// | ||||
// TODO Find a clean way to make this a variable | |||||
#define SAMPLE_RATE 44100 | |||||
struct Wire; | struct Wire; | ||||
struct Module { | struct Module { | ||||
@@ -106,17 +99,27 @@ struct Wire { | |||||
float value = 0.0; | 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 | // Optional helpers for plugins | ||||
@@ -186,6 +189,17 @@ Screw *createScrew(Vec pos) { | |||||
return screw; | return screw; | ||||
} | } | ||||
//////////////////// | |||||
// Globals | |||||
//////////////////// | |||||
extern std::string gApplicationName; | |||||
extern std::string gApplicationVersion; | |||||
extern Scene *gScene; | |||||
extern RackWidget *gRackWidget; | |||||
extern Rack *gRack; | |||||
} // namespace rack | } // namespace rack | ||||
@@ -3,6 +3,7 @@ | |||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <random> | #include <random> | ||||
#include <string> | |||||
namespace rack { | 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 | } // namespace rack |
@@ -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 |
@@ -37,21 +37,25 @@ struct AudioInterface : Module { | |||||
PaStream *stream = NULL; | PaStream *stream = NULL; | ||||
int numOutputs; | int numOutputs; | ||||
int numInputs; | int numInputs; | ||||
int numFrames; | |||||
int bufferFrame; | |||||
float outputBuffer[1<<14] = {}; | float outputBuffer[1<<14] = {}; | ||||
float inputBuffer[1<<14] = {}; | float inputBuffer[1<<14] = {}; | ||||
int bufferFrame; | |||||
// Used because the GUI thread and Rack thread can both interact with this class | // Used because the GUI thread and Rack thread can both interact with this class | ||||
std::mutex mutex; | std::mutex mutex; | ||||
// Set these and call openDevice() | |||||
int deviceId = -1; | |||||
float sampleRate = 44100.0; | |||||
int blockSize = 256; | |||||
AudioInterface(); | AudioInterface(); | ||||
~AudioInterface(); | ~AudioInterface(); | ||||
void step(); | void step(); | ||||
int getDeviceCount(); | int getDeviceCount(); | ||||
std::string getDeviceName(int deviceId); | 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); | params.resize(NUM_PARAMS); | ||||
inputs.resize(NUM_INPUTS); | inputs.resize(NUM_INPUTS); | ||||
outputs.resize(NUM_OUTPUTS); | outputs.resize(NUM_OUTPUTS); | ||||
closeDevice(); | |||||
} | } | ||||
AudioInterface::~AudioInterface() { | AudioInterface::~AudioInterface() { | ||||
openDevice(-1); | |||||
closeDevice(); | |||||
} | } | ||||
void AudioInterface::step() { | void AudioInterface::step() { | ||||
@@ -85,14 +90,14 @@ void AudioInterface::step() { | |||||
setf(outputs[i], inputBuffer[numOutputs * bufferFrame + i] * 5.0); | setf(outputs[i], inputBuffer[numOutputs * bufferFrame + i] * 5.0); | ||||
} | } | ||||
if (++bufferFrame >= numFrames) { | |||||
if (++bufferFrame >= blockSize) { | |||||
bufferFrame = 0; | bufferFrame = 0; | ||||
PaError err; | PaError err; | ||||
// Input | // Input | ||||
// (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.) | // (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.) | ||||
if (numInputs > 0) { | if (numInputs > 0) { | ||||
err = Pa_ReadStream(stream, inputBuffer, numFrames); | |||||
err = Pa_ReadStream(stream, inputBuffer, blockSize); | |||||
if (err) { | if (err) { | ||||
// Ignore buffer underflows | // Ignore buffer underflows | ||||
if (err != paInputOverflowed) { | if (err != paInputOverflowed) { | ||||
@@ -103,7 +108,7 @@ void AudioInterface::step() { | |||||
// Output | // Output | ||||
if (numOutputs > 0) { | if (numOutputs > 0) { | ||||
err = Pa_WriteStream(stream, outputBuffer, numFrames); | |||||
err = Pa_WriteStream(stream, outputBuffer, blockSize); | |||||
if (err) { | if (err) { | ||||
// Ignore buffer underflows | // Ignore buffer underflows | ||||
if (err != paOutputUnderflowed) { | if (err != paOutputUnderflowed) { | ||||
@@ -123,29 +128,13 @@ std::string AudioInterface::getDeviceName(int deviceId) { | |||||
if (!info) | if (!info) | ||||
return ""; | return ""; | ||||
const PaHostApiInfo *apiInfo = Pa_GetHostApiInfo(info->hostApi); | 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); | 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 | // Open new device | ||||
if (deviceId >= 0) { | if (deviceId >= 0) { | ||||
PaError err; | PaError err; | ||||
@@ -173,10 +162,10 @@ void AudioInterface::openDevice(int deviceId) { | |||||
inputParameters.hostApiSpecificStreamInfo = NULL; | inputParameters.hostApiSpecificStreamInfo = NULL; | ||||
// Don't use stream parameters if 0 input or output channels | // 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) { | if (err) { | ||||
printf("Failed to open audio stream: %s\n", Pa_GetErrorText(err)); | printf("Failed to open audio stream: %s\n", Pa_GetErrorText(err)); | ||||
return; | 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 { | struct AudioItem : MenuItem { | ||||
AudioInterface *audioInterface; | AudioInterface *audioInterface; | ||||
int deviceId; | int deviceId; | ||||
void onAction() { | void onAction() { | ||||
audioInterface->openDevice(deviceId); | |||||
audioInterface->deviceId = deviceId; | |||||
audioInterface->openDevice(); | |||||
} | } | ||||
}; | }; | ||||
@@ -224,6 +231,81 @@ struct AudioChoice : ChoiceButton { | |||||
overlay->addChild(menu); | overlay->addChild(menu); | ||||
gScene->setOverlay(overlay); | 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; | |||||
} | } | ||||
{ | { | ||||
@@ -92,9 +92,7 @@ std::string MidiInterface::getPortName(int portId) { | |||||
const PmDeviceInfo *info = Pm_GetDeviceInfo(portId); | const PmDeviceInfo *info = Pm_GetDeviceInfo(portId); | ||||
if (!info) | if (!info) | ||||
return ""; | 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) { | void MidiInterface::openPort(int portId) { | ||||
@@ -1,7 +1,9 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
rack::Plugin *coreInit(); | |||||
using namespace rack; | |||||
Plugin *coreInit(); | |||||
void audioInit(); | void audioInit(); | ||||
void midiInit(); | void midiInit(); | ||||
@@ -10,12 +12,12 @@ void midiInit(); | |||||
// module widgets | // module widgets | ||||
//////////////////// | //////////////////// | ||||
struct AudioInterfaceWidget : rack::ModuleWidget { | |||||
struct AudioInterfaceWidget : ModuleWidget { | |||||
AudioInterfaceWidget(); | AudioInterfaceWidget(); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
struct MidiInterfaceWidget : rack::ModuleWidget { | |||||
struct MidiInterfaceWidget : ModuleWidget { | |||||
MidiInterfaceWidget(); | MidiInterfaceWidget(); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; |
@@ -1,5 +1,5 @@ | |||||
#include <unistd.h> | #include <unistd.h> | ||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
#include <GL/glew.h> | #include <GL/glew.h> | ||||
#include <GLFW/glfw3.h> | #include <GLFW/glfw3.h> | ||||
@@ -4,7 +4,7 @@ | |||||
#include <libgen.h> | #include <libgen.h> | ||||
#endif | #endif | ||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -12,14 +12,16 @@ namespace rack { | |||||
std::string gApplicationName = "Virtuoso Rack"; | std::string gApplicationName = "Virtuoso Rack"; | ||||
std::string gApplicationVersion = "v0.0.0 alpha"; | std::string gApplicationVersion = "v0.0.0 alpha"; | ||||
Rack *gRack; | |||||
} // namespace rack | } // namespace rack | ||||
using namespace rack; | using namespace rack; | ||||
int main() { | int main() { | ||||
// Set working directory | |||||
#if defined(APPLE) | #if defined(APPLE) | ||||
// macOS workaround for setting the working directory to the location of the .app | |||||
{ | { | ||||
CFBundleRef bundle = CFBundleGetMainBundle(); | CFBundleRef bundle = CFBundleGetMainBundle(); | ||||
CFURLRef bundleURL = CFBundleCopyBundleURL(bundle); | CFURLRef bundleURL = CFBundleCopyBundleURL(bundle); | ||||
@@ -32,19 +34,18 @@ int main() { | |||||
} | } | ||||
#endif | #endif | ||||
rackInit(); | |||||
gRack = new Rack(); | |||||
guiInit(); | guiInit(); | ||||
pluginInit(); | pluginInit(); | ||||
gRackWidget->loadPatch("autosave.json"); | gRackWidget->loadPatch("autosave.json"); | ||||
rackStart(); | |||||
gRack->start(); | |||||
guiRun(); | guiRun(); | ||||
rackStop(); | |||||
gRack->stop(); | |||||
gRackWidget->savePatch("autosave.json"); | gRackWidget->savePatch("autosave.json"); | ||||
guiDestroy(); | guiDestroy(); | ||||
rackDestroy(); | |||||
delete gRack; | |||||
pluginDestroy(); | pluginDestroy(); | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -7,7 +7,7 @@ | |||||
#include <glob.h> | #include <glob.h> | ||||
#endif | #endif | ||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
#include "core/core.hpp" | #include "core/core.hpp" | ||||
@@ -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 |
@@ -1,4 +1,6 @@ | |||||
#include "util.hpp" | #include "util.hpp" | ||||
#include <stdio.h> | |||||
#include <stdarg.h> | |||||
namespace rack { | namespace rack { | ||||
@@ -23,5 +25,20 @@ float randomNormal(){ | |||||
return normalDist(rng); | 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 | } // namespace rack |
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -6,7 +6,7 @@ namespace rack { | |||||
ModuleWidget::ModuleWidget(Module *module) { | ModuleWidget::ModuleWidget(Module *module) { | ||||
this->module = module; | this->module = module; | ||||
if (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. | // Make sure WireWidget destructors are called *before* removing `module` from the rack. | ||||
disconnectPorts(); | disconnectPorts(); | ||||
if (module) { | if (module) { | ||||
rackRemoveModule(module); | |||||
gRack->removeModule(module); | |||||
delete module; | delete module; | ||||
} | } | ||||
} | } | ||||
@@ -101,10 +101,9 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
// CPU usage text | // CPU usage text | ||||
if (module) { | if (module) { | ||||
char text[32]; | |||||
snprintf(text, sizeof(text), "%.2f%%", module->cpuTime * 10); | |||||
std::string text = stringf("%.1f%%", module->cpuTime * 100.0); | |||||
nvgSave(vg); | 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); | nvgRestore(vg); | ||||
} | } | ||||
} | } | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -23,7 +23,7 @@ void ParamWidget::onChange() { | |||||
return; | return; | ||||
// moduleWidget->module->params[paramId] = value; | // moduleWidget->module->params[paramId] = value; | ||||
rackSetParamSmooth(module, paramId, value); | |||||
gRack->setParamSmooth(module, paramId, value); | |||||
} | } | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
#include <algorithm> | #include <algorithm> | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
#include <algorithm> | #include <algorithm> | ||||
@@ -1,4 +1,4 @@ | |||||
#include "Rack.hpp" | |||||
#include "rack.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -70,7 +70,7 @@ WireWidget::~WireWidget() { | |||||
void WireWidget::updateWire() { | void WireWidget::updateWire() { | ||||
if (wire) { | if (wire) { | ||||
rackDisconnectWire(wire); | |||||
gRack->removeWire(wire); | |||||
delete wire; | delete wire; | ||||
wire = NULL; | wire = NULL; | ||||
} | } | ||||
@@ -80,7 +80,7 @@ void WireWidget::updateWire() { | |||||
wire->outputId = outputPort->outputId; | wire->outputId = outputPort->outputId; | ||||
wire->inputModule = inputPort->module; | wire->inputModule = inputPort->module; | ||||
wire->inputId = inputPort->inputId; | wire->inputId = inputPort->inputId; | ||||
rackConnectWire(wire); | |||||
gRack->addWire(wire); | |||||
} | } | ||||
} | } | ||||