/* file = labeler.c */
/* allows interactive labeling of speech data.
   Reads a .au file, generates a .la file */
/* Nigel Ward, University of Tokyo, April 1994 */

  /* contents:
     - data structures
     - .au file i/o
     - .la file i/o
     - basics of the display
     - processing user keystrokes
     - main */

#include "cheap.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

/*----------------------------------------------------------------------------- */
#define  CANVAS_WIDTH       ((int) (MAX_SAMPLES / SAMPLES_PER_PIXEL))
#define  CANVAS_HEIGHT      700
#define  Y_SCALING          .1
#define  MAX_BOUNDARIES     (MAX_LABELS + 1)
#define  MAX_REGIONS        (2 * MAX_LABELS + 1) /* allow silence betwen words */
#define  DEFAULT_REGION_LEN 500  /* milliseconds */

/* define the origin point of the signal display */
#define LEFT_MARGIN     20
#define BASELINE       250
#define NOTICE_X       (LEFT_MARGIN + 200)
#define NOTICE_Y       50
#define FILENAME_YPOS            (BASELINE - 220)
#define BOUNDARY_DECORATION_YPOS (BASELINE - 200)
#define LABEL_YPOS               (BASELINE - 180)
#define BOUNDARY_LINE_TOP        (LABEL_YPOS - 0)
#define INPUT_YPOS               (BASELINE - 170)
#define REGION_DECORATION_YPOS   (BASELINE - 160)
#define BOUNDARY_LINE_BOTTOM     (BASELINE + 160)

#define FONT_HEIGHT 10     /* probably should query X for this */

#define DRAW 1
#define ERASE 0
#define LEFT 0
#define RIGHT 1

/*----------------------------------------------------------------------------- */
extern Display	*display;
extern Window	win;
extern XEvent  report;

/*-----------------------------------------------------------------------------*/
static Audio_filehdr hdr;	

char info_string[MAX_INFO_STRING_LEN];
char raw_data[MAX_SAMPLES];
int nsamples, nbars;
int bar_height[(int) (MAX_SAMPLES / SAMPLES_PER_PIXEL)];
int read_in_progress;  /* boolean, describes current state of interface */

int active_boundary;   /* measured in pixels from start of data */
int nfiles;
int this_file;
char file_list[100][MAX_PATH_LEN];

struct region {
  int start;
  char label[MAX_WORD_LEN];
  struct region *prev_region;
  struct region *next_region;
} ;

/* these regions are used to form  a double-linked list; 
   the reason why they are on an array is that I'm afraid of malloc */
struct region regions[MAX_REGIONS]; 
int free_pointer;
struct region *first_reg_ptr, *current_reg_ptr;

char string[MAX_WORD_LEN];       /* label, as input by user */

/*=============================================================================*/
struct region *allocate_region()
{
  struct region *reg_ptr;
  if (free_pointer >= MAX_REGIONS) 
    {fprintf(stderr, "!!Too many regions!!\n"); exit(STRANGE);}
  reg_ptr = &(regions[free_pointer]);
  reg_ptr->start = 0; 
  reg_ptr->prev_region = NULL; 
  reg_ptr->next_region = NULL;
  reg_ptr->label[0] = NULL;
  free_pointer++;
  return(reg_ptr); }

/*-----------------------------------------------------------------------------*/
int current_region_start()
{  return(current_reg_ptr->start);}

int current_region_end()
{ if (current_reg_ptr->next_region == NULL) 
    return( nbars - 1);
  else
    return((current_reg_ptr->next_region)->start);}
     
int next_region_start(reg_ptr)         struct region *reg_ptr;
{  return(reg_ptr->next_region == NULL ? nbars : (reg_ptr->next_region)->start);}

int current_boundary()
{   return( (active_boundary == LEFT) ? current_region_start(): current_region_end());}

/*-----------------------------------------------------------------------------*/
/* creates a new region and links it into the list of regions */
struct region *make_and_link_in(start, prev_reg_ptr, label)
     int start;  struct region *prev_reg_ptr; char *label;
{
  struct region *reg_ptr;

  reg_ptr = allocate_region();
  reg_ptr->start = start;
  strcpy(reg_ptr->label, label);
  if (start == 0) {
    first_reg_ptr = reg_ptr;}
  else {
    reg_ptr->prev_region = prev_reg_ptr;
    if (reg_ptr->prev_region != NULL) {
      (reg_ptr->prev_region)->next_region = reg_ptr;}}
  return(reg_ptr); }


