1 /+
2               Copyright Elias Batek 2017 - 2018.
3      Distributed under the Boost Software License, Version 1.0.
4         (See accompanying file LICENSE_1_0.txt or copy at
5               https://www.boost.org/LICENSE_1_0.txt)
6  +/
7 module midigamepad.lib.translation.ioprocessor;
8 
9 import core.sys.windows.mmsystem;
10 import core.thread;
11 
12 import dplug.client.midi;
13 
14 import midigamepad.lib.midi;
15 
16 public import midigamepad.lib.midi.device;
17 public import midigamepad.lib.keyboard;
18 public import midigamepad.lib.translation.mapping;
19 
20 /++
21     Stops the IO processor based on the passed data.
22     This will also stop the keyboard simulator thread.
23  +/
24 bool haltIOProcessor(IOProcessorData pcd)
25 {
26     if (pcd.keyboardSynthesizerLoopMgr.isRunning)
27         pcd.keyboardSynthesizerLoopMgr.stop();
28 
29     return (closeMIDIInputDevice(pcd.deviceHandle) == MMSYSERR_NOERROR);
30 }
31 
32 /++
33     Bootstraps the specified MIDI input device,
34     start the keyboard simulator thread,
35     and enables the translation of MIDI input to keystrokes
36 
37     Returns: success = true
38  +/
39 bool runIOProcessor(MIDIDeviceInfo di, IOProcessorData* pcd, out HMIDIIN handle)
40 in
41 {
42     assert(di.type == MIDIDeviceType.input);
43 }
44 body
45 {
46     if (!bootstrapMIDIInputDevice(di, &midigamepadProcessMIDIIn, handle, cast(size_t)(pcd)))
47     {
48         // initalization failure
49         return false;
50     }
51 
52     pcd.deviceHandle = handle;
53 
54     pcd.keyboardSynthesizerLoopMgr.tryStart();
55     return true;
56 }
57 
58 /++
59     Data and config used by the IO processor
60  +/
61 struct IOProcessorData
62 {
63 @nogc nothrow pure @safe:
64 
65     private
66     {
67         HMIDIIN _deviceHandle;
68         KeyboardSynthesizerLoopMgr _keyboardSynthesizerLoopMgr;
69         MappingsCollection _mappings;
70 
71     }
72 
73     /++
74         ctor
75      +/
76     this(KeyboardSynthesizerLoopMgr keyboardSynthesizerLoopMgr, MappingsCollection mappings)
77     {
78         this._keyboardSynthesizerLoopMgr = keyboardSynthesizerLoopMgr;
79         this._mappings = mappings;
80     }
81 
82     /++
83         Keyboard simulator to use
84      +/
85     @property KeyboardSynthesizerLoopMgr keyboardSynthesizerLoopMgr()
86     {
87         return this._keyboardSynthesizerLoopMgr;
88     }
89 
90     /++
91         MIDI input to keyboard mappings
92      +/
93     @property MappingsCollection mappings()
94     {
95         return this._mappings;
96     }
97 
98     @property
99     {
100         /++
101             OS handle for the MIDI input device
102          +/
103         HMIDIIN deviceHandle()
104         {
105             return this._deviceHandle;
106         }
107 
108         protected void deviceHandle(HMIDIIN deviceHandle)
109         {
110             this._deviceHandle = deviceHandle;
111         }
112     }
113 }
114 
115 private
116 {
117     extern (Windows) void midigamepadProcessMIDIIn(HMIDIIN, uint msgType,
118             size_t callbackData, size_t msgParam1, size_t)
119     {
120         auto pcd = cast(IOProcessorData*)(callbackData);
121 
122         switch (msgType)
123         {
124         case MIM_DATA:
125             immutable auto msg = parseMIMDATA(msgParam1);
126             switch (msg.status)
127             {
128             case MidiStatus.noteOn:
129                 {
130                     if (msg.noteVelocity == 0)
131                         goto case MidiStatus.noteOff;
132 
133                     foreach (NoteOnOffMapping mp; pcd.mappings.noteOnOff)
134                     {
135                         if (mp.noteNumber == msg.noteNumber)
136                         {
137                             pcd.keyboardSynthesizerLoopMgr.press(mp);
138                         }
139                     }
140 
141                     break;
142                 }
143 
144             case MidiStatus.noteOff:
145                 {
146                     foreach (NoteOnOffMapping mp; pcd.mappings.noteOnOff)
147                     {
148                         if (mp.noteNumber == msg.noteNumber)
149                         {
150                             pcd.keyboardSynthesizerLoopMgr.tryRelease(mp);
151                         }
152                     }
153                     break;
154                 }
155 
156             default:
157                 break;
158             }
159             break;
160 
161         default:
162             break;
163         }
164     }
165 }