/* nph-PZT.cgi.c implements the CGI executable that reads-in poles and zeros from a form, sent by a web-client, and then generates a new form that embeds the (graphical) results from the given 'POZE script'. PoZeTools version 0.55, march 24, 2015. Copyright (c) 2002-2015 Pieter Suurmond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is requested to send the modifications to the original developer so that they can be incorporated into the canonical version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include /* For printf(), fileno(). */ #include #include #include /* To choose a filter example at random. */ #include /* For flock(). */ #include /* For unlink(). */ #include "PZT.h" /* PZT headers in parent directory. */ #include "PZT_complex_math.h" /* Complex arithmetic. */ #include "PZT_poze.h" /* POZE objects represent freq. domain. */ #include "PZT_coeff.h" /* COEFF objects represent time domain. */ #include "pieter_cgi.h" /* To parse input from webclients. */ /*--------------------------------------------------------------------------------*/ /* Generates a new filename. Deletes old .poze and .coef files. Installs its' own textfile when it is called for the first time. */ static long nw_filename(void) /* To generate QUASI UNIQUE number for filenames. */ { /* In case of error, count becomes negative. */ #define kMAX_TMP_FILES (256) /* Never more than this number on disk. */ #define kMAX_TMP_CYCLE (16383) /* Number of uniques: MUST BE POWER OF 2 MINUS 1! */ FILE *fp; int fdescr; long d, count = -1L; char buffy[128]; sprintf(buffy, "inc"); /* Construct pathname. */ fp = fopen(buffy, "r+"); if (fp) /* OPEN TEXTFILE FOR READING/WRITING. */ { fdescr = fileno(fp); /* Get unix filedescriptor number. */ flock(fdescr, LOCK_EX); /* Exclusive lock, with blocking. */ if (fscanf(fp, "%16ld", &count) == 1) /* GET max 16 digits */ { count++; count &= kMAX_TMP_CYCLE; rewind(fp); /* INCREMENT & OVERWRITE COUNTFILE. */ if (fprintf(fp,"%ld\n", count) < 1) count = -2L; } flock(fdescr, LOCK_UN); /* Unlock and close. */ fclose(fp); } else /* If we connot open the file for reading, try to create it (easy install). */ { fp = fopen(buffy, "w"); if (fp) { fdescr = fileno(fp); flock(fdescr, LOCK_EX); count = 0L; if (fprintf(fp,"%ld\n", count) < 1) count = -2L; flock(fdescr, LOCK_UN); fclose(fp); } } if (count >= 0) /* Remove old file. */ { d = (count - kMAX_TMP_FILES) & kMAX_TMP_CYCLE; sprintf(buffy, "%05ld.poze", d); unlink(buffy); /* IGNORE unlink FAILURES! (files may not be there) */ sprintf(buffy, "%05ld.coef", d); unlink(buffy); } return count; /* File could not be opened. */ } /*-------------------------------------------------------------------------*/ /* Called by process() when a GET request was received by the server. The routine outputs a complete HTML page to the standard output (i.e. the client). */ static void user_interface(const char* message, const char* poze_script, short log_scale, long show_num, const POZE* pz, /* May be NULL. */ const COEFF* ab) /* May be NULL. */ { const char *ls1 = "", *ls2 = ""; if (log_scale) { ls1 = " CHECKED"; ls2 = "l"; } printf("\ %s 200 OK\n\ Server: %s\n\ Pragma: no-cache\n\ Content-type: text/html; charset=ISO-8859-1\n\ \n\ \n\ \n\ \n\ %s\n\ \n\ \n\
\n\

%s

\n\ Enter your poles and zeros [and an optional gain] below:
\n\
\n\ %s\
\
\n\ Logarithmic frequency scale.    \n\ \n\
\

Frequency domain:

\n\ \"frequency\n\
\n\
", getenv("SERVER_PROTOCOL"), getenv("SERVER_SOFTWARE"), kVERSION, getenv("SCRIPT_NAME"), /* Self-reference. */ kVERSION, poze_script, message, ls1, ls2, show_num); if (ab) { printf("

Time domain:

"); printf("\"impulse

", show_num); printf("Filter coefficients:
");
        COEFF_write(ab,                         /* A's and B's and their indices (time delays). */
                    NULL,                       /* No leading comment on the webpage. */
                    stdout);                    /* Destination. */
        COEFF_write_diff_eq(ab, stdout);
        printf("
\n"); if (pz) printf("How these coefficients were calculated...\

\n", getenv("SCRIPT_NAME"), pz->zero.number, pz->pole.number); } printf("\ \ This digital filter design software is also available as cross-platform commandline application.
\n\ This CGI service may be cloned to other UNIX (including Apple OSX) webservers: \ %s.
\n\ Pieter Suurmond, februart 15, 2014.
\n\
\ \n\ \n", kVERSION); } /*-------------------------------------------------------------------------*/ static void error_report(const char* msg) { printf("\ %s 200 OK\n\ Server: %s\n\ Pragma: no-cache\n\ Content-type: text/html; charset=ISO-8859-1\n\ \n\ \n\ \n\ \n\ %s ERROR!\n\ \n\ \n\

%s ERROR!

\n\ %s

\n\ Return...\n\ \n\ \n", getenv("SERVER_PROTOCOL"), getenv("SERVER_SOFTWARE"), kVERSION, kVERSION, msg, getenv("SCRIPT_NAME")); /* Self-reference. */ } /*-------------------------------------------------------------------------*/ /* Called by main() when a GET request was received by the server. The routine outputs a complete HTML page to the standard output (i.e. the client), and it can never fail (void). */ static void process(const char* poze_script, short log_scale) { static const int max_roots = 24; /* Don't CPU-overload the webserver. */ FILE* fp; POZE* pz; COEFF* ab = (COEFF*)NULL; char fname[32]; char msg[256]; char logline[128]; char* clientsIP; int e; long n; e = POZE_read(&pz, 1, poze_script, msg); if (e) /* Both pz and ab are NULL. */ user_interface(msg, poze_script, log_scale, -1L, pz, ab); else { clientsIP = getenv("REMOTE_ADDR"); if (!POZE_stable(pz)) /* Print some additional info. */ sprintf(msg, "CARE: unstable LTI system!\n"); e = POZE_cancelled(pz, 0.0001, 1); if (e) /* EPSILON, 1 means do it! */ sprintf(msg, "CARE: pole-zero cancelation! %d pair(s) removed!\n", e); n = nw_filename(); /* SAVE 2 TEXTFILES TO DISK. */ if (n >= 0) { e = 1; /* To see if POZE_write() succeeds. */ sprintf(fname, "%05ld.poze", n); fp = fopen(fname, "wt"); if (fp) { /* Log client's IP. */ sprintf(logline, "%s - %s", kVERSION, clientsIP); e = POZE_write(pz, logline, fp); if (e) sprintf(msg, "ERROR: POZE_write() = %d!\n", e); fclose(fp); } else sprintf(msg, "ERROR: cannot (over)write file '%s'!\n", fname); if (!e) /* Should be cleared by POZE_write(). */ { if ((pz->zero.number > max_roots) || (pz->pole.number > max_roots)) sprintf(msg, "Sorry, coefficients are not calculated for \ more than %d poles and/or zeros!\n", max_roots); else { if (COEFF_calc(&ab, pz, NULL)) /* Message may become very large, don't pass msg! */ sprintf(msg, "MEMORY FAILURE in COEFF_calc()!\n"); else { sprintf(fname, "%05ld.coef", n); fp = fopen(fname, "wt"); if (fp) { sprintf(logline, "%s - %s", kVERSION, clientsIP); e = COEFF_write(ab, logline, fp); if (e) sprintf(msg, "ERROR: COEFF_write() = %d!\n", e); fclose(fp); } else sprintf(msg, "ERROR: cannot (over)write file '%s'!\n", fname); } } } /* ab may be NULL. */ user_interface(msg, poze_script, log_scale, n, pz, ab); COEFF_free(&ab); /* No harm when ab is NULL. */ } /* Report cancellation. */ else user_interface("nw_filename() failed!", poze_script, log_scale, -1L, pz, ab); POZE_free(&pz); } } /*------------------------------------------------------------------------*/ static int rec_mul(int poles, int n) /* n is the number of roots. */ { int* idx; /* THIS SHOULD BE THE SAME ALGO- */ int i, j, a; /* RITHM AS IN FILE PZT_coeff.c ! */ long t; idx = malloc(n * sizeof(int)); if (!idx) return 1; printf("

%d ", n); if (poles) printf("pole"); else printf("zero"); if (n != 1) printf("s"); printf(" give"); if (n == 1) printf("s"); printf(" %d coefficient", n + 1); if (n) printf("s"); printf(":

\n"); for (i = 0; i <= n; i++) { printf(""); if (poles) printf("b"); else printf("a"); printf("%d = ", i); if (i) { if (i & 1) printf("-"); if (!poles) printf("G"); printf("("); t = 0L; a = 0; idx[a] = 0; do { for (j = a + 1; j < i; j++) /* Init following indices. */ idx[j] = 1 + idx[j - 1]; if (t++) printf(" + "); for (j = 0; j < i; j++) { if (j) printf("*"); if (poles) printf("P"); else printf("Z"); printf("%d", idx[j]); } a = i-1; /* Select rightmost index. */ while ((a>=0) && (++idx[a] + (i-a) > n)) /* Inc index. */ a--; /* Select preceding index on overflow. */ } while (a >= 0); printf(")"); } else if (poles) printf("1"); else printf("G"); printf("
\n"); } free(idx); printf("
\n"); return 0; } /*-------------------------------------------------------------------------*/ static void show_rec_mul(int nz, int np) { printf("\ %s 200 OK\n\ Server: %s\n\ Pragma: no-cache\n\ Content-type: text/html; charset=ISO-8859-1\n\ \n\ \n\ \n\ \n\ %s - coefficient calculation\n\ \n\ \n", getenv("SERVER_PROTOCOL"), getenv("SERVER_SOFTWARE"), kVERSION); if ((nz < 24) && (np < 24)) { printf("Here is how %s calculated filter coefficients,
", kVERSION); printf("given your set of poles, zeros and gain parameter:
"); rec_mul(0, nz); rec_mul(1, np); printf("Here is the algorithm: \ http://home.hku.nl/~pieter.suurmond/EDU/mt/FilterCoeffCalc.c .
\n"); } else printf("Sorry, too many poles and/or zeros!
\n"); printf("
Back to the online pole/zero editor.\n\ \n\ \n", getenv("SCRIPT_NAME")); } /*-----------------------------------------------------------------------------*/ int main() { /* A comment about POZE syntax that may be POZE language itself. */ static const char* explain = "\ # Empty lines and lines starting with # are ignored. Supply-\r\n\ # ing no gain invokes auto-normalization. Both polar and\r\n\ # cartesian notations are accepted. The order in which poles,\r\n\ # zeros and the (complex) gain appear does not matter.\r\n\ # Some valid lines are: P = 0.98 exp(-0.25 pi j)\r\n\ # P = j - 0.88\r\n\ # Z = -j\r\n\ # Z = 0.6 + 0.6 j\r\n\ # G = exp(-j pi)\r\n"; /* Let's provide some examples / tests. */ #define kNUM_EXAMPLES (12) static const char* examples[kNUM_EXAMPLES] = { "# Bandpass filter example:\r\n\ z=0\r\n\ z=0\r\n\ P = 0.96 exp( 0.25 j pi)\r\n\ P = 0.96 exp(-0.25 j pi)\r\n", "# Bandpass filter example:\r\n\ P = 0.96 exp( 0.25 j pi)\r\n\ P = 0.96 exp(-0.25 j pi)\r\n", "# Non-causal, non-recursive lowpass filter example:\r\n\ Z = -1\r\n\ G = 1\r\n", "# Causal FIR highpass example:\r\n\ Z = 1\r\n\ P = 0\r\n", "# Non-causal, non-recursive lowpass filter example:\r\n\ Z = -1\r\n\ Z = -1\r\n\ Z = -1\r\n\ Z = -1\r\n\ P = 0\r\n\ P = 0\r\n\ G = 1\r\n", "# Allpass filter example:\r\n\ P = 0.9 exp (-0.25 j pi)\r\n\ P = 0.9 exp (0.25 j pi)\r\n\ Z = 1.111111111111 exp (-0.25 j pi)\r\n\ Z = 1.111111111111 exp (0.25 j pi)\r\n", "# Another allpass filter example:\r\n\ p = 0.9 exp (0.25 j pi)\r\n\ z = 1.111111111111 exp (0.25 j pi)\r\n\ g = 1\r\n", "# Symmetry-test:\r\n\ G = 1\r\n\ P = 0.9\r\n\ P = 0.9\r\n\ Z = -0.9\r\n\ Z = -0.9\r\n", "# Eight-order combfilter with minimal delay:\r\n\ p = 0.96\r\n\ p = 0.96 exp (0.25 pi j)\r\n\ p = 0.96 j\r\n\ p = 0.96 exp (0.75 pi j)\r\n\ p = -0.96\r\n\ p = 0.96 exp (1.25 pi j)\r\n\ p = -0.96 j\r\n\ p = 0.96 exp (1.75 pi j)\r\n\ \r\n\ z = 0\r\n\ z = 0\r\n\ z = 0\r\n\ z = 0\r\n\ z = 0\r\n\ z = 0\r\n\ z = 0\r\n\ z = 0\r\n\ \r\n\ g = 0.278610421016\r\n", "# Tenth-order combfilter with maximal delay:\r\n\ p = 0.98\r\n\ p = 0.98 exp (0.2 pi j)\r\n\ p = 0.98 exp (0.4 pi j)\r\n\ p = 0.98 exp (0.6 pi j)\r\n\ p = 0.98 exp (0.8 pi j)\r\n\ p = -0.98\r\n\ p = 0.98 exp (1.2 pi j)\r\n\ p = 0.98 exp (1.4 pi j)\r\n\ p = 0.98 exp (1.6 pi j)\r\n\ p = 0.98 exp (1.8 pi j)\r\n", "# DC filter example:\r\n\ P = 0.98\r\n\ Z = 1\r\n", "# Nyquist filter example:\r\n\ P = -0.98\r\n\ Z = -1\r\n" }; int e; CGI_INPUT* web_input; char buffy[4096]; /* Lazy static, take care it is big enough! */ e = CGI_receive(&web_input, /* Receive pointer to input object here. */ 16384, /* Max bytes allowed from webclient. */ 3); /* Max key/value pairs allowed. */ if (e >= 0) { if ((e == 0) || /* e == 0 means received nothing. */ (web_input->request_method == kCGI_request_method_GET)) { if (web_input->pairs == 1) /* Show how coefficients are calculated. */ show_rec_mul(atoi(web_input->names[0]), atoi(web_input->values[0])); else { /* Choose an example at random. */ sprintf(buffy, "%s\r\n%s", examples[time(NULL) % kNUM_EXAMPLES], explain); process(buffy, 0); /* Linear frequency scale. */ } } else if ((web_input->content_type == kCGI_content_type_NORMAL) && (web_input->pairs >= 1) && (!strcmp(web_input->names[0], "T"))) { if ((web_input->pairs >= 2) && (!strcmp(web_input->names[1], "L"))) e = 1; /* Logarithmic frequency scale. */ else e = 0; process(web_input->values[0], e); } else { error_report("Sorry, invalid data."); } CGI_release(&web_input); /* Release dynamically allocated memory. */ } else { sprintf(buffy, "CGI_receive()=%d.", e); error_report(buffy); } return 0; }