1 /*
2  * @(#)$Id: file.c,v 2.9 2009/01/17 02:31:16 baccala Exp $
3  *
4  * Copyright (C) 1996 - 2000 Tim Witham <twitham@quiknet.com>
5  *
6  * (see the files README and COPYING for more details)
7  *
8  * This file implements the file and command-line I/O
9  *
10  */
11 
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include "oscope.h"		/* program defaults */
16 #include "display.h"		/* display routines */
17 #include "func.h"		/* signal math functions */
18 
19 int backwards_compat_1_10 = 0;	/* TRUE if parsing a pre-1.10 save file */
20 int backwards_compat_2_0 = 0;	/* TRUE if parsing a pre-2.0 save file */
21 
22 /* force num to stay within the range lo - hi */
23 int
24 limit(int num, int lo, int hi)
25 {
26   if (num < lo)
27     num = lo;
28   if (num > hi)
29     num = hi;
30   return(num);
31 }
32 
33 /* parse command-line or file opt character, using given optarg string
34  *
35  * optarg can have a trailing newline, if we're reading from a file
36  */
37 
38 void
39 handle_opt(int opt, char *optarg)
40 {
41   char *p, *q;
42   char buf[16];
43   Channel *s;
44 
45   switch (opt) {
46   case 'D':			/* data source selection */
47     if ((p = index(optarg, '\n')) != NULL) *p = '\0';
48     if (!datasrc_byname(optarg)) {
49       fprintf(stderr, "Couldn't find data source %s\n\n", optarg);
50       usage(1);
51     }
52     break;
53   case 'o':			/* data source specific option */
54   case 'O':
55     /* Older data file format used -o for BitScope only, and printed
56      * BitScope options even if BitScope wasn't enabled, so if we're
57      * reading an older file, only process -o if data source is BitScope
58      */
59     if ((datasrc != NULL) &&
60 	(!backwards_compat_1_10 || !strcasecmp(datasrc->name, "BitScope"))) {
61       if ((datasrc->set_option == NULL)
62 	  || (datasrc->set_option(optarg) == 0)) {
63 	fprintf(stderr, "Couldn't set option %s\n\n", optarg);
64 	usage(1);
65       }
66     }
67     break;
68   case 'r':			/* soundcard sample rate - deprecated */
69   case 'R':
70     if ((datasrc != NULL) && !strcasecmp(datasrc->name, "Soundcard")
71 	&& (datasrc->set_option != NULL)) {
72       snprintf(buf, sizeof(buf), "rate=%s", optarg);
73       datasrc->set_option(buf);
74     }
75     break;
76   case 's':			/* scale (zoom) */
77   case 'S':
78     scope.scale = limit(strtol(p = optarg, NULL, 0), 1, 1000);
79     if ((q = strchr(p, '/')) != NULL)
80       scope.div = limit(strtol(++q, NULL, 0), 1, 2000);
81     break;
82   case 't':			/* trigger */
83   case 'T':
84       scope.trig = limit(strtol(p = optarg, NULL, 0), 0, 255);
85       if ((q = strchr(p, ':')) != NULL) {
86 	scope.trige = limit(strtol(++q, NULL, 0), 0, 2);
87 	p = q;
88       }
89       if ((q = strchr(p, ':')) != NULL) {
90 	if (*(++q) == 'x' || *q == 'X')
91 	  scope.trigch = 0;
92 	else if (*q == 'y' || *q == 'Y')
93 	  scope.trigch = 1;
94 	else
95 	  scope.trigch = strtol(q, NULL, 0);
96       }
97       if (datasrc && datasrc->set_trigger
98 	  && datasrc->set_trigger(scope.trigch,
99 				  &scope.trig, scope.trige) == 0) {
100 	/* Unable to set trigger, so clear it */
101 	if (datasrc && datasrc->clear_trigger) datasrc->clear_trigger();
102 	scope.trige = 0;
103       }
104     break;
105   case 'l':			/* cursor lines */
106   case 'L':
107     scope.curs = 1;
108     scope.cursa = limit(strtol(p = optarg, NULL, 0), 1, MAXWID);
109     if ((q = strchr(p, ':')) != NULL) {
110       scope.cursb = limit(strtol(++q, NULL, 0), 1, MAXWID);
111       p = q;
112     }
113     if ((q = strchr(p, ':')) != NULL)
114       scope.curs = limit(strtol(++q, NULL, 0), 0, 1);
115     break;
116   case 'd':			/* dma divisor (backwards compatibility) */
117     if (datasrc && !strcasecmp(datasrc->name, "Soundcard")) {
118       snprintf(buf, sizeof(buf), "dma=%s", optarg);
119       handle_opt('o', buf);
120     }
121     break;
122   case 'f':			/* font name */
123   case 'F':
124     strcpy(fontname, optarg);
125     break;
126   case 'p':			/* plotting mode */
127   case 'P':
128     scope.mode = limit(strtol(optarg, NULL, 0), 0, 5);
129     break;
130   case 'g':			/* graticule on/off */
131   case 'G':
132     scope.grat = limit(strtol(optarg, NULL, 0), 0, 2);
133     break;
134   case 'b':			/* behind/front */
135   case 'B':
136     scope.behind = !DEF_B;
137     break;
138   case 'v':			/* verbose display */
139   case 'V':
140     scope.verbose = !DEF_V;
141     break;
142   case 'i':			/* minimum update interval */
143   case 'I':
144     scope.min_interval = strtol(optarg, NULL, 0);
145     break;
146   case 'x':			/* sound card (backwards compatibility) */
147   case 'X':
148   case 'y':
149   case 'Y':
150     break;
151   case 'z':			/* serial scope (backwards compatibility) */
152   case 'Z':
153     break;
154   case 'a':			/* Active (selected) channel */
155   case 'A':
156     scope.select = limit(strtol(optarg, NULL, 0) - 1, 0, CHANNELS - 1);
157     break;
158   case 'h':			/* help */
159   case 'H':
160     usage(0);
161     break;
162   case ':':			/* unknown option */
163   case '?':
164     usage(1);
165     break;
166   default:			/* channel settings */
167     if (opt >= '1' && opt <= '0' + CHANNELS) {
168       s = &ch[opt - '1'];
169       p = optarg;
170       s->show = 1;
171       if (*p == '+') {
172 	s->show = 0;
173 	p++;
174       }
175 
176       /* Prior to the 2.0 release, signal position was stored as the
177        * number of y pixels to offset.  Now it's stored as 100 times
178        * the decimal fraction s->pos, which is a floating point number
179        * ranging nominally from -1 (bottom of screen) to +1 (top).
180        * The data part of the display used to be 160 pixels less than
181        * v_points, so for a 640x480 display (the most common), the
182        * data part was 320 pixels tall, so we divide by 160, negative
183        * since the old number's zero point was at the top.
184        */
185       s->pos = limit(-strtol(p, NULL, 0), -1280, 1280);
186       if (backwards_compat_2_0) s->pos /= -160;
187       else s->pos /= 100;
188 
189       if ((q = strchr(p, '.')) != NULL) {
190 	s->bits = limit(strtol(++q, NULL, 0), 0, 16);
191 	p = q;
192       }
193       if ((q = strchr(p, ':')) != NULL) {
194 	s->target_mult = limit(strtol(++q, NULL, 0), 1, 100);
195 	p = q;
196       }
197       if ((q = strchr(p, '/')) != NULL) {
198 	s->target_div = limit(strtol(++q, NULL, 0), 1, 100);
199 	p = q;
200       }
201       if ((q = strchr(p, ':')) != NULL) {
202 	if (*++q >= '0' && *q <= '9') {
203 	  if (*q > '0') {
204 	    function_bynum_on_channel(strtol(q, NULL, 0), s);
205 	  }
206 	} else if (*q >= 'a' && *q <= 'z'
207 		   && (*(q + 1) == '\0' || *(q + 1) == '\n')) {
208 
209 	  /* Older versions used x, y, z for data channels, now we
210 	   * use a, b, c, etc.  Probescope used z exclusively
211 	   */
212 
213 	  if (datasrc && !backwards_compat_1_10
214 	      && ((*q - 'a') <= datasrc->nchans())) {
215 	    recall_on_channel(datasrc->chan(*q - 'a'), s);
216 	  } else if (datasrc && backwards_compat_1_10 && (*q == 'z')
217 		     && !strcasecmp(datasrc->name, "ProbeScope")) {
218 	    recall_on_channel(datasrc->chan(0), s);
219 	  } else if (datasrc && backwards_compat_1_10 && (*q >= 'x')) {
220 	    recall_on_channel(datasrc->chan(*q - 'x'), s);
221 	  } else {
222 	    /* Might have a (slight) problem here if we're
223 	     * reading an older format file that has a data source
224 	     * and memory saved on 'a', for example.  Then we'll
225 	     * end up recalling mem 'a' here, even though 'a'
226 	     * is now used as a data channel.
227 	     */
228 	    recall_on_channel(&mem[*q - 'a'], s);
229 	  }
230 
231 	} else {
232 	  start_command_on_channel(q, s);
233 	}
234       }
235     }
236     break;
237   }
238 }
239 
240 /* write scope settings and memory buffers to given filename */
241 void
242 writefile(char *filename)
243 {
244   FILE *file;
245   int i, j, k = 0, l = 0, chan[26];
246   char *s;
247   Channel *p;
248 
249   if ((file = fopen(filename, "w")) == NULL) {
250     sprintf(error, "%s: can't write %s", progname, filename);
251     message(error);
252     return;
253   }
254   fprintf(file, "# %s, version %s\n\
255 #\n\
256 # -D %s\n", progname, version, datasrc->name);
257 
258   if (datasrc->save_option != NULL) {
259     for (i=0; (s = datasrc->save_option(i)) != NULL; i++) {
260       if (s[0] != '\0') fprintf(file, "# -o %s\n", s);
261     }
262   }
263 
264   fprintf(file, "# -a %d\n\
265 # -s %d/%d\n\
266 # -t %d:%d:%d\n\
267 # -l %d:%d:%d\n\
268 # -p %d\n\
269 # -g %d\n\
270 %s%s",
271 	  scope.select + 1,
272 	  scope.scale, scope.div,
273 	  scope.trig - 128, scope.trige, scope.trigch,
274 	  scope.cursa, scope.cursb, scope.curs,
275 	  scope.mode,
276 	  scope.grat,
277 	  scope.behind ? "# -b\n" : "",
278 	  scope.verbose ? "# -v\n" : "");
279   for (i = 0 ; i < CHANNELS ; i++) {
280     p = &ch[i];
281     if (p->signal) {
282       fprintf(file, "# -%d %s%d.%d:%d/%d:%s\n", i + 1, p->show ? "" : "+",
283 	      -(int)(p->pos*100), p->bits, p->target_mult, p->target_div,
284 	      p->signal->savestr);
285     }
286   }
287   /* XXX code need to be carefully checked out */
288   for (i = 0 ; i < 26 ; i++) {
289     if (mem[i].num >  0)
290       chan[k++] = i;
291     if (mem[i].num > l)
292       l = mem[i].num;
293   }
294   if (k) {
295     for (i = 0 ; i < k ; i++) {
296       /* XXX color written is channel color (it can't be changed) */
297 #if 0
298       fprintf(file, "%s%c(%02d)", i ? "\t" : "# ",
299 	      chan[i] + 'a', roloc[mem[chan[i]].color]);
300 #else
301       fprintf(file, "%s%c(%02d)", i ? "\t" : "# ",
302 	      chan[i] + 'a', 15);
303 #endif
304     }
305     for (i = 0 ; i < k ; i++) {
306       fprintf(file, "%s%d", i ? "\t" : "\n#:", mem[chan[i]].rate);
307     }
308     for (i = 0 ; i < k ; i++) {
309       fprintf(file, "%s%d", i ? "\t" : "\n#=", mem[chan[i]].volts);
310     }
311     fprintf(file, "\n");
312     for (j = 0 ; j < l ; j++) {
313       for (i = 0 ; i < k ; i++) {
314 	fprintf(file, "%s%d", i ? "\t" : "", mem[chan[i]].data[j]);
315       }
316       fprintf(file, "\n");
317     }
318   }
319   fclose(file);
320   sprintf(error, "wrote %s", filename);
321   message(error);
322 }
323 
324 /* Backwards compatibility with older file format that didn't
325  * explicitly specify a data source, so we make another pass over the
326  * entire file to try and figure out / guess our source before
327  * continuing to parse the file.  Basically, an "-x" suggests
328  * either BitScope or ProbeScope, and a "-z" suggests either
329  * soundcard or ESD.  Neither suggests Soundcard and ProbeScope
330  * together.  Both suggest no input device at all.
331  */
332 
333 void
334 guess_input_device_pre_1_10(char *filename)
335 {
336   FILE *file;
337   char buff[256];
338   int xseen=0, zseen=0;
339 
340   if ((file = fopen(filename, "r")) == NULL) {
341     sprintf(error, "%s: can't read %s", progname, filename);
342     message(error);
343     return;
344   }
345 
346   while (fgets(buff, sizeof(buff), file)) {
347     if (!strncmp("# -x", buff, 4)) xseen=1;
348     else if (!strncmp("# -z", buff, 4)) zseen=1;
349   }
350 
351   if (!zseen && xseen) {
352     /* BitScope or ProbeScope */
353     if (!datasrc_byname("Bitscope") && !datasrc_byname("Probescope")) {
354       fprintf(stderr, "Couldn't find either a Bitscope or Probescope\n");
355     }
356   } else if (!zseen && !xseen) {
357     if (!datasrc_byname("Probescope")
358 	&& !datasrc_byname("ESD")
359 	&& !datasrc_byname("Soundcard")) {
360       fprintf(stderr, "Couldn't find either a Probescope or sound device\n");
361     }
362     /* ESD/Soundcard/ProbeScope */
363   } else if (zseen && !xseen) {
364     /* ESD/Soundcard */
365     if (!datasrc_byname("ESD") && !datasrc_byname("Soundcard")) {
366       fprintf(stderr, "Couldn't find a sound device\n");
367     }
368   } else {
369     /* None - don't have a NULL input device (maybe we should) */
370   }
371 }
372 
373 /* read scope settings and memory buffers from given filename */
374 void
375 readfile(char *filename)
376 {
377   FILE *file;
378   char c, *p, *q, buff[256];
379   int version[2];
380   int i = 0, j = 0, k, valid = 0, chan[26] =
381   {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
382    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
383 
384   if ((file = fopen(filename, "r")) == NULL) {
385     sprintf(error, "%s: can't read %s", progname, filename);
386     message(error);
387     return;
388   }
389   init_scope();			/* reset everything */
390   init_channels();
391   init_math();
392   while (fgets(buff, 256, file)) {
393     if (buff[0] == '#') {
394       if (sscanf(buff, "# %*s version %d.%d", &version[0], &version[1]) == 2) {
395 	if ((version[0] <= 1) && (version[1] < 10)) {
396 	  guess_input_device_pre_1_10(filename);
397 	  backwards_compat_1_10 = 1;
398 	}
399 	if (version[0] == 1) {
400 	  backwards_compat_2_0 = 1;
401 	}
402 	valid = 1;
403       } else if (!strncmp("# -", buff, 3)) {
404 	/* valid = 1; */
405 	handle_opt(buff[3], &buff[5]);
406       } else if (valid && buff[3] == '(' && buff[6] == ')') {
407 	j = 0;
408 	q = buff + 2;
409 	while (j < 26 && (sscanf(q, "%c(%d) ", &c, &k) == 2)) {
410 	  if (c >= 'a' && c <= 'z' && k >= 0 && k <= 255) {
411 	    chan[j++] = c - 'a';
412 	    //XXX 'k' is color and it is ignored
413 	    //mem[c - 'a'].color = color[k];
414 	    mem[c - 'a'].frame ++;
415 	    /* Guess some multiple of two here for our data size.
416 	     * We'll adjust this up if needed.
417 	     */
418 	    mem[c - 'a'].data = malloc(16 * sizeof(short));
419 	    mem[c - 'a'].width = 16;
420 	  }
421 	  q += 6;
422 	}
423       } else if (!strncmp("#:", buff, 2)) {
424 	j = 0;
425 	q = buff + 2;
426 	while (q && j < 26 && (sscanf(q, "%d ", &k) == 1)) {
427 	  mem[chan[j++]].rate = k;
428 	  q = strchr(++q, '\t');
429 	}
430       } else if (!strncmp("#=", buff, 2)) {
431 	j = 0;
432 	q = buff + 2;
433 	while (q && j < 26 && (sscanf(q, "%d ", &k) == 1)) {
434 	  mem[chan[j++]].volts = k;
435 	  q = strchr(++q, '\t');
436 	}
437       }
438     } else if (valid &&
439 	       ((buff[0] >= '0' && buff[0] <= '9') || buff[0] == '-')) {
440 
441       /* This is a line of memory data.  Integer values, separated by
442        * tabs, and we already read into the chan[] array the mapping
443        * between the tab-separated fields and memory channels.  Take
444        * care to dynamically resize the memory channel data arrays.
445        */
446 
447       j = 0;		/* Field number (starts at 0) */
448       p = buff;		/* Pointer to tab that starts the field (or buff) */
449 
450       while (j < 26 && (!j || ((p = strchr(p, '\t')) != NULL))) {
451 	if (chan[j] > -1) {
452 	  if (mem[chan[j]].width <= i) {
453 	    while (mem[chan[j]].width <= i) {
454 	      mem[chan[j]].width *= 2;
455 	    }
456 	    mem[chan[j]].data = realloc(mem[chan[j]].data,
457 					mem[chan[j]].width * sizeof(short));
458 	  }
459 	  mem[chan[j]].data[i] = strtol(p, NULL, 0);
460 	  mem[chan[j]].num = i + 1;
461 
462 	  p ++;
463 	  j ++;
464 	}
465       }
466       i++;
467     }
468   }
469   fclose(file);
470 
471   /* If we read any memory channels, set their widths to be exactly
472    * the number of data values in them.  We do this so we get an
473    * accurate display indication of the number of samples.
474    */
475 
476   for (j=0; j<26; j++) {
477     if (chan[j] != -1) {
478       mem[chan[j]].width = mem[chan[j]].num;
479     }
480   }
481 
482   backwards_compat_1_10 = 0;	/* in case we set these during the parse */
483   backwards_compat_2_0 = 0;
484   if (valid) {
485     clear();		/* XXX not sure if we need this */
486     sprintf(error, "loaded %s", filename);
487     message(error);
488   } else {
489     sprintf(error, "invalid format: %s", filename);
490     message(error);
491   }
492 }
493