/*
 * grillo_parlante.c: Text-To-Speech synthesizer that read text aloud.
 *
 *
 * Copyright (c) 2003, eazy <eazy@ondaquadra.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * *  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
 * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <linux/soundcard.h>

#define DSP "/dev/dsp"
#define DB ".dizionario"
#define MIN 120
#define MAX 140
#define BITS 8
#define CHANNELS 1
#define RATE 8000
#define SECONDS 3
#define SAMPLE_LEN BITS / 8 * CHANNELS * RATE * SECONDS
#define MASK 0xfc
#define MAXLEN 255
#define NSAMPLE 500
#define LEN 8
#define SCREEN_LEN 80

int             signo = 0, min = 255, max = 0;

typedef struct {
	int             begin, end;
	char            sillaba[10];
	unsigned char   data[SAMPLE_LEN];
}               Audio;

void
err_quit(void)
{
	perror("error");
	exit(-1);
}

void
sigint(int sig)
{
	signo = 1;
}

int
consonante(char lettera)
{
	int             i;
	char            vocale[5] = {'a', 'e', 'i', 'o', 'u'};

	for (i = 0; i < 5; i++)
		if (lettera == vocale[i])
			return 0;
	return 1;
}

int
vocale(char lettera)
{
	int             i;
	char            vocale[5] = {'a', 'e', 'i', 'o', 'u'};

	for (i = 0; i < 5; i++)
		if (lettera == vocale[i])
			return 1;
	return 0;
}

int
doppia(char *lettera)
{
	if (*lettera == 0)
		return 0;

	if (*lettera == *(lettera + 1))
		return 1;
	return 0;
}

int
nmlr(char *lettera)
{
	int             i;
	char            nmlr[4] = {'n', 'm', 'l', 'r'};

	for (i = 0; i < 4; i++)
		if (*lettera == nmlr[i])
			if (consonante(*(lettera + 1)))
				return 1;
	return 0;
}

/*
  Restituisce la sillaba che si trova nella posizione indicata da pos
 */
int
dividi_sillabe(char *parola, char **sillaba, int pos)
{
	int             i;
	char           *ptr, *begin, *end;

	for (i = 1, ptr = parola; ptr < parola + strlen(parola); i++) {
		begin = ptr;
		while (consonante(*ptr))
			ptr++;
		while (vocale(*ptr))
			ptr++;
		if (doppia(ptr)) {
			ptr++;
			goto fine;
		}
		if (nmlr(ptr))
			ptr++;

fine:		end = ptr;
		if (i == pos) {
			*sillaba = calloc(end - begin + 1, sizeof(char));
			memcpy(*sillaba, begin, end - begin);
			return 1;
		}
	}
	return 0;
}

int
trova_sillaba(int fd, char *sillaba)
{
	Audio           sample;

	lseek(fd, 0, SEEK_SET);
	memset(&sample, 0, sizeof(sample));
	while (read(fd, &sample, sizeof(sample)) > 0)
		if (strcmp(sample.sillaba, sillaba) == 0)
			return 1;
	return -1;
}

