| @@ -1,3 +1,6 @@ | |||
| [submodule "ext/nanovg"] | |||
| path = ext/nanovg | |||
| url = https://github.com/memononen/nanovg.git | |||
| [submodule "ext/nanosvg"] | |||
| path = ext/nanosvg | |||
| url = https://github.com/memononen/nanosvg.git | |||
| @@ -0,0 +1 @@ | |||
| Subproject commit dc12d90586a8ab99da0c575aafff999666aa5d55 | |||
| @@ -166,18 +166,18 @@ ParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, f | |||
| return param; | |||
| } | |||
| inline | |||
| template <class TInputPort> | |||
| InputPort *createInput(Vec pos, Module *module, int inputId) { | |||
| InputPort *port = new InputPort(); | |||
| InputPort *port = new TInputPort(); | |||
| port->box.pos = pos; | |||
| port->module = module; | |||
| port->inputId = inputId; | |||
| return port; | |||
| } | |||
| inline | |||
| template <class TOutputPort> | |||
| OutputPort *createOutput(Vec pos, Module *module, int outputId) { | |||
| OutputPort *port = new OutputPort(); | |||
| OutputPort *port = new TOutputPort(); | |||
| port->box.pos = pos; | |||
| port->module = module; | |||
| port->outputId = outputId; | |||
| @@ -94,7 +94,6 @@ struct RackWidget : OpaqueWidget { | |||
| struct ModulePanel : TransparentWidget { | |||
| NVGcolor backgroundColor; | |||
| NVGcolor highlightColor; | |||
| std::string imageFilename; | |||
| void draw(NVGcontext *vg); | |||
| }; | |||
| @@ -167,7 +166,7 @@ struct MomentarySwitch : virtual Switch { | |||
| // ports | |||
| //////////////////// | |||
| struct Port : OpaqueWidget, SpriteWidget { | |||
| struct Port : OpaqueWidget { | |||
| Module *module = NULL; | |||
| WireWidget *connectedWire = NULL; | |||
| @@ -175,8 +174,6 @@ struct Port : OpaqueWidget, SpriteWidget { | |||
| ~Port(); | |||
| void disconnect(); | |||
| int type; | |||
| void drawGlow(NVGcontext *vg); | |||
| void onMouseDown(int button); | |||
| void onDragEnd(); | |||
| }; | |||
| @@ -184,7 +181,6 @@ struct Port : OpaqueWidget, SpriteWidget { | |||
| struct InputPort : Port { | |||
| int inputId; | |||
| void draw(NVGcontext *vg); | |||
| void onDragStart(); | |||
| void onDragDrop(Widget *origin); | |||
| }; | |||
| @@ -192,7 +188,6 @@ struct InputPort : Port { | |||
| struct OutputPort : Port { | |||
| int outputId; | |||
| void draw(NVGcontext *vg); | |||
| void onDragStart(); | |||
| void onDragDrop(Widget *origin); | |||
| }; | |||
| @@ -220,5 +215,41 @@ struct Scene : OpaqueWidget { | |||
| void step(); | |||
| }; | |||
| //////////////////// | |||
| // component library | |||
| //////////////////// | |||
| struct PJ301M : SpriteWidget { | |||
| PJ301M() { | |||
| box.size = Vec(24, 24); | |||
| spriteOffset = Vec(-10, -10); | |||
| spriteSize = Vec(48, 48); | |||
| spriteFilename = "res/ComponentLibrary/PJ301M.png"; | |||
| } | |||
| }; | |||
| struct InputPortPJ301M : InputPort, PJ301M {}; | |||
| struct OutputPortPJ301M: OutputPort, PJ301M {}; | |||
| struct PJ3410 : SpriteWidget { | |||
| PJ3410() { | |||
| box.size = Vec(31, 31); | |||
| spriteOffset = Vec(-9, -9); | |||
| spriteSize = Vec(54, 54); | |||
| spriteFilename = "res/ComponentLibrary/PJ3410.png"; | |||
| } | |||
| }; | |||
| struct InputPortPJ3410 : InputPort, PJ3410 {}; | |||
| struct OutputPortPJ3410: OutputPort, PJ3410 {}; | |||
| struct CL1362 : SpriteWidget { | |||
| CL1362() { | |||
| box.size = Vec(33, 29); | |||
| spriteOffset = Vec(-10, -10); | |||
| spriteSize = Vec(57, 54); | |||
| spriteFilename = "res/ComponentLibrary/CL1362.png"; | |||
| } | |||
| }; | |||
| struct InputPortCL1362 : InputPort, CL1362 {}; | |||
| struct OutputPortCL1362 : OutputPort, CL1362 {}; | |||
| } // namespace rack | |||
| @@ -45,10 +45,11 @@ struct AudioInterface : Module { | |||
| SampleRateConverter<2> inputSrc; | |||
| SampleRateConverter<2> outputSrc; | |||
| // in device's sample rate | |||
| DoubleRingBuffer<Frame<2>, (1<<15)> inputBuffer; | |||
| // in rack's sample rate | |||
| DoubleRingBuffer<Frame<2>, 32> inputBuffer; | |||
| DoubleRingBuffer<Frame<2>, (1<<15)> outputBuffer; | |||
| // in device's sample rate | |||
| DoubleRingBuffer<Frame<2>, (1<<15)> inputSrcBuffer; | |||
| AudioInterface(); | |||
| ~AudioInterface(); | |||
| @@ -89,29 +90,35 @@ void AudioInterface::step() { | |||
| // Get input and pass it through the sample rate converter | |||
| if (numOutputs > 0) { | |||
| Frame<2> f; | |||
| f.samples[0] = getf(inputs[AUDIO1_INPUT]) / 5.0; | |||
| f.samples[1] = getf(inputs[AUDIO2_INPUT]) / 5.0; | |||
| inputSrc.setRatio(sampleRate / gRack->sampleRate); | |||
| int inLen = 1; | |||
| int outLen = inputBuffer.capacity(); | |||
| inputSrc.process((const float*) &f, &inLen, (float*) inputBuffer.endData(), &outLen); | |||
| inputBuffer.endIncr(outLen); | |||
| if (!inputBuffer.full()) { | |||
| Frame<2> f; | |||
| f.samples[0] = getf(inputs[AUDIO1_INPUT]) / 5.0; | |||
| f.samples[1] = getf(inputs[AUDIO2_INPUT]) / 5.0; | |||
| inputBuffer.push(f); | |||
| } | |||
| // Once full, sample rate convert the input | |||
| if (inputBuffer.full()) { | |||
| inputSrc.setRatio(sampleRate / gRack->sampleRate); | |||
| int inLen = inputBuffer.size(); | |||
| int outLen = inputSrcBuffer.capacity(); | |||
| inputSrc.process((const float*) inputBuffer.startData(), &inLen, (float*) inputSrcBuffer.endData(), &outLen); | |||
| inputBuffer.startIncr(inLen); | |||
| inputSrcBuffer.endIncr(outLen); | |||
| } | |||
| } | |||
| // Read/write stream if we have enough input | |||
| bool streamReady = (numOutputs > 0) ? (inputBuffer.size() >= blockSize) : (outputBuffer.empty()); | |||
| // Read/write stream if we have enough input, OR the output buffer is empty if we have no input | |||
| bool streamReady = (numOutputs > 0) ? (inputSrcBuffer.size() >= blockSize) : (outputBuffer.empty()); | |||
| if (streamReady) { | |||
| // printf("%p\t%d\t%d\n", this, inputSrcBuffer.size(), outputBuffer.size()); | |||
| PaError err; | |||
| // Read output from input stream | |||
| // (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.) | |||
| if (numInputs > 0) { | |||
| Frame<2> *buf = new Frame<2>[blockSize]; | |||
| printf("read %d\n", blockSize); | |||
| err = Pa_ReadStream(stream, (float*) buf, blockSize); | |||
| printf("read done\n"); | |||
| if (err) { | |||
| // Ignore buffer underflows | |||
| if (err != paInputOverflowed) { | |||
| @@ -131,11 +138,9 @@ void AudioInterface::step() { | |||
| // Write input to output stream | |||
| if (numOutputs > 0) { | |||
| assert(inputBuffer.size() >= blockSize); | |||
| printf("write %d\n", blockSize); | |||
| err = Pa_WriteStream(stream, (const float*) inputBuffer.startData(), blockSize); | |||
| printf("write done\n"); | |||
| inputBuffer.startIncr(blockSize); | |||
| assert(inputSrcBuffer.size() >= blockSize); | |||
| err = Pa_WriteStream(stream, (const float*) inputSrcBuffer.startData(), blockSize); | |||
| inputSrcBuffer.startIncr(blockSize); | |||
| if (err) { | |||
| // Ignore buffer underflows | |||
| if (err != paOutputUnderflowed) { | |||
| @@ -241,6 +246,7 @@ void AudioInterface::closeDevice() { | |||
| // Clear buffers | |||
| inputBuffer.clear(); | |||
| outputBuffer.clear(); | |||
| inputSrcBuffer.clear(); | |||
| inputSrc.reset(); | |||
| outputSrc.reset(); | |||
| } | |||
| @@ -355,6 +361,14 @@ struct BlockSizeChoice : ChoiceButton { | |||
| AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()) { | |||
| box.size = Vec(15*8, 380); | |||
| { | |||
| ModulePanel *panel = new ModulePanel(); | |||
| panel->box.size = box.size; | |||
| panel->backgroundColor = nvgRGBf(0.90, 0.90, 0.90); | |||
| // panel->imageFilename = ""; | |||
| addChild(panel); | |||
| } | |||
| float margin = 5; | |||
| float yPos = margin; | |||
| @@ -420,8 +434,8 @@ AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface() | |||
| } | |||
| yPos += 5; | |||
| addInput(createInput(Vec(25, yPos), module, AudioInterface::AUDIO1_INPUT)); | |||
| addInput(createInput(Vec(75, yPos), module, AudioInterface::AUDIO2_INPUT)); | |||
| addInput(createInput<InputPortPJ3410>(Vec(20, yPos), module, AudioInterface::AUDIO1_INPUT)); | |||
| addInput(createInput<InputPortPJ3410>(Vec(70, yPos), module, AudioInterface::AUDIO2_INPUT)); | |||
| yPos += 35 + margin; | |||
| { | |||
| @@ -433,12 +447,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface() | |||
| } | |||
| yPos += 5; | |||
| addOutput(createOutput(Vec(25, yPos), module, AudioInterface::AUDIO1_OUTPUT)); | |||
| addOutput(createOutput(Vec(75, yPos), module, AudioInterface::AUDIO2_OUTPUT)); | |||
| addOutput(createOutput<OutputPortPJ3410>(Vec(20, yPos), module, AudioInterface::AUDIO1_OUTPUT)); | |||
| addOutput(createOutput<OutputPortPJ3410>(Vec(70, yPos), module, AudioInterface::AUDIO2_OUTPUT)); | |||
| yPos += 35 + margin; | |||
| } | |||
| void AudioInterfaceWidget::draw(NVGcontext *vg) { | |||
| bndBackground(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | |||
| ModuleWidget::draw(vg); | |||
| } | |||
| @@ -224,6 +224,14 @@ struct MidiChoice : ChoiceButton { | |||
| MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) { | |||
| box.size = Vec(15*8, 380); | |||
| { | |||
| ModulePanel *panel = new ModulePanel(); | |||
| panel->box.size = box.size; | |||
| panel->backgroundColor = nvgRGBf(0.90, 0.90, 0.90); | |||
| // panel->imageFilename = ""; | |||
| addChild(panel); | |||
| } | |||
| float margin = 5; | |||
| float yPos = margin; | |||
| @@ -246,8 +254,8 @@ MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) { | |||
| } | |||
| yPos += 5; | |||
| addOutput(createOutput(Vec(25, yPos), module, MidiInterface::PITCH_OUTPUT)); | |||
| addOutput(createOutput(Vec(75, yPos), module, MidiInterface::GATE_OUTPUT)); | |||
| addOutput(createOutput<OutputPortPJ3410>(Vec(20, yPos), module, MidiInterface::PITCH_OUTPUT)); | |||
| addOutput(createOutput<OutputPortPJ3410>(Vec(70, yPos), module, MidiInterface::GATE_OUTPUT)); | |||
| yPos += 25 + margin; | |||
| { | |||
| @@ -264,8 +272,3 @@ MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) { | |||
| yPos += pitchLabel->box.size.y + margin; | |||
| } | |||
| } | |||
| void MidiInterfaceWidget::draw(NVGcontext *vg) { | |||
| bndBackground(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | |||
| ModuleWidget::draw(vg); | |||
| } | |||
| @@ -14,10 +14,8 @@ void midiInit(); | |||
| struct AudioInterfaceWidget : ModuleWidget { | |||
| AudioInterfaceWidget(); | |||
| void draw(NVGcontext *vg); | |||
| }; | |||
| struct MidiInterfaceWidget : ModuleWidget { | |||
| MidiInterfaceWidget(); | |||
| void draw(NVGcontext *vg); | |||
| }; | |||
| @@ -3,13 +3,6 @@ | |||
| namespace rack { | |||
| void InputPort::draw(NVGcontext *vg) { | |||
| SpriteWidget::draw(vg); | |||
| if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { | |||
| Port::drawGlow(vg); | |||
| } | |||
| } | |||
| void InputPort::onDragStart() { | |||
| if (connectedWire) { | |||
| // Disconnect wire from this port, but set it as the active wire | |||
| @@ -8,12 +8,8 @@ void ModulePanel::draw(NVGcontext *vg) { | |||
| nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | |||
| NVGpaint paint; | |||
| // Background gradient | |||
| Vec c = box.pos; | |||
| float length = box.size.norm(); | |||
| paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, highlightColor, backgroundColor); | |||
| nvgFillPaint(vg, paint); | |||
| // nvgFillColor(vg, backgroundColor); | |||
| // Background color | |||
| nvgFillColor(vg, backgroundColor); | |||
| nvgFill(vg); | |||
| // Background image | |||
| @@ -3,13 +3,6 @@ | |||
| namespace rack { | |||
| void OutputPort::draw(NVGcontext *vg) { | |||
| SpriteWidget::draw(vg); | |||
| if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { | |||
| Port::drawGlow(vg); | |||
| } | |||
| } | |||
| void OutputPort::onDragStart() { | |||
| if (connectedWire) { | |||
| // Disconnect wire from this port, but set it as the active wire | |||
| @@ -5,11 +5,6 @@ namespace rack { | |||
| Port::Port() { | |||
| box.size = Vec(20, 20); | |||
| spriteOffset = Vec(-18, -18); | |||
| spriteSize = Vec(56, 56); | |||
| spriteFilename = "res/port.png"; | |||
| index = randomu32() % 5; | |||
| } | |||
| Port::~Port() { | |||
| @@ -24,17 +19,6 @@ void Port::disconnect() { | |||
| } | |||
| } | |||
| void Port::drawGlow(NVGcontext *vg) { | |||
| Vec c = box.getCenter(); | |||
| NVGcolor icol = nvgRGBAf(1, 1, 1, 0.5); | |||
| NVGcolor ocol = nvgRGBAf(1, 1, 1, 0); | |||
| NVGpaint paint = nvgRadialGradient(vg, c.x, c.y, 0, 20, icol, ocol); | |||
| nvgFillPaint(vg, paint); | |||
| nvgBeginPath(vg); | |||
| nvgRect(vg, box.pos.x - 10, box.pos.y - 10, box.size.x + 20, box.size.y + 20); | |||
| nvgFill(vg); | |||
| } | |||
| void Port::onMouseDown(int button) { | |||
| if (button == 1) { | |||
| disconnect(); | |||
| @@ -3,37 +3,79 @@ | |||
| namespace rack { | |||
| void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, float tension, NVGcolor color) { | |||
| float dist = pos1.minus(pos2).norm(); | |||
| Vec slump; | |||
| slump.y = (1.0 - tension) * (150.0 + 1.0*dist); | |||
| Vec pos3 = pos1.plus(pos2).div(2).plus(slump); | |||
| void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, float tension, NVGcolor color, float opacity) { | |||
| NVGcolor colorShadow = nvgRGBAf(0, 0, 0, 0.08); | |||
| NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); | |||
| // Wire | |||
| if (opacity > 0.0) { | |||
| nvgSave(vg); | |||
| nvgGlobalAlpha(vg, opacity); | |||
| float dist = pos1.minus(pos2).norm(); | |||
| Vec slump; | |||
| slump.y = (1.0 - tension) * (150.0 + 1.0*dist); | |||
| Vec pos3 = pos1.plus(pos2).div(2).plus(slump); | |||
| nvgLineJoin(vg, NVG_ROUND); | |||
| // Shadow | |||
| Vec pos4 = pos3.plus(slump.mult(0.08)); | |||
| nvgBeginPath(vg); | |||
| nvgMoveTo(vg, pos1.x, pos1.y); | |||
| nvgQuadTo(vg, pos4.x, pos4.y, pos2.x, pos2.y); | |||
| nvgStrokeColor(vg, colorShadow); | |||
| nvgStrokeWidth(vg, 5); | |||
| nvgStroke(vg); | |||
| // Wire outline | |||
| nvgBeginPath(vg); | |||
| nvgMoveTo(vg, pos1.x, pos1.y); | |||
| nvgQuadTo(vg, pos3.x, pos3.y, pos2.x, pos2.y); | |||
| nvgStrokeColor(vg, colorOutline); | |||
| nvgStrokeWidth(vg, 6); | |||
| nvgStroke(vg); | |||
| // Wire solid | |||
| nvgStrokeColor(vg, color); | |||
| nvgStrokeWidth(vg, 4); | |||
| nvgStroke(vg); | |||
| nvgRestore(vg); | |||
| } | |||
| // First plug | |||
| nvgBeginPath(vg); | |||
| nvgCircle(vg, pos1.x, pos1.y, 21/2.0); | |||
| nvgFillColor(vg, colorOutline); | |||
| nvgFill(vg); | |||
| nvgLineJoin(vg, NVG_ROUND); | |||
| nvgBeginPath(vg); | |||
| nvgCircle(vg, pos1.x, pos1.y, 19/2.0); | |||
| nvgFillColor(vg, color); | |||
| nvgFill(vg); | |||
| // Shadow | |||
| Vec pos4 = pos3.plus(slump.mult(0.08)); | |||
| NVGcolor colorShadow = nvgRGBAf(0, 0, 0, 0.08); | |||
| nvgBeginPath(vg); | |||
| nvgMoveTo(vg, pos1.x, pos1.y); | |||
| nvgQuadTo(vg, pos4.x, pos4.y, pos2.x, pos2.y); | |||
| nvgStrokeColor(vg, colorShadow); | |||
| nvgStrokeWidth(vg, 5); | |||
| nvgStroke(vg); | |||
| // Wire outline | |||
| NVGcolor colorOutline = nvgRGBf(0, 0, 0); | |||
| nvgCircle(vg, pos1.x, pos1.y, 11/2.0); | |||
| nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0)); | |||
| nvgFill(vg); | |||
| // Second plug | |||
| nvgBeginPath(vg); | |||
| nvgCircle(vg, pos2.x, pos2.y, 21/2.0); | |||
| nvgFillColor(vg, colorOutline); | |||
| nvgFill(vg); | |||
| nvgBeginPath(vg); | |||
| nvgMoveTo(vg, pos1.x, pos1.y); | |||
| nvgQuadTo(vg, pos3.x, pos3.y, pos2.x, pos2.y); | |||
| nvgStrokeColor(vg, colorOutline); | |||
| nvgStrokeWidth(vg, 4); | |||
| nvgStroke(vg); | |||
| // Wire solid | |||
| nvgStrokeColor(vg, color); | |||
| nvgStrokeWidth(vg, 2); | |||
| nvgStroke(vg); | |||
| nvgCircle(vg, pos2.x, pos2.y, 19/2.0); | |||
| nvgFillColor(vg, color); | |||
| nvgFill(vg); | |||
| nvgBeginPath(vg); | |||
| nvgCircle(vg, pos2.x, pos2.y, 11/2.0); | |||
| nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0)); | |||
| nvgFill(vg); | |||
| } | |||
| @@ -87,35 +129,29 @@ void WireWidget::updateWire() { | |||
| void WireWidget::draw(NVGcontext *vg) { | |||
| Vec outputPos, inputPos; | |||
| Vec absolutePos = getAbsolutePos(); | |||
| float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0; | |||
| float opacity = gScene->toolbar->wireOpacitySlider->value / 100.0; | |||
| // Compute location of pos1 and pos2 | |||
| if (outputPort) { | |||
| outputPos = Rect(outputPort->getAbsolutePos(), outputPort->box.size).getCenter(); | |||
| } | |||
| else { | |||
| outputPos = gMousePos; | |||
| wireOpacity = 1.0; | |||
| opacity = 1.0; | |||
| } | |||
| if (inputPort) { | |||
| inputPos = Rect(inputPort->getAbsolutePos(), inputPort->box.size).getCenter(); | |||
| } | |||
| else { | |||
| inputPos = gMousePos; | |||
| wireOpacity = 1.0; | |||
| opacity = 1.0; | |||
| } | |||
| outputPos = outputPos.minus(absolutePos); | |||
| inputPos = inputPos.minus(absolutePos); | |||
| bndNodePort(vg, outputPos.x, outputPos.y, BND_DEFAULT, color); | |||
| bndNodePort(vg, inputPos.x, inputPos.y, BND_DEFAULT, color); | |||
| nvgSave(vg); | |||
| if (wireOpacity > 0.0) { | |||
| nvgGlobalAlpha(vg, wireOpacity); | |||
| float tension = gScene->toolbar->wireTensionSlider->value; | |||
| drawWire(vg, outputPos, inputPos, tension, color); | |||
| } | |||
| nvgRestore(vg); | |||
| float tension = gScene->toolbar->wireTensionSlider->value; | |||
| drawWire(vg, outputPos, inputPos, tension, color, opacity); | |||
| } | |||