Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Any C programmers wanna help convert code to convert between MIDI + CSV?
#1
I was looking to have QB64 convert MIDI files to CSV and vice-versa and found some C code that looks like it does the job.

It's just 2 files (asc2mid.c and mid2asc.c) and I figure regular C code with no fancy OO, it should be pretty straightforward to convert to BASIC, right? I did a little C back (waaaay back) in school (never mind that I hated it!) and did some standard JavaScript back in the day, so I should at least be familiar with the syntax. Should be a snap, right?
NOPE!

I feel like I'm reading Greek. There's all sorts of weird stuff going on - variables named with asterisks (pointers?), if/then and loops without begin/end braces, and even GOTO statements, not to mention a lot of statements strung all together on one line. This is painful. I ran the code through a beautifier which sort of helped, but it also did things like put spaces before/after the asterisks which might be part of pointer variable names or whatever they are. I then went through the code line by line to try and make sense of the control structure and move everything inside of begin/end {} and pretty it up. Results are below - but I am not sure what errors I may have introduced by doing this stuff.

Also I don't have a C compiler to test this, though it might come in handy later if I ever hope to get this Raw Input API multi-mouse code working or experiment with writing WAV files from scratch. Which leads me to a second question - QB64 comes with a C compiler "bundled" with it, right? Is there some way to use it to compile straight C programs? 

Is anyone interested in helping make sense of some C code?  :-D

Either way, I'll slog through it eventually, just not as fast. 

Here is the original code, the attached ZIP also includes my attempt at prettifying it... 



"mid2asc.c"
Code: (Select All)
/*
From: http://www.archduke.org/midi/

mid2asc:
Converts midi to ascii. Output can be in one of several formats
determined by command line flags.

Time of an event can be determined by one of
   (i) BA+CR:               Bar number+Crotchet within bar
  (ii) CR:                  Crotchet number from the very start
(iii) DT:                  Delta time from previous event - native midi format
  (iv) FOL[+/-crotchets]:   Time from end of previous note/rest on that track
   (v) SIM[+crotchets]:     Time from start of previous note/rest on that track

  Basic complication of mode (i) is that the Time signature affects
  the interpretation of time, and that time signature can change at
  any time on any track and should affect all tracks.

  Could make it closer to the spec. (e.g., skip over unknown chunk types)
 
  Midi files are going to be ambiguous, e.g. order of some events
  occuring at the same time, or whether choose to use running st mode,
  or whether to choose to use 0x8* or 0x9* for voff when
  velocity=0x40.

  Could print key changes from other tracks as comments in sep mode

  Copyright November 2002 A.P.Selby

  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  associated documentation files (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge, publish, distribute,
  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all copies or
  substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
  NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <string.h>

#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)>(y)?(x):(y))

#define MAXLL 10000
#define MAXCH 16
#define MAXNT 128
unsigned char *dat;
int p,siz,pr=0,raw,cro,fol,sep,frpad=9;
int bgn[12][2]={{0,0},{0,1},{1,0},{2,-1},{2,0},{3,0},{3,1},{4,0},{4,1},{5,0},{6,-1},{6,0}};
/* ^ best guess note from pitch relative to keynote: (In C maj/A min this would be: C C# D Eb E F F# G G# A Bb B) */
int maj[14]={0,2,4,5,7,9,11, 12,14,16,17,19,21,23};

void err(char *l){fprintf(stderr,"Error: %s\n",l);exit(1);}
#define assrt(x,y) if(!(x))err(y);

char *note2(int n,int s,int pad){
  static char buf[100][20];
  static int p=0;
  int i;
  p=(p+1)%100;
  buf[p][0]="CDEFGAB"[(n+700000)%7];
  i=1;
  while(s>0){assert(i<19);buf[p][i++]='#';s--;}
  while(s<0){assert(i<19);buf[p][i++]='b';s++;}
  while(n>=7){assert(i<19);buf[p][i++]='\'';n-=7;}
  while(n<0){assert(i<19);buf[p][i++]='-';n+=7;}
  if(pad)while(i<pad&&i<19)buf[p][i++]=' ';
  buf[p][i]=0;
  return buf[p];
}
char *note(int n,int sf,int mi,int pad){
  int a,b,m,a0,b0;
  assert(n>=0&&n<128&&sf>=-128&&sf<=127);
  a0=(4*sf+7000)%7;b0=(sf+7001)/7-1000;/* (a0,b0) = keynote of (sf,mi) in pair form */
  n-=60+maj[a0]+b0;/* subtract keynote in single pitch form */
  m=(n+120000)%12;a=bgn[m][0];b=bgn[m][1];/* look up most likely pair form */
  return note2(a0+a+((n-m)/12)*7,maj[a0]+maj[a]-maj[a0+a]+b0+b,pad);/* evaluate cocycle! */
}
char *key(int sf,int mi,int pad){
  static char buf[100][21];
  static int p=0;
  int i,n;
  p=(p+1)%100;
  assert(sf>=-128&&sf<=127);
  if(mi)sf+=3;
  buf[p][0]="FCGDAEB"[(sf+7001)%7];
  n=(sf+7001)/7-1000;i=1;
  while(n>0){buf[p][i++]='#';n--;}
  while(n<0){buf[p][i++]='b';n++;}
  while(i<pad&&i<20)buf[p][i++]=' ';
  buf[p][i]=0;
  return buf[p];
}
int gcd(int a,int b){if(b==0)return a; else return gcd(b,a%b);}
int sprfrac(char *l,int n,int d,int sum,int pad){
  int a,g,s;
  s=0;
  if(sum){
    a=n/d;if(a){
      s=sprintf(l,"%d",a);n-=a*d;
      if(n==0)goto pad;
      s+=sprintf(l+s,"+");
    }
  }
  g=gcd(n,d);n/=g;d/=g;
  if(d!=1)s+=sprintf(l+s,"%d/%d",n,d); else s+=sprintf(l+s,"%d",n);
pad:
  if(s<pad){memmove(l+pad-s,l,s+1);memset(l,' ',pad-s);s=pad;}
  return s;
}
int sprfrac2(char *li,int n,int d){
  /* Prints the simplest (or near simplest) number x such that (int)(n/x+.5)=d */
  int i,t,dp;
  double m,r,x0,x1;
  char l[100],*l0,*l1;
  if(li)l0=li; else l0=l;
  x0=n/(d+.49);x1=n/(d-.49);l1=l0;
  /* Find minimum dp such that [x0,x1] intersects Z*10^(-dp) */
  for(dp=0,m=1;dp<15;dp++){
    r=floor(x1*m);
    if(r>x0*m){
      t=(int)(r/m);
      l1+=sprintf(l1,"%d",t);r-=t*m;
      if(dp){
    l1+=sprintf(l1,".");
    for(i=0;i<dp;i++){m/=10;t=(int)(r/m);l1+=sprintf(l1,"%d",t);r-=t*m;}
      }
      if((int)(n/atof(l0)+.5)!=d){fprintf(stderr,"Error 2 in prfrac2 n=%d d=%d\n",n,d);exit(1);}
      if(li)return l1-l0; else return printf("%s",l);
    }
    m*=10;
  }
  fprintf(stderr,"Error 1 in prfrac2 n=%d d=%d\n",n,d);exit(1);
}

void need(int n,char *l){
  if(p+n>siz){fprintf(stderr,"Unexpected EOF in %s, p=&%06x n=%d siz=&%06x\n",l,p,n,siz);exit(1);}
}
void skip(int n){need(n,"skip");p+=n;}
void match(char *l){
  int i;
  need(strlen(l),"match");
  for(i=0;l[i];i++)if(l[i]!=dat[p++]){
    fprintf(stderr,"Bad match, p=%X i=%d l=%s &%02X!=&%02X\n",p,i,l,l[i],dat[p-1]);exit(1);
  }
}
int getn(int n,int a){
  int i,t;
  need(n,"getn");
  for(i=0,t=0;i<n;i++)t=(t<<8)+dat[p+i];
  if(a)p+=n;
  return t;
}
int get1(){return getn(1,1);}
int get2(){return getn(2,1);}
int get3(){return getn(3,1);}
int get4(){return getn(4,1);}
int look1(){return getn(1,0);}
int getv(){
  int c,t;
  t=0;
  do{
    need(1,"getv");c=dat[p++];t=(t<<7)+(c&127);
  }while(c&128);
  return t;
}
int getnnodt(int st,int e,int *st1){
  /* Returns the dt to next non-noteoff event (raw=0), or next event (raw=1). return=-1 means end of track. */
  int t,p0,dt,sth;
  dt=0;
  while(p<e){
    dt+=getv();*st1=st;if(raw)return dt;
    p0=p;
    t=look1();if(t>=0x80)st=get1(); else assert(st>=0x80);
    sth=st>>4;
    if(sth==8||(sth==9&&dat[p+1]==0))skip(2); else {p=p0;return dt;}
  }
  return -1;
}

typedef struct {int tt,ch,nt,voff;} noteoffbuf;
int cmp(const void*x,const void*y){return ((noteoffbuf*)x)->tt-((noteoffbuf*)y)->tt;}

