
*** Modified files in JOE when it aborted on Mon Mar 15 16:13:40 2004
*** JOE was aborted by signal 1

*** File '(Unnamed)'
xdmcheck
xdcheck

*** File '(Unnamed)'
335

*** File 'softspeech_server.c'
/*
 * brass - Braille and speech server
 *
 * Copyright (C) 2001 by Roger Butenuth, All rights reserved.
 *
 * This is free software, placed under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation.  Please see the file COPYING for details.
 *
 * $Id: softspeech_server.c,v 1.19 2003/12/08 20:23:49 butenuth Exp $
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <malloc.h>
#include <stdlib.h>
#include <execinfo.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#include "synthesizer.h"
#include "softspeech.h"

/* Debug and Trace macros */
#define D(x) do { x } while (0)
#define T(x) /* do { x } while (0) */

/* Macros for memory debugging */
#define MALLOC_DEBUG 1
#if MALLOC_DEBUG
#  define xmalloc(s) xdmalloc(s, __FILE__, __LINE__)
#  define xrealloc(p, s) xdrealloc(p, s, __FILE__, __LINE__)
#  define xstrdup(s) xdstrdup(s, __FILE__, __LINE__)
#  define xfree(p)   xdfree(p, __FILE__, __LINE__)
#  define xcheck(p)   xdcheck(p, __FILE__, __LINE__)
void *xdmalloc(size_t size, char *f, int l);
void *xdrealloc(void *ptr, size_t size, char *f, int l);
void *xdstrdup(const char *str, char *f, int l);
void xdfrvoid xdcheck(void *ptr, char *f, int l);
void show_all_allocs(void);
#else
void *xmalloc(size_t size);
void *xrealloc(void *ptr, size_t size);
void *xstrdup(const char *str);
void xfree(void *ptr);
#endif

static FILE *db_fp;             /* for debug output */
static int  db_fd;

/*
 * Current setting of language, pitch, speed, and volume. These are
 * valid for the time of a flush command. Blocks in the queues can
 * have different pitch and speed attributes.
 */
static int language = -1;
static int pitch = 1000;        /* pitch factor (1000 is normal pitch) */
static int speed = 1000;        /* speed factor (1000 is normal speed) */
static double volume = 1.0;     /* volume factor (1.9 is normal volume) */

/*
 * Text is collected until the client sends a flush command. After
 * this start signal, work starts (phoneme generation, synthesis,
 * output).
 */
static char *text_buf = NULL;   /* unflushed, collected text */
static int  text_buf_len = 0;

/*
 * One phoneme as it is received from the phoneme generator and sent
 * to the synthesizer. Every speech block can hold a list of phonemes.
 */
typedef struct phoneme_struct {
    char *phoneme;              /* the phoneme as a string */
    int  duration;              /* duration im milliseconds */
    int  pairs;                 /* number of pairs in pitch */
    int  *pitch;                /* pairs of position / pitch */
    struct phoneme_struct *next; /* next in phoneme queue */
} phoneme_t;

/*
 * A block of speech that holds all information while it travels
 * through the system. It is generated after a flush command and
 * destroyed after it has been completely sent to the audio driver.
 */
typedef struct speech_block_struct {
    int  sequence;              /* sequence number for ordering and indexing */
    int  do_not_process;        /* flag for disabled blocks */
    /* Fields for input text */
    char *text;                 /* text to be spoken */
    int  text_len;              /* length of text to be spoken */
    int  text_written;          /* number chars written to phonem generator */
    int  language;              /* language number of this block */
    int  pitch;                 /* pitch factor for this block (1000=normal) */
    int  speed;                 /* speed factor for this block (1000=normal) */
    double volume;              /* volume facotr for this block (1.0=normal) */
    /* Fields for partial collected lines from phonem generator */
    char *linebuf;              /* line(s) from the phonem generator */
    int  linebuf_len;           /* lenght of buffer */
    int  linebuf_used;          /* number of bytes used */
    /* Fields for broken up phonem lines */
    phoneme_t *ph_first;
    phoneme_t *ph_last;
    enum { PH_SEARCH = 0,       /* search comment (";") */
           PH_END_FOUND,        /* comment phonem found or # found */
                                /* txt2pho/hadifix: comment, freephone: # */
           PH_COMPLETE          /* silence ("_") after comment found */
    }        ph_state;
    /* phoneme lines as one big block, ready for the synthesizer */
    char  *synth_input;         /* serialized phoneme list */
    int   synth_input_len;      /* length of phonemes */
    int   synth_time;           /* length of input in milliseconds */
    int   synth_written;        /* bytes written to synthesizer */
    /* samples to be written to the sound driver */
    short *sample;              /* samples */
    int   sample_count;         /* number of samples allocated */
    int   samples_written;
    int   samples_per_second;   /* sampling rate for this block */
    /* Next pointer for queue */
    struct speech_block_struct *next;
} speech_block_t;

/*
 * A queue of speech blocks. 
 */
typedef struct queue_struct {
    speech_block_t *first;
    speech_block_t *last;
} queue_t;

/*
 * Information about a subprocess (phoneme generator, synthesizer).
 */
typedef struct subprocess_struct {
    /****** constant values ******/
    char       *base_dir;
    char       *cmd_line;
    queue_t    input_q;
    struct subprocess_struct *next_proc;
    speech_block_t *inblock;
    speech_block_t *outblock;
    void       (*to_ready)(struct subprocess_struct *prc);
    void       (*from_ready)(struct subprocess_struct *prc);
    void       (*clear_queue)(struct subprocess_struct *prc);
    void       (*reinit)(struct subprocess_struct *prc);
    /****** dynamic values ******/
    int        running;
    char       **argv;
    int        to_fd;
    int        from_fd;
    int        pid;
    int        to_active;
    int        from_active;
} subprocess_t;

static void set_param(cmd_block_t *cmd);
static void text_flush(void);
static void clear_speech_pipe(void);

static int is_empty(queue_t *q);
static void enqueue_back(queue_t *q, speech_block_t *block);
static speech_block_t *get_first(queue_t *q);
static speech_block_t *dequeue_first(queue_t *q);
static void clear_queue(queue_t *q);

static char **tokenize(const char *str);
static void free_argv(char **argv);

static void init_signals(void);
static void start_procs(subprocess_t *prc, int first, int count);
static void kill_procs(subprocess_t *prc, int first, int count);
static int start_program(const char *base_dir, char **argv,
                         int *to, int *from, int *pid);

static void sigchild_handler(int sig);

static void to_phoneme_ready(struct subprocess_struct *prc);
static void from_phoneme_ready(struct subprocess_struct *prc);
static void clear_phoneme_queue(struct subprocess_struct *prc);
static void reinit_phoneme_gen(struct subprocess_struct *prc);

static void to_synth_ready(struct subprocess_struct *prc);
static void from_synth_ready(struct subprocess_struct *prc);
static void clear_synth_queue(struct subprocess_struct *prc);
static void reinit_synth(struct subprocess_struct *prc);

static int  to_audio(void);
static void clear_sound_driver(void);
static void open_audio(int samples_per_second);
static void close_audio(int force);

static int queue_phoneme(speech_block_t *b, const char *line);
static void compute_synth_input(speech_block_t *block);
static void free_block(speech_block_t *b);
static void sigusr1_handler(int sig);
static void error_handler(int signum, struct sigcontext info);

static void set_config_var(int lang, const char *name, const char *value);

#define NR_LANGUAGES 2          /* English, German */
#define PROCS_PER_LANG 2
#define NUM_PROCS (PROCS_PER_LANG * NR_LANGUAGES)
#define PH_GEN_PROC(lang) (lang * PROCS_PER_LANG)
#define SYNTH_PROC(lang) (lang * PROCS_PER_LANG + 1)

static int samples_per_second[LANGUAGES];

