// energyXT - JACK audio interface
// author: jorgen aase - www.energy-xt.com
// compile: g++ -shared -lasound -ljack jack.cpp -o /_path_to_energyXT2_/libaam.so

#include <alsa/asoundlib.h>
#include <jack/jack.h>

#define NUM_MIDI_INS 1 

// libaam interface

int libaam (int index, int value1, int value2) asm ("libaam");

const int STREAM_INIT = 0;         
const int STREAM_OPEN = 1;         
const int STREAM_CLOSE = 2;       
//const int STREAM_PLAY = 3;         
//const int STREAM_STOP = 4; 
const int STREAM_EXIT = 5;         
const int STREAM_ABOUT = 6;       
const int STREAM_AUDIO_COUNT = 7;
const int STREAM_AUDIO_NAME = 8;
const int STREAM_FRAMES = 9;       
const int STREAM_RATE = 10;       
const int STREAM_LATENCY = 17;     
const int STREAM_RELOAD = 18;                
const int MIDI_IN_COUNT = 30;
const int MIDI_IN_NAME = 31;
const int MIDI_IN_ENABLE = 32;    
const int MIDI_IN_ENABLED = 33;    
const int AUDIO_IN_COUNT = 40;
const int AUDIO_IN_NAME = 41;
const int AUDIO_IN_ENABLE = 42;
const int AUDIO_IN_ENABLED = 43;
const int AUDIO_OUT_COUNT = 50;
const int AUDIO_OUT_NAME = 51;
const int AUDIO_OUT_ENABLE = 52;
const int AUDIO_OUT_ENABLED = 53;

// create a new alsa sequencer client

snd_seq_t *aseq_client;

snd_seq_t *open_client()
{
        snd_seq_t *handle;
        int err;
        err = snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
        if (err < 0)
                return NULL;
        snd_seq_set_client_name(handle, "EnergyXT2");
        return handle;
}

// create a new port; return the port id
// port will be read/writable and accept the read/write-subscription.
int new_in_port(snd_seq_t *handle, char *name)
{
        return snd_seq_create_simple_port(handle, name,
                        SND_SEQ_PORT_CAP_WRITE
                        |SND_SEQ_PORT_CAP_SUBS_WRITE,
                        SND_SEQ_PORT_TYPE_MIDI_GENERIC);
}

// create a new port; return the port id
// port will be read/writable and accept the read/write-subscription.
int new_out_port(snd_seq_t *handle, char *name)
{
        return snd_seq_create_simple_port(handle, name,
                        SND_SEQ_PORT_CAP_READ
                        |SND_SEQ_PORT_CAP_SUBS_READ,
                        SND_SEQ_PORT_TYPE_MIDI_GENERIC);
}

typedef int (CProcess) (float** inputs, float** outputs, int numIns, int numOuts,
    int frames, void* midiData, int midiDataCount);
CProcess* process = 0;

struct MidiThread {
  bool terminated;
  pthread_t handle;
} midithread;

void *execute(void *);

void start_midi_thread()
{
  midithread.terminated = false;
  pthread_create(&midithread.handle, 0, execute, 0); 
}

void stop_midi_thread()
{
  midithread.terminated = true;
  pthread_join(midithread.handle, 0);
}

struct CMIDIData
{
    int data;                // midi bytes
    char port;                // midi port index
    void* buffer;            // pointer to buffer (sysex etc)
};

int midiDevices = 0;

unsigned int frames = 1024;
unsigned int rate = 44100;
pthread_mutex_t mutex_midiin = PTHREAD_MUTEX_INITIALIZER;

const int maxChs = 32;

// jack audio

struct CJackPort
{
    char id[255];
    int enabled, active;
    jack_port_t *port;
};

int aoutcount = 0, aincount = 0, ainused = 0, aoutused = 0;
jack_client_t *client = 0;
CJackPort ain[maxChs], aout[maxChs];

// midi device

const int maxMIDIDevs = 64;

