Added ability to switch emulator and it's accuracy level ("enabling of 'run at PCM rate' reduces accuracy, and also reduces CPU usage")
Added draft code for future external banks support (WOPL format for ADLMIDI and WOPN format for OPNMIDI)
ADLMIDI 1.3.3 2018-06-19
* Fixed an inability to load another custom bank without of library re-initialization
* Optimizing the MIDI banks management system for MultiBanks (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
* Fixed incorrect 4-op counter which is still catch 4-op instruments on 2-op banks
* Fixed an incorrect processing of auto-flags
* Fixed incorrect initial MIDI tempo when MIDI file doesn't includes the tempo event
* Channel and Note Aftertouch features are now supported correctly! Aftertouch is the tremolo / vibrato, NOT A VOLUME!
* Updated DosBox OPL3 emulator up to r4111 of official DosBox trunk (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
* The automatical choosing of 4 operator channels count has been improved (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
* Added optional HQ resampler for Nuked OPL3 emulators which does usage of Zita-Resampler library (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
ADLMIDI 1.3.2 2018-04-24
* Added ability to disable MUS and XMI converters
* Added ability to disable embedded MIDI sequencer to use library as RealTime synthesizer only or use any custom MIDI sequencer plugins.
* Fixed blank instruments fallback in multi-bank support. When using non-zero bank, if instrument is blank, then, instrument will be taken from a root (I.e. zero bank).
* Added support for real-time switching the emulator
* Added support for CC-120 - "All sound off" on the MIDI channel
* Changed logic of CC-74 Brightness to affect sound only between 0 and 64 like real XG synthesizers. Ability to turn on a full-ranged brightness (to use full 0...127 range) is kept.
* Added support for different output sample formats (PCM8, PCM8U, PCM16, PCM16U, PCM32, PCM32U, Float32, and Float64) (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
* Reworked MIDI channels management to avoid any memory reallocations while music processing for a hard real time. (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
OPNMIDI 1.3.0 2018-06-19
* Optimizing the MIDI banks management system for MultiBanks (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
* Fixed incorrect initial MIDI tempo when MIDI file doesn't includes the tempo event
* Fixed an incorrect processing of auto-flags
* MAME YM2612 now results a more accurate sound as internal using of native sample rate makes more correct sound generation
* Channel and Note Aftertouch features are now supported correctly! Aftertouch is the tremolo / vibrato, NOT A VOLUME!
* Added optional HQ resampler for Nuked OPL3 emulators which does usage of Zita-Resampler library (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
OPNMIDI 1.2.0 2018-04-24
* Added ability to disable MUS and XMI converters
* Added ability to disable embedded MIDI sequencer to use library as RealTime synthesizer only or use any custom MIDI sequencer plugins.
* Fixed blank instruments fallback in multi-bank support. When using non-zero bank, if instrument is blank, then, instrument will be taken from a root (I.e. zero bank).
* Added support for real-time switching the emulator
* Added support for MAME YM2612 Emulator
* Added support for CC-120 - "All sound off" on the MIDI channel
* Changed logic of CC-74 Brightness to affect sound only between 0 and 64 like real XG synthesizers. Ability to turn on a full-ranged brightness (to use full 0...127 range) is kept.
* Added support for different output sample formats (PCM8, PCM8U, PCM16, PCM16U, PCM32, PCM32U, Float32, and Float64) (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
* Reworked MIDI channels management to avoid any memory reallocations while music processing for a hard real time. (Thanks to [Jean Pierre Cimalando](https://github.com/jpcima) for a work!)
563 lines
16 KiB
C++
563 lines
16 KiB
C++
/*
|
|
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 (YM2612) emulation
|
|
*
|
|
* MIDI parser and player (Original code from ADLMIDI): Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
|
|
* OPNMIDI Library and YM2612 support: Copyright (c) 2017-2018 Vitaly Novichkov <admin@wohlnet.ru>
|
|
*
|
|
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
|
|
* http://iki.fi/bisqwit/source/adlmidi.html
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "opnmidi_private.hpp"
|
|
|
|
#ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
|
|
# ifndef OPNMIDI_DISABLE_MUS_SUPPORT
|
|
# include "opnmidi_mus2mid.h"
|
|
# endif
|
|
# ifndef OPNMIDI_DISABLE_XMI_SUPPORT
|
|
# include "opnmidi_xmi2mid.h"
|
|
# endif
|
|
#endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
|
|
|
|
uint64_t OPNMIDIplay::ReadBEint(const void *buffer, size_t nbytes)
|
|
{
|
|
uint64_t result = 0;
|
|
const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer);
|
|
|
|
for(unsigned n = 0; n < nbytes; ++n)
|
|
result = (result << 8) + data[n];
|
|
|
|
return result;
|
|
}
|
|
|
|
uint64_t OPNMIDIplay::ReadLEint(const void *buffer, size_t nbytes)
|
|
{
|
|
uint64_t result = 0;
|
|
const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer);
|
|
|
|
for(unsigned n = 0; n < nbytes; ++n)
|
|
result = result + static_cast<uint64_t>(data[n] << (n * 8));
|
|
|
|
return result;
|
|
}
|
|
|
|
//uint64_t OPNMIDIplay::ReadVarLenEx(size_t tk, bool &ok)
|
|
//{
|
|
// uint64_t result = 0;
|
|
// ok = false;
|
|
|
|
// for(;;)
|
|
// {
|
|
// if(tk >= TrackData.size())
|
|
// return 1;
|
|
|
|
// if(tk >= CurrentPosition.track.size())
|
|
// return 2;
|
|
|
|
// size_t ptr = CurrentPosition.track[tk].ptr;
|
|
|
|
// if(ptr >= TrackData[tk].size())
|
|
// return 3;
|
|
|
|
// unsigned char byte = TrackData[tk][CurrentPosition.track[tk].ptr++];
|
|
// result = (result << 7) + (byte & 0x7F);
|
|
|
|
// if(!(byte & 0x80)) break;
|
|
// }
|
|
|
|
// ok = true;
|
|
// return result;
|
|
//}
|
|
|
|
bool OPNMIDIplay::LoadBank(const std::string &filename)
|
|
{
|
|
fileReader file;
|
|
file.openFile(filename.c_str());
|
|
return LoadBank(file);
|
|
}
|
|
|
|
bool OPNMIDIplay::LoadBank(const void *data, size_t size)
|
|
{
|
|
fileReader file;
|
|
file.openData(data, (size_t)size);
|
|
return LoadBank(file);
|
|
}
|
|
|
|
size_t readU16BE(OPNMIDIplay::fileReader &fr, uint16_t &out)
|
|
{
|
|
uint8_t arr[2];
|
|
size_t ret = fr.read(arr, 1, 2);
|
|
out = arr[1];
|
|
out |= ((arr[0] << 8) & 0xFF00);
|
|
return ret;
|
|
}
|
|
|
|
size_t readS16BE(OPNMIDIplay::fileReader &fr, int16_t &out)
|
|
{
|
|
uint8_t arr[2];
|
|
size_t ret = fr.read(arr, 1, 2);
|
|
out = *reinterpret_cast<signed char *>(&arr[0]);
|
|
out *= 1 << 8;
|
|
out |= arr[1];
|
|
return ret;
|
|
}
|
|
|
|
int16_t toSint16BE(uint8_t *arr)
|
|
{
|
|
int16_t num = *reinterpret_cast<const int8_t *>(&arr[0]);
|
|
num *= 1 << 8;
|
|
num |= arr[1];
|
|
return num;
|
|
}
|
|
|
|
static uint16_t toUint16LE(const uint8_t *arr)
|
|
{
|
|
uint16_t num = arr[0];
|
|
num |= ((arr[1] << 8) & 0xFF00);
|
|
return num;
|
|
}
|
|
|
|
static uint16_t toUint16BE(const uint8_t *arr)
|
|
{
|
|
uint16_t num = arr[1];
|
|
num |= ((arr[0] << 8) & 0xFF00);
|
|
return num;
|
|
}
|
|
|
|
|
|
static const char *wopn2_magic1 = "WOPN2-BANK\0";
|
|
static const char *wopn2_magic2 = "WOPN2-B2NK\0";
|
|
|
|
#define WOPL_INST_SIZE_V1 65
|
|
#define WOPL_INST_SIZE_V2 69
|
|
|
|
static const uint16_t latest_version = 2;
|
|
|
|
bool OPNMIDIplay::LoadBank(OPNMIDIplay::fileReader &fr)
|
|
{
|
|
size_t fsize;
|
|
ADL_UNUSED(fsize);
|
|
if(!fr.isValid())
|
|
{
|
|
errorStringOut = "Can't load bank file: Invalid data stream!";
|
|
return false;
|
|
}
|
|
|
|
char magic[32];
|
|
std::memset(magic, 0, 32);
|
|
uint16_t version = 1;
|
|
|
|
uint16_t count_melodic_banks = 1;
|
|
uint16_t count_percussive_banks = 1;
|
|
|
|
if(fr.read(magic, 1, 11) != 11)
|
|
{
|
|
errorStringOut = "Can't load bank file: Can't read magic number!";
|
|
return false;
|
|
}
|
|
|
|
bool is1 = std::strncmp(magic, wopn2_magic1, 11) == 0;
|
|
bool is2 = std::strncmp(magic, wopn2_magic2, 11) == 0;
|
|
|
|
if(!is1 && !is2)
|
|
{
|
|
errorStringOut = "Can't load bank file: Invalid magic number!";
|
|
return false;
|
|
}
|
|
|
|
if(is2)
|
|
{
|
|
uint8_t ver[2];
|
|
if(fr.read(ver, 1, 2) != 2)
|
|
{
|
|
errorStringOut = "Can't load bank file: Can't read version number!";
|
|
return false;
|
|
}
|
|
version = toUint16LE(ver);
|
|
if(version < 2 || version > latest_version)
|
|
{
|
|
errorStringOut = "Can't load bank file: unsupported WOPN version!";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
opn.cleanInstrumentBanks();
|
|
if((readU16BE(fr, count_melodic_banks) != 2) || (readU16BE(fr, count_percussive_banks) != 2))
|
|
{
|
|
errorStringOut = "Can't load bank file: Can't read count of banks!";
|
|
return false;
|
|
}
|
|
|
|
if((count_melodic_banks < 1) || (count_percussive_banks < 1))
|
|
{
|
|
errorStringOut = "Custom bank: Too few banks in this file!";
|
|
return false;
|
|
}
|
|
|
|
if(fr.read(&opn.regLFO, 1, 1) != 1)
|
|
{
|
|
errorStringOut = "Can't load bank file: Can't read LFO registry state!";
|
|
return false;
|
|
}
|
|
|
|
opn.cleanInstrumentBanks();
|
|
|
|
std::vector<OPN2::Bank *> banks;
|
|
banks.reserve(count_melodic_banks + count_percussive_banks);
|
|
|
|
if(version >= 2)//Read bank meta-entries
|
|
{
|
|
for(uint16_t i = 0; i < count_melodic_banks; i++)
|
|
{
|
|
uint8_t bank_meta[34];
|
|
if(fr.read(bank_meta, 1, 34) != 34)
|
|
{
|
|
opn.cleanInstrumentBanks();
|
|
errorStringOut = "Custom bank: Fail to read melodic bank meta-data!";
|
|
return false;
|
|
}
|
|
uint16_t bankno = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]);
|
|
OPN2::Bank &bank = opn.dynamic_banks[bankno];
|
|
//strncpy(bank.name, char_p(bank_meta), 32);
|
|
banks.push_back(&bank);
|
|
}
|
|
|
|
for(uint16_t i = 0; i < count_percussive_banks; i++)
|
|
{
|
|
uint8_t bank_meta[34];
|
|
if(fr.read(bank_meta, 1, 34) != 34)
|
|
{
|
|
opn.cleanInstrumentBanks();
|
|
errorStringOut = "Custom bank: Fail to read percussion bank meta-data!";
|
|
return false;
|
|
}
|
|
uint16_t bankno = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]) + OPN2::PercussionTag;
|
|
OPN2::Bank &bank = opn.dynamic_banks[bankno];
|
|
//strncpy(bank.name, char_p(bank_meta), 32);
|
|
banks.push_back(&bank);
|
|
}
|
|
}
|
|
|
|
size_t total = 128 * opn.dynamic_banks.size();
|
|
|
|
for(size_t i = 0; i < total; i++)
|
|
{
|
|
opnInstMeta2 &meta = banks[i / 128]->ins[i % 128];
|
|
opnInstData &data = meta.opn[0];
|
|
uint8_t idata[WOPL_INST_SIZE_V2];
|
|
|
|
size_t readSize = version >= 2 ? WOPL_INST_SIZE_V2 : WOPL_INST_SIZE_V1;
|
|
if(fr.read(idata, 1, readSize) != readSize)
|
|
{
|
|
opn.cleanInstrumentBanks();
|
|
errorStringOut = "Can't load bank file: Failed to read instrument data";
|
|
return false;
|
|
}
|
|
data.finetune = toSint16BE(idata + 32);
|
|
//Percussion instrument note number or a "fixed note sound"
|
|
meta.tone = idata[34];
|
|
data.fbalg = idata[35];
|
|
data.lfosens = idata[36];
|
|
for(size_t op = 0; op < 4; op++)
|
|
{
|
|
size_t off = 37 + op * 7;
|
|
std::memcpy(data.OPS[op].data, idata + off, 7);
|
|
}
|
|
|
|
meta.flags = 0;
|
|
if(version >= 2)
|
|
{
|
|
meta.ms_sound_kon = toUint16BE(idata + 65);
|
|
meta.ms_sound_koff = toUint16BE(idata + 67);
|
|
if((meta.ms_sound_kon == 0) && (meta.ms_sound_koff == 0))
|
|
meta.flags |= opnInstMeta::Flag_NoSound;
|
|
}
|
|
else
|
|
{
|
|
meta.ms_sound_kon = 1000;
|
|
meta.ms_sound_koff = 500;
|
|
}
|
|
|
|
meta.opn[1] = meta.opn[0];
|
|
|
|
/* Junk, delete later */
|
|
meta.fine_tune = 0.0;
|
|
/* Junk, delete later */
|
|
}
|
|
|
|
applySetup();
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
|
|
bool OPNMIDIplay::LoadMIDI(const std::string &filename)
|
|
{
|
|
fileReader file;
|
|
file.openFile(filename.c_str());
|
|
if(!LoadMIDI(file))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool OPNMIDIplay::LoadMIDI(const void *data, size_t size)
|
|
{
|
|
fileReader file;
|
|
file.openData(data, size);
|
|
return LoadMIDI(file);
|
|
}
|
|
|
|
bool OPNMIDIplay::LoadMIDI(OPNMIDIplay::fileReader &fr)
|
|
{
|
|
size_t fsize;
|
|
ADL_UNUSED(fsize);
|
|
//! Temp buffer for conversion
|
|
AdlMIDI_CPtr<uint8_t> cvt_buf;
|
|
errorString.clear();
|
|
|
|
if(opn.dynamic_banks.empty())
|
|
{
|
|
errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!";
|
|
return false;
|
|
}
|
|
|
|
if(!fr.isValid())
|
|
{
|
|
errorStringOut = "Invalid data stream!\n";
|
|
#ifndef _WIN32
|
|
errorStringOut += std::strerror(errno);
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
/**** Set all properties BEFORE starting of actial file reading! ****/
|
|
applySetup();
|
|
|
|
atEnd = false;
|
|
loopStart = true;
|
|
invalidLoop = false;
|
|
|
|
bool is_GMF = false; // GMD/MUS files (ScummVM)
|
|
bool is_RSXX = false; // RSXX, such as Cartooners
|
|
|
|
const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14
|
|
char HeaderBuf[HeaderSize] = "";
|
|
size_t DeltaTicks = 192, TrackCount = 1;
|
|
|
|
riffskip:
|
|
fsize = fr.read(HeaderBuf, 1, HeaderSize);
|
|
|
|
if(std::memcmp(HeaderBuf, "RIFF", 4) == 0)
|
|
{
|
|
fr.seek(6l, SEEK_CUR);
|
|
goto riffskip;
|
|
}
|
|
|
|
if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0)
|
|
{
|
|
// GMD/MUS files (ScummVM)
|
|
fr.seek(7 - static_cast<long>(HeaderSize), SEEK_CUR);
|
|
is_GMF = true;
|
|
}
|
|
|
|
#ifndef OPNMIDI_DISABLE_MUS_SUPPORT
|
|
else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0)
|
|
{
|
|
// MUS/DMX files (Doom)
|
|
fr.seek(0, SEEK_END);
|
|
size_t mus_len = fr.tell();
|
|
fr.seek(0, SEEK_SET);
|
|
uint8_t *mus = (uint8_t *)malloc(mus_len);
|
|
if(!mus)
|
|
{
|
|
errorStringOut = "Out of memory!";
|
|
return false;
|
|
}
|
|
fr.read(mus, 1, mus_len);
|
|
//Close source stream
|
|
fr.close();
|
|
|
|
uint8_t *mid = NULL;
|
|
uint32_t mid_len = 0;
|
|
int m2mret = OpnMidi_mus2midi(mus, static_cast<uint32_t>(mus_len),
|
|
&mid, &mid_len, 0);
|
|
if(mus) free(mus);
|
|
if(m2mret < 0)
|
|
{
|
|
errorStringOut = "Invalid MUS/DMX data format!";
|
|
return false;
|
|
}
|
|
cvt_buf.reset(mid);
|
|
//Open converted MIDI file
|
|
fr.openData(mid, static_cast<size_t>(mid_len));
|
|
//Re-Read header again!
|
|
goto riffskip;
|
|
}
|
|
#endif //OPNMIDI_DISABLE_MUS_SUPPORT
|
|
|
|
#ifndef OPNMIDI_DISABLE_XMI_SUPPORT
|
|
else if(std::memcmp(HeaderBuf, "FORM", 4) == 0)
|
|
{
|
|
if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0)
|
|
{
|
|
fr.close();
|
|
errorStringOut = fr._fileName + ": Invalid format\n";
|
|
return false;
|
|
}
|
|
|
|
fr.seek(0, SEEK_END);
|
|
size_t mus_len = fr.tell();
|
|
fr.seek(0, SEEK_SET);
|
|
uint8_t *mus = (uint8_t*)malloc(mus_len);
|
|
if(!mus)
|
|
{
|
|
errorStringOut = "Out of memory!";
|
|
return false;
|
|
}
|
|
fr.read(mus, 1, mus_len);
|
|
//Close source stream
|
|
fr.close();
|
|
|
|
uint8_t *mid = NULL;
|
|
uint32_t mid_len = 0;
|
|
int m2mret = OpnMidi_xmi2midi(mus, static_cast<uint32_t>(mus_len),
|
|
&mid, &mid_len, XMIDI_CONVERT_NOCONVERSION);
|
|
if(mus) free(mus);
|
|
if(m2mret < 0)
|
|
{
|
|
errorStringOut = "Invalid XMI data format!";
|
|
return false;
|
|
}
|
|
cvt_buf.reset(mid);
|
|
//Open converted MIDI file
|
|
fr.openData(mid, static_cast<size_t>(mid_len));
|
|
//Re-Read header again!
|
|
goto riffskip;
|
|
}
|
|
#endif //OPNMIDI_DISABLE_XMI_SUPPORT
|
|
|
|
else
|
|
{
|
|
// Try to identify RSXX format
|
|
if(HeaderBuf[0] == 0x7D)
|
|
{
|
|
fr.seek(0x6D, SEEK_SET);
|
|
fr.read(HeaderBuf, 6, 1);
|
|
if(std::memcmp(HeaderBuf, "rsxx}u", 6) == 0)
|
|
{
|
|
is_RSXX = true;
|
|
fr.seek(0x7D, SEEK_SET);
|
|
TrackCount = 1;
|
|
DeltaTicks = 60;
|
|
//opl.CartoonersVolumes = true;
|
|
opn.m_musicMode = OPN2::MODE_RSXX;
|
|
opn.m_volumeScale = OPN2::VOLUME_CMF;
|
|
}
|
|
}
|
|
|
|
if(!is_RSXX)
|
|
{
|
|
if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0)
|
|
{
|
|
fr.close();
|
|
errorStringOut = fr._fileName + ": Invalid format, Header signature is unknown!\n";
|
|
return false;
|
|
}
|
|
|
|
/*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/
|
|
TrackCount = (size_t)ReadBEint(HeaderBuf + 10, 2);
|
|
DeltaTicks = (size_t)ReadBEint(HeaderBuf + 12, 2);
|
|
}
|
|
}
|
|
|
|
TrackData.clear();
|
|
TrackData.resize(TrackCount, std::vector<uint8_t>());
|
|
InvDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks));
|
|
Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks) * 2);
|
|
static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00};
|
|
size_t totalGotten = 0;
|
|
|
|
for(size_t tk = 0; tk < TrackCount; ++tk)
|
|
{
|
|
// Read track header
|
|
size_t TrackLength;
|
|
{
|
|
if(is_GMF || is_RSXX) // Take the rest of the file
|
|
{
|
|
size_t pos = fr.tell();
|
|
fr.seek(0, SEEK_END);
|
|
TrackLength = fr.tell() - pos;
|
|
fr.seek(static_cast<long>(pos), SEEK_SET);
|
|
}
|
|
else
|
|
{
|
|
fsize = fr.read(HeaderBuf, 1, 8);
|
|
if(std::memcmp(HeaderBuf, "MTrk", 4) != 0)
|
|
{
|
|
fr.close();
|
|
errorStringOut = fr._fileName + ": Invalid format, MTrk signature is not found!\n";
|
|
return false;
|
|
}
|
|
TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4);
|
|
}
|
|
|
|
// Read track data
|
|
TrackData[tk].resize(TrackLength);
|
|
fsize = fr.read(&TrackData[tk][0], 1, TrackLength);
|
|
totalGotten += fsize;
|
|
|
|
if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag.
|
|
TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4);
|
|
if(is_RSXX)//Finalize raw track data with a zero
|
|
TrackData[tk].push_back(0);
|
|
|
|
//bool ok = false;
|
|
//// Read next event time
|
|
//uint64_t tkDelay = ReadVarLenEx(tk, ok);
|
|
//if(ok)
|
|
// CurrentPosition.track[tk].delay = tkDelay;
|
|
//else
|
|
//{
|
|
// std::stringstream msg;
|
|
// msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")";
|
|
// OPN2MIDI_ErrorString = msg.str();
|
|
// return false;
|
|
//}
|
|
}
|
|
}
|
|
|
|
for(size_t tk = 0; tk < TrackCount; ++tk)
|
|
totalGotten += TrackData[tk].size();
|
|
|
|
if(totalGotten == 0)
|
|
{
|
|
errorStringOut = fr._fileName + ": Empty track data";
|
|
return false;
|
|
}
|
|
|
|
//Build new MIDI events table
|
|
if(!buildTrackData())
|
|
{
|
|
errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString;
|
|
return false;
|
|
}
|
|
|
|
opn.Reset(m_setup.emulator, m_setup.PCM_RATE); // Reset OPN2 chip
|
|
ch.clear();
|
|
ch.resize(opn.NumChannels);
|
|
return true;
|
|
}
|
|
#endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
|