static subprocess_t proc[NUM_PROCS] = { 
    {
        NULL,                   /* base_dir */
        NULL,                   /* cmd_line */
        { NULL, NULL },         /* input_q */
        &proc[1],               /* next_proc */
        NULL,                   /* inblock */
        NULL,                   /* outblock */
        to_phoneme_ready,       /* to_ready */
        from_phoneme_ready,     /* from_ready */
        clear_phoneme_queue,
        reinit_phoneme_gen,     /* reinitialize after crash */
        0
    }, { 
        NULL,                   /* base_dir */
        NULL,                   /* base_dir */
        { NULL, NULL },         /* input_q */
        NULL,                   /* next_proc */
        NULL,                   /* inblock */
        NULL,                   /* outblock */
        to_synth_ready,
        from_synth_ready,
        clear_synth_queue,
        reinit_synth,           /* reinitialize after crash */
        0
    }, {
        NULL,                   /* base_dir */
        NULL,                   /* cmd_line */
        { NULL, NULL },         /* input_q */
        &proc[3],               /* next_proc */
        NULL,                   /* inblock */
        NULL,                   /* outblock */
        to_phoneme_ready,       /* to_ready */
        from_phoneme_ready,     /* from_ready */
        clear_phoneme_queue,
        reinit_phoneme_gen,     /* reinitialize after crash */
        0
    }, { 
        NULL,                   /* base_dir */
        NULL,                   /* base_dir */
        { NULL, NULL },         /* input_q */
        NULL,                   /* next_proc */
        NULL,                   /* inblock */
        NULL,                   /* outblock */
        to_synth_ready,
        from_synth_ready,
        clear_synth_queue,
        reinit_synth,           /* reinitialize after crash */
        0
    }
};


/*
 * The audio queue and audio block hold synthesized samples which are
 * sent to dhe driver.
 */
static queue_t audio_queue = { NULL, NULL };
static speech_block_t *audio_block;

/*
 * The audio device is opened on request and closed when it is no longer
 * used. This makes it free for other applications or other synthesizers
 * (e.g. IBM Viavoice).
 */
static int audio_fd = -1;
static int audio_samples_per_second;

/*
 * All function addresses with names. With the help of this table,
 * we can translates addresses to symbolic information.
 */
typedef struct sym_struct {
    unsigned value;             /* Value of symbol */
    char     *name;             /* symbol name */
} sym_struct_t;

static sym_struct_t symbols[] = {
    { 0, "(null)" },
    { (unsigned)server_process , "server_process", },
    { (unsigned)set_param, "set_param", },
    { (unsigned)text_flush, "text_flush", },
    { (unsigned)clear_speech_pipe, "clear_speech_pipe", },
    { (unsigned)is_empty, "is_empty", },
    { (unsigned)enqueue_back, "enqueue_back", },
    { (unsigned)get_first, "get_first", },
    { (unsigned)dequeue_first, "dequeue_first", },
    { (unsigned)clear_queue, "clear_queue", },
    { (unsigned)tokenize, "tokenize", },
    { (unsigned)free_argv, "free_argv", },
    { (unsigned)init_signals, "init_signals", },
    { (unsigned)start_procs, "start_procs", },
    { (unsigned)kill_procs, "kill_procs", },
    { (unsigned)start_program, "start_program", },
    { (unsigned)sigchild_handler, "sigchild_handler", },
    { (unsigned)to_phoneme_ready, "to_phoneme_ready", },
    { (unsigned)from_phoneme_ready, "from_phoneme_ready", },
    { (unsigned)clear_phoneme_queue, "clear_phoneme_queue", },
    { (unsigned)reinit_phoneme_gen, "reinit_phoneme_gen", },
    { (unsigned)to_synth_ready, "to_synth_ready", },
    { (unsigned)from_synth_ready, "from_synth_ready", },
    { (unsigned)clear_synth_queue, "clear_synth_queue", },
    { (unsigned)reinit_synth, "reinit_synth", },
    { (unsigned)to_audio, "to_audio", },
    { (unsigned)clear_sound_driver, "clear_sound_driver", },
    { (unsigned)open_audio, "open_audio", },
    { (unsigned)close_audio, "close_audio", },
    { (unsigned)queue_phoneme, "queue_phoneme", },
    { (unsigned)compute_synth_input, "compute_synth_input", },
    { (unsigned)free_block, "free_block", },
    { (unsigned)sigusr1_handler, "sigusr1_handler", },
    { (unsigned)error_handler, "error_handler", },
    { (unsigned)set_config_var, "set_config_var", },
#if MALLOC_DEBUG
    { (unsigned)xdmalloc, "xdmalloc", },
    { (unsigned)xdrealloc, "xdrealloc", },
    { (unsigned)xdstrdup, "xdstrdup", },
    { (unsigned)xdfree, "xdfree", },
    { (unsigned)xdcheck, "xdcheck", },
    { (unsigned)show_all_allocs, "show_all_allocs", },
#else
    { (unsigned)xmalloc, "xmalloc", },
    { (unsigned)xrealloc, "xrealloc", },
    { (unsigned)xstrdup, "xstrdup", },
    { (unsigned)xfree, "xfree", },
#endif
    { 0, NULL }
};


/*
 * ----------------------------------------------------------------------
 * Server process, handles communication with text-to-phoneme translator
 * (hadifix, freespeech), synthesizer (mbrola) and sound device.
 * ----------------------------------------------------------------------
 */
void server_process(int from_master, int to_master)
{
    fd_set           rfds, wfds, efds;
    struct timeval   tv;
    int              max_fd;
    int              retval;
    long             usec_10ms = 10000; /* 10 ms in microseconds */
    long             usec_1s = 1000000; /* 1 s in microseconds */
    int              p;
    int              terminate = 0;

    if ((db_fp = fopen("/tmp/speech.log", "w")) == NULL)
        db_fp = fopen("/dev/null", "w");
    setlinebuf(db_fp);
    db_fd = fileno(db_fp);
    fprintf(db_fp, "logfile openend\n");
    fprintf(db_fp, "server_process started, pid = %d\n", getpid());
    fprintf(db_fp, "from_master = %d, to_master = %d\n",
            from_master, to_master);
    init_signals();

    while (!terminate) {
        FD_ZERO(&rfds);
        FD_ZERO(&wfds);
        FD_ZERO(&efds);
        
        FD_SET(from_master, &rfds);
        max_fd = from_master;
        FD_SET(from_master, &efds);
        if (to_master > from_master)
            max_fd = to_master;

        for (p = 0; p < NUM_PROCS; p++) {
            if (proc[p].to_active) {
                FD_SET(proc[p].to_fd, &wfds);
                if (proc[p].to_fd > max_fd)
                    max_fd = proc[p].to_fd;
            }
            if (proc[p].from_active) {
                FD_SET(proc[p].from_fd, &rfds);
                if (proc[p].from_fd > max_fd)
                    max_fd = proc[p].from_fd;
            }
        }
        /*
         * While the audio part is active, we have to poll. This is
         * the reason for the short timeout.
         * When the audio queue is empty, close the audio device when
         * the device is empty, too. close_audio(0) checks for this.
         */
        if (!is_empty(&audio_queue) || audio_block != NULL) {
            int r;
            tv.tv_sec  = usec_10ms / 1000000;
            tv.tv_usec = usec_10ms % 1000000;
            T(fprintf(db_fp, ">>> to_audio\n"););
            r = to_audio();
            T(fprintf(db_fp, "<<< to_audio -> %d\n", r););
        } else {
            tv.tv_sec  = usec_1s / 1000000;
            tv.tv_usec = usec_1s % 1000000;
            close_audio(0);
        }

	retval = select(max_fd + 1, &rfds, &wfds, &efds, &tv);
	if (retval == -1) {
	    if (errno == EINTR) {
		fprintf(db_fp, "interrupted select\n");
		continue;
	    }
	}
        if (FD_ISSET(from_master, &efds)) {
            fprintf(db_fp, "error on from_master\n");
        }
        if (FD_ISSET(from_master, &rfds)) {
            cmd_block_t cmd;
            if (read(from_master, &cmd, sizeof(int)) != sizeof(int)) {
                fprintf(db_fp, "can't read command length: %s",
                        strerror(errno));
                exit(1);
            }
            if (read(from_master, (char*)&cmd + sizeof(int), 
                     cmd.cmd_length - sizeof(int))
                != (int)(cmd.cmd_length - sizeof(int))) {
                fprintf(db_fp, "can't read command data: %s",
                        strerror(errno));
                exit(1);
            }
            switch (cmd.cmd) {
            case C_SYNTH:
                text_buf = xrealloc(text_buf, text_buf_len + cmd.len);
                memcpy(text_buf + text_buf_len, cmd.data, cmd.len);
                text_buf_len += cmd.len;
                break;
            case C_FLUSH:
                text_flush();
                break;
            case C_CLEAR:
                clear_speech_pipe();
                break;
            case C_INDEX_SET:
                break;
            case C_SET_PARAM:
                set_param(&cmd);
                break;
            case C_CONFIG_VAR:
                D(fprintf(db_fp, "set config var lang = %d, "
                          "name = \"%s\", value = \"%s\"\n",
                          cmd.param_num, cmd.name, cmd.data););
                set_config_var(cmd.param_num, cmd.name, cmd.data);
                break;
            case C_SELECT_LANGUAGE:
                language = cmd.param_num;
                break;
            case C_TERMINATE:
                terminate = 1;
                break;
            } /* switch */
        }
        for (p = 0; p < NUM_PROCS; p++) {
            if (proc[p].running && FD_ISSET(proc[p].to_fd, &wfds)) {
                T(fprintf(db_fp, ">>> to proc %d (%s)\n",
                          p, proc[p].cmd_line););
                (*proc[p].to_ready)(&proc[p]);
                T(fprintf(db_fp, "<<< to proc %d (%s)\n",
                          p, proc[p].cmd_line););
            }
            if (proc[p].running && FD_ISSET(proc[p].from_fd, &rfds)) {
                T(fprintf(db_fp, ">>> from proc %d (%s)\n",
                          p, proc[p].cmd_line););
                T(fprintf(db_fp, "proc[p].from_ready = 0x%08x\n",
                          (unsigned)proc[p].from_ready););
                (*proc[p].from_ready)(&proc[p]);
                T(fprintf(db_fp, "<<< from proc %d (%s)\n",
                          p, proc[p].cmd_line););
            }
        }
    };

    kill_procs(proc, 0, NUM_PROCS);
    close_audio(1);
    fprintf(db_fp, "server_process finished\n");
#if MALLOC_DEBUG
    show_all_allocs();
#endif
}


