| @@ -337,6 +337,7 @@ struct LedDisplaySeparator : TransparentWidget { | |||
| struct LedDisplayChoice : TransparentWidget { | |||
| std::string text; | |||
| std::shared_ptr<Font> font; | |||
| Vec textOffset; | |||
| NVGcolor color; | |||
| LedDisplayChoice(); | |||
| void draw(NVGcontext *vg) override; | |||
| @@ -345,6 +346,7 @@ struct LedDisplayChoice : TransparentWidget { | |||
| struct LedDisplayTextField : TextField { | |||
| std::shared_ptr<Font> font; | |||
| Vec textOffset; | |||
| NVGcolor color; | |||
| LedDisplayTextField(); | |||
| void draw(NVGcontext *vg) override; | |||
| @@ -358,20 +360,26 @@ struct MidiIO; | |||
| struct AudioWidget : LedDisplay { | |||
| /** Not owned */ | |||
| AudioIO *audioIO = NULL; | |||
| struct Internal; | |||
| Internal *internal; | |||
| LedDisplayChoice *driverChoice; | |||
| LedDisplaySeparator *driverSeparator; | |||
| LedDisplayChoice *deviceChoice; | |||
| LedDisplaySeparator *deviceSeparator; | |||
| LedDisplayChoice *sampleRateChoice; | |||
| LedDisplaySeparator *sampleRateSeparator; | |||
| LedDisplayChoice *bufferSizeChoice; | |||
| AudioWidget(); | |||
| ~AudioWidget(); | |||
| void step() override; | |||
| }; | |||
| struct MidiWidget : LedDisplay { | |||
| /** Not owned */ | |||
| MidiIO *midiIO = NULL; | |||
| struct Internal; | |||
| Internal *internal; | |||
| LedDisplayChoice *driverChoice; | |||
| LedDisplaySeparator *driverSeparator; | |||
| LedDisplayChoice *deviceChoice; | |||
| LedDisplaySeparator *deviceSeparator; | |||
| LedDisplayChoice *channelChoice; | |||
| MidiWidget(); | |||
| ~MidiWidget(); | |||
| void step() override; | |||
| }; | |||
| @@ -23,12 +23,13 @@ struct AudioIO { | |||
| int numOutputs = 0; | |||
| int numInputs = 0; | |||
| RtAudio *rtAudio = NULL; | |||
| /** Cached */ | |||
| RtAudio::DeviceInfo deviceInfo; | |||
| AudioIO(); | |||
| virtual ~AudioIO(); | |||
| std::vector<int> listDrivers(); | |||
| std::vector<int> getDrivers(); | |||
| std::string getDriverName(int driver); | |||
| void setDriver(int driver); | |||
| @@ -40,7 +41,7 @@ struct AudioIO { | |||
| /** Returns whether the audio stream is open and running */ | |||
| bool isActive(); | |||
| std::vector<int> listSampleRates(); | |||
| std::vector<int> getSampleRates(); | |||
| virtual void processStream(const float *input, float *output, int length) {} | |||
| virtual void onCloseStream() {} | |||
| @@ -6,13 +6,13 @@ | |||
| namespace rack { | |||
| struct RCFilter { | |||
| float c = 0.0; | |||
| float c = 0.f; | |||
| float xstate[1] = {}; | |||
| float ystate[1] = {}; | |||
| // `r` is the ratio between the cutoff frequency and sample rate, i.e. r = f_c / f_s | |||
| void setCutoff(float r) { | |||
| c = 2.0 / r; | |||
| c = 2.f / r; | |||
| } | |||
| void process(float x) { | |||
| float y = (x + xstate[0] - ystate[0] * (1 - c)) / (1 + c); | |||
| @@ -29,12 +29,12 @@ struct RCFilter { | |||
| struct PeakFilter { | |||
| float state = 0.0; | |||
| float c = 0.0; | |||
| float state = 0.f; | |||
| float c = 0.f; | |||
| /** Rate is lambda / sampleRate */ | |||
| void setRate(float r) { | |||
| c = 1.0 - r; | |||
| c = 1.f - r; | |||
| } | |||
| void process(float x) { | |||
| if (x > state) | |||
| @@ -48,9 +48,9 @@ struct PeakFilter { | |||
| struct SlewLimiter { | |||
| float rise = 1.0; | |||
| float fall = 1.0; | |||
| float out = 0.0; | |||
| float rise = 1.f; | |||
| float fall = 1.f; | |||
| float out = 0.f; | |||
| void setRiseFall(float _rise, float _fall) { | |||
| rise = _rise; | |||
| @@ -64,4 +64,23 @@ struct SlewLimiter { | |||
| }; | |||
| /** Applies exponential smoothing to a signal with the ODE | |||
| dy/dt = x * lambda | |||
| */ | |||
| struct ExponentialFilter { | |||
| float out = 0.f; | |||
| float lambda = 1.f; | |||
| float process(float in) { | |||
| float y = out + (in - out) * lambda; | |||
| // If no change was detected, assume float granularity is too small and snap output to input | |||
| if (out == y) | |||
| out = in; | |||
| else | |||
| out = y; | |||
| return out; | |||
| } | |||
| }; | |||
| } // namespace rack | |||
| @@ -16,12 +16,21 @@ namespace rack { | |||
| struct MidiMessage { | |||
| double time; | |||
| std::vector<uint8_t> data; | |||
| uint8_t cmd = 0x00; | |||
| uint8_t data1 = 0x00; | |||
| uint8_t data2 = 0x00; | |||
| uint8_t channel() { | |||
| return cmd & 0xf; | |||
| } | |||
| uint8_t status() { | |||
| return (cmd >> 4) & 0xf; | |||
| } | |||
| }; | |||
| struct MidiIO { | |||
| int driver = -1; | |||
| int device = -1; | |||
| /* For MIDI output, the channel to output messages. | |||
| For MIDI input, the channel to filter. | |||
| @@ -30,11 +39,19 @@ struct MidiIO { | |||
| */ | |||
| 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) {} | |||
| virtual ~MidiIO() {} | |||
| int getDeviceCount(); | |||
| std::string getDeviceName(int device); | |||
| void openDevice(int device); | |||
| void setDevice(int device); | |||
| std::string getChannelName(int channel); | |||
| /** Returns whether the audio stream is open and running */ | |||
| bool isActive(); | |||
| json_t *toJson(); | |||
| @@ -45,21 +62,24 @@ struct MidiIO { | |||
| struct MidiInput : MidiIO { | |||
| RtMidiIn *rtMidiIn = NULL; | |||
| MidiInput(); | |||
| ~MidiInput(); | |||
| void setDriver(int driver) override; | |||
| virtual void onMessage(const MidiMessage &message) {} | |||
| }; | |||
| struct MidiInputQueue : MidiInput { | |||
| std::queue<MidiMessage> messageQueue; | |||
| int queueSize = 8192; | |||
| std::queue<MidiMessage> queue; | |||
| void onMessage(const MidiMessage &message) override; | |||
| /** If a MidiMessage is available, writes `message` and return true */ | |||
| bool shift(MidiMessage *message); | |||
| }; | |||
| struct MidiOutput : MidiIO { | |||
| RtMidiOut *rtMidiOut = NULL; | |||
| MidiOutput(); | |||
| ~MidiOutput(); | |||
| void setDriver(int driver) override; | |||
| }; | |||
| @@ -1,5 +1,5 @@ | |||
| #pragma once | |||
| #include "app.hpp" | |||
| #include "widgets.hpp" | |||
| #include <GL/glew.h> | |||
| #include <GLFW/glfw3.h> | |||
| @@ -0,0 +1,453 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | |||
| <svg | |||
| xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
| xmlns:cc="http://creativecommons.org/ns#" | |||
| xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
| xmlns:svg="http://www.w3.org/2000/svg" | |||
| xmlns="http://www.w3.org/2000/svg" | |||
| xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
| xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
| width="50.799999mm" | |||
| height="128.4993mm" | |||
| viewBox="0 0 50.799999 128.4993" | |||
| version="1.1" | |||
| id="svg35238" | |||
| inkscape:version="0.92.2 5c3e80d, 2017-08-06" | |||
| sodipodi:docname="MIDICCToCVInterface.svg"> | |||
| <defs | |||
| id="defs35232" /> | |||
| <sodipodi:namedview | |||
| id="base" | |||
| pagecolor="#ffffff" | |||
| bordercolor="#666666" | |||
| borderopacity="1.0" | |||
| inkscape:pageopacity="0.0" | |||
| inkscape:pageshadow="2" | |||
| inkscape:zoom="1.979899" | |||
| inkscape:cx="226.99608" | |||
| inkscape:cy="249.91512" | |||
| inkscape:document-units="mm" | |||
| inkscape:current-layer="layer1" | |||
| showgrid="false" | |||
| fit-margin-top="0" | |||
| fit-margin-left="0" | |||
| fit-margin-right="0" | |||
| fit-margin-bottom="0" | |||
| inkscape:snap-bbox="true" | |||
| inkscape:bbox-nodes="true" | |||
| inkscape:window-width="2560" | |||
| inkscape:window-height="1422" | |||
| inkscape:window-x="0" | |||
| inkscape:window-y="18" | |||
| inkscape:window-maximized="0" /> | |||
| <metadata | |||
| id="metadata35235"> | |||
| <rdf:RDF> | |||
| <cc:Work | |||
| rdf:about=""> | |||
| <dc:format>image/svg+xml</dc:format> | |||
| <dc:type | |||
| rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
| <dc:title></dc:title> | |||
| </cc:Work> | |||
| </rdf:RDF> | |||
| </metadata> | |||
| <g | |||
| inkscape:label="Layer 1" | |||
| inkscape:groupmode="layer" | |||
| id="layer1" | |||
| transform="translate(65.465476,-84.583683)"> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path3831" | |||
| d="m -65.371768,84.676012 h 50.612587 V 212.98928 h -50.612587 z m 0,0" | |||
| style="fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path3833" | |||
| d="m -14.665476,84.583683 h -50.8 V 213.08298 h 50.8 z M -14.852891,212.89557 H -65.278063 V 84.769721 h 50.425172 z m 0,0" | |||
| style="fill:#ababab;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path5999" | |||
| d="m -42.818798,208.9406 c -0.115753,0 -0.223241,-0.0661 -0.275607,-0.17088 l -1.001833,-2.00504 c -0.07717,-0.15297 -0.01517,-0.33762 0.137802,-0.41341 0.151585,-0.0772 0.337623,-0.0152 0.413413,0.1378 l 0.726225,1.45246 0.727604,-1.45246 c 0.07579,-0.15296 0.260449,-0.21497 0.413414,-0.1378 0.151585,0.0758 0.213596,0.26044 0.137802,0.41341 l -1.001833,2.00504 c -0.05237,0.10474 -0.159854,0.17088 -0.276987,0.17088" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6001" | |||
| d="m -37.312156,208.9406 c -0.117133,0 -0.22462,-0.0661 -0.276987,-0.17088 l -1.001831,-2.00504 c -0.07579,-0.15297 -0.01378,-0.33762 0.1378,-0.41341 0.152966,-0.0772 0.337619,-0.0152 0.413414,0.1378 l 0.727604,1.45246 0.726226,-1.45246 c 0.07579,-0.15296 0.261826,-0.21497 0.413411,-0.1378 0.152961,0.0758 0.214974,0.26044 0.137803,0.41341 l -1.001831,2.00504 c -0.05237,0.10474 -0.159856,0.17088 -0.275609,0.17088" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6003" | |||
| d="m -39.915271,208.9406 c -0.72347,0 -1.311892,-0.58842 -1.311892,-1.31189 0,-0.72209 0.588422,-1.31052 1.311892,-1.31052 0.286632,0 0.558105,0.091 0.78686,0.26183 0.135048,0.10335 0.162609,0.29628 0.06064,0.43271 -0.101978,0.13642 -0.296281,0.16398 -0.431328,0.0606 -0.121267,-0.0896 -0.264583,-0.13918 -0.416168,-0.13918 -0.383095,0 -0.694531,0.31282 -0.694531,0.69453 0,0.38309 0.311436,0.69453 0.694531,0.69453 0.151585,0 0.294901,-0.0482 0.416168,-0.13918 0.135047,-0.10198 0.32935,-0.0744 0.431328,0.062 0.101973,0.13643 0.07441,0.32935 -0.06064,0.43132 -0.228755,0.17226 -0.500228,0.26321 -0.78686,0.26321" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6005" | |||
| d="m -39.494968,207.61355 c 0,0.22186 -0.179144,0.40101 -0.40101,0.40101 -0.221865,0 -0.401009,-0.17915 -0.401009,-0.40101 0,-0.22186 0.179144,-0.40101 0.401009,-0.40101 0.221866,0 0.40101,0.17915 0.40101,0.40101" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35277775" | |||
| d="m -48.721793,93.856062 c 0,0.176389 0.137802,0.319705 0.314191,0.319705 0.176389,0 0.319705,-0.143316 0.319705,-0.319705 v -1.471743 l 0.567753,0.865407 c 0.06615,0.104732 0.148827,0.165364 0.270094,0.165364 0.115757,0 0.203952,-0.06063 0.270097,-0.165364 l 0.573264,-0.881945 v 1.477257 c 0,0.181903 0.143316,0.330729 0.319705,0.330729 0.181899,0 0.325215,-0.148826 0.325215,-0.330729 v -2.353687 c 0,-0.181903 -0.143316,-0.325219 -0.325215,-0.325219 h -0.07166 c -0.132291,0 -0.225996,0.05512 -0.292142,0.165365 l -0.79375,1.289843 -0.788239,-1.28433 c -0.05512,-0.09922 -0.154341,-0.170878 -0.292143,-0.170878 h -0.07166 c -0.181899,0 -0.325215,0.143316 -0.325215,0.325219 z m 0,0" | |||
| id="path24396" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35277775" | |||
| d="m -44.976094,93.845038 c 0,0.181903 0.143316,0.330729 0.325216,0.330729 0.181902,0 0.325218,-0.148826 0.325218,-0.330729 v -2.359201 c 0,-0.181899 -0.143316,-0.325215 -0.325218,-0.325215 -0.1819,0 -0.325216,0.143316 -0.325216,0.325215 z m 0,0" | |||
| id="path24392" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35277775" | |||
| d="m -43.54698,93.822989 c 0,0.181903 0.143316,0.325219 0.325215,0.325219 h 0.826823 c 0.931555,0 1.576475,-0.650434 1.576475,-1.48277 v -0.0055 c 0,-0.837848 -0.64492,-1.477257 -1.576475,-1.477257 h -0.826823 c -0.181899,0 -0.325215,0.148826 -0.325215,0.330729 z m 0.650433,-0.264583 v -1.785937 h 0.501605 c 0.53468,0 0.892968,0.369316 0.892968,0.892969 v 0.01101 c 0,0.523656 -0.358288,0.881944 -0.892968,0.881944 z m 0,0" | |||
| id="path24388" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35277775" | |||
| d="m -40.201321,93.845038 c 0,0.181903 0.143316,0.330729 0.325215,0.330729 0.181903,0 0.325219,-0.148826 0.325219,-0.330729 v -2.359201 c 0,-0.181899 -0.143316,-0.325215 -0.325219,-0.325215 -0.181899,0 -0.325215,0.143316 -0.325215,0.325215 z m 0,0" | |||
| id="path24384" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35277775" | |||
| d="m -38.589511,93.144997 h 0.733115 c 0.170879,0 0.314193,-0.137806 0.314193,-0.308681 0,-0.170878 -0.143314,-0.308681 -0.314193,-0.308681 h -0.733115 c -0.170879,0 -0.308682,0.137803 -0.308682,0.308681 0,0.170875 0.137803,0.308681 0.308682,0.308681 z m 0,0" | |||
| id="path24380" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35277775" | |||
| d="m -35.467565,94.197816 c 0.474046,0 0.788238,-0.137802 1.058333,-0.369313 0.05512,-0.05512 0.110244,-0.132291 0.110244,-0.237024 0,-0.165365 -0.143317,-0.297656 -0.308682,-0.297656 -0.07717,0 -0.143316,0.02755 -0.192923,0.07166 -0.187412,0.148826 -0.369316,0.23151 -0.644925,0.23151 -0.507116,0 -0.859895,-0.424437 -0.859895,-0.931555 v -0.0055 c 0,-0.507118 0.363802,-0.926042 0.859895,-0.926042 0.231511,0 0.42444,0.07717 0.606338,0.214972 0.04961,0.02755 0.104733,0.06063 0.192926,0.06063 0.181901,0 0.325216,-0.137805 0.325216,-0.314194 0,-0.115753 -0.06063,-0.209462 -0.126778,-0.259069 -0.248047,-0.181903 -0.545703,-0.30317 -0.992188,-0.30317 -0.909505,0 -1.543402,0.68902 -1.543402,1.532378 v 0.01101 c 0,0.848871 0.64492,1.521354 1.515841,1.521354 z m 0,0" | |||
| id="path24376" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.35277775" | |||
| d="m -32.396285,94.197816 c 0.474044,0 0.788236,-0.137802 1.058334,-0.369313 0.05512,-0.05512 0.110241,-0.132291 0.110241,-0.237024 0,-0.165365 -0.143314,-0.297656 -0.308679,-0.297656 -0.07717,0 -0.143317,0.02755 -0.192929,0.07166 -0.187412,0.148826 -0.36931,0.23151 -0.644919,0.23151 -0.507119,0 -0.859896,-0.424437 -0.859896,-0.931555 v -0.0055 c 0,-0.507118 0.363802,-0.926042 0.859896,-0.926042 0.23151,0 0.424434,0.07717 0.606338,0.214972 0.04961,0.02755 0.104727,0.06063 0.192923,0.06063 0.181901,0 0.325218,-0.137805 0.325218,-0.314194 0,-0.115753 -0.06063,-0.209462 -0.12678,-0.259069 -0.248047,-0.181903 -0.545703,-0.30317 -0.992188,-0.30317 -0.909505,0 -1.543404,0.68902 -1.543404,1.532378 v 0.01101 c 0,0.848871 0.644924,1.521354 1.515845,1.521354 z m 0,0" | |||
| id="path24372" /> | |||
| <path | |||
| id="path24366" | |||
| d="m -53.465518,197.19834 c 0,2.40881 -1.790072,4.36287 -4.000447,4.36287 -2.208996,0 -3.999068,-1.95406 -3.999068,-4.36287 0,-2.41019 1.790072,-4.36425 3.999068,-4.36425 2.210375,0 4.000447,1.95406 4.000447,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24354" | |||
| d="m -41.865194,197.19834 c 0,2.40881 -1.791451,4.36287 -4.000447,4.36287 -2.208992,0 -4.000443,-1.95406 -4.000443,-4.36287 0,-2.41019 1.791451,-4.36425 4.000443,-4.36425 2.208996,0 4.000447,1.95406 4.000447,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24342" | |||
| d="m -30.26487,197.19834 c 0,2.40881 -1.791446,4.36287 -4.000442,4.36287 -2.208996,0 -4.000447,-1.95406 -4.000447,-4.36287 0,-2.41019 1.791451,-4.36425 4.000447,-4.36425 2.208996,0 4.000442,1.95406 4.000442,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24330" | |||
| d="m -18.66592,197.19834 c 0,2.40881 -1.790073,4.36287 -3.999069,4.36287 -2.208996,0 -4.000442,-1.95406 -4.000442,-4.36287 0,-2.41019 1.791446,-4.36425 4.000442,-4.36425 2.208996,0 3.999069,1.95406 3.999069,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6245" | |||
| d="m -41.024591,192.81893 c 0,-0.45475 -0.372071,-0.82544 -0.826823,-0.82544 h -8.028449 c -0.454752,0 -0.826823,0.37069 -0.826823,0.82544 v 8.02983 c 0,0.45475 0.372071,0.82682 0.826823,0.82682 h 8.028449 c 0.454752,0 0.826823,-0.37207 0.826823,-0.82682 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6247" | |||
| d="m -29.424268,192.81893 c 0,-0.45475 -0.372067,-0.82544 -0.826823,-0.82544 h -8.028448 c -0.454752,0 -0.826823,0.37069 -0.826823,0.82544 v 8.02983 c 0,0.45475 0.372071,0.82682 0.826823,0.82682 h 8.028448 c 0.454756,0 0.826823,-0.37207 0.826823,-0.82682 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6249" | |||
| d="m -17.82394,192.81893 c 0,-0.45475 -0.37207,-0.82544 -0.826822,-0.82544 h -8.029829 c -0.453375,0 -0.825448,0.37069 -0.825448,0.82544 v 8.02983 c 0,0.45475 0.372073,0.82682 0.825448,0.82682 h 8.029829 c 0.454752,0 0.826822,-0.37207 0.826822,-0.82682 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6251" | |||
| d="m -52.624916,192.81893 c 0,-0.45475 -0.370691,-0.82544 -0.825447,-0.82544 h -8.029825 c -0.454755,0 -0.826823,0.37069 -0.826823,0.82544 v 8.02983 c 0,0.45475 0.372068,0.82682 0.826823,0.82682 h 8.029825 c 0.454756,0 0.825447,-0.37207 0.825447,-0.82682 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| id="path24086" | |||
| d="m -53.465518,197.19834 c 0,2.40881 -1.790072,4.36287 -4.000447,4.36287 -2.208996,0 -3.999068,-1.95406 -3.999068,-4.36287 0,-2.41019 1.790072,-4.36425 3.999068,-4.36425 2.210375,0 4.000447,1.95406 4.000447,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24074" | |||
| d="m -41.865194,197.19834 c 0,2.40881 -1.791451,4.36287 -4.000447,4.36287 -2.208992,0 -4.000443,-1.95406 -4.000443,-4.36287 0,-2.41019 1.791451,-4.36425 4.000443,-4.36425 2.208996,0 4.000447,1.95406 4.000447,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24062" | |||
| d="m -18.66592,197.19834 c 0,2.40881 -1.790073,4.36287 -3.999069,4.36287 -2.208996,0 -4.000442,-1.95406 -4.000442,-4.36287 0,-2.41019 1.791446,-4.36425 4.000442,-4.36425 2.208996,0 3.999069,1.95406 3.999069,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24050" | |||
| d="m -30.26487,197.19834 c 0,2.40881 -1.791446,4.36287 -4.000442,4.36287 -2.208996,0 -4.000447,-1.95406 -4.000447,-4.36287 0,-2.41019 1.791451,-4.36425 4.000447,-4.36425 2.208996,0 4.000442,1.95406 4.000442,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24038" | |||
| d="m -53.465518,185.59801 c 0,2.41019 -1.790072,4.36425 -4.000447,4.36425 -2.208996,0 -3.999068,-1.95406 -3.999068,-4.36425 0,-2.41018 1.790072,-4.36424 3.999068,-4.36424 2.210375,0 4.000447,1.95406 4.000447,4.36424" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24026" | |||
| d="m -41.865194,185.59801 c 0,2.41019 -1.791451,4.36425 -4.000447,4.36425 -2.208992,0 -4.000443,-1.95406 -4.000443,-4.36425 0,-2.41018 1.791451,-4.36424 4.000443,-4.36424 2.208996,0 4.000447,1.95406 4.000447,4.36424" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24014" | |||
| d="m -30.26487,185.59801 c 0,2.41019 -1.791446,4.36425 -4.000442,4.36425 -2.208996,0 -4.000447,-1.95406 -4.000447,-4.36425 0,-2.41018 1.791451,-4.36424 4.000447,-4.36424 2.208996,0 4.000442,1.95406 4.000442,4.36424" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path24002" | |||
| d="m -18.66592,185.59801 c 0,2.41019 -1.790073,4.36425 -3.999069,4.36425 -2.208996,0 -4.000442,-1.95406 -4.000442,-4.36425 0,-2.41018 1.791446,-4.36424 4.000442,-4.36424 2.208996,0 3.999069,1.95406 3.999069,4.36424" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6373" | |||
| d="m -41.024591,181.21999 c 0,-0.45475 -0.372071,-0.82682 -0.826823,-0.82682 h -8.028449 c -0.454752,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02844 c 0,0.45476 0.372071,0.82683 0.826823,0.82683 h 8.028449 c 0.454752,0 0.826823,-0.37207 0.826823,-0.82683 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6375" | |||
| d="m -29.424268,181.21999 c 0,-0.45475 -0.372067,-0.82682 -0.826823,-0.82682 h -8.028448 c -0.454752,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02844 c 0,0.45476 0.372071,0.82683 0.826823,0.82683 h 8.028448 c 0.454756,0 0.826823,-0.37207 0.826823,-0.82683 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6377" | |||
| d="m -17.82394,181.21999 c 0,-0.45475 -0.37207,-0.82682 -0.826822,-0.82682 h -8.029829 c -0.453375,0 -0.825448,0.37207 -0.825448,0.82682 v 8.02844 c 0,0.45476 0.372073,0.82683 0.825448,0.82683 h 8.029829 c 0.454752,0 0.826822,-0.37207 0.826822,-0.82683 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6379" | |||
| d="m -52.624916,181.21999 c 0,-0.45475 -0.370691,-0.82682 -0.825447,-0.82682 h -8.029825 c -0.454755,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02844 c 0,0.45476 0.372068,0.82683 0.826823,0.82683 h 8.029825 c 0.454756,0 0.825447,-0.37207 0.825447,-0.82683 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| id="path23990" | |||
| d="m -53.465518,185.59801 c 0,2.41019 -1.790072,4.36425 -4.000447,4.36425 -2.208996,0 -3.999068,-1.95406 -3.999068,-4.36425 0,-2.41018 1.790072,-4.36424 3.999068,-4.36424 2.210375,0 4.000447,1.95406 4.000447,4.36424" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23978" | |||
| d="m -41.865194,185.59801 c 0,2.41019 -1.791451,4.36425 -4.000447,4.36425 -2.208992,0 -4.000443,-1.95406 -4.000443,-4.36425 0,-2.41018 1.791451,-4.36424 4.000443,-4.36424 2.208996,0 4.000447,1.95406 4.000447,4.36424" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23966" | |||
| d="m -18.66592,185.59801 c 0,2.41019 -1.790073,4.36425 -3.999069,4.36425 -2.208996,0 -4.000442,-1.95406 -4.000442,-4.36425 0,-2.41018 1.791446,-4.36424 4.000442,-4.36424 2.208996,0 3.999069,1.95406 3.999069,4.36424" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23954" | |||
| d="m -30.26487,185.59801 c 0,2.41019 -1.791446,4.36425 -4.000442,4.36425 -2.208996,0 -4.000447,-1.95406 -4.000447,-4.36425 0,-2.41018 1.791451,-4.36424 4.000447,-4.36424 2.208996,0 4.000442,1.95406 4.000442,4.36424" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23942" | |||
| d="m -53.465518,173.99769 c 0,2.41019 -1.790072,4.36425 -4.000447,4.36425 -2.208996,0 -3.999068,-1.95406 -3.999068,-4.36425 0,-2.41019 1.790072,-4.36425 3.999068,-4.36425 2.210375,0 4.000447,1.95406 4.000447,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23930" | |||
| d="m -41.865194,173.99769 c 0,2.41019 -1.791451,4.36425 -4.000447,4.36425 -2.208992,0 -4.000443,-1.95406 -4.000443,-4.36425 0,-2.41019 1.791451,-4.36425 4.000443,-4.36425 2.208996,0 4.000447,1.95406 4.000447,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23918" | |||
| d="m -30.26487,173.99769 c 0,2.41019 -1.791446,4.36425 -4.000442,4.36425 -2.208996,0 -4.000447,-1.95406 -4.000447,-4.36425 0,-2.41019 1.791451,-4.36425 4.000447,-4.36425 2.208996,0 4.000442,1.95406 4.000442,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23906" | |||
| d="m -18.66592,173.99769 c 0,2.41019 -1.790073,4.36425 -3.999069,4.36425 -2.208996,0 -4.000442,-1.95406 -4.000442,-4.36425 0,-2.41019 1.791446,-4.36425 4.000442,-4.36425 2.208996,0 3.999069,1.95406 3.999069,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6493" | |||
| d="m -41.024591,169.61966 c 0,-0.45475 -0.372071,-0.82682 -0.826823,-0.82682 h -8.028449 c -0.454752,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02845 c 0,0.45475 0.372071,0.82683 0.826823,0.82683 h 8.028449 c 0.454752,0 0.826823,-0.37208 0.826823,-0.82683 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6495" | |||
| d="m -29.424268,169.61966 c 0,-0.45475 -0.372067,-0.82682 -0.826823,-0.82682 h -8.028448 c -0.454752,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02845 c 0,0.45475 0.372071,0.82683 0.826823,0.82683 h 8.028448 c 0.454756,0 0.826823,-0.37208 0.826823,-0.82683 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6497" | |||
| d="m -17.82394,169.61966 c 0,-0.45475 -0.37207,-0.82682 -0.826822,-0.82682 h -8.029829 c -0.453375,0 -0.825448,0.37207 -0.825448,0.82682 v 8.02845 c 0,0.45475 0.372073,0.82683 0.825448,0.82683 h 8.029829 c 0.454752,0 0.826822,-0.37208 0.826822,-0.82683 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6499" | |||
| d="m -52.624916,169.61966 c 0,-0.45475 -0.370691,-0.82682 -0.825447,-0.82682 h -8.029825 c -0.454755,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02845 c 0,0.45475 0.372068,0.82683 0.826823,0.82683 h 8.029825 c 0.454756,0 0.825447,-0.37208 0.825447,-0.82683 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| id="path23894" | |||
| d="m -53.465518,173.99769 c 0,2.41019 -1.790072,4.36425 -4.000447,4.36425 -2.208996,0 -3.999068,-1.95406 -3.999068,-4.36425 0,-2.41019 1.790072,-4.36425 3.999068,-4.36425 2.210375,0 4.000447,1.95406 4.000447,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23882" | |||
| d="m -41.865194,173.99769 c 0,2.41019 -1.791451,4.36425 -4.000447,4.36425 -2.208992,0 -4.000443,-1.95406 -4.000443,-4.36425 0,-2.41019 1.791451,-4.36425 4.000443,-4.36425 2.208996,0 4.000447,1.95406 4.000447,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23870" | |||
| d="m -18.66592,173.99769 c 0,2.41019 -1.790073,4.36425 -3.999069,4.36425 -2.208996,0 -4.000442,-1.95406 -4.000442,-4.36425 0,-2.41019 1.791446,-4.36425 4.000442,-4.36425 2.208996,0 3.999069,1.95406 3.999069,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| id="path23858" | |||
| d="m -30.26487,173.99769 c 0,2.41019 -1.791446,4.36425 -4.000442,4.36425 -2.208996,0 -4.000447,-1.95406 -4.000447,-4.36425 0,-2.41019 1.791451,-4.36425 4.000447,-4.36425 2.208996,0 4.000442,1.95406 4.000442,4.36425" | |||
| style="clip-rule:nonzero;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" | |||
| inkscape:connector-curvature="0" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6613" | |||
| d="m -41.024591,158.01934 c 0,-0.45475 -0.372071,-0.82682 -0.826823,-0.82682 h -8.028449 c -0.454752,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02983 c 0,0.45475 0.372071,0.82682 0.826823,0.82682 h 8.028449 c 0.454752,0 0.826823,-0.37207 0.826823,-0.82682 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6615" | |||
| d="m -29.424268,158.01934 c 0,-0.45475 -0.372067,-0.82682 -0.826823,-0.82682 h -8.028448 c -0.454752,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02983 c 0,0.45475 0.372071,0.82682 0.826823,0.82682 h 8.028448 c 0.454756,0 0.826823,-0.37207 0.826823,-0.82682 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6617" | |||
| d="m -17.82394,158.01934 c 0,-0.45475 -0.37207,-0.82682 -0.826822,-0.82682 h -8.029829 c -0.453375,0 -0.825448,0.37207 -0.825448,0.82682 v 8.02983 c 0,0.45475 0.372073,0.82682 0.825448,0.82682 h 8.029829 c 0.454752,0 0.826822,-0.37207 0.826822,-0.82682 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| <path | |||
| inkscape:connector-curvature="0" | |||
| id="path6619" | |||
| d="m -52.624916,158.01934 c 0,-0.45475 -0.370691,-0.82682 -0.825447,-0.82682 h -8.029825 c -0.454755,0 -0.826823,0.37207 -0.826823,0.82682 v 8.02983 c 0,0.45475 0.372068,0.82682 0.826823,0.82682 h 8.029825 c 0.454756,0 0.825447,-0.37207 0.825447,-0.82682 z m 0,0" | |||
| style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
| </g> | |||
| <g | |||
| inkscape:groupmode="layer" | |||
| id="layer5" | |||
| inkscape:label="widgets" | |||
| style="display:none"> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#ffff00;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.40717816;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect35947" | |||
| width="44.000759" | |||
| height="54.666779" | |||
| x="3.399621" | |||
| y="14.837339" /> | |||
| <rect | |||
| y="73.344704" | |||
| x="3.894335" | |||
| height="8.2117329" | |||
| width="8.2117319" | |||
| id="rect36492" | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect36494" | |||
| width="8.2117319" | |||
| height="8.2117329" | |||
| x="15.494659" | |||
| y="73.344704" /> | |||
| <rect | |||
| y="73.344704" | |||
| x="27.094982" | |||
| height="8.2117329" | |||
| width="8.2117319" | |||
| id="rect36496" | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect36498" | |||
| width="8.2117319" | |||
| height="8.2117329" | |||
| x="38.693932" | |||
| y="73.344704" /> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect36500" | |||
| width="8.2117319" | |||
| height="8.2117329" | |||
| x="3.8943355" | |||
| y="84.945023" /> | |||
| <rect | |||
| y="84.945023" | |||
| x="15.49466" | |||
| height="8.2117329" | |||
| width="8.2117319" | |||
| id="rect36502" | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect36504" | |||
| width="8.2117319" | |||
| height="8.2117329" | |||
| x="27.094982" | |||
| y="84.945023" /> | |||
| <rect | |||
| y="84.945023" | |||
| x="38.693932" | |||
| height="8.2117329" | |||
| width="8.2117319" | |||
| id="rect36506" | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> | |||
| <rect | |||
| y="96.543976" | |||
| x="3.8943343" | |||
| height="8.2117329" | |||
| width="8.2117319" | |||
| id="rect36508" | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect36510" | |||
| width="8.2117319" | |||
| height="8.2117329" | |||
| x="15.494659" | |||
| y="96.543976" /> | |||
| <rect | |||
| y="96.543976" | |||
| x="27.09498" | |||
| height="8.2117329" | |||
| width="8.2117319" | |||
| id="rect36512" | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect36514" | |||
| width="8.2117319" | |||
| height="8.2117329" | |||
| x="38.693932" | |||
| y="96.543976" /> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect36516" | |||
| width="8.2117319" | |||
| height="8.2117329" | |||
| x="3.894335" | |||
| y="108.14429" /> | |||
| <rect | |||
| y="108.14429" | |||
| x="15.49466" | |||
| height="8.2117329" | |||
| width="8.2117319" | |||
| id="rect36518" | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> | |||
| <rect | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" | |||
| id="rect36520" | |||
| width="8.2117319" | |||
| height="8.2117329" | |||
| x="27.09498" | |||
| y="108.14429" /> | |||
| <rect | |||
| y="108.14429" | |||
| x="38.693932" | |||
| height="8.2117329" | |||
| width="8.2117319" | |||
| id="rect36522" | |||
| style="opacity:1;vector-effect:none;fill:#0000ff;fill-opacity:0.50196078;fill-rule:evenodd;stroke:none;stroke-width:0.06817536;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /> | |||
| </g> | |||
| </svg> | |||
| @@ -18,7 +18,7 @@ struct AudioDriverChoice : LedDisplayChoice { | |||
| void onAction(EventAction &e) override { | |||
| Menu *menu = gScene->createMenu(); | |||
| menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Audio driver")); | |||
| for (int driver : audioWidget->audioIO->listDrivers()) { | |||
| for (int driver : audioWidget->audioIO->getDrivers()) { | |||
| AudioDriverItem *item = new AudioDriverItem(); | |||
| item->audioIO = audioWidget->audioIO; | |||
| item->driver = driver; | |||
| @@ -93,7 +93,7 @@ struct AudioSampleRateChoice : LedDisplayChoice { | |||
| void onAction(EventAction &e) override { | |||
| Menu *menu = gScene->createMenu(); | |||
| menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Sample rate")); | |||
| for (int sampleRate : audioWidget->audioIO->listSampleRates()) { | |||
| for (int sampleRate : audioWidget->audioIO->getSampleRates()) { | |||
| AudioSampleRateItem *item = new AudioSampleRateItem(); | |||
| item->audioIO = audioWidget->audioIO; | |||
| item->sampleRate = sampleRate; | |||
| @@ -139,18 +139,7 @@ struct AudioBlockSizeChoice : LedDisplayChoice { | |||
| }; | |||
| struct AudioWidget::Internal { | |||
| LedDisplayChoice *driverChoice; | |||
| LedDisplaySeparator *driverSeparator; | |||
| LedDisplayChoice *deviceChoice; | |||
| LedDisplaySeparator *deviceSeparator; | |||
| LedDisplayChoice *sampleRateChoice; | |||
| LedDisplaySeparator *sampleRateSeparator; | |||
| LedDisplayChoice *bufferSizeChoice; | |||
| }; | |||
| AudioWidget::AudioWidget() { | |||
| internal = new Internal(); | |||
| box.size = mm2px(Vec(44, 28)); | |||
| Vec pos = Vec(); | |||
| @@ -159,48 +148,44 @@ AudioWidget::AudioWidget() { | |||
| driverChoice->audioWidget = this; | |||
| addChild(driverChoice); | |||
| pos = driverChoice->box.getBottomLeft(); | |||
| internal->driverChoice = driverChoice; | |||
| this->driverChoice = driverChoice; | |||
| internal->driverSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(internal->driverSeparator); | |||
| this->driverSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(this->driverSeparator); | |||
| AudioDeviceChoice *deviceChoice = Widget::create<AudioDeviceChoice>(pos); | |||
| deviceChoice->audioWidget = this; | |||
| addChild(deviceChoice); | |||
| pos = deviceChoice->box.getBottomLeft(); | |||
| internal->deviceChoice = deviceChoice; | |||
| this->deviceChoice = deviceChoice; | |||
| internal->deviceSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(internal->deviceSeparator); | |||
| this->deviceSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(this->deviceSeparator); | |||
| AudioSampleRateChoice *sampleRateChoice = Widget::create<AudioSampleRateChoice>(pos); | |||
| sampleRateChoice->audioWidget = this; | |||
| addChild(sampleRateChoice); | |||
| internal->sampleRateChoice = sampleRateChoice; | |||
| this->sampleRateChoice = sampleRateChoice; | |||
| internal->sampleRateSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| internal->sampleRateSeparator->box.size.y = internal->sampleRateChoice->box.size.y; | |||
| addChild(internal->sampleRateSeparator); | |||
| this->sampleRateSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| this->sampleRateSeparator->box.size.y = this->sampleRateChoice->box.size.y; | |||
| addChild(this->sampleRateSeparator); | |||
| AudioBlockSizeChoice *bufferSizeChoice = Widget::create<AudioBlockSizeChoice>(pos); | |||
| bufferSizeChoice->audioWidget = this; | |||
| addChild(bufferSizeChoice); | |||
| internal->bufferSizeChoice = bufferSizeChoice; | |||
| } | |||
| AudioWidget::~AudioWidget() { | |||
| delete internal; | |||
| this->bufferSizeChoice = bufferSizeChoice; | |||
| } | |||
| void AudioWidget::step() { | |||
| internal->driverChoice->box.size.x = box.size.x; | |||
| internal->driverSeparator->box.size.x = box.size.x; | |||
| internal->deviceChoice->box.size.x = box.size.x; | |||
| internal->deviceSeparator->box.size.x = box.size.x; | |||
| internal->sampleRateChoice->box.size.x = box.size.x / 2; | |||
| internal->sampleRateSeparator->box.pos.x = box.size.x / 2; | |||
| internal->bufferSizeChoice->box.pos.x = box.size.x / 2; | |||
| internal->bufferSizeChoice->box.size.x = box.size.x / 2; | |||
| this->driverChoice->box.size.x = box.size.x; | |||
| this->driverSeparator->box.size.x = box.size.x; | |||
| this->deviceChoice->box.size.x = box.size.x; | |||
| this->deviceSeparator->box.size.x = box.size.x; | |||
| this->sampleRateChoice->box.size.x = box.size.x / 2; | |||
| this->sampleRateSeparator->box.pos.x = box.size.x / 2; | |||
| this->bufferSizeChoice->box.pos.x = box.size.x / 2; | |||
| this->bufferSizeChoice->box.size.x = box.size.x / 2; | |||
| LedDisplay::step(); | |||
| } | |||
| @@ -34,6 +34,7 @@ LedDisplayChoice::LedDisplayChoice() { | |||
| box.size = mm2px(Vec(0, 28.0 / 3)); | |||
| font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); | |||
| color = nvgRGB(0xff, 0xd7, 0x14); | |||
| textOffset = Vec(10, 18); | |||
| } | |||
| void LedDisplayChoice::draw(NVGcontext *vg) { | |||
| @@ -44,13 +45,12 @@ void LedDisplayChoice::draw(NVGcontext *vg) { | |||
| nvgFontFaceId(vg, font->handle); | |||
| nvgTextLetterSpacing(vg, 0.0); | |||
| Vec textPos = Vec(10, 18); | |||
| nvgFontSize(vg, 12); | |||
| nvgText(vg, textPos.x, textPos.y, text.c_str(), NULL); | |||
| nvgText(vg, textOffset.x, textOffset.y, text.c_str(), NULL); | |||
| } | |||
| void LedDisplayChoice::onMouseDown(EventMouseDown &e) { | |||
| if (e.button == 1) { | |||
| if (e.button == 0 || e.button == 1) { | |||
| EventAction eAction; | |||
| onAction(eAction); | |||
| e.consumed = true; | |||
| @@ -62,9 +62,9 @@ void LedDisplayChoice::onMouseDown(EventMouseDown &e) { | |||
| LedDisplayTextField::LedDisplayTextField() { | |||
| font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); | |||
| color = nvgRGB(0xff, 0xd7, 0x14); | |||
| textOffset = Vec(5, 5); | |||
| } | |||
| static const Vec textFieldPos = Vec(5, 5); | |||
| void LedDisplayTextField::draw(NVGcontext *vg) { | |||
| // Background | |||
| @@ -82,8 +82,8 @@ void LedDisplayTextField::draw(NVGcontext *vg) { | |||
| NVGcolor highlightColor = color; | |||
| highlightColor.a = 0.5; | |||
| int cend = (this == gFocusedWidget) ? end : -1; | |||
| bndIconLabelCaret(vg, textFieldPos.x, textFieldPos.y, | |||
| box.size.x - 2*textFieldPos.x, box.size.y - 2*textFieldPos.y, | |||
| bndIconLabelCaret(vg, textOffset.x, textOffset.y, | |||
| box.size.x - 2*textOffset.x, box.size.y - 2*textOffset.y, | |||
| -1, color, 12, text.c_str(), highlightColor, begin, cend); | |||
| bndSetFont(gGuiFont->handle); | |||
| @@ -91,8 +91,8 @@ void LedDisplayTextField::draw(NVGcontext *vg) { | |||
| int LedDisplayTextField::getTextPosition(Vec mousePos) { | |||
| bndSetFont(font->handle); | |||
| int textPos = bndIconLabelTextPosition(gVg, textFieldPos.x, textFieldPos.y, | |||
| box.size.x - 2*textFieldPos.x, box.size.y - 2*textFieldPos.y, | |||
| int textPos = bndIconLabelTextPosition(gVg, textOffset.x, textOffset.y, | |||
| box.size.x - 2*textOffset.x, box.size.y - 2*textOffset.y, | |||
| -1, 12, text.c_str(), mousePos.x, mousePos.y); | |||
| bndSetFont(gGuiFont->handle); | |||
| return textPos; | |||
| @@ -9,26 +9,26 @@ struct MidiDriverItem : MenuItem { | |||
| MidiIO *midiIO; | |||
| int driver; | |||
| void onAction(EventAction &e) override { | |||
| // midiIO->openDriver(device); | |||
| midiIO->setDriver(driver); | |||
| } | |||
| }; | |||
| struct MidiDriverChoice : LedDisplayChoice { | |||
| MidiWidget *midiWidget; | |||
| void onAction(EventAction &e) override { | |||
| // Menu *menu = gScene->createMenu(); | |||
| // menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Audio driver")); | |||
| // for (int driver : audioWidget->audioIO->listDrivers()) { | |||
| // AudioDriverItem *item = new AudioDriverItem(); | |||
| // item->audioIO = audioWidget->audioIO; | |||
| // item->driver = driver; | |||
| // item->text = audioWidget->audioIO->getDriverName(driver); | |||
| // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); | |||
| // menu->addChild(item); | |||
| // } | |||
| Menu *menu = gScene->createMenu(); | |||
| menu->addChild(construct<MenuLabel>(&MenuLabel::text, "MIDI driver")); | |||
| for (int driver : midiWidget->midiIO->getDrivers()) { | |||
| MidiDriverItem *item = new MidiDriverItem(); | |||
| item->midiIO = midiWidget->midiIO; | |||
| item->driver = driver; | |||
| item->text = midiWidget->midiIO->getDriverName(driver); | |||
| item->rightText = CHECKMARK(item->driver == midiWidget->midiIO->driver); | |||
| menu->addChild(item); | |||
| } | |||
| } | |||
| void step() override { | |||
| // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); | |||
| text = midiWidget->midiIO->getDriverName(midiWidget->midiIO->driver); | |||
| } | |||
| }; | |||
| @@ -36,26 +36,28 @@ struct MidiDeviceItem : MenuItem { | |||
| MidiIO *midiIO; | |||
| int device; | |||
| void onAction(EventAction &e) override { | |||
| // midiIO->openDevice(device); | |||
| midiIO->setDevice(device); | |||
| } | |||
| }; | |||
| struct MidiDeviceChoice : LedDisplayChoice { | |||
| MidiWidget *midiWidget; | |||
| void onAction(EventAction &e) override { | |||
| // Menu *menu = gScene->createMenu(); | |||
| // menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Audio driver")); | |||
| // for (int driver : audioWidget->audioIO->listDrivers()) { | |||
| // AudioDriverItem *item = new AudioDriverItem(); | |||
| // item->audioIO = audioWidget->audioIO; | |||
| // item->driver = driver; | |||
| // item->text = audioWidget->audioIO->getDriverName(driver); | |||
| // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); | |||
| // menu->addChild(item); | |||
| // } | |||
| Menu *menu = gScene->createMenu(); | |||
| menu->addChild(construct<MenuLabel>(&MenuLabel::text, "MIDI device")); | |||
| int driverCount = midiWidget->midiIO->getDeviceCount(); | |||
| for (int device = 0; device < driverCount; device++) { | |||
| MidiDeviceItem *item = new MidiDeviceItem(); | |||
| item->midiIO = midiWidget->midiIO; | |||
| item->device = device; | |||
| item->text = midiWidget->midiIO->getDeviceName(device); | |||
| item->rightText = CHECKMARK(item->device == midiWidget->midiIO->device); | |||
| menu->addChild(item); | |||
| } | |||
| } | |||
| void step() override { | |||
| // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); | |||
| text = midiWidget->midiIO->getDeviceName(midiWidget->midiIO->device); | |||
| text = ellipsize(text, 14); | |||
| } | |||
| }; | |||
| @@ -63,40 +65,31 @@ struct MidiChannelItem : MenuItem { | |||
| MidiIO *midiIO; | |||
| int channel; | |||
| void onAction(EventAction &e) override { | |||
| // midiIO->channel = channel; | |||
| midiIO->channel = channel; | |||
| } | |||
| }; | |||
| struct MidiChannelChoice : LedDisplayChoice { | |||
| MidiWidget *midiWidget; | |||
| void onAction(EventAction &e) override { | |||
| // Menu *menu = gScene->createMenu(); | |||
| // menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Audio driver")); | |||
| // for (int driver : audioWidget->audioIO->listDrivers()) { | |||
| // AudioDriverItem *item = new AudioDriverItem(); | |||
| // item->audioIO = audioWidget->audioIO; | |||
| // item->driver = driver; | |||
| // item->text = audioWidget->audioIO->getDriverName(driver); | |||
| // item->rightText = CHECKMARK(item->driver == audioWidget->audioIO->driver); | |||
| // menu->addChild(item); | |||
| // } | |||
| Menu *menu = gScene->createMenu(); | |||
| menu->addChild(construct<MenuLabel>(&MenuLabel::text, "MIDI channel")); | |||
| for (int channel = -1; channel < 16; channel++) { | |||
| MidiChannelItem *item = new MidiChannelItem(); | |||
| item->midiIO = midiWidget->midiIO; | |||
| item->channel = channel; | |||
| item->text = midiWidget->midiIO->getChannelName(channel); | |||
| item->rightText = CHECKMARK(item->channel == midiWidget->midiIO->channel); | |||
| menu->addChild(item); | |||
| } | |||
| } | |||
| void step() override { | |||
| // text = audioWidget->audioIO->getDriverName(audioWidget->audioIO->driver); | |||
| text = midiWidget->midiIO->getChannelName(midiWidget->midiIO->channel); | |||
| } | |||
| }; | |||
| struct MidiWidget::Internal { | |||
| LedDisplayChoice *driverChoice; | |||
| LedDisplaySeparator *driverSeparator; | |||
| LedDisplayChoice *deviceChoice; | |||
| LedDisplaySeparator *deviceSeparator; | |||
| LedDisplayChoice *channelChoice; | |||
| }; | |||
| MidiWidget::MidiWidget() { | |||
| internal = new Internal(); | |||
| box.size = mm2px(Vec(44, 28)); | |||
| Vec pos = Vec(); | |||
| @@ -105,85 +98,34 @@ MidiWidget::MidiWidget() { | |||
| driverChoice->midiWidget = this; | |||
| addChild(driverChoice); | |||
| pos = driverChoice->box.getBottomLeft(); | |||
| internal->driverChoice = driverChoice; | |||
| this->driverChoice = driverChoice; | |||
| internal->driverSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(internal->driverSeparator); | |||
| this->driverSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(this->driverSeparator); | |||
| MidiDeviceChoice *deviceChoice = Widget::create<MidiDeviceChoice>(pos); | |||
| deviceChoice->midiWidget = this; | |||
| addChild(deviceChoice); | |||
| pos = deviceChoice->box.getBottomLeft(); | |||
| internal->deviceChoice = deviceChoice; | |||
| this->deviceChoice = deviceChoice; | |||
| internal->deviceSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(internal->deviceSeparator); | |||
| this->deviceSeparator = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(this->deviceSeparator); | |||
| MidiChannelChoice *channelChoice = Widget::create<MidiChannelChoice>(pos); | |||
| channelChoice->midiWidget = this; | |||
| addChild(channelChoice); | |||
| internal->channelChoice = channelChoice; | |||
| } | |||
| MidiWidget::~MidiWidget() { | |||
| delete internal; | |||
| this->channelChoice = channelChoice; | |||
| } | |||
| void MidiWidget::step() { | |||
| internal->driverChoice->box.size.x = box.size.x; | |||
| internal->driverSeparator->box.size.x = box.size.x; | |||
| internal->deviceChoice->box.size.x = box.size.x; | |||
| internal->deviceSeparator->box.size.x = box.size.x; | |||
| internal->channelChoice->box.size.x = box.size.x; | |||
| this->driverChoice->box.size.x = box.size.x; | |||
| this->driverSeparator->box.size.x = box.size.x; | |||
| this->deviceChoice->box.size.x = box.size.x; | |||
| this->deviceSeparator->box.size.x = box.size.x; | |||
| this->channelChoice->box.size.x = box.size.x; | |||
| LedDisplay::step(); | |||
| } | |||
| /* | |||
| void MidiWidget::onAction(EventAction &e) { | |||
| if (!midiIO) | |||
| return; | |||
| Menu *menu = gScene->createMenu(); | |||
| menu->addChild(construct<MenuLabel>(&MenuLabel::text, "MIDI device")); | |||
| { | |||
| MidiDeviceItem *item = new MidiDeviceItem(); | |||
| item->midiIO = midiIO; | |||
| item->device = -1; | |||
| item->text = "No device"; | |||
| item->rightText = CHECKMARK(item->device == midiIO->device); | |||
| menu->addChild(item); | |||
| } | |||
| for (int device = 0; device < midiIO->getDeviceCount(); device++) { | |||
| MidiDeviceItem *item = new MidiDeviceItem(); | |||
| item->midiIO = midiIO; | |||
| item->device = device; | |||
| item->text = midiIO->getDeviceName(device); | |||
| item->rightText = CHECKMARK(item->device == midiIO->device); | |||
| menu->addChild(item); | |||
| } | |||
| menu->addChild(construct<MenuEntry>()); | |||
| menu->addChild(construct<MenuLabel>(&MenuLabel::text, "MIDI channel")); | |||
| MidiInput *midiInput = dynamic_cast<MidiInput*>(midiIO); | |||
| if (midiInput) { | |||
| MidiChannelItem *item = new MidiChannelItem(); | |||
| item->midiIO = midiIO; | |||
| item->channel = -1; | |||
| item->text = "All"; | |||
| item->rightText = CHECKMARK(item->channel == midiIO->channel); | |||
| menu->addChild(item); | |||
| } | |||
| for (int channel = 0; channel < 16; channel++) { | |||
| MidiChannelItem *item = new MidiChannelItem(); | |||
| item->midiIO = midiIO; | |||
| item->channel = channel; | |||
| item->text = stringf("%d", channel + 1); | |||
| item->rightText = CHECKMARK(item->channel == midiIO->channel); | |||
| menu->addChild(item); | |||
| } | |||
| } | |||
| */ | |||
| } // namespace rack | |||
| @@ -17,7 +17,7 @@ AudioIO::~AudioIO() { | |||
| closeStream(); | |||
| } | |||
| std::vector<int> AudioIO::listDrivers() { | |||
| std::vector<int> AudioIO::getDrivers() { | |||
| std::vector<RtAudio::Api> apis; | |||
| RtAudio::getCompiledApi(apis); | |||
| std::vector<int> drivers; | |||
| @@ -39,7 +39,7 @@ std::string AudioIO::getDriverName(int driver) { | |||
| case RtAudio::WINDOWS_WASAPI: return "WASAPI"; | |||
| case RtAudio::WINDOWS_ASIO: return "ASIO"; | |||
| case RtAudio::WINDOWS_DS: return "DirectSound"; | |||
| case RtAudio::RTAUDIO_DUMMY: return "Dummy"; | |||
| case RtAudio::RTAUDIO_DUMMY: return "Dummy Audio"; | |||
| case BRIDGE_DRIVER: return "Bridge"; | |||
| default: return "Unknown"; | |||
| } | |||
| @@ -245,7 +245,7 @@ bool AudioIO::isActive() { | |||
| } | |||
| std::vector<int> AudioIO::listSampleRates() { | |||
| std::vector<int> AudioIO::getSampleRates() { | |||
| if (rtAudio) { | |||
| try { | |||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | |||
| @@ -1,18 +1,11 @@ | |||
| #include <list> | |||
| #include <algorithm> | |||
| #include "core.hpp" | |||
| #include "midi.hpp" | |||
| #include "dsp/digital.hpp" | |||
| #include "dsp/filter.hpp" | |||
| /* | |||
| * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to | |||
| * CV | |||
| */ | |||
| struct MidiValue { | |||
| int val = 0; // Controller value | |||
| // TransitionSmoother tSmooth; | |||
| bool changed = false; // Value has been changed by midi message (only if it is in sync!) | |||
| struct MidiNoteData { | |||
| uint8_t velocity; | |||
| uint8_t aftertouch; | |||
| }; | |||
| @@ -42,109 +35,160 @@ struct MIDIToCVInterface : Module { | |||
| NUM_LIGHTS | |||
| }; | |||
| MidiInput midiIO; | |||
| MidiInputQueue midiInput; | |||
| std::list<int> notes; | |||
| bool pedal = false; | |||
| int note = 60; // C4, most modules should use 261.626 Hz | |||
| int vel = 0; | |||
| MidiValue mod; | |||
| MidiValue afterTouch; | |||
| MidiValue pitchWheel; | |||
| bool gate = false; | |||
| SchmittTrigger resetTrigger; | |||
| MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
| pitchWheel.val = 64; | |||
| // pitchWheel.tSmooth.set(0, 0); | |||
| } | |||
| ~MIDIToCVInterface() { | |||
| }; | |||
| uint8_t mod = 0; | |||
| ExponentialFilter modFilter; | |||
| uint16_t pitch = 0; | |||
| ExponentialFilter pitchFilter; | |||
| void step() override; | |||
| MidiNoteData noteData[128]; | |||
| void pressNote(int note); | |||
| MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | |||
| void releaseNote(int note); | |||
| void step() override { | |||
| MidiMessage msg; | |||
| while (midiInput.shift(&msg)) { | |||
| processMessage(msg); | |||
| } | |||
| void processMidi(std::vector<unsigned char> msg); | |||
| modFilter.lambda = 100.f * engineGetSampleTime(); | |||
| outputs[MOD_OUTPUT].value = modFilter.process(rescale(mod, 0, 127, 0.f, 10.f)); | |||
| json_t *toJson() override { | |||
| json_t *rootJ = json_object(); | |||
| // addBaseJson(rootJ); | |||
| return rootJ; | |||
| } | |||
| pitchFilter.lambda = 100.f * engineGetSampleTime(); | |||
| outputs[PITCH_OUTPUT].value = pitchFilter.process(rescale(pitch, 0, 16384, -5.f, 5.f)); | |||
| void fromJson(json_t *rootJ) override { | |||
| // baseFromJson(rootJ); | |||
| } | |||
| /* | |||
| if (isPortOpen()) { | |||
| std::vector<unsigned char> message; | |||
| void onReset() override { | |||
| // resetMidi(); | |||
| } | |||
| // midiIn->getMessage returns empty vector if there are no messages in the queue | |||
| getMessage(&message); | |||
| if (message.size() > 0) { | |||
| processMidi(message); | |||
| } | |||
| } | |||
| // void resetMidi() override; | |||
| }; | |||
| outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; | |||
| /* | |||
| void MIDIToCVInterface::resetMidi() { | |||
| mod.val = 0; | |||
| mod.tSmooth.set(0, 0); | |||
| pitchWheel.val = 64; | |||
| pitchWheel.tSmooth.set(0, 0); | |||
| afterTouch.val = 0; | |||
| afterTouch.tSmooth.set(0, 0); | |||
| vel = 0; | |||
| gate = false; | |||
| notes.clear(); | |||
| } | |||
| */ | |||
| if (resetTrigger.process(params[RESET_PARAM].value)) { | |||
| resetMidi(); | |||
| return; | |||
| } | |||
| lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / 0.55 / engineGetSampleRate(); // fade out light | |||
| outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; | |||
| outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; | |||
| void MIDIToCVInterface::step() { | |||
| /* | |||
| if (isPortOpen()) { | |||
| std::vector<unsigned char> message; | |||
| int steps = int(engineGetSampleRate() / 32); | |||
| // midiIn->getMessage returns empty vector if there are no messages in the queue | |||
| getMessage(&message); | |||
| if (message.size() > 0) { | |||
| processMidi(message); | |||
| if (mod.changed) { | |||
| mod.tSmooth.set(outputs[MOD_OUTPUT].value, (mod.val / 127.0 * 10.0), steps); | |||
| mod.changed = false; | |||
| } | |||
| } | |||
| outputs[MOD_OUTPUT].value = mod.tSmooth.next(); | |||
| outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; | |||
| if (pitchWheel.changed) { | |||
| pitchWheel.tSmooth.set(outputs[PITCHWHEEL_OUTPUT].value, (pitchWheel.val - 64) / 64.0 * 10.0, steps); | |||
| pitchWheel.changed = false; | |||
| } | |||
| outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); | |||
| if (resetTrigger.process(params[RESET_PARAM].value)) { | |||
| resetMidi(); | |||
| return; | |||
| outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; | |||
| */ | |||
| } | |||
| lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / 0.55 / engineGetSampleRate(); // fade out light | |||
| void processMessage(MidiMessage msg) { | |||
| debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.data1, msg.data2); | |||
| switch (msg.status()) { | |||
| // note off | |||
| case 0x8: { | |||
| // releaseNote(msg.data1); | |||
| } break; | |||
| // note on | |||
| case 0x9: { | |||
| if (msg.data2 > 0) { | |||
| uint8_t note = msg.data1 & 0x7f; | |||
| noteData[note].velocity = msg.data2; | |||
| noteData[note].aftertouch = 0; | |||
| // pressNote(msg.data1); | |||
| } | |||
| else { | |||
| // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released. | |||
| // releaseNote(msg.data1); | |||
| } | |||
| } break; | |||
| // cc | |||
| case 0xb: { | |||
| processCC(msg); | |||
| } break; | |||
| // pitch wheel | |||
| case 0xe: { | |||
| pitch = msg.data2 * 128 + msg.data1; | |||
| } break; | |||
| // channel aftertouch | |||
| case 0xd: { | |||
| // TODO This is pressure, not aftertouch. | |||
| // aftertouch = rescale(msg.data1, 0.f, 128.f, 0.f, 10.f); | |||
| } break; | |||
| case 0xf: { | |||
| processSystem(msg); | |||
| } break; | |||
| default: break; | |||
| } | |||
| } | |||
| outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; | |||
| outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; | |||
| void processCC(MidiMessage msg) { | |||
| switch (msg.data1) { | |||
| // mod | |||
| case 0x01: { | |||
| mod = msg.data2; | |||
| } break; | |||
| // sustain | |||
| case 0x40: { | |||
| // pedal = (msg.data2 >= 64); | |||
| // if (!pedal) { | |||
| // releaseNote(-1); | |||
| // } | |||
| } break; | |||
| default: break; | |||
| } | |||
| } | |||
| int steps = int(engineGetSampleRate() / 32); | |||
| void processSystem(MidiMessage msg) { | |||
| switch (msg.channel()) { | |||
| case 0x8: { | |||
| debug("timing clock"); | |||
| } break; | |||
| case 0xa: { | |||
| debug("start"); | |||
| } break; | |||
| case 0xb: { | |||
| debug("continue"); | |||
| } break; | |||
| case 0xc: { | |||
| debug("stop"); | |||
| } break; | |||
| default: break; | |||
| } | |||
| } | |||
| if (mod.changed) { | |||
| mod.tSmooth.set(outputs[MOD_OUTPUT].value, (mod.val / 127.0 * 10.0), steps); | |||
| mod.changed = false; | |||
| json_t *toJson() override { | |||
| json_t *rootJ = json_object(); | |||
| json_object_set_new(rootJ, "midi", midiInput.toJson()); | |||
| return rootJ; | |||
| } | |||
| outputs[MOD_OUTPUT].value = mod.tSmooth.next(); | |||
| if (pitchWheel.changed) { | |||
| pitchWheel.tSmooth.set(outputs[PITCHWHEEL_OUTPUT].value, (pitchWheel.val - 64) / 64.0 * 10.0, steps); | |||
| pitchWheel.changed = false; | |||
| void fromJson(json_t *rootJ) override { | |||
| json_t *midiJ = json_object_get(rootJ, "midi"); | |||
| midiInput.fromJson(midiJ); | |||
| } | |||
| outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); | |||
| }; | |||
| outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; | |||
| */ | |||
| } | |||
| /* | |||
| void MIDIToCVInterface::pressNote(int note) { | |||
| // Remove existing similar note | |||
| auto it = std::find(notes.begin(), notes.end(), note); | |||
| @@ -175,60 +219,7 @@ void MIDIToCVInterface::releaseNote(int note) { | |||
| gate = false; | |||
| } | |||
| } | |||
| void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
| /* | |||
| int channel = msg[0] & 0xf; | |||
| int status = (msg[0] >> 4) & 0xf; | |||
| int data1 = msg[1]; | |||
| int data2 = msg[2]; | |||
| // fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1, data2); | |||
| // Filter channels | |||
| if (this->channel >= 0 && this->channel != channel) | |||
| return; | |||
| switch (status) { | |||
| // note off | |||
| case 0x8: { | |||
| releaseNote(data1); | |||
| } | |||
| break; | |||
| case 0x9: // note on | |||
| if (data2 > 0) { | |||
| pressNote(data1); | |||
| this->vel = data2; | |||
| } else { | |||
| // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released. | |||
| releaseNote(data1); | |||
| } | |||
| break; | |||
| case 0xb: // cc | |||
| switch (data1) { | |||
| case 0x01: // mod | |||
| mod.val = data2; | |||
| mod.changed = true; | |||
| break; | |||
| case 0x40: // sustain | |||
| pedal = (data2 >= 64); | |||
| if (!pedal) { | |||
| releaseNote(-1); | |||
| } | |||
| break; | |||
| } | |||
| break; | |||
| case 0xe: // pitch wheel | |||
| pitchWheel.val = data2; | |||
| pitchWheel.changed = true; | |||
| break; | |||
| case 0xd: // channel aftertouch | |||
| afterTouch.val = data1; | |||
| afterTouch.changed = true; | |||
| break; | |||
| } | |||
| */ | |||
| } | |||
| */ | |||
| struct MIDIToCVInterfaceWidget : ModuleWidget { | |||
| @@ -255,10 +246,10 @@ struct MIDIToCVInterfaceWidget : ModuleWidget { | |||
| MidiWidget *midiWidget = Widget::create<MidiWidget>(mm2px(Vec(3.41891, 14.8373))); | |||
| midiWidget->box.size = mm2px(Vec(33.840, 28)); | |||
| midiWidget->midiIO = &module->midiIO; | |||
| midiWidget->midiIO = &module->midiInput; | |||
| addChild(midiWidget); | |||
| } | |||
| }; | |||
| Model *modelMidiToCvInterface = Model::create<MIDIToCVInterface, MIDIToCVInterfaceWidget>("Core", "MIDIToCVInterface", "MIDI-1", MIDI_TAG, EXTERNAL_TAG); | |||
| Model *modelMIDIToCVInterface = Model::create<MIDIToCVInterface, MIDIToCVInterfaceWidget>("Core", "MIDIToCVInterface", "MIDI-1", MIDI_TAG, EXTERNAL_TAG); | |||
| @@ -1,328 +1,154 @@ | |||
| #if 0 | |||
| #include <list> | |||
| #include <algorithm> | |||
| #include "core.hpp" | |||
| #include "MidiIO.hpp" | |||
| #include "midi.hpp" | |||
| #include "dsp/filter.hpp" | |||
| struct CCValue { | |||
| int val = 0; // Controller value | |||
| TransitionSmoother tSmooth; | |||
| int num = 0; // Controller number | |||
| bool numInited = false; // Num inited by config file | |||
| bool numSelected = false; // Text field selected for midi learn | |||
| bool changed = false; // Value has been changed by midi message (only if it is in sync!) | |||
| int sync = 0; // Output value sync (implies diff) | |||
| bool syncFirst = true; // First value after sync was reset | |||
| void resetSync() { | |||
| sync = 0; | |||
| syncFirst = true; | |||
| struct CcChoice : LedDisplayChoice { | |||
| CcChoice() { | |||
| box.size.y = mm2px(6.666); | |||
| textOffset.y -= 4; | |||
| } | |||
| }; | |||
| struct MIDICCToCVInterface : MidiIO, Module { | |||
| enum ParamIds { | |||
| NUM_PARAMS | |||
| }; | |||
| enum InputIds { | |||
| NUM_INPUTS | |||
| }; | |||
| enum OutputIds { | |||
| NUM_OUTPUTS = 16 | |||
| }; | |||
| enum LightIds { | |||
| NUM_LIGHTS = 16 | |||
| }; | |||
| CCValue cc[NUM_OUTPUTS]; | |||
| struct CcMidiWidget : MidiWidget { | |||
| LedDisplaySeparator *hSeparators[4]; | |||
| LedDisplaySeparator *vSeparators[4]; | |||
| LedDisplayChoice *ccChoices[4][4]; | |||
| MIDICCToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
| for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
| cc[i].num = i; | |||
| CcMidiWidget() { | |||
| Vec pos = channelChoice->box.getBottomLeft(); | |||
| for (int x = 1; x < 4; x++) { | |||
| vSeparators[x] = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(vSeparators[x]); | |||
| } | |||
| } | |||
| ~MIDICCToCVInterface() {} | |||
| void step() override; | |||
| void processMidi(std::vector<unsigned char> msg); | |||
| void resetMidi() override; | |||
| json_t *toJson() override { | |||
| json_t *rootJ = json_object(); | |||
| addBaseJson(rootJ); | |||
| for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
| json_object_set_new(rootJ, ("ccNum" + std::to_string(i)).c_str(), json_integer(cc[i].num)); | |||
| if (outputs[i].active) { | |||
| json_object_set_new(rootJ, ("ccVal" + std::to_string(i)).c_str(), json_integer(cc[i].val)); | |||
| } | |||
| } | |||
| return rootJ; | |||
| } | |||
| void fromJson(json_t *rootJ) override { | |||
| baseFromJson(rootJ); | |||
| for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
| json_t *ccNumJ = json_object_get(rootJ, ("ccNum" + std::to_string(i)).c_str()); | |||
| if (ccNumJ) { | |||
| cc[i].num = json_integer_value(ccNumJ); | |||
| cc[i].numInited = true; | |||
| } | |||
| json_t *ccValJ = json_object_get(rootJ, ("ccVal" + std::to_string(i)).c_str()); | |||
| if (ccValJ) { | |||
| cc[i].val = json_integer_value(ccValJ); | |||
| cc[i].tSmooth.set((cc[i].val / 127.0 * 10.0), (cc[i].val / 127.0 * 10.0)); | |||
| cc[i].resetSync(); | |||
| for (int y = 0; y < 4; y++) { | |||
| hSeparators[y] = Widget::create<LedDisplaySeparator>(pos); | |||
| addChild(hSeparators[y]); | |||
| for (int x = 0; x < 4; x++) { | |||
| CcChoice *ccChoice = Widget::create<CcChoice>(pos); | |||
| ccChoice->text = stringf("%d", x*4+y); | |||
| ccChoices[x][y] = ccChoice; | |||
| addChild(ccChoice); | |||
| } | |||
| pos = ccChoices[0][y]->box.getBottomLeft(); | |||
| } | |||
| } | |||
| void onReset() override { | |||
| resetMidi(); | |||
| } | |||
| }; | |||
| void MIDICCToCVInterface::step() { | |||
| if (isPortOpen()) { | |||
| std::vector<unsigned char> message; | |||
| // midiIn->getMessage returns empty vector if there are no messages in the queue | |||
| getMessage(&message); | |||
| if (message.size() > 0) { | |||
| processMidi(message); | |||
| for (int x = 1; x < 4; x++) { | |||
| vSeparators[x]->box.size.y = pos.y - vSeparators[x]->box.pos.y; | |||
| } | |||
| } | |||
| for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
| lights[i].setBrightness(cc[i].sync / 127.0); | |||
| if (cc[i].changed) { | |||
| cc[i].tSmooth.set(outputs[i].value, (cc[i].val / 127.0 * 10.0), int(engineGetSampleRate() / 32)); | |||
| cc[i].changed = false; | |||
| void step() override { | |||
| MidiWidget::step(); | |||
| for (int x = 1; x < 4; x++) { | |||
| vSeparators[x]->box.pos.x = box.size.x / 4 * x; | |||
| } | |||
| outputs[i].value = cc[i].tSmooth.next(); | |||
| } | |||
| } | |||
| void MIDICCToCVInterface::resetMidi() { | |||
| for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
| cc[i].val = 0; | |||
| cc[i].resetSync(); | |||
| cc[i].tSmooth.set(0, 0); | |||
| } | |||
| }; | |||
| void MIDICCToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
| int channel = msg[0] & 0xf; | |||
| int status = (msg[0] >> 4) & 0xf; | |||
| int data1 = msg[1]; | |||
| int data2 = msg[2]; | |||
| //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2); | |||
| // Filter channels | |||
| if (this->channel >= 0 && this->channel != channel) | |||
| return; | |||
| if (status == 0xb) { | |||
| for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
| if (cc[i].numSelected) { | |||
| cc[i].resetSync(); | |||
| cc[i].num = data1; | |||
| } | |||
| if (data1 == cc[i].num) { | |||
| /* If the first value we received after sync was reset is +/- 1 of | |||
| * the output value the values are in sync*/ | |||
| if (cc[i].syncFirst) { | |||
| cc[i].syncFirst = false; | |||
| if (data2 < cc[i].val + 2 && data2 > cc[i].val - 2) { | |||
| cc[i].sync = 0; | |||
| } | |||
| else { | |||
| cc[i].sync = absi(data2 - cc[i].val); | |||
| } | |||
| return; | |||
| } | |||
| if (cc[i].sync == 0) { | |||
| cc[i].val = data2; | |||
| cc[i].changed = true; | |||
| } | |||
| else { | |||
| cc[i].sync = absi(data2 - cc[i].val); | |||
| } | |||
| for (int y = 0; y < 4; y++) { | |||
| hSeparators[y]->box.size.x = box.size.x; | |||
| for (int x = 0; x < 4; x++) { | |||
| ccChoices[x][y]->box.size.x = box.size.x / 4; | |||
| ccChoices[x][y]->box.pos.x = box.size.x / 4 * x; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| struct CCTextField : TextField { | |||
| void onTextChange() override; | |||
| void draw(NVGcontext *vg) override; | |||
| void onMouseDown(EventMouseDown &e) override; | |||
| void onMouseUp(EventMouseUp &e) override; | |||
| void onMouseLeave(EventMouseLeave &e) override; | |||
| int outNum; | |||
| MIDICCToCVInterface *module; | |||
| }; | |||
| void CCTextField::draw(NVGcontext *vg) { | |||
| /* This is necessary, since the save | |||
| * file is loaded after constructing the widget*/ | |||
| if (module->cc[outNum].numInited) { | |||
| module->cc[outNum].numInited = false; | |||
| text = std::to_string(module->cc[outNum].num); | |||
| } | |||
| /* If number is selected for midi learn*/ | |||
| if (module->cc[outNum].numSelected) { | |||
| text = std::to_string(module->cc[outNum].num); | |||
| } | |||
| TextField::draw(vg); | |||
| } | |||
| void CCTextField::onMouseUp(EventMouseUp &e) { | |||
| if (e.button == 1) { | |||
| module->cc[outNum].numSelected = false; | |||
| e.consumed = true; | |||
| } | |||
| TextField::onMouseUp(e); | |||
| } | |||
| void CCTextField::onMouseDown(EventMouseDown &e) { | |||
| if (e.button == 1) { | |||
| module->cc[outNum].numSelected = true; | |||
| e.consumed = true; | |||
| } | |||
| TextField::onMouseDown(e); | |||
| } | |||
| void CCTextField::onMouseLeave(EventMouseLeave &e) { | |||
| module->cc[outNum].numSelected = false; | |||
| e.consumed = true; | |||
| } | |||
| struct MIDICCToCVInterface : Module { | |||
| enum ParamIds { | |||
| NUM_PARAMS | |||
| }; | |||
| enum InputIds { | |||
| NUM_INPUTS | |||
| }; | |||
| enum OutputIds { | |||
| ENUMS(CC_OUTPUT, 16), | |||
| NUM_OUTPUTS | |||
| }; | |||
| enum LightIds { | |||
| NUM_LIGHTS | |||
| }; | |||
| MidiInputQueue midiInput; | |||
| uint8_t cvs[128] = {}; | |||
| ExponentialFilter ccFilters[16]; | |||
| void CCTextField::onTextChange() { | |||
| if (text.size() > 0) { | |||
| try { | |||
| int num = std::stoi(text); | |||
| // Only allow valid cc numbers | |||
| if (num < 0 || num > 127 || text.size() > 3) { | |||
| text = ""; | |||
| begin = end = 0; | |||
| module->cc[outNum].num = -1; | |||
| } | |||
| else { | |||
| module->cc[outNum].num = num; | |||
| module->cc[outNum].resetSync(); | |||
| } | |||
| MIDICCToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | |||
| void step() override { | |||
| MidiMessage msg; | |||
| while (midiInput.shift(&msg)) { | |||
| processMessage(msg); | |||
| } | |||
| catch (...) { | |||
| text = ""; | |||
| begin = end = 0; | |||
| module->cc[outNum].num = -1; | |||
| } | |||
| }; | |||
| } | |||
| MIDICCToCVWidget::MIDICCToCVWidget() { | |||
| MIDICCToCVInterface *module = new MIDICCToCVInterface(); | |||
| setModule(module); | |||
| box.size = Vec(16 * 15, 380); | |||
| { | |||
| Panel *panel = new LightPanel(); | |||
| panel->box.size = box.size; | |||
| addChild(panel); | |||
| float lambda = 100.f * engineGetSampleTime(); | |||
| for (int i = 0; i < 16; i++) { | |||
| float value = rescale(cvs[i], 0, 127, 0.f, 10.f); | |||
| ccFilters[i].lambda = lambda; | |||
| outputs[CC_OUTPUT + i].value = ccFilters[i].process(value); | |||
| } | |||
| } | |||
| float margin = 5; | |||
| float labelHeight = 15; | |||
| float yPos = margin; | |||
| void processMessage(MidiMessage msg) { | |||
| // debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.data1, msg.data2); | |||
| addChild(createScrew<ScrewSilver>(Vec(15, 0))); | |||
| addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||
| addChild(createScrew<ScrewSilver>(Vec(15, 365))); | |||
| addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||
| { | |||
| Label *label = new Label(); | |||
| label->box.pos = Vec(box.size.x - margin - 11 * 15, margin); | |||
| label->text = "MIDI CC to CV"; | |||
| addChild(label); | |||
| yPos = labelHeight * 2; | |||
| switch (msg.status()) { | |||
| // cc | |||
| case 0xb: { | |||
| uint8_t cc = msg.data1 & 0x7f; | |||
| uint8_t cv = msg.data2 & 0x7f; | |||
| cvs[cc] = cv; | |||
| } break; | |||
| default: break; | |||
| } | |||
| } | |||
| { | |||
| Label *label = new Label(); | |||
| label->box.pos = Vec(margin, yPos); | |||
| label->text = "MIDI Interface"; | |||
| addChild(label); | |||
| MidiChoice *midiChoice = new MidiChoice(); | |||
| midiChoice->midiModule = dynamic_cast<MidiIO *>(module); | |||
| midiChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos); | |||
| midiChoice->box.size.x = (box.size.x / 2.0) - margin; | |||
| addChild(midiChoice); | |||
| yPos += midiChoice->box.size.y + margin; | |||
| json_t *toJson() override { | |||
| json_t *rootJ = json_object(); | |||
| json_object_set_new(rootJ, "midi", midiInput.toJson()); | |||
| return rootJ; | |||
| } | |||
| { | |||
| Label *label = new Label(); | |||
| label->box.pos = Vec(margin, yPos); | |||
| label->text = "Channel"; | |||
| addChild(label); | |||
| ChannelChoice *channelChoice = new ChannelChoice(); | |||
| channelChoice->midiModule = dynamic_cast<MidiIO *>(module); | |||
| channelChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos); | |||
| channelChoice->box.size.x = (box.size.x / 2.0) - margin; | |||
| addChild(channelChoice); | |||
| yPos += channelChoice->box.size.y + margin * 3; | |||
| void fromJson(json_t *rootJ) override { | |||
| json_t *midiJ = json_object_get(rootJ, "midi"); | |||
| midiInput.fromJson(midiJ); | |||
| } | |||
| }; | |||
| for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) { | |||
| CCTextField *ccNumChoice = new CCTextField(); | |||
| ccNumChoice->module = module; | |||
| ccNumChoice->outNum = i; | |||
| ccNumChoice->text = std::to_string(module->cc[i].num); | |||
| ccNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos); | |||
| ccNumChoice->box.size.x = 29; | |||
| addChild(ccNumChoice); | |||
| yPos += labelHeight + margin; | |||
| addOutput(createOutput<PJ3410Port>(Vec((i % 4) * (63) + 10, yPos + 5), module, i)); | |||
| addChild(createLight<SmallLight<RedLight>>(Vec((i % 4) * (63) + 32, yPos + 5), module, i)); | |||
| struct MIDICCToCVInterfaceWidget : ModuleWidget { | |||
| MIDICCToCVInterfaceWidget(MIDICCToCVInterface *module) : ModuleWidget(module) { | |||
| setPanel(SVG::load(assetGlobal("res/Core/MIDICCToCVInterface.svg"))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.894335, 73.344704)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 0)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.494659, 73.344704)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 1)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.094982, 73.344704)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 2)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693932, 73.344704)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 3)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.8943355, 84.945023)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 4)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.49466, 84.945023)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 5)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.094982, 84.945023)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 6)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693932, 84.945023)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 7)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.8943343, 96.543976)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 8)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.494659, 96.543976)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 9)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.09498, 96.543976)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 10)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693932, 96.543976)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 11)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(3.894335, 108.14429)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 12)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(15.49466, 108.14429)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 13)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 14)); | |||
| addOutput(Port::create<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), Port::OUTPUT, module, MIDICCToCVInterface::CC_OUTPUT + 15)); | |||
| MidiWidget *midiWidget = Widget::create<CcMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||
| midiWidget->box.size = mm2px(Vec(44, 54.667)); | |||
| midiWidget->midiIO = &module->midiInput; | |||
| addChild(midiWidget); | |||
| if ((i + 1) % 4 == 0) { | |||
| yPos += 47 + margin; | |||
| } | |||
| else { | |||
| yPos -= labelHeight + margin; | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| void MIDICCToCVWidget::step() { | |||
| ModuleWidget::step(); | |||
| } | |||
| #endif | |||
| Model *modelMIDICCToCVInterface = Model::create<MIDICCToCVInterface, MIDICCToCVInterfaceWidget>("Core", "MIDICCToCVInterface", "MIDI-CC", MIDI_TAG, EXTERNAL_TAG); | |||
| @@ -6,7 +6,8 @@ void init(rack::Plugin *p) { | |||
| p->version = TOSTRING(VERSION); | |||
| p->addModel(modelAudioInterface); | |||
| p->addModel(modelMidiToCvInterface); | |||
| p->addModel(modelMIDIToCVInterface); | |||
| p->addModel(modelMIDICCToCVInterface); | |||
| p->addModel(modelBlank); | |||
| p->addModel(modelNotes); | |||
| @@ -5,7 +5,8 @@ using namespace rack; | |||
| extern Model *modelAudioInterface; | |||
| extern Model *modelMidiToCvInterface; | |||
| extern Model *modelMIDIToCVInterface; | |||
| extern Model *modelMIDICCToCVInterface; | |||
| extern Model *modelBlank; | |||
| extern Model *modelNotes; | |||
| @@ -8,9 +8,39 @@ namespace rack { | |||
| // MidiIO | |||
| //////////////////// | |||
| MidiIO::~MidiIO() { | |||
| setDriver(-1); | |||
| } | |||
| 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); | |||
| } | |||
| // drivers.push_back(BRIDGE_DRIVER) | |||
| return drivers; | |||
| } | |||
| std::string MidiIO::getDriverName(int driver) { | |||
| switch (driver) { | |||
| case RtMidi::UNSPECIFIED: return "Unspecified"; | |||
| case RtMidi::MACOSX_CORE: return "Core MIDI"; | |||
| case RtMidi::LINUX_ALSA: return "ALSA"; | |||
| case RtMidi::UNIX_JACK: return "JACK"; | |||
| case RtMidi::WINDOWS_MM: return "Windows MIDI"; | |||
| case RtMidi::RTMIDI_DUMMY: return "Dummy MIDI"; | |||
| // case BRIDGE_DRIVER: return "Bridge"; | |||
| default: return "Unknown"; | |||
| } | |||
| } | |||
| int MidiIO::getDeviceCount() { | |||
| if (rtMidi) | |||
| if (rtMidi) { | |||
| return rtMidi->getPortCount(); | |||
| } | |||
| return 0; | |||
| } | |||
| @@ -18,22 +48,33 @@ std::string MidiIO::getDeviceName(int device) { | |||
| if (rtMidi) { | |||
| if (device < 0) | |||
| return ""; | |||
| return rtMidi->getPortName(device); | |||
| if (device == this->device) | |||
| return deviceName; | |||
| else | |||
| return rtMidi->getPortName(device); | |||
| } | |||
| return ""; | |||
| } | |||
| void MidiIO::openDevice(int device) { | |||
| void MidiIO::setDevice(int device) { | |||
| if (rtMidi) { | |||
| rtMidi->closePort(); | |||
| if (device >= 0) { | |||
| rtMidi->openPort(device); | |||
| deviceName = rtMidi->getPortName(device); | |||
| } | |||
| this->device = device; | |||
| } | |||
| } | |||
| std::string MidiIO::getChannelName(int channel) { | |||
| if (channel == -1) | |||
| return "All channels"; | |||
| else | |||
| return stringf("Channel %d", channel + 1); | |||
| } | |||
| bool MidiIO::isActive() { | |||
| if (rtMidi) | |||
| return rtMidi->isPortOpen(); | |||
| @@ -42,20 +83,27 @@ bool MidiIO::isActive() { | |||
| json_t *MidiIO::toJson() { | |||
| json_t *rootJ = json_object(); | |||
| json_object_set_new(rootJ, "driver", json_integer(driver)); | |||
| std::string deviceName = getDeviceName(device); | |||
| json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); | |||
| if (!deviceName.empty()) | |||
| json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); | |||
| json_object_set_new(rootJ, "channel", json_integer(channel)); | |||
| return rootJ; | |||
| } | |||
| void MidiIO::fromJson(json_t *rootJ) { | |||
| json_t *driverJ = json_object_get(rootJ, "driver"); | |||
| if (driverJ) | |||
| setDriver(json_integer_value(driverJ)); | |||
| json_t *deviceNameJ = json_object_get(rootJ, "deviceName"); | |||
| if (deviceNameJ) { | |||
| std::string deviceName = json_string_value(deviceNameJ); | |||
| // Search for device with equal name | |||
| for (int device = 0; device < getDeviceCount(); device++) { | |||
| int deviceCount = getDeviceCount(); | |||
| for (int device = 0; device < deviceCount; device++) { | |||
| if (getDeviceName(device) == deviceName) { | |||
| openDevice(device); | |||
| setDevice(device); | |||
| break; | |||
| } | |||
| } | |||
| @@ -77,42 +125,70 @@ static void midiInputCallback(double timeStamp, std::vector<unsigned char> *mess | |||
| MidiInput *midiInput = (MidiInput*) userData; | |||
| if (!midiInput) return; | |||
| MidiMessage midiMessage; | |||
| midiMessage.time = timeStamp; | |||
| midiMessage.data = *message; | |||
| if (message->size() >= 1) | |||
| midiMessage.cmd = (*message)[0]; | |||
| if (message->size() >= 2) | |||
| midiMessage.data1 = (*message)[1]; | |||
| if (message->size() >= 3) | |||
| midiMessage.data2 = (*message)[2]; | |||
| midiInput->onMessage(midiMessage); | |||
| } | |||
| MidiInput::MidiInput() { | |||
| rtMidiIn = new RtMidiIn(); | |||
| rtMidi = rtMidiIn; | |||
| rtMidiIn->setCallback(midiInputCallback, this); | |||
| setDriver(RtMidi::UNSPECIFIED); | |||
| } | |||
| MidiInput::~MidiInput() { | |||
| delete rtMidiIn; | |||
| void MidiInput::setDriver(int driver) { | |||
| setDevice(-1); | |||
| if (rtMidiIn) { | |||
| delete rtMidiIn; | |||
| rtMidi = rtMidiIn = NULL; | |||
| } | |||
| if (driver >= 0) { | |||
| rtMidiIn = new RtMidiIn((RtMidi::Api) driver); | |||
| rtMidiIn->setCallback(midiInputCallback, this); | |||
| rtMidi = rtMidiIn; | |||
| this->driver = rtMidiIn->getCurrentApi(); | |||
| } | |||
| } | |||
| void MidiInputQueue::onMessage(const MidiMessage &message) { | |||
| for (uint8_t d : message.data) { | |||
| debug("MIDI message: %02x", d); | |||
| } | |||
| if ((int) queue.size() < queueSize) | |||
| queue.push(message); | |||
| } | |||
| const int messageQueueSize = 8192; | |||
| if (messageQueue.size() < messageQueueSize) | |||
| messageQueue.push(message); | |||
| bool MidiInputQueue::shift(MidiMessage *message) { | |||
| if (!message) return false; | |||
| if (!queue.empty()) { | |||
| *message = queue.front(); | |||
| queue.pop(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| //////////////////// | |||
| // MidiOutput | |||
| //////////////////// | |||
| MidiOutput::MidiOutput() { | |||
| rtMidiOut = new RtMidiOut(); | |||
| rtMidi = rtMidiOut; | |||
| setDriver(RtMidi::UNSPECIFIED); | |||
| } | |||
| MidiOutput::~MidiOutput() { | |||
| delete rtMidiOut; | |||
| void MidiOutput::setDriver(int driver) { | |||
| setDevice(-1); | |||
| if (rtMidiOut) { | |||
| delete rtMidiOut; | |||
| rtMidi = rtMidiOut = NULL; | |||
| } | |||
| if (driver >= 0) { | |||
| rtMidiOut = new RtMidiOut((RtMidi::Api) driver); | |||
| rtMidi = rtMidiOut; | |||
| this->driver = rtMidiOut->getCurrentApi(); | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| #include "widgets.hpp" | |||
| #include "ui.hpp" | |||
| #include "window.hpp" | |||
| @@ -48,8 +48,10 @@ void TextField::onFocus(EventFocus &e) { | |||
| } | |||
| void TextField::onText(EventText &e) { | |||
| std::string newText(1, (char) e.codepoint); | |||
| insertText(newText); | |||
| if (e.codepoint < 128) { | |||
| std::string newText(1, (char) e.codepoint); | |||
| insertText(newText); | |||
| } | |||
| e.consumed = true; | |||
| } | |||
| @@ -110,6 +112,15 @@ void TextField::onKey(EventKey &e) { | |||
| insertText(newText); | |||
| } | |||
| break; | |||
| case GLFW_KEY_X: | |||
| if (windowIsModPressed()) { | |||
| if (begin < end) { | |||
| std::string selectedText = text.substr(begin, end - begin); | |||
| glfwSetClipboardString(gWindow, selectedText.c_str()); | |||
| insertText(""); | |||
| } | |||
| } | |||
| break; | |||
| case GLFW_KEY_C: | |||
| if (windowIsModPressed()) { | |||
| if (begin < end) { | |||
| @@ -118,6 +129,12 @@ void TextField::onKey(EventKey &e) { | |||
| } | |||
| } | |||
| break; | |||
| case GLFW_KEY_A: | |||
| if (windowIsModPressed()) { | |||
| begin = 0; | |||
| end = text.size(); | |||
| } | |||
| break; | |||
| case GLFW_KEY_ENTER: | |||
| if (multiline) { | |||
| insertText("\n"); | |||