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