/*
 * lineup.c
 *
 * (C) Jack Whitham 2009
 * 
 * Compile on Linux x86_64 using:
 * gcc -o lineup lineup.c -Wall -g -O3 -lSDL -lSDL_mixer -lSDL_gfx
 * (Not tested on other OSes or CPU architectures; should work wherever
 * SDL works, but may require a 32-bit X display.)
 * 
 * Command line usage:
 * ./lineup <audio file 1.wav> <audio file 2.wav> <output file>
 *
 * Keys:
 * 0-9          set contrast level
 * Arrow keys   move around the dot plot
 * Backspace    delete last waypoint
 * z/a          zoom in/out
 * x            play audio clip in X direction
 * y            play audio clip in Y direction
 * Escape       exit
 * 
 * Mouse:
 * Left mouse   1st time - enter place mode (maximum zoom)
 *              2nd time - place waypoint
 * Right mouse  move viewpoint (a la Google Maps)
 *              or exit place mode without placing
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_gfxPrimitives.h>

#define SAMPLE_RATE         44100
#define N_SAMPLE_RATE       32768
#define BYTES_PER_SAMPLE    2
#define STEREO              1
#define WINDOW_SIZE         1024
#define LOOKUP_TABLE_SIZE   64
#define MULTIPLIER          5
#define INITIAL_ZOOM        ( N_SAMPLE_RATE * 1 )
#define PLACE_ZOOM          ( N_SAMPLE_RATE / 16 ) 
#define MINIMUM_ZOOM        ( N_SAMPLE_RATE / 8 ) /* 1/8th of a second */
#define MAXIMUM_ZOOM        ( N_SAMPLE_RATE * 256 ) /* 256 seconds */

typedef struct Point_List_Item_struct {
            struct Point_List_Item_struct * next ;
            int x , y ;
            } Point_List_Item ;

void Scale_Samples ( FILE * fd , int sample , 
            int num_samples , int * data , int samples_visible ) ;
void Draw_Data ( uint8_t * row ,
            int * x_data , int y_item , 
            uint32_t * lookup_table , 
            SDL_PixelFormat * format ,
            int contrast_level ) ;
int Screen_To_Sample ( int screen , int centre_sample , int samples_visible ) ;
int Sample_To_Screen ( int x_sample , int y_sample ,
            int x_centre_sample , int y_centre_sample , 
            int * x_scr , int * y_scr , int samples_visible ) ;
void Delete_Point ( Point_List_Item ** point_list ) ;
int Add_Point ( Point_List_Item ** point_list , int x , int y , int check ) ;
Mix_Chunk * Play_Clip ( FILE * fd , int sample ,
            int num_samples , int samples_visible , int channel ) ;
uint32_t Lookup_Colour ( int x_item , int y_item , 
            uint32_t * lookup_table , int contrast_level ) ;


