#include #include "bf_import.h" #include "main.h" #include "midi.h" //For eof_apply_ts() #include "undo.h" #ifdef USEMEMWATCH #include "memwatch.h" #endif //Define this value to > 1 in order to lessen the amount of logging written to file #define BF_IMPORT_LOGGING_LEVEL 1 void pack_ReadDWORDBE(PACKFILE *inf, void *data) { unsigned char buffer[4] = {0}; unsigned long *ptr = data; if(inf) { if((pack_fread(buffer, 4, inf) == 4) && data) { //If the data was successfully read, and data isn't NULL, store the value *ptr=((unsigned long)buffer[0]<<24) | ((unsigned long)buffer[1]<<16) | ((unsigned long)buffer[2]<<8) | ((unsigned long)buffer[3]); //Convert to 4 byte integer } } } void pack_ReadQWORDBE(PACKFILE *inf, void *data) { unsigned char buffer[8] = {0}; unsigned long long *ptr = data; if(inf) { if((pack_fread(buffer, 8, inf) == 8) && data) { //If the data was successfully read, and data isn't NULL, store the value *ptr=((unsigned long long)buffer[0]<<56) | ((unsigned long long)buffer[1]<<48) | ((unsigned long long)buffer[2]<<40) | ((unsigned long long)buffer[3]<<32) | ((unsigned long long)buffer[4]<<24) | ((unsigned long long)buffer[5]<<16) | ((unsigned long long)buffer[6]<<8) | ((unsigned long long)buffer[7]); //Convert to 8 byte integer } } } int eof_bf_qsort_time_changes(const void * e1, const void * e2) { const struct bf_timing_change * thing1 = (struct bf_timing_change *)e1; const struct bf_timing_change * thing2 = (struct bf_timing_change *)e2; //Sort by timestamp if(thing1->realtimems < thing2->realtimems) { return -1; } else if(thing1->realtimems > thing2->realtimems) { return 1; } //Sort by item type (time signature changes before tempo changes) if(!thing1->type) { //If the first item is a time signature return -1; } if(!thing2->type) { //If the second item is a time signature return 1; } //They are equal return 0; } char *eof_lookup_bf_string_key(struct bf_string *ptr, unsigned long arraysize, unsigned long long key) { unsigned long ctr; char *result = NULL; if(!ptr) return NULL; //Invalid parameter for(ctr = 0; ctr < arraysize; ctr++) { //For each string in the array if(ptr[ctr].indkey == key) { //If the string has the target key result = ptr[ctr].string; break; } } return result; } int eof_dword_to_binary_string(unsigned long dword, char *buffer) { unsigned long index, ctr, bitmask; if(!buffer) return -1; //Return error for(ctr = 0, index = 0, bitmask = 0x80000000; ctr < 32; ctr++, bitmask >>= 1) { //For each bit in the 32 bit value, from most significant to least significant if(dword & bitmask) { //If this bit is set buffer[index++] = '1'; //Append a 1 to the string } else if(index) { //If the bit isn't set, but a previous bit was buffer[index++] = '0'; //Append a 0 to the string } } if(!index) { //If no bits in dword were set buffer[index++] = '0'; //Append a 0 to the string } buffer[index] = '\0'; //Terminate the string return 0; //Return success } EOF_SONG *eof_load_bf(char * fn) { EOF_SONG * sp = NULL; char header[5] = {0}; //Used to read header #define BF_IMPORT_BUFFER_SIZE 2048 char buffer[BF_IMPORT_BUFFER_SIZE + 1] = {0}; //Used to read strings char *lang, lang_english[] = "English", lang_japanese[] = "Japanese", lang_german[] = "German", lang_italian[] = "Italian", lang_spanish[] = "Spanish", lang_french[] = "French", *string; unsigned long sectionctr, ctr, ctr2, dword = 0, dword2 = 0, dword3 = 0, dword4, numstbentries = 0, offset; unsigned long fileadd = 0; //Use this to track the address within the input file, since Allegro's file I/O routines don't offer a way to determine this unsigned long long qword, qword2, qword3; int word; PACKFILE *inf = NULL; struct bf_section *sectiondata = NULL; //Will be used to store details of each section unsigned long numsections = 0; //The number of sections stored in the above array #define BF_IMPORT_STRING_NUM 1000 struct bf_string *stringdata = NULL; //Will be used to store strings and their key identifiers unsigned long numstrings = 0; //The number of strings stored in the above array char parent; //Tracks whether a parsed ZOBJ section is a parent or child object char nfn[1024] = {0}; #define BF_IMPORT_TIME_CHANGE_NUM 1000 struct bf_timing_change changes[BF_IMPORT_TIME_CHANGE_NUM] = {{0,0.0,0,0.0,0,0.0,0,0}}; //Used to store all time signature and tempo changes unsigned long numchanges = 0; //The number of changes stored in the above array unsigned curnum, curden, lastnum, lastden; //Used to build the tempo map double curbeatlen, curtime; //Used to build the tempo map unsigned long curtimems; //Used to build the tempo map unsigned long lastitem = 0; //Used to track the realtime position of the last item in the chart, used to build the tempo map unsigned long lyrstart, temp; //Used to correct lyric line positions unsigned effective_min_note_distance; eof_log("\tImporting Bandfuse file", 1); eof_log("eof_load_bf() entered", 1); if(eof_min_note_distance_intervals) { //Per beat/measure min. note distance isn't supported when a chart isn't already loaded effective_min_note_distance = 0; } else { effective_min_note_distance = eof_min_note_distance; } //Initialize pointers and handles if(!fn) { return NULL; } inf = pack_fopen(fn, "rb"); //Open file in binary mode if(!inf) { (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tError loading: Cannot open input xml file: \"%s\"", strerror(errno)); //Get the Operating System's reason for the failure eof_log(eof_log_string, 1); return NULL; } sp = eof_create_song_populated(); if(!sp) { (void) pack_fclose(inf); return NULL; } //Parse RIFF header pack_ReadDWORDBE(inf, header); //Read the header string and reverse the byte order pack_ReadDWORDBE(inf, &dword); //Read the RIFF chunk size fileadd += 8; //Update file address if((strcmp(header,"RIFF") != 0) || !dword) { //Doesn't match expected header or a valid chunk size wasn't read (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tInvalid RIFF header \"%s\" (size %lu). Aborting.", header, dword); eof_log(eof_log_string, 1); (void) pack_fclose(inf); eof_destroy_song(sp); return NULL; } (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tRIFF chunk size = %lu bytes.", dword); eof_log(eof_log_string, 1); //Parse INDX header dword = 0; pack_ReadDWORDBE(inf, header); //Read the header string and reverse the byte order pack_ReadDWORDBE(inf, &dword); //Read the INDX chunk size pack_ReadDWORDBE(inf, &numsections); //Read the number of sections pack_ReadDWORDBE(inf, &dword2); //Read the value expected to be 0 fileadd += 16; //Update file address if((strcmp(header,"INDX") != 0) || !dword || !numsections || (dword2 != 4)) { //Doesn't match expected header or a valid values weren't read (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tInvalid INDX header \"%s\" (size = %lu, # of sections = %lu, constant value = %lu). Aborting.", header, dword, numsections, dword2); eof_log(eof_log_string, 1); (void) pack_fclose(inf); eof_destroy_song(sp); return NULL; } (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tINDX chunk size = %lu bytes, # of sections = %lu.", dword, numsections); eof_log(eof_log_string, 1); //Build an array to store the section information into sectiondata = malloc(sizeof(struct bf_section) * numsections); //Allocate the memory if(!sectiondata) { //If the memory was not allocated eof_log("\tError allocating memory for sections. Aborting", 1); (void) pack_fclose(inf); eof_destroy_song(sp); return NULL; } memset(sectiondata, 0, sizeof(struct bf_section) * numsections); //Set the memory to all 0s //Build an array to store the strings into stringdata = malloc(sizeof(struct bf_string) * BF_IMPORT_STRING_NUM); //Allocate the memory if(!stringdata) { //If the memory was not allocated eof_log("\tError allocating memory for strings. Aborting", 1); (void) pack_fclose(inf); free(sectiondata); eof_destroy_song(sp); return NULL; } memset(stringdata, 0, sizeof(struct bf_string) * BF_IMPORT_STRING_NUM); //Set the memory to all 0s //Parse section information for(sectionctr = 0; sectionctr < numsections; sectionctr++) { //For each section in the INDX header qword = dword = 0; pack_ReadQWORDBE(inf, &qword); //Read the section's index key identifier sectiondata[sectionctr].indkey = qword; pack_ReadDWORDBE(inf, &dword); //Read the section's file address sectiondata[sectionctr].address = dword; pack_ReadDWORDBE(inf, NULL); //Read and ignore 4 bytes of padding if(!qword || !dword) { //If valid values weren't read #ifdef ALLEGRO_WINDOWS (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tUnable to read INDX section #%lu (Index key ID = 0x%016I64x, address = 0x%lX). Aborting.", sectionctr, qword, dword); #else (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tUnable to read INDX section #%lu (Index key ID = 0x%016llux, address = 0x%lX). Aborting.", sectionctr, qword, dword); #endif eof_log(eof_log_string, 1); (void) pack_fclose(inf); free(sectiondata); free(stringdata); eof_destroy_song(sp); return NULL; } fileadd += 16; //Update file address #ifdef ALLEGRO_WINDOWS (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tSection #%lu:\tIndex key = 0x%016I64x, address = 0x%lX", sectionctr, qword, dword); #else (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tSection #%lu:\tIndex key = 0x%016llux, address = 0x%lX", sectionctr, qword, dword); #endif eof_log(eof_log_string, BF_IMPORT_LOGGING_LEVEL); } //Parse sections for(sectionctr = 0; sectionctr < numsections; sectionctr++) { //For each section defined in the INDX header dword = dword2 = dword3 = dword4 = qword = qword2 = qword3 = 0; pack_ReadDWORDBE(inf, header); //Read the header string and reverse the byte order if(!strcmp(header,"STbl")) { //If this is an STbl header fileadd += 4; //Update file address pack_ReadDWORDBE(inf, &dword); //Read the STbl chunk size pack_ReadQWORDBE(inf, &qword); //Read the index key pack_ReadQWORDBE(inf, &qword2); //Read the folder key pack_ReadQWORDBE(inf, &qword3); //Read the language key pack_ReadQWORDBE(inf, NULL); //Read and ignore 8 bytes of padding pack_ReadDWORDBE(inf, &numstbentries); //Read the number of string entries pack_ReadDWORDBE(inf, &dword2); //Read the value expected to be 12 pack_ReadDWORDBE(inf, &dword3); //Read the string table size pack_ReadDWORDBE(inf, &dword4); //Read the number of bytes until the beginning of the string table (including this dword, so this value will never be zero) fileadd += 52; //Update file address if(!dword || !qword || !qword2 || !qword3 || !numstbentries || (dword2 != 12) || !dword3 || !dword4) { //If valid values weren't read #ifdef ALLEGRO_WINDOWS (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tInvalid STbl header \"%s\" (size = %lu, index key = 0x%016I64x, folder key = 0x%016I64x, language key = 0x%016I64x, number of entries = %lu, table size = %lu, bytes until table = %lu. Aborting.", header, dword, qword, qword2, qword3, numstbentries, dword3, dword4); #else (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tInvalid STbl header \"%s\" (size = %lu, index key = 0x%016llux, folder key = 0x%016llux, language key = 0x%016llux, number of entries = %lu, table size = %lu, bytes until table = %lu. Aborting.", header, dword, qword, qword2, qword3, numstbentries, dword3, dword4); #endif eof_log(eof_log_string, 1); (void) pack_fclose(inf); free(sectiondata); free(stringdata); eof_destroy_song(sp); return NULL; } switch(qword3) { case 0x4B1B8838F44C207C: lang = lang_english; break; case 0xBBC4BF723FCD64B4: lang = lang_japanese; break; case 0x750CDF9C21C7F0A0: lang = lang_german; break; case 0x3A07E146FF1CCA40: lang = lang_italian; break; case 0xE612EF05D38C0E52: lang = lang_spanish; break; case 0x58A752C7A10CF8A0: lang = lang_french; break; default: lang = "Unknown"; break; } #ifdef ALLEGRO_WINDOWS (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tSection #%lu (STbl) chunk size = %lu, index key = 0x%016I64x, folder key = 0x%016I64x, language key = 0x%016I64x (%s), number of entries = %lu, table size = %lu, bytes until table = %lu", sectionctr, dword, qword, qword2, qword3, lang, numstbentries, dword3, dword4); #else (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tSection #%lu (STbl) chunk size = %lu, index key = 0x%016llux, folder key = 0x%016llux, language key = 0x%016llux (%s), number of entries = %lu, table size = %lu, bytes until table = %lu", sectionctr, dword, qword, qword2, qword3, lang, numstbentries, dword3, dword4); #endif eof_log(eof_log_string, 1); //Parse STbl entries for(ctr = 0; ctr < numstbentries; ctr++) { //For each entry in the string table char duplicate = 0; pack_ReadQWORDBE(inf, &qword); //Read the entry key (is allowed to be all 0s for an empty entry) pack_ReadDWORDBE(inf, &dword); //Read the offset (from the start of the string table) to this entry's string (is allowed to be all 0s for an empty entry) pack_ReadDWORDBE(inf, NULL); //Read and ignore 4 bytes of padding fileadd += 16; //Update file address #ifdef ALLEGRO_WINDOWS (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tEntry #%lu:\tIndex key = 0x%016I64x, offset from string table start = %lu", ctr, qword, dword); #else (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tEntry #%lu:\tIndex key = 0x%016llux, offset from string table start = %lu", ctr, qword, dword); #endif eof_log(eof_log_string, 2); if(lang != lang_english) continue; //If this is not an English language string table, skip it if(numstrings >= BF_IMPORT_STRING_NUM) continue; //If the string table is not large enough to add this to the string array, skip it for(ctr2 = 0; ctr2 < numstrings; ctr2++) { //For each existing entry in the string array if(stringdata[ctr2].indkey == qword) { //If the key already exists in the list duplicate = 1; break; } } if(!duplicate) { //If an array entry with this key didn't already exist, add it stringdata[numstrings].indkey = qword; stringdata[numstrings].offset = dword; numstrings++; } } //Parse strings offset = 0; //Use this to track the current byte offset into the string table for(ctr = 0; ctr < numstbentries; ctr++) { //For each entry in the string table dword = 0; //Use this to count the string's size in bytes do{ word = pack_getc(inf); //Read the next byte fileadd++; //Update file address if(word == EOF) { //If the byte couldn't be read eof_log("\t\tError reading string. Aborting", 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } if(dword >= BF_IMPORT_BUFFER_SIZE) { //If the string is too large for the buffer eof_log("\t\tString too large to store. Aborting", 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } buffer[dword] = word; //Store the byte into the buffer dword++; //Track the string's size }while(word != '\0'); //Read all bytes into the buffer until a NULL terminator is reached #ifdef ALLEGRO_WINDOWS (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tString #%lu:\tIndex key = 0x%016I64x, string table offset = %lu,\t\"%s\"", ctr, stringdata[ctr].indkey, offset, buffer); #else (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tString #%lu:\tIndex key = 0x%016llux, string table offset = %lu,\t\"%s\"", ctr, stringdata[ctr].indkey, offset, buffer); #endif eof_log(eof_log_string, 1); if(lang == lang_english) { //If this is an English language string table for(ctr2 = 0; ctr2 < numstrings; ctr2++) { //For each entry in the string array int size; if(stringdata[ctr2].offset != offset) continue; //If the string just read is not at an offset at which a string is expected, skip this string entry if(stringdata[ctr2].string != NULL) continue; //If a string was already read for this offset, skip it size = ustrsize(buffer) + 1; //Get the length of the string in bytes (UTF-8 aware size) and add 1 byte for NULL stringdata[ctr2].string = malloc((size_t)size); //Allocate memory for the string if(!stringdata[ctr2].string) { //If the memory couldn't be allocated eof_log("\t\tError storing string into array. Aborting", 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } (void) ustrzcpy(stringdata[ctr2].string, size, buffer); } } offset += dword; //Advance the offset byte counter by the size of the string (including NULL terminator) } //STbl sections have a variable amount of padding, just skip the remaining bytes until the next section begins if(sectionctr + 1 < numsections) { //If there's another section while(fileadd < sectiondata[sectionctr + 1].address) { //Until the next section is reached word = pack_getc(inf); //Read the next byte fileadd++; //Update file address if(word == EOF) { //If the byte couldn't be read eof_log("\t\tError reading padding. Aborting", 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } } } }//If this is an STbl header else if(!strcmp(header,"ZOBJ")) { //If this is a ZOBJ header unsigned long zobjsize = 0; fileadd += 4; //Update file address dword = qword = qword2 = qword3 = 0; pack_ReadDWORDBE(inf, &zobjsize); //Read the ZOBJ chunk size (expected to contain a bare minimum of the four qwords below) pack_ReadQWORDBE(inf, &qword); //Read the index key pack_ReadQWORDBE(inf, &qword2); //Read the object key pack_ReadQWORDBE(inf, &qword3); //Read the type string key pack_ReadQWORDBE(inf, NULL); //Read and ignore 8 bytes of padding fileadd += 36; //Update file address if((zobjsize < 32) || !qword || !qword2 || !qword3) { //If valid values weren't read #ifdef ALLEGRO_WINDOWS (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tUnable to read ZOBJ section (index key = 0x%016I64x, object key = 0x%016I64x, type string key = 0x%016I64x. Aborting.", qword, qword2, qword3); #else (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tUnable to read ZOBJ section (index key = 0x%016llux, object key = 0x%016llux, type string key = 0x%016llux. Aborting.", qword, qword2, qword3); #endif eof_log(eof_log_string, 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } //Identify whether this is a parent or child ZOBJ section parent = 0; for(ctr = 0; ctr < sectionctr; ctr++) { //For each section in the array BEFORE the one being parsed if(sectiondata[ctr].objkey == qword2) { //If the section has the same object key as this ZOBJ if(!sectiondata[ctr].encountered) { //If this is the first the the key was referenced in the ZOBJ sections parent = 1; //It is a parent object sectiondata[ctr].encountered = 1; //Track that this key was referenced already (all future references to it are for child objects) } break; } } sectiondata[ctr].objkey = qword2; //Store the object key in the array so it's associated with this ZOBJ section string = eof_lookup_bf_string_key(stringdata, numstrings, qword); //Identify the string name of this ZOBJ section #ifdef ALLEGRO_WINDOWS (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tSection #%lu (ZOBJ) chunk size = %lu, index key = 0x%016I64x (\"%s\", %s), object key = 0x%016I64x, type string key = 0x%016I64x", sectionctr, zobjsize, qword, string ? string : "[UNKNOWN KEY]", (parent ? "Parent" : "Child"), qword2, qword3); #else (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tSection #%lu (ZOBJ) chunk size = %lu, index key = 0x%016llux (\"%s\", %s), object key = 0x%016llux, type string key = 0x%016llux", sectionctr, zobjsize, qword, string ? string : "[UNKNOWN KEY]", (parent ? "Parent" : "Child"), qword2, qword3); #endif eof_log(eof_log_string, 1); //Parse the ZOBJ section if(zobjsize < 64) { //If this object isn't large enough to be a section we're interested in string = NULL; //Don't bother parsing it } if(string) { //If the name of the string was identified char *ptr; unsigned long entryid = 0, entrysize = 0, entrycount = 0, startms, endms; float start = 0.0, end = 0.0, tempo = 0.0; pack_ReadDWORDBE(inf, &entryid); //Read entry ID pack_ReadDWORDBE(inf, &entrysize); //Read entry size pack_ReadDWORDBE(inf, &entrycount); //Read entry count pack_ReadDWORDBE(inf, &dword); //Read the value expected to be 4 fileadd += 16; //Update file address zobjsize -= 16; //Take into account the 16 bytes read from this object (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tEntry ID: %lu\tEntry size = %lu\tEntry count = %lu", entryid, entrysize, entrycount); eof_log(eof_log_string, 1); if((entryid == 0) && (entrysize == 16)) { //Time signature object for(ctr = 0; ctr < entrycount; ctr++) { //For each tempo in this object pack_ReadDWORDBE(inf, &start); //Read offset 1 pack_ReadDWORDBE(inf, &end); //Read offset 2 pack_ReadDWORDBE(inf, &dword); //Read numerator pack_ReadDWORDBE(inf, &dword2); //Read denominator fileadd += 16; //Update file address if(numchanges < BF_IMPORT_TIME_CHANGE_NUM) { //If this time signature change can be stored changes[numchanges].type = 0; //Denote this entry is a time signature change changes[numchanges].realtime = start; changes[numchanges].realtimems = start + 0.5; changes[numchanges].TS_num = dword; changes[numchanges].TS_den = dword2; numchanges++; } if(start + 0.5 > lastitem) //Keep track of the timestamp of the last imported item lastitem = start + 0.5; (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tTime signature change #%2lu offset 1 = %f, offset 2 = %f, num = %lu, den = %lu", ctr, start, end, dword, dword2); eof_log(eof_log_string, 1); } } else if((entryid == 1) && (entrysize == 12)) { //Tempo object for(ctr = 0; ctr < entrycount; ctr++) { //For each tempo in this object pack_ReadDWORDBE(inf, &start); //Read tempo start time pack_ReadDWORDBE(inf, &end); //Read tempo end time pack_ReadDWORDBE(inf, &tempo); //Read tempo fileadd += 12; //Update file address if(numchanges < BF_IMPORT_TIME_CHANGE_NUM) { //If this tempo change can be stored changes[numchanges].type = 1; //Denote this entry is a tempo change changes[numchanges].realtime = start; changes[numchanges].realtimems = start + 0.5; changes[numchanges].realtime2 = end; changes[numchanges].realtime2ms = end + 0.5; changes[numchanges].BPM = tempo; numchanges++; } if(start + 0.5 > lastitem) //Keep track of the timestamp of the last imported item lastitem = start + 0.5; (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tTempo change #%2lu start = %13fms, end = %13fms, tempo = %fBPM", ctr, start, end, tempo); eof_log(eof_log_string, 1); } } else if((entryid == 6) && (entrysize == 24)) { //Chord object for(ctr = 0; ctr < entrycount; ctr++) { //For each chord name in this object pack_ReadDWORDBE(inf, &start); //Read chord start time pack_ReadDWORDBE(inf, &end); //Read chord end time pack_ReadQWORDBE(inf, &qword); //Read chord name string index key pack_ReadQWORDBE(inf, NULL); //Read and ignore 8 bytes of padding fileadd += 24; //Update file address startms = start + 0.5; //Round to nearest millisecond endms = end + 0.5; //Round to nearest millisecond ptr = eof_lookup_bf_string_key(stringdata, numstrings, qword); //Identify the string for this chord name if(ptr) { //If the name was found (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tChord #%lu start = %lums, end = %lums, name = \"%s\"", ctr, startms, endms, ptr); eof_log(eof_log_string, 1); } } } else if((entryid == 8) && (entrysize == 32)) { //Vox object EOF_LYRIC *lp; char *type; for(ctr = 0; ctr < entrycount; ctr++) { //For each lyric in this object pack_ReadDWORDBE(inf, &start); //Read lyric start time pack_ReadDWORDBE(inf, &end); //Read lyric end time pack_ReadDWORDBE(inf, &dword); //Read lyric pitch pack_ReadDWORDBE(inf, NULL); //Read and ignore 4 bytes of padding pack_ReadQWORDBE(inf, &qword); //Read lyric string index key pack_ReadDWORDBE(inf, &dword2); //Read lyric type (1 = normal, 2 = unpitched, 3 = extended (ie. pitch shift)) pack_ReadDWORDBE(inf, NULL); //Read and ignore 4 bytes of padding fileadd += 32; //Update file address startms = start + 0.5; //Round to nearest millisecond endms = end + 0.5; //Round to nearest millisecond ptr = eof_lookup_bf_string_key(stringdata, numstrings, qword); //Identify the string for this lyric if(!ptr) continue; //If the name was not found, skip this lyric if(dword2 == 1) { type = "normal"; } else if(dword2 == 2) { type = "unpitched"; } else { type = "extended"; } (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tLyric #%lu start = %lums, end = %lums, name = \"%s\", type = %lu (%s)", ctr, startms, endms, ptr, dword2, type); eof_log(eof_log_string, 1); lp = eof_vocal_track_add_lyric(sp->vocal_track[0]); //Create a lyric structure if(!lp) { //If the lyric couldn't be added eof_log("\t\tUnable to add lyric. Aborting.", 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } (void) ustrncpy(lp->text, ptr, EOF_MAX_LYRIC_LENGTH); //Copy the lyric text into the lyric structure lp->pos = startms; lp->length = endms - startms + 0.5; //Round to nearest millisecond if(dword2 == 2) { //If this lyric is pitchless lp->note = 0; //Explicitly set it as such } else { lp->note = (dword & 0xFF00) >> 8; //Otherwise set the defined pitch } } } else if((entryid == 10) && (entrysize == 8)) { //voxpushphrase object for(ctr = 0; ctr < entrycount; ctr++) { //For each lyric line in this object pack_ReadDWORDBE(inf, &start); //Read lyric line start time pack_ReadDWORDBE(inf, &end); //Read lyric line end time fileadd += 8; //Update file address startms = start + 0.5; //Round to nearest millisecond endms = end + 0.5; //Round to nearest millisecond (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tLyric line #%lu start = %lums, end = %lums", ctr, startms, endms); eof_log(eof_log_string, 1); (void) eof_vocal_track_add_line(sp->vocal_track[0], startms, endms, 0xFF); //Add the lyric line } } else if((entryid == 11) && (entrysize == 64)) { //Tab object float amount = 0.0; EOF_PRO_GUITAR_TRACK *tp = NULL; //A pointer to appropriate destination pro guitar track referenced by this ZOBJ object EOF_PRO_GUITAR_NOTE *np = NULL; //A pointer to the most recently created note EOF_PRO_GUITAR_NOTE *npp = NULL; //A pointer to the previously created note unsigned char curdiff; //The difficulty referenced by this ZOBJ object unsigned long flags, bendtype = 0 , vibrato = 0 , stringnum = 0 , fret = 0 , finger = 0 , statuses = 0 , statuses2 = 0 , statuses3 = 0 ; char *tech; unsigned long prevfret = 0; //Used to track the fret value of the previous note, for HO/PO tracking float prevamount = 0.0; //Used to track the slide/bend/trill amount of the previous note, for pre-bend tracking unsigned long prevbendtype = 0; //Determine the track difficulty this ZOBJ section refers to if(strstr(string, "bss_")) { //If this is one of the bass arrangements tp = sp->pro_guitar_track[sp->track[EOF_TRACK_PRO_BASS]->tracknum]; tp->parent->numdiffs = 5; } else { //Otherwise assume guitar arrangement tp = sp->pro_guitar_track[sp->track[EOF_TRACK_PRO_GUITAR]->tracknum]; tp->parent->numdiffs = 6; } tp->parent->flags |= EOF_TRACK_FLAG_UNLIMITED_DIFFS; //Remove the difficulty limit for this track if(strstr(string, "bss_adv")) { //This is level 5 difficulty for bass curdiff = 4; } else if(strstr(string, "_jam")) { curdiff = 0; } else if(strstr(string, "_nov")) { curdiff = 1; } else if(strstr(string, "_beg")) { curdiff = 2; } else if(strstr(string, "_int")) { curdiff = 3; } else if(strstr(string, "_rhy")) { //This is level 5 difficulty for guitar (rhythm) curdiff = 4; } else if(strstr(string, "_adv")) { //This is level 5 difficulty for guitar (lead) curdiff = 5; } else { curdiff = 6; //Unknown tp->parent->numdiffs = 7; } for(ctr = 0; ctr < entrycount; ctr++) { //For each note in this object //Read note data pack_ReadDWORDBE(inf, &start); //Read note start time pack_ReadDWORDBE(inf, &end); //Read note end time pack_ReadDWORDBE(inf, &stringnum); //Read note string number pack_ReadDWORDBE(inf, &fret); //Read note fret number pack_ReadDWORDBE(inf, &finger); //Read note finger number pack_ReadDWORDBE(inf, &statuses); //Read note statuses pack_ReadDWORDBE(inf, &bendtype); //Read other statuses (least significant byte is bend type) pack_ReadDWORDBE(inf, &amount); //Read the slide/trill/bend amount (in steps) pack_ReadDWORDBE(inf, &vibrato); //Read other statuses (least significant bit indicates vibrato) pack_ReadDWORDBE(inf, NULL); //Read and ignore 4 bytes of unknown use pack_ReadQWORDBE(inf, NULL); //Read the value expected to be 0x427c0000427c0000 pack_ReadDWORDBE(inf, NULL); //Read and ignore 4 bytes of unknown use pack_ReadDWORDBE(inf, NULL); //Read and ignore 4 bytes of unknown use pack_ReadDWORDBE(inf, &statuses2); //Read other statuses (byte 0 indicates linknext, byte 1 indicates palm mute, byte 2 indicates tremolo) pack_ReadDWORDBE(inf, &statuses3); //Read pop/slap status (1 indicates slap, 2 indicates pop) fileadd += 64; //Update file address startms = start + 0.5; //Round to nearest millisecond endms = end + 0.5; //Round to nearest millisecond if(endms > lastitem) //Keep track of the timestamp of the last imported item lastitem = endms; if(statuses == 1) { tech = "Mute"; } else if(statuses == 2) { tech = "Slide"; } else if(statuses == 3) { tech = "UUp"; } else if(statuses == 4) { tech = "UDown"; } else if(statuses == 9) { tech = "Trill"; } else if(statuses == 10) { tech = "Harm"; } else if(statuses == 11) { tech = "PHarm"; } else if(amount != 0.0) { tech = "Bend"; } else { tech = ""; } (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tNote #%4lu start = %6lums, end = %6lums, string = %lu, fret = %2lu, finger = %lu, statuses = %lu (%5s), bend type = %lu, slide/bend/trill amount = %9f, vibrato = 0x%lX, statuses2 = 0x%08lX, pop/slap = %s", ctr, startms, endms, stringnum, fret, finger, statuses, tech, bendtype, amount, vibrato, statuses2, (!statuses3 ? "neither" : (dword == 1 ? "slap" : "pop"))); eof_log(eof_log_string, 1); //Create note np = eof_pro_guitar_track_add_note(tp); //Allocate, initialize and add the new note to the note array if(!np) { eof_log("\tError allocating memory. Aborting", 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } np->type = curdiff; stringnum = tp->numstrings - stringnum; //Convert the string numbering np->note = 1 << stringnum; np->frets[stringnum] = fret; np->finger[stringnum] = finger; np->pos = startms; flags = 0; if(end > start) { np->length = end - start + 0.5; } else { np->length = 1; } switch(statuses) { case 1: //String mute flags |= EOF_PRO_GUITAR_NOTE_FLAG_STRING_MUTE; break; case 2: //Slide to next note flags |= EOF_PRO_GUITAR_NOTE_FLAG_RS_NOTATION; if(amount >= 0.0) { flags |= EOF_PRO_GUITAR_NOTE_FLAG_SLIDE_UP; np->slideend = fret + ((amount / 0.5) + 0.5); } else { flags |= EOF_PRO_GUITAR_NOTE_FLAG_SLIDE_DOWN; np->slideend = fret + ((amount / 0.5) + 0.5); } break; case 3: //Unpitched slide up flags |= EOF_PRO_GUITAR_NOTE_FLAG_UNPITCH_SLIDE; if(amount > 0.0) { np->unpitchend = (fret + (amount / 0.5)) + 0.5; } if(fret + 1 > tp->numfrets) { //If this track doesn't have enough frets to notate this tp->numfrets = fret + 1; //Increase its limit } break; case 4: //Unpitched slide down flags |= EOF_PRO_GUITAR_NOTE_FLAG_UNPITCH_SLIDE; if(amount < 0.0) { //This floating point value is expected to be negative if(fret + amount > 0.0) { //Prevent integer underflow np->unpitchend = (fret + (amount / 0.5)) + 0.5; } } break; case 9: //Trill flags |= EOF_NOTE_FLAG_IS_TRILL; //Set this flag as a placeholder, the trill phrase will have to be created after the ZOBJ section is imported break; case 10: //Natural harmonic flags |= EOF_PRO_GUITAR_NOTE_FLAG_HARMONIC; break; case 11: //Pinch harmonic flags |= EOF_PRO_GUITAR_NOTE_FLAG_P_HARMONIC; break; default: break; } if(bendtype) { //If this note bends EOF_PRO_GUITAR_NOTE *tnp1; int bendstrength; if(bendtype != 1) { //Special bend types involve creating at least one pre-bend tech note //Add a tech note defining the pre-bend tnp1 = eof_pro_guitar_track_add_tech_note(tp); if(!tnp1) { eof_log("\tError allocating memory. Aborting", 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } bendstrength = (amount / 0.5) + 0.5; //Round bend strength to the nearest number of half steps if(!bendstrength) { //If this note doesn't have a pre-bend strength defined bendstrength = (prevamount / 0.5) + 0.5; //Use the previous note's bend strength instead if(!bendstrength) bendstrength = 1; //Or 1 if that value wasn't defined for some reason } tnp1->type = curdiff; tnp1->note = 1 << stringnum; tnp1->frets[stringnum] = fret; tnp1->finger[stringnum] = finger; tnp1->pos = np->pos; //Place this tech note at the beginning of the imported note containing the bend tnp1->length = 1; tnp1->flags |= EOF_PRO_GUITAR_NOTE_FLAG_BEND; tnp1->flags |= EOF_PRO_GUITAR_NOTE_FLAG_RS_NOTATION; tnp1->bendstrength = bendstrength; if(bendtype == 2) { //Bend and release //Add a tech note defining the release EOF_PRO_GUITAR_NOTE *tnp2 = eof_pro_guitar_track_add_tech_note(tp); if(!tnp2) { eof_log("\tError allocating memory. Aborting", 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } tnp2->type = curdiff; tnp2->note = 1 << stringnum; tnp2->frets[stringnum] = fret; tnp2->finger[stringnum] = finger; tnp2->pos = np->pos + np->length - effective_min_note_distance; //Place this tech note at the end of the imported note containing the bend tnp2->length = 1; tnp2->flags |= EOF_PRO_GUITAR_NOTE_FLAG_BEND; tnp2->flags |= EOF_PRO_GUITAR_NOTE_FLAG_RS_NOTATION; tnp2->bendstrength = 0; }//Bend and release }//Special bend types involve creating at least one pre-bend tech note else if(amount > 0.0) { //Normal bend note flags |= EOF_PRO_GUITAR_NOTE_FLAG_BEND; flags |= EOF_PRO_GUITAR_NOTE_FLAG_RS_NOTATION; np->bendstrength = (amount / 0.5) + 0.5; //Round bend strength to the nearest number of half steps } } if(vibrato) { //If this note has vibrato flags |= EOF_PRO_GUITAR_NOTE_FLAG_VIBRATO; } if((statuses2 >> 24) & 0xFF) { //If this note links to the next note flags |= EOF_PRO_GUITAR_NOTE_FLAG_LINKNEXT; } if((statuses2 >> 16) & 0xFF) { //If this note is palm muted flags |= EOF_PRO_GUITAR_NOTE_FLAG_PALM_MUTE; } if((statuses2 >> 8) & 0xFF) { //If this note uses tremolo picking flags |= EOF_NOTE_FLAG_IS_TREMOLO; //Set this flag as a placeholder, the tremolo phrase will have to be created after the ZOBJ section is imported } if(((statuses3 >> 24) & 0xFF) == 1) { //If this note uses slap technique flags |= EOF_PRO_GUITAR_NOTE_FLAG_SLAP; } if(((statuses3 >> 24) & 0xFF) == 2) { //If this note uses pop technique flags |= EOF_PRO_GUITAR_NOTE_FLAG_POP; } //Track HO/POs if(npp && (npp->flags & EOF_PRO_GUITAR_NOTE_FLAG_LINKNEXT)) { //If there was a previous note and it was marked as linked with the current note if(!prevbendtype && !(npp->flags & (EOF_PRO_GUITAR_NOTE_FLAG_SLIDE_UP | EOF_PRO_GUITAR_NOTE_FLAG_SLIDE_DOWN | EOF_PRO_GUITAR_NOTE_FLAG_BEND | EOF_PRO_GUITAR_NOTE_FLAG_UNPITCH_SLIDE))) { //If the previous note wasn't a slide or bend if(!(np->flags & (EOF_PRO_GUITAR_NOTE_FLAG_SLIDE_UP | EOF_PRO_GUITAR_NOTE_FLAG_SLIDE_DOWN | EOF_PRO_GUITAR_NOTE_FLAG_BEND | EOF_PRO_GUITAR_NOTE_FLAG_UNPITCH_SLIDE))) { //If the current note doesn't slide or bend either, assume the linked status refers to hammer on or pull off npp->flags &= ~EOF_PRO_GUITAR_NOTE_FLAG_LINKNEXT; //Remove the linknext flag from the previous note if(fret > prevfret) { //If this note's fret is higher flags |= EOF_PRO_GUITAR_NOTE_FLAG_HO; //It's a hammer on } else { flags |= EOF_PRO_GUITAR_NOTE_FLAG_PO; //It's a pull off } } } } np->flags = flags; if(fret > tp->numfrets) tp->numfrets = fret; //Track the highest used fret number npp = np; //Keep track of the previously created note prevfret = fret; //And its fret value prevamount = amount; //And its slide/bend/trill amount prevbendtype = bendtype; //And its bend type }//For each note in this object eof_build_trill_phrases(tp); //Add trill phrases to encompass the notes that have the trill flag set eof_build_tremolo_phrases(tp, curdiff); //Add tremolo phrases and define them to apply to the track difficulty that this tab object contained notes for //Apply string muting for notes for(ctr = 0; ctr < tp->notes; ctr++) { //For each note in the track unsigned long bitmask; if(tp->note[ctr]->flags & EOF_PRO_GUITAR_NOTE_FLAG_STRING_MUTE) { //If the string mute status was set during import for(ctr2 = 0, bitmask = 1; ctr2 < 6; ctr2++, bitmask <<= 1) { //For each of the 6 supported strings if(tp->note[ctr]->note & bitmask) { //If this string is used tp->note[ctr]->frets[ctr2] |= 0x80; //Set the mute bit } } } } }//Tab object else { //Unknown section type string = NULL; //Set to NULL so the logic below seeks beyond the section } } if(!string) { //If the ZOBJ section was not parsed, skip to the end of the section for(ctr = 0; ctr < zobjsize - 32; ctr++) { //For each of the remaining bytes in the ZOBJ chunk word = pack_getc(inf); //Skip to next byte fileadd++; //Update file address if(word == EOF) { //If the byte couldn't be read (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tError reading remaining %lu bytes of ZOBJ object. Aborting", zobjsize - 32); eof_log(eof_log_string, BF_IMPORT_LOGGING_LEVEL); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } } (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\t\tSeeked ahead %lu bytes to end of unrecognized/ignored ZOBJ object.", zobjsize - 32); eof_log(eof_log_string, BF_IMPORT_LOGGING_LEVEL); } }//If this is a ZOBJ header else { //Unrecognized header (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tUnrecognized section header \"%s\" at file address 0x%lX. Aborting.", header, fileadd); eof_log(eof_log_string, 1); (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); eof_destroy_song(sp); return NULL; } }//For each section defined in the INDX header //Cleanup (void) pack_fclose(inf); free(sectiondata); for(ctr = 0; ctr < numstrings; free(stringdata[ctr].string), ctr++); //Free the memory used to store each string free(stringdata); //Correct the lyric phrases, as the Bandfuse format effectively only roughly defines line break positions eof_track_sort_notes(sp, EOF_TRACK_VOCALS); lyrstart = 0; //The first lyric line will apply beginning at the start of the chart //Adjust the lyric line start and end positions to correctly reflect their scope for(ctr = 0; ctr < sp->vocal_track[0]->lines; ctr++) { //For each lyric line that was added EOF_PHRASE_SECTION *llp = &sp->vocal_track[0]->line[ctr]; //A pointer to the lyric line being checked //Extend the lyric line up to this line break temp = llp->start_pos; llp->start_pos = lyrstart; //This lyric line begins where the previous line break ended (or the beginning of the chart if this is the first line break) lyrstart = llp->end_pos; //The next lyric line will begin at the end of this line break llp->end_pos = temp; //This lyric line extends to the beginning of the line break } //Align the lyric line with the first and last lyric in its scope for(ctr = 0; ctr < sp->vocal_track[0]->lines; ctr++) { //For each lyric line that was added EOF_PHRASE_SECTION *llp = &sp->vocal_track[0]->line[ctr]; //A pointer to the lyric line being checked EOF_LYRIC *lp; //A pointer to the lyric being checked EOF_LYRIC *nlp; //A pointer to the lyric after the one being checked char firstfound = 0; //Tracks whether the first lyric in this line has been found for(ctr2 = 0; ctr2 < sp->vocal_track[0]->lyrics; ctr2++) { //For each lyric in the chart lp = sp->vocal_track[0]->lyric[ctr2]; //Simplify if(ctr2 + 1 < sp->vocal_track[0]->lyrics) { //If there is another lyric nlp = sp->vocal_track[0]->lyric[ctr2 + 1]; //Store a pointer to it } else { //This is the last lyric in the chart nlp = NULL; } if((lp->pos < llp->start_pos) || (lp->pos > llp->end_pos)) continue; //If this lyric is not within the line, skip it if(!firstfound) { //If this is the first lyric that was found to be within it firstfound = 1; llp->start_pos = lp->pos; //Move the beginning of this line to match the beginning of the lyric } if(nlp && (nlp->pos > llp->end_pos)) { //If there's another lyric but it is outside of this lyric line llp->end_pos = lp->pos + lp->length; //Move the end of this line to match the end of the line's last lyric if(ctr + 1 < sp->vocal_track[0]->lines) { //If there's another lyric line sp->vocal_track[0]->line[ctr + 1].start_pos = nlp->pos; //Preemptively move the next line's start position to the start of the next lyric (some Bandfuse songs mark the next line as starting as early as on top of the last lyric of the previous line) } } } (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tLyric line #%lu corrected to: start = %lums, end = %lums", ctr, llp->start_pos, llp->end_pos); eof_log(eof_log_string, 1); } //Build the tempo map, using the timestamps instead of the tempo changes, since the Bandfuse format doesn't use very accurate tempo values qsort(changes, (size_t)numchanges, sizeof(struct bf_timing_change), eof_bf_qsort_time_changes); //Sort the time signature and tempo changes by timestamp curnum = curden = lastnum = lastden = 4; //Until a time signature change is reached, 4/4 is assumed curbeatlen = 500.0; //Until a tempo change is reached, 120BPM is assumed (the corresponding beat length is 500ms) curtime = 0.0; curtimems = 0; while(curtimems <= lastitem) { //Add new beats until enough have been added to encompass all imported items if(eof_song_add_beat(sp) == NULL) //Add a new beat { //Or return failure if that doesn't succeed eof_destroy_song(sp); eof_log("Error adding beats. Aborting", 1); return NULL; } //Find the tempo and time signature in effect at this beat for(ctr = 0; ctr < numchanges; ctr++) { //For each imported time signature and tempo change if(curtimems < changes[ctr].realtimems) continue; //If the change is after the current beat time, skip it if(!changes[ctr].type) { //If this is a time signature change curnum = changes[ctr].TS_num; curden = changes[ctr].TS_den; } else { //This is a tempo change unsigned numbeats; //The approximate number of beats between this tempo change and the next double tempbeatlen = 60000.0 / (changes[ctr].BPM * (double)curden / 4.0); //Determine the beat length as specified by the given BPM value (taking the time signature into account) numbeats = (changes[ctr].realtime2ms - changes[ctr].realtimems) / tempbeatlen + 0.5; //Determine the approximate number of beats between this tempo change and the next if(numbeats) { //If the start and end positions are far enough apart to denote at least one beat curbeatlen = ((double)changes[ctr].realtime2ms - changes[ctr].realtimems) / numbeats; //Get a beat length that more accurately reflects the chart } else { //The final defined tempo change defines both positions with the same value curbeatlen = tempbeatlen; //Just base the beat length on the tempo } } } //Set the timestamps and time signature for this beat as applicable sp->beat[sp->beats - 1]->fpos = curtime; //Set this beat's timestamp sp->beat[sp->beats - 1]->pos = curtimems; if(eof_use_ts && ((lastnum != curnum) || (lastden != curden) || (sp->beats - 1 == 0))) { //If the user opted to import TS changes, and this time signature is different than the last beat's time signature (or this is the first beat) (void) eof_apply_ts(curnum, curden, sp->beats - 1, sp, 0); //Set the TS flags for this beat } //Advance the time variables curtime += curbeatlen; curtimems = curtime + 0.5; } eof_calculate_tempo_map(sp); //Rebuild the tempo changes based on the beat timestamps eof_truncate_chart(eof_song); //Update number of beats and the chart length as appropriate ///Debug readout of string array // eof_log("English string list", 1); // for(ctr = 0; ctr < numstrings; ctr++) // { //For each string in the array // if(stringdata[ctr].string) // { //If this entry has a string // #ifdef ALLEGRO_WINDOWS // (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tString #%lu:\tIndex key = 0x%016I64x, string = \"%s\"", ctr, stringdata[ctr].indkey, stringdata[ctr].string); // #else // (void) snprintf(eof_log_string, sizeof(eof_log_string) - 1, "\tString #%lu:\tIndex key = 0x%016llux, string = \"%s\"", ctr, stringdata[ctr].indkey, stringdata[ctr].string); // #endif // eof_log(eof_log_string, BF_IMPORT_LOGGING_LEVEL); // } // } //Update path variables (void) ustrcpy(eof_filename, fn); (void) replace_filename(eof_song_path, fn, "", 1024); (void) replace_filename(eof_last_eof_path, fn, "", 1024); (void) ustrcpy(eof_loaded_song_name, get_filename(fn)); (void) replace_extension(eof_loaded_song_name, eof_loaded_song_name, "eof", 1024); //Load guitar.ogg automatically if it's present, otherwise prompt user to browse for audio (void) append_filename(nfn, eof_song_path, "guitar.ogg", 1024); ogg_profile_name = sp->tags->ogg[0].filename; //Store the pointer to the OGG profile filename to be updated by eof_load_ogg() if(!eof_load_ogg(nfn, 2)) //If user does not provide audio, fail over to using silent audio { eof_destroy_song(sp); return NULL; } eof_song_loaded = 1; eof_chart_length = alogg_get_length_msecs_ogg_ul(eof_music_track); eof_log("\tImport complete", 1); return sp; }