#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
// oslib
#include "os.h"
//
#include "proto.h"
#include "instrument.h"
#include "externsnd.h"
#include "midi.h"
#include "tables.h"
#include "mcode.h"
#include "events.h"

// this source file contains the MIDI bytestream parser and also maintains
// the note structures and more

const signed short sinetable[];
const unsigned short log2lin_volume[];
const int note2resamplerate[];
const unsigned char panvolume[];

#define SUPPORT_MONITOR
#define SUPPORT_GLOBAL_PORT
#define SUPPORT_MTC


#define GETMIDIBYTE(n)  (port->midibytes[(port->midireadpos+n) & (MIDIBUFFERSIZE-1)])


#ifdef SUPPORT_MONITOR
// monitor is always on port A
static unsigned char *monitor_buffer = NULL;
static int monitor_buffer_size = 0;
static int monitor_buffer_write = 0;
#endif

_kernel_oserror err_bad_reason_code   = { 1, "Bad reason code" };
_kernel_oserror err_reserved_bits_set = { 1, "Reserved bits set" };

// port is the main structure... there's one global one (port A)
#ifdef SUPPORT_GLOBAL_PORT
static MIDIPORT port_a;
#endif

#define LEFTVOLUME  (((panvolume[chn->controllers[CTRL_PAN]]             * \
                       log2lin_volume[chn->controllers[CTRL_VOLUME]<<3]) * \
                       chn->controllers[CTRL_EXPRESSION])>>14)
#define RIGHTVOLUME  (((panvolume[127-chn->controllers[CTRL_PAN]]        * \
                       log2lin_volume[chn->controllers[CTRL_VOLUME]<<3]) * \
                       chn->controllers[CTRL_EXPRESSION])>>14)


static void channelpressure(MIDIPORT *port, int c, int pressure);
static void pitchwheel(MIDIPORT *port, int c, unsigned int bend);
static void programchange(MIDIPORT *port, int c, int program);
static void controlchange(MIDIPORT *port, int c, int control, int value);
static void aftertouch(MIDIPORT *port, int c, int nt, int pressure);
static void noteon(MIDIPORT *port, int c, int nt, int velocity);
static void noteoff(MIDIPORT *port, int c, int nt, int velocity);
static void reset_controls(MIDIPORT *port, int channeli);
static void release_note(MIDIPORT *port, int nti, int velocity);
static void all_notes_off(MIDIPORT *port, int c);


void reset_port_structure(MIDIPORT *port) {

  int i;

  memset(port, 0, sizeof(MIDIPORT));
  port->polyphony = 24;
  port->activenotesp = port->activenotes;
  port->log2lin_volume = log2lin_volume;
  port->notesp = port->notes;
  port->sine = sinetable;
  port->mastervolume = 4096;         // 12 bit volume
  port->workspacep = port->workspace;
  port->defaults.interpolate = 0;
  port->tuning = 0;
  port->enabledchannels = 0xffff;
  port->flags.omni = 1;
  port->flags.polyphonic = 1;
  port->frequency = 22050;
  port->framespersecond = port->frequency>>LOG2SAMPLESPERFRAME;
  port->basechannel = 0;
  port->running_status = NOTEOFF;
  port->midibytecount = 0;
  port->midireadpos = 0;
  port->midiwritepos = 0;
  port->wait_for_sysex_end = 0;

  // reset all notes
  memset(port->notes, 0, MAXPOLYPHONY*sizeof(NOTE));
  for (i = 0; i < MAXPOLYPHONY>>3; i++)   port->activenotes[i] = 0;
  for (i = 0; i < MAXPOLYPHONY; i++)      port->notes[i].noteindex = i;

  // reset channels
  for (i = 0; i < 16; i++) {
    port->channels[i].program = 0;
    port->channels[i].drumchannel = 0;
    reset_controls(port, i);
  }
  port->channels[9].drumchannel = 1; // normally channel 10, but we start at #0
}


int midi_initialise() {
#ifdef SUPPORT_GLOBAL_PORT
  reset_port_structure(&port_a);
#endif
  return 0;
}


void midi_kill() {
}


MIDIPORT *midi_get_default_port() {
#ifdef SUPPORT_GLOBAL_PORT
  return &port_a;
#else
  return NULL;
#endif
}



int midi_read_frequency() {
#ifdef SUPPORT_GLOBAL_PORT
  return port_a.frequency;
#else
  return 22050;
#endif
}