class CMIDIDev
{
  public:
    snd_rawmidi_t *handle;
    char device[255], id[255];
    int enabled;
    int index;
    CMIDIDev();
    ~CMIDIDev();
    void enable(int enable);
};

CMIDIDev* mdevs[maxMIDIDevs];

// midi data buffer

CMIDIData midiData[128];
int midiDataCount = 0;

// jack process

jack_default_audio_sample_t* outputs[maxChs], *inputs[maxChs];    

int process_jack (jack_nframes_t samples, void *arg)
{

    int i, tempBufferCount, used;
    CMIDIData tempBuffer[128];
    jack_transport_state_t ts = jack_transport_query(client, 0);
    
//    if (ts == JackTransportRolling)
//    {
        
        // intput & output buffers
        
        used = 0;
        for (i = 0; i < maxChs, used < ainused; i++)
        {
            if (ain[i].active)
                inputs[used++] = (jack_default_audio_sample_t*) jack_port_get_buffer (ain[i].port, samples);            
        }
        used = 0;
        for (i = 0; i < maxChs, used < aoutused; i++)
        {
            if (aout[i].active)
                outputs[used++] = (jack_default_audio_sample_t*) jack_port_get_buffer (aout[i].port, samples);            
        }
        
        // midi in
        
        pthread_mutex_lock(&mutex_midiin);            
        memcpy(tempBuffer, midiData, midiDataCount * sizeof(CMIDIData));
        tempBufferCount = midiDataCount;
        midiDataCount = 0;
        pthread_mutex_unlock(&mutex_midiin);     

        // process
        
        process(inputs, outputs, ainused, aoutused, samples, &tempBuffer, tempBufferCount);        
        
        // todo: midi out
        
//    }
        
    return 0;     
    
}

void jack_shutdown (void *arg)
{
    // todo
}


// jack open device

void openJack()
{

    char str[255];

    jack_status_t status;
    jack_options_t options = JackNullOption;
    
    ainused = 0;
    aoutused = 0;
    
    // open client
    
    aincount = 0;
    aoutcount = 0;

    client = jack_client_open ("energyXT2", options, &status, 0);
    if (client)
    {

        // setup process

        jack_set_process_callback(client, process_jack, 0);
        jack_on_shutdown(client, jack_shutdown, 0);

        // get sample rate

        rate = jack_get_sample_rate (client);        
        
        // set/get buffer frames

        frames = jack_get_buffer_size (client);        

        // activate client
        
        if (jack_activate (client))
        {
            jack_client_close (client);                                
            client = 0;                
            printf("jack_activate failed\n");
        }        

        // inputs and outputs    
            
        else {
            
            int i;
            const char **ports;
            
            // audio inputs
            
            ports = jack_get_ports (client, 0, 0,    JackPortIsPhysical | JackPortIsOutput);
            if (ports)
            {            
                i = -1;
                while (ports[++i])
                {
                    strcpy(ain[i].id, ports[i]);
                    aincount++;                
                        
                    // connect
                        
                    ain[i].active = 0;
                    if (ain[i].enabled)
                    {                    
                        sprintf(str, "in%d", ainused);
                        ain[i].port = jack_port_register (client, str, JACK_DEFAULT_AUDIO_TYPE,
                            JackPortIsInput, 0);                            
                        if (ain[i].port && jack_connect (client, ain[i].id, jack_port_name (ain[i].port)) == 0)
                        {
                            ainused++;
                            ain[i].active = 1;
                        }
                            
                    }
                    
                }
            }
            
            free (ports);
            
            // audio outputs            
            
            ports = jack_get_ports (client, 0, 0,    JackPortIsPhysical | JackPortIsInput);
            if (ports)
            {            
                i = -1;
                while (ports[++i])
                {
                    strcpy(aout[i].id, ports[i]);
                    aoutcount++;                
                        
                    // connect
                        
                    aout[i].active = 0;
                    if (aout[i].enabled)
                    {
                        sprintf(str, "out%d", aoutused);
                        aout[i].port = jack_port_register (client, str, JACK_DEFAULT_AUDIO_TYPE,
                          JackPortIsOutput, 0);                            
                        if (aout[i].port && jack_connect (client, jack_port_name (aout[i].port), aout[i].id) == 0)
                        {
                            aoutused++;
                            aout[i].active = 1;
                        }
                    }
                    
                }
            }

            free (ports);
            
        }
        
    }    
    
    // start jack transporter
    // todo: check if already started
    // todo: dont start automatically?
    
//    if (client)
//        jack_transport_start(client);

}

