/* * Copyrighted as an unpublished work. * (c) Copyright 1991 Brian Smith * All rights reserved. * * Read the LICENSE file for details on distribution and use. * */ #include #include #include #include #include #include #include "sb.h" #define TRUE 1 #define FALSE 0 #define lobyte(X) (((unsigned char *)&X)[0]) #define hibyte(X) (((unsigned char *)&X)[1]) /* Globals */ int fm_herz; /* clock ticks per second */ int tempo; /* clock ticks per quarter note */ int fm_fd; int note_on[22]; char **instrument_table; int note_table[12] = { 343, 363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647 }; int main(argc, argv) int argc; char **argv; { int cmf_fd; if (argc != 2) { printf("usage: %s \n", argv[0]); exit(-1); } /* open cmf file */ cmf_fd = open(argv[1], O_RDONLY); if (cmf_fd == -1) { printf("usage: %s \n", argv[0]); exit(-1); } /* verify that file is a cmf file */ if (!verify_cmf(cmf_fd)) { printf("file was not a cmf file\n"); printf("usage: %s \n", argv[0]); exit(-1); } /* read and set instruments from cmf file */ get_instruments(cmf_fd); /* get timing */ set_timing(cmf_fd); /* open soundblaster fm chips */ Sb_FM_Reset(); /* play song */ play_song(cmf_fd); return(0); } /* check for "CTMF" in first four bytes of file */ int verify_cmf(fd) int fd; { char idbuf[5]; /* get id */ lseek(fd, 0, SEEK_SET); if (read(fd, idbuf, 4) != 4) return(FALSE); /* compare to standard id */ idbuf[4] = (char)0; if (strcmp(idbuf, "CTMF") != 0) return(FALSE); return(TRUE); } int get_instruments(fd) int fd; { int offset; int num_instruments; int i; int rc; int fnum, block, note; unsigned char tmp_byte; /* get offset of instrument block */ offset = 0; lseek(fd, 0x06, SEEK_SET); read(fd, &tmp_byte, 1); lobyte(offset) = tmp_byte; read(fd, &tmp_byte, 1); hibyte(offset) = tmp_byte; /* get number of instruments */ num_instruments = 0; lseek(fd, 0x24, SEEK_SET); read(fd, &tmp_byte, 1); lobyte(num_instruments) = tmp_byte; read(fd, &tmp_byte, 1); hibyte(num_instruments) = tmp_byte; /* allocate space */ instrument_table = (char **)malloc(sizeof(int *) * num_instruments); /* read each instrument */ lseek(fd, (long)offset, SEEK_SET); for (i=0; i< num_instruments; i++) { /* allocate space */ instrument_table[i] = (char *)malloc(16); /* set instrument characteristics */ read(fd, instrument_table[i], 16); } return(0); } /* * get and set timing parameters */ int set_timing(fd) int fd; { unsigned char tmp_byte; /* get tempo */ tempo = 0; lseek(fd, 0x0C, SEEK_SET); read(fd, &tmp_byte, 1); tempo = (unsigned int)tmp_byte; read(fd, &tmp_byte, 1); tempo += (unsigned int)tmp_byte << 8; /* get herz of timing clock */ fm_herz = 0; lseek(fd, 0x0C, SEEK_SET); read(fd, &tmp_byte, 1); fm_herz = (unsigned int)tmp_byte; read(fd, &tmp_byte, 1); fm_herz += (unsigned int)tmp_byte << 8; return(0); } /* * seek to the midi stream and handle midi events for the song */ int play_song(fd) int fd; { int offset; unsigned char tmp_byte; int delta; /* get offset of music stream */ lseek(fd, 8, SEEK_SET); read(fd, &tmp_byte, 1); offset = (unsigned int)tmp_byte; read(fd, &tmp_byte, 1); offset += (unsigned int)tmp_byte << 8; lseek(fd, offset, SEEK_SET); /* process till EOF */ while(1) { /* get delta time */ delta = ReadVarLen(fd); if (delta == -1) break; /* wait delta */ if (delta > 0) delay((double)delta/(double)fm_herz * 1000); /* process midi event */ process_event(fd, delta); } return(0); } /* * read a variable length scalar in MIDI format */ int ReadVarLen(fd) int fd; { int value; unsigned char tmp_byte; if (read(fd, &tmp_byte, 1) == 0) return(-1); value = (int)tmp_byte; if (tmp_byte & 0x80) { value &= 0x7F; do { if (read(fd, &tmp_byte, 1) == 0) return(-1); value = (value << 7) + (tmp_byte & 0x7F); } while (tmp_byte & 0x80); } return(value); } /* * process a midi event */ int process_event(fd, delta) int fd; { int rc, channel; unsigned char tmp_byte; static int status = -1; /* get status byte */ read(fd, &tmp_byte, 1); if (tmp_byte & 0x80) { status = (unsigned int)tmp_byte; } else { /* running status, so back up one */ if (status == -1) { printf("ERROR in cmf file. Running status at beginning of file\n"); exit(-1); } lseek(fd, -1, SEEK_CUR); } /* switch different events */ switch (status & 0xF0) { case 0x80: /* turn note off */ channel = status & 0x0f; Sb_FM_Key_Off(channel); note_on[channel] = 0; /* waste two bytes */ read(fd, &tmp_byte, 1); read(fd, &tmp_byte, 1); break; case 0x90: /* get note */ read(fd, &tmp_byte, 1); /* determine note */ /* turn note on */ channel = status & 0x0f; if (note_on[channel]) Sb_FM_Key_Off(channel); note_on[channel] = 1; Sb_FM_Key_On(channel,note_table[tmp_byte % 12],(tmp_byte/12) & 7); /* waste a bytes */ read(fd, &tmp_byte, 1); break; case 0xA0: printf("polyphonic key pressure: not handled\n"); /* waste two bytes */ read(fd, &tmp_byte, 1); read(fd, &tmp_byte, 1); break; case 0xB0: printf("control change: not handled\n"); /* waste two bytes */ read(fd, &tmp_byte, 1); read(fd, &tmp_byte, 1); break; case 0xC0: /* change the instrument on a channel */ read(fd, &tmp_byte, 1); load_instrument(status&0x0F, tmp_byte & 0x0F); break; case 0xD0: printf("Channel Pressure: not handled\n"); /* waste a byte */ read(fd, &tmp_byte, 1); break; case 0xE0: printf("Pitch Wheel Change: not handled\n"); /* waste two bytes */ read(fd, &tmp_byte, 1); read(fd, &tmp_byte, 1); break; case 0xF0: printf("System Exclusive: not handled\n"); /* waste two bytes */ read(fd, &tmp_byte, 1); read(fd, &tmp_byte, 1); break; default: printf("internal program error\n"); /* waste two bytes */ read(fd, &tmp_byte, 1); read(fd, &tmp_byte, 1); break; } return(0); } /* * load an instrument from the instrument table into the SoundBlaster */ int load_instrument(channel, instrument) { int rc; /* error check! */ if ((channel <0) || (channel >= 9)) return; /* abort instrument if being loaded */ if (note_on[channel]) Sb_FM_Key_Off(channel); /* set instrument characteristics */ Sb_FM_Set_Voice(channel,instrument_table[instrument]); return(0); }