void midi_rx_bytes(MIDIPORT *port, unsigned char *p, int n, int *available) {

  int nomore, irq;

#ifdef SUPPORT_GLOBAL_PORT
  if (!port)  port = &port_a;
#endif
#ifdef SUPPORT_MONITOR
  if ((monitor_buffer) && (port == &port_a)) {
    if (monitor_buffer_write + n > monitor_buffer_size) {
      int n2;
      n2 = monitor_buffer_size-monitor_buffer_write;
      if (n2)  memcpy(monitor_buffer+monitor_buffer_write, p, n2);
      memcpy(monitor_buffer, p+n2, n-n2);
      monitor_buffer_write = n-n2;
    } else {
      memcpy(monitor_buffer+monitor_buffer_write, p, n);
      monitor_buffer_write += n;
    }
  }
#endif

  if (port->wait_for_sysex_end) {
    do {
      if (*p == SYSTEM_EXCLUSIVE_END)   port->wait_for_sysex_end = 0;
      p++;
      n--;
    } while ((n > 0) && (port->wait_for_sysex_end));
    if (!n) {
      *available = MIDIBUFFERSIZE-1-port->midibytecount;
      return;
    }
  }

  // add byte(s) to buffer
  while (n) {
    port->midibytes[port->midiwritepos] = *p++;
    port->midiwritepos++;
    if (port->midiwritepos >= MIDIBUFFERSIZE)
      port->midiwritepos -= MIDIBUFFERSIZE;
    port->midibytecount++;
    n--;
  }

  // handle running status
  if (port->midibytes[port->midireadpos] & 0x80) {
    if ((port->midibytes[port->midireadpos] & 0xf0) != SYSTEM)
      port->running_status = port->midibytes[port->midireadpos];
  } else {
    if (port->midireadpos == 0)
      port->midireadpos = MIDIBUFFERSIZE-1;
    else
      port->midireadpos--;
    port->midibytes[port->midireadpos] = port->running_status;
    port->midibytecount++;
  }

  *available = MIDIBUFFERSIZE-1-port->midibytecount;

  irq = _kernel_irqs_disabled();
  if (irq)  _kernel_irqs_on();

  nomore = 0;
  while ((!nomore) && (port->midibytecount)) {
    int bytesused;
    unsigned char cmd, ignore, channeli;

    bytesused = 0;
    cmd = port->midibytes[port->midireadpos];
    channeli = cmd & 0x0f;

    if (port->enabledchannels & (1<<channeli))
      ignore = 0;
    else
      ignore = 1;

    switch (cmd & 0xf0) {
    case NOTEOFF:               // d0=note  d1=velocity
      if (port->midibytecount >= 3) {
        if (!ignore)
          noteoff(port, channeli, GETMIDIBYTE(1), GETMIDIBYTE(2));
        bytesused = 3;
      } else
        nomore = 1;
      break;
    case NOTEON:                // d0=note  d1=velocity
      if (port->midibytecount >= 3) {
        if (!ignore)
          noteon(port, channeli, GETMIDIBYTE(1), GETMIDIBYTE(2));
        bytesused = 3;
      } else
        nomore = 1;
      break;
    case AFTERTOUCH:            // d0=note  d1=pressure
      if (port->midibytecount >= 3) {
        if (!ignore)
          aftertouch(port, channeli, GETMIDIBYTE(1), GETMIDIBYTE(2));
        bytesused = 3;
      } else
        nomore = 1;
      break;
    case CONTROLCHANGE:         // d0=control d1=new value
      if (port->midibytecount >= 3) {
        if (!ignore)
          controlchange(port, channeli, GETMIDIBYTE(1), GETMIDIBYTE(2));
        bytesused = 3;
      } else
        nomore = 1;
      break;
    case PROGRAMCHANGE:         // d0=new program
      if (port->midibytecount >= 2) {
        if (!ignore)
          programchange(port, channeli, GETMIDIBYTE(1));
        bytesused = 2;
      } else
        nomore = 1;
      break;
    case CHANNELPRESSURE:       // d0=pressure
      if (port->midibytecount >= 2) {
        if (!ignore)
          channelpressure(port, channeli, GETMIDIBYTE(1));
        bytesused = 2;
      } else
        nomore = 1;
      break;
    case PITCHWHEEL:            // d0=least significant, d1=most significant
      if (port->midibytecount >= 3) {
        if (!ignore)
          pitchwheel(port, channeli, GETMIDIBYTE(1)+128*GETMIDIBYTE(2));
        bytesused = 3;
      } else
        nomore = 1;
      break;
    default:
      switch (cmd) {
      case SYSTEM_EXCLUSIVE:            // F0 xx xx ... xx F7
        if (port->midibytecount >= 3) {
          port->wait_for_sysex_end = 1;
          bytesused = 1;
          while ((bytesused < port->midibytecount) && (port->wait_for_sysex_end)) {
            if (GETMIDIBYTE(bytesused) == SYSTEM_EXCLUSIVE_END)
              port->wait_for_sysex_end = 0;
            bytesused++;
          }
        } else
          nomore = 1;
        break;
      case SYSTEM_MTC_QUARTER:
        if (port->midibytecount >= 2) {
#ifdef SUPPORT_MTC
          unsigned char tc, v;

          tc = GETMIDIBYTE(1);
          v = tc &15;
          switch ((tc>>4) & 7) {
          case 0:
            port->mtc_f = (port->mtc_f & 0xf0) | v;
            break;
          case 1:
            port->mtc_f = (port->mtc_f & 0x0f) | (v<<4);
            break;
          case 2:
            port->mtc_s = (port->mtc_s & 0xf0) | v;
            break;
          case 3:
            port->mtc_s = (port->mtc_s & 0x0f) | (v<<4);
            break;
          case 4:
            port->mtc_m = (port->mtc_m & 0xf0) | v;
            break;
          case 5:
            port->mtc_m = (port->mtc_m & 0x0f) | (v<<4);
            break;
          case 6:
            port->mtc_h = (port->mtc_h & 0xf0) | v;
            break;
          case 7:
            port->mtc_h = (port->mtc_h & 0x0f) | ((v &1)<<4);
            port->mtc_format = (v>>1) &3;
            break;
          }
          port->mtc_qf = (port->mtc_qf + 1) &3;
#endif
          bytesused = 2;
        } else
          nomore = 1;
        break;
      case SYSTEM_SONG_SELECT:
        bytesused = 2;
        break;
      case SYSTEM_SONG_POS_PTR:
        if (port->midibytecount >= 3)
          bytesused = 3;
        else
          nomore = 1;
        break;
      case SYSTEM_TUNE_REQUEST:
      case SYSTEM_MIDI_CLOCK:
      case SYSTEM_MIDI_START:
      case SYSTEM_MIDI_CONTINUE:
      case SYSTEM_MIDI_STOP:
      case SYSTEM_ACTIVESENSE:
        bytesused = 1;
        break;
      case SYSTEM_RESET:
        {
          int i;

          for (i = 0; i < 16; i++)   all_notes_off(port, i);
          for (i = 0; i < 16; i++)   reset_controls(port, i);
          port->running_status = 0;
          bytesused = 0;
          nomore = 1;
          port->midibytecount = 0;
          port->midireadpos = port->midiwritepos = 0;
          port->basechannel = 0;
          port->flags.omni = 1;
          port->flags.polyphonic = 1; // monophonic not supported
          port->enabledchannels = 0xffff;
        }
        break;
      case 0xf4: // unused
      case 0xf5: // unused
      case 0xf9: // unused
      case 0xfd: // unused
        bytesused = 1;
        break;
      }
    }
    // remove the bytes that was used for the last command
    if (bytesused) {
      port->midibytecount -= bytesused;
      port->midireadpos += bytesused;
      if (port->midireadpos >= MIDIBUFFERSIZE)
        port->midireadpos -= MIDIBUFFERSIZE;
    } else
      nomore = 1;
  }

  if (irq)  _kernel_irqs_off();
}


