1 /*
2
3 $Id$
4 Copyright (C) Alois Schloegl, IST Austria <alois.schloegl@ist.ac.at>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 3
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22
23 #include <alsa/asoundlib.h>
24
25 #include <ctype.h>
26 #include <fcntl.h>
27 #include <math.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30
31
32 #ifdef WITH_BIOSIG
33 #include "../biosig2.h"
34 #ifdef NDEBUG
35 #define VERBOSE_LEVEL 0 // turn off debugging information
36 #else
37 extern int VERBOSE_LEVEL; // used for debugging
38 #endif
39 #endif
40 int VERBOSE=3;
41
42
43 #define TO_STOP_PRESS_ANY_KEY
44
45
46 #ifdef TO_STOP_PRESS_ANY_KEY
47 /* copied from http://ubuntuforums.org/showthread.php?t=936816 on 2011-Oct-28 */
48 #include <termios.h>
49 #include <sys/select.h>
50 #include <string.h>
51 #include <unistd.h>
is_key_pressed(void)52 int is_key_pressed(void)
53 {
54 struct timeval tv;
55 fd_set fds;
56 tv.tv_sec = 0;
57 tv.tv_usec = 0;
58
59 FD_ZERO(&fds);
60 FD_SET(STDIN_FILENO, &fds);
61
62 select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
63 return FD_ISSET(STDIN_FILENO, &fds);
64 }
65 #endif
66
67
main(int argc,const char * argv[])68 int main(int argc, const char* argv[]) {
69
70
71 #if defined(WITH_BIOSIG) && !defined(NDEBUG)
72 // set verbose level of libbiosig
73 VERBOSE_LEVEL = VERBOSE;
74 #endif
75
76 /*********************************************
77 read arguments
78 **********************************************/
79 const char *cmd = NULL;
80 const char *cmdf = NULL;
81 const char *cmdr = NULL;
82 int k;
83 int rc;
84 int size;
85
86 const char *pcm_name=NULL;
87 snd_pcm_t *pcm_handle;
88 snd_pcm_hw_params_t *hwparams;
89 int dir;
90 snd_pcm_uframes_t frames;
91 int16_t *buffer;
92
93 unsigned int Fs = 44100;
94 unsigned chan = 0; // default channel
95 unsigned winlen = 0;
96
97 const float WINLEN = 0.001; // window lengths for computing steepness: default is 1 ms
98 float TH = NAN;
99
100 #ifdef TO_STOP_PRESS_ANY_KEY
101 /* initialization for IS_KEY_PRESSED() */
102 struct termios old_terminal_settings, new_terminal_settings;
103
104 // Get the current terminal settings
105 if (tcgetattr(0, &old_terminal_settings) < 0) perror("tcgetattr()");
106
107 memcpy(&new_terminal_settings, &old_terminal_settings, sizeof(struct termios));
108
109 // disable canonical mode processing in the line discipline driver
110 new_terminal_settings.c_lflag &= ~ICANON;
111
112 // apply our new settings
113 if (tcsetattr(0, TCSANOW, &new_terminal_settings) < 0) perror("tcsetattr ICANON");
114 #endif
115
116
117 #ifdef WITH_BIOSIG
118 const char *outFile = NULL;
119 HDRTYPE *hdr = NULL;
120 #endif
121
122 const char help[]=
123 "TTL2TRIG reads a signal from an audio channel, in order to trigger the execution of a shell command\n"
124 " Whenever the difference within a distance of 1ms exceeds the threshold, the command string is executed.\n"
125 " Copyright (C) 2011 Alois Schloegl, IST Austria, <alois.schloegl@ist.ac.at>.\n"
126 " This program is licensed under the GNU GPL v3 or later.\n\n"
127 "Usage: ttl2trig -c \"command string\" -i hw --chan=1 --Threshold=+0.5 [-o outfile]\n"
128 " -c \"command string executed on raising edge when TH>0 or on falling edge when TH<0\"\n"
129 " -r \"command string executed on raising edge when slope becomes larger than abs(TH)\"\n"
130 " -f \"command string executed on falling edge when slope becomes smaller than -abs(TH)\"\n"
131 " --chan=# # represents than channel number used for triggering, default 1\n"
132 " -th TH\n"
133 " --threshold=TH\n"
134 " when y( t ) - y(t - 1 ms) becomes larger than +abs(TH), a raising edge is detected,\n"
135 " when y( t ) - y(t - 1 ms) becomes smaller than -abs(TH), a falling edge is detected,\n"
136 #ifdef WITH_BIOSIG
137 " -o outfile, logs the recorded signal data; this can be useful for debugging and for identifying the proper threshold and window length.\n"
138 #endif
139 " -i <hwparams> or \n"
140 " --hwparams=<hwparams> where <hwparams> is the input channel like hw:2,0 \n"
141 " The later arguments have higher precedence than the earlier ones.\n"
142 " Specifically -c overrides -r and -f; and -r and -f override -c. \n\n"
143 " Example:\n\t./bin/ttl2trig -c \"date\" -i hw:2,0 --chan=0 --Threshold=.25 \n\n"
144 ;
145 if (argc<2) {
146 fprintf(stdout,"%s",help);
147 exit(0);
148 }
149
150 k = 0;
151 while (k<argc) {
152
153 if (VERBOSE>3) fprintf(stdout,"%i/%i\t%s\n",k,argc,argv[k]);
154
155 if (0) {
156 }
157 else if (isdigit(argv[k][0]) && ( strstr(argv[k],"hz") || strstr(argv[k],"Hz") ) ) {
158 char *tmp;
159 Fs = strtod(argv[k],&tmp);
160 }
161 else if (!strcmp(argv[k],"-c")) {
162 cmd = argv[++k];
163 cmdf = NULL;
164 cmdr = NULL;
165 }
166 else if (!strcmp(argv[k],"-r")) {
167 cmdr = argv[++k];
168 cmd = NULL;
169 }
170 else if (!strcmp(argv[k],"-f")) {
171 cmdf = argv[++k];
172 cmd = NULL;
173 }
174 else if (!strncmp(argv[k],"--chan=",7)) {
175 chan = atoi(argv[k]+7);
176 if (chan>0) chan--; // change from one-based to zero-based indexing
177 }
178 else if (!strncmp(argv[k],"--threshold=",12) || !strncmp(argv[k],"--Threshold=",12) ) {
179 TH = atof(argv[k]+12);
180 }
181 else if ( !strcmp(argv[k],"-th") ) {
182 TH = atof(argv[++k]);
183 }
184 else if (!strncmp(argv[k],"-v",2)) {
185 VERBOSE = atoi(argv[k]+2);
186 #if defined(WITH_BIOSIG) && !defined(NDEBUG)
187 // set verbose level of libbiosig
188 VERBOSE_LEVEL = VERBOSE;
189 #endif
190 }
191 else if (!strcmp(argv[k],"-h") || !strcmp(argv[k],"--help")) {
192 fprintf(stdout,"%s",help);
193 exit(0);
194 }
195 #ifdef WITH_BIOSIG
196 else if (!strcmp(argv[k],"-o")) {
197 k++;
198 outFile = argv[k];
199 }
200 #endif
201 else if (!strcmp(argv[k],"--hwparams=")) {
202 pcm_name = argv[k]+11;
203 }
204 else if (!strcmp(argv[k],"-i")) {
205 k++;
206 pcm_name = argv[k];
207 }
208 k++;
209 }
210
211 /* Sanity checks of input arguments */
212 if ( (TH==0) || (TH != TH) ) {
213 fprintf(stderr,"Threshold %g undefined or invalid\n",TH);
214 // exit(-1);
215 }
216
217 if (VERBOSE>6) fprintf(stdout,"pcm_name:\t%s \n", pcm_name);
218
219 if (VERBOSE>7) {
220 int val;
221
222 printf("ALSA library version: %s\n", SND_LIB_VERSION_STR);
223
224 printf("\nPCM stream types:\n");
225 for (val = 0; val <= SND_PCM_STREAM_LAST; val++) printf(" %s\n", snd_pcm_stream_name((snd_pcm_stream_t)val));
226
227 printf("\nPCM access types:\n");
228 for (val = 0; val <= SND_PCM_ACCESS_LAST; val++) printf(" %s\n", snd_pcm_access_name((snd_pcm_access_t)val));
229
230 printf("\nPCM formats:\n");
231 for (val = 0; val <= SND_PCM_FORMAT_LAST; val++)
232 if (snd_pcm_format_name((snd_pcm_format_t)val)!= NULL)
233 printf(" %s (%s)\n", snd_pcm_format_name((snd_pcm_format_t)val), snd_pcm_format_description((snd_pcm_format_t)val));
234
235 printf("\nPCM subformats:\n");
236 for (val = 0; val <= SND_PCM_SUBFORMAT_LAST; val++)
237 printf(" %s (%s)\n", snd_pcm_subformat_name((snd_pcm_subformat_t)val), snd_pcm_subformat_description((snd_pcm_subformat_t)val));
238
239 printf("\nPCM states:\n");
240 for (val = 0; val <= SND_PCM_STATE_LAST; val++) printf(" %s\n", snd_pcm_state_name((snd_pcm_state_t)val));
241 }
242
243
244
245 /*********************************************
246 initialization
247 **********************************************/
248
249
250 /* Open PCM device for recording (capture). */
251 rc = snd_pcm_open(&pcm_handle, pcm_name, SND_PCM_STREAM_CAPTURE, 0);
252 if (rc < 0) {
253 fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
254 exit(1);
255 }
256
257 /* Allocate a hardware parameters object. */
258 snd_pcm_hw_params_alloca(&hwparams);
259
260 /* Fill it in with default values. */
261 snd_pcm_hw_params_any(pcm_handle, hwparams);
262
263 /* Set the desired hardware parameters. */
264
265 /* Interleaved mode */
266 snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
267
268 /* Signed 16-bit little-endian format */
269 snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE);
270 // snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_FLOAT_LE );
271 #ifdef WITH_BIOSIG
272 uint16_t gdftyp = 3;
273 double DigMax = ldexp(1,15)-1;
274 double PhysMax = 1;
275 TH *= DigMax/PhysMax;
276 #endif
277
278 /* Two channels (stereo) */
279 unsigned int minChan, maxChan;
280 snd_pcm_hw_params_get_channels_min(hwparams, &minChan);
281 snd_pcm_hw_params_get_channels_max(hwparams, &maxChan);
282 if (VERBOSE>7) printf("chans = [%i %i]\n", minChan, maxChan);
283
284 if ( chan >= minChan) {
285 fprintf(stderr,"ERROR: Channel %i not available.\n", chan);
286 snd_pcm_close(pcm_handle);
287 exit(-1);
288 }
289
290
291 snd_pcm_hw_params_set_channels(pcm_handle, hwparams, minChan);
292
293 /* 44100 bits/second sampling rate (CD quality) */
294 snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &Fs, &dir);
295
296 /* Set period size to 32 frames. */
297 frames = 1;//Fs*0.01; // 10 ms
298 winlen = Fs*WINLEN; // window length
299 // frames = 2*winlen;
300 snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &frames, &dir);
301
302 /* Write the parameters to the driver */
303 rc = snd_pcm_hw_params(pcm_handle, hwparams);
304 if (rc < 0) {
305 fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc));
306 exit(1);
307 }
308
309 /* Use a buffer large enough to hold one period */
310 snd_pcm_hw_params_get_period_size(hwparams, &frames, &dir);
311 size = frames * 2; /* 2 bytes/sample, 2 channels */
312 size = 2 * winlen * minChan * 2; /* 2 bytes/sample, 2 channels */
313 buffer = (int16_t *) malloc(size*2);
314 for (k=0; k < size; k++) buffer[k] = 0x8000;
315
316 #ifdef WITH_BIOSIG
317 if (outFile) {
318 hdr = constructHDR(minChan,0);
319 hdr->SampleRate = Fs;
320 hdr->SPR = 1;
321 hdr->NRec = -1;
322 hdr->EVENT.N = 0;
323 for (k = 0; k < hdr->NS; k++) {
324 CHANNEL_TYPE *hc = hdr->CHANNEL + k;
325 hc->LeadIdCode = 0;
326 strcpy(hc->Label,"chan ");
327 hc->Label[6]= k + '0';
328 hc->GDFTYP = gdftyp;
329 hc->SPR = hdr->SPR;
330 hc->PhysMax = PhysMax;
331 hc->PhysMin =-PhysMax;
332 hc->DigMax = DigMax;
333 hc->DigMin =-DigMax;
334 }
335 hdr->FLAG.UCAL = 0;
336 hdr->TYPE = GDF;
337 hdr->VERSION = 3.0;
338 hdr->FileName = strdup(outFile);
339 sopen(outFile, "w", hdr);
340 if (VERBOSE>6) hdr2ascii(hdr, stdout, 3);
341 if (hdr->FILE.OPEN < 2) {
342 destructHDR(hdr);
343 hdr = NULL;
344 }
345 }
346 #endif
347
348 #ifdef TO_STOP_PRESS_ANY_KEY
349 fprintf(stdout, "\n ====== PRESS ANY KEY TO STOP ======\n");
350 #else
351 fprintf(stdout, "\n ====== PRESS <ENTER> TO STOP ======\n");
352 // set stdin to non-blocking
353 int flags = fcntl(0, F_GETFL, 0); /* get current file status flags */
354 flags |= O_NONBLOCK; /* turn off blocking flag */
355 fcntl(0, F_SETFL, flags); /* set up non-blocking read */
356 #endif
357
358 size_t count = 0, pos = 0;
359 float delta, lastdelta = NAN;
360 while (1) {
361
362 /* stop when key pressed */
363 #ifdef TO_STOP_PRESS_ANY_KEY
364 if (is_key_pressed()) break;
365 #else
366 char tmpbuf[4];
367 if (fgets(tmpbuf, sizeof(tmpbuf), stdin) != NULL) break;
368 #endif
369
370 /* read data from alsa */
371 int16_t *buf = buffer + pos % size;
372 rc = snd_pcm_readi(pcm_handle, buf, 1);
373 pos += rc * minChan;
374
375 if (rc == -EPIPE) {
376 /* EPIPE means overrun */
377 fprintf(stderr, "overrun occurred\n");
378 snd_pcm_prepare(pcm_handle);
379 }
380 else if (rc < 0) {
381 fprintf(stderr, "error from read: %s\n", snd_strerror(rc));
382 }
383
384 #ifdef WITH_BIOSIG
385 if (hdr) {
386 /* write data to GDF file */
387 count += fwrite(buf, 2*hdr->NS, rc, hdr->FILE.FID);
388 }
389 #else
390 count = rc;
391 #endif
392
393
394 /*********************************************
395 trigger command
396 **********************************************/
397
398 /* compute dY and compare with Threshold */
399 delta = buf[chan] - buffer[ (pos - winlen + chan) % size ];
400 if ( ( (TH > 0.0) && (delta > TH) && (lastdelta < TH) )
401 || ( (TH < 0.0) && (delta < TH) && (lastdelta > TH) ) ) {
402 if (cmd) system(cmd);
403 }
404 if ( (cmdr != NULL) && (delta > TH) && (lastdelta < TH)) {
405 system(cmdr);
406 }
407 if ( (cmdf != NULL) && (delta < -TH) && (lastdelta > -TH) ) {
408 system(cmdf);
409 }
410 lastdelta = delta;
411 }
412
413 fprintf(stdout, "\n ====== STOPPED ====== \n");
414
415 snd_pcm_drain(pcm_handle);
416 snd_pcm_close(pcm_handle);
417 free(buffer);
418
419 /*********************************************
420 clean up
421 **********************************************/
422
423 #ifdef WITH_BIOSIG
424 if (hdr) {
425 hdr->NRec = count;
426 sclose(hdr);
427 destructHDR(hdr);
428 }
429 #endif
430
431 }
432
433