void
memorizza_sillaba(int dev, char *sillaba, int fd)
{
	Audio           sample;
	int             rumore, silenzio, percent_rumore, percent_silenzio;
	unsigned char  *ptr, *ptr2;

	do {
		printf("Pronuncia la sillaba %s. Premere INVIO per iniziare.\n", sillaba);
		fgetc(stdin);

		memset(&sample, 0, sizeof(sample));
		strncpy(sample.sillaba, sillaba, 9);
		if (read(dev, sample.data, SAMPLE_LEN) == -1)
			err_quit();

		for (ptr = sample.data; ptr < sample.data + SAMPLE_LEN; ptr += NSAMPLE) {
			rumore = 0;

			/*
			  Analizza un blocco di campioni per vedere la percentuale di
			  rumore in esso contenuta
			 */
			for (ptr2 = ptr; ptr2 < ptr + NSAMPLE; ptr2++)
				if (*ptr2 < min || *ptr2 > max)
					rumore++;

			/*
			  Se la percentuale di rumore contenuta nel blocco di campioni
			  analizzato supera il 20% lo considera l'inizio della sillaba
			  pronunciata
			 */
			printf("Percentuale rumore: %d%%\n", percent_rumore = 100 * rumore / NSAMPLE);
			if (percent_rumore > 20) {
				sample.begin = ptr - sample.data;
				break;
			}
		}
		if (!sample.begin)
			fprintf(stderr, "\nNon è stato possibile riconoscere l'inizio della parola.\n");

		for (ptr = sample.data + sample.begin; ptr < sample.data + SAMPLE_LEN; ptr += NSAMPLE) {
			silenzio = 0;

			/*
			  Analizza un blocco di campioni per vedere la percentuale di
			  silenzio in esso contenuta
			 */
			for (ptr2 = ptr; ptr2 < ptr + NSAMPLE; ptr2++)
				if (*ptr2 >= min && *ptr2 <= max)
					silenzio++;

			/*
			  Se la percentuale di silenzio contenuta nel blocco di campioni
			  analizzato supera l'80% lo considera la fine della sillaba
			  pronunciata
			 */
			printf("Percentuale silenzio: %d%%\n", percent_silenzio = 100 * silenzio / NSAMPLE);
			if (percent_silenzio > 80) {
				sample.end = ptr - sample.data;
				break;
			}
		}
		if (!sample.end)
			fprintf(stderr, "\nNon è stato possibile riconoscere la fine della parola.\n");

	} while (!sample.begin || !sample.end);

	/*
	  Aggiunge la nuova sillaba al database
	 */
	lseek(fd, 0, SEEK_END);
	if (write(fd, &sample, sizeof(sample)) == -1)
		err_quit();
}

int
pronuncia(int dev, char *sillaba, int fd)
{
	Audio           sample;
	lseek(fd, 0, SEEK_SET);
	memset(&sample, 0, sizeof(sample));

	/*
	  Scorre l'intero database alla ricerca della sillaba
	 */
	while (read(fd, &sample, sizeof(sample)) > 0)
		/*
		  Se la sillaba viene trovata la riproduce
		 */
		if (strcmp(sample.sillaba, sillaba) == 0) {
			write(dev, sample.data + sample.begin, sample.end - sample.begin);
			return 1;
		}
	return -1;
}

unsigned char
campiona(int dev)
{
	int             n;
	unsigned char   buff[LEN];

	if ((n = read(dev, &buff, LEN)) == LEN)
		return buff[n - 1];

	return -1;
}

/*
  tnx lesion for this function :)
 */
void
eq(int dev)
{
	int             i, l;
	struct sigaction act, old;

	act.sa_handler = sigint;
	sigemptyset(&act.sa_mask);
	act.sa_flags |= SA_RESTART;
	if (sigaction(SIGINT, &act, &old) == -1)
		err_quit();

	while (!signo) {
		l = campiona(dev) * SCREEN_LEN / 255;
		for (i = 0; i < l; i++)
			printf("|");
		printf("\n");
	}

	signo = 0;
	if (sigaction(SIGINT, &old, NULL) == -1)
		err_quit();
}

void
set_mic(int dev)
{
	int             i;

	printf("Vuoi eseguire una prova del microfono? [y/n]: ");
	if (fgetc(stdin) == 'y') {
		printf("\nPer terminare la prova microfono premi Ctrl^C ");
		for (i = 5; i > 0; i--) {
			printf("%d...", i);
			fflush(stdout);
			sleep(1);
		}
		printf("\n");
		eq(dev);
	}
	fgetc(stdin);
}

