/* Derived from midi2abc.c
 * Copyright (C) 1998 James Allwright
 * e-mail: J.R.Allwright@westminster.ac.uk
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
 
#define VERSION "0.98 January 30 2025 midistats"

/* midistrats.c is a descendent of midi2abc.c which was becoming to
   large. The object of the program is to extract statistical characterisitic 
   of a midi file. It is mainly called by the midiexplorer.tcl application,
   but it now used to create some databases using runstats.tcl which
   comes with the midiexplorer package.

   By default the program produces a summary that is described in the
   midistats.1 man file. This is done by making a single pass through
   the midi file. If the program is called with one of the runtime
   options, the program extracts particular information by making more
   than one pass. In the first pass it creates a table of all the
   midievents which is stored in memory. The midievents are sorted in
   time, and the requested information is extracted by going through
   this table.
*/

#include <limits.h>
/* Microsoft Visual C++ Version 6.0 or higher */
#ifdef _MSC_VER
#define snprintf _snprintf
#define ANSILIBS
#endif

#include <stdio.h>
#include <math.h>
#ifdef PCCFIX
#define stdout 1
#endif

/* define USE_INDEX if your C libraries have index() instead of strchr() */
#ifdef USE_INDEX
#define strchr index
#endif

#ifdef ANSILIBS
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#else
extern char* malloc();
extern char* strchr();
#endif
#include "midifile.h"
void initfuncs();
void stats_finish();
float histogram_perplexity (int *histogram, int size); 
float histogram_entropy (int *histogram, int size); 
void stats_noteoff(int chan,int pitch,int vol);
void stats_eot ();
void keymatch();
void outputChannelSummary(); 
void clearTrackNm ();

#define max(a,b)  (( a > b ? a : b))
#define min(a,b) (( a < b ? a : b))

/* Global variables and structures */

extern long Mf_toberead;

static FILE *F;
static FILE *outhandle; /* for producing the abc file */

int tracknum=0;  /* track number */
int lasttrack = 0; /* lasttrack */
int division;    /* pulses per quarter note defined in MIDI header    */
int halfdivision; /* pulses per eighth note */
int quietLimit;  /* minimum number of pulses with no activity */
long tempo = 500000; /* the default tempo is 120 quarter notes/minute */
int bpm = 120; /*default tempo */
long laston = 0; /* length of MIDI track in pulses or ticks           */
int key[12];
int sharps;
int trackno;
int maintrack;
int format; /* MIDI file type                                   */
int debug;
int noOutput;
int pulseanalysis;
int percanalysis;
int percpattern;
int percpatternfor;
int percpatternhist;
int pitchclassanalysis;
int nseqfor;
int corestats;
int chordthreshold; /* number of maximum number of pulses separating note */
int beatsPerBar = 4; /* 4/4 time */
int divisionsPerBar;
int unitDivision;
int maximumPulse;
int lastBeat;
int hasLyrics = 0;


struct eventstruc {int onsetTime;
                   unsigned char channel;
		   unsigned char pitch;
		   unsigned char velocity;
                   ;} midievents[50000];

int lastEvent = 0;


int tempocount=0;  /* number of tempo indications in MIDI file */

int stats = 0; /* flag - gather and print statistics            */

int pulseanalysis = 0;
int percanalysis = 0;
int percpattern = 0;
int percpatternfor = 0;
int percpatternhist = 0;
int pitchclassanalysis = 0;
int nseqfor = 0;
int corestats = 0;
int noOutput = 0;


/* can cope with up to 64 track MIDI files */
int trackcount = 0;

int notechan[2048],notechanvol[2048]; /*for linking on and off midi
					channel commands            */
int lastTick[2048]; /* for getting last pulse number for chan (0-15) and pitch (0-127) in MIDI file */
int last_on_tick[17]; /* for detecting chords [SS] 2019-08-02 */
int channel_active[17]; /* for dealing with chords [SS] 2023-08-30 */
int channel_used_in_track[17]; /* for dealing with quietTime [SS] 2023-09-06 */

int histogram[256];
unsigned char drumpat[8000];
unsigned char pseq[8000];
int pseqhist[128];
int percnum;
int nseqchn;
int nseqdistinct;



/* The following variables are used by the -stats option
 * which is used by a separate application called midiexplorer.tcl.
 * The channel numbers go from 1 to 16 instead of 0 to 15
 */
struct trkstat {
  int notecount[17];
  int chordcount[17];
  int notemeanpitch[17];
  int notepitchmin[17];
  int notepitchmax[17];
  int notelength[17];
  int notelengthmin[17];
  int notelengthmax[17];
  int pitchbend[17];
  int pressure[17];
  int cntlparam[17];
  int program[17];
  int tempo[17];
  int npulses[17];
  int lastNoteOff[17];
  int quietTime[17];
  int rhythmpatterns[17];
  int numberOfGaps[17];
  int chanvol[17];
  float pitchEntropy[17];
  } trkdata;

/* The trkstat references the individual channels in the midi file.
 * notecount is the number of notes or bass notes in the chord.
 * chordcount is the number of notes not counting the bass notes.
 * notemeanpitch is the average pitch for the channel.
 * notelength is the average note length.
 * pitchbend is the number of pitch bends for the channel.
 * pressure is the number of control pressure commands.
 * cntlparam is the number of control parameter commands.
 * program is number of times there is a program command for the channel.
 * tempo is the number of times there is a tempo command.
 * npulses is the number of pulses.
 */

struct notememory {int eighthUnit;
                   int nowPitch;
                   int beforePitch;
                   int previousPitch;
                   int zeroCount;
                   int stepCount;
                   int jumpCount;
                   int totalNotes;
                   int totalPitches;
                   int quietTime;
                   int used;
                   } nm[17];

struct notememory tracknm;

int progcolor[17]; /* used by stats_program */
int drumhistogram[100]; /* counts drum noteons */
int pitchhistogram[12]; /* pitch distribution for non drum notes */
int channel2prog[17]; /* maps channel to program */
int channel2nnotes[17]; /*maps channel to note count */
int chnactivity[17]; /* [SS] 2018-02-02 */
int trkactivity[40]; /* [SS] 2023-10-25 */
int progactivity[128]; /* [SS] 2018-02-02 */
int pitchclass_activity[12]; /* [SS] 2018-02-02 */
int chanpitchhistogram[204]; /* [SS] 2023-09-13 */


/* [SS] 2017-11-01 */
static int progmapper[] = {
 0,  0,  0,  0,  0,  0,  0,  0,
 0,  1,  1,  1,  1,  1,  1,  2,
 3,  3,  3,  3,  3,  3,  3,  3,
 2,  2,  4,  4,  4,  4,  4,  2,
 5,  5,  5,  5,  5,  5,  5,  5,
 6,  6,  6,  6,  6,  2,  7, 10,
 7,  7,  7,  7,  8,  8,  8,  8,
 9,  9,  9,  9,  9,  9,  9,  9,
11, 11, 11, 11, 11, 11, 11, 11,
12, 12, 12, 12, 12, 12, 12, 12,
13, 13, 13, 13, 13, 13, 13, 13,
14, 14, 14, 14, 14, 14, 14, 14,
15, 15, 15, 15, 15, 15, 15, 15,
 2,  2,  2,  2,  2, 12,  6, 12,
 1,  1, 10, 10, 10, 10, 10,  1,
16, 16, 16, 16, 16, 16, 16, 16
};

int pulseCounter[2048];
int pulseDistribution[24];

struct barPattern {
	int activeBarNumber;
	int rhythmPattern;
} barChn[17];


#define HashSize 257 
struct hashStruct {
	int pattern[HashSize];
	int count[HashSize];
} hasher[17] = {0};

int ncollisions = 0;
int nrpatterns = 0;
int nseqdistinct = 0;

void handle_collision () {
ncollisions++;
}

