Browse Source

Add partial MIDI CC Interface, MIDI CV fixes

tags/v0.6.0
Andrew Belt 6 years ago
parent
commit
db8d0fc1d6
17 changed files with 979 additions and 639 deletions
  1. +14
    -6
      include/app.hpp
  2. +3
    -2
      include/audio.hpp
  3. +27
    -8
      include/dsp/filter.hpp
  4. +27
    -7
      include/midi.hpp
  5. +1
    -1
      include/window.hpp
  6. +453
    -0
      res/Core/MIDICCToCVInterface.svg
  7. +21
    -36
      src/app/AudioWidget.cpp
  8. +8
    -8
      src/app/LedDisplay.cpp
  9. +50
    -108
      src/app/MidiWidget.cpp
  10. +3
    -3
      src/audio.cpp
  11. +135
    -144
      src/core/MIDIToCVInterface.cpp
  12. +114
    -288
      src/core/MidiCCToCV.cpp
  13. +2
    -1
      src/core/core.cpp
  14. +2
    -1
      src/core/core.hpp
  15. +99
    -23
      src/midi.cpp
  16. +1
    -1
      src/ui/Scene.cpp
  17. +19
    -2
      src/ui/TextField.cpp

+ 14
- 6
include/app.hpp View File

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



+ 3
- 2
include/audio.hpp View File

@@ -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() {}


+ 27
- 8
include/dsp/filter.hpp View File

@@ -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

+ 27
- 7
include/midi.hpp View File

@@ -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
- 1
include/window.hpp View File

@@ -1,5 +1,5 @@
#pragma once
#include "app.hpp"
#include "widgets.hpp"
#include <GL/glew.h>
#include <GLFW/glfw3.h>



+ 453
- 0
res/Core/MIDICCToCVInterface.svg View File

@@ -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>

+ 21
- 36
src/app/AudioWidget.cpp View File

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



+ 8
- 8
src/app/LedDisplay.cpp View File

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


+ 50
- 108
src/app/MidiWidget.cpp View File

@@ -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

+ 3
- 3
src/audio.cpp View File

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


src/core/MidiToCV.cpp → src/core/MIDIToCVInterface.cpp View File

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

+ 114
- 288
src/core/MidiCCToCV.cpp View File

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

+ 2
- 1
src/core/core.cpp View File

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



+ 2
- 1
src/core/core.hpp View File

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



+ 99
- 23
src/midi.cpp View File

@@ -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
- 1
src/ui/Scene.cpp View File

@@ -1,4 +1,4 @@
#include "widgets.hpp"
#include "ui.hpp"
#include "window.hpp"




+ 19
- 2
src/ui/TextField.cpp View File

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


Loading…
Cancel
Save