int main(int ac,char **av){
  int i,s,t,u,bf,bn,btt,fol1,rest,ch,dt,infinity,ln,mi,maxno,nat,non,ols,prb,help,
    sk,sf,st,sth,tis0,tis1,tis2,tpc,tr,trl,tt,ty,alltrfl,format,tracks,division,tempo,nobp[MAXCH][MAXNT];
  char l[MAXLL],*l1;
  double ert;
  typedef struct {int a,p0,l,e,p,pv,st,dt,tt,et,pt;} trackinfo;
  typedef struct fred {int all,bn,tr;char *l;struct fred *nx;} outline;
  trackinfo *tri;
  noteoffbuf *nob;
  outline *outbuf0,*outbuf;
  FILE *fpi;

  raw=cro=fol=sep=help=0;
  for(i=1;i<ac;i++){
    if(!strncmp(av[i],"-c",2))cro=1;
    if(!strncmp(av[i],"-f",2))fol=1;
    if(!strncmp(av[i],"-r",2))raw=1;
    if(!strncmp(av[i],"-s",2))sep=1;
    if(!strncmp(av[i],"-h",2)||!strncmp(av[i],"--help",6))help=1;
  }
  if(ac<2||help){
    fprintf(stderr,"Usage: mid2asc  [-c] [-f,-r] [-s] midifile > textfile\n\n");
    fprintf(stderr,"   Default timing is to use absolute time determined by BA+CR\n");
    fprintf(stderr,"   and use durations for notes.\n");
    fprintf(stderr,"-c Use absolute crotchets rather than BA+CR to determine time.\n");
    fprintf(stderr,"   Useful if inserting or deleting.\n");
    fprintf(stderr,"-f 'Follow' (take time from start or end of previous note) to determine time.\n");
    fprintf(stderr,"-r \"raw\" format: time determined by DT, and note-off events\n");
    fprintf(stderr,"   are used instead of note durations.\n\n");
    fprintf(stderr,"   Default is to print all tracks merged in chronological order.\n");
    fprintf(stderr,"-s Print all tracks separately.\n");
    exit(1);
  }
  if(fol&&raw){fprintf(stderr,"fol and raw modes cannot be combined\n");exit(1);}
  fpi=fopen(av[ac-1],"rb");if(!fpi){fprintf(stderr,"Couldn't open %s\n",av[ac-1]);exit(1);}
  fseek(fpi,0,SEEK_END);siz=ftell(fpi);rewind(fpi);
  if(pr>=1)printf("# File size %d byte%s\n",siz,siz==1?"":"s");
  dat=(unsigned char*)malloc(siz);if(!dat){fprintf(stderr,"Couldn't malloc %d bytes\n",siz);exit(1);}
  fread(dat,1,siz,fpi);p=0;fclose(fpi);
  maxno=siz/2;
  match("MThd");
  assert(get4()==6);
  format=get2();tracks=get2();division=get2();
  printf("format=%d tracks=%d division=%d\n",format,tracks,division);
  if(division&(1<<15))tpc=48; else tpc=division;
  tri=(trackinfo *)malloc(tracks*sizeof(trackinfo));assert(tri);
  nob=(noteoffbuf *)malloc(maxno*sizeof(noteoffbuf));assert(nob);
  infinity=0x7fffffff;

  for(tr=0;p<siz&&tr<tracks;tr++){
    match("MTrk");trl=get4();
    tri[tr].p0=p;tri[tr].l=trl;tri[tr].e=tri[tr].p0+tri[tr].l;
    p+=trl;
  }

  non=0;/* outside 'if' statement to stop warning */
  if(!raw){
    for(tr=0;tr<tracks;tr++){
      st=0;tt=0;p=tri[tr].p0;
      while(p<tri[tr].e){
    dt=getv();tt+=dt;
    t=look1();if(t>=0x80)st=get1(); else assert(st>=0x80);
    sth=st>>4;ch=st&15;
    if(sth==8||sth==9){
      t=get1();u=get1();
      if(sth==9){if(u)continue; else u=0x40;}
      assert(non<maxno);nob[non].tt=tt;nob[non].ch=ch;nob[non].nt=t;nob[non].voff=u;non++;
      continue;
    }
    if(sth==0xC||sth==0xD){skip(1);continue;}
    if(sth==0xF){
      if(st==0xF0||st==0xF7){skip(getv());continue;}
      if(st==0xFF){skip(1);skip(getv());continue;}
      fprintf(stderr,"Unrecognised status %02x\n",st);exit(1);
    }
    skip(2);
      }
    }
    qsort(nob,non,sizeof(noteoffbuf),cmp);
    for(ch=0;ch<MAXCH;ch++)for(t=0;t<MAXNT;t++)nobp[ch][t]=0;
  }

  nat=0;
  for(tr=0;tr<tracks;tr++){
    p=tri[tr].p=tri[tr].p0;
    t=getnnodt(0,tri[tr].e,&tri[tr].st);tri[tr].a=(t>=0);
    if(tri[tr].a){
      tri[tr].et=tri[tr].pt=0;tri[tr].dt=tri[tr].tt=t;tri[tr].p=p;tri[tr].pv=-1;nat++;
    }
  }
  sf=0;mi=0;/* Initial key C major */
  tis0=4;tis1=4;tis2=(tis0*4*tpc)/tis1;/* Initial time signature 4/4 */
  tempo=500000;/* Default usec/beat (= 120 bpm) */
  btt=0;bn=0;bf=0;ert=0;/* at time btt, bar number was bn (from 0), and part of bar complete was bf/tpc crotchets */
  prb=-1;
  ols=MAXLL*10;outbuf=outbuf0=(outline*)malloc(ols);outbuf->nx=0;
  if(!outbuf){fprintf(stderr,"Couldn't malloc %d bytes (outbuf1)\n",ols);exit(1);}
  while(nat>0){
    tt=infinity;tr=-1;alltrfl=0;
    for(i=0;i<tracks;i++)if(tri[i].a){
      t=tri[i].tt;if(fol&&tri[i].et<t)t=tri[i].et;
      if(t<tt){tr=i;tt=t;}
    }
    assert(tr>=0);
    p=tri[tr].p;t=look1();if(t>=0x80)st=get1(); else st=tri[tr].st;
    sth=st>>4;ch=st&15;
    assert(!(raw==0&&(sth==8||(sth==9&&dat[p+1]==0))));
    rest=(fol&&tri[tr].et<tri[tr].tt);
    fol1=(fol&&tri[tr].et<=tri[tr].tt);
    l1=l;
    if(tt>btt){
      ert+=(double)(tt-btt)*(double)tempo/(double)tpc;
      /* Elapsed crotchets=(btt-tt)/tpc, ticks/bar=tis2 */
      t=tt-btt+bf; bn+=t/tis2; bf=t%tis2; btt=tt;
    }
    if(!sep&&bn!=prb){l1+=sprintf(l1,"\n");prb=bn;}
    if(fol){
      u=0;
      if(fol1)l1+=sprintf(l1,"FOL"); else {
    l1+=sprintf(l1,"SIM");t=tri[tr].tt-tri[tr].pt;if(t){
      assert(t>0);l1+=sprintf(l1,"+");u=sprfrac(l1,t,tpc,1,0);l1+=u;u++;
    }
      }
      while(u<frpad+1){*(l1++)=' ';u++;}
      l1+=sprintf(l1," (");
    }
    if(raw){l1+=sprintf(l1,"DT ");l1+=sprfrac(l1,tri[tr].dt,tpc,1,frpad);l1+=sprintf(l1,"   (");}
    if(cro){l1+=sprintf(l1,"CR ");l1+=sprfrac(l1,tt,tpc,1,4+frpad);l1+=sprintf(l1,"   (");}
    l1+=sprintf(l1,"BA %4d   CR ",bn+1);l1+=sprfrac(l1,bf,tpc,1,frpad);
    if(cro)l1+=sprintf(l1,")");
    if(raw)l1+=sprintf(l1,")");
    if(fol)l1+=sprintf(l1,")");
    l1+=sprintf(l1,"   TR %2d   CH %2d   ",tr,ch+1);
    if(rest){
      l1+=sprintf(l1,"NT  R       ");l1+=sprfrac(l1,tri[tr].tt-tt,tpc,1,frpad);l1+=sprintf(l1,"\n");
      tri[tr].pt=tt;tri[tr].et=tri[tr].tt;goto bp2;
    }
    if(sth==8||sth==9){
      t=get1();u=get1();
      l1+=sprintf(l1,"NT  %s ",note(t,sf,mi,7));
      if(sth==8||(sth==9&&u==0)){
    assert(raw);
    l1+=sprintf(l1,"off");
    if(sth==8&&u!=0x40)l1+=sprintf(l1,"   voff=%d",u);
    l1+=sprintf(l1,"\n");
    goto bp1;
      }
      if(raw)l1+=sprintf(l1,"on"); else {
    assert(ch<MAXCH&&t<MAXNT);
    for(i=nobp[ch][t];i<non;i++)if(nob[i].tt>tt&&nob[i].ch==ch&&nob[i].nt==t)break;
    nobp[ch][t]=i;
    if(i<non){
      l1+=sprfrac(l1,nob[i].tt-tt,tpc,1,frpad);
      tri[tr].pt=tt;tri[tr].et=nob[i].tt;
    }else l1+=sprintf(l1,"infinity");
      }
      if(u!=tri[tr].pv){l1+=sprintf(l1,"   von=%d",u);tri[tr].pv=u;}
      if(!raw&&i<non&&nob[i].voff!=0x40)l1+=sprintf(l1,"   voff=%d",nob[i].voff);
      l1+=sprintf(l1,"\n");
      if(!raw&&i==non)l1+=sprintf(l1,"# Warning tr=%d p=&%X, note never turned off\n",tr,p);
      goto bp1;
    }
    if(sth==0xB){
      t=look1();if(t==7){
    t=get1();u=get1();
    l1+=sprintf(l1,"Channel volume %d\n",u);goto bp1;
      }
    }
    if(sth==0xC){t=get1();l1+=sprintf(l1,"Instrument %d\n",t+1);goto bp1;}
    if(sth==0xF){
      if(st==0xF0||st==0xF7){
    t=getv();l1+=sprintf(l1,"Sysex event &%02X ",st);
    for(i=0;i<t;i++)l1+=sprintf(l1," &%02X",dat[p++]);l1+=sprintf(l1,"\n");
    goto bp1;
      }
      if(st==0xFF){
    ty=get1();ln=getv();need(ln,"Meta");
    if(ty>=1&&ty<=7){
      l1+=sprintf(l1,"Text type %d: \"",ty);
      for(i=0;i<ln;i++){
        t=dat[p++];switch(t){
        case '\t':l1+=sprintf(l1,"\\t");break;
        case '\n':l1+=sprintf(l1,"\\n");break;
        case '\v':l1+=sprintf(l1,"\\v");break;
        case '\f':l1+=sprintf(l1,"\\f");break;
        case '\r':l1+=sprintf(l1,"\\r");break;
        case '\\':l1+=sprintf(l1,"\\\\");break;
        default:if(t!=0)l1+=sprintf(l1,"%c",t);break;
        }
      }
      l1+=sprintf(l1,"\"\n");
      goto bp1;
    }
    if(ty==0x2F){
      assert(ln==0);
      l1+=sprintf(l1,"End of track\n");goto bp1;
    }
    if(ty==0x51){
      assert(ln==3);tempo=get3();
      l1+=sprintf(l1,"Tempo ");l1+=sprfrac2(l1,60*1000000,tempo);l1+=sprintf(l1,"\n");
      alltrfl=1;
      goto bp1;
    }
    if(ty==0x58){
      assert(ln==4);assert(tt==btt);
      tis0=dat[p];tis1=1<<dat[p+1];assrt((tis0*4*tpc)%tis1==0,"New time signature not commensurate with division");
      tis2=(tis0*4*tpc)/tis1;
      l1+=sprintf(l1,"Time signature %d/%d, clocks/mtick %d, crotchets/32ndnote %d\n",tis0,tis1,dat[p+2],dat[p+3]);
      alltrfl=1;
      p+=ln;goto bp1;
    }
    if(ty==0x59){
      assert(ln==2);
      sf=get1();if(sf>=128)sf-=256;mi=get1();
      l1+=sprintf(l1,"Key %s %s\n",key(sf,mi,0),mi?"minor":"major");
      alltrfl=1;
      goto bp1;
    }
    l1+=sprintf(l1,"Meta Event   type &%02X  ",ty);
    for(i=0;i<ln;i++)l1+=sprintf(l1," %d",dat[p++]);l1+=sprintf(l1,"\n");
    goto bp1;
      }
      assert(0);
    }
    if(sth==0xD)sk=1; else sk=2;
    l1+=sprintf(l1,"ST &%02X",st);
    for(i=0;i<sk;i++)l1+=sprintf(l1," &%02X",dat[p++]);l1+=sprintf(l1,"\n");
  bp1:
    tri[tr].dt=getnnodt(st,tri[tr].e,&tri[tr].st);
    if(tri[tr].dt>=0){tri[tr].tt+=tri[tr].dt;tri[tr].p=p;} else {tri[tr].a=0;nat--;}
  bp2:
    if(sep){
      s=l1-l+1;t=sizeof(outline);u=(s+t-1)/t;
      assert((u+1)*t<=MAXLL&&ols>=MAXLL);
      outbuf->tr=tr;outbuf->all=alltrfl;outbuf->bn=bn;outbuf->l=(char*)(outbuf+1);
      if(ols-(u+1)*t<MAXLL){
    ols=MAXLL*10;outbuf->nx=(outline*)malloc(ols);
    if(!outbuf->nx){fprintf(stderr,"Couldn't malloc %d bytes (outbuf2)\n",ols);exit(1);}
      } else {
    outbuf->nx=outbuf+u+1;ols-=(u+1)*t;
      }
      memcpy(outbuf->l,l,s);outbuf=outbuf->nx;outbuf->nx=0;
    } else printf("%s",l);
  } /* main loop */

  if(sep)for(tr=0;tr<tracks;tr++){
    printf("# TRACK %d\n",tr);
    prb=-1;
    for(outbuf=outbuf0;outbuf->nx;outbuf=outbuf->nx){
      if((outbuf->all||outbuf->tr==tr)&&outbuf->bn!=prb){printf("\n");prb=outbuf->bn;}
      if(outbuf->all&&outbuf->tr!=tr)printf("# ");
      if(outbuf->all||outbuf->tr==tr)printf("%s",outbuf->l);
    }
    printf("\n");
  }
 
  printf("# Successfully parsed\n");
  printf("# Duration = %g seconds\n",ert/1000000);
  return 0;
}



"asc2mid.c"
Code: (Select All)
/*
From: http://www.archduke.org/midi/

asc2mid:
Converts ascii to midi. Ascii is in format output by mid2asc in one
of several modes, or it can be in a more mixed format.

Time of an event can be determined by one of
   (i) BA+CR:               Bar number+Crotchet within bar
  (ii) CR:                  Crotchet number from the very start
(iii) DT:                  Delta time from previous event - native midi format
  (iv) FOL[+/-crotchets]:   Time from end of previous note/rest on that track
   (v) SIM[+crotchets]:     Time from start of previous note/rest on that track

  Basic complication of mode (i) is that the Time signature affects
  the interpretation of time, and that time signature can change at
  any time on any track and should affect all tracks.

  At the moment it requires lines of each track to be in chronological
  order which can make it slightly awkward to change the track of a
  note in sep=1 mode. If all file were in mode (ii), then it could
  sort it out for you, I suppose. Or the user could sort the lines
  first.

  Should make more case insensitive?

  MIT License

  Copyright November 2002 A.P.Selby

  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  associated documentation files (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge, publish, distribute,
  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all copies or
  substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
  NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>

#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MAXNOB 10000

char *inbuf,*inend,*ip;
int tr,infinity;
int maj[14]={0,2,4,5,7,9,11, 12,14,16,17,19,21,23};
typedef struct {int l,st,von,bt,pt,et,sta,bn,cr,tt,ln,ip,op;unsigned char *out;} trackinfo;
trackinfo *tri;
/*
  trackinfo:
  st = midi state (state of previous midi event laid down)
  bt = time of last event laid down
  pt = start time of last note or rest
  et = end time of last note or rest
  sta = track state
     state 0 ==> Need to read time of new line
     state 1 ==> Time of next line specified by tt
     state 2 ==> Time of next line specified by bn,cr; tt is valid if there is no time sig. change before it
     state 3 ==> Track at an end - no more lines on this track
  bn,cr,tt time of next event defined by (bn,cr) or tt according to state
  ip = integer input pointer (inbuf+.ip is actual pointer)
  ln = input line number
  out = pointer to start of output buffer (can change when gets resized)
  op = integer output pointer (.out+.op is actual pointer)
  l = allocated size of output buffer
  von = volume of last note-on event
*/

void err(char *l){
  if(tr==-1)fprintf(stderr,"Error: %s\n",l); else fprintf(stderr,"Error: %s at line %d:\n%s\n",l,tri[tr].ln,ip);
  exit(1);
}
#define assrt(x,y) if(!(x))err(y);

char *getns(char *l){while(*l==' ')l++; return l;}

int strtoi(char *l,char **e){
  int b,n;
  char *l1,*l2;
  l1=l;while(*l1!=0&&*l1!='&'&&!isdigit(*l1)&&strncmp(l1,"infinity",8))l1++;
  if(strncmp(l1,"infinity",8)==0)return infinity;
  if(*l1=='&'){l1++;b=16;} else if(*l1=='0'&&l1[1]=='x'){l1+=2;b=16;} else b=10;
  n=strtol(l1,&l2,b);
  if(e){if(l2==l1)*e=l; else *e=l2;}
  return n;
}

