#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <sys/stat.h>
#include <unistd.h>

#include "views.h"

static int danceAVOpenAudioInput(DanceView *dv, DanceAudioTimeline *at);
static double danceAVFigureAmplify(DanceView *dv, char *filename);

/* Large chunks taken and modified from output.c in the ffmpeg distribution */
/*  sadly output.c uses deprecated functions so I don't know if it is */
/*  still the right way to do things */

static double danceMusicGuessCountsPerMinute(Dance *dance) {
    MusicSegment *ms, *main;
    int time, bars, counts;

    if ( dance->music.filename==NULL )
return( 0 );

    ms = dance->music.minorset;
    if ( ms==NULL )
	ms = dance->music.body;
    main = ms;
    time = bars = 0;
    while ( ms!=NULL ) {
	if ( ms->duration_millibars!=0 ) {
	    time += ms->end - ms->start;
	    bars += ms->duration_millibars;
	}
	ms = ms->next;
    }
    if ( time==0 )
return( 0 );

    dance->music.tunecnt_per_minorset = 1;
    if ( dance->music.minorset!=NULL ) {
	if ( main->repeat_as_needed ) {
	    if ( danceMinorSetMillibarCount(dance)%bars==0 )
		dance->music.tunecnt_per_minorset = danceMinorSetMillibarCount(dance)/bars;
	    else
		fprintf( stderr, "%s: Music minorset length %.1f (in bars) does not divide dance length %.1f\n", dance->name,
			bars/MILLIBARS_MUL, danceMinorSetMillibarCount(dance)/MILLIBARS_MUL );
	} else if ( bars!=danceMinorSetMillibarCount(dance)) {
	    fprintf( stderr, "%s: Music minorset length %.1f (in bars) does not match dance length %.1f\n", dance->name,
		    bars/MILLIBARS_MUL, danceMinorSetMillibarCount(dance)/MILLIBARS_MUL );
exit(1);
	}
    }
    if ( dance->music.lastminorset!=NULL ) {
	int fsbars = 0;
	ms = dance->music.lastminorset;
	while ( ms!=NULL ) {
	    fsbars += ms->duration_millibars;
	    ms = ms->next;
	}
	if ( fsbars!=danceMinorSetMillibarCount(dance)) {
	    fprintf( stderr, "Music finalset length %.1f (in bars) does not match dance length %.1f\n",
		    bars/MILLIBARS_MUL, danceMinorSetMillibarCount(dance)/MILLIBARS_MUL );
exit(1);
	}
    }
    counts = bars * dance->counts_per_bar;

return( 60 * (double) counts / (double) time );
}

int danceAVFigureFramesPerSecBar(DanceView *dv, struct video_options *options) {
    Dance *dance = dv->dance;
    double cpm;

    cpm = dance->counts_per_min;
    if ( cpm==0 )
	cpm = danceMusicGuessCountsPerMinute(dance);
    if ( options->frames_per_sec==0 && options->frames_per_bar==0 && cpm!=0 ) {
	/* Find the frames_per_sec, frames_per_bar combination that best */
	/*  approximates counts_per_min */
	/* Look at 30 fps (NTSC), 25 (PAL) and 24 (film) assuming that everybody */
	/*  will support these now-a-days. */
	int fpb30, fpb25, fpb24;
	float err30, err25, err24;
	fpb30 = round( dance->counts_per_bar*60*30 / cpm );
	fpb25 = round( dance->counts_per_bar*60*25 / cpm );
	fpb24 = round( dance->counts_per_bar*60*24 / cpm );

	err30 = 120*30 / (double) fpb30 - cpm;
	if ( err30<0 ) err30 = -err30;
	err25 = 120*25 / (double) fpb25 - cpm;
	if ( err25<0 ) err25 = -err25;
	err24 = 120*24 / (double) fpb24 - cpm;
	if ( err24<0 ) err24 = -err24;
	/* For all that I have a preference for 30 fps */
	if ( err30<=1.5*err25 && err30<=1.5*err24 ) {
	    dv->frames_per_sec = 30;
	    dv->frames_per_bar = fpb30;
	} else if ( err24<=err25 && err24<1.5*err30 ) {
	    dv->frames_per_sec = 24;
	    dv->frames_per_bar = fpb24;
	} else if ( err25<err24 && err25<1.5*err30 ) {
	    dv->frames_per_sec = 25;
	    dv->frames_per_bar = fpb25;
	} else {
	    /* I don't think it is possible to get here... but just in case */
	    dv->frames_per_sec = 30;
	    dv->frames_per_bar = fpb30;
	}
    } else if ( cpm!=0 && options->frames_per_sec!=0 &&
	    (options->frames_per_bar==0 ||
	     (options->flags&(1L<<df_test)) )) {
	dv->frames_per_sec = options->frames_per_sec;
	dv->frames_per_bar = round( 60*options->frames_per_sec * dance->counts_per_bar / cpm );
    } else {
	if ( options->frames_per_sec==0 )
	    dv->frames_per_sec = 30;
	else
	    dv->frames_per_sec  = options->frames_per_sec;
	if ( options->frames_per_bar != 0 /* && !(options->flags&(1L<<df_test))*/ )
	    dv->frames_per_bar = options->frames_per_bar;
	else
	    dv->frames_per_bar = dv->frames_per_sec * dance->counts_per_bar / 2;
    }
    dv->original_frames_per_bar = dv->frames_per_bar;
    if ( cpm!=0 && !dv->shush )
	printf( "fps=%d, fpb=%d counts per min=%.2f, desired=%.2f\n", dv->frames_per_sec, dv->frames_per_bar, 60*dance->counts_per_bar*dv->frames_per_sec/(double)dv->frames_per_bar, cpm );
return( (int) round( 60*dance->counts_per_bar*dv->frames_per_sec/(double)dv->frames_per_bar ));
}

static double danceMusicBarTimeRatio(Dance *dance) {
    MusicSegment *ms;
    int time, bars;

    if ( dance->music.filename==NULL )
return( 0 );

    ms = dance->music.minorset;
    if ( ms==NULL )
	ms = dance->music.body;
    time = bars = 0;
    while ( ms!=NULL ) {
	if ( ms->duration_millibars!=0 ) {
	    time += ms->end - ms->start;
	    bars += ms->duration_millibars;
	}
	ms = ms->next;
    }
    if ( time==0 )
return( 0 );

return( bars/(double) time );
}

static double danceMusicLastBarTimeRatio(Dance *dance) {
    MusicSegment *ms;
    int time, bars;

    if ( dance->music.filename==NULL )
return( 0 );

    ms = dance->music.lastminorset;
    if ( ms==NULL )
	ms = dance->music.body;
    time = bars = 0;
    while ( ms!=NULL ) {
	if ( ms->duration_millibars!=0 ) {
	    time += ms->end - ms->start;
	    bars += ms->duration_millibars;
	}
	ms = ms->next;
    }
    if ( time==0 )
return( 0 );

return( bars/(double) time );
}

static int danceHasAudio(Dance *dance) {
    int i;

    if ( dance->titleAudioFilename!=NULL && *dance->titleAudioFilename!='\0' )
return( true );
    if ( dance->music.filename!=NULL )
return( true );
    for ( i=0; i<dance->call_cnt; ++i )
	if ( dance->calls[i].audioFilename!=NULL && *dance->calls[i].audioFilename!='\0' )
return( true );
    if ( dance->sequence!=NULL ) {
	for ( i=0; i<dance->choreo_cnt; ++i )
	    if ( danceHasAudio(dance->sequence[i].figure))
return( true );
    }

return( false );
}

/******************************************************************************/
/******************************************************************************/
/*                          Figure out Timelines                              */
/******************************************************************************/
/******************************************************************************/

#define CACHE_MAX	20
static struct cache { char *filename; int sample_rate, samples; } cache[CACHE_MAX];
static struct ampcache { char *filename; double amplify; } ampcache[CACHE_MAX];

static void addToAudioCache(int pos, char *filename, int rate, int samples) {
    if ( pos>=CACHE_MAX )
return;
    cache[pos].filename    = strdup(filename);
    cache[pos].sample_rate = rate;
    cache[pos].samples     = samples;
}

static void addToAmplifyCache(int pos, char *filename, double amplify) {
    if ( pos>=CACHE_MAX )
return;
    ampcache[pos].filename = strdup(filename);
    ampcache[pos].amplify  = amplify;
}

static void clearAudioCache(void) {
    int i;

    for ( i=0; i<CACHE_MAX && cache[i].filename!=NULL; ++i )
	free(cache[i].filename);
    memset(cache,0,sizeof(cache));
    for ( i=0; i<CACHE_MAX && ampcache[i].filename!=NULL; ++i )
	free(ampcache[i].filename);
    memset(ampcache,0,sizeof(ampcache));
}

static double audioFileSeconds(char *filename,int desired_audio_sample_rate,int *samples44k) {
    int i,j;
    AVFormatContext *avContext;
    AVCodecParameters *codecParams;
    AVCodec *codec;
    AVCodecContext *codecContext;
    AVStream *avStream;

    if ( filename==NULL || *filename=='\0' ) {
	*samples44k = 0;
return( 0 );
    }

    av_log_set_level(AV_LOG_ERROR);	/* libav talks too much. This should quiet it down a bit */

    for ( i=0; i<CACHE_MAX && cache[i].filename!=NULL; ++i ) {
	if ( strcmp(filename,cache[i].filename)==0 ) {
	    if ( cache[i].sample_rate == desired_audio_sample_rate ) {
		*samples44k = cache[i].samples;
	    } else if ( cache[i].sample_rate!=0 ) {
		*samples44k = (cache[i].samples * desired_audio_sample_rate)/ cache[i].sample_rate;
	    } else
		*samples44k = 0;
return( *samples44k / (double) desired_audio_sample_rate );
	}
    }

    *samples44k = 0;
    if ( access(filename,R_OK)==-1 ) {
	fprintf( stderr, "Cannot read audio file %s\n", filename );
	addToAudioCache(i,filename,0,0);
return( 0 );
    }


    avContext = avformat_alloc_context();
    if ( avContext==NULL ) {
	fprintf( stderr, "Failed to allocate a format context (before even looking at %s).\n", filename );
exit(1);
    }
    if ( avformat_open_input(&avContext,filename,NULL,NULL) != 0 ) {
	fprintf( stderr, "Failed to open %s.\n", filename );
	addToAudioCache(i,filename,0,0);
return( 0 );
    }
    /* duration (in usec) is avContext->duration */

    if ( avformat_find_stream_info( avContext, NULL )<0 ) {
	fprintf( stderr, "Failed to gather stream info %s.\n", filename );
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	addToAudioCache(i,filename,0,0);
return( 0 );
    }

    codec       = NULL;
    codecParams = NULL;
    avStream    = NULL;
    /* Look for an audio stream */
    for ( j=0 ; j<avContext->nb_streams ; ++j ) {
	avStream = avContext->streams[j];
	codecParams = avStream->codecpar;
	if ( codecParams->codec_type != AVMEDIA_TYPE_AUDIO )
    continue;		/* Not interested in video today */
	codec = avcodec_find_decoder(codecParams->codec_id);
	if ( codec==NULL ) {
	    fprintf( stderr, "Warning: Failed to find a codec for stream %d of %s (ignored).\n", j, filename );
    continue;
	}
	if ( codecParams->format<AV_SAMPLE_FMT_U8 || codecParams->format>AV_SAMPLE_FMT_S64P ) {
	    fprintf( stderr, "Warning: Audio stream with an unsupported sample size in stream %d of %s (ignored).\n", j, filename );
	    codec = NULL;
    continue;
	}
    break;
    }
    if ( codec==NULL ) {
	fprintf( stderr, "Failed to find an audio stream in %s.\n", filename );
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	addToAudioCache(i,filename,0,0);
return( 0 );
    }

    codecContext = avcodec_alloc_context3(codec);
    if ( codecContext==NULL ) {
	fprintf( stderr, "Failed to allocate a codec context for audio in %s.\n", filename );
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	addToAudioCache(i,filename,0,0);
return( 0 );
    }
    if ( avcodec_parameters_to_context(codecContext,codecParams)<0 ) {
	fprintf( stderr, "Failed to copy codec parameters for audio in %s.\n", filename );
	avcodec_free_context(&codecContext);
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	addToAudioCache(i,filename,0,0);
return( 0 );
    }
    if ( avcodec_open2(codecContext,codec, NULL)<0 ) {
	fprintf( stderr, "Failed to open codec for audio in %s.\n", filename );
	avcodec_free_context(&codecContext);
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	addToAudioCache(i,filename,0,0);
return( 0 );
    }

    addToAudioCache(i,filename,codecParams->sample_rate,avStream->duration);
    *samples44k = avStream->duration * desired_audio_sample_rate / codecParams->sample_rate;

    avcodec_free_context(&codecContext);
    avformat_close_input(&avContext);
    avformat_free_context(avContext);
return( *samples44k / (double) desired_audio_sample_rate );
}

static void dvCheckCallLength(DanceView *dv, Dance *d ) {
    int i;
    double minorSetSeconds;
    double secondsSofar;
    int samples44k;

    if ( d==NULL )
	d = dv->dance;

    if ( d->comp_cnt!=0 ) {
	for ( i = 0; i<d->comp_cnt; ++i )
	    dvCheckCallLength(dv,d->components[i] );
return;
    }

    minorSetSeconds = danceMinorSetMillibarCount(d) *
	    dv->frames_per_bar /
	    (double) dv->frames_per_sec / MILLIBARS_MUL;
    secondsSofar = 0;
    for ( i = 0; i<d->call_cnt; ++i ) {
	CallList *cl = &d->calls[i];

	if ( !(cl->part_mask&(1|NOTLAST_MASK|NOTFIRST_MASK)) )
    continue;
	if ( cl->audioFilename==NULL || *cl->audioFilename=='\0' )
    continue;
	audioFileSeconds(cl->audioFilename,dv->audio_sample_rate,&samples44k);
	secondsSofar += samples44k / 44100.0;
    }
    if ( minorSetSeconds<secondsSofar )
	fprintf( stderr, "%s: Time taken to utter the calls (%.1f seconds) is longer than the minorset (%.1f)\n",
		d->name, secondsSofar, minorSetSeconds );    
}

void danceCheckCallLength(Dance *d ) {
    DanceView dv;
    struct video_options opt;

    memset(&dv,0,sizeof(dv));
    dv.dance = d;
    dv.audio_sample_rate = 44100;
    memset(&opt,0,sizeof(opt));
    danceAVFigureFramesPerSecBar(&dv,&opt);

    dvCheckCallLength(&dv,NULL);
}

static TimeLine *danceAddTimeline(DanceView *dv,TimeLine *last,int start, int end, enum timeline_type type) {
    TimeLine *tlCur;

    tlCur = calloc(1,sizeof(TimeLine));
    tlCur->start_frame = start;
    tlCur->end_frame = end;
    tlCur->timeline_type = type;
    if ( last==NULL )
	dv->timeline = tlCur;
    else
	last->next = tlCur;
return( tlCur );
}

static AudioTimelineChunk *danceAddAudioChunk(DanceView *dv,int stream,
	AudioTimelineChunk *last,long start, int samples44k, char *filename ) {
    AudioTimelineChunk *asCur;

    if ( dv->audio_sample_rate==0 )
return( 0 );

    asCur = calloc(1,sizeof(AudioTimelineChunk));
    if ( last!=NULL && last->start_time_in_44k+last->result_len > start ) {
	if ( stream==AT_CLAPS && start > last->start_time_in_44k ) {
	    /* If we have two claps in quick succession, don't wait for the */
	    /*  first to finish, make it stop early and start the next one */
	    /*  when it should */
	    last->result_len = start - last->start_time_in_44k;
	} else {
	    fprintf( stderr, "Start of audio chunk delayed by %ld for %s\n", last->start_time_in_44k+last->result_len-start, filename );
	    start = last->start_time_in_44k+last->result_len;
	}
    }
    asCur->start_time_in_44k = start;
    asCur->result_len = samples44k;
    if ( filename!=NULL )
	asCur->filename = strdup(filename);	/* In most cases the filename lives in static memory, but not in squaredances (with paramaterized choreographies) */
    if ( last==NULL )
	dv->audios[stream].first = asCur;
    else
	last->next = asCur;
    if ( stream==AT_CALLS && filename!=NULL )
	asCur->amplify = danceAVFigureAmplify(dv,filename);
return( asCur );
}