int main ( int argc , char ** argv )
{
    FILE *              x_fd ;
    FILE *              y_fd ;
    int                 x_num_samples , y_num_samples ;
    int                 x_sample , y_sample ;
    int                 save_x_sample = 0 , save_y_sample = 0 ;
    int                 samples_visible = INITIAL_ZOOM ;
    int                 draw_samples_visible = INITIAL_ZOOM ;
    SDL_Rect            mouse_horiz_rect ;
    SDL_Rect            mouse_vert_rect ;
    SDL_Surface *       window ;
    SDL_Surface *       mouse_horiz_data ;
    SDL_Surface *       mouse_vert_data ;
    SDL_PixelFormat *   pixel_format ;
    SDL_Event           ev ;
    Point_List_Item *   point_list = NULL ;
    Point_List_Item *   point_iter ;
    int                 mouse_visible = 0 ;
    int                 place_mode = 0 ;
    int                 click_x = 0 ;
    int                 click_y = 0 ;
    int                 refresh_data = 1 ;
    int                 run = 1 ;
    int                 mouse_down = 0 ;
    int                 save_points = 1 ;
    int                 i ;
    int                 contrast_level = 1 * MULTIPLIER ;
    uint32_t            red_colour ;
    uint32_t            lookup_table [ LOOKUP_TABLE_SIZE ] ;
    const char *        points_name ;
    Mix_Chunk *         chunk [ 2 ] = { NULL , NULL } ;
    FILE *              points_fd ;

    if ( argc != 4 )
    {
        fprintf ( stderr , "Expected parameters: two audio files, "
                "one output file.\n" ) ;
        return 1 ;
    }
    x_fd = fopen ( argv [ 1 ] , "rb" ) ;
    y_fd = fopen ( argv [ 2 ] , "rb" ) ;
    points_name = argv [ 3 ] ;
    if (( x_fd == NULL )
    || ( y_fd == NULL ))
    {
        fprintf ( stderr , "Unable to open both files.\n" ) ;
        return 1 ;
    }
    fseek ( x_fd , 0 , SEEK_END ) ;
    fseek ( y_fd , 0 , SEEK_END ) ;
    x_num_samples = ftell ( x_fd ) / BYTES_PER_SAMPLE ;
    y_num_samples = ftell ( y_fd ) / BYTES_PER_SAMPLE ;
    x_sample = samples_visible / 2 ;
    y_sample = samples_visible / 2 ;

    points_fd = fopen ( points_name , "rt" ) ;
    if ( points_fd != NULL )
    {
        int x , y ;
        Point_List_Item * temp = NULL ;

        /* Read list */
        while ( fscanf ( points_fd , "%d %d\n" , & x , & y ) == 2 )
        {
            Add_Point ( & temp , x , y , 0 ) ;
        }
        fclose ( points_fd ) ;

        /* Reverse list */
        while ( temp != NULL )
        {
            x = temp -> x ;
            y = temp -> y ;
            Delete_Point ( & temp ) ;
            Add_Point ( & point_list , x , y , 1 ) ;
        }

        if ( point_list == NULL )
        {
            fprintf ( stderr , "No points in '%s'?\n" , points_name ) ;
            return 1 ;
        } else {
            x_sample = point_list -> x ;
            y_sample = point_list -> y ;
        }
    }
    
    if ( SDL_Init ( SDL_INIT_AUDIO | SDL_INIT_VIDEO ) != 0 )
    {
        fprintf ( stderr , "SDL_Init failed.\n" ) ; return 1 ;
    }
    window = SDL_SetVideoMode ( WINDOW_SIZE , WINDOW_SIZE , 0 , 0 ) ;
    if ( window == NULL )
    {
        fprintf ( stderr , "SDL_SetVideoMode failed.\n" ) ;
        SDL_Quit () ;
        return 1 ;
    }
    if ( Mix_OpenAudio ( SAMPLE_RATE , AUDIO_S16 , 
            STEREO ? 2 : 1 , 4096 ) < 0 )
    {
        fprintf ( stderr , "Mix_OpenAudio failed.\n" ) ;
        SDL_Quit () ;
        return 1 ;
    }
    pixel_format = window -> format ;
    for ( i = 0 ; i < LOOKUP_TABLE_SIZE ; i ++ )
    {
        int j = ( 255 - (( i * 255 ) / ( LOOKUP_TABLE_SIZE - 1 ))) ;
        lookup_table [ i ] = SDL_MapRGB ( pixel_format , j , j , j ) ;
    }
    red_colour = SDL_MapRGB ( pixel_format , 255 , 0 , 0 ) ;
    mouse_horiz_data = SDL_CreateRGBSurface ( 0 ,
            WINDOW_SIZE , 1 , 
            pixel_format -> BitsPerPixel ,
            pixel_format -> Rmask ,
            pixel_format -> Gmask ,
            pixel_format -> Bmask ,
            pixel_format -> Amask ) ;
    mouse_vert_data = SDL_CreateRGBSurface ( 0 ,
            1 , WINDOW_SIZE , 
            pixel_format -> BitsPerPixel ,
            pixel_format -> Rmask ,
            pixel_format -> Gmask ,
            pixel_format -> Bmask ,
            pixel_format -> Amask ) ;

    while ( run )
    {
        draw_samples_visible = place_mode ? PLACE_ZOOM : samples_visible ;

        if ( refresh_data )
        {
            uint8_t *           pixels ;
            int                 x , y , x0 , y0 ;
            int                 x_data [ WINDOW_SIZE ] ;
            int                 y_data [ WINDOW_SIZE ] ;
            
            Scale_Samples ( x_fd , x_sample , 
                        x_num_samples , x_data , draw_samples_visible ) ;
            Scale_Samples ( y_fd , y_sample , 
                        y_num_samples , y_data , draw_samples_visible ) ;
            SDL_LockSurface ( window ) ;
            pixels = (uint8_t *) window -> pixels ;
            for ( y = 0 ; y < WINDOW_SIZE ; y ++ )
            {
                Draw_Data ( & pixels [ ((int) window -> pitch ) * y ] ,
                        x_data , y_data [ y ] , lookup_table , 
                        pixel_format , contrast_level ) ;
            }
            SDL_UnlockSurface ( window ) ;

            point_iter = point_list ;
            x = y = -1 ;
            while ( point_iter != NULL )
            {
                if ( Sample_To_Screen ( point_iter -> x ,
                            point_iter -> y ,
                            x_sample , y_sample , 
                            & x0 , & y0 , draw_samples_visible ) )
                {
                    lineRGBA ( window , x0 + 40 , y0 , x0 - 40 , y0 ,
                                    0 , 255 , 0 , 127 ) ;
                    lineRGBA ( window , x0 , y0 - 40 , x0 , y0 + 40 ,
                                    0 , 255 , 0 , 127 ) ;
                    filledCircleRGBA ( window , x0 , y0 , 
                                    3 , 0 , 200 , 0 , 127 ) ;
                    if ( x >= 0 )
                    {
                        lineRGBA ( window , x0 , y0 , x , y , 
                                    255 , 255 , 0 , 255 ) ;
                    }
                    x = x0 ;
                    y = y0 ;
                } else {
                    x = y = -1;
                }
                point_iter = point_iter -> next ;
            }
            if ( point_list != NULL )
            {
                for ( i = 20 ; i <= 60 ; i += 20 )
                {
                    int p1 = ( i * SAMPLE_RATE * ( STEREO ? 2 : 1 )) ;
                    int p2 = (( i + 1 ) * SAMPLE_RATE * ( STEREO ? 2 : 1 )) ;

                    if (( Sample_To_Screen ( point_list -> x + p1 ,
                                point_list -> y + p1 ,
                                x_sample , y_sample , 
                                & x0 , & y0 , draw_samples_visible ) )
                    && ( Sample_To_Screen ( point_list -> x + p2 ,
                                point_list -> y + p2 ,
                                x_sample , y_sample , 
                                & x , & y , draw_samples_visible ) ))
                    {
                        rectangleRGBA ( window , x0 , y0 , x , y , 
                                0 , 255 , 255 , 255 ) ;
                    }
                }
            }
            mouse_visible = 0 ;
            refresh_data = 0 ;
        }
        SDL_Flip ( window ) ;

        for ( i = 0 ; i < 2 ; i ++ )
        {
            if ( chunk [ i ] != NULL )
            {
                if ( ! Mix_Playing ( i ) )
                {
                    uint8_t * buf = NULL ;
                    if ( chunk [ i ] -> allocated )
                    {
                        buf = chunk [ i ] -> abuf ;
                    }
                    Mix_FreeChunk ( chunk [ i ] ) ;
                    if ( buf != NULL )
                    {
                        free ( buf ) ;
                    }
                    chunk [ i ] = NULL ;
                }
            }
        }

        ev . type = SDL_USEREVENT;
        SDL_WaitEvent ( & ev ) ;
        do {
            switch ( ev . type )
            {
                case SDL_MOUSEMOTION :
                    if ( mouse_visible )
                    {
                        SDL_BlitSurface ( mouse_horiz_data , NULL ,
                                    window , & mouse_horiz_rect ) ;
                        SDL_BlitSurface ( mouse_vert_data , NULL ,
                                    window , & mouse_vert_rect ) ;
                    }
                    mouse_horiz_rect . x = 0 ;
                    mouse_horiz_rect . y = ev . motion . y ;
                    mouse_horiz_rect . w = WINDOW_SIZE ;
                    mouse_horiz_rect . h = 1 ;
                    mouse_vert_rect . x = ev . motion . x ;
                    mouse_vert_rect . y = 0 ;
                    mouse_vert_rect . w = 1 ;
                    mouse_vert_rect . h = WINDOW_SIZE ;
                    SDL_BlitSurface ( window , & mouse_horiz_rect ,
                                mouse_horiz_data , NULL ) ;
                    SDL_BlitSurface ( window , & mouse_vert_rect ,
                                mouse_vert_data , NULL ) ;
                    SDL_FillRect ( window , & mouse_horiz_rect ,
                                red_colour ) ;
                    SDL_FillRect ( window , & mouse_vert_rect ,
                                red_colour ) ;
                    if ( mouse_down 
                    && (( click_x != ev . motion . x )
                        || ( click_y != ev . motion . y )))
                    {
                        int x = (( click_x - ev . motion . x ) * 
                                samples_visible ) / WINDOW_SIZE ;
                        int y = (( click_y - ev . motion . y ) * 
                                samples_visible ) / WINDOW_SIZE ;
                        x_sample += x ;
                        y_sample += y ;
                        click_x = ev . motion . x ;
                        click_y = ev . motion . y ;
                        refresh_data = 1 ;
                    }
                    mouse_visible = 1 ;
                    break ;
                case SDL_MOUSEBUTTONDOWN :
                    switch ( ev . button . button )
                    {
                        case 1 :
                            if (( place_mode )
                            && ( Add_Point ( & point_list ,
                                Screen_To_Sample ( ev . button . x ,
                                    x_sample , draw_samples_visible ) ,
                                Screen_To_Sample ( ev . button . y ,
                                    y_sample , draw_samples_visible ) , 1 ) ))
                            {
                                save_points = 1 ;
                                place_mode = 0 ;
                                refresh_data = 1 ;
                            } else {
                                click_x = ev . button . x ;
                                click_y = ev . button . y ;
                                mouse_down = 1 ;
                            }
                            break ;
                        case 2 :
                        case 3 :
                            if ( ! place_mode )
                            {
                                place_mode = 1 ;
                                refresh_data = 1 ;
                                save_x_sample = x_sample ;
                                save_y_sample = y_sample ;
                                x_sample = Screen_To_Sample ( ev . button . x ,
                                        x_sample , draw_samples_visible ) ;
                                y_sample = Screen_To_Sample ( ev . button . y ,
                                        y_sample , draw_samples_visible ) ;
                            } else {
                                place_mode = 0 ;
                                refresh_data = 1 ;
                                x_sample = save_x_sample ;
                                y_sample = save_y_sample ;
                            }
                            break ;
                        default :
                            break ;
                    } 
                    break ;
                case SDL_MOUSEBUTTONUP :
                    switch ( ev . button . button )
                    {
                        case 1 :
                            mouse_down = 0 ;
                            break ;
                        case 2 :
                        case 3 :
                            break ;
                        default :
                            break ;
                    }
                    break ;
                case SDL_KEYDOWN :
                    switch ( ev . key . keysym . sym )
                    {
                        case SDLK_1 :
                            contrast_level = 1 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_2 :
                            contrast_level = 2 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_3 :
                            contrast_level = 3 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_4 :
                            contrast_level = 4 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_5 :
                            contrast_level = 5 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_6 :
                            contrast_level = 6 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_7 :
                            contrast_level = 7 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_8 :
                            contrast_level = 8 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_9 :
                            contrast_level = 9 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_0 :
                            contrast_level = 10 * MULTIPLIER ; refresh_data = 1 ;
                            break ;
                        case SDLK_LEFT :
                            x_sample -= samples_visible / 10 ; 
                            refresh_data = 1 ;
                            break ;
                        case SDLK_UP :
                            y_sample -= samples_visible / 10 ; 
                            refresh_data = 1 ;
                            break ;
                        case SDLK_RIGHT :
                            x_sample += samples_visible / 10 ; 
                            refresh_data = 1 ;
                            break ;
                        case SDLK_DOWN :
                            y_sample += samples_visible / 10 ; 
                            refresh_data = 1 ;
                            break ;
                        case SDLK_BACKSPACE :
                            Delete_Point ( & point_list ) ;
                            if ( point_list != NULL )
                            {
                                x_sample = point_list -> x ;
                                y_sample = point_list -> y ;
                            }
                            save_points = 1 ;
                            refresh_data = 1 ;
                            break ;
                        case SDLK_z :
                            samples_visible /= 2 ;
                            refresh_data = 1 ;
                            if ( samples_visible < MINIMUM_ZOOM )
                            {
                                samples_visible = MINIMUM_ZOOM ;
                            } else {
                                int x , y ;
                                int mouse_x , mouse_y ;
                                SDL_GetMouseState ( & x , & y ) ;
                                // get fixed point:
                                mouse_x = Screen_To_Sample ( x ,
                                        x_sample , draw_samples_visible ) ;
                                mouse_y = Screen_To_Sample ( y ,
                                        y_sample , draw_samples_visible ) ;
                                // zoom in around mouse_x, mouse_y
                                x_sample += (( mouse_x - x_sample ) / 2 ) ;
                                y_sample += (( mouse_y - y_sample ) / 2 ) ;
                            }
                            break ;
                        case SDLK_a :
                            samples_visible *= 2 ;
                            refresh_data = 1 ;
                            if ( samples_visible > MAXIMUM_ZOOM )
                                samples_visible = MAXIMUM_ZOOM ;

                            break ;
                        case SDLK_x :
                            if ( chunk [ 0 ] == NULL )
                            {
                                chunk [ 0 ] = Play_Clip ( x_fd , x_sample , 
                                        x_num_samples , draw_samples_visible , 0 ) ;
                            }
                            break ;
                        case SDLK_y :
                            if ( chunk [ 1 ] == NULL )
                            {   
                                chunk [ 1 ] = Play_Clip ( y_fd , y_sample , 
                                        y_num_samples , draw_samples_visible , 1 ) ;
                            }
                            break ;
                        case SDLK_ESCAPE :
                            run = 0 ;
                            break ;
                        default :
                            break ;
                    }
                    break ;
                case SDL_QUIT :
                    run = 0 ;
                    break ;
                default :
                    break ;
            }
        } while ( run && SDL_PollEvent ( & ev )) ;

        if ( save_points )
        {
            points_fd = fopen ( points_name , "wt" ) ;
            assert ( points_fd != NULL ) ;
            point_iter = point_list ;
            while ( point_iter != NULL )
            {
                fprintf ( points_fd , "%d %d\n" ,
                    point_iter -> x , point_iter -> y ) ;
                point_iter = point_iter -> next ;
            }
            fclose ( points_fd ) ;
            save_points = 0 ;
        }
        if ( x_sample < 0 )
        {
            x_sample = 0 ; refresh_data = 1 ;
        }
        if ( y_sample < 0 )
        {
            y_sample = 0 ; refresh_data = 1 ;
        }
        if ( x_sample >= x_num_samples )
        {
            x_sample = x_num_samples - 1 ; refresh_data = 1 ;
        }
        if ( y_sample >= y_num_samples )
        {
            y_sample = y_num_samples - 1; refresh_data = 1 ;
        }
    }
    SDL_Quit () ;
    return 0 ;
}

