1 /*
2 * @(#)$Id: func.c,v 2.3 2009/01/17 02:31:16 baccala Exp $
3 *
4 * Copyright (C) 1996 - 2001 Tim Witham <twitham@quiknet.com>
5 *
6 * (see the files README and COPYING for more details)
7 *
8 * This file implements the signal math and memory.
9 * To add math functions, search for !!! and add to those sections.
10 *
11 */
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <string.h>
17 #include <math.h>
18 #include <signal.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include "oscope.h"
22 #include "fft.h"
23 #include "display.h"
24 #include "func.h"
25
26 Signal mem[26]; /* 26 memories, corresponding to 26 letters */
27
28 /* recall given memory register to the currently selected signal */
29 void
recall_on_channel(Signal * signal,Channel * ch)30 recall_on_channel(Signal *signal, Channel *ch)
31 {
32 if (ch->signal) ch->signal->listeners --;
33 ch->signal = signal;
34 if (signal) {
35 signal->listeners ++;
36
37 /* no guarantee that signal->bits will be correct yet */
38 ch->bits = signal->bits;
39 }
40 }
41
42 void
recall(Signal * signal)43 recall(Signal *signal)
44 {
45 recall_on_channel(signal, &ch[scope.select]);
46 }
47
48 /* store the currently selected signal to the given memory register */
49 void
save(char c)50 save(char c)
51 {
52 int i;
53
54 i = c - 'A';
55
56 if (ch[scope.select].signal == NULL) return;
57
58 /* Don't want the name - leave that at 'Memory x'
59 * Also, increment frame instead of setting it to signal->frame in
60 * case signal->frame is the same as mem's old frame number!
61 */
62 if (mem[i].data != NULL) {
63 free(mem[i].data);
64 }
65 mem[i].data = malloc(ch[scope.select].signal->width * sizeof(short));
66 memcpy(mem[i].data, ch[scope.select].signal->data,
67 ch[scope.select].signal->width * sizeof(short));
68 mem[i].rate = ch[scope.select].signal->rate;
69 mem[i].num = ch[scope.select].signal->num;
70 mem[i].width = ch[scope.select].signal->width;
71 mem[i].frame ++;
72 mem[i].volts = ch[scope.select].signal->volts;
73 }
74
75 /* !!! External process handling
76 *
77 * Could use some work... the original code (xoscope-1.8) always sent
78 * the first h_points data points from the first two display channels
79 * through to the external process, even if the scaling on those
80 * channels was set up so that weren't h_points of valid data, or
81 * set down so that what was displayed was way more than h_points.
82 * In any event, I'm not a big fan of these math functions, so
83 * I just left it alone like that.
84 */
85
86 struct external {
87 struct external *next;
88 Signal signal;
89 int pid; /* Zero if we already closed it down */
90 int to, from; /* Pipes */
91 int last_frame_ch0, last_frame_ch1;
92 int last_num_ch0, last_num_ch1;
93 };
94
95 static struct external *externals = NULL;
96
97 /* startcommand() / start_command_on_channel()
98 *
99 * Start an external command running on the current display channel.
100 *
101 * gr_* UIs call this after prompting for command to run
102 */
103
104 void
start_command_on_channel(char * command,Channel * ch)105 start_command_on_channel(char *command, Channel *ch)
106 {
107 struct external *ext;
108 int pid;
109 int from[2], to[2];
110 static char *path, *oscopepath;
111
112 if (pipe(to) || pipe(from)) { /* get a set of pipes */
113 sprintf(error, "%s: can't create pipes", progname);
114 perror(error);
115 return;
116 }
117
118 signal(SIGPIPE, SIG_IGN);
119
120 if ((pid = fork()) > 0) { /* parent */
121 close(to[0]);
122 close(from[1]);
123 } else if (pid == 0) { /* child */
124 close(to[1]);
125 close(from[0]);
126 close(0);
127 close(1); /* redirect stdin/out through pipes */
128 dup2(to[0], 0);
129 dup2(from[1], 1);
130 close(to[0]);
131 close(from[1]);
132
133 /* XXX add additional environment vars here for sampling rate
134 * and number of samples per frame
135 */
136
137 if ((oscopepath = getenv("OSCOPEPATH")) == NULL)
138 oscopepath = PACKAGE_LIBEXEC_DIR;
139 if ((path = malloc(strlen(oscopepath) + 6)) != NULL) {
140 sprintf(path,"PATH=%s", oscopepath);
141 putenv(path);
142 /* putenv() requires buffer to stick around, so no free(),
143 * but we're in the child, and about to exec, so no big deal
144 */
145 }
146
147 execlp("/bin/sh", "sh", "-c", command, NULL);
148 sprintf(error, "%s: child can't exec /bin/sh -c \"%s\"",
149 progname, command);
150 perror(error);
151 exit(1);
152 } else { /* fork error */
153 sprintf(error, "%s: can't fork", progname);
154 perror(error);
155 return;
156 }
157
158 ext = malloc(sizeof(struct external));
159 if (ext == NULL) {
160 fprintf(stderr, "malloc() struct external failed\n");
161 return;
162 }
163 bzero(ext, sizeof(struct external));
164
165 strncpy(ext->signal.savestr, command, sizeof(ext->signal.savestr));
166 ext->pid = pid;
167 ext->from = from[0];
168 ext->to = to[1];
169
170 ext->next = externals;
171 externals = ext;
172
173 message(command);
174
175 recall_on_channel(&ext->signal, ch);
176 }
177
178 void
startcommand(char * command)179 startcommand(char *command)
180 {
181 if (scope.select > 1) {
182 start_command_on_channel(command, &ch[scope.select]);
183 clear();
184 }
185 }
186
187 /* Check everything on the externals list; run what needs to be run,
188 * and clean up anything left linguring behind.
189 */
190
191 static void
run_externals(void)192 run_externals(void)
193 {
194 struct external *ext;
195
196 short *a, *b, *c;
197 int i, errors;
198
199 for (ext = externals; ext != NULL; ext = ext->next) {
200
201 if (ext->signal.listeners > 0) {
202
203 if ((ext->pid > 0) && (ch[0].signal != NULL) && (ch[1].signal != NULL)) {
204
205 /* There's a slight chance that if we change one of the channels,
206 * the new channel may have a frame number identical to the last
207 * one, but that shouldn't hurt us too bad.
208 */
209
210 if ((ch[0].signal->frame != ext->last_frame_ch0) ||
211 (ch[1].signal->frame != ext->last_frame_ch1)) {
212
213 ext->last_frame_ch0 = ch[0].signal->frame;
214 ext->last_frame_ch1 = ch[1].signal->frame;
215 ext->signal.frame ++;
216 ext->signal.num = 0;
217
218 }
219
220 /* We may already have sent and received part of a frame, so
221 * start our pointers at whatever our last offset was, and
222 * keep going until we hit the limit of either channel 0
223 * or channel 1. XXX explain this better
224 * XXX make sure this can't slow the program down!
225 */
226
227 a = ch[0].signal->data + ext->signal.num;
228 b = ch[1].signal->data + ext->signal.num;
229 c = ext->signal.data + ext->signal.num;
230 errors = 0;
231 for (i = ext->signal.num;
232 (i < ch[0].signal->num) && (i < ch[1].signal->num); i++) {
233 if (write(ext->to, a++, sizeof(short)) != sizeof(short))
234 errors ++;
235 if (write(ext->to, b++, sizeof(short)) != sizeof(short))
236 errors ++;
237 if (read(ext->from, c++, sizeof(short)) != sizeof(short))
238 errors ++;
239 }
240 ext->signal.num = i;
241
242 if (errors) {
243 sprintf(error, "%s: %d pipe r/w errors from \"%s\"",
244 progname, errors, ext->signal.savestr);
245 perror(error);
246 /* XXX do something here other than perror to notify user */
247
248 close(ext->from);
249 close(ext->to);
250 waitpid(ext->pid, NULL, 0);
251 ext->pid = 0;
252 }
253 }
254
255 } else {
256
257 /* Nobody listening anymore; close down the pipes and wait for
258 * process to exit. Maybe we should timeout in case of a hang?
259 */
260
261 if (ext->pid) {
262 close(ext->from);
263 close(ext->to);
264 waitpid(ext->pid, NULL, 0);
265 }
266
267 /* Delete ext from list and free() it */
268
269 }
270 }
271 }
272
273 /* !!! The functions; they take one arg: a Signal ptr to store results in */
274
275 /* Invert */
276 void
inv(Signal * dest,Signal * src)277 inv(Signal *dest, Signal *src)
278 {
279 int i;
280 short *a, *b;
281
282 if (src == NULL) return;
283
284 dest->rate = src->rate;
285 dest->num = src->num;
286 dest->volts = src->volts;
287 dest->frame = src->frame;
288
289 a = src->data;
290 b = dest->data;
291 for (i = 0 ; i < src->num; i++) {
292 *b++ = -1 * *a++;
293 }
294 }
295
296 void
inv1(Signal * sig)297 inv1(Signal *sig)
298 {
299 inv(sig, ch[0].signal);
300 }
301
302 void
inv2(Signal * sig)303 inv2(Signal *sig)
304 {
305 inv(sig, ch[1].signal);
306 }
307
308 /* The sum of the two channels */
309 void
sum(Signal * dest)310 sum(Signal *dest)
311 {
312 int i;
313 short *a, *b, *c;
314
315 if ((ch[0].signal == NULL) || (ch[1].signal == NULL)) return;
316
317 a = ch[0].signal->data;
318 b = ch[1].signal->data;
319 c = dest->data;
320
321 dest->frame = ch[0].signal->frame + ch[1].signal->frame;
322 dest->num = ch[0].signal->num;
323 if (dest->num > ch[1].signal->num) dest->num = ch[1].signal->num;
324
325 for (i = 0 ; i < dest->num ; i++) {
326 *c++ = *a++ + *b++;
327 }
328 }
329
330 /* The difference of the two channels */
331 void
diff(Signal * dest)332 diff(Signal *dest)
333 {
334 int i;
335 short *a, *b, *c;
336
337 if ((ch[0].signal == NULL) || (ch[1].signal == NULL)) return;
338
339 a = ch[0].signal->data;
340 b = ch[1].signal->data;
341 c = dest->data;
342
343 dest->frame = ch[0].signal->frame + ch[1].signal->frame;
344 dest->num = ch[0].signal->num;
345 if (dest->num > ch[1].signal->num) dest->num = ch[1].signal->num;
346
347 for (i = 0 ; i < dest->num ; i++) {
348 *c++ = *a++ - *b++;
349 }
350 }
351
352
353 /* The average of the two channels */
354 void
avg(Signal * dest)355 avg(Signal *dest)
356 {
357 int i;
358 short *a, *b, *c;
359
360 if ((ch[0].signal == NULL) || (ch[1].signal == NULL)) return;
361
362 a = ch[0].signal->data;
363 b = ch[1].signal->data;
364 c = dest->data;
365
366 dest->frame = ch[0].signal->frame + ch[1].signal->frame;
367 dest->num = ch[0].signal->num;
368 if (dest->num > ch[1].signal->num) dest->num = ch[1].signal->num;
369
370 for (i = 0 ; i < dest->num ; i++) {
371 *c++ = (*a++ + *b++) / 2;
372 }
373 }
374
375 /* Fast Fourier Transform of channels 0 and 1
376 *
377 * The point of the dest->frame calculation is that the value changes
378 * whenever the data changes, but if the data is constant, it doesn't
379 * change. The display code only looks at changes in frame number to
380 * decide when to redraw a signal; the actual value doesn't matter.
381 */
382
383 void
fft1(Signal * dest)384 fft1(Signal *dest)
385 {
386 if (ch[0].signal == NULL) return;
387
388 dest->num = 440;
389 dest->frame = 10000 * ch[0].signal->frame + ch[0].signal->num;
390
391 fft(ch[0].signal->data, dest->data);
392 }
393
394 void
fft2(Signal * dest)395 fft2(Signal *dest)
396 {
397 if (ch[1].signal == NULL) return;
398
399 dest->num = 440;
400 dest->frame = 10000 * ch[1].signal->frame + ch[1].signal->num;
401
402 fft(ch[1].signal->data, dest->data);
403 }
404
405 /* isvalid() functions for the various math functions.
406 *
407 * These functions also have the side effect of setting the volts/rate
408 * fields in the Signal structure, something we count on happening
409 * whenever we call update_math_signals(), which calls these
410 * functions.
411 *
412 * These functions are also responsible for mallocing the data areas
413 * in the math function's associated Signal structures.
414 */
415
ch1active(Signal * dest)416 int ch1active(Signal *dest)
417 {
418 dest->frame = 0;
419 dest->num = 0;
420
421 if (ch[0].signal == NULL) {
422 dest->rate = 0;
423 dest->volts = 0;
424 return 0;
425 }
426
427 dest->rate = ch[0].signal->rate;
428 dest->volts = ch[0].signal->volts;
429
430 if (dest->width != ch[0].signal->width) {
431 dest->width = ch[0].signal->width;
432 if (dest->data != NULL) free(dest->data);
433 dest->data = malloc(dest->width * sizeof(short));
434 }
435
436 return 1;
437 }
438
ch2active(Signal * dest)439 int ch2active(Signal *dest)
440 {
441 dest->frame = 0;
442 dest->num = 0;
443
444 if (ch[1].signal == NULL) {
445 dest->rate = 0;
446 dest->volts = 0;
447 return 0;
448 }
449
450 dest->rate = ch[1].signal->rate;
451 dest->volts = ch[1].signal->volts;
452
453 if (dest->width != ch[1].signal->width) {
454 dest->width = ch[1].signal->width;
455 if (dest->data != NULL) free(dest->data);
456 dest->data = malloc(dest->width * sizeof(short));
457 }
458
459 return 1;
460 }
461
chs12active(Signal * dest)462 int chs12active(Signal *dest)
463 {
464 dest->frame = 0;
465 dest->num = 0;
466
467 if ((ch[0].signal == NULL) || (ch[1].signal == NULL)
468 || (ch[0].signal->rate != ch[1].signal->rate)
469 || (ch[0].signal->volts != ch[1].signal->volts)) {
470 dest->rate = 0;
471 dest->volts = 0;
472 return 0;
473 }
474
475 dest->rate = ch[0].signal->rate;
476 dest->volts = ch[0].signal->volts;
477
478 /* All of the associated functions (sum, diff, avg) only use the
479 * minimum of the samples on Channels 1 and 2, so we can safely base
480 * the size of our data array on Channel 1 only... the worst that
481 * can happen is that it is too big.
482 */
483
484 if (dest->width != ch[0].signal->width) {
485 dest->width = ch[0].signal->width;
486 if (dest->data != NULL) free(dest->data);
487 dest->data = malloc(dest->width * sizeof(short));
488 }
489
490 return 1;
491 }
492
493 /* special isvalid() functions for FFT
494 *
495 * A considerable majority of the code in fft.c is devoted to scaling
496 * the FFT so that it fits in WINDOW_RIGHT - WINDOW_LEFT = 540 - 100 =
497 * 440 values. The stored rate (negated to indicate that it's in
498 * Hz/sample, not samples/sec), is the frequency increment of each
499 * sample value in Hz, times 10. Since the maximum frequency in
500 * an FFT is half the sampling rate, we divide that sampling rate
501 * by two, then by 440 to get Hz per sample, then multiply by 10,
502 * for a net of dividing by 88. This is also how we get a value
503 * of 440 for dest->num (used above, in actual FFT functions).
504 */
505
ch1FFTactive(Signal * dest)506 int ch1FFTactive(Signal *dest)
507 {
508 dest->frame = 0;
509 dest->num = 0;
510 dest->volts = 0;
511
512 if (ch[0].signal == NULL) {
513 dest->rate = 0;
514 return 0;
515 } else {
516 dest->rate = -ch[0].signal->rate / 80;
517 return 1;
518 }
519 }
520
ch2FFTactive(Signal * dest)521 int ch2FFTactive(Signal *dest)
522 {
523 dest->frame = 0;
524 dest->num = 0;
525 dest->volts = 0;
526
527 if (ch[1].signal == NULL) {
528 dest->rate = 0;
529 return 0;
530 } else {
531 dest->rate = -ch[1].signal->rate / 80;
532 return 1;
533 }
534 }
535
536 struct func {
537 void (*func)(Signal *);
538 char *name;
539 int (*isvalid)(Signal *); /* returns TRUE if this function is valid */
540 Signal signal;
541 };
542
543 struct func funcarray[] =
544 {
545 {inv1, "Inv. 1 ", ch1active},
546 {inv2, "Inv. 2 ", ch2active},
547 {sum, "Sum 1+2", chs12active},
548 {diff, "Diff 1-2", chs12active},
549 {avg, "Avg. 1,2", chs12active},
550 /* {fft1, "FFT. 1 ", ch1FFTactive}, */
551 /* {fft2, "FFT. 2 ", ch2FFTactive}, */
552 };
553
554 /* the total number of "functions" */
555 int funccount = sizeof(funcarray) / sizeof(struct func);
556
557 /* Cycle current scope chan to next function, taking heavy advantage
558 * of C incrementing pointers by the size of the thing they point to.
559 * Start by finding the current function in the function array that
560 * the channel is pointing to, and advancing to the next one,
561 * selecting the first item in the array if either we're currently
562 * pointing to the end of the array or pointing to something other
563 * than a function. Then keep going, looking for the first function
564 * that returns TRUE to an isvalid() test, taking care that none of
565 * the functions may currently be valid.
566 */
567
next_func(void)568 void next_func(void)
569 {
570 struct func *func, *func2;
571 Channel *chan = &ch[scope.select];
572
573 for (func = &funcarray[0]; func < &funcarray[funccount]; func++) {
574 if (chan->signal == &func->signal) break;
575 }
576
577 if (func == &funcarray[funccount]) func = &funcarray[0];
578 else if (func == &funcarray[funccount-1]) func = &funcarray[0];
579 else func ++;
580
581 /* At this point, func points to the candidate function structure.
582 * See if it's valid, and keep going forward if it isn't
583 */
584
585 func2 = func;
586 do {
587 if (func->isvalid(&func->signal)) {
588 recall(&func->signal);
589 return;
590 }
591 func ++;
592 if (func == &funcarray[funccount]) func = &funcarray[0];
593 } while (func != func2);
594
595 /* If we're here, it's because we went through all the functions
596 * without finding one that returned valid. No choice but to
597 * clear the channel.
598 */
599
600 recall(NULL);
601 }
602
603 /* Basically the same deal, but moving backwards in the array. */
604
prev_func(void)605 void prev_func(void)
606 {
607 struct func *func, *func2;
608 Channel *chan = &ch[scope.select];
609
610 for (func = &funcarray[0]; func < &funcarray[funccount]; func++) {
611 if (chan->signal == &func->signal) break;
612 }
613
614 if (func == &funcarray[funccount]) func = &funcarray[funccount-1];
615 else if (func == &funcarray[0]) func = &funcarray[funccount-1];
616 else func --;
617
618 /* At this point, func points to the candidate function structure.
619 * See if it's valid and go further backwards if it isn't
620 */
621
622 func2 = func;
623 do {
624 if (func->isvalid(&func->signal)) {
625 recall(&func->signal);
626 return;
627 }
628 func --;
629 if (func < &funcarray[0]) func = &funcarray[funccount-1];
630 } while (func != func2);
631
632 /* If we're here, it's because we went through all the functions
633 * without finding one that returned valid. No choice but to
634 * clear the channel.
635 */
636
637 recall(NULL);
638 }
639
function_bynum_on_channel(int fnum,Channel * ch)640 int function_bynum_on_channel(int fnum, Channel *ch)
641 {
642 if ((fnum >= 0) && (fnum < funccount)
643 && funcarray[fnum].isvalid(&funcarray[fnum].signal)) {
644 recall_on_channel(&funcarray[fnum].signal, ch);
645 return TRUE;
646 }
647 return FALSE;
648 }
649
650 /* Initialize math, called once by main at startup, and again whenever
651 * we read a file.
652 */
653
654 void
init_math()655 init_math()
656 {
657 static int i;
658 static int once = 0;
659
660 for (i = 0 ; i < 26 ; i++) {
661 if (once==1 && mem[i].data != NULL) {
662 free(mem[i].data);
663 }
664 mem[i].data = NULL;
665 mem[i].num = mem[i].frame = mem[i].volts = 0;
666 mem[i].listeners = 0;
667 sprintf(mem[i].name, "Memory %c", 'a' + i);
668 mem[i].savestr[0] = 'a' + i;
669 mem[i].savestr[1] = '\0';
670 }
671 for (i = 0; i < funccount; i++) {
672 strcpy(funcarray[i].signal.name, funcarray[i].name);
673 funcarray[i].signal.savestr[0] = '0' + i;
674 funcarray[i].signal.savestr[1] = '\0';
675 }
676 init_fft();
677 once=1;
678 }
679
680 /* update_math_signals() is called whenever 'something' has changed in
681 * the scope settings, and we may need to recompute voltage and rate
682 * values for the generated math functions. We do this by calling all
683 * the isvalid() functions for those math functions that have
684 * listeners, and return 0 if everything's OK, or -1 if some of them
685 * are no longer valid.
686 *
687 * XXX I'm still not completely clear on just what we should
688 * do with invalid channels that have listeners; don't want to
689 * arbitrarily clear them (I don't think), because then a single
690 * inadvertent keystroke on channel 0 or 1 might clear a bunch of
691 * math.
692 */
693
694 int
update_math_signals(void)695 update_math_signals(void)
696 {
697 int i;
698 int retval = 0;
699
700 for (i = 0; i < funccount; i++) {
701 if (funcarray[i].signal.listeners > 0) {
702 if (! funcarray[i].isvalid(&funcarray[i].signal)) retval = -1;
703 }
704 }
705
706 return retval;
707 }
708
709 /* Perform any math on the software channels, called many times by main loop */
710 void
do_math()711 do_math()
712 {
713 static int i;
714
715 for (i = 0; i < funccount; i++) {
716 if (funcarray[i].signal.listeners > 0) {
717 funcarray[i].func(&funcarray[i].signal);
718 }
719 }
720
721 run_externals();
722
723 }
724
725 /* Perform any math cleanup, called once by cleanup at program exit */
726 void
cleanup_math()727 cleanup_math()
728 {
729 EndFFT();
730 }
731
732 /* measure the given channel */
733 void
measure_data(Channel * sig,struct signal_stats * stats)734 measure_data(Channel *sig, struct signal_stats *stats) {
735 static long int i, j, prev;
736 int min, max, midpoint;
737 float first = 0, last = 0, count = 0, imax = 0;
738
739 stats->min = 0;
740 stats->max = 0;
741 stats->time = 0;
742 stats->freq = 0;
743 if ((sig->signal == NULL) || (sig->signal->num == 0)) return;
744
745 prev = 1;
746 if (scope.curs) { /* manual cursor measurements */
747 if (scope.cursa < scope.cursb) {
748 first = scope.cursa;
749 last = scope.cursb;
750 } else {
751 first = scope.cursb;
752 last = scope.cursa;
753 }
754 stats->min = stats->max = sig->signal->data[(int)first];
755 if ((j = sig->signal->data[(int)last]) < stats->min)
756 stats->min = j;
757 else if (j > stats->max)
758 stats->max = j;
759 count = 2;
760 } else { /* automatic period measurements */
761 min = max = sig->signal->data[0];
762 for (i = 0 ; i < sig->signal->num ; i++) {
763 j = sig->signal->data[i];
764 if (j < min)
765 min = j;
766 if (j > max) {
767 max = j;
768 imax = i;
769 }
770 }
771
772 /* locate and count rising edges
773 * doesn't handle noise very well (noisy edges can get double counted)
774 */
775
776 midpoint = (min + max)/2;
777 for (i = 0 ; i < sig->signal->num ; i++) {
778 j = sig->signal->data[i];
779 if (j > midpoint && prev <= midpoint) {
780 if (!first)
781 first = i;
782 last = i;
783 count++;
784 }
785 prev = j;
786 }
787 stats->min = min;
788 stats->max = max;
789 }
790
791 if (sig->signal->rate < 0) {
792
793 /* Special case for FFT - signal rate will be < 0, the negative of
794 * the frequency step for each point in the transform, times 10.
795 * So multiply by the index of the maximum value to get frequency peak.
796 */
797
798 stats->freq = (- sig->signal->rate) * imax / 10;
799 if (stats->freq > 0)
800 stats->time = 1000000 / stats->freq;
801
802 } else if ((sig->signal->rate > 0) && (count > 1)) {
803
804 /* estimate frequency from rising edge count
805 * assume a wave: period = length / # periods
806 */
807
808 stats->time = 1000000 * (last - first) / (count - 1) / sig->signal->rate;
809 if (stats->time > 0)
810 stats->freq = 1000000 / stats->time;
811
812 }
813 }
814