@@ -337,6 +337,7 @@ struct LedDisplaySeparator : TransparentWidget { | |||||
struct LedDisplayChoice : TransparentWidget { | struct LedDisplayChoice : TransparentWidget { | ||||
std::string text; | std::string text; | ||||
std::shared_ptr<Font> font; | std::shared_ptr<Font> font; | ||||
Vec textOffset; | |||||
NVGcolor color; | NVGcolor color; | ||||
LedDisplayChoice(); | LedDisplayChoice(); | ||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
@@ -345,6 +346,7 @@ struct LedDisplayChoice : TransparentWidget { | |||||
struct LedDisplayTextField : TextField { | struct LedDisplayTextField : TextField { | ||||
std::shared_ptr<Font> font; | std::shared_ptr<Font> font; | ||||
Vec textOffset; | |||||
NVGcolor color; | NVGcolor color; | ||||
LedDisplayTextField(); | LedDisplayTextField(); | ||||
void draw(NVGcontext *vg) override; | void draw(NVGcontext *vg) override; | ||||
@@ -358,20 +360,26 @@ struct MidiIO; | |||||
struct AudioWidget : LedDisplay { | struct AudioWidget : LedDisplay { | ||||
/** Not owned */ | /** Not owned */ | ||||
AudioIO *audioIO = NULL; | AudioIO *audioIO = NULL; | ||||
struct Internal; | |||||
Internal *internal; | |||||
LedDisplayChoice *driverChoice; | |||||
LedDisplaySeparator *driverSeparator; | |||||
LedDisplayChoice *deviceChoice; | |||||
LedDisplaySeparator *deviceSeparator; | |||||
LedDisplayChoice *sampleRateChoice; | |||||
LedDisplaySeparator *sampleRateSeparator; | |||||
LedDisplayChoice *bufferSizeChoice; | |||||
AudioWidget(); | AudioWidget(); | ||||
~AudioWidget(); | |||||
void step() override; | void step() override; | ||||
}; | }; | ||||
struct MidiWidget : LedDisplay { | struct MidiWidget : LedDisplay { | ||||
/** Not owned */ | /** Not owned */ | ||||
MidiIO *midiIO = NULL; | MidiIO *midiIO = NULL; | ||||
struct Internal; | |||||
Internal *internal; | |||||
LedDisplayChoice *driverChoice; | |||||
LedDisplaySeparator *driverSeparator; | |||||
LedDisplayChoice *deviceChoice; | |||||
LedDisplaySeparator *deviceSeparator; | |||||
LedDisplayChoice *channelChoice; | |||||
MidiWidget(); | MidiWidget(); | ||||
~MidiWidget(); | |||||
void step() override; | void step() override; | ||||
}; | }; | ||||
@@ -23,12 +23,13 @@ struct AudioIO { | |||||
int numOutputs = 0; | int numOutputs = 0; | ||||
int numInputs = 0; | int numInputs = 0; | ||||
RtAudio *rtAudio = NULL; | RtAudio *rtAudio = NULL; | ||||
/** Cached */ | |||||
RtAudio::DeviceInfo deviceInfo; | RtAudio::DeviceInfo deviceInfo; | ||||
AudioIO(); | AudioIO(); | ||||
virtual ~AudioIO(); | virtual ~AudioIO(); | ||||
std::vector<int> listDrivers(); | |||||
std::vector<int> getDrivers(); | |||||
std::string getDriverName(int driver); | std::string getDriverName(int driver); | ||||
void setDriver(int driver); | void setDriver(int driver); | ||||
@@ -40,7 +41,7 @@ struct AudioIO { | |||||
/** Returns whether the audio stream is open and running */ | /** Returns whether the audio stream is open and running */ | ||||
bool isActive(); | bool isActive(); | ||||
std::vector<int> listSampleRates(); | |||||
std::vector<int> getSampleRates(); | |||||
virtual void processStream(const float *input, float *output, int length) {} | virtual void processStream(const float *input, float *output, int length) {} | ||||
virtual void onCloseStream() {} | virtual void onCloseStream() {} | ||||
@@ -6,13 +6,13 @@ | |||||
namespace rack { | namespace rack { | ||||
struct RCFilter { | struct RCFilter { | ||||
float c = 0.0; | |||||
float c = 0.f; | |||||
float xstate[1] = {}; | float xstate[1] = {}; | ||||
float ystate[1] = {}; | float ystate[1] = {}; | ||||
// `r` is the ratio between the cutoff frequency and sample rate, i.e. r = f_c / f_s | // `r` is the ratio between the cutoff frequency and sample rate, i.e. r = f_c / f_s | ||||
void setCutoff(float r) { | void setCutoff(float r) { | ||||
c = 2.0 / r; | |||||
c = 2.f / r; | |||||
} | } | ||||
void process(float x) { | void process(float x) { | ||||
float y = (x + xstate[0] - ystate[0] * (1 - c)) / (1 + c); | float y = (x + xstate[0] - ystate[0] * (1 - c)) / (1 + c); | ||||
@@ -29,12 +29,12 @@ struct RCFilter { | |||||
struct PeakFilter { | struct PeakFilter { | ||||
float state = 0.0; | |||||
float c = 0.0; | |||||
float state = 0.f; | |||||
float c = 0.f; | |||||
/** Rate is lambda / sampleRate */ | /** Rate is lambda / sampleRate */ | ||||
void setRate(float r) { | void setRate(float r) { | ||||
c = 1.0 - r; | |||||
c = 1.f - r; | |||||
} | } | ||||
void process(float x) { | void process(float x) { | ||||
if (x > state) | if (x > state) | ||||
@@ -48,9 +48,9 @@ struct PeakFilter { | |||||
struct SlewLimiter { | 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) { | void setRiseFall(float _rise, float _fall) { | ||||
rise = _rise; | 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 | } // namespace rack |
@@ -16,12 +16,21 @@ namespace rack { | |||||
struct MidiMessage { | 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 { | struct MidiIO { | ||||
int driver = -1; | |||||
int device = -1; | int device = -1; | ||||
/* For MIDI output, the channel to output messages. | /* For MIDI output, the channel to output messages. | ||||
For MIDI input, the channel to filter. | For MIDI input, the channel to filter. | ||||
@@ -30,11 +39,19 @@ struct MidiIO { | |||||
*/ | */ | ||||
int channel = -1; | int channel = -1; | ||||
RtMidi *rtMidi = NULL; | 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(); | int getDeviceCount(); | ||||
std::string getDeviceName(int device); | 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 */ | /** Returns whether the audio stream is open and running */ | ||||
bool isActive(); | bool isActive(); | ||||
json_t *toJson(); | json_t *toJson(); | ||||
@@ -45,21 +62,24 @@ struct MidiIO { | |||||
struct MidiInput : MidiIO { | struct MidiInput : MidiIO { | ||||
RtMidiIn *rtMidiIn = NULL; | RtMidiIn *rtMidiIn = NULL; | ||||
MidiInput(); | MidiInput(); | ||||
~MidiInput(); | |||||
void setDriver(int driver) override; | |||||
virtual void onMessage(const MidiMessage &message) {} | virtual void onMessage(const MidiMessage &message) {} | ||||
}; | }; | ||||
struct MidiInputQueue : MidiInput { | struct MidiInputQueue : MidiInput { | ||||
std::queue<MidiMessage> messageQueue; | |||||
int queueSize = 8192; | |||||
std::queue<MidiMessage> queue; | |||||
void onMessage(const MidiMessage &message) override; | void onMessage(const MidiMessage &message) override; | ||||
/** If a MidiMessage is available, writes `message` and return true */ | |||||
bool shift(MidiMessage *message); | |||||
}; | }; | ||||
struct MidiOutput : MidiIO { | struct MidiOutput : MidiIO { | ||||
RtMidiOut *rtMidiOut = NULL; | RtMidiOut *rtMidiOut = NULL; | ||||
MidiOutput(); | MidiOutput(); | ||||
~MidiOutput(); | |||||
void setDriver(int driver) override; | |||||
}; | }; | ||||
@@ -1,5 +1,5 @@ | |||||
#pragma once | #pragma once | ||||
#include "app.hpp" | |||||
#include "widgets.hpp" | |||||
#include <GL/glew.h> | #include <GL/glew.h> | ||||
#include <GLFW/glfw3.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 { | void onAction(EventAction &e) override { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Audio driver")); | menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Audio driver")); | ||||
for (int driver : audioWidget->audioIO->listDrivers()) { | |||||
for (int driver : audioWidget->audioIO->getDrivers()) { | |||||
AudioDriverItem *item = new AudioDriverItem(); | AudioDriverItem *item = new AudioDriverItem(); | ||||
item->audioIO = audioWidget->audioIO; | item->audioIO = audioWidget->audioIO; | ||||
item->driver = driver; | item->driver = driver; | ||||
@@ -93,7 +93,7 @@ struct AudioSampleRateChoice : LedDisplayChoice { | |||||
void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Sample rate")); | menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Sample rate")); | ||||
for (int sampleRate : audioWidget->audioIO->listSampleRates()) { | |||||
for (int sampleRate : audioWidget->audioIO->getSampleRates()) { | |||||
AudioSampleRateItem *item = new AudioSampleRateItem(); | AudioSampleRateItem *item = new AudioSampleRateItem(); | ||||
item->audioIO = audioWidget->audioIO; | item->audioIO = audioWidget->audioIO; | ||||
item->sampleRate = sampleRate; | 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() { | AudioWidget::AudioWidget() { | ||||
internal = new Internal(); | |||||
box.size = mm2px(Vec(44, 28)); | box.size = mm2px(Vec(44, 28)); | ||||
Vec pos = Vec(); | Vec pos = Vec(); | ||||
@@ -159,48 +148,44 @@ AudioWidget::AudioWidget() { | |||||
driverChoice->audioWidget = this; | driverChoice->audioWidget = this; | ||||
addChild(driverChoice); | addChild(driverChoice); | ||||
pos = driverChoice->box.getBottomLeft(); | 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); | AudioDeviceChoice *deviceChoice = Widget::create<AudioDeviceChoice>(pos); | ||||
deviceChoice->audioWidget = this; | deviceChoice->audioWidget = this; | ||||
addChild(deviceChoice); | addChild(deviceChoice); | ||||
pos = deviceChoice->box.getBottomLeft(); | 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); | AudioSampleRateChoice *sampleRateChoice = Widget::create<AudioSampleRateChoice>(pos); | ||||
sampleRateChoice->audioWidget = this; | sampleRateChoice->audioWidget = this; | ||||
addChild(sampleRateChoice); | 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); | AudioBlockSizeChoice *bufferSizeChoice = Widget::create<AudioBlockSizeChoice>(pos); | ||||
bufferSizeChoice->audioWidget = this; | bufferSizeChoice->audioWidget = this; | ||||
addChild(bufferSizeChoice); | addChild(bufferSizeChoice); | ||||
internal->bufferSizeChoice = bufferSizeChoice; | |||||
} | |||||
AudioWidget::~AudioWidget() { | |||||
delete internal; | |||||
this->bufferSizeChoice = bufferSizeChoice; | |||||
} | } | ||||
void AudioWidget::step() { | 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(); | LedDisplay::step(); | ||||
} | } | ||||
@@ -34,6 +34,7 @@ LedDisplayChoice::LedDisplayChoice() { | |||||
box.size = mm2px(Vec(0, 28.0 / 3)); | box.size = mm2px(Vec(0, 28.0 / 3)); | ||||
font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); | font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); | ||||
color = nvgRGB(0xff, 0xd7, 0x14); | color = nvgRGB(0xff, 0xd7, 0x14); | ||||
textOffset = Vec(10, 18); | |||||
} | } | ||||
void LedDisplayChoice::draw(NVGcontext *vg) { | void LedDisplayChoice::draw(NVGcontext *vg) { | ||||
@@ -44,13 +45,12 @@ void LedDisplayChoice::draw(NVGcontext *vg) { | |||||
nvgFontFaceId(vg, font->handle); | nvgFontFaceId(vg, font->handle); | ||||
nvgTextLetterSpacing(vg, 0.0); | nvgTextLetterSpacing(vg, 0.0); | ||||
Vec textPos = Vec(10, 18); | |||||
nvgFontSize(vg, 12); | 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) { | void LedDisplayChoice::onMouseDown(EventMouseDown &e) { | ||||
if (e.button == 1) { | |||||
if (e.button == 0 || e.button == 1) { | |||||
EventAction eAction; | EventAction eAction; | ||||
onAction(eAction); | onAction(eAction); | ||||
e.consumed = true; | e.consumed = true; | ||||
@@ -62,9 +62,9 @@ void LedDisplayChoice::onMouseDown(EventMouseDown &e) { | |||||
LedDisplayTextField::LedDisplayTextField() { | LedDisplayTextField::LedDisplayTextField() { | ||||
font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); | font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); | ||||
color = nvgRGB(0xff, 0xd7, 0x14); | color = nvgRGB(0xff, 0xd7, 0x14); | ||||
textOffset = Vec(5, 5); | |||||
} | } | ||||
static const Vec textFieldPos = Vec(5, 5); | |||||
void LedDisplayTextField::draw(NVGcontext *vg) { | void LedDisplayTextField::draw(NVGcontext *vg) { | ||||
// Background | // Background | ||||
@@ -82,8 +82,8 @@ void LedDisplayTextField::draw(NVGcontext *vg) { | |||||
NVGcolor highlightColor = color; | NVGcolor highlightColor = color; | ||||
highlightColor.a = 0.5; | highlightColor.a = 0.5; | ||||
int cend = (this == gFocusedWidget) ? end : -1; | 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); | -1, color, 12, text.c_str(), highlightColor, begin, cend); | ||||
bndSetFont(gGuiFont->handle); | bndSetFont(gGuiFont->handle); | ||||
@@ -91,8 +91,8 @@ void LedDisplayTextField::draw(NVGcontext *vg) { | |||||
int LedDisplayTextField::getTextPosition(Vec mousePos) { | int LedDisplayTextField::getTextPosition(Vec mousePos) { | ||||
bndSetFont(font->handle); | 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); | -1, 12, text.c_str(), mousePos.x, mousePos.y); | ||||
bndSetFont(gGuiFont->handle); | bndSetFont(gGuiFont->handle); | ||||
return textPos; | return textPos; | ||||
@@ -9,26 +9,26 @@ struct MidiDriverItem : MenuItem { | |||||
MidiIO *midiIO; | MidiIO *midiIO; | ||||
int driver; | int driver; | ||||
void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
// midiIO->openDriver(device); | |||||
midiIO->setDriver(driver); | |||||
} | } | ||||
}; | }; | ||||
struct MidiDriverChoice : LedDisplayChoice { | struct MidiDriverChoice : LedDisplayChoice { | ||||
MidiWidget *midiWidget; | MidiWidget *midiWidget; | ||||
void onAction(EventAction &e) override { | 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 { | 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; | MidiIO *midiIO; | ||||
int device; | int device; | ||||
void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
// midiIO->openDevice(device); | |||||
midiIO->setDevice(device); | |||||
} | } | ||||
}; | }; | ||||
struct MidiDeviceChoice : LedDisplayChoice { | struct MidiDeviceChoice : LedDisplayChoice { | ||||
MidiWidget *midiWidget; | MidiWidget *midiWidget; | ||||
void onAction(EventAction &e) override { | 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 { | 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; | MidiIO *midiIO; | ||||
int channel; | int channel; | ||||
void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
// midiIO->channel = channel; | |||||
midiIO->channel = channel; | |||||
} | } | ||||
}; | }; | ||||
struct MidiChannelChoice : LedDisplayChoice { | struct MidiChannelChoice : LedDisplayChoice { | ||||
MidiWidget *midiWidget; | MidiWidget *midiWidget; | ||||
void onAction(EventAction &e) override { | 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 { | 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() { | MidiWidget::MidiWidget() { | ||||
internal = new Internal(); | |||||
box.size = mm2px(Vec(44, 28)); | box.size = mm2px(Vec(44, 28)); | ||||
Vec pos = Vec(); | Vec pos = Vec(); | ||||
@@ -105,85 +98,34 @@ MidiWidget::MidiWidget() { | |||||
driverChoice->midiWidget = this; | driverChoice->midiWidget = this; | ||||
addChild(driverChoice); | addChild(driverChoice); | ||||
pos = driverChoice->box.getBottomLeft(); | 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); | MidiDeviceChoice *deviceChoice = Widget::create<MidiDeviceChoice>(pos); | ||||
deviceChoice->midiWidget = this; | deviceChoice->midiWidget = this; | ||||
addChild(deviceChoice); | addChild(deviceChoice); | ||||
pos = deviceChoice->box.getBottomLeft(); | 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); | MidiChannelChoice *channelChoice = Widget::create<MidiChannelChoice>(pos); | ||||
channelChoice->midiWidget = this; | channelChoice->midiWidget = this; | ||||
addChild(channelChoice); | addChild(channelChoice); | ||||
internal->channelChoice = channelChoice; | |||||
} | |||||
MidiWidget::~MidiWidget() { | |||||
delete internal; | |||||
this->channelChoice = channelChoice; | |||||
} | } | ||||
void MidiWidget::step() { | 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(); | 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 | } // namespace rack |
@@ -17,7 +17,7 @@ AudioIO::~AudioIO() { | |||||
closeStream(); | closeStream(); | ||||
} | } | ||||
std::vector<int> AudioIO::listDrivers() { | |||||
std::vector<int> AudioIO::getDrivers() { | |||||
std::vector<RtAudio::Api> apis; | std::vector<RtAudio::Api> apis; | ||||
RtAudio::getCompiledApi(apis); | RtAudio::getCompiledApi(apis); | ||||
std::vector<int> drivers; | std::vector<int> drivers; | ||||
@@ -39,7 +39,7 @@ std::string AudioIO::getDriverName(int driver) { | |||||
case RtAudio::WINDOWS_WASAPI: return "WASAPI"; | case RtAudio::WINDOWS_WASAPI: return "WASAPI"; | ||||
case RtAudio::WINDOWS_ASIO: return "ASIO"; | case RtAudio::WINDOWS_ASIO: return "ASIO"; | ||||
case RtAudio::WINDOWS_DS: return "DirectSound"; | case RtAudio::WINDOWS_DS: return "DirectSound"; | ||||
case RtAudio::RTAUDIO_DUMMY: return "Dummy"; | |||||
case RtAudio::RTAUDIO_DUMMY: return "Dummy Audio"; | |||||
case BRIDGE_DRIVER: return "Bridge"; | case BRIDGE_DRIVER: return "Bridge"; | ||||
default: return "Unknown"; | default: return "Unknown"; | ||||
} | } | ||||
@@ -245,7 +245,7 @@ bool AudioIO::isActive() { | |||||
} | } | ||||
std::vector<int> AudioIO::listSampleRates() { | |||||
std::vector<int> AudioIO::getSampleRates() { | |||||
if (rtAudio) { | if (rtAudio) { | ||||
try { | try { | ||||
RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | ||||
@@ -1,18 +1,11 @@ | |||||
#include <list> | |||||
#include <algorithm> | |||||
#include "core.hpp" | #include "core.hpp" | ||||
#include "midi.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 | NUM_LIGHTS | ||||
}; | }; | ||||
MidiInput midiIO; | |||||
MidiInputQueue midiInput; | 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) { | void MIDIToCVInterface::pressNote(int note) { | ||||
// Remove existing similar note | // Remove existing similar note | ||||
auto it = std::find(notes.begin(), notes.end(), note); | auto it = std::find(notes.begin(), notes.end(), note); | ||||
@@ -175,60 +219,7 @@ void MIDIToCVInterface::releaseNote(int note) { | |||||
gate = false; | 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 { | struct MIDIToCVInterfaceWidget : ModuleWidget { | ||||
@@ -255,10 +246,10 @@ struct MIDIToCVInterfaceWidget : ModuleWidget { | |||||
MidiWidget *midiWidget = Widget::create<MidiWidget>(mm2px(Vec(3.41891, 14.8373))); | MidiWidget *midiWidget = Widget::create<MidiWidget>(mm2px(Vec(3.41891, 14.8373))); | ||||
midiWidget->box.size = mm2px(Vec(33.840, 28)); | midiWidget->box.size = mm2px(Vec(33.840, 28)); | ||||
midiWidget->midiIO = &module->midiIO; | |||||
midiWidget->midiIO = &module->midiInput; | |||||
addChild(midiWidget); | 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 "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->version = TOSTRING(VERSION); | ||||
p->addModel(modelAudioInterface); | p->addModel(modelAudioInterface); | ||||
p->addModel(modelMidiToCvInterface); | |||||
p->addModel(modelMIDIToCVInterface); | |||||
p->addModel(modelMIDICCToCVInterface); | |||||
p->addModel(modelBlank); | p->addModel(modelBlank); | ||||
p->addModel(modelNotes); | p->addModel(modelNotes); | ||||
@@ -5,7 +5,8 @@ using namespace rack; | |||||
extern Model *modelAudioInterface; | extern Model *modelAudioInterface; | ||||
extern Model *modelMidiToCvInterface; | |||||
extern Model *modelMIDIToCVInterface; | |||||
extern Model *modelMIDICCToCVInterface; | |||||
extern Model *modelBlank; | extern Model *modelBlank; | ||||
extern Model *modelNotes; | extern Model *modelNotes; | ||||
@@ -8,9 +8,39 @@ namespace rack { | |||||
// MidiIO | // 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() { | int MidiIO::getDeviceCount() { | ||||
if (rtMidi) | |||||
if (rtMidi) { | |||||
return rtMidi->getPortCount(); | return rtMidi->getPortCount(); | ||||
} | |||||
return 0; | return 0; | ||||
} | } | ||||
@@ -18,22 +48,33 @@ std::string MidiIO::getDeviceName(int device) { | |||||
if (rtMidi) { | if (rtMidi) { | ||||
if (device < 0) | if (device < 0) | ||||
return ""; | return ""; | ||||
return rtMidi->getPortName(device); | |||||
if (device == this->device) | |||||
return deviceName; | |||||
else | |||||
return rtMidi->getPortName(device); | |||||
} | } | ||||
return ""; | return ""; | ||||
} | } | ||||
void MidiIO::openDevice(int device) { | |||||
void MidiIO::setDevice(int device) { | |||||
if (rtMidi) { | if (rtMidi) { | ||||
rtMidi->closePort(); | rtMidi->closePort(); | ||||
if (device >= 0) { | if (device >= 0) { | ||||
rtMidi->openPort(device); | rtMidi->openPort(device); | ||||
deviceName = rtMidi->getPortName(device); | |||||
} | } | ||||
this->device = 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() { | bool MidiIO::isActive() { | ||||
if (rtMidi) | if (rtMidi) | ||||
return rtMidi->isPortOpen(); | return rtMidi->isPortOpen(); | ||||
@@ -42,20 +83,27 @@ bool MidiIO::isActive() { | |||||
json_t *MidiIO::toJson() { | json_t *MidiIO::toJson() { | ||||
json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
json_object_set_new(rootJ, "driver", json_integer(driver)); | |||||
std::string deviceName = getDeviceName(device); | 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)); | json_object_set_new(rootJ, "channel", json_integer(channel)); | ||||
return rootJ; | return rootJ; | ||||
} | } | ||||
void MidiIO::fromJson(json_t *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"); | json_t *deviceNameJ = json_object_get(rootJ, "deviceName"); | ||||
if (deviceNameJ) { | if (deviceNameJ) { | ||||
std::string deviceName = json_string_value(deviceNameJ); | std::string deviceName = json_string_value(deviceNameJ); | ||||
// Search for device with equal name | // 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) { | if (getDeviceName(device) == deviceName) { | ||||
openDevice(device); | |||||
setDevice(device); | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
@@ -77,42 +125,70 @@ static void midiInputCallback(double timeStamp, std::vector<unsigned char> *mess | |||||
MidiInput *midiInput = (MidiInput*) userData; | MidiInput *midiInput = (MidiInput*) userData; | ||||
if (!midiInput) return; | if (!midiInput) return; | ||||
MidiMessage midiMessage; | 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->onMessage(midiMessage); | ||||
} | } | ||||
MidiInput::MidiInput() { | 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) { | 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::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" | #include "window.hpp" | ||||
@@ -48,8 +48,10 @@ void TextField::onFocus(EventFocus &e) { | |||||
} | } | ||||
void TextField::onText(EventText &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; | e.consumed = true; | ||||
} | } | ||||
@@ -110,6 +112,15 @@ void TextField::onKey(EventKey &e) { | |||||
insertText(newText); | insertText(newText); | ||||
} | } | ||||
break; | 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: | case GLFW_KEY_C: | ||||
if (windowIsModPressed()) { | if (windowIsModPressed()) { | ||||
if (begin < end) { | if (begin < end) { | ||||
@@ -118,6 +129,12 @@ void TextField::onKey(EventKey &e) { | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case GLFW_KEY_A: | |||||
if (windowIsModPressed()) { | |||||
begin = 0; | |||||
end = text.size(); | |||||
} | |||||
break; | |||||
case GLFW_KEY_ENTER: | case GLFW_KEY_ENTER: | ||||
if (multiline) { | if (multiline) { | ||||
insertText("\n"); | insertText("\n"); | ||||