@@ -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); | |||||
} | } | ||||