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