Mix_Chunk * Play_Clip ( FILE * fd , int sample ,
            int num_samples , int samples_visible ,
            int channel )
{
    int         cur_sample = sample - ( samples_visible / 2 ) ;
    int         samples_read ;
    uint8_t *   block = malloc ( samples_visible * BYTES_PER_SAMPLE ) ;
    Mix_Chunk * chunk ;

    assert ( block != NULL ) ;
    if ( cur_sample < 0 )
    {
        fseek ( fd , 0 , SEEK_SET ) ;
    } else if ( cur_sample < num_samples )
    {
        fseek ( fd , ( cur_sample * BYTES_PER_SAMPLE ) & ~3 , SEEK_SET ) ;
    }
    samples_read = fread ( block , 
            BYTES_PER_SAMPLE , samples_visible , fd ) ;
    if ( samples_read <= 0 )
    {
        return NULL ;
    }
    /* Assume this makes a deep copy */
    chunk = Mix_QuickLoad_RAW ( block , samples_read * BYTES_PER_SAMPLE ) ;
    if ( chunk == NULL )
    {
        return NULL ;
    }
    Mix_PlayChannel ( channel , chunk , 0 ) ;
    return chunk ;
}

void Scale_Samples ( FILE * fd , int sample , 
            int num_samples , int * data , int samples_visible )
{
    int         samples_per_pixel = samples_visible / WINDOW_SIZE ;
    int         true_samples_visible = samples_per_pixel * WINDOW_SIZE ;
    int         j , i = 0 ;
    int         space = 0 ;
    int         cur_sample = sample - ( true_samples_visible / 2 ) ;
    int16_t *   block ;

    cur_sample = ( cur_sample / samples_per_pixel ) * samples_per_pixel ;

    if ( cur_sample < 0 )
    {
        fseek ( fd , 0 , SEEK_SET ) ;
    } else if ( cur_sample < num_samples )
    {
        fseek ( fd , cur_sample * BYTES_PER_SAMPLE , SEEK_SET ) ;
    }

    /* left hand side - blank space? */
    if ( cur_sample < 0 )
    {
        space = -cur_sample / samples_per_pixel ;
        for ( i = 0 ; ( i < space ) && ( i < WINDOW_SIZE ) ; i ++ )
        {
            data [ i ] = -1 ;
        }
        cur_sample = 0 ;
    }

    /* right hand side - blank space? */
    space = ( cur_sample + true_samples_visible ) - num_samples ;
    if ( space < 0 ) space = 0 ;

    /* middle - data */
    block = alloca ( samples_per_pixel * BYTES_PER_SAMPLE ) ;
    for ( ; i < ( WINDOW_SIZE - space ) ; i ++ )
    {
        int total = 0 ;

        fread ( block , samples_per_pixel * BYTES_PER_SAMPLE , 1 , fd ) ;
        for ( j = 0 ; j < samples_per_pixel ; j ++ )
        {
            total += abs ( (int) block [ j ] ) ; 
        }
        total /= samples_per_pixel ;
        data [ i ] = total ;
    }

    /* right - blank space */
    for ( ; i < WINDOW_SIZE ; i ++ )
    {
        data [ i ] = -1 ;
    }
}


