File: MidiController.h

package info (click to toggle)
bespokesynth 1.3.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 44,592 kB
  • sloc: cpp: 117,136; ansic: 18,752; python: 593; xml: 74; makefile: 4
file content (467 lines) | stat: -rw-r--r-- 15,631 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
/**
    bespoke synth, a software modular synthesizer
    Copyright (C) 2021 Ryan Challinor (contact: awwbees@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/
//
//  MidiController.h
//  modularSynth
//
//  Created by Ryan Challinor on 1/14/13.
//
//

#pragma once

#include "MidiDevice.h"
#include "IDrawableModule.h"
#include "Checkbox.h"
#include "ClickButton.h"
#include "DropdownList.h"
#include "RadioButton.h"
#include "ofxJSONElement.h"
#include "Transport.h"
#include "TextEntry.h"
#include "ModulationChain.h"
#include "INoteSource.h"

#define MIDI_PITCH_BEND_CONTROL_NUM 999
#define MIDI_PAGE_WIDTH 1000
#define MAX_MIDI_PAGES 32

enum MidiMessageType
{
   kMidiMessage_Note,
   kMidiMessage_Control,
   kMidiMessage_Program,
   kMidiMessage_PitchBend
};

enum ControlType
{
   kControlType_Slider,
   kControlType_SetValue,
   kControlType_Toggle,
   kControlType_Direct,
   kControlType_SetValueOnRelease,
   kControlType_Default
};

enum SpecialControlBinding
{
   kSpecialBinding_None,
   kSpecialBinding_Hover,
   kSpecialBinding_HotBind0,
   kSpecialBinding_HotBind1,
   kSpecialBinding_HotBind2,
   kSpecialBinding_HotBind3,
   kSpecialBinding_HotBind4,
   kSpecialBinding_HotBind5,
   kSpecialBinding_HotBind6,
   kSpecialBinding_HotBind7,
   kSpecialBinding_HotBind8,
   kSpecialBinding_HotBind9
};

enum class ChannelFilter
{
   kAny,
   k1,
   k2,
   k3,
   k4,
   k5,
   k6,
   k7,
   k8,
   k9,
   k10,
   k11,
   k12,
   k13,
   k14,
   k15,
   k16
};

namespace Json
{
   class Value;
}

class Monome;
class MidiController;
class GridControlTarget;
class INonstandardController;
class ScriptModule;

struct UIControlConnection
{
   UIControlConnection(MidiController* owner)
   : mUIOwner(owner)
   {}

   ~UIControlConnection();

   IUIControl* GetUIControl()
   {
      if (mSpecialBinding == kSpecialBinding_None)
         return mUIControl;
      else if (mSpecialBinding == kSpecialBinding_Hover)
         return gHoveredUIControl;
      int index = mSpecialBinding - kSpecialBinding_HotBind0;
      assert(index >= 0 && index <= 9);
      return gHotBindUIControl[index];
   }

   UIControlConnection* MakeCopy()
   {
      UIControlConnection* connection = new UIControlConnection(mUIOwner);
      connection->mMessageType = mMessageType;
      connection->mControl = mControl;
      connection->SetUIControl(mUIControl);
      connection->mType = mType;
      connection->mValue = mValue;
      connection->mLastControlValue = mLastControlValue;
      connection->mMidiOnValue = mMidiOnValue;
      connection->mMidiOffValue = mMidiOffValue;
      connection->mScaleOutput = mScaleOutput;
      connection->mBlink = mBlink;
      connection->mIncrementAmount = mIncrementAmount;
      connection->mTwoWay = mTwoWay;
      connection->mFeedbackControl = mFeedbackControl;
      connection->mSpecialBinding = mSpecialBinding;
      connection->mChannel = mChannel;
      connection->mPage = mPage;
      connection->mPageless = mPageless;
      connection->m14BitMode = m14BitMode;
      return connection;
   }

   void SetNext(UIControlConnection* next);

   void SetUIControl(IUIControl* control);
   void SetUIControl(std::string path);
   void CreateUIControls(int index);
   void Poll();
   void PreDraw();
   void DrawList(int index);
   void DrawLayout();
   void SetShowing(bool enabled);
   bool PostRepatch(PatchCableSource* cableSource, bool fromUserClick);

   int mControl{ -1 };
   IUIControl* mUIControl{ nullptr };
   ControlType mType{ ControlType::kControlType_Slider };
   MidiMessageType mMessageType{ MidiMessageType::kMidiMessage_Control };
   float mValue{ 0 };
   int mMidiOnValue{ 127 };
   int mMidiOffValue{ 0 };
   bool mScaleOutput{ false };
   bool mBlink{ false };
   float mIncrementAmount{ 0 };
   bool mTwoWay{ true };
   int mFeedbackControl{ -1 };
   SpecialControlBinding mSpecialBinding{ SpecialControlBinding::kSpecialBinding_None };
   int mChannel{ -1 };
   int mPage{ 0 };
   bool mPageless{ false };
   std::string mShouldRetryForUIControlAt{ "" };
   bool m14BitMode{ false };

   static bool sDrawCables;

   //state
   int mLastControlValue{ -1 };
   std::string mLastDisplayValue{ "" };
   double mLastActivityTime{ -9999 };
   MidiController* mUIOwner{ nullptr };

   //editor controls
   DropdownList* mMessageTypeDropdown{ nullptr };
   TextEntry* mControlEntry{ nullptr };
   DropdownList* mChannelDropdown{ nullptr };
   TextEntry* mUIControlPathEntry{ nullptr };
   char mUIControlPathInput[MAX_TEXTENTRY_LENGTH]{};
   DropdownList* mControlTypeDropdown{ nullptr };
   TextEntry* mValueEntry{ nullptr };
   TextEntry* mMidiOffEntry{ nullptr };
   TextEntry* mMidiOnEntry{ nullptr };
   Checkbox* mScaleOutputCheckbox{ nullptr };
   Checkbox* mBlinkCheckbox{ nullptr };
   Checkbox* m14BitModeCheckbox{ nullptr };
   TextEntry* mIncrementalEntry{ nullptr };
   Checkbox* mTwoWayCheckbox{ nullptr };
   DropdownList* mFeedbackDropdown{ nullptr };
   Checkbox* mPagelessCheckbox{ nullptr };
   ClickButton* mRemoveButton{ nullptr };
   ClickButton* mCopyButton{ nullptr };
   std::list<IUIControl*> mEditorControls;
};

enum ControlDrawType
{
   kDrawType_Button,
   kDrawType_Knob,
   kDrawType_Slider
};

struct ControlLayoutElement
{
   ControlLayoutElement()
   {}
   void Setup(MidiController* owner, MidiMessageType type, int control, ControlDrawType drawType, float incrementAmount, bool is14Bit, int offVal, int onVal, bool scaleOutput, ControlType connectionType, float x, float y, float w, float h);

   bool mActive{ false };
   MidiMessageType mType{ MidiMessageType::kMidiMessage_Control };
   int mControl{ 0 };
   ofVec2f mPosition;
   ofVec2f mDimensions;
   ControlDrawType mDrawType{ ControlDrawType::kDrawType_Slider };
   float mIncrementAmount{ 0 };
   int mOffVal{ 0 };
   int mOnVal{ 127 };
   bool mScaleOutput{ true };
   bool m14BitMode{ false };
   ControlType mConnectionType{ ControlType::kControlType_Slider };

   PatchCableSource* mControlCable{ nullptr };

   float mLastValue{ 0 };
   float mLastActivityTime{ -9999 };
};

struct GridLayout
{
   GridLayout()
   {}

   int mRows{ 1 };
   int mCols{ 8 };
   ofVec2f mPosition;
   ofVec2f mDimensions;
   MidiMessageType mType{ kMidiMessage_Note };
   std::vector<int> mControls;
   std::vector<int> mColors;

   PatchCableSource* mGridCable{ nullptr };
   GridControlTarget* mGridControlTarget[MAX_MIDI_PAGES]{};
};

#define NUM_LAYOUT_CONTROLS 128 + 128 + 128 + 1 + 1 //128 notes, 128 ccs, 128 program change, 1 pitch bend, 1 dummy

class MidiController : public MidiDeviceListener, public IDrawableModule, public IButtonListener, public IDropdownListener, public IRadioButtonListener, public IAudioPoller, public ITextEntryListener, public INoteSource, public IKeyboardFocusListener
{
public:
   MidiController();
   ~MidiController();
   static IDrawableModule* Create() { return new MidiController(); }
   static bool AcceptsAudio() { return false; }
   static bool AcceptsNotes() { return false; }
   static bool AcceptsPulses() { return false; }

   void CreateUIControls() override;

   void Init() override;

   void AddControlConnection(const ofxJSONElement& connection);
   UIControlConnection* AddControlConnection(MidiMessageType messageType, int control, int channel, IUIControl* uicontrol, int page = -1);
   void UseNegativeEdge(bool use) { mUseNegativeEdge = use; }
   void AddListener(MidiDeviceListener* listener, int page);
   void RemoveListener(MidiDeviceListener* listener);
   void AddScriptListener(ScriptModule* script);
   int GetPage() const { return mControllerPage; }
   bool IsInputConnected(bool immediate);
   std::string GetDeviceIn() const { return mDeviceIn; }
   std::string GetDeviceOut() const { return mDeviceOut; }
   UIControlConnection* GetConnectionForControl(MidiMessageType messageType, int control);
   UIControlConnection* GetConnectionForCableSource(const PatchCableSource* source);
   void ResyncControllerState();

   void SetVelocityMult(float mult) { mVelocityMult = mult; }
   void SetUseChannelAsVoice(bool use) { mUseChannelAsVoice = use; }
   void SetNoteOffset(int offset) { mNoteOffset = offset; }
   void SetPitchBendRange(float range) { mPitchBendRange = range; }

   void SendNote(int page, int pitch, int velocity, bool forceNoteOn = false, int channel = -1);
   void SendCC(int page, int ctl, int value, int channel = -1);
   void SendProgramChange(int page, int program, int channel = -1);
   void SendPitchBend(int page, int bend, int channel = -1);
   void SendData(int page, unsigned char a, unsigned char b, unsigned char c);
   void SendSysEx(int page, std::string data);

   INonstandardController* GetNonstandardController() { return mNonstandardController; }

   static std::vector<std::string> GetAvailableInputDevices();
   static std::vector<std::string> GetAvailableOutputDevices();

   //IDrawableModule
   void Poll() override;
   void SetEnabled(bool enabled) override { mEnabled = enabled; }
   void Exit() override;
   void KeyReleased(int key) override;

   //MidiDeviceListener
   void OnMidiNote(MidiNote& note) override;
   void OnMidiControl(MidiControl& control) override;
   void OnMidiPressure(MidiPressure& pressure) override;
   void OnMidiProgramChange(MidiProgramChange& program) override;
   void OnMidiPitchBend(MidiPitchBend& pitchBend) override;
   void OnMidi(const juce::MidiMessage& message) override;

   void OnTransportAdvanced(float amount) override;

   //IKeyboardFocusListener
   void OnKeyPressed(int key, bool isRepeat) override;
   bool ShouldConsumeKey(int key) override;
   bool CanTakeFocus() override;

   void CheckboxUpdated(Checkbox* checkbox, double time) override;
   void ButtonClicked(ClickButton* button, double time) override;
   void DropdownUpdated(DropdownList* list, int oldVal, double time) override;
   void DropdownClicked(DropdownList* list) override;
   void RadioButtonUpdated(RadioButton* radio, int oldVal, double time) override;
   void TextEntryActivated(TextEntry* entry) override;
   void TextEntryComplete(TextEntry* entry) override;
   void PreRepatch(PatchCableSource* cableSource) override;
   void PostRepatch(PatchCableSource* cableSource, bool fromUserClick) override;
   void OnCableGrabbed(PatchCableSource* cableSource) override;
   void SendControllerInfoString(int control, int type, std::string str);
   bool ShouldSendControllerInfoStrings() const { return mShouldSendControllerInfoStrings; }

   ControlLayoutElement& GetLayoutControl(int control, MidiMessageType type);

   virtual void LoadLayout(const ofxJSONElement& moduleInfo) override;
   virtual void SetUpFromSaveData() override;
   virtual void SaveLayout(ofxJSONElement& moduleInfo) override;

   void SaveState(FileStreamOut& out) override;
   void LoadState(FileStreamIn& in, int rev) override;
   int GetModuleSaveStateRev() const override { return 1; }

   static std::string GetDefaultTooltip(MidiMessageType type, int control);

   static double sLastConnectedActivityTime;
   static IUIControl* sLastActivityUIControl;
   static double sLastBoundControlTime;
   static IUIControl* sLastBoundUIControl;

   bool IsEnabled() const override { return mEnabled; }

private:
   enum MappingDisplayMode
   {
      kHide,
      kList,
      kLayout
   };

   //IDrawableModule
   void DrawModule() override;
   void DrawModuleUnclipped() override;
   void GetModuleDimensions(float& width, float& height) override;
   void OnClicked(float x, float y, bool right) override;
   bool MouseMoved(float x, float y) override;

   void ConnectDevice();
   void MidiReceived(MidiMessageType messageType, int control, float scaledValue, int rawValue, int channel);
   void RemoveConnection(int control, MidiMessageType messageType, int channel, int page);
   int GetNumConnectionsOnPage(int page);
   void SetEntirePageToZero(int page);
   void BuildControllerList();
   void HighlightPageControls(int page);
   void OnDeviceChanged();
   int GetLayoutControlIndexForCable(PatchCableSource* cable) const;
   int GetLayoutControlIndexForMidi(MidiMessageType type, int control) const;
   std::string GetLayoutTooltip(int controlIndex);
   void UpdateControllerIndex();
   void LoadControllerLayout(std::string filename);
   bool JustBoundControl() const { return gTime - sLastBoundControlTime < 500; }

   const std::string kDefaultLayout = "default";

   float mVelocityMult{ 1 };
   bool mUseChannelAsVoice{ false };
   float mCurrentPitchBend{ 0 };
   int mNoteOffset{ 0 };
   float mPitchBendRange{ 2 };
   int mModwheelCC{ 1 }; // or 74 in Multidimensional Polyphonic Expression (MPE) spec
   float mModWheelOffset{ 0 };
   float mPressureOffset{ 0 };

   Modulations mModulation{ true };

   std::string mDeviceIn;
   std::string mDeviceOut;
   int mOutChannel{ 1 };
   MidiDevice mDevice;
   double mInitialConnectionTime{ 0 };
   ofxJSONElement mConnectionsJson;
   std::list<UIControlConnection*> mConnections;
   bool mSendCCOutput{ false };
   bool mUseNegativeEdge{ false }; // for midi toggle, accept on or off as a button press
   bool mSlidersDefaultToIncremental{ false };
   bool mBindMode{ false };
   Checkbox* mBindCheckbox{ nullptr };
   bool mTwoWay{ true };
   bool mSendTwoWayOnChange{ true };
   bool mResendFeedbackOnRelease{ false };
   ClickButton* mAddConnectionButton{ nullptr };
   std::list<MidiNote> mQueuedNotes;
   std::list<MidiControl> mQueuedControls;
   std::list<MidiProgramChange> mQueuedProgramChanges;
   std::list<MidiPitchBend> mQueuedPitchBends;
   DropdownList* mControllerList{ nullptr };
   Checkbox* mDrawCablesCheckbox{ nullptr };
   MappingDisplayMode mMappingDisplayMode{ MappingDisplayMode::kHide };
   RadioButton* mMappingDisplayModeSelector{ nullptr };
   int mLayoutFileIndex{ 0 };
   DropdownList* mLayoutFileDropdown{ nullptr };
   int mOscInPort{ 8000 };
   TextEntry* mOscInPortEntry{ nullptr };
   int mMonomeDeviceIndex{ -1 };
   DropdownList* mMonomeDeviceDropdown{ nullptr };
   bool mShouldSendControllerInfoStrings{ false };
   bool mSendSysex{ false };

   int mControllerIndex{ -1 };
   double mLastActivityTime{ -9999 };
   bool mLastActivityBound{ false };
   bool mShowActivityUIOverlay{ true };
   bool mBlink{ false };
   int mControllerPage{ 0 };
   DropdownList* mPageSelector{ nullptr };
   std::vector<std::list<MidiDeviceListener*> > mListeners;
   std::vector<ScriptModule*> mScriptListeners;
   bool mPrintInput{ false };
   std::string mLastInput;
   INonstandardController* mNonstandardController{ nullptr };
   bool mIsConnected{ false };
   bool mHasCreatedConnectionUIControls{ false };
   float mReconnectWaitTimer{ 0 };
   ChannelFilter mChannelFilter{ ChannelFilter::kAny };
   std::string mLastLoadedLayoutFile;
   ofxJSONElement mLayoutData;
   std::string mLayoutLoadError;

   std::array<ControlLayoutElement, NUM_LAYOUT_CONTROLS> mLayoutControls;
   int mHighlightedLayoutElement{ -1 };
   int mHoveredLayoutElement{ -1 };
   int mLayoutWidth{ 0 };
   int mLayoutHeight{ 0 };
   std::vector<GridLayout*> mGrids;

   ofMutex mQueuedMessageMutex;
};