/*
int getfrn(char *l,int d){
  int a,b,c;
  char *l1;
  a=strtoi(l,&l1);
  if(l1[0]=='+')b=strtoi(l1+1,&l1); else {b=a;a=0;}
  if(a==infinity||b==infinity)return infinity;
  if(l1[0]=='/')c=strtoi(l1+1,0); else c=1;
  b+=a*c;
  assrt(d%c==0,"fraction not allowed");
  return b*(d/c);
}
*/
int getfrn(char *l,int d){
  int a,b,t;
  char *l1;
  t=0;
  do{
    a=strtoi(l,&l1);if(l1==l)break;
    if(a==infinity)return infinity;
    if(l1[0]=='/')b=strtoi(l1+1,&l1); else b=1;
    a*=d;assrt(a%b==0,"fraction not allowed");t+=a/b;
    l=l1;
  }while(l[0]=='+');
  return t;
}

int strton(char *l,char **e){
  int n;
  char *l1,*l2;
  while(*l==' ')l++;
  l1="CDEFGABR";l2=strchr(l1,toupper(*l));assrt(l2,"unrecognised note");
  if(l2-l1==7){*e=l+1;return -1;}
  n=60+maj[l2-l1];l++;
  while(*l=='#'){n++;l++;}
  while(*l=='b'){n--;l++;}
  while(*l=='\''){n+=12;l++;}
  while(*l=='-'){n-=12;l++;}
  assrt(n>=0&&n<128,"note out of range");
  *e=l;return n;
}

void acc(int p){
  int nl;
  unsigned char *new;
  if(p<=tri[tr].l)return;
  if(tri[tr].l==0)nl=10240; else nl=tri[tr].l*2;
  if(nl<p)nl=p;
  new=(unsigned char *)malloc(nl);if(!new)err("Couldn't malloc output track space");
  if(tri[tr].out){memcpy(new,tri[tr].out,tri[tr].op);free(tri[tr].out);}
  tri[tr].out=new;tri[tr].l=nl;
}
void putn(int n,int t){
  acc(tri[tr].op+n);
  while(n>0)tri[tr].out[tri[tr].op++]=(t>>(--n)*8)&255;
}
void put1(int t){putn(1,t);}
void put2(int t){putn(2,t);}
void put3(int t){putn(3,t);}
void put4(int t){putn(4,t);}
void putv(int t){
  int n,u;
  assrt(t>=0&&t<0x10000000,"variable number out of range (event out of sequence?)");
  for(n=1,u=t;u>=128;n++)u>>=7;
  acc(tri[tr].op+n);
  while(n>0)tri[tr].out[tri[tr].op++]=((t>>(--n)*7)&127)|128;
  tri[tr].out[tri[tr].op-1]&=~128;
}
void putstn(char *l,int s){acc(tri[tr].op+s);memcpy(tri[tr].out+tri[tr].op,l,s);tri[tr].op+=s;}
void putst(char *l){putstn(l,strlen(l));}

char *getl(int tr){
  char *l;
  l=inbuf+tri[tr].ip;if(tri[tr].ln>0)l+=strlen(l)+1;
  tri[tr].ln++;
  while(l<inend&&(l[0]==0||l[0]=='#'||strncmp(l,"format=",7)==0)){l+=strlen(l)+1;tri[tr].ln++;}
  tri[tr].ip=l-inbuf;
  if(l<inend)return l; else return 0;
}

typedef struct {int tr,tt,ch,nt,voff;} noteoffbuf;
int main(int ac,char **av){
  int d,i,n,s,t,cr,bn,ch,nxt,mi,tr0,non,sf,st,rest,tis0,tis1,tis2,tpc,text,tt,siz,format,tracks,division;
  char *l1,*l2,*l3;
  noteoffbuf *nob;
  FILE *fpi;

  if(ac<2){
    fprintf(stderr,"Usage: asc2mid textfile > midifile\n\n");
    fprintf(stderr," Time of an event can be determined by one of\n");
    fprintf(stderr,"   (i) BA+CR:               Bar number+Crotchet within bar\n");
    fprintf(stderr,"  (ii) CR:                  Crotchet number from the very start\n");
    fprintf(stderr," (iii) DT:                  Delta time from previous event - native midi format\n");
    fprintf(stderr,"  (iv) FOL[+/-crotchets]:   Time from end of previous note/rest\n");
    fprintf(stderr,"   (v) SIM[+crotchets]:     Time from start of previous note/rest\n");
    exit(1);
  }
  fpi=fopen(av[1],"r");if(!fpi){fprintf(stderr,"Couldn't open %s\n",av[1]);exit(1);}
  fseek(fpi,0,SEEK_END);siz=ftell(fpi);rewind(fpi);
  fprintf(stderr,"File size %d byte%s\n",siz,siz==1?"":"s");
  inbuf=(char*)malloc(siz+1);if(!inbuf){fprintf(stderr,"Couldn't malloc %d bytes (inbuf)\n",siz+1);exit(1);}
  n=fread(inbuf,1,siz,fpi);assert(n<=siz);siz=n;inbuf[siz]=0;inend=inbuf+siz;fclose(fpi);
  for(i=0;i<siz;i++)if(inbuf[i]=='\n')inbuf[i]=0;
 
  ip=inbuf;tr-=1;
  while(ip<inend&&strncmp(ip,"format=",7))ip+=strlen(ip)+1;
  if(ip==inend)err("Didn't find format line");
  l1=strstr(ip,"format=");assert(l1);format=strtoi(l1+7,0);
  l1=strstr(ip,"tracks=");assert(l1);tracks=strtoi(l1+7,0);
  l1=strstr(ip,"division=");assert(l1);division=strtoi(l1+9,0);
  fprintf(stderr,"format=%d tracks=%d division=%d\n",format,tracks,division);

  nob=(noteoffbuf *)malloc(MAXNOB*sizeof(noteoffbuf));assert(nob);
  tri=(trackinfo*)malloc((tracks+1)*sizeof(trackinfo));assert(tri); /* Track number <tracks> is for the preamble */
  for(i=0;i<=tracks;i++){
    tri[i].out=0;tri[i].ip=0;tri[i].op=(i<tracks?8:0);/* leave 8 bytes for MTrk, length */
    tri[i].l=0;tri[i].ln=0;
    tri[i].bt=tri[i].pt=tri[i].et=0;tri[i].von=100;
    tri[i].bn=tri[i].cr=tri[i].tt=0;tri[i].sta=0;
    tri[i].st=-1;
  }
 
  if(division&(1<<15))tpc=48; else tpc=division;
  infinity=0x7fffffff;
  tr=tracks;putst("MThd");put4(6);put2(format);put2(tracks);put2(division);
 
  tis0=4;tis1=4;tis2=(tis0*4*tpc)/tis1;/* Initial time signature 4/4 */
  tt=0;bn=0;cr=0;
  /* (tt,bn,cr) are always kept in step, and it is guaranteed that there
     are no time sig. changes between tt and the present new time. */
  non=0;/* number of pending note-off events */
  while(1){
    nxt=infinity;tr0=-1;
    for(tr=0;tr<tracks;tr++){
      if(tri[tr].sta==3)goto nl0;
      if(tri[tr].sta==0){
    do{
      ip=getl(tr);if(!ip){tri[tr].sta=3;goto nl0;}
      ip=getns(ip);
      l1=strstr(ip,"TR ");if(!l1)err("Expect TR");
    }while(strtoi(l1+3,0)!=tr);
    if(strncmp(ip,"DT ",3)==0){tri[tr].tt=tri[tr].bt+getfrn(ip+3,tpc);tri[tr].sta=1;goto ok0;}
    if(strncmp(ip,"FOL",3)==0||strncmp(ip,"SIM",3)==0){
      tri[tr].tt=tri[tr].pt;
      if(ip[0]=='F'){
        if(tri[tr].et>=0)tri[tr].tt=tri[tr].et; else
          fprintf(stderr,"Warning - FOL reverts to SIM following a DT-defined or infinite length note/rest\n");
      }
      if(ip[3]=='+')tri[tr].tt+=getfrn(ip+4,tpc);
      if(ip[3]=='-')tri[tr].tt-=getfrn(ip+4,tpc);
      tri[tr].sta=1;goto ok0;
    }
    if(strncmp(ip,"CR ",3)==0){
      tri[tr].tt=getfrn(ip+3,tpc);
      tri[tr].sta=1;goto ok0;
    }
    if(strncmp(ip,"BA ",3)==0){
      tri[tr].bn=strtoi(ip+3,&l1)-1;
      l1=getns(l1);if(strncmp(l1,"CR ",3))err("Expect CR after BA");
      tri[tr].cr=getfrn(l1+3,tpc);
      tri[tr].sta=2;goto ok0;
    }
    err("Expected time specification (DT, FOL, SIM, CR, or BA) at start of line");
      }
    ok0:
      if(tri[tr].sta==2)tri[tr].tt=tt+(tri[tr].bn-bn)*tis2+tri[tr].cr-cr;
      if(tri[tr].tt<nxt){nxt=tri[tr].tt;tr0=tr;}
    nl0:
      tr=tr;/* because ISO C forbids label at end of compound statement, apparently */
    }
    if(tr0==-1)break;
    tr=tr0;ip=inbuf+tri[tr].ip;tri[tr].sta=0;
   
    text=(strstr(ip,"Text")!=0);
    /* Need to know if Text line, since otherwise the quoted string
       could cause a false match with some other key word */
    t=nxt-tt+cr; bn+=t/tis2; cr=t%tis2; tt=nxt;
    /* At this point, tt=time of next non-note-off event.
       Have to lay down note-off events which have been bypassed. */
    tr0=tr;
    for(i=0;i<non&&nob[i].tt<=tt;i++){
      tr=nob[i].tr;putv(nob[i].tt-tri[tr].bt);tri[tr].bt=nob[i].tt;
      if(nob[i].voff!=0x40){
    st=0x80+nob[i].ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
    put1(nob[i].nt);put1(nob[i].voff);
      }else{
    st=0x90+nob[i].ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
    put1(nob[i].nt);put1(0);
      }
    }
    if(i>0){non-=i;memmove(nob,nob+i,non*sizeof(noteoffbuf));}
    tr=tr0;
    rest=0;if(!text){l1=strstr(ip,"NT ");if(l1)rest=(toupper(*(getns(l1+3)))=='R');}
    if(!rest){putv(tt-tri[tr].bt);tri[tr].bt=tt;}
    if(text){
      put1(tri[tr].st=0xFF);
      l1=strstr(ip,"type");assrt(l1,"missing 'type' in text line");t=strtoi(l1+4,0);put1(t);
      l1=strchr(l1+4,'"');assrt(l1,"missing opening quote in text line");l1++;
      l2=strrchr(l1,'"');assrt(l2,"missing closing quote in text line");
      for(i=s=0;i<l2-l1;i++){
    t=l1[i];
    if(t=='\\'&&i<l2-l1-1){
      t=l1[++i];
      if(t=='t')t=9;
      if(t=='n')t=10;
      if(t=='v')t=11;
      if(t=='f')t=12;
      if(t=='r')t=13;
    }
    l1[s++]=t;
      }
      putv(s);putstn(l1,s);
      continue;
    }
    l1=strstr(ip,"CH ");assrt(l1,"missing channel number");ch=strtoi(l1+3,0)-1;assrt(ch>=0&&ch<16,"channel number out of range");
    l1=strstr(ip,"Time signature");if(l1){
      tis0=strtoi(l1+14,&l1);tis1=strtoi(l1+1,0);assrt((tis0*4*tpc)%tis1==0,"New time signature not commensurate with division");
      tis2=(tis0*4*tpc)/tis1;
      put1(tri[tr].st=0xFF);
      put1(0x58);putv(4);put1(tis0);
      for(i=0,n=tis1;n>1;i++)n>>=1;put1(i);
      l2=strstr(ip,"clocks/mtick");assrt(l2,"missing clocks/mtick");put1(strtoi(l2+12,0));
      l2=strstr(ip,"crotchets/32ndnote");assrt(l2,"missing crotchets/32ndnote");put1(strtoi(l2+18,0));
      continue;
    }
    l1=strstr(ip,"Channel volume");if(l1){
      st=0xB0+ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
      t=strtoi(l1+14,0);assrt(t>=0&&t<128,"channel volume out of range");put1(7);put1(t);
      continue;
    }
    l1=strstr(ip,"Instrument");if(l1){
      st=0xC0+ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
      t=strtoi(l1+10,0)-1;assrt(t>=0&&t<128,"instrument out of range");put1(t);
      continue;
    }
    l1=strstr(ip,"Sysex event");if(l1){
      put1(tri[tr].st=strtoi(l1+11,&l1));
      n=0;l2=l1;while(1){strtoi(l2,&l3);if(l3==l2)break;l2=l3;n++;}
      putv(n);for(l2=l1;n>0;n--)put1(strtoi(l2,&l2));
      continue;
    }
    l1=strstr(ip,"End of track");if(l1){
      put1(tri[tr].st=0xFF);
      put1(0x2F);put1(0);
      continue;
    }
    l1=strstr(ip,"Tempo");if(l1){
      put1(tri[tr].st=0xFF);
      n=(int)(60*1000000./atof(l1+5)+.5);assrt(n>=0&&n<=0xFFFFFF,"tempo out of range");
      put1(0x51);putv(3);put3(n);
      continue;
    }
    l1=strstr(ip,"Key ");if(l1){
      put1(tri[tr].st=0xFF);
      l1=getns(l1+4);l2="FCGDAEB";l3=strchr(l2,toupper(*l1));assrt(l3,"unrecognised key");sf=l3-l2-1;
      do{l1++;sf+=7*((*l1=='#')-(*l1=='b'));}while(*l1=='#'||*l1=='b');
      l2=strstr(l1," major");l3=strstr(l1," minor");assrt((l2!=0)^(l3!=0),"missing minor/major in key");
      mi=(l3!=0);sf-=3*mi;
      put1(0x59);putv(2);put1(sf&255);put1(mi);
      continue;
    }
    l1=strstr(ip,"Meta Event");if(l1){
      put1(tri[tr].st=0xFF);
      put1(strtoi(l1+10,&l1));
      n=0;l2=l1;while(1){strtoi(l2,&l3);if(l3==l2)break;l2=l3;n++;}
      putv(n);for(l2=l1;n>0;n--)put1(strtoi(l2,&l2));
      continue;
    }     
    l1=strstr(ip,"ST ");if(l1){
      st=strtoi(l1+3,&l1);if(st!=tri[tr].st||((st&0xF0)==0xF0))put1(tri[tr].st=st);
      while(1){n=strtoi(l1,&l2);if(l2==l1)break;l1=l2;put1(n);}
      continue;
    }
    l1=strstr(ip,"NT ");if(l1){
      tri[tr].pt=tt;tri[tr].et=-1;
      n=strton(l1+3,&l1);l1=getns(l1);
      if(strncmp(l1,"on",2)==0){
    assrt(n>=0,"can't have rest and 'on'");
    st=0x90+ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
    l2=strstr(l1,"von=");if(l2)tri[tr].von=strtoi(l2+4,0);
    assrt(tri[tr].von>=1&&tri[tr].von<128,"von out of range");
    put1(n);put1(tri[tr].von);
    continue;
      }
      if(strncmp(l1,"off",3)==0){
    l2=strstr(l1,"voff=");if(l2)t=strtoi(l2+5,0); else t=0x40;
    assrt(t>=0&&t<128,"voff out of range");
    if(t!=0x40){/* not most efficient coding, but most likely to mimic convention used by others(?) */
      st=0x80+ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
      put1(n);put1(t);
    }else{
      st=0x90+ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
      put1(n);put1(0);
    }
    continue;
      }
      d=getfrn(l1,tpc);if(d<infinity){nxt=tt+d;tri[tr].et=nxt;}
      if(rest)continue;
      assert(n>=0);
      l2=strstr(l1,"von=");if(l2)tri[tr].von=strtoi(l2+4,0);
      assrt(tri[tr].von>=1&&tri[tr].von<128,"von out of range");
      st=0x90+ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
      put1(n);put1(tri[tr].von);
      if(d<infinity){
    l2=strstr(l1,"voff=");if(l2)t=strtoi(l2+5,0); else t=0x40;
    for(i=0;i<non;i++)if(nob[i].ch==ch&&nob[i].nt==n){
      fprintf(stderr,"Warning - overlapping note at line %d:\n%s\n",tri[tr].ln,ip);
      /*
        memmove(nob+i,nob+i+1,(non-(i+1))*sizeof(noteoffbuf));non--;
        Can't necessarily delete note-off event when notes overlap because
        some devices might stack the extra soundings of same note, and so
        deleting this event may result in a note stuck on.
      */
      break;
    }
    assrt(non<MAXNOB,"Note-off buffer full");
    for(i=0;i<non&&nob[i].tt<=nxt;i++);
    memmove(nob+i+1,nob+i,(non-i)*sizeof(noteoffbuf));non++;
    nob[i].tr=tr;nob[i].tt=nxt;nob[i].ch=ch;nob[i].nt=n;nob[i].voff=t;
      }
      continue;
    }
    fprintf(stderr,"Unrecognised line %d:\n%s\n",tri[tr].ln,ip);exit(1);
  }/* main loop */
  for(i=0;i<non;i++){
    tr=nob[i].tr;putv(nob[i].tt-tri[tr].bt);tri[tr].bt=nob[i].tt;
    if(nob[i].voff!=0x40){
      st=0x80+nob[i].ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
      put1(nob[i].nt);put1(nob[i].voff);
    }else{
      st=0x90+nob[i].ch;if(st!=tri[tr].st)put1(tri[tr].st=st);
      put1(nob[i].nt);put1(0);
    }
  }
  if(i>0){non-=i;memmove(nob,nob+i,non*sizeof(noteoffbuf));}
  fprintf(stderr,"Successfully parsed\n");
  fwrite(tri[tracks].out,1,tri[tracks].op,stdout);
  for(tr=0;tr<tracks;tr++){
    t=tri[tr].op;tri[tr].op=0;putst("MTrk");put4(t-8);
    if(tri[tr].out)fwrite(tri[tr].out,1,t,stdout); else assert(t==0);
  }
  return 0;
}