void Draw_Data ( uint8_t * row ,
            int * x_data , int y_item , 
            uint32_t * lookup_table , 
            SDL_PixelFormat * format ,
            int contrast_level )
{
    int     i = 0 ;
    int     x ;

    switch ( format -> BytesPerPixel )
    {
        case 1 :    for ( x = 0 ; x < WINDOW_SIZE ; x ++ )
                    {
                        uint32_t v = Lookup_Colour ( x_data [ x ] , y_item ,
                                lookup_table , contrast_level ) ;
                        ((uint8_t *) row) [ x ] = (uint32_t) v ;
                    }
                    break ;
        case 2 :    for ( x = 0 ; x < WINDOW_SIZE ; x ++ )
                    {
                        uint32_t v = Lookup_Colour ( x_data [ x ] , y_item ,
                                lookup_table , contrast_level ) ;
                        ((uint16_t *) row) [ x ] = (uint16_t) v ;
                    }
                    break ;
        case 3 :    for ( x = 0 ; x < WINDOW_SIZE ; x ++ )
                    {
                        uint32_t v = Lookup_Colour ( x_data [ x ] , y_item ,
                                lookup_table , contrast_level ) ;
                        row [ i + ( format -> Rshift / 8 ) ] = 
                                    v >> format -> Rshift ;
                        row [ i + ( format -> Gshift / 8 ) ] = 
                                    v >> format -> Gshift ;
                        row [ i + ( format -> Bshift / 8 ) ] = 
                                    v >> format -> Bshift ;
                        i += 3 ;
                    }
                    break ;
        case 4 :    for ( x = 0 ; x < WINDOW_SIZE ; x ++ )
                    {
                        uint32_t v = Lookup_Colour ( x_data [ x ] , y_item ,
                                lookup_table , contrast_level ) ;
                        ((uint32_t *) row) [ x ] = v ;
                    }
                    break ;
        default :   assert ( 0 ) ;
                    break ;
    }
}