void put_pattern (int chan, int pattern) {
int hashindex;
hashindex = pattern % HashSize;
if (hasher[chan].pattern[hashindex] == 0) {
	hasher[chan].pattern[hashindex] = pattern;
	nrpatterns++;
	/*printf ("hasher[%d].pattern[%d] = %d\n",chan,hashindex,pattern);*/
  } else if (hasher[chan].pattern[hashindex] != pattern) {
	 /* printf("collision\n"); */
	  handle_collision ();
  } else {
  hasher[chan].count[hashindex]++;
  }
}


int count_patterns_for (int chan) {
int i;
int sum;
sum = 0;
for (i = 0; i<HashSize; i++) {
  if (hasher[chan].pattern[i] > 0) sum++;  
  /* hasher[chan].pattern[i] = 0;  reset hasher */
  }
trkdata.rhythmpatterns[chan] = sum;
return sum;
}


void output_hasher_results () {
int i;
/* printf("rhythmPatterns ");*/
for (i = 0; i<17; i++) {
	trkdata.rhythmpatterns[i] = count_patterns_for(i);
/*	printf("%d ",count_patterns_for (i)); */
       }
}


int filegetc()
{
    return(getc(F));
}


void fatal_error(s)
char* s;
/* fatal error encounterd - abort program */
{
  fprintf(stderr, "%s\n", s);
  exit(1);
}


void event_error(s)
char *s;
/* problem encountered but OK to continue */
{
  char msg[256];

  sprintf(msg, "Error: Time=%ld Track=%d %s\n", Mf_currtime, trackno, s);
  printf("%s",msg);
}


int* checkmalloc(bytes)
/* malloc with error checking */
int bytes;
{
  int *p;

  p = (int*) malloc(bytes);
  if (p == NULL) {
    fatal_error("Out of memory error - cannot malloc!");
  };
  return (p);
}




FILE *
efopen(name,mode)
char *name;
char *mode;
{
    FILE *f;

    if ( (f=fopen(name,mode)) == NULL ) {
      char msg[256];
      sprintf(msg,"Error: - Cannot open file %s",name);
      fatal_error(msg);
    }
    return(f);
}

void error(s)
char *s;
{
    fprintf(stderr,"Error: %s\n",s);
}


/* [SS] 2017-11-19 */
void stats_error(s)
char *s;
{
    fprintf(stderr,"Error: %s\n",s);
    fprintf(stderr,"activetrack %d\n",tracknum);
    stats_finish();
}




/* Dummy functions for handling MIDI messages.
 *    */
 void no_op0() {}
 void no_op1(int dummy1) {}
 void no_op2(int dummy1, int dummy2) {}
 void no_op3(int dummy1, int dummy2, int dummy3) { }
 void no_op4(int dummy1, int dummy2, int dummy3, int dummy4) { }
 void no_op5(int dummy1, int dummy2, int dummy3, int dummy4, int dummy5) { }



/* In order to associate a channel note off message with its
 * corresponding note on message, we maintain the information
 * the notechan array. When a midi pitch (0-127) is switched
 * on for a particular channel, we record the time that it
 * was turned on in the notechan array. As there are 16 channels
 * and 128 pitches, we initialize an array 128*16 = 2048 elements
 * long.
**/
void init_notechan()
{
/* signal that there are no active notes */
 int i;
 for (i = 0; i < 2048; i++) notechan[i] = -1;
}



char * pitch2key(int note)
{
static char name[16]; /* [SDG] 2020-06-03 */
char* s = name;
  switch(note % 12)
  {
  case 0: *s++ = 'c'; break;
  case 1: *s++ = 'c'; *s++ = '#'; break;
  case 2: *s++ = 'd'; break;
  case 3: *s++ = 'd'; *s++ = '#'; break;
  case 4: *s++ = 'e'; break;
  case 5: *s++ = 'f'; break;
  case 6: *s++ = 'f'; *s++ = '#'; break;
  case 7: *s++ = 'g'; break;
  case 8: *s++ = 'g'; *s++ = '#'; break;
  case 9: *s++ = 'a'; break;
  case 10: *s++ = 'a'; *s++ = '#'; break;
  case 11: *s++ = 'b'; break;
  }
  sprintf(s, "%d", (note / 12)-1);  /* octave  (assuming Piano C4 is 60)*/
  return  name;
}


void pitch2drum(midipitch)
int midipitch;
{
static char *drumpatches[] = {
 "Acoustic Bass Drum", "Bass Drum 1", "Side Stick", "Acoustic Snare",
 "Hand Clap", "Electric Snare", "Low Floor Tom", "Closed Hi Hat",
 "High Floor Tom", "Pedal Hi-Hat", "Low Tom", "Open Hi-Hat",
 "Low-Mid Tom", "Hi Mid Tom", "Crash Cymbal 1", "High Tom",		
 "Ride Cymbal 1", "Chinese Cymbal", "Ride Bell", "Tambourine",
 "Splash Cymbal", "Cowbell", "Crash Cymbal 2", "Vibraslap",
 "Ride Cymbal 2", "Hi Bongo", "Low Bongo",	"Mute Hi Conga",
 "Open Hi Conga", "Low Conga", "High Timbale", "Low Timbale",
 "High Agogo", "Low Agogo", "Cabasa", "Maracas",
 "Short Whistle", "Long Whistle", "Short Guiro", "Long Guiro",
 "Claves", "Hi Wood Block", "Low Wood Block", "Mute Cuica",
 "Open Cuica", "Mute Triangle", "Open Triangle" };
if (midipitch >= 35 && midipitch <= 81) {
  printf(" (%s)",drumpatches[midipitch-35]);
  }
}




void stats_header (int format, int ntrks, int ldivision)
{
  int i;
  division = ldivision;
  if(division < 12) {
    printf("Error: division (PPQN) is too small\n");
    exit(1);
    }
  if(ntrks > 40) {
    printf("Error: too many tracks = %d\n",ntrks);
    exit(1);
    }
  halfdivision = ldivision/2;
  quietLimit = ldivision*8;
  divisionsPerBar = division*beatsPerBar;
  unitDivision = divisionsPerBar/24;
  lasttrack = ntrks; /* [SS] 2023-10-25 */
  if (noOutput == 0) printf("ntrks %d\n",ntrks);
  if (noOutput == 0) printf("ppqn %d\n",ldivision);
  chordthreshold = ldivision/16; /* [SS] 2018-01-21 */
  trkdata.tempo[0] = 0;
  trkdata.pressure[0] = 0;
  trkdata.program[0] = 0;
  for (i=0;i<17;i++) {
    trkdata.npulses[i] = 0;
    trkdata.pitchbend[i] = 0;
    trkdata.cntlparam[i] = 0; /* [SS] 2022-03-04 */
    trkdata.pressure[i] = 0; /* [SS] 2022-03-04 */
    trkdata.quietTime[i] = 0; /* [SS] 2022-08-22 */
    trkdata.numberOfGaps[i] = 0; /* [SS] 2023-09-07 */
    trkdata.chanvol[i] = 0; /* [SS] 2023-10-30 */
    progcolor[i] = 0;
    channel2prog[i] = 0; /* [SS] 2023-06-25-8*/
    channel2nnotes[i] = 0;
    chnactivity[i] = 0; /* [SS] 2018-02-02 */
    }
  for (i=0;i<100;i++) drumhistogram[i] = 0;
  for (i=0;i<12;i++) pitchhistogram[i] = 0; /* [SS] 2017-11-01 */
  for (i=0;i<12;i++) pitchclass_activity[i] = 0; /* [SS] 2018-02-02 */
  for (i=0;i<128;i++) progactivity[i] = 0; /* [SS] 2018-02-02 */
  for (i=0;i<40;i++) trkactivity[i]=0; /* [SS] 2023-10-25 */
}

void determine_progcolor ()
{
int i;
for (i=0;i<17;i++) progcolor[i] =0;
for (i=0;i<128;i++) {
  progcolor[progmapper[i]] += progactivity[i];
  }
}


/* [SS] 2018-04-24 */
void output_progs_data () {
int i;
/* check that there is valid progactivity */

  printf("progs ");
  for (i=0;i<128;i++) 
    if(progactivity[i] > 0) printf(" %d",i);
  printf("\nprogsact ");
  for (i=0;i<128;i++) 
    if(progactivity[i] > 0) printf(" %d",progactivity[i]);
  }


