| @@ -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<Wire*> gWires; | |||
| extern bool gCpuMeters; | |||
| extern bool gPowerMeter; | |||
| } // namespace rack | |||
| @@ -146,7 +146,6 @@ struct StringCaseInsensitiveCompare { | |||
| } | |||
| }; | |||
| //////////////////// | |||
| // Operating-system specific utilities | |||
| // 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); | |||
| // CPU meter | |||
| if (module && gCpuMeters) { | |||
| if (module && gPowerMeter) { | |||
| nvgBeginPath(vg); | |||
| nvgRect(vg, | |||
| 0, box.size.y - 20, | |||
| @@ -165,7 +165,7 @@ void RackWidget::revert() { | |||
| } | |||
| 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; | |||
| 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"))); | |||
| tooltipText = "Toggle power meter (see manual for explanation)"; | |||
| } | |||
| void onAction(EventAction &e) override { | |||
| gCpuMeters ^= true; | |||
| gPowerMeter ^= true; | |||
| } | |||
| }; | |||
| @@ -175,7 +175,7 @@ Toolbar::Toolbar() { | |||
| layout->addChild(wireTensionSlider); | |||
| layout->addChild(new SampleRateButton()); | |||
| layout->addChild(new MeterButton()); | |||
| layout->addChild(new PowerMeterButton()); | |||
| struct ZoomSlider : Slider { | |||
| void onAction(EventAction &e) override { | |||
| @@ -17,7 +17,7 @@ namespace rack { | |||
| bool gPaused = false; | |||
| std::vector<Module*> gModules; | |||
| std::vector<Wire*> gWires; | |||
| bool gCpuMeters = false; | |||
| bool gPowerMeter = false; | |||
| static bool running = false; | |||
| static float sampleRate = 44100.f; | |||
| @@ -90,7 +90,7 @@ static void engineStep() { | |||
| // Step modules | |||
| for (Module *module : gModules) { | |||
| std::chrono::high_resolution_clock::time_point startTime; | |||
| if (gCpuMeters) { | |||
| if (gPowerMeter) { | |||
| startTime = std::chrono::high_resolution_clock::now(); | |||
| module->step(); | |||
| @@ -67,6 +67,9 @@ static json_t *settingsToJson() { | |||
| // moduleBrowser | |||
| json_object_set_new(rootJ, "moduleBrowser", appModuleBrowserToJson()); | |||
| // powerMeter | |||
| json_object_set_new(rootJ, "powerMeter", json_boolean(gPowerMeter)); | |||
| return rootJ; | |||
| } | |||
| @@ -132,9 +135,14 @@ static void settingsFromJson(json_t *rootJ) { | |||
| skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); | |||
| // moduleBrowser | |||
| json_t * moduleBrowserJ = json_object_get(rootJ, "moduleBrowser"); | |||
| json_t *moduleBrowserJ = json_object_get(rootJ, "moduleBrowser"); | |||
| if (moduleBrowserJ) | |||
| appModuleBrowserFromJson(moduleBrowserJ); | |||
| // powerMeter | |||
| json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); | |||
| if (powerMeterJ) | |||
| gPowerMeter = json_boolean_value(powerMeterJ); | |||
| } | |||