// ---------------------------------------------------------------------

void channelpressure(MIDIPORT *port, int c, int pressure) {

  CHANNEL *chn;

  chn = port->channels+c;
// what now??
}


void pitchwheel(MIDIPORT *port, int c, unsigned int bend) {
// change the pitch, bend is from 0..16383 - center is 8192
  CHANNEL *chn;
  int nti;

  chn = port->channels+c;
  bend = bendtable[bend>>6];
  // we're running out of bits for fixed-point math
  for (nti = 0; nti < port->polyphony; nti++)
    if (port->activenotes[nti>>3] & (1<<(nti & 7)))
      if (port->notes[nti].channel == c) {
        // we're running out of bits for fixed-point math
        if ((bend>>3)*(port->notes[nti].base_resamplerate>>3) > 1<<25)
          port->notes[nti].resamplerate = ((port->notes[nti].base_resamplerate>>3)*(bend>>3))>>5;
        else
          port->notes[nti].resamplerate = (port->notes[nti].base_resamplerate*bend)>>11;
      }
}


void programchange(MIDIPORT *port, int c, int program) {
// select a a new program (instrument) for a channel
  CHANNEL *chn;

  chn = port->channels+c;
  chn->program = program & 127;
}


void controlchange(MIDIPORT *port, int c, int control, int value) {
// change some control
  CHANNEL *chn;

  control &= 127;
  value &= 127;

  chn = port->channels+c;
  chn->controllers[control] = value;

  switch (control) {
  case CTRL_VOLUME:           // affects note in realtime
  case CTRL_BALANCE:
  case CTRL_PAN:
  case CTRL_EXPRESSION:
    chn->volume_left  = LEFTVOLUME;
    chn->volume_right = RIGHTVOLUME;
    break;
  case CTRL_ALLCTRLOFF:
    reset_controls(port, c);
    break;
  case CTRL_BANK_SELECT_FINE:
    chn->controllers[CTRL_BANK_SELECT_FINE] = value & 15; // only banks 0..15 are possible
    break;
  case CTRL_ALLSOUNDOFF:
    {
      int i;
      for (i = 0; i < port->polyphony; i++)
        if (port->activenotes[i>>3] & (1<<(i & 7)))
          if (port->notes[i].channel == c)   port->notes[i].volume =  0;
    }
    break;
  case CTRL_ALLNOTESOFF:
    {
      int i;

      for (i = 0; i < port->polyphony; i++)
        if (port->activenotes[i>>3] & (1<<(i & 7)))
          if (port->notes[i].channel == c)   release_note(port, i, 127);
    }
    break;
  case CTRL_OMNI_MODE_ON:
    if (c == port->basechannel) {
      int i;

      for (i = 0; i < 16; i++)      all_notes_off(port, i);
      port->flags.omni = 1;
      if (port->flags.polyphonic)
        port->enabledchannels = 0xffff;         // play on all channels
    }
    break;
  case CTRL_OMNI_MODE_OFF:
    if (c == port->basechannel) {
      int i;

      for (i = 0; i < 16; i++)      all_notes_off(port, i);
      port->flags.omni = 0;
      if (port->flags.polyphonic)
        port->enabledchannels = 1<<port->basechannel;
    }
    break;
  }
}