/* [SS] 2023-10-30 */
void stats_interpret_pulseCounter () {
int i,j;
int maxcount,ncounts,npeaks,npositives,peaklimit;
int maxloc;
float threshold,peak;
int decimate;
float tripletsCriterion8,tripletsCriterion4;
int resolution = 12;
int nzeros;
threshold = 10.0/(float) division;
maxcount = 0;
ncounts = 0;
npeaks = 0;
if (division > 2047) return; /* PPQN is too large for pulseCounter */
for (i=0;i<division;i++) {
  ncounts = ncounts + pulseCounter[i];
  if (pulseCounter[i] > maxcount) {
       maxloc = i;
       maxcount = pulseCounter[i];
       } 
  } 
peaklimit = (int) (ncounts * 0.020);
for (i=0;i<division;i++) {
  if (pulseCounter[i] > peaklimit) npeaks++;
  }
for (i = 0; i < resolution; i++) pulseDistribution[i] = 0;
decimate = division/resolution;
for (i = 0; i < division; i++) {
   j = i/decimate;
   pulseDistribution[j] += pulseCounter[i];
  }

/* count zeros */
nzeros = 0;
for (i=0;i<resolution;i++) if((float) pulseDistribution[i]/(float) ncounts < 0.015 ) nzeros++;
npositives = resolution - nzeros;
if (nzeros > 3 && (float) pulseDistribution[resolution-1]/(float) ncounts < 0.1) {printf("clean_quantization\n");
 } else if ((float) pulseDistribution[resolution-1]/(float) ncounts > 0.09 ||
            npeaks > npositives) {printf("dithered_quantization\n");
 } else {
peak = (float) maxcount/ (float) ncounts;
if (peak < threshold)  printf("unquantized\n");
}

tripletsCriterion8 = (float) pulseDistribution[8]/ (float) ncounts;
tripletsCriterion4 = (float) pulseDistribution[4]/ (float) ncounts;
if (tripletsCriterion8 > 0.10 || tripletsCriterion4 > 0.10) printf("triplets\n");
if (pulseDistribution[0]/(float) ncounts > 0.95) printf("qnotes");
}

void stats_finish()
{
int i; /* [SDG] 2020-06-03 */
int npulses;
int nprogs;
double delta;


npulses = trkdata.npulses[0]; 
printf("npulses %d\n",trkdata.npulses[0]);
printf("tempocmds %d\n",trkdata.tempo[0]);
printf("pitchbends %d\n",trkdata.pitchbend[0]);
for (i=1;i<17;i++) {
  if (trkdata.pitchbend[i] > 0) {
    printf("pitchbendin %d %d\n",i,trkdata.pitchbend[i]); }
    }
 
if (trkdata.pressure[0] > 0) 
  printf("pressure %d\n",trkdata.pressure[0]);
printf("programcmd %d\n",trkdata.program[0]);

nprogs = 0; /* [SS] 2018-04-24 */
for (i=1;i<128;i++)
  if(progactivity[i] >0) nprogs++;
if (nprogs > 0) output_progs_data();
else {
  for (i=0;i<17;i++) 
    if(chnactivity[i] > 0) 
       progactivity[channel2prog[i]] += chnactivity[i]; /* [SS] 2023-06-25 */
  output_progs_data();
  }

determine_progcolor();
printf("\nprogcolor ");
if (npulses > 0)
  for (i=0;i<17;i++) printf("%5.2f ",progcolor[i]/(double) npulses);
else 
  for (i=0;i<17;i++) printf("%5.2f ",(double) progcolor[i]);
printf("\n");
  
printf("drums ");
for (i=35;i<100;i++) {
  if (drumhistogram[i] > 0) printf("%d ",i);
  }
printf("\ndrumhits ");
for (i=35;i<100;i++) {
  if (drumhistogram[i] > 0) printf("%d ",drumhistogram[i]);
  }

printf("\npitches "); /* [SS] 2017-11-01 */
for (i=0;i<12;i++) printf("%d ",pitchhistogram[i]);

keymatch();

printf("\npitchact "); /* [SS] 2018-02-02 */
if (npulses > 0)
  for (i=0;i<12;i++) printf("%5.2f ",pitchclass_activity[i]/(double) npulses);
else 
  for (i=0;i<12;i++) printf("%5.2f ",(double) pitchclass_activity[i]);
printf("\nchanvol "); /* [SS] 2023-10-30 */
for (i=1;i<17;i++) printf("%4d ",trkdata.chanvol[i]);
printf("\nchnact "); /* [SS] 2018-02-08 */
if (npulses > 0)
  for (i=1;i<17;i++) printf("%5.3f ",chnactivity[i]/(double) trkdata.npulses[0]);
else 
  for (i=0;i<17;i++) printf("%5.3f ",(double) chnactivity[i]);
printf("\ntrkact ");
  lasttrack++;
  for (i=0;i<lasttrack;i++) printf("% 5d",trkactivity[i]);
printf("\npitchperplexity %f\n",histogram_perplexity(pitchclass_activity,12));
printf("\npitchentropy %f\n",histogram_entropy(pitchclass_activity,12));
printf("totalrhythmpatterns =%d\n",nrpatterns);
printf("collisions = %d\n",ncollisions);
if (hasLyrics) printf("Lyrics\n");
stats_interpret_pulseCounter ();
printf("\n");
}



float histogram_perplexity (int *histogram, int size) 
  {
  /* The perplexity is 2 to the power of the entropy */
  int i;
  int total;
  float entropy;
  float e,p;
  total = 0;
  entropy = 0.0;
  //printf("\nhistogram_entropy of:");
  for (i=0;i<size;i++) {
    total += histogram[i];
    //printf(" %d",histogram[i]);
    } 
  for (i=0;i<size;i++) {
    if (histogram[i] < 1) continue;
    p = (float) histogram[i]/total;
    e = p*log(p);
    entropy = entropy + e;
    } 
  //printf("\n");
  return pow(2.0,-entropy/log(2.0));
  } 

float histogram_entropy (int *histogram, int size) 
  {
  /* The perplexity is 2 to the power of the entropy */
  int i;
  int total;
  float entropy;
  float e,p;
  total = 0;
  entropy = 0.0;
  //printf("\nhistogram_entropy of:");
  for (i=0;i<size;i++) {
    total += histogram[i];
    //printf(" %d",histogram[i]);
    } 
  for (i=0;i<size;i++) {
    if (histogram[i] < 1) continue;
    p = (float) histogram[i]/total;
    e = p*log(p);
    entropy = entropy + e;
    } 
  //printf("\n");
  return -entropy/log(2.0);
  } 




void output_count_trkdata(data_array,name)
int data_array[];
char *name;
{
int i,sum;
sum = 0;
for (i=1;i<17;i++) sum += data_array[i];
if (sum != 0) {
      for (i=0;i<17;i++)
        if(data_array[i]>0) {
          printf("%s ",name);
          printf("%d %d ",i,data_array[i]);
          printf("\n");
          }
     }
}



void output_track_summary () {
int i;
/* find first channel containing data */
output_hasher_results();
for (i=1;i<17;i++) {
   if(trkdata.notecount[i] == 0 && trkdata.chordcount[i] == 0) continue; 
   printf("trkinfo ");
   printf("%d %d ",i,trkdata.program[i]); /* channel number and program*/
   printf("%d %d ",trkdata.notecount[i],trkdata.chordcount[i]);
   /* [SS] 2023-08-22 */
   if (i != 10) printf("%d %d ",trkdata.notemeanpitch[i], trkdata.notelength[i]);
   else 
     printf("-1  0 ");
   printf("%d ",trkdata.pitchbend[i]); /* [SS] 2024-07-26 */
   printf("%d %d ",trkdata.cntlparam[i],trkdata.pressure[i]); /* [SS] 2022-03-04 */
   printf("%d %d ",trkdata.quietTime[i],trkdata.rhythmpatterns[i]);
   if (i != 10)  {printf("%d %d %d %d %d",trkdata.notepitchmin[i],  trkdata.notepitchmax[i] ,trkdata.notelengthmin[i],  trkdata.notelengthmax[i], trkdata.numberOfGaps[i]);
   printf(" %f",trkdata.pitchEntropy[i]);
    } else
     printf("-1 0");
   if (lasttrack > 1) printf(" %d %d %d\n",tracknm.zeroCount,tracknm.stepCount,tracknm.jumpCount);
   else
             printf(" %d %d %d\n",nm[i-1].zeroCount,nm[i-1].stepCount,nm[i-1].jumpCount);
   printf("\n");

   channel2nnotes[i] += trkdata.notecount[i] + trkdata.chordcount[i];
  }
}