/*
 * ----------------------------------------------------------------------
 * Set a speech parameter.
 * ----------------------------------------------------------------------
 */
static void set_param(cmd_block_t *cmd)
{
    switch (cmd->param_num) {
    case S_SPEED:
        speed = cmd->param_value;
        if (speed <= 0 || speed >= 10000)
            speed = 1000;
        break;
    case S_PITCH:
        if (pitch <= 0 || pitch >= 10000)
            pitch = 1000;
        pitch = cmd->param_value;
        break;
    case S_VOLUME:
        volume = cmd->param_value / 1000.0;
        if (volume <= 0 || volume > 10)
            volume = 1;
        break;
    }
}


/*
 * ----------------------------------------------------------------------
 * A piece of text is complete, start to synthesize it and send it to
 * the sound card.
 * ----------------------------------------------------------------------
 */
static void text_flush(void)
{
    static char    *flush_str[] = {
        "\n",                   /* english, for freephone */
        "\n{Comment:a}a\n"      /* german, for txt2pho (hadifix) */
    };
    static int     sequence = 0;
    int            i;
    speech_block_t *block;

    /*
     * In case there is no text, return. There is nothing to do.
     */
    if (text_buf == NULL)
        return;
    /*
     * Remove trailing blanks. Return if the block is empty after
     * removing the blanks.
     */
    while (text_buf_len > 0 && text_buf[text_buf_len - 1] == ' ')
        text_buf_len--;
    if (text_buf_len == 0) {
        xfree(text_buf);
        text_buf = NULL;
        return;
    }
    /*
     * Print debug message
     */
    D(fprintf(db_fp, "flush(\"");
      for (i = 0; i < text_buf_len; i++) {
          fputc(text_buf[i], db_fp);
      }
      fprintf(db_fp, "\")\n");
    );
    /*
     * In case the process queue for the current language is not
     * running, start it now.
     */
    if (!proc[PH_GEN_PROC(language)].running) {
        fprintf(db_fp, "starting processes for language %d\n", language);
        start_procs(proc, PH_GEN_PROC(language), PROCS_PER_LANG);
    }
    /*
     * Allocate and fill block, queue it and activate the queue.
     */
    block = xmalloc(sizeof(speech_block_t));
    block->sequence = sequence++;
    block->text = xmalloc(text_buf_len + strlen(flush_str[language]));
    memcpy(block->text, text_buf, text_buf_len);
    memcpy(block->text + text_buf_len, flush_str[language],
           strlen(flush_str[language]));
    block->text_len = text_buf_len + strlen(flush_str[language]);
    block->text_written = 0;
    block->language = language;
    block->pitch = pitch;
    block->speed = speed;
    block->volume = volume;
    block->ph_state = PH_SEARCH;
    block->samples_per_second = samples_per_second[language];
    enqueue_back(&proc[PH_GEN_PROC(language)].input_q, block);
    proc[PH_GEN_PROC(language)].to_active = 1;
    /*
     * Free the input block which is now obsolete.
     */
    xfree(text_buf);
    text_buf = NULL;
    text_buf_len = 0;
}


/*
 * ----------------------------------------------------------------------
 * Clear queued text, phonemes, and audio data.
 * Close the audio device.
 * ----------------------------------------------------------------------
 */
static void clear_speech_pipe()
{
    int i;

    for (i = 0; i < NUM_PROCS; i++) {
        if (proc[i].clear_queue != NULL) {
            (*proc[i].clear_queue)(&proc[i]);
        }
        clear_queue(&proc[i].input_q);
    }
    clear_queue(&audio_queue);
    clear_sound_driver();
    close_audio(1);
}


/*
 * ----------------------------------------------------------------------
 * Is the queue empty? Returns boolean (0, 1)
 * ----------------------------------------------------------------------
 */
static int is_empty(queue_t *q)
{
    return q->first == NULL;
}


/*
 * ----------------------------------------------------------------------
 * Enqueue the block at the end of the queue.
 * ----------------------------------------------------------------------
 */
static void enqueue_back(queue_t *q, speech_block_t *block)
{
    block->next = NULL;

    if (is_empty(q)) {
        q->first = block;
        q->last = block;
    } else {
        q->last->next = block;
        q->last = block;
    }
}


/*
 * ----------------------------------------------------------------------
 * Get the first block in the queue without removing it from the queue.
 * ----------------------------------------------------------------------
 */
static speech_block_t *get_first(queue_t *q)
{
    assert(!is_empty(q));
    return q->first;
}


/*
 * ----------------------------------------------------------------------
 * Return the first element from the queue and return it.
 * ----------------------------------------------------------------------
 */
static speech_block_t *dequeue_first(queue_t *q)
{
    speech_block_t *block;
    assert(!is_empty(q));
    block = get_first(q);
    q->first = block->next;

    return block;
}


/*
 * ----------------------------------------------------------------------
 * Clear the whole queue.
 * ----------------------------------------------------------------------
 */
static void clear_queue(queue_t *q)
{
    speech_block_t *block, *next;

    for (block = q->first; block != NULL; block = next) {
        next = block->next;
        free_block(block);
    }
    q->first = NULL;
    q->last = NULL;
}


/*
 * ----------------------------------------------------------------------
 * Split a string in substring marked by spaces. The array and all
 * substrings are allocated by xmalloc and have to be freed by the
 * caller.
 * ----------------------------------------------------------------------
 */
static char **tokenize(const char *str)
{
    char       **argv = NULL;
    int        count = 0;
    const char *start;

    while (*str) {
        while (*str == ' ' || *str == '\t')
            str++;              /* skip white space */
        if (*str == 0)
            break;
        argv = xrealloc(argv, (count + 1) * sizeof(char*));
        start = str;
        while (*str != ' ' && *str != '\t' && *str != 0)
            str++;
        argv[count] = xmalloc(str - start + 1);
        memcpy(argv[count], start, str - start);
        argv[count][str - start] = 0;
        count++;
    }
    argv = xrealloc(argv, (count + 1) * sizeof(char*));
    argv[count] = NULL;

    return argv;
}


