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'; }
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); }
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); }
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; }
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; }
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; }
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 }
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 } } }