import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
import ch.qos.logback.core.joran.spi.JoranException;

import java.io.*;
import java.util.*;

import javax.sound.sampled.*;
import javax.sound.midi.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;







// this class is the Crkodrt2PlayMidiTrack thread class

// javac -cp ".;\AliansProjects\crkodrt2\logback-classic-1.0.2.jar;\AliansProjects\crkodrt2\slf4j-api-1.6.4.jar;\AliansProjects\crkodrt2\log4j-over-slf4j-1.6.4.jar;\AliansProjects\crkodrt2\logback-core-1.0.2.jar" Crkodrt2PlayMidiTrackThread.java

public class Crkodrt2PlayMidiTrackThread extends Thread
{

  Logger C2PMT_Logger;
  int NumberOfStackTraceElementsToOutput = 20;
  int C2PMT_NumberOfLoops;
  HashMap C2PMT_NumberOfLoopsByMovementNameHashMap;
  ShortMessage NoteOnShortMessage;
  ShortMessage NoteOffShortMessage;
  int RandomNoteNumber;
  Receiver C2PMT_DesiredReceiver;
  int MaxNumberOfSongSegments;
  double C2PMT_FullSequenceDuration;
  HashMap C2PMT_FullSequenceDurationByMovementNameHashMap;
  int C2PMT_CurrentChartIndexArrayListSize;
  HashMap C2PMT_CurrentChartIndexArrayListSizeByMovementNameHashMap;
  int C2PMT_CurrentNumberOfSequences;
  HashMap C2PMT_CurrentNumberOfSequencesByMovementNameHashMap;
  int C2PMT_intGlobalMidiSpecificationNodeListLength;
  int MidiChannel;
  int[] C2PMT_MidiChannelArray;
  int SegmentStartingMillisecond;
  String MidiTrackHashMapKey;
  String MidiTrackHashMapValue;
  ArrayList C2PMT_MidiTrackHashMapArrayList;
  HashMap C2PMT_MidiTrackHashMapArrayListByMovementNameHashMap;
  String[] MidiTrackHashMapValueSplitStringArray;
  int RandomVelocityNumber;
  String[] KeyStrings = {"C","C#/Db","D","D#/Eb","E","F","F#/Gb","G","G#/Ab","A","A#/Bb","B"};
  int OctaveNumber;
  int KeyStringsIndex;

  Calendar CalendarInstance;
  long StartTime;
  long CurrentTime;
  long NextTime;
  long NextSequenceStartTime;
  long TimeLeft;
  String[] C2PMT_MovementMapSplitStringArray;
  int C2PMT_intMovementMapSplitStringArrayLength;
  String CurrentMovementName;
  int MovementMapSplitStringArrayIndex;
  int CurrentChartIndex;
  int CurrentChartIndexArrayListIndex;
  ArrayList C2PMT_CurrentChartIndexArrayList;
  HashMap C2PMT_CurrentChartIndexArrayListByMovementNameHashMap;
  ArrayList C2PMT_ChartMidiChannelArrayListOfArrayLists;
  ArrayList C2PMT_MidiChannelArraySizeArrayList;
  HashMap C2PMT_ChartMidiChannelArrayListOfArrayListsByMovementNameHashMap;
  HashMap C2PMT_MidiChannelArraySizeArrayListByMovementNameHashMap;
  int MidiChannelArraySize;
  HashMap C2PMT_MidiTrackHashMap;
  ArrayList C2PMT_ChartMidiChannelArrayList;
  HashMap C2PMT_SongSegmentDurationByMovementNameHashMap;
  HashMap C2PMT_BeatsPerMinuteByMovementNameHashMap;
  int C2PMT_SongSegmentDuration;
  int C2PMT_NumberOfSongRepeats;

  ArrayList MidiShortMessageArrayList;
  int MidiShortMessageArrayListSize;
  int MidiShortMessageArrayListIndex;
  ShortMessage MidiShortMessage;
  MidiChannel C2PMT_MidiChannelsArray[] = new MidiChannel[16];
  int C2PMT_MidiChannelsArraySize;
  int C2PMT_MidiChannelsArrayIndex;
  Synthesizer C2PMT_Synthesizer;

