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 }