/*=============================================================================*/
read_audio_file()
{
  int i, linearized;
  char raw, au_file_name[MAX_PATH_LEN];

  make_new_path(au_file_name, file_list[this_file], ".au");
  nsamples =  read_au_into_array(au_file_name, hdr, info_string, raw_data);
  if (nsamples < 1) {fprintf(stderr, "missing .au file\n"); exit(STRANGE); }

  nbars = nsamples / SAMPLES_PER_PIXEL; 
  for(i = 0 ; i < nbars; i ++) {
    raw = raw_data[(int) (i * SAMPLES_PER_PIXEL)];
    linearized = audio_u2s(raw);
    bar_height[i] = linearized * Y_SCALING; } }

/*-----------------------------------------------------------------------------*/
play_active_region()
{
  int first_byte_at, last_byte_at;
  first_byte_at =  current_region_start() * SAMPLES_PER_PIXEL;
  last_byte_at = current_region_end() * SAMPLES_PER_PIXEL;
  play_audio_data(raw_data, first_byte_at, last_byte_at - first_byte_at); }

/*-----------------------------------------------------------------------------*/
play_whole_input()
{  play_audio_data(raw_data, 0, nsamples); }


/*=============================================================================*/
write_labels()
{
  char la_file_name[MAX_PATH_LEN];
  FILE *la_fp;
  struct region *reg_ptr;

  printf(" writing labels \n");
  make_new_path(la_file_name, file_list[this_file], ".la");
  la_fp = fopen_or_exit(la_file_name, "w");

  write_la_file_header(la_fp);
  for (reg_ptr = first_reg_ptr; reg_ptr != NULL; reg_ptr = reg_ptr->next_region) {
    if (reg_ptr->label[0] != NULL)
      write_la_entry(la_fp,
		     reg_ptr->label,
		     reg_ptr->start * MS_PER_PIXEL, 
		     (reg_ptr->next_region == NULL)?
		       nbars * MS_PER_PIXEL :
		       (reg_ptr->next_region)->start * MS_PER_PIXEL); }
  fclose(la_fp);  }

/*-----------------------------------------------------------------------------*/
setup_labels()
{
  int nlabels; struct whyp label_array[MAX_LABELS];
  free_pointer = 0;
  nlabels = read_label_file(file_list[this_file], label_array);
  if (nlabels == 0) 
    setup_default_regions(); 
  else 
    setup_regions_from_labels(nlabels, label_array); 
  current_reg_ptr = first_reg_ptr; }

/*-----------------------------------------------------------------------------*/
setup_default_regions()
{
  int i;
  struct region *prev_reg_ptr;
  
  prev_reg_ptr = NULL;
  if (nbars == 0) {fprintf(stderr, "audio file too short\n"); exit(STRANGE);}
  for (i = 0; i < nbars; i += (int) DEFAULT_REGION_LEN / MS_PER_PIXEL) {
    prev_reg_ptr = make_and_link_in(i, prev_reg_ptr, ""); } }


/*-----------------------------------------------------------------------------*/
setup_regions_from_labels(nlabels, label_array)
     int nlabels; struct whyp label_array[MAX_LABELS];
{ 
  int i, startpix, endpix, end_of_previous;
  struct region *prev_reg_ptr;

  end_of_previous = 0;   /* end of previous region */
  prev_reg_ptr = NULL;
  for (i = 0; i < nlabels; i++) {
    /* use this label as the description of a region */
    startpix = label_array[i].start / MS_PER_PIXEL;
    endpix = label_array[i].end / MS_PER_PIXEL;
    /* if this region does not abut the previous region, make an extra region */
    if (startpix != end_of_previous) {
      prev_reg_ptr = make_and_link_in(end_of_previous, prev_reg_ptr, "");
      end_of_previous = startpix; }
    prev_reg_ptr = make_and_link_in(startpix, prev_reg_ptr, label_array[i].label);
    end_of_previous = endpix;
  }
  if (end_of_previous  < nbars - 1 ) {
    (void) make_and_link_in(end_of_previous, prev_reg_ptr, ""); }
} 


