/* * Copyright (c) 1983-2023 Trevor Wishart and Composers Desktop Project Ltd * http://www.trevorwishart.co.uk * http://www.composersdesktop.com * This file is part of the CDP System. The CDP System is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The CDP System is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the CDP System; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* * CREATE STANDARD MIDI FILE FROM CDP FRQ BRKPNT FILE FOR STABLE PITCHES (WHICH HAS NOTE STARTS AND ENDS) * * Assumes a single voice, using a sigle MIDI channel (0) * and note-off occurs before following note-on * * If "staccato" is set to 0, program reads note on and note off times. * If "staccato" > 0, note-offs are generated from note ons, and note-ends in input file are ignored. * * CALL WITH * convert_to_midi(seqfile,envfile,infiledatalen,staccato,outfilename,system); * * where infiledatalen is no of midi notes in input = 1/4 of entries in frq-brkpnt file. */ #include #include #include #include #include #include // RWD for macro defs //#if defined unix || defined __GNUC__ #define round(x) lround((x)) //#else //# ifndef round //static int round(double d); //static int twround(double d); //RWD //#define round(x) twround((x)) //RWD //# endif //#endif #define MIDI_NOTE_ON (unsigned char)144 /* 0x90 = 1001 0000 = 1001 is Note-on : 0000 is channel number */ #define MIDI_NOTE_OFF (unsigned char)128 /* 0x80 = 1000 0000 = 1001 is Note-on : 0000 is channel number */ #define LOW_A 6.875 /* Frequency of A below MIDI 0 */ #define CONVERT_LOG10_TO_LOG2 3.321928 /* MiditoHz conversions */ #define TRUE 1 #define FALSE 0 #define NEWLINE ('\n') #define ENDOFSTR ('\0') static void Write_ThisIsAMIDI_MThdChunk_with_mS_timings (char format_type,char track_cnt,FILE *fp); static void WriteTrackID (unsigned int midi_track_data_bytes,FILE *fp); static void WriteVarLen (unsigned int value,FILE *fp); static void WriteNormalLong (unsigned int value,FILE *fp); static void Write_TrackEnd (FILE *fp); static void create_note(double timestep,char midi,double thislevel,int on,FILE *fp); static char HzToMidi (double frq); static int get_float_from_within_string(char **str,double *val); static int midi_data_bytes_in_midi_track (int infiledatalen,double *seqdata,double staccato); static int CalcVarLen (unsigned int value); static void Write_ThisIsAMIDI_PC_header(unsigned int total_bytes_in_file,FILE *fp); #ifndef round static int round(double d); #endif int main(int argc,char *argv[]) { FILE *fp; double *seqdata, *envdata, *p, *end; char *q; double lasttime, thistime, nexttime, timestep, thislevel, stacdur; int on, padbyte; int n,m,k; unsigned int midi_track_data_bytes, total_bytes_in_file; char temp[200]; int system; char seqfile[64]; char envfile[64]; int infiledatalen; double staccato; char outfilename[64]; char thismidi; if(argc == 1) { fprintf(stdout,"Converts frqequency-brkpnt and peakdata textfiles to a standard midi data file.\n"); fprintf(stdout,"\n"); fprintf(stdout,"USAGE:\n"); fprintf(stdout,"convert_to_midi frqbrkpnt peakvals datalen staccato outfilename system\n"); fprintf(stdout,"\n"); fprintf(stdout,"FRQBRKPNT File of time-frq pairs for note starts and ends.\n"); fprintf(stdout,"PEAKVALS Levels of each note (Range 0-1).\n"); fprintf(stdout,"DATALEN Number of peaks (no. of vals in 'frqbrkpnt' should be datalen * 4)\n"); fprintf(stdout,"STACCATO Force staccato output.\n"); fprintf(stdout,"OUTFILENAME (File extension will be forced to '.mid' or '.rmi')\n"); fprintf(stdout,"SYSTEM 0 gives Standard Midi File, with extension '.mid'\n"); fprintf(stdout," 1 gives PC Midi File, with extension '.rmi'\n"); fflush(stdout); return 1; } if(argc != 7) { fprintf(stdout,"ERROR: WRONG NUMBER OF ARGUMENTS\n"); fflush(stdout); return 0; } strcpy(seqfile,argv[1]); strcpy(envfile,argv[2]); if(sscanf(argv[3],"%d",&infiledatalen)<1) { fprintf(stdout,"ERROR: CANNOT READ INFILE DATA LENGTH [%s].\n",argv[3]); fflush(stdout); return 0; } if(sscanf(argv[4],"%lf",&staccato)<1) { fprintf(stdout,"ERROR: CANNOT READ STACCATO DURATION [%s].\n",argv[4]); fflush(stdout); return 0; } strcpy(outfilename,argv[5]); q = outfilename + strlen(outfilename); q--; while(*q != '.') { q--; if(q <= outfilename) break; } if(q > outfilename) *q = ENDOFSTR; if(sscanf(argv[6],"%d",&system)<1) { fprintf(stdout,"ERROR: CANNOT READ HEADER TYPE [%s].\n",argv[4]); fflush(stdout); return 0; } if(system == 1) strcat(outfilename,".rmi"); else strcat(outfilename,".mid"); if((seqdata = (double *)malloc((infiledatalen * 4) * sizeof(double)))==NULL) { fprintf(stdout,"ERROR: INSUFFICIENT MEMORY TO CREATE INPUT DATA ARRAY.\n"); fflush(stdout); return 0; } if((envdata = (double *)malloc(infiledatalen * sizeof(double)))==NULL) { fprintf(stdout,"ERROR: INSUFFICIENT MEMORY TO CREATE LOUDNESS DATA ARRAY.\n"); fflush(stdout); return 0; } p = seqdata; if((fp = fopen(seqfile,"r"))==NULL) { fprintf(stdout,"ERROR: FAILED TO OPEN FILE %s TO READ SEQUENCE DATA\n",seqfile); fflush(stdout); return 0; } end = p + (infiledatalen * 4); while(fgets(temp,200,fp)!=NULL) { /* READ AND TEST MIDI DATA INFO, ASSUMING IT TO BE IN CORRECT FORMAT!! */ q = temp; while(get_float_from_within_string(&q,p)) { p++; if (p >= end) break; } } fclose(fp); if(p - seqdata != infiledatalen * 4) { fprintf(stdout,"ERROR: COUNT OF MIDI DATA ITEMS DOES NOT CORRESPOND TO PARAM FOR INPUT DATA LENGTH\n"); fflush(stdout); return 0; } p = envdata; end = p + infiledatalen; if((fp = fopen(envfile,"r"))==NULL) { fprintf(stdout,"ERROR: FAILED TO OPEN FILE %s TO READ LOUDNESS DATA\n",envfile); fflush(stdout); return 0; } while(fgets(temp,200,fp)!=NULL) { /* READ AND TEST MIDI DATA INFO, ASSUMING IT TO BE IN CORRECT FORMAT!! */ q = temp; while(get_float_from_within_string(&q,p)) { p++; if (p >= end) break; } } fclose(fp); if(p - envdata != infiledatalen) { fprintf(stdout,"ERROR: COUNT OF LOUDNESS ITEMS DOES NOT CORRESPOND TO PARAM FOR INPUT DATA LENGTH\n"); fflush(stdout); return 0; } if((fp = fopen(outfilename,"w"))==NULL) { fprintf(stdout,"ERROR: FAILED TO OPEN MIDI DATAFILE %s TO WRITE DATA\n",outfilename); fflush(stdout); return 0; } total_bytes_in_file = 14 /* MThd Chunk */ + 4 /*MTrk track identifier */ ; midi_track_data_bytes = midi_data_bytes_in_midi_track(infiledatalen,seqdata,staccato); total_bytes_in_file += CalcVarLen(midi_track_data_bytes) + midi_track_data_bytes; if((total_bytes_in_file/2)*2 != total_bytes_in_file) { /* Does (single) chunk have EVEN number of bytes? */ padbyte = 1; } else { padbyte = 0; } if (system == 1) total_bytes_in_file += padbyte; /* For PC, force an EVEN chunk-size */ switch(system) { case(0): break; case(1): /* PC */ Write_ThisIsAMIDI_PC_header(total_bytes_in_file,fp); break; } Write_ThisIsAMIDI_MThdChunk_with_mS_timings ((char)0,(char)1,fp); /* Assuming format type 0, 1 track only */ WriteTrackID (midi_track_data_bytes,fp); /* Assuming there is 1 track, numbered 0 */ lasttime = 0.0; on = 1; k = 0; for(n=0,m=1;n < infiledatalen * 4;n+=2,m+=2) { thistime = seqdata[n]; thismidi = (char)round(HzToMidi(seqdata[m])); timestep = thistime - lasttime; if (on) { thislevel = envdata[k++]; create_note(timestep,thismidi,thislevel,1,fp); lasttime = thistime; if (staccato > 0.0) { /* If staccato, note off determined by staccato dur */ if(m+4 < infiledatalen) { nexttime = seqdata[m+4]; stacdur = (nexttime - thistime)/2.0; stacdur = min(stacdur,staccato); } else { stacdur = staccato; } create_note(stacdur,thismidi,thislevel,0,fp); lasttime = thistime + stacdur; } on = 0; } else { if (staccato <= 0.0) { /* IF not staccato, note-off taken from input data */ create_note(timestep,thismidi,thislevel,0,fp); lasttime = thistime; /* But, if staccato, ignore the note off input */ } on = 1; } } Write_TrackEnd(fp); if(system == 1) { /* PC riff format */ if(padbyte) putc(0,fp); } fclose(fp); return 1; } /*********************** WRITE A 32-BIT REPRESENTATION OF "THIS is MIDI data = ascii MThd" ***********/ void Write_ThisIsAMIDI_MThdChunk_with_mS_timings (char format_type,char track_cnt,FILE *fp) { register char buffer; buffer = 77; /* M */ putc(buffer,fp); buffer = 84; /* T */ putc(buffer,fp); buffer = 104; /* h */ putc(buffer,fp); buffer = 100; /* d */ putc(buffer,fp); buffer = 0; /* sizeof MThd chunk is 6 = 0006 */ putc(buffer,fp); putc(buffer,fp); putc(buffer,fp); buffer = 6; /* sizeof MThd chunk is 6 = 0006 */ putc(buffer,fp); buffer = 0; /* format_type 00 0x */ putc(buffer,fp); buffer = format_type; putc(buffer,fp); buffer = 0; /* track_cnt 00 0x */ putc(buffer,fp); buffer = track_cnt; putc(buffer,fp); buffer = (char)-25; /* -25 means 25 frames per sec */ putc(buffer,fp); buffer = 40; /* 40 units per frame -> 1 unit per ms */ putc(buffer,fp); } /*********************** WRITE TRACK ID ***********/ void WriteTrackID (unsigned int midi_track_data_bytes,FILE *fp) { register char buffer; buffer = 77; /* M */ putc(buffer,fp); buffer = 84; /* T */ putc(buffer,fp); buffer = 114; /* r */ putc(buffer,fp); buffer = 107; /* k */ putc(buffer,fp); WriteVarLen(midi_track_data_bytes,fp); } /*********************** WRITE A 32-BIT int, SPLIT INTO 7-bit WORDS IN REVERSE BYTE ORDER = MIDI TIME REPRESENTATION ***********/ void WriteVarLen (unsigned int value,FILE *fp) { register unsigned int buffer; buffer = value & 0x7F; while (value >>= 7) { buffer <<= 8; buffer |= ((value & 0x7F) | 0x80); } for (;;) { putc(buffer,fp); if(buffer & 0x80) buffer >>= 8; else break; } } /*********************** WRITE A 32-BIT int IN NORMAL ORDER ***********/ void WriteNormalLong (unsigned int value,FILE *fp) { unsigned int buffer; int n = 0; while (n < 4) { buffer = value & 0xFF; putc(buffer,fp); value >>= 8; n++; } } /*********************** ASCERTAIN BYTE LENGTH OF MIDI TIME REPRESENTATION ***********/ int CalcVarLen (unsigned int value) { register unsigned int buffer; int cnt = 0; buffer = value & 0x7F; while (value >>= 7) { buffer <<= 8; buffer |= ((value & 0x7F) | 0x80); } for (;;) { cnt++; if(buffer & 0x80) buffer >>= 8; else break; } return cnt; } /*********************** WRITE "END OF TRACK" ***********/ void Write_TrackEnd (FILE *fp) { register char buffer; buffer = (char)-1; /* FF = |1111|1111| = -128 */ putc(buffer,fp); buffer = (char)0x2F; putc(buffer,fp); buffer = 0; putc(buffer,fp); } /*********************** CREATE MIDI NOTE ON OR NOTE OFF DATA ***********/ void create_note(double timestep,char midi,double thislevel,int on,FILE *fp) { register unsigned char buffer; unsigned int timedata; char velocity; timedata = (unsigned int)round(timestep * 1000.0); /* TIME IN mS */ velocity = (char)round(thislevel * 127.0); WriteVarLen(timedata,fp); if (on) buffer = MIDI_NOTE_ON; else buffer = MIDI_NOTE_OFF; putc(buffer,fp); buffer = midi; putc(buffer,fp); buffer = velocity; putc(buffer,fp); } /*********************** CONVERT FRQ TO MIDI ***********/ char HzToMidi (double frq) { double dmidi; char midi; if (frq < LOW_A) { frq = LOW_A; } dmidi = frq / LOW_A; dmidi = (log10(dmidi) * CONVERT_LOG10_TO_LOG2 * 12.0) - 3.0; midi = (char)round(dmidi); if (midi > 127) { midi = 127; } else if(midi < 0) { midi = 0; } return midi; } /************************** GET_FLOAT_FROM_WITHIN_STRING ************************** * takes a pointer TO A POINTER to a string. If it succeeds in finding * a float it returns the float value (*val), and it's new position in the * string (*str). */ int get_float_from_within_string(char **str,double *val) { char *p, *valstart; int decimal_point_cnt = 0, has_digits = 0; p = *str; while(isspace(*p)) p++; valstart = p; switch(*p) { case('-'): break; case('.'): decimal_point_cnt=1; break; default: if(!isdigit(*p)) return(FALSE); has_digits = TRUE; break; } p++; while(!isspace(*p) && *p!=NEWLINE && *p!=ENDOFSTR) { if(isdigit(*p)) has_digits = TRUE; else if(*p == '.') { if(++decimal_point_cnt>1) return(FALSE); } else return(FALSE); p++; } if(!has_digits || sscanf(valstart,"%lf",val)!=1) return(FALSE); *str = p; return(TRUE); } /*********************** CALCULATE NUMBER OF DATA BYTES TO FOLLOW ***********/ int midi_data_bytes_in_midi_track(int infiledatalen,double *seqdata,double staccato) { double thistime, nexttime, timestep, stacdur; double lasttime = 0.0; unsigned int timedata; unsigned int cnt = 0; int on = 1; int n,m; for(n=0,m=1;n < infiledatalen * 4;n+=2,m+=2) { thistime = seqdata[n]; timestep = thistime - lasttime; lasttime = thistime; if (on) { timedata = (unsigned int)round(timestep * 1000.0); /* TIME IN mS */ cnt += CalcVarLen(timedata); cnt += 3; /* MIDI status + key + velocity */ if (staccato > 0.0) { if(m+4 < infiledatalen) { nexttime = seqdata[m+4]; stacdur = (nexttime - thistime)/2.0; stacdur = min(stacdur,staccato); } else { stacdur = staccato; } timedata = (unsigned int)round(stacdur * 1000.0); cnt += CalcVarLen(timedata); cnt += 3; lasttime = thistime + stacdur; } on = 0; } else { if (staccato <= 0.0) { timedata = (unsigned int)round(timestep * 1000.0); cnt += CalcVarLen(timedata); cnt += 3; lasttime = thistime; } on = 1; } } cnt += 3; /* Add Track end bytes */ return cnt; } void Write_ThisIsAMIDI_PC_header(unsigned int total_bytes_in_file,FILE *fp) { register char buffer; buffer = 82; /* R */ putc(buffer,fp); buffer = 73; /* I */ putc(buffer,fp); buffer = 70; /* F */ putc(buffer,fp); buffer = 70; /* F */ putc(buffer,fp); WriteNormalLong(total_bytes_in_file,fp); buffer = 82; /* R */ putc(buffer,fp); buffer = 77; /* M */ putc(buffer,fp); buffer = 73; /* I */ putc(buffer,fp); buffer = 68; /* D */ putc(buffer,fp); } //#ifndef round #if 0 static int twround(double d) { d += 0.5; return (int)floor(d); } #endif //endif