Hi all,
I'm just writing to submit this patch that I've been working on /
testing for the last while.
The patch provides code for reading and updating 'XFIL' and 'DOXF'
chunks during load and save operations. The 'XFIL' chunk gives the
count, filenames and expected dlsids of extension files except a .gx99
file if present. The presence of a 'DOXF' chunk indicates that a .gx99
extension file should be present on disk and the chunk itself provides
the expected dlsid.
The addition of this code enables correct Save and Save As of multi-part
gig files (those with extension files: .gx01, .gx02 etc.). It also
simplifies the way that multi-part .gig files are loaded.
I have tested multi-part .gig files as well as ordinary 32-bit offset
.gig files saved with this patch applied in GSEdit3 and GStdio3 and they
load and play as expected. I haven't tested the changes with a library
consisting of a single .gig file which uses 64-bit offsets as I don't
have any of those to hand. There shouldn't be any change for files that
don't have extension files though.
All the best,
Ivan
diff -Naur libgig_original/src/DLS.cpp libgig_modified/src/DLS.cpp
--- libgig_original/src/DLS.cpp 2018-12-17 19:34:26.645343163 +0000
+++ libgig_modified/src/DLS.cpp 2019-02-09 21:36:50.625954000 +0000
@@ -24,6 +24,7 @@
#include "DLS.h"
#include <algorithm>
+#include <vector>
#include <time.h>
#ifdef __APPLE__
@@ -1770,12 +1771,82 @@
__notify_progress(&subprogress, 1.0); // notify subprogress done
}
+ // update the 'xfil' chunk which describes all extension files except the .gx99 file
+ RIFF::Chunk* ckXfil = pRIFF->GetSubChunk(CHUNK_ID_XFIL);
+ if (ckXfil) {
+ uint8_t* pData = (uint8_t*) ckXfil->LoadChunkData();
+ uint32_t numRecords = load32(pData);
+ int offset = 4;
+ std::list<RIFF::File*>::iterator extIt = ExtensionFiles.begin();
+ for (int i = 0; i < numRecords && offset < ckXfil->GetSize(); i++, offset += 144) {
+ if (extIt == ExtensionFiles.end()) {
+ throw Exception("Fatal error, 'xfil' chunk has wrong number of records");
+ }
+ // update the filename string and 5 byte extension of each extension file
+ std::string path = (*extIt)->GetFileName();
+ std::string file = path;
+ size_t pos = path.find_last_of(GetPathSeparator(path));
+ if (pos != std::string::npos) {
+ file = path.substr(pos+1, path.length() - pos);
+ }
+ if (file.length() + 6 > 128) {
+ throw Exception("Fatal error, 'xfil' chunk filename length exceeds 122 byte maximum");
+ }
+ uint8_t* pStrings = pData + offset;
+ memset(pStrings, 0, 128);
+ memcpy(pStrings, file.c_str(), file.length());
+ pStrings += file.length() + 1;
+ std::string ext = file.substr(file.length()-5, 5);
+ memcpy(pStrings, ext.c_str(), 5);
+ // update the dlsid of the extension file
+ uint8_t* pId = pData + offset + 128;
+ RIFF::Chunk* ckDLSID = (*extIt)->GetSubChunk(CHUNK_ID_DLID);
+ if (ckDLSID) {
+ dlsid_t id;
+ ckDLSID->Read(&id.ulData1, 1, 4);
+ ckDLSID->Read(&id.usData2, 1, 2);
+ ckDLSID->Read(&id.usData3, 1, 2);
+ ckDLSID->Read(id.abData, 8, 1);
+ store32(pId, id.ulData1);
+ store16(pId + 4, id.usData2);
+ store16(pId + 6, id.usData3);
+ memcpy(&pId[8], id.abData, 8);
+ }
+ extIt++;
+ }
+ }
+
+ // update the 'doxf' chunk which describes a .gx99 extension file
+ RIFF::Chunk* ckDoxf = pRIFF->GetSubChunk(CHUNK_ID_DOXF);
+ if (ckDoxf) {
+ if (!ExtensionFiles.size()) {
+ throw Exception("Fatal error, 'doxf' chunk present with no extension files");
+ }
+ uint8_t* pData = (uint8_t*) ckDoxf->LoadChunkData();
+ RIFF::File* last = ExtensionFiles.back();
+
+ // update the dlsid of the extension file
+ uint8_t* pId = pData + 132;
+ RIFF::Chunk* ckDLSID = last->GetSubChunk(CHUNK_ID_DLID);
+ if (ckDLSID) {
+ dlsid_t id;
+ ckDLSID->Read(&id.ulData1, 1, 4);
+ ckDLSID->Read(&id.usData2, 1, 2);
+ ckDLSID->Read(&id.usData3, 1, 2);
+ ckDLSID->Read(id.abData, 8, 1);
+ store32(pId, id.ulData1);
+ store16(pId + 4, id.usData2);
+ store16(pId + 6, id.usData3);
+ memcpy(&pId[8], id.abData, 8);
+ }
+ }
+
// the RIFF file to be written might now been grown >= 4GB or might
// been shrunk < 4GB, so we might need to update the wave pool offset
// size and thus accordingly we would need to resize the wave pool
// chunk
const file_offset_t finalFileSize = pRIFF->GetRequiredFileSize();
- const bool bRequires64Bit = (finalFileSize >> 32) != 0;
+ const bool bRequires64Bit = (finalFileSize >> 32) != 0 || ExtensionFiles.size() > 0;
if (b64BitWavePoolOffsets != bRequires64Bit) {
b64BitWavePoolOffsets = bRequires64Bit;
iPtblOffsetSize = (b64BitWavePoolOffsets) ? 8 : 4;
@@ -1801,18 +1872,43 @@
* @param pProgress - optional: callback function for progress notification
*/
void File::Save(const String& Path, progress_t* pProgress) {
+ // calculate number of tasks to display progress of
+ float tasks = 2.f + ExtensionFiles.size();
+ // get the destination folder name
+ std::string path = Path;
+ std::string folder = "";
+ std::string newfile = path.substr(0, path.length() - 4);
+ size_t pos = path.find_last_of(GetPathSeparator(path));
+ if (pos != std::string::npos) {
+ folder = path.substr(0, pos + 1);
+ newfile = path.substr(pos + 1, path.length() - pos - 5);
+ }
+ int extCount = 0;
+ std::list<RIFF::File*>::iterator it = ExtensionFiles.begin();
+ while (it != ExtensionFiles.end()) {
+ // divide local progress into subprogress
+ progress_t subprogress;
+ __divide_progress(pProgress, &subprogress, tasks, 0.f + extCount); // arbitrarily subdivided into total progress / tasks
+ std::string old = (*it)->GetFileName();
+ std::string ext = old.substr(old.length() - 5, 5);
+ std::string newpath = folder + newfile + ext;
+ // save each extension file to its new location
+ (*it)->Save(newpath, &subprogress);
+ extCount++;
+ it++;
+ }
{
// divide local progress into subprogress
progress_t subprogress;
- __divide_progress(pProgress, &subprogress, 2.f, 0.f); // arbitrarily subdivided into 50% of total progress
+ __divide_progress(pProgress, &subprogress, tasks, 1.f + extCount); // arbitrarily subdivided into total progress / tasks
// do the actual work
UpdateChunks(&subprogress);
-
+
}
{
// divide local progress into subprogress
progress_t subprogress;
- __divide_progress(pProgress, &subprogress, 2.f, 1.f); // arbitrarily subdivided into 50% of total progress
+ __divide_progress(pProgress, &subprogress, tasks, 2.f + extCount); // arbitrarily subdivided into total progress / tasks
// do the actual work
pRIFF->Save(Path, &subprogress);
}
@@ -1831,17 +1927,29 @@
* @throws DLS::Exception if any kind of DLS specific error occurred
*/
void File::Save(progress_t* pProgress) {
+ // calculate number of tasks to display progress of
+ float tasks = 2.f + ExtensionFiles.size();
+ int extCount = 0;
+ std::list<RIFF::File*>::iterator it = ExtensionFiles.begin();
+ while (it != ExtensionFiles.end()) {
+ // divide local progress into subprogress
+ progress_t subprogress;
+ __divide_progress(pProgress, &subprogress, tasks, 0.f + extCount); // arbitrarily subdivided into total progress / tasks
+ (*it)->Save(&subprogress);
+ extCount++;
+ it++;
+ }
{
// divide local progress into subprogress
progress_t subprogress;
- __divide_progress(pProgress, &subprogress, 2.f, 0.f); // arbitrarily subdivided into 50% of total progress
+ __divide_progress(pProgress, &subprogress, tasks, 1.f + extCount); // arbitrarily subdivided into total progress / tasks
// do the actual work
UpdateChunks(&subprogress);
}
{
// divide local progress into subprogress
progress_t subprogress;
- __divide_progress(pProgress, &subprogress, 2.f, 1.f); // arbitrarily subdivided into 50% of total progress
+ __divide_progress(pProgress, &subprogress, tasks, 2.f + extCount); // arbitrarily subdivided into total progress / tasks
// do the actual work
pRIFF->Save(&subprogress);
}
@@ -1934,23 +2042,66 @@
*/
void File::__UpdateWavePoolTable() {
WavePoolCount = (pSamples) ? uint32_t(pSamples->size()) : 0;
+ bool bUsingExtensionFiles = ExtensionFiles.size() > 0;
// resize wave pool table arrays
if (pWavePoolTable) delete[] pWavePoolTable;
if (pWavePoolTableHi) delete[] pWavePoolTableHi;
pWavePoolTable = new uint32_t[WavePoolCount];
pWavePoolTableHi = new uint32_t[WavePoolCount];
if (!pSamples) return;
- // update offsets int wave pool table
+ // update offsets into wave pool table
RIFF::List* wvpl = pRIFF->GetSubList(LIST_TYPE_WVPL);
uint64_t wvplFileOffset = wvpl->GetFilePos();
+ // the main gig and the extension files can contain wave data
+ std::vector<RIFF::File*> PoolFiles;
+ PoolFiles.push_back(pRIFF);
+ std::list<RIFF::File*>::iterator extIt = ExtensionFiles.begin();
+ while (extIt != ExtensionFiles.end()) {
+ PoolFiles.push_back(*extIt);
+ extIt++;
+ }
if (b64BitWavePoolOffsets) {
SampleList::iterator iter = pSamples->begin();
SampleList::iterator end = pSamples->end();
+ RIFF::File* pCurExt = NULL;
+ int fileNo;
+ int waveOffset;
for (int i = 0 ; iter != end ; ++iter, i++) {
- uint64_t _64BitOffset = (*iter)->pWaveList->GetFilePos() - wvplFileOffset - LIST_HEADER_SIZE(pRIFF->GetFileOffsetSize());
- (*iter)->ullWavePoolOffset = _64BitOffset;
- pWavePoolTableHi[i] = (uint32_t) (_64BitOffset >> 32);
- pWavePoolTable[i] = (uint32_t) _64BitOffset;
+ if (bUsingExtensionFiles) {
+ RIFF::File* pExtFile = (*iter)->pWaveList->GetFile();
+ // if this sample is located in the same pool file as the last we
+ // reuse the previously computed fileNo and waveOffset
+ if (pExtFile != pCurExt) {
+ // different extension file than the last sample
+ pCurExt = pExtFile;
+
+ std::vector<RIFF::File*>::iterator sIter;
+ sIter = std::find(PoolFiles.begin(), PoolFiles.end(), pCurExt);
+ if (sIter != PoolFiles.end()) {
+ fileNo = std::distance(PoolFiles.begin(), sIter);
+ }
+ else {
+ throw DLS::Exception("Fatal error, unknown pool file");
+ }
+
+ RIFF::List* extWvpl = pCurExt->GetSubList(LIST_TYPE_WVPL);
+ if (!extWvpl) {
+ throw DLS::Exception("Fatal error, pool file has no 'wvpl' list chunk");
+ }
+ waveOffset = extWvpl->GetFilePos() + LIST_HEADER_SIZE(pCurExt->GetFileOffsetSize());
+ }
+ uint64_t _64BitOffset = (*iter)->pWaveList->GetFilePos() - waveOffset;
+ // pWavePoolTableHi stores file number when extension files are in use
+ pWavePoolTableHi[i] = (uint32_t) fileNo;
+ pWavePoolTable[i] = (uint32_t) _64BitOffset;
+ (*iter)->ullWavePoolOffset = _64BitOffset;
+ }
+ else {
+ uint64_t _64BitOffset = (*iter)->pWaveList->GetFilePos() - wvplFileOffset - LIST_HEADER_SIZE(pRIFF->GetFileOffsetSize());
+ (*iter)->ullWavePoolOffset = _64BitOffset;
+ pWavePoolTableHi[i] = (uint32_t) (_64BitOffset >> 32);
+ pWavePoolTable[i] = (uint32_t) _64BitOffset;
+ }
}
} else { // conventional 32 bit offsets
SampleList::iterator iter = pSamples->begin();
@@ -1964,7 +2115,6 @@
}
-
// *************** Exception ***************
// *
diff -Naur libgig_original/src/DLS.h libgig_modified/src/DLS.h
--- libgig_original/src/DLS.h 2018-12-17 19:34:26.685343352 +0000
+++ libgig_modified/src/DLS.h 2019-01-12 20:09:26.244703000 +0000
@@ -60,6 +60,8 @@
# define CHUNK_ID_COLH 0x636F6C68
# define CHUNK_ID_ARTL 0x6172746C
# define CHUNK_ID_ART2 0x61727432
+# define CHUNK_ID_XFIL 0x7866696C
+# define CHUNK_ID_DOXF 0x646F7866
#else // little endian
# define RIFF_TYPE_DLS 0x20534C44
# define LIST_TYPE_WVPL 0x6C707677
@@ -94,6 +96,8 @@
# define CHUNK_ID_COLH 0x686C6F63
# define CHUNK_ID_ARTL 0x6C747261
# define CHUNK_ID_ART2 0x32747261
+# define CHUNK_ID_XFIL 0x6C696678
+# define CHUNK_ID_DOXF 0x66786F64
#endif // WORDS_BIGENDIAN
#define DLS_WAVE_FORMAT_PCM 0x0001
diff -Naur libgig_original/src/gig.cpp libgig_modified/src/gig.cpp
--- libgig_original/src/gig.cpp 2019-01-03 23:43:15.498002977 +0000
+++ libgig_modified/src/gig.cpp 2019-02-03 21:27:59.903334000 +0000
@@ -31,6 +31,8 @@
#include <iostream>
#include <assert.h>
+#include "DLS.h"
+
/// libgig's current file format version (for extending the original Giga file
/// format with libgig's own custom data / custom features).
#define GIG_FILE_EXT_VERSION 2
@@ -5702,20 +5704,84 @@
int iSampleIndex = 0;
int iTotalSamples = WavePoolCount;
- // check if samples should be loaded from extension files
- // (only for old gig files < 2 GB)
- int lastFileNo = 0;
- if (!file->IsNew() && !(file->GetCurrentFileSize() >> 31)) {
- for (int i = 0 ; i < WavePoolCount ; i++) {
- if (pWavePoolTableHi[i] > lastFileNo) lastFileNo = pWavePoolTableHi[i];
+ std::string path = pRIFF->GetFileName();
+ std::string folder = "";
+ std::string newfile = path.substr(0, path.length() - 4);
+ size_t pos = path.find_last_of(GetPathSeparator(path));
+ if (pos != std::string::npos) {
+ folder = path.substr(0, pos + 1);
+ newfile = path.substr(pos+1, path.length() - pos - 5);
+ }
+
+ // the main gig and the extension files can contain wave data
+ std::vector<RIFF::File*> PoolFiles;
+ PoolFiles.push_back(pRIFF);
+
+ // get info about extension files
+ RIFF::Chunk* ckXfil = pRIFF->GetSubChunk(CHUNK_ID_XFIL);
+ if (ckXfil) {
+ uint32_t numRecords = ckXfil->ReadInt32();
+ for (int i = 0; i < numRecords; i++) {
+ // read the filename and load the extension file
+ std::string name;
+ ckXfil->ReadString(name, 128);
+ std::string newpath = folder + name;
+ RIFF::File* pExtFile = new RIFF::File(newpath);
+ // check that the dlsids match
+ RIFF::Chunk* ckDLSID = pExtFile->GetSubChunk(CHUNK_ID_DLID);
+ if (ckDLSID) {
+ ::DLS::dlsid_t idExpected;
+ idExpected.ulData1 = ckXfil->ReadInt32();
+ idExpected.usData2 = ckXfil->ReadInt16();
+ idExpected.usData3 = ckXfil->ReadInt16();
+ ckXfil->Read(idExpected.abData, 8, 1);
+ ::DLS::dlsid_t idFound;
+ ckDLSID->Read(&idFound.ulData1, 1, 4);
+ ckDLSID->Read(&idFound.usData2, 1, 2);
+ ckDLSID->Read(&idFound.usData3, 1, 2);
+ ckDLSID->Read(idFound.abData, 8, 1);
+ if (memcmp(&idExpected, &idFound, 16) != 0) {
+ throw gig::Exception("dlsid mismatch for extension file: %s", name.c_str());
+ }
+ }
+ PoolFiles.push_back(pExtFile);
+ ExtensionFiles.push_back(pExtFile);
+ }
+ }
+
+ // check if a .gx99 extension file exists
+ RIFF::Chunk* ckDoxf = pRIFF->GetSubChunk(CHUNK_ID_DOXF);
+ if (ckDoxf) {
+ std::string name = newfile + ".gx99";
+ std::string newpath = folder + name;
+ RIFF::File* pExtFile = new RIFF::File(newpath);
+
+ // skip unused int and filename
+ ckDoxf->SetPos(132, RIFF::stream_whence_t::stream_curpos);
+
+ // check that the dlsids match
+ RIFF::Chunk* ckDLSID = pExtFile->GetSubChunk(CHUNK_ID_DLID);
+ if (ckDLSID) {
+ ::DLS::dlsid_t idExpected;
+ idExpected.ulData1 = ckDoxf->ReadInt32();
+ idExpected.usData2 = ckDoxf->ReadInt16();
+ idExpected.usData3 = ckDoxf->ReadInt16();
+ ckDoxf->Read(idExpected.abData, 8, 1);
+ ::DLS::dlsid_t idFound;
+ ckDLSID->Read(&idFound.ulData1, 1, 4);
+ ckDLSID->Read(&idFound.usData2, 1, 2);
+ ckDLSID->Read(&idFound.usData3, 1, 2);
+ ckDLSID->Read(idFound.abData, 8, 1);
+ if (memcmp(&idExpected, &idFound, 16) != 0) {
+ throw gig::Exception("dlsid mismatch for extension file: %s", name.c_str());
+ }
}
+ PoolFiles.push_back(pExtFile);
+ ExtensionFiles.push_back(pExtFile);
}
- String name(pRIFF->GetFileName());
- int nameLen = (int) name.length();
- char suffix[6];
- if (nameLen > 4 && name.substr(nameLen - 4) == ".gig") nameLen -= 4;
- for (int fileNo = 0 ; ; ) {
+ for (int i = 0; i < PoolFiles.size(); i++) {
+ RIFF::File* file = PoolFiles[i];
RIFF::List* wvpl = file->GetSubList(LIST_TYPE_WVPL);
if (wvpl) {
file_offset_t wvplFileOffset = wvpl->GetFilePos();
@@ -5727,22 +5793,13 @@
__notify_progress(pProgress, subprogress);
file_offset_t waveFileOffset = wave->GetFilePos();
- pSamples->push_back(new Sample(this, wave, waveFileOffset - wvplFileOffset, fileNo, iSampleIndex));
+ pSamples->push_back(new Sample(this, wave, waveFileOffset - wvplFileOffset, i, iSampleIndex));
iSampleIndex++;
}
wave = wvpl->GetNextSubList();
}
-
- if (fileNo == lastFileNo) break;
-
- // open extension file (*.gx01, *.gx02, ...)
- fileNo++;
- sprintf(suffix, ".gx%02d", fileNo);
- name.replace(nameLen, 5, suffix);
- file = new RIFF::File(name);
- ExtensionFiles.push_back(file);
- } else break;
+ }
}
__notify_progress(pProgress, 1.0); // notify done
diff -Naur libgig_original/src/helper.h libgig_modified/src/helper.h
--- libgig_original/src/helper.h 2018-12-17 19:34:26.689343370 +0000
+++ libgig_modified/src/helper.h 2019-01-25 23:11:07.535635000 +0000
@@ -274,4 +274,18 @@
}
}
+// get path separator for a given path
+inline char GetPathSeparator(std::string path) {
+#ifdef _WIN32
+ std::size_t pos = path.find_first_of("\\/");
+ if (pos != std::string::npos) {
+ return path[pos];
+ }
+ return '\\';
+#else
+ // Unix-like
+ return '/';
+#endif
+}
+
#endif // __LIBGIG_HELPER_H__
_______________________________________________
Linuxsampler-devel mailing list
Linuxsampler-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxsampler-devel