void stats_trackstart()
{
  int i;
  tracknum++;
  clearTrackNm (); 
  for (i=0;i<17;i++) {
     trkdata.notecount[i] = 0;
     trkdata.notemeanpitch[i] = 0;
     trkdata.notepitchmin[i] = 128;
     trkdata.notepitchmax[i] = 0;
     trkdata.notelength[i] = 0;
     trkdata.notelengthmin[i] = 10000;
     trkdata.notelengthmax[i] = 0;
     trkdata.chordcount[i] = 0;
     trkdata.cntlparam[i] = 0;
     last_on_tick[i] = -1;
     channel_active[i] = 0;
     }
 if (noOutput == 0) printf("trk %d \n",tracknum);

 for (i=0;i<2048;i++) lastTick[i] = -1;
 for (i=0;i<17;i++) channel_used_in_track[i] = 0; /* [SS] 2023-09-06 */
 for (i=0;i<204;i++) chanpitchhistogram[i] = 0;  /* [SS] 2023-09-13 */
}

void stats_trackend()
{
 int chan;
 int i;
 float entropy;
 if (trkdata.npulses[0] < Mf_currtime) trkdata.npulses[0] = Mf_currtime;
 for (chan = 1; chan < 17; chan++) /* [SS] 2023-09-06 */
   if (channel_used_in_track[chan] > 0) trkdata.quietTime[chan] += (trkdata.npulses[0] - trkdata.lastNoteOff[chan]); 
 for (chan=0;chan<16;chan++) {  /* 2023-09-13 */
   if (chan == 9 || channel_used_in_track[chan+1] == 0) continue;
   trkdata.pitchEntropy[chan+1] = histogram_perplexity(chanpitchhistogram +chan*12,11);
   }
if (noOutput == 0)  output_track_summary();
output_hasher_results();
for (i=0;i<17;i++) {
   if(trkdata.notecount[i] == 0 && trkdata.chordcount[i] == 0) continue; 
   nm[i].quietTime += trkdata.quietTime[i];
   nm[i].used = channel_used_in_track[i];
   trkdata.quietTime[i] = 0; /* in case channel i is used in another track */
   trkdata.numberOfGaps[i] = 0;
   channel2nnotes[i] += trkdata.notecount[i] + trkdata.chordcount[i];
   }
}


void clearNotememory () {
 int i;
 for (i=0;i<17;i++) {
    nm[i].eighthUnit = 0;
    nm[i].nowPitch = 0;
    nm[i].beforePitch = 0;
    nm[i].previousPitch = 0;
    nm[i].zeroCount = 0;
    nm[i].stepCount = 0;
    nm[i].jumpCount = 0;
    nm[i].totalNotes =0;
    nm[i].totalPitches =0;
    nm[i].quietTime = 0;
    nm[i].used = 0;
    }
}

void clearTrackNm () {
    tracknm.eighthUnit = 0;
    tracknm.nowPitch = 0;
    tracknm.beforePitch = 0;
    tracknm.previousPitch = 0;
    tracknm.zeroCount = 0;
    tracknm.stepCount = 0;
    tracknm.jumpCount = 0;
    tracknm.totalNotes = 0;
    tracknm.totalPitches = 0;
}
 
void updateNotememory (int unit, int chn, int pitch) {
int deltaPitch;
if (chn == 9) return;
if (unit == nm[chn].eighthUnit) {
   if (pitch > nm[chn].nowPitch)  nm[chn].nowPitch = pitch;
   return;
   }
/* unit is different */
nm[chn].beforePitch = nm[chn].nowPitch;
nm[chn].nowPitch = pitch;
if (nm[chn].previousPitch > 0)
  {
  deltaPitch = nm[chn].beforePitch - nm[chn].previousPitch;
  if (deltaPitch < 0) deltaPitch = -deltaPitch;
  if (deltaPitch == 0) nm[chn].zeroCount++;
  else if (deltaPitch < 4) nm[chn].stepCount++;
  else nm[chn].jumpCount++;
  }
if (nm[chn].beforePitch != 0) nm[chn].previousPitch = nm[chn].beforePitch;
nm[chn].eighthUnit = unit;
nm[chn].totalNotes++;
nm[chn].totalPitches = nm[chn].totalPitches + pitch;
}

void updateTrackNotememory (int unit, int chn, int pitch) {
int deltaPitch;
if (chn == 9) return;
if (unit == tracknm.eighthUnit) {
   if (pitch > tracknm.nowPitch)  tracknm.nowPitch = pitch;
   return;
   }
/* unit is different */
tracknm.beforePitch = tracknm.nowPitch;
tracknm.nowPitch = pitch;
if (tracknm.previousPitch > 0)
  {
  deltaPitch = tracknm.beforePitch - tracknm.previousPitch;
  if (deltaPitch < 0) deltaPitch = -deltaPitch;
  if (deltaPitch == 0) tracknm.zeroCount++;
  else if (deltaPitch < 4) tracknm.stepCount++;
  else tracknm.jumpCount++;
  }
if (tracknm.beforePitch != 0) tracknm.previousPitch = tracknm.beforePitch;
tracknm.eighthUnit = unit;
/*printf("%d, %d, %d, %d, %d, %d %d\n",unit,nm[chn].beforePitch,nm[chn].previousPitch,\
  deltaPitch,nm[chn].zeroCount,nm[chn].stepCount,nm[chn].jumpCount);
*/

}





void stats_noteon(chan,pitch,vol)
int chan, pitch, vol;
{
 int delta;
 int barnum;
 int unit;
 int eigthunit;
 int dithermargin; /* [SS] 2023-08-22 */
 int cpitch; /* [SS] 2023-09-13 */
 int pulsePosition;
 int beatnumber;

 cpitch = pitch % 12;
 channel_used_in_track[chan+1]++; /* [SS] 2023-09-06 */
 dithermargin = unitDivision/2 - 1; 
 if (vol == 0) {
    /* treat as noteoff */
    stats_noteoff(chan,pitch,vol);
    trkdata.lastNoteOff[chan+1] = Mf_currtime; /* [SS] 2022.08.22 */
    return;
    }
 pulsePosition = Mf_currtime % division;
 if (pulsePosition >= 2047) {printf("Error: pulsePosition = %d too large\n",pulsePosition);
     exit(1);
     }
 pulseCounter[pulsePosition]++;
 trkdata.notemeanpitch[chan+1] += pitch;
 trkdata.notepitchmax[chan+1] = max(trkdata.notepitchmax[chan+1],pitch);
 trkdata.notepitchmin[chan+1] = min(trkdata.notepitchmin[chan+1],pitch);
 if (trkdata.lastNoteOff[chan+1] >= 0) {
	 delta = Mf_currtime - trkdata.lastNoteOff[chan+1];
	 if (delta > quietLimit) {
		 trkdata.quietTime[chan+1] += delta;
                 trkdata.numberOfGaps[chan+1]++;
	         trkdata.lastNoteOff[chan+1] = -1; /* in case of chord */
	 }
    }
	 
 if (abs(Mf_currtime - last_on_tick[chan+1]) < chordthreshold) trkdata.chordcount[chan+1]++;
 else trkdata.notecount[chan+1]++; /* [SS] 2019-08-02 */
 lastTick[chan*128 + pitch] = Mf_currtime;
 last_on_tick[chan+1] = Mf_currtime; /* [SS] 2019-08-02 */
 /* last_on_tick not updated by stats_noteoff */


 if (chan != 9) {
   barnum = Mf_currtime/divisionsPerBar;
   if (barnum != barChn[chan].activeBarNumber) {
	 /*printf("%d %d %d\n",chan,barChn[chan].activeBarNumber,
			 barChn[chan].rhythmPattern);      */
         put_pattern (chan+1, barChn[chan].rhythmPattern);

	 barChn[chan].rhythmPattern = 0;
         barChn[chan].activeBarNumber = barnum;
    }
  unit = ((Mf_currtime+dithermargin) % divisionsPerBar)/unitDivision;
  //printf("unit = %d pattern = %d \n",unit,barChn[chan].rhythmPattern);
  barChn[chan].rhythmPattern = barChn[chan].rhythmPattern |= (1UL << unit);
  chanpitchhistogram[chan*12+cpitch]++;  /* [SS] 2023-09-13 */

  eigthunit = Mf_currtime/halfdivision;
  beatnumber = Mf_currtime/division;

  updateNotememory (beatnumber,  chan,  pitch); 
  updateTrackNotememory (beatnumber,  chan,  pitch); 
  }



 if (chan == 9) {
   if (pitch < 0 || pitch > 100) 
         printf("****illegal drum value %d\n",pitch);
   else  drumhistogram[pitch]++;
   }
 else pitchhistogram[pitch % 12]++;  /* [SS] 2017-11-01 */

 channel_active[chan+1]++;
}