/*=============================================================================*/
prepare_canvas()
{ sg_create(CANVAS_WIDTH, CANVAS_HEIGHT, 1, "labeler"); }

clean_up_canvas()
{ sg_close();}

/*-----------------------------------------------------------------------------*/
redraw_display()
{
  struct region *reg_ptr;

  sg_clear();
  display_filename();
  display_signal();
  for (reg_ptr = first_reg_ptr; reg_ptr != NULL; reg_ptr = reg_ptr->next_region) {
    do_boundary(reg_ptr->start, DRAW, FALSE);
    if (reg_ptr->label[0] != NULL)
      draw_label(reg_ptr->start, reg_ptr->label);}
  do_boundary(nbars - 1, DRAW, FALSE);
  decorate_boundary(DRAW); 
  mark_region(DRAW);
  print_help();
  if (read_in_progress) {
    echo_input_string();
    prompt_for_label_string(DRAW); } }

/*-----------------------------------------------------------------------------*/
display_filename()
{  char file_string[MAX_PATH_LEN + 10]; 
   sprintf(file_string, "file = %s", file_list[this_file]);
   sg_string(LEFT_MARGIN, FILENAME_YPOS, file_string); }

/*-----------------------------------------------------------------------------*/
display_signal()
{ int i;
  for (i = 0 ; i < nbars; i++) {
    draw_bar(i, bar_height[i]); }
  sg_flush(); }

/*-----------------------------------------------------------------------------*/
draw_bar(x, height)         int x, height;
{  sg_line(LEFT_MARGIN + x ,BASELINE, LEFT_MARGIN + x, BASELINE + height);}

/*-----------------------------------------------------------------------------*/
/* if draw is true, then draw, else erase */
do_boundary(where, draw, specialp)      int where, draw, specialp;
{
  int x, y;
  x = LEFT_MARGIN + where;
  /* draw a vertical bunch of dots */
  if(draw) {
    for (y = BOUNDARY_LINE_TOP; y < BOUNDARY_LINE_BOTTOM; y += 8)
      sg_pset(x,y); 
    if (specialp) {
      for (y = BOUNDARY_DECORATION_YPOS - 2; y < BOUNDARY_LINE_BOTTOM; y += 8)
	sg_pset_red(x,y); }
  }
  else {
    sg_clear_line(x, BOUNDARY_DECORATION_YPOS, x, BOUNDARY_LINE_BOTTOM);
    draw_bar(where, bar_height[where]);  } }

/*  ----------------------------------------------------------------------------- */
#define ARROW_LEN 10
#define ARROWHEAD_WIDTH 5

draw_right_arrow(x1, x2, y)       int x1, x2, y;
{  sg_line(x1, y, x2, y);
   sg_line(x2 - ARROWHEAD_WIDTH, y - ARROWHEAD_WIDTH, x2, y);
   sg_line(x2 - ARROWHEAD_WIDTH, y + ARROWHEAD_WIDTH, x2, y); }

draw_left_arrow(x1, x2, y)     int x1, x2, y;
{  sg_line(x1, y, x2, y);
   sg_line(x2 + ARROWHEAD_WIDTH, y - ARROWHEAD_WIDTH, x2, y);
   sg_line(x2 + ARROWHEAD_WIDTH, y + ARROWHEAD_WIDTH, x2, y); }

/*  ----------------------------------------------------------------------------- */
decorate_boundary(draw)         int draw;
{ 
  int x, y;
  x = LEFT_MARGIN + current_boundary();
  y = BOUNDARY_DECORATION_YPOS;
  if (draw) {
    draw_right_arrow(x, x + ARROW_LEN, y);
    draw_left_arrow(x, x - ARROW_LEN, y); 
    do_boundary(current_boundary(), DRAW, TRUE); }
  else {
    sg_erase_rect(x - ARROW_LEN, y - ARROWHEAD_WIDTH,
		  2 * ARROW_LEN + 1, 2 * ARROWHEAD_WIDTH + 1);
    do_boundary(current_boundary(), ERASE, FALSE); 
    do_boundary(current_boundary(), DRAW, FALSE);  }
}