void aftertouch(MIDIPORT *port, int c, int nt, int pressure) {

  CHANNEL *chn;

  chn = port->channels+c;
// what now??
}



static const unsigned char firstunused[16] = {0, 1, 0, 2, 0, 1, 0, 3,
                                              0, 1, 0, 2, 0, 1, 0, 255};

void noteon(MIDIPORT *port, int c, int nts, int velocity) {

  CHANNEL *chn;
  int i, nti, nt;
  unsigned int attacktime, holdtime, peakvolume, sustaintime, decaytime, decayamount;
  unsigned int samplelength, maxduration, looppoint;
  unsigned char format;
  SAMPLEINDEX samplei;
  signed short *sampledata;

  chn = port->channels+c;
  if (velocity == 0) {
    noteoff(port, c, nts, velocity);
    return;
  }

  // tuning
  nt = nts + port->tuning;
  if (nt < 0)    nt = 0;
  if (nt > 127)  nt = 127;

  // activenotes[] holds 1 bit for each possible
  // active note... find the first clear bit
  nti = -1;
  for (i = 0; (i < port->polyphony>>3) && (nti == -1); i++) {
    int mask;
    mask = port->activenotes[i];
    if (mask != 0xff) {
      nti = 8*i;
      if ((firstunused[mask & 0x0f]) != 255)
        nti += firstunused[mask & 0x0f];
      else
        nti += 4+firstunused[mask >> 4];
    }
  }
  if (nti == -1) {
    // max polyphony reached, find a note to kill
    unsigned int worst_so_far, stay_alive_worthyness;
    // look for a note in RELEASE (or SUSTAIN) state, with as low volume as possible
    worst_so_far = 0x80000000;
    for (i = 0; i < port->polyphony; i++) {
      stay_alive_worthyness = worst_so_far+1;
      if (port->notes[i].state == NOTESTATE_RELEASE)
        stay_alive_worthyness = port->notes[i].volume;
      else if (port->notes[i].state == NOTESTATE_SUSTAIN)
        stay_alive_worthyness = port->notes[i].volume*8; // notes in SUSTAIN must be 18dB lower
                                                    // than notes in RELEASE, otherwise we'll
                                                    // prefer a note in RELEASE
      if (stay_alive_worthyness < worst_so_far) {
        nti = i;
        worst_so_far = stay_alive_worthyness;
      }
    }
    // still didn't find any suitable candidate for killing?? pick a random note...
    if (nti == -1) {
      static unsigned int random_____my_ass;

      nti = (random_____my_ass+157) & (MAXPOLYPHONY-1);
      while (nti >= port->polyphony)   nti >>= 1;
    }
    stop_note(port->notes+nti, port);
  }

  // mark note structure as used, but silent as we haven't filled it in yet
  port->notes[nti].flags = NOTEFLAGS_SILENCE;
  port->activenotes[nti>>3] |= 1<<(nti&7);

  if (chn->drumchannel) {
    DRUM *drum;
    drum = instruments_get_drum(chn->controllers[CTRL_BANK_SELECT_FINE], nt);
    if (!drum)   return;      // wait for NOTEOFF

    samplei = drum->sample;
    port->notes[nti].resamplerate = (drum->frequency<<RESAMPLE_BITS)/port->frequency;
    attacktime = drum->envelope.attacktime;
    holdtime = drum->envelope.holdtime;
    decaytime = drum->envelope.decaytime;
    decayamount = drum->envelope.decayamount;
    sustaintime = drum->envelope.sustaintime;
    maxduration = drum->maxduration;

  } else {
    PATCH *patch;
    unsigned char si;

    patch = instruments_get_patch(chn->controllers[CTRL_BANK_SELECT_FINE], chn->program);
    if (!patch)  return;      // wait for NOTEOFF

    si = nt/12;
    samplei = patch->samples[si];       // sampleindex (handle) used by instruments_...()
    // resamplerates may have more than 16 bits, so you can't always just mul two rates...
    // instead, we make sure that both rates use max 16 bits
    {
      unsigned int rs1, rs2, mul;

      rs1 = (patch->frequency[si]<<RESAMPLE_BITS)/port->frequency;
      rs2 = note2resamplerate[nt];
      mul = 1;
      while (rs1 > 63*1024) { mul *= 2; rs1 /= 2; }
      while (rs2 > 63*1024) { mul *= 2; rs2 /= 2; }
      port->notes[nti].resamplerate = ((rs1*rs2)>>RESAMPLE_BITS)*mul;
    }
    attacktime = patch->envelope.attacktime;
    holdtime = patch->envelope.holdtime;
    decaytime = patch->envelope.decaytime;
    decayamount = patch->envelope.decayamount;
    sustaintime = patch->envelope.sustaintime;
    maxduration = patch->maxduration;
  }
  if (maxduration == 0)
    maxduration = 0x7fffff;   // infinite
  else
    maxduration = (maxduration*port->framespersecond)>>LOG2TIMERESOLUTION; // frames
  if (maxduration < 5)  maxduration = 5;

  if (samplei == defaultsample) {
    if (chn->drumchannel)
      generate_event(HBP10GM_EVENT_MISSING_PATCH, chn->controllers[CTRL_BANK_SELECT_FINE],
                                                  nt,
                                                  1);
    else
      generate_event(HBP10GM_EVENT_MISSING_PATCH, chn->controllers[CTRL_BANK_SELECT_FINE],
                                                  chn->program,
                                                  0);
  }

  // get pointer to structure describing the sample
  port->notes[nti].sampleptr = instruments_get_sample_ptr(samplei);

  if (instruments_use_sample(samplei, 1)) {
    // already in use, so don't play, just wait for NOTEOFF
    return;
  }

  // get information about the sampledata
  if (instruments_get_sample_info(samplei, &sampledata, &samplelength, &looppoint, &format)) {
    // can't play sample!!!
    return;
  }
  if (looppoint == NO_LOOPING) {
    // unsigned int maxdur;
    // HBP - recalculate maxduration
  }
  if (format == SOURCE_VOICE_GENERATOR)
    call_voice_generator(VOICEGENERATOR_NOTE_ON,
                         (VOICEGENERATOR *)sampledata,
                         &chn->controllers,
                         &port->notes[nti].private_word);

  // make sure the resample rate is legal; if the step-size is bigger
  // than the samplelength, detecting end-of-sample (and thus wrap)
  // becomes difficult
  if (port->notes[nti].resamplerate>>RESAMPLE_BITS >= samplelength-1)
    port->notes[nti].resamplerate = (samplelength-1)<<RESAMPLE_BITS;

  // convert from timeunits to no. of frames
  attacktime  = (attacktime*port->framespersecond)>>LOG2TIMERESOLUTION;
  holdtime    = (holdtime*port->framespersecond)>>LOG2TIMERESOLUTION;
  decaytime   = (decaytime*port->framespersecond)>>LOG2TIMERESOLUTION;
  if (sustaintime == SUSTAIN_INFINITE)
    sustaintime = 0x7fffff;
  else
    sustaintime = (sustaintime*port->framespersecond)>>LOG2TIMERESOLUTION;

  // modify attack according to velocity
  // naaahhh, don't bother right now, maybe later

  peakvolume = 1<<24;         // channel volume/pan isn't applied here, but in realtime

  // make sure values are legal
  if (attacktime > maxduration)  attacktime = maxduration;
  if (attacktime < 1)            attacktime = 1;
  if (attacktime+holdtime > maxduration)
    holdtime = maxduration-attacktime;
  if (holdtime < 1)              holdtime = 1;
  if (attacktime+holdtime+decaytime > maxduration)
    holdtime = maxduration-attacktime-holdtime;
  if (decaytime < 1)             decaytime = 1;

  // attack
  port->notes[nti].volume = 0;
  port->notes[nti].volumestep = peakvolume/attacktime;      // volume change per frame
  port->notes[nti].maxframes = attacktime;
  // hold
  port->notes[nti].hold_maxframes = holdtime;
  // decay
  port->notes[nti].decay_volumestep = -((peakvolume-(peakvolume>>8)*decayamount)/decaytime);
  port->notes[nti].decay_maxframes = decaytime;
  peakvolume -= (peakvolume>>8)*decayamount;                // volume after decay

  // sustain
  port->notes[nti].sustain_volumestep = -((peakvolume/2)/sustaintime); // drop 6dB
  port->notes[nti].sustain_maxframes = maxduration-attacktime-decaytime;
  // release
  port->notes[nti].release_volumestep = 0;                   // set when noteoff is received
  port->notes[nti].release_maxframes = 100;

  port->notes[nti].sampledata.data = sampledata;

  port->notes[nti].source_format = format;
  if (format == SOURCE_16BIT || format == SOURCE_8BIT) {
    if (looppoint == NO_LOOPING)   looppoint = samplelength-1; // silent looping
    port->notes[nti].looppoint = looppoint;
  }
  port->notes[nti].length = samplelength;
  port->notes[nti].state = NOTESTATE_ATTACK;

  port->notes[nti].frameposition = 0;
  port->notes[nti].note = nts;                               // note without tuning
  port->notes[nti].sampleposition = 0;
  port->notes[nti].channel = c;
  port->notes[nti].channelp = chn;
  port->notes[nti].base_resamplerate = port->notes[nti].resamplerate;
  port->notes[nti].flags = 0;                                // clear silence-flag
}