/*
 * ----------------------------------------------------------------------
 * Free an array of strings and the pointer holding the array.
 * ----------------------------------------------------------------------
 */
static void free_argv(char **argv)
{
    int i;
    
    for (i = 0; argv[i] != NULL; i++) {
        xfree(argv[i]);
    }
    xfree(argv);
}


/*
 * ----------------------------------------------------------------------
 * Initialise the signals handlers:
 * - sigusr1: Dump state to logfile
 * - sigchild: Restart crashed child process
 * ----------------------------------------------------------------------
 */
static void init_signals(void)
{
    struct sigaction sig_act;

    sig_act.sa_handler = sigusr1_handler;
    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_flags    =  SA_RESTART;
    sig_act.sa_restorer = NULL;
    sigaction(SIGUSR1, &sig_act, NULL);

    sig_act.sa_handler = sigchild_handler;
    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_flags    =  SA_NOCLDSTOP;
    sig_act.sa_restorer = NULL;
    sigaction(SIGCHLD, &sig_act, NULL);

    signal(SIGPIPE, (void(*)(int))error_handler);
    signal(SIGSEGV, (void(*)(int))error_handler);
    fprintf(db_fp, "signals initialized for pid = %d\n", getpid());
}


/*
 * ----------------------------------------------------------------------
 * Start all the processes in the process array.
 * ----------------------------------------------------------------------
 */
static void start_procs(subprocess_t *prc, int first, int count)
{
    int p;

    for (p = first; p < first + count; p++) {
        prc[p].argv = tokenize(prc[p].cmd_line);
        start_program(prc[p].base_dir, prc[p].argv,
                      &prc[p].to_fd, &prc[p].from_fd, &prc[p].pid);
        prc[p].running = 1;
        fprintf(db_fp, "%s started, pid = %d\n", prc[p].argv[0], getpid());
    }
}


/*
 * ----------------------------------------------------------------------
 * Terminate all the processes in the process array. @@@
 * ----------------------------------------------------------------------
 */
static void kill_procs(subprocess_t *prc, int first, int count)
{
    int p;

    /*
     * Send a SIGTERM to all the processes and clear the arguments.
     * This ensures the memory is freed and avoids a restart of the
     * processes in the signal handler.
     */
    for (p = first; p < first + count; p++) {
        kill(prc[p].pid, SIGTERM);
        free_argv(prc[p].argv);
        xfree(prc[p].base_dir);
        prc[p].base_dir = NULL;
        xfree(prc[p].cmd_line);
        prc[p].cmd_line = NULL;
        prc[p].argv = NULL;
        prc[p].running = 0;
    }
}


/*
 * ----------------------------------------------------------------------
 * Start a program (int the background) and connect it with two pipes
 * to the caller:
 *  *to from caller to program
 *  *from from programm to caller
 * Process id of the program is returned in *pid.
 * ----------------------------------------------------------------------
 */
static int start_program(const char *base_dir, char **argv,
                         int *to, int *from, int *pid)
{
    int to_pipe[2], from_pipe[2];

    assert(pipe(to_pipe) >= 0);
    assert(pipe(from_pipe) >= 0);
    
    *pid = fork();
    assert(*pid >= 0);
    if (*pid == 0) {		/* child */
	close(from_pipe[0]);
	close(to_pipe[1]);
	assert(dup2(to_pipe[0], 0) >= 0);
	assert(dup2(from_pipe[1], 1) >= 0);
	assert(dup2(db_fd, 2) >= 0);
	close(from_pipe[1]);
	close(to_pipe[0]);
	if (chdir(base_dir) < 0) {
            fprintf(db_fp, "chdir(\"%s\") failed, reason: %s\n",
                    base_dir, strerror(errno));
        }
	execvp(argv[0], argv);
	fprintf(db_fp, "exec failed, reason: %s\n", strerror(errno));
        fprintf(db_fp, "dir = \"%s\", argv[0] = \"%s\"\n", base_dir, argv[0]);
	return -1;
    } else {			/* parent */
        int flags;
	*to   = to_pipe[1];
	*from = from_pipe[0];
	close(to_pipe[0]);
	close(from_pipe[1]);

        flags = fcntl(*to, F_GETFL);
        assert(flags >= 0);
        assert(fcntl(*to, F_SETFL, flags | O_NONBLOCK) >= 0);
        flags = fcntl(*from, F_GETFL);
        assert(flags >= 0);
        assert(fcntl(*from, F_SETFL, flags | O_NONBLOCK) >= 0);
        fprintf(db_fp, "started %s, pid is %d\n", argv[0], *pid);
    }

    return 0;
}


/*
 * ----------------------------------------------------------------------
 * Wait (nonblocking) for the termination of the children.
 * ----------------------------------------------------------------------
 */
static void sigchild_handler(int sig)
{
    int   status;
    pid_t pid;

    fprintf(db_fp, "sigchild_handler\n");
    pid = waitpid(-1, &status, WNOHANG);
    while (pid > 0) {
        int        p;
        const char *proc_name;
        int        restart = 0;

        for (p = 0; p < NUM_PROCS; p++) {
            if (proc[p].pid == pid)
                break;
        }
        if (p < NUM_PROCS) {
            if (proc[p].argv != NULL) {
                restart = 1;
            }
            proc_name = proc[p].cmd_line;
        } else {
            proc_name = "<unknown process>";
        }
	if (WIFEXITED(status))
	    fprintf(db_fp, "process %s returned %d\n",
		    proc_name, WEXITSTATUS(status));
	else if (WIFSIGNALED(status))
	    fprintf(db_fp, "process %s died by signal %d (%s)\n",
		    proc_name, WTERMSIG(status), strsignal(WTERMSIG(status)));
	else
	    fprintf(db_fp, "process %s died by unknown reason\n", proc_name);

        if (restart) {
            close(proc[p].to_fd);
            close(proc[p].from_fd);
            start_program(proc[p].base_dir, proc[p].argv,
                          &proc[p].to_fd, &proc[p].from_fd, &proc[p].pid);
            if (proc[p].reinit != NULL) {
                (*proc[p].reinit)(&proc[p]);
            }
            fprintf(db_fp, "process %s restarted\n", proc[p].cmd_line);
        }
        pid = waitpid(-1, &status, WNOHANG);
    }
}


/*
 * ----------------------------------------------------------------------
 * Take text from the input queue and write it to the phonem generator.
 * ----------------------------------------------------------------------
 */
static void to_phoneme_ready(struct subprocess_struct *prc)
{
    int written;
    speech_block_t *block = prc->inblock;

    if (prc->outblock == NULL && block == NULL && !is_empty(&prc->input_q)) {
        block = dequeue_first(&prc->input_q);
        prc->inblock = block;
    }
    if (block != NULL) {
        written = write(prc->to_fd, block->text + block->text_written,
                        block->text_len - block->text_written);
        assert(written >= 0);
        block->text_written += written;
        if (block->text_written == block->text_len) {
            prc->inblock = NULL;
        }
        /*
         * Whenever we write something to a subprocess, reading from this
         * process must be activated, or deadlock can occur.
         */
        prc->from_active = 1;
        prc->outblock = block;
    }
    /*
     * When the current block is completely written to the next
     * process, we go to sleep. We are woken again when it has left
     * the subprocess.
     */
    if (prc->inblock == NULL) {
        prc->to_active = 0;
    }
}


/*
 * ----------------------------------------------------------------------
 * Read phonemes from the phoneme generator and queue them in the
 * outblock. When the end is reached, move the outblock to the next
 * queue and activate the input handler for the phoneme generator.
 * ----------------------------------------------------------------------
 */