/*-----------------------------------------------------------------------------*/
draw_notice(string)         char *string;
{ erase_notice();
  sg_string_red(NOTICE_X, NOTICE_Y, string);}

erase_notice()
{  sg_erase_rect(NOTICE_X, NOTICE_Y - FONT_HEIGHT, 1000, FONT_HEIGHT + 5); } 

/*-----------------------------------------------------------------------------*/
mark_region(draw)       int draw;
{
  int x1, x2;
  x1 = LEFT_MARGIN + current_region_start();
  x2 = LEFT_MARGIN + current_region_end();

  /* probably would look better stippled */
  if (draw)     sg_draw_rect(x1, REGION_DECORATION_YPOS, x2 - x1, 5);
  else         sg_erase_rect(x1, REGION_DECORATION_YPOS, x2 - x1, 5); }


/*=============================================================================*/
split_region()
{
  int new_boundary;
  struct region *new_reg_ptr;

  if (current_region_end() - current_region_start() < 2) {
    sg_bell();
    draw_notice("region too small to split"); }
  else {
    decorate_boundary(ERASE);
    mark_region(ERASE);
    /* splice in the new region */
    new_boundary = (current_region_end() + current_region_start()) / 2;
    do_boundary(new_boundary,DRAW, FALSE);

    new_reg_ptr = allocate_region();
    new_reg_ptr->prev_region = current_reg_ptr;
    new_reg_ptr->next_region = current_reg_ptr->next_region;
    new_reg_ptr->start = new_boundary;
    if (current_reg_ptr->next_region != NULL) {
      (current_reg_ptr->next_region)->prev_region = new_reg_ptr; }
    current_reg_ptr->next_region = new_reg_ptr;
    mark_region(DRAW);
    decorate_boundary(DRAW);
  } }

/*-----------------------------------------------------------------------------*/
merge_regions()
{
  struct region *next_reg_ptr;
  mark_region(ERASE);
  decorate_boundary(ERASE);            /* sometimes unnecessary */
  do_boundary(current_region_end(), ERASE, FALSE); 
  next_reg_ptr = current_reg_ptr->next_region;
  if (next_reg_ptr == NULL) {
    draw_notice("sorry, no more regions"); }
  else {
    current_reg_ptr->next_region = next_reg_ptr->next_region;
    if (next_reg_ptr->next_region != NULL) {
      (next_reg_ptr->next_region)->prev_region = current_reg_ptr; }
  }
  mark_region(DRAW);
  decorate_boundary(DRAW);
}

/*-----------------------------------------------------------------------------*/
toggle_boundary()    
{
  decorate_boundary(ERASE);

  if (active_boundary == LEFT && current_reg_ptr->next_region != NULL)
    active_boundary = RIGHT;
  else if (active_boundary == RIGHT && current_reg_ptr->prev_region != NULL)
    active_boundary = LEFT;
  else
    draw_notice("End boundaries not movable.  Do split perhaps");
  decorate_boundary(DRAW);
} 

/*-----------------------------------------------------------------------------*/
move_active_boundary(offset)  int offset;
{
  int tentative_new_pos;
  struct region *relevant_reg_ptr;

  if (active_boundary == LEFT && current_reg_ptr->start == 0 ||
      active_boundary == RIGHT && current_reg_ptr->next_region == NULL) {
    sg_bell();
    draw_notice("End boundaries not movable.  Do a split perhaps"); }
  else {
    if (active_boundary == LEFT) relevant_reg_ptr = current_reg_ptr;
    else  relevant_reg_ptr = current_reg_ptr->next_region;
    tentative_new_pos = relevant_reg_ptr->start + offset; 
    if (offset < 0  &&  tentative_new_pos <= (relevant_reg_ptr->prev_region)->start ||
	offset > 0  &&  tentative_new_pos >= next_region_start(relevant_reg_ptr)) {
      sg_bell();
      draw_notice("Cannot have zero length regions"); }
    else {
      decorate_boundary(ERASE);
      do_boundary(current_boundary(), ERASE, FALSE);
      relevant_reg_ptr->start = tentative_new_pos;   
      do_boundary(current_boundary(), DRAW, FALSE);
      decorate_boundary(DRAW);
      /* to save time, don't update active-region marker */
    } } }