  HashMap C2PMT_MidiFileOutputStreamByMidiChannelHashMap;
  HashMap MidiFileByteArrayInitializedFlagByMidiChannelHashMap;
  byte[][] MidiFileByteArray = new byte[16][65536];
  int MidiCommand;
  int MidiData1;
  int MidiData2;
  int TrackChunkLengthIndex;
  int[] MidiFileByteArrayIndex = new int[16];
  int[] MidiFileByteArraySize = new int[16];
  int[] TrackChunkLength = new int[16];
  Iterator C2PMT_MidiFileOutputStreamByMidiChannelHashMapIterator;
  Map.Entry    C2PMT_MidiFileOutputStreamByMidiChannelHashMapEntry;
  String      C2PMT_MidiFileOutputStreamByMidiChannelHashMapKey;
  FileOutputStream MidiFileOutputStream;
  int TrackChunkLengthHighOrderByteValue;
  int TrackChunkLengthLowOrderByteValue;
  int[] TimeOfLastMessageOnThisMidiChannel = new int[16];
  double CurrentNumberOfMidiTicks;
  double MidiTickDuration;
  double C2PMT_BeatsPerMinute;
  int ThirdOrderValue;
  int SecondOrderValue;
  int FirstOrderValue;
  int CurrentTimeOfLastMessageOnThisMidiChannel;


  // the constructor for the class

  public Crkodrt2PlayMidiTrackThread
         (
           HashMap cp_C2PMT_NumberOfLoopsByMovementNameHashMap,
           Receiver cp_C2PMT_DesiredReceiver,
           HashMap cp_C2PMT_FullSequenceDurationByMovementNameHashMap,
           HashMap cp_C2PMT_CurrentChartIndexArrayListByMovementNameHashMap,
           HashMap cp_C2PMT_CurrentChartIndexArrayListSizeByMovementNameHashMap,
           HashMap cp_C2PMT_CurrentNumberOfSequencesByMovementNameHashMap,
           int cp_C2PMT_intGlobalMidiSpecificationNodeListLength,
           HashMap cp_C2PMT_ChartMidiChannelArrayListOfArrayListsByMovementNameHashMap,
           HashMap cp_C2PMT_MidiChannelArraySizeArrayListByMovementNameHashMap,
           HashMap cp_C2PMT_MidiTrackHashMapArrayListByMovementNameHashMap,
           String[] cp_C2PMT_MovementMapSplitStringArray,
           int cp_C2PMT_intMovementMapSplitStringArrayLength,
           HashMap cp_C2PMT_SongSegmentDurationByMovementNameHashMap,
           HashMap cp_C2PMT_BeatsPerMinuteByMovementNameHashMap,
           int cp_C2PMT_NumberOfSongRepeats,
           HashMap cp_C2PMT_MidiFileOutputStreamByMidiChannelHashMap
         )
  {

    C2PMT_Logger = LoggerFactory.getLogger(Crkodrt2PlayMidiTrackThread.class);

    try
    {

      C2PMT_NumberOfLoopsByMovementNameHashMap             = cp_C2PMT_NumberOfLoopsByMovementNameHashMap;
      C2PMT_DesiredReceiver             = cp_C2PMT_DesiredReceiver;
      C2PMT_FullSequenceDurationByMovementNameHashMap      = cp_C2PMT_FullSequenceDurationByMovementNameHashMap;
      C2PMT_CurrentChartIndexArrayListByMovementNameHashMap    = cp_C2PMT_CurrentChartIndexArrayListByMovementNameHashMap;
      C2PMT_CurrentChartIndexArrayListSizeByMovementNameHashMap    = cp_C2PMT_CurrentChartIndexArrayListSizeByMovementNameHashMap;
      C2PMT_CurrentNumberOfSequencesByMovementNameHashMap           = cp_C2PMT_CurrentNumberOfSequencesByMovementNameHashMap;
      C2PMT_intGlobalMidiSpecificationNodeListLength = cp_C2PMT_intGlobalMidiSpecificationNodeListLength;
      C2PMT_ChartMidiChannelArrayListOfArrayListsByMovementNameHashMap = cp_C2PMT_ChartMidiChannelArrayListOfArrayListsByMovementNameHashMap;
      C2PMT_MidiChannelArraySizeArrayListByMovementNameHashMap = cp_C2PMT_MidiChannelArraySizeArrayListByMovementNameHashMap;
      C2PMT_MidiTrackHashMapArrayListByMovementNameHashMap     = cp_C2PMT_MidiTrackHashMapArrayListByMovementNameHashMap;
      C2PMT_MovementMapSplitStringArray             = cp_C2PMT_MovementMapSplitStringArray;
      C2PMT_intMovementMapSplitStringArrayLength    = cp_C2PMT_intMovementMapSplitStringArrayLength;
      C2PMT_SongSegmentDurationByMovementNameHashMap = cp_C2PMT_SongSegmentDurationByMovementNameHashMap;
      C2PMT_BeatsPerMinuteByMovementNameHashMap = cp_C2PMT_BeatsPerMinuteByMovementNameHashMap;
      C2PMT_NumberOfSongRepeats  = cp_C2PMT_NumberOfSongRepeats;
      C2PMT_MidiFileOutputStreamByMidiChannelHashMap = cp_C2PMT_MidiFileOutputStreamByMidiChannelHashMap;
      C2PMT_Synthesizer = MidiSystem.getSynthesizer();
      if(C2PMT_Synthesizer == null)
      {
        C2PMT_Logger.error("Crkodrt2PlayMidiTrackThread:C2PMT_ERROR:getSynthesizer() failed!");
      }
      C2PMT_MidiChannelsArray = C2PMT_Synthesizer.getChannels();
      C2PMT_MidiChannelsArraySize = java.lang.reflect.Array.getLength(C2PMT_MidiChannelsArray);

    }
    catch (Exception e)
    {
      C2PMT_Logger.error("Crkodrt2PlayMidiTrackThread:C2PMT_ERROR:" + e.getMessage());
      StackTraceElement[] steArray = e.getStackTrace();
      int steArraySize = java.lang.reflect.Array.getLength(steArray);
      for(int x = 0;x < NumberOfStackTraceElementsToOutput;x++)
      {
        if(x < steArraySize)
        {
          C2PMT_Logger.error("Crkodrt2PlayMidiTrackThread:C2PMT_ERROR:" + steArray[x].toString());
        }
      }
    }

  }