static void from_phoneme_ready(struct subprocess_struct *prc)
{
    const int      bufsize = 1000;
    const int      minfree =  200;
    int            bfree;
    speech_block_t *b = prc->outblock;
    int            got;
    int            i;
    int            found;
    int            complete = 0;

    /*
     * In case our line buffer for reading from the subprocess is
     * empty, allocate one and remember how much is allocated and how
     * much is used.
     */
    if (b->linebuf == NULL) {
        b->linebuf = xmalloc(bufsize);
        b->linebuf_len = bufsize;
        b->linebuf_used = 0;
    }
    /*
     * Compute free amount in line buffer. If this goes too low,
     * reallocate a bigger buffer (increment is equal to initial
     * buffer size).
     */
    bfree = b->linebuf_len - b->linebuf_used;
    if (bfree < minfree) {
        b->linebuf_len += bufsize;
        b->linebuf = xrealloc(b->linebuf, b->linebuf_len);
        bfree = b->linebuf_len - b->linebuf_used;
    }
    got = read(prc->from_fd, b->linebuf + b->linebuf_used, bfree);
    if (got < 0) {
        fprintf(db_fp, "from phoneme: %s\n", strerror(errno));
        return;
    }
    b->linebuf_used += got;
    do {
        found = 0;
        for (i = 0; i < b->linebuf_used; i++) {
            if (b->linebuf[i] == '\n') {
                found = 1;
                break;
            }
        }
        /*
         * When we have found end of line, then cut line at this
         * position, copy phonems to buffer, move bytes so that all
         * bytes of the line are overwritten.  
         */
        if (found) {
            b->linebuf[i] = 0;  /* replace "\n" by "\0" */
            complete = queue_phoneme(b, b->linebuf);
            memmove(b->linebuf, b->linebuf + i + 1, b->linebuf_used - i);
            b->linebuf_used -= i + 1;
        }
    } while (found);
    /*
     * When we have the complete phoneme sequence from the generator,
     * do the folowing things:
     *  - compute input to synthesizer as one memory block (instead of list)
     *  - queue it at the next handler (the synthesizer)
     *  - clear the pointer to the "current outlock"
     *  - deactivate ourself
     *  - activate the next handler
     *  - activate the input (we are empty now)
     * When the block has the "don't process flag" set, just free it.
     */
    if (complete) {
        if (b->do_not_process) {
            free_block(b);
        } else {
            compute_synth_input(b);
            enqueue_back(&prc->next_proc->input_q, b);
            prc->next_proc->to_active = 1;
            prc->to_active = 1;
        }
        prc->outblock = NULL;
        prc->from_active = 0;
    }
}


/*
 * ----------------------------------------------------------------------
 * Throw away any content that is processed by the phoneme generator.
 * We can't stop the external process, so just mark the current block
 * as "don't process".
 * ----------------------------------------------------------------------
 */
static void clear_phoneme_queue(struct subprocess_struct *prc)
{
    if (prc->inblock != NULL)
        prc->inblock->do_not_process = 1;
    if (prc->outblock != NULL)
        prc->outblock->do_not_process = 1;
}


/*
 * ----------------------------------------------------------------------
 * Called after a crash and restart of the phoneme generator. 
 * Send everything from the current block again and free everything
 * we got from the phoneme generator.
 * @@@ add crashcount in speech_block to avoid forever loops.
 * ----------------------------------------------------------------------
 */
static void reinit_phoneme_gen(struct subprocess_struct *prc)
{
    /*
     * When there is an input block, resend the complete block
     */
    if (prc->inblock != NULL) {
        speech_block_t *b = prc->inblock;
        b->text_written = 0;
    }
    /*
     * In case there is an output block, clear everything in this
     * block and reset all variables to start conditions.
     */
    if (prc->outblock != NULL) {
        speech_block_t *b = prc->outblock;
        phoneme_t *ph, *ph_next;
        if (b->linebuf != NULL) {
            xfree(b->linebuf);
            b->linebuf = NULL;
            b->linebuf_len = 0;
            b->linebuf_used = 0;
        }
        b->ph_state = PH_SEARCH;
        for (ph = b->ph_first; ph != NULL; ph = ph_next) {
            xfree(ph->phoneme);
            if (ph->pitch != NULL)
                xfree(ph->pitch);
            ph_next = ph->next;
            xfree(ph);
        }
        b->ph_first = NULL;
        b->ph_last = NULL;
        
        if (b->synth_input != NULL) {
            xfree(b->synth_input);
            b->synth_input_len = 0;
            b->synth_time = 0;
            b->synth_written = 0;
        }

        prc->outblock = NULL;
    }

    prc->to_active = 1;
}


/*
 * ----------------------------------------------------------------------
 * The synthesizer process is ready to accept input. Send input to it
 * and wake the read queue.
 * ----------------------------------------------------------------------
 */
static void to_synth_ready(struct subprocess_struct *prc)
{
    int written;
    speech_block_t *block = prc->inblock;

    if (prc->outblock == NULL && block == NULL && !is_empty(&prc->input_q)) {
        block = dequeue_first(&prc->input_q);
        prc->inblock = block;
    }
    if (block != NULL) {
        written = write(prc->to_fd, block->synth_input + block->synth_written,
                        block->synth_input_len - block->synth_written);
        assert(written >= 0);
        block->synth_written += written;
        if (block->synth_written == block->synth_input_len) {
            prc->inblock = NULL;
        }
        /*
         * Whenever we write something to a subprocess, reading from this
         * process must be activated, or deadlock can occur.
         */
        prc->from_active = 1;
        prc->outblock = block;
    }
    /*
     * When the current block is completely written to the next
     * process, we go to sleep. We are woken again when it has left
     * the subprocess.
     */
    if (prc->inblock == NULL) {
        prc->to_active = 0;
    }
}


/*
 * ----------------------------------------------------------------------
 * Read samples from the synthesizer process. When a block is complete,
 * queue it in the audio_queue. Following silence is ignored.
 * Possible improvement: Ignore silence at the beginning of a block
 *                       because it could be from the last block.
 * ----------------------------------------------------------------------
 */
static void from_synth_ready(struct subprocess_struct *prc)
{
    const int      bufsize = 8 * 1024;
    speech_block_t *b = prc->outblock;
    char           buffer[bufsize];
    int            got, rest;

    got = read(prc->from_fd, buffer, bufsize);
    if (got < 0) {
        fprintf(db_fp, "read from synth: %s\n", strerror(errno));
        return;
    }
    if (got % 2 != 0) {
        fprintf(db_fp, "got %d bytes from synth, last = %d\n", 
                got, buffer[got - 1]);
        /*
         * Read, until we get the second half sample
         */
        do {
            usleep(10 * 1000);  /* wait 1/100 s */
            rest = read(prc->from_fd, buffer + got, 1);
            if (rest < 0) {
                fprintf(db_fp, "read from synth: %s\n", strerror(errno));
                return;
            }
        } while (rest == 0);
    }
    got /= sizeof(short);   /* from bytes to samples */
    
    if (b != NULL) {
        /*
         * We have an output block, process the samples.
         * When the block is complete, check whether we have to process
         * it or throw it away.
         */
        b->sample =
            xrealloc(b->sample, (b->sample_count + got + 1) * sizeof(short));
        memcpy(&b->sample[b->sample_count], buffer, got * sizeof(short));
        b->sample_count += got;
        if (b->sample_count >= b->synth_time * b->samples_per_second / 1000) {
            if (b->do_not_process) {
                free_block(b);  /* throw away */
            } else {
                /*
                 * Whenever the volume differs more than 1% from normal,
                 * scale and clip output.
                 */
                if (b->volume < 0.99 || b->volume > 1.01) {
                    int i;
                    for (i = 0; i < b->sample_count; i++) {
                        int s = b->sample[i] * b->volume;
                        if (s >= (1 << 15))
                            s = (1 << 15) - 1;
                        else if (s < -(1 << 15))
                            s = -(1 << 15);
                        b->sample[i] = s;
                    }
                }
                enqueue_back(&audio_queue, b);
            }
            prc->outblock = NULL;
        }
    } else {
        /*
         * Debugging: We expect silence, check if there is anything else.
         */
        int i, c;
        short *s = (short*)buffer;
        
        for (i = 0, c = 0; i < got; i++) {
            if (s[i] <= -5 || s[i] >= 5) {
                c++;
                fprintf(db_fp, "val = %d\n", s[i]);
            }
        }
        if (c > 0)
            fprintf(db_fp, "%d samples of %d samples not silence\n", c, got);
    }
}


/*
 * ----------------------------------------------------------------------
 * Throw away any content that is processed by the synthesizer.  We
 * can't stop the external process, so just mark the current block as
 * "don't process".
 * ----------------------------------------------------------------------
 */