/* How much time (in audio samples) before the figure do we make the call */
static int danceAVCallBeforeSamples(DanceView *dv, CallList *call, int *samples44k,
	AudioTimelineChunk *atLast, long current_time_in_44k, char **filename ) {
    int twobars, start;
    int long_samples;

    *filename = NULL;
    if ( call==NULL || call->audioFilename==NULL || *call->audioFilename=='\0' )
return( 0 );

    if ( dv->audio_sample_rate==0 )
return( 0 );

    *filename = call->audioFilename;
    audioFileSeconds(call->audioFilename,dv->audio_sample_rate,samples44k);

    /* First, which audio file do we want to use? Use the long one if there's time */
    long_samples=0;
    if ( call->longaudioFilename!=NULL ) {
	audioFileSeconds(call->longaudioFilename,dv->audio_sample_rate,&long_samples);
	if ( long_samples/(double) dv->audio_sample_rate <= call->audio_millibars*dv->frames_per_bar/dv->frames_per_sec / MILLIBARS_MUL ) {
	    *filename = call->longaudioFilename;
	    *samples44k = long_samples;
	}
    }
    if ( *samples44k/(double) dv->audio_sample_rate > call->audio_millibars*(double) dv->frames_per_bar/dv->frames_per_sec / MILLIBARS_MUL ) {
	fprintf( stderr, "%s is too long for the time the call takes (%.1fsec > %.1fsec)\n",
		*filename, *samples44k/(double) dv->audio_sample_rate, call->audio_millibars*dv->frames_per_bar/dv->frames_per_sec / MILLIBARS_MUL);
    }

    /* Call should start 2 bars before figure */
    /* Unless call takes less than 2 bars, then it should start in however long it takes to say it */
    /* Unless the last call is still happening then, in which case it starts when the last call finishes */
    twobars= round( (2.0*dv->audio_sample_rate*dv->frames_per_bar)/dv->frames_per_sec );
    if ( *samples44k<twobars )
	start = *samples44k ;
    else
	start = twobars ;
    if ( atLast==NULL )
return( start );

    if ( atLast->start_time_in_44k+atLast->result_len > current_time_in_44k-start )
	start = current_time_in_44k - (atLast->start_time_in_44k+atLast->result_len);
return( start );
}

static char *numberAudio[]= {
    "AudioCalls/one.wav",
    "AudioCalls/two.wav",
    "AudioCalls/three.wav",
    "AudioCalls/four.wav",
    "AudioCalls/five.wav",
    "AudioCalls/six.wav",
    "AudioCalls/seven.wav",
    "AudioCalls/eight.wav",
    "AudioCalls/nine.wav",
    "AudioCalls/ten.wav",
    "AudioCalls/eleven.wav",
    "AudioCalls/twelve.wav",
    NULL
};

static char *countName(DanceView *dv, int count, int *samples44k) {
    static int drop44k=0;
    static int countSounds44k[12] = { 0 };
#define OLD_TICKFILE	"AudioCalls/drop.wav"
#define TICKFILE	"AudioCalls/metronome tick.wav"

    if ( dv->add_tones_to_audio || dv->minorset_add_tones_to_audio ) {
	if ( drop44k==0 )
	    audioFileSeconds(TICKFILE,dv->audio_sample_rate,&drop44k);
	*samples44k = drop44k;
return( TICKFILE );
    }

    if ( dv->dance->counts_per_bar==2 ) {
	count %= 8;
    } else if ( dv->dance->counts_per_bar==4 ) {
	count %= 16;
	if ( count%4==0 )
	    count /= 4;
	else
	    count %= 4;
    } else {
	count %= 12;
	if ( count%3==0 )
	    count /= 3;
	else
	    count %= 3;
    }
    if ( countSounds44k[count]==0 )
	audioFileSeconds(numberAudio[count],dv->audio_sample_rate,&countSounds44k[count]);
    *samples44k = countSounds44k[count];
return( numberAudio[count] );
}

/* We have two ways of counting at the moment */
/* if counts_per_bar==2 then we count up to eight and start over */
/* if counts_per_bar==3 then we count 1-2-3, 2-2-3, 3-2-3, 4-2-3 and start over */
/* Number of bars must be divisible by 4, length of a count (bar/2 or bar/3) */
/*   must be at least .5 seconds or there isn't time for a number */
static void danceAddCountTimeline(DanceView *dv) {
    int count, choreo;
    Dance *dance = dv->dance;
    TimeLine *tl;
    int danceSetMillibars, danceSetCounts;
    AudioTimelineChunk *atLast = NULL;
    int sample_rate = dv->audio_sample_rate;
/*
    int samples_per_count = sample_rate
	    *dv->frames_per_bar
	    /dv->frames_per_sec
	    /dv->dance->counts_per_bar;
*/

    danceSetMillibars = danceMinorSetMillibarCount(dance);
    /* we need enough time in a bar to make a beep or whatever. counting takes ~.5, beeps are shorter */
    /* Counting also only works if I understand the music structure */
    if ( dv->add_counts_to_audio ) {
	if (( dv->dance->counts_per_bar!=2 && dv->dance->counts_per_bar!=3 && dv->dance->counts_per_bar!=4 ) ||
		danceSetMillibars%4000!=0 ||
		dv->frames_per_bar / (double) dv->frames_per_sec /(double) dv->dance->counts_per_bar < .5 )
return;
    } else {
	if ( dv->frames_per_bar / (double) dv->frames_per_sec /(double) dv->dance->counts_per_bar < .25 )
return;
/*
	if ( dv->dance->comp_cnt!=0 ) {
	    if ( !danceAllComponentsSameBars(dance))
return;
	    danceSetMillibars = danceMinorSetMillibarCount(dance->components[0]);
	}
*/
    }
    danceSetCounts = danceSetMillibars * dance->counts_per_bar / MILLIBARS_MUL;

    choreo = 0;
    for ( tl = dv->timeline; tl!=NULL; tl=tl->next ) {
	int frames;
	if ( tl->timeline_type != tl_minorset && tl->timeline_type != tl_majorset )
    continue;
	if ( tl->timeline_type != tl_minorset && dv->minorset_add_tones_to_audio )
    continue;
	if ( dv->dance->choreo_cnt!=0 ) {
	    Dance *subd;
	    if ( choreo>=dv->dance->choreo_cnt )
    continue;
	    subd = dv->dance->sequence[choreo++].figure;
	    danceSetMillibars = danceMinorSetMillibarCount(subd);
	    danceSetCounts = danceSetMillibars * subd->counts_per_bar / MILLIBARS_MUL;
	}
	frames = tl->end_frame - tl->start_frame;
	for ( count=0 ; count<danceSetCounts; ++count ) {
	    int start, samples44k;
	    char *filename;
	    start = round( (tl->start_frame + (count*frames/(double)danceSetCounts)) *
			    sample_rate /
			    dv->frames_per_sec );
	    filename = countName(dv,count,&samples44k);
	    atLast = danceAddAudioChunk(dv,AT_COUNTS,atLast,
		    start,samples44k,filename);
	    if ( dv->add_tones_to_audio && count%dv->dance->counts_per_bar!=0 )
		atLast->amplify = 1.0/16;
	    
	}
    }
}

static long danceAVMusicSegmentLengthInOutputSamples(DanceView *dv,MusicSegment *ms) {
    long samples44k=0;

    if ( dv->audio_sample_rate==0 )
return( 0 );
    if ( dv->audios[AT_MUSIC].avContext == NULL )
return( 0 );
    if ( ms!=NULL && ms->end<ms->start ) {
	fprintf( stderr, "Bad music segment, start is after end.\n" );
return( 0 );
    }

    while ( ms!=NULL ) {
	if ( ms->type==it_fit ) /* assert(ms->duration_millibars!=0 ) */
	    samples44k += ceil( ms->duration_millibars * (long) dv->audio_sample_rate * dv->frames_per_bar / (MILLIBARS_MUL * dv->frames_per_sec));
	else if ( ms->type==it_copy )
	    samples44k += ceil( (ms->end-ms->start) * (long) dv->audio_sample_rate / 1000.0 );
	else {
	    double barTime = danceMusicBarTimeRatio(dv->dance);
	    double samples44kTime = barTime * dv->audio_sample_rate * dv->frames_per_bar /dv->frames_per_sec / 1000.0;
	    samples44k += round( ms->end   * samples44kTime ) -
			  round( ms->start * samples44kTime );
	}
	ms = ms->next;
    }
/* return( ceil( samples44k * dv->frames_per_sec / (double) dv->audio_sample_rate ));*/
return( samples44k );
}

static void danceAVMusicPrep(DanceView *dv) {
    /* Open a stream for the music file so we can get some basic info from it */
    /*  before we figure out the time line. Might as well keep it open after */
    /*  that for use later */
    AudioTimelineChunk temp;

    if ( dv->dance->music.filename==NULL || dv->audio_sample_rate==0 )
return;

    memset(&temp,0,sizeof(temp));
    temp.filename = dv->dance->music.filename;

    dv->audios[AT_MUSIC].upcoming = &temp;
    if ( danceAVOpenAudioInput(dv,&dv->audios[AT_MUSIC])) {
	dv->stereo_output = dv->audios[AT_MUSIC].codecParams->channels > 1;
    } else
	dv->audios[AT_MUSIC].avContext = NULL;
    dv->audios[AT_MUSIC].active = dv->audios[AT_MUSIC].upcoming = NULL;
}

static AudioTimelineChunk *danceAVAddMusicSegments(DanceView *dv,
	AudioTimelineChunk *atLast,
	long *_music_start_samples44k, MusicSegment *segment, int cache_data) {
    long music_start_samples44k = *_music_start_samples44k;
    long result_len;
    long istart, iend;
    long music_sample_rate = dv->audios[AT_MUSIC].codecParams->sample_rate;

    while ( segment!=NULL ) {
	/* result_len is the number of output samples needed to play this segment */
	istart = round( segment->start * music_sample_rate / 1000.0 );
	iend   = round( segment->end   * music_sample_rate / 1000.0 );
	if ( segment->duration_millibars!=0 && segment->type!=it_copy )
	    result_len = ceil( segment->duration_millibars * (long) dv->audio_sample_rate * dv->frames_per_bar / (MILLIBARS_MUL * dv->frames_per_sec));
	else if ( segment->type==it_fit2set || segment->type==it_fit2lastset ) {
	    double barTime = segment->type==it_fit2set ?
		    danceMusicBarTimeRatio(dv->dance) :
		    danceMusicLastBarTimeRatio(dv->dance);
	    double samples44kTime = barTime * dv->audio_sample_rate * dv->frames_per_bar /dv->frames_per_sec / 1000.0;
	    result_len = round( segment->end   * samples44kTime ) -
			 round( segment->start * samples44kTime );
	    if ( barTime == 0 )
		fprintf( stderr, "Can't use %s here no minorset/body/lastminorset segment.\n", segment->type==it_fit2set ? "fit2set" : "fit2lastset" );
	} else if ( music_sample_rate == dv->audio_sample_rate )
	    result_len = iend - istart;
	else
	    result_len = round( segment->end   * (long) dv->audio_sample_rate / 1000.0 ) -
			 round( segment->start * (long) dv->audio_sample_rate / 1000.0 );

	atLast = danceAddAudioChunk(dv,AT_MUSIC,atLast,
		music_start_samples44k,result_len,NULL);
	atLast->dont_open = true;
	atLast->dont_close = true;
	atLast->save_to_buffer = cache_data;
	music_start_samples44k += result_len;

	atLast->interpolate_start = istart;
	atLast->interpolate_end   = iend;
	if ( segment->fadein_duration!=0 && iend!=istart ) {	/* In input millisecs */
	    atLast->fadein_initial_level = segment->fadein_initial_level;
	    atLast->fadein_samples = round( segment->fadein_duration*music_sample_rate/1000.0 * result_len/(double) (iend-istart));
	}
	segment = segment->next;
    }
    *_music_start_samples44k = music_start_samples44k;
return( atLast );
}

static void danceAVAddMusic(DanceView *dv, long music_start_samples44k) {
    AudioTimelineChunk *atLast = NULL;
    int i;
    int cnt;
    MusicSegment *ms;
    int max_samples44k;
    int max_millisecs;
    int do_last=false;

    if ( dv->dance->music.filename==NULL || dv->audio_sample_rate==0 )
return;

    audioFileSeconds(dv->dance->music.filename,dv->audio_sample_rate,&max_samples44k);
    max_millisecs = max_samples44k/44.1;

    atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, dv->dance->music.intro, 0);
    if ( dv->dance->music.minorset!=NULL ) {
	cnt = (dv->minor_set_count-dv->show_minorset_first)*dv->dance->music.tunecnt_per_minorset;
	/* In choreographies I use tunecnt_per_minorset for the entire dance */
	/*  rather than one component because different components may have */
	/*  different lengths, while all true minorsets are the same length */
	if ( dv->dance->choreo_cnt!=0 )
	    cnt = dv->dance->music.tunecnt_per_minorset;
	ms  = dv->dance->music.minorset;
	if ( cnt>dv->dance->music.tunecnt_per_minorset && dv->dance->music.lastminorset!=NULL ) {
	    --cnt;
	    do_last = true;
	}
    } else if ( dv->dance->music.body!=NULL ) {
	cnt = dv->dance->music.body_repeats;
	if ( cnt==0 ) cnt=1;
	ms  = dv->dance->music.body;
    } else
	cnt = 0;
    if ( ms!=NULL && ms->end > max_millisecs ) {
	fprintf( stderr, "\a!!%s music ends outside file length (%d > %d)\n",
	    ms==dv->dance->music.body ? "Body" : "Minorset", ms->end, max_millisecs );
    }
    if ( dv->dance->music.lastminorset!=NULL && dv->dance->music.lastminorset->end > max_millisecs ) {
	fprintf( stderr, "\a!!Lastminorset music ends outside file length (%d > %d)\n",
	    dv->dance->music.lastminorset->end, max_millisecs );
    }
    if ( cnt>0 ) {
	int result_len = danceAVMusicSegmentLengthInOutputSamples(dv,ms);
	atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, ms, cnt>1 );
	for ( i=1; i<cnt; ++i ) {
	    atLast = danceAddAudioChunk(dv,AT_MUSIC,atLast,
		    music_start_samples44k,result_len,NULL);
	    atLast->dont_open = 1;
	    atLast->dont_close = 1;
	    atLast->from_buffer = 1;
	    music_start_samples44k += result_len;
	}
	if ( cnt>1 ) {
	    /* We need a buffer. It needs to contain result_len of 2 channels of shorts */
	    dv->buffered_samples = malloc(result_len*2*sizeof(short));
	    dv->write_bpt = dv->buffered_samples;
	    dv->max_buf = dv->buffered_samples + result_len*2;
	}
    }
    if ( do_last )
	atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, dv->dance->music.lastminorset, false );

    atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, dv->dance->music.ending, 0);
    if ( atLast!=NULL )
	atLast->dont_close = false;	/* Close the music input streams after the last segment */
}

static int danceMusicUseTitle(Dance *dance) {
    /* Don't use the music title if it is already included in the video title */

    if ( dance->titleAudioFilename!=NULL &&
	    strstr(dance->titleAudioFilename,"with music")!=NULL )
return( false );	/* Don't use it when the main title already contains the music title */

return( dance->music.titleAudioFilename!=NULL );
}

