Subject: CNG tone detector: FAX Modem Auto Answer Patch for mgetty-0.20
From: [email-masked] (Hiroaki Sengoku)
Date: 15 Apr 1995 14:06:48 +0900
Newsgroups: fj.sources

仙石＠横浜市です。

  Voice 機能がついていない FAX modem で、

	      FAX/modem 自動着信 & 留守番電話と回線を共有

  するためのパッチです。mgetty-0.20 用です。多くの unix 互換 OS で利用可
能でしょう。私は Linux 上で使っています。

用意するもの
************
		(1) FAX modem
		(2) sound card
		(3) ダイレクトテレホンピックアップ
		(4) mgetty-0.20

  (1) は FAX 機能付でなくても構いませんが、その場合は当然 FAX の自動着信
は出来ません :-)。(2) の sound card は、unix 互換 OS で support されてい
ればなんでも OK です。このパッチでは /dev/dsp を使って音声データを読み込
んでいます。また、mixer の設定を一時的に変更します。(3) に関しては下に述
べた「注意」をお読み下さい。(4) は version が 0.20 以外でも、容易にパッ
チをあてることが可能でしょう。

機能
****
  電話がかかってきて、留守番電話 (あるいは人間) が電話に応答しているとき、
FAX の CNG tone が聞こえてきたり、modem の CNG tone が聞こえてくると、
FAX modem が回線を強制的に奪い、FAX あるいは modem の自動着信を行ないま
す。

バグ
****
  CNG tone (1100Hz or 1300Hz) に似たような声を出す人から電話がかかってき
た時は誤動作します。:-) 高い声を出す友人がいる場合は注意しましょう。

mgetty とは ?
*************
  mgetty は多くの unix 互換 OS で動作する FAX 受信機能付 (uu)getty で、
Gert Doering <[email-masked]> 氏によって開発されました。

  mgetty を用いると FAX の自動受信と modem の自動着信が出来ます。さらに
ちょっとソースをいじれば ring back という方法で留守番電話と回線を共有す
ることが可能です。ring back とは、

  電話をかける -> 2, 3 度鳴らす -> 一旦切る -> 1 分以内にかけ直す

と FAX modem が応答する仕掛けです。もちろん普通に電話をかけた場合は留守
番電話 (あるいは留守でない場合は人間) が出ます。本パッチのおまけとして、
ring back 機能もつけてありますので、(2), (3) をお持ちでない方も御利用下
さい。

ring back の欠点
****************
  ring back の最大の欠点は言うまでもなく電話を 2 度かける必要がある、と
いうことです。1 回めの呼出は電話料金はかかりませんからお金は無駄にはなら
ないのですが、いかんせんめんどくさい。FAX を送ってくれる人にいちいち 
ring back の方法を教えてあげなければいけません。知人から FAX を受ける場
合は面倒臭い方法を強制する (^^;) ことも可能ですが、shop に FAX で見積り
を送らせる時に ring back を説明するのは現実的ではありません。

# ring back でわざわざ FAX を送ってくれた方、お手数おかけしました。(_O_)

FAX/modem の判別法
******************
  電話線の音声を sound card のマイク端子に入力して、電話線をモニタします。
電話が鳴った後の電話線の音声をフーリエ変換し、もし 1100Hz あるいは 
1300Hz 付近にピークがあれば FAX あるいは modem からの CNG tone とみなし
ます。

注意
****
  電話線には認定を受けた装置しか接続することは出来ません。したがって電話
線の音声を sound card のマイク端子に入力するためのアダプタ (3) を購入す
る必要があります。私が使ったのは、

	テレホンアクセサリー NT-100
	電話回線より音声を取り出す
	ダイレクトテレホンピック	SKYNIE CO.

などと書かれているものです。定価 1650 円です。購入価格は忘れました。もち
ろん同等品ならなんでも構いませんが、電気通信端末機器認定番号がついている
かどうか確認して下さい。

おまけ
******
  昔の modem は CNG tone (1300Hz) を出すことが出来ません。その場合は、トー
ンダイアルで「0」の音を出して下さい。「0」(だけじゃなくて、2, 5, 8 どれ
でも OK ですが) は 1300Hz 付近にピークがありますから、着信させることが可
能です。

おまけその 2
************
  Makefile で、-DRINGBACK の前のコメントを外せば、ring back 機能付きにな
ります。が、ring back は、たまたま 1 分以内に 2 度電話がかかってくると、
modem が応答してしまいますので、(2) と (3) を持っている方は ring back 機
能は off にしておいた方が良いでしょう。

