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