  // of course, we get here when we have run our thread

  @Override
  public void run()
  {

    try
    {
      C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:starting");

      C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:C2PMT_intMovementMapSplitStringArrayLength:" + C2PMT_intMovementMapSplitStringArrayLength);

      MidiFileByteArrayInitializedFlagByMidiChannelHashMap = new HashMap();

      // init the array used to compute the number of midi ticks for the next event

      for(int x = 0;x < 16;x++)
      {
        TimeOfLastMessageOnThisMidiChannel[x] = -1;
      }




      // loop through the song as many times as required

      for(
          int C2PMT_NumberOfSongRepeatsIndex = 0;
          C2PMT_NumberOfSongRepeatsIndex < C2PMT_NumberOfSongRepeats;
          C2PMT_NumberOfSongRepeatsIndex++
         )
      {

        NextSequenceStartTime = 0;

        // loop through the movements in the song

        for(
            MovementMapSplitStringArrayIndex = 0;
            MovementMapSplitStringArrayIndex < C2PMT_intMovementMapSplitStringArrayLength;
            MovementMapSplitStringArrayIndex++
           )
        {

          CurrentMovementName = C2PMT_MovementMapSplitStringArray[MovementMapSplitStringArrayIndex];
          C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:CurrentMovementName:" + CurrentMovementName);

          C2PMT_FullSequenceDuration = (double)C2PMT_FullSequenceDurationByMovementNameHashMap.get(CurrentMovementName);
          C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:C2PMT_FullSequenceDuration:" + C2PMT_FullSequenceDuration);
          C2PMT_NumberOfLoops = (int)C2PMT_NumberOfLoopsByMovementNameHashMap.get(CurrentMovementName);
          C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:C2PMT_NumberOfLoops:" + C2PMT_NumberOfLoops);

          C2PMT_CurrentChartIndexArrayList = (ArrayList)C2PMT_CurrentChartIndexArrayListByMovementNameHashMap.get(CurrentMovementName); ;

          C2PMT_CurrentChartIndexArrayListSize = (int)C2PMT_CurrentChartIndexArrayListSizeByMovementNameHashMap.get(CurrentMovementName);
          C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:C2PMT_CurrentChartIndexArrayListSize:" + C2PMT_CurrentChartIndexArrayListSize);

          C2PMT_MidiTrackHashMapArrayList = (ArrayList)C2PMT_MidiTrackHashMapArrayListByMovementNameHashMap.get(CurrentMovementName);

          C2PMT_ChartMidiChannelArrayListOfArrayLists = (ArrayList)C2PMT_ChartMidiChannelArrayListOfArrayListsByMovementNameHashMap.get(CurrentMovementName);

          C2PMT_MidiChannelArraySizeArrayList = (ArrayList)C2PMT_MidiChannelArraySizeArrayListByMovementNameHashMap.get(CurrentMovementName);

          C2PMT_SongSegmentDuration = (int)C2PMT_SongSegmentDurationByMovementNameHashMap.get(CurrentMovementName);
          C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:C2PMT_SongSegmentDuration:" + C2PMT_SongSegmentDuration);

          C2PMT_BeatsPerMinute = (double)C2PMT_BeatsPerMinuteByMovementNameHashMap.get(CurrentMovementName);
          C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:C2PMT_BeatsPerMinute:" + C2PMT_BeatsPerMinute);

          MaxNumberOfSongSegments = (int)Math.round(C2PMT_FullSequenceDuration / C2PMT_SongSegmentDuration);
          C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:MaxNumberOfSongSegments:" + MaxNumberOfSongSegments);

          // compute the duration in milliseconds of one midi tick assuming that the PPQ is hard coded to 96

          MidiTickDuration = (60000 / (C2PMT_BeatsPerMinute * 96));
          C2PMT_Logger.info("crkodrt2:main:MidiTickDuration:" + MidiTickDuration);


          for(
              int LoopCounter = 0;
              LoopCounter < C2PMT_NumberOfLoops;
              LoopCounter++
             )
          {

            C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:LoopCounter:" + LoopCounter);

            // loop through the charts that have been selected

            for(
                CurrentChartIndexArrayListIndex = 0;
                CurrentChartIndexArrayListIndex < C2PMT_CurrentChartIndexArrayListSize;
                CurrentChartIndexArrayListIndex++
               )
            {

              CurrentChartIndex = (int)C2PMT_CurrentChartIndexArrayList.get(CurrentChartIndexArrayListIndex);
              C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:CurrentChartIndex:" + CurrentChartIndex);

              C2PMT_ChartMidiChannelArrayList = (ArrayList)C2PMT_ChartMidiChannelArrayListOfArrayLists.get(CurrentChartIndex);

              C2PMT_CurrentNumberOfSequences = (int)C2PMT_CurrentNumberOfSequencesByMovementNameHashMap.get(CurrentMovementName); ;
              C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:C2PMT_CurrentNumberOfSequences:" + C2PMT_CurrentNumberOfSequences);

              C2PMT_MidiTrackHashMap = (HashMap)C2PMT_MidiTrackHashMapArrayList.get(CurrentChartIndex);

              // loop through the sequences

              for(
                  int SequenceCounter = 0;
                  SequenceCounter < C2PMT_CurrentNumberOfSequences;
                  SequenceCounter++
                 )
              {


                if(NextSequenceStartTime != 0)
                {
                  CalendarInstance = Calendar.getInstance();
                  CurrentTime = CalendarInstance.getTimeInMillis();
                  TimeLeft = NextSequenceStartTime - CurrentTime;
                  NextSequenceStartTime = 0;
                  if(TimeLeft < 1)
                  {
                    C2PMT_Logger.error("Crkodrt2PlayMidiTrackThread.run:C2PMT_ERROR:TimeLeft:" + TimeLeft);
                  }
                  else
                  {
                    C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:Thread.sleep(" + TimeLeft + ")");
                    Thread.sleep(TimeLeft);
                  }
                }

                CalendarInstance = Calendar.getInstance();
                StartTime = CalendarInstance.getTimeInMillis();

                C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:SequenceCounter:" + SequenceCounter);

                for(
                    int SongSegmentIndex = 0;
                    SongSegmentIndex < MaxNumberOfSongSegments;
                    SongSegmentIndex++
                   )
                {

                  SegmentStartingMillisecond = SongSegmentIndex * C2PMT_SongSegmentDuration;
                  C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:SegmentStartingMillisecond:" + SegmentStartingMillisecond);

                  // MidiChannel = 16;
                  // MidiTrackHashMapKey = "1~0~16";
                  // if(C2PMT_MidiTrackHashMap.containsKey(MidiTrackHashMapKey))
                  // {
                  //   // MidiTrackHashMapValue = String.format("250~0");
                  //   MidiTrackHashMapValue = (String)C2PMT_MidiTrackHashMap.get(MidiTrackHashMapKey);
                  //   MidiTrackHashMapValueSplitStringArray = MidiTrackHashMapValue.split("~");
                  //   NoteOnShortMessage = null;
                  //   NoteOnShortMessage = new ShortMessage();
                  //   NoteOnShortMessage.setMessage(ShortMessage.START);
                  //   C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:C2PMT_DesiredReceiver.send(ShortMessage.START)");
                  //   C2PMT_DesiredReceiver.send(NoteOnShortMessage, -1);
                  // }
                  //
                  //
                  //
                  // MidiChannel = 16;
                  // MidiTrackHashMapKey = String.format("1~%d~16",SegmentStartingMillisecond);
                  // if(C2PMT_MidiTrackHashMap.containsKey(MidiTrackHashMapKey))
                  // {
                  //   // MidiTrackHashMapValue = String.format("248~0");
                  //   MidiTrackHashMapValue = (String)C2PMT_MidiTrackHashMap.get(MidiTrackHashMapKey);
                  //   MidiTrackHashMapValueSplitStringArray = MidiTrackHashMapValue.split("~");
                  //   NoteOnShortMessage = null;
                  //   NoteOnShortMessage = new ShortMessage();
                  //   NoteOnShortMessage.setMessage(ShortMessage.TIMING_CLOCK);
                  //   C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:C2PMT_DesiredReceiver.send(ShortMessage.TIMING_CLOCK)");
                  //   C2PMT_DesiredReceiver.send(NoteOnShortMessage, -1);
                  // }


                  MidiChannelArraySize = (int)C2PMT_MidiChannelArraySizeArrayList.get(CurrentChartIndex);
                  C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:MidiChannelArraySize:" + MidiChannelArraySize);

                  MidiTrackHashMapKey = String.format("%d",SegmentStartingMillisecond);
                  if(C2PMT_MidiTrackHashMap.containsKey(MidiTrackHashMapKey))
                  {
                    MidiShortMessageArrayList = (ArrayList)C2PMT_MidiTrackHashMap.get(MidiTrackHashMapKey);
                    MidiShortMessageArrayListSize = MidiShortMessageArrayList.size();
                    for(
                        MidiShortMessageArrayListIndex = 0;
                        MidiShortMessageArrayListIndex < MidiShortMessageArrayListSize;
                        MidiShortMessageArrayListIndex++
                       )
                    {
                      MidiShortMessage = (ShortMessage)MidiShortMessageArrayList.get(MidiShortMessageArrayListIndex);




                      if(C2PMT_DesiredReceiver == null)
                      {

                        // midi channel 1 is stored as a 0 in a MidiShortMessage

                        MidiChannel = MidiShortMessage.getChannel();
                        C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:MidiChannel:" + MidiChannel);

                        // compute the number of MidiTicks required to get this event to be at the right time

                        if(TimeOfLastMessageOnThisMidiChannel[MidiChannel] != -1)
                        {
                          CurrentNumberOfMidiTicks = ((SegmentStartingMillisecond - TimeOfLastMessageOnThisMidiChannel[MidiChannel]) / MidiTickDuration);
                        }
                        else
                        {
                          CurrentNumberOfMidiTicks = (SegmentStartingMillisecond / MidiTickDuration);
                        }


                        // save the time that this message was sent on so that we can
                        // use it to calculate the MidiTicks necessary for the following event

                        TimeOfLastMessageOnThisMidiChannel[MidiChannel] = SegmentStartingMillisecond;


                        MidiCommand = MidiShortMessage.getCommand();
                        MidiData1 = MidiShortMessage.getData1();
                        MidiData2 = MidiShortMessage.getData2();
                        if( ! MidiFileByteArrayInitializedFlagByMidiChannelHashMap.containsKey(MidiChannel) )
                        {

                          // initialize the MidiFileByteArray for this midi channel

                          // Header Chunk
                          // Chunk Type        length           [format    tracks     ticks per quarter note]
                          // 4d 54 68 64       00 00 00 06       00 00     00 01      00 60

                          MidiFileByteArray[MidiChannel][0] = (byte)0x4d;
                          MidiFileByteArray[MidiChannel][1] = (byte)0x54;
                          MidiFileByteArray[MidiChannel][2] = (byte)0x68;
                          MidiFileByteArray[MidiChannel][3] = (byte)0x64;
                          MidiFileByteArray[MidiChannel][4] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][5] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][6] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][7] = (byte)0x06;
                          MidiFileByteArray[MidiChannel][8] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][9] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][10] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][11] = (byte)0x01;
                          MidiFileByteArray[MidiChannel][12] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][13] = (byte)0x60;

                          // Track Chunk
                          // Chunk Type        length           [data follows]
                          // 4d 54 72 6b       00 00 00 21


                          MidiFileByteArray[MidiChannel][14] = (byte)0x4d;
                          MidiFileByteArray[MidiChannel][15] = (byte)0x54;
                          MidiFileByteArray[MidiChannel][16] = (byte)0x72;
                          MidiFileByteArray[MidiChannel][17] = (byte)0x6b;
                          MidiFileByteArray[MidiChannel][18] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][19] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][20] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][21] = (byte)0x21;
                          TrackChunkLengthIndex = 18;



                          // Sequence/Track Name
                          // 00 ff 03 01 00


                          MidiFileByteArray[MidiChannel][22] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][23] = (byte)0xff;
                          MidiFileByteArray[MidiChannel][24] = (byte)0x03;
                          MidiFileByteArray[MidiChannel][25] = (byte)0x01;
                          MidiFileByteArray[MidiChannel][26] = (byte)0x00;

                          // Time Signature
                          // Time signature of the form:
                          // nn/2^dd
                          // eg: 6/8 would be specified using nn=6, dd=3
                          // The parameter cc is the number of MIDI Clocks per metronome tick.
                          //
                          // Normally, there are 24 MIDI Clocks per quarter note.
                          // However, some software allows this to be set by the user.
                          // The parameter bb defines this in terms of the number of 1/32 notes
                          // which make up the usual 24 MIDI Clocks (the 'standard' quarter note).
                          //
                          // nn	Time signature, numerator
                          // dd	Time signature, denominator expressed as a power of 2.
                          // eg a denominator of 4 is expressed as dd=2
                          // cc	MIDI Clocks per metronome tick
                          // bb	Number of 1/32 notes per 24 MIDI clocks (8 is standard)


                          // 00 ff 58 04 08 02 24 08


                          MidiFileByteArray[MidiChannel][27] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][28] = (byte)0xff;
                          MidiFileByteArray[MidiChannel][29] = (byte)0x58;
                          MidiFileByteArray[MidiChannel][30] = (byte)0x04;
                          MidiFileByteArray[MidiChannel][31] = (byte)0x08;
                          MidiFileByteArray[MidiChannel][32] = (byte)0x02;
                          MidiFileByteArray[MidiChannel][33] = (byte)0x24;
                          MidiFileByteArray[MidiChannel][34] = (byte)0x08;