/*-----------------------------------------------------------------------------*/
prompt_for_label_string(draw)       int  draw;
{
  int x1, width;
  x1 = current_region_start();
  width = current_region_end() - x1;

  if (draw) {
    draw_notice("enter label, return to end");
    sg_outline_rect(LEFT_MARGIN + x1, INPUT_YPOS - 5, width, FONT_HEIGHT + 3);  }
}

erase_input_prompt()
{  sg_clear_outline_rect(LEFT_MARGIN + current_region_start(), INPUT_YPOS - 5,
			 current_region_end() -  current_region_start(), FONT_HEIGHT + 3);}

echo_input_string()
{  
  sg_erase_rect(LEFT_MARGIN + current_region_start(), INPUT_YPOS - 1,
		LEFT_MARGIN + current_region_end()  , FONT_HEIGHT + 1);
  sg_string(LEFT_MARGIN + current_region_start() + 2, INPUT_YPOS + 6, string);
}

/*-----------------------------------------------------------------------------*/
draw_label(where, string)   int where; char *string;
{  sg_string(LEFT_MARGIN + where + 2, LABEL_YPOS, string); }

/*-----------------------------------------------------------------------------*/
edit_label()
{  read_in_progress = TRUE;
  /* erase old label */
  sg_erase_rect(LEFT_MARGIN + current_region_start(), LABEL_YPOS - FONT_HEIGHT, 
		current_region_start() - current_region_end(), FONT_HEIGHT + 2);
  prompt_for_label_string(DRAW);}

/*-----------------------------------------------------------------------------*/
store_label()
{
  read_in_progress = FALSE;
  strcpy(current_reg_ptr->label,string);
  string[0] = NULL;
  erase_input_prompt();
  draw_label(current_region_start(),current_reg_ptr->label);
  goto_next_region();       /* automatically go on to next region */
}

/*-----------------------------------------------------------------------------*/
goto_next_region()
{
  if (current_reg_ptr->next_region == NULL) {
    draw_notice("no more regions"); }
  else {
    decorate_boundary(ERASE);
    mark_region(ERASE);
    current_reg_ptr = current_reg_ptr->next_region;
    mark_region(DRAW);
    active_boundary = LEFT;
    decorate_boundary(DRAW);   } }

goto_prev_region()
{
  if (current_reg_ptr->prev_region == NULL)
    draw_notice("no more regions");
  else {
    decorate_boundary(ERASE);
    mark_region(ERASE);
    current_reg_ptr = current_reg_ptr->prev_region;
    mark_region(DRAW);
    active_boundary = RIGHT;
    decorate_boundary(DRAW);   } }

/*-----------------------------------------------------------------------------*/
open_next_file()
{ if (this_file + 1 >= nfiles) 
    draw_notice("no more files; q to exit, then restart labeler");
  else  {
    write_labels();
    this_file += 1;
    update_stuff(); } }

/*-----------------------------------------------------------------------------*/
open_previous_file()
{ if (this_file - 1 < 0)
    draw_notice("this is the first file, no previous ones");
  else {
    write_labels();
    this_file -= 1;
    update_stuff(); } }

/*-----------------------------------------------------------------------------*/
update_stuff()
{  read_audio_file();
   setup_labels();
   active_boundary = RIGHT;
   redraw_display(); }


/*=============================================================================*/
handle_command(string)          char* string;
{
  char cmd;   char msg_string[100];  

  erase_notice();    
  if (string == NULL) cmd = '?';
  else if (strcmp (string, "space") == 0) cmd = ' ';
  else if (strcmp (string, "semicolon") == 0) cmd = ';';
  else cmd = string[0];

  switch (cmd) {
  case 'j': case 'K': move_active_boundary(-10); break;
  case 'k':           move_active_boundary(-2); break;
  case 'l':           move_active_boundary( 2); break;
  case ';': case 'L': move_active_boundary(10); break;
  case 't': toggle_boundary(); break; 

  case ' ': play_active_region();    break;
  case 'p': play_whole_input();    break;
  case 'e': edit_label(); break;
  case 'f': goto_next_region(); break;
  case 'b': goto_prev_region(); break;
  case 's': split_region(); break;
  case 'm': merge_regions(); break;

  case 'w': write_labels(); break;
  case 'o': open_next_file(); break;
  case 'u': open_previous_file(); break;
  case 'r': redraw_display(); break;
  case 'q': write_labels(); clean_up_canvas(); exit(NORMAL); break;
  case 'A':                 clean_up_canvas(); exit(NORMAL); break;

  case '?': default:
    sprintf(msg_string, "unknown command `%c': see term window for command list", cmd);
    draw_notice(msg_string);
    print_help();
    break;
  }
}