void noteoff(MIDIPORT *port, int c, int nt, int velocity) {

  CHANNEL *chn;
  int nti;

  chn = port->channels+c;
  for (nti = 0; nti < port->polyphony; nti++)
    if (port->activenotes[nti>>3] & (1<<(nti & 7)))
      if (port->notes[nti].channel == c)
        if (port->notes[nti].note == nt) {
          release_note(port, nti, velocity);
          return;
        }
}


// ---------------------------------------------------------------------

void all_notes_off(MIDIPORT *port, int c) {
// stops all notes on the specified channel immediately
  int nti;

  for (nti = 0; nti < port->polyphony; nti++)
    if (port->activenotes[nti>>3] & (1<<(nti & 7)))
      if (port->notes[nti].channel == c) {
        port->notes[nti].flags |= NOTEFLAGS_SILENCE;
        release_note(port, nti, 0);
      }
}


void release_note(MIDIPORT *port, int nti, int velocity) {

  if (port->notes[nti].source_format == SOURCE_VOICE_GENERATOR)
    call_voice_generator(VOICEGENERATOR_NOTE_OFF,
                         port->notes[nti].sampledata.voice,
                         NULL,
                         &port->notes[nti].private_word);

  if (port->notes[nti].flags & NOTEFLAGS_SILENCE) {
    stop_note(port->notes+nti, port);

  } else if (port->notes[nti].state > NOTESTATE_RELEASE) {
    unsigned int frames;

    frames = ((60+port->notes[nti].channelp->controllers[CTRL_SNDRELEASETIME]/3)*port->framespersecond)>>LOG2TIMERESOLUTION;
    if (frames < port->notes[nti].release_maxframes)
      port->notes[nti].release_maxframes = frames;
    port->notes[nti].release_volumestep = -(port->notes[nti].volume/port->notes[nti].release_maxframes);
    port->notes[nti].flags |= NOTEFLAGS_FORCE_RELEASE;
    port->notes[nti].channel |= 128;   // make sure it isn't matched in noteoff()
  }
}


