1 #ifdef HAS_MIDI
2
3 // for finding memory leaks in debug mode with Visual Studio
4 #if defined _DEBUG && defined _MSC_VER
5 #include <crtdbg.h>
6 #endif
7
8 #include <stdio.h>
9 #include <stdbool.h>
10 #include "ft2_header.h"
11 #include "ft2_edit.h"
12 #include "ft2_config.h"
13 #include "ft2_gui.h"
14 #include "ft2_midi.h"
15 #include "ft2_audio.h"
16 #include "ft2_mouse.h"
17 #include "ft2_pattern_ed.h"
18 #include "ft2_structs.h"
19 #include "rtmidi/rtmidi_c.h"
20
21 #define MAX_DEV_STR_LEN 256
22
23 // hide POSIX warnings
24 #ifdef _MSC_VER
25 #pragma warning(disable: 4996)
26 #endif
27
28 // This implements MIDI input only!
29
30 midi_t midi; // globalized
31
32 static volatile bool midiDeviceOpened;
33 static bool recMIDIValidChn = true;
34 static RtMidiPtr midiDev;
35
midiInSetChannel(uint8_t status)36 static inline void midiInSetChannel(uint8_t status)
37 {
38 recMIDIValidChn = (config.recMIDIAllChn || (status & 0xF) == config.recMIDIChn-1);
39 }
40
midiInKeyAction(int8_t m,uint8_t mv)41 static inline void midiInKeyAction(int8_t m, uint8_t mv)
42 {
43 int16_t vol = (mv * 64 * config.recMIDIVolSens) / (127 * 100);
44 if (vol > 64)
45 vol = 64;
46
47 // FT2 bugfix: If velocity>0, and sensitivity made vol=0, set vol to 1 (prevent key off)
48 if (mv > 0 && vol == 0)
49 vol = 1;
50
51 if (mv > 0 && !config.recMIDIVelocity)
52 vol = -1; // don't record volume (velocity)
53
54 m -= 11;
55 if (config.recMIDITransp)
56 m += (int8_t)config.recMIDITranspVal;
57
58 if ((mv == 0 || vol != 0) && m > 0 && m < 96 && recMIDIValidChn)
59 recordNote(m, (int8_t)vol);
60 }
61
midiInControlChange(uint8_t data1,uint8_t data2)62 static inline void midiInControlChange(uint8_t data1, uint8_t data2)
63 {
64 if (data1 != 1) // 1 = modulation wheel
65 return;
66
67 midi.currMIDIVibDepth = data2 << 6;
68
69 if (recMIDIValidChn) // real FT2 forgot to check this here..
70 {
71 for (uint8_t i = 0; i < song.numChannels; i++)
72 {
73 if (channel[i].midiVibDepth != 0 || editor.keyOnTab[i] != 0)
74 channel[i].midiVibDepth = midi.currMIDIVibDepth;
75 }
76 }
77
78 const uint8_t vibDepth = (midi.currMIDIVibDepth >> 9) & 0x0F;
79 if (vibDepth > 0 && recMIDIValidChn)
80 recordMIDIEffect(0x04, 0xA0 | vibDepth);
81 }
82
midiInPitchBendChange(uint8_t data1,uint8_t data2)83 static inline void midiInPitchBendChange(uint8_t data1, uint8_t data2)
84 {
85 int16_t pitch = (int16_t)((data2 << 7) | data1) - 8192; // -8192..8191
86 pitch >>= 6; // -128..127
87
88 midi.currMIDIPitch = pitch;
89 if (recMIDIValidChn)
90 {
91 channel_t *ch = channel;
92 for (uint8_t i = 0; i < song.numChannels; i++, ch++)
93 {
94 if (ch->midiPitch != 0 || editor.keyOnTab[i] != 0)
95 ch->midiPitch = midi.currMIDIPitch;
96 }
97 }
98 }
99
midiInCallback(double dTimeStamp,const unsigned char * message,size_t messageSize,void * userData)100 static void midiInCallback(double dTimeStamp, const unsigned char *message, size_t messageSize, void *userData)
101 {
102 uint8_t byte[3];
103
104 if (!midi.enable || messageSize < 2)
105 return;
106
107 byte[0] = message[0];
108 if (byte[0] > 127 && byte[0] < 240)
109 {
110 byte[1] = message[1] & 0x7F;
111
112 if (messageSize >= 3)
113 byte[2] = message[2] & 0x7F;
114 else
115 byte[2] = 0;
116
117 midiInSetChannel(byte[0]);
118
119 if (byte[0] >= 128 && byte[0] <= 128+15) midiInKeyAction(byte[1], 0);
120 else if (byte[0] >= 144 && byte[0] <= 144+15) midiInKeyAction(byte[1], byte[2]);
121 else if (byte[0] >= 176 && byte[0] <= 176+15) midiInControlChange(byte[1], byte[2]);
122 else if (byte[0] >= 224 && byte[0] <= 224+15) midiInPitchBendChange(byte[1], byte[2]);
123 }
124
125 (void)dTimeStamp;
126 (void)userData;
127 }
128
getNumMidiInDevices(void)129 static uint32_t getNumMidiInDevices(void)
130 {
131 if (midiDev == NULL)
132 return 0;
133
134 return rtmidi_get_port_count(midiDev);
135 }
136
getMidiInDeviceName(uint32_t deviceID)137 static char *getMidiInDeviceName(uint32_t deviceID)
138 {
139 if (midiDev == NULL)
140 return NULL;
141
142 char *devStr = (char *)rtmidi_get_port_name(midiDev, deviceID);
143 if (!midiDev->ok)
144 return NULL;
145
146 return devStr;
147 }
148
closeMidiInDevice(void)149 void closeMidiInDevice(void)
150 {
151 while (!midi.initThreadDone);
152
153 if (midiDeviceOpened)
154 {
155 if (midiDev != NULL)
156 {
157 rtmidi_in_cancel_callback(midiDev);
158 rtmidi_close_port(midiDev);
159 }
160
161 midiDeviceOpened = false;
162 }
163 }
164
freeMidiIn(void)165 void freeMidiIn(void)
166 {
167 while (!midi.initThreadDone);
168
169 if (midiDev != NULL)
170 {
171 rtmidi_in_free(midiDev);
172 midiDev = NULL;
173 }
174 }
175
initMidiIn(void)176 bool initMidiIn(void)
177 {
178 if (midiDev != NULL)
179 return false; // already initialized
180
181 midiDev = rtmidi_in_create_default();
182 if (!midiDev->ok)
183 {
184 midiDev = NULL;
185 return false;
186 }
187
188 midiDeviceOpened = false;
189 return true;
190 }
191
openMidiInDevice(uint32_t deviceID)192 bool openMidiInDevice(uint32_t deviceID)
193 {
194 if (midiDev == NULL)
195 return false;
196
197 if (getNumMidiInDevices() == 0)
198 return false;
199
200 rtmidi_open_port(midiDev, deviceID, "FT2 Clone MIDI Port");
201 if (!midiDev->ok)
202 return false;
203
204 rtmidi_in_set_callback(midiDev, midiInCallback, NULL);
205 if (!midiDev->ok)
206 {
207 rtmidi_close_port(midiDev);
208 return false;
209 }
210
211 rtmidi_in_ignore_types(midiDev, true, true, true);
212
213 midiDeviceOpened = true;
214 return true;
215 }
216
recordMIDIEffect(uint8_t efx,uint8_t efxData)217 void recordMIDIEffect(uint8_t efx, uint8_t efxData)
218 {
219 // only handle this in record mode
220 if (!midi.enable || (playMode != PLAYMODE_RECSONG && playMode != PLAYMODE_RECPATT))
221 return;
222
223 if (config.multiRec)
224 {
225 note_t *p = &pattern[editor.editPattern][editor.row * MAX_CHANNELS];
226 for (int32_t i = 0; i < song.numChannels; i++, p++)
227 {
228 if (config.multiRecChn[i] && editor.chnMode[i])
229 {
230 if (!allocatePattern(editor.editPattern))
231 return;
232
233 if (p->efx == 0)
234 {
235 p->efx = efx;
236 p->efxData = efxData;
237 setSongModifiedFlag();
238 }
239 }
240 }
241 }
242 else
243 {
244 if (!allocatePattern(editor.editPattern))
245 return;
246
247 note_t *p = &pattern[editor.editPattern][(editor.row * MAX_CHANNELS) + cursor.ch];
248 if (p->efx != efx || p->efxData != efxData)
249 setSongModifiedFlag();
250
251 p->efx = efx;
252 p->efxData = efxData;
253 }
254 }
255
saveMidiInputDeviceToConfig(void)256 bool saveMidiInputDeviceToConfig(void)
257 {
258 if (!midi.initThreadDone || midiDev == NULL || !midiDeviceOpened)
259 return false;
260
261 const uint32_t numDevices = getNumMidiInDevices();
262 if (numDevices == 0)
263 return false;
264
265 char *midiInStr = getMidiInDeviceName(midi.inputDevice);
266 if (midiInStr == NULL)
267 return false;
268
269 FILE *f = UNICHAR_FOPEN(editor.midiConfigFileLocationU, "w");
270 if (f == NULL)
271 {
272 free(midiInStr);
273 return false;
274 }
275
276 fputs(midiInStr, f);
277 free(midiInStr);
278
279 fclose(f);
280 return true;
281 }
282
setMidiInputDeviceFromConfig(void)283 bool setMidiInputDeviceFromConfig(void)
284 {
285 uint32_t i;
286
287 if (midi.inputDeviceName != NULL)
288 free(midi.inputDeviceName);
289
290 const uint32_t numDevices = getNumMidiInDevices();
291 if (numDevices == 0)
292 goto setDefMidiInputDev;
293
294 FILE *f = UNICHAR_FOPEN(editor.midiConfigFileLocationU, "r");
295 if (f == NULL)
296 goto setDefMidiInputDev;
297
298 char *devString = (char *)malloc((MAX_DEV_STR_LEN+4) * sizeof (char));
299 if (devString == NULL)
300 {
301 fclose(f);
302 goto setDefMidiInputDev;
303 }
304
305 devString[0] = '\0';
306
307 if (fgets(devString, MAX_DEV_STR_LEN, f) == NULL)
308 {
309 fclose(f);
310 free(devString);
311 goto setDefMidiInputDev;
312 }
313
314 fclose(f);
315
316 // scan for device in list
317 char *midiInStr = NULL;
318 for (i = 0; i < numDevices; i++)
319 {
320 midiInStr = getMidiInDeviceName(i);
321 if (midiInStr == NULL)
322 continue;
323
324 if (!_stricmp(devString, midiInStr))
325 break; // device matched
326
327 free(midiInStr);
328 midiInStr = NULL;
329 }
330
331 free(devString);
332
333 // device not found in list, set default
334 if (i == numDevices)
335 goto setDefMidiInputDev;
336
337 midi.inputDevice = i;
338 midi.inputDeviceName = midiInStr;
339 midi.numInputDevices = numDevices;
340
341 return true;
342
343 // couldn't load device, set default
344 setDefMidiInputDev:
345 midi.inputDevice = 0;
346 midi.inputDeviceName = strdup("RtMidi");
347 midi.numInputDevices = numDevices;
348
349 return false;
350 }
351
freeMidiInputDeviceList(void)352 void freeMidiInputDeviceList(void)
353 {
354 for (int32_t i = 0; i < MAX_MIDI_DEVICES; i++)
355 {
356 if (midi.inputDeviceNames[i] != NULL)
357 {
358 free(midi.inputDeviceNames[i]);
359 midi.inputDeviceNames[i] = NULL;
360 }
361 }
362
363 midi.numInputDevices = 0;
364 }
365
rescanMidiInputDevices(void)366 void rescanMidiInputDevices(void)
367 {
368 freeMidiInputDeviceList();
369
370 midi.numInputDevices = getNumMidiInDevices();
371 if (midi.numInputDevices > MAX_MIDI_DEVICES)
372 midi.numInputDevices = MAX_MIDI_DEVICES;
373
374 for (int32_t i = 0; i < midi.numInputDevices; i++)
375 {
376 char *deviceName = getMidiInDeviceName(i);
377 if (deviceName == NULL)
378 {
379 if (midi.numInputDevices > 0)
380 midi.numInputDevices--; // hide device
381
382 continue;
383 }
384
385 midi.inputDeviceNames[i] = deviceName;
386 }
387
388 setScrollBarEnd(SB_MIDI_INPUT_SCROLL, midi.numInputDevices);
389 setScrollBarPos(SB_MIDI_INPUT_SCROLL, 0, false);
390 }
391
drawMidiInputList(void)392 void drawMidiInputList(void)
393 {
394 clearRect(114, 4, 365, 165);
395
396 if (!midi.initThreadDone || midiDev == NULL || midi.numInputDevices == 0)
397 {
398 textOut(114, 4 + (0 * 11), PAL_FORGRND, "No MIDI input devices found!");
399 textOut(114, 4 + (1 * 11), PAL_FORGRND, "Either wait a few seconds for MIDI to initialize, or restart the");
400 textOut(114, 4 + (2 * 11), PAL_FORGRND, "tracker if you recently plugged in a MIDI device.");
401 return;
402 }
403
404 for (uint16_t i = 0; i < 15; i++)
405 {
406 const int32_t deviceEntry = getScrollBarPos(SB_MIDI_INPUT_SCROLL) + i;
407 if (deviceEntry < midi.numInputDevices)
408 {
409 if (midi.inputDeviceNames[deviceEntry] == NULL)
410 continue;
411
412 const uint16_t y = 4 + (i * 11);
413
414 if (midi.inputDeviceName != NULL)
415 {
416 if (_stricmp(midi.inputDeviceName, midi.inputDeviceNames[deviceEntry]) == 0)
417 fillRect(114, y, 365, 10, PAL_BOXSLCT); // selection background color
418 }
419
420 char *tmpString = utf8ToCp437(midi.inputDeviceNames[deviceEntry], true);
421 if (tmpString != NULL)
422 {
423 textOutClipX(114, y, PAL_FORGRND, tmpString, 479);
424 free(tmpString);
425 }
426 }
427 }
428 }
429
scrollMidiInputDevListUp(void)430 void scrollMidiInputDevListUp(void)
431 {
432 scrollBarScrollUp(SB_MIDI_INPUT_SCROLL, 1);
433 }
434
scrollMidiInputDevListDown(void)435 void scrollMidiInputDevListDown(void)
436 {
437 scrollBarScrollDown(SB_MIDI_INPUT_SCROLL, 1);
438 }
439
sbMidiInputSetPos(uint32_t pos)440 void sbMidiInputSetPos(uint32_t pos)
441 {
442 if (ui.configScreenShown && editor.currConfigScreen == CONFIG_SCREEN_MIDI_INPUT)
443 drawMidiInputList();
444
445 (void)pos;
446 }
447
testMidiInputDeviceListMouseDown(void)448 bool testMidiInputDeviceListMouseDown(void)
449 {
450 if (!ui.configScreenShown || editor.currConfigScreen != CONFIG_SCREEN_MIDI_INPUT)
451 return false; // we didn't click the area
452
453 if (!midi.initThreadDone)
454 return true;
455
456 const int32_t mx = mouse.x;
457 const int32_t my = mouse.y;
458
459 if (my < 4 || my > 166 || mx < 114 || mx > 479)
460 return false; // we didn't click the area
461
462 const int32_t deviceNum = (int32_t)scrollBars[SB_MIDI_INPUT_SCROLL].pos + ((my - 4) / 11);
463 if (midi.numInputDevices <= 0 || deviceNum >= midi.numInputDevices)
464 return true;
465
466 if (midi.inputDeviceName != NULL)
467 {
468 if (!_stricmp(midi.inputDeviceName, midi.inputDeviceNames[deviceNum]))
469 return true; // we clicked the currently selected device, do nothing
470
471 free(midi.inputDeviceName);
472 }
473
474 midi.inputDeviceName = strdup(midi.inputDeviceNames[deviceNum]);
475 midi.inputDevice = deviceNum;
476
477 closeMidiInDevice();
478 freeMidiIn();
479 initMidiIn();
480 openMidiInDevice(midi.inputDevice);
481
482 drawMidiInputList();
483 return true;
484 }
485
initMidiFunc(void * ptr)486 int32_t SDLCALL initMidiFunc(void *ptr)
487 {
488 midi.closeMidiOnExit = true;
489
490 midi.initThreadDone = false;
491 initMidiIn();
492 setMidiInputDeviceFromConfig();
493 openMidiInDevice(midi.inputDevice);
494 midi.initThreadDone = true;
495
496 midi.rescanDevicesFlag = true;
497
498 return true;
499
500 (void)ptr;
501 }
502
503 #else
504 typedef int make_iso_compilers_happy; // kludge: prevent warning about empty .c file if HAS_MIDI is not defined
505 #endif
506