void danceAVBuildTimelines(DanceView *dv, struct video_options *options) {
    Dance *dance = dv->dance;
    TimeLine *tlLast=NULL;
    AudioTimelineChunk *atLast[MAX_AUDIO_TIMELINES];
    int i, f, cnt, extras;
    int nextFrame=0;
    int samples44k=0, music_samples44k=0;
    int intro_len44k, ending_len44k;
    int clap_samples, snap_samples, stamp_samples;
    int fpb = dv->frames_per_bar;
    int music_start_frame=-1;
    int sets_starting_frame;
    Dance *starting_state;
    char *call_filename=NULL;

    av_log_set_level(AV_LOG_ERROR);	/* libav talks too much. This should quiet it down a bit */

    dvCheckCallLength(dv,NULL);

    starting_state = danceSaveState(dance);

    for ( i=0; i<MAX_AUDIO_TIMELINES; ++i ) {
	atLast[i] = NULL;
	dv->audios[i].amplify = 1.0;
    }
    if ( dance->music.amplify!=0 )
	dv->audios[AT_MUSIC].amplify = dance->music.amplify * options->music_amplify ;

    dance->current_part = options->initial_part-1;
    if ( dance->current_part>=dance->part_cnt || dance->current_part<0 )
	dance->current_part = 0;
    if ( dance->roles!=NULL || dance->sequence!=NULL )
	danceStart(dance,dv->couples,options->out_at_top);
    dance->current_part = options->initial_part-1;
    if ( dance->current_part>=dance->part_cnt || dance->current_part<0 )
	dance->current_part = 0;

    if ( dv->show_title ) {
	int title_frames = 0;
	int music_frames=0;
	int music_title_start=0;

	/* If a cd image, or cd notes then display on top of title page */
	if ( dv->audio_sample_rate>0 && dance->music.cdImage==NULL && dance->music.cdNotes==NULL ) {
	    if ( dv->audio_sample_rate>0 && dance->titleAudioFilename!=NULL )
		title_frames = ceil((audioFileSeconds(dance->titleAudioFilename,dv->audio_sample_rate,&samples44k)-.5)*dv->frames_per_sec);
		/* Some of the audio title can bleed into the 1 second transion to the minor set, hence the -.5 */
	    if ( title_frames < options->titleframes )
		title_frames = options->titleframes;

	    if ( dv->audio_sample_rate>0 &&  dance->music.titleAudioFilename!=NULL && danceMusicUseTitle(dance) )
		music_frames += ceil(audioFileSeconds(dance->music.titleAudioFilename,dv->audio_sample_rate,&music_samples44k)*dv->frames_per_sec);
	    if ( music_frames<options->musictitleframes )
		music_frames = options->musictitleframes;
	    title_frames += music_frames;
	    if ( title_frames<2*dv->frames_per_sec )
		title_frames = 2*dv->frames_per_sec;
	    tlLast = danceAddTimeline(dv,tlLast,0,title_frames,tl_title);
	    music_title_start=samples44k;
	} else {
	    if ( dv->audio_sample_rate>0 && dance->titleAudioFilename!=NULL )
		title_frames = ceil((audioFileSeconds(dance->titleAudioFilename,dv->audio_sample_rate,&samples44k))*dv->frames_per_sec);
	    if ( title_frames < options->titleframes )
		title_frames = options->titleframes;
	    /* no bleed here */
	    if ( dv->audio_sample_rate>0 &&  dance->music.titleAudioFilename!=NULL && danceMusicUseTitle(dance) )
		music_frames = ceil(audioFileSeconds(dance->music.titleAudioFilename,dv->audio_sample_rate,&music_samples44k)*dv->frames_per_sec);
	    if ( music_frames<options->musictitleframes )
		music_frames = options->musictitleframes;
	    music_title_start=title_frames;
	    if ( title_frames<2*dv->frames_per_sec ) {
		title_frames = 2*dv->frames_per_sec;
		music_title_start=2*dv->audio_sample_rate;
	    }
	    tlLast = danceAddTimeline(dv,tlLast,0,title_frames,tl_title);
	    tlLast = danceAddTimeline(dv,tlLast,title_frames,title_frames+2*dv->frames_per_sec,tl_transition_title_album);
	    title_frames += 2*dv->frames_per_sec;
	    if ( music_frames < 5*dv->frames_per_sec )
		music_frames = 5*dv->frames_per_sec;
	    tlLast = danceAddTimeline(dv,tlLast,title_frames,title_frames+music_frames-2*dv->frames_per_sec,tl_album);
	    title_frames += music_frames-2*dv->frames_per_sec;
	    tlLast = danceAddTimeline(dv,tlLast,title_frames,title_frames+dv->frames_per_sec,tl_transition_album_title);
	    title_frames += dv->frames_per_sec;
	}

	atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		0,samples44k,dance->titleAudioFilename);
	if ( music_samples44k!=0 ) {
	    atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		    music_title_start,music_samples44k,dance->music.titleAudioFilename);
	    samples44k = music_title_start + music_samples44k;
	}
	nextFrame = title_frames;
    }
    if ( dv->show_minorset_first ) {
	/* Zoom in on minorset */
	tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+dv->frames_per_sec,tl_transition_title_set);

	nextFrame += dv->frames_per_sec;
    } else if ( dv->show_title ) {
	/* fade title out */
	tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+dv->frames_per_sec,tl_transition_title_fadeout);

	nextFrame += dv->frames_per_sec;
    }

    if ( dv->do_honour || danceNeedsImproperCrossover(dance) ) {
	audioFileSeconds("AudioCalls/Scottish initial chord.wav",dv->audio_sample_rate,&samples44k);
	if ( dv->do_honour ) {
	    int honor_frames = 2*dv->frames_per_sec;

	    tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+honor_frames,tl_honour);

	    atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		    nextFrame*dv->audio_sample_rate/dv->frames_per_sec,
		    samples44k,"AudioCalls/Scottish initial chord.wav");

	    nextFrame += honor_frames;
	}
    
	if ( danceNeedsImproperCrossover(dance) ) {
	    int honor_frames = 2*dv->frames_per_sec;
	    /* RSCDS Book 1, Circassian Circle doesn't seem to have a cross over */
	    /*  people start improper, same for all round the room dances (la tempete) */

	    tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+honor_frames,tl_scottishImproper);

	    atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		    nextFrame*dv->audio_sample_rate/dv->frames_per_sec,
		    samples44k,"AudioCalls/Scottish initial chord.wav");
	    danceForceImproper(dance);

	    nextFrame += honor_frames;
	}
    }

    danceAVMusicPrep(dv);
    intro_len44k = danceAVMusicSegmentLengthInOutputSamples(dv,dance->music.intro);

    audioFileSeconds("AudioCalls/clap.wav",dv->audio_sample_rate,&clap_samples);
    audioFileSeconds("AudioCalls/snap.wav",dv->audio_sample_rate,&snap_samples);
    audioFileSeconds("AudioCalls/stamp.wav",dv->audio_sample_rate,&stamp_samples);
    if ( dv->show_minorset_first )
	danceMinorsetSlowdown(dv);

    cnt = 0;
    dance->current_minorset=0;
    music_start_frame = -1;
    sets_starting_frame = nextFrame;
    for ( f=0; cnt<dv->minor_set_count; ++f ) {
	enum dancecondition dc;
	CallList *lastC = dance->current;

	dc = danceNextFrame(dance,f,dv->frames_per_bar);
	if ( f==0 ) {
	    int samples44k;
	    int precede, calllen = danceAVCallBeforeSamples(dv,dance->current,&samples44k, NULL, 0, &call_filename);
	    int precedeFrames;

	    if ( !dv->show_minorset_first && dance->music.start_millibars==0 )
		precede = calllen>intro_len44k ? calllen : intro_len44k;
	    else
		precede = calllen;
	    precedeFrames = ceil( precede * dv->frames_per_sec / (double) dv->audio_sample_rate );
	    if ( dv->audios[0].first!=NULL &&
		    (dv->audios[0].first->start_time_in_44k+dv->audios[0].first->result_len) * dv->frames_per_sec/(double) dv->audio_sample_rate > nextFrame )
		precedeFrames += (dv->audios[0].first->start_time_in_44k+dv->audios[0].first->result_len) * dv->frames_per_sec/(double) dv->audio_sample_rate - nextFrame;
	    if ( precedeFrames==0 && nextFrame==0 )
		precedeFrames = 1;	/* When there is nothing at the start we need at least one initial frame, otherwise we start with frame 1 */

	    if ( precedeFrames>0 )
		tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+precedeFrames,tl_stillFrames);
	    nextFrame += precedeFrames;

	    if ( calllen!=0 )
		atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
			nextFrame*dv->audio_sample_rate/dv->frames_per_sec-calllen,
			samples44k,call_filename);
	    if ( !dv->show_minorset_first && dance->music.start_millibars==0 ) {
		music_start_frame = nextFrame;
	    }
	    lastC = NULL;
	    sets_starting_frame = nextFrame;
	} else if ( lastC!=dance->current ) {
	    int samples44k;
	    int calllen = danceAVCallBeforeSamples(dv,dance->current,&samples44k,
		    atLast[AT_CALLS],nextFrame*dv->audio_sample_rate/dv->frames_per_sec,
		    &call_filename);

	    if ( calllen!=0 )
		atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
			nextFrame*dv->audio_sample_rate/dv->frames_per_sec-calllen + dance->current->offset_millisecs*dv->audio_sample_rate/1000,
			samples44k,call_filename);
	}

	if ( lastC!=dance->current ) {
	    if ( dance->current!=NULL && dance->current->claplist_millibars!=NULL ) {
		int j;
		char *clapfile;
		int cf_samples;
		if ( dance->current->snap_not_clap ) {
		    clapfile = "AudioCalls/snap.wav";
		    cf_samples = snap_samples;
		} else if ( dance->current->stamp_not_clap ) {
		    clapfile = "AudioCalls/stamp.wav";
		    cf_samples = stamp_samples;
		} else {
		    clapfile = "AudioCalls/clap.wav";
		    cf_samples = clap_samples;
		}
		long samples_per_bar = dv->audio_sample_rate
			*dv->frames_per_bar
			/dv->frames_per_sec;
		for ( j=0; dance->current->claplist_millibars[j]!=CLAP_DONE; ++j ) {
		    atLast[AT_CLAPS] = danceAddAudioChunk(dv,AT_CLAPS,atLast[AT_CLAPS],
			    nextFrame*dv->audio_sample_rate/dv->frames_per_sec +
				dance->current->claplist_millibars[j]*samples_per_bar/1000,
			    cf_samples,clapfile);
		}
	    }
	}

	++nextFrame;
	if ( dc==dc_finished ) {
	    tlLast = danceAddTimeline(dv,tlLast,sets_starting_frame,nextFrame,
		    cnt==0 && dv->show_minorset_first ? tl_minorset : tl_majorset);
    break;
	} else if ( dc==dc_minorSetEnded ) {
	    if ( cnt==0 && dv->show_minorset_first ) {
		tlLast = danceAddTimeline(dv,tlLast,sets_starting_frame,nextFrame,tl_minorset);
		dv->frames_per_bar = dv->original_frames_per_bar;
		/* In many cases where we show the minor set we don't change */
		/* the scale (3 Couple longways) and it looks weird to pause */
		/* while nothing happens */
		/* Unfortunately at this point we haven't calculated the scale*/
		/*  so we can't test that directly */
		if (( danceNeedsCoupleCount(dv->dance) ||
			dv->dance->formation==sp_scottishDupleMinor ||
			dv->dance->formation==sp_scottishTripleMinor ) /* &&
			(dv->dance->formation!=sp_circularMonople || !dv->dance->sicilian)*/ ) {
		    tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+3*dv->frames_per_sec,tl_transition_minor_major);
		    nextFrame += 3* dv->frames_per_sec;
		} else {
		    tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+1,tl_transition_minor_major);
		    ++nextFrame;
		}
		music_start_frame = nextFrame;
	    } else
		tlLast = danceAddTimeline(dv,tlLast,sets_starting_frame,nextFrame,tl_majorset);
	    sets_starting_frame = nextFrame;
	    ++cnt;
	    ++dance->current_minorset;
	    if ( cnt==dv->minor_set_count-1 )
		dance->last_minor_set = 1;
	}
	if ( !dv->show_minorset_first && music_start_frame==-1 &&
		f*MILLIBARS_MUL/dv->frames_per_bar >= dance->music.start_millibars ) {
	    music_start_frame = nextFrame;
	}
    }
    dv->total_dance_frames= f;

    ending_len44k = danceAVMusicSegmentLengthInOutputSamples(dv,dance->music.ending);
    extras = ceil(ending_len44k*dv->frames_per_sec/ (double) dv->audio_sample_rate );
     /* At the end of a non-minor dance, we've got all the dancers sitting at */
     /*  home but with the wrong border colors (showing what roles they were  */
     /*  doing). It's mildly annoying. In a triple minor the 2s and 3s will   */
     /*  have the wrong borders. Even in a duple the people and the ends need */
     /*  a fix up. So allow one extra frame where every one clicks over to its*/
     /*  proper role */
    if ( extras==0 )
	extras = 1;
    ++extras;	/* The last frame I add seems to be lost. Not sure why. So insure there's one extra */
     /* 44100 is divisible by 30 and 25 but not 24 (it is by 12) */
     /* this means that if we are doing 24 frame per second output and we have*/
     /* an odd number of video frames we won't be able to get exactly the right*/
     /* number of audio samples, but if we add an extra video frame, all is OK*/
    if ( dv->frames_per_sec==24 && (nextFrame+extras)%2==1 )
	++extras;
    tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+extras,tl_stillFrames);

    if ( dv->dance->do_honour_end ) {
	int honor_frames = 2*dv->frames_per_sec;

	audioFileSeconds("AudioCalls/Scottish initial chord.wav",dv->audio_sample_rate,&samples44k);
	tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+honor_frames,tl_honour);

	atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		nextFrame*dv->audio_sample_rate/dv->frames_per_sec,
		samples44k,"AudioCalls/Scottish initial chord.wav");

	nextFrame += honor_frames;

	extras=1;
	if ( dv->frames_per_sec==24 && (nextFrame+extras)%2==1 )
	    ++extras;
	tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+extras,tl_stillFrames);
    }


    if ( music_start_frame!=-1 && dv->audios[AT_MUSIC].avContext!=NULL )
	danceAVAddMusic(dv,
		music_start_frame*dv->audio_sample_rate/dv->frames_per_sec-intro_len44k);
    if ( dv->add_counts_to_audio || dv->add_tones_to_audio || dv->minorset_add_tones_to_audio )
	danceAddCountTimeline(dv);

    dance->last_minor_set = 0;
    danceResetState(dance,starting_state);
	    
    dv->frames_per_bar = fpb;

    if ( dv->verbose ) {
	TimeLine *tl;
	AudioTimelineChunk *atl;
	static char *timelineNames[] = { "Title", "Transition Title->Album", "Album", "Transition Album->Title", "Transition Title->Set", "Transition Title Fadeout", "Honour", "Scottish Improper Cross", "Still Frames (while audio plays)", "Minorset", "Transition Minor->Major Set", "MajorSet", "Hunh?", "Hunh?", NULL };
	for ( tl=dv->timeline; tl!=NULL; tl=tl->next ) {
	    printf( " %s start=%d, end=%d\n", timelineNames[tl->timeline_type], tl->start_frame, tl->end_frame );
	}
	for ( atl=dv->audios[AT_MUSIC].first; atl!=NULL; atl=atl->next ) {
	    if ( atl->from_buffer )
		printf( " [copying] start=%ld end=%ld amplify=%.2f fadein-samples=%d\n",
		    atl->start_time_in_44k, atl->start_time_in_44k+atl->result_len,
		    atl->amplify, atl->fadein_samples );
	    else
		printf( " istart='%ld' iend='%ld' start=%ld end=%ld amplify=%.2f make_copy=%d\n",
		    atl->interpolate_start, atl->interpolate_end, atl->start_time_in_44k, atl->start_time_in_44k+atl->result_len,
		    atl->amplify, atl->save_to_buffer );
	}
#if 0
	for ( atl=dv->audios[AT_CALLS].first; atl!=NULL; atl=atl->next ) {
	    printf( " call-file='%s' start=%ld end=%ld amplify=%.2f fadein-samples=%d\n",
		atl->filename, atl->start_time_in_44k, atl->start_time_in_44k+atl->result_len,
		atl->amplify, atl->fadein_samples );
	}
#endif
    }
}

static void danceAForceSegmentListToCopy(MusicSegment *ms) {

    while ( ms!=NULL ) {
	ms->type = it_copy;
	ms = ms->next;
    }
}

static void danceAForceSegmentsToCopy(DanceView *dv) {
    MusicDescription *md = &dv->dance->music;

    if ( md==NULL )
return;
    danceAForceSegmentListToCopy(md->intro);
    danceAForceSegmentListToCopy(md->body);
    danceAForceSegmentListToCopy(md->minorset);
    danceAForceSegmentListToCopy(md->middlesets);
    danceAForceSegmentListToCopy(md->lastminorset);
    danceAForceSegmentListToCopy(md->ending);
}

