annotate main.cpp @ 13:de3961f74f30 tip

Add Linux/gcc Makefile; build fix
author Chris Cannam
date Mon, 05 Sep 2011 15:22:35 +0100
parents 977f541d6683
children
rev   line source
xue@11 1
xue@9 2 /*
xue@9 3 Sample program: analysis of harmonic sinusoid in low-noise monophonic context.
xue@9 4
xue@9 5 Syntax:
xue@9 6 (executable path) filename <setting=value> <setting=value> ... <setting=value>
xue@9 7
xue@9 8 filename: path input wave form audio file, 16-bit PCM
xue@9 9 setting: optional algorithmic parameters; search "stricmp" in the source code for a complete list
xue@9 10 value: non-default values given to the algorithmic parameters
xue@9 11
xue@9 12 Output:
xue@9 13 a *.evt file containing RIFF chunks, each starting with id "EVT\0" and hosting a harmonic sinusoid.
xue@9 14
xue@9 15 The "EVT\0" chunk body contains a header ("HDR\0") chunk followed by atom ("ATM\0") chunks.
xue@9 16
xue@9 17 The "HDR\0" chunk body contains 4 __int32 values: channel index, number of partials, number of
xue@9 18 measurement points (frames), and a tag.
xue@9 19
xue@9 20 The "ATM\0" chunk body contains an atom structure (see hs.h).
xue@9 21
xue@9 22 */
xue@9 23
xue@9 24 #include <QtCore/QCoreApplication>
xue@9 25 #include "arrayalloc.h"
xue@9 26 #include "hs.h"
xue@9 27 #include "matrix.h"
xue@9 28 #include "quickspec.h"
xue@9 29 #include "procedures.h"
xue@9 30 #include "vibrato.h"
xue@9 31 #include <string.h>
xue@9 32 #include <stdio.h>
xue@9 33 #include <conio.h>
xue@9 34
xue@9 35 //structure hosting wave header data
xue@9 36 struct wavehdr
xue@9 37 {
xue@9 38 __int16 fmttag;
xue@9 39 __int16 channels;
xue@9 40 __int32 samplespersec;
xue@9 41 __int32 bytespersec;
xue@9 42 __int16 blockalign;
xue@9 43 __int16 bitspersample;
xue@9 44 };
xue@9 45
xue@9 46 //tells if a wave header contains consistent data
xue@9 47 bool isvalidwavehdr(wavehdr* hdr)
xue@9 48 {
xue@9 49 if (hdr->fmttag!=1) return false;
xue@9 50 if (hdr->bitspersample!=8 && hdr->bitspersample!=16) return false;
xue@9 51 if (hdr->channels*hdr->bitspersample!=hdr->blockalign*8) return false;
xue@9 52 if (hdr->samplespersec*hdr->blockalign!=hdr->bytespersec) return false;
xue@9 53 return true;
xue@9 54 }
xue@9 55
xue@9 56 //structure hosting a waveform audio
xue@9 57 struct waveaudio
xue@9 58 {
xue@9 59 union
xue@9 60 {
xue@9 61 wavehdr hdr;
xue@9 62 struct
xue@9 63 {
xue@9 64 __int16 fmttag;
xue@9 65 __int16 channels;
xue@9 66 __int32 samplespersec;
xue@9 67 __int32 bytespersec;
xue@9 68 __int16 blockalign;
xue@9 69 __int16 bitspersample;
xue@9 70 };
xue@9 71 };
xue@9 72 int length;
xue@9 73 union
xue@9 74 {
xue@9 75 unsigned char* data;
xue@9 76 unsigned char* data8;
xue@9 77 __int16* data16;
xue@9 78 };
xue@9 79 waveaudio(){memset(this, 0, sizeof(waveaudio));}
xue@9 80 ~waveaudio(){delete[] data;}
xue@9 81 };
xue@9 82
xue@9 83 void freewaveaudio(waveaudio* wa)
xue@9 84 {
xue@9 85 delete[] wa->data;
xue@9 86 memset(wa, 0, sizeof(waveaudio));
xue@9 87 }
xue@9 88
xue@9 89 //error messages for reading waveform audio file
xue@9 90 enum err_wavefile
xue@9 91 {
xue@9 92 err_success,
xue@9 93 err_RIFF_hdr,
xue@9 94 err_wave_hdr,
xue@9 95 err_data_hdr,
xue@9 96 err_data_chunk,
xue@9 97 err_io
xue@9 98 };
xue@9 99
xue@9 100 //reads wave header from file stream
xue@9 101 int loadwavehdr(wavehdr* hdr, FILE* fp)
xue@9 102 {
xue@9 103 int rc, hdrlen;
xue@9 104 char s[5];
xue@9 105
xue@9 106 rc=fread(s, 1, 4, fp); s[4]=0;
xue@9 107 if (rc<4) return err_wave_hdr;
xue@9 108 if (strcmp(s, "WAVE")) return err_wave_hdr;
xue@9 109
xue@9 110 rc=fread(s, 1, 4, fp); s[4]=0;
xue@9 111 if (rc<4) return err_wave_hdr;
xue@9 112 if (strcmp(s, "fmt ")) return err_wave_hdr;
xue@9 113
xue@9 114 rc=fread(&hdrlen, 1, 4, fp);
xue@9 115 if (rc<4) return err_wave_hdr;
xue@9 116 if (hdrlen<16) return err_wave_hdr;
xue@9 117
xue@9 118 rc=fread(hdr, 1, sizeof(wavehdr), fp);
xue@9 119 if (rc<sizeof(wavehdr)) return err_wave_hdr;
xue@9 120 if (!isvalidwavehdr(hdr)) return err_wave_hdr;
xue@9 121
xue@9 122 if (hdrlen>16)
xue@9 123 {
xue@9 124 rc=fseek(fp, hdrlen-16, SEEK_CUR);
xue@9 125 if (rc!=0) return err_wave_hdr;
xue@9 126 }
xue@9 127
xue@9 128 return err_success;
xue@9 129 }
xue@9 130
xue@9 131 //reads waveform audio, including header, from a file stream
xue@9 132 int loadwaveaudio(waveaudio* wa, FILE* fp)
xue@9 133 {
xue@9 134 int rc, mainlen;
xue@9 135 char s[5];
xue@9 136 wavehdr hdr;
xue@9 137
xue@9 138 rc=fread(s, 1, 4, fp); s[4]=0;
xue@9 139 if (rc<4) return err_RIFF_hdr;
xue@9 140 if (strcmp(s, "RIFF")) return err_RIFF_hdr;
xue@9 141
xue@9 142 rc=fread(&mainlen, 1, 4, fp);
xue@9 143 if (rc<4) return err_RIFF_hdr;
xue@9 144
xue@9 145 rc=loadwavehdr(&hdr, fp);
xue@9 146 if (rc!=err_success) return rc;
xue@9 147
xue@9 148 rc=fread(s, 1, 4, fp); s[4]=0;
xue@9 149 if (rc<4) return err_data_hdr;
xue@9 150 if (strcmp(s, "data")) return err_data_hdr;
xue@9 151
xue@9 152 rc=fread(&mainlen, 1, 4, fp);
xue@9 153 if (rc<4) return err_data_hdr;
xue@9 154
xue@9 155 if (mainlen<=0) return err_data_hdr;
xue@9 156
xue@9 157 unsigned char* data=new unsigned char[mainlen];
xue@9 158 rc=fread(data, 1, mainlen, fp);
xue@9 159 if (rc<mainlen)
xue@9 160 {
xue@9 161 delete[] data;
xue@9 162 return err_data_chunk;
xue@9 163 }
xue@9 164
xue@9 165 wa->hdr=hdr;
xue@9 166 wa->length=rc/hdr.blockalign;
xue@9 167 delete[] wa->data;
xue@9 168 wa->data=data;
xue@9 169
xue@9 170 return err_success;
xue@9 171 }
xue@9 172
xue@9 173 //reads waveform audio, including header, from a file
xue@9 174 int loadwavefile(waveaudio* wa, char* filename)
xue@9 175 {
xue@9 176 FILE* fp;
xue@9 177 if ((fp=fopen(filename, "rb"))==NULL) return err_io;
xue@9 178 int result=loadwaveaudio(wa, fp);
xue@9 179 fclose(fp);
xue@9 180 return result;
xue@9 181 }
xue@9 182
xue@9 183 //returns new file path with the specific extension replacing the original one
xue@9 184 char* ChangeFileExt(char* FileName, char* Ext)
xue@9 185 {
xue@9 186 char* oldext=strrchr(FileName, '.');
xue@9 187 int namelen=strlen(FileName)-strlen(oldext);
xue@9 188 char* dest=new char[namelen+strlen(Ext)+1];
xue@9 189 memcpy(dest, FileName, namelen); dest[namelen]=0;
xue@9 190 strcat(dest, Ext);
xue@9 191 return dest;
xue@9 192 }
xue@9 193
xue@9 194 //ACPower for __int16 data input
xue@9 195 double ACPower(__int16* data, int Count)
xue@9 196 {
xue@9 197 if (Count<=0) return 0;
xue@9 198 double power=0, avg=0, tmp;
xue@9 199 for (int i=0; i<Count; i++)
xue@9 200 {
xue@9 201 tmp=*(data++);
xue@9 202 power+=tmp*tmp;
xue@9 203 avg+=tmp;
xue@9 204 }
xue@9 205 power=(power-avg*avg/Count)/Count;
xue@9 206 return power;
xue@9 207 }//ACPower
xue@9 208
xue@9 209 //double QIE(double* y, double& x){double a=0.5*(y[1]+y[-1])-y[0], b=0.5*(y[1]-y[-1]); x=-0.5*b/a; return y[0]-0.25*b*b/a;}
xue@9 210
xue@9 211 //correlation coefficient
xue@9 212 double InnerC(int N, __int16* x, __int16* y)
xue@9 213 {
xue@9 214 double inner=0, inn1=0, inn2=0;
xue@9 215 for (int n=0; n<N; n++) inner+=1.0*x[n]*y[n], inn1+=1.0*x[n]*x[n], inn2+=1.0*y[n]*y[n];
xue@9 216 double inn=sqrt(inn1*inn2);
xue@9 217 return (inn<=0)?0:inner/inn;
xue@9 218 }
xue@9 219
xue@9 220 //monophonic pitch estimation by autocorrelation
xue@9 221 double pitchautocor(__int16* data, int wid, double minf0bin, double maxf0bin, double& hsr)
xue@9 222 {
xue@9 223 int hwid=wid/2, minT=wid/maxf0bin, maxT=wid/minf0bin;
xue@9 224 double ene=InnerC(wid, data, data);
xue@9 225 if (ene<=0){hsr=0; return 0;}
xue@9 226 double* autocor=new double[hwid];
xue@9 227 for (int i=1; i<maxT+2; i++)
xue@9 228 {
xue@9 229 autocor[i]=InnerC(wid-i, data, &data[i]);
xue@9 230 }
xue@9 231
xue@9 232 int prd=0, m=minT;
xue@9 233 while(m<=maxT)
xue@9 234 {
xue@9 235 while (m<=maxT && (autocor[m]<0 || autocor[m]<autocor[m-1] || autocor[m]<autocor[m+1])) m++;
xue@9 236 if (m<=maxT)
xue@9 237 {
xue@9 238 int mm=ceil(m*0.6667), mp=floor(m*1.3333), cont=0;
xue@10 239 //for (int im=mm; im<=mp; im++) if (m!=im && autocor[im]>=autocor[m]) {cont=1; break;}
xue@9 240 if (cont==0)
xue@9 241 {
xue@9 242 if (prd==0 || autocor[m]>autocor[prd]*1.05) prd=m;
xue@9 243 }
xue@9 244 m++;
xue@9 245 }
xue@9 246 }
xue@9 247
xue@9 248 double pitchbin=0;
xue@9 249 if (prd>=minT && prd<=maxT)
xue@9 250 {
xue@9 251 double mshift; hsr=QIE(&autocor[prd], mshift); pitchbin=wid/(prd+mshift);
xue@9 252 }
xue@9 253 else{hsr=0;}
xue@9 254 delete[] autocor;
xue@9 255 return pitchbin;
xue@9 256 }
xue@9 257
xue@9 258 //main function
xue@9 259 int main(int argc, char* argv[])
xue@9 260 {
xue@9 261 //read audio file
xue@9 262 if (argc<2){printf("Please specify input wave file."); getch(); return 0;}
xue@9 263 printf("Loading wave file %s... ", argv[1]);
xue@9 264 waveaudio* wa=new waveaudio;
xue@9 265 int waverr=loadwavefile(wa, argv[1]); printf("[%d]\n", waverr);
xue@9 266 if (waverr!=err_success){printf("Aborted: error loading wave file."); getch(); return 0;}
xue@9 267 if (wa->bitspersample!=16){printf("Aborted: this program accepts 16-bit pcm only."); getch(); return 0;}
xue@9 268
xue@9 269 int length=wa->length, sps=wa->samplespersec;
xue@9 270 __int16* data16=wa->data16;
xue@9 271 if (wa->channels>1)
xue@9 272 {
xue@9 273 printf("Extracting channel 0... ");
xue@9 274 for (int i=1; i<length; i++) data16[i]=data16[i*wa->channels];
xue@9 275 printf("[0]\n");
xue@9 276 }
xue@9 277 wa->data=0;
xue@9 278 delete wa;
xue@9 279
xue@9 280 //default settings
xue@9 281 int maxhscount=100;
xue@9 282 float wid_s=0.02, offstwidratio=0.5;
xue@9 283 float dur_s=0.5;
xue@9 284 float intv_s=0.05; //duration, in seconds, to scan for peaks
xue@9 285 float minf0=55, maxf0=3520;
xue@9 286 WindowType wintype=wtHann;
xue@9 287
xue@9 288 NMSettings settings; memset(&settings, 0, sizeof(NMSettings));
xue@9 289 settings.hB=3; //spectral truncation half width
xue@9 290 settings.maxp=50; //maximal number of partials
xue@9 291 settings.maxB=0.001; //stiffness coefficient upper bound
xue@9 292 settings.epf=1e-4; //frequency estimation error tolerance for LSE estimation
xue@9 293 settings.epf0=1; //input frequency error bound for harmonic grouping
xue@9 294 settings.delm=1.1; //frequency error bound for harmonic grouping
xue@9 295 settings.delp=1.1; //pitch jump upper bound
xue@9 296
xue@9 297 double mina=0.5;
xue@9 298
xue@9 299 //user settings through commandline arguments
xue@9 300 for (int i=2; i<argc; i++)
xue@9 301 {
xue@9 302 char* s1=strchr(argv[i], '=');
xue@9 303 if (s1)
xue@9 304 {
xue@9 305 s1[0]=0; s1++;
xue@9 306 if (!stricmp(argv[i], "hB")){settings.hB=atof(s1); continue;}
xue@9 307 if (!stricmp(argv[i], "maxp")){settings.maxp=atoi(s1); continue;}
xue@9 308 if (!stricmp(argv[i], "maxB")){settings.maxB=atof(s1); continue;}
xue@9 309 if (!stricmp(argv[i], "epf")){settings.epf=atof(s1); continue;}
xue@9 310 if (!stricmp(argv[i], "epf0")){settings.epf0=atof(s1); continue;}
xue@9 311 if (!stricmp(argv[i], "delm")){settings.delm=atof(s1); continue;}
xue@9 312 if (!stricmp(argv[i], "delp")){settings.delp=atof(s1); continue;}
xue@9 313 if (!stricmp(argv[i], "minf0")){minf0=atof(s1); continue;}
xue@9 314 if (!stricmp(argv[i], "maxf0")){maxf0=atof(s1); continue;}
xue@9 315 if (!stricmp(argv[i], "wid_s")){wid_s=atof(s1); continue;}
xue@9 316 if (!stricmp(argv[i], "offstwidratio")){offstwidratio=atof(s1); continue;}
xue@9 317 if (!stricmp(argv[i], "dur_s")){dur_s=atof(s1); continue;}
xue@9 318 if (!stricmp(argv[i], "intv_s")){intv_s=atof(s1); continue;}
xue@9 319 if (!stricmp(argv[i], "maxhscount")){maxhscount=atoi(s1); continue;}
xue@9 320 if (!stricmp(argv[i], "wintype"))
xue@9 321 {
xue@9 322 if (!stricmp(s1, "hann")){wintype=wtHann; continue;}
xue@9 323 if (!stricmp(s1, "hamming")){wintype=wtHamming; continue;}
xue@9 324 if (!stricmp(s1, "blackman")){wintype=wtBlackman; continue;}
xue@9 325 if (!stricmp(s1, "rectangle")){wintype=wtRectangle; continue;}
xue@9 326 if (!stricmp(s1, "hannsqr")){wintype=wtHannSqr; continue;}
xue@9 327 }
xue@9 328 }
xue@9 329 }
xue@9 330
xue@9 331 //
xue@9 332 MList* List=new MList; List->Add(data16, 1);
xue@9 333
xue@9 334 //analysis window size and hop size
xue@9 335 int wid=1<<((int)floor(log2(wid_s*sps)+1)), offst=wid*offstwidratio;
xue@9 336
xue@9 337 //set up the spectrogram
xue@9 338 TQuickSpectrogram* Sp=new TQuickSpectrogram(0, 0, true, false, 0);
xue@9 339 Sp->Data=data16; Sp->DataLength=length; Sp->BytesPerSample=2;
xue@9 340 Sp->Wid=wid; Sp->Offst=offst; Sp->WinType=wintype;
xue@9 341 Sp->Spec(-1); //this sets Sp->Capacity to the number of accessible frames and does nothing else
xue@9 342
xue@9 343 //get number of frames
xue@9 344 int Fr=Sp->Capacity;
xue@9 345
xue@9 346 //the program searches every intv_fr frames over a duration of dur_fr frames for a spectral peak to start tracking.
xue@9 347 int dur_fr=sps*dur_s/offst, intv_fr=sps*intv_s/offst;
xue@9 348 if (dur_fr<10) dur_fr=10;
xue@9 349 if (intv_fr<1) intv_fr=1;
xue@9 350
xue@9 351 //minimal and maximal fundamental frequency, in bins
xue@9 352 settings.minf0=minf0/sps*wid;
xue@9 353 settings.maxf0=maxf0/sps*wid;
xue@9 354 if (settings.minf0<settings.hB) settings.minf0=settings.hB;
xue@9 355
xue@9 356 windowspec(wintype, wid, &settings.M, settings.c, &settings.iH2);
xue@9 357 double *fps=new double[wid], *vps=&fps[wid/2]; List->Add(fps, 1);
xue@9 358 cdouble *x=new cdouble[wid/2+1]; List->Add(x, 1);
xue@9 359
xue@9 360 //output file
xue@9 361 char* filename=ChangeFileExt(argv[1], ".evt"); List->Add(filename, 1);
xue@9 362 TFileStream* File=new TFileStream(filename, fmWrite);
xue@9 363
xue@9 364 double* frames=new double[Fr]; memset(frames, 0, sizeof(double)*Fr); List->Add(frames, 1);
xue@9 365 double* framesa=new double[Fr]; memset(framesa, 0, sizeof(double)*Fr); List->Add(framesa, 1);
xue@9 366 double* rsr=new double[Fr]; List->Add(rsr, 1); memset(rsr, 0, sizeof(double)*Fr);
xue@9 367 double* f0s=new double[Fr]; List->Add(f0s, 1); memset(f0s, 0, sizeof(double)*Fr);
xue@9 368 for (int fr=0; fr<Fr-1; fr++)
xue@9 369 {
xue@9 370 __int16* ldata16=&data16[offst*fr];
xue@9 371 frames[fr]=ACPower(ldata16, wid);
xue@9 372 framesa[fr]=1;
xue@9 373 // f0s[fr]=pitchautocor(ldata16, wid, settings.minf0, settings.maxf0, rsr[fr]); rsr[fr]=1-rsr[fr];
xue@9 374 }
xue@9 375 frames[Fr-1]=ACPower(&data16[(Fr-1)*offst], length-(Fr-1)*offst);
xue@9 376 framesa[Fr-1]=1;
xue@9 377
xue@9 378 int start=0, writecount=0;
xue@9 379
xue@9 380 while (writecount<maxhscount)
xue@9 381 {
xue@9 382 int fr_m=start+intv_fr;
xue@9 383
xue@9 384 //search for the highest spectral peak to start tracking
xue@9 385 double fp_m=0, vp_m=0;
xue@9 386 for (int fr=start+intv_fr; fr<start+dur_fr && fr<Fr; fr+=intv_fr)
xue@9 387 {
xue@9 388 cmplx<QSPEC_FORMAT>* speci=Sp->Spec(fr);
xue@9 389 for (int k=0; k<=wid/2; k++) x[k]=speci[k];
xue@9 390 int pc=QuickPeaks(fps, vps, wid, x, settings.M, settings.c, settings.iH2, mina, 0, wid/4);
xue@9 391
xue@9 392 for(int p=0; p<pc; p++)
xue@9 393 if (vps[p]>vp_m)
xue@9 394 {
xue@9 395 if (f0s[fr]==0 && rsr[fr]==0) f0s[fr]=pitchautocor(&data16[offst*fr], wid, settings.minf0, settings.maxf0, rsr[fr]); rsr[fr]=1-rsr[fr];
xue@9 396 if (f0s[fr]>0)
xue@9 397 {
xue@9 398 double dpind=fps[p]/f0s[fr], pind=floor(dpind+0.5);
xue@9 399 if (fabs(dpind-pind)<0.1) vp_m=vps[p], fr_m=fr, fp_m=fps[p];
xue@9 400 }
xue@9 401 }
xue@9 402 }
xue@9 403
xue@9 404 if (fp_m<=0){start=start+dur_fr; if (start>Fr) break; continue;}
xue@9 405
xue@9 406 f0s[fr_m]=pitchautocor(&data16[offst*fr_m], wid, settings.minf0, settings.maxf0, rsr[fr_m]); rsr[fr_m]=1-rsr[fr_m];
xue@9 407
xue@9 408 int _t=fr_m*offst+wid/2;
xue@9 409 double _f=fp_m/wid;
xue@9 410 int hsM, hsFr, frst=start, fren=Fr;
xue@9 411 atom** hsPartials=0;
xue@9 412
xue@9 413 settings.pin0=floor(fp_m/f0s[fr_m]+0.5);
xue@9 414 int tag=FindNote(_t, _f, hsM, hsFr, hsPartials, frst, fren, wid, offst, Sp, settings);
xue@9 415
xue@9 416 int fr1=(hsPartials[0][0].t-wid/2)/offst;
xue@9 417 double consi=1;
xue@9 418 for (int hsfr=0; hsfr<hsFr; hsfr++)
xue@9 419 {
xue@9 420 double ene=0;
xue@9 421 for (int m=0; m<hsM; m++)
xue@9 422 {
xue@9 423 double a=(hsPartials[m][hsfr].f>0)?hsPartials[m][hsfr].a:0;
xue@9 424
xue@9 425 if (hsfr==0) ene+=a*a;
xue@9 426 else
xue@9 427 {
xue@9 428 double b=(hsPartials[m][hsfr-1].f>0)?hsPartials[m][hsfr-1].a:0;
xue@9 429 ene+=(a*a+b*b+a*b)/3;
xue@9 430 }
xue@9 431
xue@9 432 if (hsfr==hsFr-1) ene+=a*a;
xue@9 433 else
xue@9 434 {
xue@9 435 double b=(hsPartials[m][hsfr+1].f>0)?hsPartials[m][hsfr+1].a:0;
xue@9 436 ene+=(a*a+b*b+a*b)/3;
xue@9 437 }
xue@9 438 }
xue@9 439 framesa[fr1+hsfr]=ene;//*2;
xue@9 440 if (framesa[fr1+hsfr]<frames[fr1+hsfr]) consi*=frames[fr1+hsfr]/framesa[fr1+hsfr];
xue@9 441 else consi*=framesa[fr1+hsfr]/frames[fr1+hsfr];
xue@9 442 if (framesa[fr1+hsfr]>frames[fr1+hsfr]) framesa[fr1+hsfr]=0;
xue@9 443 else framesa[fr1+hsfr]=1-framesa[fr1+hsfr]/frames[fr1+hsfr];
xue@9 444 }
xue@9 445 consi=pow(consi, 1.0/hsFr);
xue@9 446
xue@9 447 THS* HS=new THS;
xue@9 448 HS->M=hsM; HS->Fr=hsFr; HS->Partials=hsPartials;
xue@9 449 printf("%d (%.2fs), %d (%.2fs), inconsistency %f\n", start, start*offst*1.0/sps, hsFr, hsFr*offst*1.0/sps, consi-1);
xue@9 450 if (consi<1.5)
xue@9 451 {
xue@9 452 HS->WriteToStream(File);
xue@9 453 printf("Write note %d\n", writecount++);
xue@9 454 }
xue@9 455 delete HS;
xue@9 456
xue@9 457 start=fr1+hsFr;
xue@9 458
xue@9 459 }
xue@9 460 delete File; delete Sp;
xue@9 461 delete List;
xue@9 462 printf("Completed\n"); getch(); return 0;
xue@9 463 }