static void clear_synth_queue(struct subprocess_struct *prc)
{
    if (prc->inblock != NULL)
        prc->inblock->do_not_process = 1;
    if (prc->outblock != NULL)
        prc->outblock->do_not_process = 1;
}


/*
 * ----------------------------------------------------------------------
 * Called after a crash and restart of the synthesizer.
 * @@@ add crashcount in speech_block to avoid forever loops.
 * ----------------------------------------------------------------------
 */
static void reinit_synth(struct subprocess_struct *prc)
{
    if (prc->inblock != NULL) {
        prc->inblock->synth_written = 0;
    }

    if (prc->outblock != NULL) {
        speech_block_t *b = prc->outblock;

        if (b->sample != NULL)
            xfree(b->sample);
        b->sample = NULL;
        b->sample_count = 0;
        b->samples_written = 0;
    }

    prc->to_active = 1;
}


/*
 * ----------------------------------------------------------------------
 * Write samples from the audio queue to the sound driver. Return how
 * many milliseconds have to be played.
 * ----------------------------------------------------------------------
 */
static int to_audio(void)
{
    int            write_size, written;
    audio_buf_info info;

    /*
     * In case we don't have an audio_block to work on, try to get one.
     */
    if (audio_block == NULL && !is_empty(&audio_queue)) {
        audio_block = dequeue_first(&audio_queue);
    }
    /*
     * When we have a block:
     *  - Check if sampling rate of an open audio device is o.k.
     *    (close it when not).
     *  - Open the audio device if it is not already open.
     */
    if (audio_block != NULL) {
        if (audio_fd >= 0 &&
            audio_samples_per_second != audio_block->samples_per_second) {
            D(fprintf(db_fp, "sampling rate has changed, closing audio\n"););
            close_audio(1);
        }
        if (audio_fd == -1) {
            open_audio(audio_block->samples_per_second);
            if (audio_fd == -1)
                return 1;
        }
    }
    /*
     * Write fragments until there are 4 in the buffer. Do not write more
     * because these would have to be killed on a "clear command".
     *
     * info.fragments:  # of available fragments (only complete free fragments)
     * info.fragstotal: total # of fragments allocated
     * info.fragsize:   size of a fragment in bytes
     */
    assert(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) >= 0);
    while (audio_block != NULL && 
           info.fragstotal - info.fragments < 4 && info.fragments > 0) {
        write_size = sizeof(short) * (audio_block->sample_count -
                                      audio_block->samples_written);
        if (write_size > info.fragsize)
            write_size = info.fragsize;
        written = write(audio_fd,
                        &audio_block->sample[audio_block->samples_written],
                        write_size);
        if (written < 0) {
            fprintf(db_fp, "can't write audio data: %s\n", strerror(errno));
            exit(1);
        }
        /*
         * When we have written an odd number of bytes (only one byte
         * of the last sample), write one more byte to complete the
         * sample. This should never happen.  */
        if (written % 2 != 0) {
            fprintf(db_fp, "wrote odd number of bytes\n");
            write(audio_fd,
                  &audio_block->sample[audio_block->samples_written +
                                      written - 1],
                  1);
        }
        audio_block->samples_written += written / 2;
        /*
         * Check if we have processed the whole block. In this case,
         * free the block and try to get a new one.
         */
        if (audio_block->samples_written == audio_block->sample_count) {
            free_block(audio_block);
            assert(ioctl(audio_fd, SNDCTL_DSP_POST, NULL) >= 0);
            if (!is_empty(&audio_queue)) {
                audio_block = dequeue_first(&audio_queue);
            } else {
                audio_block = NULL;
            }

        }
        assert(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) >= 0);
    }

    return 0;
}


/*
 * ----------------------------------------------------------------------
 * Remove any audio sent to the sound driver.
 * ----------------------------------------------------------------------
 */
static void clear_sound_driver(void)
{
    if (audio_block != NULL) {
        free_block(audio_block);
        audio_block = NULL;
    }
    if (audio_fd >= 0) {
      ioctl(audio_fd, SNDCTL_DSP_RESET);
    }
}


/*
 * ----------------------------------------------------------------------
 * Open the audio device for writing with the following parameters:
 *  - sampling frequency: given by parameter
 *  - sample size: 16 bit, signed integer, native endian
 *  - one channel (mono)
 * It would be nice to use non-blocking writes to the device or - even
 * better - select(), but it seems the audio driver does not support
 * this.
 * ----------------------------------------------------------------------
 */
static void open_audio(int samples_per_second)
{
    int format = AFMT_S16_NE; /* Expands to AFMT_S16_LE or AFMT_S16_BE resp.*/
    int channels = 1;

    D(fprintf(db_fp, ">>> open audio(%d)\n", samples_per_second););
    audio_fd = open("/dev/dsp", O_WRONLY);
    if (audio_fd < 0) {
        audio_fd = -1;
        fprintf(db_fp, "can not open /dev/dsp\n");
    } else {
        T(audio_buf_info info;
          ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info);
          fprintf(db_fp, "fragstotal = %d\n", info.fragstotal);
          fprintf(db_fp, "fragments  = %d\n", info.fragments);
          fprintf(db_fp, "fragsize   = %d\n", info.fragsize);
        );
        D(fprintf(db_fp, "/dev/dsp open, fd = %d\n", audio_fd););
	/* Some driver needs reset first before they accept requests
	 * for changing settings */
        assert(ioctl(audio_fd, SNDCTL_DSP_RESET, NULL) >= 0);  
        assert(ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format) >= 0);
        assert(ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels) >= 0);
        assert(ioctl(audio_fd, SNDCTL_DSP_SPEED, &samples_per_second) >= 0);
        audio_samples_per_second = samples_per_second;
    }
    D(fprintf(db_fp, "<<< open audio\n"););
}


/*
 * ----------------------------------------------------------------------
 * Close audio. This makes the audio device available for other
 * applications.
 * If force == 1, close the device immediately.
 * If force == 0, close only when there is no data queued.
 * ----------------------------------------------------------------------
 */
static void close_audio(int force)
{
    audio_buf_info info;

    if (audio_fd == -1) {
        return;                 /* nothing to do... */
    }
    if (force) {
        fprintf(db_fp, "forced close of audio device\n");
        close(audio_fd);
        audio_fd = -1;
        audio_samples_per_second = 0;
    } else {
        fprintf(db_fp, "unforced close of audio device\n");
        /*
         * Close if number of available fragments (fragments) is equal
         * to number of allocated fragments (fragstotal).
         * We do a sync before we ask. Otherwise some audio drivers
         * seem to hold the last buffer so we can never close.
         */
        assert(ioctl(audio_fd, SNDCTL_DSP_SYNC, NULL) >= 0);
        assert(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) >= 0);
        fprintf(db_fp, "fragments = %d, fragstotal = %d\n",
                info.fragments, info.fragstotal);
        fprintf(db_fp, "bytes     = %d\n", info.bytes);
        if (info.fragments == info.fragstotal) {
            fprintf(db_fp, "queue empty, close audio\n");
            close(audio_fd);
            audio_fd = -1;
        }
    }
}


/*
 * ----------------------------------------------------------------------
 * Take a line as it comes from the phoneme generator. Parse this line
 * and look for end of input. Every found phoneme (except the garbage
 * at the end) is queued in the phoneme queue in the current block.
 * Return 1 (true), when we found the end of input.
 * ----------------------------------------------------------------------
 */