static void danceAAddMusic(DanceView *dv, long music_start_samples44k,MusicSegment *msSets) {
    AudioTimelineChunk *atLast = NULL;
    MusicDescription *md = &dv->dance->music;
    int i;
    int cnt;
    MusicSegment *ms;
    int max_samples44k;
    int max_millisecs;
    int do_last=false, do_middle = false;

    if ( md->filename==NULL || dv->audio_sample_rate==0 )
return;

    audioFileSeconds(md->filename,dv->audio_sample_rate,&max_samples44k);
    max_millisecs = max_samples44k/44.1;

    if ( msSets==NULL || md->set_count==0 ) {
	atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, md->intro, 0);
	if ( md->body!=NULL ) {
	    cnt = md->body_repeats;
	    if ( cnt==0 ) cnt=1;
	    ms  = md->body;
	} else {
	    if ( md->lastminorset!=NULL )
		do_last = true;
	    if ( md->middlesets==NULL || 1+do_last+md->middleset_count>dv->minor_set_count )
		cnt = (dv->minor_set_count-do_last)*dv->dance->music.tunecnt_per_minorset;
	    else {
		cnt = dv->minor_set_count - md->middleset_count - do_last;
		do_middle = true;
	    }
	    ms  = md->minorset;
	}
	if ( ms->end > max_millisecs ) {
	    fprintf( stderr, "%s music ends outside file length (%d > %d)\n",
		ms==md->body ? "body" : "minorset", ms->end, max_millisecs );
	}
	if ( md->lastminorset!=NULL && md->lastminorset->end > max_millisecs ) {
	    fprintf( stderr, "lastminorset music ends outside file length (%d > %d)\n",
		md->lastminorset->end, max_millisecs );
	}
	
	if ( cnt>0 ) {
	    int result_len = danceAVMusicSegmentLengthInOutputSamples(dv,ms);
	    atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, ms, cnt>1 );
	    for ( i=1; i<cnt; ++i ) {
		atLast = danceAddAudioChunk(dv,AT_MUSIC,atLast,
			music_start_samples44k,result_len,NULL);
		atLast->dont_open = 1;
		atLast->dont_close = 1;
		atLast->from_buffer = 1;
		music_start_samples44k += result_len;
	    }
	    if ( cnt>1 ) {
		/* We need a buffer. It needs to contain result_len of 2 channels of shorts */
		dv->buffered_samples = malloc(result_len*2*sizeof(short));
		dv->write_bpt = dv->buffered_samples;
		dv->max_buf = dv->buffered_samples + result_len*2;
	    }
	}
	if ( do_middle )
	    atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, md->middlesets, false );
	if ( do_last )
	    atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, md->lastminorset, false );
    
	atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, md->ending, 0);
    } else {
	atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, &msSets[0], 0);
	cnt = dv->minor_set_count-md->set_count + 1;

	if ( cnt>1 ) {
	    int result_len = danceAVMusicSegmentLengthInOutputSamples(dv,&msSets[1]);
	    atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, &msSets[1], cnt>1 );
	    for ( i=1; i<cnt; ++i ) {
		atLast = danceAddAudioChunk(dv,AT_MUSIC,atLast,
			music_start_samples44k,result_len,NULL);
		atLast->dont_open = 1;
		atLast->dont_close = 1;
		atLast->from_buffer = 1;
		music_start_samples44k += result_len;
	    }
	    if ( cnt>1 ) {
		/* We need a buffer. It needs to contain result_len of 2 channels of shorts */
		dv->buffered_samples = malloc(result_len*2*sizeof(short));
		dv->write_bpt = dv->buffered_samples;
		dv->max_buf = dv->buffered_samples + result_len*2;
	    }
	}
	for ( i=(cnt>1); i<md->set_count; ++i )
	    atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, &msSets[i+1], 0 );
	atLast = danceAVAddMusicSegments(dv, atLast, &music_start_samples44k, &msSets[i+1], 0 );    
    }
    if ( atLast!=NULL )
	atLast->dont_close = false;	/* Close the music input streams after the last segment */

    if ( atLast!=NULL )
	dv->last_frame_cnt = atLast->start_time_in_44k + atLast->result_len;
}

void danceABuildTimelines(DanceView *dv, struct video_options *options) {
    Dance *dance = dv->dance, *d;
    int choreoI;
    AudioTimelineChunk *atLast[MAX_AUDIO_TIMELINES];
    int i, cnt;
    MusicDescription *md;
    MusicSegment *ms;
    MusicSegment *msSets=NULL;
    long nextSample=0, musicMinorsetStart=0, repeatStart= -1;
    int repeatStartCnt= -1;
    int samples44k=0, music_samples44k=0;
    int intro_len44k;
    int clap_samples, snap_samples, stamp_samples;
/*    int sets_starting_sample; */
    char *call_filename=NULL;
    int call_len;

    md = &dance->music;
    if ( dv->audio_sample_rate==0 ) {
	fprintf( stderr, "No audio output.\n" );
exit(1);
    } else if ( md->filename==NULL ) {
	fprintf( stderr, "No music.\n" );
exit(1);
    }
    /* In the following I use frame and sample synonomously */
    dv->frames_per_sec = dv->audio_sample_rate;
    /* The "frames_per_bar" is not constant, it changes slightly depending on */
    /*  the current musicsegment, but this is a rough estimate */
    if ( md->set_count>0 && dv->minor_set_count>=md->set_count ) {
	/* If I'm asking for fewer than md->set_count, then just duplicate the first minor set, see below. If asking for more, duplicate the first set as many times as needed and follow with all the others */
	msSets = calloc(md->set_count+4,sizeof(MusicSegment));
	msSets[0].type = it_copy;
	msSets[0].start = md->intro_start;
	msSets[0].end   = md->intro_end;
	for ( i=0; i<md->set_count; ++i ) {
	    msSets[i+1].type = it_copy;
	    msSets[i+1].start = msSets[i].end;
	    msSets[i+1].end   = md->set_ends[i];
	    msSets[i+1].duration_millibars = md->minorset_millibars;
	}
	msSets[i+1].type = it_copy;
	msSets[i+1].start = msSets[i].end;
	msSets[i+1].end   = md->final_end;
	ms = &msSets[1];
    } else if ( md->minorset!=NULL )
	ms = md->minorset;
    else if ( md->body!=NULL )
	ms = md->body;
    else {
	fprintf( stderr, "Neither minorset nor body specified in music description, can't estimate time for a bar.\n" );
exit(1);
    }
    dv->frames_per_bar = (ms->end-ms->start)* (MILLIBARS_MUL) * (dv->audio_sample_rate/1000.)/ms->duration_millibars;
    dv->show_minorset_first = false;
    danceAForceSegmentsToCopy(dv);

    av_log_set_level(AV_LOG_ERROR);	/* libav talks too much. This should quiet it down a bit */

    for ( i=0; i<MAX_AUDIO_TIMELINES; ++i ) {
	atLast[i] = NULL;
	dv->audios[i].amplify = 1.0;
    }
    if ( dance->music.amplify!=0 )
	dv->audios[AT_MUSIC].amplify = dance->music.amplify * options->music_amplify ;

    dance->current_part = options->initial_part-1;
    if ( dance->current_part>=dance->part_cnt || dance->current_part<0 )
	dance->current_part = 0;

    if ( dv->show_title ) {
	int title_samples = 0;

	audioFileSeconds(dance->titleAudioFilename,dv->audio_sample_rate,&samples44k);
	title_samples = samples44k+ .2*dv->audio_sample_rate;
	atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		0,samples44k,dance->titleAudioFilename);
	if ( dance->music.titleAudioFilename!=NULL && danceMusicUseTitle(dance) ) {
	    audioFileSeconds(dance->music.titleAudioFilename,dv->audio_sample_rate,&music_samples44k);
	    atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		    title_samples,music_samples44k,dance->music.titleAudioFilename);
	    title_samples += music_samples44k+ .2*dv->audio_sample_rate;
	}
	/*tlLast = danceAddTimeline(dv,tlLast,0,title_samples,tl_title);*/
	nextSample = title_samples;
    }

    if ( dv->do_honour || danceNeedsImproperCrossover(dance) ) {
	audioFileSeconds("AudioCalls/Scottish initial chord.wav",dv->audio_sample_rate,&samples44k);
	if ( dv->do_honour ) {
	    int honor_samples = 2*dv->audio_sample_rate;

	    atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		    nextSample,
		    samples44k,"AudioCalls/Scottish initial chord.wav");

	    nextSample += honor_samples;
	}
    
	if ( danceNeedsImproperCrossover(dance) ) {
	    int honor_samples = 2*dv->audio_sample_rate;

	    atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		    nextSample,
		    samples44k,"AudioCalls/Scottish initial chord.wav");

	    nextSample += honor_samples;
	}
    }

    danceAVMusicPrep(dv);
    if ( msSets!=NULL )
	intro_len44k = danceAVMusicSegmentLengthInOutputSamples(dv,&msSets[0]);
    else
	intro_len44k = danceAVMusicSegmentLengthInOutputSamples(dv,dance->music.intro);
    dance->first_minor_set = 1;
    dance->last_minor_set = dv->minor_set_count==1;
    choreoI = -1;
    d = dance;
    if ( dance->choreo_cnt!=0 ) {
	choreoI = 0;
	d = dance->sequence[choreoI++].figure;
    }
    dance->current = danceFindFirstCallInCurrentPart(d->calls,d->call_cnt,
	    (1<<d->current_part) |
	      (d->last_minor_set?LAST_MASK:0) |
	      (!d->last_minor_set?NOTLAST_MASK:0) |
	      (d->first_minor_set?FIRST_MASK:0) |
	      (!d->first_minor_set?NOTFIRST_MASK:0));
    d->current = dance->current;
    call_len = danceAVCallBeforeSamples(dv,dance->current,&samples44k, NULL, 0, &call_filename);

    audioFileSeconds("AudioCalls/clap.wav",dv->audio_sample_rate,&clap_samples);
    audioFileSeconds("AudioCalls/snap.wav",dv->audio_sample_rate,&snap_samples);
    audioFileSeconds("AudioCalls/stamp.wav",dv->audio_sample_rate,&stamp_samples);

    if ( call_len>intro_len44k )
	nextSample += (call_len-intro_len44k);

    danceAAddMusic(dv, nextSample,msSets);
    nextSample += intro_len44k;
    musicMinorsetStart = nextSample;

    cnt = 0;
    dance->current_minorset=0;
/*    sets_starting_sample = nextSample;  */
    for ( cnt=0; cnt<dv->minor_set_count; ++cnt ) {
	MusicSegment *ms;
	long mbars;

	if ( msSets!=NULL ) {
	    int first_repeat = dv->minor_set_count - md->set_count+1;
	    if ( cnt<first_repeat )
		ms = &msSets[1];
	    else
		ms = &msSets[cnt-first_repeat+2];
	} else if ( dance->music.body!=NULL )
	    ms = dance->music.body;
	else if ( cnt==dv->minor_set_count-1 && dance->music.lastminorset!=NULL )
	    ms = dance->music.lastminorset;
	else if ( dance->music.middlesets==NULL )
	    ms = dance->music.minorset;
	else if ( dv->minor_set_count < 1+(dance->music.lastminorset!=NULL)+dance->music.middleset_count )
	    ms = dance->music.minorset;
	else if ( cnt<dv->minor_set_count - (dance->music.lastminorset!=NULL) - dance->music.middleset_count )
	    ms = dance->music.minorset;
	else
	    ms = dance->music.middlesets;
	dv->frames_per_bar = (ms->end-ms->start)* (MILLIBARS_MUL) * (dv->audio_sample_rate/1000.)/ms->duration_millibars;

	mbars = 0;
	while ( dance->current!=NULL ) {
	    long callStart = nextSample + mbars*dv->frames_per_bar / MILLIBARS_MUL;
	    
	    call_len = danceAVCallBeforeSamples(dv,dance->current,&samples44k,
		    atLast[AT_CALLS],callStart, &call_filename);

	    if ( call_len!=0 )
		atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
			callStart-call_len, samples44k,call_filename);

	    if ( dance->current->claplist_millibars!=NULL ) {
		int j;
		char *clapfile;
		int cf_samples;
		if ( dance->current->snap_not_clap ) {
		    clapfile = "AudioCalls/snap.wav";
		    cf_samples = snap_samples;
		} else if ( dance->current->stamp_not_clap ) {
		    clapfile = "AudioCalls/stamp.wav";
		    cf_samples = stamp_samples;
		} else {
		    clapfile = "AudioCalls/clap.wav";
		    cf_samples = clap_samples;
		}
		for ( j=0; dance->current->claplist_millibars[j]!=CLAP_DONE; ++j ) {
		    atLast[AT_CLAPS] = danceAddAudioChunk(dv,AT_CLAPS,atLast[AT_CLAPS],
			    callStart +
				dance->current->claplist_millibars[j]*dv->frames_per_bar / MILLIBARS_MUL,
			    cf_samples,clapfile);
		}
	    }

	    mbars += dance->current->millibars;
	    danceCallNext(d);
	    dance->current = d->current;
	}

	++dance->current_part;
	if ( dance->current_part>=dance->part_cnt )
	    dance->current_part=0;
	d = dance;
	if ( dance->choreo_cnt!=0 ) {
	    d = dance->sequence[choreoI++].figure;
	    if ( choreoI>=dance->choreo_cnt )
		d = dance;
	}
	dance->first_minor_set = false;
	dance->last_minor_set = cnt==dv->minor_set_count-2;
	dance->current = danceFindFirstCallInCurrentPart(d->calls,d->call_cnt,
		(1<<dance->current_part) |
		  (d->last_minor_set?LAST_MASK:0) |
		  (!d->last_minor_set?NOTLAST_MASK:0) |
		  (d->first_minor_set?FIRST_MASK:0) |
		  (!d->first_minor_set?NOTFIRST_MASK:0));
	d->current = dance->current;
	if ( msSets!=NULL )
	    nextSample += danceAVMusicSegmentLengthInOutputSamples(dv,ms);
	else if ( ms==dance->music.body )
	    nextSample = musicMinorsetStart + ((cnt+1) * danceAVMusicSegmentLengthInOutputSamples(dv,ms))/dv->minor_set_count;
	else if ( ms==dance->music.minorset || ms==dance->music.lastminorset )
	    nextSample += danceAVMusicSegmentLengthInOutputSamples(dv,ms);
	else {
	    if ( repeatStart==-1 ) {
		repeatStart = nextSample;
		repeatStartCnt = cnt;
	    }
	    nextSample = repeatStart + ((cnt+1-repeatStartCnt)*danceAVMusicSegmentLengthInOutputSamples(dv,ms))/dance->music.middleset_count;
	}
    }

    if ( dv->dance->do_honour_end ) {
	int honor_samples = 2*dv->audio_sample_rate;

	audioFileSeconds("AudioCalls/Scottish initial chord.wav",dv->audio_sample_rate,&samples44k);

	atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		nextSample,
		samples44k,"AudioCalls/Scottish initial chord.wav");

	nextSample += honor_samples;

    }
    if ( nextSample > dv->last_frame_cnt )
	dv->last_frame_cnt = nextSample;

    if ( dv->verbose ) {
	AudioTimelineChunk *atl;
	for ( atl=dv->audios[AT_MUSIC].first; atl!=NULL; atl=atl->next ) {
	    printf( " file='%s' start=%ld end=%ld amplify=%.2f fadein-samples=%d\n",
		atl->filename, atl->start_time_in_44k, atl->start_time_in_44k+atl->result_len,
		atl->amplify, atl->fadein_samples );
	}
	printf("\n" );
	for ( atl=dv->audios[AT_CALLS].first; atl!=NULL; atl=atl->next ) {
	    printf( " file='%s' start=%ld end=%ld amplify=%.2f fadein-samples=%d\n",
		atl->filename, atl->start_time_in_44k, atl->start_time_in_44k+atl->result_len,
		atl->amplify, atl->fadein_samples );
	}
    }
}

/******************************************************************************/
/******************************************************************************/
/*                            Simple WAV stuff                                */
/******************************************************************************/
/******************************************************************************/

static void danceAudioWAVHeaderSpace(FILE *afile) {
    /* Output placeholders for the wav header */
    int i;

    for ( i=0; i<44; ++i )
	putc('\0',afile);
}