void stats_eot () {
trkdata.lastNoteOff[0] = Mf_currtime; /* [SS] 2022.08.24 */
}

void stats_noteoff(int chan,int pitch,int vol)
{
  int length;
  int program;
  /* ignore if there was no noteon */
  if (lastTick[chan*128+pitch] == -1) return;
  length = Mf_currtime - lastTick[chan*128+pitch];
  trkdata.notelength[chan+1] += length;
  trkdata.notelengthmax[chan+1] = max(trkdata.notelengthmax[chan+1],length);
  trkdata.notelengthmin[chan+1] = min(trkdata.notelengthmin[chan+1],length);
  //if (length < 3) printf("chan = %d  lasttick = %d currtime = %ld\n",chan,lastTick[chan*128+pitch],Mf_currtime);
  trkdata.lastNoteOff[chan+1] = Mf_currtime; /* [SS] 2022.08.22 */
  chnactivity[chan+1] += length;
  trkactivity[tracknum]++;
  if (chan == 9) return; /* drum channel */
  pitchclass_activity[pitch % 12] += length;
  program = trkdata.program[chan+1];
  progactivity[program] += length;
  channel_active[chan+1]--;
  /* [SS] 2018-04-18 */
  if(Mf_currtime > lastTick[chan*128+pitch] && channel_active[chan+1] == 0) 
    lastTick[chan*128+pitch] = Mf_currtime; /* [SS] 2023.08.30 handle chords */
    
  if (length > 4800) {
     lastTick[chan*128+pitch] = Mf_currtime; /* handle stuck note [SS] 2023.08.30 */
     channel_active[chan+1] = 0;
     }
}


void stats_pitchbend(chan,lsb,msb)
int chan, lsb, msb;
{
trkdata.pitchbend[0]++;
trkdata.pitchbend[chan+1]++;
}

void stats_pressure(chan,press)
int chan, press;
{
trkdata.pressure[0]++;
trkdata.pressure[chan+1]++; /* [SS] 2022.04.28 */
}



void stats_program(chan,program)
int chan, program;
{
int beatnumber;
if (program <0 || program > 127) return; /* [SS] 2018-03-06 */
if (trkdata.program[chan+1] != 0) {
  beatnumber = Mf_currtime/division;
  if (noOutput == 0) printf("cprogram %d %d %d\n",chan+1,program,beatnumber);
  /* count number of times the program was modified for a channel */
  trkdata.program[0] = trkdata.program[0]+1;
  } else {
  if (noOutput == 0) printf("program %d %d\n",chan+1,program);
  trkdata.program[chan+1] = program;
  }
  if (channel2prog[chan+1]== 0) channel2prog[chan+1] = program; /* [SS] 2023-06-25*/
}



void stats_parameter(chan,control,value)
int chan, control, value;
{
int chan1;
chan1 = chan+1;
/* There may be many volume commands for the same channel. Only
   record the first one.
*/
if (control == 7 && trkdata.chanvol[chan1] == 0) {
  /*printf("cntrlvolume %d %d \n",chan+1,value);*/
  trkdata.chanvol[chan1] = value; /* [SS] 2023-10-30 */
  }
trkdata.cntlparam[chan1]++;
}



void stats_metatext(type,leng,mess)
int type, leng;
char *mess;
{
int i; 
if (type == 5) hasLyrics = 1; /* [SS] 2023-10-30 */
if (type != 3) return;
if (noOutput == 1) return; 
printf("metatext %d ",type);
for (i=0;i<leng;i++) printf("%c",mess[i]);
printf("\n");
}



/* [SS] 2018-01-02 */
void stats_keysig(sf,mi)
int sf, mi;
{
  float beatnumber;
  static char *major[] = {"Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F",
    "C", "G", "D", "A", "E", "B", "F#", "C#"};
  static char *minor[] = {"Abmin", "Ebmin", "Bbmin", "Fmin", "Cmin",
    "Gmin", "Dmin", "Amin", "Emin", "Bmin", "F#min", "C#min", "G#min"};
  int index;
  beatnumber = Mf_currtime/division;
  index = sf + 7;
  if (index < 0 || index >12) return;
  if (mi)
    if (noOutput == 0) 
       printf("keysig %s %d %d %6.2f\n",minor[index],sf,mi,beatnumber);
  else
    if (noOutput == 0) 
       printf("keysig %s %d %d %6.2f\n",major[index],sf,mi,beatnumber);
}



/* [SS] 2018-01-02 */
void stats_tempo(ltempo)
long ltempo;
{
  float beatnumber;
  tempo = ltempo;
  beatnumber = Mf_currtime/division;
  trkdata.tempo[0]++;
  if (noOutput == 1) return;
  if (trkdata.tempo[0] == 1) printf("tempo %6.2f bpm\n",60000000.0/tempo);
  else if (trkdata.tempo[0] < 10) printf("ctempo  %6.2f %6.2f\n",60000000.0/tempo,beatnumber);
}


void stats_timesig(nn,dd,cc,bb)
int nn, dd, cc, bb;
{
  float beatnumber;
  int denom = 1;
  beatnumber = Mf_currtime/division;
  while ( dd-- > 0 )
    denom *= 2;
  if (noOutput == 1) return;
  printf("timesig %d/%d %6.2f\n",nn,denom,beatnumber);
} 

void record_noteon(int chan,int pitch,int vol)
{
if (maximumPulse < Mf_currtime) maximumPulse = Mf_currtime;
if (vol < 1) return; /* noteoff ? */
midievents[lastEvent].onsetTime = Mf_currtime;
midievents[lastEvent].channel = chan;
midievents[lastEvent].pitch = pitch;
midievents[lastEvent].velocity = vol;
lastEvent++;
if (lastEvent > 49999) {printf("ran out of space in midievents structure\n");
	exit(1);
        }
/*if (lastEvent < 20) {printf("record_noteon %d %d %d %ld\n",chan,pitch,vol,Mf_currtime/halfdivision);}*/
channel_active[chan+1]++;
}

void record_noteoff(int chan,int pitch,int vol)
{
}
void record_trackend()
{
}
void record_tempo(long ltempo)
{ 
tempo = ltempo;
if (bpm == 120) bpm = 60000000.0/tempo;
tempocount++;
}

int int_compare_events(const void *a, const void *b) {
   struct eventstruc *ia  = (struct eventstruc *)a;
   struct eventstruc *ib  = (struct eventstruc *)b;
   if (ib->onsetTime > ia->onsetTime)
       return -1;
   else if (ia ->onsetTime > ib->onsetTime)
       return  1;
   else return 0;
   }

