// 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>

// 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;

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

class CThread
{
  public:
    bool terminated;
		pthread_t handle;
    CThread();
    ~CThread();
		void start();
    virtual void execute() {};
};

CThread :: CThread()
{
	terminated = false;
//  pthread_create(&handle, 0, &threadProc, this);  
}

CThread :: ~CThread()
{
  terminated = true;
	pthread_join(handle, 0);
}

void* threadProc(void * param)
{
  CThread* thread = (CThread*)param;
  thread->execute();
	return 0;
}

void CThread :: start()
{	
	pthread_create(&handle, 0, &threadProc, this); 
}

struct CMIDIData
{
	int data;					// midi bytes
	char port;				// midi port index
	void* buffer;			// pointer to buffer (sysex etc)
};

class CMIDIThread : public CThread
{
  public:
    int port;
		snd_rawmidi_t *handle;
    CMIDIThread(int f, int dev);
    void execute();
};

int midiDevices = 0;
unsigned int frames = 1024;
unsigned int rate = 44100;

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;
    CMIDIThread* thread;	  
  	int index;
    CMIDIDev();
    ~CMIDIDev();
    void enable(int enable);
};

CMIDIDev* mdevs[maxMIDIDevs];

// midi buffer
const int MIDI_BUFFER_SIZE = 512;
CMIDIData midiData[MIDI_BUFFER_SIZE];
int midiDataCount = 0;
int midiBufferRead = 0, midiBufferWrite = 0;	// read write indexes for mnidi buffer

// jack process
jack_default_audio_sample_t* outputs[maxChs], *inputs[maxChs];