int Screen_To_Sample ( int screen , int centre_sample , int samples_visible )
{
    return ( centre_sample - ( samples_visible / 2 )) +
        (( screen * samples_visible ) / WINDOW_SIZE ) ;
}


int Sample_To_Screen ( int x_sample , int y_sample ,
            int x_centre_sample , int y_centre_sample , 
            int * x_scr , int * y_scr , int samples_visible )
{
    int64_t x = (((int64_t) ( x_sample - x_centre_sample )) * WINDOW_SIZE ) / 
                ((int64_t) samples_visible ) + ( WINDOW_SIZE / 2 ) ;
    int64_t y = (((int64_t) ( y_sample - y_centre_sample )) * WINDOW_SIZE ) / 
                ((int64_t) samples_visible ) + ( WINDOW_SIZE / 2 ) ;
    if (( x < -0x8000 ) || ( y < -0x8000 )
    || ( x > 0x7fff ) || ( y > 0x7fff ))
    {
        return 0 ;
    }
    (* x_scr) = (int) x ;
    (* y_scr) = (int) y ;
    return 1 ;
}

int Add_Point ( Point_List_Item ** point_list , int x , int y , int check )
{
    Point_List_Item * new_item ;

    if (( (* point_list) != NULL )
    && ( check ))
    {
        if (( x <= (* point_list) -> x )
        || ( y <= (* point_list) -> y ))
        {
            /* no can do! */
            return 0 ;
        }
    }
    
    new_item = calloc ( 1 , sizeof ( Point_List_Item ) ) ;
    assert ( new_item != NULL ) ;
    new_item -> next = (* point_list) ;
    (* point_list) = new_item ;
    new_item -> x = x ;
    new_item -> y = y ;
    return 1 ;
}