void reset_controls(MIDIPORT *port, int c) {

  CHANNEL *chn;

  chn = port->channels+c;
  memset(chn->controllers, 0, 120);
  chn->controllers[CTRL_VOLUME] = 90;
  chn->controllers[CTRL_PAN] = 64;
  chn->controllers[CTRL_BANK_SELECT_FINE] = 0;
  chn->controllers[CTRL_EXPRESSION] = 127;
  chn->volume_left = LEFTVOLUME;
  chn->volume_right = RIGHTVOLUME;
}


int midi_read_samples(signed short *buffer, int samples, int flags, MIDIPORT *port) {

#ifdef SUPPORT_GLOBAL_PORT
  if (!port)  port = &port_a;
#endif
  midi_generate_samples(buffer, samples, flags, port);
  return 0;
}


void kill_notes_using_sample(MIDIPORT *port, SAMPLEDATA *ptr) {

  int nti;

  for (nti = 0; nti < port->polyphony; nti++)
    if (port->activenotes[nti>>3] & (1<<(nti&7)))
      if (port->notes[nti].sampleptr == ptr)
        stop_note(port->notes+nti, port);

}

// ---------------------------------------------------------------------
// configuration etc.

#ifdef SUPPORT_GLOBAL_PORT
_kernel_oserror *midi_control(unsigned int reason, int arg1, int arg2, int *rval) {

  int write;

  if (reason & MIDICTRL_READONLY) {
    write = 0;
    reason &= ~MIDICTRL_READONLY;
  } else
    write = 1;

  switch (reason) {
  case MIDICTRL_FREQ:
    if (rval)  *rval = port_a.frequency;
    if (write) {
      port_a.frequency = arg1;
      if ((port_a.frequency < 5000) || (port_a.frequency > 50000))
        port_a.frequency = 22050;
      port_a.framespersecond = port_a.frequency>>LOG2SAMPLESPERFRAME;
    }
    break;
  case MIDICTRL_INTERPOLATE:
    if (rval)    *rval = port_a.defaults.interpolate;
    if (write)   port_a.defaults.interpolate = !!arg1;
    break;
  case MIDICTRL_POLYPHONY:
    if (rval)  *rval = port_a.polyphony;
    if (write) {
      int old, poly;

      old = port_a.polyphony;
      poly = arg1;
      if (poly < 2)              poly = 2;
      if (poly > MAXPOLYPHONY)   poly = MAXPOLYPHONY;

      if (poly != old) {
        // clear all illegal bits in activenotes[]
        int nti, max;

        if (old > poly) {
          max = old-1;
          nti = poly-1;
        } else {
          nti = old-1;
          max = poly-1;
        }
        while (nti <= max) {
          // NOTICE - could be done nicer by moving the note-structure about - but
          // for now, we just kill the notes
          if (port_a.activenotes[nti>>3] & (1<<(nti&7)))
            stop_note(port_a.notes+nti, &port_a);
          nti++;
        }

        port_a.polyphony = poly;
      }
    }
    break;
  case MIDICTRL_TUNING:
    if (rval)  *rval = port_a.tuning;
    if (write) {
      port_a.tuning = arg1;
      if (port_a.tuning < -24)  port_a.tuning = -24;
      if (port_a.tuning > 24)   port_a.tuning = 24;
    }
    break;
  case MIDICTRL_BASECHANNEL:
    if (rval)    *rval = port_a.basechannel;
    if (write) {
      port_a.basechannel = arg1 & 15;
      if ((!port_a.flags.omni) && (port_a.flags.polyphonic))
        port_a.enabledchannels = 1<<port_a.basechannel;
    }
    break;
  case MIDICTRL_VOLUME:
    if (rval)  *rval = port_a.mastervolume;
    if (write) {
      port_a.mastervolume = arg1;
      if (port_a.mastervolume > 4096)  port_a.mastervolume = 4096;
    }
    break;
  }

  return NULL;
}


