/** EMULib Emulation Library *********************************/
/**                                                         **/
/**                           MIDI.c                        **/
/**                                                         **/
/** This file contains functions to generate MIDI files.    **/
/**                                                         **/
/** Copyright (C) Marat Fayzullin 1996                      **/
/**     You are not allowed to distribute this software     **/
/**     commercially. Please, notify me, if you make any    **/
/**     changes to this file.                               **/
/*************************************************************/

#include "MIDI.h"

#include <stdio.h>
#include <string.h>

#define TIMECONST         300 /* MIDI time constant  */

#define MelodicProg       80  /* Square Lead Patch   */
#define WhiteNoiseProg    126 /* Applause Patch      */
#define PeriodicNoiseProg 125 /* Helicopter Patch    */

typedef unsigned char byte;
typedef unsigned short word;

static int XVolume[16],XFreq[16],XType[16];
static byte XNote[16];
static word XPitch[16];
static int Counter,XChannels;
static FILE *MIDIOut;

static struct { byte Note;word Wheel; } Freqs[4096] =
{
#include "MIDIFreq.h"
};

static byte Volumes[256] =
{
  0x00,0x00,0x10,0x19,0x20,0x25,0x29,0x2D,
  0x30,0x32,0x35,0x37,0x39,0x3B,0x3C,0x3E,
  0x40,0x41,0x42,0x43,0x45,0x46,0x47,0x48,
  0x49,0x4A,0x4B,0x4C,0x4C,0x4D,0x4E,0x4F,
  0x4F,0x50,0x51,0x51,0x52,0x53,0x53,0x54,
  0x55,0x55,0x56,0x56,0x57,0x57,0x58,0x58,
  0x59,0x59,0x5A,0x5A,0x5B,0x5B,0x5B,0x5C,
  0x5C,0x5D,0x5D,0x5D,0x5E,0x5E,0x5F,0x5F,
  0x5F,0x60,0x60,0x60,0x61,0x61,0x61,0x62,
  0x62,0x62,0x63,0x63,0x63,0x64,0x64,0x64,
  0x64,0x65,0x65,0x65,0x66,0x66,0x66,0x66,
  0x67,0x67,0x67,0x67,0x68,0x68,0x68,0x68,
  0x69,0x69,0x69,0x69,0x6A,0x6A,0x6A,0x6A,
  0x6A,0x6B,0x6B,0x6B,0x6B,0x6C,0x6C,0x6C,
  0x6C,0x6C,0x6D,0x6D,0x6D,0x6D,0x6D,0x6E,
  0x6E,0x6E,0x6E,0x6E,0x6E,0x6F,0x6F,0x6F,
  0x6F,0x6F,0x70,0x70,0x70,0x70,0x70,0x70,
  0x71,0x71,0x71,0x71,0x71,0x71,0x72,0x72,
  0x72,0x72,0x72,0x72,0x73,0x73,0x73,0x73,
  0x73,0x73,0x73,0x74,0x74,0x74,0x74,0x74,
  0x74,0x74,0x75,0x75,0x75,0x75,0x75,0x75,
  0x75,0x76,0x76,0x76,0x76,0x76,0x76,0x76,
  0x77,0x77,0x77,0x77,0x77,0x77,0x77,0x77,
  0x78,0x78,0x78,0x78,0x78,0x78,0x78,0x78,
  0x78,0x79,0x79,0x79,0x79,0x79,0x79,0x79,
  0x79,0x7A,0x7A,0x7A,0x7A,0x7A,0x7A,0x7A,
  0x7A,0x7A,0x7B,0x7B,0x7B,0x7B,0x7B,0x7B,
  0x7B,0x7B,0x7B,0x7C,0x7C,0x7C,0x7C,0x7C,
  0x7C,0x7C,0x7C,0x7C,0x7C,0x7D,0x7D,0x7D,
  0x7D,0x7D,0x7D,0x7D,0x7D,0x7D,0x7D,0x7E,
  0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,
  0x7E,0x7E,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F
};

static void WriteDelta(void);
static void MidiMessage(byte D0,byte D1,byte D2);
static void NoteOff(byte Channel);
static void NoteOn(byte Channel,byte Note);
static void PitchBend(byte Channel,word Wheel);
static void WriteTempo(int Freq);

/** MIDITicks() **********************************************/
/** Increase number of ticks passed since the last command. **/
/*************************************************************/
void MIDITicks(int N) { Counter+=N; }

/** SetVolume() **********************************************/
/** Set volume (0..255) for a given channel.                **/
/*************************************************************/
void SetVolume(int Channel,int Volume)
{ Sound(Channel,XFreq[Channel],Volume); }

/** SetFreq() ************************************************/
/** Set frequency (Hz) for a given channel.                 **/
/*************************************************************/
void SetFreq(int Channel,int Freq)
{ Sound(Channel,Freq,XVolume[Channel]); }

/** Sound() **************************************************/
/** Set sound frequency (Hz) and volume (0..255) for a      **/
/** given channel.                                          **/
/*************************************************************/
void Sound(int Channel,int Freq,int Volume)
{
  int WasOff,MIDIVolume;

  MIDIVolume=(Volume&0xFF)/2;
  if(!MIDIVolume||(Freq<MIN_FREQ)||(Freq>MAX_FREQ)) NoteOff(Channel);
  else
  {
    WasOff=!XNote[Channel];
    if(WasOff||(Freq!=XFreq[Channel]))
    {
      NoteOn(Channel,Freqs[Freq/3].Note);
      PitchBend(Channel,Freqs[Freq/3].Wheel);
    }
    if(WasOff||(Volume!=XVolume[Channel]))
      MidiMessage(0xB0+Channel,7,MIDIVolume);
  }
  XVolume[Channel]=Volume;
  XFreq[Channel]=Freq;
}

