ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

ft2_midi.c (11112B)


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