meterpull/1639/head
| @@ -1 +1 @@ | |||
| Subproject commit 9a74da4db5ac74083e444010d75114658581b9c7 | |||
| Subproject commit 06c1f0f3bb041d69a73bb74067d063a700215b0e | |||
| @@ -5,18 +5,17 @@ | |||
| namespace rack { | |||
| // TODO Change driver and port number to something less common | |||
| /** Driver ID in AudioIO and MidiIO */ | |||
| #define BRIDGE_DRIVER -12512 | |||
| #define BRIDGE_HOST "127.0.0.1" | |||
| #define BRIDGE_PORT 12512 | |||
| #define BRIDGE_NUM_PORTS 16 | |||
| const int BRIDGE_DRIVER = -12512; | |||
| const char* const BRIDGE_HOST = "127.0.0.1"; | |||
| const int BRIDGE_PORT = 12512; | |||
| const int BRIDGE_NUM_PORTS = 16; | |||
| /** Number of VST/AU automation parameters */ | |||
| #define BRIDGE_NUM_PARAMS 16 | |||
| const int BRIDGE_NUM_PARAMS = 16; | |||
| /** An arbitrary number which prevents connection from other protocols (like WebSockets) and old Bridge versions */ | |||
| #define BRIDGE_HELLO 0xff00fefd | |||
| #define BRIDGE_INPUTS 8 | |||
| #define BRIDGE_OUTPUTS 8 | |||
| const uint32_t BRIDGE_HELLO = 0xff00fefd; | |||
| const int BRIDGE_INPUTS = 8; | |||
| const int BRIDGE_OUTPUTS = 8; | |||
| /** All commands are called from the client and served by the server | |||
| @@ -127,6 +127,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; | |||
| } // namespace rack | |||
| @@ -0,0 +1,13 @@ | |||
| #pragma once | |||
| namespace rack { | |||
| const int GAMEPAD_DRIVER = -10; | |||
| void gamepadStep(); | |||
| } // namespace rack | |||
| @@ -1,17 +1,11 @@ | |||
| #pragma once | |||
| #include "util/common.hpp" | |||
| #include <queue> | |||
| #include <vector> | |||
| #include <queue> | |||
| #include <set> | |||
| #include <jansson.h> | |||
| #pragma GCC diagnostic push | |||
| #ifndef __clang__ | |||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | |||
| #endif | |||
| #include "rtmidi/RtMidi.h" | |||
| #pragma GCC diagnostic pop | |||
| namespace rack { | |||
| @@ -35,6 +29,13 @@ struct MidiMessage { | |||
| } | |||
| }; | |||
| //////////////////// | |||
| // MidiIO | |||
| //////////////////// | |||
| struct MidiInputDevice; | |||
| struct MidiOutputDevice; | |||
| struct MidiIO { | |||
| int driver = -1; | |||
| @@ -45,18 +46,16 @@ struct MidiIO { | |||
| Zero indexed. | |||
| */ | |||
| int channel = -1; | |||
| RtMidi *rtMidi = NULL; | |||
| /** Cached */ | |||
| std::string deviceName; | |||
| virtual ~MidiIO() {} | |||
| std::vector<int> getDrivers(); | |||
| std::string getDriverName(int driver); | |||
| virtual void setDriver(int driver) {} | |||
| void setDriver(int driver); | |||
| int getDeviceCount(); | |||
| std::string getDeviceName(int device); | |||
| virtual void setDevice(int device) {} | |||
| virtual int getDeviceCount() = 0; | |||
| virtual std::string getDeviceName(int device) = 0; | |||
| virtual void setDevice(int device) = 0; | |||
| std::string getChannelName(int channel); | |||
| json_t *toJson(); | |||
| @@ -65,17 +64,22 @@ struct MidiIO { | |||
| struct MidiInput : MidiIO { | |||
| RtMidiIn *rtMidiIn = NULL; | |||
| MidiInputDevice *midiInputDevice = NULL; | |||
| MidiInput(); | |||
| ~MidiInput(); | |||
| void setDriver(int driver) override; | |||
| int getDeviceCount() override; | |||
| std::string getDeviceName(int device) override; | |||
| void setDevice(int device) override; | |||
| virtual void onMessage(MidiMessage message) {} | |||
| }; | |||
| struct MidiInputQueue : MidiInput { | |||
| int queueSize = 8192; | |||
| // TODO Switch to RingBuffer | |||
| std::queue<MidiMessage> queue; | |||
| void onMessage(MidiMessage message) override; | |||
| /** If a MidiMessage is available, writes `message` and return true */ | |||
| @@ -84,12 +88,31 @@ struct MidiInputQueue : MidiInput { | |||
| struct MidiOutput : MidiIO { | |||
| RtMidiOut *rtMidiOut = NULL; | |||
| MidiOutputDevice *midiOutputDevice = NULL; | |||
| MidiOutput(); | |||
| ~MidiOutput(); | |||
| void setDriver(int driver) override; | |||
| void setDevice(int device) override; | |||
| }; | |||
| //////////////////// | |||
| // MidiIODevice | |||
| //////////////////// | |||
| struct MidiIODevice { | |||
| virtual ~MidiIODevice() {} | |||
| }; | |||
| struct MidiInputDevice : MidiIODevice { | |||
| std::set<MidiInput*> subscribed; | |||
| void subscribe(MidiInput *midiInput); | |||
| /** Deletes itself if nothing is subscribed */ | |||
| void unsubscribe(MidiInput *midiInput); | |||
| void onMessage(MidiMessage message); | |||
| }; | |||
| struct MidiOutputDevice : MidiIODevice { | |||
| // TODO | |||
| }; | |||
| } // namespace rack | |||
| @@ -0,0 +1,33 @@ | |||
| #pragma once | |||
| #include "util/common.hpp" | |||
| #include "midi.hpp" | |||
| #pragma GCC diagnostic push | |||
| #ifndef __clang__ | |||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | |||
| #endif | |||
| #include "rtmidi/RtMidi.h" | |||
| #pragma GCC diagnostic pop | |||
| namespace rack { | |||
| struct RtMidiInputDevice : MidiInputDevice { | |||
| RtMidiIn *rtMidiIn; | |||
| /** Cached */ | |||
| std::string deviceName; | |||
| RtMidiInputDevice(int driver, int device); | |||
| ~RtMidiInputDevice(); | |||
| }; | |||
| std::vector<int> rtmidiGetDrivers(); | |||
| int rtmidiGetDeviceCount(int driver); | |||
| std::string rtmidiGetDeviceName(int driver, int device); | |||
| RtMidiInputDevice *rtmidiGetDevice(int driver, int device); | |||
| } // namespace rack | |||
| @@ -187,6 +187,18 @@ void ModuleWidget::randomize() { | |||
| void ModuleWidget::draw(NVGcontext *vg) { | |||
| nvgScissor(vg, 0, 0, box.size.x, box.size.y); | |||
| Widget::draw(vg); | |||
| // CPU meter | |||
| if (gCpuMeters && module) { | |||
| float p = clamp(module->cpuTime, 0.f, 1.f); | |||
| nvgBeginPath(vg); | |||
| nvgRect(vg, | |||
| 0, (1.f - p) * box.size.y, | |||
| 5, p * box.size.y); | |||
| nvgFillColor(vg, nvgRGBAf(1, 0, 0, 1.0)); | |||
| nvgFill(vg); | |||
| } | |||
| nvgResetScissor(vg); | |||
| } | |||
| @@ -17,6 +17,7 @@ namespace rack { | |||
| bool gPaused = false; | |||
| std::vector<Module*> gModules; | |||
| std::vector<Wire*> gWires; | |||
| bool gCpuMeters = true; | |||
| static bool running = false; | |||
| static float sampleRate; | |||
| @@ -86,8 +87,16 @@ static void engineStep() { | |||
| // Step modules | |||
| for (Module *module : gModules) { | |||
| auto startTime = std::chrono::high_resolution_clock::now(); | |||
| module->step(); | |||
| if (gCpuMeters) { | |||
| auto stopTime = std::chrono::high_resolution_clock::now(); | |||
| float cpuTime = std::chrono::duration<float>(stopTime - startTime).count() * sampleRate; | |||
| module->cpuTime += (cpuTime - module->cpuTime) * sampleTime * 10.f; | |||
| } | |||
| // TODO skip this step when plug lights are disabled | |||
| // Step ports | |||
| for (Input &input : module->inputs) { | |||
| @@ -0,0 +1,23 @@ | |||
| #include "gamepad.hpp" | |||
| #include "util/common.hpp" | |||
| #include <GLFW/glfw3.h> | |||
| namespace rack { | |||
| void gamepadStep() { | |||
| for (int i = 0; i < 16; i++) { | |||
| if (glfwJoystickPresent(i)) { | |||
| const char *name = glfwGetJoystickName(i); | |||
| int numButtons; | |||
| const unsigned char *buttons = glfwGetJoystickButtons(i, &numButtons); | |||
| int numAxes; | |||
| const float *axes = glfwGetJoystickAxes(i, &numAxes); | |||
| debug("%d %s", i, name); | |||
| } | |||
| } | |||
| } | |||
| } // namespace rack | |||
| @@ -1,5 +1,7 @@ | |||
| #include "midi.hpp" | |||
| #include "rtmidi.hpp" | |||
| #include "bridge.hpp" | |||
| #include "gamepad.hpp" | |||
| namespace rack { | |||
| @@ -10,15 +12,10 @@ namespace rack { | |||
| //////////////////// | |||
| std::vector<int> MidiIO::getDrivers() { | |||
| std::vector<RtMidi::Api> rtApis; | |||
| RtMidi::getCompiledApi(rtApis); | |||
| std::vector<int> drivers; | |||
| for (RtMidi::Api api : rtApis) { | |||
| drivers.push_back((int) api); | |||
| } | |||
| // Add fake Bridge driver | |||
| std::vector<int> drivers = rtmidiGetDrivers(); | |||
| // Add custom drivers | |||
| drivers.push_back(BRIDGE_DRIVER); | |||
| drivers.push_back(GAMEPAD_DRIVER); | |||
| return drivers; | |||
| } | |||
| @@ -31,34 +28,14 @@ std::string MidiIO::getDriverName(int driver) { | |||
| case RtMidi::WINDOWS_MM: return "Windows MIDI"; | |||
| case RtMidi::RTMIDI_DUMMY: return "Dummy MIDI"; | |||
| case BRIDGE_DRIVER: return "Bridge"; | |||
| case GAMEPAD_DRIVER: return "Gamepad"; | |||
| default: return "Unknown"; | |||
| } | |||
| } | |||
| int MidiIO::getDeviceCount() { | |||
| if (rtMidi) { | |||
| return rtMidi->getPortCount(); | |||
| } | |||
| else if (driver == BRIDGE_DRIVER) { | |||
| return BRIDGE_NUM_PORTS; | |||
| } | |||
| return 0; | |||
| } | |||
| std::string MidiIO::getDeviceName(int device) { | |||
| if (device < 0) | |||
| return ""; | |||
| if (rtMidi) { | |||
| if (device == this->device) | |||
| return deviceName; | |||
| else | |||
| return rtMidi->getPortName(device); | |||
| } | |||
| else if (driver == BRIDGE_DRIVER) { | |||
| return stringf("Port %d", device + 1); | |||
| } | |||
| return ""; | |||
| void MidiIO::setDriver(int driver) { | |||
| setDevice(-1); | |||
| this->driver = driver; | |||
| } | |||
| std::string MidiIO::getChannelName(int channel) { | |||
| @@ -105,68 +82,55 @@ void MidiIO::fromJson(json_t *rootJ) { | |||
| // MidiInput | |||
| //////////////////// | |||
| static void midiInputCallback(double timeStamp, std::vector<unsigned char> *message, void *userData) { | |||
| if (!message) return; | |||
| if (!userData) return; | |||
| MidiInput *midiInput = (MidiInput*) userData; | |||
| if (!midiInput) return; | |||
| MidiMessage msg; | |||
| if (message->size() >= 1) | |||
| msg.cmd = (*message)[0]; | |||
| if (message->size() >= 2) | |||
| msg.data1 = (*message)[1]; | |||
| if (message->size() >= 3) | |||
| msg.data2 = (*message)[2]; | |||
| midiInput->onMessage(msg); | |||
| } | |||
| MidiInput::MidiInput() { | |||
| setDriver(RtMidi::UNSPECIFIED); | |||
| } | |||
| MidiInput::~MidiInput() { | |||
| setDriver(-1); | |||
| } | |||
| void MidiInput::setDriver(int driver) { | |||
| setDevice(-1); | |||
| if (rtMidiIn) { | |||
| delete rtMidiIn; | |||
| rtMidi = rtMidiIn = NULL; | |||
| int MidiInput::getDeviceCount() { | |||
| if (driver >= 0) { | |||
| return rtmidiGetDeviceCount(driver); | |||
| } | |||
| else if (driver == BRIDGE_DRIVER) { | |||
| // TODO | |||
| } | |||
| else if (driver == GAMEPAD_DRIVER) { | |||
| // TODO | |||
| } | |||
| return 0; | |||
| } | |||
| std::string MidiInput::getDeviceName(int device) { | |||
| if (driver >= 0) { | |||
| rtMidiIn = new RtMidiIn((RtMidi::Api) driver, "VCV Rack"); | |||
| rtMidiIn->setCallback(midiInputCallback, this); | |||
| rtMidiIn->ignoreTypes(false, false, false); | |||
| rtMidi = rtMidiIn; | |||
| this->driver = rtMidiIn->getCurrentApi(); | |||
| return rtmidiGetDeviceName(driver, device); | |||
| } | |||
| else if (driver == BRIDGE_DRIVER) { | |||
| this->driver = BRIDGE_DRIVER; | |||
| // TODO | |||
| } | |||
| else if (driver == GAMEPAD_DRIVER) { | |||
| // TODO | |||
| } | |||
| return ""; | |||
| } | |||
| void MidiInput::setDevice(int device) { | |||
| if (rtMidi) { | |||
| rtMidi->closePort(); | |||
| if (midiInputDevice) { | |||
| midiInputDevice->unsubscribe(this); | |||
| midiInputDevice = NULL; | |||
| } | |||
| if (device >= 0) { | |||
| rtMidi->openPort(device); | |||
| deviceName = rtMidi->getPortName(device); | |||
| } | |||
| this->device = device; | |||
| if (driver >= 0) { | |||
| midiInputDevice = rtmidiGetDevice(driver, device); | |||
| midiInputDevice->subscribe(this); | |||
| } | |||
| else if (driver == BRIDGE_DRIVER) { | |||
| if (device >= 0) { | |||
| bridgeMidiSubscribe(device, this); | |||
| } | |||
| else { | |||
| bridgeMidiUnsubscribe(device, this); | |||
| } | |||
| this->device = device; | |||
| // TODO | |||
| } | |||
| else if (driver == GAMEPAD_DRIVER) { | |||
| // TODO | |||
| } | |||
| } | |||
| @@ -197,38 +161,36 @@ bool MidiInputQueue::shift(MidiMessage *message) { | |||
| //////////////////// | |||
| MidiOutput::MidiOutput() { | |||
| setDriver(RtMidi::UNSPECIFIED); | |||
| } | |||
| MidiOutput::~MidiOutput() { | |||
| setDriver(-1); | |||
| // TODO | |||
| } | |||
| void MidiOutput::setDriver(int driver) { | |||
| setDevice(-1); | |||
| if (rtMidiOut) { | |||
| delete rtMidiOut; | |||
| rtMidi = rtMidiOut = NULL; | |||
| } | |||
| void MidiOutput::setDevice(int device) { | |||
| // TODO | |||
| } | |||
| if (driver >= 0) { | |||
| rtMidiOut = new RtMidiOut((RtMidi::Api) driver, "VCV Rack"); | |||
| rtMidi = rtMidiOut; | |||
| this->driver = rtMidiOut->getCurrentApi(); | |||
| } | |||
| // MidiIODevice | |||
| void MidiInputDevice::subscribe(MidiInput *midiInput) { | |||
| subscribed.insert(midiInput); | |||
| } | |||
| void MidiOutput::setDevice(int device) { | |||
| if (rtMidi) { | |||
| rtMidi->closePort(); | |||
| void MidiInputDevice::unsubscribe(MidiInput *midiInput) { | |||
| auto it = subscribed.find(midiInput); | |||
| if (it != subscribed.end()) | |||
| subscribed.erase(it); | |||
| if (device >= 0) { | |||
| rtMidi->openPort(device); | |||
| deviceName = rtMidi->getPortName(device); | |||
| } | |||
| this->device = device; | |||
| // Delete self if nothing is subscribed | |||
| if (subscribed.size() == 0) { | |||
| delete this; | |||
| } | |||
| else if (driver == BRIDGE_DRIVER) { | |||
| } | |||
| void MidiInputDevice::onMessage(MidiMessage message) { | |||
| for (MidiInput *midiInput : subscribed) { | |||
| midiInput->onMessage(message); | |||
| } | |||
| } | |||
| @@ -0,0 +1,69 @@ | |||
| #include "rtmidi.hpp" | |||
| namespace rack { | |||
| static void midiInputCallback(double timeStamp, std::vector<unsigned char> *message, void *userData) { | |||
| if (!message) return; | |||
| if (!userData) return; | |||
| RtMidiInputDevice *midiInputDevice = (RtMidiInputDevice*) userData; | |||
| if (!midiInputDevice) return; | |||
| MidiMessage msg; | |||
| if (message->size() >= 1) | |||
| msg.cmd = (*message)[0]; | |||
| if (message->size() >= 2) | |||
| msg.data1 = (*message)[1]; | |||
| if (message->size() >= 3) | |||
| msg.data2 = (*message)[2]; | |||
| midiInputDevice->onMessage(msg); | |||
| } | |||
| RtMidiInputDevice::RtMidiInputDevice(int driver, int device) { | |||
| rtMidiIn = new RtMidiIn((RtMidi::Api) driver, "VCV Rack"); | |||
| rtMidiIn->ignoreTypes(false, false, false); | |||
| rtMidiIn->setCallback(midiInputCallback, this); | |||
| } | |||
| RtMidiInputDevice::~RtMidiInputDevice() { | |||
| rtMidiIn->closePort(); | |||
| delete rtMidiIn; | |||
| } | |||
| std::vector<int> rtmidiGetDrivers() { | |||
| std::vector<RtMidi::Api> rtApis; | |||
| RtMidi::getCompiledApi(rtApis); | |||
| std::vector<int> drivers; | |||
| for (RtMidi::Api api : rtApis) { | |||
| drivers.push_back((int) api); | |||
| } | |||
| return drivers; | |||
| } | |||
| int rtmidiGetDeviceCount(int driver) { | |||
| RtMidiIn *rtMidiIn = new RtMidiIn((RtMidi::Api) driver); | |||
| int count = rtMidiIn->getPortCount(); | |||
| delete rtMidiIn; | |||
| return count; | |||
| } | |||
| std::string rtmidiGetDeviceName(int driver, int device) { | |||
| RtMidiIn *rtMidiIn = new RtMidiIn((RtMidi::Api) driver); | |||
| std::string name = rtMidiIn->getPortName(device); | |||
| delete rtMidiIn; | |||
| return name; | |||
| } | |||
| RtMidiInputDevice *rtmidiGetDevice(int driver, int device) { | |||
| // TODO Pull from cache | |||
| return new RtMidiInputDevice(driver, device); | |||
| } | |||
| } // namespace rack | |||
| @@ -1,6 +1,7 @@ | |||
| #include "window.hpp" | |||
| #include "app.hpp" | |||
| #include "asset.hpp" | |||
| #include "gamepad.hpp" | |||
| #include "util/color.hpp" | |||
| #include <map> | |||
| @@ -431,6 +432,7 @@ void windowRun() { | |||
| cursorPosCallback(gWindow, xpos, ypos); | |||
| } | |||
| mouseButtonStickyPop(); | |||
| gamepadStep(); | |||
| // Set window title | |||
| std::string windowTitle; | |||