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