@@ -7,9 +7,9 @@ | |||||
namespace rack { | namespace rack { | ||||
extern const std::string APP_NAME; | |||||
extern const std::string APP_VERSION; | |||||
extern const std::string API_HOST; | |||||
static const char APP_NAME[] = "VCV Rack"; | |||||
static const char APP_VERSION[] = TOSTRING(VERSION); | |||||
static const char API_HOST[] = "https://api.vcvrack.com"; | |||||
static const float APP_SVG_DPI = 75.0; | static const float APP_SVG_DPI = 75.0; | ||||
static const float MM_PER_IN = 25.4; | static const float MM_PER_IN = 25.4; | ||||
@@ -39,7 +39,5 @@ static const float RACK_GRID_WIDTH = 15; | |||||
static const float RACK_GRID_HEIGHT = 380; | static const float RACK_GRID_HEIGHT = 380; | ||||
static const math::Vec RACK_GRID_SIZE = math::Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); | static const math::Vec RACK_GRID_SIZE = math::Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); | ||||
static const std::string PRESET_FILTERS = "VCV Rack module preset (.vcvm):vcvm"; | |||||
} // namespace rack | } // namespace rack |
@@ -1,17 +0,0 @@ | |||||
#pragma once | |||||
#include "common.hpp" | |||||
#include "engine/Port.hpp" | |||||
namespace rack { | |||||
struct Input : Port { | |||||
/** Returns the value if a cable is plugged in, otherwise returns the given default value */ | |||||
float normalize(float normalVoltage, int channel = 0) { | |||||
return isActive() ? getVoltage(channel) : normalVoltage; | |||||
} | |||||
}; | |||||
} // namespace rack |
@@ -6,17 +6,19 @@ namespace rack { | |||||
struct Light { | struct Light { | ||||
/** The mean-square of the brightness */ | |||||
/** The mean-square of the brightness | |||||
Unstable API. Use set/getBrightness(). | |||||
*/ | |||||
float value = 0.f; | float value = 0.f; | ||||
float getBrightness() { | |||||
return std::sqrt(value); | |||||
} | |||||
void setBrightness(float brightness) { | void setBrightness(float brightness) { | ||||
value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | ||||
} | } | ||||
float getBrightness() { | |||||
return std::sqrt(value); | |||||
} | |||||
/** Emulates slow fall (but immediate rise) of LED brightness. | /** Emulates slow fall (but immediate rise) of LED brightness. | ||||
`frames` rescales the timestep. For example, if your module calls this method every 16 frames, use 16.f. | `frames` rescales the timestep. For example, if your module calls this method every 16 frames, use 16.f. | ||||
*/ | */ | ||||
@@ -2,8 +2,7 @@ | |||||
#include "common.hpp" | #include "common.hpp" | ||||
#include "string.hpp" | #include "string.hpp" | ||||
#include "engine/Param.hpp" | #include "engine/Param.hpp" | ||||
#include "engine/Input.hpp" | |||||
#include "engine/Output.hpp" | |||||
#include "engine/Port.hpp" | |||||
#include "engine/Light.hpp" | #include "engine/Light.hpp" | ||||
#include <vector> | #include <vector> | ||||
#include <jansson.h> | #include <jansson.h> | ||||
@@ -1,13 +0,0 @@ | |||||
#pragma once | |||||
#include "common.hpp" | |||||
#include "engine/Port.hpp" | |||||
namespace rack { | |||||
struct Output : Port { | |||||
}; | |||||
} // namespace rack |
@@ -1,5 +1,6 @@ | |||||
#pragma once | #pragma once | ||||
#include "common.hpp" | #include "common.hpp" | ||||
#include "math.hpp" | |||||
#include <jansson.h> | #include <jansson.h> | ||||
@@ -16,6 +17,7 @@ struct ParamQuantityFactory { | |||||
struct Param { | struct Param { | ||||
/** Unstable API. Use set/getValue() instead. */ | |||||
float value = 0.f; | float value = 0.f; | ||||
float minValue = 0.f; | float minValue = 0.f; | ||||
@@ -72,7 +74,7 @@ struct Param { | |||||
} | } | ||||
void setValue(float value) { | void setValue(float value) { | ||||
this->value = value; | |||||
this->value = math::clamp(value, minValue, maxValue); | |||||
} | } | ||||
bool isBounded(); | bool isBounded(); | ||||
@@ -12,35 +12,49 @@ static const int PORT_MAX_CHANNELS = 16; | |||||
struct Port { | struct Port { | ||||
/** Voltage of the port */ | /** Voltage of the port */ | ||||
union { | union { | ||||
float values[PORT_MAX_CHANNELS] = {}; | |||||
/** DEPRECATED. Use getVoltage() and setVoltage() instead. */ | |||||
/** Unstable API. Use set/getVoltage() instead. */ | |||||
float voltages[PORT_MAX_CHANNELS] = {}; | |||||
/** DEPRECATED. Unstable API. Use getVoltage() and setVoltage() instead. */ | |||||
float value; | float value; | ||||
}; | }; | ||||
/** Number of polyphonic channels | /** Number of polyphonic channels | ||||
Unstable API. Use set/getChannels() instead. | |||||
May be 0 to PORT_MAX_CHANNELS. | May be 0 to PORT_MAX_CHANNELS. | ||||
*/ | */ | ||||
union { | |||||
uint8_t channels = 1; | |||||
/** DEPRECATED. Use isActive() instead. */ | |||||
bool active; | |||||
}; | |||||
uint8_t channels = 1; | |||||
/** Unstable API. Use isConnected() instead. */ | |||||
bool active; | |||||
/** For rendering plug lights on cables | /** For rendering plug lights on cables | ||||
Green for positive, red for negative, and blue for polyphonic | Green for positive, red for negative, and blue for polyphonic | ||||
*/ | */ | ||||
Light plugLights[3]; | Light plugLights[3]; | ||||
void setVoltage(float voltage, int channel = 0) { | |||||
voltages[channel] = voltage; | |||||
} | |||||
float getVoltage(int channel = 0) { | float getVoltage(int channel = 0) { | ||||
return values[channel]; | |||||
return voltages[channel]; | |||||
} | } | ||||
void setVoltage(float voltage, int channel = 0) { | |||||
values[channel] = voltage; | |||||
/** Returns the voltage if a cable is plugged in, otherwise returns the given normal voltage */ | |||||
float getNormalVoltage(float normalVoltage, int channel = 0) { | |||||
return isConnected() ? getVoltage(channel) : normalVoltage; | |||||
} | |||||
/** Returns the voltage if there are enough channels, otherwise returns the first voltage (channel 0) */ | |||||
float getPolyVoltage(int channel) { | |||||
return (channel < channels) ? getVoltage(channel) : getVoltage(0); | |||||
} | |||||
float getNormalPolyVoltage(float normalVoltage, int channel) { | |||||
return isConnected() ? getPolyVoltage(channel) : normalVoltage; | |||||
} | } | ||||
void setChannels(int channels) { | void setChannels(int channels) { | ||||
// Set higher channel values to 0 | |||||
// Set higher channel voltages to 0 | |||||
for (int c = channels; c < this->channels; c++) { | for (int c = channels; c < this->channels; c++) { | ||||
values[c] = 0.f; | |||||
voltages[c] = 0.f; | |||||
} | } | ||||
this->channels = channels; | this->channels = channels; | ||||
} | } | ||||
@@ -49,12 +63,20 @@ struct Port { | |||||
return channels; | return channels; | ||||
} | } | ||||
bool isActive() { | |||||
return channels; | |||||
bool isConnected() { | |||||
return active; | |||||
} | } | ||||
void step(); | void step(); | ||||
DEPRECATED float normalize(float normalVoltage) { | |||||
return getNormalVoltage(normalVoltage); | |||||
} | |||||
}; | }; | ||||
struct Output : Port {}; | |||||
struct Input : Port {}; | |||||
} // namespace rack | } // namespace rack |
@@ -199,6 +199,9 @@ struct Vec { | |||||
float norm() const { | float norm() const { | ||||
return std::hypotf(x, y); | return std::hypotf(x, y); | ||||
} | } | ||||
float square() const { | |||||
return x * x + y * y; | |||||
} | |||||
/** Rotates counterclockwise in radians */ | /** Rotates counterclockwise in radians */ | ||||
Vec rotate(float angle) { | Vec rotate(float angle) { | ||||
float sin = std::sin(angle); | float sin = std::sin(angle); | ||||
@@ -67,10 +67,9 @@ | |||||
#include "app/CableWidget.hpp" | #include "app/CableWidget.hpp" | ||||
#include "engine/Engine.hpp" | #include "engine/Engine.hpp" | ||||
#include "engine/Input.hpp" | |||||
#include "engine/Light.hpp" | |||||
#include "engine/Param.hpp" | |||||
#include "engine/Port.hpp" | |||||
#include "engine/Module.hpp" | #include "engine/Module.hpp" | ||||
#include "engine/Output.hpp" | |||||
#include "engine/Param.hpp" | #include "engine/Param.hpp" | ||||
#include "engine/Cable.hpp" | #include "engine/Cable.hpp" | ||||
@@ -122,7 +122,99 @@ struct AudioInterface : Module { | |||||
onSampleRateChange(); | onSampleRateChange(); | ||||
} | } | ||||
void step() override; | |||||
void step() override { | |||||
// Update SRC states | |||||
int sampleRate = (int) app()->engine->getSampleRate(); | |||||
inputSrc.setRates(audioIO.sampleRate, sampleRate); | |||||
outputSrc.setRates(sampleRate, audioIO.sampleRate); | |||||
inputSrc.setChannels(audioIO.numInputs); | |||||
outputSrc.setChannels(audioIO.numOutputs); | |||||
// Inputs: audio engine -> rack engine | |||||
if (audioIO.active && audioIO.numInputs > 0) { | |||||
// Wait until inputs are present | |||||
// Give up after a timeout in case the audio device is being unresponsive. | |||||
std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||||
auto cond = [&] { | |||||
return (!audioIO.inputBuffer.empty()); | |||||
}; | |||||
auto timeout = std::chrono::milliseconds(200); | |||||
if (audioIO.engineCv.wait_for(lock, timeout, cond)) { | |||||
// Convert inputs | |||||
int inLen = audioIO.inputBuffer.size(); | |||||
int outLen = inputBuffer.capacity(); | |||||
inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); | |||||
audioIO.inputBuffer.startIncr(inLen); | |||||
inputBuffer.endIncr(outLen); | |||||
} | |||||
else { | |||||
// Give up on pulling input | |||||
audioIO.active = false; | |||||
// DEBUG("Audio Interface underflow"); | |||||
} | |||||
} | |||||
// Take input from buffer | |||||
dsp::Frame<AUDIO_INPUTS> inputFrame; | |||||
if (!inputBuffer.empty()) { | |||||
inputFrame = inputBuffer.shift(); | |||||
} | |||||
else { | |||||
std::memset(&inputFrame, 0, sizeof(inputFrame)); | |||||
} | |||||
for (int i = 0; i < audioIO.numInputs; i++) { | |||||
outputs[AUDIO_OUTPUT + i].setVoltage(10.f * inputFrame.samples[i]); | |||||
} | |||||
for (int i = audioIO.numInputs; i < AUDIO_INPUTS; i++) { | |||||
outputs[AUDIO_OUTPUT + i].setVoltage(0.f); | |||||
} | |||||
// Outputs: rack engine -> audio engine | |||||
if (audioIO.active && audioIO.numOutputs > 0) { | |||||
// Get and push output SRC frame | |||||
if (!outputBuffer.full()) { | |||||
dsp::Frame<AUDIO_OUTPUTS> outputFrame; | |||||
for (int i = 0; i < AUDIO_OUTPUTS; i++) { | |||||
outputFrame.samples[i] = inputs[AUDIO_INPUT + i].getVoltage() / 10.f; | |||||
} | |||||
outputBuffer.push(outputFrame); | |||||
} | |||||
if (outputBuffer.full()) { | |||||
// Wait until enough outputs are consumed | |||||
// Give up after a timeout in case the audio device is being unresponsive. | |||||
std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||||
auto cond = [&] { | |||||
return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); | |||||
}; | |||||
auto timeout = std::chrono::milliseconds(200); | |||||
if (audioIO.engineCv.wait_for(lock, timeout, cond)) { | |||||
// Push converted output | |||||
int inLen = outputBuffer.size(); | |||||
int outLen = audioIO.outputBuffer.capacity(); | |||||
outputSrc.process(outputBuffer.startData(), &inLen, audioIO.outputBuffer.endData(), &outLen); | |||||
outputBuffer.startIncr(inLen); | |||||
audioIO.outputBuffer.endIncr(outLen); | |||||
} | |||||
else { | |||||
// Give up on pushing output | |||||
audioIO.active = false; | |||||
outputBuffer.clear(); | |||||
// DEBUG("Audio Interface underflow"); | |||||
} | |||||
} | |||||
// Notify audio thread that an output is potentially ready | |||||
audioIO.audioCv.notify_one(); | |||||
} | |||||
// Turn on light if at least one port is enabled in the nearby pair | |||||
for (int i = 0; i < AUDIO_INPUTS / 2; i++) | |||||
lights[INPUT_LIGHT + i].setBrightness(audioIO.active && audioIO.numOutputs >= 2*i+1); | |||||
for (int i = 0; i < AUDIO_OUTPUTS / 2; i++) | |||||
lights[OUTPUT_LIGHT + i].setBrightness(audioIO.active && audioIO.numInputs >= 2*i+1); | |||||
} | |||||
json_t *dataToJson() override { | json_t *dataToJson() override { | ||||
json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
@@ -141,101 +233,6 @@ struct AudioInterface : Module { | |||||
}; | }; | ||||
void AudioInterface::step() { | |||||
// Update SRC states | |||||
int sampleRate = (int) app()->engine->getSampleRate(); | |||||
inputSrc.setRates(audioIO.sampleRate, sampleRate); | |||||
outputSrc.setRates(sampleRate, audioIO.sampleRate); | |||||
inputSrc.setChannels(audioIO.numInputs); | |||||
outputSrc.setChannels(audioIO.numOutputs); | |||||
// Inputs: audio engine -> rack engine | |||||
if (audioIO.active && audioIO.numInputs > 0) { | |||||
// Wait until inputs are present | |||||
// Give up after a timeout in case the audio device is being unresponsive. | |||||
std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||||
auto cond = [&] { | |||||
return (!audioIO.inputBuffer.empty()); | |||||
}; | |||||
auto timeout = std::chrono::milliseconds(200); | |||||
if (audioIO.engineCv.wait_for(lock, timeout, cond)) { | |||||
// Convert inputs | |||||
int inLen = audioIO.inputBuffer.size(); | |||||
int outLen = inputBuffer.capacity(); | |||||
inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); | |||||
audioIO.inputBuffer.startIncr(inLen); | |||||
inputBuffer.endIncr(outLen); | |||||
} | |||||
else { | |||||
// Give up on pulling input | |||||
audioIO.active = false; | |||||
// DEBUG("Audio Interface underflow"); | |||||
} | |||||
} | |||||
// Take input from buffer | |||||
dsp::Frame<AUDIO_INPUTS> inputFrame; | |||||
if (!inputBuffer.empty()) { | |||||
inputFrame = inputBuffer.shift(); | |||||
} | |||||
else { | |||||
std::memset(&inputFrame, 0, sizeof(inputFrame)); | |||||
} | |||||
for (int i = 0; i < audioIO.numInputs; i++) { | |||||
outputs[AUDIO_OUTPUT + i].setVoltage(10.f * inputFrame.samples[i]); | |||||
} | |||||
for (int i = audioIO.numInputs; i < AUDIO_INPUTS; i++) { | |||||
outputs[AUDIO_OUTPUT + i].setVoltage(0.f); | |||||
} | |||||
// Outputs: rack engine -> audio engine | |||||
if (audioIO.active && audioIO.numOutputs > 0) { | |||||
// Get and push output SRC frame | |||||
if (!outputBuffer.full()) { | |||||
dsp::Frame<AUDIO_OUTPUTS> outputFrame; | |||||
for (int i = 0; i < AUDIO_OUTPUTS; i++) { | |||||
outputFrame.samples[i] = inputs[AUDIO_INPUT + i].getVoltage() / 10.f; | |||||
} | |||||
outputBuffer.push(outputFrame); | |||||
} | |||||
if (outputBuffer.full()) { | |||||
// Wait until enough outputs are consumed | |||||
// Give up after a timeout in case the audio device is being unresponsive. | |||||
std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||||
auto cond = [&] { | |||||
return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); | |||||
}; | |||||
auto timeout = std::chrono::milliseconds(200); | |||||
if (audioIO.engineCv.wait_for(lock, timeout, cond)) { | |||||
// Push converted output | |||||
int inLen = outputBuffer.size(); | |||||
int outLen = audioIO.outputBuffer.capacity(); | |||||
outputSrc.process(outputBuffer.startData(), &inLen, audioIO.outputBuffer.endData(), &outLen); | |||||
outputBuffer.startIncr(inLen); | |||||
audioIO.outputBuffer.endIncr(outLen); | |||||
} | |||||
else { | |||||
// Give up on pushing output | |||||
audioIO.active = false; | |||||
outputBuffer.clear(); | |||||
// DEBUG("Audio Interface underflow"); | |||||
} | |||||
} | |||||
// Notify audio thread that an output is potentially ready | |||||
audioIO.audioCv.notify_one(); | |||||
} | |||||
// Turn on light if at least one port is enabled in the nearby pair | |||||
for (int i = 0; i < AUDIO_INPUTS / 2; i++) | |||||
lights[INPUT_LIGHT + i].setBrightness(audioIO.active && audioIO.numOutputs >= 2*i+1); | |||||
for (int i = 0; i < AUDIO_OUTPUTS / 2; i++) | |||||
lights[OUTPUT_LIGHT + i].setBrightness(audioIO.active && audioIO.numInputs >= 2*i+1); | |||||
} | |||||
struct AudioInterfaceWidget : ModuleWidget { | struct AudioInterfaceWidget : ModuleWidget { | ||||
AudioInterfaceWidget(AudioInterface *module) { | AudioInterfaceWidget(AudioInterface *module) { | ||||
setModule(module); | setModule(module); | ||||
@@ -252,7 +252,7 @@ struct CV_MIDI : Module { | |||||
} | } | ||||
for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); c++) { | for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); c++) { | ||||
int vel = (int) std::round(inputs[VEL_INPUT].normalize(10.f * 100 / 127, c) / 10.f * 127); | |||||
int vel = (int) std::round(inputs[VEL_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127); | |||||
vel = clamp(vel, 0, 127); | vel = clamp(vel, 0, 127); | ||||
midiOutput.setVelocity(vel, c); | midiOutput.setVelocity(vel, c); | ||||
@@ -260,12 +260,12 @@ struct CV_MIDI : Module { | |||||
note = clamp(note, 0, 127); | note = clamp(note, 0, 127); | ||||
midiOutput.setNote(note, c); | midiOutput.setNote(note, c); | ||||
bool gate = inputs[GATE_INPUT].getVoltage(c) >= 1.f; | |||||
bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f; | |||||
midiOutput.setGate(gate, c); | midiOutput.setGate(gate, c); | ||||
midiOutput.stepChannel(c); | midiOutput.stepChannel(c); | ||||
int aft = (int) std::round(inputs[AFT_INPUT].getVoltage(c) / 10.f * 127); | |||||
int aft = (int) std::round(inputs[AFT_INPUT].getPolyVoltage(c) / 10.f * 127); | |||||
aft = clamp(aft, 0, 127); | aft = clamp(aft, 0, 127); | ||||
midiOutput.setAftertouch(aft, c); | midiOutput.setAftertouch(aft, c); | ||||
} | } | ||||
@@ -278,7 +278,7 @@ struct CV_MIDI : Module { | |||||
mw = clamp(mw, 0, 127); | mw = clamp(mw, 0, 127); | ||||
midiOutput.setModWheel(mw); | midiOutput.setModWheel(mw); | ||||
int vol = (int) std::round(inputs[VOL_INPUT].normalize(10.f) / 10.f * 127); | |||||
int vol = (int) std::round(inputs[VOL_INPUT].getNormalVoltage(10.f) / 10.f * 127); | |||||
vol = clamp(vol, 0, 127); | vol = clamp(vol, 0, 127); | ||||
midiOutput.setVolume(vol); | midiOutput.setVolume(vol); | ||||
@@ -286,16 +286,16 @@ struct CV_MIDI : Module { | |||||
pan = clamp(pan, 0, 127); | pan = clamp(pan, 0, 127); | ||||
midiOutput.setPan(pan); | midiOutput.setPan(pan); | ||||
bool clk = inputs[CLK_INPUT].value >= 1.f; | |||||
bool clk = inputs[CLK_INPUT].getVoltage() >= 1.f; | |||||
midiOutput.setClock(clk); | midiOutput.setClock(clk); | ||||
bool start = inputs[START_INPUT].value >= 1.f; | |||||
bool start = inputs[START_INPUT].getVoltage() >= 1.f; | |||||
midiOutput.setStart(start); | midiOutput.setStart(start); | ||||
bool stop = inputs[STOP_INPUT].value >= 1.f; | |||||
bool stop = inputs[STOP_INPUT].getVoltage() >= 1.f; | |||||
midiOutput.setStop(stop); | midiOutput.setStop(stop); | ||||
bool cont = inputs[CONTINUE_INPUT].value >= 1.f; | |||||
bool cont = inputs[CONTINUE_INPUT].getVoltage() >= 1.f; | |||||
midiOutput.setContinue(cont); | midiOutput.setContinue(cont); | ||||
} | } | ||||
@@ -47,7 +47,7 @@ struct MIDI_CC : Module { | |||||
float lambda = app()->engine->getSampleTime() * 100.f; | float lambda = app()->engine->getSampleTime() * 100.f; | ||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
if (!outputs[CC_OUTPUT + i].isActive()) | |||||
if (!outputs[CC_OUTPUT + i].isConnected()) | |||||
continue; | continue; | ||||
int cc = learnedCcs[i]; | int cc = learnedCcs[i]; | ||||
@@ -221,16 +221,16 @@ void CableWidget::draw(NVGcontext *vg) { | |||||
} | } | ||||
float thickness = 5; | float thickness = 5; | ||||
if (cable && cable->outputModule) { | |||||
if (cable->outputModule) { | |||||
Output *output = &cable->outputModule->outputs[cable->outputId]; | Output *output = &cable->outputModule->outputs[cable->outputId]; | ||||
if (output->channels > 1) { | if (output->channels > 1) { | ||||
// Increase thickness if output port is polyphonic | // Increase thickness if output port is polyphonic | ||||
thickness = 7; | thickness = 7; | ||||
} | } | ||||
else if (output->channels == 0) { | |||||
// Draw translucent cable if not active (i.e. 0 channels) | |||||
nvgGlobalAlpha(vg, 0.5); | |||||
} | |||||
// else if (output->channels == 0) { | |||||
// // Draw translucent cable if not active (i.e. 0 channels) | |||||
// opacity *= 0.5; | |||||
// } | |||||
} | } | ||||
math::Vec outputPos = getOutputPos(); | math::Vec outputPos = getOutputPos(); | ||||
@@ -15,6 +15,9 @@ | |||||
namespace rack { | namespace rack { | ||||
static const char PRESET_FILTERS[] = "VCV Rack module preset (.vcvm):vcvm"; | |||||
struct ModuleDisconnectItem : MenuItem { | struct ModuleDisconnectItem : MenuItem { | ||||
ModuleWidget *moduleWidget; | ModuleWidget *moduleWidget; | ||||
ModuleDisconnectItem() { | ModuleDisconnectItem() { | ||||
@@ -499,7 +502,7 @@ void ModuleWidget::loadDialog() { | |||||
std::string dir = asset::user("presets"); | std::string dir = asset::user("presets"); | ||||
system::createDirectory(dir); | system::createDirectory(dir); | ||||
osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str()); | |||||
osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS); | |||||
DEFER({ | DEFER({ | ||||
osdialog_filters_free(filters); | osdialog_filters_free(filters); | ||||
}); | }); | ||||
@@ -520,7 +523,7 @@ void ModuleWidget::saveDialog() { | |||||
std::string dir = asset::user("presets"); | std::string dir = asset::user("presets"); | ||||
system::createDirectory(dir); | system::createDirectory(dir); | ||||
osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str()); | |||||
osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS); | |||||
DEFER({ | DEFER({ | ||||
osdialog_filters_free(filters); | osdialog_filters_free(filters); | ||||
}); | }); | ||||
@@ -25,16 +25,14 @@ float ParamQuantity::getSmoothValue() { | |||||
void ParamQuantity::setValue(float value) { | void ParamQuantity::setValue(float value) { | ||||
if (!module) | if (!module) | ||||
return; | return; | ||||
value = math::clamp(value, getMinValue(), getMaxValue()); | |||||
// TODO Smooth | |||||
// TODO Snap | |||||
getParam()->value = value; | |||||
// This setter clamps the value | |||||
getParam()->setValue(value); | |||||
} | } | ||||
float ParamQuantity::getValue() { | float ParamQuantity::getValue() { | ||||
if (!module) | if (!module) | ||||
return 0.f; | return 0.f; | ||||
return getParam()->value; | |||||
return getParam()->getValue(); | |||||
} | } | ||||
float ParamQuantity::getMinValue() { | float ParamQuantity::getMinValue() { | ||||
@@ -13,6 +13,7 @@ struct PlugLight : MultiLightWidget { | |||||
PlugLight() { | PlugLight() { | ||||
addBaseColor(color::GREEN); | addBaseColor(color::GREEN); | ||||
addBaseColor(color::RED); | addBaseColor(color::RED); | ||||
addBaseColor(color::BLUE); | |||||
box.size = math::Vec(8, 8); | box.size = math::Vec(8, 8); | ||||
bgColor = color::BLACK_TRANSPARENT; | bgColor = color::BLACK_TRANSPARENT; | ||||
} | } | ||||
@@ -35,14 +36,17 @@ void PortWidget::step() { | |||||
if (!module) | if (!module) | ||||
return; | return; | ||||
std::vector<float> values(2); | |||||
std::vector<float> values(3); | |||||
// Input and Outputs are not virtual, so we can't cast to Port to make this less redundant. | |||||
if (type == OUTPUT) { | if (type == OUTPUT) { | ||||
values[0] = module->outputs[portId].plugLights[0].getBrightness(); | values[0] = module->outputs[portId].plugLights[0].getBrightness(); | ||||
values[1] = module->outputs[portId].plugLights[1].getBrightness(); | values[1] = module->outputs[portId].plugLights[1].getBrightness(); | ||||
values[2] = module->outputs[portId].plugLights[2].getBrightness(); | |||||
} | } | ||||
else { | else { | ||||
values[0] = module->inputs[portId].plugLights[0].getBrightness(); | values[0] = module->inputs[portId].plugLights[0].getBrightness(); | ||||
values[1] = module->inputs[portId].plugLights[1].getBrightness(); | values[1] = module->inputs[portId].plugLights[1].getBrightness(); | ||||
values[2] = module->inputs[portId].plugLights[2].getBrightness(); | |||||
} | } | ||||
plugLight->setValues(values); | plugLight->setValues(values); | ||||
} | } | ||||
@@ -75,7 +75,7 @@ void Scene::step() { | |||||
// Version popup message | // Version popup message | ||||
if (!latestVersion.empty()) { | if (!latestVersion.empty()) { | ||||
std::string versionMessage = string::f("Rack %s is available.\n\nYou have Rack %s.\n\nClose Rack and download new version on the website?", latestVersion.c_str(), APP_VERSION.c_str()); | |||||
std::string versionMessage = string::f("Rack %s is available.\n\nYou have Rack %s.\n\nClose Rack and download new version on the website?", latestVersion.c_str(), APP_VERSION); | |||||
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, versionMessage.c_str())) { | if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, versionMessage.c_str())) { | ||||
std::thread t(system::openBrowser, "https://vcvrack.com/"); | std::thread t(system::openBrowser, "https://vcvrack.com/"); | ||||
t.detach(); | t.detach(); | ||||
@@ -164,17 +164,19 @@ void Scene::onPathDrop(const event::PathDrop &e) { | |||||
} | } | ||||
void Scene::runCheckVersion() { | void Scene::runCheckVersion() { | ||||
json_t *resJ = network::requestJson(network::METHOD_GET, API_HOST + "/version", NULL); | |||||
std::string versionUrl = API_HOST; | |||||
versionUrl += "/version"; | |||||
json_t *versionResJ = network::requestJson(network::METHOD_GET, versionUrl, NULL); | |||||
if (resJ) { | |||||
json_t *versionJ = json_object_get(resJ, "version"); | |||||
if (versionResJ) { | |||||
json_t *versionJ = json_object_get(versionResJ, "version"); | |||||
if (versionJ) { | if (versionJ) { | ||||
std::string version = json_string_value(versionJ); | std::string version = json_string_value(versionJ); | ||||
if (version != APP_VERSION) { | if (version != APP_VERSION) { | ||||
latestVersion = version; | latestVersion = version; | ||||
} | } | ||||
} | } | ||||
json_decref(resJ); | |||||
json_decref(versionResJ); | |||||
} | } | ||||
} | } | ||||
@@ -1,12 +0,0 @@ | |||||
#include "app/common.hpp" | |||||
namespace rack { | |||||
const std::string APP_NAME = "VCV Rack"; | |||||
const std::string APP_VERSION = TOSTRING(VERSION); | |||||
const std::string API_HOST = "https://api.vcvrack.com"; | |||||
} // namespace rack |
@@ -9,9 +9,9 @@ void Cable::step() { | |||||
Input *input = &inputModule->inputs[inputId]; | Input *input = &inputModule->inputs[inputId]; | ||||
// Match number of polyphonic channels to output port | // Match number of polyphonic channels to output port | ||||
input->channels = output->channels; | input->channels = output->channels; | ||||
// Copy values from output to input | |||||
// Copy voltages from output to input | |||||
for (int i = 0; i < output->channels; i++) { | for (int i = 0; i < output->channels; i++) { | ||||
input->values[i] = output->values[i]; | |||||
input->voltages[i] = output->voltages[i]; | |||||
} | } | ||||
} | } | ||||
@@ -100,12 +100,11 @@ static void Engine_step(Engine *engine) { | |||||
Param *param = &smoothModule->params[smoothParamId]; | Param *param = &smoothModule->params[smoothParamId]; | ||||
float value = param->value; | float value = param->value; | ||||
// decay rate is 1 graphics frame | // decay rate is 1 graphics frame | ||||
const float lambda = 60.f; | |||||
float delta = smoothValue - value; | |||||
float newValue = value + delta * lambda * engine->internal->sampleTime; | |||||
if (value == newValue) { | |||||
// Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats) | |||||
param->value = smoothValue; | |||||
const float smoothLambda = 60.f; | |||||
float newValue = value + (smoothValue - value) * smoothLambda * engine->internal->sampleTime; | |||||
if (value == newValue || !(param->minValue <= newValue && newValue <= param->maxValue)) { | |||||
// Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats), or if newValue is out of bounds | |||||
param->setValue(smoothValue); | |||||
engine->internal->smoothModule = NULL; | engine->internal->smoothModule = NULL; | ||||
} | } | ||||
else { | else { | ||||
@@ -121,11 +120,19 @@ static void Engine_step(Engine *engine) { | |||||
if (module->bypass) { | if (module->bypass) { | ||||
// Bypass module | // Bypass module | ||||
for (Output &output : module->outputs) { | for (Output &output : module->outputs) { | ||||
// This also zeros all voltages | |||||
output.setChannels(0); | output.setChannels(0); | ||||
} | } | ||||
module->cpuTime = 0.f; | |||||
if (settings::powerMeter) { | |||||
module->cpuTime = 0.f; | |||||
} | |||||
} | } | ||||
else { | else { | ||||
// Set all outputs to 1 channel so that modules are forced to specify 0 or >2 channels every frame | |||||
for (Output &output : module->outputs) { | |||||
// Don't use Port::setChannels() so we maintain all previous voltages | |||||
output.channels = 1; | |||||
} | |||||
// Step module | // Step module | ||||
if (settings::powerMeter) { | if (settings::powerMeter) { | ||||
auto startTime = std::chrono::high_resolution_clock::now(); | auto startTime = std::chrono::high_resolution_clock::now(); | ||||
@@ -266,6 +273,23 @@ void Engine::randomizeModule(Module *module) { | |||||
module->randomize(); | module->randomize(); | ||||
} | } | ||||
static void Engine_updateConnected(Engine *engine) { | |||||
// Set everything to unconnected | |||||
for (Module *module : engine->modules) { | |||||
for (Input &input : module->inputs) { | |||||
input.active = false; | |||||
} | |||||
for (Output &output : module->outputs) { | |||||
output.active = false; | |||||
} | |||||
} | |||||
// Set inputs/outputs to active | |||||
for (Cable *cable : engine->cables) { | |||||
cable->outputModule->outputs[cable->outputId].active = true; | |||||
cable->inputModule->inputs[cable->inputId].active = true; | |||||
} | |||||
} | |||||
void Engine::addCable(Cable *cable) { | void Engine::addCable(Cable *cable) { | ||||
assert(cable); | assert(cable); | ||||
VIPLock vipLock(internal->vipMutex); | VIPLock vipLock(internal->vipMutex); | ||||
@@ -293,6 +317,7 @@ void Engine::addCable(Cable *cable) { | |||||
} | } | ||||
// Add the cable | // Add the cable | ||||
cables.push_back(cable); | cables.push_back(cable); | ||||
Engine_updateConnected(this); | |||||
} | } | ||||
void Engine::removeCable(Cable *cable) { | void Engine::removeCable(Cable *cable) { | ||||
@@ -307,6 +332,7 @@ void Engine::removeCable(Cable *cable) { | |||||
input.setChannels(0); | input.setChannels(0); | ||||
// Remove the cable | // Remove the cable | ||||
cables.erase(it); | cables.erase(it); | ||||
Engine_updateConnected(this); | |||||
// Remove ID | // Remove ID | ||||
cable->id = 0; | cable->id = 0; | ||||
} | } | ||||
@@ -5,6 +5,7 @@ namespace rack { | |||||
void Port::step() { | void Port::step() { | ||||
// Set plug lights | |||||
if (channels == 0) { | if (channels == 0) { | ||||
plugLights[0].setBrightness(0.f); | plugLights[0].setBrightness(0.f); | ||||
plugLights[1].setBrightness(0.f); | plugLights[1].setBrightness(0.f); | ||||
@@ -28,7 +28,7 @@ int main(int argc, char *argv[]) { | |||||
#ifdef ARCH_WIN | #ifdef ARCH_WIN | ||||
// Windows global mutex to prevent multiple instances | // Windows global mutex to prevent multiple instances | ||||
// Handle will be closed by Windows when the process ends | // Handle will be closed by Windows when the process ends | ||||
HANDLE instanceMutex = CreateMutex(NULL, true, APP_NAME.c_str()); | |||||
HANDLE instanceMutex = CreateMutex(NULL, true, APP_NAME); | |||||
if (GetLastError() == ERROR_ALREADY_EXISTS) { | if (GetLastError() == ERROR_ALREADY_EXISTS) { | ||||
osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported."); | osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported."); | ||||
exit(1); | exit(1); | ||||
@@ -65,7 +65,7 @@ int main(int argc, char *argv[]) { | |||||
logger::init(devMode); | logger::init(devMode); | ||||
// Log environment | // Log environment | ||||
INFO("%s %s", APP_NAME.c_str(), APP_VERSION.c_str()); | |||||
INFO("%s %s", APP_NAME, APP_VERSION); | |||||
if (devMode) | if (devMode) | ||||
INFO("Development mode"); | INFO("Development mode"); | ||||
INFO("System directory: %s", asset::systemDir.c_str()); | INFO("System directory: %s", asset::systemDir.c_str()); | ||||
@@ -11,7 +11,7 @@ | |||||
namespace rack { | namespace rack { | ||||
static const std::string PATCH_FILTERS = "VCV Rack patch (.vcv):vcv"; | |||||
static const char PATCH_FILTERS[] = "VCV Rack patch (.vcv):vcv"; | |||||
void PatchManager::reset() { | void PatchManager::reset() { | ||||
@@ -72,7 +72,7 @@ void PatchManager::saveAsDialog() { | |||||
filename = string::filename(path); | filename = string::filename(path); | ||||
} | } | ||||
osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); | |||||
osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS); | |||||
DEFER({ | DEFER({ | ||||
osdialog_filters_free(filters); | osdialog_filters_free(filters); | ||||
}); | }); | ||||
@@ -140,7 +140,7 @@ void PatchManager::loadDialog() { | |||||
dir = string::directory(path); | dir = string::directory(path); | ||||
} | } | ||||
osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); | |||||
osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS); | |||||
DEFER({ | DEFER({ | ||||
osdialog_filters_free(filters); | osdialog_filters_free(filters); | ||||
}); | }); | ||||
@@ -179,7 +179,7 @@ json_t *PatchManager::toJson() { | |||||
json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
// version | // version | ||||
json_t *versionJ = json_string(APP_VERSION.c_str()); | |||||
json_t *versionJ = json_string(APP_VERSION); | |||||
json_object_set_new(rootJ, "version", versionJ); | json_object_set_new(rootJ, "version", versionJ); | ||||
// Merge with RackWidget JSON | // Merge with RackWidget JSON | ||||
@@ -200,7 +200,7 @@ void PatchManager::fromJson(json_t *rootJ) { | |||||
if (versionJ) | if (versionJ) | ||||
version = json_string_value(versionJ); | version = json_string_value(versionJ); | ||||
if (version != APP_VERSION) { | if (version != APP_VERSION) { | ||||
INFO("Patch made with Rack version %s, current Rack version is %s", version.c_str(), APP_VERSION.c_str()); | |||||
INFO("Patch made with Rack version %s, current Rack version is %s", version.c_str(), APP_VERSION); | |||||
} | } | ||||
// Detect old patches with ModuleWidget::params/inputs/outputs indices. | // Detect old patches with ModuleWidget::params/inputs/outputs indices. | ||||
@@ -378,7 +378,9 @@ void logIn(std::string email, std::string password) { | |||||
json_t *reqJ = json_object(); | json_t *reqJ = json_object(); | ||||
json_object_set(reqJ, "email", json_string(email.c_str())); | json_object_set(reqJ, "email", json_string(email.c_str())); | ||||
json_object_set(reqJ, "password", json_string(password.c_str())); | json_object_set(reqJ, "password", json_string(password.c_str())); | ||||
json_t *resJ = network::requestJson(network::METHOD_POST, API_HOST + "/token", reqJ); | |||||
std::string tokenUrl = API_HOST; | |||||
tokenUrl += "/token"; | |||||
json_t *resJ = network::requestJson(network::METHOD_POST, tokenUrl, reqJ); | |||||
json_decref(reqJ); | json_decref(reqJ); | ||||
if (resJ) { | if (resJ) { | ||||
@@ -421,7 +423,9 @@ bool sync(bool dryRun) { | |||||
// Get user's plugins list | // Get user's plugins list | ||||
json_t *pluginsReqJ = json_object(); | json_t *pluginsReqJ = json_object(); | ||||
json_object_set(pluginsReqJ, "token", json_string(token.c_str())); | json_object_set(pluginsReqJ, "token", json_string(token.c_str())); | ||||
json_t *pluginsResJ = network::requestJson(network::METHOD_GET, API_HOST + "/plugins", pluginsReqJ); | |||||
std::string pluginsUrl = API_HOST; | |||||
pluginsUrl += "/plugins"; | |||||
json_t *pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, pluginsReqJ); | |||||
json_decref(pluginsReqJ); | json_decref(pluginsReqJ); | ||||
if (!pluginsResJ) { | if (!pluginsResJ) { | ||||
WARN("Request for user's plugins failed"); | WARN("Request for user's plugins failed"); | ||||
@@ -438,7 +442,9 @@ bool sync(bool dryRun) { | |||||
} | } | ||||
// Get community manifests | // Get community manifests | ||||
json_t *manifestsResJ = network::requestJson(network::METHOD_GET, API_HOST + "/community/manifests", NULL); | |||||
std::string manifestsUrl = API_HOST; | |||||
manifestsUrl += "/community/manifests"; | |||||
json_t *manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, NULL); | |||||
if (!manifestsResJ) { | if (!manifestsResJ) { | ||||
WARN("Request for community manifests failed"); | WARN("Request for community manifests failed"); | ||||
return false; | return false; | ||||