static void danceAudioWAVFillHeader(FILE *afile, int channels, int sample_rate) {
    long datalen;
    struct stat statb;

    fflush(afile);

    fstat(fileno(afile),&statb);
    datalen = statb.st_size - 44;

    rewind(afile);

    putc('R',afile);
    putc('I',afile);
    putc('F',afile);
    putc('F',afile);

    putc(((datalen+36)&0xff),afile);
    putc((((datalen+36)>>8)&0xff),afile);
    putc((((datalen+36)>>16)&0xff),afile);
    putc(((datalen+36)>>24),afile);

    putc('W',afile);
    putc('A',afile);
    putc('V',afile);
    putc('E',afile);

    putc('f',afile);
    putc('m',afile);
    putc('t',afile);
    putc(' ',afile);

    putc(16,afile);
    putc(0,afile);
    putc(0,afile);
    putc(0,afile);

    putc(1,afile);						/* PCM (raw) format */
    putc(0,afile);

    putc(channels,afile);
    putc(0,afile);

    putc((sample_rate&0xff),afile);				/* sample rate */
    putc(((sample_rate>>8)&0xff),afile);
    putc(((sample_rate>>16)&0xff),afile);
    putc((sample_rate>>24),afile);

    putc(((2*channels*sample_rate)&0xff),afile);		/* Byte rate (2 bytes/sample) */
    putc((((2*channels*sample_rate)>>8)&0xff),afile);
    putc((((2*channels*sample_rate)>>16)&0xff),afile);
    putc(((2*channels*sample_rate)>>24),afile);

    putc(2*channels,afile);					/* block align (sample size in bytes) */
    putc(0,afile);

    putc(16,afile);						/* bits per sample */
    putc(0,afile);

    putc('d',afile);
    putc('a',afile);
    putc('t',afile);
    putc('a',afile);

    putc(((datalen)&0xff),afile);
    putc((((datalen)>>8)&0xff),afile);
    putc((((datalen)>>16)&0xff),afile);
    putc(((datalen)>>24),afile);

    fflush( afile );
}

struct wav_header {
    int bitsPerSample;
    int channels;
    int sampleRate;
    long numSamples;
};

/* parses the header of a WAV file and leaves the read pointer positioned at */
/*  the start of data (the first audio sample) */
static struct wav_header *audioFileReadWAVHeader(FILE *audio, char *audioFilename, int minSampleRate, struct wav_header *hdr) {
    unsigned char buffer[44];
    int len=0;
    int bitsPerSample;
    int channels;
    int sampleRate;
    int numSamples = 0;

    memset(buffer,0,36);
    if ( audio==NULL )
return( NULL );

    len = fread((char *) buffer,1,36,audio);
    if ( !(len==36 && buffer[0]=='R' && buffer[1]=='I' && buffer[2]=='F' && buffer[3]=='F' &&
	    buffer[8]=='W' && buffer[9]=='A' && buffer[10]=='V' && buffer[11]=='E' &&
	    buffer[12]=='f' && buffer[13]=='m' && buffer[14]=='t' && buffer[15]==' ' &&
	    buffer[16]>=16 && buffer[17]==0 && buffer[18]==0 && buffer[19]==0 &&
	    buffer[20]==1 && buffer[21]==0 )) {
	fprintf( stderr, "Bad wav file: %s\n", audioFilename);
return( NULL );
    }

    bitsPerSample = buffer[34]|(buffer[35]<<8);
    channels      = buffer[22]|(buffer[23]<<8);
    sampleRate    = buffer[24]|(buffer[25]<<8)|(buffer[26]<<16)|(buffer[27]<<24);
    if ( bitsPerSample!=8 && bitsPerSample!=16 ) {
	fprintf( stderr, "Wav file with unsupported bits/sample=%d: %s\n", bitsPerSample, audioFilename);
return( NULL );
    } else if ( channels==0 ) {
	fprintf( stderr, "Wav file with no audio channels: %s\n", audioFilename);
return( NULL );
    } else if ( sampleRate<minSampleRate ) {
	fprintf( stderr, "Wav file with a sample rate below current requirement %d<%d: %s\n",
		sampleRate, minSampleRate, audioFilename);
return( NULL );
    }

    /* The fmt header may contain optional fields, if it does skip them */
    if ( buffer[16]>16 )
	fread(buffer,1,buffer[16]-16,audio);

    fread(buffer,1,8,audio);
    if ( !(buffer[0]=='d' && buffer[1]=='a' && buffer[2]=='t' && buffer[3]=='a' )) {
	fprintf( stderr, "Wav file missing data chunk: %s\n", audioFilename);
return( NULL );
    } else {
	numSamples = (buffer[4]|(buffer[5]<<8)|(buffer[6]<<16)|(buffer[7]<<24)) /
		(channels*bitsPerSample/8);
    }
    hdr->bitsPerSample = bitsPerSample;
    hdr->channels      = channels;
    hdr->sampleRate    = sampleRate;
    hdr->numSamples    = numSamples;
return( hdr );
}

static void danceAudioAppend(DanceView *dv) {
    char buffer[400], buffer2[400];
    FILE *old;
    struct wav_header header, *hdr;
    struct wav_header oheader, *ohdr;
    int ch;

    sprintf( buffer, "%s/audio.wav", dv->framedir );
    sprintf( buffer2, "%s/oldaudio.wav", dv->framedir );

    if ( access(buffer,R_OK)==-1 )
return;
    if ( access(buffer2,R_OK)==-1 )
return;
    old = fopen(buffer2,"r+");
    if ( old==NULL )
return;
    ohdr = audioFileReadWAVHeader(old,buffer2,0,&oheader);
    if ( ohdr==NULL ) {
	fclose(old);
return;
    }
    fseek(dv->audio,0L,SEEK_SET);
    hdr = audioFileReadWAVHeader(dv->audio,buffer,0,&header);
    if ( hdr->sampleRate!=ohdr->sampleRate || hdr->channels!=ohdr->channels ) {
	fprintf( stderr, "Failed to append audio due to sampleRate or channel missmatch.\n" );
	fclose(old);
return;
    }
    fseek(old,0L,SEEK_END);
    while ( (ch=getc(dv->audio))!=EOF )
	putc(ch,old);
    fclose(dv->audio);
    fseek(old,0L,SEEK_SET);
    danceAudioWAVFillHeader(old,ohdr->channels,ohdr->sampleRate);
    unlink(buffer);
    rename(buffer,buffer2);
    dv->audio = old;
}

/******************************************************************************/
/******************************************************************************/
/*                            Initialize libAV                                */
/******************************************************************************/
/******************************************************************************/

static void initializeVideoStream(DanceView *dv) {
    int is264=0, is265=1;
    int ret;

    /* My Mac's quicktime doesn't support 265 so don't use it yet */
    /* dv->codecVideo = avcodec_find_encoder_by_name("libx265");*/
    dv->codecVideo = NULL;
    if ( dv->codecVideo==NULL ) {
	is265=0;
	dv->codecVideo = avcodec_find_encoder_by_name("libx264");
	if ( dv->codecVideo==NULL ) {
	    fprintf( stderr, "Failed to find either 265 or 264 codecs.\n" );
	    if ( dv->avContext->oformat->video_codec != AV_CODEC_ID_NONE )
		dv->codecVideo = avcodec_find_encoder(dv->avContext->oformat->video_codec);
	    if ( dv->codecVideo==NULL )
exit( 1 );
	} else
	    /*fprintf( stderr, "Failed to find 265 codec, using 264.\n" )*/;
	is264=1;
    }

    dv->avVideoStream = avformat_new_stream(dv->avContext,dv->codecVideo);

    dv->codecContextVideo = avcodec_alloc_context3(dv->codecVideo);
    if ( dv->codecContextVideo==NULL ) {
	fprintf( stderr, "Failed to create a codec context for video\n" );
exit(1);
    }
    av_opt_set(dv->codecContextVideo->priv_data, "preset", "fast", 0 );
    if ( is265 )
	av_opt_set(dv->codecContextVideo->priv_data, "x265-params", "keyint=60:min-keyint=60:scenecut=0", 0 );
    else if ( is264 )
	av_opt_set(dv->codecContextVideo->priv_data, "x264-params", "keyint=60:min-keyint=60:scenecut=0:force-cfr=1", 0 );

    dv->codecContextVideo->height = dv->height;
    dv->codecContextVideo->width  = dv->width ;
    dv->codecContextVideo->sample_aspect_ratio.den = 1;
    dv->codecContextVideo->sample_aspect_ratio.num = 1;
    dv->codecContextVideo->pix_fmt = AV_PIX_FMT_YUV420P;

	/* I have no idea what these imply, I'm just copying code */
    dv->codecContextVideo->bit_rate       = 2*1000*1000;
    dv->codecContextVideo->bit_rate       =    300*1000;	/* Originally 256*1000 */
    if ( dv->vbit_rate!=0 )
	dv->codecContextVideo->bit_rate   = dv->vbit_rate;
    else if ( dv->back_img!=NULL )
	dv->codecContextVideo->bit_rate       =    400*1000;
    dv->codecContextVideo->rc_buffer_size = 4*1000*1000;
    dv->codecContextVideo->rc_max_rate    = 2*1000*1000;
    dv->codecContextVideo->rc_min_rate    = 2.5*1000*1000;

    dv->codecContextVideo->time_base.num = 1;
    dv->codecContextVideo->time_base.den = dv->frames_per_sec;
    dv->avVideoStream->time_base         = dv->codecContextVideo->time_base;

    if ( dv->avContext->oformat->flags & AVFMT_GLOBALHEADER )
        dv->codecContextVideo->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    if ( avcodec_open2(dv->codecContextVideo,dv->codecVideo,NULL)<0 ) {
	fprintf( stderr, "Failed to open video codec\n" );
exit(1);
    }
    avcodec_parameters_from_context(dv->avVideoStream->codecpar, dv->codecContextVideo);

    dv->frameVideo = av_frame_alloc();
    if (!dv->frameVideo) {
        fprintf(stderr, "Could not allocate video frame.\n");
exit(1);
    }

    dv->frameVideo->format = dv->codecContextVideo->pix_fmt;
    dv->frameVideo->width  = dv->width;
    dv->frameVideo->height = dv->height;
    dv->frameVideo->pts = 0;

    /* allocate the buffers for the frame data */
    ret = av_frame_get_buffer(dv->frameVideo, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate video frame data.\n");
exit(1);
    }
}

