Exemplo n.º 1
0
void toasim_read_strx(char** val, FILE* file){
	char key[8];
	uint32_t len;
	key[4]='\0';
	fread_err(key,4,1,file);
	fread_err(&len,4,1,file);
	(*val)=(char*)malloc(len+1);
	fread_err(*val,1,len,file);
	(*val)[len]='\0';
}
Exemplo n.º 2
0
void toasim_read_64(void* val, FILE* file){
	char key[8];
	uint32_t len;
	key[4]='\0';
	fread_err(key,4,1,file);
	fread_err(&len,4,1,file);
	if (len!=8){
		fprintf(stderr,"Error reading key '%s' expected 64 bit value, got 32 bit value\n",key);
	}
	fread_err(val,len,1,file);
}
Exemplo n.º 3
0
void toasim_read_str(char* val, FILE* file){
	char key[8];
	uint32_t len;
	key[4]='\0';
	fread_err(key,4,1,file);
	fread_err(&len,4,1,file);
	if(len > TOASIM_STRLEN){
		fprintf(stderr,"ERROR: TOASIM_STRLEN not large enough for this data file\n");
		len=TOASIM_STRLEN;
	}
	fread_err(val,1,len,file);
}
Exemplo n.º 4
0
toasim_corrections_t *toasim_read_corrections(toasim_header_t *header, int nreal, FILE *file){
	//seek to the correction requested
	char key[8];
	uint32_t offset=header->d_start+header->d_offset*nreal;

	if(fseek(file,offset,SEEK_SET)!=0) perror("fseek");
	key[4]='\0';
	fread_err(key,4,1,file);

	if(strcmp(key,"CORR")){
		fprintf(stderr,"ERROR: Could not locate CORR keyword at offset for realisation %d\n",nreal);
		return NULL;
	}

	toasim_corrections_t *corr = (toasim_corrections_t*)malloc(sizeof(toasim_corrections_t));
	corr->offsets=(double*)malloc(sizeof(double)*header->ntoa);
	fread_err(&corr->a0,8,1,file);
	fread_err(&corr->a1,8,1,file);
	fread_err(&corr->a2,8,1,file);
	fread_err(corr->offsets,sizeof(double),header->ntoa,file);
	if(header->rparam_len > 0){
		corr->params=(char*)malloc(header->rparam_len);
		fread_err(corr->params,1,header->rparam_len+1,file);
		corr->params[header->rparam_len]='\0';
	}
	return corr;
}
Exemplo n.º 5
0
toasim_header_t *toasim_read_header(FILE *file){
	toasim_header_t *header;
	char key[8];
	int32_t dmy_32;
	uint32_t len=0;
	fread_err(key,6,1,file);
	key[6]='\0';
	if(strcmp(key,"TOASIM")){
		fprintf(stderr,"ERROR: not a TOASIM file (%s)\n",key);
		return NULL;
	}
	header = toasim_init_header();
	toasim_read_32(&header->version,file);
	if (header->version > TOASIM_VERSION){
		fprintf(stderr,"ERROR: toasim file version (%d) > library version (%d)\n",header->version,TOASIM_VERSION);
	}
	toasim_read_str(header->writer,file);
	toasim_read_str(header->timfile_name,file);
	toasim_read_str(header->parfile_name,file);
	toasim_read_str(header->invocation,file);
	toasim_read_str(header->short_desc,file);
	toasim_read_strx(&header->description,file);
	toasim_read_strx(&header->idealised_toas,file);
	toasim_read_strx(&header->orig_parfile,file);
	toasim_read_strx(&header->gparam_desc,file);
	toasim_read_strx(&header->gparam_vals,file);
	toasim_read_strx(&header->rparam_desc,file);
	toasim_read_32(&header->rparam_len,file);
	toasim_read_64(&header->seed,file);
	toasim_read_32(&header->ntoa,file);
	toasim_read_32(&header->nrealisations,file);
	if(header->version==1)toasim_read_32(&dmy_32,file); // old scale factor
	toasim_read_32(&header->d_start,file);
	toasim_read_32(&header->d_offset,file);

	return header;
}
Exemplo n.º 6
0
int VL_PreLoad(FILE *inf,char validate)
{
    long ftell_result=0;				//Used to store return value from ftell()
    char buffer[5]= {0};					//Used to read word/doubleword integers from file
    unsigned long ctr=0;				//Generic counter
    char *temp=NULL;					//Temporary pointer for allocated strings
    struct VL_Sync_entry se= {0,0,0,0,0,NULL};		//Used to store sync entries from file during parsing
    struct VL_Text_entry *ptr1=NULL;	//Used for allocating links for the text chunk list
    struct VL_Sync_entry *ptr2=NULL;	//Used for allocation links for the sync chunk list
    struct VL_Text_entry *curtext;		//Conductor for text chunk linked list
    struct VL_Sync_entry *cursync;		//Conductor for sync chunk linked list

    assert_wrapper(inf != NULL);

//Initialize variables
    VL.numlines=0;
    VL.numsyncs=0;
    VL.filesize=0;
    VL.textsize=0;
    VL.syncsize=0;
    VL.Lyrics=NULL;
    VL.Syncs=NULL;
    curtext=NULL;	//list starts out empty
    cursync=NULL;	//list starts out empty
    se.next=NULL;	//In terms of linked lists, this always represents the last link

    if(Lyrics.verbose)	(void) puts("Reading VL file header");

//Read file header
    fread_err(buffer,4,1,inf);			//Read 4 bytes, which should be 'V','L','2',0
    ReadDWORDLE(inf,&(VL.filesize));	//Read doubleword from file in little endian format

    if(strncmp(buffer,"VL2",4) != 0)
    {
        if(validate)
            return 1;
        else
        {
            (void) puts("Error: Invalid file header string\nAborting");
            exit_wrapper(1);
        }
    }

//Read text header
    fread_err(buffer,4,1,inf);			//Read 4 bytes, which should be 'T','E','X','T'
    buffer[4]='\0';						//Add a NULL character to make buffer into a proper string
    ReadDWORDLE(inf,&(VL.textsize));	//Read doubleword from file in little endian format

    if(VL.textsize % 4 != 0)
    {
        (void) puts("Error: VL spec. violation: Sync chunk is not padded to a double-word alignment");
        if(validate)
            return 2;
        else
        {
            (void) puts("Aborting");
            exit_wrapper(2);
        }
    }

    if(strncmp(buffer,"TEXT",5) != 0)
    {
        (void) puts("Error: Invalid text header string");
        if(validate)
            return 3;
        else
        {
            (void) puts("Aborting");
            exit_wrapper(3);
        }
    }

//Read sync header, which should be th.textsize+16 bytes into the file
    fseek_err(inf,VL.textsize+16,SEEK_SET);
    fread_err(buffer,4,1,inf);			//Read 4 bytes, which should be 'S','Y','N','C'
    buffer[4]='\0';						//Add a NULL character to make buffer into a proper string
    ReadDWORDLE(inf,&(VL.syncsize));	//Read doubleword from file in little endian format

    if(strncmp(buffer,"SYNC",5) != 0)
    {
        (void) puts("Error: Invalid sync header string");
        if(validate)
            return 4;
        else
        {
            (void) puts("Aborting");
            exit_wrapper(4);
        }
    }

//Validate chunk sizes given in headers (textsize was already validated if the Sync header was correctly read)
    fseek_err(inf,0,SEEK_END);	//Seek to end of file
    ftell_result = ftell(inf);	//Get position of end of file
    if(ftell_result < 0)
    {
        printf("Error determining file length during file size validation: %s\n",strerror(errno));
        if(validate)
            return 5;
        else
        {
            (void) puts("Aborting");
            exit_wrapper(5);
        }
    }
    if((unsigned long)ftell_result != VL.filesize+8)	//Validate filesize
    {
        (void) puts("Error: Filesize does not match size given in file header");
        if(validate)
            return 6;
        else
        {
            (void) puts("Aborting");
            exit_wrapper(6);
        }
    }
    if(VL.filesize != VL.textsize + VL.syncsize + 16)	//Validate syncsize
    {
        (void) puts("Error: Incorrect size given in sync chunk");
        if(validate)
            return 7;
        else
        {
            (void) puts("Aborting");
            exit_wrapper(7);
        }
    }

//Load tags
    if(Lyrics.verbose)	(void) puts("Loading VL tag strings");
    fseek_err(inf,16,SEEK_SET);		//Seek to the Title tag

    for(ctr=0; ctr<5; ctr++)	//Expecting 5 null terminated unicode strings
    {
        temp=ReadUnicodeString(inf);	//Allocate array and read Unicode string

        if(temp[0] != '\0')	//If the string wasn't empty, save the tag
            switch(ctr)
            {   //Look at ctr to determine which tag we have read, and assign to the appropriate tag string:
            case 0:
                SetTag(temp,'n',0);
                break;

            case 1:
                SetTag(temp,'s',0);
                break;

            case 2:
                SetTag(temp,'a',0);
                break;

            case 3:
                SetTag(temp,'e',0);
                break;

            case 4:	//Offset
                SetTag(temp,'o',0);	//Do not multiply the offset by -1
                break;

            default:
                (void) puts("Unexpected error");
                if(validate)
                    return 8;
                else
                {
                    (void) puts("Aborting");
                    exit_wrapper(8);
                }
                break;
            }//end switch(ctr)

        free(temp);	//Free string, a copy of which would have been stored in the Lyrics structure as necessary
    }//end for(ctr=0;ctr<5;)

//Validate the existence of three empty unicode strings
    for(ctr=0; ctr<3; ctr++)	//Expecting 3 empty, null terminated unicode strings
        if(ParseUnicodeString(inf) != 0)
        {
            (void) puts("Error: Reserved string is not empty during load");
            if(validate)
                return 9;
            else
            {
                (void) puts("Aborting");
                exit_wrapper(9);
            }
        }

//Load the lyric strings
    if(Lyrics.verbose)	(void) puts("Loading text chunk entries");

    while(1)	//Read lyric lines
    {
        //Check to see if the Sync Chunk has been reached
        if((unsigned long)ftell_err(inf) >= VL.textsize + 16)	//The Text chunk size + the file and text header sizes is the position of the Sync chunk
            break;

        //Read lyric string
        temp=ReadUnicodeString(inf);

        ftell_result=ftell_err(inf);	//If this string is empty
        if(temp[0] == '\0')
        {
            if(VL.textsize+16 - ftell_result <= 3)	//This 0 word value ends within 3 bytes of the sync header (is padding)
            {
                free(temp);	//Release string as it won't be used
                break;		//text chunk has been read
            }
            else
            {
                printf("Error: Empty lyric string detected before file position 0x%lX\n",ftell_result);
                free(temp);
                if(validate)
                    return 10;
                else
                {
                    (void) puts("Aborting");
                    exit_wrapper(10);
                }
            }
        }
        if((unsigned long)ftell_result > VL.textsize + 16)	//If reading this string caused the file position to cross into Sync chunk
        {
            (void) puts("Error: Lyric string overlapped into Sync chunk");
            free(temp);
            if(validate)
                return 11;
            else
            {
                (void) puts("Aborting");
                exit_wrapper(11);
            }
        }

        if(Lyrics.verbose)	printf("\tLoading text piece #%lu: \"%s\"\n",VL.numlines,temp);

        //Allocate link
        ptr1=malloc_err(sizeof(struct VL_Text_entry));	//Allocate memory for new link

        //Build link and insert into list
        ptr1->text=temp;
        ptr1->next=NULL;

        if(VL.Lyrics == NULL)	//This is the first lyric piece read from the input file
            VL.Lyrics=ptr1;
        else
        {
            assert_wrapper(curtext != NULL);
            curtext->next=ptr1;	//The end of the list points forward to this new link
        }

        curtext=ptr1;	//This link is now at the end of the list

        VL.numlines++;	//one more lyric string has been parsed
    }//end while(1)

    if(Lyrics.verbose)	printf("%lu text chunk entries loaded\n",VL.numlines);

//Load the Sync points
    if(Lyrics.verbose)	(void) puts("Loading sync chunk entries");

    fseek_err(inf,VL.textsize+16+8,SEEK_SET);	//Seek to first sync entry (8 bytes past start of sync header)

    while(ReadSyncEntry(&se,inf) == 0)	//Read all entries
    {
        if(se.start_char > se.end_char)
        {
            (void) puts("Error: Invalid sync point offset is specified to start after end offset");
            if(validate)
                return 12;
            else
            {
                (void) puts("Aborting");
                exit_wrapper(12);
            }
        }

        if(se.lyric_number!=0xFFFF && se.start_char!=0xFFFF && se.start_time!=0xFFFFFFFF)
        {   //This is a valid sync entry
            //Allocate link
            ptr2=malloc_err(sizeof(struct VL_Sync_entry));

            //Build link and insert into list
            memcpy(ptr2,&se,sizeof(struct VL_Sync_entry));	//copy structure into newly allocated link
            if(VL.Syncs == NULL)	//This is the first sync piece read from the input file
                VL.Syncs=ptr2;
            else					//The end of the list points forward to this new link
            {
                assert_wrapper(cursync != NULL);
                cursync->next=ptr2;
            }

            cursync=ptr2;		//This link is now at the end of the list
            VL.numsyncs++;		//one more sync entry has been parsed
        }
    }

    if(Lyrics.verbose)	(void) puts("VL_PreLoad completed");
    return 0;
}
Exemplo n.º 7
0
unsigned long ID3FrameProcessor(struct ID3Tag *ptr)
{
	unsigned char header[10]={0};	//Used to store the ID3 frame header
	unsigned long filepos=0;
	int ctr2=0;						//Used to validate frame ID
	struct ID3Frame *temp=NULL;		//Used to allocate an ID3Frame link
	struct ID3Frame *head=NULL;		//Used as the head pointer
	struct ID3Frame *cond=NULL;		//Used as the conductor
	unsigned long ctr=0;			//Count the number of frames processed
	unsigned long framesize=0;		//Used to validate frame size
	char buffer[31]={0};			//Used to read an ID3v1 tag in the absence of an ID3v2 tag
	static struct ID3Frame emptyID3Frame;	//Auto-initialize all members to 0/NULL

//Validate input parameter
	if((ptr == NULL) || (ptr->fp == NULL))
		return 0;	//Return failure

//Find and parse the ID3 header
//ID3v1
	fseek_err(ptr->fp,-128,SEEK_END);	//Seek 128 back from the end of the file, where this tag would exist
	fread_err(buffer,3,1,ptr->fp);		//Read 3 bytes for the "TAG" header
	buffer[3] = '\0';	//Ensure NULL termination
	if(strcasecmp(buffer,"TAG") == 0)	//If this is an ID3v1 header
	{
		if(Lyrics.verbose)	(void) puts("Loading ID3v1 tag");
		ptr->id3v1present=1;	//Track that this tag exists
		fread_err(buffer,30,1,ptr->fp);		//Read the first field in the tag (song title)
		buffer[30] = '\0';	//Ensure NULL termination
		if(buffer[0] != '\0')	//If the string isn't empty
		{
			ptr->id3v1present=2;	//Track that this tag is populated
			ptr->id3v1title=DuplicateString(buffer);
			if(Lyrics.verbose)	(void) puts("\tTitle loaded");
		}
		fread_err(buffer,30,1,ptr->fp);		//Read the second field in the tag (artist)
		buffer[30] = '\0';	//Ensure NULL termination
		if(buffer[0] != '\0')	//If the string isn't empty
		{
			ptr->id3v1present=2;	//Track that this tag is populated
			ptr->id3v1artist=DuplicateString(buffer);
			if(Lyrics.verbose)	(void) puts("\tArtist loaded");
		}
		fread_err(buffer,30,1,ptr->fp);		//Read the third field in the tag (album)
		buffer[30] = '\0';	//Ensure NULL termination
		if(buffer[0] != '\0')	//If the string isn't empty
		{
			ptr->id3v1present=2;	//Track that this tag is populated
			ptr->id3v1album=DuplicateString(buffer);
			if(Lyrics.verbose)	(void) puts("\tAlbum loaded");
		}
		fread_err(buffer,4,1,ptr->fp);		//Read the fourth field in the tag (year)
		buffer[4]='\0';	//Terminate the buffer to make it a string
		if(buffer[0] != '\0')	//If the string isn't empty
		{
			ptr->id3v1present=2;	//Track that this tag is populated
			ptr->id3v1year=DuplicateString(buffer);
			if(Lyrics.verbose)	(void) puts("\tYear loaded");
		}
	}

//ID3v2
//Load ID3 frames into linked list
	rewind_err(ptr->fp);		//Rewind file so that the ID3v2 header can be searched for
	if(FindID3Tag(ptr) == 0)	//Locate the ID3 tag
		return 0;				//Return if there was none

	if(Lyrics.verbose)	(void) puts("Loading ID3v2 tag");
	fseek_err(ptr->fp,ptr->framestart,SEEK_SET);	//Seek to first ID3 frame
	filepos=ftell_err(ptr->fp);						//Record file position of first expected ID3 frame
	while((filepos >= ptr->framestart) && (filepos < ptr->tagend))
	{	//While file position is at or after the end of the ID3 header, before or at end of the ID3 tag
	//Read frame header into buffer
		if(fread(header,10,1,ptr->fp) != 1)
		{
			ptr->frames=head;	//Store the linked list into the ID3 tag structure
			return 0;	//Return failure on I/O error
		}

	//Validate frame ID
		for(ctr2=0;ctr2<4;ctr2++)
		{
			if(!isupper(header[ctr2]) && !isdigit(header[ctr2]))	//If the character is NOT valid for an ID3 frame ID
			{
				ptr->frames=head;	//Store the linked list into the ID3 tag structure
				return ctr;	//Return the number of frames that were already processed
			}
		}

	//Validate frame end
		framesize=((unsigned long)header[4]<<24) | ((unsigned long)header[5]<<16) | ((unsigned long)header[6]<<8) | ((unsigned long)header[7]);	//Convert to 4 byte integer
		if(filepos + framesize + 10 > ptr->tagend)	//If the defined frame size would cause the frame to extend beyond the end of the ID3 tag
		{
			ptr->frames=head;	//Store the linked list into the ID3 tag structure
			return 0;	//Return failure
		}

	//Initialize an ID3Frame structure
		temp=malloc_err(sizeof(struct ID3Frame));	//Allocate memory
		*temp=emptyID3Frame;						//Reliably initialize all values to 0/NULL
		temp->frameid=malloc_err(5);				//Allocate 5 bytes for the ID3 frame ID
		memcpy(temp->frameid,header,4);				//Copy the 4 byte ID3 frame ID into the pre-terminated string
		temp->frameid[4]='\0';						//Terminate the string
		temp->pos=filepos;	//Record the file position of the ID3 frame
		temp->length=((unsigned long)header[4]<<24) | ((unsigned long)header[5]<<16) | ((unsigned long)header[6]<<8) | ((unsigned long)header[7]);
			//Record the frame length (defined in header as 4 byte Big Endian integer)

		//Append ID3Frame link to the list
		if(head == NULL)	//Empty list
		{
			head=temp;	//Point head to new link
			cond=temp;	//Point conductor to new link
		}
		else
		{
			assert_wrapper(cond != NULL);	//The linked list is expected to not be corrupted
			cond->next=temp;	//Conductor points forward to new link
			temp->prev=cond;	//New link points back to conductor
			cond=temp;		//Conductor points to new link
		}

		if(Lyrics.verbose >= 2)	printf("\tFrame ID %s loaded\n",temp->frameid);
		ctr++;	//Iterate counter

		(void) fseek(ptr->fp,framesize,SEEK_CUR);	//Seek ahead to the beginning of the next ID3 frame
		filepos+=framesize + 10;	//Update file position
	}//While file position is at or after the end of the ID3 header, before or at end of the ID3 tag

	if(Lyrics.verbose)	printf("%lu ID3 frames loaded\n\n",ctr);

	ptr->frames=head;	//Store the linked list into the ID3 tag structure
	return ctr;			//Return the number of frames loaded
}
Exemplo n.º 8
0
void SYLT_Parse(struct ID3Tag *tag)
{
	unsigned char frameheader[10]={0};	//This is the ID3 frame header
	unsigned char syltheader[6]={0};	//This is the SYLT frame header, excluding the terminated descriptor string that follows
	char *contentdescriptor=NULL;		//The null terminated string located after the SYLT frame header
	char timestampformat=0;				//Will be set based on the SYLT header (1= MPEG Frames, 2=Milliseconds)
	char *string=NULL;					//Used to store SYLT strings that are read
	char *string2=NULL;					//Used to build a display version of the string for debug output (minus newline)
	unsigned char timestamparray[4]={0};//Used to store the timestamp for a lyric string
	unsigned long timestamp=0;			//The timestamp converted to milliseconds
	unsigned long breakpos;				//Will be set to the first byte beyond the SYLT frame
	unsigned long framesize=0;
	char groupswithnext=0;				//Used for grouping logic
	char linebreaks=0;					//Tracks whether the imported lyrics defined linebreaks (if not, each ID3 lyric should be treated as one line)
	struct Lyric_Piece *ptr=NULL,*ptr2=NULL;	//Used to insert line breaks as necessary
	struct Lyric_Line *lineptr=NULL;			//Used to insert line breaks as necessary
	unsigned long length=0;				//Used to store the length of each string that is read from file
	unsigned long filepos=0;			//Used to track the current file position during SYLT lyric parsing

//Validate input
	assert_wrapper((tag != NULL) && (tag->fp != NULL));
	breakpos=ftell_err(tag->fp);

//Load and validate ID3 frame header and SYLT frame header
	fread_err(frameheader,10,1,tag->fp);			//Load ID3 frame header
	fread_err(syltheader,6,1,tag->fp);				//Load SYLT frame header
	if((frameheader[9] & 192) != 0)
	{
		(void) puts("ID3 Compression and Encryption are not supported\nAborting");
		exit_wrapper(3);
	}
	if(syltheader[0] != 0)
	{
		(void) puts("Unicode ID3 lyrics are not supported\nAborting");
		exit_wrapper(4);
	}

//Load and validate content descriptor string
	contentdescriptor=ReadString(tag->fp,NULL,0);	//Load content descriptor string
	if(contentdescriptor == NULL)	//If the content descriptor string couldn't be read
	{
		(void) puts("Damaged Content Descriptor String\nAborting");
		exit_wrapper(1);
	}

//Validate timestamp format
	timestampformat=syltheader[4];
	if((timestampformat != 1) && (timestampformat != 2))
	{
		printf("Warning: Invalid timestamp format (%d) specified, ms timing assumed\n",timestampformat);
		timestampformat=2;	//Assume millisecond timing
	}

//Process framesize as a 4 byte Big Endian integer
	framesize=((unsigned long)frameheader[4]<<24) | ((unsigned long)frameheader[5]<<16) | ((unsigned long)frameheader[6]<<8) | ((unsigned long)frameheader[7]);	//Convert to 4 byte integer
	if(framesize & 0x80808080)	//According to the ID3v2 specification, the MSB of each of the 4 bytes defining the tag size must be zero
		exit_wrapper(5);		//If this isn't the case, the size is invalid
	assert(framesize < 0x80000000);	//Redundant assert() to resolve a false positive with Coverity (this assertion will never be triggered because the above exit_wrapper() call would be triggered first)
	breakpos=breakpos + framesize + 10;	//Find the position that is one byte past the end of the SYLT frame

	if(Lyrics.verbose>=2)
		printf("SYLT frame info:\n\tFrame size is %lu bytes\n\tEnds after byte 0x%lX\n\tTimestamp format: %s\n\tLanguage: %c%c%c\n\tContent Type %d\n\tContent Descriptor: \"%s\"\n\n",framesize,breakpos-1,timestampformat == 1 ? "MPEG frames" : "Milliseconds",syltheader[1],syltheader[2],syltheader[3],syltheader[5],contentdescriptor != NULL ? contentdescriptor : "(none)");

	if(Lyrics.verbose)	(void) puts("Parsing SYLT frame:");

	free(contentdescriptor);	//Release this, it's not going to be used
	contentdescriptor=NULL;

//Load SYLT lyrics
	filepos=ftell_err(tag->fp);
	while(filepos < breakpos)	//While we haven't reached the end of the SYLT frame
	{
	//Load the lyric text
		string=ReadString(tag->fp,&length,0);	//Load SYLT lyric string, save the string length
		if(string == NULL)
		{
			(void) puts("Invalid SYLT lyric string\nAborting");
			exit_wrapper(6);
		}

	//Load the timestamp
		if(fread(timestamparray,4,1,tag->fp) != 1)	//Read timestamp
		{
			(void) puts("Error reading SYLT timestamp\nAborting");
			exit_wrapper(7);
		}

		filepos+=length+4;	//The number of bytes read from the input file during this iteration is the string length and the timestamp length

	//Process the timestamp as a 4 byte Big Endian integer
		timestamp=(unsigned long)((timestamparray[0]<<24) | (timestamparray[1]<<16) | (timestamparray[2]<<8) | timestamparray[3]);

		if(timestampformat == 1)	//If this timestamp is in MPEG frames instead of milliseconds
			timestamp=((double)timestamp * tag->frameduration + 0.5);	//Convert to milliseconds, rounding up

	//Perform line break logic
		assert(string != NULL);		//(check string for NULL again to satisfy cppcheck)
		if((string[0] == '\r') || (string[0] == '\n'))	//If this lyric begins with a newline or carriage return
		{
			EndLyricLine();		//End the lyric line before the lyric is added
			linebreaks=1;		//Track that line break character(s) were found in the lyrics
		}

		if(Lyrics.verbose >= 2)
		{
			string2=DuplicateString(string);		//Make a copy of the string for display purposes
			string2=TruncateString(string2,1);		//Remove leading/trailing whitespace, newline chars, etc.
			printf("Timestamp: 0x%X%X%X%X\t%lu %s\t\"%s\"\t%s",timestamparray[0],timestamparray[1],timestamparray[2],timestamparray[3],timestamp,(timestampformat == 1) ? "MPEG frames" : "Milliseconds",string2,(string[0]=='\n') ? "(newline)\n" : "\n");
			free(string2);
			string2=NULL;
		}

	//Perform grouping logic
		//Handle whitespace at the beginning of the parsed lyric piece as a signal that the piece will not group with previous piece
		if(isspace(string[0]))
			if(Lyrics.curline->pieces != NULL)	//If there was a previous lyric piece on this line
				Lyrics.lastpiece->groupswithnext=0;	//Ensure it is set to not group with this lyric piece

		if(isspace(string[strlen(string)-1]))	//If the lyric ends in a space
			groupswithnext=0;
		else
			groupswithnext=1;

		if(Lyrics.line_on == 0)		//Ensure that a line phrase is started
			CreateLyricLine();

	//Add lyric piece, during testing, I'll just write it with a duration of 1ms
		AddLyricPiece(string,timestamp,timestamp+1,PITCHLESS,groupswithnext);	//Write lyric with no defined pitch
		free(string);	//Free string

		if((Lyrics.lastpiece != NULL) && (Lyrics.lastpiece->prev != NULL) && (Lyrics.lastpiece->prev->groupswithnext))	//If this piece groups with the previous piece
			Lyrics.lastpiece->prev->duration=Lyrics.lastpiece->start-Lyrics.realoffset-Lyrics.lastpiece->prev->start;	//Extend previous piece's length to reach this piece, take the current offset into account
	}//While we haven't reached the end of the SYLT frame

//If the imported lyrics did not contain line breaks, they must be inserted manually
	if(!linebreaks && Lyrics.piececount)
	{
		if(Lyrics.verbose)	(void) puts("\nImported ID3 lyrics did not contain line breaks, adding...");
		ptr=Lyrics.lines->pieces;
		lineptr=Lyrics.lines;	//Point to first line of lyrics (should be the only line)

		assert_wrapper((lineptr != NULL) && (ptr != NULL));	//This shouldn't be possible if Lyrics.piececount is nonzero

		if(lineptr->next != NULL)	//If there is another line of lyrics defined
			return;					//abort the insertion of automatic line breaks

		while((ptr != NULL) && (ptr->next != NULL))	//For each lyric
		{
			ptr2=ptr->next;	//Store pointer to next lyric
			lineptr=InsertLyricLineBreak(lineptr,ptr2);	//Insert a line break between this lyric and the next, line conductor points to new line
			ptr=ptr2;		//lyric conductor points to the next lyric, which is at the beginning of the new line
		}
	}
}