# ring back のために、いきなり FAX modem の音を聞かされてびっくりした方、
# ごめんなさい。(_O_)

おまけその 3
************
  fax_sense.c を -DDEBUG で compile すると、フーリエ変換後のデータを標準
出力に吐きます。電話線の音声 (tone dial の音とか、dial tone とか) にどん
な周波数の音が含まれているか調べてみると面白いかもしれません。

メモ
****
  ダイレクトテレホンピックアップを使えば、電話が現在使用中か否かをチェッ
クすることも出来ます。私は電話中に uucp の polling が始まったりしないよ
うにするため、uucico を cron から実行する前に電話線が無音状態にあるか確
認するプログラムを走らせています。非常に簡単なプログラムですが、欲しい人っ
ています ?

パッチ
******
  このパッチは、http://www.yajima.kuis.kyoto-u.ac.jp/staffs/sengoku/ に
置く予定です。

diff -u mgetty.c.org mgetty.c
--- mgetty.c.org	Thu Apr 21 19:10:57 1994
+++ mgetty.c	Wed Apr 12 20:52:19 1995
@@ -26,7 +26,6 @@
 #include <sys/stat.h>
 #include <signal.h>
 #include <fcntl.h>
-
 #include "mgetty.h"
 #include "policy.h"
 #include "tio.h"
@@ -203,6 +202,10 @@
 
 	char * fax_server_file = NULL;		
 
+#ifdef RINGBACK
+	time_t ringback = 0;
+#endif
+
 #ifdef VOICE
 	int answer_mode;
 	voice_path_init();
@@ -593,9 +596,17 @@
 		    && (what_action != DIST_RING_VOICE)
 #endif
 		   ) break;
+#ifdef FAXSENSE
+		fax_sense_start();
+#endif
 		rings++;
+#ifdef RINGBACK
+		if( time(NULL) - ringback < 60 ) rings = rings_wanted;
+#endif
 	    }
-
+#ifdef FAXSENSE
+	    if( fax_sense_end() > 0 ) rings = rings_wanted;
+#endif
 	    /* timeout - the phone stopped ringing? (human picked up) */
 	    if ( rings < rings_wanted && what_action == A_TIMOUT )
 	    {
@@ -606,6 +617,9 @@
 		    exit(0);		/* let init restart mgetty */
 		}
 		lprintf( L_MESG, "phone stopped ringing" );
+#ifdef RINGBACK
+		ringback = time(NULL);
+#endif
 		goto waiting;
 	    }
 
diff -u do_chat.c.org do_chat.c
--- do_chat.c.org	Mon Apr 18 22:02:56 1994
+++ do_chat.c	Sun Apr  9 21:31:12 1995
@@ -91,7 +91,16 @@
 		    break;
 		}
 		
+#ifdef FAXSENSE
+		cnt = fax_sense_read(fd, &buffer[i], 1, chat_timeout_time);
+		if( cnt == -2 ) {
+		    errno = EINTR;
+		    retcode = FAIL;
+		    break;
+		}
+#else
 		cnt = read( fd, &buffer[i], 1 );