void load_header (int format, int ntrks, int ldivision)
{
  int i;
  division = ldivision;
  halfdivision = ldivision/2;
  lasttrack = ntrks;
  for (i=0;i<17;i++) channel_active[i] = 0; /* for counting number of channels*/
}


void initfunc_for_stats()
{
    int i;
    Mf_error = stats_error; /* [SS] 2017-11-19 */
    Mf_header = stats_header;
    Mf_trackstart = stats_trackstart;
    Mf_trackend = stats_trackend;
    Mf_noteon = stats_noteon;
    Mf_noteoff = stats_noteoff;
    Mf_pressure = no_op3;
    Mf_parameter = stats_parameter;
    Mf_pitchbend = stats_pitchbend;
    Mf_program = stats_program;
    Mf_chanpressure = stats_pressure;
    Mf_sysex = no_op2;
    Mf_metamisc = no_op3;
    Mf_seqnum = no_op1;
    Mf_eot = stats_eot;
    Mf_timesig = stats_timesig;
    Mf_smpte = no_op5;
    Mf_tempo = stats_tempo;
    Mf_keysig = stats_keysig;
    Mf_seqspecific = no_op3;
    Mf_text = stats_metatext;
    Mf_arbitrary = no_op2;
    for (i = 0; i< 2047; i++) pulseCounter[i] = 0;
}


void initfunc_for_loadNoteEvents()
{
    Mf_error = error;
    Mf_header = load_header;
    Mf_trackstart = no_op0;
    Mf_trackend = record_trackend;
    Mf_noteon = record_noteon;
    Mf_noteoff = record_noteoff;
    Mf_pressure = no_op3;
    Mf_parameter = no_op3;
    Mf_pitchbend = no_op3;
    Mf_program = no_op0;
    Mf_chanpressure = no_op3;
    Mf_sysex = no_op2;
    Mf_metamisc = no_op3;
    Mf_seqnum = no_op1;
    Mf_eot = no_op0;
    Mf_timesig = no_op4;
    Mf_smpte = no_op5;
    Mf_tempo = record_tempo;
    Mf_keysig = no_op2;
    Mf_seqspecific = no_op3;
    Mf_text = no_op3;
    Mf_arbitrary = no_op2;
}

void dumpMidievents (int from , int to)
{
int i;
for (i = from; i < to; i++) {
  printf("%5d %d %d %d\n",midievents[i].onsetTime, midievents[i].channel,
		  midievents[i].pitch, midievents[i].velocity);
  }
}

void load_finish()
{
qsort(midievents,lastEvent,sizeof(struct eventstruc),int_compare_events);
/*dumpMidievents(0,50);*/
}


void pulseHistogram() {
int i,j;
int pulsePosition;
int decimate;
float fraction;
int resolution = 12;
for (i = 0; i< 2047; i++) pulseCounter[i] = 0;
for (i = 0; i < lastEvent; i++) {
  pulsePosition = midievents[i].onsetTime % division;
  pulseCounter[pulsePosition]++;
  if (pulsePosition > 2047) {printf("pulsePosition = %d too large\n",pulsePosition);
	     exit(1);
             }
  }
for (i = 0; i < resolution; i++) pulseDistribution[i] = 0;
  /*for (i = 0; i < 1023; i++) printf(" %d",pulseCounter[i]);
  printf("\n");
  */
decimate = division/resolution;
for (i = 0; i < division; i++) {
   j = i/decimate;
   pulseDistribution[j] += pulseCounter[i];
   }
for (i = 0; i < resolution; i++)
   {fraction = (float) pulseDistribution[i] / (float) lastEvent;
    if (i == 0)
      printf("%6.4f",fraction);
    else
      printf(",%6.4f",fraction);
   }
printf("\n");
}

void drumanalysis () {
int i;
int channel;
int pitch;
for (i=0;i<100;i++) drumhistogram[i] = 0;
for (i = 0; i < lastEvent; i++) {
  channel = midievents[i].channel;
  if (channel != 9) continue;
  pitch = midievents[i].pitch;
  if (pitch < 0 || pitch > 100) {
    printf("illegal percussion instrument %d\n",pitch);
    exit(1);
    }
  drumhistogram[pitch]++;
  }
}

void drumpattern (int perc) {
int i;
int channel;
int pitch;
int onset;
int index;
int quarter;
int remainder;
int part;
quarter = division/4;
for (i = 0; i<8000; i++) drumpat[i] = 0;
for (i = 0; i <lastEvent; i++) {
  channel = midievents[i].channel;
  if (channel != 9) continue;
  pitch = midievents[i].pitch;
  if (pitch != perc) continue;
  onset = midievents[i].onsetTime;
  index = onset/division;
  remainder = onset % division;
  part = remainder/quarter;
  if (index >= 8000) {printf("index too large in drumpattern\n");
	              break;
                      }
  part = 3 - part; /* order the bits going left to right */
  drumpat[index] = drumpat[index] |= 1 << part;
  }
}

static int pitch2noteseq[] = {
 0,  0,  1,  1,  2,  3,  3,  4,
 4,  5,  5,  6};

void  noteseqmap(int chn) {
int i;
int half;
int channel;
int pitchclass;
int onset;
int index;
int remainder;
int noteNum;
int part;
half = division/2;
for (i = 0; i<8000; i++) pseq[i] = 0;
for (i = 0; i <lastEvent; i++) {
  channel = midievents[i].channel;
  if (channel == 9) continue; /* ignore percussion channel */
  if (channel == chn || chn == -1) {
    pitchclass = midievents[i].pitch % 12;
    noteNum = pitch2noteseq[pitchclass];
    onset = midievents[i].onsetTime;
    index = onset/half;
    if (index >= 8000) {printf("index too large in drumpattern\n");
	              break;
                      }
    pseq[index] = pseq[index] |= 1 << noteNum;
    }
  /*printf("pitchclass = %d noteNum =%d index = %d pseq[index] %d \n",pitchclass, noteNum, index, pseq[index]); */
  }
}

void print_pseq () {
int i;
for (i=0;i<(lastBeat+1)*2;i++) {
   printf("%d ",pseq[i]);
   if (i >= 8000) break;
   }
printf("\n");
}

int noteseqhist(int chan) {
int nonzeros;
int i;
nonzeros = 0;
noteseqmap(chan); 
for (i=0;i<128;i++) {
  pseqhist[i] = 0;
  }
for (i=0;i<lastBeat;i++) {
  pseqhist[pseq[i]]++;
  }
for (i=0;i<128;i++) {
  if (pseqhist[i] > 0)
    nonzeros++;;
  }
return nonzeros;
}

void allDistinctNoteSeq() {
int i;
int nonzeros;
for (i=0;i<17;i++) {
  /*printf("\n%d,%d",i,channel_active[i+1]);*/
  if (i == 9) continue;
  if (channel_active[i+1] == 0) continue;
  nonzeros = noteseqhist(i);
  if (channel_active[i+1] > 0) printf("\t%d,%d",i+1,nonzeros);
  }
printf("\n");
}




void outputChannelSummaryCsv() {
FILE *f;
int i;
int spread;
/*f = fopen("channelDescriptor.csv","w");*/
f = stdout;
fprintf(f,"programs,cnotes,nnotes,nzeros,nsteps,njumps,rpats,pavg,spread\n");
for (i = 1; i<17; i++) {
  if (nm[i].used > 0) spread = (100*(trkdata.npulses[0] - nm[i].quietTime)/trkdata.npulses[0]);
  else spread=0;
  fprintf(f,"%d,%d,%d,%d,%d,%d,%d,%d,%d\n",channel2prog[i],channel2nnotes[i],nm[i-1].totalNotes,\
    nm[i-1].zeroCount,nm[i-1].stepCount,nm[i-1].jumpCount,\
    trkdata.rhythmpatterns[i],nm[i-1].totalPitches/(1+nm[i-1].totalNotes),\
    spread);
  }
//fclose(f);
}



