| @@ -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); | |||||
| } | } | ||||
| } | } | ||||