/** InitMIDI() ***********************************************/
/** Write out MIDI header and initialize the variables. If  **/
/** failed, returns 0.                                      **/
/*************************************************************/
int InitMIDI(FILE *File,int Channels,int Ticks)
{
  static char MThd[] = "MThd\0\0\0\006\0\0\0\1";
                     /* ID  DataLen   Fmt Trks */
  static char MTrk[] = "MTrk\0\0\0\0";
                     /* ID  TrkLen   */
  int J;

  MIDIOut=File;
  if(fwrite(MThd,1,12,MIDIOut)!=12) return(0);
  fputc((TIMECONST>>8)&0xFF,MIDIOut);
  fputc(TIMECONST&0xFF,MIDIOut);
  if(fwrite(MTrk,1,8,MIDIOut)!=8) return(0);

  /* Clear the storage arrays and counter */
  for(J=0;J<16;J++) XNote[J]=XPitch[J]=XVolume[J]=XFreq[J]=XType[J]=0;
  Counter=0;XChannels=Channels;

  /* Write out the tempo */
  WriteTempo(Ticks);
  /* Put a MIDI controller 0 change in the file */
  /* to change to user bank 1                   */
  for(J=0;J<Channels;J++) MidiMessage(0xB0+J,0,1);
  /* Initially, load melodic patches */
  for(J=0;J<Channels;J++) SetSound(J,SND_MELODIC);
  /* Set master volume */
  for(J=0;J<Channels;J++) MidiMessage(0xB0+J,7,0);

  return(1);
}

/** TrashMIDI() **********************************************/
/** Write out the footer and the file size. User must close **/
/** the file after calling this function.                   **/
/*************************************************************/
void TrashMIDI(void)
{
  long Length;
  int J;

  /* Turn sound off */
  for(J=0;J<XChannels;J++) NoteOff(J);
  /* End of track */
  MidiMessage(0xFF,0x2F,0x00);
  /* Put track length in file */
  fseek(MIDIOut,0,SEEK_END);
  Length=ftell(MIDIOut)-22;
  fseek(MIDIOut,18,SEEK_SET);
  fputc((Length>>24)&0xFF,MIDIOut);
  fputc((Length>>16)&0xFF,MIDIOut);
  fputc((Length>>8)&0xFF,MIDIOut);
  fputc(Length&0xFF,MIDIOut);
}

/** SetSound() ***********************************************/
/** Set sound type for a given channel.                     **/
/*************************************************************/
void SetSound(int Channel,int Type)
{
  if(XType[Channel]!=Type)
  {
    XType[Channel]=Type;
    switch(Type)
    {
      case SND_MELODIC:  Type=MelodicProg;break;
      case SND_NOISE:    Type=WhiteNoiseProg;break;
      case SND_PERIODIC: Type=PeriodicNoiseProg;break;
      default:           return;
    }
    MidiMessage(0xC0+Channel,Type,255);
  }
}

/** WriteDelta() *********************************************/
/** Write number of ticks since the last MIDI command and   **/
/** reset the counter.                                      **/
/*************************************************************/
void WriteDelta(void)
{
  if(Counter<128) fputc(Counter,MIDIOut);
  else
    if(Counter<128*128)
    {
      fputc((Counter>>7)|0x80,MIDIOut);
      fputc(Counter&0x7F,MIDIOut);
    }
    else
    {
      fputc(((Counter>>14)&0x7F)|0x80,MIDIOut);
      fputc(((Counter>>7)&0x7F)|0x80,MIDIOut);
      fputc(Counter&0x7F,MIDIOut);
    }

  Counter=0;
}

/** MidiMessage() ********************************************/
/** Write out a MIDI message.                               **/
/*************************************************************/
void MidiMessage(byte D0,byte D1,byte D2)
{
 static byte Status=0;

 WriteDelta();
 if(D0!=Status) fputc(Status=D0,MIDIOut);
 if(D1<128)
 {
   fputc(D1,MIDIOut);
   if(D2<128) fputc(D2,MIDIOut);
 }
 D0&=0xF0;
 if((D0==0xC0)||(D0==0xD0)||(D0==0xF0)) Status=0;
}

/** NoteOff() ************************************************/
/** Turn off a note on a given channel.                     **/
/*************************************************************/
void NoteOff(byte Channel)
{
  if(XNote[Channel])
  { MidiMessage(0x90+Channel,XNote[Channel],0);XNote[Channel]=0; }
}

/** NoteOn() *************************************************/
/** Turn on a note on a given channel.                      **/
/*************************************************************/
void NoteOn(byte Channel,byte Note)
{
 if(XNote[Channel]!=Note)
 {
   NoteOff(Channel);
   MidiMessage(0x90+Channel,Note,100);
   XNote[Channel]=Note;
 }
}

/** PitchBend() **********************************************/
/** Change pitch bend on a channel.                         **/
/*************************************************************/
void PitchBend(byte Channel,word Wheel)
{
  if(XPitch[Channel]!=Wheel)
  {
    MidiMessage(0xE0+Channel,Wheel&0x7F,(Wheel>>7)&0x7F);
    XPitch[Channel]=Wheel;
  }
}

/** WriteTempo() *********************************************/
/** Write out soundtrack tempo (Hz).                        **/
/*************************************************************/
void WriteTempo(int Freq)
{
  int J;

  J=500000*TIMECONST*2/Freq;
  WriteDelta();
  fputc(0xFF,MIDIOut);
  fputc(0x51,MIDIOut);
  fputc(0x03,MIDIOut);
  fputc((J>>16)&0xFF,MIDIOut);
  fputc((J>>8)&0xFF,MIDIOut);
  fputc(J&0xFF,MIDIOut);
}

