| @@ -135,7 +135,7 @@ Your plugin needs to have a clear purpose for manipulating other modules and wir | |||||
| */ | */ | ||||
| extern std::vector<Module*> gModules; | extern std::vector<Module*> gModules; | ||||
| extern std::vector<Wire*> gWires; | extern std::vector<Wire*> gWires; | ||||
| extern bool gCpuMeters; | |||||
| extern bool gPowerMeter; | |||||
| } // namespace rack | } // namespace rack | ||||
| @@ -146,7 +146,6 @@ struct StringCaseInsensitiveCompare { | |||||
| } | } | ||||
| }; | }; | ||||
| //////////////////// | //////////////////// | ||||
| // Operating-system specific utilities | // Operating-system specific utilities | ||||
| // system.cpp | // system.cpp | ||||
| @@ -1,264 +0,0 @@ | |||||
| #if 0 | |||||
| #include <list> | |||||
| #include <algorithm> | |||||
| #include "core.hpp" | |||||
| #include "MidiIO.hpp" | |||||
| using namespace rack; | |||||
| /** | |||||
| * MidiIO implements the shared functionality of all midi modules, namely: | |||||
| * + Channel Selection (including helper for storing json) | |||||
| * + Interface Selection (including helper for storing json) | |||||
| * + rtMidi initialisation (input or output) | |||||
| */ | |||||
| MidiIO::MidiIO(bool isOut) { | |||||
| channel = -1; | |||||
| this->isOut = isOut; | |||||
| // TODO | |||||
| // Support MIDI out | |||||
| assert(!isOut); | |||||
| }; | |||||
| void MidiIO::setChannel(int channel) { | |||||
| this->channel = channel; | |||||
| } | |||||
| std::unordered_map<std::string, MidiInWrapper *> MidiIO::midiInMap = {}; | |||||
| json_t *MidiIO::addBaseJson(json_t *rootJ) { | |||||
| if (deviceName != "") { | |||||
| json_object_set_new(rootJ, "interfaceName", json_string(deviceName.c_str())); | |||||
| json_object_set_new(rootJ, "channel", json_integer(channel)); | |||||
| } | |||||
| return rootJ; | |||||
| } | |||||
| void MidiIO::baseFromJson(json_t *rootJ) { | |||||
| json_t *portNameJ = json_object_get(rootJ, "interfaceName"); | |||||
| if (portNameJ) { | |||||
| openDevice(json_string_value(portNameJ)); | |||||
| } | |||||
| json_t *channelJ = json_object_get(rootJ, "channel"); | |||||
| if (channelJ) { | |||||
| setChannel(json_integer_value(channelJ)); | |||||
| } | |||||
| } | |||||
| std::vector<std::string> MidiIO::getDevices() { | |||||
| std::vector<std::string> names = {}; | |||||
| if (isOut) { | |||||
| // TODO | |||||
| return names; | |||||
| } | |||||
| RtMidiIn *m; | |||||
| try { | |||||
| m = new RtMidiIn(); | |||||
| } | |||||
| catch (RtMidiError &error) { | |||||
| warn("Failed to create RtMidiIn: %s", error.getMessage().c_str()); | |||||
| return names; | |||||
| } | |||||
| for (unsigned int i = 0; i < m->getPortCount(); i++) { | |||||
| names.push_back(m->getPortName(i)); | |||||
| } | |||||
| if (!isPortOpen()) | |||||
| delete (m); | |||||
| return names; | |||||
| } | |||||
| void MidiIO::openDevice(std::string deviceName) { | |||||
| if (this->id > 0 || this->deviceName != "") { | |||||
| close(); | |||||
| } | |||||
| MidiInWrapper *mw = midiInMap[deviceName]; | |||||
| if (!mw) { | |||||
| try { | |||||
| mw = new MidiInWrapper(); | |||||
| midiInMap[deviceName] = mw; | |||||
| for (unsigned int i = 0; i < mw->getPortCount(); i++) { | |||||
| if (deviceName == mw->getPortName(i)) { | |||||
| mw->openPort(i); | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (!mw->isPortOpen()) { | |||||
| warn("Failed to create RtMidiIn: No such device %s", deviceName.c_str()); | |||||
| this->deviceName = ""; | |||||
| this->id = -1; | |||||
| return; | |||||
| } | |||||
| } | |||||
| catch (RtMidiError &error) { | |||||
| warn("Failed to create RtMidiIn: %s", error.getMessage().c_str()); | |||||
| this->deviceName = ""; | |||||
| this->id = -1; | |||||
| return; | |||||
| } | |||||
| } | |||||
| this->deviceName = deviceName; | |||||
| id = midiInMap[deviceName]->add(); | |||||
| onDeviceChange(); | |||||
| } | |||||
| void MidiIO::setIgnores(bool ignoreSysex, bool ignoreTime, bool ignoreSense) { | |||||
| bool sy = true, ti = true, se = true; | |||||
| midiInMap[deviceName]->ignoresMap[id].midiSysex = ignoreSysex; | |||||
| midiInMap[deviceName]->ignoresMap[id].midiTime = ignoreTime; | |||||
| midiInMap[deviceName]->ignoresMap[id].midiSense = ignoreSense; | |||||
| for (auto kv : midiInMap[deviceName]->ignoresMap) { | |||||
| sy = sy && kv.second.midiSysex; | |||||
| ti = ti && kv.second.midiTime; | |||||
| se = se && kv.second.midiSense; | |||||
| } | |||||
| midiInMap[deviceName]->ignoreTypes(se, ti, se); | |||||
| } | |||||
| std::string MidiIO::getDeviceName() { | |||||
| return deviceName; | |||||
| } | |||||
| double MidiIO::getMessage(std::vector<unsigned char> *msg) { | |||||
| MidiMessage next_msg = MidiMessage(); | |||||
| MidiInWrapper *mw = midiInMap[deviceName]; | |||||
| if (!mw) { | |||||
| warn("Device not opened!: %s", deviceName.c_str()); | |||||
| return 0; | |||||
| } | |||||
| next_msg.timeStamp = mw->getMessage(&next_msg.bytes); | |||||
| if (next_msg.bytes.size() > 0) { | |||||
| for (auto &kv : mw->idMessagesMap) { | |||||
| kv.second.push_back(next_msg); | |||||
| } | |||||
| } | |||||
| if (mw->idMessagesMap[id].size() > 0) { | |||||
| next_msg = mw->idMessagesMap[id].front(); | |||||
| mw->idMessagesMap[id].pop_front(); | |||||
| } | |||||
| *msg = next_msg.bytes; | |||||
| return next_msg.timeStamp; | |||||
| } | |||||
| bool MidiIO::isPortOpen() { | |||||
| return id > 0; | |||||
| } | |||||
| void MidiIO::close() { | |||||
| MidiInWrapper *mw = midiInMap[deviceName]; | |||||
| if (!mw || id < 0) { | |||||
| //warn("Trying to close already closed device!"); | |||||
| return; | |||||
| } | |||||
| setIgnores(); // reset ignore types for this instance | |||||
| mw->erase(id); | |||||
| if (mw->idMessagesMap.size() == 0) { | |||||
| mw->closePort(); | |||||
| midiInMap.erase(deviceName); | |||||
| delete (mw); | |||||
| } | |||||
| id = -1; | |||||
| deviceName = ""; | |||||
| } | |||||
| void MidiItem::onAction(EventAction &e) { | |||||
| midiModule->resetMidi(); // reset Midi values | |||||
| midiModule->openDevice(text); | |||||
| } | |||||
| void MidiChoice::onAction(EventAction &e) { | |||||
| Menu *menu = gScene->createMenu(); | |||||
| menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
| menu->box.size.x = box.size.x; | |||||
| { | |||||
| MidiItem *midiItem = new MidiItem(); | |||||
| midiItem->midiModule = midiModule; | |||||
| midiItem->text = ""; | |||||
| menu->addChild(midiItem); | |||||
| } | |||||
| std::vector<std::string> deviceNames = midiModule->getDevices(); | |||||
| for (unsigned int i = 0; i < deviceNames.size(); i++) { | |||||
| MidiItem *midiItem = new MidiItem(); | |||||
| midiItem->midiModule = midiModule; | |||||
| midiItem->text = deviceNames[i]; | |||||
| menu->addChild(midiItem); | |||||
| } | |||||
| } | |||||
| void MidiChoice::step() { | |||||
| if (midiModule->getDeviceName() == "") { | |||||
| text = "No Device"; | |||||
| return; | |||||
| } | |||||
| std::string name = midiModule->getDeviceName(); | |||||
| text = ellipsize(name, 15); | |||||
| } | |||||
| void ChannelItem::onAction(EventAction &e) { | |||||
| midiModule->resetMidi(); // reset Midi values | |||||
| midiModule->setChannel(channel); | |||||
| } | |||||
| void ChannelChoice::onAction(EventAction &e) { | |||||
| Menu *menu = gScene->createMenu(); | |||||
| menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); | |||||
| menu->box.size.x = box.size.x; | |||||
| { | |||||
| ChannelItem *channelItem = new ChannelItem(); | |||||
| channelItem->midiModule = midiModule; | |||||
| channelItem->channel = -1; | |||||
| channelItem->text = "All"; | |||||
| menu->addChild(channelItem); | |||||
| } | |||||
| for (int channel = 0; channel < 16; channel++) { | |||||
| ChannelItem *channelItem = new ChannelItem(); | |||||
| channelItem->midiModule = midiModule; | |||||
| channelItem->channel = channel; | |||||
| channelItem->text = stringf("%d", channel + 1); | |||||
| menu->addChild(channelItem); | |||||
| } | |||||
| } | |||||
| void ChannelChoice::step() { | |||||
| text = (midiModule->channel >= 0) ? stringf("%d", midiModule->channel + 1) : "All"; | |||||
| } | |||||
| #endif | |||||
| @@ -1,206 +0,0 @@ | |||||
| #if 0 | |||||
| #include <unordered_map> | |||||
| #include "rack.hpp" | |||||
| #pragma GCC diagnostic push | |||||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | |||||
| #include "rtmidi/RtMidi.h" | |||||
| #pragma GCC diagnostic pop | |||||
| using namespace rack; | |||||
| struct IgnoreFlags { | |||||
| bool midiSysex = true; | |||||
| bool midiTime = true; | |||||
| bool midiSense = true; | |||||
| }; | |||||
| struct MidiMessage { | |||||
| std::vector<unsigned char> bytes; | |||||
| double timeStamp; | |||||
| MidiMessage() : bytes(0), timeStamp(0.0) {}; | |||||
| }; | |||||
| /** | |||||
| * This class allows to use one instance of rtMidiIn with | |||||
| * multiple modules. A MidiIn port will be opened only once while multiple | |||||
| * instances can use it simultaniously, each receiving all its incoming messages. | |||||
| */ | |||||
| struct MidiInWrapper : RtMidiIn { | |||||
| std::unordered_map<int, std::list<MidiMessage>> idMessagesMap; | |||||
| std::unordered_map<int, IgnoreFlags> ignoresMap; | |||||
| int uid_c = 0; | |||||
| MidiInWrapper() : RtMidiIn() { | |||||
| idMessagesMap = {}; | |||||
| }; | |||||
| int add() { | |||||
| int id = ++uid_c; | |||||
| idMessagesMap[id] = {}; | |||||
| ignoresMap[id] = IgnoreFlags(); | |||||
| return id; | |||||
| } | |||||
| void erase(int id) { | |||||
| idMessagesMap.erase(id); | |||||
| ignoresMap.erase(id); | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * Note: MidiIO is not thread safe which might become | |||||
| * important in the future | |||||
| */ | |||||
| struct MidiIO { | |||||
| private: | |||||
| static std::unordered_map<std::string, MidiInWrapper *> midiInMap; | |||||
| /* TODO: add for midi out*/ | |||||
| int id = -1; | |||||
| std::string deviceName = ""; | |||||
| bool isOut = false; | |||||
| public: | |||||
| int channel; | |||||
| MidiIO(bool isOut = false); | |||||
| ~MidiIO() { | |||||
| close(); | |||||
| } | |||||
| std::vector<std::string> getDevices(); | |||||
| void openDevice(std::string deviceName); | |||||
| void setIgnores(bool ignoreSysex = true, bool ignoreTime = true, bool ignoreSense = true); | |||||
| std::string getDeviceName(); | |||||
| void setChannel(int channel); | |||||
| double getMessage(std::vector<unsigned char> *msg); | |||||
| json_t *addBaseJson(json_t *rootJ); | |||||
| void baseFromJson(json_t *rootJ); | |||||
| bool isPortOpen(); | |||||
| void close(); | |||||
| /* called when midi port is set */ | |||||
| virtual void resetMidi() {} | |||||
| /* called if a user switches or sets the device (and after this device is initialised)*/ | |||||
| virtual void onDeviceChange() {} | |||||
| }; | |||||
| struct TransitionSmoother { | |||||
| enum TransitionFunction { | |||||
| SMOOTHSTEP, | |||||
| EXP, | |||||
| LIN, | |||||
| }; | |||||
| enum TransitionMode { | |||||
| DELTA, | |||||
| CONST, | |||||
| }; | |||||
| float start; | |||||
| float end; | |||||
| float x; | |||||
| float delta; | |||||
| float step; | |||||
| TransitionFunction t; | |||||
| void set(float start, float end, int l = 1500, TransitionFunction t = LIN, TransitionMode m = DELTA, bool reset = true) { | |||||
| this->start = start; | |||||
| this->end = end; | |||||
| this->delta = end - start; | |||||
| this->t = t; | |||||
| if (reset || x >= 1) { | |||||
| this->x = 0; | |||||
| } | |||||
| switch (m) { | |||||
| case DELTA: | |||||
| /* If the change is smaller, the transition phase is longer */ | |||||
| this->step = delta > 0 ? delta / l : -delta / l; | |||||
| break; | |||||
| case CONST: | |||||
| this->step = 1.0 / l; | |||||
| break; | |||||
| } | |||||
| } | |||||
| float next() { | |||||
| float next = start; | |||||
| x += step; | |||||
| if (x >= 1) | |||||
| return end; | |||||
| switch (t) { | |||||
| case SMOOTHSTEP: | |||||
| next += delta * x * x * (3 - 2 * x); | |||||
| break; | |||||
| case EXP: | |||||
| next += delta * x * x; | |||||
| break; | |||||
| case LIN: | |||||
| next += delta * x; | |||||
| break; | |||||
| } | |||||
| if ((delta > 0 && next > end) || (delta <= 0 && next < end)) | |||||
| return end; | |||||
| return next;; | |||||
| } | |||||
| }; | |||||
| ////////////////////// | |||||
| // MIDI module widgets | |||||
| ////////////////////// | |||||
| struct MidiItem : MenuItem { | |||||
| MidiIO *midiModule; | |||||
| void onAction(EventAction &e) override; | |||||
| }; | |||||
| struct MidiChoice : ChoiceButton { | |||||
| MidiIO *midiModule; | |||||
| void step() override; | |||||
| void onAction(EventAction &e) override; | |||||
| }; | |||||
| struct ChannelItem : MenuItem { | |||||
| MidiIO *midiModule; | |||||
| int channel; | |||||
| void onAction(EventAction &e) override; | |||||
| }; | |||||
| struct ChannelChoice : ChoiceButton { | |||||
| MidiIO *midiModule; | |||||
| void step() override; | |||||
| void onAction(EventAction &e) override; | |||||
| }; | |||||
| #endif | |||||
| @@ -190,7 +190,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
| Widget::draw(vg); | Widget::draw(vg); | ||||
| // CPU meter | // CPU meter | ||||
| if (module && gCpuMeters) { | |||||
| if (module && gPowerMeter) { | |||||
| nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
| nvgRect(vg, | nvgRect(vg, | ||||
| 0, box.size.y - 20, | 0, box.size.y - 20, | ||||
| @@ -165,7 +165,7 @@ void RackWidget::revert() { | |||||
| } | } | ||||
| void RackWidget::disconnect() { | void RackWidget::disconnect() { | ||||
| if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Clear all patch cables?")) | |||||
| if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Remove all patch cables?")) | |||||
| return; | return; | ||||
| for (Widget *w : moduleContainer->children) { | for (Widget *w : moduleContainer->children) { | ||||
| @@ -89,13 +89,13 @@ struct DisconnectCablesButton : TooltipIconButton { | |||||
| } | } | ||||
| }; | }; | ||||
| struct MeterButton : TooltipIconButton { | |||||
| MeterButton() { | |||||
| struct PowerMeterButton : TooltipIconButton { | |||||
| PowerMeterButton() { | |||||
| setSVG(SVG::load(assetGlobal("res/icons/noun_305536_cc.svg"))); | setSVG(SVG::load(assetGlobal("res/icons/noun_305536_cc.svg"))); | ||||
| tooltipText = "Toggle power meter (see manual for explanation)"; | tooltipText = "Toggle power meter (see manual for explanation)"; | ||||
| } | } | ||||
| void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
| gCpuMeters ^= true; | |||||
| gPowerMeter ^= true; | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -175,7 +175,7 @@ Toolbar::Toolbar() { | |||||
| layout->addChild(wireTensionSlider); | layout->addChild(wireTensionSlider); | ||||
| layout->addChild(new SampleRateButton()); | layout->addChild(new SampleRateButton()); | ||||
| layout->addChild(new MeterButton()); | |||||
| layout->addChild(new PowerMeterButton()); | |||||
| struct ZoomSlider : Slider { | struct ZoomSlider : Slider { | ||||
| void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
| @@ -17,7 +17,7 @@ namespace rack { | |||||
| bool gPaused = false; | bool gPaused = false; | ||||
| std::vector<Module*> gModules; | std::vector<Module*> gModules; | ||||
| std::vector<Wire*> gWires; | std::vector<Wire*> gWires; | ||||
| bool gCpuMeters = false; | |||||
| bool gPowerMeter = false; | |||||
| static bool running = false; | static bool running = false; | ||||
| static float sampleRate = 44100.f; | static float sampleRate = 44100.f; | ||||
| @@ -90,7 +90,7 @@ static void engineStep() { | |||||
| // Step modules | // Step modules | ||||
| for (Module *module : gModules) { | for (Module *module : gModules) { | ||||
| std::chrono::high_resolution_clock::time_point startTime; | std::chrono::high_resolution_clock::time_point startTime; | ||||
| if (gCpuMeters) { | |||||
| if (gPowerMeter) { | |||||
| startTime = std::chrono::high_resolution_clock::now(); | startTime = std::chrono::high_resolution_clock::now(); | ||||
| module->step(); | module->step(); | ||||
| @@ -67,6 +67,9 @@ static json_t *settingsToJson() { | |||||
| // moduleBrowser | // moduleBrowser | ||||
| json_object_set_new(rootJ, "moduleBrowser", appModuleBrowserToJson()); | json_object_set_new(rootJ, "moduleBrowser", appModuleBrowserToJson()); | ||||
| // powerMeter | |||||
| json_object_set_new(rootJ, "powerMeter", json_boolean(gPowerMeter)); | |||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| @@ -132,9 +135,14 @@ static void settingsFromJson(json_t *rootJ) { | |||||
| skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); | skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); | ||||
| // moduleBrowser | // moduleBrowser | ||||
| json_t * moduleBrowserJ = json_object_get(rootJ, "moduleBrowser"); | |||||
| json_t *moduleBrowserJ = json_object_get(rootJ, "moduleBrowser"); | |||||
| if (moduleBrowserJ) | if (moduleBrowserJ) | ||||
| appModuleBrowserFromJson(moduleBrowserJ); | appModuleBrowserFromJson(moduleBrowserJ); | ||||
| // powerMeter | |||||
| json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); | |||||
| if (powerMeterJ) | |||||
| gPowerMeter = json_boolean_value(powerMeterJ); | |||||
| } | } | ||||