// jack close device

void closeJack()
{
    if (client)
    {
        jack_client_close (client);                                
        client = 0;
    }
}

// init

void init()
{

    char id[16];
    int i;
    int in;

    // disable all channels (by default)
    
    for (i = 0; i < maxChs; i++)
    {
          ain[i].enabled = false;
      aout[i].enabled = false;
    }

    midiDevices = 0;

    // ALSA midi    
    
    aseq_client = open_client();

    for (i=0; i<NUM_MIDI_INS; i++) {

        sprintf(id, "aseq %d", i);
        if ((in = new_in_port(aseq_client, id)) != -1) {

            mdevs[i] = new CMIDIDev();
            mdevs[i]->index = in;
            strcpy(mdevs[i]->device, "alsaseq");
            strcpy(mdevs[i]->id, id);
            midiDevices++;
   
        } // if

    } // for

    if (midiDevices)
        start_midi_thread();

} // init

// deinit

void deinit()
{
    stop_midi_thread();
    pthread_mutex_destroy(&mutex_midiin);
}

// dispatcher

int libaam (int index, int value1, int value2)
{
  int i, result = -1;

  switch (index)
  {
        
    // init library
   
    case STREAM_INIT:   
        init();               
        process = (CProcess*)value1;        
        break;
       
      // exit library
                
      case STREAM_EXIT:
          deinit();
          break;
        
      // device count
     
      case STREAM_AUDIO_COUNT:
        result = 1; // jack only
        break;
                
      // device name
      
      case STREAM_AUDIO_NAME:     
        strcpy((char*)value2, "JACK Audio");   
        break;

      // open device
     
      case STREAM_OPEN:
          openJack();
          result = 1;
        break;

      // close device
     
      case STREAM_CLOSE:
          closeJack();
        result = 1;
        break;
        
      // get/set frames
     
      case STREAM_FRAMES:
        if (value1 > 0)
          frames = value1;
        result = frames;
        break;
        
      // get/set rate
     
      case STREAM_RATE:
          if (value1 == 0)
                *(float*)value2 = rate;
        else if (value1 > 0)
          rate = value1;
        break;
        
      // audio in count
     
      case AUDIO_IN_COUNT:
          return aincount;
        break;
                    
      // audio out enable
     
      case AUDIO_IN_ENABLE:
            if (value1 >= 0 && value1 < maxChs)
                ain[value1].enabled = value2;
        break;

      // audio in enabled
     
      case AUDIO_IN_ENABLED:
            if (value1 >= 0 && value1 < maxChs)
                result = ain[value1].enabled;
        break;
        
      // audio in name
     
      case AUDIO_IN_NAME:
            if (value1 >= 0 && value1 < aincount)
              strcpy((char*)value2, ain[value1].id);
        break;
            
      // audio out count
     
      case AUDIO_OUT_COUNT:
          return aoutcount;
        break;
            
      // audio out enable
     
      case AUDIO_OUT_ENABLE:
            if (value1 >= 0 && value1 < maxChs)
                aout[value1].enabled = value2;
        break;

      // audio out enabled
     
      case AUDIO_OUT_ENABLED:
            if (value1 >= 0 && value1 < maxChs)
                result = aout[value1].enabled;
        break;
                    
      // audio out name
     
      case AUDIO_OUT_NAME:
            if (value1 >= 0 && value1 < aoutcount)
              strcpy((char*)value2, aout[value1].id);
        break;
        
        // midi in count
        
      case MIDI_IN_COUNT:
        return midiDevices;
        break;

        // midi in name

      case MIDI_IN_NAME:
          if (value1 >= 0 && value1 < midiDevices)
            strcpy((char*)value2, mdevs[value1]->id);
        break;

        // enable midi in

      case MIDI_IN_ENABLE:
          if (value1 >= 0 && value1 < midiDevices)
            mdevs[value1]->enable(value2);
        break;

        // is midi in enabled?

      case MIDI_IN_ENABLED:
          if (value1 >= 0 && value1 < midiDevices)
            return mdevs[value1]->enabled;
      break;

  }
 
  return result;
 
}

