meterpull/1639/head
| @@ -1 +1 @@ | |||||
| Subproject commit 9a74da4db5ac74083e444010d75114658581b9c7 | |||||
| Subproject commit 06c1f0f3bb041d69a73bb74067d063a700215b0e | |||||
| @@ -5,18 +5,17 @@ | |||||
| namespace rack { | namespace rack { | ||||
| // TODO Change driver and port number to something less common | |||||
| /** Driver ID in AudioIO and MidiIO */ | /** 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 */ | /** 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 */ | /** 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 | /** 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<Module*> gModules; | ||||
| extern std::vector<Wire*> gWires; | extern std::vector<Wire*> gWires; | ||||
| extern bool gCpuMeters; | |||||
| } // namespace rack | } // 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 | #pragma once | ||||
| #include "util/common.hpp" | #include "util/common.hpp" | ||||
| #include <queue> | |||||
| #include <vector> | #include <vector> | ||||
| #include <queue> | |||||
| #include <set> | |||||
| #include <jansson.h> | #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 { | namespace rack { | ||||
| @@ -35,6 +29,13 @@ struct MidiMessage { | |||||
| } | } | ||||
| }; | }; | ||||
| //////////////////// | |||||
| // MidiIO | |||||
| //////////////////// | |||||
| struct MidiInputDevice; | |||||
| struct MidiOutputDevice; | |||||
| struct MidiIO { | struct MidiIO { | ||||
| int driver = -1; | int driver = -1; | ||||
| @@ -45,18 +46,16 @@ struct MidiIO { | |||||
| Zero indexed. | Zero indexed. | ||||
| */ | */ | ||||
| int channel = -1; | int channel = -1; | ||||
| RtMidi *rtMidi = NULL; | |||||
| /** Cached */ | |||||
| std::string deviceName; | |||||
| virtual ~MidiIO() {} | virtual ~MidiIO() {} | ||||
| std::vector<int> getDrivers(); | std::vector<int> getDrivers(); | ||||
| std::string getDriverName(int driver); | 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); | std::string getChannelName(int channel); | ||||
| json_t *toJson(); | json_t *toJson(); | ||||
| @@ -65,17 +64,22 @@ struct MidiIO { | |||||
| struct MidiInput : MidiIO { | struct MidiInput : MidiIO { | ||||
| RtMidiIn *rtMidiIn = NULL; | |||||
| MidiInputDevice *midiInputDevice = NULL; | |||||
| MidiInput(); | MidiInput(); | ||||
| ~MidiInput(); | ~MidiInput(); | ||||
| void setDriver(int driver) override; | |||||
| int getDeviceCount() override; | |||||
| std::string getDeviceName(int device) override; | |||||
| void setDevice(int device) override; | void setDevice(int device) override; | ||||
| virtual void onMessage(MidiMessage message) {} | virtual void onMessage(MidiMessage message) {} | ||||
| }; | }; | ||||
| struct MidiInputQueue : MidiInput { | struct MidiInputQueue : MidiInput { | ||||
| int queueSize = 8192; | int queueSize = 8192; | ||||
| // TODO Switch to RingBuffer | |||||
| std::queue<MidiMessage> queue; | std::queue<MidiMessage> queue; | ||||
| void onMessage(MidiMessage message) override; | void onMessage(MidiMessage message) override; | ||||
| /** If a MidiMessage is available, writes `message` and return true */ | /** If a MidiMessage is available, writes `message` and return true */ | ||||
| @@ -84,12 +88,31 @@ struct MidiInputQueue : MidiInput { | |||||
| struct MidiOutput : MidiIO { | struct MidiOutput : MidiIO { | ||||
| RtMidiOut *rtMidiOut = NULL; | |||||
| MidiOutputDevice *midiOutputDevice = NULL; | |||||
| MidiOutput(); | MidiOutput(); | ||||
| ~MidiOutput(); | ~MidiOutput(); | ||||
| void setDriver(int driver) override; | |||||
| void setDevice(int device) 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 | } // 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) { | void ModuleWidget::draw(NVGcontext *vg) { | ||||
| nvgScissor(vg, 0, 0, box.size.x, box.size.y); | nvgScissor(vg, 0, 0, box.size.x, box.size.y); | ||||
| Widget::draw(vg); | 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); | nvgResetScissor(vg); | ||||
| } | } | ||||
| @@ -17,6 +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 = true; | |||||
| static bool running = false; | static bool running = false; | ||||
| static float sampleRate; | static float sampleRate; | ||||
| @@ -86,8 +87,16 @@ static void engineStep() { | |||||
| // Step modules | // Step modules | ||||
| for (Module *module : gModules) { | for (Module *module : gModules) { | ||||
| auto startTime = std::chrono::high_resolution_clock::now(); | |||||
| module->step(); | 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 | // TODO skip this step when plug lights are disabled | ||||
| // Step ports | // Step ports | ||||
| for (Input &input : module->inputs) { | 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 "midi.hpp" | ||||
| #include "rtmidi.hpp" | |||||
| #include "bridge.hpp" | #include "bridge.hpp" | ||||
| #include "gamepad.hpp" | |||||
| namespace rack { | namespace rack { | ||||
| @@ -10,15 +12,10 @@ namespace rack { | |||||
| //////////////////// | //////////////////// | ||||
| std::vector<int> MidiIO::getDrivers() { | 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(BRIDGE_DRIVER); | ||||
| drivers.push_back(GAMEPAD_DRIVER); | |||||
| return drivers; | return drivers; | ||||
| } | } | ||||
| @@ -31,34 +28,14 @@ std::string MidiIO::getDriverName(int driver) { | |||||
| case RtMidi::WINDOWS_MM: return "Windows MIDI"; | case RtMidi::WINDOWS_MM: return "Windows MIDI"; | ||||
| case RtMidi::RTMIDI_DUMMY: return "Dummy MIDI"; | case RtMidi::RTMIDI_DUMMY: return "Dummy MIDI"; | ||||
| case BRIDGE_DRIVER: return "Bridge"; | case BRIDGE_DRIVER: return "Bridge"; | ||||
| case GAMEPAD_DRIVER: return "Gamepad"; | |||||
| default: return "Unknown"; | 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) { | std::string MidiIO::getChannelName(int channel) { | ||||
| @@ -105,68 +82,55 @@ void MidiIO::fromJson(json_t *rootJ) { | |||||
| // MidiInput | // 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() { | MidiInput::MidiInput() { | ||||
| setDriver(RtMidi::UNSPECIFIED); | |||||
| } | } | ||||
| MidiInput::~MidiInput() { | MidiInput::~MidiInput() { | ||||
| setDriver(-1); | 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) { | 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) { | else if (driver == BRIDGE_DRIVER) { | ||||
| this->driver = BRIDGE_DRIVER; | |||||
| // TODO | |||||
| } | |||||
| else if (driver == GAMEPAD_DRIVER) { | |||||
| // TODO | |||||
| } | } | ||||
| return ""; | |||||
| } | } | ||||
| void MidiInput::setDevice(int device) { | 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) { | 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() { | MidiOutput::MidiOutput() { | ||||
| setDriver(RtMidi::UNSPECIFIED); | |||||
| } | } | ||||
| MidiOutput::~MidiOutput() { | 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 "window.hpp" | ||||
| #include "app.hpp" | #include "app.hpp" | ||||
| #include "asset.hpp" | #include "asset.hpp" | ||||
| #include "gamepad.hpp" | |||||
| #include "util/color.hpp" | #include "util/color.hpp" | ||||
| #include <map> | #include <map> | ||||
| @@ -431,6 +432,7 @@ void windowRun() { | |||||
| cursorPosCallback(gWindow, xpos, ypos); | cursorPosCallback(gWindow, xpos, ypos); | ||||
| } | } | ||||
| mouseButtonStickyPop(); | mouseButtonStickyPop(); | ||||
| gamepadStep(); | |||||
| // Set window title | // Set window title | ||||
| std::string windowTitle; | std::string windowTitle; | ||||