_kernel_oserror *midi_control_channel(unsigned int reason, int arg1, int arg2, int *rval) {

  CHANNEL *chn;
  int write, c;
  MIDIPORT *port;

  port = &port_a;

  write = 1;
  if (reason & MIDICTRL_READONLY) {
    write = 0;
    reason &= ~MIDICTRL_READONLY;
  }

  c = (reason>>8) & 15;
  chn = port->channels+c;

  reason &= 255;

  switch (reason) {
  case MIDICTRL_CHN_IS_DRUM:
    if (rval)    *rval = chn->drumchannel;
    if (write)   chn->drumchannel = arg1 &1;
    break;
  case MIDICTRL_CHN_PAN:
    if (rval)    *rval = chn->controllers[CTRL_PAN];
    if (write)   chn->controllers[CTRL_PAN] = arg1 &127;
    break;
  case MIDICTRL_CHN_VOLUME:
    if (rval)    *rval = chn->controllers[CTRL_VOLUME];
    if (write)   chn->controllers[CTRL_VOLUME] = arg1 &127;
    break;
  case MIDICTRL_CHN_BANK:
    if (rval)    *rval = chn->controllers[CTRL_BANK_SELECT_FINE];
    if (write)   chn->controllers[CTRL_BANK_SELECT_FINE] = arg1 &15;
    break;
  case MIDICTRL_CHN_PROGRAM:
    if (rval)    *rval = chn->program;
    if (write)   chn->program = arg1 &127;
    break;
  case MIDICTRL_READALL_CHN_IS_DRUM:
    if (rval) {
      int c, drums;
      drums = 0;
      for (c = 0; c < 16; c++)
        if (port->channels[c].drumchannel)  drums |= 1<<c;
      *rval = drums;
    }
    break;
  case MIDICTRL_READALL_CHN_PAN:
    {
      int c;
      unsigned char *channeldata;

      channeldata = (unsigned char *)arg1;
      for (c = 0; c < 16; c++)
        channeldata[c] = port->channels[c].controllers[CTRL_PAN];
    }
    break;
  case MIDICTRL_READALL_CHN_VOLUME:
    {
      int c;
      unsigned char *channeldata;

      channeldata = (unsigned char *)arg1;
      for (c = 0; c < 16; c++)
        channeldata[c] = port->channels[c].controllers[CTRL_VOLUME];
    }
    break;
  case MIDICTRL_READALL_CHN_BANK:
    {
      int c;
      unsigned char *channeldata;

      channeldata = (unsigned char *)arg1;
      for (c = 0; c < 16; c++)
        channeldata[c] = port->channels[c].controllers[CTRL_BANK_SELECT_FINE];
    }
    break;
  case MIDICTRL_READALL_CHN_PROGRAM:
    {
      int c;
      unsigned char *channeldata;

      channeldata = (unsigned char *)arg1;
      for (c = 0; c < 16; c++)  channeldata[c] = port->channels[c].program;
    }
    break;
  }

  return NULL;
}