Attached Files
.zip   convert-midi.zip (Size: 26.28 KB / Downloads: 51)
Reply
#2
(08-23-2022, 05:41 PM)madscijr Wrote: 1.   Question
I was looking to have QB64 convert MIDI files to CSV and vice-versa and found some C code that looks like it does the job.

It's just 2 files (asc2mid.c and mid2asc.c) and I figure regular C code with no fancy OO, it should be pretty straightforward to convert to BASIC, right?


Answer:
do you know that your QB64 code will be translated into C/C++ that will be compiled  by G++ /gcc
that are the C/C++ gnu compilers in the folder named internal.
So you can open your C/C++ IDE or your multilanguage IDE (Notepad++, Visual Studio,  JetBrain/IntelliJ IDEA) and after setting IDE to work with the specific language you will be able to compile C/C++ programs.
Moreover you can build an header file to include original C/C++ code as External library.
 (this sentence answers also to the question 2)


2.
Which leads me to a second question - QB64 comes with a C compiler "bundled" with it, right? Is there some way to use it to compile straight C programs? 

Answer:
see answer to  question 1

3.
Question
Is anyone interested in helping make sense of some C code?  :-D

Answer:

your QB64 code will be translated into C/C++, why do you want to translate a C program in QB64 to be translated again in C/C++?
However it seems to me  straight C programs, but this doesn't means that it is easy to translate them into QB64...starting from the point that these 2 simple programs use these other programs (Libraries) written in C and used as included file!

-------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <string.h>
--------------------------------------------
so do you want translate also these libraries or do you want include them as External Libraries?

Good thinking about this last point.
Reply
#3
(08-25-2022, 10:48 PM)TempodiBasic Wrote:
(08-23-2022, 05:41 PM)madscijr Wrote: 1.   Question
I was looking to have QB64 convert MIDI files to CSV and vice-versa and found some C code that looks like it does the job.

It's just 2 files (asc2mid.c and mid2asc.c) and I figure regular C code with no fancy OO, it should be pretty straightforward to convert to BASIC, right?


Answer:
do you know that your QB64 code will be translated into C/C++ that will be compiled  by G++ /gcc
that are the C/C++ gnu compilers in the folder named internal.
So you can open your C/C++ IDE or your multilanguage IDE (Notepad++, Visual Studio,  JetBrain/IntelliJ IDEA) and after setting IDE to work with the specific language you will be able to compile C/C++ programs.
Moreover you can build an header file to include original C/C++ code as External library.
 (this sentence answers also to the question 2)


2.
Which leads me to a second question - QB64 comes with a C compiler "bundled" with it, right? Is there some way to use it to compile straight C programs? 

Answer:
see answer to  question 1

3.
Question
Is anyone interested in helping make sense of some C code?  :-D

Answer:

your QB64 code will be translated into C/C++, why do you want to translate a C program in QB64 to be translated again in C/C++?
However it seems to me  straight C programs, but this doesn't means that it is easy to translate them into QB64...starting from the point that these 2 simple programs use these other programs (Libraries) written in C and used as included file!

-------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <string.h>
--------------------------------------------
so do you want translate also these libraries or do you want include them as External Libraries?

Good thinking about this last point.

Thank you for your answers! 

I should explain a little more, because I can see how my questions might leave one confused about what it is I am actually trying to do. 

My main goal is to write some native QB64 routines that translate a MIDI file to CSV (or tab-delimited), and vice-versa, so I can play around with editing or creating MIDI music in Excel or in my own QB64 programs. 

For this purpose, I don't want to have to deal with C code at all, I want my program to be pure BASIC, and be cross-platform. Someone should just be abme to paste it into QB64 and it will just work! 

So why all this nonsense about C, you ask? 

I don't know a lot about the technical details of reading and writing MIDI files - how to read or parse the bytes, etc. I'm not too interested in getting into all that, and if someone has already figured it out in some other language (like C) then I'm perfectly happy just taking it and converting to QB64. 

I don't know C too well, but I am familiar enough to recognize those libraries it uses (stdio, stdlib, math, assert, string) are mostly basic functionality for working with strings, math and files. 

The only one _not_ doing stuff that I've already done in QB64 a zillion times is Assert. That's a weird one, but I read that it's basically C's way of handling illegal values, which we can do in QB64 with some regular IF/THEN logic. 

Anyway, the code is just reading a binary file into some values, looking at it, and writing it out to a CSV, which is something QB64 can easily do. So I am confident it can be done in pure QB64. 