static void initializeAudioStream(DanceView *dv) {
    int nb_samples;

    dv->avAudioStream = avformat_new_stream(dv->avContext,NULL);

    dv->codecAudio = avcodec_find_encoder(dv->avContext->oformat->audio_codec);
    /*dv->codecAudio = avcodec_find_encoder_by_name("aac");*/
    if ( dv->codecAudio==NULL ) {
	fprintf( stderr, "Failed to find either aac codec.\n" );
exit( 1 );
    }

    dv->codecContextAudio = avcodec_alloc_context3(dv->codecAudio);
    if ( dv->codecContextAudio==NULL ) {
	fprintf( stderr, "Failed to create a codec context for audio\n" );
exit(1);
    }

    dv->codecContextAudio->channels        = dv->stereo_output ? 2 : 1;
    dv->codecContextAudio->channel_layout  = av_get_default_channel_layout(dv->codecContextAudio->channels);
    dv->codecContextAudio->sample_rate     = dv->audio_sample_rate;
    dv->codecContextAudio->sample_fmt      = dv->codecAudio->sample_fmts[0];
    dv->codecContextAudio->bit_rate        = dv->stereo_output ? 95000 : 50000;

    dv->codecContextAudio->time_base.num   = 1;
    dv->codecContextAudio->time_base.den   = dv->audio_sample_rate;
    dv->avAudioStream->time_base = dv->codecContextAudio->time_base;

    if ( avcodec_open2(dv->codecContextAudio,dv->codecAudio,NULL)<0 ) {
	fprintf( stderr, "Failed to open video(audio) codec\n" );
exit(1);
    }
    avcodec_parameters_from_context(dv->avAudioStream->codecpar, dv->codecContextAudio);

    av_dict_set(&dv->avAudioStream->metadata,"language","eng",0); /* This works */
#if 0	/* None of these got added to the stream */
    av_dict_set(&dv->avAudioStream->metadata,"title","ENG",0); /* This doesn't */
    av_dict_set(&dv->avAudioStream->metadata,"album","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"album_artist","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"artist","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"comment","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"composer","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"copyright","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"creation_time","2020-04-19",0);
    av_dict_set(&dv->avAudioStream->metadata,"date","2020-04-19",0);
    av_dict_set(&dv->avAudioStream->metadata,"disc","1",0);
    av_dict_set(&dv->avAudioStream->metadata,"encoder","d2v/libav",0);
    av_dict_set(&dv->avAudioStream->metadata,"encoded_by","GWW",0);
    av_dict_set(&dv->avAudioStream->metadata,"filename","fuf.dance",0);
    av_dict_set(&dv->avAudioStream->metadata,"genre","English Country Dance",0);
    av_dict_set(&dv->avAudioStream->metadata,"performer","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"publisher","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"service_name","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"service_provider","ENG",0);
    av_dict_set(&dv->avAudioStream->metadata,"track","1",0);
#endif

    if (dv->codecAudio->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
        nb_samples = 10000;
    else
        nb_samples = dv->codecContextAudio->frame_size;

    dv->frameAudio = av_frame_alloc();

    if (dv->frameAudio==NULL ) {
        fprintf(stderr, "Error allocating an audio frame\n");
        exit(1);
    }

    dv->frameAudio->format = dv->codecContextAudio->sample_fmt;
    dv->frameAudio->channel_layout = dv->codecContextAudio->channel_layout;
    dv->frameAudio->sample_rate = dv->codecContextAudio->sample_rate;
    dv->frameAudio->nb_samples = nb_samples;
    dv->frameAudio->pts = 0;

    if (nb_samples) {
        int ret = av_frame_get_buffer(dv->frameAudio, 0);
        if (ret < 0) {
            fprintf(stderr, "Error allocating an audio buffer\n");
exit(1);
        }
    }

}

static void danceVideoSetInitialFrameCount(DanceView *dv) {
    int max = -1, val;
    DIR *dir;
    struct dirent *f;
    char *pt;

    dir = opendir(dv->framedir);
    if ( dir==NULL )
return;

    while ( (f=readdir(dir))!=NULL ) {
	if ( f->d_name[0]!='D' )
    continue;
	if ( !isdigit(f->d_name[1]))
    continue;
	pt = strrchr(f->d_name,'.');
	if ( pt==NULL )
    continue;
	if ( strcmp(pt,".png")!=0 )
    continue;
	sscanf(pt, "D%06d", &val );
	if ( val>max )
	    max = val;
    }
    dv->former_frame_cnt = max;
}

static void danceAVSetFileMetadata(DanceView *dv) {
    time_t now;
    struct tm *tm;
    char buffer[50];
    Dance *dance = dv->dance;

    /* language doesn't work on the file metadata, but does on the stream */
    /* album, album_artist, comment, composer, copyright also work here */
    /* creation_time works, but must be in the right format */
    /* disc and track work */
    /* encoder is set by libav, my value is ignored */
    /* encoded_by doesn't work */
    /* filename, performer, publisher, service_name, service_provider don't work */

    av_dict_set(&dv->avContext->metadata,"title",dance->name,0);
    if ( dance->author!=NULL )
	av_dict_set(&dv->avContext->metadata,"artist",dance->author,0);
    time(&now);
    tm = gmtime(&now);
    sprintf( buffer, "%d-%02d-%02d", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday );
    av_dict_set(&dv->avContext->metadata,"date",buffer,0);

    if ( dance->animator!=NULL )	/* encoded_by doesn't work, but pretend it does */
	av_dict_set(&dv->avContext->metadata,"encoded_by",dance->animator,0);

    if ( dance->music.filename!=NULL ) {
	if ( dance->music.composer!=NULL )
	    av_dict_set(&dv->avContext->metadata,"composer",dance->music.composer,0);
	if ( dance->music.performers!=NULL ) {
	    av_dict_set(&dv->avContext->metadata,"performer",dance->music.performers,0);
	    av_dict_set(&dv->avContext->metadata,"album_artist",dance->music.performers,0);
	}
	if ( dance->music.album!=NULL )
	    av_dict_set(&dv->avContext->metadata,"album",dance->music.album,0);
    }

    av_dict_set(&dv->avContext->metadata,"genre",styleNames[dance->style],0);
}

void danceAVStart(DanceView *dv, struct video_options *options) {
    AVDictionary *formatOpts = NULL;

    /*av_register_all();*/	/* deprecated */

    if ( dv->output_pngs ) {
	if ( options->flags&(1<<df_appendFrames))
	    danceVideoSetInitialFrameCount(dv);

	if ( dv->audio_sample_rate>0 && danceHasAudio(dv->dance) ) {
	    char buffer[500];
	    snprintf( buffer, sizeof(buffer), "%s/audio.wav", dv->framedir );
	    dv->audio = fopen( buffer, "w+" );
	    if ( dv->audio==NULL ) {
		fprintf( stderr, "Failed to open audio output file: %s\n", buffer );
		dv->audio_sample_rate = 0;
	    } else
		danceAudioWAVHeaderSpace(dv->audio);
	}
return;
    } else if ( dv->output_svg ) {
return;
    }

    avformat_alloc_output_context2(&dv->avContext, NULL, NULL, dv->filename );
    if ( dv->avContext==NULL ) {
	avformat_alloc_output_context2(&dv->avContext, NULL, "mpeg", dv->filename );
	if ( dv->avContext==NULL ) {
	    fprintf( stderr, "Could not figure out what container to use for this extension: %s\n", dv->filename );
exit(1);
	}
    }

    initializeVideoStream(dv);
    if ( dv->audio_sample_rate!=0 )
	initializeAudioStream(dv);

    if ( dv->avContext->oformat->flags & AVFMT_GLOBALHEADER)
        dv->avContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    if (!(dv->avContext->oformat->flags & AVFMT_NOFILE)) {
	if ( avio_open( &dv->avContext->pb, dv->filename, AVIO_FLAG_WRITE) < 0 ) {
	    fprintf( stderr, "Failed to open output file: %s\n", dv->filename );
exit(1);
	}
    }

    if ( !(options->flags&(1L<<df_test)))	/* It's a little slower to write this kind of header (I think) so don't bother for tests */
	av_dict_set(&formatOpts,"movflags","faststart", 0 );

    danceAVSetFileMetadata(dv);


    if ( avformat_write_header(dv->avContext, &formatOpts ) < 0 ) {
	fprintf( stderr, "Failed to write header to output file: %s\n", dv->filename );
exit(1);
    }
}

void danceAStart(DanceView *dv, struct video_options *options) {
    AVDictionary *formatOpts = NULL;

    /*av_register_all();*/	/* deprecated */

    avformat_alloc_output_context2(&dv->avContext, NULL, NULL, dv->filename );
    if ( dv->avContext==NULL ) {
	avformat_alloc_output_context2(&dv->avContext, NULL, "mpeg", dv->filename );
	if ( dv->avContext==NULL ) {
	    fprintf( stderr, "Could not figure out what container to use for this extension: %s\n", dv->filename );
exit(1);
	}
    }

    initializeAudioStream(dv);

    if (!(dv->avContext->oformat->flags & AVFMT_NOFILE)) {
	if ( avio_open( &dv->avContext->pb, dv->filename, AVIO_FLAG_WRITE) < 0 ) {
	    fprintf( stderr, "Failed to open output file: %s\n", dv->filename );
exit(1);
	}
    }

    av_dict_set(&formatOpts,"movflags","faststart", 0 );

    danceAVSetFileMetadata(dv);


    if ( avformat_write_header(dv->avContext, &formatOpts ) < 0 ) {
	fprintf( stderr, "Failed to write header to output file: %s\n", dv->filename );
exit(1);
    }
}

/******************************************************************************/
/******************************************************************************/
/*                              Interpolation                                 */
/******************************************************************************/
/******************************************************************************/

static int interpolate(short *source,double pos,int max) {
    int base;
    double offset;
    double a,b,c,d;
    int val;

    if ( pos<0 )
return( source[0] );
    else if ( pos>=max-1 || max==1 )
return( source[max-1] );
    else if ( pos==floor(pos))
return( source[(int) pos] );
    base = floor(pos);
    offset = pos-base;

    if ( max<=3 ) {	/* already handled max==1 */
	/* Use linear interp if we have only 2 or 3 samples */
return( source[base]*(1-offset) + source[base+1]*offset );
    }

    /* I want samples at base-1, base, base+1, base+2 */
    if ( base==0 ) {
	base=1;
	offset -= 1;
    } else if ( base>max-3 ) {
	--base;
	offset += 1;
	if ( base==max-2 ) {
	    --base;
	    offset += 1;
	}
    }
    
    /* y = a*t^3 + b*t^2 + c*t + d */
    /* y(0) = d */
    /* y(-1) = -a + b - c + d */
    /* y(1)  =  a + b + c + d */
    /* y(1) + y(-1) = 2*b + 2*d */
    /* (y(1)+y(-1))/2 - y(0) = b */
    /* y(2) = 8*a + 4*b + 2*c + d */
    /* y(2) - y(0) + 4*y(0) - 2*y(1) - 2*y(-1) = 8*a + 2*c */
    /* y(2) - 2*y(1) - 2*y(-1) + 3*y(0) = 8*a + 2*c */
    /* y(1) - y(0) - ((y(1)+y(-1))/2 - y(0)) = a + c */
    /* y(1) - y(-1) = 2*a + 2*c */
    /* y(2) - 3*y(1) - y(-1) + 3*y(0) = 6*a */
    /* (y(2) - 3*y(1) + 3*y(0) - y(-1)/ 6 = a */
    /* 3*y(1) - 3*y(-1) = y(2) - 3*y(1) + 3*y(0) - y(-1) + 6*c */
    /* (-y(2) + 6*y(1) - 3*y(0) - 2*y(-1))/ 6 = c */

    d = source[base];
    c = (-source[base+2] + 6*source[base+1] - 3*source[base] - 2*source[base-1])/6.0;
    b = (source[base+1] + source[base-1])/2.0 - source[base];
    a = (source[base+2] - 3*source[base+1] + 3*source[base] - source[base-1])/ 6.0;

    assert( base>=1 );
    assert( base<=max-3 );
    assert( source[base-1] == round( -a + b - c + d ) );
    assert( source[base+1] == round(  a + b + c + d ) );
    assert( source[base+2] == round( 8*a + 4*b + 2*c + d ) );

    val = round( d + offset*(c + offset*(b + offset*a)));
    if ( val>32767 )
	val = 32767;
    else if ( val<-32768 )
	val = -32768;
return( val );
}

/******************************************************************************/
/******************************************************************************/
/*                             Get Audio Sample                               */
/******************************************************************************/
/******************************************************************************/

static int danceAVAppendPacket(DanceAudioTimeline *at ) {
    int response = avcodec_send_packet(at->codecContext,at->packet);
    int i, chan, val;
    short *opt0, *opt1, *opt;
    unsigned char *pt;

    at->translated_buffer_samples = INTERP_OFFSET;

    if ( response<0 ) {
	at->translated_buffer_samples = 0;
return( response );
    }

    while ( response>=0 ) {
	response = avcodec_receive_frame(at->codecContext,at->frame);
	if ( response==AVERROR(EAGAIN) || response==AVERROR_EOF )
    break;
	else if ( response<0 ) {
	    at->translated_buffer_samples -= INTERP_OFFSET;
return( response );
	}

	if ( at->translated_buffer_samples + at->frame->nb_samples > at->translated_buffer_len ) {
	    at->translated_buffer_len = at->translated_buffer_samples+ 2*at->frame->nb_samples;
	    at->translatedFrameData[0] = realloc(at->translatedFrameData[0],at->translated_buffer_len*sizeof(short));
	    if ( at->current_stereo || at->translatedFrameData[1]!=NULL )
		at->translatedFrameData[1] = realloc(at->translatedFrameData[1],at->translated_buffer_len*sizeof(short));
	} else if ( at->current_stereo && at->translatedFrameData[1]==NULL )
	    at->translatedFrameData[1] = malloc(at->translated_buffer_len*sizeof(short));
	if ( at->translated_buffer_samples==INTERP_OFFSET ) {
	    memcpy(at->translatedFrameData[0],at->endOfLastPacket[0],INTERP_OFFSET*sizeof(short));
	    if ( at->current_stereo )
		memcpy(at->translatedFrameData[1],at->endOfLastPacket[1],INTERP_OFFSET*sizeof(short));
	    /*at->translated_pts = at->frame->pts;*/	/* Wrong! pts should be interpolated */
	    at->translated_pts = at->translated_next_pts;
	}
	    
	opt0 = at->translatedFrameData[0] + at->translated_buffer_samples;
	if ( at->current_stereo )
	    opt1 = at->translatedFrameData[1] + at->translated_buffer_samples;
	else
	    opt1 = NULL;
	pt  = at->frame->data[0];

	if (( at->frame->format>=AV_SAMPLE_FMT_U8 && at->frame->format<=AV_SAMPLE_FMT_DBL) ||
		at->frame->format == AV_SAMPLE_FMT_S64 ) {
	    for ( i=0; i<at->frame->nb_samples; ++i ) {
		for ( chan=0; chan<at->codecContext->channels; ++chan ) {
		    /* Convert everything to a signed 16 bit sample */
		    /* Data are already in native endian-ness, but are they aligned? Mmm. says they are aligned to the natural alignment of the machine, which is 64bits nowadays so even doubles are good */
		    switch ( at->frame->format ) {
		      case AV_SAMPLE_FMT_U8:
			/* Unsigned byte, offset by 128 */
			val = 256* (*pt++ -0x80);
		      break;
		      case AV_SAMPLE_FMT_S16:
			val = *((short *) pt);
			pt += sizeof(short);
		      break;
		      case AV_SAMPLE_FMT_S32:
			val = *((int32_t *) pt)>>16;
			pt += sizeof(int32_t);
		      break;
		      case AV_SAMPLE_FMT_S64:
			val = *((int64_t *) pt)>>48;
			pt += sizeof(int64_t);
		      break;
		      case AV_SAMPLE_FMT_FLT:
			val = (int) (*((float *) pt)*32767);
			if ( val>32677 ) val = 32767; else if ( val<-32768 ) val = -32768;
			pt += sizeof(float);
		      break;
		      case AV_SAMPLE_FMT_DBL:
			val = (int) (*((double *) pt)*32767);
			if ( val>32677 ) val = 32767; else if ( val<-32768 ) val = -32768;
			pt += sizeof(double);
		      break;
		      default:
			/* Should not get here */
		      break;
		    }
		    if ( chan==0 )
			*opt0++ = val;
		    else if ( chan==1 )
			*opt1++ = val;
		    else
			/* Ignore it */;
		}
	    }
	} else if ( at->frame->format>=AV_SAMPLE_FMT_U8P && at->frame->format<=AV_SAMPLE_FMT_S64P &&
		at->frame->format != AV_SAMPLE_FMT_S64 ) {
	    /*int sample_size = av_get_bytes_per_sample(at->frame->format);*/
	    /* I expect my channels interleaved rather than separated. So undo all that work */
	    for ( chan=0; chan<at->codecContext->channels && chan<2; ++chan ) {
		pt  = at->frame->data[chan];
		opt = chan==0 ? opt0 : opt1;
		for ( i=0; i<at->frame->nb_samples; ++i ) {
		    /* Convert everything to a signed 16 bit sample */
		    /* Data are already in native endian-ness, but are they aligned? Mmm. says they are aligned to the natural alignment of the machine, which is 64bits nowadays so even doubles are good */
		    switch ( at->frame->format ) {
		      case AV_SAMPLE_FMT_U8P:
			/* Unsigned byte, offset by 128 */
			*opt++ = 256* (*pt++ -0x80);
		      break;
		      case AV_SAMPLE_FMT_S16P:
			*opt++ = *((short *) pt);
			pt += sizeof(short);
		      break;
		      case AV_SAMPLE_FMT_S32P:
			*opt++ = *((int32_t *) pt)>>16;
			pt += sizeof(int32_t);
		      break;
		      case AV_SAMPLE_FMT_S64P:
			*opt++ = *((int64_t *) pt)>>48;
			pt += sizeof(int64_t);
		      break;
		      case AV_SAMPLE_FMT_FLTP:
			val = (int) (*((float *) pt)*32767);
			/* Sigh. We really do get values outside the permitted range. Clamp them */
			if ( val>32677 ) val = 32767; else if ( val<-32768 ) val = -32768;
			*opt++ = val;
			pt += sizeof(float);
		      break;
		      case AV_SAMPLE_FMT_DBLP:
			val = (int) (*((double *) pt)*32767);
			if ( val>32677 ) val = 32767; else if ( val<-32768 ) val = -32768;
			*opt++ = val;
			pt += sizeof(double);
		      break;
		      default:
			/* Should not get here */
		      break;
		    }
		}
	    }
	}
	at->translated_buffer_samples += at->frame->nb_samples;
    }
    if ( at->translated_buffer_samples>INTERP_OFFSET ) {	/* Read at least 1 sample */
	memcpy(at->endOfLastPacket[0],at->translatedFrameData[0]+at->translated_buffer_samples-INTERP_OFFSET,INTERP_OFFSET*sizeof(short));
	if ( at->current_stereo )
	    memcpy(at->endOfLastPacket[1],at->translatedFrameData[1]+at->translated_buffer_samples-INTERP_OFFSET,INTERP_OFFSET*sizeof(short));
    }
    at->translated_buffer_samples -= INTERP_OFFSET;
    at->translated_next_pts = at->translated_pts + at->translated_buffer_samples;
return( 0 );
}

static void danceAVCloseAudioChunk(DanceAudioTimeline *at) {
    if ( !at->active->dont_close ) {
	av_packet_free(&at->packet);
	av_frame_free(&at->frame);
	avcodec_free_context(&at->codecContext);
	avformat_close_input(&at->avContext);
	avformat_free_context(at->avContext);
	at->avContext = NULL;
	at->translated_pts = 0;
	at->translated_next_pts = 0;
    }
    at->upcoming = at->active->next;
    at->active = NULL;
}

static int danceAVGetNextAudioFrame(DanceAudioTimeline *at) {

    while ( av_read_frame(at->avContext,at->packet)>=0 ) {
	if ( at->packet->stream_index==at->audioIndex ) {	/* Is it our audio stream */
	    if ( danceAVAppendPacket(at)<0 ) {
		fprintf( stderr, "Failed to decode an audio packet in %s.\n", at->active->filename );
		av_packet_unref(at->packet);
    break;
	    } else if ( at->translated_buffer_samples!=0 ) {
		av_packet_unref(at->packet);
return(true);		/* We read something */
	    }
	    /* We successfully read nothing. Maybe we'll have better luck */
	    /*  with the next packet */
	    av_packet_unref(at->packet);
	}
    }

    danceAVCloseAudioChunk(at);
return( false );
}
	    
static void danceAVSkipAudioFramesToStart(DanceView *dv, DanceAudioTimeline *at) {
    while ( at->active!=NULL && at->translated_next_pts < at->active->interpolate_start )
	danceAVGetNextAudioFrame(at);
}

static int danceAVOpenAudioInput(DanceView *dv, DanceAudioTimeline *at) {
    AVFormatContext *avContext;
    AVCodecParameters *codecParams;
    AVCodec *codec;
    AVCodecContext *codecContext;
    AVStream *avStream;
    AVFrame *frame;
    AVPacket *packet;
    int audioIndex;
    int i;

    at->active = at->upcoming;
    at->upcoming = NULL;
    if ( at->active==NULL )
return( 0 );

    if ( at->active->dont_open ) {
	if ( at->active->from_buffer ) {
	    dv->read_bpt = dv->buffered_samples;
return( true );
	}
	if ( at->avContext==NULL ) {
	    fprintf( stderr, "Audio music chunk marked do not open, but no context was available.\n" );
	    at->upcoming = at->active->next;
return( 0 );
	}
	danceAVSkipAudioFramesToStart(dv,at);
return( 1 );
    }
	    
    if ( access(at->active->filename,R_OK)==-1 ) {
	fprintf( stderr, "Cannot read audio file %s\n", at->active->filename );
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }

    avContext = avformat_alloc_context();
    if ( avContext==NULL ) {
	fprintf( stderr, "Failed to allocate a format context (before even looking at %s).\n", at->active->filename );
	at->upcoming = at->active->next;
	at->active = NULL;
exit(1);
    }
    if ( avformat_open_input(&avContext,at->active->filename,NULL,NULL) != 0 ) {
	fprintf( stderr, "Failed to open %s.\n", at->active->filename );
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }

    if ( avformat_find_stream_info( avContext, NULL )<0 ) {
	fprintf( stderr, "Failed to gather stream info %s.\n", at->active->filename );
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }

    codec       = NULL;
    codecParams = NULL;
    avStream    = NULL;
    audioIndex  = -1;
    /* Look for an audio stream */
    for ( i=0 ; i<avContext->nb_streams ; ++i ) {
	avStream = avContext->streams[i];
	codecParams = avStream->codecpar;
	if ( codecParams->codec_type != AVMEDIA_TYPE_AUDIO )
    continue;		/* Not interested in video today */
	codec = avcodec_find_decoder(codecParams->codec_id);
	if ( codec==NULL ) {
	    fprintf( stderr, "Warning: Failed to find a codec for stream %d of %s (ignored).\n", i, at->active->filename );
    continue;
	}
	if ( codecParams->format<AV_SAMPLE_FMT_U8 || codecParams->format>AV_SAMPLE_FMT_S64P ) {
	    fprintf( stderr, "Warning: Audio stream with an unsupported sample size in stream %d of %s (ignored).\n", i, at->active->filename );
	    codec = NULL;
    continue;
	}
	audioIndex = i;
    break;
    }
    if ( codec==NULL ) {
	fprintf( stderr, "Failed to find an audio stream in %s.\n", at->active->filename );
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }

    codecContext = avcodec_alloc_context3(codec);
    if ( codecContext==NULL ) {
	fprintf( stderr, "Failed to allocate a codec context for audio in %s.\n", at->active->filename );
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }
    if ( avcodec_parameters_to_context(codecContext,codecParams)<0 ) {
	fprintf( stderr, "Failed to copy codec parameters for audio in %s.\n", at->active->filename );
	avcodec_free_context(&codecContext);
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }
    if ( avcodec_open2(codecContext,codec, NULL)<0 ) {
	fprintf( stderr, "Failed to open codec for audio in %s.\n", at->active->filename );
	avcodec_free_context(&codecContext);
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }

    if ( codecParams->sample_rate!=dv->audio_sample_rate ) {
	at->active->interpolate_start = 0;
	at->active->interpolate_end = avStream->duration;
    }

    frame = av_frame_alloc();
    if ( frame==NULL ) {
	fprintf( stderr, "Failed to allocate an audio frame in %s.\n", at->active->filename );
	avcodec_free_context(&codecContext);
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }

    packet = av_packet_alloc();
    if ( packet==NULL ) {
	fprintf( stderr, "Failed to allocate an audio packet in %s.\n", at->active->filename );
	/*av_packet_free(&packet); */
	av_frame_free(&frame);
	avcodec_free_context(&codecContext);
	avformat_close_input(&avContext);
	avformat_free_context(avContext);
	at->upcoming = at->active->next;
	at->active = NULL;
return( 0 );
    }

    at->avContext = avContext;
    at->codecParams = codecParams;
    at->codec = codec;
    at->codecContext = codecContext;
    at->stream = avStream;
    at->frame = frame;
    at->packet = packet;
    at->audioIndex = audioIndex;
    at->current_stereo = codecParams->channels>1;

    at->translated_buffer_samples = 0;
    memset(at->endOfLastPacket,0,sizeof(at->endOfLastPacket));
    at->translated_next_pts = 0;
return( 1 );
}

static double danceAVFigureAmplify(DanceView *dv, char *filename) {
    DanceAudioTimeline fake;
    AudioTimelineChunk fakeChunk;
    int i, pos;
    int val,max;
    short *ipt0, *ipt1;
    double amplify;

    for ( i=0; i<CACHE_MAX && ampcache[i].filename!=NULL; ++i ) {
	if ( strcmp(ampcache[i].filename,filename)==0 )
return( ampcache[i].amplify );
    }
    pos = i;

    memset(&fake,0,sizeof(fake));
    memset(&fakeChunk,0,sizeof(fakeChunk));
    fake.upcoming = &fakeChunk;
    fakeChunk.filename = filename;

    if ( !danceAVOpenAudioInput(dv,&fake))
return( 0 );

    max = 0;
    while ( danceAVGetNextAudioFrame( &fake )) {
	ipt0 = fake.translatedFrameData[0];
	ipt1 = fake.translatedFrameData[1];
	for ( i=0; i<fake.translated_buffer_samples; ++i ) {
	    val = *ipt0++;
	    if ( val>max )
		max = val;
	    else if ( -val>max )
		max = -val;
	    if ( fake.current_stereo ) {
		val = *ipt1++;
		if ( val>max )
		    max = val;
		else if ( -val>max )
		    max = -val;
	    }
	}
    }
    
    /* danceAVCloseAudioChunk(&fake); */ /* This is done by GetNextAudioFrame */
    free(fake.translatedFrameData[0]);
    free(fake.translatedFrameData[1]);

    /* We want the maximum value of the input to be mapped to 80% of maximum possible value */
    if ( max==0 )
return( 1 );
    amplify = .8 * 32767 / (double) max;
    if ( amplify<1 )
	amplify = 1;
    addToAmplifyCache(pos,filename,amplify);

return( amplify );
}

/* Returns true if we provided a sample, false otherwise */
/* always provide two channels. If the input is mono then it's the same channel twice */
static int danceAVTimelineGetNextAudioSample(DanceView *dv, DanceAudioTimeline *at,
	long current_time_in_44k, int sample[2]) {
    int input_pts;
    AudioTimelineChunk *active;

    if ( current_time_in_44k==0 ) {
	at->active = NULL;
	at->upcoming = at->first;
    }
    forever {
	if ( at->active==NULL ) {
	    if ( at->upcoming==NULL || at->upcoming->start_time_in_44k>current_time_in_44k ) {
		sample[0] = 0;
		sample[1] = 0;
return(true);
	    }
	    danceAVOpenAudioInput(dv,at);
	    if ( at->active==NULL )
    continue;
	}
	active = at->active;

	if ( active->from_buffer ) {
	    if ( dv->read_bpt >= dv->write_bpt ) {
		danceAVCloseAudioChunk(at);
    continue;
	    }		
	    sample[0] = *(dv->read_bpt++);
	    sample[1] = *(dv->read_bpt++);
return(true);
	}

	input_pts = current_time_in_44k - active->start_time_in_44k;
	if ( active->interpolate_end==0 ||
		active->result_len==active->interpolate_end-active->interpolate_start ) {
	    /* Input and output at the same sample rate, no interpolation needed */
	    input_pts += active->interpolate_start;
	    if ( active->interpolate_end!=0 && input_pts>=active->interpolate_end ) {
		danceAVCloseAudioChunk(at);
    continue;
	    }
	    if ( input_pts>=at->translated_pts+at->translated_buffer_samples || at->translated_buffer_samples==0 ) {
		if ( !danceAVGetNextAudioFrame( at))
    continue;
	    }
	    sample[0] = at->translatedFrameData[0][input_pts-at->translated_pts + INTERP_OFFSET];
	    if ( at->current_stereo )
		sample[1] = at->translatedFrameData[1][input_pts-at->translated_pts + INTERP_OFFSET];
	    else
		sample[1] = sample[0];
    break;
	} else {
	    int last_frame = at->stream->duration<=at->translated_pts + at->translated_buffer_samples;
	    double ipts = input_pts *	/* Interpolated pts */
		    (active->interpolate_end-active->interpolate_start) /
		    (double) active->result_len +
		   active->interpolate_start;

	    if ( ipts >= active->interpolate_end ) {
		danceAVCloseAudioChunk(at);
    continue;
	    }
	    /* If we are 3 or fewer samples from the end (INTERP_OFFSET) then */
	    /*  we want to read in a new packet, because we can't interpolate */
	    /*  properly with so few samples to work with. BUT if we're at the*/
	    /*  last frame then do the best we can with what we've got (because*/
	    /*  there is no more) */
	    if (( last_frame && ipts>=at->translated_pts+at->translated_buffer_samples ) ||
		    (!last_frame && ipts>=at->translated_pts+at->translated_buffer_samples-INTERP_OFFSET )) {
		if ( !danceAVGetNextAudioFrame( at))
    continue;
	    }
	    sample[0] = interpolate(at->translatedFrameData[0],ipts - at->translated_pts+INTERP_OFFSET, at->translated_buffer_samples+INTERP_OFFSET);
	    if ( at->current_stereo )
		sample[1] = interpolate(at->translatedFrameData[1],ipts - at->translated_pts, at->translated_buffer_samples+INTERP_OFFSET);
	    else
		sample[1] = sample[0];
    break;
	}
    }

    if ( active->fadein_samples!=0 && current_time_in_44k < active->start_time_in_44k+active->fadein_samples ) {
	float frac = (current_time_in_44k-active->start_time_in_44k) / (float) active->fadein_samples;
	float amplify = 1*frac + active->fadein_initial_level*(1-frac);
	sample[0] *= amplify;
	sample[1] *= amplify;
    }

    if ( active->amplify!=0 ) {
	sample[0] *= active->amplify;
	sample[1] *= active->amplify;
    }

    if ( at->active!=NULL && at->active->save_to_buffer ) {
	if ( dv->write_bpt>=dv->max_buf ) {
	    fprintf( stderr, "Attempt to write too many samples into the buffer.\n" );
exit(1);
	}
	*dv->write_bpt++ = sample[0];
	*dv->write_bpt++ = sample[1];
    }
return( true );
}

static void audio_fill_output_frame(DanceView *dv, long end) {
    long t;
    int i, chan;
    unsigned char *fpt;
    unsigned char *fpt1;
    int samples[2], timelinesamples[2];

    av_frame_make_writable(dv->frameAudio);

    fpt  = dv->frameAudio->data[0];
    fpt1 = dv->frameAudio->data[1];

    if ( end==0 )
	end = dv->audio_samples_pts+dv->frameAudio->nb_samples;
    for ( t=dv->audio_samples_pts; t<end; ++t ) {
	samples[0] = samples[1] = 0;
	for ( i=0; i<MAX_AUDIO_TIMELINES; ++i ) {
	    DanceAudioTimeline *at = &dv->audios[i];
	    danceAVTimelineGetNextAudioSample(dv,at,t,timelinesamples);
	    if ( !at->only_right_samples )
		samples[0] += at->amplify*timelinesamples[0];
	    if ( !at->only_left_samples )
		samples[1] += at->amplify*timelinesamples[1];
	}
	if ( samples[0]>32767 )
	    samples[0]=32767;
	else if ( samples[0]<-32767 )	/* Yeah, I know it can go to 32768 but that makes some conversions difficult */
	    samples[0]=-32767;
	if ( samples[1]>32767 )
	    samples[1]=32767;
	else if ( samples[1]<-32767 )
	    samples[1]=-32767;

	if ((( dv->frameAudio->format>=AV_SAMPLE_FMT_U8 && dv->frameAudio->format<=AV_SAMPLE_FMT_DBL) ||
		dv->frameAudio->format == AV_SAMPLE_FMT_S64 ) ||
		dv->codecContextAudio->channels==1 ) {
	    for ( chan=0; chan<dv->codecContextAudio->channels; ++chan ) {
		switch ( dv->frameAudio->format ) {
		  case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P:
		    /* Unsigned byte, offset by 128 */
		    *fpt++ = (samples[chan]>>8) + 0x80;
		  break;
		  case AV_SAMPLE_FMT_S16: case AV_SAMPLE_FMT_S16P:
		    *(short *) fpt = samples[chan];
		    fpt += sizeof(short);
		  break;
		  case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P:
		    *(int32_t *) fpt = samples[chan]<<16;
		    fpt += sizeof(int32_t);
		  break;
		  case AV_SAMPLE_FMT_S64: case AV_SAMPLE_FMT_S64P:
		    *(int64_t *) fpt = ((int64_t) samples[chan])<<48;
		    fpt += sizeof(int64_t);
		  break;
		  case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP:
		    *(float *) fpt = samples[chan]/32767.0;
		    fpt += sizeof(float);
		  break;
		  case AV_SAMPLE_FMT_DBL: case AV_SAMPLE_FMT_DBLP:
		    *(double *) fpt = samples[chan]/32767.0;
		    fpt += sizeof(double);
		  break;
		  default:
		    /* Should not get here */
		  break;
		}
	    }
	} else {
	    switch ( dv->frameAudio->format ) {
	      case AV_SAMPLE_FMT_U8P:
		/* Unsigned byte, offset by 128 */
		*fpt++  = (samples[0]>>8) - 0x80;
		*fpt1++ = (samples[1]>>8) - 0x80;
	      break;
	      case AV_SAMPLE_FMT_S16P:
		*(short *) fpt = samples[0];
		*(short *) fpt1 = samples[1];
		fpt += sizeof(short);
		fpt1 += sizeof(short);
	      break;
	      case AV_SAMPLE_FMT_S32P:
		*(int32_t *) fpt = samples[0]<<16;
		*(int32_t *) fpt1= samples[1]<<16;
		fpt += sizeof(int32_t);
		fpt1 += sizeof(int32_t);
	      break;
	      case AV_SAMPLE_FMT_S64P:
		*(int64_t *) fpt = ((int64_t) samples[0])<<48;
		*(int64_t *) fpt1= ((int64_t) samples[1])<<48;
		fpt += sizeof(int64_t);
		fpt1 += sizeof(int64_t);
	      break;
	      case AV_SAMPLE_FMT_FLTP:
		*(float *) fpt = samples[0]/32767.0;
		*(float *) fpt1= samples[1]/32767.0;
		fpt += sizeof(float);
		fpt1+= sizeof(float);
	      break;
	      case AV_SAMPLE_FMT_DBLP:
		*(double *) fpt = samples[0]/32767.0;
		*(double *) fpt1= samples[1]/32767.0;
		fpt += sizeof(double);
		fpt1+= sizeof(double);
	      break;
	      default:
		/* Should not get here */
	      break;
	    }
	}
    }
    dv->frameAudio->nb_samples = end - dv->audio_samples_pts;
    dv->frameAudio->pts = dv->audio_samples_pts;
    dv->audio_samples_pts += dv->frameAudio->nb_samples;
}

static int audio_encode_frame(DanceView *dv, AVFrame *frameAudio) {
    int ret;

    /* Sending a NULL frame to the codec will flush the stream */
    ret = avcodec_send_frame(dv->codecContextAudio, frameAudio);
    if (ret < 0) {
        fprintf(stderr, "Error submitting a frame for encoding\n");
        exit(1);
    }
    while (ret >= 0) {
        AVPacket pkt = { 0 }; // data and size must be 0;
        av_init_packet(&pkt);
        ret = avcodec_receive_packet(dv->codecContextAudio, &pkt);
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
            fprintf(stderr, "Error encoding an audio frame\n");
exit(1);
        } else if (ret >= 0) {
            av_packet_rescale_ts(&pkt, dv->codecContextAudio->time_base, dv->avAudioStream->time_base);
            pkt.stream_index = dv->avAudioStream->index;
            /* Write the compressed frame to the media file. */
            ret = av_interleaved_write_frame(dv->avContext, &pkt);
            if (ret < 0) {
                fprintf(stderr, "Error while writing audio frame\n");
exit(1);
            }
        }
    }
return ret == AVERROR_EOF;
}

static void danceAVWriteAudioSamplesForFrame(DanceView *dv,int flush) {
    int i;
    long t;
    long start, end;
    int samples[2], timelinesamples[2];

    start = dv->audio_samples_pts;
    if ( dv->audio_output_only )
	end = dv->last_frame_cnt;
    else
	end   = floor(  dv->last_frame_cnt   *dv->audio_sample_rate/(double) dv->frames_per_sec );

    if ( dv->audio_sample_rate==0 )
return;
    if ( dv->output_pngs ) {
	if ( flush )
return;
	if ( dv->audio==NULL )
return;
	for ( t=start; t<end; ++t ) {
	    samples[0] = samples[1] = 0;
	    for ( i=0; i<MAX_AUDIO_TIMELINES; ++i ) {
		DanceAudioTimeline *at = &dv->audios[i];
		danceAVTimelineGetNextAudioSample(dv,at,t,timelinesamples);
		if ( !at->only_right_samples )
		    samples[0] += at->amplify*timelinesamples[0];
		if ( !at->only_left_samples )
		    samples[1] += at->amplify*timelinesamples[1];
	    }
	    if ( samples[0]>32767 )
		samples[0]=32767;
	    else if ( samples[0]<-32767 )	/* Yeah, I know it can go to 32768 but that makes some conversions difficult */
		samples[0]=-32767;
	    putc( ( samples[0]    &0xff), dv->audio);
	    putc( ((samples[0]>>8)&0xff), dv->audio );
	    if ( dv->stereo_output ) {
		if ( samples[1]>32767 )
		    samples[1]=32767;
		else if ( samples[1]<-32767 )
		    samples[1]=-32767;
		putc( ( samples[1]    &0xff), dv->audio);
		putc( ((samples[1]>>8)&0xff), dv->audio );
	    }
	}
	dv->audio_samples_pts = end;
    } else {
	while ( dv->audio_samples_pts + dv->frameAudio->nb_samples < end ) {
	    audio_fill_output_frame(dv,0);
	    audio_encode_frame(dv, dv->frameAudio);
	}
	if ( flush && end>dv->audio_samples_pts ) {
	    audio_fill_output_frame(dv,end);
	    audio_encode_frame(dv, dv->frameAudio);
	}
	if ( flush )
	    audio_encode_frame(dv, NULL);
    }
}

/******************************************************************************/
/******************************************************************************/
/*                             Add Video Frame                                */
/******************************************************************************/
/******************************************************************************/

/* Values for ITU-R BT.709 */
static const double KR = .2126;
static const double KB = .0722;
/* *********************** */
#define KG (1.0-(KR+KB))

static void convert_video_frame_from_cairo(DanceView *dv, cairo_surface_t *image) {
    AVCodecContext *c = dv->codecContextVideo;

    if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
	fprintf( stderr, "Unexpected frame format.\n" );
exit(1);
    } else {
	const unsigned char *pixels, *pt;
	uint8_t *ypt, *upt, *vpt;
	int rowstride, width, height;
	int r,c;
	AVFrame *frame = dv->frameVideo;
	union endiandetection {	/* Cairo's image surface is documented */
	    int32_t ival;		/*  in terms of integers, but it returns */
	    char cval[4];		/*  data as uchars. Which means we need */
	} endian;			/*  to know our endian-ness */
	int littleendian;

	av_frame_make_writable(frame);

	cairo_surface_flush(image);
  /* Before doing anything else we must convert the rgb surface to an */
  /*  YCrCb x264_pic */
	endian.ival = 4;
	littleendian= endian.cval[0]==4;
	pixels      = cairo_image_surface_get_data(image);
	width       = cairo_image_surface_get_width(image);
	height      = cairo_image_surface_get_height(image);
	rowstride   = cairo_image_surface_get_stride(image);

	for ( r=0; r<height; ++r ) {
	    pt = pixels + r*rowstride;
	    ypt = frame->data[0] + r*frame->linesize[0];
	    if ( (r&1)==0 ) {
		upt = frame->data[1] + (r>>1)*frame->linesize[1];
		vpt = frame->data[2] + (r>>1)*frame->linesize[2];
	    }
	    for ( c=0; c<width; ++c ) {
		int red, green, blue;
		int pre_y, cb/*u*/, cr/*v*/;
		if ( littleendian ) {
		    blue = pt[0]; green = pt[1]; red = pt[2];
		} else {
		    red = pt[1]; green = pt[2]; blue = pt[3];
		}
		pt += 4;
		pre_y  = (KR*red+KG*green+KB*blue);
		*ypt++ = 16 + (219*pre_y+127)/255;
		if ( (c&1)==0 && (r&1)==0 ) {
		    cb = 128 + 240/2 * (blue-pre_y)/(1-KB)/255;
		    cr = 128 + 240/2 * (red -pre_y)/(1-KR)/255;
		    *upt++ = cb;
		    *vpt++ = cr;
		}
	    }
	}
    }
}

