/* * Copyright (c) 1983-2023 Trevor Wishart and Composers Desktop Project Ltd * http://www.trevorwishart.co.uk * http://www.composersdesktop.com * This file is part of the CDP System. The CDP System is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The CDP System is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the CDP System; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* * Take a set of amp-frq data in the correct range, * find the notches in the spectrum. * Create a new spectrum by inverting these notches to become peaks */ #define PSEUDOCENT (1.0009) // approx 1/64th of a tone #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int sloom = 0; int sloombatch = 0; const char* cdp_version = "6.1.0"; //CDP LIB REPLACEMENTS //static int get_tk_cmdline_word(int *cmdlinecnt,char ***cmdline,char *q); static int handle_the_special_data(FILE *fpi, double **parray, int *itemcnt); static int test_the_special_data(double *parray, int itemcnt, double nyquist); static int peakiso(double *parray,int itemcnt,double *outarray,double minnotch); static int get_float_with_e_from_within_string(char **str,double *val); /**************************************** MAIN *********************************************/ int main(int argc,char *argv[]) { FILE *fpi, *fpo; int k; int exit_status; double *parray, *outarray; int itemcnt, srate; double minnotch = 0.0, nyquist; char temp[200], temp2[200]; if(argc < 4 || argc > 5) { usage1(); return(FAILED); } if(argc == 5) { if(sscanf(argv[4],"%lf",&minnotch) != 1) { fprintf(stderr,"CANNOT READ POINTCNT (%s)\n",argv[3]); return(FAILED); } if(minnotch > 1.0 || minnotch < 0.0) { fprintf(stderr,"INVALID MINIMUM NOTCH %s (range 0 - 1)\n",argv[3]); return(FAILED); } } if(sscanf(argv[3],"%d",&srate) != 1) { fprintf(stderr,"CANNOT READ SAMPLERATE (%s)\n",argv[3]); return(FAILED); } if(srate < 44100 || BAD_SR(srate)) { fprintf(stderr,"INVALID SAMPLE RATE ENTERED (44100,48000,88200,96000 only).\n"); return(FAILED); } nyquist = (double)(srate/2); if((fpi = fopen(argv[1],"r")) == NULL) { fprintf(stderr,"CANNOT OPEN DATAFILE %s\n",argv[1]); return(FAILED); } if((exit_status = handle_the_special_data(fpi,&parray,&itemcnt))<0) return(FAILED); if((exit_status = test_the_special_data(parray,itemcnt,nyquist))<0) return(FAILED); if((outarray = (double *)malloc(itemcnt * sizeof(double)))==NULL) { fprintf(stderr,"INSUFFICIENT MEMORY for input data.\n"); return(MEMORY_ERROR); } if((fpo = fopen(argv[2],"w")) == NULL) { fprintf(stderr,"CANNOT OPEN OUTPUT DATAFILE %s\n",argv[2]); return(FAILED); } if((exit_status = peakiso(parray,itemcnt,outarray,minnotch)) < 0) return(FAILED); k = 0; for(k = 0;k < itemcnt;k+=2) { sprintf(temp,"%lf",outarray[k]); sprintf(temp2,"%lf",outarray[k+1]); strcat(temp2,"\n"); strcat(temp,"\t"); strcat(temp,temp2); fputs(temp,fpo); } if(fclose(fpo)<0) { fprintf(stderr,"WARNING: Failed to close output data file %s.\n",argv[2]); } return(SUCCEEDED); } /******************************** USAGE1 ********************************/ int usage1(void) { fprintf(stderr, "USAGE: peakiso datafile outdatafile srate [minnotch]\n" "\n" "Isolate peaks in a spectrum.\n" "\n" "DATAFILE textfile of frq/amp pairs.\n" "OUTDATAFILE isolated peaks as frq/amp pairs.\n" "MINNOTCH min depth of notch to qualify as peak-separator (Range 0-1).\n" "\n"); return(USAGE_ONLY); } /************************** HANDLE_THE_SPECIAL_DATA **********************************/ int handle_the_special_data(FILE *fpi, double **parray, int *itemcnt) { int cnt; double *p, dummy; char temp[200], *q; cnt = 0; p = &dummy; while(fgets(temp,200,fpi)==temp) { q = temp; if(*q == ';') // Allow comments in file continue; while(get_float_with_e_from_within_string(&q,p)) cnt++; } if(ODD(cnt)) { fprintf(stderr,"Data not paired correctly in input data file\n"); return(DATA_ERROR); } if(cnt == 0) { fprintf(stderr,"No data in input data file\n"); return(DATA_ERROR); } if((*parray = (double *)malloc(cnt * sizeof(double)))==NULL) { fprintf(stderr,"INSUFFICIENT MEMORY for input data.\n"); return(MEMORY_ERROR); } fseek(fpi,0,0); p = *parray; while(fgets(temp,200,fpi)==temp) { q = temp; if(*q == ';') // Allow comments in file continue; while(get_float_with_e_from_within_string(&q,p)) { p++; } } if(fclose(fpi)<0) { fprintf(stderr,"WARNING: Failed to close input data file.\n"); } *itemcnt = cnt; return(FINISHED); } /************************** TEST_THE_SPECIAL_DATA **********************************/ int test_the_special_data(double *parray, int itemcnt, double nyquist) { double *p; int isamp = 0, linecnt = 1; p = parray; while(linecnt <= itemcnt/2) { if(isamp) { if(*p < 0.0 || *p > 1.0) { fprintf(stderr,"Amp (%lf) out of range (0-1) at line %d in datafile.\n",*p,linecnt); return(DATA_ERROR); } } else { if(*p < 0.0 || *p > nyquist) { fprintf(stderr,"Frq (%lf) out of range (0 - %lf) at line %d in datafile.\n",*p,nyquist,linecnt); return(DATA_ERROR); } } if(!isamp) linecnt++; isamp = !isamp; p++; } return(FINISHED); } /************************** PEAKISO **********************************/ int peakiso(double *parray,int itemcnt,double *outarray,double minnotch) { int ampat, trofampat, pkampbelowat, pkampaboveat, nexttrofat, writeat, j; double amp, lastamp, ampstep, lastampstep, belowstep, abovestep, notchdepth; double *pkbefore, *pkafter, *notch; int trofcnt, badnotches, jj, kk, notchwidth, prepeak, postpeak, pksttej, pkendej=0; int ampsttej, ampendej; double sttval, endval; int *pkbeforeat, *notchat, *orig_notchat; memset((char *)outarray,0,itemcnt * sizeof(double)); /* FIND PEAKS AND TROUGHS */ lastamp = parray[1]; lastampstep = 0; ampat = 3; trofcnt = 0; while(ampat < itemcnt) { amp = parray[ampat]; ampstep = amp - lastamp; if(ampstep < 0) { if(lastampstep > 0) { // falling after rise = peak outarray[ampat-2] = 1.0; // peak } lastampstep = ampstep; } else if(ampstep > 0) { if(lastampstep < 0) { // rising after fall = trough outarray[ampat-2] = -1.0; // trough trofcnt++; } lastampstep = ampstep; } // else, if flat : lastampstep not updated lastamp = amp; // saddle points are thus ignored, while flat peaks or troughs are spotted ampat += 2; } if(trofcnt == 0) { fprintf(stderr,"NO NOTCHES FOUND\n"); for(j = 0; j < itemcnt; j+=2) { outarray[j] = parray[j]; outarray[j+1] = 0.0; } return FINISHED; } if((pkbefore = (double *)malloc((trofcnt + 6) * sizeof(double)))==NULL) { fprintf(stderr,"INSUFFICIENT MEMORY for trough calculations (1).\n"); return(MEMORY_ERROR); } if((pkafter = (double *)malloc((trofcnt + 6) * sizeof(double)))==NULL) { fprintf(stderr,"INSUFFICIENT MEMORY for trough calculations (2).\n"); return(MEMORY_ERROR); } if((pkbeforeat = (int *)malloc((trofcnt + 6) * sizeof(int)))==NULL) { fprintf(stderr,"INSUFFICIENT MEMORY for trough calculations (1).\n"); return(MEMORY_ERROR); } if((notch = (double *)malloc((trofcnt + 6) * sizeof(double)))==NULL) { fprintf(stderr,"INSUFFICIENT MEMORY for trough calculations (2).\n"); return(MEMORY_ERROR); } if((notchat = (int *)malloc((trofcnt + 6) * sizeof(int)))==NULL) { fprintf(stderr,"INSUFFICIENT MEMORY for trough calculations (2).\n"); return(MEMORY_ERROR); } if((orig_notchat = (int *)malloc((trofcnt + 6) * sizeof(int)))==NULL) { fprintf(stderr,"INSUFFICIENT MEMORY for trough calculations (2).\n"); return(MEMORY_ERROR); } trofcnt = 0; ampat = 1; pkampbelowat = -1; // position of peak before trough pkampaboveat = -1; // position of peak after trough trofampat = -1; // position of trough /* NOTE LEVELS BEFORE AND AFTER NOTCHES AND MARK BAD NOTCHES */ badnotches = 0; while(ampat < itemcnt) { amp = outarray[ampat]; if(amp < 0.0) { if(pkampbelowat < 0) { pkampbelowat = 1; pkbefore[trofcnt] = parray[pkampbelowat]; pkbeforeat[trofcnt] = pkampbelowat; } trofampat = ampat; notch[trofcnt] = parray[ampat]; notchat[trofcnt] = ampat; } else if(amp > 0.0) { // found peak if(trofampat < 0) { // if not yet found trof, this is the peak below the trof pkampbelowat = ampat; pkbefore[trofcnt] = parray[pkampbelowat]; pkbeforeat[trofcnt] = pkampbelowat; } else { // otherwise, its peak above trof : we now have complete notch pkampaboveat = ampat; pkafter[trofcnt] = parray[pkampaboveat]; belowstep = pkbefore[trofcnt] - parray[trofampat]; abovestep = pkafter[trofcnt] - parray[trofampat]; notchdepth = min(belowstep,abovestep); if(notchdepth < minnotch) badnotches = 1; trofcnt++; pkampbelowat = pkampaboveat; // move to next notch pkbefore[trofcnt] = parray[pkampbelowat]; pkbeforeat[trofcnt] = pkampbelowat; pkampaboveat = -1; trofampat = -1; } } ampat += 2; // if no peak found after last trough .. } // and last trof is not at very end (shoulf be impossible) if(trofampat >= 0 && (trofampat != itemcnt-1) && (pkampaboveat < 0)) { pkampaboveat = itemcnt - 1; // set peak to top edge of spectrum pkafter[trofcnt] = parray[pkampaboveat]; belowstep = pkbefore[trofcnt] - parray[trofampat]; abovestep = pkafter[trofcnt] - parray[trofampat]; notchdepth = min(belowstep,abovestep); if(notchdepth < minnotch) // Mark notches that are not deep enough badnotches = 1; trofcnt++; pkbefore[trofcnt] = parray[pkampaboveat]; // Extra "pkbefore" after last notch pkbeforeat[trofcnt] = pkampaboveat; } for(kk=0;kk0) && (pkbefore[kk] - notch[kk-1] < minnotch)) { // x x x x //ELIMINATE PEAK // x o outarray[pkbeforeat[kk]] = 0.0; // KEEP DEEPEST NOTCH if(notch[kk] <= notch[kk-1]) { // x x outarray[notchat[kk-1]] = 0.0; // x o x notch[kk-1] = notch[kk]; // x x x x notchat[kk-1]= notchat[kk]; // + o } else { outarray[notchat[kk]] = 0.0; } for(jj = kk+1;jj < trofcnt; jj++) { pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; notch[jj-1] = notch[jj]; notchat[jj-1] = notchat[jj]; } pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; } else if(pkbefore[kk+1] - notch[kk] < minnotch) { // o + //ELIMINATE NOTCH // x x x x outarray[notchat[kk]] = 0.0; // x o x //ELIMINATE LOWEST PEAK // x x if(pkbefore[kk] >= pkbefore[kk+1]) { outarray[pkbeforeat[kk+1]] = 0.0; if(kk < trofcnt-1) { notch[kk] = notch[kk+1]; notchat[kk] = notchat[kk+1]; } for(jj = kk+2;jj < trofcnt; jj++) { pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; notch[jj-1] = notch[jj]; notchat[jj-1] = notchat[jj]; } pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; } else { outarray[pkbeforeat[kk]] = 0.0; for(jj = kk+1;jj < trofcnt; jj++) { pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; notch[jj-1] = notch[jj]; notchat[jj-1] = notchat[jj]; } pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; } } else { //ELIMINATE PEAK BEFORE AND NOTCH // x outarray[pkbeforeat[kk]] = 0.0; // o x outarray[notchat[kk]] = 0.0; // x x x for(jj = kk+1;jj < trofcnt; jj++) { // x o pkbefore[jj-1] = pkbefore[jj]; // x pkbeforeat[jj-1] = pkbeforeat[jj]; notch[jj-1] = notch[jj]; notchat[jj-1] = notchat[jj]; } pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; } kk--; trofcnt--; } else if(pkbefore[kk+1] - notch[kk] < minnotch) { // x //ELIMINATE PEAK AFTER AND NOTCH // x o outarray[pkbeforeat[kk+1]] = 0.0; // x x x outarray[notchat[kk]] = 0.0; // o x if(kk < trofcnt-1) { // x notch[kk] = notch[kk+1]; notchat[kk] = notchat[kk+1]; } for(jj = kk+2;jj < trofcnt; jj++) { pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; notch[jj-1] = notch[jj]; notchat[jj-1] = notchat[jj]; } pkbefore[jj-1] = pkbefore[jj]; pkbeforeat[jj-1] = pkbeforeat[jj]; kk--; trofcnt--; } } } writeat = 0; // position of write to output /* TRIM THE PEAKS */ if(trofcnt > 0) { for(kk = 0;kk < trofcnt;kk++) { nexttrofat = notchat[kk] - 1; // nexttrofat points to the PAIR of values: notchat points to its amplitude if(kk == 0) prepeak = pkbeforeat[kk]; else prepeak = pkbeforeat[kk] - notchat[kk-1]; postpeak = notchat[kk] - pkbeforeat[kk]; notchwidth = min(prepeak,postpeak); pksttej = (pkbeforeat[kk] - notchwidth) - 1; ampsttej = pksttej + 1; sttval = parray[ampsttej]; ampsttej += 2; while(parray[ampsttej] < sttval) { // avoid any curling up again at peak edges sttval = parray[ampsttej] ; ampsttej += 2; } ampsttej -= 2; pksttej = ampsttej - 1; if(pksttej == pkendej) // Avoid peaks overlapping each other pksttej += 2; pkendej = (pkbeforeat[kk] + notchwidth) - 1; ampendej = pkendej + 1; endval = parray[ampendej]; ampendej -= 2; while(parray[ampendej] < endval) { // avoid any curling up again at peak edges endval = parray[ampendej] ; ampendej -= 2; } ampendej += 2; pkendej = ampendej - 1; while(writeat < pksttej) { outarray[writeat] = parray[writeat]; writeat++; outarray[writeat] = 0.0; writeat++; } while(writeat < pkendej) { outarray[writeat] = parray[writeat]; writeat++; outarray[writeat] = parray[writeat]; writeat++; } while(writeat < nexttrofat) { outarray[writeat] = parray[writeat]; writeat++; outarray[writeat] = 0.0; writeat++; } } nexttrofat = itemcnt; prepeak = pkbeforeat[kk] - notchat[kk-1]; postpeak = (itemcnt + 1) - pkbeforeat[kk]; notchwidth = min(prepeak,postpeak); pksttej = (pkbeforeat[kk] - notchwidth) - 1; if(pksttej == pkendej) pksttej += 2; pkendej = (pkbeforeat[kk] + notchwidth) - 1; while(writeat 1)) return(FALSE); } else return(FALSE); lastchar = *p; p++; } if(!has_digits || sscanf(valstart,"%lf",val)!=1) return(FALSE); *str = p; return(TRUE); }