                          // time signature duplicate, for some reason

                          // 00 ff 58 04 08 02 24 08

                          MidiFileByteArray[MidiChannel][35] = (byte)0x00;
                          MidiFileByteArray[MidiChannel][36] = (byte)0xff;
                          MidiFileByteArray[MidiChannel][37] = (byte)0x58;
                          MidiFileByteArray[MidiChannel][38] = (byte)0x04;
                          MidiFileByteArray[MidiChannel][39] = (byte)0x08;
                          MidiFileByteArray[MidiChannel][40] = (byte)0x02;
                          MidiFileByteArray[MidiChannel][41] = (byte)0x24;
                          MidiFileByteArray[MidiChannel][42] = (byte)0x08;
                          MidiFileByteArrayIndex[MidiChannel] = 43;
                          MidiFileByteArraySize[MidiChannel] = 43;
                          TrackChunkLength[MidiChannel] = 21;


                          MidiFileByteArrayInitializedFlagByMidiChannelHashMap.put(MidiChannel, 0);
                        }

                        // C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:MidiFileByteArrayIndex[" + MidiChannel + "]:" + MidiFileByteArrayIndex[MidiChannel]);


                        // now we write the event to the MidiFileByteArray

                        ThirdOrderValue = (int)(CurrentNumberOfMidiTicks / 16384);
                        SecondOrderValue = (int)((CurrentNumberOfMidiTicks - (ThirdOrderValue * 16384)) / 128);
                        FirstOrderValue = (int)(CurrentNumberOfMidiTicks - (ThirdOrderValue * 16384) - (SecondOrderValue * 128));
                        if(ThirdOrderValue > 0)
                        {
                          MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)Math.round(ThirdOrderValue + 128);
                          MidiFileByteArrayIndex[MidiChannel]++;
                          MidiFileByteArraySize[MidiChannel]++;
                          TrackChunkLength[MidiChannel]++;

                        }
                        if(SecondOrderValue > 0)
                        {
                          MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)Math.round(SecondOrderValue + 128);
                          MidiFileByteArrayIndex[MidiChannel]++;
                          MidiFileByteArraySize[MidiChannel]++;
                          TrackChunkLength[MidiChannel]++;

                        }
                        MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)Math.round(FirstOrderValue);
                        MidiFileByteArrayIndex[MidiChannel]++;
                        MidiFileByteArraySize[MidiChannel]++;
                        TrackChunkLength[MidiChannel]++;




                        MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)MidiCommand;
                        MidiFileByteArrayIndex[MidiChannel]++;
                        MidiFileByteArraySize[MidiChannel]++;
                        TrackChunkLength[MidiChannel]++;
                        MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)MidiData1;
                        MidiFileByteArrayIndex[MidiChannel]++;
                        MidiFileByteArraySize[MidiChannel]++;
                        TrackChunkLength[MidiChannel]++;
                        MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)MidiData2;
                        MidiFileByteArrayIndex[MidiChannel]++;
                        MidiFileByteArraySize[MidiChannel]++;
                        TrackChunkLength[MidiChannel]++;

                      }
                      else
                      {
                        C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:C2PMT_DesiredReceiver.send(Channel:" + MidiShortMessage.getChannel() + ":Data1:" + MidiShortMessage.getData1() + ", -1)");
                        C2PMT_DesiredReceiver.send(MidiShortMessage, -1);
                      }
                    }
                  }

                  // sleep for however much time is left in this segment

                  CalendarInstance = Calendar.getInstance();
                  CurrentTime = CalendarInstance.getTimeInMillis();
                  NextTime = ((SongSegmentIndex + 1) * C2PMT_SongSegmentDuration) + StartTime;
                  TimeLeft = NextTime - CurrentTime;
                  if(TimeLeft < 1)
                  {
                    C2PMT_Logger.error("Crkodrt2PlayMidiTrackThread.run:C2PMT_ERROR:TimeLeft:" + TimeLeft);
                  }
                  else
                  {

                    if(
                       (SequenceCounter == (C2PMT_CurrentNumberOfSequences - 1)) &&
                       (SongSegmentIndex == (MaxNumberOfSongSegments - 1))
                      )
                    {
                      NextSequenceStartTime = NextTime;
                      C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread:NextSequenceStartTime:" + NextSequenceStartTime);
                    }
                    else
                    {

                      C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:Thread.sleep(" + TimeLeft + ")");
                      Thread.sleep(TimeLeft);
                    }
                  }
                  // Thread.sleep(10);

                }


                // compute the millisecond that the next Sequence would start at

                SegmentStartingMillisecond = MaxNumberOfSongSegments * C2PMT_SongSegmentDuration;

                // init the array used to compute the number of midi ticks for the next event

                for(int x = 0;x < 16;x++)
                {
                  if(TimeOfLastMessageOnThisMidiChannel[x] != -1)
                  {
                    CurrentTimeOfLastMessageOnThisMidiChannel = TimeOfLastMessageOnThisMidiChannel[x];
                    TimeOfLastMessageOnThisMidiChannel[x] = (0 - (SegmentStartingMillisecond - CurrentTimeOfLastMessageOnThisMidiChannel));
                  }
                }


                for(
                    C2PMT_MidiChannelsArrayIndex = 0;
                    C2PMT_MidiChannelsArrayIndex < C2PMT_MidiChannelsArraySize;
                    C2PMT_MidiChannelsArrayIndex++
                   )
                {
                  C2PMT_MidiChannelsArray[C2PMT_MidiChannelsArrayIndex].allNotesOff();
                }

              }
            }

          }

        }
      }

      // if we are outputting to midi files, then we need to finish them up here

      if(C2PMT_DesiredReceiver == null)
      {

        C2PMT_MidiFileOutputStreamByMidiChannelHashMapIterator = C2PMT_MidiFileOutputStreamByMidiChannelHashMap.entrySet().iterator();
        while(C2PMT_MidiFileOutputStreamByMidiChannelHashMapIterator.hasNext())
        {
          C2PMT_MidiFileOutputStreamByMidiChannelHashMapEntry = (Map.Entry) C2PMT_MidiFileOutputStreamByMidiChannelHashMapIterator.next();
          MidiChannel = (Integer)C2PMT_MidiFileOutputStreamByMidiChannelHashMapEntry.getKey();
          MidiFileOutputStream = (FileOutputStream)C2PMT_MidiFileOutputStreamByMidiChannelHashMapEntry.getValue();
          MidiChannel--;
          C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:MidiChannel:" + MidiChannel);

          // EndOfTrack
          // 00 ff 2f 00

          MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)0x00;
          MidiFileByteArrayIndex[MidiChannel]++;
          MidiFileByteArraySize[MidiChannel]++;
          TrackChunkLength[MidiChannel]++;
          MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)0xff;
          MidiFileByteArrayIndex[MidiChannel]++;
          MidiFileByteArraySize[MidiChannel]++;
          TrackChunkLength[MidiChannel]++;
          MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)0x2f;
          MidiFileByteArrayIndex[MidiChannel]++;
          MidiFileByteArraySize[MidiChannel]++;
          TrackChunkLength[MidiChannel]++;
          MidiFileByteArray[MidiChannel][MidiFileByteArrayIndex[MidiChannel]] = (byte)0x00;
          MidiFileByteArrayIndex[MidiChannel]++;
          MidiFileByteArraySize[MidiChannel]++;
          TrackChunkLength[MidiChannel]++;

          // the MidiFileByteArray only supports a limited amount of characters right now,
          // so it is only necessary to populate the two low order bytes of the Track Chunk length

          TrackChunkLengthHighOrderByteValue = TrackChunkLength[MidiChannel] / 256;
          TrackChunkLengthLowOrderByteValue = TrackChunkLength[MidiChannel] % 256;
          MidiFileByteArray[MidiChannel][(TrackChunkLengthIndex + 2)] = (byte)TrackChunkLengthHighOrderByteValue;
          MidiFileByteArray[MidiChannel][(TrackChunkLengthIndex + 3)] = (byte)TrackChunkLengthLowOrderByteValue;


          C2PMT_Logger.info("crkodrt2:main:MidiFileOutputStream.write(MidiFileByteArray[" + MidiChannel + "], 0, " + MidiFileByteArraySize[MidiChannel] + ")");
          MidiFileOutputStream.write(MidiFileByteArray[MidiChannel], 0, MidiFileByteArraySize[MidiChannel]);

          MidiFileOutputStream.close();
        }
      }

      // otherwise, kill all the notes now that we are done

      else
      {
        // kill all the notes

        for(
            int MidiChannel = 1;
            MidiChannel < 17;
            MidiChannel++
           )
        {

          for(
              int NoteNumberIndex = 0;
              NoteNumberIndex < 128;
              NoteNumberIndex++
             )
          {

            NoteOffShortMessage = null;
            NoteOffShortMessage = new ShortMessage();
            NoteOffShortMessage.setMessage(ShortMessage.NOTE_OFF, (MidiChannel - 1), NoteNumberIndex, 0);
            C2PMT_DesiredReceiver.send(NoteOffShortMessage, -1);
          }
        }
      }

      C2PMT_Logger.info("Crkodrt2PlayMidiTrackThread.run:EXITING");


    }
    catch (Exception e)
    {
      C2PMT_Logger.error("Crkodrt2PlayMidiTrackThread.run:C2PMT_ERROR:" + e.getMessage());
      StackTraceElement[] steArray = e.getStackTrace();
      int steArraySize = java.lang.reflect.Array.getLength(steArray);
      for(int x = 0;x < NumberOfStackTraceElementsToOutput;x++)
      {
        if(x < steArraySize)
        {
          C2PMT_Logger.error("Crkodrt2PlayMidiTrackThread.run:C2PMT_ERROR:" + steArray[x].toString());
        }
      }
    }
  }

























































































}