static void write_video_frame(DanceView *dv, int flush) {
    AVCodecContext *c;
    AVFrame *frame = dv->frameVideo;
    int ret;

    c = dv->codecContextVideo;
    if ( !flush ) {
	frame->pts = dv->last_frame_cnt++;
	ret = avcodec_send_frame(c, frame);
    } else
	ret = avcodec_send_frame(c, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error submitting a frame for encoding\n");
        exit(1);
    }
    while (ret >= 0) {
        AVPacket pkt = { 0 };
        av_init_packet(&pkt);
        ret = avcodec_receive_packet(c, &pkt);
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
            fprintf(stderr, "Error encoding a video frame\n");
exit(1);
        } else if (ret >= 0) {
            av_packet_rescale_ts(&pkt, c->time_base, dv->avVideoStream->time_base);
            pkt.stream_index = dv->avVideoStream->index;
            /* Write the compressed frame to the media file. */
            ret = av_interleaved_write_frame(dv->avContext, &pkt);
            if (ret < 0) {
                fprintf(stderr, "Error while writing video frame\n");
exit(1);
            }
        }
    }
return/*( ret == AVERROR_EOF )*/;
}

static void danceAVWriteVideoFrame(DanceView *dv) {

    cairo_surface_flush(dv->frame);
    if ( dv->output_pngs ) {
	char buffer[500];
	cairo_status_t status;

	sprintf( buffer, "%s/D%06d.png", dv->framedir, ++dv->last_frame_cnt + dv->former_frame_cnt );
	status = cairo_surface_write_to_png(dv->frame,buffer);
	if ( status!=CAIRO_STATUS_SUCCESS ) {
	    fprintf( stderr, "Failed to write png file: %s\n", cairo_status_to_string(status));
exit(1);
	}
    } else {
	convert_video_frame_from_cairo(dv, dv->frame);
	write_video_frame(dv,false);
    }

    if ( (dv->last_frame_cnt&0x3f)==0 )
	fprintf( stderr, "%d\n", dv->last_frame_cnt );
}

