/******************************************************************** Guitar tablature player. Copyright (C) 1993, 1994, 2020 by R.Harmsen. Reads a file containing a guitar tablature, and plays it on a Soundblaster or Adlib. It can also play on the PC-speaker (monophonic only; -p option), or create a WAV-file for MS-Windows (-w option). The -m option generates a Midi type-1 file, with the .mid-extension. The tablature (filename specified as first argument) must be organized vertically instead of the usual horizontal tablature. Each line is considered as 7 to 9 space-separated fixed-width columns, each containing a number, or spaces. The first column specifies the note-duration, expressed in units, which are taken to be sixteenths. The next six columns correspond to strings e to E, numbers denote finger positions, 0 being the open string. Spaces or tabs indicate that the string must be mute. The durations may consist of one integer, or of a pair separated by a slash (/), to indicate division. This is handy to denote notes shorter than the unit, and trioles, quintoles etc. The first line in the file (after introductory comment lines, if any) must indicate column-widths, and consist of spaces, and 7 to 9 non-space characters, at the rightmost positions of each column. Optionally, the second line may specify tempo in quarters per minute (default is 120). This line must then look like: #tempo 160 Any other lines whose first character is # are comments, and will be ignored. The virtual guitar that the tablature is written for is much larger than a real guitar: You may use negative positions, and positions above the 24th fret. And two super low strings A and E are optionally available too. But the range is limited to the range of MIDI notes 0 through 103, and no more than 9 notes can be played at any moment on the Soundblaster (If stereo capability is detected (for sbpro1), each instrument is played on its own FM-chip.) The -b option sets bass-guitar mode: instead of 6 to 8 guitar string, the first channel now has 4 or 5 bass strings, tuned G, D, A, E and C. Optionally, you can provide a second file, containing a second guitar part, but with its own independent timing. This second file must have the same name (before the dot) as the first, with the extension 2. For example xx.tab and xx.2 belong together. The notes from this second file are played a little louder than those from the first, to make it suitable for a solo part. The instrument tunes in the 53-tone system, meaning the octave is equally divided into 53 commas. This gives a rather good approximation of traditional western intonation, but also allows Turkish melodies. Therefore, decimal tonal distances may be specified. For example, 0.9 and 1.1 produce different notes, one 90 cents, the other 113 cent above the 0 level, being 4/53 and 5/53 of the octave respectively. (See TONES_PER_OCTAVE and other #defines. Later: variables.) ******************************************************************* Some directives are only effective when specified the -r (resync) option. In this case, the file is re-created after some modifications, controlled by the directives. The directives are: #Posit n This means from this point on you want all notes to be played in the specified position on the guitar neck. If for example n is 7, you get what in guitar music sheets is usually indicated by a Roman number VII. A negative position, or no specification from the beginning of the file, means no change. #Resync n With this directive, you specify that here a multiple of n 16ths should have passed since the previous resynchronization point (or the beginning of the file if no previous point specified). The value n should be the number of sixteenth in one bar, if you put the directive where a bar line would be. The effect is, that in the recreated file, all durations between this and the previous resync-point, are adjusted such that the point hits the nearest multiple of n. A negative or zero resync value leaves the previous region unchanged. Resync mode operates only on the specified file name, not on a second file. The original file is preserved, the result appears in a new file with the ".new" extension. Time indication, in sixteenth are added as comments in resync mode. *******************************************************************/ #include #include #include #include #include #include #include #include /* Interface to AdLib or Soundblaster */ #include "fmchip.h" /* Interface to UltraMid driver for Gravis Ultrasound */ #include "gravultr.h" #include "ritemidi.h" #include "snd.h" #include "timerchp.h" /* For optimized calculations, TICKS_PER_MEASURE should be a multiple of 16 */ #define TICKS_PER_SIXTEENTH 60 #define TICKS_PER_MEASURE (TICKS_PER_SIXTEENTH * 16) #define TICKS_PER_SIXTEENTH_B60 (TICKS_PER_SIXTEENTH / 60.0) #define NUM_INSTRUM_STRINGS 8 #define LINELEN 81 #if 0 #define TONALSYSTEM_53 1 /* But the 53 tone system can be activated per command line option -t, or at runtime by pressing t or T. Works only on Sound Blaster by the way, not Gravis Ultrasound. */ #if TONALSYSTEM_53 #define TONES_PER_OCTAVE 53 #define SBFREQ_ELEM SBFREQ53_ELEM #define SBFREQ_VALUE sbfreq53_value #else #define TONES_PER_OCTAVE 24 #define SBFREQ_ELEM SBFREQ24_ELEM #define SBFREQ_VALUE sbfreq24_value #endif #else int TONES_PER_OCTAVE = 24; size_t SBFREQ_ELEM = SBFREQ24_ELEM; fmchip_value_t *SBFREQ_VALUE = sbfreq24_value; #endif #define SNDFREQ_VALUE snd53_period #if TONALSYSTEM_53 /* 0xff and "unsigned char" was efficient (in 1990s DOS terms) for a system with 24 tones per octave, or 218 in total, just less than 256. But for 53 notes per octave, 8 bits are insufficient, Because there are 9*53+1 = 478 possible values. So then we use unsigned shorts (16 bits), and oxffff as the rest symbol. */ #define REST 0xffff struct eightnotes { int dura; unsigned short int note[NUM_INSTRUM_STRINGS]; }; #else #define REST 0xff struct eightnotes { int dura; unsigned char note[NUM_INSTRUM_STRINGS]; }; #endif #define BUFELEM (size_t)(56.0*1024/(sizeof (struct eightnotes))) static struct eightnotes *notesbuf1; static struct eightnotes *notesbuf2; static struct eightnotes *p1; static struct eightnotes *e1; static struct eightnotes *p2; static struct eightnotes *e2; static int t1 = 0, t2 = 0; static struct markpos_t { struct eightnotes *p1; struct eightnotes *p2; int t1; int t2; } mrkp[10]; int keyb_handler (void); int read_and_scan (int *p_argc, char **argv); struct eightnotes *file2buf ( long *p_totdura, char *filename, struct eightnotes *b, struct eightnotes *n, struct eightnotes *e, int file_1_or_2); int scan_1st (char *linebuf, int *posp, FILE *fp, struct eightnotes **pp); int scan_line ( long *p_totdura, char *linebuf, int *posp, struct eightnotes *pp, int file_1_or_2); void init_sb (void); /* The handler originally attached to the timer interrupt. To maintain time and date, this will still be called 18.2 times a second, even after speeding up the timer chip. */ static time_handler *orig_time_handler; static time_handler own_time_handler; void playnotes ( int *t, struct eightnotes *b, struct eightnotes **pp, struct eightnotes *e, int file_1_or_2); void keyusage (void); void show (void); void read_markposs (char *tabfilename); void save_markposs (char *tabfilename); int chkkeyb (void); char *expandtabs (register char *t, register char *s, register int l); char *set_extension (char *path_and_file, char *extension); void do_resync (void); void reduce_fraction (int *n, int *d, int f); int normalize (int raw); void make_wavfile (void); void make_midifile (void); void rite_chord (FILE *midi_fp, int channel, struct eightnotes *p); int to_twelve_tone (int in); static void setup_gravis (void); #ifdef DEBUG void ReportFreqs (int file_1_or_2, int note, char *notename); #endif /* Call ratio: once in how many times must the sped up interrupt handler call the original handler, so it is still being called 18.2 times a second. */ static long call_ratio, r_cnt; static int sixteenth_per_minute = 120 * 4; static int backward = 0; static int g_argc; static char **g_argv; static char scnd_name[LINELEN]; static char *dflt_editor = "ed"; static int pause = 0; static int pcsound = 0; static int gravis = 0; static int gravis_instrument_1 = 114; /* Steel drum */ static int gravis_instrument_2 = 12; /* Marimba */ static int makewave = 0; static int makemidi = 0; static int resync_mode = 0; static long totdura = 0; /* Minus twelve because a guitar sounds an octave lower than written */ static int midi_open_guitar_string[NUM_INSTRUM_STRINGS] = {76 - 12, 71 - 12, 67 - 12, 62 - 12, 57 - 12, 52 - 12, 45 - 12, 40 - 12}; static int midi_open_bassguitar_string[NUM_INSTRUM_STRINGS] = {67 - 24, 62 - 24, 57 - 24, 52 - 24, 48 - 24, 48 - 24, 48 - 24, 48 - 24}; static int *midi_open_string; static char *string_string; /* Filename for output file containing timing info */ FILE *fpsync = NULL; #ifdef DEBUG FILE *fplog = NULL; #endif /******************************************************************** *******************************************************************/ int main (int argc, char **argv) { #ifdef DEBUG fplog = fopen("guittabl.log", "wt"); #endif /* Default to guitar, not bass guitar */ midi_open_string = midi_open_guitar_string; string_string = "\n\ne' b g d A E e' b g d A E\n"; while (argc > 2 && argv[1][0] == '-') { if (strcmp(argv[1], "-p") == 0) { pcsound = 1; } else if (strcmp(argv[1], "-w") == 0) { makewave = 1; } else if (strcmp(argv[1], "-m") == 0) { makemidi = 1; } else if (strcmp(argv[1], "-b") == 0) { midi_open_string = midi_open_bassguitar_string; string_string = "\n\nG D A' E' C' e' b g d A E\n"; } else if (strcmp(argv[1], "-r") == 0) { resync_mode = 1; } else if (strncmp(argv[1], "-i", 2) == 0) { gravis_instrument_1 = atoi(argv[1] + 2); } else if (strncmp(argv[1], "-j", 2) == 0) { gravis_instrument_2 = atoi(argv[1] + 2); } /* 27 September 2020, tuning systems are now switchable at runtime */ else if (strncmp(argv[1], "-t", 2) == 0) { TONES_PER_OCTAVE = TONES_PER_OCTAVE == 24 ? 53 : 24; SBFREQ_ELEM = SBFREQ_ELEM == SBFREQ24_ELEM ? SBFREQ53_ELEM : SBFREQ24_ELEM; SBFREQ_VALUE = SBFREQ_VALUE == sbfreq24_value ? sbfreq53_value : sbfreq24_value; } argc--, argv++; } g_argc = argc; g_argv = argv; if ((notesbuf1 = (struct eightnotes *)malloc( (1 + BUFELEM) * sizeof (struct eightnotes))) == NULL || (notesbuf2 = (struct eightnotes *)malloc( (1 + BUFELEM) * sizeof (struct eightnotes))) == NULL) { fprintf(stderr, "Line %4d: Can't malloc enough space\n", __LINE__); return 1; } else { memset(notesbuf1, REST, (1 + BUFELEM) * sizeof (struct eightnotes)); memset(notesbuf2, REST, (1 + BUFELEM) * sizeof (struct eightnotes)); } /* Keep room for pointer running back */ notesbuf1++; notesbuf2++; if (read_and_scan(&argc, argv)) return 1; // Stop here if only profiling scanning speed // return 0; _setcursortype(_NOCURSOR); if (!makewave && !makemidi && !resync_mode) { long rate; keyusage(); /* Setup AdLib or Soundblaster */ init_sb(); /* Set up Gravis Ultrasound */ if (gus_set_ultrasound_hook() == 0) { setup_gravis(); } signal(SIGINT, (void(*)(int))SIG_IGN); rate = sixteenth_per_minute * TICKS_PER_SIXTEENTH_B60; call_ratio = r_cnt = (long)(0.5 + (rate / ((double)BASE_FREQ / 0xffff))); orig_time_handler = set_timer_interrupt(rate, own_time_handler); { /* Filename for output file containing timing info */ char newname[LINELEN]; strcpy(newname, g_argv[1]); set_extension(newname, "snc"); fpsync = fopen(newname, "wt"); } while (1) { static struct eightnotes *prev1, *prev2; if (keyb_handler()) { break; } if (p1 != prev1 || p2 != prev2) { prev2 = p1; prev2 = p2; } } reset_timer_interrupt(); save_markposs(argv[1]); fclose(fpsync); } else if (makewave) { make_wavfile(); } else if (makemidi) { make_midifile(); } else if (resync_mode) { do_resync(); } free(--notesbuf1); free(--notesbuf2); _setcursortype(_NORMALCURSOR); sound_off(); gus_all_midi_voices_off(); #ifdef DEBUG if (fplog) fclose (fplog); #endif return 0; } /******************************************************************** Look if a key was pressed, and act accordingly on some. Return 0 normally, 1 when wanting to quit. *******************************************************************/ int keyb_handler (void) { register int c = chkkeyb(); int keystroke_waiting = 0; long rate; if (!c) return 0; switch (c & 0xff) { case 'q': case 'Q': /* Quit the program */ return 1; case ' ': case '\t': /* Toggle between play and pause */ pause = !pause; gus_all_midi_voices_off(); break; case 's': case 'S': //system("c:\\command.com"); system(getenv("COMSPEC")); keyusage(); break; case 'e': case 'E': /* Run the editor of your choice in a subshell */ save_markposs(g_argv[1]); { char cmd[129]; char *e = getenv("EDITOR"); sprintf(cmd, "%s %s %s", e ? e : dflt_editor, g_argv[1], scnd_name); system(cmd); } read_and_scan(&g_argc, g_argv); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { /* Mark current position */ int i = (c & 0xff) - '0'; disable(); mrkp[i].p1 = p1; mrkp[i].p2 = p2; mrkp[i].t1 = t1; mrkp[i].t2 = t2; enable(); } break; case 'F': case 'f': case 'G': case 'g': { /* Go to a previously marked position */ int i = (getch() & 0xff) - '0'; if (i >= 0 && i <= 9 && mrkp[i].p1) { disable(); p1 = mrkp[i].p1; p2 = mrkp[i].p2; t1 = mrkp[i].t1; t2 = mrkp[i].t2; enable(); } } break; case 't': case 'T': /* 27 September 2020, tuning systems are now switchable at runtime */ /* Toggle between 24 (default) and 53 tone per octave */ TONES_PER_OCTAVE = TONES_PER_OCTAVE == 24 ? 53 : 24; SBFREQ_ELEM = SBFREQ_ELEM == SBFREQ24_ELEM ? SBFREQ53_ELEM : SBFREQ24_ELEM; SBFREQ_VALUE = SBFREQ_VALUE == sbfreq24_value ? sbfreq53_value : sbfreq24_value; /* Have to read and scan the input files after toggling the tuning. Same as when the letter r or R was pressed. So no break here, but a deliberate fall-through. */ case 'r': case 'R': /* Read and scan the (possibly meanwhile edited) files from disk */ save_markposs(g_argv[1]); read_and_scan(&g_argc, g_argv); break; case 'M': /* Write timing info to a file */ fprintf(fpsync, "%3d/%2d %4d <> %4d %3d/%2d\n", (int)(t1), TICKS_PER_SIXTEENTH, (int)(p1 - notesbuf1 + 2), (int)(p2 - notesbuf2 + 2), (int)(t2), TICKS_PER_SIXTEENTH ); break; case 'm': /* Save marks to disk (also done on exit) */ save_markposs(g_argv[1]); break; default: /* Not ASCII, look at scancode */ switch (c >> 8) { case 0x01: /* Escape key */ /* Quit the problem, alias for q or Q */ return 1; case 0x47: /* Home */ case 0x29: /* ` backtick */ /* Back to start of music piece */ p1 = notesbuf1; p2 = notesbuf2; t1 = t2 = 0; break; case 0x49: /* Page Up */ case 0x48: /* Arrow Up */ /* Increase playback speed (higher tempo) */ sixteenth_per_minute += 32; /* Fall through */ case 0x50: /* Page Down */ case 0x51: /* Page Down */ /* Decrease playback speed (lower tempo) */ if (sixteenth_per_minute > 120) sixteenth_per_minute -= 16; rate = 0.5 + sixteenth_per_minute * TICKS_PER_SIXTEENTH_B60; call_ratio = r_cnt = (long)(0.5 + (rate / ((double)BASE_FREQ / 0xffff))); rate = set_timer_speed(rate); sixteenth_per_minute = rate / TICKS_PER_SIXTEENTH_B60 + 0.5; gotoxy(9, wherey() - 3); cprintf("Tempo is %3d quarters a minute", sixteenth_per_minute / 4); gotoxy(1, wherey() + 3); break; case 0x4b: /* Cursor Left */ /* Fast skip back one measure */ backward = 1; /* Fall through */ case 0x4d: /* Cursor Right */ /* Fast skip forward one measure */ pause = 0; for (int fast = TICKS_PER_MEASURE; fast > 0; fast--) { if (keystroke_waiting) chkkeyb(); else keystroke_waiting = chkkeyb(); playnotes(&t1, notesbuf1, &p1, e1, 1); playnotes(&t2, notesbuf2, &p2, e2, 2); if (p1 < notesbuf1 || p2 < notesbuf2) { p1 = notesbuf1; p2 = notesbuf2; t1 = t2 = 0; break; } } if (!keystroke_waiting) gus_all_midi_voices_off(); backward = 0; keystroke_waiting = 0; break; } } return 0; } /******************************************************************** Read and scan the input file, and build a note buffer from its contents. Returns zero if success, 1 if failure. *******************************************************************/ int read_and_scan (int *p_argc, char **argv) { long this_totdura = 0; e1 = notesbuf1 + BUFELEM; e2 = notesbuf2 + BUFELEM; if (*p_argc <= 1 || (e1 = file2buf(&this_totdura, argv[1], notesbuf1, notesbuf1, e1, 1)) == notesbuf1) { fprintf(stderr, "Line %4d: Can't open input file %s, errno=%d\n", __LINE__, *p_argc > 1 ? argv[1] : "", errno); fprintf(stderr, "Usage: guittabl file\n"); return 1; } else { fprintf(stdout, "\n\tSoundblaster guitar tablature player\n"); fprintf(stdout, "\tCopyright (C) 1993-2020 by R.Harmsen\n\n"); fprintf(stdout, "\tTuning system is now %d tones per octave\n", (int)TONES_PER_OCTAVE); fprintf(stdout, "\tTablature file %s", argv[1]); } totdura = this_totdura; this_totdura = 0; /* Try to use a second tablature file; If not found, it is ignored. This only works if the first file has an extension .1, then the second is assumed to have .2, or else, a .2 results in .1 being the other extension. */ { int doit = 0; strncpy(scnd_name, argv[1], sizeof scnd_name); if (argv[2]) { strncpy(scnd_name, argv[2], sizeof scnd_name); doit = 1; argv++, *p_argc--; } else if (strcmp(argv[1] + strlen(argv[1]) - 2, ".1") == 0) { set_extension(scnd_name, ".2"); doit = 1; } else if (strcmp(argv[1] + strlen(argv[1]) - 2, ".2") == 0) { set_extension(scnd_name, ".1"); doit = 1; } if (doit) { e2 = file2buf( &this_totdura, scnd_name, notesbuf2, notesbuf2, e2, 2); if (e2 > p2) fprintf(stdout, " and %s", scnd_name); } fprintf(stdout, "\n\n"); } if (this_totdura > totdura) totdura = this_totdura; if (!makewave) { read_markposs(argv[1]); } else { p1 = notesbuf1; p2 = notesbuf2; } return 0; } /******************************************************************** Read and scan an input file, and build a note buffer from its contents. Returns a pointer to the structure just beyond the last one filled, or b if error. *******************************************************************/ struct eightnotes *file2buf ( long *p_totdura, char *filename, struct eightnotes *b, struct eightnotes *n, struct eightnotes *e, int file_1_or_2) { FILE *fp; char linebuf[LINELEN]; /* The pospos arrays know where the columns in the input files end */ int pospos[NUM_INSTRUM_STRINGS + 1]; if ((fp = fopen(filename, "rt")) == NULL) { if (file_1_or_2 == 1) fprintf(stderr, "Line %4d: Can't open input file %s, errno=%d\n", __LINE__, filename, errno); return b; } do { fgets(linebuf, sizeof linebuf, fp); } while (scan_1st(linebuf, pospos, fp, &n)); while (fgets(linebuf, sizeof linebuf, fp)) { scan_line(p_totdura, linebuf, pospos, n, file_1_or_2); if (++n >= e) { fprintf(stderr, "Line %4d: Insufficient memory, had to truncate some\n", __LINE__); fprintf(stderr, "Room for only %d elements\n", (int)(e - b)); break; } } fclose(fp); return n; } /******************************************************************** Scan the first line in a file, to record the proposed column positions. *******************************************************************/ int scan_1st (char *linebuf, int *posp, FILE *fp, struct eightnotes **pp) { register int i; register char *p; fpos_t fpos; char tempoline[LINELEN]; char l[LINELEN]; if (*linebuf == '#') { (*pp)->dura = -1; (*pp)++; return 1; } expandtabs(l, linebuf, LINELEN); for (i = 0; i < NUM_INSTRUM_STRINGS + 1; i++) { posp[i] = -1; } i = 0; for (p = l; *p && i < NUM_INSTRUM_STRINGS + 1; p++) { if (!isspace(*p)) { posp[i] = p - l + 1; i++; } } for ( ; i < NUM_INSTRUM_STRINGS + 1; i++) { posp[i] = (p += 3) - l; } for (i = 0; i < NUM_INSTRUM_STRINGS; i++) { if (posp[i] < 0 || posp[i] >= posp[i + 1]) { fprintf(stderr, "Invalid first line:\n%s\n", linebuf); exit(3); } } fgetpos(fp, &fpos); fgets(tempoline, sizeof tempoline, fp); if (strnicmp(tempoline, "#tempo", 6) == 0 || strnicmp(tempoline, "# tempo", 7) == 0) { sixteenth_per_minute = atoi(tempoline + 7) * 4; (*pp)->dura = -1; (*pp)++; } else { fsetpos(fp, &fpos); } return 0; } /******************************************************************** *******************************************************************/ int scan_line ( long *p_totdura, char *linebuf, int *posp, struct eightnotes *pp, int file_1_or_2) { static float diff; float dura; register int i; register char *p; char l[LINELEN]; if (*linebuf == '#') { pp->dura = -1; return 1; } expandtabs(l, linebuf, LINELEN); /* Remove comments after # character */ if ((p = strchr(l, '#')) != NULL) { memset(p, '\0', LINELEN - (p - l)); } for (i = 0; i < NUM_INSTRUM_STRINGS + 1; i++) { /* Even the position just left of the previous column may be used, provided the previous column has a space there for separation */ if (l[posp[i] - 1] == ' ') l[posp[i] - 1] = '\0'; else l[posp[i]] = '\0'; } /* First decode the note duration, at the start of the line */ if ((p = strchr(l, '/')) != NULL) { int denom = atoi(p + 1); dura = 0; if (denom > 0) dura = (float)((TICKS_PER_MEASURE >> 4) * atol(l)) / (float)denom; else fprintf(stdout, "Invalid duration denominator, line was\n%s\n", linebuf); if (dura <= 0) fprintf(stdout, "Invalid duration, line was\n%s\n", linebuf); } else { dura = (TICKS_PER_MEASURE >> 4) * atol(l); } pp->dura = dura + 0.5 + diff; diff += dura - pp->dura; *p_totdura += pp->dura; /* Now continue with the notes of various strings. mos = midi note of open string. */ int *midiOpenStr = file_1_or_2 == 1 ? midi_open_string : midi_open_guitar_string; for (i = 1; i < NUM_INSTRUM_STRINGS + 1; i++) { p = l + posp[i - 1]; if (!*p) p++; if (*p && strspn(p, " \n") != strlen(p)) { register int n; char *slashpos; if ((slashpos = strchr(p, '/')) != NULL) { /* Special case, added 28 September 2020: fret position as the ratio of two integers, e.g. 9/8 for a major second. Note that is a frequency ratio, NOT the number of semitones written as a ratio! In fact, this only makes sense on the A-string and in 53s equidistant tuning. */ long denom = atol(slashpos + 1); n = 0; if (denom > 0) { double ratio, midinum; ratio = (double)atol(p) / (double)denom; midinum = log(ratio) / log(2.0) * 12; n = 0.5 + TONES_PER_OCTAVE / 12.0 * (midinum + midiOpenStr[i - 1]); } else { fprintf(stdout, "Invalid note denominator, line was\n%s\n", linebuf); } if (n <= 0) fprintf(stdout, "Invalid note, line was\n%s\n", linebuf); } else if (strchr(p, '.')) { /* Fret position specified as fractional number */ n = 0.5 + TONES_PER_OCTAVE / 12.0 * (atof(p) + midiOpenStr[i - 1]); } else { /* Fret position specified as integer number */ /* Optimization, avoiding floating calculations if no decimal point specified. September 2020: this made sense for 1990s hardware, with or without a mathematical copressor (80387 etc.), when floating point arithmetic could be 100s or 1000s of times slower than integer arithmetic. Those days are over, now nearly as fast or even faster. I leave it in as a curiosity. */ n = (1 + ((long)TONES_PER_OCTAVE * (atol(p) + midiOpenStr[i - 1])) / 6L) >> 1; } if (n > 0 && n < SBFREQ_ELEM) { /* Optimization, so the index number fits into one 8-bit unsigned character, even if a tuning with 53 notes per octave is used. */ pp->note[i - 1] = n - 2 * TONES_PER_OCTAVE; } else { fprintf(stdout, "Invalid note, line was\n%s\n", linebuf); } } } return 0; } /******************************************************************** Set up frequency table. Set up instruments. *******************************************************************/ void init_sb (void) { int channel; /* Init soundblaster, set instruments etc. */ init_fmchip(); set_left_right(SB_BOTH); set_percussion(0); seti_am_vib(1, 1); for (channel = 1; channel <= 9; channel++) { set_synth_mode(channel, FREQMODU_SYNTH); // set_synth_mode(channel, ADDITIVE_SYNTH); set_ws (channel, CARRIER , 0); set_ws (channel, MODULATOR, 0); set_ksl(channel, CARRIER , 2); set_ksl(channel, MODULATOR, 2); set_ksr(channel, CARRIER , 1); set_ksr(channel, MODULATOR, 1); set_am_vib(channel, CARRIER , 0, 0); set_am_vib(channel, MODULATOR, 0, 0); set_envelope(channel, CARRIER, 0, /* no sustain */ 9, /* attack */ 1, /* decay */ 15, /* sustain */ 1 /* release */ ); set_envelope(channel, MODULATOR, 0, /* no sustain */ 9, /* attack */ 1, /* decay */ 15, /* sustain */ 1 /* release */ ); set_multiplier(channel, CARRIER, 1); set_multiplier(channel, MODULATOR, 3); } } /******************************************************************** *******************************************************************/ static time_handler own_time_handler { if (p1 >= e1 && p2 >= e2 || p1 < notesbuf1 || p2 < notesbuf2) { p1 = notesbuf1; p2 = notesbuf2; t1 = t2 = 0; } playnotes(&t1, notesbuf1, &p1, e1, 1); playnotes(&t2, notesbuf2, &p2, e2, 2); if (--r_cnt <= 0) { /* Reset counter BEFORE calling the previous timer tick handler, because it may take a while, which could result in a new call before the previous one was finished */ r_cnt = call_ratio; orig_time_handler(); } else { /* Tell the 8259 interrupt handler, that this one has been handled, so new interrupts may be accepted (the original handler does this automatically, so if called this need not be done */ outportb(0x20, 0x20); } } /******************************************************************** *******************************************************************/ void playnotes ( int *t, struct eightnotes *b, struct eightnotes **pp, struct eightnotes *e, int file_1_or_2) { static prev_midinote[16]; static func_in_use = 0; struct eightnotes *p = *pp; if (pause || p >= e || func_in_use) return; func_in_use = 1; if (*t == 0 && p->dura > 0) { int s; /* Reserve 8 SB-channels for the first guitar, and supposing the second guitar is often monophonic, use channel 9 for that. But if the second guitar does play more notes at the same time, use lower channels, beginning with 8 and 7, which are hardly ever used for the first guitar.*/ int c2 = 9; int all_strings_rest = 1; /* Display notes on screen */ show(); /* Play notes for this buffer */ if (gravis) { /* Dampen all previous notes on all strings */ for (s = 0; s < NUM_INSTRUM_STRINGS; s++) { int midi_ch = s + (file_1_or_2 == 1 ? 0 : 8); if (midi_ch == 9) midi_ch = 8; gus_send_midibyte(0x90 + midi_ch); gus_send_midibyte(prev_midinote[midi_ch]); gus_send_midibyte(0); } } else { set_left_right(file_1_or_2 == 1 ? SB_LEFT : SB_RIGHT); } for (s = 0; s < NUM_INSTRUM_STRINGS; s++) { if (p->note[s] != REST) { register int i = p->note[s] + 2 * TONES_PER_OCTAVE; all_strings_rest = 0; if (pcsound) { set_period(SNDFREQ_VALUE[i]); sound_on(); } else { if (!gravis) { /* Sound Blaster */ int ch = (file_1_or_2 == 1 ? s + 1 : c2--); /* 0 = Maximum, 63 = minimum */ set_level(ch, CARRIER, file_1_or_2 == 2 ? 0 : 4); set_level(ch, MODULATOR, file_1_or_2 == 2 ? 19 : 23); /* Play on AdLib or SoundBlaster. */ set_frequency_v(ch, SBFREQ_VALUE[i]); note_on(ch); } else { /* Send a midi-event to the Ultramid TSR, to play on Gravis UltraSound. Not if TSR not loaded, or no card available. */ int midi_note; /* Each string has its own Midi channel here, except that channel 9 (counted from 0) is the drum channel, so we let the B-string share a channel with the high E). */ int midi_ch = s + (file_1_or_2 == 1 ? 0 : 8); if (midi_ch == 9) midi_ch = 8; // Switch off any previous not on same string gus_send_midibyte(0x90 + midi_ch); gus_send_midibyte(prev_midinote[midi_ch]); gus_send_midibyte(0); midi_note = to_twelve_tone(p->note[s]); // Note-on Midi-event gus_send_midibyte(0x90 + midi_ch); // The note to be played. prev_midinote[midi_ch] = midi_note; gus_send_midibyte(midi_note); // Velocity always the same here, except that file // number two is played a little louder. gus_send_midibyte(0x60 + (file_1_or_2 == 1 ? -5 : +5)); } } } } if (gravis && all_strings_rest) { /* Dampen all previous notes on all strings */ for (s = 0; s < NUM_INSTRUM_STRINGS; s++) { int midi_ch = s + (file_1_or_2 == 1 ? 0 : 8); if (midi_ch == 9) midi_ch = 8; gus_send_midibyte(0x90 + midi_ch); gus_send_midibyte(prev_midinote[midi_ch]); gus_send_midibyte(0); } } if (backward) { while (p1 >= notesbuf1 && p2 >= notesbuf2 && (--(*pp))->dura < 0) ; *t = p->dura - 1; } else { (*t) = 1; } } else if (backward) { (*t)--; } else if (++(*t) >= p->dura) { /* Duration for current note has passed, prepare to play the next */ while ((++(*pp))->dura < 0 && p1 <= e1 && p2 <= e2) ; *t = 0; } func_in_use = 0; } /******************************************************************** *******************************************************************/ void keyusage (void) { char *e = getenv("EDITOR"); fprintf(stdout, "\n\tKeys:\n"); fprintf(stdout, "\tEsc, Q or q : Quit playing\n"); fprintf(stdout, "\tHome : Replay from start\n"); fprintf(stdout, "\tRight arrow : Fast backward\n"); fprintf(stdout, "\tLeft arrow : Fast forward\n"); fprintf(stdout, "\t0, 1, 2 etc.: Mark up to 10 positions\n"); fprintf(stdout, "\tg0, g1, etc.: Goto one of the marks\n"); fprintf(stdout, "\tm : Save marks to disk (also done on exit)\n"); fprintf(stdout, "\tM : Write timing info to a file\n"); fprintf(stdout, "\tS or s : Shell to Dos (\"exit\" to return)\n"); fprintf(stdout, "\tE or e : Run editor \"%s\"\n", e ? e : dflt_editor); fprintf(stdout, "\tR or r : Rescan files and marks\n"); fprintf(stdout, "\tT or t : Toggle 24/53 and rescan\n"); fprintf(stdout, "\tTab or Space: Pause / Resume\n"); fprintf(stdout, "\tPgUp/ArrowUp: Play faster\n"); fprintf(stdout, "\tPgDn/ArrowDn: Play slower\n"); fprintf(stdout, string_string); } /******************************************************************** Show current position, and notes played. *******************************************************************/ void show (void) { int i; static char *note_names[] = { "C","C#","D","D#","E","F","F#","G","G#","A","Bb","B","."}; for (i = 0; i < NUM_INSTRUM_STRINGS; i++) { int n; disable(); if (p1 >= e1 || p1->note[i] == REST) n = 12; else { n = 0.5 + p1->note[i] * 12.0 / TONES_PER_OCTAVE; n %= 12; } enable(); fprintf(stdout, "%-3s", note_names[n]); #ifdef DEBUG if (p1->note[i] != REST) ReportFreqs(1, p1->note[i], note_names[n]); #endif } fprintf(stdout, "<%4d:", (int)(p1 - notesbuf1 + 2)); if (p2 < e2) { fprintf(stdout, "%4d> ", (int)(p2 - notesbuf2 + 2)); for (i = 0; i < NUM_INSTRUM_STRINGS; i++) { int n; disable(); if (p2 >= e2 || p2->note[i] == REST) n = 12; else { n = 0.5 + p2->note[i] * 12.0 / TONES_PER_OCTAVE; n %= 12; } enable(); fprintf(stdout, "%-3s", note_names[n]); #ifdef DEBUG if (p2->note[i] != REST) ReportFreqs(2, p2->note[i], note_names[n]); #endif } } putc('\r', stdout); } /******************************************************************** Read marked positions from a disk file, if previously saved. *******************************************************************/ void read_markposs (char *tabfilename) { char mark_file[129]; FILE *fp; int i; struct eightnotes *p; long ticks, t; strcpy(mark_file, tabfilename); set_extension(mark_file, "mrk"); if ((fp = fopen(mark_file, "rt")) == NULL) return; for (i = 0; i < sizeof mrkp / sizeof mrkp[0]; i++) { fscanf(fp, "%ld", &ticks); for (t = ticks, p = notesbuf1; t > p->dura && p < e1; p++) { if (p->dura > 0) t -= p->dura; } mrkp[i].p1 = p; mrkp[i].t1 = t; for (t = ticks, p = notesbuf2; t > p->dura && p < e2; p++) { if (p->dura > 0) t -= p->dura; } mrkp[i].p2 = p; mrkp[i].t2 = p < e2 ? t : 0; } fscanf(fp, "%ld", &ticks); for (t = ticks, p = notesbuf1; t > p->dura && p < e1; p++) { if (p->dura > 0) t -= p->dura; } p1 = p; t1 = t; for (t = ticks, p = notesbuf2; t > p->dura && p < e2; p++) { if (p->dura > 0) t -= p->dura; } p2 = p; t2 = p < e2 ? t : 0; fclose(fp); } /******************************************************************** Write marked positions to a disk file, so they can be restored when playing the song again. *******************************************************************/ void save_markposs (char *tabfilename) { char mark_file[129]; FILE *fp; int i; struct eightnotes *p; long ticks = 0; strcpy(mark_file, tabfilename); set_extension(mark_file, "mrk"); if ((fp = fopen(mark_file, "wt")) == NULL) return; for (i = 0; i < sizeof mrkp / sizeof mrkp[0]; i++) { ticks = 0; if (mrkp[i].p1) { for (p = notesbuf1; p < mrkp[i].p1 && p < e1; p++) { if (p->dura > 0) ticks += p->dura; } ticks += mrkp[i].t1; } fprintf(fp, "%09ld\n", ticks); } /* Ten marks now saved, save also current position */ ticks = 0; for (p = notesbuf1; p < p1 && p < e1; p++) { if (p->dura > 0) ticks += p->dura; } ticks += t1; fprintf(fp, "%09ld\n", ticks); fclose(fp); } /******************************************************************** Look if a character was added to the keyboard buffer. If so, return its scancode, else return 0. *******************************************************************/ int chkkeyb (void) { union REGS r; r.h.ah = 0x01; int86(0x16, &r, &r); if (!(r.x.flags & 0x40)) { r.h.ah = 0x00; int86(0x16, &r, &r); return r.x.ax; } return 0; } /******************************************************************** Copies string s to t, but expanding tab characters to the appropriate number of spaces. No more than l bytes are written in t, including the null-byte. Expandtabs returns t. *******************************************************************/ char *expandtabs (register char *t, register char *s, register int l) { /* Optimization: if no TABs in the line, then no need to expand. And standard ANSI-functions are much faster than looping with a pointer, as they use specialized machine instructions */ if (strchr(s, '\t') == NULL) { strncpy(t, s, l); return t; } char *orig_t = t; const int TAB = 8; while (l > 0 && *s) { if (*s != '\t') { /* Simple copy */ *t++ = *s++; l--; } else { /* Tab expansion */ int i = t - orig_t; i = (i / TAB + 1) * TAB - i; while (i--) { *t++ = ' '; l--; } s++; } } memset(t, '\0', l); return orig_t; } /******************************************************************** Changes or adds the extension (part after dot) in a filename, possibly with pathname. *******************************************************************/ char *set_extension (char *path_and_file, char *extension) { char *p; if ((p = strrchr(path_and_file, '.')) != NULL) *p = '\0'; if (extension[0] != '.') strcat(path_and_file, "."); strcat(path_and_file, extension); return path_and_file; } /******************************************************************** Recreate files after specified modifications, like re-synchronization to a specified number of sixteenths, and moving fingers to a specified position on the guitar neck. *******************************************************************/ void do_resync (void) { char newname[LINELEN]; char linebuf[LINELEN]; char lb[LINELEN]; FILE *fpi, *fpo; struct eightnotes *p; long totdura = 0; struct eightnotes *prev_sync; long prev_syncdura = 0; int pospos_1[NUM_INSTRUM_STRINGS + 1]; strcpy(newname, g_argv[1]); set_extension(newname, "new"); if ((fpi = fopen(g_argv[1], "rt")) == NULL) { fprintf(stderr, "Can't open %s for reading, errno=%d\n", g_argv[1], errno); return; } p = notesbuf1; do { fgets(linebuf, sizeof linebuf, fpi); } while (scan_1st(linebuf, pospos_1, fpi, &p)); /* Examine the buffer, and resync where required */ for (prev_sync = p; p < e1; p++) { fgets(linebuf, sizeof linebuf, fpi); if (p->dura < 0) { if (strnicmp(linebuf, "#Resync", 7) == 0) { int resync = atoi(linebuf + 7); long should_be = totdura - prev_syncdura; double corr_factor; double residue = 0; resync = resync * (TICKS_PER_MEASURE / 16); if (resync > 0) { should_be = (long)((double)should_be / resync + 0.5) * resync; if (totdura > prev_syncdura) { corr_factor = (double)should_be / (totdura - prev_syncdura); if ((float)corr_factor != 1.0) for (struct eightnotes *pp = prev_sync; pp < p; pp++) { if (pp->dura > 0) { double newdura = pp->dura * corr_factor; pp->dura = (int)(newdura + 0.5 + residue); pp->dura = normalize(pp->dura); residue += newdura - pp->dura; } } } } prev_sync = p; prev_syncdura = totdura; } } else { totdura += p->dura; } } if ((fpo = fopen(newname, "wt")) == NULL) { fprintf(stderr, "Can't open %s for writing\n", newname); return; } int posit = -1; fseek(fpi, 0, SEEK_SET); p = notesbuf1; do { fgets(linebuf, sizeof linebuf, fpi); fputs(linebuf, fpo); } while (scan_1st(linebuf, pospos_1, fpi, &p)); double sixteenths_passed = 0; for (prev_sync = p; p < e1; p++) { char *newl; fgets(lb, sizeof lb, fpi); expandtabs(linebuf, lb, sizeof linebuf); if ((newl = strrchr(linebuf, '\n')) != NULL && newl - linebuf < pospos_1[NUM_INSTRUM_STRINGS]) { memset(newl, ' ', pospos_1[NUM_INSTRUM_STRINGS] - (newl - linebuf)); linebuf[pospos_1[NUM_INSTRUM_STRINGS]] = '\n'; linebuf[pospos_1[NUM_INSTRUM_STRINGS] + 1] = '\0'; } if (strnicmp(lb, "#Resync", 7) == 0 || strncmp(lb, "#------", 7) == 0) { sixteenths_passed = 0; } if (p->dura < 0) { /* posit is for transposing to some other position */ if (strnicmp(linebuf, "#Posit", 6) == 0) { posit = atoi(linebuf + 6); } fputs(linebuf, fpo); } else { char buf1[40], buf2[12]; int numerator, denominator; int len; numerator = p->dura; denominator = TICKS_PER_MEASURE / 16; reduce_fraction(&numerator, &denominator, 2); reduce_fraction(&numerator, &denominator, 3); reduce_fraction(&numerator, &denominator, 5); if (denominator == 1) sprintf(buf1, "%d", numerator); else sprintf(buf1, "%d/%d", numerator, denominator); sprintf(buf2, "%-*s", pospos_1[0], buf1); len = strlen(buf2); // (Not needed, \n done below) // if (len > strlen(linebuf) - 1) // sprintf(linebuf + len, "\n"); strncpy(linebuf, buf2, len); if (posit >= 0) { /* Reorganise notes on strings, to achieve the requested positions */ int maxi = midi_open_string == midi_open_guitar_string ? 5 : 3; for (int i = maxi; i >= 0; i--) { int pl = to_twelve_tone(p->note[i]); if (p->note[i] != REST && pl - midi_open_string[i] < posit) { for (int j = i + 1; j <= maxi; j++) { if (p->note[j] == REST && pl - midi_open_string[j] >= posit) { p->note[j] = p->note[i]; p->note[i] = REST; sprintf(buf2, "%-*s", pospos_1[i+1] - pospos_1[i], ""); strncpy(linebuf + pospos_1[i], buf2, strlen(buf2)); sprintf(buf2, "%*d", pospos_1[j+1] - pospos_1[j], pl - midi_open_string[j]); strncpy(linebuf + pospos_1[j], buf2, strlen(buf2)); break; } } } else if (p->note[i] != REST && pl - midi_open_string[i] > posit + 4) { for (int j = i - 1; j >= 0; j--) { if (p->note[j] == REST && pl - midi_open_string[j] <= posit + 4) { p->note[j] = p->note[i]; p->note[i] = REST; sprintf(buf2, "%-*s", pospos_1[i+1] - pospos_1[i], ""); strncpy(linebuf + pospos_1[i], buf2, strlen(buf2)); sprintf(buf2, "%*d", pospos_1[j+1] - pospos_1[j], pl - midi_open_string[j]); strncpy(linebuf + pospos_1[j], buf2, strlen(buf2)); break; } } } } } /* Write timing information as a comment */ sixteenths_passed += (double)numerator / (double)denominator; sprintf(buf1, "# 16ths = %6.2f\n", sixteenths_passed); /* Remove a possible previous count, so it will be overwritten */ { char *p = strstr(linebuf, "# 16ths ="); if (p) { p[0] = '\n'; p[1] = '\0'; } } strcpy(linebuf + strlen(linebuf) - 1, buf1); fputs(linebuf, fpo); } } fclose(fpi); fclose(fpo); } /******************************************************************** *******************************************************************/ void reduce_fraction (int *n, int *d, int f) { while (*n % f == 0 && *d % f == 0) { *n /= f; *d /= f; } } /******************************************************************** Given a number of ticks, look for the nearest acceptable duration in fractions of a sixteenth. This is controlled by a table, which allows only decent looking durations. *******************************************************************/ int normalize (int raw) { static int ok_tab[TICKS_PER_SIXTEENTH + 1] = { /* 0/60 */ 1, /* 1/60 */ 0, /* 2/60 */ 0, /* 3/60 */ 0, /* 4/60 */ 0, /* 4/60 */ 0, /* 6/60 */ 0, /* 7/60 */ 0, /* 8/60 */ 0, /* 9/60 */ 0, /* 10/60 */ 0, /* 11/60 */ 0, /* 12/60 */ 0, /* 13/60 */ 0, /* 14/60 */ 0, /* 1/4 */ 1, /* 16/60 */ 0, /* 17/60 */ 0, /* 18/60 */ 0, /* 19/60 */ 0, /* 1/3 */ 1, /* 21/60 */ 0, /* 22/60 */ 0, /* 23/60 */ 0, /* 24/60 */ 0, /* 25/60 */ 0, /* 26/60 */ 0, /* 27/60 */ 0, /* 28/60 */ 0, /* 29/60 */ 0, /* 1/2 */ 1, /* 31/60 */ 0, /* 32/60 */ 0, /* 33/60 */ 0, /* 34/60 */ 0, /* 35/60 */ 0, /* 36/60 */ 0, /* 37/60 */ 0, /* 38/60 */ 0, /* 39/60 */ 0, /* 2/3 */ 1, /* 41/60 */ 0, /* 42/60 */ 0, /* 43/60 */ 0, /* 44/60 */ 0, /* 3/4 */ 1, /* 46/60 */ 0, /* 47/60 */ 0, /* 4/5 */ 0, /* 49/60 */ 0, /* 50/60 */ 0, /* 51/60 */ 0, /* 52/60 */ 0, /* 53/60 */ 0, /* 54/60 */ 0, /* 55/60 */ 0, /* 56/60 */ 0, /* 57/60 */ 0, /* 58/60 */ 0, /* 59/60 */ 0, /* 1/1 */ 1, }; int i, ilo, ior, ihi; int sixteenths = raw / TICKS_PER_SIXTEENTH; ilo = ior = ihi = raw % TICKS_PER_SIXTEENTH; while (!ok_tab[ihi]) ihi++; while (!ok_tab[ilo]) ilo--; if (ihi - ior < ior - ilo) i = ihi; else i = ilo; if (i == 0 && sixteenths == 0) return 1; else return sixteenths * TICKS_PER_SIXTEENTH + i; } /******************************************************************** WAV-File *******************************************************************/ FILE *wavfp; void fillnotes (int *t, struct eightnotes **p, struct eightnotes *e, int file_1_or_2); void generate_samples (void); class blockgen { public: blockgen (void); int get_value (void); void note_on (int level); void set_period (float period); void decay (void); void smpl (void); private: int level; int min; float samples_half_period; float rnd; int cnt; }; class blockgen *voices[NUM_INSTRUM_STRINGS * 2]; const long WAVE_SAMPLES = 1320 * 4; int samples_per_tick; /******************************************************************** Create a Windows' WAV-file, which makes it possible to playback even without a soundcard, using the pcspeak driver in Windows 3.1 Format info obtained from the Multimedia Development Kit docu, page 8-40 and after. *******************************************************************/ void make_wavfile (void) { char wavname[129]; struct wavheader { char riff[4]; long riffl; char wavefmt[4 + 4]; long headl; int wFormatTag; int nChannels; long nSamplesPerSec; long nAvgBytesPerSec; int nBlockAlign; int nBitsPerSample; char data[4]; long datal; } wavhead; for (int i = 0; i < NUM_INSTRUM_STRINGS * 2; i++) { voices[i] = new blockgen; } strcpy(wavname, g_argv[1]); set_extension(wavname, "wav"); if ((wavfp = fopen(wavname, "wb")) == NULL) { fprintf(stderr, "Can't write to %s\n", wavname); return; } /* Init and header */ strncpy(wavhead.riff, "RIFF", sizeof wavhead.riff); strncpy(wavhead.wavefmt, "WAVEfmt ", sizeof wavhead.wavefmt); wavhead.wFormatTag = 1; wavhead.nChannels = 1; wavhead.nAvgBytesPerSec = wavhead.nSamplesPerSec = WAVE_SAMPLES; wavhead.nBitsPerSample = 8; wavhead.nBlockAlign = wavhead.nBitsPerSample * wavhead.nChannels / 8; strncpy(wavhead.data, "data", sizeof wavhead.data); wavhead.headl = 16; samples_per_tick = (double)WAVE_SAMPLES / ((double)sixteenth_per_minute * TICKS_PER_SIXTEENTH_B60); wavhead.datal = samples_per_tick * totdura; wavhead.riffl = wavhead.datal + sizeof wavhead - 8; fwrite(&wavhead, 1, sizeof wavhead, wavfp); while (p1 < e1 || p2 < e2) { static struct eightnotes *prev1, *prev2; if (p1 != prev1 || p2 != prev2) { show(); prev2 = p1; prev2 = p2; } fillnotes(&t1, &p1, e1, 1); fillnotes(&t2, &p2, e2, 2); for (int i = 0; i < sizeof voices / sizeof voices[0]; i++) { voices[i]->decay(); } generate_samples(); } fclose(wavfp); for (i = 0; i < sizeof voices / sizeof voices[0]; i++) { delete voices[i]; } } /******************************************************************** Write samples for the notes to the WAV-file *******************************************************************/ void fillnotes (int *t, struct eightnotes **p, struct eightnotes *e, int file_1_or_2) { if (*p >= e) return; if (*t == 0 && (*p)->dura > 0) { int s; /* Set amplitude for notes for this buffer */ for (s = 0; s < NUM_INSTRUM_STRINGS; s++) { int ch = s; if (file_1_or_2 == 2) ch += NUM_INSTRUM_STRINGS; if ((*p)->note[s] != REST) { register int i = (*p)->note[s] + 2 * TONES_PER_OCTAVE; voices[ch]->note_on(file_1_or_2 == 1 ? 15 : 30); voices[ch]->set_period( SNDFREQ_VALUE[i] / (float)BASE_FREQ * (WAVE_SAMPLES / 2.0)); } } (*t) = 1; } else if (++(*t) >= (*p)->dura) { /* Duration for current note has passed, prepare to play the next */ while (p1 <= e1 && p2 <= e2 && (++(*p))->dura < 0) ; *t = 0; } } /******************************************************************** Write samples for the notes to the WAV-file *******************************************************************/ void generate_samples (void) { int value; for (int j = 0; j < samples_per_tick; j++) { value = 0; for (int i = 0; i < sizeof voices / sizeof voices[0]; i++) { value += voices[i]->get_value(); voices[i]->smpl(); } putc(value + 0x80, wavfp); } } /******************************************************************** Implementation of class blockgen *******************************************************************/ blockgen::blockgen (void) { level = min = 0; cnt = samples_half_period = 0; } int blockgen::get_value (void) { return min ? -level : level; } void blockgen::note_on (int level) { blockgen::level = level; min = 0; } void blockgen::set_period (float period) { samples_half_period = period; cnt = (int)(samples_half_period); rnd = (samples_half_period - cnt) / 7.0; } void blockgen::decay (void) { if (level) level--; } void blockgen::smpl (void) { if (level && --cnt <= 0) { cnt = samples_half_period + rnd; rnd += (samples_half_period - cnt) / 7.0; min = !min; } } /******************************************************************** Create a Midi file from the tablature files, if so requested. *******************************************************************/ void make_midifile (void) { char midiname[129]; FILE *midi_fp; struct eightnotes *p; strcpy(midiname, g_argv[1]); set_extension(midiname, "mid"); if ((midi_fp = fopen(midiname, "wb")) == NULL) { fprintf(stderr, "Can't write to %s\n", midiname); return; } /* Init and header */ ritemidi_head(midi_fp, 4, TICKS_PER_SIXTEENTH); ritemidi_opentrack(midi_fp, sixteenth_per_minute, 1, 2); for (p = notesbuf2; p < e2; p++) { rite_chord(midi_fp, 1, p); } ritemidi_clostrack(midi_fp); ritemidi_opentrack(midi_fp, sixteenth_per_minute, 2, 1); for (p = notesbuf1; p < e1; p++) { rite_chord(midi_fp, 2, p); } ritemidi_clostrack(midi_fp); /* And the same thing once again, to support both basic and extended devices. (Microsoft / General Midi standards). */ ritemidi_opentrack(midi_fp, sixteenth_per_minute, 12, 2); for (p = notesbuf2; p < e2; p++) { rite_chord(midi_fp, 12, p); } ritemidi_clostrack(midi_fp); ritemidi_opentrack(midi_fp, sixteenth_per_minute, 13, 1); for (p = notesbuf1; p < e1; p++) { rite_chord(midi_fp, 13, p); } ritemidi_clostrack(midi_fp); fclose(midi_fp); } /******************************************************************** *******************************************************************/ static void rite_chord (FILE *midi_fp, int channel, struct eightnotes *p) { int i; if (p->dura <= 0) { /* This must be a comment, skip for midi */ return; } for (i = 0; i < NUM_INSTRUM_STRINGS; i++) { if (p->note[i] != REST) { ritemidi_note_on_off(RITEMIDI_NOTE_ON, midi_fp, channel, 12 + to_twelve_tone(p->note[i])); } } ritemidi_pass_time(p->dura); for (i = 0; i < NUM_INSTRUM_STRINGS; i++) { if (p->note[i] != REST) { ritemidi_note_on_off(RITEMIDI_NOTE_OFF, midi_fp, channel, 12 + to_twelve_tone(p->note[i])); } } } /******************************************************************** Converts from internal 53-tone system (shifted two octaves to stay within a character width), to normal 12-tone midi. *******************************************************************/ int to_twelve_tone (int in) { return 0.5 + (in + 2 * TONES_PER_OCTAVE) / (TONES_PER_OCTAVE / 12.0); } /******************************************************************** *******************************************************************/ static void setup_gravis (void) { int i; gravis = 1; gus_unload_all_patches(); gus_load_patch(gravis_instrument_1); gus_load_patch(gravis_instrument_2); /* Set program numbers on all Midi-channels */ for (i = 0; i < 8; i++) { gus_send_midibyte(0xC0 + i); gus_send_midibyte(gravis_instrument_1); } for (i = 8; i < 16; i++) { gus_send_midibyte(0xC0 + i); gus_send_midibyte(gravis_instrument_2); } } #ifdef DEBUG /******************************************************************** *******************************************************************/ void ReportFreqs (int file_1_or_2, int note, char *notename) { /* 29 September 2020: The reason for adding two octaves was: trying to represent even the 53-tone system within the 256 possible values of an 8-bit unsigned character. But meanwhile I use unsigned shorts, so this is now unnecessary, and might be removed everywhere. */ int val = SBFREQ_VALUE[note + 2 * TONES_PER_OCTAVE]; int frqval = val & 0x3ff; int frqoct = (val >> 10) & 0x7; double freq = value2freq(val); double above_a = freq / (440.0 / (1L << 5)); double cents_a = fmod(log(above_a) / log(2.0) * 1200.0, 1200); double n53s = cents_a / 1200.0 * 53; fprintf(fplog, "File %d: n=%3d note=%-3s octave=%1d val=%4d freq=%6.2f Hz %7.1f cents %5.2fx53\n", file_1_or_2, note, notename, frqoct, frqval, freq, cents_a, n53s); } #endif