#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/soundcard.h>
#include <math.h>
#include <termios.h>

/* Temporizaciones */

#define BASET 50 /* ms */
#define PUNTO 1
#define RAYA  3
#define INTERS 1
#define INTERL 3
#define INTERW 10

/* Muestreo */

#define CARRIER 1000  /* Hz */
#define FS 11025      /* Hz */

#define FREQ ((CARRIER*65536)/FS)

char *codigo[]={
	"A._",
	"B_...",
	"C_._.",
	"D_..",
	"E.",
	"F.._.",
	"G__.",
	"H....",
	"I..",
	"J.___",
	"K_._",
	"L._..",
	"M__",
	"N_.",
	"O___",
	"P.__.",
	"Q__._",
	"R._.",
	"S...",
	"T_",
	"U.._",
	"V..._",
	"W.__",
	"X_.._",
	"Y_.__",
	"Z__..",
	"0_____",
	"1.____",
	"2..___",
	"3...__",
	"4...._",
	"5.....",
	"6_....",
	"7__...",
	"8___..",
	"9____.",
	".._._._",
	",__..__",
        "?..__..",
	"~"
	};

int soundcard_fd;
float sine[256];

extern inline float biq_bp(float x,float *pcoef,float *buf)
{
        float y;
        y =(*pcoef++)*(x-buf[1]);
        y+=(*pcoef++)*buf[2];
        y+=(*pcoef)*buf[3];

        buf[1]=buf[0]; buf[0]=x;
        buf[3]=buf[2]; buf[2]=y;

        return y;
}

void gen_coef(int tipo, float f0, float Q, float *pcoef)
{
        float w0,a,d;

        w0=2.*M_PI*f0;

        //Prewharping
        w0=2.0*FS*tan(w0/FS/2.0);

        a=FS/w0;
        d=4.*a*a +2.*a/Q +1.;

        switch(tipo){
        case 0: (*pcoef++)=1.0/d;       break;  // LP
        case 1: (*pcoef++)=2.*a/Q/d;    break;  // BP
        case 2: (*pcoef++)=4.*a*a/d;    break;  // HP
        }

        (*pcoef++)=(8.*a*a -2.)/d;
        (*pcoef++)=-(4.*a*a -2.*a/Q +1.)/d;
}

float bp_c[3],bp_b[4];

void sound(int amp,int len)
{
        static unsigned short phase;
        int ns,i;
        short buf[FS*20*BASET/1000];

        ns=len*BASET*FS/1000;
        for (i=0;i<ns;i++) {
                buf[i]=16000*biq_bp((amp)?sine[phase>>8]:0,bp_c,bp_b);
                phase+=FREQ;
        }
        write(soundcard_fd,buf,ns<<1);
}

void morsechar(char a)
{
        int i;
        char *p;

        if (a>='a' && a<='z') a-=32;

        switch (a){
        case ' ':
        case '\n':      sound(0,INTERW);
        		printf("\n");
        		break;
        default:
        		for(i=0;;i++) {
			if (*codigo[i]==a) break;
				if (*codigo[i]=='~') {
					printf("~\n",a);
					break;
				}
			}
                        if (*codigo[i]=='~') break;
			printf("%c\t",a);
                        for (p=codigo[i]+1;*p;p++) {
                                printf((*p=='.')?"_ ":"___ ");
                        }
                        printf("\n"); fflush(stdout);
			for (p=codigo[i]+1;*p;p++) {
				sound(1,(*p=='.')?PUNTO:RAYA);
                                sound(0,INTERS);
			}
                        sound(0,INTERL);
                        //ioctl(soundcard_fd, SNDCTL_DSP_SYNC, 0);
        }
}

// Set canonical mode for stdin (line editing)
void setcanon()
{
        struct termios term;
        tcgetattr(0,&term);
        term.c_lflag|=ICANON;
        term.c_lflag|=ECHO;
        if (tcsetattr(0,TCSANOW,&term)) {
                fprintf(stderr,"tcsetattr failed\n");
                exit(0);
        }
}

// Set NOcanonical mode for stdin (single character input)
// gcc-2.95.3(i386) generates buggy code for this function
// (uncontrolled stack pointer)

void setnocanon()
{
        struct termios term;
        tcgetattr(0,&term);
        term.c_lflag&=~ICANON;
        term.c_lflag&=~ECHO;
        // Solaris is a bit stupid fetching 4 chars at a time by default.
        // It tries to be "more efficient". But I think noncanonical mode
        // is used for reading 1 char at a time 99.9(period) % of times
        term.c_cc[VMIN]=1;      // Force 1 char at a time (too smart Sunos)
        term.c_cc[VTIME]=0;
        if (tcsetattr(0,TCSANOW,&term)) {
                fprintf(stderr,"tcsetattr failed\n");
                exit(0);
        }
}

main(int argc, char **argv)
{

	int i,j,tm=0,file_fd;
	char a,*p;

	if (argc<2) {
		printf("Uso: morse [-t] [file]\n");
                printf("-t: test mode\n");
		exit(0);
	}
        p=argv[1]; if (p[0]=='-' && p[1]=='t') tm=1;


        for (i=0;i<256;i++) sine[i]=sin(i*M_PI/128);
        gen_coef(1, CARRIER, CARRIER*BASET/4000., bp_c);

        if ((soundcard_fd=open("/dev/dsp", O_WRONLY, 0)) == EOF) {
		fprintf(stderr, "Cannot open sound device");
                exit(0);
        }
        i=0x00020008;  //256 bytes/128 samples
        if (ioctl(soundcard_fd, SNDCTL_DSP_SETFRAGMENT, &i)) {
                fprintf(stderr, "Cannot set fragment size\n");
        }
        i=16;
        if (ioctl(soundcard_fd, SOUND_PCM_WRITE_BITS, &i) == EOF) {
                fprintf(stderr, "Cannot set %d bits/sample\n",i);
        }
        i=1;
        if (ioctl(soundcard_fd, SOUND_PCM_WRITE_CHANNELS, &i) == EOF) {
                fprintf(stderr, "Cannot set %ld channel(s)\n",i);
        }
        i=FS;
        if (ioctl(soundcard_fd, SOUND_PCM_WRITE_RATE, &i) == EOF) {
                fprintf(stderr, "Cannot set rate %d Hz\n",i);
        }

        if (!tm) {
	        if (*argv[1]=='-') {
                        file_fd=0;
                        i=fcntl(file_fd,F_GETFL);
                        fcntl(file_fd,F_SETFL,i|O_NONBLOCK);
                        setnocanon();
                }else
	        if((file_fd=open(argv[1],O_RDONLY))==-1) exit(0);
                for (;;) {
                        i=read(file_fd,&a,1);
                        if (i==0) break;
                        morsechar((i<0)?' ':a);
                        //printf("read=%d\n",i);
                }
                close(file_fd);
        } else {
                for (i=0;;) {
                        p=codigo[i];
                        if (*p=='~') { i=0; continue;}
                        morsechar(*p);
                        i++;
                }
        }

	close(soundcard_fd);
}