CMIDIDev :: CMIDIDev()
{
        enabled = 0; 
}

CMIDIDev::~CMIDIDev()
{
    enable(0);
}

// midi dev enable

void CMIDIDev :: enable(int enable)
{
  enabled = enable;
}

//CMIDIThread :: CMIDIThread()
//{    
//};

// handle midi

void handleMIDI(snd_seq_event_t *ev)
{

    int i = 0;
    int data = 0;

    switch (ev->type) {

    case SND_SEQ_EVENT_NOTEON:
      data = (0x90 | ev->data.note.channel) |
              (ev->data.note.note << 8) |
              (ev->data.note.velocity << 16);
      break;
    case SND_SEQ_EVENT_NOTEOFF:
      data = (0x80 | ev->data.note.channel) |
              (ev->data.note.note << 8) |
              (ev->data.note.off_velocity << 16);
      break;
    case SND_SEQ_EVENT_KEYPRESS:
      data = (0xA0 | ev->data.note.channel) |
              (ev->data.note.note << 8) |
              (ev->data.note.velocity << 16);
      break;
    case SND_SEQ_EVENT_CONTROLLER:
      data = (0xB0 | ev->data.control.channel) |
              (ev->data.control.param << 8) |
              (ev->data.control.value << 16);
      break;
    case SND_SEQ_EVENT_PGMCHANGE:
      data = (0xC0 | ev->data.control.channel) |
              (ev->data.control.value << 8);
      break;
    case SND_SEQ_EVENT_CHANPRESS:
      data = (0xD0 | ev->data.control.channel) |
              (ev->data.control.value << 8);
      break;
    case SND_SEQ_EVENT_PITCHBEND:
      ev->data.control.value += 8192; 
      data = (0xE0 | ev->data.control.channel) |
              ((ev->data.control.value & 0x7f) << 8)|
              (((ev->data.control.value >> 7) & 0x7f) << 16);
      break;
    case SND_SEQ_EVENT_SONGPOS:
      data = 0xF2 |
             ((ev->data.control.value & 0x7f) << 8)|
             (((ev->data.control.value >> 7) & 0x7f) << 16);
      break;
    case SND_SEQ_EVENT_START:
      data = 0xFA;
      break;
    case SND_SEQ_EVENT_STOP:
      data = 0xFC;
      break;
    case SND_SEQ_EVENT_CONTINUE:
      data = 0xFB;
      break;

    default:
      break;

    } // switch (ev)->type

    if (data)
    {        
    pthread_mutex_lock(&mutex_midiin);             
        midiData[midiDataCount].data = data;
        midiData[midiDataCount].port = ev->dest.port;
        midiDataCount++;           
    pthread_mutex_unlock(&mutex_midiin);       
    }
   
} // handleMIDI

// midi thread

void *execute(void *param)
{
  int count, ms = 1;
  snd_seq_event_t *ev = 0;

  while (!midithread.terminated)
  {
    count = snd_seq_event_input_pending(aseq_client, 1);

    for (int i=0; i<count; i++) {

        snd_seq_event_input(aseq_client, &ev);
        if (mdevs[ev->dest.port]->enabled)
          handleMIDI(ev);

    } // while input pending

    usleep(1000 * ms);

  } // while not terminated

} // CMIDIThread :: execute()

