gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

midiToSysex.cpp (6888B)


      1 #include "midiToSysex.h"
      2 
      3 #include <cstdio>
      4 #include <cstring>	// memcmp
      5 
      6 #include "dsp56kEmu/logging.h"
      7 
      8 #include "baseLib/filesystem.h"
      9 
     10 #ifdef _MSC_VER
     11 #include <Windows.h>
     12 #endif
     13 
     14 namespace synthLib
     15 {
     16 #ifdef _MSC_VER
     17 	std::wstring ToUtf16(const std::string& str)
     18 	{
     19 		std::wstring ret;
     20 		const int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), static_cast<int>(str.size()), nullptr, 0);
     21 		if (len > 0)
     22 		{
     23 			ret.resize(len);
     24 			MultiByteToWideChar(CP_UTF8, 0, str.c_str(), static_cast<int>(str.size()), &ret[0], len);
     25 		}
     26 		return ret;
     27 	}
     28 #endif
     29 
     30 	bool MidiToSysex::readFile(std::vector<uint8_t>& _sysexMessages, const char* _filename)
     31 	{
     32 #ifdef _MSC_VER
     33 		FILE* hFile = _wfopen(ToUtf16(_filename).c_str(), L"rb");
     34 #else
     35 		FILE* hFile = fopen(_filename, "rb");
     36 #endif
     37 
     38 		if (hFile == nullptr)
     39 		{
     40 			LOG("Failed to open file " << _filename);
     41 			return false;
     42 		}
     43 
     44 		if (!checkChunk(hFile,"MThd"))
     45 		{
     46 			fclose(hFile);	// file isn't midi file
     47 			hFile = nullptr;
     48 			return false;
     49 		}
     50 		// ok, file is a midi file, we can start reading
     51 
     52 		// skip the rest of the id chunk
     53 		ignoreChunk(hFile);
     54 
     55 		while (!feof(hFile))
     56 		{
     57 			if (checkChunk(hFile, "MTrk"))
     58 			{
     59 				// ignore the length of the MTrk chunk, we don't need it
     60 				char temp[4];
     61 				fread(temp, 4, 1, hFile);
     62 
     63 				bool bReadNextEvent = true;
     64 				while (bReadNextEvent)
     65 				{
     66 					int numBytesRead;
     67 					const auto timestamp = readVarLen(hFile, &numBytesRead);
     68 
     69 					if (timestamp == -1)
     70 					{
     71 						LOG("Failed to read variable length variable");
     72 						fclose(hFile);
     73 						hFile = nullptr;
     74 						return false;
     75 					}
     76 
     77 					const auto ch = getc(hFile);
     78 
     79 					if (ch == EOF)
     80 						break;
     81 
     82 					const auto ev = static_cast<uint8_t>(ch);
     83 
     84 					switch (ev)
     85 					{
     86 					case 0xf0: // that's what we're searching for, sysex data
     87 						{
     88 							const auto sysExLen = readVarLen(hFile, &numBytesRead);
     89 
     90 							std::vector<uint8_t> sysex;
     91 							sysex.reserve(sysExLen + 1);
     92 
     93 							sysex.push_back(0xf0);
     94 
     95 							// we ignore the provided sysex length as I've seen files that do not have the length encoded properly
     96 							while(true)
     97 							{
     98 								const auto c = getc(hFile);
     99 
    100 								if(c == 0xf7 || c == 0xf8)	// Virus Powercore writes f8 instead of f7
    101 								{
    102 									sysex.push_back(0xf7);
    103 									_sysexMessages.insert(_sysexMessages.end(), sysex.begin(), sysex.end());
    104 									break;
    105 								}
    106 
    107 								sysex.push_back(static_cast<uint8_t>(c));
    108 
    109 								if (feof(hFile))
    110 									break;
    111 							}
    112 						}
    113 						break;
    114 
    115 					case 0xff:	// meta event
    116 						{
    117 							const auto metaEvent = getc(hFile);
    118 							const auto eventLen = getc(hFile);
    119 
    120 							switch (metaEvent)
    121 							{
    122 							case 0x2f:
    123 								// track end
    124 								bReadNextEvent = false;
    125 								break;
    126 							default:
    127 								std::vector<char> buffer;
    128 								buffer.resize(eventLen);
    129 								fread(&buffer[0], eventLen, 1, hFile);
    130 							}
    131 						}
    132 						break;
    133 					default:
    134 						// Other events like notes, .....
    135 						break;
    136 					}
    137 				}
    138 			}
    139 			else if (!ignoreChunk(hFile))
    140 				break;
    141 		}
    142 		fclose(hFile);
    143 		return true;
    144 	}
    145 
    146 	void MidiToSysex::splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src, const bool _isMidiFileData/* = false*/)
    147 	{
    148 		if(!_isMidiFileData)
    149 		{
    150 			std::vector<size_t> indices;
    151 
    152 			for (size_t i = 0; i < _src.size(); ++i)
    153 			{
    154 				if (indices.size() & 1)
    155 				{
    156 					if (_src[i] == 0xf7)
    157 						indices.push_back(i);
    158 				}
    159 				else if (_src[i] == 0xf0)
    160 				{
    161 					indices.push_back(i);
    162 				}
    163 			}
    164 
    165 			if (indices.size() & 1)
    166 				indices.pop_back();
    167 
    168 			for(size_t i=0; i<indices.size(); i += 2)
    169 			{
    170 				auto& e =_dst.emplace_back();
    171 				e.assign(_src.begin() + indices[i], _src.begin() + indices[i + 1] + 1);
    172 			}
    173 			return;
    174 		}
    175 
    176 		for (size_t i = 0; i < _src.size(); ++i)
    177 		{
    178 			if (_src[i] != 0xf0)
    179 				continue;
    180 
    181 			uint32_t numBytesRead = 0;
    182 			uint32_t length = 0;
    183 
    184 			readVarLen(numBytesRead, length, &_src[i + 1], _src.size() - i - 1);
    185 
    186 			// do some simple validation here, I've seen midi files where sysex is stored without varlength encoding
    187 			if (length == 0 || (numBytesRead > 1 && length < 128))
    188 				numBytesRead = 0;
    189 
    190 			const auto jStart = i + numBytesRead + 1;
    191 
    192 			for(size_t j = jStart; j < _src.size(); ++j)
    193 			{
    194 				if(_src[j] <= 0xf0)
    195 					continue;
    196 
    197 				std::vector<uint8_t> entry;
    198 				entry.reserve(j - jStart + 2);
    199 				entry.push_back(0xf0);
    200 				entry.insert(entry.end(), _src.begin() + jStart, _src.begin() + j);
    201 				entry.push_back(0xf7);
    202 				_dst.emplace_back(std::move(entry));
    203 				i = j;
    204 				break;
    205 			}
    206 		}
    207 	}
    208 
    209 	bool MidiToSysex::extractSysexFromFile(std::vector<std::vector<uint8_t>>& _messages, const std::string& _filename)
    210 	{
    211 		std::vector<uint8_t> data;
    212 
    213 		if(!baseLib::filesystem::readFile(data, _filename))
    214 			return false;
    215 
    216 		return extractSysexFromData(_messages, data);
    217 	}
    218 
    219 	bool MidiToSysex::extractSysexFromData(std::vector<std::vector<uint8_t>>& _messages, const std::vector<uint8_t>& _data)
    220 	{
    221 		constexpr uint8_t midiHeader[] = "MThd";
    222 		const auto isMidiFile = _data.size() >= 4 && memcmp(_data.data(), midiHeader, 4) == 0;
    223 		splitMultipleSysex(_messages, _data, isMidiFile);
    224 		return !_messages.empty();
    225 	}
    226 
    227 	bool MidiToSysex::checkChunk(FILE* hFile, const char* _pCompareChunk)
    228 	{
    229 		char readChunk[4];
    230 
    231 		if (fread(readChunk, 1, 4, hFile) != 4)
    232 			return false;
    233 
    234 		if (readChunk[0] == _pCompareChunk[0] && readChunk[1] == _pCompareChunk[1] &&
    235 			readChunk[2] == _pCompareChunk[2] && readChunk[3] == _pCompareChunk[3])
    236 			return true;
    237 
    238 		return false;
    239 	}
    240 	uint32_t MidiToSysex::getChunkLength(FILE* hFile)
    241 	{
    242 		const auto a = getc(hFile);
    243 		const auto b = getc(hFile);
    244 		const auto c = getc(hFile);
    245 		const auto d = getc(hFile);
    246 		
    247 		if(a == EOF || b == EOF || c == EOF || d == EOF)
    248 			return -1;
    249 
    250 		return (uint32_t(a) << 24 | uint32_t(b) << 16 | uint32_t(c) << 8 | uint32_t(d));
    251 	}
    252 	bool MidiToSysex::ignoreChunk(FILE* hFile)
    253 	{
    254 		uint32_t len = getChunkLength(hFile);
    255 
    256 		if ((int32_t)len == -1)
    257 			return false;
    258 
    259 		fseek(hFile, len, SEEK_CUR);
    260 
    261 		if (feof(hFile))
    262 			return false;
    263 
    264 		return true;
    265 	}
    266 
    267 	int32_t MidiToSysex::readVarLen(FILE* hFile, int* _pNumBytesRead)
    268 	{
    269 		if (feof(hFile))
    270 		{
    271 			*_pNumBytesRead = 0;
    272 			return -1;
    273 		}
    274 
    275 		uint32_t value;
    276 		uint8_t c;
    277 
    278 		*_pNumBytesRead = 1;
    279 
    280 		if ((value = getc(hFile)) & 0x80)
    281 		{
    282 			value &= 0x7F;
    283 			do
    284 			{
    285 				value = (value << 7) + ((c = getc(hFile)) & 0x7F);
    286 				*_pNumBytesRead += 1;
    287 				if (feof(hFile))
    288 				{
    289 					*_pNumBytesRead = 0;
    290 					return -1;
    291 				}
    292 			} while (c & 0x80);
    293 		}
    294 		return(value);
    295 	}
    296 
    297 	void MidiToSysex::readVarLen(uint32_t& _numBytesRead, uint32_t& _result, const uint8_t* _data, const size_t _numBytes)
    298 	{
    299 		_numBytesRead = 0;
    300 		_result = 0;
    301 
    302 		for(size_t i=0; i<_numBytes; ++i)
    303 		{
    304 			const auto b = _data[i];
    305 
    306 			const uint32_t v = b & 0x7f;
    307 
    308 			_result += v;
    309 
    310 			++_numBytesRead;
    311 
    312 			if (b & 0x80)
    313 				_result <<= 7;
    314 			else
    315 				break;
    316 		}
    317 	}
    318 }