diff --git a/include/app.hpp b/include/app.hpp index 4aa57478..b7d0b4be 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -87,7 +87,12 @@ struct ModuleWidget : OpaqueWidget { Called when the user clicks Randomize in the context menu. */ virtual void randomize(); + /** Do not subclass this to add context menu entries. Use appendContextMenu() instead */ virtual Menu *createContextMenu(); + /** Override to add context menu entries to your subclass. + It is recommended to add a blank MenuEntry first for spacing. + */ + virtual void appendContextMenu(Menu *menu) {} void draw(NVGcontext *vg) override; void drawShadow(NVGcontext *vg); @@ -320,19 +325,54 @@ struct MomentarySwitch : virtual Switch { // IO widgets //////////////////// +struct LedDisplay : Widget { + void draw(NVGcontext *vg) override; +}; + +struct LedDisplaySeparator : TransparentWidget { + LedDisplaySeparator(); + void draw(NVGcontext *vg) override; +}; + +struct LedDisplayChoice : TransparentWidget { + std::string text; + std::shared_ptr font; + NVGcolor color; + LedDisplayChoice(); + void draw(NVGcontext *vg) override; + void onMouseDown(EventMouseDown &e) override; +}; + +struct LedDisplayTextField : TextField { + std::shared_ptr font; + NVGcolor color; + LedDisplayTextField(); + void draw(NVGcontext *vg) override; + int getTextPosition(Vec mousePos) override; +}; + + struct AudioIO; struct MidiIO; -struct AudioWidget : OpaqueWidget { +struct AudioWidget : LedDisplay { /** Not owned */ AudioIO *audioIO = NULL; - void onMouseDown(EventMouseDown &e) override; + struct Internal; + Internal *internal; + AudioWidget(); + ~AudioWidget(); + void step() override; }; -struct MidiWidget : OpaqueWidget { +struct MidiWidget : LedDisplay { /** Not owned */ MidiIO *midiIO = NULL; - void onMouseDown(EventMouseDown &e) override; + struct Internal; + Internal *internal; + MidiWidget(); + ~MidiWidget(); + void step() override; }; //////////////////// diff --git a/include/audio.hpp b/include/audio.hpp index daf8f833..7c677d0b 100644 --- a/include/audio.hpp +++ b/include/audio.hpp @@ -23,6 +23,7 @@ struct AudioIO { int numOutputs = 0; int numInputs = 0; RtAudio *rtAudio = NULL; + RtAudio::DeviceInfo deviceInfo; AudioIO(); virtual ~AudioIO(); diff --git a/include/componentlibrary.hpp b/include/componentlibrary.hpp index c5de23e7..7f6a8fca 100644 --- a/include/componentlibrary.hpp +++ b/include/componentlibrary.hpp @@ -338,22 +338,6 @@ struct BefacoSlidePot : SVGFader { } }; -//////////////////// -// IO widgets -//////////////////// - -struct USB_B_AudioWidget : AudioWidget, SVGWidget { - USB_B_AudioWidget() { - setSVG(SVG::load(assetGlobal("res/ComponentLibrary/USB-B.svg"))); - } -}; - -struct MIDI_DIN_MidiWidget : MidiWidget, SVGWidget { - MIDI_DIN_MidiWidget() { - setSVG(SVG::load(assetGlobal("res/ComponentLibrary/MIDI_DIN.svg"))); - } -}; - //////////////////// // Jacks //////////////////// diff --git a/include/ui.hpp b/include/ui.hpp index 4d74b7d8..c62818af 100644 --- a/include/ui.hpp +++ b/include/ui.hpp @@ -44,24 +44,46 @@ struct Menu : OpaqueWidget { }; struct MenuEntry : OpaqueWidget { - std::string text; MenuEntry() { box.size = Vec(0, BND_WIDGET_HEIGHT); } + + template + static T *create() { + T *o = Widget::create(Vec()); + return o; + } }; struct MenuLabel : MenuEntry { + std::string text; void draw(NVGcontext *vg) override; void step() override; + + template + static T *create(std::string text) { + T *o = MenuEntry::create(); + o->text = text; + return o; + } }; struct MenuItem : MenuEntry { + std::string text; std::string rightText; void draw(NVGcontext *vg) override; void step() override; virtual Menu *createChildMenu() {return NULL;} void onMouseEnter(EventMouseEnter &e) override; void onDragDrop(EventDragDrop &e) override; + + template + static T *create(std::string text, std::string rightText = "") { + T *o = MenuEntry::create(); + o->text = text; + o->rightText = rightText; + return o; + } }; struct WindowOverlay : OpaqueWidget { @@ -153,16 +175,19 @@ struct TextField : OpaqueWidget { bool multiline = false; int begin = 0; int end = 0; + int dragPos = 0; TextField() { box.size.y = BND_WIDGET_HEIGHT; } void draw(NVGcontext *vg) override; void onMouseDown(EventMouseDown &e) override; + void onMouseMove(EventMouseMove &e) override; void onFocus(EventFocus &e) override; void onText(EventText &e) override; void onKey(EventKey &e) override; void insertText(std::string newText); + virtual int getTextPosition(Vec mousePos); virtual void onTextChange() {} }; @@ -170,7 +195,7 @@ struct PasswordField : TextField { void draw(NVGcontext *vg) override; }; -struct ProgressBar : TransparentWidget, QuantityWidget { +struct ProgressBar : QuantityWidget { ProgressBar() { box.size.y = BND_WIDGET_HEIGHT; } diff --git a/res/Core/AudioInterface.svg b/res/Core/AudioInterface.svg new file mode 100644 index 00000000..175ba6ac --- /dev/null +++ b/res/Core/AudioInterface.svg @@ -0,0 +1,463 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/Core/LICENSE.txt b/res/Core/LICENSE.txt new file mode 100644 index 00000000..10b99ce4 --- /dev/null +++ b/res/Core/LICENSE.txt @@ -0,0 +1,3 @@ +Core panel and VCV logo design © 2018 by Grayscale + +Derivative works may not use Core panel and VCV logo design. diff --git a/res/Core/MIDIToCVInterface.svg b/res/Core/MIDIToCVInterface.svg new file mode 100644 index 00000000..12c206ca --- /dev/null +++ b/res/Core/MIDIToCVInterface.svg @@ -0,0 +1,505 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/Core/Notes.svg b/res/Core/Notes.svg new file mode 100644 index 00000000..e1676db5 --- /dev/null +++ b/res/Core/Notes.svg @@ -0,0 +1,132 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/res/LICENSE-DejaVuSans.txt b/res/fonts/DejaVuSans-LICENSE.txt similarity index 54% rename from res/LICENSE-DejaVuSans.txt rename to res/fonts/DejaVuSans-LICENSE.txt index 254e2cc4..df52c170 100644 --- a/res/LICENSE-DejaVuSans.txt +++ b/res/fonts/DejaVuSans-LICENSE.txt @@ -1,6 +1,7 @@ Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + Bitstream Vera Fonts Copyright ------------------------------ @@ -46,7 +47,7 @@ Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot -org. +org. Arev Fonts Copyright ------------------------------ @@ -96,4 +97,91 @@ dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. -$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $ +TeX Gyre DJV Math +----------------- +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. + +Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski +(on behalf of TeX users groups) are in public domain. + +Letters imported from Euler Fraktur from AMSfonts are (c) American +Mathematical Society (see below). +Bitstream Vera Fonts Copyright +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera +is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license (“Fonts”) and associated +documentation +files (the “Font Software”), to reproduce and distribute the Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, +and/or sell copies of the Font Software, and to permit persons to whom +the Font Software is furnished to do so, subject to the following +conditions: + +The above copyright and trademark notices and this permission notice +shall be +included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional +glyphs or characters may be added to the Fonts, only if the fonts are +renamed +to names not containing either the words “Bitstream” or the word “Vera”. + +This License becomes null and void to the extent applicable to Fonts or +Font Software +that has been modified and is distributed under the “Bitstream Vera” +names. + +The Font Software may be sold as part of a larger software package but +no copy +of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, +SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN +ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR +INABILITY TO USE +THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. +Except as contained in this notice, the names of GNOME, the GNOME +Foundation, +and Bitstream Inc., shall not be used in advertising or otherwise to promote +the sale, use or other dealings in this Font Software without prior written +authorization from the GNOME Foundation or Bitstream Inc., respectively. +For further information, contact: fonts at gnome dot org. + +AMSFonts (v. 2.2) copyright + +The PostScript Type 1 implementation of the AMSFonts produced by and +previously distributed by Blue Sky Research and Y&Y, Inc. are now freely +available for general use. This has been accomplished through the +cooperation +of a consortium of scientific publishers with Blue Sky Research and Y&Y. +Members of this consortium include: + +Elsevier Science IBM Corporation Society for Industrial and Applied +Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) + +In order to assure the authenticity of these fonts, copyright will be +held by +the American Mathematical Society. This is not meant to restrict in any way +the legitimate use of the fonts, such as (but not limited to) electronic +distribution of documents containing these fonts, inclusion of these fonts +into other public domain or commercial font collections or computer +applications, use of the outline data to create derivative fonts and/or +faces, etc. However, the AMS does require that the AMS copyright notice be +removed from any derivative versions of the fonts which have been altered in +any way. In addition, to ensure the fidelity of TeX documents using Computer +Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, +has requested that any alterations which yield different font metrics be +given a different name. + +$Id$ diff --git a/res/DejaVuSans.ttf b/res/fonts/DejaVuSans.ttf similarity index 73% rename from res/DejaVuSans.ttf rename to res/fonts/DejaVuSans.ttf index 9d40c325..e5f7eecc 100644 Binary files a/res/DejaVuSans.ttf and b/res/fonts/DejaVuSans.ttf differ diff --git a/res/fonts/ShareTechMono-Regular-LICENSE.txt b/res/fonts/ShareTechMono-Regular-LICENSE.txt new file mode 100644 index 00000000..d75b6409 --- /dev/null +++ b/res/fonts/ShareTechMono-Regular-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright (c) 2012, Carrois Type Design, Ralph du Carrois (post@carrois.com www.carrois.com), with Reserved Font Name 'Share' + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/res/fonts/ShareTechMono-Regular.ttf b/res/fonts/ShareTechMono-Regular.ttf new file mode 100644 index 00000000..1611474a Binary files /dev/null and b/res/fonts/ShareTechMono-Regular.ttf differ diff --git a/src/app/AddModuleWindow.cpp b/src/app/AddModuleWindow.cpp index 8e6fd377..f80a4c73 100644 --- a/src/app/AddModuleWindow.cpp +++ b/src/app/AddModuleWindow.cpp @@ -55,7 +55,7 @@ struct MetadataMenu : ListMenu { // Tag list if (!model->tags.empty()) { for (ModelTag tag : model->tags) { - addChild(construct(&MenuEntry::text, gTagNames[tag])); + addChild(construct(&MenuLabel::text, gTagNames[tag])); } addChild(construct()); } @@ -66,17 +66,17 @@ struct MetadataMenu : ListMenu { pluginName += " v"; pluginName += model->plugin->version; } - addChild(construct(&MenuEntry::text, pluginName)); + addChild(construct(&MenuLabel::text, pluginName)); // Plugin metadata if (!model->plugin->website.empty()) { - addChild(construct(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->website)); + addChild(construct(&MenuItem::text, "Website", &UrlItem::url, model->plugin->website)); } if (!model->plugin->manual.empty()) { - addChild(construct(&MenuEntry::text, "Manual", &UrlItem::url, model->plugin->manual)); + addChild(construct(&MenuItem::text, "Manual", &UrlItem::url, model->plugin->manual)); } if (!model->plugin->path.empty()) { - addChild(construct(&MenuEntry::text, "Browse directory", &UrlItem::url, model->plugin->path)); + addChild(construct(&MenuItem::text, "Browse directory", &UrlItem::url, model->plugin->path)); } } } @@ -137,7 +137,7 @@ struct ModelMenu : ListMenu { for (Plugin *plugin : gPlugins) { for (Model *model : plugin->models) { if (model->manufacturer == manufacturer) { - addChild(construct(&MenuEntry::text, model->name, &ModelItem::model, model)); + addChild(construct(&MenuItem::text, model->name, &ModelItem::model, model)); } } } @@ -190,7 +190,7 @@ struct ManufacturerMenu : ListMenu { } // Add menu item for each manufacturer name for (std::string manufacturer : manufacturers) { - addChild(construct(&MenuEntry::text, manufacturer)); + addChild(construct(&MenuItem::text, manufacturer)); } } diff --git a/src/app/AudioWidget.cpp b/src/app/AudioWidget.cpp index bd73f662..0b0aedf3 100644 --- a/src/app/AudioWidget.cpp +++ b/src/app/AudioWidget.cpp @@ -13,6 +13,25 @@ struct AudioDriverItem : MenuItem { } }; +struct AudioDriverChoice : LedDisplayChoice { + AudioWidget *audioWidget; + void onAction(EventAction &e) override { + Menu *menu = gScene->createMenu(); + menu->addChild(construct(&MenuLabel::text, "Audio driver")); + for (int driver : audioWidget->audioIO->listDrivers()) { + AudioDriverItem *item = new AudioDriverItem(); + item->audioIO = audioWidget->audioIO; + item->driver = driver; + item->text = audioWidget->audioIO->getDriverName(driver); + item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); + menu->addChild(item); + } + } + void step() override { + text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); + } +}; + struct AudioDeviceItem : MenuItem { AudioIO *audioIO; @@ -23,6 +42,42 @@ struct AudioDeviceItem : MenuItem { } }; +struct AudioDeviceChoice : LedDisplayChoice { + AudioWidget *audioWidget; + void onAction(EventAction &e) override { + Menu *menu = gScene->createMenu(); + menu->addChild(construct(&MenuLabel::text, "Audio device")); + int deviceCount = audioWidget->audioIO->getDeviceCount(); + { + AudioDeviceItem *item = new AudioDeviceItem(); + item->audioIO = audioWidget->audioIO; + item->device = -1; + item->text = "(No device)"; + item->rightText = CHECKMARK(item->device == audioWidget->audioIO->device); + menu->addChild(item); + } + for (int device = 0; device < deviceCount; device++) { + AudioDeviceItem *item = new AudioDeviceItem(); + item->audioIO = audioWidget->audioIO; + item->device = device; + item->text = audioWidget->audioIO->getDeviceDetail(device); + item->rightText = CHECKMARK(item->device == audioWidget->audioIO->device); + menu->addChild(item); + } + } + void step() override { + text = audioWidget->audioIO->getDeviceDetail(audioWidget->audioIO->device); + if (text.empty()) { + text = "(No device)"; + color.a = 0.5f; + } + else { + color.a = 1.f; + } + text = ellipsize(text, 18); + } +}; + struct AudioSampleRateItem : MenuItem { AudioIO *audioIO; @@ -33,6 +88,25 @@ struct AudioSampleRateItem : MenuItem { } }; +struct AudioSampleRateChoice : LedDisplayChoice { + AudioWidget *audioWidget; + void onAction(EventAction &e) override { + Menu *menu = gScene->createMenu(); + menu->addChild(construct(&MenuLabel::text, "Sample rate")); + for (int sampleRate : audioWidget->audioIO->listSampleRates()) { + AudioSampleRateItem *item = new AudioSampleRateItem(); + item->audioIO = audioWidget->audioIO; + item->sampleRate = sampleRate; + item->text = stringf("%d Hz", sampleRate); + item->rightText = CHECKMARK(item->sampleRate == audioWidget->audioIO->sampleRate); + menu->addChild(item); + } + } + void step() override { + text = stringf("%g kHz", audioWidget->audioIO->sampleRate / 1000.f); + } +}; + struct AudioBlockSizeItem : MenuItem { AudioIO *audioIO; @@ -43,72 +117,91 @@ struct AudioBlockSizeItem : MenuItem { } }; +struct AudioBlockSizeChoice : LedDisplayChoice { + AudioWidget *audioWidget; + void onAction(EventAction &e) override { + Menu *menu = gScene->createMenu(); + menu->addChild(construct(&MenuLabel::text, "Block size")); + std::vector blockSizes = {64, 128, 256, 512, 1024, 2048, 4096}; + for (int blockSize : blockSizes) { + AudioBlockSizeItem *item = new AudioBlockSizeItem(); + item->audioIO = audioWidget->audioIO; + item->blockSize = blockSize; + float latency = (float) blockSize / audioWidget->audioIO->sampleRate * 1000.0; + item->text = stringf("%d (%.1f ms)", blockSize, latency); + item->rightText = CHECKMARK(item->blockSize == audioWidget->audioIO->blockSize); + menu->addChild(item); + } + } + void step() override { + text = stringf("%d", audioWidget->audioIO->blockSize); + } +}; + -void AudioWidget::onMouseDown(EventMouseDown &e) { - OpaqueWidget::onMouseDown(e); +struct AudioWidget::Internal { + LedDisplayChoice *driverChoice; + LedDisplaySeparator *driverSeparator; + LedDisplayChoice *deviceChoice; + LedDisplaySeparator *deviceSeparator; + LedDisplayChoice *sampleRateChoice; + LedDisplaySeparator *sampleRateSeparator; + LedDisplayChoice *bufferSizeChoice; +}; - if (!audioIO) - return; +AudioWidget::AudioWidget() { + internal = new Internal(); + box.size = mm2px(Vec(44, 28)); - Menu *menu = gScene->createMenu(); + Vec pos = Vec(); - // Audio driver - menu->addChild(construct(&MenuLabel::text, "Audio driver")); - for (int driver : audioIO->listDrivers()) { - AudioDriverItem *item = new AudioDriverItem(); - item->audioIO = audioIO; - item->driver = driver; - item->text = audioIO->getDriverName(driver); - item->rightText = CHECKMARK(item->driver == audioIO->driver); - menu->addChild(item); - } - menu->addChild(construct()); - - // Audio device - menu->addChild(construct(&MenuLabel::text, "Audio device")); - int deviceCount = audioIO->getDeviceCount(); - { - AudioDeviceItem *item = new AudioDeviceItem(); - item->audioIO = audioIO; - item->device = -1; - item->text = "No device"; - item->rightText = CHECKMARK(item->device == audioIO->device); - menu->addChild(item); - } - for (int device = 0; device < deviceCount; device++) { - AudioDeviceItem *item = new AudioDeviceItem(); - item->audioIO = audioIO; - item->device = device; - item->text = audioIO->getDeviceDetail(device); - item->rightText = CHECKMARK(item->device == audioIO->device); - menu->addChild(item); - } - menu->addChild(construct()); - - // Sample rate - menu->addChild(construct(&MenuLabel::text, "Sample rate")); - for (int sampleRate : audioIO->listSampleRates()) { - AudioSampleRateItem *item = new AudioSampleRateItem(); - item->audioIO = audioIO; - item->sampleRate = sampleRate; - item->text = stringf("%d Hz", sampleRate); - item->rightText = CHECKMARK(item->sampleRate == audioIO->sampleRate); - menu->addChild(item); - } - menu->addChild(construct()); - - // Block size - menu->addChild(construct(&MenuLabel::text, "Block size")); - std::vector blockSizes = {64, 128, 256, 512, 1024, 2048, 4096}; - for (int blockSize : blockSizes) { - AudioBlockSizeItem *item = new AudioBlockSizeItem(); - item->audioIO = audioIO; - item->blockSize = blockSize; - float latency = (float) blockSize / audioIO->sampleRate * 1000.0; - item->text = stringf("%d (%.1f ms)", blockSize, latency); - item->rightText = CHECKMARK(item->blockSize == audioIO->blockSize); - menu->addChild(item); - } + AudioDriverChoice *driverChoice = Widget::create(pos); + driverChoice->audioWidget = this; + addChild(driverChoice); + pos = driverChoice->box.getBottomLeft(); + internal->driverChoice = driverChoice; + + internal->driverSeparator = Widget::create(pos); + addChild(internal->driverSeparator); + + AudioDeviceChoice *deviceChoice = Widget::create(pos); + deviceChoice->audioWidget = this; + addChild(deviceChoice); + pos = deviceChoice->box.getBottomLeft(); + internal->deviceChoice = deviceChoice; + + internal->deviceSeparator = Widget::create(pos); + addChild(internal->deviceSeparator); + + AudioSampleRateChoice *sampleRateChoice = Widget::create(pos); + sampleRateChoice->audioWidget = this; + addChild(sampleRateChoice); + internal->sampleRateChoice = sampleRateChoice; + + internal->sampleRateSeparator = Widget::create(pos); + internal->sampleRateSeparator->box.size.y = internal->sampleRateChoice->box.size.y; + addChild(internal->sampleRateSeparator); + + AudioBlockSizeChoice *bufferSizeChoice = Widget::create(pos); + bufferSizeChoice->audioWidget = this; + addChild(bufferSizeChoice); + internal->bufferSizeChoice = bufferSizeChoice; +} + +AudioWidget::~AudioWidget() { + delete internal; +} + +void AudioWidget::step() { + internal->driverChoice->box.size.x = box.size.x; + internal->driverSeparator->box.size.x = box.size.x; + internal->deviceChoice->box.size.x = box.size.x; + internal->deviceSeparator->box.size.x = box.size.x; + internal->sampleRateChoice->box.size.x = box.size.x / 2; + internal->sampleRateSeparator->box.pos.x = box.size.x / 2; + internal->bufferSizeChoice->box.pos.x = box.size.x / 2; + internal->bufferSizeChoice->box.size.x = box.size.x / 2; + LedDisplay::step(); } diff --git a/src/app/LedDisplay.cpp b/src/app/LedDisplay.cpp new file mode 100644 index 00000000..0305b3da --- /dev/null +++ b/src/app/LedDisplay.cpp @@ -0,0 +1,102 @@ +#include "app.hpp" +#include "asset.hpp" +#include "window.hpp" + + +namespace rack { + + +void LedDisplay::draw(NVGcontext *vg) { + nvgBeginPath(vg); + nvgRoundedRect(vg, 0, 0, box.size.x, box.size.y, 5.0); + nvgFillColor(vg, nvgRGB(0x00, 0x00, 0x00)); + nvgFill(vg); + + Widget::draw(vg); +} + + +LedDisplaySeparator::LedDisplaySeparator() { + box.size = Vec(); +} + +void LedDisplaySeparator::draw(NVGcontext *vg) { + nvgBeginPath(vg); + nvgMoveTo(vg, 0, 0); + nvgLineTo(vg, box.size.x, box.size.y); + nvgStrokeWidth(vg, 1.0); + nvgStrokeColor(vg, nvgRGB(0x33, 0x33, 0x33)); + nvgStroke(vg); +} + + +LedDisplayChoice::LedDisplayChoice() { + box.size = mm2px(Vec(0, 28.0 / 3)); + font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); + color = nvgRGB(0xff, 0xd7, 0x14); +} + +void LedDisplayChoice::draw(NVGcontext *vg) { + if (font->handle < 0) + return; + + nvgFillColor(vg, color); + nvgFontFaceId(vg, font->handle); + nvgTextLetterSpacing(vg, 0.0); + + Vec textPos = Vec(10, 18); + nvgFontSize(vg, 12); + nvgText(vg, textPos.x, textPos.y, text.c_str(), NULL); +} + +void LedDisplayChoice::onMouseDown(EventMouseDown &e) { + if (e.button == 1) { + EventAction eAction; + onAction(eAction); + e.consumed = true; + e.target = this; + } +} + + +LedDisplayTextField::LedDisplayTextField() { + font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); + color = nvgRGB(0xff, 0xd7, 0x14); +} + +static const Vec textFieldPos = Vec(5, 5); + +void LedDisplayTextField::draw(NVGcontext *vg) { + // Background + nvgBeginPath(vg); + nvgRoundedRect(vg, 0, 0, box.size.x, box.size.y, 5.0); + nvgFillColor(vg, nvgRGB(0x00, 0x00, 0x00)); + nvgFill(vg); + + // Text + if (font->handle < 0) + return; + + bndSetFont(font->handle); + + NVGcolor highlightColor = color; + highlightColor.a = 0.5; + int cend = (this == gFocusedWidget) ? end : -1; + bndIconLabelCaret(vg, textFieldPos.x, textFieldPos.y, + box.size.x - 2*textFieldPos.x, box.size.y - 2*textFieldPos.y, + -1, color, 12, text.c_str(), highlightColor, begin, cend); + + bndSetFont(gGuiFont->handle); +} + +int LedDisplayTextField::getTextPosition(Vec mousePos) { + bndSetFont(font->handle); + int textPos = bndIconLabelTextPosition(gVg, textFieldPos.x, textFieldPos.y, + box.size.x - 2*textFieldPos.x, box.size.y - 2*textFieldPos.y, + -1, 12, text.c_str(), mousePos.x, mousePos.y); + bndSetFont(gGuiFont->handle); + return textPos; +} + + +} // namespace rack diff --git a/src/app/MidiWidget.cpp b/src/app/MidiWidget.cpp index b3826c3c..2de8c063 100644 --- a/src/app/MidiWidget.cpp +++ b/src/app/MidiWidget.cpp @@ -5,27 +5,141 @@ namespace rack { +struct MidiDriverItem : MenuItem { + MidiIO *midiIO; + int driver; + void onAction(EventAction &e) override { + // midiIO->openDriver(device); + } +}; + +struct MidiDriverChoice : LedDisplayChoice { + MidiWidget *midiWidget; + void onAction(EventAction &e) override { + // Menu *menu = gScene->createMenu(); + // menu->addChild(construct(&MenuLabel::text, "Audio driver")); + // for (int driver : audioWidget->audioIO->listDrivers()) { + // AudioDriverItem *item = new AudioDriverItem(); + // item->audioIO = audioWidget->audioIO; + // item->driver = driver; + // item->text = audioWidget->audioIO->getDriverName(driver); + // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); + // menu->addChild(item); + // } + } + void step() override { + // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); + } +}; + struct MidiDeviceItem : MenuItem { MidiIO *midiIO; int device; void onAction(EventAction &e) override { - midiIO->openDevice(device); + // midiIO->openDevice(device); } }; +struct MidiDeviceChoice : LedDisplayChoice { + MidiWidget *midiWidget; + void onAction(EventAction &e) override { + // Menu *menu = gScene->createMenu(); + // menu->addChild(construct(&MenuLabel::text, "Audio driver")); + // for (int driver : audioWidget->audioIO->listDrivers()) { + // AudioDriverItem *item = new AudioDriverItem(); + // item->audioIO = audioWidget->audioIO; + // item->driver = driver; + // item->text = audioWidget->audioIO->getDriverName(driver); + // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); + // menu->addChild(item); + // } + } + void step() override { + // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); + } +}; struct MidiChannelItem : MenuItem { MidiIO *midiIO; int channel; void onAction(EventAction &e) override { - midiIO->channel = channel; + // midiIO->channel = channel; + } +}; + +struct MidiChannelChoice : LedDisplayChoice { + MidiWidget *midiWidget; + void onAction(EventAction &e) override { + // Menu *menu = gScene->createMenu(); + // menu->addChild(construct(&MenuLabel::text, "Audio driver")); + // for (int driver : audioWidget->audioIO->listDrivers()) { + // AudioDriverItem *item = new AudioDriverItem(); + // item->audioIO = audioWidget->audioIO; + // item->driver = driver; + // item->text = audioWidget->audioIO->getDriverName(driver); + // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); + // menu->addChild(item); + // } } + void step() override { + // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); + } +}; + + +struct MidiWidget::Internal { + LedDisplayChoice *driverChoice; + LedDisplaySeparator *driverSeparator; + LedDisplayChoice *deviceChoice; + LedDisplaySeparator *deviceSeparator; + LedDisplayChoice *channelChoice; }; +MidiWidget::MidiWidget() { + internal = new Internal(); + box.size = mm2px(Vec(44, 28)); + + Vec pos = Vec(); + + MidiDriverChoice *driverChoice = Widget::create(pos); + driverChoice->midiWidget = this; + addChild(driverChoice); + pos = driverChoice->box.getBottomLeft(); + internal->driverChoice = driverChoice; -void MidiWidget::onMouseDown(EventMouseDown &e) { - OpaqueWidget::onMouseDown(e); + internal->driverSeparator = Widget::create(pos); + addChild(internal->driverSeparator); + + MidiDeviceChoice *deviceChoice = Widget::create(pos); + deviceChoice->midiWidget = this; + addChild(deviceChoice); + pos = deviceChoice->box.getBottomLeft(); + internal->deviceChoice = deviceChoice; + + internal->deviceSeparator = Widget::create(pos); + addChild(internal->deviceSeparator); + + MidiChannelChoice *channelChoice = Widget::create(pos); + channelChoice->midiWidget = this; + addChild(channelChoice); + internal->channelChoice = channelChoice; +} + +MidiWidget::~MidiWidget() { + delete internal; +} + +void MidiWidget::step() { + internal->driverChoice->box.size.x = box.size.x; + internal->driverSeparator->box.size.x = box.size.x; + internal->deviceChoice->box.size.x = box.size.x; + internal->deviceSeparator->box.size.x = box.size.x; + internal->channelChoice->box.size.x = box.size.x; + LedDisplay::step(); +} +/* +void MidiWidget::onAction(EventAction &e) { if (!midiIO) return; @@ -69,6 +183,7 @@ void MidiWidget::onMouseDown(EventMouseDown &e) { menu->addChild(item); } } +*/ } // namespace rack diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index da9a080d..3af3a989 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -346,6 +346,8 @@ Menu *ModuleWidget::createContextMenu() { deleteItem->moduleWidget = this; menu->addChild(deleteItem); + appendContextMenu(menu); + return menu; } diff --git a/src/audio.cpp b/src/audio.cpp index 06e5741c..2d1477c3 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -2,7 +2,8 @@ #include "audio.hpp" -#define DRIVER_BRIDGE -1 +#define BRIDGE_DRIVER -1 +#define BRIDGE_CHANNELS 16 namespace rack { @@ -23,7 +24,7 @@ std::vector AudioIO::listDrivers() { for (RtAudio::Api api : apis) drivers.push_back((int) api); // Add Bridge fake driver - // drivers.push_back(DRIVER_BRIDGE); + drivers.push_back(BRIDGE_DRIVER); return drivers; } @@ -39,7 +40,7 @@ std::string AudioIO::getDriverName(int driver) { case RtAudio::WINDOWS_ASIO: return "ASIO"; case RtAudio::WINDOWS_DS: return "DirectSound"; case RtAudio::RTAUDIO_DUMMY: return "Dummy"; - case DRIVER_BRIDGE: return "VCV Bridge"; + case BRIDGE_DRIVER: return "Bridge"; default: return "Unknown"; } } @@ -58,9 +59,9 @@ void AudioIO::setDriver(int driver) { rtAudio = new RtAudio((RtAudio::Api) driver); this->driver = (int) rtAudio->getCurrentApi(); } - else if (driver == DRIVER_BRIDGE) { + else if (driver == BRIDGE_DRIVER) { // TODO Connect to Bridge - this->driver = DRIVER_BRIDGE; + this->driver = BRIDGE_DRIVER; } } @@ -68,40 +69,56 @@ int AudioIO::getDeviceCount() { if (rtAudio) { return rtAudio->getDeviceCount(); } - if (driver == DRIVER_BRIDGE) { - return 16; + if (driver == BRIDGE_DRIVER) { + return BRIDGE_CHANNELS; } return 0; } std::string AudioIO::getDeviceName(int device) { + if (device < 0) + return ""; + if (rtAudio) { try { - RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); + RtAudio::DeviceInfo deviceInfo; + if (device == this->device) + deviceInfo = this->deviceInfo; + else + deviceInfo = rtAudio->getDeviceInfo(device); return deviceInfo.name; } catch (RtAudioError &e) { warn("Failed to query RtAudio device: %s", e.what()); } } - if (driver == DRIVER_BRIDGE) { - return stringf("%d", device + 1); + if (driver == BRIDGE_DRIVER) { + if (device >= 0) + return stringf("%d", device + 1); } return ""; } std::string AudioIO::getDeviceDetail(int device) { + if (device < 0) + return ""; + if (rtAudio) { try { - RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); + RtAudio::DeviceInfo deviceInfo; + if (device == this->device) + deviceInfo = this->deviceInfo; + else + deviceInfo = rtAudio->getDeviceInfo(device); return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels); } catch (RtAudioError &e) { warn("Failed to query RtAudio device: %s", e.what()); } } - if (driver == DRIVER_BRIDGE) { - return stringf("Channel %d", device + 1); + if (driver == BRIDGE_DRIVER) { + if (device >= 0) + return stringf("Channel %d", device + 1); } return ""; } @@ -123,7 +140,6 @@ void AudioIO::openStream() { if (rtAudio) { // Open new device - RtAudio::DeviceInfo deviceInfo; try { deviceInfo = rtAudio->getDeviceInfo(device); } @@ -184,6 +200,11 @@ void AudioIO::openStream() { this->device = device; onOpenStream(); } + if (driver == BRIDGE_DRIVER) { + if (device < BRIDGE_CHANNELS) { + this->device = device; + } + } } void AudioIO::closeStream() { @@ -206,6 +227,7 @@ void AudioIO::closeStream() { warn("Failed to close RtAudio stream %s", e.what()); } } + deviceInfo = RtAudio::DeviceInfo(); } // Reset rtAudio settings @@ -234,7 +256,7 @@ std::vector AudioIO::listSampleRates() { warn("Failed to query RtAudio device: %s", e.what()); } } - if (driver == DRIVER_BRIDGE) { + if (driver == BRIDGE_DRIVER) { return {44100, 48000, 88200, 96000, 176400, 192000}; } diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index c9c66ac8..861b3f8c 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -15,8 +15,8 @@ #pragma GCC diagnostic pop -#define MAX_OUTPUTS 8 -#define MAX_INPUTS 8 +#define OUTPUTS 8 +#define INPUTS 8 static const auto audioTimeout = std::chrono::milliseconds(100); @@ -30,13 +30,13 @@ struct AudioInterfaceIO : AudioIO { std::mutex audioMutex; std::condition_variable audioCv; // Audio thread produces, engine thread consumes - DoubleRingBuffer, (1<<15)> inputBuffer; + DoubleRingBuffer, (1<<15)> inputBuffer; // Audio thread consumes, engine thread produces - DoubleRingBuffer, (1<<15)> outputBuffer; + DoubleRingBuffer, (1<<15)> outputBuffer; AudioInterfaceIO() { - maxOutputs = MAX_OUTPUTS; - maxInputs = MAX_INPUTS; + maxOutputs = OUTPUTS; + maxInputs = INPUTS; } ~AudioInterfaceIO() { @@ -49,7 +49,7 @@ struct AudioInterfaceIO : AudioIO { for (int i = 0; i < length; i++) { if (inputBuffer.full()) break; - Frame f; + Frame f; memset(&f, 0, sizeof(f)); memcpy(&f, &input[numInputs * i], numInputs * sizeof(float)); inputBuffer.push(f); @@ -64,7 +64,7 @@ struct AudioInterfaceIO : AudioIO { if (audioCv.wait_for(lock, audioTimeout, cond)) { // Consume audio block for (int i = 0; i < length; i++) { - Frame f = outputBuffer.shift(); + Frame f = outputBuffer.shift(); memcpy(&output[numOutputs * i], &f, numOutputs * sizeof(float)); } } @@ -90,27 +90,28 @@ struct AudioInterface : Module { NUM_PARAMS }; enum InputIds { - ENUMS(AUDIO_INPUT, MAX_INPUTS), + ENUMS(AUDIO_INPUT, INPUTS), NUM_INPUTS }; enum OutputIds { - ENUMS(AUDIO_OUTPUT, MAX_OUTPUTS), + ENUMS(AUDIO_OUTPUT, OUTPUTS), NUM_OUTPUTS }; enum LightIds { - ACTIVE_LIGHT, + ENUMS(INPUT_LIGHT, INPUTS), + ENUMS(OUTPUT_LIGHT, OUTPUTS), NUM_LIGHTS }; AudioInterfaceIO audioIO; int lastSampleRate = 0; - SampleRateConverter inputSrc; - SampleRateConverter outputSrc; + SampleRateConverter inputSrc; + SampleRateConverter outputSrc; // in rack's sample rate - DoubleRingBuffer, 16> inputBuffer; - DoubleRingBuffer, 16> outputBuffer; + DoubleRingBuffer, 16> inputBuffer; + DoubleRingBuffer, 16> outputBuffer; AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { onSampleRateChange(); @@ -130,10 +131,10 @@ struct AudioInterface : Module { } void onSampleRateChange() override { - // for (int i = 0; i < MAX_INPUTS; i++) { + // for (int i = 0; i < INPUTS; i++) { // inputSrc[i].setRates(audioIO.sampleRate, engineGetSampleRate()); // } - // for (int i = 0; i < MAX_OUTPUTS; i++) { + // for (int i = 0; i < OUTPUTS; i++) { // outputSrc[i].setRates(engineGetSampleRate(), audioIO.sampleRate); // } inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); @@ -147,7 +148,7 @@ struct AudioInterface : Module { void AudioInterface::step() { - Frame inputFrame; + Frame inputFrame; memset(&inputFrame, 0, sizeof(inputFrame)); // Update sample rate if changed by audio driver @@ -169,14 +170,14 @@ void AudioInterface::step() { if (!inputBuffer.empty()) { inputFrame = inputBuffer.shift(); } - for (int i = 0; i < MAX_INPUTS; i++) { + for (int i = 0; i < INPUTS; i++) { outputs[AUDIO_OUTPUT + i].value = 10.0 * inputFrame.samples[i]; } if (audioIO.numOutputs > 0) { // Get and push output SRC frame if (!outputBuffer.full()) { - Frame f; + Frame f; for (int i = 0; i < audioIO.numOutputs; i++) { f.samples[i] = inputs[AUDIO_INPUT + i].value / 10.0; } @@ -203,109 +204,72 @@ void AudioInterface::step() { } } - audioIO.audioCv.notify_all(); + for (int i = 0; i < INPUTS; i++) + lights[INPUT_LIGHT + i].value = (audioIO.numInputs > i); + for (int i = 0; i < OUTPUTS; i++) + lights[OUTPUT_LIGHT + i].value = (audioIO.numOutputs > i); - // Lights - lights[ACTIVE_LIGHT].value = audioIO.isActive() ? 1.0 : 0.0; + audioIO.audioCv.notify_all(); } struct AudioInterfaceWidget : ModuleWidget { - AudioInterfaceWidget(AudioInterface *module) : ModuleWidget(module) { - box.size = Vec(15*12, 380); - { - Panel *panel = new LightPanel(); - panel->box.size = box.size; - addChild(panel); - } - - addChild(Widget::create(Vec(15, 0))); - addChild(Widget::create(Vec(box.size.x-30, 0))); - addChild(Widget::create(Vec(15, 365))); - addChild(Widget::create(Vec(box.size.x-30, 365))); - - Vec margin = Vec(5, 2); - float labelHeight = 15; - float yPos = margin.y + 100; - float xPos; - - { - Label *label = new Label(); - label->box.pos = Vec(margin.x, yPos); - label->text = "Outputs (DACs)"; - addChild(label); - yPos += labelHeight + margin.y; - } - - yPos += 5; - xPos = 10; - for (int i = 0; i < 4; i++) { - addInput(Port::create(Vec(xPos, yPos), Port::INPUT, module, AudioInterface::AUDIO_INPUT + i)); - Label *label = new Label(); - label->box.pos = Vec(xPos + 4, yPos + 28); - label->text = stringf("%d", i + 1); - addChild(label); - - xPos += 37 + margin.x; - } - yPos += 35 + margin.y; - - yPos += 5; - xPos = 10; - for (int i = 4; i < 8; i++) { - addInput(Port::create(Vec(xPos, yPos), Port::INPUT, module, AudioInterface::AUDIO_INPUT + i)); - Label *label = new Label(); - label->box.pos = Vec(xPos + 4, yPos + 28); - label->text = stringf("%d", i + 1); - addChild(label); - - xPos += 37 + margin.x; - } - yPos += 35 + margin.y; - - { - Label *label = new Label(); - label->box.pos = Vec(margin.x, yPos); - label->text = "Inputs (ADCs)"; - addChild(label); - yPos += labelHeight + margin.y; - } - - yPos += 5; - xPos = 10; - for (int i = 0; i < 4; i++) { - Port *port = Port::create(Vec(xPos, yPos), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + i); - addOutput(port); - Label *label = new Label(); - label->box.pos = Vec(xPos + 4, yPos + 28); - label->text = stringf("%d", i + 1); - addChild(label); - - xPos += 37 + margin.x; - } - yPos += 35 + margin.y; - - yPos += 5; - xPos = 10; - for (int i = 4; i < 8; i++) { - addOutput(Port::create(Vec(xPos, yPos), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + i)); - Label *label = new Label(); - label->box.pos = Vec(xPos + 4, yPos + 28); - label->text = stringf("%d", i + 1); - addChild(label); - - xPos += 37 + margin.x; - } - yPos += 35 + margin.y; + AudioInterfaceWidget(AudioInterface *module); +}; - AudioWidget *audioWidget = construct(); - audioWidget->audioIO = &module->audioIO; - addChild(audioWidget); - // Lights - addChild(ModuleLightWidget::create>(Vec(40, 20), module, AudioInterface::ACTIVE_LIGHT)); - } -}; +AudioInterfaceWidget::AudioInterfaceWidget(AudioInterface *module) : ModuleWidget(module) { + setPanel(SVG::load(assetGlobal("res/Core/AudioInterface.svg"))); + + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addInput(Port::create(mm2px(Vec(3.89433, 55.5308)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 0)); + addInput(Port::create(mm2px(Vec(15.4947, 55.5308)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 1)); + addInput(Port::create(mm2px(Vec(27.095, 55.5308)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 2)); + addInput(Port::create(mm2px(Vec(38.6953, 55.5308)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 3)); + addInput(Port::create(mm2px(Vec(3.89433, 70.1449)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 4)); + addInput(Port::create(mm2px(Vec(15.4947, 70.1449)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 5)); + addInput(Port::create(mm2px(Vec(27.095, 70.1449)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 6)); + addInput(Port::create(mm2px(Vec(38.6953, 70.1449)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 7)); + + addOutput(Port::create(mm2px(Vec(3.89295, 92.1439)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 0)); + addOutput(Port::create(mm2px(Vec(15.4933, 92.1439)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 1)); + addOutput(Port::create(mm2px(Vec(27.0936, 92.1439)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 2)); + addOutput(Port::create(mm2px(Vec(38.6939, 92.1439)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 3)); + addOutput(Port::create(mm2px(Vec(3.89433, 108.144)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 4)); + addOutput(Port::create(mm2px(Vec(15.4947, 108.144)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 5)); + addOutput(Port::create(mm2px(Vec(27.095, 108.144)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 6)); + addOutput(Port::create(mm2px(Vec(38.6953, 108.144)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 7)); + + Vec lightOffset = mm2px(Vec(7.21812, -0.1833)); + + addChild(ModuleLightWidget::create>(mm2px(Vec(3.89433, 55.5308)).plus(lightOffset), module, AudioInterface::INPUT_LIGHT + 0)); + addChild(ModuleLightWidget::create>(mm2px(Vec(15.4947, 55.5308)).plus(lightOffset), module, AudioInterface::INPUT_LIGHT + 1)); + addChild(ModuleLightWidget::create>(mm2px(Vec(27.095, 55.5308)).plus(lightOffset), module, AudioInterface::INPUT_LIGHT + 2)); + addChild(ModuleLightWidget::create>(mm2px(Vec(38.6953, 55.5308)).plus(lightOffset), module, AudioInterface::INPUT_LIGHT + 3)); + addChild(ModuleLightWidget::create>(mm2px(Vec(3.89433, 70.1449)).plus(lightOffset), module, AudioInterface::INPUT_LIGHT + 4)); + addChild(ModuleLightWidget::create>(mm2px(Vec(15.4947, 70.1449)).plus(lightOffset), module, AudioInterface::INPUT_LIGHT + 5)); + addChild(ModuleLightWidget::create>(mm2px(Vec(27.095, 70.1449)).plus(lightOffset), module, AudioInterface::INPUT_LIGHT + 6)); + addChild(ModuleLightWidget::create>(mm2px(Vec(38.6953, 70.1449)).plus(lightOffset), module, AudioInterface::INPUT_LIGHT + 7)); + + addChild(ModuleLightWidget::create>(mm2px(Vec(3.89295, 92.1439)).plus(lightOffset), module, AudioInterface::OUTPUT_LIGHT + 0)); + addChild(ModuleLightWidget::create>(mm2px(Vec(15.4933, 92.1439)).plus(lightOffset), module, AudioInterface::OUTPUT_LIGHT + 1)); + addChild(ModuleLightWidget::create>(mm2px(Vec(27.0936, 92.1439)).plus(lightOffset), module, AudioInterface::OUTPUT_LIGHT + 2)); + addChild(ModuleLightWidget::create>(mm2px(Vec(38.6939, 92.1439)).plus(lightOffset), module, AudioInterface::OUTPUT_LIGHT + 3)); + addChild(ModuleLightWidget::create>(mm2px(Vec(3.89433, 108.144)).plus(lightOffset), module, AudioInterface::OUTPUT_LIGHT + 4)); + addChild(ModuleLightWidget::create>(mm2px(Vec(15.4947, 108.144)).plus(lightOffset), module, AudioInterface::OUTPUT_LIGHT + 5)); + addChild(ModuleLightWidget::create>(mm2px(Vec(27.095, 108.144)).plus(lightOffset), module, AudioInterface::OUTPUT_LIGHT + 6)); + addChild(ModuleLightWidget::create>(mm2px(Vec(38.6953, 108.144)).plus(lightOffset), module, AudioInterface::OUTPUT_LIGHT + 7)); + + + AudioWidget *audioWidget = Widget::create(mm2px(Vec(3.401, 14.8373))); + audioWidget->box.size = mm2px(Vec(44, 28)); + audioWidget->audioIO = &module->audioIO; + addChild(audioWidget); +} -Model *modelAudioInterface = Model::create("Core", "AudioInterface", "Audio Interface", EXTERNAL_TAG); \ No newline at end of file +Model *modelAudioInterface = Model::create("Core", "AudioInterface", "Audio", EXTERNAL_TAG); \ No newline at end of file diff --git a/src/core/MidiToCV.cpp b/src/core/MidiToCV.cpp index 598fdb18..bdfed3fe 100644 --- a/src/core/MidiToCV.cpp +++ b/src/core/MidiToCV.cpp @@ -6,7 +6,7 @@ /* - * MidiToCvInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to + * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to * CV */ struct MidiValue { @@ -16,29 +16,34 @@ struct MidiValue { }; -struct MidiToCvInterface : Module { +struct MIDIToCVInterface : Module { enum ParamIds { - RESET_PARAM, NUM_PARAMS }; enum InputIds { NUM_INPUTS }; enum OutputIds { - PITCH_OUTPUT = 0, + CV_OUTPUT, GATE_OUTPUT, VELOCITY_OUTPUT, MOD_OUTPUT, - PITCHWHEEL_OUTPUT, - CHANNEL_AFTERTOUCH_OUTPUT, + PITCH_OUTPUT, + AFTERTOUCH_OUTPUT, + START_OUTPUT, + STOP_OUTPUT, + CONTINUE_OUTPUT, + CLOCK_OUTPUT, + CLOCK_2_OUTPUT, + CLOCK_HALF_OUTPUT, NUM_OUTPUTS }; enum LightIds { - ACTIVE_LIGHT, - RESET_LIGHT, NUM_LIGHTS }; + MidiInput midiIO; + MidiInputQueue midiInput; std::list notes; bool pedal = false; @@ -51,12 +56,12 @@ struct MidiToCvInterface : Module { SchmittTrigger resetTrigger; - MidiToCvInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { pitchWheel.val = 64; // pitchWheel.tSmooth.set(0, 0); } - ~MidiToCvInterface() { + ~MIDIToCVInterface() { }; void step() override; @@ -85,7 +90,7 @@ struct MidiToCvInterface : Module { }; /* -void MidiToCvInterface::resetMidi() { +void MIDIToCVInterface::resetMidi() { mod.val = 0; mod.tSmooth.set(0, 0); pitchWheel.val = 64; @@ -98,7 +103,7 @@ void MidiToCvInterface::resetMidi() { } */ -void MidiToCvInterface::step() { +void MIDIToCVInterface::step() { /* if (isPortOpen()) { std::vector message; @@ -138,12 +143,9 @@ void MidiToCvInterface::step() { outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; */ - - // Lights - lights[ACTIVE_LIGHT].value = midiInput.isActive() ? 1.0 : 0.0; } -void MidiToCvInterface::pressNote(int note) { +void MIDIToCVInterface::pressNote(int note) { // Remove existing similar note auto it = std::find(notes.begin(), notes.end(), note); if (it != notes.end()) @@ -154,7 +156,7 @@ void MidiToCvInterface::pressNote(int note) { gate = true; } -void MidiToCvInterface::releaseNote(int note) { +void MIDIToCVInterface::releaseNote(int note) { // Remove the note auto it = std::find(notes.begin(), notes.end(), note); if (it != notes.end()) @@ -174,7 +176,7 @@ void MidiToCvInterface::releaseNote(int note) { } } -void MidiToCvInterface::processMidi(std::vector msg) { +void MIDIToCVInterface::processMidi(std::vector msg) { /* int channel = msg[0] & 0xf; int status = (msg[0] >> 4) & 0xf; @@ -229,58 +231,34 @@ void MidiToCvInterface::processMidi(std::vector msg) { } -struct MidiToCvInterfaceWidget : ModuleWidget { - MidiToCvInterfaceWidget(MidiToCvInterface *module) : ModuleWidget(module) { - box.size = Vec(15 * 9, 380); - - { - Panel *panel = new LightPanel(); - panel->box.size = box.size; - addChild(panel); - } - - float margin = 5; - float labelHeight = 15; - float yPos = margin; - float yGap = 35; - - addChild(Widget::create(Vec(15, 0))); - addChild(Widget::create(Vec(box.size.x - 30, 0))); - addChild(Widget::create(Vec(15, 365))); - addChild(Widget::create(Vec(box.size.x - 30, 365))); - - { - Label *label = new Label(); - label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); - label->text = "MIDI to CV"; - addChild(label); - yPos = labelHeight * 2; - } - - addParam(ParamWidget::create(Vec(7 * 15, labelHeight), module, MidiToCvInterface::RESET_PARAM, 0.0, 1.0, 0.0)); - addChild(ModuleLightWidget::create>(Vec(7 * 15 + 5, labelHeight + 5), module, MidiToCvInterface::RESET_LIGHT)); - - std::string labels[MidiToCvInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", "Pitch Wheel", "Aftertouch"}; - - for (int i = 0; i < MidiToCvInterface::NUM_OUTPUTS; i++) { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = labels[i]; - addChild(label); - - addOutput(Port::create(Vec(15 * 6, yPos - 5), Port::OUTPUT, module, i)); - - yPos += yGap + margin; - } - - MidiWidget *midiWidget = construct(); - midiWidget->midiIO = &module->midiInput; +struct MIDIToCVInterfaceWidget : ModuleWidget { + MIDIToCVInterfaceWidget(MIDIToCVInterface *module) : ModuleWidget(module) { + setPanel(SVG::load(assetGlobal("res/Core/MIDIToCVInterface.svg"))); + + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addOutput(Port::create(mm2px(Vec(4.61505, 60.1445)), Port::OUTPUT, module, MIDIToCVInterface::CV_OUTPUT)); + addOutput(Port::create(mm2px(Vec(16.214, 60.1445)), Port::OUTPUT, module, MIDIToCVInterface::GATE_OUTPUT)); + addOutput(Port::create(mm2px(Vec(27.8143, 60.1445)), Port::OUTPUT, module, MIDIToCVInterface::VELOCITY_OUTPUT)); + addOutput(Port::create(mm2px(Vec(4.61505, 76.1449)), Port::OUTPUT, module, MIDIToCVInterface::MOD_OUTPUT)); + addOutput(Port::create(mm2px(Vec(16.214, 76.1449)), Port::OUTPUT, module, MIDIToCVInterface::PITCH_OUTPUT)); + addOutput(Port::create(mm2px(Vec(27.8143, 76.1449)), Port::OUTPUT, module, MIDIToCVInterface::AFTERTOUCH_OUTPUT)); + addOutput(Port::create(mm2px(Vec(4.61505, 92.1439)), Port::OUTPUT, module, MIDIToCVInterface::START_OUTPUT)); + addOutput(Port::create(mm2px(Vec(16.214, 92.1439)), Port::OUTPUT, module, MIDIToCVInterface::STOP_OUTPUT)); + addOutput(Port::create(mm2px(Vec(27.8143, 92.1439)), Port::OUTPUT, module, MIDIToCVInterface::CONTINUE_OUTPUT)); + addOutput(Port::create(mm2px(Vec(4.61505, 108.144)), Port::OUTPUT, module, MIDIToCVInterface::CLOCK_OUTPUT)); + addOutput(Port::create(mm2px(Vec(16.214, 108.144)), Port::OUTPUT, module, MIDIToCVInterface::CLOCK_2_OUTPUT)); + addOutput(Port::create(mm2px(Vec(27.8143, 108.144)), Port::OUTPUT, module, MIDIToCVInterface::CLOCK_HALF_OUTPUT)); + + MidiWidget *midiWidget = Widget::create(mm2px(Vec(3.41891, 14.8373))); + midiWidget->box.size = mm2px(Vec(33.840, 28)); + midiWidget->midiIO = &module->midiIO; addChild(midiWidget); - - // Lights - addChild(ModuleLightWidget::create>(Vec(40, 20), module, MidiToCvInterface::ACTIVE_LIGHT)); } }; -Model *modelMidiToCvInterface = Model::create("Core", "MIDIToCVInterface", "MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG); +Model *modelMidiToCvInterface = Model::create("Core", "MIDIToCVInterface", "MIDI-1", MIDI_TAG, EXTERNAL_TAG); diff --git a/src/core/Notes.cpp b/src/core/Notes.cpp index dfe5c611..a113f265 100644 --- a/src/core/Notes.cpp +++ b/src/core/Notes.cpp @@ -8,22 +8,15 @@ struct NotesWidget : ModuleWidget { TextField *textField; NotesWidget(Module *module) : ModuleWidget(module) { - box.size = Vec(RACK_GRID_WIDTH * 18, RACK_GRID_HEIGHT); - - { - Panel *panel = new LightPanel(); - panel->box.size = box.size; - addChild(panel); - } - - addChild(Widget::create(Vec(15, 0))); - addChild(Widget::create(Vec(15, 365))); - addChild(Widget::create(Vec(box.size.x - 30, 0))); - addChild(Widget::create(Vec(box.size.x - 30, 365))); - - textField = new TextField(); - textField->box.pos = Vec(15, 15); - textField->box.size = box.size.minus(Vec(30, 30)); + setPanel(SVG::load(assetGlobal("res/Core/Notes.svg"))); + + addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + textField = Widget::create(mm2px(Vec(3.39962, 14.8373))); + textField->box.size = mm2px(Vec(74.480, 102.753)); textField->multiline = true; addChild(textField); } diff --git a/src/engine.cpp b/src/engine.cpp index 151ad926..9976373d 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -72,15 +72,15 @@ static void engineStep() { if (smoothModule) { float value = smoothModule->params[smoothParamId].value; const float lambda = 60.0; // decay rate is 1 graphics frame - const float snap = 0.0001; float delta = smoothValue - value; - if (fabsf(delta) < snap) { + float newValue = value + delta * lambda * sampleTime; + if (value == newValue) { + // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats) smoothModule->params[smoothParamId].value = smoothValue; smoothModule = NULL; } else { - value += delta * lambda * sampleTime; - smoothModule->params[smoothParamId].value = value; + smoothModule->params[smoothParamId].value = newValue; } } diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index f093ac1e..cf17fcea 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -25,10 +25,22 @@ void TextField::draw(NVGcontext *vg) { } void TextField::onMouseDown(EventMouseDown &e) { - end = begin = bndTextFieldTextPosition(gVg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), e.pos.x, e.pos.y); + dragPos = getTextPosition(e.pos); + begin = end = dragPos; OpaqueWidget::onMouseDown(e); } +void TextField::onMouseMove(EventMouseMove &e) { + if (this == gDraggedWidget) { + int pos = getTextPosition(e.pos); + if (pos != dragPos) { + begin = min(dragPos, pos); + end = max(dragPos, pos); + } + } + OpaqueWidget::onMouseMove(e); +} + void TextField::onFocus(EventFocus &e) { begin = 0; end = text.size(); @@ -131,5 +143,9 @@ void TextField::insertText(std::string newText) { onTextChange(); } +int TextField::getTextPosition(Vec mousePos) { + return bndTextFieldTextPosition(gVg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y); +} + } // namespace rack diff --git a/src/window.cpp b/src/window.cpp index f65c00a0..f600a29d 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -382,7 +382,7 @@ void windowInit() { assert(gFramebufferVg); // Set up Blendish - gGuiFont = Font::load(assetGlobal("res/DejaVuSans.ttf")); + gGuiFont = Font::load(assetGlobal("res/fonts/DejaVuSans.ttf")); bndSetFont(gGuiFont->handle); // bndSetIconImage(loadImage(assetGlobal("res/icons.png")));