CMIDIData tempBuffer[MIDI_BUFFER_SIZE];
int process_jack (jack_nframes_t samples, void *arg)
{

	// todo: store pos and state
	// so can be called from dispatcher (AUDIO_POS, AUDIO_STATE...)
	//jack_transport_state_t ts = jack_transport_query(client, 0);
	
	// todo: support play/stop
	//if (ts == JackTransportRolling)
	{
		
		// intput & output buffers
		int used = 0, i;
		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		 1234
		int tempBufferCount = midiBufferWrite - midiBufferRead;
		if (tempBufferCount < 0)
			tempBufferCount += MIDI_BUFFER_SIZE;
		if (tempBufferCount > 0)
		{			
			for (int i = 0; i < tempBufferCount; i++)
			{
	      tempBuffer[i].data = midiData[midiBufferRead].data;
	      tempBuffer[i].port = midiData[midiBufferRead].port;
	      midiBufferRead = (midiBufferRead + 1) & (MIDI_BUFFER_SIZE-1);
			}				
		}

		// 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 ("energyXT", 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");
		}		

		// audio channels
		else {
			
			int i;
			const char **ports;
			
			// inputs			
			ports = jack_get_ports (client, 0, 0,	JackPortIsPhysical | JackPortIsOutput);
			if (ports)
			{			
				i = -1;
				while (ports[++i])
				{
					strcpy(ain[i].id, ports[i]);
					aincount++;				
						
					// enabled					
					ain[i].active = 0;
					if (ain[i].enabled) 
					{					
						// register and connect
						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);
			
			// outputs		
			ports = jack_get_ports (client, 0, 0,	JackPortIsPhysical | JackPortIsInput);
			if (ports)
			{			
				i = -1;
				while (ports[++i])
				{
					strcpy(aout[i].id, ports[i]);
					aoutcount++;				
						
					// enabled 
					aout[i].active = 0;
					if (aout[i].enabled)
					{
						// register and connect
						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()
{

	int i, dev, inputs, outputs;

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

  midiDevices = 0;

	// ALSA rawmidi	
	
  int card;
  snd_ctl_t *ctl;
  snd_ctl_card_info_t *cardInfo;
	snd_rawmidi_info_t *rawmidiInfo;
        
  snd_ctl_card_info_alloca( &cardInfo );
	snd_rawmidi_info_alloca(&rawmidiInfo);
  
  card = -1;
  while (snd_card_next( &card ) == 0 && card >= 0)
  {
  
  	char device[255];
  	sprintf(device, "hw:%d", card);
        
		if (snd_ctl_open( &ctl, device, 0 ) >= 0)
		{

			// card info
			snd_ctl_card_info(ctl, cardInfo);

			// raw midi devices
			outputs = 0, inputs = 0;
			dev = -1;
						
			while (snd_ctl_rawmidi_next_device(ctl, &dev) >= 0 && dev >= 0) 
			{
			
				snd_rawmidi_info_set_device(rawmidiInfo, dev);
				snd_rawmidi_info_set_subdevice(rawmidiInfo, 0);
				
				// midi input 				
				snd_rawmidi_info_set_stream(rawmidiInfo, SND_RAWMIDI_STREAM_INPUT);
				if (snd_ctl_rawmidi_info(ctl, rawmidiInfo) >= 0)
					inputs++;
					
				// todo: midi out			
					
			}
			
			// add midi card			
			if (inputs) 
			{			
		   	mdevs[midiDevices] = new CMIDIDev();
		   	mdevs[midiDevices]->index = card;
		   	strcpy(mdevs[midiDevices]->device, device);
		   	strcpy(mdevs[midiDevices]->id, snd_ctl_card_info_get_name(cardInfo));
		   	midiDevices++;
			}
     
			snd_ctl_close( ctl );
		
		}
    
	}
	
}

// deinit

void deinit()
{
	// delete midi devices
	for (int i = 0; i < midiDevices; i++)
		delete mdevs[i];	
	midiDevices = 0;	
}

// 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: 	 
			// todo: get jack server/version name?
	    strcpy((char*)value2, "JACK Audio");    
	    break;

	  // open device	  
	  case STREAM_OPEN:
	  	if (value1 >= 0)
	  	{ 
		  	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()
{
	handle = 0;
  enabled = 0;  
	thread = 0;
}

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

// midi dev enable

void CMIDIDev :: enable(int enable)
{ 

  if (enabled != enable) 
  { 
  
    enabled = enable;
    
    // enable    
    if (enabled)
    {
			if (snd_rawmidi_open(&handle, NULL, device, SND_RAWMIDI_NONBLOCK)) 
				printf("open device failed\n");
      else thread = new CMIDIThread((int)handle, index);
			      
    }
    
    // disable    
    else { 

	    // kill thread    
    	if (thread) 
    	{
	      delete thread;
	    	thread = 0;
	    }
	    
	    // close device			
			if (handle) 
			{
				snd_rawmidi_close(handle);	
				handle = 0;
			}			
			    	
 	  }
 	  
 	}

}

CMIDIThread :: CMIDIThread(int f, int dev)
{	
	port = dev;
	handle = (snd_rawmidi_t *)f;
	start();
};

// handle midi

void handleMIDI(unsigned char *buffer, int count, int port)
{

 	// todo: check for running status
 	
  int data, pos = 0;  
  while (pos < count)
  {	
  	data = 0;    	
    switch( buffer[pos] & 0xF0 ) 
    {    		
      case 0x80:	// note off
      case 0x90:	// note on               
      case 0xA0:	// aftertouch
      case 0xB0:	// control change
      case 0xE0:	// pitch wheel
        data = buffer[pos] + (buffer[pos+1] << 8) + (buffer[pos+2] << 16);
        pos += 3;
        break;
        
      case 0xC0:	// program change
      case 0xD0:	// channel pressure        
        data = buffer[pos] + (buffer[pos+1] << 8);
        pos += 2;
        break;
          
      default:
      	count = 0;	// break loop
    }

		if (data) 
		{		
      midiData[midiBufferWrite].data = data;
      midiData[midiBufferWrite].port = port;
      midiBufferWrite = (midiBufferWrite + 1) & (MIDI_BUFFER_SIZE-1);
    }
    
	}
     
}

// midi thread

void CMIDIThread :: execute()
{
  int count, ms = 5;
  unsigned char buffer[128];
  while (!terminated)
  {
    count = snd_rawmidi_read(handle, buffer, sizeof(buffer));
		handleMIDI(buffer, count, port);
		usleep(1000 * ms);
  }  
}

