/* Generated tags: 0x80,
                   0xb0 ... 0xb5, 0,
                   0x40,
                   0xc0 ... 0xc5, 0 ... 17,
                   0xc9, 0,
                   0x7f,
                   0xff,
                   0x2f, 0
   For the Timidity midi-player the events must be in a certain order - found out by trial-and-error.
*/
#include <stdio.h>
#include <string.h>
#include <SDL/SDL_stdinc.h> // for datatypes
#include "bigband.h"
#include "midi-out.h"
#include "gm.h"

MidiOut midi_out;

static Uint8 *reord16(Uint32 n) {
  static Uint8 buf[2];
  buf[0]=(n>>8) & 0xff;
  buf[1]=n & 0xff;
  return buf;
}

static Uint8 *reord8(Uint8 n) {
  static Uint8 buf[1];
  buf[0]=n;
  return buf;
}

static Uint8 *reord24(Uint32 n) {
  static Uint8 buf[3];
  buf[0]=(n>>16) & 0xff;
  buf[1]=(n>>8) & 0xff;
  buf[2]=n & 0xff;
  return buf;
}

static Uint8 *reord32(Uint32 n) {
  static Uint8 buf[4];
  buf[0]=(n>>24) & 0xff;
  buf[1]=(n>>16) & 0xff;
  buf[2]=(n>>8) & 0xff;
  buf[3]=n & 0xff;
  return buf;
}

void check(bool ok) { if (!ok) puts("not okay"); }

static void wr(Uint8 *n,int nr) {
  midi_out.data_size+=nr;
  check(1==fwrite(n, nr,1,midi_out.midi_f));
}

static void del_0() {
  Uint8 i8=0;
  wr(&i8, 1);
}

static void var_int(Uint32 val) {
  Uint8 bytes[5];
  Uint8 i8;
  bytes[0] = (val >> 28) & 0x7f;    // most significant 5 bits
  bytes[1] = (val >> 21) & 0x7f;    // next largest 7 bits
  bytes[2] = (val >> 14) & 0x7f;
  bytes[3] = (val >> 7)  & 0x7f;
  bytes[4] = (val)       & 0x7f;    // least significant 7 bits

  int start = 0;
  while (start<5 && bytes[start] == 0)  start++;

  for (int i=start; i<4; i++) {
    i8=bytes[i] | 0x80;
    wr(&i8, 1);
  }
  i8=bytes[4];
  wr(&i8, 1);
}
/*
int col2midi_instr[inst_max] = { 
   65-1,  // soprano
   66-1,  // alto
   67-1,  // tenor
   68-1,  // bariton
   74-1,  // flute
   57-1,  // trumpet
   58-1,  // trombone
   1-1,   // piano
   5-1,   // el.piano
   17-1,  // drawbar organ
   27-1,  // jazz guitar
   35-1   // bass guitar
};

int col2smp_instr[perc_max] = {
  42,  // closed hi-hat
  46,  // open hi-hat
  38,  // snare
  48,  // hi-mid tom
  43,  // tom
  41   // low floor tom
};
*/

bool MidiOut::init(const char *fn,int subd,int us_pb) {  // sets init_ok if succesful
  init_ok=false;
  us_per_beat=us_pb;
  if ((midi_f=fopen(fn,"w"))==0) {
    alert("%s not opened",fn);
    return false;
  }
  for (int n=0;n<inst_max;++n)
    instr_mapping[n]=-1; // force program change
  new_chan=-1;
  for (int i=0;i<inst_max;++i) col_gr2chan[i]= -1;
  init_ok=true;
  chan_warn=false;
  check(1==fwrite("MThd", 4,1,midi_f)); // header
  wr(reord32(6), 4);          // header size
  wr(reord16(1), 2);          // file type
  return true;
}

void MidiOut::init2(int nr_tracks) {
  wr(reord16(nr_tracks), 2);  // # tracks
  wr(reord16(2*subdiv), 2);   // time format
}

void MidiOut::write_track1() {
  check(1==fwrite("MTrk", 4,1,midi_f)); // track data
  seek_setsize=ftell(midi_f);
  wr(reord32(0), 4);          // track size (temporary)
  data_size=0; cur_time=0;

  del_0();
  wr(reord8(0xff),1);        // meta event
  wr(reord8(0x58),1);        // time signature
  var_int(4);
  wr(reord8(meter/nupq),1);  // numerator of time signature
  wr(reord8(2),1);           // 2log of denominator of time signature (2^2 = 4)
  wr(reord8(24),1);          // midi clocks (1 clock = 1/24 quarter note) per quarter note
  wr(reord8(8),1);           // 1/32 notes per quarter note

  del_0();
  wr(reord8(0xff),1);        // meta event
  wr(reord8(0x51),1);        // tempo meta event (must be after previous timing events)
  var_int(3);
  wr(reord24(us_per_beat),3); // micro sec per quarter note, typical 500000
}