/*-----------------------------------------------------------------------------*/
print_help()
{
  printf("Commands:  \n");
  printf("  j ,K - move active boundary 10 to left      \n");
  printf("     k - move active boundary  2 to left \n");
  printf("     l - move active boundary  2 to right \n");
  printf("  ; ,L - move active boundary 10 to right \n");
  printf("     t - toggle active boundary (left or right of region) \n");
  printf(" \n");
  printf(" space - play active region (to speaker or jack) \n");
  printf("     p - play entire file (to speaker or jack) \n");
  printf("     e - edit label for active region \n");
  printf("     f - move active region forward \n");
  printf("     b - move active region backward \n");
  printf("     s - split active region into two \n");
  printf("     m - merge active region with next region \n");
  printf(" \n");
  printf("     w - write labels \n");
  printf("     o - open next file (does w too) \n");
  printf("     u - reopen previous file \n");
  printf("     r - redraw display \n");
  printf("     q - quit (does w too) \n");
  printf("     A - abort \n"); 
}


/*-----------------------------------------------------------------------------*/
event_loop()
{
  KeySym keysym;
  static char buffer[MAX_WORD_LEN];
  XComposeStatus compose;
  int length;

  /* event loop code taken from Nye, volume 2 pg 72 */
  XSelectInput(display, win,
	       ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask);

  while (TRUE) {
    XNextEvent(display, &report);
    switch (report.type) {
    case Expose: 
      if (report.xexpose.count != 0)
	break;
      redraw_display();
      break;
    case KeyPress:
      (void) XLookupString( (XKeyEvent *) &report, buffer,
				MAX_WORD_LEN, &keysym, &compose);
      /* printf ("ks=`%s'\n ", XKeysymToString(keysym)); */
      if (!read_in_progress) {
	/* it's a command */
	handle_command(XKeysymToString(keysym));
	break; }
      else { /* it's part of the label string */ /* code from Nye pg 270 */
	if ((keysym == XK_Return)    || (keysym  == XK_KP_Enter) ||
	    (keysym ==  XK_Linefeed) || (keysym == XK_Escape)) {
	  erase_notice();   /* remove the "input string" notice */
	  store_label();
	  break;}
	else if (((keysym >= XK_KP_Space) && (keysym <=  XK_KP_9)) ||
		 ((keysym >= XK_space) && (keysym <=  XK_asciitilde))) {
	  if ((strlen(string) + strlen(buffer)) >= MAX_WORD_LEN)
	    draw_notice("word label too long; subsequent characters ignored");
	  else {
	    strcat(string, buffer);
	    echo_input_string(); } }
	else if ((keysym == XK_BackSpace) || (keysym == XK_Delete)) {
	  if ((length = strlen(string)) > 0) {
	    string[length - 1] = NULL;
	    echo_input_string(); }
	  else sg_bell();}	    
	else {printf("keysym %s is not handled \n", XKeysymToString(keysym));}
	break;
      } } }
}

/*-----------------------------------------------------------------------------*/

main(argc,argv)      int argc;      char *argv[];
{
  int i;
  nfiles = argc - 1;
  if (nfiles == 0) {
    fprintf(stderr, "please specify one or more .au files\n");
    exit(STRANGE);}
  for (i = 0; i < nfiles; i++) {
    strcpy(file_list[i], argv[i+1]);
    printf("file_list[%d] = %s \n", i, file_list[i]);   }
  this_file = 0;
  prepare_canvas();
  update_stuff();
  event_loop(); 
  /* not reached */
  exit(NORMAL); }

/*-----------------------------------------------------------------------------*/