+#endif
 
 		if ( cnt < 0 )
 		{
diff -u fax_sense.c.org fax_sense.c
--- fax_sense.c.org	Fri Apr 14 20:49:43 1995
+++ fax_sense.c	Wed Apr 12 21:07:49 1995
@@ -0,0 +1,349 @@
+/*
+ * fax_sense.c	sense MIC to detect CNG tone
+ * Copyright(C)1995 by Hiroaki Sengoku <[email-masked]>
+ * Version 1.0	Apr 8, 1995
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Emacs; see the file COPYING.  If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <ulimit.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <math.h>
+#ifdef DEBUG
+#include <stdarg.h>
+#define L_ERROR 1
+#define L_WARN 3
+#define L_MESG 4
+#else
+#include "mgetty.h"
+#endif
+#ifndef EINTR
+#include <errno.h>
+#endif
+
+#ifndef PI
+#define		PI			3.1415926535897932384626
+#endif
+#ifndef NSTAGE
+#define		NSTAGE		10
+#endif
+
+#define		NSAMPLE		(1<<NSTAGE)
+#define		CNG_FREQ	143		/* 1100 Hz */
+#define		VCNG_FREQ	169		/* 1300 Hz */
+#define		FREQ_WIDTH	5
+
+/* FFT */
+
+typedef struct {
+	double r;	/* real */
+	double i;	/* imaginary */
+} Complex;
+
+static Complex addc(Complex a, Complex b) {
+	Complex ret;
+	ret.r = a.r + b.r;
+	ret.i = a.i + b.i;
+	return ret;
+}
+
+static Complex mulc(Complex a, Complex b) {
+	Complex ret;
+	ret.r = a.r * b.r - a.i * b.i;
+	ret.i = a.r * b.i + a.i * b.r;
+	return ret;
+}
+
+static void mkSinTbl(int n, Complex *tbl) {	/* W=-2pi/n pitch sin => tbl */
+	int i;
+	for( i=0; i < n; i++ ) {
+		tbl[i].r = cos(2*PI*i/n);
+		tbl[i].i = -sin(2*PI*i/n);
+	}
+}
+
+static void mkRevTbl(int *tbl) {	/* bit reverse table => tbl */
+	int i, j, k;
+	for( i=0; i < NSAMPLE; i++ ) {
+		tbl[i] = 0;
+		for( j=0, k=i; j < NSTAGE; j++, k >>= 1 ) {
+			tbl[i] <<= 1;
+			tbl[i] |= (k & 1);
+		}
+	}
+}
+
+static Complex sinTbl[NSAMPLE];	/* sin table */
+
+static void butterfly(Complex *y, int stage, int node) {
+	int pair;		/* distance between pair nodes */
+	int p, p2;		/* power of W */
+	Complex t[2];
+	int i, j;
+	pair = 1 << stage;
+	p = (NSAMPLE/2)/pair;
+	for( i=0; i < 2; i++ ) {
+		t[i].r = t[i].i = 0;
+		p2 = (node+pair*i)*p;
+		for( j=0; j < 2; j++ ) {
+			t[i] = addc(t[i],mulc(sinTbl[p2*j % NSAMPLE],y[node + pair*j]));
+		}
+	}
+	for( i=0; i < 2; i++ ) y[node + pair*i] = t[i];
+}
+
+static void fft(Complex *y) {
+	int stage;		/* stage of butterflies */
+	int node;		/* node number */
+	int pair;		/* distance between pair nodes */
+	int size;		/* size of butterfly */
+	int i;
+	for( stage=0; stage < NSTAGE; stage++ ) {
+		pair = 1 << stage;
+		size = pair * 2;
+		for( node=0; node < NSAMPLE; node += size )
+			for( i=0; i < pair; i++ ) butterfly(y,stage,node+i);
+	}
+	for( i=0; i < NSAMPLE; i++ ) {
+		y[i].r /= NSAMPLE;
+		y[i].i /= NSAMPLE;
+	}
+}
+
+/* sense FAX */
+
+static int revTbl[NSAMPLE];		/* bit reverse table */
+
+#ifdef DEBUG
+static void lprintf(int level, const char *fmt, ...) {
+	char buf[256];
+	va_list args;
+	va_start(args, fmt);
+	vsprintf(buf,fmt,args);
+	va_end(args);
+	fprintf(stderr,"[%d] %s\n",level,buf);
+}
+#endif
+
+static int set_mixer(int baz, int foo, int bar) {
+	if( ioctl(baz,MIXER_WRITE(foo),&bar) == -1 ) {
+		lprintf(L_ERROR,"fail to set mixer");
+		return -1;
+	}
+	return 0;
+}
+
+static int get_mixer(int baz, int foo) {
+	int bar;
+	if( ioctl(baz,MIXER_READ(foo),&bar) == -1 ) {
+		lprintf(L_ERROR,"fail to get mixer setting");
+		return -1;
+	}
+	return bar & ((0x7f <<8) | 0x7f);
+}
+
+static float peak(float *a, int width, int *pos) {
+	float max, min;
+	int i, peak = 0;
+	max = 0;
+	for( i=-width; i < width; i++ ) if( a[i] > max ) max = a[peak=i];
+	max = 0;
+	for( i=peak-width/2; i < peak+width/2; i++ ) max += a[i];
+	min = 0;
+	for( i=peak-width; i < peak-width/2; i++ ) min += a[i];
+	for( i=peak+width/2; i < peak+width; i++ ) min += a[i];
+	*pos = peak;
+	if( max < 10 ) return 0;	/* ignore noise */
+	return max/min;
+}
+
+static int sense(unsigned char *x) {
+	Complex y[NSAMPLE];
+	float a[NSAMPLE/2];
+	float p_fax, p_modem;
+	int pos_fax, pos_modem;
+	char *msg;
+	int j, ret;
+	ret = 0;
+	for( j=0; j < NSAMPLE/2; j++ ) a[j] = 0;
+	for( j=0; j < NSAMPLE; j++ ) {
+		y[j].r = (float)(x[revTbl[j]]-128);
+		y[j].i = 0;
+	}
+	fft(y);
+#ifdef DEBUG
+	for( j=0; j < NSAMPLE/2; j++ )
+		a[j] += y[j].r * y[j].r + y[j].i * y[j].i;
+#else
+	for( j=CNG_FREQ-FREQ_WIDTH*2; j < CNG_FREQ+FREQ_WIDTH*2; j++ )
+		a[j] += y[j].r * y[j].r + y[j].i * y[j].i;
+	for( j=VCNG_FREQ-FREQ_WIDTH*2; j < VCNG_FREQ+FREQ_WIDTH*2; j++ )
+		a[j] += y[j].r * y[j].r + y[j].i * y[j].i;
+#endif
+	p_fax = peak(&a[CNG_FREQ],FREQ_WIDTH,&pos_fax);
+	p_modem = peak(&a[VCNG_FREQ],FREQ_WIDTH,&pos_modem);
+	if( p_fax > 10 ) {
+		ret = 1;
+		msg = " -> FAX !";
+	} else if( p_modem > 10 ) {
+		ret = 2;
+		msg = " -> MODEM !";
+	} else msg = "";
+	lprintf(ret ? L_WARN : L_MESG,"sense CNG: %.0f(%d), V.25: %.0f(%d)%s",
+			p_fax,pos_fax,p_modem,pos_modem,msg);
+#ifdef DEBUG
+	for( j=0; j < NSAMPLE/2; j++ ) printf("%9.6f\n",a[j]);
+#endif
+	return ret;
+}
+
+static int save_vol, save_mic, save_rec, save_recsrc;
+static int initialized = 0;
+static Complex X[NSAMPLE];
+static int fax_fd;
+static int fax_sense_ret = 0;
+
+int fax_sense_start() {	/* save volumes ... */
+	int recsrc, baz;
+	char *name;
+	if( initialized ) {		/* synchronize */
+		if( ioctl(fax_fd,SNDCTL_DSP_SYNC) == -1 ) {	/* now start sampling */
+			lprintf(L_ERROR,"fail to synchronize dsp");
+			return -1;
+		}
+		return 0;
+	}
+	name = "/dev/dsp";
+	if( (fax_fd=open(name,O_RDONLY)) < 0 ) {
+		lprintf(L_ERROR,"Can't open %s",name);
+		return -1;
+	}
+	name = "/dev/mixer";
+	if( (baz=open(name,O_RDWR)) < 0 ) {
+		lprintf(L_ERROR,"Can't open %s",name);
+		return -1;
+	}
+	save_vol = get_mixer(baz,SOUND_MIXER_VOLUME);
+	save_rec = get_mixer(baz,SOUND_MIXER_RECLEV);
+	save_mic = get_mixer(baz,SOUND_MIXER_MIC);
+	if( save_rec == -1
+	   || save_vol == -1
+	   || set_mixer(baz,SOUND_MIXER_VOLUME,  0) == -1			/* mute */
+	   || set_mixer(baz,SOUND_MIXER_RECLEV,(100<<8)|100) == -1	/* full */
+	   || set_mixer(baz,SOUND_MIXER_MIC,   (100<<8)|100) == -1
+	   ) return -1;
+	if( ioctl(baz, SOUND_MIXER_READ_RECSRC, &save_recsrc) == -1 ) {
+		lprintf(L_ERROR,"fail to read recsrc");
+		return -1;
+	}
+	recsrc = SOUND_MASK_MIC;	/* recording MIC only */
+	if( ioctl(baz, SOUND_MIXER_WRITE_RECSRC, &recsrc) == -1 ) {
+		lprintf(L_ERROR,"fail to write recsrc");
+		return -1;
+	}
+	close(baz);
+	usleep(200000);		/* wait 0.2 sec */
+	mkSinTbl(NSAMPLE,sinTbl);
+	mkRevTbl(revTbl);
+	fax_sense_ret = 0;
+	initialized = 1;	/* successfuly initialized */
+	return fax_fd;
+}
+
+int fax_sense() {
+	unsigned char buf[NSAMPLE];
+	if( !initialized ) return 0;
+	if( read(fax_fd,buf,NSAMPLE) != NSAMPLE ) {
+		lprintf(L_ERROR,"read error: audio device");
+		return 0;
+	}
+	return sense(buf);
+}
+
+int fax_sense_end() {	/* restore volumes ... */
+	int baz;
+	char *name;
+	if( !initialized ) return 0;
+	name = "/dev/mixer";
+	if( (baz=open(name, O_RDWR)) < 0 ) {
+		lprintf(L_ERROR,"Can't open %s",name);
+		return -1;
+	}
+	if( ioctl(baz, SOUND_MIXER_WRITE_RECSRC, &save_recsrc) == -1 ) {
+		lprintf(L_ERROR,"fail to write recsrc");
+		return -1;
+	}
+	if(   set_mixer(baz,SOUND_MIXER_MIC,   save_mic) == -1
+	   || set_mixer(baz,SOUND_MIXER_RECLEV,save_rec) == -1 ) return -1;
+	usleep(200000);	/* wait 0.2 sec */
+	if(   set_mixer(baz,SOUND_MIXER_VOLUME,save_vol) == -1 ) return -1;
+	close(baz);
+	close(fax_fd);
+	initialized = 0;
+	return fax_sense_ret;
+}
+
+int fax_sense_read(int fd, char *buf, size_t size, time_t timeout) {
+    fd_set fds, rfds;
+	int width;
+	int ret;
+	time_t t;
+	struct timeval timezero;
+	if( !initialized ) return read(fd,buf,size);
+    FD_ZERO(&fds);
+	FD_SET(fd,&fds);
+	width = ulimit(4,0);
+	timezero.tv_sec = timezero.tv_usec = 0;
+	for( t=time(NULL); time(NULL)-t < timeout; ) {
+		rfds = fds;
+		if( select(width,&rfds,NULL,NULL,&timezero) < 0 ) break;
+		if( FD_ISSET(fd,&rfds) ) return read(fd,buf,size);
+		ret = fax_sense();
+		if( ret > 0 ) {
+			fax_sense_ret = ret;
+			return -2;
+		}
+	}
+	errno = EINTR;
+	return -1;
+}
+
+#ifdef DEBUG
+int main(int argc, char *argv[]) {
+	int ret;
+	char c;
+	do {
+		c = 0;
+		fax_sense_start();
+		fax_sense_read(0,&c,1,8);
+	} while( c == 0x0a );
+	ret = fax_sense_end();
+	if( ret == 1 ) fprintf(stderr,"FAX detected !\n");
+	else if( ret == 2 ) fprintf(stderr,"MODEM detected !\n");
+}
+#endif
+
+/*
+ * For Gnu Emacs.
+ * Local Variables:
+ * tab-width: 4
+ * End:
+ */
diff -u Makefile.org Makefile
--- Makefile.org	Mon Apr 25 08:22:44 1994
+++ Makefile	Sun Apr  9 20:10:29 1995
@@ -84,7 +84,7 @@
 #	    USTAT	  - ustat(), no statfs etc.
 #
 #CFLAGS=-Wall -g -O2 -pipe -DSECUREWARE -DUSE_POLL
-CFLAGS=-g -O2 -Wall -pipe
+CFLAGS=-g -O2 -Wall -pipe -DFAXSENSE #-DRINGBACK 
 #CFLAGS=-g -O -DSVR4
 #CFLAGS=-g -O -DSVR4 -DSVR42
 #CFLAGS=-g -O -DUSE_POLL
@@ -106,7 +106,7 @@
 #
 # For Sequent Dynix/ptx, you have to add "-lsocket"
 #
-LDFLAGS=
+LDFLAGS=-lm
 #LDFLAGS=-lprot -lsocket
 #LDFLAGS=-s -shlib
 #LDFLAGS=-lsocket
@@ -194,7 +194,8 @@
 #
 OBJS=mgetty.o logfile.o do_chat.o locks.o utmp.o logname.o login.o \
      faxrec.o faxsend.o faxlib.o faxhng.o \
-     io.o gettydefs.o tio.o config.o cnd.o getdisk.o
+     io.o gettydefs.o tio.o config.o cnd.o getdisk.o \
+     fax_sense.o
 
 SFAXOBJ=sendfax.o logfile.o locks.o faxlib.o faxsend.o faxrec.o \
      io.o tio.o faxhng.o cnd.o getdisk.o
--
					        仙石 浩明 @ 横浜市青葉区
#303.					      [email-masked]
<A HREF="http://www.yajima.kuis.kyoto-u.ac.jp/staffs/sengoku/">info.</A>