The trick is, I am _not_ very knowledgable about C (I don't even like it!) Even though I'm familiar with the syntax (having done JavaScript and C#, and some C back in school), it still is alien to me, and all the manual work you have to worry about in C is painful for someone whose favorite languages are VBA and QuickBasic! 

But if I am going to understand and translate code that parses a format I don't understand, in a language I don't know very well, to a language I do sort of know, then it would probably be helpful to be able to run some of that code in a test environment - just to see (just to "c"! lol) what it does. I learn by experimenting. And it eliminates the guesswork by being able to try out a line of code to see what it does. 

So I would want a C compiler for that purpose. But I want to avoid downloading and installing yet more unknown applications on my laptop, because who knows what malware is out there, etc. I already trust and love QB64, so if I can use its already installed C compiler, great! 

But beyond this MIDI to CSV project, you may have seen my many posts going on about this ridiculous pipe dream of getting the PC to read 2 or more USB mice as separate input devices, for local multiplayer Pong games I have dreamed of for a long time. Well, I found some code that supposedly does that, and it is also in C! I gather that this mouse stuff is much lower level operations, and needs to be in a language like C, so that piece of software will probably be a DLL that a QB64 program calls. So yet another reason I would like a C compiler! And there is at least one other project that similarly has me looking at C code (I don't recall what it was, but it's there!)

Really, I am not that excited about doing C. It's a necessary evil and I just want to get the dirty work done, so I can get back to writing QB64 code! 

Anyway I hope that explains where I'm coming from a little more. Thanks again for your reply!
Reply
#4
(08-25-2022, 11:32 PM)madscijr Wrote: But beyond this MIDI to CSV project, you may have seen my many posts going on about this ridiculous pipe dream of getting the PC to read 2 or more USB mice as separate input devices, for local multiplayer Pong games I have dreamed of for a long time. Well, I found some code that supposedly does that, and it is also in C! I gather that this mouse stuff is much lower level operations, and needs to be in a language like C, so that piece of software will probably be a DLL that a QB64 program calls. So yet another reason I would like a C compiler! And there is at least one other project that similarly has me looking at C code (I don't recall what it was, but it's there!)

Really, I am not that excited about doing C. It's a necessary evil and I just want to get the dirty work done, so I can get back to writing QB64 code! 

Anyway I hope that explains where I'm coming from a little more. Thanks again for your reply!

Ok MadSciJr
reading your answer to my post this is the first attempt to translate 
mid2asc.c

I have read the instructions to use it here http://www.archduke.org/midi/instrux.html

I have used C, but I've not so practice with all its structures and ways to code... never used and seen Assert.h  https://en.wikipedia.org/wiki/Assert.h
and never seen this way to assign a value to a string variable 
  buf[p][0]="CDEFGAB"[(n+700000)%7];

it seems to me
buf(p,0) = "CDEFGAB " + ltrim$(str$(n+700000 mod 7))

I have tried to compile this C code with Gcc /G++ getting back only so much warning about dirty use/assign/modify of *char and *string not compatible with ISO C and using wstring/ Wchar_t*   https://en.wikipedia.org/wiki/C_string_handling


Starting from code you have posted mid2asc.c   I have put in QB64 comment by ' the code in BASIC in relation with code in C showed above.

Code: (Select All)
[quote]
/*
From: http://www.archduke.org/midi/

mid2asc:
Converts midi to ascii. Output can be in one of several formats
determined by command line flags.

Time of an event can be determined by one of
  (i) BA+CR:              Bar number+Crotchet within bar
  (ii) CR:                  Crotchet number from the very start
(iii) DT:                  Delta time from previous event - native midi format
  (iv) FOL[+/-crotchets]:  Time from end of previous note/rest on that track
  (v) SIM[+crotchets]:    Time from start of previous note/rest on that track

  Basic complication of mode (i) is that the Time signature affects
  the interpretation of time, and that time signature can change at
  any time on any track and should affect all tracks.

  Could make it closer to the spec. (e.g., skip over unknown chunk types)

  Midi files are going to be ambiguous, e.g. order of some events
  occuring at the same time, or whether choose to use running st mode,
  or whether to choose to use 0x8* or 0x9* for voff when
  velocity=0x40.

  Could print key changes from other tracks as comments in sep mode

  Copyright November 2002 A.P.Selby

  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  associated documentation files (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge, publish, distribute,
  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all copies or
  substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
  NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <string.h>

#define MIN(x,y) ((x)<(y)?(x):(y))
'Function MIN (x,y)
' if x<y then MIN = x else MIN = y
' End Function


#define MAX(x,y) ((x)>(y)?(x):(y))
'Function Max (x,y)
' if X>Y then MAX = x else MAX = y
' End Function

#define MAXLL 10000
#define MAXCH 16
#define MAXNT 128
' Const MAXLL = 10000. MAXCH = 16, MAXNT = 128
unsigned char *dat;
' DIM dat as String

int p,siz,pr=0,raw,cro,fol,sep,frpad=9;
' DIM AS INT p,siz,pr,raw,cro,fol,sep,frpad: pr = 0: frpad = 9

int bgn[12][2]={{0,0},{0,1},{1,0},{2,-1},{2,0},{3,0},{3,1},{4,0},{4,1},{5,0},{6,-1},{6,0}};
/* ^ best guess note from pitch relative to keynote: (In C maj/A min this would be: C C# D Eb E F F# G G# A Bb B) */

' Dim    bgn (0 to 12,0 to 2) AS INT
' bgn (1,1) = 0 : bgn (1,2) = 0
' bgn (2,1) = 0 : bgn (2,2) = 2
' bgn (3,1) = 1 : bgn (3,2) = 0
' bgn (4,1) = 2 : bgn (4,2) = -1
' bgn (5,1) = 2 : bgn (5,2) = 0
' bgn (6,1) = 3 : bgn (6,2) = 0
' bgn (7,1) = 3 : bgn (7,2) = 1
' bgn (8,1) = 4 : bgn (8,2) = 0
' bgn (9,1) = 4 : bgn (9,2) = 1
' bgn (10,1) = 5 : bgn (10,2) = 0
' bgn (11,1) = 6 : bgn (11,2) = -1
' bgn (12,1) = 6 : bgn (12,2) = 0

int maj[14]={0,2,4,5,7,9,11, 12,14,16,17,19,21,23};
' Dim maj (0 to 14) AS Int
' maj(1) = 0:maj(2) = 2: maj(3) = 4:maj(4) = 5: maj(5) = 7:maj(6) = 9:maj(7) = 11:
' maj(8) = 12:maj(9) = 14:maj(10) = 16:maj(11) = 17:maj(12) = 19:maj(13) = 21:maj(14) = 23

void err(char *l){fprintf(stderr,"Error: %s\n",l);exit(1);}
'SUB err (l as string)
' n& = freefile
' open "Error.txt" for output as #n&
' print #n&, "Error: ";l
' close n&
' system
' End SUB

#define assrt(x,y) if(!(x))err(y);
' Function assrt(x,y)
' if NOT x then err(Str$(y))
' End Function

char *note2(int n,int s,int pad){
  static char buf[100][20];
  static int p=0;
  int i;
  p=(p+1)%100;
  buf[p][0]="CDEFGAB"[(n+700000)%7];
  i=1;
  while(s>0){assert(i<19);buf[p][i++]='#';s--;}
  while(s<0){assert(i<19);buf[p][i++]='b';s++;}
  while(n>=7){assert(i<19);buf[p][i++]='\'';n-=7;}
  while(n<0){assert(i<19);buf[p][i++]='-';n+=7;}
  if(pad)while(i<pad&&i<19)buf[p][i++]=' ';
  buf[p][i]=0;
  return buf[p];
}

' Function  note2 (n as Integer, s as Integer, pad as Integer)
' Static buf (0 to 100, 0 to 20) as string *1
' Static p as Integer
' REM p = 0 not translated because only at first creation p = 0, after it is static
' Dim i as integer
' p = (p+1) MOD 100
' buf(p,0) = "CDEFGAB " + ltrim$(str$(n+700000 mod 7))
' i = 1
' While s > 0
' if i < 19 then
'  buf(p,i) = "#"+ ltrim$(str$(s))
'  i = i +1
'  s = s-1
' else
' print "Error: "; i: " <19  false"
'  end
'  wend
' While s < 0
  '  if i < 19 then
  '    buf(p,i) = "b"+ ltrim$(str$(s))
  '    i = i +1
  '    s = s+1
  ' else
  '  print "Error: "; i: " <19  false"
  '  end
'wend
' While n >=7
'  if i < 19 then
'    buf(p,i) = "\"+ ltrim$(str$(n))
'    i = i +1
'    n = n-7
' else
'  print "Error: "; i: " <19  false": end
'wend
' While n < 0
'  if i < 19 then
'    buf(p,i) = "-"+ ltrim$(str$(n))
'    i = i+1
'    n = n +7
'  else
'    print "Error: "; i: " <19  false": end
' wend
' if pad then
'      while i<pad AND i<19
'        buf(p, i)= " "
'        i = i+1
'      wend
' end if
' buf (p,i) = 0
' note2 = buf(p) '<---- it returns the first memory allocation of array buf, this line must be modified to share buf(,) string array
' End Function

char *note(int n,int sf,int mi,int pad){
  int a,b,m,a0,b0;
  assert(n>=0&&n<128&&sf>=-128&&sf<=127);
  a0=(4*sf+7000)%7;b0=(sf+7001)/7-1000;/* (a0,b0) = keynote of (sf,mi) in pair form */
  n-=60+maj[a0]+b0;/* subtract keynote in single pitch form */
  m=(n+120000)%12;a=bgn[m][0];b=bgn[m][1];/* look up most likely pair form */
  return note2(a0+a+((n-m)/12)*7,maj[a0]+maj[a]-maj[a0+a]+b0+b,pad);/* evaluate cocycle! */
}
char *key(int sf,int mi,int pad){
  static char buf[100][21];
  static int p=0;
  int i,n;
  p=(p+1)%100;
  assert(sf>=-128&&sf<=127);
  if(mi)sf+=3;
  buf[p][0]="FCGDAEB"[(sf+7001)%7];
  n=(sf+7001)/7-1000;i=1;
  while(n>0){buf[p][i++]='#';n--;}
  while(n<0){buf[p][i++]='b';n++;}
  while(i<pad&&i<20)buf[p][i++]=' ';
  buf[p][i]=0;
  return buf[p];
}
int gcd(int a,int b){if(b==0)return a; else return gcd(b,a%b);}
int sprfrac(char *l,int n,int d,int sum,int pad){
  int a,g,s;
  s=0;
  if(sum){
    a=n/d;if(a){
      s=sprintf(l,"%d",a);n-=a*d;
      if(n==0)goto pad;
      s+=sprintf(l+s,"+");
    }
  }
  g=gcd(n,d);n/=g;d/=g;
  if(d!=1)s+=sprintf(l+s,"%d/%d",n,d); else s+=sprintf(l+s,"%d",n);
pad:
  if(s<pad){memmove(l+pad-s,l,s+1);memset(l,' ',pad-s);s=pad;}
  return s;
}
int sprfrac2(char *li,int n,int d){
  /* Prints the simplest (or near simplest) number x such that (int)(n/x+.5)=d */
  int i,t,dp;
  double m,r,x0,x1;
  char l[100],*l0,*l1;
  if(li)l0=li; else l0=l;
  x0=n/(d+.49);x1=n/(d-.49);l1=l0;
  /* Find minimum dp such that [x0,x1] intersects Z*10^(-dp) */
  for(dp=0,m=1;dp<15;dp++){
    r=floor(x1*m);
    if(r>x0*m){
      t=(int)(r/m);
      l1+=sprintf(l1,"%d",t);r-=t*m;
      if(dp){
    l1+=sprintf(l1,".");
    for(i=0;i<dp;i++){m/=10;t=(int)(r/m);l1+=sprintf(l1,"%d",t);r-=t*m;}
      }
      if((int)(n/atof(l0)+.5)!=d){fprintf(stderr,"Error 2 in prfrac2 n=%d d=%d\n",n,d);exit(1);}
      if(li)return l1-l0; else return printf("%s",l);
    }
    m*=10;
  }
  fprintf(stderr,"Error 1 in prfrac2 n=%d d=%d\n",n,d);exit(1);
}

void need(int n,char *l){
  if(p+n>siz){fprintf(stderr,"Unexpected EOF in %s, p=&%06x n=%d siz=&%06x\n",l,p,n,siz);exit(1);}
}
void skip(int n){need(n,"skip");p+=n;}
void match(char *l){
  int i;
  need(strlen(l),"match");
  for(i=0;l[i];i++)if(l[i]!=dat[p++]){
    fprintf(stderr,"Bad match, p=%X i=%d l=%s &%02X!=&%02X\n",p,i,l,l[i],dat[p-1]);exit(1);
  }
}
int getn(int n,int a){
  int i,t;
  need(n,"getn");
  for(i=0,t=0;i<n;i++)t=(t<<8)+dat[p+i];
  if(a)p+=n;
  return t;
}
int get1(){return getn(1,1);}
int get2(){return getn(2,1);}
int get3(){return getn(3,1);}
int get4(){return getn(4,1);}
int look1(){return getn(1,0);}
int getv(){
  int c,t;
  t=0;
  do{
    need(1,"getv");c=dat[p++];t=(t<<7)+(c&127);
  }while(c&128);
  return t;
}
int getnnodt(int st,int e,int *st1){
  /* Returns the dt to next non-noteoff event (raw=0), or next event (raw=1). return=-1 means end of track. */
  int t,p0,dt,sth;
  dt=0;
  while(p<e){
    dt+=getv();*st1=st;if(raw)return dt;
    p0=p;
    t=look1();if(t>=0x80)st=get1(); else assert(st>=0x80);
    sth=st>>4;
    if(sth==8||(sth==9&&dat[p+1]==0))skip(2); else {p=p0;return dt;}
  }
  return -1;
}

typedef struct {int tt,ch,nt,voff;} noteoffbuf;
int cmp(const void*x,const void*y){return ((noteoffbuf*)x)->tt-((noteoffbuf*)y)->tt;}

int main(int ac,char **av){
  int i,s,t,u,bf,bn,btt,fol1,rest,ch,dt,infinity,ln,mi,maxno,nat,non,ols,prb,help,
    sk,sf,st,sth,tis0,tis1,tis2,tpc,tr,trl,tt,ty,alltrfl,format,tracks,division,tempo,nobp[MAXCH][MAXNT];
  char l[MAXLL],*l1;
  double ert;
  typedef struct {int a,p0,l,e,p,pv,st,dt,tt,et,pt;} trackinfo;
  typedef struct fred {int all,bn,tr;char *l;struct fred *nx;} outline;
  trackinfo *tri;
  noteoffbuf *nob;
  outline *outbuf0,*outbuf;
  FILE *fpi;

  raw=cro=fol=sep=help=0;
  for(i=1;i<ac;i++){
    if(!strncmp(av[i],"-c",2))cro=1;
    if(!strncmp(av[i],"-f",2))fol=1;
    if(!strncmp(av[i],"-r",2))raw=1;
    if(!strncmp(av[i],"-s",2))sep=1;
    if(!strncmp(av[i],"-h",2)||!strncmp(av[i],"--help",6))help=1;
  }
  if(ac<2||help){
    fprintf(stderr,"Usage: mid2asc  [-c] [-f,-r] [-s] midifile > textfile\n\n");
    fprintf(stderr,"  Default timing is to use absolute time determined by BA+CR\n");
    fprintf(stderr,"  and use durations for notes.\n");
    fprintf(stderr,"-c Use absolute crotchets rather than BA+CR to determine time.\n");
    fprintf(stderr,"  Useful if inserting or deleting.\n");
    fprintf(stderr,"-f 'Follow' (take time from start or end of previous note) to determine time.\n");
    fprintf(stderr,"-r \"raw\" format: time determined by DT, and note-off events\n");
    fprintf(stderr,"  are used instead of note durations.\n\n");
    fprintf(stderr,"  Default is to print all tracks merged in chronological order.\n");
    fprintf(stderr,"-s Print all tracks separately.\n");
    exit(1);
  }
  if(fol&&raw){fprintf(stderr,"fol and raw modes cannot be combined\n");exit(1);}
  fpi=fopen(av[ac-1],"rb");if(!fpi){fprintf(stderr,"Couldn't open %s\n",av[ac-1]);exit(1);}
  fseek(fpi,0,SEEK_END);siz=ftell(fpi);rewind(fpi);
  if(pr>=1)printf("# File size %d byte%s\n",siz,siz==1?"":"s");
  dat=(unsigned char*)malloc(siz);if(!dat){fprintf(stderr,"Couldn't malloc %d bytes\n",siz);exit(1);}
  fread(dat,1,siz,fpi);p=0;fclose(fpi);
  maxno=siz/2;
  match("MThd");
  assert(get4()==6);
  format=get2();tracks=get2();division=get2();
  printf("format=%d tracks=%d division=%d\n",format,tracks,division);
  if(division&(1<<15))tpc=48; else tpc=division;
  tri=(trackinfo *)malloc(tracks*sizeof(trackinfo));assert(tri);
  nob=(noteoffbuf *)malloc(maxno*sizeof(noteoffbuf));assert(nob);
  infinity=0x7fffffff;

  for(tr=0;p<siz&&tr<tracks;tr++){
    match("MTrk");trl=get4();
    tri[tr].p0=p;tri[tr].l=trl;tri[tr].e=tri[tr].p0+tri[tr].l;
    p+=trl;
  }

  non=0;/* outside 'if' statement to stop warning */
  if(!raw){
    for(tr=0;tr<tracks;tr++){
      st=0;tt=0;p=tri[tr].p0;
      while(p<tri[tr].e){
    dt=getv();tt+=dt;
    t=look1();if(t>=0x80)st=get1(); else assert(st>=0x80);
    sth=st>>4;ch=st&15;
    if(sth==8||sth==9){
      t=get1();u=get1();
      if(sth==9){if(u)continue; else u=0x40;}
      assert(non<maxno);nob[non].tt=tt;nob[non].ch=ch;nob[non].nt=t;nob[non].voff=u;non++;
      continue;
    }
    if(sth==0xC||sth==0xD){skip(1);continue;}
    if(sth==0xF){
      if(st==0xF0||st==0xF7){skip(getv());continue;}
      if(st==0xFF){skip(1);skip(getv());continue;}
      fprintf(stderr,"Unrecognised status %02x\n",st);exit(1);
    }
    skip(2);
      }
    }
    qsort(nob,non,sizeof(noteoffbuf),cmp);
    for(ch=0;ch<MAXCH;ch++)for(t=0;t<MAXNT;t++)nobp[ch][t]=0;
  }

  nat=0;
  for(tr=0;tr<tracks;tr++){
    p=tri[tr].p=tri[tr].p0;
    t=getnnodt(0,tri[tr].e,&tri[tr].st);tri[tr].a=(t>=0);
    if(tri[tr].a){
      tri[tr].et=tri[tr].pt=0;tri[tr].dt=tri[tr].tt=t;tri[tr].p=p;tri[tr].pv=-1;nat++;
    }
  }
  sf=0;mi=0;/* Initial key C major */
  tis0=4;tis1=4;tis2=(tis0*4*tpc)/tis1;/* Initial time signature 4/4 */
  tempo=500000;/* Default usec/beat (= 120 bpm) */
  btt=0;bn=0;bf=0;ert=0;/* at time btt, bar number was bn (from 0), and part of bar complete was bf/tpc crotchets */
  prb=-1;
  ols=MAXLL*10;outbuf=outbuf0=(outline*)malloc(ols);outbuf->nx=0;
  if(!outbuf){fprintf(stderr,"Couldn't malloc %d bytes (outbuf1)\n",ols);exit(1);}
  while(nat>0){
    tt=infinity;tr=-1;alltrfl=0;
    for(i=0;i<tracks;i++)if(tri[i].a){
      t=tri[i].tt;if(fol&&tri[i].et<t)t=tri[i].et;
      if(t<tt){tr=i;tt=t;}
    }
    assert(tr>=0);
    p=tri[tr].p;t=look1();if(t>=0x80)st=get1(); else st=tri[tr].st;
    sth=st>>4;ch=st&15;
    assert(!(raw==0&&(sth==8||(sth==9&&dat[p+1]==0))));
    rest=(fol&&tri[tr].et<tri[tr].tt);
    fol1=(fol&&tri[tr].et<=tri[tr].tt);
    l1=l;
    if(tt>btt){
      ert+=(double)(tt-btt)*(double)tempo/(double)tpc;
      /* Elapsed crotchets=(btt-tt)/tpc, ticks/bar=tis2 */
      t=tt-btt+bf; bn+=t/tis2; bf=t%tis2; btt=tt;
    }
    if(!sep&&bn!=prb){l1+=sprintf(l1,"\n");prb=bn;}
    if(fol){
      u=0;
      if(fol1)l1+=sprintf(l1,"FOL"); else {
    l1+=sprintf(l1,"SIM");t=tri[tr].tt-tri[tr].pt;if(t){
      assert(t>0);l1+=sprintf(l1,"+");u=sprfrac(l1,t,tpc,1,0);l1+=u;u++;
    }
      }
      while(u<frpad+1){*(l1++)=' ';u++;}
      l1+=sprintf(l1," (");
    }
    if(raw){l1+=sprintf(l1,"DT ");l1+=sprfrac(l1,tri[tr].dt,tpc,1,frpad);l1+=sprintf(l1,"  (");}
    if(cro){l1+=sprintf(l1,"CR ");l1+=sprfrac(l1,tt,tpc,1,4+frpad);l1+=sprintf(l1,"  (");}
    l1+=sprintf(l1,"BA %4d  CR ",bn+1);l1+=sprfrac(l1,bf,tpc,1,frpad);
    if(cro)l1+=sprintf(l1,")");
    if(raw)l1+=sprintf(l1,")");
    if(fol)l1+=sprintf(l1,")");
    l1+=sprintf(l1,"  TR %2d  CH %2d  ",tr,ch+1);
    if(rest){
      l1+=sprintf(l1,"NT  R      ");l1+=sprfrac(l1,tri[tr].tt-tt,tpc,1,frpad);l1+=sprintf(l1,"\n");
      tri[tr].pt=tt;tri[tr].et=tri[tr].tt;goto bp2;
    }
    if(sth==8||sth==9){
      t=get1();u=get1();
      l1+=sprintf(l1,"NT  %s ",note(t,sf,mi,7));
      if(sth==8||(sth==9&&u==0)){
    assert(raw);
    l1+=sprintf(l1,"off");
    if(sth==8&&u!=0x40)l1+=sprintf(l1,"  voff=%d",u);
    l1+=sprintf(l1,"\n");
    goto bp1;
      }
      if(raw)l1+=sprintf(l1,"on"); else {
    assert(ch<MAXCH&&t<MAXNT);
    for(i=nobp[ch][t];i<non;i++)if(nob[i].tt>tt&&nob[i].ch==ch&&nob[i].nt==t)break;
    nobp[ch][t]=i;
    if(i<non){
      l1+=sprfrac(l1,nob[i].tt-tt,tpc,1,frpad);
      tri[tr].pt=tt;tri[tr].et=nob[i].tt;
    }else l1+=sprintf(l1,"infinity");
      }
      if(u!=tri[tr].pv){l1+=sprintf(l1,"  von=%d",u);tri[tr].pv=u;}
      if(!raw&&i<non&&nob[i].voff!=0x40)l1+=sprintf(l1,"  voff=%d",nob[i].voff);
      l1+=sprintf(l1,"\n");
      if(!raw&&i==non)l1+=sprintf(l1,"# Warning tr=%d p=&%X, note never turned off\n",tr,p);
      goto bp1;
    }
    if(sth==0xB){
      t=look1();if(t==7){
    t=get1();u=get1();
    l1+=sprintf(l1,"Channel volume %d\n",u);goto bp1;
      }
    }
    if(sth==0xC){t=get1();l1+=sprintf(l1,"Instrument %d\n",t+1);goto bp1;}
    if(sth==0xF){
      if(st==0xF0||st==0xF7){
    t=getv();l1+=sprintf(l1,"Sysex event &%02X ",st);
    for(i=0;i<t;i++)l1+=sprintf(l1," &%02X",dat[p++]);l1+=sprintf(l1,"\n");
    goto bp1;
      }
      if(st==0xFF){
    ty=get1();ln=getv();need(ln,"Meta");
    if(ty>=1&&ty<=7){
      l1+=sprintf(l1,"Text type %d: \"",ty);
      for(i=0;i<ln;i++){
        t=dat[p++];switch(t){
        case '\t':l1+=sprintf(l1,"\\t");break;
        case '\n':l1+=sprintf(l1,"\\n");break;
        case '\v':l1+=sprintf(l1,"\\v");break;
        case '\f':l1+=sprintf(l1,"\\f");break;
        case '\r':l1+=sprintf(l1,"\\r");break;
        case '\\':l1+=sprintf(l1,"\\\\");break;
        default:if(t!=0)l1+=sprintf(l1,"%c",t);break;
        }
      }
      l1+=sprintf(l1,"\"\n");
      goto bp1;
    }
    if(ty==0x2F){
      assert(ln==0);
      l1+=sprintf(l1,"End of track\n");goto bp1;
    }
    if(ty==0x51){
      assert(ln==3);tempo=get3();
      l1+=sprintf(l1,"Tempo ");l1+=sprfrac2(l1,60*1000000,tempo);l1+=sprintf(l1,"\n");
      alltrfl=1;
      goto bp1;
    }
    if(ty==0x58){
      assert(ln==4);assert(tt==btt);
      tis0=dat[p];tis1=1<<dat[p+1];assrt((tis0*4*tpc)%tis1==0,"New time signature not commensurate with division");
      tis2=(tis0*4*tpc)/tis1;
      l1+=sprintf(l1,"Time signature %d/%d, clocks/mtick %d, crotchets/32ndnote %d\n",tis0,tis1,dat[p+2],dat[p+3]);
      alltrfl=1;
      p+=ln;goto bp1;
    }
    if(ty==0x59){
      assert(ln==2);
      sf=get1();if(sf>=128)sf-=256;mi=get1();
      l1+=sprintf(l1,"Key %s %s\n",key(sf,mi,0),mi?"minor":"major");
      alltrfl=1;
      goto bp1;
    }
    l1+=sprintf(l1,"Meta Event  type &%02X  ",ty);
    for(i=0;i<ln;i++)l1+=sprintf(l1," %d",dat[p++]);l1+=sprintf(l1,"\n");
    goto bp1;
      }
      assert(0);
    }
    if(sth==0xD)sk=1; else sk=2;
    l1+=sprintf(l1,"ST &%02X",st);
    for(i=0;i<sk;i++)l1+=sprintf(l1," &%02X",dat[p++]);l1+=sprintf(l1,"\n");
  bp1:
    tri[tr].dt=getnnodt(st,tri[tr].e,&tri[tr].st);
    if(tri[tr].dt>=0){tri[tr].tt+=tri[tr].dt;tri[tr].p=p;} else {tri[tr].a=0;nat--;}
  bp2:
    if(sep){
      s=l1-l+1;t=sizeof(outline);u=(s+t-1)/t;
      assert((u+1)*t<=MAXLL&&ols>=MAXLL);
      outbuf->tr=tr;outbuf->all=alltrfl;outbuf->bn=bn;outbuf->l=(char*)(outbuf+1);
      if(ols-(u+1)*t<MAXLL){
    ols=MAXLL*10;outbuf->nx=(outline*)malloc(ols);
    if(!outbuf->nx){fprintf(stderr,"Couldn't malloc %d bytes (outbuf2)\n",ols);exit(1);}
      } else {
    outbuf->nx=outbuf+u+1;ols-=(u+1)*t;
      }
      memcpy(outbuf->l,l,s);outbuf=outbuf->nx;outbuf->nx=0;
    } else printf("%s",l);
  } /* main loop */

  if(sep)for(tr=0;tr<tracks;tr++){
    printf("# TRACK %d\n",tr);
    prb=-1;
    for(outbuf=outbuf0;outbuf->nx;outbuf=outbuf->nx){
      if((outbuf->all||outbuf->tr==tr)&&outbuf->bn!=prb){printf("\n");prb=outbuf->bn;}
      if(outbuf->all&&outbuf->tr!=tr)printf("# ");
      if(outbuf->all||outbuf->tr==tr)printf("%s",outbuf->l);
    }
    printf("\n");
  }

  printf("# Successfully parsed\n");
  printf("# Duration = %g seconds\n",ert/1000000);
  return 0;
}
[/quote]

Just a first step towards your goal...
Reply
#5
we can ask to somebody that eat C/C++ each breakfast to look at for debugging the translation from C to QB64...
Reply
#6
(08-26-2022, 01:48 AM)TempodiBasic Wrote: stuff
You need to use the "code" delimeters if you're going to post something very long and annoying like that.
Reply
#7
Quote:The only one _not_ doing stuff that I've already done in QB64 a zillion times is Assert. That's a weird one, but I read that it's basically C's way of handling illegal values, which we can do in QB64 with some regular IF/THEN logic.
I'm not an expert about programming in C. But "assert" is supposed to be used for debugging. "assert" is not a keyword, and it's not supposed to be a function adhering to POSIX standard. A good C compiler package would define it as a macro so that when "release" version of the app is to be compiled, there is no hint of it. On a few applications, it's more embarrassing instead to see "Runtime error!" crash box from M$CRT. During debug phase, everytime an "assert" is raised, the programmer is supposed to fix it, cannot become lazy about it. Provided source code with a lot of "asserts" indicates somebody didn't have the time to finish it.
Reply
#8
(08-26-2022, 01:48 AM)TempodiBasic Wrote: Just a first step towards your goal...

Well thank you. Yes even math expressions are weird - a single line of code can take 20 minutes to figure out.

I agree with your idea to find someone very familiar with C, but also with QuickBasic, to help check the translation.

Know any such QB64 users who might be interested in doing stuff with MIDI files? :-D
It would have to be for the fun of it, because I'm not getting paid for this!
Reply
#9
(08-26-2022, 02:06 AM)mnrvovrfc Wrote:
Quote:The only one _not_ doing stuff that I've already done in QB64 a zillion times is Assert. That's a weird one, but I read that it's basically C's way of handling illegal values, which we can do in QB64 with some regular IF/THEN logic.
I'm not an expert about programming in C. But "assert" is supposed to be used for debugging. "assert" is not a keyword, and it's not supposed to be a function adhering to POSIX standard. A good C compiler package would define it as a macro so that when "release" version of the app is to be compiled, there is no hint of it. On a few applications, it's more embarrassing instead to see "Runtime error!" crash box from M$CRT. During debug phase, everytime an "assert" is raised, the programmer is supposed to fix it, cannot become lazy about it. Provided source code with a lot of "asserts" indicates somebody didn't have the time to finish it.

Well if it's for debugging, that makes it easy. 

But it does provide some rules for what values are out of bounds for certain variables. 

You know far too much about this stuff that "not an expert" sounds more like humility. 

Might you be the expert we're looking for? 
:-D
Reply
#10
(08-26-2022, 01:48 AM)TempodiBasic Wrote:
(08-25-2022, 11:32 PM)madscijr Wrote: But beyond this MIDI to CSV project, you may have seen my many posts going on about this ridiculous pipe dream of getting the PC to read 2 or more USB mice as separate input devices, for local multiplayer Pong games I have dreamed of for a long time. Well, I found some code that supposedly does that, and it is also in C! I gather that this mouse stuff is much lower level operations, and needs to be in a language like C, so that piece of software will probably be a DLL that a QB64 program calls. So yet another reason I would like a C compiler! And there is at least one other project that similarly has me looking at C code (I don't recall what it was, but it's there!)

Really, I am not that excited about doing C. It's a necessary evil and I just want to get the dirty work done, so I can get back to writing QB64 code! 

Anyway I hope that explains where I'm coming from a little more. Thanks again for your reply!

Ok MadSciJr
reading your answer to my post this is the first attempt to translate 
mid2asc.c

I have read the instructions to use it here http://www.archduke.org/midi/instrux.html

I have used C, but I've not so practice with all its structures and ways to code... never used and seen Assert.h  https://en.wikipedia.org/wiki/Assert.h
and never seen this way to assign a value to a string variable 
  buf[p][0]="CDEFGAB"[(n+700000)%7];

it seems to me
buf(p,0) = "CDEFGAB " + ltrim$(str$(n+700000 mod 7))

I have tried to compile this C code with Gcc /G++ getting back only so much warning about dirty use/assign/modify of *char and *string not compatible with ISO C and using wstring/ Wchar_t*   https://en.wikipedia.org/wiki/C_string_handling


Starting from code you have posted mid2asc.c   I have put in QB64 comment by ' the code in BASIC in relation with code in C showed above.

Quote:/*
From: http://www.archduke.org/midi/

mid2asc:
Converts midi to ascii. Output can be in one of several formats
determined by command line flags.

Time of an event can be determined by one of
  (i) BA+CR:              Bar number+Crotchet within bar
  (ii) CR:                  Crotchet number from the very start
(iii) DT:                  Delta time from previous event - native midi format
  (iv) FOL[+/-crotchets]:  Time from end of previous note/rest on that track
  (v) SIM[+crotchets]:    Time from start of previous note/rest on that track

  Basic complication of mode (i) is that the Time signature affects
  the interpretation of time, and that time signature can change at
  any time on any track and should affect all tracks.

  Could make it closer to the spec. (e.g., skip over unknown chunk types)

  Midi files are going to be ambiguous, e.g. order of some events
  occuring at the same time, or whether choose to use running st mode,
  or whether to choose to use 0x8* or 0x9* for voff when
  velocity=0x40.

  Could print key changes from other tracks as comments in sep mode

  Copyright November 2002 A.P.Selby

  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  associated documentation files (the "Software"), to deal in the Software without restriction,
  including without limitation the rights to use, copy, modify, merge, publish, distribute,
  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all copies or
  substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
  NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <string.h>

#define MIN(x,y) ((x)<(y)?(x)Sady))
'Function MIN (x,y)
' if x<y then MIN = x else MIN = y
' End Function


#define MAX(x,y) ((x)>(y)?(x)Sady))
'Function Max (x,y)
' if X>Y then MAX = x else MAX = y
' End Function

#define MAXLL 10000
#define MAXCH 16
#define MAXNT 128
' Const MAXLL = 10000. MAXCH = 16, MAXNT = 128
unsigned char *dat;
' DIM dat as String

int p,siz,pr=0,raw,cro,fol,sep,frpad=9;
' DIM AS INT p,siz,pr,raw,cro,fol,sep,frpad: pr = 0: frpad = 9

int bgn[12][2]={{0,0},{0,1},{1,0},{2,-1},{2,0},{3,0},{3,1},{4,0},{4,1},{5,0},{6,-1},{6,0}};
/* ^ best guess note from pitch relative to keynote: (In C maj/A min this would be: C C# D Eb E F F# G G# A Bb B) */

' Dim    bgn (0 to 12,0 to 2) AS INT
' bgn (1,1) = 0 : bgn (1,2) = 0
' bgn (2,1) = 0 : bgn (2,2) = 2
' bgn (3,1) = 1 : bgn (3,2) = 0
' bgn (4,1) = 2 : bgn (4,2) = -1
' bgn (5,1) = 2 : bgn (5,2) = 0
' bgn (6,1) = 3 : bgn (6,2) = 0
' bgn (7,1) = 3 : bgn (7,2) = 1
' bgn (8,1) = 4 : bgn (8,2) = 0
' bgn (9,1) = 4 : bgn (9,2) = 1
' bgn (10,1) = 5 : bgn (10,2) = 0
' bgn (11,1) = 6 : bgn (11,2) = -1
' bgn (12,1) = 6 : bgn (12,2) = 0

int maj[14]={0,2,4,5,7,9,11, 12,14,16,17,19,21,23};
' Dim maj (0 to 14) AS Int
' maj(1) = 0:maj(2) = 2: maj(3) = 4:maj(4) = 5: maj(5) = 7:maj(6) = 9:maj(7) = 11:
' maj(8) = 12:maj(9) = 14:maj(10) = 16:maj(11) = 17:maj(12) = 19:maj(13) = 21:maj(14) = 23

void err(char *l){fprintf(stderr,"Error: %s\n",l);exit(1);}
'SUB err (l as string)
' n& = freefile
' open "Error.txt" for output as #n&
' print #n&, "Error: ";l
' close n&
' system
' End SUB

#define assrt(x,y) if(!(x))err(y);
' Function assrt(x,y)
' if NOT x then err(Str$(y))
' End Function

char *note2(int n,int s,int pad){
  static char buf[100][20];
  static int p=0;
  int i;
  p=(p+1)%100;
  buf[p][0]="CDEFGAB"[(n+700000)%7];
  i=1;
  while(s>0){assert(i<19);buf[p][i++]='#';s--;}
  while(s<0){assert(i<19);buf[p][i++]='b';s++;}
  while(n>=7){assert(i<19);buf[p][i++]='\'';n-=7;}
  while(n<0){assert(i<19);buf[p][i++]='-';n+=7;}
  if(pad)while(i<pad&&i<19)buf[p][i++]=' ';
  buf[p][i]=0;
  return buf[p];
}

' Function  note2 (n as Integer, s as Integer, pad as Integer)
' Static buf (0 to 100, 0 to 20) as string *1
' Static p as Integer
' REM p = 0 not translated because only at first creation p = 0, after it is static
' Dim i as integer
' p = (p+1) MOD 100
' buf(p,0) = "CDEFGAB " + ltrim$(str$(n+700000 mod 7))
' i = 1
' While s > 0
' if i < 19 then
'  buf(p,i) = "#"+ ltrim$(str$(s))
'  i = i +1
'  s = s-1
' else
' print "Error: "; i: " <19  false"
'  end
'  wend
' While s < 0
  '  if i < 19 then
  '    buf(p,i) = "b"+ ltrim$(str$(s))
  '    i = i +1
  '    s = s+1
  ' else
  '  print "Error: "; i: " <19  false"
  '  end
'wend
' While n >=7
'  if i < 19 then
'    buf(p,i) = "\"+ ltrim$(str$(n))
'    i = i +1
'    n = n-7
' else
'  print "Error: "; i: " <19  false": end
'wend
' While n < 0
'  if i < 19 then
'    buf(p,i) = "-"+ ltrim$(str$(n))
'    i = i+1
'    n = n +7
'  else
'    print "Error: "; i: " <19  false": end
' wend
' if pad then
'      while i<pad AND i<19
'        buf(p, i)= " "
'        i = i+1
'      wend
' end if
' buf (p,i) = 0
' note2 = buf(p) '<---- it returns the first memory allocation of array buf, this line must be modified to share buf(,) string array
' End Function

char *note(int n,int sf,int mi,int pad){
  int a,b,m,a0,b0;
  assert(n>=0&&n<128&&sf>=-128&&sf<=127);
  a0=(4*sf+7000)%7;b0=(sf+7001)/7-1000;/* (a0,b0) = keynote of (sf,mi) in pair form */
  n-=60+maj[a0]+b0;/* subtract keynote in single pitch form */
  m=(n+120000)%12;a=bgn[m][0];b=bgn[m][1];/* look up most likely pair form */
  return note2(a0+a+((n-m)/12)*7,maj[a0]+maj[a]-maj[a0+a]+b0+b,pad);/* evaluate cocycle! */
}
char *key(int sf,int mi,int pad){
  static char buf[100][21];
  static int p=0;
  int i,n;
  p=(p+1)%100;
  assert(sf>=-128&&sf<=127);
  if(mi)sf+=3;
  buf[p][0]="FCGDAEB"[(sf+7001)%7];
  n=(sf+7001)/7-1000;i=1;
  while(n>0){buf[p][i++]='#';n--;}
  while(n<0){buf[p][i++]='b';n++;}
  while(i<pad&&i<20)buf[p][i++]=' ';
  buf[p][i]=0;
  return buf[p];
}
int gcd(int a,int b){if(b==0)return a; else return gcd(b,a%b);}
int sprfrac(char *l,int n,int d,int sum,int pad){
  int a,g,s;
  s=0;
  if(sum){
    a=n/d;if(a){
      s=sprintf(l,"%d",a);n-=a*d;
      if(n==0)goto pad;
      s+=sprintf(l+s,"+");
    }
  }
  g=gcd(n,d);n/=g;d/=g;
  if(d!=1)s+=sprintf(l+s,"%d/%d",n,d); else s+=sprintf(l+s,"%d",n);
pad:
  if(s<pad){memmove(l+pad-s,l,s+1);memset(l,' ',pad-s);s=pad;}
  return s;
}
int sprfrac2(char *li,int n,int d){
  /* Prints the simplest (or near simplest) number x such that (int)(n/x+.5)=d */
  int i,t,dp;
  double m,r,x0,x1;
  char l[100],*l0,*l1;
  if(li)l0=li; else l0=l;
  x0=n/(d+.49);x1=n/(d-.49);l1=l0;
  /* Find minimum dp such that [x0,x1] intersects Z*10^(-dp) */
  for(dp=0,m=1;dp<15;dp++){
    r=floor(x1*m);
    if(r>x0*m){
      t=(int)(r/m);
      l1+=sprintf(l1,"%d",t);r-=t*m;
      if(dp){
    l1+=sprintf(l1,".");
    for(i=0;i<dp;i++){m/=10;t=(int)(r/m);l1+=sprintf(l1,"%d",t);r-=t*m;}
      }
      if((int)(n/atof(l0)+.5)!=d){fprintf(stderr,"Error 2 in prfrac2 n=%d d=%d\n",n,d);exit(1);}
      if(li)return l1-l0; else return printf("%s",l);
    }
    m*=10;
  }
  fprintf(stderr,"Error 1 in prfrac2 n=%d d=%d\n",n,d);exit(1);
}

void need(int n,char *l){
  if(p+n>siz){fprintf(stderr,"Unexpected EOF in %s, p=&%06x n=%d siz=&%06x\n",l,p,n,siz);exit(1);}
}
void skip(int n){need(n,"skip");p+=n;}
void match(char *l){
  int i;
  need(strlen(l),"match");
  for(i=0;l[i];i++)if(l[i]!=dat[p++]){
    fprintf(stderr,"Bad match, p=%X i=%d l=%s &%02X!=&%02X\n",p,i,l,l[i],dat[p-1]);exit(1);
  }
}
int getn(int n,int a){
  int i,t;
  need(n,"getn");
  for(i=0,t=0;i<n;i++)t=(t<<8)+dat[p+i];
  if(a)p+=n;
  return t;
}
int get1(){return getn(1,1);}
int get2(){return getn(2,1);}
int get3(){return getn(3,1);}
int get4(){return getn(4,1);}
int look1(){return getn(1,0);}
int getv(){
  int c,t;
  t=0;
  do{
    need(1,"getv");c=dat[p++];t=(t<<7)+(c&127);
  }while(c&128);
  return t;
}
int getnnodt(int st,int e,int *st1){
  /* Returns the dt to next non-noteoff event (raw=0), or next event (raw=1). return=-1 means end of track. */
  int t,p0,dt,sth;
  dt=0;
  while(p<e){
    dt+=getv();*st1=st;if(raw)return dt;
    p0=p;
    t=look1();if(t>=0x80)st=get1(); else assert(st>=0x80);
    sth=st>>4;
    if(sth==8||(sth==9&&dat[p+1]==0))skip(2); else {p=p0;return dt;}
  }
  return -1;
}

typedef struct {int tt,ch,nt,voff;} noteoffbuf;
int cmp(const void*x,const void*y){return ((noteoffbuf*)x)->tt-((noteoffbuf*)y)->tt;}

int main(int ac,char **av){
  int i,s,t,u,bf,bn,btt,fol1,rest,ch,dt,infinity,ln,mi,maxno,nat,non,ols,prb,help,
    sk,sf,st,sth,tis0,tis1,tis2,tpc,tr,trl,tt,ty,alltrfl,format,tracks,division,tempo,nobp[MAXCH][MAXNT];
  char l[MAXLL],*l1;
  double ert;
  typedef struct {int a,p0,l,e,p,pv,st,dt,tt,et,pt;} trackinfo;
  typedef struct fred {int all,bn,tr;char *l;struct fred *nx;} outline;
  trackinfo *tri;
  noteoffbuf *nob;
  outline *outbuf0,*outbuf;
  FILE *fpi;

  raw=cro=fol=sep=help=0;
  for(i=1;i<ac;i++){
    if(!strncmp(av[i],"-c",2))cro=1;
    if(!strncmp(av[i],"-f",2))fol=1;
    if(!strncmp(av[i],"-r",2))raw=1;
    if(!strncmp(av[i],"-s",2))sep=1;
    if(!strncmp(av[i],"-h",2)||!strncmp(av[i],"--help",6))help=1;
  }
  if(ac<2||help){
    fprintf(stderr,"Usage: mid2asc  [-c] [-f,-r] [-s] midifile > textfile\n\n");
    fprintf(stderr,"  Default timing is to use absolute time determined by BA+CR\n");
    fprintf(stderr,"  and use durations for notes.\n");
    fprintf(stderr,"-c Use absolute crotchets rather than BA+CR to determine time.\n");
    fprintf(stderr,"  Useful if inserting or deleting.\n");
    fprintf(stderr,"-f 'Follow' (take time from start or end of previous note) to determine time.\n");
    fprintf(stderr,"-r \"raw\" format: time determined by DT, and note-off events\n");
    fprintf(stderr,"  are used instead of note durations.\n\n");
    fprintf(stderr,"  Default is to print all tracks merged in chronological order.\n");
    fprintf(stderr,"-s Print all tracks separately.\n");
    exit(1);
  }
  if(fol&&raw){fprintf(stderr,"fol and raw modes cannot be combined\n");exit(1);}
  fpi=fopen(av[ac-1],"rb");if(!fpi){fprintf(stderr,"Couldn't open %s\n",av[ac-1]);exit(1);}
  fseek(fpi,0,SEEK_END);siz=ftell(fpi);rewind(fpi);
  if(pr>=1)printf("# File size %d byte%s\n",siz,siz==1?"":"s");
  dat=(unsigned char*)malloc(siz);if(!dat){fprintf(stderr,"Couldn't malloc %d bytes\n",siz);exit(1);}
  fread(dat,1,siz,fpi);p=0;fclose(fpi);
  maxno=siz/2;
  match("MThd");
  assert(get4()==6);
  format=get2();tracks=get2();division=get2();
  printf("format=%d tracks=%d division=%d\n",format,tracks,division);
  if(division&(1<<15))tpc=48; else tpc=division;
  tri=(trackinfo *)malloc(tracks*sizeof(trackinfo));assert(tri);
  nob=(noteoffbuf *)malloc(maxno*sizeof(noteoffbuf));assert(nob);
  infinity=0x7fffffff;

  for(tr=0;p<siz&&tr<tracks;tr++){
    match("MTrk");trl=get4();
    tri[tr].p0=p;tri[tr].l=trl;tri[tr].e=tri[tr].p0+tri[tr].l;
    p+=trl;
  }

  non=0;/* outside 'if' statement to stop warning */
  if(!raw){
    for(tr=0;tr<tracks;tr++){
      st=0;tt=0;p=tri[tr].p0;
      while(p<tri[tr].e){
    dt=getv();tt+=dt;
    t=look1();if(t>=0x80)st=get1(); else assert(st>=0x80);
    sth=st>>4;ch=st&15;
    if(sth==8||sth==9){
      t=get1();u=get1();
      if(sth==9){if(u)continue; else u=0x40;}
      assert(non<maxno);nob[non].tt=tt;nob[non].ch=ch;nob[non].nt=t;nob[non].voff=u;non++;
      continue;
    }
    if(sth==0xC||sth==0xD){skip(1);continue;}
    if(sth==0xF){
      if(st==0xF0||st==0xF7){skip(getv());continue;}
      if(st==0xFF){skip(1);skip(getv());continue;}
      fprintf(stderr,"Unrecognised status %02x\n",st);exit(1);
    }
    skip(2);
      }
    }
    qsort(nob,non,sizeof(noteoffbuf),cmp);
    for(ch=0;ch<MAXCH;ch++)for(t=0;t<MAXNT;t++)nobp[ch][t]=0;
  }

  nat=0;
  for(tr=0;tr<tracks;tr++){
    p=tri[tr].p=tri[tr].p0;
    t=getnnodt(0,tri[tr].e,&tri[tr].st);tri[tr].a=(t>=0);
    if(tri[tr].a){
      tri[tr].et=tri[tr].pt=0;tri[tr].dt=tri[tr].tt=t;tri[tr].p=p;tri[tr].pv=-1;nat++;
    }
  }
  sf=0;mi=0;/* Initial key C major */
  tis0=4;tis1=4;tis2=(tis0*4*tpc)/tis1;/* Initial time signature 4/4 */
  tempo=500000;/* Default usec/beat (= 120 bpm) */
  btt=0;bn=0;bf=0;ert=0;/* at time btt, bar number was bn (from 0), and part of bar complete was bf/tpc crotchets */
  prb=-1;
  ols=MAXLL*10;outbuf=outbuf0=(outline*)malloc(ols);outbuf->nx=0;
  if(!outbuf){fprintf(stderr,"Couldn't malloc %d bytes (outbuf1)\n",ols);exit(1);}
  while(nat>0){
    tt=infinity;tr=-1;alltrfl=0;
    for(i=0;i<tracks;i++)if(tri[i].a){
      t=tri[i].tt;if(fol&&tri[i].et<t)t=tri[i].et;
      if(t<tt){tr=i;tt=t;}
    }
    assert(tr>=0);
    p=tri[tr].p;t=look1();if(t>=0x80)st=get1(); else st=tri[tr].st;
    sth=st>>4;ch=st&15;
    assert(!(raw==0&&(sth==8||(sth==9&&dat[p+1]==0))));
    rest=(fol&&tri[tr].et<tri[tr].tt);
    fol1=(fol&&tri[tr].et<=tri[tr].tt);
    l1=l;
    if(tt>btt){
      ert+=(double)(tt-btt)*(double)tempo/(double)tpc;
      /* Elapsed crotchets=(btt-tt)/tpc, ticks/bar=tis2 */
      t=tt-btt+bf; bn+=t/tis2; bf=t%tis2; btt=tt;
    }
    if(!sep&&bn!=prb){l1+=sprintf(l1,"\n");prb=bn;}
    if(fol){
      u=0;
      if(fol1)l1+=sprintf(l1,"FOL"); else {
    l1+=sprintf(l1,"SIM");t=tri[tr].tt-tri[tr].pt;if(t){
      assert(t>0);l1+=sprintf(l1,"+");u=sprfrac(l1,t,tpc,1,0);l1+=u;u++;
    }
      }
      while(u<frpad+1){*(l1++)=' ';u++;}
      l1+=sprintf(l1," (");
    }
    if(raw){l1+=sprintf(l1,"DT ");l1+=sprfrac(l1,tri[tr].dt,tpc,1,frpad);l1+=sprintf(l1,"  (");}
    if(cro){l1+=sprintf(l1,"CR ");l1+=sprfrac(l1,tt,tpc,1,4+frpad);l1+=sprintf(l1,"  (");}
    l1+=sprintf(l1,"BA %4d  CR ",bn+1);l1+=sprfrac(l1,bf,tpc,1,frpad);
    if(cro)l1+=sprintf(l1,")");
    if(raw)l1+=sprintf(l1,")");
    if(fol)l1+=sprintf(l1,")");
    l1+=sprintf(l1,"  TR %2d  CH %2d  ",tr,ch+1);
    if(rest){
      l1+=sprintf(l1,"NT  R      ");l1+=sprfrac(l1,tri[tr].tt-tt,tpc,1,frpad);l1+=sprintf(l1,"\n");
      tri[tr].pt=tt;tri[tr].et=tri[tr].tt;goto bp2;
    }
    if(sth==8||sth==9){
      t=get1();u=get1();
      l1+=sprintf(l1,"NT  %s ",note(t,sf,mi,7));
      if(sth==8||(sth==9&&u==0)){
    assert(raw);
    l1+=sprintf(l1,"off");
    if(sth==8&&u!=0x40)l1+=sprintf(l1,"  voff=%d",u);
    l1+=sprintf(l1,"\n");
    goto bp1;
      }
      if(raw)l1+=sprintf(l1,"on"); else {
    assert(ch<MAXCH&&t<MAXNT);
    for(i=nobp[ch][t];i<non;i++)if(nob[i].tt>tt&&nob[i].ch==ch&&nob[i].nt==t)break;
    nobp[ch][t]=i;
    if(i<non){
      l1+=sprfrac(l1,nob[i].tt-tt,tpc,1,frpad);
      tri[tr].pt=tt;tri[tr].et=nob[i].tt;
    }else l1+=sprintf(l1,"infinity");
      }
      if(u!=tri[tr].pv){l1+=sprintf(l1,"  von=%d",u);tri[tr].pv=u;}
      if(!raw&&i<non&&nob[i].voff!=0x40)l1+=sprintf(l1,"  voff=%d",nob[i].voff);
      l1+=sprintf(l1,"\n");
      if(!raw&&i==non)l1+=sprintf(l1,"# Warning tr=%d p=&%X, note never turned off\n",tr,p);
      goto bp1;
    }
    if(sth==0xB){
      t=look1();if(t==7){
    t=get1();u=get1();
    l1+=sprintf(l1,"Channel volume %d\n",u);goto bp1;
      }
    }
    if(sth==0xC){t=get1();l1+=sprintf(l1,"Instrument %d\n",t+1);goto bp1;}
    if(sth==0xF){
      if(st==0xF0||st==0xF7){
    t=getv();l1+=sprintf(l1,"Sysex event &%02X ",st);
    for(i=0;i<t;i++)l1+=sprintf(l1," &%02X",dat[p++]);l1+=sprintf(l1,"\n");
    goto bp1;
      }
      if(st==0xFF){
    ty=get1();ln=getv();need(ln,"Meta");
    if(ty>=1&&ty<=7){
      l1+=sprintf(l1,"Text type %d: \"",ty);
      for(i=0;i<ln;i++){
        t=dat[p++];switch(t){
        case '\t':l1+=sprintf(l1,"\\t");break;
        case '\n':l1+=sprintf(l1,"\\n");break;
        case '\v':l1+=sprintf(l1,"\\v");break;
        case '\f':l1+=sprintf(l1,"\\f");break;
        case '\r':l1+=sprintf(l1,"\\r");break;
        case '\\':l1+=sprintf(l1,"\\\\");break;
        default:if(t!=0)l1+=sprintf(l1,"%c",t);break;
        }
      }
      l1+=sprintf(l1,"\"\n");
      goto bp1;
    }
    if(ty==0x2F){
      assert(ln==0);
      l1+=sprintf(l1,"End of track\n");goto bp1;
    }
    if(ty==0x51){
      assert(ln==3);tempo=get3();
      l1+=sprintf(l1,"Tempo ");l1+=sprfrac2(l1,60*1000000,tempo);l1+=sprintf(l1,"\n");
      alltrfl=1;
      goto bp1;
    }
    if(ty==0x58){
      assert(ln==4);assert(tt==btt);
      tis0=dat[p];tis1=1<<dat[p+1];assrt((tis0*4*tpc)%tis1==0,"New time signature not commensurate with division");
      tis2=(tis0*4*tpc)/tis1;
      l1+=sprintf(l1,"Time signature %d/%d, clocks/mtick %d, crotchets/32ndnote %d\n",tis0,tis1,dat[p+2],dat[p+3]);
      alltrfl=1;
      p+=ln;goto bp1;
    }
    if(ty==0x59){
      assert(ln==2);
      sf=get1();if(sf>=128)sf-=256;mi=get1();
      l1+=sprintf(l1,"Key %s %s\n",key(sf,mi,0),mi?"minor":"major");
      alltrfl=1;
      goto bp1;
    }
    l1+=sprintf(l1,"Meta Event  type &%02X  ",ty);
    for(i=0;i<ln;i++)l1+=sprintf(l1," %d",dat[p++]);l1+=sprintf(l1,"\n");
    goto bp1;
      }
      assert(0);
    }
    if(sth==0xD)sk=1; else sk=2;
    l1+=sprintf(l1,"ST &%02X",st);
    for(i=0;i<sk;i++)l1+=sprintf(l1," &%02X",dat[p++]);l1+=sprintf(l1,"\n");
  bp1:
    tri[tr].dt=getnnodt(st,tri[tr].e,&tri[tr].st);
    if(tri[tr].dt>=0){tri[tr].tt+=tri[tr].dt;tri[tr].p=p;} else {tri[tr].a=0;nat--;}
  bp2:
    if(sep){
      s=l1-l+1;t=sizeof(outline);u=(s+t-1)/t;
      assert((u+1)*t<=MAXLL&&ols>=MAXLL);
      outbuf->tr=tr;outbuf->all=alltrfl;outbuf->bn=bn;outbuf->l=(char*)(outbuf+1);
      if(ols-(u+1)*t<MAXLL){
    ols=MAXLL*10;outbuf->nx=(outline*)malloc(ols);
    if(!outbuf->nx){fprintf(stderr,"Couldn't malloc %d bytes (outbuf2)\n",ols);exit(1);}
      } else {
    outbuf->nx=outbuf+u+1;ols-=(u+1)*t;
      }
      memcpy(outbuf->l,l,s);outbuf=outbuf->nx;outbuf->nx=0;
    } else printf("%s",l);
  } /* main loop */

  if(sep)for(tr=0;tr<tracks;tr++){
    printf("# TRACK %d\n",tr);
    prb=-1;
    for(outbuf=outbuf0;outbuf->nx;outbuf=outbuf->nx){
      if((outbuf->all||outbuf->tr==tr)&&outbuf->bn!=prb){printf("\n");prb=outbuf->bn;}
      if(outbuf->all&&outbuf->tr!=tr)printf("# ");
      if(outbuf->all||outbuf->tr==tr)printf("%s",outbuf->l);
    }
    printf("\n");
  }

  printf("# Successfully parsed\n");
  printf("# Duration = %g seconds\n",ert/1000000);
  return 0;
}

Just a first step towards your goal...


"... very long and... " 

what?
b = b + ...
Reply




Users browsing this thread: 4 Guest(s)