Programming The Sound Blaster 16 Example 3 |
Mission : Be Able To Read The .VOC Format.IntroDownload the Expansion Pack!
Bytes | Description | ||
0x00-0x13h | This is the file type description. It MUST read 'Creative Voice File' followed by an EOF (0x1A) | ||
0x14-0x15 | The start of the data block. This is defined because in future versions, the size of the header might change. Now it is set to 0x1A | ||
0x16-0x17 | The File Version. The first byte is the major version and the second is the minor version number. | ||
0x18-0x19 | The identification code. This can verify that we are reading a real .VOC file. It is the compliment of the version in 16 and 17h plus 1234h. |
Block Number | Info | |
0 | End Of Data Block | This is the only block type without data indicating its length. |
1 | New Sample Data | Block length includes the Sample Rate and Data Packing fields
. The Sample Rate is calculated as: SR=256-1,000,000/real sampling rate, so to get the real sampling rate, that would be
equal to: real sampling rate = (-1000000)/(SR-256), where SR is the value read from the .VOC file in each. The Data Packing field corresponds to the following: 0 : 8 bits normal, unpacked samples 1 : 4 bits packed 2 : 2.6 bits packed 3 : 2 bits packed |
2 | Sample Data | Not Utilized by the Source Code |
3 | Silence | Not Utilized by the Source Code |
4 | Marker | Not Utilized by the Source Code |
5 | ASCII Text | Not Utilized by the Source Code |
6 | Start of a Repetition | Not Utilized by the Source Code |
7 | End of Repetition | Not Utilized by the Source Code |
8 | Additional Information | Not Utilized by the Source Code |
9 | New Block Type | This is a recent addition to the block types. It does the same thing as block type 1, but makes things a little easier. |
int SB16::ReadVocHeader(FILE *stream) { struct VocHeader { unsigned char Description[20]; unsigned short DataBlockOffset; unsigned short Version; unsigned short IDCode; }header; fread((VocHeader*)&header,sizeof(header),1,stream); if(strncmp((char *)header.Description,"Creative Voice File\x1A",10)!=0) { return 1; //Not a Valid VOC File } if(header.Version!=0x010A && header.Version!=0x0114) { //Supports version 110 and 120 cout<<"Header Version : "<<header.Version<<"NOT Recognized!"<<endl; return 2;//See Above } if(~header.Version + 0x1234 != header.IDCode) { return 3;//See Above } cout<<"Version: "<<dec<<(header.Version>>8)<<"."<<dec<<(header.Version&0xff)<<" "; fseek(stream,header.DataBlockOffset,SEEK_SET); return 0; //All is OK! }This function reads the main header of the .VOC file and determines if it authentic or not. If it is, it will return a 0 meaning everything is ok. Any other number can be used as a flag that something went wrong. Here is the function that calls ReadVodheader().
void SB16::Load_Sound(char *filename,int placenumber) { FILE *VocFile; char Eof=0,Error=0,blocktype; VocFile= fopen(filename,"rb"); if(VocFile==NULL) {cout<<"Unable to open \""<<filename<<"\""<<endl; } cout<<filename<<": "; Error=ReadVocHeader(VocFile); if(Error) {cout<<"Can't open "<<filename<<"!!\n";exit(1); } while(Eof != 1) {blocktype=0; fread(&blocktype,1,1,VocFile);//read attribute of it switch(blocktype & 0xFF) { case 0: Eof=1;break; case 1: Error=BlockTypeOne(VocFile,placenumber);break; case 9: Error=BlockTypeNine(VocFile,placenumber);break; default: {cout<<"Unsupported sub-block format '"<<(blocktype&0xFF) cout<<"' in "<<filename<<endl<<endl; Error=1; break; } } if(Error) { cout<<"An error has occured while trying to read : " cout<<filename<<endl; } } fclose(VocFile); }This function opens the file filename, reads its header (Error=ReadVocHeader(VocFile);) and then goes into a loop where it processes subblocks. As stated before, it only recognizes blocks of type 0,1 and 9. If the data block type isn't legal, it spits out an error message and exits. In all reality it is still possible to keeps on processing. This is possible because we know what the block type AND lengths are. This enables us to advance the file pointer to the start of the next data block. Now that we've narrowed the field down to 3 possible data block types , lets go over those as well. Block 0 simply means the end of the file so thats pretty self explanitory. Here's 1 and 9:
int SB16::BlockTypeOne(FILE *stream,int index) { struct header {char Tc; char Pack; }Header; unsigned long Len=0,SampleRate=0; char *temp,*len,*samprate; fread(&Len,3,1,stream); //size for REAL fread(&Header,sizeof(header),1,stream); SampleRate=1000000L/256L-(long)Header.Tc; Len-=2; temp = new unsigned char[Len]; if(temp ==NULL) {cout<<"Error allocating memory!\n"; return 1; } fread((unsigned char *)temp,Len,1,stream); switch((int)Header.Pack & 0xFF) { case 0:cout<<"8 bit unsigned\n";break; case 1:cout<<"4 bit ADPCM\n";break; case 2:cout<<"2.6 bit ADPCM\n";break; case 3:cout<<"2 bit ADPCM\n";break; default:break; } cout<<"Length : "<<Len<<endl; Sounds[index].Length=Len; Sounds[index].Sound=(unsigned char*)temp; return 0; }The only essential parts of this function are the fread calls. If we really wanted, we could make this function VERY small. Instead, lets read in the data, calculate the sampling rate, data pack fields, and the length of the data block. While we're at it, why not print everything out!?
int SB16::BlockTypeNine(FILE *stream,int index) {struct block9 {unsigned long SamplesPerSecond; char BitsPerSample; char Channel; unsigned short Format; char Reserved[4]; }Block9; unsigned char *dataptr; char MonoStereo[3][9]={"","Mono","Stereo"}; char Bits[17][8]={"0","1","2","3","4","5","6","7","8","9","10", "11","12","13","14","15","16"}; long Length=0; fread(&Length,3,1,stream); fread(&Block9,sizeof(block9),1,stream); Length-=12; cout<<"Length: "<<dec<<Length; dataptr = new unsigned char[1+Length]; if(dataptr == NULL) {cout<<"ERROR: dataptr == NULL!\nLength = "<<dec<<Length; } cout<<" "<<Bits[Block9.BitsPerSample]<<" bit "<<MonoStereo[Block9.Channel]; switch(Block9.Format) {case 0x0000:cout<<" PCM ";break; case 0x0001: case 0x0002: case 0x0200: case 0x0003:cout<<" ADPCM ";break; case 0x0004:cout<<" Signed ";break; case 0x0006:cout<<" ALAW ";break; case 0x0007:cout<<" MULAW ";break; default:cout<<"Unsupported Format!\n";break; } cout<<dec<<Block9.SamplesPerSecond<<" hz"<<endl; fread((unsigned char*)dataptr,Length,1,stream); dataptr[strlen((char*)dataptr)+1]='\0'; Sounds[index].Length=Length; Sounds[index].Sound=dataptr; return 0; }As with the last function, this one really only needs the fread calls. This data block type provides us with a lot more information than block number 1. For the curious, PCM stands for pulse code modulation, and ADPCM stands for Adaptive Delta Pulse Code Modulation. Instead of having the sample values actually stand for voltages, in ADPCM format they stand for the difference in voltage giving unlimited voltages, but each signal differing less than 255. In both examples, Sounds[] is a dynamically allocated array of structures. We use index to tell us which position we are at in filling in the Sounds array. Get it? To finish everything off, lets create a function that reads the file names from a data file, and another function that frees up all the memory taken up by the sound files after runtime.
void SB16::Load_Sounds() { FILE *s; int x; if((s=fopen("sounds.dat","r+"))== NULL) { cout<<"Error reading sounds.dat\n"; } else { char *filename = new char[15]; for(x=0;x<2/*NumberOfSounds*/;x++) {fgets(filename,14,s); filename[strlen(filename)-1]='\0'; Load_Sound(filename,x); } delete filename; } fclose(s); }
void SB16::Unload_Sound(unsigned char *sound_ptr) { if(sound_ptr != NULL) {delete sound_ptr; } }Well, there we have it, the wonderful .VOC format. If you have any questions, comments, or some tips on how i can improve this page, please send me some Feedback!