_kernel_oserror *midi_control_patches(unsigned int reason, int arg1, int arg2, int *rval) {

  unsigned int banki, programi, drum;
  int write;

  write = 1;
  if (reason & MIDICTRL_READONLY) {
    write = 0;
    reason &= ~MIDICTRL_READONLY;
  }

  banki = (reason>>8) &15;
  drum = !!(reason & (1<<12));
  programi = (reason>>16) &127;

  switch (reason & 0xff) {
  case MIDICTRL_ATTACKTIME:
  case MIDICTRL_HOLDTIME:
  case MIDICTRL_DECAYTIME:
  case MIDICTRL_DECAYAMOUNT:
  case MIDICTRL_SUSTAINTIME:
  case MIDICTRL_MAXDURATION:
  case MIDICTRL_LOOP:
  case MIDICTRL_PATCH_FREQ:
    break;
  }

  return NULL;
}


_kernel_oserror *midi_info(unsigned int reason, int arg1, int *rval) {

  MIDIPORT *port;

  port = &port_a;

  switch (reason & 0xff) {
  case 0:
    if (reason & 0xffffff00)   return &err_reserved_bits_set;
    {
      int nti, n;

      n = 0;
      for (nti = 0; nti < port->polyphony; nti++)
        if (port->activenotes[nti>>3] & (1<<(nti &3)))  n++;
      *rval = n;
    }
    break;
  case 1:
    if (reason & 0xffffff00)   return &err_reserved_bits_set;
    {
      int nti, vol, vol2, l;

      vol = 0;
      for (nti = 0; nti < port->polyphony; nti++)
        if (port->activenotes[nti>>3] & (1<<(nti &3)))
          vol += log2lin_volume[port->notes[nti].volume>>14];  // lin vol 0..65536
      // apply master volume
      vol = ((vol>>4)*port->mastervolume)>>13;
      // now convert to something vaguely resembling dB...
      if (vol > (1<<18))  vol = 1<<18;                   // upper limit
      vol |= 1;                                          // makes life easier...
      l = 13;
      while ((vol & (1<<l)) == 0)  l--;                  // find highest bit that's set
      vol2 = 19*l;                                       // scale to sensible range
      if (l > 0)
        if (vol & (1<<(l-1)))  vol2 += 9*l;              // 2nd highest bit
      if (l > 1)
        if (vol & (1<<(l-2)))  vol2 += 4*l;              // 3rd highest bit
      if (vol2 > 255)  vol2 = 255;
      *rval = vol2 | (vol2<<8) | (vol2<<16);             // combined, left, right
    }
    break;
  default:
    return &err_bad_reason_code;
    break;
  }

  return NULL;
}
#endif

_kernel_oserror *midi_monitor(unsigned int reason, int arg1, int arg2, int *rval) {

  switch (reason & 0xff) {
#ifdef SUPPORT_MONITOR
  case 0:
    monitor_buffer = NULL;
    break;
  case 1:
    monitor_buffer_size = arg2;
    monitor_buffer_write = 0;
    monitor_buffer = (unsigned char *)arg1;
    break;
  case 2:
    *rval = (int)monitor_buffer;
    break;
  case 3:
    *rval = monitor_buffer_write;
    break;
#endif
  default:
    return &err_bad_reason_code;
    break;
  }

  return NULL;
}