void
rumore_fondo(int dev)
{
	int             i;
	Audio           sample;
	unsigned char  *ptr;

	do {
		set_mic(dev);

		fprintf(stderr, "Il programma procedera' al campionamento e alla soppressione del rumore\n"
			"di fondo. Durante questa fase e' necessario rimanere in silenzio.\n"
			"Premere INVIO per iniziare.\n");
		fgetc(stdin);

		fprintf(stderr, "\nInizio procedura tra ");
		for (i = 5; i > 0; i--) {
			printf("%d...", i);
			fflush(stdout);
			sleep(1);
		}
		printf("silenzio!\n");
		memset(&sample, 0, sizeof(sample));
		if (read(dev, sample.data, SAMPLE_LEN) == -1)
			err_quit();

		/*
		  Ricerca i picchi massimi e minimi nel rumore di fondo campionato.
		  I valori dei campioni possono oscillare tra 0 e 255 dove 128 equivale
		  alla condizione di totale silenzio
		 */
		for (ptr = sample.data; ptr < sample.data + SAMPLE_LEN; ptr++) {
			*ptr &= MASK;
			if (*ptr < min)
				min = *ptr;
			if (*ptr > max)
				max = *ptr;
		}

		/*
		  Se i picchi massimi e minimi riscontrati nel rumore di fondo si discostano
		  eccessivamente da 128, che equivale alla condizione di totale silenzio,
		  mostra un messaggio d'errore e ripete la procedura
		 */
		if (min < MIN || max > MAX)
			fprintf(stderr, "Operazione non riuscita. Questo puo' essere dovuto ad un'errata\n"
				"configurazione del mixer o ad un eccessivo rumore di fondo. Provare\n"
				"a diminuire il livello del mixer relativo al microfono e riprovare.\n");
	} while (min < MIN || max > MAX);
}

void
set_device(int dev)
{
	int             arg;

	arg = BITS;
	if (ioctl(dev, SOUND_PCM_WRITE_BITS, &arg) == -1)
		err_quit();
	if (arg != BITS) {
		fprintf(stderr, "Unable to set required sample bits size\n");
		exit(-1);
	}
	arg = CHANNELS;
	if (ioctl(dev, SOUND_PCM_WRITE_CHANNELS, &arg) == -1)
		err_quit();
	if (arg != CHANNELS) {
		fprintf(stderr, "Unable to set required channels\n");
		exit(-1);
	}
	arg = RATE;
	if (ioctl(dev, SOUND_PCM_WRITE_RATE, &arg) == -1)
		err_quit();
	if (arg != RATE) {
		fprintf(stderr, "Unable to set required sample rate\n");
		exit(-1);
	}
}

int
main(int argc, char **argv)
{
	int             dev, fd, i;
	char            buff[MAXLEN], temp[MAXLEN], *parola, *sillaba;

	if ((dev = open(DSP, O_RDWR)) == -1)
		err_quit();

	if ((fd = open(DB, O_RDWR | O_CREAT, S_IRWXU)) == -1)
		err_quit();

	set_device(dev);
	rumore_fondo(dev);
	for (;;) {
		printf("Scrivi una frase: ");
		if (fgets(buff, sizeof(buff), stdin) == NULL)
			err_quit();

		buff[strlen(buff) - 1] = 0;
		memcpy(temp, buff, sizeof(buff));

		for (parola = strtok(temp, " "); parola != NULL; parola = strtok(NULL, " "))
			/*
			  Divide in sillabe la parola
			 */
			for (i = 1; dividi_sillabe(parola, &sillaba, i); i++) {

				/*
				  Se la sillaba non è presente nel database procede alla
				  sua memorizzazione
				 */
				if (trova_sillaba(fd, sillaba) == -1)
					memorizza_sillaba(dev, sillaba, fd);
				free(sillaba);
			}

		for (parola = strtok(buff, " "); parola != NULL; parola = strtok(NULL, " ")) {

			/*
			  Divide in sillabe la parola
			 */
			for (i = 1; dividi_sillabe(parola, &sillaba, i); i++) {

				/*
				  Riproduce la sillaba contenuta nel database
				 */
				if (pronuncia(dev, sillaba, fd) == -1)
					fprintf(stderr, "Sillaba sconosciuta\n");
				free(sillaba);
			}
			usleep(300000);
		}
	}
}