void MidiOut::init_track(int tracknr,int col) {
  cur_track=tracknr;
  if (debug) puts("-- new track --");
  check(1==fwrite("MTrk", 4,1,midi_f)); // track data
  seek_setsize=ftell(midi_f);
  wr(reord32(0), 4);          // track size (temporary)
  data_size=0; cur_time=0;
}

void MidiOut::init_perc_track(int tracknr) {
  cur_track=tracknr;
  check(1==fwrite("MTrk", 4,1,midi_f)); // track data
  seek_setsize=ftell(midi_f);
  wr(reord32(0), 4);          // track size (temporary)
  data_size=0; cur_time=0;

  del_0();
  wr(reord8(0xff),1);      // meta event
  wr(reord8(0x03),1);      // track title
  char tr_title[50];
  int len=snprintf(tr_title,50,"track %d - Percussion",cur_track);
  var_int(len);
  wr((Uint8*)tr_title,len);

  del_0();
  wr(reord8(0xc9),1);   // select instrument channel 10
  wr(reord8(0),1);      // instrument
  del_0();
  wr(reord8(0xb9),1);   // change mode channel 10
  wr(reord8(0x7),1);    // volume
  wr(reord8(112),1);
  del_0();
  wr(reord8(0x0a),1);   // pan
  wr(reord8(60),1);
}

void MidiOut::close_track() {
  del_0();
  wr(reord8(0xff),1);
  wr(reord8(0x2f),1);
  wr(reord8(0),1);
  int trackend=ftell(midi_f);
  fseek(midi_f, seek_setsize, SEEK_SET);
  wr(reord32(data_size), 4);
  fseek(midi_f, trackend, SEEK_SET);
}

static int note2note(int n) {         // midi: middle C = 60, Amuc: middle C = 20
  if (n<0 || n>=100) { alert("midi: bad note"); return 60; }
  return n;
}

int MidiOut::get_chan(bool sampled,int ind) { // ind = index in col_gr2chan[]
  if (sampled) return 9;
  if (col_gr2chan[ind]>=0) return col_gr2chan[ind];
  if (new_chan==16-1) {
    if (!chan_warn) { chan_warn=true; alert("midi: channels > 16"); }
    return 0;
  }
  ++new_chan;
  if (new_chan==9) ++new_chan;  // skip percussion channel
  return (col_gr2chan[ind]=new_chan);
}

void MidiOut::note_onoff(bool start,int col,int midi_instr,bool sampled,int tim,int nr,int ampl) {
  if (!init_ok) return;
  if (debug) {
    printf("midi note_onoff: start=%d col=%d instr=%d sampled=%d tim=%d midi-nr=%d amp=%d\n",
            start,col,midi_instr,sampled,tim,nr,ampl);
  }
  const int chan=get_chan(sampled,col);
  if (!sampled && instr_mapping[col]!=midi_instr) {
    if (debug) printf("instr from %d to %d, chan=%d\n",instr_mapping[col],midi_instr,chan);
    instr_mapping[col]=midi_instr;

    del_0();
    wr(reord8(0xff),1);      // meta event
    wr(reord8(0x03),1);      // track title
    char tr_title[100];
    int len=snprintf(tr_title,100,"track %d - %s",cur_track,gm_instr[midi_instr].name);
    var_int(len);
    wr((Uint8*)tr_title,len);
    del_0();
    wr(reord8(0xb0 + chan),1);   // control change mode

    wr(reord8(0x40),1);          // damper on/off
    wr(reord8(0),1);             // damped
    del_0();
    wr(reord8(0x7),1);           // volume
    wr(reord8(100),1);    // good balance with percussion amplitude

    // following events will be timed!
    if (tim==cur_time) del_0();
    else {
      var_int(tim-cur_time);
      cur_time=tim;
    }
    wr(reord8(0x0a),1);          // pan
    wr(reord8(iprop[col].midi_pan),1);
    del_0();
    wr(reord8(0xc0 + chan),1);   // program change
    wr(reord8(midi_instr),1);    // instrument
    del_0();
  }
  else {
    if (tim==cur_time) del_0();
    else {
      var_int(tim-cur_time);
      cur_time=tim;
    }
  }

  if (sampled) {
    wr(reord8(0xb9),1);   // change mode channel 10
    wr(reord8(0x0a),1);   // pan
    wr(reord8(pprop[col].midi_pan),1);
    del_0();
    wr(reord8((start ? 0x90 : 0x80)+chan),1);        // note on, channel #
    wr(reord8(midi_instr),1);
  }
  else {
    wr(reord8((start ? 0x90 : 0x80)+chan),1);        // note on, channel #
    wr(reord8(note2note(nr)),1); // note nr
  }
  wr(reord8(start ? ampl : 0),1); // velocity
}

void MidiOut::close() {
  if (!init_ok) return;
  fclose(midi_f);
  init_ok=false;
}