static int queue_phoneme(speech_block_t *b, const char *line)
{
    char      **argv = tokenize(line);
    phoneme_t *ph = xmalloc(sizeof(phoneme_t));
    int       i, j;
    int       orig_duration;

    ph->phoneme = xstrdup(argv[0]);
    if (argv[1] != NULL) {
        orig_duration = atoi(argv[1]);
    } else {
        orig_duration = 0;
    }
    ph->duration = (orig_duration * 1000) / b->speed;
    ph->next = NULL;

    switch (b->ph_state) {
    case PH_SEARCH: 
        if (!strcmp(ph->phoneme, ";") || !strcmp(ph->phoneme, "#")) {
            b->ph_state = PH_END_FOUND;
        }
        break;
    case PH_END_FOUND:
        if (!strcmp(ph->phoneme, "_") && orig_duration >= 300) {
            b->ph_state = PH_COMPLETE;
        }
        break;
    case PH_COMPLETE:
        break;
    }
    /*
     * Count numbers after phoneme and duration. The result must be
     * even because we expect position / pitch pairs.
     * Remember: argv[0] is the phoneme, argv[1] the duration
     *           Pairs of position / pitch follow.
     */
    for (i = 1; argv[i] != NULL; i++)
        ;
    if (strcmp(ph->phoneme, "_") && strcmp(ph->phoneme, ";") && i > 2) {
        if ((i - 2) % 2 != 0) {
            /*
             * Position and pitch should arrive in pairs. Ignore
             * other lines.
             */
            fprintf(db_fp, "got strange phoneme line: \"%s\"\n", line);
            ph->pairs = 0;
            ph->pitch = NULL;
        } else {
            /*
             * Allocate memory for the pairs. Parse the pairs and
             * change the position according to the current speech speed
             * and the pitch according to the current pitch setting.
             */
            ph->pairs = (i - 2) / 2;
            ph->pitch = xmalloc(2 * ph->pairs * sizeof(int*));
            for (i = 2, j = 0; argv[i] != NULL; i += 2, j += 2) {
                ph->pitch[j    ] = (atoi(argv[i    ]) * 1000) / b->speed;
                ph->pitch[j + 1] = (atoi(argv[i + 1]) * b->pitch) / 1000;
            }
        }
    } else {
        ph->pairs = 0;
        ph->pitch = NULL;
    }
    /*
     * Only phonemes up to the comment are queued. All following
     * phonemes are only garbage and thrown away.
     */
    if (b->ph_state == PH_SEARCH) {
        if (b->ph_first == NULL) {
            b->ph_first = ph;
            b->ph_last = ph;
        } else {
            b->ph_last->next = ph;
            b->ph_last = ph;
        }
    } else {
        xfree(ph->phoneme);
        if (ph->pitch != NULL)
            xfree(ph->pitch);
        xfree(ph);
    }
    free_argv(argv);

    return (b->ph_state == PH_COMPLETE);
}


/*
 * ----------------------------------------------------------------------
 * Take the list of phonemes with duration and pitch info and convert
 * in one continuous buffer that can be written to the synthesizer.
 * ----------------------------------------------------------------------
 */
static void compute_synth_input(speech_block_t *block)
{
    phoneme_t *ph;
    int       i;
    int       est_size, real_size, size;
    char      *pos;

    /*
     * First loop: Estimate how much memory we need.
     */
    est_size = 0;
    for (ph = block->ph_first; ph != NULL; ph = ph->next) {
        est_size += strlen(ph->phoneme);
        est_size += 8;              /* duration */
        est_size += ph->pairs * 16; /* position / pitch pairs */
        est_size += 1;              /* \n */
    }
    est_size += 10;
    /*
     * Allocate memory and fill it in a second loop with content.
     * Here we could add pitch or speed modifications.
     */
    real_size = 0;
    block->synth_input = xmalloc(est_size);
    block->synth_time = 0;
    pos = block->synth_input;
    for (ph = block->ph_first; ph != NULL; ph = ph->next) {
        size = sprintf(pos, "%s %d", ph->phoneme, ph->duration);
        block->synth_time += ph->duration;
        pos += size;
        real_size += size;
        for (i = 0; i < 2 * ph->pairs; i++) {
            size = sprintf(pos, " %d", ph->pitch[i]);
            pos += size;
            real_size += size;
        }
        size = sprintf(pos, "\n");
        pos += size;
        real_size += size;
    }
    /*
     * Add two lines:
     *  - 500 ms silence
     *  - an embrola flush sign
     */
    size = sprintf(pos, "_ 500\n#\n");
    pos += size;
    real_size += size;
    
    block->synth_input_len = real_size;
    assert(block->synth_input_len < est_size);
    block->synth_input = xrealloc(block->synth_input, block->synth_input_len);
}


/*
 * ----------------------------------------------------------------------
 * Free a speech_block with all its sub structures.
 * ----------------------------------------------------------------------
 */
static void free_block(speech_block_t *b)
{
    phoneme_t *ph, *ph_next;

    if (b->text != NULL)
        xfree(b->text);
    if (b->linebuf != NULL)
        xfree(b->linebuf);
    for (ph = b->ph_first; ph != NULL; ph = ph_next) {
        xfree(ph->phoneme);
        if (ph->pitch != NULL)
            xfree(ph->pitch);
        ph_next = ph->next;
        xfree(ph);
    }
    if (b->synth_input != NULL)
        xfree(b->synth_input);
    if (b->sample != NULL)
        xfree(b->sample);
    xfree(b);
}


static void show_phoneme_list(phoneme_t *ph)
{
        fprintf(db_fp, "  phoneme list:\n");
    if (ph == NULL)
        fprintf(db_fp, "    (empty)\n");
    while (ph != NULL) {
        fprintf(db_fp, "    phoneme  = \"%s\"\n", ph->phoneme);
        fprintf(db_fp, "    duration = %d ms\n", ph->duration);
        fprintf(db_fp, "    pairs    = %d\n", ph->pairs);
        ph = ph->next;
    }
}

static void show_block(speech_block_t *b)
{
    int i;

    if (b == NULL) {
        fprintf(db_fp, "(null)\n");
        return;
    }

    fprintf(db_fp, "[\n");
    fprintf(db_fp, "  sequence       = %d\n", b->sequence);
    fprintf(db_fp, "  do_not_process = %d\n", b->do_not_process);
    fprintf(db_fp, "  text = \"");
    for (i = 0; i < b->text_len; i++) {
        fputc(b->text[i], db_fp);
    }
    fprintf(db_fp, "\"\n");
    fprintf(db_fp, "  linebuf_used = %d\n", b->linebuf_used);
    fprintf(db_fp, "  linebuf = \"");
    for (i = 0; i <  b->linebuf_used; i++)
        fputc(b->linebuf[i], db_fp);
    fprintf(db_fp, "\"\n");
    show_phoneme_list(b->ph_first);
    switch (b->ph_state) {
    case PH_SEARCH:
        fprintf(db_fp, "  ph_state = PH_SEARCH\n");
        break;
    case PH_END_FOUND:
        fprintf(db_fp, "  ph_state = PH_END_FOUND\n");
        break;
    case PH_COMPLETE:
        fprintf(db_fp, "  ph_state = PH_COMPLETE\n");
        break;
    }
    fprintf(db_fp, "  sample_count   = %d\n", b->sample_count);
    fprintf(db_fp, "]\n");
}

static void show_queue(queue_t *q)
{
    speech_block_t *block;
    for (block = q->first; block != NULL; block = block->next) {
        show_block(block);
    }
}

static void sigusr1_handler(int sig)
{
    int pnum;

    fprintf(db_fp, "sigusr1, state:\n");
    for (pnum = 0; pnum < NUM_PROCS; pnum++) {
        subprocess_t *p = &proc[pnum];
        fprintf(db_fp, "p = 0x%08x\n", (unsigned)p);
        if (p->argv != NULL)
            fprintf(db_fp, "proc %d (%s)\n", pnum, p->argv[0]);
        fprintf(db_fp, "to_active   = %d\n", p->to_active);
        fprintf(db_fp, "from_active = %d\n", p->from_active);
        fprintf(db_fp, "inblock:\n");
        show_block(p->inblock);
        fprintf(db_fp, "outblock:\n");
        show_block(p->outblock);
        fprintf(db_fp, "inputqueue:\n");
        show_queue(&p->input_q);
        fprintf(db_fp, "\n");
    }
}


/*
 * ----------------------------------------------------------------------
 * Signal handler for unexpected signals.
 * ----------------------------------------------------------------------
 */
