/* ** M2MPU -- Convert Music file to MPU (time-tagged MIDI) ** psl 1/5/85 ** Music file format is: ** lyric note note note note ... ** Note format is MUTRAN-like: ** e.g. C4q for a quarter-note of middle C ** C#4qt for a quarter-triplet of middle C sharp ** Bb3e. for a dotted eighth of Bb below middle C */ #include #include #define TIECHAR '(' #define MAXEVENT 4096 #define MAXV 16 #define MAXCHAN 16 #define TSCALE 9 /* MPU clock multiples */ int Chksync = 1; /* check voice sync at each #BAR */ int Nn[] = { 9, 11, 0, 2, 4, 5, 7, }; int Tv[6]; int Nr; long Cc[MAXV]; /* current clock for voice */ int Kv[MAXV]; /* key velocity for voice */ int Chan[MAXV]; /* channel for voice */ int Vol[] = { /* for #SOLO */ 0x01, 0x0e, 0x1c, 0x2a, 0x38, 0x47, 0x55, 0x63, 0x71, 0x7f, }; double Artic[MAXV] = { /* articulation, note duty cycle */ 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, }; #define u_char unsigned char struct evstr { long when; u_char mode; u_char key; u_char vel; }; struct evstr Event[MAXEVENT]; /* key-on/off events */ struct evstr *Ep = Event; /* next unused Event[] */ struct evstr *Lop[MAXV]; /* last key-off event pointers */ extern double atof(); main(argc, argv) char *argv[]; { char *infile; int i; FILE *ifp; /****/setbuf(stderr, 0); infile = (char *) 0; for (i = 1; i < argc; i++) { if (argv[i][0] == '-') { switch (argv[i][1]) { case 'i': /* ignore sync problems */ Chksync = 0; break; default: goto syntax; } } else if (infile == (char *) 0) infile = argv[i]; else goto syntax; } if (infile) { if (!(ifp = fopen(infile, "r"))) { perror(infile); syntax: fprintf(stderr, "Usage: %s [-i] [file or stdin]\n", argv[0]); exit(2); } } else ifp = stdin; tempocalc(100); /* default tempo is 100 / minute */ m2mpu(ifp); exit(0); } m2mpu(ifp) FILE *ifp; { char a[7][64], buf[512]; int nf, nv, iv, q; double artic; int comp(); nv = 0; for (iv = MAXV; --iv >= 0; Lop[iv] = (struct evstr *) 0); for (Nr = 1; fgets(buf, sizeof buf, ifp) != NULL; Nr++) { nf = sscanf(buf, "%s%s%s%s%s%s%s", a[0], a[1], a[2], a[3], a[4], a[5], a[6]); if (nf <= 0) continue; if (a[0][0] == '#') { if (strcmp(a[0], "#ARTIC") == 0) { for (iv = nv; --iv >= 0; ) { artic = atof(a[(iv+1) > (nf-1)? nf - 1 : iv + 1]); if (artic <= 0. || artic > 1.) { fprintf(stderr, "Error in: %s", buf); fprintf(stderr, "Artic range is 0.1 to 1.0\n"); artic = 0.8; } Artic[iv] = artic; } } else if (strcmp(a[0], "#BAR") == 0) { for (iv = nv; --iv > 0 && Cc[iv] == Cc[0]; ); if (Chksync && iv) { fprintf(stderr, "Voices out of sync by line %d\n", Nr); for (iv = nv; --iv > 0; Cc[iv] = Cc[0]); } Ep->when = Cc[0]; Ep->mode = RT_TCWME; Ep++; } else if (strcmp(a[0], "#CHAN") == 0) { if (nf - 1 != nv) { fprintf(stderr, "Too %s voices in line %d\n", nf-1>nv? "many" : "few", Nr); continue; } for (iv = 0; iv < nv; iv++) { Chan[iv] = atoi(a[iv + 1]) - 1; if (Chan[iv] < 0 || Chan[iv] >= MAXCHAN) { fprintf(stderr, "Chan %d out of range\n", Chan[iv] + 1); } } } else if (strcmp(a[0], "#SOLO") == 0) { if (nf - 1 != nv) { fprintf(stderr, "Too %s voices in SOLO line %d\n", nf-1>nv? "many" : "few", Nr); continue; } for (iv = 0; iv < nv; iv++) { q = a[iv + 1][0]; if (q == '-') Kv[iv] = 0; else if ('0' <= q && q <= '9') Kv[iv] = Vol[q - '0']; else if (q == 'S') Kv[iv] = 21; else if (q == 'M') Kv[iv] = 64; else if (q == 'L') Kv[iv] = 106; else { fprintf(stderr, "Bad symbol '%s' in line %d\n", a[iv+1], Nr); continue; } } } else if (strcmp(a[0], "#SYNC") == 0) { q = 0; for (iv = nv; --iv > 0; ) if (Cc[iv] > Cc[0]) Cc[0] = Cc[iv]; for (iv = nv; --iv > 0; Cc[iv] = Cc[0]); } else if (strcmp(a[0], "#TEMPO") == 0) { tempocalc(atoi(a[1])); } else if (strcmp(a[0], "#VOICES") == 0) { mflush(nv); nv = nf - 1; for (iv = 0; iv < nv; iv++) { Cc[iv] = 0; Kv[iv] = 60; } } } else { if (nf != nv + 1) { fprintf(stderr, "%s on line %d: %s", nf > nv + 1? "extra garbage" : "missing note(s)", Nr, buf); continue; } for (iv = 0; iv < nv; iv++) addevents(iv, a[iv + 1]); } } for (iv = nv; --iv > 0 && Cc[iv] == Cc[0]; ); /* EOF sync check */ if (Chksync && iv > 0) fprintf(stderr, "Voices out of sync at EOF (V%d != V0)\n", iv); mflush(nv); } mflush(nv) { register int iv; qsort(Event, Ep - Event, sizeof *Ep, comp); dump(Cc[0]); Ep = Event; for (iv = nv; --iv >= 0; Cc[iv] = 0); } addevents(iv, code) char *code; { register char *cp; int oct, note, dur, k, dotdur, adur, tied; cp = code; if (*cp == '-') return; if (*cp == TIECHAR) { tied = *cp++; oct = 1; } else { tied = 0; if (*cp == 'R') oct = 0; else if (*cp < 'A' || 'G' < *cp) { fprintf(stderr, "Bad note format '%s' in line %d\n", code, Nr); return; } else { note = Nn[*cp++ - 'A']; if (*cp == 'b') { --note; cp++; } else if (*cp == '#') { note++; cp++; } oct = *cp - '0'; if (oct <= 0 || 9 < oct) { fprintf(stderr, "Octave out of range in '%s', line %d\n", code, Nr); return; } } cp++; } dur = Tv[tnum(*cp++)]; dotdur = dur / 2; while (*cp == '.') { dur += dotdur; dotdur /= 2; cp++; } while (*cp == 't') { dur = (2 * dur) / 3; cp++; } if (dur <= 0) { fprintf(stderr, "Bad time value for '%s' in line %d\n", code, Nr); return; } if (oct <= 0) { Cc[iv] += dur; return; } if (Kv[iv]) { adur = Artic[iv] * dur + 0.5; adur = adur <= 0? 1 : adur; if (tied) { if (Lop[iv]) Lop[iv]->when = Cc[iv] + adur; } else { k = 12 + 12 * oct + note; Ep->when = Cc[iv]; Ep->mode = 0x90 + Chan[iv]; Ep->key = k; Ep->vel = Kv[iv]; Ep++; Ep->when = Cc[iv] + adur; Ep->mode = 0x90 + Chan[iv]; Ep->key = k; Ep->vel = 0; Lop[iv] = Ep; Ep++; } } Cc[iv] += dur; } /* Key-off comes before TCWME comes before key-on. ** Lower notes come before higher ones. ** Softer notes come before louder ones. */ comp(a, b) struct evstr *a, *b; { register int i; i = (int)(a->when - b->when); if (i == 0) { if (a->mode == RT_TCWME) i = b->vel == 0? 1 : -1; else if (b->mode == RT_TCWME) i = a->vel == 0? -1 : 1; else if ((a->vel == 0) ^ (b->vel == 0)) i = a->vel - b->vel; else { i = a->key - b->key; if (i == 0) i = a->vel - b->vel; } } return(i); } dump(maxclock) long maxclock; { long last; struct evstr *evp; long putdt(); last = 0; for (evp = Event; evp < Ep; evp++) { last = putdt(last, evp->when, stdout); putc(evp->mode, stdout); if (evp->mode != RT_TCWME) { putc(evp->key, stdout); putc(evp->vel, stdout); } } if (last < maxclock) { putdt(last, maxclock, stdout); putc(MPU_NO_OP, stdout); } } long putdt(last, when, ofp) long last, when; FILE *ofp; { int dt; dt = (when - last) / TSCALE; last += dt * TSCALE; while (dt >= MPU_CLOCK_PERIOD) { putc(RT_TCIP, ofp); dt -= MPU_CLOCK_PERIOD; } putc((char) dt, ofp); return(last); } tempocalc(tempo) { register int cpq; cpq = (TSCALE * 12000) / tempo; Tv[tnum('w')] = 4 * cpq; Tv[tnum('h')] = 2 * cpq; Tv[tnum('q')] = cpq; Tv[tnum('e')] = (cpq + 1) / 2; Tv[tnum('s')] = (cpq + 2) / 4; Tv[tnum('t')] = (cpq + 4) / 8; } tnum(c) char c; { switch (c) { case 'w': return(0); case 'h': return(1); case 'q': return(2); case 'e': return(3); case 's': return(4); case 't': return(5); } fprintf(stderr, "Bad time value '%c' in line %d\n", c, Nr); return(5); }