void outputChannelSummary() {
int i;
  
  printf("programs: ");
  for(i=1;i<17;i++) printf(" %d",channel2prog[i]);
  printf("\ncnotes: ");
  for(i=1;i<17;i++) printf(" %d",channel2nnotes[i]);
  printf("\nnnotes: ");
  for(i=0;i<16;i++) printf(" %d",nm[i].totalNotes);
  printf("\nnzeros: ");
  for(i=0;i<16;i++) printf(" %d",nm[i].zeroCount);
  printf("\nnsteps: ");
  for(i=0;i<16;i++) printf(" %d",nm[i].stepCount);
  printf("\nnjumps: ");
  for(i=0;i<16;i++) printf(" %d",nm[i].jumpCount);
  printf("\nrpats:  ");
  for(i=1;i<17;i++) printf(" %d",trkdata.rhythmpatterns[i]);
  printf("\npavg:   "); 
  /* avoid dividing by 0 */
  for(i=0;i<16;i++) printf(" %d",nm[i].totalPitches/(1+nm[i].totalNotes));
  printf("\nspread: "); 
  for(i=1;i<17;i++) if(nm[i].used > 0) printf(" %d",100*(trkdata.npulses[0] - nm[i].quietTime)/trkdata.npulses[0]);
                    else printf(" %d",0);
  printf("\n");
}



void dualDrumPattern (int perc1, int perc2) {
int i;
int channel;
int pitch;
int onset;
int index;
int quarter;
int remainder;
int part;
quarter = division/4;
for (i = 0; i<8000; i++) drumpat[i] = 0;
for (i = 0; i <lastEvent; i++) {
  channel = midievents[i].channel;
  if (channel != 9) continue;
  pitch = midievents[i].pitch;
  if (pitch != perc1  && pitch != perc2) continue;
  onset = midievents[i].onsetTime;
  index = onset/division;
  if (index >= 8000) {printf("index too large in drumpattern\n");
	              break;
                      }
  remainder = onset % division;
  part = remainder/quarter;
  part = 3 - part; /* order the bits from left to right */
  if (pitch == perc1) drumpat[index] = drumpat[index] |= 1 << part;
  else drumpat[index] = drumpat[index] |= 16 * (1 << part);
  }
}

void pitchClassAnalysis () {
int i;
int channel;
int pitch;
for (i=0;i<12;i++) pitchclass_activity[i] = 0;
for (i = 0; i < lastEvent; i++) {
  channel = midievents[i].channel;
  if (channel == 9) continue;
  pitch = midievents[i].pitch;
  pitchclass_activity[pitch % 12]++;
  }
}

void output_perc_pattern (int i) {
int left,right;
left = i/16;
right = i % 16;
printf(" (%d.%d)",left,right);
}


void drumPatternHistogram () {
int i;
for (i=0;i<256;i++) {
 histogram[i] = 0;
}
for (i=0;i<lastBeat;i++) {
  histogram[drumpat[i]]++;
  }
for (i=1;i<256;i++) {
  if (histogram[i] > 0) {
	  printf(" %d",i);
	  output_perc_pattern(i);
	  printf(" %d ", histogram[i]);
         }
    }
  printf("\n");
}


void output_drumpat (char * c) {
int i;
for (i=0;i<=lastBeat;i++) printf(c,drumpat[i]);
/*for (i=0;i<lastBeat;i++) output_perc_pattern(drumpat[i]);*/
printf("\n");
}

/*
The key match algorithm is based on the work of Craig Sapp
Visual Hierarchical Key Analysis
 https://ccrma.stanford.edu/~craig/papers/05/p3d-sapp.pdf
published in Proceedings of the International Computer Music
Conference,2001,
and the work of Krumhansl and Schmukler.

 Craig Sapp's simple coefficients (mkeyscape)
 Major C scale

The algorithm correlates the pitch class class histogram with
the ssMj or ssMn coefficients trying all 12 key centers, and
looks for a maximum.

The algorithm returns the key, sf (the number of sharps or
flats), and the maximum peak which is relatable to the
level of confidence we have of the result.
*/
static float  ssMj[] = { 1.25, -0.75, 0.25, -0.75, 0.25, 0.25,
  -0.75, 1.25, -0.75, 0.25, -0.75, 0.25};

/* Minor C scale (3 flats)
*/
static float ssMn[] = { 1.25, -0.75, 0.25, 0.25, -0.75, 0.25,
  -0.75, 1.25, 0.25, -0.75, 0.25, -0.75};

static char *keylist[] = {"C", "C#", "D", "Eb", "E", "F",
  "F#", "G", "Ab", "A", "Bb", "B"};

static char *majmin[] = {"maj", "min"};

/* number of sharps or flats for major keys in keylist */
static int maj2sf[] = {0,  7,  2, -3,  4, -1,  6,  1, -4, 3, -2, 5};
static int min2sf[] = {-3, 4, -1, -6, -4,  3, -4  -2, -7, 0, -5, 2};

void keymatch () {
int i;
int r;
int k;
float c2M,c2m,h2,hM,hm;
float rmaj[12],rmin[12];
float hist[12];
float best;
int bestIndex,bestMode;
int sf; /* number of flats or sharps (flats negative) */ 
int total;
float fnorm;

c2M = 0.0;
c2m = 0.0;
h2 = 0.0;
best = 0.0;
bestIndex = 0;
bestMode = -1;
total =0;
for (i=0;i<12;i++) {
   total += pitchhistogram[i];
   }
for (i=0;i<12;i++) {
   hist[i] = (float) pitchhistogram[i]/(float) total;
   }
fnorm = 0.0;
for (i=0;i<12;i++) {
   fnorm = hist[i]*hist[i] + fnorm;
   }
fnorm = sqrt(fnorm);
for (i=0;i<12;i++) {
   hist[i] = hist[i]/fnorm;
   }

for (i=0;i<12;i++) {
  c2M += ssMj[i]*ssMj[i];
  c2m += ssMn[i]*ssMn[i];
  h2 += hist[i]*hist[i]; 
  }
if (h2 < 0.0001) {
  printf("zero histogram\n");
  return;
  }
for (r=0;r<12;r++) {
  hM = 0.0;
  hm = 0.0;
  for (i=0;i<12;i++) {
    k = (i - r) % 12;
    if (k < 0) k = k + 12;
    hM += hist[i]*ssMj[k];
    hm += hist[i]*ssMn[k];
    }
  rmaj[r] = hM/sqrt(h2*c2M);
  rmin[r] = hm/sqrt(h2*c2m);
  }

for (r=0;r<12;r++) {
  if(rmaj[r] > best) {
      best = rmaj[r];
      bestIndex = r;
      bestMode = 0;
      }
  if(rmin[r] > best) {
      best = rmin[r];
      bestIndex = r;
      bestMode = 1;
      }
  }
if (bestMode == 0) sf = maj2sf[bestIndex];
else sf = min2sf[bestIndex];
 
printf("\nkey %s%s %d %f",keylist[bestIndex],majmin[bestMode],sf,best);
printf("\nrmaj ");
for (r=0;r<12;r++) printf("%7.3f",rmaj[r]);
printf("\nrmin ");
for (r=0;r<12;r++) printf("%7.3f",rmin[r]);
}


void percsummary () {
int i;
int bassindex,snareindex;
int bassmax,snaremax;
int bassdrum[] = {35,36,41,45};
int snaredrum[] = {37,38,39,40};
bassmax = 0;
snaremax = 0;
for (i=0;i<4;i++) {
  if(drumhistogram[bassdrum[i]] > bassmax) {
      bassindex = bassdrum[i];
      bassmax = drumhistogram[bassindex];
      }
  if(drumhistogram[snaredrum[i]] > snaremax) {
      snareindex = snaredrum[i];
      snaremax = drumhistogram[snareindex];
      }
  }
if (bassmax || snaremax) {
	printf("bass %d %d\n",bassindex,bassmax);
	printf("snare %d %d\n",snareindex,snaremax);
   } else {
        printf("missing bass and snare\n");
	return;
   }
dualDrumPattern(bassindex,snareindex);
}