static void error_handler(int signum, struct sigcontext info)
{
    extern char *strsignal(int sig);
    unsigned array[100];
    int      count, i;
    unsigned addr;
    int      j;
    int      min_j = 0;
    unsigned min_diff;
    fprintf(db_fp, "softspeech, received %s, terminating!\n", 
            strsignal(signum));

#if defined(__PPC__)
    addr = info.regs->nip;
#elif defined(__i386__)
    addr = info.eip;
#endif
    min_j = 0;
    min_diff = addr - symbols[0].value;
    for (j = 1; symbols[j].name != NULL; j++) {
        if (addr > symbols[j].value &&
            addr - symbols[j].value < min_diff) {
            min_j    = j;
            min_diff = addr - symbols[j].value;
        }
    }
    fprintf(db_fp, "fault at: [0x%08x] %s + 0x%08x\n",
            addr, symbols[min_j].name, min_diff);

    fprintf(db_fp, "stacktrace:\n");
    count = backtrace((void**)array, 100);
    for (i = 0; i < count; i++) {
        addr = array[i];
        min_j = 0;
        min_diff = addr - symbols[0].value;
        for (j = 1; symbols[j].name != NULL; j++) {
            if (addr > symbols[j].value &&
                addr - symbols[j].value < min_diff) {
                min_j    = j;
                min_diff = addr - symbols[j].value;
            }
        }
        fprintf(db_fp, "%2d [0x%08x] %s + 0x%08x\n",
                i, addr, symbols[min_j].name, min_diff);
    }

    abort();
}


/*
 * ----------------------------------------------------------------------
 * 
 * ----------------------------------------------------------------------
 */
static void set_config_var(int lang, const char *name, const char *value)
{
    if (!strcmp(name, "phoneme_generator_directory")) {
        proc[PH_GEN_PROC(lang)].base_dir = xstrdup(value);
    } else if (!strcmp(name, "phoneme_generator_command")) {
        proc[PH_GEN_PROC(lang)].cmd_line = xstrdup(value);
    } else if (!strcmp(name, "synthesizer_directory")) {
        proc[SYNTH_PROC(lang)].base_dir = xstrdup(value);
    } else if (!strcmp(name, "synthesizer_command")) {
        proc[SYNTH_PROC(lang)].cmd_line = xstrdup(value);
    } else if (!strcmp(name, "samples_per_second")) {
        samples_per_second[lang] = atoi(value);
        if (samples_per_second[lang] == 0) {
          fprintf(db_fp, "invalid sampling rate: %d, setting to 16000/s\n",
                  samples_per_second[lang]);
          samples_per_second[lang] = 16000;
        }
    } else {
        fprintf(db_fp, "unknown config var: \"%s\"\n", name);
    }
}

static int total_alloc = 0;
static int total_alloc_count = 0;

#if MALLOC_DEBUG
#  undef xmalloc
#  undef xstrdup
#  undef xfree

struct mlist {
    struct mlist *next;
    const char   *file;
    unsigned     line;
    size_t       size;
} *ml;

void *xdmalloc(size_t size, char *f, int l)
{
    struct mlist *m;

    m = malloc(sizeof(struct mlist) + size);
    assert(m != NULL);
    total_alloc += size;
    total_alloc_count++;
    m->size = size;
    m->line = l;
    m->file = f;
    m->next = ml;
    ml = m;
    memset(ml + 1, 0, size);

    return ml + 1;
}


void *xdrealloc(void *ptr, size_t size, char *f, int l)
{
    struct mlist **mlp, *m, *new_m;

    if (ptr == NULL)
        return xdmalloc(size, f, l);
    if (size == 0)
        xdfree(ptr, f, l);

    /*
     * Search block in alloc list and dequeue it from the list.
     */
    m = ptr;
    m--;
    for (mlp = &ml; *mlp != NULL; mlp = &(*mlp)->next)
	if (*mlp == m)
	    break;
    if (*mlp != m) {
	fprintf(db_fp, "Call of xrealloc with non allocated block!\n");
	return NULL;
    }
    total_alloc -= m->size;
    total_alloc_count--;
    *mlp = m->next;
    /*
     * Allocate new block and insert it into the list of allocated blocks.
     */
    new_m = malloc(sizeof(struct mlist) + size);
    assert(new_m != NULL);
    total_alloc += size;
    total_alloc_count++;
    new_m->size = size;
    new_m->line = l;
    new_m->file = f;
    new_m->next = ml;
    ml = new_m;
    /*
     * Clear new block, copy content from old to new block.
     */
    memset(new_m + 1, 0, size);
    memcpy(new_m + 1, m + 1, (size < m->size) ? size : m->size);
    /*
     * Delete content and free old block.
     */
    memset(m + 1, 0, m->size);
    free(m);

    return new_m + 1;
}


void *xdstrdup(const char *str, char *f, int l)
{
    char *str2;
    size_t size = strlen(str) + 1;

    str2 = xdmalloc(size, f, l);
    strcpy((char*)str2, str);

    return str2;
}

void xdfree(void *ptr, char *f, int l)
{
    struct mlist **mlp, *m;

    m = ptr;
    m--;
    for (mlp = &ml; *mlp != NULL; mlp = &(*mlp)->next)
	if (*mlp == m)
	    break;
    if (*mlp != m) {
	fprintf(db_fp, "Call of free for non allocated block"
                ", ptr = 0x%08x (%s, %d)!\n",
                (unsigned)ptr, f, l);
	return;
    }
    total_alloc -= m->size;
    total_alloc_count--;
    memset(ptr, 0, m->size);

    *mlp = m->next;
    free(m);
}

void xdcheck(void *ptr, char *f, int l)
{
    struct mlist **mlp, *m;

    m = ptr;
    m--;
    for (mlp = &ml; *mlp != NULL; mlp = &(*mlp)->next)
	if (*mlp == m)
	    break;
    if (*mlp != m) {
	fprintf(db_fp, "block not allocated, ptr = 0x%08x (%s, %d)!\n",
                (unsigned)ptr, f, l);
    } else {
        fprintf(db_fp, "block ok, ptr = 0x%08x (%s, %d)!\n",
                (unsigned)ptr, f, l);
    }
}

void show_all_allocs(void)
{
    struct mlist *m;
#if 1
    char *p;
    unsigned i;
#endif

    if (ml != NULL)
        fprintf(db_fp, "Memory still allocated:\n");
    else
        fprintf(db_fp, "All memory freed\n");
    for (m = ml; m != NULL; m = m->next) {
	fprintf(db_fp, "%s:%d: size = %d\n", m->file, m->line, m->size);
#if 1
	p = (char*)m + sizeof(struct mlist);
	for (i = 0; i < m->size && i < 4; i++)
            fprintf(db_fp, "%d ", ((char*)(m + 1))[i]);
	fputc('\n', db_fp);
#endif
    }
}


#  define xmalloc(s) xdmalloc(s, __FILE__, __LINE__)
#  define xstrdup(s) xdstrdup(s, __FILE__, __LINE__)
#  define xfree(p)   xdfree(p, __FILE__, __LINE__)
#else
void *xmalloc(size_t size)
{
    size_t *sp;

    sp = malloc(sizeof(size_t*) + size);
    assert(sp != NULL);
    total_alloc += size;
    total_alloc_count++;
    *sp = size;
    sp++;
    memset(sp, 0, size);

    return sp;
}


void *xrealloc(void *ptr, size_t size)
{
    size_t *old_sp, *new_sp;
    size_t old_size;

    if (ptr == NULL)
        return xmalloc(size);
    if (size == 0)
        xfree(ptr);

    old_sp = ((size_t*)ptr)--;
    old_size = *old_sp;
    new_sp = malloc(sizeof(size_t*) + size);
    assert(new_sp != NULL);
    total_alloc += size;
    total_alloc -= *old_sp;
    *new_sp = size;
    new_sp++;
    old_sp++;
    memset(new_sp, 0, size);
    memcpy(new_sp, old_sp, (size < old_size) ? size : old_size);
    free(old_sp - 1);

    return new_sp;
}


void *xstrdup(const char *str)
{
    size_t *sp;
    size_t size = strlen(str) + 1;

    sp = malloc(sizeof(size_t*) + size);
    assert(sp != NULL);
    total_alloc += size;
    total_alloc_count++;
    *sp = size;
    sp++;
    strcpy((char*)sp, str);

    return sp;
}

void xfree(void *ptr)
{
    size_t *sp;

    sp = ptr;
    sp--;
    total_alloc -= *sp;
    total_alloc_count--;
    memset(ptr, 0, *sp);
    free(sp);
}
#endif