void Delete_Point ( Point_List_Item ** point_list )
{
    Point_List_Item * d = (* point_list);

    if ( d != NULL )
    {
        (* point_list) = d -> next ;
        free ( d ) ;
    }
}

uint32_t Lookup_Colour ( int x_item , int y_item , 
            uint32_t * lookup_table , int contrast_level )
{
    unsigned    diff ;
    int         index = 0 ;

    if (( x_item < 0 ) || ( y_item < 0 ))
    {
        return lookup_table [ LOOKUP_TABLE_SIZE - 1 ] ;
    }
    diff = (unsigned) abs ( x_item - y_item ) ;

    index = diff / contrast_level ;
    if ( index >= LOOKUP_TABLE_SIZE ) index = LOOKUP_TABLE_SIZE - 1 ;
    index = ( LOOKUP_TABLE_SIZE - 1 ) - index ;

    /*if ( diff < 50 ) diff = 50 ;

    index = ( LOOKUP_TABLE_SIZE - 1 ) - (
        ( LOOKUP_TABLE_SIZE - 1 ) / ( diff / 50 )) ;*/
    return lookup_table [ index ] ;

/*
    while ( diff != 0 )
    {
        index ++ ;
        diff = ( diff * 3 ) / 4 ;
    }
    if ( index >= LOOKUP_TABLE_SIZE ) index = LOOKUP_TABLE_SIZE - 1 ;
    return lookup_table [ index ] ; */
}