void outputPitchClassHistogram() {
int i;
float activity;
activity = 0;
for (i=0;i<12;i++) activity += (float) pitchclass_activity[i];
for (i=0;i<11;i++) printf("%5.3f,",pitchclass_activity[i]/activity);
printf("%5.3f",pitchclass_activity[11]/activity);
printf("\n");
}


  
void corestatsOutput() {
int i;
int nchannels;
nchannels = 0;
for (i=1;i<17;i++) 
  if (channel_active[i] > 0) nchannels++;
printf("%d\t%d\t%d\t%d\t%d\t%d\n",lasttrack,nchannels, division,bpm,lastEvent,lastBeat);
/*printf("%d\n",tempocount);*/
}



int readnum(num) 
/* read a number from a string */
/* used for processing command line */
char *num;
{
  int t;
  char *p;
  int neg;
  
  t = 0;
  neg = 1;
  p = num;
  if (*p == '-') {
    p = p + 1;
    neg = -1;
  };
  /* [JA] 2021-05-25 */
  while (((int)*p >= '0') && ((int)*p <= '9') && (t < (INT_MAX-9)/10)) {
    t = t * 10 + (int) *p - '0';
    p = p + 1;
  };
  return neg*t;
}


int readnump(p) 
/* read a number from a string (subtly different) */
/* used for processing command line */
char **p;
{
  int t;
  
  t = 0;
  /* [JA] 2021-05-25 */
  while (((int)**p >= '0') && ((int)**p <= '9') && (t < (INT_MAX-9)/10)) {
    t = t * 10 + (int) **p - '0';
    *p = *p + 1;
  };
  /* advance over any spurious extra digits */
  while (isdigit(**p)) {
    *p = *p + 1;
  }
  return t;
}


int getarg(option, argc, argv)
/* extract arguments from command line */
char *option;
char *argv[];
int argc;
{
  int j, place;

  place = -1;
  for (j=0; j<argc; j++) {
    if (strcmp(option, argv[j]) == 0) {
      place = j + 1;
    };
  };
  return (place);
}

int huntfilename(argc, argv)
/* look for filename argument if -f option is missing */
/* assumes filename does not begin with '-'           */
char *argv[];
int argc;
{
  int j, place;

  place = -1;
  j = 1;
  while ((place == -1) && (j < argc)) {
    if (strncmp("-", argv[j], 1) != 0) {
      place = j;
    } 
    else {
     if (strchr("ambQkcou", *(argv[j]+1)) == NULL) {
       j = j + 1;
     }
     else {
       j = j + 2;
     };
    };
  };
  return(place);
}

int process_command_line_arguments(argc,argv)
char *argv[];
int argc;
{
  int val;
  int arg;
  arg = getarg("-ver",argc,argv);
  if (arg != -1) {printf("%s\n",VERSION); exit(0);}

  stats = 1;

  arg = getarg("-d",argc,argv);
  if ((arg != -1) && (arg <argc)) {
   debug = readnum(argv[arg]);
   }

  arg = getarg("-CSV",argc,argv);
  if (arg != -1) {
          noOutput = 1;
          stats = 1;
          }
  
  arg = getarg("-pulseanalysis",argc,argv);
  if (arg != -1) {
	  pulseanalysis = 1;
	  stats = 0;
          }

  arg = getarg("-corestats",argc,argv);
  if (arg != -1) {
	  corestats = 1;
	  stats = 0;
          }

  arg = getarg("-panal",argc,argv);
  if (arg != -1) {
	  percanalysis = 1;
	  stats = 0;
          }

  arg = getarg("-ppat",argc,argv);
  if (arg != -1) {
	  percpattern = 1;
	  stats = 0;
          }

  arg = getarg("-ppatfor",argc,argv);
  if (arg != -1) {
	  percpatternfor = 1;
	  stats = 0;
	  if (arg != -1 && arg <argc) {
		  percnum = readnum(argv[arg]);
	          printf("percnum = %d\n",percnum);
                  }
          }

  arg = getarg("-nseqfor",argc,argv);
  if (arg != -1) {
	  nseqfor = 1;
	  stats = 0;
	  if (arg != -1 && arg <argc) {
		  nseqchn = readnum(argv[arg]);
	          printf("nseqch = %d\n",nseqchn);
                  }
          }

  arg = getarg("-nseq",argc,argv);
  if (arg != -1) {
	  nseqfor = 1;
	  stats = 0;
          nseqchn = -1;
          }


  arg = getarg("-nseqtokens",argc,argv);
  if (arg != -1) {
	  nseqdistinct = 1;
	  stats = 0;
          }


  arg = getarg("-ppathist",argc,argv);
  if (arg != -1) {
	  percpatternhist = 1;
	  stats = 0;
          }

  arg = getarg("-pitchclass",argc,argv);
  if (arg != -1) {
	  pitchclassanalysis = 1;
	  stats = 0;
          }


  arg = getarg("-o",argc,argv);
  if ((arg != -1) && (arg < argc))  {
    outhandle = efopen(argv[arg],"w");  /* open output abc file */
  } 
  else {
    outhandle = stdout;
  };

  arg = getarg("-f", argc, argv);
  if (arg == -1) {
    arg = huntfilename(argc, argv);
  };

  if ((arg != -1) && (arg < argc)) {
    F = efopen(argv[arg],"rb");
/*    fprintf(outhandle,"%% input file %s\n", argv[arg]); */
  }
  else {
    printf("midistats version %s\n  usage :\n",VERSION);
    printf("midistats filename <options>\n");
    printf("         -corestats\n");
    printf("         -CSV\n");
    printf("         -pulseanalysis\n");
    printf("         -panal\n");
    printf("         -ppat\n");
    printf("         -ppatfor pitch\n");
    printf("         -ppathist\n");
    printf("         -pitchclass\n");
    printf("         -nseq\n");
    printf("         -nseqfor channel\n");
    printf("         -nseqtokens\n");
    printf("         -ver version number\n");
    printf("         -d <number> debug parameter\n");
    printf(" The input filename is assumed to be any string not\n");
    printf(" beginning with a - (hyphen).\n");
    exit(0);
  };
  return arg;
}



void midistats(argc,argv)
char *argv[];
int argc;
{
initfunc_for_stats();
Mf_getc = filegetc;
mfread();
if (noOutput == 0)  stats_finish(); 
if (noOutput == 0) outputChannelSummary(); 
if (noOutput == 1) outputChannelSummaryCsv();
}

void loadEvents() {
int i;
initfunc_for_loadNoteEvents();
Mf_getc = filegetc;
maximumPulse = 0;
mfread();
lastBeat = maximumPulse/division;
load_finish();
if (pulseanalysis) pulseHistogram(); 
if (percanalysis) {
	drumanalysis();
        for (i = 0; i< 100; i++) {
          if (drumhistogram[i] > 0) printf("%d %d\t",i,drumhistogram[i]);
        }
        printf("\n");
      }
if (percpattern) {
    drumanalysis();
    percsummary();
    output_drumpat("%02x ");
    }
if (percpatternfor) {
    drumpattern(percnum);
    output_drumpat("%d ");
    }
if (percpatternhist) {
    drumanalysis();
    percsummary();
    drumPatternHistogram();
    }
if (nseqfor) {
    noteseqmap(nseqchn);
    print_pseq();  
    }
if (nseqdistinct) {
   allDistinctNoteSeq(); 
   }
if (corestats) corestatsOutput();
if (pitchclassanalysis) {
   pitchClassAnalysis();
   outputPitchClassHistogram(); 
   }
}



int main(argc,argv)
char *argv[];
int argc;
{
  FILE *efopen();
  int arg;

  arg = process_command_line_arguments(argc,argv);
  if(stats == 1)  midistats(argc,argv);
  if(pulseanalysis || corestats || percanalysis ||\
    percpatternfor || percpattern || percpatternhist ||\
    pitchclassanalysis || nseqfor || nseqdistinct) loadEvents();
  return 0;
}