void danceAVAddVideoFrame(DanceView *dv,int frame) {
    /* We do two things:
     *  1) we output a video frame
     *  2) we follow that with enough audio frames to fill up one frame per second
     */

    if ( dv->output_svg ) {
	danceSVGWriteVideoFrame(dv);
return;
    }
    danceAVWriteVideoFrame(dv);
    danceAVWriteAudioSamplesForFrame(dv,false);
    /*assert( frame+dv->title_frame_offset == dv->last_frame_cnt );*/
    /* It just isn't */
}

void danceAAddAudioFrames(DanceView *dv) {
    danceAVWriteAudioSamplesForFrame(dv,false);
}

/******************************************************************************/
/******************************************************************************/
/*                                Finish up                                   */
/******************************************************************************/
/******************************************************************************/

void removePngs(char *framedir) {
    DIR *dir;
    struct dirent *f;
    char buffer[500];
    char *pt;

    dir = opendir(framedir);
    if ( dir==NULL )
return;

    while ( (f=readdir(dir))!=NULL ) {
	if ( f->d_name[0]!='D' )
    continue;
	if ( !isdigit(f->d_name[1]))
    continue;
	pt = strrchr(f->d_name,'.');
	if ( pt==NULL )
    continue;
	if ( strcmp(pt,".png")!=0 )
    continue;
	snprintf( buffer, sizeof(buffer), "%s/%s", framedir, f->d_name );
	unlink( buffer );
    }
#if 1
    snprintf( buffer, sizeof(buffer), "%s/audio.wav", framedir );
    unlink( buffer );
#endif
    closedir(dir);
}

static void pngFinish(DanceView *dv, struct video_options *options ) {
    char buffer[500];
    char buffer2[500];

    if ( dv->audio_sample_rate!=0 && dv->audio!=NULL )
	danceAudioWAVFillHeader(dv->audio, dv->stereo_output?2:1, dv->audio_sample_rate);

    if ( options->flags&(1<<df_appendFrames))
	danceAudioAppend(dv);

    if ( !(options->flags&(1<<df_retainFrames)) ) {
	char cropbuf[100], *cpt="";

	if ( options->crop.width>0 && options->crop.height>0 ) {
	    sprintf( cropbuf, "-filter:v \"crop=%d:%d:%d:%d\" ",
		options->crop.width, options->crop.height,
		options->crop.x, options->crop.y );
	    cpt = cropbuf;
	}
	if ( options->gifOutput ) {
	    danceOutputAnimatedGif(dv,dv->framedir,dv->filename,&options->crop);
	    removePngs(dv->framedir);
	} else {
	    if ( dv->audio != NULL ) {
		sprintf( buffer, "ffmpeg -hide_banner -loglevel warning -y -r %d -i %s/D%%06d.png %s -i %s/audio.wav -movflags faststart -vcodec libx264 -crf 25 -pix_fmt yuv420p -shortest '%s'\n",
			dv->frames_per_sec, dv->framedir, cpt, dv->framedir, dv->filename );
		fclose(dv->audio);
	    } else
		sprintf( buffer, "ffmpeg -hide_banner -loglevel warning -y -r %d -i %s/D%%06d.png %s -movflags faststart -vcodec libx264 -crf 25 -pix_fmt yuv420p '%s'\n",
			dv->frames_per_sec, dv->framedir, cpt, dv->filename );
	printf( "%s\n", buffer );
	    if ( system( buffer )==0 ) {
		removePngs(dv->framedir);
		if ( options->flags & (1<<df_openAfter) ) {
#ifdef __APPLE__
		    sprintf( buffer, "open '%s'\n", dv->filename );
#else
		    sprintf( buffer, "ffplay '%s'\n", dv->filename );
#endif
		    system( buffer );
		}
	    }
	}
    } else {
	if ( dv->audio!=NULL ) {
	    fclose(dv->audio);
	    sprintf( buffer, "%s/audio.wav", dv->framedir );
	    sprintf( buffer2, "%s/oldaudio.wav", dv->framedir );
	    rename(buffer2, buffer);
	}
    }
}

void danceAVFinish(DanceView *dv, struct video_options *options) {

    if ( dv->output_pngs ) {
	pngFinish(dv, options );
    } else if ( dv->output_svg ) {
	danceSVGFinish(dv, options );
    } else {
	 /* Flush the streams */
	/* Video doesn't need flushing I think since one frame is one sample */
	/* Oh. It does. I think that's a bug in output.c then (or maybe I did something wrong) */
	/* Audio may have an incomplete frame to output */
	danceAVWriteAudioSamplesForFrame(dv,true);
	if ( !dv->audio_output_only )
	    write_video_frame(dv,true);

	av_write_trailer(dv->avContext);

	 /* Close the streams */
	avcodec_free_context(&dv->codecContextAudio);
	av_frame_free(&dv->frameAudio);
	if ( !dv->audio_output_only ) {
	    avcodec_free_context(&dv->codecContextVideo);
	    av_frame_free(&dv->frameVideo);
	}

	 /* Close the file */
	if (!(dv->avContext->oformat->flags & AVFMT_NOFILE)) {
	    avio_close( dv->avContext->pb );
	}

	 /* Free the context */
	avformat_free_context(dv->avContext);
	if ( options->flags & (1<<df_openAfter) ) {
	    char buffer[400];
#ifdef __APPLE__
	    sprintf( buffer, "open '%s'\n", dv->filename );
#else
	    sprintf( buffer, "ffplay '%s'\n", dv->filename );
#endif
	    system( buffer );
	}
    }
    free(dv->buffered_samples);
    {
	TimeLine *t, *n;
	AudioTimelineChunk *at, *atn;
	int i;
	for ( t=dv->timeline; t!=NULL; t = n ) {
	    n = t->next;
	    free(t);
	}
	for ( i=0; i<MAX_AUDIO_TIMELINES; ++i ) {
	    for ( at = dv->audios[i].first; at!=NULL; at=atn ) {
		atn = at->next;
		free(at->filename);
		free(at);
	    }
	    free( dv->audios[i].translatedFrameData[0] );
	    free( dv->audios[i].translatedFrameData[1] );
	}
    }
    clearAudioCache();
}

/******************************************************************************/
/******************************************************************************/
/*                               Comparisons                                  */
/******************************************************************************/
/******************************************************************************/

void danceFigureTitleFrames(DanceView *dv, Comparison *comp, struct video_options *options ) {
    DanceView temp;
    int frames_per_sec;
    int frames;
    int samples44k, music_samples44k;

    memset(&temp,0,sizeof(temp));
    temp.dance = comp->dances[0].d;
    temp.shush = true;
    danceAVFigureFramesPerSecBar(&temp, options);
    frames_per_sec = temp.frames_per_sec;
    temp.dance = comp->dances[1].d;
    danceAVFigureFramesPerSecBar(&temp, options);
    if ( frames_per_sec!=temp.frames_per_sec )
	frames_per_sec = 30;
    options->frames_per_sec = frames_per_sec;
    danceAVFigureFramesPerSecBar(&temp, options);
    dv->frames_per_sec = temp.frames_per_sec;
    dv->frames_per_bar = temp.frames_per_bar;

    if ( options->titleframes==0 ) {
	options->titleframes = ceil((audioFileSeconds(comp->dances[0].d->titleAudioFilename,dv->audio_sample_rate,&samples44k))*dv->frames_per_sec);
	frames = ceil((audioFileSeconds(comp->dances[1].d->titleAudioFilename,dv->audio_sample_rate,&samples44k))*dv->frames_per_sec);
	if ( frames>options->titleframes )
	    options->titleframes = frames;
    }

    if ( options->musictitleframes==0 ) {
	options->musictitleframes = ceil(audioFileSeconds(comp->dances[0].d->music.titleAudioFilename,dv->audio_sample_rate,&music_samples44k)*dv->frames_per_sec);
	frames = ceil(audioFileSeconds(comp->dances[1].d->music.titleAudioFilename,dv->audio_sample_rate,&music_samples44k)*dv->frames_per_sec);
	if ( frames>options->musictitleframes )
	    options->musictitleframes = frames;
    }
}

void danceAVBuildComparisonTimelines(DanceView *dv, struct video_options *options) {
    Comparison *comp = (Comparison *) dv->dance;
    TimeLine *tlLast=NULL;
    AudioTimelineChunk *atLast[MAX_AUDIO_TIMELINES];
    int i;
    int nextFrame=0, frames;
    int samples44k=0;
    char buffer[300];

    av_log_set_level(AV_LOG_ERROR);	/* libav talks too much. This should quiet it down a bit */

    for ( i=0; i<MAX_AUDIO_TIMELINES; ++i ) {
	atLast[i] = NULL;
	dv->audios[i].amplify = 1.0;
    }

    if ( dv->show_title ) {
	int title_frames = 0;

	if ( dv->audio_sample_rate>0 && comp->dance.titleAudioFilename!=NULL )
	    title_frames = ceil((audioFileSeconds(comp->dance.titleAudioFilename,dv->audio_sample_rate,&samples44k))*dv->frames_per_sec);

	if ( title_frames<2*dv->frames_per_sec )
	    title_frames = 2*dv->frames_per_sec;
	tlLast = danceAddTimeline(dv,tlLast,0,title_frames,tl_title);
	nextFrame = title_frames;

	if ( comp->dance.titleAudioFilename!=NULL )
	    atLast[AT_CALLS] = danceAddAudioChunk(dv,AT_CALLS,atLast[AT_CALLS],
		    0, samples44k,comp->dance.titleAudioFilename);
    }
    frames = dv->biggestframe[0]>dv->biggestframe[1] ? dv->biggestframe[0] : dv->biggestframe[1];
    tlLast = danceAddTimeline(dv,tlLast,nextFrame,nextFrame+frames,
	    tl_majorset);

    sprintf( buffer, "%s/audio.wav", dv->framedirs[0] );
    audioFileSeconds(buffer,dv->audio_sample_rate,&samples44k);
    atLast[AT_CLAPS] = danceAddAudioChunk(dv,AT_CLAPS,atLast[AT_CLAPS],
	    nextFrame*dv->audio_sample_rate/dv->frames_per_sec,
	    samples44k,strdup(buffer));

    sprintf( buffer, "%s/audio.wav", dv->framedirs[1] );
    audioFileSeconds(buffer,dv->audio_sample_rate,&samples44k);
    atLast[AT_COUNTS] = danceAddAudioChunk(dv,AT_COUNTS,atLast[AT_COUNTS],
	    nextFrame*dv->audio_sample_rate/dv->frames_per_sec,
	    samples44k,strdup(buffer));

    dv->audios[AT_CLAPS].only_left_samples = true;
    dv->audios[AT_COUNTS].only_right_samples = true;
}
